/** * Bongoto WooCommerce — Header JS (FINAL) * - Drawer: open/close, focus trap, scroll lock, click-outside * - Drawer: smooth scrolling + multi-level accordion (no auto-close on parents) * - Sticky shadow on scroll * - Desktop search dropdown (toggle + outside/ESC close) * - Drawer offset: auto match header height */ (function () { 'use strict'; /* ----------------- Helpers ----------------- */ const qs = (s, r = document) => r.querySelector(s); const qsa = (s, r = document) => Array.from(r.querySelectorAll(s)); const on = (t, h, o = document, opt = { passive: true }) => o.addEventListener(t, h, opt); const isDesktop = () => matchMedia('(min-width: 769px)').matches; const FOCUSABLE = 'a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])'; /* ----------------- Nodes ------------------- */ const header = qs('.bt-header') || qs('header.site-header'); const drawer = qs('.bt-drawer'); const drawerInner = drawer ? qs('.bt-drawer-inner', drawer) : null; const toggleBtn = qs('.bt-menu-toggle'); const closeBtn = qs('.bt-drawer-close'); /* ---------------- Drawer offset ------------- */ function headerHeight() { const h = header || qs('.site-header') || qs('header'); return h ? Math.round(h.getBoundingClientRect().height) : 64; } function setDrawerOffset() { const off = headerHeight(); document.documentElement.style.setProperty('--drawer-offset', off + 'px'); if (drawerInner) { drawerInner.style.maxHeight = `calc(100vh - ${off}px)`; drawerInner.style.overflowY = 'auto'; drawerInner.style.webkitOverflowScrolling = 'touch'; drawerInner.style.overscrollBehavior = 'contain'; drawerInner.style.scrollBehavior = 'smooth'; } } /* -------------- Scroll lock utils ----------- */ let scrollY = 0, lastFocus = null; const lockScroll = () => { scrollY = window.scrollY || 0; Object.assign(document.body.style, { position: 'fixed', top: `-${scrollY}px`, left: '0', right: '0', width: '100%' }); }; const unlockScroll = () => { Object.assign(document.body.style, { position: '', top: '', left: '', right: '', width: '' }); window.scrollTo(0, scrollY); }; /* -------------- Drawer open/close ----------- */ const openDrawer = () => { if (!drawer) return; lastFocus = document.activeElement; drawer.classList.add('is-open'); drawer.setAttribute('aria-hidden', 'false'); toggleBtn?.setAttribute('aria-expanded', 'true'); lockScroll(); setDrawerOffset(); // focus first focusable element inside drawer (drawerInner?.querySelector(FOCUSABLE) || drawer)?.focus?.(); }; const closeDrawer = () => { if (!drawer) return; drawer.classList.remove('is-open'); drawer.setAttribute('aria-hidden', 'true'); toggleBtn?.setAttribute('aria-expanded', 'false'); unlockScroll(); (toggleBtn || lastFocus)?.focus?.(); }; // Init ARIA if (drawer && !drawer.hasAttribute('aria-hidden')) { drawer.setAttribute('aria-hidden', 'true'); } if (toggleBtn && !toggleBtn.hasAttribute('aria-controls')) { toggleBtn.setAttribute('aria-controls', drawer?.id || 'bt-drawer'); } // Open/close triggers toggleBtn?.addEventListener('click', (e) => { e.preventDefault(); openDrawer(); }); closeBtn?.addEventListener('click', (e) => { e.preventDefault(); closeDrawer(); }); // Outside click closes drawer on('click', (e) => { if (!drawer?.classList.contains('is-open')) return; const inside = drawerInner?.contains(e.target); const onToggle = toggleBtn?.contains(e.target); if (!inside && !onToggle) closeDrawer(); }); // Drawer accordion logic (NO auto-close on other parents) // - Parent items with children: toggle .is-open and aria-expanded // - Leaf links (no children): navigate and close drawer drawerInner?.addEventListener('click', (e) => { const link = e.target.closest('a'); const li = e.target.closest('li.menu-item'); if (!link || !li) return; const hasChildren = li.classList.contains('menu-item-has-children'); if (hasChildren) { e.preventDefault(); li.classList.toggle('is-open'); link.setAttribute( 'aria-expanded', li.classList.contains('is-open') ? 'true' : 'false' ); return; } // Leaf item: allow navigation, then close drawer const href = link.getAttribute('href') || ''; if (href.startsWith('#')) { setTimeout(closeDrawer, 0); } else { closeDrawer(); } }); // Keyboard (ESC + focus trap) document.addEventListener('keydown', (e) => { if (!drawer?.classList.contains('is-open')) return; if (e.key === 'Escape') { e.preventDefault(); closeDrawer(); return; } if (e.key !== 'Tab') return; // Focus trap inside drawer const focusables = qsa(FOCUSABLE, drawer); if (!focusables.length) return; const first = focusables[0]; const last = focusables.at(-1); if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }); // Close on desktop resize on('resize', () => { setDrawerOffset(); if (isDesktop() && drawer?.classList.contains('is-open')) { closeDrawer(); } }, window); // Update offset on load/orientation change ['load', 'orientationchange'].forEach((ev) => on(ev, setDrawerOffset, window) ); // Watch header height (logo load, font swap, etc.) if (header && 'ResizeObserver' in window) { const ro = new ResizeObserver(setDrawerOffset); ro.observe(header); } /* -------- Sticky Shadow -------- */ if (header?.classList.contains('bt-header--sticky')) { const applyShadow = () => header.classList.toggle('bt-header-scrolled', window.scrollY > 10); applyShadow(); on('scroll', applyShadow, window); } /* -------- Desktop Search Dropdown -------- */ const closeAllSearchDropdowns = () => { qsa('.bt-search-dropdown').forEach((dd) => { dd.hidden = true; dd .closest('.bt-header-search') ?.querySelector('.bt-search-toggle') ?.setAttribute('aria-expanded', 'false'); }); }; qsa('.bt-search-toggle').forEach((btn) => { btn.setAttribute('aria-expanded', 'false'); btn.addEventListener('click', (e) => { e.preventDefault(); if (!isDesktop()) return; const host = btn.closest('.bt-header-search'); const dropdown = host?.querySelector('.bt-search-dropdown'); if (!dropdown) return; const willOpen = dropdown.hidden; closeAllSearchDropdowns(); if (willOpen) { dropdown.hidden = false; btn.setAttribute('aria-expanded', 'true'); dropdown .querySelector('input[type="search"],input[type="text"]') ?.focus?.(); } }); }); document.addEventListener( 'click', (e) => { if (!isDesktop()) return; const inside = e.target.closest( '.bt-search-dropdown, .bt-search-toggle' ); if (!inside) closeAllSearchDropdowns(); }, { passive: true } ); document.addEventListener('keydown', (e) => { if (!isDesktop()) return; if (e.key === 'Escape') closeAllSearchDropdowns(); }); })();