Comment faire accessibilité et éco-conception @Démo publique

Fiche: Gestion d'une pop-in modale

Description

Le principe de fonctionnement répond à une séquence assez simple :

  1. ouverture de la fenêtre ;
  2. mémorisation de l'origine de l'appel ;
  3. lors de l'ouverture on met <header>, <main> et <footer> en INERT (la div de la modale étant en dehors de ces régions).
  4. passage de focus à la pop-in ouverte ;
  5. lors de l'ouverture, utilisation de l'attribut ARIA message ;
  6. restriction de la navigation aux éléments présents dans la pop-in
  7. lors de la fermeture, passage du focus à l'origine de l'appel.

Ainsi lors de l'ouverture de la modale, l'utilisateur est averti et le focus est positionné au début de la modale.

Il peut naviguer dedans sans la pollution des autres éléments de la page

Lorsqu'il ferme la modale, le focus est remis au point de départ. 

Il y a bien continuité du focus de manière transparente pour l'utilisateur.

Structure

	[button] --> ouvre(type,comment,retour,fichier,style,cible)
---
	[div] --> modale, role="dialog", aria-modal="true", tabindex="0", aria-label="Fenêtre de dialogue"

Implémentation

Dans cet exemple, le lien déclenche l'ouverture d'une modale. Dans ce cas, le focus est transmis à la fenêtre modale puis, il est rendu à la fenêtre appelante.

La lecture reprend là où l'utilisateur a décidé de s'arrêter.

Code

HTML

L'appel
<button onclick="ouvre('modale','mod','bt01','compo/howto/fr/popin/modale.php','video',0)" id="bt01" class="lien">Ouvrir la fenêtre modale (lecture bloc)</button>
La modale
<div id="modale" class="cache modale fenetre dialogs" role="dialog" aria-modal="true" tabindex="0" aria-label="Fenêtre de dialogue"></div>

JavaScript

function ouvre(quoi,comment,rtr,fichier,style,cible=0) {
    if (quoi == "modale") {
        document.querySelector("header").setAttribute("inert","true");    
        document.querySelector("main").setAttribute("inert","true");  
        document.querySelector("footer").setAttribute("inert","true");    
        document.getElementById(quoi).classList.add('modal-open');
        if (cible == 1) document.addEventListener(`keydown`, initTrapFocus2);
        else document.addEventListener(`keydown`, initTrapFocus);
    }
    if (fichier != "") charge(fichier,"#"+quoi);
    document.querySelector("#"+quoi).classList.toggle('cache');
    if (style != "") document.querySelector("#"+quoi).classList.add(style);
    if (comment != "") {
        document.querySelector("#"+quoi).focus()
        retour = rtr;
    }
    document.querySelector("#"+quoi).focus();
}
function ferme(quoi,mode=0) {
    if (quoi == 1) document.querySelector("#alerte").classList.add("cache");
    else if (quoi == 2 || quoi == "modale") {
        if (mode == 0) {
            var video = document.getElementById("lecteurVideo");
            if (typeof video!== 'undefined' && video!= null) {
                video.pause();
                video.currentTime = 0;
            }
        }
        if (mode == 1) {
            var video = document.getElementById("lecteurVideo2");
            if (typeof video!== 'undefined' && video!= null) document.querySelector("#lecteurVideo2").src = null;
        }
        document.getElementById(quoi).classList.remove('modal-open');
        document.removeEventListener(`keydown`, initTrapFocus);
        document.querySelector("header").removeAttribute("inert","true"); 
        document.querySelector("main").removeAttribute("inert","true");   
        document.querySelector("footer").removeAttribute("inert","true"); 
        document.querySelector("#"+quoi).classList.add("cache");
        if (retour != "") document.querySelector("#"+retour).focus();
    }
    else {
        document.querySelector("#"+quoi).classList.add("cache");
        if (retour != "") document.querySelector("#"+retour).focus();
    }
}
function trapFocus(e, modalId, quoi=0) {
  const isTabPressed = e.key === `Tab` || e.keyCode === 9;
  if (!isTabPressed) return;
  const modal = document.getElementById(modalId);
  if (quoi == 1) focusableElements = '[href], iframe, input';
  else focusableElements = 'button, [href], input, select, textarea, iframe, video, audio [tabindex]:not([tabindex="-1"])';
  const firstFocusableElement = modal.querySelectorAll(focusableElements)[0];
  const focusableContent = modal.querySelectorAll(focusableElements);
  const lastFocusableElement = focusableContent[focusableContent.length - 1];
  if (e.shiftKey) {
    if (document.activeElement === firstFocusableElement) {
      lastFocusableElement.focus();
      e.preventDefault();
    }
  } 
  else if (document.activeElement === lastFocusableElement) {
    firstFocusableElement.focus();
    e.preventDefault();
  }
}
function initTrapFocus(e) {
  return trapFocus(e, `modale`);
}
function initTrapFocus2(e) {
  return trapFocus(e, `modale`,1);
}