@crazyhappyone/auto-graph 0.1.2 → 0.1.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.
package/dist/cli/index.js CHANGED
@@ -1289,6 +1289,9 @@ function applyLayoutConstraints(input) {
1289
1289
  const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
1290
1290
  applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
1291
1291
  applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
1292
+ if (input.distributeContainedChildren) {
1293
+ yieldFixedPositionLocks(input, boxes, locks);
1294
+ }
1292
1295
  applyContainment(input.constraints, boxes, locks, diagnostics, false);
1293
1296
  applyRelative(input.constraints, boxes, locks, diagnostics);
1294
1297
  applyAlign(input.constraints, boxes, locks, diagnostics);
@@ -1302,6 +1305,13 @@ function applyLayoutConstraints(input) {
1302
1305
  );
1303
1306
  applyContainment(input.constraints, boxes, locks, diagnostics, true);
1304
1307
  applyDistributeContained(input, boxes, locks, diagnostics);
1308
+ if (input.distributeContainedChildren) {
1309
+ const diagBefore = diagnostics.length;
1310
+ applyContainment(input.constraints, boxes, locks, diagnostics, true);
1311
+ applyDistributeContained(input, boxes, locks, diagnostics);
1312
+ dedupReplayDiagnostics(diagnostics, diagBefore);
1313
+ }
1314
+ removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
1305
1315
  reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
1306
1316
  reportIntraContainerOverflow(input, boxes, diagnostics);
1307
1317
  return { boxes, locks, diagnostics };
@@ -1347,6 +1357,62 @@ function applyFixedPositionLocks(nodes, boxes, locks, diagnostics) {
1347
1357
  locks.set(node.id, { nodeId: node.id, source: "fixed-position" });
1348
1358
  }
1349
1359
  }
1360
+ function dedupReplayDiagnostics(diagnostics, keepUpTo) {
1361
+ const seen = /* @__PURE__ */ new Set();
1362
+ for (let i = 0; i < keepUpTo && i < diagnostics.length; i += 1) {
1363
+ const d = diagnostics[i];
1364
+ if (d === void 0) continue;
1365
+ seen.add(diagnosticFingerprint(d));
1366
+ }
1367
+ for (let i = diagnostics.length - 1; i >= keepUpTo; i -= 1) {
1368
+ const d = diagnostics[i];
1369
+ if (d === void 0) continue;
1370
+ const fp = diagnosticFingerprint(d);
1371
+ if (seen.has(fp)) {
1372
+ diagnostics.splice(i, 1);
1373
+ } else {
1374
+ seen.add(fp);
1375
+ }
1376
+ }
1377
+ }
1378
+ function diagnosticFingerprint(d) {
1379
+ const nodeId = typeof d.detail?.nodeId === "string" ? d.detail.nodeId : "";
1380
+ const containerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : "";
1381
+ return `${d.code}|${nodeId}|${containerId}`;
1382
+ }
1383
+ function yieldFixedPositionLocks(input, boxes, locks) {
1384
+ for (const c of input.constraints) {
1385
+ if (c.kind !== "containment") continue;
1386
+ const container = boxes.get(c.containerId);
1387
+ if (container === void 0) continue;
1388
+ const content = contentBox(container, c.padding);
1389
+ const mainAxis = input.direction === "LR" || input.direction === "RL" ? "width" : "height";
1390
+ const crossAxis = mainAxis === "width" ? "height" : "width";
1391
+ let eligible = 0;
1392
+ for (const childId of c.childIds) {
1393
+ const box = boxes.get(childId);
1394
+ if (box === void 0) continue;
1395
+ const lock = locks.get(childId);
1396
+ if (lock?.source === "exact-position") continue;
1397
+ const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
1398
+ if (fits) {
1399
+ eligible += 1;
1400
+ }
1401
+ }
1402
+ if (eligible < 2) continue;
1403
+ for (const childId of c.childIds) {
1404
+ const lock = locks.get(childId);
1405
+ if (lock?.source === "fixed-position") {
1406
+ const box = boxes.get(childId);
1407
+ if (box === void 0) continue;
1408
+ const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
1409
+ if (fits) {
1410
+ locks.delete(childId);
1411
+ }
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1350
1416
  function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
1351
1417
  for (const constraint of constraints) {
1352
1418
  if (constraint.kind !== "exact-position") {
@@ -1419,7 +1485,7 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
1419
1485
  code: "constraints.locked-target-not-moved",
1420
1486
  message: `Locked child ${childId} was not moved into containment.`,
1421
1487
  path: ["constraints", constraint.id ?? constraint.containerId],
1422
- detail: { nodeId: childId }
1488
+ detail: { nodeId: childId, containerId: constraint.containerId }
1423
1489
  });
1424
1490
  if (!isInside(child, content)) {
1425
1491
  diagnostics.push({
@@ -1571,6 +1637,60 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
1571
1637
  }
1572
1638
  reportOverlaps(boxes, diagnostics, ignoredPairs);
1573
1639
  }
1640
+ function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
1641
+ for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
1642
+ const d = diagnostics[i];
1643
+ if (d === void 0) continue;
1644
+ if (d.code === "constraints.overlap.unresolved") {
1645
+ const aId = d.detail?.firstId;
1646
+ const bId = d.detail?.secondId;
1647
+ if (typeof aId !== "string" || typeof bId !== "string") continue;
1648
+ const a = boxes.get(aId);
1649
+ const b = boxes.get(bId);
1650
+ if (a !== void 0 && b !== void 0 && !intersectsAabb(a, b)) {
1651
+ diagnostics.splice(i, 1);
1652
+ }
1653
+ continue;
1654
+ }
1655
+ if (d.code === "constraints.containment.impossible" || d.code === "constraints.locked-target-not-moved" && typeof d.message === "string" && d.message.includes("not moved into containment")) {
1656
+ const nodeId = d.detail?.nodeId;
1657
+ if (typeof nodeId !== "string") continue;
1658
+ const child = boxes.get(nodeId);
1659
+ if (child === void 0) continue;
1660
+ const diagContainerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : void 0;
1661
+ let resolved = false;
1662
+ for (const c of constraints) {
1663
+ if (c.kind !== "containment") continue;
1664
+ if (!c.childIds.includes(nodeId)) continue;
1665
+ if (diagContainerId !== void 0 && c.containerId !== diagContainerId) {
1666
+ continue;
1667
+ }
1668
+ const container = boxes.get(c.containerId);
1669
+ if (container === void 0) continue;
1670
+ const content = contentBox(container, c.padding);
1671
+ if (isInside(child, content)) {
1672
+ diagnostics.splice(i, 1);
1673
+ resolved = true;
1674
+ }
1675
+ break;
1676
+ }
1677
+ if (!resolved && diagContainerId !== void 0) {
1678
+ for (const c of constraints) {
1679
+ if (c.kind !== "containment") continue;
1680
+ if (c.containerId !== diagContainerId) continue;
1681
+ if (!c.childIds.includes(nodeId)) continue;
1682
+ const container = boxes.get(c.containerId);
1683
+ if (container === void 0) continue;
1684
+ const content = contentBox(container, c.padding);
1685
+ if (isInside(child, content)) {
1686
+ diagnostics.splice(i, 1);
1687
+ }
1688
+ break;
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1574
1694
  function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
1575
1695
  const ids = [...boxes.keys()].sort();
1576
1696
  const reported = new Set(
@@ -1878,6 +1998,12 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
1878
1998
  continue;
1879
1999
  }
1880
2000
  if (locks.has(childId)) {
2001
+ const lock = locks.get(childId);
2002
+ if (lock?.source === "fixed-position") {
2003
+ unlocked.push({ id: childId, box });
2004
+ locks.delete(childId);
2005
+ continue;
2006
+ }
1881
2007
  diagnostics.push({
1882
2008
  severity: "warning",
1883
2009
  code: "constraints.locked-target-not-moved",
@@ -1936,6 +2062,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
1936
2062
  });
1937
2063
  }
1938
2064
  boxes.set(child.id, clamped);
2065
+ locks.delete(child.id);
1939
2066
  pos = clamped[axis] + clamped[mainSize] + minGap;
1940
2067
  }
1941
2068
  diagnostics.push({
@@ -3256,6 +3383,7 @@ function routeEdge(input) {
3256
3383
  const diagnostics = [];
3257
3384
  const softObstacles = input.obstacles ?? [];
3258
3385
  const hardObstacles = input.hardObstacles ?? [];
3386
+ const maxAttempts = input.maxRoutingAttempts ?? 5;
3259
3387
  const defaultAnchors = defaultAnchorsForGeometry(
3260
3388
  input.source.box,
3261
3389
  input.target.box,
@@ -3364,7 +3492,7 @@ function routeEdge(input) {
3364
3492
  const rerouted2 = greedyRerouteAroundObstacles(
3365
3493
  candidate.points,
3366
3494
  allObstacles,
3367
- 3
3495
+ maxAttempts
3368
3496
  );
3369
3497
  if (!routeCrossesBoxes(rerouted2, allObstacles) && !routeIntersectsEndpointInteriors(
3370
3498
  rerouted2,
@@ -3384,7 +3512,7 @@ function routeEdge(input) {
3384
3512
  const rerouted = greedyRerouteAroundObstacles(
3385
3513
  bestPoints2,
3386
3514
  allObstacles,
3387
- 3
3515
+ maxAttempts
3388
3516
  );
3389
3517
  const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
3390
3518
  rerouted,
@@ -3419,7 +3547,7 @@ function routeEdge(input) {
3419
3547
  const rerouted = greedyRerouteAroundObstacles(
3420
3548
  candidate.points,
3421
3549
  allObstacles,
3422
- 5
3550
+ maxAttempts
3423
3551
  );
3424
3552
  if (!routeCrossesBoxes(rerouted, allObstacles)) {
3425
3553
  return {
@@ -3436,7 +3564,7 @@ function routeEdge(input) {
3436
3564
  bestPoints2 = greedyRerouteAroundObstacles(
3437
3565
  candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
3438
3566
  allObstacles,
3439
- 5
3567
+ maxAttempts
3440
3568
  );
3441
3569
  }
3442
3570
  diagnostics.push({
@@ -3461,7 +3589,7 @@ function routeEdge(input) {
3461
3589
  const rerouted = greedyRerouteAroundObstacles(
3462
3590
  candidate.points,
3463
3591
  allObstacles,
3464
- 5
3592
+ maxAttempts
3465
3593
  );
3466
3594
  if (!routeCrossesBoxes(rerouted, allObstacles)) {
3467
3595
  return {
@@ -3478,7 +3606,7 @@ function routeEdge(input) {
3478
3606
  bestPoints = greedyRerouteAroundObstacles(
3479
3607
  candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
3480
3608
  allObstacles,
3481
- 5
3609
+ maxAttempts
3482
3610
  );
3483
3611
  }
3484
3612
  diagnostics.push({
@@ -4146,9 +4274,7 @@ function solveDiagram(diagram, options = {}) {
4146
4274
  options?.overlapSpacing ?? 40,
4147
4275
  Math.max(0, options?.minLaneGutter ?? 0)
4148
4276
  );
4149
- if (swimlaneContracts.layouts.size > 0) {
4150
- removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4151
- }
4277
+ removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4152
4278
  diagnostics.push(...swimlaneContracts.diagnostics);
4153
4279
  const coordinatedNodes = coordinateNodes(
4154
4280
  styledNodes,
@@ -4313,7 +4439,9 @@ function solveDiagram(diagram, options = {}) {
4313
4439
  ...baseTextAnnotations.map((annotation) => annotation.box),
4314
4440
  ...frameTextAnnotation.map((annotation) => annotation.box)
4315
4441
  ],
4316
- options.textMeasurer
4442
+ options.textMeasurer,
4443
+ options.labelPlacement,
4444
+ options.labelOffset
4317
4445
  );
4318
4446
  const textAnnotations = [
4319
4447
  ...baseTextAnnotations,
@@ -6089,7 +6217,8 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
6089
6217
  ...softObstacles,
6090
6218
  ...routeTextObstacles
6091
6219
  ],
6092
- hardObstacles
6220
+ hardObstacles,
6221
+ ...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
6093
6222
  });
6094
6223
  diagnostics.push(
6095
6224
  ...route.diagnostics.map((diagnostic) => ({
@@ -6251,7 +6380,8 @@ function coordinateBaseTextAnnotations(input) {
6251
6380
  }
6252
6381
  return annotations;
6253
6382
  }
6254
- function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer) {
6383
+ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, labelPlacement, labelOffset3) {
6384
+ const labelBaseOffset = labelPlacement === "beside" ? labelOffset3 ?? 16 : 10;
6255
6385
  const measurer = textMeasurer ?? createDefaultTextMeasurer();
6256
6386
  const annotations = [];
6257
6387
  const placedLabelBoxes = [];
@@ -6278,7 +6408,8 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer) {
6278
6408
  layout2,
6279
6409
  edges,
6280
6410
  obstacleBoxes,
6281
- placedLabelBoxes
6411
+ placedLabelBoxes,
6412
+ labelBaseOffset
6282
6413
  );
6283
6414
  placedLabelBoxes.push({
6284
6415
  x: center.x - layout2.box.width / 2,
@@ -6562,15 +6693,16 @@ function fallbackLabelLayout(text) {
6562
6693
  diagnostics: []
6563
6694
  };
6564
6695
  }
6565
- function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes) {
6566
- const placement = labelPlacementOnPolyline2(edge.points);
6696
+ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes, baseOffset = 10) {
6697
+ const placement = labelPlacementOnPolyline2(edge.points, baseOffset);
6567
6698
  if (placement === void 0) {
6568
6699
  return { x: 0, y: 0 };
6569
6700
  }
6570
6701
  for (const candidate of edgeLabelAnchorCandidates(
6571
6702
  edge.points,
6572
6703
  placement,
6573
- layout2
6704
+ layout2,
6705
+ baseOffset
6574
6706
  )) {
6575
6707
  const labelBox = {
6576
6708
  x: candidate.x - layout2.box.width / 2,
@@ -6602,8 +6734,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes)
6602
6734
  }
6603
6735
  return placement;
6604
6736
  }
6605
- function edgeLabelAnchorCandidates(points, placement, layout2) {
6606
- const segment = labelSegmentOnPolyline(points);
6737
+ function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
6738
+ const segment = labelSegmentOnPolyline(points, baseOffset);
6607
6739
  if (segment === void 0) {
6608
6740
  return [placement];
6609
6741
  }
@@ -6653,7 +6785,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
6653
6785
  }, 0);
6654
6786
  if (totalLen > 200) {
6655
6787
  for (const ratio of [0.25, 0.75]) {
6656
- const qp = labelPlacementAtRatio(points, ratio, totalLen);
6788
+ const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
6657
6789
  if (qp !== void 0) {
6658
6790
  candidates.push(qp);
6659
6791
  const qTargetDist = totalLen * ratio;
@@ -6708,10 +6840,10 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
6708
6840
  }
6709
6841
  return candidates;
6710
6842
  }
6711
- function labelPlacementOnPolyline2(points) {
6712
- return labelSegmentOnPolyline(points)?.placement;
6843
+ function labelPlacementOnPolyline2(points, baseOffset = 10) {
6844
+ return labelSegmentOnPolyline(points, baseOffset)?.placement;
6713
6845
  }
6714
- function labelSegmentOnPolyline(points) {
6846
+ function labelSegmentOnPolyline(points, baseOffset = 10) {
6715
6847
  const segments = nonZeroSegments2(points);
6716
6848
  const totalLength = segments.reduce(
6717
6849
  (sum, segment) => sum + segment.length,
@@ -6726,7 +6858,7 @@ function labelSegmentOnPolyline(points) {
6726
6858
  const ratio = remaining / segment.length;
6727
6859
  const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
6728
6860
  const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
6729
- const offset2 = labelOffset2(segment);
6861
+ const offset2 = labelOffset2(segment, baseOffset);
6730
6862
  return {
6731
6863
  start: segment.start,
6732
6864
  end: segment.end,
@@ -6739,7 +6871,7 @@ function labelSegmentOnPolyline(points) {
6739
6871
  if (last === void 0) {
6740
6872
  return void 0;
6741
6873
  }
6742
- const offset = labelOffset2(last);
6874
+ const offset = labelOffset2(last, baseOffset);
6743
6875
  return {
6744
6876
  start: last.start,
6745
6877
  end: last.end,
@@ -6761,7 +6893,7 @@ function nonZeroSegments2(points) {
6761
6893
  }
6762
6894
  return segments;
6763
6895
  }
6764
- function labelPlacementAtRatio(points, ratio, totalLength) {
6896
+ function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
6765
6897
  if (points.length < 2 || ratio < 0 || ratio > 1) {
6766
6898
  return void 0;
6767
6899
  }
@@ -6779,7 +6911,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
6779
6911
  }
6780
6912
  if (travelled + segLen >= targetDist) {
6781
6913
  const t = (targetDist - travelled) / segLen;
6782
- const offset = labelOffset2({ start: prev, end: curr, length: segLen });
6914
+ const offset = labelOffset2(
6915
+ { start: prev, end: curr, length: segLen },
6916
+ baseOffset
6917
+ );
6783
6918
  return {
6784
6919
  x: prev.x + (curr.x - prev.x) * t + offset.x,
6785
6920
  y: prev.y + (curr.y - prev.y) * t + offset.y
@@ -6789,8 +6924,8 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
6789
6924
  }
6790
6925
  return void 0;
6791
6926
  }
6792
- function labelOffset2(segment) {
6793
- const offset = 10;
6927
+ function labelOffset2(segment, baseOffset = 10) {
6928
+ const offset = baseOffset;
6794
6929
  const dx = segment.end.x - segment.start.x;
6795
6930
  const dy = segment.end.y - segment.start.y;
6796
6931
  return {