export function initNavOverflow() { const cluster = document.querySelector(".header__cluster"); const nav = document.querySelector("[data-top-nav]"); const moreWrap = document.querySelector("[data-nav-more]"); const moreBtn = document.querySelector("[data-nav-more-btn]"); const menuDock = document.querySelector("[data-nav-more-menu]"); const menuFloating = document.querySelector("[data-nav-more-menu-floating]"); const headerEl = document.querySelector(".header"); if (!nav || !moreWrap || !moreBtn || !menuDock || !menuFloating || !headerEl) return; const links = Array.from(nav.querySelectorAll("a")); const MENU_GAP = 6; function getEdgePad() { const raw = getComputedStyle(document.documentElement).getPropertyValue("--edge-pad").trim(); if (!raw) return 8; // resolve через временный элемент, т.к. значение — max(...clamp...) const probe = document.createElement("span"); probe.style.all = "unset"; probe.style.position = "fixed"; probe.style.left = raw; document.body.appendChild(probe); const px = probe.getBoundingClientRect().left; probe.remove(); return Math.max(0, Math.round(px)); } let VIEWPORT_PAD = getEdgePad(); let openInFloatingMode = null; let resizeRaf = 0; const isFloatingHeader = () => headerEl.classList.contains("header--scrolled"); const moveOverflowToFloating = () => { while (menuDock.firstChild) menuFloating.appendChild(menuDock.firstChild); }; const moveOverflowToDock = () => { while (menuFloating.firstChild) menuDock.appendChild(menuFloating.firstChild); }; function closeMore() { moveOverflowToDock(); menuDock.hidden = true; menuFloating.hidden = true; menuFloating.setAttribute("aria-hidden", "true"); moreBtn.setAttribute("aria-expanded", "false"); openInFloatingMode = null; menuFloating.style.removeProperty("--nav-more-menu-left"); menuFloating.style.removeProperty("--nav-more-menu-top"); } function placeMoreMenu() { if (menuFloating.hidden) return; const btnRect = moreBtn.getBoundingClientRect(); const menuRect = menuFloating.getBoundingClientRect(); const vw = document.documentElement.clientWidth; const vh = document.documentElement.clientHeight; let left = btnRect.right - menuRect.width; left = Math.min(left, vw - menuRect.width - VIEWPORT_PAD); left = Math.max(VIEWPORT_PAD, left); let top = btnRect.bottom + MENU_GAP; if (top + menuRect.height > vh - VIEWPORT_PAD) top = Math.max(VIEWPORT_PAD, btnRect.top - MENU_GAP - menuRect.height); menuFloating.style.setProperty("--nav-more-menu-left", `${Math.round(left)}px`); menuFloating.style.setProperty("--nav-more-menu-top", `${Math.round(top)}px`); } function navGapPx() { const cs = getComputedStyle(nav); const col = parseFloat(cs.columnGap); if (Number.isFinite(col)) return col; const row = parseFloat(cs.rowGap); if (Number.isFinite(row)) return row; const g = cs.gap; if (!g || g === "normal") return 0; const parts = String(g).trim().split(/\s+/); const n = parseFloat(parts.length > 1 ? parts[1] : parts[0]); return Number.isFinite(n) ? n : 0; } function rowContentWidth(el) { const gap = navGapPx(); const kids = Array.from(el.children); let total = 0; kids.forEach((child, i) => { total += child.getBoundingClientRect().width; if (i > 0) total += gap; }); return total; } function layoutLinks() { closeMore(); links.forEach((link) => nav.appendChild(link)); menuDock.innerHTML = ""; menuFloating.innerHTML = ""; moreWrap.hidden = true; if (rowContentWidth(nav) <= nav.clientWidth + 0.5) return; moreWrap.hidden = false; void nav.offsetWidth; while (rowContentWidth(nav) > nav.clientWidth + 0.5 && nav.children.length > 1) { const last = nav.lastElementChild; if (!last) break; menuDock.prepend(last); } } moreBtn.addEventListener("click", (e) => { e.stopPropagation(); const open = menuDock.hidden && menuFloating.hidden; if (open) { openInFloatingMode = isFloatingHeader(); if (openInFloatingMode) { moveOverflowToFloating(); menuFloating.hidden = false; menuFloating.setAttribute("aria-hidden", "false"); } else { menuDock.hidden = false; } moreBtn.setAttribute("aria-expanded", "true"); requestAnimationFrame(() => { placeMoreMenu(); requestAnimationFrame(placeMoreMenu); }); } else { closeMore(); } }); document.addEventListener("click", (e) => { const t = e.target; if (!(t instanceof Node)) return; if (moreWrap.contains(t) || menuDock.contains(t) || menuFloating.contains(t)) { const el = t instanceof Element ? t : t.parentElement; if (el?.closest(".nav-more__dock a, .nav-more__floating a")) closeMore(); return; } closeMore(); }); const onMenuLinkClick = (e) => { const t = e.target; const el = t instanceof Element ? t : t.parentElement; const a = el?.closest?.("a[href]"); if (a && (menuDock.contains(a) || menuFloating.contains(a))) closeMore(); }; menuDock.addEventListener("click", onMenuLinkClick); menuFloating.addEventListener("click", onMenuLinkClick); const onResize = () => { if (resizeRaf) cancelAnimationFrame(resizeRaf); resizeRaf = requestAnimationFrame(() => { VIEWPORT_PAD = getEdgePad(); layoutLinks(); placeMoreMenu(); resizeRaf = 0; }); }; window.addEventListener("resize", onResize, { passive: true }); window.addEventListener( "scroll", () => { requestAnimationFrame(() => { if (openInFloatingMode !== null && isFloatingHeader() !== openInFloatingMode) closeMore(); else placeMoreMenu(); }); }, { passive: true }, ); window.addEventListener("load", layoutLinks, { once: true }); layoutLinks(); if (typeof ResizeObserver === "function" && cluster) { const ro = new ResizeObserver(() => { if (!menuDock.hidden || !menuFloating.hidden) return; layoutLinks(); }); ro.observe(cluster); } }