@pixerate/editor-svelte 0.15.2 → 0.16.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/BubbleMenu.svelte.d.ts +14 -0
- package/dist/BubbleMenu.svelte.d.ts.map +1 -0
- package/{src → dist}/EditableTextNodeEditor.svelte +1 -1
- package/dist/EditableTextNodeEditor.svelte.d.ts +26 -0
- package/dist/EditableTextNodeEditor.svelte.d.ts.map +1 -0
- package/dist/TemplateRenderer.svelte.d.ts +14 -0
- package/dist/TemplateRenderer.svelte.d.ts.map +1 -0
- package/dist/canvas/AgentPresenceLayer.svelte.d.ts +9 -0
- package/dist/canvas/AgentPresenceLayer.svelte.d.ts.map +1 -0
- package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts +9 -0
- package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasClipboard.svelte.d.ts +32 -0
- package/dist/canvas/createCanvasClipboard.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasClipboard.svelte.js +171 -0
- package/dist/canvas/createCanvasDisplacement.d.ts +32 -0
- package/dist/canvas/createCanvasDisplacement.d.ts.map +1 -0
- package/dist/canvas/createCanvasDisplacement.js +170 -0
- package/dist/canvas/createCanvasDocking.svelte.d.ts +56 -0
- package/dist/canvas/createCanvasDocking.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasDocking.svelte.js +227 -0
- package/dist/canvas/createCanvasExplosion.svelte.d.ts +31 -0
- package/dist/canvas/createCanvasExplosion.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasExplosion.svelte.js +346 -0
- package/dist/canvas/createCanvasGraph.svelte.d.ts +62 -0
- package/dist/canvas/createCanvasGraph.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasGraph.svelte.js +223 -0
- package/dist/canvas/createCanvasInteractions.svelte.d.ts +28 -0
- package/dist/canvas/createCanvasInteractions.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasInteractions.svelte.js +95 -0
- package/dist/canvas/createCanvasLayout.d.ts +22 -0
- package/dist/canvas/createCanvasLayout.d.ts.map +1 -0
- package/dist/canvas/createCanvasLayout.js +76 -0
- package/dist/canvas/createCanvasMultiDrag.svelte.d.ts +61 -0
- package/dist/canvas/createCanvasMultiDrag.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasMultiDrag.svelte.js +105 -0
- package/dist/canvas/createCanvasNodeSync.svelte.d.ts +58 -0
- package/dist/canvas/createCanvasNodeSync.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasNodeSync.svelte.js +82 -0
- package/dist/canvas/createCanvasSelection.svelte.d.ts +26 -0
- package/dist/canvas/createCanvasSelection.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasSelection.svelte.js +26 -0
- package/dist/canvas/createCanvasShortcuts.d.ts +9 -0
- package/dist/canvas/createCanvasShortcuts.d.ts.map +1 -0
- package/dist/canvas/createCanvasShortcuts.js +97 -0
- package/dist/canvas/createCanvasTrajectory.d.ts +31 -0
- package/dist/canvas/createCanvasTrajectory.d.ts.map +1 -0
- package/dist/canvas/createCanvasTrajectory.js +141 -0
- package/dist/canvas/createStreamingCanvas.svelte.d.ts +19 -0
- package/dist/canvas/createStreamingCanvas.svelte.d.ts.map +1 -0
- package/dist/canvas/createStreamingCanvas.svelte.js +32 -0
- package/dist/canvas/index.d.ts +18 -688
- package/dist/canvas/index.d.ts.map +1 -0
- package/dist/canvas/index.js +34 -1839
- package/dist/canvas/types.d.ts +183 -0
- package/dist/canvas/types.d.ts.map +1 -0
- package/dist/canvas/types.js +1 -0
- package/dist/createNavigationGuard.svelte.d.ts +24 -0
- package/dist/createNavigationGuard.svelte.d.ts.map +1 -0
- package/dist/createNavigationGuard.svelte.js +65 -0
- package/dist/editor.svelte.d.ts +23 -0
- package/dist/editor.svelte.d.ts.map +1 -0
- package/dist/editor.svelte.js +41 -0
- package/dist/history/createHistoryShortcuts.d.ts +24 -0
- package/dist/history/createHistoryShortcuts.d.ts.map +1 -0
- package/dist/history/createHistoryShortcuts.js +57 -0
- package/dist/history/history.svelte.d.ts +18 -0
- package/dist/history/history.svelte.d.ts.map +1 -0
- package/dist/history/history.svelte.js +43 -0
- package/dist/history/index.d.ts +3 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +2 -0
- package/{src → dist}/image-editor/ImageEditor.svelte +1 -1
- package/dist/image-editor/ImageEditor.svelte.d.ts +17 -0
- package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -0
- package/dist/image-editor/imageEditorState.svelte.d.ts +50 -0
- package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -0
- package/dist/image-editor/imageEditorState.svelte.js +132 -0
- package/dist/image-editor/index.d.ts +6 -16
- package/dist/image-editor/index.d.ts.map +1 -0
- package/dist/image-editor/index.js +2 -9
- package/dist/index.d.ts +20 -249
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -1119
- package/dist/navigationGuard.d.ts +21 -0
- package/dist/navigationGuard.d.ts.map +1 -0
- package/dist/navigationGuard.js +59 -0
- package/{src → dist}/spreadsheet/FormulaBar.svelte +1 -1
- package/dist/spreadsheet/FormulaBar.svelte.d.ts +17 -0
- package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -0
- package/{src → dist}/spreadsheet/SpreadsheetEditor.svelte +1 -1
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +22 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/index.d.ts +9 -0
- package/dist/spreadsheet/index.d.ts.map +1 -0
- package/{src/spreadsheet/index.ts → dist/spreadsheet/index.js} +1 -1
- package/dist/spreadsheet/spreadsheet.svelte.d.ts +33 -0
- package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/spreadsheet.svelte.js +160 -0
- package/package.json +14 -16
- package/dist/canvas/index.js.map +0 -1
- package/dist/chunk-HEOI4UNF.js +0 -1191
- package/dist/chunk-HEOI4UNF.js.map +0 -1
- package/dist/image-editor/index.js.map +0 -1
- package/dist/index.css +0 -50
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/canvas/__tests__/canvas-clipboard.test.ts +0 -154
- package/src/canvas/__tests__/canvas-docking.test.ts +0 -288
- package/src/canvas/__tests__/canvas-explosion.test.ts +0 -634
- package/src/canvas/__tests__/canvas-graph.test.ts +0 -159
- package/src/canvas/__tests__/canvas-interactions.test.ts +0 -86
- package/src/canvas/__tests__/canvas-layout.test.ts +0 -75
- package/src/canvas/__tests__/canvas-multi-drag.test.ts +0 -112
- package/src/canvas/__tests__/canvas-node-sync.svelte.test.ts +0 -104
- package/src/canvas/__tests__/canvas-shortcuts.test.ts +0 -59
- package/src/canvas/createCanvasClipboard.svelte.ts +0 -219
- package/src/canvas/createCanvasDisplacement.ts +0 -253
- package/src/canvas/createCanvasDocking.svelte.ts +0 -355
- package/src/canvas/createCanvasExplosion.svelte.ts +0 -457
- package/src/canvas/createCanvasGraph.svelte.ts +0 -330
- package/src/canvas/createCanvasInteractions.svelte.ts +0 -112
- package/src/canvas/createCanvasLayout.ts +0 -129
- package/src/canvas/createCanvasMultiDrag.svelte.ts +0 -175
- package/src/canvas/createCanvasNodeSync.svelte.ts +0 -150
- package/src/canvas/createCanvasSelection.svelte.ts +0 -51
- package/src/canvas/createCanvasShortcuts.ts +0 -99
- package/src/canvas/createCanvasTrajectory.ts +0 -207
- package/src/canvas/createStreamingCanvas.svelte.ts +0 -56
- package/src/canvas/index.d.ts +0 -688
- package/src/canvas/index.ts +0 -126
- package/src/canvas/types.ts +0 -223
- package/src/createNavigationGuard.svelte.ts +0 -97
- package/src/editor.svelte.ts +0 -65
- package/src/history/createHistoryShortcuts.ts +0 -95
- package/src/history/history.svelte.ts +0 -68
- package/src/history/index.ts +0 -2
- package/src/image-editor/imageEditorState.svelte.ts +0 -151
- package/src/image-editor/index.d.ts +0 -16
- package/src/image-editor/index.ts +0 -2
- package/src/index.d.ts +0 -249
- package/src/index.ts +0 -89
- package/src/navigationGuard.ts +0 -82
- package/src/spreadsheet/spreadsheet.svelte.ts +0 -176
- package/src/svelte.d.ts +0 -5
- /package/{src → dist}/BubbleMenu.svelte +0 -0
- /package/{src → dist}/TemplateRenderer.svelte +0 -0
- /package/{src → dist}/canvas/AgentPresenceLayer.svelte +0 -0
- /package/{src → dist}/canvas/FloatingHorizontalScrollbar.svelte +0 -0
- /package/{src → dist}/canvas/README.md +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { cubicOut } from 'svelte/easing';
|
|
2
|
+
/**
|
|
3
|
+
* Pure calculation of graph differences between a current state and a baseline state.
|
|
4
|
+
*/
|
|
5
|
+
export function calculateGraphDifferences(currentState, baselineState) {
|
|
6
|
+
const currentNodes = currentState.nodes ?? [];
|
|
7
|
+
const currentEdges = currentState.edges ?? [];
|
|
8
|
+
const baselineNodes = baselineState.nodes ?? [];
|
|
9
|
+
const baselineEdges = baselineState.edges ?? [];
|
|
10
|
+
// Map baseline for fast O(1) lookup
|
|
11
|
+
const baselineNodeMap = new Map(baselineNodes.map((n) => [n.id, n]));
|
|
12
|
+
const baselineEdgeMap = new Map(baselineEdges.map((e) => [e.id, e]));
|
|
13
|
+
const currentNodeMap = new Map(currentNodes.map((n) => [n.id, n]));
|
|
14
|
+
const currentEdgeMap = new Map(currentEdges.map((e) => [e.id, e]));
|
|
15
|
+
const removedNodes = baselineNodes.filter((oldNode) => !currentNodeMap.has(oldNode.id));
|
|
16
|
+
const removedEdges = baselineEdges.filter((oldEdge) => !currentEdgeMap.has(oldEdge.id));
|
|
17
|
+
const changedNodes = currentNodes.filter((newNode) => {
|
|
18
|
+
const oldNode = baselineNodeMap.get(newNode.id);
|
|
19
|
+
if (!oldNode)
|
|
20
|
+
return true;
|
|
21
|
+
return JSON.stringify(newNode) !== JSON.stringify(oldNode);
|
|
22
|
+
});
|
|
23
|
+
const changedEdges = currentEdges.filter((newEdge) => {
|
|
24
|
+
const oldEdge = baselineEdgeMap.get(newEdge.id);
|
|
25
|
+
if (!oldEdge)
|
|
26
|
+
return true;
|
|
27
|
+
return JSON.stringify(newEdge) !== JSON.stringify(oldEdge);
|
|
28
|
+
});
|
|
29
|
+
return { changedNodes, changedEdges, removedNodes, removedEdges };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pure function to replace a node in a graph, optionally preserving its connected edges.
|
|
33
|
+
*/
|
|
34
|
+
export function replaceNodeInGraph(nodes, edges, oldNode, newNode, keepEdges = false) {
|
|
35
|
+
const updatedNodes = nodes.map((n) => (n.id === oldNode.id ? newNode : n));
|
|
36
|
+
if (!keepEdges) {
|
|
37
|
+
return { nodes: updatedNodes, edges };
|
|
38
|
+
}
|
|
39
|
+
const updatedEdges = edges.map((edge) => {
|
|
40
|
+
if (edge.source === oldNode.id || edge.target === oldNode.id) {
|
|
41
|
+
return {
|
|
42
|
+
...edge,
|
|
43
|
+
id: crypto.randomUUID(),
|
|
44
|
+
source: edge.source === oldNode.id ? newNode.id : edge.source,
|
|
45
|
+
target: edge.target === oldNode.id ? newNode.id : edge.target
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return edge;
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
nodes: updatedNodes,
|
|
52
|
+
edges: updatedEdges
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Headless Svelte 5 rune managing canvas graph state and operations.
|
|
57
|
+
*/
|
|
58
|
+
export function createCanvasGraph(options = {}) {
|
|
59
|
+
let nodes = $state.raw(options.initialNodes ?? []);
|
|
60
|
+
let edges = $state.raw(options.initialEdges ?? []);
|
|
61
|
+
function setNodes(newNodes, triggerCallback = true) {
|
|
62
|
+
nodes = newNodes;
|
|
63
|
+
if (triggerCallback) {
|
|
64
|
+
options.onNodesChange?.(nodes);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function setEdges(newEdges, triggerCallback = true) {
|
|
68
|
+
edges = newEdges;
|
|
69
|
+
if (triggerCallback) {
|
|
70
|
+
options.onEdgesChange?.(edges);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function animateNodePosition(node, targetPosition, duration = 400, easing = cubicOut) {
|
|
74
|
+
const delay = node.data?.animationDelay ?? 0;
|
|
75
|
+
const startPosition = { ...node.position };
|
|
76
|
+
const startTime = Date.now() + delay;
|
|
77
|
+
function step() {
|
|
78
|
+
const currentTime = Date.now();
|
|
79
|
+
if (currentTime < startTime) {
|
|
80
|
+
if (typeof requestAnimationFrame !== 'undefined') {
|
|
81
|
+
requestAnimationFrame(step);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
86
|
+
const eased = easing(progress);
|
|
87
|
+
const currentPos = {
|
|
88
|
+
x: startPosition.x + (targetPosition.x - startPosition.x) * eased,
|
|
89
|
+
y: startPosition.y + (targetPosition.y - startPosition.y) * eased
|
|
90
|
+
};
|
|
91
|
+
// Mutate position in place without recreating the entire array every frame
|
|
92
|
+
const targetNode = nodes.find((n) => n.id === node.id);
|
|
93
|
+
if (targetNode) {
|
|
94
|
+
targetNode.position = currentPos;
|
|
95
|
+
}
|
|
96
|
+
if (progress < 1) {
|
|
97
|
+
if (typeof requestAnimationFrame !== 'undefined') {
|
|
98
|
+
requestAnimationFrame(step);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Animation completed: trigger final reactive update and change event
|
|
103
|
+
setNodes([...nodes]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
step();
|
|
107
|
+
}
|
|
108
|
+
function addNodeOrNodes(nodeOrNodesToAdd, animateFromPosition, save = true) {
|
|
109
|
+
const toAdd = Array.isArray(nodeOrNodesToAdd) ? nodeOrNodesToAdd : [nodeOrNodesToAdd];
|
|
110
|
+
const existingMap = new Map(nodes.map((n) => [n.id, n]));
|
|
111
|
+
toAdd.forEach((node) => {
|
|
112
|
+
if (!existingMap.has(node.id)) {
|
|
113
|
+
existingMap.set(node.id, node);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
setNodes(Array.from(existingMap.values()));
|
|
117
|
+
if (animateFromPosition) {
|
|
118
|
+
toAdd.forEach((node) => {
|
|
119
|
+
const target = { ...node.position };
|
|
120
|
+
node.position = { ...animateFromPosition };
|
|
121
|
+
animateNodePosition(node, target);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (save)
|
|
125
|
+
options.onSave?.();
|
|
126
|
+
}
|
|
127
|
+
function addEdge(edgeToAdd, save = true) {
|
|
128
|
+
setEdges([...edges, edgeToAdd]);
|
|
129
|
+
if (save)
|
|
130
|
+
options.onSave?.();
|
|
131
|
+
}
|
|
132
|
+
function addEdges(edgesToAdd, save = true) {
|
|
133
|
+
setEdges([...edges, ...edgesToAdd]);
|
|
134
|
+
if (save)
|
|
135
|
+
options.onSave?.();
|
|
136
|
+
}
|
|
137
|
+
function removeNodes(nodesToRemove, save = true) {
|
|
138
|
+
const idsToRemove = new Set(nodesToRemove.map((n) => n.id));
|
|
139
|
+
setNodes(nodes.filter((n) => !idsToRemove.has(n.id)));
|
|
140
|
+
if (save)
|
|
141
|
+
options.onSave?.();
|
|
142
|
+
}
|
|
143
|
+
function removeEdges(edgesToRemove, save = true) {
|
|
144
|
+
const idsToRemove = new Set(edgesToRemove.map((e) => e.id));
|
|
145
|
+
setEdges(edges.filter((e) => !idsToRemove.has(e.id)));
|
|
146
|
+
if (save)
|
|
147
|
+
options.onSave?.();
|
|
148
|
+
}
|
|
149
|
+
function replaceNode(oldNode, newNode, save = true, keepEdges = false) {
|
|
150
|
+
const result = replaceNodeInGraph(nodes, edges, oldNode, newNode, keepEdges);
|
|
151
|
+
setNodes(result.nodes);
|
|
152
|
+
if (result.edges !== edges) {
|
|
153
|
+
setEdges(result.edges);
|
|
154
|
+
}
|
|
155
|
+
if (save)
|
|
156
|
+
options.onSave?.();
|
|
157
|
+
}
|
|
158
|
+
function addConnectedNodes(params) {
|
|
159
|
+
const { nodesToAdd, inboundNodes = [], outboundNodes = [], animatedEdges = false, animateFromPosition, inboundEdgeData = {}, outboundEdgeData = {}, getEdgeLabel } = params;
|
|
160
|
+
const defaultCreateEdge = (sourceId, targetId, opt) => {
|
|
161
|
+
if (options.createEdge) {
|
|
162
|
+
return options.createEdge(sourceId, targetId, opt);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
id: crypto.randomUUID(),
|
|
166
|
+
type: 'default',
|
|
167
|
+
source: sourceId,
|
|
168
|
+
target: targetId,
|
|
169
|
+
label: opt?.label ?? '',
|
|
170
|
+
animated: opt?.animated ?? false,
|
|
171
|
+
data: opt?.data ?? {}
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
nodesToAdd.forEach((addedNode) => {
|
|
175
|
+
addNodeOrNodes(addedNode, animateFromPosition, false);
|
|
176
|
+
inboundNodes.forEach((sourceNode) => {
|
|
177
|
+
const label = getEdgeLabel ? getEdgeLabel(sourceNode, addedNode) : '';
|
|
178
|
+
addEdge(defaultCreateEdge(sourceNode.id, addedNode.id, {
|
|
179
|
+
label,
|
|
180
|
+
animated: animatedEdges,
|
|
181
|
+
data: inboundEdgeData
|
|
182
|
+
}), false);
|
|
183
|
+
});
|
|
184
|
+
outboundNodes.forEach((targetNode) => {
|
|
185
|
+
const label = getEdgeLabel ? getEdgeLabel(addedNode, targetNode) : '';
|
|
186
|
+
addEdge(defaultCreateEdge(addedNode.id, targetNode.id, {
|
|
187
|
+
label,
|
|
188
|
+
animated: animatedEdges,
|
|
189
|
+
data: outboundEdgeData
|
|
190
|
+
}), false);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
options.onSave?.();
|
|
194
|
+
}
|
|
195
|
+
function getDifferences(baseline) {
|
|
196
|
+
return calculateGraphDifferences({ nodes, edges }, baseline);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
get nodes() {
|
|
200
|
+
return nodes;
|
|
201
|
+
},
|
|
202
|
+
set nodes(val) {
|
|
203
|
+
setNodes(val);
|
|
204
|
+
},
|
|
205
|
+
get edges() {
|
|
206
|
+
return edges;
|
|
207
|
+
},
|
|
208
|
+
set edges(val) {
|
|
209
|
+
setEdges(val);
|
|
210
|
+
},
|
|
211
|
+
setNodes,
|
|
212
|
+
setEdges,
|
|
213
|
+
addNodeOrNodes,
|
|
214
|
+
addEdge,
|
|
215
|
+
addEdges,
|
|
216
|
+
removeNodes,
|
|
217
|
+
removeEdges,
|
|
218
|
+
replaceNode,
|
|
219
|
+
addConnectedNodes,
|
|
220
|
+
animateNodePosition,
|
|
221
|
+
getDifferences
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CanvasInteractionPresetConfig } from './types.js';
|
|
2
|
+
export interface CreateCanvasInteractionsOptions {
|
|
3
|
+
targetElement?: HTMLElement | Window;
|
|
4
|
+
onPanModeChange?: (active: boolean) => void;
|
|
5
|
+
onTrackpadDetected?: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Headless Svelte 5 rune managing canvas pan interactions (spacebar panning, trackpad pinch detection).
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCanvasInteractions(options?: CreateCanvasInteractionsOptions): {
|
|
11
|
+
readonly isSpacebarPanning: boolean;
|
|
12
|
+
readonly trackpadDetected: boolean;
|
|
13
|
+
attachListeners: (target?: HTMLElement | Window) => () => void;
|
|
14
|
+
destroy: () => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Default interaction configuration presets for SvelteFlow.
|
|
18
|
+
*/
|
|
19
|
+
export declare const defaultSvelteFlowPreset: CanvasInteractionPresetConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Miro-compatible interaction preset (middle/right drag to pan, left drag to select).
|
|
22
|
+
*/
|
|
23
|
+
export declare const miroCompatiblePreset: CanvasInteractionPresetConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Helper to ensure panels restore pointer events after selection rectangle releases.
|
|
26
|
+
*/
|
|
27
|
+
export declare function restorePanelPointerEvents(panelSelector?: string): void;
|
|
28
|
+
//# sourceMappingURL=createCanvasInteractions.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCanvasInteractions.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasInteractions.svelte.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,WAAW,+BAA+B;IAC/C,aAAa,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IACrC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,+BAAoC;;;+BAsCnD,WAAW,GAAG,MAAM,KAAG,MAAM,IAAI;mBAAJ,IAAI;EA4BnE;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,6BAOrC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,6BAIlC,CAAC;AAEF;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,aAAa,SAAwB,GAAG,IAAI,CAMrF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isEventFromTextInput } from './createCanvasClipboard.svelte.js';
|
|
2
|
+
/**
|
|
3
|
+
* Headless Svelte 5 rune managing canvas pan interactions (spacebar panning, trackpad pinch detection).
|
|
4
|
+
*/
|
|
5
|
+
export function createCanvasInteractions(options = {}) {
|
|
6
|
+
let isSpacebarPanning = $state.raw(false);
|
|
7
|
+
let trackpadDetected = $state.raw(false);
|
|
8
|
+
let toggledPanMode = false;
|
|
9
|
+
function handleKeyDown(event) {
|
|
10
|
+
if (event.key === ' ' || event.code === 'Space') {
|
|
11
|
+
if (isSpacebarPanning || isEventFromTextInput(event))
|
|
12
|
+
return;
|
|
13
|
+
isSpacebarPanning = true;
|
|
14
|
+
toggledPanMode = true;
|
|
15
|
+
options.onPanModeChange?.(true);
|
|
16
|
+
if (typeof document !== 'undefined') {
|
|
17
|
+
document.body.classList.add('cursor-grab');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function handleKeyUp(event) {
|
|
22
|
+
if (event.key === ' ' || event.code === 'Space') {
|
|
23
|
+
if (!isSpacebarPanning && !toggledPanMode)
|
|
24
|
+
return;
|
|
25
|
+
isSpacebarPanning = false;
|
|
26
|
+
toggledPanMode = false;
|
|
27
|
+
options.onPanModeChange?.(false);
|
|
28
|
+
if (typeof document !== 'undefined') {
|
|
29
|
+
document.body.classList.remove('cursor-grab');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function handleWheel(event) {
|
|
34
|
+
if (event.ctrlKey && !trackpadDetected) {
|
|
35
|
+
trackpadDetected = true;
|
|
36
|
+
options.onTrackpadDetected?.();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function attachListeners(target) {
|
|
40
|
+
const el = target ?? options.targetElement ?? (typeof window !== 'undefined' ? window : null);
|
|
41
|
+
if (!el)
|
|
42
|
+
return () => { };
|
|
43
|
+
el.addEventListener('keydown', handleKeyDown);
|
|
44
|
+
el.addEventListener('keyup', handleKeyUp);
|
|
45
|
+
el.addEventListener('wheel', handleWheel, { passive: true });
|
|
46
|
+
return () => {
|
|
47
|
+
el.removeEventListener('keydown', handleKeyDown);
|
|
48
|
+
el.removeEventListener('keyup', handleKeyUp);
|
|
49
|
+
el.removeEventListener('wheel', handleWheel);
|
|
50
|
+
if (typeof document !== 'undefined') {
|
|
51
|
+
document.body.classList.remove('cursor-grab');
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
get isSpacebarPanning() {
|
|
57
|
+
return isSpacebarPanning;
|
|
58
|
+
},
|
|
59
|
+
get trackpadDetected() {
|
|
60
|
+
return trackpadDetected;
|
|
61
|
+
},
|
|
62
|
+
attachListeners,
|
|
63
|
+
destroy: attachListeners()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Default interaction configuration presets for SvelteFlow.
|
|
68
|
+
*/
|
|
69
|
+
export const defaultSvelteFlowPreset = {
|
|
70
|
+
snapGrid: [25, 25],
|
|
71
|
+
panOnDrag: [0, 1, 2, 3, 4],
|
|
72
|
+
panOnScroll: false,
|
|
73
|
+
selectionOnDrag: false,
|
|
74
|
+
colorMode: 'system',
|
|
75
|
+
multiSelectionKey: ['Shift', 'Meta']
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Miro-compatible interaction preset (middle/right drag to pan, left drag to select).
|
|
79
|
+
*/
|
|
80
|
+
export const miroCompatiblePreset = {
|
|
81
|
+
...defaultSvelteFlowPreset,
|
|
82
|
+
panOnDrag: [1, 2],
|
|
83
|
+
selectionOnDrag: true
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Helper to ensure panels restore pointer events after selection rectangle releases.
|
|
87
|
+
*/
|
|
88
|
+
export function restorePanelPointerEvents(panelSelector = '.svelte-flow__panel') {
|
|
89
|
+
if (typeof document === 'undefined')
|
|
90
|
+
return;
|
|
91
|
+
const panels = document.querySelectorAll(panelSelector);
|
|
92
|
+
panels.forEach((panel) => {
|
|
93
|
+
panel.style.pointerEvents = 'auto';
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type XYPosition, type Rect } from '@xyflow/svelte';
|
|
2
|
+
import type { CanvasNode, CanvasEdge, CanvasLayoutOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Calculates upper-left flow coordinate for an element to be centered in the viewport.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getCenteredNodePosition(width: number | undefined, height: number | undefined, screenToFlowPosition: (position: XYPosition) => XYPosition, screenDimensions?: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}): XYPosition;
|
|
10
|
+
/**
|
|
11
|
+
* Updates positions of nodes so their collective bounding box is centered in the current viewport.
|
|
12
|
+
*/
|
|
13
|
+
export declare function centerNodes<TNode extends CanvasNode = CanvasNode>(nodes: TNode[], screenToFlowPosition: (position: XYPosition) => XYPosition, getNodesBounds: (nodes: TNode[]) => Rect, screenDimensions?: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}): TNode[];
|
|
17
|
+
/**
|
|
18
|
+
* Automatically calculates directed hierarchical layout of nodes and edges using Dagre.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getLayoutedNodes<TNode extends CanvasNode = CanvasNode, TEdge extends CanvasEdge = CanvasEdge>(nodes: TNode[], edges: TEdge[], getNodesBounds: (nodes: TNode[]) => Rect, options?: CanvasLayoutOptions): TNode[];
|
|
21
|
+
export { ensureLayout, placeBeside, estimateNodeSize, flowSide } from '@pixerate/editor/canvas';
|
|
22
|
+
//# sourceMappingURL=createCanvasLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCanvasLayout.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasLayout.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE9E;;GAEG;AACH,wBAAgB,uBAAuB,CACtC,KAAK,oBAAI,EACT,MAAM,oBAAI,EACV,oBAAoB,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,UAAU,EAC1D,gBAAgB,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD,UAAU,CAUZ;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EAChE,KAAK,EAAE,KAAK,EAAE,EACd,oBAAoB,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,UAAU,EAC1D,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,EACxC,gBAAgB,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD,KAAK,EAAE,CAyBT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU,EAErC,KAAK,EAAE,KAAK,EAAE,EACd,KAAK,EAAE,KAAK,EAAE,EACd,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,EACxC,OAAO,GAAE,mBAAwB,GAC/B,KAAK,EAAE,CAiDT;AAGD,OAAO,EACN,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import dagre from '@dagrejs/dagre';
|
|
2
|
+
import { Position } from '@xyflow/svelte';
|
|
3
|
+
/**
|
|
4
|
+
* Calculates upper-left flow coordinate for an element to be centered in the viewport.
|
|
5
|
+
*/
|
|
6
|
+
export function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
|
|
7
|
+
const rect = screenDimensions ?? (typeof document !== 'undefined'
|
|
8
|
+
? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }
|
|
9
|
+
: { width: 1920, height: 1080 });
|
|
10
|
+
const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
|
|
11
|
+
return {
|
|
12
|
+
x: flowPos.x - width / 2,
|
|
13
|
+
y: flowPos.y - height / 2
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Updates positions of nodes so their collective bounding box is centered in the current viewport.
|
|
18
|
+
*/
|
|
19
|
+
export function centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions) {
|
|
20
|
+
if (nodes.length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const bounds = getNodesBounds(nodes);
|
|
24
|
+
const boundingBoxOrigin = { x: bounds.x, y: bounds.y };
|
|
25
|
+
const centeredPosition = getCenteredNodePosition(bounds.width, bounds.height, screenToFlowPosition, screenDimensions);
|
|
26
|
+
const deltaX = centeredPosition.x - boundingBoxOrigin.x;
|
|
27
|
+
const deltaY = centeredPosition.y - boundingBoxOrigin.y;
|
|
28
|
+
return nodes.map((node) => ({
|
|
29
|
+
...node,
|
|
30
|
+
position: {
|
|
31
|
+
x: node.position.x + deltaX,
|
|
32
|
+
y: node.position.y + deltaY
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Automatically calculates directed hierarchical layout of nodes and edges using Dagre.
|
|
38
|
+
*/
|
|
39
|
+
export function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
|
|
40
|
+
if (nodes.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
const { direction = 'LR', ranksep = 100, nodesep = 25, defaultNodeWidth = 200, defaultNodeHeight = 200 } = options;
|
|
43
|
+
const isHorizontal = direction === 'LR' || direction === 'RL';
|
|
44
|
+
const bounds = getNodesBounds(nodes);
|
|
45
|
+
const dagreGraph = new dagre.graphlib.Graph();
|
|
46
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
47
|
+
dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
|
|
48
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
49
|
+
nodes.forEach((node) => {
|
|
50
|
+
const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
|
|
51
|
+
const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
|
|
52
|
+
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
|
53
|
+
});
|
|
54
|
+
edges.forEach((edge) => {
|
|
55
|
+
if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
|
|
56
|
+
dagreGraph.setEdge(edge.source, edge.target);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
dagre.layout(dagreGraph);
|
|
60
|
+
return nodes.map((node) => {
|
|
61
|
+
const nodeWithPosition = dagreGraph.node(node.id);
|
|
62
|
+
const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
|
|
63
|
+
const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
|
|
64
|
+
return {
|
|
65
|
+
...node,
|
|
66
|
+
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
|
67
|
+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
|
68
|
+
position: {
|
|
69
|
+
x: bounds.x + (nodeWithPosition?.x ?? 0) - nodeWidth / 2,
|
|
70
|
+
y: bounds.y + (nodeWithPosition?.y ?? 0) - nodeHeight / 2
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// Re-export incremental layout and collision-aware placement from core
|
|
76
|
+
export { ensureLayout, placeBeside, estimateNodeSize, flowSide } from '@pixerate/editor/canvas';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { XYPosition } from '@xyflow/svelte';
|
|
2
|
+
import type { CanvasNode } from './types.js';
|
|
3
|
+
export interface MultiDragFollowerOffset {
|
|
4
|
+
deltaX: number;
|
|
5
|
+
deltaY: number;
|
|
6
|
+
originalPosition: XYPosition;
|
|
7
|
+
stackOffsetX: number;
|
|
8
|
+
stackOffsetY: number;
|
|
9
|
+
rotationDeg: number;
|
|
10
|
+
}
|
|
11
|
+
export interface MultiDragState {
|
|
12
|
+
leadId: string;
|
|
13
|
+
offsets: Record<string, MultiDragFollowerOffset>;
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CreateCanvasMultiDragOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Base horizontal fan-out offset per stacked follower card (default: 4px).
|
|
19
|
+
*/
|
|
20
|
+
stackOffsetStepX?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Base vertical fan-out offset per stacked follower card (default: 4px).
|
|
23
|
+
*/
|
|
24
|
+
stackOffsetStepY?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Alternating angular tilt in degrees for follower cards (default: [-2, 2.5, -3, 3]).
|
|
27
|
+
*/
|
|
28
|
+
rotationAngles?: number[];
|
|
29
|
+
/**
|
|
30
|
+
* Base z-index for stacked cards during active dragging (default: 50).
|
|
31
|
+
*/
|
|
32
|
+
baseZIndex?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface FollowerTransform {
|
|
35
|
+
isFollower: boolean;
|
|
36
|
+
stackOffsetX: number;
|
|
37
|
+
stackOffsetY: number;
|
|
38
|
+
rotationDeg: number;
|
|
39
|
+
zIndex: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Headless Svelte 5 rune orchestrating multi-node drag operations with stacked visual fanning,
|
|
43
|
+
* dynamic z-index layering, and position restoration.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createCanvasMultiDrag(options?: CreateCanvasMultiDragOptions): {
|
|
46
|
+
readonly dragState: MultiDragState | null;
|
|
47
|
+
readonly isMultiDrag: boolean;
|
|
48
|
+
readonly count: number;
|
|
49
|
+
readonly leadId: string | null;
|
|
50
|
+
startMultiDrag: (leadNodeId: string, selectedNodeIds: string[] | Set<string>, nodes: CanvasNode[] | Map<string, CanvasNode>) => MultiDragState | null;
|
|
51
|
+
getFollowerTransform: (nodeId: string) => FollowerTransform;
|
|
52
|
+
cancelMultiDrag: (applyRestore?: (restores: {
|
|
53
|
+
id: string;
|
|
54
|
+
originalPosition: XYPosition;
|
|
55
|
+
}[]) => void) => {
|
|
56
|
+
id: string;
|
|
57
|
+
originalPosition: XYPosition;
|
|
58
|
+
}[];
|
|
59
|
+
commitMultiDrag: () => void;
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=createCanvasMultiDrag.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCanvasMultiDrag.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasMultiDrag.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,UAAU,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IACjD,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,4BAA4B;IAC5C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,4BAAiC;;;;;iCASlE,MAAM,mBACD,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,SAChC,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,KAC3C,cAAc,GAAG,IAAI;mCAiDc,MAAM,KAAG,iBAAiB;qCAyBhD,CAAC,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,UAAU,CAAA;KAAE,EAAE,KAAK,IAAI,KAC/E;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,UAAU,CAAA;KAAE,EAAE;2BAgBrB,IAAI;EAsBhC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless Svelte 5 rune orchestrating multi-node drag operations with stacked visual fanning,
|
|
3
|
+
* dynamic z-index layering, and position restoration.
|
|
4
|
+
*/
|
|
5
|
+
export function createCanvasMultiDrag(options = {}) {
|
|
6
|
+
const stepX = options.stackOffsetStepX ?? 4;
|
|
7
|
+
const stepY = options.stackOffsetStepY ?? 4;
|
|
8
|
+
const angles = options.rotationAngles ?? [-2, 2.5, -3, 3];
|
|
9
|
+
const baseZIndex = options.baseZIndex ?? 50;
|
|
10
|
+
let dragState = $state(null);
|
|
11
|
+
function startMultiDrag(leadNodeId, selectedNodeIds, nodes) {
|
|
12
|
+
const selectedSet = selectedNodeIds instanceof Set ? selectedNodeIds : new Set(selectedNodeIds);
|
|
13
|
+
if (selectedSet.size <= 1 || !selectedSet.has(leadNodeId)) {
|
|
14
|
+
dragState = null;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const nodeMap = nodes instanceof Map ? nodes : new Map(nodes.map((n) => [n.id, n]));
|
|
18
|
+
const leadNode = nodeMap.get(leadNodeId);
|
|
19
|
+
if (!leadNode) {
|
|
20
|
+
dragState = null;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const leadPos = leadNode.position ?? { x: 0, y: 0 };
|
|
24
|
+
const offsets = {};
|
|
25
|
+
let followerIndex = 0;
|
|
26
|
+
for (const id of selectedSet) {
|
|
27
|
+
if (id === leadNodeId)
|
|
28
|
+
continue;
|
|
29
|
+
const follower = nodeMap.get(id);
|
|
30
|
+
const fPos = follower?.position ?? { x: 0, y: 0 };
|
|
31
|
+
const angle = angles[followerIndex % angles.length];
|
|
32
|
+
offsets[id] = {
|
|
33
|
+
deltaX: fPos.x - leadPos.x,
|
|
34
|
+
deltaY: fPos.y - leadPos.y,
|
|
35
|
+
originalPosition: { ...fPos },
|
|
36
|
+
stackOffsetX: (followerIndex + 1) * stepX,
|
|
37
|
+
stackOffsetY: (followerIndex + 1) * stepY,
|
|
38
|
+
rotationDeg: angle
|
|
39
|
+
};
|
|
40
|
+
followerIndex++;
|
|
41
|
+
}
|
|
42
|
+
const state = {
|
|
43
|
+
leadId: leadNodeId,
|
|
44
|
+
offsets,
|
|
45
|
+
count: selectedSet.size
|
|
46
|
+
};
|
|
47
|
+
dragState = state;
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
50
|
+
function getFollowerTransform(nodeId) {
|
|
51
|
+
if (!dragState || !(nodeId in dragState.offsets)) {
|
|
52
|
+
return {
|
|
53
|
+
isFollower: false,
|
|
54
|
+
stackOffsetX: 0,
|
|
55
|
+
stackOffsetY: 0,
|
|
56
|
+
rotationDeg: 0,
|
|
57
|
+
zIndex: 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const offset = dragState.offsets[nodeId];
|
|
61
|
+
const followerKeys = Object.keys(dragState.offsets);
|
|
62
|
+
const index = followerKeys.indexOf(nodeId);
|
|
63
|
+
return {
|
|
64
|
+
isFollower: true,
|
|
65
|
+
stackOffsetX: offset.stackOffsetX,
|
|
66
|
+
stackOffsetY: offset.stackOffsetY,
|
|
67
|
+
rotationDeg: offset.rotationDeg,
|
|
68
|
+
zIndex: baseZIndex - (index + 1)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function cancelMultiDrag(applyRestore) {
|
|
72
|
+
if (!dragState)
|
|
73
|
+
return [];
|
|
74
|
+
const restores = Object.entries(dragState.offsets).map(([id, offset]) => ({
|
|
75
|
+
id,
|
|
76
|
+
originalPosition: { ...offset.originalPosition }
|
|
77
|
+
}));
|
|
78
|
+
if (applyRestore) {
|
|
79
|
+
applyRestore(restores);
|
|
80
|
+
}
|
|
81
|
+
dragState = null;
|
|
82
|
+
return restores;
|
|
83
|
+
}
|
|
84
|
+
function commitMultiDrag() {
|
|
85
|
+
dragState = null;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
get dragState() {
|
|
89
|
+
return dragState;
|
|
90
|
+
},
|
|
91
|
+
get isMultiDrag() {
|
|
92
|
+
return Boolean(dragState && dragState.count > 1);
|
|
93
|
+
},
|
|
94
|
+
get count() {
|
|
95
|
+
return dragState ? dragState.count : 0;
|
|
96
|
+
},
|
|
97
|
+
get leadId() {
|
|
98
|
+
return dragState?.leadId ?? null;
|
|
99
|
+
},
|
|
100
|
+
startMultiDrag,
|
|
101
|
+
getFollowerTransform,
|
|
102
|
+
cancelMultiDrag,
|
|
103
|
+
commitMultiDrag
|
|
104
|
+
};
|
|
105
|
+
}
|