1// Mobile navigation for the homepage: the header's top-level dropdown 2// (MobileHeader) and the slide-in page drawer (MobileMenu). 3// 4// Pure-JS (the homepage renders as static SSR, so no Blazor event handlers). 5// Behaviour is driven by data-attributes via a single delegated click handler 6// so it survives enhanced-navigation DOM morphing: 7// [data-topmenu-toggle] opens/closes the header's top-level dropdown 8// [data-drawer-open] opens the page drawer; an optional 9// data-drawer-sub="ID" lands straight in 10// #sa-drawer-sub-ID instead of the root list 11// [data-drawer-close] closes it (backdrop, close button) 12// [data-sub-open="ID"] slides in the submenu panel #sa-drawer-sub-ID 13// [data-sub-close] slides the current submenu back out 14// It also toggles html.sa-scrolled, which compacts the sticky header. 15(function () { 16 var TOPMENU_OPEN = 'sa-mobile-header__menu--open'; 17 18 function closeTopMenu() { 19 var open = document.querySelectorAll('.' + TOPMENU_OPEN); 20 for (var i = 0; i < open.length; i++) { 21 open[i].classList.remove(TOPMENU_OPEN); 22 var btn = open[i].querySelector('[data-topmenu-toggle]'); 23 if (btn) btn.setAttribute('aria-expanded', 'false'); 24 } 25 } 26 27 function toggleTopMenu(btn) { 28 var wrap = btn.closest('.sa-mobile-header__menu'); 29 if (!wrap) return; 30 var wasOpen = wrap.classList.contains(TOPMENU_OPEN); 31 closeTopMenu(); 32 if (!wasOpen) { 33 wrap.classList.add(TOPMENU_OPEN); 34 btn.setAttribute('aria-expanded', 'true'); 35 } 36 } 37 38 function resetSubs() { 39 var open = document.querySelectorAll('.sa-drawer__panel--active'); 40 for (var i = 0; i < open.length; i++) { 41 open[i].classList.remove('sa-drawer__panel--active'); 42 } 43 } 44 45 function openSub(id) { 46 var panel = document.getElementById('sa-drawer-sub-' + id); 47 if (panel) { 48 panel.classList.add('sa-drawer__panel--active'); 49 } 50 } 51 52 // Pending post-close reset (see closeDrawer) â cancelled on reopen so a 53 // quick close/reopen can't wipe the panel the reopen just asked for. 54 var resetTimer = null; 55 56 // The breadcrumb row's hamburger is the drawer's only trigger. 57 function markDrawerTriggers(expanded) { 58 var triggers = document.querySelectorAll('[data-drawer-open]'); 59 for (var i = 0; i < triggers.length; i++) { 60 triggers[i].setAttribute('aria-expanded', expanded ? 'true' : 'false'); 61 } 62 } 63 64 function openDrawer(sub) { 65 if (!document.getElementById('sa-drawer')) return; 66 if (resetTimer !== null) { window.clearTimeout(resetTimer); resetTimer = null; } 67 resetSubs(); 68 if (sub) openSub(sub); 69 document.body.classList.add('sa-drawer-open'); 70 markDrawerTriggers(true); 71 } 72 73 function closeDrawer() { 74 document.body.classList.remove('sa-drawer-open'); 75 markDrawerTriggers(false); 76 // Reset after the slide-out finishes so submenus don't flash on reopen. 77 if (resetTimer !== null) window.clearTimeout(resetTimer); 78 resetTimer = window.setTimeout(function () { 79 resetTimer = null; 80 resetSubs(); 81 }, 250); 82 } 83 84 function closeSub(el) { 85 var panel = el.closest('.sa-drawer__panel--sub'); 86 if (panel) { 87 panel.classList.remove('sa-drawer__panel--active'); 88 } 89 } 90 91 document.addEventListener('click', function (e) { 92 // e.target is an Element for real clicks, but guard anyway so a stray 93 // non-Element target can never throw and kill the handler. 94 var t = e.target; 95 if (!t || typeof t.closest !== 'function') return; 96 97 // Any click outside the header dropdown dismisses it, including clicks 98 // that go on to open the drawer below. 99 if (!t.closest('.sa-mobile-header__menu')) closeTopMenu(); 100 101 var topToggle = t.closest('[data-topmenu-toggle]'); 102 if (topToggle) { e.preventDefault(); toggleTopMenu(topToggle); return; } 103 104 var open = t.closest('[data-drawer-open]'); 105 if (open) { e.preventDefault(); openDrawer(open.getAttribute('data-drawer-sub')); return; } 106 107 var close = t.closest('[data-drawer-close]'); 108 if (close) { e.preventDefault(); closeDrawer(); return; } 109 110 var subOpen = t.closest('[data-sub-open]'); 111 if (subOpen) { e.preventDefault(); openSub(subOpen.getAttribute('data-sub-open')); return; } 112 113 var subClose = t.closest('[data-sub-close]'); 114 if (subClose) { e.preventDefault(); closeSub(subClose); return; } 115 116 // A navigation link inside the drawer: let it navigate, but drop the 117 // open state so the drawer can't survive an enhanced-navigation morph. 118 if (t.closest('#sa-drawer a[href]')) { 119 document.body.classList.remove('sa-drawer-open'); 120 markDrawerTriggers(false); 121 } 122 }); 123 124 document.addEventListener('keydown', function (e) { 125 if (e.key !== 'Escape') return; 126 if (document.body.classList.contains('sa-drawer-open')) closeDrawer(); 127 closeTopMenu(); 128 }); 129 130 // Compact sticky header. The class goes on <html>, not <body>: enhanced 131 // navigation morphs the body and would drop it mid-scroll.
132 var SCROLLED = 'sa-scrolled'; 133 // Separate thresholds on purpose. The header is sticky, so it occupies flow 134 // space â compacting it shortens the document, which can nudge the scroll 135 // position back over a single threshold and flap the state. 136 var COMPACT_AT = 48; 137 var EXPAND_AT = 16; 138 var scrollQueued = false; 139 140 function syncScrolled() { 141 scrollQueued = false; 142 var root = document.documentElement; 143 var y = window.pageYOffset || root.scrollTop || 0; 144 if (root.classList.contains(SCROLLED)) { 145 if (y <= EXPAND_AT) root.classList.remove(SCROLLED); 146 } else if (y >= COMPACT_AT) { 147 root.classList.add(SCROLLED); 148 } 149 } 150 151 window.addEventListener('scroll', function () { 152 if (scrollQueued) return; 153 scrollQueued = true; 154 window.requestAnimationFrame(syncScrolled); 155 }, { passive: true }); 156 157 // Enhanced navigation can land on a page already scrolled, so settle the 158 // state up front rather than waiting for the first scroll event. 159 syncScrolled(); 160}());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.