import { useCallback, useEffect, useRef } from "react";
import { useLocation } from "react-router-dom";
/**
* Manages SPA route-change accessibility: updates document.title from the
* page's
, moves focus to it, and announces the navigation via an
* aria-live region.
*
* Returns a ref to attach to a visually-hidden announcer element:
* `
`
*/
export const useRouteAnnouncer = () => {
const { pathname } = useLocation();
const announcerRef = useRef(null);
const isFirstRender = useRef(true);
const announce = useCallback((text: string) => {
if (announcerRef.current) {
announcerRef.current.textContent = text;
}
}, []);
useEffect(() => {
// Skip the initial mount — only announce subsequent navigations.
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
// Wait a tick for the new page component to render its .
const id = requestAnimationFrame(() => {
const h1 = document.querySelector("h1");
const title = h1?.textContent?.trim() ?? "anvil";
document.title = title === "anvil" ? "anvil" : `${title} — anvil`;
if (h1) {
if (!h1.hasAttribute("tabindex")) {
h1.setAttribute("tabindex", "-1");
}
h1.focus({ preventScroll: true });
}
announce(`Navigated to ${title}`);
});
return () => cancelAnimationFrame(id);
}, [pathname, announce]);
return announcerRef;
};