@particle-academy/fancy-flow 0.23.0 → 0.25.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 (47) hide show
  1. package/dist/{chunk-AUL4LU63.js → chunk-ATQXZEVD.js} +122 -7
  2. package/dist/chunk-ATQXZEVD.js.map +1 -0
  3. package/dist/{chunk-OHVSEHDC.js → chunk-N5ICD2WZ.js} +3 -3
  4. package/dist/{chunk-OHVSEHDC.js.map → chunk-N5ICD2WZ.js.map} +1 -1
  5. package/dist/{chunk-O7F6SGV4.js → chunk-OFW7GN3Y.js} +18 -6
  6. package/dist/chunk-OFW7GN3Y.js.map +1 -0
  7. package/dist/{chunk-ZB4HHQMR.js → chunk-OWENS2H5.js} +11 -3
  8. package/dist/chunk-OWENS2H5.js.map +1 -0
  9. package/dist/{chunk-YXRLIV7A.js → chunk-Q6IPK64P.js} +4 -2
  10. package/dist/chunk-Q6IPK64P.js.map +1 -0
  11. package/dist/{chunk-KDHLKBL2.js → chunk-U2VJB7HR.js} +7 -5
  12. package/dist/chunk-U2VJB7HR.js.map +1 -0
  13. package/dist/engine.cjs +4 -2
  14. package/dist/engine.cjs.map +1 -1
  15. package/dist/engine.js +3 -3
  16. package/dist/index.cjs +382 -130
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +7 -10
  19. package/dist/index.d.ts +7 -10
  20. package/dist/index.js +177 -64
  21. package/dist/index.js.map +1 -1
  22. package/dist/registry/index.d.cts +2 -2
  23. package/dist/registry/index.d.ts +2 -2
  24. package/dist/registry.cjs +114 -2
  25. package/dist/registry.cjs.map +1 -1
  26. package/dist/registry.js +4 -4
  27. package/dist/runtime/index.d.cts +13 -1
  28. package/dist/runtime/index.d.ts +13 -1
  29. package/dist/runtime.cjs +17 -3
  30. package/dist/runtime.cjs.map +1 -1
  31. package/dist/runtime.js +4 -4
  32. package/dist/schema.cjs.map +1 -1
  33. package/dist/schema.js +2 -2
  34. package/dist/styles.css +80 -0
  35. package/dist/styles.css.map +1 -1
  36. package/dist/{types-CFEtuRWJ.d.ts → types-BGtR3k9J.d.ts} +8 -1
  37. package/dist/{types-DneUoivl.d.cts → types-B_pxRqfw.d.cts} +8 -1
  38. package/dist/ux.cjs.map +1 -1
  39. package/dist/ux.d.cts +1 -1
  40. package/dist/ux.d.ts +1 -1
  41. package/dist/ux.js +1 -1
  42. package/package.json +1 -1
  43. package/dist/chunk-AUL4LU63.js.map +0 -1
  44. package/dist/chunk-KDHLKBL2.js.map +0 -1
  45. package/dist/chunk-O7F6SGV4.js.map +0 -1
  46. package/dist/chunk-YXRLIV7A.js.map +0 -1
  47. package/dist/chunk-ZB4HHQMR.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -9,10 +9,10 @@ import { WorkflowSchema, WorkflowMetadata } from './schema/index.cjs';
9
9
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.cjs';
10
10
  import { AutoLayoutOptions } from './layout/index.cjs';
11
11
  export { AutoLayoutDirection } from './layout/index.cjs';
12
- import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-DneUoivl.cjs';
13
- export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-DneUoivl.cjs';
12
+ import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-B_pxRqfw.cjs';
13
+ export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-B_pxRqfw.cjs';
14
14
  import { FlowRunFeedEntry } from './runtime/index.cjs';
15
- export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
15
+ export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
16
16
  export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-AXzOR-Wq.cjs';
17
17
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, P as PauseAwaiting, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from './pause-9iT4tCEV.cjs';
18
18
  import './capabilities-exVeADXz.cjs';
@@ -36,6 +36,8 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
36
36
  * disable. An `isValidConnection` you pass yourself always takes precedence.
37
37
  */
38
38
  validateConnections?: boolean | ConnectionValidatorOptions;
39
+ /** Show alignment guide lines while dragging, and snap to them. Default off. */
40
+ showHelperLines?: boolean;
39
41
  /** Optional toolbar / palette etc. rendered above the canvas. */
40
42
  toolbar?: ReactNode;
41
43
  className?: string;
@@ -50,7 +52,7 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
50
52
  * (xyflow's standard pattern). The surrounding `useFlowState` hook in
51
53
  * `runtime/use-flow-state.ts` is a convenience that wires those for you.
52
54
  */
53
- declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
55
+ declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, colorMode, showHelperLines, onNodesChange, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
54
56
 
55
57
  /**
56
58
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
@@ -421,12 +423,7 @@ declare const DecisionNode: react.MemoExoticComponent<typeof DecisionNodeInner>;
421
423
  declare function OutputNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
422
424
  declare const OutputNode: react.MemoExoticComponent<typeof OutputNodeInner>;
423
425
 
424
- /**
425
- * NoteNode — annotation card with no ports. Useful for documenting graphs.
426
- * Body is plain text; hosts can wire onChange via the editor's onNodesChange
427
- * if they want it editable.
428
- */
429
- declare function NoteNodeInner({ data, selected }: NodeProps<FlowNode>): react.JSX.Element;
426
+ declare function NoteNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
430
427
  declare const NoteNode: react.MemoExoticComponent<typeof NoteNodeInner>;
431
428
 
432
429
  /**
package/dist/index.d.ts CHANGED
@@ -9,10 +9,10 @@ import { WorkflowSchema, WorkflowMetadata } from './schema/index.js';
9
9
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.js';
10
10
  import { AutoLayoutOptions } from './layout/index.js';
11
11
  export { AutoLayoutDirection } from './layout/index.js';
12
- import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-CFEtuRWJ.js';
13
- export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-CFEtuRWJ.js';
12
+ import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-BGtR3k9J.js';
13
+ export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-BGtR3k9J.js';
14
14
  import { FlowRunFeedEntry } from './runtime/index.js';
15
- export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
15
+ export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
16
16
  export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-9U-gaYVF.js';
17
17
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, P as PauseAwaiting, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from './pause-9iT4tCEV.js';
18
18
  import './capabilities-85Mjn4uW.js';
@@ -36,6 +36,8 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
36
36
  * disable. An `isValidConnection` you pass yourself always takes precedence.
37
37
  */
38
38
  validateConnections?: boolean | ConnectionValidatorOptions;
39
+ /** Show alignment guide lines while dragging, and snap to them. Default off. */
40
+ showHelperLines?: boolean;
39
41
  /** Optional toolbar / palette etc. rendered above the canvas. */
40
42
  toolbar?: ReactNode;
41
43
  className?: string;
@@ -50,7 +52,7 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
50
52
  * (xyflow's standard pattern). The surrounding `useFlowState` hook in
51
53
  * `runtime/use-flow-state.ts` is a convenience that wires those for you.
52
54
  */
53
- declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
55
+ declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, colorMode, showHelperLines, onNodesChange, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
54
56
 
55
57
  /**
56
58
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
@@ -421,12 +423,7 @@ declare const DecisionNode: react.MemoExoticComponent<typeof DecisionNodeInner>;
421
423
  declare function OutputNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
422
424
  declare const OutputNode: react.MemoExoticComponent<typeof OutputNodeInner>;
423
425
 
424
- /**
425
- * NoteNode — annotation card with no ports. Useful for documenting graphs.
426
- * Body is plain text; hosts can wire onChange via the editor's onNodesChange
427
- * if they want it editable.
428
- */
429
- declare function NoteNodeInner({ data, selected }: NodeProps<FlowNode>): react.JSX.Element;
426
+ declare function NoteNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
430
427
  declare const NoteNode: react.MemoExoticComponent<typeof NoteNodeInner>;
431
428
 
432
429
  /**
package/dist/index.js CHANGED
@@ -1,19 +1,19 @@
1
- import { useFlowState, useFlowRun, useFlowHistory, applyStatusesToNodes } from './chunk-O7F6SGV4.js';
2
- export { applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-O7F6SGV4.js';
3
- import { buildNodeTypes, registerBuiltinKinds, createConnectionValidator } from './chunk-AUL4LU63.js';
4
- export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds } from './chunk-AUL4LU63.js';
5
- import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap } from './chunk-ZB4HHQMR.js';
1
+ import { useFlowState, useFlowRun, useFlowHistory, applyOutputsToNodes, applyStatusesToNodes } from './chunk-OFW7GN3Y.js';
2
+ export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-OFW7GN3Y.js';
3
+ import { buildNodeTypes, FlowEditorProvider, registerBuiltinKinds, NoteNode, createConnectionValidator } from './chunk-ATQXZEVD.js';
4
+ export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional } from './chunk-ATQXZEVD.js';
5
+ import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap, ViewportPortal } from './chunk-OWENS2H5.js';
6
6
  export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
7
7
  import './chunk-USL4FMFU.js';
8
- export { runFlow } from './chunk-KDHLKBL2.js';
9
- import { workflowToBlob, importWorkflow, exportWorkflow } from './chunk-OHVSEHDC.js';
10
- export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './chunk-OHVSEHDC.js';
8
+ export { runFlow } from './chunk-U2VJB7HR.js';
9
+ import { workflowToBlob, importWorkflow, exportWorkflow } from './chunk-N5ICD2WZ.js';
10
+ export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './chunk-N5ICD2WZ.js';
11
11
  export { resolveNodePorts, resolvePortSpec } from './chunk-TITD5W4Y.js';
12
- import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, validateConfig, categoryAccent } from './chunk-YXRLIV7A.js';
13
- export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-YXRLIV7A.js';
12
+ import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, validateConfig, categoryAccent } from './chunk-Q6IPK64P.js';
13
+ export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-Q6IPK64P.js';
14
14
  import { getRichInputAdapter } from './chunk-F5RPRB7A.js';
15
15
  export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter } from './chunk-F5RPRB7A.js';
16
- import { memo, createContext, forwardRef, useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle, useContext } from 'react';
16
+ import { memo, forwardRef, useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle } from 'react';
17
17
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
18
18
 
19
19
  function NodeShellInner({
@@ -108,14 +108,6 @@ function OutputNodeInner(props) {
108
108
  return /* @__PURE__ */ jsx(NodeShell, { node: props, accent: "#a855f7", tag: "OUTPUT", icon: "\u25CF", showOutputs: false });
109
109
  }
110
110
  var OutputNode = memo(OutputNodeInner);
111
- function NoteNodeInner({ data, selected }) {
112
- const noteData = data;
113
- return /* @__PURE__ */ jsxs("div", { className: `ff-note ${selected ? "ff-note--selected" : ""}`, children: [
114
- noteData.label && /* @__PURE__ */ jsx("div", { className: "ff-note__title", children: noteData.label }),
115
- noteData.body && /* @__PURE__ */ jsx("p", { className: "ff-note__body", children: noteData.body })
116
- ] });
117
- }
118
- var NoteNode = memo(NoteNodeInner);
119
111
  function SubgraphNodeInner(props) {
120
112
  const data = props.data;
121
113
  const childCount = data.childIds?.length ?? 0;
@@ -273,6 +265,97 @@ function distributeNodes(nodes, axis) {
273
265
  return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
274
266
  });
275
267
  }
268
+
269
+ // src/components/canvas/helper-lines.ts
270
+ var w = (n) => n.width ?? n.measured?.width ?? 0;
271
+ var h = (n) => n.height ?? n.measured?.height ?? 0;
272
+ function getHelperLines(change, nodes, distance = 6) {
273
+ const result = { snapPosition: { x: void 0, y: void 0 } };
274
+ const a = nodes.find((n) => n.id === change.id);
275
+ if (!a || !change.position) return result;
276
+ const A = {
277
+ left: change.position.x,
278
+ right: change.position.x + w(a),
279
+ top: change.position.y,
280
+ bottom: change.position.y + h(a),
281
+ width: w(a),
282
+ height: h(a)
283
+ };
284
+ let vDist = distance;
285
+ let hDist = distance;
286
+ for (const b of nodes) {
287
+ if (b.id === a.id) continue;
288
+ const B = { left: b.position.x, right: b.position.x + w(b), top: b.position.y, bottom: b.position.y + h(b) };
289
+ const ll = Math.abs(A.left - B.left);
290
+ if (ll < vDist) {
291
+ result.snapPosition.x = B.left;
292
+ result.vertical = B.left;
293
+ vDist = ll;
294
+ }
295
+ const rr = Math.abs(A.right - B.right);
296
+ if (rr < vDist) {
297
+ result.snapPosition.x = B.right - A.width;
298
+ result.vertical = B.right;
299
+ vDist = rr;
300
+ }
301
+ const lr = Math.abs(A.left - B.right);
302
+ if (lr < vDist) {
303
+ result.snapPosition.x = B.right;
304
+ result.vertical = B.right;
305
+ vDist = lr;
306
+ }
307
+ const rl = Math.abs(A.right - B.left);
308
+ if (rl < vDist) {
309
+ result.snapPosition.x = B.left - A.width;
310
+ result.vertical = B.left;
311
+ vDist = rl;
312
+ }
313
+ const tt = Math.abs(A.top - B.top);
314
+ if (tt < hDist) {
315
+ result.snapPosition.y = B.top;
316
+ result.horizontal = B.top;
317
+ hDist = tt;
318
+ }
319
+ const bb = Math.abs(A.bottom - B.bottom);
320
+ if (bb < hDist) {
321
+ result.snapPosition.y = B.bottom - A.height;
322
+ result.horizontal = B.bottom;
323
+ hDist = bb;
324
+ }
325
+ const tb = Math.abs(A.top - B.bottom);
326
+ if (tb < hDist) {
327
+ result.snapPosition.y = B.bottom;
328
+ result.horizontal = B.bottom;
329
+ hDist = tb;
330
+ }
331
+ const bt = Math.abs(A.bottom - B.top);
332
+ if (bt < hDist) {
333
+ result.snapPosition.y = B.top - A.height;
334
+ result.horizontal = B.top;
335
+ hDist = bt;
336
+ }
337
+ }
338
+ return result;
339
+ }
340
+ function HelperLines({ horizontal, vertical }) {
341
+ if (horizontal === void 0 && vertical === void 0) return null;
342
+ return /* @__PURE__ */ jsxs(ViewportPortal, { children: [
343
+ vertical !== void 0 && /* @__PURE__ */ jsx(
344
+ "div",
345
+ {
346
+ className: "ff-helper-line",
347
+ style: { position: "absolute", transform: `translateX(${vertical}px)`, top: -5e3, height: 1e4, width: 1, pointerEvents: "none" }
348
+ }
349
+ ),
350
+ horizontal !== void 0 && /* @__PURE__ */ jsx(
351
+ "div",
352
+ {
353
+ className: "ff-helper-line",
354
+ style: { position: "absolute", transform: `translateY(${horizontal}px)`, left: -5e3, width: 1e4, height: 1, pointerEvents: "none" }
355
+ }
356
+ )
357
+ ] });
358
+ }
276
359
  var DEFAULT_FIT_VIEW = { padding: 0.2 };
277
360
  var DEFAULT_EDGE_OPTIONS = {
278
361
  type: "smoothstep",
@@ -287,6 +370,9 @@ function FlowCanvas({
287
370
  height = 600,
288
371
  validateConnections = true,
289
372
  isValidConnection,
373
+ colorMode,
374
+ showHelperLines = false,
375
+ onNodesChange,
290
376
  toolbar,
291
377
  nodeTypes,
292
378
  edgeTypes,
@@ -313,33 +399,68 @@ function FlowCanvas({
313
399
  [edgeTypes]
314
400
  );
315
401
  const orderedNodes = useMemo(() => sortNodesParentFirst(nodes), [nodes]);
316
- return /* @__PURE__ */ jsxs("div", { className: ["ff-canvas", className ?? ""].filter(Boolean).join(" "), style: { height, ...style }, children: [
317
- toolbar && /* @__PURE__ */ jsx("div", { className: "ff-canvas__toolbar", children: toolbar }),
318
- /* @__PURE__ */ jsx("div", { className: "ff-canvas__surface", children: /* @__PURE__ */ jsxs(
319
- index,
320
- {
321
- nodes: orderedNodes,
322
- edges,
323
- nodeTypes: mergedNodeTypes,
324
- edgeTypes: mergedEdgeTypes,
325
- fitView: true,
326
- fitViewOptions: DEFAULT_FIT_VIEW,
327
- defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
328
- proOptions: { hideAttribution: true },
329
- zoomActivationKeyCode: "Shift",
330
- preventScrolling: false,
331
- isValidConnection: resolvedIsValidConnection,
332
- ...rest,
333
- children: [
334
- background !== "none" && /* @__PURE__ */ jsx(Background, { variant: background, gap: 20, size: 1, color: "rgba(0,0,0,0.18)" }),
335
- showControls && /* @__PURE__ */ jsx(Controls, { className: "ff-controls", position: "bottom-right" }),
336
- showMinimap && /* @__PURE__ */ jsx(MiniMap, { className: "ff-minimap", pannable: true, zoomable: true })
337
- ]
402
+ const [helperLines, setHelperLines] = useState({});
403
+ const handleNodesChange = useCallback(
404
+ (changes) => {
405
+ if (showHelperLines) {
406
+ const pos = changes.filter((c) => c.type === "position" && c.position);
407
+ if (pos.length === 1 && pos[0].dragging) {
408
+ const lines = getHelperLines(pos[0], nodesRef.current);
409
+ if (lines.snapPosition.x !== void 0) pos[0].position.x = lines.snapPosition.x;
410
+ if (lines.snapPosition.y !== void 0) pos[0].position.y = lines.snapPosition.y;
411
+ setHelperLines({ horizontal: lines.horizontal, vertical: lines.vertical });
412
+ } else {
413
+ setHelperLines({});
414
+ }
338
415
  }
339
- ) })
340
- ] });
416
+ onNodesChange?.(changes);
417
+ },
418
+ [showHelperLines, onNodesChange]
419
+ );
420
+ return /* @__PURE__ */ jsxs(
421
+ "div",
422
+ {
423
+ className: [
424
+ "ff-canvas",
425
+ // colorMode drives BOTH react-flow's chrome (below) and our `ff-` styles
426
+ // via the shared `.dark` class / light opt-out — one theme signal.
427
+ colorMode === "dark" ? "dark" : "",
428
+ colorMode === "light" ? "ff-canvas--light" : "",
429
+ className ?? ""
430
+ ].filter(Boolean).join(" "),
431
+ style: { height, ...style },
432
+ children: [
433
+ toolbar && /* @__PURE__ */ jsx("div", { className: "ff-canvas__toolbar", children: toolbar }),
434
+ /* @__PURE__ */ jsx("div", { className: "ff-canvas__surface", children: /* @__PURE__ */ jsxs(
435
+ index,
436
+ {
437
+ nodes: orderedNodes,
438
+ edges,
439
+ onNodesChange: handleNodesChange,
440
+ colorMode,
441
+ nodeTypes: mergedNodeTypes,
442
+ edgeTypes: mergedEdgeTypes,
443
+ fitView: true,
444
+ fitViewOptions: DEFAULT_FIT_VIEW,
445
+ defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
446
+ proOptions: { hideAttribution: true },
447
+ zoomActivationKeyCode: "Shift",
448
+ preventScrolling: false,
449
+ isValidConnection: resolvedIsValidConnection,
450
+ ...rest,
451
+ children: [
452
+ background !== "none" && /* @__PURE__ */ jsx(Background, { variant: background, gap: 20, size: 1, color: "rgba(0,0,0,0.18)" }),
453
+ showControls && /* @__PURE__ */ jsx(Controls, { className: "ff-controls", position: "bottom-right" }),
454
+ showMinimap && /* @__PURE__ */ jsx(MiniMap, { className: "ff-minimap", pannable: true, zoomable: true }),
455
+ showHelperLines && /* @__PURE__ */ jsx(HelperLines, { horizontal: helperLines.horizontal, vertical: helperLines.vertical })
456
+ ]
457
+ }
458
+ ) })
459
+ ]
460
+ }
461
+ );
341
462
  }
342
- var CATEGORY_ORDER = ["trigger", "logic", "data", "ai", "io", "human", "output", "layout", "custom"];
463
+ var CATEGORY_ORDER = ["trigger", "logic", "data", "ai", "io", "human", "output", "layout", "annotation", "custom"];
343
464
  var CATEGORY_LABELS = {
344
465
  trigger: "Triggers",
345
466
  logic: "Logic",
@@ -349,6 +470,7 @@ var CATEGORY_LABELS = {
349
470
  human: "Human",
350
471
  output: "Output",
351
472
  layout: "Layout",
473
+ annotation: "Notes",
352
474
  custom: "Custom"
353
475
  };
354
476
  function NodePalette({ categories, onPick, className, style }) {
@@ -940,18 +1062,6 @@ function formatTime(at) {
940
1062
  const d = new Date(at);
941
1063
  return `${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${Math.floor(d.getMilliseconds() / 100)}`;
942
1064
  }
943
- var FlowEditorContext = createContext(null);
944
- var FlowEditorProvider = FlowEditorContext.Provider;
945
- function useFlowEditor() {
946
- const api = useContext(FlowEditorContext);
947
- if (api === null) {
948
- throw new Error("useFlowEditor() must be called inside <FlowEditor>.");
949
- }
950
- return api;
951
- }
952
- function useFlowEditorOptional() {
953
- return useContext(FlowEditorContext);
954
- }
955
1065
  var FlowEditor = forwardRef(function FlowEditor2(props, ref) {
956
1066
  return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(
957
1067
  "div",
@@ -993,8 +1103,11 @@ function FlowEditorInner({
993
1103
  useEffect(() => onNodeKindsChanged(() => force((n) => n + 1)), []);
994
1104
  const nodeTypes = useMemo(() => buildNodeTypes(), [listNodeKinds().length]);
995
1105
  const renderedNodes = useMemo(
996
- () => applyStatusesToNodes(flow.nodes, runner.statuses, runner.statusText),
997
- [flow.nodes, runner.statuses, runner.statusText]
1106
+ () => applyOutputsToNodes(
1107
+ applyStatusesToNodes(flow.nodes, runner.statuses, runner.statusText),
1108
+ runner.outputs
1109
+ ),
1110
+ [flow.nodes, runner.statuses, runner.statusText, runner.outputs]
998
1111
  );
999
1112
  const [selectedId, setSelectedId] = useState(null);
1000
1113
  const selected = useMemo(() => flow.nodes.find((n) => n.id === selectedId) ?? null, [flow.nodes, selectedId]);
@@ -1178,10 +1291,10 @@ function FlowEditorInner({
1178
1291
  (orientation = "horizontal", title) => {
1179
1292
  const vertical = orientation === "vertical";
1180
1293
  const lanes = flow.nodes.filter(isLaneNode);
1181
- const w = vertical ? 280 : 680;
1182
- const h = vertical ? 480 : 168;
1294
+ const w2 = vertical ? 280 : 680;
1295
+ const h2 = vertical ? 480 : 168;
1183
1296
  const end = lanes.reduce(
1184
- (m, l) => Math.max(m, vertical ? l.position.x + (l.width ?? w) : l.position.y + (l.height ?? h)),
1297
+ (m, l) => Math.max(m, vertical ? l.position.x + (l.width ?? w2) : l.position.y + (l.height ?? h2)),
1185
1298
  0
1186
1299
  );
1187
1300
  const id = newNodeId();
@@ -1190,8 +1303,8 @@ function FlowEditorInner({
1190
1303
  id,
1191
1304
  type: "@particle-academy/lane",
1192
1305
  position: vertical ? { x: lanes.length ? end + 12 : 0, y: 0 } : { x: 0, y: lanes.length ? end + 12 : 0 },
1193
- width: w,
1194
- height: h,
1306
+ width: w2,
1307
+ height: h2,
1195
1308
  data: { kind: "@particle-academy/lane", label: name, config: { title: name, orientation } }
1196
1309
  };
1197
1310
  flow.setNodes((all) => [...all, node]);
@@ -1637,6 +1750,6 @@ function NodePort({ side, type, id, style, title, className }) {
1637
1750
  // src/index.ts
1638
1751
  registerBuiltinKinds();
1639
1752
 
1640
- export { ActionNode, ConfigFieldRenderer, DecisionNode, FlowCanvas, FlowEditor, FlowRunControls, FlowRunFeed, NodeConfigPanel, NodePalette, NodePort, NodeShell, NoteNode, OutputNode, SubgraphNode, TriggerNode, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge2 as reconnectEdge, useFlowEditor, useFlowEditorOptional };
1753
+ export { ActionNode, ConfigFieldRenderer, DecisionNode, FlowCanvas, FlowEditor, FlowRunControls, FlowRunFeed, NodeConfigPanel, NodePalette, NodePort, NodeShell, OutputNode, SubgraphNode, TriggerNode, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge2 as reconnectEdge };
1641
1754
  //# sourceMappingURL=index.js.map
1642
1755
  //# sourceMappingURL=index.js.map