@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.
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 +14 -16
  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,219 +0,0 @@
1
- import { tick } from 'svelte';
2
- import type { XYPosition } from '@xyflow/svelte';
3
- import type { CanvasNode, CanvasEdge, CanvasClipboardPayload } from './types.js';
4
-
5
- /**
6
- * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.
7
- */
8
- export function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {
9
- const target = event.target as HTMLElement | null;
10
- if (!target) return false;
11
- if (
12
- target.tagName === 'INPUT' ||
13
- target.tagName === 'TEXTAREA' ||
14
- target.tagName === 'SELECT' ||
15
- target.isContentEditable ||
16
- target.closest?.('[contenteditable="true"]') ||
17
- target.closest?.('.ProseMirror')
18
- ) {
19
- return true;
20
- }
21
- return false;
22
- }
23
-
24
- /**
25
- * Validates that an object conforms to minimal CanvasNode shape.
26
- */
27
- export function isValidCanvasNode(obj: unknown): obj is CanvasNode {
28
- return (
29
- typeof obj === 'object' &&
30
- obj !== null &&
31
- 'id' in obj &&
32
- typeof (obj as any).id === 'string' &&
33
- 'position' in obj &&
34
- typeof (obj as any).position === 'object' &&
35
- (obj as any).position !== null &&
36
- 'x' in (obj as any).position &&
37
- 'y' in (obj as any).position
38
- );
39
- }
40
-
41
- /**
42
- * Checks if a string is a valid HTTP/HTTPS URL.
43
- */
44
- export function isValidUrl(url: string): boolean {
45
- try {
46
- const parsed = new URL(url);
47
- return parsed.protocol === 'http:' || parsed.protocol === 'https:';
48
- } catch {
49
- return false;
50
- }
51
- }
52
-
53
- export interface CreateCanvasClipboardOptions<
54
- TNode extends CanvasNode = CanvasNode,
55
- TEdge extends CanvasEdge = CanvasEdge
56
- > {
57
- onPasteNodes?: (nodes: TNode[], edges?: TEdge[]) => void | Promise<void>;
58
- onPasteUrl?: (url: string, position?: XYPosition) => void;
59
- onPasteText?: (text: string, position?: XYPosition) => void;
60
- onPasteImage?: (blob: File) => void | Promise<void>;
61
- screenToFlowPosition?: (position: XYPosition) => XYPosition;
62
- }
63
-
64
- /**
65
- * Headless Svelte 5 rune managing canvas copy, cut, and paste interactions.
66
- */
67
- export function createCanvasClipboard<
68
- TNode extends CanvasNode = CanvasNode,
69
- TEdge extends CanvasEdge = CanvasEdge
70
- >(options: CreateCanvasClipboardOptions<TNode, TEdge> = {}) {
71
- function copySelectedNodes(
72
- selectedNodes: TNode[],
73
- connectedEdges?: TEdge[],
74
- event?: ClipboardEvent
75
- ): boolean {
76
- if (event && isEventFromTextInput(event)) {
77
- return false;
78
- }
79
-
80
- if (selectedNodes.length === 0) return false;
81
-
82
- const payload: CanvasClipboardPayload<TNode, TEdge> = {
83
- version: 1,
84
- nodes: selectedNodes,
85
- edges: connectedEdges
86
- };
87
-
88
- const serialized = JSON.stringify(payload);
89
-
90
- if (event?.clipboardData) {
91
- event.clipboardData.setData('application/json', serialized);
92
- event.clipboardData.setData('text/plain', serialized);
93
- event.preventDefault();
94
- } else if (typeof navigator !== 'undefined' && navigator.clipboard) {
95
- navigator.clipboard.writeText(serialized).catch(() => {});
96
- }
97
-
98
- return true;
99
- }
100
-
101
- function cutSelectedNodes(
102
- selectedNodes: TNode[],
103
- connectedEdges?: TEdge[],
104
- onRemove?: (nodes: TNode[]) => void,
105
- event?: ClipboardEvent
106
- ): boolean {
107
- if (event && isEventFromTextInput(event)) {
108
- return false;
109
- }
110
-
111
- const copied = copySelectedNodes(selectedNodes, connectedEdges, event);
112
- if (copied && onRemove) {
113
- onRemove(selectedNodes);
114
- }
115
- return copied;
116
- }
117
-
118
- async function handlePasteEvent(event: ClipboardEvent): Promise<boolean> {
119
- if (isEventFromTextInput(event)) {
120
- return false;
121
- }
122
-
123
- const clipboardData = event.clipboardData;
124
- if (!clipboardData) return false;
125
-
126
- // 1. Check for files (images)
127
- const files = clipboardData.files;
128
- if (files && files.length > 0) {
129
- const imageFile = Array.from(files).find((f) => f.type.startsWith('image/'));
130
- if (imageFile && options.onPasteImage) {
131
- event.preventDefault();
132
- await options.onPasteImage(imageFile);
133
- return true;
134
- }
135
- }
136
-
137
- // 2. Check for serialized nodes (application/json or json in text/plain)
138
- const rawJson =
139
- clipboardData.getData('application/json') || clipboardData.getData('text/plain');
140
-
141
- if (rawJson) {
142
- try {
143
- const parsed = JSON.parse(rawJson);
144
- let rawNodes: TNode[] | undefined;
145
- let rawEdges: TEdge[] | undefined;
146
-
147
- if (Array.isArray(parsed) && parsed.every(isValidCanvasNode)) {
148
- rawNodes = parsed as TNode[];
149
- } else if (
150
- typeof parsed === 'object' &&
151
- parsed !== null &&
152
- Array.isArray((parsed as any).nodes) &&
153
- (parsed as any).nodes.every(isValidCanvasNode)
154
- ) {
155
- rawNodes = (parsed as any).nodes;
156
- rawEdges = (parsed as any).edges;
157
- }
158
-
159
- if (rawNodes && rawNodes.length > 0) {
160
- event.preventDefault();
161
-
162
- // Create ID mapping for cloned nodes
163
- const idMap = new Map<string, string>();
164
- const newNodes: TNode[] = rawNodes.map((node) => {
165
- const newId = crypto.randomUUID();
166
- idMap.set(node.id, newId);
167
- return {
168
- ...node,
169
- id: newId,
170
- selected: true,
171
- position: {
172
- x: node.position.x + 40,
173
- y: node.position.y + 40
174
- }
175
- };
176
- });
177
-
178
- // Clone edges mapping them to the newly generated node IDs
179
- const newEdges: TEdge[] | undefined = rawEdges?.map((edge) => ({
180
- ...edge,
181
- id: crypto.randomUUID(),
182
- source: idMap.get(edge.source) ?? edge.source,
183
- target: idMap.get(edge.target) ?? edge.target
184
- }));
185
-
186
- if (options.onPasteNodes) {
187
- await options.onPasteNodes(newNodes, newEdges);
188
- await tick();
189
- }
190
- return true;
191
- }
192
- } catch {
193
- // Not JSON, fall through to text/URL processing
194
- }
195
- }
196
-
197
- // 3. Check for URLs or plain text
198
- const text = clipboardData.getData('text/plain')?.trim();
199
- if (text) {
200
- if (isValidUrl(text) && options.onPasteUrl) {
201
- event.preventDefault();
202
- options.onPasteUrl(text);
203
- return true;
204
- } else if (options.onPasteText) {
205
- event.preventDefault();
206
- options.onPasteText(text);
207
- return true;
208
- }
209
- }
210
-
211
- return false;
212
- }
213
-
214
- return {
215
- copySelectedNodes,
216
- cutSelectedNodes,
217
- handlePasteEvent
218
- };
219
- }
@@ -1,253 +0,0 @@
1
- import dagre from '@dagrejs/dagre';
2
- import type { Rect, XYPosition } from '@xyflow/svelte';
3
- import type {
4
- CanvasEdge,
5
- CanvasLayoutOptions,
6
- CanvasNode,
7
- DirectionalDisplacementOptions
8
- } from './types.js';
9
-
10
- /**
11
- * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.
12
- */
13
- export function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(
14
- nodes: TNode[],
15
- defaultWidth = 200,
16
- defaultHeight = 60
17
- ): Rect {
18
- if (nodes.length === 0) {
19
- return { x: 0, y: 0, width: 0, height: 0 };
20
- }
21
-
22
- let minX = Infinity;
23
- let minY = Infinity;
24
- let maxX = -Infinity;
25
- let maxY = -Infinity;
26
-
27
- for (const node of nodes) {
28
- const width = node.measured?.width ?? (node as any).width ?? defaultWidth;
29
- const height = node.measured?.height ?? (node as any).height ?? defaultHeight;
30
-
31
- minX = Math.min(minX, node.position.x);
32
- minY = Math.min(minY, node.position.y);
33
- maxX = Math.max(maxX, node.position.x + width);
34
- maxY = Math.max(maxY, node.position.y + height);
35
- }
36
-
37
- return {
38
- x: minX,
39
- y: minY,
40
- width: maxX - minX,
41
- height: maxY - minY
42
- };
43
- }
44
-
45
- /**
46
- * Pure directional displacement calculation.
47
- * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.
48
- */
49
- export function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {
50
- existingNodes: TNode[];
51
- originNode: TNode;
52
- childNodes: TNode[];
53
- getNodesBounds?: (nodes: TNode[]) => Rect;
54
- options?: DirectionalDisplacementOptions;
55
- }): Map<string, XYPosition> {
56
- const {
57
- existingNodes,
58
- originNode,
59
- childNodes,
60
- getNodesBounds,
61
- options = {}
62
- } = params;
63
-
64
- const {
65
- direction = 'right',
66
- gap = 40,
67
- spreadSiblingLanes = false,
68
- siblingTolerance = 50,
69
- minY
70
- } = options;
71
-
72
- const displacedMap = new Map<string, XYPosition>();
73
- if (childNodes.length === 0 || existingNodes.length === 0) {
74
- return displacedMap;
75
- }
76
-
77
- const childBounds = getNodesBounds
78
- ? getNodesBounds(childNodes)
79
- : computeNodesBoundingBox(childNodes);
80
-
81
- const originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;
82
- const originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;
83
-
84
- // The delta space required along primary axis
85
- const deltaPrimary = (direction === 'right' || direction === 'left')
86
- ? childBounds.width + gap
87
- : childBounds.height + gap;
88
-
89
- // Vertical displacement parameters: upward and downward row clearance
90
- const deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);
91
- const deltaDown = Math.max(
92
- 0,
93
- childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap
94
- );
95
-
96
- // Both above and below displace equally so the row expands symmetrically
97
- const equalDelta = Math.max(deltaUp, deltaDown);
98
-
99
- for (const node of existingNodes) {
100
- if (node.id === originNode.id) continue;
101
-
102
- let newX = node.position.x;
103
- let newY = node.position.y;
104
- let moved = false;
105
-
106
- if (direction === 'right') {
107
- // Downstream nodes strictly to the right of the origin
108
- if (node.position.x >= originNode.position.x + originWidth * 0.5) {
109
- newX += deltaPrimary;
110
- moved = true;
111
- } else if (spreadSiblingLanes) {
112
- // Sibling nodes occupying the same vertical space as the exploded cluster
113
- const isVerticallyOverlapping =
114
- node.position.y + ((node as any).height ?? 60) >= childBounds.y - gap &&
115
- node.position.y <= childBounds.y + childBounds.height + gap;
116
-
117
- if (isVerticallyOverlapping && Math.abs(node.position.x - originNode.position.x) <= siblingTolerance) {
118
- const pushDirection = node.position.y >= originNode.position.y ? 1 : -1;
119
- newY += pushDirection * (childBounds.height * 0.5 + gap);
120
- moved = true;
121
- }
122
- }
123
- } else if (direction === 'left') {
124
- if (node.position.x <= originNode.position.x) {
125
- newX -= deltaPrimary;
126
- moved = true;
127
- }
128
- } else if (direction === 'down') {
129
- if (node.position.y >= originNode.position.y + originHeight * 0.5) {
130
- newY += deltaPrimary;
131
- moved = true;
132
- }
133
- } else if (direction === 'up') {
134
- if (node.position.y <= originNode.position.y) {
135
- newY -= deltaPrimary;
136
- moved = true;
137
- }
138
- } else if (direction === 'vertical') {
139
- // Row grows vertically:
140
- // Nodes strictly above the origin node move up by equalDelta
141
- // Nodes strictly below the origin node move down by equalDelta
142
- // Both above and below displace equally; horizontal position (x) is strictly preserved
143
- if (node.position.y < originNode.position.y) {
144
- if (equalDelta > 0) {
145
- newY -= equalDelta;
146
- moved = true;
147
- }
148
- } else if (node.position.y >= originNode.position.y + originHeight * 0.5) {
149
- if (equalDelta > 0) {
150
- newY += equalDelta;
151
- moved = true;
152
- }
153
- }
154
- }
155
-
156
- if (moved) {
157
- displacedMap.set(node.id, { x: newX, y: newY });
158
- }
159
- }
160
-
161
- return displacedMap;
162
- }
163
-
164
- /**
165
- * Pure hierarchical Dagre reflow displacement calculation.
166
- * Calculates new coordinates for surrounding nodes using Dagre hierarchical layout,
167
- * anchoring the origin node at its current position to eliminate layout jumping.
168
- */
169
- export function calculateReflowDisplacement<
170
- TNode extends CanvasNode = CanvasNode,
171
- TEdge extends CanvasEdge = CanvasEdge
172
- >(params: {
173
- existingNodes: TNode[];
174
- existingEdges: TEdge[];
175
- originNode: TNode;
176
- childNodes: TNode[];
177
- childEdges?: TEdge[];
178
- getNodesBounds?: (nodes: TNode[]) => Rect;
179
- options?: CanvasLayoutOptions;
180
- }): Map<string, XYPosition> {
181
- const {
182
- existingNodes,
183
- existingEdges,
184
- originNode,
185
- childNodes,
186
- childEdges = [],
187
- options = {}
188
- } = params;
189
-
190
- const {
191
- direction = 'LR',
192
- ranksep = 80,
193
- nodesep = 30,
194
- defaultNodeWidth = 200,
195
- defaultNodeHeight = 60
196
- } = options;
197
-
198
- const displacedMap = new Map<string, XYPosition>();
199
- if (existingNodes.length === 0) return displacedMap;
200
-
201
- const allNodes = [...existingNodes, ...childNodes];
202
- const allEdges = [...existingEdges, ...childEdges];
203
-
204
- const dagreGraph = new dagre.graphlib.Graph();
205
- dagreGraph.setDefaultEdgeLabel(() => ({}));
206
- dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
207
-
208
- const allNodeIds = new Set(allNodes.map((n) => n.id));
209
-
210
- allNodes.forEach((node) => {
211
- const nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;
212
- const nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;
213
- dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
214
- });
215
-
216
- allEdges.forEach((edge) => {
217
- if (allNodeIds.has(edge.source) && allNodeIds.has(edge.target)) {
218
- dagreGraph.setEdge(edge.source, edge.target);
219
- }
220
- });
221
-
222
- dagre.layout(dagreGraph);
223
-
224
- const dagreOrigin = dagreGraph.node(originNode.id);
225
- if (!dagreOrigin) return displacedMap;
226
-
227
- const originWidth = originNode.measured?.width ?? (originNode as any).width ?? defaultNodeWidth;
228
- const originHeight = originNode.measured?.height ?? (originNode as any).height ?? defaultNodeHeight;
229
-
230
- // Calculate offset so origin node remains anchored at its exact initial coordinates
231
- const anchorOffsetX = originNode.position.x - (dagreOrigin.x - originWidth / 2);
232
- const anchorOffsetY = originNode.position.y - (dagreOrigin.y - originHeight / 2);
233
-
234
- existingNodes.forEach((node) => {
235
- if (node.id === originNode.id) return;
236
-
237
- const dagreNode = dagreGraph.node(node.id);
238
- if (!dagreNode) return;
239
-
240
- const nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;
241
- const nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;
242
-
243
- const targetX = dagreNode.x - nodeWidth / 2 + anchorOffsetX;
244
- const targetY = dagreNode.y - nodeHeight / 2 + anchorOffsetY;
245
-
246
- // Only include if position actually shifted
247
- if (Math.abs(targetX - node.position.x) > 0.5 || Math.abs(targetY - node.position.y) > 0.5) {
248
- displacedMap.set(node.id, { x: targetX, y: targetY });
249
- }
250
- });
251
-
252
- return displacedMap;
253
- }