4802f23b72
Anneau outline m-primary affiché uniquement à la navigation clavier (Tab), pas au clic souris. Composable partagé useKbdFocusRing (détection de modalité clavier/souris) + utilitaire CSS .m-focus-ring-kbd. Le visuel existant (grossissement, label flottant, bordure bleue) reste inchangé. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import {ref} from 'vue'
|
|
|
|
/**
|
|
* Détection de la modalité de focus (clavier vs souris/tactile).
|
|
*
|
|
* Sur les champs texte, `:focus-visible` natif se déclenche AUSSI au clic souris
|
|
* (le navigateur suppose qu'on va taper). Pour n'afficher l'anneau de focus qu'à
|
|
* la navigation clavier (Tab), on suit la dernière interaction au niveau document
|
|
* et on n'arme l'anneau que si le focus a été précédé d'un évènement clavier.
|
|
*
|
|
* Le visuel « champ actif » existant (grossissement, label flottant, bordure bleue)
|
|
* reste piloté par `:focus` et n'est pas affecté : ce composable ne gère QUE l'anneau.
|
|
*/
|
|
|
|
let hadKeyboardEvent = false
|
|
let listenersAttached = false
|
|
|
|
function ensureGlobalListeners() {
|
|
if (listenersAttached || typeof document === 'undefined') return
|
|
listenersAttached = true
|
|
|
|
// capture=true pour observer l'évènement avant qu'il n'atteigne sa cible
|
|
document.addEventListener('keydown', () => {
|
|
hadKeyboardEvent = true
|
|
}, true)
|
|
|
|
const markPointer = () => {
|
|
hadKeyboardEvent = false
|
|
}
|
|
document.addEventListener('mousedown', markPointer, true)
|
|
document.addEventListener('pointerdown', markPointer, true)
|
|
document.addEventListener('touchstart', markPointer, true)
|
|
}
|
|
|
|
export function useKbdFocusRing() {
|
|
ensureGlobalListeners()
|
|
|
|
const keyboardFocused = ref(false)
|
|
|
|
const onFocus = () => {
|
|
keyboardFocused.value = hadKeyboardEvent
|
|
}
|
|
const onBlur = () => {
|
|
keyboardFocused.value = false
|
|
}
|
|
|
|
return {keyboardFocused, onFocus, onBlur}
|
|
}
|