@weasel-js/diagram 1.11.0 → 1.12.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 (37) hide show
  1. package/README.md +696 -4
  2. package/dist/chunk-2SYY4W3B.js +2055 -0
  3. package/dist/chunk-2SYY4W3B.js.map +1 -0
  4. package/dist/chunk-5GIP2LA6.js +312 -0
  5. package/dist/chunk-5GIP2LA6.js.map +1 -0
  6. package/dist/chunk-DYDX6EJC.js +3653 -0
  7. package/dist/chunk-DYDX6EJC.js.map +1 -0
  8. package/dist/chunk-GA4BX7SH.js +1431 -0
  9. package/dist/chunk-GA4BX7SH.js.map +1 -0
  10. package/dist/{chunk-NUMMW5BI.js → chunk-R2XB6D32.js} +416 -36
  11. package/dist/chunk-R2XB6D32.js.map +1 -0
  12. package/dist/chunk-WDIE35AK.js +3 -0
  13. package/dist/chunk-WDIE35AK.js.map +1 -0
  14. package/dist/{fromData-GKqRgPFs.d.ts → dataTypes-DHdkzCL8.d.ts} +238 -59
  15. package/dist/effect-audio.d.ts +139 -0
  16. package/dist/effect-audio.js +533 -0
  17. package/dist/effect-audio.js.map +1 -0
  18. package/dist/effect.d.ts +486 -121
  19. package/dist/effect.js +1632 -347
  20. package/dist/effect.js.map +1 -1
  21. package/dist/icons.table.d.ts +6 -0
  22. package/dist/icons.table.js +32 -0
  23. package/dist/icons.table.js.map +1 -0
  24. package/dist/icons.table.json +24 -0
  25. package/dist/index.d.ts +2159 -92
  26. package/dist/index.js +1056 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/layout.d.ts +3 -3
  29. package/dist/layout.entry-B-jJMc50.d.ts +617 -0
  30. package/dist/layout.js +3 -1
  31. package/dist/stageTypes-DvnR8cKQ.d.ts +189 -0
  32. package/dist/{body-2lC7IiIA.d.ts → types-M_ccNAlk.d.ts} +221 -182
  33. package/package.json +28 -7
  34. package/dist/chunk-C4NAI6NV.js +0 -2041
  35. package/dist/chunk-C4NAI6NV.js.map +0 -1
  36. package/dist/chunk-NUMMW5BI.js.map +0 -1
  37. package/dist/layout.entry-BoWFcR4G.d.ts +0 -285
@@ -1,5 +1,5 @@
1
- import { AUTO_POSE_DESCRIPTOR, rotatePoint, rectPath, polygonFromPoints, PATH_M, PATH_L, PATH_C, flattenCubic, PATH_Q, PATH_Z, PathBuilder } from '@weasel-js/core/math';
2
- import { transformPath } from '@weasel-js/geom';
1
+ import { AUTO_POSE_DESCRIPTOR, rotatePoint, rectPath, polygonFromPoints, PATH_M, PATH_L, PATH_C, flattenCubic, PATH_Q, PATH_Z, PathBuilder, clipNothing, pointAlongPath } from '@weasel-js/core/math';
2
+ import { transformPath, ellipsePath, PATH_M as PATH_M$1, PATH_CMD_LENGTHS, measurePath, forEachSegment, PATH_L as PATH_L$1, nearestOnLine, PATH_Q as PATH_Q$1, nearestOnQuadratic, PATH_C as PATH_C$1, nearestOnCubic } from '@weasel-js/geom';
3
3
  import { cssFamilyName } from '@weasel-js/font/metrics';
4
4
  import { createSimulation } from '@weasel-js/simulation';
5
5
 
@@ -48,6 +48,8 @@ function outlinePath(outline, bounds) {
48
48
  ]);
49
49
  case "stadium":
50
50
  return stadiumPath(x, y, w, h);
51
+ case "ellipse":
52
+ return ellipsePath(bounds);
51
53
  }
52
54
  }
53
55
  function stadiumPath(x, y, w, h) {
@@ -85,6 +87,11 @@ function contentBox(outline, bounds) {
85
87
  return { x, y: y + h / 4, width: w, height: h / 2 };
86
88
  case "hexagon":
87
89
  return { x: x + w * POINT, y, width: w * (1 - POINT * 2), height: h };
90
+ case "ellipse": {
91
+ const cw = w / Math.SQRT2;
92
+ const ch = h / Math.SQRT2;
93
+ return { x: x + (w - cw) / 2, y: y + (h - ch) / 2, width: cw, height: ch };
94
+ }
88
95
  case "stadium": {
89
96
  const r = Math.min(w, h) / 2;
90
97
  return w >= h ? { x: x + r, y, width: Math.max(w - r * 2, 0), height: h } : { x, y: y + r, width: w, height: Math.max(h - r * 2, 0) };
@@ -106,6 +113,8 @@ function boxForContent(outline, content) {
106
113
  return { width: content.width / (1 - POINT * 2), height: content.height };
107
114
  case "stadium":
108
115
  return { width: content.width + content.height, height: content.height };
116
+ case "ellipse":
117
+ return { width: content.width * Math.SQRT2, height: content.height * Math.SQRT2 };
109
118
  }
110
119
  }
111
120
  var TOLERANCE = 0.25;
@@ -217,6 +226,7 @@ var dataKeyReader = (node) => {
217
226
  if (trait === null || typeof trait !== "object") return null;
218
227
  if ("from" in trait && "to" in trait) return null;
219
228
  if ("label" in trait || "anchor" in trait || "mark" in trait) return null;
229
+ if (trait.container?.role === "lane") return null;
220
230
  return trait;
221
231
  };
222
232
  function createDiagramNodes(entries) {
@@ -492,6 +502,7 @@ function buildBody(spec, at2, opts) {
492
502
  layer: opts.layer,
493
503
  pose,
494
504
  data: opts.body(trait, pose),
505
+ clipFromPose: clipNothing,
495
506
  ...opts.id === void 0 ? {} : { id: opts.id }
496
507
  }];
497
508
  const leaf = (box, data, rowId) => {
@@ -735,6 +746,10 @@ function crossInset(inset, axes) {
735
746
  const c = canonical(inset, axes);
736
747
  return { lo: c.crossLo, hi: c.crossHi };
737
748
  }
749
+ function rankInset(inset, axes) {
750
+ const c = canonical(inset, axes);
751
+ return { lo: c.rankLo, hi: c.rankHi };
752
+ }
738
753
  function rankGaps(graph, rows, axes, rankGap) {
739
754
  const gaps = rows.slice(1).map(() => rankGap);
740
755
  const ends2 = /* @__PURE__ */ new Map();
@@ -922,6 +937,181 @@ function expandGroupEdges(edges, groups) {
922
937
  }
923
938
  return out;
924
939
  }
940
+ function subpathOf(path, index) {
941
+ if (path.kind !== "polygon") return index === 0 ? path : null;
942
+ const { commands, coords } = path;
943
+ let seen = -1;
944
+ let start = -1;
945
+ let startCoord = 0;
946
+ let coord2 = 0;
947
+ for (let i = 0; i <= commands.length; i++) {
948
+ const cmd = commands[i];
949
+ if (i === commands.length || cmd === PATH_M$1) {
950
+ if (seen === index) {
951
+ if (start === 0 && i === commands.length) return path;
952
+ return { kind: "polygon", fillRule: path.fillRule, commands: commands.slice(start, i), coords: coords.slice(startCoord, coord2) };
953
+ }
954
+ seen++;
955
+ start = i;
956
+ startCoord = coord2;
957
+ }
958
+ if (cmd !== void 0) coord2 += PATH_CMD_LENGTHS[cmd] ?? 0;
959
+ }
960
+ return null;
961
+ }
962
+ var NAMED = { start: 0, mid: 0.5, end: 1 };
963
+ function nearestStation(path, near) {
964
+ if (path.kind !== "polygon") return null;
965
+ const { commands, coords } = path;
966
+ let best = null;
967
+ let heading = { x: 1, y: 0 };
968
+ forEachSegment(commands, coords, (cmd, ci, x0, y0) => {
969
+ let hit;
970
+ let d;
971
+ if (cmd === PATH_L$1) {
972
+ hit = nearestOnLine(near.x, near.y, x0, y0, coords[ci], coords[ci + 1]);
973
+ d = { x: coords[ci] - x0, y: coords[ci + 1] - y0 };
974
+ } else if (cmd === PATH_Q$1) {
975
+ hit = nearestOnQuadratic(near.x, near.y, x0, y0, coords[ci], coords[ci + 1], coords[ci + 2], coords[ci + 3]);
976
+ const t = hit.t;
977
+ d = {
978
+ x: 2 * (1 - t) * (coords[ci] - x0) + 2 * t * (coords[ci + 2] - coords[ci]),
979
+ y: 2 * (1 - t) * (coords[ci + 1] - y0) + 2 * t * (coords[ci + 3] - coords[ci + 1])
980
+ };
981
+ } else if (cmd === PATH_C$1) {
982
+ hit = nearestOnCubic(
983
+ near.x,
984
+ near.y,
985
+ x0,
986
+ y0,
987
+ coords[ci],
988
+ coords[ci + 1],
989
+ coords[ci + 2],
990
+ coords[ci + 3],
991
+ coords[ci + 4],
992
+ coords[ci + 5]
993
+ );
994
+ const t = hit.t;
995
+ const u = 1 - t;
996
+ d = {
997
+ x: 3 * u * u * (coords[ci] - x0) + 6 * u * t * (coords[ci + 2] - coords[ci]) + 3 * t * t * (coords[ci + 4] - coords[ci + 2]),
998
+ y: 3 * u * u * (coords[ci + 1] - y0) + 6 * u * t * (coords[ci + 3] - coords[ci + 1]) + 3 * t * t * (coords[ci + 5] - coords[ci + 3])
999
+ };
1000
+ } else return;
1001
+ const len = Math.hypot(d.x, d.y);
1002
+ if (len === 0 || best !== null && hit.dist >= best.dist) return;
1003
+ best = hit;
1004
+ heading = { x: d.x / len, y: d.y / len };
1005
+ });
1006
+ const found = best;
1007
+ return found === null ? null : { point: { x: found.x, y: found.y }, tangent: heading };
1008
+ }
1009
+ function labelCenter(whole, label, size) {
1010
+ const path = subpathOf(whole, 0) ?? whole;
1011
+ const at2 = label.placed ?? label;
1012
+ const station = typeof at2.at === "object" ? nearestStation(path, at2.at.near) ?? pointAlongPath(path, 0.5) : pointAlongPath(path, typeof at2.at === "number" ? at2.at : NAMED[at2.at] ?? 0.5);
1013
+ if (station === null) return null;
1014
+ return besideStation(station.point, station.tangent, at2.offset ?? 0, size);
1015
+ }
1016
+ function halfDepth(tangent, size) {
1017
+ return (Math.abs(tangent.y) * size.width + Math.abs(tangent.x) * size.height) / 2;
1018
+ }
1019
+ function besideStation(point, tangent, offset, size) {
1020
+ const reach = offset + Math.sign(offset) * halfDepth(tangent, size);
1021
+ return { x: point.x + tangent.y * reach, y: point.y - tangent.x * reach };
1022
+ }
1023
+ function offsetFrom(point, tangent, center2, size) {
1024
+ const d = (center2.x - point.x) * tangent.y - (center2.y - point.y) * tangent.x;
1025
+ const offset = Math.sign(d) * Math.max(Math.abs(d) - halfDepth(tangent, size), 0);
1026
+ return offset === 0 ? 0 : offset;
1027
+ }
1028
+ var NUDGE = 1e-9;
1029
+ function labelSpotAt(whole, center2, size) {
1030
+ const measure = measurePath(subpathOf(whole, 0) ?? whole);
1031
+ if (measure === null) return null;
1032
+ const station = measure.nearest(center2);
1033
+ if (measure.length === 0) return { at: 0.5, offset: offsetFrom(station.point, station.tangent, center2, size) };
1034
+ const at2 = station.s / measure.length;
1035
+ const step = NUDGE * measure.length;
1036
+ const before = measure.at(station.s - step).tangent;
1037
+ const after = measure.at(station.s + step).tangent;
1038
+ if (before.x === after.x && before.y === after.y) {
1039
+ return { at: at2, offset: offsetFrom(station.point, station.tangent, center2, size) };
1040
+ }
1041
+ const reads = measure.at(at2 * measure.length).tangent;
1042
+ let best = { at: at2, offset: 0 };
1043
+ let bestDist = Infinity;
1044
+ for (const [tangent, side] of [[before, -1], [after, 1]]) {
1045
+ const offset = offsetFrom(station.point, tangent, center2, size);
1046
+ const back = besideStation(station.point, tangent, offset, size);
1047
+ const dist = Math.hypot(back.x - center2.x, back.y - center2.y);
1048
+ if (dist >= bestDist) continue;
1049
+ const onLeg = reads.x === tangent.x && reads.y === tangent.y;
1050
+ best = { at: onLeg ? at2 : at2 + side * NUDGE, offset };
1051
+ bestDist = dist;
1052
+ }
1053
+ return best;
1054
+ }
1055
+
1056
+ // src/edgeTrait.ts
1057
+ function layoutOwnsPort(end) {
1058
+ return end.port === void 0 || end.port === end.laidOut;
1059
+ }
1060
+ function layoutOwnsRoute(edge) {
1061
+ const points = edge.waypoints ?? [];
1062
+ if (points.length === 0) return true;
1063
+ return edge.laidOut !== void 0 && samePoints(points, edge.laidOut);
1064
+ }
1065
+ function layoutWritesRoute(edge) {
1066
+ return edge.router !== "avoid" && layoutOwnsRoute(edge);
1067
+ }
1068
+ function samePoints(a, b) {
1069
+ return a.length === b.length && a.every((p, i) => p.x === b[i].x && p.y === b[i].y);
1070
+ }
1071
+ function diagramEdgeOf(node) {
1072
+ const data = node.data;
1073
+ if (data === null || typeof data !== "object") return null;
1074
+ const edge = data.diagram;
1075
+ if (edge === null || typeof edge !== "object") return null;
1076
+ return "from" in edge && "to" in edge ? edge : null;
1077
+ }
1078
+
1079
+ // src/avoid/holds.ts
1080
+ var EPS2 = 1e-6;
1081
+ function runsAlong(d, normal, sign) {
1082
+ const len = Math.hypot(d.x, d.y) * Math.hypot(normal.x, normal.y);
1083
+ if (len === 0) return false;
1084
+ const cross = d.x * normal.y - d.y * normal.x;
1085
+ return Math.abs(cross) <= EPS2 * len && sign * (d.x * normal.x + d.y * normal.y) > 0;
1086
+ }
1087
+ function onSegment(p, a, b) {
1088
+ const dx = b.x - a.x;
1089
+ const dy = b.y - a.y;
1090
+ const len2 = dx * dx + dy * dy;
1091
+ const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2));
1092
+ return Math.hypot(a.x + t * dx - p.x, a.y + t * dy - p.y) <= EPS2 * Math.max(1, Math.sqrt(len2)) ? t : null;
1093
+ }
1094
+ function routeHolds(from, to, waypoints, route) {
1095
+ const points = [from.point, ...route, to.point];
1096
+ const n = points.length;
1097
+ const first = { x: points[1].x - points[0].x, y: points[1].y - points[0].y };
1098
+ const last = { x: points[n - 1].x - points[n - 2].x, y: points[n - 1].y - points[n - 2].y };
1099
+ if (from.normal !== null && !runsAlong(first, from.normal, 1)) return false;
1100
+ if (to.normal !== null && !runsAlong(last, to.normal, -1)) return false;
1101
+ let seg = 0;
1102
+ let along = 0;
1103
+ for (const w of waypoints) {
1104
+ for (; seg < n - 1; seg++, along = 0) {
1105
+ const t = onSegment(w, points[seg], points[seg + 1]);
1106
+ if (t !== null && t >= along) {
1107
+ along = t;
1108
+ break;
1109
+ }
1110
+ }
1111
+ if (seg === n - 1) return false;
1112
+ }
1113
+ return true;
1114
+ }
925
1115
 
926
1116
  // src/forceGroups.ts
927
1117
  function resolve(groups, byId) {
@@ -1036,7 +1226,7 @@ var DEFAULTS = {
1036
1226
  groupStrength: 0.2
1037
1227
  };
1038
1228
  var GOLDEN = Math.PI * (3 - Math.sqrt(5));
1039
- var NUDGE = 0.5;
1229
+ var NUDGE2 = 0.5;
1040
1230
  function charge(bodies, strength) {
1041
1231
  return (alpha) => {
1042
1232
  for (let i = 0; i < bodies.length; i++) {
@@ -1179,7 +1369,7 @@ function separate(bodies) {
1179
1369
  seen.set(key, nth + 1);
1180
1370
  if (nth === 0 || body.fx != null || body.fy != null) return;
1181
1371
  const angle = GOLDEN * i;
1182
- const radius = NUDGE * nth;
1372
+ const radius = NUDGE2 * nth;
1183
1373
  body.x += Math.cos(angle) * radius;
1184
1374
  body.y += Math.sin(angle) * radius;
1185
1375
  });
@@ -1786,8 +1976,18 @@ function chainsOf(groups) {
1786
1976
  };
1787
1977
  return (group) => group === void 0 ? [] : chain2(group, /* @__PURE__ */ new Set());
1788
1978
  }
1789
- var newBlock = (first) => ({ items: [], sub: /* @__PURE__ */ new Map(), sum: 0, count: 0, first });
1790
- function sortRank(row, key, chainOf) {
1979
+ var newBlock = (first, group) => ({
1980
+ ...group !== void 0 ? { group } : {},
1981
+ items: [],
1982
+ sub: /* @__PURE__ */ new Map(),
1983
+ sum: 0,
1984
+ count: 0,
1985
+ first
1986
+ });
1987
+ function placesOf(groups) {
1988
+ return new Map(groups.filter((g) => g.ordered !== void 0).map((g) => [g.id, new Map(g.ordered.map((m, i) => [m, i]))]));
1989
+ }
1990
+ function sortRank(row, key, chainOf, places = /* @__PURE__ */ new Map()) {
1791
1991
  const root = newBlock(0);
1792
1992
  const keyOf = /* @__PURE__ */ new Map();
1793
1993
  const firstOf = /* @__PURE__ */ new Map();
@@ -1797,7 +1997,7 @@ function sortRank(row, key, chainOf) {
1797
1997
  for (const g of chainOf(n.group)) {
1798
1998
  let next = at2.sub.get(g);
1799
1999
  if (next === void 0) {
1800
- next = newBlock(i);
2000
+ next = newBlock(i, g);
1801
2001
  at2.sub.set(g, next);
1802
2002
  at2.items.push(next);
1803
2003
  }
@@ -1811,7 +2011,12 @@ function sortRank(row, key, chainOf) {
1811
2011
  });
1812
2012
  const out = [];
1813
2013
  const flatten = (block) => {
2014
+ const place = block.group === void 0 ? void 0 : places.get(block.group);
1814
2015
  const items = block.items.map((item) => {
2016
+ if (place !== void 0) {
2017
+ const k = place.get("id" in item ? item.id : item.group) ?? Infinity;
2018
+ return { item, k, first: "id" in item ? firstOf.get(item) : item.first };
2019
+ }
1815
2020
  if ("id" in item) return { item, k: keyOf.get(item), first: firstOf.get(item) };
1816
2021
  return { item, k: item.sum / item.count, first: item.first };
1817
2022
  });
@@ -1837,7 +2042,7 @@ function reorder(work, r, side) {
1837
2042
  if (near === void 0 || near.length === 0) return i;
1838
2043
  return near.reduce((s, m) => s + landing(m, n.id, work) - 0.5, 0) / near.length;
1839
2044
  };
1840
- work.rows[r] = sortRank(work.rows[r], key, work.chainOf);
2045
+ work.rows[r] = sortRank(work.rows[r], key, work.chainOf, work.places);
1841
2046
  reindex(work, r);
1842
2047
  }
1843
2048
  function between(work, side, u, v) {
@@ -1858,7 +2063,7 @@ function transpose(work) {
1858
2063
  for (let i = 0; i + 1 < row.length; i++) {
1859
2064
  const u = row[i];
1860
2065
  const v = row[i + 1];
1861
- if (u.group !== v.group) continue;
2066
+ if (u.group !== v.group || u.group !== void 0 && work.places.has(u.group)) continue;
1862
2067
  const now = cost(u.id, v.id);
1863
2068
  pos.set(v.id, i);
1864
2069
  pos.set(u.id, i + 1);
@@ -1878,6 +2083,7 @@ function orderRows(rows, hops, opts = {}) {
1878
2083
  const patience = opts.patience ?? 4;
1879
2084
  const maxSweeps = opts.maxSweeps ?? 24;
1880
2085
  const chainOf = chainsOf(opts.groups ?? []);
2086
+ const places = placesOf(opts.groups ?? []);
1881
2087
  const byRank = hopsByRank(rows, hops);
1882
2088
  const ups = /* @__PURE__ */ new Map();
1883
2089
  const downs = /* @__PURE__ */ new Map();
@@ -1887,9 +2093,9 @@ function orderRows(rows, hops, opts = {}) {
1887
2093
  (ups.get(p.to) ?? ups.set(p.to, []).get(p.to)).push({ id: p.from, frac: p.fromAt, places: p.fromPlaces, side: "down" });
1888
2094
  }
1889
2095
  }
1890
- const seeded = rows.map((row) => sortRank(row, (_, i) => i, chainOf));
2096
+ const seeded = rows.map((row) => sortRank(row, (_, i) => i, chainOf, places));
1891
2097
  const sides = sidesOf(hops);
1892
- const work = { rows: seeded, pos: positions(seeded), ups, downs, sides, chainOf };
2098
+ const work = { rows: seeded, pos: positions(seeded), ups, downs, sides, chainOf, places };
1893
2099
  let best = seeded.map((row) => [...row]);
1894
2100
  let fewest = total(work.rows, byRank, work.pos, sides);
1895
2101
  for (let sweep = 0, stale = 0; sweep < maxSweeps && stale < patience && fewest > 0; sweep++) {
@@ -1975,7 +2181,7 @@ function around(a, b, others, axes, rankGap) {
1975
2181
  }
1976
2182
 
1977
2183
  // src/layered/across.ts
1978
- var EPS2 = 1e-6;
2184
+ var EPS3 = 1e-6;
1979
2185
  function settleAcross(input) {
1980
2186
  const { graph, rows, axes } = input;
1981
2187
  const groups = new Map((graph.groups ?? []).map((g) => [g.id, g]));
@@ -2034,7 +2240,7 @@ function squeeze(items, order, xs, gap) {
2034
2240
  for (const list of order) {
2035
2241
  let edge = -Infinity;
2036
2242
  for (const i of list) {
2037
- if (free(i) && xs[i] < edge - EPS2) [xs[i], moved] = [edge, true];
2243
+ if (free(i) && xs[i] < edge - EPS3) [xs[i], moved] = [edge, true];
2038
2244
  edge = Math.max(edge, xs[i] + items[i].width + gap);
2039
2245
  }
2040
2246
  }
@@ -2045,7 +2251,7 @@ function squeeze(items, order, xs, gap) {
2045
2251
  let edge = Infinity;
2046
2252
  for (const i of [...list].reverse()) {
2047
2253
  const most = edge - items[i].width - gap;
2048
- if (free(i) && xs[i] > most + EPS2) [xs[i], moved] = [most, true];
2254
+ if (free(i) && xs[i] > most + EPS3) [xs[i], moved] = [most, true];
2049
2255
  edge = Math.min(edge, xs[i]);
2050
2256
  }
2051
2257
  }
@@ -2100,7 +2306,7 @@ function clear(items, list, xs, gap) {
2100
2306
  for (const j of list.slice(k + 1)) {
2101
2307
  const pin = (k2) => items[k2].fixed !== void 0 && items[k2].group === void 0;
2102
2308
  if (pin(i) && pin(j)) continue;
2103
- const apart = xs[j] >= xs[i] + items[i].width + gap - EPS2 || xs[i] >= xs[j] + items[j].width + gap - EPS2;
2309
+ const apart = xs[j] >= xs[i] + items[i].width + gap - EPS3 || xs[i] >= xs[j] + items[j].width + gap - EPS3;
2104
2310
  if (!apart) return false;
2105
2311
  }
2106
2312
  }
@@ -2118,7 +2324,7 @@ function jump(items, list, xs, gap) {
2118
2324
  for (let moved = true; moved; ) {
2119
2325
  moved = false;
2120
2326
  for (const b of blocks) {
2121
- if (x < b.hi - EPS2 && x + width > b.lo + EPS2) [x, moved] = [dir === 1 ? b.hi : b.lo - width, true];
2327
+ if (x < b.hi - EPS3 && x + width > b.lo + EPS3) [x, moved] = [dir === 1 ? b.hi : b.lo - width, true];
2122
2328
  }
2123
2329
  }
2124
2330
  out[i] = x;
@@ -2134,7 +2340,7 @@ function jump(items, list, xs, gap) {
2134
2340
  }
2135
2341
 
2136
2342
  // src/layered/bands.ts
2137
- var EPS3 = 1e-6;
2343
+ var EPS4 = 1e-6;
2138
2344
  function placeBands(input, covers) {
2139
2345
  const { natural, minGaps } = input;
2140
2346
  const n = natural.length;
@@ -2143,7 +2349,7 @@ function placeBands(input, covers) {
2143
2349
  for (let k = 0, lo = -Infinity; k < n; k++) {
2144
2350
  earliest.push(lo);
2145
2351
  const cover = covers.get(k);
2146
- if (cover !== void 0 && lo > cover.lo + EPS3) return void 0;
2352
+ if (cover !== void 0 && lo > cover.lo + EPS4) return void 0;
2147
2353
  lo = hiOf(k, lo) + (minGaps[k] ?? 0);
2148
2354
  }
2149
2355
  const los = preferred(input, covers, hiOf).map((lo, k) => Math.max(lo, earliest[k]));
@@ -2185,7 +2391,7 @@ function spread(input, covers, los, hiOf) {
2185
2391
  const share = (k) => {
2186
2392
  if (room >= usual) return gaps[k] + (usual > 0 ? (room - usual) * gaps[k] / usual : (room - usual) / ks.length);
2187
2393
  const slack = usual - least;
2188
- return minGaps[k] + (slack > EPS3 ? (room - least) * (gaps[k] - minGaps[k]) / slack : (room - least) / ks.length);
2394
+ return minGaps[k] + (slack > EPS4 ? (room - least) * (gaps[k] - minGaps[k]) / slack : (room - least) / ks.length);
2189
2395
  };
2190
2396
  for (let k = f + 1; k < g; k++) los[k] = hiOf(k - 1, los[k - 1]) + share(k - 1);
2191
2397
  }
@@ -2490,12 +2696,12 @@ function planOnce(graph, opts = {}) {
2490
2696
  spaced[r] = spaced[r] - rankGap * (1 - LABEL_GAP);
2491
2697
  }
2492
2698
  const pinned = pinnedSet(graph, opts);
2493
- const { crossAt, gaps, depths, rankAt, unheld } = holdPins({ graph, rows, crossAt: packed, gaps: spaced, pinned, axes, nodeGap, minGaps });
2699
+ const { crossAt, gaps, depths: depths2, rankAt, unheld } = holdPins({ graph, rows, crossAt: packed, gaps: spaced, pinned, axes, nodeGap, minGaps });
2494
2700
  const slots = /* @__PURE__ */ new Map();
2495
2701
  const bands = [];
2496
2702
  let along = 0;
2497
2703
  for (const [i, row] of rows.entries()) {
2498
- const depth = depths[i];
2704
+ const depth = depths2[i];
2499
2705
  bands.push([along, along + depth]);
2500
2706
  for (const node of row) {
2501
2707
  slots.set(node.id, { cross: crossAt.get(node.id), rank: rankAt.get(node.id) ?? along + (depth - extent(node.bounds, axes.rank)) / 2 });
@@ -2557,6 +2763,173 @@ var layered = Object.assign(
2557
2763
  { plan: layeredPlan }
2558
2764
  );
2559
2765
 
2766
+ // src/chainGroups.ts
2767
+ function depths(groups) {
2768
+ const byId = new Map(groups.map((g) => [g.id, g]));
2769
+ const depth = /* @__PURE__ */ new Map();
2770
+ const of = (g) => {
2771
+ if (!depth.has(g.id)) {
2772
+ const parent = g.parent === void 0 ? void 0 : byId.get(g.parent);
2773
+ depth.set(g.id, parent === void 0 ? 0 : of(parent) + 1);
2774
+ }
2775
+ return depth.get(g.id);
2776
+ };
2777
+ groups.forEach(of);
2778
+ return depth;
2779
+ }
2780
+ var deepestFirst = (groups) => {
2781
+ const depth = depths(groups);
2782
+ return [...groups].sort((p, q) => depth.get(q.id) - depth.get(p.id));
2783
+ };
2784
+ function groupHanging(graph, line, hanging) {
2785
+ const groups = graph.groups ?? [];
2786
+ if (groups.length === 0) return hanging;
2787
+ const index = new Map(line.map((n, i) => [n.id, i]));
2788
+ const byId = new Map(groups.map((g) => [g.id, g]));
2789
+ const onLine = new Map(groups.map((g) => [g.id, g.members.filter((m) => index.has(m))]));
2790
+ const owner = new Map(hanging.owner);
2791
+ for (const node of graph.nodes) {
2792
+ if (index.has(node.id)) continue;
2793
+ const first = index.get(hanging.owner.get(node.id));
2794
+ for (let g = node.group === void 0 ? void 0 : byId.get(node.group); g !== void 0; g = g.parent === void 0 ? void 0 : byId.get(g.parent)) {
2795
+ const members = onLine.get(g.id);
2796
+ if (members.length === 0) continue;
2797
+ owner.set(node.id, nearest(members, first, index));
2798
+ break;
2799
+ }
2800
+ }
2801
+ const ordered = deepestFirst(groups);
2802
+ for (const g of ordered) {
2803
+ if (onLine.get(g.id).length > 0) continue;
2804
+ const hung = g.members.filter((m) => owner.has(m));
2805
+ if (hung.length === 0) continue;
2806
+ const counts = /* @__PURE__ */ new Map();
2807
+ for (const m of hung) counts.set(owner.get(m), (counts.get(owner.get(m)) ?? 0) + 1);
2808
+ let best = owner.get(hung[0]);
2809
+ for (const [id, n] of counts) {
2810
+ const lead = counts.get(best);
2811
+ if (n > lead || n === lead && index.get(id) < index.get(best)) best = id;
2812
+ }
2813
+ for (const m of hung) owner.set(m, best);
2814
+ }
2815
+ return { owner, level: clearColumns(graph, ordered, index, owner, hanging.level) };
2816
+ }
2817
+ function nearest(members, from, index) {
2818
+ let best = members[0];
2819
+ for (const m of members) {
2820
+ const d = Math.abs(index.get(m) - from);
2821
+ const lead = Math.abs(index.get(best) - from);
2822
+ if (d < lead || d === lead && index.get(m) < index.get(best)) best = m;
2823
+ }
2824
+ return best;
2825
+ }
2826
+ function clearColumns(graph, ordered, index, owner, start) {
2827
+ const level = new Map(start);
2828
+ const columns = /* @__PURE__ */ new Map();
2829
+ for (const node of graph.nodes) {
2830
+ if (index.has(node.id)) continue;
2831
+ const id = owner.get(node.id);
2832
+ columns.set(id, [...columns.get(id) ?? [], node.id]);
2833
+ }
2834
+ for (const [lineId, hung] of columns) {
2835
+ const holding = ordered.filter((g) => g.members.some((m) => m === lineId || hung.includes(m)));
2836
+ const atLine = holding.filter((g) => g.members.includes(lineId));
2837
+ const kept = new Set(atLine.flatMap((g) => g.members));
2838
+ const clear2 = (g, fixed) => {
2839
+ const members = new Set(g.members);
2840
+ const own = hung.filter((id) => members.has(id)).map((id) => level.get(id));
2841
+ const low = members.has(lineId) ? 0 : Math.min(...own);
2842
+ const deep = Math.max(0, ...own);
2843
+ const movers = hung.filter((id) => !members.has(id) && !fixed.has(id) && level.get(id) >= low);
2844
+ if (!movers.some((id) => level.get(id) <= deep)) return;
2845
+ const steps = [...new Set(movers.map((id) => level.get(id)))].sort((p, q) => p - q);
2846
+ for (const id of movers) level.set(id, deep + 1 + steps.indexOf(level.get(id)));
2847
+ };
2848
+ for (const g of holding) if (!atLine.includes(g)) clear2(g, kept);
2849
+ for (const g of atLine) clear2(g, /* @__PURE__ */ new Set());
2850
+ }
2851
+ return level;
2852
+ }
2853
+ function lineGaps(graph, line, hanging, rankGap, axes, packed) {
2854
+ const index = new Map(line.map((n, i) => [n.id, i]));
2855
+ const columns = line.map((n) => [n]);
2856
+ for (const node of graph.nodes) {
2857
+ if (!index.has(node.id)) columns[index.get(hanging.owner.get(node.id))].push(node);
2858
+ }
2859
+ return bandGaps(graph, columns, axes, columns.slice(1).map(() => rankGap), (id) => packed.get(id)?.rank);
2860
+ }
2861
+ function levelGaps(graph, hanging, levelSize, axes, before, base) {
2862
+ const levels = Array.from(levelSize, () => []);
2863
+ for (const node of graph.nodes) (levels[hanging.level.get(node.id)] ??= []).push(node);
2864
+ const outward = { rank: axes.cross, cross: axes.rank, sign: before ? -1 : 1 };
2865
+ const from = [0];
2866
+ for (let k = 1; k < levelSize.length; k++) from[k] = from[k - 1] + (levelSize[k - 1] ?? 0);
2867
+ return bandGaps(graph, levels, outward, base, (id) => {
2868
+ const node = graph.node(id);
2869
+ const at2 = hanging.level.get(id);
2870
+ if (node === void 0 || at2 === void 0) return void 0;
2871
+ return at2 === 0 ? (levelSize[0] - extent(node.bounds, outward.rank)) / 2 : from[at2];
2872
+ });
2873
+ }
2874
+ function bandGaps(graph, bands, axes, gaps, start) {
2875
+ const insets = rankGaps(graph, bands, axes, 0);
2876
+ const out = gaps.map((gap, i) => gap + (insets[i] ?? 0));
2877
+ const groups = graph.groups ?? [];
2878
+ if (axes.rank !== "x" || !groups.some((g) => g.minWidth !== void 0)) return out;
2879
+ const bandOf = /* @__PURE__ */ new Map();
2880
+ bands.forEach((band, i) => band.forEach((n) => bandOf.set(n.id, i)));
2881
+ const children = /* @__PURE__ */ new Map();
2882
+ for (const g of groups) if (g.parent !== void 0) children.set(g.parent, [...children.get(g.parent) ?? [], g.id]);
2883
+ const byId = new Map(groups.map((g) => [g.id, g]));
2884
+ const span = (id, now) => {
2885
+ let [lo, hi] = [Infinity, -Infinity];
2886
+ for (const m of byId.get(id).members) {
2887
+ const at2 = start(m);
2888
+ const band = bandOf.get(m);
2889
+ const node = graph.node(m);
2890
+ if (at2 === void 0 || band === void 0 || node === void 0) continue;
2891
+ const from = at2 + now.slice(0, band).reduce((s, gap) => s + gap, 0);
2892
+ [lo, hi] = [Math.min(lo, from), Math.max(hi, from + extent(node.bounds, axes.rank))];
2893
+ }
2894
+ return { lo, hi };
2895
+ };
2896
+ widenForMinWidth(deepestFirst(groups).map((g) => {
2897
+ const placed = g.members.flatMap((id) => bandOf.has(id) && start(id) !== void 0 ? [bandOf.get(id)] : []);
2898
+ return {
2899
+ id: g.id,
2900
+ ...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {},
2901
+ inset: rankInset(g.inset, axes),
2902
+ children: children.get(g.id) ?? [],
2903
+ before: placed.length === 0 ? -1 : Math.min(...placed) - 1,
2904
+ after: placed.length === 0 ? -1 : Math.max(...placed)
2905
+ };
2906
+ }), out, span);
2907
+ return out;
2908
+ }
2909
+ function widenForMinWidth(groups, gaps, span) {
2910
+ const byId = new Map(groups.map((g) => [g.id, g]));
2911
+ const boxOf2 = (g, minWidth) => {
2912
+ let { lo, hi } = span(g.id, gaps);
2913
+ [lo, hi] = [lo - g.inset.lo, hi + g.inset.hi];
2914
+ for (const id of g.children) {
2915
+ const child = byId.get(id);
2916
+ if (child === void 0) continue;
2917
+ const box = boxOf2(child, child.minWidth ?? 0);
2918
+ [lo, hi] = [Math.min(lo, box.lo - g.inset.lo), Math.max(hi, box.hi + g.inset.hi)];
2919
+ }
2920
+ const grow = Math.max(0, minWidth - (hi - lo)) / 2;
2921
+ return { lo: lo - grow, hi: hi + grow };
2922
+ };
2923
+ for (const g of groups) {
2924
+ if (g.minWidth === void 0) continue;
2925
+ const box = boxOf2(g, 0);
2926
+ if (!(box.lo <= box.hi)) continue;
2927
+ const half = Math.max(0, g.minWidth - (box.hi - box.lo)) / 2;
2928
+ if (g.before >= 0 && g.before < gaps.length) gaps[g.before] += half;
2929
+ if (g.after >= 0 && g.after < gaps.length) gaps[g.after] += half;
2930
+ }
2931
+ }
2932
+
2560
2933
  // src/chainRoutes.ts
2561
2934
  var portOf3 = (node, id) => id === void 0 ? void 0 : node.ports?.find((p) => p.id === id);
2562
2935
  function coord(node, at2, id, axis) {
@@ -2712,7 +3085,7 @@ function chainPlan(graph, opts = {}) {
2712
3085
  const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
2713
3086
  const line = lineOf(graph, opts.chain);
2714
3087
  if (line.length === 0) return { moves: /* @__PURE__ */ new Map() };
2715
- const hanging = hangFrom(graph, line);
3088
+ const hanging = groupHanging(graph, line, hangFrom(graph, line));
2716
3089
  const { owner, level } = hanging;
2717
3090
  const rows = new Map(line.map((n) => [n.id, []]));
2718
3091
  const levelSize = [Math.max(...line.map((n) => extent(n.bounds, axes.cross)))];
@@ -2725,24 +3098,31 @@ function chainPlan(graph, opts = {}) {
2725
3098
  }
2726
3099
  for (const [id, column] of rows) if (column[1]) column[1] = inPortOrder(graph, id, column[1], axes);
2727
3100
  const before = opts.chainSide === "before";
2728
- const place = (side2) => settle(graph, slotsFor(line, rows, levelSize, axes, { node: nodeGap, rank: rankGap, side: side2 }, before), opts);
3101
+ const grouped = (graph.groups?.length ?? 0) > 0;
3102
+ const across = (side2) => {
3103
+ const base = Array.from(levelSize.slice(1), (_, k) => k === 0 ? side2 : nodeGap);
3104
+ return grouped ? levelGaps(graph, hanging, levelSize, axes, before, base) : base;
3105
+ };
3106
+ const slots = (rank, level2) => slotsFor(line, rows, levelSize, axes, { node: nodeGap, rank, level: level2 }, before);
3107
+ const gaps = grouped ? lineGaps(graph, line, hanging, rankGap, axes, slots(line.map(() => 0), [])) : line.map(() => rankGap);
3108
+ const place = (side2) => settle(graph, slots(gaps, across(side2)), opts);
2729
3109
  let moves = place(nodeGap);
2730
3110
  const side = (tracksNeeded(graph, hanging, moves, axes, before) + 1) * SIDE_TRACK_GAP;
2731
3111
  if (side > nodeGap) moves = place(side);
2732
3112
  return { moves, waypoints: sideRoutes(graph, hanging, moves, axes, before) };
2733
3113
  }
2734
3114
  function slotsFor(line, rows, levelSize, axes, gaps, before) {
2735
- const { node: nodeGap, rank: rankGap } = gaps;
3115
+ const { node: nodeGap, rank: rankGap, level: levelGap } = gaps;
2736
3116
  const rowSpan = (row) => row.reduce((sum, n) => sum + extent(n.bounds, axes.rank), 0) + nodeGap * Math.max(row.length - 1, 0);
2737
3117
  const bandStart = (at2) => {
2738
3118
  let offset = 0;
2739
- const first = before ? 1 : 0;
2740
- for (let k = first; k < (before ? at2 + 1 : at2); k++) offset += (levelSize[k] ?? 0) + (k === first ? gaps.side : nodeGap);
3119
+ if (before) for (let k = 1; k <= at2; k++) offset += (levelSize[k] ?? 0) + (levelGap[k - 1] ?? 0);
3120
+ else for (let k = 0; k < at2; k++) offset += (levelSize[k] ?? 0) + (levelGap[k] ?? 0);
2741
3121
  return before ? -offset : offset;
2742
3122
  };
2743
3123
  const slots = /* @__PURE__ */ new Map();
2744
3124
  let cursor = 0;
2745
- for (const node of line) {
3125
+ line.forEach((node, i) => {
2746
3126
  const column = rows.get(node.id);
2747
3127
  const span = Math.max(extent(node.bounds, axes.rank), ...Array.from(column, (row) => row ? rowSpan(row) : 0));
2748
3128
  slots.set(node.id, {
@@ -2758,8 +3138,8 @@ function slotsFor(line, rows, levelSize, axes, gaps, before) {
2758
3138
  along += extent(hung.bounds, axes.rank) + nodeGap;
2759
3139
  }
2760
3140
  });
2761
- cursor += span + rankGap;
2762
- }
3141
+ cursor += span + (rankGap[i] ?? 0);
3142
+ });
2763
3143
  return slots;
2764
3144
  }
2765
3145
 
@@ -2807,13 +3187,13 @@ var treeOnce = (graph, opts = {}) => {
2807
3187
  const at2 = depth.get(node.id) ?? 0;
2808
3188
  bands.set(at2, Math.max(bands.get(at2) ?? 0, extent(node.bounds, axes.rank)));
2809
3189
  }
2810
- const depths = [...bands.keys()].sort((a, b) => a - b);
2811
- const rowsAt = depths.map((at2) => graph.nodes.filter((n) => (depth.get(n.id) ?? 0) === at2));
3190
+ const depths2 = [...bands.keys()].sort((a, b) => a - b);
3191
+ const rowsAt = depths2.map((at2) => graph.nodes.filter((n) => (depth.get(n.id) ?? 0) === at2));
2812
3192
  const grouped = (graph.groups?.length ?? 0) > 0;
2813
- const gaps = grouped ? rankGaps(graph, rowsAt, axes, rankGap) : depths.slice(1).map(() => rankGap);
3193
+ const gaps = grouped ? rankGaps(graph, rowsAt, axes, rankGap) : depths2.slice(1).map(() => rankGap);
2814
3194
  const bandStart = /* @__PURE__ */ new Map();
2815
3195
  let along = 0;
2816
- for (const [i, at2] of depths.entries()) {
3196
+ for (const [i, at2] of depths2.entries()) {
2817
3197
  bandStart.set(at2, along);
2818
3198
  along += bands.get(at2) + (gaps[i] ?? 0);
2819
3199
  }
@@ -2860,6 +3240,6 @@ var treeOnce = (graph, opts = {}) => {
2860
3240
  return settle(graph, slots, opts);
2861
3241
  };
2862
3242
 
2863
- export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, PORT_LABEL_GAP, SIDE_TRACK_GAP, anchorOffset, authoredOver, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, center, chain, contentBox, createDiagramNodes, dataKeyReader, diagramNodeOf, expandGroupEdges, extent, footprintNode, force, forceRelaxation, forestOf, graphOrder, grownBy, insetOf, isPinned, isPromise, layered, layeredPlan, layeredRoutes, layoutBody, layoutRowPorts, marginOf, measureBody, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, planOf, plannerPlan, portLabelBox, portLabelsOf, portOf, portSitesOf, portsOf, portsOnOutline, rankGaps, ranksByLength, ranksOf, rayHit, seededOrder, settle, sizeToBody, translated, tree, untilSettled };
2864
- //# sourceMappingURL=chunk-NUMMW5BI.js.map
2865
- //# sourceMappingURL=chunk-NUMMW5BI.js.map
3243
+ export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, PORT_LABEL_GAP, SIDE_TRACK_GAP, anchorOffset, authoredOver, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, center, chain, contentBox, createDiagramNodes, dataKeyReader, diagramEdgeOf, diagramNodeOf, expandGroupEdges, extent, footprintNode, force, forceRelaxation, forestOf, graphOrder, grownBy, insetOf, isPinned, isPromise, labelCenter, labelSpotAt, layered, layeredPlan, layeredRoutes, layoutBody, layoutOwnsPort, layoutOwnsRoute, layoutRowPorts, layoutWritesRoute, marginOf, measureBody, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, planOf, plannerPlan, portLabelBox, portLabelsOf, portOf, portSitesOf, portsOf, portsOnOutline, rankGaps, ranksByLength, ranksOf, rayHit, routeHolds, samePoints, seededOrder, settle, sizeToBody, subpathOf, translated, tree, untilSettled, widenForMinWidth };
3244
+ //# sourceMappingURL=chunk-R2XB6D32.js.map
3245
+ //# sourceMappingURL=chunk-R2XB6D32.js.map