/** * React hooks for the rect registry. * * Components mount, get a ref to a DOM node, and ask the registry to * track it via `useRegisterRect(kind, id, ref)`. Unmount/ref-change * unregisters automatically. * * The registry itself lives behind a React context so multiple subtrees * can share one registry (the overlay sees what every renderer publishes). */ import { createContext, useCallback, useContext, useEffect, useSyncExternalStore, type RefObject, } from "react"; import { createRectRegistry, type RectKind, type RectRegistry, } from "./rect-registry"; import { attachRectChangePumps, type RectChangeObserverHandle } from "./events"; export interface RectRegistryContextValue { readonly registry: RectRegistry; readonly observer: RectChangeObserverHandle; } const RectRegistryContext = createContext(null); /** * Create an isolated registry + change pump pair for tests or app * composition roots that wire their own provider. */ export function createRectRegistryContextValue(): RectRegistryContextValue { const registry = createRectRegistry(); const observer = attachRectChangePumps(registry); return { registry, observer }; } export function useRectRegistryContext(): RectRegistryContextValue { const ctx = useContext(RectRegistryContext); if (!ctx) { throw new Error( "useRectRegistryContext must be used inside ", ); } return ctx; } export const RectRegistryProvider = RectRegistryContext.Provider; /** * Register a DOM ref's bounding rect with the registry. * * Re-runs when `kind`/`id`/`ref.current` change. The observer also starts * watching the element for scroll/resize so the overlay can re-query * without polling. */ export function useRegisterRect( kind: RectKind, id: string, ref: RefObject, ): void { const { registry, observer } = useRectRegistryContext(); useEffect(() => { const el = ref.current; if (!el) return; const unregister = registry.register(kind, id, () => el.getBoundingClientRect()); const unobserve = observer.observe(el); return () => { unobserve(); unregister(); }; }, [kind, id, ref, registry, observer]); } /** * Subscribe to registry change events from inside React. Returns a * monotonically-increasing version number that bumps on every event, so * `useMemo`/`useEffect` deps can include it to re-derive cached values. * * Implementation: leans on `registry.getVersion()` for the snapshot so * `useSyncExternalStore` doesn't accumulate per-render subscribers. */ export function useRectRegistryVersion(): number { const { registry } = useRectRegistryContext(); const subscribe = useCallback( (callback: () => void) => registry.subscribe(callback), [registry], ); const getSnapshot = useCallback(() => registry.getVersion(), [registry]); return useSyncExternalStore(subscribe, getSnapshot, () => 0); }