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.
Files changed (75) hide show
  1. package/CHANGELOG.md +42 -4
  2. package/README.md +15 -32
  3. package/ai/api-contract.json +88 -3
  4. package/ai/manifest.json +1 -1
  5. package/dist/cjs/examples.js +6519 -4423
  6. package/dist/cjs/index.js +1513 -120
  7. package/dist/cjs/types/api/createDiagramEditor.d.ts +4 -1
  8. package/dist/cjs/types/api/types.d.ts +43 -1
  9. package/dist/cjs/types/displaybox/demos/LinkColorPoolDemoTab.d.ts +3 -0
  10. package/dist/cjs/types/displaybox/demos/OffsetAnchorAvoidanceDemoTab.d.ts +3 -0
  11. package/dist/cjs/types/displaybox/demos/SimpleDemo.d.ts +4 -1
  12. package/dist/cjs/types/displaybox/demos/ZoomToFitElementsDemoTab.d.ts +3 -0
  13. package/dist/cjs/types/displaybox/demos/linkColorPoolDemo.d.ts +2 -0
  14. package/dist/cjs/types/displaybox/demos/offsetAnchorAvoidanceDemo.d.ts +4 -0
  15. package/dist/cjs/types/displaybox/demos/textDemo.d.ts +3 -0
  16. package/dist/cjs/types/displaybox/demos/zoomToFitElementsDemo.d.ts +4 -0
  17. package/dist/cjs/types/displaybox/types.d.ts +3 -1
  18. package/dist/cjs/types/displaybox/useDemoEditor.d.ts +4 -2
  19. package/dist/cjs/types/engine/AutoLayoutService.d.ts +59 -2
  20. package/dist/cjs/types/engine/DiagramEngine.d.ts +20 -1
  21. package/dist/cjs/types/engine/LinkRoutingService.d.ts +9 -0
  22. package/dist/cjs/types/models/TextModel.d.ts +1 -0
  23. package/dist/cjs/types/renderer/konva/KonvaInteraction.d.ts +6 -0
  24. package/dist/cjs/types/strategies/ObstacleRouter.d.ts +2 -0
  25. package/dist/cjs/types/strategies/RouterStrategy.d.ts +10 -0
  26. package/dist/esm/examples.js +6519 -4423
  27. package/dist/esm/examples.js.map +1 -1
  28. package/dist/esm/index.js +1513 -120
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/types/api/createDiagramEditor.d.ts +4 -1
  31. package/dist/esm/types/api/types.d.ts +43 -1
  32. package/dist/esm/types/displaybox/demos/LinkColorPoolDemoTab.d.ts +3 -0
  33. package/dist/esm/types/displaybox/demos/OffsetAnchorAvoidanceDemoTab.d.ts +3 -0
  34. package/dist/esm/types/displaybox/demos/SimpleDemo.d.ts +4 -1
  35. package/dist/esm/types/displaybox/demos/ZoomToFitElementsDemoTab.d.ts +3 -0
  36. package/dist/esm/types/displaybox/demos/linkColorPoolDemo.d.ts +2 -0
  37. package/dist/esm/types/displaybox/demos/offsetAnchorAvoidanceDemo.d.ts +4 -0
  38. package/dist/esm/types/displaybox/demos/textDemo.d.ts +3 -0
  39. package/dist/esm/types/displaybox/demos/zoomToFitElementsDemo.d.ts +4 -0
  40. package/dist/esm/types/displaybox/types.d.ts +3 -1
  41. package/dist/esm/types/displaybox/useDemoEditor.d.ts +4 -2
  42. package/dist/esm/types/engine/AutoLayoutService.d.ts +59 -2
  43. package/dist/esm/types/engine/DiagramEngine.d.ts +20 -1
  44. package/dist/esm/types/engine/LinkRoutingService.d.ts +9 -0
  45. package/dist/esm/types/models/TextModel.d.ts +1 -0
  46. package/dist/esm/types/renderer/konva/KonvaInteraction.d.ts +6 -0
  47. package/dist/esm/types/strategies/ObstacleRouter.d.ts +2 -0
  48. package/dist/esm/types/strategies/RouterStrategy.d.ts +10 -0
  49. package/dist/examples.d.ts +56 -1
  50. package/dist/index.d.ts +60 -2
  51. package/docs/API_CONTRACT.md +73 -3
  52. package/docs/CAPABILITIES.md +8 -0
  53. package/docs/COMMANDS_EVENTS.md +8 -0
  54. package/docs/DOCUMENTATION_WORKFLOW.md +4 -1
  55. package/docs/INTEGRATION_PLAYBOOK.md +3 -0
  56. package/docs/STATE_INVARIANTS.md +4 -0
  57. package/package.json +1 -1
  58. package/src/displaybox/demos/AutoLayoutDemoTab.tsx +515 -381
  59. package/src/displaybox/demos/EngineEventsDemoTab.tsx +12 -11
  60. package/src/displaybox/demos/LinkColorPoolDemoTab.tsx +49 -0
  61. package/src/displaybox/demos/ObstacleRoutingDemoTab.tsx +52 -31
  62. package/src/displaybox/demos/OffsetAnchorAvoidanceDemoTab.tsx +30 -0
  63. package/src/displaybox/demos/SimpleDemo.tsx +18 -13
  64. package/src/displaybox/demos/TextLayoutDemoTab.tsx +18 -1
  65. package/src/displaybox/demos/ZoomToFitElementsDemoTab.tsx +29 -0
  66. package/src/displaybox/demos/autoLayoutDemo.ts +62 -26
  67. package/src/displaybox/demos/index.tsx +81 -57
  68. package/src/displaybox/demos/linkColorPoolDemo.ts +122 -0
  69. package/src/displaybox/demos/obstacleRoutingDemo.ts +77 -1
  70. package/src/displaybox/demos/offsetAnchorAvoidanceDemo.ts +211 -0
  71. package/src/displaybox/demos/routingDemo.ts +5 -5
  72. package/src/displaybox/demos/shared.ts +17 -12
  73. package/src/displaybox/demos/textDemo.ts +6 -2
  74. package/src/displaybox/demos/zoomToFitElementsDemo.ts +83 -0
  75. package/src/displaybox/types.ts +10 -8
@@ -0,0 +1,122 @@
1
+ import type { DiagramState } from '../../api';
2
+ import { createId } from '../../utils/ids';
3
+ import type { DemoConfig } from '../types';
4
+ import { baseElementShapes, basePortShapes } from './shared';
5
+
6
+ const createLinkColorPoolState = (): DiagramState => ({
7
+ elements: [
8
+ {
9
+ id: 'lc-off-source',
10
+ position: { x: 80, y: 170 },
11
+ size: { width: 190, height: 120 },
12
+ shapeId: 'default',
13
+ },
14
+ {
15
+ id: 'lc-off-target',
16
+ position: { x: 350, y: 170 },
17
+ size: { width: 190, height: 120 },
18
+ shapeId: 'default',
19
+ },
20
+ {
21
+ id: 'lc-default-source',
22
+ position: { x: 80, y: 360 },
23
+ size: { width: 190, height: 120 },
24
+ shapeId: 'panel',
25
+ },
26
+ {
27
+ id: 'lc-default-target',
28
+ position: { x: 350, y: 360 },
29
+ size: { width: 190, height: 120 },
30
+ shapeId: 'panel',
31
+ },
32
+ {
33
+ id: 'lc-custom-source',
34
+ position: { x: 620, y: 170 },
35
+ size: { width: 190, height: 120 },
36
+ shapeId: 'default',
37
+ },
38
+ {
39
+ id: 'lc-custom-target',
40
+ position: { x: 620, y: 360 },
41
+ size: { width: 190, height: 120 },
42
+ shapeId: 'default',
43
+ },
44
+ ],
45
+ ports: [
46
+ { id: 'lc-off-source-port', elementId: 'lc-off-source', position: { x: 170, y: 60 }, shapeId: 'port-circle' },
47
+ { id: 'lc-off-target-port', elementId: 'lc-off-target', position: { x: 20, y: 60 }, shapeId: 'port-circle' },
48
+ { id: 'lc-default-source-port', elementId: 'lc-default-source', position: { x: 170, y: 60 }, shapeId: 'port-circle' },
49
+ { id: 'lc-default-target-port', elementId: 'lc-default-target', position: { x: 20, y: 60 }, shapeId: 'port-circle' },
50
+ { id: 'lc-custom-source-port', elementId: 'lc-custom-source', position: { x: 95, y: 110 }, shapeId: 'port-circle' },
51
+ { id: 'lc-custom-target-port', elementId: 'lc-custom-target', position: { x: 95, y: 10 }, shapeId: 'port-circle' },
52
+ ],
53
+ links: [
54
+ {
55
+ id: 'lc-reference-link',
56
+ sourcePortId: 'lc-off-source-port',
57
+ targetPortId: 'lc-off-target-port',
58
+ points: [],
59
+ style: { stroke: '#374151', strokeWidth: 2 },
60
+ },
61
+ ],
62
+ texts: [
63
+ {
64
+ id: 'lc-intro',
65
+ content:
66
+ 'Use Color Mode above: Off keeps baseline link color behavior. Default Pool enables random colors from the built-in 20-color pool. Custom Pool restricts random colors to #0b7285, #e8590c, #5f3dc4.',
67
+ position: { x: 80, y: 86 },
68
+ },
69
+ { id: 'lc-off-label', content: 'Off scenario pair', ownerId: 'lc-off-source', position: { x: 10, y: -16 } },
70
+ { id: 'lc-default-label', content: 'Default-pool scenario pair', ownerId: 'lc-default-source', position: { x: 10, y: -16 } },
71
+ { id: 'lc-custom-label', content: 'Custom-pool scenario pair', ownerId: 'lc-custom-source', position: { x: 10, y: -16 } },
72
+ ],
73
+ });
74
+
75
+ export const linkColorPoolDemoConfig: DemoConfig = {
76
+ id: 'link-color-pool',
77
+ title: 'Link Color Pool',
78
+ description: 'Verify disabled/default/custom random link-color assignment on new links.',
79
+ createState: createLinkColorPoolState,
80
+ elementShapes: baseElementShapes,
81
+ portShapes: basePortShapes,
82
+ defaultElementShapeId: 'default',
83
+ defaultPortShapeId: 'port-circle',
84
+ actions: [
85
+ {
86
+ id: 'add-link-off-pair',
87
+ label: 'Add link (off pair)',
88
+ run: (editor) => {
89
+ editor.addLink({
90
+ id: `link-color-off-${createId()}`,
91
+ sourcePortId: 'lc-off-source-port',
92
+ targetPortId: 'lc-off-target-port',
93
+ points: [],
94
+ });
95
+ },
96
+ },
97
+ {
98
+ id: 'add-link-default-pair',
99
+ label: 'Add link (default pair)',
100
+ run: (editor) => {
101
+ editor.addLink({
102
+ id: `link-color-default-${createId()}`,
103
+ sourcePortId: 'lc-default-source-port',
104
+ targetPortId: 'lc-default-target-port',
105
+ points: [],
106
+ });
107
+ },
108
+ },
109
+ {
110
+ id: 'add-link-custom-pair',
111
+ label: 'Add link (custom pair)',
112
+ run: (editor) => {
113
+ editor.addLink({
114
+ id: `link-color-custom-${createId()}`,
115
+ sourcePortId: 'lc-custom-source-port',
116
+ targetPortId: 'lc-custom-target-port',
117
+ points: [],
118
+ });
119
+ },
120
+ },
121
+ ],
122
+ };
@@ -31,6 +31,17 @@ const obstacleRoutingLinks: LinkData[] = [
31
31
  points: [],
32
32
  routing: 'auto',
33
33
  },
34
+ {
35
+ id: 'cross-branch-corridor-link',
36
+ sourcePortId: 'corridor-left-port',
37
+ targetPortId: 'corridor-right-port',
38
+ routing: 'auto',
39
+ points: [
40
+ { x: 310, y: 560 },
41
+ { x: 470, y: 500 },
42
+ { x: 630, y: 560 },
43
+ ],
44
+ },
34
45
  ];
35
46
 
36
47
  const obstacleRoutingShapes: SimpleShape[] = [
@@ -94,6 +105,45 @@ const createObstacleRoutingState = (): DiagramState => ({
94
105
  parentId: 'attach-parent',
95
106
  portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
96
107
  },
108
+ {
109
+ id: 'corridor-root',
110
+ position: { x: 40, y: 390 },
111
+ size: { width: 940, height: 260 },
112
+ shapeId: 'panel',
113
+ style: { fill: '#f5f9ff' },
114
+ },
115
+ {
116
+ id: 'corridor-left-branch',
117
+ position: { x: 80, y: 40 },
118
+ size: { width: 260, height: 170 },
119
+ shapeId: 'routing-ellipse',
120
+ parentId: 'corridor-root',
121
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
122
+ },
123
+ {
124
+ id: 'corridor-right-branch',
125
+ position: { x: 560, y: 50 },
126
+ size: { width: 280, height: 170 },
127
+ shapeId: 'routing-ellipse',
128
+ parentId: 'corridor-root',
129
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
130
+ },
131
+ {
132
+ id: 'corridor-left-leaf',
133
+ position: { x: 120, y: 60 },
134
+ size: { width: 120, height: 70 },
135
+ shapeId: 'default',
136
+ parentId: 'corridor-left-branch',
137
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
138
+ },
139
+ {
140
+ id: 'corridor-right-leaf',
141
+ position: { x: 50, y: 70 },
142
+ size: { width: 120, height: 70 },
143
+ shapeId: 'default',
144
+ parentId: 'corridor-right-branch',
145
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
146
+ },
97
147
  ],
98
148
  ports: [
99
149
  {
@@ -142,6 +192,22 @@ const createObstacleRoutingState = (): DiagramState => ({
142
192
  externalLinkAttachPoint: { x: 12, y: 0 },
143
193
  internalLinkAttachPoint: { x: -12, y: 0 },
144
194
  },
195
+ {
196
+ id: 'corridor-left-port',
197
+ elementId: 'corridor-left-leaf',
198
+ position: { x: 120, y: 35 },
199
+ shapeId: 'port-dark',
200
+ anchorCenter: true,
201
+ currentAnchorId: 'right',
202
+ },
203
+ {
204
+ id: 'corridor-right-port',
205
+ elementId: 'corridor-right-leaf',
206
+ position: { x: 0, y: 35 },
207
+ shapeId: 'port-dark',
208
+ anchorCenter: true,
209
+ currentAnchorId: 'left',
210
+ },
145
211
  ],
146
212
  links: obstacleRoutingLinks,
147
213
  texts: [
@@ -176,6 +242,16 @@ const createObstacleRoutingState = (): DiagramState => ({
176
242
  content: 'child-A->child-B: sibling endpoints EXTERNAL on both ends',
177
243
  position: { x: 620, y: 394 },
178
244
  },
245
+ {
246
+ id: 'corridor-group-label',
247
+ content: 'Scenario C: Cross-branch route should leave source branch early, travel at ancestor corridor, and descend near target.',
248
+ position: { x: 52, y: 360 },
249
+ },
250
+ {
251
+ id: 'corridor-redraw-label',
252
+ content: 'The cross-branch link starts with stale points. Toggle redraw refresh and click Manual Render to compare behavior.',
253
+ position: { x: 52, y: 676 },
254
+ },
179
255
  ],
180
256
  });
181
257
 
@@ -202,7 +278,7 @@ const rerouteAllAction: DemoAction = {
202
278
  export const obstacleRoutingDemoConfig: DemoConfig = {
203
279
  id: 'obstacle-routing',
204
280
  title: 'Obstacle Routing',
205
- description: 'Nested multi-anchor checks: sibling interior-avoidance + directional internal/external parent-child attach semantics.',
281
+ description: 'Nested routing checks: sibling interior-avoidance, directional parent-child attach semantics, and cross-parent common-ancestor corridor preference.',
206
282
  createState: createObstacleRoutingState,
207
283
  elementShapes: obstacleRoutingShapes,
208
284
  portShapes: basePortShapes,
@@ -0,0 +1,211 @@
1
+ import type { DiagramState, SimpleShape } from '../../api';
2
+ import type { DemoAction, DemoConfig } from '../types';
3
+ import { baseElementShapes, basePortShapes } from './shared';
4
+
5
+ const offsetAnchorShapes: SimpleShape[] = [
6
+ ...baseElementShapes,
7
+ {
8
+ id: 'offset-anchor-ellipse',
9
+ kind: 'ellipse',
10
+ style: {
11
+ fill: '#eef8ff',
12
+ stroke: '#2563eb',
13
+ strokeWidth: 2,
14
+ },
15
+ },
16
+ ];
17
+
18
+ export const createOffsetAnchorAvoidanceState = (): DiagramState => ({
19
+ elements: [
20
+ {
21
+ id: 'offset-sibling-parent',
22
+ position: { x: 40, y: 110 },
23
+ size: { width: 520, height: 250 },
24
+ shapeId: 'panel',
25
+ style: { fill: '#faf7ef' },
26
+ },
27
+ {
28
+ id: 'offset-sibling-a',
29
+ position: { x: 60, y: 78 },
30
+ size: { width: 130, height: 96 },
31
+ shapeId: 'offset-anchor-ellipse',
32
+ parentId: 'offset-sibling-parent',
33
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
34
+ },
35
+ {
36
+ id: 'offset-sibling-b',
37
+ position: { x: 320, y: 78 },
38
+ size: { width: 130, height: 96 },
39
+ shapeId: 'offset-anchor-ellipse',
40
+ parentId: 'offset-sibling-parent',
41
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
42
+ },
43
+ {
44
+ id: 'offset-direction-parent',
45
+ position: { x: 620, y: 110 },
46
+ size: { width: 330, height: 250 },
47
+ shapeId: 'offset-anchor-ellipse',
48
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
49
+ },
50
+ {
51
+ id: 'offset-direction-child',
52
+ position: { x: 48, y: 74 },
53
+ size: { width: 128, height: 92 },
54
+ shapeId: 'default',
55
+ parentId: 'offset-direction-parent',
56
+ portMovement: { moveMode: 'anchors', anchorConstraint: { preset: 'cardinal' } },
57
+ },
58
+ {
59
+ id: 'offset-direction-sibling',
60
+ position: { x: 190, y: 126 },
61
+ size: { width: 96, height: 70 },
62
+ shapeId: 'default',
63
+ parentId: 'offset-direction-parent',
64
+ style: { fill: '#fff6e8', stroke: '#b45309', strokeWidth: 2 },
65
+ },
66
+ ],
67
+ ports: [
68
+ {
69
+ id: 'offset-sibling-a-port',
70
+ elementId: 'offset-sibling-a',
71
+ position: { x: 0, y: 48 },
72
+ shapeId: 'port-dark',
73
+ anchorCenter: true,
74
+ currentAnchorId: 'left',
75
+ moveMode: 'border',
76
+ externalLinkAttachPoint: { x: 0, y: 24 },
77
+ },
78
+ {
79
+ id: 'offset-sibling-b-port',
80
+ elementId: 'offset-sibling-b',
81
+ position: { x: 130, y: 48 },
82
+ shapeId: 'port-dark',
83
+ anchorCenter: true,
84
+ currentAnchorId: 'right',
85
+ moveMode: 'border',
86
+ externalLinkAttachPoint: { x: 0, y: 24 },
87
+ },
88
+ {
89
+ id: 'offset-parent-port',
90
+ elementId: 'offset-direction-parent',
91
+ position: { x: 18, y: 125 },
92
+ shapeId: 'port-circle',
93
+ anchorCenter: true,
94
+ currentAnchorId: 'left',
95
+ moveMode: 'border',
96
+ externalLinkAttachPoint: { x: 0, y: 18 },
97
+ internalLinkAttachPoint: { x: 0, y: -18 },
98
+ },
99
+ {
100
+ id: 'offset-child-port',
101
+ elementId: 'offset-direction-child',
102
+ position: { x: 128, y: 46 },
103
+ shapeId: 'port-circle',
104
+ anchorCenter: true,
105
+ currentAnchorId: 'right',
106
+ moveMode: 'border',
107
+ externalLinkAttachPoint: { x: 0, y: 18 },
108
+ internalLinkAttachPoint: { x: 0, y: -18 },
109
+ },
110
+ ],
111
+ links: [
112
+ {
113
+ id: 'offset-sibling-link',
114
+ sourcePortId: 'offset-sibling-a-port',
115
+ targetPortId: 'offset-sibling-b-port',
116
+ points: [],
117
+ routing: 'auto',
118
+ },
119
+ {
120
+ id: 'offset-parent-child-link',
121
+ sourcePortId: 'offset-parent-port',
122
+ targetPortId: 'offset-child-port',
123
+ points: [],
124
+ routing: 'auto',
125
+ },
126
+ {
127
+ id: 'offset-child-parent-link',
128
+ sourcePortId: 'offset-child-port',
129
+ targetPortId: 'offset-parent-port',
130
+ points: [],
131
+ routing: 'auto',
132
+ },
133
+ ],
134
+ texts: [
135
+ {
136
+ id: 'offset-anchor-intro',
137
+ content:
138
+ 'Offset attach anchors now keep endpoint hosts forbidden during auto routing. Inspect that routes exit from projected border sides instead of cutting through the source or target host interior.',
139
+ position: { x: 40, y: 28 },
140
+ },
141
+ {
142
+ id: 'offset-sibling-label',
143
+ content: 'Scenario A: sibling children use external attach points offset beyond left/right edges.',
144
+ position: { x: 52, y: 82 },
145
+ },
146
+ {
147
+ id: 'offset-direction-label',
148
+ content: 'Scenario B: parent/child links reuse the same offset anchors but resolve internal vs external semantics by direction.',
149
+ position: { x: 620, y: 82 },
150
+ },
151
+ {
152
+ id: 'offset-sibling-a-text',
153
+ content: 'Child A\nexternal offset 24',
154
+ ownerId: 'offset-sibling-a',
155
+ position: { x: 14, y: -20 },
156
+ },
157
+ {
158
+ id: 'offset-sibling-b-text',
159
+ content: 'Child B\nexternal offset 24',
160
+ ownerId: 'offset-sibling-b',
161
+ position: { x: 14, y: -20 },
162
+ },
163
+ {
164
+ id: 'offset-direction-parent-text',
165
+ content: 'Parent port\ninternal 18 / external 18',
166
+ ownerId: 'offset-direction-parent',
167
+ position: { x: 48, y: -20 },
168
+ },
169
+ {
170
+ id: 'offset-direction-child-text',
171
+ content: 'Child port\nexternal 18 / internal 18',
172
+ ownerId: 'offset-direction-child',
173
+ position: { x: 6, y: -18 },
174
+ },
175
+ {
176
+ id: 'offset-direction-links-text',
177
+ content: 'Blue links: parent→child and child→parent should keep the host border side semantics even with offset attach coordinates.',
178
+ position: { x: 620, y: 378 },
179
+ },
180
+ ],
181
+ });
182
+
183
+ const rerouteAllAction: DemoAction = {
184
+ id: 'offset-reroute-links',
185
+ label: 'Reroute All Links',
186
+ run: (editor) => {
187
+ editor.rerouteAllLinks();
188
+ },
189
+ };
190
+
191
+ const shiftTargetsAction: DemoAction = {
192
+ id: 'offset-shift-targets',
193
+ label: 'Shift targets + reroute',
194
+ run: (editor) => {
195
+ editor.moveElementTo('offset-sibling-b', 320, 96);
196
+ editor.moveElementTo('offset-direction-child', 74, 96);
197
+ editor.rerouteAllLinks();
198
+ },
199
+ };
200
+
201
+ export const offsetAnchorAvoidanceDemoConfig: DemoConfig = {
202
+ id: 'offset-anchor-avoidance',
203
+ title: 'Offset Anchor Avoidance',
204
+ description: 'Visual QA for projected-boundary-equivalent offset attach anchors that must still keep endpoint host interiors forbidden.',
205
+ createState: createOffsetAnchorAvoidanceState,
206
+ elementShapes: offsetAnchorShapes,
207
+ portShapes: basePortShapes,
208
+ defaultElementShapeId: 'default',
209
+ defaultPortShapeId: 'port-circle',
210
+ actions: [rerouteAllAction, shiftTargetsAction],
211
+ };
@@ -43,11 +43,11 @@ const createRoutingDemoState = (): DiagramState => ({
43
43
  ],
44
44
  });
45
45
 
46
- export const routingDemoConfig: DemoConfig = ({
47
- id: 'routing-demo',
48
- title: 'Routing Demo',
49
- description: 'Auto routing uses an orthogonal router. Toggle routing to compare.',
50
- createState: createRoutingDemoState,
46
+ export const routingDemoConfig: DemoConfig = ({
47
+ id: 'routing-demo',
48
+ title: 'Routing Demo',
49
+ description: 'Auto routing is obstacle-aware. Toggle routing to compare manual vs auto reroute.',
50
+ createState: createRoutingDemoState,
51
51
  elementShapes: baseElementShapes,
52
52
  portShapes: basePortShapes,
53
53
  defaultElementShapeId: 'default',
@@ -1,7 +1,8 @@
1
- import type { CSSProperties } from 'react';
2
- import type { SimpleShape } from '../../api';
3
- import type { ToolboxItem } from '../types';
4
- import type { RouterStrategy } from '../../strategies/RouterStrategy';
1
+ import type { CSSProperties } from 'react';
2
+ import type { SimpleShape } from '../../api';
3
+ import type { ToolboxItem } from '../types';
4
+ import type { RouterStrategy } from '../../strategies/RouterStrategy';
5
+ import ObstacleRouter from '../../strategies/ObstacleRouter';
5
6
 
6
7
  export const baseElementShapes: SimpleShape[] = [
7
8
  {
@@ -135,14 +136,18 @@ export const externalToolboxItems: ToolboxItem[] = [
135
136
 
136
137
  export const defaultGhostSize = { width: 180, height: 120 };
137
138
 
138
- export const defaultSvgPath = 'M10 10 H110 V90 H10 Z';
139
- export const defaultSvgPathSize = { width: 180, height: 120 };
140
-
141
- export const orthogonalRouter: RouterStrategy = {
142
- route: (source, target) => {
143
- if (Math.abs(source.x - target.x) < 1 || Math.abs(source.y - target.y) < 1) {
144
- return [{ ...source }, { ...target }];
145
- }
139
+ export const defaultSvgPath = 'M10 10 H110 V90 H10 Z';
140
+ export const defaultSvgPathSize = { width: 180, height: 120 };
141
+ const obstacleAwareRouter = new ObstacleRouter();
142
+
143
+ export const orthogonalRouter: RouterStrategy = {
144
+ route: (source, target, context) => {
145
+ if ((context?.obstacles?.length ?? 0) > 0) {
146
+ return obstacleAwareRouter.route(source, target, context);
147
+ }
148
+ if (Math.abs(source.x - target.x) < 1 || Math.abs(source.y - target.y) < 1) {
149
+ return [{ ...source }, { ...target }];
150
+ }
146
151
  const midX = Math.round((source.x + target.x) / 2);
147
152
  return [
148
153
  { ...source },
@@ -1,4 +1,4 @@
1
- import type { DiagramState } from '../../api';
1
+ import type { DiagramState, TextInteractionPolicy } from '../../api';
2
2
  import type { DemoConfig } from '../types';
3
3
  import { baseElementShapes, basePortShapes } from './shared';
4
4
 
@@ -7,6 +7,8 @@ export const textDemoOwnedTextId = 'text-owned-overflow';
7
7
  export const textDemoFloatingTextId = 'text-floating-overflow';
8
8
  export const textDemoLongSample =
9
9
  'Long diagram label: middle ellipsis should keep the beginning and end visible while preserving the full canonical content in model state.';
10
+ export const textDemoOwnedInteraction: TextInteractionPolicy = { movable: false, editable: false };
11
+ export const textDemoFloatingInteraction: TextInteractionPolicy = { movable: true, editable: true };
10
12
 
11
13
  const createTextOwnershipState = (): DiagramState => ({
12
14
  elements: [
@@ -25,6 +27,7 @@ const createTextOwnershipState = (): DiagramState => ({
25
27
  content: textDemoLongSample,
26
28
  position: { x: 12, y: 12 },
27
29
  ownerId: textDemoOwnerId,
30
+ interaction: textDemoOwnedInteraction,
28
31
  layout: {
29
32
  boundsMode: 'owner-width',
30
33
  wrap: 'none',
@@ -36,6 +39,7 @@ const createTextOwnershipState = (): DiagramState => ({
36
39
  id: textDemoFloatingTextId,
37
40
  content: textDemoLongSample,
38
41
  position: { x: 470, y: 120 },
42
+ interaction: textDemoFloatingInteraction,
39
43
  layout: {
40
44
  boundsMode: 'fixed',
41
45
  fixedSize: { width: 240, height: 90 },
@@ -52,7 +56,7 @@ export const textDemoConfig: DemoConfig = ({
52
56
  id: 'text',
53
57
  title: 'Text Layout + Overflow',
54
58
  description:
55
- 'Owner-constrained and fixed-bounds text with wrap and ellipsis behavior. Full content is preserved in model state; truncation is visual-only.',
59
+ 'Owner-constrained and fixed-bounds text with wrap, ellipsis, and per-text interaction behavior. Full content is preserved in model state; truncation is visual-only.',
56
60
  createState: createTextOwnershipState,
57
61
  elementShapes: baseElementShapes,
58
62
  portShapes: basePortShapes,
@@ -0,0 +1,83 @@
1
+ import type { DiagramState } from '../../api';
2
+ import type { DemoAction, DemoConfig } from '../types';
3
+ import { baseElementShapes, basePortShapes } from './shared';
4
+
5
+ export const createZoomToFitElementsState = (): DiagramState => ({
6
+ elements: [
7
+ {
8
+ id: 'fit-node-a',
9
+ position: { x: 40, y: 60 },
10
+ size: { width: 140, height: 90 },
11
+ shapeId: 'default',
12
+ style: { fill: '#eef6ff', stroke: '#1d4ed8', strokeWidth: 2 },
13
+ },
14
+ {
15
+ id: 'fit-node-b',
16
+ position: { x: 760, y: 120 },
17
+ size: { width: 170, height: 110 },
18
+ shapeId: 'panel',
19
+ style: { fill: '#fef3c7', stroke: '#b45309', strokeWidth: 2 },
20
+ },
21
+ {
22
+ id: 'fit-node-c',
23
+ position: { x: 140, y: 420 },
24
+ size: { width: 220, height: 120 },
25
+ shapeId: 'default',
26
+ style: { fill: '#ecfdf5', stroke: '#047857', strokeWidth: 2 },
27
+ },
28
+ {
29
+ id: 'fit-node-d',
30
+ position: { x: 860, y: 430 },
31
+ size: { width: 150, height: 90 },
32
+ shapeId: 'default',
33
+ style: { fill: '#f5f3ff', stroke: '#6d28d9', strokeWidth: 2 },
34
+ },
35
+ ],
36
+ ports: [],
37
+ links: [],
38
+ texts: [
39
+ {
40
+ id: 'fit-text-intro',
41
+ content: 'Use actions to spread nodes and run the zoom-to-fit viewport command.',
42
+ position: { x: 40, y: 24 },
43
+ },
44
+ ],
45
+ });
46
+
47
+ const zoomFitAction: DemoAction = {
48
+ id: 'fit-zoom-default',
49
+ label: 'Run Zoom-To-Fit',
50
+ run: (editor) => {
51
+ editor.zoomToFitElements();
52
+ },
53
+ };
54
+
55
+ const zoomFitPaddingAction: DemoAction = {
56
+ id: 'fit-zoom-padding',
57
+ label: 'Run Zoom-To-Fit (Padding 48)',
58
+ run: (editor) => {
59
+ editor.zoomToFitElements({ padding: 48 });
60
+ },
61
+ };
62
+
63
+ const spreadSceneAction: DemoAction = {
64
+ id: 'fit-spread-scene',
65
+ label: 'Spread Scene',
66
+ run: (editor) => {
67
+ editor.moveElementTo('fit-node-b', 880, 80);
68
+ editor.moveElementTo('fit-node-d', 980, 500);
69
+ editor.moveElementTo('fit-node-c', 80, 520);
70
+ },
71
+ };
72
+
73
+ export const zoomToFitElementsDemoConfig: DemoConfig = {
74
+ id: 'zoom-to-fit-elements',
75
+ title: 'Zoom To Fit Elements',
76
+ description: 'Calls a viewport command that zooms and centers so all elements are visible.',
77
+ createState: createZoomToFitElementsState,
78
+ elementShapes: baseElementShapes,
79
+ portShapes: basePortShapes,
80
+ defaultElementShapeId: 'default',
81
+ defaultPortShapeId: 'port-circle',
82
+ actions: [zoomFitAction, zoomFitPaddingAction, spreadSceneAction],
83
+ };
@@ -1,6 +1,6 @@
1
- import type { CSSProperties } from 'react';
2
- import type { DiagramEditorHandle, DiagramState, SimpleShape } from '../api';
3
- import type { RouterStrategy } from '../strategies/RouterStrategy';
1
+ import type { CSSProperties } from 'react';
2
+ import type { DiagramEditorHandle, DiagramState, LinkColorPoolPolicy, LinkRouteRefreshPolicy, SimpleShape } from '../api';
3
+ import type { RouterStrategy } from '../strategies/RouterStrategy';
4
4
 
5
5
  export type DemoActionHelpers = {
6
6
  nextOffset: () => number;
@@ -26,11 +26,13 @@ export type DemoConfig = {
26
26
  portShapes?: SimpleShape[];
27
27
  defaultElementShapeId?: string;
28
28
  defaultPortShapeId?: string;
29
- router?: RouterStrategy;
30
- snapDefault?: boolean;
31
- stageStyle?: CSSProperties;
32
- actions: DemoAction[];
33
- };
29
+ router?: RouterStrategy;
30
+ snapDefault?: boolean;
31
+ linkRouteRefreshPolicy?: LinkRouteRefreshPolicy;
32
+ linkColorPoolPolicy?: LinkColorPoolPolicy;
33
+ stageStyle?: CSSProperties;
34
+ actions: DemoAction[];
35
+ };
34
36
 
35
37
  export type DemoDefinition = {
36
38
  id: string;