/** * 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'); // Return early if the navigation doesn't exist. if (!siteNavigation) { return; } const button = siteNavigation.getElementsByTagName('button')[0]; // Return early if the button doesn't exist. if (typeof button === 'undefined') { return; } const menu = siteNavigation.getElementsByTagName('ul')[0]; // Hide menu toggle button if menu is empty and return early. if (typeof menu === 'undefined') { button.style.display = 'none'; return; } if (!menu.classList.contains('nav-menu')) { menu.classList.add('nav-menu'); } // Toggle the .toggled class and the aria-expanded value each time the button is clicked. button.addEventListener('click', function () { siteNavigation.classList.toggle('toggled'); const expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); }); // Also allow Enter or Space to toggle the mobile menu. button.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); } }); // Remove the .toggled class and set aria-expanded to false when the user clicks outside the navigation. document.addEventListener('click', function (event) { const isClickInside = siteNavigation.contains(event.target); if (!isClickInside) { siteNavigation.classList.remove('toggled'); button.setAttribute('aria-expanded', 'false'); } }); // Get all the link elements within the menu. const links = menu.getElementsByTagName('a'); // Get all the link elements with children within the menu. const linksWithChildren = menu.querySelectorAll('.menu-item-has-children > a, .page_item_has_children > a'); // Set ARIA attributes and add keyboard toggle for submenus. 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 open 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'); } } }); } } }); }); // Mobile and Tablet: Toggle submenus only when clicking the submenu icon image function bsd_isMobileMenuContext() { // Mobile/Tablet when menu is collapsed or when media query matches (tablets typically up to 1024px) 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; // Do nothing on desktop } e.preventDefault(); e.stopPropagation(); const link = this.closest('a'); if (!link) return; 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'); // Optionally close sibling submenus for accordion behavior 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'); } } }); // Toggle current submenu submenu.classList.toggle('toggled', willOpen); // Add class on li so CSS can rotate the icon (previous-sibling styling is not possible) if (parentLi) parentLi.classList.toggle('toggled', willOpen); // Update ARIA state on the link link.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); // Clear any inline style so CSS rules apply submenu.style.display = ''; }); }); // Toggle focus each time a menu link is focused or blurred. for (const link of links) { link.addEventListener('focus', bsd_toggleFocus, true); link.addEventListener('blur', bsd_toggleFocus, true); } // Toggle focus each time a menu link with children receives a touch event. for (const link of linksWithChildren) { link.addEventListener('touchstart', bsd_toggleFocus, false); } /** * Sets or removes .focus class on an element. */ 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; // ✅ only preventDefault if it’s allowed if (event.cancelable) { event.preventDefault(); } for (const link of menuItem.parentNode.children) { if (menuItem !== link) { link.classList.remove('focus'); } } menuItem.classList.toggle('focus'); } } })();