1/** 2 * Navigation â sous-menus et panneau mobile. 3 * 4 * Remplace js/mobile-menu.js et sa dépendance à Lottie : le bouton est un 5 * vrai <button>, son état est porté par aria-expanded, et le dessin de la 6 * croix est fait en CSS. Plus rien à télécharger pour ouvrir un menu. 7 * 8 * Principes : 9 * - le balisage reste celui de wp_nav_menu. Tout ce qui est ajouté ici 10 * (chevrons, boutons de repli) est décoratif ou redondant avec un lien 11 * déjà présent : sans JavaScript, le menu reste une liste de liens 12 * parfaitement utilisable ; 13 * - aucune hauteur codée en dur pour les replis : elle est mesurée, donc un 14 * sous-menu de trois entrées comme de douze s'ouvre au bon format ; 15 * - le panneau est rendu inerte quand il est fermé, pour que la tabulation 16 * ne s'y perde pas. 17 */ 18(function () { 19 'use strict'; 20 21 var BASCULE = 1125; // Doit rester aligné sur la requête média de refonte.css. 22 23 document.addEventListener('DOMContentLoaded', function () { 24 25 /* --------------------------------------------------------------- 26 * Chevrons du menu grand écran 27 * ------------------------------------------------------------- 28 * Ajoutés ici plutôt qu'en ::after pour pouvoir les animer sans 29 * toucher au pseudo-élément déjà utilisé ailleurs sur les liens. 30 */ 31 32 // Les entrées à sous-menu classique et celle du méga-menu, qui n'a pas 33 // d'enfants au sens de WordPress mais ouvre bien un panneau. 34 var entreesBureau = document.querySelectorAll('.gd-nav__liste > li.menu-item-has-children > a, .gd-nav__liste > li.gd-mega > a'); 35 36 entreesBureau.forEach(function (lien) { 37 var chevron = document.createElement('span'); 38 chevron.className = 'gd-nav__chevron'; 39 chevron.setAttribute('aria-hidden', 'true'); 40 lien.appendChild(chevron); 41 }); 42 43 /* --------------------------------------------------------------- 44 * Alignement du méga-menu 45 * ------------------------------------------------------------- 46 * Le panneau doit finir au bord de la capsule, pas au bord de son 47 * entrée de menu. L'écart entre les deux dépend des entrées qui 48 * suivent : c'est une mesure, pas une constante. 49 */ 50 51 var entreeMega = document.querySelector('.gd-nav__liste > li.gd-mega'); 52 var capsule = document.querySelector('.main-header'); 53 54 if (entreeMega && capsule) { 55 var placerMega = function () { 56 var bordCapsule = capsule.getBoundingClientRect().right 57 - parseFloat(getComputedStyle(capsule).paddingRight); 58 var ecart = bordCapsule - entreeMega.getBoundingClientRect().right; 59 60 entreeMega.style.setProperty('--gd-mega-decalage', Math.max(0, Math.round(ecart)) + 'px'); 61 }; 62 63 window.addEventListener('resize', placerMega); 64 placerMega(); 65 } 66 67 /* --------------------------------------------------------------- 68 * Panneau mobile 69 * ------------------------------------------------------------- */ 70 71 var bouton = document.querySelector('.gd-burger'); 72 var panneau = document.getElementById('gd-menu-mobile'); 73 74 if (!bouton || !panneau) { 75 return; 76 } 77 78 var listeMobile = panneau.querySelector('.gd-menu-mobile__liste'); 79 80 // Ordre de cascade à l'ouverture. 81 if (listeMobile) { 82 Array.prototype.forEach.call(listeMobile.children, function (item, i) { 83 item.style.setProperty('--gd-ordre', i); 84 }); 85 } 86 87 function ouvrir() { 88 document.body.classList.add('gd-menu-ouvert'); 89 bouton.setAttribute('aria-expanded', 'true'); 90 bouton.setAttribute('aria-label', 'Fermer le menu'); 91 panneau.removeAttribute('inert'); 92 } 93 94 function fermer() { 95 document.body.classList.remove('gd-menu-ouvert'); 96 bouton.setAttribute('aria-expanded', 'false'); 97 bouton.setAttribute('aria-label', 'Ouvrir le menu'); 98 panneau.setAttribute('inert', ''); 99 100 // Les replis reviennent à zéro : rouvrir le menu ne doit pas 101 // afficher un sous-menu resté déplié de la fois précédente. 102 replis.forEach(function (repli) { 103 repli.fermer(); 104 }); 105 } 106 107 function estOuvert() { 108 return document.body.classList.contains('gd-menu-ouvert'); 109 } 110 111 /* 112 * Deux entrées plutôt qu'une. Sur iOS, le premier appui sur un bouton 113 * porteur d'un style de survol est parfois consommé comme un survol : 114 * pointerup arrive, click non, et il faut appuyer une seconde fois. 115 * On écoute donc les deux, avec un verrou de 400 ms pour que le click
116 * qui suit normalement le pointerup ne rebascule pas aussitôt. 117 */ 118 var dernierAppui = 0; 119 120 function basculer() { 121 var maintenant = Date.now(); 122 123 if (maintenant - dernierAppui < 400) { 124 return; 125 } 126 127 dernierAppui = maintenant; 128 129 if (estOuvert()) { 130 fermer(); 131 } else { 132 ouvrir(); 133 } 134 } 135 136 bouton.addEventListener('click', basculer); 137 bouton.addEventListener('pointerup', basculer); 138 139 // Ãchap ferme, et le focus revient au bouton : sinon il resterait sur 140 // un lien devenu inerte. 141 document.addEventListener('keydown', function (evenement) { 142 if (evenement.key === 'Escape' && estOuvert()) { 143 fermer(); 144 bouton.focus(); 145 } 146 }); 147 148 // Un lien cliqué ferme le panneau. Utile quand la cible est une ancre 149 // sur la page courante : sans cela, rien ne se passerait à l'écran. 150 panneau.addEventListener('click', function (evenement) { 151 var lien = evenement.target.closest('a[href]'); 152 153 if (lien && lien.getAttribute('href') !== '#') { 154 fermer(); 155 } 156 }); 157 158 // Repasser en grand écran doit libérer le défilement, même si le 159 // panneau était ouvert : il devient invisible sans être « fermé ». 160 window.addEventListener('resize', function () { 161 if (window.innerWidth > BASCULE && estOuvert()) { 162 fermer(); 163 } 164 }); 165 166 /* --------------------------------------------------------------- 167 * Replis des sous-menus 168 * ------------------------------------------------------------- */ 169 170 var replis = []; 171 172 if (listeMobile) { 173 Array.prototype.forEach.call( 174 listeMobile.querySelectorAll(':scope > li.menu-item-has-children'), 175 function (item, i) { 176 var sousMenu = item.querySelector(':scope > .sub-menu'); 177 var lien = item.querySelector(':scope > a'); 178 179 if (!sousMenu) { 180 return; 181 } 182 183 sousMenu.id = 'gd-sous-menu-' + i; 184 185 var bascule = document.createElement('button'); 186 bascule.type = 'button'; 187 bascule.className = 'gd-menu-mobile__bascule'; 188 bascule.setAttribute('aria-expanded', 'false'); 189 bascule.setAttribute('aria-controls', sousMenu.id); 190 bascule.setAttribute( 191 'aria-label', 192 'Afficher les pages de ' + (lien ? lien.textContent.trim() : 'cette rubrique') 193 ); 194 195 item.insertBefore(bascule, sousMenu); 196 197 var repli = { 198 ouvert: false, 199 ouvrir: function () { 200 repli.ouvert = true; 201 // Mesurée à l'instant de l'ouverture : la largeur 202 // du panneau, donc le nombre de lignes, a pu 203 // changer depuis le chargement. 204 sousMenu.style.maxHeight = sousMenu.scrollHeight + 'px'; 205 bascule.setAttribute('aria-expanded', 'true'); 206 }, 207 fermer: function () { 208 repli.ouvert = false; 209 sousMenu.style.maxHeight = ''; 210 bascule.setAttribute('aria-expanded', 'false'); 211 } 212 }; 213 214 bascule.addEventListener('click', function () { 215 if (repli.ouvert) { 216 repli.fermer(); 217 } else { 218 repli.ouvrir(); 219 } 220 }); 221 222 /* 223 * Une entrée de menu sans destination réelle (href="#") 224 * n'a rien à ouvrir : sur mobile, c'est le libellé 225 * lui-même qui déplie sa rubrique. Les entrées qui 226 * pointent vraiment quelque part gardent leur lien, et 227 * seul le bouton déplie. 228 */ 229 if (lien) { 230 var cible = lien.getAttribute('href'); 231 232 if (!cible || cible === '#' || cible.slice(-1) === '#') { 233 lien.addEventListener('click', function (evenement) { 234 evenement.preventDefault(); 235 236 if (repli.ouvert) { 237 repli.fermer(); 238 } else { 239 repli.ouvrir(); 240 } 241 }); 242 } 243 } 244 245 replis.push(repli); 246 } 247 ); 248 } 249 }); 250})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.