/**
* Modern Mobile Hamburger Menu
*
* Handles animated hamburger toggle, slide-in panel,
* overlay, sub-menu accordions, focus trapping, and accessibility.
*/
(function () {
const hamburgerBtn = document.getElementById('mmenu-btn');
const navPanel = document.getElementById('mobile-navigation');
const overlay = document.getElementById('mobile-menu-overlay');
const closeBtn = document.getElementById('mobile-nav-close');
if (!navPanel || !hamburgerBtn) {
return;
}
// --- Open / Close helpers ---
function openMenu() {
hamburgerBtn.classList.add('is-active');
hamburgerBtn.setAttribute('aria-expanded', 'true');
navPanel.classList.add('is-open');
if (overlay) overlay.classList.add('is-visible');
document.body.style.overflow = 'hidden';
}
function closeMenu() {
hamburgerBtn.classList.remove('is-active');
hamburgerBtn.setAttribute('aria-expanded', 'false');
navPanel.classList.remove('is-open');
if (overlay) overlay.classList.remove('is-visible');
document.body.style.overflow = '';
}
function toggleMenu() {
if (navPanel.classList.contains('is-open')) {
closeMenu();
} else {
openMenu();
}
}
// --- Event listeners ---
hamburgerBtn.addEventListener('click', function (e) {
e.stopPropagation();
toggleMenu();
});
if (closeBtn) {
closeBtn.addEventListener('click', closeMenu);
}
if (overlay) {
overlay.addEventListener('click', closeMenu);
}
// Close on Escape key
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && navPanel.classList.contains('is-open')) {
closeMenu();
hamburgerBtn.focus();
}
});
// --- Sub-menu accordion toggles ---
const chevronSVG =
'';
const parentItems = navPanel.querySelectorAll(
'.menu-item-has-children, .page_item_has_children'
);
parentItems.forEach(function (item) {
const toggleBtn = document.createElement('button');
toggleBtn.className = 'submenu-toggle';
toggleBtn.setAttribute('aria-expanded', 'false');
toggleBtn.setAttribute('aria-label', 'Toggle submenu');
toggleBtn.innerHTML = chevronSVG;
toggleBtn.setAttribute('tabindex', '0');
// Insert after the link
const link = item.querySelector(':scope > a');
if (link && link.nextSibling) {
item.insertBefore(toggleBtn, link.nextSibling);
} else {
item.appendChild(toggleBtn);
}
toggleBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
const isExpanded = item.classList.contains('is-expanded');
// Close sibling sub-menus at the same level
const siblings = item.parentNode.querySelectorAll(
':scope > .menu-item-has-children.is-expanded, :scope > .page_item_has_children.is-expanded'
);
siblings.forEach(function (sib) {
if (sib !== item) {
sib.classList.remove('is-expanded');
const sibBtn = sib.querySelector(':scope > .submenu-toggle');
if (sibBtn) sibBtn.setAttribute('aria-expanded', 'false');
}
});
if (isExpanded) {
item.classList.remove('is-expanded');
toggleBtn.setAttribute('aria-expanded', 'false');
} else {
item.classList.add('is-expanded');
toggleBtn.setAttribute('aria-expanded', 'true');
}
});
});
// --- Focus trap inside open nav ---
navPanel.addEventListener('keydown', function (e) {
if (e.key !== 'Tab') return;
const focusable = navPanel.querySelectorAll(
'button, [href], [tabindex]:not([tabindex="-1"])'
);
if (!focusable.length) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
})();