@particle-academy/fancy-flow 0.35.0 → 0.37.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 (54) hide show
  1. package/dist/FlowViewer-C4QAVgWv.d.ts +62 -0
  2. package/dist/FlowViewer-nLqo7s2E.d.cts +62 -0
  3. package/dist/{chunk-N5ICD2WZ.js → chunk-3BKU4SQZ.js} +3 -3
  4. package/dist/{chunk-N5ICD2WZ.js.map → chunk-3BKU4SQZ.js.map} +1 -1
  5. package/dist/chunk-5UCWY7G3.js +593 -0
  6. package/dist/chunk-5UCWY7G3.js.map +1 -0
  7. package/dist/{chunk-EBE3FEAJ.js → chunk-A6RFLGWV.js} +4 -4
  8. package/dist/{chunk-EBE3FEAJ.js.map → chunk-A6RFLGWV.js.map} +1 -1
  9. package/dist/{chunk-Q6IPK64P.js → chunk-PVROYW7C.js} +32 -5
  10. package/dist/chunk-PVROYW7C.js.map +1 -0
  11. package/dist/{chunk-FKOXQP76.js → chunk-QZTGV3ZL.js} +3 -3
  12. package/dist/{chunk-FKOXQP76.js.map → chunk-QZTGV3ZL.js.map} +1 -1
  13. package/dist/{chunk-THU5LW22.js → chunk-UX65ML3J.js} +4 -4
  14. package/dist/{chunk-THU5LW22.js.map → chunk-UX65ML3J.js.map} +1 -1
  15. package/dist/{chunk-RE7T5GKQ.js → chunk-VLATLVGH.js} +3 -3
  16. package/dist/{chunk-RE7T5GKQ.js.map → chunk-VLATLVGH.js.map} +1 -1
  17. package/dist/engine.cjs +10 -2
  18. package/dist/engine.cjs.map +1 -1
  19. package/dist/engine.d.cts +1 -1
  20. package/dist/engine.d.ts +1 -1
  21. package/dist/engine.js +5 -5
  22. package/dist/index.cjs +163 -4
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +2 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +21 -473
  27. package/dist/index.js.map +1 -1
  28. package/dist/registry/index.d.cts +1 -1
  29. package/dist/registry/index.d.ts +1 -1
  30. package/dist/{registry-Ru5jIS5H.d.cts → registry-BADFn97n.d.cts} +41 -1
  31. package/dist/{registry-Df2-bTXZ.d.ts → registry-DYbFLEcW.d.ts} +41 -1
  32. package/dist/registry.cjs +31 -2
  33. package/dist/registry.cjs.map +1 -1
  34. package/dist/registry.js +3 -3
  35. package/dist/runtime.cjs +7 -1
  36. package/dist/runtime.cjs.map +1 -1
  37. package/dist/runtime.js +4 -4
  38. package/dist/schema.cjs +7 -1
  39. package/dist/schema.cjs.map +1 -1
  40. package/dist/schema.js +2 -2
  41. package/dist/screens.cjs +9834 -0
  42. package/dist/screens.cjs.map +1 -0
  43. package/dist/screens.css +563 -0
  44. package/dist/screens.css.map +1 -0
  45. package/dist/screens.d.cts +25 -0
  46. package/dist/screens.d.ts +25 -0
  47. package/dist/screens.js +21 -0
  48. package/dist/screens.js.map +1 -0
  49. package/dist/styles.css +103 -0
  50. package/dist/styles.css.map +1 -1
  51. package/dist/ux.cjs.map +1 -1
  52. package/dist/ux.js +1 -1
  53. package/package.json +196 -181
  54. package/dist/chunk-Q6IPK64P.js.map +0 -1
@@ -0,0 +1,593 @@
1
+ import { NoteNode, createConnectionValidator, buildNodeTypes } from './chunk-UX65ML3J.js';
2
+ import { Handle, Position, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap, ViewportPortal } from './chunk-OWENS2H5.js';
3
+ import { getNodeKind, categoryAccent } from './chunk-PVROYW7C.js';
4
+ import { memo, useMemo, useRef, useState, useCallback } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function NodeShellInner({
8
+ node,
9
+ accent,
10
+ tag,
11
+ icon,
12
+ showInputs = true,
13
+ showOutputs = true,
14
+ children
15
+ }) {
16
+ const data = node.data;
17
+ const status = data.status ?? "idle";
18
+ const inputs = data.inputs ?? defaultInputs(showInputs);
19
+ const outputs = data.outputs ?? defaultOutputs(showOutputs);
20
+ return /* @__PURE__ */ jsxs(
21
+ "div",
22
+ {
23
+ className: ["ff-node", `ff-node--status-${status}`, node.selected ? "ff-node--selected" : ""].filter(Boolean).join(" "),
24
+ style: { borderColor: node.selected ? accent : void 0 },
25
+ children: [
26
+ /* @__PURE__ */ jsxs("header", { className: "ff-node__header", style: { background: accent }, children: [
27
+ /* @__PURE__ */ jsx("span", { className: "ff-node__icon", "aria-hidden": true, children: icon ?? null }),
28
+ /* @__PURE__ */ jsx("span", { className: "ff-node__tag", children: tag }),
29
+ /* @__PURE__ */ jsx("span", { className: "ff-node__label", children: data.label }),
30
+ status !== "idle" && /* @__PURE__ */ jsx(StatusDot, { status })
31
+ ] }),
32
+ data.description && /* @__PURE__ */ jsx("p", { className: "ff-node__desc", children: data.description }),
33
+ children && /* @__PURE__ */ jsx("div", { className: "ff-node__body", children }),
34
+ data.statusText && /* @__PURE__ */ jsx("p", { className: "ff-node__status-text", children: data.statusText }),
35
+ inputs.map((p, i) => /* @__PURE__ */ jsx(
36
+ Handle,
37
+ {
38
+ type: "target",
39
+ position: Position.Left,
40
+ id: p.id,
41
+ style: portStyle(i, inputs.length),
42
+ title: p.label ?? p.id
43
+ },
44
+ p.id
45
+ )),
46
+ outputs.map((p, i) => /* @__PURE__ */ jsx(
47
+ Handle,
48
+ {
49
+ type: "source",
50
+ position: Position.Right,
51
+ id: p.id,
52
+ style: portStyle(i, outputs.length),
53
+ title: p.label ?? p.id
54
+ },
55
+ p.id
56
+ ))
57
+ ]
58
+ }
59
+ );
60
+ }
61
+ var NodeShell = memo(NodeShellInner);
62
+ function defaultInputs(show) {
63
+ return show ? [{ id: "in" }] : [];
64
+ }
65
+ function defaultOutputs(show) {
66
+ return show ? [{ id: "out" }] : [];
67
+ }
68
+ function portStyle(i, total) {
69
+ if (total <= 1) return {};
70
+ const slot = 100 / (total + 1) * (i + 1);
71
+ return { top: `${slot}%` };
72
+ }
73
+ function StatusDot({ status }) {
74
+ return /* @__PURE__ */ jsx("span", { className: `ff-node__dot ff-node__dot--${status}`, "aria-label": `status ${status}` });
75
+ }
76
+ function TriggerNodeInner(props) {
77
+ return /* @__PURE__ */ jsx(NodeShell, { node: props, accent: "#10b981", tag: "TRIGGER", icon: "\u26A1", showInputs: false });
78
+ }
79
+ var TriggerNode = memo(TriggerNodeInner);
80
+ function ActionNodeInner(props) {
81
+ return /* @__PURE__ */ jsx(NodeShell, { node: props, accent: "#3b82f6", tag: "ACTION", icon: "\u25B8" });
82
+ }
83
+ var ActionNode = memo(ActionNodeInner);
84
+ var DEFAULT_BRANCHES = [
85
+ { id: "true", label: "true" },
86
+ { id: "false", label: "false" }
87
+ ];
88
+ function DecisionNodeInner(props) {
89
+ if (!props.data.outputs) {
90
+ props = { ...props, data: { ...props.data, outputs: DEFAULT_BRANCHES } };
91
+ }
92
+ return /* @__PURE__ */ jsx(NodeShell, { node: props, accent: "#f59e0b", tag: "DECISION", icon: "\u25C7" });
93
+ }
94
+ var DecisionNode = memo(DecisionNodeInner);
95
+ function OutputNodeInner(props) {
96
+ return /* @__PURE__ */ jsx(NodeShell, { node: props, accent: "#a855f7", tag: "OUTPUT", icon: "\u25CF", showOutputs: false });
97
+ }
98
+ var OutputNode = memo(OutputNodeInner);
99
+ function SubgraphNodeInner(props) {
100
+ const data = props.data;
101
+ const childCount = data.childIds?.length ?? 0;
102
+ return /* @__PURE__ */ jsx(NodeShell, { node: props, accent: "#0ea5e9", tag: "SUBGRAPH", icon: "\u2750", children: /* @__PURE__ */ jsxs("div", { className: "ff-subgraph__meta", children: [
103
+ /* @__PURE__ */ jsxs("span", { children: [
104
+ childCount,
105
+ " node",
106
+ childCount === 1 ? "" : "s"
107
+ ] }),
108
+ /* @__PURE__ */ jsx("span", { children: data.collapsed === false ? "expanded" : "collapsed" })
109
+ ] }) });
110
+ }
111
+ var SubgraphNode = memo(SubgraphNodeInner);
112
+
113
+ // src/components/nodes/index.ts
114
+ var defaultNodeTypes = {
115
+ trigger: TriggerNode,
116
+ action: ActionNode,
117
+ decision: DecisionNode,
118
+ output: OutputNode,
119
+ note: NoteNode,
120
+ subgraph: SubgraphNode
121
+ };
122
+
123
+ // src/components/FlowEditor/graph-ops.ts
124
+ function removeNodes(graph, ids) {
125
+ if (ids.length === 0) return graph;
126
+ const doomed = new Set(ids);
127
+ return {
128
+ nodes: graph.nodes.filter((n) => !doomed.has(n.id)),
129
+ edges: graph.edges.filter((e) => !doomed.has(e.source) && !doomed.has(e.target))
130
+ };
131
+ }
132
+ function removeEdges(edges, ids) {
133
+ if (ids.length === 0) return edges;
134
+ const doomed = new Set(ids);
135
+ return edges.filter((e) => !doomed.has(e.id));
136
+ }
137
+ function setEdgeLabel(edges, id, label) {
138
+ const next = label?.trim();
139
+ return edges.map((e) => {
140
+ if (e.id !== id) return e;
141
+ if (!next) {
142
+ const { label: _drop, ...rest } = e;
143
+ return rest;
144
+ }
145
+ return { ...e, label: next };
146
+ });
147
+ }
148
+ function duplicateNode(node, id, offset = 40) {
149
+ return {
150
+ ...node,
151
+ id,
152
+ position: { x: node.position.x + offset, y: node.position.y + offset },
153
+ // Deep copy so the clone's config edits don't mutate the original.
154
+ data: JSON.parse(JSON.stringify(node.data ?? {}))
155
+ };
156
+ }
157
+ function cloneSubgraph(nodes, edges, opts) {
158
+ const offset = opts.offset ?? 40;
159
+ const idMap = /* @__PURE__ */ new Map();
160
+ for (const n of nodes) idMap.set(n.id, opts.makeId());
161
+ const clonedNodes = nodes.map((n) => {
162
+ const cloned = duplicateNode(n, idMap.get(n.id), offset);
163
+ const parentId = n.parentId;
164
+ if (parentId && idMap.has(parentId)) cloned.parentId = idMap.get(parentId);
165
+ else if (parentId) delete cloned.parentId;
166
+ return cloned;
167
+ });
168
+ const clonedEdges = edges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((e) => ({ ...e, id: opts.makeId(), source: idMap.get(e.source), target: idMap.get(e.target) }));
169
+ return { nodes: clonedNodes, edges: clonedEdges, idMap };
170
+ }
171
+ function reconnectEdge2(edges, oldEdge, newConnection) {
172
+ return reconnectEdge(oldEdge, newConnection, edges, { shouldReplaceId: false });
173
+ }
174
+ var nodeW = (n) => n.width ?? n.measured?.width ?? 0;
175
+ var nodeH = (n) => n.height ?? n.measured?.height ?? 0;
176
+ function alignNodes(nodes, edge) {
177
+ if (nodes.length < 2) return nodes;
178
+ const minL = Math.min(...nodes.map((n) => n.position.x));
179
+ const maxR = Math.max(...nodes.map((n) => n.position.x + nodeW(n)));
180
+ const minT = Math.min(...nodes.map((n) => n.position.y));
181
+ const maxB = Math.max(...nodes.map((n) => n.position.y + nodeH(n)));
182
+ const cx2 = (minL + maxR) / 2;
183
+ const cy = (minT + maxB) / 2;
184
+ return nodes.map((n) => {
185
+ let { x, y } = n.position;
186
+ if (edge === "left") x = minL;
187
+ else if (edge === "right") x = maxR - nodeW(n);
188
+ else if (edge === "hcenter") x = cx2 - nodeW(n) / 2;
189
+ else if (edge === "top") y = minT;
190
+ else if (edge === "bottom") y = maxB - nodeH(n);
191
+ else if (edge === "vcenter") y = cy - nodeH(n) / 2;
192
+ return { ...n, position: { x, y } };
193
+ });
194
+ }
195
+ function sortNodesParentFirst(nodes) {
196
+ const byId = new Map(nodes.map((n) => [n.id, n]));
197
+ const seen = /* @__PURE__ */ new Set();
198
+ const out = [];
199
+ const visit = (n) => {
200
+ if (seen.has(n.id)) return;
201
+ const pid = n.parentId;
202
+ if (pid && byId.has(pid) && !seen.has(pid)) visit(byId.get(pid));
203
+ seen.add(n.id);
204
+ out.push(n);
205
+ };
206
+ for (const n of nodes) visit(n);
207
+ return out;
208
+ }
209
+ function absolutePosition(node, nodes) {
210
+ const pid = node.parentId;
211
+ if (!pid) return node.position;
212
+ const parent = nodes.find((n) => n.id === pid);
213
+ return parent ? { x: node.position.x + parent.position.x, y: node.position.y + parent.position.y } : node.position;
214
+ }
215
+ function assignToLane(nodes, nodeId, laneId) {
216
+ if (nodeId === laneId) return nodes;
217
+ const node = nodes.find((n) => n.id === nodeId);
218
+ const lane = nodes.find((n) => n.id === laneId);
219
+ if (!node || !lane) return nodes;
220
+ const abs = absolutePosition(node, nodes);
221
+ const laneAbs = absolutePosition(lane, nodes);
222
+ const rel = { x: abs.x - laneAbs.x, y: abs.y - laneAbs.y };
223
+ return nodes.map((n) => n.id === nodeId ? { ...n, parentId: laneId, extent: "parent", position: rel } : n);
224
+ }
225
+ function removeFromLane(nodes, nodeId) {
226
+ const node = nodes.find((n) => n.id === nodeId);
227
+ if (!node || !node.parentId) return nodes;
228
+ const abs = absolutePosition(node, nodes);
229
+ return nodes.map((n) => {
230
+ if (n.id !== nodeId) return n;
231
+ const { parentId: _p, extent: _e, ...rest } = n;
232
+ return { ...rest, position: abs };
233
+ });
234
+ }
235
+ function distributeNodes(nodes, axis) {
236
+ if (nodes.length < 3) return nodes;
237
+ const size = (n) => axis === "h" ? nodeW(n) : nodeH(n);
238
+ const coord = (n) => axis === "h" ? n.position.x : n.position.y;
239
+ const sorted = [...nodes].sort((a, b) => coord(a) - coord(b));
240
+ const start = coord(sorted[0]);
241
+ const last = sorted[sorted.length - 1];
242
+ const end = coord(last) + size(last);
243
+ const totalSize = sorted.reduce((s, n) => s + size(n), 0);
244
+ const gap = (end - start - totalSize) / (sorted.length - 1);
245
+ const posById = /* @__PURE__ */ new Map();
246
+ let cursor = start;
247
+ for (const n of sorted) {
248
+ posById.set(n.id, cursor);
249
+ cursor += size(n) + gap;
250
+ }
251
+ return nodes.map((n) => {
252
+ const p = posById.get(n.id);
253
+ return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
254
+ });
255
+ }
256
+
257
+ // src/components/canvas/helper-lines.ts
258
+ var w = (n) => n.width ?? n.measured?.width ?? 0;
259
+ var h = (n) => n.height ?? n.measured?.height ?? 0;
260
+ function getHelperLines(change, nodes, distance = 6) {
261
+ const result = { snapPosition: { x: void 0, y: void 0 } };
262
+ const a = nodes.find((n) => n.id === change.id);
263
+ if (!a || !change.position) return result;
264
+ const A = {
265
+ left: change.position.x,
266
+ right: change.position.x + w(a),
267
+ top: change.position.y,
268
+ bottom: change.position.y + h(a),
269
+ width: w(a),
270
+ height: h(a)
271
+ };
272
+ let vDist = distance;
273
+ let hDist = distance;
274
+ for (const b of nodes) {
275
+ if (b.id === a.id) continue;
276
+ const B = { left: b.position.x, right: b.position.x + w(b), top: b.position.y, bottom: b.position.y + h(b) };
277
+ const ll = Math.abs(A.left - B.left);
278
+ if (ll < vDist) {
279
+ result.snapPosition.x = B.left;
280
+ result.vertical = B.left;
281
+ vDist = ll;
282
+ }
283
+ const rr = Math.abs(A.right - B.right);
284
+ if (rr < vDist) {
285
+ result.snapPosition.x = B.right - A.width;
286
+ result.vertical = B.right;
287
+ vDist = rr;
288
+ }
289
+ const lr = Math.abs(A.left - B.right);
290
+ if (lr < vDist) {
291
+ result.snapPosition.x = B.right;
292
+ result.vertical = B.right;
293
+ vDist = lr;
294
+ }
295
+ const rl = Math.abs(A.right - B.left);
296
+ if (rl < vDist) {
297
+ result.snapPosition.x = B.left - A.width;
298
+ result.vertical = B.left;
299
+ vDist = rl;
300
+ }
301
+ const tt = Math.abs(A.top - B.top);
302
+ if (tt < hDist) {
303
+ result.snapPosition.y = B.top;
304
+ result.horizontal = B.top;
305
+ hDist = tt;
306
+ }
307
+ const bb = Math.abs(A.bottom - B.bottom);
308
+ if (bb < hDist) {
309
+ result.snapPosition.y = B.bottom - A.height;
310
+ result.horizontal = B.bottom;
311
+ hDist = bb;
312
+ }
313
+ const tb = Math.abs(A.top - B.bottom);
314
+ if (tb < hDist) {
315
+ result.snapPosition.y = B.bottom;
316
+ result.horizontal = B.bottom;
317
+ hDist = tb;
318
+ }
319
+ const bt = Math.abs(A.bottom - B.top);
320
+ if (bt < hDist) {
321
+ result.snapPosition.y = B.top - A.height;
322
+ result.horizontal = B.top;
323
+ hDist = bt;
324
+ }
325
+ }
326
+ return result;
327
+ }
328
+ function HelperLines({ horizontal, vertical }) {
329
+ if (horizontal === void 0 && vertical === void 0) return null;
330
+ return /* @__PURE__ */ jsxs(ViewportPortal, { children: [
331
+ vertical !== void 0 && /* @__PURE__ */ jsx(
332
+ "div",
333
+ {
334
+ className: "ff-helper-line",
335
+ style: { position: "absolute", transform: `translateX(${vertical}px)`, top: -5e3, height: 1e4, width: 1, pointerEvents: "none" }
336
+ }
337
+ ),
338
+ horizontal !== void 0 && /* @__PURE__ */ jsx(
339
+ "div",
340
+ {
341
+ className: "ff-helper-line",
342
+ style: { position: "absolute", transform: `translateY(${horizontal}px)`, left: -5e3, width: 1e4, height: 1, pointerEvents: "none" }
343
+ }
344
+ )
345
+ ] });
346
+ }
347
+ function preventScrollWhileZooming(event) {
348
+ if (event.shiftKey) event.preventDefault();
349
+ }
350
+ function wheelZoomProps(zoomOnWheel) {
351
+ return zoomOnWheel ? { zoomActivationKeyCode: null, preventScrolling: true } : {
352
+ zoomActivationKeyCode: "Shift",
353
+ preventScrolling: false,
354
+ onWheelCapture: preventScrollWhileZooming
355
+ };
356
+ }
357
+ var DEFAULT_FIT_VIEW = { padding: 0.2 };
358
+ var DEFAULT_EDGE_OPTIONS = {
359
+ type: "smoothstep",
360
+ animated: false
361
+ };
362
+ function FlowCanvas({
363
+ nodes,
364
+ edges,
365
+ background = BackgroundVariant.Dots,
366
+ showControls = true,
367
+ showMinimap = false,
368
+ height = 600,
369
+ validateConnections = true,
370
+ isValidConnection,
371
+ colorMode,
372
+ showHelperLines = false,
373
+ zoomOnWheel = true,
374
+ onNodesChange,
375
+ toolbar,
376
+ nodeTypes,
377
+ edgeTypes,
378
+ className,
379
+ style,
380
+ ...rest
381
+ }) {
382
+ const mergedNodeTypes = useMemo(
383
+ () => ({ ...defaultNodeTypes, ...nodeTypes ?? {} }),
384
+ [nodeTypes]
385
+ );
386
+ const nodesRef = useRef(nodes);
387
+ nodesRef.current = nodes;
388
+ const builtinValidator = useMemo(
389
+ () => validateConnections === false ? void 0 : createConnectionValidator(
390
+ () => nodesRef.current,
391
+ validateConnections === true ? void 0 : validateConnections
392
+ ),
393
+ [validateConnections]
394
+ );
395
+ const resolvedIsValidConnection = isValidConnection ?? builtinValidator;
396
+ const mergedEdgeTypes = useMemo(
397
+ () => edgeTypes ? { ...edgeTypes } : void 0,
398
+ [edgeTypes]
399
+ );
400
+ const orderedNodes = useMemo(() => sortNodesParentFirst(nodes), [nodes]);
401
+ const [helperLines, setHelperLines] = useState({});
402
+ const handleNodesChange = useCallback(
403
+ (changes) => {
404
+ if (showHelperLines) {
405
+ const pos = changes.filter((c) => c.type === "position" && c.position);
406
+ if (pos.length === 1 && pos[0].dragging) {
407
+ const lines = getHelperLines(pos[0], nodesRef.current);
408
+ if (lines.snapPosition.x !== void 0) pos[0].position.x = lines.snapPosition.x;
409
+ if (lines.snapPosition.y !== void 0) pos[0].position.y = lines.snapPosition.y;
410
+ setHelperLines({ horizontal: lines.horizontal, vertical: lines.vertical });
411
+ } else {
412
+ setHelperLines({});
413
+ }
414
+ }
415
+ onNodesChange?.(changes);
416
+ },
417
+ [showHelperLines, onNodesChange]
418
+ );
419
+ return /* @__PURE__ */ jsxs(
420
+ "div",
421
+ {
422
+ className: [
423
+ "ff-canvas",
424
+ // colorMode drives BOTH react-flow's chrome (below) and our `ff-` styles
425
+ // via the shared `.dark` class / light opt-out — one theme signal.
426
+ colorMode === "dark" ? "dark" : "",
427
+ colorMode === "light" ? "ff-canvas--light" : "",
428
+ className ?? ""
429
+ ].filter(Boolean).join(" "),
430
+ style: { height, ...style },
431
+ children: [
432
+ toolbar && /* @__PURE__ */ jsx("div", { className: "ff-canvas__toolbar", children: toolbar }),
433
+ /* @__PURE__ */ jsx("div", { className: "ff-canvas__surface", children: /* @__PURE__ */ jsxs(
434
+ index,
435
+ {
436
+ nodes: orderedNodes,
437
+ edges,
438
+ onNodesChange: handleNodesChange,
439
+ colorMode,
440
+ nodeTypes: mergedNodeTypes,
441
+ edgeTypes: mergedEdgeTypes,
442
+ fitView: true,
443
+ fitViewOptions: DEFAULT_FIT_VIEW,
444
+ defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
445
+ proOptions: { hideAttribution: true },
446
+ ...wheelZoomProps(zoomOnWheel),
447
+ isValidConnection: resolvedIsValidConnection,
448
+ ...rest,
449
+ children: [
450
+ background !== "none" && /* @__PURE__ */ jsx(Background, { variant: background, gap: 20, size: 1, color: "rgba(0,0,0,0.18)" }),
451
+ showControls && /* @__PURE__ */ jsx(Controls, { className: "ff-controls", position: "bottom-right" }),
452
+ showMinimap && /* @__PURE__ */ jsx(MiniMap, { className: "ff-minimap", pannable: true, zoomable: true }),
453
+ showHelperLines && /* @__PURE__ */ jsx(HelperLines, { horizontal: helperLines.horizontal, vertical: helperLines.vertical })
454
+ ]
455
+ }
456
+ ) })
457
+ ]
458
+ }
459
+ );
460
+ }
461
+ var STATUS_LABEL = {
462
+ ok: "ok",
463
+ running: "running",
464
+ failed: "failed",
465
+ skipped: "skipped",
466
+ pending: "pending"
467
+ };
468
+ function FlowViewer({
469
+ graph,
470
+ variant = "canvas",
471
+ height = 480,
472
+ showMinimap = false,
473
+ showControls = true,
474
+ statuses,
475
+ selectedNodeId = null,
476
+ onSelectNode,
477
+ className,
478
+ classNames = {},
479
+ style,
480
+ empty
481
+ }) {
482
+ const nodeTypes = useMemo(() => buildNodeTypes(), []);
483
+ const rows = useMemo(
484
+ () => graph.nodes.map((node, index2) => {
485
+ const kind = getNodeKind(node.data?.kind ?? node.type ?? "");
486
+ return {
487
+ node,
488
+ index: index2,
489
+ title: kind?.label ?? node.data?.label ?? node.data?.kind ?? node.id,
490
+ description: kind?.description ?? null,
491
+ accent: kind?.accent ?? categoryAccent(kind?.category ?? "custom"),
492
+ status: statuses?.[node.id] ?? null
493
+ };
494
+ }),
495
+ [graph.nodes, statuses]
496
+ );
497
+ if (graph.nodes.length === 0) {
498
+ return /* @__PURE__ */ jsx("div", { className: cx("ff-viewer ff-viewer--empty", className, classNames.root), style, children: empty ?? /* @__PURE__ */ jsx("span", { className: "ff-viewer__empty", children: "This flow has no nodes." }) });
499
+ }
500
+ if (variant === "list") {
501
+ return /* @__PURE__ */ jsx(
502
+ "div",
503
+ {
504
+ className: cx("ff-viewer ff-viewer--list", className, classNames.root),
505
+ style,
506
+ "data-flow-viewer": "list",
507
+ children: /* @__PURE__ */ jsx("ol", { className: cx("ff-viewer__list", classNames.list), children: rows.map(({ node, index: index2, title, description, accent, status }) => {
508
+ const selected = node.id === selectedNodeId;
509
+ const Row = onSelectNode ? "button" : "div";
510
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
511
+ Row,
512
+ {
513
+ ...onSelectNode ? { type: "button", onClick: () => onSelectNode(node) } : {},
514
+ className: cx(
515
+ "ff-viewer__row",
516
+ selected && "ff-viewer__row--selected",
517
+ onSelectNode && "ff-viewer__row--interactive",
518
+ classNames.row
519
+ ),
520
+ "data-flow-viewer-node": node.id,
521
+ "data-flow-viewer-status": status ?? void 0,
522
+ "aria-current": selected || void 0,
523
+ children: [
524
+ /* @__PURE__ */ jsx(
525
+ "span",
526
+ {
527
+ className: cx("ff-viewer__index", classNames.rowIndex),
528
+ style: { backgroundColor: accent },
529
+ "aria-hidden": true,
530
+ children: index2 + 1
531
+ }
532
+ ),
533
+ /* @__PURE__ */ jsxs("span", { className: "ff-viewer__body", children: [
534
+ /* @__PURE__ */ jsx("span", { className: cx("ff-viewer__title", classNames.rowTitle), children: title }),
535
+ description && /* @__PURE__ */ jsx("span", { className: cx("ff-viewer__desc", classNames.rowDescription), children: description })
536
+ ] }),
537
+ status && /* @__PURE__ */ jsx(
538
+ "span",
539
+ {
540
+ className: cx(
541
+ "ff-viewer__status",
542
+ `ff-viewer__status--${status}`,
543
+ classNames.rowStatus
544
+ ),
545
+ children: STATUS_LABEL[status]
546
+ }
547
+ )
548
+ ]
549
+ }
550
+ ) }, node.id);
551
+ }) })
552
+ }
553
+ );
554
+ }
555
+ return /* @__PURE__ */ jsx(
556
+ "div",
557
+ {
558
+ className: cx("ff-viewer ff-viewer--canvas", className, classNames.root),
559
+ style,
560
+ "data-flow-viewer": "canvas",
561
+ children: /* @__PURE__ */ jsx(
562
+ FlowCanvas,
563
+ {
564
+ nodes: graph.nodes,
565
+ edges: graph.edges,
566
+ nodeTypes,
567
+ height,
568
+ showControls,
569
+ showMinimap,
570
+ nodesDraggable: false,
571
+ nodesConnectable: false,
572
+ nodesFocusable: Boolean(onSelectNode),
573
+ edgesFocusable: false,
574
+ elementsSelectable: Boolean(onSelectNode),
575
+ deleteKeyCode: null,
576
+ selectionKeyCode: null,
577
+ multiSelectionKeyCode: null,
578
+ connectOnClick: false,
579
+ zoomOnScroll: false,
580
+ onNodeClick: onSelectNode ? (_, node) => onSelectNode(node) : void 0,
581
+ fitView: true
582
+ }
583
+ )
584
+ }
585
+ );
586
+ }
587
+ function cx(...parts) {
588
+ return parts.filter(Boolean).join(" ");
589
+ }
590
+
591
+ export { ActionNode, DecisionNode, FlowCanvas, FlowViewer, NodeShell, OutputNode, SubgraphNode, TriggerNode, alignNodes, assignToLane, cloneSubgraph, defaultNodeTypes, distributeNodes, duplicateNode, reconnectEdge2 as reconnectEdge, removeEdges, removeFromLane, removeNodes, setEdgeLabel };
592
+ //# sourceMappingURL=chunk-5UCWY7G3.js.map
593
+ //# sourceMappingURL=chunk-5UCWY7G3.js.map