@crazyhappyone/auto-graph 0.1.3 → 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({
@@ -3991,7 +4118,7 @@ function routeEdge(input) {
3991
4118
  const rerouted = greedyRerouteAroundObstacles(
3992
4119
  bestPoints2,
3993
4120
  allObstacles,
3994
- Math.min(maxAttempts, 3)
4121
+ maxAttempts
3995
4122
  );
3996
4123
  const reroutedAvoidsEndpointInteriors = !routeIntersectsEndpointInteriors(
3997
4124
  rerouted,
@@ -4753,9 +4880,7 @@ function solveDiagram(diagram, options = {}) {
4753
4880
  options?.overlapSpacing ?? 40,
4754
4881
  Math.max(0, options?.minLaneGutter ?? 0)
4755
4882
  );
4756
- if (swimlaneContracts.layouts.size > 0) {
4757
- removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4758
- }
4883
+ removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4759
4884
  diagnostics.push(...swimlaneContracts.diagnostics);
4760
4885
  const coordinatedNodes = coordinateNodes(
4761
4886
  styledNodes,
@@ -6701,7 +6826,8 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
6701
6826
  ...softObstacles,
6702
6827
  ...routeTextObstacles
6703
6828
  ],
6704
- hardObstacles
6829
+ hardObstacles,
6830
+ ...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts }
6705
6831
  });
6706
6832
  diagnostics.push(
6707
6833
  ...route.diagnostics.map((diagnostic) => ({
@@ -7184,7 +7310,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
7184
7310
  for (const candidate of edgeLabelAnchorCandidates(
7185
7311
  edge.points,
7186
7312
  placement,
7187
- layout2
7313
+ layout2,
7314
+ baseOffset
7188
7315
  )) {
7189
7316
  const labelBox = {
7190
7317
  x: candidate.x - layout2.box.width / 2,
@@ -7216,8 +7343,8 @@ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes,
7216
7343
  }
7217
7344
  return placement;
7218
7345
  }
7219
- function edgeLabelAnchorCandidates(points, placement, layout2) {
7220
- const segment = labelSegmentOnPolyline(points);
7346
+ function edgeLabelAnchorCandidates(points, placement, layout2, baseOffset = 10) {
7347
+ const segment = labelSegmentOnPolyline(points, baseOffset);
7221
7348
  if (segment === void 0) {
7222
7349
  return [placement];
7223
7350
  }
@@ -7267,7 +7394,7 @@ function edgeLabelAnchorCandidates(points, placement, layout2) {
7267
7394
  }, 0);
7268
7395
  if (totalLen > 200) {
7269
7396
  for (const ratio of [0.25, 0.75]) {
7270
- const qp = labelPlacementAtRatio(points, ratio, totalLen);
7397
+ const qp = labelPlacementAtRatio(points, ratio, totalLen, baseOffset);
7271
7398
  if (qp !== void 0) {
7272
7399
  candidates.push(qp);
7273
7400
  const qTargetDist = totalLen * ratio;
@@ -7353,7 +7480,7 @@ function labelSegmentOnPolyline(points, baseOffset = 10) {
7353
7480
  if (last === void 0) {
7354
7481
  return void 0;
7355
7482
  }
7356
- const offset = labelOffset2(last);
7483
+ const offset = labelOffset2(last, baseOffset);
7357
7484
  return {
7358
7485
  start: last.start,
7359
7486
  end: last.end,
@@ -7375,7 +7502,7 @@ function nonZeroSegments2(points) {
7375
7502
  }
7376
7503
  return segments;
7377
7504
  }
7378
- function labelPlacementAtRatio(points, ratio, totalLength) {
7505
+ function labelPlacementAtRatio(points, ratio, totalLength, baseOffset = 10) {
7379
7506
  if (points.length < 2 || ratio < 0 || ratio > 1) {
7380
7507
  return void 0;
7381
7508
  }
@@ -7393,7 +7520,10 @@ function labelPlacementAtRatio(points, ratio, totalLength) {
7393
7520
  }
7394
7521
  if (travelled + segLen >= targetDist) {
7395
7522
  const t = (targetDist - travelled) / segLen;
7396
- const offset = labelOffset2({ start: prev, end: curr, length: segLen });
7523
+ const offset = labelOffset2(
7524
+ { start: prev, end: curr, length: segLen },
7525
+ baseOffset
7526
+ );
7397
7527
  return {
7398
7528
  x: prev.x + (curr.x - prev.x) * t + offset.x,
7399
7529
  y: prev.y + (curr.y - prev.y) * t + offset.y