@ship-it-ui/graph-editor 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/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.cjs +874 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +225 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +856 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +49 -0
- package/dist/styles.css.map +1 -0
- package/package.json +88 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
DefaultNode: () => DefaultNode,
|
|
26
|
+
GraphEditorCanvas: () => GraphEditorCanvas,
|
|
27
|
+
GraphNodeShell: () => GraphNodeShell,
|
|
28
|
+
toCytoscapeElements: () => toCytoscapeElements,
|
|
29
|
+
toFlowElements: () => toFlowElements,
|
|
30
|
+
useGraphEditorTheme: () => useGraphEditorTheme
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/GraphEditorCanvas.tsx
|
|
35
|
+
var import_icons2 = require("@ship-it-ui/icons");
|
|
36
|
+
var import_react8 = require("@xyflow/react");
|
|
37
|
+
var import_react9 = require("react");
|
|
38
|
+
|
|
39
|
+
// src/adapter.ts
|
|
40
|
+
function pickDataPassthrough(data, exclude) {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const key of Object.keys(data)) {
|
|
43
|
+
if (exclude.includes(key)) continue;
|
|
44
|
+
out[key] = data[key];
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function pickElementExtras(element) {
|
|
49
|
+
const extras = {};
|
|
50
|
+
let hasExtras = false;
|
|
51
|
+
for (const key of Object.keys(element)) {
|
|
52
|
+
if (key === "data" || key === "position" || key === "classes") continue;
|
|
53
|
+
extras[key] = element[key];
|
|
54
|
+
hasExtras = true;
|
|
55
|
+
}
|
|
56
|
+
return { extras: hasExtras ? extras : void 0, hasExtras };
|
|
57
|
+
}
|
|
58
|
+
function toFlowElements(elements) {
|
|
59
|
+
const nodes = [];
|
|
60
|
+
const edges = [];
|
|
61
|
+
for (const el of elements) {
|
|
62
|
+
const data = el.data ?? {};
|
|
63
|
+
const isEdge = typeof data.source === "string" && typeof data.target === "string";
|
|
64
|
+
if (isEdge) {
|
|
65
|
+
const id = typeof data.id === "string" ? data.id : `${data.source}->${data.target}`;
|
|
66
|
+
const { extras, hasExtras } = pickElementExtras(el);
|
|
67
|
+
const edge = {
|
|
68
|
+
id,
|
|
69
|
+
source: data.source,
|
|
70
|
+
target: data.target,
|
|
71
|
+
data: {
|
|
72
|
+
...pickDataPassthrough(data, ["id", "source", "target"]),
|
|
73
|
+
...hasExtras ? { _extra: extras } : {}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
if (el.classes) edge.className = el.classes;
|
|
77
|
+
edges.push(edge);
|
|
78
|
+
} else {
|
|
79
|
+
const id = data.id;
|
|
80
|
+
if (typeof id !== "string") continue;
|
|
81
|
+
const { extras, hasExtras } = pickElementExtras(el);
|
|
82
|
+
const node = {
|
|
83
|
+
id,
|
|
84
|
+
position: el.position ?? { x: 0, y: 0 },
|
|
85
|
+
data: {
|
|
86
|
+
...pickDataPassthrough(data, ["id"]),
|
|
87
|
+
...hasExtras ? { _extra: extras } : {}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
if (el.classes) node.className = el.classes;
|
|
91
|
+
nodes.push(node);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { nodes, edges };
|
|
95
|
+
}
|
|
96
|
+
function toCytoscapeElements(split) {
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const node of split.nodes) {
|
|
99
|
+
const data = node.data ?? {};
|
|
100
|
+
const passthrough = { ...data };
|
|
101
|
+
const extras = passthrough._extra;
|
|
102
|
+
delete passthrough._extra;
|
|
103
|
+
const element = {
|
|
104
|
+
data: { id: node.id, ...passthrough },
|
|
105
|
+
position: { x: node.position.x, y: node.position.y }
|
|
106
|
+
};
|
|
107
|
+
if (node.className) element.classes = node.className;
|
|
108
|
+
if (extras) {
|
|
109
|
+
for (const key of Object.keys(extras)) {
|
|
110
|
+
if (key === "data" || key === "position" || key === "classes") continue;
|
|
111
|
+
element[key] = extras[key];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
out.push(element);
|
|
115
|
+
}
|
|
116
|
+
for (const edge of split.edges) {
|
|
117
|
+
const data = edge.data ?? {};
|
|
118
|
+
const passthrough = { ...data };
|
|
119
|
+
const extras = passthrough._extra;
|
|
120
|
+
delete passthrough._extra;
|
|
121
|
+
const element = {
|
|
122
|
+
data: {
|
|
123
|
+
id: edge.id,
|
|
124
|
+
source: edge.source,
|
|
125
|
+
target: edge.target,
|
|
126
|
+
...passthrough
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
if (edge.className) element.classes = edge.className;
|
|
130
|
+
if (extras) {
|
|
131
|
+
for (const key of Object.keys(extras)) {
|
|
132
|
+
if (key === "data" || key === "position" || key === "classes") continue;
|
|
133
|
+
element[key] = extras[key];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
out.push(element);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/DefaultNode.tsx
|
|
142
|
+
var import_react2 = require("@xyflow/react");
|
|
143
|
+
|
|
144
|
+
// src/GraphNodeShell.tsx
|
|
145
|
+
var import_icons = require("@ship-it-ui/icons");
|
|
146
|
+
var import_shipit = require("@ship-it-ui/shipit");
|
|
147
|
+
var import_ui = require("@ship-it-ui/ui");
|
|
148
|
+
var import_react = require("react");
|
|
149
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
150
|
+
var GraphNodeShell = (0, import_react.forwardRef)(
|
|
151
|
+
function GraphNodeShell2({ type, state = "default", glyph, label, size = 52, pathColor, className, style, ...props }, ref) {
|
|
152
|
+
const meta = (0, import_shipit.getEntityTypeMeta)(type);
|
|
153
|
+
const color = state === "path" ? pathColor ?? "var(--color-purple)" : meta.colorVar;
|
|
154
|
+
const glowPct = state === "hover" ? 50 : 25;
|
|
155
|
+
const opacity = state === "dim" ? 0.35 : 1;
|
|
156
|
+
const showRing = state === "selected" || state === "path";
|
|
157
|
+
const ariaRole = typeof label === "string" || label == null ? "img" : void 0;
|
|
158
|
+
const ariaLabel = typeof label === "string" ? label : label == null ? `${type} node` : void 0;
|
|
159
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
ref,
|
|
163
|
+
role: ariaRole,
|
|
164
|
+
"aria-label": ariaLabel,
|
|
165
|
+
"data-state": state,
|
|
166
|
+
"data-entity-type": type,
|
|
167
|
+
className: (0, import_ui.cn)("inline-flex flex-col items-center gap-[6px]", className),
|
|
168
|
+
style,
|
|
169
|
+
...props,
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
172
|
+
"div",
|
|
173
|
+
{
|
|
174
|
+
className: "bg-panel grid place-items-center rounded-[14px] border-[1.5px] transition-all duration-(--duration-micro)",
|
|
175
|
+
style: {
|
|
176
|
+
width: size,
|
|
177
|
+
height: size,
|
|
178
|
+
borderColor: color,
|
|
179
|
+
color,
|
|
180
|
+
fontSize: Math.round(size * 0.42),
|
|
181
|
+
boxShadow: `0 0 ${state === "hover" ? 30 : 20}px color-mix(in oklab, ${color} ${glowPct}%, transparent)`,
|
|
182
|
+
outline: showRing ? `2px solid ${color}` : void 0,
|
|
183
|
+
outlineOffset: showRing ? 4 : void 0,
|
|
184
|
+
opacity
|
|
185
|
+
},
|
|
186
|
+
children: glyph ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.DynamicIconGlyph, { name: meta.iconName, size: Math.round(size * 0.42) })
|
|
187
|
+
}
|
|
188
|
+
),
|
|
189
|
+
label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-text-dim font-mono text-[10px]", children: label })
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
GraphNodeShell.displayName = "GraphNodeShell";
|
|
196
|
+
|
|
197
|
+
// src/DefaultNode.tsx
|
|
198
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
199
|
+
function DefaultNode(props) {
|
|
200
|
+
const data = props.data ?? {};
|
|
201
|
+
const type = data.entityType ?? "service";
|
|
202
|
+
const state = props.selected ? "selected" : data.state ?? "default";
|
|
203
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
204
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Handle, { type: "target", position: import_react2.Position.Top, className: "ship-graph-handle" }),
|
|
205
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Handle, { type: "target", position: import_react2.Position.Left, className: "ship-graph-handle" }),
|
|
206
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GraphNodeShell, { type, state, label: data.label }),
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Handle, { type: "source", position: import_react2.Position.Bottom, className: "ship-graph-handle" }),
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Handle, { type: "source", position: import_react2.Position.Right, className: "ship-graph-handle" })
|
|
209
|
+
] });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/history.ts
|
|
213
|
+
var import_react3 = require("react");
|
|
214
|
+
function inverseOf(command) {
|
|
215
|
+
switch (command.kind) {
|
|
216
|
+
case "add-node":
|
|
217
|
+
return { kind: "delete-node", node: command.node, incidentEdges: [] };
|
|
218
|
+
case "delete-node":
|
|
219
|
+
return {
|
|
220
|
+
kind: "batch",
|
|
221
|
+
commands: [
|
|
222
|
+
{ kind: "add-node", node: command.node },
|
|
223
|
+
...command.incidentEdges.map((edge) => ({ kind: "add-edge", edge }))
|
|
224
|
+
]
|
|
225
|
+
};
|
|
226
|
+
case "add-edge":
|
|
227
|
+
return { kind: "delete-edge", edge: command.edge };
|
|
228
|
+
case "delete-edge":
|
|
229
|
+
return { kind: "add-edge", edge: command.edge };
|
|
230
|
+
case "move-node":
|
|
231
|
+
return { kind: "move-node", id: command.id, from: command.to, to: command.from };
|
|
232
|
+
case "batch":
|
|
233
|
+
return { kind: "batch", commands: command.commands.map(inverseOf).reverse() };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function useHistory({ maxSize = 50 } = {}) {
|
|
237
|
+
const undoStack = (0, import_react3.useRef)([]);
|
|
238
|
+
const redoStack = (0, import_react3.useRef)([]);
|
|
239
|
+
const push = (0, import_react3.useCallback)(
|
|
240
|
+
(cmd) => {
|
|
241
|
+
if (maxSize <= 0) return;
|
|
242
|
+
undoStack.current.push(cmd);
|
|
243
|
+
if (undoStack.current.length > maxSize) undoStack.current.shift();
|
|
244
|
+
redoStack.current.length = 0;
|
|
245
|
+
},
|
|
246
|
+
[maxSize]
|
|
247
|
+
);
|
|
248
|
+
const undo = (0, import_react3.useCallback)(() => {
|
|
249
|
+
if (maxSize <= 0) return null;
|
|
250
|
+
const cmd = undoStack.current.pop();
|
|
251
|
+
if (!cmd) return null;
|
|
252
|
+
redoStack.current.push(cmd);
|
|
253
|
+
return inverseOf(cmd);
|
|
254
|
+
}, [maxSize]);
|
|
255
|
+
const redo = (0, import_react3.useCallback)(() => {
|
|
256
|
+
if (maxSize <= 0) return null;
|
|
257
|
+
const cmd = redoStack.current.pop();
|
|
258
|
+
if (!cmd) return null;
|
|
259
|
+
undoStack.current.push(cmd);
|
|
260
|
+
return cmd;
|
|
261
|
+
}, [maxSize]);
|
|
262
|
+
const reset = (0, import_react3.useCallback)(() => {
|
|
263
|
+
undoStack.current.length = 0;
|
|
264
|
+
redoStack.current.length = 0;
|
|
265
|
+
}, []);
|
|
266
|
+
const size = (0, import_react3.useCallback)(
|
|
267
|
+
() => ({ undo: undoStack.current.length, redo: redoStack.current.length }),
|
|
268
|
+
[]
|
|
269
|
+
);
|
|
270
|
+
return (0, import_react3.useMemo)(() => ({ push, undo, redo, reset, size }), [push, undo, redo, reset, size]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/keyboard.ts
|
|
274
|
+
var import_react4 = require("react");
|
|
275
|
+
var NUDGE_BASE = 8;
|
|
276
|
+
var NUDGE_LARGE = 32;
|
|
277
|
+
function useGraphEditorKeyboard({
|
|
278
|
+
nodes,
|
|
279
|
+
edges,
|
|
280
|
+
onNodesChange,
|
|
281
|
+
onEdgesChange,
|
|
282
|
+
onNodeDelete,
|
|
283
|
+
onEdgeDelete,
|
|
284
|
+
onNodeMove,
|
|
285
|
+
onArrowNudge,
|
|
286
|
+
onClearSelection,
|
|
287
|
+
undo,
|
|
288
|
+
redo
|
|
289
|
+
}) {
|
|
290
|
+
return (0, import_react4.useCallback)(
|
|
291
|
+
(event) => {
|
|
292
|
+
const key = event.key;
|
|
293
|
+
const meta = event.metaKey || event.ctrlKey;
|
|
294
|
+
const target = event.target;
|
|
295
|
+
if (target) {
|
|
296
|
+
const tag = target.tagName;
|
|
297
|
+
const editable = target.isContentEditable;
|
|
298
|
+
if (editable || tag === "INPUT" || tag === "TEXTAREA") return;
|
|
299
|
+
}
|
|
300
|
+
if (meta && (key === "z" || key === "Z")) {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
if (event.shiftKey) redo?.();
|
|
303
|
+
else undo?.();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (key === "Delete" || key === "Backspace") {
|
|
307
|
+
const selectedNodes2 = nodes.filter((n) => n.selected);
|
|
308
|
+
const selectedEdges = edges.filter((e) => e.selected);
|
|
309
|
+
if (selectedNodes2.length === 0 && selectedEdges.length === 0) return;
|
|
310
|
+
event.preventDefault();
|
|
311
|
+
for (const n of selectedNodes2) onNodeDelete?.(n.id);
|
|
312
|
+
for (const e of selectedEdges) onEdgeDelete?.(e.id);
|
|
313
|
+
if (selectedNodes2.length > 0) {
|
|
314
|
+
onNodesChange(selectedNodes2.map((n) => ({ type: "remove", id: n.id })));
|
|
315
|
+
}
|
|
316
|
+
if (selectedEdges.length > 0) {
|
|
317
|
+
onEdgesChange(selectedEdges.map((e) => ({ type: "remove", id: e.id })));
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (key === "Escape") {
|
|
322
|
+
const selectedNodes2 = nodes.filter((n) => n.selected);
|
|
323
|
+
const selectedEdges = edges.filter((e) => e.selected);
|
|
324
|
+
if (selectedNodes2.length === 0 && selectedEdges.length === 0) return;
|
|
325
|
+
event.preventDefault();
|
|
326
|
+
onNodesChange(selectedNodes2.map((n) => ({ type: "select", id: n.id, selected: false })));
|
|
327
|
+
onEdgesChange(selectedEdges.map((e) => ({ type: "select", id: e.id, selected: false })));
|
|
328
|
+
onClearSelection?.();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const dx = key === "ArrowLeft" ? -1 : key === "ArrowRight" ? 1 : 0;
|
|
332
|
+
const dy = key === "ArrowUp" ? -1 : key === "ArrowDown" ? 1 : 0;
|
|
333
|
+
if (dx === 0 && dy === 0) return;
|
|
334
|
+
const selectedNodes = nodes.filter((n) => n.selected);
|
|
335
|
+
if (selectedNodes.length === 0) return;
|
|
336
|
+
event.preventDefault();
|
|
337
|
+
const step = event.shiftKey ? NUDGE_LARGE : NUDGE_BASE;
|
|
338
|
+
const changes = selectedNodes.map((n) => {
|
|
339
|
+
const from = { x: n.position.x, y: n.position.y };
|
|
340
|
+
const nextPos = { x: from.x + dx * step, y: from.y + dy * step };
|
|
341
|
+
onArrowNudge?.(n.id, from, nextPos);
|
|
342
|
+
onNodeMove?.(n.id, nextPos);
|
|
343
|
+
return {
|
|
344
|
+
type: "position",
|
|
345
|
+
id: n.id,
|
|
346
|
+
position: nextPos,
|
|
347
|
+
dragging: false
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
onNodesChange(changes);
|
|
351
|
+
},
|
|
352
|
+
[
|
|
353
|
+
nodes,
|
|
354
|
+
edges,
|
|
355
|
+
onNodesChange,
|
|
356
|
+
onEdgesChange,
|
|
357
|
+
onNodeDelete,
|
|
358
|
+
onEdgeDelete,
|
|
359
|
+
onNodeMove,
|
|
360
|
+
onArrowNudge,
|
|
361
|
+
onClearSelection,
|
|
362
|
+
undo,
|
|
363
|
+
redo
|
|
364
|
+
]
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/MiniMap.tsx
|
|
369
|
+
var import_shipit2 = require("@ship-it-ui/shipit");
|
|
370
|
+
var import_react5 = require("@xyflow/react");
|
|
371
|
+
var import_react6 = require("react");
|
|
372
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
373
|
+
var PADDING = 60;
|
|
374
|
+
function nodeBounds(nodes) {
|
|
375
|
+
if (nodes.length === 0) return null;
|
|
376
|
+
let minX = Infinity;
|
|
377
|
+
let minY = Infinity;
|
|
378
|
+
let maxX = -Infinity;
|
|
379
|
+
let maxY = -Infinity;
|
|
380
|
+
for (const node of nodes) {
|
|
381
|
+
const { x, y } = node.position;
|
|
382
|
+
const w = node.measured?.width ?? node.width ?? 52;
|
|
383
|
+
const h = node.measured?.height ?? node.height ?? 52;
|
|
384
|
+
if (x < minX) minX = x;
|
|
385
|
+
if (y < minY) minY = y;
|
|
386
|
+
if (x + w > maxX) maxX = x + w;
|
|
387
|
+
if (y + h > maxY) maxY = y + h;
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
x: minX - PADDING,
|
|
391
|
+
y: minY - PADDING,
|
|
392
|
+
width: maxX - minX + 2 * PADDING,
|
|
393
|
+
height: maxY - minY + 2 * PADDING
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function GraphEditorMiniMap({
|
|
397
|
+
width = 120,
|
|
398
|
+
height = 72,
|
|
399
|
+
className
|
|
400
|
+
}) {
|
|
401
|
+
const nodes = (0, import_react5.useNodes)();
|
|
402
|
+
const viewport = (0, import_react5.useViewport)();
|
|
403
|
+
const canvasSize = (0, import_react5.useStore)((s) => ({ width: s.width, height: s.height }));
|
|
404
|
+
const { points, viewportRect } = (0, import_react6.useMemo)(() => {
|
|
405
|
+
const bbox = nodeBounds(nodes);
|
|
406
|
+
if (!bbox || bbox.width === 0 || bbox.height === 0) {
|
|
407
|
+
return { points: [], viewportRect: void 0 };
|
|
408
|
+
}
|
|
409
|
+
const norm = (x, y) => ({
|
|
410
|
+
x: (x - bbox.x) / bbox.width,
|
|
411
|
+
y: (y - bbox.y) / bbox.height
|
|
412
|
+
});
|
|
413
|
+
const points2 = nodes.map((node) => {
|
|
414
|
+
const center = {
|
|
415
|
+
x: node.position.x + (node.measured?.width ?? node.width ?? 52) / 2,
|
|
416
|
+
y: node.position.y + (node.measured?.height ?? node.height ?? 52) / 2
|
|
417
|
+
};
|
|
418
|
+
return norm(center.x, center.y);
|
|
419
|
+
});
|
|
420
|
+
const z = viewport.zoom || 1;
|
|
421
|
+
const vp = {
|
|
422
|
+
x: -viewport.x / z,
|
|
423
|
+
y: -viewport.y / z,
|
|
424
|
+
w: canvasSize.width / z,
|
|
425
|
+
h: canvasSize.height / z
|
|
426
|
+
};
|
|
427
|
+
const tl = norm(vp.x, vp.y);
|
|
428
|
+
const br = norm(vp.x + vp.w, vp.y + vp.h);
|
|
429
|
+
const clampedX = Math.max(0, Math.min(1, tl.x));
|
|
430
|
+
const clampedY = Math.max(0, Math.min(1, tl.y));
|
|
431
|
+
const clampedRight = Math.max(0, Math.min(1, br.x));
|
|
432
|
+
const clampedBottom = Math.max(0, Math.min(1, br.y));
|
|
433
|
+
const viewportRect2 = {
|
|
434
|
+
x: clampedX,
|
|
435
|
+
y: clampedY,
|
|
436
|
+
width: Math.max(0, clampedRight - clampedX),
|
|
437
|
+
height: Math.max(0, clampedBottom - clampedY)
|
|
438
|
+
};
|
|
439
|
+
return { points: points2, viewportRect: viewportRect2 };
|
|
440
|
+
}, [nodes, viewport, canvasSize.width, canvasSize.height]);
|
|
441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
442
|
+
import_shipit2.GraphMinimap,
|
|
443
|
+
{
|
|
444
|
+
points,
|
|
445
|
+
viewport: viewportRect,
|
|
446
|
+
width,
|
|
447
|
+
height,
|
|
448
|
+
className
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/theme-bridge.ts
|
|
454
|
+
var import_graph_tokens = require("@ship-it-ui/graph-tokens");
|
|
455
|
+
var import_react7 = require("react");
|
|
456
|
+
var RF_VAR_MAP = {
|
|
457
|
+
"--xy-background-color-default": "bg",
|
|
458
|
+
"--xy-node-background-color-default": "panel",
|
|
459
|
+
"--xy-node-border-default": "accent",
|
|
460
|
+
"--xy-node-color-default": "text",
|
|
461
|
+
"--xy-edge-stroke-default": "accent",
|
|
462
|
+
"--xy-edge-stroke-selected-default": "accent",
|
|
463
|
+
"--xy-selection-background-color-default": "accent",
|
|
464
|
+
"--xy-controls-button-background-color-default": "panel",
|
|
465
|
+
"--xy-controls-button-background-color-hover-default": "panel-2",
|
|
466
|
+
"--xy-controls-button-color-default": "text",
|
|
467
|
+
"--xy-controls-button-border-color-default": "border"
|
|
468
|
+
};
|
|
469
|
+
function paletteLookup(palette, key) {
|
|
470
|
+
switch (key) {
|
|
471
|
+
case "bg":
|
|
472
|
+
return palette.bg;
|
|
473
|
+
case "panel":
|
|
474
|
+
return palette.panel;
|
|
475
|
+
case "panel-2":
|
|
476
|
+
return palette.panel2;
|
|
477
|
+
case "border":
|
|
478
|
+
return palette.border;
|
|
479
|
+
case "border-strong":
|
|
480
|
+
return palette.borderStrong;
|
|
481
|
+
case "text":
|
|
482
|
+
return palette.text;
|
|
483
|
+
case "text-muted":
|
|
484
|
+
return palette.textMuted;
|
|
485
|
+
case "text-dim":
|
|
486
|
+
return palette.textDim;
|
|
487
|
+
case "accent":
|
|
488
|
+
return palette.accent;
|
|
489
|
+
case "ok":
|
|
490
|
+
return palette.ok;
|
|
491
|
+
case "warn":
|
|
492
|
+
return palette.warn;
|
|
493
|
+
case "err":
|
|
494
|
+
return palette.err;
|
|
495
|
+
case "purple":
|
|
496
|
+
return palette.purple;
|
|
497
|
+
case "pink":
|
|
498
|
+
return palette.pink;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function applyPalette(el, palette) {
|
|
502
|
+
for (const [cssVar, key] of Object.entries(RF_VAR_MAP)) {
|
|
503
|
+
el.style.setProperty(cssVar, paletteLookup(palette, key));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function useGraphEditorTheme(wrapperRef, options = {}) {
|
|
507
|
+
const { observe = true } = options;
|
|
508
|
+
const [palette, setPalette] = (0, import_react7.useState)(() => (0, import_graph_tokens.readThemeTokens)());
|
|
509
|
+
const apply = (0, import_react7.useCallback)(() => {
|
|
510
|
+
const next = (0, import_graph_tokens.readThemeTokens)();
|
|
511
|
+
setPalette(next);
|
|
512
|
+
if (wrapperRef.current) applyPalette(wrapperRef.current, next);
|
|
513
|
+
}, [wrapperRef]);
|
|
514
|
+
(0, import_react7.useEffect)(() => {
|
|
515
|
+
apply();
|
|
516
|
+
if (!observe || typeof document === "undefined") return void 0;
|
|
517
|
+
const observer = new MutationObserver(apply);
|
|
518
|
+
observer.observe(document.documentElement, {
|
|
519
|
+
attributes: true,
|
|
520
|
+
attributeFilter: ["data-theme"]
|
|
521
|
+
});
|
|
522
|
+
return () => observer.disconnect();
|
|
523
|
+
}, [apply, observe]);
|
|
524
|
+
return { palette, refresh: apply };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/GraphEditorCanvas.tsx
|
|
528
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
529
|
+
var GraphEditorCanvasInner = (0, import_react9.forwardRef)(
|
|
530
|
+
function GraphEditorCanvasInner2({
|
|
531
|
+
elements,
|
|
532
|
+
onNodeAdd,
|
|
533
|
+
onConnect,
|
|
534
|
+
onNodeMove,
|
|
535
|
+
onNodeDelete,
|
|
536
|
+
onEdgeDelete,
|
|
537
|
+
onSelect,
|
|
538
|
+
onClearSelection,
|
|
539
|
+
renderNode,
|
|
540
|
+
renderEdge,
|
|
541
|
+
toolbar,
|
|
542
|
+
inspector,
|
|
543
|
+
panZoom = true,
|
|
544
|
+
historySize = 50,
|
|
545
|
+
miniMap = true,
|
|
546
|
+
background = "none",
|
|
547
|
+
className,
|
|
548
|
+
"aria-label": ariaLabel = "Graph editor",
|
|
549
|
+
...rest
|
|
550
|
+
}, forwardedRef) {
|
|
551
|
+
const wrapperRef = (0, import_react9.useRef)(null);
|
|
552
|
+
const { refresh } = useGraphEditorTheme(wrapperRef);
|
|
553
|
+
const { getViewport, screenToFlowPosition } = (0, import_react8.useReactFlow)();
|
|
554
|
+
const initialRef = (0, import_react9.useRef)(null);
|
|
555
|
+
if (initialRef.current === null) initialRef.current = toFlowElements(elements);
|
|
556
|
+
const [nodes, setNodes, baseOnNodesChange] = (0, import_react8.useNodesState)(initialRef.current.nodes);
|
|
557
|
+
const [edges, setEdges, baseOnEdgesChange] = (0, import_react8.useEdgesState)(initialRef.current.edges);
|
|
558
|
+
const history = useHistory({ maxSize: historySize });
|
|
559
|
+
const nodeTypes = (0, import_react9.useMemo)(() => {
|
|
560
|
+
if (!renderNode) return { default: DefaultNode };
|
|
561
|
+
const ConsumerNode = (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: renderNode({
|
|
562
|
+
id: props.id,
|
|
563
|
+
data: props.data ?? {},
|
|
564
|
+
selected: Boolean(props.selected),
|
|
565
|
+
position: { x: props.positionAbsoluteX ?? 0, y: props.positionAbsoluteY ?? 0 }
|
|
566
|
+
}) });
|
|
567
|
+
return { default: ConsumerNode };
|
|
568
|
+
}, [renderNode]);
|
|
569
|
+
const edgeTypes = (0, import_react9.useMemo)(() => {
|
|
570
|
+
if (!renderEdge) return void 0;
|
|
571
|
+
const ConsumerEdge = (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: renderEdge({
|
|
572
|
+
id: props.id,
|
|
573
|
+
source: props.source,
|
|
574
|
+
target: props.target,
|
|
575
|
+
data: props.data ?? {},
|
|
576
|
+
selected: Boolean(props.selected)
|
|
577
|
+
}) });
|
|
578
|
+
return { default: ConsumerEdge };
|
|
579
|
+
}, [renderEdge]);
|
|
580
|
+
const handleConnect = (0, import_react9.useCallback)(
|
|
581
|
+
(connection) => {
|
|
582
|
+
if (!connection.source || !connection.target) return;
|
|
583
|
+
const id = `e-${connection.source}-${connection.target}-${Date.now()}`;
|
|
584
|
+
const edge = {
|
|
585
|
+
id,
|
|
586
|
+
source: connection.source,
|
|
587
|
+
target: connection.target
|
|
588
|
+
};
|
|
589
|
+
setEdges((prev) => [...prev, edge]);
|
|
590
|
+
history.push({ kind: "add-edge", edge });
|
|
591
|
+
onConnect?.({ id, source: connection.source, target: connection.target });
|
|
592
|
+
},
|
|
593
|
+
[setEdges, history, onConnect]
|
|
594
|
+
);
|
|
595
|
+
const dragStartPositions = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
|
|
596
|
+
const handleNodesChange = (0, import_react9.useCallback)(
|
|
597
|
+
(changes) => {
|
|
598
|
+
baseOnNodesChange(changes);
|
|
599
|
+
for (const change of changes) {
|
|
600
|
+
if (change.type !== "position") continue;
|
|
601
|
+
if (change.dragging === true) {
|
|
602
|
+
if (!dragStartPositions.current.has(change.id)) {
|
|
603
|
+
const node = nodes.find((n) => n.id === change.id);
|
|
604
|
+
if (node) dragStartPositions.current.set(change.id, { ...node.position });
|
|
605
|
+
}
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (change.dragging === false && change.position) {
|
|
609
|
+
const from = dragStartPositions.current.get(change.id);
|
|
610
|
+
dragStartPositions.current.delete(change.id);
|
|
611
|
+
if (from) {
|
|
612
|
+
history.push({
|
|
613
|
+
kind: "move-node",
|
|
614
|
+
id: change.id,
|
|
615
|
+
from,
|
|
616
|
+
to: change.position
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
onNodeMove?.(change.id, change.position);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
[baseOnNodesChange, history, nodes, onNodeMove]
|
|
624
|
+
);
|
|
625
|
+
const handleEdgesChange = baseOnEdgesChange;
|
|
626
|
+
const handleNodeClick = (0, import_react9.useCallback)(
|
|
627
|
+
(_event, node) => {
|
|
628
|
+
onSelect?.({ kind: "node", id: node.id });
|
|
629
|
+
},
|
|
630
|
+
[onSelect]
|
|
631
|
+
);
|
|
632
|
+
const handleEdgeClick = (0, import_react9.useCallback)(
|
|
633
|
+
(_event, edge) => {
|
|
634
|
+
onSelect?.({ kind: "edge", id: edge.id });
|
|
635
|
+
},
|
|
636
|
+
[onSelect]
|
|
637
|
+
);
|
|
638
|
+
const handlePaneClick = (0, import_react9.useCallback)(() => {
|
|
639
|
+
onClearSelection?.();
|
|
640
|
+
}, [onClearSelection]);
|
|
641
|
+
const deleteSelected = (0, import_react9.useCallback)(() => {
|
|
642
|
+
const selectedNodes = nodes.filter((n) => n.selected);
|
|
643
|
+
const selectedEdges = edges.filter((e) => e.selected);
|
|
644
|
+
if (selectedNodes.length === 0 && selectedEdges.length === 0) return;
|
|
645
|
+
const removedNodeIds = new Set(selectedNodes.map((n) => n.id));
|
|
646
|
+
const incidentByNodeId = /* @__PURE__ */ new Map();
|
|
647
|
+
const incidentEdgeIds = /* @__PURE__ */ new Set();
|
|
648
|
+
for (const node of selectedNodes) {
|
|
649
|
+
const incident = edges.filter((e) => e.source === node.id || e.target === node.id);
|
|
650
|
+
incidentByNodeId.set(node.id, incident);
|
|
651
|
+
for (const e of incident) incidentEdgeIds.add(e.id);
|
|
652
|
+
}
|
|
653
|
+
for (const node of selectedNodes) {
|
|
654
|
+
history.push({
|
|
655
|
+
kind: "delete-node",
|
|
656
|
+
node,
|
|
657
|
+
incidentEdges: incidentByNodeId.get(node.id) ?? []
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
for (const edge of selectedEdges) {
|
|
661
|
+
if (incidentEdgeIds.has(edge.id)) continue;
|
|
662
|
+
history.push({ kind: "delete-edge", edge });
|
|
663
|
+
}
|
|
664
|
+
for (const node of selectedNodes) onNodeDelete?.(node.id);
|
|
665
|
+
for (const edge of selectedEdges) {
|
|
666
|
+
if (incidentEdgeIds.has(edge.id)) continue;
|
|
667
|
+
onEdgeDelete?.(edge.id);
|
|
668
|
+
}
|
|
669
|
+
if (removedNodeIds.size > 0) {
|
|
670
|
+
setNodes((prev) => prev.filter((n) => !removedNodeIds.has(n.id)));
|
|
671
|
+
}
|
|
672
|
+
const droppedEdgeIds = new Set(incidentEdgeIds);
|
|
673
|
+
for (const e of selectedEdges) droppedEdgeIds.add(e.id);
|
|
674
|
+
if (droppedEdgeIds.size > 0) {
|
|
675
|
+
setEdges((prev) => prev.filter((e) => !droppedEdgeIds.has(e.id)));
|
|
676
|
+
}
|
|
677
|
+
}, [edges, history, nodes, onEdgeDelete, onNodeDelete, setEdges, setNodes]);
|
|
678
|
+
const applyCommand = (0, import_react9.useCallback)(
|
|
679
|
+
(command) => {
|
|
680
|
+
switch (command.kind) {
|
|
681
|
+
case "add-node":
|
|
682
|
+
setNodes((prev) => [...prev, command.node]);
|
|
683
|
+
onNodeAdd?.({
|
|
684
|
+
id: command.node.id,
|
|
685
|
+
position: command.node.position,
|
|
686
|
+
data: command.node.data ?? {}
|
|
687
|
+
});
|
|
688
|
+
return;
|
|
689
|
+
case "delete-node": {
|
|
690
|
+
const removedId = command.node.id;
|
|
691
|
+
setNodes((prev) => prev.filter((n) => n.id !== removedId));
|
|
692
|
+
const incidentIds = new Set(command.incidentEdges.map((e) => e.id));
|
|
693
|
+
setEdges((prev) => prev.filter((e) => !incidentIds.has(e.id)));
|
|
694
|
+
onNodeDelete?.(removedId);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
case "add-edge":
|
|
698
|
+
setEdges((prev) => [...prev, command.edge]);
|
|
699
|
+
onConnect?.({
|
|
700
|
+
id: command.edge.id,
|
|
701
|
+
source: command.edge.source,
|
|
702
|
+
target: command.edge.target
|
|
703
|
+
});
|
|
704
|
+
return;
|
|
705
|
+
case "delete-edge":
|
|
706
|
+
setEdges((prev) => prev.filter((e) => e.id !== command.edge.id));
|
|
707
|
+
onEdgeDelete?.(command.edge.id);
|
|
708
|
+
return;
|
|
709
|
+
case "move-node":
|
|
710
|
+
setNodes(
|
|
711
|
+
(prev) => prev.map((n) => n.id === command.id ? { ...n, position: command.to } : n)
|
|
712
|
+
);
|
|
713
|
+
onNodeMove?.(command.id, command.to);
|
|
714
|
+
return;
|
|
715
|
+
case "batch":
|
|
716
|
+
for (const child of command.commands) applyCommand(child);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
[onConnect, onEdgeDelete, onNodeAdd, onNodeDelete, onNodeMove, setEdges, setNodes]
|
|
721
|
+
);
|
|
722
|
+
const undo = (0, import_react9.useCallback)(() => {
|
|
723
|
+
const inverse = history.undo();
|
|
724
|
+
if (inverse) applyCommand(inverse);
|
|
725
|
+
}, [history, applyCommand]);
|
|
726
|
+
const redo = (0, import_react9.useCallback)(() => {
|
|
727
|
+
const cmd = history.redo();
|
|
728
|
+
if (cmd) applyCommand(cmd);
|
|
729
|
+
}, [history, applyCommand]);
|
|
730
|
+
const deleteFlushedRef = (0, import_react9.useRef)(false);
|
|
731
|
+
const onKeyDown = useGraphEditorKeyboard({
|
|
732
|
+
nodes,
|
|
733
|
+
edges,
|
|
734
|
+
onNodesChange: (changes) => {
|
|
735
|
+
const removals = changes.filter((c) => c.type === "remove");
|
|
736
|
+
if (removals.length > 0) {
|
|
737
|
+
if (!deleteFlushedRef.current) {
|
|
738
|
+
deleteSelected();
|
|
739
|
+
deleteFlushedRef.current = true;
|
|
740
|
+
queueMicrotask(() => {
|
|
741
|
+
deleteFlushedRef.current = false;
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
const remaining = changes.filter((c) => c.type !== "remove");
|
|
745
|
+
if (remaining.length > 0) baseOnNodesChange(remaining);
|
|
746
|
+
} else {
|
|
747
|
+
baseOnNodesChange(changes);
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
onEdgesChange: (changes) => {
|
|
751
|
+
const removals = changes.filter((c) => c.type === "remove");
|
|
752
|
+
if (removals.length > 0) {
|
|
753
|
+
if (!deleteFlushedRef.current) {
|
|
754
|
+
deleteSelected();
|
|
755
|
+
deleteFlushedRef.current = true;
|
|
756
|
+
queueMicrotask(() => {
|
|
757
|
+
deleteFlushedRef.current = false;
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
const remaining = changes.filter((c) => c.type !== "remove");
|
|
761
|
+
if (remaining.length > 0) baseOnEdgesChange(remaining);
|
|
762
|
+
} else {
|
|
763
|
+
baseOnEdgesChange(changes);
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
// Don't double-fire delete events — deleteSelected*() already does that.
|
|
767
|
+
onNodeDelete: void 0,
|
|
768
|
+
onEdgeDelete: void 0,
|
|
769
|
+
onNodeMove,
|
|
770
|
+
onArrowNudge: (id, from, to) => {
|
|
771
|
+
history.push({ kind: "move-node", id, from, to });
|
|
772
|
+
},
|
|
773
|
+
onClearSelection,
|
|
774
|
+
undo,
|
|
775
|
+
redo
|
|
776
|
+
});
|
|
777
|
+
const handleAddClick = (0, import_react9.useCallback)(() => {
|
|
778
|
+
const vp = getViewport();
|
|
779
|
+
const wrapper = wrapperRef.current;
|
|
780
|
+
const rect = wrapper?.getBoundingClientRect();
|
|
781
|
+
const position = rect ? screenToFlowPosition({
|
|
782
|
+
x: rect.left + rect.width / 2,
|
|
783
|
+
y: rect.top + rect.height / 2
|
|
784
|
+
}) : { x: -vp.x, y: -vp.y };
|
|
785
|
+
const id = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
786
|
+
const newNode = { id, position, data: {} };
|
|
787
|
+
setNodes((prev) => [...prev, newNode]);
|
|
788
|
+
history.push({ kind: "add-node", node: newNode });
|
|
789
|
+
onNodeAdd?.({ id, position, data: {} });
|
|
790
|
+
}, [getViewport, screenToFlowPosition, setNodes, history, onNodeAdd]);
|
|
791
|
+
(0, import_react9.useImperativeHandle)(forwardedRef, () => ({ refreshStyles: refresh, undo, redo }), [
|
|
792
|
+
refresh,
|
|
793
|
+
undo,
|
|
794
|
+
redo
|
|
795
|
+
]);
|
|
796
|
+
return (
|
|
797
|
+
// `role="application"` is the ARIA-recommended container for a custom
|
|
798
|
+
// widget that owns its keyboard semantics — the canvas implements its
|
|
799
|
+
// own arrow-nudge / Delete / Escape / ⌘Z bindings. eslint-jsx-a11y
|
|
800
|
+
// doesn't recognize `application` as interactive, so the rules below
|
|
801
|
+
// are disabled for this single element.
|
|
802
|
+
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
804
|
+
"div",
|
|
805
|
+
{
|
|
806
|
+
ref: wrapperRef,
|
|
807
|
+
role: "application",
|
|
808
|
+
"aria-label": ariaLabel,
|
|
809
|
+
tabIndex: 0,
|
|
810
|
+
"data-background": background,
|
|
811
|
+
className: ["ship-graph-editor", className].filter(Boolean).join(" "),
|
|
812
|
+
onKeyDown,
|
|
813
|
+
...rest,
|
|
814
|
+
children: [
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
816
|
+
import_react8.ReactFlow,
|
|
817
|
+
{
|
|
818
|
+
nodes,
|
|
819
|
+
edges,
|
|
820
|
+
nodeTypes,
|
|
821
|
+
edgeTypes,
|
|
822
|
+
onNodesChange: handleNodesChange,
|
|
823
|
+
onEdgesChange: handleEdgesChange,
|
|
824
|
+
onConnect: handleConnect,
|
|
825
|
+
onNodeClick: handleNodeClick,
|
|
826
|
+
onEdgeClick: handleEdgeClick,
|
|
827
|
+
onPaneClick: handlePaneClick,
|
|
828
|
+
panOnDrag: panZoom,
|
|
829
|
+
zoomOnScroll: panZoom,
|
|
830
|
+
zoomOnPinch: panZoom,
|
|
831
|
+
zoomOnDoubleClick: false,
|
|
832
|
+
deleteKeyCode: null,
|
|
833
|
+
proOptions: { hideAttribution: true },
|
|
834
|
+
fitView: true,
|
|
835
|
+
children: background === "dots" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Background, { variant: import_react8.BackgroundVariant.Dots, gap: 20, size: 1 })
|
|
836
|
+
}
|
|
837
|
+
),
|
|
838
|
+
toolbar ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute top-4 left-4 z-10", children: toolbar }) : onNodeAdd && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute top-4 left-4 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
839
|
+
"button",
|
|
840
|
+
{
|
|
841
|
+
type: "button",
|
|
842
|
+
onClick: handleAddClick,
|
|
843
|
+
"aria-label": "Add node",
|
|
844
|
+
className: "bg-panel border-border text-text hover:bg-panel-2 focus-visible:ring-accent-dim flex h-8 items-center gap-1 rounded-md border px-3 text-[12px] font-medium transition-colors outline-none focus-visible:ring-[3px]",
|
|
845
|
+
children: [
|
|
846
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_icons2.IconGlyph, { name: "add", size: 14 }),
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Add" })
|
|
848
|
+
]
|
|
849
|
+
}
|
|
850
|
+
) }),
|
|
851
|
+
inspector && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute top-4 right-4 z-10", children: inspector }),
|
|
852
|
+
miniMap && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute right-4 bottom-4 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GraphEditorMiniMap, {}) })
|
|
853
|
+
]
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
var GraphEditorCanvas = (0, import_react9.forwardRef)(
|
|
860
|
+
function GraphEditorCanvas2(props, ref) {
|
|
861
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.ReactFlowProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GraphEditorCanvasInner, { ...props, ref }) });
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
GraphEditorCanvas.displayName = "GraphEditorCanvas";
|
|
865
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
866
|
+
0 && (module.exports = {
|
|
867
|
+
DefaultNode,
|
|
868
|
+
GraphEditorCanvas,
|
|
869
|
+
GraphNodeShell,
|
|
870
|
+
toCytoscapeElements,
|
|
871
|
+
toFlowElements,
|
|
872
|
+
useGraphEditorTheme
|
|
873
|
+
});
|
|
874
|
+
//# sourceMappingURL=index.cjs.map
|