@particle-academy/fancy-flow 0.21.0 → 0.23.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 (61) hide show
  1. package/dist/{capabilities-DUhAc-EJ.d.ts → capabilities-85Mjn4uW.d.ts} +1 -1
  2. package/dist/{capabilities-ChE3Xi8R.d.cts → capabilities-exVeADXz.d.cts} +1 -1
  3. package/dist/{chunk-JE4ENQSE.js → chunk-AUL4LU63.js} +55 -7
  4. package/dist/chunk-AUL4LU63.js.map +1 -0
  5. package/dist/{chunk-L4AX73Q6.js → chunk-KDHLKBL2.js} +11 -5
  6. package/dist/chunk-KDHLKBL2.js.map +1 -0
  7. package/dist/{chunk-G3WHXMZ3.js → chunk-O7F6SGV4.js} +3 -3
  8. package/dist/{chunk-G3WHXMZ3.js.map → chunk-O7F6SGV4.js.map} +1 -1
  9. package/dist/{chunk-HMWYJIRP.js → chunk-OHVSEHDC.js} +3 -3
  10. package/dist/{chunk-HMWYJIRP.js.map → chunk-OHVSEHDC.js.map} +1 -1
  11. package/dist/{chunk-U5F22BHV.js → chunk-YXRLIV7A.js} +4 -2
  12. package/dist/chunk-YXRLIV7A.js.map +1 -0
  13. package/dist/engine.cjs +8 -2
  14. package/dist/engine.cjs.map +1 -1
  15. package/dist/engine.d.cts +3 -3
  16. package/dist/engine.d.ts +3 -3
  17. package/dist/engine.js +4 -4
  18. package/dist/index.cjs +5271 -3395
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +32 -7
  21. package/dist/index.d.ts +32 -7
  22. package/dist/index.js +213 -107
  23. package/dist/index.js.map +1 -1
  24. package/dist/layout/index.d.cts +30 -0
  25. package/dist/layout/index.d.ts +30 -0
  26. package/dist/layout.cjs +1693 -0
  27. package/dist/layout.cjs.map +1 -0
  28. package/dist/layout.js +1691 -0
  29. package/dist/layout.js.map +1 -0
  30. package/dist/llm/vercel-ai.d.cts +2 -2
  31. package/dist/llm/vercel-ai.d.ts +2 -2
  32. package/dist/registry/index.d.cts +5 -5
  33. package/dist/registry/index.d.ts +5 -5
  34. package/dist/registry.cjs +59 -3
  35. package/dist/registry.cjs.map +1 -1
  36. package/dist/registry.js +4 -4
  37. package/dist/{run-flow-ChBi6q-W.d.ts → run-flow-9U-gaYVF.d.ts} +1 -1
  38. package/dist/{run-flow-VKm57Hcj.d.cts → run-flow-AXzOR-Wq.d.cts} +1 -1
  39. package/dist/runtime/index.d.cts +3 -3
  40. package/dist/runtime/index.d.ts +3 -3
  41. package/dist/runtime.cjs +8 -2
  42. package/dist/runtime.cjs.map +1 -1
  43. package/dist/runtime.js +3 -3
  44. package/dist/schema/index.d.cts +1 -1
  45. package/dist/schema/index.d.ts +1 -1
  46. package/dist/schema.cjs.map +1 -1
  47. package/dist/schema.js +2 -2
  48. package/dist/styles.css +41 -0
  49. package/dist/styles.css.map +1 -1
  50. package/dist/{types-VGkCXEO6.d.ts → types-CFEtuRWJ.d.ts} +11 -3
  51. package/dist/{types-sOmpCitB.d.cts → types-CMSrWVYM.d.cts} +1 -1
  52. package/dist/{types-sOmpCitB.d.ts → types-CMSrWVYM.d.ts} +1 -1
  53. package/dist/{types-LG-YckGh.d.cts → types-DneUoivl.d.cts} +11 -3
  54. package/dist/ux.cjs.map +1 -1
  55. package/dist/ux.d.cts +3 -3
  56. package/dist/ux.d.ts +3 -3
  57. package/dist/ux.js +1 -1
  58. package/package.json +12 -1
  59. package/dist/chunk-JE4ENQSE.js.map +0 -1
  60. package/dist/chunk-L4AX73Q6.js.map +0 -1
  61. package/dist/chunk-U5F22BHV.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -3,17 +3,19 @@ import { ReactNode, CSSProperties, ComponentType } from 'react';
3
3
  import { ReactFlowProps, Edge, BackgroundVariant, Connection, NodeProps, NodeTypes } from '@xyflow/react';
4
4
  import { ConnectionValidatorOptions } from './registry/index.cjs';
5
5
  export { ANY_PORT_TYPE, BUILTIN_KINDS, PortCompatibility, RegistryNode, RichInputAdapter, RichInputPreview, buildNodeTypes, categoryAccent, createConnectionValidator, defaultConfigFor, defaultPortCompatibility, getNodeKind, getRichInputAdapter, isRichInputEnabled, listNodeKinds, onNodeKindsChanged, onRichInputAdapterChanged, registerBuiltinKinds, registerNodeKind, registerRichInputAdapter, resolveNodePorts, resolvePortSpec, validateConfig } from './registry/index.cjs';
6
- import { b as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-sOmpCitB.cjs';
7
- export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, a as FlowEdge, c as FlowNodeData, d as FlowNodeKind, N as NodeExecutor, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.cjs';
6
+ import { a as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-CMSrWVYM.cjs';
7
+ export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, b as FlowEdge, c as FlowNodeData, d as FlowNodeKind, N as NodeExecutor, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-CMSrWVYM.cjs';
8
8
  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
- import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-LG-YckGh.cjs';
11
- export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-LG-YckGh.cjs';
10
+ import { AutoLayoutOptions } from './layout/index.cjs';
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
14
  import { FlowRunFeedEntry } from './runtime/index.cjs';
13
15
  export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
14
- export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-VKm57Hcj.cjs';
16
+ export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-AXzOR-Wq.cjs';
15
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';
16
- import './capabilities-ChE3Xi8R.cjs';
18
+ import './capabilities-exVeADXz.cjs';
17
19
 
18
20
  type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" | "height"> & {
19
21
  nodes: FlowNode[];
@@ -136,6 +138,16 @@ type FlowEditorApi = {
136
138
  alignSelected: (edge: AlignEdge) => void;
137
139
  /** Evenly distribute the multi-selection's gaps along an axis (needs 3+). */
138
140
  distributeSelected: (axis: "h" | "v") => void;
141
+ /** Add a swimlane, stacked with existing lanes. Returns the new lane id. */
142
+ addLane: (orientation?: "horizontal" | "vertical", title?: string) => string | null;
143
+ /** Put a node inside a lane (sets parentId + a lane-relative position). */
144
+ assignToLane: (nodeId: string, laneId: string) => void;
145
+ /** Remove a node from its lane (restores its absolute position). */
146
+ removeFromLane: (nodeId: string) => void;
147
+ /** Auto-arrange the graph (or a lane's children) into a tidy DAG layout. */
148
+ autoLayout: (options?: AutoLayoutOptions) => void;
149
+ /** Tidy just one lane's children. */
150
+ tidyLane: (laneId: string) => void;
139
151
  /** Copy the current selection to the editor clipboard. */
140
152
  copy: () => void;
141
153
  /** Copy then delete the current selection. */
@@ -192,6 +204,10 @@ type FlowEditorBuiltins = {
192
204
  delete?: boolean;
193
205
  /** Undo/redo toolbar buttons. Default true. */
194
206
  history?: boolean;
207
+ /** "Add lane" toolbar button. Default true. */
208
+ addLane?: boolean;
209
+ /** "Tidy" (auto-layout) toolbar button. Default true. */
210
+ autoLayout?: boolean;
195
211
  /** Right-click a node for Delete / Duplicate. Default true. */
196
212
  contextMenu?: boolean;
197
213
  /** Right-click a connection for Label / Delete. Default true. */
@@ -426,6 +442,15 @@ declare const NoteNode: react.MemoExoticComponent<typeof NoteNodeInner>;
426
442
  declare function SubgraphNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
427
443
  declare const SubgraphNode: react.MemoExoticComponent<typeof SubgraphNodeInner>;
428
444
 
445
+ /**
446
+ * LaneNode — a resizable swimlane band. A portless container: child nodes are
447
+ * parented into it (`parentId` + `extent:'parent'`) and render on top, so this
448
+ * node is just the titled background. Wired as the `@particle-academy/lane`
449
+ * kind's `component`, so `buildNodeTypes` uses it instead of the default card.
450
+ */
451
+ declare function LaneNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
452
+ declare const LaneNode: react.MemoExoticComponent<typeof LaneNodeInner>;
453
+
429
454
  type NodeShellProps = {
430
455
  /** Required: the xyflow node this is rendering. */
431
456
  node: NodeProps<FlowNode>;
@@ -522,4 +547,4 @@ type FlowRunFeedProps = {
522
547
  /** FlowRunFeed — scrolling log panel. Auto-scrolls to bottom on new entries. */
523
548
  declare function FlowRunFeed({ entries, className, style }: FlowRunFeedProps): react.JSX.Element;
524
549
 
525
- export { ActionNode, type AlignEdge, ConfigField, ConfigFieldRenderer, type ConfigFieldRendererProps, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
550
+ export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigField, ConfigFieldRenderer, type ConfigFieldRendererProps, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.d.ts CHANGED
@@ -3,17 +3,19 @@ import { ReactNode, CSSProperties, ComponentType } from 'react';
3
3
  import { ReactFlowProps, Edge, BackgroundVariant, Connection, NodeProps, NodeTypes } from '@xyflow/react';
4
4
  import { ConnectionValidatorOptions } from './registry/index.js';
5
5
  export { ANY_PORT_TYPE, BUILTIN_KINDS, PortCompatibility, RegistryNode, RichInputAdapter, RichInputPreview, buildNodeTypes, categoryAccent, createConnectionValidator, defaultConfigFor, defaultPortCompatibility, getNodeKind, getRichInputAdapter, isRichInputEnabled, listNodeKinds, onNodeKindsChanged, onRichInputAdapterChanged, registerBuiltinKinds, registerNodeKind, registerRichInputAdapter, resolveNodePorts, resolvePortSpec, validateConfig } from './registry/index.js';
6
- import { b as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-sOmpCitB.js';
7
- export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, a as FlowEdge, c as FlowNodeData, d as FlowNodeKind, N as NodeExecutor, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.js';
6
+ import { a as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-CMSrWVYM.js';
7
+ export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, b as FlowEdge, c as FlowNodeData, d as FlowNodeKind, N as NodeExecutor, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-CMSrWVYM.js';
8
8
  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
- import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-VGkCXEO6.js';
11
- export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-VGkCXEO6.js';
10
+ import { AutoLayoutOptions } from './layout/index.js';
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
14
  import { FlowRunFeedEntry } from './runtime/index.js';
13
15
  export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
14
- export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-ChBi6q-W.js';
16
+ export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-9U-gaYVF.js';
15
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';
16
- import './capabilities-DUhAc-EJ.js';
18
+ import './capabilities-85Mjn4uW.js';
17
19
 
18
20
  type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" | "height"> & {
19
21
  nodes: FlowNode[];
@@ -136,6 +138,16 @@ type FlowEditorApi = {
136
138
  alignSelected: (edge: AlignEdge) => void;
137
139
  /** Evenly distribute the multi-selection's gaps along an axis (needs 3+). */
138
140
  distributeSelected: (axis: "h" | "v") => void;
141
+ /** Add a swimlane, stacked with existing lanes. Returns the new lane id. */
142
+ addLane: (orientation?: "horizontal" | "vertical", title?: string) => string | null;
143
+ /** Put a node inside a lane (sets parentId + a lane-relative position). */
144
+ assignToLane: (nodeId: string, laneId: string) => void;
145
+ /** Remove a node from its lane (restores its absolute position). */
146
+ removeFromLane: (nodeId: string) => void;
147
+ /** Auto-arrange the graph (or a lane's children) into a tidy DAG layout. */
148
+ autoLayout: (options?: AutoLayoutOptions) => void;
149
+ /** Tidy just one lane's children. */
150
+ tidyLane: (laneId: string) => void;
139
151
  /** Copy the current selection to the editor clipboard. */
140
152
  copy: () => void;
141
153
  /** Copy then delete the current selection. */
@@ -192,6 +204,10 @@ type FlowEditorBuiltins = {
192
204
  delete?: boolean;
193
205
  /** Undo/redo toolbar buttons. Default true. */
194
206
  history?: boolean;
207
+ /** "Add lane" toolbar button. Default true. */
208
+ addLane?: boolean;
209
+ /** "Tidy" (auto-layout) toolbar button. Default true. */
210
+ autoLayout?: boolean;
195
211
  /** Right-click a node for Delete / Duplicate. Default true. */
196
212
  contextMenu?: boolean;
197
213
  /** Right-click a connection for Label / Delete. Default true. */
@@ -426,6 +442,15 @@ declare const NoteNode: react.MemoExoticComponent<typeof NoteNodeInner>;
426
442
  declare function SubgraphNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
427
443
  declare const SubgraphNode: react.MemoExoticComponent<typeof SubgraphNodeInner>;
428
444
 
445
+ /**
446
+ * LaneNode — a resizable swimlane band. A portless container: child nodes are
447
+ * parented into it (`parentId` + `extent:'parent'`) and render on top, so this
448
+ * node is just the titled background. Wired as the `@particle-academy/lane`
449
+ * kind's `component`, so `buildNodeTypes` uses it instead of the default card.
450
+ */
451
+ declare function LaneNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
452
+ declare const LaneNode: react.MemoExoticComponent<typeof LaneNodeInner>;
453
+
429
454
  type NodeShellProps = {
430
455
  /** Required: the xyflow node this is rendering. */
431
456
  node: NodeProps<FlowNode>;
@@ -522,4 +547,4 @@ type FlowRunFeedProps = {
522
547
  /** FlowRunFeed — scrolling log panel. Auto-scrolls to bottom on new entries. */
523
548
  declare function FlowRunFeed({ entries, className, style }: FlowRunFeedProps): react.JSX.Element;
524
549
 
525
- export { ActionNode, type AlignEdge, ConfigField, ConfigFieldRenderer, type ConfigFieldRendererProps, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
550
+ export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigField, ConfigFieldRenderer, type ConfigFieldRendererProps, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
- import { useFlowState, useFlowRun, useFlowHistory, applyStatusesToNodes } from './chunk-G3WHXMZ3.js';
2
- export { applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-G3WHXMZ3.js';
3
- import { buildNodeTypes, registerBuiltinKinds, createConnectionValidator } from './chunk-JE4ENQSE.js';
4
- export { ANY_PORT_TYPE, BUILTIN_KINDS, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds } from './chunk-JE4ENQSE.js';
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
5
  import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap } from './chunk-ZB4HHQMR.js';
6
6
  export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
7
- export { runFlow } from './chunk-L4AX73Q6.js';
8
- import { workflowToBlob, importWorkflow, exportWorkflow } from './chunk-HMWYJIRP.js';
9
- export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './chunk-HMWYJIRP.js';
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';
10
11
  export { resolveNodePorts, resolvePortSpec } from './chunk-TITD5W4Y.js';
11
- import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, validateConfig, categoryAccent } from './chunk-U5F22BHV.js';
12
- export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-U5F22BHV.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';
13
14
  import { getRichInputAdapter } from './chunk-F5RPRB7A.js';
14
15
  export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter } from './chunk-F5RPRB7A.js';
15
- import './chunk-USL4FMFU.js';
16
16
  import { memo, createContext, forwardRef, useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle, useContext } from 'react';
17
17
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
18
18
 
@@ -139,6 +139,140 @@ var defaultNodeTypes = {
139
139
  note: NoteNode,
140
140
  subgraph: SubgraphNode
141
141
  };
142
+
143
+ // src/components/FlowEditor/graph-ops.ts
144
+ function removeNodes(graph, ids) {
145
+ if (ids.length === 0) return graph;
146
+ const doomed = new Set(ids);
147
+ return {
148
+ nodes: graph.nodes.filter((n) => !doomed.has(n.id)),
149
+ edges: graph.edges.filter((e) => !doomed.has(e.source) && !doomed.has(e.target))
150
+ };
151
+ }
152
+ function removeEdges(edges, ids) {
153
+ if (ids.length === 0) return edges;
154
+ const doomed = new Set(ids);
155
+ return edges.filter((e) => !doomed.has(e.id));
156
+ }
157
+ function setEdgeLabel(edges, id, label) {
158
+ const next = label?.trim();
159
+ return edges.map((e) => {
160
+ if (e.id !== id) return e;
161
+ if (!next) {
162
+ const { label: _drop, ...rest } = e;
163
+ return rest;
164
+ }
165
+ return { ...e, label: next };
166
+ });
167
+ }
168
+ function duplicateNode(node, id, offset = 40) {
169
+ return {
170
+ ...node,
171
+ id,
172
+ position: { x: node.position.x + offset, y: node.position.y + offset },
173
+ // Deep copy so the clone's config edits don't mutate the original.
174
+ data: JSON.parse(JSON.stringify(node.data ?? {}))
175
+ };
176
+ }
177
+ function cloneSubgraph(nodes, edges, opts) {
178
+ const offset = opts.offset ?? 40;
179
+ const idMap = /* @__PURE__ */ new Map();
180
+ for (const n of nodes) idMap.set(n.id, opts.makeId());
181
+ const clonedNodes = nodes.map((n) => {
182
+ const cloned = duplicateNode(n, idMap.get(n.id), offset);
183
+ const parentId = n.parentId;
184
+ if (parentId && idMap.has(parentId)) cloned.parentId = idMap.get(parentId);
185
+ else if (parentId) delete cloned.parentId;
186
+ return cloned;
187
+ });
188
+ 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) }));
189
+ return { nodes: clonedNodes, edges: clonedEdges, idMap };
190
+ }
191
+ function reconnectEdge2(edges, oldEdge, newConnection) {
192
+ return reconnectEdge(oldEdge, newConnection, edges, { shouldReplaceId: false });
193
+ }
194
+ var nodeW = (n) => n.width ?? n.measured?.width ?? 0;
195
+ var nodeH = (n) => n.height ?? n.measured?.height ?? 0;
196
+ function alignNodes(nodes, edge) {
197
+ if (nodes.length < 2) return nodes;
198
+ const minL = Math.min(...nodes.map((n) => n.position.x));
199
+ const maxR = Math.max(...nodes.map((n) => n.position.x + nodeW(n)));
200
+ const minT = Math.min(...nodes.map((n) => n.position.y));
201
+ const maxB = Math.max(...nodes.map((n) => n.position.y + nodeH(n)));
202
+ const cx = (minL + maxR) / 2;
203
+ const cy = (minT + maxB) / 2;
204
+ return nodes.map((n) => {
205
+ let { x, y } = n.position;
206
+ if (edge === "left") x = minL;
207
+ else if (edge === "right") x = maxR - nodeW(n);
208
+ else if (edge === "hcenter") x = cx - nodeW(n) / 2;
209
+ else if (edge === "top") y = minT;
210
+ else if (edge === "bottom") y = maxB - nodeH(n);
211
+ else if (edge === "vcenter") y = cy - nodeH(n) / 2;
212
+ return { ...n, position: { x, y } };
213
+ });
214
+ }
215
+ function sortNodesParentFirst(nodes) {
216
+ const byId = new Map(nodes.map((n) => [n.id, n]));
217
+ const seen = /* @__PURE__ */ new Set();
218
+ const out = [];
219
+ const visit = (n) => {
220
+ if (seen.has(n.id)) return;
221
+ const pid = n.parentId;
222
+ if (pid && byId.has(pid) && !seen.has(pid)) visit(byId.get(pid));
223
+ seen.add(n.id);
224
+ out.push(n);
225
+ };
226
+ for (const n of nodes) visit(n);
227
+ return out;
228
+ }
229
+ function absolutePosition(node, nodes) {
230
+ const pid = node.parentId;
231
+ if (!pid) return node.position;
232
+ const parent = nodes.find((n) => n.id === pid);
233
+ return parent ? { x: node.position.x + parent.position.x, y: node.position.y + parent.position.y } : node.position;
234
+ }
235
+ function assignToLane(nodes, nodeId, laneId) {
236
+ if (nodeId === laneId) return nodes;
237
+ const node = nodes.find((n) => n.id === nodeId);
238
+ const lane = nodes.find((n) => n.id === laneId);
239
+ if (!node || !lane) return nodes;
240
+ const abs = absolutePosition(node, nodes);
241
+ const laneAbs = absolutePosition(lane, nodes);
242
+ const rel = { x: abs.x - laneAbs.x, y: abs.y - laneAbs.y };
243
+ return nodes.map((n) => n.id === nodeId ? { ...n, parentId: laneId, extent: "parent", position: rel } : n);
244
+ }
245
+ function removeFromLane(nodes, nodeId) {
246
+ const node = nodes.find((n) => n.id === nodeId);
247
+ if (!node || !node.parentId) return nodes;
248
+ const abs = absolutePosition(node, nodes);
249
+ return nodes.map((n) => {
250
+ if (n.id !== nodeId) return n;
251
+ const { parentId: _p, extent: _e, ...rest } = n;
252
+ return { ...rest, position: abs };
253
+ });
254
+ }
255
+ function distributeNodes(nodes, axis) {
256
+ if (nodes.length < 3) return nodes;
257
+ const size = (n) => axis === "h" ? nodeW(n) : nodeH(n);
258
+ const coord = (n) => axis === "h" ? n.position.x : n.position.y;
259
+ const sorted = [...nodes].sort((a, b) => coord(a) - coord(b));
260
+ const start = coord(sorted[0]);
261
+ const last = sorted[sorted.length - 1];
262
+ const end = coord(last) + size(last);
263
+ const totalSize = sorted.reduce((s, n) => s + size(n), 0);
264
+ const gap = (end - start - totalSize) / (sorted.length - 1);
265
+ const posById = /* @__PURE__ */ new Map();
266
+ let cursor = start;
267
+ for (const n of sorted) {
268
+ posById.set(n.id, cursor);
269
+ cursor += size(n) + gap;
270
+ }
271
+ return nodes.map((n) => {
272
+ const p = posById.get(n.id);
273
+ return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
274
+ });
275
+ }
142
276
  var DEFAULT_FIT_VIEW = { padding: 0.2 };
143
277
  var DEFAULT_EDGE_OPTIONS = {
144
278
  type: "smoothstep",
@@ -178,12 +312,13 @@ function FlowCanvas({
178
312
  () => edgeTypes ? { ...edgeTypes } : void 0,
179
313
  [edgeTypes]
180
314
  );
315
+ const orderedNodes = useMemo(() => sortNodesParentFirst(nodes), [nodes]);
181
316
  return /* @__PURE__ */ jsxs("div", { className: ["ff-canvas", className ?? ""].filter(Boolean).join(" "), style: { height, ...style }, children: [
182
317
  toolbar && /* @__PURE__ */ jsx("div", { className: "ff-canvas__toolbar", children: toolbar }),
183
318
  /* @__PURE__ */ jsx("div", { className: "ff-canvas__surface", children: /* @__PURE__ */ jsxs(
184
319
  index,
185
320
  {
186
- nodes,
321
+ nodes: orderedNodes,
187
322
  edges,
188
323
  nodeTypes: mergedNodeTypes,
189
324
  edgeTypes: mergedEdgeTypes,
@@ -204,7 +339,7 @@ function FlowCanvas({
204
339
  ) })
205
340
  ] });
206
341
  }
207
- var CATEGORY_ORDER = ["trigger", "logic", "data", "ai", "io", "human", "output", "custom"];
342
+ var CATEGORY_ORDER = ["trigger", "logic", "data", "ai", "io", "human", "output", "layout", "custom"];
208
343
  var CATEGORY_LABELS = {
209
344
  trigger: "Triggers",
210
345
  logic: "Logic",
@@ -213,6 +348,7 @@ var CATEGORY_LABELS = {
213
348
  io: "Connectors",
214
349
  human: "Human",
215
350
  output: "Output",
351
+ layout: "Layout",
216
352
  custom: "Custom"
217
353
  };
218
354
  function NodePalette({ categories, onPick, className, style }) {
@@ -804,100 +940,6 @@ function formatTime(at) {
804
940
  const d = new Date(at);
805
941
  return `${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${Math.floor(d.getMilliseconds() / 100)}`;
806
942
  }
807
-
808
- // src/components/FlowEditor/graph-ops.ts
809
- function removeNodes(graph, ids) {
810
- if (ids.length === 0) return graph;
811
- const doomed = new Set(ids);
812
- return {
813
- nodes: graph.nodes.filter((n) => !doomed.has(n.id)),
814
- edges: graph.edges.filter((e) => !doomed.has(e.source) && !doomed.has(e.target))
815
- };
816
- }
817
- function removeEdges(edges, ids) {
818
- if (ids.length === 0) return edges;
819
- const doomed = new Set(ids);
820
- return edges.filter((e) => !doomed.has(e.id));
821
- }
822
- function setEdgeLabel(edges, id, label) {
823
- const next = label?.trim();
824
- return edges.map((e) => {
825
- if (e.id !== id) return e;
826
- if (!next) {
827
- const { label: _drop, ...rest } = e;
828
- return rest;
829
- }
830
- return { ...e, label: next };
831
- });
832
- }
833
- function duplicateNode(node, id, offset = 40) {
834
- return {
835
- ...node,
836
- id,
837
- position: { x: node.position.x + offset, y: node.position.y + offset },
838
- // Deep copy so the clone's config edits don't mutate the original.
839
- data: JSON.parse(JSON.stringify(node.data ?? {}))
840
- };
841
- }
842
- function cloneSubgraph(nodes, edges, opts) {
843
- const offset = opts.offset ?? 40;
844
- const idMap = /* @__PURE__ */ new Map();
845
- for (const n of nodes) idMap.set(n.id, opts.makeId());
846
- const clonedNodes = nodes.map((n) => {
847
- const cloned = duplicateNode(n, idMap.get(n.id), offset);
848
- const parentId = n.parentId;
849
- if (parentId && idMap.has(parentId)) cloned.parentId = idMap.get(parentId);
850
- else if (parentId) delete cloned.parentId;
851
- return cloned;
852
- });
853
- 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) }));
854
- return { nodes: clonedNodes, edges: clonedEdges, idMap };
855
- }
856
- function reconnectEdge2(edges, oldEdge, newConnection) {
857
- return reconnectEdge(oldEdge, newConnection, edges, { shouldReplaceId: false });
858
- }
859
- var nodeW = (n) => n.width ?? n.measured?.width ?? 0;
860
- var nodeH = (n) => n.height ?? n.measured?.height ?? 0;
861
- function alignNodes(nodes, edge) {
862
- if (nodes.length < 2) return nodes;
863
- const minL = Math.min(...nodes.map((n) => n.position.x));
864
- const maxR = Math.max(...nodes.map((n) => n.position.x + nodeW(n)));
865
- const minT = Math.min(...nodes.map((n) => n.position.y));
866
- const maxB = Math.max(...nodes.map((n) => n.position.y + nodeH(n)));
867
- const cx = (minL + maxR) / 2;
868
- const cy = (minT + maxB) / 2;
869
- return nodes.map((n) => {
870
- let { x, y } = n.position;
871
- if (edge === "left") x = minL;
872
- else if (edge === "right") x = maxR - nodeW(n);
873
- else if (edge === "hcenter") x = cx - nodeW(n) / 2;
874
- else if (edge === "top") y = minT;
875
- else if (edge === "bottom") y = maxB - nodeH(n);
876
- else if (edge === "vcenter") y = cy - nodeH(n) / 2;
877
- return { ...n, position: { x, y } };
878
- });
879
- }
880
- function distributeNodes(nodes, axis) {
881
- if (nodes.length < 3) return nodes;
882
- const size = (n) => axis === "h" ? nodeW(n) : nodeH(n);
883
- const coord = (n) => axis === "h" ? n.position.x : n.position.y;
884
- const sorted = [...nodes].sort((a, b) => coord(a) - coord(b));
885
- const start = coord(sorted[0]);
886
- const last = sorted[sorted.length - 1];
887
- const end = coord(last) + size(last);
888
- const totalSize = sorted.reduce((s, n) => s + size(n), 0);
889
- const gap = (end - start - totalSize) / (sorted.length - 1);
890
- const posById = /* @__PURE__ */ new Map();
891
- let cursor = start;
892
- for (const n of sorted) {
893
- posById.set(n.id, cursor);
894
- cursor += size(n) + gap;
895
- }
896
- return nodes.map((n) => {
897
- const p = posById.get(n.id);
898
- return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
899
- });
900
- }
901
943
  var FlowEditorContext = createContext(null);
902
944
  var FlowEditorProvider = FlowEditorContext.Provider;
903
945
  function useFlowEditor() {
@@ -1128,6 +1170,49 @@ function FlowEditorInner({
1128
1170
  (oldEdge, conn) => flow.setEdges((eds) => reconnectEdge2(eds, oldEdge, conn)),
1129
1171
  [flow]
1130
1172
  );
1173
+ const isLaneNode = useCallback(
1174
+ (n) => getNodeKind(n.data?.kind ?? n.type)?.category === "layout",
1175
+ []
1176
+ );
1177
+ const addLane = useCallback(
1178
+ (orientation = "horizontal", title) => {
1179
+ const vertical = orientation === "vertical";
1180
+ const lanes = flow.nodes.filter(isLaneNode);
1181
+ const w = vertical ? 280 : 680;
1182
+ const h = vertical ? 480 : 168;
1183
+ const end = lanes.reduce(
1184
+ (m, l) => Math.max(m, vertical ? l.position.x + (l.width ?? w) : l.position.y + (l.height ?? h)),
1185
+ 0
1186
+ );
1187
+ const id = newNodeId();
1188
+ const name = title ?? `Lane ${lanes.length + 1}`;
1189
+ const node = {
1190
+ id,
1191
+ type: "@particle-academy/lane",
1192
+ position: vertical ? { x: lanes.length ? end + 12 : 0, y: 0 } : { x: 0, y: lanes.length ? end + 12 : 0 },
1193
+ width: w,
1194
+ height: h,
1195
+ data: { kind: "@particle-academy/lane", label: name, config: { title: name, orientation } }
1196
+ };
1197
+ flow.setNodes((all) => [...all, node]);
1198
+ setSelectedId(id);
1199
+ return id;
1200
+ },
1201
+ [flow, isLaneNode]
1202
+ );
1203
+ const handleNodeDragStop = useCallback(
1204
+ (_e, node) => {
1205
+ if (isLaneNode(node)) return;
1206
+ const overLane = rf.getIntersectingNodes(node).find((n) => isLaneNode(n));
1207
+ const currentParent = node.parentId;
1208
+ if (overLane && overLane.id !== currentParent) {
1209
+ flow.setNodes((all) => assignToLane(all, node.id, overLane.id));
1210
+ } else if (!overLane && currentParent) {
1211
+ flow.setNodes((all) => removeFromLane(all, node.id));
1212
+ }
1213
+ },
1214
+ [rf, flow, isLaneNode]
1215
+ );
1131
1216
  useEffect(() => {
1132
1217
  const onKey = (e) => {
1133
1218
  const t = e.target;
@@ -1191,6 +1276,21 @@ function FlowEditorInner({
1191
1276
  deleteNodes(selectedIds);
1192
1277
  },
1193
1278
  paste: pasteClipboard,
1279
+ addLane,
1280
+ assignToLane: (nodeId, laneId) => flow.setNodes((all) => assignToLane(all, nodeId, laneId)),
1281
+ removeFromLane: (nodeId) => flow.setNodes((all) => removeFromLane(all, nodeId)),
1282
+ // dagre is loaded lazily so it stays out of the eager bundle — consumers
1283
+ // who never tidy pay nothing for it.
1284
+ autoLayout: (opts) => {
1285
+ void import('./layout.js').then(
1286
+ ({ autoLayout }) => flow.setGraph({ nodes: autoLayout({ nodes: flow.nodes, edges: flow.edges }, opts), edges: flow.edges })
1287
+ );
1288
+ },
1289
+ tidyLane: (laneId) => {
1290
+ void import('./layout.js').then(
1291
+ ({ autoLayout }) => flow.setGraph({ nodes: autoLayout({ nodes: flow.nodes, edges: flow.edges }, { scope: laneId }), edges: flow.edges })
1292
+ );
1293
+ },
1194
1294
  run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, executors),
1195
1295
  cancel: runner.cancel,
1196
1296
  reset: runner.reset,
@@ -1203,7 +1303,7 @@ function FlowEditorInner({
1203
1303
  canUndo: hist.canUndo,
1204
1304
  canRedo: hist.canRedo
1205
1305
  };
1206
- }, [flow, selectedId, selected, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, executors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, hist, rf]);
1306
+ }, [flow, selectedId, selected, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, executors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, addLane, hist, rf]);
1207
1307
  useImperativeHandle(apiRef, () => api, [api]);
1208
1308
  const dropHandlers = paletteDropHandlers((kindName, evt) => {
1209
1309
  const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
@@ -1239,6 +1339,11 @@ function FlowEditorInner({
1239
1339
  }
1240
1340
  )
1241
1341
  ] }),
1342
+ builtins.addLane !== false && /* @__PURE__ */ jsxs(Fragment, { children: [
1343
+ /* @__PURE__ */ jsx("span", { className: "ff-editor__sep" }),
1344
+ /* @__PURE__ */ jsx("button", { className: "ff-editor__btn", "data-action": "add-lane", title: "Add a swimlane", onClick: () => api.addLane(), children: "\u25A4 Lane" })
1345
+ ] }),
1346
+ builtins.autoLayout !== false && /* @__PURE__ */ jsx("button", { className: "ff-editor__btn", "data-action": "auto-layout", title: "Tidy \u2014 auto-arrange", onClick: () => api.autoLayout(), children: "\u2922 Tidy" }),
1242
1347
  (builtins.export !== false || builtins.import !== false) && /* @__PURE__ */ jsx("span", { className: "ff-editor__sep" }),
1243
1348
  builtins.export !== false && /* @__PURE__ */ jsx("button", { className: "ff-editor__btn", "data-action": "export", onClick: api.exportWorkflow, children: "\u2193 Export" }),
1244
1349
  builtins.import !== false && /* @__PURE__ */ jsx("button", { className: "ff-editor__btn", "data-action": "import", onClick: api.importWorkflow, children: "\u2191 Import" }),
@@ -1266,6 +1371,7 @@ function FlowEditorInner({
1266
1371
  onReconnect,
1267
1372
  edgesReconnectable: true,
1268
1373
  onNodeDragStart: hist.onNodeDragStart,
1374
+ onNodeDragStop: handleNodeDragStop,
1269
1375
  onNodeClick: handleNodeClick,
1270
1376
  onNodeContextMenu: builtins.contextMenu === false ? void 0 : handleNodeContextMenu,
1271
1377
  onEdgeClick: handleEdgeClick,