1document.addEventListener("DOMContentLoaded", function() { 2 var isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 3 var body = document.body; 4 5 // Determine hover setting from body classes 6 var hoverSetting = 'auto'; 7 if (body.classList.contains('submenu-hover-yes')) hoverSetting = 'yes'; 8 else if (body.classList.contains('submenu-hover-no')) hoverSetting = 'no'; 9 10 // Resolve effective hover mode 11 var useHover; 12 if (hoverSetting === 'yes') useHover = true; 13 else if (hoverSetting === 'no') useHover = false; 14 else useHover = !isTouchDevice; 15 16 // Determine submenu display mode 17 var submenuDisplayMode = 'auto'; 18 if (body.classList.contains('submenu-display-burger')) submenuDisplayMode = 'burger'; 19 20 // Burger submenu mode forces click-only 21 if (submenuDisplayMode === 'burger') useHover = false; 22 23 // Add body class so CSS can target resolved no-hover state 24 if (!useHover) { 25 body.classList.add('fps-no-hover'); 26 } 27 28 // Burger menu mode 29 var burgerMenuMode = 'auto'; 30 if (body.classList.contains('burger-menu-yes')) burgerMenuMode = 'yes'; 31 else if (body.classList.contains('burger-menu-no')) burgerMenuMode = 'no'; 32 33 // Touch + burger (auto or yes): add CSS class for responsive burger 34 if (burgerMenuMode !== 'no' && isTouchDevice) { 35 body.classList.add('fps-burger-touch'); 36 } 37 38 // Default Fomantic dropdown options 39 var dropdownOpts = { 40 on: useHover ? 'hover' : 'click', 41 showOnFocus: true, 42 allowTab: true, 43 forceSelection: false, 44 action: function(text, value, element) { element.click(); }, 45 onChange: function(value, text, $choice) {} 46 }; 47 48 // --- Helper: inject parent link as first submenu item (idempotent) --- 49 function injectParentLink(el) { 50 var parentUrl = el.getAttribute('data-parent-url'); 51 var parentTitle = el.getAttribute('data-parent-title'); 52 var parentIcon = el.getAttribute('data-parent-icon'); 53 if (!parentUrl || !parentTitle) return; 54 var submenu = el.querySelector('.fps-submenu'); 55 if (!submenu) return; 56 if (submenu.querySelector('.fps-parent-link')) return; 57 var a = document.createElement('a'); 58 a.href = parentUrl; 59 a.className = 'item fps-parent-link'; 60 a.setAttribute('role', 'menuitem'); 61 a.setAttribute('title', parentTitle); 62 a.innerHTML = '<i class="' + parentIcon + ' menu-item-icon"></i> <span class="menu-item-title"><strong>' + parentTitle + '</strong></span>'; 63 submenu.insertBefore(a, submenu.firstChild); 64 } 65 66 // When hover is disabled globally, inject parent links and remove hrefs 67 if (!useHover) { 68 document.querySelectorAll('.fps-has-submenu').forEach(function(el) { 69 injectParentLink(el); 70 }); 71 document.querySelectorAll('.fpsDropdown > a').forEach(function(link) { 72 link.removeAttribute('href'); 73 link.style.cursor = 'pointer'; 74 makeSubmenuToggle(link, false); 75 }); 76 } 77 78 // Initialize Fomantic dropdowns globally 79 jQuery('.fpsDropdown').dropdown(dropdownOpts); 80 syncDropdownExpandedState(document); 81 82 if (window.MutationObserver) { 83 document.querySelectorAll('.fpsDropdown').forEach(function(dropdown) { 84 var observer = new MutationObserver(function() { 85 syncDropdownExpandedState(dropdown); 86 }); 87 observer.observe(dropdown, { attributes: true, attributeFilter: ['class'] }); 88 }); 89 } 90 91 // Main menu mode detection 92 var mainMenuMode = 'auto'; 93 if (body.classList.contains('menu-display-big_icon')) mainMenuMode = 'big_icon'; 94 else if (body.classList.contains('menu-display-small_icon_text')) mainMenuMode = 'small_icon_text'; 95 if (mainMenuMode === 'auto' && isTouchDevice) { 96 body.classList.add('menu-display-touch'); 97 } 98 99 // Narrow sidebar: watch for open dropdowns and temporarily allow overflow so 100 // flyout submenus can extend beyond the sidebar boundary. CSS (overflow-x: hidden) 101 // prevents the horizontal scrollbar when no dropdown is open. 102 var isNarrowSidebar = mainMenuMode === 'big_icon' || 103 (mainMenuMode === 'auto' && (isTouchDevice || window.innerWidth <= 768)); 104 if (isNarrowSidebar && window.MutationObserver) { 105 var $sidebar = jQuery('.site-sidebar'); 106 var sidebarObserver = new MutationObserver(function() { 107 var anyActive = !!document.querySelector('.site-sidebar .fpsDropdown.active'); 108 // overflow:visible lets the flyout menu escape the sidebar; 109 // '' removes the inline style so CSS (overflow-y:auto; overflow-x:hidden) takes over. 110 $sidebar.css('overflow', anyActive ? 'visible' : ''); 111 }); 112 document.querySelectorAll('.site-sidebar .fpsDropdown').forEach(function(el) { 113 sidebarObserver.observe(el, { attributes: true, attributeFilter: ['class'] }); 114 }); 115 } 116 117 // === Focus trap + Escape key for overlay menus === 118 var FOCUSABLE = 'a[href], button:not([disabled]), [role="button"][tabindex="0"], [tabindex]:not
118([tabindex="-1"])'; 119 120 function isFocusableVisible(el) { 121 if (!el || el.disabled || el.getAttribute('aria-hidden') === 'true') { 122 return false; 123 } 124 125 var style = window.getComputedStyle(el); 126 if (style.visibility === 'hidden' || style.display === 'none') { 127 return false; 128 } 129 130 return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length); 131 } 132 133 function getFocusable(container) { 134 return Array.prototype.slice.call(container.querySelectorAll(FOCUSABLE)).filter(isFocusableVisible); 135 } 136 137 function makeSubmenuToggle(link, expanded) { 138 if (!link) return; 139 link.setAttribute('role', 'button'); 140 link.setAttribute('tabindex', '0'); 141 link.setAttribute('aria-expanded', expanded ? 'true' : 'false'); 142 } 143 144 function restoreSubmenuToggle(link) { 145 if (!link) return; 146 link.removeAttribute('role'); 147 link.removeAttribute('aria-expanded'); 148 } 149 150 function syncDropdownExpandedState(root) { 151 var dropdowns = root.classList && root.classList.contains('fpsDropdown') 152 ? [root] 153 : root.querySelectorAll('.fpsDropdown'); 154 155 Array.prototype.forEach.call(dropdowns, function(dropdown) { 156 var toggle = dropdown.querySelector(':scope > a[role="button"]'); 157 if (!toggle) return; 158 toggle.setAttribute('aria-expanded', dropdown.classList.contains('active') ? 'true' : 'false'); 159 }); 160 } 161 162 function activateOnKeyboard(handler) { 163 return function(e) { 164 if (e.key !== 'Enter' && e.key !== ' ') { 165 return; 166 } 167 168 e.preventDefault(); 169 handler(e); 170 }; 171 } 172 173 function trapFocus(container, returnFocus) { 174 var focusable = getFocusable(container); 175 if (focusable.length) focusable[0].focus(); 176 177 function keyHandler(e) { 178 if (e.key === 'Escape') { 179 closeOverlayMenu(container, returnFocus); 180 document.removeEventListener('keydown', keyHandler); 181 return; 182 } 183 if (e.key === 'Tab') { 184 var focusableNow = getFocusable(container); 185 if (focusableNow.length === 0) return; 186 var first = focusableNow[0]; 187 var last = focusableNow[focusableNow.length - 1]; 188 if (e.shiftKey && document.activeElement === first) { 189 e.preventDefault(); 190 last.focus(); 191 } else if (!e.shiftKey && document.activeElement === last) { 192 e.preventDefault(); 193 first.focus(); 194 } 195 } 196 } 197 document.addEventListener('keydown', keyHandler); 198 container._fpsTrapHandler = keyHandler; 199 } 200 201 function closeOverlayMenu(container, returnFocus) { 202 if (!container) return; 203 204 if (container.id === 'topbar-expanded-menu') { 205 container.style.display = 'none'; 206 container.setAttribute('aria-hidden', 'true'); 207 restoreMenu(container); 208 } else if (container.id === 'sidebar-menu-panel') { 209 container.classList.remove('fps-open'); 210 container.style.display = ''; 211 restoreMenu(container); 212 } else { 213 container.classList.remove('fps-open'); 214 container.style.display = 'none'; 215 } 216 217 if (returnFocus) { 218 returnFocus.setAttribute('aria-expanded', 'false'); 219 returnFocus.focus(); 220 } 221 } 222 223 function releaseFocus(container) { 224 if (container._fpsTrapHandler) { 225 document.removeEventListener('keydown', container._fpsTrapHandler); 226 delete container._fpsTrapHandler; 227 } 228 } 229 230 // === Burger overlay helpers === 231 232 // Prepare a container for burger/overlay mode (always click-to-open) 233 function prepareBurgerMenu(container) { 234 // Destroy and re-init Fomantic dropdowns for click mode 235 var $dd = jQuery(container).find('.fpsDropdown'); 236 $dd.dropdown('destroy'); 237 $dd.dropdown({ 238 on: 'click', 239 showOnFocus: true, 240 allowTab: true, 241 forceSelection: false, 242 action: function(text, value, element) { element.click(); } 243 }); 244 245 // Save and remove hrefs from all parent links with submenus 246 container.querySelectorAll('.fps-has-submenu > a').forEach(function(link) { 247 if (link.hasAttribute('href') && !link.hasAttribute('data-burger-href')) { 248 link.setAttribute('data-burger-href', link.getAttribute('href')); 249 link.removeAttribute('href'); 250 link.style.cursor = 'pointer'; 251 } 252 makeSubmenuToggle(link, false); 253 }); 254 255 // Inject parent links into submenus 256 container.querySelectorAll('.fps-has-submenu').forEach(function(el) { 257 injectParentLink(el); 258 }); 259 } 260 261 // Restore a container from burger mode to normal sidebar mode 262 function restoreMenu(container) { 263 // Destroy and re-init Fomantic dropdowns with original mode 264 var $dd = jQuery(container).find('.fpsDropdown'); 265 $dd.dropdown('destroy'); 266 $dd.dropdown(dropdownOpts); 267 268 // Restore all hrefs saved during burger mode 269 container.querySelectorAll('[data-burger-href]').forEach(function(link) { 270 link.setAttribute('href', link.getAttribute('data-burger-href')); 271 link.removeAttribute('data-burger-href'); 272 link.style.cursor = ''; 273 restoreSubmenuToggle(link); 274 }); 275 276 // Re-remove hrefs for top-level dropdowns if global no-hover mode 277 if (!useHover) { 278 container.querySelectorAll('.fpsDropdown > a').forEach(function(link) { 279 link.removeAttribute('href'); 280 link.style.cursor = 'pointer'; 281 makeSubmenuToggle(link, false); 282 }); 283 } 284 285 // Close any open nested submenus 286 container.querySelectorAll('.fps-submenu.fps-nested-open').forEach(function(sub) { 287 sub.classList.remove('fps-nested-open'); 288 }); 289 } 290 291 // Delegated click handler for nested submenus (non-Fomantic) in burger mode 292 function addNestedSubmenuClickHandler(container, activeCheck) { 293 container.addEventListener('click', function(e) { 294 if (activeCheck && !activeCheck()) return; 295 296 var link = e.target.closest('.fps-has-submenu:not(.fpsDropdown) > a'); 297 if (!link || !container.contains(link)) return; 298 299 var parent = link.closest('.fps-has-submenu'); 300 var submenu = parent.querySelector('.fps-submenu'); 301 if (!submenu) return; 302 303 e.preventDefault(); 304 e.stopPropagation(); 305 var isOpen = submenu.classList.toggle('fps-nested-open'); 306 makeSubmenuToggle(link, isOpen); 307 }); 308 309 container.addEventListener('keydown', function(e) { 310 var link = e.target.closest('.fps-has-submenu:not(.fpsDropdown) > a, .fps-has-submenu > a[role="button"]'); 311 if (!link || !container.contains(link)) return; 312 activateOnKeyboard(function(event) { 313 link.click(); 314 })(e); 315 }); 316 } 317 318 // === Top bar hamburger toggle === 319 var hamburger = document.getElementById('topbar-hamburger'); 320 var expandedMenu = document.getElementById('topbar-expanded-menu'); 321 if (hamburger && expandedMenu) { 322 expandedMenu.setAttribute('aria-hidden', 'true'); 323 324 // Expanded menu always uses burger/click mode 325 addNestedSubmenuClickHandler(expandedMenu); 326 327 hamburger.addEventListener('click', function() { 328 if (expandedMenu.style.display === 'none' || expandedMenu.style.display === '') { 329 expandedMenu.style.display = 'block'; 330 expandedMenu.setAttribute('aria-hidden', 'false'); 331 hamburger.setAttribute('aria-expanded', 'true'); 332 prepareBurgerMenu(expandedMenu); 333 trapFocus(expandedMenu, hamburger); 334 } else { 335 releaseFocus(expandedMenu); 336 closeOverlayMenu(expandedMenu, hamburger); 337 } 338 }); 339 } 340 341 // === Sidebar hamburger toggle (left-sidebar layout) === 342 var sidebarHamburger = document.getElementById('sidebar-hamburger'); 343 var sidebarMenuPanel = document.getElementById('sidebar-menu-panel'); 344 if (sidebarHamburger && sidebarMenuPanel) { 345 // Nested click handler only active when panel is open as overlay 346 addNestedSubmenuClickHandler(sidebarMenuPanel, function() { 347 return sidebarMenuPanel.classList.contains('fps-open'); 348 }); 349 350 sidebarHamburger.addEventListener('click', function() { 351 if (sidebarMenuPanel.classList.contains('fps-open')) { 352 releaseFocus(sidebarMenuPanel); 353 closeOverlayMenu(sidebarMenuPanel, sidebarHamburger); 354 } else { 355 sidebarMenuPanel.classList.add('fps-open'); 356 sidebarHamburger.setAttribute('aria-expanded', 'true'); 357 prepareBurgerMenu(sidebarMenuPanel); 358 trapFocus(sidebarMenuPanel, sidebarHamburger); 359 } 360 }); 361 362 var sidebarPanelClose = document.getElementById('sidebar-panel-close'); 363 if (sidebarPanelClose) { 364 sidebarPanelClose.addEventListener('click', function() { 365 releaseFocus(sidebarMenuPanel); 366 closeOverlayMenu(sidebarMenuPanel, sidebarHamburger); 367 }); 368 } 369 } 370 371 // === Top bar submenu toggle on click (no-hover / touch / burger submenu mode) === 372 var useTopbarClick = !useHover || submenuDisplayMode === 'burger'; 373 374 if (useTopbarClick) { 375 document.querySelectorAll('.topbar-item.has-children').forEach(function(item) { 376 var link = item.querySelector('.topbar-icon-link'); 377 var submenu = item.querySelector('.topbar-submenu'); 378 if (!link || !submenu) return; 379 380 link.removeAttribute('href'); 381 link.style.cursor = 'pointer'; 382 makeSubmenuToggle(link, false); 383 384 function toggleTopbarSubmenu(e) { 385 e.preventDefault(); 386 e.stopPropagation(); 387 document.querySelectorAll('.topbar-submenu.fps-open').forEach(function(other) { 388 if (other !== submenu) { 389 other.classList.remove('fps-open'); 390 other.style.display = 'none'; 391 var otherToggle = other.closest('.topbar-item'); 392 if (otherToggle) { 393 var otherLink = otherToggle.querySelector('.topbar-icon-link[role="button"]'); 394 if (otherLink) otherLink.setAttribute('aria-expanded', 'false'); 395 } 396 } 397 }); 398 if (submenu.classList.contains('fps-open')) { 399 submenu.classList.remove('fps-open'); 400 submenu.style.display = 'none'; 401 link.setAttribute('aria-expanded', 'false'); 402 } else { 403 submenu.classList.add('fps-open'); 404 submenu.style.display = 'block'; 405 link.setAttribute('aria-expanded', 'true'); 406 } 407 } 408 409 link.addEventListener('click', toggleTopbarSubmenu); 410 link.addEventListener('keydown', activateOnKeyboard(toggleTopbarSubmenu)); 411 }); 412 413 // Nested submenu toggle on click (top bar) 414 document.querySelectorAll('.topbar-nested-item.has-children').forEach(function(item) { 415 var toggle = item.querySelector('.topbar-nested-toggle'); 416 var nested = item.querySelector('.topbar-submenu-nested'); 417 if (!toggle || !nested) return; 418 419 toggle.removeAttribute('href'); 420 toggle.style.cursor = 'pointer'; 421 makeSubmenuToggle(toggle, false); 422 423 function toggleNestedSubmenu(e) { 424 e.preventDefault(); 425 e.stopPropagation(); 426 item.parentElement.querySelectorAll('.topbar-submenu-nested.fps-open').forEach(function(other) { 427 if (other !== nested) { 428 other.classList.remove('fps-open'); 429 other.style.display = 'none'; 430 var otherItem = other.closest('.topbar-nested-item'); 431 if (otherItem) { 432 var otherToggle = otherItem.querySelector('.topbar-nested-toggle[role="button"]'); 433 if (otherToggle) otherToggle.setAttribute('aria-expanded', 'false'); 434 } 435 } 436 }); 437 if (nested.classList.contains('fps-open')) { 438 nested.classList.remove('fps-open'); 439 nested.style.display = 'none'; 440 toggle.setAttribute('aria-expanded', 'false'); 441 } else { 442 nested.classList.add('fps-open'); 443 nested.style.display = 'block'; 444 toggle.setAttribute('aria-expanded', 'true'); 445 } 446 } 447 448 toggle.addEventListener('click', toggleNestedSubmenu); 449 toggle.addEventListener('keydown', activateOnKeyboard(toggleNestedSubmenu)); 450 }); 451 452 // Close submenus when clicking outside 453 document.addEventListener('click', function(e) { 454 if (!e.target.closest('.topbar-item')) { 455 document.querySelectorAll('.topbar-submenu.fps-open, .topbar-submenu-nested.fps-open').forEach(function(sub) { 456 sub.classList.remove('fps-open'); 457 sub.style.display = 'none'; 458 }); 459 document.querySelectorAll('.topbar-icon-link[role="button"], .topbar-nested-toggle[role="button"]').forEach(function(toggle) { 460 toggle.setAttribute('aria-expanded', 'false'); 461 }); 462 } 463 }); 464 } 465 466 // === Burger submenu mode: inject close buttons === 467 if (submenuDisplayMode === 'burger') { 468 document.querySelectorAll('.fps-submenu').forEach(function(submenu) { 469 if (submenu.querySelector('.fps-burger-close')) return; 470 var closeBtn = document.createElement('button'); 471 closeBtn.className = 'fps-burger-close'; 472 closeBtn.setAttribute('aria-label', 'Close submenu'); 473 closeBtn.innerHTML = '<i class="fas fa-times"></i>'; 474 closeBtn.addEventListener('click', function(e) { 475 e.preventDefault(); 476 e.stopPropagation(); 477 var dropdown = submenu.closest('.fpsDropdown'); 478 if (dropdown) jQuery(dropdown).dropdown('hide'); 479 }); 480 submenu.insertBefore(closeBtn, submenu.firstChild); 481 }); 482 483 document.querySelectorAll('.topbar-submenu').forEach(function(submenu) { 484 if (submenu.querySelector('.fps-burger-close')) return; 485 var closeBtn = document.createElement('button'); 486 closeBtn.className = 'fps-burger-close'; 487 closeBtn.setAttribute('aria-label', 'Close submenu'); 488 closeBtn.innerHTML = '<i class="fas fa-times"></i>'; 489 closeBtn.addEventListener('click', function(e) { 490 e.preventDefault(); 491 e.stopPropagation(); 492 submenu.classList.remove('fps-open'); 493 submenu.style.display = 'none'; 494 }); 495 submenu.insertBefore(closeBtn, submenu.firstChild); 496 }); 497 } 498 499});
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.