@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.d.ts +2291 -0
- package/dist/index.js +2251 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
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
|