@pixerate/editor-svelte 0.3.12 → 0.5.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.d.ts +572 -0
- package/dist/canvas/index.js +1197 -0
- package/dist/canvas/index.js.map +1 -0
- package/package.json +24 -1
- package/src/canvas/README.md +167 -0
- package/src/canvas/__tests__/canvas-clipboard.test.ts +154 -0
- package/src/canvas/__tests__/canvas-docking.test.ts +136 -0
- package/src/canvas/__tests__/canvas-explosion.test.ts +249 -0
- package/src/canvas/__tests__/canvas-graph.test.ts +159 -0
- package/src/canvas/__tests__/canvas-interactions.test.ts +86 -0
- package/src/canvas/__tests__/canvas-layout.test.ts +75 -0
- package/src/canvas/__tests__/canvas-shortcuts.test.ts +59 -0
- package/src/canvas/createCanvasClipboard.svelte.ts +219 -0
- package/src/canvas/createCanvasDisplacement.ts +226 -0
- package/src/canvas/createCanvasDocking.svelte.ts +145 -0
- package/src/canvas/createCanvasExplosion.svelte.ts +368 -0
- package/src/canvas/createCanvasGraph.svelte.ts +330 -0
- package/src/canvas/createCanvasInteractions.svelte.ts +112 -0
- package/src/canvas/createCanvasLayout.ts +120 -0
- package/src/canvas/createCanvasShortcuts.ts +99 -0
- package/src/canvas/createCanvasTrajectory.ts +188 -0
- package/src/canvas/index.d.ts +572 -0
- package/src/canvas/index.ts +68 -0
- package/src/canvas/types.ts +212 -0
|
@@ -0,0 +1,1197 @@
|
|
|
1
|
+
// src/canvas/createCanvasGraph.svelte.ts
|
|
2
|
+
import * as $ from "svelte/internal/client";
|
|
3
|
+
import { cubicOut } from "svelte/easing";
|
|
4
|
+
function calculateGraphDifferences(currentState, baselineState) {
|
|
5
|
+
const currentNodes = currentState.nodes ?? [];
|
|
6
|
+
const currentEdges = currentState.edges ?? [];
|
|
7
|
+
const baselineNodes = baselineState.nodes ?? [];
|
|
8
|
+
const baselineEdges = baselineState.edges ?? [];
|
|
9
|
+
const baselineNodeMap = new Map(baselineNodes.map((n) => [n.id, n]));
|
|
10
|
+
const baselineEdgeMap = new Map(baselineEdges.map((e) => [e.id, e]));
|
|
11
|
+
const currentNodeMap = new Map(currentNodes.map((n) => [n.id, n]));
|
|
12
|
+
const currentEdgeMap = new Map(currentEdges.map((e) => [e.id, e]));
|
|
13
|
+
const removedNodes = baselineNodes.filter((oldNode) => !currentNodeMap.has(oldNode.id));
|
|
14
|
+
const removedEdges = baselineEdges.filter((oldEdge) => !currentEdgeMap.has(oldEdge.id));
|
|
15
|
+
const changedNodes = currentNodes.filter((newNode) => {
|
|
16
|
+
const oldNode = baselineNodeMap.get(newNode.id);
|
|
17
|
+
if (!oldNode) return true;
|
|
18
|
+
return JSON.stringify(newNode) !== JSON.stringify(oldNode);
|
|
19
|
+
});
|
|
20
|
+
const changedEdges = currentEdges.filter((newEdge) => {
|
|
21
|
+
const oldEdge = baselineEdgeMap.get(newEdge.id);
|
|
22
|
+
if (!oldEdge) return true;
|
|
23
|
+
return JSON.stringify(newEdge) !== JSON.stringify(oldEdge);
|
|
24
|
+
});
|
|
25
|
+
return { changedNodes, changedEdges, removedNodes, removedEdges };
|
|
26
|
+
}
|
|
27
|
+
function replaceNodeInGraph(nodes, edges, oldNode, newNode, keepEdges = false) {
|
|
28
|
+
const updatedNodes = nodes.map((n) => n.id === oldNode.id ? newNode : n);
|
|
29
|
+
if (!keepEdges) {
|
|
30
|
+
return { nodes: updatedNodes, edges };
|
|
31
|
+
}
|
|
32
|
+
const updatedEdges = edges.map((edge) => {
|
|
33
|
+
if (edge.source === oldNode.id || edge.target === oldNode.id) {
|
|
34
|
+
return {
|
|
35
|
+
...edge,
|
|
36
|
+
id: crypto.randomUUID(),
|
|
37
|
+
source: edge.source === oldNode.id ? newNode.id : edge.source,
|
|
38
|
+
target: edge.target === oldNode.id ? newNode.id : edge.target
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return edge;
|
|
42
|
+
});
|
|
43
|
+
return { nodes: updatedNodes, edges: updatedEdges };
|
|
44
|
+
}
|
|
45
|
+
function createCanvasGraph(options = {}) {
|
|
46
|
+
let nodes = $.state(options.initialNodes ?? []);
|
|
47
|
+
let edges = $.state(options.initialEdges ?? []);
|
|
48
|
+
function setNodes(newNodes, triggerCallback = true) {
|
|
49
|
+
$.set(nodes, newNodes);
|
|
50
|
+
if (triggerCallback) {
|
|
51
|
+
options.onNodesChange?.($.get(nodes));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function setEdges(newEdges, triggerCallback = true) {
|
|
55
|
+
$.set(edges, newEdges);
|
|
56
|
+
if (triggerCallback) {
|
|
57
|
+
options.onEdgesChange?.($.get(edges));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function animateNodePosition(node, targetPosition, duration = 400, easing = cubicOut) {
|
|
61
|
+
const delay = node.data?.animationDelay ?? 0;
|
|
62
|
+
const startPosition = { ...node.position };
|
|
63
|
+
const startTime = Date.now() + delay;
|
|
64
|
+
function step() {
|
|
65
|
+
const currentTime = Date.now();
|
|
66
|
+
if (currentTime < startTime) {
|
|
67
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
68
|
+
requestAnimationFrame(step);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
73
|
+
const eased = easing(progress);
|
|
74
|
+
const currentPos = {
|
|
75
|
+
x: startPosition.x + (targetPosition.x - startPosition.x) * eased,
|
|
76
|
+
y: startPosition.y + (targetPosition.y - startPosition.y) * eased
|
|
77
|
+
};
|
|
78
|
+
const targetNode = $.get(nodes).find((n) => n.id === node.id);
|
|
79
|
+
if (targetNode) {
|
|
80
|
+
targetNode.position = currentPos;
|
|
81
|
+
}
|
|
82
|
+
if (progress < 1) {
|
|
83
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
84
|
+
requestAnimationFrame(step);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
setNodes([...$.get(nodes)]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
step();
|
|
91
|
+
}
|
|
92
|
+
function addNodeOrNodes(nodeOrNodesToAdd, animateFromPosition, save = true) {
|
|
93
|
+
const toAdd = Array.isArray(nodeOrNodesToAdd) ? nodeOrNodesToAdd : [nodeOrNodesToAdd];
|
|
94
|
+
const existingMap = new Map($.get(nodes).map((n) => [n.id, n]));
|
|
95
|
+
toAdd.forEach((node) => {
|
|
96
|
+
if (!existingMap.has(node.id)) {
|
|
97
|
+
existingMap.set(node.id, node);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
setNodes(Array.from(existingMap.values()));
|
|
101
|
+
if (animateFromPosition) {
|
|
102
|
+
toAdd.forEach((node) => {
|
|
103
|
+
const target = { ...node.position };
|
|
104
|
+
node.position = { ...animateFromPosition };
|
|
105
|
+
animateNodePosition(node, target);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (save) options.onSave?.();
|
|
109
|
+
}
|
|
110
|
+
function addEdge(edgeToAdd, save = true) {
|
|
111
|
+
setEdges([...$.get(edges), edgeToAdd]);
|
|
112
|
+
if (save) options.onSave?.();
|
|
113
|
+
}
|
|
114
|
+
function addEdges(edgesToAdd, save = true) {
|
|
115
|
+
setEdges([...$.get(edges), ...edgesToAdd]);
|
|
116
|
+
if (save) options.onSave?.();
|
|
117
|
+
}
|
|
118
|
+
function removeNodes(nodesToRemove, save = true) {
|
|
119
|
+
const idsToRemove = new Set(nodesToRemove.map((n) => n.id));
|
|
120
|
+
setNodes($.get(nodes).filter((n) => !idsToRemove.has(n.id)));
|
|
121
|
+
if (save) options.onSave?.();
|
|
122
|
+
}
|
|
123
|
+
function removeEdges(edgesToRemove, save = true) {
|
|
124
|
+
const idsToRemove = new Set(edgesToRemove.map((e) => e.id));
|
|
125
|
+
setEdges($.get(edges).filter((e) => !idsToRemove.has(e.id)));
|
|
126
|
+
if (save) options.onSave?.();
|
|
127
|
+
}
|
|
128
|
+
function replaceNode(oldNode, newNode, save = true, keepEdges = false) {
|
|
129
|
+
const result = replaceNodeInGraph($.get(nodes), $.get(edges), oldNode, newNode, keepEdges);
|
|
130
|
+
setNodes(result.nodes);
|
|
131
|
+
if (result.edges !== $.get(edges)) {
|
|
132
|
+
setEdges(result.edges);
|
|
133
|
+
}
|
|
134
|
+
if (save) options.onSave?.();
|
|
135
|
+
}
|
|
136
|
+
function addConnectedNodes(params) {
|
|
137
|
+
const {
|
|
138
|
+
nodesToAdd,
|
|
139
|
+
inboundNodes = [],
|
|
140
|
+
outboundNodes = [],
|
|
141
|
+
animatedEdges = false,
|
|
142
|
+
animateFromPosition,
|
|
143
|
+
inboundEdgeData = {},
|
|
144
|
+
outboundEdgeData = {},
|
|
145
|
+
getEdgeLabel
|
|
146
|
+
} = params;
|
|
147
|
+
const defaultCreateEdge = (sourceId, targetId, opt) => {
|
|
148
|
+
if (options.createEdge) {
|
|
149
|
+
return options.createEdge(sourceId, targetId, opt);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
id: crypto.randomUUID(),
|
|
153
|
+
type: "default",
|
|
154
|
+
source: sourceId,
|
|
155
|
+
target: targetId,
|
|
156
|
+
label: opt?.label ?? "",
|
|
157
|
+
animated: opt?.animated ?? false,
|
|
158
|
+
data: opt?.data ?? {}
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
nodesToAdd.forEach((addedNode) => {
|
|
162
|
+
addNodeOrNodes(addedNode, animateFromPosition, false);
|
|
163
|
+
inboundNodes.forEach((sourceNode) => {
|
|
164
|
+
const label = getEdgeLabel ? getEdgeLabel(sourceNode, addedNode) : "";
|
|
165
|
+
addEdge(defaultCreateEdge(sourceNode.id, addedNode.id, { label, animated: animatedEdges, data: inboundEdgeData }), false);
|
|
166
|
+
});
|
|
167
|
+
outboundNodes.forEach((targetNode) => {
|
|
168
|
+
const label = getEdgeLabel ? getEdgeLabel(addedNode, targetNode) : "";
|
|
169
|
+
addEdge(defaultCreateEdge(addedNode.id, targetNode.id, { label, animated: animatedEdges, data: outboundEdgeData }), false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
options.onSave?.();
|
|
173
|
+
}
|
|
174
|
+
function getDifferences(baseline) {
|
|
175
|
+
return calculateGraphDifferences({ nodes: $.get(nodes), edges: $.get(edges) }, baseline);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
get nodes() {
|
|
179
|
+
return $.get(nodes);
|
|
180
|
+
},
|
|
181
|
+
set nodes(val) {
|
|
182
|
+
setNodes(val);
|
|
183
|
+
},
|
|
184
|
+
get edges() {
|
|
185
|
+
return $.get(edges);
|
|
186
|
+
},
|
|
187
|
+
set edges(val) {
|
|
188
|
+
setEdges(val);
|
|
189
|
+
},
|
|
190
|
+
setNodes,
|
|
191
|
+
setEdges,
|
|
192
|
+
addNodeOrNodes,
|
|
193
|
+
addEdge,
|
|
194
|
+
addEdges,
|
|
195
|
+
removeNodes,
|
|
196
|
+
removeEdges,
|
|
197
|
+
replaceNode,
|
|
198
|
+
addConnectedNodes,
|
|
199
|
+
animateNodePosition,
|
|
200
|
+
getDifferences
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/canvas/createCanvasLayout.ts
|
|
205
|
+
import dagre from "@dagrejs/dagre";
|
|
206
|
+
import { Position } from "@xyflow/svelte";
|
|
207
|
+
function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
|
|
208
|
+
const rect = screenDimensions ?? (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 });
|
|
209
|
+
const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
|
|
210
|
+
return {
|
|
211
|
+
x: flowPos.x - width / 2,
|
|
212
|
+
y: flowPos.y - height / 2
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions) {
|
|
216
|
+
if (nodes.length === 0) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const bounds = getNodesBounds(nodes);
|
|
220
|
+
const boundingBoxOrigin = { x: bounds.x, y: bounds.y };
|
|
221
|
+
const centeredPosition = getCenteredNodePosition(
|
|
222
|
+
bounds.width,
|
|
223
|
+
bounds.height,
|
|
224
|
+
screenToFlowPosition,
|
|
225
|
+
screenDimensions
|
|
226
|
+
);
|
|
227
|
+
const deltaX = centeredPosition.x - boundingBoxOrigin.x;
|
|
228
|
+
const deltaY = centeredPosition.y - boundingBoxOrigin.y;
|
|
229
|
+
return nodes.map((node) => ({
|
|
230
|
+
...node,
|
|
231
|
+
position: {
|
|
232
|
+
x: node.position.x + deltaX,
|
|
233
|
+
y: node.position.y + deltaY
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
|
|
238
|
+
if (nodes.length === 0) return [];
|
|
239
|
+
const {
|
|
240
|
+
direction = "LR",
|
|
241
|
+
ranksep = 100,
|
|
242
|
+
nodesep = 25,
|
|
243
|
+
defaultNodeWidth = 200,
|
|
244
|
+
defaultNodeHeight = 200
|
|
245
|
+
} = options;
|
|
246
|
+
const isHorizontal = direction === "LR" || direction === "RL";
|
|
247
|
+
const bounds = getNodesBounds(nodes);
|
|
248
|
+
const dagreGraph = new dagre.graphlib.Graph();
|
|
249
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
250
|
+
dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
|
|
251
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
252
|
+
nodes.forEach((node) => {
|
|
253
|
+
const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
|
|
254
|
+
const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
|
|
255
|
+
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
|
256
|
+
});
|
|
257
|
+
edges.forEach((edge) => {
|
|
258
|
+
if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
|
|
259
|
+
dagreGraph.setEdge(edge.source, edge.target);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
dagre.layout(dagreGraph);
|
|
263
|
+
return nodes.map((node) => {
|
|
264
|
+
const nodeWithPosition = dagreGraph.node(node.id);
|
|
265
|
+
const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
|
|
266
|
+
const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
|
|
267
|
+
return {
|
|
268
|
+
...node,
|
|
269
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
270
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
271
|
+
position: {
|
|
272
|
+
x: bounds.x + (nodeWithPosition?.x ?? 0) - nodeWidth / 2,
|
|
273
|
+
y: bounds.y + (nodeWithPosition?.y ?? 0) - nodeHeight / 2
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/canvas/createCanvasClipboard.svelte.ts
|
|
280
|
+
import * as $2 from "svelte/internal/client";
|
|
281
|
+
import { tick } from "svelte";
|
|
282
|
+
function isEventFromTextInput(event) {
|
|
283
|
+
const target = event.target;
|
|
284
|
+
if (!target) return false;
|
|
285
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable || target.closest?.('[contenteditable="true"]') || target.closest?.(".ProseMirror")) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
function isValidCanvasNode(obj) {
|
|
291
|
+
return typeof obj === "object" && obj !== null && "id" in obj && typeof obj.id === "string" && "position" in obj && typeof obj.position === "object" && obj.position !== null && "x" in obj.position && "y" in obj.position;
|
|
292
|
+
}
|
|
293
|
+
function isValidUrl(url) {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = new URL(url);
|
|
296
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function createCanvasClipboard(options = {}) {
|
|
302
|
+
function copySelectedNodes(selectedNodes, connectedEdges, event) {
|
|
303
|
+
if (event && isEventFromTextInput(event)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
if (selectedNodes.length === 0) return false;
|
|
307
|
+
const payload = { version: 1, nodes: selectedNodes, edges: connectedEdges };
|
|
308
|
+
const serialized = JSON.stringify(payload);
|
|
309
|
+
if (event?.clipboardData) {
|
|
310
|
+
event.clipboardData.setData("application/json", serialized);
|
|
311
|
+
event.clipboardData.setData("text/plain", serialized);
|
|
312
|
+
event.preventDefault();
|
|
313
|
+
} else if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
314
|
+
navigator.clipboard.writeText(serialized).catch(() => {
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
function cutSelectedNodes(selectedNodes, connectedEdges, onRemove, event) {
|
|
320
|
+
if (event && isEventFromTextInput(event)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
const copied = copySelectedNodes(selectedNodes, connectedEdges, event);
|
|
324
|
+
if (copied && onRemove) {
|
|
325
|
+
onRemove(selectedNodes);
|
|
326
|
+
}
|
|
327
|
+
return copied;
|
|
328
|
+
}
|
|
329
|
+
async function handlePasteEvent(event) {
|
|
330
|
+
if (isEventFromTextInput(event)) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const clipboardData = event.clipboardData;
|
|
334
|
+
if (!clipboardData) return false;
|
|
335
|
+
const files = clipboardData.files;
|
|
336
|
+
if (files && files.length > 0) {
|
|
337
|
+
const imageFile = Array.from(files).find((f) => f.type.startsWith("image/"));
|
|
338
|
+
if (imageFile && options.onPasteImage) {
|
|
339
|
+
event.preventDefault();
|
|
340
|
+
await options.onPasteImage(imageFile);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const rawJson = clipboardData.getData("application/json") || clipboardData.getData("text/plain");
|
|
345
|
+
if (rawJson) {
|
|
346
|
+
try {
|
|
347
|
+
const parsed = JSON.parse(rawJson);
|
|
348
|
+
let rawNodes;
|
|
349
|
+
let rawEdges;
|
|
350
|
+
if (Array.isArray(parsed) && parsed.every(isValidCanvasNode)) {
|
|
351
|
+
rawNodes = parsed;
|
|
352
|
+
} else if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.nodes) && parsed.nodes.every(isValidCanvasNode)) {
|
|
353
|
+
rawNodes = parsed.nodes;
|
|
354
|
+
rawEdges = parsed.edges;
|
|
355
|
+
}
|
|
356
|
+
if (rawNodes && rawNodes.length > 0) {
|
|
357
|
+
event.preventDefault();
|
|
358
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
359
|
+
const newNodes = rawNodes.map((node) => {
|
|
360
|
+
const newId = crypto.randomUUID();
|
|
361
|
+
idMap.set(node.id, newId);
|
|
362
|
+
return {
|
|
363
|
+
...node,
|
|
364
|
+
id: newId,
|
|
365
|
+
selected: true,
|
|
366
|
+
position: { x: node.position.x + 40, y: node.position.y + 40 }
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
const newEdges = rawEdges?.map((edge) => ({
|
|
370
|
+
...edge,
|
|
371
|
+
id: crypto.randomUUID(),
|
|
372
|
+
source: idMap.get(edge.source) ?? edge.source,
|
|
373
|
+
target: idMap.get(edge.target) ?? edge.target
|
|
374
|
+
}));
|
|
375
|
+
if (options.onPasteNodes) {
|
|
376
|
+
await options.onPasteNodes(newNodes, newEdges);
|
|
377
|
+
await tick();
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const text = clipboardData.getData("text/plain")?.trim();
|
|
385
|
+
if (text) {
|
|
386
|
+
if (isValidUrl(text) && options.onPasteUrl) {
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
options.onPasteUrl(text);
|
|
389
|
+
return true;
|
|
390
|
+
} else if (options.onPasteText) {
|
|
391
|
+
event.preventDefault();
|
|
392
|
+
options.onPasteText(text);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
return { copySelectedNodes, cutSelectedNodes, handlePasteEvent };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/canvas/createCanvasDocking.svelte.ts
|
|
402
|
+
import * as $3 from "svelte/internal/client";
|
|
403
|
+
function createCanvasDocking(options = {}) {
|
|
404
|
+
let draggedNode = $3.state(null);
|
|
405
|
+
let draggedInitialPosition = $3.state(null);
|
|
406
|
+
let connectingHandle = $3.state("");
|
|
407
|
+
let connectingNode = $3.state(null);
|
|
408
|
+
let currentHoverTargets = $3.state(/* @__PURE__ */ new Set());
|
|
409
|
+
const isDockTarget = options.isDockTarget ?? ((target) => target.type === "boardNode");
|
|
410
|
+
function handleNodeDragStart(params) {
|
|
411
|
+
$3.set(draggedNode, params.targetNode);
|
|
412
|
+
$3.set(draggedInitialPosition, params.targetNode?.position ? { ...params.targetNode.position } : null);
|
|
413
|
+
$3.set(currentHoverTargets, /* @__PURE__ */ new Set());
|
|
414
|
+
}
|
|
415
|
+
function handleNodeDrag(params, getIntersectingNodes) {
|
|
416
|
+
$3.set(draggedNode, params.targetNode);
|
|
417
|
+
if (!$3.get(draggedNode)) return;
|
|
418
|
+
const intersecting = getIntersectingNodes($3.get(draggedNode));
|
|
419
|
+
const currentDockTargets = intersecting.filter(isDockTarget);
|
|
420
|
+
const newHoverSet = new Set(currentDockTargets.map((t) => t.id));
|
|
421
|
+
$3.get(currentHoverTargets).forEach((targetId) => {
|
|
422
|
+
if (!newHoverSet.has(targetId)) {
|
|
423
|
+
const leftNode = intersecting.find((n) => n.id === targetId) ?? { id: targetId };
|
|
424
|
+
options.onDockLeave?.(leftNode, $3.get(draggedNode));
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
currentDockTargets.forEach((target) => {
|
|
428
|
+
if (!$3.get(currentHoverTargets).has(target.id)) {
|
|
429
|
+
options.onDockHover?.(target, $3.get(draggedNode));
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
$3.set(currentHoverTargets, newHoverSet);
|
|
433
|
+
}
|
|
434
|
+
function handleNodeDragStop(params, getIntersectingNodes, updateNodePosition) {
|
|
435
|
+
$3.set(draggedNode, params.targetNode);
|
|
436
|
+
if ($3.get(draggedNode)) {
|
|
437
|
+
const intersecting = getIntersectingNodes($3.get(draggedNode));
|
|
438
|
+
const dockTarget = intersecting.find(isDockTarget);
|
|
439
|
+
if (dockTarget) {
|
|
440
|
+
options.onDockDrop?.(dockTarget, $3.get(draggedNode));
|
|
441
|
+
if (options.restorePositionOnDock !== false && $3.get(draggedInitialPosition) && updateNodePosition) {
|
|
442
|
+
updateNodePosition($3.get(draggedNode).id, $3.get(draggedInitialPosition));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
$3.set(draggedNode, null);
|
|
447
|
+
$3.set(draggedInitialPosition, null);
|
|
448
|
+
$3.set(currentHoverTargets, /* @__PURE__ */ new Set());
|
|
449
|
+
options.onSave?.();
|
|
450
|
+
}
|
|
451
|
+
function handleConnectStart(params, getNode) {
|
|
452
|
+
$3.set(connectingHandle, params.handleType ?? "");
|
|
453
|
+
$3.set(connectingNode, params.nodeId ? getNode(params.nodeId) ?? null : null);
|
|
454
|
+
}
|
|
455
|
+
function handleConnectEnd() {
|
|
456
|
+
$3.set(connectingHandle, "");
|
|
457
|
+
$3.set(connectingNode, null);
|
|
458
|
+
}
|
|
459
|
+
function reset() {
|
|
460
|
+
$3.set(draggedNode, null);
|
|
461
|
+
$3.set(draggedInitialPosition, null);
|
|
462
|
+
$3.set(connectingHandle, "");
|
|
463
|
+
$3.set(connectingNode, null);
|
|
464
|
+
$3.set(currentHoverTargets, /* @__PURE__ */ new Set());
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
get draggedNode() {
|
|
468
|
+
return $3.get(draggedNode);
|
|
469
|
+
},
|
|
470
|
+
get draggedInitialPosition() {
|
|
471
|
+
return $3.get(draggedInitialPosition);
|
|
472
|
+
},
|
|
473
|
+
get connectingHandle() {
|
|
474
|
+
return $3.get(connectingHandle);
|
|
475
|
+
},
|
|
476
|
+
get connectingNode() {
|
|
477
|
+
return $3.get(connectingNode);
|
|
478
|
+
},
|
|
479
|
+
get isDragging() {
|
|
480
|
+
return $3.get(draggedNode) !== null;
|
|
481
|
+
},
|
|
482
|
+
handleNodeDragStart,
|
|
483
|
+
handleNodeDrag,
|
|
484
|
+
handleNodeDragStop,
|
|
485
|
+
handleConnectStart,
|
|
486
|
+
handleConnectEnd,
|
|
487
|
+
reset
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/canvas/createCanvasShortcuts.ts
|
|
492
|
+
function createCanvasShortcuts(options) {
|
|
493
|
+
const target = options.targetElement ?? (typeof window !== "undefined" ? window : null);
|
|
494
|
+
if (!target) return () => {
|
|
495
|
+
};
|
|
496
|
+
function handleKeyDown(event) {
|
|
497
|
+
const keyboardEvent = event;
|
|
498
|
+
if (isEventFromTextInput(keyboardEvent)) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const isMod = keyboardEvent.metaKey || keyboardEvent.ctrlKey;
|
|
502
|
+
const isShift = keyboardEvent.shiftKey;
|
|
503
|
+
const key = keyboardEvent.key;
|
|
504
|
+
const selectedNodes = options.getSelectedNodes ? options.getSelectedNodes() : [];
|
|
505
|
+
if (key === "Backspace" || key === "Delete") {
|
|
506
|
+
if (options.onDelete && selectedNodes.length > 0) {
|
|
507
|
+
keyboardEvent.preventDefault();
|
|
508
|
+
options.onDelete(selectedNodes);
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (isMod) {
|
|
513
|
+
const lowerKey = key.toLowerCase();
|
|
514
|
+
if (lowerKey === "c" && !isShift) {
|
|
515
|
+
if (options.onCopy) {
|
|
516
|
+
options.onCopy();
|
|
517
|
+
}
|
|
518
|
+
} else if (lowerKey === "x" && !isShift) {
|
|
519
|
+
if (options.onCut) {
|
|
520
|
+
options.onCut();
|
|
521
|
+
}
|
|
522
|
+
} else if (lowerKey === "d" && !isShift) {
|
|
523
|
+
if (options.onDuplicate && selectedNodes.length > 0) {
|
|
524
|
+
keyboardEvent.preventDefault();
|
|
525
|
+
options.onDuplicate(selectedNodes);
|
|
526
|
+
}
|
|
527
|
+
} else if (lowerKey === "a" && !isShift) {
|
|
528
|
+
if (options.onSelectAll) {
|
|
529
|
+
keyboardEvent.preventDefault();
|
|
530
|
+
options.onSelectAll();
|
|
531
|
+
}
|
|
532
|
+
} else if (lowerKey === "g") {
|
|
533
|
+
if (isShift) {
|
|
534
|
+
if (options.onUngroup && selectedNodes.length > 0) {
|
|
535
|
+
keyboardEvent.preventDefault();
|
|
536
|
+
options.onUngroup(selectedNodes);
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
if (options.onGroup && selectedNodes.length > 0) {
|
|
540
|
+
keyboardEvent.preventDefault();
|
|
541
|
+
options.onGroup(selectedNodes);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} else if (key === "=" || key === "+") {
|
|
545
|
+
if (options.onZoomIn) {
|
|
546
|
+
keyboardEvent.preventDefault();
|
|
547
|
+
options.onZoomIn();
|
|
548
|
+
}
|
|
549
|
+
} else if (key === "-") {
|
|
550
|
+
if (options.onZoomOut) {
|
|
551
|
+
keyboardEvent.preventDefault();
|
|
552
|
+
options.onZoomOut();
|
|
553
|
+
}
|
|
554
|
+
} else if (key === "0") {
|
|
555
|
+
if (options.onFitView) {
|
|
556
|
+
keyboardEvent.preventDefault();
|
|
557
|
+
options.onFitView();
|
|
558
|
+
}
|
|
559
|
+
} else if (lowerKey === "f") {
|
|
560
|
+
if (options.onSearch) {
|
|
561
|
+
keyboardEvent.preventDefault();
|
|
562
|
+
options.onSearch();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
target.addEventListener("keydown", handleKeyDown);
|
|
568
|
+
return () => {
|
|
569
|
+
target.removeEventListener("keydown", handleKeyDown);
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/canvas/createCanvasInteractions.svelte.ts
|
|
574
|
+
import * as $4 from "svelte/internal/client";
|
|
575
|
+
function createCanvasInteractions(options = {}) {
|
|
576
|
+
let isSpacebarPanning = $4.state(false);
|
|
577
|
+
let trackpadDetected = $4.state(false);
|
|
578
|
+
let toggledPanMode = false;
|
|
579
|
+
function handleKeyDown(event) {
|
|
580
|
+
if (event.key === " " || event.code === "Space") {
|
|
581
|
+
if ($4.get(isSpacebarPanning) || isEventFromTextInput(event)) return;
|
|
582
|
+
$4.set(isSpacebarPanning, true);
|
|
583
|
+
toggledPanMode = true;
|
|
584
|
+
options.onPanModeChange?.(true);
|
|
585
|
+
if (typeof document !== "undefined") {
|
|
586
|
+
document.body.classList.add("cursor-grab");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function handleKeyUp(event) {
|
|
591
|
+
if (event.key === " " || event.code === "Space") {
|
|
592
|
+
if (!$4.get(isSpacebarPanning) && !toggledPanMode) return;
|
|
593
|
+
$4.set(isSpacebarPanning, false);
|
|
594
|
+
toggledPanMode = false;
|
|
595
|
+
options.onPanModeChange?.(false);
|
|
596
|
+
if (typeof document !== "undefined") {
|
|
597
|
+
document.body.classList.remove("cursor-grab");
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function handleWheel(event) {
|
|
602
|
+
if (event.ctrlKey && !$4.get(trackpadDetected)) {
|
|
603
|
+
$4.set(trackpadDetected, true);
|
|
604
|
+
options.onTrackpadDetected?.();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function attachListeners(target) {
|
|
608
|
+
const el = target ?? options.targetElement ?? (typeof window !== "undefined" ? window : null);
|
|
609
|
+
if (!el) return () => {
|
|
610
|
+
};
|
|
611
|
+
el.addEventListener("keydown", handleKeyDown);
|
|
612
|
+
el.addEventListener("keyup", handleKeyUp);
|
|
613
|
+
el.addEventListener("wheel", handleWheel, { passive: true });
|
|
614
|
+
return () => {
|
|
615
|
+
el.removeEventListener("keydown", handleKeyDown);
|
|
616
|
+
el.removeEventListener("keyup", handleKeyUp);
|
|
617
|
+
el.removeEventListener("wheel", handleWheel);
|
|
618
|
+
if (typeof document !== "undefined") {
|
|
619
|
+
document.body.classList.remove("cursor-grab");
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
get isSpacebarPanning() {
|
|
625
|
+
return $4.get(isSpacebarPanning);
|
|
626
|
+
},
|
|
627
|
+
get trackpadDetected() {
|
|
628
|
+
return $4.get(trackpadDetected);
|
|
629
|
+
},
|
|
630
|
+
attachListeners,
|
|
631
|
+
destroy: attachListeners()
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
var defaultSvelteFlowPreset = {
|
|
635
|
+
snapGrid: [25, 25],
|
|
636
|
+
panOnDrag: [0, 1, 2, 3, 4],
|
|
637
|
+
panOnScroll: false,
|
|
638
|
+
selectionOnDrag: false,
|
|
639
|
+
colorMode: "system",
|
|
640
|
+
multiSelectionKey: ["Shift", "Meta"]
|
|
641
|
+
};
|
|
642
|
+
var miroCompatiblePreset = {
|
|
643
|
+
...defaultSvelteFlowPreset,
|
|
644
|
+
panOnDrag: [1, 2],
|
|
645
|
+
selectionOnDrag: true
|
|
646
|
+
};
|
|
647
|
+
function restorePanelPointerEvents(panelSelector = ".svelte-flow__panel") {
|
|
648
|
+
if (typeof document === "undefined") return;
|
|
649
|
+
const panels = document.querySelectorAll(panelSelector);
|
|
650
|
+
panels.forEach((panel) => {
|
|
651
|
+
panel.style.pointerEvents = "auto";
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/canvas/createCanvasTrajectory.ts
|
|
656
|
+
import { cubicOut as cubicOut2 } from "svelte/easing";
|
|
657
|
+
var linearTrajectory = (origin, target, progress) => {
|
|
658
|
+
return {
|
|
659
|
+
position: {
|
|
660
|
+
x: origin.x + (target.x - origin.x) * progress,
|
|
661
|
+
y: origin.y + (target.y - origin.y) * progress
|
|
662
|
+
},
|
|
663
|
+
scale: progress,
|
|
664
|
+
opacity: Math.min(1, progress * 1.5)
|
|
665
|
+
};
|
|
666
|
+
};
|
|
667
|
+
function createFanOutTrajectory(options = {}) {
|
|
668
|
+
const {
|
|
669
|
+
curvature = 0.4,
|
|
670
|
+
startScale = 0,
|
|
671
|
+
endScale = 1,
|
|
672
|
+
startOpacity = 0,
|
|
673
|
+
endOpacity = 1
|
|
674
|
+
} = options;
|
|
675
|
+
return (origin, target, progress, index, total) => {
|
|
676
|
+
const dx = target.x - origin.x;
|
|
677
|
+
const dy = target.y - origin.y;
|
|
678
|
+
const normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;
|
|
679
|
+
const midX = origin.x + dx * 0.4;
|
|
680
|
+
const midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;
|
|
681
|
+
const t = progress;
|
|
682
|
+
const oneMinusT = 1 - t;
|
|
683
|
+
const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;
|
|
684
|
+
const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;
|
|
685
|
+
const scale = startScale + (endScale - startScale) * progress;
|
|
686
|
+
const opacity = startOpacity + (endOpacity - startOpacity) * progress;
|
|
687
|
+
return {
|
|
688
|
+
position: { x, y },
|
|
689
|
+
scale,
|
|
690
|
+
opacity
|
|
691
|
+
};
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function createBezierTrajectory(getControlPoint) {
|
|
695
|
+
return (origin, target, progress) => {
|
|
696
|
+
const cp = getControlPoint(origin, target);
|
|
697
|
+
const t = progress;
|
|
698
|
+
const oneMinusT = 1 - t;
|
|
699
|
+
const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;
|
|
700
|
+
const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;
|
|
701
|
+
return {
|
|
702
|
+
position: { x, y },
|
|
703
|
+
scale: progress,
|
|
704
|
+
opacity: progress
|
|
705
|
+
};
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function runMultiNodeTransition(transitions, options = {}) {
|
|
709
|
+
if (transitions.length === 0) {
|
|
710
|
+
options.onComplete?.();
|
|
711
|
+
return () => {
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const { defaultDuration = 400, defaultEasing = cubicOut2, onUpdate, onComplete } = options;
|
|
715
|
+
let isCancelled = false;
|
|
716
|
+
const startTime = Date.now();
|
|
717
|
+
const items = transitions.map((tr, i) => ({
|
|
718
|
+
...tr,
|
|
719
|
+
index: tr.index ?? i,
|
|
720
|
+
total: tr.total ?? transitions.length,
|
|
721
|
+
duration: tr.duration ?? defaultDuration,
|
|
722
|
+
easing: tr.easing ?? defaultEasing,
|
|
723
|
+
delay: tr.delay ?? 0,
|
|
724
|
+
trajectory: tr.trajectory ?? linearTrajectory
|
|
725
|
+
}));
|
|
726
|
+
function frame() {
|
|
727
|
+
if (isCancelled) return;
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
let allCompleted = true;
|
|
730
|
+
for (let i = 0; i < items.length; i++) {
|
|
731
|
+
const item = items[i];
|
|
732
|
+
const elapsed = now - (startTime + item.delay);
|
|
733
|
+
if (elapsed < 0) {
|
|
734
|
+
allCompleted = false;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);
|
|
738
|
+
const progress = item.duration <= 0 ? 1 : item.easing(rawProgress);
|
|
739
|
+
const point = item.trajectory(item.from, item.to, progress, item.index, item.total);
|
|
740
|
+
item.node.position = { ...point.position };
|
|
741
|
+
if (point.scale !== void 0 || point.opacity !== void 0) {
|
|
742
|
+
if (!item.node.data) {
|
|
743
|
+
item.node.data = {};
|
|
744
|
+
}
|
|
745
|
+
if (point.scale !== void 0) {
|
|
746
|
+
item.node.data.scale = point.scale;
|
|
747
|
+
}
|
|
748
|
+
if (point.opacity !== void 0) {
|
|
749
|
+
item.node.data.opacity = point.opacity;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (rawProgress < 1) {
|
|
753
|
+
allCompleted = false;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
onUpdate?.();
|
|
757
|
+
if (!allCompleted) {
|
|
758
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
759
|
+
requestAnimationFrame(frame);
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
onComplete?.();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
frame();
|
|
766
|
+
return () => {
|
|
767
|
+
isCancelled = true;
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/canvas/createCanvasDisplacement.ts
|
|
772
|
+
import dagre2 from "@dagrejs/dagre";
|
|
773
|
+
function computeNodesBoundingBox(nodes, defaultWidth = 200, defaultHeight = 60) {
|
|
774
|
+
if (nodes.length === 0) {
|
|
775
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
776
|
+
}
|
|
777
|
+
let minX = Infinity;
|
|
778
|
+
let minY = Infinity;
|
|
779
|
+
let maxX = -Infinity;
|
|
780
|
+
let maxY = -Infinity;
|
|
781
|
+
for (const node of nodes) {
|
|
782
|
+
const width = node.measured?.width ?? node.width ?? defaultWidth;
|
|
783
|
+
const height = node.measured?.height ?? node.height ?? defaultHeight;
|
|
784
|
+
minX = Math.min(minX, node.position.x);
|
|
785
|
+
minY = Math.min(minY, node.position.y);
|
|
786
|
+
maxX = Math.max(maxX, node.position.x + width);
|
|
787
|
+
maxY = Math.max(maxY, node.position.y + height);
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
x: minX,
|
|
791
|
+
y: minY,
|
|
792
|
+
width: maxX - minX,
|
|
793
|
+
height: maxY - minY
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function calculateDirectionalDisplacement(params) {
|
|
797
|
+
const {
|
|
798
|
+
existingNodes,
|
|
799
|
+
originNode,
|
|
800
|
+
childNodes,
|
|
801
|
+
getNodesBounds,
|
|
802
|
+
options = {}
|
|
803
|
+
} = params;
|
|
804
|
+
const {
|
|
805
|
+
direction = "right",
|
|
806
|
+
gap = 40,
|
|
807
|
+
spreadSiblingLanes = false,
|
|
808
|
+
siblingTolerance = 50
|
|
809
|
+
} = options;
|
|
810
|
+
const displacedMap = /* @__PURE__ */ new Map();
|
|
811
|
+
if (childNodes.length === 0 || existingNodes.length === 0) {
|
|
812
|
+
return displacedMap;
|
|
813
|
+
}
|
|
814
|
+
const childBounds = getNodesBounds ? getNodesBounds(childNodes) : computeNodesBoundingBox(childNodes);
|
|
815
|
+
const originWidth = originNode.measured?.width ?? originNode.width ?? 200;
|
|
816
|
+
const originHeight = originNode.measured?.height ?? originNode.height ?? 60;
|
|
817
|
+
const deltaPrimary = direction === "right" || direction === "left" ? childBounds.width + gap : childBounds.height + gap;
|
|
818
|
+
for (const node of existingNodes) {
|
|
819
|
+
if (node.id === originNode.id) continue;
|
|
820
|
+
let newX = node.position.x;
|
|
821
|
+
let newY = node.position.y;
|
|
822
|
+
let moved = false;
|
|
823
|
+
if (direction === "right") {
|
|
824
|
+
if (node.position.x >= originNode.position.x + originWidth * 0.5) {
|
|
825
|
+
newX += deltaPrimary;
|
|
826
|
+
moved = true;
|
|
827
|
+
} else if (spreadSiblingLanes) {
|
|
828
|
+
const isVerticallyOverlapping = node.position.y + (node.height ?? 60) >= childBounds.y - gap && node.position.y <= childBounds.y + childBounds.height + gap;
|
|
829
|
+
if (isVerticallyOverlapping && Math.abs(node.position.x - originNode.position.x) <= siblingTolerance) {
|
|
830
|
+
const pushDirection = node.position.y >= originNode.position.y ? 1 : -1;
|
|
831
|
+
newY += pushDirection * (childBounds.height * 0.5 + gap);
|
|
832
|
+
moved = true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
} else if (direction === "left") {
|
|
836
|
+
if (node.position.x <= originNode.position.x) {
|
|
837
|
+
newX -= deltaPrimary;
|
|
838
|
+
moved = true;
|
|
839
|
+
}
|
|
840
|
+
} else if (direction === "down") {
|
|
841
|
+
if (node.position.y >= originNode.position.y + originHeight * 0.5) {
|
|
842
|
+
newY += deltaPrimary;
|
|
843
|
+
moved = true;
|
|
844
|
+
}
|
|
845
|
+
} else if (direction === "up") {
|
|
846
|
+
if (node.position.y <= originNode.position.y) {
|
|
847
|
+
newY -= deltaPrimary;
|
|
848
|
+
moved = true;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (moved) {
|
|
852
|
+
displacedMap.set(node.id, { x: newX, y: newY });
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return displacedMap;
|
|
856
|
+
}
|
|
857
|
+
function calculateReflowDisplacement(params) {
|
|
858
|
+
const {
|
|
859
|
+
existingNodes,
|
|
860
|
+
existingEdges,
|
|
861
|
+
originNode,
|
|
862
|
+
childNodes,
|
|
863
|
+
childEdges = [],
|
|
864
|
+
options = {}
|
|
865
|
+
} = params;
|
|
866
|
+
const {
|
|
867
|
+
direction = "LR",
|
|
868
|
+
ranksep = 80,
|
|
869
|
+
nodesep = 30,
|
|
870
|
+
defaultNodeWidth = 200,
|
|
871
|
+
defaultNodeHeight = 60
|
|
872
|
+
} = options;
|
|
873
|
+
const displacedMap = /* @__PURE__ */ new Map();
|
|
874
|
+
if (existingNodes.length === 0) return displacedMap;
|
|
875
|
+
const allNodes = [...existingNodes, ...childNodes];
|
|
876
|
+
const allEdges = [...existingEdges, ...childEdges];
|
|
877
|
+
const dagreGraph = new dagre2.graphlib.Graph();
|
|
878
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
879
|
+
dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
|
|
880
|
+
const allNodeIds = new Set(allNodes.map((n) => n.id));
|
|
881
|
+
allNodes.forEach((node) => {
|
|
882
|
+
const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
|
|
883
|
+
const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
|
|
884
|
+
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
|
885
|
+
});
|
|
886
|
+
allEdges.forEach((edge) => {
|
|
887
|
+
if (allNodeIds.has(edge.source) && allNodeIds.has(edge.target)) {
|
|
888
|
+
dagreGraph.setEdge(edge.source, edge.target);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
dagre2.layout(dagreGraph);
|
|
892
|
+
const dagreOrigin = dagreGraph.node(originNode.id);
|
|
893
|
+
if (!dagreOrigin) return displacedMap;
|
|
894
|
+
const originWidth = originNode.measured?.width ?? originNode.width ?? defaultNodeWidth;
|
|
895
|
+
const originHeight = originNode.measured?.height ?? originNode.height ?? defaultNodeHeight;
|
|
896
|
+
const anchorOffsetX = originNode.position.x - (dagreOrigin.x - originWidth / 2);
|
|
897
|
+
const anchorOffsetY = originNode.position.y - (dagreOrigin.y - originHeight / 2);
|
|
898
|
+
existingNodes.forEach((node) => {
|
|
899
|
+
if (node.id === originNode.id) return;
|
|
900
|
+
const dagreNode = dagreGraph.node(node.id);
|
|
901
|
+
if (!dagreNode) return;
|
|
902
|
+
const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
|
|
903
|
+
const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
|
|
904
|
+
const targetX = dagreNode.x - nodeWidth / 2 + anchorOffsetX;
|
|
905
|
+
const targetY = dagreNode.y - nodeHeight / 2 + anchorOffsetY;
|
|
906
|
+
if (Math.abs(targetX - node.position.x) > 0.5 || Math.abs(targetY - node.position.y) > 0.5) {
|
|
907
|
+
displacedMap.set(node.id, { x: targetX, y: targetY });
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
return displacedMap;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/canvas/createCanvasExplosion.svelte.ts
|
|
914
|
+
import * as $5 from "svelte/internal/client";
|
|
915
|
+
import { cubicOut as cubicOut3 } from "svelte/easing";
|
|
916
|
+
function createCanvasExplosion(graph, options = {}) {
|
|
917
|
+
let explodedNodeIds = $5.state($5.proxy([]));
|
|
918
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
919
|
+
let activeAnimationCancel = null;
|
|
920
|
+
function isExploded(nodeId) {
|
|
921
|
+
return $5.get(explodedNodeIds).includes(nodeId);
|
|
922
|
+
}
|
|
923
|
+
function explodeNode(parentId, explodeOptions) {
|
|
924
|
+
if (isExploded(parentId)) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
const parentNode = graph.nodes.find((n) => n.id === parentId);
|
|
928
|
+
if (!parentNode) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
const {
|
|
932
|
+
childNodes,
|
|
933
|
+
childEdges = [],
|
|
934
|
+
connectParentToChildren = "all",
|
|
935
|
+
displacementStrategy = "directional",
|
|
936
|
+
directionalOptions = {},
|
|
937
|
+
reflowOptions = {},
|
|
938
|
+
trajectory,
|
|
939
|
+
duration = 500,
|
|
940
|
+
staggerDelay = 20,
|
|
941
|
+
easing = cubicOut3,
|
|
942
|
+
onComplete
|
|
943
|
+
} = explodeOptions;
|
|
944
|
+
if (childNodes.length === 0) {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
let displacementMap;
|
|
948
|
+
if (displacementStrategy === "dagre") {
|
|
949
|
+
displacementMap = calculateReflowDisplacement({
|
|
950
|
+
existingNodes: graph.nodes,
|
|
951
|
+
existingEdges: graph.edges,
|
|
952
|
+
originNode: parentNode,
|
|
953
|
+
childNodes,
|
|
954
|
+
childEdges,
|
|
955
|
+
getNodesBounds: options.getNodesBounds,
|
|
956
|
+
options: { ...reflowOptions, direction: reflowOptions.direction ?? "LR" }
|
|
957
|
+
});
|
|
958
|
+
} else if (displacementStrategy === "none") {
|
|
959
|
+
displacementMap = /* @__PURE__ */ new Map();
|
|
960
|
+
} else {
|
|
961
|
+
displacementMap = calculateDirectionalDisplacement({
|
|
962
|
+
existingNodes: graph.nodes,
|
|
963
|
+
originNode: parentNode,
|
|
964
|
+
childNodes,
|
|
965
|
+
getNodesBounds: options.getNodesBounds,
|
|
966
|
+
options: directionalOptions
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
const displacedNodesSnapshot = Array.from(displacementMap.entries()).map(([id]) => {
|
|
970
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
971
|
+
return {
|
|
972
|
+
id,
|
|
973
|
+
originalPosition: { ...node?.position ?? { x: 0, y: 0 } }
|
|
974
|
+
};
|
|
975
|
+
});
|
|
976
|
+
const edgesToAdd = [...childEdges];
|
|
977
|
+
const defaultCreateEdge = (sourceId, targetId) => {
|
|
978
|
+
if (graph.createEdge) {
|
|
979
|
+
return graph.createEdge(sourceId, targetId);
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
id: crypto.randomUUID(),
|
|
983
|
+
type: "default",
|
|
984
|
+
source: sourceId,
|
|
985
|
+
target: targetId
|
|
986
|
+
};
|
|
987
|
+
};
|
|
988
|
+
if (connectParentToChildren === "all" || connectParentToChildren === true) {
|
|
989
|
+
childNodes.forEach((child) => {
|
|
990
|
+
const exists = edgesToAdd.some((e) => e.source === parentId && e.target === child.id);
|
|
991
|
+
if (!exists) {
|
|
992
|
+
edgesToAdd.push(defaultCreateEdge(parentId, child.id));
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
} else if (connectParentToChildren === "first" && childNodes.length > 0) {
|
|
996
|
+
const firstChild = childNodes[0];
|
|
997
|
+
const exists = edgesToAdd.some((e) => e.source === parentId && e.target === firstChild.id);
|
|
998
|
+
if (!exists) {
|
|
999
|
+
edgesToAdd.push(defaultCreateEdge(parentId, firstChild.id));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const snapshot = {
|
|
1003
|
+
parentId,
|
|
1004
|
+
childNodeIds: childNodes.map((n) => n.id),
|
|
1005
|
+
childEdgeIds: edgesToAdd.map((e) => e.id),
|
|
1006
|
+
displacedNodes: displacedNodesSnapshot,
|
|
1007
|
+
timestamp: Date.now()
|
|
1008
|
+
};
|
|
1009
|
+
snapshots.set(parentId, snapshot);
|
|
1010
|
+
const originPos = { ...parentNode.position };
|
|
1011
|
+
const targetPositions = childNodes.map((child) => ({ ...child.position }));
|
|
1012
|
+
childNodes.forEach((child) => {
|
|
1013
|
+
child.position = { ...originPos };
|
|
1014
|
+
if (!child.data) child.data = {};
|
|
1015
|
+
child.data.scale = 0;
|
|
1016
|
+
child.data.opacity = 0;
|
|
1017
|
+
});
|
|
1018
|
+
graph.addNodeOrNodes(childNodes, void 0, false);
|
|
1019
|
+
if (edgesToAdd.length > 0) {
|
|
1020
|
+
graph.addEdges(edgesToAdd, false);
|
|
1021
|
+
}
|
|
1022
|
+
const chosenTrajectory = trajectory ?? createFanOutTrajectory({ curvature: 0.35 });
|
|
1023
|
+
const transitions = [];
|
|
1024
|
+
const effectiveStagger = duration === 0 ? 0 : staggerDelay;
|
|
1025
|
+
childNodes.forEach((child, i) => {
|
|
1026
|
+
transitions.push({
|
|
1027
|
+
node: child,
|
|
1028
|
+
from: originPos,
|
|
1029
|
+
to: targetPositions[i],
|
|
1030
|
+
trajectory: chosenTrajectory,
|
|
1031
|
+
delay: i * effectiveStagger,
|
|
1032
|
+
duration,
|
|
1033
|
+
easing,
|
|
1034
|
+
index: i,
|
|
1035
|
+
total: childNodes.length
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
displacementMap.forEach((targetPos, nodeId) => {
|
|
1039
|
+
const node = graph.nodes.find((n) => n.id === nodeId);
|
|
1040
|
+
if (node) {
|
|
1041
|
+
transitions.push({
|
|
1042
|
+
node,
|
|
1043
|
+
from: { ...node.position },
|
|
1044
|
+
to: targetPos,
|
|
1045
|
+
trajectory: linearTrajectory,
|
|
1046
|
+
duration,
|
|
1047
|
+
easing
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
if (activeAnimationCancel) {
|
|
1052
|
+
activeAnimationCancel();
|
|
1053
|
+
}
|
|
1054
|
+
activeAnimationCancel = runMultiNodeTransition(transitions, {
|
|
1055
|
+
defaultDuration: duration,
|
|
1056
|
+
defaultEasing: easing,
|
|
1057
|
+
onUpdate: () => {
|
|
1058
|
+
graph.setNodes([...graph.nodes]);
|
|
1059
|
+
},
|
|
1060
|
+
onComplete: () => {
|
|
1061
|
+
graph.setNodes([...graph.nodes]);
|
|
1062
|
+
$5.set(explodedNodeIds, [...$5.get(explodedNodeIds), parentId], true);
|
|
1063
|
+
activeAnimationCancel = null;
|
|
1064
|
+
options.onSave?.();
|
|
1065
|
+
graph.onSave?.();
|
|
1066
|
+
onComplete?.();
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
function collapseNode(parentId, collapseOptions = {}) {
|
|
1072
|
+
const snapshot = snapshots.get(parentId);
|
|
1073
|
+
if (!snapshot) {
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
const parentNode = graph.nodes.find((n) => n.id === parentId);
|
|
1077
|
+
if (!parentNode) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
const {
|
|
1081
|
+
duration = 400,
|
|
1082
|
+
staggerDelay = 15,
|
|
1083
|
+
easing = cubicOut3,
|
|
1084
|
+
trajectory,
|
|
1085
|
+
onComplete
|
|
1086
|
+
} = collapseOptions;
|
|
1087
|
+
const childIdSet = new Set(snapshot.childNodeIds);
|
|
1088
|
+
const activeChildNodes = graph.nodes.filter((n) => childIdSet.has(n.id));
|
|
1089
|
+
const originPos = { ...parentNode.position };
|
|
1090
|
+
const reverseTrajectory = trajectory ?? createFanOutTrajectory({
|
|
1091
|
+
curvature: 0.2,
|
|
1092
|
+
startScale: 1,
|
|
1093
|
+
endScale: 0,
|
|
1094
|
+
startOpacity: 1,
|
|
1095
|
+
endOpacity: 0
|
|
1096
|
+
});
|
|
1097
|
+
const transitions = [];
|
|
1098
|
+
const effectiveStagger = duration === 0 ? 0 : staggerDelay;
|
|
1099
|
+
activeChildNodes.forEach((child, i) => {
|
|
1100
|
+
transitions.push({
|
|
1101
|
+
node: child,
|
|
1102
|
+
from: { ...child.position },
|
|
1103
|
+
to: originPos,
|
|
1104
|
+
trajectory: reverseTrajectory,
|
|
1105
|
+
delay: (activeChildNodes.length - 1 - i) * effectiveStagger,
|
|
1106
|
+
duration,
|
|
1107
|
+
easing,
|
|
1108
|
+
index: i,
|
|
1109
|
+
total: activeChildNodes.length
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
snapshot.displacedNodes.forEach(({ id, originalPosition }) => {
|
|
1113
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
1114
|
+
if (node) {
|
|
1115
|
+
transitions.push({
|
|
1116
|
+
node,
|
|
1117
|
+
from: { ...node.position },
|
|
1118
|
+
to: originalPosition,
|
|
1119
|
+
trajectory: linearTrajectory,
|
|
1120
|
+
duration,
|
|
1121
|
+
easing
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
if (activeAnimationCancel) {
|
|
1126
|
+
activeAnimationCancel();
|
|
1127
|
+
}
|
|
1128
|
+
activeAnimationCancel = runMultiNodeTransition(transitions, {
|
|
1129
|
+
defaultDuration: duration,
|
|
1130
|
+
defaultEasing: easing,
|
|
1131
|
+
onUpdate: () => {
|
|
1132
|
+
graph.setNodes([...graph.nodes]);
|
|
1133
|
+
},
|
|
1134
|
+
onComplete: () => {
|
|
1135
|
+
graph.removeNodes(activeChildNodes, false);
|
|
1136
|
+
const edgeIdSet = new Set(snapshot.childEdgeIds);
|
|
1137
|
+
const edgesToRemove = graph.edges.filter((e) => edgeIdSet.has(e.id));
|
|
1138
|
+
if (edgesToRemove.length > 0) {
|
|
1139
|
+
graph.removeEdges(edgesToRemove, false);
|
|
1140
|
+
}
|
|
1141
|
+
snapshots.delete(parentId);
|
|
1142
|
+
$5.set(explodedNodeIds, $5.get(explodedNodeIds).filter((id) => id !== parentId), true);
|
|
1143
|
+
activeAnimationCancel = null;
|
|
1144
|
+
options.onSave?.();
|
|
1145
|
+
graph.onSave?.();
|
|
1146
|
+
onComplete?.();
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
function toggleExplode(parentId, explodeOptions, collapseOptions) {
|
|
1152
|
+
if (isExploded(parentId)) {
|
|
1153
|
+
return collapseNode(parentId, collapseOptions);
|
|
1154
|
+
}
|
|
1155
|
+
return explodeNode(parentId, explodeOptions);
|
|
1156
|
+
}
|
|
1157
|
+
function getSnapshot(parentId) {
|
|
1158
|
+
return snapshots.get(parentId);
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
get explodedNodeIds() {
|
|
1162
|
+
return $5.get(explodedNodeIds);
|
|
1163
|
+
},
|
|
1164
|
+
isExploded,
|
|
1165
|
+
explodeNode,
|
|
1166
|
+
collapseNode,
|
|
1167
|
+
toggleExplode,
|
|
1168
|
+
getSnapshot
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
export {
|
|
1172
|
+
calculateDirectionalDisplacement,
|
|
1173
|
+
calculateGraphDifferences,
|
|
1174
|
+
calculateReflowDisplacement,
|
|
1175
|
+
centerNodes,
|
|
1176
|
+
computeNodesBoundingBox,
|
|
1177
|
+
createBezierTrajectory,
|
|
1178
|
+
createCanvasClipboard,
|
|
1179
|
+
createCanvasDocking,
|
|
1180
|
+
createCanvasExplosion,
|
|
1181
|
+
createCanvasGraph,
|
|
1182
|
+
createCanvasInteractions,
|
|
1183
|
+
createCanvasShortcuts,
|
|
1184
|
+
createFanOutTrajectory,
|
|
1185
|
+
defaultSvelteFlowPreset,
|
|
1186
|
+
getCenteredNodePosition,
|
|
1187
|
+
getLayoutedNodes,
|
|
1188
|
+
isEventFromTextInput,
|
|
1189
|
+
isValidCanvasNode,
|
|
1190
|
+
isValidUrl,
|
|
1191
|
+
linearTrajectory,
|
|
1192
|
+
miroCompatiblePreset,
|
|
1193
|
+
replaceNodeInGraph,
|
|
1194
|
+
restorePanelPointerEvents,
|
|
1195
|
+
runMultiNodeTransition
|
|
1196
|
+
};
|
|
1197
|
+
//# sourceMappingURL=index.js.map
|