@statelyai/layout 0.5.2 → 0.5.3

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.
@@ -1608,174 +1608,8 @@ function findNode(nodes, id) {
1608
1608
  return position !== void 0 && nodes[position]?.id === id ? nodes[position] : nodes.find((node) => node.id === id);
1609
1609
  }
1610
1610
 
1611
- //#endregion
1612
- //#region src/layered/self-loop-ports.ts
1613
- const hiddenBySettings = /* @__PURE__ */ new WeakMap();
1614
- /** Sizes of the ports SelfLoopPreProcessor hid from a loop, if any. */
1615
- function hiddenLoopPorts(settings, edgeId) {
1616
- return hiddenBySettings.get(settings)?.get(edgeId);
1617
- }
1618
- /**
1619
- * ELK's SelfLoopPreProcessor removes ports that carry only self loops from
1620
- * nodes whose port order is not fixed, and SelfLoopPortRestorer later puts
1621
- * them on the loop's side. The layered phases then see implicit loop ports;
1622
- * {@link restoreSelfLoopPorts} places the hidden ports at the loop ends.
1623
- */
1624
- function hideSelfLoopPorts(graph, options) {
1625
- const uses = /* @__PURE__ */ new Map();
1626
- const nonLoop = /* @__PURE__ */ new Set();
1627
- for (const edge of graph.edges) for (const [node, port] of [[edge.sourceId, edge.sourcePort], [edge.targetId, edge.targetPort]]) {
1628
- if (port === void 0) continue;
1629
- const key = `${node}\0${port}`;
1630
- uses.set(key, (uses.get(key) ?? 0) + 1);
1631
- if (edge.sourceId !== edge.targetId) nonLoop.add(key);
1632
- }
1633
- const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
1634
- const hidden = /* @__PURE__ */ new Map();
1635
- const edges = /* @__PURE__ */ new Map();
1636
- const hiddenPorts = /* @__PURE__ */ new Set();
1637
- for (const edge of graph.edges) {
1638
- if (edge.sourceId !== edge.targetId || edge.sourcePort === void 0 || edge.targetPort === void 0 || edge.sourcePort === edge.targetPort) continue;
1639
- const node = nodeById.get(edge.sourceId);
1640
- const constraints = node && options.nodeSettings?.(node)?.portConstraints;
1641
- if (!node || constraints !== void 0 && constraints !== "FREE" && constraints !== "UNDEFINED") continue;
1642
- const ports = [edge.sourcePort, edge.targetPort].map((name) => node.ports?.find((port) => port.name === name));
1643
- if (ports.some((port) => !port || uses.get(`${node.id}\0${port.name}`) !== 1 || nonLoop.has(`${node.id}\0${port.name}`))) continue;
1644
- const size$1 = (port) => ({
1645
- width: port.width ?? 0,
1646
- height: port.height ?? 0
1647
- });
1648
- hidden.set(edge.id, {
1649
- source: size$1(ports[0]),
1650
- target: size$1(ports[1])
1651
- });
1652
- edges.set(edge.id, edge);
1653
- for (const port of ports) hiddenPorts.add(`${node.id}\0${port.name}`);
1654
- }
1655
- if (hidden.size === 0) return void 0;
1656
- const settings = { ...options.settings };
1657
- hiddenBySettings.set(settings, hidden);
1658
- return {
1659
- graph: {
1660
- ...graph,
1661
- nodes: graph.nodes.map((node) => node.ports?.some((port) => hiddenPorts.has(`${node.id}\0${port.name}`)) ? {
1662
- ...node,
1663
- ports: node.ports.filter((port) => !hiddenPorts.has(`${node.id}\0${port.name}`))
1664
- } : node),
1665
- edges: graph.edges.map((edge) => hidden.has(edge.id) ? {
1666
- ...edge,
1667
- sourcePort: void 0,
1668
- targetPort: void 0
1669
- } : edge)
1670
- },
1671
- options: {
1672
- ...options,
1673
- settings
1674
- },
1675
- edges
1676
- };
1677
- }
1678
- /** Put hidden loop ports back, anchored on the side their loop leaves from. */
1679
- function restoreSelfLoopPorts(graph, result, edges) {
1680
- const anchors = /* @__PURE__ */ new Map();
1681
- for (const edge of result.edges) {
1682
- const original = edges.get(edge.id);
1683
- const points = edge.points;
1684
- if (!original || !points?.length) continue;
1685
- anchors.set(`${edge.sourceId}\0${original.sourcePort}`, points[0]);
1686
- anchors.set(`${edge.targetId}\0${original.targetPort}`, points.at(-1));
1687
- }
1688
- const originalById = new Map(graph.nodes.map((node) => [node.id, node]));
1689
- return {
1690
- ...result,
1691
- nodes: result.nodes.map((node) => {
1692
- const original = originalById.get(node.id)?.ports;
1693
- if (!original?.some((port) => anchors.has(`${node.id}\0${port.name}`))) return node;
1694
- const placed = new Map(node.ports?.map((port) => [port.name, port]));
1695
- return {
1696
- ...node,
1697
- ports: original.map((port) => {
1698
- const anchor = anchors.get(`${node.id}\0${port.name}`);
1699
- const width = port.width ?? 0, height = port.height ?? 0;
1700
- if (!anchor) return placed.get(port.name) ?? {
1701
- ...port,
1702
- width,
1703
- height,
1704
- x: 0,
1705
- y: 0
1706
- };
1707
- const x = anchor.x - node.x, y = anchor.y - node.y;
1708
- return {
1709
- ...port,
1710
- width,
1711
- height,
1712
- x: x <= 0 ? x : x >= node.width ? x - width : x - width / 2,
1713
- y: x <= 0 || x >= node.width ? y - height / 2 : y <= 0 ? y : y - height
1714
- };
1715
- })
1716
- };
1717
- }),
1718
- edges: result.edges.map((edge) => {
1719
- const original = edges.get(edge.id);
1720
- return original ? {
1721
- ...edge,
1722
- sourcePort: original.sourcePort,
1723
- targetPort: original.targetPort
1724
- } : edge;
1725
- })
1726
- };
1727
- }
1728
-
1729
1611
  //#endregion
1730
1612
  //#region src/layered/loop-envelopes.ts
1731
- const crossSide = (side) => side === "NORTH" || side === "SOUTH";
1732
- /**
1733
- * Extents of a movable loop's hidden ports in the canonical rightward frame:
1734
- * along its side, and protruding from it.
1735
- */
1736
- function loopPortExtents(input, edge, side) {
1737
- const ports = hiddenLoopPorts(input.settings, edge.id);
1738
- const horizontal$1 = input.direction === "right" || input.direction === "left";
1739
- const along = crossSide(side) === horizontal$1 ? "width" : "height";
1740
- const normal = along === "width" ? "height" : "width";
1741
- return {
1742
- along: ports ? ports.source[along] + ports.target[along] : 0,
1743
- normal: ports ? Math.max(ports.source[normal], ports.target[normal]) : 0
1744
- };
1745
- }
1746
- /** A movable loop's label extent away from its side, in the canonical frame. */
1747
- function movableLoopLabelExtent(input, edge, side) {
1748
- const horizontal$1 = input.direction === "right" || input.direction === "left";
1749
- const size$1 = (crossSide(side) === horizontal$1 ? edge.height : edge.width) ?? 0;
1750
- return size$1 > 0 ? size$1 + Number(input.settings["spacing.edgeLabel"] ?? 2) : 0;
1751
- }
1752
- /**
1753
- * ELK's OrthogonalSelfLoopRouter routing slot of a movable loop, measured
1754
- * from its node side in the canonical frame: past the protruding ports and
1755
- * the self-loop spacing, then one edge spacing plus any label per inner slot.
1756
- */
1757
- function movableLoopTrack(input, sideLoops, edge, side, ordering) {
1758
- const ordered = ordering === "REVERSE_STACKED" ? [...sideLoops].reverse() : sideLoops;
1759
- const inner = ordering === "SEQUENCED" ? [] : ordered.slice(0, ordered.indexOf(edge));
1760
- const edgeSpacing = Number(input.settings["spacing.edgeEdge"] ?? 10);
1761
- return Math.max(0, ...sideLoops.map((loop) => loopPortExtents(input, loop, side).normal)) + Number(input.settings["spacing.nodeSelfLoop"] ?? 10) + inner.reduce((sum, loop) => sum + edgeSpacing + (crossSide(side) ? movableLoopLabelExtent(input, loop, side) : 0), 0);
1762
- }
1763
- /**
1764
- * Canonical position of a movable loop port along its side, given its
1765
- * DISTRIBUTED ratio among the side's implicit ports (ELK's port placement
1766
- * with equal gaps between hidden ports of their own size).
1767
- */
1768
- function movableLoopPortOffset(input, sideLoops, side, length, ratio) {
1769
- const count = sideLoops.length * 2;
1770
- const extents = sideLoops.map((loop) => loopPortExtents(input, loop, side));
1771
- const total = extents.reduce((sum, extent$2) => sum + extent$2.along, 0);
1772
- const size$1 = total / count;
1773
- const index = Math.round(ratio * (count + 1)) - 1;
1774
- return {
1775
- along: (length - total) / (count + 1) * (index + 1) + size$1 * index + size$1 / 2,
1776
- normal: Math.max(0, ...extents.map((extent$2) => extent$2.normal))
1777
- };
1778
- }
1779
1613
  /** Unconstrained implicit loop ports are restored on loop faces after the sweep. */
1780
1614
  function hasMovableLoopPorts(input, edge) {
1781
1615
  if (edge.sourceId !== edge.targetId || edge.sourcePort !== void 0 || edge.targetPort !== void 0) return false;
@@ -1796,14 +1630,14 @@ function loopEnvelopes(input) {
1796
1630
  const allLoops = loopsByNodeId.get(node.id)?.slice() ?? [];
1797
1631
  const constraints = input.nodeSettings?.(node)?.portConstraints;
1798
1632
  if (allLoops.length && constraints && constraints !== "FREE" && constraints !== "UNDEFINED" && allLoops.every((edge) => edge.sourcePort !== void 0 && edge.targetPort !== void 0 && input.edgeSettings?.(edge)?.["edgeLabels.inline"] !== true && ((edge.width ?? 0) === 0 && (edge.height ?? 0) === 0 || (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER"))) {
1799
- const size$2 = input.sizes.get(node.id) ?? {
1633
+ const size$1 = input.sizes.get(node.id) ?? {
1800
1634
  width: 0,
1801
1635
  height: 0
1802
1636
  };
1803
1637
  const rect = {
1804
1638
  x: 0,
1805
1639
  y: 0,
1806
- ...size$2
1640
+ ...size$1
1807
1641
  };
1808
1642
  const envelope$1 = {
1809
1643
  before: 0,
@@ -1811,9 +1645,9 @@ function loopEnvelopes(input) {
1811
1645
  flowBefore: 0,
1812
1646
  flowAfter: 0
1813
1647
  };
1814
- const horizontal$2 = input.direction === "right" || input.direction === "left";
1648
+ const horizontal$1 = input.direction === "right" || input.direction === "left";
1815
1649
  const reverse = input.direction === "left" || input.direction === "up";
1816
- const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
1650
+ const spacing$1 = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
1817
1651
  let supported = true;
1818
1652
  const tracks = selfLoopTracks(allLoops);
1819
1653
  for (const edge of allLoops) {
@@ -1832,7 +1666,7 @@ function loopEnvelopes(input) {
1832
1666
  }, input.direction, input), getPortPoint(node, edge.targetPort, rect, {
1833
1667
  x: 0,
1834
1668
  y: 0
1835
- }, input.direction, input), sourceSide, targetSide, spacing * (tracks.get(edge.id) + 1), input.direction);
1669
+ }, input.direction, input), sourceSide, targetSide, spacing$1 * (tracks.get(edge.id) + 1), input.direction);
1836
1670
  if (labeled) {
1837
1671
  const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
1838
1672
  const { width = 0, height = 0 } = edge;
@@ -1842,13 +1676,13 @@ function loopEnvelopes(input) {
1842
1676
  });
1843
1677
  }
1844
1678
  for (const point of route) {
1845
- const cross = horizontal$2 ? point.y : point.x;
1846
- const flow = horizontal$2 ? point.x : point.y;
1847
- const flowSize$1 = horizontal$2 ? size$2.width : size$2.height;
1679
+ const cross = horizontal$1 ? point.y : point.x;
1680
+ const flow = horizontal$1 ? point.x : point.y;
1681
+ const flowSize = horizontal$1 ? size$1.width : size$1.height;
1848
1682
  envelope$1.before = Math.max(envelope$1.before, -cross);
1849
- envelope$1.after = Math.max(envelope$1.after, cross - (horizontal$2 ? size$2.height : size$2.width));
1850
- envelope$1.flowBefore = Math.max(envelope$1.flowBefore, reverse ? flow - flowSize$1 : -flow);
1851
- envelope$1.flowAfter = Math.max(envelope$1.flowAfter, reverse ? -flow : flow - flowSize$1);
1683
+ envelope$1.after = Math.max(envelope$1.after, cross - (horizontal$1 ? size$1.height : size$1.width));
1684
+ envelope$1.flowBefore = Math.max(envelope$1.flowBefore, reverse ? flow - flowSize : -flow);
1685
+ envelope$1.flowAfter = Math.max(envelope$1.flowAfter, reverse ? -flow : flow - flowSize);
1852
1686
  }
1853
1687
  }
1854
1688
  if (supported) {
@@ -1857,10 +1691,11 @@ function loopEnvelopes(input) {
1857
1691
  }
1858
1692
  }
1859
1693
  const loops = allLoops.filter((edge) => hasMovableLoopPorts(input, edge));
1860
- if (loops.length === 0) continue;
1694
+ if (loops.length === 0 || loops.some((edge) => input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true)) continue;
1861
1695
  const settings = input.nodeSettings?.(node);
1862
1696
  const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
1863
1697
  const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
1698
+ const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
1864
1699
  const envelope = {
1865
1700
  before: 0,
1866
1701
  after: 0,
@@ -1873,28 +1708,12 @@ function loopEnvelopes(input) {
1873
1708
  "flowAfter",
1874
1709
  "flowBefore"
1875
1710
  ] : distribution === "NORTH_SOUTH" ? ["before", "after"] : ["before"];
1876
- const canonicalSides = {
1877
- before: "NORTH",
1878
- after: "SOUTH",
1879
- flowAfter: "EAST",
1880
- flowBefore: "WEST"
1881
- };
1882
- const horizontal$1 = input.direction === "right" || input.direction === "left";
1883
- const size$1 = input.sizes.get(node.id) ?? {
1884
- width: 0,
1885
- height: 0
1886
- };
1887
- const flowSize = horizontal$1 ? size$1.width : size$1.height;
1888
1711
  for (const side of sides$1) {
1889
1712
  const selected = loops.filter((_, index) => sides$1[index % sides$1.length] === side);
1890
- const canonical = canonicalSides[side];
1891
- envelope[side] = Math.max(envelope[side], ...selected.map((edge) => movableLoopTrack(input, selected, edge, canonical, ordering) + movableLoopLabelExtent(input, edge, canonical)));
1892
- if (!crossSide(canonical)) continue;
1893
- for (const edge of selected) {
1894
- const overhang = ((horizontal$1 ? edge.width : edge.height) ?? 0) - flowSize;
1895
- envelope.flowBefore = Math.max(envelope.flowBefore, overhang / 2);
1896
- envelope.flowAfter = Math.max(envelope.flowAfter, overhang / 2);
1897
- }
1713
+ const horizontal$1 = input.direction === "right" || input.direction === "left";
1714
+ const crossSize$3 = side === "before" || side === "after" ? horizontal$1 ? "height" : "width" : horizontal$1 ? "width" : "height";
1715
+ const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
1716
+ envelope[side] = spacing * (ordering === "SEQUENCED" ? Math.min(1, selected.length) : selected.length) + selected.reduce((sum, edge) => sum + ((edge[crossSize$3] ?? 0) > 0 ? (edge[crossSize$3] ?? 0) + labelSpacing : 0), 0);
1898
1717
  }
1899
1718
  envelopes.set(node.id, envelope);
1900
1719
  }
@@ -3062,7 +2881,7 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
3062
2881
  if (point.x >= rect.x + rect.width - 1e-9) return "EAST";
3063
2882
  return point.y <= rect.y ? "NORTH" : "SOUTH";
3064
2883
  };
3065
- const crossSide$1 = (side) => side === "NORTH" || side === "SOUTH";
2884
+ const crossSide = (side) => side === "NORTH" || side === "SOUTH";
3066
2885
  const segments = [];
3067
2886
  for (const [edgeId, points] of canonicalRoutes) {
3068
2887
  const edge = edgeById.get(edgeId);
@@ -3072,8 +2891,8 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
3072
2891
  const first = points[index];
3073
2892
  const second = points[index + 1];
3074
2893
  if (Math.abs(first.x - second.x) > 1e-9 || Math.abs(first.y - second.y) < 1e-9) continue;
3075
- if (index === 0 && !crossSide$1(endpointSide(edge.sourceId, edge.sourcePort, first))) continue;
3076
- if (index === points.length - 2 && !crossSide$1(endpointSide(edge.targetId, edge.targetPort, second))) continue;
2894
+ if (index === 0 && !crossSide(endpointSide(edge.sourceId, edge.sourcePort, first))) continue;
2895
+ if (index === points.length - 2 && !crossSide(endpointSide(edge.targetId, edge.targetPort, second))) continue;
3077
2896
  const owner = index === 0 ? nodes.get(edge.sourceId) : index === points.length - 2 ? nodes.get(edge.targetId) : void 0;
3078
2897
  const ownerSide = owner ? endpointSide(index === 0 ? edge.sourceId : edge.targetId, index === 0 ? edge.sourcePort : edge.targetPort, index === 0 ? first : second) : void 0;
3079
2898
  const bend = index === 0 ? second : first;
@@ -3236,7 +3055,7 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
3236
3055
  const points = canonicalRoutes.get(edge.id);
3237
3056
  const sourceSide = endpointSide(edge.sourceId, edge.sourcePort, points?.[0]);
3238
3057
  const targetSide = endpointSide(edge.targetId, edge.targetPort, points?.at(-1));
3239
- if (crossSide$1(sourceSide) && crossSide$1(targetSide)) continue;
3058
+ if (crossSide(sourceSide) && crossSide(targetSide)) continue;
3240
3059
  const reversed = orientation?.reversedEdgeIds.has(edge.id) ?? source.x > target.x;
3241
3060
  const from = reversed ? target : source, to = reversed ? source : target;
3242
3061
  constraints.push({
@@ -3330,8 +3149,8 @@ function compactJoinedGeometryUnchecked(input, placement, routes, orientation) {
3330
3149
  const start = pointToCanonical(originals[0]);
3331
3150
  const next = pointToCanonical(originals[1]);
3332
3151
  const previous = pointToCanonical(originals.at(-2));
3333
- if (!crossSide$1(endpointSide(edge.sourceId, edge.sourcePort, start)) && Math.abs(start.x - next.x) < 1e-9) points[1].x = points[0].x;
3334
- if (!crossSide$1(endpointSide(edge.targetId, edge.targetPort, end)) && Math.abs(end.x - previous.x) < 1e-9) points[points.length - 2].x = points.at(-1).x;
3152
+ if (!crossSide(endpointSide(edge.sourceId, edge.sourcePort, start)) && Math.abs(start.x - next.x) < 1e-9) points[1].x = points[0].x;
3153
+ if (!crossSide(endpointSide(edge.targetId, edge.targetPort, end)) && Math.abs(end.x - previous.x) < 1e-9) points[points.length - 2].x = points.at(-1).x;
3335
3154
  }
3336
3155
  }
3337
3156
  const extent$2 = Math.max(...items.map((item) => item.x + item.width), ...[...canonicalRoutes.values()].flatMap((points) => points.map((point) => point.x)));
@@ -6608,13 +6427,13 @@ function routeEdges(style) {
6608
6427
  "WEST"
6609
6428
  ][index % 4] : distribution === "NORTH_SOUTH" && index % 2 === 1 ? "SOUTH" : "NORTH";
6610
6429
  };
6611
- const loopDistance = (loops, edge, side, ordering) => {
6612
- const axis = side === "NORTH" || side === "SOUTH" ? "height" : "width";
6430
+ const loopDistance = (loops, edge, side, ordering, canonical = false) => {
6431
+ const axis = (side === "NORTH" || side === "SOUTH") === (!canonical || horizontal$1) ? "height" : "width";
6613
6432
  const ordered = ordering === "REVERSE_STACKED" ? [...loops].reverse() : loops;
6614
6433
  const index = ordered.indexOf(edge);
6615
6434
  const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
6616
6435
  const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
6617
- const size$1 = (candidate) => input.edgeSettings?.(candidate)?.["edgeLabels.inline"] === true ? candidate[axis] ?? 0 : 0;
6436
+ const size$1 = (candidate) => canonical || input.edgeSettings?.(candidate)?.["edgeLabels.inline"] === true ? candidate[axis] ?? 0 : 0;
6618
6437
  return spacing * (ordering === "SEQUENCED" ? 1 : index + 1) + ordered.slice(0, index).reduce((extent$2, candidate) => extent$2 + (size$1(candidate) > 0 ? size$1(candidate) + labelSpacing : 0), 0) + (size$1(edge) > 0 && input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? size$1(edge) / 2 + labelSpacing : 0);
6619
6438
  };
6620
6439
  const hasFixedLoopPorts = (node) => {
@@ -6669,7 +6488,7 @@ function routeEdges(style) {
6669
6488
  for (const [id, loops] of selfLoopsByNodeId) {
6670
6489
  const rect = mutableRects.get(id);
6671
6490
  const node = nodeById.get(id);
6672
- if (!rect || !node || hasPlacementLoopEnvelope(placement, id)) continue;
6491
+ if (!rect || !node) continue;
6673
6492
  const settings = input.nodeSettings?.(node);
6674
6493
  const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
6675
6494
  const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
@@ -7784,7 +7603,7 @@ function routeEdges(style) {
7784
7603
  outsideFeedbackEdgeIds.add(edge.id);
7785
7604
  continue;
7786
7605
  }
7787
- const movable = hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id);
7606
+ const movable = hasMovableLoopPorts(input, edge) && hasPlacementLoopEnvelope(placement, source.id) && input.edgeSettings?.(edge)?.["edgeLabels.inline"] !== true;
7788
7607
  const loopRect = movable ? {
7789
7608
  x: 0,
7790
7609
  y: 0,
@@ -7824,32 +7643,7 @@ function routeEdges(style) {
7824
7643
  startRatio = (countOnSide + nestingIndex + 1) / denominator;
7825
7644
  endRatio = (countOnSide - nestingIndex) / denominator;
7826
7645
  }
7827
- const sideLoops = loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side);
7828
- if (movable) {
7829
- const track$1 = movableLoopTrack(input, sideLoops, edge, side, ordering);
7830
- const sign = side === "NORTH" ? -1 : 1;
7831
- const base = side === "NORTH" ? 0 : loopRect.height;
7832
- const [first, last] = [startRatio, endRatio].map((ratio) => movableLoopPortOffset(input, sideLoops, side, loopRect.width, ratio));
7833
- return [
7834
- {
7835
- x: first.along,
7836
- y: base + sign * first.normal
7837
- },
7838
- {
7839
- x: first.along,
7840
- y: base + sign * track$1
7841
- },
7842
- {
7843
- x: last.along,
7844
- y: base + sign * track$1
7845
- },
7846
- {
7847
- x: last.along,
7848
- y: base + sign * last.normal
7849
- }
7850
- ];
7851
- }
7852
- const distance = loopDistance(sideLoops, edge, side, ordering);
7646
+ const distance = loopDistance(loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side), edge, side, ordering, movable);
7853
7647
  const y = side === "NORTH" ? loopRect.y - distance : loopRect.y + loopRect.height + distance;
7854
7648
  const start$2 = {
7855
7649
  x: loopRect.x + loopRect.width * startRatio,
@@ -7879,32 +7673,7 @@ function routeEdges(style) {
7879
7673
  const secondRatio = sequenced ? 1 / denominator : 2 / denominator;
7880
7674
  const startRatio = side === "EAST" ? firstRatio : secondRatio;
7881
7675
  const endRatio = side === "EAST" ? secondRatio : firstRatio;
7882
- const sideLoops = loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side);
7883
- if (movable) {
7884
- const track$1 = movableLoopTrack(input, sideLoops, edge, side, ordering);
7885
- const sign = side === "WEST" ? -1 : 1;
7886
- const base = side === "WEST" ? 0 : loopRect.width;
7887
- const [first, last] = [startRatio, endRatio].map((ratio) => movableLoopPortOffset(input, sideLoops, side, loopRect.height, ratio));
7888
- return [
7889
- {
7890
- x: base + sign * first.normal,
7891
- y: first.along
7892
- },
7893
- {
7894
- x: base + sign * track$1,
7895
- y: first.along
7896
- },
7897
- {
7898
- x: base + sign * track$1,
7899
- y: last.along
7900
- },
7901
- {
7902
- x: base + sign * last.normal,
7903
- y: last.along
7904
- }
7905
- ];
7906
- }
7907
- const distance = input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? loopDistance(sideLoops, edge, side, ordering) : spacing * (indexOnSide + 1);
7676
+ const distance = movable || input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? loopDistance(loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side), edge, side, ordering, movable) : spacing * (indexOnSide + 1);
7908
7677
  const x = side === "EAST" ? loopRect.x + loopRect.width + distance : loopRect.x - distance;
7909
7678
  const start$2 = {
7910
7679
  x: side === "EAST" ? loopRect.x + loopRect.width : loopRect.x,
@@ -19167,8 +18936,6 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
19167
18936
  const separated$1 = runSeparatedComponents(graph, options, context);
19168
18937
  if (separated$1) return separated$1;
19169
18938
  }
19170
- const hiddenLoopPorts$1 = hideSelfLoopPorts(graph, options);
19171
- if (hiddenLoopPorts$1) return restoreSelfLoopPorts(graph, yield* createLayeredScopePipeline(hiddenLoopPorts$1.graph, hiddenLoopPorts$1.options, context, fallbacks), hiddenLoopPorts$1.edges);
19172
18939
  const direction = options.direction ?? graph.direction ?? "right";
19173
18940
  const padding = typeof options.padding === "number" ? {
19174
18941
  top: options.padding,
@@ -19842,20 +19609,16 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
19842
19609
  const forwardDummyRect = forwardDummy && placement.rectByNodeId.get(forwardDummy.id);
19843
19610
  const forwardDummyPort = forwardDummy?.ports?.[0];
19844
19611
  const forwardCross = forwardDummyRect && forwardDummyPort ? horizontal$1 ? forwardDummyRect.y + (forwardDummyPort.y ?? 0) : forwardDummyRect.x + (forwardDummyPort.x ?? 0) : horizontal$1 ? Math.round(forwardLabel.y + (forward.height ?? 0) / 2) : Math.round(forwardLabel.x + (forward.width ?? 0) / 2);
19845
- const alignedCross = (node, portName) => {
19612
+ const alignPort = (node, portName) => {
19846
19613
  const rect = placement.rectByNodeId.get(node.id);
19847
19614
  const port = node.ports?.find((candidate) => candidate.name === portName);
19848
- if (!rect || !port) return void 0;
19615
+ if (!rect || !port) return;
19849
19616
  const placedPort = placePorts(node.ports, rect, direction, (candidate) => input.portSettings?.(candidate, node), {
19850
19617
  ...options.settings,
19851
19618
  ...options.nodeSettings?.(node)
19852
19619
  }, (candidate) => getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate))?.find((candidate) => candidate.name === portName);
19853
- if (!placedPort) return void 0;
19854
- return forwardCross - (horizontal$1 ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2);
19855
- };
19856
- const alignPort = (node, nextCross) => {
19857
- const rect = placement.rectByNodeId.get(node.id);
19858
- if (!rect || nextCross === void 0) return;
19620
+ if (!placedPort) return;
19621
+ const nextCross = forwardCross - (horizontal$1 ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2 : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2);
19859
19622
  const currentCross = horizontal$1 ? rect.y : rect.x;
19860
19623
  postCompactionNodeCrossDeltas.set(node.id, (postCompactionNodeCrossDeltas.get(node.id) ?? 0) + nextCross - currentCross);
19861
19624
  mutableRects.set(node.id, horizontal$1 ? {
@@ -19867,12 +19630,8 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
19867
19630
  });
19868
19631
  };
19869
19632
  if (isolatedAntiparallelPair) {
19870
- const ends = [[nodesById.get(forward.sourceId), forward.sourcePort], [nodesById.get(forward.targetId), forward.targetPort]];
19871
- const crosses$1 = ends.map(([node, port]) => alignedCross(node, port));
19872
- if (!ends.some(([node], index) => {
19873
- const rect = placement.rectByNodeId.get(node.id);
19874
- return rect !== void 0 && crosses$1[index] !== void 0 && Math.abs(crosses$1[index] - (horizontal$1 ? rect.y : rect.x)) > 1e-9 && graph.edges.some((edge) => edge.sourceId === node.id && edge.targetId === node.id);
19875
- })) ends.forEach(([node], index) => alignPort(node, crosses$1[index]));
19633
+ alignPort(nodesById.get(forward.sourceId), forward.sourcePort);
19634
+ alignPort(nodesById.get(forward.targetId), forward.targetPort);
19876
19635
  }
19877
19636
  }
19878
19637
  }
@@ -20258,7 +20017,7 @@ function* createLayeredScopePipeline(graph, options, context, fallbacks = {
20258
20017
  y: (points[1].y + points[2].y - height) / 2
20259
20018
  } : void 0;
20260
20019
  const movableExteriorLoopLabelPosition = (() => {
20261
- if (edgeRouting !== "ORTHOGONAL" || labelPlacement !== "CENTER" || !hasMovableLoopPorts(input, edge) || !sourceRect || points.length !== 4) return void 0;
20020
+ if (edgeRouting !== "ORTHOGONAL" || labelPlacement !== "CENTER" || inlineLabel || !hasMovableLoopPorts(input, edge) || !sourceRect || points.length !== 4) return void 0;
20262
20021
  const first = points[1], last = points[2];
20263
20022
  if (first.y === last.y) {
20264
20023
  const low = first.y < sourceRect.y;
@@ -1,4 +1,4 @@
1
- import { K as placePorts, X as JavaRandom, i as layeredAlgorithm, n as elkjs0111LayeredAlgorithm, x as setElkjs0111ResultPolicy, z as getPolylineMidpoint } from "./layered-BtnmL9_b.mjs";
1
+ import { K as placePorts, X as JavaRandom, i as layeredAlgorithm, n as elkjs0111LayeredAlgorithm, x as setElkjs0111ResultPolicy, z as getPolylineMidpoint } from "./layered-CmL7oynw.mjs";
2
2
  import { getNodeSize } from "@statelyai/graph/layout";
3
3
 
4
4
  //#region src/fixed.ts
@@ -1,4 +1,4 @@
1
- const require_layered = require('./layered-B1AE7Zpr.cjs');
1
+ const require_layered = require('./layered-SB2mlAqt.cjs');
2
2
  let _statelyai_graph_layout = require("@statelyai/graph/layout");
3
3
 
4
4
  //#region src/fixed.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/layout",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Extensible, graph-native layout algorithms for @statelyai/graph",
5
5
  "license": "EPL-2.0 OR GPL-3.0-or-later",
6
6
  "repository": {
@@ -8,7 +8,6 @@ import { countAllCrossings } from "./crossing-counter";
8
8
  import { crossingGraph } from "./crossing-graph";
9
9
  import { sortInitialModelOrder, markInitialModelOrderPrepared } from "./initial-model-order";
10
10
  import { hasMovableLoopPorts } from "./loop-envelopes";
11
- import { hideSelfLoopPorts, restoreSelfLoopPorts } from "./self-loop-ports";
12
11
  import { fixedSelfLoopLabels, fixedSelfLoopSide } from "./fixed-self-loop";
13
12
  import { compactionBounds, placementCrossBounds } from "./compaction-bounds";
14
13
  import { setElkjs0111ResultPolicy } from "../internal/elkjs-compatibility";
@@ -1425,18 +1424,6 @@ export function* createLayeredScopePipeline<N, E, G, P>(
1425
1424
  const separated = runSeparatedComponents(graph, options, context);
1426
1425
  if (separated) return separated;
1427
1426
  }
1428
- const hiddenLoopPorts = hideSelfLoopPorts(graph as Graph<N, E, G, P>, options);
1429
- if (hiddenLoopPorts)
1430
- return restoreSelfLoopPorts(
1431
- graph as Graph<N, E, G, P>,
1432
- yield* createLayeredScopePipeline(
1433
- hiddenLoopPorts.graph,
1434
- hiddenLoopPorts.options,
1435
- context,
1436
- fallbacks,
1437
- ),
1438
- hiddenLoopPorts.edges,
1439
- );
1440
1427
  const direction = options.direction ?? graph.direction ?? "right";
1441
1428
  const padding =
1442
1429
  typeof options.padding === "number"
@@ -2520,10 +2507,10 @@ export function* createLayeredScopePipeline<N, E, G, P>(
2520
2507
  : horizontal
2521
2508
  ? Math.round(forwardLabel.y + (forward.height ?? 0) / 2)
2522
2509
  : Math.round(forwardLabel.x + (forward.width ?? 0) / 2);
2523
- const alignedCross = (node: GraphNode, portName: string): number | undefined => {
2510
+ const alignPort = (node: GraphNode, portName: string): void => {
2524
2511
  const rect = placement.rectByNodeId.get(node.id);
2525
2512
  const port = node.ports?.find((candidate) => candidate.name === portName);
2526
- if (!rect || !port) return undefined;
2513
+ if (!rect || !port) return;
2527
2514
  const placedPort = placePorts(
2528
2515
  node.ports,
2529
2516
  rect,
@@ -2533,15 +2520,11 @@ export function* createLayeredScopePipeline<N, E, G, P>(
2533
2520
  (candidate) =>
2534
2521
  getOrientedPortDirection(expanded.input, expanded.orientation, node, candidate),
2535
2522
  )?.find((candidate) => candidate.name === portName);
2536
- if (!placedPort) return undefined;
2523
+ if (!placedPort) return;
2537
2524
  const localCross = horizontal
2538
2525
  ? (placedPort.y ?? 0) + (placedPort.height ?? 0) / 2
2539
2526
  : (placedPort.x ?? 0) + (placedPort.width ?? 0) / 2;
2540
- return forwardCross - localCross;
2541
- };
2542
- const alignPort = (node: GraphNode, nextCross: number | undefined): void => {
2543
- const rect = placement.rectByNodeId.get(node.id);
2544
- if (!rect || nextCross === undefined) return;
2527
+ const nextCross = forwardCross - localCross;
2545
2528
  const currentCross = horizontal ? rect.y : rect.x;
2546
2529
  postCompactionNodeCrossDeltas.set(
2547
2530
  node.id,
@@ -2553,23 +2536,8 @@ export function* createLayeredScopePipeline<N, E, G, P>(
2553
2536
  );
2554
2537
  };
2555
2538
  if (isolatedAntiparallelPair) {
2556
- const ends = [
2557
- [nodesById.get(forward.sourceId)!, forward.sourcePort],
2558
- [nodesById.get(forward.targetId)!, forward.targetPort],
2559
- ] as const;
2560
- const crosses = ends.map(([node, port]) => alignedCross(node, port));
2561
- // ELK keeps a loop owner where BK placed it, and moving it here would
2562
- // leave its loop and label behind, so such a pair stays as placed.
2563
- const movesLoopOwner = ends.some(([node], index) => {
2564
- const rect = placement.rectByNodeId.get(node.id);
2565
- return (
2566
- rect !== undefined &&
2567
- crosses[index] !== undefined &&
2568
- Math.abs(crosses[index]! - (horizontal ? rect.y : rect.x)) > 1e-9 &&
2569
- graph.edges.some((edge) => edge.sourceId === node.id && edge.targetId === node.id)
2570
- );
2571
- });
2572
- if (!movesLoopOwner) ends.forEach(([node], index) => alignPort(node, crosses[index]));
2539
+ alignPort(nodesById.get(forward.sourceId)!, forward.sourcePort);
2540
+ alignPort(nodesById.get(forward.targetId)!, forward.targetPort);
2573
2541
  }
2574
2542
  }
2575
2543
  }
@@ -3174,6 +3142,7 @@ export function* createLayeredScopePipeline<N, E, G, P>(
3174
3142
  if (
3175
3143
  edgeRouting !== "ORTHOGONAL" ||
3176
3144
  labelPlacement !== "CENTER" ||
3145
+ inlineLabel ||
3177
3146
  !hasMovableLoopPorts(input, edge) ||
3178
3147
  !sourceRect ||
3179
3148
  points.length !== 4