@unofficialbox/box-open-elements 0.23.0 → 0.25.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 (54) hide show
  1. package/README.md +15 -13
  2. package/dist/components/actions/button.d.ts +1 -0
  3. package/dist/components/actions/button.js +16 -3
  4. package/dist/components/actions/segmented-control.d.ts +1 -0
  5. package/dist/components/actions/segmented-control.js +20 -6
  6. package/dist/components/feedback/alert.js +1 -1
  7. package/dist/components/feedback/progress-bar.js +2 -2
  8. package/dist/components/forms/checkbox.d.ts +1 -0
  9. package/dist/components/forms/checkbox.js +6 -2
  10. package/dist/components/forms/code-editor.d.ts +86 -0
  11. package/dist/components/forms/code-editor.js +447 -0
  12. package/dist/components/forms/combobox.js +7 -8
  13. package/dist/components/forms/number-input.d.ts +1 -0
  14. package/dist/components/forms/number-input.js +8 -2
  15. package/dist/components/forms/select.d.ts +1 -0
  16. package/dist/components/forms/select.js +3 -1
  17. package/dist/components/forms/text-area.js +5 -7
  18. package/dist/components/forms/text-field.d.ts +1 -0
  19. package/dist/components/forms/text-field.js +2 -0
  20. package/dist/components/navigation/accordion.d.ts +7 -0
  21. package/dist/components/navigation/accordion.js +31 -8
  22. package/dist/components/output/code-block.js +3 -1
  23. package/dist/components/overlays/drawer.js +2 -2
  24. package/dist/core/form-associated.d.ts +1 -0
  25. package/dist/core/form-associated.js +3 -0
  26. package/dist/entries/code-editor.d.ts +1 -0
  27. package/dist/entries/code-editor.js +1 -0
  28. package/dist/foundations/geometry/tokens.d.ts +2 -2
  29. package/dist/foundations/geometry/tokens.js +3 -3
  30. package/dist/foundations/icons/box-iconography.generated.js +1 -0
  31. package/dist/foundations/icons/glyphs/constants.d.ts +472 -0
  32. package/dist/foundations/icons/glyphs/constants.js +473 -0
  33. package/dist/foundations/icons/glyphs/index.d.ts +1 -1
  34. package/dist/foundations/icons/glyphs/index.js +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/patterns/flow-builder/flow-builder.d.ts +7 -1
  38. package/dist/patterns/flow-builder/flow-builder.js +32 -15
  39. package/dist/patterns/flow-builder/model.d.ts +2 -0
  40. package/dist/patterns/flow-builder/model.js +3 -3
  41. package/dist/patterns/flow-builder/primitives.d.ts +1 -0
  42. package/dist/patterns/flow-builder/primitives.js +5 -4
  43. package/dist/patterns/insights/metric-card.js +2 -2
  44. package/dist/patterns/process-modeler/bridge.d.ts +10 -0
  45. package/dist/patterns/process-modeler/bridge.js +173 -0
  46. package/dist/patterns/process-modeler/geometry.d.ts +18 -0
  47. package/dist/patterns/process-modeler/geometry.js +307 -0
  48. package/dist/patterns/process-modeler/index.d.ts +4 -0
  49. package/dist/patterns/process-modeler/index.js +4 -0
  50. package/dist/patterns/process-modeler/model.d.ts +135 -0
  51. package/dist/patterns/process-modeler/model.js +113 -0
  52. package/dist/patterns/process-modeler/process-modeler.d.ts +130 -0
  53. package/dist/patterns/process-modeler/process-modeler.js +1507 -0
  54. package/package.json +21 -3
@@ -0,0 +1,307 @@
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 width = box.frame ? Math.max(320, group.width + PADDING * 2) : 220;
57
+ const height = box.frame ? Math.max(180, group.height + HEADER + PADDING) : 96;
58
+ sizes.set(box.id, { x: 0, y: 0, width, height });
59
+ // Store local child coordinates until the parent's absolute position is known.
60
+ for (const [id, position] of group.positions)
61
+ result.boxes[id] = position;
62
+ }
63
+ const positions = new Map();
64
+ let x = 0, height = 0;
65
+ const ranks = [...new Set(rank.values())].sort((a, b) => a - b);
66
+ for (const layer of ranks) {
67
+ let y = 0, width = 0;
68
+ for (const box of siblings.filter(box => rank.get(box.id) === layer)) {
69
+ const size = sizes.get(box.id);
70
+ positions.set(box.id, { ...size, x, y });
71
+ width = Math.max(width, size.width);
72
+ y += size.height + GAP_Y;
73
+ }
74
+ height = Math.max(height, y - GAP_Y);
75
+ x += width + GAP_X;
76
+ }
77
+ return { width: Math.max(0, x - GAP_X), height, positions };
78
+ };
79
+ const roots = measureGroup();
80
+ const place = (positions, x, y) => {
81
+ for (const [id, local] of positions) {
82
+ const position = { ...local, x: local.x + x, y: local.y + y };
83
+ result.boxes[id] = position;
84
+ const nested = new Map((children.get(id) ?? []).map(child => [child.id, result.boxes[child.id]]));
85
+ place(nested, position.x + PADDING, position.y + HEADER);
86
+ }
87
+ };
88
+ place(roots.positions, 40, 40);
89
+ return result;
90
+ }
91
+ function rectangle(position, frame = false) {
92
+ return { ...position, width: position.width ?? (frame ? 320 : 220), height: position.height ?? (frame ? 180 : 96) };
93
+ }
94
+ const center = (box) => ({ x: box.x + box.width / 2, y: box.y + box.height / 2 });
95
+ const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
96
+ const same = (a, b) => a.x === b.x && a.y === b.y;
97
+ const inside = (p, box) => p.x > box.x && p.x < box.x + box.width && p.y > box.y && p.y < box.y + box.height;
98
+ function pin(box, toward, side) {
99
+ if (side) {
100
+ const middle = center(box);
101
+ switch (side) {
102
+ case "north": return { x: middle.x, y: box.y };
103
+ case "east": return { x: box.x + box.width, y: middle.y };
104
+ case "south": return { x: middle.x, y: box.y + box.height };
105
+ case "west": return { x: box.x, y: middle.y };
106
+ }
107
+ }
108
+ const x = Math.max(box.x, Math.min(toward.x, box.x + box.width));
109
+ const y = Math.max(box.y, Math.min(toward.y, box.y + box.height));
110
+ const candidates = [
111
+ { x: box.x + box.width, y }, { x: box.x, y },
112
+ { x, y: box.y + box.height }, { x, y: box.y },
113
+ ];
114
+ return candidates.reduce((best, point) => distance(point, toward) < distance(best, toward) ? point : best);
115
+ }
116
+ function portLead(point, side) {
117
+ switch (side) {
118
+ case "north": return { x: point.x, y: point.y - CLEARANCE };
119
+ case "east": return { x: point.x + CLEARANCE, y: point.y };
120
+ case "south": return { x: point.x, y: point.y + CLEARANCE };
121
+ case "west": return { x: point.x - CLEARANCE, y: point.y };
122
+ default: return point;
123
+ }
124
+ }
125
+ function blocked(a, b, obstacles) {
126
+ return obstacles.some(box => a.x === b.x
127
+ ? 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
128
+ : 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);
129
+ }
130
+ function simplify(points) {
131
+ const result = [];
132
+ for (const point of points) {
133
+ if (result.length && same(result[result.length - 1], point))
134
+ continue;
135
+ while (result.length > 1) {
136
+ const a = result[result.length - 2], b = result[result.length - 1];
137
+ if (!((a.x === b.x && b.x === point.x) || (a.y === b.y && b.y === point.y)))
138
+ break;
139
+ result.pop();
140
+ }
141
+ result.push(point);
142
+ }
143
+ return result;
144
+ }
145
+ /** Rectilinear visibility grid, with deterministic shortest-path tie breaking. */
146
+ function orthogonal(start, end, obstacles) {
147
+ if (obstacles.some(box => inside(start, box) || inside(end, box)))
148
+ return [];
149
+ if (same(start, end))
150
+ return [{ ...start }];
151
+ if ((start.x === end.x || start.y === end.y) && !blocked(start, end, obstacles))
152
+ return [{ ...start }, { ...end }];
153
+ const xs = [...new Set([start.x, end.x, ...obstacles.flatMap(box => [box.x, box.x + box.width])])].sort((a, b) => a - b);
154
+ const ys = [...new Set([start.y, end.y, ...obstacles.flatMap(box => [box.y, box.y + box.height])])].sort((a, b) => a - b);
155
+ const count = xs.length * ys.length;
156
+ const point = (id) => ({ x: xs[id % xs.length], y: ys[Math.floor(id / xs.length)] });
157
+ const index = (p) => ys.indexOf(p.y) * xs.length + xs.indexOf(p.x);
158
+ const first = index(start), last = index(end);
159
+ const costs = new Float64Array(count).fill(Infinity);
160
+ const previous = new Int32Array(count).fill(-1);
161
+ const visited = new Uint8Array(count);
162
+ const heap = [];
163
+ const less = (a, b) => a.cost < b.cost || (a.cost === b.cost && a.id < b.id);
164
+ const push = (entry) => {
165
+ heap.push(entry);
166
+ let i = heap.length - 1;
167
+ while (i > 0) {
168
+ const parent = Math.floor((i - 1) / 2);
169
+ if (!less(heap[i], heap[parent]))
170
+ break;
171
+ [heap[i], heap[parent]] = [heap[parent], heap[i]];
172
+ i = parent;
173
+ }
174
+ };
175
+ const pop = () => {
176
+ const first = heap[0], tail = heap.pop();
177
+ if (heap.length) {
178
+ heap[0] = tail;
179
+ let i = 0;
180
+ while (i * 2 + 1 < heap.length) {
181
+ let child = i * 2 + 1;
182
+ if (child + 1 < heap.length && less(heap[child + 1], heap[child]))
183
+ child++;
184
+ if (!less(heap[child], heap[i]))
185
+ break;
186
+ [heap[i], heap[child]] = [heap[child], heap[i]];
187
+ i = child;
188
+ }
189
+ }
190
+ return first;
191
+ };
192
+ costs[first] = 0;
193
+ push({ id: first, cost: 0 });
194
+ while (heap.length) {
195
+ const { id } = pop();
196
+ if (visited[id])
197
+ continue;
198
+ visited[id] = 1;
199
+ if (id === last) {
200
+ const path = [];
201
+ for (let cursor = last; cursor !== -1; cursor = previous[cursor])
202
+ path.push(point(cursor));
203
+ return simplify(path.reverse());
204
+ }
205
+ const a = point(id), column = id % xs.length, row = Math.floor(id / xs.length);
206
+ const neighbors = [column > 0 ? id - 1 : -1, column + 1 < xs.length ? id + 1 : -1,
207
+ row > 0 ? id - xs.length : -1, row + 1 < ys.length ? id + xs.length : -1];
208
+ for (const next of neighbors) {
209
+ if (next < 0 || visited[next])
210
+ continue;
211
+ const b = point(next);
212
+ if (blocked(a, b, obstacles))
213
+ continue;
214
+ const cost = costs[id] + distance(a, b);
215
+ if (cost >= costs[next])
216
+ continue;
217
+ costs[next] = cost;
218
+ previous[next] = id;
219
+ push({ id: next, cost });
220
+ }
221
+ }
222
+ return [];
223
+ }
224
+ /**
225
+ * Authored points are interior waypoints; layout points override projection points.
226
+ * Explicit sides use centered ports with outward leads; unset sides snap to the
227
+ * nearest edge. Returns [] for missing endpoints, blocked ports or impossible
228
+ * waypoints (inside an obstacle), never an obstacle-crossing fallback.
229
+ */
230
+ export function routeProcessLine(line, layout, projection) {
231
+ const boxes = new Map(projection.boxes.map(box => [box.id, box]));
232
+ const fromPosition = layout.boxes[line.from], toPosition = layout.boxes[line.to];
233
+ if (!fromPosition || !toPosition)
234
+ return [];
235
+ const from = rectangle(fromPosition, boxes.get(line.from)?.frame);
236
+ const to = rectangle(toPosition, boxes.get(line.to)?.frame);
237
+ const excluded = new Set();
238
+ for (const id of [line.from, line.to]) {
239
+ let parent = boxes.get(id)?.parentId;
240
+ while (parent && !excluded.has(parent)) {
241
+ excluded.add(parent);
242
+ parent = boxes.get(parent)?.parentId;
243
+ }
244
+ }
245
+ const obstacleBoxes = projection.boxes.filter(box => !excluded.has(box.id) && layout.boxes[box.id]);
246
+ const obstacles = obstacleBoxes.map(box => rectangle(layout.boxes[box.id], box.frame));
247
+ const waypoints = (layout.lines?.[line.id] ?? line.points ?? []).map(point => ({ ...point }));
248
+ if (waypoints.some(point => !Number.isFinite(point.x) || !Number.isFinite(point.y)))
249
+ return [];
250
+ if (line.from === line.to && !waypoints.length) {
251
+ 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 });
252
+ }
253
+ const start = pin(from, waypoints[0] ?? center(to), line.fromSide);
254
+ const end = pin(to, waypoints.at(-1) ?? center(from), line.toSide);
255
+ const startLead = portLead(start, line.fromSide), endLead = portLead(end, line.toSide);
256
+ // Fixed leads preserve port direction rather than detouring along a box edge.
257
+ if (line.fromSide && blocked(start, startLead, obstacles)
258
+ || line.toSide && blocked(endLead, end, obstacles))
259
+ return [];
260
+ const anchors = [startLead, ...waypoints, endLead];
261
+ const padded = obstacles.map((rect, index) => {
262
+ if (obstacleBoxes[index].id === line.from || obstacleBoxes[index].id === line.to)
263
+ return rect;
264
+ return { x: rect.x - CLEARANCE, y: rect.y - CLEARANCE, width: rect.width + CLEARANCE * 2, height: rect.height + CLEARANCE * 2 };
265
+ });
266
+ const result = line.fromSide ? [start, startLead] : [];
267
+ for (let i = 1; i < anchors.length; i++) {
268
+ // Prefer clearance, but never reject a valid narrow corridor or waypoint.
269
+ let segment = orthogonal(anchors[i - 1], anchors[i], padded);
270
+ if (!segment.length)
271
+ segment = orthogonal(anchors[i - 1], anchors[i], obstacles);
272
+ if (!segment.length)
273
+ return [];
274
+ result.push(...(result.length ? segment.slice(1) : segment));
275
+ }
276
+ if (line.toSide)
277
+ result.push(end);
278
+ return result;
279
+ }
280
+ /** Halfway along the polyline's length, not halfway between its endpoints. */
281
+ export function lineMidpoint(points) {
282
+ if (!points.length)
283
+ return { x: 0, y: 0 };
284
+ let remaining = points.slice(1).reduce((sum, point, i) => sum + distance(points[i], point), 0) / 2;
285
+ for (let i = 1; i < points.length; i++) {
286
+ const a = points[i - 1], b = points[i], length = distance(a, b);
287
+ if (length && remaining <= length) {
288
+ const ratio = remaining / length;
289
+ return { x: a.x + (b.x - a.x) * ratio, y: a.y + (b.y - a.y) * ratio };
290
+ }
291
+ remaining -= length;
292
+ }
293
+ return { ...points[0] };
294
+ }
295
+ /** Segment-distance hit testing, including zero-length and diagonal segments. */
296
+ export function nearProcessLine(point, points, tolerance = 16) {
297
+ if (tolerance < 0 || !points.length)
298
+ return false;
299
+ if (points.length === 1)
300
+ return distance(point, points[0]) <= tolerance;
301
+ return points.slice(1).some((b, i) => {
302
+ const a = points[i], dx = b.x - a.x, dy = b.y - a.y;
303
+ const squared = dx * dx + dy * dy;
304
+ const t = squared ? Math.max(0, Math.min(1, ((point.x - a.x) * dx + (point.y - a.y) * dy) / squared)) : 0;
305
+ return distance(point, { x: a.x + dx * t, y: a.y + dy * t }) <= tolerance;
306
+ });
307
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./model.js";
2
+ export * from "./geometry.js";
3
+ export * from "./bridge.js";
4
+ export * from "./process-modeler.js";
@@ -0,0 +1,4 @@
1
+ export * from "./model.js";
2
+ export * from "./geometry.js";
3
+ export * from "./bridge.js";
4
+ export * from "./process-modeler.js";
@@ -0,0 +1,135 @@
1
+ import type { FlowKind, NodePath } from "../flow-builder/model.js";
2
+ export interface ProcessBox<N = unknown> {
3
+ id: string;
4
+ node: N;
5
+ kind: string;
6
+ title: string;
7
+ description?: string;
8
+ path?: NodePath;
9
+ /** Host identity at this path, used to reject stale saved positions. */
10
+ fingerprint?: string;
11
+ /** A frame draws behind its children and connects as one box. */
12
+ frame?: boolean;
13
+ parentId?: string;
14
+ }
15
+ export interface ProcessLine {
16
+ id: string;
17
+ from: string;
18
+ to: string;
19
+ label?: string;
20
+ weight?: number;
21
+ fromSide?: ProcessSide;
22
+ toSide?: ProcessSide;
23
+ dashed?: boolean;
24
+ points?: {
25
+ x: number;
26
+ y: number;
27
+ }[];
28
+ }
29
+ export type ProcessSide = "north" | "east" | "south" | "west";
30
+ export interface ProcessConnection {
31
+ name: string;
32
+ kind: string;
33
+ id?: string;
34
+ }
35
+ export interface ProcessVariable {
36
+ name: string;
37
+ value?: unknown;
38
+ description?: string;
39
+ }
40
+ export interface ProcessPositionSnapshot {
41
+ path?: NodePath;
42
+ fingerprint?: string;
43
+ id: string;
44
+ position: BoxPosition;
45
+ }
46
+ export interface ProcessLoadOptions {
47
+ /** Keys are JSON.stringify(path); snapshots are also accepted. Fingerprints must match. */
48
+ positions?: Readonly<Record<string, {
49
+ fingerprint?: string;
50
+ position: BoxPosition;
51
+ }>> | readonly ProcessPositionSnapshot[];
52
+ version?: string | number;
53
+ selectedPath?: NodePath | null;
54
+ }
55
+ export interface ProcessProjection<N = unknown> {
56
+ boxes: readonly ProcessBox<N>[];
57
+ lines: readonly ProcessLine[];
58
+ }
59
+ export interface ProcessModel<D = unknown, N = unknown> {
60
+ project(document: D): ProcessProjection<N>;
61
+ /** Optional host arrangement, used by Tidy up and for missing positions. */
62
+ arrange?(projection: ProcessProjection<N>): ProcessLayout;
63
+ /** Host-specific semantics supplement the generic free-graph checks. */
64
+ validate?(document: D, projection: ProcessProjection<N>): readonly ProcessCheck[];
65
+ }
66
+ export interface BoxPosition {
67
+ x: number;
68
+ y: number;
69
+ width?: number;
70
+ height?: number;
71
+ }
72
+ export interface ProcessLayout {
73
+ boxes: Record<string, BoxPosition>;
74
+ notes?: {
75
+ id: string;
76
+ text: string;
77
+ x: number;
78
+ y: number;
79
+ }[];
80
+ sections?: {
81
+ id: string;
82
+ title: string;
83
+ x: number;
84
+ y: number;
85
+ width: number;
86
+ height: number;
87
+ }[];
88
+ lines?: Record<string, {
89
+ x: number;
90
+ y: number;
91
+ }[]>;
92
+ }
93
+ export interface ProcessCheck {
94
+ message: string;
95
+ boxId?: string;
96
+ path?: NodePath;
97
+ }
98
+ export interface ProcessEdit {
99
+ type: "add" | "delete" | "connect" | "disconnect" | "insert" | "reparent";
100
+ boxId?: string;
101
+ from?: string;
102
+ to?: string;
103
+ lineId?: string;
104
+ kind?: FlowKind;
105
+ position?: BoxPosition;
106
+ parentId?: string;
107
+ fromSide?: ProcessSide;
108
+ toSide?: ProcessSide;
109
+ }
110
+ export interface ReversibleProcessEdit {
111
+ undo(): void;
112
+ redo(): void;
113
+ /** The already-applied edit's layout, recorded atomically with the document. */
114
+ layout?: ProcessLayout;
115
+ }
116
+ /** Host applies the edit, then calls accept with its inverse and replay. */
117
+ export interface ProcessEditRequest extends ProcessEdit {
118
+ accept(command: ReversibleProcessEdit): void;
119
+ }
120
+ export declare function validateProjection<N>(projection: ProcessProjection<N>): void;
121
+ /** Deterministic fallback, preserving host-authored positions. */
122
+ export declare function completeLayout<N>(projection: ProcessProjection<N>, layout: ProcessLayout): ProcessLayout;
123
+ /** Bounded history shared by layout edits and host-accepted document commands. */
124
+ export declare class ProcessHistory {
125
+ private replayingInternal;
126
+ private past;
127
+ private future;
128
+ get canUndo(): boolean;
129
+ get canRedo(): boolean;
130
+ get replaying(): boolean;
131
+ record(command: ReversibleProcessEdit): void;
132
+ undo(): void;
133
+ redo(): void;
134
+ clear(): void;
135
+ }
@@ -0,0 +1,113 @@
1
+ export function validateProjection(projection) {
2
+ const ids = new Set();
3
+ for (const box of projection.boxes) {
4
+ if (!box.id || ids.has(box.id))
5
+ throw new TypeError("Process boxes need unique non-empty IDs");
6
+ ids.add(box.id);
7
+ }
8
+ for (const box of projection.boxes) {
9
+ const ancestors = new Set([box.id]);
10
+ let parent = box.parentId;
11
+ while (parent) {
12
+ const frame = projection.boxes.find((candidate) => candidate.id === parent);
13
+ if (!frame?.frame || ancestors.has(parent))
14
+ throw new TypeError("Frame parents must exist and cannot form a cycle");
15
+ ancestors.add(parent);
16
+ parent = frame.parentId;
17
+ }
18
+ }
19
+ const lines = new Set();
20
+ for (const line of projection.lines) {
21
+ if (!line.id ||
22
+ lines.has(line.id) ||
23
+ !ids.has(line.from) ||
24
+ !ids.has(line.to))
25
+ throw new TypeError("Process lines need unique IDs and existing endpoints");
26
+ lines.add(line.id);
27
+ }
28
+ }
29
+ /** Deterministic fallback, preserving host-authored positions. */
30
+ export function completeLayout(projection, layout) {
31
+ const boxes = { ...layout.boxes };
32
+ const sizes = new Map();
33
+ const measure = (box) => {
34
+ const children = projection.boxes
35
+ .filter((child) => child.parentId === box.id)
36
+ .map(measure);
37
+ const size = {
38
+ width: Math.max(box.frame ? 320 : 220, ...children.map((child) => child.width + 40)),
39
+ height: box.frame
40
+ ? Math.max(180, 64 + children.reduce((sum, child) => sum + child.height + 24, 0))
41
+ : 96,
42
+ };
43
+ sizes.set(box.id, size);
44
+ return size;
45
+ };
46
+ const roots = projection.boxes.filter((box) => !box.parentId);
47
+ roots.forEach(measure);
48
+ const place = (siblings, x, top) => {
49
+ let y = top;
50
+ for (const box of siblings) {
51
+ const size = sizes.get(box.id);
52
+ boxes[box.id] ??= { x, y, ...size };
53
+ const position = boxes[box.id];
54
+ place(projection.boxes.filter((child) => child.parentId === box.id), position.x + 20, position.y + 64);
55
+ y += (position.height ?? size.height) + 60;
56
+ }
57
+ };
58
+ place(roots, 40, 40);
59
+ return { ...layout, boxes };
60
+ }
61
+ /** Bounded history shared by layout edits and host-accepted document commands. */
62
+ export class ProcessHistory {
63
+ replayingInternal = false;
64
+ past = [];
65
+ future = [];
66
+ get canUndo() {
67
+ return this.past.length > 0;
68
+ }
69
+ get canRedo() {
70
+ return this.future.length > 0;
71
+ }
72
+ get replaying() {
73
+ return this.replayingInternal;
74
+ }
75
+ record(command) {
76
+ this.past.push(command);
77
+ if (this.past.length > 100)
78
+ this.past.shift();
79
+ this.future = [];
80
+ }
81
+ undo() {
82
+ const command = this.past.at(-1);
83
+ if (!command)
84
+ return;
85
+ this.replayingInternal = true;
86
+ try {
87
+ command.undo();
88
+ }
89
+ finally {
90
+ this.replayingInternal = false;
91
+ }
92
+ this.past.pop();
93
+ this.future.push(command);
94
+ }
95
+ redo() {
96
+ const command = this.future.at(-1);
97
+ if (!command)
98
+ return;
99
+ this.replayingInternal = true;
100
+ try {
101
+ command.redo();
102
+ }
103
+ finally {
104
+ this.replayingInternal = false;
105
+ }
106
+ this.future.pop();
107
+ this.past.push(command);
108
+ }
109
+ clear() {
110
+ this.past = [];
111
+ this.future = [];
112
+ }
113
+ }
@@ -0,0 +1,130 @@
1
+ import { BaseElement } from "../../core/index.js";
2
+ import type { FlowKind, NodePath } from "../flow-builder/model.js";
3
+ import type { ProcessConnection, ProcessVariable, ProcessLoadOptions } from "./model.js";
4
+ import { ProcessHistory, type ProcessBox, type ProcessCheck, type ProcessEdit, type ProcessLayout, type ProcessModel, type ProcessProjection } from "./model.js";
5
+ /** Projects host documents without owning workflow schemas or persistence. */
6
+ export declare class ProcessModeler<D = ProcessProjection, N = unknown> extends BaseElement {
7
+ static readonly tagName = "box-process-modeler";
8
+ static get observedAttributes(): string[];
9
+ private documentValue?;
10
+ private modelValue;
11
+ private projection;
12
+ private layoutValue;
13
+ private catalogValue;
14
+ private selectedId;
15
+ private connecting;
16
+ private checks;
17
+ private renderer?;
18
+ private cleanupInspector?;
19
+ private inspected?;
20
+ private inspectedId?;
21
+ private historyValue;
22
+ private viewport;
23
+ private pointers;
24
+ private drag?;
25
+ private pinchDistance;
26
+ private gestureZoom;
27
+ private insertion?;
28
+ private selectedIds;
29
+ private versionValue;
30
+ private lastProjection;
31
+ private lastDocument?;
32
+ private connectionsValue;
33
+ private variablesValue;
34
+ private activePane;
35
+ private portDrag?;
36
+ private marquee?;
37
+ private segmentDrag?;
38
+ private dropLine?;
39
+ private narrowValue;
40
+ private suppressClick;
41
+ private resizeObserver?;
42
+ private routedLines;
43
+ private computedChecks;
44
+ get selectedBoxes(): readonly ProcessBox<N>[];
45
+ get selectedPath(): NodePath | null;
46
+ set selectedPath(path: NodePath | null);
47
+ get connections(): readonly ProcessConnection[];
48
+ set connections(value: readonly ProcessConnection[]);
49
+ get variables(): readonly ProcessVariable[];
50
+ set variables(value: readonly ProcessVariable[]);
51
+ get version(): string | number;
52
+ get narrow(): boolean;
53
+ get positions(): readonly import("./model.js").ProcessPositionSnapshot[];
54
+ load(document: D, options?: ProcessLoadOptions): void;
55
+ selectMany(ids: readonly string[]): void;
56
+ get headingLevel(): number;
57
+ set headingLevel(value: number);
58
+ get disableConnections(): boolean;
59
+ set disableConnections(value: boolean);
60
+ private arrangedLayout;
61
+ get document(): D | undefined;
62
+ get history(): ProcessHistory;
63
+ set history(value: ProcessHistory);
64
+ set document(value: D | undefined);
65
+ get model(): ProcessModel<D, N>;
66
+ set model(value: ProcessModel<D, N>);
67
+ get layout(): ProcessLayout;
68
+ set layout(value: ProcessLayout);
69
+ get catalog(): readonly FlowKind[];
70
+ set catalog(value: readonly FlowKind[]);
71
+ get locked(): boolean;
72
+ set locked(value: boolean);
73
+ get snapToGrid(): boolean;
74
+ set snapToGrid(value: boolean);
75
+ get selected(): ProcessBox<N> | null;
76
+ get renderInspector(): ((node: N, container: HTMLElement) => void | (() => void)) | undefined;
77
+ set renderInspector(value: ((node: N, container: HTMLElement) => void | (() => void)) | undefined);
78
+ disconnectedCallback(): void;
79
+ connectedCallback(): void;
80
+ refresh(): void;
81
+ select(id: string | null): void;
82
+ setValidation(checks: readonly ProcessCheck[]): void;
83
+ setValidationAtPath(message: string, path: NodePath): void;
84
+ requestEdit(edit: ProcessEdit): void;
85
+ undo(): void;
86
+ redo(): void;
87
+ private commitLayout;
88
+ move(id: string, x: number, y: number): void;
89
+ private emitPositions;
90
+ private translatedLayout;
91
+ tidy(): void;
92
+ resize(id: string, width: number, height: number): void;
93
+ routeLine(id: string, points: readonly {
94
+ x: number;
95
+ y: number;
96
+ }[]): void;
97
+ setNote(note: NonNullable<ProcessLayout["notes"]>[number] | null, id?: string | undefined): void;
98
+ setSection(section: NonNullable<ProcessLayout["sections"]>[number] | null, id?: string | undefined): void;
99
+ zoomBy(factor: number): void;
100
+ fit(): void;
101
+ private bounds;
102
+ protected renderTemplate(): void;
103
+ private setupPanes;
104
+ private renderPane;
105
+ private selectionCommand;
106
+ protected setupListeners(): void;
107
+ private onKey;
108
+ private pointerDown;
109
+ private distance;
110
+ private pointerMove;
111
+ private pointerEnd;
112
+ private canvasPoint;
113
+ private boxAt;
114
+ private lineAt;
115
+ private markDropLine;
116
+ private alignedPoint;
117
+ private openChooser;
118
+ protected update(): void;
119
+ private get allChecks();
120
+ private collectChecks;
121
+ private renderWorld;
122
+ private place;
123
+ private renderPalette;
124
+ private renderChecks;
125
+ private renderSelection;
126
+ private updateToolbar;
127
+ private paintViewport;
128
+ private focusBox;
129
+ private setStatus;
130
+ }