@statelyai/graph 0.12.0 → 0.13.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.
@@ -1,6 +1,23 @@
1
1
  import { getChildren } from "../../queries.mjs";
2
2
 
3
3
  //#region src/formats/elk/index.ts
4
+ const STATELYAI_METADATA_KEY = "statelyai.metadata";
5
+ function addMetadata(target, metadata) {
6
+ target.layoutOptions = {
7
+ ...target.layoutOptions ?? {},
8
+ [STATELYAI_METADATA_KEY]: JSON.stringify(metadata)
9
+ };
10
+ return target;
11
+ }
12
+ function readMetadata(value) {
13
+ const raw = value.layoutOptions?.[STATELYAI_METADATA_KEY];
14
+ if (typeof raw !== "string") return void 0;
15
+ try {
16
+ return JSON.parse(raw);
17
+ } catch {
18
+ return;
19
+ }
20
+ }
4
21
  const DIRECTION_TO_ELK = {
5
22
  down: "DOWN",
6
23
  up: "UP",
@@ -20,7 +37,21 @@ function convertEdge(edge) {
20
37
  targets: [edge.targetPort ?? edge.targetId]
21
38
  };
22
39
  if (edge.label) elkEdge.labels = [{ text: edge.label }];
23
- return elkEdge;
40
+ return addMetadata(elkEdge, { edge: {
41
+ sourceId: edge.sourceId,
42
+ targetId: edge.targetId,
43
+ sourcePort: edge.sourcePort,
44
+ targetPort: edge.targetPort,
45
+ label: edge.label,
46
+ data: edge.data,
47
+ weight: edge.weight,
48
+ color: edge.color,
49
+ style: edge.style,
50
+ x: edge.x,
51
+ y: edge.y,
52
+ width: edge.width,
53
+ height: edge.height
54
+ } });
24
55
  }
25
56
  function convertPort(port) {
26
57
  const elkPort = {
@@ -32,7 +63,10 @@ function convertPort(port) {
32
63
  };
33
64
  if (port.label) elkPort.labels = [{ text: port.label }];
34
65
  if (port.direction !== "inout") elkPort.layoutOptions = { "org.eclipse.elk.port.side": port.direction === "in" ? "WEST" : "EAST" };
35
- return elkPort;
66
+ return addMetadata(elkPort, { port: {
67
+ data: port.data,
68
+ style: port.style
69
+ } });
36
70
  }
37
71
  function convertNode(graph, node) {
38
72
  const elkNode = {
@@ -44,6 +78,13 @@ function convertNode(graph, node) {
44
78
  };
45
79
  if (node.label) elkNode.labels = [{ text: node.label }];
46
80
  if (node.ports && node.ports.length > 0) elkNode.ports = node.ports.map(convertPort);
81
+ addMetadata(elkNode, { node: {
82
+ initialNodeId: node.initialNodeId,
83
+ data: node.data,
84
+ shape: node.shape,
85
+ color: node.color,
86
+ style: node.style
87
+ } });
47
88
  const children = getChildren(graph, node.id);
48
89
  if (children.length > 0) {
49
90
  elkNode.children = children.map((child) => convertNode(graph, child));
@@ -85,6 +126,14 @@ function toELK(graph) {
85
126
  const root = { id: graph.id };
86
127
  const elkDir = DIRECTION_TO_ELK[graph.direction];
87
128
  if (elkDir) root.layoutOptions = { "elk.direction": elkDir };
129
+ addMetadata(root, { graph: {
130
+ id: graph.id,
131
+ type: graph.type,
132
+ initialNodeId: graph.initialNodeId,
133
+ data: graph.data,
134
+ direction: graph.direction,
135
+ style: graph.style
136
+ } });
88
137
  const roots = getChildren(graph, null);
89
138
  if (roots.length > 0) root.children = roots.map((node) => convertNode(graph, node));
90
139
  const allInnerEdgeIds = /* @__PURE__ */ new Set();
@@ -100,20 +149,25 @@ function toELK(graph) {
100
149
  function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner) {
101
150
  if (elkNode.children) for (const child of elkNode.children) {
102
151
  const label = child.labels?.[0]?.text ?? "";
152
+ const metadata = readMetadata(child)?.node;
103
153
  const node = {
104
154
  type: "node",
105
155
  id: child.id,
106
156
  parentId,
107
- initialNodeId: null,
157
+ initialNodeId: metadata && "initialNodeId" in metadata ? metadata.initialNodeId : null,
108
158
  label,
109
- data: void 0,
159
+ data: metadata && "data" in metadata ? metadata.data : void 0,
110
160
  x: child.x ?? 0,
111
161
  y: child.y ?? 0,
112
162
  width: child.width ?? 0,
113
- height: child.height ?? 0
163
+ height: child.height ?? 0,
164
+ ...metadata?.shape !== void 0 && { shape: metadata.shape },
165
+ ...metadata?.color !== void 0 && { color: metadata.color },
166
+ ...metadata?.style !== void 0 && { style: metadata.style }
114
167
  };
115
168
  if (child.ports && child.ports.length > 0) node.ports = child.ports.map((elkPort) => {
116
169
  portOwner.set(elkPort.id, child.id);
170
+ const metadata$1 = readMetadata(elkPort)?.port;
117
171
  const sideOpt = elkPort.layoutOptions?.["org.eclipse.elk.port.side"];
118
172
  let direction = "inout";
119
173
  if (sideOpt === "WEST") direction = "in";
@@ -122,33 +176,40 @@ function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner) {
122
176
  name: elkPort.id,
123
177
  direction,
124
178
  label: elkPort.labels?.[0]?.text,
125
- data: void 0,
179
+ data: metadata$1 && "data" in metadata$1 ? metadata$1.data : void 0,
126
180
  x: elkPort.x ?? 0,
127
181
  y: elkPort.y ?? 0,
128
182
  width: elkPort.width ?? 0,
129
- height: elkPort.height ?? 0
183
+ height: elkPort.height ?? 0,
184
+ ...metadata$1?.style !== void 0 && { style: metadata$1.style }
130
185
  };
131
186
  });
132
187
  nodes.push(node);
133
188
  flattenElkNodes(child, child.id, nodes, edges, edgeIdx, portOwner);
134
189
  }
135
190
  if (elkNode.edges) for (const elkEdge of elkNode.edges) for (const source of elkEdge.sources) for (const target of elkEdge.targets) {
191
+ const metadata = readMetadata(elkEdge)?.edge;
136
192
  const sourceNodeId = portOwner.get(source);
137
193
  const targetNodeId = portOwner.get(target);
138
194
  const edge = {
139
195
  type: "edge",
140
196
  id: elkEdge.id ?? `e${edgeIdx.value++}`,
141
- sourceId: sourceNodeId ?? source,
142
- targetId: targetNodeId ?? target,
143
- label: elkEdge.labels?.[0]?.text ?? "",
144
- data: void 0,
145
- x: 0,
146
- y: 0,
147
- width: 0,
148
- height: 0
197
+ sourceId: metadata?.sourceId ?? sourceNodeId ?? source,
198
+ targetId: metadata?.targetId ?? targetNodeId ?? target,
199
+ label: metadata && "label" in metadata ? metadata.label : elkEdge.labels?.[0]?.text ?? "",
200
+ data: metadata && "data" in metadata ? metadata.data : void 0,
201
+ x: metadata?.x ?? 0,
202
+ y: metadata?.y ?? 0,
203
+ width: metadata?.width ?? 0,
204
+ height: metadata?.height ?? 0,
205
+ ...metadata?.weight !== void 0 && { weight: metadata.weight },
206
+ ...metadata?.color !== void 0 && { color: metadata.color },
207
+ ...metadata?.style !== void 0 && { style: metadata.style }
149
208
  };
150
- if (sourceNodeId) edge.sourcePort = source;
151
- if (targetNodeId) edge.targetPort = target;
209
+ if (metadata && "sourcePort" in metadata) edge.sourcePort = metadata.sourcePort;
210
+ else if (sourceNodeId) edge.sourcePort = source;
211
+ if (metadata && "targetPort" in metadata) edge.targetPort = metadata.targetPort;
212
+ else if (targetNodeId) edge.targetPort = target;
152
213
  edges.push(edge);
153
214
  }
154
215
  }
@@ -179,15 +240,17 @@ function fromELK(elkRoot) {
179
240
  const seenEdges = /* @__PURE__ */ new Map();
180
241
  for (const edge of edges) if (!seenEdges.has(edge.id)) seenEdges.set(edge.id, edge);
181
242
  const elkDir = elkRoot.layoutOptions?.["elk.direction"];
182
- const direction = (elkDir ? ELK_TO_DIRECTION[elkDir] : void 0) ?? "down";
243
+ const graphMetadata = readMetadata(elkRoot)?.graph;
244
+ const direction = graphMetadata?.direction ?? (elkDir ? ELK_TO_DIRECTION[elkDir] : void 0) ?? "down";
183
245
  return {
184
- id: elkRoot.id,
185
- type: "directed",
186
- initialNodeId: null,
246
+ id: graphMetadata?.id ?? elkRoot.id,
247
+ type: graphMetadata?.type === "undirected" ? "undirected" : "directed",
248
+ initialNodeId: graphMetadata && "initialNodeId" in graphMetadata ? graphMetadata.initialNodeId : null,
187
249
  nodes,
188
250
  edges: [...seenEdges.values()],
189
- data: void 0,
190
- direction
251
+ data: graphMetadata && "data" in graphMetadata ? graphMetadata.data : void 0,
252
+ direction,
253
+ ...graphMetadata?.style !== void 0 && { style: graphMetadata.style }
191
254
  };
192
255
  }
193
256
  /**
@@ -24,6 +24,21 @@ function toGEXF(graph) {
24
24
  "@_title": "shape",
25
25
  "@_type": "string"
26
26
  },
27
+ {
28
+ "@_id": "a_width",
29
+ "@_title": "width",
30
+ "@_type": "double"
31
+ },
32
+ {
33
+ "@_id": "a_height",
34
+ "@_title": "height",
35
+ "@_type": "double"
36
+ },
37
+ {
38
+ "@_id": "a_style",
39
+ "@_title": "style",
40
+ "@_type": "string"
41
+ },
27
42
  {
28
43
  "@_id": "a_ports",
29
44
  "@_title": "ports",
@@ -36,6 +51,36 @@ function toGEXF(graph) {
36
51
  "@_title": "data",
37
52
  "@_type": "string"
38
53
  },
54
+ {
55
+ "@_id": "a_edgeWeight",
56
+ "@_title": "weight",
57
+ "@_type": "double"
58
+ },
59
+ {
60
+ "@_id": "a_edgeX",
61
+ "@_title": "x",
62
+ "@_type": "double"
63
+ },
64
+ {
65
+ "@_id": "a_edgeY",
66
+ "@_title": "y",
67
+ "@_type": "double"
68
+ },
69
+ {
70
+ "@_id": "a_edgeWidth",
71
+ "@_title": "width",
72
+ "@_type": "double"
73
+ },
74
+ {
75
+ "@_id": "a_edgeHeight",
76
+ "@_title": "height",
77
+ "@_type": "double"
78
+ },
79
+ {
80
+ "@_id": "a_edgeStyle",
81
+ "@_title": "style",
82
+ "@_type": "string"
83
+ },
39
84
  {
40
85
  "@_id": "a_sourcePort",
41
86
  "@_title": "sourcePort",
@@ -65,6 +110,18 @@ function toGEXF(graph) {
65
110
  "@_for": "a_shape",
66
111
  "@_value": n.shape
67
112
  });
113
+ if (n.width !== void 0) attvalues.push({
114
+ "@_for": "a_width",
115
+ "@_value": n.width
116
+ });
117
+ if (n.height !== void 0) attvalues.push({
118
+ "@_for": "a_height",
119
+ "@_value": n.height
120
+ });
121
+ if (n.style !== void 0) attvalues.push({
122
+ "@_for": "a_style",
123
+ "@_value": JSON.stringify(n.style)
124
+ });
68
125
  if (n.ports !== void 0) attvalues.push({
69
126
  "@_for": "a_ports",
70
127
  "@_value": JSON.stringify(n.ports)
@@ -102,6 +159,30 @@ function toGEXF(graph) {
102
159
  "@_value": JSON.stringify(e.data)
103
160
  }] };
104
161
  const edgeAttvalues = edge.attvalues?.attvalue ?? [];
162
+ if (e.weight !== void 0) edgeAttvalues.push({
163
+ "@_for": "a_edgeWeight",
164
+ "@_value": e.weight
165
+ });
166
+ if (e.x !== void 0) edgeAttvalues.push({
167
+ "@_for": "a_edgeX",
168
+ "@_value": e.x
169
+ });
170
+ if (e.y !== void 0) edgeAttvalues.push({
171
+ "@_for": "a_edgeY",
172
+ "@_value": e.y
173
+ });
174
+ if (e.width !== void 0) edgeAttvalues.push({
175
+ "@_for": "a_edgeWidth",
176
+ "@_value": e.width
177
+ });
178
+ if (e.height !== void 0) edgeAttvalues.push({
179
+ "@_for": "a_edgeHeight",
180
+ "@_value": e.height
181
+ });
182
+ if (e.style !== void 0) edgeAttvalues.push({
183
+ "@_for": "a_edgeStyle",
184
+ "@_value": JSON.stringify(e.style)
185
+ });
105
186
  if (e.sourcePort !== void 0) edgeAttvalues.push({
106
187
  "@_for": "a_sourcePort",
107
188
  "@_value": e.sourcePort
@@ -136,6 +217,7 @@ function toGEXF(graph) {
136
217
  ...graph.initialNodeId && { "@_initialNodeId": graph.initialNodeId },
137
218
  ...graph.direction && { "@_direction": graph.direction },
138
219
  ...graph.data !== void 0 && { "@_data": JSON.stringify(graph.data) },
220
+ ...graph.style !== void 0 && { "@_style": JSON.stringify(graph.style) },
139
221
  attributes: [{
140
222
  "@_class": "node",
141
223
  attribute: nodeAttrs
@@ -193,6 +275,7 @@ function fromGEXF(xml) {
193
275
  data: attvals["data"] !== void 0 ? tryParseJSON(attvals["data"]) : void 0
194
276
  };
195
277
  if (attvals["shape"]) node.shape = attvals["shape"];
278
+ if (attvals["style"] !== void 0) node.style = tryParseJSON(attvals["style"]);
196
279
  if (attvals["ports"] !== void 0) node.ports = tryParseJSON(attvals["ports"]);
197
280
  const pos = n["viz:position"];
198
281
  if (pos) {
@@ -204,6 +287,8 @@ function fromGEXF(xml) {
204
287
  node.width = Number(size["@_value"] ?? 0);
205
288
  node.height = Number(size["@_value"] ?? 0);
206
289
  }
290
+ if (attvals["width"] !== void 0) node.width = Number(attvals["width"]);
291
+ if (attvals["height"] !== void 0) node.height = Number(attvals["height"]);
207
292
  const color = n["viz:color"];
208
293
  if (color) {
209
294
  const r = Number(color["@_r"] ?? 0);
@@ -226,6 +311,12 @@ function fromGEXF(xml) {
226
311
  targetId: String(e["@_target"]),
227
312
  label: e["@_label"] ?? "",
228
313
  data: attvals["data"] !== void 0 ? tryParseJSON(attvals["data"]) : void 0,
314
+ ...attvals["weight"] !== void 0 && { weight: Number(attvals["weight"]) },
315
+ ...attvals["x"] !== void 0 && { x: Number(attvals["x"]) },
316
+ ...attvals["y"] !== void 0 && { y: Number(attvals["y"]) },
317
+ ...attvals["width"] !== void 0 && { width: Number(attvals["width"]) },
318
+ ...attvals["height"] !== void 0 && { height: Number(attvals["height"]) },
319
+ ...attvals["style"] !== void 0 && { style: tryParseJSON(attvals["style"]) },
229
320
  ...attvals["sourcePort"] !== void 0 && { sourcePort: attvals["sourcePort"] },
230
321
  ...attvals["targetPort"] !== void 0 && { targetPort: attvals["targetPort"] }
231
322
  };
@@ -245,7 +336,8 @@ function fromGEXF(xml) {
245
336
  nodes,
246
337
  edges,
247
338
  data: graphEl["@_data"] !== void 0 ? tryParseJSON(String(graphEl["@_data"])) : void 0,
248
- ...graphEl["@_direction"] && { direction: graphEl["@_direction"] }
339
+ ...graphEl["@_direction"] && { direction: graphEl["@_direction"] },
340
+ ...graphEl["@_style"] !== void 0 && { style: tryParseJSON(String(graphEl["@_style"])) }
249
341
  };
250
342
  }
251
343
  function asArray(val) {
@@ -28,6 +28,10 @@ function toGML(graph) {
28
28
  lines.push("graph [");
29
29
  lines.push(` directed ${graph.type === "directed" ? 1 : 0}`);
30
30
  if (graph.id) lines.push(` id ${gmlString(graph.id)}`);
31
+ if (graph.initialNodeId) lines.push(` initialNodeId ${gmlString(graph.initialNodeId)}`);
32
+ if (graph.data !== void 0) lines.push(` data ${gmlString(JSON.stringify(graph.data))}`);
33
+ if (graph.direction) lines.push(` direction ${gmlString(graph.direction)}`);
34
+ if (graph.style !== void 0) lines.push(` style ${gmlString(JSON.stringify(graph.style))}`);
31
35
  const childrenMap = /* @__PURE__ */ new Map();
32
36
  for (const node of graph.nodes) {
33
37
  const pid = node.parentId ?? null;
@@ -43,6 +47,7 @@ function toGML(graph) {
43
47
  if (node.ports !== void 0) lines.push(`${indent} ports ${gmlString(JSON.stringify(node.ports))}`);
44
48
  if (node.shape) lines.push(`${indent} shape ${gmlString(node.shape)}`);
45
49
  if (node.color) lines.push(`${indent} color ${gmlString(node.color)}`);
50
+ if (node.style !== void 0) lines.push(`${indent} style ${gmlString(JSON.stringify(node.style))}`);
46
51
  if (node.x !== void 0 || node.y !== void 0 || node.width !== void 0 || node.height !== void 0) {
47
52
  lines.push(`${indent} graphics [`);
48
53
  if (node.x !== void 0) lines.push(`${indent} x ${node.x}`);
@@ -64,9 +69,19 @@ function toGML(graph) {
64
69
  lines.push(` target ${gmlString(edge.targetId)}`);
65
70
  if (edge.label) lines.push(` label ${gmlString(edge.label)}`);
66
71
  if (edge.data !== void 0) lines.push(` data ${gmlString(JSON.stringify(edge.data))}`);
72
+ if (edge.weight !== void 0) lines.push(` weight ${edge.weight}`);
67
73
  if (edge.sourcePort !== void 0) lines.push(` sourcePort ${gmlString(edge.sourcePort)}`);
68
74
  if (edge.targetPort !== void 0) lines.push(` targetPort ${gmlString(edge.targetPort)}`);
69
75
  if (edge.color) lines.push(` color ${gmlString(edge.color)}`);
76
+ if (edge.style !== void 0) lines.push(` style ${gmlString(JSON.stringify(edge.style))}`);
77
+ if (edge.x !== void 0 || edge.y !== void 0 || edge.width !== void 0 || edge.height !== void 0) {
78
+ lines.push(" graphics [");
79
+ if (edge.x !== void 0) lines.push(` x ${edge.x}`);
80
+ if (edge.y !== void 0) lines.push(` y ${edge.y}`);
81
+ if (edge.width !== void 0) lines.push(` w ${edge.width}`);
82
+ if (edge.height !== void 0) lines.push(` h ${edge.height}`);
83
+ lines.push(" ]");
84
+ }
70
85
  lines.push(" ]");
71
86
  }
72
87
  lines.push("]");
@@ -118,6 +133,7 @@ function fromGML(gml) {
118
133
  ...n["ports"] !== void 0 && { ports: tryParseJSON(n["ports"]) },
119
134
  ...n["shape"] && { shape: n["shape"] },
120
135
  ...n["color"] && { color: n["color"] },
136
+ ...n["style"] !== void 0 && { style: tryParseJSON(n["style"]) },
121
137
  ...gfx?.x !== void 0 && { x: gfx.x },
122
138
  ...gfx?.y !== void 0 && { y: gfx.y },
123
139
  ...gfx?.w !== void 0 && { width: gfx.w },
@@ -128,24 +144,35 @@ function fromGML(gml) {
128
144
  }
129
145
  parseNodes(graphBlock, null);
130
146
  const edgeEntries = asArray(graphBlock["edge"]);
131
- for (const e of edgeEntries) edges.push({
132
- type: "edge",
133
- id: String(e["id"] ?? `e${edges.length}`),
134
- sourceId: String(e["source"] ?? ""),
135
- targetId: String(e["target"] ?? ""),
136
- label: e["label"] ?? "",
137
- data: e["data"] !== void 0 ? tryParseJSON(e["data"]) : void 0,
138
- ...e["sourcePort"] !== void 0 && { sourcePort: String(e["sourcePort"]) },
139
- ...e["targetPort"] !== void 0 && { targetPort: String(e["targetPort"]) },
140
- ...e["color"] && { color: e["color"] }
141
- });
147
+ for (const e of edgeEntries) {
148
+ const gfx = e["graphics"];
149
+ edges.push({
150
+ type: "edge",
151
+ id: String(e["id"] ?? `e${edges.length}`),
152
+ sourceId: String(e["source"] ?? ""),
153
+ targetId: String(e["target"] ?? ""),
154
+ label: e["label"] ?? "",
155
+ data: e["data"] !== void 0 ? tryParseJSON(e["data"]) : void 0,
156
+ ...e["weight"] !== void 0 && { weight: Number(e["weight"]) },
157
+ ...e["sourcePort"] !== void 0 && { sourcePort: String(e["sourcePort"]) },
158
+ ...e["targetPort"] !== void 0 && { targetPort: String(e["targetPort"]) },
159
+ ...e["color"] && { color: e["color"] },
160
+ ...e["style"] !== void 0 && { style: tryParseJSON(e["style"]) },
161
+ ...gfx?.x !== void 0 && { x: gfx.x },
162
+ ...gfx?.y !== void 0 && { y: gfx.y },
163
+ ...gfx?.w !== void 0 && { width: gfx.w },
164
+ ...gfx?.h !== void 0 && { height: gfx.h }
165
+ });
166
+ }
142
167
  return {
143
168
  id: graphId,
144
169
  type: directed ? "directed" : "undirected",
145
- initialNodeId: null,
170
+ initialNodeId: graphBlock["initialNodeId"] ?? null,
146
171
  nodes,
147
172
  edges,
148
- data: void 0
173
+ data: graphBlock["data"] !== void 0 ? tryParseJSON(graphBlock["data"]) : void 0,
174
+ ...graphBlock["direction"] && { direction: String(graphBlock["direction"]) },
175
+ ...graphBlock["style"] !== void 0 && { style: tryParseJSON(graphBlock["style"]) }
149
176
  };
150
177
  }
151
178
  function tokenize(input) {
@@ -23,6 +23,7 @@ function toJGF(graph) {
23
23
  if (graph.initialNodeId) metadata.initialNodeId = graph.initialNodeId;
24
24
  if (graph.data !== void 0) metadata.data = graph.data;
25
25
  if (graph.direction) metadata.direction = graph.direction;
26
+ if (graph.style !== void 0) metadata.style = graph.style;
26
27
  return { graph: {
27
28
  id: graph.id || void 0,
28
29
  directed: graph.type === "directed",
@@ -38,6 +39,7 @@ function toJGF(graph) {
38
39
  if (n.height !== void 0) meta.height = n.height;
39
40
  if (n.shape) meta.shape = n.shape;
40
41
  if (n.color) meta.color = n.color;
42
+ if (n.style !== void 0) meta.style = n.style;
41
43
  if (n.ports !== void 0) meta.ports = n.ports;
42
44
  return {
43
45
  id: n.id,
@@ -48,7 +50,13 @@ function toJGF(graph) {
48
50
  edges: graph.edges.map((e) => {
49
51
  const meta = {};
50
52
  if (e.data !== void 0) meta.data = e.data;
53
+ if (e.weight !== void 0) meta.weight = e.weight;
54
+ if (e.x !== void 0) meta.x = e.x;
55
+ if (e.y !== void 0) meta.y = e.y;
56
+ if (e.width !== void 0) meta.width = e.width;
57
+ if (e.height !== void 0) meta.height = e.height;
51
58
  if (e.color) meta.color = e.color;
59
+ if (e.style !== void 0) meta.style = e.style;
52
60
  if (e.sourcePort !== void 0) meta.sourcePort = e.sourcePort;
53
61
  if (e.targetPort !== void 0) meta.targetPort = e.targetPort;
54
62
  return {
@@ -89,6 +97,7 @@ function fromJGF(jgf) {
89
97
  initialNodeId: g.metadata?.initialNodeId ?? null,
90
98
  data: g.metadata?.data,
91
99
  ...g.metadata?.direction && { direction: g.metadata.direction },
100
+ ...g.metadata?.style !== void 0 && { style: g.metadata.style },
92
101
  nodes: g.nodes.map((n) => ({
93
102
  type: "node",
94
103
  id: n.id,
@@ -102,6 +111,7 @@ function fromJGF(jgf) {
102
111
  ...n.metadata?.height !== void 0 && { height: n.metadata.height },
103
112
  ...n.metadata?.shape && { shape: n.metadata.shape },
104
113
  ...n.metadata?.color && { color: n.metadata.color },
114
+ ...n.metadata?.style !== void 0 && { style: n.metadata.style },
105
115
  ...n.metadata?.ports !== void 0 && { ports: n.metadata.ports }
106
116
  })),
107
117
  edges: g.edges.map((e, i) => ({
@@ -111,7 +121,13 @@ function fromJGF(jgf) {
111
121
  targetId: e.target,
112
122
  label: e.label ?? "",
113
123
  data: e.metadata?.data,
124
+ ...e.metadata?.weight !== void 0 && { weight: e.metadata.weight },
125
+ ...e.metadata?.x !== void 0 && { x: e.metadata.x },
126
+ ...e.metadata?.y !== void 0 && { y: e.metadata.y },
127
+ ...e.metadata?.width !== void 0 && { width: e.metadata.width },
128
+ ...e.metadata?.height !== void 0 && { height: e.metadata.height },
114
129
  ...e.metadata?.color && { color: e.metadata.color },
130
+ ...e.metadata?.style !== void 0 && { style: e.metadata.style },
115
131
  ...e.metadata?.sourcePort !== void 0 && { sourcePort: e.metadata.sourcePort },
116
132
  ...e.metadata?.targetPort !== void 0 && { targetPort: e.metadata.targetPort }
117
133
  }))
@@ -165,6 +165,7 @@ interface StateNodeData {
165
165
  notes?: Array<{
166
166
  position: 'left' | 'right';
167
167
  text: string;
168
+ format?: 'inline' | 'block';
168
169
  }>;
169
170
  isStart?: boolean;
170
171
  isEnd?: boolean;
@@ -1415,10 +1415,16 @@ function fromMermaidState(input) {
1415
1415
  const parentStack = [null];
1416
1416
  const regionCounters = /* @__PURE__ */ new Map();
1417
1417
  function ensureNode(id) {
1418
- if (!nodeMap.has(id)) nodeMap.set(id, {
1418
+ const parentId = parentStack[parentStack.length - 1];
1419
+ const existing = nodeMap.get(id);
1420
+ if (existing) {
1421
+ if (parentId && existing.parentId === null && existing.id !== parentId) existing.parentId = parentId;
1422
+ return existing;
1423
+ }
1424
+ nodeMap.set(id, {
1419
1425
  type: "node",
1420
1426
  id,
1421
- parentId: parentStack[parentStack.length - 1],
1427
+ parentId,
1422
1428
  initialNodeId: null,
1423
1429
  label: id,
1424
1430
  data: {}
@@ -1462,6 +1468,15 @@ function fromMermaidState(input) {
1462
1468
  parentStack.push(stateId);
1463
1469
  continue;
1464
1470
  }
1471
+ const compositeStateAsMatch = line.match(/^state\s+"([^"]+)"\s+as\s+(\S+)\s*\{\s*$/);
1472
+ if (compositeStateAsMatch) {
1473
+ const description = compositeStateAsMatch[1];
1474
+ const stateId = compositeStateAsMatch[2];
1475
+ const node = ensureNode(stateId);
1476
+ node.data.description = description;
1477
+ parentStack.push(stateId);
1478
+ continue;
1479
+ }
1465
1480
  const stereotypeMatch = line.match(/^state\s+(\S+)\s+<<(choice|fork|join)>>\s*$/);
1466
1481
  if (stereotypeMatch) {
1467
1482
  const stateId = stereotypeMatch[1];
@@ -1542,16 +1557,27 @@ function fromMermaidState(input) {
1542
1557
  }
1543
1558
  continue;
1544
1559
  }
1545
- const noteMatch = line.match(/^note\s+(left|right)\s+of\s+(\S+)\s*:\s*(.+)$/i);
1560
+ const noteMatch = line.match(/^note\s+(left|right)\s+of\s+(\S+)\s*(?::\s*(.*))?$/i);
1546
1561
  if (noteMatch) {
1547
1562
  const position = noteMatch[1].toLowerCase();
1548
1563
  const stateId = noteMatch[2];
1549
- const text = noteMatch[3].trim();
1564
+ const inlineText = noteMatch[3]?.trim();
1565
+ const text = inlineText && inlineText.length > 0 ? inlineText : (() => {
1566
+ const content = [];
1567
+ while (i + 1 < lines.length) {
1568
+ i++;
1569
+ const noteLine = lines[i].trim();
1570
+ if (/^end\s+note$/i.test(noteLine)) break;
1571
+ content.push(noteLine);
1572
+ }
1573
+ return content.join("\n").trim();
1574
+ })();
1550
1575
  const node = ensureNode(stateId);
1551
1576
  if (!node.data.notes) node.data.notes = [];
1552
1577
  node.data.notes.push({
1553
1578
  position,
1554
- text
1579
+ text,
1580
+ format: inlineText && inlineText.length > 0 ? "inline" : "block"
1555
1581
  });
1556
1582
  continue;
1557
1583
  }
@@ -1687,9 +1713,9 @@ function toMermaidState(graph) {
1687
1713
  if (node.data?.isStart || node.data?.isEnd) continue;
1688
1714
  if (node.id.includes("_region_")) continue;
1689
1715
  if (node.data?.stateType && node.data.stateType !== "parallel") lines.push(`${indent}state ${node.id} <<${node.data.stateType}>>`);
1690
- if (node.data?.description) lines.push(`${indent}state "${escapeMermaidLabel(node.data.description)}" as ${node.id}`);
1691
1716
  if (isParent.has(node.id)) {
1692
- lines.push(`${indent}state ${node.id} {`);
1717
+ const stateDecl = node.data?.description ? `state "${escapeMermaidLabel(node.data.description)}" as ${node.id} {` : `state ${node.id} {`;
1718
+ lines.push(`${indent}${stateDecl}`);
1693
1719
  if (node.data?.direction) {
1694
1720
  const mDir = DIRECTION_TO_MERMAID[node.data.direction];
1695
1721
  if (mDir) lines.push(`${indent} direction ${mDir}`);
@@ -1702,8 +1728,12 @@ function toMermaidState(graph) {
1702
1728
  }
1703
1729
  } else writeNodes(node.id, indent + " ");
1704
1730
  lines.push(`${indent}}`);
1705
- }
1706
- if (node.data?.notes) for (const note of node.data.notes) lines.push(`${indent}note ${note.position} of ${node.id} : ${escapeMermaidLabel(note.text)}`);
1731
+ } else if (node.data?.description) lines.push(`${indent}state "${escapeMermaidLabel(node.data.description)}" as ${node.id}`);
1732
+ if (node.data?.notes) for (const note of node.data.notes) if (note.format === "block" || note.text.includes("\n")) {
1733
+ lines.push(`${indent}note ${note.position} of ${node.id}`);
1734
+ for (const noteLine of note.text.split("\n")) lines.push(`${indent} ${escapeMermaidLabel(noteLine)}`);
1735
+ lines.push(`${indent}end note`);
1736
+ } else lines.push(`${indent}note ${note.position} of ${node.id} : ${escapeMermaidLabel(note.text)}`);
1707
1737
  }
1708
1738
  }
1709
1739
  writeNodes(null, " ");
@@ -9,6 +9,7 @@ type XYFlowEdge<TEdgeData extends Record<string, unknown> = Record<string, unkno
9
9
  interface XYFlow<TNodeData extends Record<string, unknown> = Record<string, unknown>, TEdgeData extends Record<string, unknown> = Record<string, unknown>> {
10
10
  nodes: XYFlowNode<TNodeData>[];
11
11
  edges: XYFlowEdge<TEdgeData>[];
12
+ data?: Record<string, unknown>;
12
13
  }
13
14
  /**
14
15
  * Converts a visual graph to xyflow (React Flow / Svelte Flow) format.