@statelyai/graph 0.4.0 → 0.5.0

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.
Files changed (45) hide show
  1. package/dist/{algorithms-CnTmuX9t.mjs → algorithms-DldwenLt.mjs} +1 -1
  2. package/dist/algorithms.d.mts +1 -1
  3. package/dist/algorithms.mjs +1 -1
  4. package/dist/{converter-C5DlzzHs.mjs → converter-B5CUD0r9.mjs} +2 -2
  5. package/dist/formats/adjacency-list/index.d.mts +1 -1
  6. package/dist/formats/adjacency-list/index.mjs +1 -1
  7. package/dist/formats/converter/index.d.mts +2 -2
  8. package/dist/formats/converter/index.mjs +1 -1
  9. package/dist/formats/cytoscape/index.d.mts +1 -1
  10. package/dist/formats/cytoscape/index.mjs +1 -1
  11. package/dist/formats/d3/index.d.mts +1 -1
  12. package/dist/formats/d3/index.mjs +1 -1
  13. package/dist/formats/dot/index.d.mts +1 -1
  14. package/dist/formats/dot/index.mjs +1 -1
  15. package/dist/formats/edge-list/index.d.mts +1 -1
  16. package/dist/formats/edge-list/index.mjs +1 -1
  17. package/dist/formats/elk/index.d.mts +61 -0
  18. package/dist/formats/elk/index.mjs +176 -0
  19. package/dist/formats/gexf/index.d.mts +1 -1
  20. package/dist/formats/gexf/index.mjs +1 -1
  21. package/dist/formats/gml/index.d.mts +1 -1
  22. package/dist/formats/gml/index.mjs +1 -1
  23. package/dist/formats/graphml/index.d.mts +1 -1
  24. package/dist/formats/graphml/index.mjs +1 -1
  25. package/dist/formats/jgf/index.d.mts +1 -1
  26. package/dist/formats/jgf/index.mjs +1 -1
  27. package/dist/formats/mermaid/index.d.mts +46 -33
  28. package/dist/formats/mermaid/index.mjs +315 -31
  29. package/dist/formats/tgf/index.d.mts +1 -1
  30. package/dist/formats/tgf/index.mjs +1 -1
  31. package/dist/formats/xyflow/index.d.mts +1 -1
  32. package/dist/index.d.mts +1 -1
  33. package/dist/index.mjs +3 -3
  34. package/dist/queries.d.mts +1 -1
  35. package/dist/queries.mjs +1 -1
  36. package/dist/schemas.d.mts +37 -4
  37. package/dist/schemas.mjs +26 -5
  38. package/dist/{types-Bq_fmLwW.d.mts → types-FBZCrmnG.d.mts} +6 -6
  39. package/package.json +7 -1
  40. package/schemas/edge.schema.json +32 -1
  41. package/schemas/graph.schema.json +114 -4
  42. package/schemas/node.schema.json +45 -2
  43. /package/dist/{adjacency-list-Bv4tfiM3.mjs → adjacency-list-fldj-QAL.mjs} +0 -0
  44. /package/dist/{edge-list-R1SUbHwe.mjs → edge-list-Br05wXMg.mjs} +0 -0
  45. /package/dist/{indexing-DitHphT7.mjs → indexing-DyfgLuzw.mjs} +0 -0
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-C5DlzzHs.mjs";
1
+ import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
2
2
 
3
3
  //#region src/formats/mermaid/shared.ts
4
4
  const MERMAID_TO_DIRECTION = {
@@ -146,6 +146,7 @@ function fromMermaidSequence(input) {
146
146
  let autonumber = false;
147
147
  let edgeCounter = 0;
148
148
  let seqNum = 0;
149
+ let currentBox = null;
149
150
  const blockStack = [];
150
151
  function ensureNode(id, actorType = "participant") {
151
152
  if (!nodeMap.has(id)) nodeMap.set(id, {
@@ -173,7 +174,7 @@ function fromMermaidSequence(input) {
173
174
  autonumber = true;
174
175
  continue;
175
176
  }
176
- const participantMatch = line.match(/^(participant|actor)\s+(\S+?)(?:\s+as\s+(.+))?$/);
177
+ const participantMatch = line.match(/^(participant|actor|boundary|control|entity|database|collections|queue)\s+(\S+?)(?:\s+as\s+(.+))?$/);
177
178
  if (participantMatch) {
178
179
  const actorType = participantMatch[1];
179
180
  const id = participantMatch[2];
@@ -185,9 +186,10 @@ function fromMermaidSequence(input) {
185
186
  node.data.alias = alias;
186
187
  node.label = alias;
187
188
  }
189
+ if (currentBox) node.data.box = { ...currentBox };
188
190
  continue;
189
191
  }
190
- const createMatch = line.match(/^create\s+(participant|actor)\s+(\S+?)(?:\s+as\s+(.+))?$/);
192
+ const createMatch = line.match(/^create\s+(participant|actor|boundary|control|entity|database|collections|queue)\s+(\S+?)(?:\s+as\s+(.+))?$/);
191
193
  if (createMatch) {
192
194
  const actorType = createMatch[1];
193
195
  const id = createMatch[2];
@@ -226,6 +228,22 @@ function fromMermaidSequence(input) {
226
228
  });
227
229
  continue;
228
230
  }
231
+ const boxMatch = line.match(/^box\s*(.*)?$/);
232
+ if (boxMatch) {
233
+ const rest = (boxMatch[1] ?? "").trim();
234
+ let color;
235
+ let title;
236
+ const colorTitleMatch = rest.match(/^(rgb\([^)]*\)|#[a-fA-F0-9]+|[a-zA-Z]+)?\s*(.*)$/);
237
+ if (colorTitleMatch) {
238
+ color = colorTitleMatch[1]?.trim() || void 0;
239
+ title = colorTitleMatch[2]?.trim() || void 0;
240
+ }
241
+ currentBox = {
242
+ ...color && { color },
243
+ ...title && { title }
244
+ };
245
+ continue;
246
+ }
229
247
  if (/^(loop|alt|opt|par|critical|break)\s+/.test(line) || line.startsWith("rect ")) {
230
248
  const spaceIdx = line.indexOf(" ");
231
249
  const keyword = line.slice(0, spaceIdx);
@@ -290,6 +308,10 @@ function fromMermaidSequence(input) {
290
308
  continue;
291
309
  }
292
310
  if (line === "end") {
311
+ if (currentBox && blockStack.length === 0) {
312
+ currentBox = null;
313
+ continue;
314
+ }
293
315
  if (blockStack.length > 0) {
294
316
  const finished = blockStack.pop();
295
317
  const block = buildBlock(finished);
@@ -300,6 +322,25 @@ function fromMermaidSequence(input) {
300
322
  }
301
323
  continue;
302
324
  }
325
+ const noteMatch = line.match(/^Note\s+(left of|right of|over)\s+([^:]+):\s*(.*)$/i);
326
+ if (noteMatch) {
327
+ const posRaw = noteMatch[1].toLowerCase();
328
+ const position = posRaw === "left of" ? "left" : posRaw === "right of" ? "right" : "over";
329
+ const actorsPart = noteMatch[2].trim();
330
+ const text = noteMatch[3].trim();
331
+ const actorIds = actorsPart.split(",").map((s) => s.trim());
332
+ for (const actorId of actorIds) {
333
+ ensureNode(actorId);
334
+ const node = nodeMap.get(actorId);
335
+ if (!node.data.notes) node.data.notes = [];
336
+ node.data.notes.push({
337
+ position,
338
+ text,
339
+ ...position === "over" && actorIds.length > 1 ? { over: actorIds } : {}
340
+ });
341
+ }
342
+ continue;
343
+ }
303
344
  if (/^Note\s+(left|right|over)\s+/i.test(line)) continue;
304
345
  const msgMatch = line.match(MESSAGE_RE);
305
346
  if (msgMatch) {
@@ -443,12 +484,38 @@ function toMermaidSequence(graph) {
443
484
  const lines = ["sequenceDiagram"];
444
485
  const gd = graph.data;
445
486
  if (gd?.autonumber) lines.push(" autonumber");
487
+ const boxGroups = /* @__PURE__ */ new Map();
488
+ const noBoxNodes = [];
446
489
  for (const node of graph.nodes) {
490
+ const box = node.data?.box;
491
+ if (box) {
492
+ const key = JSON.stringify(box);
493
+ if (!boxGroups.has(key)) boxGroups.set(key, {
494
+ box,
495
+ nodes: []
496
+ });
497
+ boxGroups.get(key).nodes.push(node);
498
+ } else noBoxNodes.push(node);
499
+ }
500
+ function emitParticipant(node, indent$1) {
447
501
  const d = node.data;
448
- const keyword = d?.actorType === "actor" ? "actor" : "participant";
502
+ const keyword = d?.actorType ?? "participant";
449
503
  const alias = d?.alias ? ` as ${escapeMermaidLabel(d.alias)}` : "";
450
- if (d?.created) lines.push(` create ${keyword} ${node.id}${alias}`);
451
- else lines.push(` ${keyword} ${node.id}${alias}`);
504
+ if (d?.created) lines.push(`${indent$1}create ${keyword} ${node.id}${alias}`);
505
+ else lines.push(`${indent$1}${keyword} ${node.id}${alias}`);
506
+ }
507
+ for (const { box, nodes: boxNodes } of boxGroups.values()) {
508
+ const colorStr = box.color ? ` ${box.color}` : "";
509
+ const titleStr = box.title ? ` ${box.title}` : "";
510
+ lines.push(` box${colorStr}${titleStr}`);
511
+ for (const node of boxNodes) emitParticipant(node, " ");
512
+ lines.push(" end");
513
+ }
514
+ for (const node of noBoxNodes) emitParticipant(node, " ");
515
+ for (const node of graph.nodes) if (node.data?.notes) for (const note of node.data.notes) if (note.position === "over" && note.over && note.over.length > 1) lines.push(` Note over ${note.over.join(",")}: ${escapeMermaidLabel(note.text)}`);
516
+ else {
517
+ const posStr = note.position === "left" ? "left of" : note.position === "right" ? "right of" : "over";
518
+ lines.push(` Note ${posStr} ${node.id}: ${escapeMermaidLabel(note.text)}`);
452
519
  }
453
520
  const blocks = gd?.blocks ?? [];
454
521
  const edgeIdSet = new Set(graph.edges.map((e) => e.id));
@@ -705,6 +772,32 @@ const SHAPE_OPENERS = [
705
772
  const SHAPE_TO_BRACKETS$1 = {};
706
773
  for (const [opener, closer, name] of SHAPE_OPENERS) SHAPE_TO_BRACKETS$1[name] = [opener, closer];
707
774
  function parseNodeDecl(text) {
775
+ let className;
776
+ const classIdx = text.indexOf(":::");
777
+ if (classIdx >= 0) {
778
+ className = text.slice(classIdx + 3).trim();
779
+ text = text.slice(0, classIdx);
780
+ }
781
+ const atMatch = text.match(/^([a-zA-Z_][\w]*)@\{(.+)\}$/s);
782
+ if (atMatch) {
783
+ const id = atMatch[1];
784
+ const body = atMatch[2].trim();
785
+ let shape = "rectangle";
786
+ let label = "";
787
+ for (const line of body.split(",")) {
788
+ const kv = line.match(/^\s*(\w+)\s*:\s*"?([^"]*)"?\s*$/);
789
+ if (kv) {
790
+ if (kv[1] === "shape") shape = kv[2].trim();
791
+ else if (kv[1] === "label") label = kv[2].trim();
792
+ }
793
+ }
794
+ return {
795
+ id,
796
+ label,
797
+ shape,
798
+ ...className && { className }
799
+ };
800
+ }
708
801
  for (const [opener, closer, shapeName] of SHAPE_OPENERS) {
709
802
  const opIdx = text.indexOf(opener);
710
803
  if (opIdx < 0) continue;
@@ -714,13 +807,15 @@ function parseNodeDecl(text) {
714
807
  return {
715
808
  id,
716
809
  label: text.slice(opIdx + opener.length, text.length - closer.length).trim(),
717
- shape: shapeName
810
+ shape: shapeName,
811
+ ...className && { className }
718
812
  };
719
813
  }
720
814
  if (/^[a-zA-Z_][\w]*$/.test(text)) return {
721
815
  id: text,
722
816
  label: "",
723
- shape: "rectangle"
817
+ shape: "rectangle",
818
+ ...className && { className }
724
819
  };
725
820
  return null;
726
821
  }
@@ -836,6 +931,12 @@ const EDGE_ARROWS = [
836
931
  arrowType: "none",
837
932
  endMarker: "arrow",
838
933
  bidirectional: false
934
+ }],
935
+ [/^~~~$/, {
936
+ stroke: "invisible",
937
+ arrowType: "none",
938
+ endMarker: "arrow",
939
+ bidirectional: false
839
940
  }]
840
941
  ];
841
942
  function findEdge(line) {
@@ -903,7 +1004,7 @@ function fromMermaidFlowchart(input) {
903
1004
  const classAssignments = {};
904
1005
  let edgeCounter = 0;
905
1006
  const parentStack = [null];
906
- function ensureNode(id, label, shape) {
1007
+ function ensureNode(id, label, shape, className) {
907
1008
  if (!nodeMap.has(id)) nodeMap.set(id, {
908
1009
  type: "node",
909
1010
  id,
@@ -916,6 +1017,10 @@ function fromMermaidFlowchart(input) {
916
1017
  const node = nodeMap.get(id);
917
1018
  if (label && !node.label) node.label = label;
918
1019
  if (shape && shape !== "rectangle" && !node.shape) node.shape = shape;
1020
+ if (className) {
1021
+ if (!classAssignments[id]) classAssignments[id] = [];
1022
+ if (!classAssignments[id].includes(className)) classAssignments[id].push(className);
1023
+ }
919
1024
  return node;
920
1025
  }
921
1026
  for (let i = 1; i < lines.length; i++) {
@@ -928,7 +1033,15 @@ function fromMermaidFlowchart(input) {
928
1033
  parentStack.push(subId);
929
1034
  continue;
930
1035
  }
931
- if (/^direction\s+(TD|TB|BT|LR|RL)\s*$/.test(line)) continue;
1036
+ const dirMatch = line.match(/^direction\s+(TD|TB|BT|LR|RL)\s*$/);
1037
+ if (dirMatch) {
1038
+ const parentId = parentStack[parentStack.length - 1];
1039
+ if (parentId) {
1040
+ const parentNode = nodeMap.get(parentId);
1041
+ if (parentNode) parentNode.data.direction = MERMAID_TO_DIRECTION[dirMatch[1]];
1042
+ }
1043
+ continue;
1044
+ }
932
1045
  if (line === "end") {
933
1046
  if (parentStack.length > 1) parentStack.pop();
934
1047
  continue;
@@ -992,11 +1105,11 @@ function fromMermaidFlowchart(input) {
992
1105
  if (!edgeResult) break;
993
1106
  foundEdge = true;
994
1107
  const sourceDecl = parseNodeDecl(edgeResult.sourceText);
995
- if (sourceDecl) ensureNode(sourceDecl.id, sourceDecl.label, sourceDecl.shape);
1108
+ if (sourceDecl) ensureNode(sourceDecl.id, sourceDecl.label, sourceDecl.shape, sourceDecl.className);
996
1109
  const targetDecl = parseNodeDecl(edgeResult.targetText);
997
1110
  let targetId;
998
1111
  if (targetDecl) {
999
- ensureNode(targetDecl.id, targetDecl.label, targetDecl.shape);
1112
+ ensureNode(targetDecl.id, targetDecl.label, targetDecl.shape, targetDecl.className);
1000
1113
  targetId = targetDecl.id;
1001
1114
  remaining = "";
1002
1115
  } else {
@@ -1004,7 +1117,7 @@ function fromMermaidFlowchart(input) {
1004
1117
  if (nextEdge) {
1005
1118
  const tDecl = parseNodeDecl(nextEdge.sourceText);
1006
1119
  if (tDecl) {
1007
- ensureNode(tDecl.id, tDecl.label, tDecl.shape);
1120
+ ensureNode(tDecl.id, tDecl.label, tDecl.shape, tDecl.className);
1008
1121
  targetId = tDecl.id;
1009
1122
  } else {
1010
1123
  targetId = nextEdge.sourceText;
@@ -1039,7 +1152,7 @@ function fromMermaidFlowchart(input) {
1039
1152
  if (foundEdge) continue;
1040
1153
  const nodeDecl = parseNodeDecl(line);
1041
1154
  if (nodeDecl) {
1042
- ensureNode(nodeDecl.id, nodeDecl.label, nodeDecl.shape);
1155
+ ensureNode(nodeDecl.id, nodeDecl.label, nodeDecl.shape, nodeDecl.className);
1043
1156
  continue;
1044
1157
  }
1045
1158
  if (line.includes(";")) {
@@ -1108,6 +1221,10 @@ function toMermaidFlowchart(graph) {
1108
1221
  for (const node of children) if (isParent.has(node.id)) {
1109
1222
  const label = node.label ? `[${escapeMermaidLabel(node.label)}]` : "";
1110
1223
  lines.push(`${indent}subgraph ${node.id}${label}`);
1224
+ if (node.data?.direction) {
1225
+ const subDir = DIRECTION_TO_MERMAID[node.data.direction] ?? "TD";
1226
+ lines.push(`${indent} direction ${subDir}`);
1227
+ }
1111
1228
  writeNodes(node.id, indent + " ");
1112
1229
  lines.push(`${indent}end`);
1113
1230
  } else {
@@ -1120,12 +1237,18 @@ function toMermaidFlowchart(graph) {
1120
1237
  for (const edge of graph.edges) {
1121
1238
  const d = edge.data;
1122
1239
  let arrow;
1123
- if (d?.bidirectional) arrow = d.stroke === "thick" ? "<==>" : d.stroke === "dotted" ? "<-.->" : "<-->";
1240
+ if (d?.stroke === "invisible") arrow = "~~~";
1241
+ else if (d?.bidirectional) arrow = d.stroke === "thick" ? "<==>" : d.stroke === "dotted" ? "<-.->" : "<-->";
1124
1242
  else if (d?.stroke === "thick") arrow = d.arrowType === "none" ? "===" : "==>";
1125
1243
  else if (d?.stroke === "dotted") arrow = d.arrowType === "none" ? "-.-" : "-.->";
1126
- else if (d?.endMarker === "circle") arrow = "--o";
1127
- else if (d?.endMarker === "cross") arrow = "--x";
1128
- else arrow = d?.arrowType === "none" ? "---" : "-->";
1244
+ else {
1245
+ let prefix = "";
1246
+ if (d?.startMarker === "circle") prefix = "o";
1247
+ else if (d?.startMarker === "cross") prefix = "x";
1248
+ if (d?.endMarker === "circle") arrow = `${prefix}--o`;
1249
+ else if (d?.endMarker === "cross") arrow = `${prefix}--x`;
1250
+ else arrow = d?.arrowType === "none" ? `${prefix}---` : `${prefix}-->`;
1251
+ }
1129
1252
  let labelStr = "";
1130
1253
  if (edge.label) labelStr = `|${escapeMermaidLabel(edge.label)}|`;
1131
1254
  lines.push(` ${edge.sourceId} ${arrow}${labelStr} ${edge.targetId}`);
@@ -1177,7 +1300,11 @@ function fromMermaidState(input) {
1177
1300
  let edgeCounter = 0;
1178
1301
  let startCounter = 0;
1179
1302
  let endCounter = 0;
1303
+ let graphDirection;
1304
+ const classDefs = {};
1305
+ const classAssignments = {};
1180
1306
  const parentStack = [null];
1307
+ const regionCounters = /* @__PURE__ */ new Map();
1181
1308
  function ensureNode(id) {
1182
1309
  if (!nodeMap.has(id)) nodeMap.set(id, {
1183
1310
  type: "node",
@@ -1209,7 +1336,16 @@ function fromMermaidState(input) {
1209
1336
  for (let i = 1; i < lines.length; i++) {
1210
1337
  const line = lines[i].trim();
1211
1338
  if (!line) continue;
1212
- if (/^direction\s+(TD|TB|BT|LR|RL)\s*$/.test(line)) continue;
1339
+ const dirMatch = line.match(/^direction\s+(TD|TB|BT|LR|RL)\s*$/);
1340
+ if (dirMatch) {
1341
+ const dir = MERMAID_TO_DIRECTION[dirMatch[1]];
1342
+ const currentParent = parentStack[parentStack.length - 1];
1343
+ if (currentParent) {
1344
+ const parentNode = nodeMap.get(currentParent);
1345
+ if (parentNode) parentNode.data.direction = dir;
1346
+ } else graphDirection = dir;
1347
+ continue;
1348
+ }
1213
1349
  const compositeMatch = line.match(/^state\s+(\S+)\s*\{?\s*$/);
1214
1350
  if (compositeMatch && line.includes("{")) {
1215
1351
  const stateId = compositeMatch[1];
@@ -1235,7 +1371,66 @@ function fromMermaidState(input) {
1235
1371
  continue;
1236
1372
  }
1237
1373
  if (line === "}" || line === "end") {
1238
- if (parentStack.length > 1) parentStack.pop();
1374
+ if (parentStack.length > 1) {
1375
+ const top = parentStack[parentStack.length - 1];
1376
+ if (top && top.includes("_region_")) parentStack.pop();
1377
+ parentStack.pop();
1378
+ }
1379
+ continue;
1380
+ }
1381
+ if (/^-{2,}$/.test(line)) {
1382
+ const compositeParent = (() => {
1383
+ for (let s = parentStack.length - 1; s >= 0; s--) {
1384
+ const id = parentStack[s];
1385
+ if (id && !id.includes("_region_")) return id;
1386
+ }
1387
+ return null;
1388
+ })();
1389
+ if (compositeParent) {
1390
+ const parentNode = nodeMap.get(compositeParent);
1391
+ parentNode.data.stateType = "parallel";
1392
+ const regionIndex = regionCounters.get(compositeParent) ?? 0;
1393
+ if (regionIndex === 0) {
1394
+ const region0Id = `${compositeParent}_region_0`;
1395
+ const region0 = {
1396
+ type: "node",
1397
+ id: region0Id,
1398
+ parentId: compositeParent,
1399
+ initialNodeId: null,
1400
+ label: region0Id,
1401
+ data: {}
1402
+ };
1403
+ nodeMap.set(region0Id, region0);
1404
+ for (const node of nodeMap.values()) if (node.parentId === compositeParent && node.id !== region0Id) node.parentId = region0Id;
1405
+ const region1Id = `${compositeParent}_region_1`;
1406
+ const region1 = {
1407
+ type: "node",
1408
+ id: region1Id,
1409
+ parentId: compositeParent,
1410
+ initialNodeId: null,
1411
+ label: region1Id,
1412
+ data: {}
1413
+ };
1414
+ nodeMap.set(region1Id, region1);
1415
+ if (parentStack[parentStack.length - 1] === compositeParent) parentStack.push(region1Id);
1416
+ regionCounters.set(compositeParent, 2);
1417
+ } else {
1418
+ const top = parentStack[parentStack.length - 1];
1419
+ if (top && top.includes("_region_")) parentStack.pop();
1420
+ const nextRegionId = `${compositeParent}_region_${regionIndex}`;
1421
+ const nextRegion = {
1422
+ type: "node",
1423
+ id: nextRegionId,
1424
+ parentId: compositeParent,
1425
+ initialNodeId: null,
1426
+ label: nextRegionId,
1427
+ data: {}
1428
+ };
1429
+ nodeMap.set(nextRegionId, nextRegion);
1430
+ parentStack.push(nextRegionId);
1431
+ regionCounters.set(compositeParent, regionIndex + 1);
1432
+ }
1433
+ }
1239
1434
  continue;
1240
1435
  }
1241
1436
  const noteMatch = line.match(/^note\s+(left|right)\s+of\s+(\S+)\s*:\s*(.+)$/i);
@@ -1251,15 +1446,29 @@ function fromMermaidState(input) {
1251
1446
  });
1252
1447
  continue;
1253
1448
  }
1254
- const transMatch = line.match(/^(\S+)\s*-->\s*(\S+)\s*(?::\s*(.+))?$/);
1449
+ const transMatch = line.match(/^(\S+?)(?::::([\w]+))?\s*-->\s*(\S+?)(?::::([\w]+))?\s*(?::\s*(.+))?$/);
1255
1450
  if (transMatch) {
1256
1451
  let sourceId = transMatch[1];
1257
- let targetId = transMatch[2];
1258
- const label = transMatch[3]?.trim() ?? "";
1452
+ const sourceClass = transMatch[2];
1453
+ let targetId = transMatch[3];
1454
+ const targetClass = transMatch[4];
1455
+ const label = transMatch[5]?.trim() ?? "";
1259
1456
  if (sourceId === "[*]") sourceId = resolveStarNode("source");
1260
- else ensureNode(sourceId);
1457
+ else {
1458
+ ensureNode(sourceId);
1459
+ if (sourceClass) {
1460
+ if (!classAssignments[sourceId]) classAssignments[sourceId] = [];
1461
+ classAssignments[sourceId].push(sourceClass);
1462
+ }
1463
+ }
1261
1464
  if (targetId === "[*]") targetId = resolveStarNode("target");
1262
- else ensureNode(targetId);
1465
+ else {
1466
+ ensureNode(targetId);
1467
+ if (targetClass) {
1468
+ if (!classAssignments[targetId]) classAssignments[targetId] = [];
1469
+ classAssignments[targetId].push(targetClass);
1470
+ }
1471
+ }
1263
1472
  const edgeId = generateEdgeId(sourceId, targetId, edgeCounter++);
1264
1473
  edges.push({
1265
1474
  type: "edge",
@@ -1271,7 +1480,7 @@ function fromMermaidState(input) {
1271
1480
  });
1272
1481
  continue;
1273
1482
  }
1274
- const descMatch = line.match(/^(\S+)\s*:\s*(.+)$/);
1483
+ const descMatch = line.match(/^([a-zA-Z_][\w]*)\s*:\s*([^:].*)$/);
1275
1484
  if (descMatch) {
1276
1485
  const stateId = descMatch[1];
1277
1486
  const description = descMatch[2].trim();
@@ -1279,18 +1488,67 @@ function fromMermaidState(input) {
1279
1488
  node.data.description = description;
1280
1489
  continue;
1281
1490
  }
1282
- if (/^[a-zA-Z_][\w]*$/.test(line)) {
1283
- ensureNode(line);
1491
+ const classDefMatch = line.match(/^classDef\s+(\S+)\s+(.+)$/);
1492
+ if (classDefMatch) {
1493
+ const className = classDefMatch[1];
1494
+ const propsStr = classDefMatch[2];
1495
+ const props = {};
1496
+ for (const pair of propsStr.split(",")) {
1497
+ const [k, v] = pair.split(":").map((s) => s.trim());
1498
+ if (k && v) props[k] = v;
1499
+ }
1500
+ classDefs[className] = props;
1501
+ continue;
1502
+ }
1503
+ const classAssignMatch = line.match(/^class\s+(.+)\s+(\S+)\s*$/);
1504
+ if (classAssignMatch) {
1505
+ const nodeIds = classAssignMatch[1].split(",").map((s) => s.trim());
1506
+ const className = classAssignMatch[2];
1507
+ for (const nid of nodeIds) {
1508
+ if (!classAssignments[nid]) classAssignments[nid] = [];
1509
+ classAssignments[nid].push(className);
1510
+ }
1511
+ continue;
1512
+ }
1513
+ const bareMatch = line.match(/^([a-zA-Z_][\w]*)(?::::([\w]+))?$/);
1514
+ if (bareMatch) {
1515
+ const node = ensureNode(bareMatch[1]);
1516
+ if (bareMatch[2]) {
1517
+ if (!classAssignments[node.id]) classAssignments[node.id] = [];
1518
+ classAssignments[node.id].push(bareMatch[2]);
1519
+ }
1284
1520
  continue;
1285
1521
  }
1286
1522
  }
1523
+ for (const [nodeId, classes] of Object.entries(classAssignments)) {
1524
+ const node = nodeMap.get(nodeId);
1525
+ if (node) {
1526
+ node.data.classes = classes;
1527
+ for (const cls of classes) {
1528
+ const def = classDefs[cls];
1529
+ if (def) {
1530
+ if (def.fill) node.color = def.fill;
1531
+ const style = {};
1532
+ for (const [k, v] of Object.entries(def)) style[k] = v;
1533
+ if (Object.keys(style).length > 0) node.style = {
1534
+ ...node.style ?? {},
1535
+ ...style
1536
+ };
1537
+ }
1538
+ }
1539
+ }
1540
+ }
1287
1541
  return {
1288
1542
  id: "",
1289
1543
  type: "directed",
1290
1544
  initialNodeId: null,
1291
1545
  nodes: Array.from(nodeMap.values()),
1292
1546
  edges,
1293
- data: { diagramType: "stateDiagram" }
1547
+ data: {
1548
+ diagramType: "stateDiagram",
1549
+ ...Object.keys(classDefs).length > 0 && { classDefs }
1550
+ },
1551
+ direction: graphDirection
1294
1552
  };
1295
1553
  }
1296
1554
  /**
@@ -1302,6 +1560,10 @@ function fromMermaidState(input) {
1302
1560
  */
1303
1561
  function toMermaidState(graph) {
1304
1562
  const lines = ["stateDiagram-v2"];
1563
+ if (graph.direction) {
1564
+ const mDir = DIRECTION_TO_MERMAID[graph.direction];
1565
+ if (mDir) lines.push(` direction ${mDir}`);
1566
+ }
1305
1567
  const childrenMap = /* @__PURE__ */ new Map();
1306
1568
  for (const node of graph.nodes) {
1307
1569
  const pid = node.parentId ?? null;
@@ -1314,11 +1576,22 @@ function toMermaidState(graph) {
1314
1576
  const children = childrenMap.get(parentId) ?? [];
1315
1577
  for (const node of children) {
1316
1578
  if (node.data?.isStart || node.data?.isEnd) continue;
1317
- if (node.data?.stateType) lines.push(`${indent}state ${node.id} <<${node.data.stateType}>>`);
1579
+ if (node.id.includes("_region_")) continue;
1580
+ if (node.data?.stateType && node.data.stateType !== "parallel") lines.push(`${indent}state ${node.id} <<${node.data.stateType}>>`);
1318
1581
  if (node.data?.description) lines.push(`${indent}state "${escapeMermaidLabel(node.data.description)}" as ${node.id}`);
1319
1582
  if (isParent.has(node.id)) {
1320
1583
  lines.push(`${indent}state ${node.id} {`);
1321
- writeNodes(node.id, indent + " ");
1584
+ if (node.data?.direction) {
1585
+ const mDir = DIRECTION_TO_MERMAID[node.data.direction];
1586
+ if (mDir) lines.push(`${indent} direction ${mDir}`);
1587
+ }
1588
+ if (node.data?.stateType === "parallel") {
1589
+ const regions = (childrenMap.get(node.id) ?? []).filter((r) => r.id.includes("_region_"));
1590
+ for (let ri = 0; ri < regions.length; ri++) {
1591
+ if (ri > 0) lines.push(`${indent} --`);
1592
+ writeNodes(regions[ri].id, indent + " ");
1593
+ }
1594
+ } else writeNodes(node.id, indent + " ");
1322
1595
  lines.push(`${indent}}`);
1323
1596
  }
1324
1597
  if (node.data?.notes) for (const note of node.data.notes) lines.push(`${indent}note ${note.position} of ${node.id} : ${escapeMermaidLabel(note.text)}`);
@@ -1335,6 +1608,17 @@ function toMermaidState(graph) {
1335
1608
  const label = edge.label ? ` : ${escapeMermaidLabel(edge.label)}` : "";
1336
1609
  lines.push(` ${sourceId} --> ${targetId}${label}`);
1337
1610
  }
1611
+ const gd = graph.data;
1612
+ if (gd?.classDefs) for (const [name, props] of Object.entries(gd.classDefs)) {
1613
+ const propsStr = Object.entries(props).map(([k, v]) => `${k}:${v}`).join(",");
1614
+ lines.push(` classDef ${name} ${propsStr}`);
1615
+ }
1616
+ const classMap = /* @__PURE__ */ new Map();
1617
+ for (const node of graph.nodes) if (node.data?.classes?.length) for (const cls of node.data.classes) {
1618
+ if (!classMap.has(cls)) classMap.set(cls, []);
1619
+ classMap.get(cls).push(node.id);
1620
+ }
1621
+ for (const [cls, nodeIds] of classMap) lines.push(` class ${nodeIds.join(",")} ${cls}`);
1338
1622
  return lines.join("\n");
1339
1623
  }
1340
1624
  /**
@@ -1,4 +1,4 @@
1
- import { c as Graph, f as GraphFormatConverter } from "../../types-Bq_fmLwW.mjs";
1
+ import { c as Graph, f as GraphFormatConverter } from "../../types-FBZCrmnG.mjs";
2
2
 
3
3
  //#region src/formats/tgf/index.d.ts
4
4
 
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-C5DlzzHs.mjs";
1
+ import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
2
2
 
3
3
  //#region src/formats/tgf/index.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { D as VisualGraphFormatConverter, T as VisualGraph } from "../../types-Bq_fmLwW.mjs";
1
+ import { D as VisualGraphFormatConverter, T as VisualGraph } from "../../types-FBZCrmnG.mjs";
2
2
  import { EdgeBase, NodeBase } from "@xyflow/system";
3
3
 
4
4
  //#region src/formats/xyflow/index.d.ts
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as TraversalOptions, E as VisualGraphConfig, O as VisualNode, S as TransitionOptions, T as VisualGraph, _ as MSTOptions, a as EntitiesConfig, b as PathOptions, c as Graph, d as GraphEdge, f as GraphFormatConverter, g as GraphStep, h as GraphPath, i as EdgeConfig, l as GraphConfig, m as GraphPatch, n as DeleteNodeOptions, o as EntitiesUpdate, p as GraphNode, r as EdgeChange, s as EntityRect, t as AllPairsShortestPathsOptions, u as GraphDiff, v as NodeChange, w as VisualEdge, x as SinglePathOptions, y as NodeConfig } from "./types-Bq_fmLwW.mjs";
1
+ import { C as TraversalOptions, E as VisualGraphConfig, O as VisualNode, S as TransitionOptions, T as VisualGraph, _ as MSTOptions, a as EntitiesConfig, b as PathOptions, c as Graph, d as GraphEdge, f as GraphFormatConverter, g as GraphStep, h as GraphPath, i as EdgeConfig, l as GraphConfig, m as GraphPatch, n as DeleteNodeOptions, o as EntitiesUpdate, p as GraphNode, r as EdgeChange, s as EntityRect, t as AllPairsShortestPathsOptions, u as GraphDiff, v as NodeChange, w as VisualEdge, x as SinglePathOptions, y as NodeConfig } from "./types-FBZCrmnG.mjs";
2
2
  import { bfs, dfs, genCycles, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAllPairsShortestPaths, getConnectedComponents, getCycles, getMinimumSpanningTree, getPostorder, getPostorders, getPreorder, getPreorders, getShortestPath, getShortestPaths, getSimplePath, getSimplePaths, getStronglyConnectedComponents, getTopologicalSort, hasPath, isAcyclic, isConnected, isTree, joinPaths } from "./algorithms.mjs";
3
3
  import { createFormatConverter } from "./formats/converter/index.mjs";
4
4
  import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { o as invalidateIndex, t as getIndex } from "./indexing-DitHphT7.mjs";
2
- import { A as addNode, B as hasNode, C as isAcyclic, D as GraphInstance, E as joinPaths, F as deleteEntities, H as updateEntities, I as deleteNode, L as getEdge, M as createGraphFromTransition, N as createVisualGraph, O as addEdge, P as deleteEdge, R as getNode, S as hasPath, T as isTree, U as updateNode, V as updateEdge, _ as getShortestPaths, a as genPreorders, b as getStronglyConnectedComponents, c as getAllPairsShortestPaths, d as getMinimumSpanningTree, f as getPostorder, g as getShortestPath, h as getPreorders, i as genPostorders, j as createGraph, k as addEntities, l as getConnectedComponents, m as getPreorder, n as dfs, o as genShortestPaths, p as getPostorders, r as genCycles, s as genSimplePaths, t as bfs, u as getCycles, v as getSimplePath, w as isConnected, x as getTopologicalSort, y as getSimplePaths, z as hasEdge } from "./algorithms-CnTmuX9t.mjs";
1
+ import { o as invalidateIndex, t as getIndex } from "./indexing-DyfgLuzw.mjs";
2
+ import { A as addNode, B as hasNode, C as isAcyclic, D as GraphInstance, E as joinPaths, F as deleteEntities, H as updateEntities, I as deleteNode, L as getEdge, M as createGraphFromTransition, N as createVisualGraph, O as addEdge, P as deleteEdge, R as getNode, S as hasPath, T as isTree, U as updateNode, V as updateEdge, _ as getShortestPaths, a as genPreorders, b as getStronglyConnectedComponents, c as getAllPairsShortestPaths, d as getMinimumSpanningTree, f as getPostorder, g as getShortestPath, h as getPreorders, i as genPostorders, j as createGraph, k as addEntities, l as getConnectedComponents, m as getPreorder, n as dfs, o as genShortestPaths, p as getPostorders, r as genCycles, s as genSimplePaths, t as bfs, u as getCycles, v as getSimplePath, w as isConnected, x as getTopologicalSort, y as getSimplePaths, z as hasEdge } from "./algorithms-DldwenLt.mjs";
3
3
  import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
4
- import { n as createFormatConverter } from "./converter-C5DlzzHs.mjs";
4
+ import { n as createFormatConverter } from "./converter-B5CUD0r9.mjs";
5
5
 
6
6
  //#region src/diff.ts
7
7
  function nodeToConfig(node) {
@@ -1,4 +1,4 @@
1
- import { c as Graph, d as GraphEdge, p as GraphNode } from "./types-Bq_fmLwW.mjs";
1
+ import { c as Graph, d as GraphEdge, p as GraphNode } from "./types-FBZCrmnG.mjs";
2
2
 
3
3
  //#region src/queries.d.ts
4
4
 
package/dist/queries.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as getIndex } from "./indexing-DitHphT7.mjs";
1
+ import { t as getIndex } from "./indexing-DyfgLuzw.mjs";
2
2
 
3
3
  //#region src/queries.ts
4
4
  /**