/* * Âncoras da home (links #id para a mesma página), num lugar só. * * - Um único ouvinte de clique no documento (capturando, antes de qualquer menu remover o link ativado). Quem quer * reagir a um clique num link de âncora se inscreve com `onFragmentClick`: a rolagem suave (useCinematicScroll) e a * chegada por fragmento (useHomeFragmentNavigation) recebem o mesmo clique já interpretado, sem cada uma ler o * evento de novo. * - Um único dono de `history.scrollRestoration`: o script que roda antes da hidratação (`FRAGMENT_NAV_BOOT_SCRIPT`, * que app/layout.tsx põe no
) e `setScrollRestoration`, que os ganchos usam. Nenhum outro código escreve nele. * * Sem "use client": o layout (servidor) importa só a constante do script; o resto roda no navegador. */ /** Clique do botão principal, sem tecla modificadora, que nenhum outro código já tratou. */ export function isPlainPrimaryClick(event: MouseEvent) { return !( event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ); } /** O link () que o clique atingiu, se abre na mesma aba e não baixa arquivo; `null` para qualquer outro clique. */ function sameTabLink(event: MouseEvent) { const link = event.target instanceof Element ? event.target.closest("a[href]") : null; if (!(link instanceof HTMLAnchorElement) || link.hasAttribute("download")) return null; return !link.target || link.target === "_self" ? link : null; } /** O `#id` de um link para esta mesma página (mesmo caminho e mesma busca); `null` se leva a outro lugar ou não tem âncora. */ function hashOnThisPage(link: HTMLAnchorElement) { const url = new URL(link.href); const same = url.origin === location.origin && url.pathname === location.pathname && url.search === location.search; return same && url.hash ? url.hash : null; } /** O elemento que um `#id` aponta (aceita percent-encoding); `null` se não existe ou o hash é malformado. */ export function elementForHash(hash: string) { if (!hash) return null; try { return document.getElementById(decodeURIComponent(hash.slice(1))); } catch { return null; } } export type FragmentHit = { target: HTMLElement; hash: string }; /** Link para outro ponto da MESMA página (`#id`): o elemento de destino e o hash; `null` para qualquer outro clique. */ export function samePageHashTarget(event: MouseEvent): FragmentHit | null { const link = sameTabLink(event); const hash = link && hashOnThisPage(link); const target = hash ? elementForHash(hash) : null; return hash && target ? { target, hash } : null; } export type FragmentClickHandler = ( hit: FragmentHit, event: MouseEvent, ) => void; const handlers = new Set