@weasel-js/diagram 1.10.1 → 1.11.1

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.
@@ -0,0 +1,2053 @@
1
+ import { grownBy, labelCenter, diagramEdgeOf, DIAGRAM_TRAIT_KEY, insetOf, subpathOf, portsOf, portSitesOf, routeHolds, sizeToBody, measureBody, layoutBody, layoutOwnsRoute, diagramNodeOf, expandGroupEdges, DEFAULT_GROUP_INSET, bodyTrait, authoredOver, chain, force, tree, layered, planOf, buildBody } from './chunk-IM6DHWZO.js';
2
+ import { bodyPredicate, AUTO_POSE_DESCRIPTOR, translatePoseViaDescriptor, createTransformOp, polylineFromPoints, PathBuilder, documentDepOf, documentPose, composeAffordanceLayer, ActionDisabledReason, pageName, pageOfBounds, layoutPages, RECT_POSE_DESCRIPTOR, commandFill, commandStroke, transformPath, registerNodeShape, pathFromD } from '@weasel-js/core';
3
+ import { PathBuilder as PathBuilder$1, polylineFromPoints as polylineFromPoints$1, portCurvePoints } from '@weasel-js/geom';
4
+ import { AUTO_POSE_DESCRIPTOR as AUTO_POSE_DESCRIPTOR$1 } from '@weasel-js/core/math';
5
+
6
+ function traitOf(node) {
7
+ const data = node.data;
8
+ if (data === null || typeof data !== "object") return null;
9
+ const trait = data[DIAGRAM_TRAIT_KEY];
10
+ return trait !== null && typeof trait === "object" ? trait : null;
11
+ }
12
+ function diagramGroupOf(node) {
13
+ const group = traitOf(node)?.group;
14
+ return group !== null && typeof group === "object" ? group : null;
15
+ }
16
+ function diagramAnchorOf(node) {
17
+ const anchor = traitOf(node)?.anchor;
18
+ return anchor !== null && typeof anchor === "object" && "at" in anchor ? anchor : null;
19
+ }
20
+ function groupDerivePose(opts = {}) {
21
+ const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
22
+ return (node, deps) => {
23
+ const group = diagramGroupOf(node);
24
+ if (group === null) return null;
25
+ let x0 = Infinity;
26
+ let y0 = Infinity;
27
+ let x1 = -Infinity;
28
+ let y1 = -Infinity;
29
+ for (const dep of deps) {
30
+ if (dep === void 0) continue;
31
+ const b = grownBy(geometry.getBounds(dep.pose), traitOf(dep.node)?.margin);
32
+ x0 = Math.min(x0, b.x);
33
+ y0 = Math.min(y0, b.y);
34
+ x1 = Math.max(x1, b.x + b.width);
35
+ y1 = Math.max(y1, b.y + b.height);
36
+ }
37
+ if (!Number.isFinite(x0)) return null;
38
+ return geometry.fromBounds(boxAround({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 }, group), node.pose);
39
+ };
40
+ }
41
+ function boxAround(union, group) {
42
+ const inset = insetOf(group.inset);
43
+ const width = union.width + inset.left + inset.right;
44
+ const grow = Math.max(0, (group.minWidth ?? 0) - width);
45
+ return {
46
+ x: union.x - inset.left - grow / 2,
47
+ y: union.y - inset.top,
48
+ width: width + grow,
49
+ height: union.height + inset.top + inset.bottom
50
+ };
51
+ }
52
+ function anchorDerivePose(opts = {}) {
53
+ const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
54
+ return (node, deps) => {
55
+ const anchor = diagramAnchorOf(node);
56
+ const dep = deps[0];
57
+ if (anchor === null || dep === void 0) return null;
58
+ const target = geometry.getBounds(dep.pose);
59
+ const own = geometry.getBounds(node.pose);
60
+ const self = anchor.self ?? anchor.at;
61
+ const x = target.x + anchor.at.u * target.width + (anchor.offset?.x ?? 0) - self.u * own.width;
62
+ const y = target.y + anchor.at.v * target.height + (anchor.offset?.y ?? 0) - self.v * own.height;
63
+ return translatePoseViaDescriptor(node.pose, x - own.x, y - own.y, geometry);
64
+ };
65
+ }
66
+ var DIAGRAM_GROUP = "diagram:group";
67
+ var DIAGRAM_ANCHOR = "diagram:anchor";
68
+ var GROUP_DERIVE_POSE = groupDerivePose();
69
+ var ANCHOR_DERIVE_POSE = anchorDerivePose();
70
+
71
+ // src/containers/trait.ts
72
+ function traitOf2(node) {
73
+ const data = node.data;
74
+ if (data === null || typeof data !== "object") return null;
75
+ const trait = data[DIAGRAM_TRAIT_KEY];
76
+ return trait !== null && typeof trait === "object" ? trait : null;
77
+ }
78
+ function diagramContainerOf(node) {
79
+ const c = traitOf2(node)?.container;
80
+ return c !== null && typeof c === "object" && typeof c.role === "string" ? c : null;
81
+ }
82
+ function laneOutlineOf(node) {
83
+ if (diagramContainerOf(node)?.role !== "lane") return void 0;
84
+ return traitOf2(node)?.outline;
85
+ }
86
+ var titleIdOf = (containerId) => `${containerId}/title`;
87
+ function headerBox(bounds, header) {
88
+ if (header === void 0) return null;
89
+ return header.side === "top" ? { x: bounds.x, y: bounds.y, width: bounds.width, height: Math.min(header.size, bounds.height) } : { x: bounds.x, y: bounds.y, width: Math.min(header.size, bounds.width), height: bounds.height };
90
+ }
91
+ function bodyBox(bounds, header) {
92
+ const h = headerBox(bounds, header);
93
+ if (h === null) return { ...bounds };
94
+ return header.side === "top" ? { x: bounds.x, y: bounds.y + h.height, width: bounds.width, height: bounds.height - h.height } : { x: bounds.x + h.width, y: bounds.y, width: bounds.width - h.width, height: bounds.height };
95
+ }
96
+
97
+ // src/containers/titlePose.ts
98
+ function titlePose(bounds, header) {
99
+ const h = headerBox(bounds, header) ?? { ...bounds, height: 0 };
100
+ if (header?.side !== "left") return { x: h.x, y: h.y, width: h.width, height: h.height };
101
+ const cx = h.x + h.width / 2;
102
+ const cy = h.y + h.height / 2;
103
+ return { x: cx - h.height / 2, y: cy - h.width / 2, width: h.height, height: h.width, rotation: -Math.PI / 2 };
104
+ }
105
+ var center = (p) => ({ x: p.x + p.width / 2, y: p.y + p.height / 2 });
106
+ function titlePoses(container, children, header) {
107
+ const out = /* @__PURE__ */ new Map();
108
+ const title = children.find((c) => c.id === titleIdOf(container.id));
109
+ if (title !== void 0) out.set(title.id, titlePose(container.bounds, header));
110
+ return out;
111
+ }
112
+ function diagramContainerLayout(opts = {}) {
113
+ const { header } = opts;
114
+ return {
115
+ snap: { pickTarget: (targets) => targets[0] ?? null },
116
+ childPoses: (container, children) => titlePoses(container, children, header),
117
+ getDropTargets: (_container, _children, dragged) => [{ pose: dragged.pose, origin: center(dragged.pose) }],
118
+ reflowPoses: () => /* @__PURE__ */ new Map(),
119
+ commitDrop: (_container, _children, dragged, target) => [createTransformOp({
120
+ id: dragged.id,
121
+ from: dragged.originPose,
122
+ to: target?.pose ?? dragged.pose,
123
+ label: "Drop"
124
+ })],
125
+ dropRegion(container) {
126
+ const b = container.bounds;
127
+ const body = bodyBox(b, header);
128
+ return { kind: "rect", x: body.x - b.x, y: body.y - b.y, width: body.width, height: body.height };
129
+ }
130
+ };
131
+ }
132
+ var POOL_HEADER_SIZE = 32;
133
+ var MIN_LANE_SIZE = 40;
134
+ var poolHeader = (axis, size = POOL_HEADER_SIZE) => ({ side: axis === "y" ? "left" : "top", size });
135
+ function stackedLanes(axis, bounds, header, lanes) {
136
+ const body = bodyBox(bounds, header);
137
+ const out = /* @__PURE__ */ new Map();
138
+ let at = axis === "y" ? bounds.y : bounds.x;
139
+ for (const { id, size } of lanes) {
140
+ out.set(id, axis === "y" ? { x: body.x, y: at, width: body.width, height: size } : { x: at, y: body.y, width: size, height: body.height });
141
+ at += size;
142
+ }
143
+ return out;
144
+ }
145
+ function laneStackLayout(opts) {
146
+ const { axis } = opts;
147
+ const header = poolHeader(axis, opts.headerSize);
148
+ const along = (p) => axis === "y" ? p.height : p.width;
149
+ const mid = (p) => axis === "y" ? p.y + p.height / 2 : p.x + p.width / 2;
150
+ const lanesOf = (container, children, skip) => children.filter((c) => c.id !== titleIdOf(container.id) && c.id !== skip).sort((a, b) => mid(a.pose) - mid(b.pose));
151
+ const sized = (lanes) => lanes.map((l) => ({ id: l.id, size: along(l.pose) }));
152
+ const withTitle = (container, children, poses, bounds = container.bounds) => {
153
+ if (children.some((c) => c.id === titleIdOf(container.id))) poses.set(titleIdOf(container.id), titlePose(bounds, header));
154
+ return poses;
155
+ };
156
+ const resized = (container, children, lanes) => {
157
+ const sizes = sized(lanes);
158
+ const total = Math.max(MIN_LANE_SIZE, sizes.reduce((s, l) => s + l.size, 0));
159
+ const bounds = axis === "y" ? { ...container.bounds, height: total } : { ...container.bounds, width: total };
160
+ return { poses: withTitle(container, children, stackedLanes(axis, bounds, header, sizes), bounds), bounds };
161
+ };
162
+ const insertAt = (container, children, dragged, index) => {
163
+ const sizes = sized(lanesOf(container, children, dragged.id));
164
+ sizes.splice(index, 0, { id: dragged.id, size: along(dragged.pose) });
165
+ return stackedLanes(axis, container.bounds, header, sizes);
166
+ };
167
+ const shifted = (container, children, dragged, target) => {
168
+ if (target === null) return /* @__PURE__ */ new Map();
169
+ const poses = insertAt(container, children, dragged, target.meta.index);
170
+ poses.delete(dragged.id);
171
+ return poses;
172
+ };
173
+ return {
174
+ snap: {
175
+ pickTarget(targets, pointer) {
176
+ const p = axis === "y" ? pointer.y : pointer.x;
177
+ let best = null;
178
+ for (const t of targets) {
179
+ if (best === null || Math.abs(t.origin[axis] - p) < Math.abs(best.origin[axis] - p)) best = t;
180
+ }
181
+ return best;
182
+ }
183
+ },
184
+ childPoses(container, children) {
185
+ const sizes = sized(lanesOf(container, children));
186
+ const last = sizes[sizes.length - 1];
187
+ if (last !== void 0) {
188
+ const slack = along(container.bounds) - sizes.reduce((s, l) => s + l.size, 0);
189
+ last.size = Math.max(MIN_LANE_SIZE, last.size + slack);
190
+ }
191
+ return withTitle(container, children, stackedLanes(axis, container.bounds, header, sizes));
192
+ },
193
+ getDropTargets(container, children, dragged) {
194
+ const count = lanesOf(container, children, dragged.id).length;
195
+ const self = { id: dragged.id, pose: dragged.pose };
196
+ const targets = [];
197
+ for (let index = 0; index <= count; index++) {
198
+ const pose = insertAt(container, children, self, index).get(dragged.id);
199
+ targets.push({ pose, origin: { x: pose.x + pose.width / 2, y: pose.y + pose.height / 2 }, meta: { index } });
200
+ }
201
+ return targets;
202
+ },
203
+ reflowPoses: shifted,
204
+ commitDrop(container, children, dragged, target) {
205
+ const ops = [];
206
+ const poses = shifted(container, children, dragged, target);
207
+ for (const c of children) {
208
+ const to = poses.get(c.id);
209
+ if (to !== void 0 && (to.x !== c.pose.x || to.y !== c.pose.y)) {
210
+ ops.push(createTransformOp({ id: c.id, from: c.pose, to, label: "Shift lane" }));
211
+ }
212
+ }
213
+ ops.push(createTransformOp({
214
+ id: dragged.id,
215
+ from: dragged.originPose,
216
+ to: target?.pose ?? dragged.pose,
217
+ label: "Move lane"
218
+ }));
219
+ return ops;
220
+ },
221
+ arrive: (container, children) => resized(container, children, lanesOf(container, children)),
222
+ depart: (container, children) => resized(container, children, lanesOf(container, children)),
223
+ dropRegion(container) {
224
+ const b = container.bounds;
225
+ const body = bodyBox(b, header);
226
+ return { kind: "rect", x: body.x - b.x, y: body.y - b.y, width: body.width, height: body.height };
227
+ },
228
+ releaseDrop: () => []
229
+ };
230
+ }
231
+
232
+ // src/containers/layouts.ts
233
+ var CONTAINER_HEADER = { side: "top", size: 28 };
234
+ var LANE_HEADER_SIZE = 28;
235
+ var laneHeader = (axis) => ({ side: axis === "y" ? "left" : "top", size: LANE_HEADER_SIZE });
236
+ var DIAGRAM_CONTAINER_LAYOUT = "diagram:container";
237
+ var DIAGRAM_POOL_ROWS_LAYOUT = "diagram:pool-rows";
238
+ var DIAGRAM_POOL_COLUMNS_LAYOUT = "diagram:pool-columns";
239
+ var DIAGRAM_LANE_ROWS_LAYOUT = "diagram:lane-rows";
240
+ var DIAGRAM_LANE_COLUMNS_LAYOUT = "diagram:lane-columns";
241
+ var CONTAINER_LAYOUTS = Object.freeze({
242
+ [DIAGRAM_CONTAINER_LAYOUT]: diagramContainerLayout({ header: CONTAINER_HEADER }),
243
+ [DIAGRAM_POOL_ROWS_LAYOUT]: laneStackLayout({ axis: "y" }),
244
+ [DIAGRAM_POOL_COLUMNS_LAYOUT]: laneStackLayout({ axis: "x" }),
245
+ [DIAGRAM_LANE_ROWS_LAYOUT]: diagramContainerLayout({ header: laneHeader("y") }),
246
+ [DIAGRAM_LANE_COLUMNS_LAYOUT]: diagramContainerLayout({ header: laneHeader("x") })
247
+ });
248
+ function containerShape(role, axis = "y") {
249
+ if (role === "pool") return { layout: axis === "y" ? DIAGRAM_POOL_ROWS_LAYOUT : DIAGRAM_POOL_COLUMNS_LAYOUT, header: poolHeader(axis) };
250
+ if (role === "lane") return { layout: axis === "y" ? DIAGRAM_LANE_ROWS_LAYOUT : DIAGRAM_LANE_COLUMNS_LAYOUT, header: laneHeader(axis) };
251
+ return { layout: DIAGRAM_CONTAINER_LAYOUT, header: CONTAINER_HEADER };
252
+ }
253
+
254
+ // src/labelTrait.ts
255
+ var LABEL_PLACEMENT = { at: "mid", offset: 9 };
256
+ function diagramLabelOf(node) {
257
+ const data = node.data;
258
+ if (data === null || typeof data !== "object") return null;
259
+ const trait = data.diagram;
260
+ if (trait === null || typeof trait !== "object") return null;
261
+ const label = trait.label;
262
+ if (label === null || typeof label !== "object") return null;
263
+ return "at" in label ? label : null;
264
+ }
265
+ function labelDerivePose(opts = {}) {
266
+ const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
267
+ return (node, deps) => {
268
+ const label = diagramLabelOf(node);
269
+ if (label === null) return null;
270
+ const path = deps[0]?.path ?? null;
271
+ if (path === null) return null;
272
+ const bounds = geometry.getBounds(node.pose);
273
+ const center2 = labelCenter(path, label, bounds);
274
+ if (center2 === null) return null;
275
+ return translatePoseViaDescriptor(
276
+ node.pose,
277
+ center2.x - (bounds.x + bounds.width / 2),
278
+ center2.y - (bounds.y + bounds.height / 2),
279
+ geometry
280
+ );
281
+ };
282
+ }
283
+ var DIAGRAM_LABEL = "diagram:label";
284
+ var LABEL_DERIVE_POSE = labelDerivePose();
285
+ var LABEL_DRAG_LAYER_ID = "diagram-edge-labels";
286
+ var LABEL_DRAG_AFFORDANCE_KIND = `layer:${LABEL_DRAG_LAYER_ID}`;
287
+ function labelScratchOf(hit) {
288
+ const payload = hit?.payload;
289
+ return payload !== void 0 && typeof payload.labelId === "string" && typeof payload.edgeId === "string" ? payload : null;
290
+ }
291
+ var labelEditable = (scene, labelId, edgeId) => !scene.isLocked(labelId) && !scene.isLocked(edgeId) && !scene.isHidden(labelId);
292
+ function* editableLabels(scene, edges) {
293
+ for (const node of scene.renderOrderNodes()) {
294
+ const label = diagramLabelOf(node);
295
+ if (label === null) continue;
296
+ const deps = node.dependsOn;
297
+ if (!Array.isArray(deps) || deps.length !== 1) continue;
298
+ const edgeId = deps[0];
299
+ if (edges !== void 0 && !edges.has(edgeId)) continue;
300
+ if (labelEditable(scene, node.id, edgeId)) yield { labelId: node.id, edgeId, label };
301
+ }
302
+ }
303
+ function* draggableLabels(scene) {
304
+ for (const { labelId, edgeId } of editableLabels(scene)) {
305
+ const path = documentDepOf(scene, edgeId)?.path ?? null;
306
+ if (path === null) continue;
307
+ const box = AUTO_POSE_DESCRIPTOR.getBounds(documentPose(scene, scene.get(labelId)));
308
+ yield { labelId, edgeId, box, path };
309
+ }
310
+ }
311
+ var holds = (b, at) => at.x >= b.x && at.x <= b.x + b.width && at.y >= b.y && at.y <= b.y + b.height;
312
+ function labelUnder(scene, at) {
313
+ let found = null;
314
+ for (const { labelId, edgeId, box } of draggableLabels(scene)) {
315
+ if (holds(box, at)) found = { targetId: labelId, labelId, edgeId };
316
+ }
317
+ return found;
318
+ }
319
+ function createLabelDragAffordance(scene, cursor) {
320
+ return {
321
+ id: LABEL_DRAG_LAYER_ID,
322
+ regions() {
323
+ const out = [];
324
+ for (const { labelId, edgeId, box } of draggableLabels(scene)) {
325
+ out.push({
326
+ id: labelId,
327
+ targetId: null,
328
+ shape: { kind: "rect", x: box.x, y: box.y, width: box.width, height: box.height },
329
+ cursor,
330
+ strength: "exclusive",
331
+ claimedKinds: ["pointer"],
332
+ bind: () => ({ initialScratch: { targetId: labelId, labelId, edgeId } })
333
+ });
334
+ }
335
+ return out;
336
+ }
337
+ };
338
+ }
339
+ function labelDragLayer(scene, opts = {}) {
340
+ return composeAffordanceLayer(
341
+ LABEL_DRAG_LAYER_ID,
342
+ "Diagram edge labels",
343
+ [createLabelDragAffordance(scene, opts.cursor ?? "move")]
344
+ );
345
+ }
346
+ var labelStyleOf = (font) => ({ ...font, fontSize: font.fontSize - 2 });
347
+ var DEFAULT_FONT = { fontFamily: "sans-serif", fontSize: 12 };
348
+ var GLYPH = 0.6;
349
+ var LABEL_STYLE = labelStyleOf(DEFAULT_FONT);
350
+ var LABEL_FILL = "#dbe7f2";
351
+ function labelSize(text, style, measure) {
352
+ const size = style.fontSize ?? LABEL_STYLE.fontSize;
353
+ return { width: measure !== void 0 ? measure(text, style).width : text.length * size * GLYPH, height: size };
354
+ }
355
+ function edgeLabelSpec(edgeId, text, opts) {
356
+ const style = opts.style ?? LABEL_STYLE;
357
+ return {
358
+ id: `${edgeId}/label`,
359
+ kind: "leaf",
360
+ layer: opts.layer,
361
+ pickable: false,
362
+ pose: { x: 0, y: 0, ...labelSize(text, style, opts.measure) },
363
+ data: {
364
+ diagram: { label: opts.label ?? LABEL_PLACEMENT },
365
+ text,
366
+ style,
367
+ fill: { paint: { color: opts.fill ?? LABEL_FILL } }
368
+ },
369
+ dependsOn: [edgeId],
370
+ derivePose: LABEL_DERIVE_POSE
371
+ };
372
+ }
373
+ function dropEmptyEdgeLabel(scene, id) {
374
+ const node = scene.get(id);
375
+ if (node === void 0 || diagramLabelOf(node) === null) return false;
376
+ const text = node.data.text;
377
+ if (typeof text === "string" && text.trim() !== "") return false;
378
+ scene.remove(id);
379
+ return true;
380
+ }
381
+ var EDIT_EDGE_LABEL_ACTION_ID = "diagram.editEdgeLabel";
382
+ function edgeUnder(deps, at) {
383
+ if (deps === void 0 || at === void 0) return null;
384
+ const label = deps.scene === void 0 ? null : labelUnder(deps.scene, at);
385
+ if (label !== null) return label.edgeId;
386
+ const id = deps.nodeAtPoint?.(at);
387
+ const node = id == null ? void 0 : deps.scene?.get(id);
388
+ return node !== void 0 && diagramEdgeOf(node) !== null ? id : null;
389
+ }
390
+ function beginEditing(edgeId, ctx, look) {
391
+ const { scene, textEdit } = ctx.deps;
392
+ if (scene === void 0) return;
393
+ const labelId = `${edgeId}/label`;
394
+ if (scene.get(labelId) === void 0) {
395
+ const edge = scene.get(edgeId);
396
+ if (edge === void 0) return;
397
+ scene.add(edgeLabelSpec(edgeId, "", {
398
+ layer: edge.layer,
399
+ ...look.style ? { style: look.style } : {},
400
+ ...look.fill !== void 0 ? { fill: look.fill } : {}
401
+ }));
402
+ }
403
+ textEdit?.startEdit(labelId);
404
+ }
405
+ function editEdgeLabelAction(opts = {}) {
406
+ const commit = opts.commit ?? ((edgeId, ctx) => beginEditing(edgeId, ctx, opts));
407
+ return {
408
+ id: EDIT_EDGE_LABEL_ACTION_ID,
409
+ label: "Edit edge label",
410
+ group: "diagram",
411
+ requires: ["scene", "nodeAtPoint", "textEdit"],
412
+ eligible: { mode: { in: ["normal", "isolation"] } },
413
+ invoker: {
414
+ timing: "immediate",
415
+ run(deps, params) {
416
+ const world = { x: Number(params?.worldX), y: Number(params?.worldY) };
417
+ const pressed = labelScratchOf(params?.affordance);
418
+ const edgeId = pressed?.edgeId ?? edgeUnder(deps, world);
419
+ if (edgeId === null) return;
420
+ const ctx = { world, modifiers: { alt: false, shift: false, meta: false, ctrl: false }, deps };
421
+ commit(edgeId, ctx);
422
+ }
423
+ },
424
+ enabled: (deps, at) => edgeUnder(deps, at) !== null ? true : ActionDisabledReason.NotApplicable
425
+ };
426
+ }
427
+ var onBody = bodyPredicate((target) => target === "selected-body" || target === "unselected-body");
428
+ function editEdgeLabelBinding(actionId = EDIT_EDGE_LABEL_ACTION_ID) {
429
+ return { spec: { kind: "doubleClick", target: { kindOf: onBody } }, actionId };
430
+ }
431
+
432
+ // src/theme/themes.ts
433
+ var SANS = { family: "sans-serif", size: 12 };
434
+ var INK = "#1f2933";
435
+ var paper = {
436
+ id: "paper",
437
+ name: "Paper",
438
+ roles: {
439
+ default: { fill: "#ffffff", stroke: "#3e4c59", text: INK },
440
+ accent: { fill: "#e6f0ff", stroke: "#2f6fd6", text: INK },
441
+ muted: { fill: "#f3f5f7", stroke: "#9aa5b1", text: "#616e7c" },
442
+ positive: { fill: "#e8f6ec", stroke: "#2f9e57", text: INK },
443
+ warning: { fill: "#fdf4dc", stroke: "#c98a0b", text: INK },
444
+ negative: { fill: "#fdecec", stroke: "#d14343", text: INK }
445
+ },
446
+ defaultRole: "default",
447
+ edge: { stroke: "#3e4c59", width: 2, markerEnd: "arrow" },
448
+ label: { fill: INK },
449
+ container: { fill: "rgba(62, 76, 89, 0.04)", stroke: "#7b8794", text: "#52606d", strokeWidth: 1, dash: [4, 3] },
450
+ lane: {
451
+ header: { fill: "#e4e7eb", stroke: "#9aa5b1", text: INK, strokeWidth: 1 },
452
+ bodies: [
453
+ { fill: "#ffffff", stroke: "#cbd2d9", text: INK, strokeWidth: 1 },
454
+ { fill: "#f5f7fa", stroke: "#cbd2d9", text: INK, strokeWidth: 1 }
455
+ ]
456
+ },
457
+ page: "#ffffff",
458
+ font: SANS,
459
+ series: ["#2f6fd6", "#8b5cf6", "#c98a0b", "#2f9e57", "#e8590c", "#0f9b8e", "#d6336c", "#7b8794"]
460
+ };
461
+ var NIGHT_TEXT = "#dbe7f2";
462
+ var night = {
463
+ id: "night",
464
+ name: "Night",
465
+ roles: {
466
+ default: { fill: "#16222c", stroke: "#3d566b", text: NIGHT_TEXT, strokeWidth: 1.5 },
467
+ accent: { fill: "#13263a", stroke: "#6fb3ff", text: NIGHT_TEXT, strokeWidth: 1.5 },
468
+ muted: { fill: "#121a21", stroke: "#3d566b", text: "#6d8496", strokeWidth: 1.5 },
469
+ positive: { fill: "#14261c", stroke: "#5ad07f", text: NIGHT_TEXT, strokeWidth: 1.5 },
470
+ warning: { fill: "#2a2414", stroke: "#f2c14e", text: NIGHT_TEXT, strokeWidth: 1.5 },
471
+ negative: { fill: "#2c1618", stroke: "#ff5d5d", text: NIGHT_TEXT, strokeWidth: 1.5 }
472
+ },
473
+ defaultRole: "default",
474
+ edge: { stroke: "#7ba7c7", width: 2, markerEnd: "arrow" },
475
+ label: { fill: NIGHT_TEXT },
476
+ container: { fill: "rgba(123, 167, 199, 0.08)", stroke: "#7ba7c7", text: "#9fbdd6", strokeWidth: 1, dash: [4, 3] },
477
+ lane: {
478
+ header: { fill: "rgba(255, 255, 255, 0.06)", stroke: "rgba(255, 255, 255, 0.06)", text: "#9fbdd6", strokeWidth: 1 },
479
+ bodies: [
480
+ { fill: "rgba(255, 255, 255, 0.035)", stroke: "rgba(255, 255, 255, 0.06)", text: "#9fbdd6", strokeWidth: 1 },
481
+ { fill: "rgba(255, 255, 255, 0)", stroke: "rgba(255, 255, 255, 0.06)", text: "#9fbdd6", strokeWidth: 1 }
482
+ ]
483
+ },
484
+ page: "#0e151b",
485
+ font: SANS,
486
+ series: ["#6fb3ff", "#c58bff", "#f2c14e", "#5ad07f", "#ff8a6b", "#4fd1c5", "#e58fb8", "#b0bec5"]
487
+ };
488
+ var LINE = "#e8f1ff";
489
+ var blueprint = {
490
+ id: "blueprint",
491
+ name: "Blueprint",
492
+ roles: {
493
+ default: { fill: "#1d4a85", stroke: LINE, text: LINE },
494
+ accent: { fill: "#2560a8", stroke: "#ffe28a", text: "#ffffff" },
495
+ muted: { fill: "#183f72", stroke: "#8fb1dc", text: "#b9cfee" },
496
+ positive: { fill: "#1d4a85", stroke: "#8ff0b0", text: LINE },
497
+ warning: { fill: "#1d4a85", stroke: "#ffd166", text: LINE },
498
+ negative: { fill: "#1d4a85", stroke: "#ff9b9b", text: LINE }
499
+ },
500
+ defaultRole: "default",
501
+ edge: { stroke: LINE, width: 1.5, markerEnd: "arrow" },
502
+ label: { fill: LINE },
503
+ container: { fill: "rgba(232, 241, 255, 0.06)", stroke: "#8fb1dc", text: "#b9cfee", strokeWidth: 1, dash: [6, 4] },
504
+ lane: {
505
+ header: { fill: "#1d4a85", stroke: "#8fb1dc", text: LINE, strokeWidth: 1 },
506
+ bodies: [
507
+ { fill: "rgba(232, 241, 255, 0.05)", stroke: "#8fb1dc", text: LINE, strokeWidth: 1 },
508
+ { fill: "rgba(232, 241, 255, 0)", stroke: "#8fb1dc", text: LINE, strokeWidth: 1 }
509
+ ]
510
+ },
511
+ page: "#163a6b",
512
+ font: { family: "ui-monospace, monospace", size: 12 },
513
+ series: ["#ffe28a", "#8ff0b0", "#9fd3ff", "#ffb38a", "#d9b8ff", "#7fe3d8", "#ff9b9b", "#c9d6e8"]
514
+ };
515
+ var PLUM = "#4a3b40";
516
+ var pastel = {
517
+ id: "pastel",
518
+ name: "Pastel",
519
+ roles: {
520
+ default: { fill: "#e3f0ff", stroke: "#7d9cc4", text: PLUM },
521
+ accent: { fill: "#fde2e4", stroke: "#c9848f", text: PLUM },
522
+ muted: { fill: "#f4f1ee", stroke: "#bfb5ad", text: "#8a7f78" },
523
+ positive: { fill: "#dcf5e3", stroke: "#6fb487", text: PLUM },
524
+ warning: { fill: "#fff1cc", stroke: "#d4a84a", text: PLUM },
525
+ negative: { fill: "#ffdcd6", stroke: "#d77f73", text: PLUM }
526
+ },
527
+ defaultRole: "default",
528
+ edge: { stroke: "#8a7f99", width: 2, markerEnd: "arrow" },
529
+ label: { fill: PLUM },
530
+ container: { fill: "rgba(201, 132, 143, 0.06)", stroke: "#c9b8c2", text: "#7a6670", strokeWidth: 1, dash: [4, 3] },
531
+ lane: {
532
+ header: { fill: "#efe6f5", stroke: "#c9b8c2", text: PLUM, strokeWidth: 1 },
533
+ bodies: [
534
+ { fill: "#fffaf3", stroke: "#e6dccf", text: PLUM, strokeWidth: 1 },
535
+ { fill: "#fbf3ea", stroke: "#e6dccf", text: PLUM, strokeWidth: 1 }
536
+ ]
537
+ },
538
+ page: "#fffaf3",
539
+ font: SANS,
540
+ series: ["#7d9cc4", "#c9848f", "#d4a84a", "#6fb487", "#b48fd1", "#5fb3ad", "#e08fb0", "#a3a3a3"]
541
+ };
542
+ var BLACK = "#111111";
543
+ var shade = (fill) => ({ fill, stroke: BLACK, text: BLACK });
544
+ var mono = {
545
+ id: "mono",
546
+ name: "Mono",
547
+ roles: {
548
+ default: shade("#ffffff"),
549
+ accent: shade("#d9d9d9"),
550
+ muted: { fill: "#f5f5f5", stroke: "#9e9e9e", text: "#757575" },
551
+ positive: shade("#ededed"),
552
+ warning: shade("#c4c4c4"),
553
+ negative: { fill: "#595959", stroke: BLACK, text: "#ffffff" }
554
+ },
555
+ defaultRole: "default",
556
+ edge: { stroke: BLACK, width: 1.5, markerEnd: "arrow" },
557
+ label: { fill: BLACK },
558
+ container: { fill: "rgba(0, 0, 0, 0.03)", stroke: "#757575", text: "#424242", strokeWidth: 1, dash: [4, 3] },
559
+ lane: {
560
+ header: { fill: "#e0e0e0", stroke: "#757575", text: BLACK, strokeWidth: 1 },
561
+ bodies: [
562
+ { fill: "#ffffff", stroke: "#bdbdbd", text: BLACK, strokeWidth: 1 },
563
+ { fill: "#f5f5f5", stroke: "#bdbdbd", text: BLACK, strokeWidth: 1 }
564
+ ]
565
+ },
566
+ page: "#ffffff",
567
+ font: SANS,
568
+ series: ["#111111", "#555555", "#888888", "#333333", "#777777", "#222222", "#666666", "#999999"]
569
+ };
570
+ var DIAGRAM_THEMES = Object.freeze({ paper, night, blueprint, pastel, mono });
571
+
572
+ // src/theme/types.ts
573
+ var DIAGRAM_ROLES = [
574
+ { id: "default", label: "Default" },
575
+ { id: "accent", label: "Accent" },
576
+ { id: "muted", label: "Muted" },
577
+ { id: "positive", label: "Positive" },
578
+ { id: "warning", label: "Warning" },
579
+ { id: "negative", label: "Negative" }
580
+ ];
581
+ var DIAGRAM_THEME_KEY = "diagram:theme";
582
+
583
+ // src/theme/resolve.ts
584
+ function roleStyle(theme, role) {
585
+ return (role !== void 0 ? theme.roles[role] : void 0) ?? theme.roles[theme.defaultRole];
586
+ }
587
+ function roleEdgeStyle(theme, role) {
588
+ const own = role !== void 0 ? theme.roles[role] : void 0;
589
+ if (own !== void 0) return { ...theme.edge, stroke: own.stroke, label: own.stroke };
590
+ return { ...theme.edge, label: theme.edge.label ?? theme.label.fill };
591
+ }
592
+ function themeEdgeStroke(theme) {
593
+ const { stroke, width, dash, markerStart, markerEnd } = theme.edge;
594
+ return {
595
+ paint: { color: stroke },
596
+ width: width ?? 2,
597
+ ...dash ? { dash } : {},
598
+ ...markerStart ? { markerStart } : {},
599
+ ...markerEnd !== null ? { markerEnd: markerEnd ?? "arrow" } : {}
600
+ };
601
+ }
602
+ var themeFont = (theme) => ({ fontFamily: theme.font.family, fontSize: theme.font.size });
603
+ var themeLabelFamily = (theme) => theme.label.font?.family ?? theme.font.family;
604
+ var themeLabelFont = (theme, font = themeFont(theme)) => theme.label.font ? { fontFamily: theme.label.font.family, fontSize: theme.label.font.size } : font;
605
+ var themeLabelStyle = (theme) => labelStyleOf(themeLabelFont(theme));
606
+ function diagramThemeFrom(value, fallback = DIAGRAM_THEMES.night) {
607
+ if (typeof value === "string") return DIAGRAM_THEMES[value] ?? fallback;
608
+ if (value !== null && typeof value === "object" && "roles" in value) return value;
609
+ return fallback;
610
+ }
611
+ function diagramThemeOf(scene, fallback) {
612
+ return diagramThemeFrom(scene.doc.get(DIAGRAM_THEME_KEY), fallback);
613
+ }
614
+ var themeDocValue = (theme) => DIAGRAM_THEMES[theme.id] === theme ? theme.id : theme;
615
+
616
+ // src/offPage/trait.ts
617
+ var offPageTagId = (edgeId, end) => `${edgeId}/offpage-${end}`;
618
+ function offPageTagOf(node) {
619
+ const data = node.data;
620
+ if (data === null || typeof data !== "object") return null;
621
+ const trait = data.diagram;
622
+ const tag = trait !== null && typeof trait === "object" ? trait.offPageTag : void 0;
623
+ if (tag === null || typeof tag !== "object") return null;
624
+ const { edge, end } = tag;
625
+ return typeof edge === "string" && (end === "from" || end === "to") ? { edge, end } : null;
626
+ }
627
+ var OFF_PAGE_TAG_PADDING = 3;
628
+ var GAP = 2;
629
+ var offPageTagLook = (theme) => ({ style: themeLabelStyle(theme), fill: theme.label.fill, background: theme.page });
630
+ var offPageTagText = (pageName2, ref) => `${pageName2} \xB7 ${ref}`;
631
+ function offPageFarText(pages, split, end) {
632
+ const far = end === "from" ? split.to : split.from;
633
+ const index = pages.findIndex((p) => p.id === far);
634
+ return offPageTagText(index < 0 ? far : pageName(pages[index], index), split.ref);
635
+ }
636
+ function offPageTagSize(text, look) {
637
+ const pad = look.padding ?? OFF_PAGE_TAG_PADDING;
638
+ const size = labelSize(text, look.style, look.measure);
639
+ return { width: size.width + 2 * pad, height: size.height + 2 * pad };
640
+ }
641
+ function offPageTagData(edgeId, end, text, look) {
642
+ return {
643
+ diagram: { offPageTag: { edge: edgeId, end } },
644
+ text,
645
+ style: look.style,
646
+ fill: { paint: { color: look.fill } },
647
+ background: { paint: { color: look.background } },
648
+ padding: look.padding ?? OFF_PAGE_TAG_PADDING
649
+ };
650
+ }
651
+ function offPageTagSpec(edgeId, end, text, look) {
652
+ return {
653
+ id: offPageTagId(edgeId, end),
654
+ kind: "leaf",
655
+ layer: look.layer,
656
+ pose: { x: 0, y: 0, ...offPageTagSize(text, look) },
657
+ data: offPageTagData(edgeId, end, text, look),
658
+ dependsOn: [edgeId],
659
+ derivePose: OFF_PAGE_TAG_DERIVE_POSE
660
+ };
661
+ }
662
+ function offPageTagSpecs(edgeId, split, pages, look) {
663
+ return ["from", "to"].map((end) => offPageTagSpec(edgeId, end, offPageFarText(pages, split, end), look));
664
+ }
665
+ function stubTip(path, end) {
666
+ const stub = subpathOf(path, end === "from" ? 0 : 1);
667
+ if (stub === null || stub.kind !== "polygon" || stub.coords.length < 4) return null;
668
+ const c = stub.coords;
669
+ const n = c.length;
670
+ const [tip, base] = end === "from" ? [{ x: c[n - 2], y: c[n - 1] }, { x: c[0], y: c[1] }] : [{ x: c[0], y: c[1] }, { x: c[n - 2], y: c[n - 1] }];
671
+ const len = Math.hypot(tip.x - base.x, tip.y - base.y);
672
+ return len === 0 ? null : { tip, out: { x: (tip.x - base.x) / len, y: (tip.y - base.y) / len } };
673
+ }
674
+ function offPageTagDerivePose(opts = {}) {
675
+ const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
676
+ return (node, deps) => {
677
+ const tag = offPageTagOf(node);
678
+ const path = deps[0]?.path ?? null;
679
+ if (tag === null || path === null) return null;
680
+ const at = stubTip(path, tag.end);
681
+ if (at === null) return null;
682
+ const b = geometry.getBounds(node.pose);
683
+ const reach = GAP + (Math.abs(at.out.x) * b.width + Math.abs(at.out.y) * b.height) / 2;
684
+ const cx = at.tip.x + at.out.x * reach;
685
+ const cy = at.tip.y + at.out.y * reach;
686
+ return translatePoseViaDescriptor(node.pose, cx - (b.x + b.width / 2), cy - (b.y + b.height / 2), geometry);
687
+ };
688
+ }
689
+ var DIAGRAM_OFF_PAGE_TAG = "diagram:offPageTag";
690
+ var OFF_PAGE_TAG_DERIVE_POSE = offPageTagDerivePose();
691
+
692
+ // src/selfLoop.ts
693
+ var dot = (a, b) => a.x * b.x + a.y * b.y;
694
+ function selfLoop(ports, standoff) {
695
+ const facing2 = ports.filter((p) => p.normal !== null);
696
+ const most = (dir, among) => among.reduce(
697
+ (best, p) => best === void 0 || dot(p.normal, dir) > dot(best.normal, dir) ? p : best,
698
+ void 0
699
+ );
700
+ const from = most({ x: 1, y: 0 }, facing2);
701
+ if (from === void 0) return null;
702
+ const to = most({ x: 0, y: -1 }, facing2.filter((p) => Math.abs(dot(p.normal, from.normal)) < 0.5));
703
+ if (to === void 0) return null;
704
+ const out = { x: from.point.x + from.normal.x * standoff, y: from.point.y + from.normal.y * standoff };
705
+ const reach = dot({ x: to.point.x - out.x, y: to.point.y - out.y }, to.normal) + standoff;
706
+ return { from, to, via: { x: out.x + to.normal.x * reach, y: out.y + to.normal.y * reach } };
707
+ }
708
+ var ON_ROUTE = 0.5;
709
+ var KAPPA = 0.5523;
710
+ function bulge(u) {
711
+ if (Math.abs(u.y) < 1e-9) return { x: 0, y: -1 };
712
+ if (Math.abs(u.x) < 1e-9) return { x: -1, y: 0 };
713
+ return { x: u.y, y: -u.x };
714
+ }
715
+ function placedOn(a, b, hops) {
716
+ const len = Math.hypot(b.x - a.x, b.y - a.y);
717
+ if (len === 0) return [];
718
+ const ux = (b.x - a.x) / len;
719
+ const uy = (b.y - a.y) / len;
720
+ const found = [];
721
+ for (const hop of hops) {
722
+ const dx = hop.at.x - a.x;
723
+ const dy = hop.at.y - a.y;
724
+ const t = dx * ux + dy * uy;
725
+ if (Math.abs(dx * uy - dy * ux) > ON_ROUTE || t - hop.r < 0 || t + hop.r > len) continue;
726
+ found.push({ t, r: hop.r });
727
+ }
728
+ found.sort((m, n) => m.t - n.t);
729
+ const out = [];
730
+ for (const p of found) {
731
+ const last = out[out.length - 1];
732
+ if (last === void 0 || p.t - p.r >= last.t + last.r) out.push(p);
733
+ }
734
+ return out;
735
+ }
736
+ function withHops(points, hops) {
737
+ const b = new PathBuilder$1();
738
+ const first = points[0];
739
+ if (first === void 0 || hops.length === 0) return polylineFromPoints$1(points);
740
+ b.moveTo(first.x, first.y);
741
+ let arcs = 0;
742
+ for (let i = 1; i < points.length; i++) {
743
+ const p = points[i - 1];
744
+ const q = points[i];
745
+ const len = Math.hypot(q.x - p.x, q.y - p.y);
746
+ const u = len === 0 ? { x: 1, y: 0 } : { x: (q.x - p.x) / len, y: (q.y - p.y) / len };
747
+ const n = bulge(u);
748
+ for (const { t, r } of placedOn(p, q, hops)) {
749
+ const c = { x: p.x + u.x * t, y: p.y + u.y * t };
750
+ const top = { x: c.x + n.x * r, y: c.y + n.y * r };
751
+ const k = KAPPA * r;
752
+ b.lineTo(c.x - u.x * r, c.y - u.y * r);
753
+ b.curveTo(c.x - u.x * r + n.x * k, c.y - u.y * r + n.y * k, top.x - u.x * k, top.y - u.y * k, top.x, top.y);
754
+ b.curveTo(top.x + u.x * k, top.y + u.y * k, c.x + u.x * r + n.x * k, c.y + u.y * r + n.y * k, c.x + u.x * r, c.y + u.y * r);
755
+ arcs++;
756
+ }
757
+ b.lineTo(q.x, q.y);
758
+ }
759
+ return arcs === 0 ? polylineFromPoints$1(points) : b.build();
760
+ }
761
+ var straight = (req) => [req.from.point, ...req.waypoints, req.to.point];
762
+ var orthogonal = (req) => {
763
+ const stops = [req.from.point, ...req.waypoints, req.to.point];
764
+ const out = [stops[0]];
765
+ for (let i = 1; i < stops.length; i++) {
766
+ const a = out[out.length - 1];
767
+ const b = stops[i];
768
+ if (a.x === b.x || a.y === b.y) {
769
+ out.push(b);
770
+ continue;
771
+ }
772
+ const leaveHorizontal = i === 1 ? isHorizontal(req.from.normal) : out.length > 1 && out[out.length - 2].y === a.y;
773
+ const arriving = i === stops.length - 1 ? req.to.normal : null;
774
+ const arriveHorizontal = arriving == null ? !leaveHorizontal : isHorizontal(arriving);
775
+ if (leaveHorizontal !== arriveHorizontal) {
776
+ out.push(leaveHorizontal ? { x: b.x, y: a.y } : { x: a.x, y: b.y });
777
+ out.push(b);
778
+ continue;
779
+ }
780
+ const prev = out.length > 1 ? out[out.length - 2] : null;
781
+ if (leaveHorizontal) {
782
+ const mid = (a.x + b.x) / 2;
783
+ if (prev !== null && (mid - a.x) * (a.x - prev.x) < 0) out.push({ x: a.x, y: b.y }, b);
784
+ else out.push({ x: mid, y: a.y }, { x: mid, y: b.y }, b);
785
+ } else {
786
+ const mid = (a.y + b.y) / 2;
787
+ if (prev !== null && (mid - a.y) * (a.y - prev.y) < 0) out.push({ x: b.x, y: a.y }, b);
788
+ else out.push({ x: a.x, y: mid }, { x: b.x, y: mid }, b);
789
+ }
790
+ }
791
+ return out;
792
+ };
793
+ function isHorizontal(normal) {
794
+ return Math.abs(normal?.x ?? 1) >= Math.abs(normal?.y ?? 0);
795
+ }
796
+ var bezier = (req) => {
797
+ const stops = [req.from.point, ...req.waypoints, req.to.point];
798
+ const out = [stops[0]];
799
+ for (let i = 1; i < stops.length; i++) {
800
+ const a = stops[i - 1];
801
+ const b = stops[i];
802
+ const outN = i === 1 ? req.from.normal : through(stops, i - 1);
803
+ const inN = i === stops.length - 1 ? req.to.normal : negated(through(stops, i));
804
+ const leg = portCurvePoints(
805
+ a.x,
806
+ a.y,
807
+ b.x,
808
+ b.y,
809
+ outN == null ? null : [outN.x, outN.y],
810
+ inN == null ? null : [inN.x, inN.y],
811
+ BEZIER_SAMPLES
812
+ );
813
+ for (const [x, y] of leg) out.push({ x, y });
814
+ }
815
+ return out;
816
+ };
817
+ var BEZIER_SAMPLES = 16;
818
+ function through(stops, i) {
819
+ const last = stops.length - 1;
820
+ const run = (a, b) => a >= 1 && b <= last - 1 && (stops[a].x === stops[b].x || stops[a].y === stops[b].y);
821
+ const [from, to] = run(i, i + 1) ? [i, i + 1] : run(i - 1, i) ? [i - 1, i] : [i - 1, i + 1];
822
+ const dx = stops[to].x - stops[from].x;
823
+ const dy = stops[to].y - stops[from].y;
824
+ const len = Math.hypot(dx, dy);
825
+ return len === 0 ? null : { x: dx / len, y: dy / len };
826
+ }
827
+ var negated = (v) => v === null ? null : { x: -v.x, y: -v.y };
828
+ var avoid = (req) => req.route !== void 0 && routeHolds(req.from, req.to, req.waypoints, req.route) ? [req.from.point, ...req.route, req.to.point] : orthogonal(req);
829
+ var ROUTERS = Object.freeze({
830
+ straight,
831
+ orthogonal,
832
+ bezier,
833
+ avoid
834
+ });
835
+ function resolveEnd(end, dep, toward, opts, allPorts) {
836
+ if (end.port !== void 0) {
837
+ const sites = portSitesOf(dep.node, dep.pose, end.port, opts);
838
+ const ahead = sites.filter((p) => p.normal !== null && p.normal.x * (toward.x - p.point.x) + p.normal.y * (toward.y - p.point.y) > 0);
839
+ if (sites.length > 0) return ahead.length > 0 ? facing(ahead, toward) : sites[0];
840
+ }
841
+ return facing(allPorts(dep), toward);
842
+ }
843
+ function boxCenter(geometry, pose) {
844
+ const b = geometry.getBounds(pose);
845
+ return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
846
+ }
847
+ function endAims(fromCenter, toCenter, waypoints, route = []) {
848
+ return {
849
+ from: route[0] ?? waypoints[0] ?? toCenter,
850
+ to: route[route.length - 1] ?? waypoints[waypoints.length - 1] ?? fromCenter
851
+ };
852
+ }
853
+ function facing(ports, toward) {
854
+ let best;
855
+ let bestScore = -Infinity;
856
+ for (const port of ports) {
857
+ const dx = toward.x - port.point.x;
858
+ const dy = toward.y - port.point.y;
859
+ const len = Math.hypot(dx, dy) || 1;
860
+ const aim = port.normal === null ? 0 : (port.normal.x * dx + port.normal.y * dy) / len;
861
+ const score = aim - len / 1e6;
862
+ if (score > bestScore) {
863
+ bestScore = score;
864
+ best = port;
865
+ }
866
+ }
867
+ return best;
868
+ }
869
+ function routerOf(edge, routers = ROUTERS) {
870
+ return edge.router !== void 0 && routers[edge.router] || straight;
871
+ }
872
+ function edgeDerivePath(opts = {}) {
873
+ const routeOf = edgeRouter(opts);
874
+ return (node, deps) => {
875
+ const route = routeOf(node, deps);
876
+ if (route?.stubs !== void 0) return stubsPath(route.stubs);
877
+ const points = route?.points;
878
+ if (points === void 0 || points.length < 2) return null;
879
+ const hops = diagramEdgeOf(node)?.hops;
880
+ return hops !== void 0 && hops.length > 0 ? withHops(points, hops) : polylineFromPoints(points);
881
+ };
882
+ }
883
+ var OFF_PAGE_STUB = 24;
884
+ function stubTip2(port, toward) {
885
+ const n = port.normal ?? unit(toward.x - port.point.x, toward.y - port.point.y);
886
+ return { x: port.point.x + n.x * OFF_PAGE_STUB, y: port.point.y + n.y * OFF_PAGE_STUB };
887
+ }
888
+ function unit(x, y) {
889
+ const len = Math.hypot(x, y);
890
+ return len === 0 ? { x: 1, y: 0 } : { x: x / len, y: y / len };
891
+ }
892
+ function stubsPath(stubs) {
893
+ const b = new PathBuilder();
894
+ for (const run of stubs) run.forEach((p, i) => i === 0 ? b.moveTo(p.x, p.y) : b.lineTo(p.x, p.y));
895
+ return b.build();
896
+ }
897
+ function edgeRouter(opts = {}) {
898
+ const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
899
+ const portOpts = { geometry, ...opts.read ? { read: opts.read } : {} };
900
+ const allPorts = (d) => portsOf(d.node, d.pose, portOpts);
901
+ const standoffOf = (d) => {
902
+ const b = geometry.getBounds(d.pose);
903
+ return Math.min(b.width, b.height) / 2;
904
+ };
905
+ return (node, deps) => {
906
+ const edge = diagramEdgeOf(node);
907
+ if (edge === null) return null;
908
+ const [fromDep, toDep] = deps;
909
+ if (fromDep === void 0 || toDep === void 0) return null;
910
+ const loop = fromDep.node.id === toDep.node.id && edge.from.port === void 0 && edge.to.port === void 0 ? selfLoop(allPorts(fromDep), opts.selfLoopStandoff ?? standoffOf(fromDep)) : null;
911
+ const authored = edge.waypoints ?? [];
912
+ const aims = endAims(boxCenter(geometry, fromDep.pose), boxCenter(geometry, toDep.pose), authored, edge.route);
913
+ const from = loop?.from ?? resolveEnd(edge.from, fromDep, aims.from, portOpts, allPorts);
914
+ const to = loop?.to ?? resolveEnd(edge.to, toDep, aims.to, portOpts, allPorts);
915
+ if (from === void 0 || to === void 0) return null;
916
+ const waypoints = loop !== null && authored.length === 0 ? [loop.via] : authored;
917
+ const points = routerOf(edge, opts.routers)({ from, to, waypoints, route: edge.route });
918
+ if (edge.offPage === void 0) return { from, to, waypoints, points };
919
+ const stubs = [[from.point, stubTip2(from, to.point)], [stubTip2(to, from.point), to.point]];
920
+ return { from, to, waypoints, points, stubs };
921
+ };
922
+ }
923
+ var DIAGRAM_EDGE = "diagram:edge";
924
+ var EDGE_DERIVE_PATH = edgeDerivePath();
925
+ function withDiagramRegistry(registry = {}) {
926
+ return {
927
+ ...registry,
928
+ derivePath: {
929
+ [DIAGRAM_EDGE]: EDGE_DERIVE_PATH,
930
+ ...registry.derivePath
931
+ },
932
+ derivePose: {
933
+ [DIAGRAM_LABEL]: LABEL_DERIVE_POSE,
934
+ [DIAGRAM_GROUP]: GROUP_DERIVE_POSE,
935
+ [DIAGRAM_ANCHOR]: ANCHOR_DERIVE_POSE,
936
+ [DIAGRAM_OFF_PAGE_TAG]: OFF_PAGE_TAG_DERIVE_POSE,
937
+ ...registry.derivePose
938
+ },
939
+ layout: {
940
+ ...CONTAINER_LAYOUTS,
941
+ ...registry.layout
942
+ }
943
+ };
944
+ }
945
+
946
+ // src/bodyRelayout.ts
947
+ function bodyLayoutOf(spec, trait) {
948
+ if (trait.margin !== void 0 || !spec.rows.every((r) => r.kind === "label" || r.kind === "field")) return {};
949
+ return {
950
+ body: { padding: spec.padding ?? 8, gap: spec.gap ?? 4, verticalAlign: spec.verticalAlign ?? "center", rows: spec.rows.length }
951
+ };
952
+ }
953
+ var rowStyle = (s) => ({
954
+ ...s?.fontSize !== void 0 ? { fontSize: s.fontSize } : {},
955
+ ...s?.fontFamily !== void 0 ? { fontFamily: s.fontFamily } : {},
956
+ ...(s?.fontWeight ?? 400) >= 700 ? { bold: true } : {}
957
+ });
958
+ var same = (a, b) => a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
959
+ function relayoutBodies(scene, ids, measure) {
960
+ for (const id of new Set(ids)) {
961
+ const node = scene.get(id);
962
+ const trait = node?.data?.diagram;
963
+ if (node === void 0 || trait?.body === void 0 || trait.outline === void 0) continue;
964
+ const layout = trait.body;
965
+ const rowNodes = /* @__PURE__ */ new Map();
966
+ for (const child of scene.childrenOf(id)) {
967
+ const index = scene.get(child)?.data?.bodyRow;
968
+ if (typeof index === "number") rowNodes.set(index, child);
969
+ }
970
+ const rows = Array.from({ length: layout.rows }, (_, i) => {
971
+ const data = scene.get(rowNodes.get(i))?.data;
972
+ return { kind: "label", text: typeof data?.text === "string" ? data.text : "", style: rowStyle(data?.style) };
973
+ });
974
+ const spec = { outline: trait.outline, rows, padding: layout.padding, gap: layout.gap, verticalAlign: layout.verticalAlign };
975
+ const pose = node.pose;
976
+ const grown = sizeToBody(pose, measureBody(spec, measure));
977
+ if (grown !== pose) scene.setPose(id, grown);
978
+ for (const box of layoutBody(spec, grown, measure)) {
979
+ const child = rowNodes.get(box.index);
980
+ const at = child === void 0 ? void 0 : scene.get(child)?.pose;
981
+ if (at === void 0) continue;
982
+ const next = { ...at, x: box.x, y: box.y, width: box.width, height: box.height };
983
+ if (!same(at, next)) scene.setPose(child, next);
984
+ }
985
+ }
986
+ }
987
+
988
+ // src/dataTypes.ts
989
+ var DEFAULT_POOL_ACROSS = 600;
990
+ var DEFAULT_LANE_SIZE = 120;
991
+ var NODE = { fill: "#16222c", stroke: "#7ba7c7", text: "#dbe7f2" };
992
+ var NODE_STROKE_WIDTH = 2;
993
+ var LANE_STROKE_WIDTH = 1;
994
+ var EDGE_STROKE_WIDTH = 1.5;
995
+
996
+ // src/containers/regionSpecs.ts
997
+ var NEW_POOL_LANES = 2;
998
+ function regionData(role, axis, style, fitted) {
999
+ const container = { role, header: containerShape(role, axis).header, ...fitted ? { fitted: true } : {} };
1000
+ return {
1001
+ diagram: { outline: "rect", container },
1002
+ ...role === "lane" ? {} : { fill: { paint: { color: style.fill } } },
1003
+ stroke: { paint: { color: style.stroke }, width: role === "lane" ? LANE_STROKE_WIDTH : style.strokeWidth ?? NODE_STROKE_WIDTH }
1004
+ };
1005
+ }
1006
+ var titleData = (text, style, fontFamily = DEFAULT_FONT.fontFamily) => ({
1007
+ text,
1008
+ style: { fontFamily, fontSize: 13, fontWeight: 700, align: "center" },
1009
+ verticalAlign: "center",
1010
+ fill: { paint: { color: style.text } }
1011
+ });
1012
+ function regionSpecs(role, axis, pose, title, opts) {
1013
+ const style = opts.style ?? NODE;
1014
+ const { layout, header } = containerShape(role, axis);
1015
+ const region = {
1016
+ id: opts.id,
1017
+ kind: "container",
1018
+ layer: opts.layer,
1019
+ pose,
1020
+ parent: opts.parent ?? null,
1021
+ data: regionData(role, axis, style, opts.fitted === true),
1022
+ layout: CONTAINER_LAYOUTS[layout]
1023
+ };
1024
+ if (title === null) return [region];
1025
+ return [region, {
1026
+ id: titleIdOf(opts.id),
1027
+ kind: "leaf",
1028
+ layer: opts.layer,
1029
+ parent: opts.id,
1030
+ pickable: false,
1031
+ pose: titlePose(pose, header),
1032
+ data: titleData(title, style, opts.fontFamily)
1033
+ }];
1034
+ }
1035
+ function firstLanes(poolId, axis, at, opts) {
1036
+ const { header } = containerShape("pool", axis);
1037
+ const size = Math.max(MIN_LANE_SIZE, (axis === "y" ? at.height : at.width) / NEW_POOL_LANES);
1038
+ const out = [];
1039
+ for (let i = 0; i < NEW_POOL_LANES; i++) {
1040
+ const pose = axis === "y" ? { x: at.x + header.size, y: at.y + i * size, width: at.width - header.size, height: size } : { x: at.x + i * size, y: at.y + header.size, width: size, height: at.height - header.size };
1041
+ const bodies = opts.laneStyles?.bodies ?? [];
1042
+ const style = bodies.length > 0 ? bodies[i % bodies.length] : opts.style;
1043
+ out.push(...regionSpecs("lane", axis, pose, `Lane ${i + 1}`, {
1044
+ id: opts.newId(),
1045
+ layer: opts.layer,
1046
+ parent: poolId,
1047
+ ...style ? { style } : {}
1048
+ }));
1049
+ }
1050
+ return out;
1051
+ }
1052
+
1053
+ // src/dataColors.ts
1054
+ var NODE_FIELDS = ["fill", "stroke", "text"];
1055
+ function ownNodeStyle(colors) {
1056
+ const own = NODE_FIELDS.filter((f) => colors?.[f] !== void 0);
1057
+ return own.length > 0 ? { ownStyle: own } : {};
1058
+ }
1059
+ function ownEdgeStyle(colors) {
1060
+ const own = [];
1061
+ if (colors?.stroke !== void 0) own.push("stroke");
1062
+ if (colors?.label !== void 0) own.push("text");
1063
+ return own.length > 0 ? { ownStyle: own } : {};
1064
+ }
1065
+ function withColors(style, colors) {
1066
+ if (colors === void 0) return style;
1067
+ const out = { ...style };
1068
+ for (const [k, v] of Object.entries(colors)) if (v !== void 0) out[k] = v;
1069
+ return out;
1070
+ }
1071
+
1072
+ // src/dataStyles.ts
1073
+ var EDGE = { stroke: "#7ba7c7" };
1074
+ function dataNodeStyle(node, theme) {
1075
+ return withColors(theme ? roleStyle(theme, node.style) : NODE, node.colors);
1076
+ }
1077
+ function dataEdgeStyle(edge, theme) {
1078
+ return withColors(theme ? roleEdgeStyle(theme, edge.style) : EDGE, edge.colors);
1079
+ }
1080
+ function dataRegionStyle(region, theme, laneIndex = 0) {
1081
+ return withColors(themeRegionStyle(region, theme, laneIndex), region.colors);
1082
+ }
1083
+ function themeRegionStyle(r, theme, laneIndex) {
1084
+ if (theme === void 0) return NODE;
1085
+ const own = r.style !== void 0 ? theme.roles[r.style] : void 0;
1086
+ if (own !== void 0) return own;
1087
+ if (r.role === "pool") return theme.lane.header;
1088
+ if (r.role === "lane") return theme.lane.bodies[laneIndex % Math.max(1, theme.lane.bodies.length)] ?? theme.container;
1089
+ return theme.container;
1090
+ }
1091
+
1092
+ // src/edgeId.ts
1093
+ var idPart = (s) => s.replace(/[\\.>:/]/g, "\\$&");
1094
+ var edgeIdOf = (e) => {
1095
+ if (e.id !== void 0) return e.id;
1096
+ const part = (mark, p) => p === void 0 ? "" : `${mark}${idPart(p)}`;
1097
+ return `edge:${idPart(e.from)}${part(".", e.fromPort)}->${idPart(e.to)}${part(".", e.toPort)}${part(":", e.label)}`;
1098
+ };
1099
+ function refAt(n) {
1100
+ let out = "";
1101
+ for (let k = n + 1; k > 0; k = Math.floor((k - 1) / 26)) out = String.fromCharCode(65 + (k - 1) % 26) + out;
1102
+ return out;
1103
+ }
1104
+ function nextOffPageRef(used) {
1105
+ let n = 0;
1106
+ while (used.has(refAt(n))) n++;
1107
+ return refAt(n);
1108
+ }
1109
+ function offPageSplits(input) {
1110
+ const used = new Set(input.used ?? []);
1111
+ const out = /* @__PURE__ */ new Map();
1112
+ const fresh = [];
1113
+ for (const edge of input.edges) {
1114
+ const from = pageOfBounds(input.pages, edge.from);
1115
+ const to = pageOfBounds(input.pages, edge.to);
1116
+ if (from === null || to === null || from.id === to.id) {
1117
+ out.set(edge.id, null);
1118
+ continue;
1119
+ }
1120
+ const held = edge.offPage?.ref;
1121
+ if (held !== void 0 && !used.has(held)) {
1122
+ used.add(held);
1123
+ out.set(edge.id, { ref: held, from: from.id, to: to.id });
1124
+ } else {
1125
+ out.set(edge.id, null);
1126
+ fresh.push({ id: edge.id, from: from.id, to: to.id });
1127
+ }
1128
+ }
1129
+ for (const split of fresh) {
1130
+ const ref = nextOffPageRef(used);
1131
+ used.add(ref);
1132
+ out.set(split.id, { ref, from: split.from, to: split.to });
1133
+ }
1134
+ return out;
1135
+ }
1136
+ function diagramPages(data) {
1137
+ return layoutPages(data.pages ?? [], data.pageLayout);
1138
+ }
1139
+ function dataOffPage(edges, pages, boxOf, look) {
1140
+ const input = edges.flatMap((e) => {
1141
+ const from = boxOf(e.from);
1142
+ const to = boxOf(e.to);
1143
+ return from === void 0 || to === void 0 ? [] : [{ id: e.id, from, to }];
1144
+ });
1145
+ const splits = /* @__PURE__ */ new Map();
1146
+ const tags = [];
1147
+ for (const [id, split] of offPageSplits({ pages, edges: input })) {
1148
+ if (split === null) continue;
1149
+ splits.set(id, split);
1150
+ tags.push(...offPageTagSpecs(id, split, pages, { ...look, layer: "main" }));
1151
+ }
1152
+ return { splits, tags };
1153
+ }
1154
+ function scenePageSplits(data, placedAt, specs, edges, look) {
1155
+ const pages = diagramPages(data);
1156
+ if (pages.length === 0) return null;
1157
+ const groups = new Map(specs.filter((s) => diagramGroupOf(s) !== null).map((s) => [String(s.id), s.pose]));
1158
+ return dataOffPage(edges, pages, (id) => placedAt.get(id) ?? groups.get(id), look());
1159
+ }
1160
+
1161
+ // src/groupTree.ts
1162
+ function nestGroups(declared, isNode) {
1163
+ const groupIds = new Set(declared.map((g) => g.id));
1164
+ const parentOf = /* @__PURE__ */ new Map();
1165
+ const claimedNodes = /* @__PURE__ */ new Set();
1166
+ const direct = /* @__PURE__ */ new Map();
1167
+ const isAncestorOrSelf = (candidate, of) => {
1168
+ for (let at = of; at !== void 0; at = parentOf.get(at)) {
1169
+ if (at === candidate) return true;
1170
+ }
1171
+ return false;
1172
+ };
1173
+ for (const group of declared) {
1174
+ const own = [];
1175
+ for (const id of group.members) {
1176
+ if (isNode(id)) {
1177
+ if (claimedNodes.has(id)) continue;
1178
+ claimedNodes.add(id);
1179
+ own.push(id);
1180
+ } else if (groupIds.has(id) && !parentOf.has(id) && !isAncestorOrSelf(id, group.id)) {
1181
+ parentOf.set(id, group.id);
1182
+ own.push(id);
1183
+ }
1184
+ }
1185
+ direct.set(group.id, own);
1186
+ }
1187
+ const flat = /* @__PURE__ */ new Map();
1188
+ const flatten = (id) => {
1189
+ const known = flat.get(id);
1190
+ if (known !== void 0) return known;
1191
+ const out = [];
1192
+ for (const member of direct.get(id)) {
1193
+ if (groupIds.has(member)) out.push(...flatten(member));
1194
+ else out.push(member);
1195
+ }
1196
+ flat.set(id, out);
1197
+ return out;
1198
+ };
1199
+ const live = new Set(declared.filter((g) => flatten(g.id).length > 0).map((g) => g.id));
1200
+ return declared.filter((g) => live.has(g.id)).map((g) => {
1201
+ const parent = parentOf.get(g.id);
1202
+ return {
1203
+ id: g.id,
1204
+ direct: direct.get(g.id).filter((id) => !groupIds.has(id) || live.has(id)),
1205
+ members: flat.get(g.id),
1206
+ ...parent !== void 0 ? { parent } : {}
1207
+ };
1208
+ });
1209
+ }
1210
+
1211
+ // src/graph.ts
1212
+ function buildGraph(source, opts = {}) {
1213
+ const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR$1;
1214
+ const nodes = [];
1215
+ const byId = /* @__PURE__ */ new Map();
1216
+ const candidates = [];
1217
+ const groupNodes = [];
1218
+ const routed = /* @__PURE__ */ new Set();
1219
+ const rooms = /* @__PURE__ */ new Map();
1220
+ for (const { node, pose } of source()) {
1221
+ const edge = diagramEdgeOf(node);
1222
+ if (edge !== null) {
1223
+ const deps = node.dependsOn;
1224
+ if (Array.isArray(deps) && deps.length === 2) {
1225
+ candidates.push({
1226
+ id: node.id,
1227
+ from: deps[0],
1228
+ to: deps[1],
1229
+ ...edge.from.port !== void 0 ? { fromPort: edge.from.port } : {},
1230
+ ...edge.to.port !== void 0 ? { toPort: edge.to.port } : {},
1231
+ ...edge.waypoints !== void 0 && edge.waypoints.length > 0 ? { waypoints: edge.waypoints } : {},
1232
+ ...layoutOwnsRoute(edge) ? {} : { authored: true }
1233
+ });
1234
+ if (layoutOwnsRoute(edge)) routed.add(node.id);
1235
+ }
1236
+ continue;
1237
+ }
1238
+ const label = diagramLabelOf(node);
1239
+ if (label !== null) {
1240
+ const deps = node.dependsOn;
1241
+ if (Array.isArray(deps) && deps.length === 1 && labelFollowsLayout(label.at) && !rooms.has(deps[0])) {
1242
+ const { width, height } = geometry.getBounds(pose);
1243
+ rooms.set(deps[0], { width, height, ...label.offset ? { offset: label.offset } : {} });
1244
+ }
1245
+ continue;
1246
+ }
1247
+ const trait = diagramNodeOf(node, opts.read);
1248
+ if (trait === null) continue;
1249
+ if (trait.group !== void 0) {
1250
+ const deps = node.dependsOn;
1251
+ groupNodes.push({
1252
+ id: node.id,
1253
+ members: Array.isArray(deps) ? deps : [],
1254
+ inset: insetOf(trait.group.inset),
1255
+ ...trait.group.minWidth !== void 0 ? { minWidth: trait.group.minWidth } : {},
1256
+ ...trait.group.reorder !== void 0 ? { reorders: true } : {}
1257
+ });
1258
+ continue;
1259
+ }
1260
+ const entry = {
1261
+ id: node.id,
1262
+ bounds: geometry.getBounds(pose),
1263
+ pinned: trait.pinned === true,
1264
+ ...trait.ports !== void 0 ? { ports: trait.ports } : {},
1265
+ ...trait.outline !== void 0 ? { outline: trait.outline } : {},
1266
+ ...trait.margin !== void 0 ? { margin: trait.margin } : {}
1267
+ };
1268
+ nodes.push(entry);
1269
+ byId.set(entry.id, entry);
1270
+ }
1271
+ const declared = new Map(groupNodes.map((g) => [g.id, g]));
1272
+ const live = nestGroups(groupNodes, (id) => byId.has(id)).map((g) => {
1273
+ for (const id of g.direct) {
1274
+ const node = byId.get(id);
1275
+ if (node !== void 0) node.group = g.id;
1276
+ }
1277
+ return {
1278
+ id: g.id,
1279
+ members: g.members,
1280
+ inset: declared.get(g.id).inset,
1281
+ ...declared.get(g.id).minWidth !== void 0 ? { minWidth: declared.get(g.id).minWidth } : {},
1282
+ ...g.parent !== void 0 ? { parent: g.parent } : {},
1283
+ ...declared.get(g.id).reorders === true ? { ordered: g.direct } : {}
1284
+ };
1285
+ });
1286
+ const known = (id) => byId.has(id) || live.some((g) => g.id === id);
1287
+ const roomed = candidates.map((e) => {
1288
+ const room = rooms.get(e.id);
1289
+ return room !== void 0 && routed.has(e.id) ? { ...e, label: room } : e;
1290
+ });
1291
+ return graphOf(nodes, expandGroupEdges(roomed.filter((e) => known(e.from) && known(e.to)), live), live);
1292
+ }
1293
+ var NONE = Object.freeze([]);
1294
+ function graphOf(nodes, edges, groups = []) {
1295
+ const byId = new Map(nodes.map((n) => [n.id, n]));
1296
+ const out = /* @__PURE__ */ new Map();
1297
+ const into = /* @__PURE__ */ new Map();
1298
+ for (const edge of edges) {
1299
+ push(out, edge.from, edge);
1300
+ push(into, edge.to, edge);
1301
+ }
1302
+ return {
1303
+ nodes,
1304
+ edges,
1305
+ groups,
1306
+ node: (id) => byId.get(id),
1307
+ outgoing: (id) => out.get(id) ?? NONE,
1308
+ incoming: (id) => into.get(id) ?? NONE
1309
+ };
1310
+ }
1311
+ function labelFollowsLayout(at) {
1312
+ return at === "mid" || typeof at === "object";
1313
+ }
1314
+ function push(index, key, edge) {
1315
+ const list = index.get(key);
1316
+ if (list === void 0) index.set(key, [edge]);
1317
+ else list.push(edge);
1318
+ }
1319
+
1320
+ // src/rowStyle.ts
1321
+ var textFace = (s, font) => ({
1322
+ fontFamily: s?.fontFamily ?? font.fontFamily,
1323
+ fontSize: s?.fontSize ?? font.fontSize,
1324
+ ...s?.bold === true ? { fontWeight: 700 } : {}
1325
+ });
1326
+ function rowTextStyle(row, font) {
1327
+ const face = textFace(row?.kind === "label" || row?.kind === "field" ? row.style : void 0, font);
1328
+ return row?.kind === "label" ? { ...face, align: "center" } : face;
1329
+ }
1330
+ function diagramMarkOf(node) {
1331
+ const data = node.data;
1332
+ if (data === null || typeof data !== "object") return null;
1333
+ const trait = data[DIAGRAM_TRAIT_KEY];
1334
+ if (trait === null || typeof trait !== "object" || !("mark" in trait)) return null;
1335
+ const mark = trait.mark;
1336
+ return mark !== null && typeof mark === "object" && typeof mark.d === "string" ? mark : null;
1337
+ }
1338
+ var PARSED = /* @__PURE__ */ new Map();
1339
+ var PARSED_MAX = 256;
1340
+ function unitPath(d, onWarn) {
1341
+ let path = PARSED.get(d);
1342
+ if (path === void 0) {
1343
+ let warned = false;
1344
+ path = pathFromD(d, (message) => {
1345
+ warned = true;
1346
+ onWarn?.(message);
1347
+ });
1348
+ if (path.kind === "polygon" && path.commands.length === 0) onWarn?.("it draws nothing");
1349
+ else if (!warned) {
1350
+ if (PARSED.size >= PARSED_MAX) PARSED.clear();
1351
+ PARSED.set(d, path);
1352
+ }
1353
+ }
1354
+ return path;
1355
+ }
1356
+ function checkMarkPath(nodeId, index, d) {
1357
+ unitPath(d, (message) => {
1358
+ throw new Error(`diagramScene: node "${nodeId}", mark ${index}: path data ${JSON.stringify(d)} does not parse: ${message}`);
1359
+ });
1360
+ }
1361
+ function markShape(opts = {}) {
1362
+ const geometry = opts.geometry ?? RECT_POSE_DESCRIPTOR;
1363
+ return {
1364
+ id: "diagram:mark",
1365
+ matches: (node) => diagramMarkOf(node) !== null,
1366
+ paint: (node, pose) => {
1367
+ const mark = diagramMarkOf(node);
1368
+ if (mark === null) return [];
1369
+ const box = geometry.getBounds(pose);
1370
+ const d = node.data;
1371
+ const fill = d.fill && commandFill(d.fill, box);
1372
+ const stroke = d.stroke && commandStroke(d.stroke, box);
1373
+ return [{
1374
+ kind: "path",
1375
+ path: transformPath(unitPath(mark.d), [box.width, 0, 0, box.height, box.x, box.y]),
1376
+ ...fill ? { fill } : {},
1377
+ ...stroke ? { stroke } : {}
1378
+ }];
1379
+ }
1380
+ };
1381
+ }
1382
+ function registerMarkShape(opts = {}) {
1383
+ return registerNodeShape(markShape(opts), { priority: "high" });
1384
+ }
1385
+ function markSpecs(nodeId, marks, box, ctx) {
1386
+ return marks.map((mark, i) => {
1387
+ const id = `${nodeId}/mark:${i}`;
1388
+ if ("d" in mark) {
1389
+ checkMarkPath(nodeId, i, mark.d);
1390
+ const paint = (color) => ({ color, ...mark.opacity !== void 0 ? { opacity: mark.opacity } : {} });
1391
+ const stroke = mark.stroke ?? (mark.fill === void 0 ? ctx.style.stroke : void 0);
1392
+ return {
1393
+ id,
1394
+ kind: "leaf",
1395
+ layer: "main",
1396
+ parent: nodeId,
1397
+ pickable: false,
1398
+ pose: { x: box.x, y: box.y, width: box.width, height: box.height },
1399
+ data: {
1400
+ diagram: { mark: { d: mark.d } },
1401
+ ...mark.fill !== void 0 ? { fill: { paint: paint(mark.fill) } } : {},
1402
+ ...stroke !== void 0 ? { stroke: { paint: paint(stroke), width: mark.strokeWidth ?? 1.5, ...mark.dash ? { dash: mark.dash } : {} } } : {}
1403
+ }
1404
+ };
1405
+ }
1406
+ const size = ctx.measure(mark.text, mark.style ?? {});
1407
+ const self = mark.self ?? mark.at;
1408
+ return {
1409
+ id,
1410
+ kind: "leaf",
1411
+ layer: "main",
1412
+ pickable: false,
1413
+ pose: {
1414
+ x: box.x + mark.at.u * box.width + (mark.offset?.x ?? 0) - self.u * size.width,
1415
+ y: box.y + mark.at.v * box.height + (mark.offset?.y ?? 0) - self.v * size.height,
1416
+ width: size.width,
1417
+ height: size.height
1418
+ },
1419
+ data: {
1420
+ diagram: { anchor: { at: mark.at, self, ...mark.offset ? { offset: mark.offset } : {} } },
1421
+ text: mark.text,
1422
+ style: ctx.textStyle(mark.style),
1423
+ fill: { paint: { color: mark.color ?? ctx.style.text } }
1424
+ },
1425
+ dependsOn: [nodeId],
1426
+ derivePose: ANCHOR_DERIVE_POSE
1427
+ };
1428
+ });
1429
+ }
1430
+
1431
+ // src/dataContainers.ts
1432
+ var EMPTY_CONTAINER = { width: 240, height: 160 };
1433
+ function resolveContainers(containers, ids, nodeIds) {
1434
+ const byId = /* @__PURE__ */ new Map();
1435
+ const parentOf = /* @__PURE__ */ new Map();
1436
+ const declare = (r) => {
1437
+ if (ids.has(r.id)) throw new Error(`diagramScene: container id "${r.id}" is already a node, group, or container id`);
1438
+ ids.add(r.id);
1439
+ byId.set(r.id, r);
1440
+ };
1441
+ const base = { direct: [], pinned: false, fitted: false, index: 0 };
1442
+ for (const c of containers) {
1443
+ const axis = c.role === "pool" ? c.axis ?? "y" : "y";
1444
+ declare({
1445
+ ...base,
1446
+ direct: [],
1447
+ id: c.id,
1448
+ role: c.role,
1449
+ axis,
1450
+ pinned: c.pinned === true,
1451
+ ...c.label !== void 0 ? { label: c.label } : {},
1452
+ ...c.style !== void 0 ? { style: c.style } : {},
1453
+ ...c.colors !== void 0 ? { colors: c.colors } : {},
1454
+ ...c.box !== void 0 ? { box: { ...c.box } } : {}
1455
+ });
1456
+ if (c.role !== "pool") continue;
1457
+ (c.lanes ?? []).forEach((l, index) => {
1458
+ declare({
1459
+ ...base,
1460
+ direct: [],
1461
+ id: l.id,
1462
+ role: "lane",
1463
+ axis,
1464
+ index,
1465
+ parent: c.id,
1466
+ ...l.label !== void 0 ? { label: l.label } : {},
1467
+ ...l.size !== void 0 ? { size: l.size } : {},
1468
+ ...l.colors !== void 0 ? { colors: l.colors } : {}
1469
+ });
1470
+ parentOf.set(l.id, c.id);
1471
+ byId.get(c.id).direct.push(l.id);
1472
+ });
1473
+ }
1474
+ const claim = (holder, id) => {
1475
+ if (parentOf.has(id)) return;
1476
+ const region = byId.get(id);
1477
+ if (region === void 0 ? !nodeIds.has(id) : region.role !== "container") return;
1478
+ for (let at = holder.id; at !== void 0; at = parentOf.get(at)) if (at === id) return;
1479
+ parentOf.set(id, holder.id);
1480
+ holder.direct.push(id);
1481
+ };
1482
+ for (const c of containers) {
1483
+ if (c.role === "container") for (const m of c.members ?? []) claim(byId.get(c.id), m);
1484
+ else for (const l of c.lanes ?? []) for (const m of l.members ?? []) claim(byId.get(l.id), m);
1485
+ }
1486
+ const regions = [];
1487
+ const visit = (r) => {
1488
+ const parent = parentOf.get(r.id);
1489
+ if (parent !== void 0) r.parent = parent;
1490
+ r.fitted = r.role === "container" && r.box === void 0 && r.direct.length > 0;
1491
+ regions.push(r);
1492
+ for (const id of r.direct) {
1493
+ const inner = byId.get(id);
1494
+ if (inner !== void 0) visit(inner);
1495
+ }
1496
+ };
1497
+ for (const r of byId.values()) if (!parentOf.has(r.id)) visit(r);
1498
+ return { regions, parentOf };
1499
+ }
1500
+ var fittedInset = (pad) => ({ top: CONTAINER_HEADER.size + pad, right: pad, bottom: pad, left: pad });
1501
+ function laneSizes(lanes, along) {
1502
+ if (along === void 0) return lanes.map((l) => Math.max(MIN_LANE_SIZE, l.size ?? DEFAULT_LANE_SIZE));
1503
+ const given = lanes.reduce((s, l) => s + (l.size ?? 0), 0);
1504
+ const unsized = lanes.filter((l) => l.size === void 0).length;
1505
+ const share = unsized === 0 ? 0 : (along - given) / unsized;
1506
+ const sizes = lanes.map((l) => Math.max(MIN_LANE_SIZE, l.size ?? share));
1507
+ if (sizes.length > 0) sizes[sizes.length - 1] = Math.max(MIN_LANE_SIZE, sizes[sizes.length - 1] + along - sizes.reduce((s, n) => s + n, 0));
1508
+ return sizes;
1509
+ }
1510
+ function startBox(r, lanes, seed) {
1511
+ if (r.box !== void 0) return r.box;
1512
+ if (r.role !== "pool") return { ...seed, ...EMPTY_CONTAINER };
1513
+ const along = Math.max(MIN_LANE_SIZE, laneSizes(lanes, void 0).reduce((s, n) => s + n, 0));
1514
+ return r.axis === "y" ? { ...seed, width: DEFAULT_POOL_ACROSS, height: along } : { ...seed, width: along, height: DEFAULT_POOL_ACROSS };
1515
+ }
1516
+ function containerLayoutNodes({ regions, parentOf }, opts) {
1517
+ const byId = new Map(regions.map((r) => [r.id, r]));
1518
+ const rootOf = /* @__PURE__ */ new Map();
1519
+ for (const r of regions) {
1520
+ const outer = r.parent === void 0 ? void 0 : rootOf.get(r.parent);
1521
+ const root = outer ?? (r.fitted ? void 0 : r.id);
1522
+ if (root !== void 0) rootOf.set(r.id, root);
1523
+ }
1524
+ const endOf2 = (id) => {
1525
+ if (byId.has(id)) return rootOf.get(id) ?? id;
1526
+ const parent = parentOf.get(id);
1527
+ return (parent === void 0 ? void 0 : rootOf.get(parent)) ?? id;
1528
+ };
1529
+ const hidden = new Set([...parentOf.keys()].filter((id) => endOf2(id) !== id));
1530
+ const nodes = [];
1531
+ regions.forEach((r, k) => {
1532
+ if (rootOf.get(r.id) !== r.id) return;
1533
+ const lanes = r.direct.flatMap((id) => {
1534
+ const l = byId.get(id);
1535
+ return l?.role === "lane" ? [l] : [];
1536
+ });
1537
+ nodes.push({ id: r.id, bounds: startBox(r, lanes, opts.seed(k)), pinned: r.pinned });
1538
+ });
1539
+ const seen = (id) => !hidden.has(id) && !(byId.get(id)?.fitted ?? false);
1540
+ const below = (r) => r.direct.flatMap((id) => seen(id) ? [id] : byId.get(id)?.fitted === true && !hidden.has(id) ? below(byId.get(id)) : []);
1541
+ const groups = [];
1542
+ const groupOf = /* @__PURE__ */ new Map();
1543
+ for (const r of regions) {
1544
+ if (!r.fitted || hidden.has(r.id) || rootOf.has(r.id)) continue;
1545
+ groups.push({ id: r.id, members: below(r), inset: fittedInset(opts.padding), ...r.parent !== void 0 ? { parent: r.parent } : {} });
1546
+ for (const id of r.direct) if (seen(id)) groupOf.set(id, r.id);
1547
+ }
1548
+ return { nodes, groups, groupOf, hidden, endOf: endOf2 };
1549
+ }
1550
+
1551
+ // src/dataContainerPlace.ts
1552
+ function seedRows(out, ids, body, sizes, pad) {
1553
+ let x = body.x + pad;
1554
+ let y = body.y + pad;
1555
+ let row = 0;
1556
+ for (const id of ids) {
1557
+ const { width, height } = sizes.get(id);
1558
+ if (x > body.x + pad && x + width > body.x + body.width - pad) {
1559
+ x = body.x + pad;
1560
+ y += row + pad;
1561
+ row = 0;
1562
+ }
1563
+ out.set(id, { x, y, width, height });
1564
+ x += width + pad;
1565
+ row = Math.max(row, height);
1566
+ }
1567
+ }
1568
+ function placeContainers({ regions, parentOf }, layout, placed, sizes, pad) {
1569
+ const byId = new Map(regions.map((r) => [r.id, r]));
1570
+ const start = new Map(layout.nodes.map((n) => [n.id, n.bounds]));
1571
+ const delta = (id) => {
1572
+ const root = layout.endOf(id);
1573
+ const from = start.get(root);
1574
+ const to = placed.get(root);
1575
+ return from === void 0 || to === void 0 ? { x: 0, y: 0 } : { x: to.x - from.x, y: to.y - from.y };
1576
+ };
1577
+ const out = /* @__PURE__ */ new Map();
1578
+ const seated = new Map(sizes);
1579
+ const empty = [];
1580
+ for (const r of regions) {
1581
+ if (r.fitted || r.role === "lane") continue;
1582
+ const own = placed.get(r.id);
1583
+ const d = delta(r.id);
1584
+ const box = own ?? (r.box !== void 0 ? { ...r.box, x: r.box.x + d.x, y: r.box.y + d.y } : null);
1585
+ if (box === null) {
1586
+ empty.push(r.id);
1587
+ seated.set(r.id, EMPTY_CONTAINER);
1588
+ continue;
1589
+ }
1590
+ out.set(r.id, box);
1591
+ if (r.role !== "pool") continue;
1592
+ const lanes = r.direct.map((id) => byId.get(id));
1593
+ const along = r.axis === "y" ? box.height : box.width;
1594
+ const sizes2 = laneSizes(lanes, r.box === void 0 ? void 0 : along);
1595
+ const stacked = stackedLanes(r.axis, box, containerShape("pool", r.axis).header, lanes.map((l, i) => ({ id: l.id, size: sizes2[i] })));
1596
+ for (const [id, pose] of stacked) out.set(id, pose);
1597
+ }
1598
+ const seatOf = (id) => {
1599
+ let at = byId.get(parentOf.get(id) ?? "");
1600
+ while (at !== void 0 && at.fitted) at = byId.get(at.parent ?? "");
1601
+ return at;
1602
+ };
1603
+ const unseated = /* @__PURE__ */ new Map();
1604
+ for (const [id, size] of seated) {
1605
+ if (!layout.hidden.has(id) || byId.has(id) && !empty.includes(id)) continue;
1606
+ if (size.at !== void 0) {
1607
+ const d = delta(id);
1608
+ out.set(id, { x: size.at.x + d.x, y: size.at.y + d.y, width: size.width, height: size.height });
1609
+ continue;
1610
+ }
1611
+ const seat = seatOf(id);
1612
+ unseated.set(seat, [...unseated.get(seat) ?? [], id]);
1613
+ }
1614
+ for (const [seat, ids] of unseated) {
1615
+ seedRows(out, ids, bodyBox(out.get(seat.id), containerShape(seat.role, seat.axis).header), seated, pad);
1616
+ }
1617
+ const footprint = (id) => {
1618
+ if (byId.has(id)) return out.get(id);
1619
+ const at = out.get(id) ?? placed.get(id);
1620
+ return at === void 0 ? void 0 : grownBy(at, sizes.get(id)?.margin);
1621
+ };
1622
+ for (const r of [...regions].reverse()) {
1623
+ if (!r.fitted) continue;
1624
+ const rs = r.direct.map(footprint).filter((b) => b !== void 0);
1625
+ const inset = fittedInset(pad);
1626
+ const x = Math.min(...rs.map((b) => b.x)) - inset.left;
1627
+ const y = Math.min(...rs.map((b) => b.y)) - inset.top;
1628
+ out.set(r.id, {
1629
+ x,
1630
+ y,
1631
+ width: Math.max(...rs.map((b) => b.x + b.width)) + inset.right - x,
1632
+ height: Math.max(...rs.map((b) => b.y + b.height)) + inset.bottom - y
1633
+ });
1634
+ }
1635
+ return out;
1636
+ }
1637
+ function containerSpecs({ regions }, boxes, theme, fontFamily) {
1638
+ return regions.flatMap((r) => {
1639
+ const box = boxes.get(r.id);
1640
+ if (box === void 0) return [];
1641
+ const specs = regionSpecs(r.role, r.axis, { ...box }, r.label ?? null, {
1642
+ id: r.id,
1643
+ layer: "main",
1644
+ parent: r.parent ?? null,
1645
+ style: dataRegionStyle(r, theme, r.index),
1646
+ fitted: r.fitted,
1647
+ fontFamily
1648
+ });
1649
+ const marks = { ...r.pinned ? { pinned: true } : {}, ...ownNodeStyle(r.colors) };
1650
+ if (Object.keys(marks).length === 0) return specs;
1651
+ const [region, ...rest] = specs;
1652
+ return [{ ...region, data: { ...region.data, diagram: { ...region.data.diagram, ...marks } } }, ...rest];
1653
+ });
1654
+ }
1655
+
1656
+ // src/dataGroups.ts
1657
+ var groupMinWidth = (title, pad) => title + 2 * pad;
1658
+ function resolveGroups(groups, ids, nodeIds, opts) {
1659
+ for (const group of groups) {
1660
+ if (ids.has(group.id)) throw new Error(`diagramScene: group id "${group.id}" is already a node or group id`);
1661
+ ids.add(group.id);
1662
+ }
1663
+ const declared = new Map(groups.map((g) => [g.id, g]));
1664
+ const nested = nestGroups(groups, (id) => nodeIds.has(id));
1665
+ const depthOf = /* @__PURE__ */ new Map();
1666
+ const byId = new Map(nested.map((g) => [g.id, g]));
1667
+ const depth = (id) => {
1668
+ const parent = byId.get(id).parent;
1669
+ let d = depthOf.get(id);
1670
+ if (d === void 0) depthOf.set(id, d = parent === void 0 ? 0 : depth(parent) + 1);
1671
+ return d;
1672
+ };
1673
+ return nested.map((g, i) => ({ g, i })).sort((a, b) => depth(a.g.id) - depth(b.g.id) || a.i - b.i).map(({ g }) => {
1674
+ const group = declared.get(g.id);
1675
+ const pad = group.padding ?? opts.padding;
1676
+ const title = group.label ? opts.title(group.label) : null;
1677
+ const inset = { top: pad + (title ? title.height + pad / 2 : 0), right: pad, bottom: pad, left: pad };
1678
+ return {
1679
+ group,
1680
+ direct: g.direct,
1681
+ members: g.members,
1682
+ inset,
1683
+ pad,
1684
+ title,
1685
+ ...title ? { minWidth: groupMinWidth(title.width, pad) } : {},
1686
+ ...g.parent !== void 0 ? { parent: g.parent } : {}
1687
+ };
1688
+ });
1689
+ }
1690
+ var groupLabelIdOf = (id) => `${id}/label`;
1691
+ function labelsByGroup(groups, labels) {
1692
+ const byId = new Map(groups.map((g) => [g.group.id, g]));
1693
+ const holder = /* @__PURE__ */ new Map();
1694
+ for (const g of groups) for (const m of g.direct) holder.set(m, g.group.id);
1695
+ const ancestry = (id) => {
1696
+ const out2 = [];
1697
+ for (let at = byId.has(id) ? byId.get(id).parent : holder.get(id); at !== void 0; at = byId.get(at)?.parent) out2.push(at);
1698
+ return out2;
1699
+ };
1700
+ const out = /* @__PURE__ */ new Map();
1701
+ for (const label of labels) {
1702
+ const up = new Set(ancestry(label.to));
1703
+ const shared = ancestry(label.from).find((g) => up.has(g));
1704
+ if (shared !== void 0) out.set(shared, [...out.get(shared) ?? [], label]);
1705
+ }
1706
+ return out;
1707
+ }
1708
+ function groupBoxes(groups, placedAt, labels = /* @__PURE__ */ new Map()) {
1709
+ const byId = new Map(groups.map((g) => [g.group.id, g]));
1710
+ const boxes = /* @__PURE__ */ new Map();
1711
+ const boxOf = (id) => {
1712
+ const known = boxes.get(id);
1713
+ if (known !== void 0) return known;
1714
+ const g = byId.get(id);
1715
+ const rs = g.direct.map((m) => byId.has(m) ? boxOf(m) : placedAt.get(m));
1716
+ for (const label of labels.get(id) ?? []) if (label.at !== null) rs.push(label.at);
1717
+ const x = Math.min(...rs.map((r) => r.x));
1718
+ const y = Math.min(...rs.map((r) => r.y));
1719
+ const box = boxAround({
1720
+ x,
1721
+ y,
1722
+ width: Math.max(...rs.map((r) => r.x + r.width)) - x,
1723
+ height: Math.max(...rs.map((r) => r.y + r.height)) - y
1724
+ }, g);
1725
+ boxes.set(id, box);
1726
+ return box;
1727
+ };
1728
+ for (const g of groups) boxOf(g.group.id);
1729
+ return boxes;
1730
+ }
1731
+ function groupSpecs(groups, placedAt, styleOf, wireLabels2 = []) {
1732
+ const labels = labelsByGroup(groups, wireLabels2);
1733
+ const boxes = groupBoxes(groups, placedAt, labels);
1734
+ const specs = [];
1735
+ for (const g of groups) {
1736
+ const { style, text } = styleOf(g.group);
1737
+ const { x, y, width, height } = boxes.get(g.group.id);
1738
+ specs.push({
1739
+ id: g.group.id,
1740
+ kind: "leaf",
1741
+ layer: "main",
1742
+ pose: { x, y, width, height },
1743
+ data: {
1744
+ diagram: {
1745
+ outline: "rect",
1746
+ group: {
1747
+ inset: g.inset,
1748
+ ...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {},
1749
+ ...g.group.reorder !== void 0 ? { reorder: g.group.reorder } : {}
1750
+ }
1751
+ },
1752
+ fill: { paint: { color: style.fill } },
1753
+ stroke: {
1754
+ paint: { color: style.stroke },
1755
+ width: style.strokeWidth ?? 1,
1756
+ ...style.dash ? { dash: style.dash } : {}
1757
+ }
1758
+ },
1759
+ dependsOn: [...g.direct, ...(labels.get(g.group.id) ?? []).map((l) => l.id)],
1760
+ derivePose: GROUP_DERIVE_POSE
1761
+ });
1762
+ if (g.title === null || !g.group.label) continue;
1763
+ const anchor = { at: { u: 0, v: 0 }, offset: { x: g.pad, y: g.pad } };
1764
+ specs.push({
1765
+ id: groupLabelIdOf(g.group.id),
1766
+ kind: "leaf",
1767
+ layer: "main",
1768
+ pickable: false,
1769
+ pose: { x: x + g.pad, y: y + g.pad, width: g.title.width, height: g.title.height },
1770
+ data: { diagram: { anchor }, text: g.group.label, style: text, fill: { paint: { color: style.text } } },
1771
+ dependsOn: [g.group.id],
1772
+ derivePose: ANCHOR_DERIVE_POSE
1773
+ });
1774
+ }
1775
+ return specs;
1776
+ }
1777
+ function placementOf(e, route, ctx) {
1778
+ const placement = e.labelPlacement ?? ctx.opts.labelPlacement ?? ctx.labelPlacement;
1779
+ return route?.labelAt === void 0 ? placement : { ...placement, at: { near: route.labelAt } };
1780
+ }
1781
+ function wireLabels(edges, ctx) {
1782
+ const out = [];
1783
+ for (const e of edges) {
1784
+ if (!e.label) continue;
1785
+ const id = ctx.idOf(e);
1786
+ const route = ctx.routes?.get(id);
1787
+ const size = labelSize(e.label, labelStyleOf(ctx.labelFont ?? ctx.font), ctx.measure);
1788
+ const placement = placementOf(e, route, ctx);
1789
+ const center2 = route?.waypoints !== void 0 && typeof placement.at === "object" ? labelCenter(polylineFromPoints([...route.waypoints]), placement, size) : null;
1790
+ out.push({
1791
+ id: `${id}/label`,
1792
+ from: e.from,
1793
+ to: e.to,
1794
+ at: center2 === null ? null : { x: center2.x - size.width / 2, y: center2.y - size.height / 2, ...size }
1795
+ });
1796
+ }
1797
+ return out;
1798
+ }
1799
+ var endOf = (own, port) => !port ? {} : own === void 0 ? { port, laidOut: port } : { port };
1800
+ function edgeSpecs(edges, ctx) {
1801
+ const specs = [];
1802
+ for (const e of edges) {
1803
+ const id = ctx.idOf(e);
1804
+ const route = ctx.routes?.get(id);
1805
+ const router = e.router ?? ctx.opts.router ?? "bezier";
1806
+ const planned = route ?? {
1807
+ fromPort: e.fromPort ?? e.routedPorts?.from,
1808
+ toPort: e.toPort ?? e.routedPorts?.to,
1809
+ waypoints: e.waypoints ?? e.routed
1810
+ };
1811
+ const { fromPort, toPort } = planned;
1812
+ const waypoints = router === "avoid" ? e.waypoints : planned.waypoints;
1813
+ const laidOut = e.waypoints === void 0 && waypoints !== void 0;
1814
+ const own = ctx.opts.edgeStyle?.(e);
1815
+ const style = own !== void 0 ? withColors(own, e.colors) : dataEdgeStyle(e, ctx.theme);
1816
+ const markerStart = style.markerStart ?? null;
1817
+ const markerEnd = style.markerEnd === void 0 ? "arrow" : style.markerEnd;
1818
+ const split = ctx.splits?.get(id);
1819
+ specs.push({
1820
+ id,
1821
+ kind: "leaf",
1822
+ layer: "main",
1823
+ pickable: false,
1824
+ pose: { x: 0, y: 0, width: 0, height: 0 },
1825
+ data: {
1826
+ diagram: {
1827
+ from: endOf(e.fromPort, fromPort),
1828
+ to: endOf(e.toPort, toPort),
1829
+ router,
1830
+ ...waypoints ? { waypoints } : {},
1831
+ ...laidOut ? { laidOut: waypoints } : {},
1832
+ ...e.style !== void 0 ? { style: e.style } : {},
1833
+ ...ownEdgeStyle(e.colors),
1834
+ ...split !== void 0 ? { offPage: split } : {}
1835
+ },
1836
+ stroke: {
1837
+ paint: { color: style.stroke },
1838
+ width: style.width ?? EDGE_STROKE_WIDTH,
1839
+ ...style.dash ? { dash: style.dash } : {},
1840
+ ...markerStart !== null ? { markerStart } : {},
1841
+ ...markerEnd !== null ? { markerEnd } : {}
1842
+ }
1843
+ },
1844
+ dependsOn: [e.from, e.to],
1845
+ derivePath: EDGE_DERIVE_PATH
1846
+ });
1847
+ if (!e.label) continue;
1848
+ specs.push(edgeLabelSpec(id, e.label, {
1849
+ layer: "main",
1850
+ label: placementOf(e, route, ctx),
1851
+ style: labelStyleOf(ctx.labelFont ?? ctx.font),
1852
+ fill: style.label ?? style.stroke,
1853
+ measure: ctx.measure
1854
+ }));
1855
+ }
1856
+ return specs;
1857
+ }
1858
+
1859
+ // src/fromData.ts
1860
+ var SLOT_FRAME = "M0 0H1V1H0Z";
1861
+ var GROUP = { fill: "rgba(123, 167, 199, 0.08)", stroke: "#7ba7c7", text: "#9fbdd6", strokeWidth: 1, dash: [4, 3] };
1862
+ var LINE2 = 1.4;
1863
+ var MIN_WIDTH = 64;
1864
+ var LAYOUTS = { layered, tree, force, chain };
1865
+ function labelRoomOf(e, opts, font, measure) {
1866
+ if (!e.label || e.waypoints !== void 0) return null;
1867
+ if (e.labelPlacement !== void 0 && !labelFollowsLayout(e.labelPlacement.at)) return null;
1868
+ const style = labelStyleOf(font);
1869
+ const offset = (e.labelPlacement ?? opts.labelPlacement ?? LABEL_PLACEMENT).offset;
1870
+ return { ...labelSize(e.label, style, measure), ...offset !== void 0 ? { offset } : {} };
1871
+ }
1872
+ function graphEdgeOf(e, room) {
1873
+ return {
1874
+ id: edgeIdOf(e),
1875
+ from: e.from,
1876
+ to: e.to,
1877
+ ...e.fromPort ? { fromPort: e.fromPort } : {},
1878
+ ...e.toPort ? { toPort: e.toPort } : {},
1879
+ ...room !== null ? { label: room } : {}
1880
+ };
1881
+ }
1882
+ function nodeSpecs(n, body, box, look) {
1883
+ const { style } = look;
1884
+ const textData = (text, row) => ({ text, style: look.text(row), fill: { paint: { color: style.text } } });
1885
+ const built = buildBody(body, box, {
1886
+ id: n.id,
1887
+ layer: "main",
1888
+ measure: look.measure,
1889
+ body: (trait) => ({
1890
+ diagram: {
1891
+ ...trait,
1892
+ ...bodyLayoutOf(body, trait),
1893
+ ...n.pinned === true ? { pinned: true } : {},
1894
+ ...n.style !== void 0 ? { style: n.style } : {},
1895
+ ...ownNodeStyle(n.colors)
1896
+ },
1897
+ fill: { paint: { color: style.fill } },
1898
+ stroke: { paint: { color: style.stroke }, width: style.strokeWidth ?? NODE_STROKE_WIDTH, ...style.dash ? { dash: style.dash } : {} }
1899
+ }),
1900
+ row: (text, at) => at.row.kind === "slot" ? at.row.id === void 0 ? null : { diagram: { mark: { d: SLOT_FRAME } }, stroke: { paint: { color: style.stroke }, width: 1 } } : text === "" ? null : { ...textData(text, at.row), bodyRow: at.index },
1901
+ portLabel: (text) => textData(text, void 0)
1902
+ });
1903
+ return built.specs.map((s, k) => ({ ...s, ...s.id !== void 0 ? {} : { id: `${n.id}/${k}` } }));
1904
+ }
1905
+ function diagramScene(data, opts = {}) {
1906
+ const { layer, ...rest } = opts;
1907
+ const specs = mainSpecs(data, rest);
1908
+ return layer === void 0 ? specs : specs.map((s) => ({ ...s, layer }));
1909
+ }
1910
+ function mainSpecs(data, opts) {
1911
+ const { theme } = opts;
1912
+ const font = opts.font ?? (theme ? themeFont(theme) : DEFAULT_FONT);
1913
+ const labelFont = theme ? themeLabelFont(theme, font) : font;
1914
+ const styleOf = (s) => textFace(s, font);
1915
+ const measure = opts.measure ? (text, s) => opts.measure(text, { ...s, fontFamily: s.fontFamily ?? font.fontFamily, fontSize: s.fontSize ?? font.fontSize }) : (text, s) => {
1916
+ const size = s.fontSize ?? font.fontSize;
1917
+ return { width: text.length * GLYPH * size, height: Math.ceil(LINE2 * size) };
1918
+ };
1919
+ const ids = /* @__PURE__ */ new Set();
1920
+ for (const n of data.nodes) {
1921
+ if (ids.has(n.id)) throw new Error(`diagramScene: node id "${n.id}" appears more than once`);
1922
+ ids.add(n.id);
1923
+ }
1924
+ const nodeIds = new Set(ids);
1925
+ const pad = opts.groupPadding ?? DEFAULT_GROUP_INSET;
1926
+ const regions = resolveContainers(data.containers ?? [], ids, nodeIds);
1927
+ for (const g of data.groups ?? []) {
1928
+ for (const m of g.members) {
1929
+ const region = nodeIds.has(m) ? regions.parentOf.get(m) : void 0;
1930
+ if (region !== void 0) throw new Error(`diagramScene: node "${m}" is in group "${g.id}" and in region "${region}"; a node may be in one or the other`);
1931
+ }
1932
+ }
1933
+ const boxes = containerLayoutNodes(regions, { padding: pad, seed: (k) => ({ x: data.nodes.length + k, y: data.nodes.length + k }) });
1934
+ const titleStyle = { ...font, fontSize: font.fontSize - 1 };
1935
+ const groups = resolveGroups(data.groups ?? [], ids, new Set([...nodeIds].filter((id) => !boxes.hidden.has(id))), {
1936
+ padding: pad,
1937
+ title: (text) => measure(text, titleStyle)
1938
+ });
1939
+ const seen = /* @__PURE__ */ new Set();
1940
+ const edges = data.edges.filter((e) => {
1941
+ const id = edgeIdOf(e);
1942
+ if (seen.has(id) || !ids.has(e.from) || !ids.has(e.to)) return false;
1943
+ seen.add(id);
1944
+ return true;
1945
+ });
1946
+ const bodies = data.nodes.map((n) => ({
1947
+ outline: n.outline ?? opts.outline ?? "rect",
1948
+ rows: n.rows ?? (n.lines ?? []).map((text) => ({ kind: "label", text })),
1949
+ padding: n.padding ?? opts.padding ?? 8,
1950
+ gap: n.gap ?? opts.gap ?? 4,
1951
+ verticalAlign: n.verticalAlign ?? opts.verticalAlign ?? "center",
1952
+ ...n.ports ? { ports: n.ports } : {},
1953
+ ...n.portLabels ? { portLabels: n.portLabels } : {}
1954
+ }));
1955
+ const seeds = data.nodes.map((n, i) => {
1956
+ const pose = sizeToBody(
1957
+ { ...n.at ?? { x: i, y: i }, width: n.width ?? opts.minWidth ?? MIN_WIDTH, height: n.height ?? 0 },
1958
+ measureBody(bodies[i], measure)
1959
+ );
1960
+ const { ports, outline, margin } = bodyTrait(bodies[i], pose, measure);
1961
+ return {
1962
+ id: n.id,
1963
+ bounds: pose,
1964
+ pinned: n.pinned === true,
1965
+ ...ports !== void 0 ? { ports } : {},
1966
+ ...outline !== void 0 ? { outline } : {},
1967
+ ...margin !== void 0 ? { margin } : {}
1968
+ };
1969
+ });
1970
+ const laid = layOut(seeds, edges, groups, boxes, opts, (e) => labelRoomOf(e, opts, labelFont, measure));
1971
+ const specs = [];
1972
+ const sizes = new Map(data.nodes.map((n, i) => [n.id, {
1973
+ ...seeds[i].bounds,
1974
+ ...n.at ? { at: n.at } : {},
1975
+ ...seeds[i].margin ? { margin: seeds[i].margin } : {}
1976
+ }]));
1977
+ const placedAt = new Map([...laid.placed, ...placeContainers(regions, boxes, laid.placed, sizes, pad)]);
1978
+ const routes = laid.plan === null ? null : authoredOver(edges.filter((e) => !laid.remapped.has(e)).map((e) => ({ ...e, id: edgeIdOf(e) })), laid.plan);
1979
+ const edgeCtx = { idOf: edgeIdOf, font, labelFont, measure, labelPlacement: LABEL_PLACEMENT, routes, opts, ...theme ? { theme } : {} };
1980
+ const groupLook = (g) => ({ style: opts.groupStyle?.(g) ?? theme?.container ?? GROUP, text: styleOf(titleStyle) });
1981
+ const footprints = new Map([...placedAt, ...seeds.map((s) => [s.id, grownBy(placedAt.get(s.id), s.margin)])]);
1982
+ specs.push(...containerSpecs(regions, placedAt, theme, font.fontFamily));
1983
+ specs.push(...groupSpecs(groups, footprints, groupLook, wireLabels(edges, edgeCtx)));
1984
+ for (const [i, n] of data.nodes.entries()) {
1985
+ const seed = seeds[i].bounds;
1986
+ const at = placedAt.get(n.id);
1987
+ const own = opts.nodeStyle?.(n);
1988
+ const style = own !== void 0 ? withColors(own, n.colors) : dataNodeStyle(n, theme);
1989
+ const parent = regions.parentOf.get(n.id);
1990
+ const built = nodeSpecs(n, bodies[i], { x: at.x, y: at.y, width: seed.width, height: seed.height }, { style, text: (row) => rowTextStyle(row, font), measure });
1991
+ specs.push(...parent === void 0 ? built : built.map((s) => s.id === n.id ? { ...s, parent } : s));
1992
+ if (n.marks) specs.push(...markSpecs(n.id, n.marks, at, { style, textStyle: styleOf, measure }));
1993
+ }
1994
+ const pages = scenePageSplits(
1995
+ data,
1996
+ placedAt,
1997
+ specs,
1998
+ edges.map((e) => ({ id: edgeIdOf(e), from: e.from, to: e.to })),
1999
+ () => ({ ...offPageTagLook(theme ?? diagramThemeFrom(void 0)), measure })
2000
+ );
2001
+ specs.push(...edgeSpecs(edges, pages === null ? edgeCtx : { ...edgeCtx, splits: pages.splits }), ...pages?.tags ?? []);
2002
+ return specs;
2003
+ }
2004
+ function layOut(seeds, edges, groups, boxes, opts, room) {
2005
+ const graphGroups = [...groups.map((g) => ({
2006
+ id: g.group.id,
2007
+ members: g.members,
2008
+ inset: g.inset,
2009
+ ...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {},
2010
+ ...g.parent !== void 0 ? { parent: g.parent } : {},
2011
+ ...g.group.reorder !== void 0 ? { ordered: g.direct } : {}
2012
+ })), ...boxes.groups];
2013
+ const visible = [...seeds.filter((s) => !boxes.hidden.has(s.id)), ...boxes.nodes];
2014
+ const seedOf = new Map(visible.map((n) => [n.id, n]));
2015
+ for (const [id, group] of boxes.groupOf) seedOf.get(id).group = group;
2016
+ for (const g of groups) {
2017
+ for (const m of g.direct) {
2018
+ const seed = seedOf.get(m);
2019
+ if (seed !== void 0) seed.group = g.group.id;
2020
+ }
2021
+ }
2022
+ const remapped = /* @__PURE__ */ new Set();
2023
+ const graphEdges = edges.flatMap((e) => {
2024
+ const edge = graphEdgeOf(e, room(e));
2025
+ const from = boxes.endOf(e.from);
2026
+ const to = boxes.endOf(e.to);
2027
+ if (from === e.from && to === e.to) return [edge];
2028
+ remapped.add(e);
2029
+ if (from === to) return [];
2030
+ const { fromPort: _f, toPort: _t, ...rest } = edge;
2031
+ return [{
2032
+ ...rest,
2033
+ from,
2034
+ to,
2035
+ ...from === e.from && e.fromPort ? { fromPort: e.fromPort } : {},
2036
+ ...to === e.to && e.toPort ? { toPort: e.toPort } : {}
2037
+ }];
2038
+ });
2039
+ const graph = graphOf(visible, expandGroupEdges(graphEdges, graphGroups), graphGroups);
2040
+ const layout = typeof opts.layout === "function" ? opts.layout : opts.layout === "none" ? null : LAYOUTS[opts.layout ?? "layered"];
2041
+ const layoutOptions = { order: "barycenter", ...opts.layoutOptions };
2042
+ const plan = layout === null ? null : planOf(layout, graph, layoutOptions);
2043
+ const moved = plan?.moves ?? /* @__PURE__ */ new Map();
2044
+ const placed = new Map(visible.map((n) => {
2045
+ const at = moved.get(n.id) ?? n.bounds;
2046
+ return [n.id, { x: at.x, y: at.y, width: n.bounds.width, height: n.bounds.height }];
2047
+ }));
2048
+ return { placed, plan: layout?.plan === void 0 ? null : plan, remapped };
2049
+ }
2050
+
2051
+ export { ANCHOR_DERIVE_POSE, CONTAINER_HEADER, CONTAINER_LAYOUTS, DEFAULT_FONT, DEFAULT_LANE_SIZE, DEFAULT_POOL_ACROSS, DIAGRAM_ANCHOR, DIAGRAM_CONTAINER_LAYOUT, DIAGRAM_EDGE, DIAGRAM_GROUP, DIAGRAM_LABEL, DIAGRAM_LANE_COLUMNS_LAYOUT, DIAGRAM_LANE_ROWS_LAYOUT, DIAGRAM_OFF_PAGE_TAG, DIAGRAM_POOL_COLUMNS_LAYOUT, DIAGRAM_POOL_ROWS_LAYOUT, DIAGRAM_ROLES, DIAGRAM_THEMES, DIAGRAM_THEME_KEY, EDGE_DERIVE_PATH, EDGE_STROKE_WIDTH, EDIT_EDGE_LABEL_ACTION_ID, GROUP_DERIVE_POSE, LABEL_DERIVE_POSE, LABEL_DRAG_AFFORDANCE_KIND, LABEL_DRAG_LAYER_ID, LABEL_PLACEMENT, LANE_HEADER_SIZE, LANE_STROKE_WIDTH, MIN_LANE_SIZE, NODE, NODE_STROKE_WIDTH, OFF_PAGE_STUB, OFF_PAGE_TAG_DERIVE_POSE, OFF_PAGE_TAG_PADDING, POOL_HEADER_SIZE, ROUTERS, anchorDerivePose, avoid, bezier, bodyBox, bodyLayoutOf, boxAround, boxCenter, buildGraph, containerShape, dataEdgeStyle, dataNodeStyle, dataOffPage, dataRegionStyle, diagramAnchorOf, diagramContainerLayout, diagramContainerOf, diagramGroupOf, diagramLabelOf, diagramMarkOf, diagramPages, diagramScene, diagramThemeFrom, diagramThemeOf, draggableLabels, dropEmptyEdgeLabel, edgeDerivePath, edgeIdOf, edgeLabelSpec, edgeRouter, editEdgeLabelAction, editEdgeLabelBinding, editableLabels, endAims, firstLanes, groupDerivePose, groupLabelIdOf, groupMinWidth, headerBox, labelDerivePose, labelDragLayer, labelFollowsLayout, labelScratchOf, labelSize, labelStyleOf, labelUnder, laneHeader, laneOutlineOf, laneStackLayout, markShape, nestGroups, nextOffPageRef, offPageFarText, offPageSplits, offPageTagData, offPageTagDerivePose, offPageTagId, offPageTagLook, offPageTagOf, offPageTagSize, offPageTagSpec, offPageTagSpecs, offPageTagText, orthogonal, poolHeader, regionSpecs, registerMarkShape, relayoutBodies, resolveEnd, roleEdgeStyle, roleStyle, routerOf, rowTextStyle, selfLoop, stackedLanes, straight, themeDocValue, themeEdgeStroke, themeFont, themeLabelFamily, themeLabelFont, themeLabelStyle, titleIdOf, titlePose, withDiagramRegistry, withHops };
2052
+ //# sourceMappingURL=chunk-43LDZ6L4.js.map
2053
+ //# sourceMappingURL=chunk-43LDZ6L4.js.map