@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
package/dist/index.js ADDED
@@ -0,0 +1,827 @@
1
+ import { setSearchResultUnavailableCallback, useScaleInfoSubscription } from './chunk-QJ7JXQVK.js';
2
+ export { useGraphCrossfilter, useGraphDiagnostics, useGraphEdgeHover, useGraphHistory, useGraphHover, useGraphInstance, useGraphOverlays, useGraphPendingExpansions, useGraphPins, useGraphScope, useGraphSearch, useGraphSelection, useGraphSimulationRunning, useGraphStatus, useGraphTheme, useGraphTimeline, useGraphViewport, useGraphVisible } from './chunk-QJ7JXQVK.js';
3
+ import './chunk-JN7QCMFW.js';
4
+ import { overlayLayerStyle, overlaySurface, GraphProvider, visuallyHiddenStyle, useAmbientGraphInstance } from './chunk-7XB47YST.js';
5
+ export { DEFAULT_CORNER_OFFSET, GraphProvider, useResolvedInstance } from './chunk-7XB47YST.js';
6
+ import { forwardRef, useState, useRef, useLayoutEffect, useEffect, useImperativeHandle, useId, useCallback, useMemo } from 'react';
7
+ import { createGraphInstance, canonicalJson, sameGroupSpecArrays, canonicalScaleKey } from '@modernrelay/orbit-core';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
9
+
10
+ var OVERLAY_BASE = {
11
+ position: "absolute",
12
+ inset: 0,
13
+ touchAction: "none",
14
+ userSelect: "none"
15
+ };
16
+ var OVERLAY_INACTIVE = { ...OVERLAY_BASE, pointerEvents: "none" };
17
+ var OVERLAY_ACTIVE = {
18
+ ...OVERLAY_BASE,
19
+ pointerEvents: "auto",
20
+ cursor: "crosshair"
21
+ };
22
+ var SVG_STYLE = {
23
+ position: "absolute",
24
+ inset: 0,
25
+ width: "100%",
26
+ height: "100%",
27
+ pointerEvents: "none",
28
+ overflow: "visible"
29
+ };
30
+ function toPathData(points) {
31
+ let d = `M ${points[0][0]} ${points[0][1]}`;
32
+ for (let i = 1; i < points.length; i++) d += ` L ${points[i][0]} ${points[i][1]}`;
33
+ return points.length >= 3 ? `${d} Z` : d;
34
+ }
35
+ function scheduleFrame(cb) {
36
+ if (typeof requestAnimationFrame === "function") return requestAnimationFrame(cb);
37
+ cb();
38
+ return null;
39
+ }
40
+ function cancelFrame(id) {
41
+ if (id !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(id);
42
+ }
43
+ function capturePointer(el, pointerId) {
44
+ if (typeof el.setPointerCapture === "function") {
45
+ try {
46
+ el.setPointerCapture(pointerId);
47
+ } catch {
48
+ }
49
+ }
50
+ }
51
+ function releasePointer(el, pointerId) {
52
+ if (typeof el.releasePointerCapture === "function") {
53
+ try {
54
+ el.releasePointerCapture(pointerId);
55
+ } catch {
56
+ }
57
+ }
58
+ }
59
+ function Lasso() {
60
+ const instance = useAmbientGraphInstance("Lasso");
61
+ const overlayRef = useRef(null);
62
+ const dragRef = useRef(null);
63
+ const [shiftHeld, setShiftHeld] = useState(false);
64
+ const [preview, setPreview] = useState(null);
65
+ const releaseDrag = useCallback(() => {
66
+ const drag = dragRef.current;
67
+ if (drag === null) return;
68
+ dragRef.current = null;
69
+ cancelFrame(drag.frame);
70
+ const el = overlayRef.current;
71
+ if (el !== null) releasePointer(el, drag.pointerId);
72
+ setPreview(null);
73
+ }, []);
74
+ useEffect(() => {
75
+ const onKeyDown = (e) => {
76
+ if (e.key === "Shift") setShiftHeld(true);
77
+ else if (e.key === "Escape") releaseDrag();
78
+ };
79
+ const onKeyUp = (e) => {
80
+ if (e.key === "Shift") setShiftHeld(false);
81
+ };
82
+ const onBlur = () => setShiftHeld(false);
83
+ window.addEventListener("keydown", onKeyDown);
84
+ window.addEventListener("keyup", onKeyUp);
85
+ window.addEventListener("blur", onBlur);
86
+ return () => {
87
+ window.removeEventListener("keydown", onKeyDown);
88
+ window.removeEventListener("keyup", onKeyUp);
89
+ window.removeEventListener("blur", onBlur);
90
+ releaseDrag();
91
+ };
92
+ }, [releaseDrag]);
93
+ const localPoint = (e) => {
94
+ const el = overlayRef.current;
95
+ if (el === null) return [e.clientX, e.clientY];
96
+ const rect = el.getBoundingClientRect();
97
+ return [e.clientX - rect.left, e.clientY - rect.top];
98
+ };
99
+ const commitPending = (drag) => {
100
+ if (drag.pending === null) return;
101
+ drag.points.push(drag.pending);
102
+ drag.pending = null;
103
+ setPreview([...drag.points]);
104
+ };
105
+ const onPointerDown = (e) => {
106
+ if (dragRef.current !== null || e.button !== 0) return;
107
+ if (!e.shiftKey && !shiftHeld) return;
108
+ const pointerId = e.pointerId ?? -1;
109
+ const el = overlayRef.current;
110
+ if (el !== null) capturePointer(el, pointerId);
111
+ const p = localPoint(e);
112
+ dragRef.current = { pointerId, points: [p], pending: null, frame: null };
113
+ setPreview([p]);
114
+ e.preventDefault();
115
+ };
116
+ const onPointerMove = (e) => {
117
+ const drag = dragRef.current;
118
+ if (drag === null || (e.pointerId ?? -1) !== drag.pointerId) return;
119
+ drag.pending = localPoint(e);
120
+ if (drag.frame !== null) return;
121
+ commitPending(drag);
122
+ const tick = () => {
123
+ drag.frame = null;
124
+ if (dragRef.current !== drag) return;
125
+ if (drag.pending !== null) {
126
+ commitPending(drag);
127
+ drag.frame = scheduleFrame(tick);
128
+ }
129
+ };
130
+ drag.frame = scheduleFrame(tick);
131
+ };
132
+ const onPointerUp = (e) => {
133
+ const drag = dragRef.current;
134
+ if (drag === null || (e.pointerId ?? -1) !== drag.pointerId) return;
135
+ commitPending(drag);
136
+ const polygon = drag.points;
137
+ const additive = e.metaKey;
138
+ releaseDrag();
139
+ if (polygon.length >= 3) instance.selectWithinPolygon(polygon, { additive });
140
+ };
141
+ const onPointerCancel = (e) => {
142
+ const drag = dragRef.current;
143
+ if (drag === null || (e.pointerId ?? -1) !== drag.pointerId) return;
144
+ releaseDrag();
145
+ };
146
+ const active = shiftHeld || preview !== null;
147
+ return /* @__PURE__ */ jsx(
148
+ "div",
149
+ {
150
+ ref: overlayRef,
151
+ "data-orbit-lasso": "",
152
+ style: active ? OVERLAY_ACTIVE : OVERLAY_INACTIVE,
153
+ onPointerDown,
154
+ onPointerMove,
155
+ onPointerUp,
156
+ onPointerCancel,
157
+ children: preview !== null && preview.length > 0 ? /* @__PURE__ */ jsx("svg", { "data-orbit-lasso-preview": "", style: SVG_STYLE, children: /* @__PURE__ */ jsx(
158
+ "path",
159
+ {
160
+ d: toPathData(preview),
161
+ fill: "rgba(88, 134, 255, 0.12)",
162
+ stroke: "rgba(88, 134, 255, 0.9)",
163
+ strokeWidth: 1.5,
164
+ strokeDasharray: "4 3"
165
+ }
166
+ ) }) : null
167
+ }
168
+ );
169
+ }
170
+ function placementKey(p) {
171
+ return p.kind === "cluster" ? `cluster\0${p.id}` : `node\0${p.id}`;
172
+ }
173
+ var LAYER_STYLE = { ...overlayLayerStyle, overflow: "hidden" };
174
+ var LABEL_BASE_STYLE = {
175
+ position: "absolute",
176
+ top: 0,
177
+ left: 0,
178
+ pointerEvents: "auto",
179
+ cursor: "pointer",
180
+ whiteSpace: "nowrap",
181
+ userSelect: "none",
182
+ willChange: "transform"
183
+ };
184
+ function transformOf(x, y) {
185
+ return `translate3d(${x}px, ${y}px, 0)`;
186
+ }
187
+ function LabelLayer(props) {
188
+ const instance = useAmbientGraphInstance("LabelLayer");
189
+ const labels = overlaySurface(instance).labels;
190
+ const [candidates, setCandidates] = useState([]);
191
+ const elementsRef = useRef(/* @__PURE__ */ new Map());
192
+ const positionsRef = useRef(/* @__PURE__ */ new Map());
193
+ useEffect(() => {
194
+ if (labels === void 0) return void 0;
195
+ return labels.subscribeCandidates((list) => {
196
+ const positions = positionsRef.current;
197
+ const keep = /* @__PURE__ */ new Set();
198
+ for (const p of list) {
199
+ const key = placementKey(p);
200
+ keep.add(key);
201
+ positions.set(key, [p.x, p.y]);
202
+ }
203
+ for (const key of [...positions.keys()]) {
204
+ if (!keep.has(key)) positions.delete(key);
205
+ }
206
+ setCandidates(list.map((p) => ({ ...p })));
207
+ });
208
+ }, [labels]);
209
+ useLayoutEffect(() => {
210
+ if (labels === void 0) return void 0;
211
+ return labels.subscribePositions((list) => {
212
+ const elements = elementsRef.current;
213
+ const positions = positionsRef.current;
214
+ for (const p of list) {
215
+ const key = placementKey(p);
216
+ positions.set(key, [p.x, p.y]);
217
+ const el = elements.get(key);
218
+ if (el !== void 0) el.style.transform = transformOf(p.x, p.y);
219
+ }
220
+ });
221
+ }, [labels]);
222
+ const onLabelClick = (id, e) => {
223
+ e.stopPropagation();
224
+ if (e.metaKey || e.shiftKey) {
225
+ const cur = instance.store.getState().selection.nodeIds;
226
+ instance.setSelection(cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]);
227
+ return;
228
+ }
229
+ instance.setSelection([id]);
230
+ };
231
+ const onClusterLabelClick = (key, e) => {
232
+ e.stopPropagation();
233
+ instance.selectCluster(key, { additive: e.metaKey || e.shiftKey });
234
+ };
235
+ const layerRef = useRef(null);
236
+ const onLabelContextMenu = (id, e) => {
237
+ e.preventDefault();
238
+ e.stopPropagation();
239
+ const rect = layerRef.current?.getBoundingClientRect();
240
+ if (rect === void 0) return;
241
+ instance.requestNodeContextMenu(id, [e.clientX - rect.left, e.clientY - rect.top]);
242
+ };
243
+ const clusterMembers = (key) => instance.getClusters().find((c) => c.key === key)?.memberIds ?? [];
244
+ return /* @__PURE__ */ jsx("div", { "data-orbit-label-layer": "", ref: layerRef, style: LAYER_STYLE, children: candidates.map((p) => {
245
+ const key = placementKey(p);
246
+ const [x, y] = positionsRef.current.get(key) ?? [p.x, p.y];
247
+ const isCluster = p.kind === "cluster";
248
+ const className = isCluster ? [props.labelClassName, props.clusterLabelClassName].filter(Boolean).join(" ") || void 0 : props.labelClassName;
249
+ return /* @__PURE__ */ jsx(
250
+ "div",
251
+ {
252
+ ...isCluster ? { "data-orbit-cluster-label": p.id } : { "data-orbit-label": p.id },
253
+ className,
254
+ style: { ...LABEL_BASE_STYLE, transform: transformOf(x, y) },
255
+ ref: (el) => {
256
+ if (el === null) elementsRef.current.delete(key);
257
+ else elementsRef.current.set(key, el);
258
+ },
259
+ onClick: (e) => isCluster ? onClusterLabelClick(p.id, e) : onLabelClick(p.id, e),
260
+ onContextMenu: isCluster ? void 0 : (e) => onLabelContextMenu(p.id, e),
261
+ children: isCluster ? props.renderClusterLabel !== void 0 ? props.renderClusterLabel({
262
+ clusterKey: p.id,
263
+ text: p.text,
264
+ memberIds: clusterMembers(p.id)
265
+ }) : p.text : props.renderNodeLabel !== void 0 ? props.renderNodeLabel({
266
+ node: instance.getNode(p.id) ?? { id: p.id },
267
+ text: p.text
268
+ }) : p.text
269
+ },
270
+ key
271
+ );
272
+ }) });
273
+ }
274
+ var ANNOUNCE_INTERVAL_MS = 800;
275
+ function summarize(s) {
276
+ const selected = s.selection.nodeIds.length + s.selection.edgeIds.length;
277
+ return `Graph ${s.status}. ${s.nodeCount} nodes, ${s.edgeCount} edges. ${selected} selected.`;
278
+ }
279
+ function LiveRegion(props) {
280
+ const instance = useAmbientGraphInstance("LiveRegion");
281
+ const enabled = props.announcements !== false;
282
+ const [message, setMessage] = useState("");
283
+ const lastAnnouncedRef = useRef(null);
284
+ const timerRef = useRef(null);
285
+ useEffect(() => {
286
+ if (!enabled) return void 0;
287
+ const store = instance.store;
288
+ lastAnnouncedRef.current = summarize(store.getState());
289
+ const fire = () => {
290
+ timerRef.current = null;
291
+ const next = summarize(store.getState());
292
+ if (next === lastAnnouncedRef.current) return;
293
+ lastAnnouncedRef.current = next;
294
+ setMessage(next);
295
+ };
296
+ const unsubscribe = store.subscribe(() => {
297
+ if (timerRef.current !== null) return;
298
+ if (summarize(store.getState()) === lastAnnouncedRef.current) return;
299
+ timerRef.current = setTimeout(fire, ANNOUNCE_INTERVAL_MS);
300
+ });
301
+ return () => {
302
+ unsubscribe();
303
+ if (timerRef.current !== null) {
304
+ clearTimeout(timerRef.current);
305
+ timerRef.current = null;
306
+ }
307
+ };
308
+ }, [instance, enabled]);
309
+ return /* @__PURE__ */ jsx(
310
+ "div",
311
+ {
312
+ "data-orbit-live-region": "",
313
+ role: "status",
314
+ "aria-live": "polite",
315
+ "aria-atomic": "true",
316
+ style: visuallyHiddenStyle,
317
+ children: message
318
+ }
319
+ );
320
+ }
321
+ function scaleKeyOf(value) {
322
+ if (typeof value !== "object" || value === null) return void 0;
323
+ const kind = value.kind;
324
+ if (kind !== "sequential" && kind !== "categorical" && kind !== "diverging") return void 0;
325
+ return canonicalScaleKey(value);
326
+ }
327
+ function styleChannelChanged(prevValue, prevKey, nextValue, nextKey) {
328
+ if (nextKey !== void 0 || prevKey !== void 0) return nextKey !== prevKey;
329
+ return prevValue !== nextValue;
330
+ }
331
+ function filterSideKey(side) {
332
+ if (side === void 0) return "u";
333
+ if (typeof side === "function") return "fn";
334
+ return JSON.stringify(side);
335
+ }
336
+ function filterKeyOf(spec) {
337
+ if (spec === null) return "null";
338
+ return `${spec.mode ?? "hide"}|${filterSideKey(spec.nodes)}|${filterSideKey(spec.edges)}`;
339
+ }
340
+ function sameFilterSpec(prev, prevKey, next, nextKey) {
341
+ if (prevKey === void 0 || prevKey !== nextKey) return false;
342
+ if (next === null) return prev === null;
343
+ if (prev === null || prev === void 0) return false;
344
+ if (typeof next.nodes === "function" && next.nodes !== prev.nodes) return false;
345
+ if (typeof next.edges === "function" && next.edges !== prev.edges) return false;
346
+ return true;
347
+ }
348
+ function sameIdArrays(a, b) {
349
+ if (a === b) return true;
350
+ if (a.length !== b.length) return false;
351
+ for (let i = 0; i < a.length; i++) {
352
+ if (a[i] !== b[i]) return false;
353
+ }
354
+ return true;
355
+ }
356
+ var CONTAINER_BASE_STYLE = {
357
+ position: "relative",
358
+ width: "100%",
359
+ height: "100%"
360
+ };
361
+ var CANVAS_STYLE = { position: "absolute", inset: 0 };
362
+ var LEGEND_SLOT_STYLE = {
363
+ position: "absolute",
364
+ left: 12,
365
+ bottom: 12
366
+ };
367
+ function LegendSlot(props) {
368
+ const { instance } = props;
369
+ const { version, theme } = useScaleInfoSubscription(instance);
370
+ const info = useMemo(
371
+ () => ({
372
+ nodeColor: instance.getScaleInfo("nodeColor"),
373
+ nodeSize: instance.getScaleInfo("nodeSize"),
374
+ theme
375
+ }),
376
+ // `version` is the invalidation token: a new store revision can change
377
+ // what getScaleInfo returns without changing any other dependency.
378
+ [instance, theme, version]
379
+ );
380
+ return /* @__PURE__ */ jsx("div", { "data-orbit-legend-slot": "", style: LEGEND_SLOT_STYLE, children: props.renderLegend(info) });
381
+ }
382
+ function GraphInner(props, ref) {
383
+ const [instance] = useState(() => {
384
+ const options = { engine: props.engine };
385
+ if (props.fitViewOnFirstData !== void 0) {
386
+ options.fitViewOnFirstData = props.fitViewOnFirstData;
387
+ }
388
+ if (props.searchIndex !== void 0) options.searchIndex = props.searchIndex;
389
+ if (props.services !== void 0) options.services = props.services;
390
+ return createGraphInstance(options);
391
+ });
392
+ const searchIndexWarnedRef = useRef(false);
393
+ const containerRef = useRef(null);
394
+ const callbacksRef = useRef({
395
+ onNodeClick: void 0,
396
+ onBackgroundClick: void 0,
397
+ onNodeHover: void 0,
398
+ onEdgeClick: void 0,
399
+ onEdgeHover: void 0,
400
+ onNodeDragStart: void 0,
401
+ onNodeDragEnd: void 0,
402
+ onSelectionChange: void 0,
403
+ onGroupClick: void 0,
404
+ onMetaEdgeClick: void 0,
405
+ onGroupsChange: void 0,
406
+ onViewStateRestore: void 0,
407
+ onViewStateMismatch: void 0,
408
+ onPinnedChange: void 0,
409
+ onViewportChange: void 0,
410
+ onReady: void 0,
411
+ onError: void 0
412
+ });
413
+ useLayoutEffect(() => {
414
+ callbacksRef.current = {
415
+ onNodeClick: props.onNodeClick,
416
+ onBackgroundClick: props.onBackgroundClick,
417
+ onNodeHover: props.onNodeHover,
418
+ onEdgeClick: props.onEdgeClick,
419
+ onEdgeHover: props.onEdgeHover,
420
+ onNodeDragStart: props.onNodeDragStart,
421
+ onNodeDragEnd: props.onNodeDragEnd,
422
+ onSelectionChange: props.onSelectionChange,
423
+ onGroupClick: props.onGroupClick,
424
+ onMetaEdgeClick: props.onMetaEdgeClick,
425
+ onGroupsChange: props.onGroupsChange,
426
+ onViewStateRestore: props.onViewStateRestore,
427
+ onViewStateMismatch: props.onViewStateMismatch,
428
+ onPinnedChange: props.onPinnedChange,
429
+ onViewportChange: props.onViewportChange,
430
+ onReady: props.onReady,
431
+ onError: props.onError
432
+ };
433
+ });
434
+ const prevRef = useRef(null);
435
+ useLayoutEffect(() => {
436
+ const prev = prevRef.current;
437
+ const update = {};
438
+ let dirty = false;
439
+ if (props.data !== void 0 && (prev === null || prev.data !== props.data)) {
440
+ update.data = props.data;
441
+ dirty = true;
442
+ }
443
+ const nodeColorKey = scaleKeyOf(props.nodeColor);
444
+ if (props.nodeColor !== void 0 && (prev === null || styleChannelChanged(prev.nodeColor, prev.nodeColorKey, props.nodeColor, nodeColorKey))) {
445
+ update.nodeColor = props.nodeColor;
446
+ dirty = true;
447
+ }
448
+ const nodeSizeKey = scaleKeyOf(props.nodeSize);
449
+ if (props.nodeSize !== void 0 && (prev === null || styleChannelChanged(prev.nodeSize, prev.nodeSizeKey, props.nodeSize, nodeSizeKey))) {
450
+ update.nodeSize = props.nodeSize;
451
+ dirty = true;
452
+ }
453
+ if (props.linkColor !== void 0 && (prev === null || prev.linkColor !== props.linkColor)) {
454
+ update.linkColor = props.linkColor;
455
+ dirty = true;
456
+ }
457
+ if (props.linkWidth !== void 0 && (prev === null || prev.linkWidth !== props.linkWidth)) {
458
+ update.linkWidth = props.linkWidth;
459
+ dirty = true;
460
+ }
461
+ if (props.layout !== void 0 && (prev === null || prev.layout !== props.layout)) {
462
+ update.layout = props.layout;
463
+ dirty = true;
464
+ }
465
+ if (props.simulation !== void 0 && (prev === null || prev.simulation !== props.simulation)) {
466
+ update.simulation = props.simulation;
467
+ dirty = true;
468
+ }
469
+ let themeJson;
470
+ if (props.theme !== void 0) {
471
+ themeJson = JSON.stringify(props.theme);
472
+ if (prev === null || prev.themeJson !== themeJson) {
473
+ update.theme = props.theme;
474
+ dirty = true;
475
+ }
476
+ }
477
+ if (props.metrics !== void 0 && (prev === null || prev.metrics !== props.metrics)) {
478
+ update.metrics = props.metrics;
479
+ dirty = true;
480
+ }
481
+ if (props.nodeImage !== void 0 && (prev === null || prev.nodeImage !== props.nodeImage)) {
482
+ update.nodeImage = props.nodeImage;
483
+ dirty = true;
484
+ } else if (props.nodeImage === void 0 && prev !== null && prev.nodeImage !== void 0) {
485
+ update.nodeImage = null;
486
+ dirty = true;
487
+ }
488
+ if (props.edgeArrows !== void 0 && (prev === null || prev.edgeArrows !== props.edgeArrows)) {
489
+ update.edgeArrows = props.edgeArrows;
490
+ dirty = true;
491
+ }
492
+ if (props.showLinks !== void 0 && (prev === null || prev.showLinks !== props.showLinks)) {
493
+ update.showLinks = props.showLinks;
494
+ dirty = true;
495
+ }
496
+ if (props.selection !== void 0 && (prev === null || prev.selection === void 0 || !sameIdArrays(prev.selection, props.selection))) {
497
+ update.selection = props.selection;
498
+ dirty = true;
499
+ }
500
+ if (props.labels !== void 0 && (prev === null || prev.labels !== props.labels)) {
501
+ update.labels = props.labels;
502
+ dirty = true;
503
+ }
504
+ if (props.accessibility !== void 0 && (prev === null || prev.accessibility !== props.accessibility)) {
505
+ update.accessibility = props.accessibility;
506
+ dirty = true;
507
+ }
508
+ let subgraphJson;
509
+ if (props.subgraph !== void 0) {
510
+ subgraphJson = JSON.stringify(props.subgraph);
511
+ if (prev === null || prev.subgraphJson !== subgraphJson) {
512
+ update.subgraph = props.subgraph;
513
+ dirty = true;
514
+ }
515
+ }
516
+ let filterKey;
517
+ if (props.filter !== void 0) {
518
+ filterKey = filterKeyOf(props.filter);
519
+ if (prev === null || !sameFilterSpec(prev.filter, prev.filterKey, props.filter, filterKey)) {
520
+ update.filter = props.filter;
521
+ dirty = true;
522
+ }
523
+ }
524
+ if (props.crossfilter !== void 0 && (prev === null || prev.crossfilter !== props.crossfilter)) {
525
+ update.crossfilter = props.crossfilter;
526
+ dirty = true;
527
+ }
528
+ if (props.dataRef !== void 0) {
529
+ const prevRef2 = prev === null ? void 0 : prev.dataRef;
530
+ if (prevRef2 === void 0 || canonicalJson(props.dataRef) !== canonicalJson(prevRef2)) {
531
+ update.dataRef = props.dataRef;
532
+ dirty = true;
533
+ }
534
+ }
535
+ if (props.groups !== void 0) {
536
+ const prevGroups = prev === null ? void 0 : prev.groups;
537
+ if (prevGroups === void 0 || !sameGroupSpecArrays(prevGroups, props.groups)) {
538
+ update.groups = props.groups;
539
+ dirty = true;
540
+ }
541
+ }
542
+ if (props.groupBy !== void 0 && (prev === null || prev.groupBy !== props.groupBy)) {
543
+ update.groupBy = props.groupBy;
544
+ dirty = true;
545
+ }
546
+ if (props.clusters !== void 0 && (prev === null || prev.clusters !== props.clusters)) {
547
+ update.clusters = props.clusters;
548
+ dirty = true;
549
+ }
550
+ if (props.pinnedNodeIds !== void 0) {
551
+ const prevPinned = prev === null ? void 0 : prev.pinnedNodeIds;
552
+ const changed = prevPinned === void 0 || prevPinned === null || props.pinnedNodeIds === null || !sameIdArrays(prevPinned, props.pinnedNodeIds);
553
+ if (changed) {
554
+ update.pinnedNodeIds = props.pinnedNodeIds;
555
+ dirty = true;
556
+ }
557
+ }
558
+ if (prev !== null && !sameIdArrays(prev.searchIndex ?? [], props.searchIndex ?? []) && !searchIndexWarnedRef.current) {
559
+ searchIndexWarnedRef.current = true;
560
+ console.warn(
561
+ "[orbit] searchIndex is construction-only (\xA716.5): the changed value was ignored \u2014 key-remount <Graph> to change it"
562
+ );
563
+ }
564
+ prevRef.current = {
565
+ data: props.data,
566
+ nodeColor: props.nodeColor,
567
+ nodeColorKey,
568
+ nodeSize: props.nodeSize,
569
+ nodeSizeKey,
570
+ linkColor: props.linkColor,
571
+ linkWidth: props.linkWidth,
572
+ layout: props.layout,
573
+ simulation: props.simulation,
574
+ themeJson,
575
+ metrics: props.metrics,
576
+ nodeImage: props.nodeImage,
577
+ edgeArrows: props.edgeArrows,
578
+ showLinks: props.showLinks,
579
+ selection: props.selection,
580
+ labels: props.labels,
581
+ accessibility: props.accessibility,
582
+ subgraphJson,
583
+ filter: props.filter,
584
+ filterKey,
585
+ crossfilter: props.crossfilter,
586
+ groups: props.groups,
587
+ dataRef: props.dataRef,
588
+ groupBy: props.groupBy,
589
+ clusters: props.clusters,
590
+ pinnedNodeIds: props.pinnedNodeIds,
591
+ searchIndex: props.searchIndex
592
+ };
593
+ if (dirty) instance.applyHostUpdate(update);
594
+ });
595
+ useLayoutEffect(() => {
596
+ setSearchResultUnavailableCallback(instance, props.onSearchResultUnavailable);
597
+ });
598
+ useEffect(() => {
599
+ const offs = [
600
+ instance.on("nodeClick", (p) => callbacksRef.current.onNodeClick?.(p)),
601
+ instance.on("backgroundClick", () => callbacksRef.current.onBackgroundClick?.()),
602
+ instance.on("nodeHover", (p) => callbacksRef.current.onNodeHover?.(p)),
603
+ instance.on("edgeClick", (p) => callbacksRef.current.onEdgeClick?.(p)),
604
+ instance.on("edgeHover", (p) => callbacksRef.current.onEdgeHover?.(p)),
605
+ instance.on("nodeDragStart", (p) => callbacksRef.current.onNodeDragStart?.(p)),
606
+ instance.on("nodeDragEnd", (p) => callbacksRef.current.onNodeDragEnd?.(p)),
607
+ instance.on("selectionChange", (p) => callbacksRef.current.onSelectionChange?.(p)),
608
+ instance.on("groupClick", (p) => callbacksRef.current.onGroupClick?.(p)),
609
+ instance.on("metaEdgeClick", (p) => callbacksRef.current.onMetaEdgeClick?.(p)),
610
+ instance.on("groupsChange", (p) => callbacksRef.current.onGroupsChange?.(p.groups)),
611
+ instance.on("viewStateRestore", (p) => callbacksRef.current.onViewStateRestore?.(p)),
612
+ instance.on(
613
+ "viewStateMismatch",
614
+ (p) => callbacksRef.current.onViewStateMismatch?.(p.stored, p.current)
615
+ ),
616
+ instance.on("pinnedChange", (p) => callbacksRef.current.onPinnedChange?.(p.pinnedNodeIds)),
617
+ instance.on("viewportChange", (v) => callbacksRef.current.onViewportChange?.(v)),
618
+ instance.on("ready", () => callbacksRef.current.onReady?.()),
619
+ instance.on("error", (p) => callbacksRef.current.onError?.(p))
620
+ ];
621
+ return () => {
622
+ for (const off of offs) off();
623
+ };
624
+ }, [instance]);
625
+ useEffect(() => {
626
+ const container = containerRef.current;
627
+ if (container === null) return void 0;
628
+ let active = true;
629
+ void instance.attach(container).catch((err) => {
630
+ if (!active) return;
631
+ if (instance.store.getState().status === "error") return;
632
+ const error = err instanceof Error ? err : new Error(String(err));
633
+ callbacksRef.current.onError?.({ error });
634
+ });
635
+ return () => {
636
+ active = false;
637
+ instance.detach();
638
+ };
639
+ }, [instance]);
640
+ const destroyGateRef = useRef({ pendingDestroy: false });
641
+ useEffect(() => {
642
+ const gate = destroyGateRef.current;
643
+ gate.pendingDestroy = false;
644
+ return () => {
645
+ gate.pendingDestroy = true;
646
+ queueMicrotask(() => {
647
+ if (gate.pendingDestroy) instance.destroy();
648
+ });
649
+ };
650
+ }, [instance]);
651
+ useEffect(() => {
652
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
653
+ return void 0;
654
+ }
655
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
656
+ const surface = overlaySurface(instance);
657
+ surface.setReducedMotion?.(mq.matches);
658
+ const onChange = (e) => {
659
+ surface.setReducedMotion?.(e.matches);
660
+ };
661
+ if (typeof mq.addEventListener === "function") {
662
+ mq.addEventListener("change", onChange);
663
+ return () => {
664
+ mq.removeEventListener("change", onChange);
665
+ };
666
+ }
667
+ if (typeof mq.addListener === "function") {
668
+ mq.addListener(onChange);
669
+ return () => {
670
+ mq.removeListener(onChange);
671
+ };
672
+ }
673
+ return void 0;
674
+ }, [instance]);
675
+ useImperativeHandle(
676
+ ref,
677
+ () => ({
678
+ fitView: (_opts) => {
679
+ instance.fitView();
680
+ },
681
+ zoomIn: () => {
682
+ instance.zoomIn();
683
+ },
684
+ zoomOut: () => {
685
+ instance.zoomOut();
686
+ },
687
+ setViewport: (v, _opts) => {
688
+ instance.setViewport(v);
689
+ },
690
+ requestNodeContextMenu: (id, screen) => {
691
+ instance.requestNodeContextMenu(id, screen);
692
+ },
693
+ getViewState: (opts) => instance.getViewState(opts),
694
+ setViewState: (raw, opts) => instance.setViewState(raw, opts),
695
+ exportImage: ((format, opts) => format === "png" ? instance.exportImage("png") : instance.exportImage("svg", opts)),
696
+ exportData: (scope, opts) => instance.exportData(scope, opts),
697
+ exportDataStream: (scope) => instance.exportDataStream(scope),
698
+ exportLayout: (opts) => instance.exportLayout(opts),
699
+ exportLayoutStream: () => instance.exportLayoutStream(),
700
+ focusNode: (id) => {
701
+ instance.focusNode(id);
702
+ },
703
+ setSelection: (ids) => {
704
+ instance.setSelection(ids);
705
+ },
706
+ findPath: (sourceId, targetId, options) => instance.findPath(sourceId, targetId, options),
707
+ clearPath: () => {
708
+ instance.clearPath();
709
+ },
710
+ clearSelection: () => {
711
+ instance.clearSelection();
712
+ },
713
+ selectNeighbors: (id) => {
714
+ instance.selectNeighbors(id);
715
+ },
716
+ selectAll: () => {
717
+ instance.selectAll();
718
+ },
719
+ invertSelection: () => {
720
+ instance.invertSelection();
721
+ },
722
+ selectWithinPolygon: (polygon, opts) => instance.selectWithinPolygon(polygon, opts),
723
+ pinNode: (id, xy) => {
724
+ instance.pinNode(id, xy);
725
+ },
726
+ unpinNode: (id) => {
727
+ instance.unpinNode(id);
728
+ },
729
+ clearPins: () => {
730
+ instance.clearPins();
731
+ },
732
+ hideNodes: (ids) => {
733
+ instance.hideNodes(ids);
734
+ },
735
+ showAll: () => {
736
+ instance.showAll();
737
+ },
738
+ isolateSelection: () => {
739
+ instance.isolateSelection();
740
+ },
741
+ resetIsolation: () => {
742
+ instance.resetIsolation();
743
+ },
744
+ expandNode: (id, opts) => instance.expandNode(id, opts),
745
+ retractExpansion: (id) => {
746
+ instance.retractExpansion(id);
747
+ },
748
+ pinNodes: (ids) => {
749
+ instance.pinNodes(ids);
750
+ },
751
+ unpinNodes: (ids) => {
752
+ instance.unpinNodes(ids);
753
+ },
754
+ groupNodes: (spec) => {
755
+ instance.groupNodes(spec);
756
+ },
757
+ ungroup: (groupId) => {
758
+ instance.ungroup(groupId);
759
+ },
760
+ setGroupCollapsed: (groupId, collapsed) => {
761
+ instance.setGroupCollapsed(groupId, collapsed);
762
+ },
763
+ foldNode: (id, opts) => {
764
+ instance.foldNode(id, opts);
765
+ },
766
+ unfoldNode: (id) => {
767
+ instance.unfoldNode(id);
768
+ },
769
+ getFold: (id) => instance.getFold(id),
770
+ getClusters: () => instance.getClusters(),
771
+ selectCluster: (key, opts) => {
772
+ instance.selectCluster(key, opts);
773
+ },
774
+ beginIngest: (opts) => instance.beginIngest(opts),
775
+ removeOverlay: (overlayId) => instance.removeOverlay(overlayId),
776
+ undo: () => instance.undo(),
777
+ redo: () => instance.redo(),
778
+ playTimeline: (key, playback) => {
779
+ instance.playTimeline(key, playback);
780
+ },
781
+ pauseTimeline: () => {
782
+ instance.pauseTimeline();
783
+ },
784
+ getCrossfilterSession: () => instance.getCrossfilterSession(),
785
+ getScaleInfo: (channel) => instance.getScaleInfo(channel),
786
+ getMetricValue: (metric, id) => instance.getMetricValue(metric, id),
787
+ getRevisions: () => instance.getRevisions(),
788
+ getDiagnostics: () => instance.getDiagnostics(),
789
+ instance
790
+ }),
791
+ [instance]
792
+ );
793
+ const descriptionId = useId();
794
+ const description = props.accessibility?.description;
795
+ return /* @__PURE__ */ jsx("div", { className: props.className, style: { ...CONTAINER_BASE_STYLE, ...props.style }, children: /* @__PURE__ */ jsxs(GraphProvider, { instance, children: [
796
+ /* @__PURE__ */ jsx(
797
+ "div",
798
+ {
799
+ ref: containerRef,
800
+ "data-orbit-canvas": "",
801
+ style: CANVAS_STYLE,
802
+ role: "application",
803
+ "aria-label": props.accessibility?.label ?? "Graph visualization",
804
+ "aria-describedby": description !== void 0 ? descriptionId : void 0
805
+ }
806
+ ),
807
+ description !== void 0 ? /* @__PURE__ */ jsx("div", { id: descriptionId, style: visuallyHiddenStyle, children: description }) : null,
808
+ props.labels?.enabled !== false ? /* @__PURE__ */ jsx(
809
+ LabelLayer,
810
+ {
811
+ labelClassName: props.labelClassName,
812
+ renderNodeLabel: props.renderNodeLabel,
813
+ clusterLabelClassName: props.clusterLabelClassName,
814
+ renderClusterLabel: props.renderClusterLabel
815
+ }
816
+ ) : null,
817
+ props.enableLasso !== false ? /* @__PURE__ */ jsx(Lasso, {}) : null,
818
+ /* @__PURE__ */ jsx(LiveRegion, { announcements: props.accessibility?.announcements }),
819
+ props.renderLegend !== void 0 ? /* @__PURE__ */ jsx(LegendSlot, { instance, renderLegend: props.renderLegend }) : null,
820
+ props.children
821
+ ] }) });
822
+ }
823
+ var Graph = forwardRef(GraphInner);
824
+
825
+ export { Graph, LabelLayer, LiveRegion };
826
+ //# sourceMappingURL=index.js.map
827
+ //# sourceMappingURL=index.js.map