@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.
- package/README.md +13 -13
- package/dist/components/actions/button.d.ts +1 -0
- package/dist/components/actions/button.js +13 -0
- package/dist/components/forms/checkbox.d.ts +1 -0
- package/dist/components/forms/checkbox.js +2 -0
- package/dist/components/forms/code-editor.d.ts +28 -3
- package/dist/components/forms/code-editor.js +71 -6
- package/dist/components/forms/combobox.js +7 -8
- package/dist/components/forms/number-input.d.ts +1 -0
- package/dist/components/forms/number-input.js +4 -1
- package/dist/components/forms/select.d.ts +1 -0
- package/dist/components/forms/select.js +3 -1
- package/dist/components/forms/text-area.js +5 -7
- package/dist/components/forms/text-field.d.ts +1 -0
- package/dist/components/forms/text-field.js +2 -0
- package/dist/components/navigation/accordion.d.ts +7 -0
- package/dist/components/navigation/accordion.js +31 -8
- package/dist/components/output/code-block.js +3 -1
- package/dist/components/overlays/drawer.js +2 -2
- package/dist/foundations/geometry/tokens.d.ts +2 -2
- package/dist/foundations/geometry/tokens.js +3 -3
- package/dist/foundations/icons/box-iconography.generated.d.ts +472 -1
- package/dist/foundations/icons/box-iconography.generated.js +473 -2
- package/dist/patterns/flow-builder/flow-builder.d.ts +5 -1
- package/dist/patterns/flow-builder/flow-builder.js +18 -7
- package/dist/patterns/flow-builder/primitives.d.ts +1 -0
- package/dist/patterns/flow-builder/primitives.js +11 -4
- package/dist/patterns/process-modeler/bridge.d.ts +10 -0
- package/dist/patterns/process-modeler/bridge.js +173 -0
- package/dist/patterns/process-modeler/design.d.ts +2 -0
- package/dist/patterns/process-modeler/design.js +288 -0
- package/dist/patterns/process-modeler/geometry.d.ts +20 -0
- package/dist/patterns/process-modeler/geometry.js +330 -0
- package/dist/patterns/process-modeler/index.d.ts +2 -0
- package/dist/patterns/process-modeler/index.js +2 -0
- package/dist/patterns/process-modeler/model.d.ts +106 -1
- package/dist/patterns/process-modeler/model.js +2 -2
- package/dist/patterns/process-modeler/process-modeler.d.ts +115 -3
- package/dist/patterns/process-modeler/process-modeler.js +1864 -128
- 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
|
+
}
|