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.
Files changed (133) hide show
  1. package/README.md +22 -1
  2. package/ai/api-contract.json +57 -5
  3. package/ai/invariants.json +5 -3
  4. package/ai/manifest.json +1 -1
  5. package/dist/cjs/examples.js +11775 -0
  6. package/dist/cjs/index.js +3889 -1112
  7. package/dist/cjs/types/api/createDiagramEditor.d.ts +7 -2
  8. package/dist/cjs/types/api/types.d.ts +178 -0
  9. package/dist/cjs/types/displaybox/demos/DeletionEventsDemoTab.d.ts +3 -0
  10. package/dist/cjs/types/displaybox/demos/ShapeHoverControlsDemoTab.d.ts +3 -0
  11. package/dist/cjs/types/displaybox/demos/TextLayoutDemoTab.d.ts +3 -0
  12. package/dist/cjs/types/displaybox/demos/deletionEventsDemo.d.ts +2 -0
  13. package/dist/cjs/types/displaybox/demos/rotatedCreationDemo.d.ts +2 -0
  14. package/dist/cjs/types/displaybox/demos/roundedRectRadiusDemo.d.ts +2 -0
  15. package/dist/cjs/types/displaybox/demos/shapeBorderMovementDemo.d.ts +2 -0
  16. package/dist/cjs/types/displaybox/demos/shapeHoverControlsDemo.d.ts +10 -0
  17. package/dist/cjs/types/displaybox/demos/textDemo.d.ts +4 -0
  18. package/dist/cjs/types/displaybox/useDemoEditor.d.ts +5 -2
  19. package/dist/cjs/types/engine/AutoLayoutService.d.ts +24 -0
  20. package/dist/cjs/types/engine/DiagramEngine.d.ts +32 -14
  21. package/dist/cjs/types/engine/EngineCommands.d.ts +4 -1
  22. package/dist/cjs/types/engine/LinkRoutingService.d.ts +35 -0
  23. package/dist/cjs/types/engine/MutationPipeline.d.ts +23 -0
  24. package/dist/cjs/types/engine/TextLayoutService.d.ts +40 -0
  25. package/dist/cjs/types/examples/index.d.ts +2 -0
  26. package/dist/cjs/types/measure/textStyleDefaults.d.ts +9 -0
  27. package/dist/cjs/types/models/DiagramModel.d.ts +1 -0
  28. package/dist/cjs/types/models/ElementModel.d.ts +1 -0
  29. package/dist/cjs/types/models/PortModel.d.ts +3 -0
  30. package/dist/cjs/types/models/TextModel.d.ts +8 -0
  31. package/dist/cjs/types/renderer/RenderTypes.d.ts +34 -1
  32. package/dist/cjs/types/renderer/konva/KonvaHitTester.d.ts +1 -1
  33. package/dist/cjs/types/renderer/konva/KonvaInteraction.d.ts +53 -3
  34. package/dist/cjs/types/renderer/konva/KonvaNodeFactory.d.ts +18 -1
  35. package/dist/cjs/types/renderer/konva/KonvaRenderer.d.ts +49 -2
  36. package/dist/cjs/types/shapes/BuiltInShapes.d.ts +107 -0
  37. package/dist/cjs/types/shapes/__tests__/BuiltInShapes.test.d.ts +1 -0
  38. package/dist/cjs/types/shapes/index.d.ts +1 -0
  39. package/dist/cjs/types/utils/__tests__/borderGeometry.test.d.ts +1 -0
  40. package/dist/cjs/types/utils/borderGeometry.d.ts +6 -0
  41. package/dist/cjs/types/utils/geometry.d.ts +22 -0
  42. package/dist/esm/examples.js +11767 -0
  43. package/dist/esm/examples.js.map +1 -0
  44. package/dist/esm/index.js +3890 -1113
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/types/api/createDiagramEditor.d.ts +7 -2
  47. package/dist/esm/types/api/types.d.ts +178 -0
  48. package/dist/esm/types/displaybox/demos/DeletionEventsDemoTab.d.ts +3 -0
  49. package/dist/esm/types/displaybox/demos/ShapeHoverControlsDemoTab.d.ts +3 -0
  50. package/dist/esm/types/displaybox/demos/TextLayoutDemoTab.d.ts +3 -0
  51. package/dist/esm/types/displaybox/demos/deletionEventsDemo.d.ts +2 -0
  52. package/dist/esm/types/displaybox/demos/rotatedCreationDemo.d.ts +2 -0
  53. package/dist/esm/types/displaybox/demos/roundedRectRadiusDemo.d.ts +2 -0
  54. package/dist/esm/types/displaybox/demos/shapeBorderMovementDemo.d.ts +2 -0
  55. package/dist/esm/types/displaybox/demos/shapeHoverControlsDemo.d.ts +10 -0
  56. package/dist/esm/types/displaybox/demos/textDemo.d.ts +4 -0
  57. package/dist/esm/types/displaybox/useDemoEditor.d.ts +5 -2
  58. package/dist/esm/types/engine/AutoLayoutService.d.ts +24 -0
  59. package/dist/esm/types/engine/DiagramEngine.d.ts +32 -14
  60. package/dist/esm/types/engine/EngineCommands.d.ts +4 -1
  61. package/dist/esm/types/engine/LinkRoutingService.d.ts +35 -0
  62. package/dist/esm/types/engine/MutationPipeline.d.ts +23 -0
  63. package/dist/esm/types/engine/TextLayoutService.d.ts +40 -0
  64. package/dist/esm/types/examples/index.d.ts +2 -0
  65. package/dist/esm/types/measure/textStyleDefaults.d.ts +9 -0
  66. package/dist/esm/types/models/DiagramModel.d.ts +1 -0
  67. package/dist/esm/types/models/ElementModel.d.ts +1 -0
  68. package/dist/esm/types/models/PortModel.d.ts +3 -0
  69. package/dist/esm/types/models/TextModel.d.ts +8 -0
  70. package/dist/esm/types/renderer/RenderTypes.d.ts +34 -1
  71. package/dist/esm/types/renderer/konva/KonvaHitTester.d.ts +1 -1
  72. package/dist/esm/types/renderer/konva/KonvaInteraction.d.ts +53 -3
  73. package/dist/esm/types/renderer/konva/KonvaNodeFactory.d.ts +18 -1
  74. package/dist/esm/types/renderer/konva/KonvaRenderer.d.ts +49 -2
  75. package/dist/esm/types/shapes/BuiltInShapes.d.ts +107 -0
  76. package/dist/esm/types/shapes/__tests__/BuiltInShapes.test.d.ts +1 -0
  77. package/dist/esm/types/shapes/index.d.ts +1 -0
  78. package/dist/esm/types/utils/__tests__/borderGeometry.test.d.ts +1 -0
  79. package/dist/esm/types/utils/borderGeometry.d.ts +6 -0
  80. package/dist/esm/types/utils/geometry.d.ts +22 -0
  81. package/dist/examples.d.ts +532 -0
  82. package/dist/index.d.ts +233 -2
  83. package/docs/API_CONTRACT.md +59 -3
  84. package/docs/ARCHITECTURE.md +1 -0
  85. package/docs/CAPABILITIES.md +3 -1
  86. package/docs/COMMANDS_EVENTS.md +5 -0
  87. package/docs/DOCUMENTATION_WORKFLOW.md +6 -8
  88. package/docs/INTEGRATION_PLAYBOOK.md +2 -0
  89. package/docs/PORTING_CHECKLIST.md +1 -0
  90. package/docs/STATE_INVARIANTS.md +4 -0
  91. package/package.json +20 -10
  92. package/src/displaybox/demos/AutoLayoutDemoTab.tsx +501 -0
  93. package/src/displaybox/demos/DeletionEventsDemoTab.tsx +147 -0
  94. package/src/displaybox/demos/EngineEventsDemoTab.tsx +151 -0
  95. package/src/displaybox/demos/EventHandlersDemoTab.tsx +110 -0
  96. package/src/displaybox/demos/ExternalDragDropDemoTab.tsx +261 -0
  97. package/src/displaybox/demos/LinkCancelDemoTab.tsx +238 -0
  98. package/src/displaybox/demos/ObstacleRoutingDemoTab.tsx +30 -0
  99. package/src/displaybox/demos/ShapeHoverControlsDemoTab.tsx +558 -0
  100. package/src/displaybox/demos/SimpleDemo.tsx +73 -0
  101. package/src/displaybox/demos/SvgPathDemoTab.tsx +327 -0
  102. package/src/displaybox/demos/TextLayoutDemoTab.tsx +386 -0
  103. package/src/displaybox/demos/autoLayoutDemo.ts +111 -0
  104. package/src/displaybox/demos/basicDemo.ts +131 -0
  105. package/src/displaybox/demos/childConstraintsDemo.ts +65 -0
  106. package/src/displaybox/demos/customDemo.ts +59 -0
  107. package/src/displaybox/demos/deletionEventsDemo.ts +91 -0
  108. package/src/displaybox/demos/engineEventsDemo.ts +64 -0
  109. package/src/displaybox/demos/eventHandlersDemo.ts +41 -0
  110. package/src/displaybox/demos/externalDragDropDemo.ts +28 -0
  111. package/src/displaybox/demos/gridOverlayDemo.ts +50 -0
  112. package/src/displaybox/demos/index.tsx +217 -0
  113. package/src/displaybox/demos/linkBendHandlesDemo.ts +143 -0
  114. package/src/displaybox/demos/linkCancelDemo.ts +56 -0
  115. package/src/displaybox/demos/linkPortCreationDemo.ts +46 -0
  116. package/src/displaybox/demos/multiLevelTreeDemo.ts +120 -0
  117. package/src/displaybox/demos/multipleElementsDemo.ts +62 -0
  118. package/src/displaybox/demos/nestedDemo.ts +78 -0
  119. package/src/displaybox/demos/obstacleRoutingDemo.ts +176 -0
  120. package/src/displaybox/demos/portBorderDemo.ts +98 -0
  121. package/src/displaybox/demos/portConstraintsDemo.ts +175 -0
  122. package/src/displaybox/demos/rotatedCreationDemo.ts +185 -0
  123. package/src/displaybox/demos/roundedRectRadiusDemo.ts +93 -0
  124. package/src/displaybox/demos/routingDemo.ts +57 -0
  125. package/src/displaybox/demos/selectionDemo.ts +49 -0
  126. package/src/displaybox/demos/shapeBorderMovementDemo.ts +126 -0
  127. package/src/displaybox/demos/shapeGalleryDemo.ts +73 -0
  128. package/src/displaybox/demos/shapeHoverControlsDemo.ts +172 -0
  129. package/src/displaybox/demos/shared.ts +161 -0
  130. package/src/displaybox/demos/svgPathDemo.ts +71 -0
  131. package/src/displaybox/demos/textDemo.ts +62 -0
  132. package/src/displaybox/types.ts +66 -0
  133. package/src/examples/index.ts +21 -0
@@ -0,0 +1,327 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import DisplayBoxControls from '../DisplayBoxControls';
3
+ import DisplayBoxStage from '../DisplayBoxStage';
4
+ import useDemoControls from '../useDemoControls';
5
+ import useDemoEditor from '../useDemoEditor';
6
+ import useOffsetSequence from '../useOffsetSequence';
7
+ import type { DemoActionHelpers } from '../types';
8
+ import { createId } from '../../utils/ids';
9
+ import { defaultSvgPath, defaultSvgPathSize } from './shared';
10
+ import { svgPathDemoConfig } from './svgPathDemo';
11
+
12
+ type GlyphPreset = {
13
+ id: string;
14
+ label: string;
15
+ path: string;
16
+ semantics: string;
17
+ borderRotationOffset?: number;
18
+ };
19
+
20
+ const glyphPresets: GlyphPreset[] = [
21
+ {
22
+ id: 'crow-foot',
23
+ label: 'Crow Foot (1..n)',
24
+ path: 'M0 10 L14 10 M14 10 L24 2 M14 10 L24 10 M14 10 L24 18',
25
+ semantics: 'One-to-many side marker (crow-foot/trident style).',
26
+ borderRotationOffset: -90,
27
+ },
28
+ {
29
+ id: 'optional-circle',
30
+ label: 'Optional (0..1)',
31
+ path: 'M0 10 L14 10 M22 10 m-6 0 a6 6 0 1 0 12 0 a6 6 0 1 0 -12 0',
32
+ semantics: 'Optional endpoint marker (0..1).',
33
+ borderRotationOffset: -90,
34
+ },
35
+ {
36
+ id: 'mandatory-bars',
37
+ label: 'Mandatory (1..1)',
38
+ path: 'M0 10 L12 10 M16 3 L16 17 M22 3 L22 17',
39
+ semantics: 'Mandatory single endpoint marker (1..1).',
40
+ borderRotationOffset: -90,
41
+ },
42
+ ];
43
+
44
+ const SvgPathDemo = () => {
45
+ const demo = svgPathDemoConfig;
46
+ const { containerRef, editorRef, diagramState, selection, snapEnabled, setSnapEnabled } = useDemoEditor({
47
+ createState: demo.createState,
48
+ elementShapes: demo.elementShapes,
49
+ portShapes: demo.portShapes,
50
+ });
51
+
52
+ const nextOffset = useOffsetSequence();
53
+ const actionHelpers: DemoActionHelpers = useMemo(() => ({ nextOffset }), [nextOffset]);
54
+
55
+ const controls = useDemoControls({
56
+ demo,
57
+ editorRef,
58
+ diagramState,
59
+ selection,
60
+ snapEnabled,
61
+ setSnapEnabled,
62
+ actionHelpers,
63
+ });
64
+
65
+ const [svgPathInput, setSvgPathInput] = useState(defaultSvgPath);
66
+ const [addMode, setAddMode] = useState<'element' | 'port'>('element');
67
+ const [portMode, setPortMode] = useState<'manual' | 'border'>('manual');
68
+ const [targetElementId, setTargetElementId] = useState<string>('svg-host');
69
+ const [glyphSize, setGlyphSize] = useState<number>(18);
70
+ const [rotationAngle, setRotationAngle] = useState<number>(0);
71
+ const [anchorOffsetX, setAnchorOffsetX] = useState<number>(0);
72
+ const [anchorOffsetY, setAnchorOffsetY] = useState<number>(0);
73
+ const [presetId, setPresetId] = useState<string>(glyphPresets[0].id);
74
+
75
+ const hostElements = diagramState?.elements ?? [];
76
+ const selectedHost = hostElements.find((element) => element.id === targetElementId) ?? hostElements[0] ?? null;
77
+
78
+ const addSvgShape = (options?: {
79
+ path?: string;
80
+ mode?: 'element' | 'port';
81
+ forcedPortMode?: 'manual' | 'border';
82
+ rotationOffset?: number;
83
+ }) => {
84
+ const editor = editorRef.current;
85
+ const container = containerRef.current;
86
+ const mode = options?.mode ?? addMode;
87
+ const resolvedPortMode = options?.forcedPortMode ?? portMode;
88
+ const path = (options?.path ?? svgPathInput).trim();
89
+ if (!editor || !container || !path) return;
90
+ const shapeId = `svg-shape-${createId()}`;
91
+ editor.registerShape({ id: shapeId, svgPath: path });
92
+ if (mode === 'element') {
93
+ const size = defaultSvgPathSize;
94
+ const rect = container.getBoundingClientRect();
95
+ const world = editor.clientToWorld(
96
+ { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
97
+ container,
98
+ );
99
+ const offset = nextOffset();
100
+ const id = `svg-element-${createId()}`;
101
+ editor.addElement({
102
+ id,
103
+ position: { x: world.x - size.width / 2 + offset, y: world.y - size.height / 2 + offset },
104
+ size,
105
+ shapeId,
106
+ });
107
+ editor.setSelection([id]);
108
+ return;
109
+ }
110
+
111
+ if (!selectedHost) return;
112
+ const offset = Math.abs(nextOffset());
113
+ const usableHeight = Math.max(1, selectedHost.size.height - 20);
114
+ const y = 10 + (offset % usableHeight) + anchorOffsetY;
115
+ const portId = `svg-port-${createId()}`;
116
+ editor.addPortToElement(selectedHost.id, {
117
+ id: portId,
118
+ elementId: selectedHost.id,
119
+ position:
120
+ resolvedPortMode === 'border'
121
+ ? { x: selectedHost.size.width + anchorOffsetX, y }
122
+ : {
123
+ x: selectedHost.size.width / 2 + anchorOffsetX,
124
+ y: selectedHost.size.height / 2 + anchorOffsetY,
125
+ },
126
+ shapeId,
127
+ size: { width: glyphSize, height: glyphSize },
128
+ moveMode: resolvedPortMode === 'border' ? 'border' : 'inside',
129
+ anchorCenter: true,
130
+ orientToHostBorder: resolvedPortMode === 'border',
131
+ style: { rotation: rotationAngle + (options?.rotationOffset ?? 0) },
132
+ });
133
+ editor.setSelection([portId]);
134
+ };
135
+
136
+ const handleAddSvgShape = () => addSvgShape();
137
+
138
+ const selectedPreset = glyphPresets.find((preset) => preset.id === presetId) ?? glyphPresets[0];
139
+
140
+ const insertPresetPort = (forcedPortMode: 'manual' | 'border') => {
141
+ setSvgPathInput(selectedPreset.path);
142
+ addSvgShape({
143
+ path: selectedPreset.path,
144
+ mode: 'port',
145
+ forcedPortMode,
146
+ rotationOffset: forcedPortMode === 'border' ? (selectedPreset.borderRotationOffset ?? 0) : 0,
147
+ });
148
+ };
149
+
150
+ return (
151
+ <section>
152
+ <div style={{ marginBottom: 12 }}>
153
+ <h2 style={{ marginTop: 0, marginBottom: 4 }}>{demo.title}</h2>
154
+ <p style={{ marginTop: 0 }}>{demo.description}</p>
155
+ </div>
156
+ <DisplayBoxControls
157
+ actions={demo.actions}
158
+ snapEnabled={controls.snapEnabled}
159
+ selectedLinkRouting={controls.selectedLinkRouting}
160
+ canToggleLinkRouting={controls.canToggleLinkRouting}
161
+ onReload={controls.handleReload}
162
+ onZoomIn={controls.handleZoomIn}
163
+ onZoomOut={controls.handleZoomOut}
164
+ onResetViewport={controls.handleResetViewport}
165
+ onToggleSnap={controls.handleToggleSnap}
166
+ onManualRender={controls.handleManualRender}
167
+ onToggleLinkRouting={controls.handleToggleLinkRouting}
168
+ onAction={controls.handleAction}
169
+ />
170
+ <div
171
+ style={{
172
+ display: 'grid',
173
+ gridTemplateColumns: 'minmax(0, 1fr) 200px',
174
+ gap: 12,
175
+ marginBottom: 12,
176
+ alignItems: 'start',
177
+ }}
178
+ >
179
+ <div>
180
+ <label htmlFor="svg-path-input" style={{ fontWeight: 600, display: 'block', marginBottom: 6 }}>
181
+ SVG Path
182
+ </label>
183
+ <textarea
184
+ id="svg-path-input"
185
+ value={svgPathInput}
186
+ onChange={(event) => setSvgPathInput(event.target.value)}
187
+ rows={3}
188
+ style={{ width: '100%', resize: 'vertical', padding: '6px 8px' }}
189
+ />
190
+ <div style={{ fontSize: 12, color: '#555', marginTop: 6 }}>
191
+ Example: <code>{defaultSvgPath}</code>
192
+ </div>
193
+ <div style={{ fontSize: 12, color: '#555', marginTop: 4 }}>
194
+ Relationship icon ports are glyph visuals: interaction anchoring stays simple and deterministic.
195
+ </div>
196
+ <div style={{ marginTop: 10, borderTop: '1px solid #dde3ef', paddingTop: 10 }}>
197
+ <label htmlFor="svg-preset-select" style={{ fontWeight: 600, display: 'block', marginBottom: 6 }}>
198
+ Relationship Presets
199
+ </label>
200
+ <select
201
+ id="svg-preset-select"
202
+ value={presetId}
203
+ onChange={(event) => setPresetId(event.target.value)}
204
+ style={{ width: '100%', padding: '6px 8px' }}
205
+ >
206
+ {glyphPresets.map((preset) => (
207
+ <option key={preset.id} value={preset.id}>
208
+ {preset.label}
209
+ </option>
210
+ ))}
211
+ </select>
212
+ <div style={{ fontSize: 12, color: '#555', marginTop: 6 }}>{selectedPreset.semantics}</div>
213
+ <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
214
+ <button type="button" onClick={() => insertPresetPort('manual')} style={{ padding: '6px 8px' }}>
215
+ Insert Manual Glyph
216
+ </button>
217
+ <button type="button" onClick={() => insertPresetPort('border')} style={{ padding: '6px 8px' }}>
218
+ Insert Border-Oriented Glyph
219
+ </button>
220
+ </div>
221
+ <div style={{ fontSize: 12, color: '#555', marginTop: 6 }}>
222
+ Quick check: insert border-oriented presets on each side and verify the marker foot stays perpendicular to the
223
+ boundary while link endpoints remain stable.
224
+ </div>
225
+ </div>
226
+ </div>
227
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
228
+ <label htmlFor="svg-add-mode" style={{ fontWeight: 600, fontSize: 12 }}>
229
+ Add As
230
+ </label>
231
+ <select
232
+ id="svg-add-mode"
233
+ value={addMode}
234
+ onChange={(event) => setAddMode(event.target.value as 'element' | 'port')}
235
+ style={{ padding: '6px 8px' }}
236
+ >
237
+ <option value="element">Element</option>
238
+ <option value="port">Port (host border)</option>
239
+ </select>
240
+ {addMode === 'port' && (
241
+ <>
242
+ <label htmlFor="svg-port-mode" style={{ fontWeight: 600, fontSize: 12 }}>
243
+ Port Rotation Mode
244
+ </label>
245
+ <select
246
+ id="svg-port-mode"
247
+ value={portMode}
248
+ onChange={(event) => setPortMode(event.target.value as 'manual' | 'border')}
249
+ style={{ padding: '6px 8px' }}
250
+ >
251
+ <option value="manual">Manual rotation glyph</option>
252
+ <option value="border">Border-normal oriented glyph</option>
253
+ </select>
254
+ <label htmlFor="svg-port-host" style={{ fontWeight: 600, fontSize: 12 }}>
255
+ Host Element
256
+ </label>
257
+ <select
258
+ id="svg-port-host"
259
+ value={selectedHost?.id ?? ''}
260
+ onChange={(event) => setTargetElementId(event.target.value)}
261
+ style={{ padding: '6px 8px' }}
262
+ >
263
+ {hostElements.map((element) => (
264
+ <option key={element.id} value={element.id}>
265
+ {element.id}
266
+ </option>
267
+ ))}
268
+ </select>
269
+ <label htmlFor="svg-port-size" style={{ fontWeight: 600, fontSize: 12 }}>
270
+ Glyph Size
271
+ </label>
272
+ <input
273
+ id="svg-port-size"
274
+ type="number"
275
+ min={8}
276
+ value={glyphSize}
277
+ onChange={(event) => setGlyphSize(Math.max(8, Number(event.target.value) || 8))}
278
+ style={{ padding: '6px 8px' }}
279
+ />
280
+ <label htmlFor="svg-port-rotation" style={{ fontWeight: 600, fontSize: 12 }}>
281
+ Rotation Angle
282
+ </label>
283
+ <input
284
+ id="svg-port-rotation"
285
+ type="number"
286
+ value={rotationAngle}
287
+ onChange={(event) => setRotationAngle(Number(event.target.value) || 0)}
288
+ style={{ padding: '6px 8px' }}
289
+ />
290
+ <label htmlFor="svg-port-anchor-x" style={{ fontWeight: 600, fontSize: 12 }}>
291
+ Anchor Offset X / Y
292
+ </label>
293
+ <div style={{ display: 'flex', gap: 6 }}>
294
+ <input
295
+ id="svg-port-anchor-x"
296
+ type="number"
297
+ value={anchorOffsetX}
298
+ onChange={(event) => setAnchorOffsetX(Number(event.target.value) || 0)}
299
+ style={{ padding: '6px 8px', width: '50%' }}
300
+ />
301
+ <input
302
+ type="number"
303
+ value={anchorOffsetY}
304
+ onChange={(event) => setAnchorOffsetY(Number(event.target.value) || 0)}
305
+ style={{ padding: '6px 8px', width: '50%' }}
306
+ />
307
+ </div>
308
+ </>
309
+ )}
310
+ <button
311
+ onClick={handleAddSvgShape}
312
+ style={{ padding: '8px 12px' }}
313
+ disabled={!svgPathInput.trim() || (addMode === 'port' && !selectedHost)}
314
+ >
315
+ {addMode === 'element' ? 'Add SVG Element' : 'Add SVG Port'}
316
+ </button>
317
+ <div style={{ fontSize: 12, color: '#555' }}>
318
+ Size: {defaultSvgPathSize.width} x {defaultSvgPathSize.height}
319
+ </div>
320
+ </div>
321
+ </div>
322
+ <DisplayBoxStage containerRef={containerRef} />
323
+ </section>
324
+ );
325
+ };
326
+
327
+ export default SvgPathDemo;