/** * File navigation.js. * * Handles toggling the navigation menu for small screens and enables TAB key * navigation support for dropdown menus, including keyboard accessibility. */ (function () { const siteNavigation = document.getElementById('site-navigation'); if (!siteNavigation) return; const button = siteNavigation.getElementsByTagName('button')[0]; if (typeof button === 'undefined') return; const menu = siteNavigation.getElementsByTagName('ul')[0]; if (typeof menu === 'undefined') { button.style.display = 'none'; return; } if (!menu.classList.contains('nav-menu')) { menu.classList.add('nav-menu'); } // Toggle menu open/close button.addEventListener('click', function () { siteNavigation.classList.toggle('toggled'); const expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); // When menu opens, move focus to first link if (siteNavigation.classList.contains('toggled')) { trapFocus(siteNavigation); const firstLink = menu.querySelector('a'); if (firstLink) firstLink.focus(); } else { releaseFocusTrap(); } }); // Enter/Space toggles button button.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); } }); // Close menu when clicking outside document.addEventListener('click', function (event) { const isClickInside = siteNavigation.contains(event.target); if (!isClickInside) { siteNavigation.classList.remove('toggled'); button.setAttribute('aria-expanded', 'false'); releaseFocusTrap(); } }); // Submenu ARIA setup const links = menu.getElementsByTagName('a'); const linksWithChildren = menu.querySelectorAll('.menu-item-has-children > a, .page_item_has_children > a'); linksWithChildren.forEach(link => { link.setAttribute('aria-haspopup', 'true'); link.setAttribute('aria-expanded', 'false'); link.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); const submenu = this.nextElementSibling; if (submenu && submenu.classList.contains('sub-menu')) { const expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); submenu.classList.toggle('toggled'); // Close other submenus const otherSubmenus = menu.querySelectorAll('.sub-menu.toggled'); otherSubmenus.forEach(otherSubmenu => { if (otherSubmenu !== submenu) { otherSubmenu.classList.remove('toggled'); const otherLink = otherSubmenu.previousElementSibling; if (otherLink && otherLink.tagName === 'A') { otherLink.setAttribute('aria-expanded', 'false'); } } }); } } }); }); // Touch submenu icons function bsd_isMobileMenuContext() { return siteNavigation.classList.contains('toggled') || window.matchMedia('(max-width: 1024px)').matches; } const submenuIcons = menu.querySelectorAll('a > img.submenu-icon'); submenuIcons.forEach(icon => { icon.addEventListener('click', function (e) { if (!bsd_isMobileMenuContext()) return; e.preventDefault(); e.stopPropagation(); const link = this.closest('a'); const parentLi = link.closest('li.menu-item-has-children'); const submenu = link.nextElementSibling; if (!submenu || !submenu.classList.contains('sub-menu')) return; const willOpen = !submenu.classList.contains('toggled'); // Close siblings const siblingLis = parentLi && parentLi.parentElement ? parentLi.parentElement.children : []; Array.from(siblingLis).forEach(sib => { if (sib !== parentLi) { const sibLink = sib.querySelector(':scope > a[aria-expanded]'); const sibSub = sib.querySelector(':scope > ul.sub-menu'); if (sibSub && sibSub.classList.contains('toggled')) { sibSub.classList.remove('toggled'); sib.classList.remove('toggled'); if (sibLink) sibLink.setAttribute('aria-expanded', 'false'); } } }); submenu.classList.toggle('toggled', willOpen); if (parentLi) parentLi.classList.toggle('toggled', willOpen); link.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); submenu.style.display = ''; }); }); // Focus styling for keyboard users for (const link of links) { link.addEventListener('focus', bsd_toggleFocus, true); link.addEventListener('blur', bsd_toggleFocus, true); } for (const link of linksWithChildren) { link.addEventListener('touchstart', bsd_toggleFocus, false); } function bsd_toggleFocus(event) { if (event.type === 'focus' || event.type === 'blur') { let self = this; while (!self.classList.contains('nav-menu')) { if (self.tagName.toLowerCase() === 'li') { self.classList.toggle('focus'); } self = self.parentNode; } } if (event.type === 'touchstart') { const menuItem = this.parentNode; if (event.cancelable) event.preventDefault(); for (const link of menuItem.parentNode.children) { if (menuItem !== link) link.classList.remove('focus'); } menuItem.classList.toggle('focus'); } } /** * 🔒 Focus Trap for Mobile Menu */ let focusTrapHandler = null; function trapFocus(container) { const focusableSelectors = 'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'; const focusableElements = container.querySelectorAll(focusableSelectors); if (!focusableElements.length) return; const firstEl = focusableElements[0]; const lastEl = focusableElements[focusableElements.length - 1]; focusTrapHandler = function (e) { if (e.key !== 'Tab') return; if (e.shiftKey) { // SHIFT + TAB if (document.activeElement === firstEl) { e.preventDefault(); lastEl.focus(); } } else { // TAB if (document.activeElement === lastEl) { e.preventDefault(); firstEl.focus(); } } }; document.addEventListener('keydown', focusTrapHandler); } function releaseFocusTrap() { if (focusTrapHandler) { document.removeEventListener('keydown', focusTrapHandler); focusTrapHandler = null; } } })();