Extract binder package from citation-evidence (EBIND-WP-0001)
Extracts citation-evidence/src/binder/ into this repo as the standalone @citation-evidence/evidence-binder package: headless binding service + in-memory link repo, active-state machine, the SharedContracts §7 rect-registry contract (registry, change pumps, hooks, SVG overlay), and the target-neutral reference FormRenderer. - toolchain mirrors sibling extracted repos (pnpm/tsc/vitest/eslint); imports rewritten from @shared/@engine aliases to the engine's @citation-evidence/engine package specifiers - dependency boundary (engine + anchor only; no source/work/umbrella) enforced via eslint no-restricted-imports - docs: extraction inventory + contract deltas, ADR-0001 (reference UI kept as supported exports), refreshed README/SCOPE/INTENT, populated capabilities index - typecheck + lint green, 37 tests passing Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
87d4eb3006
commit
a10f080f12
36 changed files with 6643 additions and 170 deletions
98
src/visual-guide/react-hooks.ts
Normal file
98
src/visual-guide/react-hooks.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* 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<RectRegistryContextValue | null>(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 <RectRegistryProvider />",
|
||||
);
|
||||
}
|
||||
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<Element | null>,
|
||||
): 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue