tooltip —— Infobulle
L’infobulle est « l’explication complémentaire que l’on ne regarde qu’au survol » : une icône de bouton incompréhensible, un texte de lien tronqué, une cellule de tableau qui ne peut pas contenir tout le contenu. core/tooltip est très mince — le positionnement est confié à shared/popper, les délais à shared/delay ; il ne fait que déclarer le rythme par défaut de ce composant et la convention d’appariement aria.
Quand l’utiliser
Section intitulée « Quand l’utiliser »- Chaque framework implémente
<LhTooltip>: ajouter une courte phrase d’explication (une seule phrase) à l’élément déclencheur. - Afficher le contenu complet d’un texte tronqué (ellipsis).
- Explication sémantique d’un bouton icône.
- À ne pas utiliser pour : du contenu exigeant une interaction (utilisez
popover— aucune icône dans un tooltip) ; les opérations de confirmation (utilisezpopconfirm) ; les retours d’erreur (utilisezmessage) ; sur mobile (pas de survol, l’infobulle est quasiment impossible à déclencher sur écran tactile — remplacez-la par le déclenchement click de popover ou un appui prolongé).
import { getPopperClassNames, getPopperStyle, getDefaultDelay, getTooltipPopperAria, getTooltipTriggerAria,} from '@lh-ui/share/core/tooltip'import type { LhTooltipPlacementCore, LhTooltipTriggerCore, LhTooltipEffectCore, LhTooltipState,} from '@lh-ui/share/core/tooltip'Intentions de conception
Section intitulée « Intentions de conception »L’asymétrie des délais par défaut : 200 ms à l’affichage, 0 ms au masquage — affichage lent, masquage rapide. Le délai d’affichage protège contre les erreurs (la souris qui balaie une série de boutons ne devrait pas déclencher une infobulle pour chacun), et le masquage à 0 ms garantit une disparition immédiate à la sortie, sans traîner. À comparer au masquage à 100 ms de popover (voir plus bas) : la différence de rythme entre les deux reflète le poids de l’interaction.
La convention d’appariement aria : le déclencheur pointe son aria-describedby vers l’id de la couche flottante, et la couche a role="tooltip". C’est la pratique standard WAI-ARIA : lorsque le lecteur d’écran focusse le déclencheur, il lit aussi le contenu de l’infobulle — l’utilisateur n’a pas besoin de survoler pour obtenir l’information — c’est le point d’accessibilité le plus facilement négligé : une infobulle purement visuelle est totalement invisible pour les utilisateurs au clavier.
Le traitement des alias @deprecated : getPopperClass / getPopperStyle sont les anciens noms, que le dépôt aval vue2 utilise encore ; ils sont conservés pour la compatibilité mais marqués deprecated, et le nouveau code utilise getPopperClassNames (la convention getXxxClassNames). Ils ne peuvent pas être supprimés — les consommateurs en aval les importent encore ; un renommage devra également modifier les consommateurs en aval.
getPopperStyle ne gère que maxWidth : un nombre → px, une chaîne est utilisée telle quelle. Les coordonnées de positionnement sont calculées par computePosition de shared/popper, et le transform/les déplacements de la couche flottante sont le résultat du binding de la couche UI à l’état du popper.
Tableau des fonctions
Section intitulée « Tableau des fonctions »| Fonction | Signature | Retour | Rôle |
|---|---|---|---|
getPopperClassNames |
(state: { effect, placement }) => string[] |
noms de classes | effet de thème + position |
getPopperStyle ⚠️ |
(state: { maxWidth }) => Record<string, string> |
style en ligne | normalisation de maxWidth |
getDefaultDelay |
(show?, hide?) => { show, hide } |
délais | 200 / 0 par défaut |
getTooltipPopperAria |
(id?) => LhAriaAttrs |
paquet aria | role=“tooltip” |
getTooltipTriggerAria |
(tooltipId?) => LhAriaAttrs |
paquet aria | aria-describedby |
⚠️ : alias de compatibilité @deprecated, le nouveau code utilise les nouveaux noms.
Constantes (types) : LhTooltipPlacementCore 12 positions, LhTooltipTriggerCore, LhTooltipEffectCore = 'dark' | 'light'.
Définitions de types
Section intitulée « Définitions de types »export type LhTooltipPlacementCore = | 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end'
export type LhTooltipTriggerCore = 'hover' | 'focus' | 'click' | 'contextmenu'export type LhTooltipEffectCore = 'dark' | 'light'
export interface LhTooltipState { visible: boolean effect: LhTooltipEffectCore placement: LhTooltipPlacementCore maxWidth?: number | string showDelay?: number hideDelay?: number}Accessibilité
Section intitulée « Accessibilité »getTooltipPopperAria('tip-1')// { role: 'tooltip', id: 'tip-1' }getTooltipTriggerAria('tip-1')// { 'aria-describedby': 'tip-1' }getTooltipTriggerAria() // {} (sans id transmis, rien n'est produit : utilisez aria-label en repli)- L’id doit être identique aux deux extrémités : la valeur du
aria-describedbydu déclencheur doit être égale au véritable id rendu par la couche flottante. En rendu côté serveur, l’id peut être vide — sans id transmis, la fonction renvoie un objet vide et la couche UI doit utiliseraria-labelen repli ; unaria-describedbyne doit pas pointer vers un élément inexistant (le lecteur d’écran annoncera « l’élément référencé n’existe pas »). - Le déclenchement focus couvre les utilisateurs au clavier : avec
trigger: 'focus', le focus via Tab affiche l’infobulle — c’est le comportement d’accessibilité recommandé par défaut ; un déclenchement exclusif au survol est totalement invisible pour les utilisateurs au clavier. - Les limites de contenu d’un role=“tooltip” : WAI-ARIA stipule qu’un tooltip ne doit pas contenir d’éléments interactifs (boutons, liens) — si l’interaction est nécessaire, utilisez
popover(role=dialog). Cette contrainte se reflète aussi dans les styles : le tooltip a généralement pointer-events: none. - Texte long :
maxWidthlimite la largeur du retour à la ligne, mais un tooltip ne devrait pas porter des paragraphes — au-delà d’une phrase, envisagez popover.
Exemples d’utilisation
Section intitulée « Exemples d’utilisation »import { getDefaultDelay, getPopperClassNames, getPopperStyle } from '@lh-ui/share/core/tooltip'
getDefaultDelay(undefined, undefined) // { show: 200, hide: 0 }getDefaultDelay(0, 500) // { show: 0, hide: 500 } (recouvrement explicite)
getPopperClassNames({ effect: 'dark', placement: 'top' })// ['lh-tooltip__popper', 'lh-tooltip__popper--dark', 'lh-tooltip__popper--top']
getPopperStyle({ maxWidth: 200 }) // { maxWidth: '200px' }getPopperStyle({ maxWidth: '30em' }) // { maxWidth: '30em' }getPopperStyle({}) // {}// Machine à états affichage/masquage (couche UI) : les délais sont normalisés par// resolveDelay de shared/delayconst state = reactive<LhTooltipState>({ visible: false, effect: 'dark', placement: 'top',})
let showTimer: ReturnType<typeof setTimeout> | undefinedfunction scheduleShow() { clearTimeout(showTimer) const { show } = getDefaultDelay(props.showDelay, props.hideDelay) showTimer = setTimeout(() => { state.visible = true }, show)}function scheduleHide() { clearTimeout(showTimer) const { hide } = getDefaultDelay(props.showDelay, props.hideDelay) setTimeout(() => { state.visible = false }, hide)}<!-- Vue3 --><template> <LhTooltip content="La suppression est irréversible" placement="top"> <button type="button" aria-label="Supprimer"><LhIcon name="delete" /></button> </LhTooltip></template>
<!-- À l'intérieur du composant : le déclencheur et la couche flottante --><template> <span class="lh-tooltip__trigger" v-bind="getTooltipTriggerAria(popperId)" @mouseenter="scheduleShow" @mouseleave="scheduleHide" @focus="scheduleShow" @blur="scheduleHide" > <slot /> </span> <teleport to="body"> <transition name="lh-tooltip-fade"> <div v-if="state.visible" :id="popperId" :class="getPopperClassNames({ effect: state.effect, placement: state.placement })" :style="{ ...getPopperStyle({ maxWidth: props.maxWidth }), ...popperStyle }" v-bind="getTooltipPopperAria(popperId)" role="tooltip" > <slot name="content">{{ props.content }}</slot> </div> </transition> </teleport></template>// React : l'élément déclencheur clone les children pour injecter les événements et les// attributs aria (React.cloneElement) ; la couche flottante utilise createPortal +// useFloating (@floating-ui ou un popper maison)// uniapp : <view> + catchtouchstart ; le survol n'existe pas côté mini-programme, le// tooltip sert principalement sur h5 ; les scénarios en mini-programme devraient// utiliser le déclenchement click de popoverStyles et jetons
Section intitulée « Styles et jetons »| Classe | Sémantique |
|---|---|
.lh-tooltip__trigger |
enveloppe du déclencheur (inline) |
.lh-tooltip__popper |
couche flottante |
.lh-tooltip__popper--{dark|light} |
effet de thème |
.lh-tooltip__popper--{top|top-start|…} |
12 positions (orientation de la flèche comprise) |
Effet dark : --lh-tooltip-bg-dark (proche du noir) + texte blanc ; effet light : --lh-bg-color + --lh-text-primary. Arrondi --lh-border-radius-small, marges internes --lh-tooltip-padding, taille de police --lh-font-size-small.
Remarques
Section intitulée « Remarques »- La double purge des minuteurs : si le minuteur d’affichage n’a pas encore déclenché et que la souris sort, il faut clearTimeout ; sinon la souris est partie et l’infobulle apparaît. La machine à états ci-dessus ne purge que showTimer ; une implémentation complète doit purger dans les deux sens.
- L’entrée dans la couche flottante elle-même : le
pointer-eventsdu tooltip est généralement none, donc il n’existe pas de « survol de la couche » — mais popover l’autorise, et c’est de là que vient la différence de délai de masquage entre les deux. - Contrôlé et non contrôlé : en mode contrôlé
v-model:visible, les changements visibles externes doivent être synchronisés avec l’état interne ; les minuteurs doivent être réinitialisés avec l’état contrôlé. - La coexistence de plusieurs tooltips : le popperId de chaque instance doit être unique (useId ou un numéro de séquence auto-incrémenté), sinon aria-describedby pointe vers un mauvais élément.
getPopperClass/getPopperStylesont des alias deprecated : le nouveau code utilisegetPopperClassNames; les anciens noms sont encore importés par le dépôt aval vue2, et avant de pouvoir les supprimer, il faut d’abord modifier les consommateurs (l’ordre de publication de la ligne rouge 5).- Le traitement d’un contenu vide : un
contentvide ne devrait pas afficher une couche flottante vide — la couche UI saute l’affichage quand le contenu est vide (v-if="content"), ce n’est pas la responsabilité de core. - L’élément déclencheur désactivé : un bouton natif
disabledne déclenche pas les événements mouseenter/focus, et le tooltip devient inopérant. La pratique courante est d’envelopper le déclencheur dans un élément non désactivé (le span.lh-tooltip__trigger) qui recevra les événements — c’est l’une des raisons d’être de la classe trigger.
@lh-ui/share/core/tooltip → getDefaultDelay( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue