"use client"; import { useCallback, useEffect, useRef } from "react"; function fragmentTarget(hash: string) { if (!hash) return null; try { return document.getElementById(decodeURIComponent(hash.slice(1))); } catch { return null; } } function focusFragment(target: HTMLElement) { if (!target.hasAttribute("tabindex")) target.tabIndex = -1; target.focus({ preventScroll: true }); } /** Correct only an outstanding arrival; ordinary links and history keep native scrolling. */ export function useHomeFragmentNavigation() { const ready = useRef(false); const resolveArrival = useRef<(() => void) | null>(null); useEffect(() => { const navigation = performance.getEntriesByType?.("navigation")[0] as | PerformanceNavigationTiming | undefined; // A history restoration owns its saved position, not the URL's old fragment. let pending = navigation?.type === "back_forward" ? "" : window.location.hash; const resolve = () => { if (!ready.current || !pending) return; const hash = pending; pending = ""; if (hash !== window.location.hash) return; const target = fragmentTarget(hash); if (!target) return; focusFragment(target); target.scrollIntoView({ block: "start", behavior: "instant" }); }; const cancelArrival = () => { pending = ""; }; const onKeyDown = (event: KeyboardEvent) => { if ( [ "ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " ", "Tab", ].includes(event.key) ) { cancelArrival(); } }; const onClick = (event: MouseEvent) => { if ( event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) return; const link = event.target instanceof Element ? event.target.closest("a[href]") : null; if ( !(link instanceof HTMLAnchorElement) || link.hasAttribute("download") || (link.target && link.target !== "_self") ) return; const url = new URL(link.href); if ( url.origin !== window.location.origin || url.pathname !== window.location.pathname || url.search !== window.location.search ) return; const target = fragmentTarget(url.hash); if (!target) return; pending = ready.current ? "" : url.hash; // Capture before a mobile menu removes its activated link. Do not cancel navigation. focusFragment(target); }; const onHashChange = () => { if (pending && pending !== window.location.hash) cancelArrival(); if (!ready.current) return; const target = fragmentTarget(window.location.hash); if (target) focusFragment(target); }; resolveArrival.current = resolve; resolve(); document.addEventListener("click", onClick, true); window.addEventListener("hashchange", onHashChange); window.addEventListener("wheel", cancelArrival, { passive: true }); window.addEventListener("touchmove", cancelArrival, { passive: true }); window.addEventListener("keydown", onKeyDown); window.addEventListener("pointerdown", cancelArrival, { passive: true }); return () => { resolveArrival.current = null; document.removeEventListener("click", onClick, true); window.removeEventListener("hashchange", onHashChange); window.removeEventListener("wheel", cancelArrival); window.removeEventListener("touchmove", cancelArrival); window.removeEventListener("keydown", onKeyDown); window.removeEventListener("pointerdown", cancelArrival); }; }, []); return useCallback(() => { ready.current = true; resolveArrival.current?.(); }, []); }