@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.
Files changed (149) hide show
  1. package/dist/BubbleMenu.svelte.d.ts +14 -0
  2. package/dist/BubbleMenu.svelte.d.ts.map +1 -0
  3. package/{src → dist}/EditableTextNodeEditor.svelte +1 -1
  4. package/dist/EditableTextNodeEditor.svelte.d.ts +26 -0
  5. package/dist/EditableTextNodeEditor.svelte.d.ts.map +1 -0
  6. package/dist/TemplateRenderer.svelte.d.ts +14 -0
  7. package/dist/TemplateRenderer.svelte.d.ts.map +1 -0
  8. package/dist/canvas/AgentPresenceLayer.svelte.d.ts +9 -0
  9. package/dist/canvas/AgentPresenceLayer.svelte.d.ts.map +1 -0
  10. package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts +9 -0
  11. package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts.map +1 -0
  12. package/dist/canvas/createCanvasClipboard.svelte.d.ts +32 -0
  13. package/dist/canvas/createCanvasClipboard.svelte.d.ts.map +1 -0
  14. package/dist/canvas/createCanvasClipboard.svelte.js +171 -0
  15. package/dist/canvas/createCanvasDisplacement.d.ts +32 -0
  16. package/dist/canvas/createCanvasDisplacement.d.ts.map +1 -0
  17. package/dist/canvas/createCanvasDisplacement.js +170 -0
  18. package/dist/canvas/createCanvasDocking.svelte.d.ts +56 -0
  19. package/dist/canvas/createCanvasDocking.svelte.d.ts.map +1 -0
  20. package/dist/canvas/createCanvasDocking.svelte.js +227 -0
  21. package/dist/canvas/createCanvasExplosion.svelte.d.ts +31 -0
  22. package/dist/canvas/createCanvasExplosion.svelte.d.ts.map +1 -0
  23. package/dist/canvas/createCanvasExplosion.svelte.js +346 -0
  24. package/dist/canvas/createCanvasGraph.svelte.d.ts +62 -0
  25. package/dist/canvas/createCanvasGraph.svelte.d.ts.map +1 -0
  26. package/dist/canvas/createCanvasGraph.svelte.js +223 -0
  27. package/dist/canvas/createCanvasInteractions.svelte.d.ts +28 -0
  28. package/dist/canvas/createCanvasInteractions.svelte.d.ts.map +1 -0
  29. package/dist/canvas/createCanvasInteractions.svelte.js +95 -0
  30. package/dist/canvas/createCanvasLayout.d.ts +22 -0
  31. package/dist/canvas/createCanvasLayout.d.ts.map +1 -0
  32. package/dist/canvas/createCanvasLayout.js +76 -0
  33. package/dist/canvas/createCanvasMultiDrag.svelte.d.ts +61 -0
  34. package/dist/canvas/createCanvasMultiDrag.svelte.d.ts.map +1 -0
  35. package/dist/canvas/createCanvasMultiDrag.svelte.js +105 -0
  36. package/dist/canvas/createCanvasNodeSync.svelte.d.ts +58 -0
  37. package/dist/canvas/createCanvasNodeSync.svelte.d.ts.map +1 -0
  38. package/dist/canvas/createCanvasNodeSync.svelte.js +82 -0
  39. package/dist/canvas/createCanvasSelection.svelte.d.ts +26 -0
  40. package/dist/canvas/createCanvasSelection.svelte.d.ts.map +1 -0
  41. package/dist/canvas/createCanvasSelection.svelte.js +26 -0
  42. package/dist/canvas/createCanvasShortcuts.d.ts +9 -0
  43. package/dist/canvas/createCanvasShortcuts.d.ts.map +1 -0
  44. package/dist/canvas/createCanvasShortcuts.js +97 -0
  45. package/dist/canvas/createCanvasTrajectory.d.ts +31 -0
  46. package/dist/canvas/createCanvasTrajectory.d.ts.map +1 -0
  47. package/dist/canvas/createCanvasTrajectory.js +141 -0
  48. package/dist/canvas/createStreamingCanvas.svelte.d.ts +19 -0
  49. package/dist/canvas/createStreamingCanvas.svelte.d.ts.map +1 -0
  50. package/dist/canvas/createStreamingCanvas.svelte.js +32 -0
  51. package/dist/canvas/index.d.ts +18 -688
  52. package/dist/canvas/index.d.ts.map +1 -0
  53. package/dist/canvas/index.js +34 -1839
  54. package/dist/canvas/types.d.ts +183 -0
  55. package/dist/canvas/types.d.ts.map +1 -0
  56. package/dist/canvas/types.js +1 -0
  57. package/dist/createNavigationGuard.svelte.d.ts +24 -0
  58. package/dist/createNavigationGuard.svelte.d.ts.map +1 -0
  59. package/dist/createNavigationGuard.svelte.js +65 -0
  60. package/dist/editor.svelte.d.ts +23 -0
  61. package/dist/editor.svelte.d.ts.map +1 -0
  62. package/dist/editor.svelte.js +41 -0
  63. package/dist/history/createHistoryShortcuts.d.ts +24 -0
  64. package/dist/history/createHistoryShortcuts.d.ts.map +1 -0
  65. package/dist/history/createHistoryShortcuts.js +57 -0
  66. package/dist/history/history.svelte.d.ts +18 -0
  67. package/dist/history/history.svelte.d.ts.map +1 -0
  68. package/dist/history/history.svelte.js +43 -0
  69. package/dist/history/index.d.ts +3 -0
  70. package/dist/history/index.d.ts.map +1 -0
  71. package/dist/history/index.js +2 -0
  72. package/{src → dist}/image-editor/ImageEditor.svelte +1 -1
  73. package/dist/image-editor/ImageEditor.svelte.d.ts +17 -0
  74. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -0
  75. package/dist/image-editor/imageEditorState.svelte.d.ts +50 -0
  76. package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -0
  77. package/dist/image-editor/imageEditorState.svelte.js +132 -0
  78. package/dist/image-editor/index.d.ts +6 -16
  79. package/dist/image-editor/index.d.ts.map +1 -0
  80. package/dist/image-editor/index.js +2 -9
  81. package/dist/index.d.ts +20 -249
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +10 -1119
  84. package/dist/navigationGuard.d.ts +21 -0
  85. package/dist/navigationGuard.d.ts.map +1 -0
  86. package/dist/navigationGuard.js +59 -0
  87. package/{src → dist}/spreadsheet/FormulaBar.svelte +1 -1
  88. package/dist/spreadsheet/FormulaBar.svelte.d.ts +17 -0
  89. package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -0
  90. package/{src → dist}/spreadsheet/SpreadsheetEditor.svelte +1 -1
  91. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +22 -0
  92. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -0
  93. package/dist/spreadsheet/index.d.ts +9 -0
  94. package/dist/spreadsheet/index.d.ts.map +1 -0
  95. package/{src/spreadsheet/index.ts → dist/spreadsheet/index.js} +1 -1
  96. package/dist/spreadsheet/spreadsheet.svelte.d.ts +33 -0
  97. package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -0
  98. package/dist/spreadsheet/spreadsheet.svelte.js +160 -0
  99. package/package.json +13 -15
  100. package/dist/canvas/index.js.map +0 -1
  101. package/dist/chunk-HEOI4UNF.js +0 -1191
  102. package/dist/chunk-HEOI4UNF.js.map +0 -1
  103. package/dist/image-editor/index.js.map +0 -1
  104. package/dist/index.css +0 -50
  105. package/dist/index.css.map +0 -1
  106. package/dist/index.js.map +0 -1
  107. package/src/canvas/__tests__/canvas-clipboard.test.ts +0 -154
  108. package/src/canvas/__tests__/canvas-docking.test.ts +0 -288
  109. package/src/canvas/__tests__/canvas-explosion.test.ts +0 -634
  110. package/src/canvas/__tests__/canvas-graph.test.ts +0 -159
  111. package/src/canvas/__tests__/canvas-interactions.test.ts +0 -86
  112. package/src/canvas/__tests__/canvas-layout.test.ts +0 -75
  113. package/src/canvas/__tests__/canvas-multi-drag.test.ts +0 -112
  114. package/src/canvas/__tests__/canvas-node-sync.svelte.test.ts +0 -104
  115. package/src/canvas/__tests__/canvas-shortcuts.test.ts +0 -59
  116. package/src/canvas/createCanvasClipboard.svelte.ts +0 -219
  117. package/src/canvas/createCanvasDisplacement.ts +0 -253
  118. package/src/canvas/createCanvasDocking.svelte.ts +0 -355
  119. package/src/canvas/createCanvasExplosion.svelte.ts +0 -457
  120. package/src/canvas/createCanvasGraph.svelte.ts +0 -330
  121. package/src/canvas/createCanvasInteractions.svelte.ts +0 -112
  122. package/src/canvas/createCanvasLayout.ts +0 -129
  123. package/src/canvas/createCanvasMultiDrag.svelte.ts +0 -175
  124. package/src/canvas/createCanvasNodeSync.svelte.ts +0 -150
  125. package/src/canvas/createCanvasSelection.svelte.ts +0 -51
  126. package/src/canvas/createCanvasShortcuts.ts +0 -99
  127. package/src/canvas/createCanvasTrajectory.ts +0 -207
  128. package/src/canvas/createStreamingCanvas.svelte.ts +0 -56
  129. package/src/canvas/index.d.ts +0 -688
  130. package/src/canvas/index.ts +0 -126
  131. package/src/canvas/types.ts +0 -223
  132. package/src/createNavigationGuard.svelte.ts +0 -97
  133. package/src/editor.svelte.ts +0 -65
  134. package/src/history/createHistoryShortcuts.ts +0 -95
  135. package/src/history/history.svelte.ts +0 -68
  136. package/src/history/index.ts +0 -2
  137. package/src/image-editor/imageEditorState.svelte.ts +0 -151
  138. package/src/image-editor/index.d.ts +0 -16
  139. package/src/image-editor/index.ts +0 -2
  140. package/src/index.d.ts +0 -249
  141. package/src/index.ts +0 -89
  142. package/src/navigationGuard.ts +0 -82
  143. package/src/spreadsheet/spreadsheet.svelte.ts +0 -176
  144. package/src/svelte.d.ts +0 -5
  145. /package/{src → dist}/BubbleMenu.svelte +0 -0
  146. /package/{src → dist}/TemplateRenderer.svelte +0 -0
  147. /package/{src → dist}/canvas/AgentPresenceLayer.svelte +0 -0
  148. /package/{src → dist}/canvas/FloatingHorizontalScrollbar.svelte +0 -0
  149. /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
- });