@pixerate/editor-react 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas/index.cjs +824 -0
- package/dist/canvas/index.cjs.map +1 -0
- package/dist/canvas/index.d.cts +150 -0
- package/dist/canvas/index.d.ts +150 -0
- package/dist/canvas/index.js +810 -0
- package/dist/canvas/index.js.map +1 -0
- package/dist/index.cjs +39 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/package.json +20 -2
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
// src/canvas/AgentPresenceLayer.tsx
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
function AgentPresenceLayer({ presences, className = "" }) {
|
|
4
|
+
if (!presences || presences.length === 0) return null;
|
|
5
|
+
return /* @__PURE__ */ jsx(
|
|
6
|
+
"div",
|
|
7
|
+
{
|
|
8
|
+
className: `pixerate-agent-presence-layer pointer-events-none absolute inset-0 z-50 overflow-hidden ${className}`,
|
|
9
|
+
"aria-hidden": "true",
|
|
10
|
+
children: presences.map((agent) => {
|
|
11
|
+
if (!agent.cursorPosition) return null;
|
|
12
|
+
const { x, y } = agent.cursorPosition;
|
|
13
|
+
const color = agent.color || "#3b82f6";
|
|
14
|
+
return /* @__PURE__ */ jsxs(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
className: "absolute transition-transform duration-75 ease-out",
|
|
18
|
+
style: {
|
|
19
|
+
transform: `translate3d(${x}px, ${y}px, 0)`
|
|
20
|
+
},
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ jsx(
|
|
23
|
+
"svg",
|
|
24
|
+
{
|
|
25
|
+
className: "h-5 w-5 drop-shadow-md",
|
|
26
|
+
viewBox: "0 0 24 24",
|
|
27
|
+
fill: "none",
|
|
28
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
29
|
+
children: /* @__PURE__ */ jsx(
|
|
30
|
+
"path",
|
|
31
|
+
{
|
|
32
|
+
d: "M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z",
|
|
33
|
+
fill: color,
|
|
34
|
+
stroke: "white",
|
|
35
|
+
strokeWidth: "1"
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
/* @__PURE__ */ jsxs(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: "mt-1 flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-md whitespace-nowrap",
|
|
44
|
+
style: { backgroundColor: color },
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx("span", { children: agent.name }),
|
|
47
|
+
agent.statusMessage && /* @__PURE__ */ jsxs("span", { className: "opacity-80 text-[10px]", children: [
|
|
48
|
+
"(",
|
|
49
|
+
agent.statusMessage,
|
|
50
|
+
")"
|
|
51
|
+
] })
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
agent.agentId
|
|
58
|
+
);
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/canvas/useStreamingCanvas.ts
|
|
65
|
+
import { useEffect, useRef, useCallback } from "react";
|
|
66
|
+
import {
|
|
67
|
+
StreamingLayoutManager
|
|
68
|
+
} from "@pixerate/editor/canvas";
|
|
69
|
+
function useStreamingCanvas(options) {
|
|
70
|
+
const managerRef = useRef(null);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const manager = new StreamingLayoutManager({
|
|
73
|
+
throttleMs: options.throttleMs,
|
|
74
|
+
glideDurationMs: options.glideDurationMs,
|
|
75
|
+
layoutOptions: options.layoutOptions,
|
|
76
|
+
onLayoutUpdated: options.onPositionsChange
|
|
77
|
+
});
|
|
78
|
+
managerRef.current = manager;
|
|
79
|
+
return () => {
|
|
80
|
+
manager.destroy();
|
|
81
|
+
managerRef.current = null;
|
|
82
|
+
};
|
|
83
|
+
}, [options.throttleMs, options.glideDurationMs, options.onPositionsChange]);
|
|
84
|
+
const pushStreamUpdate = useCallback(
|
|
85
|
+
(nodes, edges, currentPositions) => {
|
|
86
|
+
managerRef.current?.pushStreamUpdate(nodes, edges, currentPositions);
|
|
87
|
+
},
|
|
88
|
+
[]
|
|
89
|
+
);
|
|
90
|
+
const flush = useCallback(
|
|
91
|
+
(nodes, edges, currentPositions) => {
|
|
92
|
+
managerRef.current?.flush(nodes, edges, currentPositions);
|
|
93
|
+
},
|
|
94
|
+
[]
|
|
95
|
+
);
|
|
96
|
+
return {
|
|
97
|
+
pushStreamUpdate,
|
|
98
|
+
flush
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/canvas/useCanvasGraph.ts
|
|
103
|
+
import { useState, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
104
|
+
function useCanvasGraph(options = {}) {
|
|
105
|
+
const [nodes, setNodesState] = useState(options.initialNodes ?? []);
|
|
106
|
+
const [edges, setEdgesState] = useState(options.initialEdges ?? []);
|
|
107
|
+
const baselineNodesRef = useRef2(options.initialNodes ?? []);
|
|
108
|
+
const baselineEdgesRef = useRef2(options.initialEdges ?? []);
|
|
109
|
+
const setNodes = useCallback2(
|
|
110
|
+
(updater) => {
|
|
111
|
+
setNodesState((prev) => {
|
|
112
|
+
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
113
|
+
options.onNodesChange?.(next);
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
[options.onNodesChange]
|
|
118
|
+
);
|
|
119
|
+
const setEdges = useCallback2(
|
|
120
|
+
(updater) => {
|
|
121
|
+
setEdgesState((prev) => {
|
|
122
|
+
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
123
|
+
options.onEdgesChange?.(next);
|
|
124
|
+
return next;
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
[options.onEdgesChange]
|
|
128
|
+
);
|
|
129
|
+
const addNode = useCallback2(
|
|
130
|
+
(node) => {
|
|
131
|
+
setNodes((prev) => [...prev, node]);
|
|
132
|
+
},
|
|
133
|
+
[setNodes]
|
|
134
|
+
);
|
|
135
|
+
const removeNodes = useCallback2(
|
|
136
|
+
(ids) => {
|
|
137
|
+
const idSet = new Set(ids);
|
|
138
|
+
setNodes((prev) => prev.filter((n) => !idSet.has(n.id)));
|
|
139
|
+
setEdges((prev) => prev.filter((e) => !idSet.has(e.source) && !idSet.has(e.target)));
|
|
140
|
+
},
|
|
141
|
+
[setNodes, setEdges]
|
|
142
|
+
);
|
|
143
|
+
const updateNodeData = useCallback2(
|
|
144
|
+
(id, patch) => {
|
|
145
|
+
setNodes(
|
|
146
|
+
(prev) => prev.map((node) => {
|
|
147
|
+
if (node.id !== id) return node;
|
|
148
|
+
return {
|
|
149
|
+
...node,
|
|
150
|
+
data: { ...node.data, ...patch }
|
|
151
|
+
};
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
[setNodes]
|
|
156
|
+
);
|
|
157
|
+
const updateNodePosition = useCallback2(
|
|
158
|
+
(id, position) => {
|
|
159
|
+
setNodes(
|
|
160
|
+
(prev) => prev.map((node) => {
|
|
161
|
+
if (node.id !== id) return node;
|
|
162
|
+
return {
|
|
163
|
+
...node,
|
|
164
|
+
position: { ...position }
|
|
165
|
+
};
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
[setNodes]
|
|
170
|
+
);
|
|
171
|
+
const addEdge = useCallback2(
|
|
172
|
+
(edge) => {
|
|
173
|
+
setEdges((prev) => [...prev, edge]);
|
|
174
|
+
},
|
|
175
|
+
[setEdges]
|
|
176
|
+
);
|
|
177
|
+
const removeEdges = useCallback2(
|
|
178
|
+
(ids) => {
|
|
179
|
+
const idSet = new Set(ids);
|
|
180
|
+
setEdges((prev) => prev.filter((e) => !idSet.has(e.id)));
|
|
181
|
+
},
|
|
182
|
+
[setEdges]
|
|
183
|
+
);
|
|
184
|
+
const resetBaseline = useCallback2(() => {
|
|
185
|
+
baselineNodesRef.current = nodes;
|
|
186
|
+
baselineEdgesRef.current = edges;
|
|
187
|
+
}, [nodes, edges]);
|
|
188
|
+
const getDifferences = useCallback2(() => {
|
|
189
|
+
const currentNodesMap = new Map(nodes.map((n) => [n.id, n]));
|
|
190
|
+
const currentEdgesMap = new Map(edges.map((e) => [e.id, e]));
|
|
191
|
+
const baselineNodesMap = new Map(baselineNodesRef.current.map((n) => [n.id, n]));
|
|
192
|
+
const baselineEdgesMap = new Map(baselineEdgesRef.current.map((e) => [e.id, e]));
|
|
193
|
+
const changedNodes = [];
|
|
194
|
+
const removedNodes = [];
|
|
195
|
+
const changedEdges = [];
|
|
196
|
+
const removedEdges = [];
|
|
197
|
+
for (const [id, node] of currentNodesMap) {
|
|
198
|
+
const base = baselineNodesMap.get(id);
|
|
199
|
+
if (!base || base.position.x !== node.position.x || base.position.y !== node.position.y) {
|
|
200
|
+
changedNodes.push(node);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const [id, node] of baselineNodesMap) {
|
|
204
|
+
if (!currentNodesMap.has(id)) {
|
|
205
|
+
removedNodes.push(node);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const [id, edge] of currentEdgesMap) {
|
|
209
|
+
const base = baselineEdgesMap.get(id);
|
|
210
|
+
if (!base || base.source !== edge.source || base.target !== edge.target) {
|
|
211
|
+
changedEdges.push(edge);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const [id, edge] of baselineEdgesMap) {
|
|
215
|
+
if (!currentEdgesMap.has(id)) {
|
|
216
|
+
removedEdges.push(edge);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { changedNodes, changedEdges, removedNodes, removedEdges };
|
|
220
|
+
}, [nodes, edges]);
|
|
221
|
+
return {
|
|
222
|
+
nodes,
|
|
223
|
+
edges,
|
|
224
|
+
setNodes,
|
|
225
|
+
setEdges,
|
|
226
|
+
addNode,
|
|
227
|
+
removeNodes,
|
|
228
|
+
updateNodeData,
|
|
229
|
+
updateNodePosition,
|
|
230
|
+
addEdge,
|
|
231
|
+
removeEdges,
|
|
232
|
+
resetBaseline,
|
|
233
|
+
getDifferences
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/canvas/useCanvasLayout.ts
|
|
238
|
+
import { useCallback as useCallback3 } from "react";
|
|
239
|
+
import {
|
|
240
|
+
centerNodes,
|
|
241
|
+
ensureLayout,
|
|
242
|
+
getCenteredNodePosition,
|
|
243
|
+
getLayoutedNodes,
|
|
244
|
+
placeBeside
|
|
245
|
+
} from "@pixerate/editor/canvas";
|
|
246
|
+
function useCanvasLayout(options) {
|
|
247
|
+
const { nodes, edges, setNodes, getNodesBounds, screenToFlowPosition } = options;
|
|
248
|
+
const applyLayout = useCallback3(
|
|
249
|
+
(layoutOptions = {}) => {
|
|
250
|
+
const layouted = getLayoutedNodes(nodes, edges, getNodesBounds, layoutOptions);
|
|
251
|
+
setNodes(layouted);
|
|
252
|
+
return layouted;
|
|
253
|
+
},
|
|
254
|
+
[nodes, edges, getNodesBounds, setNodes]
|
|
255
|
+
);
|
|
256
|
+
const applyIncrementalLayout = useCallback3(
|
|
257
|
+
(incrementalOptions = {}) => {
|
|
258
|
+
const storedPositions = incrementalOptions.storedPositions ?? Object.fromEntries(nodes.map((n) => [n.id, n.position]));
|
|
259
|
+
const blended = ensureLayout(nodes, edges, {
|
|
260
|
+
...incrementalOptions,
|
|
261
|
+
storedPositions
|
|
262
|
+
});
|
|
263
|
+
setNodes(blended);
|
|
264
|
+
return blended;
|
|
265
|
+
},
|
|
266
|
+
[nodes, edges, setNodes]
|
|
267
|
+
);
|
|
268
|
+
const center = useCallback3(
|
|
269
|
+
(screenDimensions) => {
|
|
270
|
+
if (!screenToFlowPosition || !getNodesBounds) {
|
|
271
|
+
console.warn(
|
|
272
|
+
"[useCanvasLayout] Centering requires screenToFlowPosition and getNodesBounds options."
|
|
273
|
+
);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const centered = centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions);
|
|
277
|
+
setNodes(centered);
|
|
278
|
+
},
|
|
279
|
+
[nodes, screenToFlowPosition, getNodesBounds, setNodes]
|
|
280
|
+
);
|
|
281
|
+
const getPlacementPosition = useCallback3(
|
|
282
|
+
(size, side = "right", nearNodeId, placementOptions = {}) => {
|
|
283
|
+
const rects = {};
|
|
284
|
+
for (const node of nodes) {
|
|
285
|
+
const width = node.measured?.width ?? node.width ?? 200;
|
|
286
|
+
const height = node.measured?.height ?? node.height ?? 60;
|
|
287
|
+
rects[node.id] = {
|
|
288
|
+
x: node.position.x,
|
|
289
|
+
y: node.position.y,
|
|
290
|
+
width,
|
|
291
|
+
height
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return placeBeside(rects, size, side, nearNodeId, placementOptions);
|
|
295
|
+
},
|
|
296
|
+
[nodes]
|
|
297
|
+
);
|
|
298
|
+
return {
|
|
299
|
+
applyLayout,
|
|
300
|
+
applyIncrementalLayout,
|
|
301
|
+
center,
|
|
302
|
+
getPlacementPosition,
|
|
303
|
+
getCenteredNodePosition
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/canvas/useCanvasClipboard.ts
|
|
308
|
+
import { useCallback as useCallback4 } from "react";
|
|
309
|
+
import {
|
|
310
|
+
isEventFromTextInput,
|
|
311
|
+
isValidUrl,
|
|
312
|
+
parseCanvasClipboardData,
|
|
313
|
+
remapPastedNodes,
|
|
314
|
+
serializeCanvasNodes
|
|
315
|
+
} from "@pixerate/editor/canvas";
|
|
316
|
+
function useCanvasClipboard(options = {}) {
|
|
317
|
+
const copySelectedNodes = useCallback4(
|
|
318
|
+
(selectedNodes, connectedEdges, event) => {
|
|
319
|
+
const native = event && "nativeEvent" in event ? event.nativeEvent : event;
|
|
320
|
+
if (native && isEventFromTextInput(native)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
if (selectedNodes.length === 0) return false;
|
|
324
|
+
const serialized = serializeCanvasNodes(selectedNodes, connectedEdges);
|
|
325
|
+
const clipboardData = event?.clipboardData || native?.clipboardData;
|
|
326
|
+
if (clipboardData) {
|
|
327
|
+
clipboardData.setData("application/json", serialized);
|
|
328
|
+
clipboardData.setData("text/plain", serialized);
|
|
329
|
+
event?.preventDefault?.();
|
|
330
|
+
} else if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
331
|
+
navigator.clipboard.writeText(serialized).catch(() => {
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
},
|
|
336
|
+
[]
|
|
337
|
+
);
|
|
338
|
+
const handlePasteEvent = useCallback4(
|
|
339
|
+
async (event) => {
|
|
340
|
+
const native = "nativeEvent" in event ? event.nativeEvent : event;
|
|
341
|
+
if (isEventFromTextInput(native)) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const clipboardData = native?.clipboardData || event?.clipboardData;
|
|
345
|
+
if (!clipboardData) return false;
|
|
346
|
+
const prevent = () => {
|
|
347
|
+
event?.preventDefault?.();
|
|
348
|
+
native?.preventDefault?.();
|
|
349
|
+
};
|
|
350
|
+
const jsonText = clipboardData.getData("application/json");
|
|
351
|
+
if (jsonText) {
|
|
352
|
+
const parsed = parseCanvasClipboardData(jsonText);
|
|
353
|
+
if (parsed && parsed.nodes.length > 0) {
|
|
354
|
+
prevent();
|
|
355
|
+
const remapped = remapPastedNodes(parsed.nodes, parsed.edges);
|
|
356
|
+
await options.onPasteNodes?.(remapped.nodes, remapped.edges);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const text = clipboardData.getData("text/plain")?.trim();
|
|
361
|
+
if (text) {
|
|
362
|
+
const parsed = parseCanvasClipboardData(text);
|
|
363
|
+
if (parsed && parsed.nodes.length > 0) {
|
|
364
|
+
prevent();
|
|
365
|
+
const remapped = remapPastedNodes(parsed.nodes, parsed.edges);
|
|
366
|
+
await options.onPasteNodes?.(remapped.nodes, remapped.edges);
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
if (isValidUrl(text)) {
|
|
370
|
+
prevent();
|
|
371
|
+
options.onPasteUrl?.(text);
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (options.onPasteText) {
|
|
375
|
+
prevent();
|
|
376
|
+
options.onPasteText(text);
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
},
|
|
382
|
+
[options]
|
|
383
|
+
);
|
|
384
|
+
return {
|
|
385
|
+
copySelectedNodes,
|
|
386
|
+
handlePasteEvent
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/canvas/useCanvasShortcuts.ts
|
|
391
|
+
import { useEffect as useEffect2 } from "react";
|
|
392
|
+
import { isEventFromTextInput as isEventFromTextInput2 } from "@pixerate/editor/canvas";
|
|
393
|
+
function useCanvasShortcuts(options = {}) {
|
|
394
|
+
const {
|
|
395
|
+
targetElement,
|
|
396
|
+
enabled = true,
|
|
397
|
+
onCopy,
|
|
398
|
+
onCut,
|
|
399
|
+
onPaste,
|
|
400
|
+
onDelete,
|
|
401
|
+
onDuplicate,
|
|
402
|
+
onSelectAll,
|
|
403
|
+
onGroup,
|
|
404
|
+
onUngroup,
|
|
405
|
+
onZoomIn,
|
|
406
|
+
onZoomOut,
|
|
407
|
+
onFitView,
|
|
408
|
+
getSelectedNodes
|
|
409
|
+
} = options;
|
|
410
|
+
useEffect2(() => {
|
|
411
|
+
if (!enabled) return;
|
|
412
|
+
const target = targetElement ?? (typeof window !== "undefined" ? window : null);
|
|
413
|
+
if (!target) return;
|
|
414
|
+
function handleKeyDown(event) {
|
|
415
|
+
const keyboardEvent = event;
|
|
416
|
+
if (isEventFromTextInput2(keyboardEvent)) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
420
|
+
const isMod = isMac ? keyboardEvent.metaKey : keyboardEvent.ctrlKey;
|
|
421
|
+
const key = keyboardEvent.key.toLowerCase();
|
|
422
|
+
const selectedNodes = getSelectedNodes ? getSelectedNodes() : [];
|
|
423
|
+
if ((keyboardEvent.key === "Backspace" || keyboardEvent.key === "Delete") && !isMod) {
|
|
424
|
+
if (selectedNodes.length > 0 && onDelete) {
|
|
425
|
+
keyboardEvent.preventDefault();
|
|
426
|
+
onDelete(selectedNodes);
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (isMod && key === "d") {
|
|
431
|
+
if (selectedNodes.length > 0 && onDuplicate) {
|
|
432
|
+
keyboardEvent.preventDefault();
|
|
433
|
+
onDuplicate(selectedNodes);
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (isMod && key === "a" && onSelectAll) {
|
|
438
|
+
keyboardEvent.preventDefault();
|
|
439
|
+
onSelectAll();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (isMod && key === "g" && !keyboardEvent.shiftKey && onGroup) {
|
|
443
|
+
if (selectedNodes.length > 0) {
|
|
444
|
+
keyboardEvent.preventDefault();
|
|
445
|
+
onGroup(selectedNodes);
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (isMod && key === "g" && keyboardEvent.shiftKey && onUngroup) {
|
|
450
|
+
if (selectedNodes.length > 0) {
|
|
451
|
+
keyboardEvent.preventDefault();
|
|
452
|
+
onUngroup(selectedNodes);
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (isMod && (key === "+" || key === "=") && onZoomIn) {
|
|
457
|
+
keyboardEvent.preventDefault();
|
|
458
|
+
onZoomIn();
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (isMod && key === "-" && onZoomOut) {
|
|
462
|
+
keyboardEvent.preventDefault();
|
|
463
|
+
onZoomOut();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (isMod && key === "0" && onFitView) {
|
|
467
|
+
keyboardEvent.preventDefault();
|
|
468
|
+
onFitView();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
target.addEventListener("keydown", handleKeyDown);
|
|
473
|
+
return () => {
|
|
474
|
+
target.removeEventListener("keydown", handleKeyDown);
|
|
475
|
+
};
|
|
476
|
+
}, [
|
|
477
|
+
targetElement,
|
|
478
|
+
enabled,
|
|
479
|
+
onCopy,
|
|
480
|
+
onCut,
|
|
481
|
+
onPaste,
|
|
482
|
+
onDelete,
|
|
483
|
+
onDuplicate,
|
|
484
|
+
onSelectAll,
|
|
485
|
+
onGroup,
|
|
486
|
+
onUngroup,
|
|
487
|
+
onZoomIn,
|
|
488
|
+
onZoomOut,
|
|
489
|
+
onFitView,
|
|
490
|
+
getSelectedNodes
|
|
491
|
+
]);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/canvas/useCanvasDocking.ts
|
|
495
|
+
import { useCallback as useCallback5, useRef as useRef3 } from "react";
|
|
496
|
+
function useCanvasDocking(options = {}) {
|
|
497
|
+
const {
|
|
498
|
+
strategy = "center-point",
|
|
499
|
+
dockableTypes,
|
|
500
|
+
targetTypes,
|
|
501
|
+
onDockHover,
|
|
502
|
+
onDockLeave,
|
|
503
|
+
onDockDrop
|
|
504
|
+
} = options;
|
|
505
|
+
const activeDockTargetRef = useRef3(null);
|
|
506
|
+
const isDockable = useCallback5(
|
|
507
|
+
(node) => {
|
|
508
|
+
if (!dockableTypes || dockableTypes.length === 0) return true;
|
|
509
|
+
return !!node.type && dockableTypes.includes(node.type);
|
|
510
|
+
},
|
|
511
|
+
[dockableTypes]
|
|
512
|
+
);
|
|
513
|
+
const isTarget = useCallback5(
|
|
514
|
+
(node) => {
|
|
515
|
+
if (!targetTypes || targetTypes.length === 0) return true;
|
|
516
|
+
return !!node.type && targetTypes.includes(node.type);
|
|
517
|
+
},
|
|
518
|
+
[targetTypes]
|
|
519
|
+
);
|
|
520
|
+
const findDockTarget = useCallback5(
|
|
521
|
+
(draggedNode, candidates, cursorPos) => {
|
|
522
|
+
if (!isDockable(draggedNode)) return null;
|
|
523
|
+
const draggedWidth = draggedNode.measured?.width ?? draggedNode.width ?? 200;
|
|
524
|
+
const draggedHeight = draggedNode.measured?.height ?? draggedNode.height ?? 60;
|
|
525
|
+
const draggedCenter = {
|
|
526
|
+
x: draggedNode.position.x + draggedWidth / 2,
|
|
527
|
+
y: draggedNode.position.y + draggedHeight / 2
|
|
528
|
+
};
|
|
529
|
+
const validCandidates = candidates.filter(
|
|
530
|
+
(c) => c.id !== draggedNode.id && isTarget(c)
|
|
531
|
+
);
|
|
532
|
+
let bestTarget = null;
|
|
533
|
+
let maxIntersectionArea = 0;
|
|
534
|
+
let minDistance = Infinity;
|
|
535
|
+
for (const candidate of validCandidates) {
|
|
536
|
+
const candWidth = candidate.measured?.width ?? candidate.width ?? 200;
|
|
537
|
+
const candHeight = candidate.measured?.height ?? candidate.height ?? 60;
|
|
538
|
+
const candRect = {
|
|
539
|
+
x: candidate.position.x,
|
|
540
|
+
y: candidate.position.y,
|
|
541
|
+
width: candWidth,
|
|
542
|
+
height: candHeight
|
|
543
|
+
};
|
|
544
|
+
const overlapX = Math.max(
|
|
545
|
+
0,
|
|
546
|
+
Math.min(draggedNode.position.x + draggedWidth, candRect.x + candRect.width) - Math.max(draggedNode.position.x, candRect.x)
|
|
547
|
+
);
|
|
548
|
+
const overlapY = Math.max(
|
|
549
|
+
0,
|
|
550
|
+
Math.min(draggedNode.position.y + draggedHeight, candRect.y + candRect.height) - Math.max(draggedNode.position.y, candRect.y)
|
|
551
|
+
);
|
|
552
|
+
const overlapArea = overlapX * overlapY;
|
|
553
|
+
if (strategy === "max-overlap") {
|
|
554
|
+
if (overlapArea > maxIntersectionArea) {
|
|
555
|
+
maxIntersectionArea = overlapArea;
|
|
556
|
+
bestTarget = candidate;
|
|
557
|
+
}
|
|
558
|
+
} else if (strategy === "center-point") {
|
|
559
|
+
const candCenter = {
|
|
560
|
+
x: candRect.x + candRect.width / 2,
|
|
561
|
+
y: candRect.y + candRect.height / 2
|
|
562
|
+
};
|
|
563
|
+
const dist = Math.hypot(
|
|
564
|
+
draggedCenter.x - candCenter.x,
|
|
565
|
+
draggedCenter.y - candCenter.y
|
|
566
|
+
);
|
|
567
|
+
if (overlapArea > 0 && dist < minDistance) {
|
|
568
|
+
minDistance = dist;
|
|
569
|
+
bestTarget = candidate;
|
|
570
|
+
}
|
|
571
|
+
} else if (strategy === "first") {
|
|
572
|
+
if (overlapArea > 0) return candidate;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return bestTarget;
|
|
576
|
+
},
|
|
577
|
+
[isDockable, isTarget, strategy]
|
|
578
|
+
);
|
|
579
|
+
const updateDragOver = useCallback5(
|
|
580
|
+
(draggedNode, candidates, cursorPos) => {
|
|
581
|
+
const target = findDockTarget(draggedNode, candidates, cursorPos);
|
|
582
|
+
if (target?.id !== activeDockTargetRef.current?.id) {
|
|
583
|
+
if (activeDockTargetRef.current) {
|
|
584
|
+
onDockLeave?.(activeDockTargetRef.current, draggedNode);
|
|
585
|
+
}
|
|
586
|
+
activeDockTargetRef.current = target;
|
|
587
|
+
if (target) {
|
|
588
|
+
onDockHover?.(target, draggedNode);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
[findDockTarget, onDockHover, onDockLeave]
|
|
593
|
+
);
|
|
594
|
+
const finalizeDock = useCallback5(
|
|
595
|
+
(draggedNode) => {
|
|
596
|
+
if (activeDockTargetRef.current) {
|
|
597
|
+
onDockDrop?.(activeDockTargetRef.current, draggedNode);
|
|
598
|
+
activeDockTargetRef.current = null;
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
[onDockDrop]
|
|
602
|
+
);
|
|
603
|
+
return {
|
|
604
|
+
findDockTarget,
|
|
605
|
+
updateDragOver,
|
|
606
|
+
finalizeDock,
|
|
607
|
+
getActiveDockTarget: () => activeDockTargetRef.current
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/canvas/useCanvasExplosion.ts
|
|
612
|
+
import { useCallback as useCallback6, useRef as useRef4 } from "react";
|
|
613
|
+
import {
|
|
614
|
+
calculateDirectionalDisplacement,
|
|
615
|
+
createFanOutTrajectory,
|
|
616
|
+
linearPositionTrajectory,
|
|
617
|
+
runMultiNodeTransition
|
|
618
|
+
} from "@pixerate/editor/canvas";
|
|
619
|
+
function useCanvasExplosion(target) {
|
|
620
|
+
const snapshotsRef = useRef4(/* @__PURE__ */ new Map());
|
|
621
|
+
const explodeNode = useCallback6(
|
|
622
|
+
(parentNodeId, options) => {
|
|
623
|
+
const { nodes, edges, setNodes, setEdges } = target;
|
|
624
|
+
const parent = nodes.find((n) => n.id === parentNodeId);
|
|
625
|
+
if (!parent || options.childNodes.length === 0) return;
|
|
626
|
+
const originPos = { ...parent.position };
|
|
627
|
+
const displacedMap = options.displacementStrategy === "directional" ? calculateDirectionalDisplacement({
|
|
628
|
+
existingNodes: nodes,
|
|
629
|
+
originNode: parent,
|
|
630
|
+
childNodes: options.childNodes,
|
|
631
|
+
options: options.directionalOptions
|
|
632
|
+
}) : /* @__PURE__ */ new Map();
|
|
633
|
+
const snapshot = {
|
|
634
|
+
parentId: parentNodeId,
|
|
635
|
+
childNodeIds: options.childNodes.map((c) => c.id),
|
|
636
|
+
childEdgeIds: (options.childEdges ?? []).map((e) => e.id),
|
|
637
|
+
displacedNodes: Array.from(displacedMap.entries()).map(([id]) => {
|
|
638
|
+
const node = nodes.find((n) => n.id === id);
|
|
639
|
+
return { id, originalPosition: { ...node.position } };
|
|
640
|
+
}),
|
|
641
|
+
timestamp: Date.now()
|
|
642
|
+
};
|
|
643
|
+
snapshotsRef.current.set(parentNodeId, snapshot);
|
|
644
|
+
const initialChildren = options.childNodes.map((child) => ({
|
|
645
|
+
...child,
|
|
646
|
+
position: { ...originPos }
|
|
647
|
+
}));
|
|
648
|
+
const mergedNodes = [...nodes, ...initialChildren];
|
|
649
|
+
setNodes(mergedNodes);
|
|
650
|
+
if (options.childEdges && options.childEdges.length > 0) {
|
|
651
|
+
setEdges((prev) => [...prev, ...options.childEdges]);
|
|
652
|
+
}
|
|
653
|
+
const trajectory = options.trajectory ?? createFanOutTrajectory();
|
|
654
|
+
const transitions = [];
|
|
655
|
+
options.childNodes.forEach((child, index) => {
|
|
656
|
+
const liveChild = mergedNodes.find((n) => n.id === child.id);
|
|
657
|
+
if (liveChild) {
|
|
658
|
+
transitions.push({
|
|
659
|
+
node: liveChild,
|
|
660
|
+
from: originPos,
|
|
661
|
+
to: child.position,
|
|
662
|
+
trajectory,
|
|
663
|
+
index,
|
|
664
|
+
total: options.childNodes.length,
|
|
665
|
+
duration: options.duration ?? 400,
|
|
666
|
+
easing: options.easing
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
displacedMap.forEach((targetPos, nodeId) => {
|
|
671
|
+
const node = mergedNodes.find((n) => n.id === nodeId);
|
|
672
|
+
if (node) {
|
|
673
|
+
transitions.push({
|
|
674
|
+
node,
|
|
675
|
+
from: { ...node.position },
|
|
676
|
+
to: targetPos,
|
|
677
|
+
trajectory: linearPositionTrajectory,
|
|
678
|
+
duration: options.duration ?? 400,
|
|
679
|
+
easing: options.easing
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
runMultiNodeTransition(transitions, {
|
|
684
|
+
onUpdate: () => setNodes([...mergedNodes]),
|
|
685
|
+
onComplete: options.onComplete
|
|
686
|
+
});
|
|
687
|
+
},
|
|
688
|
+
[target]
|
|
689
|
+
);
|
|
690
|
+
const collapseNode = useCallback6(
|
|
691
|
+
(parentNodeId, options = {}) => {
|
|
692
|
+
const { nodes, setNodes, setEdges } = target;
|
|
693
|
+
const snapshot = snapshotsRef.current.get(parentNodeId);
|
|
694
|
+
if (!snapshot) return;
|
|
695
|
+
const parent = nodes.find((n) => n.id === parentNodeId);
|
|
696
|
+
const originPos = parent ? { ...parent.position } : { x: 0, y: 0 };
|
|
697
|
+
const childIdSet = new Set(snapshot.childNodeIds);
|
|
698
|
+
const edgeIdSet = new Set(snapshot.childEdgeIds);
|
|
699
|
+
const transitions = [];
|
|
700
|
+
nodes.forEach((node) => {
|
|
701
|
+
if (childIdSet.has(node.id)) {
|
|
702
|
+
transitions.push({
|
|
703
|
+
node,
|
|
704
|
+
from: { ...node.position },
|
|
705
|
+
to: originPos,
|
|
706
|
+
duration: options.duration ?? 300,
|
|
707
|
+
easing: options.easing,
|
|
708
|
+
trajectory: options.trajectory
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
snapshot.displacedNodes.forEach((disp) => {
|
|
713
|
+
const node = nodes.find((n) => n.id === disp.id);
|
|
714
|
+
if (node) {
|
|
715
|
+
transitions.push({
|
|
716
|
+
node,
|
|
717
|
+
from: { ...node.position },
|
|
718
|
+
to: disp.originalPosition,
|
|
719
|
+
duration: options.duration ?? 300,
|
|
720
|
+
easing: options.easing,
|
|
721
|
+
trajectory: linearPositionTrajectory
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
runMultiNodeTransition(transitions, {
|
|
726
|
+
onUpdate: () => setNodes([...nodes]),
|
|
727
|
+
onComplete: () => {
|
|
728
|
+
setNodes((prev) => prev.filter((n) => !childIdSet.has(n.id)));
|
|
729
|
+
setEdges((prev) => prev.filter((e) => !edgeIdSet.has(e.id)));
|
|
730
|
+
snapshotsRef.current.delete(parentNodeId);
|
|
731
|
+
options.onComplete?.();
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
},
|
|
735
|
+
[target]
|
|
736
|
+
);
|
|
737
|
+
return {
|
|
738
|
+
explodeNode,
|
|
739
|
+
collapseNode,
|
|
740
|
+
isExploded: (parentNodeId) => snapshotsRef.current.has(parentNodeId),
|
|
741
|
+
getSnapshot: (parentNodeId) => snapshotsRef.current.get(parentNodeId)
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/canvas/index.ts
|
|
746
|
+
import {
|
|
747
|
+
getLayoutedNodes as getLayoutedNodes2,
|
|
748
|
+
ensureLayout as ensureLayout2,
|
|
749
|
+
centerNodes as centerNodes2,
|
|
750
|
+
getCenteredNodePosition as getCenteredNodePosition2,
|
|
751
|
+
placeBeside as placeBeside2,
|
|
752
|
+
estimateNodeSize,
|
|
753
|
+
flowSide,
|
|
754
|
+
boundsOf,
|
|
755
|
+
centre,
|
|
756
|
+
overlaps,
|
|
757
|
+
linearTrajectory,
|
|
758
|
+
linearPositionTrajectory as linearPositionTrajectory2,
|
|
759
|
+
createFanOutTrajectory as createFanOutTrajectory2,
|
|
760
|
+
createBezierTrajectory,
|
|
761
|
+
runMultiNodeTransition as runMultiNodeTransition2,
|
|
762
|
+
defaultEasing,
|
|
763
|
+
computeNodesBoundingBox,
|
|
764
|
+
calculateDirectionalDisplacement as calculateDirectionalDisplacement2,
|
|
765
|
+
calculateDagreReflowDisplacement,
|
|
766
|
+
isEventFromTextInput as isEventFromTextInput3,
|
|
767
|
+
isValidCanvasNode,
|
|
768
|
+
isValidUrl as isValidUrl2,
|
|
769
|
+
serializeCanvasNodes as serializeCanvasNodes2,
|
|
770
|
+
parseCanvasClipboardData as parseCanvasClipboardData2,
|
|
771
|
+
remapPastedNodes as remapPastedNodes2,
|
|
772
|
+
repositionNodesTo
|
|
773
|
+
} from "@pixerate/editor/canvas";
|
|
774
|
+
export {
|
|
775
|
+
AgentPresenceLayer,
|
|
776
|
+
boundsOf,
|
|
777
|
+
calculateDagreReflowDisplacement,
|
|
778
|
+
calculateDirectionalDisplacement2 as calculateDirectionalDisplacement,
|
|
779
|
+
centerNodes2 as centerNodes,
|
|
780
|
+
centre,
|
|
781
|
+
computeNodesBoundingBox,
|
|
782
|
+
createBezierTrajectory,
|
|
783
|
+
createFanOutTrajectory2 as createFanOutTrajectory,
|
|
784
|
+
defaultEasing,
|
|
785
|
+
ensureLayout2 as ensureLayout,
|
|
786
|
+
estimateNodeSize,
|
|
787
|
+
flowSide,
|
|
788
|
+
getCenteredNodePosition2 as getCenteredNodePosition,
|
|
789
|
+
getLayoutedNodes2 as getLayoutedNodes,
|
|
790
|
+
isEventFromTextInput3 as isEventFromTextInput,
|
|
791
|
+
isValidCanvasNode,
|
|
792
|
+
isValidUrl2 as isValidUrl,
|
|
793
|
+
linearPositionTrajectory2 as linearPositionTrajectory,
|
|
794
|
+
linearTrajectory,
|
|
795
|
+
overlaps,
|
|
796
|
+
parseCanvasClipboardData2 as parseCanvasClipboardData,
|
|
797
|
+
placeBeside2 as placeBeside,
|
|
798
|
+
remapPastedNodes2 as remapPastedNodes,
|
|
799
|
+
repositionNodesTo,
|
|
800
|
+
runMultiNodeTransition2 as runMultiNodeTransition,
|
|
801
|
+
serializeCanvasNodes2 as serializeCanvasNodes,
|
|
802
|
+
useCanvasClipboard,
|
|
803
|
+
useCanvasDocking,
|
|
804
|
+
useCanvasExplosion,
|
|
805
|
+
useCanvasGraph,
|
|
806
|
+
useCanvasLayout,
|
|
807
|
+
useCanvasShortcuts,
|
|
808
|
+
useStreamingCanvas
|
|
809
|
+
};
|
|
810
|
+
//# sourceMappingURL=index.js.map
|