@modernrelay/orbit-react 0.2.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/GraphProvider-B8ITDXS0.d.ts +11 -0
  3. package/dist/chunk-7XB47YST.js +69 -0
  4. package/dist/chunk-7XB47YST.js.map +1 -0
  5. package/dist/chunk-DGFLU2SN.js +108 -0
  6. package/dist/chunk-DGFLU2SN.js.map +1 -0
  7. package/dist/chunk-JN7QCMFW.js +93 -0
  8. package/dist/chunk-JN7QCMFW.js.map +1 -0
  9. package/dist/chunk-QJ7JXQVK.js +119 -0
  10. package/dist/chunk-QJ7JXQVK.js.map +1 -0
  11. package/dist/components/ContextMenu.d.ts +73 -0
  12. package/dist/components/ContextMenu.js +367 -0
  13. package/dist/components/ContextMenu.js.map +1 -0
  14. package/dist/components/Histogram.d.ts +33 -0
  15. package/dist/components/Histogram.js +216 -0
  16. package/dist/components/Histogram.js.map +1 -0
  17. package/dist/components/Inspector.d.ts +63 -0
  18. package/dist/components/Inspector.js +246 -0
  19. package/dist/components/Inspector.js.map +1 -0
  20. package/dist/components/Legend.d.ts +45 -0
  21. package/dist/components/Legend.js +185 -0
  22. package/dist/components/Legend.js.map +1 -0
  23. package/dist/components/Minimap.d.ts +63 -0
  24. package/dist/components/Minimap.js +222 -0
  25. package/dist/components/Minimap.js.map +1 -0
  26. package/dist/components/Navigator.d.ts +90 -0
  27. package/dist/components/Navigator.js +381 -0
  28. package/dist/components/Navigator.js.map +1 -0
  29. package/dist/components/Search.d.ts +70 -0
  30. package/dist/components/Search.js +203 -0
  31. package/dist/components/Search.js.map +1 -0
  32. package/dist/components/SelectionActions.d.ts +36 -0
  33. package/dist/components/SelectionActions.js +126 -0
  34. package/dist/components/SelectionActions.js.map +1 -0
  35. package/dist/components/SimControls.d.ts +124 -0
  36. package/dist/components/SimControls.js +221 -0
  37. package/dist/components/SimControls.js.map +1 -0
  38. package/dist/components/Table.d.ts +92 -0
  39. package/dist/components/Table.js +505 -0
  40. package/dist/components/Table.js.map +1 -0
  41. package/dist/components/Timeline.d.ts +34 -0
  42. package/dist/components/Timeline.js +182 -0
  43. package/dist/components/Timeline.js.map +1 -0
  44. package/dist/components/Toolbar.d.ts +60 -0
  45. package/dist/components/Toolbar.js +220 -0
  46. package/dist/components/Toolbar.js.map +1 -0
  47. package/dist/components/Tooltip.d.ts +65 -0
  48. package/dist/components/Tooltip.js +178 -0
  49. package/dist/components/Tooltip.js.map +1 -0
  50. package/dist/index.d.ts +488 -0
  51. package/dist/index.js +827 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/shared-Bj3Gk219.d.ts +43 -0
  54. package/package.json +99 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Legend/index.tsx"],"names":[],"mappings":"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","file":"Legend.js","sourcesContent":["/**\n * <GraphLegend> — §11/§16.7 built-in legend over one scale-valued styling\n * channel (S10).\n *\n * Reads `instance.getScaleInfo(channel)` and re-reads on store model/scope/\n * render revision advances and theme changes. Renders nothing when the\n * channel is not scale-valued. Forms:\n * - sequential color → gradient ramp bar + min/max domain ticks + metric name\n * - diverging color → two-tone bar + min/mid/max ticks\n * - categorical → swatch rows (color chip + value + count); a row click\n * calls `onCategoryClick(value)`, and rows listed in `excludedValues`\n * render dimmed\n * - numeric range (nodeSize scales) → graduated dots (4 steps, value labels)\n *\n * The HOST owns actual filtering: wire `onCategoryClick` to your `filter`\n * prop and feed the same values back through `excludedValues` for the dimmed\n * affordance. Library-owned legend toggling arrives with S12.\n *\n * All data-derived strings (category values) render as TEXT NODES — never\n * markup. Headless-styleable: providing `className` drops the built-in\n * inline styles (theme-token text/border colors included).\n */\n\nimport { useMemo } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport { CATEGORICAL_PALETTE } from '@modernrelay/orbit-core';\nimport type { GraphTheme, ScaleChannelInfo, ScaleInfoRow } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { useResolvedInstance } from '../shared';\nimport { useScaleInfoSubscription } from '../../hooks';\n\nexport interface GraphLegendProps {\n /** Styling channel to describe. Default 'nodeColor'. */\n channel?: 'nodeColor' | 'nodeSize';\n /** Categorical values the host currently filters out — their rows render\n * dimmed (the host owns the actual filter wiring; S12 adds library-owned\n * toggling). */\n excludedValues?: readonly string[];\n /** Fired with the clicked categorical row's value. */\n onCategoryClick?: (value: string) => void;\n /** Explicit instance; defaults to the ambient <Graph>/<GraphProvider> one. */\n instance?: AnyGraphInstance;\n /** Class hook for the root; providing it drops the default styles. */\n className?: string;\n style?: CSSProperties;\n}\n\n/** Graduated-dot step count for numeric-range (size) legends. */\nconst SIZE_LEGEND_STEPS = 4;\n\n// --- default styling (dropped when `className` is provided). Text/border\n// tokens come from the resolved GraphTheme so the legend follows the theme\n// prop; the translucent panel keeps the §16.7 overlay look. ---\n\nfunction rootStyle(theme: GraphTheme): CSSProperties {\n return {\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n padding: 8,\n borderRadius: 8,\n minWidth: 120,\n background: 'rgba(23, 25, 32, 0.85)',\n border: '1px solid rgba(148, 163, 184, 0.25)',\n color: theme.labelFg,\n font: '12px/1.4 system-ui, sans-serif',\n };\n}\n\nconst TITLE_STYLE: CSSProperties = { opacity: 0.75 };\n\nconst RAMP_STYLE: CSSProperties = { height: 10, borderRadius: 5 };\n\nconst TICK_ROW_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'space-between',\n gap: 8,\n opacity: 0.85,\n};\n\nconst CATEGORY_ROW_STYLE: CSSProperties = {\n appearance: 'none',\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n background: 'transparent',\n border: 'none',\n borderRadius: 4,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n padding: '2px 4px',\n textAlign: 'left',\n};\n\nconst EXCLUDED_ROW_STYLE: CSSProperties = {\n ...CATEGORY_ROW_STYLE,\n opacity: 0.45,\n textDecoration: 'line-through',\n};\n\nconst VALUE_STYLE: CSSProperties = { flex: 1, minWidth: 0 };\n\nconst COUNT_STYLE: CSSProperties = { opacity: 0.7 };\n\nconst DOT_ROW_STYLE: CSSProperties = {\n display: 'flex',\n alignItems: 'flex-end',\n gap: 12,\n};\n\nconst DOT_STEP_STYLE: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: 4,\n};\n\n/** Compact tick label: integers verbatim, else 3 significant digits. */\nfunction formatTick(v: number): string {\n if (!Number.isFinite(v)) return '';\n if (Number.isInteger(v)) return String(v);\n return String(Number(v.toPrecision(3)));\n}\n\nfunction dotStyle(size: number, theme: GraphTheme): CSSProperties {\n const px = Math.max(2, Math.abs(size));\n return {\n width: px,\n height: px,\n borderRadius: '50%',\n background: theme.accent,\n };\n}\n\nexport function GraphLegend(props: GraphLegendProps): ReactElement | null {\n const instance = useResolvedInstance(props.instance, '<GraphLegend>');\n const channel = props.channel ?? 'nodeColor';\n const { version, theme } = useScaleInfoSubscription(instance);\n const info: ScaleChannelInfo | null = useMemo(\n () => instance.getScaleInfo(channel),\n // `version` invalidates: a new store revision can change scale info.\n [instance, channel, version],\n );\n if (info === null) return null;\n\n const styled = props.className === undefined;\n const scale = info.scale;\n const title =\n scale.kind === 'categorical'\n ? typeof scale.by === 'string'\n ? scale.by\n : 'category'\n : scale.metric;\n\n let body: ReactNode;\n if (scale.kind === 'categorical') {\n const palette: readonly (string | number)[] = scale.palette ?? CATEGORICAL_PALETTE;\n const excluded = props.excludedValues ?? [];\n const renderRow = (row: ScaleInfoRow): ReactElement => {\n const isExcluded = excluded.includes(row.value);\n const entry = row.colorIndex >= 0 ? palette[row.colorIndex] : undefined;\n return (\n <button\n key={row.value}\n type=\"button\"\n data-orbit-legend-row={row.value}\n aria-pressed={isExcluded}\n style={styled ? (isExcluded ? EXCLUDED_ROW_STYLE : CATEGORY_ROW_STYLE) : undefined}\n onClick={() => {\n props.onCategoryClick?.(row.value);\n }}\n >\n <span\n data-orbit-legend-swatch=\"\"\n aria-hidden=\"true\"\n style={\n typeof entry === 'number'\n ? dotStyle(entry, theme)\n : {\n width: 10,\n height: 10,\n borderRadius: 2,\n background: entry ?? 'transparent',\n flex: 'none',\n }\n }\n />\n <span data-orbit-legend-value=\"\" style={styled ? VALUE_STYLE : undefined}>\n {row.value}\n </span>\n <span data-orbit-legend-count=\"\" style={styled ? COUNT_STYLE : undefined}>\n {row.count}\n </span>\n </button>\n );\n };\n body = (\n <div\n data-orbit-legend-rows=\"\"\n style={styled ? { display: 'flex', flexDirection: 'column' } : undefined}\n >\n {(info.rows ?? []).map(renderRow)}\n </div>\n );\n } else {\n // sequential | diverging — tuple stops are colors (string) or sizes\n // (number); the domain resolves through the same frozen DomainStore\n // coordinate the projection uses, and may be absent (§12 pending metric).\n const stops = scale.range as readonly (string | number)[];\n const domain = info.domain;\n if (typeof stops[0] === 'number') {\n // Size legend: graduated dots, small→large, with value labels.\n const lo = stops[0];\n const hi = stops[stops.length - 1] as number;\n body = (\n <div data-orbit-legend-dots=\"\" style={styled ? DOT_ROW_STYLE : undefined}>\n {Array.from({ length: SIZE_LEGEND_STEPS }, (_, i) => {\n const t = i / (SIZE_LEGEND_STEPS - 1);\n const size = lo + (hi - lo) * t;\n const label =\n domain !== undefined ? formatTick(domain[0] + (domain[1] - domain[0]) * t) : '';\n return (\n <span key={i} data-orbit-legend-dot-step=\"\" style={styled ? DOT_STEP_STYLE : undefined}>\n <span data-orbit-legend-dot=\"\" aria-hidden=\"true\" style={dotStyle(size, theme)} />\n <span data-orbit-legend-tick=\"\">{label}</span>\n </span>\n );\n })}\n </div>\n );\n } else {\n // Color legend: two-stop ramp (sequential) or three-stop bar with a\n // mid tick (diverging). Stops are host-provided CSS colors.\n const gradient = `linear-gradient(to right, ${stops.join(', ')})`;\n const ticks: string[] =\n domain === undefined\n ? [] // §12 pending metric: no resolvable domain, no ticks\n : scale.kind === 'diverging'\n ? [formatTick(domain[0]), formatTick(scale.mid), formatTick(domain[1])]\n : [formatTick(domain[0]), formatTick(domain[1])];\n body = (\n <div style={styled ? { display: 'flex', flexDirection: 'column', gap: 4 } : undefined}>\n <div\n data-orbit-legend-ramp={scale.kind}\n style={styled ? { ...RAMP_STYLE, background: gradient } : { background: gradient }}\n />\n {ticks.length > 0 ? (\n <div data-orbit-legend-ticks=\"\" style={styled ? TICK_ROW_STYLE : undefined}>\n {ticks.map((tick, i) => (\n <span key={i} data-orbit-legend-tick=\"\">\n {tick}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n );\n }\n }\n\n return (\n <div\n data-orbit-legend={channel}\n role=\"group\"\n aria-label={`${title} legend`}\n className={props.className}\n style={styled ? { ...rootStyle(theme), ...props.style } : props.style}\n >\n <div data-orbit-legend-title=\"\" style={styled ? TITLE_STYLE : undefined}>\n {title}\n </div>\n {body}\n </div>\n );\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import { CSSProperties, ReactElement } from 'react';
2
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
3
+ import { G as GraphCorner } from '../shared-Bj3Gk219.js';
4
+ import '@modernrelay/orbit-core';
5
+
6
+ /**
7
+ * `@modernrelay/orbit-react/components/Minimap` — `<GraphMinimap>`, the §16.8
8
+ * whole-graph thumbnail + viewport rectangle (S11-T11).
9
+ *
10
+ * v0.9 is CPU-fallback-only (no engine exposes `capabilities.overviewPass` —
11
+ * cosmos M0 matrix): the component owns an {@link OverviewController} and
12
+ * blits its `rasterize()` output into a small canvas via `putImageData`. The
13
+ * three §16.8 lanes stay strictly decoupled:
14
+ *
15
+ * 1. **Thumbnail refresh** — a 500 ms `setInterval` probes the controller's
16
+ * `shouldRefresh(now, simulationRunning, epoch)` gate, so the effective
17
+ * cadence is the controller's spec budget (≤ 2 Hz hot, ≤ 1 Hz idle, zero
18
+ * work idle-with-unchanged-epoch) and each `true` is followed by exactly
19
+ * one `rasterize()` + blit. The interval — not a per-frame engine lane —
20
+ * is the documented v0.9 cadence: the S7 `onFrame` fan-out only reaches
21
+ * React through the label lane, which is gated on labels being enabled,
22
+ * so a plain timer is the correct always-on ≤ 2 Hz driver. The epoch is
23
+ * `revisions.render` (a positions-bearing superset — over-approximate but
24
+ * bounded by the same throttle).
25
+ * 2. **Viewport rectangle** — an imperative store subscription writes the
26
+ * rect's transform/size O(1) per `store.viewport` publication (CSS
27
+ * transform on a ref; no React re-render, no re-rasterize).
28
+ * 3. **Pan** — pointer down/drag maps minimap px → world via the
29
+ * controller's `minimapToWorld` (exact inverse over the last rasterized
30
+ * frame) and calls `instance.setViewport({x, y})` — real §13 camera pans.
31
+ *
32
+ * Scene source (documented v0.9 trim): the instance does not yet expose the
33
+ * §7.1 posBuf mirror (that read lands with the S13 columnar lane), so the
34
+ * component probes a pinned structural seam `getOverviewScene()` first
35
+ * (S7-precedent optional access — picked up automatically once core lands
36
+ * it) and falls back to the DECLARED node positions (`GraphNode.x/y`) of the
37
+ * current scene roster — exact for prepared/fixed-position datasets (the S11
38
+ * orbit-data lane), empty for force layouts until S13. Position-less nodes
39
+ * rasterize as NaN tombstones (skipped); §9.1 mask-hidden nodes draw dimmed
40
+ * via `getVisible`.
41
+ */
42
+
43
+ interface GraphMinimapProps {
44
+ /** Explicit instance (multi-instance pages); ambient context otherwise. */
45
+ instance?: AnyGraphInstance;
46
+ /** Square thumbnail edge in CSS px (also the raster resolution). Default 200. */
47
+ size?: number;
48
+ /** Accessible name. Default 'Graph minimap'. */
49
+ label?: string;
50
+ /** Corner this panel anchors to. Default 'bottom-right'. */
51
+ position?: GraphCorner;
52
+ /** Distance from the two anchored edges in CSS px. Default 12. */
53
+ offset?: number;
54
+ /** Class hook for the root; providing it drops the default styles
55
+ * INCLUDING placement — position it yourself in CSS. */
56
+ className?: string;
57
+ /** Style overrides merged over the defaults; an inset you set here
58
+ * wins over the one `position` implies (resolved per axis). */
59
+ style?: CSSProperties;
60
+ }
61
+ declare function GraphMinimap(props: GraphMinimapProps): ReactElement;
62
+
63
+ export { GraphMinimap, type GraphMinimapProps };
@@ -0,0 +1,222 @@
1
+ import { useResolvedInstance, mergeStyle, cornerStyle } from '../chunk-7XB47YST.js';
2
+ import { useRef, useMemo, useEffect } from 'react';
3
+ import { OverviewController, OVERVIEW_HOT_INTERVAL_MS } from '@modernrelay/orbit-core';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var SIZE_DEFAULT = 200;
7
+ var MIN_RECT_PX = 6;
8
+ var ROOT_STYLE = {
9
+ pointerEvents: "auto",
10
+ overflow: "hidden",
11
+ borderRadius: 8,
12
+ background: "rgba(23, 25, 32, 0.85)",
13
+ border: "1px solid rgba(255, 255, 255, 0.14)"
14
+ };
15
+ var CANVAS_STYLE = {
16
+ display: "block",
17
+ cursor: "crosshair",
18
+ touchAction: "none"
19
+ };
20
+ var RECT_STYLE = {
21
+ position: "absolute",
22
+ left: 0,
23
+ top: 0,
24
+ display: "none",
25
+ border: "1px solid rgba(255, 255, 255, 0.85)",
26
+ background: "rgba(255, 255, 255, 0.12)",
27
+ pointerEvents: "none",
28
+ willChange: "transform",
29
+ boxSizing: "border-box"
30
+ };
31
+ function GraphMinimap(props) {
32
+ const instance = useResolvedInstance(props.instance, "<GraphMinimap>");
33
+ const size = props.size ?? SIZE_DEFAULT;
34
+ const rootRef = useRef(null);
35
+ const canvasRef = useRef(null);
36
+ const rectRef = useRef(null);
37
+ const draggingRef = useRef(false);
38
+ const declaredSceneRef = useRef({ ids: [], scene: null });
39
+ const visibleRef = useRef({
40
+ ids: [],
41
+ set: null
42
+ });
43
+ const controller = useMemo(() => {
44
+ const getScene = () => {
45
+ const mirrored = instance.getOverviewScene?.();
46
+ if (mirrored !== void 0) return mirrored;
47
+ const ids = instance.getSceneNodeIds();
48
+ if (ids.length === 0) return null;
49
+ const cache = declaredSceneRef.current;
50
+ if (cache.ids === ids) return cache.scene;
51
+ const positions = new Float32Array(2 * ids.length);
52
+ let known = 0;
53
+ for (let i = 0; i < ids.length; i++) {
54
+ const node = instance.getNode(ids[i]);
55
+ const x = node?.x;
56
+ const y = node?.y;
57
+ if (typeof x === "number" && typeof y === "number") {
58
+ positions[2 * i] = x;
59
+ positions[2 * i + 1] = y;
60
+ known++;
61
+ } else {
62
+ positions[2 * i] = NaN;
63
+ positions[2 * i + 1] = NaN;
64
+ }
65
+ }
66
+ const scene = known > 0 ? { positions, count: ids.length } : null;
67
+ declaredSceneRef.current = { ids, scene };
68
+ return scene;
69
+ };
70
+ const getVisible = (index) => {
71
+ const v = visibleRef.current;
72
+ if (v.set === null) return true;
73
+ const id = v.ids[index];
74
+ return id === void 0 ? true : v.set.has(id);
75
+ };
76
+ return new OverviewController({ getScene, getVisible, size });
77
+ }, [instance, size]);
78
+ const updateRect = (v) => {
79
+ const rect = rectRef.current;
80
+ if (rect === null) return;
81
+ if (v === null || v.zoom <= 0) {
82
+ rect.style.display = "none";
83
+ return;
84
+ }
85
+ const host = rootRef.current?.offsetParent;
86
+ const hostW = host instanceof HTMLElement ? host.clientWidth : 0;
87
+ const hostH = host instanceof HTMLElement ? host.clientHeight : 0;
88
+ const halfW = hostW > 0 ? hostW / v.zoom / 2 : 0;
89
+ const halfH = hostH > 0 ? hostH / v.zoom / 2 : 0;
90
+ const a = controller.worldToMinimap(v.x - halfW, v.y + halfH);
91
+ const b = controller.worldToMinimap(v.x + halfW, v.y - halfH);
92
+ if (a === null || b === null) {
93
+ rect.style.display = "none";
94
+ return;
95
+ }
96
+ const w = Math.max(Math.abs(b[0] - a[0]), MIN_RECT_PX);
97
+ const h = Math.max(Math.abs(b[1] - a[1]), MIN_RECT_PX);
98
+ const x = (a[0] + b[0]) / 2 - w / 2;
99
+ const y = (a[1] + b[1]) / 2 - h / 2;
100
+ rect.style.display = "block";
101
+ rect.style.width = `${w}px`;
102
+ rect.style.height = `${h}px`;
103
+ rect.style.transform = `translate3d(${x}px, ${y}px, 0)`;
104
+ };
105
+ useEffect(() => {
106
+ const blit = () => {
107
+ const state = instance.store.getState();
108
+ if (state.visible.nodes !== state.nodeCount || state.hiddenNodeIds.size > 0) {
109
+ const ids = instance.getSceneNodeIds();
110
+ visibleRef.current = { ids, set: new Set(instance.getVisibleNodeIds()) };
111
+ } else {
112
+ visibleRef.current = { ids: [], set: null };
113
+ }
114
+ const raster = controller.rasterize();
115
+ const canvas = canvasRef.current;
116
+ if (canvas === null) return;
117
+ const ctx = canvas.getContext("2d");
118
+ if (ctx === null) return;
119
+ if (raster === null) {
120
+ ctx.clearRect(0, 0, size, size);
121
+ updateRect(null);
122
+ return;
123
+ }
124
+ if (typeof ImageData === "undefined") return;
125
+ const pixels = new Uint8ClampedArray(raster.bitmap);
126
+ ctx.putImageData(new ImageData(pixels, controller.size, controller.size), 0, 0);
127
+ updateRect(instance.store.getState().viewport);
128
+ };
129
+ const tick = () => {
130
+ const state = instance.store.getState();
131
+ if (!controller.shouldRefresh(Date.now(), state.simulationRunning, state.revisions.render)) {
132
+ return;
133
+ }
134
+ blit();
135
+ };
136
+ tick();
137
+ const timer = setInterval(tick, OVERVIEW_HOT_INTERVAL_MS);
138
+ return () => {
139
+ clearInterval(timer);
140
+ };
141
+ }, [instance, controller, size]);
142
+ useEffect(() => {
143
+ let prev = instance.store.getState().viewport;
144
+ updateRect(prev);
145
+ return instance.store.subscribe(() => {
146
+ const next = instance.store.getState().viewport;
147
+ if (next === prev) return;
148
+ prev = next;
149
+ updateRect(next);
150
+ });
151
+ }, [instance, controller]);
152
+ const panTo = (e) => {
153
+ const canvas = canvasRef.current;
154
+ if (canvas === null) return;
155
+ const b = canvas.getBoundingClientRect();
156
+ const sx = b.width > 0 ? controller.size / b.width : 1;
157
+ const sy = b.height > 0 ? controller.size / b.height : 1;
158
+ const world = controller.minimapToWorld((e.clientX - b.left) * sx, (e.clientY - b.top) * sy);
159
+ if (world === null) return;
160
+ instance.setViewport({ x: world[0], y: world[1] });
161
+ };
162
+ const onPointerDown = (e) => {
163
+ e.preventDefault();
164
+ draggingRef.current = true;
165
+ try {
166
+ e.currentTarget.setPointerCapture(e.pointerId);
167
+ } catch {
168
+ }
169
+ panTo(e);
170
+ };
171
+ const onPointerMove = (e) => {
172
+ if (!draggingRef.current) return;
173
+ panTo(e);
174
+ };
175
+ const endDrag = () => {
176
+ draggingRef.current = false;
177
+ };
178
+ return /* @__PURE__ */ jsxs(
179
+ "div",
180
+ {
181
+ ref: rootRef,
182
+ "data-orbit-minimap": "",
183
+ role: "group",
184
+ "aria-label": props.label ?? "Graph minimap",
185
+ className: props.className,
186
+ style: props.className !== void 0 ? (
187
+ // The thumbnail is MECHANICAL, not decorative: dropping its box
188
+ // would collapse the canvas, so size survives the class hook.
189
+ { width: size, height: size, ...props.style }
190
+ ) : mergeStyle(
191
+ {
192
+ ...cornerStyle(props.position ?? "bottom-right", props.offset),
193
+ ...ROOT_STYLE,
194
+ width: size,
195
+ height: size
196
+ },
197
+ props.style
198
+ ),
199
+ children: [
200
+ /* @__PURE__ */ jsx(
201
+ "canvas",
202
+ {
203
+ ref: canvasRef,
204
+ "data-orbit-minimap-canvas": "",
205
+ width: size,
206
+ height: size,
207
+ style: CANVAS_STYLE,
208
+ onPointerDown,
209
+ onPointerMove,
210
+ onPointerUp: endDrag,
211
+ onPointerLeave: endDrag
212
+ }
213
+ ),
214
+ /* @__PURE__ */ jsx("div", { ref: rectRef, "data-orbit-minimap-viewport": "", style: RECT_STYLE })
215
+ ]
216
+ }
217
+ );
218
+ }
219
+
220
+ export { GraphMinimap };
221
+ //# sourceMappingURL=Minimap.js.map
222
+ //# sourceMappingURL=Minimap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Minimap/index.tsx"],"names":[],"mappings":";;;;;AA+CA,IAAM,YAAA,GAAe,GAAA;AAIrB,IAAM,WAAA,GAAc,CAAA;AA4BpB,IAAM,UAAA,GAA4B;AAAA,EAChC,aAAA,EAAe,MAAA;AAAA,EACf,QAAA,EAAU,QAAA;AAAA,EACV,YAAA,EAAc,CAAA;AAAA,EACd,UAAA,EAAY,wBAAA;AAAA,EACZ,MAAA,EAAQ;AACV,CAAA;AAEA,IAAM,YAAA,GAA8B;AAAA,EAClC,OAAA,EAAS,OAAA;AAAA,EACT,MAAA,EAAQ,WAAA;AAAA,EACR,WAAA,EAAa;AACf,CAAA;AAEA,IAAM,UAAA,GAA4B;AAAA,EAChC,QAAA,EAAU,UAAA;AAAA,EACV,IAAA,EAAM,CAAA;AAAA,EACN,GAAA,EAAK,CAAA;AAAA,EACL,OAAA,EAAS,MAAA;AAAA,EACT,MAAA,EAAQ,qCAAA;AAAA,EACR,UAAA,EAAY,2BAAA;AAAA,EACZ,aAAA,EAAe,MAAA;AAAA,EACf,UAAA,EAAY,WAAA;AAAA,EACZ,SAAA,EAAW;AACb,CAAA;AASO,SAAS,aAAa,KAAA,EAAwC;AACnE,EAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,KAAA,CAAM,QAAA,EAAU,gBAAgB,CAAA;AACrE,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,IAAQ,YAAA;AAE3B,EAAA,MAAM,OAAA,GAAU,OAA8B,IAAI,CAAA;AAClD,EAAA,MAAM,SAAA,GAAY,OAAiC,IAAI,CAAA;AACvD,EAAA,MAAM,OAAA,GAAU,OAA8B,IAAI,CAAA;AAClD,EAAA,MAAM,WAAA,GAAc,OAAO,KAAK,CAAA;AAChC,EAAA,MAAM,gBAAA,GAAmB,OAA2B,EAAE,GAAA,EAAK,EAAC,EAAG,KAAA,EAAO,MAAM,CAAA;AAE5E,EAAA,MAAM,aAAa,MAAA,CAAoE;AAAA,IACrF,KAAK,EAAC;AAAA,IACN,GAAA,EAAK;AAAA,GACN,CAAA;AAED,EAAA,MAAM,UAAA,GAAa,QAAQ,MAAM;AAC/B,IAAA,MAAM,WAAW,MAA4B;AAE3C,MAAA,MAAM,QAAA,GAAY,SAA6C,gBAAA,IAAmB;AAClF,MAAA,IAAI,QAAA,KAAa,QAAW,OAAO,QAAA;AAEnC,MAAA,MAAM,GAAA,GAAM,SAAS,eAAA,EAAgB;AACrC,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAC7B,MAAA,MAAM,QAAQ,gBAAA,CAAiB,OAAA;AAC/B,MAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,GAAA,EAAK,OAAO,KAAA,CAAM,KAAA;AACpC,MAAA,MAAM,SAAA,GAAY,IAAI,YAAA,CAAa,CAAA,GAAI,IAAI,MAAM,CAAA;AACjD,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,QAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAE,CAAA;AACrC,QAAA,MAAM,IAAI,IAAA,EAAM,CAAA;AAChB,QAAA,MAAM,IAAI,IAAA,EAAM,CAAA;AAChB,QAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,MAAM,QAAA,EAAU;AAClD,UAAA,SAAA,CAAU,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA;AACnB,UAAA,SAAA,CAAU,CAAA,GAAI,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA;AACvB,UAAA,KAAA,EAAA;AAAA,QACF,CAAA,MAAO;AACL,UAAA,SAAA,CAAU,CAAA,GAAI,CAAC,CAAA,GAAI,GAAA;AACnB,UAAA,SAAA,CAAU,CAAA,GAAI,CAAA,GAAI,CAAC,CAAA,GAAI,GAAA;AAAA,QACzB;AAAA,MACF;AACA,MAAA,MAAM,KAAA,GAA8B,QAAQ,CAAA,GAAI,EAAE,WAAW,KAAA,EAAO,GAAA,CAAI,QAAO,GAAI,IAAA;AACnF,MAAA,gBAAA,CAAiB,OAAA,GAAU,EAAE,GAAA,EAAK,KAAA,EAAM;AACxC,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AACA,IAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAA2B;AAC7C,MAAA,MAAM,IAAI,UAAA,CAAW,OAAA;AACrB,MAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,IAAA,EAAM,OAAO,IAAA;AAC3B,MAAA,MAAM,EAAA,GAAK,CAAA,CAAE,GAAA,CAAI,KAAK,CAAA;AACtB,MAAA,OAAO,OAAO,MAAA,GAAY,IAAA,GAAO,CAAA,CAAE,GAAA,CAAI,IAAI,EAAE,CAAA;AAAA,IAC/C,CAAA;AACA,IAAA,OAAO,IAAI,kBAAA,CAAmB,EAAE,QAAA,EAAU,UAAA,EAAY,MAAM,CAAA;AAAA,EAC9D,CAAA,EAAG,CAAC,QAAA,EAAU,IAAI,CAAC,CAAA;AAOnB,EAAA,MAAM,UAAA,GAAa,CAAC,CAAA,KAAkC;AACpD,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,IAAI,CAAA,KAAM,IAAA,IAAQ,CAAA,CAAE,IAAA,IAAQ,CAAA,EAAG;AAC7B,MAAA,IAAA,CAAK,MAAM,OAAA,GAAU,MAAA;AACrB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,GAAO,QAAQ,OAAA,EAAS,YAAA;AAC9B,IAAA,MAAM,KAAA,GAAQ,IAAA,YAAgB,WAAA,GAAc,IAAA,CAAK,WAAA,GAAc,CAAA;AAC/D,IAAA,MAAM,KAAA,GAAQ,IAAA,YAAgB,WAAA,GAAc,IAAA,CAAK,YAAA,GAAe,CAAA;AAChE,IAAA,MAAM,QAAQ,KAAA,GAAQ,CAAA,GAAI,KAAA,GAAQ,CAAA,CAAE,OAAO,CAAA,GAAI,CAAA;AAC/C,IAAA,MAAM,QAAQ,KAAA,GAAQ,CAAA,GAAI,KAAA,GAAQ,CAAA,CAAE,OAAO,CAAA,GAAI,CAAA;AAE/C,IAAA,MAAM,CAAA,GAAI,WAAW,cAAA,CAAe,CAAA,CAAE,IAAI,KAAA,EAAO,CAAA,CAAE,IAAI,KAAK,CAAA;AAC5D,IAAA,MAAM,CAAA,GAAI,WAAW,cAAA,CAAe,CAAA,CAAE,IAAI,KAAA,EAAO,CAAA,CAAE,IAAI,KAAK,CAAA;AAC5D,IAAA,IAAI,CAAA,KAAM,IAAA,IAAQ,CAAA,KAAM,IAAA,EAAM;AAC5B,MAAA,IAAA,CAAK,MAAM,OAAA,GAAU,MAAA;AACrB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,WAAW,CAAA;AACrD,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,WAAW,CAAA;AACrD,IAAA,MAAM,CAAA,GAAA,CAAK,EAAE,CAAC,CAAA,GAAI,EAAE,CAAC,CAAA,IAAK,IAAI,CAAA,GAAI,CAAA;AAClC,IAAA,MAAM,CAAA,GAAA,CAAK,EAAE,CAAC,CAAA,GAAI,EAAE,CAAC,CAAA,IAAK,IAAI,CAAA,GAAI,CAAA;AAClC,IAAA,IAAA,CAAK,MAAM,OAAA,GAAU,OAAA;AACrB,IAAA,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AACvB,IAAA,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,CAAM,SAAA,GAAY,CAAA,YAAA,EAAe,CAAC,OAAO,CAAC,CAAA,MAAA,CAAA;AAAA,EACjD,CAAA;AAGA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,OAAO,MAAY;AACvB,MAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS;AAEtC,MAAA,IAAI,KAAA,CAAM,QAAQ,KAAA,KAAU,KAAA,CAAM,aAAa,KAAA,CAAM,aAAA,CAAc,OAAO,CAAA,EAAG;AAC3E,QAAA,MAAM,GAAA,GAAM,SAAS,eAAA,EAAgB;AACrC,QAAA,UAAA,CAAW,OAAA,GAAU,EAAE,GAAA,EAAK,GAAA,EAAK,IAAI,GAAA,CAAI,QAAA,CAAS,iBAAA,EAAmB,CAAA,EAAE;AAAA,MACzE,CAAA,MAAO;AACL,QAAA,UAAA,CAAW,UAAU,EAAE,GAAA,EAAK,EAAC,EAAG,KAAK,IAAA,EAAK;AAAA,MAC5C;AACA,MAAA,MAAM,MAAA,GAAS,WAAW,SAAA,EAAU;AACpC,MAAA,MAAM,SAAS,SAAA,CAAU,OAAA;AACzB,MAAA,IAAI,WAAW,IAAA,EAAM;AACrB,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,MAAA,IAAI,QAAQ,IAAA,EAAM;AAClB,MAAA,IAAI,WAAW,IAAA,EAAM;AACnB,QAAA,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAA,EAAG,IAAA,EAAM,IAAI,CAAA;AAC9B,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA;AAAA,MACF;AACA,MAAA,IAAI,OAAO,cAAc,WAAA,EAAa;AAEtC,MAAA,MAAM,MAAA,GAAS,IAAI,iBAAA,CAAkB,MAAA,CAAO,MAAM,CAAA;AAClD,MAAA,GAAA,CAAI,YAAA,CAAa,IAAI,SAAA,CAAU,MAAA,EAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,IAAI,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAE9E,MAAA,UAAA,CAAW,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS,CAAE,QAAQ,CAAA;AAAA,IAC/C,CAAA;AACA,IAAA,MAAM,OAAO,MAAY;AACvB,MAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS;AAGtC,MAAA,IAAI,CAAC,UAAA,CAAW,aAAA,CAAc,IAAA,CAAK,GAAA,EAAI,EAAG,KAAA,CAAM,iBAAA,EAAmB,KAAA,CAAM,SAAA,CAAU,MAAM,CAAA,EAAG;AAC1F,QAAA;AAAA,MACF;AACA,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AACA,IAAA,IAAA,EAAK;AACL,IAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,EAAM,wBAAwB,CAAA;AACxD,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,IACrB,CAAA;AAAA,EAIF,CAAA,EAAG,CAAC,QAAA,EAAU,UAAA,EAAY,IAAI,CAAC,CAAA;AAI/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAA,GAAO,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS,CAAE,QAAA;AACrC,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,OAAO,QAAA,CAAS,KAAA,CAAM,SAAA,CAAU,MAAM;AACpC,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS,CAAE,QAAA;AACvC,MAAA,IAAI,SAAS,IAAA,EAAM;AACnB,MAAA,IAAA,GAAO,IAAA;AACP,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA,IACjB,CAAC,CAAA;AAAA,EAEH,CAAA,EAAG,CAAC,QAAA,EAAU,UAAU,CAAC,CAAA;AAGzB,EAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAkD;AAC/D,IAAA,MAAM,SAAS,SAAA,CAAU,OAAA;AACzB,IAAA,IAAI,WAAW,IAAA,EAAM;AACrB,IAAA,MAAM,CAAA,GAAI,OAAO,qBAAA,EAAsB;AAEvC,IAAA,MAAM,KAAK,CAAA,CAAE,KAAA,GAAQ,IAAI,UAAA,CAAW,IAAA,GAAO,EAAE,KAAA,GAAQ,CAAA;AACrD,IAAA,MAAM,KAAK,CAAA,CAAE,MAAA,GAAS,IAAI,UAAA,CAAW,IAAA,GAAO,EAAE,MAAA,GAAS,CAAA;AACvD,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,cAAA,CAAA,CAAgB,CAAA,CAAE,OAAA,GAAU,CAAA,CAAE,IAAA,IAAQ,EAAA,EAAA,CAAK,CAAA,CAAE,OAAA,GAAU,CAAA,CAAE,GAAA,IAAO,EAAE,CAAA;AAC3F,IAAA,IAAI,UAAU,IAAA,EAAM;AACpB,IAAA,QAAA,CAAS,WAAA,CAAY,EAAE,CAAA,EAAG,KAAA,CAAM,CAAC,GAAG,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA;AAAA,EACnD,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB,CAAC,CAAA,KAAkD;AACvE,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,IAAA,IAAI;AACF,MAAA,CAAA,CAAE,aAAA,CAAc,iBAAA,CAAkB,CAAA,CAAE,SAAS,CAAA;AAAA,IAC/C,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,KAAA,CAAM,CAAC,CAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB,CAAC,CAAA,KAAkD;AACvE,IAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AAC1B,IAAA,KAAA,CAAM,CAAC,CAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,UAAU,MAAY;AAC1B,IAAA,WAAA,CAAY,OAAA,GAAU,KAAA;AAAA,EACxB,CAAA;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,oBAAA,EAAmB,EAAA;AAAA,MACnB,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAY,MAAM,KAAA,IAAS,eAAA;AAAA,MAC3B,WAAW,KAAA,CAAM,SAAA;AAAA,MACjB,KAAA,EACE,MAAM,SAAA,KAAc,MAAA;AAAA;AAAA;AAAA,QAGhB,EAAE,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAM,GAAG,MAAM,KAAA;AAAM,UAC5C,UAAA;AAAA,QACE;AAAA,UACE,GAAG,WAAA,CAAY,KAAA,CAAM,QAAA,IAAY,cAAA,EAAgB,MAAM,MAAM,CAAA;AAAA,UAC7D,GAAG,UAAA;AAAA,UACH,KAAA,EAAO,IAAA;AAAA,UACP,MAAA,EAAQ;AAAA,SACV;AAAA,QACA,KAAA,CAAM;AAAA,OACR;AAAA,MAGN,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,SAAA;AAAA,YACL,2BAAA,EAA0B,EAAA;AAAA,YAC1B,KAAA,EAAO,IAAA;AAAA,YACP,MAAA,EAAQ,IAAA;AAAA,YACR,KAAA,EAAO,YAAA;AAAA,YACP,aAAA;AAAA,YACA,aAAA;AAAA,YACA,WAAA,EAAa,OAAA;AAAA,YACb,cAAA,EAAgB;AAAA;AAAA,SAClB;AAAA,4BACC,KAAA,EAAA,EAAI,GAAA,EAAK,SAAS,6BAAA,EAA4B,EAAA,EAAG,OAAO,UAAA,EAAY;AAAA;AAAA;AAAA,GACvE;AAEJ","file":"Minimap.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Minimap` — `<GraphMinimap>`, the §16.8\n * whole-graph thumbnail + viewport rectangle (S11-T11).\n *\n * v0.9 is CPU-fallback-only (no engine exposes `capabilities.overviewPass` —\n * cosmos M0 matrix): the component owns an {@link OverviewController} and\n * blits its `rasterize()` output into a small canvas via `putImageData`. The\n * three §16.8 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 S7 `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 §13 camera pans.\n *\n * Scene source (documented v0.9 trim): the instance does not yet expose the\n * §7.1 posBuf mirror (that read lands with the S13 columnar lane), so the\n * component probes a pinned structural seam `getOverviewScene()` first\n * (S7-precedent 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 (the S11\n * orbit-data lane), empty for force layouts until S13. Position-less nodes\n * rasterize as NaN tombstones (skipped); §9.1 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 §16.8 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 S13 structural seam (S7 shared.ts precedent): the instance-side\n * §7.1 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 /** §9.1 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 (S13): the §7.1 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 (§13); 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 // §9.1 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 per (instance, controller) — 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"]}
@@ -0,0 +1,90 @@
1
+ import { CSSProperties, ReactElement } from 'react';
2
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
3
+ import '@modernrelay/orbit-core';
4
+
5
+ /**
6
+ * `@modernrelay/orbit-react/components/Navigator` — `<GraphNavigator>`, the
7
+ * bounded semantic navigator (spec §15.1; S7-T11/T19).
8
+ *
9
+ * The navigator is the keyboard/screen-reader equivalent surface for the
10
+ * canvas: a visually compact panel with `role="listbox"` semantics driven by
11
+ * a roving tabindex — exactly one item is tabbable at any time, and
12
+ * `aria-activedescendant` on the listbox container mirrors the active item's
13
+ * stable DOM id. It is bounded by design: every list is paged by
14
+ * `accessibility.navigatorWindow` (default 50), so the DOM never grows one
15
+ * row per entity.
16
+ *
17
+ * Sections:
18
+ * - **Search results** (S11-T15) — the last completed §16.5 search
19
+ * (`store.search`), shown whenever the slice is non-null (it clears on
20
+ * `clearSearch()` / dataset swap). Paged like every other list; Enter
21
+ * routes through `instance.activateSearchResult` (§16.5 result contract:
22
+ * an in-scene, mask-visible id focuses; anything else is classification
23
+ * only — the navigator never mutates scope/filters). Row text prefers the
24
+ * service-supplied `label`.
25
+ * - **Focused node** — the transiently focused root plus its paged 1-hop
26
+ * neighborhood.
27
+ * - **Selection** — the current node selection, paged.
28
+ * - **All nodes** — the entry list (first `navigatorWindow` nodes in
29
+ * accepted-base order, paged over the full set), shown when nothing is
30
+ * focused.
31
+ *
32
+ * ## Keyboard model — `keyboardMode: 'topology'` (the v0.4 default and only mode)
33
+ *
34
+ * | Key | Action |
35
+ * |---------------------|-------------------------------------------------------------------|
36
+ * | ArrowDown / ArrowUp | move the active item within the current list |
37
+ * | Home / End | move to the bounds of the current list |
38
+ * | PageDown / PageUp | next / previous page of the active item's list |
39
+ * | Enter | focus the node (`instance.focusNode` — camera fly) and re-root the neighborhood at it |
40
+ * | Space | toggle the node's selection membership |
41
+ * | Escape | clear the transient focus (returns to the entry list) |
42
+ *
43
+ * `keyboardMode: 'spatial'` (arrows move by screen direction) is a
44
+ * documented post-v0.4 mode and intentionally NOT part of the accepted prop
45
+ * union yet — only `'topology'` type-checks in v0.4.
46
+ *
47
+ * Text names resolve via `accessibility.getAccessibleLabel`, falling back to
48
+ * `attrs.label` (when a string) and then the node id — always rendered as
49
+ * TEXT NODES (§14 untrusted-content rule; markup in labels stays literal).
50
+ * Each item exposes selection state (`aria-selected`), its neighbor count
51
+ * when known, and pinned/hidden state in text (e.g. `hub · 5 neighbors ·
52
+ * pinned`).
53
+ *
54
+ * Neighborhood semantics (v0.4): direction is UNDIRECTED — `focusNode`
55
+ * resolves the 1-hop neighbor ids through the engine's adjacency when the
56
+ * adapter provides one, else through the core CSR adjacency
57
+ * (`buildAdjacency`/`neighborsOf`) built lazily per accepted model revision
58
+ * and invalidated on model change; self-loops are excluded and parallel
59
+ * edges deduplicate. The navigator caches the resolved neighborhood and the
60
+ * per-node neighbor counts it has learned in refs keyed by
61
+ * `revisions.model`; a model change drops the (now stale) transient root and
62
+ * returns to the entry list. Neighbor counts therefore appear on items whose
63
+ * neighborhood has been resolved (the current and previously visited roots);
64
+ * a public O(1) degree read is a post-v0.4 core surface.
65
+ *
66
+ * Focus never lives only in WebGL pixels (§15.1): the active item carries a
67
+ * visible DOM focus outline and receives real DOM focus on keyboard moves.
68
+ * Sync is navigator → graph (Enter → `focusNode` camera fly); graph →
69
+ * navigator focus sync is deferred past v0.4. Space routes through
70
+ * `instance.selectNodes`, so a controlled selection surfaces intent only
71
+ * (§6.4) — `aria-selected` always reflects the store.
72
+ */
73
+
74
+ interface GraphNavigatorProps {
75
+ /** Explicit instance (multi-instance pages); ambient context otherwise. */
76
+ instance?: AnyGraphInstance;
77
+ /**
78
+ * Keyboard model. v0.4 ships `'topology'` (the default; documented above);
79
+ * `'spatial'` (arrows-by-screen-direction) is post-v0.4 and not accepted
80
+ * by this union yet.
81
+ */
82
+ keyboardMode?: 'topology';
83
+ /** Accessible name of the navigator panel. Default 'Graph navigator'. */
84
+ label?: string;
85
+ className?: string;
86
+ style?: CSSProperties;
87
+ }
88
+ declare function GraphNavigator(props: GraphNavigatorProps): ReactElement;
89
+
90
+ export { GraphNavigator, type GraphNavigatorProps };