1class NavigationManager { 2 constructor() { 3 this.navbar = document.querySelector("#navbarNavDropdown"); 4 this.navList = document.querySelector("#nav-main-ul"); 5 this.burgerButton = document.querySelector(".navbar-toggler"); 6 this.isMobileNav = false; 7 this.isTransitioning = false; 8 this.openDropdowns = []; 9 this.mobileBreakpoint = 768; 10 this.init(); 11 } 12 init() { 13 if (!this.navList) return; 14 this.createBackButtons(); 15 this.checkNavFit(); 16 this.setupEventListeners(); 17 this.setupResizeObserver(); 18 this.setupKeyboardNavigation(); 19 } 20 /** 21 * Erstellt Zurück-Buttons für mobile Dropdowns 22 */ 23 createBackButtons() { 24 const dropdowns = this.navList.querySelectorAll(".dropdown-menu"); 25 dropdowns.forEach((dropdown) => { 26 let backButton; 27 if (!dropdown.querySelector(".mobile-back-button")) { 28 backButton = document.createElement("li"); 29 backButton.className = "mobile-back-button col-12"; 30 backButton.innerHTML = ` 31 <button type="button" class="btn-back" aria-label="Zurück"> 32 <span class="arrow-left"></span> Zurück 33 </button> 34 `; 35 dropdown.insertBefore(backButton, dropdown.firstChild); 36 } else { 37 backButton = dropdown.querySelector(".mobile-back-button"); 38 } 39 backButton.querySelector(".btn-back").addEventListener("click", (e) => { 40 e.preventDefault(); 41 e.stopPropagation(); 42 this.closeDropdown(dropdown); 43 }); 44 }); 45 } 46 /** 47 * Setup aller Event Listeners 48 */ 49 setupEventListeners() { 50 const navItems = this.navList.querySelectorAll(".nav-item.dropdown"); 51 navItems.forEach((item) => { 52 const link = item.querySelector(".nav-link"); 53 const button = item.querySelector(".dropdown-toggle"); 54 const dropdown = item.querySelector(".dropdown-menu"); 55 if (!button || !dropdown) return; 56 item.addEventListener("mouseenter", () => { 57 if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) { 58 this.openDropdown(dropdown, item); 59 } 60 }); 61 item.addEventListener("mouseleave", () => { 62 if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) { 63 this.closeDropdown(dropdown, item); 64 } 65 }); 66 button.addEventListener("click", (e) => { 67 if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) { 68 e.preventDefault(); 69 e.stopPropagation(); 70 this.toggleDropdown(dropdown, item); 71 } 72 }); 73 button.addEventListener("click", (e) => { 74 if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) { 75 e.preventDefault(); 76 e.stopPropagation(); 77 } 78 }); 79 link.addEventListener("click", (e) => { 80 if (e.target === link) { 81 return true; 82 } 83 }); 84 }); 85 this.setupSubDropdowns(); 86 if (this.burgerButton) { 87 this.burgerButton.addEventListener("click", () => { 88 this.toggleMobileNav(); 89 }); 90 } 91 document.addEventListener("click", (e) => { 92 if (!e.target.closest(".navbar-nav")) { 93 this.closeAllDropdowns(); 94 } 95 }); 96 } 97 /** 98 * Setup für Keyboard Navigation 99 */ 100 setupKeyboardNavigation() { 101 const navItems = this.navList.querySelectorAll(".nav-item.dropdown"); 102 navItems.forEach((item) => { 103 const link = item.querySelector(".nav-link"); 104 const button = item.querySelector(".dropdown-toggle"); 105 const dropdown = item.querySelector(".dropdown-menu"); 106 if (!link || !button || !dropdown) return; 107 link.addEventListener("keydown", (e) => { 108 switch (e.key) { 109 case " ": 110 e.preventDefault(); 111 e.stopPropagation(); 112 this.toggleDropdown(dropdown, item); 113 if (dropdown.classList.contains("show")) { 114 setTimeout(() => { 115 const firstLink = dropdown.querySelector("a, button"); 116 if (firstLink) firstLink.focus(); 117 }, 50); 118 } 119 break; 120 case "Enter": 121 if (dropdown.classList.contains("show")) { 122 this.closeDropdown(dropdown, item); 123 } 124 break; 125 case "Escape": 126 e.preventDefault(); 127 this.closeDropdown(dropdown, item); 128 link.focus(); 129 break; 130 case "ArrowDown": 131 e.preventDefault(); 132 if (!dropdown.classList.contains("show")) { 133 this.openDropdown(dropdown, item); 134 } 135 setTimeout(() => { 136 const firstLink = dropdown.querySelector("a, button"); 137 if (firstLink) firstLink.focus(); 138 }, 50); 139 break; 140 case "ArrowUp": 141 e.preventDefault(); 142 if (!dropdown.classList.contains("show")) { 143 this.openDropdown(dropdown, item); 144 } 145 setTimeout(() => { 146 const links = dropdown.querySelectorAll("a, button:not(.btn-back)"); 147 if (links.length > 0) { 148 links[links.length - 1].focus(); 149 } 150 }, 50); 151 break; 152 } 153 }); 154 dropdown.addEventListener("keydown", (e) => { 155 if (e.key === "Tab") { 156 const focusableElements = dropdown.querySelectorAll("a, button"); 157 const firstElement = focusableElements[0]; 158 const lastElement = focusableElements[focusableElements.length - 1]; 159 if (e.shiftKey && document.activeElement === firstElement) { 160 e.preventDefault(); 161 this.closeDropdown(dropdown, item); 162 link.focus(); 163 } else if (!e.shiftKey && document.activeElement === lastElement) { 164 this.closeDropdown(dropdown, item); 165 } 166 } 167 }); 168 }); 169 document.addEventListener("keydown", (e) => { 170 if (e.key === "Escape" && this.openDropdowns.length > 0) { 171 e.preventDefault(); 172 const lastOpenDropdown = this.openDropdowns[this.openDropdowns.length - 1];
173 const parentItem = lastOpenDropdown.closest(".nav-item"); 174 const link = parentItem?.querySelector(".nav-link"); 175 this.closeDropdown(lastOpenDropdown, parentItem); 176 if (link) { 177 link.focus(); 178 } 179 } 180 }); 181 } 182 /** 183 * Setup für verschachtelte Sub-Dropdowns 184 */ 185 setupSubDropdowns() { 186 const subItems = this.navList.querySelectorAll(".dropdown-menu .dropdown-toggle"); 187 subItems.forEach((button) => { 188 const parentLi = button.closest("li"); 189 const subDropdown = parentLi.querySelector(".dropdown-menu"); 190 const subLink = parentLi.querySelector("a.nav-link, a.dropdown-item"); 191 if (!subDropdown) return; 192 button.addEventListener("click", (e) => { 193 e.preventDefault(); 194 e.stopPropagation(); 195 if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) { 196 this.openDropdown(subDropdown, parentLi); 197 } else { 198 this.toggleDropdown(subDropdown, parentLi); 199 } 200 }); 201 if (subLink) { 202 subLink.addEventListener("keydown", (e) => { 203 switch (e.key) { 204 case " ": 205 e.preventDefault(); 206 e.stopPropagation(); 207 this.toggleDropdown(subDropdown, parentLi); 208 if (subDropdown.classList.contains("show")) { 209 setTimeout(() => { 210 const firstLink = subDropdown.querySelector("a, button"); 211 if (firstLink) firstLink.focus(); 212 }, 50); 213 } 214 break; 215 case "Enter": 216 if (subDropdown.classList.contains("show")) { 217 this.closeDropdown(subDropdown, parentLi); 218 } 219 break; 220 case "Escape": 221 e.preventDefault(); 222 this.closeDropdown(subDropdown, parentLi); 223 subLink.focus(); 224 break; 225 case "ArrowRight": 226 e.preventDefault(); 227 if (!subDropdown.classList.contains("show")) { 228 this.openDropdown(subDropdown, parentLi); 229 setTimeout(() => { 230 const firstLink = subDropdown.querySelector("a, button"); 231 if (firstLink) firstLink.focus(); 232 }, 50); 233 } 234 break; 235 case "ArrowLeft": 236 e.preventDefault(); 237 if (subDropdown.classList.contains("show")) { 238 this.closeDropdown(subDropdown, parentLi); 239 subLink.focus(); 240 } 241 break; 242 case "ArrowDown": 243 e.preventDefault(); 244 if (!subDropdown.classList.contains("show")) { 245 this.openDropdown(subDropdown, parentLi); 246 } 247 setTimeout(() => { 248 const firstLink = subDropdown.querySelector("a, button"); 249 if (firstLink) firstLink.focus(); 250 }, 50); 251 break; 252 case "ArrowUp": 253 e.preventDefault(); 254 if (!subDropdown.classList.contains("show")) { 255 this.openDropdown(subDropdown, parentLi); 256 } 257 setTimeout(() => { 258 const links = subDropdown.querySelectorAll("a, button:not(.btn-back)"); 259 if (links.length > 0) { 260 links[links.length - 1].focus(); 261 } 262 }, 50); 263 break; 264 } 265 }); 266 } 267 if (window.innerWidth >= this.mobileBreakpoint) { 268 parentLi.addEventListener("mouseenter", () => { 269 if (!this.isMobileNav) { 270 this.openDropdown(subDropdown, parentLi); 271 } 272 }); 273 parentLi.addEventListener("mouseleave", () => { 274 if (!this.isMobileNav) { 275 this.closeDropdown(subDropdown, parentLi); 276 } 277 }); 278 } 279 }); 280 } 281 /** 282 * Ãffnet ein Dropdown 283 */ 284 openDropdown(dropdown, parentItem) { 285 if (this.isTransitioning) return; 286 if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) { 287 const siblings = Array.from(parentItem.parentElement.children);
288 siblings.forEach((sibling) => { 289 if (sibling !== parentItem) { 290 const siblingDropdown = sibling.querySelector(".dropdown-menu"); 291 if (siblingDropdown) { 292 this.closeDropdown(siblingDropdown, sibling); 293 } 294 } 295 }); 296 } 297 dropdown.classList.add("show"); 298 if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) { 299 dropdown.style.display = "flex"; 300 } else { 301 dropdown.style.display = "block"; 302 } 303 dropdown.removeAttribute("inert"); 304 parentItem.classList.add("open"); 305 const button = parentItem.querySelector(".dropdown-toggle"); 306 if (button) { 307 button.setAttribute("aria-expanded", "true"); 308 } 309 if (!this.openDropdowns.includes(dropdown)) { 310 this.openDropdowns.push(dropdown); 311 } 312 if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) { 313 requestAnimationFrame(() => { 314 }); 315 } 316 } 317 /** 318 * SchlieÃt ein Dropdown 319 */ 320 closeDropdown(dropdown, parentItem = null) { 321 if (!dropdown) return; 322 if (!parentItem) { 323 parentItem = dropdown.closest("li"); 324 } 325 if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) { 326 setTimeout(() => { 327 dropdown.classList.remove("show"); 328 dropdown.style.display = "none"; 329 if (parentItem) { 330 parentItem.classList.remove("open"); 331 } 332 }, 300); 333 } else { 334 dropdown.classList.remove("show"); 335 dropdown.style.display = "none"; 336 if (dropdown.classList.contains("dropdown-menu--lvl-2")) { 337 dropdown.setAttribute("inert", ""); 338 } 339 if (parentItem) { 340 parentItem.classList.remove("open"); 341 } 342 } 343 const button = parentItem?.querySelector(".dropdown-toggle"); 344 if (button) { 345 button.setAttribute("aria-expanded", "false"); 346 } 347 const index = this.openDropdowns.indexOf(dropdown); 348 if (index > -1) { 349 this.openDropdowns.splice(index, 1); 350 } 351 const subDropdowns = dropdown.querySelectorAll(".dropdown-menu.show"); 352 subDropdowns.forEach((sub) => this.closeDropdown(sub)); 353 } 354 /** 355 * Toggle Dropdown 356 */ 357 toggleDropdown(dropdown, parentItem) { 358 if (dropdown.classList.contains("show")) { 359 this.closeDropdown(dropdown, parentItem); 360 } else { 361 this.openDropdown(dropdown, parentItem); 362 } 363 } 364 /** 365 * SchlieÃt alle offenen Dropdowns 366 */ 367 closeAllDropdowns() { 368 [...this.openDropdowns].forEach((dropdown) => { 369 this.closeDropdown(dropdown); 370 }); 371 } 372 /** 373 * Toggle Mobile Navigation 374 */ 375 toggleMobileNav() { 376 this.navbar.classList.toggle("show"); 377 const isOpen = this.navbar.classList.contains("show"); 378 if (this.burgerButton) { 379 this.burgerButton.setAttribute("aria-expanded", isOpen); 380 this.burgerButton.classList.toggle("collapsed"); 381 } 382 if (!isOpen) { 383 this.closeAllDropdowns(); 384 } 385 } 386 /** 387 * Prüft ob Navigation in Container passt 388 */ 389 checkNavFit() { 390 if (!this.navList) return; 391 const windowWidth = window.innerWidth; 392 if (windowWidth < this.mobileBreakpoint) { 393 this.switchToMobile(); 394 return; 395 } 396 const clone = this.navList.cloneNode(true); 397 clone.style.position = "absolute"; 398 clone.style.visibility = "hidden"; 399 clone.style.display = "flex"; 400 clone.style.flexDirection = "row"; 401 clone.style.width = "auto"; 402 clone.style.whiteSpace = "nowrap"; 403 const cloneInnerDropdowns = clone.querySelectorAll(".dropdown-menu"); 404 cloneInnerDropdowns.forEach((cloneInnerDropdown) => { 405 cloneInnerDropdown.style.display = "none"; 406 }); 407 clone.classList.add("navbar-nav-cloned"); 408 document.body.appendChild(clone); 409 let totalWidth = 0; 410 const items = clone.querySelectorAll(".nav-item"); 411 items.forEach((item) => { 412 item.style.display = "block"; 413 const rect = item.getBoundingClientRect(); 414 totalWidth += rect.width; 415 }); 416 document.body.removeChild(clone); 417 const containerWidth = windowWidth - 160; 418 if (totalWidth > containerWidth) { 419 this.switchToMobile(); 420 } else { 421 this.switchToDesktop(); 422 } 423 } 424 /** 425 * Wechselt zur Mobile-Darstellung 426 */ 427 switchToMobile() { 428 if (this.isMobileNav) return; 429 this.navbar.querySelectorAll(".navbar-nav").forEach((navElement) => { 430 navElement.classList.remove("loaded"); 431 }); 432 this.isMobileNav = true; 433 document.body.classList.add("body-mobile-nav"); 434 document.body.classList.remove("body-desktop-nav"); 435 this.navbar.classList.add("mobile-nav"); 436 this.navbar.classList.remove("desktop-nav", "show"); 437 if (this.burgerButton) { 438 this.burgerButton.style.display = "flex"; 439 this.burgerButton.classList.add("collapsed"); 440 } 441 this.closeAllDropdowns(); 442 this.navbar.querySelectorAll(".dropdown-menu"); 443 const dropdownToggles = this.navbar.querySelectorAll(".dropdown-toggle");
444 dropdownToggles.forEach((dropdownToggle) => dropdownToggle.setAttribute("tabindex", "0")); 445 const backButtons = this.navList.querySelectorAll(".mobile-back-button"); 446 backButtons.forEach((btn) => btn.style.display = "block"); 447 setTimeout(() => { 448 this.navbar.querySelectorAll(".navbar-nav").forEach((navElement) => { 449 navElement.classList.add("loaded"); 450 }); 451 }, 1e3); 452 } 453 /** 454 * Wechselt zur Desktop-Darstellung 455 */ 456 switchToDesktop() { 457 this.isMobileNav = false; 458 document.body.classList.add("body-desktop-nav"); 459 document.body.classList.remove("body-mobile-nav"); 460 this.navbar.classList.remove("mobile-nav"); 461 this.navbar.classList.add("desktop-nav", "show"); 462 if (this.burgerButton) { 463 this.burgerButton.style.display = "none"; 464 } 465 this.closeAllDropdowns(); 466 const backButtons = this.navList.querySelectorAll(".mobile-back-button"); 467 backButtons.forEach((btn) => btn.style.display = "none"); 468 const dropdownToggles = this.navbar.querySelectorAll(".dropdown-toggle"); 469 dropdownToggles.forEach((dropdownToggle) => dropdownToggle.setAttribute("tabindex", "-1")); 470 const dropdowns = this.navList.querySelectorAll(".dropdown-menu"); 471 dropdowns.forEach((dropdown) => { 472 if (dropdown.classList.contains("dropdown-menu--lvl-2")) { 473 dropdown.setAttribute("inert", ""); 474 } 475 }); 476 this.navbar.querySelectorAll(".navbar-nav").forEach((navElement) => { 477 navElement.classList.add("loaded"); 478 }); 479 } 480 /** 481 * Setup Resize Observer für bessere Performance 482 */ 483 setupResizeObserver() { 484 let resizeTimer; 485 window.addEventListener("resize", () => { 486 clearTimeout(resizeTimer); 487 resizeTimer = setTimeout(() => { 488 this.closeAllDropdowns(); 489 this.checkNavFit(); 490 }, 250); 491 }); 492 setTimeout(() => { 493 this.checkNavFit(); 494 }, 100); 495 } 496} 497document.addEventListener("DOMContentLoaded", () => { 498 window.navManager = new NavigationManager(); 499}); 500//# sourceMappingURL=desktop-navigation-new.js.map
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.