Les listbox et la navigation au clavier
Thématiques associées :- Composant
Date de parution
Mise à jour de l'article du
Introduction
Pour cet article, nous repartons de l’exemple sur les listbox ou zone de liste avec cases à cocher. L’objectif est d’améliorer la navigation au clavier afin de se rapprocher des spécifications design patterns ou modèle de conception 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 Guide.
La spécification concernant les listbox nous apprend, par exemple, 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").
- 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
. - La liste doit être focusable (