@coral-viz/viz 0.2.3

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 (191) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +11 -0
  3. package/dist/compatibility/CompatibilityEdge.d.ts +106 -0
  4. package/dist/compatibility/CompatibilityEdge.d.ts.map +1 -0
  5. package/dist/compatibility/CompatibilityEdge.js +233 -0
  6. package/dist/compatibility/CompatibilityEdge.js.map +1 -0
  7. package/dist/compatibility/IncompatibilityTooltip.d.ts +44 -0
  8. package/dist/compatibility/IncompatibilityTooltip.d.ts.map +1 -0
  9. package/dist/compatibility/IncompatibilityTooltip.js +145 -0
  10. package/dist/compatibility/IncompatibilityTooltip.js.map +1 -0
  11. package/dist/compatibility/index.d.ts +10 -0
  12. package/dist/compatibility/index.d.ts.map +1 -0
  13. package/dist/compatibility/index.js +10 -0
  14. package/dist/compatibility/index.js.map +1 -0
  15. package/dist/compatibility/useEdgeCompatibility.d.ts +82 -0
  16. package/dist/compatibility/useEdgeCompatibility.d.ts.map +1 -0
  17. package/dist/compatibility/useEdgeCompatibility.js +170 -0
  18. package/dist/compatibility/useEdgeCompatibility.js.map +1 -0
  19. package/dist/compatibility/validateConnection.d.ts +56 -0
  20. package/dist/compatibility/validateConnection.d.ts.map +1 -0
  21. package/dist/compatibility/validateConnection.js +382 -0
  22. package/dist/compatibility/validateConnection.js.map +1 -0
  23. package/dist/components/EditContext.d.ts +45 -0
  24. package/dist/components/EditContext.d.ts.map +1 -0
  25. package/dist/components/EditContext.js +41 -0
  26. package/dist/components/EditContext.js.map +1 -0
  27. package/dist/components/ShapeRenderer.d.ts +35 -0
  28. package/dist/components/ShapeRenderer.d.ts.map +1 -0
  29. package/dist/components/ShapeRenderer.js +77 -0
  30. package/dist/components/ShapeRenderer.js.map +1 -0
  31. package/dist/components/SymbolNode.d.ts +53 -0
  32. package/dist/components/SymbolNode.d.ts.map +1 -0
  33. package/dist/components/SymbolNode.js +333 -0
  34. package/dist/components/SymbolNode.js.map +1 -0
  35. package/dist/components/index.d.ts +8 -0
  36. package/dist/components/index.d.ts.map +1 -0
  37. package/dist/components/index.js +10 -0
  38. package/dist/components/index.js.map +1 -0
  39. package/dist/components/nodes.d.ts +43 -0
  40. package/dist/components/nodes.d.ts.map +1 -0
  41. package/dist/components/nodes.js +111 -0
  42. package/dist/components/nodes.js.map +1 -0
  43. package/dist/editor/CoralEditor.d.ts +29 -0
  44. package/dist/editor/CoralEditor.d.ts.map +1 -0
  45. package/dist/editor/CoralEditor.js +112 -0
  46. package/dist/editor/CoralEditor.js.map +1 -0
  47. package/dist/editor/FileControls.d.ts +34 -0
  48. package/dist/editor/FileControls.d.ts.map +1 -0
  49. package/dist/editor/FileControls.js +139 -0
  50. package/dist/editor/FileControls.js.map +1 -0
  51. package/dist/editor/LayoutControls.d.ts +41 -0
  52. package/dist/editor/LayoutControls.d.ts.map +1 -0
  53. package/dist/editor/LayoutControls.js +124 -0
  54. package/dist/editor/LayoutControls.js.map +1 -0
  55. package/dist/editor/converter.d.ts +37 -0
  56. package/dist/editor/converter.d.ts.map +1 -0
  57. package/dist/editor/converter.js +256 -0
  58. package/dist/editor/converter.js.map +1 -0
  59. package/dist/editor/index.d.ts +8 -0
  60. package/dist/editor/index.d.ts.map +1 -0
  61. package/dist/editor/index.js +8 -0
  62. package/dist/editor/index.js.map +1 -0
  63. package/dist/file/deserialize.d.ts +15 -0
  64. package/dist/file/deserialize.d.ts.map +1 -0
  65. package/dist/file/deserialize.js +145 -0
  66. package/dist/file/deserialize.js.map +1 -0
  67. package/dist/file/index.d.ts +15 -0
  68. package/dist/file/index.d.ts.map +1 -0
  69. package/dist/file/index.js +20 -0
  70. package/dist/file/index.js.map +1 -0
  71. package/dist/file/migrate.d.ts +29 -0
  72. package/dist/file/migrate.d.ts.map +1 -0
  73. package/dist/file/migrate.js +103 -0
  74. package/dist/file/migrate.js.map +1 -0
  75. package/dist/file/schema.d.ts +385 -0
  76. package/dist/file/schema.d.ts.map +1 -0
  77. package/dist/file/schema.js +180 -0
  78. package/dist/file/schema.js.map +1 -0
  79. package/dist/file/serialize.d.ts +30 -0
  80. package/dist/file/serialize.d.ts.map +1 -0
  81. package/dist/file/serialize.js +218 -0
  82. package/dist/file/serialize.js.map +1 -0
  83. package/dist/file/validate.d.ts +21 -0
  84. package/dist/file/validate.d.ts.map +1 -0
  85. package/dist/file/validate.js +165 -0
  86. package/dist/file/validate.js.map +1 -0
  87. package/dist/index.d.ts +24 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +42 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/layout/elk.d.ts +77 -0
  92. package/dist/layout/elk.d.ts.map +1 -0
  93. package/dist/layout/elk.js +379 -0
  94. package/dist/layout/elk.js.map +1 -0
  95. package/dist/layout/index.d.ts +12 -0
  96. package/dist/layout/index.d.ts.map +1 -0
  97. package/dist/layout/index.js +12 -0
  98. package/dist/layout/index.js.map +1 -0
  99. package/dist/layout/nodeSizing.d.ts +65 -0
  100. package/dist/layout/nodeSizing.d.ts.map +1 -0
  101. package/dist/layout/nodeSizing.js +319 -0
  102. package/dist/layout/nodeSizing.js.map +1 -0
  103. package/dist/layout/positionStability.d.ts +73 -0
  104. package/dist/layout/positionStability.d.ts.map +1 -0
  105. package/dist/layout/positionStability.js +199 -0
  106. package/dist/layout/positionStability.js.map +1 -0
  107. package/dist/layout/useAxisConstrain.d.ts +20 -0
  108. package/dist/layout/useAxisConstrain.d.ts.map +1 -0
  109. package/dist/layout/useAxisConstrain.js +106 -0
  110. package/dist/layout/useAxisConstrain.js.map +1 -0
  111. package/dist/layout/useDiagramState.d.ts +92 -0
  112. package/dist/layout/useDiagramState.d.ts.map +1 -0
  113. package/dist/layout/useDiagramState.js +256 -0
  114. package/dist/layout/useDiagramState.js.map +1 -0
  115. package/dist/layout/useLayoutHistory.d.ts +53 -0
  116. package/dist/layout/useLayoutHistory.d.ts.map +1 -0
  117. package/dist/layout/useLayoutHistory.js +124 -0
  118. package/dist/layout/useLayoutHistory.js.map +1 -0
  119. package/dist/properties/NodePropertiesPanel.d.ts +47 -0
  120. package/dist/properties/NodePropertiesPanel.d.ts.map +1 -0
  121. package/dist/properties/NodePropertiesPanel.js +199 -0
  122. package/dist/properties/NodePropertiesPanel.js.map +1 -0
  123. package/dist/properties/index.d.ts +10 -0
  124. package/dist/properties/index.d.ts.map +1 -0
  125. package/dist/properties/index.js +10 -0
  126. package/dist/properties/index.js.map +1 -0
  127. package/dist/properties/propertyUtils.d.ts +60 -0
  128. package/dist/properties/propertyUtils.d.ts.map +1 -0
  129. package/dist/properties/propertyUtils.js +89 -0
  130. package/dist/properties/propertyUtils.js.map +1 -0
  131. package/dist/settings/FontSettingsForm.d.ts +28 -0
  132. package/dist/settings/FontSettingsForm.d.ts.map +1 -0
  133. package/dist/settings/FontSettingsForm.js +129 -0
  134. package/dist/settings/FontSettingsForm.js.map +1 -0
  135. package/dist/settings/LayoutSettingsForm.d.ts +37 -0
  136. package/dist/settings/LayoutSettingsForm.d.ts.map +1 -0
  137. package/dist/settings/LayoutSettingsForm.js +165 -0
  138. package/dist/settings/LayoutSettingsForm.js.map +1 -0
  139. package/dist/settings/SettingsPanel.d.ts +55 -0
  140. package/dist/settings/SettingsPanel.d.ts.map +1 -0
  141. package/dist/settings/SettingsPanel.js +84 -0
  142. package/dist/settings/SettingsPanel.js.map +1 -0
  143. package/dist/settings/index.d.ts +13 -0
  144. package/dist/settings/index.d.ts.map +1 -0
  145. package/dist/settings/index.js +13 -0
  146. package/dist/settings/index.js.map +1 -0
  147. package/dist/settings/useSettings.d.ts +51 -0
  148. package/dist/settings/useSettings.d.ts.map +1 -0
  149. package/dist/settings/useSettings.js +154 -0
  150. package/dist/settings/useSettings.js.map +1 -0
  151. package/dist/text-editor/SplitEditor.d.ts +72 -0
  152. package/dist/text-editor/SplitEditor.d.ts.map +1 -0
  153. package/dist/text-editor/SplitEditor.js +172 -0
  154. package/dist/text-editor/SplitEditor.js.map +1 -0
  155. package/dist/text-editor/TextEditor.d.ts +53 -0
  156. package/dist/text-editor/TextEditor.d.ts.map +1 -0
  157. package/dist/text-editor/TextEditor.js +181 -0
  158. package/dist/text-editor/TextEditor.js.map +1 -0
  159. package/dist/text-editor/hooks/useBidirectionalSync.d.ts +106 -0
  160. package/dist/text-editor/hooks/useBidirectionalSync.d.ts.map +1 -0
  161. package/dist/text-editor/hooks/useBidirectionalSync.js +151 -0
  162. package/dist/text-editor/hooks/useBidirectionalSync.js.map +1 -0
  163. package/dist/text-editor/index.d.ts +9 -0
  164. package/dist/text-editor/index.d.ts.map +1 -0
  165. package/dist/text-editor/index.js +11 -0
  166. package/dist/text-editor/index.js.map +1 -0
  167. package/dist/types.d.ts +242 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +5 -0
  170. package/dist/types.js.map +1 -0
  171. package/dist/waypoints/index.d.ts +8 -0
  172. package/dist/waypoints/index.d.ts.map +1 -0
  173. package/dist/waypoints/index.js +7 -0
  174. package/dist/waypoints/index.js.map +1 -0
  175. package/dist/waypoints/operations.d.ts +29 -0
  176. package/dist/waypoints/operations.d.ts.map +1 -0
  177. package/dist/waypoints/operations.js +85 -0
  178. package/dist/waypoints/operations.js.map +1 -0
  179. package/dist/waypoints/router.d.ts +23 -0
  180. package/dist/waypoints/router.d.ts.map +1 -0
  181. package/dist/waypoints/router.js +109 -0
  182. package/dist/waypoints/router.js.map +1 -0
  183. package/dist/waypoints/types.d.ts +15 -0
  184. package/dist/waypoints/types.d.ts.map +1 -0
  185. package/dist/waypoints/types.js +5 -0
  186. package/dist/waypoints/types.js.map +1 -0
  187. package/dist/waypoints/useWaypointManager.d.ts +38 -0
  188. package/dist/waypoints/useWaypointManager.d.ts.map +1 -0
  189. package/dist/waypoints/useWaypointManager.js +106 -0
  190. package/dist/waypoints/useWaypointManager.js.map +1 -0
  191. package/package.json +76 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Node Sizing Utilities (CORAL-REQ-011)
3
+ *
4
+ * Provides text measurement and adaptive node sizing based on:
5
+ * - Text content dimensions
6
+ * - Shape geometry constraints (textBoundsRatio)
7
+ * - Minimum size requirements
8
+ * - Padding specifications
9
+ */
10
+ import { shapeRegistry } from '@coral-viz/render';
11
+ /**
12
+ * Default sizing for shapes without explicit sizing metadata
13
+ */
14
+ const DEFAULT_SIZING = {
15
+ textBoundsRatio: { x: 1.0, y: 1.0 },
16
+ minSize: { width: 60, height: 30 },
17
+ padding: { x: 16, y: 12 },
18
+ };
19
+ /**
20
+ * Default font settings
21
+ */
22
+ const DEFAULT_FONT = {
23
+ fontSize: 12,
24
+ fontFamily: 'system-ui, -apple-system, sans-serif',
25
+ fontWeight: 500,
26
+ lineHeight: 1.4,
27
+ };
28
+ /**
29
+ * Canvas context for text measurement (cached for performance)
30
+ */
31
+ let measureContext = null;
32
+ /**
33
+ * Get or create a canvas context for text measurement
34
+ */
35
+ function getMeasureContext() {
36
+ if (measureContext) {
37
+ return measureContext;
38
+ }
39
+ // Check if we're in a browser environment
40
+ if (typeof document === 'undefined') {
41
+ return null;
42
+ }
43
+ const canvas = document.createElement('canvas');
44
+ measureContext = canvas.getContext('2d');
45
+ return measureContext;
46
+ }
47
+ /**
48
+ * Measure text dimensions using canvas
49
+ *
50
+ * Uses the Canvas API's measureText() for accurate text width measurement,
51
+ * with word wrapping support when maxWidth is specified.
52
+ */
53
+ export function measureText(options) {
54
+ const { text, fontSize = DEFAULT_FONT.fontSize, fontFamily = DEFAULT_FONT.fontFamily, fontWeight = DEFAULT_FONT.fontWeight, maxWidth, lineHeight = DEFAULT_FONT.lineHeight, } = options;
55
+ const ctx = getMeasureContext();
56
+ // Fallback for non-browser environments (SSR, tests)
57
+ if (!ctx) {
58
+ return measureTextFallback(options);
59
+ }
60
+ // Set font for measurement
61
+ ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
62
+ // If no max width, measure as single line
63
+ if (!maxWidth) {
64
+ const metrics = ctx.measureText(text);
65
+ return {
66
+ width: Math.ceil(metrics.width),
67
+ height: Math.ceil(fontSize * lineHeight),
68
+ lines: [text],
69
+ };
70
+ }
71
+ // Word wrap to fit maxWidth
72
+ const lines = wrapText(ctx, text, maxWidth);
73
+ const lineHeightPx = fontSize * lineHeight;
74
+ // Find the widest line
75
+ let maxLineWidth = 0;
76
+ for (const line of lines) {
77
+ const metrics = ctx.measureText(line);
78
+ maxLineWidth = Math.max(maxLineWidth, metrics.width);
79
+ }
80
+ return {
81
+ width: Math.ceil(maxLineWidth),
82
+ height: Math.ceil(lines.length * lineHeightPx),
83
+ lines,
84
+ };
85
+ }
86
+ /**
87
+ * Wrap text to fit within maxWidth
88
+ */
89
+ function wrapText(ctx, text, maxWidth) {
90
+ // Handle empty text
91
+ if (!text || text.trim() === '') {
92
+ return [''];
93
+ }
94
+ // Split by explicit line breaks first
95
+ const paragraphs = text.split('\n');
96
+ const lines = [];
97
+ for (const paragraph of paragraphs) {
98
+ const words = paragraph.split(/\s+/).filter((w) => w.length > 0);
99
+ if (words.length === 0) {
100
+ lines.push('');
101
+ continue;
102
+ }
103
+ let currentLine = words[0];
104
+ for (let i = 1; i < words.length; i++) {
105
+ const word = words[i];
106
+ const testLine = currentLine + ' ' + word;
107
+ const metrics = ctx.measureText(testLine);
108
+ if (metrics.width <= maxWidth) {
109
+ currentLine = testLine;
110
+ }
111
+ else {
112
+ lines.push(currentLine);
113
+ currentLine = word;
114
+ }
115
+ }
116
+ lines.push(currentLine);
117
+ }
118
+ return lines;
119
+ }
120
+ /**
121
+ * Fallback text measurement for non-browser environments
122
+ * Uses character-based approximation
123
+ */
124
+ function measureTextFallback(options) {
125
+ const { text, fontSize = DEFAULT_FONT.fontSize, lineHeight = DEFAULT_FONT.lineHeight, maxWidth, } = options;
126
+ // Approximate character width (varies by font, but 0.6 is reasonable for sans-serif)
127
+ const avgCharWidth = fontSize * 0.6;
128
+ const lineHeightPx = fontSize * lineHeight;
129
+ // Always split by newlines first
130
+ const paragraphs = text.split('\n');
131
+ if (!maxWidth) {
132
+ // No wrapping, just return paragraphs as lines
133
+ const maxLineWidth = Math.max(0, ...paragraphs.map((p) => p.length * avgCharWidth));
134
+ return {
135
+ width: Math.ceil(maxLineWidth),
136
+ height: Math.ceil(paragraphs.length * lineHeightPx),
137
+ lines: paragraphs,
138
+ };
139
+ }
140
+ // Approximate word wrapping
141
+ const charsPerLine = Math.floor(maxWidth / avgCharWidth);
142
+ const lines = [];
143
+ for (const paragraph of paragraphs) {
144
+ if (paragraph.length === 0) {
145
+ lines.push('');
146
+ }
147
+ else if (paragraph.length <= charsPerLine) {
148
+ lines.push(paragraph);
149
+ }
150
+ else {
151
+ // Simple word-aware wrapping
152
+ const words = paragraph.split(/\s+/);
153
+ let currentLine = '';
154
+ for (const word of words) {
155
+ if ((currentLine + ' ' + word).length <= charsPerLine) {
156
+ currentLine = currentLine ? currentLine + ' ' + word : word;
157
+ }
158
+ else {
159
+ if (currentLine)
160
+ lines.push(currentLine);
161
+ currentLine = word;
162
+ }
163
+ }
164
+ if (currentLine)
165
+ lines.push(currentLine);
166
+ }
167
+ }
168
+ const maxLineWidth = Math.max(0, ...lines.map((l) => l.length * avgCharWidth));
169
+ return {
170
+ width: Math.ceil(maxLineWidth),
171
+ height: Math.ceil(lines.length * lineHeightPx),
172
+ lines,
173
+ };
174
+ }
175
+ /**
176
+ * Compute optimal node size based on text content and shape geometry
177
+ *
178
+ * The sizing algorithm:
179
+ * 1. Measure text dimensions
180
+ * 2. Apply shape's textBoundsRatio to account for geometry (e.g., diamond needs more space)
181
+ * 3. Add padding for visual breathing room
182
+ * 4. Enforce minimum size constraints
183
+ * 5. Optionally apply uniform sizing across nodes of the same shape
184
+ */
185
+ export function computeNodeSize(options) {
186
+ const { text, shapeId, sizingMode, fontSize = DEFAULT_FONT.fontSize, fontFamily = DEFAULT_FONT.fontFamily, fontWeight = DEFAULT_FONT.fontWeight, minWidth, minHeight, uniformSizes, } = options;
187
+ // Get shape definition and sizing metadata
188
+ const shape = shapeRegistry.get(shapeId);
189
+ const sizing = shape?.sizing || DEFAULT_SIZING;
190
+ const defaultSize = shape?.defaultSize || { width: 100, height: 60 };
191
+ // For uniform mode, use cached sizes if available
192
+ if (sizingMode === 'uniform' && uniformSizes?.has(shapeId)) {
193
+ const uniformSize = uniformSizes.get(shapeId);
194
+ return {
195
+ width: uniformSize.width,
196
+ height: uniformSize.height,
197
+ textBox: computeTextBox(uniformSize.width, uniformSize.height, sizing),
198
+ };
199
+ }
200
+ // Measure text with initial max width estimate
201
+ const initialMaxWidth = defaultSize.width - sizing.padding.x * 2;
202
+ const textDims = measureText({
203
+ text: text || '',
204
+ fontSize,
205
+ fontFamily,
206
+ fontWeight,
207
+ maxWidth: initialMaxWidth > 0 ? initialMaxWidth : undefined,
208
+ });
209
+ // Apply textBoundsRatio to account for shape geometry
210
+ // For a diamond, text occupies ~70% of the bounding box, so ratio is 1.45
211
+ const contentWidth = textDims.width * sizing.textBoundsRatio.x + sizing.padding.x * 2;
212
+ const contentHeight = textDims.height * sizing.textBoundsRatio.y + sizing.padding.y * 2;
213
+ // Determine final size based on sizing mode
214
+ let width;
215
+ let height;
216
+ switch (sizingMode) {
217
+ case 'adaptive':
218
+ // Size adapts to content
219
+ width = Math.max(contentWidth, sizing.minSize.width, minWidth || 0);
220
+ height = Math.max(contentHeight, sizing.minSize.height, minHeight || 0);
221
+ break;
222
+ case 'uniform':
223
+ // All nodes of this shape use the same size (largest needed)
224
+ // This will be populated on a second pass
225
+ width = Math.max(contentWidth, sizing.minSize.width, minWidth || 0);
226
+ height = Math.max(contentHeight, sizing.minSize.height, minHeight || 0);
227
+ break;
228
+ case 'hybrid':
229
+ default:
230
+ // Blend: adaptive width, uniform height per shape type
231
+ width = Math.max(contentWidth, sizing.minSize.width, minWidth || 0);
232
+ height = Math.max(sizing.minSize.height, defaultSize.height, minHeight || 0);
233
+ break;
234
+ }
235
+ // Round to reasonable values
236
+ width = Math.ceil(width);
237
+ height = Math.ceil(height);
238
+ return {
239
+ width,
240
+ height,
241
+ textBox: computeTextBox(width, height, sizing),
242
+ };
243
+ }
244
+ /**
245
+ * Compute the text bounding box within a node
246
+ * This is where text can be safely rendered without clipping
247
+ */
248
+ function computeTextBox(width, height, sizing) {
249
+ // Text box is centered within the shape, reduced by the textBoundsRatio
250
+ const textWidth = (width - sizing.padding.x * 2) / sizing.textBoundsRatio.x;
251
+ const textHeight = (height - sizing.padding.y * 2) / sizing.textBoundsRatio.y;
252
+ return {
253
+ x: (width - textWidth) / 2,
254
+ y: (height - textHeight) / 2,
255
+ width: textWidth,
256
+ height: textHeight,
257
+ };
258
+ }
259
+ /**
260
+ * Compute uniform sizes for all shapes in a diagram
261
+ *
262
+ * For uniform sizing mode, we need to find the maximum required size
263
+ * for each shape type, then apply that to all nodes of that type.
264
+ */
265
+ export function computeUniformSizes(nodes, options) {
266
+ const uniformSizes = new Map();
267
+ // First pass: compute adaptive sizes for all nodes
268
+ const sizesByShape = new Map();
269
+ for (const node of nodes) {
270
+ const dims = computeNodeSize({
271
+ text: node.text,
272
+ shapeId: node.shapeId,
273
+ sizingMode: 'adaptive',
274
+ ...options,
275
+ });
276
+ if (!sizesByShape.has(node.shapeId)) {
277
+ sizesByShape.set(node.shapeId, []);
278
+ }
279
+ sizesByShape.get(node.shapeId).push({ width: dims.width, height: dims.height });
280
+ }
281
+ // Second pass: find max size for each shape
282
+ for (const [shapeId, sizes] of sizesByShape) {
283
+ const maxWidth = Math.max(...sizes.map((s) => s.width));
284
+ const maxHeight = Math.max(...sizes.map((s) => s.height));
285
+ uniformSizes.set(shapeId, { width: maxWidth, height: maxHeight });
286
+ }
287
+ return uniformSizes;
288
+ }
289
+ /**
290
+ * Apply adaptive sizing to a set of nodes
291
+ *
292
+ * This is the main entry point for layout integration.
293
+ * Returns nodes with updated width/height properties.
294
+ */
295
+ export function applyAdaptiveSizing(nodes, options) {
296
+ const { sizingMode, getShapeId, fontSize, fontFamily, fontWeight } = options;
297
+ // For uniform mode, pre-compute sizes
298
+ let uniformSizes;
299
+ if (sizingMode === 'uniform') {
300
+ uniformSizes = computeUniformSizes(nodes.map((n) => ({ text: n.data.label || '', shapeId: getShapeId(n) })), { fontSize, fontFamily, fontWeight });
301
+ }
302
+ return nodes.map((node) => {
303
+ const dims = computeNodeSize({
304
+ text: node.data.label || '',
305
+ shapeId: getShapeId(node),
306
+ sizingMode,
307
+ fontSize,
308
+ fontFamily,
309
+ fontWeight,
310
+ uniformSizes,
311
+ });
312
+ return {
313
+ ...node,
314
+ width: dims.width,
315
+ height: dims.height,
316
+ };
317
+ });
318
+ }
319
+ //# sourceMappingURL=nodeSizing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeSizing.js","sourceRoot":"","sources":["../../src/layout/nodeSizing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;GAEG;AACH,MAAM,cAAc,GAAgB;IAClC,eAAe,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE;IACnC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;IAClC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,YAAY,GAAG;IACnB,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,sCAAsC;IAClD,UAAU,EAAE,GAAG;IACf,UAAU,EAAE,GAAG;CAChB,CAAC;AAEF;;GAEG;AACH,IAAI,cAAc,GAAoC,IAAI,CAAC;AAE3D;;GAEG;AACH,SAAS,iBAAiB;IACxB,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,0CAA0C;IAC1C,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EACJ,IAAI,EACJ,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAChC,UAAU,GAAG,YAAY,CAAC,UAAU,EACpC,UAAU,GAAG,YAAY,CAAC,UAAU,EACpC,QAAQ,EACR,UAAU,GAAG,YAAY,CAAC,UAAU,GACrC,GAAG,OAAO,CAAC;IAEZ,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAEhC,qDAAqD;IACrD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,2BAA2B;IAC3B,GAAG,CAAC,IAAI,GAAG,GAAG,UAAU,IAAI,QAAQ,MAAM,UAAU,EAAE,CAAC;IAEvD,0CAA0C;IAC1C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YAC/B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC;YACxC,KAAK,EAAE,CAAC,IAAI,CAAC;SACd,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAC;IAE3C,uBAAuB;IACvB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC;IAED,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;QAC9B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;QAC9C,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CACf,GAA6B,EAC7B,IAAY,EACZ,QAAgB;IAEhB,oBAAoB;IACpB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAChC,OAAO,CAAC,EAAE,CAAC,CAAC;IACd,CAAC;IAED,sCAAsC;IACtC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QAED,IAAI,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAE3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACtB,MAAM,QAAQ,GAAG,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC;YAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YAE1C,IAAI,OAAO,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC;gBAC9B,WAAW,GAAG,QAAQ,CAAC;YACzB,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACxB,WAAW,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,OAA2B;IACtD,MAAM,EACJ,IAAI,EACJ,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAChC,UAAU,GAAG,YAAY,CAAC,UAAU,EACpC,QAAQ,GACT,GAAG,OAAO,CAAC;IAEZ,qFAAqF;IACrF,MAAM,YAAY,GAAG,QAAQ,GAAG,GAAG,CAAC;IACpC,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAC;IAE3C,iCAAiC;IACjC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,+CAA+C;QAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,CAAC,EACD,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,YAAY,CAAC,CAClD,CAAC;QACF,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;YAC9B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,YAAY,CAAC;YACnD,KAAK,EAAE,UAAU;SAClB,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAC;IACzD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;aAAM,IAAI,SAAS,CAAC,MAAM,IAAI,YAAY,EAAE,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,6BAA6B;YAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrC,IAAI,WAAW,GAAG,EAAE,CAAC;YAErB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,YAAY,EAAE,CAAC;oBACtD,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9D,CAAC;qBAAM,CAAC;oBACN,IAAI,WAAW;wBAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBACzC,WAAW,GAAG,IAAI,CAAC;gBACrB,CAAC;YACH,CAAC;YACD,IAAI,WAAW;gBAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;IAE/E,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;QAC9B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;QAC9C,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAA0B;IACxD,MAAM,EACJ,IAAI,EACJ,OAAO,EACP,UAAU,EACV,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAChC,UAAU,GAAG,YAAY,CAAC,UAAU,EACpC,UAAU,GAAG,YAAY,CAAC,UAAU,EACpC,QAAQ,EACR,SAAS,EACT,YAAY,GACb,GAAG,OAAO,CAAC;IAEZ,2CAA2C;IAC3C,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,cAAc,CAAC;IAC/C,MAAM,WAAW,GAAG,KAAK,EAAE,WAAW,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAErE,kDAAkD;IAClD,IAAI,UAAU,KAAK,SAAS,IAAI,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,MAAM,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAE,CAAC;QAC/C,OAAO;YACL,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,OAAO,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;SACvE,CAAC;IACJ,CAAC;IAED,+CAA+C;IAC/C,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,WAAW,CAAC;QAC3B,IAAI,EAAE,IAAI,IAAI,EAAE;QAChB,QAAQ;QACR,UAAU;QACV,UAAU;QACV,QAAQ,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;KAC5D,CAAC,CAAC;IAEH,sDAAsD;IACtD,0EAA0E;IAC1E,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;IACtF,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;IAExF,4CAA4C;IAC5C,IAAI,KAAa,CAAC;IAClB,IAAI,MAAc,CAAC;IAEnB,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,UAAU;YACb,yBAAyB;YACzB,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC;YACpE,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC;YACxE,MAAM;QAER,KAAK,SAAS;YACZ,6DAA6D;YAC7D,0CAA0C;YAC1C,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC;YACpE,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC;YACxE,MAAM;QAER,KAAK,QAAQ,CAAC;QACd;YACE,uDAAuD;YACvD,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC;YACpE,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC;YAC7E,MAAM;IACV,CAAC;IAED,6BAA6B;IAC7B,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE3B,OAAO;QACL,KAAK;QACL,MAAM;QACN,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CACrB,KAAa,EACb,MAAc,EACd,MAAmB;IAEnB,wEAAwE;IACxE,MAAM,SAAS,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC;IAE9E,OAAO;QACL,CAAC,EAAE,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC;QAC1B,CAAC,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC;QAC5B,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,UAAU;KACnB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAA+C,EAC/C,OAIC;IAED,MAAM,YAAY,GAAG,IAAI,GAAG,EAA6C,CAAC;IAE1E,mDAAmD;IACnD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAoD,CAAC;IAEjF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,eAAe,CAAC;YAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,UAAU,EAAE,UAAU;YACtB,GAAG,OAAO;SACX,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,4CAA4C;IAC5C,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1D,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAU,EACV,OAMC;IAED,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAE7E,sCAAsC;IACtC,IAAI,YAAwE,CAAC;IAC7E,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,YAAY,GAAG,mBAAmB,CAChC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACxE,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,CACrC,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,IAAI,GAAG,eAAe,CAAC;YAC3B,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YAC3B,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC;YACzB,UAAU;YACV,QAAQ;YACR,UAAU;YACV,UAAU;YACV,YAAY;SACb,CAAC,CAAC;QAEH,OAAO;YACL,GAAG,IAAI;YACP,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Position Stability Module (CORAL-REQ-013)
3
+ *
4
+ * Provides utilities for maintaining node positions across DSL changes.
5
+ * Separates parse, render, and layout operations to prevent undo/redo
6
+ * positions from being overwritten by automatic layout.
7
+ */
8
+ import type { DiffableGraph, GraphDiff, Position, PositionResolution } from '../types.js';
9
+ /**
10
+ * Compare two graphs and identify added, removed, modified, and unchanged nodes.
11
+ *
12
+ * A node is considered "modified" if its type or label has changed.
13
+ * Edge changes are not considered for node modification status.
14
+ *
15
+ * @param oldGraph - The previous graph state
16
+ * @param newGraph - The new graph state
17
+ * @returns GraphDiff with categorized node IDs
18
+ */
19
+ export declare function diffGraphs(oldGraph: DiffableGraph, newGraph: DiffableGraph): GraphDiff;
20
+ /**
21
+ * Resolve positions for nodes in the new graph based on the diff and current positions.
22
+ *
23
+ * - Unchanged and modified nodes keep their current positions
24
+ * - Added nodes are marked as needing layout (placed at 0,0 temporarily)
25
+ * - Removed nodes are not included in the result
26
+ *
27
+ * @param oldGraph - The previous graph state
28
+ * @param newGraph - The new graph state
29
+ * @param currentPositions - Map of node IDs to their current positions
30
+ * @returns PositionResolution with resolved positions and nodes needing layout
31
+ */
32
+ export declare function resolvePositions(oldGraph: DiffableGraph, newGraph: DiffableGraph, currentPositions: Map<string, Position>): PositionResolution;
33
+ /**
34
+ * Node info for incremental layout
35
+ */
36
+ interface LayoutNodeInfo {
37
+ id: string;
38
+ type: string;
39
+ label: string;
40
+ width: number;
41
+ height: number;
42
+ }
43
+ /**
44
+ * Edge info for incremental layout
45
+ */
46
+ interface LayoutEdgeInfo {
47
+ id: string;
48
+ source: string;
49
+ target: string;
50
+ }
51
+ /**
52
+ * Perform incremental layout with pinned positions for existing nodes.
53
+ *
54
+ * Strategy: Run ELK on all nodes but restore pinned positions afterwards.
55
+ * This ensures edge routing works correctly while preserving user positions.
56
+ *
57
+ * If all nodes need layout (no pins), this performs a full ELK layout.
58
+ * If no nodes need layout (all pinned), this returns the pinned positions unchanged.
59
+ *
60
+ * @param nodes - All nodes to layout (with dimensions)
61
+ * @param edges - All edges in the graph
62
+ * @param pinnedPositions - Positions to preserve (node IDs to positions)
63
+ * @param needsLayout - Node IDs that need ELK positioning
64
+ * @param options - Optional layout options (direction, algorithm, etc.)
65
+ * @returns Map of node IDs to computed positions
66
+ */
67
+ export declare function incrementalLayout(nodes: LayoutNodeInfo[], edges: LayoutEdgeInfo[], pinnedPositions: Map<string, Position>, needsLayout: string[], options?: {
68
+ direction?: 'DOWN' | 'UP' | 'LEFT' | 'RIGHT';
69
+ algorithm?: string;
70
+ spacing?: number;
71
+ }): Promise<Map<string, Position>>;
72
+ export {};
73
+ //# sourceMappingURL=positionStability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positionStability.d.ts","sourceRoot":"","sources":["../../src/layout/positionStability.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAgB,SAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIxG;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,GAAG,SAAS,CAuCtF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,aAAa,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,GACtC,kBAAkB,CA0BpB;AAED;;GAEG;AACH,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,cAAc,EAAE,EACvB,KAAK,EAAE,cAAc,EAAE,EACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,EACtC,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAoGhC"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Position Stability Module (CORAL-REQ-013)
3
+ *
4
+ * Provides utilities for maintaining node positions across DSL changes.
5
+ * Separates parse, render, and layout operations to prevent undo/redo
6
+ * positions from being overwritten by automatic layout.
7
+ */
8
+ import ELK from 'elkjs/lib/elk.bundled.js';
9
+ const elk = new ELK();
10
+ /**
11
+ * Compare two graphs and identify added, removed, modified, and unchanged nodes.
12
+ *
13
+ * A node is considered "modified" if its type or label has changed.
14
+ * Edge changes are not considered for node modification status.
15
+ *
16
+ * @param oldGraph - The previous graph state
17
+ * @param newGraph - The new graph state
18
+ * @returns GraphDiff with categorized node IDs
19
+ */
20
+ export function diffGraphs(oldGraph, newGraph) {
21
+ const oldNodeMap = new Map();
22
+ for (const node of oldGraph.nodes) {
23
+ oldNodeMap.set(node.id, node);
24
+ }
25
+ const newNodeMap = new Map();
26
+ for (const node of newGraph.nodes) {
27
+ newNodeMap.set(node.id, node);
28
+ }
29
+ const added = [];
30
+ const removed = [];
31
+ const modified = [];
32
+ const unchanged = [];
33
+ // Check nodes in new graph
34
+ for (const node of newGraph.nodes) {
35
+ const oldNode = oldNodeMap.get(node.id);
36
+ if (!oldNode) {
37
+ // Node doesn't exist in old graph - it's new
38
+ added.push(node.id);
39
+ }
40
+ else if (oldNode.type !== node.type || oldNode.label !== node.label) {
41
+ // Node exists but has changed type or label
42
+ modified.push(node.id);
43
+ }
44
+ else {
45
+ // Node is unchanged
46
+ unchanged.push(node.id);
47
+ }
48
+ }
49
+ // Check for nodes removed from old graph
50
+ for (const node of oldGraph.nodes) {
51
+ if (!newNodeMap.has(node.id)) {
52
+ removed.push(node.id);
53
+ }
54
+ }
55
+ return { added, removed, modified, unchanged };
56
+ }
57
+ /**
58
+ * Resolve positions for nodes in the new graph based on the diff and current positions.
59
+ *
60
+ * - Unchanged and modified nodes keep their current positions
61
+ * - Added nodes are marked as needing layout (placed at 0,0 temporarily)
62
+ * - Removed nodes are not included in the result
63
+ *
64
+ * @param oldGraph - The previous graph state
65
+ * @param newGraph - The new graph state
66
+ * @param currentPositions - Map of node IDs to their current positions
67
+ * @returns PositionResolution with resolved positions and nodes needing layout
68
+ */
69
+ export function resolvePositions(oldGraph, newGraph, currentPositions) {
70
+ const diff = diffGraphs(oldGraph, newGraph);
71
+ const positions = new Map();
72
+ const needsLayout = [];
73
+ // Process unchanged and modified nodes - keep their positions
74
+ for (const id of [...diff.unchanged, ...diff.modified]) {
75
+ const pos = currentPositions.get(id);
76
+ if (pos) {
77
+ positions.set(id, pos);
78
+ }
79
+ else {
80
+ // Node exists in both graphs but has no position - needs layout
81
+ needsLayout.push(id);
82
+ positions.set(id, { x: 0, y: 0 });
83
+ }
84
+ }
85
+ // Process added nodes - need layout
86
+ for (const id of diff.added) {
87
+ needsLayout.push(id);
88
+ positions.set(id, { x: 0, y: 0 }); // Placeholder position
89
+ }
90
+ // Removed nodes are not included in the result
91
+ return { positions, needsLayout };
92
+ }
93
+ /**
94
+ * Perform incremental layout with pinned positions for existing nodes.
95
+ *
96
+ * Strategy: Run ELK on all nodes but restore pinned positions afterwards.
97
+ * This ensures edge routing works correctly while preserving user positions.
98
+ *
99
+ * If all nodes need layout (no pins), this performs a full ELK layout.
100
+ * If no nodes need layout (all pinned), this returns the pinned positions unchanged.
101
+ *
102
+ * @param nodes - All nodes to layout (with dimensions)
103
+ * @param edges - All edges in the graph
104
+ * @param pinnedPositions - Positions to preserve (node IDs to positions)
105
+ * @param needsLayout - Node IDs that need ELK positioning
106
+ * @param options - Optional layout options (direction, algorithm, etc.)
107
+ * @returns Map of node IDs to computed positions
108
+ */
109
+ export async function incrementalLayout(nodes, edges, pinnedPositions, needsLayout, options) {
110
+ // Handle empty case
111
+ if (nodes.length === 0) {
112
+ return new Map();
113
+ }
114
+ // If no nodes need layout, return pinned positions directly
115
+ if (needsLayout.length === 0) {
116
+ const result = new Map();
117
+ for (const node of nodes) {
118
+ const pos = pinnedPositions.get(node.id);
119
+ if (pos) {
120
+ result.set(node.id, pos);
121
+ }
122
+ }
123
+ return result;
124
+ }
125
+ const needsLayoutSet = new Set(needsLayout);
126
+ // Strategy: Run ELK on only the nodes that need layout, plus any nodes
127
+ // they connect to. Then merge results with pinned positions.
128
+ // For simplicity, we'll run ELK on all nodes but restore pinned positions after
129
+ const elkChildren = nodes.map((node) => {
130
+ const elkNode = {
131
+ id: node.id,
132
+ width: node.width,
133
+ height: node.height,
134
+ };
135
+ // Set initial position for ELK (helps with layout stability)
136
+ const pinnedPos = pinnedPositions.get(node.id);
137
+ if (pinnedPos) {
138
+ elkNode.x = pinnedPos.x;
139
+ elkNode.y = pinnedPos.y;
140
+ }
141
+ return elkNode;
142
+ });
143
+ const elkEdges = edges.map((edge) => ({
144
+ id: edge.id,
145
+ sources: [edge.source],
146
+ targets: [edge.target],
147
+ }));
148
+ const elkGraph = {
149
+ id: 'root',
150
+ layoutOptions: {
151
+ 'elk.algorithm': options?.algorithm || 'layered',
152
+ 'elk.direction': options?.direction || 'DOWN',
153
+ 'elk.spacing.nodeNode': String(options?.spacing || 50),
154
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '70',
155
+ },
156
+ children: elkChildren,
157
+ edges: elkEdges,
158
+ };
159
+ try {
160
+ const layoutedGraph = await elk.layout(elkGraph);
161
+ // Collect positions from layout result, but restore pinned positions
162
+ const result = new Map();
163
+ for (const elkNode of layoutedGraph.children || []) {
164
+ // For pinned nodes, use the original position
165
+ const pinnedPos = pinnedPositions.get(elkNode.id);
166
+ if (pinnedPos && !needsLayoutSet.has(elkNode.id)) {
167
+ result.set(elkNode.id, pinnedPos);
168
+ }
169
+ else {
170
+ // Use ELK-computed position for new nodes
171
+ result.set(elkNode.id, { x: elkNode.x || 0, y: elkNode.y || 0 });
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+ catch (error) {
177
+ console.error('Incremental layout failed:', error);
178
+ // Fallback: return pinned positions and place new nodes nearby
179
+ const result = new Map();
180
+ let fallbackY = 0;
181
+ // Find the max Y of pinned positions to place new nodes below
182
+ for (const pos of pinnedPositions.values()) {
183
+ fallbackY = Math.max(fallbackY, pos.y + 100);
184
+ }
185
+ for (const node of nodes) {
186
+ const pinnedPos = pinnedPositions.get(node.id);
187
+ if (pinnedPos && !needsLayoutSet.has(node.id)) {
188
+ result.set(node.id, pinnedPos);
189
+ }
190
+ else {
191
+ // Place new nodes below existing ones
192
+ result.set(node.id, { x: 100, y: fallbackY });
193
+ fallbackY += node.height + 50;
194
+ }
195
+ }
196
+ return result;
197
+ }
198
+ }
199
+ //# sourceMappingURL=positionStability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positionStability.js","sourceRoot":"","sources":["../../src/layout/positionStability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * useAxisConstrain — Shift+drag axis-locked node movement
3
+ *
4
+ * Wraps React Flow's onNodesChange to constrain drag movement to a single
5
+ * axis (horizontal or vertical) when the Shift key is held. The dominant
6
+ * axis is detected after a small movement threshold.
7
+ */
8
+ import type { NodeChange, NodeBase } from '@xyflow/system';
9
+ export interface UseAxisConstrainResult<NodeType extends NodeBase = NodeBase> {
10
+ /** Wrapped onNodesChange that applies axis constraints when Shift is held */
11
+ onNodesChange: (changes: NodeChange<NodeType>[]) => void;
12
+ }
13
+ /**
14
+ * Wraps onNodesChange to add Shift+drag axis constraint.
15
+ *
16
+ * @param originalOnNodesChange - The onNodesChange from useNodesState
17
+ * @param nodes - Current nodes array (to read start positions)
18
+ */
19
+ export declare function useAxisConstrain<NodeType extends NodeBase = NodeBase>(originalOnNodesChange: (changes: NodeChange<NodeType>[]) => void, nodes: NodeType[]): UseAxisConstrainResult<NodeType>;
20
+ //# sourceMappingURL=useAxisConstrain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAxisConstrain.d.ts","sourceRoot":"","sources":["../../src/layout/useAxisConstrain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAc,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAWvE,MAAM,WAAW,sBAAsB,CAAC,QAAQ,SAAS,QAAQ,GAAG,QAAQ;IAC1E,6EAA6E;IAC7E,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,SAAS,QAAQ,GAAG,QAAQ,EACnE,qBAAqB,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,EAChE,KAAK,EAAE,QAAQ,EAAE,GAChB,sBAAsB,CAAC,QAAQ,CAAC,CAoGlC"}