@crazyhappyone/auto-graph 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -76,6 +76,17 @@ function intersectsAabb(a, b) {
76
76
  validateBox(b, "b");
77
77
  return a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y;
78
78
  }
79
+ function overlapArea(first, second) {
80
+ const x = Math.max(
81
+ 0,
82
+ Math.min(first.x + first.width, second.x + second.width) - Math.max(first.x, second.x)
83
+ );
84
+ const y = Math.max(
85
+ 0,
86
+ Math.min(first.y + first.height, second.y + second.height) - Math.max(first.y, second.y)
87
+ );
88
+ return x * y;
89
+ }
79
90
  function validateMargin(value, label) {
80
91
  validateFinite(value, label);
81
92
  if (value < 0) {
@@ -88,6 +99,72 @@ function validateFinite(value, label) {
88
99
  }
89
100
  }
90
101
 
102
+ // src/geometry/spatial-index.ts
103
+ function createBoxSpatialIndex(entries, cellSize = 128) {
104
+ const normalizedCellSize = Number.isFinite(cellSize) && cellSize > 0 ? cellSize : 128;
105
+ const boxes = /* @__PURE__ */ new Map();
106
+ const mutableCells = /* @__PURE__ */ new Map();
107
+ for (const entry of entries) {
108
+ boxes.set(entry.id, { ...entry.box });
109
+ for (const key of cellKeysForBox(entry.box, normalizedCellSize)) {
110
+ const ids = mutableCells.get(key) ?? [];
111
+ ids.push(entry.id);
112
+ mutableCells.set(key, ids);
113
+ }
114
+ }
115
+ const cells = /* @__PURE__ */ new Map();
116
+ for (const [key, ids] of mutableCells) {
117
+ cells.set(key, [...new Set(ids)].sort());
118
+ }
119
+ return { cellSize: normalizedCellSize, entries: boxes, cells };
120
+ }
121
+ function queryBoxSpatialIndex(index, box) {
122
+ const ids = /* @__PURE__ */ new Set();
123
+ for (const key of cellKeysForBox(box, index.cellSize)) {
124
+ for (const id of index.cells.get(key) ?? []) {
125
+ ids.add(id);
126
+ }
127
+ }
128
+ return [...ids].sort().flatMap((id) => {
129
+ const candidate = index.entries.get(id);
130
+ return candidate !== void 0 && intersectsAabb(candidate, box) ? [{ id, box: candidate }] : [];
131
+ });
132
+ }
133
+ function querySegmentSpatialIndex(index, start, end) {
134
+ return queryBoxSpatialIndex(index, segmentBox(start, end));
135
+ }
136
+ function expandBoxForQuery(box, margin) {
137
+ return {
138
+ x: box.x - margin,
139
+ y: box.y - margin,
140
+ width: box.width + margin * 2,
141
+ height: box.height + margin * 2
142
+ };
143
+ }
144
+ function cellKeysForBox(box, cellSize) {
145
+ const minCol = Math.floor(box.x / cellSize);
146
+ const maxCol = Math.floor((box.x + Math.max(1, box.width)) / cellSize);
147
+ const minRow = Math.floor(box.y / cellSize);
148
+ const maxRow = Math.floor((box.y + Math.max(1, box.height)) / cellSize);
149
+ const keys = [];
150
+ for (let col = minCol; col <= maxCol; col += 1) {
151
+ for (let row = minRow; row <= maxRow; row += 1) {
152
+ keys.push(`${col}:${row}`);
153
+ }
154
+ }
155
+ return keys;
156
+ }
157
+ function segmentBox(start, end) {
158
+ const x = Math.min(start.x, end.x);
159
+ const y = Math.min(start.y, end.y);
160
+ return {
161
+ x,
162
+ y,
163
+ width: Math.max(1, Math.abs(start.x - end.x)),
164
+ height: Math.max(1, Math.abs(start.y - end.y))
165
+ };
166
+ }
167
+
91
168
  // src/constraints/solver.ts
92
169
  function applyLayoutConstraints(input) {
93
170
  const diagnostics = [];
@@ -119,7 +196,12 @@ function applyLayoutConstraints(input) {
119
196
  dedupReplayDiagnostics(diagnostics, diagBefore);
120
197
  }
121
198
  removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
122
- reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
199
+ reportOverlaps(
200
+ boxes,
201
+ diagnostics,
202
+ containmentOverlapKeys(input.constraints),
203
+ locks
204
+ );
123
205
  reportIntraContainerOverflow(input, boxes, diagnostics);
124
206
  return { boxes, locks, diagnostics };
125
207
  }
@@ -285,14 +367,19 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
285
367
  if (samePosition(child, next)) {
286
368
  continue;
287
369
  }
288
- if (locks.has(childId)) {
370
+ const lock = locks.get(childId);
371
+ if (lock !== void 0) {
289
372
  if (!reportOverflow) {
290
373
  diagnostics.push({
291
374
  severity: "warning",
292
375
  code: "constraints.locked-target-not-moved",
293
376
  message: `Locked child ${childId} was not moved into containment.`,
294
377
  path: ["constraints", constraint.id ?? constraint.containerId],
295
- detail: { nodeId: childId, containerId: constraint.containerId }
378
+ detail: {
379
+ nodeId: childId,
380
+ containerId: constraint.containerId,
381
+ lockSource: lock.source
382
+ }
296
383
  });
297
384
  if (!isInside(child, content)) {
298
385
  diagnostics.push({
@@ -407,18 +494,29 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
407
494
  const secondaryAxis = axis === "x" ? "y" : "x";
408
495
  const ignoredPairs = containmentOverlapKeys(input.constraints);
409
496
  const ids = [...boxes.keys()].sort();
497
+ const index = createBoxSpatialIndex(
498
+ ids.flatMap((id) => {
499
+ const box = boxes.get(id);
500
+ return box === void 0 ? [] : [{ id, box }];
501
+ }),
502
+ spacing
503
+ );
410
504
  for (let pass = 0; pass < 2; pass += 1) {
411
505
  for (const firstId of ids) {
412
- for (const secondId of ids) {
506
+ const first = boxes.get(firstId);
507
+ if (first === void 0) {
508
+ continue;
509
+ }
510
+ const candidateIds = queryBoxSpatialIndex(index, first).map((candidate) => candidate.id).filter((id) => id > firstId).sort();
511
+ for (const secondId of candidateIds) {
413
512
  if (firstId >= secondId) {
414
513
  continue;
415
514
  }
416
515
  if (ignoredPairs.has(overlapKey(firstId, secondId))) {
417
516
  continue;
418
517
  }
419
- const first = boxes.get(firstId);
420
518
  const second = boxes.get(secondId);
421
- if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
519
+ if (second === void 0 || !intersectsAabb(first, second)) {
422
520
  continue;
423
521
  }
424
522
  const firstLocked = locks.has(firstId);
@@ -442,7 +540,7 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
442
540
  }
443
541
  }
444
542
  }
445
- reportOverlaps(boxes, diagnostics, ignoredPairs);
543
+ reportOverlaps(boxes, diagnostics, ignoredPairs, locks);
446
544
  }
447
545
  function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
448
546
  for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
@@ -498,29 +596,56 @@ function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
498
596
  }
499
597
  }
500
598
  }
501
- function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
599
+ function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set(), locks = /* @__PURE__ */ new Map()) {
502
600
  const ids = [...boxes.keys()].sort();
503
601
  const reported = new Set(
504
602
  diagnostics.filter(
505
- (diagnostic) => diagnostic.code === "constraints.overlap.unresolved"
603
+ (diagnostic) => diagnostic.code === "constraints.overlap.unresolved" || diagnostic.code === "constraints.overlap.locked-conflict"
506
604
  ).map((diagnostic) => {
507
605
  const firstId = diagnostic.detail?.firstId;
508
606
  const secondId = diagnostic.detail?.secondId;
509
607
  return typeof firstId === "string" && typeof secondId === "string" ? overlapKey(firstId, secondId) : void 0;
510
608
  }).filter((key) => key !== void 0)
511
609
  );
610
+ const index = createBoxSpatialIndex(
611
+ ids.flatMap((id) => {
612
+ const box = boxes.get(id);
613
+ return box === void 0 ? [] : [{ id, box }];
614
+ }),
615
+ 40
616
+ );
512
617
  for (const firstId of ids) {
513
- for (const secondId of ids) {
514
- if (firstId >= secondId) {
515
- continue;
516
- }
618
+ const first = boxes.get(firstId);
619
+ if (first === void 0) {
620
+ continue;
621
+ }
622
+ const candidateIds = queryBoxSpatialIndex(index, first).map((candidate) => candidate.id).filter((id) => id > firstId).sort();
623
+ for (const secondId of candidateIds) {
517
624
  const key = overlapKey(firstId, secondId);
518
625
  if (reported.has(key) || ignoredPairs.has(key)) {
519
626
  continue;
520
627
  }
521
- const first = boxes.get(firstId);
522
628
  const second = boxes.get(secondId);
523
- if (first !== void 0 && second !== void 0 && intersectsAabb(first, second)) {
629
+ if (second !== void 0 && intersectsAabb(first, second)) {
630
+ const firstLock = locks.get(firstId);
631
+ const secondLock = locks.get(secondId);
632
+ if (firstLock !== void 0 && secondLock !== void 0) {
633
+ diagnostics.push({
634
+ severity: "warning",
635
+ code: "constraints.overlap.locked-conflict",
636
+ message: `Locked boxes ${firstId} (${firstLock.source}) and ${secondId} (${secondLock.source}) overlap and cannot be repaired.`,
637
+ path: ["boxes"],
638
+ detail: {
639
+ firstId,
640
+ secondId,
641
+ firstLockSource: firstLock.source,
642
+ secondLockSource: secondLock.source,
643
+ overlapArea: overlapArea(first, second)
644
+ }
645
+ });
646
+ reported.add(key);
647
+ continue;
648
+ }
524
649
  diagnostics.push({
525
650
  severity: "warning",
526
651
  code: "constraints.overlap.unresolved",
@@ -676,12 +801,17 @@ function setUnlockedBox(id, next, boxes, locks, diagnostics, constraint) {
676
801
  return;
677
802
  }
678
803
  if (locks.has(id) && !samePosition(current, next)) {
804
+ const lock = locks.get(id);
679
805
  diagnostics.push({
680
806
  severity: "warning",
681
807
  code: "constraints.locked-target-not-moved",
682
808
  message: `Locked target ${id} was not moved by ${constraint.kind}.`,
683
809
  path: ["constraints", constraint.id ?? id],
684
- detail: { nodeId: id, constraintKind: constraint.kind }
810
+ detail: {
811
+ nodeId: id,
812
+ constraintKind: constraint.kind,
813
+ ...lock === void 0 ? {} : { lockSource: lock.source }
814
+ }
685
815
  });
686
816
  return;
687
817
  }
@@ -850,7 +980,28 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
850
980
  if (distributable.length < 2) {
851
981
  continue;
852
982
  }
983
+ const spread = typeof input.distributeContainedChildren === "string";
984
+ let effectiveGap = minGap;
853
985
  let pos = content[axis];
986
+ if (spread) {
987
+ let totalChildSpan = 0;
988
+ for (const child of distributable) {
989
+ totalChildSpan += child.box[mainSize];
990
+ }
991
+ let reservedSpan = 0;
992
+ const contentEnd = content[axis] + content[mainSize];
993
+ for (const r of reserved) {
994
+ const rStart = Math.max(r.start, content[axis]);
995
+ const rEnd = Math.min(r.end, contentEnd);
996
+ if (rEnd > rStart) {
997
+ reservedSpan += rEnd - rStart + minGap;
998
+ }
999
+ }
1000
+ const remaining = content[mainSize] - totalChildSpan - reservedSpan - minGap * (distributable.length - 1);
1001
+ if (remaining > 0) {
1002
+ effectiveGap = minGap + remaining / (distributable.length - 1);
1003
+ }
1004
+ }
854
1005
  for (const child of distributable) {
855
1006
  pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
856
1007
  const crossPos = content[crossAxis] + Math.max(0, (content[crossSize] - child.box[crossSize]) / 2);
@@ -869,7 +1020,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
869
1020
  }
870
1021
  boxes.set(child.id, clamped);
871
1022
  locks.delete(child.id);
872
- pos = clamped[axis] + clamped[mainSize] + minGap;
1023
+ pos = clamped[axis] + clamped[mainSize] + effectiveGap;
873
1024
  }
874
1025
  diagnostics.push({
875
1026
  severity: "info",
@@ -1625,6 +1776,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1625
1776
  const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
1626
1777
  const routeKind = dsl.routing?.kind ?? "orthogonal";
1627
1778
  const portShifting = normalizePortShifting(dsl.routing?.portShifting);
1779
+ const initialLayout = dsl.layout?.mode;
1628
1780
  const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
1629
1781
  const matrices = normalizeMatrices(dsl);
1630
1782
  const tables = normalizeTables(dsl);
@@ -1645,6 +1797,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1645
1797
  ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
1646
1798
  metadata: {
1647
1799
  routeKind,
1800
+ ...initialLayout === void 0 ? {} : { initialLayout },
1648
1801
  ...primaryReadingDirection === void 0 ? {} : { primaryReadingDirection },
1649
1802
  ...portShifting === void 0 ? {} : { portShifting }
1650
1803
  }
@@ -2194,6 +2347,7 @@ function point(value) {
2194
2347
  return { x: value.x, y: value.y };
2195
2348
  }
2196
2349
  var directionSchema = z.enum(["TB", "LR", "BT", "RL"]);
2350
+ var layoutModeSchema = z.enum(["dagre", "positions"]);
2197
2351
  var routeKindSchema = z.enum(["orthogonal", "straight", "obstacle-avoiding"]);
2198
2352
  var outputFormatSchema = z.enum(["svg", "excalidraw"]);
2199
2353
  var edgeStrokeStyleSchema = z.enum(["solid", "dashed"]);
@@ -2504,6 +2658,7 @@ var diagramDslSchema = z.object({
2504
2658
  direction: directionSchema.optional(),
2505
2659
  layout: z.object({
2506
2660
  direction: directionSchema.optional(),
2661
+ mode: layoutModeSchema.optional(),
2507
2662
  primaryReadingDirection: primaryReadingDirectionSchema.optional()
2508
2663
  }).optional(),
2509
2664
  routing: z.object({
@@ -2840,13 +2995,22 @@ function exportExcalidraw(diagram, options = {}) {
2840
2995
  appState: {
2841
2996
  name: options.title ?? diagram.title ?? diagram.id,
2842
2997
  viewBackgroundColor: "#ffffff",
2843
- gridSize: null
2998
+ gridSize: null,
2999
+ ...options.viewportPadding === void 0 ? {} : viewportAppState(diagram.bounds, options.viewportPadding)
2844
3000
  },
2845
3001
  files: {}
2846
3002
  };
2847
3003
  return `${JSON.stringify(scene, null, 2)}
2848
3004
  `;
2849
3005
  }
3006
+ function viewportAppState(bounds, padding) {
3007
+ const safePadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
3008
+ return {
3009
+ scrollX: finite(-bounds.x + safePadding),
3010
+ scrollY: finite(-bounds.y + safePadding),
3011
+ zoom: { value: 1 }
3012
+ };
3013
+ }
2850
3014
  function renderGroup(group) {
2851
3015
  return {
2852
3016
  ...baseElement(`group:${group.id}`, "rectangle", group.box),
@@ -3170,6 +3334,9 @@ function exportSvg(diagram, options = {}) {
3170
3334
  return `${[
3171
3335
  `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
3172
3336
  ...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
3337
+ ...options.viewportPadding === void 0 ? [] : [
3338
+ ` <metadata data-dge-viewport="${escapeAttribute(viewportMetadata(diagram.bounds, options.viewportPadding))}"></metadata>`
3339
+ ],
3173
3340
  ` <rect class="background" x="${formatNumber(diagram.bounds.x)}" y="${formatNumber(diagram.bounds.y)}" width="${formatNumber(diagram.bounds.width)}" height="${formatNumber(diagram.bounds.height)}" fill="#ffffff"/>`,
3174
3341
  ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame, annotations))],
3175
3342
  ...(diagram.swimlanes ?? []).flatMap(
@@ -3203,6 +3370,16 @@ function exportSvg(diagram, options = {}) {
3203
3370
  ].join("\n")}
3204
3371
  `;
3205
3372
  }
3373
+ function viewportMetadata(bounds, padding) {
3374
+ const safePadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
3375
+ return JSON.stringify({
3376
+ x: bounds.x - safePadding,
3377
+ y: bounds.y - safePadding,
3378
+ width: bounds.width + safePadding * 2,
3379
+ height: bounds.height + safePadding * 2,
3380
+ padding: safePadding
3381
+ });
3382
+ }
3206
3383
  function renderGroup2(group) {
3207
3384
  return `<rect class="group" data-id="${escapeAttribute(group.id)}" x="${formatNumber(group.box.x)}" y="${formatNumber(group.box.y)}" width="${formatNumber(group.box.width)}" height="${formatNumber(group.box.height)}" fill="${GROUP_FILL}" stroke="${STROKE}" stroke-dasharray="6 4"/>`;
3208
3385
  }
@@ -3877,6 +4054,7 @@ function indentLines(values) {
3877
4054
  // src/ir/diagnostics.ts
3878
4055
  var DELIVERABILITY_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
3879
4056
  "constraints.locked-target-not-moved",
4057
+ "constraints.overlap.locked-conflict",
3880
4058
  "routing.evidence.crossing_forbidden",
3881
4059
  "routing.obstacle.unavoidable",
3882
4060
  "route_obstacle_fallback",
@@ -3888,6 +4066,7 @@ var DEFAULT_OPTIONS = {
3888
4066
  edgesep: 40,
3889
4067
  marginx: 0,
3890
4068
  marginy: 0,
4069
+ componentGap: 160,
3891
4070
  ranker: "network-simplex"
3892
4071
  };
3893
4072
  function runDagreInitialLayout(input) {
@@ -3976,9 +4155,116 @@ function runDagreInitialLayout(input) {
3976
4155
  }
3977
4156
  return { boxes, diagnostics };
3978
4157
  }
4158
+ function runComponentAwareDagreInitialLayout(input) {
4159
+ const options = { ...DEFAULT_OPTIONS, ...input.options };
4160
+ const diagnostics = reportMissingEdgeReferences(input);
4161
+ const validNodeIds = new Set(input.nodes.map((node) => node.id));
4162
+ const validEdges = input.edges.filter(
4163
+ (edge) => validNodeIds.has(edge.sourceId) && validNodeIds.has(edge.targetId)
4164
+ );
4165
+ const components = connectedComponents(input.nodes, validEdges);
4166
+ if (components.length <= 1) {
4167
+ const layout2 = runDagreInitialLayout({ ...input, edges: validEdges });
4168
+ return {
4169
+ boxes: layout2.boxes,
4170
+ diagnostics: [...diagnostics, ...layout2.diagnostics]
4171
+ };
4172
+ }
4173
+ const boxes = /* @__PURE__ */ new Map();
4174
+ let cursor = 0;
4175
+ for (const component of components) {
4176
+ const componentNodeIds = new Set(component.map((node) => node.id));
4177
+ const componentLayout = runDagreInitialLayout({
4178
+ ...input,
4179
+ nodes: component,
4180
+ edges: validEdges.filter(
4181
+ (edge) => componentNodeIds.has(edge.sourceId) && componentNodeIds.has(edge.targetId)
4182
+ )
4183
+ });
4184
+ diagnostics.push(...componentLayout.diagnostics);
4185
+ if (componentLayout.boxes.size === 0) {
4186
+ continue;
4187
+ }
4188
+ const bounds = unionBoxes([...componentLayout.boxes.values()]);
4189
+ const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
4190
+ const dx = axis === "x" ? cursor - bounds.x : -bounds.x;
4191
+ const dy = axis === "y" ? cursor - bounds.y : -bounds.y;
4192
+ for (const [id, box] of componentLayout.boxes) {
4193
+ boxes.set(id, { ...box, x: box.x + dx, y: box.y + dy });
4194
+ }
4195
+ cursor += (axis === "x" ? bounds.width : bounds.height) + options.componentGap;
4196
+ }
4197
+ return { boxes, diagnostics };
4198
+ }
4199
+ function reportMissingEdgeReferences(input) {
4200
+ const validNodeIds = new Set(input.nodes.map((node) => node.id));
4201
+ return input.edges.flatMap((edge) => {
4202
+ if (validNodeIds.has(edge.sourceId) && validNodeIds.has(edge.targetId)) {
4203
+ return [];
4204
+ }
4205
+ return [
4206
+ {
4207
+ severity: "error",
4208
+ code: "layout.edge-reference.missing",
4209
+ message: `Edge ${edge.id} references a missing layout node.`,
4210
+ path: ["edges", edge.id],
4211
+ detail: {
4212
+ edgeId: edge.id,
4213
+ sourceId: edge.sourceId,
4214
+ targetId: edge.targetId
4215
+ }
4216
+ }
4217
+ ];
4218
+ });
4219
+ }
3979
4220
  function isValidDimension(value) {
3980
4221
  return Number.isFinite(value) && value >= 0;
3981
4222
  }
4223
+ function connectedComponents(nodes, edges) {
4224
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
4225
+ const adjacency = new Map(nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
4226
+ for (const edge of edges) {
4227
+ if (!nodeById.has(edge.sourceId) || !nodeById.has(edge.targetId)) {
4228
+ continue;
4229
+ }
4230
+ adjacency.get(edge.sourceId)?.add(edge.targetId);
4231
+ adjacency.get(edge.targetId)?.add(edge.sourceId);
4232
+ }
4233
+ const visited = /* @__PURE__ */ new Set();
4234
+ const components = [];
4235
+ for (const node of [...nodes].sort((a, b) => a.id.localeCompare(b.id))) {
4236
+ if (visited.has(node.id)) {
4237
+ continue;
4238
+ }
4239
+ const ids = [];
4240
+ const stack = [node.id];
4241
+ visited.add(node.id);
4242
+ while (stack.length > 0) {
4243
+ const id = stack.pop();
4244
+ if (id === void 0) {
4245
+ continue;
4246
+ }
4247
+ ids.push(id);
4248
+ for (const neighbor of [...adjacency.get(id) ?? []].sort().reverse()) {
4249
+ if (!visited.has(neighbor)) {
4250
+ visited.add(neighbor);
4251
+ stack.push(neighbor);
4252
+ }
4253
+ }
4254
+ }
4255
+ components.push(
4256
+ ids.sort().flatMap((id) => {
4257
+ const componentNode = nodeById.get(id);
4258
+ return componentNode === void 0 ? [] : [componentNode];
4259
+ })
4260
+ );
4261
+ }
4262
+ return components.sort((a, b) => {
4263
+ const left = a[0]?.id ?? "";
4264
+ const right = b[0]?.id ?? "";
4265
+ return left.localeCompare(right);
4266
+ });
4267
+ }
3982
4268
 
3983
4269
  // src/routing/astar.ts
3984
4270
  function findObstacleFreePath(source, target, obstacles, options = {}, diagnostics) {
@@ -4021,7 +4307,7 @@ function collectXs(source, target, obstacles, margin) {
4021
4307
  for (const obs of obstacles) {
4022
4308
  raw.push(obs.x - margin - 2, obs.x + obs.width + margin + 2);
4023
4309
  }
4024
- const deduped = dedupSorted(raw);
4310
+ const deduped = insertChannelMidpoints(dedupSorted(raw));
4025
4311
  for (const v of [source.x, target.x]) {
4026
4312
  if (!deduped.includes(v)) {
4027
4313
  deduped.push(v);
@@ -4034,7 +4320,7 @@ function collectYs(source, target, obstacles, margin) {
4034
4320
  for (const obs of obstacles) {
4035
4321
  raw.push(obs.y - margin - 2, obs.y + obs.height + margin + 2);
4036
4322
  }
4037
- const deduped = dedupSorted(raw);
4323
+ const deduped = insertChannelMidpoints(dedupSorted(raw));
4038
4324
  for (const v of [source.y, target.y]) {
4039
4325
  if (!deduped.includes(v)) {
4040
4326
  deduped.push(v);
@@ -4053,6 +4339,19 @@ function dedupSorted(values) {
4053
4339
  }
4054
4340
  return result;
4055
4341
  }
4342
+ function insertChannelMidpoints(sorted, minGap = 8) {
4343
+ const result = [];
4344
+ for (let i = 0; i < sorted.length - 1; i++) {
4345
+ const a = sorted[i];
4346
+ const b = sorted[i + 1];
4347
+ result.push(a);
4348
+ if (b - a > minGap) {
4349
+ result.push((a + b) / 2);
4350
+ }
4351
+ }
4352
+ result.push(sorted[sorted.length - 1]);
4353
+ return result.sort((a, b) => a - b);
4354
+ }
4056
4355
  function buildGraph(xs, ys) {
4057
4356
  const nodes = [];
4058
4357
  const nodeIndex = /* @__PURE__ */ new Map();
@@ -4215,10 +4514,36 @@ function areCollinear(a, b, c) {
4215
4514
  }
4216
4515
 
4217
4516
  // src/routing/routes.ts
4517
+ function checkBacktracking(points, source, target, diagnostics) {
4518
+ if (points.length < 2) return;
4519
+ const direct = Math.hypot(target.x - source.x, target.y - source.y);
4520
+ if (direct <= 0) return;
4521
+ let routeLen = 0;
4522
+ for (let i = 0; i < points.length - 1; i++) {
4523
+ const a = points[i];
4524
+ const b = points[i + 1];
4525
+ routeLen += Math.hypot(b.x - a.x, b.y - a.y);
4526
+ }
4527
+ const threshold = 10;
4528
+ if (routeLen > direct * threshold) {
4529
+ diagnostics.push({
4530
+ severity: "warning",
4531
+ code: "routing.backtracking_excessive",
4532
+ message: `Route length ${Math.round(routeLen)} px exceeds ${threshold}\xD7 direct distance ${Math.round(direct)} px.`,
4533
+ detail: {
4534
+ routeLength: Math.round(routeLen),
4535
+ directDistance: Math.round(direct),
4536
+ threshold
4537
+ }
4538
+ });
4539
+ }
4540
+ }
4218
4541
  function routeEdge(input) {
4219
4542
  const diagnostics = [];
4220
4543
  const softObstacles = input.obstacles ?? [];
4221
4544
  const hardObstacles = input.hardObstacles ?? [];
4545
+ const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
4546
+ const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
4222
4547
  const maxAttempts = input.maxRoutingAttempts ?? 5;
4223
4548
  const defaultAnchors = defaultAnchorsForGeometry(
4224
4549
  input.source.box,
@@ -4240,9 +4565,11 @@ function routeEdge(input) {
4240
4565
  [source, target],
4241
4566
  softObstacles,
4242
4567
  hardObstacles,
4243
- diagnostics
4568
+ diagnostics,
4569
+ softObstacleIndex,
4570
+ hardObstacleIndex
4244
4571
  );
4245
- if (routeCrossesBoxes(points, hardObstacles)) {
4572
+ if (routeCrossesBoxes(points, hardObstacles, hardObstacleIndex)) {
4246
4573
  diagnostics.push({
4247
4574
  severity: "error",
4248
4575
  code: "routing.evidence.crossing_forbidden",
@@ -4250,7 +4577,7 @@ function routeEdge(input) {
4250
4577
  });
4251
4578
  return { points, diagnostics };
4252
4579
  }
4253
- if (routeCrossesBoxes(points, softObstacles)) {
4580
+ if (routeCrossesBoxes(points, softObstacles, softObstacleIndex)) {
4254
4581
  diagnostics.push({
4255
4582
  severity: "warning",
4256
4583
  code: "routing.obstacle.unavoidable",
@@ -4289,9 +4616,16 @@ function routeEdge(input) {
4289
4616
  path,
4290
4617
  softObstacles,
4291
4618
  hardObstacles,
4292
- diagnostics
4619
+ diagnostics,
4620
+ softObstacleIndex,
4621
+ hardObstacleIndex
4293
4622
  );
4294
- if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
4623
+ if (!routeIntersectsObstacles(
4624
+ finalized,
4625
+ softObstacles,
4626
+ softObstacleIndex
4627
+ ) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
4628
+ checkBacktracking(finalized, source, target, diagnostics);
4295
4629
  return { points: finalized, diagnostics };
4296
4630
  }
4297
4631
  }
@@ -4331,23 +4665,41 @@ function routeEdge(input) {
4331
4665
  }
4332
4666
  );
4333
4667
  for (const candidate of candidateRoutes) {
4334
- if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
4668
+ if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(
4669
+ candidate.points,
4670
+ softObstacles,
4671
+ softObstacleIndex
4672
+ ) && !routeIntersectsObstacles(
4673
+ candidate.points,
4674
+ hardObstacles,
4675
+ hardObstacleIndex
4676
+ ) && !routeIntersectsEndpointInteriors(
4335
4677
  candidate.points,
4336
4678
  candidate.endpointObstacles
4337
4679
  )) {
4338
- return {
4339
- points: finalizeRoute(
4340
- candidate.points,
4341
- softObstacles,
4342
- hardObstacles,
4343
- diagnostics
4344
- ),
4680
+ const finalizedClean = finalizeRoute(
4681
+ candidate.points,
4682
+ softObstacles,
4683
+ hardObstacles,
4684
+ diagnostics,
4685
+ softObstacleIndex,
4686
+ hardObstacleIndex
4687
+ );
4688
+ checkBacktracking(
4689
+ finalizedClean,
4690
+ candidate.points[0],
4691
+ candidate.points[candidate.points.length - 1],
4345
4692
  diagnostics
4346
- };
4693
+ );
4694
+ return { points: finalizedClean, diagnostics };
4347
4695
  }
4348
4696
  }
4349
4697
  const hardClearCandidate = candidateRoutes.find(
4350
- (candidate) => !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
4698
+ (candidate) => !routeIntersectsObstacles(
4699
+ candidate.points,
4700
+ hardObstacles,
4701
+ hardObstacleIndex
4702
+ ) && !routeIntersectsEndpointInteriors(
4351
4703
  candidate.points,
4352
4704
  candidate.endpointObstacles
4353
4705
  )
@@ -4498,13 +4850,21 @@ function routeEdge(input) {
4498
4850
  diagnostics
4499
4851
  };
4500
4852
  }
4501
- function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4853
+ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics, softObstacleIndex, hardObstacleIndex) {
4502
4854
  const simplified = simplifyRoute2(points);
4503
4855
  if (simplified.length >= 3) {
4504
4856
  return simplified;
4505
4857
  }
4506
- const crossesHardObstacles = routeCrossesBoxes(simplified, hardObstacles);
4507
- const crossesSoftObstacles = routeCrossesBoxes(simplified, softObstacles);
4858
+ const crossesHardObstacles = routeCrossesBoxes(
4859
+ simplified,
4860
+ hardObstacles,
4861
+ hardObstacleIndex
4862
+ );
4863
+ const crossesSoftObstacles = routeCrossesBoxes(
4864
+ simplified,
4865
+ softObstacles,
4866
+ softObstacleIndex
4867
+ );
4508
4868
  if (!crossesHardObstacles && !crossesSoftObstacles) {
4509
4869
  return simplified;
4510
4870
  }
@@ -4512,8 +4872,16 @@ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4512
4872
  ...softObstacles,
4513
4873
  ...hardObstacles
4514
4874
  ]);
4515
- const expandedCrossesHard = routeCrossesBoxes(expanded, hardObstacles);
4516
- const expandedCrossesSoft = routeCrossesBoxes(expanded, softObstacles);
4875
+ const expandedCrossesHard = routeCrossesBoxes(
4876
+ expanded,
4877
+ hardObstacles,
4878
+ hardObstacleIndex
4879
+ );
4880
+ const expandedCrossesSoft = routeCrossesBoxes(
4881
+ expanded,
4882
+ softObstacles,
4883
+ softObstacleIndex
4884
+ );
4517
4885
  if (expandedCrossesHard || expandedCrossesSoft) {
4518
4886
  diagnostics.push({
4519
4887
  severity: expandedCrossesHard ? "error" : "warning",
@@ -4955,15 +5323,20 @@ function sortedUniqueLanes(lanes, midpoint) {
4955
5323
  return distance === 0 ? left - right : distance;
4956
5324
  });
4957
5325
  }
4958
- function routeIntersectsObstacles(points, obstacles) {
4959
- for (let index = 0; index < points.length - 1; index += 1) {
4960
- const a = points[index];
4961
- const b = points[index + 1];
5326
+ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
5327
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
5328
+ const a = points[pointIndex];
5329
+ const b = points[pointIndex + 1];
4962
5330
  if (a === void 0 || b === void 0) {
4963
5331
  continue;
4964
5332
  }
4965
- const segment = segmentBox(a, b);
4966
- for (const obstacle of obstacles) {
5333
+ const segment = segmentBox2(a, b);
5334
+ for (const obstacle of candidateBoxesForSegment(
5335
+ obstacles,
5336
+ a,
5337
+ b,
5338
+ spatialIndex
5339
+ )) {
4967
5340
  validateBox(obstacle);
4968
5341
  if (intersectsAabb(segment, obstacle)) {
4969
5342
  return true;
@@ -4979,7 +5352,7 @@ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4979
5352
  if (a === void 0 || b === void 0) {
4980
5353
  continue;
4981
5354
  }
4982
- const segment = segmentBox(a, b);
5355
+ const segment = segmentBox2(a, b);
4983
5356
  for (const endpointInterior of endpointInteriors) {
4984
5357
  validateBox(endpointInterior);
4985
5358
  if (intersectsAabb(segment, endpointInterior)) {
@@ -4989,14 +5362,19 @@ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4989
5362
  }
4990
5363
  return false;
4991
5364
  }
4992
- function routeCrossesBoxes(points, obstacles) {
4993
- for (let index = 0; index < points.length - 1; index += 1) {
4994
- const a = points[index];
4995
- const b = points[index + 1];
5365
+ function routeCrossesBoxes(points, obstacles, spatialIndex) {
5366
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
5367
+ const a = points[pointIndex];
5368
+ const b = points[pointIndex + 1];
4996
5369
  if (a === void 0 || b === void 0) {
4997
5370
  continue;
4998
5371
  }
4999
- for (const obstacle of obstacles) {
5372
+ for (const obstacle of candidateBoxesForSegment(
5373
+ obstacles,
5374
+ a,
5375
+ b,
5376
+ spatialIndex
5377
+ )) {
5000
5378
  validateBox(obstacle);
5001
5379
  if (segmentIntersectsBox(a, b, obstacle)) {
5002
5380
  return true;
@@ -5005,6 +5383,12 @@ function routeCrossesBoxes(points, obstacles) {
5005
5383
  }
5006
5384
  return false;
5007
5385
  }
5386
+ function candidateBoxesForSegment(obstacles, start, end, index) {
5387
+ return index === void 0 ? obstacles : querySegmentSpatialIndex(index, start, end).map((entry) => entry.box);
5388
+ }
5389
+ function indexedBoxes(obstacles) {
5390
+ return obstacles.map((box, index) => ({ id: `obstacle:${index}`, box }));
5391
+ }
5008
5392
  function segmentIntersectsBox(start, end, box) {
5009
5393
  const left = box.x;
5010
5394
  const right = box.x + box.width;
@@ -5038,7 +5422,7 @@ function segmentIntersectsBoxEdge(start, end, x1, y1, x2, y2) {
5038
5422
  const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
5039
5423
  return t > 0 && t < 1 && u > 0 && u < 1;
5040
5424
  }
5041
- function segmentBox(a, b) {
5425
+ function segmentBox2(a, b) {
5042
5426
  const minX = Math.min(a.x, b.x);
5043
5427
  const minY = Math.min(a.y, b.y);
5044
5428
  return {
@@ -5110,17 +5494,16 @@ function solveDiagram(diagram, options = {}) {
5110
5494
  (swimlane) => enhanceSwimlaneCjkTypography(swimlane, cjkTypography, diagnostics)
5111
5495
  );
5112
5496
  const constraints = stableByConstraintId(diagram.constraints);
5113
- const layout2 = runDagreInitialLayout({
5497
+ const initialLayoutMode = options.initialLayout ?? "dagre";
5498
+ const layout2 = runInitialLayout({
5499
+ mode: initialLayoutMode,
5500
+ componentAware: options.maxStackDepth === void 0,
5114
5501
  direction: diagram.direction,
5115
- nodes: styledNodes.map((node) => ({ id: node.id, size: node.size })),
5116
- edges: styledEdges.map((edge) => ({
5117
- id: edge.id,
5118
- sourceId: edge.source.nodeId,
5119
- targetId: edge.target.nodeId
5120
- }))
5502
+ nodes: styledNodes,
5503
+ edges: styledEdges
5121
5504
  });
5122
5505
  diagnostics.push(...layout2.diagnostics);
5123
- const initialNodeBoxes = wrapVerticalStackIfNeeded(
5506
+ const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
5124
5507
  layout2.boxes,
5125
5508
  styledNodes,
5126
5509
  styledEdges,
@@ -5380,6 +5763,84 @@ function solveDiagram(diagram, options = {}) {
5380
5763
  function solveDiagramSafe(diagram, options = {}) {
5381
5764
  return solveDiagram(diagram, { ...options, prefitLabelSize: true });
5382
5765
  }
5766
+ function runInitialLayout(input) {
5767
+ if (input.mode === "positions") {
5768
+ return runPositionSeededInitialLayout(input);
5769
+ }
5770
+ const runAutoLayout = input.componentAware ? runComponentAwareDagreInitialLayout : runDagreInitialLayout;
5771
+ return runAutoLayout({
5772
+ direction: input.direction,
5773
+ nodes: input.nodes.map((node) => ({ id: node.id, size: node.size })),
5774
+ edges: input.edges.map((edge) => ({
5775
+ id: edge.id,
5776
+ sourceId: edge.source.nodeId,
5777
+ targetId: edge.target.nodeId
5778
+ }))
5779
+ });
5780
+ }
5781
+ function runPositionSeededInitialLayout(input) {
5782
+ const diagnostics = [];
5783
+ const boxes = /* @__PURE__ */ new Map();
5784
+ const autoNodes = [];
5785
+ for (const node of input.nodes) {
5786
+ if (!isValidInitialDimension(node.size.width) || !isValidInitialDimension(node.size.height)) {
5787
+ diagnostics.push({
5788
+ severity: "error",
5789
+ code: "layout.node-size.invalid",
5790
+ message: `Node ${node.id} has invalid layout dimensions.`,
5791
+ path: ["nodes", node.id, "size"],
5792
+ detail: { nodeId: node.id }
5793
+ });
5794
+ continue;
5795
+ }
5796
+ if (node.position === void 0) {
5797
+ autoNodes.push(node);
5798
+ continue;
5799
+ }
5800
+ if (!isFiniteInitialPoint(node.position)) {
5801
+ diagnostics.push({
5802
+ severity: "error",
5803
+ code: "layout.node-position.invalid",
5804
+ message: `Node ${node.id} has an invalid seeded position.`,
5805
+ path: ["nodes", node.id, "position"],
5806
+ detail: { nodeId: node.id }
5807
+ });
5808
+ continue;
5809
+ }
5810
+ boxes.set(node.id, {
5811
+ x: node.position.x,
5812
+ y: node.position.y,
5813
+ width: node.size.width,
5814
+ height: node.size.height
5815
+ });
5816
+ }
5817
+ if (autoNodes.length === 0) {
5818
+ return { boxes, diagnostics };
5819
+ }
5820
+ const autoNodeIds = new Set(autoNodes.map((node) => node.id));
5821
+ const autoLayout = runComponentAwareDagreInitialLayout({
5822
+ direction: input.direction,
5823
+ nodes: autoNodes.map((node) => ({ id: node.id, size: node.size })),
5824
+ edges: input.edges.filter(
5825
+ (edge) => autoNodeIds.has(edge.source.nodeId) && autoNodeIds.has(edge.target.nodeId)
5826
+ ).map((edge) => ({
5827
+ id: edge.id,
5828
+ sourceId: edge.source.nodeId,
5829
+ targetId: edge.target.nodeId
5830
+ }))
5831
+ });
5832
+ diagnostics.push(...autoLayout.diagnostics);
5833
+ for (const [id, box] of autoLayout.boxes) {
5834
+ boxes.set(id, box);
5835
+ }
5836
+ return { boxes, diagnostics };
5837
+ }
5838
+ function isValidInitialDimension(value) {
5839
+ return Number.isFinite(value) && value >= 0;
5840
+ }
5841
+ function isFiniteInitialPoint(point2) {
5842
+ return Number.isFinite(point2.x) && Number.isFinite(point2.y);
5843
+ }
5383
5844
  function prefitNodeLabelSize(node, options, diagnostics) {
5384
5845
  if (node.label === void 0) {
5385
5846
  return node;
@@ -7131,6 +7592,10 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7131
7592
  const coordinatedNodeById = new Map(
7132
7593
  coordinatedNodes.map((node) => [node.id, node])
7133
7594
  );
7595
+ const nodeObstacleIndex = createBoxSpatialIndex(
7596
+ obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
7597
+ options.routingGutter ?? 160
7598
+ );
7134
7599
  for (const edge of edges) {
7135
7600
  const source = nodes.get(edge.source.nodeId);
7136
7601
  const target = nodes.get(edge.target.nodeId);
@@ -7151,6 +7616,14 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7151
7616
  const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
7152
7617
  const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
7153
7618
  const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
7619
+ const corridor = edgeCorridorBox(
7620
+ source.box,
7621
+ target.box,
7622
+ options.routingGutter ?? 160
7623
+ );
7624
+ const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
7625
+ (obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
7626
+ );
7154
7627
  const route = routeEdge({
7155
7628
  kind: options.routeKind ?? "orthogonal",
7156
7629
  direction,
@@ -7159,9 +7632,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7159
7632
  ...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
7160
7633
  ...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
7161
7634
  obstacles: [
7162
- ...obstacles.filter(
7163
- (obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
7164
- ),
7635
+ ...routeNodeObstacles,
7165
7636
  ...softObstacles,
7166
7637
  ...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
7167
7638
  ...routeTextObstacles
@@ -7182,6 +7653,19 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7182
7653
  }
7183
7654
  return coordinated;
7184
7655
  }
7656
+ function edgeCorridorBox(source, target, margin) {
7657
+ const minX = Math.min(source.x, target.x);
7658
+ const minY = Math.min(source.y, target.y);
7659
+ const maxX = Math.max(source.x + source.width, target.x + target.width);
7660
+ const maxY = Math.max(source.y + source.height, target.y + target.height);
7661
+ return expandBoxForQuery(
7662
+ { x: minX, y: minY, width: maxX - minX, height: maxY - minY },
7663
+ margin
7664
+ );
7665
+ }
7666
+ function sameBox(first, second) {
7667
+ return first.x === second.x && first.y === second.y && first.width === second.width && first.height === second.height;
7668
+ }
7185
7669
  function isEdgeConnectedTextAnnotation(edge, annotation) {
7186
7670
  switch (annotation.surfaceKind) {
7187
7671
  case "edge-label":
@@ -8070,6 +8554,7 @@ function renderDiagramDsl(source, options = {}) {
8070
8554
  return { diagnostics };
8071
8555
  }
8072
8556
  const solved = solveDiagram(normalized.diagram, {
8557
+ ...solveInitialLayoutOption(normalized.diagram.metadata?.initialLayout),
8073
8558
  routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : normalized.diagram.metadata?.routeKind === "obstacle-avoiding" ? "obstacle-avoiding" : "orthogonal",
8074
8559
  ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
8075
8560
  ...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
@@ -8111,6 +8596,9 @@ function renderDiagramDsl(source, options = {}) {
8111
8596
  function toSolveDiagnostic(diagnostic) {
8112
8597
  return { ...diagnostic, layer: "solve" };
8113
8598
  }
8599
+ function solveInitialLayoutOption(value) {
8600
+ return value === "positions" ? { initialLayout: "positions" } : {};
8601
+ }
8114
8602
  function solvePortShiftingOption(value) {
8115
8603
  if (!isJsonObject(value)) {
8116
8604
  return {};
@@ -8260,6 +8748,6 @@ function isPointLikeRecord(value) {
8260
8748
  return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
8261
8749
  }
8262
8750
 
8263
- export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createDefaultTextMeasurer, expandBox, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, parseDiagramDsl, parseEdgeShorthand, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
8751
+ export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
8264
8752
  //# sourceMappingURL=index.js.map
8265
8753
  //# sourceMappingURL=index.js.map