orcasvn-react-diagrams 0.2.4 → 0.2.6
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/CHANGELOG.md +42 -4
- package/README.md +15 -32
- package/ai/api-contract.json +88 -3
- package/ai/manifest.json +1 -1
- package/dist/cjs/examples.js +6519 -4423
- package/dist/cjs/index.js +1513 -120
- package/dist/cjs/types/api/createDiagramEditor.d.ts +4 -1
- package/dist/cjs/types/api/types.d.ts +43 -1
- package/dist/cjs/types/displaybox/demos/LinkColorPoolDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/OffsetAnchorAvoidanceDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/SimpleDemo.d.ts +4 -1
- package/dist/cjs/types/displaybox/demos/ZoomToFitElementsDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/linkColorPoolDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/offsetAnchorAvoidanceDemo.d.ts +4 -0
- package/dist/cjs/types/displaybox/demos/textDemo.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/zoomToFitElementsDemo.d.ts +4 -0
- package/dist/cjs/types/displaybox/types.d.ts +3 -1
- package/dist/cjs/types/displaybox/useDemoEditor.d.ts +4 -2
- package/dist/cjs/types/engine/AutoLayoutService.d.ts +59 -2
- package/dist/cjs/types/engine/DiagramEngine.d.ts +20 -1
- package/dist/cjs/types/engine/LinkRoutingService.d.ts +9 -0
- package/dist/cjs/types/models/TextModel.d.ts +1 -0
- package/dist/cjs/types/renderer/konva/KonvaInteraction.d.ts +6 -0
- package/dist/cjs/types/strategies/ObstacleRouter.d.ts +2 -0
- package/dist/cjs/types/strategies/RouterStrategy.d.ts +10 -0
- package/dist/esm/examples.js +6519 -4423
- package/dist/esm/examples.js.map +1 -1
- package/dist/esm/index.js +1513 -120
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/api/createDiagramEditor.d.ts +4 -1
- package/dist/esm/types/api/types.d.ts +43 -1
- package/dist/esm/types/displaybox/demos/LinkColorPoolDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/OffsetAnchorAvoidanceDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/SimpleDemo.d.ts +4 -1
- package/dist/esm/types/displaybox/demos/ZoomToFitElementsDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/linkColorPoolDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/offsetAnchorAvoidanceDemo.d.ts +4 -0
- package/dist/esm/types/displaybox/demos/textDemo.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/zoomToFitElementsDemo.d.ts +4 -0
- package/dist/esm/types/displaybox/types.d.ts +3 -1
- package/dist/esm/types/displaybox/useDemoEditor.d.ts +4 -2
- package/dist/esm/types/engine/AutoLayoutService.d.ts +59 -2
- package/dist/esm/types/engine/DiagramEngine.d.ts +20 -1
- package/dist/esm/types/engine/LinkRoutingService.d.ts +9 -0
- package/dist/esm/types/models/TextModel.d.ts +1 -0
- package/dist/esm/types/renderer/konva/KonvaInteraction.d.ts +6 -0
- package/dist/esm/types/strategies/ObstacleRouter.d.ts +2 -0
- package/dist/esm/types/strategies/RouterStrategy.d.ts +10 -0
- package/dist/examples.d.ts +56 -1
- package/dist/index.d.ts +60 -2
- package/docs/API_CONTRACT.md +73 -3
- package/docs/CAPABILITIES.md +8 -0
- package/docs/COMMANDS_EVENTS.md +8 -0
- package/docs/DOCUMENTATION_WORKFLOW.md +4 -1
- package/docs/INTEGRATION_PLAYBOOK.md +3 -0
- package/docs/STATE_INVARIANTS.md +4 -0
- package/package.json +1 -1
- package/src/displaybox/demos/AutoLayoutDemoTab.tsx +515 -381
- package/src/displaybox/demos/EngineEventsDemoTab.tsx +12 -11
- package/src/displaybox/demos/LinkColorPoolDemoTab.tsx +49 -0
- package/src/displaybox/demos/ObstacleRoutingDemoTab.tsx +52 -31
- package/src/displaybox/demos/OffsetAnchorAvoidanceDemoTab.tsx +30 -0
- package/src/displaybox/demos/SimpleDemo.tsx +18 -13
- package/src/displaybox/demos/TextLayoutDemoTab.tsx +18 -1
- package/src/displaybox/demos/ZoomToFitElementsDemoTab.tsx +29 -0
- package/src/displaybox/demos/autoLayoutDemo.ts +62 -26
- package/src/displaybox/demos/index.tsx +81 -57
- package/src/displaybox/demos/linkColorPoolDemo.ts +122 -0
- package/src/displaybox/demos/obstacleRoutingDemo.ts +77 -1
- package/src/displaybox/demos/offsetAnchorAvoidanceDemo.ts +211 -0
- package/src/displaybox/demos/routingDemo.ts +5 -5
- package/src/displaybox/demos/shared.ts +17 -12
- package/src/displaybox/demos/textDemo.ts +6 -2
- package/src/displaybox/demos/zoomToFitElementsDemo.ts +83 -0
- package/src/displaybox/types.ts +10 -8
|
@@ -1,131 +1,175 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ElementLayout,
|
|
4
|
+
ElementLayoutAutoResizeMode,
|
|
5
|
+
ElementLayoutGridRowTemplate,
|
|
6
|
+
ElementLayoutLabelReservedSpaceMode,
|
|
7
|
+
ElementLayoutMode,
|
|
8
|
+
} from '../../api';
|
|
9
|
+
import { createId } from '../../utils/ids';
|
|
10
|
+
import DisplayBoxControls from '../DisplayBoxControls';
|
|
11
|
+
import DisplayBoxStage from '../DisplayBoxStage';
|
|
12
|
+
import useDemoControls from '../useDemoControls';
|
|
13
|
+
import useDemoEditor from '../useDemoEditor';
|
|
14
|
+
import useOffsetSequence from '../useOffsetSequence';
|
|
15
|
+
import type { DemoActionHelpers } from '../types';
|
|
16
|
+
import { autoLayoutDemoConfig } from './autoLayoutDemo';
|
|
17
|
+
import { gridStageStyle } from './shared';
|
|
18
|
+
|
|
13
19
|
const parentOptions = [
|
|
14
20
|
{ id: 'layout-row', label: 'Horizontal layout' },
|
|
15
21
|
{ id: 'layout-column', label: 'Vertical layout' },
|
|
22
|
+
{ id: 'layout-grid', label: 'Grid layout' },
|
|
16
23
|
{ id: 'layout-nested', label: 'Nested layout' },
|
|
17
24
|
{ id: 'layout-manual', label: 'Manual (compare)' },
|
|
18
25
|
];
|
|
19
26
|
const shortLabel = 'Parent label lane demo';
|
|
20
27
|
const longLabel =
|
|
21
28
|
'Parent label lane demo with longer content to increase flexible reserved space and push children downward.';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
const defaultGridTemplateText = '[12,4,8]';
|
|
30
|
+
|
|
31
|
+
const serializeGridTemplate = (template?: ElementLayoutGridRowTemplate[]): string =>
|
|
32
|
+
Array.isArray(template) && template.length > 0 ? JSON.stringify(template) : defaultGridTemplateText;
|
|
33
|
+
|
|
34
|
+
const parseGridTemplate = (value: string): ElementLayoutGridRowTemplate[] | undefined | null => {
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
if (!trimmed) return undefined;
|
|
37
|
+
let parsed: unknown;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(trimmed);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(parsed)) return null;
|
|
44
|
+
const normalized: ElementLayoutGridRowTemplate[] = [];
|
|
45
|
+
for (let i = 0; i < parsed.length; i += 1) {
|
|
46
|
+
const entry = parsed[i];
|
|
47
|
+
if (typeof entry === 'number') {
|
|
48
|
+
if (!Number.isFinite(entry) || entry <= 0) return null;
|
|
49
|
+
normalized.push(Math.floor(entry));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(entry) || entry.length === 0) return null;
|
|
53
|
+
for (let j = 0; j < entry.length; j += 1) {
|
|
54
|
+
const cell = entry[j];
|
|
55
|
+
if (typeof cell !== 'number' || !Number.isFinite(cell) || cell <= 0) return null;
|
|
56
|
+
normalized.push(Math.floor(cell));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const AutoLayoutDemo = () => {
|
|
63
|
+
const demo = autoLayoutDemoConfig;
|
|
64
|
+
const { containerRef, editorRef, diagramState, selection, snapEnabled, setSnapEnabled } = useDemoEditor({
|
|
65
|
+
createState: demo.createState,
|
|
66
|
+
elementShapes: demo.elementShapes,
|
|
67
|
+
portShapes: demo.portShapes,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const nextOffset = useOffsetSequence();
|
|
71
|
+
const actionHelpers: DemoActionHelpers = useMemo(() => ({ nextOffset }), [nextOffset]);
|
|
72
|
+
|
|
73
|
+
const controls = useDemoControls({
|
|
74
|
+
demo,
|
|
75
|
+
editorRef,
|
|
76
|
+
diagramState,
|
|
77
|
+
selection,
|
|
78
|
+
snapEnabled,
|
|
79
|
+
setSnapEnabled,
|
|
80
|
+
actionHelpers,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const [targetId, setTargetId] = useState<string>(parentOptions[0].id);
|
|
84
|
+
const [mode, setMode] = useState<ElementLayoutMode>('horizontal');
|
|
46
85
|
const [align, setAlign] = useState<ElementLayout['align']>('center');
|
|
86
|
+
const [autoResize, setAutoResize] = useState<ElementLayoutAutoResizeMode>('grow');
|
|
47
87
|
const [padding, setPadding] = useState<number>(12);
|
|
48
88
|
const [gap, setGap] = useState<number>(12);
|
|
49
89
|
const [childFitMainAxis, setChildFitMainAxis] = useState<ElementLayout['childFitMainAxis']>('none');
|
|
50
90
|
const [childFitCrossAxis, setChildFitCrossAxis] = useState<ElementLayout['childFitCrossAxis']>('none');
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
53
|
-
const [
|
|
54
|
-
const [
|
|
91
|
+
const [childMinWidth, setChildMinWidth] = useState<number | ''>('');
|
|
92
|
+
const [childMinHeight, setChildMinHeight] = useState<number | ''>('');
|
|
93
|
+
const [childMaxWidth, setChildMaxWidth] = useState<number | ''>('');
|
|
94
|
+
const [childMaxHeight, setChildMaxHeight] = useState<number | ''>('');
|
|
95
|
+
const [gridTemplateText, setGridTemplateText] = useState<string>(defaultGridTemplateText);
|
|
96
|
+
const [gridChildWidthResizeEnabled, setGridChildWidthResizeEnabled] = useState<boolean>(false);
|
|
55
97
|
const [labelReservedMode, setLabelReservedMode] = useState<ElementLayoutLabelReservedSpaceMode>('none');
|
|
56
98
|
const [labelReservedFixedSize, setLabelReservedFixedSize] = useState<number>(32);
|
|
57
99
|
const [labelReservedMinSize, setLabelReservedMinSize] = useState<number | ''>('');
|
|
58
100
|
const [labelReservedMaxSize, setLabelReservedMaxSize] = useState<number | ''>('');
|
|
59
101
|
const [lastTrigger, setLastTrigger] = useState<string>('None yet');
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
const editor = editorRef.current;
|
|
63
|
-
if (!editor) return undefined;
|
|
64
|
-
const disposers = [
|
|
65
|
-
editor.on('elementMoved', () => setLastTrigger('child moved')),
|
|
66
|
-
editor.on('elementResized', () => setLastTrigger('child resized')),
|
|
67
|
-
editor.on('
|
|
68
|
-
editor.on('
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const editor = editorRef.current;
|
|
105
|
+
if (!editor) return undefined;
|
|
106
|
+
const disposers = [
|
|
107
|
+
editor.on('elementMoved', () => setLastTrigger('child moved')),
|
|
108
|
+
editor.on('elementResized', () => setLastTrigger('child resized')),
|
|
109
|
+
editor.on('gridLayoutChanged', (event) => setLastTrigger(`grid layout changed (${event.reason})`)),
|
|
110
|
+
editor.on('elementDeleted', () => setLastTrigger('child removed')),
|
|
111
|
+
editor.on('change', () => setLastTrigger((prev) => prev)),
|
|
112
|
+
];
|
|
113
|
+
return () => disposers.forEach((off) => off && off());
|
|
114
|
+
}, [editorRef]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!diagramState) return;
|
|
118
|
+
if (selection.length === 1) {
|
|
119
|
+
const selectedId = selection[0];
|
|
120
|
+
const exists = diagramState.elements.some((el) => el.id === selectedId);
|
|
121
|
+
if (exists && selectedId !== targetId) {
|
|
122
|
+
setTargetId(selectedId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [selection, diagramState, targetId]);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!diagramState) return;
|
|
129
|
+
const exists = diagramState.elements.some((el) => el.id === targetId);
|
|
130
|
+
if (!exists) {
|
|
131
|
+
setTargetId(parentOptions[0].id);
|
|
132
|
+
}
|
|
133
|
+
}, [diagramState, targetId]);
|
|
134
|
+
|
|
135
|
+
const targetElement = useMemo(
|
|
136
|
+
() => diagramState?.elements.find((el) => el.id === targetId),
|
|
137
|
+
[diagramState, targetId],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const targetOptions = useMemo(() => {
|
|
141
|
+
const seen = new Set<string>();
|
|
142
|
+
const options: Array<{ id: string; label: string }> = [];
|
|
143
|
+
parentOptions.forEach((option) => {
|
|
144
|
+
if (!seen.has(option.id)) {
|
|
145
|
+
options.push(option);
|
|
146
|
+
seen.add(option.id);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if (diagramState) {
|
|
150
|
+
diagramState.elements
|
|
151
|
+
.filter((el) => (el.layout && el.layout.mode !== 'manual') || el.parentId)
|
|
152
|
+
.forEach((el) => {
|
|
153
|
+
if (!seen.has(el.id)) {
|
|
154
|
+
const modeLabel = el.layout?.mode ?? 'manual';
|
|
155
|
+
options.push({ id: el.id, label: `${el.id} (${modeLabel})` });
|
|
156
|
+
seen.add(el.id);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (targetElement && !seen.has(targetElement.id)) {
|
|
161
|
+
options.push({ id: targetElement.id, label: `${targetElement.id} (selected)` });
|
|
162
|
+
}
|
|
163
|
+
return options;
|
|
164
|
+
}, [diagramState, targetElement]);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const parent = diagramState?.elements.find((el) => el.id === targetId);
|
|
168
|
+
if (!parent) return;
|
|
169
|
+
const layout = parent.layout;
|
|
170
|
+
setMode(layout?.mode ?? 'manual');
|
|
171
|
+
setAlign(layout?.align ?? 'center');
|
|
172
|
+
setAutoResize(layout?.autoResize ?? 'grow');
|
|
129
173
|
const paddingValue =
|
|
130
174
|
typeof layout?.padding === 'number'
|
|
131
175
|
? layout.padding
|
|
@@ -136,60 +180,61 @@ const AutoLayoutDemo = () => {
|
|
|
136
180
|
setGap(layout?.gap ?? 12);
|
|
137
181
|
setChildFitMainAxis(layout?.childFitMainAxis ?? 'none');
|
|
138
182
|
setChildFitCrossAxis(layout?.childFitCrossAxis ?? 'none');
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
setChildMinWidth(layout?.childMinWidth ?? layout?.childFitMinSize?.width ?? '');
|
|
184
|
+
setChildMinHeight(layout?.childMinHeight ?? layout?.childFitMinSize?.height ?? '');
|
|
185
|
+
setChildMaxWidth(layout?.childMaxWidth ?? layout?.childFitMaxSize?.width ?? '');
|
|
186
|
+
setChildMaxHeight(layout?.childMaxHeight ?? layout?.childFitMaxSize?.height ?? '');
|
|
187
|
+
setGridTemplateText(serializeGridTemplate(layout?.gridTemplate));
|
|
188
|
+
setGridChildWidthResizeEnabled(Boolean(layout?.gridChildWidthResizeEnabled));
|
|
143
189
|
setLabelReservedMode(layout?.labelReservedSpace?.mode ?? 'none');
|
|
144
190
|
setLabelReservedFixedSize(layout?.labelReservedSpace?.size ?? 32);
|
|
145
191
|
setLabelReservedMinSize(layout?.labelReservedSpace?.minSize ?? '');
|
|
146
192
|
setLabelReservedMaxSize(layout?.labelReservedSpace?.maxSize ?? '');
|
|
147
193
|
}, [diagramState, targetId]);
|
|
148
|
-
|
|
149
|
-
const childOrder = useMemo(() => {
|
|
150
|
-
if (!diagramState) return [];
|
|
151
|
-
const children = diagramState.elements.filter((el) => el.parentId === targetId);
|
|
152
|
-
const parent = diagramState.elements.find((el) => el.id === targetId);
|
|
153
|
-
const
|
|
154
|
-
return [...children].sort((a, b) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
|
|
195
|
+
const childOrder = useMemo(() => {
|
|
196
|
+
if (!diagramState) return [];
|
|
197
|
+
const children = diagramState.elements.filter((el) => el.parentId === targetId);
|
|
198
|
+
const parent = diagramState.elements.find((el) => el.id === targetId);
|
|
199
|
+
const layoutMode = parent?.layout?.mode;
|
|
200
|
+
return [...children].sort((a, b) => {
|
|
201
|
+
if (layoutMode === 'vertical' || layoutMode === 'grid') {
|
|
202
|
+
if (a.position.y === b.position.y) {
|
|
203
|
+
if (a.position.x === b.position.x) return a.id.localeCompare(b.id);
|
|
204
|
+
return a.position.x - b.position.x;
|
|
205
|
+
}
|
|
206
|
+
return a.position.y - b.position.y;
|
|
207
|
+
}
|
|
208
|
+
if (a.position.x === b.position.x) {
|
|
209
|
+
if (a.position.y === b.position.y) return a.id.localeCompare(b.id);
|
|
210
|
+
return a.position.y - b.position.y;
|
|
211
|
+
}
|
|
212
|
+
return a.position.x - b.position.x;
|
|
213
|
+
});
|
|
214
|
+
}, [diagramState, targetId]);
|
|
215
|
+
|
|
216
|
+
const parentSummaries = useMemo(() => {
|
|
217
|
+
return parentOptions.map((option) => {
|
|
218
|
+
const parent = diagramState?.elements.find((el) => el.id === option.id);
|
|
219
|
+
const childCount = diagramState?.elements.filter((el) => el.parentId === option.id).length ?? 0;
|
|
220
|
+
return {
|
|
221
|
+
...option,
|
|
222
|
+
layoutMode: parent?.layout?.mode ?? 'manual',
|
|
223
|
+
size: parent?.size ?? { width: 0, height: 0 },
|
|
224
|
+
childCount,
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}, [diagramState]);
|
|
228
|
+
|
|
175
229
|
const handleApplyLayout = () => {
|
|
176
|
-
const editor = editorRef.current;
|
|
230
|
+
const editor = editorRef.current;
|
|
177
231
|
if (!editor) return;
|
|
178
232
|
if (!targetElement) return;
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
...(childFitMinHeight === '' ? {} : { height: childFitMinHeight }),
|
|
185
|
-
};
|
|
186
|
-
const childFitMaxSize =
|
|
187
|
-
childFitMaxWidth === '' && childFitMaxHeight === ''
|
|
188
|
-
? undefined
|
|
189
|
-
: {
|
|
190
|
-
...(childFitMaxWidth === '' ? {} : { width: childFitMaxWidth }),
|
|
191
|
-
...(childFitMaxHeight === '' ? {} : { height: childFitMaxHeight }),
|
|
192
|
-
};
|
|
233
|
+
const parsedGridTemplate = mode === 'grid' ? parseGridTemplate(gridTemplateText) : undefined;
|
|
234
|
+
if (mode === 'grid' && parsedGridTemplate === null) {
|
|
235
|
+
setLastTrigger('invalid grid template JSON');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
193
238
|
const labelReservedSpace =
|
|
194
239
|
mode === 'manual'
|
|
195
240
|
? undefined
|
|
@@ -205,13 +250,18 @@ const AutoLayoutDemo = () => {
|
|
|
205
250
|
? { mode: 'manual' as const }
|
|
206
251
|
: {
|
|
207
252
|
mode,
|
|
253
|
+
autoResize,
|
|
208
254
|
padding,
|
|
209
255
|
gap,
|
|
210
256
|
align: align ?? 'center',
|
|
211
257
|
childFitMainAxis,
|
|
212
258
|
childFitCrossAxis,
|
|
213
|
-
|
|
214
|
-
|
|
259
|
+
...(childMinWidth === '' ? {} : { childMinWidth }),
|
|
260
|
+
...(childMinHeight === '' ? {} : { childMinHeight }),
|
|
261
|
+
...(childMaxWidth === '' ? {} : { childMaxWidth }),
|
|
262
|
+
...(childMaxHeight === '' ? {} : { childMaxHeight }),
|
|
263
|
+
...(mode === 'grid' && parsedGridTemplate ? { gridTemplate: parsedGridTemplate } : {}),
|
|
264
|
+
...(mode === 'grid' ? { gridChildWidthResizeEnabled } : {}),
|
|
215
265
|
labelReservedSpace,
|
|
216
266
|
};
|
|
217
267
|
editor.setElementLayout(targetElement.id, layout);
|
|
@@ -226,139 +276,179 @@ const AutoLayoutDemo = () => {
|
|
|
226
276
|
editor.updateText(label.id, content);
|
|
227
277
|
setLastTrigger('label updated');
|
|
228
278
|
};
|
|
229
|
-
|
|
230
|
-
const handleAddChild = () => {
|
|
231
|
-
const editor = editorRef.current;
|
|
232
|
-
if (!editor) return;
|
|
233
|
-
if (!targetElement) return;
|
|
234
|
-
const offset = nextOffset();
|
|
235
|
-
const sizeBump = (offset % 3) * 8;
|
|
236
|
-
editor.addElement({
|
|
237
|
-
id: `auto-child-${createId()}`,
|
|
238
|
-
position: { x: 16 + offset, y: 16 + offset },
|
|
239
|
-
size: { width: 50 + sizeBump, height: 30 + (offset % 2) * 6 },
|
|
240
|
-
shapeId: demo.defaultElementShapeId ?? 'default',
|
|
241
|
-
parentId: targetElement.id,
|
|
242
|
-
});
|
|
243
|
-
setLastTrigger('child added');
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const handleRemoveChild = () => {
|
|
247
|
-
const editor = editorRef.current;
|
|
248
|
-
if (!editor) return;
|
|
249
|
-
const children = childOrder;
|
|
250
|
-
const target = children[children.length - 1];
|
|
251
|
-
if (target) {
|
|
252
|
-
editor.removeElement(target.id);
|
|
253
|
-
setLastTrigger('child removed');
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
279
|
+
|
|
280
|
+
const handleAddChild = () => {
|
|
281
|
+
const editor = editorRef.current;
|
|
282
|
+
if (!editor) return;
|
|
283
|
+
if (!targetElement) return;
|
|
284
|
+
const offset = nextOffset();
|
|
285
|
+
const sizeBump = (offset % 3) * 8;
|
|
286
|
+
editor.addElement({
|
|
287
|
+
id: `auto-child-${createId()}`,
|
|
288
|
+
position: { x: 16 + offset, y: 16 + offset },
|
|
289
|
+
size: { width: 50 + sizeBump, height: 30 + (offset % 2) * 6 },
|
|
290
|
+
shapeId: demo.defaultElementShapeId ?? 'default',
|
|
291
|
+
parentId: targetElement.id,
|
|
292
|
+
});
|
|
293
|
+
setLastTrigger('child added');
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const handleRemoveChild = () => {
|
|
297
|
+
const editor = editorRef.current;
|
|
298
|
+
if (!editor) return;
|
|
299
|
+
const children = childOrder;
|
|
300
|
+
const target = children[children.length - 1];
|
|
301
|
+
if (target) {
|
|
302
|
+
editor.removeElement(target.id);
|
|
303
|
+
setLastTrigger('child removed');
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const handleFocusResizeLockChild = () => {
|
|
308
|
+
const editor = editorRef.current;
|
|
309
|
+
if (!editor) return;
|
|
310
|
+
setTargetId('layout-grid');
|
|
311
|
+
editor.setSelection(['grid-b']);
|
|
312
|
+
setLastTrigger('focus resize-lock child');
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const handleFocusResizeLockParent = () => {
|
|
316
|
+
const editor = editorRef.current;
|
|
317
|
+
if (!editor) return;
|
|
318
|
+
setTargetId('layout-grid');
|
|
319
|
+
editor.setSelection(['layout-grid']);
|
|
320
|
+
setLastTrigger('focus resize-lock parent');
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const alignmentLabel =
|
|
324
|
+
mode === 'horizontal'
|
|
325
|
+
? 'Vertical align (top/center/bottom)'
|
|
326
|
+
: mode === 'vertical'
|
|
327
|
+
? 'Horizontal align (left/center/right)'
|
|
328
|
+
: 'Cell align (start/center/end)';
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<section>
|
|
332
|
+
<div style={{ marginBottom: 12 }}>
|
|
333
|
+
<h2 style={{ marginTop: 0, marginBottom: 4 }}>{demo.title}</h2>
|
|
264
334
|
<p style={{ marginTop: 0 }}>
|
|
265
|
-
Try horizontal, vertical, nested, and manual layout containers. Select a layout parent directly on the
|
|
266
|
-
or pick it from the list; the controls below always act on the current selection. Drag, resize, add, or
|
|
267
|
-
children to see automatic reflow. Use fit controls
|
|
268
|
-
|
|
335
|
+
Try horizontal, vertical, grid, nested, and manual layout containers. Select a layout parent directly on the
|
|
336
|
+
canvas or pick it from the list; the controls below always act on the current selection. Drag, resize, add, or
|
|
337
|
+
remove children to see automatic reflow. Use fit controls, explicit child min/max constraints, auto-resize
|
|
338
|
+
policy, 12-unit grid templates, and the optional grid child width-resize capability to validate current behavior.
|
|
339
|
+
</p>
|
|
340
|
+
<p style={{ marginTop: 0, marginBottom: 0, fontSize: 13, color: '#333' }}>
|
|
341
|
+
Grid child width resize is disabled by default. With the capability off, select <code>grid-b</code> and drag a
|
|
342
|
+
corner mostly downward: width stays stable while height changes. Turn the capability on to let horizontal drag
|
|
343
|
+
wrap rows automatically from a flat 12-unit slot array, then resize children in 1/12 steps. Any corner can drive width changes, width always grows or shrinks on the right, siblings keep their own widths, and dragging can swap grid item order.
|
|
269
344
|
</p>
|
|
270
345
|
</div>
|
|
271
|
-
<DisplayBoxControls
|
|
272
|
-
actions={demo.actions}
|
|
273
|
-
snapEnabled={controls.snapEnabled}
|
|
274
|
-
selectedLinkRouting={controls.selectedLinkRouting}
|
|
275
|
-
canToggleLinkRouting={controls.canToggleLinkRouting}
|
|
276
|
-
onReload={controls.handleReload}
|
|
277
|
-
onZoomIn={controls.handleZoomIn}
|
|
278
|
-
onZoomOut={controls.handleZoomOut}
|
|
279
|
-
onResetViewport={controls.handleResetViewport}
|
|
280
|
-
onToggleSnap={controls.handleToggleSnap}
|
|
281
|
-
onManualRender={controls.handleManualRender}
|
|
282
|
-
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
283
|
-
onAction={controls.handleAction}
|
|
284
|
-
onExportImage={controls.handleExportImage}
|
|
285
|
-
onClearExportPreview={controls.handleClearExportPreview}
|
|
286
|
-
exportPreviewDataUrl={controls.exportPreviewDataUrl}
|
|
287
|
-
exportError={controls.exportError}
|
|
288
|
-
/>
|
|
289
|
-
|
|
290
|
-
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
291
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center' }}>
|
|
292
|
-
<label htmlFor="parent-select" style={{ fontWeight: 600 }}>
|
|
293
|
-
Target element (follows selection)
|
|
294
|
-
</label>
|
|
295
|
-
<select
|
|
296
|
-
id="parent-select"
|
|
297
|
-
value={targetId}
|
|
298
|
-
onChange={(event) => {
|
|
299
|
-
const nextId = event.target.value;
|
|
300
|
-
setTargetId(nextId);
|
|
301
|
-
if (editorRef.current) {
|
|
302
|
-
editorRef.current.setSelection([nextId]);
|
|
303
|
-
}
|
|
304
|
-
}}
|
|
305
|
-
style={{ padding: '6px 10px', minWidth: 180 }}
|
|
306
|
-
>
|
|
307
|
-
{targetOptions.map((option) => (
|
|
308
|
-
<option key={option.id} value={option.id}>
|
|
309
|
-
{option.label}
|
|
310
|
-
</option>
|
|
311
|
-
))}
|
|
312
|
-
</select>
|
|
313
|
-
|
|
314
|
-
<label htmlFor="mode-select" style={{ fontWeight: 600 }}>
|
|
315
|
-
Layout
|
|
316
|
-
</label>
|
|
317
|
-
<select
|
|
318
|
-
id="mode-select"
|
|
319
|
-
value={mode}
|
|
320
|
-
onChange={(event) => setMode(event.target.value as ElementLayoutMode)}
|
|
321
|
-
style={{ padding: '6px 10px', minWidth: 140 }}
|
|
322
|
-
>
|
|
323
|
-
<option value="manual">Manual</option>
|
|
324
|
-
<option value="horizontal">Horizontal</option>
|
|
325
|
-
<option value="vertical">Vertical</option>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<option value="
|
|
339
|
-
<option value="
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
value={
|
|
349
|
-
onChange={(event) =>
|
|
350
|
-
style={{
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
346
|
+
<DisplayBoxControls
|
|
347
|
+
actions={demo.actions}
|
|
348
|
+
snapEnabled={controls.snapEnabled}
|
|
349
|
+
selectedLinkRouting={controls.selectedLinkRouting}
|
|
350
|
+
canToggleLinkRouting={controls.canToggleLinkRouting}
|
|
351
|
+
onReload={controls.handleReload}
|
|
352
|
+
onZoomIn={controls.handleZoomIn}
|
|
353
|
+
onZoomOut={controls.handleZoomOut}
|
|
354
|
+
onResetViewport={controls.handleResetViewport}
|
|
355
|
+
onToggleSnap={controls.handleToggleSnap}
|
|
356
|
+
onManualRender={controls.handleManualRender}
|
|
357
|
+
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
358
|
+
onAction={controls.handleAction}
|
|
359
|
+
onExportImage={controls.handleExportImage}
|
|
360
|
+
onClearExportPreview={controls.handleClearExportPreview}
|
|
361
|
+
exportPreviewDataUrl={controls.exportPreviewDataUrl}
|
|
362
|
+
exportError={controls.exportError}
|
|
363
|
+
/>
|
|
364
|
+
|
|
365
|
+
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
366
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center' }}>
|
|
367
|
+
<label htmlFor="parent-select" style={{ fontWeight: 600 }}>
|
|
368
|
+
Target element (follows selection)
|
|
369
|
+
</label>
|
|
370
|
+
<select
|
|
371
|
+
id="parent-select"
|
|
372
|
+
value={targetId}
|
|
373
|
+
onChange={(event) => {
|
|
374
|
+
const nextId = event.target.value;
|
|
375
|
+
setTargetId(nextId);
|
|
376
|
+
if (editorRef.current) {
|
|
377
|
+
editorRef.current.setSelection([nextId]);
|
|
378
|
+
}
|
|
379
|
+
}}
|
|
380
|
+
style={{ padding: '6px 10px', minWidth: 180 }}
|
|
381
|
+
>
|
|
382
|
+
{targetOptions.map((option) => (
|
|
383
|
+
<option key={option.id} value={option.id}>
|
|
384
|
+
{option.label}
|
|
385
|
+
</option>
|
|
386
|
+
))}
|
|
387
|
+
</select>
|
|
388
|
+
|
|
389
|
+
<label htmlFor="mode-select" style={{ fontWeight: 600 }}>
|
|
390
|
+
Layout
|
|
391
|
+
</label>
|
|
392
|
+
<select
|
|
393
|
+
id="mode-select"
|
|
394
|
+
value={mode}
|
|
395
|
+
onChange={(event) => setMode(event.target.value as ElementLayoutMode)}
|
|
396
|
+
style={{ padding: '6px 10px', minWidth: 140 }}
|
|
397
|
+
>
|
|
398
|
+
<option value="manual">Manual</option>
|
|
399
|
+
<option value="horizontal">Horizontal</option>
|
|
400
|
+
<option value="vertical">Vertical</option>
|
|
401
|
+
<option value="grid">Grid</option>
|
|
402
|
+
</select>
|
|
403
|
+
|
|
404
|
+
<label htmlFor="align-select" style={{ fontWeight: 600 }}>
|
|
405
|
+
{alignmentLabel}
|
|
406
|
+
</label>
|
|
407
|
+
<select
|
|
408
|
+
id="align-select"
|
|
409
|
+
value={align ?? 'center'}
|
|
410
|
+
onChange={(event) => setAlign(event.target.value as ElementLayout['align'])}
|
|
411
|
+
style={{ padding: '6px 10px', minWidth: 140 }}
|
|
412
|
+
>
|
|
413
|
+
<option value="start">{mode === 'horizontal' ? 'Top' : mode === 'vertical' ? 'Left' : 'Start'}</option>
|
|
414
|
+
<option value="center">Center</option>
|
|
415
|
+
<option value="end">{mode === 'horizontal' ? 'Bottom' : mode === 'vertical' ? 'Right' : 'End'}</option>
|
|
416
|
+
</select>
|
|
417
|
+
|
|
418
|
+
<label htmlFor="auto-resize-select" style={{ fontWeight: 600 }}>
|
|
419
|
+
Auto resize
|
|
420
|
+
</label>
|
|
421
|
+
<select
|
|
422
|
+
id="auto-resize-select"
|
|
423
|
+
value={autoResize}
|
|
424
|
+
onChange={(event) => setAutoResize(event.target.value as ElementLayoutAutoResizeMode)}
|
|
425
|
+
style={{ padding: '6px 10px', minWidth: 130 }}
|
|
426
|
+
disabled={mode === 'manual'}
|
|
427
|
+
>
|
|
428
|
+
<option value="grow">Grow only</option>
|
|
429
|
+
<option value="grow-shrink">Grow + shrink</option>
|
|
430
|
+
</select>
|
|
431
|
+
|
|
432
|
+
<label htmlFor="padding-input" style={{ fontWeight: 600 }}>
|
|
433
|
+
Padding
|
|
434
|
+
</label>
|
|
435
|
+
<input
|
|
436
|
+
id="padding-input"
|
|
437
|
+
type="number"
|
|
438
|
+
value={padding}
|
|
439
|
+
onChange={(event) => setPadding(Number(event.target.value) || 0)}
|
|
440
|
+
style={{ width: 72, padding: '6px 8px' }}
|
|
441
|
+
min={0}
|
|
442
|
+
/>
|
|
443
|
+
|
|
354
444
|
<label htmlFor="gap-input" style={{ fontWeight: 600 }}>
|
|
355
445
|
Gap
|
|
356
446
|
</label>
|
|
357
447
|
<input
|
|
358
448
|
id="gap-input"
|
|
359
|
-
type="number"
|
|
360
|
-
value={gap}
|
|
361
|
-
onChange={(event) => setGap(Number(event.target.value) || 0)}
|
|
449
|
+
type="number"
|
|
450
|
+
value={gap}
|
|
451
|
+
onChange={(event) => setGap(Number(event.target.value) || 0)}
|
|
362
452
|
style={{ width: 72, padding: '6px 8px' }}
|
|
363
453
|
min={0}
|
|
364
454
|
/>
|
|
@@ -371,6 +461,7 @@ const AutoLayoutDemo = () => {
|
|
|
371
461
|
value={childFitMainAxis ?? 'none'}
|
|
372
462
|
onChange={(event) => setChildFitMainAxis(event.target.value as ElementLayout['childFitMainAxis'])}
|
|
373
463
|
style={{ padding: '6px 10px', minWidth: 130 }}
|
|
464
|
+
disabled={mode === 'manual' || mode === 'grid'}
|
|
374
465
|
>
|
|
375
466
|
<option value="none">Off</option>
|
|
376
467
|
<option value="distribute">Distribute</option>
|
|
@@ -384,28 +475,29 @@ const AutoLayoutDemo = () => {
|
|
|
384
475
|
value={childFitCrossAxis ?? 'none'}
|
|
385
476
|
onChange={(event) => setChildFitCrossAxis(event.target.value as ElementLayout['childFitCrossAxis'])}
|
|
386
477
|
style={{ padding: '6px 10px', minWidth: 130 }}
|
|
478
|
+
disabled={mode === 'manual'}
|
|
387
479
|
>
|
|
388
480
|
<option value="none">Off</option>
|
|
389
481
|
<option value="stretch">Stretch</option>
|
|
390
482
|
</select>
|
|
391
483
|
|
|
392
484
|
<label htmlFor="fit-min-width-input" style={{ fontWeight: 600 }}>
|
|
393
|
-
|
|
485
|
+
Child min W/H
|
|
394
486
|
</label>
|
|
395
487
|
<div style={{ display: 'inline-flex', gap: 6 }}>
|
|
396
488
|
<input
|
|
397
489
|
id="fit-min-width-input"
|
|
398
490
|
type="number"
|
|
399
|
-
value={
|
|
400
|
-
onChange={(event) =>
|
|
491
|
+
value={childMinWidth}
|
|
492
|
+
onChange={(event) => setChildMinWidth(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
401
493
|
style={{ width: 64, padding: '6px 8px' }}
|
|
402
494
|
min={0}
|
|
403
495
|
placeholder="w"
|
|
404
496
|
/>
|
|
405
497
|
<input
|
|
406
498
|
type="number"
|
|
407
|
-
value={
|
|
408
|
-
onChange={(event) =>
|
|
499
|
+
value={childMinHeight}
|
|
500
|
+
onChange={(event) => setChildMinHeight(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
409
501
|
style={{ width: 64, padding: '6px 8px' }}
|
|
410
502
|
min={0}
|
|
411
503
|
placeholder="h"
|
|
@@ -413,28 +505,55 @@ const AutoLayoutDemo = () => {
|
|
|
413
505
|
</div>
|
|
414
506
|
|
|
415
507
|
<label htmlFor="fit-max-width-input" style={{ fontWeight: 600 }}>
|
|
416
|
-
|
|
508
|
+
Child max W/H
|
|
417
509
|
</label>
|
|
418
510
|
<div style={{ display: 'inline-flex', gap: 6 }}>
|
|
419
511
|
<input
|
|
420
512
|
id="fit-max-width-input"
|
|
421
513
|
type="number"
|
|
422
|
-
value={
|
|
423
|
-
onChange={(event) =>
|
|
514
|
+
value={childMaxWidth}
|
|
515
|
+
onChange={(event) => setChildMaxWidth(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
424
516
|
style={{ width: 64, padding: '6px 8px' }}
|
|
425
517
|
min={0}
|
|
426
518
|
placeholder="w"
|
|
427
519
|
/>
|
|
428
520
|
<input
|
|
429
521
|
type="number"
|
|
430
|
-
value={
|
|
431
|
-
onChange={(event) =>
|
|
522
|
+
value={childMaxHeight}
|
|
523
|
+
onChange={(event) => setChildMaxHeight(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
432
524
|
style={{ width: 64, padding: '6px 8px' }}
|
|
433
525
|
min={0}
|
|
434
526
|
placeholder="h"
|
|
435
527
|
/>
|
|
436
528
|
</div>
|
|
437
529
|
|
|
530
|
+
<label htmlFor="grid-template-input" style={{ fontWeight: 600 }}>
|
|
531
|
+
Grid template
|
|
532
|
+
</label>
|
|
533
|
+
<input
|
|
534
|
+
id="grid-template-input"
|
|
535
|
+
type="text"
|
|
536
|
+
value={gridTemplateText}
|
|
537
|
+
onChange={(event) => setGridTemplateText(event.target.value)}
|
|
538
|
+
style={{ minWidth: 220, padding: '6px 8px' }}
|
|
539
|
+
placeholder="[12,4,8] or [3,3,6,9]"
|
|
540
|
+
disabled={mode !== 'grid'}
|
|
541
|
+
/>
|
|
542
|
+
|
|
543
|
+
<label htmlFor="grid-child-width-resize-enabled" style={{ fontWeight: 600 }}>
|
|
544
|
+
Grid child width resize
|
|
545
|
+
</label>
|
|
546
|
+
<label style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
|
547
|
+
<input
|
|
548
|
+
id="grid-child-width-resize-enabled"
|
|
549
|
+
type="checkbox"
|
|
550
|
+
checked={gridChildWidthResizeEnabled}
|
|
551
|
+
onChange={(event) => setGridChildWidthResizeEnabled(event.target.checked)}
|
|
552
|
+
disabled={mode !== 'grid'}
|
|
553
|
+
/>
|
|
554
|
+
Enable 12-unit resize on child width drag
|
|
555
|
+
</label>
|
|
556
|
+
|
|
438
557
|
<label htmlFor="label-reserved-mode-select" style={{ fontWeight: 600 }}>
|
|
439
558
|
Label lane
|
|
440
559
|
</label>
|
|
@@ -487,26 +606,26 @@ const AutoLayoutDemo = () => {
|
|
|
487
606
|
<button type="button" onClick={handleApplyLayout} style={{ padding: '6px 12px', fontWeight: 600 }}>
|
|
488
607
|
Apply layout
|
|
489
608
|
</button>
|
|
490
|
-
<button type="button" onClick={handleAddChild} style={{ padding: '6px 10px' }} disabled={!targetElement}>
|
|
491
|
-
Add child
|
|
492
|
-
</button>
|
|
493
|
-
<button type="button" onClick={handleRemoveChild} style={{ padding: '6px 10px' }} disabled={!targetElement}>
|
|
494
|
-
Remove last child
|
|
495
|
-
</button>
|
|
496
|
-
<span
|
|
497
|
-
style={{
|
|
498
|
-
padding: '6px 10px',
|
|
499
|
-
borderRadius: 8,
|
|
500
|
-
background: '#f7f7f8',
|
|
501
|
-
border: '1px solid #e0e0e0',
|
|
502
|
-
display: 'inline-flex',
|
|
503
|
-
gap: 6,
|
|
504
|
-
alignItems: 'center',
|
|
505
|
-
}}
|
|
506
|
-
>
|
|
609
|
+
<button type="button" onClick={handleAddChild} style={{ padding: '6px 10px' }} disabled={!targetElement}>
|
|
610
|
+
Add child
|
|
611
|
+
</button>
|
|
612
|
+
<button type="button" onClick={handleRemoveChild} style={{ padding: '6px 10px' }} disabled={!targetElement}>
|
|
613
|
+
Remove last child
|
|
614
|
+
</button>
|
|
615
|
+
<span
|
|
616
|
+
style={{
|
|
617
|
+
padding: '6px 10px',
|
|
618
|
+
borderRadius: 8,
|
|
619
|
+
background: '#f7f7f8',
|
|
620
|
+
border: '1px solid #e0e0e0',
|
|
621
|
+
display: 'inline-flex',
|
|
622
|
+
gap: 6,
|
|
623
|
+
alignItems: 'center',
|
|
624
|
+
}}
|
|
625
|
+
>
|
|
507
626
|
<span style={{ fontWeight: 600 }}>Target:</span> {targetElement ? targetElement.id : '—'}
|
|
508
|
-
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#1f4d99', display: 'inline-block' }} />
|
|
509
|
-
Last trigger: <strong>{lastTrigger}</strong>
|
|
627
|
+
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#1f4d99', display: 'inline-block' }} />
|
|
628
|
+
Last trigger: <strong>{lastTrigger}</strong>
|
|
510
629
|
</span>
|
|
511
630
|
<button
|
|
512
631
|
type="button"
|
|
@@ -524,80 +643,95 @@ const AutoLayoutDemo = () => {
|
|
|
524
643
|
>
|
|
525
644
|
Label long
|
|
526
645
|
</button>
|
|
646
|
+
<button type="button" onClick={handleFocusResizeLockChild} style={{ padding: '6px 10px' }}>
|
|
647
|
+
Focus lock child
|
|
648
|
+
</button>
|
|
649
|
+
<button type="button" onClick={handleFocusResizeLockParent} style={{ padding: '6px 10px' }}>
|
|
650
|
+
Focus lock parent
|
|
651
|
+
</button>
|
|
527
652
|
</div>
|
|
528
|
-
|
|
529
|
-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
|
530
|
-
<div>
|
|
531
|
-
<div style={{ fontWeight: 600, marginBottom: 6 }}>Child order (
|
|
532
|
-
<ol style={{ marginTop: 0, paddingLeft: 18 }}>
|
|
533
|
-
{childOrder.map((child) => (
|
|
534
|
-
<li key={child.id} style={{ marginBottom: 4, fontSize: 13 }}>
|
|
535
|
-
{child.id} — position ({Math.round(child.position.x)}, {Math.round(child.position.y)}) size (
|
|
536
|
-
{child.size.width}×{child.size.height})
|
|
537
|
-
</li>
|
|
538
|
-
))}
|
|
539
|
-
{childOrder.length === 0 && <li style={{ color: '#555' }}>No children.</li>}
|
|
540
|
-
</ol>
|
|
541
|
-
<p style={{ fontSize: 12, color: '#555', marginTop: 4 }}>
|
|
542
|
-
Drag a child before/after its sibling or resize it. The order above reflects the reflow sequence.
|
|
543
|
-
</p>
|
|
544
|
-
</div>
|
|
545
|
-
<div>
|
|
546
|
-
<div style={{ fontWeight: 600, marginBottom: 6 }}>Scenario tips</div>
|
|
547
|
-
<ul style={{ marginTop: 0, paddingLeft: 18, fontSize: 13 }}>
|
|
653
|
+
|
|
654
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
|
655
|
+
<div>
|
|
656
|
+
<div style={{ fontWeight: 600, marginBottom: 6 }}>Child order (axis or grid row-major)</div>
|
|
657
|
+
<ol style={{ marginTop: 0, paddingLeft: 18 }}>
|
|
658
|
+
{childOrder.map((child) => (
|
|
659
|
+
<li key={child.id} style={{ marginBottom: 4, fontSize: 13 }}>
|
|
660
|
+
{child.id} — position ({Math.round(child.position.x)}, {Math.round(child.position.y)}) size (
|
|
661
|
+
{child.size.width}×{child.size.height})
|
|
662
|
+
</li>
|
|
663
|
+
))}
|
|
664
|
+
{childOrder.length === 0 && <li style={{ color: '#555' }}>No children.</li>}
|
|
665
|
+
</ol>
|
|
666
|
+
<p style={{ fontSize: 12, color: '#555', marginTop: 4 }}>
|
|
667
|
+
Drag a child before/after its sibling or resize it. The order above reflects the reflow sequence.
|
|
668
|
+
</p>
|
|
669
|
+
</div>
|
|
670
|
+
<div>
|
|
671
|
+
<div style={{ fontWeight: 600, marginBottom: 6 }}>Scenario tips</div>
|
|
672
|
+
<ul style={{ marginTop: 0, paddingLeft: 18, fontSize: 13 }}>
|
|
673
|
+
<li>Resize lock: with grid child width resize off, select grid-b and drag diagonally; width should stay fixed.</li>
|
|
674
|
+
<li>Enable grid child width resize, then shrink a full-width grid child; it should snap smaller without flashing, and later siblings should keep their own widths.</li>
|
|
675
|
+
<li>Enable grid child width resize, then grow a child; siblings on the right should keep their own widths and only move down when they no longer fit.</li>
|
|
676
|
+
<li>Try dragging a left resize handle on the first child in a row; the child should still grow, but only to the right.</li>
|
|
677
|
+
<li>Try dragging a grid child across a sibling; the row-major order should swap to match the drag position.</li>
|
|
678
|
+
<li>Whenever enabled child width resize changes row allocation, the last-trigger badge should report grid layout changed.</li>
|
|
679
|
+
<li>Parent resize: select layout-grid, drag corner; parent width and height should both track drag.</li>
|
|
548
680
|
<li>Horizontal: try center vs bottom alignment and larger padding.</li>
|
|
549
681
|
<li>Vertical: see parent grow taller as children stack.</li>
|
|
682
|
+
<li>Grid: apply templates like [12,4,8] and [3,3,6,9]. Rows wrap automatically when the next item cannot fit.</li>
|
|
683
|
+
<li>Auto resize: compare grow-only vs grow-shrink while adding/removing children.</li>
|
|
550
684
|
<li>Label lane: compare none/fixed/flexible and observe children start below the reserved lane.</li>
|
|
551
685
|
<li>Fit main-axis distribute fills inner layout space across siblings.</li>
|
|
552
686
|
<li>Fit cross-axis stretch extends children across inner cross axis.</li>
|
|
553
|
-
<li>
|
|
687
|
+
<li>Child min/max guards cap distributed, stretched, and grid cell sizes.</li>
|
|
554
688
|
<li>Nested: the left column owns its own children and expands the row.</li>
|
|
555
|
-
<li>Deep bounds: stack-overflow widens the nested column and the outer row.</li>
|
|
556
|
-
<li>Re-order: drag along the layout axis then apply layout to keep the new order.</li>
|
|
557
|
-
<li>Select any container on the canvas to target it instantly—controls follow your selection.</li>
|
|
558
|
-
<li>Manual: switch to manual to compare with auto layout.</li>
|
|
559
|
-
</ul>
|
|
560
|
-
</div>
|
|
561
|
-
</div>
|
|
562
|
-
|
|
563
|
-
<div>
|
|
564
|
-
<div style={{ fontWeight: 600, marginBottom: 6 }}>Parent bounds snapshot</div>
|
|
565
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 8 }}>
|
|
566
|
-
{parentSummaries.map((parent) => (
|
|
567
|
-
<div
|
|
568
|
-
key={parent.id}
|
|
569
|
-
style={{
|
|
570
|
-
padding: 10,
|
|
571
|
-
borderRadius: 8,
|
|
572
|
-
border: '1px solid #e0e0e0',
|
|
573
|
-
background: '#fafafa',
|
|
574
|
-
boxShadow: '0 1px 0 rgba(0,0,0,0.04)',
|
|
575
|
-
}}
|
|
576
|
-
>
|
|
577
|
-
<div style={{ fontWeight: 700 }}>{parent.label}</div>
|
|
578
|
-
<div style={{ fontSize: 12, color: '#444', marginTop: 2 }}>
|
|
579
|
-
Layout: <code>{parent.layoutMode}</code> · {parent.childCount} children
|
|
580
|
-
</div>
|
|
581
|
-
<div
|
|
582
|
-
style={{
|
|
583
|
-
marginTop: 4,
|
|
584
|
-
fontFamily:
|
|
585
|
-
'ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace',
|
|
586
|
-
fontSize: 12,
|
|
587
|
-
}}
|
|
588
|
-
>
|
|
589
|
-
{Math.round(parent.size.width)} × {Math.round(parent.size.height)} px
|
|
590
|
-
</div>
|
|
591
|
-
</div>
|
|
592
|
-
))}
|
|
593
|
-
</div>
|
|
594
|
-
</div>
|
|
595
|
-
</div>
|
|
596
|
-
|
|
597
|
-
<DisplayBoxStage containerRef={containerRef} stageStyle={gridStageStyle} />
|
|
598
|
-
</section>
|
|
599
|
-
);
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
export default AutoLayoutDemo;
|
|
603
|
-
|
|
689
|
+
<li>Deep bounds: stack-overflow widens the nested column and the outer row.</li>
|
|
690
|
+
<li>Re-order: drag along the layout axis then apply layout to keep the new order.</li>
|
|
691
|
+
<li>Select any container on the canvas to target it instantly—controls follow your selection.</li>
|
|
692
|
+
<li>Manual: switch to manual to compare with auto layout.</li>
|
|
693
|
+
</ul>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<div>
|
|
698
|
+
<div style={{ fontWeight: 600, marginBottom: 6 }}>Parent bounds snapshot</div>
|
|
699
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 8 }}>
|
|
700
|
+
{parentSummaries.map((parent) => (
|
|
701
|
+
<div
|
|
702
|
+
key={parent.id}
|
|
703
|
+
style={{
|
|
704
|
+
padding: 10,
|
|
705
|
+
borderRadius: 8,
|
|
706
|
+
border: '1px solid #e0e0e0',
|
|
707
|
+
background: '#fafafa',
|
|
708
|
+
boxShadow: '0 1px 0 rgba(0,0,0,0.04)',
|
|
709
|
+
}}
|
|
710
|
+
>
|
|
711
|
+
<div style={{ fontWeight: 700 }}>{parent.label}</div>
|
|
712
|
+
<div style={{ fontSize: 12, color: '#444', marginTop: 2 }}>
|
|
713
|
+
Layout: <code>{parent.layoutMode}</code> · {parent.childCount} children
|
|
714
|
+
</div>
|
|
715
|
+
<div
|
|
716
|
+
style={{
|
|
717
|
+
marginTop: 4,
|
|
718
|
+
fontFamily:
|
|
719
|
+
'ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace',
|
|
720
|
+
fontSize: 12,
|
|
721
|
+
}}
|
|
722
|
+
>
|
|
723
|
+
{Math.round(parent.size.width)} × {Math.round(parent.size.height)} px
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
))}
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
</div>
|
|
730
|
+
|
|
731
|
+
<DisplayBoxStage containerRef={containerRef} stageStyle={gridStageStyle} />
|
|
732
|
+
</section>
|
|
733
|
+
);
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
export default AutoLayoutDemo;
|
|
737
|
+
|