@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.
@@ -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