1/** 2 * Frontend JavaScript for Menu A block 3 * Handles mega menu interactions, mobile menu, and responsive behavior 4 */ 5 6document.addEventListener('DOMContentLoaded', function() { 7 const menuContainer = document.querySelector('.vast-menu-a'); 8 if (!menuContainer) return; 9 10 const mobileToggle = menuContainer.querySelector('.vast-menu-a__mobile-toggle'); 11 const menuItems = menuContainer.querySelector('.vast-menu-a__items'); 12 const dropdownToggles = menuContainer.querySelectorAll('.vast-menu-a__toggle'); 13 const mobileDropdownToggles = menuContainer.querySelectorAll('.vast-menu-a__mobile-toggle-button'); 14 const panels = menuContainer.querySelectorAll('.vast-menu-a__panel'); 15 16 // Create overlay element 17 const overlay = document.createElement('div'); 18 overlay.className = 'vast-menu-a__overlay'; 19 document.body.appendChild(overlay); 20 21 let activePanel = null; 22 let isMobile = window.innerWidth < 1200; 23 let isScrolled = false; 24 let isMenuOpen = false; 25 26 // Scroll detection at 80vh 27 function handleScroll() { 28 const scrollPosition = window.scrollY; 29 const triggerHeight = window.innerHeight * 0.8; // 80vh 30 const shouldBeScrolled = scrollPosition >= triggerHeight; 31 32 if (shouldBeScrolled !== isScrolled) { 33 isScrolled = shouldBeScrolled; 34 updateNavigationState(); 35 } 36 } 37 38 // Update navigation classes based on state 39 function updateNavigationState() { 40 if (isScrolled || isMenuOpen) { 41 document.body.classList.add('scrolled'); 42 if (isMenuOpen) { 43 document.body.classList.add('menu-open'); 44 } 45 } else { 46 document.body.classList.remove('scrolled'); 47 document.body.classList.remove('menu-open'); 48 } 49 50 // Force mobile to always show scrolled state for readability 51 if (isMobile) { 52 document.body.classList.add('scrolled'); 53 } 54 } 55 56 // Initialize scroll listener 57 window.addEventListener('scroll', handleScroll); 58 handleScroll(); // Initial check 59 updateNavigationState(); // Initial state setup for mobile 60 61 // Update mobile state on resize 62 window.addEventListener('resize', function() { 63 const newIsMobile = window.innerWidth < 1200; 64 if (newIsMobile !== isMobile) { 65 isMobile = newIsMobile; 66 closeAllPanels(); 67 closeMobileMenu(); 68 updateNavigationState(); // Ensure mobile gets scrolled class 69 } 70 }); 71 72 // Mobile menu toggle 73 if (mobileToggle && menuItems) { 74 mobileToggle.addEventListener('click', function(e) { 75 e.preventDefault(); 76 e.stopPropagation(); 77 toggleMobileMenu(); 78 }); 79 } 80 81 // Desktop dropdown toggles 82 dropdownToggles.forEach(function(toggle) { 83 const item = toggle.closest('.vast-menu-a__item'); 84 const panel = item.querySelector('.vast-menu-a__panel--desktop'); 85 86 if (!panel) return; 87 88 // Click handler 89 toggle.addEventListener('click', function(e) { 90 e.preventDefault(); 91 e.stopPropagation(); 92 93 if (!isMobile) { 94 togglePanel(panel, toggle); 95 } 96 }); 97 98 // Remove hover handlers - click-only interaction 99 }); 100 101 // Mobile dropdown toggles (including mobile-only items) 102 function setupMobileDropdowns() { 103 const allMobileToggles = menuContainer.querySelectorAll('.vast-menu-a__mobile-toggle-button'); 104 105 allMobileToggles.forEach(function(toggle) { 106 const item = toggle.closest('.vast-menu-a__item'); 107 const panel = item.querySelector('.vast-menu-a__panel:not(.vast-menu-a__panel--desktop)'); 108 109 if (!panel) return; 110 111 toggle.addEventListener('click', function(e) { 112 e.preventDefault(); 113 e.stopPropagation(); 114 115 if (isMobile) { 116 toggleMobilePanel(panel, toggle); 117 } 118 }); 119 }); 120 } 121 122 // Setup mobile dropdowns 123 setupMobileDropdowns(); 124 125 // Close panels when clicking outside 126 document.addEventListener('click', function(e) { 127 if (!menuContainer.contains(e.target)) { 128 closeAllPanels(); 129 closeMobileMenu(); 130 } 131 }); 132 133 // Close menu when clicking the overlay 134 overlay.addEventListener('click', function(e) { 135 e.preventDefault(); 136 closeAllPanels(); 137 closeMobileMenu(); 138 }); 139 140 // Close on escape key 141 document.addEventListener('keydown', function(e) { 142 if (e.key === 'Escape') { 143 closeAllPanels(); 144 closeMobileMenu(); 145 } 146 }); 147 148 // Functions 149 function toggleMobileMenu() { 150 const isOpen = menuItems.classList.contains('is-active'); 151 152 if (isOpen) { 153 closeMobileMenu(); 154 } else { 155 openMobileMenu(); 156 } 157 } 158 159 function openMobileMenu() { 160 menuItems.classList.add('is-active'); 161 mobileToggle.setAttribute('aria-expanded', 'true'); 162 document.body.classList.add('vast-menu-open'); 163 } 164 165 function closeMobileMenu() { 166 menuItems.classList.remove('is-active'); 167 if (mobileToggle) { 168 mobileToggle.setAttribute('aria-expanded', 'false'); 169 } 170 document.body.classList.remove('vast-menu-open'); 171 172 // Close all mobile panels (including mobile-only items) 173 const allMobileToggles = menuContainer.querySelectorAll('.vast-menu-a__mobile-toggle-button');
174 allMobileToggles.forEach(function(toggle) { 175 const item = toggle.closest('.vast-menu-a__item'); 176 const panel = item.querySelector('.vast-menu-a__panel:not(.vast-menu-a__panel--desktop)'); 177 if (panel) { 178 panel.classList.remove('is-active'); 179 toggle.setAttribute('aria-expanded', 'false'); 180 } 181 }); 182 } 183 184 function togglePanel(panel, toggle) { 185 if (activePanel === panel) { 186 hidePanel(panel, toggle); 187 } else { 188 closeAllPanels(); 189 showPanel(panel, toggle); 190 } 191 } 192 193 function showPanel(panel, toggle) { 194 closeAllPanels(); 195 panel.classList.add('is-active'); 196 toggle.setAttribute('aria-expanded', 'true'); 197 panel.setAttribute('aria-hidden', 'false'); 198 activePanel = panel; 199 isMenuOpen = true; 200 overlay.classList.add('is-active'); 201 updateNavigationState(); 202 } 203 204 function hidePanel(panel, toggle) { 205 panel.classList.remove('is-active'); 206 toggle.setAttribute('aria-expanded', 'false'); 207 panel.setAttribute('aria-hidden', 'true'); 208 if (activePanel === panel) { 209 activePanel = null; 210 } 211 isMenuOpen = false; 212 overlay.classList.remove('is-active'); 213 updateNavigationState(); 214 } 215 216 function toggleMobilePanel(panel, toggle) { 217 const isOpen = panel.classList.contains('is-active'); 218 219 if (isOpen) { 220 panel.classList.remove('is-active'); 221 toggle.setAttribute('aria-expanded', 'false'); 222 panel.setAttribute('aria-hidden', 'true'); 223 } else { 224 panel.classList.add('is-active'); 225 toggle.setAttribute('aria-expanded', 'true'); 226 panel.setAttribute('aria-hidden', 'false'); 227 } 228 } 229 230 function closeAllPanels() { 231 panels.forEach(function(panel) { 232 panel.classList.remove('is-active'); 233 panel.setAttribute('aria-hidden', 'true'); 234 }); 235 236 dropdownToggles.forEach(function(toggle) { 237 toggle.setAttribute('aria-expanded', 'false'); 238 }); 239 240 activePanel = null; 241 isMenuOpen = false; 242 overlay.classList.remove('is-active'); 243 updateNavigationState(); 244 } 245 246 // Enhanced keyboard navigation support 247 menuContainer.addEventListener('keydown', function(e) { 248 const focusedElement = document.activeElement; 249 const menuItems = Array.from(menuContainer.querySelectorAll('.vast-menu-a__link, .vast-menu-a__toggle')); 250 const currentIndex = menuItems.indexOf(focusedElement); 251 252 switch(e.key) { 253 case 'Enter': 254 case ' ': 255 if (focusedElement.classList.contains('vast-menu-a__toggle')) { 256 e.preventDefault(); 257 focusedElement.click(); 258 } else if (focusedElement.classList.contains('vast-menu-a__mobile-toggle')) { 259 e.preventDefault(); 260 focusedElement.click(); 261 } else if (focusedElement.classList.contains('vast-menu-a__mobile-toggle-button')) { 262 e.preventDefault(); 263 focusedElement.click(); 264 } 265 break; 266 267 case 'ArrowRight': 268 e.preventDefault(); 269 if (currentIndex < menuItems.length - 1) { 270 menuItems[currentIndex + 1].focus(); 271 } else { 272 menuItems[0].focus(); // Wrap to first item 273 } 274 break; 275 276 case 'ArrowLeft': 277 e.preventDefault(); 278 if (currentIndex > 0) { 279 menuItems[currentIndex - 1].focus(); 280 } else { 281 menuItems[menuItems.length - 1].focus(); // Wrap to last item 282 } 283 break; 284 285 case 'ArrowDown': 286 e.preventDefault(); 287 // If focused on toggle with dropdown, open it and focus first link 288 if (focusedElement.classList.contains('vast-menu-a__toggle')) { 289 const item = focusedElement.closest('.vast-menu-a__item'); 290 const panel = item.querySelector('.vast-menu-a__panel'); 291 if (panel) { 292 showPanel(panel, focusedElement); 293 setTimeout(() => { 294 const firstLink = panel.querySelector('a, button'); 295 if (firstLink) firstLink.focus(); 296 }, 100); 297 } 298 } 299 break; 300 301 case 'Escape': 302 closeAllPanels(); 303 closeMobileMenu(); 304 // Return focus to the toggle that opened the panel 305 if (focusedElement.closest('.vast-menu-a__panel')) { 306 const panel = focusedElement.closest('.vast-menu-a__panel'); 307 const item = panel.closest('.vast-menu-a__item'); 308 const toggle = item.querySelector('.vast-menu-a__toggle'); 309 if (toggle) toggle.focus(); 310 } 311 break; 312 } 313 }); 314 315 // Focus management for accessibility
316 dropdownToggles.forEach(function(toggle) { 317 const item = toggle.closest('.vast-menu-a__item'); 318 const panel = item.querySelector('.vast-menu-a__panel'); 319 320 if (!panel) return; 321 322 panel.addEventListener('keydown', function(e) { 323 if (e.key === 'Tab') { 324 const focusableElements = panel.querySelectorAll('a, button, [tabindex]:not([tabindex="-1"])'); 325 const firstElement = focusableElements[0]; 326 const lastElement = focusableElements[focusableElements.length - 1]; 327 328 if (e.shiftKey && document.activeElement === firstElement) { 329 e.preventDefault(); 330 toggle.focus(); 331 } else if (!e.shiftKey && document.activeElement === lastElement) { 332 e.preventDefault(); 333 // Move to next menu item or close panel 334 hidePanel(panel, toggle); 335 const nextItem = item.nextElementSibling; 336 if (nextItem) { 337 const nextLink = nextItem.querySelector('.vast-menu-a__link, .vast-menu-a__toggle'); 338 if (nextLink) nextLink.focus(); 339 } 340 } 341 } 342 }); 343 }); 344});
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.