orcasvn-react-diagrams 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +11 -3
  3. package/dist/cjs/examples.js +1768 -161
  4. package/dist/cjs/index.js +786 -120
  5. package/dist/cjs/types/api/createDiagramEditor.d.ts +19 -1
  6. package/dist/cjs/types/api/index.d.ts +1 -1
  7. package/dist/cjs/types/api/types.d.ts +32 -0
  8. package/dist/cjs/types/displaybox/DisplayBoxControls.d.ts +5 -1
  9. package/dist/cjs/types/displaybox/demos/AsymmetricPortMultiAnchorDemoTab.d.ts +3 -0
  10. package/dist/cjs/types/displaybox/demos/LayoutLabelReservedSpaceDemoTab.d.ts +3 -0
  11. package/dist/cjs/types/displaybox/demos/VertexControlLinkSessionDemoTab.d.ts +3 -0
  12. package/dist/cjs/types/displaybox/demos/asymmetricPortMultiAnchorDemo.d.ts +31 -0
  13. package/dist/cjs/types/displaybox/demos/layoutLabelReservedSpaceDemo.d.ts +11 -0
  14. package/dist/cjs/types/displaybox/demos/vertexControlLinkSessionDemo.d.ts +12 -0
  15. package/dist/cjs/types/displaybox/useDemoControls.d.ts +4 -0
  16. package/dist/cjs/types/engine/AutoLayoutService.d.ts +2 -0
  17. package/dist/cjs/types/engine/DiagramEngine.d.ts +5 -0
  18. package/dist/cjs/types/engine/LinkRoutingService.d.ts +9 -1
  19. package/dist/cjs/types/models/PortModel.d.ts +5 -0
  20. package/dist/cjs/types/renderer/RenderTypes.d.ts +3 -1
  21. package/dist/cjs/types/renderer/konva/KonvaInteraction.d.ts +14 -0
  22. package/dist/cjs/types/renderer/konva/KonvaNodeFactory.d.ts +1 -0
  23. package/dist/cjs/types/renderer/konva/KonvaRenderer.d.ts +0 -1
  24. package/dist/cjs/types/shapes/BuiltInShapes.d.ts +3 -1
  25. package/dist/cjs/types/utils/__tests__/portGeometry.test.d.ts +1 -0
  26. package/dist/cjs/types/utils/portGeometry.d.ts +44 -0
  27. package/dist/esm/examples.js +1769 -162
  28. package/dist/esm/examples.js.map +1 -1
  29. package/dist/esm/index.js +786 -120
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/esm/types/api/createDiagramEditor.d.ts +19 -1
  32. package/dist/esm/types/api/index.d.ts +1 -1
  33. package/dist/esm/types/api/types.d.ts +32 -0
  34. package/dist/esm/types/displaybox/DisplayBoxControls.d.ts +5 -1
  35. package/dist/esm/types/displaybox/demos/AsymmetricPortMultiAnchorDemoTab.d.ts +3 -0
  36. package/dist/esm/types/displaybox/demos/LayoutLabelReservedSpaceDemoTab.d.ts +3 -0
  37. package/dist/esm/types/displaybox/demos/VertexControlLinkSessionDemoTab.d.ts +3 -0
  38. package/dist/esm/types/displaybox/demos/asymmetricPortMultiAnchorDemo.d.ts +31 -0
  39. package/dist/esm/types/displaybox/demos/layoutLabelReservedSpaceDemo.d.ts +11 -0
  40. package/dist/esm/types/displaybox/demos/vertexControlLinkSessionDemo.d.ts +12 -0
  41. package/dist/esm/types/displaybox/useDemoControls.d.ts +4 -0
  42. package/dist/esm/types/engine/AutoLayoutService.d.ts +2 -0
  43. package/dist/esm/types/engine/DiagramEngine.d.ts +5 -0
  44. package/dist/esm/types/engine/LinkRoutingService.d.ts +9 -1
  45. package/dist/esm/types/models/PortModel.d.ts +5 -0
  46. package/dist/esm/types/renderer/RenderTypes.d.ts +3 -1
  47. package/dist/esm/types/renderer/konva/KonvaInteraction.d.ts +14 -0
  48. package/dist/esm/types/renderer/konva/KonvaNodeFactory.d.ts +1 -0
  49. package/dist/esm/types/renderer/konva/KonvaRenderer.d.ts +0 -1
  50. package/dist/esm/types/shapes/BuiltInShapes.d.ts +3 -1
  51. package/dist/esm/types/utils/__tests__/portGeometry.test.d.ts +1 -0
  52. package/dist/esm/types/utils/portGeometry.d.ts +44 -0
  53. package/dist/examples.d.ts +50 -0
  54. package/dist/index.d.ts +58 -1
  55. package/package.json +11 -10
  56. package/src/displaybox/demos/AsymmetricPortMultiAnchorDemoTab.tsx +269 -0
  57. package/src/displaybox/demos/AutoLayoutDemoTab.tsx +113 -11
  58. package/src/displaybox/demos/DeletionEventsDemoTab.tsx +6 -1
  59. package/src/displaybox/demos/EngineEventsDemoTab.tsx +5 -0
  60. package/src/displaybox/demos/EventHandlersDemoTab.tsx +5 -0
  61. package/src/displaybox/demos/ExternalDragDropDemoTab.tsx +5 -0
  62. package/src/displaybox/demos/LayoutLabelReservedSpaceDemoTab.tsx +291 -0
  63. package/src/displaybox/demos/LinkCancelDemoTab.tsx +5 -0
  64. package/src/displaybox/demos/ShapeHoverControlsDemoTab.tsx +6 -1
  65. package/src/displaybox/demos/SimpleDemo.tsx +5 -0
  66. package/src/displaybox/demos/SvgPathDemoTab.tsx +5 -0
  67. package/src/displaybox/demos/TextLayoutDemoTab.tsx +6 -1
  68. package/src/displaybox/demos/VertexControlLinkSessionDemoTab.tsx +302 -0
  69. package/src/displaybox/demos/asymmetricPortMultiAnchorDemo.ts +357 -0
  70. package/src/displaybox/demos/autoLayoutDemo.ts +23 -5
  71. package/src/displaybox/demos/index.tsx +91 -75
  72. package/src/displaybox/demos/layoutLabelReservedSpaceDemo.ts +121 -0
  73. package/src/displaybox/demos/vertexControlLinkSessionDemo.ts +145 -0
@@ -82,6 +82,10 @@ const EventHandlersDemo = () => {
82
82
  onManualRender={controls.handleManualRender}
83
83
  onToggleLinkRouting={controls.handleToggleLinkRouting}
84
84
  onAction={controls.handleAction}
85
+ onExportImage={controls.handleExportImage}
86
+ onClearExportPreview={controls.handleClearExportPreview}
87
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
88
+ exportError={controls.exportError}
85
89
  />
86
90
  <div style={{ display: 'flex', gap: 12, alignItems: 'center', marginBottom: 12 }}>
87
91
  <label htmlFor="event-handler-select" style={{ fontWeight: 600 }}>
@@ -108,3 +112,4 @@ const EventHandlersDemo = () => {
108
112
  };
109
113
 
110
114
  export default EventHandlersDemo;
115
+
@@ -167,6 +167,10 @@ const ExternalDragDropDemo = () => {
167
167
  onManualRender={controls.handleManualRender}
168
168
  onToggleLinkRouting={controls.handleToggleLinkRouting}
169
169
  onAction={controls.handleAction}
170
+ onExportImage={controls.handleExportImage}
171
+ onClearExportPreview={controls.handleClearExportPreview}
172
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
173
+ exportError={controls.exportError}
170
174
  />
171
175
  <div style={{ display: 'flex', gap: 16, marginBottom: 12, alignItems: 'stretch' }}>
172
176
  <div
@@ -259,3 +263,4 @@ const ExternalDragDropDemo = () => {
259
263
  };
260
264
 
261
265
  export default ExternalDragDropDemo;
266
+
@@ -0,0 +1,291 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import type {
3
+ DiagramState,
4
+ ElementLayout,
5
+ ElementLayoutChildFitCrossAxis,
6
+ ElementLayoutChildFitMainAxis,
7
+ ElementLayoutLabelReservedSpaceMode,
8
+ } from '../../api';
9
+ import DisplayBoxControls from '../DisplayBoxControls';
10
+ import DisplayBoxStage from '../DisplayBoxStage';
11
+ import useDemoControls from '../useDemoControls';
12
+ import useDemoEditor from '../useDemoEditor';
13
+ import useOffsetSequence from '../useOffsetSequence';
14
+ import type { DemoActionHelpers } from '../types';
15
+ import { gridStageStyle } from './shared';
16
+ import { layoutLabelReservedSpaceDemoConfig, layoutReservedDemoIds } from './layoutLabelReservedSpaceDemo';
17
+
18
+ const shortLabel = 'Parent label lane demo';
19
+ const longLabel =
20
+ 'Parent label lane demo with long wrapped content that expands height and pushes children lower in flexible mode.';
21
+
22
+ const clamp = (value: number, min: number, max: number): number => Math.max(min, Math.min(max, value));
23
+
24
+ const resolveLaneFromState = (state: DiagramState, parentId: string): number => {
25
+ const parent = state.elements.find((element) => element.id === parentId);
26
+ const policy = parent?.layout?.labelReservedSpace;
27
+ if (!parent || !policy) return 0;
28
+ const mode = policy.mode ?? 'none';
29
+ if (mode === 'none') return 0;
30
+ let lane = 0;
31
+ if (mode === 'fixed') {
32
+ lane = Math.max(0, policy.size ?? 0);
33
+ } else {
34
+ state.texts.forEach((text) => {
35
+ if (text.ownerId !== parentId) return;
36
+ const offsetY = text.displayOffset?.y ?? 0;
37
+ const height = text.displayClipSize?.height ?? text.size?.height ?? 0;
38
+ lane = Math.max(lane, text.position.y + offsetY + height);
39
+ });
40
+ }
41
+ const minSize = Math.max(0, policy.minSize ?? 0);
42
+ const maxSize = policy.maxSize !== undefined ? Math.max(minSize, policy.maxSize) : Number.POSITIVE_INFINITY;
43
+ return clamp(lane, minSize, maxSize);
44
+ };
45
+
46
+ const LayoutLabelReservedSpaceDemo = () => {
47
+ const demo = layoutLabelReservedSpaceDemoConfig;
48
+ const { containerRef, editorRef, diagramState, selection, snapEnabled, setSnapEnabled } = useDemoEditor({
49
+ createState: demo.createState,
50
+ elementShapes: demo.elementShapes,
51
+ portShapes: demo.portShapes,
52
+ });
53
+
54
+ const nextOffset = useOffsetSequence();
55
+ const actionHelpers: DemoActionHelpers = useMemo(() => ({ nextOffset }), [nextOffset]);
56
+ const controls = useDemoControls({
57
+ demo,
58
+ editorRef,
59
+ diagramState,
60
+ selection,
61
+ snapEnabled,
62
+ setSnapEnabled,
63
+ actionHelpers,
64
+ });
65
+
66
+ const [mode, setMode] = useState<ElementLayoutLabelReservedSpaceMode>('none');
67
+ const [fixedSize, setFixedSize] = useState(32);
68
+ const [minSize, setMinSize] = useState<number | ''>('');
69
+ const [maxSize, setMaxSize] = useState<number | ''>('');
70
+ const [childFitMainAxis, setChildFitMainAxis] = useState<ElementLayoutChildFitMainAxis>('none');
71
+ const [childFitCrossAxis, setChildFitCrossAxis] = useState<ElementLayoutChildFitCrossAxis>('none');
72
+
73
+ const parent = useMemo(
74
+ () => diagramState?.elements.find((element) => element.id === layoutReservedDemoIds.parent),
75
+ [diagramState],
76
+ );
77
+
78
+ const effectiveLane = useMemo(() => {
79
+ if (!diagramState) return 0;
80
+ return resolveLaneFromState(diagramState, layoutReservedDemoIds.parent);
81
+ }, [diagramState]);
82
+
83
+ const childBounds = useMemo(() => {
84
+ if (!parent) return { x: 0, y: 0, width: 0, height: 0 };
85
+ const paddingRaw = parent.layout?.padding ?? 12;
86
+ const padding = typeof paddingRaw === 'number' ? paddingRaw : Math.max(paddingRaw.x ?? 0, paddingRaw.y ?? 0);
87
+ return {
88
+ x: padding,
89
+ y: padding + effectiveLane,
90
+ width: Math.max(0, parent.size.width - padding * 2),
91
+ height: Math.max(0, parent.size.height - padding * 2 - effectiveLane),
92
+ };
93
+ }, [parent, effectiveLane]);
94
+
95
+ const applyLayout = () => {
96
+ if (!editorRef.current) return;
97
+ const labelReservedSpace: NonNullable<ElementLayout['labelReservedSpace']> = {
98
+ mode,
99
+ placement: 'top',
100
+ ...(mode === 'fixed' ? { size: fixedSize } : {}),
101
+ ...(minSize === '' ? {} : { minSize }),
102
+ ...(maxSize === '' ? {} : { maxSize }),
103
+ };
104
+ editorRef.current.setElementLayout(layoutReservedDemoIds.parent, {
105
+ mode: 'horizontal',
106
+ padding: 12,
107
+ gap: 10,
108
+ align: 'start',
109
+ childFitMainAxis,
110
+ childFitCrossAxis,
111
+ labelReservedSpace,
112
+ });
113
+ };
114
+
115
+ const setLabelContent = (value: string) => {
116
+ editorRef.current?.updateText(layoutReservedDemoIds.parentLabel, value);
117
+ };
118
+
119
+ const children = useMemo(() => {
120
+ if (!diagramState) return [];
121
+ return diagramState.elements.filter((element) => element.parentId === layoutReservedDemoIds.parent);
122
+ }, [diagramState]);
123
+
124
+ return (
125
+ <section>
126
+ <div style={{ marginBottom: 12 }}>
127
+ <h2 style={{ marginTop: 0, marginBottom: 4 }}>{demo.title}</h2>
128
+ <p style={{ marginTop: 0 }}>{demo.description}</p>
129
+ </div>
130
+ <DisplayBoxControls
131
+ actions={demo.actions}
132
+ snapEnabled={controls.snapEnabled}
133
+ selectedLinkRouting={controls.selectedLinkRouting}
134
+ canToggleLinkRouting={controls.canToggleLinkRouting}
135
+ onReload={controls.handleReload}
136
+ onZoomIn={controls.handleZoomIn}
137
+ onZoomOut={controls.handleZoomOut}
138
+ onResetViewport={controls.handleResetViewport}
139
+ onToggleSnap={controls.handleToggleSnap}
140
+ onManualRender={controls.handleManualRender}
141
+ onToggleLinkRouting={controls.handleToggleLinkRouting}
142
+ onAction={controls.handleAction}
143
+ onExportImage={controls.handleExportImage}
144
+ onClearExportPreview={controls.handleClearExportPreview}
145
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
146
+ exportError={controls.exportError}
147
+ />
148
+
149
+ <div style={{ marginBottom: 12, padding: 12, border: '1px solid #d9e1ec', borderRadius: 8, background: '#fbfdff' }}>
150
+ <div style={{ fontWeight: 700, marginBottom: 8 }}>Scenario controls</div>
151
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', marginBottom: 10 }}>
152
+ <label style={{ fontSize: 13 }}>
153
+ Mode
154
+ <select value={mode} onChange={(event) => setMode(event.target.value as ElementLayoutLabelReservedSpaceMode)} style={{ marginLeft: 8 }}>
155
+ <option value="none">none</option>
156
+ <option value="fixed">fixed</option>
157
+ <option value="flexible">flexible</option>
158
+ </select>
159
+ </label>
160
+ <label style={{ fontSize: 13 }}>
161
+ Fixed size
162
+ <input
163
+ type="number"
164
+ value={fixedSize}
165
+ onChange={(event) => setFixedSize(Math.max(0, Number(event.target.value) || 0))}
166
+ style={{ width: 74, marginLeft: 8 }}
167
+ min={0}
168
+ />
169
+ </label>
170
+ <label style={{ fontSize: 13 }}>
171
+ Min
172
+ <input
173
+ type="number"
174
+ value={minSize}
175
+ onChange={(event) => setMinSize(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
176
+ style={{ width: 74, marginLeft: 8 }}
177
+ min={0}
178
+ />
179
+ </label>
180
+ <label style={{ fontSize: 13 }}>
181
+ Max
182
+ <input
183
+ type="number"
184
+ value={maxSize}
185
+ onChange={(event) => setMaxSize(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
186
+ style={{ width: 74, marginLeft: 8 }}
187
+ min={0}
188
+ />
189
+ </label>
190
+ <label style={{ fontSize: 13 }}>
191
+ Main-axis fit
192
+ <select
193
+ value={childFitMainAxis}
194
+ onChange={(event) => setChildFitMainAxis(event.target.value as ElementLayoutChildFitMainAxis)}
195
+ style={{ marginLeft: 8 }}
196
+ >
197
+ <option value="none">none</option>
198
+ <option value="distribute">distribute</option>
199
+ </select>
200
+ </label>
201
+ <label style={{ fontSize: 13 }}>
202
+ Cross-axis fit
203
+ <select
204
+ value={childFitCrossAxis}
205
+ onChange={(event) => setChildFitCrossAxis(event.target.value as ElementLayoutChildFitCrossAxis)}
206
+ style={{ marginLeft: 8 }}
207
+ >
208
+ <option value="none">none</option>
209
+ <option value="stretch">stretch</option>
210
+ </select>
211
+ </label>
212
+ <button type="button" onClick={applyLayout} style={{ padding: '6px 12px', fontWeight: 600 }}>
213
+ Apply
214
+ </button>
215
+ </div>
216
+
217
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 10 }}>
218
+ <button type="button" onClick={() => setLabelContent(shortLabel)} style={{ padding: '6px 10px' }}>
219
+ Flexible label: short
220
+ </button>
221
+ <button type="button" onClick={() => setLabelContent(longLabel)} style={{ padding: '6px 10px' }}>
222
+ Flexible label: long
223
+ </button>
224
+ </div>
225
+
226
+ <div style={{ display: 'grid', gridTemplateColumns: 'minmax(300px, 1fr) minmax(260px, 1fr)', gap: 12 }}>
227
+ <div>
228
+ <div style={{ fontWeight: 600, marginBottom: 6 }}>Visual guide</div>
229
+ <svg width="320" height="180" viewBox="0 0 320 180" style={{ background: '#fff', border: '1px solid #e0e6ef', borderRadius: 6 }}>
230
+ <rect x="16" y="16" width="288" height="148" fill="#f5f8fd" stroke="#9fb6d6" strokeWidth="2" />
231
+ <rect
232
+ x={16 + (childBounds.x / Math.max(1, parent?.size.width ?? 1)) * 288}
233
+ y={16 + ((childBounds.y - effectiveLane) / Math.max(1, parent?.size.height ?? 1)) * 148}
234
+ width={(childBounds.width / Math.max(1, parent?.size.width ?? 1)) * 288}
235
+ height={(effectiveLane / Math.max(1, parent?.size.height ?? 1)) * 148}
236
+ fill="rgba(255,122,0,0.25)"
237
+ stroke="#ff7a00"
238
+ />
239
+ <rect
240
+ x={16 + (childBounds.x / Math.max(1, parent?.size.width ?? 1)) * 288}
241
+ y={16 + (childBounds.y / Math.max(1, parent?.size.height ?? 1)) * 148}
242
+ width={(childBounds.width / Math.max(1, parent?.size.width ?? 1)) * 288}
243
+ height={(childBounds.height / Math.max(1, parent?.size.height ?? 1)) * 148}
244
+ fill="rgba(31,77,153,0.08)"
245
+ stroke="#1f4d99"
246
+ strokeDasharray="6 4"
247
+ />
248
+ <text x="22" y="30" fontSize="11" fill="#24415f">
249
+ Parent bounds
250
+ </text>
251
+ <text x="22" y="46" fontSize="11" fill="#8a4a00">
252
+ Reserved lane
253
+ </text>
254
+ <text x="22" y="62" fontSize="11" fill="#1f4d99">
255
+ Child layout bounds
256
+ </text>
257
+ </svg>
258
+ </div>
259
+ <div>
260
+ <div style={{ fontWeight: 600, marginBottom: 6 }}>Verification</div>
261
+ <ol style={{ marginTop: 0, paddingLeft: 20, fontSize: 13 }}>
262
+ <li>Set mode none and apply: children can use the normal top area.</li>
263
+ <li>Set mode fixed (for example 32) and apply: children shift below lane.</li>
264
+ <li>Set mode flexible, apply, then switch label short/long to trigger relayout.</li>
265
+ <li>Enable distribute/stretch and apply to confirm fit options stay deterministic.</li>
266
+ <li>Inspect nested parent block on canvas to verify nested layout consistency.</li>
267
+ </ol>
268
+ <div style={{ fontSize: 13 }}>
269
+ Effective lane: <strong>{Math.round(effectiveLane)} px</strong>
270
+ </div>
271
+ <div style={{ fontSize: 12, marginTop: 4 }}>
272
+ Child bounds (relative): x {Math.round(childBounds.x)}, y {Math.round(childBounds.y)}, w {Math.round(childBounds.width)}, h {Math.round(childBounds.height)}
273
+ </div>
274
+ <div style={{ marginTop: 6, fontSize: 12 }}>
275
+ Children:
276
+ <pre style={{ marginTop: 4, background: '#fff', border: '1px solid #d9e1ec', borderRadius: 6, padding: 8, maxHeight: 120, overflow: 'auto' }}>
277
+ {children
278
+ .map((child) => `${child.id}: pos(${Math.round(child.position.x)}, ${Math.round(child.position.y)}) size(${child.size.width}x${child.size.height})`)
279
+ .join('\n') || 'No children'}
280
+ </pre>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <DisplayBoxStage containerRef={containerRef} stageStyle={gridStageStyle} />
287
+ </section>
288
+ );
289
+ };
290
+
291
+ export default LayoutLabelReservedSpaceDemo;
@@ -118,6 +118,10 @@ const LinkCancelDemo = () => {
118
118
  onManualRender={controls.handleManualRender}
119
119
  onToggleLinkRouting={controls.handleToggleLinkRouting}
120
120
  onAction={controls.handleAction}
121
+ onExportImage={controls.handleExportImage}
122
+ onClearExportPreview={controls.handleClearExportPreview}
123
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
124
+ exportError={controls.exportError}
121
125
  />
122
126
  <div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
123
127
  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center' }}>
@@ -236,3 +240,4 @@ const LinkCancelDemo = () => {
236
240
  };
237
241
 
238
242
  export default LinkCancelDemo;
243
+
@@ -355,7 +355,11 @@ const ShapeHoverControlsDemo = () => {
355
355
  onToggleSnap={controls.handleToggleSnap}
356
356
  onManualRender={controls.handleManualRender}
357
357
  onToggleLinkRouting={controls.handleToggleLinkRouting}
358
- onAction={controls.handleAction}
358
+ onAction={controls.handleAction}
359
+ onExportImage={controls.handleExportImage}
360
+ onClearExportPreview={controls.handleClearExportPreview}
361
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
362
+ exportError={controls.exportError}
359
363
  />
360
364
  <div
361
365
  style={{
@@ -556,3 +560,4 @@ const EllipseMidPointControlSection = ({
556
560
  );
557
561
 
558
562
  export default ShapeHoverControlsDemo;
563
+
@@ -57,6 +57,10 @@ const SimpleDemo = ({ demo, beforeStage, stageHandlers }: SimpleDemoProps) => {
57
57
  onManualRender={controls.handleManualRender}
58
58
  onToggleLinkRouting={controls.handleToggleLinkRouting}
59
59
  onAction={controls.handleAction}
60
+ onExportImage={controls.handleExportImage}
61
+ onClearExportPreview={controls.handleClearExportPreview}
62
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
63
+ exportError={controls.exportError}
60
64
  />
61
65
  {beforeStage}
62
66
  <DisplayBoxStage
@@ -71,3 +75,4 @@ const SimpleDemo = ({ demo, beforeStage, stageHandlers }: SimpleDemoProps) => {
71
75
  };
72
76
 
73
77
  export default SimpleDemo;
78
+
@@ -166,6 +166,10 @@ const SvgPathDemo = () => {
166
166
  onManualRender={controls.handleManualRender}
167
167
  onToggleLinkRouting={controls.handleToggleLinkRouting}
168
168
  onAction={controls.handleAction}
169
+ onExportImage={controls.handleExportImage}
170
+ onClearExportPreview={controls.handleClearExportPreview}
171
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
172
+ exportError={controls.exportError}
169
173
  />
170
174
  <div
171
175
  style={{
@@ -325,3 +329,4 @@ const SvgPathDemo = () => {
325
329
  };
326
330
 
327
331
  export default SvgPathDemo;
332
+
@@ -176,7 +176,11 @@ const TextLayoutDemo = () => {
176
176
  onToggleSnap={controls.handleToggleSnap}
177
177
  onManualRender={controls.handleManualRender}
178
178
  onToggleLinkRouting={controls.handleToggleLinkRouting}
179
- onAction={controls.handleAction}
179
+ onAction={controls.handleAction}
180
+ onExportImage={controls.handleExportImage}
181
+ onClearExportPreview={controls.handleClearExportPreview}
182
+ exportPreviewDataUrl={controls.exportPreviewDataUrl}
183
+ exportError={controls.exportError}
180
184
  />
181
185
 
182
186
  <div style={{ display: 'grid', gap: 10, marginBottom: 12 }}>
@@ -384,3 +388,4 @@ const TextLayoutDemo = () => {
384
388
  };
385
389
 
386
390
  export default TextLayoutDemo;
391
+