@unofficialbox/box-open-elements 0.24.0 → 0.26.0

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 (40) hide show
  1. package/README.md +13 -13
  2. package/dist/components/actions/button.d.ts +1 -0
  3. package/dist/components/actions/button.js +13 -0
  4. package/dist/components/forms/checkbox.d.ts +1 -0
  5. package/dist/components/forms/checkbox.js +2 -0
  6. package/dist/components/forms/code-editor.d.ts +28 -3
  7. package/dist/components/forms/code-editor.js +71 -6
  8. package/dist/components/forms/combobox.js +7 -8
  9. package/dist/components/forms/number-input.d.ts +1 -0
  10. package/dist/components/forms/number-input.js +4 -1
  11. package/dist/components/forms/select.d.ts +1 -0
  12. package/dist/components/forms/select.js +3 -1
  13. package/dist/components/forms/text-area.js +5 -7
  14. package/dist/components/forms/text-field.d.ts +1 -0
  15. package/dist/components/forms/text-field.js +2 -0
  16. package/dist/components/navigation/accordion.d.ts +7 -0
  17. package/dist/components/navigation/accordion.js +31 -8
  18. package/dist/components/output/code-block.js +3 -1
  19. package/dist/components/overlays/drawer.js +2 -2
  20. package/dist/foundations/geometry/tokens.d.ts +2 -2
  21. package/dist/foundations/geometry/tokens.js +3 -3
  22. package/dist/foundations/icons/box-iconography.generated.d.ts +472 -1
  23. package/dist/foundations/icons/box-iconography.generated.js +473 -2
  24. package/dist/patterns/flow-builder/flow-builder.d.ts +5 -1
  25. package/dist/patterns/flow-builder/flow-builder.js +18 -7
  26. package/dist/patterns/flow-builder/primitives.d.ts +1 -0
  27. package/dist/patterns/flow-builder/primitives.js +11 -4
  28. package/dist/patterns/process-modeler/bridge.d.ts +10 -0
  29. package/dist/patterns/process-modeler/bridge.js +173 -0
  30. package/dist/patterns/process-modeler/design.d.ts +2 -0
  31. package/dist/patterns/process-modeler/design.js +288 -0
  32. package/dist/patterns/process-modeler/geometry.d.ts +20 -0
  33. package/dist/patterns/process-modeler/geometry.js +330 -0
  34. package/dist/patterns/process-modeler/index.d.ts +2 -0
  35. package/dist/patterns/process-modeler/index.js +2 -0
  36. package/dist/patterns/process-modeler/model.d.ts +106 -1
  37. package/dist/patterns/process-modeler/model.js +2 -2
  38. package/dist/patterns/process-modeler/process-modeler.d.ts +115 -3
  39. package/dist/patterns/process-modeler/process-modeler.js +1864 -128
  40. package/package.json +4 -3
@@ -0,0 +1,288 @@
1
+ /** Riptide Diagram anatomy expressed through public Box Open Elements tokens. */
2
+ export const processModelerDesign = `
3
+ :host([hidden]), [hidden] { display: none !important; }
4
+ *, *::before, *::after { box-sizing: border-box; }
5
+ button, input, textarea, select { font: inherit; color: inherit; }
6
+ button { min-height: 36px; padding: 8px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 8px; background: var(--boe-token-surface-surface, #fff); cursor: pointer; }
7
+ button:disabled { opacity: .5; cursor: default; }
8
+ :is(button,input,textarea,select,[part=canvas],[part=minimap],[part=box],[part=frame]):focus-visible { outline: 2px solid var(--boe-token-surface-surface-brand, #0061d5); outline-offset: 2px; }
9
+ :host {
10
+ display: block;
11
+ min-width: 0;
12
+ --boe-process-box-edge: color-mix(in srgb, var(--boe-token-text-text, #141413) 55%, var(--boe-token-surface-surface, #fff));
13
+ color: var(--boe-token-text-text, #141413);
14
+ background: var(--boe-token-surface-surface, #fff);
15
+ font: 14px/1.5 var(--boe-token-font-family-base, Inter, sans-serif);
16
+ }
17
+ [part=sr-only] { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
18
+ [part=toolbar] {
19
+ min-height: 56px;
20
+ margin: 0;
21
+ padding: 8px 16px;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: flex-end;
25
+ gap: 8px;
26
+ flex-wrap: wrap;
27
+ border-bottom: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
28
+ background: var(--boe-token-surface-surface, #fff);
29
+ }
30
+ [part=toolbar] button, [part=controls] button, [part=selection-toolbar] button {
31
+ min-height: 30px;
32
+ padding: 4px 10px;
33
+ border: 1px solid transparent;
34
+ border-radius: 8px;
35
+ background: transparent;
36
+ font-size: 13px;
37
+ font-weight: 600;
38
+ }
39
+ [part=toolbar] svg, [part=controls] svg { width: 16px; height: 16px; display: block; }
40
+ [part=toolbar] [data-command=undo], [part=toolbar] [data-command=redo], [part=controls] button:not([data-command=reset]) { width: 30px; height: 30px; padding: 6px; display: grid; place-items: center; }
41
+ [part=controls] [data-command=snap][aria-pressed=true], [part=controls] [data-command=lock][aria-pressed=true] { background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 10%, var(--boe-token-surface-surface, #fff)); color: var(--boe-token-surface-surface-brand, #0061d5); }
42
+ [part=toolbar] button:hover, [part=controls] button:hover, [part=selection-toolbar] button:hover {
43
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
44
+ }
45
+ [part=toolbar] [data-command=tidy] { border-color: var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 18px; }
46
+ [part=view-switch] { display: flex; gap: 2px; padding: 3px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 20px; }
47
+ [part=view-switch] button { border-radius: 16px; }
48
+ [part=view-switch] button[aria-pressed=true] { border-color: var(--boe-token-stroke-stroke, #e8e8e8); background: var(--boe-token-surface-surface, #fff); }
49
+ [part=run-toggle] { display: flex; align-items: center; gap: 6px; white-space: nowrap; font-size: 13px; font-weight: 600; }
50
+ [part=run-toggle] input { accent-color: var(--boe-token-surface-surface-brand, #0061d5); }
51
+ [part=view-menu] { display: none; position: relative; }
52
+ [part=view-menu] summary { list-style: none; padding: 4px 14px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 18px; font-size: 13px; font-weight: 600; cursor: pointer; }
53
+ [part=view-menu][open] { z-index: 20; }
54
+ [part=view-menu-options] { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; width: 192px; padding: 6px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 10px; background: var(--boe-token-surface-surface, #fff); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); }
55
+ [part=view-menu] button { display: block; width: 100%; text-align: start; background: transparent; }
56
+ [part=layout] {
57
+ display: grid;
58
+ grid-template-columns: 248px minmax(0, 1fr) 320px;
59
+ gap: 0;
60
+ min-height: 0;
61
+ height: var(--boe-process-height, 660px);
62
+ overflow: hidden;
63
+ }
64
+ [part=pane-drawer] { display: contents; color: inherit; }
65
+ [part=pane-drawer]::backdrop, [part=insert-chooser]::backdrop { background: rgb(0 0 0 / .45); }
66
+ [part=pane-close], [data-command=palette], [data-command=details] { display: none; }
67
+ [part=palette], [part=inspector] {
68
+ min-width: 0;
69
+ overflow: auto;
70
+ padding: 12px;
71
+ border: 0;
72
+ border-radius: 0;
73
+ background: var(--boe-token-surface-surface, #fff);
74
+ }
75
+ [part=palette] { display: flex; flex-direction: column; gap: 12px; border-right: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); }
76
+ [part=skip-link] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
77
+ [part=skip-link]:focus-visible { position: static; width: auto; height: auto; clip-path: none; }
78
+ [part=inspector] { border-left: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); padding: 16px 0; }
79
+ [part=palette] label { font-size: 0; position: relative; }
80
+ [part=search-icon] { position: absolute; left: 12px; top: 9px; width: 18px; height: 18px; color: var(--boe-token-text-text-secondary, #6f6f6f); pointer-events: none; }
81
+ [part=palette] input {
82
+ width: 100%; min-width: 0;
83
+ height: 36px;
84
+ padding: 0 12px 0 36px;
85
+ border: 1px solid transparent;
86
+ border-radius: 24px;
87
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
88
+ font-size: 13px;
89
+ }
90
+ [part=palette] input:focus-visible { border-color: var(--boe-token-surface-surface-brand, #0061d5); background: var(--boe-token-surface-surface, #fff); }
91
+ [part=choices] { display: grid; align-content: start; gap: 4px; max-height: none; overflow: visible; }
92
+ [part=group] { margin: 4px 4px 6px; font-size: 12px; font-weight: 650; color: var(--boe-token-text-text-secondary, #6f6f6f); }
93
+ [part=choice] {
94
+ display: grid; grid-template-columns: 20px minmax(0,1fr); gap: 0 10px;
95
+ min-height: 44px; padding: 8px 10px; border: 1px solid transparent; border-radius: 10px;
96
+ text-align: start; background: transparent; cursor: grab;
97
+ }
98
+ [part=choice]:hover { border-color: var(--boe-token-stroke-stroke, #e8e8e8); background: var(--boe-token-surface-surface-hover, #f4f4f4); }
99
+ [part=choice-icon] { grid-row: span 2; width: 20px; height: 20px; color: var(--boe-token-text-text-secondary, #6f6f6f); }
100
+ [part=choice][data-tone=accent] [part=choice-icon] { color: var(--boe-token-surface-surface-brand, #0061d5); }
101
+ [part=choice-icon] svg { width: 20px; height: 20px; }
102
+ [part=choice-name] { font-size: 13px; font-weight: 600; line-height: 1.3; }
103
+ [part=choice-description] { grid-column: 2; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12px; line-height: 1.35; }
104
+ [part=palette-hint] { margin: 4px; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12px; line-height: 1.45; }
105
+ [part=canvas] {
106
+ position: relative; min-width: 0; height: 100%; overflow: hidden;
107
+ border: 0; border-radius: 0;
108
+ background-color: var(--boe-token-surface-surface-secondary, #fbfbfb);
109
+ background-image: radial-gradient(circle, var(--boe-token-stroke-stroke-hover, #d3d3d3) 1px, transparent 1.2px);
110
+ background-size: 12px 12px;
111
+ touch-action: none; user-select: none; cursor: grab;
112
+ }
113
+ [part=canvas][data-panning] { cursor: grabbing; }
114
+ [part=hold] { position: absolute; z-index: 12; top: 12px; left: 50%; translate: -50% 0; display: flex; align-items: center; gap: 12px; max-width: calc(100% - 24px); padding: 8px 12px; border: 1px solid var(--boe-token-stroke-status-stroke-warning, #9a6b00); border-radius: 8px; background: var(--boe-token-surface-status-surface-warning, #fff7df); color: var(--boe-token-text-status-text-warning, #6b4a00); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); font-size: 12.5px; cursor: default; }
115
+ [part=hold] button { min-height: 28px; padding: 3px 8px; border: 1px solid currentColor; border-radius: 6px; background: transparent; font-size: 12px; font-weight: 650; white-space: nowrap; }
116
+ [part=world] { position: absolute; inset: 0; transform-origin: 0 0; }
117
+ [part=lines] { position: absolute; inset: 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; z-index: 3; }
118
+ [part=line] { stroke: var(--boe-token-text-text-secondary, #6f6f6f); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; fill: none; }
119
+ [part=line][data-selected=true] { stroke: var(--boe-token-surface-surface-brand, #0061d5); stroke-width: 2; }
120
+ [part=line][data-drop=true] { stroke: var(--boe-token-surface-surface-brand, #0061d5); stroke-width: 2.5; stroke-dasharray: 6 4; }
121
+ [part=line-hit] { fill: none; stroke: transparent; stroke-width: 24; pointer-events: stroke; }
122
+ [part=connection-preview] { fill: none; stroke: var(--boe-token-surface-surface-brand, #0061d5); stroke-width: 2.5; stroke-dasharray: 6 4; }
123
+ [part=pinned-end] { fill: var(--boe-token-surface-surface-brand, #0061d5); pointer-events: none; }
124
+ [part=box], [part=frame] {
125
+ position: absolute; touch-action: none; text-align: start; z-index: 4; display: grid; grid-template-columns: 20px minmax(0,1fr); align-content: center;
126
+ gap: 2px 10px; min-width: 0; min-height: 64px; padding: 11px 12px;
127
+ border: 1px solid var(--boe-process-box-edge, var(--boe-control-edge, #858585));
128
+ border-radius: 8px; background: var(--boe-token-surface-surface, #fff);
129
+ font-size: 13px; line-height: 1.3;
130
+ transition: border-color var(--boe-profile-motion-interactive, 200ms) ease-out,
131
+ background var(--boe-profile-motion-interactive, 200ms) ease-out;
132
+ }
133
+ [part=box]:hover, [part=frame]:hover { border-color: var(--boe-token-text-text-secondary, #6f6f6f); }
134
+ [data-connect-target=true] { border-color: var(--boe-token-surface-surface-brand, #0061d5) !important; }
135
+ [part=box] strong, [part=frame] strong { overflow-wrap: anywhere; }
136
+ [part=box] strong, [part=frame] strong { font-size: 13.5px; font-weight: 650; line-height: 1.3; }
137
+ [part=box] small, [part=frame] small { grid-column: 2; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12.5px; line-height: 1.35; }
138
+ [part=technical-description] { font: 12px/1.4 ui-monospace, 'SF Mono', Menlo, Consolas, monospace !important; overflow-wrap: anywhere; }
139
+ [part=metrics] { grid-column: 2; font-size: 12px; font-variant-numeric: tabular-nums; }
140
+ [part=metric-warning] { color: var(--boe-token-text-status-text-warning, #8a5800); }
141
+ [part=icon] { grid-row: span 3; width: 20px; height: 20px; color: var(--boe-token-text-text-secondary, #6f6f6f); }
142
+ [part=icon] svg { display: block; width: 20px; height: 20px; }
143
+ [data-tone=accent] > [part=icon] { color: var(--boe-token-surface-surface-brand, #0061d5); }
144
+ [part=box][aria-current=true], [part=frame][aria-current=true] {
145
+ border: 1px solid var(--boe-token-surface-surface-brand, #0061d5);
146
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 7%, var(--boe-token-surface-surface, #fff));
147
+ }
148
+ [data-invalid=true] { border-color: var(--boe-token-text-status-text-error, #b92340) !important; }
149
+ [part=problem] { grid-column: 1 / -1; color: var(--boe-token-text-status-text-error, #b92340); font-size: 12px; }
150
+ [part=frame] { z-index: 1; align-content: start; border-radius: 12px; background: color-mix(in srgb, var(--boe-token-surface-surface, #fff) 78%, transparent); }
151
+ [part=frame] small { grid-column: 2; }
152
+ [part=loop-mark] { position: absolute; right: 12px; bottom: 8px; font-size: 20px; line-height: 1; color: var(--boe-token-text-text-secondary, #6f6f6f); }
153
+ [part=frame-resize] { position: absolute; right: -12px; bottom: -12px; z-index: 8; width: 28px; height: 28px; min-height: 28px; padding: 0; border: 0; border-radius: 8px; background: var(--boe-token-surface-surface, #fff); box-shadow: 0 0 0 1px var(--boe-process-box-edge, #858585); cursor: nwse-resize; }
154
+ [part=frame-resize]::before { content: ''; position: absolute; right: 7px; bottom: 7px; width: 11px; height: 11px; border-right: 2px solid var(--boe-token-text-text-secondary, #6f6f6f); border-bottom: 2px solid var(--boe-token-text-text-secondary, #6f6f6f); }
155
+ [part=frame]:not([aria-current=true]) [part=frame-resize] { visibility: hidden; }
156
+ [data-shape=gateway], [data-shape=event] { width: 56px !important; height: 56px !important; min-height: 0; padding: 0; border: 0 !important; background: transparent !important; display: block; overflow: visible; }
157
+ [data-shape=gateway]::before { content: ''; position: absolute; left: 8px; top: 8px; width: 40px; height: 40px; rotate: 45deg; border: 1.5px solid var(--boe-process-box-edge, var(--boe-control-edge, #858585)); border-radius: 8px; background: var(--boe-token-surface-surface, #fff); }
158
+ [data-shape=event]::before { content: ''; position: absolute; left: 8px; top: 8px; width: 40px; height: 40px; border: 1.5px solid var(--boe-process-box-edge, var(--boe-control-edge, #858585)); border-radius: 50%; background: var(--boe-token-surface-surface, #fff); }
159
+ [data-shape=event][data-kind=finish]::before { border-width: 4px; }
160
+ [data-shape=event][data-kind=scheduled-start]::after { content: ''; position: absolute; left: 13px; top: 13px; width: 30px; height: 30px; border: 1px solid currentColor; border-radius: 50%; }
161
+ [data-shape=gateway] > [part=icon], [data-shape=event] > [part=icon] { position: absolute; left: 18px; top: 18px; z-index: 1; }
162
+ [data-shape=gateway] > strong, [data-shape=event] > strong { position: absolute; top: 58px; left: 50%; width: 150px; translate: -50% 0; text-align: center; font-size: 12px; }
163
+ [data-shape=gateway] > small { display: none; }
164
+ [data-shape=event] > small { position: absolute; top: 75px; left: 50%; width: 172px; translate: -50% 0; text-align: center; font-size: 11px; line-height: 1.25; }
165
+ [data-shape=gateway][aria-current=true]::before, [data-shape=event][aria-current=true]::before { border-color: var(--boe-token-surface-surface-brand, #0061d5); background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 7%, var(--boe-token-surface-surface, #fff)); }
166
+ [part=port] { position: absolute; z-index: 5; width: 24px; height: 24px; min-height: 24px; padding: 0; border: 0; border-radius: 50%; background: transparent; opacity: 0; }
167
+ [part=port][data-side=north] { left: calc(50% - 12px); top: -12px; }
168
+ [part=port][data-side=south] { left: calc(50% - 12px); bottom: -12px; }
169
+ [part=port][data-side=east] { right: -12px; top: calc(50% - 12px); }
170
+ [part=port][data-side=west] { left: -12px; top: calc(50% - 12px); }
171
+ [part=port]::before { content: ''; display: block; width: 10px; height: 10px; margin: 7px; border: 1.5px solid var(--boe-token-surface-surface-brand, #0061d5); border-radius: 50%; background: var(--boe-token-surface-surface, #fff); }
172
+ [part=port]:hover::before, [part=port]:focus-visible::before { width: 22px; height: 22px; margin: 1px; background: var(--boe-token-surface-surface-brand, #0061d5); }
173
+ [part=port]::after { position: absolute; inset: 1px; display: none; place-items: center; color: #fff; font-size: 15px; font-weight: 700; line-height: 1; }
174
+ [part=port][data-side=north]::after { content: '↑'; }
175
+ [part=port][data-side=east]::after { content: '→'; }
176
+ [part=port][data-side=south]::after { content: '↓'; }
177
+ [part=port][data-side=west]::after { content: '←'; }
178
+ [part=port]:hover::after, [part=port]:focus-visible::after, [part=port][data-hot=true]::after { display: grid; }
179
+ [part=port][data-hot=true] { opacity: 1; }
180
+ [part=port][data-hot=true]::before { width: 22px; height: 22px; margin: 1px; background: var(--boe-token-surface-surface-brand, #0061d5); }
181
+ [part=end-grip] { position: absolute; z-index: 8; width: 24px; height: 24px; min-height: 24px; padding: 0; border: 0; border-radius: 50%; background: transparent; }
182
+ [part=end-grip]::before { content: ''; display: block; width: 10px; height: 10px; margin: 7px; border: 1.5px solid var(--boe-token-surface-surface-brand, #0061d5); border-radius: 50%; background: var(--boe-token-surface-surface, #fff); }
183
+ [part=box]:hover [part=port], [part=box]:focus-within [part=port], [part=box][aria-current=true] [part=port], [part=frame]:hover [part=port] { opacity: 1; }
184
+ [part=connection] { position: absolute; z-index: 5; display: flex; align-items: center; gap: 4px; transform: translate(-50%,-50%); min-width: 0; min-height: 0; padding: 2px 6px; border-radius: 6px; background: var(--boe-token-surface-surface-secondary, #fbfbfb); font-size: 12px; font-weight: 600; white-space: nowrap; }
185
+ [part=connection]:has(> span:empty) { padding: 0; background: transparent; min-width: 0; min-height: 0; }
186
+ [part=connection-actions] { position: absolute; top: 100%; left: 50%; transform: translateX(-50%); display: flex; gap: 4px; width: max-content; padding: 4px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 8px; background: var(--boe-token-surface-surface, #fff); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); opacity: 0; pointer-events: none; }
187
+ [part=connection]:hover [part=connection-actions], [part=connection]:focus-within [part=connection-actions] { opacity: 1; pointer-events: auto; }
188
+ [part=connection-actions] button { min-height: 32px; padding: 4px; font-size: 12px; }
189
+ [part=note] { width: 208px; border: 1px dashed var(--boe-process-box-edge, var(--boe-control-edge, #858585)); border-radius: 8px; }
190
+ [part=section] { z-index: 0; border: 1px solid var(--boe-process-box-edge, var(--boe-control-edge, #858585)); border-radius: 16px; background: color-mix(in srgb, var(--boe-token-surface-surface, #fff) 45%, transparent); }
191
+ [part=section] strong { display: block; font-size: 12.5px; font-weight: 650; line-height: 1.3; }
192
+ [part=section] small { display: block; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 11.5px; line-height: 1.35; }
193
+ [part=note], [part=section] { position: absolute; padding: 8px; pointer-events: none; }
194
+ [part=marquee] { position: absolute; border: 2px solid var(--boe-token-surface-surface-brand, #0061d5); background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 12%, transparent); pointer-events: none; }
195
+ [part=connect-tooltip] { position: absolute; z-index: 20; max-width: 240px; padding: 5px 8px; border-radius: 7px; background: var(--boe-token-text-text, #141413); color: var(--boe-token-surface-surface, #fff); font-size: 12px; pointer-events: none; box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); }
196
+ [part=connect-tooltip][data-invalid=true] { background: var(--boe-token-text-status-text-error, #b92340); color: #fff; }
197
+ [part=ghost-box], [part=palette-ghost] { position: absolute; z-index: 5; display: flex; align-items: center; padding: 11px 12px; border: 1.5px dashed var(--boe-token-surface-surface-brand, #0061d5); border-radius: 8px; background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 10%, var(--boe-token-surface-surface, #fff)); font-size: 13px; font-weight: 650; pointer-events: none; }
198
+ [part=guide] { position: absolute; background: var(--boe-token-surface-surface-brand, #0061d5); pointer-events: none; }
199
+ [part=measure] { position: absolute; padding: 4px; background: var(--boe-token-surface-surface, #fff); font-size: 12px; pointer-events: none; }
200
+ [part=insert-chooser] { max-width: calc(100vw - 32px); max-height: 65vh; overflow: auto; padding: 12px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 12px; background: var(--boe-token-surface-surface, #fff); }
201
+ [part=controls] { position: absolute; left: 16px; bottom: 16px; z-index: 10; display: flex; align-items: center; gap: 2px; padding: 4px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 12px; background: var(--boe-token-surface-surface, #fff); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); }
202
+ [part=controls] [data-command=reset] { min-width: 56px; font-variant-numeric: tabular-nums; }
203
+ [part=minimap] { position: absolute; right: 16px; bottom: 16px; width: 208px; height: 136px; touch-action: none; background: var(--boe-token-surface-surface, #fff); border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 12px; box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); }
204
+ [part=minimap] rect:not([data-viewport]) { fill: var(--boe-token-stroke-stroke-hover, #d3d3d3); }
205
+ [part=minimap] [data-viewport] { fill: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 9%, transparent); stroke: var(--boe-token-surface-surface-brand, #0061d5); stroke-width: 1; }
206
+ [part=selection-toolbar] { position: absolute; bottom: auto; z-index: 11; display: flex; flex-wrap: nowrap; gap: 2px; padding: 4px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 12px; background: var(--boe-token-surface-surface, #fff); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); white-space: nowrap; }
207
+ [part=checks] { display: grid; gap: 6px; margin-top: 12px; }
208
+ [part=checks] button { color: var(--boe-token-text-status-text-error, #b92340); }
209
+ [part=process-heading] { display: grid; gap: 2px; padding: 0 16px 12px; }
210
+ :host([embed-mode]) [part=process-heading], :host([embed-mode]) [part=view-switch], :host([embed-mode]) [part=run-toggle], :host([embed-mode]) [part=view-menu] { display: none; }
211
+ [part=process-title] { font-size: 15px; line-height: 1.4; }
212
+ [part=process-summary] { color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12.5px; }
213
+ [part=pane-tabs] { display: flex; flex-wrap: nowrap; gap: 4px; margin: 0; padding: 0 16px; border-bottom: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); overflow-x: auto; }
214
+ [part=pane-tabs] button { flex: none; min-height: 40px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: transparent; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 13px; font-weight: 600; }
215
+ [part=pane-tabs] button[aria-selected=true] { color: var(--boe-token-text-text, #141413); border-bottom-color: currentColor; }
216
+ [part=editor], [part=pane-content] { padding: 8px 16px; }
217
+ [part=editor] { display: grid; align-content: start; gap: 12px; }
218
+ [part=inspector-kind] { display: none; }
219
+ [part=inspector-purpose] { margin: 0; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12.5px; }
220
+ [part=field] { display: grid; gap: 4px; }
221
+ [part=field] label { display: grid; gap: 5px; font-size: 12.5px; font-weight: 650; }
222
+ [part=field] :is(input:not([type=checkbox]),textarea,select) { width: 100%; min-height: 36px; padding: 7px 10px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 8px; background: var(--boe-token-surface-surface, #fff); color: var(--boe-token-text-text, #141413); font-family: inherit; font-size: 13px; line-height: 1.4; }
223
+ [part=field] textarea { min-height: 76px; resize: vertical; }
224
+ [part=field]:has([part=action-options]) { position: relative; }
225
+ [part=field]:has([part=action-options]) input { padding-right: 34px; }
226
+ [part=action-caret] { position: absolute; z-index: 1; top: 25px; right: 2px; width: 32px; min-height: 32px; padding: 0; border: 0; background: transparent; font-size: 18px; }
227
+ [part=action-options] { position: absolute; z-index: 30; top: calc(100% - 1px); left: 0; right: 0; max-height: 280px; overflow: auto; padding: 6px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 10px; background: var(--boe-token-surface-surface, #fff); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .16)); }
228
+ [part=action-options][hidden] { display: none; }
229
+ [part=action-options] button { display: block; width: 100%; min-height: 36px; padding: 7px 9px; border: 0; border-radius: 7px; text-align: start; background: transparent; font-size: 12.5px; }
230
+ [part=action-options] button:hover, [part=action-options] [aria-selected=true] { background: var(--boe-token-surface-surface-hover, #f4f4f4); }
231
+ [part=action-options] button strong { display: block; font-size: 13px; font-weight: 650; }
232
+ [part=action-options] button small { display: block; color: var(--boe-token-text-text-secondary, #6f6f6f); }
233
+ [part=action-options] [part=action-back] { color: var(--boe-token-text-text-secondary, #6f6f6f); }
234
+ [part=field] input[type=checkbox] { justify-self: start; width: 18px; height: 18px; accent-color: var(--boe-token-surface-surface-brand, #0061d5); }
235
+ [part=field] small { color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12px; line-height: 1.4; }
236
+ [part=field] [part=field-problem] { color: var(--boe-token-text-status-text-error, #b92340); }
237
+ [part=variable-chips] { display: flex; flex-wrap: wrap; gap: 4px; }
238
+ [part=variable-chips] button { min-height: 24px; padding: 2px 8px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 12px; background: var(--boe-token-surface-surface-secondary, #fbfbfb); font-size: 11.5px; }
239
+ [part=leads-to] { display: grid; gap: 6px; padding-top: 10px; border-top: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); }
240
+ [part=leads-to] h3 { margin: 0; font-size: 12.5px; }
241
+ [part=lead-row] { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12.5px; }
242
+ [part=lead-row] button, [part=leads-to] > button, [part=inspector-actions] button { min-height: 28px; padding: 4px 8px; font-size: 12px; }
243
+ [part=inspector-metrics] { margin: 0; padding-top: 10px; border-top: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); font-size: 12px; font-variant-numeric: tabular-nums; }
244
+ [part=inspector-metrics] h3 { margin: 0 0 6px; font-size: 12.5px; }
245
+ [part=inspector-metrics] dl { display: grid; grid-template-columns: 1fr auto; gap: 4px 8px; margin: 0; }
246
+ [part=inspector-metrics] dt { color: var(--boe-token-text-text-secondary, #6f6f6f); }
247
+ [part=inspector-metrics] dd { margin: 0; font-weight: 650; }
248
+ [part=inspector-actions] { display: flex; gap: 8px; padding-top: 10px; border-top: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); }
249
+ [part=arrange-actions] { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 6px; }
250
+ [part=arrange-actions] button { min-height: 32px; padding: 5px 7px; font-size: 12px; text-align: start; }
251
+ [part=outline-intro] { margin: 4px 0 14px; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12.5px; line-height: 1.5; }
252
+ [part=copy-outline] { justify-self: start; min-height: 28px; margin-bottom: 8px; padding: 4px 10px; border-radius: 16px; font-size: 12px; font-weight: 650; }
253
+ [part=outline-list] { margin: 0; padding-left: 18px; display: grid; gap: 8px; }
254
+ [part=outline-list] [part=outline-list] { margin: 6px 0 0; gap: 6px; }
255
+ [part=outline-list] li { padding-left: 0; font-size: 12.5px; }
256
+ [part=outline-list] li::marker { color: var(--boe-token-text-text-secondary, #6f6f6f); }
257
+ [part=outline-list] button, [part=outline-list] strong { border: 0; border-radius: 4px; background: transparent; padding: 0; min-height: 0; font-size: 12.5px; font-weight: 650; line-height: 1.45; text-align: start; }
258
+ [part=outline-list] button:hover { color: var(--boe-token-surface-surface-brand, #0061d5); text-decoration: underline; }
259
+ [part=outline-list] button[aria-current=true] { color: var(--boe-token-surface-surface-brand, #0061d5); }
260
+ [part=outline-list] p { margin: 2px 0 0; color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 12px; line-height: 1.45; }
261
+ [part=inspector-heading] { margin: 0 0 2px; font-size: 15px; font-weight: 650; }
262
+ [part=inspector-heading][data-single=true]:not(:focus-visible) { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
263
+ [part=checks] { margin: 0; padding: 0; }
264
+ [part=checks] button { width: 100%; border: 0; border-bottom: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 0; text-align: start; background: transparent; }
265
+ [part=variable-row], [part=connection-row] { display: grid; gap: 4px; padding: 10px 0; border-bottom: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); font-size: 12.5px; }
266
+ [part=variable-row] p { margin: 0; color: var(--boe-token-text-text-secondary, #6f6f6f); }
267
+ [part=variable-scope] { color: var(--boe-token-text-text-secondary, #6f6f6f); font-size: 11.5px; }
268
+ [part=variable-row] label { display: grid; gap: 4px; font-weight: 650; }
269
+ [part=variable-row] input { min-height: 36px; padding: 7px 10px; border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8); border-radius: 8px; background: var(--boe-token-surface-surface, #fff); }
270
+ [part=help] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
271
+ :host([data-narrow]) [part=layout] { grid-template-columns: minmax(0,1fr); }
272
+ :host([data-narrow]) [part=pane-drawer] { display: none; position: fixed; z-index: 40; margin: 0; max-width: none; max-height: none; padding: 0; overflow: auto; border: 0; border-radius: 0; background: var(--boe-token-surface-surface, #fff); box-shadow: var(--boe-shadow-overlay, 0 4px 12px rgb(0 0 0 / .24)); }
273
+ :host([data-narrow]) [part=pane-drawer][open] { display: block; }
274
+ :host([data-narrow]) [part=pane-close], :host([data-narrow]) [data-command=palette], :host([data-narrow]) [data-command=details] { display: inline-block; }
275
+ :host([data-narrow]) [part=pane-close] { position: absolute; top: 12px; right: 12px; z-index: 1; min-height: 28px; padding: 4px 9px; border-radius: 14px; font-size: 12px; }
276
+ :host([data-narrow]) [part=pane-drawer][data-pane=palette] { left: 0; top: 0; bottom: 0; width: min(320px, 86vw); }
277
+ :host([data-narrow]) [part=pane-drawer][data-pane=inspector] { left: 0; right: 0; top: auto; bottom: 0; width: 100%; height: min(62vh, 560px); border-radius: 24px 24px 0 0; }
278
+ :host([data-narrow]) [part=pane-drawer][data-pane=inspector]::before { content: ''; display: block; width: 36px; height: 4px; margin: 6px auto; border-radius: 2px; background: var(--boe-token-surface-surface-secondary, #e8e8e8); }
279
+ :host([data-narrow]) [part=palette], :host([data-narrow]) [part=inspector] { height: 100%; }
280
+ :host([data-narrow]) [part=palette] { padding-top: 54px; }
281
+ :host([data-narrow]) [part=minimap] { display: none; }
282
+ :host([data-narrow]) [part=toolbar] { justify-content: flex-start; }
283
+ :host([data-narrow]) [part=view-switch], :host([data-narrow]) [part=run-toggle] { display: none; }
284
+ :host([data-narrow]) [part=view-menu] { display: block; }
285
+ :host([data-narrow]) [part=controls] { left: 12px; bottom: 12px; }
286
+ :host([data-phone]) [data-command=tidy] { display: none; }
287
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; scroll-behavior: auto !important; } }
288
+ `;
@@ -0,0 +1,20 @@
1
+ import type { ProcessLayout, ProcessLine, ProcessProjection } from "./model.js";
2
+ export interface ProcessPoint {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /** Stable left-to-right layers; cycles break at the smallest remaining ID. */
7
+ export declare function arrangeProcess(projection: ProcessProjection): ProcessLayout;
8
+ /**
9
+ * Authored points are interior waypoints; layout points override projection points.
10
+ * Explicit sides use centered ports with outward leads; unset sides snap to the
11
+ * nearest edge. Returns [] for missing endpoints, blocked ports or impossible
12
+ * waypoints (inside an obstacle), never an obstacle-crossing fallback.
13
+ */
14
+ export declare function routeProcessLine(line: ProcessLine, layout: ProcessLayout, projection: ProcessProjection): ProcessPoint[];
15
+ /** Halfway along the polyline's length, not halfway between its endpoints. */
16
+ export declare function lineMidpoint(points: readonly ProcessPoint[]): ProcessPoint;
17
+ /** SVG route with small rounded orthogonal turns, retaining exact endpoints. */
18
+ export declare function roundedProcessPath(points: readonly ProcessPoint[], radius?: number): string;
19
+ /** Segment-distance hit testing, including zero-length and diagonal segments. */
20
+ export declare function nearProcessLine(point: ProcessPoint, points: readonly ProcessPoint[], tolerance?: number): boolean;
@@ -0,0 +1,330 @@
1
+ import { validateProjection } from "./model.js";
2
+ const GAP_X = 80;
3
+ const GAP_Y = 48;
4
+ const PADDING = 24;
5
+ const HEADER = 64;
6
+ const CLEARANCE = 12;
7
+ const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
8
+ /** Stable left-to-right layers; cycles break at the smallest remaining ID. */
9
+ export function arrangeProcess(projection) {
10
+ validateProjection(projection);
11
+ const boxes = new Map(projection.boxes.map(box => [box.id, box]));
12
+ const children = new Map();
13
+ for (const box of projection.boxes) {
14
+ const siblings = children.get(box.parentId) ?? [];
15
+ siblings.push(box);
16
+ children.set(box.parentId, siblings);
17
+ }
18
+ for (const siblings of children.values())
19
+ siblings.sort((a, b) => compare(a.id, b.id));
20
+ const result = { boxes: Object.fromEntries(projection.boxes.map(box => [box.id, { x: 0, y: 0 }])) };
21
+ const measureGroup = (parentId) => {
22
+ const siblings = children.get(parentId) ?? [];
23
+ const ids = new Set(siblings.map(box => box.id));
24
+ // Collapse descendant endpoints to the sibling frame at this level.
25
+ const representative = (id) => {
26
+ let box = boxes.get(id);
27
+ while (box && !ids.has(box.id))
28
+ box = boxes.get(box.parentId ?? "");
29
+ return box?.id;
30
+ };
31
+ const outgoing = new Map(siblings.map(box => [box.id, new Set()]));
32
+ const incoming = new Map(siblings.map(box => [box.id, 0]));
33
+ for (const line of projection.lines) {
34
+ const from = representative(line.from), to = representative(line.to);
35
+ if (!from || !to || from === to || outgoing.get(from).has(to))
36
+ continue;
37
+ outgoing.get(from).add(to);
38
+ incoming.set(to, incoming.get(to) + 1);
39
+ }
40
+ const remaining = new Set(ids);
41
+ const rank = new Map(siblings.map(box => [box.id, 0]));
42
+ while (remaining.size) {
43
+ const ready = [...remaining].filter(id => incoming.get(id) === 0).sort(compare);
44
+ const id = ready[0] ?? [...remaining].sort(compare)[0];
45
+ remaining.delete(id);
46
+ for (const next of outgoing.get(id)) {
47
+ if (!remaining.has(next))
48
+ continue;
49
+ rank.set(next, Math.max(rank.get(next), rank.get(id) + 1));
50
+ incoming.set(next, incoming.get(next) - 1);
51
+ }
52
+ }
53
+ const sizes = new Map();
54
+ for (const box of siblings) {
55
+ const group = measureGroup(box.id);
56
+ const compact = box.shape === "gateway" || box.shape === "event";
57
+ const width = box.frame ? Math.max(320, group.width + PADDING * 2) : compact ? 56 : 224;
58
+ const height = box.frame ? Math.max(180, group.height + HEADER + PADDING) : compact ? 56 : 64;
59
+ sizes.set(box.id, { x: 0, y: 0, width, height });
60
+ // Store local child coordinates until the parent's absolute position is known.
61
+ for (const [id, position] of group.positions)
62
+ result.boxes[id] = position;
63
+ }
64
+ const positions = new Map();
65
+ let x = 0, height = 0;
66
+ const ranks = [...new Set(rank.values())].sort((a, b) => a - b);
67
+ for (const layer of ranks) {
68
+ let y = 0, width = 0;
69
+ for (const box of siblings.filter(box => rank.get(box.id) === layer)) {
70
+ const size = sizes.get(box.id);
71
+ positions.set(box.id, { ...size, x, y });
72
+ width = Math.max(width, size.width);
73
+ y += size.height + GAP_Y;
74
+ }
75
+ height = Math.max(height, y - GAP_Y);
76
+ x += width + GAP_X;
77
+ }
78
+ return { width: Math.max(0, x - GAP_X), height, positions };
79
+ };
80
+ const roots = measureGroup();
81
+ const place = (positions, x, y) => {
82
+ for (const [id, local] of positions) {
83
+ const position = { ...local, x: local.x + x, y: local.y + y };
84
+ result.boxes[id] = position;
85
+ const nested = new Map((children.get(id) ?? []).map(child => [child.id, result.boxes[child.id]]));
86
+ place(nested, position.x + PADDING, position.y + HEADER);
87
+ }
88
+ };
89
+ place(roots.positions, 40, 40);
90
+ return result;
91
+ }
92
+ function rectangle(position, box) {
93
+ const compact = box?.shape === "gateway" || box?.shape === "event";
94
+ return { ...position, width: position.width ?? (box?.frame ? 320 : compact ? 56 : 224), height: position.height ?? (box?.frame ? 180 : compact ? 56 : 64) };
95
+ }
96
+ const center = (box) => ({ x: box.x + box.width / 2, y: box.y + box.height / 2 });
97
+ const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
98
+ const same = (a, b) => a.x === b.x && a.y === b.y;
99
+ const inside = (p, box) => p.x > box.x && p.x < box.x + box.width && p.y > box.y && p.y < box.y + box.height;
100
+ function pin(box, toward, side) {
101
+ if (side) {
102
+ const middle = center(box);
103
+ switch (side) {
104
+ case "north": return { x: middle.x, y: box.y };
105
+ case "east": return { x: box.x + box.width, y: middle.y };
106
+ case "south": return { x: middle.x, y: box.y + box.height };
107
+ case "west": return { x: box.x, y: middle.y };
108
+ }
109
+ }
110
+ const x = Math.max(box.x, Math.min(toward.x, box.x + box.width));
111
+ const y = Math.max(box.y, Math.min(toward.y, box.y + box.height));
112
+ const candidates = [
113
+ { x: box.x + box.width, y }, { x: box.x, y },
114
+ { x, y: box.y + box.height }, { x, y: box.y },
115
+ ];
116
+ return candidates.reduce((best, point) => distance(point, toward) < distance(best, toward) ? point : best);
117
+ }
118
+ function portLead(point, side) {
119
+ switch (side) {
120
+ case "north": return { x: point.x, y: point.y - CLEARANCE };
121
+ case "east": return { x: point.x + CLEARANCE, y: point.y };
122
+ case "south": return { x: point.x, y: point.y + CLEARANCE };
123
+ case "west": return { x: point.x - CLEARANCE, y: point.y };
124
+ default: return point;
125
+ }
126
+ }
127
+ function blocked(a, b, obstacles) {
128
+ return obstacles.some(box => a.x === b.x
129
+ ? a.x > box.x && a.x < box.x + box.width && Math.max(a.y, b.y) > box.y && Math.min(a.y, b.y) < box.y + box.height
130
+ : a.y > box.y && a.y < box.y + box.height && Math.max(a.x, b.x) > box.x && Math.min(a.x, b.x) < box.x + box.width);
131
+ }
132
+ function simplify(points) {
133
+ const result = [];
134
+ for (const point of points) {
135
+ if (result.length && same(result[result.length - 1], point))
136
+ continue;
137
+ while (result.length > 1) {
138
+ const a = result[result.length - 2], b = result[result.length - 1];
139
+ if (!((a.x === b.x && b.x === point.x) || (a.y === b.y && b.y === point.y)))
140
+ break;
141
+ result.pop();
142
+ }
143
+ result.push(point);
144
+ }
145
+ return result;
146
+ }
147
+ /** Rectilinear visibility grid, with deterministic shortest-path tie breaking. */
148
+ function orthogonal(start, end, obstacles) {
149
+ if (obstacles.some(box => inside(start, box) || inside(end, box)))
150
+ return [];
151
+ if (same(start, end))
152
+ return [{ ...start }];
153
+ if ((start.x === end.x || start.y === end.y) && !blocked(start, end, obstacles))
154
+ return [{ ...start }, { ...end }];
155
+ const xs = [...new Set([start.x, end.x, ...obstacles.flatMap(box => [box.x, box.x + box.width])])].sort((a, b) => a - b);
156
+ const ys = [...new Set([start.y, end.y, ...obstacles.flatMap(box => [box.y, box.y + box.height])])].sort((a, b) => a - b);
157
+ const count = xs.length * ys.length;
158
+ const point = (id) => ({ x: xs[id % xs.length], y: ys[Math.floor(id / xs.length)] });
159
+ const index = (p) => ys.indexOf(p.y) * xs.length + xs.indexOf(p.x);
160
+ const first = index(start), last = index(end);
161
+ const costs = new Float64Array(count).fill(Infinity);
162
+ const previous = new Int32Array(count).fill(-1);
163
+ const visited = new Uint8Array(count);
164
+ const heap = [];
165
+ const less = (a, b) => a.cost < b.cost || (a.cost === b.cost && a.id < b.id);
166
+ const push = (entry) => {
167
+ heap.push(entry);
168
+ let i = heap.length - 1;
169
+ while (i > 0) {
170
+ const parent = Math.floor((i - 1) / 2);
171
+ if (!less(heap[i], heap[parent]))
172
+ break;
173
+ [heap[i], heap[parent]] = [heap[parent], heap[i]];
174
+ i = parent;
175
+ }
176
+ };
177
+ const pop = () => {
178
+ const first = heap[0], tail = heap.pop();
179
+ if (heap.length) {
180
+ heap[0] = tail;
181
+ let i = 0;
182
+ while (i * 2 + 1 < heap.length) {
183
+ let child = i * 2 + 1;
184
+ if (child + 1 < heap.length && less(heap[child + 1], heap[child]))
185
+ child++;
186
+ if (!less(heap[child], heap[i]))
187
+ break;
188
+ [heap[i], heap[child]] = [heap[child], heap[i]];
189
+ i = child;
190
+ }
191
+ }
192
+ return first;
193
+ };
194
+ costs[first] = 0;
195
+ push({ id: first, cost: 0 });
196
+ while (heap.length) {
197
+ const { id } = pop();
198
+ if (visited[id])
199
+ continue;
200
+ visited[id] = 1;
201
+ if (id === last) {
202
+ const path = [];
203
+ for (let cursor = last; cursor !== -1; cursor = previous[cursor])
204
+ path.push(point(cursor));
205
+ return simplify(path.reverse());
206
+ }
207
+ const a = point(id), column = id % xs.length, row = Math.floor(id / xs.length);
208
+ const neighbors = [column > 0 ? id - 1 : -1, column + 1 < xs.length ? id + 1 : -1,
209
+ row > 0 ? id - xs.length : -1, row + 1 < ys.length ? id + xs.length : -1];
210
+ for (const next of neighbors) {
211
+ if (next < 0 || visited[next])
212
+ continue;
213
+ const b = point(next);
214
+ if (blocked(a, b, obstacles))
215
+ continue;
216
+ const cost = costs[id] + distance(a, b);
217
+ if (cost >= costs[next])
218
+ continue;
219
+ costs[next] = cost;
220
+ previous[next] = id;
221
+ push({ id: next, cost });
222
+ }
223
+ }
224
+ return [];
225
+ }
226
+ /**
227
+ * Authored points are interior waypoints; layout points override projection points.
228
+ * Explicit sides use centered ports with outward leads; unset sides snap to the
229
+ * nearest edge. Returns [] for missing endpoints, blocked ports or impossible
230
+ * waypoints (inside an obstacle), never an obstacle-crossing fallback.
231
+ */
232
+ export function routeProcessLine(line, layout, projection) {
233
+ const boxes = new Map(projection.boxes.map(box => [box.id, box]));
234
+ const fromPosition = layout.boxes[line.from], toPosition = layout.boxes[line.to];
235
+ if (!fromPosition || !toPosition)
236
+ return [];
237
+ const from = rectangle(fromPosition, boxes.get(line.from));
238
+ const to = rectangle(toPosition, boxes.get(line.to));
239
+ const excluded = new Set();
240
+ for (const id of [line.from, line.to]) {
241
+ let parent = boxes.get(id)?.parentId;
242
+ while (parent && !excluded.has(parent)) {
243
+ excluded.add(parent);
244
+ parent = boxes.get(parent)?.parentId;
245
+ }
246
+ }
247
+ const obstacleBoxes = projection.boxes.filter(box => !excluded.has(box.id) && layout.boxes[box.id]);
248
+ const obstacles = obstacleBoxes.map(box => rectangle(layout.boxes[box.id], box));
249
+ const waypoints = (layout.lines?.[line.id] ?? line.points ?? []).map(point => ({ ...point }));
250
+ if (waypoints.some(point => !Number.isFinite(point.x) || !Number.isFinite(point.y)))
251
+ return [];
252
+ if (line.from === line.to && !waypoints.length) {
253
+ waypoints.push({ x: from.x + from.width + CLEARANCE * 2, y: from.y + from.height / 2 }, { x: from.x + from.width / 2, y: from.y - CLEARANCE * 2 });
254
+ }
255
+ const start = pin(from, waypoints[0] ?? center(to), line.fromSide);
256
+ const end = pin(to, waypoints.at(-1) ?? center(from), line.toSide);
257
+ const startLead = portLead(start, line.fromSide), endLead = portLead(end, line.toSide);
258
+ // Fixed leads preserve port direction rather than detouring along a box edge.
259
+ if (line.fromSide && blocked(start, startLead, obstacles)
260
+ || line.toSide && blocked(endLead, end, obstacles))
261
+ return [];
262
+ const anchors = [startLead, ...waypoints, endLead];
263
+ const padded = obstacles.map((rect, index) => {
264
+ if (obstacleBoxes[index].id === line.from || obstacleBoxes[index].id === line.to)
265
+ return rect;
266
+ return { x: rect.x - CLEARANCE, y: rect.y - CLEARANCE, width: rect.width + CLEARANCE * 2, height: rect.height + CLEARANCE * 2 };
267
+ });
268
+ const result = line.fromSide ? [start, startLead] : [];
269
+ for (let i = 1; i < anchors.length; i++) {
270
+ // Prefer clearance, but never reject a valid narrow corridor or waypoint.
271
+ let segment = orthogonal(anchors[i - 1], anchors[i], padded);
272
+ if (!segment.length)
273
+ segment = orthogonal(anchors[i - 1], anchors[i], obstacles);
274
+ if (!segment.length)
275
+ return [];
276
+ result.push(...(result.length ? segment.slice(1) : segment));
277
+ }
278
+ if (line.toSide)
279
+ result.push(end);
280
+ return result;
281
+ }
282
+ /** Halfway along the polyline's length, not halfway between its endpoints. */
283
+ export function lineMidpoint(points) {
284
+ if (!points.length)
285
+ return { x: 0, y: 0 };
286
+ let remaining = points.slice(1).reduce((sum, point, i) => sum + distance(points[i], point), 0) / 2;
287
+ for (let i = 1; i < points.length; i++) {
288
+ const a = points[i - 1], b = points[i], length = distance(a, b);
289
+ if (length && remaining <= length) {
290
+ const ratio = remaining / length;
291
+ return { x: a.x + (b.x - a.x) * ratio, y: a.y + (b.y - a.y) * ratio };
292
+ }
293
+ remaining -= length;
294
+ }
295
+ return { ...points[0] };
296
+ }
297
+ /** SVG route with small rounded orthogonal turns, retaining exact endpoints. */
298
+ export function roundedProcessPath(points, radius = 6) {
299
+ if (!points.length)
300
+ return "";
301
+ const commands = [`M ${points[0].x} ${points[0].y}`];
302
+ for (let index = 1; index < points.length - 1; index++) {
303
+ const previous = points[index - 1], corner = points[index], next = points[index + 1];
304
+ const beforeLength = Math.hypot(corner.x - previous.x, corner.y - previous.y);
305
+ const afterLength = Math.hypot(next.x - corner.x, next.y - corner.y);
306
+ if (!beforeLength || !afterLength || (corner.x - previous.x) * (next.y - corner.y) === (corner.y - previous.y) * (next.x - corner.x)) {
307
+ commands.push(`L ${corner.x} ${corner.y}`);
308
+ continue;
309
+ }
310
+ const turn = Math.min(radius, beforeLength / 2, afterLength / 2);
311
+ const enter = { x: corner.x - (corner.x - previous.x) / beforeLength * turn, y: corner.y - (corner.y - previous.y) / beforeLength * turn };
312
+ const leave = { x: corner.x + (next.x - corner.x) / afterLength * turn, y: corner.y + (next.y - corner.y) / afterLength * turn };
313
+ commands.push(`L ${enter.x} ${enter.y}`, `Q ${corner.x} ${corner.y} ${leave.x} ${leave.y}`);
314
+ }
315
+ commands.push(`L ${points.at(-1).x} ${points.at(-1).y}`);
316
+ return commands.join(" ");
317
+ }
318
+ /** Segment-distance hit testing, including zero-length and diagonal segments. */
319
+ export function nearProcessLine(point, points, tolerance = 16) {
320
+ if (tolerance < 0 || !points.length)
321
+ return false;
322
+ if (points.length === 1)
323
+ return distance(point, points[0]) <= tolerance;
324
+ return points.slice(1).some((b, i) => {
325
+ const a = points[i], dx = b.x - a.x, dy = b.y - a.y;
326
+ const squared = dx * dx + dy * dy;
327
+ const t = squared ? Math.max(0, Math.min(1, ((point.x - a.x) * dx + (point.y - a.y) * dy) / squared)) : 0;
328
+ return distance(point, { x: a.x + dx * t, y: a.y + dy * t }) <= tolerance;
329
+ });
330
+ }