@crazyhappyone/auto-graph 0.0.1 → 0.0.21

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.
@@ -3,12 +3,15 @@
3
3
 
4
4
  var commander = require('commander');
5
5
  var dagre = require('@dagrejs/dagre');
6
+ var module$1 = require('module');
7
+ var pretext = require('@chenglou/pretext');
6
8
  var buffer = require('buffer');
7
9
  var yaml = require('yaml');
8
10
  var zod = require('zod');
9
11
  var promises = require('fs/promises');
10
12
  var path = require('path');
11
13
 
14
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
15
  // src/dsl/diagnostics.ts
13
16
  var SEVERITY_RANK = /* @__PURE__ */ new Map([
14
17
  ["error", 0],
@@ -196,11 +199,12 @@ function renderArrow(edge) {
196
199
  height: box.height
197
200
  }),
198
201
  backgroundColor: "transparent",
202
+ strokeStyle: edge.style ?? "solid",
199
203
  points: relativePoints,
200
204
  startBinding: { elementId: `node:${edge.source.nodeId}`, focus: 0, gap: 0 },
201
205
  endBinding: { elementId: `node:${edge.target.nodeId}`, focus: 0, gap: 0 },
202
206
  startArrowhead: null,
203
- endArrowhead: "arrow"
207
+ endArrowhead: mapArrowhead(edge.arrowhead)
204
208
  };
205
209
  }
206
210
  function renderText(id, label, box, containerId, groupIds) {
@@ -278,6 +282,16 @@ function mapShape(shape) {
278
282
  return "cylinder";
279
283
  }
280
284
  }
285
+ function mapArrowhead(arrowhead) {
286
+ switch (arrowhead) {
287
+ case void 0:
288
+ return "arrow";
289
+ case "triangle":
290
+ return "triangle";
291
+ case "hollowTriangle":
292
+ return "triangle_outline";
293
+ }
294
+ }
281
295
  function createGroupMembership(groups) {
282
296
  const membership = /* @__PURE__ */ new Map();
283
297
  for (const group of groups) {
@@ -344,19 +358,28 @@ function exportSvg(diagram, options = {}) {
344
358
  `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
345
359
  ...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
346
360
  ` <rect class="background" x="${formatNumber(diagram.bounds.x)}" y="${formatNumber(diagram.bounds.y)}" width="${formatNumber(diagram.bounds.width)}" height="${formatNumber(diagram.bounds.height)}" fill="#ffffff"/>`,
361
+ ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame))],
362
+ ...(diagram.swimlanes ?? []).flatMap(
363
+ (swimlane) => renderSwimlane(swimlane)
364
+ ),
347
365
  ...diagram.groups.map((group) => indent(renderGroup2(group))),
348
366
  ...diagram.edges.flatMap((edge) => {
349
- const path = renderEdgePath(edge.points, edge.id);
367
+ const path = renderEdgePath(edge);
350
368
  if (path === void 0) {
351
369
  return [];
352
370
  }
353
- return [indent(path), indent(renderArrowhead(edge.points, edge.id))];
371
+ return [indent(path), indent(renderArrowhead(edge))];
354
372
  }),
355
373
  ...diagram.nodes.map((node) => indent(renderNode2(node))),
374
+ ...diagram.nodes.flatMap((node) => renderCompartments(node)),
375
+ ...diagram.nodes.flatMap((node) => renderPorts(node)),
356
376
  ...diagram.groups.flatMap(
357
377
  (group) => renderLabel(group.label, group.box, group)
358
378
  ),
359
- ...diagram.nodes.flatMap((node) => renderLabel(node.label, node.box, node)),
379
+ ...diagram.nodes.flatMap(
380
+ (node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node) : []
381
+ ),
382
+ ...diagram.edges.flatMap((edge) => renderEdgeLabel(edge)),
360
383
  "</svg>"
361
384
  ];
362
385
  return `${lines.join("\n")}
@@ -366,7 +389,9 @@ function renderGroup2(group) {
366
389
  return `<rect class="group" data-id="${escapeAttribute(group.id)}" x="${formatNumber(group.box.x)}" y="${formatNumber(group.box.y)}" width="${formatNumber(group.box.width)}" height="${formatNumber(group.box.height)}" fill="${GROUP_FILL}" stroke="${STROKE}" stroke-dasharray="6 4"/>`;
367
390
  }
368
391
  function renderNode2(node) {
369
- const common = `class="node node-${node.shape}" data-id="${escapeAttribute(node.id)}" fill="${NODE_FILL}" stroke="${STROKE}"`;
392
+ const fill = node.style?.fill ?? NODE_FILL;
393
+ const stroke = node.style?.stroke ?? STROKE;
394
+ const common = `class="node node-${node.shape}" data-id="${escapeAttribute(node.id)}" fill="${escapeAttribute(fill)}" stroke="${escapeAttribute(stroke)}"`;
370
395
  switch (node.shape) {
371
396
  case "rectangle":
372
397
  return renderRect(node.box, common);
@@ -382,16 +407,111 @@ function renderNode2(node) {
382
407
  return `<path ${common} d="${formatCylinderPath(node.box)}"/>`;
383
408
  }
384
409
  }
410
+ function renderFrame(frame) {
411
+ const stroke = frame.style?.stroke ?? "#6b7280";
412
+ const fill = frame.style?.fill ?? "transparent";
413
+ return [
414
+ `<g class="sysml-frame" data-kind="${escapeAttribute(frame.kind)}">`,
415
+ ` <rect class="sysml-frame-border" x="${formatNumber(frame.box.x)}" y="${formatNumber(frame.box.y)}" width="${formatNumber(frame.box.width)}" height="${formatNumber(frame.box.height)}" fill="${escapeAttribute(fill)}" stroke="${escapeAttribute(stroke)}"/>`,
416
+ ` <path class="sysml-title-tab" d="M ${formatNumber(frame.titleBox.x)} ${formatNumber(frame.titleBox.y + frame.titleBox.height)} L ${formatNumber(frame.titleBox.x)} ${formatNumber(frame.titleBox.y)} L ${formatNumber(frame.titleBox.x + frame.titleBox.width - 16)} ${formatNumber(frame.titleBox.y)} L ${formatNumber(frame.titleBox.x + frame.titleBox.width)} ${formatNumber(frame.titleBox.y + frame.titleBox.height)} Z" fill="#f3f4f6" stroke="${escapeAttribute(stroke)}"/>`,
417
+ ` <text class="sysml-title-tab-label" x="${formatNumber(frame.titleBox.x + 8)}" y="${formatNumber(frame.titleBox.y + frame.titleBox.height / 2)}" dominant-baseline="middle" font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(frame.titleTab)}</text>`,
418
+ "</g>"
419
+ ].join("\n");
420
+ }
421
+ function renderSwimlane(swimlane) {
422
+ if (swimlane.box === void 0) {
423
+ return [];
424
+ }
425
+ const lines = [
426
+ ` <g class="swimlane" data-id="${escapeAttribute(swimlane.id)}">`,
427
+ ` <rect class="swimlane-frame" x="${formatNumber(swimlane.box.x)}" y="${formatNumber(swimlane.box.y)}" width="${formatNumber(swimlane.box.width)}" height="${formatNumber(swimlane.box.height)}" fill="#ffffff" stroke="${STROKE}"/>`
428
+ ];
429
+ for (const lane of swimlane.lanes) {
430
+ if (lane.box === void 0) {
431
+ continue;
432
+ }
433
+ lines.push(
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
+ );
436
+ if (lane.label?.text !== void 0) {
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>`
439
+ );
440
+ }
441
+ }
442
+ lines.push(" </g>");
443
+ return lines;
444
+ }
445
+ function renderPorts(node) {
446
+ return (node.ports ?? []).flatMap((port) => [
447
+ ` <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)}"/>`,
448
+ ...port.label?.text === void 0 ? [] : [
449
+ ` <text class="port-label" data-for="${escapeAttribute(`${node.id}.${port.id}`)}" x="${formatNumber(portLabelX(port.anchor.x, port.side))}" y="${formatNumber(port.anchor.y - 8)}" text-anchor="${port.side === "left" ? "end" : "start"}" font-family="${FONT_FAMILY}" font-size="10" fill="#111827">${escapeXml(port.label.text)}</text>`
450
+ ]
451
+ ]);
452
+ }
453
+ function renderCompartments(node) {
454
+ const compartments2 = node.compartments;
455
+ if (compartments2 === void 0) {
456
+ return [];
457
+ }
458
+ const rows = [
459
+ ...compartments2.stereotype === void 0 ? [] : [{ className: "stereotype", text: compartments2.stereotype }],
460
+ {
461
+ className: "name",
462
+ text: compartments2.name ?? node.label?.text ?? node.id
463
+ },
464
+ ...(compartments2.properties ?? []).map((text) => ({
465
+ className: "properties",
466
+ text
467
+ })),
468
+ ...(compartments2.constraints ?? []).map((text) => ({
469
+ className: "constraints",
470
+ text
471
+ }))
472
+ ];
473
+ const lineHeight = 16;
474
+ const lines = [
475
+ ` <g class="compartment" data-for="${escapeAttribute(node.id)}">`
476
+ ];
477
+ for (let index = 0; index < rows.length; index += 1) {
478
+ const row = rows[index];
479
+ if (row === void 0) {
480
+ continue;
481
+ }
482
+ const y = node.box.y + 18 + index * lineHeight;
483
+ if (index > 1) {
484
+ lines.push(
485
+ ` <line class="compartment-separator" x1="${formatNumber(node.box.x)}" y1="${formatNumber(y - 12)}" x2="${formatNumber(node.box.x + node.box.width)}" y2="${formatNumber(y - 12)}" stroke="${STROKE}"/>`
486
+ );
487
+ }
488
+ lines.push(
489
+ ` <text class="compartment-${row.className}" x="${formatNumber(node.box.x + node.box.width / 2)}" y="${formatNumber(y)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="11" fill="#111827">${escapeXml(row.text)}</text>`
490
+ );
491
+ }
492
+ lines.push(" </g>");
493
+ return lines;
494
+ }
495
+ function portLabelX(x, side) {
496
+ if (side === "left") {
497
+ return x - 8;
498
+ }
499
+ if (side === "right") {
500
+ return x + 8;
501
+ }
502
+ return x + 8;
503
+ }
385
504
  function renderRect(box, attributes) {
386
505
  return `<rect ${attributes} x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}"/>`;
387
506
  }
388
507
  function renderLabel(label, box, item) {
389
508
  const labelLayout = item.labelLayout;
390
509
  if (labelLayout?.lines !== void 0 && labelLayout.lines.length > 0) {
510
+ const offset = { x: box.x, y: box.y };
391
511
  return [
392
512
  ` <text class="label" data-for="${escapeAttribute(item.id)}" font-family="${FONT_FAMILY}" font-size="${formatNumber(labelLayout.font.fontSize)}" fill="#111827">`,
393
513
  ...labelLayout.lines.map(
394
- (line) => ` <tspan x="${formatNumber(line.box.x)}" y="${formatNumber(line.baselineY)}">${escapeXml(line.text)}</tspan>`
514
+ (line) => ` <tspan x="${formatNumber(offset.x + line.box.x)}" y="${formatNumber(offset.y + line.baselineY)}">${escapeXml(line.text)}</tspan>`
395
515
  ),
396
516
  " </text>"
397
517
  ];
@@ -403,15 +523,88 @@ function renderLabel(label, box, item) {
403
523
  ` <text class="label" data-for="${escapeAttribute(item.id)}" x="${formatNumber(box.x + box.width / 2)}" y="${formatNumber(box.y + box.height / 2)}" text-anchor="middle" dominant-baseline="middle" font-family="${FONT_FAMILY}" font-size="14" fill="#111827">${escapeXml(label.text)}</text>`
404
524
  ];
405
525
  }
406
- function renderEdgePath(points, id) {
407
- if (points.length < 2) {
526
+ function renderEdgePath(edge) {
527
+ if (edge.points.length < 2) {
528
+ return void 0;
529
+ }
530
+ const dash = edge.style === "dashed" ? ' stroke-dasharray="6 4"' : "";
531
+ return `<path class="edge" data-id="${escapeAttribute(edge.id)}" d="${formatPath(pathPointsBeforeArrowhead(edge.points))}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"${dash}/>`;
532
+ }
533
+ function renderEdgeLabel(edge) {
534
+ if (edge.label?.text === void 0 || edge.points.length < 2) {
535
+ return [];
536
+ }
537
+ const placement = labelPlacementOnPolyline(edge.points);
538
+ if (placement === void 0) {
539
+ return [];
540
+ }
541
+ return [
542
+ ` <text class="edge-label" data-for="${escapeAttribute(edge.id)}" x="${formatNumber(placement.x)}" y="${formatNumber(placement.y)}" text-anchor="middle" dominant-baseline="middle" font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(edge.label.text)}</text>`
543
+ ];
544
+ }
545
+ function renderArrowhead(edge) {
546
+ const arrowhead = computeArrowhead(edge.points);
547
+ const fill = edge.arrowhead === "hollowTriangle" ? "none" : EDGE_STROKE;
548
+ return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(edge.id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${fill}" stroke="${EDGE_STROKE}"/>`;
549
+ }
550
+ function labelPlacementOnPolyline(points) {
551
+ const segments = nonZeroSegments(points);
552
+ const totalLength = segments.reduce(
553
+ (sum, segment) => sum + segment.length,
554
+ 0
555
+ );
556
+ if (totalLength <= 0) {
408
557
  return void 0;
409
558
  }
410
- return `<path class="edge" data-id="${escapeAttribute(id)}" d="${formatPath(points)}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"/>`;
559
+ let remaining = totalLength / 2;
560
+ for (const segment of segments) {
561
+ if (remaining <= segment.length) {
562
+ const ratio = remaining / segment.length;
563
+ const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
564
+ const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
565
+ const offset2 = labelOffset(segment);
566
+ return { x: x + offset2.x, y: y + offset2.y };
567
+ }
568
+ remaining -= segment.length;
569
+ }
570
+ const last = segments.at(-1);
571
+ if (last === void 0) {
572
+ return void 0;
573
+ }
574
+ const offset = labelOffset(last);
575
+ return { x: last.end.x + offset.x, y: last.end.y + offset.y };
576
+ }
577
+ function nonZeroSegments(points) {
578
+ const segments = [];
579
+ for (let index = 0; index < points.length - 1; index += 1) {
580
+ const start = points[index];
581
+ const end = points[index + 1];
582
+ if (start === void 0 || end === void 0) {
583
+ continue;
584
+ }
585
+ const length = Math.hypot(end.x - start.x, end.y - start.y);
586
+ if (length > 0) {
587
+ segments.push({ start, end, length });
588
+ }
589
+ }
590
+ return segments;
411
591
  }
412
- function renderArrowhead(points, id) {
592
+ function labelOffset(segment) {
593
+ const offset = 10;
594
+ const dx = segment.end.x - segment.start.x;
595
+ const dy = segment.end.y - segment.start.y;
596
+ return {
597
+ x: -dy / segment.length * offset,
598
+ y: dx / segment.length * offset
599
+ };
600
+ }
601
+ function pathPointsBeforeArrowhead(points) {
413
602
  const arrowhead = computeArrowhead(points);
414
- return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${EDGE_STROKE}" stroke="${EDGE_STROKE}"/>`;
603
+ const base = {
604
+ x: (arrowhead.left.x + arrowhead.right.x) / 2,
605
+ y: (arrowhead.left.y + arrowhead.right.y) / 2
606
+ };
607
+ return [...points.slice(0, -1), base];
415
608
  }
416
609
  function shapePoints(shape, box) {
417
610
  const left = box.x;
@@ -1296,20 +1489,33 @@ function isValidDimension(value) {
1296
1489
  // src/routing/routes.ts
1297
1490
  function routeEdge(input) {
1298
1491
  const diagnostics = [];
1492
+ const defaultAnchors = defaultAnchorsForGeometry(
1493
+ input.source.box,
1494
+ input.target.box,
1495
+ input.direction
1496
+ );
1299
1497
  const source = getEdgePort(
1300
1498
  input.source,
1301
1499
  input.target.center,
1302
- input.sourceAnchor
1500
+ input.sourceAnchor ?? defaultAnchors.sourceAnchor
1303
1501
  );
1304
1502
  const target = getEdgePort(
1305
1503
  input.target,
1306
1504
  input.source.center,
1307
- input.targetAnchor
1505
+ input.targetAnchor ?? defaultAnchors.targetAnchor
1308
1506
  );
1309
1507
  if ((input.kind ?? "orthogonal") === "straight") {
1310
1508
  return { points: simplifyRoute([source, target]), diagnostics };
1311
1509
  }
1312
1510
  const candidates = orthogonalCandidates(source, target, input.direction);
1511
+ candidates.push(
1512
+ ...expandedObstacleCandidates(
1513
+ source,
1514
+ target,
1515
+ input.direction,
1516
+ input.obstacles ?? []
1517
+ )
1518
+ );
1313
1519
  for (const candidate of candidates) {
1314
1520
  if (!routeIntersectsObstacles(candidate, input.obstacles ?? [])) {
1315
1521
  return { points: simplifyRoute(candidate), diagnostics };
@@ -1348,27 +1554,113 @@ function simplifyRoute(points) {
1348
1554
  function orthogonalCandidates(source, target, direction) {
1349
1555
  const midpointX = (source.x + target.x) / 2;
1350
1556
  const midpointY = (source.y + target.y) / 2;
1351
- const candidates = [
1352
- [source, { x: target.x, y: source.y }, target],
1353
- [source, { x: source.x, y: target.y }, target]
1354
- ];
1557
+ const candidates = [];
1355
1558
  if (direction === "TB" || direction === "BT") {
1356
1559
  candidates.push([
1357
1560
  source,
1358
- { x: midpointX, y: source.y },
1359
- { x: midpointX, y: target.y },
1561
+ { x: source.x, y: midpointY },
1562
+ { x: target.x, y: midpointY },
1360
1563
  target
1361
1564
  ]);
1362
1565
  } else {
1363
1566
  candidates.push([
1364
1567
  source,
1365
- { x: source.x, y: midpointY },
1366
- { x: target.x, y: midpointY },
1568
+ { x: midpointX, y: source.y },
1569
+ { x: midpointX, y: target.y },
1367
1570
  target
1368
1571
  ]);
1369
1572
  }
1573
+ candidates.push(
1574
+ [source, { x: target.x, y: source.y }, target],
1575
+ [source, { x: source.x, y: target.y }, target]
1576
+ );
1577
+ return candidates;
1578
+ }
1579
+ function defaultSourceAnchor(direction) {
1580
+ switch (direction) {
1581
+ case "LR":
1582
+ return "right";
1583
+ case "RL":
1584
+ return "left";
1585
+ case "TB":
1586
+ return "bottom";
1587
+ case "BT":
1588
+ return "top";
1589
+ }
1590
+ }
1591
+ function defaultAnchorsForGeometry(source, target, direction) {
1592
+ const dx = target.x + target.width / 2 - (source.x + source.width / 2);
1593
+ const dy = target.y + target.height / 2 - (source.y + source.height / 2);
1594
+ if (Math.abs(dy) > Math.abs(dx)) {
1595
+ return dy >= 0 ? { sourceAnchor: "bottom", targetAnchor: "top" } : { sourceAnchor: "top", targetAnchor: "bottom" };
1596
+ }
1597
+ if (Math.abs(dx) > 0) {
1598
+ return dx >= 0 ? { sourceAnchor: "right", targetAnchor: "left" } : { sourceAnchor: "left", targetAnchor: "right" };
1599
+ }
1600
+ return {
1601
+ sourceAnchor: defaultSourceAnchor(direction),
1602
+ targetAnchor: defaultTargetAnchor(direction)
1603
+ };
1604
+ }
1605
+ function defaultTargetAnchor(direction) {
1606
+ switch (direction) {
1607
+ case "LR":
1608
+ return "left";
1609
+ case "RL":
1610
+ return "right";
1611
+ case "TB":
1612
+ return "top";
1613
+ case "BT":
1614
+ return "bottom";
1615
+ }
1616
+ }
1617
+ function expandedObstacleCandidates(source, target, direction, obstacles) {
1618
+ if (obstacles.length === 0) {
1619
+ return [];
1620
+ }
1621
+ const margin = 16;
1622
+ const candidates = [];
1623
+ if (direction === "TB" || direction === "BT") {
1624
+ const lanes = sortedUniqueLanes(
1625
+ obstacles.flatMap((obstacle) => [
1626
+ obstacle.x - margin,
1627
+ obstacle.x + obstacle.width + margin
1628
+ ]),
1629
+ (source.x + target.x) / 2
1630
+ );
1631
+ for (const laneX of lanes) {
1632
+ candidates.push([
1633
+ source,
1634
+ { x: laneX, y: source.y },
1635
+ { x: laneX, y: target.y },
1636
+ target
1637
+ ]);
1638
+ }
1639
+ } else {
1640
+ const lanes = sortedUniqueLanes(
1641
+ obstacles.flatMap((obstacle) => [
1642
+ obstacle.y - margin,
1643
+ obstacle.y + obstacle.height + margin
1644
+ ]),
1645
+ (source.y + target.y) / 2
1646
+ );
1647
+ for (const laneY of lanes) {
1648
+ candidates.push([
1649
+ source,
1650
+ { x: source.x, y: laneY },
1651
+ { x: target.x, y: laneY },
1652
+ target
1653
+ ]);
1654
+ }
1655
+ }
1370
1656
  return candidates;
1371
1657
  }
1658
+ function sortedUniqueLanes(lanes, midpoint) {
1659
+ return [...new Set(lanes)].filter((lane) => Number.isFinite(lane)).sort((left, right) => {
1660
+ const distance = Math.abs(left - midpoint) - Math.abs(right - midpoint);
1661
+ return distance === 0 ? left - right : distance;
1662
+ });
1663
+ }
1372
1664
  function routeIntersectsObstacles(points, obstacles) {
1373
1665
  for (let index = 0; index < points.length - 1; index += 1) {
1374
1666
  const a = points[index];
@@ -1447,12 +1739,17 @@ function solveDiagram(diagram, options = {}) {
1447
1739
  options,
1448
1740
  diagnostics
1449
1741
  );
1742
+ const coordinatedSwimlanes = coordinateSwimlanes(
1743
+ diagram.swimlanes ?? [],
1744
+ constrained.boxes
1745
+ );
1450
1746
  const groupBoxes = new Map(
1451
1747
  coordinatedGroups.map((group) => [group.id, group.box])
1452
1748
  );
1453
1749
  const coordinatedEdges = coordinateEdges(
1454
1750
  edges,
1455
1751
  nodeGeometryById,
1752
+ coordinatedNodes,
1456
1753
  [...nodeGeometryById.values()].map((geometry) => geometry.obstacleBox),
1457
1754
  diagram.direction,
1458
1755
  options,
@@ -1460,8 +1757,18 @@ function solveDiagram(diagram, options = {}) {
1460
1757
  );
1461
1758
  const allBoxes = [
1462
1759
  ...coordinatedNodes.map((node) => node.box),
1463
- ...groupBoxes.values()
1760
+ ...coordinatedNodes.flatMap(
1761
+ (node) => (node.ports ?? []).flatMap(
1762
+ (port) => port.label === void 0 ? [port.box] : [port.box, portLabelBox(port)]
1763
+ )
1764
+ ),
1765
+ ...groupBoxes.values(),
1766
+ ...coordinatedSwimlanes.flatMap(
1767
+ (swimlane) => swimlane.box === void 0 ? [] : [swimlane.box]
1768
+ )
1464
1769
  ];
1770
+ const contentBounds = allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes);
1771
+ const frame = diagram.frame === void 0 ? void 0 : coordinateFrame(diagram.frame, contentBounds);
1465
1772
  return {
1466
1773
  id: diagram.id,
1467
1774
  ...diagram.title === void 0 ? {} : { title: diagram.title },
@@ -1469,8 +1776,10 @@ function solveDiagram(diagram, options = {}) {
1469
1776
  nodes: coordinatedNodes,
1470
1777
  edges: coordinatedEdges,
1471
1778
  groups: coordinatedGroups,
1779
+ ...coordinatedSwimlanes.length === 0 ? {} : { swimlanes: coordinatedSwimlanes },
1472
1780
  diagnostics,
1473
- bounds: allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes),
1781
+ bounds: frame === void 0 ? contentBounds : unionBoxes([contentBounds, frame.box, frame.titleBox]),
1782
+ ...frame === void 0 ? {} : { frame },
1474
1783
  ...diagram.metadata === void 0 ? {} : { metadata: diagram.metadata }
1475
1784
  };
1476
1785
  }
@@ -1496,6 +1805,9 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
1496
1805
  coordinated.push({
1497
1806
  id: node.id,
1498
1807
  ...node.label === void 0 ? {} : { label: node.label },
1808
+ ...node.style === void 0 ? {} : { style: node.style },
1809
+ ...node.ports === void 0 ? {} : { ports: coordinatePorts(node, box, options.portShifting) },
1810
+ ...node.compartments === void 0 ? {} : { compartments: node.compartments },
1499
1811
  ...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
1500
1812
  shape: node.shape,
1501
1813
  ...node.metadata === void 0 ? {} : { metadata: node.metadata },
@@ -1506,6 +1818,142 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
1506
1818
  }
1507
1819
  return coordinated;
1508
1820
  }
1821
+ function coordinatePorts(node, nodeBox, portShifting) {
1822
+ const portsBySide = /* @__PURE__ */ new Map();
1823
+ for (const port of node.ports ?? []) {
1824
+ const ports = portsBySide.get(port.side) ?? [];
1825
+ ports.push(port);
1826
+ portsBySide.set(port.side, ports);
1827
+ }
1828
+ const coordinated = [];
1829
+ for (const [side, ports] of portsBySide) {
1830
+ const sorted = [...ports ?? []].sort((a, b) => {
1831
+ const order = (a.order ?? 0) - (b.order ?? 0);
1832
+ return order === 0 ? a.id.localeCompare(b.id) : order;
1833
+ });
1834
+ for (let index = 0; index < sorted.length; index += 1) {
1835
+ const port = sorted[index];
1836
+ if (port === void 0) {
1837
+ continue;
1838
+ }
1839
+ const anchor = portAnchor(
1840
+ nodeBox,
1841
+ side,
1842
+ index,
1843
+ sorted.length,
1844
+ portShifting
1845
+ );
1846
+ const box = portBox(anchor);
1847
+ coordinated.push({ ...port, box, anchor });
1848
+ }
1849
+ }
1850
+ return coordinated.sort((a, b) => a.id.localeCompare(b.id));
1851
+ }
1852
+ function portAnchor(nodeBox, side, index, count, portShifting) {
1853
+ const shiftingEnabled = portShifting?.enabled ?? true;
1854
+ const spacing = portShifting?.spacing ?? 24;
1855
+ const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
1856
+ switch (side) {
1857
+ case "left":
1858
+ return {
1859
+ x: nodeBox.x,
1860
+ y: nodeBox.y + nodeBox.height / 2 + centeredOffset
1861
+ };
1862
+ case "right":
1863
+ return {
1864
+ x: nodeBox.x + nodeBox.width,
1865
+ y: nodeBox.y + nodeBox.height / 2 + centeredOffset
1866
+ };
1867
+ case "top":
1868
+ return {
1869
+ x: nodeBox.x + nodeBox.width / 2 + centeredOffset,
1870
+ y: nodeBox.y
1871
+ };
1872
+ case "bottom":
1873
+ return {
1874
+ x: nodeBox.x + nodeBox.width / 2 + centeredOffset,
1875
+ y: nodeBox.y + nodeBox.height
1876
+ };
1877
+ }
1878
+ }
1879
+ function portBox(anchor) {
1880
+ const size = 10;
1881
+ return {
1882
+ x: anchor.x - size / 2,
1883
+ y: anchor.y - size / 2,
1884
+ width: size,
1885
+ height: size
1886
+ };
1887
+ }
1888
+ function portLabelBox(port) {
1889
+ const textWidth = Math.max(0, (port.label?.text.length ?? 0) * 6);
1890
+ const height = 12;
1891
+ const gap = 8;
1892
+ const x = port.side === "left" ? port.anchor.x - gap - textWidth : port.anchor.x + gap;
1893
+ return {
1894
+ x,
1895
+ y: port.anchor.y - 8 - height,
1896
+ width: textWidth,
1897
+ height
1898
+ };
1899
+ }
1900
+ function coordinateSwimlanes(swimlanes, nodeBoxes) {
1901
+ const titleSize = 28;
1902
+ const padding = 16;
1903
+ return swimlanes.map((swimlane) => {
1904
+ const laneBoxes = swimlane.lanes.flatMap((lane) => {
1905
+ const childBoxes = lane.children.map((child) => nodeBoxes.get(child)).filter((box) => box !== void 0);
1906
+ return childBoxes.length === 0 ? [] : [unionBoxes(childBoxes)];
1907
+ });
1908
+ const laneUnion = laneBoxes.length === 0 ? { x: 0, y: 0, width: 120, height: 80 } : unionBoxes(laneBoxes);
1909
+ const outer = expand(laneUnion, padding, titleSize);
1910
+ const laneCount = Math.max(1, swimlane.lanes.length);
1911
+ const lanes = swimlane.lanes.map((lane, index) => {
1912
+ const box = swimlane.orientation === "vertical" ? {
1913
+ x: outer.x + outer.width / laneCount * index,
1914
+ y: outer.y,
1915
+ width: outer.width / laneCount,
1916
+ height: outer.height
1917
+ } : {
1918
+ x: outer.x,
1919
+ y: outer.y + outer.height / laneCount * index,
1920
+ width: outer.width,
1921
+ height: outer.height / laneCount
1922
+ };
1923
+ return { ...lane, box };
1924
+ });
1925
+ return { ...swimlane, lanes, box: outer };
1926
+ });
1927
+ }
1928
+ function coordinateFrame(frame, contentBounds) {
1929
+ const padding = 32;
1930
+ const titleHeight = 28;
1931
+ const titleWidth = Math.max(180, frame.titleTab.length * 7);
1932
+ const box = {
1933
+ x: contentBounds.x - padding,
1934
+ y: contentBounds.y - padding - titleHeight,
1935
+ width: contentBounds.width + padding * 2,
1936
+ height: contentBounds.height + padding * 2 + titleHeight
1937
+ };
1938
+ return {
1939
+ ...frame,
1940
+ box,
1941
+ titleBox: {
1942
+ x: box.x,
1943
+ y: box.y,
1944
+ width: Math.min(titleWidth, box.width * 0.8),
1945
+ height: titleHeight
1946
+ }
1947
+ };
1948
+ }
1949
+ function expand(box, padding, titleSize) {
1950
+ return {
1951
+ x: box.x - padding,
1952
+ y: box.y - padding - titleSize,
1953
+ width: box.width + padding * 2,
1954
+ height: box.height + padding * 2 + titleSize
1955
+ };
1956
+ }
1509
1957
  function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
1510
1958
  const coordinated = [];
1511
1959
  const groupBoxes = /* @__PURE__ */ new Map();
@@ -1554,8 +2002,11 @@ function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
1554
2002
  }
1555
2003
  return coordinated;
1556
2004
  }
1557
- function coordinateEdges(edges, nodes, obstacles, direction, options, diagnostics) {
2005
+ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, options, diagnostics) {
1558
2006
  const coordinated = [];
2007
+ const coordinatedNodeById = new Map(
2008
+ coordinatedNodes.map((node) => [node.id, node])
2009
+ );
1559
2010
  for (const edge of edges) {
1560
2011
  const source = nodes.get(edge.source.nodeId);
1561
2012
  const target = nodes.get(edge.target.nodeId);
@@ -1573,11 +2024,13 @@ function coordinateEdges(edges, nodes, obstacles, direction, options, diagnostic
1573
2024
  });
1574
2025
  continue;
1575
2026
  }
2027
+ const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
2028
+ const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
1576
2029
  const route = routeEdge({
1577
2030
  kind: options.routeKind ?? "orthogonal",
1578
2031
  direction,
1579
- source,
1580
- target,
2032
+ source: portGeometry(source, sourcePort),
2033
+ target: portGeometry(target, targetPort),
1581
2034
  ...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
1582
2035
  ...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
1583
2036
  obstacles: obstacles.filter(
@@ -1597,6 +2050,21 @@ function coordinateEdges(edges, nodes, obstacles, direction, options, diagnostic
1597
2050
  }
1598
2051
  return coordinated;
1599
2052
  }
2053
+ function portGeometry(nodeGeometry, port) {
2054
+ if (port === void 0) {
2055
+ return nodeGeometry;
2056
+ }
2057
+ return {
2058
+ ...nodeGeometry,
2059
+ box: port.box,
2060
+ center: port.anchor,
2061
+ anchors: nodeGeometry.anchors.map((anchor) => ({
2062
+ name: anchor.name,
2063
+ point: port.anchor
2064
+ })),
2065
+ obstacleBox: port.box
2066
+ };
2067
+ }
1600
2068
  function stableById(items) {
1601
2069
  return [...items].sort((a, b) => a.id.localeCompare(b.id));
1602
2070
  }
@@ -1626,34 +2094,34 @@ function assertFiniteNonNegative(value, label) {
1626
2094
  throw new TypeError(`${label} must be a finite non-negative width`);
1627
2095
  }
1628
2096
  }
1629
- function validateTextStyle(style) {
1630
- assertFinitePositive(style.fontSize, "fontSize");
1631
- if (style.lineHeight !== void 0) {
1632
- assertFinitePositive(style.lineHeight, "lineHeight");
2097
+ function validateTextStyle(style2) {
2098
+ assertFinitePositive(style2.fontSize, "fontSize");
2099
+ if (style2.lineHeight !== void 0) {
2100
+ assertFinitePositive(style2.lineHeight, "lineHeight");
1633
2101
  }
1634
- if (style.letterSpacing !== void 0 && !Number.isFinite(style.letterSpacing)) {
2102
+ if (style2.letterSpacing !== void 0 && !Number.isFinite(style2.letterSpacing)) {
1635
2103
  throw new TypeError("letterSpacing must be finite");
1636
2104
  }
1637
2105
  }
1638
- function resolveLineHeight(style) {
1639
- validateTextStyle(style);
1640
- return style.lineHeight ?? style.fontSize * 1.2;
2106
+ function resolveLineHeight(style2) {
2107
+ validateTextStyle(style2);
2108
+ return style2.lineHeight ?? style2.fontSize * 1.2;
1641
2109
  }
1642
- function toCanvasFont(style) {
1643
- validateTextStyle(style);
1644
- const fontStyle = style.fontStyle === "italic" ? "italic " : "";
1645
- const fontWeight = style.fontWeight ?? 400;
1646
- return `${fontStyle}${fontWeight} ${style.fontSize}px ${style.fontFamily}`;
2110
+ function toCanvasFont(style2) {
2111
+ validateTextStyle(style2);
2112
+ const fontStyle = style2.fontStyle === "italic" ? "italic " : "";
2113
+ const fontWeight = style2.fontWeight ?? 400;
2114
+ return `${fontStyle}${fontWeight} ${style2.fontSize}px ${style2.fontFamily}`;
1647
2115
  }
1648
2116
 
1649
2117
  // src/text/fallback.ts
1650
2118
  var DeterministicTextMeasurer = class {
1651
- prepare(text, style) {
1652
- validateTextStyle(style);
2119
+ prepare(text, style2) {
2120
+ validateTextStyle(style2);
1653
2121
  return {
1654
2122
  text,
1655
- font: toCanvasFont(style),
1656
- style: { ...style },
2123
+ font: toCanvasFont(style2),
2124
+ style: { ...style2 },
1657
2125
  backend: "deterministic"
1658
2126
  };
1659
2127
  }
@@ -1712,9 +2180,9 @@ var DeterministicTextMeasurer = class {
1712
2180
  return output;
1713
2181
  }
1714
2182
  };
1715
- function getCharacterWidth(style) {
1716
- const letterSpacing = style.letterSpacing ?? 0;
1717
- return Math.max(0, style.fontSize * 0.6 + letterSpacing);
2183
+ function getCharacterWidth(style2) {
2184
+ const letterSpacing = style2.letterSpacing ?? 0;
2185
+ return Math.max(0, style2.fontSize * 0.6 + letterSpacing);
1718
2186
  }
1719
2187
  function createLine(text, width, segmentIndex, start, end) {
1720
2188
  return {
@@ -1735,6 +2203,108 @@ function assertFinitePositiveLineHeight(lineHeight) {
1735
2203
  throw new TypeError("lineHeight must be finite and positive");
1736
2204
  }
1737
2205
  }
2206
+ var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
2207
+ function installNodeCanvasRuntime(loadNodeCanvasModule = loadDefaultNodeCanvasModule) {
2208
+ if (typeof globalThis.OffscreenCanvas === "function") {
2209
+ return true;
2210
+ }
2211
+ try {
2212
+ const canvasModule = loadNodeCanvasModule();
2213
+ const { createCanvas } = canvasModule;
2214
+ const NodeOffscreenCanvas = class {
2215
+ canvas;
2216
+ constructor(width, height) {
2217
+ this.canvas = createCanvas(width, height);
2218
+ }
2219
+ getContext(contextId) {
2220
+ return contextId === "2d" ? this.canvas.getContext("2d") : null;
2221
+ }
2222
+ };
2223
+ globalThis.OffscreenCanvas = NodeOffscreenCanvas;
2224
+ return true;
2225
+ } catch {
2226
+ return false;
2227
+ }
2228
+ }
2229
+ function loadDefaultNodeCanvasModule() {
2230
+ return require2("@napi-rs/canvas");
2231
+ }
2232
+ var RUNTIME_UNAVAILABLE = "text.pretext.runtime-unavailable";
2233
+ function isPretextRuntimeAvailable() {
2234
+ return typeof Intl.Segmenter === "function" && typeof globalThis.OffscreenCanvas === "function";
2235
+ }
2236
+ var PretextTextMeasurer = class {
2237
+ prepare(text, style2) {
2238
+ if (!isPretextRuntimeAvailable()) {
2239
+ throw new TypeError(RUNTIME_UNAVAILABLE);
2240
+ }
2241
+ validateTextStyle(style2);
2242
+ const font = toCanvasFont(style2);
2243
+ const options = {
2244
+ ...style2.whiteSpace === void 0 ? {} : { whiteSpace: style2.whiteSpace },
2245
+ ...style2.wordBreak === void 0 ? {} : { wordBreak: style2.wordBreak },
2246
+ ...style2.letterSpacing === void 0 ? {} : { letterSpacing: style2.letterSpacing }
2247
+ };
2248
+ const prepared = pretext.prepareWithSegments(text, font, options);
2249
+ return {
2250
+ text,
2251
+ font,
2252
+ style: { ...style2 },
2253
+ backend: "pretext",
2254
+ pretextPrepared: prepared
2255
+ };
2256
+ }
2257
+ layout(prepared, maxWidth, lineHeight = resolveLineHeight(prepared.style)) {
2258
+ assertFiniteNonNegative(maxWidth, "maxWidth");
2259
+ if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
2260
+ throw new TypeError("lineHeight must be finite and positive");
2261
+ }
2262
+ const result = pretext.layoutWithLines(
2263
+ toInternalPrepared(prepared),
2264
+ maxWidth,
2265
+ lineHeight
2266
+ );
2267
+ const width = result.lines.reduce(
2268
+ (current, line) => Math.max(current, line.width),
2269
+ 0
2270
+ );
2271
+ return {
2272
+ width,
2273
+ height: result.height,
2274
+ lineHeight,
2275
+ lineCount: result.lineCount,
2276
+ lines: result.lines.map((line) => ({
2277
+ text: line.text,
2278
+ width: line.width,
2279
+ start: {
2280
+ segmentIndex: line.start.segmentIndex,
2281
+ graphemeIndex: line.start.graphemeIndex
2282
+ },
2283
+ end: {
2284
+ segmentIndex: line.end.segmentIndex,
2285
+ graphemeIndex: line.end.graphemeIndex
2286
+ }
2287
+ })),
2288
+ diagnostics: []
2289
+ };
2290
+ }
2291
+ naturalWidth(prepared) {
2292
+ return pretext.measureNaturalWidth(toInternalPrepared(prepared));
2293
+ }
2294
+ };
2295
+ function toInternalPrepared(prepared) {
2296
+ if (prepared.backend !== "pretext" || !("pretextPrepared" in prepared)) {
2297
+ throw new TypeError("prepared text was not created by PretextTextMeasurer");
2298
+ }
2299
+ return prepared.pretextPrepared;
2300
+ }
2301
+
2302
+ // src/text/default.ts
2303
+ function createDefaultTextMeasurer(options = {}) {
2304
+ const installRuntime = options.installNodeCanvasRuntime ?? installNodeCanvasRuntime;
2305
+ installRuntime();
2306
+ return isPretextRuntimeAvailable() ? new PretextTextMeasurer() : new DeterministicTextMeasurer();
2307
+ }
1738
2308
 
1739
2309
  // src/labels/fit.ts
1740
2310
  function fitLabel(text, options, measurer) {
@@ -1793,6 +2363,7 @@ function computeLabelLayout(text, options, measurer) {
1793
2363
  fittedSize,
1794
2364
  padding,
1795
2365
  font: { ...options.font },
2366
+ textBackend: prepared.backend,
1796
2367
  lineHeight,
1797
2368
  lines: buildLines(textLayout, contentBox2, lineHeight),
1798
2369
  overflow,
@@ -1887,8 +2458,9 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1887
2458
  ...outputResult(dsl)
1888
2459
  };
1889
2460
  }
1890
- const measurer = options.textMeasurer ?? new DeterministicTextMeasurer();
2461
+ const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
1891
2462
  const routeKind = dsl.routing?.kind ?? "orthogonal";
2463
+ const portShifting = normalizePortShifting(dsl.routing?.portShifting);
1892
2464
  const diagram = {
1893
2465
  id: options.id ?? dsl.id ?? "diagram",
1894
2466
  ...dsl.title === void 0 ? {} : { title: dsl.title },
@@ -1896,9 +2468,14 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1896
2468
  nodes: normalizeNodes(dsl, measurer),
1897
2469
  edges: normalizeEdges(dsl),
1898
2470
  groups: normalizeGroups(dsl, measurer),
2471
+ swimlanes: normalizeSwimlanes(dsl),
1899
2472
  constraints: normalizeConstraints(dsl),
1900
2473
  diagnostics: [],
1901
- metadata: { routeKind }
2474
+ ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
2475
+ metadata: {
2476
+ routeKind,
2477
+ ...portShifting === void 0 ? {} : { portShifting }
2478
+ }
1902
2479
  };
1903
2480
  return {
1904
2481
  diagram,
@@ -1906,6 +2483,15 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1906
2483
  ...outputResult(dsl)
1907
2484
  };
1908
2485
  }
2486
+ function normalizePortShifting(portShifting) {
2487
+ if (portShifting === void 0) {
2488
+ return void 0;
2489
+ }
2490
+ return {
2491
+ ...portShifting.enabled === void 0 ? {} : { enabled: portShifting.enabled },
2492
+ ...portShifting.spacing === void 0 ? {} : { spacing: portShifting.spacing }
2493
+ };
2494
+ }
1909
2495
  function outputResult(dsl) {
1910
2496
  return dsl.output?.format === void 0 ? {} : { output: { format: dsl.output.format } };
1911
2497
  }
@@ -1915,15 +2501,24 @@ function normalizeNodes(dsl, measurer) {
1915
2501
  const label = toLabel(node?.label);
1916
2502
  const labelLayout = label === void 0 ? void 0 : fitDslLabel(label, measurer);
1917
2503
  const fittedSize = labelLayout?.fittedSize;
2504
+ const nodeCompartments = node?.compartments === void 0 ? void 0 : compartments(node.compartments);
2505
+ const compartmentWidth = nodeCompartments === void 0 ? 0 : compartmentNaturalWidth(id, label, nodeCompartments, measurer);
1918
2506
  return {
1919
2507
  id,
1920
2508
  ...label === void 0 ? {} : { label },
1921
2509
  shape: node?.shape ?? "rectangle",
1922
2510
  ...node?.position === void 0 ? {} : { position: point(node.position) },
2511
+ ...node?.style === void 0 ? {} : { style: style(node.style) },
2512
+ ...node?.ports === void 0 ? {} : { ports: normalizePorts(node.ports) },
2513
+ ...nodeCompartments === void 0 ? {} : { compartments: nodeCompartments },
1923
2514
  size: {
1924
- width: Math.max(DEFAULT_NODE_MIN_SIZE.width, fittedSize?.width ?? 0),
2515
+ width: Math.max(
2516
+ DEFAULT_NODE_MIN_SIZE.width,
2517
+ fittedSize?.width ?? 0,
2518
+ compartmentWidth
2519
+ ),
1925
2520
  height: Math.max(
1926
- DEFAULT_NODE_MIN_SIZE.height,
2521
+ nodeCompartments === void 0 ? DEFAULT_NODE_MIN_SIZE.height : compartmentHeight(nodeCompartments),
1927
2522
  fittedSize?.height ?? 0
1928
2523
  )
1929
2524
  },
@@ -1932,11 +2527,42 @@ function normalizeNodes(dsl, measurer) {
1932
2527
  };
1933
2528
  });
1934
2529
  }
2530
+ function compartmentHeight(value) {
2531
+ const rowCount = (value.stereotype === void 0 ? 0 : 1) + 1 + (value.properties?.length ?? 0) + (value.constraints?.length ?? 0);
2532
+ const rowHeight = 16;
2533
+ const verticalPadding = 20;
2534
+ return Math.max(
2535
+ DEFAULT_NODE_MIN_SIZE.height,
2536
+ rowCount * rowHeight + verticalPadding
2537
+ );
2538
+ }
2539
+ function compartmentNaturalWidth(id, label, value, measurer) {
2540
+ const rows = compartmentRows(id, label, value);
2541
+ const maxRowWidth = rows.reduce((width, row) => {
2542
+ const prepared = measurer.prepare(row, DEFAULT_FONT);
2543
+ return Math.max(width, measurer.naturalWidth(prepared));
2544
+ }, 0);
2545
+ return Math.ceil(
2546
+ maxRowWidth + DEFAULT_NODE_PADDING.left + DEFAULT_NODE_PADDING.right
2547
+ );
2548
+ }
2549
+ function compartmentRows(id, label, value) {
2550
+ return [
2551
+ ...value.stereotype === void 0 ? [] : [value.stereotype],
2552
+ value.name ?? label?.text ?? id,
2553
+ ...value.properties ?? [],
2554
+ ...value.constraints ?? []
2555
+ ];
2556
+ }
1935
2557
  function normalizeEdges(dsl) {
1936
2558
  const counts = /* @__PURE__ */ new Map();
1937
2559
  return (dsl.edges ?? []).map((edge) => {
1938
- const sourceId = typeof edge === "string" ? "" : edge.sourceId ?? edge.source ?? "";
1939
- const targetId = typeof edge === "string" ? "" : edge.targetId ?? edge.target ?? "";
2560
+ const source = typeof edge === "string" ? void 0 : edge.source;
2561
+ const target = typeof edge === "string" ? void 0 : edge.target;
2562
+ const sourceId = typeof edge === "string" ? "" : edge.sourceId ?? endpointNodeId(source) ?? "";
2563
+ const targetId = typeof edge === "string" ? "" : edge.targetId ?? endpointNodeId(target) ?? "";
2564
+ const sourceEndpoint = typeof edge === "string" ? { nodeId: sourceId } : endpoint(source, edge.sourceId);
2565
+ const targetEndpoint = typeof edge === "string" ? { nodeId: targetId } : endpoint(target, edge.targetId);
1940
2566
  const baseId = `${sourceId}-${targetId}`;
1941
2567
  const count = counts.get(baseId) ?? 0;
1942
2568
  counts.set(baseId, count + 1);
@@ -1944,9 +2570,96 @@ function normalizeEdges(dsl) {
1944
2570
  const label = typeof edge === "string" ? void 0 : toLabel(edge.label);
1945
2571
  return {
1946
2572
  id,
1947
- source: { nodeId: sourceId },
1948
- target: { nodeId: targetId },
1949
- ...label === void 0 ? {} : { label }
2573
+ source: sourceEndpoint,
2574
+ target: targetEndpoint,
2575
+ ...label === void 0 ? {} : { label },
2576
+ ...typeof edge === "string" || edge.style === void 0 ? {} : { style: edge.style },
2577
+ ...typeof edge === "string" || edge.arrowhead === void 0 ? {} : { arrowhead: edge.arrowhead }
2578
+ };
2579
+ });
2580
+ }
2581
+ function normalizePorts(ports) {
2582
+ return Object.keys(ports ?? {}).sort().map((id) => {
2583
+ const port = ports?.[id];
2584
+ const label = toLabel(port?.label);
2585
+ return {
2586
+ id,
2587
+ ...label === void 0 ? {} : { label },
2588
+ side: port?.side ?? "right",
2589
+ kind: port?.kind ?? "proxy",
2590
+ ...port?.order === void 0 ? {} : { order: port.order },
2591
+ ...port?.style === void 0 ? {} : { style: style(port.style) }
2592
+ };
2593
+ });
2594
+ }
2595
+ function endpoint(value, nodeIdOverride) {
2596
+ if (nodeIdOverride !== void 0) {
2597
+ return {
2598
+ nodeId: nodeIdOverride,
2599
+ ...typeof value === "object" && value.node === nodeIdOverride && value.port !== void 0 ? { portId: value.port } : {}
2600
+ };
2601
+ }
2602
+ if (value === void 0) {
2603
+ return { nodeId: "" };
2604
+ }
2605
+ if (typeof value === "string") {
2606
+ return { nodeId: value };
2607
+ }
2608
+ return {
2609
+ nodeId: value.node,
2610
+ ...value.port === void 0 ? {} : { portId: value.port }
2611
+ };
2612
+ }
2613
+ function style(value) {
2614
+ return {
2615
+ ...value.fill === void 0 ? {} : { fill: value.fill },
2616
+ ...value.stroke === void 0 ? {} : { stroke: value.stroke }
2617
+ };
2618
+ }
2619
+ function compartments(value) {
2620
+ return {
2621
+ ...value.stereotype === void 0 ? {} : { stereotype: value.stereotype },
2622
+ ...value.name === void 0 ? {} : { name: value.name },
2623
+ ...value.properties === void 0 ? {} : { properties: value.properties.map(formatCompartmentEntry) },
2624
+ ...value.constraints === void 0 ? {} : { constraints: [...value.constraints] }
2625
+ };
2626
+ }
2627
+ function normalizeFrame(frame) {
2628
+ return {
2629
+ kind: frame.kind,
2630
+ ...frame.context === void 0 ? {} : { context: frame.context },
2631
+ ...frame.name === void 0 ? {} : { name: frame.name },
2632
+ titleTab: frame.titleTab,
2633
+ ...frame.style === void 0 ? {} : { style: style(frame.style) }
2634
+ };
2635
+ }
2636
+ function formatCompartmentEntry(value) {
2637
+ if (typeof value === "string") {
2638
+ return value;
2639
+ }
2640
+ const [entry] = Object.entries(value);
2641
+ if (entry === void 0) {
2642
+ return "";
2643
+ }
2644
+ return `${entry[0]}: ${entry[1]}`;
2645
+ }
2646
+ function normalizeSwimlanes(dsl) {
2647
+ return Object.keys(dsl.swimlanes ?? {}).sort().map((id) => {
2648
+ const swimlane = dsl.swimlanes?.[id];
2649
+ const label = toLabel(swimlane?.label);
2650
+ return {
2651
+ id,
2652
+ ...label === void 0 ? {} : { label },
2653
+ orientation: swimlane?.orientation ?? "vertical",
2654
+ lanes: Object.keys(swimlane?.lanes ?? {}).sort().map((laneId) => {
2655
+ const lane = swimlane?.lanes[laneId];
2656
+ const laneLabel = toLabel(lane?.label);
2657
+ return {
2658
+ id: laneId,
2659
+ ...laneLabel === void 0 ? {} : { label: laneLabel },
2660
+ children: [...lane?.children ?? []]
2661
+ };
2662
+ })
1950
2663
  };
1951
2664
  });
1952
2665
  }
@@ -2020,14 +2733,28 @@ function validateReferences(dsl) {
2020
2733
  if (typeof edge === "string") {
2021
2734
  return;
2022
2735
  }
2023
- const sourceId = edge.sourceId ?? edge.source;
2024
- const targetId = edge.targetId ?? edge.target;
2736
+ const sourceId = edge.sourceId ?? endpointNodeId(edge.source);
2737
+ const targetId = edge.targetId ?? endpointNodeId(edge.target);
2738
+ const sourceEndpoint = endpoint(edge.source, edge.sourceId);
2739
+ const targetEndpoint = endpoint(edge.target, edge.targetId);
2025
2740
  if (sourceId !== void 0 && !nodeIds.has(sourceId)) {
2026
2741
  diagnostics.push(referenceMissing(["edges", index, "source"], sourceId));
2027
2742
  }
2028
2743
  if (targetId !== void 0 && !nodeIds.has(targetId)) {
2029
2744
  diagnostics.push(referenceMissing(["edges", index, "target"], targetId));
2030
2745
  }
2746
+ validateEndpointPort(
2747
+ dsl,
2748
+ sourceEndpoint,
2749
+ ["edges", index, "source"],
2750
+ diagnostics
2751
+ );
2752
+ validateEndpointPort(
2753
+ dsl,
2754
+ targetEndpoint,
2755
+ ["edges", index, "target"],
2756
+ diagnostics
2757
+ );
2031
2758
  });
2032
2759
  for (const [groupId, group] of Object.entries(dsl.groups ?? {})) {
2033
2760
  (group.nodes ?? []).forEach((nodeId, index) => {
@@ -2045,6 +2772,27 @@ function validateReferences(dsl) {
2045
2772
  }
2046
2773
  });
2047
2774
  }
2775
+ for (const [swimlaneId, swimlane] of Object.entries(dsl.swimlanes ?? {})) {
2776
+ for (const [laneId, lane] of Object.entries(swimlane.lanes)) {
2777
+ (lane.children ?? []).forEach((child, childIndex) => {
2778
+ if (!nodeIds.has(child)) {
2779
+ diagnostics.push(
2780
+ referenceMissing(
2781
+ [
2782
+ "swimlanes",
2783
+ swimlaneId,
2784
+ "lanes",
2785
+ laneId,
2786
+ "children",
2787
+ childIndex
2788
+ ],
2789
+ child
2790
+ )
2791
+ );
2792
+ }
2793
+ });
2794
+ }
2795
+ }
2048
2796
  (dsl.constraints ?? []).forEach((constraint, index) => {
2049
2797
  switch (constraint.kind) {
2050
2798
  case "exact-position": {
@@ -2088,10 +2836,12 @@ function validateReferences(dsl) {
2088
2836
  break;
2089
2837
  case "containment": {
2090
2838
  const container = constraint.containerId ?? constraint.container;
2091
- if (container !== void 0 && !hasNodeOrGroup(container, nodeIds, groupIds)) {
2092
- diagnostics.push(
2093
- referenceMissing(["constraints", index, "container"], container)
2094
- );
2839
+ if (container !== void 0) {
2840
+ if (!nodeIds.has(container)) {
2841
+ diagnostics.push(
2842
+ referenceMissing(["constraints", index, "container"], container)
2843
+ );
2844
+ }
2095
2845
  }
2096
2846
  (constraint.childIds ?? constraint.children ?? []).forEach(
2097
2847
  (child, childIndex) => {
@@ -2121,8 +2871,23 @@ function referenceMissing(path, id) {
2121
2871
  hint: "Define the referenced node or group id, or update this reference."
2122
2872
  };
2123
2873
  }
2124
- function hasNodeOrGroup(id, nodeIds, groupIds) {
2125
- return nodeIds.has(id) || groupIds.has(id);
2874
+ function hasNodeOrGroup(id, nodeIds, groupIds, swimlaneLaneIds = /* @__PURE__ */ new Set()) {
2875
+ return nodeIds.has(id) || groupIds.has(id) || swimlaneLaneIds.has(id);
2876
+ }
2877
+ function endpointNodeId(endpointValue) {
2878
+ if (typeof endpointValue === "string" || endpointValue === void 0) {
2879
+ return endpointValue;
2880
+ }
2881
+ return endpointValue.node;
2882
+ }
2883
+ function validateEndpointPort(dsl, endpointValue, path, diagnostics) {
2884
+ if (endpointValue.portId === void 0) {
2885
+ return;
2886
+ }
2887
+ const node = dsl.nodes[endpointValue.nodeId];
2888
+ if (node !== void 0 && node.ports?.[endpointValue.portId] === void 0) {
2889
+ diagnostics.push(referenceMissing([...path, "port"], endpointValue.portId));
2890
+ }
2126
2891
  }
2127
2892
  function toLabel(value) {
2128
2893
  if (value === void 0) {
@@ -2192,6 +2957,8 @@ function isValidEdgeId(value) {
2192
2957
  var directionSchema = zod.z.enum(["TB", "LR", "BT", "RL"]);
2193
2958
  var routeKindSchema = zod.z.enum(["orthogonal", "straight"]);
2194
2959
  var outputFormatSchema = zod.z.enum(["svg", "excalidraw"]);
2960
+ var edgeStrokeStyleSchema = zod.z.enum(["solid", "dashed"]);
2961
+ var edgeArrowheadSchema = zod.z.enum(["triangle", "hollowTriangle"]);
2195
2962
  var nodeShapeSchema = zod.z.enum([
2196
2963
  "rectangle",
2197
2964
  "rounded-rectangle",
@@ -2219,18 +2986,49 @@ var labelSchema = zod.z.union([
2219
2986
  maxWidth: finiteNumberSchema.optional()
2220
2987
  })
2221
2988
  ]);
2989
+ var styleSchema = zod.z.object({
2990
+ fill: zod.z.string().optional(),
2991
+ stroke: zod.z.string().optional()
2992
+ });
2993
+ var portSideSchema = zod.z.enum(["top", "right", "bottom", "left"]);
2994
+ var portKindSchema = zod.z.enum(["proxy", "flow"]);
2995
+ var portSchema = zod.z.object({
2996
+ label: labelSchema.optional(),
2997
+ side: portSideSchema,
2998
+ kind: portKindSchema.optional(),
2999
+ order: finiteNumberSchema.optional(),
3000
+ style: styleSchema.optional()
3001
+ });
3002
+ var compartmentsSchema = zod.z.object({
3003
+ stereotype: zod.z.string().optional(),
3004
+ name: zod.z.string().optional(),
3005
+ properties: zod.z.array(zod.z.record(zod.z.string(), zod.z.string()).or(zod.z.string())).optional(),
3006
+ constraints: zod.z.array(zod.z.string()).optional()
3007
+ });
2222
3008
  var nodeSchema = zod.z.object({
2223
3009
  label: labelSchema.optional(),
2224
3010
  shape: nodeShapeSchema.optional(),
2225
- position: pointSchema.optional()
3011
+ position: pointSchema.optional(),
3012
+ style: styleSchema.optional(),
3013
+ ports: zod.z.record(zod.z.string(), portSchema).optional(),
3014
+ compartments: compartmentsSchema.optional()
2226
3015
  });
3016
+ var endpointSchema = zod.z.union([
3017
+ zod.z.string(),
3018
+ zod.z.object({
3019
+ node: zod.z.string(),
3020
+ port: zod.z.string().optional()
3021
+ })
3022
+ ]);
2227
3023
  var structuredEdgeSchema = zod.z.object({
2228
3024
  id: zod.z.string().optional(),
2229
- source: zod.z.string().optional(),
2230
- target: zod.z.string().optional(),
3025
+ source: endpointSchema.optional(),
3026
+ target: endpointSchema.optional(),
2231
3027
  sourceId: zod.z.string().optional(),
2232
3028
  targetId: zod.z.string().optional(),
2233
- label: labelSchema.optional()
3029
+ label: labelSchema.optional(),
3030
+ style: edgeStrokeStyleSchema.optional(),
3031
+ arrowhead: edgeArrowheadSchema.optional()
2234
3032
  }).superRefine((edge, context) => {
2235
3033
  if (edge.source === void 0 && edge.sourceId === void 0) {
2236
3034
  context.addIssue({
@@ -2254,6 +3052,17 @@ var groupSchema = zod.z.object({
2254
3052
  groups: zod.z.array(zod.z.string()).optional(),
2255
3053
  padding: insetsSchema.optional()
2256
3054
  });
3055
+ var swimlaneSchema = zod.z.object({
3056
+ label: labelSchema.optional(),
3057
+ orientation: zod.z.enum(["vertical", "horizontal"]).optional(),
3058
+ lanes: zod.z.record(
3059
+ zod.z.string(),
3060
+ zod.z.object({
3061
+ label: labelSchema.optional(),
3062
+ children: zod.z.array(zod.z.string()).optional()
3063
+ })
3064
+ )
3065
+ });
2257
3066
  var exactPositionConstraintSchema = zod.z.object({
2258
3067
  kind: zod.z.literal("exact-position"),
2259
3068
  target: zod.z.string().optional(),
@@ -2314,12 +3123,24 @@ var diagramDslSchema = zod.z.object({
2314
3123
  direction: directionSchema.optional()
2315
3124
  }).optional(),
2316
3125
  routing: zod.z.object({
2317
- kind: routeKindSchema.optional()
3126
+ kind: routeKindSchema.optional(),
3127
+ portShifting: zod.z.object({
3128
+ enabled: zod.z.boolean().optional(),
3129
+ spacing: finiteNumberSchema.optional()
3130
+ }).optional()
2318
3131
  }).optional(),
2319
3132
  nodes: zod.z.record(zod.z.string(), nodeSchema),
2320
3133
  edges: zod.z.array(edgeSchema).optional(),
2321
3134
  groups: zod.z.record(zod.z.string(), groupSchema).optional(),
3135
+ swimlanes: zod.z.record(zod.z.string(), swimlaneSchema).optional(),
2322
3136
  constraints: zod.z.array(constraintSchema).optional(),
3137
+ frame: zod.z.object({
3138
+ kind: zod.z.string(),
3139
+ context: zod.z.string().optional(),
3140
+ name: zod.z.string().optional(),
3141
+ titleTab: zod.z.string(),
3142
+ style: styleSchema.optional()
3143
+ }).optional(),
2323
3144
  output: zod.z.object({
2324
3145
  format: outputFormatSchema.optional()
2325
3146
  }).optional()
@@ -2516,7 +3337,8 @@ function renderDiagramDsl(source, options = {}) {
2516
3337
  return { diagnostics };
2517
3338
  }
2518
3339
  const solved = solveDiagram(normalized.diagram, {
2519
- routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal"
3340
+ routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
3341
+ ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting)
2520
3342
  });
2521
3343
  const solveDiagnostics = solved.diagnostics.map(toSolveDiagnostic);
2522
3344
  if (hasErrorDiagnostics2(solveDiagnostics)) {
@@ -2555,6 +3377,22 @@ function renderDiagramDsl(source, options = {}) {
2555
3377
  function toSolveDiagnostic(diagnostic) {
2556
3378
  return { ...diagnostic, layer: "solve" };
2557
3379
  }
3380
+ function solvePortShiftingOption(value) {
3381
+ if (!isJsonObject(value)) {
3382
+ return {};
3383
+ }
3384
+ const portShifting = {};
3385
+ if (value.enabled === false) {
3386
+ portShifting.enabled = false;
3387
+ }
3388
+ if (typeof value.spacing === "number") {
3389
+ portShifting.spacing = value.spacing;
3390
+ }
3391
+ return { portShifting };
3392
+ }
3393
+ function isJsonObject(value) {
3394
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3395
+ }
2558
3396
  function toExportDiagnostic(diagnostic) {
2559
3397
  return { ...diagnostic, layer: "export" };
2560
3398
  }