/** * Menu mobile + sous-menus du header (équivalent logique de header.ts côté dynamiques). */ class HeaderMenu { constructor() { this.header = document.querySelector('#primary-header'); this.primaryMenu = this.header?.querySelector('#primary-menu') ?? null; this.mobileMenuToggle = this.header?.querySelector('#mobile-menu-toggle') ?? null; this.menusWrapper = this.header?.querySelector('.menus-wrapper') ?? null; this.submenuToggles = this.header ? this.header.querySelectorAll('.menu-item__submenu-toggle') : []; this.init(); } /** * Vérifie les éléments nécessaires et attache les écouteurs. */ init() { if (!this.header || !this.mobileMenuToggle) { console.warn('HeaderMenu : #primary-header ou #mobile-menu-toggle introuvable'); return; } this.bindEvents(); } /** * Lie tous les événements (toggle, focus, sous-menus, document). */ bindEvents() { this.mobileMenuToggle.addEventListener('click', (e) => this.handleToggleClick(e)); document.addEventListener('focusin', (e) => this.handleFocusOut(e), true); this.submenuToggles.forEach((button) => { button.addEventListener('click', (e) => this.handleSubmenuToggle(e, button)); }); document.addEventListener('click', (e) => this.handleDocumentClick(e)); document.addEventListener('keydown', (e) => this.handleDocumentKeydown(e)); } isMenuOpen() { return this.header?.classList.contains('nav-open') ?? false; } openMenu() { if (!this.header || !this.mobileMenuToggle || this.isMenuOpen()) return; this.header.classList.add('nav-open'); this.mobileMenuToggle.setAttribute('aria-expanded', 'true'); this.mobileMenuToggle.textContent = 'Fermer'; if (typeof gsap !== 'undefined' && this.menusWrapper) { gsap.from(this.menusWrapper, { opacity: 20, y: '-100vh', duration: 0.5, ease: Power4.easeOut, }); } } closeMenu() { if (!this.header || !this.mobileMenuToggle || !this.isMenuOpen()) return; this.header.classList.remove('nav-open'); this.mobileMenuToggle.removeAttribute('aria-expanded'); this.mobileMenuToggle.textContent = 'Menu'; } toggleMenu() { if (this.isMenuOpen()) { this.closeMenu(); } else { this.openMenu(); } } closeAllSubmenus() { this.submenuToggles.forEach((button) => { button.setAttribute('aria-expanded', 'false'); const sub = button.parentElement?.querySelector('.sub-menu'); if (sub) sub.classList.remove('sub-menu-open'); }); } handleToggleClick(e) { e.preventDefault(); this.toggleMenu(); } /** * Tab / focus hors du header : ferme la nav et renvoie le focus sur le bouton. */ handleFocusOut(e) { if (!this.header || !this.mobileMenuToggle || !this.isMenuOpen()) return; const target = e.target; if (!(target instanceof Node)) return; if (this.header.contains(target)) return; this.closeMenu(); this.closeAllSubmenus(); this.mobileMenuToggle.focus(); } /** * Clic en dehors du header : ferme uniquement les sous-menus (comportement actuel). */ handleDocumentClick(e) { const target = e.target; if (!(target instanceof Node) || !this.header) return; if (this.header.contains(target)) return; this.closeAllSubmenus(); } handleDocumentKeydown(e) { if (e.key === 'Escape') { this.closeAllSubmenus(); } } /** * Accordéon : un seul sous-menu ouvert à la fois. */ handleSubmenuToggle(e, button) { const isExpanded = button.getAttribute('aria-expanded') === 'true'; this.closeAllSubmenus(); if (!isExpanded) { button.setAttribute('aria-expanded', 'true'); button.parentElement?.querySelector('.sub-menu')?.classList.add('sub-menu-open'); } } } export default function menuInit() { return new HeaderMenu(); }