Les listbox et la navigation au clavier
Thématiques associées :- Composant
Introduction
Pour cet article, nous repartons de l’exemple sur les listbox avec cases à cocher. L’objectif est d’améliorer la navigation au clavier afin de se rapprocher des spécifications design patterns du W3C/WAI.
Les patterns ARIA
Le W3C maintient en ligne des spécifications qui décrivent comment doivent se comporter les composants ARIA : WAI-ARIA Authoring Practices 1.1.
La spécification concernant les listbox nous apprend que :
- La liste doit être focusable (tabindex="0").
- Les flèches haut/bas doivent pouvoir servir à choisir l’élément sélectionné.
- On doit pouvoir sélectionner un élément en tapant rapidement ses premiers caractères (ex. : si on appuie sur les touches p et h, la sélection doit se déplacer sur "Photos").
- Les touches Shift + F10 doivent permettre d’ouvrir le menu contextuel de l’élément focusé si un menu contextuel est disponible.
- Dans le cas d’une liste à sélection multiple, le raccourci Ctrl+a doit permettre de sélectionner tous les éléments.
- Les touches Début et Fin doivent permettre de sélectionner les éléments depuis le début ou la fin de la liste.
Exemple
Implémentation
La fonctionnalité permettant de sélectionner automatiquement l’élément dont l’utilisateur tape les premières lettres n’est pas forcément simple à implémenter. Voici un exemple qui utilise
XPath
.La première chose à faire est d’écouter le clavier lorsque le focus est positionné sur la liste. En fonction de la touche pressée, on effectue une action (déplacement de l’élément sélectionné, sélection d’un élément…).
… // Init const listbox = document.querySelector("[role=listbox]"); // On keydown listbox.onkeydown = function(e) { var currentItem = this.querySelector("[aria-selected=true]"); switch (e.keyCode) { case 9: // TAB break; case 36: // home … e.preventDefault(); break; case 35: // end … e.preventDefault(); break; case 38: // Up arrow … e.preventDefault(); break; …
Pour les autres touches qui ne servent pas à effectuer une action sur la liste, c’est à dire les lettres et les chiffres, on les mémorise pour former une chaîne de caractère. Lorsque l’utilisateur ne tape pas de touche pendant quelques millisecondes (500 dans notre exemple), on recherche si un élément de liste commence par la chaîne tapée et on sélectionne cet élément de liste.
… case 65: // Ctrl + A if (e.ctrlKey) { … } default: // Search item starts with // Cancel current timer clearTimeout(timer); // Create search string searchString += e.key; var self = this; // Set a timer to search item after 500ms timer = setTimeout(function(){ // Search item var xpath = "li/span[starts-with(translate(text(), ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’, ’abcdefghijklmnopqrstuvwxyz’), ’" + searchString + "’)]"; var matchingElement = document.evaluate(xpath, self, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; // Reset search string searchString = ""; // If an item is found… if (matchingElement) { currentItem.setAttribute("aria-selected", "false"); matchingElement.parentElement.setAttribute("aria-selected", "true"); matchingElement.parentElement.focus(); matchingElement.parentElement.classList.add("active"); } }, 500); e.preventDefault();
Pour rechercher l’élément dans la liste, nous utilisons la requête
XPath
suivante :/li/span[starts-with(text(), "la chaine à rechercher")]
. De plus, pour s’affranchir de la casse des caractères, nous utilisons la fonctiontranslate
.Enfin pour la compatibilité avec Internet Explorer, nous utilisons le polyfill Xpath de Google qu’il suffit d’inclure dans la page et de charger à l’aide de la ligne de code
wgxpath.install();
au chargement de la page.