@modernrelay/orbit-react 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Minimap.js +10 -5
- package/dist/components/Minimap.js.map +1 -1
- package/dist/components/Navigator.js +8 -1
- package/dist/components/Navigator.js.map +1 -1
- package/dist/components/Tooltip.js +2 -1
- package/dist/components/Tooltip.js.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.js +38 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -34,7 +34,7 @@ function GraphMinimap(props) {
|
|
|
34
34
|
const rootRef = useRef(null);
|
|
35
35
|
const canvasRef = useRef(null);
|
|
36
36
|
const rectRef = useRef(null);
|
|
37
|
-
const
|
|
37
|
+
const draggingPointerRef = useRef(null);
|
|
38
38
|
const declaredSceneRef = useRef({ ids: [], scene: null });
|
|
39
39
|
const visibleRef = useRef({
|
|
40
40
|
ids: [],
|
|
@@ -160,8 +160,10 @@ function GraphMinimap(props) {
|
|
|
160
160
|
instance.setViewport({ x: world[0], y: world[1] });
|
|
161
161
|
};
|
|
162
162
|
const onPointerDown = (e) => {
|
|
163
|
+
const owner = draggingPointerRef.current;
|
|
164
|
+
if (owner !== null && owner !== e.pointerId) return;
|
|
163
165
|
e.preventDefault();
|
|
164
|
-
|
|
166
|
+
draggingPointerRef.current = e.pointerId;
|
|
165
167
|
try {
|
|
166
168
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
167
169
|
} catch {
|
|
@@ -169,11 +171,12 @@ function GraphMinimap(props) {
|
|
|
169
171
|
panTo(e);
|
|
170
172
|
};
|
|
171
173
|
const onPointerMove = (e) => {
|
|
172
|
-
if (
|
|
174
|
+
if (draggingPointerRef.current !== e.pointerId) return;
|
|
173
175
|
panTo(e);
|
|
174
176
|
};
|
|
175
|
-
const endDrag = () => {
|
|
176
|
-
|
|
177
|
+
const endDrag = (e) => {
|
|
178
|
+
if (draggingPointerRef.current !== e.pointerId) return;
|
|
179
|
+
draggingPointerRef.current = null;
|
|
177
180
|
};
|
|
178
181
|
return /* @__PURE__ */ jsxs(
|
|
179
182
|
"div",
|
|
@@ -208,6 +211,8 @@ function GraphMinimap(props) {
|
|
|
208
211
|
onPointerDown,
|
|
209
212
|
onPointerMove,
|
|
210
213
|
onPointerUp: endDrag,
|
|
214
|
+
onPointerCancel: endDrag,
|
|
215
|
+
onLostPointerCapture: endDrag,
|
|
211
216
|
onPointerLeave: endDrag
|
|
212
217
|
}
|
|
213
218
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Minimap/index.tsx"],"names":[],"mappings":"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","file":"Minimap.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Minimap` — `<GraphMinimap>`, the\n * whole-graph thumbnail + viewport rectangle.\n *\n * v0.9 is CPU-fallback-only (no engine exposes `capabilities.overviewPass`\n * cosmos matrix): the component owns an {@link OverviewController} and\n * blits its `rasterize` output into a small canvas via `putImageData`. The\n * three lanes stay strictly decoupled:\n *\n * 1. **Thumbnail refresh** — a 500 ms `setInterval` probes the controller's\n * `shouldRefresh(now, simulationRunning, epoch)` gate, so the effective\n * cadence is the controller's spec budget (≤ 2 Hz hot, ≤ 1 Hz idle, zero\n * work idle-with-unchanged-epoch) and each `true` is followed by exactly\n * one `rasterize` + blit. The interval — not a per-frame engine lane —\n * is the documented v0.9 cadence: the `onFrame` fan-out only reaches\n * React through the label lane, which is gated on labels being enabled,\n * so a plain timer is the correct always-on ≤ 2 Hz driver. The epoch is\n * `revisions.render` (a positions-bearing superset — over-approximate but\n * bounded by the same throttle).\n * 2. **Viewport rectangle** — an imperative store subscription writes the\n * rect's transform/size O(1) per `store.viewport` publication (CSS\n * transform on a ref; no React re-render, no re-rasterize).\n * 3. **Pan** — pointer down/drag maps minimap px → world via the\n * controller's `minimapToWorld` (exact inverse over the last rasterized\n * frame) and calls `instance.setViewport({x, y})` — real camera pans.\n *\n * Scene source (documented v0.9 trim): the instance does not yet expose the\n * posBuf mirror (reserved for the columnar lane), so the\n * component probes a pinned structural seam `getOverviewScene` first\n * (optional access — picked up automatically once core lands\n * it) and falls back to the DECLARED node positions (`GraphNode.x/y`) of the\n * current scene roster — exact for prepared/fixed-position datasets from\n * orbit-data, and empty for force layouts without a position mirror.\n * Position-less nodes rasterize as NaN tombstones (skipped); mask-hidden nodes draw dimmed\n * via `getVisible`.\n */\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport type { CSSProperties, PointerEvent as ReactPointerEvent, ReactElement } from 'react';\nimport { OverviewController, OVERVIEW_HOT_INTERVAL_MS } from '@modernrelay/orbit-core';\nimport type { NodeId, OverviewScene, ViewportState } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { cornerStyle, mergeStyle, useResolvedInstance } from '../shared';\nimport type { GraphCorner } from '../shared';\n\n/** Default thumbnail edge (px) — the component default; the CPU target\n * default (256²) applies when a caller constructs a bare controller. */\nconst SIZE_DEFAULT = 200;\n\n/** Minimum rendered viewport-rect edge so a tiny/degenerate viewport stays\n * visible as a marker. */\nconst MIN_RECT_PX = 6;\n\n/** Pinned structural seam: the instance-side\n * posBuf mirror read. Optional access until core lands it — this view\n * stays signature-compatible when the member becomes required. */\ninterface OverviewSceneSurface {\n getOverviewScene?(): OverviewScene | null;\n}\n\nexport interface GraphMinimapProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Square thumbnail edge in CSS px (also the raster resolution). Default 200. */\n size?: number;\n /** Accessible name. Default 'Graph minimap'. */\n label?: string;\n /** Corner this panel anchors to. Default 'bottom-right'. */\n position?: GraphCorner;\n /** Distance from the two anchored edges in CSS px. Default 12. */\n offset?: number;\n /** Class hook for the root; providing it drops the default styles\n * INCLUDING placement — position it yourself in CSS. */\n className?: string;\n /** Style overrides merged over the defaults; an inset you set here\n * wins over the one `position` implies (resolved per axis). */\n style?: CSSProperties;\n}\n\nconst ROOT_STYLE: CSSProperties = {\n pointerEvents: 'auto',\n overflow: 'hidden',\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.85)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n};\n\nconst CANVAS_STYLE: CSSProperties = {\n display: 'block',\n cursor: 'crosshair',\n touchAction: 'none',\n};\n\nconst RECT_STYLE: CSSProperties = {\n position: 'absolute',\n left: 0,\n top: 0,\n display: 'none',\n border: '1px solid rgba(255, 255, 255, 0.85)',\n background: 'rgba(255, 255, 255, 0.12)',\n pointerEvents: 'none',\n willChange: 'transform',\n boxSizing: 'border-box',\n};\n\n/** Declared-position scene over the current roster, cached by the roster's\n * identity (`scene.idByIndex` is a stable reference per reconciled scene). */\ninterface DeclaredSceneCache {\n ids: readonly NodeId[];\n scene: OverviewScene | null;\n}\n\nexport function GraphMinimap(props: GraphMinimapProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphMinimap>');\n const size = props.size ?? SIZE_DEFAULT;\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const rectRef = useRef<HTMLDivElement | null>(null);\n const draggingRef = useRef(false);\n const declaredSceneRef = useRef<DeclaredSceneCache>({ ids: [], scene: null });\n /** mask lane for `getVisible`, recomputed once per refresh tick. */\n const visibleRef = useRef<{ ids: readonly NodeId[]; set: ReadonlySet<NodeId> | null }>({\n ids: [],\n set: null,\n });\n\n const controller = useMemo(() => {\n const getScene = (): OverviewScene | null => {\n // 1. Pinned core seam: the posBuf mirror, once it lands.\n const mirrored = (instance as unknown as OverviewSceneSurface).getOverviewScene?.();\n if (mirrored !== undefined) return mirrored;\n // 2. v0.9 fallback: declared node positions over the scene roster.\n const ids = instance.getSceneNodeIds();\n if (ids.length === 0) return null;\n const cache = declaredSceneRef.current;\n if (cache.ids === ids) return cache.scene;\n const positions = new Float32Array(2 * ids.length);\n let known = 0;\n for (let i = 0; i < ids.length; i++) {\n const node = instance.getNode(ids[i]!);\n const x = node?.x;\n const y = node?.y;\n if (typeof x === 'number' && typeof y === 'number') {\n positions[2 * i] = x;\n positions[2 * i + 1] = y;\n known++;\n } else {\n positions[2 * i] = NaN;\n positions[2 * i + 1] = NaN;\n }\n }\n const scene: OverviewScene | null = known > 0 ? { positions, count: ids.length } : null;\n declaredSceneRef.current = { ids, scene };\n return scene;\n };\n const getVisible = (index: number): boolean => {\n const v = visibleRef.current;\n if (v.set === null) return true;\n const id = v.ids[index];\n return id === undefined ? true : v.set.has(id);\n };\n return new OverviewController({ getScene, getVisible, size });\n }, [instance, size]);\n\n /** O(1) rect update from the store viewport over the LAST rasterized frame.\n * `zoom` is screen px per space unit; the world extent in view is the\n * host container's CSS size / zoom — measured from the positioned ancestor\n * (the <Graph> container). Zero-size hosts (jsdom/unmounted) degrade to a\n * MIN_RECT_PX center marker. */\n const updateRect = (v: ViewportState | null): void => {\n const rect = rectRef.current;\n if (rect === null) return;\n if (v === null || v.zoom <= 0) {\n rect.style.display = 'none';\n return;\n }\n const host = rootRef.current?.offsetParent;\n const hostW = host instanceof HTMLElement ? host.clientWidth : 0;\n const hostH = host instanceof HTMLElement ? host.clientHeight : 0;\n const halfW = hostW > 0 ? hostW / v.zoom / 2 : 0;\n const halfH = hostH > 0 ? hostH / v.zoom / 2 : 0;\n // Two opposite world corners (world y grows up, minimap y grows down).\n const a = controller.worldToMinimap(v.x - halfW, v.y + halfH);\n const b = controller.worldToMinimap(v.x + halfW, v.y - halfH);\n if (a === null || b === null) {\n rect.style.display = 'none'; // nothing rasterized yet\n return;\n }\n const w = Math.max(Math.abs(b[0] - a[0]), MIN_RECT_PX);\n const h = Math.max(Math.abs(b[1] - a[1]), MIN_RECT_PX);\n const x = (a[0] + b[0]) / 2 - w / 2;\n const y = (a[1] + b[1]) / 2 - h / 2;\n rect.style.display = 'block';\n rect.style.width = `${w}px`;\n rect.style.height = `${h}px`;\n rect.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n };\n\n // Lane 1: thumbnail refresh — interval-probed controller throttle.\n useEffect(() => {\n const blit = (): void => {\n const state = instance.store.getState();\n // mask lane for the dimmed-alpha rasterization.\n if (state.visible.nodes !== state.nodeCount || state.hiddenNodeIds.size > 0) {\n const ids = instance.getSceneNodeIds();\n visibleRef.current = { ids, set: new Set(instance.getVisibleNodeIds()) };\n } else {\n visibleRef.current = { ids: [], set: null };\n }\n const raster = controller.rasterize();\n const canvas = canvasRef.current;\n if (canvas === null) return;\n const ctx = canvas.getContext('2d');\n if (ctx === null) return;\n if (raster === null) {\n ctx.clearRect(0, 0, size, size);\n updateRect(null);\n return;\n }\n if (typeof ImageData === 'undefined') return; // non-DOM environment\n // Copy: ImageData requires a plain-ArrayBuffer-backed clamped array.\n const pixels = new Uint8ClampedArray(raster.bitmap);\n ctx.putImageData(new ImageData(pixels, controller.size, controller.size), 0, 0);\n // A fresh frame can move the world→minimap mapping: re-derive the rect.\n updateRect(instance.store.getState().viewport);\n };\n const tick = (): void => {\n const state = instance.store.getState();\n // `revisions.render` is the v0.9 positions epoch (see module JSDoc):\n // idle with an unchanged render revision is provably position-stable.\n if (!controller.shouldRefresh(Date.now(), state.simulationRunning, state.revisions.render)) {\n return;\n }\n blit();\n };\n tick(); // first paint without waiting a full interval\n const timer = setInterval(tick, OVERVIEW_HOT_INTERVAL_MS);\n return () => {\n clearInterval(timer);\n };\n // updateRect is stable for each (instance, controller) pair — refs + controller\n // only — so it is deliberately not a dependency (this repo does not\n // configure the react-hooks lint plugin).\n }, [instance, controller, size]);\n\n // Lane 2: viewport rectangle — imperative store subscription, O(1) per\n // viewport publication, no re-rasterize and no React re-render.\n useEffect(() => {\n let prev = instance.store.getState().viewport;\n updateRect(prev);\n return instance.store.subscribe(() => {\n const next = instance.store.getState().viewport;\n if (next === prev) return;\n prev = next;\n updateRect(next);\n });\n // updateRect deliberately not a dependency (see the refresh effect above).\n }, [instance, controller]);\n\n // Lane 3: pan. Down pans immediately; move keeps panning while dragging.\n const panTo = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n const canvas = canvasRef.current;\n if (canvas === null) return;\n const b = canvas.getBoundingClientRect();\n // Canvas CSS size vs raster size (zero-size rects — jsdom — map 1:1).\n const sx = b.width > 0 ? controller.size / b.width : 1;\n const sy = b.height > 0 ? controller.size / b.height : 1;\n const world = controller.minimapToWorld((e.clientX - b.left) * sx, (e.clientY - b.top) * sy);\n if (world === null) return; // nothing rasterized yet — nothing to map\n instance.setViewport({ x: world[0], y: world[1] });\n };\n\n const onPointerDown = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n e.preventDefault();\n draggingRef.current = true;\n try {\n e.currentTarget.setPointerCapture(e.pointerId);\n } catch {\n /* pointer capture unavailable (jsdom/legacy) */\n }\n panTo(e);\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n if (!draggingRef.current) return;\n panTo(e);\n };\n\n const endDrag = (): void => {\n draggingRef.current = false;\n };\n\n return (\n <div\n ref={rootRef}\n data-orbit-minimap=\"\"\n role=\"group\"\n aria-label={props.label ?? 'Graph minimap'}\n className={props.className}\n style={\n props.className !== undefined\n ? // The thumbnail is MECHANICAL, not decorative: dropping its box\n // would collapse the canvas, so size survives the class hook.\n { width: size, height: size, ...props.style }\n : mergeStyle(\n {\n ...cornerStyle(props.position ?? 'bottom-right', props.offset),\n ...ROOT_STYLE,\n width: size,\n height: size,\n },\n props.style,\n )\n }\n >\n <canvas\n ref={canvasRef}\n data-orbit-minimap-canvas=\"\"\n width={size}\n height={size}\n style={CANVAS_STYLE}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerLeave={endDrag}\n />\n <div ref={rectRef} data-orbit-minimap-viewport=\"\" style={RECT_STYLE} />\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Minimap/index.tsx"],"names":[],"mappings":"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","file":"Minimap.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Minimap` — `<GraphMinimap>`, the\n * whole-graph thumbnail + viewport rectangle.\n *\n * v0.9 is CPU-fallback-only (no engine exposes `capabilities.overviewPass`\n * cosmos matrix): the component owns an {@link OverviewController} and\n * blits its `rasterize` output into a small canvas via `putImageData`. The\n * three lanes stay strictly decoupled:\n *\n * 1. **Thumbnail refresh** — a 500 ms `setInterval` probes the controller's\n * `shouldRefresh(now, simulationRunning, epoch)` gate, so the effective\n * cadence is the controller's spec budget (≤ 2 Hz hot, ≤ 1 Hz idle, zero\n * work idle-with-unchanged-epoch) and each `true` is followed by exactly\n * one `rasterize` + blit. The interval — not a per-frame engine lane —\n * is the documented v0.9 cadence: the `onFrame` fan-out only reaches\n * React through the label lane, which is gated on labels being enabled,\n * so a plain timer is the correct always-on ≤ 2 Hz driver. The epoch is\n * `revisions.render` (a positions-bearing superset — over-approximate but\n * bounded by the same throttle).\n * 2. **Viewport rectangle** — an imperative store subscription writes the\n * rect's transform/size O(1) per `store.viewport` publication (CSS\n * transform on a ref; no React re-render, no re-rasterize).\n * 3. **Pan** — pointer down/drag maps minimap px → world via the\n * controller's `minimapToWorld` (exact inverse over the last rasterized\n * frame) and calls `instance.setViewport({x, y})` — real camera pans.\n *\n * Scene source (documented v0.9 trim): the instance does not yet expose the\n * posBuf mirror (reserved for the columnar lane), so the\n * component probes a pinned structural seam `getOverviewScene` first\n * (optional access — picked up automatically once core lands\n * it) and falls back to the DECLARED node positions (`GraphNode.x/y`) of the\n * current scene roster — exact for prepared/fixed-position datasets from\n * orbit-data, and empty for force layouts without a position mirror.\n * Position-less nodes rasterize as NaN tombstones (skipped); mask-hidden nodes draw dimmed\n * via `getVisible`.\n */\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport type { CSSProperties, PointerEvent as ReactPointerEvent, ReactElement } from 'react';\nimport { OverviewController, OVERVIEW_HOT_INTERVAL_MS } from '@modernrelay/orbit-core';\nimport type { NodeId, OverviewScene, ViewportState } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { cornerStyle, mergeStyle, useResolvedInstance } from '../shared';\nimport type { GraphCorner } from '../shared';\n\n/** Default thumbnail edge (px) — the component default; the CPU target\n * default (256²) applies when a caller constructs a bare controller. */\nconst SIZE_DEFAULT = 200;\n\n/** Minimum rendered viewport-rect edge so a tiny/degenerate viewport stays\n * visible as a marker. */\nconst MIN_RECT_PX = 6;\n\n/** Pinned structural seam: the instance-side\n * posBuf mirror read. Optional access until core lands it — this view\n * stays signature-compatible when the member becomes required. */\ninterface OverviewSceneSurface {\n getOverviewScene?(): OverviewScene | null;\n}\n\nexport interface GraphMinimapProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Square thumbnail edge in CSS px (also the raster resolution). Default 200. */\n size?: number;\n /** Accessible name. Default 'Graph minimap'. */\n label?: string;\n /** Corner this panel anchors to. Default 'bottom-right'. */\n position?: GraphCorner;\n /** Distance from the two anchored edges in CSS px. Default 12. */\n offset?: number;\n /** Class hook for the root; providing it drops the default styles\n * INCLUDING placement — position it yourself in CSS. */\n className?: string;\n /** Style overrides merged over the defaults; an inset you set here\n * wins over the one `position` implies (resolved per axis). */\n style?: CSSProperties;\n}\n\nconst ROOT_STYLE: CSSProperties = {\n pointerEvents: 'auto',\n overflow: 'hidden',\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.85)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n};\n\nconst CANVAS_STYLE: CSSProperties = {\n display: 'block',\n cursor: 'crosshair',\n touchAction: 'none',\n};\n\nconst RECT_STYLE: CSSProperties = {\n position: 'absolute',\n left: 0,\n top: 0,\n display: 'none',\n border: '1px solid rgba(255, 255, 255, 0.85)',\n background: 'rgba(255, 255, 255, 0.12)',\n pointerEvents: 'none',\n willChange: 'transform',\n boxSizing: 'border-box',\n};\n\n/** Declared-position scene over the current roster, cached by the roster's\n * identity (`scene.idByIndex` is a stable reference per reconciled scene). */\ninterface DeclaredSceneCache {\n ids: readonly NodeId[];\n scene: OverviewScene | null;\n}\n\nexport function GraphMinimap(props: GraphMinimapProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphMinimap>');\n const size = props.size ?? SIZE_DEFAULT;\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const rectRef = useRef<HTMLDivElement | null>(null);\n /** Pointer that owns the current pan gesture. Other simultaneous pointers\n * cannot move or terminate it. */\n const draggingPointerRef = useRef<number | null>(null);\n const declaredSceneRef = useRef<DeclaredSceneCache>({ ids: [], scene: null });\n /** mask lane for `getVisible`, recomputed once per refresh tick. */\n const visibleRef = useRef<{ ids: readonly NodeId[]; set: ReadonlySet<NodeId> | null }>({\n ids: [],\n set: null,\n });\n\n const controller = useMemo(() => {\n const getScene = (): OverviewScene | null => {\n // 1. Pinned core seam: the posBuf mirror, once it lands.\n const mirrored = (instance as unknown as OverviewSceneSurface).getOverviewScene?.();\n if (mirrored !== undefined) return mirrored;\n // 2. v0.9 fallback: declared node positions over the scene roster.\n const ids = instance.getSceneNodeIds();\n if (ids.length === 0) return null;\n const cache = declaredSceneRef.current;\n if (cache.ids === ids) return cache.scene;\n const positions = new Float32Array(2 * ids.length);\n let known = 0;\n for (let i = 0; i < ids.length; i++) {\n const node = instance.getNode(ids[i]!);\n const x = node?.x;\n const y = node?.y;\n if (typeof x === 'number' && typeof y === 'number') {\n positions[2 * i] = x;\n positions[2 * i + 1] = y;\n known++;\n } else {\n positions[2 * i] = NaN;\n positions[2 * i + 1] = NaN;\n }\n }\n const scene: OverviewScene | null = known > 0 ? { positions, count: ids.length } : null;\n declaredSceneRef.current = { ids, scene };\n return scene;\n };\n const getVisible = (index: number): boolean => {\n const v = visibleRef.current;\n if (v.set === null) return true;\n const id = v.ids[index];\n return id === undefined ? true : v.set.has(id);\n };\n return new OverviewController({ getScene, getVisible, size });\n }, [instance, size]);\n\n /** O(1) rect update from the store viewport over the LAST rasterized frame.\n * `zoom` is screen px per space unit; the world extent in view is the\n * host container's CSS size / zoom — measured from the positioned ancestor\n * (the <Graph> container). Zero-size hosts (jsdom/unmounted) degrade to a\n * MIN_RECT_PX center marker. */\n const updateRect = (v: ViewportState | null): void => {\n const rect = rectRef.current;\n if (rect === null) return;\n if (v === null || v.zoom <= 0) {\n rect.style.display = 'none';\n return;\n }\n const host = rootRef.current?.offsetParent;\n const hostW = host instanceof HTMLElement ? host.clientWidth : 0;\n const hostH = host instanceof HTMLElement ? host.clientHeight : 0;\n const halfW = hostW > 0 ? hostW / v.zoom / 2 : 0;\n const halfH = hostH > 0 ? hostH / v.zoom / 2 : 0;\n // Two opposite world corners (world y grows up, minimap y grows down).\n const a = controller.worldToMinimap(v.x - halfW, v.y + halfH);\n const b = controller.worldToMinimap(v.x + halfW, v.y - halfH);\n if (a === null || b === null) {\n rect.style.display = 'none'; // nothing rasterized yet\n return;\n }\n const w = Math.max(Math.abs(b[0] - a[0]), MIN_RECT_PX);\n const h = Math.max(Math.abs(b[1] - a[1]), MIN_RECT_PX);\n const x = (a[0] + b[0]) / 2 - w / 2;\n const y = (a[1] + b[1]) / 2 - h / 2;\n rect.style.display = 'block';\n rect.style.width = `${w}px`;\n rect.style.height = `${h}px`;\n rect.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n };\n\n // Lane 1: thumbnail refresh — interval-probed controller throttle.\n useEffect(() => {\n const blit = (): void => {\n const state = instance.store.getState();\n // mask lane for the dimmed-alpha rasterization.\n if (state.visible.nodes !== state.nodeCount || state.hiddenNodeIds.size > 0) {\n const ids = instance.getSceneNodeIds();\n visibleRef.current = { ids, set: new Set(instance.getVisibleNodeIds()) };\n } else {\n visibleRef.current = { ids: [], set: null };\n }\n const raster = controller.rasterize();\n const canvas = canvasRef.current;\n if (canvas === null) return;\n const ctx = canvas.getContext('2d');\n if (ctx === null) return;\n if (raster === null) {\n ctx.clearRect(0, 0, size, size);\n updateRect(null);\n return;\n }\n if (typeof ImageData === 'undefined') return; // non-DOM environment\n // Copy: ImageData requires a plain-ArrayBuffer-backed clamped array.\n const pixels = new Uint8ClampedArray(raster.bitmap);\n ctx.putImageData(new ImageData(pixels, controller.size, controller.size), 0, 0);\n // A fresh frame can move the world→minimap mapping: re-derive the rect.\n updateRect(instance.store.getState().viewport);\n };\n const tick = (): void => {\n const state = instance.store.getState();\n // `revisions.render` is the v0.9 positions epoch (see module JSDoc):\n // idle with an unchanged render revision is provably position-stable.\n if (!controller.shouldRefresh(Date.now(), state.simulationRunning, state.revisions.render)) {\n return;\n }\n blit();\n };\n tick(); // first paint without waiting a full interval\n const timer = setInterval(tick, OVERVIEW_HOT_INTERVAL_MS);\n return () => {\n clearInterval(timer);\n };\n // updateRect is stable for each (instance, controller) pair — refs + controller\n // only — so it is deliberately not a dependency (this repo does not\n // configure the react-hooks lint plugin).\n }, [instance, controller, size]);\n\n // Lane 2: viewport rectangle — imperative store subscription, O(1) per\n // viewport publication, no re-rasterize and no React re-render.\n useEffect(() => {\n let prev = instance.store.getState().viewport;\n updateRect(prev);\n return instance.store.subscribe(() => {\n const next = instance.store.getState().viewport;\n if (next === prev) return;\n prev = next;\n updateRect(next);\n });\n // updateRect deliberately not a dependency (see the refresh effect above).\n }, [instance, controller]);\n\n // Lane 3: pan. Down pans immediately; move keeps panning while dragging.\n const panTo = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n const canvas = canvasRef.current;\n if (canvas === null) return;\n const b = canvas.getBoundingClientRect();\n // Canvas CSS size vs raster size (zero-size rects — jsdom — map 1:1).\n const sx = b.width > 0 ? controller.size / b.width : 1;\n const sy = b.height > 0 ? controller.size / b.height : 1;\n const world = controller.minimapToWorld((e.clientX - b.left) * sx, (e.clientY - b.top) * sy);\n if (world === null) return; // nothing rasterized yet — nothing to map\n instance.setViewport({ x: world[0], y: world[1] });\n };\n\n const onPointerDown = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n const owner = draggingPointerRef.current;\n if (owner !== null && owner !== e.pointerId) return;\n e.preventDefault();\n draggingPointerRef.current = e.pointerId;\n try {\n e.currentTarget.setPointerCapture(e.pointerId);\n } catch {\n /* pointer capture unavailable (jsdom/legacy) */\n }\n panTo(e);\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n if (draggingPointerRef.current !== e.pointerId) return;\n panTo(e);\n };\n\n const endDrag = (e: ReactPointerEvent<HTMLCanvasElement>): void => {\n if (draggingPointerRef.current !== e.pointerId) return;\n draggingPointerRef.current = null;\n };\n\n return (\n <div\n ref={rootRef}\n data-orbit-minimap=\"\"\n role=\"group\"\n aria-label={props.label ?? 'Graph minimap'}\n className={props.className}\n style={\n props.className !== undefined\n ? // The thumbnail is MECHANICAL, not decorative: dropping its box\n // would collapse the canvas, so size survives the class hook.\n { width: size, height: size, ...props.style }\n : mergeStyle(\n {\n ...cornerStyle(props.position ?? 'bottom-right', props.offset),\n ...ROOT_STYLE,\n width: size,\n height: size,\n },\n props.style,\n )\n }\n >\n <canvas\n ref={canvasRef}\n data-orbit-minimap-canvas=\"\"\n width={size}\n height={size}\n style={CANVAS_STYLE}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onLostPointerCapture={endDrag}\n onPointerLeave={endDrag}\n />\n <div ref={rectRef} data-orbit-minimap-viewport=\"\" style={RECT_STYLE} />\n </div>\n );\n}\n"]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getSearchResultUnavailableCallback } from '../chunk-ODOPSUI7.js';
|
|
2
|
+
import '../chunk-S36LHSNX.js';
|
|
1
3
|
import { useResolvedInstance } from '../chunk-NPOV5GA3.js';
|
|
2
4
|
import { useId, useState, useRef, useEffect, useCallback, useSyncExternalStore } from 'react';
|
|
3
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
@@ -252,7 +254,12 @@ function GraphNavigator(props) {
|
|
|
252
254
|
const activate = (row) => {
|
|
253
255
|
if (row.listKey === "search") {
|
|
254
256
|
const result = searchResults.find((r) => r.id === row.nodeId);
|
|
255
|
-
if (result !== void 0)
|
|
257
|
+
if (result !== void 0) {
|
|
258
|
+
const activation = instance.activateSearchResult(result);
|
|
259
|
+
if (activation.status === "unavailable") {
|
|
260
|
+
getSearchResultUnavailableCallback(instance)?.(result, activation.reason);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
256
263
|
return;
|
|
257
264
|
}
|
|
258
265
|
const neighbors = instance.focusNode(row.nodeId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Navigator/index.tsx"],"names":[],"mappings":";;;;AAkFA,IAAM,wBAAA,GAA2B,EAAA;AAgEjC,SAAS,iBAAiB,QAAA,EAA2C;AACnE,EAAA,MAAM,QAAA,GAAW,OAA6B,IAAI,CAAA;AAClD,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,CAAC,EAAA,KAAmB,QAAA,CAAS,KAAA,CAAM,UAAU,EAAE,CAAA;AAAA,IAC/C,CAAC,QAAQ;AAAA,GACX;AACA,EAAA,MAAM,WAAA,GAAc,YAAY,MAAqB;AACnD,IAAA,MAAM,CAAA,GAAI,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS;AAClC,IAAA,MAAM,OAAO,QAAA,CAAS,OAAA;AACtB,IAAA,IACE,IAAA,KAAS,IAAA,IACT,IAAA,CAAK,SAAA,KAAc,CAAA,CAAE,SAAA,IACrB,IAAA,CAAK,IAAA,KAAS,CAAA,CAAE,IAAA,IAChB,IAAA,CAAK,MAAA,KAAW,CAAA,CAAE,iBAClB,IAAA,CAAK,KAAA,KAAU,CAAA,CAAE,SAAA,CAAU,KAAA,IAC3B,IAAA,CAAK,SAAA,KAAc,CAAA,CAAE,SAAA,IACrB,IAAA,CAAK,MAAA,KAAW,CAAA,CAAE,MAAA,EAClB;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAsB;AAAA,MAC1B,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,QAAQ,CAAA,CAAE,aAAA;AAAA,MACV,KAAA,EAAO,EAAE,SAAA,CAAU,KAAA;AAAA,MACnB,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,QAAQ,CAAA,CAAE;AAAA,KACZ;AACA,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACb,EAAA,OAAO,oBAAA,CAAqB,SAAA,EAAW,WAAA,EAAa,WAAW,CAAA;AACjE;AASA,SAAS,UAAA,CAAW,GAAA,EAAwB,OAAA,EAAiB,IAAA,EAA0B;AACrF,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,IAAA,CAAK,GAAA,CAAI,MAAA,GAAS,IAAI,CAAC,CAAA;AAC1D,EAAA,MAAM,IAAA,GAAO,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,OAAA,EAAS,CAAC,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,GAAA,CAAI,KAAA,CAAM,IAAA,GAAO,IAAA,EAAM,IAAA,GAAO,IAAA,GAAO,IAAI,CAAA,EAAE;AAC9E;AAGA,IAAM,WAAA,GAA6B;AAAA,EACjC,aAAA,EAAe,MAAA;AAAA,EACf,OAAA,EAAS,MAAA;AAAA,EACT,aAAA,EAAe,QAAA;AAAA,EACf,GAAA,EAAK,CAAA;AAAA,EACL,QAAA,EAAU,GAAA;AAAA,EACV,QAAA,EAAU;AACZ,CAAA;AACA,IAAM,UAAA,GAA4B,EAAE,SAAA,EAAW,MAAA,EAAQ,WAAW,GAAA,EAAI;AACtE,IAAM,YAAA,GAA8B,EAAE,UAAA,EAAY,GAAA,EAAK,SAAS,SAAA,EAAU;AAC1E,IAAM,UAAA,GAA4B;AAAA,EAChC,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ,SAAA;AAAA,EACR,UAAA,EAAY,QAAA;AAAA,EACZ,QAAA,EAAU,QAAA;AAAA,EACV,YAAA,EAAc;AAChB,CAAA;AAEA,IAAM,iBAAA,GAAmC;AAAA,EACvC,GAAG,UAAA;AAAA,EACH,OAAA,EAAS,wBAAA;AAAA,EACT,aAAA,EAAe;AACjB,CAAA;AACA,IAAM,eAA8B,EAAE,OAAA,EAAS,QAAQ,UAAA,EAAY,QAAA,EAAU,KAAK,CAAA,EAAE;AACpF,IAAM,WAAA,GAA6B,EAAE,OAAA,EAAS,SAAA,EAAW,WAAW,QAAA,EAAS;AAEtE,SAAS,eAAe,KAAA,EAA0C;AACvE,EAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,KAAA,CAAM,QAAA,EAAU,kBAAkB,CAAA;AACvE,EAAA,MAAM,KAAA,GAAQ,iBAAiB,QAAQ,CAAA;AAIvC,EAAA,KAAK,KAAA,CAAM,YAAA;AAEX,EAAA,MAAM,SAAS,KAAA,EAAM;AACrB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAyB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAAuC;AAAA,IAC/D,MAAA,EAAQ,CAAA;AAAA,IACR,SAAA,EAAW,CAAA;AAAA,IACX,SAAA,EAAW,CAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACR,CAAA;AAED,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAwB,IAAI,CAAA;AAG9D,EAAA,MAAM,SAAA,GAAY,MAAA,iBAA4B,IAAI,GAAA,EAAK,CAAA;AAEvD,EAAA,MAAM,aAAa,MAAA,CAAuD;AAAA,IACxE,KAAA,EAAO,EAAA;AAAA,IACP,MAAA,sBAAY,GAAA;AAAI,GACjB,CAAA;AACD,EAAA,MAAM,QAAA,GAAW,MAAA,iBAAiC,IAAI,GAAA,EAAK,CAAA;AAE3D,EAAA,MAAM,eAAA,GAAkB,OAAO,KAAK,CAAA;AAIpC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAS,IAAA,IAAQ,IAAA,CAAK,UAAU,KAAA,CAAM,KAAA,UAAe,IAAI,CAAA;AAAA,EAC/D,CAAA,EAAG,CAAC,IAAA,EAAM,KAAA,CAAM,KAAK,CAAC,CAAA;AAMtB,EAAA,MAAM,eAAA,GAAkB,YAAY,MAAY;AAC9C,IAAA,IAAI,QAAA,CAAS,MAAM,QAAA,EAAS,CAAE,MAAM,MAAA,KAAW,IAAA,EAAM,QAAA,CAAS,aAAA,CAAc,IAAI,CAAA;AAAA,EAClF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAGb,EAAA,SAAA,CAAU,MAAM,MAAM,eAAA,EAAgB,EAAG,CAAC,eAAe,CAAC,CAAA;AAG1D,EAAA,IAAI,UAAA,CAAW,OAAA,CAAQ,KAAA,KAAU,KAAA,CAAM,KAAA,EAAO;AAC5C,IAAA,UAAA,CAAW,OAAA,GAAU,EAAE,KAAA,EAAO,KAAA,CAAM,OAAO,MAAA,kBAAQ,IAAI,KAAI,EAAE;AAAA,EAC/D;AAEA,EAAA,MAAM,aAAA,GAAgB,SAAS,gBAAA,EAAiB;AAGhD,EAAA,MAAM,YAAY,aAAA,EAAe,eAAA;AACjC,EAAA,MAAM,UAAA,GACJ,OAAO,SAAA,KAAc,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,IAAK,SAAA,IAAa,CAAA,GACxE,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA,GACpB,wBAAA;AAEN,EAAA,MAAM,qBAAqB,aAAA,EAAe,kBAAA;AAC1C,EAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAuB;AACrC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA;AAChC,IAAA,IAAI,IAAA,KAAS,QAAW,OAAO,EAAA;AAC/B,IAAA,IAAI,uBAAuB,MAAA,EAAW;AACpC,MAAA,IAAI;AACF,QAAA,OAAO,mBAAmB,IAAI,CAAA;AAAA,MAChC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,GAAQ,OAAO,CAAA;AAClC,IAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,IAAA,CAAK,EAAA;AAAA,EAClD,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAY,KAAA,CAAM,UAAU,OAAO,CAAA;AAG3D,EAAA,MAAM,OAAA,GAAU,CAAC,EAAA,KAAuB;AACtC,IAAA,MAAM,KAAA,GAAQ,CAAC,MAAA,CAAO,EAAE,CAAC,CAAA;AACzB,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,OAAA,CAAQ,MAAA,CAAO,IAAI,EAAE,CAAA;AAC9C,IAAA,IAAI,KAAA,KAAU,QAAW,KAAA,CAAM,IAAA,CAAK,UAAU,CAAA,GAAI,YAAA,GAAe,CAAA,EAAG,KAAK,CAAA,UAAA,CAAY,CAAA;AACrF,IAAA,IAAI,MAAM,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA,EAAG,KAAA,CAAM,KAAK,QAAQ,CAAA;AAC3C,IAAA,IAAI,MAAM,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA,EAAG,KAAA,CAAM,KAAK,QAAQ,CAAA;AAC7C,IAAA,OAAO,KAAA,CAAM,KAAK,QAAK,CAAA;AAAA,EACzB,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,OAAA,EAAiB,EAAA,KAAuB;AACxD,IAAA,MAAM,SAAS,SAAA,CAAU,OAAA;AACzB,IAAA,IAAI,KAAA,GAAQ,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA;AACzB,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,KAAA,GAAQ,MAAA,CAAO,IAAA;AACf,MAAA,MAAA,CAAO,GAAA,CAAI,IAAI,KAAK,CAAA;AAAA,IACtB;AACA,IAAA,OAAO,CAAA,EAAG,MAAM,CAAA,IAAA,EAAO,OAAO,IAAI,KAAK,CAAA,CAAA;AAAA,EACzC,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,OAAA,EAAuB,EAAA,MAAwB;AAAA,IAC9D,GAAA,EAAK,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AAAA,IACrB,KAAA,EAAO,QAAA,CAAS,OAAA,EAAS,EAAE,CAAA;AAAA,IAC3B,MAAA,EAAQ,EAAA;AAAA,IACR,OAAA;AAAA,IACA,IAAA,EAAM,QAAQ,EAAE,CAAA;AAAA,IAChB,QAAA,EAAU,WAAA,CAAY,GAAA,CAAI,EAAE;AAAA,GAC9B,CAAA;AAGA,EAAA,MAAM,gBAAyC,KAAA,CAAM,MAAA,KAAW,OAAO,EAAC,GAAI,MAAM,MAAA,CAAO,OAAA;AACzF,EAAA,MAAM,SAAA,GAAqD;AAAA,IACzD,QAAQ,aAAA,CAAc,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,EAAE,CAAA;AAAA,IACrC,SAAA,EAAW,IAAA,KAAS,IAAA,GAAO,IAAA,CAAK,YAAY,EAAC;AAAA,IAC7C,SAAA,EAAW,MAAM,SAAA,CAAU,OAAA;AAAA;AAAA;AAAA,IAG3B,OAAO,IAAA,KAAS,IAAA,GAAO,QAAA,CAAS,eAAA,KAAoB;AAAC,GACvD;AAEA,EAAA,MAAM,WAAyB,EAAC;AAChC,EAAA,IAAI,KAAA,CAAM,WAAW,IAAA,EAAM;AACzB,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,MAAA,EAAQ,KAAA,CAAM,QAAQ,UAAU,CAAA;AAClE,IAAA,MAAM,QAAQ,aAAA,CAAc,MAAA;AAC5B,IAAA,MAAM,KAAA,GAAQ,KAAK,IAAA,GAAO,UAAA;AAC1B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,QAAA;AAAA,MACL,OAAA,EAAS,QAAA;AAAA,MACT,KAAA,EAAO,gBAAA;AAAA,MACP,YAAY,CAAA,mBAAA,EAAsB,KAAA,CAAM,OAAO,KAAK,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,EAC5D,KAAA,KAAU,CAAA,GAAI,QAAA,GAAW,SAC3B,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,CAAC,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA;AAAA;AAAA;AAAA,MAI5C,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,IAAI,CAAA,KAAM;AAC9B,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAChC,QAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,GAAQ,CAAC,CAAA,EAAG,KAAA;AACxC,QAAA,OAAO,UAAU,MAAA,GAAY,GAAA,GAAM,EAAE,GAAG,GAAA,EAAK,MAAM,KAAA,EAAM;AAAA,MAC3D,CAAC,CAAA;AAAA,MACD,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,SAAA,EAAW,KAAA,CAAM,WAAW,UAAU,CAAA;AACxE,IAAA,MAAM,OAAA,GAAkB;AAAA,MACtB,GAAA,EAAK,CAAA,KAAA,EAAQ,IAAA,CAAK,EAAE,CAAA,CAAA;AAAA,MACpB,KAAA,EAAO,QAAA,CAAS,MAAA,EAAQ,IAAA,CAAK,EAAE,CAAA;AAAA,MAC/B,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,OAAA,EAAS,WAAA;AAAA,MACT,IAAA,EAAM,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAA;AAAA,MACrB,QAAA,EAAU,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE;AAAA,KACnC;AACA,IAAA,MAAM,KAAA,GAAQ,KAAK,SAAA,CAAU,MAAA;AAC7B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,SAAA;AAAA,MACL,OAAA,EAAS,WAAA;AAAA,MACT,KAAA,EAAO,cAAA;AAAA,MACP,YAAY,CAAA,aAAA,EAAgB,MAAA,CAAO,KAAK,EAAE,CAAC,KAAK,KAAK,CAAA,CAAA,EACnD,UAAU,CAAA,GAAI,UAAA,GAAa,WAC7B,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,CAAC,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA,MAC5C,IAAA,EAAM,CAAC,OAAA,EAAS,GAAG,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAO,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAC,CAAC,CAAA;AAAA,MACnE,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,IAAI,KAAA,CAAM,SAAA,CAAU,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACtC,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,SAAA,EAAW,KAAA,CAAM,WAAW,UAAU,CAAA;AACxE,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,SAAA,CAAU,OAAA,CAAQ,MAAA;AACtC,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,WAAA;AAAA,MACL,OAAA,EAAS,WAAA;AAAA,MACT,KAAA,EAAO,WAAA;AAAA,MACP,UAAA,EAAY,CAAA,WAAA,EAAc,KAAK,CAAA,CAAA,EAAI,UAAU,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,OAAA,EAC/D,IAAA,CAAK,IAAA,GAAO,CACd,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA,MACrB,IAAA,EAAM,KAAK,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAO,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAC,CAAA;AAAA,MACrD,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,KAAA,EAAO,KAAA,CAAM,OAAO,UAAU,CAAA;AAChE,IAAA,MAAM,KAAA,GAAQ,UAAU,KAAA,CAAM,MAAA;AAC9B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,OAAA;AAAA,MACL,OAAA,EAAS,OAAA;AAAA,MACT,KAAA,EAAO,WAAA;AAAA,MACP,UAAA,EAAY,CAAA,WAAA,EAAc,KAAK,CAAA,CAAA,EAAI,UAAU,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,OAAA,EAC/D,IAAA,CAAK,IAAA,GAAO,CACd,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA,MACrB,IAAA,EAAM,KAAK,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAO,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAC,CAAA;AAAA,MACjD,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,OAAiB,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AACrD,EAAA,IAAI,WAAA,GAAc,SAAA,KAAc,IAAA,GAAO,EAAA,GAAK,IAAA,CAAK,UAAU,CAAC,CAAA,KAAM,CAAA,CAAE,GAAA,KAAQ,SAAS,CAAA;AACrF,EAAA,IAAI,WAAA,KAAgB,EAAA,IAAM,IAAA,CAAK,MAAA,GAAS,GAAG,WAAA,GAAc,CAAA;AACzD,EAAA,MAAM,SAAA,GAAY,WAAA,IAAe,CAAA,GAAI,IAAA,CAAK,WAAW,CAAA,GAAI,MAAA;AACzD,EAAA,MAAM,aAAA,GACJ,SAAA,KAAc,MAAA,GAAY,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,SAAA,CAAU,OAAO,CAAA,GAAI,MAAA;AAIpF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,gBAAgB,OAAA,EAAS;AAC9B,IAAA,eAAA,CAAgB,OAAA,GAAU,KAAA;AAC1B,IAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,IAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,SAAA,CAAU,KAAK,GAAG,KAAA,EAAM;AAAA,EAC/C,CAAC,CAAA;AAGD,EAAA,MAAM,gBAAgB,MAAY;AAChC,IAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAAA,EAC5B,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,KAAA,KAAwB;AACtC,IAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACvB,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,CAAC,CAAA,EAAG,IAAA,CAAK,MAAA,GAAS,CAAC,CAAC,CAAA;AAC9D,IAAA,IAAI,QAAQ,MAAA,EAAW;AACvB,IAAA,YAAA,CAAa,IAAI,GAAG,CAAA;AAGpB,IAAA,QAAA,CAAS,aAAA,CAAc,IAAI,MAAM,CAAA;AACjC,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,KAAA,KAAwB;AACtC,IAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,IAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,IAAA,MAAM,KAAA,GAAQ,UAAU,OAAO,CAAA;AAC/B,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,UAAU,CAAC,CAAA;AAClE,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG,CAAC,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA;AACnE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,UAAU,KAAA,EAAO,CAAC,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA;AACjE,IAAA,IAAI,SAAS,OAAA,EAAS;AACtB,IAAA,QAAA,CAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,GAAG,IAAA,EAAK,CAAE,CAAA;AAC3C,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,GAAO,UAAU,CAAA;AACrC,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,YAAA,CAAa,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,CAAA;AAClC,MAAA,QAAA,CAAS,cAAc,KAAK,CAAA;AAAA,IAC9B;AACA,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAQA,EAAA,MAAM,QAAA,GAAW,CAAC,GAAA,KAAsB;AACtC,IAAA,IAAI,GAAA,CAAI,YAAY,QAAA,EAAU;AAC5B,MAAA,MAAM,MAAA,GAAS,cAAc,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,IAAI,MAAM,CAAA;AAC5D,MAAA,IAAI,MAAA,KAAW,MAAA,EAAW,QAAA,CAAS,oBAAA,CAAqB,MAAM,CAAA;AAC9D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,SAAA,GAAY,QAAA,CAAS,SAAA,CAAU,GAAA,CAAI,MAAM,CAAA;AAC/C,IAAA,UAAA,CAAW,QAAQ,MAAA,CAAO,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,UAAU,MAAM,CAAA;AAC1D,IAAA,OAAA,CAAQ,EAAE,IAAI,GAAA,CAAI,MAAA,EAAQ,WAAW,KAAA,EAAO,KAAA,CAAM,OAAO,CAAA;AACzD,IAAA,QAAA,CAAS,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,SAAA,EAAW,GAAE,CAAE,CAAA;AACxC,IAAA,YAAA,CAAa,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACjC,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAIA,EAAA,MAAM,eAAA,GAAkB,CAAC,GAAA,KAAsB;AAC7C,IAAA,IAAI,GAAA,CAAI,YAAY,QAAA,IAAY,QAAA,CAAS,QAAQ,GAAA,CAAI,MAAM,MAAM,MAAA,EAAW;AAC5E,IAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,OAAA;AAChC,IAAA,MAAM,OAAO,OAAA,CAAQ,QAAA,CAAS,IAAI,MAAM,CAAA,GACpC,QAAQ,MAAA,CAAO,CAAC,EAAA,KAAO,EAAA,KAAO,IAAI,MAAM,CAAA,GACxC,CAAC,GAAG,OAAA,EAAS,IAAI,MAAM,CAAA;AAC3B,IAAA,QAAA,CAAS,YAAY,IAAI,CAAA;AAAA,EAC3B,CAAA;AAEA,EAAA,MAAM,sBAAsB,MAAY;AACtC,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,eAAA,EAAgB;AAChB,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAA2C;AAC5D,IAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,IAAA,QAAQ,EAAE,GAAA;AAAK,MACb,KAAK,WAAA;AACH,QAAA,MAAA,CAAO,cAAc,CAAC,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,SAAA;AACH,QAAA,MAAA,CAAO,cAAc,CAAC,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,MAAA,CAAO,CAAC,CAAA;AACR,QAAA;AAAA,MACF,KAAK,KAAA;AACH,QAAA,MAAA,CAAO,IAAA,CAAK,SAAS,CAAC,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,UAAA;AACH,QAAA,MAAA,CAAO,CAAC,CAAA;AACR,QAAA;AAAA,MACF,KAAK,QAAA;AACH,QAAA,MAAA,CAAO,EAAE,CAAA;AACT,QAAA;AAAA,MACF,KAAK,OAAA;AACH,QAAA,QAAA,CAAS,SAAS,CAAA;AAClB,QAAA;AAAA,MACF,KAAK,GAAA;AACH,QAAA,eAAA,CAAgB,SAAS,CAAA;AACzB,QAAA;AAAA,MACF,KAAK,QAAA;AACH,QAAA,IAAI,SAAS,IAAA,EAAM;AACnB,QAAA,mBAAA,EAAoB;AACpB,QAAA;AAAA,MACF;AACE,QAAA;AAAA;AAEJ,IAAA,CAAA,CAAE,cAAA,EAAe;AAAA,EACnB,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,IAAS,iBAAA;AAElC,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,sBAAA,EAAqB,EAAA;AAAA,MACrB,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAY,UAAA;AAAA,MACZ,WAAW,KAAA,CAAM,SAAA;AAAA,MACjB,OAAO,EAAE,GAAG,WAAA,EAAa,GAAG,MAAM,KAAA,EAAM;AAAA,MACxC,MAAA,EAAQ,CAAC,CAAA,KAAM;AAGb,QAAA,IAAI,CAAC,CAAA,CAAE,aAAA,CAAc,QAAA,CAAS,CAAA,CAAE,aAA4B,CAAA,EAAG;AAC7D,UAAA,eAAA,EAAgB;AAAA,QAClB;AAAA,MACF,CAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,SAAA;AAAA,YACL,YAAA,EAAY,GAAG,UAAU,CAAA,MAAA,CAAA;AAAA,YACzB,yBAAuB,SAAA,EAAW,KAAA;AAAA,YAClC,mBAAA,EAAkB,+DAAA;AAAA,YAClB,SAAA;AAAA,YACA,KAAA,EAAO,UAAA;AAAA,YAEN,QAAA,EAAA,IAAA,CAAK,WAAW,CAAA,mBACf,GAAA,CAAC,SAAI,IAAA,EAAK,cAAA,EAAe,OAAO,WAAA,EAAa,QAAA,EAAA,UAAA,EAE7C,IAEA,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,qBACZ,IAAA,CAAC,SAAsB,IAAA,EAAK,OAAA,EAAQ,YAAA,EAAY,OAAA,CAAQ,UAAA,EACtD,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,SAAI,IAAA,EAAK,cAAA,EAAe,KAAA,EAAO,YAAA,EAC7B,kBAAQ,KAAA,EACX,CAAA;AAAA,cACC,OAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ;AACzB,gBAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,KAAQ,SAAA,EAAW,GAAA;AACxC,gBAAA,uBACE,GAAA;AAAA,kBAAC,KAAA;AAAA,kBAAA;AAAA,oBAEC,IAAI,GAAA,CAAI,KAAA;AAAA,oBACR,IAAA,EAAK,QAAA;AAAA,oBACL,iBAAe,GAAA,CAAI,QAAA;AAAA,oBACnB,QAAA,EAAU,WAAW,CAAA,GAAI,EAAA;AAAA,oBACzB,GAAA,EAAK,CAAC,EAAA,KAAO;AACX,sBAAA,IAAI,OAAO,IAAA,EAAM,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,IAAI,KAAK,CAAA;AAAA,2BAC7C,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,OAAO,EAAE,CAAA;AAAA,oBACzC,CAAA;AAAA,oBACA,SAAS,MAAM;AACb,sBAAA,YAAA,CAAa,IAAI,GAAG,CAAA;AACpB,sBAAA,QAAA,CAAS,aAAA,CAAc,IAAI,MAAM,CAAA;AACjC,sBAAA,aAAA,EAAc;AAAA,oBAChB,CAAA;AAAA,oBACA,KAAA,EAAO,WAAW,iBAAA,GAAoB,UAAA;AAAA,oBAErC,QAAA,EAAA,GAAA,CAAI;AAAA,mBAAA;AAAA,kBAhBA,GAAA,CAAI;AAAA,iBAiBX;AAAA,cAEJ,CAAC;AAAA,aAAA,EAAA,EA3BO,OAAA,CAAQ,GA4BlB,CACD;AAAA;AAAA,SAEL;AAAA,QACC,kBAAkB,MAAA,mBACjB,IAAA,CAAC,SAAI,6BAAA,EAA4B,EAAA,EAAG,OAAO,YAAA,EACzC,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,MAAA,CAAO,EAAE,CAAA;AAAA,cACxB,QAAA,EAAU,cAAc,IAAA,KAAS,CAAA;AAAA,cACjC,mBAAA,EAAkB,QAAA;AAAA,cACnB,QAAA,EAAA;AAAA;AAAA,WAED;AAAA,0BACA,GAAA,CAAC,MAAA,EAAA,EAAK,IAAA,EAAK,QAAA,EAAS,kCAAA,EAAiC,IAClD,QAAA,EAAA,CAAA,EAAG,aAAA,CAAc,KAAK,CAAA,OAAA,EAAU,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,IAAA,EACrD,aAAA,CAAc,SAChB,CAAA,EAAA,EAAK,aAAA,CAAc,KAAK,CAAA,CAAA,EAAI,aAAA,CAAc,KAAA,KAAU,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,CAAA,CAAA,EAC1E,CAAA;AAAA,0BACA,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,MAAA,CAAO,CAAC,CAAA;AAAA,cACvB,QAAA,EAAU,aAAA,CAAc,IAAA,IAAQ,aAAA,CAAc,SAAA,GAAY,CAAA;AAAA,cAC1D,mBAAA,EAAkB,UAAA;AAAA,cACnB,QAAA,EAAA;AAAA;AAAA;AAED,SAAA,EACF,CAAA,GACE;AAAA;AAAA;AAAA,GACN;AAEJ","file":"Navigator.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Navigator` — `<GraphNavigator>`, the\n * bounded semantic navigator.\n *\n * The navigator is the keyboard/screen-reader equivalent surface for the\n * canvas: a visually compact panel with `role=\"listbox\"` semantics driven by\n * a roving tabindex — exactly one item is tabbable at any time, and\n * `aria-activedescendant` on the listbox container mirrors the active item's\n * stable DOM id. It is bounded by design: every list is paged by\n * `accessibility.navigatorWindow` (default 50), so the DOM never grows one\n * row per entity.\n *\n * Sections:\n * - **Search results** — the last completed search\n * (`store.search`), shown whenever the slice is non-null (it clears on\n * `clearSearch` / dataset swap). Paged like every other list; Enter\n * routes through `instance.activateSearchResult` (result contract:\n * an in-scene, mask-visible id focuses; anything else is classification\n * only — the navigator never mutates scope/filters). Row text prefers the\n * service-supplied `label`.\n * - **Focused node** — the transiently focused root plus its paged 1-hop\n * neighborhood.\n * - **Selection** — the current node selection, paged.\n * - **All nodes** — the entry list (first `navigatorWindow` nodes in\n * accepted-base order, paged over the full set), shown when nothing is\n * focused.\n *\n * ## Keyboard model — `keyboardMode: 'topology'` (the v0.4 default and only mode)\n *\n * | Key | Action |\n * |---------------------|-------------------------------------------------------------------|\n * | ArrowDown / ArrowUp | move the active item within the current list |\n * | Home / End | move to the bounds of the current list |\n * | PageDown / PageUp | next / previous page of the active item's list |\n * | Enter | focus the node (`instance.focusNode` — camera fly) and re-root the neighborhood at it |\n * | Space | toggle the node's selection membership |\n * | Escape | clear the transient focus (returns to the entry list) |\n *\n * `keyboardMode: 'spatial'` (arrows move by screen direction) is a\n * documented post-v0.4 mode and intentionally NOT part of the accepted prop\n * union yet — only `'topology'` type-checks in v0.4.\n *\n * Text names resolve via `accessibility.getAccessibleLabel`, falling back to\n * `attrs.label` (when a string) and then the node id — always rendered as\n * TEXT NODES.\n * Each item exposes selection state (`aria-selected`), its neighbor count\n * when known, and pinned/hidden state in text (e.g. `hub · 5 neighbors ·\n * pinned`).\n *\n * Neighborhood semantics (v0.4): direction is UNDIRECTED — `focusNode`\n * resolves the 1-hop neighbor ids through the engine's adjacency when the\n * adapter provides one, else through the core CSR adjacency\n * (`buildAdjacency`/`neighborsOf`) built lazily per accepted model revision\n * and invalidated on model change; self-loops are excluded and parallel\n * edges deduplicate. The navigator caches the resolved neighborhood and the\n * per-node neighbor counts it has learned in refs keyed by\n * `revisions.model`; a model change drops the (now stale) transient root and\n * returns to the entry list. Neighbor counts therefore appear on items whose\n * neighborhood has been resolved (the current and previously visited roots);\n * a public O(1) degree read is a post-v0.4 core surface.\n *\n * Focus never lives only in WebGL pixels: the active item carries a\n * visible DOM focus outline and receives real DOM focus on keyboard moves.\n * Sync is navigator → graph (Enter → `focusNode` camera fly); graph →\n * navigator focus sync is deferred past v0.4. Space routes through\n * `instance.selectNodes`, so a controlled selection surfaces intent only\n * — `aria-selected` always reflects the store.\n */\n\nimport { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, KeyboardEvent, ReactElement } from 'react';\nimport type {\n AccessibilityConfig,\n GraphNode,\n NodeId,\n SearchResult,\n SelectionState,\n} from '@modernrelay/orbit-core';\nimport { useResolvedInstance } from '../shared';\nimport type { AnyGraphInstance } from '../../GraphProvider';\n\n/** default page size when `accessibility.navigatorWindow` is unset. */\nconst DEFAULT_NAVIGATOR_WINDOW = 50;\n\nexport interface GraphNavigatorProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /**\n * Keyboard model. v0.4 ships `'topology'` (the default; documented above);\n * `'spatial'` (arrows-by-screen-direction) is post-v0.4 and not accepted\n * by this union yet.\n */\n keyboardMode?: 'topology';\n /** Accessible name of the navigator panel. Default 'Graph navigator'. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/** Which paged id list a row belongs to (drives PageUp/PageDown targeting). */\ntype PagedListKey = 'search' | 'neighbors' | 'selection' | 'entry';\n\ninterface NavRow {\n /** Stable identity across renders: `${list}:${nodeId}` (`root:` for the root row). */\n key: string;\n /** Stable DOM id (aria-activedescendant target). */\n domId: string;\n nodeId: NodeId;\n listKey: PagedListKey;\n text: string;\n selected: boolean;\n}\n\ninterface NavSection {\n key: 'search' | 'focused' | 'selection' | 'entry';\n listKey: PagedListKey;\n label: string;\n /** Group accessible name; carries the 'page x of y' state. */\n groupLabel: string;\n rows: NavRow[];\n page: number;\n pageCount: number;\n /** Total pageable items (excludes the focused section's root chrome row). */\n total: number;\n}\n\n/** Transient navigator focus: the neighborhood root. */\ninterface NavRoot {\n id: NodeId;\n neighbors: readonly NodeId[];\n /** Model revision the neighborhood was resolved against. */\n model: number;\n}\n\ninterface NavStoreSlice {\n selection: SelectionState;\n pins: ReadonlyMap<NodeId, readonly [number, number]>;\n hidden: ReadonlySet<NodeId>;\n model: number;\n nodeCount: number;\n /** Last completed search; null hides the section. */\n search: { query: string; results: readonly SearchResult[] } | null;\n}\n\n/** Narrow store subscription: re-renders only when a navigator-relevant slice\n * changes (never on per-frame viewport writes). */\nfunction useNavStoreSlice(instance: AnyGraphInstance): NavStoreSlice {\n const cacheRef = useRef<NavStoreSlice | null>(null);\n const subscribe = useCallback(\n (cb: () => void) => instance.store.subscribe(cb),\n [instance],\n );\n const getSnapshot = useCallback((): NavStoreSlice => {\n const s = instance.store.getState();\n const prev = cacheRef.current;\n if (\n prev !== null &&\n prev.selection === s.selection &&\n prev.pins === s.pins &&\n prev.hidden === s.hiddenNodeIds &&\n prev.model === s.revisions.model &&\n prev.nodeCount === s.nodeCount &&\n prev.search === s.search\n ) {\n return prev;\n }\n const next: NavStoreSlice = {\n selection: s.selection,\n pins: s.pins,\n hidden: s.hiddenNodeIds,\n model: s.revisions.model,\n nodeCount: s.nodeCount,\n search: s.search,\n };\n cacheRef.current = next;\n return next;\n }, [instance]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\ninterface PageWindow {\n page: number;\n pageCount: number;\n items: readonly NodeId[];\n}\n\n/** Clamp a raw page into `[0, pageCount)` and slice that page's ids. */\nfunction pageWindow(all: readonly NodeId[], rawPage: number, size: number): PageWindow {\n const pageCount = Math.max(1, Math.ceil(all.length / size));\n const page = Math.min(Math.max(rawPage, 0), pageCount - 1);\n return { page, pageCount, items: all.slice(page * size, page * size + size) };\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n maxWidth: 320,\n fontSize: 12,\n};\nconst LIST_STYLE: CSSProperties = { overflowY: 'auto', maxHeight: 320 };\nconst HEADER_STYLE: CSSProperties = { fontWeight: 600, padding: '4px 6px' };\nconst ITEM_STYLE: CSSProperties = {\n padding: '2px 6px',\n cursor: 'default',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n/** visible DOM focus ring on the active item. */\nconst ACTIVE_ITEM_STYLE: CSSProperties = {\n ...ITEM_STYLE,\n outline: '2px solid currentColor',\n outlineOffset: -2,\n};\nconst PAGING_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 8 };\nconst EMPTY_STYLE: CSSProperties = { padding: '4px 6px', fontStyle: 'italic' };\n\nexport function GraphNavigator(props: GraphNavigatorProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphNavigator>');\n const slice = useNavStoreSlice(instance);\n\n // 'topology' is the only v0.4 mode; the prop exists so the shape is stable\n // when 'spatial' lands post-v0.4 (no behavioral switch yet).\n void props.keyboardMode;\n\n const baseId = useId();\n const [root, setRoot] = useState<NavRoot | null>(null);\n const [pages, setPages] = useState<Record<PagedListKey, number>>({\n search: 0,\n neighbors: 0,\n selection: 0,\n entry: 0,\n });\n /** Active row identity (roving target); null → first row. */\n const [activeKey, setActiveKey] = useState<string | null>(null);\n\n /** Stable per-node DOM-id tokens (node ids may contain arbitrary text). */\n const tokensRef = useRef<Map<NodeId, number>>(new Map());\n /** Learned neighbor counts, cached per model revision (see module JSDoc). */\n const degreesRef = useRef<{ model: number; counts: Map<NodeId, number> }>({\n model: -1,\n counts: new Map(),\n });\n const itemsRef = useRef<Map<string, HTMLElement>>(new Map());\n /** Set by keyboard handlers so the focus effect moves DOM focus once. */\n const pendingFocusRef = useRef(false);\n\n // A model change invalidates the resolved neighborhood: drop the transient\n // root (back to the entry list) rather than rendering stale rows.\n useEffect(() => {\n if (root !== null && root.model !== slice.model) setRoot(null);\n }, [root, slice.model]);\n\n // release the keyboard ring — but ONLY when no pointer hover owns the\n // shared channel (blur while the pointer rests on a canvas node must not\n // erase that node's hover ring; hover already superseded the sticky\n // keyboard target inside core, so there is nothing else to clear).\n const releaseEmphasis = useCallback((): void => {\n if (instance.store.getState().hover.nodeId === null) instance.emphasizeNode(null);\n }, [instance]);\n\n // A ring left by keyboard navigation must not outlive the navigator.\n useEffect(() => () => releaseEmphasis(), [releaseEmphasis]);\n\n // Idempotent render-time cache reset (ref only — safe under StrictMode).\n if (degreesRef.current.model !== slice.model) {\n degreesRef.current = { model: slice.model, counts: new Map() };\n }\n\n const accessibility = instance.getAccessibility() as\n | AccessibilityConfig<Record<string, unknown>>\n | undefined;\n const rawWindow = accessibility?.navigatorWindow;\n const windowSize =\n typeof rawWindow === 'number' && Number.isFinite(rawWindow) && rawWindow >= 1\n ? Math.floor(rawWindow)\n : DEFAULT_NAVIGATOR_WINDOW;\n\n const getAccessibleLabel = accessibility?.getAccessibleLabel;\n const nameOf = (id: NodeId): string => {\n const node = instance.getNode(id) as GraphNode<Record<string, unknown>> | undefined;\n if (node === undefined) return id;\n if (getAccessibleLabel !== undefined) {\n try {\n return getAccessibleLabel(node);\n } catch {\n // fall through to the built-in resolution\n }\n }\n const label = node.attrs?.['label'];\n return typeof label === 'string' ? label : node.id;\n };\n\n const selectedSet = new Set<NodeId>(slice.selection.nodeIds);\n\n /** '{name} · {n} neighbors · pinned · hidden' — always a single text node. */\n const rowText = (id: NodeId): string => {\n const parts = [nameOf(id)];\n const count = degreesRef.current.counts.get(id);\n if (count !== undefined) parts.push(count === 1 ? '1 neighbor' : `${count} neighbors`);\n if (slice.pins.has(id)) parts.push('pinned');\n if (slice.hidden.has(id)) parts.push('hidden');\n return parts.join(' · ');\n };\n\n const domIdFor = (listKey: string, id: NodeId): string => {\n const tokens = tokensRef.current;\n let token = tokens.get(id);\n if (token === undefined) {\n token = tokens.size;\n tokens.set(id, token);\n }\n return `${baseId}nav-${listKey}-${token}`;\n };\n\n const makeRow = (listKey: PagedListKey, id: NodeId): NavRow => ({\n key: `${listKey}:${id}`,\n domId: domIdFor(listKey, id),\n nodeId: id,\n listKey,\n text: rowText(id),\n selected: selectedSet.has(id),\n });\n\n // --- section/row model (all O(navigatorWindow) work) ---\n const searchResults: readonly SearchResult[] = slice.search === null ? [] : slice.search.results;\n const listItems: Record<PagedListKey, readonly NodeId[]> = {\n search: searchResults.map((r) => r.id),\n neighbors: root !== null ? root.neighbors : [],\n selection: slice.selection.nodeIds,\n // the entry list is the SCENE roster (scope applied, mask not)\n // hidden/filtered nodes stay listed with their state exposed in text.\n entry: root === null ? instance.getSceneNodeIds() : [],\n };\n\n const sections: NavSection[] = [];\n if (slice.search !== null) {\n const view = pageWindow(listItems.search, pages.search, windowSize);\n const count = searchResults.length;\n const start = view.page * windowSize;\n sections.push({\n key: 'search',\n listKey: 'search',\n label: 'Search results',\n groupLabel: `Search results for ${slice.search.query}: ${count} ${\n count === 1 ? 'result' : 'results'\n }, page ${view.page + 1} of ${view.pageCount}`,\n // row text prefers the service-supplied label (still a TEXT\n // NODE); results may name ids outside the model — nameOf falls back to\n // the id for those.\n rows: view.items.map((id, i) => {\n const row = makeRow('search', id);\n const label = searchResults[start + i]?.label;\n return label === undefined ? row : { ...row, text: label };\n }),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root !== null) {\n const view = pageWindow(listItems.neighbors, pages.neighbors, windowSize);\n const rootRow: NavRow = {\n key: `root:${root.id}`,\n domId: domIdFor('root', root.id),\n nodeId: root.id,\n listKey: 'neighbors',\n text: rowText(root.id),\n selected: selectedSet.has(root.id),\n };\n const count = root.neighbors.length;\n sections.push({\n key: 'focused',\n listKey: 'neighbors',\n label: 'Focused node',\n groupLabel: `Focused node ${nameOf(root.id)}: ${count} ${\n count === 1 ? 'neighbor' : 'neighbors'\n }, page ${view.page + 1} of ${view.pageCount}`,\n rows: [rootRow, ...view.items.map((id) => makeRow('neighbors', id))],\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (slice.selection.nodeIds.length > 0) {\n const view = pageWindow(listItems.selection, pages.selection, windowSize);\n const count = slice.selection.nodeIds.length;\n sections.push({\n key: 'selection',\n listKey: 'selection',\n label: 'Selection',\n groupLabel: `Selection: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('selection', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root === null) {\n const view = pageWindow(listItems.entry, pages.entry, windowSize);\n const count = listItems.entry.length;\n sections.push({\n key: 'entry',\n listKey: 'entry',\n label: 'All nodes',\n groupLabel: `All nodes: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('entry', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n\n const rows: NavRow[] = sections.flatMap((s) => s.rows);\n let activeIndex = activeKey === null ? -1 : rows.findIndex((r) => r.key === activeKey);\n if (activeIndex === -1 && rows.length > 0) activeIndex = 0;\n const activeRow = activeIndex >= 0 ? rows[activeIndex] : undefined;\n const activeSection =\n activeRow !== undefined ? sections.find((s) => s.listKey === activeRow.listKey) : undefined;\n\n // Move real DOM focus to the active item after keyboard-driven changes so\n // the focus ring is a genuine DOM focus, never only a WebGL highlight.\n useEffect(() => {\n if (!pendingFocusRef.current) return;\n pendingFocusRef.current = false;\n if (activeRow === undefined) return;\n itemsRef.current.get(activeRow.domId)?.focus();\n });\n\n // --- keyboard model ('topology'; see module JSDoc) ---\n const focusItemSoon = (): void => {\n pendingFocusRef.current = true;\n };\n\n const moveTo = (index: number): void => {\n if (rows.length === 0) return;\n const row = rows[Math.min(Math.max(index, 0), rows.length - 1)];\n if (row === undefined) return;\n setActiveKey(row.key);\n // Keyboard parity with pointer hover: ring the active row\n // WITHOUT flying the camera (that stays on Enter via focusNode).\n instance.emphasizeNode(row.nodeId);\n focusItemSoon();\n };\n\n const pageBy = (delta: number): void => {\n if (activeRow === undefined) return;\n const listKey = activeRow.listKey;\n const items = listItems[listKey];\n const pageCount = Math.max(1, Math.ceil(items.length / windowSize));\n const current = Math.min(Math.max(pages[listKey], 0), pageCount - 1);\n const next = Math.min(Math.max(current + delta, 0), pageCount - 1);\n if (next === current) return;\n setPages((p) => ({ ...p, [listKey]: next }));\n const first = items[next * windowSize];\n if (first !== undefined) {\n setActiveKey(`${listKey}:${first}`);\n instance.emphasizeNode(first);\n }\n focusItemSoon();\n };\n\n /** Enter: focus the node in the graph (camera fly) and re-root the\n * neighborhood. The returned ids come from the core adjacency resolution\n * (undirected; see module JSDoc). Search rows route through the\n * result contract instead: `activateSearchResult` focuses an in-scene,\n * mask-visible id and otherwise only CLASSIFIES — the navigator never\n * mutates scope/filters (the host reacts through its own channels). */\n const activate = (row: NavRow): void => {\n if (row.listKey === 'search') {\n const result = searchResults.find((r) => r.id === row.nodeId);\n if (result !== undefined) instance.activateSearchResult(result);\n return;\n }\n const neighbors = instance.focusNode(row.nodeId);\n degreesRef.current.counts.set(row.nodeId, neighbors.length);\n setRoot({ id: row.nodeId, neighbors, model: slice.model });\n setPages((p) => ({ ...p, neighbors: 0 }));\n setActiveKey(`root:${row.nodeId}`);\n focusItemSoon();\n };\n\n /** Space: toggle membership through the selection ownership path.\n * Search rows may name ids outside the accepted model — those no-op. */\n const toggleSelection = (row: NavRow): void => {\n if (row.listKey === 'search' && instance.getNode(row.nodeId) === undefined) return;\n const current = slice.selection.nodeIds;\n const next = current.includes(row.nodeId)\n ? current.filter((id) => id !== row.nodeId)\n : [...current, row.nodeId];\n instance.selectNodes(next);\n };\n\n const clearTransientFocus = (): void => {\n setRoot(null);\n setActiveKey(null);\n releaseEmphasis();\n focusItemSoon();\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (activeRow === undefined) return;\n switch (e.key) {\n case 'ArrowDown':\n moveTo(activeIndex + 1);\n break;\n case 'ArrowUp':\n moveTo(activeIndex - 1);\n break;\n case 'Home':\n moveTo(0);\n break;\n case 'End':\n moveTo(rows.length - 1);\n break;\n case 'PageDown':\n pageBy(1);\n break;\n case 'PageUp':\n pageBy(-1);\n break;\n case 'Enter':\n activate(activeRow);\n break;\n case ' ':\n toggleSelection(activeRow);\n break;\n case 'Escape':\n if (root === null) return; // nothing transient — let it bubble\n clearTransientFocus();\n break;\n default:\n return;\n }\n e.preventDefault();\n };\n\n const panelLabel = props.label ?? 'Graph navigator';\n\n return (\n <div\n data-orbit-navigator=\"\"\n role=\"group\"\n aria-label={panelLabel}\n className={props.className}\n style={{ ...PANEL_STYLE, ...props.style }}\n onBlur={(e) => {\n // Focus left the panel entirely (paging buttons stay inside): the\n // keyboard ring clears, mirroring pointer hover leaving the canvas.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {\n releaseEmphasis();\n }\n }}\n >\n <div\n role=\"listbox\"\n aria-label={`${panelLabel} items`}\n aria-activedescendant={activeRow?.domId}\n aria-keyshortcuts=\"ArrowUp ArrowDown Home End PageUp PageDown Enter Space Escape\"\n onKeyDown={onKeyDown}\n style={LIST_STYLE}\n >\n {rows.length === 0 ? (\n <div role=\"presentation\" style={EMPTY_STYLE}>\n No nodes\n </div>\n ) : (\n sections.map((section) => (\n <div key={section.key} role=\"group\" aria-label={section.groupLabel}>\n <div role=\"presentation\" style={HEADER_STYLE}>\n {section.label}\n </div>\n {section.rows.map((row) => {\n const isActive = row.key === activeRow?.key;\n return (\n <div\n key={row.key}\n id={row.domId}\n role=\"option\"\n aria-selected={row.selected}\n tabIndex={isActive ? 0 : -1}\n ref={(el) => {\n if (el === null) itemsRef.current.delete(row.domId);\n else itemsRef.current.set(row.domId, el);\n }}\n onClick={() => {\n setActiveKey(row.key);\n instance.emphasizeNode(row.nodeId); // active row = ringed row\n focusItemSoon();\n }}\n style={isActive ? ACTIVE_ITEM_STYLE : ITEM_STYLE}\n >\n {row.text}\n </div>\n );\n })}\n </div>\n ))\n )}\n </div>\n {activeSection !== undefined ? (\n <div data-orbit-navigator-paging=\"\" style={PAGING_STYLE}>\n <button\n type=\"button\"\n onClick={() => pageBy(-1)}\n disabled={activeSection.page === 0}\n aria-keyshortcuts=\"PageUp\"\n >\n Previous page\n </button>\n <span role=\"status\" data-orbit-navigator-page-status=\"\">\n {`${activeSection.label}: page ${activeSection.page + 1} of ${\n activeSection.pageCount\n } (${activeSection.total} ${activeSection.total === 1 ? 'item' : 'items'})`}\n </span>\n <button\n type=\"button\"\n onClick={() => pageBy(1)}\n disabled={activeSection.page >= activeSection.pageCount - 1}\n aria-keyshortcuts=\"PageDown\"\n >\n Next page\n </button>\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Navigator/index.tsx"],"names":[],"mappings":"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","file":"Navigator.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Navigator` — `<GraphNavigator>`, the\n * bounded semantic navigator.\n *\n * The navigator is the keyboard/screen-reader equivalent surface for the\n * canvas: a visually compact panel with `role=\"listbox\"` semantics driven by\n * a roving tabindex — exactly one item is tabbable at any time, and\n * `aria-activedescendant` on the listbox container mirrors the active item's\n * stable DOM id. It is bounded by design: every list is paged by\n * `accessibility.navigatorWindow` (default 50), so the DOM never grows one\n * row per entity.\n *\n * Sections:\n * - **Search results** — the last completed search\n * (`store.search`), shown whenever the slice is non-null (it clears on\n * `clearSearch` / dataset swap). Paged like every other list; Enter\n * routes through `instance.activateSearchResult` (result contract:\n * an in-scene, mask-visible id focuses; anything else is classification\n * only — the navigator never mutates scope/filters). Row text prefers the\n * service-supplied `label`.\n * - **Focused node** — the transiently focused root plus its paged 1-hop\n * neighborhood.\n * - **Selection** — the current node selection, paged.\n * - **All nodes** — the entry list (first `navigatorWindow` nodes in\n * accepted-base order, paged over the full set), shown when nothing is\n * focused.\n *\n * ## Keyboard model — `keyboardMode: 'topology'` (the v0.4 default and only mode)\n *\n * | Key | Action |\n * |---------------------|-------------------------------------------------------------------|\n * | ArrowDown / ArrowUp | move the active item within the current list |\n * | Home / End | move to the bounds of the current list |\n * | PageDown / PageUp | next / previous page of the active item's list |\n * | Enter | focus the node (`instance.focusNode` — camera fly) and re-root the neighborhood at it |\n * | Space | toggle the node's selection membership |\n * | Escape | clear the transient focus (returns to the entry list) |\n *\n * `keyboardMode: 'spatial'` (arrows move by screen direction) is a\n * documented post-v0.4 mode and intentionally NOT part of the accepted prop\n * union yet — only `'topology'` type-checks in v0.4.\n *\n * Text names resolve via `accessibility.getAccessibleLabel`, falling back to\n * `attrs.label` (when a string) and then the node id — always rendered as\n * TEXT NODES.\n * Each item exposes selection state (`aria-selected`), its neighbor count\n * when known, and pinned/hidden state in text (e.g. `hub · 5 neighbors ·\n * pinned`).\n *\n * Neighborhood semantics (v0.4): direction is UNDIRECTED — `focusNode`\n * resolves the 1-hop neighbor ids through the engine's adjacency when the\n * adapter provides one, else through the core CSR adjacency\n * (`buildAdjacency`/`neighborsOf`) built lazily per accepted model revision\n * and invalidated on model change; self-loops are excluded and parallel\n * edges deduplicate. The navigator caches the resolved neighborhood and the\n * per-node neighbor counts it has learned in refs keyed by\n * `revisions.model`; a model change drops the (now stale) transient root and\n * returns to the entry list. Neighbor counts therefore appear on items whose\n * neighborhood has been resolved (the current and previously visited roots);\n * a public O(1) degree read is a post-v0.4 core surface.\n *\n * Focus never lives only in WebGL pixels: the active item carries a\n * visible DOM focus outline and receives real DOM focus on keyboard moves.\n * Sync is navigator → graph (Enter → `focusNode` camera fly); graph →\n * navigator focus sync is deferred past v0.4. Space routes through\n * `instance.selectNodes`, so a controlled selection surfaces intent only\n * — `aria-selected` always reflects the store.\n */\n\nimport { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, KeyboardEvent, ReactElement } from 'react';\nimport type {\n AccessibilityConfig,\n GraphNode,\n NodeId,\n SearchResult,\n SelectionState,\n} from '@modernrelay/orbit-core';\nimport { getSearchResultUnavailableCallback } from '../../hooks';\nimport { useResolvedInstance } from '../shared';\nimport type { AnyGraphInstance } from '../../GraphProvider';\n\n/** default page size when `accessibility.navigatorWindow` is unset. */\nconst DEFAULT_NAVIGATOR_WINDOW = 50;\n\nexport interface GraphNavigatorProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /**\n * Keyboard model. v0.4 ships `'topology'` (the default; documented above);\n * `'spatial'` (arrows-by-screen-direction) is post-v0.4 and not accepted\n * by this union yet.\n */\n keyboardMode?: 'topology';\n /** Accessible name of the navigator panel. Default 'Graph navigator'. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/** Which paged id list a row belongs to (drives PageUp/PageDown targeting). */\ntype PagedListKey = 'search' | 'neighbors' | 'selection' | 'entry';\n\ninterface NavRow {\n /** Stable identity across renders: `${list}:${nodeId}` (`root:` for the root row). */\n key: string;\n /** Stable DOM id (aria-activedescendant target). */\n domId: string;\n nodeId: NodeId;\n listKey: PagedListKey;\n text: string;\n selected: boolean;\n}\n\ninterface NavSection {\n key: 'search' | 'focused' | 'selection' | 'entry';\n listKey: PagedListKey;\n label: string;\n /** Group accessible name; carries the 'page x of y' state. */\n groupLabel: string;\n rows: NavRow[];\n page: number;\n pageCount: number;\n /** Total pageable items (excludes the focused section's root chrome row). */\n total: number;\n}\n\n/** Transient navigator focus: the neighborhood root. */\ninterface NavRoot {\n id: NodeId;\n neighbors: readonly NodeId[];\n /** Model revision the neighborhood was resolved against. */\n model: number;\n}\n\ninterface NavStoreSlice {\n selection: SelectionState;\n pins: ReadonlyMap<NodeId, readonly [number, number]>;\n hidden: ReadonlySet<NodeId>;\n model: number;\n nodeCount: number;\n /** Last completed search; null hides the section. */\n search: { query: string; results: readonly SearchResult[] } | null;\n}\n\n/** Narrow store subscription: re-renders only when a navigator-relevant slice\n * changes (never on per-frame viewport writes). */\nfunction useNavStoreSlice(instance: AnyGraphInstance): NavStoreSlice {\n const cacheRef = useRef<NavStoreSlice | null>(null);\n const subscribe = useCallback(\n (cb: () => void) => instance.store.subscribe(cb),\n [instance],\n );\n const getSnapshot = useCallback((): NavStoreSlice => {\n const s = instance.store.getState();\n const prev = cacheRef.current;\n if (\n prev !== null &&\n prev.selection === s.selection &&\n prev.pins === s.pins &&\n prev.hidden === s.hiddenNodeIds &&\n prev.model === s.revisions.model &&\n prev.nodeCount === s.nodeCount &&\n prev.search === s.search\n ) {\n return prev;\n }\n const next: NavStoreSlice = {\n selection: s.selection,\n pins: s.pins,\n hidden: s.hiddenNodeIds,\n model: s.revisions.model,\n nodeCount: s.nodeCount,\n search: s.search,\n };\n cacheRef.current = next;\n return next;\n }, [instance]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\ninterface PageWindow {\n page: number;\n pageCount: number;\n items: readonly NodeId[];\n}\n\n/** Clamp a raw page into `[0, pageCount)` and slice that page's ids. */\nfunction pageWindow(all: readonly NodeId[], rawPage: number, size: number): PageWindow {\n const pageCount = Math.max(1, Math.ceil(all.length / size));\n const page = Math.min(Math.max(rawPage, 0), pageCount - 1);\n return { page, pageCount, items: all.slice(page * size, page * size + size) };\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n maxWidth: 320,\n fontSize: 12,\n};\nconst LIST_STYLE: CSSProperties = { overflowY: 'auto', maxHeight: 320 };\nconst HEADER_STYLE: CSSProperties = { fontWeight: 600, padding: '4px 6px' };\nconst ITEM_STYLE: CSSProperties = {\n padding: '2px 6px',\n cursor: 'default',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n/** visible DOM focus ring on the active item. */\nconst ACTIVE_ITEM_STYLE: CSSProperties = {\n ...ITEM_STYLE,\n outline: '2px solid currentColor',\n outlineOffset: -2,\n};\nconst PAGING_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 8 };\nconst EMPTY_STYLE: CSSProperties = { padding: '4px 6px', fontStyle: 'italic' };\n\nexport function GraphNavigator(props: GraphNavigatorProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphNavigator>');\n const slice = useNavStoreSlice(instance);\n\n // 'topology' is the only v0.4 mode; the prop exists so the shape is stable\n // when 'spatial' lands post-v0.4 (no behavioral switch yet).\n void props.keyboardMode;\n\n const baseId = useId();\n const [root, setRoot] = useState<NavRoot | null>(null);\n const [pages, setPages] = useState<Record<PagedListKey, number>>({\n search: 0,\n neighbors: 0,\n selection: 0,\n entry: 0,\n });\n /** Active row identity (roving target); null → first row. */\n const [activeKey, setActiveKey] = useState<string | null>(null);\n\n /** Stable per-node DOM-id tokens (node ids may contain arbitrary text). */\n const tokensRef = useRef<Map<NodeId, number>>(new Map());\n /** Learned neighbor counts, cached per model revision (see module JSDoc). */\n const degreesRef = useRef<{ model: number; counts: Map<NodeId, number> }>({\n model: -1,\n counts: new Map(),\n });\n const itemsRef = useRef<Map<string, HTMLElement>>(new Map());\n /** Set by keyboard handlers so the focus effect moves DOM focus once. */\n const pendingFocusRef = useRef(false);\n\n // A model change invalidates the resolved neighborhood: drop the transient\n // root (back to the entry list) rather than rendering stale rows.\n useEffect(() => {\n if (root !== null && root.model !== slice.model) setRoot(null);\n }, [root, slice.model]);\n\n // release the keyboard ring — but ONLY when no pointer hover owns the\n // shared channel (blur while the pointer rests on a canvas node must not\n // erase that node's hover ring; hover already superseded the sticky\n // keyboard target inside core, so there is nothing else to clear).\n const releaseEmphasis = useCallback((): void => {\n if (instance.store.getState().hover.nodeId === null) instance.emphasizeNode(null);\n }, [instance]);\n\n // A ring left by keyboard navigation must not outlive the navigator.\n useEffect(() => () => releaseEmphasis(), [releaseEmphasis]);\n\n // Idempotent render-time cache reset (ref only — safe under StrictMode).\n if (degreesRef.current.model !== slice.model) {\n degreesRef.current = { model: slice.model, counts: new Map() };\n }\n\n const accessibility = instance.getAccessibility() as\n | AccessibilityConfig<Record<string, unknown>>\n | undefined;\n const rawWindow = accessibility?.navigatorWindow;\n const windowSize =\n typeof rawWindow === 'number' && Number.isFinite(rawWindow) && rawWindow >= 1\n ? Math.floor(rawWindow)\n : DEFAULT_NAVIGATOR_WINDOW;\n\n const getAccessibleLabel = accessibility?.getAccessibleLabel;\n const nameOf = (id: NodeId): string => {\n const node = instance.getNode(id) as GraphNode<Record<string, unknown>> | undefined;\n if (node === undefined) return id;\n if (getAccessibleLabel !== undefined) {\n try {\n return getAccessibleLabel(node);\n } catch {\n // fall through to the built-in resolution\n }\n }\n const label = node.attrs?.['label'];\n return typeof label === 'string' ? label : node.id;\n };\n\n const selectedSet = new Set<NodeId>(slice.selection.nodeIds);\n\n /** '{name} · {n} neighbors · pinned · hidden' — always a single text node. */\n const rowText = (id: NodeId): string => {\n const parts = [nameOf(id)];\n const count = degreesRef.current.counts.get(id);\n if (count !== undefined) parts.push(count === 1 ? '1 neighbor' : `${count} neighbors`);\n if (slice.pins.has(id)) parts.push('pinned');\n if (slice.hidden.has(id)) parts.push('hidden');\n return parts.join(' · ');\n };\n\n const domIdFor = (listKey: string, id: NodeId): string => {\n const tokens = tokensRef.current;\n let token = tokens.get(id);\n if (token === undefined) {\n token = tokens.size;\n tokens.set(id, token);\n }\n return `${baseId}nav-${listKey}-${token}`;\n };\n\n const makeRow = (listKey: PagedListKey, id: NodeId): NavRow => ({\n key: `${listKey}:${id}`,\n domId: domIdFor(listKey, id),\n nodeId: id,\n listKey,\n text: rowText(id),\n selected: selectedSet.has(id),\n });\n\n // --- section/row model (all O(navigatorWindow) work) ---\n const searchResults: readonly SearchResult[] = slice.search === null ? [] : slice.search.results;\n const listItems: Record<PagedListKey, readonly NodeId[]> = {\n search: searchResults.map((r) => r.id),\n neighbors: root !== null ? root.neighbors : [],\n selection: slice.selection.nodeIds,\n // the entry list is the SCENE roster (scope applied, mask not)\n // hidden/filtered nodes stay listed with their state exposed in text.\n entry: root === null ? instance.getSceneNodeIds() : [],\n };\n\n const sections: NavSection[] = [];\n if (slice.search !== null) {\n const view = pageWindow(listItems.search, pages.search, windowSize);\n const count = searchResults.length;\n const start = view.page * windowSize;\n sections.push({\n key: 'search',\n listKey: 'search',\n label: 'Search results',\n groupLabel: `Search results for ${slice.search.query}: ${count} ${\n count === 1 ? 'result' : 'results'\n }, page ${view.page + 1} of ${view.pageCount}`,\n // row text prefers the service-supplied label (still a TEXT\n // NODE); results may name ids outside the model — nameOf falls back to\n // the id for those.\n rows: view.items.map((id, i) => {\n const row = makeRow('search', id);\n const label = searchResults[start + i]?.label;\n return label === undefined ? row : { ...row, text: label };\n }),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root !== null) {\n const view = pageWindow(listItems.neighbors, pages.neighbors, windowSize);\n const rootRow: NavRow = {\n key: `root:${root.id}`,\n domId: domIdFor('root', root.id),\n nodeId: root.id,\n listKey: 'neighbors',\n text: rowText(root.id),\n selected: selectedSet.has(root.id),\n };\n const count = root.neighbors.length;\n sections.push({\n key: 'focused',\n listKey: 'neighbors',\n label: 'Focused node',\n groupLabel: `Focused node ${nameOf(root.id)}: ${count} ${\n count === 1 ? 'neighbor' : 'neighbors'\n }, page ${view.page + 1} of ${view.pageCount}`,\n rows: [rootRow, ...view.items.map((id) => makeRow('neighbors', id))],\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (slice.selection.nodeIds.length > 0) {\n const view = pageWindow(listItems.selection, pages.selection, windowSize);\n const count = slice.selection.nodeIds.length;\n sections.push({\n key: 'selection',\n listKey: 'selection',\n label: 'Selection',\n groupLabel: `Selection: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('selection', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root === null) {\n const view = pageWindow(listItems.entry, pages.entry, windowSize);\n const count = listItems.entry.length;\n sections.push({\n key: 'entry',\n listKey: 'entry',\n label: 'All nodes',\n groupLabel: `All nodes: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('entry', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n\n const rows: NavRow[] = sections.flatMap((s) => s.rows);\n let activeIndex = activeKey === null ? -1 : rows.findIndex((r) => r.key === activeKey);\n if (activeIndex === -1 && rows.length > 0) activeIndex = 0;\n const activeRow = activeIndex >= 0 ? rows[activeIndex] : undefined;\n const activeSection =\n activeRow !== undefined ? sections.find((s) => s.listKey === activeRow.listKey) : undefined;\n\n // Move real DOM focus to the active item after keyboard-driven changes so\n // the focus ring is a genuine DOM focus, never only a WebGL highlight.\n useEffect(() => {\n if (!pendingFocusRef.current) return;\n pendingFocusRef.current = false;\n if (activeRow === undefined) return;\n itemsRef.current.get(activeRow.domId)?.focus();\n });\n\n // --- keyboard model ('topology'; see module JSDoc) ---\n const focusItemSoon = (): void => {\n pendingFocusRef.current = true;\n };\n\n const moveTo = (index: number): void => {\n if (rows.length === 0) return;\n const row = rows[Math.min(Math.max(index, 0), rows.length - 1)];\n if (row === undefined) return;\n setActiveKey(row.key);\n // Keyboard parity with pointer hover: ring the active row\n // WITHOUT flying the camera (that stays on Enter via focusNode).\n instance.emphasizeNode(row.nodeId);\n focusItemSoon();\n };\n\n const pageBy = (delta: number): void => {\n if (activeRow === undefined) return;\n const listKey = activeRow.listKey;\n const items = listItems[listKey];\n const pageCount = Math.max(1, Math.ceil(items.length / windowSize));\n const current = Math.min(Math.max(pages[listKey], 0), pageCount - 1);\n const next = Math.min(Math.max(current + delta, 0), pageCount - 1);\n if (next === current) return;\n setPages((p) => ({ ...p, [listKey]: next }));\n const first = items[next * windowSize];\n if (first !== undefined) {\n setActiveKey(`${listKey}:${first}`);\n instance.emphasizeNode(first);\n }\n focusItemSoon();\n };\n\n /** Enter: focus the node in the graph (camera fly) and re-root the\n * neighborhood. The returned ids come from the core adjacency resolution\n * (undirected; see module JSDoc). Search rows route through the\n * result contract instead: `activateSearchResult` focuses an in-scene,\n * mask-visible id and otherwise only CLASSIFIES — the navigator never\n * mutates scope/filters (the host reacts through its own channels). */\n const activate = (row: NavRow): void => {\n if (row.listKey === 'search') {\n const result = searchResults.find((r) => r.id === row.nodeId);\n if (result !== undefined) {\n const activation = instance.activateSearchResult(result);\n if (activation.status === 'unavailable') {\n getSearchResultUnavailableCallback(instance)?.(result, activation.reason);\n }\n }\n return;\n }\n const neighbors = instance.focusNode(row.nodeId);\n degreesRef.current.counts.set(row.nodeId, neighbors.length);\n setRoot({ id: row.nodeId, neighbors, model: slice.model });\n setPages((p) => ({ ...p, neighbors: 0 }));\n setActiveKey(`root:${row.nodeId}`);\n focusItemSoon();\n };\n\n /** Space: toggle membership through the selection ownership path.\n * Search rows may name ids outside the accepted model — those no-op. */\n const toggleSelection = (row: NavRow): void => {\n if (row.listKey === 'search' && instance.getNode(row.nodeId) === undefined) return;\n const current = slice.selection.nodeIds;\n const next = current.includes(row.nodeId)\n ? current.filter((id) => id !== row.nodeId)\n : [...current, row.nodeId];\n instance.selectNodes(next);\n };\n\n const clearTransientFocus = (): void => {\n setRoot(null);\n setActiveKey(null);\n releaseEmphasis();\n focusItemSoon();\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (activeRow === undefined) return;\n switch (e.key) {\n case 'ArrowDown':\n moveTo(activeIndex + 1);\n break;\n case 'ArrowUp':\n moveTo(activeIndex - 1);\n break;\n case 'Home':\n moveTo(0);\n break;\n case 'End':\n moveTo(rows.length - 1);\n break;\n case 'PageDown':\n pageBy(1);\n break;\n case 'PageUp':\n pageBy(-1);\n break;\n case 'Enter':\n activate(activeRow);\n break;\n case ' ':\n toggleSelection(activeRow);\n break;\n case 'Escape':\n if (root === null) return; // nothing transient — let it bubble\n clearTransientFocus();\n break;\n default:\n return;\n }\n e.preventDefault();\n };\n\n const panelLabel = props.label ?? 'Graph navigator';\n\n return (\n <div\n data-orbit-navigator=\"\"\n role=\"group\"\n aria-label={panelLabel}\n className={props.className}\n style={{ ...PANEL_STYLE, ...props.style }}\n onBlur={(e) => {\n // Focus left the panel entirely (paging buttons stay inside): the\n // keyboard ring clears, mirroring pointer hover leaving the canvas.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {\n releaseEmphasis();\n }\n }}\n >\n <div\n role=\"listbox\"\n aria-label={`${panelLabel} items`}\n aria-activedescendant={activeRow?.domId}\n aria-keyshortcuts=\"ArrowUp ArrowDown Home End PageUp PageDown Enter Space Escape\"\n onKeyDown={onKeyDown}\n style={LIST_STYLE}\n >\n {rows.length === 0 ? (\n <div role=\"presentation\" style={EMPTY_STYLE}>\n No nodes\n </div>\n ) : (\n sections.map((section) => (\n <div key={section.key} role=\"group\" aria-label={section.groupLabel}>\n <div role=\"presentation\" style={HEADER_STYLE}>\n {section.label}\n </div>\n {section.rows.map((row) => {\n const isActive = row.key === activeRow?.key;\n return (\n <div\n key={row.key}\n id={row.domId}\n role=\"option\"\n aria-selected={row.selected}\n tabIndex={isActive ? 0 : -1}\n ref={(el) => {\n if (el === null) itemsRef.current.delete(row.domId);\n else itemsRef.current.set(row.domId, el);\n }}\n onClick={() => {\n setActiveKey(row.key);\n instance.emphasizeNode(row.nodeId); // active row = ringed row\n focusItemSoon();\n }}\n style={isActive ? ACTIVE_ITEM_STYLE : ITEM_STYLE}\n >\n {row.text}\n </div>\n );\n })}\n </div>\n ))\n )}\n </div>\n {activeSection !== undefined ? (\n <div data-orbit-navigator-paging=\"\" style={PAGING_STYLE}>\n <button\n type=\"button\"\n onClick={() => pageBy(-1)}\n disabled={activeSection.page === 0}\n aria-keyshortcuts=\"PageUp\"\n >\n Previous page\n </button>\n <span role=\"status\" data-orbit-navigator-page-status=\"\">\n {`${activeSection.label}: page ${activeSection.page + 1} of ${\n activeSection.pageCount\n } (${activeSection.total} ${activeSection.total === 1 ? 'item' : 'items'})`}\n </span>\n <button\n type=\"button\"\n onClick={() => pageBy(1)}\n disabled={activeSection.page >= activeSection.pageCount - 1}\n aria-keyshortcuts=\"PageDown\"\n >\n Next page\n </button>\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
@@ -106,6 +106,7 @@ function GraphTooltip(props) {
|
|
|
106
106
|
setVisibleKey(null);
|
|
107
107
|
return void 0;
|
|
108
108
|
}
|
|
109
|
+
setVisibleKey((current) => current === hoverKey ? current : null);
|
|
109
110
|
const timer = setTimeout(() => {
|
|
110
111
|
setVisibleKey(hoverKey);
|
|
111
112
|
}, delayMs);
|
|
@@ -146,7 +147,7 @@ function GraphTooltip(props) {
|
|
|
146
147
|
useLayoutEffect(() => {
|
|
147
148
|
if (visibleKey !== null) position(decodeHover(visibleKey).id);
|
|
148
149
|
}, [visibleKey, position]);
|
|
149
|
-
const target = visibleKey === null ? null : decodeHover(visibleKey);
|
|
150
|
+
const target = visibleKey === null || visibleKey !== hoverKey ? null : decodeHover(visibleKey);
|
|
150
151
|
const node = target?.kind === "node" ? instance.getNode(target.id) : void 0;
|
|
151
152
|
const edge = target?.kind === "edge" ? instance.getEdge(target.id) : void 0;
|
|
152
153
|
const item = node ?? edge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Tooltip/index.tsx"],"names":[],"mappings":"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","file":"Tooltip.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the\n * hover card.\n *\n * Visibility rides the store hover slice: a\n * non-null hover arms a `delayMs` timer (default 150); the card mounts when\n * it fires and unmounts IMMEDIATELY when hover clears — no flicker-out delay.\n *\n * BOTH hover namespaces are served: `hover.nodeId` and `hover.edgeId`. A node\n * WINS when both are set — it is the more specific target and sits on top of\n * its own links. Edges have no entry in the overlay position lane (that lane\n * tracks labeled NODE slots), so an edge card always rides the cursor\n * fallback, which is exactly right: the hovered edge is under the pointer.\n *\n * Positioning is imperative (transform writes on a ref — never a React\n * re-render per movement), fed by two lanes:\n * 1. the overlay position lane (`instance.labels.subscribePositions`,\n * the same public seam <LabelLayer> renders from): when the hovered id is\n * in the tracked candidate set, each scheduler tick writes its fresh\n * container-relative screen x/y, so the card follows the node through\n * simulation drift and camera moves;\n * 2. the pointer fallback: when the hovered id is NOT label-tracked (the\n * common case — labels cover k ranked nodes), a window `pointermove`\n * listener (attached only while hovering) pins the card to the cursor\n * + a fixed offset. The engine's `spaceToScreen` is not reachable through\n * the public instance surface in v0.9, so cursor-anchoring is the\n * documented fallback — the hovered node is by definition under the\n * cursor.\n *\n * Default content: the target's label (`attrs.label` string, else id) plus up\n * to {@link TOOLTIP_ATTR_ROWS} attr rows (label excluded — it is the title),\n * every key/value rendered as TEXT NODES only (untrusted-content rule;\n * hostile attr strings appear literally). `render` / `renderEdge` replace the\n * card content entirely, and `getEdgeText` retitles an edge card without\n * taking over its body — useful where the meaningful name lives in an\n * adapter-injected attr rather than `label` (an Omnigraph edge's relationship\n * type is at `attrs['orbit:type']`, for instance). The card is pointer-inert\n * (it must never steal canvas hover).\n */\n\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { AcceptedEdge, GraphNode, NodeId } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { overlaySurface, useResolvedInstance } from '../shared';\n\nconst DELAY_MS_DEFAULT = 150;\n/** Max attr rows in the default card content. */\nexport const TOOLTIP_ATTR_ROWS = 6;\n/** Cursor-anchored offset (px) so the card never sits under the pointer. */\nconst POINTER_OFFSET_PX = 12;\n\nexport interface GraphTooltipProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Replaces the default card content for a NODE (label + attr rows). */\n render?: (node: GraphNode<any>) => ReactNode;\n /** Replaces the default card content for an EDGE. */\n renderEdge?: (edge: AcceptedEdge<any>) => ReactNode;\n /** Title text for an edge card. Default `attrs.label ?? id`. */\n getEdgeText?: (edge: AcceptedEdge<any>) => string;\n /** Set false to ignore edge hovers entirely (nodes only). Default true. */\n edges?: boolean;\n /** Hover → show delay in ms. Default 150. */\n delayMs?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst LAYER_STYLE: CSSProperties = {\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst CARD_STYLE: CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n maxWidth: 280,\n padding: '6px 10px',\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.96)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '12px/1.5 system-ui, sans-serif',\n pointerEvents: 'none',\n whiteSpace: 'nowrap',\n willChange: 'transform',\n};\n\nconst TITLE_STYLE: CSSProperties = {\n fontWeight: 600,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n\nconst ROW_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'space-between',\n gap: 12,\n overflow: 'hidden',\n};\n\nconst ROW_KEY_STYLE: CSSProperties = { opacity: 0.65 };\nconst ROW_VALUE_STYLE: CSSProperties = { overflow: 'hidden', textOverflow: 'ellipsis' };\n\n/** Same derivation as the core label default: `attrs.label ?? id`. */\nfunction nodeLabelText(node: GraphNode<any>): string {\n const attrs = node.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(node.id) : String(label);\n}\n\n/** Attr value → display TEXT (never markup): primitives verbatim, structured\n * values JSON (guarded — cycles degrade to the String form). */\nfunction attrValueText(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/** The hovered target, node-first. Encoded as a STRING (`'node\\u0000id'`)\n * rather than an object so `useSyncExternalStore` compares by value — a fresh\n * object every publication would loop. */\ntype HoverKey = string;\n\nfunction encodeHover(kind: 'node' | 'edge', id: string): HoverKey {\n return `${kind}\\u0000${id}`;\n}\n\nfunction decodeHover(key: HoverKey): { kind: 'node' | 'edge'; id: string } {\n const at = key.indexOf('\\u0000');\n return { kind: key.slice(0, at) as 'node' | 'edge', id: key.slice(at + 1) };\n}\n\nfunction useHoveredTarget(instance: AnyGraphInstance, edges: boolean): HoverKey | null {\n const subscribe = useCallback(\n (onChange: () => void) => instance.store.subscribe(onChange),\n [instance],\n );\n const getSnapshot = useCallback((): HoverKey | null => {\n const hover = instance.store.getState().hover;\n // Node wins: it is the more specific target and sits above its links.\n if (hover.nodeId !== null) return encodeHover('node', hover.nodeId);\n if (edges && hover.edgeId !== null) return encodeHover('edge', hover.edgeId);\n return null;\n }, [instance, edges]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Same derivation as the node default: `attrs.label ?? id`. */\nfunction edgeLabelText(edge: AcceptedEdge<any>): string {\n const attrs = edge.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(edge.id) : String(label);\n}\n\nexport function GraphTooltip(props: GraphTooltipProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphTooltip>');\n const labels = overlaySurface(instance).labels;\n const delayMs = props.delayMs ?? DELAY_MS_DEFAULT;\n\n const edgesEnabled = props.edges ?? true;\n const hoverKey = useHoveredTarget(instance, edgesEnabled);\n /** The target the mounted card shows — hoverKey promoted after delayMs. */\n const [visibleKey, setVisibleKey] = useState<HoverKey | null>(null);\n\n const layerRef = useRef<HTMLDivElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n /** Freshest label-lane screen position per tracked id (container-relative). */\n const trackedRef = useRef(new Map<NodeId, readonly [number, number]>());\n /** Freshest cursor position (container-relative, offset applied). */\n const pointerRef = useRef<readonly [number, number] | null>(null);\n\n /** One place decides the transform: label lane wins when it tracks the id. */\n const position = useCallback(\n (id: NodeId): void => {\n const el = cardRef.current;\n if (el === null) return;\n const at = trackedRef.current.get(id) ?? pointerRef.current;\n if (at === null || at === undefined) return;\n el.style.transform = `translate3d(${at[0]}px, ${at[1]}px, 0)`;\n },\n [],\n );\n\n // Delay lane: arm on hover, cancel/hide immediately on unhover.\n useEffect(() => {\n if (hoverKey === null) {\n setVisibleKey(null);\n return undefined;\n }\n const timer = setTimeout(() => {\n setVisibleKey(hoverKey);\n }, delayMs);\n return () => {\n clearTimeout(timer);\n };\n }, [hoverKey, delayMs]);\n\n // Position lane 1: the overlay position lane (same seam as LabelLayer).\n useLayoutEffect(() => {\n if (labels === undefined) return undefined;\n return labels.subscribePositions((list) => {\n const tracked = trackedRef.current;\n tracked.clear();\n for (const p of list) tracked.set(p.id, [p.x, p.y]);\n const id = instance.store.getState().hover.nodeId;\n if (id !== null && tracked.has(id)) position(id);\n });\n }, [labels, instance, position]);\n\n // Position lane 2: cursor fallback. The listener lives for the component's\n // lifetime (storing two numbers per move) so a hover that lands WITHOUT a\n // subsequent move still knows where the cursor already is; it only writes\n // the card transform while a node is hovered and the id is untracked.\n useEffect(() => {\n const onMove = (e: PointerEvent): void => {\n // Container-relative like the label lane: subtract the positioned\n // ancestor's origin (zero-rect hosts — jsdom — degrade to client px).\n const host = layerRef.current?.offsetParent;\n const origin = host instanceof Element ? host.getBoundingClientRect() : null;\n pointerRef.current = [\n e.clientX - (origin?.left ?? 0) + POINTER_OFFSET_PX,\n e.clientY - (origin?.top ?? 0) + POINTER_OFFSET_PX,\n ];\n const hover = instance.store.getState().hover;\n if (hover.nodeId !== null) {\n if (!trackedRef.current.has(hover.nodeId)) position(hover.nodeId);\n } else if (hover.edgeId !== null) {\n // Edges never appear in the label lane — always cursor-anchored.\n position(hover.edgeId);\n }\n };\n window.addEventListener('pointermove', onMove);\n return () => {\n window.removeEventListener('pointermove', onMove);\n };\n }, [instance, position]);\n\n // Place the card the moment it mounts (before paint — no corner flash).\n useLayoutEffect(() => {\n if (visibleKey !== null) position(decodeHover(visibleKey).id);\n }, [visibleKey, position]);\n\n const target = visibleKey === null ? null : decodeHover(visibleKey);\n const node =\n target?.kind === 'node'\n ? (instance.getNode(target.id) as GraphNode<any> | undefined)\n : undefined;\n const edge =\n target?.kind === 'edge'\n ? (instance.getEdge(target.id) as AcceptedEdge<any> | undefined)\n : undefined;\n const item = node ?? edge;\n const isEdge = edge !== undefined;\n\n /** Title + custom body, shared by both kinds — only the text derivation and\n * the `data-orbit-tooltip-edge` marker differ. Computed ONLY when a target\n * actually resolved: `visibleKey` can name an id the model no longer holds\n * (a hover surviving a dataset swap), and `target` is null whenever nothing\n * is hovered at all. */\n const title =\n item === undefined\n ? ''\n : isEdge\n ? (props.getEdgeText ?? edgeLabelText)(edge)\n : nodeLabelText(node as GraphNode<any>);\n const custom =\n item === undefined\n ? undefined\n : isEdge\n ? props.renderEdge?.(edge)\n : props.render?.(node as GraphNode<any>);\n\n return (\n <div ref={layerRef} data-orbit-tooltip-layer=\"\" style={LAYER_STYLE}>\n {item !== undefined ? (\n <div\n ref={cardRef}\n data-orbit-tooltip=\"\"\n {...(isEdge ? { 'data-orbit-tooltip-edge': '' } : {})}\n role=\"tooltip\"\n className={props.className}\n style={{ ...CARD_STYLE, ...props.style }}\n >\n {custom !== undefined ? (\n custom\n ) : (\n <>\n {/* Untrusted text renders as TEXT NODES only. */}\n <div data-orbit-tooltip-label=\"\" style={TITLE_STYLE}>\n {title}\n </div>\n {Object.entries((item.attrs ?? {}) as Record<string, unknown>)\n .filter(([key]) => key !== 'label')\n .slice(0, TOOLTIP_ATTR_ROWS)\n .map(([key, value]) => (\n <div key={key} data-orbit-tooltip-attr={key} style={ROW_STYLE}>\n <span style={ROW_KEY_STYLE}>{key}</span>\n <span style={ROW_VALUE_STYLE}>{attrValueText(value)}</span>\n </div>\n ))}\n </>\n )}\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Tooltip/index.tsx"],"names":[],"mappings":"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","file":"Tooltip.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the\n * hover card.\n *\n * Visibility rides the store hover slice: a\n * non-null hover arms a `delayMs` timer (default 150); the card mounts when\n * it fires and unmounts IMMEDIATELY when hover clears — no flicker-out delay.\n *\n * BOTH hover namespaces are served: `hover.nodeId` and `hover.edgeId`. A node\n * WINS when both are set — it is the more specific target and sits on top of\n * its own links. Edges have no entry in the overlay position lane (that lane\n * tracks labeled NODE slots), so an edge card always rides the cursor\n * fallback, which is exactly right: the hovered edge is under the pointer.\n *\n * Positioning is imperative (transform writes on a ref — never a React\n * re-render per movement), fed by two lanes:\n * 1. the overlay position lane (`instance.labels.subscribePositions`,\n * the same public seam <LabelLayer> renders from): when the hovered id is\n * in the tracked candidate set, each scheduler tick writes its fresh\n * container-relative screen x/y, so the card follows the node through\n * simulation drift and camera moves;\n * 2. the pointer fallback: when the hovered id is NOT label-tracked (the\n * common case — labels cover k ranked nodes), a window `pointermove`\n * listener (attached only while hovering) pins the card to the cursor\n * + a fixed offset. The engine's `spaceToScreen` is not reachable through\n * the public instance surface in v0.9, so cursor-anchoring is the\n * documented fallback — the hovered node is by definition under the\n * cursor.\n *\n * Default content: the target's label (`attrs.label` string, else id) plus up\n * to {@link TOOLTIP_ATTR_ROWS} attr rows (label excluded — it is the title),\n * every key/value rendered as TEXT NODES only (untrusted-content rule;\n * hostile attr strings appear literally). `render` / `renderEdge` replace the\n * card content entirely, and `getEdgeText` retitles an edge card without\n * taking over its body — useful where the meaningful name lives in an\n * adapter-injected attr rather than `label` (an Omnigraph edge's relationship\n * type is at `attrs['orbit:type']`, for instance). The card is pointer-inert\n * (it must never steal canvas hover).\n */\n\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { AcceptedEdge, GraphNode, NodeId } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { overlaySurface, useResolvedInstance } from '../shared';\n\nconst DELAY_MS_DEFAULT = 150;\n/** Max attr rows in the default card content. */\nexport const TOOLTIP_ATTR_ROWS = 6;\n/** Cursor-anchored offset (px) so the card never sits under the pointer. */\nconst POINTER_OFFSET_PX = 12;\n\nexport interface GraphTooltipProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Replaces the default card content for a NODE (label + attr rows). */\n render?: (node: GraphNode<any>) => ReactNode;\n /** Replaces the default card content for an EDGE. */\n renderEdge?: (edge: AcceptedEdge<any>) => ReactNode;\n /** Title text for an edge card. Default `attrs.label ?? id`. */\n getEdgeText?: (edge: AcceptedEdge<any>) => string;\n /** Set false to ignore edge hovers entirely (nodes only). Default true. */\n edges?: boolean;\n /** Hover → show delay in ms. Default 150. */\n delayMs?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst LAYER_STYLE: CSSProperties = {\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst CARD_STYLE: CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n maxWidth: 280,\n padding: '6px 10px',\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.96)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '12px/1.5 system-ui, sans-serif',\n pointerEvents: 'none',\n whiteSpace: 'nowrap',\n willChange: 'transform',\n};\n\nconst TITLE_STYLE: CSSProperties = {\n fontWeight: 600,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n\nconst ROW_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'space-between',\n gap: 12,\n overflow: 'hidden',\n};\n\nconst ROW_KEY_STYLE: CSSProperties = { opacity: 0.65 };\nconst ROW_VALUE_STYLE: CSSProperties = { overflow: 'hidden', textOverflow: 'ellipsis' };\n\n/** Same derivation as the core label default: `attrs.label ?? id`. */\nfunction nodeLabelText(node: GraphNode<any>): string {\n const attrs = node.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(node.id) : String(label);\n}\n\n/** Attr value → display TEXT (never markup): primitives verbatim, structured\n * values JSON (guarded — cycles degrade to the String form). */\nfunction attrValueText(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/** The hovered target, node-first. Encoded as a STRING (`'node\\u0000id'`)\n * rather than an object so `useSyncExternalStore` compares by value — a fresh\n * object every publication would loop. */\ntype HoverKey = string;\n\nfunction encodeHover(kind: 'node' | 'edge', id: string): HoverKey {\n return `${kind}\\u0000${id}`;\n}\n\nfunction decodeHover(key: HoverKey): { kind: 'node' | 'edge'; id: string } {\n const at = key.indexOf('\\u0000');\n return { kind: key.slice(0, at) as 'node' | 'edge', id: key.slice(at + 1) };\n}\n\nfunction useHoveredTarget(instance: AnyGraphInstance, edges: boolean): HoverKey | null {\n const subscribe = useCallback(\n (onChange: () => void) => instance.store.subscribe(onChange),\n [instance],\n );\n const getSnapshot = useCallback((): HoverKey | null => {\n const hover = instance.store.getState().hover;\n // Node wins: it is the more specific target and sits above its links.\n if (hover.nodeId !== null) return encodeHover('node', hover.nodeId);\n if (edges && hover.edgeId !== null) return encodeHover('edge', hover.edgeId);\n return null;\n }, [instance, edges]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Same derivation as the node default: `attrs.label ?? id`. */\nfunction edgeLabelText(edge: AcceptedEdge<any>): string {\n const attrs = edge.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(edge.id) : String(label);\n}\n\nexport function GraphTooltip(props: GraphTooltipProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphTooltip>');\n const labels = overlaySurface(instance).labels;\n const delayMs = props.delayMs ?? DELAY_MS_DEFAULT;\n\n const edgesEnabled = props.edges ?? true;\n const hoverKey = useHoveredTarget(instance, edgesEnabled);\n /** The target the mounted card shows — hoverKey promoted after delayMs. */\n const [visibleKey, setVisibleKey] = useState<HoverKey | null>(null);\n\n const layerRef = useRef<HTMLDivElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n /** Freshest label-lane screen position per tracked id (container-relative). */\n const trackedRef = useRef(new Map<NodeId, readonly [number, number]>());\n /** Freshest cursor position (container-relative, offset applied). */\n const pointerRef = useRef<readonly [number, number] | null>(null);\n\n /** One place decides the transform: label lane wins when it tracks the id. */\n const position = useCallback(\n (id: NodeId): void => {\n const el = cardRef.current;\n if (el === null) return;\n const at = trackedRef.current.get(id) ?? pointerRef.current;\n if (at === null || at === undefined) return;\n el.style.transform = `translate3d(${at[0]}px, ${at[1]}px, 0)`;\n },\n [],\n );\n\n // Delay lane: arm on hover, cancel/hide immediately on unhover OR target\n // replacement. Keeping the previous card mounted while a new target's\n // timer runs presents stale content for an item that is no longer hovered.\n useEffect(() => {\n if (hoverKey === null) {\n setVisibleKey(null);\n return undefined;\n }\n setVisibleKey((current) => (current === hoverKey ? current : null));\n const timer = setTimeout(() => {\n setVisibleKey(hoverKey);\n }, delayMs);\n return () => {\n clearTimeout(timer);\n };\n }, [hoverKey, delayMs]);\n\n // Position lane 1: the overlay position lane (same seam as LabelLayer).\n useLayoutEffect(() => {\n if (labels === undefined) return undefined;\n return labels.subscribePositions((list) => {\n const tracked = trackedRef.current;\n tracked.clear();\n for (const p of list) tracked.set(p.id, [p.x, p.y]);\n const id = instance.store.getState().hover.nodeId;\n if (id !== null && tracked.has(id)) position(id);\n });\n }, [labels, instance, position]);\n\n // Position lane 2: cursor fallback. The listener lives for the component's\n // lifetime (storing two numbers per move) so a hover that lands WITHOUT a\n // subsequent move still knows where the cursor already is; it only writes\n // the card transform while a node is hovered and the id is untracked.\n useEffect(() => {\n const onMove = (e: PointerEvent): void => {\n // Container-relative like the label lane: subtract the positioned\n // ancestor's origin (zero-rect hosts — jsdom — degrade to client px).\n const host = layerRef.current?.offsetParent;\n const origin = host instanceof Element ? host.getBoundingClientRect() : null;\n pointerRef.current = [\n e.clientX - (origin?.left ?? 0) + POINTER_OFFSET_PX,\n e.clientY - (origin?.top ?? 0) + POINTER_OFFSET_PX,\n ];\n const hover = instance.store.getState().hover;\n if (hover.nodeId !== null) {\n if (!trackedRef.current.has(hover.nodeId)) position(hover.nodeId);\n } else if (hover.edgeId !== null) {\n // Edges never appear in the label lane — always cursor-anchored.\n position(hover.edgeId);\n }\n };\n window.addEventListener('pointermove', onMove);\n return () => {\n window.removeEventListener('pointermove', onMove);\n };\n }, [instance, position]);\n\n // Place the card the moment it mounts (before paint — no corner flash).\n useLayoutEffect(() => {\n if (visibleKey !== null) position(decodeHover(visibleKey).id);\n }, [visibleKey, position]);\n\n // Render-gate synchronously as well as clearing state in the effect: on the\n // first commit for a replacement hover, effects have not run yet and\n // `visibleKey` still names the previous target.\n const target = visibleKey === null || visibleKey !== hoverKey ? null : decodeHover(visibleKey);\n const node =\n target?.kind === 'node'\n ? (instance.getNode(target.id) as GraphNode<any> | undefined)\n : undefined;\n const edge =\n target?.kind === 'edge'\n ? (instance.getEdge(target.id) as AcceptedEdge<any> | undefined)\n : undefined;\n const item = node ?? edge;\n const isEdge = edge !== undefined;\n\n /** Title + custom body, shared by both kinds — only the text derivation and\n * the `data-orbit-tooltip-edge` marker differ. Computed ONLY when a target\n * actually resolved: `visibleKey` can name an id the model no longer holds\n * (a hover surviving a dataset swap), and `target` is null whenever nothing\n * is hovered at all. */\n const title =\n item === undefined\n ? ''\n : isEdge\n ? (props.getEdgeText ?? edgeLabelText)(edge)\n : nodeLabelText(node as GraphNode<any>);\n const custom =\n item === undefined\n ? undefined\n : isEdge\n ? props.renderEdge?.(edge)\n : props.render?.(node as GraphNode<any>);\n\n return (\n <div ref={layerRef} data-orbit-tooltip-layer=\"\" style={LAYER_STYLE}>\n {item !== undefined ? (\n <div\n ref={cardRef}\n data-orbit-tooltip=\"\"\n {...(isEdge ? { 'data-orbit-tooltip-edge': '' } : {})}\n role=\"tooltip\"\n className={props.className}\n style={{ ...CARD_STYLE, ...props.style }}\n >\n {custom !== undefined ? (\n custom\n ) : (\n <>\n {/* Untrusted text renders as TEXT NODES only. */}\n <div data-orbit-tooltip-label=\"\" style={TITLE_STYLE}>\n {title}\n </div>\n {Object.entries((item.attrs ?? {}) as Record<string, unknown>)\n .filter(([key]) => key !== 'label')\n .slice(0, TOOLTIP_ATTR_ROWS)\n .map(([key, value]) => (\n <div key={key} data-orbit-tooltip-attr={key} style={ROW_STYLE}>\n <span style={ROW_KEY_STYLE}>{key}</span>\n <span style={ROW_VALUE_STYLE}>{attrValueText(value)}</span>\n </div>\n ))}\n </>\n )}\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode, CSSProperties, RefAttributes, ReactElement } from 'react';
|
|
2
|
-
import { GraphSnapshotInput, Accessor, GraphNode, Scale, AcceptedEdge, LayoutKind,
|
|
2
|
+
import { GraphSnapshotInput, Accessor, GraphNode, Scale, AcceptedEdge, LayoutKind, SimulationInput, ThemeInput, MetricColumn, ScaleChannelInfo, GraphTheme, LabelConfig, AccessibilityConfig, NodeId, SubgraphSpec, FilterSpec, DimensionSpec, GroupSpec, GroupBySpec, ClusterSpec, ScaleLimits, WorkerFactoryOption, SearchResult, SearchUnavailableReason, CreateGraphInstanceOptions, SelectionState, DegradeEvent, GraphPerfSnapshot, ResolvedGroup, MetaEdge, JsonValue, ViewportState, GraphViewState, SetViewStateResult, PathOptions, PathResult, ExpandNodeResult, ResolvedCluster, BeginIngestOptions, IngestSession, TimelinePlayback, CrossfilterSession, MetricName, Revisions, GraphDiagnostic, GraphInstance, DimensionSummary, BrushState, EdgeId, InstanceStatus } from '@modernrelay/orbit-core';
|
|
3
3
|
import { EngineFactory, FitViewOptions } from '@modernrelay/orbit-core/engine';
|
|
4
4
|
export { G as GraphProvider, a as GraphProviderProps } from './GraphProvider-B8ITDXS0.js';
|
|
5
5
|
export { D as DEFAULT_CORNER_OFFSET, G as GraphCorner, a as GraphLabelsSurface, b as GraphOverlaySurface, u as useResolvedInstance } from './shared-n98tkXJe.js';
|
|
@@ -32,7 +32,9 @@ interface GraphProps<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
|
32
32
|
linkColor?: Accessor<AcceptedEdge<E>, string>;
|
|
33
33
|
linkWidth?: Accessor<AcceptedEdge<E>, number>;
|
|
34
34
|
layout?: LayoutKind;
|
|
35
|
-
|
|
35
|
+
/** Full config or a named preset ('calm' | 'spread' | 'tight' |
|
|
36
|
+
* 'lively'). Omitted = the 'calm' preset — the measured-good default. */
|
|
37
|
+
simulation?: SimulationInput;
|
|
36
38
|
/** theme tokens: full GraphTheme, Partial over a named base, or the
|
|
37
39
|
* v0.1 `{background}` shorthand. Diffed structurally (JSON). */
|
|
38
40
|
theme?: ThemeInput;
|
|
@@ -146,6 +148,20 @@ interface GraphProps<N = Record<string, unknown>, E = Record<string, unknown>> {
|
|
|
146
148
|
onSearchResultUnavailable?: (result: SearchResult, reason: SearchUnavailableReason) => void;
|
|
147
149
|
/** Captured at first render (instance construction option). Default true. */
|
|
148
150
|
fitViewOnFirstData?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Camera behavior while the FIRST force-layout settle runs (construction-
|
|
153
|
+
* only). 'follow' (default): the camera keeps the contracting graph framed
|
|
154
|
+
* with periodic animated refits and a final fit at quiescence; any user
|
|
155
|
+
* camera input cancels it. 'once': single fit at first quiescence.
|
|
156
|
+
* false: fit at first data only.
|
|
157
|
+
*/
|
|
158
|
+
fitViewOnSettle?: 'follow' | 'once' | false;
|
|
159
|
+
/**
|
|
160
|
+
* Upper bound on the zoom any internally issued fit lands at (construction-
|
|
161
|
+
* only). Small graphs otherwise fit until nodes balloon. Default 1.5;
|
|
162
|
+
* null disables the clamp.
|
|
163
|
+
*/
|
|
164
|
+
fitViewMaxZoom?: number | null;
|
|
149
165
|
/** service seam (instance construction option, D7): custom
|
|
150
166
|
* revision-aware services — most usefully an async `expansion` service
|
|
151
167
|
* backed by the host's own data source, so `expandNode`/the context menu's
|
|
@@ -380,9 +396,12 @@ declare const Graph: GraphComponent;
|
|
|
380
396
|
* the SAME set → imperative `style.transform` writes through refs keyed by
|
|
381
397
|
* id. No React re-render per tick (M0: the label lane is pure CPU O(k)).
|
|
382
398
|
*
|
|
383
|
-
* The layer is pointer-inert; label divs opt back in
|
|
384
|
-
*
|
|
385
|
-
*
|
|
399
|
+
* The layer is pointer-inert; built-in label divs opt back in as focusable
|
|
400
|
+
* buttons and drive the same public selection mutators by click or Enter/Space
|
|
401
|
+
* (replace normally, toggle with meta/shift). Custom renderers own their
|
|
402
|
+
* content's focus semantics, avoiding a button-role wrapper around a native
|
|
403
|
+
* interactive control. Node labels expose their context action through
|
|
404
|
+
* right-click plus ContextMenu/Shift+F10.
|
|
386
405
|
*/
|
|
387
406
|
|
|
388
407
|
interface LabelLayerProps {
|