1(() => { 2 let activeMenu = null 3 4 document.querySelectorAll("[data-services-nav], [data-mobile-catalog]").forEach(menu => { 5 const trigger = menu.querySelector(".nav-services-trigger") 6 const panel = menu.querySelector("[data-services-panel]") 7 const link = menu.querySelector("[data-catalog-link]") 8 const mobile = menu.hasAttribute("data-mobile-catalog") 9 if (!trigger || !panel) return 10 let closeTimer 11 let imagesPrepared = false 12 13 const controller = { menu, trigger, close: () => setOpen(false) } 14 const setOpen = open => { 15 window.clearTimeout(closeTimer) 16 if (open) { 17 if (activeMenu !== controller) activeMenu?.close() 18 activeMenu = controller 19 if (!imagesPrepared) { 20 panel.querySelectorAll("[data-service-image]").forEach(item => { 21 item.style.setProperty("--service-image", `url("${item.dataset.serviceImage}")`) 22 if (item.dataset.serviceFit) item.style.setProperty("--service-fit", item.dataset.serviceFit) 23 }) 24 imagesPrepared = true 25 } 26 } else if (activeMenu === controller) { 27 activeMenu = null 28 } 29 menu.classList.toggle("is-open", open) 30 trigger.setAttribute("aria-expanded", String(open)) 31 panel.setAttribute("aria-hidden", String(!open)) 32 panel.inert = !open 33 if (mobile) panel.hidden = !open 34 } 35 36 const openOnHover = event => { 37 if (event.pointerType === "mouse" || event.pointerType === "pen") setOpen(true) 38 } 39 const scheduleClose = () => { 40 window.clearTimeout(closeTimer) 41 closeTimer = window.setTimeout(() => { 42 if (!menu.matches(":hover") && !menu.contains(document.activeElement)) setOpen(false) 43 }, 220) 44 } 45 46 trigger.addEventListener("click", () => setOpen(!menu.classList.contains("is-open"))) 47 if (!mobile) { 48 menu.addEventListener("pointerenter", openOnHover) 49 panel.addEventListener("pointerenter", openOnHover) 50 menu.addEventListener("pointerleave", scheduleClose) 51 panel.addEventListener("pointerleave", scheduleClose) 52 } 53 menu.addEventListener("focusout", event => { 54 if (!menu.contains(event.relatedTarget)) setOpen(false) 55 }) 56 menu.addEventListener("keydown", event => { 57 if (event.key !== "ArrowDown" || (event.target !== trigger && event.target !== link)) return 58 event.preventDefault() 59 setOpen(true) 60 panel.querySelector("a[href], button:not([disabled])")?.focus() 61 }) 62 setOpen(false) 63 }) 64 65 document.addEventListener("click", event => { 66 if (activeMenu && !activeMenu.menu.contains(event.target)) activeMenu.close() 67 }) 68 document.addEventListener("keydown", event => { 69 if (event.key !== "Escape" || !activeMenu) return 70 const { menu, trigger, close } = activeMenu 71 const restoreFocus = menu.contains(document.activeElement) 72 event.preventDefault() 73 close() 74 if (restoreFocus) trigger.focus() 75 }) 76 window.matchMedia("(min-width: 1201px)").addEventListener("change", ({ matches }) => { 77 activeMenu?.close() 78 if (!matches) return 79 document.body.classList.remove("menu-open") 80 document.querySelector(".menu-button")?.setAttribute("aria-expanded", "false") 81 const mobileMenu = document.querySelector(".mobile-menu") 82 mobileMenu?.setAttribute("aria-hidden", "true") 83 mobileMenu?.querySelectorAll("details").forEach(details => { details.open = false }) 84 }) 85})()
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.