1/** 2 * Header Dropdown Menu Functionality 3 * Handles both desktop hover-based dropdowns and mobile click-based dropdowns 4 */ 5 6document.addEventListener('DOMContentLoaded', function() { 7 8 // Desktop dropdown functionality 9 const dropdownItems = document.querySelectorAll('.header-nav-desktop_inner li.has-dropdown'); 10 11 // Edge-aware positioning: a dropdown defaults to left-aligned under its item. 12 // If it would cross either viewport edge, nudge it horizontally by ONLY the 13 // overflow amount so it stays anchored under its parent (a full right-align 14 // flip threw short-label dropdowns like "About" far off to the left). We shift 15 // `left` (not `transform`) so the translateY open/close animation is preserved. 16 const EDGE = 16; // min viewport gutter, px 17 function positionDropdown(item) { 18 const dropdown = item.querySelector('.header-nav-dropdown'); 19 if (!dropdown) { return; } 20 item.classList.remove('dd-align-right'); // legacy full-flip, no longer used 21 dropdown.style.left = ''; // drop prior correction â measure natural (left:0) box 22 const vw = document.documentElement.clientWidth; 23 const rect = dropdown.getBoundingClientRect(); 24 let shift = 0; 25 if (rect.right > vw - EDGE) { shift = (vw - EDGE) - rect.right; } // overflow right â pull left 26 else if (rect.left < EDGE) { shift = EDGE - rect.left; } // overflow left â push right 27 if (shift) { dropdown.style.left = Math.round(shift) + 'px'; } 28 } 29 30 // Re-clamp the open/hovered dropdown if the window resizes under it. 31 window.addEventListener('resize', function () { 32 if (window.innerWidth <= 1100) { return; } 33 const active = document.querySelector('.header-nav-desktop_inner li.has-dropdown.dropdown-open, .header-nav-desktop_inner li.has-dropdown:hover'); 34 if (active) { positionDropdown(active); } 35 }); 36 37 dropdownItems.forEach(function(item) { 38 const link = item.querySelector('a'); 39 const dropdown = item.querySelector('.header-nav-dropdown'); 40 41 if (!link || !dropdown) { 42 return; 43 } 44 45 // Reposition on hover (desktop opens dropdowns via CSS :hover) 46 item.addEventListener('mouseenter', function() { 47 if (window.innerWidth > 1100) { positionDropdown(item); } 48 }); 49 50 // Add click functionality for touch devices 51 link.addEventListener('click', function(e) { 52 // Only prevent default if this is a parent item with dropdown 53 if (window.innerWidth <= 1100) { 54 return; // Let mobile menu handle this 55 } 56 57 // Check if dropdown is currently open 58 const isOpen = item.classList.contains('dropdown-open'); 59 60 // Close all other dropdowns 61 dropdownItems.forEach(function(otherItem) { 62 if (otherItem !== item) { 63 otherItem.classList.remove('dropdown-open'); 64 } 65 }); 66 67 // Toggle current dropdown 68 if (isOpen) { 69 item.classList.remove('dropdown-open'); 70 } else { 71 positionDropdown(item); 72 item.classList.add('dropdown-open'); 73 e.preventDefault(); // Prevent navigation on click for parent items 74 } 75 }); 76 77 // Add keyboard accessibility 78 link.addEventListener('keydown', function(e) { 79 if (e.key === 'Enter' || e.key === ' ') { 80 e.preventDefault(); 81 link.click(); 82 } else if (e.key === 'Escape') { 83 item.classList.remove('dropdown-open'); 84 } 85 }); 86 87 // Add focus/blur events for keyboard navigation 88 item.addEventListener('focusout', function(e) { 89 // Close dropdown when focus leaves the entire menu item 90 setTimeout(function() { 91 if (!item.contains(document.activeElement)) { 92 item.classList.remove('dropdown-open'); 93 } 94 }, 100); 95 }); 96 }); 97 98 // Mobile dropdown functionality 99 const mobileDropdownToggles = document.querySelectorAll('.mobile-dropdown-toggle'); 100 const mobileParentLinks = document.querySelectorAll('.header-nav-mobile li.has-mobile-dropdown .mobile-main-link'); 101 102 function toggleMobileDropdown(parentLi) { 103 const toggle = parentLi.querySelector('.mobile-dropdown-toggle'); 104 const toggleIcon = toggle.querySelector('.mobile-toggle-icon'); 105 const submenu = parentLi.querySelector('.header-nav-mobile-submenu'); 106 107 if (!submenu || !toggleIcon) { 108 return; 109 } 110 111 // Close all other mobile dropdowns 112 document.querySelectorAll('.header-nav-mobile li.has-mobile-dropdown').forEach(function(otherLi) { 113 if (otherLi !== parentLi) { 114 const otherSubmenu = otherLi.querySelector('.header-nav-mobile-submenu'); 115 const otherToggle = otherLi.querySelector('.mobile-dropdown-toggle'); 116 const otherIcon = otherToggle ? otherToggle.querySelector('.mobile-toggle-icon') : null; 117 if (otherSubmenu && otherToggle && otherIcon) { 118 otherSubmenu.classList.remove('open'); 119 otherToggle.classList.remove('active'); 120 otherToggle.setAttribute('aria-expanded', 'false'); 121 otherIcon.textContent = '+'; 122 } 123 } 124 }); 125 126 // Toggle current dropdown 127 const isOpen = submenu.classList.contains('open'); 128 129 if (isOpen) { 130 // Close dropdown 131 submenu.classList.remove('open'); 132 toggle.classList.remove('active'); 133 toggle.setAttribute('aria-expanded', 'false'); 134 toggleIcon.textContent = '+'; 135 } else { 136 // Open dropdown 137 submenu.classList.add('open'); 138 toggle.classList.add('active'); 139 toggle.setAttribute('aria-expanded', 'true'); 140 toggleIcon.textContent = 'â'; 141 } 142 } 143 144 // Make parent links clickable to toggle dropdown 145 mobileParentLinks.forEach(function(link) { 146 link.addEventListener('click', function(e) { 147 e.preventDefault(); 148 const parentLi = link.closest('li.has-mobile-dropdown'); 149 toggleMobileDropdown(parentLi); 150 }); 151 }); 152 153 // Keep existing toggle button functionality
154 mobileDropdownToggles.forEach(function(toggle) { 155 toggle.addEventListener('click', function(e) { 156 e.preventDefault(); 157 e.stopPropagation(); 158 const parentLi = toggle.closest('li.has-mobile-dropdown'); 159 toggleMobileDropdown(parentLi); 160 }); 161 162 // Add keyboard accessibility for mobile toggles 163 toggle.addEventListener('keydown', function(e) { 164 if (e.key === 'Enter' || e.key === ' ') { 165 e.preventDefault(); 166 toggle.click(); 167 } 168 }); 169 }); 170 171 // Close desktop dropdowns when clicking outside 172 document.addEventListener('click', function(e) { 173 if (!e.target.closest('.header-nav-desktop_inner')) { 174 dropdownItems.forEach(function(item) { 175 item.classList.remove('dropdown-open'); 176 }); 177 } 178 }); 179 180 // Close all dropdowns on window resize (mobile/desktop switch) 181 let resizeTimer; 182 window.addEventListener('resize', function() { 183 clearTimeout(resizeTimer); 184 resizeTimer = setTimeout(function() { 185 // Close desktop dropdowns 186 dropdownItems.forEach(function(item) { 187 item.classList.remove('dropdown-open'); 188 }); 189 190 // Close mobile dropdowns 191 mobileDropdownToggles.forEach(function(toggle) { 192 const submenu = toggle.closest('li').querySelector('.header-nav-mobile-submenu'); 193 const toggleIcon = toggle.querySelector('.mobile-toggle-icon'); 194 if (submenu && toggleIcon) { 195 submenu.classList.remove('open'); 196 toggle.classList.remove('active'); 197 toggle.setAttribute('aria-expanded', 'false'); 198 toggleIcon.textContent = '+'; 199 } 200 }); 201 }, 250); 202 }); 203}); 204 205/* ========================================================= 206 Sub-header: dropdowns + search + language selector (#2026-045) 207 Hover open is handled in CSS; JS adds click/touch/keyboard 208 toggles, one-open-at-a-time, outside-click/resize close, and 209 wires the language selector to Google Translate. 210 ========================================================= */ 211document.addEventListener('DOMContentLoaded', function () { 212 var bar = document.querySelector('.header_sub_header'); 213 if (!bar) { return; } 214 215 var dropdownItems = bar.querySelectorAll('li.has-subheader-dropdown'); 216 var search = bar.querySelector('.header_sub_header_search'); 217 var lang = bar.querySelector('.header_sub_header_lang'); 218 219 function closeAll(except) { 220 dropdownItems.forEach(function (li) { 221 if (li !== except) { 222 li.classList.remove('is-open'); 223 var a = li.querySelector('a'); 224 if (a) { a.setAttribute('aria-expanded', 'false'); } 225 } 226 }); 227 [search, lang].forEach(function (c) { 228 if (c && c !== except) { 229 c.classList.remove('is-open'); 230 var btn = c.querySelector('button'); 231 if (btn) { btn.setAttribute('aria-expanded', 'false'); } 232 } 233 }); 234 } 235 236 // Nav dropdown parents â click toggles (touch + keyboard); hover handled by CSS 237 dropdownItems.forEach(function (li) { 238 var link = li.querySelector('a'); 239 if (!link) { return; } 240 link.addEventListener('click', function (e) { 241 if (window.innerWidth <= 1100) { return; } 242 e.preventDefault(); 243 var open = li.classList.contains('is-open'); 244 closeAll(li); 245 li.classList.toggle('is-open', !open); 246 link.setAttribute('aria-expanded', String(!open)); 247 }); 248 }); 249 250 // Search + language triggers 251 [search, lang].forEach(function (c) { 252 if (!c) { return; } 253 var btn = c.querySelector('button'); 254 if (!btn) { return; } 255 btn.addEventListener('click', function (e) { 256 e.preventDefault(); 257 var open = c.classList.contains('is-open'); 258 closeAll(c); 259 c.classList.toggle('is-open', !open); 260 btn.setAttribute('aria-expanded', String(!open)); 261 }); 262 }); 263 264 // Language selection -> drive Google Translate 265 function setGoogTrans(code) { 266 // GTranslate runs in "onfly" mode: it reads the googtrans cookie on load 267 // and translates the page. Set the cookie (clearing any prior value) and 268 // reload. (doGTranslate/.goog-te-combo aren't reliable here because the 269 // default widget is hidden, so its combo never populates.) 270 var host = location.hostname; 271 ['', '; domain=' + host, '; domain=.' + host].forEach(function (d) { 272 document.cookie = 'googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/' + d; 273 }); 274 if (code && code !== 'en') { 275 var val = '/en/' + code; 276 document.cookie = 'googtrans=' + val + '; path=/'; 277 document.cookie = 'googtrans=' + val + '; path=/; domain=.' + host; 278 } 279 location.reload(); 280 } 281 282 // Bind every language option (desktop sub-header + mobile menu) 283 document.querySelectorAll('.sh-lang-option').forEach(function (opt) { 284 opt.addEventListener('click', function (e) { 285 e.preventDefault(); 286 document.querySelectorAll('.sh-lang-option').forEach(function (o) { 287 o.classList.remove('is-selected'); 288 }); 289 opt.classList.add('is-selected'); 290 if (lang) { 291 var current = lang.querySelector('.sh-lang-current'); 292 if (current) { current.textContent = opt.textContent.trim(); } 293 } 294 setGoogTrans(opt.getAttribute('data-gt-lang') || 'en'); 295 }); 296 }); 297 298 // Close on outside click, Escape, and resize 299 document.addEventListener('click', function (e) { 300 if (!e.target.closest('.header_sub_header')) { closeAll(null); } 301 }); 302 document.addEventListener('keydown', function (e) { 303 if (e.key === 'Escape') { closeAll(null); } 304 }); 305 var shResizeTimer; 306 window.addEventListener('resize', function () { 307 clearTimeout(shResizeTimer); 308 shResizeTimer = setTimeout(function () { closeAll(null); }, 200); 309 }); 310});
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.