@invana/canvas-react 0.0.2

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/index.js ADDED
@@ -0,0 +1,2251 @@
1
+ import { createContext, forwardRef, useRef, useState, useEffect, useImperativeHandle, useContext, useCallback, useMemo } from 'react';
2
+ import { Canvas as Canvas$1, BackgroundLayer as BackgroundLayer$1, DragPanBehaviour as DragPanBehaviour$1, WheelZoomBehaviour as WheelZoomBehaviour$1, PinchZoomBehaviour as PinchZoomBehaviour$1, KeyboardCameraInputBehaviour as KeyboardCameraInputBehaviour$1 } from '@invana/canvas';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { GraphHistory, GraphClipboard, GraphLayer as GraphLayer$1, MiniMapLayer as MiniMapLayer$1, DragNodeBehaviour as DragNodeBehaviour$1, ContextMenuBehaviour as ContextMenuBehaviour$1, CreateNodeBehaviour as CreateNodeBehaviour$1, DrawEdgeBehaviour as DrawEdgeBehaviour$1, EraseBehaviour as EraseBehaviour$1, HoverActivateBehaviour as HoverActivateBehaviour$1, ClickSelectBehaviour as ClickSelectBehaviour$1, ClickInspectBehaviour as ClickInspectBehaviour$1, BrushSelectBehaviour as BrushSelectBehaviour$1, LassoSelectBehaviour as LassoSelectBehaviour$1, CollapseExpandBehaviour as CollapseExpandBehaviour$1, NodeResizeBehaviour as NodeResizeBehaviour$1, LabelCollisionBehaviour as LabelCollisionBehaviour$1, LabelResolutionLODBehaviour as LabelResolutionLODBehaviour$1, NodeSizeLODBehaviour as NodeSizeLODBehaviour$1, EdgeSizeLODBehaviour as EdgeSizeLODBehaviour$1, ParallelEdgeBehaviour as ParallelEdgeBehaviour$1, DegreeSizeBehaviour as DegreeSizeBehaviour$1, ResponsiveThemeBehaviour as ResponsiveThemeBehaviour$1 } from '@invana/graph';
5
+ import { D3ForceLayout as D3ForceLayout$1 } from '@invana/graph-layout-d3-force';
6
+ import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuItem, DropdownMenuSeparator, NestedMenu, NavHorizontal, NavVertical, Separator } from '@invana/ui';
7
+
8
+ // src/Canvas.tsx
9
+ var CanvasContext = createContext(null);
10
+ function useCanvas() {
11
+ const canvas = useContext(CanvasContext);
12
+ if (!canvas) {
13
+ throw new Error("useCanvas() must be called inside a <Canvas> component");
14
+ }
15
+ return canvas;
16
+ }
17
+ var Canvas = forwardRef(function Canvas2({ children, style, className, ...engineOpts }, ref) {
18
+ const hostRef = useRef(null);
19
+ const [canvas, setCanvas] = useState(null);
20
+ const optsRef = useRef(engineOpts);
21
+ optsRef.current = engineOpts;
22
+ useEffect(() => {
23
+ const container = hostRef.current;
24
+ if (!container) return;
25
+ let cancelled = false;
26
+ const engine = new Canvas$1();
27
+ void engine.init({ container, ...optsRef.current }).then(() => {
28
+ if (cancelled) {
29
+ engine.destroy();
30
+ return;
31
+ }
32
+ setCanvas(engine);
33
+ }).catch((err) => {
34
+ if (!cancelled) {
35
+ console.error("[canvas-react] Canvas.init() failed:", err);
36
+ }
37
+ });
38
+ return () => {
39
+ cancelled = true;
40
+ setCanvas(null);
41
+ if (engine.isInitialised) engine.destroy();
42
+ };
43
+ }, []);
44
+ useImperativeHandle(ref, () => canvas, [canvas]);
45
+ return /* @__PURE__ */ jsx(
46
+ "div",
47
+ {
48
+ ref: hostRef,
49
+ className,
50
+ style: { width: "100%", height: "100%", position: "relative", ...style },
51
+ children: canvas && /* @__PURE__ */ jsx(CanvasContext.Provider, { value: canvas, children })
52
+ }
53
+ );
54
+ });
55
+ var HistoryContext = createContext(null);
56
+ var ClipboardContext = createContext(null);
57
+ var ToolContext = createContext(null);
58
+ function useResolvedCanvas(explicit) {
59
+ const fromContext = useContext(CanvasContext);
60
+ const canvas = explicit ?? fromContext;
61
+ if (!canvas) {
62
+ throw new Error(
63
+ "Canvas hooks need a <Canvas> ancestor, or an explicit `canvas` argument."
64
+ );
65
+ }
66
+ return canvas;
67
+ }
68
+ function GraphHistoryProvider({
69
+ layerId = "graph",
70
+ limit,
71
+ canvas,
72
+ children
73
+ }) {
74
+ const resolved = useResolvedCanvas(canvas);
75
+ const [history, setHistory] = useState(null);
76
+ useEffect(() => {
77
+ const layer = resolved.layers.get(layerId);
78
+ const store = layer?.store;
79
+ if (!store) return;
80
+ const instance = new GraphHistory(store, limit !== void 0 ? { limit } : {});
81
+ setHistory(instance);
82
+ return () => {
83
+ instance.clear();
84
+ setHistory(null);
85
+ };
86
+ }, [resolved, layerId, limit]);
87
+ useEffect(() => {
88
+ if (!history) return;
89
+ const layer = resolved.layers.get(layerId);
90
+ const store = layer?.store;
91
+ if (!layer || !store) return;
92
+ let before = null;
93
+ const offStart = layer.events.on("node:drag-start", ({ nodeId, nodeIds }) => {
94
+ const ids = /* @__PURE__ */ new Set();
95
+ for (const primary of nodeIds ?? [nodeId]) {
96
+ ids.add(primary);
97
+ for (const desc of store.descendantsOf(primary)) ids.add(desc);
98
+ }
99
+ before = /* @__PURE__ */ new Map();
100
+ for (const id of ids) {
101
+ const p = store.getPosition(id);
102
+ if (p) before.set(id, { x: p.x, y: p.y });
103
+ }
104
+ });
105
+ const offEnd = layer.events.on("node:drag-end", () => {
106
+ const snap = before;
107
+ before = null;
108
+ if (!snap) return;
109
+ const ops = [];
110
+ for (const [id, from] of snap) {
111
+ const to = store.getPosition(id);
112
+ if (to && (to.x !== from.x || to.y !== from.y)) {
113
+ ops.push({ kind: "moveNode", id, before: from, after: { x: to.x, y: to.y } });
114
+ }
115
+ }
116
+ if (ops.length > 0) history.push({ ops, label: "move" });
117
+ });
118
+ return () => {
119
+ offStart();
120
+ offEnd();
121
+ };
122
+ }, [resolved, layerId, history]);
123
+ return /* @__PURE__ */ jsx(HistoryContext.Provider, { value: history, children });
124
+ }
125
+ function GraphClipboardProvider({
126
+ layerId = "graph",
127
+ pasteOffset,
128
+ canvas,
129
+ children
130
+ }) {
131
+ const resolved = useResolvedCanvas(canvas);
132
+ const [clipboard, setClipboard] = useState(null);
133
+ useEffect(() => {
134
+ const layer = resolved.layers.get(layerId);
135
+ const store = layer?.store;
136
+ if (!store) return;
137
+ const instance = new GraphClipboard(store, pasteOffset ? { pasteOffset } : {});
138
+ setClipboard(instance);
139
+ return () => setClipboard(null);
140
+ }, [resolved, layerId, pasteOffset]);
141
+ return /* @__PURE__ */ jsx(ClipboardContext.Provider, { value: clipboard, children });
142
+ }
143
+ function GraphToolProvider({
144
+ defaultTool = "select",
145
+ defaultNodeKind = "circle",
146
+ escapeToSelect = true,
147
+ children
148
+ }) {
149
+ const [tool, setTool] = useState(defaultTool);
150
+ const [nodeKind, setNodeKind] = useState(defaultNodeKind);
151
+ const reset = useCallback(() => setTool("select"), []);
152
+ useEffect(() => {
153
+ if (!escapeToSelect) return;
154
+ const onKey = (e) => {
155
+ if (e.key === "Escape") reset();
156
+ };
157
+ window.addEventListener("keydown", onKey);
158
+ return () => window.removeEventListener("keydown", onKey);
159
+ }, [escapeToSelect, reset]);
160
+ const value = useMemo(
161
+ () => ({ tool, setTool, nodeKind, setNodeKind }),
162
+ [tool, nodeKind]
163
+ );
164
+ return /* @__PURE__ */ jsx(ToolContext.Provider, { value, children });
165
+ }
166
+ function GraphLayer({ id = "graph", data, store, ...rest }) {
167
+ const canvas = useCanvas();
168
+ const layerRef = useRef(null);
169
+ useEffect(() => {
170
+ const layer = new GraphLayer$1({
171
+ id,
172
+ options: { ...rest, ...store ? { store } : {} }
173
+ });
174
+ canvas.layers.add(layer);
175
+ layerRef.current = layer;
176
+ return () => {
177
+ canvas.layers.remove(id);
178
+ layerRef.current = null;
179
+ };
180
+ }, [canvas, id]);
181
+ useEffect(() => {
182
+ if (data && layerRef.current) {
183
+ layerRef.current.setData(data);
184
+ }
185
+ }, [data]);
186
+ return null;
187
+ }
188
+ function BackgroundLayer({ id = "background", ...options }) {
189
+ const canvas = useCanvas();
190
+ const layerRef = useRef(null);
191
+ useEffect(() => {
192
+ const layer = new BackgroundLayer$1({ id, options });
193
+ canvas.layers.add(layer);
194
+ layerRef.current = layer;
195
+ return () => {
196
+ canvas.layers.remove(id);
197
+ layerRef.current = null;
198
+ };
199
+ }, [canvas, id]);
200
+ const optionsKey = useMemo(() => JSON.stringify(options), [options]);
201
+ useEffect(() => {
202
+ layerRef.current?.setOptions(options);
203
+ }, [optionsKey]);
204
+ return null;
205
+ }
206
+ function MiniMapLayer({
207
+ id = "minimap",
208
+ graphLayerId = "graph",
209
+ ...options
210
+ }) {
211
+ const canvas = useCanvas();
212
+ const layerRef = useRef(null);
213
+ useEffect(() => {
214
+ const layer = new MiniMapLayer$1({ id, options: { graphLayerId, ...options } });
215
+ canvas.layers.add(layer);
216
+ layerRef.current = layer;
217
+ return () => {
218
+ canvas.layers.remove(id);
219
+ layerRef.current = null;
220
+ };
221
+ }, [canvas, id, graphLayerId]);
222
+ const optionsKey = useMemo(() => JSON.stringify(options), [options]);
223
+ useEffect(() => {
224
+ layerRef.current?.setOptions(options);
225
+ }, [optionsKey]);
226
+ return null;
227
+ }
228
+ function useBehaviourRegistration(create, id, enabled, identity) {
229
+ const canvas = useCanvas();
230
+ useEffect(() => {
231
+ const behaviour = create();
232
+ canvas.behaviours.register(behaviour);
233
+ return () => {
234
+ canvas.behaviours.unregister(id);
235
+ };
236
+ }, [canvas, ...identity]);
237
+ useEffect(() => {
238
+ canvas.behaviours.setEnabled(id, enabled);
239
+ }, [canvas, enabled, ...identity]);
240
+ }
241
+
242
+ // src/behaviours/DragPanBehaviour.tsx
243
+ function DragPanBehaviour({ id = "pan", enabled = true, ...rest }) {
244
+ useBehaviourRegistration(
245
+ () => new DragPanBehaviour$1({ id, enabled, ...rest }),
246
+ id,
247
+ enabled,
248
+ [id]
249
+ );
250
+ return null;
251
+ }
252
+ function WheelZoomBehaviour({
253
+ id = "zoom",
254
+ enabled = true,
255
+ ...rest
256
+ }) {
257
+ useBehaviourRegistration(
258
+ () => new WheelZoomBehaviour$1({ id, enabled, ...rest }),
259
+ id,
260
+ enabled,
261
+ [id]
262
+ );
263
+ return null;
264
+ }
265
+ function PinchZoomBehaviour({
266
+ id = "pinch",
267
+ enabled = true,
268
+ ...rest
269
+ }) {
270
+ useBehaviourRegistration(
271
+ () => new PinchZoomBehaviour$1({ id, enabled, ...rest }),
272
+ id,
273
+ enabled,
274
+ [id]
275
+ );
276
+ return null;
277
+ }
278
+ function KeyboardCameraInputBehaviour({
279
+ id = "keyboard-camera",
280
+ enabled = true,
281
+ ...rest
282
+ }) {
283
+ useBehaviourRegistration(
284
+ () => new KeyboardCameraInputBehaviour$1({ id, enabled, ...rest }),
285
+ id,
286
+ enabled,
287
+ [id]
288
+ );
289
+ return null;
290
+ }
291
+ function DragNodeBehaviour({
292
+ id = "drag-node",
293
+ layerId = "graph",
294
+ enabled = true,
295
+ ...rest
296
+ }) {
297
+ useBehaviourRegistration(
298
+ () => new DragNodeBehaviour$1({ id, layerId, enabled, ...rest }),
299
+ id,
300
+ enabled,
301
+ [id, layerId]
302
+ );
303
+ return null;
304
+ }
305
+ function ContextMenuBehaviour({
306
+ id = "context-menu",
307
+ layerId = "graph",
308
+ enabled = true,
309
+ ...rest
310
+ }) {
311
+ useBehaviourRegistration(
312
+ () => new ContextMenuBehaviour$1({ id, layerId, enabled, ...rest }),
313
+ id,
314
+ enabled,
315
+ [id, layerId]
316
+ );
317
+ return null;
318
+ }
319
+ function CreateNodeBehaviour({
320
+ id = "create-node",
321
+ layerId = "graph",
322
+ enabled = true,
323
+ ...rest
324
+ }) {
325
+ useBehaviourRegistration(
326
+ () => new CreateNodeBehaviour$1({ id, layerId, enabled, ...rest }),
327
+ id,
328
+ enabled,
329
+ [id, layerId]
330
+ );
331
+ return null;
332
+ }
333
+ function DrawEdgeBehaviour({
334
+ id = "draw-edge",
335
+ layerId = "graph",
336
+ enabled = true,
337
+ ...rest
338
+ }) {
339
+ useBehaviourRegistration(
340
+ () => new DrawEdgeBehaviour$1({ id, layerId, enabled, ...rest }),
341
+ id,
342
+ enabled,
343
+ [id, layerId]
344
+ );
345
+ return null;
346
+ }
347
+ function EraseBehaviour({
348
+ id = "erase",
349
+ layerId = "graph",
350
+ enabled = true,
351
+ ...rest
352
+ }) {
353
+ useBehaviourRegistration(
354
+ () => new EraseBehaviour$1({ id, layerId, enabled, ...rest }),
355
+ id,
356
+ enabled,
357
+ [id, layerId]
358
+ );
359
+ return null;
360
+ }
361
+ function HoverActivateBehaviour({
362
+ id = "hover",
363
+ layerId = "graph",
364
+ enabled = true,
365
+ ...rest
366
+ }) {
367
+ useBehaviourRegistration(
368
+ () => new HoverActivateBehaviour$1({ id, layerId, enabled, ...rest }),
369
+ id,
370
+ enabled,
371
+ [id, layerId]
372
+ );
373
+ return null;
374
+ }
375
+ function ClickSelectBehaviour({
376
+ id = "click-select",
377
+ layerId = "graph",
378
+ enabled = true,
379
+ ...rest
380
+ }) {
381
+ useBehaviourRegistration(
382
+ () => new ClickSelectBehaviour$1({ id, layerId, enabled, ...rest }),
383
+ id,
384
+ enabled,
385
+ [id, layerId]
386
+ );
387
+ return null;
388
+ }
389
+ function ClickInspectBehaviour({
390
+ id = "click-inspect",
391
+ layerId = "graph",
392
+ enabled = true,
393
+ ...rest
394
+ }) {
395
+ useBehaviourRegistration(
396
+ () => new ClickInspectBehaviour$1({ id, layerId, enabled, ...rest }),
397
+ id,
398
+ enabled,
399
+ [id, layerId]
400
+ );
401
+ return null;
402
+ }
403
+ function BrushSelectBehaviour({
404
+ id = "brush-select",
405
+ layerId = "graph",
406
+ enabled = true,
407
+ ...rest
408
+ }) {
409
+ useBehaviourRegistration(
410
+ () => new BrushSelectBehaviour$1({ id, layerId, enabled, ...rest }),
411
+ id,
412
+ enabled,
413
+ [id, layerId]
414
+ );
415
+ return null;
416
+ }
417
+ function LassoSelectBehaviour({
418
+ id = "lasso-select",
419
+ layerId = "graph",
420
+ enabled = true,
421
+ ...rest
422
+ }) {
423
+ useBehaviourRegistration(
424
+ () => new LassoSelectBehaviour$1({ id, layerId, enabled, ...rest }),
425
+ id,
426
+ enabled,
427
+ [id, layerId]
428
+ );
429
+ return null;
430
+ }
431
+ function CollapseExpandBehaviour({
432
+ id = "collapse-expand",
433
+ layerId = "graph",
434
+ enabled = true,
435
+ ...rest
436
+ }) {
437
+ useBehaviourRegistration(
438
+ () => new CollapseExpandBehaviour$1({ id, layerId, enabled, ...rest }),
439
+ id,
440
+ enabled,
441
+ [id, layerId]
442
+ );
443
+ return null;
444
+ }
445
+ function NodeResizeBehaviour({
446
+ id = "node-resize",
447
+ layerId = "graph",
448
+ enabled = true,
449
+ ...rest
450
+ }) {
451
+ useBehaviourRegistration(
452
+ () => new NodeResizeBehaviour$1({ id, layerId, enabled, ...rest }),
453
+ id,
454
+ enabled,
455
+ [id, layerId]
456
+ );
457
+ return null;
458
+ }
459
+ function LabelCollisionBehaviour({
460
+ id = "label-collision",
461
+ layerId = "graph",
462
+ enabled = true,
463
+ ...rest
464
+ }) {
465
+ useBehaviourRegistration(
466
+ () => new LabelCollisionBehaviour$1({ id, layerId, enabled, ...rest }),
467
+ id,
468
+ enabled,
469
+ [id, layerId]
470
+ );
471
+ return null;
472
+ }
473
+ function LabelResolutionLODBehaviour({
474
+ id = "label-lod",
475
+ layerId = "graph",
476
+ enabled = true,
477
+ ...rest
478
+ }) {
479
+ useBehaviourRegistration(
480
+ () => new LabelResolutionLODBehaviour$1({ id, layerId, enabled, ...rest }),
481
+ id,
482
+ enabled,
483
+ [id, layerId]
484
+ );
485
+ return null;
486
+ }
487
+ function NodeSizeLODBehaviour({
488
+ id = "node-size-lod",
489
+ layerId = "graph",
490
+ enabled = true,
491
+ ...rest
492
+ }) {
493
+ useBehaviourRegistration(
494
+ () => new NodeSizeLODBehaviour$1({ id, layerId, enabled, ...rest }),
495
+ id,
496
+ enabled,
497
+ [id, layerId]
498
+ );
499
+ return null;
500
+ }
501
+ function EdgeSizeLODBehaviour({
502
+ id = "edge-size-lod",
503
+ layerId = "graph",
504
+ enabled = true,
505
+ ...rest
506
+ }) {
507
+ useBehaviourRegistration(
508
+ () => new EdgeSizeLODBehaviour$1({ id, layerId, enabled, ...rest }),
509
+ id,
510
+ enabled,
511
+ [id, layerId]
512
+ );
513
+ return null;
514
+ }
515
+ function ParallelEdgeBehaviour({
516
+ id = "parallel-edge",
517
+ layerId = "graph",
518
+ enabled = true,
519
+ ...rest
520
+ }) {
521
+ useBehaviourRegistration(
522
+ () => new ParallelEdgeBehaviour$1({ id, layerId, enabled, ...rest }),
523
+ id,
524
+ enabled,
525
+ [id, layerId]
526
+ );
527
+ return null;
528
+ }
529
+ function DegreeSizeBehaviour({
530
+ id = "degree-size",
531
+ layerId = "graph",
532
+ enabled = true,
533
+ ...rest
534
+ }) {
535
+ useBehaviourRegistration(
536
+ () => new DegreeSizeBehaviour$1({ id, layerId, enabled, ...rest }),
537
+ id,
538
+ enabled,
539
+ [id, layerId]
540
+ );
541
+ return null;
542
+ }
543
+ function ResponsiveThemeBehaviour({
544
+ id = "responsive-theme",
545
+ layerId = "graph",
546
+ enabled = true,
547
+ ...rest
548
+ }) {
549
+ useBehaviourRegistration(
550
+ () => new ResponsiveThemeBehaviour$1({ id, layerId, enabled, ...rest }),
551
+ id,
552
+ enabled,
553
+ [id, layerId]
554
+ );
555
+ return null;
556
+ }
557
+ function D3ForceLayout({
558
+ targetLayerId = "graph",
559
+ fitPadding = 80,
560
+ options
561
+ }) {
562
+ const canvas = useCanvas();
563
+ useEffect(() => {
564
+ const layer = canvas.layers.get(targetLayerId);
565
+ if (!layer) {
566
+ console.warn(
567
+ `[canvas-react] <D3ForceLayout> could not find layer "${targetLayerId}". Make sure the corresponding <GraphLayer> is mounted earlier in the JSX tree.`
568
+ );
569
+ return;
570
+ }
571
+ const layout = new D3ForceLayout$1(options);
572
+ if (fitPadding != null) {
573
+ layout.events.on("end", () => {
574
+ canvas.camera.fitContent(layer.getBounds(), fitPadding);
575
+ });
576
+ }
577
+ void layout.apply(layer);
578
+ return () => {
579
+ layout.stop();
580
+ };
581
+ }, [canvas, targetLayerId]);
582
+ return null;
583
+ }
584
+ var DEFAULT_ZOOM_STEP = 1.2;
585
+ function useCamera(canvas) {
586
+ const resolved = useResolvedCanvas(canvas);
587
+ return useMemo(() => {
588
+ const camera = resolved.camera;
589
+ return {
590
+ zoomIn: (factor = DEFAULT_ZOOM_STEP) => camera.zoomAt(factor),
591
+ zoomOut: (factor = DEFAULT_ZOOM_STEP) => camera.zoomAt(1 / factor),
592
+ setZoom: (scale) => camera.setZoom(scale),
593
+ zoomTo: (scale, centerX, centerY) => camera.zoomAt(scale / camera.scale, centerX, centerY),
594
+ pan: (dx, dy) => camera.pan(dx, dy),
595
+ fitContent: (worldRect, padding) => camera.fitContent(worldRect, padding),
596
+ getZoom: () => camera.scale
597
+ };
598
+ }, [resolved]);
599
+ }
600
+ function useZoom(canvas) {
601
+ const resolved = useResolvedCanvas(canvas);
602
+ const { zoomIn, zoomOut, setZoom, zoomTo } = useCamera(resolved);
603
+ const [zoom, setZoomState] = useState(() => resolved.camera.scale);
604
+ useEffect(() => {
605
+ setZoomState(resolved.camera.scale);
606
+ return resolved.events.on("camera:zoom", ({ scale }) => setZoomState(scale));
607
+ }, [resolved]);
608
+ return { zoom, zoomIn, zoomOut, setZoom, zoomTo };
609
+ }
610
+ var DEFAULT_FIT_PADDING = 80;
611
+ function hasGetBounds(layer) {
612
+ return typeof layer?.getBounds === "function";
613
+ }
614
+ function useFitContent(layerId, canvas) {
615
+ const resolved = useResolvedCanvas(canvas);
616
+ const [hasContent, setHasContent] = useState(() => resolved.layers.has(layerId));
617
+ useEffect(() => {
618
+ const sync = () => setHasContent(resolved.layers.has(layerId));
619
+ sync();
620
+ const offAdded = resolved.events.on("layer:added", sync);
621
+ const offRemoved = resolved.events.on("layer:removed", sync);
622
+ return () => {
623
+ offAdded();
624
+ offRemoved();
625
+ };
626
+ }, [resolved, layerId]);
627
+ const fitContent = useCallback(
628
+ (padding = DEFAULT_FIT_PADDING) => {
629
+ const layer = resolved.layers.get(layerId);
630
+ if (hasGetBounds(layer)) {
631
+ resolved.camera.fitContent(layer.getBounds(), padding);
632
+ }
633
+ },
634
+ [resolved, layerId]
635
+ );
636
+ return { fitContent, hasContent };
637
+ }
638
+ function useCanvasEvent(event, handler, canvas) {
639
+ const resolved = useResolvedCanvas(canvas);
640
+ const handlerRef = useRef(handler);
641
+ handlerRef.current = handler;
642
+ useEffect(() => {
643
+ return resolved.events.on(event, (payload) => handlerRef.current(payload));
644
+ }, [resolved, event]);
645
+ }
646
+ function hasClear(layer) {
647
+ return typeof layer?.clear === "function";
648
+ }
649
+ function useClearGraph(layerId, canvas) {
650
+ const resolved = useResolvedCanvas(canvas);
651
+ const history = useContext(HistoryContext);
652
+ const clear = useCallback(() => {
653
+ const layer = resolved.layers.get(layerId);
654
+ if (!hasClear(layer)) return;
655
+ const store = layer.store;
656
+ if (history && store) {
657
+ const ids = [...store.nodes()].map((n) => n.id);
658
+ if (ids.length > 0) {
659
+ history.transaction("clear", (rec) => {
660
+ for (const id of ids) rec.removeNode(id);
661
+ });
662
+ return;
663
+ }
664
+ }
665
+ layer.clear();
666
+ }, [resolved, layerId, history]);
667
+ return { clear };
668
+ }
669
+ function useSelection(options = {}, canvas) {
670
+ const { clickSelectId = "click-select" } = options;
671
+ const resolved = useResolvedCanvas(canvas);
672
+ const [selectedNodeIds, setNodeIds] = useState([]);
673
+ const [selectedEdgeIds, setEdgeIds] = useState([]);
674
+ useEffect(() => {
675
+ const behaviour = resolved.behaviours.get(clickSelectId);
676
+ if (!behaviour) {
677
+ setNodeIds([]);
678
+ setEdgeIds([]);
679
+ return;
680
+ }
681
+ setNodeIds(behaviour.getSelectedShapeIds());
682
+ setEdgeIds(behaviour.getSelectedConnectorIds());
683
+ return behaviour.events.on("selection:change", (snapshot) => {
684
+ setNodeIds(snapshot.shapeIds);
685
+ setEdgeIds(snapshot.connectorIds);
686
+ });
687
+ }, [resolved, clickSelectId]);
688
+ const clear = useCallback(() => {
689
+ resolved.behaviours.get(clickSelectId)?.clearSelection();
690
+ }, [resolved, clickSelectId]);
691
+ return {
692
+ selectedNodeIds,
693
+ selectedEdgeIds,
694
+ count: selectedNodeIds.length + selectedEdgeIds.length,
695
+ clear
696
+ };
697
+ }
698
+ function useInspectTarget(options = {}, canvas) {
699
+ const { inspectId = "click-inspect" } = options;
700
+ const resolved = useResolvedCanvas(canvas);
701
+ const [target, setTarget] = useState(null);
702
+ useEffect(() => {
703
+ const behaviour = resolved.behaviours.get(inspectId);
704
+ if (!behaviour) {
705
+ setTarget(null);
706
+ return;
707
+ }
708
+ setTarget(behaviour.getTarget());
709
+ return behaviour.events.on("inspect:change", setTarget);
710
+ }, [resolved, inspectId]);
711
+ return target;
712
+ }
713
+ function hasRedraw(layer) {
714
+ return typeof layer?.redraw === "function";
715
+ }
716
+ function useHistory(options = {}, canvas) {
717
+ const { layerId = "graph" } = options;
718
+ const resolved = useResolvedCanvas(canvas);
719
+ const history = useContext(HistoryContext);
720
+ const [state, setState] = useState({ canUndo: false, canRedo: false });
721
+ useEffect(() => {
722
+ if (!history) {
723
+ setState({ canUndo: false, canRedo: false });
724
+ return;
725
+ }
726
+ setState({ canUndo: history.canUndo, canRedo: history.canRedo });
727
+ return history.events.on("change", ({ canUndo, canRedo }) => setState({ canUndo, canRedo }));
728
+ }, [history]);
729
+ const undo = useCallback(() => history?.undo(), [history]);
730
+ const redo = useCallback(() => history?.redo(), [history]);
731
+ const redraw = useCallback(() => {
732
+ const layer = resolved.layers.get(layerId);
733
+ if (hasRedraw(layer)) layer.redraw();
734
+ }, [resolved, layerId]);
735
+ return { undo, redo, redraw, canUndo: state.canUndo, canRedo: state.canRedo };
736
+ }
737
+ function useClipboard(options = {}, canvas) {
738
+ const { clickSelectId = "click-select" } = options;
739
+ const resolved = useResolvedCanvas(canvas);
740
+ const clipboard = useContext(ClipboardContext);
741
+ const history = useContext(HistoryContext);
742
+ const { selectedNodeIds, selectedEdgeIds, count } = useSelection({ clickSelectId }, resolved);
743
+ const [canPaste, setCanPaste] = useState(false);
744
+ useEffect(() => {
745
+ if (!clipboard) {
746
+ setCanPaste(false);
747
+ return;
748
+ }
749
+ setCanPaste(clipboard.hasContent);
750
+ return clipboard.events.on("change", ({ hasContent }) => setCanPaste(hasContent));
751
+ }, [clipboard]);
752
+ const copy = useCallback(() => {
753
+ clipboard?.copy(selectedNodeIds, selectedEdgeIds);
754
+ }, [clipboard, selectedNodeIds, selectedEdgeIds]);
755
+ const cut = useCallback(() => {
756
+ clipboard?.cut(selectedNodeIds, selectedEdgeIds, history ?? void 0);
757
+ }, [clipboard, history, selectedNodeIds, selectedEdgeIds]);
758
+ const remove = useCallback(() => {
759
+ clipboard?.delete(selectedNodeIds, selectedEdgeIds, history ?? void 0);
760
+ }, [clipboard, history, selectedNodeIds, selectedEdgeIds]);
761
+ const paste = useCallback(() => {
762
+ if (!clipboard) return;
763
+ const { nodeIds, edgeIds } = clipboard.paste(history ?? void 0);
764
+ const behaviour = resolved.behaviours.get(clickSelectId);
765
+ behaviour?.selectMultiple([
766
+ ...nodeIds.map((id) => ({ id, type: "shape" })),
767
+ ...edgeIds.map((id) => ({ id, type: "connector" }))
768
+ ]);
769
+ }, [clipboard, history, resolved, clickSelectId]);
770
+ return { cut, copy, paste, remove, canPaste, hasSelection: count > 0 };
771
+ }
772
+ function useGrid(options = {}, canvas) {
773
+ const { backgroundLayerId = "background", patternType } = options;
774
+ const resolved = useResolvedCanvas(canvas);
775
+ const [showGrid, setShowGrid] = useState(false);
776
+ useEffect(() => {
777
+ const layer = resolved.layers.get(backgroundLayerId);
778
+ if (layer) setShowGrid(layer.getOptions().type === "pattern");
779
+ }, [resolved, backgroundLayerId]);
780
+ const setGrid = useCallback(
781
+ (on) => {
782
+ const layer = resolved.layers.get(backgroundLayerId);
783
+ if (!layer) return;
784
+ const next = { type: on ? "pattern" : "solid" };
785
+ if (on && patternType) next.patternType = patternType;
786
+ layer.setOptions(next);
787
+ setShowGrid(on);
788
+ },
789
+ [resolved, backgroundLayerId, patternType]
790
+ );
791
+ const toggleGrid = useCallback(() => setGrid(!showGrid), [setGrid, showGrid]);
792
+ return { showGrid, toggleGrid, setGrid };
793
+ }
794
+ function useTheme(options = {}, canvas) {
795
+ const { behaviourId = "responsive-theme", backgroundLayerId, onChange } = options;
796
+ const resolved = useResolvedCanvas(canvas);
797
+ const [mode, setModeState] = useState("auto");
798
+ const [kind, setKindState] = useState("light");
799
+ const getBehaviour = useCallback(
800
+ () => resolved.behaviours.get(behaviourId),
801
+ [resolved, behaviourId]
802
+ );
803
+ const getBackground = useCallback(
804
+ () => backgroundLayerId ? resolved.layers.get(backgroundLayerId) : void 0,
805
+ [resolved, backgroundLayerId]
806
+ );
807
+ const read = useCallback(() => {
808
+ const source = getBehaviour() ?? getBackground();
809
+ return {
810
+ mode: source?.getMode() ?? "auto",
811
+ kind: source?.getResolvedKind() ?? "light"
812
+ };
813
+ }, [getBehaviour, getBackground]);
814
+ useEffect(() => {
815
+ const { mode: m, kind: k } = read();
816
+ setModeState(m);
817
+ setKindState(k);
818
+ }, [read]);
819
+ const applyMode = useCallback(
820
+ (next) => {
821
+ getBehaviour()?.setMode(next);
822
+ getBackground()?.setMode(next);
823
+ const { mode: m, kind: k } = read();
824
+ setModeState(m);
825
+ setKindState(k);
826
+ onChange?.(k, m);
827
+ },
828
+ [getBehaviour, getBackground, read, onChange]
829
+ );
830
+ const setMode = useCallback((next) => applyMode(next), [applyMode]);
831
+ const toggle = useCallback(() => {
832
+ applyMode(read().kind === "dark" ? "light" : "dark");
833
+ }, [applyMode, read]);
834
+ return { mode, kind, setMode, toggle };
835
+ }
836
+ function useLayout(layouts, options = {}, canvas) {
837
+ const { layerId = "graph", fitPadding = 80 } = options;
838
+ const resolved = useResolvedCanvas(canvas);
839
+ const keys = Object.keys(layouts);
840
+ const [layout, setLayout] = useState(options.initial ?? keys[0] ?? "");
841
+ const [isRunning, setRunning] = useState(false);
842
+ const activeRef = useRef(null);
843
+ const applyLayout = useCallback(
844
+ (key) => {
845
+ const factory = layouts[key];
846
+ const layer = resolved.layers.get(layerId);
847
+ if (!factory || !layer) return;
848
+ activeRef.current?.stop?.();
849
+ const instance = factory();
850
+ activeRef.current = instance;
851
+ setLayout(key);
852
+ setRunning(true);
853
+ Promise.resolve(instance.apply(layer)).then(() => {
854
+ if (activeRef.current !== instance) return;
855
+ resolved.camera.fitContent(layer.getBounds(), fitPadding);
856
+ setRunning(false);
857
+ }).catch(() => {
858
+ if (activeRef.current === instance) setRunning(false);
859
+ });
860
+ },
861
+ [layouts, resolved, layerId, fitPadding]
862
+ );
863
+ const didInit = useRef(false);
864
+ const applyInitial = options.applyInitial ?? true;
865
+ useEffect(() => {
866
+ if (didInit.current || !applyInitial || !layout) return;
867
+ if (!resolved.layers.has(layerId)) return;
868
+ didInit.current = true;
869
+ applyLayout(layout);
870
+ }, [resolved, layerId, layout, applyLayout, applyInitial]);
871
+ const layoutOptions = options.labels ?? Object.fromEntries(keys.map((k) => [k, k]));
872
+ return { layout, layoutOptions, applyLayout, isRunning };
873
+ }
874
+ function useSelectMode(behaviourIds, options = {}, canvas) {
875
+ const resolved = useResolvedCanvas(canvas);
876
+ const keys = Object.keys(behaviourIds);
877
+ const [mode, setModeState] = useState(options.initial ?? keys[0] ?? "");
878
+ const modeRef = useRef(mode);
879
+ modeRef.current = mode;
880
+ const setMode = useCallback(
881
+ (next) => {
882
+ for (const [key, id] of Object.entries(behaviourIds)) {
883
+ const behaviour = resolved.behaviours.get(id);
884
+ if (!behaviour) continue;
885
+ if (key === next) behaviour.enable();
886
+ else behaviour.disable();
887
+ }
888
+ setModeState(next);
889
+ },
890
+ [resolved, behaviourIds]
891
+ );
892
+ useEffect(() => {
893
+ setMode(modeRef.current);
894
+ }, [setMode]);
895
+ const modeOptions = options.labels ?? Object.fromEntries(keys.map((k) => [k, k]));
896
+ return { mode, modeOptions, setMode };
897
+ }
898
+ var DEFAULT_EDGE_TYPES = [
899
+ "straight",
900
+ "orth",
901
+ "bezier",
902
+ "rounded",
903
+ "smooth"
904
+ ];
905
+ var DEFAULT_EDGE_TYPE_LABELS = {
906
+ straight: "Straight",
907
+ orth: "Orthogonal",
908
+ bezier: "Curved",
909
+ quadratic: "Quadratic",
910
+ rounded: "Rounded",
911
+ smooth: "Smooth",
912
+ manhattan: "Manhattan",
913
+ "bump-radial": "Bump (radial)",
914
+ "bump-horizontal": "Bump (horizontal)",
915
+ "step-radial": "Step (radial)",
916
+ bundle: "Bundled"
917
+ };
918
+ function useEdgeType(options = {}, canvas) {
919
+ const { layerId = "graph", initial, types = DEFAULT_EDGE_TYPES, labels } = options;
920
+ const resolved = useResolvedCanvas(canvas);
921
+ const [edgeType, setEdgeTypeState] = useState(initial ?? types[0] ?? "straight");
922
+ useEffect(() => {
923
+ if (initial) return;
924
+ const layer = resolved.layers.get(layerId);
925
+ const current = layer?.edgeDefaults;
926
+ const shape = current && typeof current === "object" ? current.shape : void 0;
927
+ const pathType = shape && typeof shape === "object" ? shape.pathType : void 0;
928
+ if (pathType) setEdgeTypeState(pathType);
929
+ }, [resolved, layerId, initial]);
930
+ const setEdgeType = useCallback(
931
+ (next) => {
932
+ const layer = resolved.layers.get(layerId);
933
+ if (!layer) return;
934
+ const prevShape = layer.edgeDefaults?.shape;
935
+ const baseShape = prevShape && typeof prevShape === "object" ? prevShape : {};
936
+ const shape = { ...baseShape, pathType: next };
937
+ layer.setEdgeDefaults({ shape });
938
+ setEdgeTypeState(next);
939
+ },
940
+ [resolved, layerId]
941
+ );
942
+ const edgeTypeOptions = labels ?? Object.fromEntries(types.map((t) => [t, DEFAULT_EDGE_TYPE_LABELS[t] ?? t]));
943
+ return { edgeType, edgeTypeOptions, setEdgeType };
944
+ }
945
+ var DEFAULT_LOCK_IDS = ["pan", "drag-node"];
946
+ function useLock(options = {}, canvas) {
947
+ const { behaviourIds = DEFAULT_LOCK_IDS, initialLocked = false } = options;
948
+ const resolved = useResolvedCanvas(canvas);
949
+ const [locked, setLocked] = useState(initialLocked);
950
+ const setLock = useCallback(
951
+ (next) => {
952
+ for (const id of behaviourIds) {
953
+ const behaviour = resolved.behaviours.get(id);
954
+ if (!behaviour) continue;
955
+ if (next) behaviour.disable();
956
+ else behaviour.enable();
957
+ }
958
+ setLocked(next);
959
+ },
960
+ [resolved, behaviourIds]
961
+ );
962
+ const toggleLock = useCallback(() => setLock(!locked), [setLock, locked]);
963
+ return { locked, toggleLock, setLock };
964
+ }
965
+ function useTool() {
966
+ const ctx = useContext(ToolContext);
967
+ if (!ctx) {
968
+ throw new Error("useTool must be used within a <GraphToolProvider>.");
969
+ }
970
+ return ctx;
971
+ }
972
+ function useDrawHistory() {
973
+ const history = useContext(HistoryContext);
974
+ const ref = useRef(history);
975
+ ref.current = history;
976
+ const push = useCallback((op, label) => {
977
+ ref.current?.push({ ops: [op], label });
978
+ }, []);
979
+ const onNodeCreate = useCallback(
980
+ (node) => push({ kind: "addNode", node }, "add node"),
981
+ [push]
982
+ );
983
+ const onEdgeCreate = useCallback(
984
+ (edge) => push({ kind: "addEdge", edge }, "connect"),
985
+ [push]
986
+ );
987
+ const onErase = useCallback(
988
+ (removed) => {
989
+ const op = removed.kind === "node" ? { kind: "removeNode", node: removed.node, edges: removed.edges } : { kind: "removeEdge", edge: removed.edge };
990
+ push(op, "delete");
991
+ },
992
+ [push]
993
+ );
994
+ return { onNodeCreate, onEdgeCreate, onErase };
995
+ }
996
+ function toStringMap(data) {
997
+ if (!data || typeof data !== "object") return {};
998
+ const out = {};
999
+ for (const [k, v] of Object.entries(data)) {
1000
+ if (v === null || v === void 0) continue;
1001
+ out[k] = typeof v === "string" ? v : typeof v === "object" ? JSON.stringify(v) : String(v);
1002
+ }
1003
+ return out;
1004
+ }
1005
+ function useEntityEditor(options = {}, canvas) {
1006
+ const { layerId = "graph", inspectId = "click-inspect", typeAsLabel = false } = options;
1007
+ const resolved = useResolvedCanvas(canvas);
1008
+ const history = useContext(HistoryContext);
1009
+ const single = useInspectTarget({ inspectId }, canvas);
1010
+ if (!single) return null;
1011
+ const layer = resolved.layers.get(layerId);
1012
+ const store = layer?.store;
1013
+ if (!layer || !store) return null;
1014
+ if (single.kind === "node") {
1015
+ const node = store.getNode(single.id);
1016
+ if (!node) return null;
1017
+ const label = layer.resolveNodeStyle(node).labelText ?? "";
1018
+ if (typeAsLabel) {
1019
+ const type2 = node.type ?? label;
1020
+ const commit3 = ({ type: nextType, data }) => {
1021
+ const value = nextType ?? "";
1022
+ const prior = node.style ?? {};
1023
+ const patch = { type: value, style: { ...prior, labelText: value }, data };
1024
+ if (history) history.transaction("edit node", (rec) => rec.updateNode(single.id, patch));
1025
+ else store.updateNode(single.id, patch);
1026
+ };
1027
+ return { kind: "node", id: single.id, label: "", type: type2, data: toStringMap(node.data), commit: commit3 };
1028
+ }
1029
+ const commit2 = ({ label: nextLabel, data }) => {
1030
+ const prior = node.style ?? {};
1031
+ const patch = { style: { ...prior, labelText: nextLabel }, data };
1032
+ if (history) history.transaction("edit node", (rec) => rec.updateNode(single.id, patch));
1033
+ else store.updateNode(single.id, patch);
1034
+ };
1035
+ return { kind: "node", id: single.id, label, data: toStringMap(node.data), commit: commit2 };
1036
+ }
1037
+ const edge = store.getEdge(single.id);
1038
+ if (!edge) return null;
1039
+ const edgeLabel = layer.resolveEdgeStyle(edge).labelText ?? "";
1040
+ const type = edge.type ?? (typeAsLabel ? edgeLabel : "");
1041
+ const commit = ({ type: nextType, data }) => {
1042
+ const value = nextType ?? "";
1043
+ const patch = { data };
1044
+ if (nextType !== void 0) patch.type = value;
1045
+ if (typeAsLabel) {
1046
+ const prior = edge.style ?? {};
1047
+ patch.style = { ...prior, labelText: value };
1048
+ }
1049
+ if (history) history.transaction("edit edge", (rec) => rec.updateEdge(single.id, patch));
1050
+ else store.updateEdge(single.id, patch);
1051
+ };
1052
+ const reverse = () => {
1053
+ const swap = { source: edge.target, target: edge.source };
1054
+ if (history) history.transaction("reverse edge", (rec) => rec.updateEdge(single.id, swap));
1055
+ else store.updateEdge(single.id, swap);
1056
+ };
1057
+ return {
1058
+ kind: "edge",
1059
+ id: single.id,
1060
+ label: "",
1061
+ type,
1062
+ data: toStringMap(edge.data),
1063
+ commit,
1064
+ reverse
1065
+ };
1066
+ }
1067
+ function useContextMenu() {
1068
+ const [menu, setMenu] = useState(null);
1069
+ const close = useCallback(() => setMenu(null), []);
1070
+ const open = useCallback((x, y, items) => {
1071
+ setMenu({ x, y, items });
1072
+ }, []);
1073
+ useEffect(() => {
1074
+ if (!menu) return;
1075
+ const onKey = (ev) => {
1076
+ if (ev.key === "Escape") close();
1077
+ };
1078
+ window.addEventListener("pointerdown", close);
1079
+ window.addEventListener("keydown", onKey);
1080
+ return () => {
1081
+ window.removeEventListener("pointerdown", close);
1082
+ window.removeEventListener("keydown", onKey);
1083
+ };
1084
+ }, [menu, close]);
1085
+ return { menu, open, close };
1086
+ }
1087
+ function pinStyle(position, offset) {
1088
+ const [vertical, horizontal] = position.split("-");
1089
+ const style = { position: "absolute", [vertical]: offset };
1090
+ if (horizontal === "center") {
1091
+ style.left = "50%";
1092
+ style.transform = "translateX(-50%)";
1093
+ } else {
1094
+ style[horizontal] = offset;
1095
+ }
1096
+ return style;
1097
+ }
1098
+ function Panel({
1099
+ position = "top-left",
1100
+ orientation = "vertical",
1101
+ offset = 8,
1102
+ gap = 4,
1103
+ zIndex = 5,
1104
+ className,
1105
+ style,
1106
+ children
1107
+ }) {
1108
+ return /* @__PURE__ */ jsx("div", { style: { ...pinStyle(position, offset), zIndex, pointerEvents: "none" }, children: /* @__PURE__ */ jsx(
1109
+ "div",
1110
+ {
1111
+ className,
1112
+ style: {
1113
+ display: "flex",
1114
+ flexDirection: orientation === "vertical" ? "column" : "row",
1115
+ gap,
1116
+ pointerEvents: "auto",
1117
+ ...style
1118
+ },
1119
+ children
1120
+ }
1121
+ ) });
1122
+ }
1123
+ function Tooltipped({ label, side, delayDuration = 0, children }) {
1124
+ if (label == null || label === "") return children;
1125
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1126
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children }),
1127
+ /* @__PURE__ */ jsx(TooltipContent, { side, style: { backgroundColor: "var(--color-popover)" }, children: label })
1128
+ ] }) });
1129
+ }
1130
+ function ControlButton({
1131
+ icon: Icon,
1132
+ onClick,
1133
+ title,
1134
+ tooltipSide,
1135
+ active = false,
1136
+ disabled = false,
1137
+ className
1138
+ }) {
1139
+ return /* @__PURE__ */ jsx(Tooltipped, { label: title, side: tooltipSide, children: /* @__PURE__ */ jsx(
1140
+ Button,
1141
+ {
1142
+ variant: active ? "default" : "ghost",
1143
+ size: "icon",
1144
+ "aria-label": title,
1145
+ disabled,
1146
+ onClick,
1147
+ className,
1148
+ children: /* @__PURE__ */ jsx(Icon, { size: 16 })
1149
+ }
1150
+ ) });
1151
+ }
1152
+ function PropertiesEditor({
1153
+ title,
1154
+ defaults,
1155
+ onSubmit,
1156
+ submitLabel = "Apply",
1157
+ showLabel = true,
1158
+ showType = false,
1159
+ onReverse,
1160
+ className
1161
+ }) {
1162
+ const [label, setLabel] = useState(defaults?.label ?? "");
1163
+ const [type, setType] = useState(defaults?.type ?? "");
1164
+ const [rows, setRows] = useState(
1165
+ () => Object.entries(defaults?.data ?? {}).map(([k, v]) => ({ k, v }))
1166
+ );
1167
+ const setRow = (i, patch) => setRows((rs) => rs.map((r, j) => j === i ? { ...r, ...patch } : r));
1168
+ const removeRow = (i) => setRows((rs) => rs.filter((_, j) => j !== i));
1169
+ const addRow = () => setRows((rs) => [...rs, { k: "", v: "" }]);
1170
+ const apply = () => {
1171
+ const data = {};
1172
+ for (const { k, v } of rows) {
1173
+ const key = k.trim();
1174
+ if (key) data[key] = v;
1175
+ }
1176
+ onSubmit(showType ? { label, type, data } : { label, data });
1177
+ };
1178
+ return /* @__PURE__ */ jsxs("div", { className, style: cardStyle, children: [
1179
+ title && /* @__PURE__ */ jsx("div", { style: titleStyle, children: title }),
1180
+ showLabel && /* @__PURE__ */ jsxs("label", { style: fieldStyle, children: [
1181
+ /* @__PURE__ */ jsx("span", { style: captionStyle, children: "Label" }),
1182
+ /* @__PURE__ */ jsx(
1183
+ "input",
1184
+ {
1185
+ style: inputStyle,
1186
+ value: label,
1187
+ placeholder: "Label text",
1188
+ onChange: (e) => setLabel(e.target.value)
1189
+ }
1190
+ )
1191
+ ] }),
1192
+ showType && /* @__PURE__ */ jsxs("label", { style: fieldStyle, children: [
1193
+ /* @__PURE__ */ jsx("span", { style: captionStyle, children: "Type" }),
1194
+ /* @__PURE__ */ jsx(
1195
+ "input",
1196
+ {
1197
+ style: inputStyle,
1198
+ value: type,
1199
+ placeholder: "Type tag",
1200
+ onChange: (e) => setType(e.target.value)
1201
+ }
1202
+ )
1203
+ ] }),
1204
+ /* @__PURE__ */ jsxs("div", { style: fieldStyle, children: [
1205
+ /* @__PURE__ */ jsx("span", { style: captionStyle, children: "Properties" }),
1206
+ rows.length === 0 && /* @__PURE__ */ jsx("span", { style: emptyStyle, children: "No properties yet." }),
1207
+ rows.map((row, i) => /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
1208
+ /* @__PURE__ */ jsx(
1209
+ "input",
1210
+ {
1211
+ style: inputStyle,
1212
+ value: row.k,
1213
+ placeholder: "key",
1214
+ onChange: (e) => setRow(i, { k: e.target.value })
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ jsx(
1218
+ "input",
1219
+ {
1220
+ style: inputStyle,
1221
+ value: row.v,
1222
+ placeholder: "value",
1223
+ onChange: (e) => setRow(i, { v: e.target.value })
1224
+ }
1225
+ ),
1226
+ /* @__PURE__ */ jsx(
1227
+ Button,
1228
+ {
1229
+ variant: "ghost",
1230
+ size: "icon",
1231
+ "aria-label": "Remove field",
1232
+ onClick: () => removeRow(i),
1233
+ children: "\u2715"
1234
+ }
1235
+ )
1236
+ ] }, i)),
1237
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: addRow, children: "Add field" }) })
1238
+ ] }),
1239
+ /* @__PURE__ */ jsxs(
1240
+ "div",
1241
+ {
1242
+ style: {
1243
+ display: "flex",
1244
+ justifyContent: onReverse ? "space-between" : "flex-end",
1245
+ alignItems: "center"
1246
+ },
1247
+ children: [
1248
+ onReverse && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onReverse, children: "Reverse direction" }),
1249
+ /* @__PURE__ */ jsx(Button, { onClick: apply, children: submitLabel })
1250
+ ]
1251
+ }
1252
+ )
1253
+ ] });
1254
+ }
1255
+ var cardStyle = {
1256
+ display: "flex",
1257
+ flexDirection: "column",
1258
+ gap: 12,
1259
+ padding: 12,
1260
+ minWidth: 240,
1261
+ background: "var(--color-popover)",
1262
+ color: "var(--color-popover-foreground)",
1263
+ border: "1px solid var(--color-border)",
1264
+ borderRadius: 8,
1265
+ boxShadow: "0 4px 16px rgba(0,0,0,0.18)"
1266
+ };
1267
+ var titleStyle = { fontSize: 13, fontWeight: 600, opacity: 0.85 };
1268
+ var fieldStyle = { display: "flex", flexDirection: "column", gap: 6 };
1269
+ var captionStyle = { fontSize: 12, fontWeight: 500, opacity: 0.8 };
1270
+ var rowStyle = { display: "flex", gap: 6, alignItems: "center" };
1271
+ var emptyStyle = { fontSize: 12, opacity: 0.6 };
1272
+ var inputStyle = {
1273
+ flex: 1,
1274
+ width: "100%",
1275
+ height: 28,
1276
+ padding: "0 8px",
1277
+ fontSize: 13,
1278
+ color: "var(--color-foreground)",
1279
+ background: "var(--color-background)",
1280
+ border: "1px solid var(--color-border)",
1281
+ borderRadius: 6,
1282
+ outline: "none"
1283
+ };
1284
+ function OptionPicker({
1285
+ label,
1286
+ value,
1287
+ options,
1288
+ icons,
1289
+ onChange,
1290
+ align = "start",
1291
+ tooltip,
1292
+ tooltipSide,
1293
+ className
1294
+ }) {
1295
+ const ActiveIcon = icons?.[value];
1296
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1297
+ /* @__PURE__ */ jsx(Tooltipped, { label: tooltip ?? label, side: tooltipSide, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1298
+ Button,
1299
+ {
1300
+ variant: "outline",
1301
+ size: "sm",
1302
+ className: ["ring-offset-background", className].filter(Boolean).join(" "),
1303
+ children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1304
+ ActiveIcon && /* @__PURE__ */ jsx(ActiveIcon, { size: 16 }),
1305
+ label,
1306
+ ": ",
1307
+ options[value] ?? value
1308
+ ] })
1309
+ }
1310
+ ) }) }),
1311
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align, style: { backgroundColor: "var(--color-popover)" }, children: [
1312
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: label }),
1313
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value, onValueChange: onChange, children: Object.keys(options).map((key) => {
1314
+ const Icon = icons?.[key];
1315
+ return /* @__PURE__ */ jsx(DropdownMenuRadioItem, { value: key, children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1316
+ Icon && /* @__PURE__ */ jsx(Icon, { size: 14 }),
1317
+ options[key] ?? key
1318
+ ] }) }, key);
1319
+ }) })
1320
+ ] })
1321
+ ] });
1322
+ }
1323
+ function ZoomControls({
1324
+ canvas,
1325
+ zoomInIcon: ZoomInIcon,
1326
+ zoomOutIcon: ZoomOutIcon,
1327
+ showLevel = false,
1328
+ orientation = "horizontal",
1329
+ zoomLevel,
1330
+ tooltipSide,
1331
+ className
1332
+ }) {
1333
+ const { zoom, zoomIn, zoomOut } = useZoom(canvas);
1334
+ const resolvedLevel = zoomLevel != null ? zoomLevel : showLevel ? `${Math.round(zoom * 100)}%` : void 0;
1335
+ const zoomInBtn = /* @__PURE__ */ jsx(Tooltipped, { label: "Zoom in", side: tooltipSide, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Zoom in", onClick: () => zoomIn(), children: /* @__PURE__ */ jsx(ZoomInIcon, { size: 16 }) }) });
1336
+ const zoomOutBtn = /* @__PURE__ */ jsx(Tooltipped, { label: "Zoom out", side: tooltipSide, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Zoom out", onClick: () => zoomOut(), children: /* @__PURE__ */ jsx(ZoomOutIcon, { size: 16 }) }) });
1337
+ const level = resolvedLevel != null ? /* @__PURE__ */ jsx(
1338
+ "span",
1339
+ {
1340
+ style: {
1341
+ fontSize: 12,
1342
+ fontVariantNumeric: "tabular-nums",
1343
+ opacity: 0.8,
1344
+ textAlign: "center",
1345
+ minWidth: 36
1346
+ },
1347
+ children: resolvedLevel
1348
+ }
1349
+ ) : null;
1350
+ return /* @__PURE__ */ jsx(
1351
+ "div",
1352
+ {
1353
+ className,
1354
+ style: {
1355
+ display: "flex",
1356
+ flexDirection: orientation === "vertical" ? "column" : "row",
1357
+ alignItems: "center",
1358
+ gap: 4
1359
+ },
1360
+ children: orientation === "horizontal" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1361
+ zoomOutBtn,
1362
+ level,
1363
+ zoomInBtn
1364
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1365
+ zoomInBtn,
1366
+ level,
1367
+ zoomOutBtn
1368
+ ] })
1369
+ }
1370
+ );
1371
+ }
1372
+ function LockToggle({
1373
+ locked,
1374
+ onToggle,
1375
+ lockedIcon: LockedIcon,
1376
+ unlockedIcon: UnlockedIcon,
1377
+ tooltipSide,
1378
+ className
1379
+ }) {
1380
+ const Icon = locked ? LockedIcon : UnlockedIcon;
1381
+ const label = locked ? "Unlock view" : "Lock view";
1382
+ return /* @__PURE__ */ jsx(Tooltipped, { label, side: tooltipSide, children: /* @__PURE__ */ jsx(
1383
+ Button,
1384
+ {
1385
+ variant: locked ? "default" : "ghost",
1386
+ size: "icon",
1387
+ "aria-label": label,
1388
+ onClick: onToggle,
1389
+ className,
1390
+ children: /* @__PURE__ */ jsx(Icon, { size: 16 })
1391
+ }
1392
+ ) });
1393
+ }
1394
+ function ClearButton({
1395
+ icon: Icon,
1396
+ canvas,
1397
+ layerId = "graph",
1398
+ label = "Clear",
1399
+ tooltipSide,
1400
+ className
1401
+ }) {
1402
+ const { clear } = useClearGraph(layerId, canvas);
1403
+ return /* @__PURE__ */ jsx(Tooltipped, { label, side: tooltipSide, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => clear(), className, children: [
1404
+ Icon ? /* @__PURE__ */ jsx(Icon, { size: 16 }) : null,
1405
+ label
1406
+ ] }) });
1407
+ }
1408
+ function FitContentButton({
1409
+ icon: Icon,
1410
+ canvas,
1411
+ layerId = "graph",
1412
+ title = "Fit to content",
1413
+ tooltipSide,
1414
+ className
1415
+ }) {
1416
+ const { fitContent } = useFitContent(layerId, canvas);
1417
+ return /* @__PURE__ */ jsx(Tooltipped, { label: title, side: tooltipSide, children: /* @__PURE__ */ jsx(
1418
+ Button,
1419
+ {
1420
+ variant: "ghost",
1421
+ size: "icon",
1422
+ "aria-label": title,
1423
+ onClick: () => fitContent(),
1424
+ className,
1425
+ children: /* @__PURE__ */ jsx(Icon, { size: 16 })
1426
+ }
1427
+ ) });
1428
+ }
1429
+ var DEFAULT_PRESETS = [25, 50, 75, 100, 125, 150, 200, 300, 400];
1430
+ function ZoomPicker({
1431
+ canvas,
1432
+ layerId = "graph",
1433
+ presets = DEFAULT_PRESETS,
1434
+ showFit = true,
1435
+ fitLabel = "Fit / Reset View",
1436
+ title = "Zoom level",
1437
+ tooltipSide,
1438
+ className
1439
+ }) {
1440
+ const { zoom, setZoom } = useZoom(canvas);
1441
+ const { fitContent } = useFitContent(layerId, canvas);
1442
+ const currentPct = String(Math.round(zoom * 100));
1443
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1444
+ /* @__PURE__ */ jsx(Tooltipped, { label: title, side: tooltipSide, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", "aria-label": title, className, children: [
1445
+ currentPct,
1446
+ "% \u25BE"
1447
+ ] }) }) }),
1448
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { style: { backgroundColor: "var(--color-popover)" }, children: [
1449
+ showFit && /* @__PURE__ */ jsxs(Fragment, { children: [
1450
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => fitContent(), children: fitLabel }),
1451
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
1452
+ ] }),
1453
+ /* @__PURE__ */ jsx(
1454
+ DropdownMenuRadioGroup,
1455
+ {
1456
+ value: currentPct,
1457
+ onValueChange: (v) => setZoom(Number(v) / 100),
1458
+ children: presets.map((pct) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, { value: String(pct), children: [
1459
+ pct,
1460
+ "%"
1461
+ ] }, pct))
1462
+ }
1463
+ )
1464
+ ] })
1465
+ ] });
1466
+ }
1467
+ function UndoButton({ icon, title = "Undo", tooltipSide, canvas, className }) {
1468
+ const { undo, canUndo } = useHistory({}, canvas);
1469
+ return /* @__PURE__ */ jsx(
1470
+ ControlButton,
1471
+ {
1472
+ icon,
1473
+ title,
1474
+ tooltipSide,
1475
+ onClick: undo,
1476
+ disabled: !canUndo,
1477
+ className
1478
+ }
1479
+ );
1480
+ }
1481
+ function RedoButton({ icon, title = "Redo", tooltipSide, canvas, className }) {
1482
+ const { redo, canRedo } = useHistory({}, canvas);
1483
+ return /* @__PURE__ */ jsx(
1484
+ ControlButton,
1485
+ {
1486
+ icon,
1487
+ title,
1488
+ tooltipSide,
1489
+ onClick: redo,
1490
+ disabled: !canRedo,
1491
+ className
1492
+ }
1493
+ );
1494
+ }
1495
+ function RedrawButton({
1496
+ icon,
1497
+ title = "Redraw",
1498
+ tooltipSide,
1499
+ layerId = "graph",
1500
+ canvas,
1501
+ className
1502
+ }) {
1503
+ const { redraw } = useHistory({ layerId }, canvas);
1504
+ return /* @__PURE__ */ jsx(
1505
+ ControlButton,
1506
+ {
1507
+ icon,
1508
+ title,
1509
+ tooltipSide,
1510
+ onClick: redraw,
1511
+ className
1512
+ }
1513
+ );
1514
+ }
1515
+ function CutButton({
1516
+ icon,
1517
+ title = "Cut",
1518
+ tooltipSide,
1519
+ clickSelectId,
1520
+ canvas,
1521
+ className
1522
+ }) {
1523
+ const { cut, hasSelection } = useClipboard(
1524
+ clickSelectId ? { clickSelectId } : {},
1525
+ canvas
1526
+ );
1527
+ return /* @__PURE__ */ jsx(
1528
+ ControlButton,
1529
+ {
1530
+ icon,
1531
+ title,
1532
+ tooltipSide,
1533
+ onClick: cut,
1534
+ disabled: !hasSelection,
1535
+ className
1536
+ }
1537
+ );
1538
+ }
1539
+ function CopyButton({
1540
+ icon,
1541
+ title = "Copy",
1542
+ tooltipSide,
1543
+ clickSelectId,
1544
+ canvas,
1545
+ className
1546
+ }) {
1547
+ const { copy, hasSelection } = useClipboard(
1548
+ clickSelectId ? { clickSelectId } : {},
1549
+ canvas
1550
+ );
1551
+ return /* @__PURE__ */ jsx(
1552
+ ControlButton,
1553
+ {
1554
+ icon,
1555
+ title,
1556
+ tooltipSide,
1557
+ onClick: copy,
1558
+ disabled: !hasSelection,
1559
+ className
1560
+ }
1561
+ );
1562
+ }
1563
+ function PasteButton({
1564
+ icon,
1565
+ title = "Paste",
1566
+ tooltipSide,
1567
+ clickSelectId,
1568
+ canvas,
1569
+ className
1570
+ }) {
1571
+ const { paste, canPaste } = useClipboard(
1572
+ clickSelectId ? { clickSelectId } : {},
1573
+ canvas
1574
+ );
1575
+ return /* @__PURE__ */ jsx(
1576
+ ControlButton,
1577
+ {
1578
+ icon,
1579
+ title,
1580
+ tooltipSide,
1581
+ onClick: paste,
1582
+ disabled: !canPaste,
1583
+ className
1584
+ }
1585
+ );
1586
+ }
1587
+ function DeleteSelectionButton({
1588
+ icon,
1589
+ title = "Delete selection",
1590
+ tooltipSide,
1591
+ clickSelectId,
1592
+ canvas,
1593
+ className
1594
+ }) {
1595
+ const { remove, hasSelection } = useClipboard(
1596
+ clickSelectId ? { clickSelectId } : {},
1597
+ canvas
1598
+ );
1599
+ return /* @__PURE__ */ jsx(
1600
+ ControlButton,
1601
+ {
1602
+ icon,
1603
+ title,
1604
+ tooltipSide,
1605
+ onClick: remove,
1606
+ disabled: !hasSelection,
1607
+ className
1608
+ }
1609
+ );
1610
+ }
1611
+ function GridToggle({
1612
+ icon,
1613
+ title = "Toggle grid",
1614
+ tooltipSide,
1615
+ backgroundLayerId,
1616
+ patternType,
1617
+ canvas,
1618
+ className
1619
+ }) {
1620
+ const { showGrid, toggleGrid } = useGrid(
1621
+ { ...backgroundLayerId ? { backgroundLayerId } : {}, ...patternType ? { patternType } : {} },
1622
+ canvas
1623
+ );
1624
+ return /* @__PURE__ */ jsx(
1625
+ ControlButton,
1626
+ {
1627
+ icon,
1628
+ title,
1629
+ tooltipSide,
1630
+ onClick: toggleGrid,
1631
+ active: showGrid,
1632
+ className
1633
+ }
1634
+ );
1635
+ }
1636
+ function ThemeToggle({
1637
+ lightIcon,
1638
+ darkIcon,
1639
+ title,
1640
+ tooltipSide,
1641
+ behaviourId,
1642
+ backgroundLayerId,
1643
+ onChange,
1644
+ canvas,
1645
+ className
1646
+ }) {
1647
+ const { kind, toggle } = useTheme(
1648
+ {
1649
+ ...behaviourId ? { behaviourId } : {},
1650
+ ...backgroundLayerId ? { backgroundLayerId } : {},
1651
+ ...onChange ? { onChange } : {}
1652
+ },
1653
+ canvas
1654
+ );
1655
+ const dark = kind === "dark";
1656
+ return /* @__PURE__ */ jsx(
1657
+ ControlButton,
1658
+ {
1659
+ icon: dark ? darkIcon : lightIcon,
1660
+ title: title ?? (dark ? "Switch to light theme" : "Switch to dark theme"),
1661
+ tooltipSide,
1662
+ onClick: toggle,
1663
+ active: dark,
1664
+ className
1665
+ }
1666
+ );
1667
+ }
1668
+ function LockButton({
1669
+ lockedIcon,
1670
+ unlockedIcon,
1671
+ behaviourIds,
1672
+ initialLocked,
1673
+ tooltipSide,
1674
+ canvas,
1675
+ className
1676
+ }) {
1677
+ const { locked, toggleLock } = useLock(
1678
+ {
1679
+ ...behaviourIds ? { behaviourIds } : {},
1680
+ ...initialLocked !== void 0 ? { initialLocked } : {}
1681
+ },
1682
+ canvas
1683
+ );
1684
+ return /* @__PURE__ */ jsx(
1685
+ LockToggle,
1686
+ {
1687
+ locked,
1688
+ onToggle: toggleLock,
1689
+ lockedIcon,
1690
+ unlockedIcon,
1691
+ tooltipSide,
1692
+ className
1693
+ }
1694
+ );
1695
+ }
1696
+ function LayoutPicker({
1697
+ layouts,
1698
+ label = "Layout",
1699
+ layerId,
1700
+ fitPadding,
1701
+ initial,
1702
+ labels,
1703
+ align,
1704
+ tooltipSide,
1705
+ canvas,
1706
+ className
1707
+ }) {
1708
+ const { layout, layoutOptions, applyLayout } = useLayout(
1709
+ layouts,
1710
+ {
1711
+ ...layerId ? { layerId } : {},
1712
+ ...fitPadding !== void 0 ? { fitPadding } : {},
1713
+ ...initial ? { initial } : {},
1714
+ ...labels ? { labels } : {}
1715
+ },
1716
+ canvas
1717
+ );
1718
+ return /* @__PURE__ */ jsx(
1719
+ OptionPicker,
1720
+ {
1721
+ label,
1722
+ value: layout,
1723
+ options: layoutOptions,
1724
+ onChange: applyLayout,
1725
+ align,
1726
+ tooltipSide,
1727
+ className
1728
+ }
1729
+ );
1730
+ }
1731
+ function SelectModePicker({
1732
+ behaviourIds,
1733
+ label = "Select",
1734
+ initial,
1735
+ labels,
1736
+ icons,
1737
+ align,
1738
+ tooltipSide,
1739
+ canvas,
1740
+ className
1741
+ }) {
1742
+ const { mode, modeOptions, setMode } = useSelectMode(
1743
+ behaviourIds,
1744
+ { ...initial ? { initial } : {}, ...labels ? { labels } : {} },
1745
+ canvas
1746
+ );
1747
+ return /* @__PURE__ */ jsx(
1748
+ OptionPicker,
1749
+ {
1750
+ label,
1751
+ value: mode,
1752
+ options: modeOptions,
1753
+ icons,
1754
+ onChange: setMode,
1755
+ align,
1756
+ tooltipSide,
1757
+ className
1758
+ }
1759
+ );
1760
+ }
1761
+ function EdgeTypePicker({
1762
+ layerId,
1763
+ label = "Edge",
1764
+ initial,
1765
+ types,
1766
+ labels,
1767
+ icons,
1768
+ align,
1769
+ tooltipSide,
1770
+ canvas,
1771
+ className
1772
+ }) {
1773
+ const { edgeType, edgeTypeOptions, setEdgeType } = useEdgeType(
1774
+ {
1775
+ ...layerId ? { layerId } : {},
1776
+ ...initial ? { initial } : {},
1777
+ ...types ? { types } : {},
1778
+ ...labels ? { labels } : {}
1779
+ },
1780
+ canvas
1781
+ );
1782
+ return /* @__PURE__ */ jsx(
1783
+ OptionPicker,
1784
+ {
1785
+ label,
1786
+ value: edgeType,
1787
+ options: edgeTypeOptions,
1788
+ icons,
1789
+ onChange: setEdgeType,
1790
+ align,
1791
+ tooltipSide,
1792
+ className
1793
+ }
1794
+ );
1795
+ }
1796
+ function ContextMenuOverlay({ x, y, items, zIndex = 1e3, style }) {
1797
+ return /* @__PURE__ */ jsx(
1798
+ "div",
1799
+ {
1800
+ style: { position: "absolute", left: x, top: y, zIndex, ...style },
1801
+ onPointerDown: (ev) => ev.stopPropagation(),
1802
+ onContextMenu: (ev) => ev.preventDefault(),
1803
+ children: /* @__PURE__ */ jsx(NestedMenu, { menuItems: items })
1804
+ }
1805
+ );
1806
+ }
1807
+ function CanvasControlsToolbar({
1808
+ position = "bottom-left",
1809
+ orientation = "vertical",
1810
+ fitLayerId = "graph",
1811
+ showZoom = true,
1812
+ showZoomLevel = false,
1813
+ showFit = true,
1814
+ icons,
1815
+ locked,
1816
+ onToggleLock,
1817
+ bare = false,
1818
+ canvas,
1819
+ children,
1820
+ className
1821
+ }) {
1822
+ const showLock = locked !== void 0 && onToggleLock && icons.locked && icons.unlocked;
1823
+ const controls = /* @__PURE__ */ jsxs(Fragment, { children: [
1824
+ showZoom && /* @__PURE__ */ jsx(
1825
+ ZoomControls,
1826
+ {
1827
+ orientation,
1828
+ canvas,
1829
+ zoomInIcon: icons.zoomIn,
1830
+ zoomOutIcon: icons.zoomOut,
1831
+ showLevel: showZoomLevel
1832
+ }
1833
+ ),
1834
+ showFit && /* @__PURE__ */ jsx(FitContentButton, { icon: icons.fit, canvas, layerId: fitLayerId }),
1835
+ showLock && /* @__PURE__ */ jsx(
1836
+ LockToggle,
1837
+ {
1838
+ locked,
1839
+ onToggle: onToggleLock,
1840
+ lockedIcon: icons.locked,
1841
+ unlockedIcon: icons.unlocked
1842
+ }
1843
+ ),
1844
+ children
1845
+ ] });
1846
+ const nav = orientation === "vertical" ? /* @__PURE__ */ jsx(NavVertical, { top: controls, className }) : /* @__PURE__ */ jsx(NavHorizontal, { left: controls, className });
1847
+ if (bare) return nav;
1848
+ return /* @__PURE__ */ jsx(Panel, { position, orientation, children: nav });
1849
+ }
1850
+ function GraphToolbar({
1851
+ layout,
1852
+ layoutOptions,
1853
+ onLayoutChange,
1854
+ selectMode,
1855
+ selectModeOptions,
1856
+ onSelectModeChange,
1857
+ edgeTypeLayerId = "graph",
1858
+ edgeTypes,
1859
+ edgeTypeLabels,
1860
+ edgeTypeIcons,
1861
+ clearLayerId = "graph",
1862
+ clearIcon,
1863
+ canvas,
1864
+ position = "top-center",
1865
+ className
1866
+ }) {
1867
+ return /* @__PURE__ */ jsx(Panel, { position, orientation: "horizontal", children: /* @__PURE__ */ jsx(
1868
+ NavHorizontal,
1869
+ {
1870
+ className,
1871
+ center: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
1872
+ /* @__PURE__ */ jsx(
1873
+ OptionPicker,
1874
+ {
1875
+ label: "Layout",
1876
+ value: layout,
1877
+ options: layoutOptions,
1878
+ onChange: onLayoutChange
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ jsx(
1882
+ OptionPicker,
1883
+ {
1884
+ label: "Select",
1885
+ value: selectMode,
1886
+ options: selectModeOptions,
1887
+ onChange: onSelectModeChange
1888
+ }
1889
+ ),
1890
+ edgeTypeLayerId != null && /* @__PURE__ */ jsx(
1891
+ EdgeTypePicker,
1892
+ {
1893
+ layerId: edgeTypeLayerId,
1894
+ ...edgeTypes ? { types: edgeTypes } : {},
1895
+ ...edgeTypeLabels ? { labels: edgeTypeLabels } : {},
1896
+ ...edgeTypeIcons ? { icons: edgeTypeIcons } : {},
1897
+ canvas
1898
+ }
1899
+ ),
1900
+ /* @__PURE__ */ jsx(ClearButton, { icon: clearIcon, layerId: clearLayerId, canvas })
1901
+ ] })
1902
+ }
1903
+ ) });
1904
+ }
1905
+ function HistoryToolbar({
1906
+ icons,
1907
+ position = "top-left",
1908
+ orientation = "horizontal",
1909
+ showRedraw = true,
1910
+ layerId,
1911
+ bare = false,
1912
+ canvas,
1913
+ className
1914
+ }) {
1915
+ const controls = /* @__PURE__ */ jsxs(Fragment, { children: [
1916
+ /* @__PURE__ */ jsx(UndoButton, { icon: icons.undo, canvas }),
1917
+ /* @__PURE__ */ jsx(RedoButton, { icon: icons.redo, canvas }),
1918
+ showRedraw && icons.redraw && /* @__PURE__ */ jsx(RedrawButton, { icon: icons.redraw, layerId, canvas })
1919
+ ] });
1920
+ const nav = orientation === "vertical" ? /* @__PURE__ */ jsx(NavVertical, { top: controls, className }) : /* @__PURE__ */ jsx(NavHorizontal, { left: controls, className });
1921
+ if (bare) return nav;
1922
+ return /* @__PURE__ */ jsx(Panel, { position, orientation, children: nav });
1923
+ }
1924
+ function EditToolbar({
1925
+ icons,
1926
+ position = "top-left",
1927
+ orientation = "horizontal",
1928
+ showClear = true,
1929
+ clickSelectId,
1930
+ layerId,
1931
+ bare = false,
1932
+ canvas,
1933
+ className
1934
+ }) {
1935
+ const controls = /* @__PURE__ */ jsxs(Fragment, { children: [
1936
+ /* @__PURE__ */ jsx(CutButton, { icon: icons.cut, clickSelectId, canvas }),
1937
+ /* @__PURE__ */ jsx(CopyButton, { icon: icons.copy, clickSelectId, canvas }),
1938
+ /* @__PURE__ */ jsx(PasteButton, { icon: icons.paste, clickSelectId, canvas }),
1939
+ /* @__PURE__ */ jsx(
1940
+ DeleteSelectionButton,
1941
+ {
1942
+ icon: icons.delete,
1943
+ clickSelectId,
1944
+ canvas
1945
+ }
1946
+ ),
1947
+ showClear && /* @__PURE__ */ jsx(ClearButton, { icon: icons.clear, layerId, canvas })
1948
+ ] });
1949
+ const nav = orientation === "vertical" ? /* @__PURE__ */ jsx(NavVertical, { top: controls, className }) : /* @__PURE__ */ jsx(NavHorizontal, { left: controls, className });
1950
+ if (bare) return nav;
1951
+ return /* @__PURE__ */ jsx(Panel, { position, orientation, children: nav });
1952
+ }
1953
+ function ViewToolbar({
1954
+ icons,
1955
+ position = "bottom-left",
1956
+ orientation = "vertical",
1957
+ showZoom = true,
1958
+ showZoomLevel = false,
1959
+ showZoomPicker = true,
1960
+ showFit = true,
1961
+ showLock = true,
1962
+ layerId = "graph",
1963
+ lockBehaviourIds,
1964
+ bare = false,
1965
+ canvas,
1966
+ className
1967
+ }) {
1968
+ const lockReady = showLock && icons.locked && icons.unlocked;
1969
+ const controls = /* @__PURE__ */ jsxs(Fragment, { children: [
1970
+ showZoom && /* @__PURE__ */ jsx(
1971
+ ZoomControls,
1972
+ {
1973
+ orientation,
1974
+ canvas,
1975
+ zoomInIcon: icons.zoomIn,
1976
+ zoomOutIcon: icons.zoomOut,
1977
+ showLevel: showZoomLevel
1978
+ }
1979
+ ),
1980
+ showZoomPicker && /* @__PURE__ */ jsx(ZoomPicker, { canvas, layerId }),
1981
+ showFit && /* @__PURE__ */ jsx(FitContentButton, { icon: icons.fit, canvas, layerId }),
1982
+ lockReady && /* @__PURE__ */ jsx(
1983
+ LockButton,
1984
+ {
1985
+ lockedIcon: icons.locked,
1986
+ unlockedIcon: icons.unlocked,
1987
+ behaviourIds: lockBehaviourIds,
1988
+ canvas
1989
+ }
1990
+ )
1991
+ ] });
1992
+ const nav = orientation === "vertical" ? /* @__PURE__ */ jsx(NavVertical, { top: controls, className }) : /* @__PURE__ */ jsx(NavHorizontal, { left: controls, className });
1993
+ if (bare) return nav;
1994
+ return /* @__PURE__ */ jsx(Panel, { position, orientation, children: nav });
1995
+ }
1996
+ function GridToolbar({
1997
+ icons,
1998
+ position = "bottom-right",
1999
+ orientation = "horizontal",
2000
+ backgroundLayerId,
2001
+ patternType,
2002
+ bare = false,
2003
+ canvas,
2004
+ className
2005
+ }) {
2006
+ const controls = /* @__PURE__ */ jsx(
2007
+ GridToggle,
2008
+ {
2009
+ icon: icons.grid,
2010
+ backgroundLayerId,
2011
+ patternType,
2012
+ canvas
2013
+ }
2014
+ );
2015
+ const nav = orientation === "vertical" ? /* @__PURE__ */ jsx(NavVertical, { top: controls, className }) : /* @__PURE__ */ jsx(NavHorizontal, { left: controls, className });
2016
+ if (bare) return nav;
2017
+ return /* @__PURE__ */ jsx(Panel, { position, orientation, children: nav });
2018
+ }
2019
+ function GraphLayoutToolbar({
2020
+ layouts,
2021
+ selectModeBehaviourIds,
2022
+ layoutLabels,
2023
+ selectModeLabels,
2024
+ selectModeIcons,
2025
+ initialLayout,
2026
+ initialSelectMode,
2027
+ layerId,
2028
+ position = "top-center",
2029
+ bare = false,
2030
+ canvas,
2031
+ className
2032
+ }) {
2033
+ const nav = /* @__PURE__ */ jsx(
2034
+ NavHorizontal,
2035
+ {
2036
+ className,
2037
+ center: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
2038
+ /* @__PURE__ */ jsx(
2039
+ LayoutPicker,
2040
+ {
2041
+ layouts,
2042
+ labels: layoutLabels,
2043
+ initial: initialLayout,
2044
+ layerId,
2045
+ canvas
2046
+ }
2047
+ ),
2048
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", style: { alignSelf: "center", height: 24 } }),
2049
+ /* @__PURE__ */ jsx(
2050
+ SelectModePicker,
2051
+ {
2052
+ behaviourIds: selectModeBehaviourIds,
2053
+ labels: selectModeLabels,
2054
+ icons: selectModeIcons,
2055
+ initial: initialSelectMode,
2056
+ canvas
2057
+ }
2058
+ )
2059
+ ] })
2060
+ }
2061
+ );
2062
+ if (bare) return nav;
2063
+ return /* @__PURE__ */ jsx(Panel, { position, orientation: "horizontal", children: nav });
2064
+ }
2065
+ var DEFAULT_TOOLS = ["select", "add", "connect", "delete"];
2066
+ var DEFAULT_LABELS = {
2067
+ select: "Select",
2068
+ add: "Add node",
2069
+ connect: "Connect",
2070
+ delete: "Delete"
2071
+ };
2072
+ function ModellerToolbar({
2073
+ icons,
2074
+ tools = DEFAULT_TOOLS,
2075
+ labels,
2076
+ nodeKinds,
2077
+ nodeKindIcons,
2078
+ showHistory = true,
2079
+ showClear = true,
2080
+ layerId,
2081
+ position = "top-center",
2082
+ orientation = "horizontal",
2083
+ bare = false,
2084
+ canvas,
2085
+ className
2086
+ }) {
2087
+ const { tool, setTool, nodeKind, setNodeKind } = useTool();
2088
+ const tipSide = orientation === "vertical" ? "right" : "bottom";
2089
+ const label = (t) => labels?.[t] ?? DEFAULT_LABELS[t];
2090
+ const controls = /* @__PURE__ */ jsxs(Fragment, { children: [
2091
+ tools.map((t) => /* @__PURE__ */ jsx(
2092
+ ControlButton,
2093
+ {
2094
+ icon: icons[t],
2095
+ title: label(t),
2096
+ tooltipSide: tipSide,
2097
+ active: tool === t,
2098
+ onClick: () => setTool(t)
2099
+ },
2100
+ t
2101
+ )),
2102
+ tool === "add" && nodeKinds && Object.keys(nodeKinds).length > 0 && /* @__PURE__ */ jsx(
2103
+ OptionPicker,
2104
+ {
2105
+ label: "Shape",
2106
+ value: nodeKind,
2107
+ options: nodeKinds,
2108
+ ...nodeKindIcons ? { icons: nodeKindIcons } : {},
2109
+ onChange: setNodeKind,
2110
+ tooltipSide: tipSide
2111
+ }
2112
+ ),
2113
+ showHistory && icons.undo && icons.redo && /* @__PURE__ */ jsxs(Fragment, { children: [
2114
+ /* @__PURE__ */ jsx(UndoButton, { icon: icons.undo, tooltipSide: tipSide, canvas }),
2115
+ /* @__PURE__ */ jsx(RedoButton, { icon: icons.redo, tooltipSide: tipSide, canvas })
2116
+ ] }),
2117
+ showClear && /* @__PURE__ */ jsx(ClearButton, { icon: icons.clear, layerId, tooltipSide: tipSide, canvas })
2118
+ ] });
2119
+ const nav = orientation === "vertical" ? /* @__PURE__ */ jsx(NavVertical, { top: controls, className }) : /* @__PURE__ */ jsx(NavHorizontal, { left: controls, className });
2120
+ if (bare) return nav;
2121
+ return /* @__PURE__ */ jsx(Panel, { position, orientation, children: nav });
2122
+ }
2123
+ function InspectorPanel({
2124
+ layerId,
2125
+ inspectId,
2126
+ position = "top-right",
2127
+ showLabel,
2128
+ typeAsLabel = false,
2129
+ nodeTitle = "Node",
2130
+ edgeTitle = "Edge",
2131
+ bare = false,
2132
+ canvas,
2133
+ className
2134
+ }) {
2135
+ const opts = {};
2136
+ if (layerId !== void 0) opts.layerId = layerId;
2137
+ if (inspectId !== void 0) opts.inspectId = inspectId;
2138
+ if (typeAsLabel) opts.typeAsLabel = true;
2139
+ const target = useEntityEditor(opts, canvas);
2140
+ if (!target) return null;
2141
+ const isEdge = target.kind === "edge";
2142
+ const showTypeField = typeAsLabel || isEdge;
2143
+ const showLabelField = !showTypeField && (showLabel ?? true);
2144
+ const editor = /* @__PURE__ */ jsx(
2145
+ PropertiesEditor,
2146
+ {
2147
+ title: isEdge ? edgeTitle : nodeTitle,
2148
+ defaults: { label: target.label, type: target.type, data: target.data },
2149
+ onSubmit: target.commit,
2150
+ showLabel: showLabelField,
2151
+ ...showTypeField ? { showType: true } : {},
2152
+ ...isEdge && target.reverse ? { onReverse: target.reverse } : {},
2153
+ ...className !== void 0 ? { className } : {}
2154
+ },
2155
+ `${target.kind}:${target.id}`
2156
+ );
2157
+ if (bare) return editor;
2158
+ return /* @__PURE__ */ jsx(Panel, { position, orientation: "vertical", children: editor });
2159
+ }
2160
+ function withAutoClose(items, close) {
2161
+ return items.map((item) => {
2162
+ const next = { ...item };
2163
+ if (item.children) next.children = withAutoClose(item.children, close);
2164
+ if (item.onClick) {
2165
+ const original = item.onClick;
2166
+ next.onClick = () => {
2167
+ original();
2168
+ close();
2169
+ };
2170
+ }
2171
+ return next;
2172
+ });
2173
+ }
2174
+ function GraphContextMenuRoot({
2175
+ target,
2176
+ id,
2177
+ layerId = "graph",
2178
+ enabled = true,
2179
+ zIndex,
2180
+ style,
2181
+ autoClose = true,
2182
+ state = null,
2183
+ build
2184
+ }) {
2185
+ const canvas = useCanvas();
2186
+ const { menu, open, close } = useContextMenu();
2187
+ const onContextMenu = useCallback(
2188
+ (event) => {
2189
+ const base = {
2190
+ world: event.world,
2191
+ screen: event.screen,
2192
+ canvas,
2193
+ close
2194
+ };
2195
+ const items = build(event, base);
2196
+ open(event.screen.x, event.screen.y, autoClose ? withAutoClose(items, close) : items);
2197
+ },
2198
+ [canvas, build, autoClose, open, close]
2199
+ );
2200
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2201
+ /* @__PURE__ */ jsx(
2202
+ ContextMenuBehaviour,
2203
+ {
2204
+ id,
2205
+ layerId,
2206
+ enabled,
2207
+ targets: [target],
2208
+ state,
2209
+ onContextMenu
2210
+ }
2211
+ ),
2212
+ menu && /* @__PURE__ */ jsx(ContextMenuOverlay, { x: menu.x, y: menu.y, items: menu.items, zIndex, style })
2213
+ ] });
2214
+ }
2215
+ function GraphNodeContextMenu({
2216
+ items,
2217
+ id = "node-context-menu",
2218
+ ...common
2219
+ }) {
2220
+ const build = useCallback(
2221
+ (event, base) => items({ ...base, id: event.id, data: event.data }),
2222
+ [items]
2223
+ );
2224
+ return /* @__PURE__ */ jsx(GraphContextMenuRoot, { target: "node", id, build, ...common });
2225
+ }
2226
+ function GraphEdgeContextMenu({
2227
+ items,
2228
+ id = "edge-context-menu",
2229
+ ...common
2230
+ }) {
2231
+ const build = useCallback(
2232
+ (event, base) => items({ ...base, id: event.id, data: event.data }),
2233
+ [items]
2234
+ );
2235
+ return /* @__PURE__ */ jsx(GraphContextMenuRoot, { target: "edge", id, build, ...common });
2236
+ }
2237
+ function GraphBackgroundContextMenu({
2238
+ items,
2239
+ id = "background-context-menu",
2240
+ ...common
2241
+ }) {
2242
+ const build = useCallback(
2243
+ (_event, base) => items(base),
2244
+ [items]
2245
+ );
2246
+ return /* @__PURE__ */ jsx(GraphContextMenuRoot, { target: "canvas", id, build, ...common });
2247
+ }
2248
+
2249
+ export { BackgroundLayer, BrushSelectBehaviour, Canvas, CanvasContext, CanvasControlsToolbar, ClearButton, ClickInspectBehaviour, ClickSelectBehaviour, ClipboardContext, CollapseExpandBehaviour, ContextMenuBehaviour, ContextMenuOverlay, ControlButton, CopyButton, CreateNodeBehaviour, CutButton, D3ForceLayout, DEFAULT_EDGE_TYPES, DEFAULT_EDGE_TYPE_LABELS, DegreeSizeBehaviour, DeleteSelectionButton, DragNodeBehaviour, DragPanBehaviour, DrawEdgeBehaviour, EdgeSizeLODBehaviour, EdgeTypePicker, EditToolbar, EraseBehaviour, FitContentButton, GraphBackgroundContextMenu, GraphClipboardProvider, GraphEdgeContextMenu, GraphHistoryProvider, GraphLayer, GraphLayoutToolbar, GraphNodeContextMenu, GraphToolProvider, GraphToolbar, GridToggle, GridToolbar, HistoryContext, HistoryToolbar, HoverActivateBehaviour, InspectorPanel, KeyboardCameraInputBehaviour, LabelCollisionBehaviour, LabelResolutionLODBehaviour, LassoSelectBehaviour, LayoutPicker, LockButton, LockToggle, MiniMapLayer, ModellerToolbar, NodeResizeBehaviour, NodeSizeLODBehaviour, OptionPicker, Panel, ParallelEdgeBehaviour, PasteButton, PinchZoomBehaviour, PropertiesEditor, RedoButton, RedrawButton, ResponsiveThemeBehaviour, SelectModePicker, ThemeToggle, ToolContext, Tooltipped, UndoButton, ViewToolbar, WheelZoomBehaviour, ZoomControls, ZoomPicker, useCamera, useCanvas, useCanvasEvent, useClearGraph, useClipboard, useContextMenu, useDrawHistory, useEdgeType, useEntityEditor, useFitContent, useGrid, useHistory, useInspectTarget, useLayout, useLock, useSelectMode, useSelection, useTheme, useTool, useZoom };
2250
+ //# sourceMappingURL=index.js.map
2251
+ //# sourceMappingURL=index.js.map