"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 }); } /** * A document owns one outstanding homepage arrival. Later mounts are same-document * history, where the browser restores its own position. */ let arrivalSettled = false; /** Tests replay several documents inside one jsdom document. */ export function resetHomeArrivalState() { arrivalSettled = false; } /** 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. const restoration = navigation?.type === "back_forward"; const arrival = !restoration && !arrivalSettled; let pending = restoration ? "" : window.location.hash; let pendingTop = arrival && !window.location.hash; if (arrival) window.history.scrollRestoration = "manual"; const resolve = () => { if (!ready.current) return; if (pendingTop) { pendingTop = false; if (!window.location.hash) window.scrollTo({ top: 0, left: 0, behavior: "instant" }); } if (!pending) return; const hash = pending; pending = ""; if (hash !== window.location.hash) return; const target = fragmentTarget(hash); if (!target) return; focusFragment(target); const padding = Number.parseFloat( getComputedStyle(document.documentElement).scrollPaddingTop, ) || 0; const margin = Number.parseFloat(getComputedStyle(target).scrollMarginTop) || 0; window.scrollTo({ top: Math.max( 0, window.scrollY + target.getBoundingClientRect().top - padding - margin, ), left: 0, behavior: "instant", }); }; const cancelArrival = () => { pending = ""; pendingTop = false; window.history.scrollRestoration = "auto"; }; 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; pendingTop = false; pending = ready.current ? "" : url.hash; // Capture before a mobile menu removes its activated link. Do not cancel navigation. focusFragment(target); }; const onHashChange = () => { pendingTop = false; 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; window.history.scrollRestoration = "auto"; 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?.(); arrivalSettled = true; window.history.scrollRestoration = "auto"; }, []); }