@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/index.cjs CHANGED
@@ -99,6 +99,9 @@ function applyLayoutConstraints(input) {
99
99
  const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
100
100
  applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
101
101
  applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
102
+ if (input.distributeContainedChildren) {
103
+ yieldFixedPositionLocks(input, boxes, locks);
104
+ }
102
105
  applyContainment(input.constraints, boxes, locks, diagnostics, false);
103
106
  applyRelative(input.constraints, boxes, locks, diagnostics);
104
107
  applyAlign(input.constraints, boxes, locks, diagnostics);
@@ -112,6 +115,13 @@ function applyLayoutConstraints(input) {
112
115
  );
113
116
  applyContainment(input.constraints, boxes, locks, diagnostics, true);
114
117
  applyDistributeContained(input, boxes, locks, diagnostics);
118
+ if (input.distributeContainedChildren) {
119
+ const diagBefore = diagnostics.length;
120
+ applyContainment(input.constraints, boxes, locks, diagnostics, true);
121
+ applyDistributeContained(input, boxes, locks, diagnostics);
122
+ dedupReplayDiagnostics(diagnostics, diagBefore);
123
+ }
124
+ removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
115
125
  reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
116
126
  reportIntraContainerOverflow(input, boxes, diagnostics);
117
127
  return { boxes, locks, diagnostics };
@@ -157,6 +167,62 @@ function applyFixedPositionLocks(nodes, boxes, locks, diagnostics) {
157
167
  locks.set(node.id, { nodeId: node.id, source: "fixed-position" });
158
168
  }
159
169
  }
170
+ function dedupReplayDiagnostics(diagnostics, keepUpTo) {
171
+ const seen = /* @__PURE__ */ new Set();
172
+ for (let i = 0; i < keepUpTo && i < diagnostics.length; i += 1) {
173
+ const d = diagnostics[i];
174
+ if (d === void 0) continue;
175
+ seen.add(diagnosticFingerprint(d));
176
+ }
177
+ for (let i = diagnostics.length - 1; i >= keepUpTo; i -= 1) {
178
+ const d = diagnostics[i];
179
+ if (d === void 0) continue;
180
+ const fp = diagnosticFingerprint(d);
181
+ if (seen.has(fp)) {
182
+ diagnostics.splice(i, 1);
183
+ } else {
184
+ seen.add(fp);
185
+ }
186
+ }
187
+ }
188
+ function diagnosticFingerprint(d) {
189
+ const nodeId = typeof d.detail?.nodeId === "string" ? d.detail.nodeId : "";
190
+ const containerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : "";
191
+ return `${d.code}|${nodeId}|${containerId}`;
192
+ }
193
+ function yieldFixedPositionLocks(input, boxes, locks) {
194
+ for (const c of input.constraints) {
195
+ if (c.kind !== "containment") continue;
196
+ const container = boxes.get(c.containerId);
197
+ if (container === void 0) continue;
198
+ const content = contentBox(container, c.padding);
199
+ const mainAxis = input.direction === "LR" || input.direction === "RL" ? "width" : "height";
200
+ const crossAxis = mainAxis === "width" ? "height" : "width";
201
+ let eligible = 0;
202
+ for (const childId of c.childIds) {
203
+ const box = boxes.get(childId);
204
+ if (box === void 0) continue;
205
+ const lock = locks.get(childId);
206
+ if (lock?.source === "exact-position") continue;
207
+ const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
208
+ if (fits) {
209
+ eligible += 1;
210
+ }
211
+ }
212
+ if (eligible < 2) continue;
213
+ for (const childId of c.childIds) {
214
+ const lock = locks.get(childId);
215
+ if (lock?.source === "fixed-position") {
216
+ const box = boxes.get(childId);
217
+ if (box === void 0) continue;
218
+ const fits = box[mainAxis] <= content[mainAxis] && box[crossAxis] <= content[crossAxis];
219
+ if (fits) {
220
+ locks.delete(childId);
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
160
226
  function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
161
227
  for (const constraint of constraints) {
162
228
  if (constraint.kind !== "exact-position") {
@@ -229,7 +295,7 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
229
295
  code: "constraints.locked-target-not-moved",
230
296
  message: `Locked child ${childId} was not moved into containment.`,
231
297
  path: ["constraints", constraint.id ?? constraint.containerId],
232
- detail: { nodeId: childId }
298
+ detail: { nodeId: childId, containerId: constraint.containerId }
233
299
  });
234
300
  if (!isInside(child, content)) {
235
301
  diagnostics.push({
@@ -381,6 +447,60 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
381
447
  }
382
448
  reportOverlaps(boxes, diagnostics, ignoredPairs);
383
449
  }
450
+ function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
451
+ for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
452
+ const d = diagnostics[i];
453
+ if (d === void 0) continue;
454
+ if (d.code === "constraints.overlap.unresolved") {
455
+ const aId = d.detail?.firstId;
456
+ const bId = d.detail?.secondId;
457
+ if (typeof aId !== "string" || typeof bId !== "string") continue;
458
+ const a = boxes.get(aId);
459
+ const b = boxes.get(bId);
460
+ if (a !== void 0 && b !== void 0 && !intersectsAabb(a, b)) {
461
+ diagnostics.splice(i, 1);
462
+ }
463
+ continue;
464
+ }
465
+ if (d.code === "constraints.containment.impossible" || d.code === "constraints.locked-target-not-moved" && typeof d.message === "string" && d.message.includes("not moved into containment")) {
466
+ const nodeId = d.detail?.nodeId;
467
+ if (typeof nodeId !== "string") continue;
468
+ const child = boxes.get(nodeId);
469
+ if (child === void 0) continue;
470
+ const diagContainerId = typeof d.detail?.containerId === "string" ? d.detail.containerId : void 0;
471
+ let resolved = false;
472
+ for (const c of constraints) {
473
+ if (c.kind !== "containment") continue;
474
+ if (!c.childIds.includes(nodeId)) continue;
475
+ if (diagContainerId !== void 0 && c.containerId !== diagContainerId) {
476
+ continue;
477
+ }
478
+ const container = boxes.get(c.containerId);
479
+ if (container === void 0) continue;
480
+ const content = contentBox(container, c.padding);
481
+ if (isInside(child, content)) {
482
+ diagnostics.splice(i, 1);
483
+ resolved = true;
484
+ }
485
+ break;
486
+ }
487
+ if (!resolved && diagContainerId !== void 0) {
488
+ for (const c of constraints) {
489
+ if (c.kind !== "containment") continue;
490
+ if (c.containerId !== diagContainerId) continue;
491
+ if (!c.childIds.includes(nodeId)) continue;
492
+ const container = boxes.get(c.containerId);
493
+ if (container === void 0) continue;
494
+ const content = contentBox(container, c.padding);
495
+ if (isInside(child, content)) {
496
+ diagnostics.splice(i, 1);
497
+ }
498
+ break;
499
+ }
500
+ }
501
+ }
502
+ }
503
+ }
384
504
  function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
385
505
  const ids = [...boxes.keys()].sort();
386
506
  const reported = new Set(
@@ -688,6 +808,12 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
688
808
  continue;
689
809
  }
690
810
  if (locks.has(childId)) {
811
+ const lock = locks.get(childId);
812
+ if (lock?.source === "fixed-position") {
813
+ unlocked.push({ id: childId, box });
814
+ locks.delete(childId);
815
+ continue;
816
+ }
691
817
  diagnostics.push({
692
818
  severity: "warning",
693
819
  code: "constraints.locked-target-not-moved",
@@ -746,6 +872,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
746
872
  });
747
873
  }
748
874
  boxes.set(child.id, clamped);
875
+ locks.delete(child.id);
749
876
  pos = clamped[axis] + clamped[mainSize] + minGap;
750
877
  }
751
878
  diagnostics.push({
@@ -3862,6 +3989,7 @@ function routeEdge(input) {
3862
3989
  const diagnostics = [];
3863
3990
  const softObstacles = input.obstacles ?? [];
3864
3991
  const hardObstacles = input.hardObstacles ?? [];
3992
+ const maxAttempts = input.maxRoutingAttempts ?? 5;
3865
3993
  const defaultAnchors = defaultAnchorsForGeometry(
3866
3994
  input.source.box,
3867
3995
  input.target.box,
@@ -3970,7 +4098,7 @@ function routeEdge(input) {
3970
4098
  const rerouted2 = greedyRerouteAroundObstacles(
3971
4099
  candidate.points,
3972
4100
  allObstacles,
3973
- 3
4101
+ maxAttempts
3974
4102
  );
3975
4103
  if (!routeCrossesBoxes(rerouted2, allObstacles) && !routeIntersectsEndpointInteriors(
3976
4104
  rerouted2,
@@ -3990,7 +4118,7 @@ function routeEdge(input) {
3990
4118
  const rerouted = greedyRerouteAroundObstacles(
3991
4119
  bestPoints2,
3992
4120
  allObstacles,
3993
- 3
4121
+ maxAttempts
3994
4122
  );
3995
4123
  const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
3996
4124
  rerouted,
@@ -4025,7 +4153,7 @@ function routeEdge(input) {
4025
4153
  const rerouted = greedyRerouteAroundObstacles(
4026
4154
  candidate.points,
4027
4155
  allObstacles,
4028
- 5
4156
+ maxAttempts
4029
4157
  );
4030
4158
  if (!routeCrossesBoxes(rerouted, allObstacles)) {
4031
4159
  return {
@@ -4042,7 +4170,7 @@ function routeEdge(input) {
4042
4170
  bestPoints2 = greedyRerouteAroundObstacles(
4043
4171
  candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
4044
4172
  allObstacles,
4045
- 5
4173
+ maxAttempts
4046
4174
  );
4047
4175
  }
4048
4176
  diagnostics.push({
@@ -4067,7 +4195,7 @@ function routeEdge(input) {
4067
4195
  const rerouted = greedyRerouteAroundObstacles(
4068
4196
  candidate.points,
4069
4197
  allObstacles,
4070
- 5
4198
+ maxAttempts
4071
4199
  );
4072
4200
  if (!routeCrossesBoxes(rerouted, allObstacles)) {
4073
4201
  return {
@@ -4084,7 +4212,7 @@ function routeEdge(input) {
4084
4212
  bestPoints = greedyRerouteAroundObstacles(
4085
4213
  candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
4086
4214
  allObstacles,
4087
- 5
4215
+ maxAttempts
4088
4216
  );
4089
4217
  }
4090
4218
  diagnostics.push({
@@ -4752,9 +4880,7 @@ function solveDiagram(diagram, options = {}) {
4752
4880
  options?.overlapSpacing ?? 40,
4753
4881
  Math.max(0, options?.minLaneGutter ?? 0)
4754
4882
  );
4755
- if (swimlaneContracts.layouts.size > 0) {
4756
- removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4757
- }
4883
+ removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4758
4884
  diagnostics.push(...swimlaneContracts.diagnostics);
4759
4885
  const coordinatedNodes = coordinateNodes(
4760
4886
  styledNodes,
@@ -4919,7 +5045,9 @@ function solveDiagram(diagram, options = {}) {
4919
5045
  ...baseTextAnnotations.map((annotation) => annotation.box),
4920
5046
  ...frameTextAnnotation.map((annotation) => annotation.box)
4921
5047
  ],
4922
- options.textMeasurer
5048
+ options.textMeasurer,
5049
+ options.labelPlacement,
5050
+ options.labelOffset
4923
5051
  );
4924
5052
  const textAnnotations = [
4925
5053
  ...baseTextAnnotations,
@@ -6698,7 +6826,8 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
6698
6826
  ...softObstacles,
6699
6827
  ...routeTextObstacles
6700
6828
  ],
6701
- hardObstacles
6829
+ hardObstacles,
6830
+ ...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
6702
6831
  });
6703
6832
  diagnostics.push(
6704
6833
  ...route.diagnostics.map((diagnostic) => ({
@@ -6860,7 +6989,8 @@ function coordinateBaseTextAnnotations(input) {
6860
6989
  }
6861
6990
  return annotations;
6862
6991
  }
6863
- function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer) {
6992
+ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer, labelPlacement, labelOffset3) {
6993
+ const labelBaseOffset = labelPlacement === "beside" ? labelOffset3 ?? 16 : 10;
6864
6994
  const measurer = textMeasurer ?? createDefaultTextMeasurer();
6865
6995
  const annotations = [];
6866
6996
  const placedLabelBoxes = [];
@@ -6887,7 +7017,8 @@ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer) {
6887
7017
  layout2,
6888
7018
  edges,
6889
7019
  obstacleBoxes,
6890
- placedLabelBoxes
7020
+ placedLabelBoxes,
7021
+ labelBaseOffset
6891
7022
  );
6892
7023
  placedLabelBoxes.push({
6893
7024
  x: center.x - layout2.box.width / 2,
@@ -7171,15 +7302,16 @@ function fallbackLabelLayout(text) {
7171
7302
  diagnostics: []
7172
7303
  };
7173
7304
  }
7174
- function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes) {
7175
- const placement = labelPlacementOnPolyline2(edge.points);
7305
+ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes, baseOffset = 10) {
7306
+ const placement = labelPlacementOnPolyline2(edge.points, baseOffset);
7176
7307
  if (placement === void 0) {
7177
7308
  return { x: 0, y: 0 };
7178
7309
  }
7179
7310
  for (const candidate of edgeLabelAnchorCandidates(
7180
7311
  edge.points,
7181
7312
  placement,
7182
- layout2
7313
+ layout2,
7314
+ baseOffset
7183
7315
  )) {
7184
7316
  const labelBox = {
7185
7317
  x: candidate.x - layout2.box.width / 2,
@@ -7211,8 +7343,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes)
7211
7343
  }
7212
7344
  return placement;
7213
7345
  }
7214
- function edgeLabelAnchorCandidates(points, placement, layout2) {
7215
- const segment = labelSegmentOnPolyline(points);
7346
+ function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
7347
+ const segment = labelSegmentOnPolyline(points, baseOffset);
7216
7348
  if (segment === void 0) {
7217
7349
  return [placement];
7218
7350
  }
@@ -7262,7 +7394,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
7262
7394
  }, 0);
7263
7395
  if (totalLen > 200) {
7264
7396
  for (const ratio of [0.25, 0.75]) {
7265
- const qp = labelPlacementAtRatio(points, ratio, totalLen);
7397
+ const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
7266
7398
  if (qp !== void 0) {
7267
7399
  candidates.push(qp);
7268
7400
  const qTargetDist = totalLen * ratio;
@@ -7317,10 +7449,10 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
7317
7449
  }
7318
7450
  return candidates;
7319
7451
  }
7320
- function labelPlacementOnPolyline2(points) {
7321
- return labelSegmentOnPolyline(points)?.placement;
7452
+ function labelPlacementOnPolyline2(points, baseOffset = 10) {
7453
+ return labelSegmentOnPolyline(points, baseOffset)?.placement;
7322
7454
  }
7323
- function labelSegmentOnPolyline(points) {
7455
+ function labelSegmentOnPolyline(points, baseOffset = 10) {
7324
7456
  const segments = nonZeroSegments2(points);
7325
7457
  const totalLength = segments.reduce(
7326
7458
  (sum, segment) => sum + segment.length,
@@ -7335,7 +7467,7 @@ function labelSegmentOnPolyline(points) {
7335
7467
  const ratio = remaining / segment.length;
7336
7468
  const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
7337
7469
  const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
7338
- const offset2 = labelOffset2(segment);
7470
+ const offset2 = labelOffset2(segment, baseOffset);
7339
7471
  return {
7340
7472
  start: segment.start,
7341
7473
  end: segment.end,
@@ -7348,7 +7480,7 @@ function labelSegmentOnPolyline(points) {
7348
7480
  if (last === void 0) {
7349
7481
  return void 0;
7350
7482
  }
7351
- const offset = labelOffset2(last);
7483
+ const offset = labelOffset2(last, baseOffset);
7352
7484
  return {
7353
7485
  start: last.start,
7354
7486
  end: last.end,
@@ -7370,7 +7502,7 @@ function nonZeroSegments2(points) {
7370
7502
  }
7371
7503
  return segments;
7372
7504
  }
7373
- function labelPlacementAtRatio(points, ratio, totalLength) {
7505
+ function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
7374
7506
  if (points.length < 2 || ratio < 0 || ratio > 1) {
7375
7507
  return void 0;
7376
7508
  }
@@ -7388,7 +7520,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
7388
7520
  }
7389
7521
  if (travelled + segLen >= targetDist) {
7390
7522
  const t = (targetDist - travelled) / segLen;
7391
- const offset = labelOffset2({ start: prev, end: curr, length: segLen });
7523
+ const offset = labelOffset2(
7524
+ { start: prev, end: curr, length: segLen },
7525
+ baseOffset
7526
+ );
7392
7527
  return {
7393
7528
  x: prev.x + (curr.x - prev.x) * t + offset.x,
7394
7529
  y: prev.y + (curr.y - prev.y) * t + offset.y
@@ -7398,8 +7533,8 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
7398
7533
  }
7399
7534
  return void 0;
7400
7535
  }
7401
- function labelOffset2(segment) {
7402
- const offset = 10;
7536
+ function labelOffset2(segment, baseOffset = 10) {
7537
+ const offset = baseOffset;
7403
7538
  const dx = segment.end.x - segment.start.x;
7404
7539
  const dy = segment.end.y - segment.start.y;
7405
7540
  return {