@statelyai/layout 0.3.0 → 0.4.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.
Files changed (110) hide show
  1. package/NOTICE.md +4 -2
  2. package/README.md +150 -11
  3. package/dist/elkjs/index.cjs +2 -2
  4. package/dist/elkjs/index.d.cts +2 -0
  5. package/dist/elkjs/index.d.mts +2 -0
  6. package/dist/elkjs/index.mjs +2 -2
  7. package/dist/elkjs/main.cjs +1 -1
  8. package/dist/elkjs/main.mjs +1 -1
  9. package/dist/elkjs/worker.cjs +2 -2
  10. package/dist/elkjs/worker.mjs +2 -2
  11. package/dist/elkjs-BBfKN4Dw.mjs +3075 -0
  12. package/dist/elkjs-DNfXDPup.cjs +3104 -0
  13. package/dist/{index-BEA_pzeT.d.cts → index-B6cWNrMw.d.cts} +9 -1
  14. package/dist/{index-DNTLRsDl.d.mts → index-CJQLUKD9.d.mts} +9 -1
  15. package/dist/{index-DNixtE3h.d.mts → index-DT6lPMfS.d.cts} +85 -5
  16. package/dist/{index-D9dhxrRl.d.cts → index-F1U-fCTu.d.mts} +85 -5
  17. package/dist/index.cjs +6 -3
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.mts +3 -3
  20. package/dist/index.mjs +4 -4
  21. package/dist/layered/index.cjs +3 -1
  22. package/dist/layered/index.d.cts +2 -2
  23. package/dist/layered/index.d.mts +2 -2
  24. package/dist/layered/index.mjs +2 -2
  25. package/dist/{layered-B2buJqMf.cjs → layered-5RxQhryb.cjs} +11184 -2932
  26. package/dist/{layered-DGFTeQSl.mjs → layered-DSesbctm.mjs} +12059 -3903
  27. package/dist/{layout-engine-Dn1PiFwP.cjs → layout-engine-CLFySz3R.cjs} +5 -18
  28. package/dist/{layout-engine-Cs5tatA6.mjs → layout-engine-GpBIX6ng.mjs} +3 -10
  29. package/dist/routing/index.cjs +2 -1
  30. package/dist/routing/index.d.cts +2 -2
  31. package/dist/routing/index.d.mts +2 -2
  32. package/dist/routing/index.mjs +2 -2
  33. package/dist/{routing-A2hrK9MP.mjs → routing-CR8flv8b.mjs} +977 -593
  34. package/dist/{routing-ujEP7Qh6.cjs → routing-QIdGGFBe.cjs} +986 -596
  35. package/package.json +8 -2
  36. package/src/authoring/coordinates.ts +28 -8
  37. package/src/authoring/partial.ts +7 -2
  38. package/src/authoring/routing.ts +4 -1
  39. package/src/constraints.ts +174 -6
  40. package/src/elkjs/index.ts +1476 -449
  41. package/src/elkjs/layout-quality.ts +267 -0
  42. package/src/elkjs/opposing-tracks.ts +161 -0
  43. package/src/elkjs/orthogonal-junctions.ts +174 -0
  44. package/src/elkjs/statechart.ts +9 -3
  45. package/src/elkjs/types.ts +2 -0
  46. package/src/index.ts +2 -0
  47. package/src/internal/elkjs-compatibility.ts +4 -1
  48. package/src/internal/layered-trace.ts +48 -0
  49. package/src/internal/layout-engine.ts +2 -1
  50. package/src/java-random.ts +6 -0
  51. package/src/layered/bk-node-placement.ts +403 -92
  52. package/src/layered/center-labels.ts +398 -0
  53. package/src/layered/compaction-bounds.ts +26 -0
  54. package/src/layered/compaction-errors.ts +2 -0
  55. package/src/layered/compaction-scanline.ts +63 -0
  56. package/src/layered/compound-boundaries.ts +99 -0
  57. package/src/layered/compound.ts +35 -7
  58. package/src/layered/constraint-orientation.ts +141 -0
  59. package/src/layered/crossing-constraints.ts +243 -0
  60. package/src/layered/crossing-counter.ts +233 -0
  61. package/src/layered/crossing-graph.ts +401 -0
  62. package/src/layered/cycle-random.ts +22 -0
  63. package/src/layered/elk-port-lists.ts +243 -0
  64. package/src/layered/end-labels.ts +124 -0
  65. package/src/layered/external-port-dummy.ts +137 -0
  66. package/src/layered/feedback-chains.ts +114 -0
  67. package/src/layered/fixed-self-loop.ts +150 -0
  68. package/src/layered/greedy-switch-decider.ts +356 -0
  69. package/src/layered/grouped-compaction.ts +787 -0
  70. package/src/layered/gwt-sort.ts +53 -0
  71. package/src/layered/hierarchical-port-phases.ts +242 -0
  72. package/src/layered/hierarchical-port-restoration.ts +233 -0
  73. package/src/layered/hierarchical-port-routing.ts +179 -0
  74. package/src/layered/hierarchy-crossing.ts +121 -0
  75. package/src/layered/hierarchy-sweepiness.ts +79 -0
  76. package/src/layered/hints.ts +371 -0
  77. package/src/layered/hyperedge-dummy-merger.ts +195 -0
  78. package/src/layered/index.ts +875 -161
  79. package/src/layered/initial-model-order.ts +404 -0
  80. package/src/layered/inverted-ports.ts +207 -0
  81. package/src/layered/label-dummy-switcher.ts +200 -0
  82. package/src/layered/long-edges.ts +135 -44
  83. package/src/layered/loop-envelopes.ts +184 -0
  84. package/src/layered/native-routing.ts +275 -0
  85. package/src/layered/network-simplex.ts +11 -15
  86. package/src/layered/node-margins.ts +59 -0
  87. package/src/layered/north-south-ports.ts +271 -0
  88. package/src/layered/opposing-tracks.ts +1044 -0
  89. package/src/layered/orthogonal-cycle-order.ts +106 -0
  90. package/src/layered/orthogonal-hypersegments.ts +154 -0
  91. package/src/layered/orthogonal-junctions.ts +202 -0
  92. package/src/layered/orthogonal-segments.ts +243 -0
  93. package/src/layered/placement-orientation.ts +22 -0
  94. package/src/layered/port-distributor.ts +192 -0
  95. package/src/layered/port-edge-order.ts +20 -0
  96. package/src/layered/replace-routing.ts +45 -0
  97. package/src/layered/routing-coordinates.ts +32 -0
  98. package/src/layered/separate-exterior-labels.ts +24 -6
  99. package/src/layered/spacing.ts +34 -12
  100. package/src/layered/splitter-order.ts +106 -0
  101. package/src/layered/strategies.ts +2124 -886
  102. package/src/layered/types.ts +13 -0
  103. package/src/layered/weighted-compaction.ts +84 -0
  104. package/src/routing/adapters.ts +32 -0
  105. package/src/routing/algorithms.ts +433 -21
  106. package/src/routing/coordination.ts +8 -3
  107. package/src/routing/index.ts +1 -0
  108. package/src/routing/search.ts +93 -11
  109. package/dist/elkjs-DaHMISnQ.mjs +0 -1677
  110. package/dist/elkjs-dMghiiWW.cjs +0 -1706
@@ -0,0 +1,3104 @@
1
+ const require_routing = require('./routing-QIdGGFBe.cjs');
2
+ const require_layered = require('./layered-5RxQhryb.cjs');
3
+ const require_layout_engine = require('./layout-engine-CLFySz3R.cjs');
4
+ let _statelyai_graph = require("@statelyai/graph");
5
+
6
+ //#region src/layered/hierarchy-sweepiness.ts
7
+ /**
8
+ * Whether a child graph is swept on its own before its parent. Like ELK, a
9
+ * node starts a random path when no western port carries an edge and ends one
10
+ * when no eastern port does, on the canonical crossing graph's port sides.
11
+ */
12
+ function useBottomUpHierarchySweep(input, graph, parent) {
13
+ const boundary = input.settings["crossingMinimization.hierarchicalSweepiness"] ?? .1;
14
+ if (boundary < -1 || parent.portOrderFixed || parent.inputPorts < 2 && parent.outputPorts < 2) return true;
15
+ if (parent.deterministic) return false;
16
+ const nodes = new Map(input.graph.nodes.map((node) => [node.id, node]));
17
+ const owner = /* @__PURE__ */ new Map();
18
+ for (const node of graph.layers.flat()) for (const port of node.ports) owner.set(port.id, node.id);
19
+ const degree = /* @__PURE__ */ new Map(), targets = /* @__PURE__ */ new Map();
20
+ for (const { source, target } of graph.edges) {
21
+ degree.set(source, (degree.get(source) ?? 0) + 1);
22
+ degree.set(target, (degree.get(target) ?? 0) + 1);
23
+ const from = owner.get(source);
24
+ targets.set(from, [...targets.get(from) ?? [], owner.get(target)]);
25
+ }
26
+ const infos = new Map(graph.layers.flat().map((node) => [node.id, {
27
+ random: 0,
28
+ hierarchy: 0,
29
+ edges: 0
30
+ }]));
31
+ let randomPaths = 0, hierarchyPaths = 0;
32
+ const transfer = (source, target) => {
33
+ const a = infos.get(source), b = infos.get(target);
34
+ randomPaths += a.random;
35
+ hierarchyPaths += a.hierarchy;
36
+ b.random += a.random;
37
+ b.hierarchy += a.hierarchy;
38
+ b.edges += a.edges + 1;
39
+ };
40
+ const outputSide = {
41
+ right: "EAST",
42
+ left: "WEST",
43
+ down: "SOUTH",
44
+ up: "NORTH"
45
+ }[input.direction];
46
+ for (const layer of graph.layers) {
47
+ const northSouth = [];
48
+ for (const node of layer) {
49
+ if (node.type === "NORTH_SOUTH_PORT") {
50
+ northSouth.push(node.id);
51
+ continue;
52
+ }
53
+ const state = infos.get(node.id);
54
+ const connected = (side) => node.ports.some((port) => port.side === side && degree.has(port.id));
55
+ const external = require_layered.externalPortDummyOf(nodes.get(node.id));
56
+ if (external) {
57
+ state.hierarchy = 1;
58
+ if (external.side === outputSide) hierarchyPaths += state.edges;
59
+ } else if (!connected("WEST")) state.random = 1;
60
+ else if (!connected("EAST")) randomPaths += state.edges;
61
+ for (const target of targets.get(node.id) ?? []) transfer(node.id, target);
62
+ for (const port of node.ports) if ((port.side === "NORTH" || port.side === "SOUTH") && port.dummy) transfer(node.id, port.dummy);
63
+ }
64
+ for (const id of northSouth) for (const target of targets.get(id) ?? []) transfer(id, target);
65
+ }
66
+ const all = randomPaths + hierarchyPaths;
67
+ return (all === 0 ? Number.POSITIVE_INFINITY : (randomPaths - hierarchyPaths) / all) >= boundary;
68
+ }
69
+
70
+ //#endregion
71
+ //#region src/layered/compound-boundaries.ts
72
+ /** Return both effects of ELK's transfer: parent port and corrected content-space dummy. */
73
+ function transferExternalPort(input) {
74
+ const { contentSize: size, padding: p, offset: o, borderOffset: b, portSize } = input;
75
+ const dummy = {
76
+ x: input.dummy.x,
77
+ y: input.dummy.y
78
+ };
79
+ const port = {
80
+ x: dummy.x + input.dummy.width / 2,
81
+ y: dummy.y + input.dummy.height / 2
82
+ };
83
+ switch (input.side) {
84
+ case "NORTH":
85
+ port.x += p.left + o.x - portSize.width / 2;
86
+ port.y = -portSize.height - b;
87
+ dummy.y = -(p.top + b + o.y);
88
+ break;
89
+ case "EAST":
90
+ port.x = size.width + p.left + p.right + b;
91
+ port.y += p.top + o.y - portSize.height / 2;
92
+ dummy.x = size.width + p.right + b - o.x;
93
+ break;
94
+ case "SOUTH":
95
+ port.x += p.left + o.x - portSize.width / 2;
96
+ port.y = size.height + p.top + p.bottom + b;
97
+ dummy.y = size.height + p.bottom + b - o.y;
98
+ break;
99
+ case "WEST":
100
+ port.x = -portSize.width - b;
101
+ port.y += p.top + o.y - portSize.height / 2;
102
+ dummy.x = -(p.left + b + o.x);
103
+ break;
104
+ }
105
+ return {
106
+ port,
107
+ dummy
108
+ };
109
+ }
110
+ /** Segments are directed source-to-target and translated to one reference graph. */
111
+ function joinCompoundRouteSegments(segments, unnecessaryBendpoints = false) {
112
+ const bends = [];
113
+ let start, end, last;
114
+ for (const segment of segments) {
115
+ if (segment.points.length < 2) throw new Error("Compound route segment needs two anchors");
116
+ const points = segment.points.map((p) => ({
117
+ x: p.x + segment.offset.x,
118
+ y: p.y + segment.offset.y
119
+ }));
120
+ const source = points[0], target = points.at(-1);
121
+ const inner = points.slice(1, -1);
122
+ if (!start) start = source;
123
+ if (last) {
124
+ const next = inner[0] ?? target;
125
+ const dx = Math.abs(last.x - next.x) > 1e-6;
126
+ const dy = Math.abs(last.y - next.y) > 1e-6;
127
+ if (unnecessaryBendpoints ? dx || dy : dx && dy) bends.push(source);
128
+ }
129
+ bends.push(...inner);
130
+ last = inner.at(-1) ?? source;
131
+ end = target;
132
+ }
133
+ if (!start || !end) return [];
134
+ const route = [
135
+ start,
136
+ ...bends,
137
+ end
138
+ ];
139
+ const straight = (a, b) => Math.abs(a.x - b.x) <= 1e-6 || Math.abs(a.y - b.y) <= 1e-6;
140
+ for (let index = 0; index + 1 < route.length; index++) {
141
+ const a = route[index], b = route[index + 1];
142
+ if (straight(a, b)) continue;
143
+ const next = route[index + 2], previous = route[index - 1];
144
+ const verticalFirst = next !== void 0 ? Math.abs(b.y - next.y) <= 1e-6 : previous !== void 0 && Math.abs(previous.x - a.x) <= 1e-6;
145
+ route.splice(index + 1, 0, verticalFirst ? {
146
+ x: a.x,
147
+ y: b.y
148
+ } : {
149
+ x: b.x,
150
+ y: a.y
151
+ });
152
+ }
153
+ return route;
154
+ }
155
+
156
+ //#endregion
157
+ //#region src/elkjs/layout-quality.ts
158
+ const EPS = 1e-6;
159
+ /** Shared endpoint nodes get this clearance, so fan-in and fan-out are not penalized. */
160
+ const TERMINAL_MARGIN = 12;
161
+ const horizontal = (s) => Math.abs(s.a.y - s.b.y) < EPS;
162
+ const vertical = (s) => Math.abs(s.a.x - s.b.x) < EPS;
163
+ const span = (s, axis) => [Math.min(s.a[axis], s.b[axis]), Math.max(s.a[axis], s.b[axis])];
164
+ const strictlyInside = (value, low, high) => value > low + EPS && value < high - EPS;
165
+ const overlapLength = (a, b) => Math.max(0, Math.min(a[1], b[1]) - Math.max(a[0], b[0]));
166
+ /** Within `margin` of the rectangle, its border included. */
167
+ const inRect = (point, rect, margin) => point.x >= rect.x - margin - EPS && point.x <= rect.x + rect.width + margin + EPS && point.y >= rect.y - margin - EPS && point.y <= rect.y + rect.height + margin + EPS;
168
+ /**
169
+ * Measure an ELK JSON result in absolute coordinates. `defectsOnly` skips the
170
+ * pairwise route measures (crossings and overlap stay 0).
171
+ */
172
+ function measureLayout(root, defectsOnly = false) {
173
+ const frames = new Map([[String(root.id), {
174
+ x: 0,
175
+ y: 0
176
+ }]]);
177
+ const rects = /* @__PURE__ */ new Map();
178
+ const owner = /* @__PURE__ */ new Map();
179
+ const leaves = [];
180
+ const bounds = [];
181
+ const place = (parent, offset) => {
182
+ for (const child of parent.children ?? []) {
183
+ const id = String(child.id);
184
+ const at = {
185
+ x: offset.x + (child.x ?? 0),
186
+ y: offset.y + (child.y ?? 0)
187
+ };
188
+ const rect = {
189
+ ...at,
190
+ width: child.width ?? 0,
191
+ height: child.height ?? 0
192
+ };
193
+ frames.set(id, at);
194
+ rects.set(id, rect);
195
+ owner.set(id, id);
196
+ for (const port of child.ports ?? []) owner.set(String(port.id), id);
197
+ bounds.push(at, {
198
+ x: at.x + rect.width,
199
+ y: at.y + rect.height
200
+ });
201
+ if (child.children?.length) place(child, at);
202
+ else leaves.push(rect);
203
+ }
204
+ };
205
+ place(root, {
206
+ x: 0,
207
+ y: 0
208
+ });
209
+ const routes = [];
210
+ let defects = 0, bends = 0, length = 0;
211
+ const collect = (parent) => {
212
+ for (const edge of parent.edges ?? []) {
213
+ const container = String(edge.container ?? parent.id);
214
+ const offset = frames.get(container) ?? {
215
+ x: 0,
216
+ y: 0
217
+ };
218
+ const ends = [edge.sources?.[0], edge.targets?.[0]].flatMap((end) => {
219
+ const node = owner.get(String(end));
220
+ return node === void 0 ? [] : [node];
221
+ });
222
+ const segments = [];
223
+ for (const section of edge.sections ?? []) {
224
+ const points = [
225
+ section.startPoint,
226
+ ...section.bendPoints ?? [],
227
+ section.endPoint
228
+ ].map((point) => ({
229
+ x: point.x + offset.x,
230
+ y: point.y + offset.y
231
+ }));
232
+ bounds.push(...points);
233
+ let direction;
234
+ for (let index = 0; index + 1 < points.length; index++) {
235
+ const segment = {
236
+ a: points[index],
237
+ b: points[index + 1]
238
+ };
239
+ const size = Math.hypot(segment.b.x - segment.a.x, segment.b.y - segment.a.y);
240
+ if (size < EPS) continue;
241
+ if (!horizontal(segment) && !vertical(segment)) defects++;
242
+ const next = {
243
+ x: Math.sign(segment.b.x - segment.a.x),
244
+ y: Math.sign(segment.b.y - segment.a.y)
245
+ };
246
+ if (direction && (direction.x !== next.x || direction.y !== next.y)) bends++;
247
+ direction = next;
248
+ length += size;
249
+ segments.push(segment);
250
+ }
251
+ }
252
+ const labels = (edge.labels ?? []).flatMap((label) => label.width && label.height ? [{
253
+ x: (label.x ?? 0) + offset.x,
254
+ y: (label.y ?? 0) + offset.y,
255
+ width: label.width,
256
+ height: label.height
257
+ }] : []);
258
+ for (const label of labels) bounds.push(label, {
259
+ x: label.x + label.width,
260
+ y: label.y + label.height
261
+ });
262
+ for (const segment of segments) for (const rect of leaves) if (horizontal(segment) && strictlyInside(segment.a.y, rect.y, rect.y + rect.height) && overlapLength(span(segment, "x"), [rect.x, rect.x + rect.width]) > EPS || vertical(segment) && strictlyInside(segment.a.x, rect.x, rect.x + rect.width) && overlapLength(span(segment, "y"), [rect.y, rect.y + rect.height]) > EPS) defects++;
263
+ for (const [index, first] of segments.entries()) for (const second of segments.slice(index + 1)) if (sharedLength(first, second) > EPS) defects++;
264
+ routes.push({
265
+ ends,
266
+ segments,
267
+ visible: labels.length && !defectsOnly ? segments.flatMap((s) => outsideAll(s, labels)) : segments,
268
+ labels
269
+ });
270
+ }
271
+ for (const child of parent.children ?? []) collect(child);
272
+ };
273
+ collect(root);
274
+ const allLabels = routes.flatMap((route) => route.labels);
275
+ const covers = (a, b) => a.x < b.x + b.width - EPS && a.x + a.width > b.x + EPS && a.y < b.y + b.height - EPS && a.y + a.height > b.y + EPS;
276
+ for (const [index, label] of allLabels.entries()) {
277
+ for (const rect of leaves) if (covers(label, rect)) defects++;
278
+ for (const other of allLabels.slice(index + 1)) if (covers(label, other)) defects++;
279
+ }
280
+ for (const route of routes) for (const other of routes) if (other !== route) {
281
+ for (const label of other.labels) if (route.segments.some((segment) => horizontal(segment) && strictlyInside(segment.a.y, label.y, label.y + label.height) && overlapLength(span(segment, "x"), [label.x, label.x + label.width]) > EPS || vertical(segment) && strictlyInside(segment.a.x, label.x, label.x + label.width) && overlapLength(span(segment, "y"), [label.y, label.y + label.height]) > EPS)) defects++;
282
+ }
283
+ let crossings = 0, overlap = 0;
284
+ if (!defectsOnly) for (const [index, first] of routes.entries()) for (const second of routes.slice(index + 1)) {
285
+ const shared = first.ends.filter((id) => second.ends.includes(id)).flatMap((id) => {
286
+ const rect = rects.get(id);
287
+ return rect ? [rect] : [];
288
+ });
289
+ const clear = (point) => shared.every((rect) => !inRect(point, rect, TERMINAL_MARGIN));
290
+ let points;
291
+ const cross$1 = (s, t) => {
292
+ const h = horizontal(s) && vertical(t) ? s : horizontal(t) && vertical(s) ? t : void 0;
293
+ if (!h) return;
294
+ const v = h === s ? t : s;
295
+ const point = {
296
+ x: v.a.x,
297
+ y: h.a.y
298
+ };
299
+ if (strictlyInside(point.x, ...span(h, "x")) && strictlyInside(point.y, ...span(v, "y")) && clear(point)) (points ??= /* @__PURE__ */ new Set()).add(`${Math.round(point.x / EPS)}:${Math.round(point.y / EPS)}`);
300
+ };
301
+ const unlabeled = first.visible === first.segments && second.visible === second.segments;
302
+ for (const s of first.segments) for (const t of second.segments) {
303
+ if (unlabeled) cross$1(s, t);
304
+ const shared$1 = sharedLength(s, t);
305
+ if (shared$1 > EPS && clear(s.a) && clear(s.b)) overlap += shared$1;
306
+ }
307
+ if (!unlabeled) for (const s of first.visible) for (const t of second.visible) cross$1(s, t);
308
+ crossings += points?.size ?? 0;
309
+ }
310
+ const xs = bounds.map((point) => point.x).filter(Number.isFinite), ys = bounds.map((point) => point.y).filter(Number.isFinite);
311
+ const area = xs.length ? (Math.max(...xs) - Math.min(...xs)) * (Math.max(...ys) - Math.min(...ys)) : 0;
312
+ return {
313
+ defects,
314
+ crossings,
315
+ overlap,
316
+ bends,
317
+ length,
318
+ area
319
+ };
320
+ }
321
+ /** The parts of an axis-parallel segment outside the open interiors of `rects`. */
322
+ function outsideAll(segment, rects) {
323
+ let parts = [segment];
324
+ for (const rect of rects) parts = parts.flatMap((part) => {
325
+ const axis = horizontal(part) ? "x" : vertical(part) ? "y" : void 0;
326
+ if (!axis) return [part];
327
+ const cross$1 = axis === "x" ? "y" : "x";
328
+ const [low, high] = axis === "x" ? [rect.x, rect.x + rect.width] : [rect.y, rect.y + rect.height];
329
+ const [crossLow, crossHigh] = cross$1 === "x" ? [rect.x, rect.x + rect.width] : [rect.y, rect.y + rect.height];
330
+ if (!strictlyInside(part.a[cross$1], crossLow, crossHigh)) return [part];
331
+ const [from, to] = span(part, axis);
332
+ if (overlapLength([from, to], [low, high]) <= EPS) return [part];
333
+ const at = (value) => ({
334
+ ...part.a,
335
+ [axis]: value
336
+ });
337
+ return [[from, Math.max(from, low)], [Math.min(to, high), to]].filter(([a, b]) => b - a > EPS).map(([a, b]) => ({
338
+ a: at(a),
339
+ b: at(b)
340
+ }));
341
+ });
342
+ return parts;
343
+ }
344
+ function sharedLength(s, t) {
345
+ if (horizontal(s) && horizontal(t) && Math.abs(s.a.y - t.a.y) < EPS) return overlapLength(span(s, "x"), span(t, "x"));
346
+ if (vertical(s) && vertical(t) && Math.abs(s.a.x - t.a.x) < EPS) return overlapLength(span(s, "y"), span(t, "y"));
347
+ return 0;
348
+ }
349
+ const TOLERANCE = .02;
350
+ /** True when `candidate` is better at the first measure where the two layouts differ. */
351
+ function isBetterLayout(candidate, current) {
352
+ for (const key of [
353
+ "defects",
354
+ "crossings",
355
+ "overlap",
356
+ "bends",
357
+ "length",
358
+ "area"
359
+ ]) {
360
+ const limit = key === "overlap" || key === "length" || key === "area" ? Math.max(TOLERANCE * Math.abs(current[key]), EPS) : 0;
361
+ if (Math.abs(candidate[key] - current[key]) > limit) return candidate[key] < current[key];
362
+ }
363
+ return false;
364
+ }
365
+
366
+ //#endregion
367
+ //#region src/elkjs/orthogonal-junctions.ts
368
+ /** Reconstruct native segment spans from final orthogonal tracks, then emit ELK's unique junctions. */
369
+ function applyOrthogonalJunctions(root, horizontal$1, increasing, mergeEdges, hypernodes, preservedEdges) {
370
+ const edges = root.edges ?? [], ports = [], byPort = /* @__PURE__ */ new Map();
371
+ const explicit = new Map((root.children ?? []).flatMap((n) => (n.ports ?? []).map((p) => [String(p.id), n])));
372
+ const owner = new Map((root.children ?? []).map((node) => [String(node.id), node]));
373
+ for (const [id, node] of explicit) owner.set(id, node);
374
+ const records = edges.flatMap((edge, index) => {
375
+ if (preservedEdges.has(String(edge.id))) return [];
376
+ const sourceOwner = owner.get(String(edge.sources?.[0] ?? edge.source));
377
+ if (sourceOwner !== void 0 && sourceOwner === owner.get(String(edge.targets?.[0] ?? edge.target))) return [];
378
+ const section = edge.sections?.[0];
379
+ if (!section || edge.sections?.length !== 1) return [];
380
+ let points = [
381
+ section.startPoint,
382
+ ...section.bendPoints ?? [],
383
+ section.endPoint
384
+ ];
385
+ if (points.some((p, i) => i > 0 && Math.abs(p.x - points[i - 1].x) > .001 && Math.abs(p.y - points[i - 1].y) > .001)) return [];
386
+ let source = String(edge.sources?.[0] ?? edge.source), target = String(edge.targets?.[0] ?? edge.target);
387
+ const flow$1 = (p) => horizontal$1 ? p.x : p.y;
388
+ if (flow$1(points[0]) > flow$1(points.at(-1)) === increasing) {
389
+ points = [...points].reverse();
390
+ [source, target] = [target, source];
391
+ }
392
+ const key = (ref, side) => explicit.has(ref) ? JSON.stringify(["port", ref]) : mergeEdges || hypernodes.has(ref) ? JSON.stringify([
393
+ "implicit",
394
+ ref,
395
+ side
396
+ ]) : JSON.stringify([
397
+ "edge",
398
+ index,
399
+ side
400
+ ]);
401
+ return [{
402
+ edge,
403
+ points,
404
+ source: key(source, "source"),
405
+ target: key(target, "target")
406
+ }];
407
+ });
408
+ const cross$1 = (p) => horizontal$1 ? p.y : p.x, flow = (p) => horizontal$1 ? p.x : p.y;
409
+ for (const side of ["source", "target"]) {
410
+ const ordered = [...records].sort((a, b) => cross$1(side === "source" ? a.points[0] : a.points.at(-1)) - cross$1(side === "source" ? b.points[0] : b.points.at(-1)));
411
+ for (const record of ordered) {
412
+ const id = record[side];
413
+ if (!byPort.has(id)) {
414
+ const port = {
415
+ id,
416
+ side,
417
+ position: cross$1(side === "source" ? record.points[0] : record.points.at(-1))
418
+ };
419
+ byPort.set(id, port);
420
+ ports.push(port);
421
+ }
422
+ }
423
+ }
424
+ const grouped = require_layered.createOrthogonalHypersegments(ports, records.map((r) => ({
425
+ source: r.source,
426
+ target: r.target
427
+ })));
428
+ const created = /* @__PURE__ */ new Set();
429
+ for (const group of grouped.segments) {
430
+ const members = records.filter((r) => group.ports.includes(r.source));
431
+ if (members.length === 1) {
432
+ delete members[0].edge.junctionPoints;
433
+ continue;
434
+ }
435
+ const tracks = /* @__PURE__ */ new Map();
436
+ const track = (point) => {
437
+ const key = flow(point);
438
+ let t = tracks.get(key);
439
+ if (!t) {
440
+ t = {
441
+ start: Infinity,
442
+ end: -Infinity,
443
+ incoming: [],
444
+ outgoing: []
445
+ };
446
+ tracks.set(key, t);
447
+ }
448
+ t.start = Math.min(t.start, cross$1(point));
449
+ t.end = Math.max(t.end, cross$1(point));
450
+ return t;
451
+ };
452
+ for (const r of members) {
453
+ const bends = r.points.slice(1, -1);
454
+ if (!bends.length) continue;
455
+ for (const p of bends) track(p);
456
+ track(bends[0]).incoming.push(cross$1(bends[0]));
457
+ track(bends.at(-1)).outgoing.push(cross$1(bends.at(-1)));
458
+ for (let i = 1; i < bends.length; i++) if (Math.abs(flow(bends[i]) - flow(bends[i - 1])) > .001) {
459
+ track(bends[i - 1]).outgoing.push(cross$1(bends[i - 1]));
460
+ track(bends[i]).incoming.push(cross$1(bends[i]));
461
+ }
462
+ }
463
+ for (const r of members.filter((r$1) => r$1.points.length === 2)) {
464
+ const start = Math.min(flow(r.points[0]), flow(r.points[1]));
465
+ const end = Math.max(flow(r.points[0]), flow(r.points[1]));
466
+ for (const [position, t] of tracks) {
467
+ if (position < start || position > end) continue;
468
+ t.incoming.push(cross$1(r.points[0]));
469
+ t.outgoing.push(cross$1(r.points[1]));
470
+ }
471
+ }
472
+ for (const r of members) for (let index = 1; index < r.points.length; index++) {
473
+ const a = r.points[index - 1], b = r.points[index];
474
+ if (Math.abs(cross$1(a) - cross$1(b)) >= .001) continue;
475
+ const start = Math.min(flow(a), flow(b)), end = Math.max(flow(a), flow(b));
476
+ for (const [position, t] of tracks) {
477
+ if (position <= start || position >= end) continue;
478
+ t.incoming.push(cross$1(a));
479
+ t.outgoing.push(cross$1(b));
480
+ }
481
+ }
482
+ for (const t of tracks.values()) {
483
+ t.start = Math.min(t.start, ...t.incoming, ...t.outgoing);
484
+ t.end = Math.max(t.end, ...t.incoming, ...t.outgoing);
485
+ }
486
+ for (const port of group.ports) for (const r of members.filter((r$1) => r$1.source === port)) {
487
+ delete r.edge.junctionPoints;
488
+ for (const p of r.points.slice(1, -1)) {
489
+ const t = tracks.get(flow(p)), value = cross$1(p), key = `${p.x}:${p.y}`;
490
+ const boundary = t.incoming.length && t.outgoing.length && (Math.abs(value - Math.min(...t.incoming)) < .001 && Math.abs(value - Math.min(...t.outgoing)) < .001 || Math.abs(value - Math.max(...t.incoming)) < .001 && Math.abs(value - Math.max(...t.outgoing)) < .001);
491
+ if (!created.has(key) && (value > t.start && value < t.end || boundary)) {
492
+ (r.edge.junctionPoints ??= []).push({ ...p });
493
+ created.add(key);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ //#endregion
501
+ //#region src/elkjs/opposing-tracks.ts
502
+ const option$1 = (options, suffix) => Object.entries(options ?? {}).find(([key]) => key === suffix || key.endsWith(`.${suffix}`))?.[1];
503
+ /**
504
+ * Separate opposite-direction track sharing in a finished ELK JSON layout,
505
+ * across nested containers, in world coordinates.
506
+ */
507
+ function separateElkOpposingTracks(root, options) {
508
+ const orthogonal = (node, inherited) => {
509
+ const value = option$1(node.layoutOptions, "edgeRouting");
510
+ return value === void 0 ? inherited : String(value) === "ORTHOGONAL";
511
+ };
512
+ const rootOrthogonal = orthogonal(root, String(option$1(options, "edgeRouting") ?? "ORTHOGONAL") === "ORTHOGONAL");
513
+ const frames = new Map([[String(root.id), {
514
+ x: 0,
515
+ y: 0
516
+ }]]);
517
+ const routing = new Map([[String(root.id), rootOrthogonal]]);
518
+ const nodes = /* @__PURE__ */ new Map(), leaves = /* @__PURE__ */ new Set(), owner = /* @__PURE__ */ new Map(), ports = /* @__PURE__ */ new Map();
519
+ const place = (parent, offset) => {
520
+ for (const child of parent.children ?? []) {
521
+ const id = String(child.id);
522
+ const at = {
523
+ x: offset.x + (child.x ?? 0),
524
+ y: offset.y + (child.y ?? 0)
525
+ };
526
+ frames.set(id, at);
527
+ routing.set(id, orthogonal(child, routing.get(String(parent.id)) ?? rootOrthogonal));
528
+ nodes.set(id, {
529
+ ...at,
530
+ width: child.width ?? 0,
531
+ height: child.height ?? 0
532
+ });
533
+ owner.set(id, id);
534
+ for (const port of child.ports ?? []) {
535
+ owner.set(String(port.id), id);
536
+ ports.set(String(port.id), {
537
+ node: id,
538
+ rect: {
539
+ x: at.x + (port.x ?? 0),
540
+ y: at.y + (port.y ?? 0),
541
+ width: port.width ?? 0,
542
+ height: port.height ?? 0
543
+ }
544
+ });
545
+ }
546
+ if (child.children?.length) place(child, at);
547
+ else leaves.add(id);
548
+ }
549
+ };
550
+ place(root, {
551
+ x: 0,
552
+ y: 0
553
+ });
554
+ const records = [];
555
+ const collect = (parent) => {
556
+ for (const edge of parent.edges ?? []) {
557
+ if ((edge.sections?.length ?? 0) > 1) continue;
558
+ const container = String(edge.container ?? parent.id);
559
+ const offset = frames.get(container) ?? {
560
+ x: 0,
561
+ y: 0
562
+ };
563
+ const points = (edge.sections ?? []).flatMap((section) => [
564
+ section.startPoint,
565
+ ...section.bendPoints ?? [],
566
+ section.endPoint
567
+ ].map((p) => ({
568
+ x: p.x + offset.x,
569
+ y: p.y + offset.y
570
+ })));
571
+ const axisParallel = points.every((p, i) => i === 0 || Math.abs(p.x - points[i - 1].x) < 1e-6 || Math.abs(p.y - points[i - 1].y) < 1e-6);
572
+ const ends = [edge.sources?.[0] ?? edge.source, edge.targets?.[0] ?? edge.target].flatMap((end) => {
573
+ const id = owner.get(String(end));
574
+ return id === void 0 ? [] : [id];
575
+ });
576
+ const portOf = (end) => ports.has(String(end)) ? String(end) : void 0;
577
+ records.push({
578
+ edge,
579
+ offset,
580
+ route: {
581
+ id: String(edge.id),
582
+ ends,
583
+ points,
584
+ ports: [portOf(edge.sources?.[0] ?? edge.source), portOf(edge.targets?.[0] ?? edge.target)],
585
+ labels: (edge.labels ?? []).flatMap((label) => label.width && label.height ? [{
586
+ x: (label.x ?? 0) + offset.x,
587
+ y: (label.y ?? 0) + offset.y,
588
+ width: label.width,
589
+ height: label.height
590
+ }] : []),
591
+ movable: points.length > 0 && axisParallel && (routing.get(container) ?? rootOrthogonal)
592
+ }
593
+ });
594
+ }
595
+ for (const child of parent.children ?? []) collect(child);
596
+ };
597
+ collect(root);
598
+ if (!records.some((record) => record.route.movable)) return;
599
+ const spacing = Number(option$1(options, "spacing.edgeEdge") ?? 10);
600
+ const changed = require_layered.separateOpposingTracks({
601
+ routes: records.map((record) => record.route),
602
+ nodes,
603
+ leaves,
604
+ ports,
605
+ spacing: Number.isFinite(spacing) && spacing > 0 ? spacing : 10
606
+ });
607
+ if (!changed.size) return;
608
+ const routes = new Map(records.map((record) => [record.route.id, changed.get(record.route.id) ?? record.route.points]));
609
+ const on = (point, points) => points.some((b, i) => {
610
+ const a = points[i - 1];
611
+ return a !== void 0 && point.x >= Math.min(a.x, b.x) - 1e-6 && point.x <= Math.max(a.x, b.x) + 1e-6 && point.y >= Math.min(a.y, b.y) - 1e-6 && point.y <= Math.max(a.y, b.y) + 1e-6;
612
+ });
613
+ for (const { edge, offset, route } of records) {
614
+ const points = changed.get(route.id);
615
+ if (points) {
616
+ const section = edge.sections[0];
617
+ const local = points.map((p) => ({
618
+ x: p.x - offset.x,
619
+ y: p.y - offset.y
620
+ }));
621
+ section.startPoint = local[0];
622
+ section.endPoint = local.at(-1);
623
+ section.bendPoints = local.slice(1, -1);
624
+ }
625
+ if (!edge.junctionPoints?.length) continue;
626
+ const own = routes.get(route.id);
627
+ edge.junctionPoints = edge.junctionPoints.filter((junction) => {
628
+ const point = {
629
+ x: junction.x + offset.x,
630
+ y: junction.y + offset.y
631
+ };
632
+ return on(point, own) && [...routes].some(([id, other]) => id !== route.id && on(point, other));
633
+ });
634
+ if (!edge.junctionPoints.length) delete edge.junctionPoints;
635
+ }
636
+ }
637
+
638
+ //#endregion
639
+ //#region src/elkjs/statechart.ts
640
+ function option(node, suffix) {
641
+ const settings = {
642
+ ...node.properties,
643
+ ...node.layoutOptions
644
+ };
645
+ return settings[suffix] ?? settings[`elk.${suffix}`] ?? settings[`org.eclipse.elk.${suffix}`];
646
+ }
647
+ const endpoint$1 = (edge, source) => source ? edge.sources?.[0] ?? edge.source : edge.targets?.[0] ?? edge.target;
648
+ /** Compile semantic hints into settings implemented by this package's ELK adapter. */
649
+ function compileStatechartLayout(input, scopes) {
650
+ const graph = structuredClone(input);
651
+ const paths = Object.create(null);
652
+ const commonExits = Object.create(null);
653
+ const containers = /* @__PURE__ */ new Map();
654
+ const edges = [];
655
+ const collect = (node) => {
656
+ if (node.id === void 0 || containers.has(String(node.id))) throw new Error("Statechart nodes require unique IDs");
657
+ containers.set(String(node.id), node);
658
+ edges.push(...node.edges ?? []);
659
+ node.children?.forEach(collect);
660
+ };
661
+ collect(graph);
662
+ for (const [id, scope] of Object.entries(scopes)) {
663
+ const container = containers.get(id);
664
+ if (!container) throw new Error(`Unknown statechart container: ${id}`);
665
+ const children = container.children ?? [];
666
+ const ids = new Set(children.map((node) => node.id));
667
+ const owner = /* @__PURE__ */ new Map();
668
+ const own = (node, childId) => {
669
+ owner.set(node.id, childId);
670
+ node.ports?.forEach((port) => owner.set(port.id, childId));
671
+ node.children?.forEach((descendant) => own(descendant, childId));
672
+ };
673
+ children.forEach((child) => own(child, child.id));
674
+ const localEdges = edges.filter((edge) => owner.has(endpoint$1(edge, true)) && owner.has(endpoint$1(edge, false)));
675
+ const successors = new Map(children.map((node) => [node.id, /* @__PURE__ */ new Set()]));
676
+ const predecessors = new Map(children.map((node) => [node.id, /* @__PURE__ */ new Set()]));
677
+ for (const edge of localEdges) {
678
+ const source = owner.get(endpoint$1(edge, true));
679
+ const target = owner.get(endpoint$1(edge, false));
680
+ if (source === target) continue;
681
+ successors.get(source).add(target);
682
+ predecessors.get(target).add(source);
683
+ }
684
+ const exits = children.filter((node) => successors.get(node.id).size === 0 && predecessors.get(node.id).size >= 2).map((node) => node.id);
685
+ commonExits[id] = exits;
686
+ const path = [...scope.preferredPath ?? (scope.initialNodeId === void 0 ? [] : [scope.initialNodeId])];
687
+ if (new Set(path).size !== path.length || path.some((nodeId) => !ids.has(nodeId))) throw new Error(`Path must contain distinct direct children of ${id}`);
688
+ if (scope.initialNodeId !== void 0 && (!ids.has(scope.initialNodeId) || path[0] !== scope.initialNodeId)) throw new Error(`Path must start at initial child of ${id}`);
689
+ if (scope.preferredPath) {
690
+ for (let i = 1; i < path.length; i++) if (!successors.get(path[i - 1])?.has(path[i])) throw new Error(`Disconnected preferred path in ${id}`);
691
+ } else while (path.length) {
692
+ const next = [...successors.get(path.at(-1))].filter((nodeId) => !path.includes(nodeId) && !exits.includes(nodeId));
693
+ if (next.length !== 1) break;
694
+ path.push(next[0]);
695
+ }
696
+ paths[id] = path;
697
+ container.children = [
698
+ ...path,
699
+ ...children.map((node) => node.id).filter((nodeId) => !path.includes(nodeId) && !exits.includes(nodeId)),
700
+ ...exits.filter((nodeId) => !path.includes(nodeId))
701
+ ].map((nodeId) => children.find((node) => node.id === nodeId));
702
+ container.layoutOptions = {
703
+ ...path.length > 1 && option(container, "layered.cycleBreaking.strategy") === void 0 ? { "elk.layered.cycleBreaking.strategy": "MODEL_ORDER" } : {},
704
+ ...path.length > 1 && option(container, "layered.feedbackEdges") === void 0 ? { "elk.layered.feedbackEdges": true } : {},
705
+ ...container.layoutOptions,
706
+ ...scope.direction ? { direction: scope.direction } : {}
707
+ };
708
+ }
709
+ return {
710
+ graph,
711
+ paths,
712
+ commonExits
713
+ };
714
+ }
715
+ const intersects = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
716
+ const cross = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
717
+ /** Diagnostic objective, not a proof of geometric validity. Coordinates are container-relative. */
718
+ function scoreStatechartLayout(graph, paths = {}) {
719
+ const score = {
720
+ invalid: 0,
721
+ overlaps: 0,
722
+ pathOrder: 0,
723
+ crossings: 0,
724
+ bends: 0,
725
+ routeLength: 0
726
+ };
727
+ const segments = [];
728
+ const boxes = [];
729
+ const rect = (shape, x, y) => {
730
+ if ([
731
+ shape.x,
732
+ shape.y,
733
+ shape.width,
734
+ shape.height
735
+ ].some((value) => value === void 0 || !Number.isFinite(value)) || (shape.width ?? 0) < 0 || (shape.height ?? 0) < 0) score.invalid++;
736
+ return {
737
+ x: x + (shape.x ?? 0),
738
+ y: y + (shape.y ?? 0),
739
+ width: shape.width ?? 0,
740
+ height: shape.height ?? 0
741
+ };
742
+ };
743
+ const visit = (node, x, y, ancestors, inheritedDirection = "RIGHT") => {
744
+ const direction = String(option(node, "direction") ?? inheritedDirection).toUpperCase();
745
+ const horizontal$1 = direction === "RIGHT" || direction === "LEFT";
746
+ const sign = direction === "LEFT" || direction === "UP" ? -1 : 1;
747
+ const path = paths[String(node.id)] ?? [];
748
+ for (let i = 1; i < path.length; i++) {
749
+ const a = node.children?.find((child) => child.id === path[i - 1]);
750
+ const b = node.children?.find((child) => child.id === path[i]);
751
+ if (!a || !b || sign * (horizontal$1 ? b.x - a.x : b.y - a.y) <= 0) score.pathOrder++;
752
+ }
753
+ for (const child of node.children ?? []) {
754
+ if (String(option(child, "noLayout")).toLowerCase() === "true") continue;
755
+ const box = rect(child, x, y);
756
+ if ((child.x ?? 0) < 0 || (child.y ?? 0) < 0 || (child.x ?? 0) + (child.width ?? 0) > (node.width ?? Infinity) + .001 || (child.y ?? 0) + (child.height ?? 0) > (node.height ?? Infinity) + .001) score.invalid++;
757
+ boxes.push({
758
+ rect: box,
759
+ ancestors,
760
+ node: child
761
+ });
762
+ visit(child, box.x, box.y, [...ancestors, child], direction);
763
+ }
764
+ for (const edge of node.edges ?? []) {
765
+ if (String(option(edge, "noLayout")).toLowerCase() === "true") continue;
766
+ if (!edge.sections?.length) score.invalid++;
767
+ for (const label of edge.labels ?? []) {
768
+ if (String(option(label, "noLayout")).toLowerCase() === "true") continue;
769
+ boxes.push({
770
+ rect: rect(label, x, y),
771
+ ancestors
772
+ });
773
+ }
774
+ for (const section of edge.sections ?? []) {
775
+ const points = [
776
+ section.startPoint,
777
+ ...section.bendPoints ?? [],
778
+ section.endPoint
779
+ ];
780
+ score.bends += section.bendPoints?.length ?? 0;
781
+ for (const point of points) if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) score.invalid++;
782
+ for (let i = 1; i < points.length; i++) {
783
+ const a = {
784
+ x: x + points[i - 1].x,
785
+ y: y + points[i - 1].y
786
+ };
787
+ const b = {
788
+ x: x + points[i].x,
789
+ y: y + points[i].y
790
+ };
791
+ score.routeLength += Math.hypot(b.x - a.x, b.y - a.y);
792
+ segments.push({
793
+ a,
794
+ b,
795
+ edge
796
+ });
797
+ }
798
+ }
799
+ }
800
+ };
801
+ visit(graph, 0, 0, [graph]);
802
+ for (let i = 0; i < boxes.length; i++) for (let j = i + 1; j < boxes.length; j++) {
803
+ const a = boxes[i], b = boxes[j];
804
+ if (a.node && b.ancestors.includes(a.node) || b.node && a.ancestors.includes(b.node)) continue;
805
+ if (intersects(a.rect, b.rect)) score.overlaps++;
806
+ }
807
+ for (let i = 0; i < segments.length; i++) for (let j = i + 1; j < segments.length; j++) {
808
+ const a = segments[i], b = segments[j];
809
+ if (a.edge === b.edge) continue;
810
+ if (cross(a.a, a.b, b.a) * cross(a.a, a.b, b.b) < 0 && cross(b.a, b.b, a.a) * cross(b.a, b.b, a.b) < 0) score.crossings++;
811
+ }
812
+ if (!Number.isFinite(score.routeLength)) score.routeLength = Number.MAX_VALUE;
813
+ return score;
814
+ }
815
+ const objectives = [
816
+ "invalid",
817
+ "overlaps",
818
+ "pathOrder",
819
+ "crossings",
820
+ "bends",
821
+ "routeLength"
822
+ ];
823
+ function better(a, b) {
824
+ for (const key of objectives) if (a[key] !== b[key]) return a[key] < b[key];
825
+ return false;
826
+ }
827
+ /** Each attempt starts from original input. No post-layout geometry edits. */
828
+ async function layoutStatechart(input, options) {
829
+ const maxAttempts = options.maxAttempts ?? 3;
830
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3) throw new RangeError("maxAttempts must be an integer from 1 to 3");
831
+ const plan = compileStatechartLayout(input, options.scopes);
832
+ const attempts = [];
833
+ let selected;
834
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
835
+ const candidate = structuredClone(attempt === 0 ? input : plan.graph);
836
+ const configure = (node) => {
837
+ const scope = Object.hasOwn(options.scopes, String(node.id)) ? options.scopes[String(node.id)] : void 0;
838
+ if (scope?.direction) node.layoutOptions = {
839
+ ...node.layoutOptions,
840
+ direction: scope.direction
841
+ };
842
+ if (attempt === 2 && scope) node.layoutOptions = {
843
+ ...node.layoutOptions,
844
+ "spacing.nodeNode": Math.max(40, Number(option(node, "spacing.nodeNode") ?? 20) * 1.5),
845
+ "layered.spacing.nodeNodeBetweenLayers": Math.max(60, Number(option(node, "layered.spacing.nodeNodeBetweenLayers") ?? 20) * 1.5)
846
+ };
847
+ if (attempt === 2 && scope) {
848
+ const groups = /* @__PURE__ */ new Map();
849
+ for (const edge of node.edges ?? []) {
850
+ if (!edge.labels?.length) continue;
851
+ const key = JSON.stringify([endpoint$1(edge, true), endpoint$1(edge, false)]);
852
+ const group = groups.get(key) ?? [];
853
+ group.push(edge);
854
+ groups.set(key, group);
855
+ }
856
+ for (const group of groups.values()) for (let i = 1; i < Math.min(group.length, 3); i++) {
857
+ const edge = group[i];
858
+ if (option(edge, "edgeLabels.placement") === void 0 && edge.labels.every((label) => option(label, "edgeLabels.placement") === void 0)) {
859
+ const placement = i === 1 ? "HEAD" : "TAIL";
860
+ edge.layoutOptions = {
861
+ ...edge.layoutOptions,
862
+ "elk.edgeLabels.placement": placement
863
+ };
864
+ edge.labels = edge.labels.map((label) => ({
865
+ ...label,
866
+ layoutOptions: {
867
+ ...label.layoutOptions,
868
+ "elk.edgeLabels.placement": placement
869
+ }
870
+ }));
871
+ }
872
+ }
873
+ }
874
+ node.children?.forEach(configure);
875
+ };
876
+ configure(candidate);
877
+ const graph = await new ELK().layout(candidate);
878
+ const score = scoreStatechartLayout(graph, plan.paths);
879
+ attempts.push({
880
+ kind: attempt === 0 ? "baseline" : attempt === 1 ? "policy" : "spacing",
881
+ score
882
+ });
883
+ if (!selected || better(score, selected.score)) selected = {
884
+ graph,
885
+ score,
886
+ attempt
887
+ };
888
+ }
889
+ return {
890
+ ...selected,
891
+ attempts,
892
+ paths: plan.paths,
893
+ commonExits: plan.commonExits
894
+ };
895
+ }
896
+
897
+ //#endregion
898
+ //#region src/elkjs/routes.ts
899
+ /** Normalize any ELK layout's section geometry, preserving ports and hyperedge junctions. */
900
+ function getElkRoutes(graph, options = {}) {
901
+ const shapes = /* @__PURE__ */ new Map(), frames = /* @__PURE__ */ new Map();
902
+ const edges = [];
903
+ function visit(node, offset, inherited) {
904
+ const position = {
905
+ x: offset.x + (node.x ?? 0),
906
+ y: offset.y + (node.y ?? 0)
907
+ };
908
+ const routing = node.layoutOptions?.["elk.edgeRouting"] ?? node.layoutOptions?.["org.eclipse.elk.edgeRouting"] ?? inherited;
909
+ frames.set(node.id, position);
910
+ shapes.set(node.id, {
911
+ ref: {
912
+ kind: "node",
913
+ nodeId: node.id
914
+ },
915
+ center: {
916
+ x: position.x + (node.width ?? 0) / 2,
917
+ y: position.y + (node.height ?? 0) / 2
918
+ }
919
+ });
920
+ for (const port of node.ports ?? []) shapes.set(port.id, {
921
+ ref: {
922
+ kind: "node",
923
+ nodeId: node.id,
924
+ port: port.id
925
+ },
926
+ center: {
927
+ x: position.x + (port.x ?? 0) + (port.width ?? 0) / 2,
928
+ y: position.y + (port.y ?? 0) + (port.height ?? 0) / 2
929
+ }
930
+ });
931
+ for (const edge of node.edges ?? []) edges.push({
932
+ edge,
933
+ offset: position,
934
+ routing: edge.layoutOptions?.["elk.edgeRouting"] ?? edge.layoutOptions?.["org.eclipse.elk.edgeRouting"] ?? routing
935
+ });
936
+ for (const child of node.children ?? []) visit(child, position, routing);
937
+ }
938
+ visit(graph, {
939
+ x: 0,
940
+ y: 0
941
+ }, options.routing ?? "POLYLINE");
942
+ const result = /* @__PURE__ */ new Map();
943
+ for (const { edge, offset: inheritedOffset, routing } of edges) {
944
+ const offset = edge.container ? frames.get(edge.container) ?? inheritedOffset : inheritedOffset;
945
+ const point = (p) => ({
946
+ x: p.x + offset.x,
947
+ y: p.y + offset.y
948
+ });
949
+ const parents = /* @__PURE__ */ new Map();
950
+ const root = (key) => {
951
+ let current = key;
952
+ while (parents.has(current)) current = parents.get(current);
953
+ return current;
954
+ };
955
+ const join = (a, b) => {
956
+ const x = root(a), y = root(b);
957
+ if (x !== y) parents.set(x < y ? y : x, x < y ? x : y);
958
+ };
959
+ for (const section of edge.sections ?? []) {
960
+ for (const id of section.outgoingSections ?? []) join(`${section.id}:end`, `${id}:start`);
961
+ for (const id of section.incomingSections ?? []) join(`${id}:end`, `${section.id}:start`);
962
+ }
963
+ const sections = [];
964
+ let malformed = false;
965
+ for (const section of edge.sections ?? []) {
966
+ const points = [
967
+ section.startPoint,
968
+ ...section.bendPoints ?? [],
969
+ section.endPoint
970
+ ].map(point);
971
+ let path;
972
+ if (points.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) {
973
+ malformed = true;
974
+ for (let i = 0; i < points.length; i++) if (!Number.isFinite(points[i].x) || !Number.isFinite(points[i].y)) points[i] = {
975
+ x: offset.x + i * 20,
976
+ y: offset.y
977
+ };
978
+ }
979
+ if (routing === "SPLINES") if ((points.length - 1) % 3 || points.length < 4) {
980
+ malformed = true;
981
+ path = require_routing.pathFromPoints([points[0], points.at(-1)]);
982
+ } else path = require_routing.pathFromSplinePoints(points);
983
+ else path = require_routing.pathFromPoints(points);
984
+ const fromShape = section.incomingShape ?? (!section.incomingSections?.length && edge.sources.length === 1 ? edge.sources[0] : void 0);
985
+ const toShape = section.outgoingShape ?? (!section.outgoingSections?.length && edge.targets.length === 1 ? edge.targets[0] : void 0);
986
+ sections.push({
987
+ id: section.id,
988
+ from: fromShape ? shapes.get(fromShape)?.ref ?? {
989
+ kind: "node",
990
+ nodeId: fromShape
991
+ } : {
992
+ kind: "junction",
993
+ id: `${edge.id}:${root(`${section.id}:start`)}`
994
+ },
995
+ to: toShape ? shapes.get(toShape)?.ref ?? {
996
+ kind: "node",
997
+ nodeId: toShape
998
+ } : {
999
+ kind: "junction",
1000
+ id: `${edge.id}:${root(`${section.id}:end`)}`
1001
+ },
1002
+ path
1003
+ });
1004
+ }
1005
+ if (!sections.length) {
1006
+ malformed = true;
1007
+ const source = shapes.get(edge.sources[0] ?? ""), origin = source?.center ?? {
1008
+ x: 0,
1009
+ y: 0
1010
+ };
1011
+ const targets = [...edge.targets, ...edge.sources.slice(1)];
1012
+ for (const [i, id] of (targets.length ? targets : [""]).entries()) {
1013
+ const target = shapes.get(id);
1014
+ sections.push({
1015
+ id: `${edge.id}:fallback:${i}`,
1016
+ from: source?.ref ?? {
1017
+ kind: "node",
1018
+ nodeId: edge.sources[0] ?? ""
1019
+ },
1020
+ to: target?.ref ?? {
1021
+ kind: "node",
1022
+ nodeId: id
1023
+ },
1024
+ path: require_routing.pathFromPoints([origin, target?.center ?? {
1025
+ x: origin.x + 40,
1026
+ y: origin.y + 40
1027
+ }])
1028
+ });
1029
+ }
1030
+ }
1031
+ result.set(edge.id, require_routing.freeze({
1032
+ edgeId: edge.id,
1033
+ sections,
1034
+ status: malformed ? "fallback" : "routed",
1035
+ diagnostics: malformed ? [{
1036
+ code: "MISSING_GEOMETRY",
1037
+ edgeId: edge.id,
1038
+ message: "ELK sections contain missing or invalid geometry; rendered fallback paths"
1039
+ }] : []
1040
+ }));
1041
+ }
1042
+ return result;
1043
+ }
1044
+
1045
+ //#endregion
1046
+ //#region src/elkjs/index.ts
1047
+ function isLayoutEdgeLabel(label) {
1048
+ return getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true && (Boolean(label.text) || label.width !== void 0 || label.height !== void 0);
1049
+ }
1050
+ /** The crossing-model setting a layout passes to crossing minimization. */
1051
+ const crossingModel = Symbol("crossingModel");
1052
+ var ELK = class {
1053
+ #options;
1054
+ #algorithmIds;
1055
+ constructor(options = {}) {
1056
+ this.#options = options;
1057
+ this.#algorithmIds = new Set([
1058
+ "box",
1059
+ "fixed",
1060
+ "random",
1061
+ "rectpacking",
1062
+ "sporeCompaction",
1063
+ "sporeOverlap",
1064
+ ...(options.algorithms ?? ["layered"]).filter((id) => id === "layered")
1065
+ ]);
1066
+ }
1067
+ async knownLayoutAlgorithms() {
1068
+ return [...this.#algorithmIds].map((id) => ({
1069
+ id: id === "layered" ? "org.eclipse.elk.layered" : id,
1070
+ name: {
1071
+ layered: "Layered",
1072
+ box: "Box",
1073
+ fixed: "Fixed",
1074
+ random: "Random",
1075
+ rectpacking: "Rectangle Packing",
1076
+ sporeCompaction: "SPOrE Compaction",
1077
+ sporeOverlap: "SPOrE Overlap Removal"
1078
+ }[id],
1079
+ category: id === "layered" ? "layered" : "other",
1080
+ knownOptions: id === "layered" ? require_layered.elkLayeredOptionDefinitions.map((definition) => definition.elkId) : id === "box" ? [
1081
+ "padding",
1082
+ "spacing.nodeNode",
1083
+ "aspectRatio",
1084
+ "box.packingMode"
1085
+ ] : id === "random" ? [
1086
+ "padding",
1087
+ "spacing.nodeNode",
1088
+ "aspectRatio",
1089
+ "randomSeed"
1090
+ ] : ["position", "bendPoints"]
1091
+ }));
1092
+ }
1093
+ async knownLayoutOptions() {
1094
+ return [{
1095
+ id: "org.eclipse.elk.algorithm",
1096
+ name: "Layout Algorithm",
1097
+ type: "STRING"
1098
+ }, ...require_layered.elkLayeredOptionDefinitions.map((definition) => ({
1099
+ id: definition.elkId,
1100
+ name: definition.name,
1101
+ type: definition.type,
1102
+ targets: [...definition.targets]
1103
+ }))];
1104
+ }
1105
+ async knownLayoutCategories() {
1106
+ return [{
1107
+ id: "layered",
1108
+ name: "Layered",
1109
+ knownLayouters: this.#algorithmIds.has("layered") ? ["layered"] : []
1110
+ }, {
1111
+ id: "other",
1112
+ name: "Other",
1113
+ knownLayouters: [
1114
+ "box",
1115
+ "fixed",
1116
+ "random"
1117
+ ]
1118
+ }];
1119
+ }
1120
+ terminateWorker() {}
1121
+ async layout(graph, arguments_ = {}) {
1122
+ const options = {
1123
+ ...this.#options.defaultLayoutOptions,
1124
+ ...arguments_.layoutOptions
1125
+ };
1126
+ const seeds = crossingVariants(options, graph);
1127
+ const probe = mayShareTracks(graph, options) ? {
1128
+ mixedPorts: false,
1129
+ sharedTracks: false
1130
+ } : void 0;
1131
+ const pristine = seeds.length || probe ? structuredClone(graph) : void 0;
1132
+ let best = separated(await this.#layoutWithoutDefectiveCompaction(graph, probe ? {
1133
+ ...arguments_,
1134
+ [crossingModel]: { probe }
1135
+ } : arguments_), options);
1136
+ if (!pristine) return best;
1137
+ const reseed = seeds.length > 0 && (thorough(options, graph) || measureLayout(best, true).defects > 0);
1138
+ const variants = [
1139
+ ...probe?.mixedPorts ? [{
1140
+ ...arguments_,
1141
+ [crossingModel]: { mixedPorts: false }
1142
+ }] : [],
1143
+ ...probe?.sharedTracks ? [{
1144
+ ...arguments_,
1145
+ [crossingModel]: {
1146
+ mixedPorts: false,
1147
+ separateSharedTracks: true
1148
+ }
1149
+ }] : [],
1150
+ ...(reseed ? seeds : []).map((variant) => ({
1151
+ ...arguments_,
1152
+ layoutOptions: {
1153
+ ...arguments_.layoutOptions,
1154
+ ...variant
1155
+ }
1156
+ }))
1157
+ ];
1158
+ if (!variants.length) return best;
1159
+ let quality = measureLayout(best);
1160
+ for (const variant of variants) {
1161
+ const candidate = separated(await this.#layoutWithoutDefectiveCompaction(structuredClone(pristine), variant), options);
1162
+ const candidateQuality = measureLayout(candidate);
1163
+ if (isBetterLayout(candidateQuality, quality)) {
1164
+ best = candidate;
1165
+ quality = candidateQuality;
1166
+ }
1167
+ }
1168
+ return best;
1169
+ }
1170
+ async #layoutWithoutDefectiveCompaction(graph, arguments_) {
1171
+ const pristine = usesPostCompaction({
1172
+ ...this.#options.defaultLayoutOptions,
1173
+ ...arguments_.layoutOptions
1174
+ }, graph) ? structuredClone(graph) : void 0;
1175
+ const result = await this.#layout(graph, arguments_);
1176
+ const defects = pristine ? measureLayout(result, true).defects : 0;
1177
+ if (!pristine || defects === 0) return result;
1178
+ const strategy = { "elk.layered.compaction.postCompaction.strategy": "NONE" };
1179
+ const uncompacted = await this.#layout(disablePostCompaction(pristine), {
1180
+ ...arguments_,
1181
+ layoutOptions: {
1182
+ ...arguments_.layoutOptions,
1183
+ ...strategy
1184
+ }
1185
+ });
1186
+ return measureLayout(uncompacted, true).defects < defects ? uncompacted : result;
1187
+ }
1188
+ async #layout(graph, arguments_ = {}, compoundLayout = false) {
1189
+ const prepared = await this.#prepareLayout(graph, arguments_, compoundLayout);
1190
+ const orders = coordinatePreparedScopes(prepared);
1191
+ return await prepared.finish(orders);
1192
+ }
1193
+ async #prepareLayout(graph, arguments_ = {}, compoundLayout = false) {
1194
+ const startedAt = performance.now();
1195
+ if (graph === void 0 || graph === null) throw new TypeError("Missing mandatory parameter: graph");
1196
+ if (typeof graph.id !== "string" && !(typeof graph.id === "number" && Number.isInteger(graph.id))) throw new TypeError("Graph id must be a string or integer");
1197
+ delete graph.logging;
1198
+ const layoutOptions = {
1199
+ ...this.#options.defaultLayoutOptions,
1200
+ ...arguments_.layoutOptions,
1201
+ ...graph.properties,
1202
+ ...graph.layoutOptions
1203
+ };
1204
+ getOption(layoutOptions, "debugMode");
1205
+ getOption(layoutOptions, "interactiveLayout");
1206
+ getOption(layoutOptions, "layered.generatePositionAndLayerIds");
1207
+ getOption(layoutOptions, "topdown.scaleFactor");
1208
+ getOption(layoutOptions, "contentAlignment");
1209
+ for (const child of graph.children ?? []) {
1210
+ const childOptions = child.layoutOptions ?? {};
1211
+ getOption(childOptions, "layered.layering.layerId");
1212
+ getOption(childOptions, "layered.crossingMinimization.positionId");
1213
+ getOption(childOptions, "layered.layering.layerChoiceConstraint");
1214
+ getOption(childOptions, "layered.crossingMinimization.positionChoiceConstraint");
1215
+ getOption(childOptions, "layered.crossingMinimization.inLayerPredOf");
1216
+ getOption(childOptions, "layered.crossingMinimization.inLayerSuccOf");
1217
+ getOption(childOptions, "topdown.scaleFactor");
1218
+ getOption(childOptions, "layered.considerModelOrder.groupModelOrder.componentGroupId");
1219
+ for (const port of child.ports ?? []) getOption(port.layoutOptions ?? {}, "layered.considerModelOrder.groupModelOrder.componentGroupId");
1220
+ }
1221
+ for (const edge of graph.edges ?? []) getOption(edge.layoutOptions ?? {}, "layered.considerModelOrder.groupModelOrder.componentGroupId");
1222
+ const requestedAlgorithm = String(getOption(layoutOptions, "algorithm") ?? "layered");
1223
+ const algorithm = requestedAlgorithm.replace(/^(?:org\.eclipse\.)?elk\./, "");
1224
+ if (algorithm !== "layered" && algorithm !== "box" && algorithm !== "fixed" && algorithm !== "random" && algorithm !== "rectpacking" && algorithm !== "sporeCompaction" && algorithm !== "sporeOverlap") throw new Error(`org.eclipse.elk.core.UnsupportedConfigurationException: Layout algorithm '${requestedAlgorithm}' not found`);
1225
+ const preparedChildren = /* @__PURE__ */ new Map();
1226
+ const finishChildren = [];
1227
+ const hasHierarchy = (graph.children ?? []).some((child) => (child.children?.length ?? 0) > 0);
1228
+ const insideSelfLoopBaseHeightByNodeId = /* @__PURE__ */ new Map();
1229
+ const hierarchyRestorations = [];
1230
+ const boundaryRoutes = /* @__PURE__ */ new Map();
1231
+ const syntheticPortIds = /* @__PURE__ */ new Set();
1232
+ const authoredPortsByCompound = /* @__PURE__ */ new Map();
1233
+ const authoredOptionsByCompound = /* @__PURE__ */ new Map();
1234
+ const originalHierarchyEndpoints = new Map((graph.edges ?? []).map((edge) => [edge, {
1235
+ sourceId: String(edge.sources?.[0] ?? edge.source),
1236
+ targetId: String(edge.targets?.[0] ?? edge.target),
1237
+ sources: edge.sources,
1238
+ targets: edge.targets,
1239
+ source: edge.source,
1240
+ target: edge.target
1241
+ }]));
1242
+ const hierarchyHandling = getOption(layoutOptions, "hierarchyHandling");
1243
+ const topdownLayout = getBooleanOption(layoutOptions, "topdownLayout") === true;
1244
+ const separateHierarchy = hasHierarchy && hierarchyHandling !== void 0 && hierarchyHandling !== "INCLUDE_CHILDREN";
1245
+ if (hasHierarchy && topdownLayout && hierarchyHandling === "INCLUDE_CHILDREN") throw new Error("org.eclipse.elk.core.UnsupportedConfigurationException: Topdown layout cannot be used together with hierarchy handling.");
1246
+ if (separateHierarchy) {
1247
+ const hasCrossHierarchyEdge = (container) => {
1248
+ const directEndpointIds = /* @__PURE__ */ new Set();
1249
+ for (const child of container.children ?? []) {
1250
+ directEndpointIds.add(String(child.id));
1251
+ for (const port of child.ports ?? []) directEndpointIds.add(String(port.id));
1252
+ }
1253
+ if ((container.edges ?? []).some((edge) => {
1254
+ const sourceId = String(edge.sources?.[0] ?? edge.source);
1255
+ const targetId = String(edge.targets?.[0] ?? edge.target);
1256
+ return !directEndpointIds.has(sourceId) || !directEndpointIds.has(targetId);
1257
+ })) return true;
1258
+ return (container.children ?? []).some((child) => hasCrossHierarchyEdge(child));
1259
+ };
1260
+ if (hasCrossHierarchyEdge(graph)) throw new Error("org.eclipse.elk.core.UnsupportedGraphException: Hierarchical edges require INCLUDE_CHILDREN");
1261
+ for (const child of graph.children ?? []) {
1262
+ if ((child.children?.length ?? 0) === 0) continue;
1263
+ await this.#layout(child, {
1264
+ ...arguments_,
1265
+ layoutOptions: {
1266
+ ...arguments_.layoutOptions,
1267
+ ...child.layoutOptions,
1268
+ hierarchyHandling
1269
+ },
1270
+ logging: false,
1271
+ measureExecutionTime: false
1272
+ });
1273
+ }
1274
+ }
1275
+ if (hasHierarchy && topdownLayout) {
1276
+ if (getOption(layoutOptions, "topdown.nodeType") === void 0) throw new Error(`${String(graph.id)} has not been assigned a top-down node type.`);
1277
+ for (const child of graph.children ?? []) {
1278
+ if ((child.children?.length ?? 0) === 0) continue;
1279
+ const childOptions = {
1280
+ ...layoutOptions,
1281
+ ...child.layoutOptions
1282
+ };
1283
+ const childPadding = parsePadding(getOption(childOptions, "padding"), 12);
1284
+ const parallelNode = getOption(childOptions, "topdown.nodeType") === "PARALLEL_NODE";
1285
+ const width = getNumberOption(childOptions, "topdown.hierarchicalNodeWidth") ?? (parallelNode ? 150 : 0);
1286
+ const aspectRatio = getNumberOption(childOptions, "topdown.hierarchicalNodeAspectRatio") ?? (parallelNode ? 1.414 : 1 / Math.sqrt(2));
1287
+ child.width = Math.max(child.width ?? 0, width + childPadding.left + childPadding.right);
1288
+ child.height = Math.max(child.height ?? 0, width / aspectRatio + childPadding.top + childPadding.bottom);
1289
+ }
1290
+ } else if (hasHierarchy && !separateHierarchy) {
1291
+ const scopeSegmentOrder = segmentOrderByScope.get(graph) ?? elkSegmentOrder(graph, graph, originalHierarchyEndpoints, false);
1292
+ for (const child of graph.children ?? []) {
1293
+ if ((child.children?.length ?? 0) === 0) continue;
1294
+ const descendantOwnerByEndpointId = /* @__PURE__ */ new Map();
1295
+ const collectDescendants = (node) => {
1296
+ for (const descendant of node.children ?? []) {
1297
+ descendantOwnerByEndpointId.set(String(descendant.id), descendant);
1298
+ for (const port of descendant.ports ?? []) descendantOwnerByEndpointId.set(String(port.id), descendant);
1299
+ collectDescendants(descendant);
1300
+ }
1301
+ };
1302
+ collectDescendants(child);
1303
+ const internalEdges = (graph.edges ?? []).filter((edge) => {
1304
+ const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
1305
+ return descendantOwnerByEndpointId.has(sourceId) && descendantOwnerByEndpointId.has(targetId);
1306
+ });
1307
+ for (const edge of child.edges ?? []) if (!originalHierarchyEndpoints.has(edge)) originalHierarchyEndpoints.set(edge, {
1308
+ sourceId: String(edge.sources?.[0] ?? edge.source),
1309
+ targetId: String(edge.targets?.[0] ?? edge.target),
1310
+ sources: edge.sources,
1311
+ targets: edge.targets,
1312
+ source: edge.source,
1313
+ target: edge.target
1314
+ });
1315
+ const crossingEdges = [...graph.edges ?? [], ...child.edges ?? []].filter((edge) => {
1316
+ const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
1317
+ return descendantOwnerByEndpointId.has(sourceId) !== descendantOwnerByEndpointId.has(targetId);
1318
+ });
1319
+ const boundaryRank = new Map(elkBoundaryEdgeOrder(child, graph, originalHierarchyEndpoints).map((edge, index) => [edge, index]));
1320
+ crossingEdges.sort((a, b) => (boundaryRank.get(a) ?? Number.MAX_SAFE_INTEGER) - (boundaryRank.get(b) ?? Number.MAX_SAFE_INTEGER));
1321
+ const mergeHierarchyEdges = getBooleanOption(layoutOptions, "layered.mergeHierarchyEdges") !== false;
1322
+ const proxyByKind = /* @__PURE__ */ new Map();
1323
+ const implicitOwnPorts = /* @__PURE__ */ new Map();
1324
+ const ownPortFor = (edge) => {
1325
+ const e = originalHierarchyEndpoints.get(edge);
1326
+ const outside = descendantOwnerByEndpointId.has(e.sourceId) ? e.targetId : e.sourceId;
1327
+ if (e.sourceId === String(child.id) || e.targetId === String(child.id)) {
1328
+ let port = implicitOwnPorts.get(edge);
1329
+ if (!port) {
1330
+ const direction = getDirection(layoutOptions);
1331
+ const side = e.sourceId === String(child.id) ? {
1332
+ right: "EAST",
1333
+ left: "WEST",
1334
+ down: "SOUTH",
1335
+ up: "NORTH"
1336
+ }[direction] : {
1337
+ right: "WEST",
1338
+ left: "EAST",
1339
+ down: "NORTH",
1340
+ up: "SOUTH"
1341
+ }[direction];
1342
+ port = {
1343
+ id: `__native_hierarchy_own_${String(child.id)}_${implicitOwnPorts.size}`,
1344
+ width: 0,
1345
+ height: 0,
1346
+ layoutOptions: { "elk.port.side": side }
1347
+ };
1348
+ implicitOwnPorts.set(edge, port);
1349
+ syntheticPortIds.add(String(port.id));
1350
+ }
1351
+ return child.ports?.find((p) => p.id === port.id) ?? port;
1352
+ }
1353
+ return child.ports?.find((p) => String(p.id) === outside);
1354
+ };
1355
+ const proxyKey = (kind, edge) => {
1356
+ const own = ownPortFor(edge);
1357
+ if (own) return JSON.stringify(["authored", own.id]);
1358
+ const endpoints = originalHierarchyEndpoints.get(edge);
1359
+ const endpoint$2 = kind === "output" ? endpoints.sourceId : endpoints.targetId;
1360
+ const explicitPort = descendantOwnerByEndpointId.get(endpoint$2)?.ports?.some((port) => String(port.id) === endpoint$2);
1361
+ return JSON.stringify([kind, mergeHierarchyEdges && explicitPort ? endpoint$2 : String(edge.id)]);
1362
+ };
1363
+ const createBoundaryProxy = (key, kind, own, netFlow) => {
1364
+ let proxy = proxyByKind.get(key);
1365
+ if (!proxy) {
1366
+ const direction = getDirection({
1367
+ ...layoutOptions,
1368
+ ...child.layoutOptions
1369
+ });
1370
+ const authoredAnchor = own ? getElementLayeredSettings(own.layoutOptions ?? {})["port.anchor"] : void 0;
1371
+ const origin = require_layered.createExternalPortDummy({
1372
+ constraints: getOption(child.layoutOptions ?? {}, "portConstraints") ?? "FREE",
1373
+ side: own ? getOption(own.layoutOptions ?? {}, "port.side") ?? (kind === "output" ? "EAST" : "WEST") : kind === "output" ? "EAST" : "WEST",
1374
+ position: {
1375
+ x: own?.x ?? 0,
1376
+ y: own?.y ?? 0
1377
+ },
1378
+ anchor: authoredAnchor,
1379
+ index: own ? getNumberOption(own.layoutOptions ?? {}, "port.index") : void 0,
1380
+ direction: direction.toUpperCase(),
1381
+ netFlow,
1382
+ borderOffset: own ? getNumberOption(own.layoutOptions ?? {}, "port.borderOffset") ?? 0 : (getNumberOption(layoutOptions, "spacing.edgeEdge") ?? 10) / 2,
1383
+ size: {
1384
+ width: own?.width ?? 0,
1385
+ height: own?.height ?? 0
1386
+ }
1387
+ });
1388
+ if (own) origin.parentPortId = String(own.id);
1389
+ const id = `__native_hierarchy_${String(child.id)}_${proxyByKind.size}`;
1390
+ proxy = require_layered.attachExternalPortDummy({
1391
+ id,
1392
+ width: origin.width,
1393
+ height: origin.height,
1394
+ layoutOptions: {
1395
+ "elk.portConstraints": origin.constraints,
1396
+ "elk.layered.layering.layerConstraint": origin.side === {
1397
+ right: "EAST",
1398
+ left: "WEST",
1399
+ down: "SOUTH",
1400
+ up: "NORTH"
1401
+ }[direction] ? "LAST_SEPARATE" : origin.side === {
1402
+ right: "WEST",
1403
+ left: "EAST",
1404
+ down: "NORTH",
1405
+ up: "SOUTH"
1406
+ }[direction] ? "FIRST_SEPARATE" : void 0
1407
+ },
1408
+ ports: [{
1409
+ id: `${id}:boundary`,
1410
+ x: origin.port.x,
1411
+ y: origin.port.y,
1412
+ width: 0,
1413
+ height: 0,
1414
+ layoutOptions: { "elk.port.side": origin.port.side }
1415
+ }]
1416
+ }, origin);
1417
+ proxyByKind.set(key, proxy);
1418
+ }
1419
+ return proxy;
1420
+ };
1421
+ const ownedPortNetFlow = (id) => {
1422
+ let votes = 0;
1423
+ for (const edge of originalHierarchyEndpoints.values()) {
1424
+ if (edge.sourceId === id) votes += descendantOwnerByEndpointId.has(edge.targetId) ? -1 : 1;
1425
+ if (edge.targetId === id) votes += descendantOwnerByEndpointId.has(edge.sourceId) ? 1 : -1;
1426
+ }
1427
+ return votes;
1428
+ };
1429
+ const proxyFor = (kind, edge) => {
1430
+ const own = ownPortFor(edge);
1431
+ const flow = implicitOwnPorts.has(edge) ? kind === "output" ? 1 : -1 : own ? ownedPortNetFlow(String(own.id)) : kind === "output" ? 1 : -1;
1432
+ return createBoundaryProxy(proxyKey(kind, edge), kind, own, flow);
1433
+ };
1434
+ const temporaryEdges = [
1435
+ ...crossingEdges.map((edge) => {
1436
+ const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
1437
+ const sourceInside = descendantOwnerByEndpointId.has(sourceId);
1438
+ const proxy = proxyFor(sourceInside ? "output" : "input", edge);
1439
+ const labels = (graph.edges ?? []).includes(edge) && !ownPortFor(edge) ? edge.labels?.filter((label) => (getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER") !== "CENTER") : edge.labels;
1440
+ const segment = {
1441
+ ...edge,
1442
+ labels,
1443
+ id: `__native_hierarchy_edge_${String(child.id)}_${String(edge.id)}`,
1444
+ sources: [sourceInside ? sourceId : proxy.ports[0].id],
1445
+ targets: [sourceInside ? proxy.ports[0].id : targetId],
1446
+ source: void 0,
1447
+ target: void 0,
1448
+ sections: void 0
1449
+ };
1450
+ originalEdgeId.set(segment, originalEdgeId.get(edge) ?? String(edge.id));
1451
+ return segment;
1452
+ }),
1453
+ ...(child.edges ?? []).filter((edge) => !crossingEdges.includes(edge)),
1454
+ ...internalEdges.filter((edge) => !(child.edges ?? []).some((candidate) => String(candidate.id) === String(edge.id)))
1455
+ ];
1456
+ const hasExternalPorts = proxyByKind.size > 0;
1457
+ const inactiveAuthoredPorts = [];
1458
+ if (hasExternalPorts) for (const port of child.ports ?? []) {
1459
+ const key = JSON.stringify(["authored", port.id]);
1460
+ if (proxyByKind.has(key)) continue;
1461
+ const flow = ownedPortNetFlow(String(port.id));
1462
+ inactiveAuthoredPorts.push({
1463
+ port,
1464
+ proxy: createBoundaryProxy(key, flow >= 0 ? "output" : "input", port, flow)
1465
+ });
1466
+ }
1467
+ const outsideNodeEdges = !hasExternalPorts ? [] : (graph.edges ?? []).filter((edge) => {
1468
+ const e = originalHierarchyEndpoints.get(edge);
1469
+ return (e.sourceId === String(child.id) || e.targetId === String(child.id)) && !descendantOwnerByEndpointId.has(e.sourceId) && !descendantOwnerByEndpointId.has(e.targetId);
1470
+ });
1471
+ for (const edge of outsideNodeEdges) proxyFor(originalHierarchyEndpoints.get(edge).sourceId === String(child.id) ? "output" : "input", edge);
1472
+ const temporaryChild = {
1473
+ ...child,
1474
+ layoutOptions: proxyByKind.size ? {
1475
+ ...child.layoutOptions,
1476
+ "elk.portConstraints": "FIXED_SIDE"
1477
+ } : child.layoutOptions,
1478
+ children: [...child.children ?? [], ...proxyByKind.values()],
1479
+ edges: temporaryEdges
1480
+ };
1481
+ const childSegmentOrder = elkSegmentOrder(child, graph, originalHierarchyEndpoints, true);
1482
+ segmentOrderByScope.set(temporaryChild, childSegmentOrder);
1483
+ for (const edge of temporaryEdges) {
1484
+ const id = originalEdgeId.get(edge);
1485
+ const rank = id === void 0 ? void 0 : childSegmentOrder.get(id);
1486
+ if (rank !== void 0) hierarchySegmentRank.set(edge, rank);
1487
+ }
1488
+ const preparedChild = await this.#prepareLayout(temporaryChild, {
1489
+ ...arguments_,
1490
+ layoutOptions: {
1491
+ ...arguments_.layoutOptions,
1492
+ direction: getDirection(getOption(child.layoutOptions ?? {}, "direction") === void 0 ? layoutOptions : child.layoutOptions).toUpperCase(),
1493
+ hierarchyHandling: "INCLUDE_CHILDREN"
1494
+ },
1495
+ logging: false,
1496
+ measureExecutionTime: false
1497
+ }, true);
1498
+ preparedChildren.set(String(child.id), preparedChild);
1499
+ authoredPortsByCompound.set(child, child.ports);
1500
+ authoredOptionsByCompound.set(child, child.layoutOptions);
1501
+ const initialPorts = (child.ports ?? []).map((port) => ({
1502
+ ...port,
1503
+ layoutOptions: port.layoutOptions ? { ...port.layoutOptions } : void 0
1504
+ }));
1505
+ for (const edge of crossingEdges) {
1506
+ const own = ownPortFor(edge);
1507
+ if (own) {
1508
+ const original$1 = originalHierarchyEndpoints.get(edge);
1509
+ const sourceInside$1 = descendantOwnerByEndpointId.has(original$1.sourceId);
1510
+ const origin$1 = require_layered.externalPortDummyOf(proxyByKind.get(proxyKey(sourceInside$1 ? "output" : "input", edge)));
1511
+ let port = initialPorts.find((p) => p.id === own.id);
1512
+ if (!port) {
1513
+ port = {
1514
+ ...own,
1515
+ layoutOptions: { ...own.layoutOptions }
1516
+ };
1517
+ initialPorts.push(port);
1518
+ }
1519
+ port.layoutOptions = {
1520
+ ...port.layoutOptions,
1521
+ "elk.port.side": origin$1.side
1522
+ };
1523
+ continue;
1524
+ }
1525
+ const original = originalHierarchyEndpoints.get(edge);
1526
+ const sourceInside = descendantOwnerByEndpointId.has(original.sourceId);
1527
+ const proxy = proxyByKind.get(proxyKey(sourceInside ? "output" : "input", edge));
1528
+ const origin = require_layered.externalPortDummyOf(proxy);
1529
+ const portId = `${String(proxy.id)}:parent`;
1530
+ syntheticPortIds.add(portId);
1531
+ if (!initialPorts.some((port) => port.id === portId)) initialPorts.push({
1532
+ id: portId,
1533
+ width: origin.externalSize.width,
1534
+ height: origin.externalSize.height,
1535
+ x: 0,
1536
+ y: 0,
1537
+ layoutOptions: {
1538
+ "elk.port.side": origin.side,
1539
+ "elk.port.borderOffset": origin.borderOffset
1540
+ }
1541
+ });
1542
+ if (!hierarchyRestorations.some((restoration) => restoration.edge === edge)) hierarchyRestorations.push({
1543
+ edge,
1544
+ ...original
1545
+ });
1546
+ if (sourceInside) edge.sources = [portId];
1547
+ else edge.targets = [portId];
1548
+ const rank = scopeSegmentOrder.get(originalEdgeId.get(edge) ?? String(edge.id));
1549
+ if (rank !== void 0) hierarchySegmentRank.set(edge, rank);
1550
+ edge.source = void 0;
1551
+ edge.target = void 0;
1552
+ }
1553
+ for (const { port, proxy } of inactiveAuthoredPorts) {
1554
+ const actual = initialPorts.find((p) => p.id === port.id);
1555
+ actual.layoutOptions = {
1556
+ ...actual.layoutOptions,
1557
+ "elk.port.side": require_layered.externalPortDummyOf(proxy).side
1558
+ };
1559
+ }
1560
+ for (const edge of outsideNodeEdges) {
1561
+ const original = originalHierarchyEndpoints.get(edge);
1562
+ const source = original.sourceId === String(child.id);
1563
+ const proxy = proxyFor(source ? "output" : "input", edge), own = ownPortFor(edge);
1564
+ const origin = require_layered.externalPortDummyOf(proxy);
1565
+ if (!initialPorts.some((p) => p.id === own.id)) initialPorts.push({
1566
+ ...own,
1567
+ layoutOptions: {
1568
+ ...own.layoutOptions,
1569
+ "elk.port.side": origin.side
1570
+ }
1571
+ });
1572
+ if (!hierarchyRestorations.some((r) => r.edge === edge)) hierarchyRestorations.push({
1573
+ edge,
1574
+ ...original
1575
+ });
1576
+ if (source) edge.sources = [own.id];
1577
+ else edge.targets = [own.id];
1578
+ edge.source = void 0;
1579
+ edge.target = void 0;
1580
+ }
1581
+ child.ports = initialPorts;
1582
+ child.layoutOptions = {
1583
+ ...child.layoutOptions,
1584
+ "elk.portConstraints": crossingEdges.length > 0 || outsideNodeEdges.length > 0 ? "FIXED_SIDE" : getOption(authoredOptionsByCompound.get(child) ?? {}, "portConstraints") ?? "FREE"
1585
+ };
1586
+ finishChildren.push(async () => {
1587
+ await preparedChild.finish(activeOrders);
1588
+ const childPadding = parsePadding(getOption({
1589
+ ...layoutOptions,
1590
+ ...child.layoutOptions
1591
+ }, "padding"), 12);
1592
+ for (const internalEdge of internalEdges) {
1593
+ const temporary = temporaryEdges.find((candidate) => String(candidate.id) === String(internalEdge.id));
1594
+ if (temporary?.sections) {
1595
+ internalEdge.sections = temporary.sections;
1596
+ internalEdge.container = temporary.container ?? child.id;
1597
+ }
1598
+ }
1599
+ const childDirection = getDirection({
1600
+ ...layoutOptions,
1601
+ ...child.layoutOptions
1602
+ });
1603
+ const horizontalChild = childDirection === "right" || childDirection === "left";
1604
+ const restoredPerpendicular = [...proxyByKind.values()].some(require_layered.isPerpendicularPortRestored);
1605
+ child.width = restoredPerpendicular ? temporaryChild.width : horizontalChild ? Math.max(0, ...(temporaryChild.children ?? []).map((node) => (node.x ?? 0) + (node.width ?? 0))) + childPadding.right : temporaryChild.width;
1606
+ child.height = restoredPerpendicular ? temporaryChild.height : horizontalChild ? temporaryChild.height : Math.max(0, ...(temporaryChild.children ?? []).map((node) => (node.y ?? 0) + (node.height ?? 0))) + childPadding.bottom;
1607
+ const relativeRect = (id) => {
1608
+ const owner = descendantOwnerByEndpointId.get(id);
1609
+ if (!owner) return void 0;
1610
+ const path = [];
1611
+ const visit = (parent) => {
1612
+ for (const candidate of parent.children ?? []) {
1613
+ path.push(candidate);
1614
+ if (candidate === owner || visit(candidate)) return true;
1615
+ path.pop();
1616
+ }
1617
+ return false;
1618
+ };
1619
+ if (!visit(child)) return void 0;
1620
+ const port = owner.ports?.find((candidate) => String(candidate.id) === id);
1621
+ const ownerX = path.reduce((sum, node) => sum + (node.x ?? 0), 0);
1622
+ const ownerY = path.reduce((sum, node) => sum + (node.y ?? 0), 0);
1623
+ if (port) return {
1624
+ x: ownerX + (port.x ?? 0) + (port.width ?? 0) / 2,
1625
+ y: ownerY + (port.y ?? 0) + (port.height ?? 0) / 2,
1626
+ width: 0,
1627
+ height: 0
1628
+ };
1629
+ return {
1630
+ x: ownerX,
1631
+ y: ownerY,
1632
+ width: path.at(-1)?.width ?? 0,
1633
+ height: path.at(-1)?.height ?? 0
1634
+ };
1635
+ };
1636
+ const ports = (authoredPortsByCompound.get(child) ?? []).map((original) => child.ports?.find((port) => port.id === original.id) ?? original);
1637
+ for (const edge of crossingEdges) {
1638
+ const original = originalHierarchyEndpoints.get(edge);
1639
+ const { sourceId, targetId } = original;
1640
+ const sourceInside = descendantOwnerByEndpointId.has(sourceId);
1641
+ if (!relativeRect(sourceInside ? sourceId : targetId)) continue;
1642
+ const internalEdge = temporaryEdges.find((candidate) => String(candidate.id) === `__native_hierarchy_edge_${String(child.id)}_${String(edge.id)}`);
1643
+ const internalRoute = internalEdge?.sections;
1644
+ const kind = sourceInside ? "output" : "input";
1645
+ const proxy = proxyByKind.get(proxyKey(kind, edge));
1646
+ const origin = require_layered.externalPortDummyOf(proxy);
1647
+ const endpoint$2 = sourceInside ? internalRoute?.at(-1)?.endPoint : internalRoute?.[0]?.startPoint;
1648
+ if (!endpoint$2) throw new Error(`Missing compound boundary route for ${String(edge.id)}`);
1649
+ const transferred = transferExternalPort({
1650
+ contentSize: {
1651
+ width: child.width - childPadding.left - childPadding.right,
1652
+ height: child.height - childPadding.top - childPadding.bottom
1653
+ },
1654
+ padding: childPadding,
1655
+ offset: {
1656
+ x: 0,
1657
+ y: 0
1658
+ },
1659
+ dummy: {
1660
+ x: endpoint$2.x - childPadding.left - (ownPortFor(edge) ? origin.port.x : 0),
1661
+ y: endpoint$2.y - childPadding.top - (ownPortFor(edge) ? origin.port.y : 0),
1662
+ width: origin.width,
1663
+ height: origin.height
1664
+ },
1665
+ side: origin.side,
1666
+ borderOffset: origin.borderOffset,
1667
+ portSize: origin.externalSize
1668
+ });
1669
+ const points = internalRoute.flatMap((section, index) => [
1670
+ ...index === 0 ? [section.startPoint] : [],
1671
+ ...section.bendPoints ?? [],
1672
+ section.endPoint
1673
+ ]).map((point) => ({ ...point }));
1674
+ const boundary = sourceInside ? points.at(-1) : points[0];
1675
+ boundary.x = transferred.dummy.x + childPadding.left;
1676
+ boundary.y = transferred.dummy.y + childPadding.top;
1677
+ const own = ownPortFor(edge);
1678
+ if (own) {
1679
+ if (implicitOwnPorts.has(edge) && !ports.some((p) => p.id === own.id)) ports.push(own);
1680
+ own.x = transferred.port.x;
1681
+ own.y = transferred.port.y;
1682
+ const anchor = getElementLayeredSettings(own.layoutOptions ?? {})["port.anchor"] ?? {
1683
+ x: origin.side === "EAST" ? own.width ?? 0 : origin.side === "WEST" ? 0 : (own.width ?? 0) / 2,
1684
+ y: origin.side === "SOUTH" ? own.height ?? 0 : origin.side === "NORTH" ? 0 : (own.height ?? 0) / 2
1685
+ };
1686
+ boundary.x = own.x + anchor.x;
1687
+ boundary.y = own.y + anchor.y;
1688
+ edge.sections = [{
1689
+ id: `${String(edge.id)}_s0`,
1690
+ startPoint: points[0],
1691
+ endPoint: points.at(-1),
1692
+ ...points.length > 2 ? { bendPoints: points.slice(1, -1) } : {},
1693
+ incomingShape: sourceId,
1694
+ outgoingShape: targetId
1695
+ }];
1696
+ if (internalEdge?.junctionPoints?.length) edge.junctionPoints = internalEdge.junctionPoints.map((point) => ({ ...point }));
1697
+ else delete edge.junctionPoints;
1698
+ edge.container = child.id;
1699
+ continue;
1700
+ }
1701
+ const routes = boundaryRoutes.get(edge) ?? {};
1702
+ routes[sourceInside ? "source" : "target"] = {
1703
+ owner: child,
1704
+ points,
1705
+ junctionPoints: internalEdge?.junctionPoints
1706
+ };
1707
+ boundaryRoutes.set(edge, routes);
1708
+ const portId = `${String(proxy.id)}:parent`;
1709
+ syntheticPortIds.add(portId);
1710
+ if (!ports.some((port) => port.id === portId)) ports.push({
1711
+ id: portId,
1712
+ width: origin.externalSize.width,
1713
+ height: origin.externalSize.height,
1714
+ x: transferred.port.x,
1715
+ y: transferred.port.y,
1716
+ layoutOptions: {
1717
+ "elk.port.side": origin.side,
1718
+ "elk.port.borderOffset": origin.borderOffset
1719
+ }
1720
+ });
1721
+ if (!hierarchyRestorations.some((restoration) => restoration.edge === edge)) hierarchyRestorations.push({
1722
+ edge,
1723
+ ...original
1724
+ });
1725
+ if (sourceInside) edge.sources = [portId];
1726
+ else edge.targets = [portId];
1727
+ edge.source = void 0;
1728
+ edge.target = void 0;
1729
+ }
1730
+ for (const { port, proxy } of inactiveAuthoredPorts) {
1731
+ const origin = require_layered.externalPortDummyOf(proxy);
1732
+ const actual = ports.find((p) => p.id === port.id);
1733
+ const transferred = transferExternalPort({
1734
+ contentSize: {
1735
+ width: child.width - childPadding.left - childPadding.right,
1736
+ height: child.height - childPadding.top - childPadding.bottom
1737
+ },
1738
+ padding: childPadding,
1739
+ offset: {
1740
+ x: 0,
1741
+ y: 0
1742
+ },
1743
+ dummy: {
1744
+ x: (proxy.x ?? 0) - childPadding.left,
1745
+ y: (proxy.y ?? 0) - childPadding.top,
1746
+ width: origin.width,
1747
+ height: origin.height
1748
+ },
1749
+ side: origin.side,
1750
+ borderOffset: origin.borderOffset,
1751
+ portSize: origin.externalSize
1752
+ });
1753
+ actual.x = transferred.port.x;
1754
+ actual.y = transferred.port.y;
1755
+ }
1756
+ for (const edge of outsideNodeEdges) {
1757
+ const proxy = proxyFor(originalHierarchyEndpoints.get(edge).sourceId === String(child.id) ? "output" : "input", edge), origin = require_layered.externalPortDummyOf(proxy), own = ownPortFor(edge);
1758
+ const transferred = transferExternalPort({
1759
+ contentSize: {
1760
+ width: child.width - childPadding.left - childPadding.right,
1761
+ height: child.height - childPadding.top - childPadding.bottom
1762
+ },
1763
+ padding: childPadding,
1764
+ offset: {
1765
+ x: 0,
1766
+ y: 0
1767
+ },
1768
+ dummy: {
1769
+ x: (proxy.x ?? 0) - childPadding.left,
1770
+ y: (proxy.y ?? 0) - childPadding.top,
1771
+ width: origin.width,
1772
+ height: origin.height
1773
+ },
1774
+ side: origin.side,
1775
+ borderOffset: origin.borderOffset,
1776
+ portSize: origin.externalSize
1777
+ });
1778
+ own.x = transferred.port.x;
1779
+ own.y = transferred.port.y;
1780
+ if (!ports.some((p) => p.id === own.id)) ports.push(own);
1781
+ }
1782
+ child.ports = ports;
1783
+ child.layoutOptions = {
1784
+ ...child.layoutOptions,
1785
+ "elk.portConstraints": "FIXED_POS"
1786
+ };
1787
+ const insideLoopCount = (graph.edges ?? []).filter((edge) => isInsideSelfLoop(graph, edge, String(child.id))).length;
1788
+ if (insideLoopCount > 0) {
1789
+ const baseHeight = child.height ?? 0;
1790
+ insideSelfLoopBaseHeightByNodeId.set(String(child.id), baseHeight);
1791
+ child.height = baseHeight + insideLoopCount * 11;
1792
+ }
1793
+ });
1794
+ }
1795
+ }
1796
+ applyNodeMicroLayout(graph, layoutOptions);
1797
+ const graph_ = toGraph(graph, layoutOptions);
1798
+ const hierarchyProxyIds = new Set((graph.children ?? []).filter((node) => String(node.id).startsWith("__native_hierarchy_")).map((node) => String(node.id)));
1799
+ const layerConstraintByNodeId = new Map((graph.children ?? []).map((node) => [String(node.id), String(getOption(node.layoutOptions ?? {}, "layered.layering.layerConstraint") ?? getOption(node.layoutOptions ?? {}, "layerConstraint") ?? "NONE")]));
1800
+ if (algorithm === "layered" && graph_.edges.some((edge) => {
1801
+ if (edge.sourceId === edge.targetId) return false;
1802
+ if (hierarchyProxyIds.has(edge.sourceId) || hierarchyProxyIds.has(edge.targetId)) return false;
1803
+ const sourceConstraint = layerConstraintByNodeId.get(edge.sourceId);
1804
+ const targetConstraint = layerConstraintByNodeId.get(edge.targetId);
1805
+ return (sourceConstraint === "FIRST" || sourceConstraint === "FIRST_SEPARATE") && (targetConstraint === "FIRST" || targetConstraint === "FIRST_SEPARATE");
1806
+ })) throw new Error("org.eclipse.elk.core.UnsupportedConfigurationException: Layer constraints conflict");
1807
+ const padding = parsePadding(getOption(layoutOptions, "padding"), algorithm === "layered" ? 12 : 0);
1808
+ const options = {
1809
+ direction: getDirection(layoutOptions),
1810
+ spacing: {
1811
+ node: getNumberOption(layoutOptions, "spacing.nodeNode") ?? getNumberOption(layoutOptions, "layered.spacing.baseValue"),
1812
+ layer: getNumberOption(layoutOptions, "layered.spacing.nodeNodeBetweenLayers") ?? getNumberOption(layoutOptions, "layered.spacing.baseValue") ?? 20
1813
+ },
1814
+ padding,
1815
+ constraints: { layer: () => void 0 },
1816
+ settings: {
1817
+ ...getLayeredSettings(layoutOptions),
1818
+ ...arguments_[crossingModel] ? { [require_layered.CROSSING_MODEL]: arguments_[crossingModel] } : {},
1819
+ ...compoundLayout || hasHierarchy && !separateHierarchy && !topdownLayout ? { separateConnectedComponents: false } : {},
1820
+ ...hierarchyHandling === "INCLUDE_CHILDREN" ? { "crossingMinimization.greedySwitch.type": String(getOption(layoutOptions, "layered.crossingMinimization.greedySwitchHierarchical.type") ?? "TWO_SIDED") } : {}
1821
+ },
1822
+ nodeSettings: (node) => {
1823
+ const child = findById(graph.children, node.id)?.item;
1824
+ return getElementLayeredSettings(child?.layoutOptions ?? {});
1825
+ },
1826
+ edgeSettings: (edge) => {
1827
+ const elkEdge = findById(graph.edges, edge.id)?.item;
1828
+ return {
1829
+ ...getElementLayeredSettings(elkEdge?.layoutOptions ?? {}),
1830
+ ...getElementLayeredSettings(elkEdge?.labels?.[0]?.layoutOptions ?? {}),
1831
+ "edge.hierarchyRank": elkEdge && hierarchySegmentRank.get(elkEdge)
1832
+ };
1833
+ },
1834
+ portSettings: (port, node) => {
1835
+ const child = findById(graph.children, node.id)?.item;
1836
+ const found = findById(child?.ports, port.name);
1837
+ const elkPort = found?.item;
1838
+ return {
1839
+ ...getElementLayeredSettings(elkPort?.layoutOptions ?? {}),
1840
+ "port.authoredIndex": found?.position,
1841
+ "port.labelWidth": Math.max(0, ...(elkPort?.labels ?? []).map((label) => label.width ?? 0)),
1842
+ "port.labelHeight": Math.max(0, ...(elkPort?.labels ?? []).map((label) => label.height ?? 0))
1843
+ };
1844
+ }
1845
+ };
1846
+ const pipeline = algorithm === "layered" ? require_layered.createLayeredScopePipeline(graph_, options) : void 0;
1847
+ const initial = pipeline?.next();
1848
+ const phase = initial && !initial.done ? initial.value : void 0;
1849
+ let activeOrders;
1850
+ let finished;
1851
+ const prepared = {
1852
+ graph,
1853
+ native: graph_,
1854
+ phase,
1855
+ options,
1856
+ children: preparedChildren,
1857
+ finish: (orders) => {
1858
+ if (finished) return finished;
1859
+ activeOrders = orders;
1860
+ finished = finish();
1861
+ return finished;
1862
+ }
1863
+ };
1864
+ const finishLayered = () => {
1865
+ if (!pipeline || !initial) throw new Error("Missing layered pipeline");
1866
+ if (initial.done) return initial.value;
1867
+ const completed = pipeline.next(activeOrders?.has(prepared) ? initial.value.finishOrder(activeOrders.get(prepared)) : initial.value.minimize());
1868
+ if (!completed.done) throw new Error("Layered scope yielded more than one crossing phase");
1869
+ return completed.value;
1870
+ };
1871
+ const finish = async () => {
1872
+ for (const complete of finishChildren) await complete();
1873
+ for (const nativeGraph of new Set([graph_, phase?.input.graph].filter((value) => value !== void 0))) for (const child of graph.children ?? []) {
1874
+ const native = nativeGraph.nodes.find((node) => node.id === String(child.id));
1875
+ if (!native || !preparedChildren.has(String(child.id))) continue;
1876
+ phase?.updateNodeSize(native.id, {
1877
+ width: child.width ?? 0,
1878
+ height: child.height ?? 0
1879
+ });
1880
+ Object.assign(native, {
1881
+ width: child.width,
1882
+ height: child.height
1883
+ });
1884
+ for (const port of native.ports ?? []) {
1885
+ const actual = child.ports?.find((candidate) => String(candidate.id) === port.name);
1886
+ if (actual) Object.assign(port, {
1887
+ x: actual.x,
1888
+ y: actual.y,
1889
+ width: actual.width,
1890
+ height: actual.height
1891
+ });
1892
+ }
1893
+ }
1894
+ const laidOut = await (algorithm === "sporeCompaction" ? require_layout_engine.executeElkjs0111Layout({
1895
+ algorithm: "sporeCompaction",
1896
+ graph: graph_,
1897
+ options: {
1898
+ padding,
1899
+ spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
1900
+ }
1901
+ }) : algorithm === "sporeOverlap" ? require_layout_engine.executeElkjs0111Layout({
1902
+ algorithm: "sporeOverlap",
1903
+ graph: graph_,
1904
+ options: {
1905
+ padding,
1906
+ spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
1907
+ }
1908
+ }) : algorithm === "rectpacking" ? require_layout_engine.executeElkjs0111Layout({
1909
+ algorithm: "rectpacking",
1910
+ graph: graph_,
1911
+ options: {
1912
+ padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
1913
+ spacing: getNumberOption(layoutOptions, "spacing.nodeNode") ?? 15
1914
+ }
1915
+ }) : algorithm === "random" ? require_layout_engine.executeElkjs0111Layout({
1916
+ algorithm: "random",
1917
+ graph: graph_,
1918
+ options: {
1919
+ padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
1920
+ spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
1921
+ aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
1922
+ seed: getNumberOption(layoutOptions, "randomSeed")
1923
+ }
1924
+ }) : algorithm === "box" ? require_layout_engine.executeElkjs0111Layout({
1925
+ algorithm: "box",
1926
+ graph: graph_,
1927
+ options: {
1928
+ padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
1929
+ spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
1930
+ aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
1931
+ interactive: getBooleanOption(layoutOptions, "interactive"),
1932
+ expandNodes: getBooleanOption(layoutOptions, "expandNodes"),
1933
+ priority: (node) => {
1934
+ const child = graph.children?.find((candidate) => String(candidate.id) === node.id);
1935
+ return getNumberOption(child?.layoutOptions ?? {}, "priority");
1936
+ }
1937
+ }
1938
+ }) : algorithm === "fixed" ? require_layout_engine.executeElkjs0111Layout({
1939
+ algorithm: "fixed",
1940
+ graph: graph_,
1941
+ options: { direction: getDirection(layoutOptions) }
1942
+ }) : finishLayered());
1943
+ const resultPolicy = laidOut[require_layered.elkjs0111ResultPolicy];
1944
+ applyLayout(graph, laidOut, padding, layoutOptions, resultPolicy);
1945
+ if (resultPolicy?.providerBounds) {
1946
+ graph.width = resultPolicy.providerBounds.width;
1947
+ graph.height = resultPolicy.providerBounds.height;
1948
+ }
1949
+ for (const restoration of hierarchyRestorations) {
1950
+ const splits = boundaryRoutes.get(restoration.edge);
1951
+ const sections = restoration.edge.sections;
1952
+ if (splits && sections?.length) {
1953
+ const parentPoints = sections.flatMap((section, index) => [
1954
+ ...index === 0 ? [section.startPoint] : [],
1955
+ ...section.bendPoints ?? [],
1956
+ section.endPoint
1957
+ ]);
1958
+ const points = joinCompoundRouteSegments([
1959
+ ...splits.source ? [{
1960
+ points: splits.source.points,
1961
+ offset: {
1962
+ x: splits.source.owner.x ?? 0,
1963
+ y: splits.source.owner.y ?? 0
1964
+ }
1965
+ }] : [],
1966
+ {
1967
+ points: parentPoints,
1968
+ offset: {
1969
+ x: 0,
1970
+ y: 0
1971
+ }
1972
+ },
1973
+ ...splits.target ? [{
1974
+ points: splits.target.points,
1975
+ offset: {
1976
+ x: splits.target.owner.x ?? 0,
1977
+ y: splits.target.owner.y ?? 0
1978
+ }
1979
+ }] : []
1980
+ ], getBooleanOption(layoutOptions, "unnecessaryBendpoints") === true);
1981
+ const childJunctions = (split) => (split?.junctionPoints ?? []).map((point) => ({
1982
+ x: point.x + (split?.owner.x ?? 0),
1983
+ y: point.y + (split?.owner.y ?? 0)
1984
+ }));
1985
+ const junctions = [
1986
+ ...childJunctions(splits.source),
1987
+ ...restoration.edge.junctionPoints ?? [],
1988
+ ...childJunctions(splits.target)
1989
+ ];
1990
+ if (junctions.length) restoration.edge.junctionPoints = junctions;
1991
+ else delete restoration.edge.junctionPoints;
1992
+ restoration.edge.sections = [{
1993
+ ...sections[0],
1994
+ startPoint: points[0],
1995
+ endPoint: points.at(-1),
1996
+ bendPoints: points.slice(1, -1)
1997
+ }];
1998
+ }
1999
+ restoration.edge.sources = restoration.sources;
2000
+ restoration.edge.targets = restoration.targets;
2001
+ restoration.edge.source = restoration.source;
2002
+ restoration.edge.target = restoration.target;
2003
+ for (const section of restoration.edge.sections ?? []) {
2004
+ if (section.incomingShape != null && syntheticPortIds.has(String(section.incomingShape))) section.incomingShape = restoration.sources?.[0] ?? restoration.source;
2005
+ if (section.outgoingShape != null && syntheticPortIds.has(String(section.outgoingShape))) section.outgoingShape = restoration.targets?.[0] ?? restoration.target;
2006
+ }
2007
+ }
2008
+ for (const [compound, ports] of authoredPortsByCompound) {
2009
+ for (const port of ports ?? []) {
2010
+ const actual = compound.ports?.find((p) => p.id === port.id);
2011
+ if (actual) Object.assign(port, {
2012
+ x: actual.x,
2013
+ y: actual.y,
2014
+ width: actual.width,
2015
+ height: actual.height
2016
+ });
2017
+ }
2018
+ compound.ports = ports;
2019
+ }
2020
+ for (const [compound, options_] of authoredOptionsByCompound) compound.layoutOptions = options_;
2021
+ if (hasHierarchy && topdownLayout) for (const child of graph.children ?? []) {
2022
+ if ((child.children?.length ?? 0) === 0) continue;
2023
+ const position = {
2024
+ x: child.x,
2025
+ y: child.y
2026
+ };
2027
+ await this.#layout(child, {
2028
+ ...arguments_,
2029
+ logging: false,
2030
+ measureExecutionTime: false
2031
+ });
2032
+ child.x = position.x;
2033
+ child.y = position.y;
2034
+ }
2035
+ applyInsideSelfLoops(graph, insideSelfLoopBaseHeightByNodeId);
2036
+ if (arguments_.logging || arguments_.measureExecutionTime) graph.logging = {
2037
+ name: "Native TypeScript layout",
2038
+ children: [{ name: String(algorithm) }],
2039
+ ...arguments_.measureExecutionTime ? { executionTime: (performance.now() - startedAt) / 1e3 } : {}
2040
+ };
2041
+ return graph;
2042
+ };
2043
+ return prepared;
2044
+ }
2045
+ };
2046
+ /** Move opposite-direction track sharing apart on the finished layout. */
2047
+ function separated(graph, options) {
2048
+ separateElkOpposingTracks(graph, {
2049
+ ...options,
2050
+ ...graph.layoutOptions
2051
+ });
2052
+ return graph;
2053
+ }
2054
+ function coordinatePreparedScopes(root) {
2055
+ if (!root.phase || root.children.size === 0) return void 0;
2056
+ const prepared = [root];
2057
+ for (let i = 0; i < prepared.length; i++) prepared.push(...prepared[i].children.values());
2058
+ if (prepared.some((scope) => !scope.phase || !["LAYER_SWEEP", "MEDIAN_LAYER_SWEEP"].includes(scope.options?.settings?.["crossingMinimization.strategy"] ?? "LAYER_SWEEP"))) return void 0;
2059
+ const scopes = /* @__PURE__ */ new Map();
2060
+ for (const scope of prepared) {
2061
+ const phase = scope.phase;
2062
+ scopes.set(scope, {
2063
+ session: require_layered.createLayerSweepSession(phase.input, phase.orientation, phase.assignment, scope.options?.settings?.["crossingMinimization.strategy"] === "MEDIAN_LAYER_SWEEP" ? "median" : "mean", 7, { resetSeed: scope === root }),
2064
+ childrenByNodeId: /* @__PURE__ */ new Map(),
2065
+ useBottomUp: true,
2066
+ independentRandom: scope !== root
2067
+ });
2068
+ }
2069
+ for (const parent of prepared) {
2070
+ const parentScope = scopes.get(parent);
2071
+ for (const [id, child] of parent.children) {
2072
+ const nativeParent = parent.phase.input.graph.nodes.find((node) => node.id === id);
2073
+ const parentPortOrderFixed = [
2074
+ "FIXED_ORDER",
2075
+ "FIXED_POS",
2076
+ "FIXED_RATIO"
2077
+ ].includes(parent.phase.input.nodeSettings?.(nativeParent)?.portConstraints ?? "FREE");
2078
+ const ports = nativeParent.ports ?? [];
2079
+ const childPhase = child.phase;
2080
+ const childSession = scopes.get(child).session;
2081
+ const childNodeById = /* @__PURE__ */ new Map();
2082
+ for (const node of childPhase.input.graph.nodes) if (!childNodeById.has(node.id)) childNodeById.set(node.id, node);
2083
+ const parentEdgeById = /* @__PURE__ */ new Map();
2084
+ for (const edge of parent.phase.input.graph.edges) if (!parentEdgeById.has(edge.id)) parentEdgeById.set(edge.id, edge);
2085
+ const flowSides = require_layered.hierarchicalPortSides(parent.phase.input);
2086
+ const inputPorts = ports.filter((port) => parent.phase.input.portSettings?.(port, nativeParent)?.["port.side"] === flowSides.before).length;
2087
+ const outputPorts = ports.filter((port) => parent.phase.input.portSettings?.(port, nativeParent)?.["port.side"] === flowSides.after).length;
2088
+ const childScope = {
2089
+ ...scopes.get(child),
2090
+ useBottomUp: useBottomUpHierarchySweep(childPhase.input, childSession.crossingGraph(), {
2091
+ portOrderFixed: parentPortOrderFixed,
2092
+ inputPorts,
2093
+ outputPorts,
2094
+ deterministic: child.options?.settings?.["crossingMinimization.strategy"] === "MEDIAN_LAYER_SWEEP"
2095
+ }),
2096
+ alignBoundary: (forward) => {
2097
+ const childLayers = childSession.currentLayers();
2098
+ const layerIndex = forward ? 0 : childLayers.length - 1;
2099
+ const layer = childLayers[layerIndex];
2100
+ if (!layer.every((id$1) => require_layered.externalPortDummyOf(childNodeById.get(id$1)))) return false;
2101
+ const physical = parentScope.session.physicalPortOrder(id);
2102
+ const dummyByParentPort = new Map(childPhase.input.graph.nodes.filter((n) => require_layered.externalPortDummyOf(n)).map((n) => [require_layered.externalPortDummyOf(n).parentPortId ?? `${n.id}:parent`, n.id]));
2103
+ const ordered = physical.flatMap((portId) => {
2104
+ const [nodeId, portName] = JSON.parse(portId);
2105
+ const dummy = nodeId === id ? dummyByParentPort.get(portName) : void 0;
2106
+ return dummy && layer.includes(dummy) ? [dummy] : [];
2107
+ });
2108
+ if (forward) ordered.reverse();
2109
+ const unique = [...new Set(ordered)];
2110
+ if (unique.length !== layer.length) throw new Error(`Incomplete hierarchical port order for ${id}`);
2111
+ childSession.replaceLayer(layerIndex, unique);
2112
+ return true;
2113
+ },
2114
+ publishBoundary: (forward) => {
2115
+ const childLayers = childSession.currentLayers();
2116
+ const layer = childLayers[forward ? childLayers.length - 1 : 0];
2117
+ if (!layer.every((id$1) => require_layered.externalPortDummyOf(childNodeById.get(id$1)))) return;
2118
+ const rank = new Map(layer.map((dummy, index) => [require_layered.externalPortDummyOf(childNodeById.get(dummy)).parentPortId ?? `${dummy}:parent`, index]));
2119
+ const physical = parentScope.session.physicalPortOrder(id);
2120
+ const boundarySlots = physical.flatMap((portId, index) => {
2121
+ const [nodeId, portName] = JSON.parse(portId);
2122
+ return nodeId === id && rank.has(portName) ? [index] : [];
2123
+ });
2124
+ const boundaryPorts = boundarySlots.map((index) => physical[index]).sort((a, b) => {
2125
+ const portA = JSON.parse(a)[1];
2126
+ const portB = JSON.parse(b)[1];
2127
+ const result = rank.get(portA) - rank.get(portB);
2128
+ return forward ? result : -result;
2129
+ });
2130
+ const updatedPhysical = [...physical];
2131
+ boundarySlots.forEach((slot, index) => {
2132
+ updatedPhysical[slot] = boundaryPorts[index];
2133
+ });
2134
+ const edgePortRank = (edgeId) => {
2135
+ const edge = parentEdgeById.get(edgeId);
2136
+ const portName = edge.sourceId === id ? edge.sourcePort : edge.targetPort;
2137
+ return updatedPhysical.indexOf(JSON.stringify([id, portName]));
2138
+ };
2139
+ const { incoming, outgoing } = parentScope.session.edgeOrders(id);
2140
+ const byPort = (current) => [...current].sort((a, b) => edgePortRank(a) - edgePortRank(b));
2141
+ parentScope.session.reorderNodePorts(id, updatedPhysical, byPort(incoming), byPort(outgoing));
2142
+ }
2143
+ };
2144
+ childScope.publishBottomUp = () => {
2145
+ childScope.publishBoundary(true);
2146
+ childScope.publishBoundary(false);
2147
+ parentScope.session.lockPortOrder(id);
2148
+ };
2149
+ scopes.set(child, childScope);
2150
+ parentScope.childrenByNodeId.set(id, childScope);
2151
+ }
2152
+ }
2153
+ const orders = require_layered.minimizeHierarchyCrossings(scopes.get(root));
2154
+ return new Map(prepared.map((scope) => [scope, orders.get(scopes.get(scope))]));
2155
+ }
2156
+ function parsePadding(value, fallback = 0) {
2157
+ if (typeof value === "number") return {
2158
+ top: value,
2159
+ right: value,
2160
+ bottom: value,
2161
+ left: value
2162
+ };
2163
+ if (typeof value !== "string") return {
2164
+ top: fallback,
2165
+ right: fallback,
2166
+ bottom: fallback,
2167
+ left: fallback
2168
+ };
2169
+ const padding = {
2170
+ top: 0,
2171
+ right: 0,
2172
+ bottom: 0,
2173
+ left: 0
2174
+ };
2175
+ for (const match of value.matchAll(/(top|right|bottom|left)\s*=\s*(-?\d+(?:\.\d+)?)/g)) {
2176
+ const side = match[1];
2177
+ padding[side] = Number(match[2]);
2178
+ }
2179
+ return padding;
2180
+ }
2181
+ function parseVector(value) {
2182
+ if (typeof value === "object" && value !== null && "x" in value && "y" in value) return {
2183
+ x: Number(value.x),
2184
+ y: Number(value.y)
2185
+ };
2186
+ if (typeof value !== "string") return void 0;
2187
+ const match = value.match(/^\s*\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)\s*$/);
2188
+ return match ? {
2189
+ x: Number(match[1]),
2190
+ y: Number(match[2])
2191
+ } : void 0;
2192
+ }
2193
+ function applyNodeMicroLayout(graph, globalOptions) {
2194
+ const labelPadding = parsePadding(getOption(globalOptions, "nodeLabels.padding"), 5);
2195
+ for (const node of graph.children ?? []) {
2196
+ const constraints = String(getOption(node.layoutOptions ?? {}, "nodeSize.constraints") ?? "");
2197
+ if (!constraints) continue;
2198
+ const configuredSizeOptions = getOption(node.layoutOptions ?? {}, "nodeSize.options");
2199
+ const sizeOptions = new Set(configuredSizeOptions === void 0 ? ["DEFAULT_MINIMUM_SIZE"] : String(configuredSizeOptions).split(/[\s,;]+/).filter(Boolean));
2200
+ const effectivelyFixedPortLabelSize = constraints.includes("PORT_LABELS") && !constraints.includes("NODE_LABELS") && !constraints.includes("MINIMUM_SIZE");
2201
+ const preserveComputedCompoundSize = (node.children?.length ?? 0) > 0;
2202
+ let width = effectivelyFixedPortLabelSize || preserveComputedCompoundSize ? node.width ?? 0 : 0;
2203
+ let height = effectivelyFixedPortLabelSize || preserveComputedCompoundSize ? node.height ?? 0 : 0;
2204
+ let insideHorizontalInset = 0;
2205
+ let insideVerticalInset = 0;
2206
+ const insideLabelCells = /* @__PURE__ */ new Map();
2207
+ if (constraints.includes("PORTS")) {
2208
+ const spacing = getNumberOption(globalOptions, "spacing.portPort") ?? 10;
2209
+ const sideCounts = {
2210
+ NORTH: 0,
2211
+ EAST: 0,
2212
+ SOUTH: 0,
2213
+ WEST: 0
2214
+ };
2215
+ for (const port of node.ports ?? []) {
2216
+ const side = String(getOption(port.layoutOptions ?? {}, "port.side") ?? "EAST");
2217
+ if (side in sideCounts) sideCounts[side]++;
2218
+ }
2219
+ const nodeId = String(node.id);
2220
+ for (const edge of graph.edges ?? []) {
2221
+ const source = String(edge.sources?.[0] ?? edge.source);
2222
+ const target = String(edge.targets?.[0] ?? edge.target);
2223
+ if (source === nodeId) sideCounts.EAST++;
2224
+ if (target === nodeId) sideCounts.WEST++;
2225
+ }
2226
+ width = Math.max(width, (Math.max(sideCounts.NORTH, sideCounts.SOUTH) + 1) * spacing);
2227
+ height = Math.max(height, (Math.max(sideCounts.EAST, sideCounts.WEST) + 1) * spacing);
2228
+ if (!preserveComputedCompoundSize && sideCounts.NORTH === 0 && sideCounts.SOUTH === 0) width = 0;
2229
+ if (!preserveComputedCompoundSize && sideCounts.EAST === 0 && sideCounts.WEST === 0) height = 0;
2230
+ }
2231
+ if (constraints.includes("NODE_LABELS")) {
2232
+ for (const label of node.labels ?? []) {
2233
+ if (!label.text) continue;
2234
+ const placement = String(getOption({
2235
+ ...globalOptions,
2236
+ ...node.layoutOptions,
2237
+ ...label.layoutOptions
2238
+ }, "nodeLabels.placement") ?? "");
2239
+ if (!placement) continue;
2240
+ const labelWidth = label.width ?? 0;
2241
+ const labelHeight = label.height ?? 0;
2242
+ if (placement.includes("OUTSIDE")) {
2243
+ if (!sizeOptions.has("OUTSIDE_NODE_LABELS_OVERHANG")) width = Math.max(width, labelWidth);
2244
+ } else {
2245
+ width = Math.max(width, labelWidth + labelPadding.left + labelPadding.right);
2246
+ const verticalInset = labelHeight * (placement.includes("V_CENTER") ? 1 : 2) + labelPadding.top + labelPadding.bottom;
2247
+ height = Math.max(height, verticalInset);
2248
+ insideHorizontalInset = Math.max(insideHorizontalInset, labelPadding.left + labelPadding.right);
2249
+ insideVerticalInset = Math.max(insideVerticalInset, verticalInset);
2250
+ const key = `${placement.includes("V_CENTER") ? "center" : placement.includes("V_BOTTOM") ? "bottom" : "top"}:${placement.includes("H_CENTER") ? "center" : placement.includes("H_RIGHT") ? "right" : "left"}`;
2251
+ const cell = insideLabelCells.get(key) ?? {
2252
+ width: 0,
2253
+ height: 0
2254
+ };
2255
+ cell.width = Math.max(cell.width, labelWidth);
2256
+ cell.height += labelHeight;
2257
+ insideLabelCells.set(key, cell);
2258
+ }
2259
+ }
2260
+ if (insideLabelCells.size > 0) {
2261
+ const rows = [
2262
+ "top",
2263
+ "center",
2264
+ "bottom"
2265
+ ];
2266
+ const columns = [
2267
+ "left",
2268
+ "center",
2269
+ "right"
2270
+ ];
2271
+ const cellWidth = (row, column) => insideLabelCells.get(`${row}:${column}`)?.width ?? 0;
2272
+ const forceTabular = sizeOptions.has("FORCE_TABULAR_NODE_LABELS");
2273
+ const asymmetrical = sizeOptions.has("ASYMMETRICAL");
2274
+ const globalColumns = columns.map((column) => Math.max(...rows.map((row) => cellWidth(row, column))));
2275
+ const labelGridWidth = forceTabular ? globalColumns.reduce((sum, value) => sum + value, 0) : Math.max(...rows.map((row) => {
2276
+ const left = cellWidth(row, "left");
2277
+ const center = cellWidth(row, "center");
2278
+ const right = cellWidth(row, "right");
2279
+ return asymmetrical ? left + center + right : 2 * Math.max(left, right) + center;
2280
+ }));
2281
+ const labelGridHeight = rows.reduce((sum, row) => sum + Math.max(...columns.map((column) => insideLabelCells.get(`${row}:${column}`)?.height ?? 0)), 0);
2282
+ width = Math.max(width, labelGridWidth + labelPadding.left + labelPadding.right);
2283
+ height = Math.max(height, labelGridHeight + labelPadding.top + labelPadding.bottom);
2284
+ }
2285
+ }
2286
+ if (constraints.includes("MINIMUM_SIZE")) {
2287
+ const configuredMinimum = parseVector(getOption(node.layoutOptions ?? {}, "nodeSize.minimum"));
2288
+ const minimum = {
2289
+ x: configuredMinimum?.x && configuredMinimum.x > 0 ? configuredMinimum.x : sizeOptions.has("DEFAULT_MINIMUM_SIZE") ? 20 : 0,
2290
+ y: configuredMinimum?.y && configuredMinimum.y > 0 ? configuredMinimum.y : sizeOptions.has("DEFAULT_MINIMUM_SIZE") ? 20 : 0
2291
+ };
2292
+ if (sizeOptions.has("MINIMUM_SIZE_ACCOUNTS_FOR_PADDING")) {
2293
+ width = Math.max(width, minimum.x + insideHorizontalInset);
2294
+ height = Math.max(height, minimum.y + insideVerticalInset);
2295
+ } else {
2296
+ width = Math.max(width, minimum.x);
2297
+ height = Math.max(height, minimum.y);
2298
+ }
2299
+ }
2300
+ sizeOptions.has("COMPUTE_PADDING");
2301
+ node.width = width;
2302
+ node.height = height;
2303
+ }
2304
+ }
2305
+ function getOption(options, suffix) {
2306
+ const exactKeys = [
2307
+ suffix,
2308
+ `elk.${suffix}`,
2309
+ `org.eclipse.elk.${suffix}`
2310
+ ];
2311
+ for (const key of exactKeys) if (options[key] !== void 0) return options[key];
2312
+ }
2313
+ const ergonomicallyMappedLayeredSettings = new Set([
2314
+ "direction",
2315
+ "padding",
2316
+ "spacing.node",
2317
+ "spacing.layer"
2318
+ ]);
2319
+ function coerceLayeredOptionValue(value, type) {
2320
+ if (type === "BOOLEAN") {
2321
+ if (typeof value === "boolean") return value;
2322
+ if (typeof value === "string") return value.toLowerCase() === "true";
2323
+ }
2324
+ if (type === "DOUBLE" || type === "INT") {
2325
+ if (typeof value === "number") return value;
2326
+ if (typeof value === "string" && value.trim() !== "") return Number(value);
2327
+ }
2328
+ return value;
2329
+ }
2330
+ function parseIndividualSpacing(value) {
2331
+ if (typeof value === "object" && value !== null) return value;
2332
+ if (typeof value !== "string") return value;
2333
+ const result = {};
2334
+ for (const entry of value.split(/;,;|;/)) {
2335
+ const match = entry.match(/^\s*(?:org\.eclipse\.elk\.)?(?:layered\.)?([^:]+)\s*:\s*(-?\d+(?:\.\d+)?)\s*$/);
2336
+ if (!match) continue;
2337
+ const sourceName = match[1];
2338
+ const definition = require_layered.elkLayeredOptionDefinitions.find((candidate) => {
2339
+ const suffix = candidate.elkId.replace(/^org\.eclipse\.elk\./, "");
2340
+ return candidate.name === sourceName || suffix === sourceName;
2341
+ });
2342
+ if (definition) result[definition.name] = Number(match[2]);
2343
+ }
2344
+ return result;
2345
+ }
2346
+ function parseMargin(value) {
2347
+ if (typeof value === "object" && value !== null) return value;
2348
+ if (typeof value !== "string") return value;
2349
+ return parsePadding(value, 0);
2350
+ }
2351
+ const layeredOptionAliases = /* @__PURE__ */ new Map();
2352
+ for (const definition of require_layered.elkLayeredOptionDefinitions) {
2353
+ const suffix = definition.elkId.replace(/^org\.eclipse\.elk\./, "");
2354
+ [
2355
+ definition.name,
2356
+ suffix,
2357
+ `elk.${suffix}`,
2358
+ definition.elkId
2359
+ ].forEach((key, rank) => {
2360
+ const current = layeredOptionAliases.get(key);
2361
+ if (!current || rank < current.rank) layeredOptionAliases.set(key, {
2362
+ definition,
2363
+ rank
2364
+ });
2365
+ });
2366
+ }
2367
+ const indexByIdCache = /* @__PURE__ */ new WeakMap();
2368
+ /** First element whose stringified id matches, like `find`, via a per-array index. */
2369
+ function findById(items, id) {
2370
+ if (!items) return void 0;
2371
+ let cached = indexByIdCache.get(items);
2372
+ if (!cached || cached.length !== items.length) {
2373
+ const index = /* @__PURE__ */ new Map();
2374
+ items.forEach((item, position$1) => {
2375
+ const key = String(item.id);
2376
+ if (!index.has(key)) index.set(key, position$1);
2377
+ });
2378
+ indexByIdCache.set(items, cached = {
2379
+ length: items.length,
2380
+ index
2381
+ });
2382
+ }
2383
+ const position = cached.index.get(id);
2384
+ return position === void 0 ? void 0 : {
2385
+ item: items[position],
2386
+ position
2387
+ };
2388
+ }
2389
+ function getElementLayeredSettings(options) {
2390
+ const settings = {};
2391
+ const chosen = /* @__PURE__ */ new Map();
2392
+ for (const [key, value] of Object.entries(options)) {
2393
+ const alias = layeredOptionAliases.get(key);
2394
+ if (!alias || value === void 0) continue;
2395
+ const current = chosen.get(alias.definition);
2396
+ if (!current || alias.rank < current.rank) chosen.set(alias.definition, {
2397
+ rank: alias.rank,
2398
+ value
2399
+ });
2400
+ }
2401
+ for (const [definition, { value }] of chosen) {
2402
+ const vectorMatch = definition.name === "port.anchor" && typeof value === "string" ? value.match(/^\s*\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)\s*$/) : void 0;
2403
+ settings[definition.name] = vectorMatch ? {
2404
+ x: Number(vectorMatch[1]),
2405
+ y: Number(vectorMatch[2])
2406
+ } : definition.name === "spacing.individual" ? parseIndividualSpacing(value) : definition.name === "spacing.portsSurrounding" ? parseMargin(value) : coerceLayeredOptionValue(value, definition.type);
2407
+ }
2408
+ return settings;
2409
+ }
2410
+ function getLayeredSettings(options) {
2411
+ const settings = getElementLayeredSettings(options);
2412
+ const baseValue = settings["spacing.baseValue"];
2413
+ if (baseValue !== void 0) for (const [name, defaultValue] of [
2414
+ ["spacing.componentComponent", 20],
2415
+ ["spacing.edgeEdge", 10],
2416
+ ["spacing.edgeLabel", 2],
2417
+ ["spacing.edgeNode", 10],
2418
+ ["spacing.labelLabel", 0],
2419
+ ["spacing.labelNode", 5],
2420
+ ["spacing.labelPortHorizontal", 1],
2421
+ ["spacing.labelPortVertical", 1],
2422
+ ["spacing.nodeSelfLoop", 10],
2423
+ ["spacing.portPort", 10],
2424
+ ["spacing.edgeEdgeBetweenLayers", 10],
2425
+ ["spacing.edgeNodeBetweenLayers", 10]
2426
+ ]) settings[name] ??= baseValue * defaultValue / 20;
2427
+ for (const name of ergonomicallyMappedLayeredSettings) delete settings[name];
2428
+ return settings;
2429
+ }
2430
+ function getNumberOption(options, suffix) {
2431
+ const value = getOption(options, suffix);
2432
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2433
+ if (typeof value === "string" && value.trim() !== "") {
2434
+ const parsed = Number(value);
2435
+ if (Number.isFinite(parsed)) return parsed;
2436
+ }
2437
+ }
2438
+ function getBooleanOption(options, suffix) {
2439
+ const value = getOption(options, suffix);
2440
+ if (typeof value === "boolean") return value;
2441
+ if (typeof value === "string") {
2442
+ if (value.toLowerCase() === "true") return true;
2443
+ if (value.toLowerCase() === "false") return false;
2444
+ }
2445
+ }
2446
+ function getDirection(options) {
2447
+ const direction = String(getOption(options, "direction") ?? "RIGHT").toLowerCase();
2448
+ return direction === "up" || direction === "left" || direction === "down" ? direction : "right";
2449
+ }
2450
+ function endpoint(value, portOwnerById) {
2451
+ const id = String(value);
2452
+ const ownerId = portOwnerById.get(id);
2453
+ return ownerId === void 0 ? { nodeId: id } : {
2454
+ nodeId: ownerId,
2455
+ port: id
2456
+ };
2457
+ }
2458
+ function isInsideSelfLoop(root, edge, expectedNodeId) {
2459
+ const source = String(edge.sources?.[0] ?? edge.source);
2460
+ if (source !== String(edge.targets?.[0] ?? edge.target) || expectedNodeId !== void 0 && source !== expectedNodeId) return false;
2461
+ const node = root.children?.find((child) => String(child.id) === source);
2462
+ return getBooleanOption(node?.layoutOptions ?? {}, "insideSelfLoops.activate") === true && getBooleanOption(edge.layoutOptions ?? {}, "insideSelfLoops.yo") === true;
2463
+ }
2464
+ function applyInsideSelfLoops(root, baseHeightByNodeId) {
2465
+ const indexByNodeId = /* @__PURE__ */ new Map();
2466
+ for (const edge of root.edges ?? []) {
2467
+ if (!isInsideSelfLoop(root, edge)) continue;
2468
+ const nodeId = String(edge.sources?.[0] ?? edge.source);
2469
+ const node = root.children?.find((child) => String(child.id) === nodeId);
2470
+ const baseHeight = baseHeightByNodeId.get(nodeId);
2471
+ if (!node || baseHeight === void 0) continue;
2472
+ const index = indexByNodeId.get(nodeId) ?? 0;
2473
+ indexByNodeId.set(nodeId, index + 1);
2474
+ const y = (node.y ?? 0) + baseHeight - 2 + index * 11;
2475
+ edge.sections = [{
2476
+ id: `${String(edge.id)}_s0`,
2477
+ startPoint: {
2478
+ x: node.x ?? 0,
2479
+ y
2480
+ },
2481
+ endPoint: {
2482
+ x: (node.x ?? 0) + (node.width ?? 0),
2483
+ y
2484
+ },
2485
+ incomingShape: edge.sources?.[0] ?? edge.source,
2486
+ outgoingShape: edge.targets?.[0] ?? edge.target
2487
+ }];
2488
+ }
2489
+ }
2490
+ function toGraph(root, globalOptions = {}) {
2491
+ const children = (root.children ?? []).filter((child) => getBooleanOption(child.layoutOptions ?? {}, "noLayout") !== true);
2492
+ const nodeIds = new Set(children.map((child) => String(child.id)));
2493
+ const portOwnerById = /* @__PURE__ */ new Map();
2494
+ for (const child of children) for (const port of child.ports ?? []) if (port.id !== void 0) portOwnerById.set(String(port.id), String(child.id));
2495
+ const sourcePortIds = new Set((root.edges ?? []).flatMap((edge) => (edge.sources ?? (edge.source === void 0 ? [] : [edge.source])).map(String)));
2496
+ const targetPortIds = new Set((root.edges ?? []).flatMap((edge) => (edge.targets ?? (edge.target === void 0 ? [] : [edge.target])).map(String)));
2497
+ const sourcePortDegree = /* @__PURE__ */ new Map();
2498
+ const targetPortDegree = /* @__PURE__ */ new Map();
2499
+ const edgeModelOrderByPortId = /* @__PURE__ */ new Map();
2500
+ for (const [edgeIndex, edge] of (root.edges ?? []).entries()) {
2501
+ for (const source of edge.sources ?? (edge.source === void 0 ? [] : [edge.source])) {
2502
+ const id = String(source);
2503
+ sourcePortDegree.set(id, (sourcePortDegree.get(id) ?? 0) + 1);
2504
+ if (portOwnerById.has(id) && !edgeModelOrderByPortId.has(id)) edgeModelOrderByPortId.set(id, edgeIndex);
2505
+ }
2506
+ for (const target of edge.targets ?? (edge.target === void 0 ? [] : [edge.target])) {
2507
+ const id = String(target);
2508
+ targetPortDegree.set(id, (targetPortDegree.get(id) ?? 0) + 1);
2509
+ if (portOwnerById.has(id) && !edgeModelOrderByPortId.has(id)) edgeModelOrderByPortId.set(id, edgeIndex);
2510
+ }
2511
+ }
2512
+ const orderedPorts = (child) => {
2513
+ const ports = [...child.ports ?? []];
2514
+ if (String(getOption(child.layoutOptions ?? {}, "portConstraints")) !== "FIXED_SIDE") return ports;
2515
+ const canonicalSides = getDirection(globalOptions) === "left" ? {
2516
+ NORTH: "NORTH",
2517
+ EAST: "WEST",
2518
+ SOUTH: "SOUTH",
2519
+ WEST: "EAST"
2520
+ } : getDirection(globalOptions) === "down" ? {
2521
+ NORTH: "WEST",
2522
+ EAST: "SOUTH",
2523
+ SOUTH: "EAST",
2524
+ WEST: "NORTH"
2525
+ } : getDirection(globalOptions) === "up" ? {
2526
+ NORTH: "EAST",
2527
+ EAST: "SOUTH",
2528
+ SOUTH: "WEST",
2529
+ WEST: "NORTH"
2530
+ } : {
2531
+ NORTH: "NORTH",
2532
+ EAST: "EAST",
2533
+ SOUTH: "SOUTH",
2534
+ WEST: "WEST"
2535
+ };
2536
+ const side = (port) => canonicalSides[String(getOption(port.layoutOptions ?? {}, "port.side"))] ?? "UNDEFINED";
2537
+ const sideOrder = [
2538
+ "NORTH",
2539
+ "EAST",
2540
+ "SOUTH",
2541
+ "WEST"
2542
+ ];
2543
+ ports.sort((left, right) => {
2544
+ const leftSide = side(left);
2545
+ const rightSide = side(right);
2546
+ const sideDifference = sideOrder.indexOf(leftSide) - sideOrder.indexOf(rightSide);
2547
+ if (sideDifference !== 0) return sideDifference;
2548
+ if (String(getOption(globalOptions, "layered.portSortingStrategy")) === "PORT_DEGREE") {
2549
+ if (leftSide === "EAST") return (sourcePortDegree.get(String(right.id)) ?? 0) - (sourcePortDegree.get(String(left.id)) ?? 0);
2550
+ if (leftSide === "WEST") return (targetPortDegree.get(String(left.id)) ?? 0) - (targetPortDegree.get(String(right.id)) ?? 0);
2551
+ }
2552
+ const modelOrderStrategy = String(getOption(globalOptions, "layered.considerModelOrder.strategy") ?? "NONE");
2553
+ const usePortModelOrder = getBooleanOption(globalOptions, "layered.considerModelOrder.portModelOrder") === true;
2554
+ if (modelOrderStrategy !== "NONE" && !usePortModelOrder) {
2555
+ const leftOrder = edgeModelOrderByPortId.get(String(left.id));
2556
+ const rightOrder = edgeModelOrderByPortId.get(String(right.id));
2557
+ if (leftOrder === void 0 && rightOrder === void 0) return 0;
2558
+ if (leftOrder !== rightOrder) return (leftOrder ?? Infinity) - (rightOrder ?? Infinity);
2559
+ }
2560
+ return (leftSide === "WEST" ? -1 : 1) * ((child.ports?.indexOf(left) ?? 0) - (child.ports?.indexOf(right) ?? 0));
2561
+ });
2562
+ return ports;
2563
+ };
2564
+ const native = (0, _statelyai_graph.createGraph)({
2565
+ id: String(root.id),
2566
+ nodes: children.map((child) => ({
2567
+ id: String(child.id),
2568
+ x: child.x,
2569
+ y: child.y,
2570
+ ...parsePosition(getOption(child.layoutOptions ?? {}, "position")),
2571
+ width: child.width,
2572
+ height: child.height,
2573
+ label: child.labels?.[0]?.text,
2574
+ ports: orderedPorts(child).filter((port) => getBooleanOption(port.layoutOptions ?? {}, "noLayout") !== true).map((port) => ({
2575
+ name: String(port.id),
2576
+ direction: sourcePortIds.has(String(port.id)) ? targetPortIds.has(String(port.id)) ? "inout" : "out" : targetPortIds.has(String(port.id)) ? "in" : "inout",
2577
+ x: port.x,
2578
+ y: port.y,
2579
+ width: port.width,
2580
+ height: port.height
2581
+ }))
2582
+ })),
2583
+ edges: (root.edges ?? []).flatMap((edge) => {
2584
+ if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true) return [];
2585
+ if (isInsideSelfLoop(root, edge)) return [];
2586
+ const source = endpoint(edge.sources?.[0] ?? edge.source, portOwnerById);
2587
+ const target = endpoint(edge.targets?.[0] ?? edge.target, portOwnerById);
2588
+ const labels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
2589
+ const labelLabelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
2590
+ const labelWidth = Math.max(0, ...labels.map((label) => label.width ?? 0));
2591
+ const labelHeight = labels.reduce((sum, label) => sum + (label.height ?? 0), 0) + Math.max(0, labels.length - 1) * labelLabelSpacing;
2592
+ return nodeIds.has(source.nodeId) && nodeIds.has(target.nodeId) ? [{
2593
+ id: String(edge.id),
2594
+ sourceId: source.nodeId,
2595
+ targetId: target.nodeId,
2596
+ sourcePort: source.port,
2597
+ targetPort: target.port,
2598
+ label: edge.labels?.[0]?.text,
2599
+ width: labelWidth,
2600
+ height: labelHeight,
2601
+ points: parsePoints(getOption(edge.layoutOptions ?? {}, "bendPoints"))
2602
+ }] : [];
2603
+ })
2604
+ });
2605
+ for (const node of native.nodes) {
2606
+ const child = children.find((candidate) => String(candidate.id) === node.id);
2607
+ const origin = child && require_layered.externalPortDummyOf(child);
2608
+ if (origin) require_layered.attachExternalPortDummy(node, origin);
2609
+ }
2610
+ return native;
2611
+ }
2612
+ function parsePoints(value) {
2613
+ if (typeof value !== "string") return void 0;
2614
+ const points = [...value.matchAll(/\{\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\}/g)].map((match) => ({
2615
+ x: Number(match[1]),
2616
+ y: Number(match[2])
2617
+ }));
2618
+ return points.length > 0 ? points : void 0;
2619
+ }
2620
+ function parsePosition(value) {
2621
+ if (typeof value !== "string") return void 0;
2622
+ const match = value.match(/\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/);
2623
+ return match ? {
2624
+ x: Number(match[1]),
2625
+ y: Number(match[2])
2626
+ } : void 0;
2627
+ }
2628
+ function toSection(edge, points) {
2629
+ const startPoint = points[0];
2630
+ const endPoint = points.at(-1);
2631
+ if (!startPoint || !endPoint) return void 0;
2632
+ return {
2633
+ id: `${String(edge.id)}_s0`,
2634
+ startPoint: { ...startPoint },
2635
+ endPoint: { ...endPoint },
2636
+ incomingShape: edge.sources?.[0] ?? edge.source,
2637
+ outgoingShape: edge.targets?.[0] ?? edge.target,
2638
+ ...points.length > 2 ? { bendPoints: points.slice(1, -1).map((point) => ({ ...point })) } : {}
2639
+ };
2640
+ }
2641
+ function applyLayout(root, graph, padding, layoutOptions, resultPolicy) {
2642
+ const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
2643
+ const edgeById = new Map(graph.edges.map((edge) => [edge.id, edge]));
2644
+ for (const child of root.children ?? []) {
2645
+ const node = nodeById.get(String(child.id));
2646
+ if (!node) continue;
2647
+ child.x = node.x;
2648
+ child.y = node.y;
2649
+ child.width = node.width;
2650
+ child.height = node.height;
2651
+ placeNodeLabels(child, layoutOptions);
2652
+ for (const port of child.ports ?? []) {
2653
+ if (getBooleanOption(port.layoutOptions ?? {}, "noLayout") === true) continue;
2654
+ const laidOutPort = node.ports?.find((candidate) => candidate.name === String(port.id));
2655
+ if (!laidOutPort) continue;
2656
+ port.x = laidOutPort.x;
2657
+ port.y = laidOutPort.y;
2658
+ port.width = laidOutPort.width;
2659
+ port.height = laidOutPort.height;
2660
+ }
2661
+ placePortLabels(child, layoutOptions);
2662
+ }
2663
+ for (const edge of root.edges ?? []) {
2664
+ if (resultPolicy?.preserveEdgeSections) {
2665
+ for (const section$1 of edge.sections ?? []) {
2666
+ section$1.incomingShape ??= edge.sources?.[0] ?? edge.source;
2667
+ section$1.outgoingShape ??= edge.targets?.[0] ?? edge.target;
2668
+ }
2669
+ continue;
2670
+ }
2671
+ const laidOutEdge = edgeById.get(String(edge.id));
2672
+ if (!laidOutEdge) {
2673
+ if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true) {
2674
+ for (const section$2 of edge.sections ?? []) {
2675
+ section$2.incomingShape ??= edge.sources?.[0] ?? edge.source;
2676
+ section$2.outgoingShape ??= edge.targets?.[0] ?? edge.target;
2677
+ }
2678
+ continue;
2679
+ }
2680
+ const section$1 = getParentEdgeSection(root, edge);
2681
+ if (section$1) {
2682
+ edge.sections = [section$1];
2683
+ edge.container = root.id;
2684
+ }
2685
+ continue;
2686
+ }
2687
+ const nativeJunctions = resultPolicy?.junctionPointsByEdgeId?.get(String(edge.id));
2688
+ if (nativeJunctions) if (nativeJunctions.length) edge.junctionPoints = nativeJunctions.map((point) => ({ ...point }));
2689
+ else delete edge.junctionPoints;
2690
+ const section = toSection(edge, laidOutEdge.points ?? []);
2691
+ edge.sections = section ? [section] : [];
2692
+ if (section) edge.container = root.id;
2693
+ const target = root.children?.find((child) => String(child.id) === String(edge.targets?.[0] ?? edge.target));
2694
+ if (getBooleanOption(target?.layoutOptions ?? {}, "hypernode") === true && (section?.bendPoints?.length ?? 0) > 0) edge.junctionPoints = [section.bendPoints.at(-1)];
2695
+ let labelY = laidOutEdge.y ?? 0;
2696
+ const points = laidOutEdge.points ?? [];
2697
+ const firstPoint = points[0] ?? {
2698
+ x: laidOutEdge.x ?? 0,
2699
+ y: laidOutEdge.y ?? 0
2700
+ };
2701
+ const lastPoint = points.at(-1) ?? firstPoint;
2702
+ const midpointX = (laidOutEdge.x ?? 0) + (laidOutEdge.width ?? 0) / 2;
2703
+ const edgeLabelSpacing = getNumberOption(layoutOptions, "spacing.edgeLabel") ?? 2;
2704
+ const labelLabelSpacing = getNumberOption(layoutOptions, "spacing.labelLabel") ?? 0;
2705
+ const loneLabel = (edge.labels ?? []).filter(isLayoutEdgeLabel).length === 1;
2706
+ for (const label of edge.labels ?? []) {
2707
+ if (getBooleanOption(label.layoutOptions ?? {}, "noLayout") === true) {
2708
+ label.x ??= 0;
2709
+ label.y ??= 0;
2710
+ continue;
2711
+ }
2712
+ if (!isLayoutEdgeLabel(label)) {
2713
+ label.x ??= 0;
2714
+ label.y ??= 0;
2715
+ continue;
2716
+ }
2717
+ const placement = String(getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER");
2718
+ const width = label.width ?? 0;
2719
+ label.x = loneLabel && placement !== "CENTER" && laidOutEdge.x !== void 0 ? laidOutEdge.x : placement === "TAIL" ? firstPoint.x + edgeLabelSpacing : placement === "HEAD" ? lastPoint.x - width - edgeLabelSpacing : midpointX - width / 2;
2720
+ label.y = labelY;
2721
+ labelY += (label.height ?? 0) + labelLabelSpacing;
2722
+ }
2723
+ }
2724
+ if (!resultPolicy?.skipBoundsNormalization) normalizeElkGraphBounds(root, padding, layoutOptions, resultPolicy?.normalizationBounds, resultPolicy?.placementCrossBounds);
2725
+ if (!resultPolicy?.preserveEdgeSections && String(getOption(layoutOptions, "edgeRouting") ?? "ORTHOGONAL") === "ORTHOGONAL") {
2726
+ const direction = getDirection(layoutOptions);
2727
+ applyOrthogonalJunctions(root, direction === "right" || direction === "left", direction === "right" || direction === "down", getBooleanOption(layoutOptions, "layered.mergeEdges") === true, new Set((root.children ?? []).filter((n) => getBooleanOption(n.layoutOptions ?? {}, "hypernode") === true).map((n) => String(n.id))), new Set((root.edges ?? []).filter((e) => getBooleanOption(e.layoutOptions ?? {}, "noLayout") === true || resultPolicy?.junctionPointsByEdgeId?.has(String(e.id)) === true).map((e) => String(e.id))));
2728
+ }
2729
+ }
2730
+ function normalizeElkGraphBounds(root, padding, layoutOptions = {}, compactionBounds, placementCrossBounds) {
2731
+ const layoutEdges = (root.edges ?? []).filter((edge) => edge.container == null || String(edge.container) === String(root.id));
2732
+ const authoredWidth = root.width;
2733
+ const authoredHeight = root.height;
2734
+ const fixedGraphSize = getBooleanOption(layoutOptions, "nodeSize.fixedGraphSize") === true;
2735
+ const layoutChildren = (root.children ?? []).filter((node) => getBooleanOption(node.layoutOptions ?? {}, "noLayout") !== true);
2736
+ let minimumX = compactionBounds?.left ?? Number.POSITIVE_INFINITY;
2737
+ let minimumY = compactionBounds?.top ?? Number.POSITIVE_INFINITY;
2738
+ for (const node of layoutChildren) {
2739
+ minimumX = Math.min(minimumX, node.x ?? 0, ...(node.labels ?? []).map((label) => (node.x ?? 0) + (label.x ?? 0)), ...(node.ports ?? []).map((port) => (node.x ?? 0) + (port.x ?? 0)), ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.x ?? 0) + (port.x ?? 0) + (label.x ?? 0))));
2740
+ minimumY = Math.min(minimumY, node.y ?? 0, ...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0)), ...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0)), ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0))));
2741
+ }
2742
+ for (const edge of layoutEdges) {
2743
+ const laidOutLabels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
2744
+ minimumX = Math.min(minimumX, ...laidOutLabels.map((label) => label.x ?? 0));
2745
+ minimumY = Math.min(minimumY, ...laidOutLabels.map((label) => label.y ?? 0));
2746
+ if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") !== true) {
2747
+ const points = (edge.sections ?? []).flatMap((section) => [
2748
+ section.startPoint,
2749
+ ...section.bendPoints ?? [],
2750
+ section.endPoint
2751
+ ]);
2752
+ minimumX = Math.min(minimumX, ...points.map((point) => point.x));
2753
+ minimumY = Math.min(minimumY, ...points.map((point) => point.y));
2754
+ }
2755
+ }
2756
+ const shiftX = Number.isFinite(minimumX) ? Math.max(0, padding.left - minimumX) : 0;
2757
+ const shiftY = Number.isFinite(minimumY) ? Math.max(0, padding.top - minimumY) : 0;
2758
+ if (shiftX !== 0 || shiftY !== 0) {
2759
+ for (const node of layoutChildren) {
2760
+ node.x = (node.x ?? 0) + shiftX;
2761
+ node.y = (node.y ?? 0) + shiftY;
2762
+ }
2763
+ for (const edge of layoutEdges) {
2764
+ for (const section of edge.sections ?? []) for (const point of [
2765
+ section.startPoint,
2766
+ ...section.bendPoints ?? [],
2767
+ section.endPoint
2768
+ ]) {
2769
+ point.x += shiftX;
2770
+ point.y += shiftY;
2771
+ }
2772
+ for (const point of edge.junctionPoints ?? []) {
2773
+ point.x += shiftX;
2774
+ point.y += shiftY;
2775
+ }
2776
+ for (const label of (edge.labels ?? []).filter(isLayoutEdgeLabel)) {
2777
+ label.x = (label.x ?? 0) + shiftX;
2778
+ label.y = (label.y ?? 0) + shiftY;
2779
+ }
2780
+ }
2781
+ }
2782
+ const maximumNodeX = Math.max(0, ...layoutChildren.map((node) => (node.x ?? 0) + (node.width ?? 0)));
2783
+ const maximumNodeY = Math.max(0, ...layoutChildren.map((node) => (node.y ?? 0) + (node.height ?? 0)));
2784
+ const layoutEdgePoints = layoutEdges.flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
2785
+ section.startPoint,
2786
+ ...section.bendPoints ?? [],
2787
+ section.endPoint
2788
+ ]));
2789
+ const wrappingStrategy = String(getOption(layoutOptions, "layered.wrapping.strategy") ?? "OFF");
2790
+ const direction = getDirection(layoutOptions);
2791
+ const laidOutChildById = new Map((root.children ?? []).map((child) => [String(child.id), child]));
2792
+ const childByEndpointId = new Map(laidOutChildById);
2793
+ for (const child of root.children ?? []) for (const port of child.ports ?? []) childByEndpointId.set(String(port.id), child);
2794
+ const wrappedEdgeCount = layoutEdges.filter((edge) => {
2795
+ const source = childByEndpointId.get(String(edge.sources?.[0] ?? edge.source));
2796
+ const target = childByEndpointId.get(String(edge.targets?.[0] ?? edge.target));
2797
+ if (!source || !target) return false;
2798
+ return direction === "right" ? (source.x ?? 0) > (target.x ?? 0) : direction === "left" ? (source.x ?? 0) < (target.x ?? 0) : direction === "down" ? (source.y ?? 0) > (target.y ?? 0) : (source.y ?? 0) < (target.y ?? 0);
2799
+ }).length;
2800
+ const hasWrappedEdge = wrappedEdgeCount > 0;
2801
+ const retainPlacementBounds = wrappingStrategy !== "MULTI_EDGE" && !(wrappingStrategy === "SINGLE_EDGE" && hasWrappedEdge) && String(getOption(layoutOptions, "layered.compaction.postCompaction.strategy") ?? "NONE") === "NONE" && getBooleanOption(layoutOptions, "layered.feedbackEdges") !== true && String(getOption(layoutOptions, "layered.layering.nodePromotion.strategy") ?? "NONE") !== "MODEL_ORDER_LEFT_TO_RIGHT";
2802
+ const addBoundaryPixel = retainPlacementBounds && !(root.children ?? []).some((child) => (child.children?.length ?? 0) > 0);
2803
+ const isSelfLoop = (edge) => {
2804
+ const source = childByEndpointId.get(String(edge.sources?.[0] ?? edge.source));
2805
+ return source !== void 0 && source === childByEndpointId.get(String(edge.targets?.[0] ?? edge.target));
2806
+ };
2807
+ const hasSelfLoops = layoutEdges.some(isSelfLoop);
2808
+ const edgeBoundsExtraX = addBoundaryPixel && placementCrossBounds?.axis !== "x" && !(hasSelfLoops && (direction === "right" || direction === "left")) && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.x)) >= maximumNodeX - 1e-9 ? 1 : 0;
2809
+ const edgeBoundsExtraY = addBoundaryPixel && placementCrossBounds?.axis !== "y" && !(hasSelfLoops && (direction === "down" || direction === "up")) && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.y)) >= maximumNodeY - 1e-9 ? 1 : 0;
2810
+ const singleMultiEdgeCutBoundsExtraY = wrappingStrategy === "MULTI_EDGE" && wrappedEdgeCount === 1 && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.y)) >= maximumNodeY - 1e-9 ? 1 : 0;
2811
+ const postCompactionBoundsExtraX = !compactionBounds && !addBoundaryPixel && !hasWrappedEdge && String(getOption(layoutOptions, "layered.compaction.postCompaction.strategy") ?? "NONE") !== "NONE" && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.x)) > maximumNodeX + 1e-9 ? .04 : 0;
2812
+ const postCompactionBoundsExtraY = !compactionBounds && !addBoundaryPixel && !hasWrappedEdge && String(getOption(layoutOptions, "layered.compaction.postCompaction.strategy") ?? "NONE") !== "NONE" && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.y)) > maximumNodeY + 1e-9 ? .04 : 0;
2813
+ const calculatedWidth = Math.max(compactionBounds ? compactionBounds.right + shiftX : 0, retainPlacementBounds && placementCrossBounds?.axis === "x" ? placementCrossBounds.maximum + shiftX : 0, ...layoutChildren.flatMap((node) => [
2814
+ (node.x ?? 0) + (node.width ?? 0),
2815
+ ...(node.labels ?? []).map((label) => (node.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)),
2816
+ ...(node.ports ?? []).map((port) => (node.x ?? 0) + (port.x ?? 0) + (port.width ?? 0)),
2817
+ ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.x ?? 0) + (port.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)))
2818
+ ]), ...layoutEdges.flatMap((edge) => (edge.labels ?? []).filter(isLayoutEdgeLabel).map((label) => (label.x ?? 0) + (label.width ?? 0))), ...layoutEdges.flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
2819
+ section.startPoint.x + (isSelfLoop(edge) ? 0 : edgeBoundsExtraX),
2820
+ ...(section.bendPoints ?? []).map((point) => point.x + (isSelfLoop(edge) ? 0 : edgeBoundsExtraX)),
2821
+ section.endPoint.x + (isSelfLoop(edge) ? 0 : edgeBoundsExtraX)
2822
+ ]))) + padding.right + postCompactionBoundsExtraX + (getBooleanOption(layoutOptions, "layered.feedbackEdges") === true && (getDirection(layoutOptions) === "down" || getDirection(layoutOptions) === "up") ? 1 : 0);
2823
+ const calculatedHeight = Math.max(compactionBounds ? compactionBounds.bottom + shiftY : 0, retainPlacementBounds && placementCrossBounds?.axis === "y" ? placementCrossBounds.maximum + shiftY : 0, ...layoutChildren.flatMap((node) => [
2824
+ (node.y ?? 0) + (node.height ?? 0),
2825
+ ...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)),
2826
+ ...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0) + (port.height ?? 0)),
2827
+ ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)))
2828
+ ]), ...layoutEdges.flatMap((edge) => (edge.labels ?? []).filter(isLayoutEdgeLabel).map((label) => (label.y ?? 0) + (label.height ?? 0) + (String(getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER") === "CENTER" && childByEndpointId.get(String(edge.sources?.[0] ?? edge.source)) !== childByEndpointId.get(String(edge.targets?.[0] ?? edge.target)) ? 1 : 0))), ...layoutEdges.flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
2829
+ section.startPoint.y + (isSelfLoop(edge) ? 0 : edgeBoundsExtraY),
2830
+ ...(section.bendPoints ?? []).map((point) => point.y + (isSelfLoop(edge) ? 0 : edgeBoundsExtraY)),
2831
+ section.endPoint.y + (isSelfLoop(edge) ? 0 : edgeBoundsExtraY)
2832
+ ]))) + padding.bottom + singleMultiEdgeCutBoundsExtraY + postCompactionBoundsExtraY + (getBooleanOption(layoutOptions, "layered.feedbackEdges") === true && (getDirection(layoutOptions) === "right" || getDirection(layoutOptions) === "left") ? 1 : 0);
2833
+ root.width = fixedGraphSize ? authoredWidth ?? 0 : calculatedWidth;
2834
+ root.height = fixedGraphSize ? authoredHeight ?? 0 : calculatedHeight;
2835
+ }
2836
+ function getParentEdgeSection(root, edge) {
2837
+ const sourceId = String(edge.sources?.[0] ?? edge.source);
2838
+ const targetId = String(edge.targets?.[0] ?? edge.target);
2839
+ const rootId = String(root.id);
2840
+ const source = root.children?.find((child) => String(child.id) === sourceId);
2841
+ const target = root.children?.find((child) => String(child.id) === targetId);
2842
+ if (source && targetId === rootId) {
2843
+ const startPoint = {
2844
+ x: (source.x ?? 0) + (source.width ?? 0) / 2,
2845
+ y: (source.y ?? 0) + (source.height ?? 0)
2846
+ };
2847
+ return {
2848
+ id: `${String(edge.id)}_s0`,
2849
+ startPoint,
2850
+ endPoint: {
2851
+ x: startPoint.x,
2852
+ y: 0
2853
+ }
2854
+ };
2855
+ }
2856
+ if (sourceId === rootId && target) {
2857
+ const endPoint = {
2858
+ x: (target.x ?? 0) + (target.width ?? 0) / 2,
2859
+ y: target.y ?? 0
2860
+ };
2861
+ return {
2862
+ id: `${String(edge.id)}_s0`,
2863
+ startPoint: {
2864
+ x: endPoint.x,
2865
+ y: 0
2866
+ },
2867
+ endPoint
2868
+ };
2869
+ }
2870
+ }
2871
+ function placeNodeLabels(node, globalOptions) {
2872
+ const labelPadding = parsePadding(getOption(globalOptions, "nodeLabels.padding"), 5);
2873
+ for (const label of node.labels ?? []) {
2874
+ if (!label.text) continue;
2875
+ if (getBooleanOption(label.layoutOptions ?? {}, "noLayout") === true) continue;
2876
+ const placement = String(getOption({
2877
+ ...globalOptions,
2878
+ ...node.layoutOptions,
2879
+ ...label.layoutOptions
2880
+ }, "nodeLabels.placement") ?? "");
2881
+ if (!placement) continue;
2882
+ const width = label.width ?? 0;
2883
+ const height = label.height ?? 0;
2884
+ const nodeWidth = node.width ?? 0;
2885
+ const nodeHeight = node.height ?? 0;
2886
+ label.x = placement.includes("H_CENTER") ? (nodeWidth - width + labelPadding.left - labelPadding.right) / 2 : placement.includes("H_RIGHT") ? nodeWidth - width - labelPadding.right : labelPadding.left;
2887
+ if (placement.includes("OUTSIDE") && placement.includes("V_TOP")) label.y = -height - Number(getOption(globalOptions, "spacing.labelNode") ?? 5);
2888
+ else if (placement.includes("OUTSIDE") && placement.includes("V_BOTTOM")) label.y = nodeHeight + Number(getOption(globalOptions, "spacing.labelNode") ?? 5);
2889
+ else if (placement.includes("V_CENTER")) label.y = (nodeHeight - height + labelPadding.top - labelPadding.bottom) / 2;
2890
+ else if (placement.includes("V_BOTTOM")) label.y = nodeHeight - height - labelPadding.bottom;
2891
+ else label.y = labelPadding.top;
2892
+ }
2893
+ }
2894
+ function placePortLabels(node, globalOptions) {
2895
+ const placement = String(getOption({
2896
+ ...globalOptions,
2897
+ ...node.layoutOptions
2898
+ }, "portLabels.placement") ?? "OUTSIDE");
2899
+ const inside = placement.includes("INSIDE");
2900
+ const alwaysOtherSide = placement.includes("ALWAYS_OTHER_SAME_SIDE");
2901
+ const spaceEfficient = placement.includes("SPACE_EFFICIENT") || String(getOption(node.layoutOptions ?? {}, "nodeSize.options") ?? "").includes("SPACE_EFFICIENT_PORT_LABELS");
2902
+ const horizontalSpacing = getNumberOption(globalOptions, "spacing.labelPortHorizontal") ?? 1;
2903
+ const verticalSpacing = getNumberOption(globalOptions, "spacing.labelPortVertical") ?? 1;
2904
+ const labelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
2905
+ const treatAsGroup = getBooleanOption(node.layoutOptions ?? {}, "portLabels.treatAsGroup") ?? false;
2906
+ const placeNextToPort = placement.includes("NEXT_TO_PORT_IF_POSSIBLE") || getBooleanOption(node.layoutOptions ?? {}, "portLabels.nextToPortIfPossible") === true;
2907
+ const nodeWidth = node.width ?? 0;
2908
+ for (const port of node.ports ?? []) {
2909
+ if (getBooleanOption(port.layoutOptions ?? {}, "noLayout") === true) continue;
2910
+ const portWidth = port.width ?? 0;
2911
+ const portHeight = port.height ?? 0;
2912
+ const side = (port.x ?? 0) < 0 ? "WEST" : (port.x ?? 0) >= nodeWidth ? "EAST" : (port.y ?? 0) < 0 ? "NORTH" : "SOUTH";
2913
+ const portsOnSide = (node.ports ?? []).filter((candidate) => {
2914
+ return ((candidate.x ?? 0) < 0 ? "WEST" : (candidate.x ?? 0) >= nodeWidth ? "EAST" : (candidate.y ?? 0) < 0 ? "NORTH" : "SOUTH") === side;
2915
+ });
2916
+ const labels = (port.labels ?? []).filter((label) => Boolean(label.text) && getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true);
2917
+ const totalLabelHeight = labels.reduce((sum, label) => sum + (label.height ?? 0), 0) + Math.max(0, labels.length - 1) * labelSpacing;
2918
+ let stackedY = labels.length > 1 ? inside || placeNextToPort ? treatAsGroup ? (portHeight - totalLabelHeight) / 2 : (portHeight - (labels[0]?.height ?? 0)) / 2 : portHeight + verticalSpacing : void 0;
2919
+ for (const label of labels) {
2920
+ if (!label.text) continue;
2921
+ const width = label.width ?? 0;
2922
+ const height = label.height ?? 0;
2923
+ if (side === "EAST") {
2924
+ label.x = inside ? -width - horizontalSpacing : portWidth + horizontalSpacing;
2925
+ label.y = stackedY ?? (inside || placeNextToPort ? (portHeight - height) / 2 : alwaysOtherSide ? -height - verticalSpacing : portHeight + verticalSpacing);
2926
+ } else if (side === "WEST") {
2927
+ label.x = inside ? portWidth + horizontalSpacing : -width - horizontalSpacing;
2928
+ label.y = stackedY ?? (inside || placeNextToPort ? (portHeight - height) / 2 : alwaysOtherSide ? -height - verticalSpacing : portHeight + verticalSpacing);
2929
+ } else if (side === "NORTH") {
2930
+ label.x = inside || placeNextToPort ? (portWidth - width) / 2 : alwaysOtherSide ? -width - horizontalSpacing : portWidth + horizontalSpacing;
2931
+ label.y = inside ? portHeight + verticalSpacing : -height - verticalSpacing;
2932
+ } else {
2933
+ label.x = inside || placeNextToPort ? (portWidth - width) / 2 : alwaysOtherSide ? -width - horizontalSpacing : portWidth + horizontalSpacing;
2934
+ label.y = inside ? -height - verticalSpacing : portHeight + verticalSpacing;
2935
+ }
2936
+ if (spaceEfficient && portsOnSide[0] === port && portsOnSide.length >= 2 && !placeNextToPort) if (side === "EAST" || side === "WEST") label.y = -height - verticalSpacing;
2937
+ else label.x = -width - horizontalSpacing;
2938
+ if (stackedY !== void 0) stackedY += height + labelSpacing;
2939
+ }
2940
+ }
2941
+ }
2942
+ /** Edges crossing `group`, in the order ELK's CompoundGraphPreprocessor creates its boundary ports. */
2943
+ function elkBoundaryEdgeOrder(group, scope, originals) {
2944
+ const edges = [];
2945
+ const collect = (node) => {
2946
+ edges.push(...node.edges ?? []);
2947
+ for (const child of node.children ?? []) collect(child);
2948
+ };
2949
+ collect(scope);
2950
+ const endpoints = (edge) => originals.get(edge) ?? {
2951
+ sourceId: String(edge.sources?.[0] ?? edge.source),
2952
+ targetId: String(edge.targets?.[0] ?? edge.target)
2953
+ };
2954
+ const order = (node) => {
2955
+ const inside = /* @__PURE__ */ new Set();
2956
+ const mark = (parent) => {
2957
+ for (const child of parent.children ?? []) {
2958
+ inside.add(String(child.id));
2959
+ for (const port of child.ports ?? []) inside.add(String(port.id));
2960
+ mark(child);
2961
+ }
2962
+ };
2963
+ mark(node);
2964
+ const leaves = (edge) => inside.has(endpoints(edge).sourceId) !== inside.has(endpoints(edge).targetId);
2965
+ const result = /* @__PURE__ */ new Set();
2966
+ for (const child of node.children ?? []) if (child.children?.length) {
2967
+ for (const edge of order(child)) if (leaves(edge)) result.add(edge);
2968
+ }
2969
+ for (const child of node.children ?? []) {
2970
+ for (const port of (child.ports ?? []).map((p) => String(p.id))) {
2971
+ for (const edge of edges) if (endpoints(edge).sourceId === port && leaves(edge)) result.add(edge);
2972
+ for (const edge of edges) if (endpoints(edge).targetId === port && leaves(edge)) result.add(edge);
2973
+ }
2974
+ const id = String(child.id);
2975
+ for (const edge of edges) {
2976
+ const { sourceId, targetId } = endpoints(edge);
2977
+ if ((sourceId === id || targetId === id) && leaves(edge)) result.add(edge);
2978
+ }
2979
+ }
2980
+ return [...result];
2981
+ };
2982
+ return order(group);
2983
+ }
2984
+ /** Creation order of hierarchy segments in ELK's CompoundGraphPreprocessor. */
2985
+ const hierarchySegmentRank = /* @__PURE__ */ new WeakMap();
2986
+ const segmentOrderByScope = /* @__PURE__ */ new WeakMap();
2987
+ const originalEdgeId = /* @__PURE__ */ new WeakMap();
2988
+ /**
2989
+ * Segments inside `group`, in creation order: the inner pass connects child
2990
+ * groups' exported ports first, then the outer pass leaves `group` itself.
2991
+ */
2992
+ function elkSegmentOrder(group, scope, originals, ownBoundary) {
2993
+ const order = /* @__PURE__ */ new Map();
2994
+ const add = (edge) => {
2995
+ const id = originalEdgeId.get(edge) ?? String(edge.id);
2996
+ if (!order.has(id)) order.set(id, order.size);
2997
+ };
2998
+ for (const child of group.children ?? []) if (child.children?.length) for (const edge of elkBoundaryEdgeOrder(child, scope, originals)) add(edge);
2999
+ if (ownBoundary) for (const edge of elkBoundaryEdgeOrder(group, scope, originals)) add(edge);
3000
+ return order;
3001
+ }
3002
+ const isPostCompactionKey = (key) => /(^|\.)compaction\.postCompaction\.strategy$/.test(key);
3003
+ function usesPostCompaction(options, node) {
3004
+ const active = (values) => Object.entries(values ?? {}).some(([key, value]) => isPostCompactionKey(key) && String(value) !== "NONE");
3005
+ const visit = (current) => active(current.layoutOptions) || active(current.properties) || (current.children ?? []).some(visit);
3006
+ return active(options) || visit(node);
3007
+ }
3008
+ function disablePostCompaction(node) {
3009
+ for (const values of [node.layoutOptions, node.properties]) for (const key of Object.keys(values ?? {})) if (isPostCompactionKey(key)) values[key] = "NONE";
3010
+ for (const child of node.children ?? []) disablePostCompaction(child);
3011
+ return node;
3012
+ }
3013
+ /**
3014
+ * Alternative random seeds worth trying for a compound layout, unless the
3015
+ * graph chooses its hierarchical greedy switch explicitly.
3016
+ */
3017
+ function crossingVariants(options, graph) {
3018
+ const own = {
3019
+ ...options,
3020
+ ...graph.properties,
3021
+ ...graph.layoutOptions
3022
+ };
3023
+ if (!(graph.children ?? []).some((child) => child.children?.length) || getOption(own, "hierarchyHandling") !== "INCLUDE_CHILDREN" || getOption(own, "layered.crossingMinimization.greedySwitchHierarchical.type") !== void 0) return [];
3024
+ return [{ "elk.randomSeed": "2" }, { "elk.randomSeed": "3" }];
3025
+ }
3026
+ /**
3027
+ * Whether two edges between different ends can share a port: an explicit port
3028
+ * carrying two or more of them, or implicit ports that merge because some
3029
+ * scope merges edges or a node is a hypernode.
3030
+ */
3031
+ function mayShareTracks(graph, options) {
3032
+ const merging = (node, inherited = {}) => {
3033
+ const settings = getElementLayeredSettings({
3034
+ ...inherited,
3035
+ ...node.properties,
3036
+ ...node.layoutOptions
3037
+ });
3038
+ return settings.mergeEdges === true || settings.hypernode === true;
3039
+ };
3040
+ const anyMerging = (node) => merging(node) || (node.children ?? []).some(anyMerging);
3041
+ if (merging(graph, options) || (graph.children ?? []).some(anyMerging)) return true;
3042
+ const ports = /* @__PURE__ */ new Set();
3043
+ const collectPorts = (node) => {
3044
+ for (const port of node.ports ?? []) ports.add(String(port.id));
3045
+ for (const child of node.children ?? []) collectPorts(child);
3046
+ };
3047
+ collectPorts(graph);
3048
+ if (!ports.size) return false;
3049
+ const seen = /* @__PURE__ */ new Set();
3050
+ const visit = (node) => {
3051
+ for (const edge of node.edges ?? []) {
3052
+ const source = String(edge.sources?.[0] ?? edge.source), target = String(edge.targets?.[0] ?? edge.target);
3053
+ if (source === target) continue;
3054
+ for (const end of [source, target]) {
3055
+ if (!ports.has(end)) continue;
3056
+ if (seen.has(end)) return true;
3057
+ seen.add(end);
3058
+ }
3059
+ }
3060
+ return (node.children ?? []).some(visit);
3061
+ };
3062
+ return visit(graph);
3063
+ }
3064
+ /** Whether the graph asks for more than ELK's default layered thoroughness (7). */
3065
+ function thorough(options, graph) {
3066
+ const own = {
3067
+ ...options,
3068
+ ...graph.properties,
3069
+ ...graph.layoutOptions
3070
+ };
3071
+ return Number(getOption(own, "layered.thoroughness") ?? 7) > 7;
3072
+ }
3073
+
3074
+ //#endregion
3075
+ Object.defineProperty(exports, 'ELK', {
3076
+ enumerable: true,
3077
+ get: function () {
3078
+ return ELK;
3079
+ }
3080
+ });
3081
+ Object.defineProperty(exports, 'compileStatechartLayout', {
3082
+ enumerable: true,
3083
+ get: function () {
3084
+ return compileStatechartLayout;
3085
+ }
3086
+ });
3087
+ Object.defineProperty(exports, 'getElkRoutes', {
3088
+ enumerable: true,
3089
+ get: function () {
3090
+ return getElkRoutes;
3091
+ }
3092
+ });
3093
+ Object.defineProperty(exports, 'layoutStatechart', {
3094
+ enumerable: true,
3095
+ get: function () {
3096
+ return layoutStatechart;
3097
+ }
3098
+ });
3099
+ Object.defineProperty(exports, 'scoreStatechartLayout', {
3100
+ enumerable: true,
3101
+ get: function () {
3102
+ return scoreStatechartLayout;
3103
+ }
3104
+ });