PageSourceSearch

https://guilhemdelachapelle.com/wp-content/themes/gdlcp-template/js/menu.js?ver=1785680639

js guilhemdelachapelle.com collected 2026-10-07 22:33:28 UTC 8,004 bytes, 250 lines download raw bytes

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.