@pixerate/editor-svelte 0.15.3 → 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 +13 -15
- 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,634 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
linearTrajectory,
|
|
4
|
-
linearPositionTrajectory,
|
|
5
|
-
createFanOutTrajectory,
|
|
6
|
-
createBezierTrajectory,
|
|
7
|
-
runMultiNodeTransition,
|
|
8
|
-
} from "../createCanvasTrajectory.js";
|
|
9
|
-
import {
|
|
10
|
-
computeNodesBoundingBox,
|
|
11
|
-
calculateDirectionalDisplacement,
|
|
12
|
-
calculateReflowDisplacement,
|
|
13
|
-
} from "../createCanvasDisplacement.js";
|
|
14
|
-
import { createCanvasExplosion } from "../createCanvasExplosion.svelte.js";
|
|
15
|
-
import { createCanvasGraph } from "../createCanvasGraph.svelte.js";
|
|
16
|
-
import type { CanvasNode, CanvasEdge } from "../types.js";
|
|
17
|
-
|
|
18
|
-
describe("Canvas Trajectory Math & Transition Runner", () => {
|
|
19
|
-
it("evaluates linear trajectory at progress intervals", () => {
|
|
20
|
-
const origin = { x: 100, y: 100 };
|
|
21
|
-
const target = { x: 300, y: 200 };
|
|
22
|
-
|
|
23
|
-
const start = linearTrajectory(origin, target, 0, 0, 1);
|
|
24
|
-
expect(start.position).toEqual({ x: 100, y: 100 });
|
|
25
|
-
expect(start.scale).toBe(0);
|
|
26
|
-
|
|
27
|
-
const mid = linearTrajectory(origin, target, 0.5, 0, 1);
|
|
28
|
-
expect(mid.position).toEqual({ x: 200, y: 150 });
|
|
29
|
-
expect(mid.scale).toBe(0.5);
|
|
30
|
-
|
|
31
|
-
const end = linearTrajectory(origin, target, 1, 0, 1);
|
|
32
|
-
expect(end.position).toEqual({ x: 300, y: 200 });
|
|
33
|
-
expect(end.scale).toBe(1);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("creates fan-out curved trajectory with scale and opacity", () => {
|
|
37
|
-
const origin = { x: 50, y: 100 };
|
|
38
|
-
const targetTop = { x: 250, y: 50 };
|
|
39
|
-
const targetBottom = { x: 250, y: 150 };
|
|
40
|
-
|
|
41
|
-
const trajectory = createFanOutTrajectory({
|
|
42
|
-
curvature: 0.5,
|
|
43
|
-
startScale: 0.2,
|
|
44
|
-
endScale: 1,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const topPoint = trajectory(origin, targetTop, 0.5, 0, 2);
|
|
48
|
-
const bottomPoint = trajectory(origin, targetBottom, 0.5, 1, 2);
|
|
49
|
-
|
|
50
|
-
expect(topPoint.scale).toBeCloseTo(0.6);
|
|
51
|
-
expect(bottomPoint.scale).toBeCloseTo(0.6);
|
|
52
|
-
expect(topPoint.opacity).toBeCloseTo(0.5);
|
|
53
|
-
expect(bottomPoint.opacity).toBeCloseTo(0.5);
|
|
54
|
-
// Trajectory arc deflects top and bottom differently based on normalized index
|
|
55
|
-
expect(topPoint.position.y).not.toBe(bottomPoint.position.y);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("evaluates linearPositionTrajectory without altering scale or opacity", () => {
|
|
59
|
-
const origin = { x: 10, y: 20 };
|
|
60
|
-
const target = { x: 110, y: 120 };
|
|
61
|
-
|
|
62
|
-
const pt = linearPositionTrajectory(origin, target, 0.5, 0, 1);
|
|
63
|
-
expect(pt.position).toEqual({ x: 60, y: 70 });
|
|
64
|
-
expect(pt.scale).toBeUndefined();
|
|
65
|
-
expect(pt.opacity).toBeUndefined();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("creates reverse trajectory fading out opacity from 1 to 0", () => {
|
|
69
|
-
const reverseTrajectory = createFanOutTrajectory({
|
|
70
|
-
startOpacity: 1,
|
|
71
|
-
endOpacity: 0,
|
|
72
|
-
startScale: 1,
|
|
73
|
-
endScale: 0,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const pt = reverseTrajectory({ x: 100, y: 100 }, { x: 0, y: 0 }, 0.5, 0, 1);
|
|
77
|
-
expect(pt.opacity).toBeCloseTo(0.5);
|
|
78
|
-
expect(pt.scale).toBeCloseTo(0.5);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("creates custom bezier trajectory with control point calculator", () => {
|
|
82
|
-
const trajectory = createBezierTrajectory((orig, tgt) => ({
|
|
83
|
-
x: orig.x,
|
|
84
|
-
y: tgt.y,
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
const pt = trajectory({ x: 0, y: 0 }, { x: 100, y: 100 }, 0.5, 0, 1);
|
|
88
|
-
// Quadratic Bezier at t=0.5 with P0=(0,0), P1=(0,100), P2=(100,100)
|
|
89
|
-
// x = 0.25*0 + 0.5*0 + 0.25*100 = 25
|
|
90
|
-
// y = 0.25*0 + 0.5*100 + 0.25*100 = 75
|
|
91
|
-
expect(pt.position.x).toBe(25);
|
|
92
|
-
expect(pt.position.y).toBe(75);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("runs multi-node transitions and notifies on completion", () => {
|
|
96
|
-
const node: CanvasNode = {
|
|
97
|
-
id: "n1",
|
|
98
|
-
position: { x: 0, y: 0 },
|
|
99
|
-
data: {},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const onComplete = vi.fn();
|
|
103
|
-
const cancel = runMultiNodeTransition(
|
|
104
|
-
[
|
|
105
|
-
{
|
|
106
|
-
node,
|
|
107
|
-
from: { x: 0, y: 0 },
|
|
108
|
-
to: { x: 100, y: 100 },
|
|
109
|
-
duration: 0, // Immediate completion for testing
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
{ onComplete },
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
expect(node.position).toEqual({ x: 100, y: 100 });
|
|
116
|
-
expect(onComplete).toHaveBeenCalled();
|
|
117
|
-
cancel();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("animates opacity and sets style in runMultiNodeTransition", () => {
|
|
121
|
-
const node: CanvasNode = {
|
|
122
|
-
id: "n1",
|
|
123
|
-
position: { x: 0, y: 0 },
|
|
124
|
-
data: {},
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const trajectory = createFanOutTrajectory({
|
|
128
|
-
startOpacity: 0,
|
|
129
|
-
endOpacity: 1,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const cancel = runMultiNodeTransition([
|
|
133
|
-
{
|
|
134
|
-
node,
|
|
135
|
-
from: { x: 0, y: 0 },
|
|
136
|
-
to: { x: 100, y: 100 },
|
|
137
|
-
trajectory,
|
|
138
|
-
duration: 0,
|
|
139
|
-
},
|
|
140
|
-
]);
|
|
141
|
-
|
|
142
|
-
expect(node.position).toEqual({ x: 100, y: 100 });
|
|
143
|
-
expect((node.data as any).opacity).toBe(1);
|
|
144
|
-
expect(node.style).toContain("opacity: 1");
|
|
145
|
-
cancel();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe("Spatial Displacement Solvers", () => {
|
|
150
|
-
const createNode = (
|
|
151
|
-
id: string,
|
|
152
|
-
x: number,
|
|
153
|
-
y: number,
|
|
154
|
-
w = 150,
|
|
155
|
-
h = 50,
|
|
156
|
-
): CanvasNode => ({
|
|
157
|
-
id,
|
|
158
|
-
position: { x, y },
|
|
159
|
-
measured: { width: w, height: h },
|
|
160
|
-
data: {},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("computes bounding box for a set of nodes", () => {
|
|
164
|
-
const nodes = [
|
|
165
|
-
createNode("a", 10, 20, 100, 40),
|
|
166
|
-
createNode("b", 50, 100, 200, 80),
|
|
167
|
-
];
|
|
168
|
-
const bbox = computeNodesBoundingBox(nodes);
|
|
169
|
-
|
|
170
|
-
expect(bbox.x).toBe(10);
|
|
171
|
-
expect(bbox.y).toBe(20);
|
|
172
|
-
expect(bbox.width).toBe(240); // 50 + 200 - 10 = 240
|
|
173
|
-
expect(bbox.height).toBe(160); // 100 + 80 - 20 = 160
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("calculates directional displacement shifting downstream nodes to make space", () => {
|
|
177
|
-
const origin = createNode("origin", 100, 100, 100, 50);
|
|
178
|
-
const downstream = createNode("downstream", 300, 100, 100, 50);
|
|
179
|
-
const upstream = createNode("upstream", 0, 100, 100, 50);
|
|
180
|
-
|
|
181
|
-
// Child cluster with width=200
|
|
182
|
-
const children = [
|
|
183
|
-
createNode("c1", 150, 80, 200, 40),
|
|
184
|
-
createNode("c2", 150, 120, 200, 40),
|
|
185
|
-
];
|
|
186
|
-
|
|
187
|
-
const displaced = calculateDirectionalDisplacement({
|
|
188
|
-
existingNodes: [origin, downstream, upstream],
|
|
189
|
-
originNode: origin,
|
|
190
|
-
childNodes: children,
|
|
191
|
-
options: { direction: "right", gap: 40 },
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Child cluster width is 200, gap is 40 -> deltaPrimary = 240
|
|
195
|
-
expect(displaced.has("downstream")).toBe(true);
|
|
196
|
-
expect(displaced.get("downstream")?.x).toBe(300 + 240);
|
|
197
|
-
// Upstream node remains untouched
|
|
198
|
-
expect(displaced.has("upstream")).toBe(false);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("calculates vertical directional displacement moving above nodes up and below nodes down equally without shifting x", () => {
|
|
202
|
-
const origin = createNode("origin", 100, 200, 100, 40);
|
|
203
|
-
const above = createNode("above", 100, 100, 100, 40);
|
|
204
|
-
const below = createNode("below", 300, 300, 100, 40);
|
|
205
|
-
|
|
206
|
-
// Child cluster extending above and below origin
|
|
207
|
-
const children = [
|
|
208
|
-
createNode("c1", 250, 140, 100, 30),
|
|
209
|
-
createNode("c2", 250, 280, 100, 30), // bottom at 310
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
const displaced = calculateDirectionalDisplacement({
|
|
213
|
-
existingNodes: [origin, above, below],
|
|
214
|
-
originNode: origin,
|
|
215
|
-
childNodes: children,
|
|
216
|
-
options: { direction: "vertical", gap: 20 },
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// deltaUp = (200 - 140) + 20 = 80
|
|
220
|
-
// deltaDown = (310 - (200 + 40)) + 20 = 90
|
|
221
|
-
// equalDelta = max(80, 90) = 90 -> BOTH above and below displace equally by 90!
|
|
222
|
-
expect(displaced.get("above")?.y).toBe(100 - 90);
|
|
223
|
-
expect(displaced.get("above")?.x).toBe(100);
|
|
224
|
-
|
|
225
|
-
expect(displaced.get("below")?.y).toBe(300 + 90);
|
|
226
|
-
expect(displaced.get("below")?.x).toBe(300);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("displaces both above and below nodes equally when deltaUp is greater than deltaDown", () => {
|
|
230
|
-
const origin = createNode("origin", 100, 200, 100, 40);
|
|
231
|
-
const above = createNode("above", 100, 100, 100, 40);
|
|
232
|
-
const below = createNode("below", 300, 300, 100, 40);
|
|
233
|
-
|
|
234
|
-
const children = [
|
|
235
|
-
createNode("c1", 250, 100, 100, 30), // top at 100 -> deltaUp = 200 - 100 + 20 = 120
|
|
236
|
-
createNode("c2", 250, 220, 100, 30), // bottom at 250 -> deltaDown = 250 - 240 + 20 = 30
|
|
237
|
-
];
|
|
238
|
-
|
|
239
|
-
const displaced = calculateDirectionalDisplacement({
|
|
240
|
-
existingNodes: [origin, above, below],
|
|
241
|
-
originNode: origin,
|
|
242
|
-
childNodes: children,
|
|
243
|
-
options: { direction: "vertical", gap: 20 },
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// equalDelta = max(120, 30) = 120 -> BOTH displace equally by 120
|
|
247
|
-
expect(displaced.get("above")?.y).toBe(100 - 120);
|
|
248
|
-
expect(displaced.get("below")?.y).toBe(300 + 120);
|
|
249
|
-
expect(displaced.get("below")?.x).toBe(300);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("calculates Dagre reflow displacement anchoring origin node position", () => {
|
|
253
|
-
const origin = createNode("origin", 100, 100, 120, 60);
|
|
254
|
-
const downstream = createNode("downstream", 300, 100, 120, 60);
|
|
255
|
-
const edges: CanvasEdge[] = [
|
|
256
|
-
{ id: "e1", source: "origin", target: "downstream" },
|
|
257
|
-
];
|
|
258
|
-
|
|
259
|
-
const children = [
|
|
260
|
-
createNode("c1", 0, 0, 120, 60),
|
|
261
|
-
createNode("c2", 0, 0, 120, 60),
|
|
262
|
-
];
|
|
263
|
-
const childEdges: CanvasEdge[] = [
|
|
264
|
-
{ id: "ec1", source: "origin", target: "c1" },
|
|
265
|
-
{ id: "ec2", source: "c1", target: "downstream" },
|
|
266
|
-
];
|
|
267
|
-
|
|
268
|
-
const displaced = calculateReflowDisplacement({
|
|
269
|
-
existingNodes: [origin, downstream],
|
|
270
|
-
existingEdges: edges,
|
|
271
|
-
originNode: origin,
|
|
272
|
-
childNodes: children,
|
|
273
|
-
childEdges,
|
|
274
|
-
options: { direction: "LR", ranksep: 100 },
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// Downstream node should be pushed further to make space for child rank
|
|
278
|
-
expect(displaced.has("downstream")).toBe(true);
|
|
279
|
-
expect(displaced.get("downstream")!.x).toBeGreaterThan(origin.position.x);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
describe("createCanvasExplosion Rune Lifecycle & Reversibility", () => {
|
|
284
|
-
const createNode = (
|
|
285
|
-
id: string,
|
|
286
|
-
x: number,
|
|
287
|
-
y: number,
|
|
288
|
-
w = 150,
|
|
289
|
-
h = 50,
|
|
290
|
-
): CanvasNode => ({
|
|
291
|
-
id,
|
|
292
|
-
position: { x, y },
|
|
293
|
-
measured: { width: w, height: h },
|
|
294
|
-
data: {},
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it("explodes node, connects children, shifts downstream nodes, and collapses reversibly", () => {
|
|
298
|
-
const origin = createNode("origin", 100, 100);
|
|
299
|
-
const downstream = createNode("downstream", 300, 100);
|
|
300
|
-
const initialEdge: CanvasEdge = {
|
|
301
|
-
id: "e1",
|
|
302
|
-
source: "origin",
|
|
303
|
-
target: "downstream",
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const graph = createCanvasGraph({
|
|
307
|
-
initialNodes: [origin, downstream],
|
|
308
|
-
initialEdges: [initialEdge],
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
const explosion = createCanvasExplosion(graph);
|
|
312
|
-
|
|
313
|
-
expect(explosion.isExploded("origin")).toBe(false);
|
|
314
|
-
|
|
315
|
-
const child1 = createNode("c1", 250, 70);
|
|
316
|
-
const child2 = createNode("c2", 250, 130);
|
|
317
|
-
|
|
318
|
-
// 1. Explode
|
|
319
|
-
const exploded = explosion.explodeNode("origin", {
|
|
320
|
-
childNodes: [child1, child2],
|
|
321
|
-
duration: 0, // instantaneous for sync unit testing
|
|
322
|
-
connectParentToChildren: "all",
|
|
323
|
-
directionalOptions: { direction: "right", gap: 50 },
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
expect(exploded).toBe(true);
|
|
327
|
-
expect(explosion.isExploded("origin")).toBe(true);
|
|
328
|
-
|
|
329
|
-
// Check that children were added
|
|
330
|
-
expect(graph.nodes.some((n) => n.id === "c1")).toBe(true);
|
|
331
|
-
expect(graph.nodes.some((n) => n.id === "c2")).toBe(true);
|
|
332
|
-
|
|
333
|
-
// Check that parent was connected to children
|
|
334
|
-
expect(
|
|
335
|
-
graph.edges.some((e) => e.source === "origin" && e.target === "c1"),
|
|
336
|
-
).toBe(true);
|
|
337
|
-
expect(
|
|
338
|
-
graph.edges.some((e) => e.source === "origin" && e.target === "c2"),
|
|
339
|
-
).toBe(true);
|
|
340
|
-
|
|
341
|
-
// Check that downstream node was displaced rightward
|
|
342
|
-
const displacedDownstream = graph.nodes.find((n) => n.id === "downstream")!;
|
|
343
|
-
expect(displacedDownstream.position.x).toBeGreaterThan(300);
|
|
344
|
-
|
|
345
|
-
// 2. Collapse
|
|
346
|
-
const collapsed = explosion.collapseNode("origin", {
|
|
347
|
-
duration: 0,
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
expect(collapsed).toBe(true);
|
|
351
|
-
expect(explosion.isExploded("origin")).toBe(false);
|
|
352
|
-
|
|
353
|
-
// Check that children were removed from graph
|
|
354
|
-
expect(graph.nodes.some((n) => n.id === "c1")).toBe(false);
|
|
355
|
-
expect(graph.nodes.some((n) => n.id === "c2")).toBe(false);
|
|
356
|
-
|
|
357
|
-
// Check that child edges were pruned
|
|
358
|
-
expect(
|
|
359
|
-
graph.edges.some((e) => e.source === "origin" && e.target === "c1"),
|
|
360
|
-
).toBe(false);
|
|
361
|
-
|
|
362
|
-
// Check that downstream node returned to its exact pre-explosion baseline coordinate
|
|
363
|
-
const restoredDownstream = graph.nodes.find((n) => n.id === "downstream")!;
|
|
364
|
-
expect(restoredDownstream.position.x).toBe(300);
|
|
365
|
-
expect(restoredDownstream.position.y).toBe(100);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it("toggles explosion with toggleExplode", () => {
|
|
369
|
-
const origin = createNode("origin", 100, 100);
|
|
370
|
-
const graph = createCanvasGraph({ initialNodes: [origin] });
|
|
371
|
-
const explosion = createCanvasExplosion(graph);
|
|
372
|
-
|
|
373
|
-
const child = createNode("c1", 200, 100);
|
|
374
|
-
|
|
375
|
-
explosion.toggleExplode("origin", {
|
|
376
|
-
childNodes: [child],
|
|
377
|
-
duration: 0,
|
|
378
|
-
});
|
|
379
|
-
expect(explosion.isExploded("origin")).toBe(true);
|
|
380
|
-
|
|
381
|
-
explosion.toggleExplode(
|
|
382
|
-
"origin",
|
|
383
|
-
{ childNodes: [child], duration: 0 },
|
|
384
|
-
{ duration: 0 },
|
|
385
|
-
);
|
|
386
|
-
expect(explosion.isExploded("origin")).toBe(false);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("updates node positions and opacity continuously across multiple animation frames without freezing", () => {
|
|
390
|
-
let currentTime = 1000;
|
|
391
|
-
const originalDateNow = Date.now;
|
|
392
|
-
Date.now = () => currentTime;
|
|
393
|
-
|
|
394
|
-
let rafCallback: any = null;
|
|
395
|
-
const originalRaf = globalThis.requestAnimationFrame;
|
|
396
|
-
globalThis.requestAnimationFrame = ((cb: () => void) => {
|
|
397
|
-
rafCallback = cb;
|
|
398
|
-
return 1;
|
|
399
|
-
}) as any;
|
|
400
|
-
|
|
401
|
-
try {
|
|
402
|
-
const origin = createNode("origin", 0, 0);
|
|
403
|
-
const graph = createCanvasGraph({ initialNodes: [origin] });
|
|
404
|
-
const explosion = createCanvasExplosion(graph);
|
|
405
|
-
|
|
406
|
-
const child = createNode("c1", 100, 100);
|
|
407
|
-
|
|
408
|
-
explosion.explodeNode("origin", {
|
|
409
|
-
childNodes: [child],
|
|
410
|
-
duration: 200,
|
|
411
|
-
easing: (t) => t,
|
|
412
|
-
trajectory: linearPositionTrajectory,
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Frame 0 ran at t=0 (currentTime = 1000)
|
|
416
|
-
let graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
417
|
-
expect(graphChild).toBeDefined();
|
|
418
|
-
|
|
419
|
-
// Advance time by 100ms (progress = 0.5)
|
|
420
|
-
currentTime = 1100;
|
|
421
|
-
expect(rafCallback).not.toBeNull();
|
|
422
|
-
if (rafCallback) {
|
|
423
|
-
const nextCb = rafCallback;
|
|
424
|
-
rafCallback = null;
|
|
425
|
-
nextCb();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
429
|
-
expect(graphChild.position.x).toBeCloseTo(50, 0);
|
|
430
|
-
expect(graphChild.position.y).toBeCloseTo(50, 0);
|
|
431
|
-
|
|
432
|
-
// Advance time by another 100ms (progress = 1.0)
|
|
433
|
-
currentTime = 1200;
|
|
434
|
-
expect(rafCallback).not.toBeNull();
|
|
435
|
-
if (rafCallback) {
|
|
436
|
-
const nextCb = rafCallback;
|
|
437
|
-
rafCallback = null;
|
|
438
|
-
nextCb();
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
442
|
-
expect(graphChild.position.x).toBeCloseTo(100, 0);
|
|
443
|
-
expect(graphChild.position.y).toBeCloseTo(100, 0);
|
|
444
|
-
|
|
445
|
-
// Collapse node over 200ms
|
|
446
|
-
explosion.collapseNode("origin", {
|
|
447
|
-
duration: 200,
|
|
448
|
-
easing: (t) => t,
|
|
449
|
-
trajectory: linearPositionTrajectory,
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
// Advance time by 100ms into collapse (progress = 0.5)
|
|
453
|
-
currentTime = 1300;
|
|
454
|
-
expect(rafCallback).not.toBeNull();
|
|
455
|
-
if (rafCallback) {
|
|
456
|
-
const nextCb = rafCallback;
|
|
457
|
-
rafCallback = null;
|
|
458
|
-
nextCb();
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
462
|
-
expect(graphChild).toBeDefined();
|
|
463
|
-
expect(graphChild.position.x).toBeCloseTo(50, 0);
|
|
464
|
-
expect(graphChild.position.y).toBeCloseTo(50, 0);
|
|
465
|
-
|
|
466
|
-
// Advance time by another 100ms (collapse completion)
|
|
467
|
-
currentTime = 1400;
|
|
468
|
-
expect(rafCallback).not.toBeNull();
|
|
469
|
-
if (rafCallback) {
|
|
470
|
-
const nextCb = rafCallback;
|
|
471
|
-
rafCallback = null;
|
|
472
|
-
nextCb();
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
expect(graph.nodes.some((n) => n.id === "c1")).toBe(false);
|
|
476
|
-
expect(explosion.isExploded("origin")).toBe(false);
|
|
477
|
-
} finally {
|
|
478
|
-
Date.now = originalDateNow;
|
|
479
|
-
globalThis.requestAnimationFrame = originalRaf;
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it("animates opacity for child nodes and connecting edges while preserving full opacity of displaced sibling nodes", () => {
|
|
484
|
-
let currentTime = 1000;
|
|
485
|
-
const originalDateNow = Date.now;
|
|
486
|
-
Date.now = () => currentTime;
|
|
487
|
-
|
|
488
|
-
let rafCallback: any = null;
|
|
489
|
-
const originalRaf = globalThis.requestAnimationFrame;
|
|
490
|
-
globalThis.requestAnimationFrame = ((cb: () => void) => {
|
|
491
|
-
rafCallback = cb;
|
|
492
|
-
return 1;
|
|
493
|
-
}) as any;
|
|
494
|
-
|
|
495
|
-
try {
|
|
496
|
-
const origin = createNode("origin", 200, 200);
|
|
497
|
-
const siblingAbove = createNode("above", 200, 100);
|
|
498
|
-
const siblingBelow = createNode("below", 200, 300);
|
|
499
|
-
|
|
500
|
-
const graph = createCanvasGraph({
|
|
501
|
-
initialNodes: [origin, siblingAbove, siblingBelow],
|
|
502
|
-
});
|
|
503
|
-
const explosion = createCanvasExplosion(graph);
|
|
504
|
-
|
|
505
|
-
const child = createNode("c1", 500, 200);
|
|
506
|
-
|
|
507
|
-
const forwardTrajectory = createFanOutTrajectory({
|
|
508
|
-
startOpacity: 0,
|
|
509
|
-
endOpacity: 1,
|
|
510
|
-
curvature: 0,
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
const reverseTrajectory = createFanOutTrajectory({
|
|
514
|
-
startOpacity: 1,
|
|
515
|
-
endOpacity: 0,
|
|
516
|
-
curvature: 0,
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
explosion.explodeNode("origin", {
|
|
520
|
-
childNodes: [child],
|
|
521
|
-
duration: 200,
|
|
522
|
-
easing: (t) => t,
|
|
523
|
-
trajectory: forwardTrajectory,
|
|
524
|
-
connectParentToChildren: "all",
|
|
525
|
-
directionalOptions: { direction: "vertical", gap: 20 },
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
// Frame 0: child node and edge start with opacity 0
|
|
529
|
-
let graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
530
|
-
let graphEdge = graph.edges.find(
|
|
531
|
-
(e) => e.source === "origin" && e.target === "c1",
|
|
532
|
-
)!;
|
|
533
|
-
let graphAbove = graph.nodes.find((n) => n.id === "above")!;
|
|
534
|
-
let graphBelow = graph.nodes.find((n) => n.id === "below")!;
|
|
535
|
-
|
|
536
|
-
expect(graphChild.style).toContain("opacity: 0");
|
|
537
|
-
expect(graphEdge.style).toContain("opacity: 0");
|
|
538
|
-
// Displaced siblings should never be faded
|
|
539
|
-
expect(graphAbove.style || "").not.toContain("opacity: 0");
|
|
540
|
-
expect(graphBelow.style || "").not.toContain("opacity: 0");
|
|
541
|
-
|
|
542
|
-
// Advance 100ms (50% progress)
|
|
543
|
-
currentTime = 1100;
|
|
544
|
-
if (rafCallback) {
|
|
545
|
-
const nextCb = rafCallback;
|
|
546
|
-
rafCallback = null;
|
|
547
|
-
nextCb();
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
551
|
-
graphEdge = graph.edges.find(
|
|
552
|
-
(e) => e.source === "origin" && e.target === "c1",
|
|
553
|
-
)!;
|
|
554
|
-
graphAbove = graph.nodes.find((n) => n.id === "above")!;
|
|
555
|
-
graphBelow = graph.nodes.find((n) => n.id === "below")!;
|
|
556
|
-
|
|
557
|
-
expect(graphChild.position.x).toBeCloseTo(335, 0); // quadratic bezier curve at t=0.5
|
|
558
|
-
expect(graphChild.style).toContain("opacity: 0.5");
|
|
559
|
-
expect(graphEdge.style).toContain("opacity: 0.5");
|
|
560
|
-
// Displaced siblings move vertically without horizontal change and without opacity modification
|
|
561
|
-
expect(graphAbove.position.x).toBe(200);
|
|
562
|
-
expect(graphBelow.position.x).toBe(200);
|
|
563
|
-
expect(graphAbove.style || "").not.toContain("opacity: 0.5");
|
|
564
|
-
|
|
565
|
-
// Advance 100ms (100% progress)
|
|
566
|
-
currentTime = 1200;
|
|
567
|
-
if (rafCallback) {
|
|
568
|
-
const nextCb = rafCallback;
|
|
569
|
-
rafCallback = null;
|
|
570
|
-
nextCb();
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
574
|
-
graphEdge = graph.edges.find(
|
|
575
|
-
(e) => e.source === "origin" && e.target === "c1",
|
|
576
|
-
)!;
|
|
577
|
-
graphAbove = graph.nodes.find((n) => n.id === "above")!;
|
|
578
|
-
graphBelow = graph.nodes.find((n) => n.id === "below")!;
|
|
579
|
-
|
|
580
|
-
expect(graphChild.position.x).toBe(500);
|
|
581
|
-
expect(graphChild.style || "").not.toContain("opacity: 0");
|
|
582
|
-
expect((graphChild.data as any).opacity).toBe(1);
|
|
583
|
-
expect(graphAbove.position.y).toBeLessThan(100);
|
|
584
|
-
expect(graphBelow.position.y).toBeGreaterThan(300);
|
|
585
|
-
|
|
586
|
-
// Now collapse with reverse trajectory
|
|
587
|
-
explosion.collapseNode("origin", {
|
|
588
|
-
duration: 200,
|
|
589
|
-
easing: (t) => t,
|
|
590
|
-
trajectory: reverseTrajectory,
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
// Advance 100ms into collapse (50%)
|
|
594
|
-
currentTime = 1300;
|
|
595
|
-
if (rafCallback) {
|
|
596
|
-
const nextCb = rafCallback;
|
|
597
|
-
rafCallback = null;
|
|
598
|
-
nextCb();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
graphChild = graph.nodes.find((n) => n.id === "c1")!;
|
|
602
|
-
graphEdge = graph.edges.find(
|
|
603
|
-
(e) => e.source === "origin" && e.target === "c1",
|
|
604
|
-
)!;
|
|
605
|
-
expect(graphChild.position.x).toBeCloseTo(365, 0); // quadratic bezier curve at t=0.5 on collapse
|
|
606
|
-
expect(graphChild.style).toContain("opacity: 0.5");
|
|
607
|
-
expect(graphEdge.style).toContain("opacity: 0.5");
|
|
608
|
-
|
|
609
|
-
// Advance 100ms (collapse completion)
|
|
610
|
-
currentTime = 1400;
|
|
611
|
-
if (rafCallback) {
|
|
612
|
-
const nextCb = rafCallback;
|
|
613
|
-
rafCallback = null;
|
|
614
|
-
nextCb();
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
expect(graph.nodes.some((n) => n.id === "c1")).toBe(false);
|
|
618
|
-
expect(
|
|
619
|
-
graph.edges.some((e) => e.source === "origin" && e.target === "c1"),
|
|
620
|
-
).toBe(false);
|
|
621
|
-
expect(explosion.isExploded("origin")).toBe(false);
|
|
622
|
-
|
|
623
|
-
graphAbove = graph.nodes.find((n) => n.id === "above")!;
|
|
624
|
-
graphBelow = graph.nodes.find((n) => n.id === "below")!;
|
|
625
|
-
expect(graphAbove.position.y).toBe(100);
|
|
626
|
-
expect(graphBelow.position.y).toBe(300);
|
|
627
|
-
expect(graphAbove.position.x).toBe(200);
|
|
628
|
-
expect(graphBelow.position.x).toBe(200);
|
|
629
|
-
} finally {
|
|
630
|
-
Date.now = originalDateNow;
|
|
631
|
-
globalThis.requestAnimationFrame = originalRaf;
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
});
|