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 fonction translate.