import { type RefObject, useLayoutEffect, useRef, useSyncExternalStore } from "react"; // The browser tab names the place AND the object in it (DESIGN § Composition and the reader, "Where am // I"): "Nicolas Cage · Plex · Siftlode". The place is the module (NavigationProvider); the object is the // view's own visible title, so it is read from the element that already shows it (`PageHeading`), not // handed over a second time by every view — a view with a title on screen names the tab by having it. // The latest mounted title wins (a view stacked over another); an empty one (still loading) is skipped. type Entry = { text: string }; let entries: Entry[] = []; const subs = new Set<() => void>(); function emit(): void { for (const cb of subs) cb(); } function snapshot(): string | null { let found: string | null = null; for (const e of entries) if (e.text) found = e.text; return found; } /** The object the screen shows, for the tab title; null when the view has no title of its own. */ export function useDocumentObjectTitle(): string | null { return useSyncExternalStore( (cb) => { subs.add(cb); return () => subs.delete(cb); }, snapshot, snapshot, ); } /** Names the tab after what `el` shows (its text), or after `override` when given (a season page's * heading is "Season 1"; the tab wants the show too). Re-read after every render, so a title that * arrives with its data, or changes in place, follows. */ export function useNameTabAfter(el: RefObject, override?: string): void { const entry = useRef(null); useLayoutEffect(() => { const e: Entry = { text: "" }; entry.current = e; entries = [...entries, e]; return () => { entries = entries.filter((x) => x !== e); entry.current = null; emit(); }; }, []); // No dependency list: the text is the DOM's, read after each render of the heading. useLayoutEffect(() => { const e = entry.current; const text = (override ?? el.current?.textContent ?? "").replace(/\s+/g, " ").trim(); if (!e || e.text === text) return; e.text = text; emit(); }); }