@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.
- package/LICENSE +4 -0
- package/README.md +11 -0
- package/dist/compatibility/CompatibilityEdge.d.ts +106 -0
- package/dist/compatibility/CompatibilityEdge.d.ts.map +1 -0
- package/dist/compatibility/CompatibilityEdge.js +233 -0
- package/dist/compatibility/CompatibilityEdge.js.map +1 -0
- package/dist/compatibility/IncompatibilityTooltip.d.ts +44 -0
- package/dist/compatibility/IncompatibilityTooltip.d.ts.map +1 -0
- package/dist/compatibility/IncompatibilityTooltip.js +145 -0
- package/dist/compatibility/IncompatibilityTooltip.js.map +1 -0
- package/dist/compatibility/index.d.ts +10 -0
- package/dist/compatibility/index.d.ts.map +1 -0
- package/dist/compatibility/index.js +10 -0
- package/dist/compatibility/index.js.map +1 -0
- package/dist/compatibility/useEdgeCompatibility.d.ts +82 -0
- package/dist/compatibility/useEdgeCompatibility.d.ts.map +1 -0
- package/dist/compatibility/useEdgeCompatibility.js +170 -0
- package/dist/compatibility/useEdgeCompatibility.js.map +1 -0
- package/dist/compatibility/validateConnection.d.ts +56 -0
- package/dist/compatibility/validateConnection.d.ts.map +1 -0
- package/dist/compatibility/validateConnection.js +382 -0
- package/dist/compatibility/validateConnection.js.map +1 -0
- package/dist/components/EditContext.d.ts +45 -0
- package/dist/components/EditContext.d.ts.map +1 -0
- package/dist/components/EditContext.js +41 -0
- package/dist/components/EditContext.js.map +1 -0
- package/dist/components/ShapeRenderer.d.ts +35 -0
- package/dist/components/ShapeRenderer.d.ts.map +1 -0
- package/dist/components/ShapeRenderer.js +77 -0
- package/dist/components/ShapeRenderer.js.map +1 -0
- package/dist/components/SymbolNode.d.ts +53 -0
- package/dist/components/SymbolNode.d.ts.map +1 -0
- package/dist/components/SymbolNode.js +333 -0
- package/dist/components/SymbolNode.js.map +1 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +10 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/nodes.d.ts +43 -0
- package/dist/components/nodes.d.ts.map +1 -0
- package/dist/components/nodes.js +111 -0
- package/dist/components/nodes.js.map +1 -0
- package/dist/editor/CoralEditor.d.ts +29 -0
- package/dist/editor/CoralEditor.d.ts.map +1 -0
- package/dist/editor/CoralEditor.js +112 -0
- package/dist/editor/CoralEditor.js.map +1 -0
- package/dist/editor/FileControls.d.ts +34 -0
- package/dist/editor/FileControls.d.ts.map +1 -0
- package/dist/editor/FileControls.js +139 -0
- package/dist/editor/FileControls.js.map +1 -0
- package/dist/editor/LayoutControls.d.ts +41 -0
- package/dist/editor/LayoutControls.d.ts.map +1 -0
- package/dist/editor/LayoutControls.js +124 -0
- package/dist/editor/LayoutControls.js.map +1 -0
- package/dist/editor/converter.d.ts +37 -0
- package/dist/editor/converter.d.ts.map +1 -0
- package/dist/editor/converter.js +256 -0
- package/dist/editor/converter.js.map +1 -0
- package/dist/editor/index.d.ts +8 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +8 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/file/deserialize.d.ts +15 -0
- package/dist/file/deserialize.d.ts.map +1 -0
- package/dist/file/deserialize.js +145 -0
- package/dist/file/deserialize.js.map +1 -0
- package/dist/file/index.d.ts +15 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/file/index.js +20 -0
- package/dist/file/index.js.map +1 -0
- package/dist/file/migrate.d.ts +29 -0
- package/dist/file/migrate.d.ts.map +1 -0
- package/dist/file/migrate.js +103 -0
- package/dist/file/migrate.js.map +1 -0
- package/dist/file/schema.d.ts +385 -0
- package/dist/file/schema.d.ts.map +1 -0
- package/dist/file/schema.js +180 -0
- package/dist/file/schema.js.map +1 -0
- package/dist/file/serialize.d.ts +30 -0
- package/dist/file/serialize.d.ts.map +1 -0
- package/dist/file/serialize.js +218 -0
- package/dist/file/serialize.js.map +1 -0
- package/dist/file/validate.d.ts +21 -0
- package/dist/file/validate.d.ts.map +1 -0
- package/dist/file/validate.js +165 -0
- package/dist/file/validate.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/elk.d.ts +77 -0
- package/dist/layout/elk.d.ts.map +1 -0
- package/dist/layout/elk.js +379 -0
- package/dist/layout/elk.js.map +1 -0
- package/dist/layout/index.d.ts +12 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +12 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/nodeSizing.d.ts +65 -0
- package/dist/layout/nodeSizing.d.ts.map +1 -0
- package/dist/layout/nodeSizing.js +319 -0
- package/dist/layout/nodeSizing.js.map +1 -0
- package/dist/layout/positionStability.d.ts +73 -0
- package/dist/layout/positionStability.d.ts.map +1 -0
- package/dist/layout/positionStability.js +199 -0
- package/dist/layout/positionStability.js.map +1 -0
- package/dist/layout/useAxisConstrain.d.ts +20 -0
- package/dist/layout/useAxisConstrain.d.ts.map +1 -0
- package/dist/layout/useAxisConstrain.js +106 -0
- package/dist/layout/useAxisConstrain.js.map +1 -0
- package/dist/layout/useDiagramState.d.ts +92 -0
- package/dist/layout/useDiagramState.d.ts.map +1 -0
- package/dist/layout/useDiagramState.js +256 -0
- package/dist/layout/useDiagramState.js.map +1 -0
- package/dist/layout/useLayoutHistory.d.ts +53 -0
- package/dist/layout/useLayoutHistory.d.ts.map +1 -0
- package/dist/layout/useLayoutHistory.js +124 -0
- package/dist/layout/useLayoutHistory.js.map +1 -0
- package/dist/properties/NodePropertiesPanel.d.ts +47 -0
- package/dist/properties/NodePropertiesPanel.d.ts.map +1 -0
- package/dist/properties/NodePropertiesPanel.js +199 -0
- package/dist/properties/NodePropertiesPanel.js.map +1 -0
- package/dist/properties/index.d.ts +10 -0
- package/dist/properties/index.d.ts.map +1 -0
- package/dist/properties/index.js +10 -0
- package/dist/properties/index.js.map +1 -0
- package/dist/properties/propertyUtils.d.ts +60 -0
- package/dist/properties/propertyUtils.d.ts.map +1 -0
- package/dist/properties/propertyUtils.js +89 -0
- package/dist/properties/propertyUtils.js.map +1 -0
- package/dist/settings/FontSettingsForm.d.ts +28 -0
- package/dist/settings/FontSettingsForm.d.ts.map +1 -0
- package/dist/settings/FontSettingsForm.js +129 -0
- package/dist/settings/FontSettingsForm.js.map +1 -0
- package/dist/settings/LayoutSettingsForm.d.ts +37 -0
- package/dist/settings/LayoutSettingsForm.d.ts.map +1 -0
- package/dist/settings/LayoutSettingsForm.js +165 -0
- package/dist/settings/LayoutSettingsForm.js.map +1 -0
- package/dist/settings/SettingsPanel.d.ts +55 -0
- package/dist/settings/SettingsPanel.d.ts.map +1 -0
- package/dist/settings/SettingsPanel.js +84 -0
- package/dist/settings/SettingsPanel.js.map +1 -0
- package/dist/settings/index.d.ts +13 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +13 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/useSettings.d.ts +51 -0
- package/dist/settings/useSettings.d.ts.map +1 -0
- package/dist/settings/useSettings.js +154 -0
- package/dist/settings/useSettings.js.map +1 -0
- package/dist/text-editor/SplitEditor.d.ts +72 -0
- package/dist/text-editor/SplitEditor.d.ts.map +1 -0
- package/dist/text-editor/SplitEditor.js +172 -0
- package/dist/text-editor/SplitEditor.js.map +1 -0
- package/dist/text-editor/TextEditor.d.ts +53 -0
- package/dist/text-editor/TextEditor.d.ts.map +1 -0
- package/dist/text-editor/TextEditor.js +181 -0
- package/dist/text-editor/TextEditor.js.map +1 -0
- package/dist/text-editor/hooks/useBidirectionalSync.d.ts +106 -0
- package/dist/text-editor/hooks/useBidirectionalSync.d.ts.map +1 -0
- package/dist/text-editor/hooks/useBidirectionalSync.js +151 -0
- package/dist/text-editor/hooks/useBidirectionalSync.js.map +1 -0
- package/dist/text-editor/index.d.ts +9 -0
- package/dist/text-editor/index.d.ts.map +1 -0
- package/dist/text-editor/index.js +11 -0
- package/dist/text-editor/index.js.map +1 -0
- package/dist/types.d.ts +242 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/waypoints/index.d.ts +8 -0
- package/dist/waypoints/index.d.ts.map +1 -0
- package/dist/waypoints/index.js +7 -0
- package/dist/waypoints/index.js.map +1 -0
- package/dist/waypoints/operations.d.ts +29 -0
- package/dist/waypoints/operations.d.ts.map +1 -0
- package/dist/waypoints/operations.js +85 -0
- package/dist/waypoints/operations.js.map +1 -0
- package/dist/waypoints/router.d.ts +23 -0
- package/dist/waypoints/router.d.ts.map +1 -0
- package/dist/waypoints/router.js +109 -0
- package/dist/waypoints/router.js.map +1 -0
- package/dist/waypoints/types.d.ts +15 -0
- package/dist/waypoints/types.d.ts.map +1 -0
- package/dist/waypoints/types.js +5 -0
- package/dist/waypoints/types.js.map +1 -0
- package/dist/waypoints/useWaypointManager.d.ts +38 -0
- package/dist/waypoints/useWaypointManager.d.ts.map +1 -0
- package/dist/waypoints/useWaypointManager.js +106 -0
- package/dist/waypoints/useWaypointManager.js.map +1 -0
- 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":"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"}
|
|
@@ -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":"AAAA;;;;;;GAMG;AAEH,OAAO,GAAG,MAAM,0BAA0B,CAAC;AAI3C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;AAEtB;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,QAAuB,EAAE,QAAuB;IACzE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAwB,CAAC;IACnD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAwB,CAAC;IACnD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,2BAA2B;IAC3B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,6CAA6C;YAC7C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YACtE,4CAA4C;YAC5C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,oBAAoB;YACpB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAuB,EACvB,QAAuB,EACvB,gBAAuC;IAEvC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC9C,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,8DAA8D;IAC9D,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,GAAG,EAAE,CAAC;YACR,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,gEAAgE;YAChE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACrB,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,uBAAuB;IAC5D,CAAC;IAED,+CAA+C;IAE/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AACpC,CAAC;AAsBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAuB,EACvB,KAAuB,EACvB,eAAsC,EACtC,WAAqB,EACrB,OAIC;IAED,oBAAoB;IACpB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,4DAA4D;IAC5D,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzC,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAE5C,uEAAuE;IACvE,6DAA6D;IAE7D,gFAAgF;IAChF,MAAM,WAAW,GAAc,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAChD,MAAM,OAAO,GAAY;YACvB,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;QAEF,6DAA6D;QAC7D,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;YACxB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;QAC1B,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAsB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvD,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;QACtB,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;KACvB,CAAC,CAAC,CAAC;IAEJ,MAAM,QAAQ,GAAY;QACxB,EAAE,EAAE,MAAM;QACV,aAAa,EAAE;YACb,eAAe,EAAE,OAAO,EAAE,SAAS,IAAI,SAAS;YAChD,eAAe,EAAE,OAAO,EAAE,SAAS,IAAI,MAAM;YAC7C,sBAAsB,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC;YACtD,2CAA2C,EAAE,IAAI;SAClD;QACD,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,QAAQ;KAChB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAEjD,qEAAqE;QACrE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,MAAM,OAAO,IAAI,aAAa,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACnD,8CAA8C;YAC9C,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClD,IAAI,SAAS,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;gBACjD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,0CAA0C;gBAC1C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;QACnD,+DAA+D;QAC/D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,8DAA8D;QAC9D,KAAK,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC/C,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/C,IAAI,SAAS,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC9C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,sCAAsC;gBACtC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC9C,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YAChC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC"}
|
|
@@ -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"}
|