select —— Liste déroulante
La liste déroulante est l’implémentation la plus simple du modèle « déclencheur + liste flottante ». core/select gère la recherche d’options, la génération déterministe des id d’option, ainsi que les trois paquets d’attributs d’accessibilité du pattern combobox de l’APG. Le calcul du positionnement n’est pas ici — les coordonnées de la couche flottante sont prises en charge par core/shared/popper (voir popper).
Quand l’utiliser
Section intitulée « Quand l’utiliser »- Chaque framework implémente
LhSelect. - Les scénarios à sélection unique ; pour la sélection multiple, utilisez le sélecteur en cascade ou un groupe de cases à cocher.
- À ne pas utiliser pour : les options nécessitant une hiérarchie en arbre (
cascader), ou un nombre d’options de plusieurs centaines (envisagez la virtualisation avecvirtual-list).
import { findSelectOption, getNextEnabledIndex, getSelectOptionId, getSelectTriggerAria, getSelectListboxAria, getSelectOptionAria,} from '@lh-ui/share/core/select'import type { LhSelectOption, LhSelectState } from '@lh-ui/share/core/select'Intentions de conception
Section intitulée « Intentions de conception »Le « déterminisme » de getSelectOptionId(listboxId, index) est la condition préalable au fonctionnement du pattern combobox de l’APG : le déclencheur référence l’élément actuellement mis en surbrillance via aria-activedescendant, et les deux doivent pointer vers le même identifiant DOM. Plutôt que de laisser la couche UI générer les id au hasard puis chercher à les synchroniser, la couche core impose ${listboxId}-option-${index}, les deux côtés appliquant cette même règle.
Ses gardes sont tout aussi importantes : listboxId vide, index non entier, ou index < 0 (-1 signifie aucune surbrillance) renvoient undefined — les propriétés undefined sont ignorées par le v-bind du framework, et ne produisent donc pas d’aria-activedescendant pointant vers un élément inexistant.
Tableau des fonctions
Section intitulée « Tableau des fonctions »| Fonction | Signature | Retour | Rôle |
|---|---|---|---|
findSelectOption |
(options: LhSelectOption[], value: unknown) => LhSelectOption | undefined |
option | correspondance exacte de value par Object.is |
getNextEnabledIndex |
(options: LhSelectOption[], currentIndex: number, direction: 1 | -1) => number |
index | @deprecated, délègue à shared/keyboard.getNextListIndex |
getSelectOptionId |
(listboxId: string | undefined, index: number) => string | undefined |
id | concaténation déterministe, entrées illégales → undefined |
getSelectTriggerAria |
(options: { visible: boolean; listboxId?: string; highlightedIndex: number }) => LhAriaAttrs |
paquet d’attributs aria | déclencheur combobox |
getSelectListboxAria |
(id?: string) => LhAriaAttrs |
paquet d’attributs aria | conteneur listbox |
getSelectOptionAria |
(options: { listboxId?: string; index: number; selected: boolean; disabled?: boolean }) => LhAriaAttrs |
paquet d’attributs aria | option unique |
Définitions de types
Section intitulée « Définitions de types »export interface LhSelectOption { label: string value: unknown disabled?: boolean}
export interface LhSelectState { options: LhSelectOption[] highlightedIndex: number}Accessibilité
Section intitulée « Accessibilité »Les trois paquets d’attributs constituent le pattern combobox complet :
// Déclencheur : aria-haspopup="listbox" + aria-expanded + aria-controlsgetSelectTriggerAria({ visible: true, listboxId: 'my-listbox', highlightedIndex: 2 })// { role: 'combobox', 'aria-expanded': true, 'aria-controls': 'my-listbox',// 'aria-haspopup': 'listbox', 'aria-activedescendant': 'my-listbox-option-2' }
// Liste flottante : role="listbox" + id (en correspondance avec le aria-controls du déclencheur)getSelectListboxAria('my-listbox') // { role: 'listbox', id: 'my-listbox' }
// Option : role="option" + id + aria-selectedgetSelectOptionAria({ listboxId: 'my-listbox', index: 2, selected: true })// { role: 'option', id: 'my-listbox-option-2', 'aria-selected': true }La boucle complète de l’interaction clavier (mise en œuvre par la couche UI, les algorithmes fournis par core) : ArrowDown/Up déplace highlightedIndex (via shared/keyboard.getNextListIndex, avec circulation entre le premier et le dernier élément et saut des éléments désactivés), Enter sélectionne, Esc ferme. La surbrillance visuelle doit avoir la même source que highlightedIndex, sinon on aboutit à « aria pointe vers A, les yeux voient B ».
Exemples d’utilisation
Section intitulée « Exemples d’utilisation »import { findSelectOption, getSelectOptionId, getSelectTriggerAria } from '@lh-ui/share/core/select'
const options = [ { label: 'Porcelaine bleue et blanche', value: 'cerblue' }, { label: 'Encre de Chine', value: 'shuimo', disabled: true },]findSelectOption(options, 'cerblue') // { label: 'Porcelaine bleue et blanche', value: 'cerblue' }findSelectOption(options, 'nope') // undefinedgetSelectOptionId('lb', 0) // 'lb-option-0'getSelectOptionId('lb', -1) // undefined (aucune surbrillance)getSelectOptionId(undefined, 0) // undefined<!-- Vue3 --><div :class="['lh-select', `lh-select--${size}`, { 'lh-select--open': visible, 'lh-select--disabled': disabled }]"> <button class="lh-select__trigger" v-bind="getSelectTriggerAria({ visible, listboxId, highlightedIndex })" @click="toggle" > <span class="lh-select__value" :class="{ 'lh-select__value--placeholder': !selected }"> {{ selected ? selected.label : placeholder }} </span> <LhIcon class="lh-select__arrow" name="more" :size="12" /> </button> <div v-if="visible" :id="listboxId" class="lh-select__dropdown" v-bind="getSelectListboxAria(listboxId)"> <div v-for="(opt, i) in options" :key="i" class="lh-select__option" :class="{ 'lh-select__option--highlighted': i === highlightedIndex, 'lh-select__option--selected': i === selectedIdx, 'lh-select__option--disabled': opt.disabled }" v-bind="getSelectOptionAria({ listboxId, index: i, selected: i === selectedIdx, disabled: opt.disabled })" @click="choose(opt)" >{{ opt.label }}</div> </div></div>// React : l'état visible + un useRef pour conserver listboxId (useId génère un id stable)// uniapp : la liste flottante est positionnée de manière absolue par un view ;// aria-activedescendant n'est pas supporté sur les autres cibles que h5,// la navigation clavier dégénère en un simple déplacement du focus, et le lecteur// d'écran s'appuie sur l'événement focus pour l'annonceStyles et jetons
Section intitulée « Styles et jetons »| Classe | Sémantique |
|---|---|
.lh-select |
classe de base |
.lh-select--{small|large} / --disabled / --open |
modificateurs d’état |
.lh-select__trigger |
bouton déclencheur |
.lh-select__value / --placeholder |
valeur affichée / état réservé |
.lh-select__arrow / __clear / __actions |
flèche / effacement / zone d’actions |
.lh-select__dropdown / --teleported |
liste flottante (teleported = téléportée vers le body) |
.lh-select__option / --highlighted / --selected / --disabled |
les quatre états des options |
.lh-select__empty |
espace réservé sans option |
.lh-select-fade-* |
transitions d’entrée / sortie de la liste flottante (classes Vue3, alignées par mixin côté Vue2) |
Remarques
Section intitulée « Remarques »Object.isplutôt que===:findSelectOptionutiliseObject.is, qui traite correctement les cas limites commeNaNet-0(même si les valeurs d’options réelles y recourent rarement).getNextEnabledIndexest obsolète : l’implémentation a converge versshared/keyboard.getNextListIndex, cette fonction ne fait que déléguer afin de préserver l’ancienne signature. Le nouveau code importe directement le noyau.- Stabilité de l’id :
listboxIdne doit pas changer durant le cycle de vie du composant (sinonaria-activedescendantpointe vers un id périmé), utilisez le générateur d’id stable de chaque cible. - Le portail (teleport) : lorsque la liste flottante est téléportée vers le body,
aria-controlsreste valable (même document), mais il faut veiller à la gestion du focus — après un clic sur une option, le focus doit revenir au déclencheur.
@lh-ui/share/core/select → findSelectOption( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue