@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
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
createCanvasGraph,
|
|
4
|
-
calculateGraphDifferences,
|
|
5
|
-
replaceNodeInGraph
|
|
6
|
-
} from '../createCanvasGraph.svelte.js';
|
|
7
|
-
import type { CanvasNode, CanvasEdge } from '../types.js';
|
|
8
|
-
|
|
9
|
-
describe('createCanvasGraph & Graph Operations', () => {
|
|
10
|
-
const createSampleNode = (id: string, x = 0, y = 0): CanvasNode => ({
|
|
11
|
-
id,
|
|
12
|
-
position: { x, y },
|
|
13
|
-
data: { label: `Node ${id}` }
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const createSampleEdge = (id: string, source: string, target: string): CanvasEdge => ({
|
|
17
|
-
id,
|
|
18
|
-
source,
|
|
19
|
-
target
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('initializes with default empty or provided nodes and edges', () => {
|
|
23
|
-
const graph = createCanvasGraph({
|
|
24
|
-
initialNodes: [createSampleNode('1')],
|
|
25
|
-
initialEdges: [createSampleEdge('e1', '1', '2')]
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
expect(graph.nodes).toHaveLength(1);
|
|
29
|
-
expect(graph.edges).toHaveLength(1);
|
|
30
|
-
expect(graph.nodes[0].id).toBe('1');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('adds nodes and ignores duplicates', () => {
|
|
34
|
-
const onNodesChange = vi.fn();
|
|
35
|
-
const graph = createCanvasGraph({ onNodesChange });
|
|
36
|
-
|
|
37
|
-
graph.addNodeOrNodes(createSampleNode('1'));
|
|
38
|
-
expect(graph.nodes).toHaveLength(1);
|
|
39
|
-
expect(onNodesChange).toHaveBeenCalledTimes(1);
|
|
40
|
-
|
|
41
|
-
// Adding existing node does not duplicate
|
|
42
|
-
graph.addNodeOrNodes(createSampleNode('1'));
|
|
43
|
-
expect(graph.nodes).toHaveLength(1);
|
|
44
|
-
|
|
45
|
-
// Adding multiple nodes
|
|
46
|
-
graph.addNodeOrNodes([createSampleNode('2'), createSampleNode('3')]);
|
|
47
|
-
expect(graph.nodes).toHaveLength(3);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('adds and removes edges cleanly', () => {
|
|
51
|
-
const onEdgesChange = vi.fn();
|
|
52
|
-
const graph = createCanvasGraph({ onEdgesChange });
|
|
53
|
-
|
|
54
|
-
const e1 = createSampleEdge('e1', '1', '2');
|
|
55
|
-
const e2 = createSampleEdge('e2', '2', '3');
|
|
56
|
-
|
|
57
|
-
graph.addEdge(e1);
|
|
58
|
-
expect(graph.edges).toHaveLength(1);
|
|
59
|
-
|
|
60
|
-
graph.addEdges([e2]);
|
|
61
|
-
expect(graph.edges).toHaveLength(2);
|
|
62
|
-
|
|
63
|
-
graph.removeEdges([e1]);
|
|
64
|
-
expect(graph.edges).toHaveLength(1);
|
|
65
|
-
expect(graph.edges[0].id).toBe('e2');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('removes nodes by reference or ID matching', () => {
|
|
69
|
-
const n1 = createSampleNode('1');
|
|
70
|
-
const n2 = createSampleNode('2');
|
|
71
|
-
const graph = createCanvasGraph({ initialNodes: [n1, n2] });
|
|
72
|
-
|
|
73
|
-
graph.removeNodes([n1]);
|
|
74
|
-
expect(graph.nodes).toHaveLength(1);
|
|
75
|
-
expect(graph.nodes[0].id).toBe('2');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('replaces a node and preserves connected edges when keepEdges is true', () => {
|
|
79
|
-
const n1 = createSampleNode('1');
|
|
80
|
-
const n2 = createSampleNode('2');
|
|
81
|
-
const e1 = createSampleEdge('e1', '1', '2');
|
|
82
|
-
const n1Replacement = createSampleNode('new-1', 10, 10);
|
|
83
|
-
|
|
84
|
-
const result = replaceNodeInGraph([n1, n2], [e1], n1, n1Replacement, true);
|
|
85
|
-
expect(result.nodes.some((n) => n.id === 'new-1')).toBe(true);
|
|
86
|
-
expect(result.nodes.some((n) => n.id === '1')).toBe(false);
|
|
87
|
-
|
|
88
|
-
// Edge should have updated source to new-1
|
|
89
|
-
expect(result.edges).toHaveLength(1);
|
|
90
|
-
expect(result.edges[0].source).toBe('new-1');
|
|
91
|
-
expect(result.edges[0].target).toBe('2');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('replaces a node without preserving edges when keepEdges is false', () => {
|
|
95
|
-
const n1 = createSampleNode('1');
|
|
96
|
-
const n2 = createSampleNode('2');
|
|
97
|
-
const e1 = createSampleEdge('e1', '1', '2');
|
|
98
|
-
const n1Replacement = createSampleNode('new-1', 10, 10);
|
|
99
|
-
|
|
100
|
-
const result = replaceNodeInGraph([n1, n2], [e1], n1, n1Replacement, false);
|
|
101
|
-
expect(result.nodes.some((n) => n.id === 'new-1')).toBe(true);
|
|
102
|
-
expect(result.edges).toHaveLength(1);
|
|
103
|
-
expect(result.edges[0].source).toBe('1'); // Unchanged
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('adds connected nodes connecting inbound and outbound targets', () => {
|
|
107
|
-
const graph = createCanvasGraph();
|
|
108
|
-
const inNode = createSampleNode('in');
|
|
109
|
-
const outNode = createSampleNode('out');
|
|
110
|
-
const midNode = createSampleNode('mid');
|
|
111
|
-
|
|
112
|
-
graph.addNodeOrNodes([inNode, outNode]);
|
|
113
|
-
|
|
114
|
-
graph.addConnectedNodes({
|
|
115
|
-
nodesToAdd: [midNode],
|
|
116
|
-
inboundNodes: [inNode],
|
|
117
|
-
outboundNodes: [outNode],
|
|
118
|
-
getEdgeLabel: (src, tgt) => `${src.id}->${tgt.id}`
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
expect(graph.nodes).toHaveLength(3);
|
|
122
|
-
expect(graph.edges).toHaveLength(2);
|
|
123
|
-
expect(graph.edges.find((e) => e.source === 'in' && e.target === 'mid')?.label).toBe('in->mid');
|
|
124
|
-
expect(graph.edges.find((e) => e.source === 'mid' && e.target === 'out')?.label).toBe('mid->out');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('accurately calculates graph differences against a baseline', () => {
|
|
128
|
-
const n1 = createSampleNode('1', 0, 0);
|
|
129
|
-
const n2 = createSampleNode('2', 50, 50);
|
|
130
|
-
const e1 = createSampleEdge('e1', '1', '2');
|
|
131
|
-
|
|
132
|
-
const baseline = {
|
|
133
|
-
nodes: [n1, n2],
|
|
134
|
-
edges: [e1]
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Node 2 position changed, node 3 added, edge 1 removed, edge 2 added
|
|
138
|
-
const n2Changed = createSampleNode('2', 100, 100);
|
|
139
|
-
const n3Added = createSampleNode('3', 200, 200);
|
|
140
|
-
const e2Added = createSampleEdge('e2', '2', '3');
|
|
141
|
-
|
|
142
|
-
const current = {
|
|
143
|
-
nodes: [n1, n2Changed, n3Added],
|
|
144
|
-
edges: [e2Added]
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const diff = calculateGraphDifferences(current, baseline);
|
|
148
|
-
|
|
149
|
-
expect(diff.changedNodes).toHaveLength(2); // n2 changed + n3 newly added
|
|
150
|
-
expect(diff.changedNodes.map((n) => n.id)).toContain('2');
|
|
151
|
-
expect(diff.changedNodes.map((n) => n.id)).toContain('3');
|
|
152
|
-
expect(diff.removedNodes).toHaveLength(0);
|
|
153
|
-
|
|
154
|
-
expect(diff.removedEdges).toHaveLength(1);
|
|
155
|
-
expect(diff.removedEdges[0].id).toBe('e1');
|
|
156
|
-
expect(diff.changedEdges).toHaveLength(1);
|
|
157
|
-
expect(diff.changedEdges[0].id).toBe('e2');
|
|
158
|
-
});
|
|
159
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
createCanvasInteractions,
|
|
4
|
-
defaultSvelteFlowPreset,
|
|
5
|
-
miroCompatiblePreset,
|
|
6
|
-
restorePanelPointerEvents
|
|
7
|
-
} from '../createCanvasInteractions.svelte.js';
|
|
8
|
-
|
|
9
|
-
describe('createCanvasInteractions', () => {
|
|
10
|
-
it('exposes valid default presets', () => {
|
|
11
|
-
expect(defaultSvelteFlowPreset.snapGrid).toEqual([25, 25]);
|
|
12
|
-
expect(defaultSvelteFlowPreset.panOnScroll).toBe(false);
|
|
13
|
-
expect(defaultSvelteFlowPreset.selectionOnDrag).toBe(false);
|
|
14
|
-
|
|
15
|
-
expect(miroCompatiblePreset.selectionOnDrag).toBe(true);
|
|
16
|
-
expect(miroCompatiblePreset.panOnDrag).toEqual([1, 2]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('toggles spacebar panning mode on Space keydown and keyup', () => {
|
|
20
|
-
const target = new EventTarget();
|
|
21
|
-
const onPanModeChange = vi.fn();
|
|
22
|
-
|
|
23
|
-
const interactions = createCanvasInteractions({
|
|
24
|
-
targetElement: target as unknown as HTMLElement,
|
|
25
|
-
onPanModeChange
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const cleanup = interactions.attachListeners(target as unknown as HTMLElement);
|
|
29
|
-
|
|
30
|
-
// Press Space
|
|
31
|
-
const downEvent = new KeyboardEvent('keydown', { key: ' ' });
|
|
32
|
-
target.dispatchEvent(downEvent);
|
|
33
|
-
|
|
34
|
-
expect(interactions.isSpacebarPanning).toBe(true);
|
|
35
|
-
expect(onPanModeChange).toHaveBeenCalledWith(true);
|
|
36
|
-
|
|
37
|
-
// Release Space
|
|
38
|
-
const upEvent = new KeyboardEvent('keyup', { key: ' ' });
|
|
39
|
-
target.dispatchEvent(upEvent);
|
|
40
|
-
|
|
41
|
-
expect(interactions.isSpacebarPanning).toBe(false);
|
|
42
|
-
expect(onPanModeChange).toHaveBeenCalledWith(false);
|
|
43
|
-
|
|
44
|
-
cleanup();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('detects trackpad pinch on wheel event with ctrlKey', () => {
|
|
48
|
-
const target = new EventTarget();
|
|
49
|
-
const onTrackpadDetected = vi.fn();
|
|
50
|
-
|
|
51
|
-
const interactions = createCanvasInteractions({
|
|
52
|
-
targetElement: target as unknown as HTMLElement,
|
|
53
|
-
onTrackpadDetected
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const cleanup = interactions.attachListeners(target as unknown as HTMLElement);
|
|
57
|
-
|
|
58
|
-
expect(interactions.trackpadDetected).toBe(false);
|
|
59
|
-
|
|
60
|
-
// Normal wheel without ctrlKey
|
|
61
|
-
const wheelEvent = new WheelEvent('wheel', { ctrlKey: false });
|
|
62
|
-
target.dispatchEvent(wheelEvent);
|
|
63
|
-
expect(interactions.trackpadDetected).toBe(false);
|
|
64
|
-
expect(onTrackpadDetected).not.toHaveBeenCalled();
|
|
65
|
-
|
|
66
|
-
// Trackpad pinch with ctrlKey
|
|
67
|
-
const pinchEvent = new WheelEvent('wheel', { ctrlKey: true });
|
|
68
|
-
target.dispatchEvent(pinchEvent);
|
|
69
|
-
expect(interactions.trackpadDetected).toBe(true);
|
|
70
|
-
expect(onTrackpadDetected).toHaveBeenCalledTimes(1);
|
|
71
|
-
|
|
72
|
-
cleanup();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('restores pointer events on DOM panels', () => {
|
|
76
|
-
const panel = document.createElement('div');
|
|
77
|
-
panel.className = 'svelte-flow__panel';
|
|
78
|
-
panel.style.pointerEvents = 'none';
|
|
79
|
-
document.body.appendChild(panel);
|
|
80
|
-
|
|
81
|
-
restorePanelPointerEvents();
|
|
82
|
-
|
|
83
|
-
expect(panel.style.pointerEvents).toBe('auto');
|
|
84
|
-
document.body.removeChild(panel);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
getCenteredNodePosition,
|
|
4
|
-
centerNodes,
|
|
5
|
-
getLayoutedNodes
|
|
6
|
-
} from '../createCanvasLayout.js';
|
|
7
|
-
import type { CanvasNode, CanvasEdge } from '../types.js';
|
|
8
|
-
|
|
9
|
-
describe('createCanvasLayout & Geometry Helpers', () => {
|
|
10
|
-
it('calculates centered node position correctly', () => {
|
|
11
|
-
const screenDimensions = { width: 1000, height: 800 };
|
|
12
|
-
const screenToFlowPosition = (pos: { x: number; y: number }) => ({ x: pos.x, y: pos.y });
|
|
13
|
-
|
|
14
|
-
const pos = getCenteredNodePosition(200, 100, screenToFlowPosition, screenDimensions);
|
|
15
|
-
// Center of 1000x800 is (500, 400). Top-left of 200x100 is (500 - 100, 400 - 50) = (400, 350)
|
|
16
|
-
expect(pos.x).toBe(400);
|
|
17
|
-
expect(pos.y).toBe(350);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('centers multiple nodes using centerNodes', () => {
|
|
21
|
-
const screenDimensions = { width: 1000, height: 800 };
|
|
22
|
-
const screenToFlowPosition = (pos: { x: number; y: number }) => pos;
|
|
23
|
-
|
|
24
|
-
const nodes: CanvasNode[] = [
|
|
25
|
-
{ id: '1', position: { x: 0, y: 0 }, data: {} },
|
|
26
|
-
{ id: '2', position: { x: 200, y: 100 }, data: {} }
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const getNodesBounds = () => ({ x: 0, y: 0, width: 300, height: 200 });
|
|
30
|
-
|
|
31
|
-
const centered = centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions);
|
|
32
|
-
|
|
33
|
-
// Center of viewport is (500, 400). Upper left for 300x200 bounding box is (350, 300).
|
|
34
|
-
// DeltaX = 350 - 0 = 350, DeltaY = 300 - 0 = 300.
|
|
35
|
-
expect(centered[0].position.x).toBe(350);
|
|
36
|
-
expect(centered[0].position.y).toBe(300);
|
|
37
|
-
expect(centered[1].position.x).toBe(550);
|
|
38
|
-
expect(centered[1].position.y).toBe(400);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('computes Dagre hierarchical layout positions for nodes and edges', () => {
|
|
42
|
-
const nodes: CanvasNode[] = [
|
|
43
|
-
{ id: 'root', position: { x: 0, y: 0 }, data: {}, measured: { width: 100, height: 100 } },
|
|
44
|
-
{ id: 'child1', position: { x: 0, y: 0 }, data: {}, measured: { width: 100, height: 100 } },
|
|
45
|
-
{ id: 'child2', position: { x: 0, y: 0 }, data: {}, measured: { width: 100, height: 100 } }
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const edges: CanvasEdge[] = [
|
|
49
|
-
{ id: 'e1', source: 'root', target: 'child1' },
|
|
50
|
-
{ id: 'e2', source: 'root', target: 'child2' }
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const getNodesBounds = () => ({ x: 100, y: 100, width: 400, height: 400 });
|
|
54
|
-
|
|
55
|
-
const layouted = getLayoutedNodes(nodes, edges, getNodesBounds, {
|
|
56
|
-
direction: 'LR',
|
|
57
|
-
ranksep: 80,
|
|
58
|
-
nodesep: 40
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
expect(layouted).toHaveLength(3);
|
|
62
|
-
|
|
63
|
-
const root = layouted.find((n) => n.id === 'root')!;
|
|
64
|
-
const child1 = layouted.find((n) => n.id === 'child1')!;
|
|
65
|
-
const child2 = layouted.find((n) => n.id === 'child2')!;
|
|
66
|
-
|
|
67
|
-
// Root should be to the left of child nodes in 'LR' layout
|
|
68
|
-
expect(root.position.x).toBeLessThan(child1.position.x);
|
|
69
|
-
expect(root.position.x).toBeLessThan(child2.position.x);
|
|
70
|
-
|
|
71
|
-
// Target and source positions should be configured for horizontal layout
|
|
72
|
-
expect(root.sourcePosition).toBe('right');
|
|
73
|
-
expect(child1.targetPosition).toBe('left');
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createCanvasMultiDrag } from '../createCanvasMultiDrag.svelte.js';
|
|
3
|
-
import {
|
|
4
|
-
getMarqueeSelectionPreset,
|
|
5
|
-
handleDeselectOnEscape
|
|
6
|
-
} from '../createCanvasSelection.svelte.js';
|
|
7
|
-
import type { CanvasNode } from '../types.js';
|
|
8
|
-
|
|
9
|
-
describe('createCanvasMultiDrag & Selection Helpers', () => {
|
|
10
|
-
const sampleNodes: CanvasNode[] = [
|
|
11
|
-
{ id: 'node-lead', position: { x: 100, y: 100 }, data: {} },
|
|
12
|
-
{ id: 'node-sub-1', position: { x: 100, y: 180 }, data: {} },
|
|
13
|
-
{ id: 'node-sub-2', position: { x: 100, y: 260 }, data: {} }
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
it('returns null and does not activate multi-drag if only 1 node is selected', () => {
|
|
17
|
-
const multiDrag = createCanvasMultiDrag();
|
|
18
|
-
const state = multiDrag.startMultiDrag('node-lead', ['node-lead'], sampleNodes);
|
|
19
|
-
|
|
20
|
-
expect(state).toBeNull();
|
|
21
|
-
expect(multiDrag.isMultiDrag).toBe(false);
|
|
22
|
-
expect(multiDrag.count).toBe(0);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('computes relative follower offsets, stack fan-out, and rotational tilt for multi-selection', () => {
|
|
26
|
-
const multiDrag = createCanvasMultiDrag({
|
|
27
|
-
stackOffsetStepX: 5,
|
|
28
|
-
stackOffsetStepY: 6,
|
|
29
|
-
rotationAngles: [-2, 3]
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const state = multiDrag.startMultiDrag(
|
|
33
|
-
'node-lead',
|
|
34
|
-
['node-lead', 'node-sub-1', 'node-sub-2'],
|
|
35
|
-
sampleNodes
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
expect(state).not.toBeNull();
|
|
39
|
-
expect(multiDrag.isMultiDrag).toBe(true);
|
|
40
|
-
expect(multiDrag.count).toBe(3);
|
|
41
|
-
expect(multiDrag.leadId).toBe('node-lead');
|
|
42
|
-
|
|
43
|
-
// Check follower 1
|
|
44
|
-
const f1 = multiDrag.getFollowerTransform('node-sub-1');
|
|
45
|
-
expect(f1.isFollower).toBe(true);
|
|
46
|
-
expect(f1.stackOffsetX).toBe(5);
|
|
47
|
-
expect(f1.stackOffsetY).toBe(6);
|
|
48
|
-
expect(f1.rotationDeg).toBe(-2);
|
|
49
|
-
expect(f1.zIndex).toBe(49); // baseZIndex (50) - 1
|
|
50
|
-
|
|
51
|
-
// Check follower 2
|
|
52
|
-
const f2 = multiDrag.getFollowerTransform('node-sub-2');
|
|
53
|
-
expect(f2.isFollower).toBe(true);
|
|
54
|
-
expect(f2.stackOffsetX).toBe(10);
|
|
55
|
-
expect(f2.stackOffsetY).toBe(12);
|
|
56
|
-
expect(f2.rotationDeg).toBe(3);
|
|
57
|
-
expect(f2.zIndex).toBe(48); // baseZIndex (50) - 2
|
|
58
|
-
|
|
59
|
-
// Non-follower check
|
|
60
|
-
const fLead = multiDrag.getFollowerTransform('node-lead');
|
|
61
|
-
expect(fLead.isFollower).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('restores original positions upon drag cancellation', () => {
|
|
65
|
-
const multiDrag = createCanvasMultiDrag();
|
|
66
|
-
multiDrag.startMultiDrag(
|
|
67
|
-
'node-lead',
|
|
68
|
-
['node-lead', 'node-sub-1'],
|
|
69
|
-
sampleNodes
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
let restoredItems: any[] = [];
|
|
73
|
-
const restores = multiDrag.cancelMultiDrag((items) => {
|
|
74
|
-
restoredItems = items;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
expect(restores).toHaveLength(1);
|
|
78
|
-
expect(restores[0].id).toBe('node-sub-1');
|
|
79
|
-
expect(restores[0].originalPosition).toEqual({ x: 100, y: 180 });
|
|
80
|
-
expect(restoredItems).toEqual(restores);
|
|
81
|
-
expect(multiDrag.isMultiDrag).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('generates marquee selection preset with spacebar panning awareness', () => {
|
|
85
|
-
const normalPreset = getMarqueeSelectionPreset({ isSpacebarPanning: false });
|
|
86
|
-
expect(normalPreset.selectionOnDrag).toBe(true);
|
|
87
|
-
expect(normalPreset.panOnDrag).toEqual([1, 2]);
|
|
88
|
-
|
|
89
|
-
const panPreset = getMarqueeSelectionPreset({ isSpacebarPanning: true });
|
|
90
|
-
expect(panPreset.selectionOnDrag).toBe(false);
|
|
91
|
-
expect(panPreset.panOnDrag).toEqual([0, 1, 2]);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('handles deselect on Escape key press', () => {
|
|
95
|
-
let deselected = false;
|
|
96
|
-
const handled = handleDeselectOnEscape(
|
|
97
|
-
{ key: 'Escape', code: 'Escape' } as KeyboardEvent,
|
|
98
|
-
() => {
|
|
99
|
-
deselected = true;
|
|
100
|
-
}
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
expect(handled).toBe(true);
|
|
104
|
-
expect(deselected).toBe(true);
|
|
105
|
-
|
|
106
|
-
const ignored = handleDeselectOnEscape(
|
|
107
|
-
{ key: 'Enter', code: 'Enter' } as KeyboardEvent,
|
|
108
|
-
() => {}
|
|
109
|
-
);
|
|
110
|
-
expect(ignored).toBe(false);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { flushSync } from 'svelte';
|
|
3
|
-
import { Position } from '@xyflow/svelte';
|
|
4
|
-
import {
|
|
5
|
-
preserveNodeMeasurements,
|
|
6
|
-
normalizeNodeHandles,
|
|
7
|
-
createCanvasNodeSync,
|
|
8
|
-
type HandleConfig
|
|
9
|
-
} from '../createCanvasNodeSync.svelte.js';
|
|
10
|
-
import type { CanvasNode } from '../types.js';
|
|
11
|
-
|
|
12
|
-
describe('createCanvasNodeSync & Node Measurement Caching', () => {
|
|
13
|
-
it('preserves measured dimensions from existing nodes to newly created nodes', () => {
|
|
14
|
-
const existingNodes: CanvasNode[] = [
|
|
15
|
-
{
|
|
16
|
-
id: 'node-1',
|
|
17
|
-
position: { x: 100, y: 100 },
|
|
18
|
-
data: {},
|
|
19
|
-
measured: { width: 280, height: 160 }
|
|
20
|
-
} as any,
|
|
21
|
-
{
|
|
22
|
-
id: 'node-2',
|
|
23
|
-
position: { x: 200, y: 200 },
|
|
24
|
-
data: {}
|
|
25
|
-
} as any
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const freshNodes: CanvasNode[] = [
|
|
29
|
-
{ id: 'node-1', position: { x: 100, y: 100 }, data: { title: 'Updated' } } as any,
|
|
30
|
-
{ id: 'node-2', position: { x: 200, y: 200 }, data: { title: 'Fresh' } } as any,
|
|
31
|
-
{ id: 'node-3', position: { x: 300, y: 300 }, data: { title: 'Brand New' } } as any
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const preserved = preserveNodeMeasurements(freshNodes, existingNodes);
|
|
35
|
-
|
|
36
|
-
expect((preserved[0] as any).measured).toEqual({ width: 280, height: 160 });
|
|
37
|
-
expect((preserved[1] as any).measured).toBeUndefined();
|
|
38
|
-
expect((preserved[2] as any).measured).toBeUndefined();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('normalizes node handles to prevent xyflow forceInitialRender selection flash', () => {
|
|
42
|
-
const rawNode: CanvasNode = {
|
|
43
|
-
id: 'node-1',
|
|
44
|
-
position: { x: 0, y: 0 },
|
|
45
|
-
data: {}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const defaultHandles: HandleConfig[] = [
|
|
49
|
-
{ id: null, type: 'target', position: Position.Left, x: 0, y: 50, width: 12, height: 12 },
|
|
50
|
-
{ id: null, type: 'source', position: Position.Right, x: 260, y: 50, width: 12, height: 12 }
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const normalized = normalizeNodeHandles(rawNode, defaultHandles);
|
|
54
|
-
|
|
55
|
-
expect((normalized as any).handles).toHaveLength(2);
|
|
56
|
-
expect((normalized as any).handles[0].position).toBe(Position.Left);
|
|
57
|
-
expect((normalized as any).handles[1].position).toBe(Position.Right);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('synchronizes domain items to nodes and edges while caching measurements across ticks', () => {
|
|
61
|
-
let domainItems = [
|
|
62
|
-
{ id: 'item-1', name: 'Task 1', dependencies: [] },
|
|
63
|
-
{ id: 'item-2', name: 'Task 2', dependencies: ['item-1'] }
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
let sync: ReturnType<typeof createCanvasNodeSync<any>>;
|
|
67
|
-
|
|
68
|
-
const cleanup = $effect.root(() => {
|
|
69
|
-
sync = createCanvasNodeSync({
|
|
70
|
-
items: () => domainItems,
|
|
71
|
-
toNode: (item) => ({
|
|
72
|
-
id: item.id,
|
|
73
|
-
position: { x: 10, y: 20 },
|
|
74
|
-
data: { label: item.name }
|
|
75
|
-
}),
|
|
76
|
-
toEdges: (items) =>
|
|
77
|
-
items.flatMap((item) =>
|
|
78
|
-
item.dependencies.map((depId) => ({
|
|
79
|
-
id: `edge-${depId}-${item.id}`,
|
|
80
|
-
source: depId,
|
|
81
|
-
target: item.id
|
|
82
|
-
}))
|
|
83
|
-
),
|
|
84
|
-
defaultHandles: () => [
|
|
85
|
-
{ id: 'in', type: 'target', position: Position.Left },
|
|
86
|
-
{ id: 'out', type: 'source', position: Position.Right }
|
|
87
|
-
]
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
flushSync();
|
|
92
|
-
|
|
93
|
-
expect(sync!.nodes).toHaveLength(2);
|
|
94
|
-
expect(sync!.edges).toHaveLength(1);
|
|
95
|
-
expect(sync!.edges[0].id).toBe('edge-item-1-item-2');
|
|
96
|
-
expect((sync!.nodes[0] as any).handles).toHaveLength(2);
|
|
97
|
-
|
|
98
|
-
// Simulate xyflow measuring node-1
|
|
99
|
-
sync!.updateNodeMeasurements('item-1', 250, 120);
|
|
100
|
-
expect((sync!.nodes[0] as any).measured).toEqual({ width: 250, height: 120 });
|
|
101
|
-
|
|
102
|
-
cleanup();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { createCanvasShortcuts } from '../createCanvasShortcuts.js';
|
|
3
|
-
import type { CanvasNode } from '../types.js';
|
|
4
|
-
|
|
5
|
-
describe('createCanvasShortcuts', () => {
|
|
6
|
-
it('attaches listener, handles hotkeys, and cleans up', () => {
|
|
7
|
-
const target = new EventTarget();
|
|
8
|
-
const onDelete = vi.fn();
|
|
9
|
-
const onDuplicate = vi.fn();
|
|
10
|
-
const onSelectAll = vi.fn();
|
|
11
|
-
|
|
12
|
-
const selectedNodes: CanvasNode[] = [{ id: 'node-1', position: { x: 0, y: 0 }, data: {} }];
|
|
13
|
-
|
|
14
|
-
const destroy = createCanvasShortcuts({
|
|
15
|
-
targetElement: target as unknown as HTMLElement,
|
|
16
|
-
getSelectedNodes: () => selectedNodes,
|
|
17
|
-
onDelete,
|
|
18
|
-
onDuplicate,
|
|
19
|
-
onSelectAll
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// Trigger Backspace -> onDelete
|
|
23
|
-
const deleteEvent = new CustomEvent('keydown', { cancelable: true });
|
|
24
|
-
Object.assign(deleteEvent, { key: 'Backspace' });
|
|
25
|
-
target.dispatchEvent(deleteEvent);
|
|
26
|
-
|
|
27
|
-
expect(onDelete).toHaveBeenCalledWith(selectedNodes);
|
|
28
|
-
|
|
29
|
-
// Trigger Meta + D -> onDuplicate
|
|
30
|
-
const dupEvent = new CustomEvent('keydown', { cancelable: true });
|
|
31
|
-
Object.assign(dupEvent, { key: 'd', metaKey: true });
|
|
32
|
-
target.dispatchEvent(dupEvent);
|
|
33
|
-
|
|
34
|
-
expect(onDuplicate).toHaveBeenCalledWith(selectedNodes);
|
|
35
|
-
|
|
36
|
-
// Trigger Meta + A -> onSelectAll
|
|
37
|
-
const allEvent = new CustomEvent('keydown', { cancelable: true });
|
|
38
|
-
Object.assign(allEvent, { key: 'a', metaKey: true });
|
|
39
|
-
target.dispatchEvent(allEvent);
|
|
40
|
-
|
|
41
|
-
expect(onSelectAll).toHaveBeenCalledTimes(1);
|
|
42
|
-
|
|
43
|
-
// Trigger shortcut while targeting an input -> should be ignored
|
|
44
|
-
const inputEl = { tagName: 'INPUT' } as unknown as HTMLElement;
|
|
45
|
-
const ignoredEvent = new CustomEvent('keydown', { cancelable: true });
|
|
46
|
-
Object.defineProperty(ignoredEvent, 'target', { value: inputEl });
|
|
47
|
-
Object.assign(ignoredEvent, { key: 'Backspace' });
|
|
48
|
-
target.dispatchEvent(ignoredEvent);
|
|
49
|
-
|
|
50
|
-
expect(onDelete).toHaveBeenCalledTimes(1); // Not called again
|
|
51
|
-
|
|
52
|
-
// Destroy listeners
|
|
53
|
-
destroy();
|
|
54
|
-
|
|
55
|
-
// Dispatching after destroy should do nothing
|
|
56
|
-
target.dispatchEvent(deleteEvent);
|
|
57
|
-
expect(onDelete).toHaveBeenCalledTimes(1);
|
|
58
|
-
});
|
|
59
|
-
});
|