class NavigationHandler extends UIHandler { constructor() { super(); this.initializeHandlers({ menuToggle: { forEach: true, click: (e) => this.toggleMenu(e) }, submenuToggles: { forEach: true, handler: (toggle) => { toggle._boundHandler = this.toggleSubmenu.bind(this); toggle.addEventListener('click', toggle._boundHandler); }, click: (e) => this.toggleSubmenu(e) } }); this.bindElements(); this.bindEvents(); } bindElements() { this.elements = { mobileNav: document.querySelectorAll('nav.mobile, nav:has(.has-submenu)'), menuToggle: document.querySelectorAll('button.toggle.main'), submenus: document.querySelectorAll('.has-submenu'), submenuToggles: document.querySelectorAll('button.toggle:not(.main)') }; } bindEvents() { super.bindEvents(); this.bindComponentEvents(); } toggleMenu(event) { event?.preventDefault(); const { mobileNav, menuToggle } = this.elements; const isExpanded = !this.isComponentActive('menu'); const firstLink = event.target.closest('nav').querySelector('a'); this.setComponentState('menu', isExpanded, { element: event.target.closest('nav'), toggle: event.target.closest('.toggle'), focusElement: isExpanded ? firstLink : menuToggle, cleanup: () => { // Close any open submenus when closing menu if (!isExpanded) { this.elements.submenus?.forEach(submenu => { if (this.isComponentActive(submenu.id || 'submenu')) { this.closeSubmenu(submenu); } }); } } }); } toggleSubmenu(event) { event?.preventDefault(); const toggle = event.currentTarget; const submenuParent = toggle.closest('.has-submenu'); const submenuId = submenuParent.id || 'submenu'; const isExpanded = !this.isComponentActive(submenuId); const firstSubmenuLink = submenuParent?.querySelector('.submenu a'); this.setComponentState(submenuId, isExpanded, { element: submenuParent, toggle: toggle, focusElement: isExpanded ? firstSubmenuLink : toggle }); } closeMenu(mobileNav) { let menuToggle = mobileNav.querySelector('.toggle.main'); // Close main menu this.setComponentState('menu', false, { element: mobileNav, toggle: menuToggle, focusElement: menuToggle }); let submenus = mobileNav.querySelectorAll('.has-submenu'); // Close any open submenus submenus?.forEach(submenu => { const toggle = submenu.querySelector('button.toggle'); const submenuId = submenu.id || 'submenu'; if (this.isComponentActive(submenuId)) { this.setComponentState(submenuId, false, { element: submenu, toggle: toggle }); } }); } closeSubmenu(submenu) { const toggle = submenu.querySelector('button.toggle'); const submenuId = submenu.id || 'submenu'; this.setComponentState(submenuId, false, { element: submenu, toggle: toggle, focusElement: toggle }); } handleOutsideClick(event) { if (!event.target.closest('nav')) { this.closeOpenItems(); } } handleEscapeKey(event) { if (event.key !== 'Escape') return; this.closeOpenItems(); event.preventDefault(); } closeOpenItems(){ const { mobileNav, submenus } = this.elements; const openSubmenu = Array.from(submenus || []) .find(submenu => submenu.classList.contains('open')); const openMenu = Array.from(mobileNav || []) .find(menu => menu.classList.contains('open')); if (openSubmenu) { this.closeSubmenu(openSubmenu); } if (openMenu) { this.closeMenu(openMenu); } } // Add cleanup method cleanup() { super.cleanup(); this.cleanupComponentEvents(); } } // Initialize on DOM load document.addEventListener('DOMContentLoaded', () => { const navigation = new NavigationHandler(); });