Files
portifolio-evandrodevbr/app/components/cinematic/useHomeFragmentNavigation.ts
T
Evandro Fonseca Junior 8b12e51bc5
CI / verify (push) Failing after 5m9s
feat: refine portfolio showcase, services and quality lab
Align the notebook browser with the physical LCD, refresh real project captures and navigation, simplify service content, and stabilize hero hydration.
2026-09-05 14:20:54 -03:00

126 lines
3.8 KiB
TypeScript

"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?.();
}, []);
}