@ai-digital-factory/state-flow 0.1.0 → 0.1.1

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.js CHANGED
@@ -7,7 +7,7 @@ function StateFlowPlaceholder() {
7
7
  // src/Flow.tsx
8
8
  import { useId, useMemo } from "react";
9
9
 
10
- // src/chart-visuals.tsx
10
+ // src/flow-visuals.tsx
11
11
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
12
12
  function SvgIcon({
13
13
  children,
@@ -15,10 +15,8 @@ function SvgIcon({
15
15
  }) {
16
16
  return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", children });
17
17
  }
18
- function ChartHeaderIcon({
19
- chartId
20
- }) {
21
- const key = (chartId ?? "").toLowerCase();
18
+ function FlowHeaderIcon({ flowId }) {
19
+ const key = (flowId ?? "").toLowerCase();
22
20
  if (key.includes("visitor") || key.includes("access")) {
23
21
  return /* @__PURE__ */ jsxs(SvgIcon, { children: [
24
22
  /* @__PURE__ */ jsx2(
@@ -126,8 +124,8 @@ function ChartHeaderIcon({
126
124
  )
127
125
  ] });
128
126
  }
129
- function chartNavIcon(chartId) {
130
- const key = chartId.toLowerCase();
127
+ function flowNavIcon(flowId) {
128
+ const key = flowId.toLowerCase();
131
129
  if (key.includes("visitor") || key.includes("access")) {
132
130
  return "\u25CE";
133
131
  }
@@ -140,16 +138,16 @@ function chartNavIcon(chartId) {
140
138
  return "\u25C6";
141
139
  }
142
140
 
143
- // src/ChartPageHeader.tsx
141
+ // src/FlowPageHeader.tsx
144
142
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
145
- function ChartPageHeader({
143
+ function FlowPageHeader({
146
144
  title,
147
145
  description,
148
- chartId
146
+ flowId
149
147
  }) {
150
- return /* @__PURE__ */ jsxs2("header", { className: "sf-chart-page-header", children: [
151
- /* @__PURE__ */ jsx3("div", { className: "sf-chart-page-header-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(ChartHeaderIcon, { chartId: chartId ?? title }) }),
152
- /* @__PURE__ */ jsxs2("div", { className: "sf-chart-page-header-text", children: [
148
+ return /* @__PURE__ */ jsxs2("header", { className: "sf-flow-page-header", children: [
149
+ /* @__PURE__ */ jsx3("div", { className: "sf-flow-page-header-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(FlowHeaderIcon, { flowId: flowId ?? title }) }),
150
+ /* @__PURE__ */ jsxs2("div", { className: "sf-flow-page-header-text", children: [
153
151
  /* @__PURE__ */ jsx3("h2", { className: "sf-title", children: title }),
154
152
  /* @__PURE__ */ jsx3("p", { className: "sf-intro", children: description })
155
153
  ] })
@@ -377,6 +375,39 @@ function focusToSelection(focus, edges) {
377
375
  function outgoingTransitions(edges, stateId) {
378
376
  return edges.filter((edge) => edge.fromId === stateId);
379
377
  }
378
+ function DestinationList({
379
+ edges,
380
+ edge
381
+ }) {
382
+ const destinations = edges.filter(
383
+ (candidate) => candidate.name === edge.name && candidate.fromId === edge.fromId
384
+ );
385
+ return /* @__PURE__ */ jsx5(
386
+ "ul",
387
+ {
388
+ className: "sf-overview-destination-list",
389
+ "aria-label": destinations.length > 1 ? "Branch destinations" : "Destination",
390
+ children: destinations.map((destination) => /* @__PURE__ */ jsxs4(
391
+ "li",
392
+ {
393
+ className: `sf-overview-legend-route${destination.toId === edge.toId && destinations.length > 1 ? " is-selected" : ""}`,
394
+ children: [
395
+ /* @__PURE__ */ jsxs4("code", { children: [
396
+ destination.fromName,
397
+ " \u2192 ",
398
+ destination.toName
399
+ ] }),
400
+ destination.destinationDescription ? /* @__PURE__ */ jsxs4("span", { className: "sf-overview-destination-detail", children: [
401
+ " ",
402
+ destination.destinationDescription
403
+ ] }) : null
404
+ ]
405
+ },
406
+ destination.toId
407
+ ))
408
+ }
409
+ );
410
+ }
380
411
  function DiagramOverview({
381
412
  svgHtml,
382
413
  states,
@@ -687,12 +718,8 @@ function DiagramOverview({
687
718
  ] }),
688
719
  /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-title", children: /* @__PURE__ */ jsx5("strong", { children: edge.name }) }),
689
720
  guardBlockedActive ? /* @__PURE__ */ jsx5("p", { className: "sf-overview-guard-blocked-note", children: "Guard blocked this transition." }) : null,
690
- /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-route", children: /* @__PURE__ */ jsxs4("code", { children: [
691
- edge.fromName,
692
- " \u2192 ",
693
- edge.toName
694
- ] }) }),
695
- /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-detail", children: edge.description })
721
+ /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-detail", children: edge.description }),
722
+ /* @__PURE__ */ jsx5(DestinationList, { edges, edge })
696
723
  ]
697
724
  }
698
725
  );
@@ -711,7 +738,13 @@ function DiagramOverview({
711
738
  ] }),
712
739
  /* @__PURE__ */ jsxs4("div", { className: "sf-overview-detail-title-row", children: [
713
740
  /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-title", children: /* @__PURE__ */ jsx5("strong", { children: state.name }) }),
714
- /* @__PURE__ */ jsx5("span", { className: "sf-overview-detail-kind-tag", children: "State" })
741
+ /* @__PURE__ */ jsx5(
742
+ "span",
743
+ {
744
+ className: `sf-overview-detail-kind-tag${state.kind === "final" ? " is-final" : ""}`,
745
+ children: state.kind === "final" ? "Final state" : "State"
746
+ }
747
+ )
715
748
  ] }),
716
749
  /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-detail", children: state.description })
717
750
  ] }),
@@ -749,7 +782,8 @@ function DiagramOverview({
749
782
  /* @__PURE__ */ jsxs4("span", { className: "sf-overview-transitions-route", children: [
750
783
  edge.fromName,
751
784
  " \u2192 ",
752
- edge.toName
785
+ edge.toName,
786
+ edge.destinationCount > 1 ? ` (${edge.destinationDescription})` : ""
753
787
  ] })
754
788
  ]
755
789
  }
@@ -776,12 +810,8 @@ function DiagramOverview({
776
810
  detailContent = /* @__PURE__ */ jsxs4(Fragment, { children: [
777
811
  /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-title", children: /* @__PURE__ */ jsx5("strong", { children: edge.name }) }),
778
812
  guardBlockedActive ? /* @__PURE__ */ jsx5("p", { className: "sf-overview-guard-blocked-note", children: "Guard blocked this transition." }) : null,
779
- /* @__PURE__ */ jsxs4("p", { className: "sf-overview-legend-route", children: [
780
- edge.fromName,
781
- " \u2192 ",
782
- edge.toName
783
- ] }),
784
- /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-detail", children: edge.description })
813
+ /* @__PURE__ */ jsx5("p", { className: "sf-overview-legend-detail", children: edge.description }),
814
+ /* @__PURE__ */ jsx5(DestinationList, { edges, edge })
785
815
  ] });
786
816
  }
787
817
  } else {
@@ -1054,9 +1084,9 @@ function layoutSelfLoop(cx, top, slot, slotsOnState) {
1054
1084
  function roundCoord(value) {
1055
1085
  return Math.round(value * 10) / 10;
1056
1086
  }
1057
- function countSelfLoopsPerState(chart) {
1087
+ function countSelfLoopsPerState(flow) {
1058
1088
  const counts = /* @__PURE__ */ new Map();
1059
- for (const edge of chart.edges) {
1089
+ for (const edge of flow.edges) {
1060
1090
  if (edge.fromId !== edge.toId) {
1061
1091
  continue;
1062
1092
  }
@@ -1071,8 +1101,26 @@ function maxSelfLoopsOnOneState(counts) {
1071
1101
  }
1072
1102
  return max;
1073
1103
  }
1104
+ function wrapBranchLabel(text) {
1105
+ const words = text.trim().split(/\s+/);
1106
+ const lines = [""];
1107
+ for (const word of words) {
1108
+ const last = lines.length - 1;
1109
+ if (lines[last] && lines[last].length + word.length + 1 > 20) {
1110
+ if (lines.length === 2) {
1111
+ lines[last] = `${lines[last].slice(0, 18)}\u2026`;
1112
+ return lines;
1113
+ }
1114
+ lines.push(word);
1115
+ } else {
1116
+ lines[last] += `${lines[last] ? " " : ""}${word}`;
1117
+ }
1118
+ }
1119
+ return lines;
1120
+ }
1074
1121
  function transitionLabelBadge(options) {
1075
- const lines = wrapEdgeLabel(options.edgeName);
1122
+ const branch = options.branchLabel !== void 0;
1123
+ const lines = branch ? wrapBranchLabel(options.branchLabel) : wrapEdgeLabel(options.edgeName);
1076
1124
  const charWidth = 6.35;
1077
1125
  const lineHeight = 12;
1078
1126
  const padX = 10;
@@ -1088,20 +1136,34 @@ function transitionLabelBadge(options) {
1088
1136
  const textContent = lines.map(
1089
1137
  (line, lineIndex) => `<tspan x="${options.cx}" y="${textAnchorY + lineIndex * lineHeight}">${escapeXml(line)}</tspan>`
1090
1138
  ).join("");
1091
- return `<g class="overview-edge-hit" data-edge-index="${options.index}" role="button" tabindex="0" aria-label="${escapeXml(options.edgeName)}"><path d="${options.path}" fill="none" stroke="transparent" stroke-width="22"/><rect class="overview-edge-hit-area" x="${x - 6}" y="${y - 6}" width="${width + 12}" height="${height + 12}" rx="${(height + 12) / 2}" fill="transparent"/><rect class="overview-edge-badge" x="${x}" y="${y}" width="${width}" height="${height}" rx="${height / 2}" fill="var(--sf-selected)" stroke="var(--sf-accent)" stroke-width="2"/><text text-anchor="middle" fill="var(--sf-ink)" font-size="10" font-weight="600" pointer-events="none">${textContent}</text></g>`;
1139
+ const label = branch ? `${options.edgeName}: ${options.branchLabel}` : options.edgeName;
1140
+ return `<g class="overview-edge-hit${branch ? " overview-branch-hit" : ""}" data-edge-index="${options.index}" role="button" tabindex="0" aria-label="${escapeXml(label)}"><path d="${options.path}" fill="none" stroke="transparent" stroke-width="22"/><rect class="overview-edge-hit-area" x="${x - 6}" y="${y - 6}" width="${width + 12}" height="${height + 12}" rx="${(height + 12) / 2}" fill="transparent"/><rect class="overview-edge-badge${branch ? " overview-branch-badge" : ""}" x="${x}" y="${y}" width="${width}" height="${height}" rx="${branch ? 6 : height / 2}" fill="${branch ? "var(--sf-panel)" : "var(--sf-selected)"}" stroke="var(--sf-accent)" stroke-width="${branch ? 1.5 : 2}"${branch ? ' stroke-dasharray="4 3"' : ""}/><text text-anchor="middle" fill="var(--sf-ink)" font-size="10" font-weight="${branch ? 500 : 600}" ${branch ? 'font-style="italic" ' : ""}pointer-events="none">${textContent}</text></g>`;
1092
1141
  }
1093
- function diagramSvg(chart, title, instanceId) {
1094
- const labels = chart.states.map((state) => wrapLabel(state.name));
1142
+ function splitNodeMarkup(options) {
1143
+ const r = 7;
1144
+ const { x, y } = options;
1145
+ return `<g class="overview-edge-hit overview-split-hit" data-edge-index="${options.index}" role="button" tabindex="0" aria-label="${escapeXml(`${options.edgeName} (branches)`)}"><path class="overview-split-node" d="M ${x} ${y - r} L ${x + r} ${y} L ${x} ${y + r} L ${x - r} ${y} Z" fill="var(--sf-accent)" stroke="var(--sf-panel)" stroke-width="1.5"/></g>`;
1146
+ }
1147
+ function diagramSvg(flow, title, instanceId) {
1148
+ const templates = flow.states.map(
1149
+ (state) => (state.composition?.regions ?? []).filter((region) => region.kind !== "inline").map(
1150
+ (region) => region.kind === "list" ? `${region.name}: ${region.flow}[ ]` : `${region.name}: ${region.flow}`
1151
+ )
1152
+ );
1153
+ const labels = flow.states.map((state, index) => [
1154
+ ...wrapLabel(state.name),
1155
+ ...templates[index]
1156
+ ]);
1095
1157
  const boxHeight = Math.max(
1096
1158
  54,
1097
1159
  ...labels.map((lines) => lines.length * 17 + 24)
1098
1160
  );
1099
- const width = Math.max(280, chart.states.length * 260 + 24);
1161
+ const width = Math.max(280, flow.states.length * 260 + 24);
1100
1162
  const markerId = escapeXml(`${instanceId}-arrow`);
1101
1163
  const centers = new Map(
1102
- chart.states.map((state, index) => [state.id, 142 + index * 260])
1164
+ flow.states.map((state, index) => [state.id, 142 + index * 260])
1103
1165
  );
1104
- const routes = chart.edges.map((edge, index) => {
1166
+ const routes = flow.edges.map((edge, index) => {
1105
1167
  const from = centers.get(edge.fromId);
1106
1168
  const to = centers.get(edge.toId);
1107
1169
  return {
@@ -1117,7 +1179,7 @@ function diagramSvg(chart, title, instanceId) {
1117
1179
  for (const center of centers.values()) {
1118
1180
  for (const above of [true, false]) {
1119
1181
  const ports = routes.filter((route) => {
1120
- const edge = chart.edges[route.index];
1182
+ const edge = flow.edges[route.index];
1121
1183
  return edge.fromId !== edge.toId && route.above === above;
1122
1184
  }).flatMap((route) => [
1123
1185
  ...route.from === center ? [{ route, key: "start", neighbor: route.to }] : [],
@@ -1130,14 +1192,23 @@ function diagramSvg(chart, title, instanceId) {
1130
1192
  });
1131
1193
  }
1132
1194
  }
1133
- const selfLoopCounts = countSelfLoopsPerState(chart);
1195
+ const sharedStart = /* @__PURE__ */ new Map();
1196
+ for (const route of routes) {
1197
+ const edge = flow.edges[route.index];
1198
+ if (edge.destinationCount < 2 || edge.fromId === edge.toId) continue;
1199
+ const key = `${edge.name}:${route.above}`;
1200
+ const shared = sharedStart.get(key);
1201
+ if (shared === void 0) sharedStart.set(key, route.start);
1202
+ else route.start = shared;
1203
+ }
1204
+ const selfLoopCounts = countSelfLoopsPerState(flow);
1134
1205
  const maxSelfLoopsOnState = maxSelfLoopsOnOneState(selfLoopCounts);
1135
1206
  const selfLoopReserved = maxSelfLoopsOnState > 0 ? SELF_LOOP_BAND_HEIGHT + (maxSelfLoopsOnState - 1) * 4 : 0;
1136
1207
  const laneCounts = { above: 0, below: 0 };
1137
1208
  for (const above of [true, false]) {
1138
1209
  const lanes = [];
1139
1210
  const sorted = routes.filter((route) => {
1140
- const edge = chart.edges[route.index];
1211
+ const edge = flow.edges[route.index];
1141
1212
  return edge.fromId !== edge.toId && route.above === above;
1142
1213
  }).sort(
1143
1214
  (a, b) => Math.abs(a.end - a.start) - Math.abs(b.end - b.start) || a.index - b.index
@@ -1161,8 +1232,12 @@ function diagramSvg(chart, title, instanceId) {
1161
1232
  const bottom = top + boxHeight;
1162
1233
  const paths = [];
1163
1234
  const badges = [];
1235
+ const splits = [];
1236
+ const drawnSplits = /* @__PURE__ */ new Set();
1164
1237
  const selfLoopSlot = /* @__PURE__ */ new Map();
1165
- chart.edges.forEach((edge, index) => {
1238
+ const edgeClass = (edge) => edge.destinationCount > 1 ? "flow-edge flow-edge-branch" : "flow-edge";
1239
+ flow.edges.forEach((edge, index) => {
1240
+ const branching = edge.destinationCount > 1;
1166
1241
  if (edge.fromId === edge.toId) {
1167
1242
  const cx = centers.get(edge.fromId);
1168
1243
  const slotsOnState = selfLoopCounts.get(edge.fromId) ?? 1;
@@ -1170,7 +1245,7 @@ function diagramSvg(chart, title, instanceId) {
1170
1245
  selfLoopSlot.set(edge.fromId, slot + 1);
1171
1246
  const layout = layoutSelfLoop(cx, top, slot, slotsOnState);
1172
1247
  paths.push(
1173
- `<path class="flow-edge flow-edge-self" data-edge-index="${index}" d="${layout.path}" fill="none" stroke="var(--sf-accent)" stroke-width="2.5" marker-end="url(#${markerId})"/>`
1248
+ `<path class="${edgeClass(edge)} flow-edge-self" data-edge-index="${index}" d="${layout.path}" fill="none" stroke="var(--sf-accent)" stroke-width="2.5" data-transition="${escapeXml(edge.name)}" marker-end="url(#${markerId})"/>`
1174
1249
  );
1175
1250
  badges.push(
1176
1251
  transitionLabelBadge({
@@ -1178,7 +1253,8 @@ function diagramSvg(chart, title, instanceId) {
1178
1253
  cx: layout.labelCx,
1179
1254
  cy: layout.labelCy,
1180
1255
  index,
1181
- path: layout.path
1256
+ path: layout.path,
1257
+ ...branching ? { branchLabel: edge.destinationDescription } : {}
1182
1258
  })
1183
1259
  );
1184
1260
  return;
@@ -1188,106 +1264,293 @@ function diagramSvg(chart, title, instanceId) {
1188
1264
  const y = above ? top : bottom;
1189
1265
  const path = orthogonalEdgePath(start, end, y, lane);
1190
1266
  paths.push(
1191
- `<path class="flow-edge" data-edge-index="${index}" d="${path}" fill="none" stroke="var(--sf-accent)" stroke-width="2.5" marker-end="url(#${markerId})"/>`
1267
+ `<path class="${edgeClass(edge)}" data-edge-index="${index}" d="${path}" fill="none" stroke="var(--sf-accent)" stroke-width="2.5" data-transition="${escapeXml(edge.name)}" marker-end="url(#${markerId})"/>`
1192
1268
  );
1269
+ if (branching) {
1270
+ const splitKey = `${edge.name}:${above}`;
1271
+ const splitY = above ? y - 32 : y + 32;
1272
+ if (!drawnSplits.has(splitKey)) {
1273
+ const firstSplit = ![...drawnSplits].some(
1274
+ (key) => key.startsWith(`${edge.name}:`)
1275
+ );
1276
+ drawnSplits.add(splitKey);
1277
+ splits.push(
1278
+ splitNodeMarkup({ edgeName: edge.name, x: start, y: splitY, index })
1279
+ );
1280
+ if (firstSplit) {
1281
+ badges.push(
1282
+ transitionLabelBadge({
1283
+ edgeName: edge.name,
1284
+ cx: start,
1285
+ cy: (y + splitY) / 2,
1286
+ index,
1287
+ path: `M ${start} ${y} L ${start} ${splitY}`
1288
+ }).replace(
1289
+ 'class="overview-edge-hit"',
1290
+ 'class="overview-edge-hit overview-split-label"'
1291
+ )
1292
+ );
1293
+ }
1294
+ }
1295
+ }
1193
1296
  badges.push(
1194
1297
  transitionLabelBadge({
1195
1298
  edgeName: edge.name,
1196
1299
  cx: (start + end) / 2,
1197
1300
  cy: lane,
1198
1301
  index,
1199
- path
1302
+ path,
1303
+ ...branching ? { branchLabel: edge.destinationDescription } : {}
1200
1304
  })
1201
1305
  );
1202
1306
  });
1203
- const nodes = chart.states.map((state, index) => {
1307
+ const brackets = [];
1308
+ for (const parent of flow.states) {
1309
+ for (const region of parent.composition?.regions ?? []) {
1310
+ if (region.kind !== "inline") continue;
1311
+ const children = flow.states.filter(
1312
+ (state) => state.parent?.state === parent.name && state.parent.region === region.name
1313
+ );
1314
+ if (children.length === 0) continue;
1315
+ const xs = children.map((state) => centers.get(state.id));
1316
+ const left = Math.min(...xs) - 95;
1317
+ const right = Math.max(...xs) + 95;
1318
+ const y = bottom + 14;
1319
+ const label = parent.composition.kind === "compound" ? `inside ${parent.name}` : `inside ${parent.name} \u203A ${region.name}`;
1320
+ brackets.push(
1321
+ `<g class="overview-region" pointer-events="none"><path d="M ${left} ${y - 6} L ${left} ${y} L ${right} ${y} L ${right} ${y - 6}" fill="none" stroke="var(--sf-accent)" stroke-width="1.2" stroke-dasharray="4 3"/><text x="${(left + right) / 2}" y="${y + 13}" text-anchor="middle" fill="var(--sf-ink)" font-size="10" font-style="italic">${escapeXml(label)}</text></g>`
1322
+ );
1323
+ }
1324
+ }
1325
+ const nodes = flow.states.map((state, index) => {
1204
1326
  const x = centers.get(state.id);
1205
- const initial = state.id === chart.initialId;
1327
+ const initial = state.id === flow.initialId;
1206
1328
  const lines = labels[index];
1207
- const textY = top + boxHeight / 2 - (lines.length - 1) * 8.5 + 4;
1329
+ const final = state.kind === "final";
1330
+ const kindTag = final ? null : state.composition?.kind === "parallel" ? "PARALLEL" : state.composition ? "COMPOUND" : null;
1331
+ const textY = top + boxHeight / 2 - (lines.length - 1) * 8.5 + (final || kindTag ? 9 : 4);
1208
1332
  const iconY = top + 22;
1209
1333
  const labelX = x + 8;
1210
- return `${initial ? `<circle cx="${x - 108}" cy="${top + boxHeight / 2}" r="7" fill="var(--sf-accent)"/>` : ""}${stateIconMarkup(state.id, x, iconY)}<g class="overview-state-hit${initial ? " is-initial-state" : ""}" data-state-id="${escapeXml(state.id)}" role="button" tabindex="0" aria-label="${escapeXml(state.name)}"><rect class="overview-state-box" x="${x - 95}" y="${top}" width="190" height="${boxHeight}" rx="12" fill="var(--sf-panel)" stroke="var(--sf-accent)" stroke-width="${initial ? 3 : 2}"/><text text-anchor="middle" fill="var(--sf-ink)" font-size="13" font-weight="500" pointer-events="none">${lines.map((line, lineIndex) => `<tspan x="${labelX}" y="${textY + lineIndex * 17}">${escapeXml(line)}</tspan>`).join("")}</text></g>`;
1334
+ const stroke = final ? "var(--sf-final)" : "var(--sf-accent)";
1335
+ const finalMarkup = final ? `<rect class="overview-state-final-ring" x="${x - 90}" y="${top + 5}" width="180" height="${boxHeight - 10}" rx="9" fill="none" stroke="${stroke}" stroke-width="1.2" pointer-events="none"/><text class="overview-state-kind" x="${x}" y="${top + 17}" text-anchor="middle" fill="${stroke}" font-size="9" font-weight="700" letter-spacing="0.08em" pointer-events="none">FINAL</text>` : kindTag ? `<text class="overview-state-kind" x="${x}" y="${top + 17}" text-anchor="middle" fill="${stroke}" font-size="9" font-weight="700" letter-spacing="0.08em" pointer-events="none">${kindTag}</text>` : "";
1336
+ return `${initial ? `<circle cx="${x - 108}" cy="${top + boxHeight / 2}" r="7" fill="var(--sf-accent)"/>` : ""}${final ? "" : stateIconMarkup(state.id, x, iconY)}<g class="overview-state-hit${initial ? " is-initial-state" : ""}${final ? " is-final-state" : ""}" data-state-id="${escapeXml(state.id)}" data-state-kind="${state.kind}" role="button" tabindex="0" aria-label="${escapeXml(final ? `${state.name} (final state)` : state.name)}"><rect class="overview-state-box" x="${x - 95}" y="${top}" width="190" height="${boxHeight}" rx="12" fill="${final ? "var(--sf-final-fill)" : "var(--sf-panel)"}" stroke="${stroke}" stroke-width="${initial ? 3 : 2}"/>${finalMarkup}<text text-anchor="middle" fill="var(--sf-ink)" font-size="13" font-weight="500" pointer-events="none">${lines.map((line, lineIndex) => `<tspan x="${labelX}" y="${textY + lineIndex * 17}">${escapeXml(line)}</tspan>`).join("")}</text></g>`;
1211
1337
  });
1212
- const height = bottom + 32 + (laneCounts.below ? 64 + (laneCounts.below - 1) * 44 : 0);
1213
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" class="sf-flow-svg" style="font-family:system-ui,sans-serif"><title>${escapeXml(title)}</title><defs><marker id="${markerId}" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M 0 1 L 9 5 L 0 9 z" fill="var(--sf-accent)"/></marker></defs>${paths.join("")}${badges.join("")}${nodes.join("")}</svg>`;
1338
+ const height = bottom + (brackets.length > 0 ? 40 : 32) + (laneCounts.below ? 64 + (laneCounts.below - 1) * 44 : 0);
1339
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" class="sf-flow-svg" style="font-family:system-ui,sans-serif"><title>${escapeXml(title)}</title><defs><marker id="${markerId}" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M 0 1 L 9 5 L 0 9 z" fill="var(--sf-accent)"/></marker></defs>${paths.join("")}${brackets.join("")}${splits.join("")}${badges.join("")}${nodes.join("")}</svg>`;
1214
1340
  }
1215
1341
 
1216
- // src/parse-chart.ts
1217
- import { Children, isValidElement } from "react";
1342
+ // src/parse-flow.ts
1343
+ import {
1344
+ compileFlow
1345
+ } from "@ai-digital-factory/state-flow-definition";
1218
1346
 
1219
- // src/State.tsx
1220
- function State(_props) {
1221
- void _props;
1222
- return null;
1347
+ // src/extract-flow-definition.ts
1348
+ import {
1349
+ Children,
1350
+ isValidElement
1351
+ } from "react";
1352
+ var FLOW_DISPLAY_NAME = "StateFlowFlow";
1353
+ var STATE_DISPLAY_NAME = "StateFlowState";
1354
+ var FINAL_STATE_DISPLAY_NAME = "StateFlowFinalState";
1355
+ var PARALLEL_STATE_DISPLAY_NAME = "StateFlowParallelState";
1356
+ var REGION_DISPLAY_NAME = "StateFlowRegion";
1357
+ var FLOW_LIST_DISPLAY_NAME = "StateFlowFlowList";
1358
+ var TRANSITION_DISPLAY_NAME = "StateFlowTransition";
1359
+ var TO_DISPLAY_NAME = "StateFlowTo";
1360
+ function elementDisplayName(type) {
1361
+ if (typeof type === "function" || typeof type === "object") {
1362
+ if (type && "displayName" in type) {
1363
+ const displayName = type.displayName;
1364
+ if (displayName) {
1365
+ return displayName;
1366
+ }
1367
+ }
1368
+ if (typeof type === "function" && type.name) {
1369
+ return type.name;
1370
+ }
1371
+ }
1372
+ return void 0;
1223
1373
  }
1224
- State.displayName = "StateFlowState";
1225
-
1226
- // src/Transition.tsx
1227
- function Transition(_props) {
1228
- void _props;
1374
+ function isElement(node, displayName) {
1375
+ return isValidElement(node) && elementDisplayName(node.type) === displayName;
1376
+ }
1377
+ function isFlowElement(node) {
1378
+ return isElement(node, FLOW_DISPLAY_NAME);
1379
+ }
1380
+ function stateKind(node) {
1381
+ if (isElement(node, STATE_DISPLAY_NAME)) return "state";
1382
+ if (isElement(node, FINAL_STATE_DISPLAY_NAME)) return "final";
1383
+ if (isElement(node, PARALLEL_STATE_DISPLAY_NAME)) return "parallel";
1229
1384
  return null;
1230
1385
  }
1231
- Transition.displayName = "StateFlowTransition";
1232
-
1233
- // src/parse-chart.ts
1234
- function parseFlow(children, defaultStateName) {
1235
- const rawStates = [];
1236
- const edges = [];
1237
- const nameToId = /* @__PURE__ */ new Map();
1238
- Children.forEach(children, (child) => {
1239
- if (!isValidElement(child) || child.type !== State) {
1386
+ function transitionsOf(children) {
1387
+ const transitions = [];
1388
+ Children.forEach(children, (transitionChild) => {
1389
+ if (!isElement(transitionChild, TRANSITION_DISPLAY_NAME)) {
1240
1390
  return;
1241
1391
  }
1242
- const id = child.props.id ?? `state-${rawStates.length}`;
1243
- if (nameToId.has(child.props.name)) {
1244
- throw new Error(`Duplicate state name: ${child.props.name}`);
1245
- }
1246
- if (rawStates.some((state) => state.id === id)) {
1247
- throw new Error(`Duplicate state id: ${id}`);
1248
- }
1249
- rawStates.push({
1250
- id,
1251
- name: child.props.name,
1252
- description: child.props.description
1392
+ const destinations = [];
1393
+ Children.forEach(transitionChild.props.children, (toChild) => {
1394
+ if (isElement(toChild, TO_DISPLAY_NAME)) {
1395
+ destinations.push({
1396
+ state: toChild.props.state,
1397
+ description: toChild.props.description
1398
+ });
1399
+ }
1400
+ });
1401
+ const { childWork, trigger } = transitionChild.props;
1402
+ transitions.push({
1403
+ name: transitionChild.props.name,
1404
+ description: transitionChild.props.description,
1405
+ destinations,
1406
+ ...childWork !== void 0 ? { childWork } : {},
1407
+ ...trigger !== void 0 && trigger !== "dispatch" ? { trigger } : {}
1253
1408
  });
1254
- nameToId.set(child.props.name, id);
1255
1409
  });
1410
+ return transitions;
1411
+ }
1412
+ function statesOf(children) {
1413
+ const states = [];
1256
1414
  Children.forEach(children, (child) => {
1257
- if (!isValidElement(child) || child.type !== State) {
1258
- return;
1415
+ const kind = stateKind(child);
1416
+ if (kind === null) return;
1417
+ states.push(stateOf(child, kind));
1418
+ });
1419
+ return states;
1420
+ }
1421
+ function flowListRegion(node) {
1422
+ return {
1423
+ kind: "list",
1424
+ name: node.props.name,
1425
+ description: node.props.description,
1426
+ flow: node.props.flow
1427
+ };
1428
+ }
1429
+ function stateOf(element, kind) {
1430
+ const props = element.props;
1431
+ const regions = [];
1432
+ if (kind === "parallel") {
1433
+ Children.forEach(props.children, (child) => {
1434
+ if (isElement(child, REGION_DISPLAY_NAME)) {
1435
+ const { name, description, flow } = child.props;
1436
+ regions.push(
1437
+ flow !== void 0 ? { kind: "flow", name, description, flow } : {
1438
+ kind: "inline",
1439
+ name,
1440
+ description,
1441
+ default: child.props.default ?? "",
1442
+ states: statesOf(child.props.children)
1443
+ }
1444
+ );
1445
+ } else if (isElement(child, FLOW_LIST_DISPLAY_NAME)) {
1446
+ regions.push(flowListRegion(child));
1447
+ }
1448
+ });
1449
+ } else {
1450
+ const nested = statesOf(props.children);
1451
+ if (nested.length > 0 || props.default !== void 0) {
1452
+ regions.push({
1453
+ kind: "inline",
1454
+ name: props.name,
1455
+ description: props.description,
1456
+ default: props.default ?? "",
1457
+ states: nested
1458
+ });
1259
1459
  }
1260
- const fromId = nameToId.get(child.props.name);
1261
- if (!fromId) {
1262
- return;
1460
+ if (props.flow !== void 0) {
1461
+ regions.push({
1462
+ kind: "flow",
1463
+ name: props.name,
1464
+ description: props.description,
1465
+ flow: props.flow
1466
+ });
1263
1467
  }
1264
- Children.forEach(child.props.children, (transitionChild) => {
1265
- if (!isValidElement(transitionChild) || transitionChild.type !== Transition) {
1266
- return;
1468
+ Children.forEach(props.children, (child) => {
1469
+ if (isElement(child, FLOW_LIST_DISPLAY_NAME)) {
1470
+ regions.push(flowListRegion(child));
1471
+ }
1472
+ });
1473
+ }
1474
+ const composite = kind === "parallel" || regions.length > 0;
1475
+ return {
1476
+ name: props.name,
1477
+ description: props.description,
1478
+ kind: kind === "final" ? "final" : "state",
1479
+ transitions: transitionsOf(props.children),
1480
+ ...composite ? {
1481
+ composition: {
1482
+ kind: kind === "parallel" ? "parallel" : "compound",
1483
+ regions
1267
1484
  }
1268
- const toName = transitionChild.props.to;
1269
- const toId = nameToId.get(toName);
1270
- if (!toId) {
1271
- throw new Error(`Unknown transition target state: ${toName}`);
1485
+ } : {}
1486
+ };
1487
+ }
1488
+ function extractFlowDefinition(node) {
1489
+ if (!isFlowElement(node)) {
1490
+ throw new Error("Application flow module must render a Flow root");
1491
+ }
1492
+ const { name, description, default: defaultState, children } = node.props;
1493
+ return {
1494
+ name,
1495
+ description,
1496
+ default: defaultState,
1497
+ states: statesOf(children)
1498
+ };
1499
+ }
1500
+ function extractFlowDefinitionFromComponent(component) {
1501
+ return extractFlowDefinition(component());
1502
+ }
1503
+ function explicitStateIds(children) {
1504
+ const ids = /* @__PURE__ */ new Map();
1505
+ const visit = (nodes) => {
1506
+ Children.forEach(nodes, (child) => {
1507
+ if (!isValidElement(child)) return;
1508
+ const display = elementDisplayName(child.type);
1509
+ if (display === STATE_DISPLAY_NAME || display === FINAL_STATE_DISPLAY_NAME || display === PARALLEL_STATE_DISPLAY_NAME) {
1510
+ if (child.props.id !== void 0)
1511
+ ids.set(child.props.name, child.props.id);
1512
+ visit(child.props.children);
1513
+ } else if (display === REGION_DISPLAY_NAME) {
1514
+ visit(child.props.children);
1272
1515
  }
1273
- edges.push({
1274
- fromId,
1275
- fromName: child.props.name,
1276
- toId,
1277
- toName,
1278
- name: transitionChild.props.name,
1279
- description: transitionChild.props.description
1280
- });
1281
1516
  });
1517
+ };
1518
+ visit(children);
1519
+ return ids;
1520
+ }
1521
+
1522
+ // src/parse-flow.ts
1523
+ function parseFlow(children, defaultStateName) {
1524
+ const parsed = compileFlow({
1525
+ name: "",
1526
+ description: "",
1527
+ default: defaultStateName,
1528
+ states: statesOf(children)
1282
1529
  });
1283
- const initialId = nameToId.get(defaultStateName);
1284
- if (!initialId) {
1285
- throw new Error(`Unknown default state: ${defaultStateName}`);
1530
+ const explicit = explicitStateIds(children);
1531
+ if (explicit.size === 0) return parsed;
1532
+ const remap = new Map(
1533
+ parsed.states.map((state) => [
1534
+ state.id,
1535
+ explicit.get(state.name) ?? state.id
1536
+ ])
1537
+ );
1538
+ const ids = [...remap.values()];
1539
+ const duplicate = ids.find((id, index) => ids.indexOf(id) !== index);
1540
+ if (duplicate !== void 0) {
1541
+ throw new Error(`Duplicate state id: ${duplicate}`);
1286
1542
  }
1287
1543
  return {
1288
- states: rawStates,
1289
- edges,
1290
- initialId
1544
+ states: parsed.states.map((state) => ({
1545
+ ...state,
1546
+ id: remap.get(state.id)
1547
+ })),
1548
+ edges: parsed.edges.map((edge) => ({
1549
+ ...edge,
1550
+ fromId: remap.get(edge.fromId),
1551
+ toId: remap.get(edge.toId)
1552
+ })),
1553
+ initialId: remap.get(parsed.initialId)
1291
1554
  };
1292
1555
  }
1293
1556
 
@@ -1305,42 +1568,91 @@ function Flow({
1305
1568
  default: defaultStateName,
1306
1569
  children
1307
1570
  }) {
1308
- const chart = useMemo(
1571
+ const flow = useMemo(
1309
1572
  () => parseFlow(children, defaultStateName),
1310
1573
  [children, defaultStateName]
1311
1574
  );
1312
1575
  const instanceId = useId();
1313
- const chartTitle = titleCaseName(name);
1314
- const svgHtml = diagramSvg(chart, chartTitle, instanceId);
1576
+ const flowTitle = titleCaseName(name);
1577
+ const svgHtml = diagramSvg(flow, flowTitle, instanceId);
1315
1578
  return /* @__PURE__ */ jsxs5("article", { className: "sf-root", children: [
1316
1579
  /* @__PURE__ */ jsx6(
1317
- ChartPageHeader,
1580
+ FlowPageHeader,
1318
1581
  {
1319
- title: chartTitle,
1582
+ title: flowTitle,
1320
1583
  description,
1321
- chartId: name
1584
+ flowId: name
1322
1585
  }
1323
1586
  ),
1324
1587
  svgHtml ? /* @__PURE__ */ jsx6(
1325
1588
  DiagramOverview,
1326
1589
  {
1327
1590
  svgHtml,
1328
- states: chart.states,
1329
- edges: chart.edges,
1591
+ states: flow.states,
1592
+ edges: flow.edges,
1330
1593
  viewPersistenceKey: name,
1331
1594
  detailLayout: "split",
1332
- initialStateId: chart.initialId
1595
+ initialStateId: flow.initialId
1333
1596
  }
1334
1597
  ) : /* @__PURE__ */ jsx6("p", { className: "sf-notice", children: "No overview diagram is available for this flow." })
1335
1598
  ] });
1336
1599
  }
1337
1600
  Flow.displayName = "StateFlowFlow";
1338
1601
 
1339
- // src/ApplicationChartRenderer.tsx
1602
+ // src/State.tsx
1603
+ function State(_props) {
1604
+ void _props;
1605
+ return null;
1606
+ }
1607
+ State.displayName = "StateFlowState";
1608
+
1609
+ // src/ParallelState.tsx
1610
+ function ParallelState(_props) {
1611
+ void _props;
1612
+ return null;
1613
+ }
1614
+ ParallelState.displayName = "StateFlowParallelState";
1615
+
1616
+ // src/Region.tsx
1617
+ function Region(_props) {
1618
+ void _props;
1619
+ return null;
1620
+ }
1621
+ Region.displayName = "StateFlowRegion";
1622
+
1623
+ // src/FlowList.tsx
1624
+ function FlowList(_props) {
1625
+ void _props;
1626
+ return null;
1627
+ }
1628
+ FlowList.displayName = "StateFlowFlowList";
1629
+
1630
+ // src/FinalState.tsx
1631
+ function FinalState(_props) {
1632
+ void _props;
1633
+ return null;
1634
+ }
1635
+ FinalState.displayName = "StateFlowFinalState";
1636
+
1637
+ // src/Transition.tsx
1638
+ function Transition(_props) {
1639
+ void _props;
1640
+ return null;
1641
+ }
1642
+ Transition.displayName = "StateFlowTransition";
1643
+
1644
+ // src/To.tsx
1645
+ function To(_props) {
1646
+ void _props;
1647
+ return null;
1648
+ }
1649
+ To.displayName = "StateFlowTo";
1650
+
1651
+ // src/FlowRenderer.tsx
1340
1652
  import { useId as useId2, useMemo as useMemo2 } from "react";
1341
1653
  import {
1342
- compileApplicationChart
1343
- } from "@ai-digital-factory/state-flow-chart";
1654
+ compileFlow as compileFlow2
1655
+ } from "@ai-digital-factory/state-flow-definition";
1344
1656
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1345
1657
  function titleCaseName2(name) {
1346
1658
  if (!name) {
@@ -1348,20 +1660,18 @@ function titleCaseName2(name) {
1348
1660
  }
1349
1661
  return name.charAt(0).toUpperCase() + name.slice(1);
1350
1662
  }
1351
- function ApplicationChartRenderer({
1352
- chart
1353
- }) {
1354
- const parsed = useMemo2(() => compileApplicationChart(chart), [chart]);
1663
+ function FlowRenderer({ flow }) {
1664
+ const parsed = useMemo2(() => compileFlow2(flow), [flow]);
1355
1665
  const instanceId = useId2();
1356
- const chartTitle = titleCaseName2(chart.name);
1357
- const svgHtml = diagramSvg(parsed, chartTitle, instanceId);
1666
+ const flowTitle = titleCaseName2(flow.name);
1667
+ const svgHtml = diagramSvg(parsed, flowTitle, instanceId);
1358
1668
  return /* @__PURE__ */ jsxs6("article", { className: "sf-root", children: [
1359
1669
  /* @__PURE__ */ jsx7(
1360
- ChartPageHeader,
1670
+ FlowPageHeader,
1361
1671
  {
1362
- title: chartTitle,
1363
- description: chart.description,
1364
- chartId: chart.name
1672
+ title: flowTitle,
1673
+ description: flow.description,
1674
+ flowId: flow.name
1365
1675
  }
1366
1676
  ),
1367
1677
  svgHtml ? /* @__PURE__ */ jsx7(
@@ -1370,7 +1680,7 @@ function ApplicationChartRenderer({
1370
1680
  svgHtml,
1371
1681
  states: parsed.states,
1372
1682
  edges: parsed.edges,
1373
- viewPersistenceKey: chart.name,
1683
+ viewPersistenceKey: flow.name,
1374
1684
  detailLayout: "split",
1375
1685
  initialStateId: parsed.initialId
1376
1686
  }
@@ -1378,97 +1688,33 @@ function ApplicationChartRenderer({
1378
1688
  ] });
1379
1689
  }
1380
1690
 
1381
- // src/extract-chart-definition.ts
1382
- import {
1383
- Children as Children2,
1384
- isValidElement as isValidElement2
1385
- } from "react";
1386
- var FLOW_DISPLAY_NAME = "StateFlowFlow";
1387
- var STATE_DISPLAY_NAME = "StateFlowState";
1388
- var TRANSITION_DISPLAY_NAME = "StateFlowTransition";
1389
- function elementDisplayName(type) {
1390
- if (typeof type === "function" || typeof type === "object") {
1391
- if (type && "displayName" in type) {
1392
- const displayName = type.displayName;
1393
- if (displayName) {
1394
- return displayName;
1395
- }
1396
- }
1397
- if (typeof type === "function" && type.name) {
1398
- return type.name;
1399
- }
1400
- }
1401
- return void 0;
1402
- }
1403
- function isFlowElement(node) {
1404
- return isValidElement2(node) && elementDisplayName(node.type) === FLOW_DISPLAY_NAME;
1405
- }
1406
- function isStateElement(node) {
1407
- return isValidElement2(node) && elementDisplayName(node.type) === STATE_DISPLAY_NAME;
1408
- }
1409
- function isTransitionElement(node) {
1410
- return isValidElement2(node) && elementDisplayName(node.type) === TRANSITION_DISPLAY_NAME;
1411
- }
1412
- function extractApplicationChartDefinition(node) {
1413
- if (!isFlowElement(node)) {
1414
- throw new Error("Application flow module must render a Flow root");
1415
- }
1416
- const { name, description, default: defaultState, children } = node.props;
1417
- const states = [];
1418
- Children2.forEach(children, (child) => {
1419
- if (!isStateElement(child)) {
1420
- return;
1421
- }
1422
- const transitions = [];
1423
- Children2.forEach(child.props.children, (transitionChild) => {
1424
- if (!isTransitionElement(transitionChild)) {
1425
- return;
1426
- }
1427
- transitions.push({
1428
- to: transitionChild.props.to,
1429
- name: transitionChild.props.name,
1430
- description: transitionChild.props.description
1431
- });
1432
- });
1433
- states.push({
1434
- name: child.props.name,
1435
- description: child.props.description,
1436
- transitions
1437
- });
1438
- });
1439
- return {
1440
- name,
1441
- description,
1442
- default: defaultState,
1443
- states
1444
- };
1445
- }
1446
- function extractApplicationChartDefinitionFromComponent(component) {
1447
- return extractApplicationChartDefinition(component());
1448
- }
1449
-
1450
1691
  // src/index.tsx
1451
1692
  import {
1452
1693
  DEFAULT_TRANSITION_NAME,
1453
- STATE_APPLICATION_HOOK_EXPORT_NAMES,
1454
- TRANSITION_APPLICATION_HOOK_EXPORT_NAMES
1455
- } from "@ai-digital-factory/state-flow-chart";
1694
+ STATE_HANDLER_EXPORT_NAMES,
1695
+ TRANSITION_HANDLER_EXPORT_NAMES
1696
+ } from "@ai-digital-factory/state-flow-definition";
1456
1697
  export {
1457
- ApplicationChartRenderer,
1458
- ChartHeaderIcon,
1459
- ChartPageHeader,
1460
1698
  DEFAULT_TRANSITION_NAME,
1461
1699
  DiagramOverview,
1700
+ FinalState,
1462
1701
  Flow,
1463
- STATE_APPLICATION_HOOK_EXPORT_NAMES,
1702
+ FlowHeaderIcon,
1703
+ FlowList,
1704
+ FlowPageHeader,
1705
+ FlowRenderer,
1706
+ ParallelState,
1707
+ Region,
1708
+ STATE_HANDLER_EXPORT_NAMES,
1464
1709
  State,
1465
1710
  StateFlowPlaceholder,
1466
- TRANSITION_APPLICATION_HOOK_EXPORT_NAMES,
1711
+ TRANSITION_HANDLER_EXPORT_NAMES,
1712
+ To,
1467
1713
  Transition,
1468
- chartNavIcon,
1469
1714
  diagramSvg,
1470
- extractApplicationChartDefinition,
1471
- extractApplicationChartDefinitionFromComponent,
1715
+ extractFlowDefinition,
1716
+ extractFlowDefinitionFromComponent,
1717
+ flowNavIcon,
1472
1718
  mergeScrollYIntoSearch,
1473
1719
  parseScrollYFromSearch,
1474
1720
  replaceBrowserUrlSearch