@statelyai/layout 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +71 -2
  2. package/dist/elkjs/index.cjs +3 -2
  3. package/dist/elkjs/index.d.cts +11 -2
  4. package/dist/elkjs/index.d.mts +11 -2
  5. package/dist/elkjs/index.mjs +3 -3
  6. package/dist/elkjs/main.cjs +2 -2
  7. package/dist/elkjs/main.d.cts +2 -2
  8. package/dist/elkjs/main.d.mts +2 -2
  9. package/dist/elkjs/main.mjs +2 -2
  10. package/dist/elkjs/worker-api.cjs +1 -1
  11. package/dist/elkjs/worker-api.d.cts +2 -2
  12. package/dist/elkjs/worker-api.d.mts +2 -2
  13. package/dist/elkjs/worker-api.mjs +1 -1
  14. package/dist/elkjs/worker.cjs +2 -2
  15. package/dist/elkjs/worker.mjs +2 -2
  16. package/dist/{elkjs-CFlfkEBz.mjs → elkjs-DaHMISnQ.mjs} +152 -3
  17. package/dist/{elkjs-BVnDwnBQ.cjs → elkjs-dMghiiWW.cjs} +157 -2
  18. package/dist/index-BEA_pzeT.d.cts +72 -0
  19. package/dist/{index-BSH2bloA.d.mts → index-D9dhxrRl.d.cts} +35 -3
  20. package/dist/index-DNTLRsDl.d.mts +72 -0
  21. package/dist/{index-CG712jzV.d.cts → index-DNixtE3h.d.mts} +35 -3
  22. package/dist/index.cjs +36 -9
  23. package/dist/index.d.cts +4 -3
  24. package/dist/index.d.mts +4 -3
  25. package/dist/index.mjs +4 -3
  26. package/dist/layered/index.cjs +1 -1
  27. package/dist/layered/index.d.cts +3 -2
  28. package/dist/layered/index.d.mts +3 -2
  29. package/dist/layered/index.mjs +1 -1
  30. package/dist/{layered-Cf9Rn8l7.cjs → layered-B2buJqMf.cjs} +417 -469
  31. package/dist/{layered-Bz2nWmVp.mjs → layered-DGFTeQSl.mjs} +391 -431
  32. package/dist/{layout-engine-WbC0YjjT.mjs → layout-engine-Cs5tatA6.mjs} +1 -1
  33. package/dist/{layout-engine-BNex9jxg.cjs → layout-engine-Dn1PiFwP.cjs} +1 -1
  34. package/dist/routing/index.cjs +28 -0
  35. package/dist/routing/index.d.cts +3 -0
  36. package/dist/routing/index.d.mts +3 -0
  37. package/dist/routing/index.mjs +3 -0
  38. package/dist/routing-A2hrK9MP.mjs +2396 -0
  39. package/dist/routing-ujEP7Qh6.cjs +2606 -0
  40. package/dist/types-CT2akdK0.d.mts +133 -0
  41. package/dist/types-uxPlGRof.d.cts +133 -0
  42. package/dist/{worker-client-DAT_z2OR.d.mts → worker-client-BwGGRD5y.d.mts} +1 -1
  43. package/dist/{worker-client-DVhklF6A.d.cts → worker-client-D4zNyPEI.d.cts} +1 -1
  44. package/package.json +7 -2
  45. package/src/authoring/coordinates.ts +10 -2
  46. package/src/elkjs/index.ts +5 -3
  47. package/src/elkjs/routes.ts +147 -0
  48. package/src/index.ts +6 -0
  49. package/src/layered/compound.ts +417 -0
  50. package/src/layered/index.ts +27 -102
  51. package/src/layered/types.ts +21 -0
  52. package/src/routing/adapters.ts +111 -0
  53. package/src/routing/algorithms.ts +676 -0
  54. package/src/routing/coordination.ts +86 -0
  55. package/src/routing/engine.ts +473 -0
  56. package/src/routing/index.ts +68 -0
  57. package/src/routing/layout-cache.ts +65 -0
  58. package/src/routing/model.ts +218 -0
  59. package/src/routing/path.ts +324 -0
  60. package/src/routing/persistent.ts +117 -0
  61. package/src/routing/search.ts +276 -0
  62. package/src/routing/spatial.ts +78 -0
  63. package/src/routing/types.ts +146 -0
  64. /package/dist/{public-types-B8rAALJq.d.mts → public-types-9NCPoqbp.d.mts} +0 -0
  65. /package/dist/{public-types-CVB0hXGk.d.cts → public-types-BcRZoJE3.d.cts} +0 -0
  66. /package/dist/{worker-client-DkwS4sHx.mjs → worker-client-BNjwT1AJ.mjs} +0 -0
  67. /package/dist/{worker-client-D3HMK33r.cjs → worker-client-D4oEvhAl.cjs} +0 -0
@@ -1,21 +1,6 @@
1
+ const require_routing = require('./routing-ujEP7Qh6.cjs');
1
2
  let _statelyai_graph_layout = require("@statelyai/graph/layout");
2
3
 
3
- //#region src/errors.ts
4
- var LayoutError = class extends Error {
5
- constructor(message, code) {
6
- super(message);
7
- this.code = code;
8
- this.name = "LayoutError";
9
- }
10
- };
11
- var UnsupportedLayoutError = class extends LayoutError {
12
- constructor(message) {
13
- super(message, "UNSUPPORTED_LAYOUT");
14
- this.name = "UnsupportedLayoutError";
15
- }
16
- };
17
-
18
- //#endregion
19
4
  //#region src/java-random.ts
20
5
  /** Java's 48-bit `java.util.Random`, used by ELK for seeded tie-breaking. */
21
6
  var JavaRandom = class JavaRandom {
@@ -597,7 +582,7 @@ const assignLayersByLongestPath = (input, orientation) => {
597
582
  if (sourceId === targetId) continue;
598
583
  const sourceConstraint = input.constrainedLayerByNodeId.get(sourceId);
599
584
  const targetConstraint = input.constrainedLayerByNodeId.get(targetId);
600
- if (sourceConstraint !== void 0 && targetConstraint !== void 0 && targetConstraint <= sourceConstraint) throw new LayoutError(`Layer constraints conflict on edge ${edge.id}`, "UNSATISFIABLE_CONSTRAINTS");
585
+ if (sourceConstraint !== void 0 && targetConstraint !== void 0 && targetConstraint <= sourceConstraint) throw new require_routing.LayoutError(`Layer constraints conflict on edge ${edge.id}`, "UNSATISFIABLE_CONSTRAINTS");
601
586
  successors.get(sourceId)?.push(targetId);
602
587
  indegree.set(targetId, (indegree.get(targetId) ?? 0) + 1);
603
588
  }
@@ -608,7 +593,7 @@ const assignLayersByLongestPath = (input, orientation) => {
608
593
  const sourceLayer = layerByNodeId.get(sourceId) ?? 0;
609
594
  for (const targetId of successors.get(sourceId) ?? []) {
610
595
  const targetConstraint = input.constrainedLayerByNodeId.get(targetId);
611
- if (targetConstraint !== void 0 && targetConstraint < sourceLayer + 1) throw new LayoutError(`Layer constraint conflicts at node ${targetId}`, "UNSATISFIABLE_CONSTRAINTS");
596
+ if (targetConstraint !== void 0 && targetConstraint < sourceLayer + 1) throw new require_routing.LayoutError(`Layer constraint conflicts at node ${targetId}`, "UNSATISFIABLE_CONSTRAINTS");
612
597
  layerByNodeId.set(targetId, targetConstraint ?? Math.max(layerByNodeId.get(targetId) ?? 0, sourceLayer + 1));
613
598
  const nextIndegree = (indegree.get(targetId) ?? 1) - 1;
614
599
  indegree.set(targetId, nextIndegree);
@@ -2229,15 +2214,15 @@ function implicitEdgeEndpoints(input, placement, orientation) {
2229
2214
  const hypernode = nodeSettings?.hypernode;
2230
2215
  const unzipping = (input.settings["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING";
2231
2216
  const crossingStrategy = input.settings["crossingMinimization.strategy"] ?? "LAYER_SWEEP";
2232
- const interactiveTargetOrder = crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "target") && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2233
- const verticalNoneTargetOrder = !horizontal && crossingStrategy === "NONE" && input.settings["layering.strategy"] === "STRETCH_WIDTH" && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "target") && !input.graph.edges.some(({ sourceId }) => sourceId === nodeId) && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2234
- const interactiveLeftSourceOrder = crossingStrategy === "INTERACTIVE" && input.direction === "left" && entries.length >= 3 && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "source") ? [...entries].sort((left, right) => {
2217
+ const interactiveTargetOrder = crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint }) => endpoint === "target") && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2218
+ const verticalNoneTargetOrder = !horizontal && crossingStrategy === "NONE" && input.settings["layering.strategy"] === "STRETCH_WIDTH" && entries.every(({ endpoint }) => endpoint === "target") && !input.graph.edges.some(({ sourceId }) => sourceId === nodeId) && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
2219
+ const interactiveLeftSourceOrder = crossingStrategy === "INTERACTIVE" && input.direction === "left" && entries.length >= 3 && entries.every(({ endpoint }) => endpoint === "source") ? [...entries].sort((left, right) => {
2235
2220
  const leftRect = placement.rectByNodeId.get(left.edge.targetId);
2236
2221
  const rightRect = placement.rectByNodeId.get(right.edge.targetId);
2237
2222
  const leftCross = (leftRect?.y ?? 0) + (leftRect?.height ?? 0) / 2;
2238
2223
  return (rightRect?.y ?? 0) + (rightRect?.height ?? 0) / 2 - leftCross || (edgeModelOrder.get(left.edge.id) ?? 0) - (edgeModelOrder.get(right.edge.id) ?? 0);
2239
2224
  }).map(({ edge }) => edge.id) : void 0;
2240
- const interactiveForwardLongEdgeSource = input.direction !== "left" && crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint: endpoint$1 }) => endpoint$1 === "source") && entries.some(({ edge }) => edge.targetId.startsWith("__layout_dummy:"));
2225
+ const interactiveForwardLongEdgeSource = input.direction !== "left" && crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint }) => endpoint === "source") && entries.some(({ edge }) => edge.targetId.startsWith("__layout_dummy:"));
2241
2226
  const layerOrdered = unzipping || interactiveTargetOrder || verticalNoneTargetOrder || input.settings.hierarchyHandling !== "INCLUDE_CHILDREN" && (crossingStrategy === "LAYER_SWEEP" || crossingStrategy === "MEDIAN_LAYER_SWEEP");
2242
2227
  entries.sort((left, right) => {
2243
2228
  if (interactiveLeftSourceOrder) {
@@ -2290,8 +2275,8 @@ function implicitEdgeEndpoints(input, placement, orientation) {
2290
2275
  const constraints = nodeSettings?.portConstraints;
2291
2276
  const portSpacing = Number(input.settings["spacing.portPort"] ?? 10);
2292
2277
  const fixedWrapAnchor = nodeId.startsWith("__layout_dummy:wrap:");
2293
- entries.forEach(({ edge, endpoint: endpoint$1 }, index) => {
2294
- const ordinal = endpoint$1 === "target" && input.settings["crossingMinimization.strategy"] !== "INTERACTIVE" && !layerOrdered && !(input.settings.directionCongruency === "ROTATION" && (input.direction === "down" || input.direction === "left")) ? entries.length - index : index + 1;
2278
+ entries.forEach(({ edge, endpoint }, index) => {
2279
+ const ordinal = endpoint === "target" && input.settings["crossingMinimization.strategy"] !== "INTERACTIVE" && !layerOrdered && !(input.settings.directionCongruency === "ROTATION" && (input.direction === "down" || input.direction === "left")) ? entries.length - index : index + 1;
2295
2280
  const orderedIndex = ordinal - 1;
2296
2281
  const crossPosition = mergeEdges || hypernode === true ? axisSize / 2 : constraints === "FIXED_RATIO" || constraints === "FIXED_POS" ? 0 : alignment === "BEGIN" ? axisStart + orderedIndex * portSpacing : alignment === "END" ? axisStart + availableAxisSize - (entries.length - orderedIndex - 1) * portSpacing : alignment === "CENTER" ? axisStart + (availableAxisSize - (entries.length - 1) * portSpacing) / 2 + orderedIndex * portSpacing : alignment === "JUSTIFIED" ? entries.length === 1 ? axisStart + availableAxisSize / 2 : axisStart + orderedIndex * availableAxisSize / (entries.length - 1) : axisStart + availableAxisSize * ordinal / (entries.length + 1);
2297
2282
  let point = horizontal ? {
@@ -2312,7 +2297,7 @@ function implicitEdgeEndpoints(input, placement, orientation) {
2312
2297
  source: point,
2313
2298
  target: point
2314
2299
  };
2315
- pair[endpoint$1] = point;
2300
+ pair[endpoint] = point;
2316
2301
  result.set(edge.id, pair);
2317
2302
  });
2318
2303
  }
@@ -2497,12 +2482,12 @@ function routeEdges(style) {
2497
2482
  flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
2498
2483
  }
2499
2484
  const increasing = input.direction === "right" || input.direction === "down";
2500
- const flowPortProtrusion = (edge, endpoint$1) => {
2501
- const node = nodeById.get(endpoint$1 === "source" ? edge.sourceId : edge.targetId);
2502
- const portName = endpoint$1 === "source" ? edge.sourcePort : edge.targetPort;
2485
+ const flowPortProtrusion = (edge, endpoint) => {
2486
+ const node = nodeById.get(endpoint === "source" ? edge.sourceId : edge.targetId);
2487
+ const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
2503
2488
  const port = node?.ports?.find((candidate) => candidate.name === portName);
2504
2489
  if (!node || !port) return 0;
2505
- return input.portSettings?.(port, node)?.["port.side"] === (horizontal ? endpoint$1 === "source" ? input.direction === "right" ? "EAST" : "WEST" : input.direction === "right" ? "WEST" : "EAST" : endpoint$1 === "source" ? input.direction === "down" ? "SOUTH" : "NORTH" : input.direction === "down" ? "NORTH" : "SOUTH") ? horizontal ? port.width ?? 0 : port.height ?? 0 : 0;
2490
+ return input.portSettings?.(port, node)?.["port.side"] === (horizontal ? endpoint === "source" ? input.direction === "right" ? "EAST" : "WEST" : input.direction === "right" ? "WEST" : "EAST" : endpoint === "source" ? input.direction === "down" ? "SOUTH" : "NORTH" : input.direction === "down" ? "NORTH" : "SOUTH") ? horizontal ? port.width ?? 0 : port.height ?? 0 : 0;
2506
2491
  };
2507
2492
  const adjacentNodeIds = /* @__PURE__ */ new Map();
2508
2493
  for (const edge of input.graph.edges) {
@@ -3821,108 +3806,355 @@ function getPolylineMidpoint(points) {
3821
3806
  }
3822
3807
 
3823
3808
  //#endregion
3824
- //#region src/authoring/coordinates.ts
3825
- /** Native compound nodes and sibling edges are parent-relative; crossing edges are world-relative. */
3826
- function frames(graph) {
3827
- const nodes = new Map(graph.nodes.map((n) => [n.id, n]));
3828
- const offsets = /* @__PURE__ */ new Map();
3829
- const visiting = /* @__PURE__ */ new Set();
3830
- function offset(id) {
3831
- const cached = offsets.get(id);
3832
- if (cached) return cached;
3833
- if (visiting.has(id)) throw new LayoutError(`Cyclic parent hierarchy at ${id}`, "INVALID_GRAPH");
3834
- visiting.add(id);
3835
- const parentId = nodes.get(id)?.parentId;
3836
- if (parentId == null) {
3837
- const origin = {
3838
- x: 0,
3839
- y: 0
3840
- };
3841
- visiting.delete(id);
3842
- offsets.set(id, origin);
3843
- return origin;
3844
- }
3845
- const parent = nodes.get(parentId);
3846
- const ancestor = offset(parentId);
3847
- const result = {
3848
- x: ancestor.x + parent.x,
3849
- y: ancestor.y + parent.y
3809
+ //#region src/layered/compound.ts
3810
+ const paddingOf = (value) => ({
3811
+ top: typeof value === "number" ? value : value?.top ?? 12,
3812
+ right: typeof value === "number" ? value : value?.right ?? 12,
3813
+ bottom: typeof value === "number" ? value : value?.bottom ?? 12,
3814
+ left: typeof value === "number" ? value : value?.left ?? 12
3815
+ });
3816
+ /** Solve each scope's occupied envelope bottom-up; never repair returned sibling positions. */
3817
+ function layoutCompounds(graph, options, flat) {
3818
+ const nodes = graph.nodes.map((node) => {
3819
+ const size = options.measure?.(node);
3820
+ return {
3821
+ ...node,
3822
+ width: size?.width ?? node.width ?? 0,
3823
+ height: size?.height ?? node.height ?? 0
3850
3824
  };
3851
- visiting.delete(id);
3852
- offsets.set(id, result);
3853
- return result;
3825
+ });
3826
+ const byId = new Map(nodes.map((node) => [node.id, node]));
3827
+ const ancestry = (id) => {
3828
+ const chain = [];
3829
+ let current = id;
3830
+ while (current != null) {
3831
+ if (chain.includes(current)) throw new Error(`Cyclic compound hierarchy at ${current}`);
3832
+ chain.push(current);
3833
+ current = byId.get(current)?.parentId ?? null;
3834
+ }
3835
+ return [...chain, null];
3836
+ };
3837
+ const branch = (id, scope) => {
3838
+ const chain = ancestry(id);
3839
+ return chain[chain.indexOf(scope) - 1];
3840
+ };
3841
+ const owner = (edge) => {
3842
+ const a = ancestry(edge.sourceId), b = ancestry(edge.targetId);
3843
+ if (edge.sourceId !== edge.targetId && b.includes(edge.sourceId)) return edge.sourceId;
3844
+ if (edge.sourceId !== edge.targetId && a.includes(edge.targetId)) return edge.targetId;
3845
+ return a.slice(1).find((id) => b.slice(1).includes(id)) ?? null;
3846
+ };
3847
+ const owned = /* @__PURE__ */ new Map();
3848
+ for (const edge of graph.edges) {
3849
+ const scope = owner(edge);
3850
+ owned.set(scope, [...owned.get(scope) ?? [], edge]);
3854
3851
  }
3855
- for (const node of graph.nodes) offsets.set(node.id, offset(node.id));
3856
- return {
3857
- nodes: offsets,
3858
- edges: new Map(graph.edges.map((edge) => {
3859
- const source = nodes.get(edge.sourceId);
3860
- const target = nodes.get(edge.targetId);
3861
- return [edge.id, (source.parentId ?? null) === (target.parentId ?? null) ? offset(source.id) : {
3862
- x: 0,
3863
- y: 0
3852
+ const edges = new Map(graph.edges.map((e) => [e.id, {
3853
+ ...e,
3854
+ x: 0,
3855
+ y: 0,
3856
+ width: e.width ?? 0,
3857
+ height: e.height ?? 0
3858
+ }]));
3859
+ const localLabels = /* @__PURE__ */ new Map();
3860
+ const compoundGeometry = /* @__PURE__ */ new Map();
3861
+ const childScopes = [...new Set(nodes.flatMap((n) => n.parentId == null ? [] : [n.parentId]))].sort((a, b) => ancestry(b).length - ancestry(a).length);
3862
+ const solve = (scope) => {
3863
+ const children = nodes.filter((n) => (n.parentId ?? null) === scope);
3864
+ if (!children.length) return;
3865
+ const container = scope == null ? void 0 : byId.get(scope);
3866
+ const config = container && options.compound?.(container);
3867
+ const direction = config?.direction ?? options.direction ?? graph.direction ?? "right";
3868
+ const horizontal = direction === "left" || direction === "right";
3869
+ const padding = paddingOf(config?.padding ?? options.padding);
3870
+ const insets = { ...padding };
3871
+ const header = config?.header;
3872
+ const side = header?.side ?? "top";
3873
+ if (header) insets[side] += side === "top" || side === "bottom" ? header.height : header.width;
3874
+ const scopeEdges = owned.get(scope) ?? [];
3875
+ const boundary = /* @__PURE__ */ new Map();
3876
+ const projected = [];
3877
+ for (const edge of scopeEdges) if (edge.sourceId === scope || edge.targetId === scope) {
3878
+ const id = branch(edge.sourceId === scope ? edge.targetId : edge.sourceId, scope);
3879
+ boundary.set(id, [...boundary.get(id) ?? [], edge]);
3880
+ } else projected.push({
3881
+ ...edge,
3882
+ sourceId: branch(edge.sourceId, scope),
3883
+ targetId: branch(edge.targetId, scope),
3884
+ points: void 0
3885
+ });
3886
+ const gap = options.settings?.["spacing.labelNode"] ?? options.spacing?.node ?? 20;
3887
+ const labelGap = options.settings?.["spacing.labelLabel"] ?? 8;
3888
+ const sizes = new Map(children.map((n) => {
3889
+ const labels$1 = boundary.get(n.id) ?? [];
3890
+ const labelWidth = Math.max(0, ...labels$1.map((e) => e.width ?? 0));
3891
+ const labelHeight = labels$1.reduce((sum, e) => sum + (e.height ?? 0), 0) + Math.max(0, labels$1.length - 1) * labelGap;
3892
+ return [n.id, {
3893
+ width: labels$1.length && !horizontal ? n.width + gap + labelWidth : Math.max(n.width, labelWidth),
3894
+ height: labels$1.length && horizontal ? n.height + gap + labelHeight : Math.max(n.height, labelHeight)
3864
3895
  }];
3865
- }))
3896
+ }));
3897
+ const labels = projected.filter((edge) => (edge.width ?? 0) > 0 && (edge.height ?? 0) > 0);
3898
+ const used = new Set([...graph.nodes, ...graph.edges].map((entity) => entity.id));
3899
+ const labelIds = /* @__PURE__ */ new Map();
3900
+ for (const edge of labels) {
3901
+ let id = `__layout_label_${edge.id}`;
3902
+ while ([
3903
+ id,
3904
+ `${id}_source`,
3905
+ `${id}_target`
3906
+ ].some((key) => used.has(key))) id += "_";
3907
+ for (const key of [
3908
+ id,
3909
+ `${id}_source`,
3910
+ `${id}_target`
3911
+ ]) used.add(key);
3912
+ labelIds.set(edge.id, id);
3913
+ sizes.set(id, {
3914
+ width: edge.width,
3915
+ height: edge.height
3916
+ });
3917
+ }
3918
+ const originalEdges = new Map(graph.edges.map((edge) => [edge.id, edge]));
3919
+ for (const edge of labels) {
3920
+ originalEdges.set(`${labelIds.get(edge.id)}_source`, edges.get(edge.id));
3921
+ originalEdges.set(`${labelIds.get(edge.id)}_target`, edges.get(edge.id));
3922
+ }
3923
+ const placementOrder = new Map(children.map((node, index) => [node.id, index]));
3924
+ for (const edge of labels) placementOrder.set(labelIds.get(edge.id), ((placementOrder.get(edge.sourceId) ?? 0) + (placementOrder.get(edge.targetId) ?? 0)) / 2 + (edge.sourceId === edge.targetId ? .25 : 0));
3925
+ const result$1 = flat({
3926
+ ...graph,
3927
+ nodes: [...children.map((n) => ({
3928
+ ...n,
3929
+ parentId: null
3930
+ })), ...labels.map((edge) => ({
3931
+ type: "node",
3932
+ id: labelIds.get(edge.id),
3933
+ data: void 0
3934
+ }))].sort((a, b) => placementOrder.get(a.id) - placementOrder.get(b.id)),
3935
+ edges: projected.flatMap((edge) => {
3936
+ const labelId = labelIds.get(edge.id);
3937
+ return labelId ? [{
3938
+ ...edge,
3939
+ id: `${labelId}_source`,
3940
+ targetId: labelId,
3941
+ targetPort: void 0,
3942
+ width: 0,
3943
+ height: 0
3944
+ }, {
3945
+ ...edge,
3946
+ id: `${labelId}_target`,
3947
+ sourceId: labelId,
3948
+ sourcePort: void 0,
3949
+ width: 0,
3950
+ height: 0
3951
+ }] : [edge];
3952
+ })
3953
+ }, {
3954
+ ...options,
3955
+ compound: void 0,
3956
+ direction,
3957
+ padding: insets,
3958
+ measure: (node) => sizes.get(node.id),
3959
+ nodeSettings: (node) => {
3960
+ const original = byId.get(node.id);
3961
+ return original ? options.nodeSettings?.(original) : void 0;
3962
+ },
3963
+ edgeSettings: (edge) => options.edgeSettings?.(originalEdges.get(edge.id) ?? edge)
3964
+ });
3965
+ for (const placed of result$1.nodes) {
3966
+ const node = byId.get(placed.id);
3967
+ if (node) Object.assign(node, {
3968
+ x: placed.x,
3969
+ y: placed.y,
3970
+ ports: placed.ports
3971
+ });
3972
+ }
3973
+ for (const edge of projected) {
3974
+ const labelId = labelIds.get(edge.id);
3975
+ if (labelId) {
3976
+ const label = result$1.nodes.find((n) => n.id === labelId);
3977
+ localLabels.set(edge.id, {
3978
+ scope,
3979
+ rect: {
3980
+ x: label.x,
3981
+ y: label.y,
3982
+ width: label.width,
3983
+ height: label.height
3984
+ }
3985
+ });
3986
+ }
3987
+ }
3988
+ for (const [id, labels$1] of boundary) {
3989
+ const node = byId.get(id);
3990
+ const totalHeight = labels$1.reduce((sum, e) => sum + (e.height ?? 0), 0) + Math.max(0, labels$1.length - 1) * labelGap;
3991
+ let cross = horizontal ? 0 : (Math.max(node.height, totalHeight) - totalHeight) / 2;
3992
+ for (const edge of labels$1) {
3993
+ const width = edge.width ?? 0, height = edge.height ?? 0;
3994
+ localLabels.set(edge.id, {
3995
+ scope,
3996
+ rect: {
3997
+ x: node.x + (horizontal ? (Math.max(node.width, width) - width) / 2 : node.width + gap),
3998
+ y: node.y + (horizontal ? node.height + gap : 0) + cross,
3999
+ width,
4000
+ height
4001
+ }
4002
+ });
4003
+ cross += height + labelGap;
4004
+ }
4005
+ }
4006
+ const boxes = [...children.map((n) => ({
4007
+ x: n.x,
4008
+ y: n.y,
4009
+ ...sizes.get(n.id)
4010
+ })), ...[...localLabels.values()].filter((l) => l.scope === scope).map((l) => l.rect)];
4011
+ let dx = Math.max(0, insets.left - Math.min(...boxes.map((b) => b.x)));
4012
+ let dy = Math.max(0, insets.top - Math.min(...boxes.map((b) => b.y)));
4013
+ if (header && (side === "top" || side === "bottom")) {
4014
+ const occupiedWidth = Math.max(...boxes.map((b) => b.x + b.width)) - Math.min(...boxes.map((b) => b.x)) + padding.left + padding.right;
4015
+ dx += Math.max(0, (header.width - occupiedWidth) / 2);
4016
+ }
4017
+ if (header && (side === "left" || side === "right")) {
4018
+ const occupiedHeight = Math.max(...boxes.map((b) => b.y + b.height)) - Math.min(...boxes.map((b) => b.y)) + padding.top + padding.bottom;
4019
+ dy += Math.max(0, (header.height - occupiedHeight) / 2);
4020
+ }
4021
+ const right = Math.max(...boxes.map((b) => b.x + dx + b.width));
4022
+ const bottom = Math.max(...boxes.map((b) => b.y + dy + b.height));
4023
+ for (const n of children) {
4024
+ n.x += dx;
4025
+ n.y += dy;
4026
+ }
4027
+ for (const l of localLabels.values()) if (l.scope === scope) {
4028
+ l.rect.x += dx;
4029
+ l.rect.y += dy;
4030
+ }
4031
+ if (container) {
4032
+ const width = Math.max(header?.width ?? 0, config?.minContentSize?.width ? config.minContentSize.width + insets.left + insets.right : 0, right + insets.right);
4033
+ const height = Math.max(header?.height ?? 0, config?.minContentSize?.height ? config.minContentSize.height + insets.top + insets.bottom : 0, bottom + insets.bottom);
4034
+ container.width = Math.max(container.width, width);
4035
+ container.height = Math.max(container.height, height);
4036
+ compoundGeometry.set(container.id, {
4037
+ bounds: {
4038
+ x: 0,
4039
+ y: 0,
4040
+ width: container.width,
4041
+ height: container.height
4042
+ },
4043
+ content: {
4044
+ x: insets.left,
4045
+ y: insets.top,
4046
+ width: container.width - insets.left - insets.right,
4047
+ height: container.height - insets.top - insets.bottom
4048
+ },
4049
+ ...header ? { header: {
4050
+ x: side === "right" ? container.width - header.width : 0,
4051
+ y: side === "bottom" ? container.height - header.height : 0,
4052
+ width: side === "top" || side === "bottom" ? container.width : header.width,
4053
+ height: side === "left" || side === "right" ? container.height : header.height
4054
+ } } : {}
4055
+ });
4056
+ }
3866
4057
  };
3867
- }
3868
- function worldGeometry(graph) {
3869
- const offsets = frames(graph);
3870
- return {
4058
+ for (const scope of childScopes) solve(scope);
4059
+ const roots = nodes.filter((n) => n.parentId == null);
4060
+ if (roots.length === 1 && compoundGeometry.has(roots[0].id) && !owned.get(null)?.length) Object.assign(roots[0], {
4061
+ x: 0,
4062
+ y: 0
4063
+ });
4064
+ else solve(null);
4065
+ const origin = (id) => {
4066
+ if (id == null) return {
4067
+ x: 0,
4068
+ y: 0
4069
+ };
4070
+ return ancestry(id).slice(0, -1).reduce((p, key) => ({
4071
+ x: p.x + byId.get(key).x,
4072
+ y: p.y + byId.get(key).y
4073
+ }), {
4074
+ x: 0,
4075
+ y: 0
4076
+ });
4077
+ };
4078
+ for (const [id, label] of localLabels) {
4079
+ const offset = origin(label.scope);
4080
+ Object.assign(edges.get(id), {
4081
+ ...label.rect,
4082
+ x: label.rect.x + offset.x,
4083
+ y: label.rect.y + offset.y
4084
+ });
4085
+ }
4086
+ const attachmentSettings = {};
4087
+ for (const edge of edges.values()) {
4088
+ const sourceGeometry = compoundGeometry.get(edge.sourceId), targetGeometry = compoundGeometry.get(edge.targetId);
4089
+ const intent = options.edgeAttachment?.(edge);
4090
+ attachmentSettings[edge.id] = {
4091
+ ...sourceGeometry && edge.sourceId !== edge.targetId && ancestry(edge.targetId).includes(edge.sourceId) ? { sourceAttachment: {
4092
+ bounds: intent?.source === "outer" ? {
4093
+ x: 0,
4094
+ y: 0,
4095
+ width: sourceGeometry.bounds.width,
4096
+ height: sourceGeometry.bounds.height
4097
+ } : sourceGeometry.content,
4098
+ facing: intent?.source === "outer" ? "outward" : "inward"
4099
+ } } : {},
4100
+ ...targetGeometry && intent?.target === "content" ? { targetAttachment: {
4101
+ bounds: targetGeometry.content,
4102
+ facing: "inward"
4103
+ } } : {}
4104
+ };
4105
+ }
4106
+ const worldNodes = nodes.map((n) => ({
4107
+ ...n,
4108
+ ...origin(n.id)
4109
+ }));
4110
+ const usedIds = new Set(nodes.map((n) => n.id));
4111
+ const headers = [...compoundGeometry].flatMap(([id, geometry]) => {
4112
+ if (!geometry.header) return [];
4113
+ const p = origin(id);
4114
+ let key = `__layout_header_${id}`;
4115
+ while (usedIds.has(key)) key += "_";
4116
+ usedIds.add(key);
4117
+ return [{
4118
+ type: "node",
4119
+ id: key,
4120
+ parentId: id,
4121
+ data: void 0,
4122
+ ...geometry.header,
4123
+ x: p.x + geometry.header.x,
4124
+ y: p.y + geometry.header.y
4125
+ }];
4126
+ });
4127
+ const snapshot = require_routing.orthogonalRouting.route({
3871
4128
  ...graph,
3872
- nodes: graph.nodes.map((n) => {
3873
- const p = offsets.nodes.get(n.id);
3874
- return p.x === 0 && p.y === 0 ? n : {
3875
- ...n,
3876
- x: n.x + p.x,
3877
- y: n.y + p.y
3878
- };
3879
- }),
3880
- edges: graph.edges.map((e) => {
3881
- const p = offsets.edges.get(e.id);
3882
- return p.x === 0 && p.y === 0 ? e : {
3883
- ...e,
3884
- x: e.x + p.x,
3885
- y: e.y + p.y,
3886
- ...e.points ? { points: e.points.map((v) => ({
3887
- x: v.x + p.x,
3888
- y: v.y + p.y
3889
- })) } : {}
3890
- };
3891
- })
4129
+ nodes: [...worldNodes, ...headers],
4130
+ edges: [...edges.values()]
4131
+ }, {
4132
+ coordinateSpace: "world",
4133
+ edges: attachmentSettings
4134
+ });
4135
+ for (const [id, route] of snapshot.routes) {
4136
+ const polylines = require_routing.routeToPolylines(route);
4137
+ Object.assign(edges.get(id), {
4138
+ points: polylines.flatMap((p) => p.map((point) => ({ ...point }))),
4139
+ routing: "orthogonal"
4140
+ });
4141
+ }
4142
+ for (const [id, geometry] of compoundGeometry) geometry.bounds = {
4143
+ ...geometry.bounds,
4144
+ x: byId.get(id).x,
4145
+ y: byId.get(id).y
3892
4146
  };
3893
- }
3894
- /** Ancestor frames stay fixed during authoring. Preserve untouched coordinates exactly. */
3895
- function nativeGeometry(graph, original) {
3896
- const offsets = frames(original);
3897
- const world = worldGeometry(original);
3898
- const originalNodes = new Map(original.nodes.map((n) => [n.id, n]));
3899
- const originalEdges = new Map(original.edges.map((e) => [e.id, e]));
3900
- const worldNodes = new Map(world.nodes.map((n) => [n.id, n]));
3901
- const worldEdges = new Map(world.edges.map((e) => [e.id, e]));
3902
- return {
4147
+ const result = {
3903
4148
  ...graph,
3904
- nodes: graph.nodes.map((n) => {
3905
- const p = offsets.nodes.get(n.id), before = originalNodes.get(n.id), prior = worldNodes.get(n.id);
3906
- return {
3907
- ...n,
3908
- x: n.x === prior.x ? before.x : n.x - p.x,
3909
- y: n.y === prior.y ? before.y : n.y - p.y
3910
- };
3911
- }),
3912
- edges: graph.edges.map((e) => {
3913
- const p = offsets.edges.get(e.id), before = originalEdges.get(e.id), prior = worldEdges.get(e.id);
3914
- const points = JSON.stringify(e.points) === JSON.stringify(prior.points) ? before.points : e.points?.map((v) => ({
3915
- x: v.x - p.x,
3916
- y: v.y - p.y
3917
- }));
3918
- return {
3919
- ...e,
3920
- x: e.x === prior.x ? before.x : e.x - p.x,
3921
- y: e.y === prior.y ? before.y : e.y - p.y,
3922
- ...points === void 0 ? {} : { points }
3923
- };
3924
- })
4149
+ edgeCoordinateSpace: "world",
4150
+ direction: options.direction ?? graph.direction ?? "right",
4151
+ nodes,
4152
+ edges: graph.edges.map((e) => edges.get(e.id)),
4153
+ compoundGeometry,
4154
+ compoundRoutes: snapshot.routes
3925
4155
  };
4156
+ require_routing.recordRouteGeometry(result, snapshot.routes);
4157
+ return result;
3926
4158
  }
3927
4159
 
3928
4160
  //#endregion
@@ -5305,7 +5537,7 @@ const c = {
5305
5537
  })
5306
5538
  };
5307
5539
  function constraintFailure(constraint, message) {
5308
- throw new LayoutError(`${constraint.id}: ${message}`, "UNSATISFIED_CONSTRAINT");
5540
+ throw new require_routing.LayoutError(`${constraint.id}: ${message}`, "UNSATISFIED_CONSTRAINT");
5309
5541
  }
5310
5542
  /** Solve positions only. Fixed geometry is represented by constants, never edit variables. */
5311
5543
  function solveGeometryConstraints(graph, constraints, movableNodes, movableLabels, diagnostics) {
@@ -5317,7 +5549,7 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
5317
5549
  const node = "nodeId" in ref;
5318
5550
  const id = node ? ref.nodeId : ref.edgeId;
5319
5551
  const rect = node ? nodes.get(id) : edges.get(id);
5320
- if (!rect) throw new LayoutError(`Unknown constraint entity: ${id}`, "INVALID_CONSTRAINT");
5552
+ if (!rect) throw new require_routing.LayoutError(`Unknown constraint entity: ${id}`, "INVALID_CONSTRAINT");
5321
5553
  const key = JSON.stringify([node ? "node" : "label", id]);
5322
5554
  if ((node ? movableNodes : movableLabels).has(id) && !variables.has(key)) {
5323
5555
  const pair = {
@@ -5343,13 +5575,13 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
5343
5575
  const checks = [];
5344
5576
  const ids = /* @__PURE__ */ new Set();
5345
5577
  for (const constraint of constraints) {
5346
- if (ids.has(constraint.id)) throw new LayoutError(`Duplicate constraint ID: ${constraint.id}`, "INVALID_CONSTRAINT");
5578
+ if (ids.has(constraint.id)) throw new require_routing.LayoutError(`Duplicate constraint ID: ${constraint.id}`, "INVALID_CONSTRAINT");
5347
5579
  ids.add(constraint.id);
5348
5580
  if (constraint.kind === "waypoint") continue;
5349
5581
  const strength = Strength[constraint.strength ?? "required"];
5350
5582
  const add = (lhs, operator = Operator.Eq, rhs = 0) => {
5351
5583
  const difference = lhs.minus(rhs);
5352
- if (!Number.isFinite(difference.constant()) || !difference.terms().array.every((item) => Number.isFinite(item.second))) throw new LayoutError(`${constraint.id}: non-finite expression`, "INVALID_CONSTRAINT");
5584
+ if (!Number.isFinite(difference.constant()) || !difference.terms().array.every((item) => Number.isFinite(item.second))) throw new require_routing.LayoutError(`${constraint.id}: non-finite expression`, "INVALID_CONSTRAINT");
5353
5585
  try {
5354
5586
  solver.addConstraint(new Constraint(difference, operator, 0, strength));
5355
5587
  } catch {
@@ -5407,7 +5639,7 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
5407
5639
  }
5408
5640
  const coordinate = (variable) => {
5409
5641
  const value = variable.value();
5410
- if (!Number.isFinite(value)) throw new LayoutError("Constraint solution contains non-finite geometry", "INVALID_CONSTRAINT");
5642
+ if (!Number.isFinite(value)) throw new require_routing.LayoutError("Constraint solution contains non-finite geometry", "INVALID_CONSTRAINT");
5411
5643
  return value === 0 ? 0 : value;
5412
5644
  };
5413
5645
  return {
@@ -5431,173 +5663,6 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
5431
5663
  };
5432
5664
  }
5433
5665
 
5434
- //#endregion
5435
- //#region src/authoring/routing.ts
5436
- function overlaps(a, b, gap = 0) {
5437
- return a.width > 0 && a.height > 0 && b.width > 0 && b.height > 0 && a.x < b.x + b.width + gap && a.x + a.width + gap > b.x && a.y < b.y + b.height + gap && a.y + a.height + gap > b.y;
5438
- }
5439
- /** Open-interior intersection: travelling along a rectangle boundary is allowed. */
5440
- function crossesRect(a, b, rect) {
5441
- let low = 0, high = 1;
5442
- for (const axis of ["x", "y"]) {
5443
- const delta = b[axis] - a[axis];
5444
- const min = rect[axis], max = min + (axis === "x" ? rect.width : rect.height);
5445
- if (Math.abs(delta) < 1e-10) {
5446
- if (a[axis] <= min || a[axis] >= max) return false;
5447
- } else {
5448
- const t1 = (min - a[axis]) / delta, t2 = (max - a[axis]) / delta;
5449
- low = Math.max(low, Math.min(t1, t2));
5450
- high = Math.min(high, Math.max(t1, t2));
5451
- }
5452
- }
5453
- return low < high - 1e-10;
5454
- }
5455
- function routeCrosses(points, rect) {
5456
- return points?.some((point, i) => i > 0 && crossesRect(points[i - 1], point, rect)) ?? false;
5457
- }
5458
- function endpoint(node, portName, direction, source) {
5459
- if (portName !== void 0) {
5460
- const port = node.ports?.find((p) => p.name === portName);
5461
- if (port?.x !== void 0 && port.y !== void 0) return {
5462
- x: node.x + port.x + (port.width ?? 0) / 2,
5463
- y: node.y + port.y + (port.height ?? 0) / 2
5464
- };
5465
- throw new LayoutError(`Port ${portName} on ${node.id} requires positioned geometry`, "MISSING_GEOMETRY");
5466
- }
5467
- const horizontal = direction === "left" || direction === "right";
5468
- const far = source === (direction === "right" || direction === "down");
5469
- return horizontal ? {
5470
- x: node.x + (far ? node.width : 0),
5471
- y: node.y + node.height / 2
5472
- } : {
5473
- x: node.x + node.width / 2,
5474
- y: node.y + (far ? node.height : 0)
5475
- };
5476
- }
5477
- /** Deterministic orthogonal visibility-grid search around fixed rectangles. */
5478
- function connect(start, end, obstacles, check) {
5479
- if (start.x === end.x && start.y === end.y) return [start];
5480
- for (let attempt = 0; attempt < 8; attempt++) {
5481
- check();
5482
- const margin = 64 * 2 ** attempt;
5483
- const left = Math.min(start.x, end.x) - margin;
5484
- const right = Math.max(start.x, end.x) + margin;
5485
- const top = Math.min(start.y, end.y) - margin;
5486
- const bottom = Math.max(start.y, end.y) + margin;
5487
- const local = obstacles.filter((r) => r.x <= right && r.x + r.width >= left && r.y <= bottom && r.y + r.height >= top);
5488
- const result = search(start, end, local, obstacles, check);
5489
- if (result || local.length === obstacles.length) return result;
5490
- }
5491
- }
5492
- function search(start, end, gridObstacles, obstacles, check) {
5493
- const xs = [...new Set([
5494
- start.x,
5495
- end.x,
5496
- ...gridObstacles.flatMap((r) => [r.x, r.x + r.width])
5497
- ])].sort((a, b) => a - b);
5498
- const ys = [...new Set([
5499
- start.y,
5500
- end.y,
5501
- ...gridObstacles.flatMap((r) => [r.y, r.y + r.height])
5502
- ])].sort((a, b) => a - b);
5503
- if (xs.length * ys.length > 4e4) return void 0;
5504
- const width = xs.length;
5505
- const point = (id) => ({
5506
- x: xs[id % width],
5507
- y: ys[Math.floor(id / width)]
5508
- });
5509
- const from = ys.indexOf(start.y) * width + xs.indexOf(start.x);
5510
- const to = ys.indexOf(end.y) * width + xs.indexOf(end.x);
5511
- const distances = new Map([[from, 0]]);
5512
- const previous = /* @__PURE__ */ new Map();
5513
- const queue = [];
5514
- function push(item) {
5515
- queue.push(item);
5516
- let i = queue.length - 1;
5517
- while (i > 0) {
5518
- const parent = i - 1 >> 1;
5519
- if (queue[parent].cost <= item.cost) break;
5520
- queue[i] = queue[parent];
5521
- i = parent;
5522
- }
5523
- queue[i] = item;
5524
- }
5525
- function pop() {
5526
- const first = queue[0];
5527
- const last = queue.pop();
5528
- if (queue.length) {
5529
- let i = 0;
5530
- while (i * 2 + 1 < queue.length) {
5531
- let child = i * 2 + 1;
5532
- if (child + 1 < queue.length && queue[child + 1].cost < queue[child].cost) child++;
5533
- if (last.cost <= queue[child].cost) break;
5534
- queue[i] = queue[child];
5535
- i = child;
5536
- }
5537
- queue[i] = last;
5538
- }
5539
- return first;
5540
- }
5541
- push({
5542
- id: from,
5543
- cost: 0
5544
- });
5545
- let visits = 0;
5546
- while (queue.length) {
5547
- if (++visits % 128 === 0) check();
5548
- const current = pop();
5549
- if (current.cost !== distances.get(current.id)) continue;
5550
- if (current.id === to) {
5551
- const result = [end];
5552
- let id = to;
5553
- while (id !== from) {
5554
- id = previous.get(id);
5555
- result.push(point(id));
5556
- }
5557
- return result.reverse();
5558
- }
5559
- const x = current.id % width, y = Math.floor(current.id / width);
5560
- const neighbors = [
5561
- x > 0 ? current.id - 1 : -1,
5562
- x + 1 < width ? current.id + 1 : -1,
5563
- y > 0 ? current.id - width : -1,
5564
- y + 1 < ys.length ? current.id + width : -1
5565
- ];
5566
- const a = point(current.id);
5567
- for (const next of neighbors) {
5568
- if (next < 0) continue;
5569
- const b = point(next);
5570
- if (obstacles.some((rect) => crossesRect(a, b, rect))) continue;
5571
- const cost = current.cost + Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
5572
- if (cost >= (distances.get(next) ?? Infinity)) continue;
5573
- distances.set(next, cost);
5574
- previous.set(next, current.id);
5575
- push({
5576
- id: next,
5577
- cost
5578
- });
5579
- }
5580
- }
5581
- }
5582
- function routeOrthogonal(edge, source, target, direction, obstacles, waypoints, check) {
5583
- const anchors = [
5584
- endpoint(source, edge.sourcePort, direction, true),
5585
- ...waypoints,
5586
- endpoint(target, edge.targetPort, direction, false)
5587
- ];
5588
- const points = [];
5589
- for (let i = 1; i < anchors.length; i++) {
5590
- const segment = connect(anchors[i - 1], anchors[i], obstacles, check);
5591
- if (!segment) return void 0;
5592
- points.push(...points.length ? segment.slice(1) : segment);
5593
- }
5594
- return points.filter((p, i) => {
5595
- if (i === 0 || i === points.length - 1) return true;
5596
- const before = points[i - 1], after = points[i + 1];
5597
- return !(before.x === p.x && p.x === after.x && (p.y - before.y) * (after.y - p.y) >= 0 || before.y === p.y && p.y === after.y && (p.x - before.x) * (after.x - p.x) >= 0);
5598
- });
5599
- }
5600
-
5601
5666
  //#endregion
5602
5667
  //#region src/authoring/partial.ts
5603
5668
  const geometryKeys = [
@@ -5642,14 +5707,14 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5642
5707
  const constrainedLabels = labelReferences(constraints);
5643
5708
  for (const [ids, entities] of [[selectedNodes, input.nodes], [selectedEdges, input.edges]]) {
5644
5709
  const known = new Set(entities.map((e) => e.id));
5645
- for (const id of ids) if (!known.has(id)) throw new LayoutError(`Unknown selected entity: ${id}`, "INVALID_SELECTION");
5710
+ for (const id of ids) if (!known.has(id)) throw new require_routing.LayoutError(`Unknown selected entity: ${id}`, "INVALID_SELECTION");
5646
5711
  }
5647
5712
  const containers = new Set(input.nodes.flatMap((n) => n.parentId == null ? [] : [n.parentId]));
5648
- if ([...selectedNodes].some((id) => containers.has(id))) throw new UnsupportedLayoutError("Authoring layout cannot move containers; select leaf nodes or use full layout without geometry constraints");
5713
+ if ([...selectedNodes].some((id) => containers.has(id))) throw new require_routing.UnsupportedLayoutError("Authoring layout cannot move containers; select leaf nodes or use full layout without geometry constraints");
5649
5714
  const previousNodes = new Map(scope.previous?.nodes.map((n) => [n.id, n]));
5650
5715
  const previousEdges = new Map(scope.previous?.edges.map((e) => [e.id, e]));
5651
5716
  const missing = (id) => {
5652
- throw new LayoutError(`Fixed entity ${id} requires finite geometry in graph or previous`, "MISSING_GEOMETRY");
5717
+ throw new require_routing.LayoutError(`Fixed entity ${id} requires finite geometry in graph or previous`, "MISSING_GEOMETRY");
5653
5718
  };
5654
5719
  let nodes = input.nodes.map((node) => {
5655
5720
  const previous = previousNodes.get(node.id);
@@ -5684,7 +5749,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5684
5749
  nodes,
5685
5750
  edges
5686
5751
  };
5687
- const baseline = worldGeometry(authored);
5752
+ const baseline = require_routing.worldGeometry(authored);
5688
5753
  nodes = baseline.nodes;
5689
5754
  edges = baseline.edges;
5690
5755
  let graph = baseline;
@@ -5705,7 +5770,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5705
5770
  for (const node of nodes) ancestors(node.id);
5706
5771
  const direction = options.direction ?? input.direction ?? scope.previous?.direction ?? "down";
5707
5772
  const gap = options.spacing?.node ?? 20;
5708
- if (!finite(gap) || gap < 0) throw new LayoutError("Spacing must be finite and non-negative", "INVALID_OPTIONS");
5773
+ if (!finite(gap) || gap < 0) throw new require_routing.LayoutError("Spacing must be finite and non-negative", "INVALID_OPTIONS");
5709
5774
  context.throwIfAborted();
5710
5775
  if (selectedNodes.size && phase === "selection") {
5711
5776
  const groups = /* @__PURE__ */ new Map();
@@ -5782,7 +5847,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5782
5847
  };
5783
5848
  const parent = n.parentId == null ? void 0 : placed.get(n.parentId);
5784
5849
  if (parent && (translated.x < parent.x || translated.y < parent.y || translated.x + translated.width > parent.x + parent.width || translated.y + translated.height > parent.y + parent.height)) return false;
5785
- return fixedLabels.every((label) => budget() && !overlaps(translated, label, gap)) && obstacles.every((o) => budget() && (ancestors(n.id).has(o.id) || ancestors(o.id).has(n.id) || !overlaps(translated, o, gap)));
5850
+ return fixedLabels.every((label) => budget() && !require_routing.overlaps(translated, label, gap)) && obstacles.every((o) => budget() && (ancestors(n.id).has(o.id) || ancestors(o.id).has(n.id) || !require_routing.overlaps(translated, o, gap)));
5786
5851
  });
5787
5852
  };
5788
5853
  const offset = candidates.find((candidate) => comparisons <= 1e5 && fits(candidate));
@@ -5809,7 +5874,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5809
5874
  const movedIds = new Set(moved.map((n) => n.id));
5810
5875
  const resultNodes = new Map(graph.nodes.map((n) => [n.id, n]));
5811
5876
  const automatic = /* @__PURE__ */ new Set();
5812
- const affected = (edge) => movedIds.has(edge.sourceId) || movedIds.has(edge.targetId) || moved.some((n) => !ancestors(edge.sourceId).has(n.id) && !ancestors(edge.targetId).has(n.id) && (routeCrosses(edge.points, n) || overlaps(edge, n) || edge.routing === "splines" && edge.points?.length && overlaps(n, {
5877
+ const affected = (edge) => movedIds.has(edge.sourceId) || movedIds.has(edge.targetId) || moved.some((n) => !ancestors(edge.sourceId).has(n.id) && !ancestors(edge.targetId).has(n.id) && (require_routing.routeCrosses(edge.points, n) || require_routing.overlaps(edge, n) || edge.routing === "splines" && edge.points?.length && require_routing.overlaps(n, {
5813
5878
  x: Math.min(...edge.points.map((p) => p.x)),
5814
5879
  y: Math.min(...edge.points.map((p) => p.y)),
5815
5880
  width: Math.max(...edge.points.map((p) => p.x)) - Math.min(...edge.points.map((p) => p.x)),
@@ -5817,7 +5882,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5817
5882
  })));
5818
5883
  const edgeById = new Map(graph.edges.map((e) => [e.id, e]));
5819
5884
  for (const constraint of constraints) if (constraint.kind === "waypoint") {
5820
- if (!finite(constraint.point.x) || !finite(constraint.point.y) || !edgeById.has(constraint.edgeId)) throw new LayoutError(`${constraint.id}: invalid waypoint`, "INVALID_CONSTRAINT");
5885
+ if (!finite(constraint.point.x) || !finite(constraint.point.y) || !edgeById.has(constraint.edgeId)) throw new require_routing.LayoutError(`${constraint.id}: invalid waypoint`, "INVALID_CONSTRAINT");
5821
5886
  }
5822
5887
  graph = {
5823
5888
  ...graph,
@@ -5840,7 +5905,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5840
5905
  geometry: "routes"
5841
5906
  });
5842
5907
  if (routeAllowed) {
5843
- if (options.settings?.edgeRouting === "SPLINES" || options.strategies?.routeEdges) throw new UnsupportedLayoutError("Partial layout supports its built-in orthogonal router only");
5908
+ if (options.settings?.edgeRouting === "SPLINES" || options.strategies?.routeEdges) throw new require_routing.UnsupportedLayoutError("Partial layout supports its built-in orthogonal router only");
5844
5909
  const ancestorIds = new Set([...ancestors(source.id), ...ancestors(target.id)]);
5845
5910
  const nodeObstacles = graph.nodes.filter((n) => !ancestorIds.has(n.id)).map((n) => n.id === source.id || n.id === target.id ? n : {
5846
5911
  ...n,
@@ -5852,7 +5917,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5852
5917
  const labelObstacles = [...edgeById.values()].filter((e) => e.id !== edge.id && e.width > 0 && e.height > 0);
5853
5918
  const required = waypoints.filter((c$1) => (c$1.strength ?? "required") === "required");
5854
5919
  const accepted = new Set(required.map((c$1) => c$1.id));
5855
- const route = () => routeOrthogonal(edge, source, target, direction, [...nodeObstacles, ...labelObstacles], waypoints.filter((c$1) => c$1.kind === "waypoint" && accepted.has(c$1.id)).map((c$1) => c$1.kind === "waypoint" ? c$1.point : {
5920
+ const route = () => require_routing.routeOrthogonal(edge, source, target, direction, [...nodeObstacles, ...labelObstacles], waypoints.filter((c$1) => c$1.kind === "waypoint" && accepted.has(c$1.id)).map((c$1) => c$1.kind === "waypoint" ? c$1.point : {
5856
5921
  x: 0,
5857
5922
  y: 0
5858
5923
  }), () => context.throwIfAborted());
@@ -5940,7 +6005,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5940
6005
  ...next,
5941
6006
  ...p
5942
6007
  };
5943
- return !graph.nodes.some((n) => !parentIds.has(n.id) && overlaps(rect, n)) && ![...edgeById.values()].some((e) => e.id !== edge.id && overlaps(rect, e));
6008
+ return !graph.nodes.some((n) => !parentIds.has(n.id) && require_routing.overlaps(rect, n)) && ![...edgeById.values()].some((e) => e.id !== edge.id && require_routing.overlaps(rect, e));
5944
6009
  });
5945
6010
  if (chosen) next = {
5946
6011
  ...next,
@@ -5981,7 +6046,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5981
6046
  entityIds: [node.id, parent.id],
5982
6047
  geometry: "node"
5983
6048
  });
5984
- if (graph.nodes.some((other) => other.id !== node.id && !ancestors(node.id).has(other.id) && !ancestors(other.id).has(node.id) && overlaps(node, other))) context.diagnostics.push({
6049
+ if (graph.nodes.some((other) => other.id !== node.id && !ancestors(node.id).has(other.id) && !ancestors(other.id).has(node.id) && require_routing.overlaps(node, other))) context.diagnostics.push({
5985
6050
  severity: "warning",
5986
6051
  code: "NODE_OVERLAP",
5987
6052
  message: `Node ${node.id} overlaps another node`,
@@ -5993,7 +6058,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
5993
6058
  const before = baseline.edges.find((e) => e.id === edge.id);
5994
6059
  if (!samePosition(edge, before)) {
5995
6060
  const containerIds = new Set([...ancestors(edge.sourceId), ...ancestors(edge.targetId)]);
5996
- const collisions = [...graph.nodes.filter((n) => !containerIds.has(n.id) && overlaps(edge, n)), ...graph.edges.filter((other) => other.id !== edge.id && overlaps(edge, other))];
6061
+ const collisions = [...graph.nodes.filter((n) => !containerIds.has(n.id) && require_routing.overlaps(edge, n)), ...graph.edges.filter((other) => other.id !== edge.id && require_routing.overlaps(edge, other))];
5997
6062
  if (collisions.length) context.diagnostics.push({
5998
6063
  severity: "warning",
5999
6064
  code: "LABEL_OVERLAP",
@@ -6001,7 +6066,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
6001
6066
  entityIds: [edge.id, ...collisions.map((e) => e.id)],
6002
6067
  geometry: "labels"
6003
6068
  });
6004
- for (const other of graph.edges) if (other.id !== edge.id && routeCrosses(other.points, edge) && !routeCrosses(other.points, before)) context.diagnostics.push({
6069
+ for (const other of graph.edges) if (other.id !== edge.id && require_routing.routeCrosses(other.points, edge) && !require_routing.routeCrosses(other.points, before)) context.diagnostics.push({
6005
6070
  severity: "warning",
6006
6071
  code: "ROUTE_REPAIR_REQUIRED",
6007
6072
  message: `Route ${other.id} crosses the moved label ${edge.id}`,
@@ -6009,7 +6074,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
6009
6074
  geometry: "routes"
6010
6075
  });
6011
6076
  }
6012
- if (geometry === "routes" && routeCrosses(before.points, before) && !routeCrosses(edge.points, edge)) context.diagnostics.push({
6077
+ if (geometry === "routes" && require_routing.routeCrosses(before.points, before) && !require_routing.routeCrosses(edge.points, edge)) context.diagnostics.push({
6013
6078
  severity: "warning",
6014
6079
  code: "LABEL_REPAIR_REQUIRED",
6015
6080
  message: `Label ${edge.id} is no longer attached to its route`,
@@ -6017,7 +6082,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
6017
6082
  geometry: "labels"
6018
6083
  });
6019
6084
  }
6020
- return nativeGeometry(graph, authored);
6085
+ return require_routing.nativeGeometry(graph, authored);
6021
6086
  }
6022
6087
 
6023
6088
  //#endregion
@@ -10556,7 +10621,7 @@ function runCommentBoxPipeline(graph, options, context) {
10556
10621
  x: horizontal ? point.x : point.x + crossShift,
10557
10622
  y: horizontal ? point.y + crossShift : point.y
10558
10623
  }));
10559
- const endpoint$1 = (source) => {
10624
+ const endpoint = (source) => {
10560
10625
  if (horizontal) return {
10561
10626
  x: direction === "right" === source ? (targetRect.x ?? 0) + (targetRect.width ?? 0) : targetRect.x ?? 0,
10562
10627
  y: (targetRect.y ?? 0) + (targetRect.height ?? 0) / 2
@@ -10566,8 +10631,8 @@ function runCommentBoxPipeline(graph, options, context) {
10566
10631
  y: direction === "down" === source ? (targetRect.y ?? 0) + (targetRect.height ?? 0) : targetRect.y ?? 0
10567
10632
  };
10568
10633
  };
10569
- if (edge.sourceId === targetId && points.length > 0) points[0] = endpoint$1(true);
10570
- if (edge.targetId === targetId && points.length > 0) points[points.length - 1] = endpoint$1(false);
10634
+ if (edge.sourceId === targetId && points.length > 0) points[0] = endpoint(true);
10635
+ if (edge.targetId === targetId && points.length > 0) points[points.length - 1] = endpoint(false);
10571
10636
  return [edge.id, {
10572
10637
  ...edge,
10573
10638
  x: horizontal ? edge.x : (edge.x ?? 0) + crossShift,
@@ -10769,126 +10834,16 @@ function runSeparatedComponents(graph, options, context) {
10769
10834
  };
10770
10835
  }
10771
10836
  function runCompoundPipeline(graph, options, context) {
10772
- const nodes = graph.nodes.map((node) => ({ ...node }));
10773
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
10774
- const depth = (node) => {
10775
- let value = 0;
10776
- let parentId = node.parentId;
10777
- const seen = /* @__PURE__ */ new Set();
10778
- while (parentId != null && !seen.has(parentId)) {
10779
- seen.add(parentId);
10780
- value++;
10781
- parentId = nodeById.get(parentId)?.parentId;
10782
- }
10783
- return value;
10784
- };
10785
- const parentIds = [...new Set(nodes.flatMap((node) => node.parentId == null ? [] : [node.parentId]))].sort((left, right) => depth(nodeById.get(right)) - depth(nodeById.get(left)));
10786
- const edgeById = new Map(graph.edges.map((edge) => [edge.id, { ...edge }]));
10787
- const padding = typeof options.padding === "number" ? options.padding : 12;
10788
- const layoutSiblings = (parentId) => {
10789
- const siblings = nodes.filter((node) => (node.parentId ?? null) === parentId);
10790
- if (siblings.length === 0) return;
10791
- const siblingIds = new Set(siblings.map((node) => node.id));
10792
- const siblingEdges = graph.edges.filter((edge) => siblingIds.has(edge.sourceId) && siblingIds.has(edge.targetId));
10793
- const flatNodes = siblings.map((node) => ({
10794
- ...node,
10795
- parentId: null
10796
- }));
10797
- const result = runLayeredPipeline({
10798
- ...graph,
10799
- nodes: flatNodes,
10800
- edges: siblingEdges
10801
- }, options, context);
10802
- for (const laidOut of result.nodes) {
10803
- const node = nodeById.get(laidOut.id);
10804
- if (!node) continue;
10805
- Object.assign(node, laidOut, { parentId });
10806
- }
10807
- for (const edge of result.edges) edgeById.set(edge.id, edge);
10808
- };
10809
- for (const parentId of parentIds) {
10810
- layoutSiblings(parentId);
10811
- const parent = nodeById.get(parentId);
10812
- if (!parent) continue;
10813
- const children = nodes.filter((node) => node.parentId === parentId);
10814
- const right = Math.max(0, ...children.map((node) => (node.x ?? 0) + (node.width ?? 0)));
10815
- const bottom = Math.max(0, ...children.map((node) => (node.y ?? 0) + (node.height ?? 0)));
10816
- parent.width = Math.max(parent.width ?? 0, right + padding);
10817
- parent.height = Math.max(parent.height ?? 0, bottom + padding);
10818
- }
10819
- const roots = nodes.filter((node) => node.parentId == null);
10820
- if (roots.length === 1 && parentIds.includes(roots[0].id)) Object.assign(roots[0], {
10821
- x: 0,
10822
- y: 0
10837
+ const result = layoutCompounds(graph, options, (scope, local) => runLayeredPipeline(scope, local, context));
10838
+ for (const route of result.compoundRoutes.values()) for (const diagnostic of route.diagnostics) context?.diagnostics.push({
10839
+ severity: "warning",
10840
+ code: diagnostic.code,
10841
+ message: diagnostic.message,
10842
+ entityIds: [route.edgeId],
10843
+ phase: "compound-routing",
10844
+ geometry: "routes"
10823
10845
  });
10824
- else layoutSiblings(null);
10825
- const absoluteRect = (id) => {
10826
- const node = nodeById.get(id);
10827
- if (!node) return {
10828
- x: 0,
10829
- y: 0,
10830
- width: 0,
10831
- height: 0
10832
- };
10833
- let x = node.x ?? 0;
10834
- let y = node.y ?? 0;
10835
- let parentId = node.parentId;
10836
- const seen = /* @__PURE__ */ new Set();
10837
- while (parentId != null && !seen.has(parentId)) {
10838
- seen.add(parentId);
10839
- const parent = nodeById.get(parentId);
10840
- if (!parent) break;
10841
- x += parent.x ?? 0;
10842
- y += parent.y ?? 0;
10843
- parentId = parent.parentId;
10844
- }
10845
- return {
10846
- x,
10847
- y,
10848
- width: node.width ?? 0,
10849
- height: node.height ?? 0
10850
- };
10851
- };
10852
- for (const edge of graph.edges) {
10853
- if (edgeById.get(edge.id)?.points !== void 0) continue;
10854
- const source = absoluteRect(edge.sourceId);
10855
- const target = absoluteRect(edge.targetId);
10856
- const start = {
10857
- x: source.x + source.width,
10858
- y: source.y + source.height / 2
10859
- };
10860
- const end = {
10861
- x: target.x,
10862
- y: target.y + target.height / 2
10863
- };
10864
- const track = (start.x + end.x) / 2;
10865
- const points = [
10866
- start,
10867
- {
10868
- x: track,
10869
- y: start.y
10870
- },
10871
- {
10872
- x: track,
10873
- y: end.y
10874
- },
10875
- end
10876
- ];
10877
- edgeById.set(edge.id, {
10878
- ...edge,
10879
- points,
10880
- x: track,
10881
- y: (start.y + end.y) / 2,
10882
- width: edge.width ?? 0,
10883
- height: edge.height ?? 0,
10884
- routing: "orthogonal"
10885
- });
10886
- }
10887
- return {
10888
- ...graph,
10889
- nodes,
10890
- edges: graph.edges.map((edge) => edgeById.get(edge.id) ?? edge)
10891
- };
10846
+ return result;
10892
10847
  }
10893
10848
  function runLayeredPipeline(graph, options, context) {
10894
10849
  if (options.settings?.noLayout) return {
@@ -10988,7 +10943,7 @@ function runLayeredPipeline(graph, options, context) {
10988
10943
  if (cycleBreakingStrategy === "BFS_NODE_ORDER") return breakCyclesWithModelOrderBreadthFirstSearch;
10989
10944
  if (cycleBreakingStrategy === "SCC_CONNECTIVITY") return breakCyclesByStronglyConnectedConnectivity;
10990
10945
  if (cycleBreakingStrategy === "SCC_NODE_TYPE") return breakCyclesByStronglyConnectedNodeType;
10991
- throw new UnsupportedLayoutError(`Cycle-breaking strategy ${cycleBreakingStrategy} is not implemented yet`);
10946
+ throw new require_routing.UnsupportedLayoutError(`Cycle-breaking strategy ${cycleBreakingStrategy} is not implemented yet`);
10992
10947
  })();
10993
10948
  const orientation = measure("cycle-breaking", () => applyPartitionOrientation(input, applyLayerConstraintOrientation(input, cycleBreaker(input))));
10994
10949
  const layeringStrategy = options.settings?.["layering.strategy"] ?? "NETWORK_SIMPLEX";
@@ -11003,7 +10958,7 @@ function runLayeredPipeline(graph, options, context) {
11003
10958
  if (layeringStrategy === "NETWORK_SIMPLEX") return assignLayersWithNetworkSimplex;
11004
10959
  if (layeringStrategy === "MIN_WIDTH") return assignLayersWithMinWidth;
11005
10960
  if (layeringStrategy === "STRETCH_WIDTH") return assignLayersWithStretchWidth;
11006
- throw new UnsupportedLayoutError(`Layering strategy ${layeringStrategy} is not implemented yet`);
10961
+ throw new require_routing.UnsupportedLayoutError(`Layering strategy ${layeringStrategy} is not implemented yet`);
11007
10962
  })();
11008
10963
  const assignment = measure("layer-assignment", () => applyHighDegreeNodeTreatment(input, orientation, applyNodePromotion(input, orientation, applyPartitions(input, applyLayerConstraints(input, layerAssigner(input, orientation))))));
11009
10964
  let expanded = measure("long-edge-splitting", () => splitLongEdges(input, orientation, assignment));
@@ -11014,7 +10969,7 @@ function runLayeredPipeline(graph, options, context) {
11014
10969
  if (crossingStrategy === "MEDIAN_LAYER_SWEEP") return minimizeCrossingsWithMedian(options.crossingSweeps);
11015
10970
  if (crossingStrategy === "INTERACTIVE") return minimizeCrossingsInteractively;
11016
10971
  if (crossingStrategy === "NONE") return minimizeCrossingsWithModelOrder;
11017
- throw new UnsupportedLayoutError(`Crossing-minimization strategy ${crossingStrategy} is not implemented`);
10972
+ throw new require_routing.UnsupportedLayoutError(`Crossing-minimization strategy ${crossingStrategy} is not implemented`);
11018
10973
  })();
11019
10974
  let order = measure("crossing-minimization", () => applyLayerConstraintOrder(expanded.input, applyGreedySwitch(expanded.input, expanded.orientation, applySemiInteractiveOrder(expanded.input, applyForcedModelOrder(expanded.input, expanded.orientation, crossingMinimizer(expanded.input, expanded.orientation, expanded.assignment))))));
11020
10975
  const unzippingFanIn = graph.edges.length === graph.nodes.length - 1 && graph.nodes.some((node) => graph.edges.filter((edge) => edge.targetId === node.id).length === graph.nodes.length - 1);
@@ -11361,16 +11316,16 @@ function runLayeredPipeline(graph, options, context) {
11361
11316
  const targetDelta = postCompactionNodeCrossDeltas.get(edge.targetId) ?? 0;
11362
11317
  const shiftEndpoint = (endpointIndex, adjacentIndex, delta) => {
11363
11318
  if (delta === 0) return;
11364
- const endpoint$1 = points[endpointIndex];
11319
+ const endpoint = points[endpointIndex];
11365
11320
  const adjacent = points[adjacentIndex];
11366
- if (!endpoint$1) return;
11367
- const originalCross = horizontalAntiparallelFlow ? endpoint$1.y : endpoint$1.x;
11321
+ if (!endpoint) return;
11322
+ const originalCross = horizontalAntiparallelFlow ? endpoint.y : endpoint.x;
11368
11323
  points[endpointIndex] = horizontalAntiparallelFlow ? {
11369
- ...endpoint$1,
11370
- y: endpoint$1.y + delta
11324
+ ...endpoint,
11325
+ y: endpoint.y + delta
11371
11326
  } : {
11372
- ...endpoint$1,
11373
- x: endpoint$1.x + delta
11327
+ ...endpoint,
11328
+ x: endpoint.x + delta
11374
11329
  };
11375
11330
  if (adjacent && (horizontalAntiparallelFlow ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontalAntiparallelFlow ? {
11376
11331
  ...adjacent,
@@ -11460,14 +11415,14 @@ function runLayeredPipeline(graph, options, context) {
11460
11415
  for (const edge of [...incoming, ...outgoing]) {
11461
11416
  const points = [...mutableRouteMap.get(edge.id) ?? []];
11462
11417
  const endpointIndex = edge.sourceId === node.id ? 0 : points.length - 1;
11463
- const endpoint$1 = points[endpointIndex];
11464
- if (!endpoint$1) continue;
11418
+ const endpoint = points[endpointIndex];
11419
+ if (!endpoint) continue;
11465
11420
  points[endpointIndex] = horizontal ? {
11466
- ...endpoint$1,
11467
- x: endpoint$1.x + delta
11421
+ ...endpoint,
11422
+ x: endpoint.x + delta
11468
11423
  } : {
11469
- ...endpoint$1,
11470
- y: endpoint$1.y + delta
11424
+ ...endpoint,
11425
+ y: endpoint.y + delta
11471
11426
  };
11472
11427
  mutableRouteMap.set(edge.id, points);
11473
11428
  }
@@ -11572,14 +11527,14 @@ function runLayeredPipeline(graph, options, context) {
11572
11527
  if (points.length === 0) continue;
11573
11528
  const index = edge.sourceId === nodeId ? 0 : edge.targetId === nodeId ? points.length - 1 : void 0;
11574
11529
  if (index === void 0) continue;
11575
- const endpoint$1 = points[index];
11576
- const originalCross = horizontal ? endpoint$1.y : endpoint$1.x;
11530
+ const endpoint = points[index];
11531
+ const originalCross = horizontal ? endpoint.y : endpoint.x;
11577
11532
  points[index] = horizontal ? {
11578
- ...endpoint$1,
11579
- y: endpoint$1.y + delta
11533
+ ...endpoint,
11534
+ y: endpoint.y + delta
11580
11535
  } : {
11581
- ...endpoint$1,
11582
- x: endpoint$1.x + delta
11536
+ ...endpoint,
11537
+ x: endpoint.x + delta
11583
11538
  };
11584
11539
  const adjacentIndex = index === 0 ? 1 : points.length - 2;
11585
11540
  const adjacent = points[adjacentIndex];
@@ -11777,7 +11732,12 @@ function runLayeredPipeline(graph, options, context) {
11777
11732
  * custom phase strategies, and all layered edge-routing styles.
11778
11733
  */
11779
11734
  function getLayeredLayout(graph, options = {}) {
11780
- return runLayeredPipeline(graph, options);
11735
+ const result = runLayeredPipeline(graph, options);
11736
+ return {
11737
+ ...result,
11738
+ compoundGeometry: result.compoundGeometry ?? /* @__PURE__ */ new Map(),
11739
+ compoundRoutes: result.compoundRoutes ?? /* @__PURE__ */ new Map()
11740
+ };
11781
11741
  }
11782
11742
  const layeredAlgorithm = {
11783
11743
  id: "layered",
@@ -11792,7 +11752,7 @@ const layeredAlgorithm = {
11792
11752
  },
11793
11753
  layout(graph, options, context) {
11794
11754
  if (context.scope.mode === "partial" || context.scope.mode === "route-only") return context.measurePhase("partial-layout", () => runPartialLayout(graph, options ?? {}, context, getLayeredLayout));
11795
- if (context.constraints?.length && graph.nodes.some((node) => node.parentId != null)) throw new UnsupportedLayoutError("Full layout with geometry constraints does not support containers; use partial leaf constraints or full layout without geometry constraints");
11755
+ if (context.constraints?.length && graph.nodes.some((node) => node.parentId != null)) throw new require_routing.UnsupportedLayoutError("Full layout with geometry constraints does not support containers; use partial leaf constraints or full layout without geometry constraints");
11796
11756
  const result = runLayeredPipeline(graph, options ?? {}, context);
11797
11757
  if (!context.constraints?.length) return result;
11798
11758
  return context.measurePhase("constraints", () => runPartialLayout(result, options ?? {}, {
@@ -11813,18 +11773,6 @@ Object.defineProperty(exports, 'JavaRandom', {
11813
11773
  return JavaRandom;
11814
11774
  }
11815
11775
  });
11816
- Object.defineProperty(exports, 'LayoutError', {
11817
- enumerable: true,
11818
- get: function () {
11819
- return LayoutError;
11820
- }
11821
- });
11822
- Object.defineProperty(exports, 'UnsupportedLayoutError', {
11823
- enumerable: true,
11824
- get: function () {
11825
- return UnsupportedLayoutError;
11826
- }
11827
- });
11828
11776
  Object.defineProperty(exports, 'assignLayersByBreadthFirstModelOrder', {
11829
11777
  enumerable: true,
11830
11778
  get: function () {