@crazyhappyone/auto-graph 0.0.1 → 0.0.2

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
@@ -1,11 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ var module$1 = require('module');
3
4
  var pretext = require('@chenglou/pretext');
4
5
  var buffer = require('buffer');
5
6
  var yaml = require('yaml');
6
7
  var zod = require('zod');
7
8
  var dagre = require('@dagrejs/dagre');
8
9
 
10
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
11
  // src/geometry/boxes.ts
10
12
  function normalizeInsets(input = 0) {
11
13
  if (typeof input === "number") {
@@ -822,34 +824,34 @@ function assertFiniteNonNegative(value, label) {
822
824
  throw new TypeError(`${label} must be a finite non-negative width`);
823
825
  }
824
826
  }
825
- function validateTextStyle(style) {
826
- assertFinitePositive(style.fontSize, "fontSize");
827
- if (style.lineHeight !== void 0) {
828
- assertFinitePositive(style.lineHeight, "lineHeight");
827
+ function validateTextStyle(style2) {
828
+ assertFinitePositive(style2.fontSize, "fontSize");
829
+ if (style2.lineHeight !== void 0) {
830
+ assertFinitePositive(style2.lineHeight, "lineHeight");
829
831
  }
830
- if (style.letterSpacing !== void 0 && !Number.isFinite(style.letterSpacing)) {
832
+ if (style2.letterSpacing !== void 0 && !Number.isFinite(style2.letterSpacing)) {
831
833
  throw new TypeError("letterSpacing must be finite");
832
834
  }
833
835
  }
834
- function resolveLineHeight(style) {
835
- validateTextStyle(style);
836
- return style.lineHeight ?? style.fontSize * 1.2;
836
+ function resolveLineHeight(style2) {
837
+ validateTextStyle(style2);
838
+ return style2.lineHeight ?? style2.fontSize * 1.2;
837
839
  }
838
- function toCanvasFont(style) {
839
- validateTextStyle(style);
840
- const fontStyle = style.fontStyle === "italic" ? "italic " : "";
841
- const fontWeight = style.fontWeight ?? 400;
842
- return `${fontStyle}${fontWeight} ${style.fontSize}px ${style.fontFamily}`;
840
+ function toCanvasFont(style2) {
841
+ validateTextStyle(style2);
842
+ const fontStyle = style2.fontStyle === "italic" ? "italic " : "";
843
+ const fontWeight = style2.fontWeight ?? 400;
844
+ return `${fontStyle}${fontWeight} ${style2.fontSize}px ${style2.fontFamily}`;
843
845
  }
844
846
 
845
847
  // src/text/fallback.ts
846
848
  var DeterministicTextMeasurer = class {
847
- prepare(text, style) {
848
- validateTextStyle(style);
849
+ prepare(text, style2) {
850
+ validateTextStyle(style2);
849
851
  return {
850
852
  text,
851
- font: toCanvasFont(style),
852
- style: { ...style },
853
+ font: toCanvasFont(style2),
854
+ style: { ...style2 },
853
855
  backend: "deterministic"
854
856
  };
855
857
  }
@@ -908,9 +910,9 @@ var DeterministicTextMeasurer = class {
908
910
  return output;
909
911
  }
910
912
  };
911
- function getCharacterWidth(style) {
912
- const letterSpacing = style.letterSpacing ?? 0;
913
- return Math.max(0, style.fontSize * 0.6 + letterSpacing);
913
+ function getCharacterWidth(style2) {
914
+ const letterSpacing = style2.letterSpacing ?? 0;
915
+ return Math.max(0, style2.fontSize * 0.6 + letterSpacing);
914
916
  }
915
917
  function createLine(text, width, segmentIndex, start, end) {
916
918
  return {
@@ -931,27 +933,53 @@ function assertFinitePositiveLineHeight(lineHeight) {
931
933
  throw new TypeError("lineHeight must be finite and positive");
932
934
  }
933
935
  }
936
+ 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)));
937
+ function installNodeCanvasRuntime(loadNodeCanvasModule = loadDefaultNodeCanvasModule) {
938
+ if (typeof globalThis.OffscreenCanvas === "function") {
939
+ return true;
940
+ }
941
+ try {
942
+ const canvasModule = loadNodeCanvasModule();
943
+ const { createCanvas } = canvasModule;
944
+ const NodeOffscreenCanvas = class {
945
+ canvas;
946
+ constructor(width, height) {
947
+ this.canvas = createCanvas(width, height);
948
+ }
949
+ getContext(contextId) {
950
+ return contextId === "2d" ? this.canvas.getContext("2d") : null;
951
+ }
952
+ };
953
+ globalThis.OffscreenCanvas = NodeOffscreenCanvas;
954
+ return true;
955
+ } catch {
956
+ return false;
957
+ }
958
+ }
959
+ function loadDefaultNodeCanvasModule() {
960
+ return require2("@napi-rs/canvas");
961
+ }
934
962
  var RUNTIME_UNAVAILABLE = "text.pretext.runtime-unavailable";
935
963
  function isPretextRuntimeAvailable() {
936
964
  return typeof Intl.Segmenter === "function" && typeof globalThis.OffscreenCanvas === "function";
937
965
  }
938
966
  var PretextTextMeasurer = class {
939
- prepare(text, style) {
967
+ prepare(text, style2) {
940
968
  if (!isPretextRuntimeAvailable()) {
941
969
  throw new TypeError(RUNTIME_UNAVAILABLE);
942
970
  }
943
- validateTextStyle(style);
944
- const font = toCanvasFont(style);
971
+ validateTextStyle(style2);
972
+ const font = toCanvasFont(style2);
945
973
  const options = {
946
- ...style.whiteSpace === void 0 ? {} : { whiteSpace: style.whiteSpace },
947
- ...style.wordBreak === void 0 ? {} : { wordBreak: style.wordBreak },
948
- ...style.letterSpacing === void 0 ? {} : { letterSpacing: style.letterSpacing }
974
+ ...style2.whiteSpace === void 0 ? {} : { whiteSpace: style2.whiteSpace },
975
+ ...style2.wordBreak === void 0 ? {} : { wordBreak: style2.wordBreak },
976
+ ...style2.letterSpacing === void 0 ? {} : { letterSpacing: style2.letterSpacing }
949
977
  };
950
978
  const prepared = pretext.prepareWithSegments(text, font, options);
951
979
  return {
952
980
  text,
953
981
  font,
954
- style: { ...style },
982
+ style: { ...style2 },
955
983
  backend: "pretext",
956
984
  pretextPrepared: prepared
957
985
  };
@@ -1001,6 +1029,13 @@ function toInternalPrepared(prepared) {
1001
1029
  return prepared.pretextPrepared;
1002
1030
  }
1003
1031
 
1032
+ // src/text/default.ts
1033
+ function createDefaultTextMeasurer(options = {}) {
1034
+ const installRuntime = options.installNodeCanvasRuntime ?? installNodeCanvasRuntime;
1035
+ installRuntime();
1036
+ return isPretextRuntimeAvailable() ? new PretextTextMeasurer() : new DeterministicTextMeasurer();
1037
+ }
1038
+
1004
1039
  // src/labels/fit.ts
1005
1040
  function fitLabel(text, options, measurer) {
1006
1041
  return computeLabelLayout(text, options, measurer);
@@ -1067,6 +1102,7 @@ function computeLabelLayout(text, options, measurer) {
1067
1102
  fittedSize,
1068
1103
  padding,
1069
1104
  font: { ...options.font },
1105
+ textBackend: prepared.backend,
1070
1106
  lineHeight,
1071
1107
  lines: buildLines(textLayout, contentBox2, lineHeight),
1072
1108
  overflow,
@@ -1161,8 +1197,9 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1161
1197
  ...outputResult(dsl)
1162
1198
  };
1163
1199
  }
1164
- const measurer = options.textMeasurer ?? new DeterministicTextMeasurer();
1200
+ const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
1165
1201
  const routeKind = dsl.routing?.kind ?? "orthogonal";
1202
+ const portShifting = normalizePortShifting(dsl.routing?.portShifting);
1166
1203
  const diagram = {
1167
1204
  id: options.id ?? dsl.id ?? "diagram",
1168
1205
  ...dsl.title === void 0 ? {} : { title: dsl.title },
@@ -1170,9 +1207,14 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1170
1207
  nodes: normalizeNodes(dsl, measurer),
1171
1208
  edges: normalizeEdges(dsl),
1172
1209
  groups: normalizeGroups(dsl, measurer),
1210
+ swimlanes: normalizeSwimlanes(dsl),
1173
1211
  constraints: normalizeConstraints(dsl),
1174
1212
  diagnostics: [],
1175
- metadata: { routeKind }
1213
+ ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
1214
+ metadata: {
1215
+ routeKind,
1216
+ ...portShifting === void 0 ? {} : { portShifting }
1217
+ }
1176
1218
  };
1177
1219
  return {
1178
1220
  diagram,
@@ -1180,6 +1222,15 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1180
1222
  ...outputResult(dsl)
1181
1223
  };
1182
1224
  }
1225
+ function normalizePortShifting(portShifting) {
1226
+ if (portShifting === void 0) {
1227
+ return void 0;
1228
+ }
1229
+ return {
1230
+ ...portShifting.enabled === void 0 ? {} : { enabled: portShifting.enabled },
1231
+ ...portShifting.spacing === void 0 ? {} : { spacing: portShifting.spacing }
1232
+ };
1233
+ }
1183
1234
  function outputResult(dsl) {
1184
1235
  return dsl.output?.format === void 0 ? {} : { output: { format: dsl.output.format } };
1185
1236
  }
@@ -1189,15 +1240,24 @@ function normalizeNodes(dsl, measurer) {
1189
1240
  const label = toLabel(node?.label);
1190
1241
  const labelLayout = label === void 0 ? void 0 : fitDslLabel(label, measurer);
1191
1242
  const fittedSize = labelLayout?.fittedSize;
1243
+ const nodeCompartments = node?.compartments === void 0 ? void 0 : compartments(node.compartments);
1244
+ const compartmentWidth = nodeCompartments === void 0 ? 0 : compartmentNaturalWidth(id, label, nodeCompartments, measurer);
1192
1245
  return {
1193
1246
  id,
1194
1247
  ...label === void 0 ? {} : { label },
1195
1248
  shape: node?.shape ?? "rectangle",
1196
1249
  ...node?.position === void 0 ? {} : { position: point(node.position) },
1250
+ ...node?.style === void 0 ? {} : { style: style(node.style) },
1251
+ ...node?.ports === void 0 ? {} : { ports: normalizePorts(node.ports) },
1252
+ ...nodeCompartments === void 0 ? {} : { compartments: nodeCompartments },
1197
1253
  size: {
1198
- width: Math.max(DEFAULT_NODE_MIN_SIZE.width, fittedSize?.width ?? 0),
1254
+ width: Math.max(
1255
+ DEFAULT_NODE_MIN_SIZE.width,
1256
+ fittedSize?.width ?? 0,
1257
+ compartmentWidth
1258
+ ),
1199
1259
  height: Math.max(
1200
- DEFAULT_NODE_MIN_SIZE.height,
1260
+ nodeCompartments === void 0 ? DEFAULT_NODE_MIN_SIZE.height : compartmentHeight(nodeCompartments),
1201
1261
  fittedSize?.height ?? 0
1202
1262
  )
1203
1263
  },
@@ -1206,11 +1266,42 @@ function normalizeNodes(dsl, measurer) {
1206
1266
  };
1207
1267
  });
1208
1268
  }
1269
+ function compartmentHeight(value) {
1270
+ const rowCount = (value.stereotype === void 0 ? 0 : 1) + 1 + (value.properties?.length ?? 0) + (value.constraints?.length ?? 0);
1271
+ const rowHeight = 16;
1272
+ const verticalPadding = 20;
1273
+ return Math.max(
1274
+ DEFAULT_NODE_MIN_SIZE.height,
1275
+ rowCount * rowHeight + verticalPadding
1276
+ );
1277
+ }
1278
+ function compartmentNaturalWidth(id, label, value, measurer) {
1279
+ const rows = compartmentRows(id, label, value);
1280
+ const maxRowWidth = rows.reduce((width, row) => {
1281
+ const prepared = measurer.prepare(row, DEFAULT_FONT);
1282
+ return Math.max(width, measurer.naturalWidth(prepared));
1283
+ }, 0);
1284
+ return Math.ceil(
1285
+ maxRowWidth + DEFAULT_NODE_PADDING.left + DEFAULT_NODE_PADDING.right
1286
+ );
1287
+ }
1288
+ function compartmentRows(id, label, value) {
1289
+ return [
1290
+ ...value.stereotype === void 0 ? [] : [value.stereotype],
1291
+ value.name ?? label?.text ?? id,
1292
+ ...value.properties ?? [],
1293
+ ...value.constraints ?? []
1294
+ ];
1295
+ }
1209
1296
  function normalizeEdges(dsl) {
1210
1297
  const counts = /* @__PURE__ */ new Map();
1211
1298
  return (dsl.edges ?? []).map((edge) => {
1212
- const sourceId = typeof edge === "string" ? "" : edge.sourceId ?? edge.source ?? "";
1213
- const targetId = typeof edge === "string" ? "" : edge.targetId ?? edge.target ?? "";
1299
+ const source = typeof edge === "string" ? void 0 : edge.source;
1300
+ const target = typeof edge === "string" ? void 0 : edge.target;
1301
+ const sourceId = typeof edge === "string" ? "" : edge.sourceId ?? endpointNodeId(source) ?? "";
1302
+ const targetId = typeof edge === "string" ? "" : edge.targetId ?? endpointNodeId(target) ?? "";
1303
+ const sourceEndpoint = typeof edge === "string" ? { nodeId: sourceId } : endpoint(source, edge.sourceId);
1304
+ const targetEndpoint = typeof edge === "string" ? { nodeId: targetId } : endpoint(target, edge.targetId);
1214
1305
  const baseId = `${sourceId}-${targetId}`;
1215
1306
  const count = counts.get(baseId) ?? 0;
1216
1307
  counts.set(baseId, count + 1);
@@ -1218,9 +1309,96 @@ function normalizeEdges(dsl) {
1218
1309
  const label = typeof edge === "string" ? void 0 : toLabel(edge.label);
1219
1310
  return {
1220
1311
  id,
1221
- source: { nodeId: sourceId },
1222
- target: { nodeId: targetId },
1223
- ...label === void 0 ? {} : { label }
1312
+ source: sourceEndpoint,
1313
+ target: targetEndpoint,
1314
+ ...label === void 0 ? {} : { label },
1315
+ ...typeof edge === "string" || edge.style === void 0 ? {} : { style: edge.style },
1316
+ ...typeof edge === "string" || edge.arrowhead === void 0 ? {} : { arrowhead: edge.arrowhead }
1317
+ };
1318
+ });
1319
+ }
1320
+ function normalizePorts(ports) {
1321
+ return Object.keys(ports ?? {}).sort().map((id) => {
1322
+ const port = ports?.[id];
1323
+ const label = toLabel(port?.label);
1324
+ return {
1325
+ id,
1326
+ ...label === void 0 ? {} : { label },
1327
+ side: port?.side ?? "right",
1328
+ kind: port?.kind ?? "proxy",
1329
+ ...port?.order === void 0 ? {} : { order: port.order },
1330
+ ...port?.style === void 0 ? {} : { style: style(port.style) }
1331
+ };
1332
+ });
1333
+ }
1334
+ function endpoint(value, nodeIdOverride) {
1335
+ if (nodeIdOverride !== void 0) {
1336
+ return {
1337
+ nodeId: nodeIdOverride,
1338
+ ...typeof value === "object" && value.node === nodeIdOverride && value.port !== void 0 ? { portId: value.port } : {}
1339
+ };
1340
+ }
1341
+ if (value === void 0) {
1342
+ return { nodeId: "" };
1343
+ }
1344
+ if (typeof value === "string") {
1345
+ return { nodeId: value };
1346
+ }
1347
+ return {
1348
+ nodeId: value.node,
1349
+ ...value.port === void 0 ? {} : { portId: value.port }
1350
+ };
1351
+ }
1352
+ function style(value) {
1353
+ return {
1354
+ ...value.fill === void 0 ? {} : { fill: value.fill },
1355
+ ...value.stroke === void 0 ? {} : { stroke: value.stroke }
1356
+ };
1357
+ }
1358
+ function compartments(value) {
1359
+ return {
1360
+ ...value.stereotype === void 0 ? {} : { stereotype: value.stereotype },
1361
+ ...value.name === void 0 ? {} : { name: value.name },
1362
+ ...value.properties === void 0 ? {} : { properties: value.properties.map(formatCompartmentEntry) },
1363
+ ...value.constraints === void 0 ? {} : { constraints: [...value.constraints] }
1364
+ };
1365
+ }
1366
+ function normalizeFrame(frame) {
1367
+ return {
1368
+ kind: frame.kind,
1369
+ ...frame.context === void 0 ? {} : { context: frame.context },
1370
+ ...frame.name === void 0 ? {} : { name: frame.name },
1371
+ titleTab: frame.titleTab,
1372
+ ...frame.style === void 0 ? {} : { style: style(frame.style) }
1373
+ };
1374
+ }
1375
+ function formatCompartmentEntry(value) {
1376
+ if (typeof value === "string") {
1377
+ return value;
1378
+ }
1379
+ const [entry] = Object.entries(value);
1380
+ if (entry === void 0) {
1381
+ return "";
1382
+ }
1383
+ return `${entry[0]}: ${entry[1]}`;
1384
+ }
1385
+ function normalizeSwimlanes(dsl) {
1386
+ return Object.keys(dsl.swimlanes ?? {}).sort().map((id) => {
1387
+ const swimlane = dsl.swimlanes?.[id];
1388
+ const label = toLabel(swimlane?.label);
1389
+ return {
1390
+ id,
1391
+ ...label === void 0 ? {} : { label },
1392
+ orientation: swimlane?.orientation ?? "vertical",
1393
+ lanes: Object.keys(swimlane?.lanes ?? {}).sort().map((laneId) => {
1394
+ const lane = swimlane?.lanes[laneId];
1395
+ const laneLabel = toLabel(lane?.label);
1396
+ return {
1397
+ id: laneId,
1398
+ ...laneLabel === void 0 ? {} : { label: laneLabel },
1399
+ children: [...lane?.children ?? []]
1400
+ };
1401
+ })
1224
1402
  };
1225
1403
  });
1226
1404
  }
@@ -1290,18 +1468,37 @@ function validateReferences(dsl) {
1290
1468
  const diagnostics = [];
1291
1469
  const nodeIds = new Set(Object.keys(dsl.nodes));
1292
1470
  const groupIds = new Set(Object.keys(dsl.groups ?? {}));
1471
+ const swimlaneLaneIds = new Set(
1472
+ Object.entries(dsl.swimlanes ?? {}).flatMap(
1473
+ ([swimlaneId, swimlane]) => Object.keys(swimlane.lanes).map((laneId) => `${swimlaneId}.${laneId}`)
1474
+ )
1475
+ );
1293
1476
  (dsl.edges ?? []).forEach((edge, index) => {
1294
1477
  if (typeof edge === "string") {
1295
1478
  return;
1296
1479
  }
1297
- const sourceId = edge.sourceId ?? edge.source;
1298
- const targetId = edge.targetId ?? edge.target;
1480
+ const sourceId = edge.sourceId ?? endpointNodeId(edge.source);
1481
+ const targetId = edge.targetId ?? endpointNodeId(edge.target);
1482
+ const sourceEndpoint = endpoint(edge.source, edge.sourceId);
1483
+ const targetEndpoint = endpoint(edge.target, edge.targetId);
1299
1484
  if (sourceId !== void 0 && !nodeIds.has(sourceId)) {
1300
1485
  diagnostics.push(referenceMissing(["edges", index, "source"], sourceId));
1301
1486
  }
1302
1487
  if (targetId !== void 0 && !nodeIds.has(targetId)) {
1303
1488
  diagnostics.push(referenceMissing(["edges", index, "target"], targetId));
1304
1489
  }
1490
+ validateEndpointPort(
1491
+ dsl,
1492
+ sourceEndpoint,
1493
+ ["edges", index, "source"],
1494
+ diagnostics
1495
+ );
1496
+ validateEndpointPort(
1497
+ dsl,
1498
+ targetEndpoint,
1499
+ ["edges", index, "target"],
1500
+ diagnostics
1501
+ );
1305
1502
  });
1306
1503
  for (const [groupId, group] of Object.entries(dsl.groups ?? {})) {
1307
1504
  (group.nodes ?? []).forEach((nodeId, index) => {
@@ -1319,6 +1516,27 @@ function validateReferences(dsl) {
1319
1516
  }
1320
1517
  });
1321
1518
  }
1519
+ for (const [swimlaneId, swimlane] of Object.entries(dsl.swimlanes ?? {})) {
1520
+ for (const [laneId, lane] of Object.entries(swimlane.lanes)) {
1521
+ (lane.children ?? []).forEach((child, childIndex) => {
1522
+ if (!nodeIds.has(child)) {
1523
+ diagnostics.push(
1524
+ referenceMissing(
1525
+ [
1526
+ "swimlanes",
1527
+ swimlaneId,
1528
+ "lanes",
1529
+ laneId,
1530
+ "children",
1531
+ childIndex
1532
+ ],
1533
+ child
1534
+ )
1535
+ );
1536
+ }
1537
+ });
1538
+ }
1539
+ }
1322
1540
  (dsl.constraints ?? []).forEach((constraint, index) => {
1323
1541
  switch (constraint.kind) {
1324
1542
  case "exact-position": {
@@ -1362,7 +1580,7 @@ function validateReferences(dsl) {
1362
1580
  break;
1363
1581
  case "containment": {
1364
1582
  const container = constraint.containerId ?? constraint.container;
1365
- if (container !== void 0 && !hasNodeOrGroup(container, nodeIds, groupIds)) {
1583
+ if (container !== void 0 && !hasNodeOrGroup(container, nodeIds, groupIds, swimlaneLaneIds)) {
1366
1584
  diagnostics.push(
1367
1585
  referenceMissing(["constraints", index, "container"], container)
1368
1586
  );
@@ -1395,8 +1613,23 @@ function referenceMissing(path, id) {
1395
1613
  hint: "Define the referenced node or group id, or update this reference."
1396
1614
  };
1397
1615
  }
1398
- function hasNodeOrGroup(id, nodeIds, groupIds) {
1399
- return nodeIds.has(id) || groupIds.has(id);
1616
+ function hasNodeOrGroup(id, nodeIds, groupIds, swimlaneLaneIds = /* @__PURE__ */ new Set()) {
1617
+ return nodeIds.has(id) || groupIds.has(id) || swimlaneLaneIds.has(id);
1618
+ }
1619
+ function endpointNodeId(endpointValue) {
1620
+ if (typeof endpointValue === "string" || endpointValue === void 0) {
1621
+ return endpointValue;
1622
+ }
1623
+ return endpointValue.node;
1624
+ }
1625
+ function validateEndpointPort(dsl, endpointValue, path, diagnostics) {
1626
+ if (endpointValue.portId === void 0) {
1627
+ return;
1628
+ }
1629
+ const node = dsl.nodes[endpointValue.nodeId];
1630
+ if (node !== void 0 && node.ports?.[endpointValue.portId] === void 0) {
1631
+ diagnostics.push(referenceMissing([...path, "port"], endpointValue.portId));
1632
+ }
1400
1633
  }
1401
1634
  function toLabel(value) {
1402
1635
  if (value === void 0) {
@@ -1425,6 +1658,8 @@ function point(value) {
1425
1658
  var directionSchema = zod.z.enum(["TB", "LR", "BT", "RL"]);
1426
1659
  var routeKindSchema = zod.z.enum(["orthogonal", "straight"]);
1427
1660
  var outputFormatSchema = zod.z.enum(["svg", "excalidraw"]);
1661
+ var edgeStrokeStyleSchema = zod.z.enum(["solid", "dashed"]);
1662
+ var edgeArrowheadSchema = zod.z.enum(["triangle", "hollowTriangle"]);
1428
1663
  var nodeShapeSchema = zod.z.enum([
1429
1664
  "rectangle",
1430
1665
  "rounded-rectangle",
@@ -1452,18 +1687,49 @@ var labelSchema = zod.z.union([
1452
1687
  maxWidth: finiteNumberSchema.optional()
1453
1688
  })
1454
1689
  ]);
1690
+ var styleSchema = zod.z.object({
1691
+ fill: zod.z.string().optional(),
1692
+ stroke: zod.z.string().optional()
1693
+ });
1694
+ var portSideSchema = zod.z.enum(["top", "right", "bottom", "left"]);
1695
+ var portKindSchema = zod.z.enum(["proxy", "flow"]);
1696
+ var portSchema = zod.z.object({
1697
+ label: labelSchema.optional(),
1698
+ side: portSideSchema,
1699
+ kind: portKindSchema.optional(),
1700
+ order: finiteNumberSchema.optional(),
1701
+ style: styleSchema.optional()
1702
+ });
1703
+ var compartmentsSchema = zod.z.object({
1704
+ stereotype: zod.z.string().optional(),
1705
+ name: zod.z.string().optional(),
1706
+ properties: zod.z.array(zod.z.record(zod.z.string(), zod.z.string()).or(zod.z.string())).optional(),
1707
+ constraints: zod.z.array(zod.z.string()).optional()
1708
+ });
1455
1709
  var nodeSchema = zod.z.object({
1456
1710
  label: labelSchema.optional(),
1457
1711
  shape: nodeShapeSchema.optional(),
1458
- position: pointSchema.optional()
1712
+ position: pointSchema.optional(),
1713
+ style: styleSchema.optional(),
1714
+ ports: zod.z.record(zod.z.string(), portSchema).optional(),
1715
+ compartments: compartmentsSchema.optional()
1459
1716
  });
1717
+ var endpointSchema = zod.z.union([
1718
+ zod.z.string(),
1719
+ zod.z.object({
1720
+ node: zod.z.string(),
1721
+ port: zod.z.string().optional()
1722
+ })
1723
+ ]);
1460
1724
  var structuredEdgeSchema = zod.z.object({
1461
1725
  id: zod.z.string().optional(),
1462
- source: zod.z.string().optional(),
1463
- target: zod.z.string().optional(),
1726
+ source: endpointSchema.optional(),
1727
+ target: endpointSchema.optional(),
1464
1728
  sourceId: zod.z.string().optional(),
1465
1729
  targetId: zod.z.string().optional(),
1466
- label: labelSchema.optional()
1730
+ label: labelSchema.optional(),
1731
+ style: edgeStrokeStyleSchema.optional(),
1732
+ arrowhead: edgeArrowheadSchema.optional()
1467
1733
  }).superRefine((edge, context) => {
1468
1734
  if (edge.source === void 0 && edge.sourceId === void 0) {
1469
1735
  context.addIssue({
@@ -1487,6 +1753,17 @@ var groupSchema = zod.z.object({
1487
1753
  groups: zod.z.array(zod.z.string()).optional(),
1488
1754
  padding: insetsSchema.optional()
1489
1755
  });
1756
+ var swimlaneSchema = zod.z.object({
1757
+ label: labelSchema.optional(),
1758
+ orientation: zod.z.enum(["vertical", "horizontal"]).optional(),
1759
+ lanes: zod.z.record(
1760
+ zod.z.string(),
1761
+ zod.z.object({
1762
+ label: labelSchema.optional(),
1763
+ children: zod.z.array(zod.z.string()).optional()
1764
+ })
1765
+ )
1766
+ });
1490
1767
  var exactPositionConstraintSchema = zod.z.object({
1491
1768
  kind: zod.z.literal("exact-position"),
1492
1769
  target: zod.z.string().optional(),
@@ -1547,12 +1824,24 @@ var diagramDslSchema = zod.z.object({
1547
1824
  direction: directionSchema.optional()
1548
1825
  }).optional(),
1549
1826
  routing: zod.z.object({
1550
- kind: routeKindSchema.optional()
1827
+ kind: routeKindSchema.optional(),
1828
+ portShifting: zod.z.object({
1829
+ enabled: zod.z.boolean().optional(),
1830
+ spacing: finiteNumberSchema.optional()
1831
+ }).optional()
1551
1832
  }).optional(),
1552
1833
  nodes: zod.z.record(zod.z.string(), nodeSchema),
1553
1834
  edges: zod.z.array(edgeSchema).optional(),
1554
1835
  groups: zod.z.record(zod.z.string(), groupSchema).optional(),
1836
+ swimlanes: zod.z.record(zod.z.string(), swimlaneSchema).optional(),
1555
1837
  constraints: zod.z.array(constraintSchema).optional(),
1838
+ frame: zod.z.object({
1839
+ kind: zod.z.string(),
1840
+ context: zod.z.string().optional(),
1841
+ name: zod.z.string().optional(),
1842
+ titleTab: zod.z.string(),
1843
+ style: styleSchema.optional()
1844
+ }).optional(),
1556
1845
  output: zod.z.object({
1557
1846
  format: outputFormatSchema.optional()
1558
1847
  }).optional()
@@ -1828,11 +2117,12 @@ function renderArrow(edge) {
1828
2117
  height: box.height
1829
2118
  }),
1830
2119
  backgroundColor: "transparent",
2120
+ strokeStyle: edge.style ?? "solid",
1831
2121
  points: relativePoints,
1832
2122
  startBinding: { elementId: `node:${edge.source.nodeId}`, focus: 0, gap: 0 },
1833
2123
  endBinding: { elementId: `node:${edge.target.nodeId}`, focus: 0, gap: 0 },
1834
2124
  startArrowhead: null,
1835
- endArrowhead: "arrow"
2125
+ endArrowhead: mapArrowhead(edge.arrowhead)
1836
2126
  };
1837
2127
  }
1838
2128
  function renderText(id, label, box, containerId, groupIds) {
@@ -1910,6 +2200,16 @@ function mapShape(shape) {
1910
2200
  return "cylinder";
1911
2201
  }
1912
2202
  }
2203
+ function mapArrowhead(arrowhead) {
2204
+ switch (arrowhead) {
2205
+ case void 0:
2206
+ return "arrow";
2207
+ case "triangle":
2208
+ return "triangle";
2209
+ case "hollowTriangle":
2210
+ return "triangle_outline";
2211
+ }
2212
+ }
1913
2213
  function createGroupMembership(groups) {
1914
2214
  const membership = /* @__PURE__ */ new Map();
1915
2215
  for (const group of groups) {
@@ -1976,19 +2276,28 @@ function exportSvg(diagram, options = {}) {
1976
2276
  `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
1977
2277
  ...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
1978
2278
  ` <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"/>`,
2279
+ ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame))],
2280
+ ...(diagram.swimlanes ?? []).flatMap(
2281
+ (swimlane) => renderSwimlane(swimlane)
2282
+ ),
1979
2283
  ...diagram.groups.map((group) => indent(renderGroup2(group))),
1980
2284
  ...diagram.edges.flatMap((edge) => {
1981
- const path = renderEdgePath(edge.points, edge.id);
2285
+ const path = renderEdgePath(edge);
1982
2286
  if (path === void 0) {
1983
2287
  return [];
1984
2288
  }
1985
- return [indent(path), indent(renderArrowhead(edge.points, edge.id))];
2289
+ return [indent(path), indent(renderArrowhead(edge))];
1986
2290
  }),
1987
2291
  ...diagram.nodes.map((node) => indent(renderNode2(node))),
2292
+ ...diagram.nodes.flatMap((node) => renderCompartments(node)),
2293
+ ...diagram.nodes.flatMap((node) => renderPorts(node)),
1988
2294
  ...diagram.groups.flatMap(
1989
2295
  (group) => renderLabel(group.label, group.box, group)
1990
2296
  ),
1991
- ...diagram.nodes.flatMap((node) => renderLabel(node.label, node.box, node)),
2297
+ ...diagram.nodes.flatMap(
2298
+ (node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node) : []
2299
+ ),
2300
+ ...diagram.edges.flatMap((edge) => renderEdgeLabel(edge)),
1992
2301
  "</svg>"
1993
2302
  ];
1994
2303
  return `${lines.join("\n")}
@@ -1998,7 +2307,9 @@ function renderGroup2(group) {
1998
2307
  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"/>`;
1999
2308
  }
2000
2309
  function renderNode2(node) {
2001
- const common = `class="node node-${node.shape}" data-id="${escapeAttribute(node.id)}" fill="${NODE_FILL}" stroke="${STROKE}"`;
2310
+ const fill = node.style?.fill ?? NODE_FILL;
2311
+ const stroke = node.style?.stroke ?? STROKE;
2312
+ const common = `class="node node-${node.shape}" data-id="${escapeAttribute(node.id)}" fill="${escapeAttribute(fill)}" stroke="${escapeAttribute(stroke)}"`;
2002
2313
  switch (node.shape) {
2003
2314
  case "rectangle":
2004
2315
  return renderRect(node.box, common);
@@ -2014,16 +2325,111 @@ function renderNode2(node) {
2014
2325
  return `<path ${common} d="${formatCylinderPath(node.box)}"/>`;
2015
2326
  }
2016
2327
  }
2328
+ function renderFrame(frame) {
2329
+ const stroke = frame.style?.stroke ?? "#6b7280";
2330
+ const fill = frame.style?.fill ?? "transparent";
2331
+ return [
2332
+ `<g class="sysml-frame" data-kind="${escapeAttribute(frame.kind)}">`,
2333
+ ` <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)}"/>`,
2334
+ ` <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)}"/>`,
2335
+ ` <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>`,
2336
+ "</g>"
2337
+ ].join("\n");
2338
+ }
2339
+ function renderSwimlane(swimlane) {
2340
+ if (swimlane.box === void 0) {
2341
+ return [];
2342
+ }
2343
+ const lines = [
2344
+ ` <g class="swimlane" data-id="${escapeAttribute(swimlane.id)}">`,
2345
+ ` <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}"/>`
2346
+ ];
2347
+ for (const lane of swimlane.lanes) {
2348
+ if (lane.box === void 0) {
2349
+ continue;
2350
+ }
2351
+ lines.push(
2352
+ ` <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}"/>`
2353
+ );
2354
+ if (lane.label?.text !== void 0) {
2355
+ lines.push(
2356
+ ` <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>`
2357
+ );
2358
+ }
2359
+ }
2360
+ lines.push(" </g>");
2361
+ return lines;
2362
+ }
2363
+ function renderPorts(node) {
2364
+ return (node.ports ?? []).flatMap((port) => [
2365
+ ` <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)}"/>`,
2366
+ ...port.label?.text === void 0 ? [] : [
2367
+ ` <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>`
2368
+ ]
2369
+ ]);
2370
+ }
2371
+ function renderCompartments(node) {
2372
+ const compartments2 = node.compartments;
2373
+ if (compartments2 === void 0) {
2374
+ return [];
2375
+ }
2376
+ const rows = [
2377
+ ...compartments2.stereotype === void 0 ? [] : [{ className: "stereotype", text: compartments2.stereotype }],
2378
+ {
2379
+ className: "name",
2380
+ text: compartments2.name ?? node.label?.text ?? node.id
2381
+ },
2382
+ ...(compartments2.properties ?? []).map((text) => ({
2383
+ className: "properties",
2384
+ text
2385
+ })),
2386
+ ...(compartments2.constraints ?? []).map((text) => ({
2387
+ className: "constraints",
2388
+ text
2389
+ }))
2390
+ ];
2391
+ const lineHeight = 16;
2392
+ const lines = [
2393
+ ` <g class="compartment" data-for="${escapeAttribute(node.id)}">`
2394
+ ];
2395
+ for (let index = 0; index < rows.length; index += 1) {
2396
+ const row = rows[index];
2397
+ if (row === void 0) {
2398
+ continue;
2399
+ }
2400
+ const y = node.box.y + 18 + index * lineHeight;
2401
+ if (index > 1) {
2402
+ lines.push(
2403
+ ` <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}"/>`
2404
+ );
2405
+ }
2406
+ lines.push(
2407
+ ` <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>`
2408
+ );
2409
+ }
2410
+ lines.push(" </g>");
2411
+ return lines;
2412
+ }
2413
+ function portLabelX(x, side) {
2414
+ if (side === "left") {
2415
+ return x - 8;
2416
+ }
2417
+ if (side === "right") {
2418
+ return x + 8;
2419
+ }
2420
+ return x + 8;
2421
+ }
2017
2422
  function renderRect(box, attributes) {
2018
2423
  return `<rect ${attributes} x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}"/>`;
2019
2424
  }
2020
2425
  function renderLabel(label, box, item) {
2021
2426
  const labelLayout = item.labelLayout;
2022
2427
  if (labelLayout?.lines !== void 0 && labelLayout.lines.length > 0) {
2428
+ const offset = isAbsoluteLabelLayout(labelLayout.box, box) ? { x: 0, y: 0 } : { x: box.x, y: box.y };
2023
2429
  return [
2024
2430
  ` <text class="label" data-for="${escapeAttribute(item.id)}" font-family="${FONT_FAMILY}" font-size="${formatNumber(labelLayout.font.fontSize)}" fill="#111827">`,
2025
2431
  ...labelLayout.lines.map(
2026
- (line) => ` <tspan x="${formatNumber(line.box.x)}" y="${formatNumber(line.baselineY)}">${escapeXml(line.text)}</tspan>`
2432
+ (line) => ` <tspan x="${formatNumber(offset.x + line.box.x)}" y="${formatNumber(offset.y + line.baselineY)}">${escapeXml(line.text)}</tspan>`
2027
2433
  ),
2028
2434
  " </text>"
2029
2435
  ];
@@ -2035,15 +2441,91 @@ function renderLabel(label, box, item) {
2035
2441
  ` <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>`
2036
2442
  ];
2037
2443
  }
2038
- function renderEdgePath(points, id) {
2039
- if (points.length < 2) {
2444
+ function renderEdgePath(edge) {
2445
+ if (edge.points.length < 2) {
2040
2446
  return void 0;
2041
2447
  }
2042
- return `<path class="edge" data-id="${escapeAttribute(id)}" d="${formatPath(points)}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"/>`;
2448
+ const dash = edge.style === "dashed" ? ' stroke-dasharray="6 4"' : "";
2449
+ return `<path class="edge" data-id="${escapeAttribute(edge.id)}" d="${formatPath(pathPointsBeforeArrowhead(edge.points))}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"${dash}/>`;
2043
2450
  }
2044
- function renderArrowhead(points, id) {
2451
+ function renderEdgeLabel(edge) {
2452
+ if (edge.label?.text === void 0 || edge.points.length < 2) {
2453
+ return [];
2454
+ }
2455
+ const placement = labelPlacementOnPolyline(edge.points);
2456
+ if (placement === void 0) {
2457
+ return [];
2458
+ }
2459
+ return [
2460
+ ` <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>`
2461
+ ];
2462
+ }
2463
+ function renderArrowhead(edge) {
2464
+ const arrowhead = computeArrowhead(edge.points);
2465
+ const fill = edge.arrowhead === "hollowTriangle" ? "none" : EDGE_STROKE;
2466
+ return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(edge.id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${fill}" stroke="${EDGE_STROKE}"/>`;
2467
+ }
2468
+ function labelPlacementOnPolyline(points) {
2469
+ const segments = nonZeroSegments(points);
2470
+ const totalLength = segments.reduce(
2471
+ (sum, segment) => sum + segment.length,
2472
+ 0
2473
+ );
2474
+ if (totalLength <= 0) {
2475
+ return void 0;
2476
+ }
2477
+ let remaining = totalLength / 2;
2478
+ for (const segment of segments) {
2479
+ if (remaining <= segment.length) {
2480
+ const ratio = remaining / segment.length;
2481
+ const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
2482
+ const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
2483
+ const offset2 = labelOffset(segment);
2484
+ return { x: x + offset2.x, y: y + offset2.y };
2485
+ }
2486
+ remaining -= segment.length;
2487
+ }
2488
+ const last = segments.at(-1);
2489
+ if (last === void 0) {
2490
+ return void 0;
2491
+ }
2492
+ const offset = labelOffset(last);
2493
+ return { x: last.end.x + offset.x, y: last.end.y + offset.y };
2494
+ }
2495
+ function nonZeroSegments(points) {
2496
+ const segments = [];
2497
+ for (let index = 0; index < points.length - 1; index += 1) {
2498
+ const start = points[index];
2499
+ const end = points[index + 1];
2500
+ if (start === void 0 || end === void 0) {
2501
+ continue;
2502
+ }
2503
+ const length = Math.hypot(end.x - start.x, end.y - start.y);
2504
+ if (length > 0) {
2505
+ segments.push({ start, end, length });
2506
+ }
2507
+ }
2508
+ return segments;
2509
+ }
2510
+ function labelOffset(segment) {
2511
+ const offset = 10;
2512
+ const dx = segment.end.x - segment.start.x;
2513
+ const dy = segment.end.y - segment.start.y;
2514
+ return {
2515
+ x: -dy / segment.length * offset,
2516
+ y: dx / segment.length * offset
2517
+ };
2518
+ }
2519
+ function pathPointsBeforeArrowhead(points) {
2045
2520
  const arrowhead = computeArrowhead(points);
2046
- return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${EDGE_STROKE}" stroke="${EDGE_STROKE}"/>`;
2521
+ const base = {
2522
+ x: (arrowhead.left.x + arrowhead.right.x) / 2,
2523
+ y: (arrowhead.left.y + arrowhead.right.y) / 2
2524
+ };
2525
+ return [...points.slice(0, -1), base];
2526
+ }
2527
+ function isAbsoluteLabelLayout(labelBox, itemBox) {
2528
+ return labelBox.x >= itemBox.x && labelBox.y >= itemBox.y && labelBox.x + labelBox.width <= itemBox.x + itemBox.width && labelBox.y + labelBox.height <= itemBox.y + itemBox.height;
2047
2529
  }
2048
2530
  function shapePoints(shape, box) {
2049
2531
  const left = box.x;
@@ -2230,20 +2712,33 @@ function isValidDimension(value) {
2230
2712
  // src/routing/routes.ts
2231
2713
  function routeEdge(input) {
2232
2714
  const diagnostics = [];
2715
+ const defaultAnchors = defaultAnchorsForGeometry(
2716
+ input.source.box,
2717
+ input.target.box,
2718
+ input.direction
2719
+ );
2233
2720
  const source = getEdgePort(
2234
2721
  input.source,
2235
2722
  input.target.center,
2236
- input.sourceAnchor
2723
+ input.sourceAnchor ?? defaultAnchors.sourceAnchor
2237
2724
  );
2238
2725
  const target = getEdgePort(
2239
2726
  input.target,
2240
2727
  input.source.center,
2241
- input.targetAnchor
2728
+ input.targetAnchor ?? defaultAnchors.targetAnchor
2242
2729
  );
2243
2730
  if ((input.kind ?? "orthogonal") === "straight") {
2244
2731
  return { points: simplifyRoute([source, target]), diagnostics };
2245
2732
  }
2246
2733
  const candidates = orthogonalCandidates(source, target, input.direction);
2734
+ candidates.push(
2735
+ ...expandedObstacleCandidates(
2736
+ source,
2737
+ target,
2738
+ input.direction,
2739
+ input.obstacles ?? []
2740
+ )
2741
+ );
2247
2742
  for (const candidate of candidates) {
2248
2743
  if (!routeIntersectsObstacles(candidate, input.obstacles ?? [])) {
2249
2744
  return { points: simplifyRoute(candidate), diagnostics };
@@ -2282,27 +2777,113 @@ function simplifyRoute(points) {
2282
2777
  function orthogonalCandidates(source, target, direction) {
2283
2778
  const midpointX = (source.x + target.x) / 2;
2284
2779
  const midpointY = (source.y + target.y) / 2;
2285
- const candidates = [
2286
- [source, { x: target.x, y: source.y }, target],
2287
- [source, { x: source.x, y: target.y }, target]
2288
- ];
2780
+ const candidates = [];
2289
2781
  if (direction === "TB" || direction === "BT") {
2290
2782
  candidates.push([
2291
2783
  source,
2292
- { x: midpointX, y: source.y },
2293
- { x: midpointX, y: target.y },
2784
+ { x: source.x, y: midpointY },
2785
+ { x: target.x, y: midpointY },
2294
2786
  target
2295
2787
  ]);
2296
2788
  } else {
2297
2789
  candidates.push([
2298
2790
  source,
2299
- { x: source.x, y: midpointY },
2300
- { x: target.x, y: midpointY },
2791
+ { x: midpointX, y: source.y },
2792
+ { x: midpointX, y: target.y },
2301
2793
  target
2302
2794
  ]);
2303
2795
  }
2796
+ candidates.push(
2797
+ [source, { x: target.x, y: source.y }, target],
2798
+ [source, { x: source.x, y: target.y }, target]
2799
+ );
2304
2800
  return candidates;
2305
2801
  }
2802
+ function defaultSourceAnchor(direction) {
2803
+ switch (direction) {
2804
+ case "LR":
2805
+ return "right";
2806
+ case "RL":
2807
+ return "left";
2808
+ case "TB":
2809
+ return "bottom";
2810
+ case "BT":
2811
+ return "top";
2812
+ }
2813
+ }
2814
+ function defaultAnchorsForGeometry(source, target, direction) {
2815
+ const dx = target.x + target.width / 2 - (source.x + source.width / 2);
2816
+ const dy = target.y + target.height / 2 - (source.y + source.height / 2);
2817
+ if (Math.abs(dy) > Math.abs(dx)) {
2818
+ return dy >= 0 ? { sourceAnchor: "bottom", targetAnchor: "top" } : { sourceAnchor: "top", targetAnchor: "bottom" };
2819
+ }
2820
+ if (Math.abs(dx) > 0) {
2821
+ return dx >= 0 ? { sourceAnchor: "right", targetAnchor: "left" } : { sourceAnchor: "left", targetAnchor: "right" };
2822
+ }
2823
+ return {
2824
+ sourceAnchor: defaultSourceAnchor(direction),
2825
+ targetAnchor: defaultTargetAnchor(direction)
2826
+ };
2827
+ }
2828
+ function defaultTargetAnchor(direction) {
2829
+ switch (direction) {
2830
+ case "LR":
2831
+ return "left";
2832
+ case "RL":
2833
+ return "right";
2834
+ case "TB":
2835
+ return "top";
2836
+ case "BT":
2837
+ return "bottom";
2838
+ }
2839
+ }
2840
+ function expandedObstacleCandidates(source, target, direction, obstacles) {
2841
+ if (obstacles.length === 0) {
2842
+ return [];
2843
+ }
2844
+ const margin = 16;
2845
+ const candidates = [];
2846
+ if (direction === "TB" || direction === "BT") {
2847
+ const lanes = sortedUniqueLanes(
2848
+ obstacles.flatMap((obstacle) => [
2849
+ obstacle.x - margin,
2850
+ obstacle.x + obstacle.width + margin
2851
+ ]),
2852
+ (source.x + target.x) / 2
2853
+ );
2854
+ for (const laneX of lanes) {
2855
+ candidates.push([
2856
+ source,
2857
+ { x: laneX, y: source.y },
2858
+ { x: laneX, y: target.y },
2859
+ target
2860
+ ]);
2861
+ }
2862
+ } else {
2863
+ const lanes = sortedUniqueLanes(
2864
+ obstacles.flatMap((obstacle) => [
2865
+ obstacle.y - margin,
2866
+ obstacle.y + obstacle.height + margin
2867
+ ]),
2868
+ (source.y + target.y) / 2
2869
+ );
2870
+ for (const laneY of lanes) {
2871
+ candidates.push([
2872
+ source,
2873
+ { x: source.x, y: laneY },
2874
+ { x: target.x, y: laneY },
2875
+ target
2876
+ ]);
2877
+ }
2878
+ }
2879
+ return candidates;
2880
+ }
2881
+ function sortedUniqueLanes(lanes, midpoint) {
2882
+ return [...new Set(lanes)].filter((lane) => Number.isFinite(lane)).sort((left, right) => {
2883
+ const distance = Math.abs(left - midpoint) - Math.abs(right - midpoint);
2884
+ return distance === 0 ? left - right : distance;
2885
+ });
2886
+ }
2306
2887
  function routeIntersectsObstacles(points, obstacles) {
2307
2888
  for (let index = 0; index < points.length - 1; index += 1) {
2308
2889
  const a = points[index];
@@ -2381,12 +2962,17 @@ function solveDiagram(diagram, options = {}) {
2381
2962
  options,
2382
2963
  diagnostics
2383
2964
  );
2965
+ const coordinatedSwimlanes = coordinateSwimlanes(
2966
+ diagram.swimlanes ?? [],
2967
+ constrained.boxes
2968
+ );
2384
2969
  const groupBoxes = new Map(
2385
2970
  coordinatedGroups.map((group) => [group.id, group.box])
2386
2971
  );
2387
2972
  const coordinatedEdges = coordinateEdges(
2388
2973
  edges,
2389
2974
  nodeGeometryById,
2975
+ coordinatedNodes,
2390
2976
  [...nodeGeometryById.values()].map((geometry) => geometry.obstacleBox),
2391
2977
  diagram.direction,
2392
2978
  options,
@@ -2394,8 +2980,18 @@ function solveDiagram(diagram, options = {}) {
2394
2980
  );
2395
2981
  const allBoxes = [
2396
2982
  ...coordinatedNodes.map((node) => node.box),
2397
- ...groupBoxes.values()
2983
+ ...coordinatedNodes.flatMap(
2984
+ (node) => (node.ports ?? []).flatMap(
2985
+ (port) => port.label === void 0 ? [port.box] : [port.box, portLabelBox(port)]
2986
+ )
2987
+ ),
2988
+ ...groupBoxes.values(),
2989
+ ...coordinatedSwimlanes.flatMap(
2990
+ (swimlane) => swimlane.box === void 0 ? [] : [swimlane.box]
2991
+ )
2398
2992
  ];
2993
+ const contentBounds = allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes);
2994
+ const frame = diagram.frame === void 0 ? void 0 : coordinateFrame(diagram.frame, contentBounds);
2399
2995
  return {
2400
2996
  id: diagram.id,
2401
2997
  ...diagram.title === void 0 ? {} : { title: diagram.title },
@@ -2403,8 +2999,10 @@ function solveDiagram(diagram, options = {}) {
2403
2999
  nodes: coordinatedNodes,
2404
3000
  edges: coordinatedEdges,
2405
3001
  groups: coordinatedGroups,
3002
+ ...coordinatedSwimlanes.length === 0 ? {} : { swimlanes: coordinatedSwimlanes },
2406
3003
  diagnostics,
2407
- bounds: allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes),
3004
+ bounds: frame === void 0 ? contentBounds : unionBoxes([contentBounds, frame.box, frame.titleBox]),
3005
+ ...frame === void 0 ? {} : { frame },
2408
3006
  ...diagram.metadata === void 0 ? {} : { metadata: diagram.metadata }
2409
3007
  };
2410
3008
  }
@@ -2430,6 +3028,9 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
2430
3028
  coordinated.push({
2431
3029
  id: node.id,
2432
3030
  ...node.label === void 0 ? {} : { label: node.label },
3031
+ ...node.style === void 0 ? {} : { style: node.style },
3032
+ ...node.ports === void 0 ? {} : { ports: coordinatePorts(node, box, options.portShifting) },
3033
+ ...node.compartments === void 0 ? {} : { compartments: node.compartments },
2433
3034
  ...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
2434
3035
  shape: node.shape,
2435
3036
  ...node.metadata === void 0 ? {} : { metadata: node.metadata },
@@ -2440,6 +3041,142 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
2440
3041
  }
2441
3042
  return coordinated;
2442
3043
  }
3044
+ function coordinatePorts(node, nodeBox, portShifting) {
3045
+ const portsBySide = /* @__PURE__ */ new Map();
3046
+ for (const port of node.ports ?? []) {
3047
+ const ports = portsBySide.get(port.side) ?? [];
3048
+ ports.push(port);
3049
+ portsBySide.set(port.side, ports);
3050
+ }
3051
+ const coordinated = [];
3052
+ for (const [side, ports] of portsBySide) {
3053
+ const sorted = [...ports ?? []].sort((a, b) => {
3054
+ const order = (a.order ?? 0) - (b.order ?? 0);
3055
+ return order === 0 ? a.id.localeCompare(b.id) : order;
3056
+ });
3057
+ for (let index = 0; index < sorted.length; index += 1) {
3058
+ const port = sorted[index];
3059
+ if (port === void 0) {
3060
+ continue;
3061
+ }
3062
+ const anchor = portAnchor(
3063
+ nodeBox,
3064
+ side,
3065
+ index,
3066
+ sorted.length,
3067
+ portShifting
3068
+ );
3069
+ const box = portBox(anchor);
3070
+ coordinated.push({ ...port, box, anchor });
3071
+ }
3072
+ }
3073
+ return coordinated.sort((a, b) => a.id.localeCompare(b.id));
3074
+ }
3075
+ function portAnchor(nodeBox, side, index, count, portShifting) {
3076
+ const shiftingEnabled = portShifting?.enabled ?? true;
3077
+ const spacing = portShifting?.spacing ?? 24;
3078
+ const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
3079
+ switch (side) {
3080
+ case "left":
3081
+ return {
3082
+ x: nodeBox.x,
3083
+ y: nodeBox.y + nodeBox.height / 2 + centeredOffset
3084
+ };
3085
+ case "right":
3086
+ return {
3087
+ x: nodeBox.x + nodeBox.width,
3088
+ y: nodeBox.y + nodeBox.height / 2 + centeredOffset
3089
+ };
3090
+ case "top":
3091
+ return {
3092
+ x: nodeBox.x + nodeBox.width / 2 + centeredOffset,
3093
+ y: nodeBox.y
3094
+ };
3095
+ case "bottom":
3096
+ return {
3097
+ x: nodeBox.x + nodeBox.width / 2 + centeredOffset,
3098
+ y: nodeBox.y + nodeBox.height
3099
+ };
3100
+ }
3101
+ }
3102
+ function portBox(anchor) {
3103
+ const size = 10;
3104
+ return {
3105
+ x: anchor.x - size / 2,
3106
+ y: anchor.y - size / 2,
3107
+ width: size,
3108
+ height: size
3109
+ };
3110
+ }
3111
+ function portLabelBox(port) {
3112
+ const textWidth = Math.max(0, (port.label?.text.length ?? 0) * 6);
3113
+ const height = 12;
3114
+ const gap = 8;
3115
+ const x = port.side === "left" ? port.anchor.x - gap - textWidth : port.anchor.x + gap;
3116
+ return {
3117
+ x,
3118
+ y: port.anchor.y - 8 - height,
3119
+ width: textWidth,
3120
+ height
3121
+ };
3122
+ }
3123
+ function coordinateSwimlanes(swimlanes, nodeBoxes) {
3124
+ const titleSize = 28;
3125
+ const padding = 16;
3126
+ return swimlanes.map((swimlane) => {
3127
+ const laneBoxes = swimlane.lanes.flatMap((lane) => {
3128
+ const childBoxes = lane.children.map((child) => nodeBoxes.get(child)).filter((box) => box !== void 0);
3129
+ return childBoxes.length === 0 ? [] : [unionBoxes(childBoxes)];
3130
+ });
3131
+ const laneUnion = laneBoxes.length === 0 ? { x: 0, y: 0, width: 120, height: 80 } : unionBoxes(laneBoxes);
3132
+ const outer = expand(laneUnion, padding, titleSize);
3133
+ const laneCount = Math.max(1, swimlane.lanes.length);
3134
+ const lanes = swimlane.lanes.map((lane, index) => {
3135
+ const box = swimlane.orientation === "vertical" ? {
3136
+ x: outer.x + outer.width / laneCount * index,
3137
+ y: outer.y,
3138
+ width: outer.width / laneCount,
3139
+ height: outer.height
3140
+ } : {
3141
+ x: outer.x,
3142
+ y: outer.y + outer.height / laneCount * index,
3143
+ width: outer.width,
3144
+ height: outer.height / laneCount
3145
+ };
3146
+ return { ...lane, box };
3147
+ });
3148
+ return { ...swimlane, lanes, box: outer };
3149
+ });
3150
+ }
3151
+ function coordinateFrame(frame, contentBounds) {
3152
+ const padding = 32;
3153
+ const titleHeight = 28;
3154
+ const titleWidth = Math.max(180, frame.titleTab.length * 7);
3155
+ const box = {
3156
+ x: contentBounds.x - padding,
3157
+ y: contentBounds.y - padding - titleHeight,
3158
+ width: contentBounds.width + padding * 2,
3159
+ height: contentBounds.height + padding * 2 + titleHeight
3160
+ };
3161
+ return {
3162
+ ...frame,
3163
+ box,
3164
+ titleBox: {
3165
+ x: box.x,
3166
+ y: box.y,
3167
+ width: Math.min(titleWidth, box.width * 0.8),
3168
+ height: titleHeight
3169
+ }
3170
+ };
3171
+ }
3172
+ function expand(box, padding, titleSize) {
3173
+ return {
3174
+ x: box.x - padding,
3175
+ y: box.y - padding - titleSize,
3176
+ width: box.width + padding * 2,
3177
+ height: box.height + padding * 2 + titleSize
3178
+ };
3179
+ }
2443
3180
  function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
2444
3181
  const coordinated = [];
2445
3182
  const groupBoxes = /* @__PURE__ */ new Map();
@@ -2488,8 +3225,11 @@ function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
2488
3225
  }
2489
3226
  return coordinated;
2490
3227
  }
2491
- function coordinateEdges(edges, nodes, obstacles, direction, options, diagnostics) {
3228
+ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, options, diagnostics) {
2492
3229
  const coordinated = [];
3230
+ const coordinatedNodeById = new Map(
3231
+ coordinatedNodes.map((node) => [node.id, node])
3232
+ );
2493
3233
  for (const edge of edges) {
2494
3234
  const source = nodes.get(edge.source.nodeId);
2495
3235
  const target = nodes.get(edge.target.nodeId);
@@ -2507,11 +3247,13 @@ function coordinateEdges(edges, nodes, obstacles, direction, options, diagnostic
2507
3247
  });
2508
3248
  continue;
2509
3249
  }
3250
+ const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
3251
+ const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
2510
3252
  const route = routeEdge({
2511
3253
  kind: options.routeKind ?? "orthogonal",
2512
3254
  direction,
2513
- source,
2514
- target,
3255
+ source: portGeometry(source, sourcePort),
3256
+ target: portGeometry(target, targetPort),
2515
3257
  ...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
2516
3258
  ...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
2517
3259
  obstacles: obstacles.filter(
@@ -2531,6 +3273,21 @@ function coordinateEdges(edges, nodes, obstacles, direction, options, diagnostic
2531
3273
  }
2532
3274
  return coordinated;
2533
3275
  }
3276
+ function portGeometry(nodeGeometry, port) {
3277
+ if (port === void 0) {
3278
+ return nodeGeometry;
3279
+ }
3280
+ return {
3281
+ ...nodeGeometry,
3282
+ box: port.box,
3283
+ center: port.anchor,
3284
+ anchors: nodeGeometry.anchors.map((anchor) => ({
3285
+ name: anchor.name,
3286
+ point: port.anchor
3287
+ })),
3288
+ obstacleBox: port.box
3289
+ };
3290
+ }
2534
3291
  function stableById(items) {
2535
3292
  return [...items].sort((a, b) => a.id.localeCompare(b.id));
2536
3293
  }
@@ -2591,7 +3348,8 @@ function renderDiagramDsl(source, options = {}) {
2591
3348
  return { diagnostics };
2592
3349
  }
2593
3350
  const solved = solveDiagram(normalized.diagram, {
2594
- routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal"
3351
+ routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
3352
+ ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting)
2595
3353
  });
2596
3354
  const solveDiagnostics = solved.diagnostics.map(toSolveDiagnostic);
2597
3355
  if (hasErrorDiagnostics2(solveDiagnostics)) {
@@ -2630,6 +3388,22 @@ function renderDiagramDsl(source, options = {}) {
2630
3388
  function toSolveDiagnostic(diagnostic) {
2631
3389
  return { ...diagnostic, layer: "solve" };
2632
3390
  }
3391
+ function solvePortShiftingOption(value) {
3392
+ if (!isJsonObject(value)) {
3393
+ return {};
3394
+ }
3395
+ const portShifting = {};
3396
+ if (value.enabled === false) {
3397
+ portShifting.enabled = false;
3398
+ }
3399
+ if (typeof value.spacing === "number") {
3400
+ portShifting.spacing = value.spacing;
3401
+ }
3402
+ return { portShifting };
3403
+ }
3404
+ function isJsonObject(value) {
3405
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3406
+ }
2633
3407
  function toExportDiagnostic(diagnostic) {
2634
3408
  return { ...diagnostic, layer: "export" };
2635
3409
  }
@@ -2776,11 +3550,13 @@ exports.canonicalize = canonicalize;
2776
3550
  exports.computeArrowhead = computeArrowhead;
2777
3551
  exports.computeContainerGeometry = computeContainerGeometry;
2778
3552
  exports.computeShapeGeometry = computeShapeGeometry;
3553
+ exports.createDefaultTextMeasurer = createDefaultTextMeasurer;
2779
3554
  exports.expandBox = expandBox;
2780
3555
  exports.exportExcalidraw = exportExcalidraw;
2781
3556
  exports.exportSvg = exportSvg;
2782
3557
  exports.fitLabel = fitLabel;
2783
3558
  exports.getEdgePort = getEdgePort;
3559
+ exports.installNodeCanvasRuntime = installNodeCanvasRuntime;
2784
3560
  exports.intersectsAabb = intersectsAabb;
2785
3561
  exports.isPretextRuntimeAvailable = isPretextRuntimeAvailable;
2786
3562
  exports.normalizeDiagramDsl = normalizeDiagramDsl;