orcasvn-react-diagrams 0.2.0 → 0.2.2
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/README.md +22 -1
- package/ai/api-contract.json +57 -5
- package/ai/invariants.json +5 -3
- package/ai/manifest.json +1 -1
- package/dist/cjs/examples.js +11775 -0
- package/dist/cjs/index.js +3889 -1112
- package/dist/cjs/types/api/createDiagramEditor.d.ts +7 -2
- package/dist/cjs/types/api/types.d.ts +178 -0
- package/dist/cjs/types/displaybox/demos/DeletionEventsDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/ShapeHoverControlsDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/TextLayoutDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/deletionEventsDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/rotatedCreationDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/roundedRectRadiusDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/shapeBorderMovementDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/shapeHoverControlsDemo.d.ts +10 -0
- package/dist/cjs/types/displaybox/demos/textDemo.d.ts +4 -0
- package/dist/cjs/types/displaybox/useDemoEditor.d.ts +5 -2
- package/dist/cjs/types/engine/AutoLayoutService.d.ts +24 -0
- package/dist/cjs/types/engine/DiagramEngine.d.ts +32 -14
- package/dist/cjs/types/engine/EngineCommands.d.ts +4 -1
- package/dist/cjs/types/engine/LinkRoutingService.d.ts +35 -0
- package/dist/cjs/types/engine/MutationPipeline.d.ts +23 -0
- package/dist/cjs/types/engine/TextLayoutService.d.ts +40 -0
- package/dist/cjs/types/examples/index.d.ts +2 -0
- package/dist/cjs/types/measure/textStyleDefaults.d.ts +9 -0
- package/dist/cjs/types/models/DiagramModel.d.ts +1 -0
- package/dist/cjs/types/models/ElementModel.d.ts +1 -0
- package/dist/cjs/types/models/PortModel.d.ts +3 -0
- package/dist/cjs/types/models/TextModel.d.ts +8 -0
- package/dist/cjs/types/renderer/RenderTypes.d.ts +34 -1
- package/dist/cjs/types/renderer/konva/KonvaHitTester.d.ts +1 -1
- package/dist/cjs/types/renderer/konva/KonvaInteraction.d.ts +53 -3
- package/dist/cjs/types/renderer/konva/KonvaNodeFactory.d.ts +18 -1
- package/dist/cjs/types/renderer/konva/KonvaRenderer.d.ts +49 -2
- package/dist/cjs/types/shapes/BuiltInShapes.d.ts +107 -0
- package/dist/cjs/types/shapes/__tests__/BuiltInShapes.test.d.ts +1 -0
- package/dist/cjs/types/shapes/index.d.ts +1 -0
- package/dist/cjs/types/utils/__tests__/borderGeometry.test.d.ts +1 -0
- package/dist/cjs/types/utils/borderGeometry.d.ts +6 -0
- package/dist/cjs/types/utils/geometry.d.ts +22 -0
- package/dist/esm/examples.js +11767 -0
- package/dist/esm/examples.js.map +1 -0
- package/dist/esm/index.js +3890 -1113
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/api/createDiagramEditor.d.ts +7 -2
- package/dist/esm/types/api/types.d.ts +178 -0
- package/dist/esm/types/displaybox/demos/DeletionEventsDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/ShapeHoverControlsDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/TextLayoutDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/deletionEventsDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/rotatedCreationDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/roundedRectRadiusDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/shapeBorderMovementDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/shapeHoverControlsDemo.d.ts +10 -0
- package/dist/esm/types/displaybox/demos/textDemo.d.ts +4 -0
- package/dist/esm/types/displaybox/useDemoEditor.d.ts +5 -2
- package/dist/esm/types/engine/AutoLayoutService.d.ts +24 -0
- package/dist/esm/types/engine/DiagramEngine.d.ts +32 -14
- package/dist/esm/types/engine/EngineCommands.d.ts +4 -1
- package/dist/esm/types/engine/LinkRoutingService.d.ts +35 -0
- package/dist/esm/types/engine/MutationPipeline.d.ts +23 -0
- package/dist/esm/types/engine/TextLayoutService.d.ts +40 -0
- package/dist/esm/types/examples/index.d.ts +2 -0
- package/dist/esm/types/measure/textStyleDefaults.d.ts +9 -0
- package/dist/esm/types/models/DiagramModel.d.ts +1 -0
- package/dist/esm/types/models/ElementModel.d.ts +1 -0
- package/dist/esm/types/models/PortModel.d.ts +3 -0
- package/dist/esm/types/models/TextModel.d.ts +8 -0
- package/dist/esm/types/renderer/RenderTypes.d.ts +34 -1
- package/dist/esm/types/renderer/konva/KonvaHitTester.d.ts +1 -1
- package/dist/esm/types/renderer/konva/KonvaInteraction.d.ts +53 -3
- package/dist/esm/types/renderer/konva/KonvaNodeFactory.d.ts +18 -1
- package/dist/esm/types/renderer/konva/KonvaRenderer.d.ts +49 -2
- package/dist/esm/types/shapes/BuiltInShapes.d.ts +107 -0
- package/dist/esm/types/shapes/__tests__/BuiltInShapes.test.d.ts +1 -0
- package/dist/esm/types/shapes/index.d.ts +1 -0
- package/dist/esm/types/utils/__tests__/borderGeometry.test.d.ts +1 -0
- package/dist/esm/types/utils/borderGeometry.d.ts +6 -0
- package/dist/esm/types/utils/geometry.d.ts +22 -0
- package/dist/examples.d.ts +532 -0
- package/dist/index.d.ts +233 -2
- package/docs/API_CONTRACT.md +59 -3
- package/docs/ARCHITECTURE.md +1 -0
- package/docs/CAPABILITIES.md +3 -1
- package/docs/COMMANDS_EVENTS.md +5 -0
- package/docs/DOCUMENTATION_WORKFLOW.md +6 -8
- package/docs/INTEGRATION_PLAYBOOK.md +2 -0
- package/docs/PORTING_CHECKLIST.md +1 -0
- package/docs/STATE_INVARIANTS.md +4 -0
- package/package.json +20 -10
- package/src/displaybox/demos/AutoLayoutDemoTab.tsx +501 -0
- package/src/displaybox/demos/DeletionEventsDemoTab.tsx +147 -0
- package/src/displaybox/demos/EngineEventsDemoTab.tsx +151 -0
- package/src/displaybox/demos/EventHandlersDemoTab.tsx +110 -0
- package/src/displaybox/demos/ExternalDragDropDemoTab.tsx +261 -0
- package/src/displaybox/demos/LinkCancelDemoTab.tsx +238 -0
- package/src/displaybox/demos/ObstacleRoutingDemoTab.tsx +30 -0
- package/src/displaybox/demos/ShapeHoverControlsDemoTab.tsx +558 -0
- package/src/displaybox/demos/SimpleDemo.tsx +73 -0
- package/src/displaybox/demos/SvgPathDemoTab.tsx +327 -0
- package/src/displaybox/demos/TextLayoutDemoTab.tsx +386 -0
- package/src/displaybox/demos/autoLayoutDemo.ts +111 -0
- package/src/displaybox/demos/basicDemo.ts +131 -0
- package/src/displaybox/demos/childConstraintsDemo.ts +65 -0
- package/src/displaybox/demos/customDemo.ts +59 -0
- package/src/displaybox/demos/deletionEventsDemo.ts +91 -0
- package/src/displaybox/demos/engineEventsDemo.ts +64 -0
- package/src/displaybox/demos/eventHandlersDemo.ts +41 -0
- package/src/displaybox/demos/externalDragDropDemo.ts +28 -0
- package/src/displaybox/demos/gridOverlayDemo.ts +50 -0
- package/src/displaybox/demos/index.tsx +217 -0
- package/src/displaybox/demos/linkBendHandlesDemo.ts +143 -0
- package/src/displaybox/demos/linkCancelDemo.ts +56 -0
- package/src/displaybox/demos/linkPortCreationDemo.ts +46 -0
- package/src/displaybox/demos/multiLevelTreeDemo.ts +120 -0
- package/src/displaybox/demos/multipleElementsDemo.ts +62 -0
- package/src/displaybox/demos/nestedDemo.ts +78 -0
- package/src/displaybox/demos/obstacleRoutingDemo.ts +176 -0
- package/src/displaybox/demos/portBorderDemo.ts +98 -0
- package/src/displaybox/demos/portConstraintsDemo.ts +175 -0
- package/src/displaybox/demos/rotatedCreationDemo.ts +185 -0
- package/src/displaybox/demos/roundedRectRadiusDemo.ts +93 -0
- package/src/displaybox/demos/routingDemo.ts +57 -0
- package/src/displaybox/demos/selectionDemo.ts +49 -0
- package/src/displaybox/demos/shapeBorderMovementDemo.ts +126 -0
- package/src/displaybox/demos/shapeGalleryDemo.ts +73 -0
- package/src/displaybox/demos/shapeHoverControlsDemo.ts +172 -0
- package/src/displaybox/demos/shared.ts +161 -0
- package/src/displaybox/demos/svgPathDemo.ts +71 -0
- package/src/displaybox/demos/textDemo.ts +62 -0
- package/src/displaybox/types.ts +66 -0
- package/src/examples/index.ts +21 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import type { ElementLayout, ElementLayoutMode } from '../../api';
|
|
3
|
+
import { createId } from '../../utils/ids';
|
|
4
|
+
import DisplayBoxControls from '../DisplayBoxControls';
|
|
5
|
+
import DisplayBoxStage from '../DisplayBoxStage';
|
|
6
|
+
import useDemoControls from '../useDemoControls';
|
|
7
|
+
import useDemoEditor from '../useDemoEditor';
|
|
8
|
+
import useOffsetSequence from '../useOffsetSequence';
|
|
9
|
+
import type { DemoActionHelpers } from '../types';
|
|
10
|
+
import { autoLayoutDemoConfig } from './autoLayoutDemo';
|
|
11
|
+
import { gridStageStyle } from './shared';
|
|
12
|
+
|
|
13
|
+
const parentOptions = [
|
|
14
|
+
{ id: 'layout-row', label: 'Horizontal layout' },
|
|
15
|
+
{ id: 'layout-column', label: 'Vertical layout' },
|
|
16
|
+
{ id: 'layout-nested', label: 'Nested layout' },
|
|
17
|
+
{ id: 'layout-manual', label: 'Manual (compare)' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const AutoLayoutDemo = () => {
|
|
21
|
+
const demo = autoLayoutDemoConfig;
|
|
22
|
+
const { containerRef, editorRef, diagramState, selection, snapEnabled, setSnapEnabled } = useDemoEditor({
|
|
23
|
+
createState: demo.createState,
|
|
24
|
+
elementShapes: demo.elementShapes,
|
|
25
|
+
portShapes: demo.portShapes,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const nextOffset = useOffsetSequence();
|
|
29
|
+
const actionHelpers: DemoActionHelpers = useMemo(() => ({ nextOffset }), [nextOffset]);
|
|
30
|
+
|
|
31
|
+
const controls = useDemoControls({
|
|
32
|
+
demo,
|
|
33
|
+
editorRef,
|
|
34
|
+
diagramState,
|
|
35
|
+
selection,
|
|
36
|
+
snapEnabled,
|
|
37
|
+
setSnapEnabled,
|
|
38
|
+
actionHelpers,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const [targetId, setTargetId] = useState<string>(parentOptions[0].id);
|
|
42
|
+
const [mode, setMode] = useState<ElementLayoutMode>('horizontal');
|
|
43
|
+
const [align, setAlign] = useState<ElementLayout['align']>('center');
|
|
44
|
+
const [padding, setPadding] = useState<number>(12);
|
|
45
|
+
const [gap, setGap] = useState<number>(12);
|
|
46
|
+
const [childFitMainAxis, setChildFitMainAxis] = useState<ElementLayout['childFitMainAxis']>('none');
|
|
47
|
+
const [childFitCrossAxis, setChildFitCrossAxis] = useState<ElementLayout['childFitCrossAxis']>('none');
|
|
48
|
+
const [childFitMinWidth, setChildFitMinWidth] = useState<number | ''>('');
|
|
49
|
+
const [childFitMinHeight, setChildFitMinHeight] = useState<number | ''>('');
|
|
50
|
+
const [childFitMaxWidth, setChildFitMaxWidth] = useState<number | ''>('');
|
|
51
|
+
const [childFitMaxHeight, setChildFitMaxHeight] = useState<number | ''>('');
|
|
52
|
+
const [lastTrigger, setLastTrigger] = useState<string>('None yet');
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const editor = editorRef.current;
|
|
56
|
+
if (!editor) return undefined;
|
|
57
|
+
const disposers = [
|
|
58
|
+
editor.on('elementMoved', () => setLastTrigger('child moved')),
|
|
59
|
+
editor.on('elementResized', () => setLastTrigger('child resized')),
|
|
60
|
+
editor.on('elementDeleted', () => setLastTrigger('child removed')),
|
|
61
|
+
editor.on('change', () => setLastTrigger((prev) => prev)),
|
|
62
|
+
];
|
|
63
|
+
return () => disposers.forEach((off) => off && off());
|
|
64
|
+
}, [editorRef]);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!diagramState) return;
|
|
68
|
+
if (selection.length === 1) {
|
|
69
|
+
const selectedId = selection[0];
|
|
70
|
+
const exists = diagramState.elements.some((el) => el.id === selectedId);
|
|
71
|
+
if (exists && selectedId !== targetId) {
|
|
72
|
+
setTargetId(selectedId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}, [selection, diagramState, targetId]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!diagramState) return;
|
|
79
|
+
const exists = diagramState.elements.some((el) => el.id === targetId);
|
|
80
|
+
if (!exists) {
|
|
81
|
+
setTargetId(parentOptions[0].id);
|
|
82
|
+
}
|
|
83
|
+
}, [diagramState, targetId]);
|
|
84
|
+
|
|
85
|
+
const targetElement = useMemo(
|
|
86
|
+
() => diagramState?.elements.find((el) => el.id === targetId),
|
|
87
|
+
[diagramState, targetId],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const targetOptions = useMemo(() => {
|
|
91
|
+
const seen = new Set<string>();
|
|
92
|
+
const options: Array<{ id: string; label: string }> = [];
|
|
93
|
+
parentOptions.forEach((option) => {
|
|
94
|
+
if (!seen.has(option.id)) {
|
|
95
|
+
options.push(option);
|
|
96
|
+
seen.add(option.id);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (diagramState) {
|
|
100
|
+
diagramState.elements
|
|
101
|
+
.filter((el) => (el.layout && el.layout.mode !== 'manual') || el.parentId)
|
|
102
|
+
.forEach((el) => {
|
|
103
|
+
if (!seen.has(el.id)) {
|
|
104
|
+
const modeLabel = el.layout?.mode ?? 'manual';
|
|
105
|
+
options.push({ id: el.id, label: `${el.id} (${modeLabel})` });
|
|
106
|
+
seen.add(el.id);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (targetElement && !seen.has(targetElement.id)) {
|
|
111
|
+
options.push({ id: targetElement.id, label: `${targetElement.id} (selected)` });
|
|
112
|
+
}
|
|
113
|
+
return options;
|
|
114
|
+
}, [diagramState, targetElement]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const parent = diagramState?.elements.find((el) => el.id === targetId);
|
|
118
|
+
if (!parent) return;
|
|
119
|
+
const layout = parent.layout;
|
|
120
|
+
setMode(layout?.mode ?? 'manual');
|
|
121
|
+
setAlign(layout?.align ?? 'center');
|
|
122
|
+
const paddingValue =
|
|
123
|
+
typeof layout?.padding === 'number'
|
|
124
|
+
? layout.padding
|
|
125
|
+
: layout?.padding
|
|
126
|
+
? Math.max(layout.padding.x ?? 0, layout.padding.y ?? 0)
|
|
127
|
+
: 12;
|
|
128
|
+
setPadding(paddingValue ?? 12);
|
|
129
|
+
setGap(layout?.gap ?? 12);
|
|
130
|
+
setChildFitMainAxis(layout?.childFitMainAxis ?? 'none');
|
|
131
|
+
setChildFitCrossAxis(layout?.childFitCrossAxis ?? 'none');
|
|
132
|
+
setChildFitMinWidth(layout?.childFitMinSize?.width ?? '');
|
|
133
|
+
setChildFitMinHeight(layout?.childFitMinSize?.height ?? '');
|
|
134
|
+
setChildFitMaxWidth(layout?.childFitMaxSize?.width ?? '');
|
|
135
|
+
setChildFitMaxHeight(layout?.childFitMaxSize?.height ?? '');
|
|
136
|
+
}, [diagramState, targetId]);
|
|
137
|
+
|
|
138
|
+
const childOrder = useMemo(() => {
|
|
139
|
+
if (!diagramState) return [];
|
|
140
|
+
const children = diagramState.elements.filter((el) => el.parentId === targetId);
|
|
141
|
+
const parent = diagramState.elements.find((el) => el.id === targetId);
|
|
142
|
+
const axis = parent?.layout?.mode === 'vertical' ? 'y' : 'x';
|
|
143
|
+
return [...children].sort((a, b) => {
|
|
144
|
+
const aPos = axis === 'y' ? a.position.y : a.position.x;
|
|
145
|
+
const bPos = axis === 'y' ? b.position.y : b.position.x;
|
|
146
|
+
if (aPos === bPos) return a.id.localeCompare(b.id);
|
|
147
|
+
return aPos - bPos;
|
|
148
|
+
});
|
|
149
|
+
}, [diagramState, targetId]);
|
|
150
|
+
|
|
151
|
+
const parentSummaries = useMemo(() => {
|
|
152
|
+
return parentOptions.map((option) => {
|
|
153
|
+
const parent = diagramState?.elements.find((el) => el.id === option.id);
|
|
154
|
+
const childCount = diagramState?.elements.filter((el) => el.parentId === option.id).length ?? 0;
|
|
155
|
+
return {
|
|
156
|
+
...option,
|
|
157
|
+
layoutMode: parent?.layout?.mode ?? 'manual',
|
|
158
|
+
size: parent?.size ?? { width: 0, height: 0 },
|
|
159
|
+
childCount,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}, [diagramState]);
|
|
163
|
+
|
|
164
|
+
const handleApplyLayout = () => {
|
|
165
|
+
const editor = editorRef.current;
|
|
166
|
+
if (!editor) return;
|
|
167
|
+
if (!targetElement) return;
|
|
168
|
+
const childFitMinSize =
|
|
169
|
+
childFitMinWidth === '' && childFitMinHeight === ''
|
|
170
|
+
? undefined
|
|
171
|
+
: {
|
|
172
|
+
...(childFitMinWidth === '' ? {} : { width: childFitMinWidth }),
|
|
173
|
+
...(childFitMinHeight === '' ? {} : { height: childFitMinHeight }),
|
|
174
|
+
};
|
|
175
|
+
const childFitMaxSize =
|
|
176
|
+
childFitMaxWidth === '' && childFitMaxHeight === ''
|
|
177
|
+
? undefined
|
|
178
|
+
: {
|
|
179
|
+
...(childFitMaxWidth === '' ? {} : { width: childFitMaxWidth }),
|
|
180
|
+
...(childFitMaxHeight === '' ? {} : { height: childFitMaxHeight }),
|
|
181
|
+
};
|
|
182
|
+
const layout =
|
|
183
|
+
mode === 'manual'
|
|
184
|
+
? { mode: 'manual' as const }
|
|
185
|
+
: {
|
|
186
|
+
mode,
|
|
187
|
+
padding,
|
|
188
|
+
gap,
|
|
189
|
+
align: align ?? 'center',
|
|
190
|
+
childFitMainAxis,
|
|
191
|
+
childFitCrossAxis,
|
|
192
|
+
childFitMinSize,
|
|
193
|
+
childFitMaxSize,
|
|
194
|
+
};
|
|
195
|
+
editor.setElementLayout(targetElement.id, layout);
|
|
196
|
+
setLastTrigger(`layout applied (${mode})`);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handleAddChild = () => {
|
|
200
|
+
const editor = editorRef.current;
|
|
201
|
+
if (!editor) return;
|
|
202
|
+
if (!targetElement) return;
|
|
203
|
+
const offset = nextOffset();
|
|
204
|
+
const sizeBump = (offset % 3) * 8;
|
|
205
|
+
editor.addElement({
|
|
206
|
+
id: `auto-child-${createId()}`,
|
|
207
|
+
position: { x: 16 + offset, y: 16 + offset },
|
|
208
|
+
size: { width: 50 + sizeBump, height: 30 + (offset % 2) * 6 },
|
|
209
|
+
shapeId: demo.defaultElementShapeId ?? 'default',
|
|
210
|
+
parentId: targetElement.id,
|
|
211
|
+
});
|
|
212
|
+
setLastTrigger('child added');
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const handleRemoveChild = () => {
|
|
216
|
+
const editor = editorRef.current;
|
|
217
|
+
if (!editor) return;
|
|
218
|
+
const children = childOrder;
|
|
219
|
+
const target = children[children.length - 1];
|
|
220
|
+
if (target) {
|
|
221
|
+
editor.removeElement(target.id);
|
|
222
|
+
setLastTrigger('child removed');
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const alignmentLabel =
|
|
227
|
+
mode === 'horizontal' ? 'Vertical align (top/center/bottom)' : 'Horizontal align (left/center/right)';
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<section>
|
|
231
|
+
<div style={{ marginBottom: 12 }}>
|
|
232
|
+
<h2 style={{ marginTop: 0, marginBottom: 4 }}>{demo.title}</h2>
|
|
233
|
+
<p style={{ marginTop: 0 }}>
|
|
234
|
+
Try horizontal, vertical, nested, and manual layout containers. Select a layout parent directly on the canvas
|
|
235
|
+
or pick it from the list; the controls below always act on the current selection. Drag, resize, add, or remove
|
|
236
|
+
children to see automatic reflow. Use fit controls to distribute/stretch child sizes with optional min/max
|
|
237
|
+
guards. Nested column shows fit propagation boundaries to its parent row.
|
|
238
|
+
</p>
|
|
239
|
+
</div>
|
|
240
|
+
<DisplayBoxControls
|
|
241
|
+
actions={demo.actions}
|
|
242
|
+
snapEnabled={controls.snapEnabled}
|
|
243
|
+
selectedLinkRouting={controls.selectedLinkRouting}
|
|
244
|
+
canToggleLinkRouting={controls.canToggleLinkRouting}
|
|
245
|
+
onReload={controls.handleReload}
|
|
246
|
+
onZoomIn={controls.handleZoomIn}
|
|
247
|
+
onZoomOut={controls.handleZoomOut}
|
|
248
|
+
onResetViewport={controls.handleResetViewport}
|
|
249
|
+
onToggleSnap={controls.handleToggleSnap}
|
|
250
|
+
onManualRender={controls.handleManualRender}
|
|
251
|
+
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
252
|
+
onAction={controls.handleAction}
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
256
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center' }}>
|
|
257
|
+
<label htmlFor="parent-select" style={{ fontWeight: 600 }}>
|
|
258
|
+
Target element (follows selection)
|
|
259
|
+
</label>
|
|
260
|
+
<select
|
|
261
|
+
id="parent-select"
|
|
262
|
+
value={targetId}
|
|
263
|
+
onChange={(event) => {
|
|
264
|
+
const nextId = event.target.value;
|
|
265
|
+
setTargetId(nextId);
|
|
266
|
+
if (editorRef.current) {
|
|
267
|
+
editorRef.current.setSelection([nextId]);
|
|
268
|
+
}
|
|
269
|
+
}}
|
|
270
|
+
style={{ padding: '6px 10px', minWidth: 180 }}
|
|
271
|
+
>
|
|
272
|
+
{targetOptions.map((option) => (
|
|
273
|
+
<option key={option.id} value={option.id}>
|
|
274
|
+
{option.label}
|
|
275
|
+
</option>
|
|
276
|
+
))}
|
|
277
|
+
</select>
|
|
278
|
+
|
|
279
|
+
<label htmlFor="mode-select" style={{ fontWeight: 600 }}>
|
|
280
|
+
Layout
|
|
281
|
+
</label>
|
|
282
|
+
<select
|
|
283
|
+
id="mode-select"
|
|
284
|
+
value={mode}
|
|
285
|
+
onChange={(event) => setMode(event.target.value as ElementLayoutMode)}
|
|
286
|
+
style={{ padding: '6px 10px', minWidth: 140 }}
|
|
287
|
+
>
|
|
288
|
+
<option value="manual">Manual</option>
|
|
289
|
+
<option value="horizontal">Horizontal</option>
|
|
290
|
+
<option value="vertical">Vertical</option>
|
|
291
|
+
</select>
|
|
292
|
+
|
|
293
|
+
<label htmlFor="align-select" style={{ fontWeight: 600 }}>
|
|
294
|
+
{alignmentLabel}
|
|
295
|
+
</label>
|
|
296
|
+
<select
|
|
297
|
+
id="align-select"
|
|
298
|
+
value={align ?? 'center'}
|
|
299
|
+
onChange={(event) => setAlign(event.target.value as ElementLayout['align'])}
|
|
300
|
+
style={{ padding: '6px 10px', minWidth: 140 }}
|
|
301
|
+
>
|
|
302
|
+
<option value="start">{mode === 'horizontal' ? 'Top' : 'Left'}</option>
|
|
303
|
+
<option value="center">Center</option>
|
|
304
|
+
<option value="end">{mode === 'horizontal' ? 'Bottom' : 'Right'}</option>
|
|
305
|
+
</select>
|
|
306
|
+
|
|
307
|
+
<label htmlFor="padding-input" style={{ fontWeight: 600 }}>
|
|
308
|
+
Padding
|
|
309
|
+
</label>
|
|
310
|
+
<input
|
|
311
|
+
id="padding-input"
|
|
312
|
+
type="number"
|
|
313
|
+
value={padding}
|
|
314
|
+
onChange={(event) => setPadding(Number(event.target.value) || 0)}
|
|
315
|
+
style={{ width: 72, padding: '6px 8px' }}
|
|
316
|
+
min={0}
|
|
317
|
+
/>
|
|
318
|
+
|
|
319
|
+
<label htmlFor="gap-input" style={{ fontWeight: 600 }}>
|
|
320
|
+
Gap
|
|
321
|
+
</label>
|
|
322
|
+
<input
|
|
323
|
+
id="gap-input"
|
|
324
|
+
type="number"
|
|
325
|
+
value={gap}
|
|
326
|
+
onChange={(event) => setGap(Number(event.target.value) || 0)}
|
|
327
|
+
style={{ width: 72, padding: '6px 8px' }}
|
|
328
|
+
min={0}
|
|
329
|
+
/>
|
|
330
|
+
|
|
331
|
+
<label htmlFor="fit-main-select" style={{ fontWeight: 600 }}>
|
|
332
|
+
Main-axis fit
|
|
333
|
+
</label>
|
|
334
|
+
<select
|
|
335
|
+
id="fit-main-select"
|
|
336
|
+
value={childFitMainAxis ?? 'none'}
|
|
337
|
+
onChange={(event) => setChildFitMainAxis(event.target.value as ElementLayout['childFitMainAxis'])}
|
|
338
|
+
style={{ padding: '6px 10px', minWidth: 130 }}
|
|
339
|
+
>
|
|
340
|
+
<option value="none">Off</option>
|
|
341
|
+
<option value="distribute">Distribute</option>
|
|
342
|
+
</select>
|
|
343
|
+
|
|
344
|
+
<label htmlFor="fit-cross-select" style={{ fontWeight: 600 }}>
|
|
345
|
+
Cross-axis fit
|
|
346
|
+
</label>
|
|
347
|
+
<select
|
|
348
|
+
id="fit-cross-select"
|
|
349
|
+
value={childFitCrossAxis ?? 'none'}
|
|
350
|
+
onChange={(event) => setChildFitCrossAxis(event.target.value as ElementLayout['childFitCrossAxis'])}
|
|
351
|
+
style={{ padding: '6px 10px', minWidth: 130 }}
|
|
352
|
+
>
|
|
353
|
+
<option value="none">Off</option>
|
|
354
|
+
<option value="stretch">Stretch</option>
|
|
355
|
+
</select>
|
|
356
|
+
|
|
357
|
+
<label htmlFor="fit-min-width-input" style={{ fontWeight: 600 }}>
|
|
358
|
+
Min W/H
|
|
359
|
+
</label>
|
|
360
|
+
<div style={{ display: 'inline-flex', gap: 6 }}>
|
|
361
|
+
<input
|
|
362
|
+
id="fit-min-width-input"
|
|
363
|
+
type="number"
|
|
364
|
+
value={childFitMinWidth}
|
|
365
|
+
onChange={(event) => setChildFitMinWidth(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
366
|
+
style={{ width: 64, padding: '6px 8px' }}
|
|
367
|
+
min={0}
|
|
368
|
+
placeholder="w"
|
|
369
|
+
/>
|
|
370
|
+
<input
|
|
371
|
+
type="number"
|
|
372
|
+
value={childFitMinHeight}
|
|
373
|
+
onChange={(event) => setChildFitMinHeight(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
374
|
+
style={{ width: 64, padding: '6px 8px' }}
|
|
375
|
+
min={0}
|
|
376
|
+
placeholder="h"
|
|
377
|
+
/>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<label htmlFor="fit-max-width-input" style={{ fontWeight: 600 }}>
|
|
381
|
+
Max W/H
|
|
382
|
+
</label>
|
|
383
|
+
<div style={{ display: 'inline-flex', gap: 6 }}>
|
|
384
|
+
<input
|
|
385
|
+
id="fit-max-width-input"
|
|
386
|
+
type="number"
|
|
387
|
+
value={childFitMaxWidth}
|
|
388
|
+
onChange={(event) => setChildFitMaxWidth(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
389
|
+
style={{ width: 64, padding: '6px 8px' }}
|
|
390
|
+
min={0}
|
|
391
|
+
placeholder="w"
|
|
392
|
+
/>
|
|
393
|
+
<input
|
|
394
|
+
type="number"
|
|
395
|
+
value={childFitMaxHeight}
|
|
396
|
+
onChange={(event) => setChildFitMaxHeight(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
397
|
+
style={{ width: 64, padding: '6px 8px' }}
|
|
398
|
+
min={0}
|
|
399
|
+
placeholder="h"
|
|
400
|
+
/>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<button type="button" onClick={handleApplyLayout} style={{ padding: '6px 12px', fontWeight: 600 }}>
|
|
404
|
+
Apply layout
|
|
405
|
+
</button>
|
|
406
|
+
<button type="button" onClick={handleAddChild} style={{ padding: '6px 10px' }} disabled={!targetElement}>
|
|
407
|
+
Add child
|
|
408
|
+
</button>
|
|
409
|
+
<button type="button" onClick={handleRemoveChild} style={{ padding: '6px 10px' }} disabled={!targetElement}>
|
|
410
|
+
Remove last child
|
|
411
|
+
</button>
|
|
412
|
+
<span
|
|
413
|
+
style={{
|
|
414
|
+
padding: '6px 10px',
|
|
415
|
+
borderRadius: 8,
|
|
416
|
+
background: '#f7f7f8',
|
|
417
|
+
border: '1px solid #e0e0e0',
|
|
418
|
+
display: 'inline-flex',
|
|
419
|
+
gap: 6,
|
|
420
|
+
alignItems: 'center',
|
|
421
|
+
}}
|
|
422
|
+
>
|
|
423
|
+
<span style={{ fontWeight: 600 }}>Target:</span> {targetElement ? targetElement.id : '—'}
|
|
424
|
+
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#1f4d99', display: 'inline-block' }} />
|
|
425
|
+
Last trigger: <strong>{lastTrigger}</strong>
|
|
426
|
+
</span>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
|
430
|
+
<div>
|
|
431
|
+
<div style={{ fontWeight: 600, marginBottom: 6 }}>Child order (based on layout axis)</div>
|
|
432
|
+
<ol style={{ marginTop: 0, paddingLeft: 18 }}>
|
|
433
|
+
{childOrder.map((child) => (
|
|
434
|
+
<li key={child.id} style={{ marginBottom: 4, fontSize: 13 }}>
|
|
435
|
+
{child.id} — position ({Math.round(child.position.x)}, {Math.round(child.position.y)}) size (
|
|
436
|
+
{child.size.width}×{child.size.height})
|
|
437
|
+
</li>
|
|
438
|
+
))}
|
|
439
|
+
{childOrder.length === 0 && <li style={{ color: '#555' }}>No children.</li>}
|
|
440
|
+
</ol>
|
|
441
|
+
<p style={{ fontSize: 12, color: '#555', marginTop: 4 }}>
|
|
442
|
+
Drag a child before/after its sibling or resize it. The order above reflects the reflow sequence.
|
|
443
|
+
</p>
|
|
444
|
+
</div>
|
|
445
|
+
<div>
|
|
446
|
+
<div style={{ fontWeight: 600, marginBottom: 6 }}>Scenario tips</div>
|
|
447
|
+
<ul style={{ marginTop: 0, paddingLeft: 18, fontSize: 13 }}>
|
|
448
|
+
<li>Horizontal: try center vs bottom alignment and larger padding.</li>
|
|
449
|
+
<li>Vertical: see parent grow taller as children stack.</li>
|
|
450
|
+
<li>Fit main-axis distribute fills inner layout space across siblings.</li>
|
|
451
|
+
<li>Fit cross-axis stretch extends children across inner cross axis.</li>
|
|
452
|
+
<li>Min/max fit guards cap distributed or stretched child sizes.</li>
|
|
453
|
+
<li>Nested: the left column owns its own children and expands the row.</li>
|
|
454
|
+
<li>Deep bounds: stack-overflow widens the nested column and the outer row.</li>
|
|
455
|
+
<li>Re-order: drag along the layout axis then apply layout to keep the new order.</li>
|
|
456
|
+
<li>Select any container on the canvas to target it instantly—controls follow your selection.</li>
|
|
457
|
+
<li>Manual: switch to manual to compare with auto layout.</li>
|
|
458
|
+
</ul>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
<div>
|
|
463
|
+
<div style={{ fontWeight: 600, marginBottom: 6 }}>Parent bounds snapshot</div>
|
|
464
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 8 }}>
|
|
465
|
+
{parentSummaries.map((parent) => (
|
|
466
|
+
<div
|
|
467
|
+
key={parent.id}
|
|
468
|
+
style={{
|
|
469
|
+
padding: 10,
|
|
470
|
+
borderRadius: 8,
|
|
471
|
+
border: '1px solid #e0e0e0',
|
|
472
|
+
background: '#fafafa',
|
|
473
|
+
boxShadow: '0 1px 0 rgba(0,0,0,0.04)',
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
<div style={{ fontWeight: 700 }}>{parent.label}</div>
|
|
477
|
+
<div style={{ fontSize: 12, color: '#444', marginTop: 2 }}>
|
|
478
|
+
Layout: <code>{parent.layoutMode}</code> · {parent.childCount} children
|
|
479
|
+
</div>
|
|
480
|
+
<div
|
|
481
|
+
style={{
|
|
482
|
+
marginTop: 4,
|
|
483
|
+
fontFamily:
|
|
484
|
+
'ui-monospace, SFMono-Regular, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace',
|
|
485
|
+
fontSize: 12,
|
|
486
|
+
}}
|
|
487
|
+
>
|
|
488
|
+
{Math.round(parent.size.width)} × {Math.round(parent.size.height)} px
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
))}
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<DisplayBoxStage containerRef={containerRef} stageStyle={gridStageStyle} />
|
|
497
|
+
</section>
|
|
498
|
+
);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
export default AutoLayoutDemo;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ElementDeletedEvent,
|
|
4
|
+
LinkDeletedEvent,
|
|
5
|
+
PortDeletedEvent,
|
|
6
|
+
TextDeletedEvent,
|
|
7
|
+
} from '../../api';
|
|
8
|
+
import DisplayBoxControls from '../DisplayBoxControls';
|
|
9
|
+
import DisplayBoxStage from '../DisplayBoxStage';
|
|
10
|
+
import useDemoControls from '../useDemoControls';
|
|
11
|
+
import useDemoEditor from '../useDemoEditor';
|
|
12
|
+
import useOffsetSequence from '../useOffsetSequence';
|
|
13
|
+
import type { DemoActionHelpers } from '../types';
|
|
14
|
+
import { deletionEventsDemoConfig } from './deletionEventsDemo';
|
|
15
|
+
|
|
16
|
+
type EventPayloads = {
|
|
17
|
+
elementDeleted: ElementDeletedEvent | null;
|
|
18
|
+
portDeleted: PortDeletedEvent | null;
|
|
19
|
+
linkDeleted: LinkDeletedEvent | null;
|
|
20
|
+
textDeleted: TextDeletedEvent | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const initialPayloads: EventPayloads = {
|
|
24
|
+
elementDeleted: null,
|
|
25
|
+
portDeleted: null,
|
|
26
|
+
linkDeleted: null,
|
|
27
|
+
textDeleted: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const eventBoxStyle: React.CSSProperties = {
|
|
31
|
+
width: '100%',
|
|
32
|
+
minHeight: 130,
|
|
33
|
+
padding: '8px 10px',
|
|
34
|
+
borderRadius: 8,
|
|
35
|
+
border: '1px solid #d0d5dd',
|
|
36
|
+
background: '#fff',
|
|
37
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Courier New", monospace',
|
|
38
|
+
fontSize: 12,
|
|
39
|
+
resize: 'vertical',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DeletionEventsDemo = () => {
|
|
43
|
+
const demo = deletionEventsDemoConfig;
|
|
44
|
+
const { containerRef, editorRef, diagramState, selection, snapEnabled, setSnapEnabled } = useDemoEditor({
|
|
45
|
+
createState: demo.createState,
|
|
46
|
+
elementShapes: demo.elementShapes,
|
|
47
|
+
portShapes: demo.portShapes,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const nextOffset = useOffsetSequence();
|
|
51
|
+
const actionHelpers: DemoActionHelpers = useMemo(() => ({ nextOffset }), [nextOffset]);
|
|
52
|
+
|
|
53
|
+
const controls = useDemoControls({
|
|
54
|
+
demo,
|
|
55
|
+
editorRef,
|
|
56
|
+
diagramState,
|
|
57
|
+
selection,
|
|
58
|
+
snapEnabled,
|
|
59
|
+
setSnapEnabled,
|
|
60
|
+
actionHelpers,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const [payloads, setPayloads] = useState<EventPayloads>(initialPayloads);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const editor = editorRef.current;
|
|
67
|
+
if (!editor) return undefined;
|
|
68
|
+
setPayloads(initialPayloads);
|
|
69
|
+
const unsubs = [
|
|
70
|
+
editor.on('elementDeleted', (payload) => setPayloads((prev) => ({ ...prev, elementDeleted: payload }))),
|
|
71
|
+
editor.on('portDeleted', (payload) => setPayloads((prev) => ({ ...prev, portDeleted: payload }))),
|
|
72
|
+
editor.on('linkDeleted', (payload) => setPayloads((prev) => ({ ...prev, linkDeleted: payload }))),
|
|
73
|
+
editor.on('textDeleted', (payload) => setPayloads((prev) => ({ ...prev, textDeleted: payload }))),
|
|
74
|
+
];
|
|
75
|
+
return () => {
|
|
76
|
+
unsubs.forEach((unsub) => unsub());
|
|
77
|
+
};
|
|
78
|
+
}, [editorRef]);
|
|
79
|
+
|
|
80
|
+
const hasPort = (diagramState?.ports ?? []).some((port) => port.id === 'delete-source-free-port');
|
|
81
|
+
const hasLink = (diagramState?.links ?? []).some((link) => link.id === 'delete-link');
|
|
82
|
+
const hasText = (diagramState?.texts ?? []).some((text) => text.id === 'delete-free-text');
|
|
83
|
+
const hasCascadeElement = (diagramState?.elements ?? []).some((element) => element.id === 'delete-source');
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<section>
|
|
87
|
+
<div style={{ marginBottom: 12 }}>
|
|
88
|
+
<h2 style={{ marginTop: 0, marginBottom: 4 }}>{demo.title}</h2>
|
|
89
|
+
<p style={{ marginTop: 0 }}>
|
|
90
|
+
Use direct delete buttons for port/link/text, then delete <code>delete-source</code> to trigger cascade
|
|
91
|
+
removals. Latest payloads are shown below for integration debugging.
|
|
92
|
+
</p>
|
|
93
|
+
</div>
|
|
94
|
+
<DisplayBoxControls
|
|
95
|
+
actions={demo.actions}
|
|
96
|
+
snapEnabled={controls.snapEnabled}
|
|
97
|
+
selectedLinkRouting={controls.selectedLinkRouting}
|
|
98
|
+
canToggleLinkRouting={controls.canToggleLinkRouting}
|
|
99
|
+
onReload={controls.handleReload}
|
|
100
|
+
onZoomIn={controls.handleZoomIn}
|
|
101
|
+
onZoomOut={controls.handleZoomOut}
|
|
102
|
+
onResetViewport={controls.handleResetViewport}
|
|
103
|
+
onToggleSnap={controls.handleToggleSnap}
|
|
104
|
+
onManualRender={controls.handleManualRender}
|
|
105
|
+
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
106
|
+
onAction={controls.handleAction}
|
|
107
|
+
/>
|
|
108
|
+
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
109
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
110
|
+
<button type="button" onClick={() => editorRef.current?.removePort('delete-source-free-port')} disabled={!hasPort}>
|
|
111
|
+
Delete Port
|
|
112
|
+
</button>
|
|
113
|
+
<button type="button" onClick={() => editorRef.current?.removeLink('delete-link')} disabled={!hasLink}>
|
|
114
|
+
Delete Link
|
|
115
|
+
</button>
|
|
116
|
+
<button type="button" onClick={() => editorRef.current?.removeText('delete-free-text')} disabled={!hasText}>
|
|
117
|
+
Delete Text
|
|
118
|
+
</button>
|
|
119
|
+
<button type="button" onClick={() => editorRef.current?.removeElement('delete-source')} disabled={!hasCascadeElement}>
|
|
120
|
+
Delete Element (Cascade)
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 12 }}>
|
|
124
|
+
<div>
|
|
125
|
+
<label style={{ display: 'block', fontWeight: 600, marginBottom: 6 }}>elementDeleted</label>
|
|
126
|
+
<textarea readOnly value={payloads.elementDeleted ? JSON.stringify(payloads.elementDeleted, null, 2) : ''} style={eventBoxStyle} />
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
<label style={{ display: 'block', fontWeight: 600, marginBottom: 6 }}>portDeleted</label>
|
|
130
|
+
<textarea readOnly value={payloads.portDeleted ? JSON.stringify(payloads.portDeleted, null, 2) : ''} style={eventBoxStyle} />
|
|
131
|
+
</div>
|
|
132
|
+
<div>
|
|
133
|
+
<label style={{ display: 'block', fontWeight: 600, marginBottom: 6 }}>linkDeleted</label>
|
|
134
|
+
<textarea readOnly value={payloads.linkDeleted ? JSON.stringify(payloads.linkDeleted, null, 2) : ''} style={eventBoxStyle} />
|
|
135
|
+
</div>
|
|
136
|
+
<div>
|
|
137
|
+
<label style={{ display: 'block', fontWeight: 600, marginBottom: 6 }}>textDeleted</label>
|
|
138
|
+
<textarea readOnly value={payloads.textDeleted ? JSON.stringify(payloads.textDeleted, null, 2) : ''} style={eventBoxStyle} />
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
<DisplayBoxStage containerRef={containerRef} />
|
|
143
|
+
</section>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default DeletionEventsDemo;
|