@crazyhappyone/auto-graph 0.0.3 → 0.0.4

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.
@@ -433,15 +433,30 @@ function renderSwimlane(swimlane) {
433
433
  lines.push(
434
434
  ` <rect class="swimlane-lane" data-lane="${escapeAttribute(`${swimlane.id}.${lane.id}`)}" x="${formatNumber(lane.box.x)}" y="${formatNumber(lane.box.y)}" width="${formatNumber(lane.box.width)}" height="${formatNumber(lane.box.height)}" fill="none" stroke="${STROKE}"/>`
435
435
  );
436
- if (lane.label?.text !== void 0) {
436
+ if (lane.headerBox !== void 0) {
437
437
  lines.push(
438
- ` <text class="swimlane-label" x="${formatNumber(lane.box.x + lane.box.width / 2)}" y="${formatNumber(lane.box.y + 16)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(lane.label.text)}</text>`
438
+ ` <rect class="swimlane-header" data-lane-header="${escapeAttribute(`${swimlane.id}.${lane.id}`)}" x="${formatNumber(lane.headerBox.x)}" y="${formatNumber(lane.headerBox.y)}" width="${formatNumber(lane.headerBox.width)}" height="${formatNumber(lane.headerBox.height)}" fill="#f3f4f6" stroke="${STROKE}"/>`
439
439
  );
440
440
  }
441
+ if (lane.contentBox !== void 0) {
442
+ lines.push(
443
+ ` <rect class="swimlane-content" data-lane-content="${escapeAttribute(`${swimlane.id}.${lane.id}`)}" x="${formatNumber(lane.contentBox.x)}" y="${formatNumber(lane.contentBox.y)}" width="${formatNumber(lane.contentBox.width)}" height="${formatNumber(lane.contentBox.height)}" fill="none" stroke="none"/>`
444
+ );
445
+ }
446
+ if (lane.label?.text !== void 0) {
447
+ const labelBox = lane.headerBox ?? lane.box;
448
+ lines.push(renderSwimlaneLabel(swimlane, lane.label.text, labelBox));
449
+ }
441
450
  }
442
451
  lines.push(" </g>");
443
452
  return lines;
444
453
  }
454
+ function renderSwimlaneLabel(swimlane, text, labelBox) {
455
+ const x = labelBox.x + labelBox.width / 2;
456
+ const y = labelBox.y + labelBox.height / 2;
457
+ const transform = swimlane.orientation === "horizontal" ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
458
+ return ` <text class="swimlane-label" x="${formatNumber(x)}" y="${formatNumber(y)}" text-anchor="middle" dominant-baseline="middle"${transform} font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(text)}</text>`;
459
+ }
445
460
  function renderPorts(node) {
446
461
  return (node.ports ?? []).flatMap((port) => [
447
462
  ` <rect class="port" data-kind="${escapeAttribute(port.kind)}" data-port="${escapeAttribute(`${node.id}.${port.id}`)}" x="${formatNumber(port.box.x)}" y="${formatNumber(port.box.y)}" width="${formatNumber(port.box.width)}" height="${formatNumber(port.box.height)}" fill="${escapeAttribute(port.style?.fill ?? "#d9ead3")}" stroke="${escapeAttribute(port.style?.stroke ?? STROKE)}"/>`,
@@ -1717,6 +1732,19 @@ function solveDiagram(diagram, options = {}) {
1717
1732
  constraints
1718
1733
  });
1719
1734
  diagnostics.push(...constrained.diagnostics);
1735
+ const swimlaneContracts = applySwimlaneLayoutContracts(
1736
+ diagram.swimlanes ?? [],
1737
+ constraints,
1738
+ edges,
1739
+ isTopToBottomReadingDirection(diagram.metadata?.primaryReadingDirection),
1740
+ constrained.boxes,
1741
+ constrained.locks,
1742
+ options?.overlapSpacing ?? 40
1743
+ );
1744
+ if (swimlaneContracts.layouts.size > 0) {
1745
+ removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
1746
+ }
1747
+ diagnostics.push(...swimlaneContracts.diagnostics);
1720
1748
  const coordinatedNodes = coordinateNodes(
1721
1749
  nodes,
1722
1750
  constrained.boxes,
@@ -1741,7 +1769,8 @@ function solveDiagram(diagram, options = {}) {
1741
1769
  );
1742
1770
  const coordinatedSwimlanes = coordinateSwimlanes(
1743
1771
  diagram.swimlanes ?? [],
1744
- constrained.boxes
1772
+ constrained.boxes,
1773
+ swimlaneContracts.layouts
1745
1774
  );
1746
1775
  const groupBoxes = new Map(
1747
1776
  coordinatedGroups.map((group) => [group.id, group.box])
@@ -1783,6 +1812,591 @@ function solveDiagram(diagram, options = {}) {
1783
1812
  ...diagram.metadata === void 0 ? {} : { metadata: diagram.metadata }
1784
1813
  };
1785
1814
  }
1815
+ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing) {
1816
+ const layouts = /* @__PURE__ */ new Map();
1817
+ const diagnostics = [];
1818
+ const movedChildIds = /* @__PURE__ */ new Set();
1819
+ for (const swimlane of swimlanes) {
1820
+ if ((swimlane.layout ?? "overlay") !== "contract") {
1821
+ continue;
1822
+ }
1823
+ if (swimlane.lanes.length === 0) {
1824
+ continue;
1825
+ }
1826
+ const layout2 = applySingleSwimlaneContract(
1827
+ swimlane,
1828
+ edges,
1829
+ topToBottomFlow,
1830
+ nodeBoxes,
1831
+ locks,
1832
+ diagnostics,
1833
+ movedChildIds
1834
+ );
1835
+ if (layout2 !== void 0) {
1836
+ layouts.set(swimlane.id, layout2);
1837
+ }
1838
+ }
1839
+ if (layouts.size > 0) {
1840
+ diagnostics.push(
1841
+ ...reportSwimlaneOverlaps(nodeBoxes, locks, overlapSpacing),
1842
+ ...reportSwimlaneConstraintInvalidations(
1843
+ constraints,
1844
+ nodeBoxes,
1845
+ movedChildIds
1846
+ )
1847
+ );
1848
+ }
1849
+ return { layouts, diagnostics, movedChildIds };
1850
+ }
1851
+ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds) {
1852
+ const headerHeight = swimlane.headerHeight ?? 28;
1853
+ const padding = swimlane.padding ?? 16;
1854
+ const laneBounds = swimlane.lanes.map((lane) => {
1855
+ const childBoxes = lane.children.map((child) => nodeBoxes.get(child)).filter((box) => box !== void 0);
1856
+ return childBoxes.length === 0 ? void 0 : unionBoxes(childBoxes);
1857
+ });
1858
+ const populatedBounds = laneBounds.filter(
1859
+ (box) => box !== void 0
1860
+ );
1861
+ if (populatedBounds.length === 0) {
1862
+ return void 0;
1863
+ }
1864
+ if (swimlane.orientation === "vertical") {
1865
+ return applyVerticalSwimlaneContract(
1866
+ swimlane,
1867
+ edges,
1868
+ topToBottomFlow,
1869
+ nodeBoxes,
1870
+ laneBounds,
1871
+ headerHeight,
1872
+ padding,
1873
+ locks,
1874
+ diagnostics,
1875
+ movedChildIds
1876
+ );
1877
+ }
1878
+ return applyHorizontalSwimlaneContract(
1879
+ swimlane,
1880
+ nodeBoxes,
1881
+ laneBounds,
1882
+ headerHeight,
1883
+ padding,
1884
+ locks,
1885
+ diagnostics,
1886
+ movedChildIds
1887
+ );
1888
+ }
1889
+ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds) {
1890
+ const populatedBounds = laneBounds.filter(
1891
+ (box) => box !== void 0
1892
+ );
1893
+ const top = Math.min(...populatedBounds.map((box) => box.y));
1894
+ const left = Math.min(...populatedBounds.map((box) => box.x));
1895
+ const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
1896
+ const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
1897
+ const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
1898
+ const rankStackGap = Math.max(8, padding / 2);
1899
+ const maxRankStackHeight = maxVerticalRankStackHeight(
1900
+ swimlane,
1901
+ nodeBoxes,
1902
+ flowRanks,
1903
+ rankStackGap
1904
+ );
1905
+ const rankSpacing = Math.max(96, maxRankStackHeight + padding);
1906
+ const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
1907
+ const slotWidth = Math.max(...populatedBounds.map((box) => box.width)) + padding * 2;
1908
+ const laneContentTop = top + headerHeight + padding;
1909
+ for (let index = 0; index < swimlane.lanes.length; index += 1) {
1910
+ const lane = swimlane.lanes[index];
1911
+ const bounds = laneBounds[index];
1912
+ if (lane === void 0 || bounds === void 0) {
1913
+ continue;
1914
+ }
1915
+ const target = {
1916
+ x: left + slotWidth * index + padding,
1917
+ y: laneContentTop
1918
+ };
1919
+ if (maxRank === 0) {
1920
+ moveLaneChildren(
1921
+ lane.children,
1922
+ nodeBoxes,
1923
+ locks,
1924
+ diagnostics,
1925
+ movedChildIds,
1926
+ {
1927
+ x: target.x - bounds.x,
1928
+ y: target.y - bounds.y
1929
+ }
1930
+ );
1931
+ continue;
1932
+ }
1933
+ moveRankedVerticalLaneChildren(
1934
+ lane.children,
1935
+ nodeBoxes,
1936
+ locks,
1937
+ diagnostics,
1938
+ movedChildIds,
1939
+ flowRanks,
1940
+ rankSpacing,
1941
+ rankStackGap,
1942
+ {
1943
+ x: target.x - bounds.x,
1944
+ y: laneContentTop
1945
+ }
1946
+ );
1947
+ }
1948
+ return {
1949
+ box: {
1950
+ x: left,
1951
+ y: top,
1952
+ width: slotWidth * swimlane.lanes.length,
1953
+ height: contentHeight + padding * 2 + headerHeight
1954
+ },
1955
+ slotWidth,
1956
+ slotHeight: contentHeight + padding * 2 + headerHeight
1957
+ };
1958
+ }
1959
+ function isTopToBottomReadingDirection(value) {
1960
+ return value === "top_to_bottom" || value === "top-to-bottom";
1961
+ }
1962
+ function rankVerticalSwimlaneChildren(swimlane, edges) {
1963
+ const childOrder = /* @__PURE__ */ new Map();
1964
+ for (const lane of swimlane.lanes) {
1965
+ for (const childId of lane.children) {
1966
+ if (!childOrder.has(childId)) {
1967
+ childOrder.set(childId, childOrder.size);
1968
+ }
1969
+ }
1970
+ }
1971
+ if (childOrder.size === 0) {
1972
+ return /* @__PURE__ */ new Map();
1973
+ }
1974
+ const childIds = new Set(childOrder.keys());
1975
+ const relevantEdges = edges.filter(
1976
+ (edge) => childIds.has(edge.source.nodeId) && childIds.has(edge.target.nodeId) && edge.source.nodeId !== edge.target.nodeId
1977
+ );
1978
+ if (relevantEdges.length === 0) {
1979
+ return /* @__PURE__ */ new Map();
1980
+ }
1981
+ const ranks = new Map([...childIds].map((id) => [id, 0]));
1982
+ const outgoing = /* @__PURE__ */ new Map();
1983
+ const inDegree = new Map([...childIds].map((id) => [id, 0]));
1984
+ for (const edge of relevantEdges) {
1985
+ const targets = outgoing.get(edge.source.nodeId) ?? [];
1986
+ targets.push(edge.target.nodeId);
1987
+ outgoing.set(edge.source.nodeId, targets);
1988
+ inDegree.set(
1989
+ edge.target.nodeId,
1990
+ (inDegree.get(edge.target.nodeId) ?? 0) + 1
1991
+ );
1992
+ }
1993
+ const queue = [...childIds].filter((id) => (inDegree.get(id) ?? 0) === 0).sort((a, b) => (childOrder.get(a) ?? 0) - (childOrder.get(b) ?? 0));
1994
+ let visited = 0;
1995
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
1996
+ const sourceId = queue[cursor];
1997
+ if (sourceId === void 0) {
1998
+ continue;
1999
+ }
2000
+ visited += 1;
2001
+ for (const targetId of outgoing.get(sourceId) ?? []) {
2002
+ ranks.set(
2003
+ targetId,
2004
+ Math.max(ranks.get(targetId) ?? 0, (ranks.get(sourceId) ?? 0) + 1)
2005
+ );
2006
+ const nextInDegree = (inDegree.get(targetId) ?? 0) - 1;
2007
+ inDegree.set(targetId, nextInDegree);
2008
+ if (nextInDegree === 0) {
2009
+ queue.push(targetId);
2010
+ }
2011
+ }
2012
+ }
2013
+ return visited === childIds.size ? ranks : rankCyclicSwimlaneChildren(childIds, relevantEdges);
2014
+ }
2015
+ function rankCyclicSwimlaneChildren(childIds, edges) {
2016
+ const maxRank = Math.max(0, childIds.size - 1);
2017
+ const ranks = new Map([...childIds].map((id) => [id, 0]));
2018
+ for (let iteration = 0; iteration < childIds.size; iteration += 1) {
2019
+ let changed = false;
2020
+ for (const edge of edges) {
2021
+ const nextRank = Math.min(
2022
+ maxRank,
2023
+ (ranks.get(edge.source.nodeId) ?? 0) + 1
2024
+ );
2025
+ if (nextRank > (ranks.get(edge.target.nodeId) ?? 0)) {
2026
+ ranks.set(edge.target.nodeId, nextRank);
2027
+ changed = true;
2028
+ }
2029
+ }
2030
+ if (!changed) {
2031
+ break;
2032
+ }
2033
+ }
2034
+ return ranks;
2035
+ }
2036
+ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
2037
+ let maxHeight = 0;
2038
+ for (const lane of swimlane.lanes) {
2039
+ for (const stack of rankStacks(
2040
+ lane.children,
2041
+ nodeBoxes,
2042
+ flowRanks
2043
+ ).values()) {
2044
+ const height = stack.reduce(
2045
+ (total, item, index) => total + item.box.height + (index === 0 ? 0 : gap),
2046
+ 0
2047
+ );
2048
+ maxHeight = Math.max(maxHeight, height);
2049
+ }
2050
+ }
2051
+ return maxHeight;
2052
+ }
2053
+ function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target) {
2054
+ for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
2055
+ let yOffset = 0;
2056
+ for (const item of stack) {
2057
+ const { childId, box } = item;
2058
+ if (locks.has(childId)) {
2059
+ diagnostics.push({
2060
+ severity: "warning",
2061
+ code: "constraints.locked-target-not-moved",
2062
+ message: `Locked child ${childId} was not moved into contract swimlane slot.`,
2063
+ path: ["swimlanes"],
2064
+ detail: { nodeId: childId }
2065
+ });
2066
+ continue;
2067
+ }
2068
+ const next = {
2069
+ ...box,
2070
+ x: box.x + target.x,
2071
+ y: target.y + rank * rankSpacing + yOffset
2072
+ };
2073
+ if (next.x !== box.x || next.y !== box.y) {
2074
+ movedChildIds.add(childId);
2075
+ }
2076
+ nodeBoxes.set(childId, next);
2077
+ yOffset += box.height + rankStackGap;
2078
+ }
2079
+ }
2080
+ }
2081
+ function rankStacks(childIds, nodeBoxes, flowRanks) {
2082
+ const stacks = /* @__PURE__ */ new Map();
2083
+ for (const childId of childIds) {
2084
+ const box = nodeBoxes.get(childId);
2085
+ if (box === void 0) {
2086
+ continue;
2087
+ }
2088
+ const rank = flowRanks.get(childId) ?? 0;
2089
+ const stack = stacks.get(rank) ?? [];
2090
+ stack.push({ childId, box });
2091
+ stacks.set(rank, stack);
2092
+ }
2093
+ for (const stack of stacks.values()) {
2094
+ stack.sort((a, b) => {
2095
+ const deltaY = a.box.y - b.box.y;
2096
+ return deltaY === 0 ? a.childId.localeCompare(b.childId) : deltaY;
2097
+ });
2098
+ }
2099
+ return stacks;
2100
+ }
2101
+ function applyHorizontalSwimlaneContract(swimlane, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds) {
2102
+ const populatedBounds = laneBounds.filter(
2103
+ (box) => box !== void 0
2104
+ );
2105
+ const top = Math.min(...populatedBounds.map((box) => box.y));
2106
+ const left = Math.min(...populatedBounds.map((box) => box.x));
2107
+ const slotWidth = Math.max(...populatedBounds.map((box) => box.width)) + headerHeight + padding * 2;
2108
+ const slotHeight = Math.max(...populatedBounds.map((box) => box.height)) + padding * 2;
2109
+ for (let index = 0; index < swimlane.lanes.length; index += 1) {
2110
+ const lane = swimlane.lanes[index];
2111
+ const bounds = laneBounds[index];
2112
+ if (lane === void 0 || bounds === void 0) {
2113
+ continue;
2114
+ }
2115
+ const target = {
2116
+ x: left + headerHeight + padding,
2117
+ y: top + slotHeight * index + padding
2118
+ };
2119
+ moveLaneChildren(
2120
+ lane.children,
2121
+ nodeBoxes,
2122
+ locks,
2123
+ diagnostics,
2124
+ movedChildIds,
2125
+ {
2126
+ x: target.x - bounds.x,
2127
+ y: target.y - bounds.y
2128
+ }
2129
+ );
2130
+ }
2131
+ return {
2132
+ box: {
2133
+ x: left,
2134
+ y: top,
2135
+ width: slotWidth,
2136
+ height: slotHeight * swimlane.lanes.length
2137
+ },
2138
+ slotWidth,
2139
+ slotHeight
2140
+ };
2141
+ }
2142
+ function moveLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, offset) {
2143
+ for (const childId of childIds) {
2144
+ const box = nodeBoxes.get(childId);
2145
+ if (box === void 0) {
2146
+ continue;
2147
+ }
2148
+ if (locks.has(childId)) {
2149
+ diagnostics.push({
2150
+ severity: "warning",
2151
+ code: "constraints.locked-target-not-moved",
2152
+ message: `Locked child ${childId} was not moved into contract swimlane slot.`,
2153
+ path: ["swimlanes"],
2154
+ detail: { nodeId: childId }
2155
+ });
2156
+ continue;
2157
+ }
2158
+ if (offset.x !== 0 || offset.y !== 0) {
2159
+ movedChildIds.add(childId);
2160
+ }
2161
+ nodeBoxes.set(childId, {
2162
+ ...box,
2163
+ x: box.x + offset.x,
2164
+ y: box.y + offset.y
2165
+ });
2166
+ }
2167
+ }
2168
+ function removeResolvedOverlapDiagnostics(diagnostics, nodeBoxes) {
2169
+ for (let index = diagnostics.length - 1; index >= 0; index -= 1) {
2170
+ const diagnostic = diagnostics[index];
2171
+ if (diagnostic?.code !== "constraints.overlap.unresolved") {
2172
+ continue;
2173
+ }
2174
+ const firstId = detailString(diagnostic, "firstId");
2175
+ const secondId = detailString(diagnostic, "secondId");
2176
+ const first = firstId === void 0 ? void 0 : nodeBoxes.get(firstId);
2177
+ const second = secondId === void 0 ? void 0 : nodeBoxes.get(secondId);
2178
+ if (first !== void 0 && second !== void 0 && !intersectsAabb(first, second)) {
2179
+ diagnostics.splice(index, 1);
2180
+ }
2181
+ }
2182
+ }
2183
+ function reportSwimlaneConstraintInvalidations(constraints, nodeBoxes, movedChildIds) {
2184
+ const diagnostics = [];
2185
+ for (const constraint of constraints) {
2186
+ const invalidatedNodeIds = movedConstraintNodeIds(
2187
+ constraint,
2188
+ nodeBoxes,
2189
+ movedChildIds
2190
+ );
2191
+ if (invalidatedNodeIds.length === 0) {
2192
+ continue;
2193
+ }
2194
+ diagnostics.push({
2195
+ severity: "warning",
2196
+ code: "constraints.swimlane-contract.invalidated",
2197
+ message: `Contract swimlane placement moved node(s) after ${constraint.kind} constraint solving; final geometry no longer satisfies that constraint.`,
2198
+ path: ["swimlanes"],
2199
+ detail: {
2200
+ constraintKind: constraint.kind,
2201
+ ...constraint.id === void 0 ? {} : { constraintId: constraint.id },
2202
+ nodeIds: invalidatedNodeIds
2203
+ }
2204
+ });
2205
+ }
2206
+ return diagnostics;
2207
+ }
2208
+ function movedConstraintNodeIds(constraint, nodeBoxes, movedChildIds) {
2209
+ switch (constraint.kind) {
2210
+ case "exact-position":
2211
+ return [];
2212
+ case "containment":
2213
+ return movedContainmentViolations(constraint, nodeBoxes, movedChildIds);
2214
+ case "relative-position":
2215
+ return movedRelativeViolations(constraint, nodeBoxes, movedChildIds);
2216
+ case "align":
2217
+ return movedAlignViolations(constraint, nodeBoxes, movedChildIds);
2218
+ case "distribute":
2219
+ return movedDistributeViolations(constraint, nodeBoxes, movedChildIds);
2220
+ }
2221
+ }
2222
+ function movedContainmentViolations(constraint, nodeBoxes, movedChildIds) {
2223
+ const container = nodeBoxes.get(constraint.containerId);
2224
+ if (container === void 0) {
2225
+ return [];
2226
+ }
2227
+ const content = paddedContentBox(container, constraint.padding);
2228
+ return constraint.childIds.filter((childId) => {
2229
+ if (!movedChildIds.has(childId)) {
2230
+ return false;
2231
+ }
2232
+ const child = nodeBoxes.get(childId);
2233
+ return child !== void 0 && !boxInside(child, content);
2234
+ });
2235
+ }
2236
+ function movedRelativeViolations(constraint, nodeBoxes, movedChildIds) {
2237
+ if (!movedChildIds.has(constraint.sourceId) && !movedChildIds.has(constraint.referenceId)) {
2238
+ return [];
2239
+ }
2240
+ const source = nodeBoxes.get(constraint.sourceId);
2241
+ const reference = nodeBoxes.get(constraint.referenceId);
2242
+ if (source === void 0 || reference === void 0) {
2243
+ return [];
2244
+ }
2245
+ return sameBoxPosition(
2246
+ source,
2247
+ expectedRelativeBox(source, reference, constraint)
2248
+ ) ? [] : [constraint.sourceId];
2249
+ }
2250
+ function movedAlignViolations(constraint, nodeBoxes, movedChildIds) {
2251
+ if (!constraint.targetIds.some((id) => movedChildIds.has(id))) {
2252
+ return [];
2253
+ }
2254
+ const targets = constraint.targetIds.map((id) => ({ id, box: nodeBoxes.get(id) })).filter(
2255
+ (target) => target.box !== void 0
2256
+ );
2257
+ const anchor = targets[0];
2258
+ if (anchor === void 0) {
2259
+ return [];
2260
+ }
2261
+ const expected = alignmentValue2(anchor.box, constraint.axis);
2262
+ return targets.filter(
2263
+ (target) => movedChildIds.has(target.id) && !sameNumber(alignmentValue2(target.box, constraint.axis), expected)
2264
+ ).map((target) => target.id);
2265
+ }
2266
+ function movedDistributeViolations(constraint, nodeBoxes, movedChildIds) {
2267
+ if (!constraint.targetIds.some((id) => movedChildIds.has(id))) {
2268
+ return [];
2269
+ }
2270
+ const targets = constraint.targetIds.map((id) => ({ id, box: nodeBoxes.get(id) })).filter(
2271
+ (target) => target.box !== void 0
2272
+ ).sort((a, b) => {
2273
+ const delta = constraint.axis === "horizontal" ? a.box.x - b.box.x : a.box.y - b.box.y;
2274
+ return delta === 0 ? a.id.localeCompare(b.id) : delta;
2275
+ });
2276
+ if (targets.length < 3) {
2277
+ return [];
2278
+ }
2279
+ const first = targets[0];
2280
+ const last = targets.at(-1);
2281
+ if (first === void 0 || last === void 0) {
2282
+ return [];
2283
+ }
2284
+ const expectedSpacing = constraint.spacing ?? (distributionStart2(last.box, constraint.axis) - distributionStart2(first.box, constraint.axis)) / (targets.length - 1);
2285
+ return targets.slice(1).filter((target, index) => {
2286
+ const previous = targets[index];
2287
+ if (previous === void 0 || !movedChildIds.has(target.id)) {
2288
+ return false;
2289
+ }
2290
+ return !sameNumber(
2291
+ distributionStart2(target.box, constraint.axis) - distributionStart2(previous.box, constraint.axis),
2292
+ expectedSpacing
2293
+ );
2294
+ }).map((target) => target.id);
2295
+ }
2296
+ function expectedRelativeBox(source, reference, constraint) {
2297
+ const offset = constraint.offset ?? { x: 0, y: 0 };
2298
+ switch (constraint.relation) {
2299
+ case "above":
2300
+ return {
2301
+ ...source,
2302
+ x: reference.x + offset.x,
2303
+ y: reference.y - source.height + offset.y
2304
+ };
2305
+ case "right-of":
2306
+ return {
2307
+ ...source,
2308
+ x: reference.x + reference.width + offset.x,
2309
+ y: reference.y + offset.y
2310
+ };
2311
+ case "below":
2312
+ return {
2313
+ ...source,
2314
+ x: reference.x + offset.x,
2315
+ y: reference.y + reference.height + offset.y
2316
+ };
2317
+ case "left-of":
2318
+ return {
2319
+ ...source,
2320
+ x: reference.x - source.width + offset.x,
2321
+ y: reference.y + offset.y
2322
+ };
2323
+ }
2324
+ }
2325
+ function paddedContentBox(container, padding) {
2326
+ const margin = padding ?? { top: 0, right: 0, bottom: 0, left: 0 };
2327
+ return {
2328
+ x: container.x + margin.left,
2329
+ y: container.y + margin.top,
2330
+ width: container.width - margin.left - margin.right,
2331
+ height: container.height - margin.top - margin.bottom
2332
+ };
2333
+ }
2334
+ function boxInside(child, container) {
2335
+ return child.x >= container.x && child.y >= container.y && child.x + child.width <= container.x + container.width && child.y + child.height <= container.y + container.height;
2336
+ }
2337
+ function sameBoxPosition(first, second) {
2338
+ return sameNumber(first.x, second.x) && sameNumber(first.y, second.y);
2339
+ }
2340
+ function sameNumber(first, second) {
2341
+ return Math.abs(first - second) < 1e-3;
2342
+ }
2343
+ function alignmentValue2(box, axis) {
2344
+ switch (axis) {
2345
+ case "x":
2346
+ case "left":
2347
+ return box.x;
2348
+ case "y":
2349
+ case "top":
2350
+ return box.y;
2351
+ case "center-x":
2352
+ return box.x + box.width / 2;
2353
+ case "center-y":
2354
+ return box.y + box.height / 2;
2355
+ case "right":
2356
+ return box.x + box.width;
2357
+ case "bottom":
2358
+ return box.y + box.height;
2359
+ }
2360
+ }
2361
+ function distributionStart2(box, axis) {
2362
+ return axis === "horizontal" ? box.x : box.y;
2363
+ }
2364
+ function detailString(diagnostic, key) {
2365
+ const value = diagnostic.detail?.[key];
2366
+ return typeof value === "string" ? value : void 0;
2367
+ }
2368
+ function reportSwimlaneOverlaps(nodeBoxes, locks, overlapSpacing) {
2369
+ const diagnostics = [];
2370
+ const ids = [...nodeBoxes.keys()].sort();
2371
+ for (const firstId of ids) {
2372
+ for (const secondId of ids) {
2373
+ if (firstId >= secondId) {
2374
+ continue;
2375
+ }
2376
+ const first = nodeBoxes.get(firstId);
2377
+ const second = nodeBoxes.get(secondId);
2378
+ if (first === void 0 || second === void 0) {
2379
+ continue;
2380
+ }
2381
+ if (!intersectsAabb(first, second)) {
2382
+ continue;
2383
+ }
2384
+ diagnostics.push({
2385
+ severity: "warning",
2386
+ code: "constraints.overlap.unresolved",
2387
+ message: `Boxes ${firstId} and ${secondId} still overlap after contract swimlane placement with configured spacing ${overlapSpacing}.`,
2388
+ path: ["swimlanes"],
2389
+ detail: {
2390
+ firstId,
2391
+ secondId,
2392
+ firstLocked: locks.has(firstId),
2393
+ secondLocked: locks.has(secondId)
2394
+ }
2395
+ });
2396
+ }
2397
+ }
2398
+ return diagnostics;
2399
+ }
1786
2400
  function coordinateNodes(nodes, boxes, options, diagnostics) {
1787
2401
  const coordinated = [];
1788
2402
  for (const node of nodes) {
@@ -1897,16 +2511,70 @@ function portLabelBox(port) {
1897
2511
  height
1898
2512
  };
1899
2513
  }
1900
- function coordinateSwimlanes(swimlanes, nodeBoxes) {
1901
- const titleSize = 28;
1902
- const padding = 16;
2514
+ function coordinateSwimlanes(swimlanes, nodeBoxes, layouts) {
1903
2515
  return swimlanes.map((swimlane) => {
1904
- const laneBoxes = swimlane.lanes.flatMap((lane) => {
2516
+ const layout2 = swimlane.layout ?? "overlay";
2517
+ const headerHeight = swimlane.headerHeight ?? 28;
2518
+ const padding = swimlane.padding ?? 16;
2519
+ const contractLayout = layouts.get(swimlane.id);
2520
+ if (layout2 === "contract" && contractLayout !== void 0) {
2521
+ const lanes2 = swimlane.lanes.map((lane, index) => {
2522
+ const box = swimlane.orientation === "vertical" ? {
2523
+ x: contractLayout.box.x + contractLayout.slotWidth * index,
2524
+ y: contractLayout.box.y,
2525
+ width: contractLayout.slotWidth,
2526
+ height: contractLayout.box.height
2527
+ } : {
2528
+ x: contractLayout.box.x,
2529
+ y: contractLayout.box.y + contractLayout.slotHeight * index,
2530
+ width: contractLayout.box.width,
2531
+ height: contractLayout.slotHeight
2532
+ };
2533
+ const headerBox = swimlane.orientation === "vertical" ? {
2534
+ x: box.x,
2535
+ y: box.y,
2536
+ width: box.width,
2537
+ height: headerHeight
2538
+ } : {
2539
+ x: box.x,
2540
+ y: box.y,
2541
+ width: headerHeight,
2542
+ height: box.height
2543
+ };
2544
+ const contentBox2 = swimlane.orientation === "vertical" ? {
2545
+ x: box.x,
2546
+ y: box.y + headerHeight,
2547
+ width: box.width,
2548
+ height: Math.max(0, box.height - headerHeight)
2549
+ } : {
2550
+ x: box.x + headerHeight,
2551
+ y: box.y,
2552
+ width: Math.max(0, box.width - headerHeight),
2553
+ height: box.height
2554
+ };
2555
+ return {
2556
+ ...lane,
2557
+ box,
2558
+ headerBox,
2559
+ contentBox: contentBox2
2560
+ };
2561
+ });
2562
+ return {
2563
+ ...swimlane,
2564
+ lanes: lanes2,
2565
+ box: contractLayout.box,
2566
+ ...headerHeight === void 0 ? {} : { headerHeight },
2567
+ ...padding === void 0 ? {} : { padding }
2568
+ };
2569
+ }
2570
+ const laneContentBoxes = swimlane.lanes.map((lane) => {
1905
2571
  const childBoxes = lane.children.map((child) => nodeBoxes.get(child)).filter((box) => box !== void 0);
1906
- return childBoxes.length === 0 ? [] : [unionBoxes(childBoxes)];
2572
+ return childBoxes.length === 0 ? void 0 : unionBoxes(childBoxes);
1907
2573
  });
1908
- const laneUnion = laneBoxes.length === 0 ? { x: 0, y: 0, width: 120, height: 80 } : unionBoxes(laneBoxes);
1909
- const outer = expand(laneUnion, padding, titleSize);
2574
+ const laneUnion = laneContentBoxes.filter((box) => box !== void 0).length === 0 ? { x: 0, y: 0, width: 120, height: 80 } : unionBoxes(
2575
+ laneContentBoxes.filter((box) => box !== void 0)
2576
+ );
2577
+ const outer = expand(laneUnion, padding, headerHeight);
1910
2578
  const laneCount = Math.max(1, swimlane.lanes.length);
1911
2579
  const lanes = swimlane.lanes.map((lane, index) => {
1912
2580
  const box = swimlane.orientation === "vertical" ? {
@@ -1920,9 +2588,42 @@ function coordinateSwimlanes(swimlanes, nodeBoxes) {
1920
2588
  width: outer.width,
1921
2589
  height: outer.height / laneCount
1922
2590
  };
1923
- return { ...lane, box };
2591
+ const headerBox = layout2 === "contract" ? swimlane.orientation === "vertical" ? {
2592
+ x: box.x,
2593
+ y: box.y,
2594
+ width: box.width,
2595
+ height: headerHeight
2596
+ } : {
2597
+ x: box.x,
2598
+ y: box.y,
2599
+ width: headerHeight,
2600
+ height: box.height
2601
+ } : void 0;
2602
+ const contentBox2 = layout2 === "contract" ? swimlane.orientation === "vertical" ? {
2603
+ x: box.x,
2604
+ y: box.y + headerHeight,
2605
+ width: box.width,
2606
+ height: Math.max(0, box.height - headerHeight)
2607
+ } : {
2608
+ x: box.x + headerHeight,
2609
+ y: box.y,
2610
+ width: Math.max(0, box.width - headerHeight),
2611
+ height: box.height
2612
+ } : void 0;
2613
+ return {
2614
+ ...lane,
2615
+ box,
2616
+ ...headerBox === void 0 ? {} : { headerBox },
2617
+ ...contentBox2 === void 0 ? {} : { contentBox: contentBox2 }
2618
+ };
1924
2619
  });
1925
- return { ...swimlane, lanes, box: outer };
2620
+ return {
2621
+ ...swimlane,
2622
+ lanes,
2623
+ box: outer,
2624
+ ...headerHeight === void 0 ? {} : { headerHeight },
2625
+ ...padding === void 0 ? {} : { padding }
2626
+ };
1926
2627
  });
1927
2628
  }
1928
2629
  function coordinateFrame(frame, contentBounds) {
@@ -2461,6 +3162,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
2461
3162
  const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
2462
3163
  const routeKind = dsl.routing?.kind ?? "orthogonal";
2463
3164
  const portShifting = normalizePortShifting(dsl.routing?.portShifting);
3165
+ const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
2464
3166
  const diagram = {
2465
3167
  id: options.id ?? dsl.id ?? "diagram",
2466
3168
  ...dsl.title === void 0 ? {} : { title: dsl.title },
@@ -2474,6 +3176,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
2474
3176
  ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
2475
3177
  metadata: {
2476
3178
  routeKind,
3179
+ ...primaryReadingDirection === void 0 ? {} : { primaryReadingDirection },
2477
3180
  ...portShifting === void 0 ? {} : { portShifting }
2478
3181
  }
2479
3182
  };
@@ -2644,14 +3347,17 @@ function formatCompartmentEntry(value) {
2644
3347
  return `${entry[0]}: ${entry[1]}`;
2645
3348
  }
2646
3349
  function normalizeSwimlanes(dsl) {
2647
- return Object.keys(dsl.swimlanes ?? {}).sort().map((id) => {
3350
+ return Object.keys(dsl.swimlanes ?? {}).map((id) => {
2648
3351
  const swimlane = dsl.swimlanes?.[id];
2649
3352
  const label = toLabel(swimlane?.label);
2650
3353
  return {
2651
3354
  id,
2652
3355
  ...label === void 0 ? {} : { label },
2653
3356
  orientation: swimlane?.orientation ?? "vertical",
2654
- lanes: Object.keys(swimlane?.lanes ?? {}).sort().map((laneId) => {
3357
+ layout: swimlane?.layout ?? "overlay",
3358
+ ...swimlane?.headerHeight === void 0 ? {} : { headerHeight: swimlane.headerHeight },
3359
+ ...swimlane?.padding === void 0 ? {} : { padding: swimlane.padding },
3360
+ lanes: Object.keys(swimlane?.lanes ?? {}).map((laneId) => {
2655
3361
  const lane = swimlane?.lanes[laneId];
2656
3362
  const laneLabel = toLabel(lane?.label);
2657
3363
  return {
@@ -2959,6 +3665,10 @@ var routeKindSchema = zod.z.enum(["orthogonal", "straight"]);
2959
3665
  var outputFormatSchema = zod.z.enum(["svg", "excalidraw"]);
2960
3666
  var edgeStrokeStyleSchema = zod.z.enum(["solid", "dashed"]);
2961
3667
  var edgeArrowheadSchema = zod.z.enum(["triangle", "hollowTriangle"]);
3668
+ var primaryReadingDirectionSchema = zod.z.enum([
3669
+ "top_to_bottom",
3670
+ "top-to-bottom"
3671
+ ]);
2962
3672
  var nodeShapeSchema = zod.z.enum([
2963
3673
  "rectangle",
2964
3674
  "rounded-rectangle",
@@ -2969,6 +3679,7 @@ var nodeShapeSchema = zod.z.enum([
2969
3679
  "cylinder"
2970
3680
  ]);
2971
3681
  var finiteNumberSchema = zod.z.number().finite();
3682
+ var nonNegativeNumberSchema = finiteNumberSchema.min(0);
2972
3683
  var pointSchema = zod.z.object({
2973
3684
  x: finiteNumberSchema,
2974
3685
  y: finiteNumberSchema
@@ -3055,6 +3766,9 @@ var groupSchema = zod.z.object({
3055
3766
  var swimlaneSchema = zod.z.object({
3056
3767
  label: labelSchema.optional(),
3057
3768
  orientation: zod.z.enum(["vertical", "horizontal"]).optional(),
3769
+ layout: zod.z.enum(["overlay", "contract"]).optional(),
3770
+ headerHeight: nonNegativeNumberSchema.optional(),
3771
+ padding: nonNegativeNumberSchema.optional(),
3058
3772
  lanes: zod.z.record(
3059
3773
  zod.z.string(),
3060
3774
  zod.z.object({
@@ -3120,7 +3834,8 @@ var diagramDslSchema = zod.z.object({
3120
3834
  title: zod.z.string().optional(),
3121
3835
  direction: directionSchema.optional(),
3122
3836
  layout: zod.z.object({
3123
- direction: directionSchema.optional()
3837
+ direction: directionSchema.optional(),
3838
+ primaryReadingDirection: primaryReadingDirectionSchema.optional()
3124
3839
  }).optional(),
3125
3840
  routing: zod.z.object({
3126
3841
  kind: routeKindSchema.optional(),