@particle-academy/fancy-flow 0.40.3 → 0.41.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.
package/dist/index.d.cts CHANGED
@@ -1,21 +1,23 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, CSSProperties, ComponentType } from 'react';
2
+ import { ReactNode, CSSProperties, ComponentProps, 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, createConnectionValidator, defaultPortCompatibility, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerBuiltinKinds, registerRichInputAdapter, resolveNodePorts, resolvePortSpec } from './registry/index.cjs';
6
6
  import { b as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-sOmpCitB.cjs';
7
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';
8
+ import { C as ConfigFieldRenderFn } from './ConfigFieldRenderer-4xK9hzvd.cjs';
9
+ export { a as ConfigFieldRenderContext, b as ConfigFieldRenderer, c as ConfigFieldRendererProps } from './ConfigFieldRenderer-4xK9hzvd.cjs';
8
10
  import { WorkflowSchema, WorkflowMetadata } from './schema/index.cjs';
9
11
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.cjs';
10
12
  import { AutoLayoutOptions } from './layout/index.cjs';
11
13
  export { AutoLayoutDirection } from './layout/index.cjs';
12
- import { c as NodeCategory, d as NodeKindDefinition, a as ConfigField } from './types-D8Dezh9v.cjs';
13
- export { D as DocumentConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from './types-D8Dezh9v.cjs';
14
+ import { c as NodeCategory, d as NodeKindDefinition } from './types-cAdD1Og2.cjs';
15
+ export { a as ConfigField, D as DocumentConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from './types-cAdD1Og2.cjs';
14
16
  export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-nLqo7s2E.cjs';
15
17
  import { FlowRunFeedEntry } from './runtime/index.cjs';
16
18
  export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
17
19
  export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, R as RunOptions, d as RunResult, r as runCohort, e as runFlow } from './run-cohort-Bjv2ZU6g.cjs';
18
- export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-BADFn97n.cjs';
20
+ export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-BNyT66SS.cjs';
19
21
  import './capabilities-BtJQTbnw.cjs';
20
22
 
21
23
  type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" | "height"> & {
@@ -67,6 +69,49 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
67
69
  */
68
70
  declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, colorMode, showHelperLines, zoomOnWheel, onNodesChange, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
69
71
 
72
+ type NodeConfigPanelProps = {
73
+ /** Currently-selected node — pass null to render the empty state. */
74
+ node: FlowNode | null;
75
+ /** Called when the user edits the node label, description, or config. */
76
+ onChange: (next: FlowNode) => void;
77
+ /**
78
+ * Called when the user deletes the node from the panel. When provided, the
79
+ * panel renders a "Delete node" button while a node is selected — so the
80
+ * delete affordance lives WITH the panel (a dev composing their own editor
81
+ * gets it for free), rather than in a host toolbar it has to re-implement.
82
+ */
83
+ onDelete?: (node: FlowNode) => void;
84
+ /** Label for the delete button. Default "Delete node". */
85
+ deleteLabel?: string;
86
+ /** Optional header content (e.g. close button). */
87
+ header?: ReactNode;
88
+ /** Optional credential picker hook — host renders the picker. */
89
+ renderCredentialField?: (props: {
90
+ credentialType: string;
91
+ value: unknown;
92
+ onChange: (next: unknown) => void;
93
+ }) => ReactNode;
94
+ /**
95
+ * Optional document editor hook — host renders the editor for `document`
96
+ * fields. Lets rich authored content live in node config without fancy-flow
97
+ * taking on a document model.
98
+ */
99
+ /** Host renderers keyed by field `type`. See {@link ConfigFieldRenderer}. */
100
+ fieldRenderers?: Record<string, ConfigFieldRenderFn>;
101
+ renderDocumentField?: (props: {
102
+ documentType?: string;
103
+ value: unknown;
104
+ onChange: (next: unknown) => void;
105
+ }) => ReactNode;
106
+ className?: string;
107
+ style?: React.CSSProperties;
108
+ };
109
+ /**
110
+ * NodeConfigPanel — schema-driven form for the selected node. Defers to
111
+ * `kind.renderPanel` if the kind opts out of the auto-form.
112
+ */
113
+ declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, className, style, }: NodeConfigPanelProps): react.JSX.Element;
114
+
70
115
  /**
71
116
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
72
117
  * This is the piece `duplicateNode` lacks: a copy/paste (or duplicate-selection)
@@ -273,6 +318,20 @@ type FlowEditorProps = {
273
318
  showPalette?: boolean;
274
319
  /** Show the config panel sidebar. Default true. */
275
320
  showPanel?: boolean;
321
+ /**
322
+ * Host renderers for config-field types, keyed by `type`, handed straight to
323
+ * `NodeConfigPanel`.
324
+ *
325
+ * Use it to claim a type the package renders plainly — the built-in `json`
326
+ * field is a bare textarea, and
327
+ * `@particle-academy/fancy-flow/fields/react-fancy` swaps it for a real typed
328
+ * editor — or to supply one the package has never heard of.
329
+ *
330
+ * This forwarding is the whole point: the seam existed on `NodeConfigPanel`
331
+ * and had no route through `FlowEditor`, so reaching it meant abandoning the
332
+ * editor and composing one by hand.
333
+ */
334
+ fieldRenderers?: ComponentProps<typeof NodeConfigPanel>["fieldRenderers"];
276
335
  /** Show run feed below the canvas. Default true. */
277
336
  showFeed?: boolean;
278
337
  /** Total editor height. Default 720. */
@@ -347,128 +406,6 @@ declare function paletteDropHandlers(onDrop: (kindName: string, evt: React.DragE
347
406
  onDrop: (e: React.DragEvent) => void;
348
407
  };
349
408
 
350
- /** What a host renderer is handed. */
351
- type ConfigFieldRenderContext = {
352
- field: ConfigField;
353
- value: unknown;
354
- onChange: (next: unknown) => void;
355
- /** The id the panel's `<label>` points at. Put it on your control. */
356
- id?: string;
357
- };
358
- /**
359
- * Render one field. Return `null` to fall back to the package's own rendering,
360
- * so a host can claim a type conditionally instead of reimplementing every case.
361
- *
362
- * Named `...Fn` because `ConfigFieldRenderer` is already the component this
363
- * module exports; two things with one name in a public API is a paper cut a
364
- * consumer pays for, not us.
365
- */
366
- type ConfigFieldRenderFn = (ctx: ConfigFieldRenderContext) => ReactNode;
367
- type ConfigFieldRendererProps = {
368
- field: ConfigField;
369
- value: unknown;
370
- onChange: (value: unknown) => void;
371
- /**
372
- * DOM id for the control this renders.
373
- *
374
- * Supplied by the caller rather than generated here so the panel's `<label>`
375
- * can point at it — a label beside a control is not a label attached to one.
376
- * Also the agent-facing handle: the Human+ contract asks that every
377
- * interactive element have a stable identity so an agent targets it instead
378
- * of guessing at the DOM, and until this existed nothing in the editor had
379
- * one.
380
- */
381
- id?: string;
382
- renderCredentialField?: (props: {
383
- credentialType: string;
384
- value: unknown;
385
- onChange: (next: unknown) => void;
386
- }) => ReactNode;
387
- /**
388
- * Editor for `document` fields. fancy-flow stores the document but never
389
- * interprets it — the host owns the editing surface, exactly as it does for
390
- * `credential`. This is the seam for rich human-input steps (authored pages,
391
- * required reading, multi-section forms) without the package depending on
392
- * any particular document model.
393
- */
394
- renderDocumentField?: (props: {
395
- documentType?: string;
396
- value: unknown;
397
- onChange: (next: unknown) => void;
398
- }) => ReactNode;
399
- /**
400
- * Host renderers keyed by field `type`.
401
- *
402
- * The generic form of `renderDocumentField` / `renderCredentialField`: those
403
- * cover two types the package deliberately does not interpret, this covers
404
- * any type at all — including one the package has never heard of.
405
- *
406
- * Without it, a richer field had to be rendered OUTSIDE the panel, so that
407
- * node's config stopped living where every other field does. An unknown type
408
- * also fell through to `default:` and rendered nothing, so the schema said the
409
- * field existed and the panel showed empty space.
410
- *
411
- * Consulted BEFORE the built-in switch, so a host can also replace a built-in
412
- * (react-fancy inputs, say) through the same seam rather than a second one.
413
- */
414
- fieldRenderers?: Record<string, ConfigFieldRenderFn>;
415
- };
416
- /**
417
- * ConfigFieldRenderer — dispatches to the right input element per field type.
418
- *
419
- * Plain HTML inputs styled via the package's CSS so the package stays
420
- * standalone (no react-fancy import required) and every surface stays themeable
421
- * through the `--ff-*` token layer. Hosts that want react-fancy form components
422
- * can supply their own field renderers via the kind's `renderPanel`.
423
- *
424
- * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
425
- * the field, so a label can point at it and an agent can find it by name.
426
- */
427
- declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, fieldRenderers, }: ConfigFieldRendererProps): react.JSX.Element | null;
428
-
429
- type NodeConfigPanelProps = {
430
- /** Currently-selected node — pass null to render the empty state. */
431
- node: FlowNode | null;
432
- /** Called when the user edits the node label, description, or config. */
433
- onChange: (next: FlowNode) => void;
434
- /**
435
- * Called when the user deletes the node from the panel. When provided, the
436
- * panel renders a "Delete node" button while a node is selected — so the
437
- * delete affordance lives WITH the panel (a dev composing their own editor
438
- * gets it for free), rather than in a host toolbar it has to re-implement.
439
- */
440
- onDelete?: (node: FlowNode) => void;
441
- /** Label for the delete button. Default "Delete node". */
442
- deleteLabel?: string;
443
- /** Optional header content (e.g. close button). */
444
- header?: ReactNode;
445
- /** Optional credential picker hook — host renders the picker. */
446
- renderCredentialField?: (props: {
447
- credentialType: string;
448
- value: unknown;
449
- onChange: (next: unknown) => void;
450
- }) => ReactNode;
451
- /**
452
- * Optional document editor hook — host renders the editor for `document`
453
- * fields. Lets rich authored content live in node config without fancy-flow
454
- * taking on a document model.
455
- */
456
- /** Host renderers keyed by field `type`. See {@link ConfigFieldRenderer}. */
457
- fieldRenderers?: Record<string, ConfigFieldRenderFn>;
458
- renderDocumentField?: (props: {
459
- documentType?: string;
460
- value: unknown;
461
- onChange: (next: unknown) => void;
462
- }) => ReactNode;
463
- className?: string;
464
- style?: React.CSSProperties;
465
- };
466
- /**
467
- * NodeConfigPanel — schema-driven form for the selected node. Defers to
468
- * `kind.renderPanel` if the kind opts out of the auto-form.
469
- */
470
- declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, className, style, }: NodeConfigPanelProps): react.JSX.Element;
471
-
472
409
  /** Entry-point node — outputs only, no inputs. */
473
410
  declare function TriggerNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
474
411
  declare const TriggerNode: react.MemoExoticComponent<typeof TriggerNodeInner>;
@@ -683,4 +620,4 @@ declare const flowKeys: {
683
620
  readonly run: (runId: string) => readonly ["flow", "runs", string];
684
621
  };
685
622
 
686
- export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigField, type ConfigFieldRenderContext, type ConfigFieldRenderFn, 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, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
623
+ export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigFieldRenderFn, 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, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,23 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, CSSProperties, ComponentType } from 'react';
2
+ import { ReactNode, CSSProperties, ComponentProps, 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, createConnectionValidator, defaultPortCompatibility, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerBuiltinKinds, registerRichInputAdapter, resolveNodePorts, resolvePortSpec } from './registry/index.js';
6
6
  import { b as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-sOmpCitB.js';
7
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';
8
+ import { C as ConfigFieldRenderFn } from './ConfigFieldRenderer-D2TejNY1.js';
9
+ export { a as ConfigFieldRenderContext, b as ConfigFieldRenderer, c as ConfigFieldRendererProps } from './ConfigFieldRenderer-D2TejNY1.js';
8
10
  import { WorkflowSchema, WorkflowMetadata } from './schema/index.js';
9
11
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.js';
10
12
  import { AutoLayoutOptions } from './layout/index.js';
11
13
  export { AutoLayoutDirection } from './layout/index.js';
12
- import { c as NodeCategory, d as NodeKindDefinition, a as ConfigField } from './types-D6lsIGoe.js';
13
- export { D as DocumentConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from './types-D6lsIGoe.js';
14
+ import { c as NodeCategory, d as NodeKindDefinition } from './types-Cf75IbxY.js';
15
+ export { a as ConfigField, D as DocumentConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from './types-Cf75IbxY.js';
14
16
  export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-C4QAVgWv.js';
15
17
  import { FlowRunFeedEntry } from './runtime/index.js';
16
18
  export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
17
19
  export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, R as RunOptions, d as RunResult, r as runCohort, e as runFlow } from './run-cohort-BnP6ErHi.js';
18
- export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-DYbFLEcW.js';
20
+ export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-CS7kjMto.js';
19
21
  import './capabilities-CkhKj_U_.js';
20
22
 
21
23
  type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" | "height"> & {
@@ -67,6 +69,49 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
67
69
  */
68
70
  declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, colorMode, showHelperLines, zoomOnWheel, onNodesChange, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
69
71
 
72
+ type NodeConfigPanelProps = {
73
+ /** Currently-selected node — pass null to render the empty state. */
74
+ node: FlowNode | null;
75
+ /** Called when the user edits the node label, description, or config. */
76
+ onChange: (next: FlowNode) => void;
77
+ /**
78
+ * Called when the user deletes the node from the panel. When provided, the
79
+ * panel renders a "Delete node" button while a node is selected — so the
80
+ * delete affordance lives WITH the panel (a dev composing their own editor
81
+ * gets it for free), rather than in a host toolbar it has to re-implement.
82
+ */
83
+ onDelete?: (node: FlowNode) => void;
84
+ /** Label for the delete button. Default "Delete node". */
85
+ deleteLabel?: string;
86
+ /** Optional header content (e.g. close button). */
87
+ header?: ReactNode;
88
+ /** Optional credential picker hook — host renders the picker. */
89
+ renderCredentialField?: (props: {
90
+ credentialType: string;
91
+ value: unknown;
92
+ onChange: (next: unknown) => void;
93
+ }) => ReactNode;
94
+ /**
95
+ * Optional document editor hook — host renders the editor for `document`
96
+ * fields. Lets rich authored content live in node config without fancy-flow
97
+ * taking on a document model.
98
+ */
99
+ /** Host renderers keyed by field `type`. See {@link ConfigFieldRenderer}. */
100
+ fieldRenderers?: Record<string, ConfigFieldRenderFn>;
101
+ renderDocumentField?: (props: {
102
+ documentType?: string;
103
+ value: unknown;
104
+ onChange: (next: unknown) => void;
105
+ }) => ReactNode;
106
+ className?: string;
107
+ style?: React.CSSProperties;
108
+ };
109
+ /**
110
+ * NodeConfigPanel — schema-driven form for the selected node. Defers to
111
+ * `kind.renderPanel` if the kind opts out of the auto-form.
112
+ */
113
+ declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, className, style, }: NodeConfigPanelProps): react.JSX.Element;
114
+
70
115
  /**
71
116
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
72
117
  * This is the piece `duplicateNode` lacks: a copy/paste (or duplicate-selection)
@@ -273,6 +318,20 @@ type FlowEditorProps = {
273
318
  showPalette?: boolean;
274
319
  /** Show the config panel sidebar. Default true. */
275
320
  showPanel?: boolean;
321
+ /**
322
+ * Host renderers for config-field types, keyed by `type`, handed straight to
323
+ * `NodeConfigPanel`.
324
+ *
325
+ * Use it to claim a type the package renders plainly — the built-in `json`
326
+ * field is a bare textarea, and
327
+ * `@particle-academy/fancy-flow/fields/react-fancy` swaps it for a real typed
328
+ * editor — or to supply one the package has never heard of.
329
+ *
330
+ * This forwarding is the whole point: the seam existed on `NodeConfigPanel`
331
+ * and had no route through `FlowEditor`, so reaching it meant abandoning the
332
+ * editor and composing one by hand.
333
+ */
334
+ fieldRenderers?: ComponentProps<typeof NodeConfigPanel>["fieldRenderers"];
276
335
  /** Show run feed below the canvas. Default true. */
277
336
  showFeed?: boolean;
278
337
  /** Total editor height. Default 720. */
@@ -347,128 +406,6 @@ declare function paletteDropHandlers(onDrop: (kindName: string, evt: React.DragE
347
406
  onDrop: (e: React.DragEvent) => void;
348
407
  };
349
408
 
350
- /** What a host renderer is handed. */
351
- type ConfigFieldRenderContext = {
352
- field: ConfigField;
353
- value: unknown;
354
- onChange: (next: unknown) => void;
355
- /** The id the panel's `<label>` points at. Put it on your control. */
356
- id?: string;
357
- };
358
- /**
359
- * Render one field. Return `null` to fall back to the package's own rendering,
360
- * so a host can claim a type conditionally instead of reimplementing every case.
361
- *
362
- * Named `...Fn` because `ConfigFieldRenderer` is already the component this
363
- * module exports; two things with one name in a public API is a paper cut a
364
- * consumer pays for, not us.
365
- */
366
- type ConfigFieldRenderFn = (ctx: ConfigFieldRenderContext) => ReactNode;
367
- type ConfigFieldRendererProps = {
368
- field: ConfigField;
369
- value: unknown;
370
- onChange: (value: unknown) => void;
371
- /**
372
- * DOM id for the control this renders.
373
- *
374
- * Supplied by the caller rather than generated here so the panel's `<label>`
375
- * can point at it — a label beside a control is not a label attached to one.
376
- * Also the agent-facing handle: the Human+ contract asks that every
377
- * interactive element have a stable identity so an agent targets it instead
378
- * of guessing at the DOM, and until this existed nothing in the editor had
379
- * one.
380
- */
381
- id?: string;
382
- renderCredentialField?: (props: {
383
- credentialType: string;
384
- value: unknown;
385
- onChange: (next: unknown) => void;
386
- }) => ReactNode;
387
- /**
388
- * Editor for `document` fields. fancy-flow stores the document but never
389
- * interprets it — the host owns the editing surface, exactly as it does for
390
- * `credential`. This is the seam for rich human-input steps (authored pages,
391
- * required reading, multi-section forms) without the package depending on
392
- * any particular document model.
393
- */
394
- renderDocumentField?: (props: {
395
- documentType?: string;
396
- value: unknown;
397
- onChange: (next: unknown) => void;
398
- }) => ReactNode;
399
- /**
400
- * Host renderers keyed by field `type`.
401
- *
402
- * The generic form of `renderDocumentField` / `renderCredentialField`: those
403
- * cover two types the package deliberately does not interpret, this covers
404
- * any type at all — including one the package has never heard of.
405
- *
406
- * Without it, a richer field had to be rendered OUTSIDE the panel, so that
407
- * node's config stopped living where every other field does. An unknown type
408
- * also fell through to `default:` and rendered nothing, so the schema said the
409
- * field existed and the panel showed empty space.
410
- *
411
- * Consulted BEFORE the built-in switch, so a host can also replace a built-in
412
- * (react-fancy inputs, say) through the same seam rather than a second one.
413
- */
414
- fieldRenderers?: Record<string, ConfigFieldRenderFn>;
415
- };
416
- /**
417
- * ConfigFieldRenderer — dispatches to the right input element per field type.
418
- *
419
- * Plain HTML inputs styled via the package's CSS so the package stays
420
- * standalone (no react-fancy import required) and every surface stays themeable
421
- * through the `--ff-*` token layer. Hosts that want react-fancy form components
422
- * can supply their own field renderers via the kind's `renderPanel`.
423
- *
424
- * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
425
- * the field, so a label can point at it and an agent can find it by name.
426
- */
427
- declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, fieldRenderers, }: ConfigFieldRendererProps): react.JSX.Element | null;
428
-
429
- type NodeConfigPanelProps = {
430
- /** Currently-selected node — pass null to render the empty state. */
431
- node: FlowNode | null;
432
- /** Called when the user edits the node label, description, or config. */
433
- onChange: (next: FlowNode) => void;
434
- /**
435
- * Called when the user deletes the node from the panel. When provided, the
436
- * panel renders a "Delete node" button while a node is selected — so the
437
- * delete affordance lives WITH the panel (a dev composing their own editor
438
- * gets it for free), rather than in a host toolbar it has to re-implement.
439
- */
440
- onDelete?: (node: FlowNode) => void;
441
- /** Label for the delete button. Default "Delete node". */
442
- deleteLabel?: string;
443
- /** Optional header content (e.g. close button). */
444
- header?: ReactNode;
445
- /** Optional credential picker hook — host renders the picker. */
446
- renderCredentialField?: (props: {
447
- credentialType: string;
448
- value: unknown;
449
- onChange: (next: unknown) => void;
450
- }) => ReactNode;
451
- /**
452
- * Optional document editor hook — host renders the editor for `document`
453
- * fields. Lets rich authored content live in node config without fancy-flow
454
- * taking on a document model.
455
- */
456
- /** Host renderers keyed by field `type`. See {@link ConfigFieldRenderer}. */
457
- fieldRenderers?: Record<string, ConfigFieldRenderFn>;
458
- renderDocumentField?: (props: {
459
- documentType?: string;
460
- value: unknown;
461
- onChange: (next: unknown) => void;
462
- }) => ReactNode;
463
- className?: string;
464
- style?: React.CSSProperties;
465
- };
466
- /**
467
- * NodeConfigPanel — schema-driven form for the selected node. Defers to
468
- * `kind.renderPanel` if the kind opts out of the auto-form.
469
- */
470
- declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, className, style, }: NodeConfigPanelProps): react.JSX.Element;
471
-
472
409
  /** Entry-point node — outputs only, no inputs. */
473
410
  declare function TriggerNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
474
411
  declare const TriggerNode: react.MemoExoticComponent<typeof TriggerNodeInner>;
@@ -683,4 +620,4 @@ declare const flowKeys: {
683
620
  readonly run: (runId: string) => readonly ["flow", "runs", string];
684
621
  };
685
622
 
686
- export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigField, type ConfigFieldRenderContext, type ConfigFieldRenderFn, 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, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
623
+ export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigFieldRenderFn, 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, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { removeNodes, removeEdges, cloneSubgraph, alignNodes, distributeNodes, reconnectEdge, assignToLane, removeFromLane, duplicateNode, setEdgeLabel, FlowCanvas } from './chunk-5UCWY7G3.js';
2
- export { ActionNode, DecisionNode, FlowCanvas, FlowViewer, NodeShell, OutputNode, SubgraphNode, TriggerNode, alignNodes, cloneSubgraph, defaultNodeTypes, distributeNodes, reconnectEdge } from './chunk-5UCWY7G3.js';
1
+ import { removeNodes, removeEdges, cloneSubgraph, alignNodes, distributeNodes, reconnectEdge, assignToLane, removeFromLane, duplicateNode, setEdgeLabel, FlowCanvas } from './chunk-WOO4B7M5.js';
2
+ export { ActionNode, DecisionNode, FlowCanvas, FlowViewer, NodeShell, OutputNode, SubgraphNode, TriggerNode, alignNodes, cloneSubgraph, defaultNodeTypes, distributeNodes, reconnectEdge } from './chunk-WOO4B7M5.js';
3
3
  import { useFlowState, useFlowRun, useFlowHistory, applyOutputsToNodes, applyStatusesToNodes } from './chunk-A6RFLGWV.js';
4
4
  export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-A6RFLGWV.js';
5
5
  export { runCohort } from './chunk-QZTGV3ZL.js';
@@ -195,7 +195,7 @@ function ConfigFieldRenderer({
195
195
  }
196
196
  );
197
197
  case "json":
198
- return /* @__PURE__ */ jsx(JsonField, { value, onChange, rows: field.rows });
198
+ return /* @__PURE__ */ jsx(JsonField, { value, onChange, rows: field.rows, handle });
199
199
  case "expression":
200
200
  return /* @__PURE__ */ jsx(
201
201
  "textarea",
@@ -227,6 +227,7 @@ function ConfigFieldRenderer({
227
227
  return /* @__PURE__ */ jsx(
228
228
  RepeaterField,
229
229
  {
230
+ id,
230
231
  field,
231
232
  value,
232
233
  onChange,
@@ -236,7 +237,7 @@ function ConfigFieldRenderer({
236
237
  }
237
238
  );
238
239
  case "keyvalue":
239
- return /* @__PURE__ */ jsx(KeyValueField, { field, value, onChange });
240
+ return /* @__PURE__ */ jsx(KeyValueField, { id, field, value, onChange });
240
241
  case "document":
241
242
  if (renderDocumentField) {
242
243
  return /* @__PURE__ */ jsx(Fragment, { children: renderDocumentField({ documentType: field.documentType, value, onChange }) });
@@ -281,6 +282,7 @@ function ChoiceField({
281
282
  );
282
283
  }
283
284
  function RepeaterField({
285
+ id,
284
286
  field,
285
287
  value,
286
288
  onChange,
@@ -315,7 +317,7 @@ function RepeaterField({
315
317
  if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
316
318
  return `Item ${i + 1}`;
317
319
  };
318
- return /* @__PURE__ */ jsxs("div", { className: "ff-repeater", "data-ff-repeater": field.key, children: [
320
+ return /* @__PURE__ */ jsxs("div", { id, role: "group", className: "ff-repeater", "data-ff-repeater": field.key, "data-ff-field": field.key, children: [
319
321
  rows.length === 0 && /* @__PURE__ */ jsx("p", { className: "ff-repeater__empty", children: "None yet." }),
320
322
  rows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: "ff-repeater__row", "data-ff-repeater-row": i, children: [
321
323
  /* @__PURE__ */ jsxs("div", { className: "ff-repeater__row-head", children: [
@@ -390,6 +392,7 @@ function RepeaterField({
390
392
  ] });
391
393
  }
392
394
  function KeyValueField({
395
+ id,
393
396
  field,
394
397
  value,
395
398
  onChange
@@ -408,7 +411,7 @@ function KeyValueField({
408
411
  const setVal = (i, val) => commit(entries.map(([k, v], idx) => idx === i ? [k, val] : [k, v]));
409
412
  const remove = (i) => commit(entries.filter((_, idx) => idx !== i));
410
413
  const add = () => commit([...entries, ["", ""]]);
411
- return /* @__PURE__ */ jsxs("div", { className: "ff-keyvalue", "data-ff-keyvalue": field.key, children: [
414
+ return /* @__PURE__ */ jsxs("div", { id, role: "group", className: "ff-keyvalue", "data-ff-keyvalue": field.key, "data-ff-field": field.key, children: [
412
415
  entries.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ff-keyvalue__head", children: [
413
416
  /* @__PURE__ */ jsx("span", { children: field.keyLabel ?? "Key" }),
414
417
  /* @__PURE__ */ jsx("span", { children: field.valueLabel ?? "Value" }),
@@ -464,34 +467,76 @@ function KeyValueField({
464
467
  ] })
465
468
  ] });
466
469
  }
467
- function JsonField({ value, onChange, rows }) {
468
- const initial = useMemo(() => {
470
+ function JsonField({
471
+ value,
472
+ onChange,
473
+ rows,
474
+ handle
475
+ }) {
476
+ const serialized = useMemo(() => {
469
477
  try {
470
478
  return value === void 0 ? "" : JSON.stringify(value, null, 2);
471
479
  } catch {
472
480
  return "";
473
481
  }
474
482
  }, [value]);
475
- return /* @__PURE__ */ jsx(
476
- "textarea",
477
- {
478
- className: "ff-panel__input ff-panel__input--json",
479
- rows: rows ?? 6,
480
- defaultValue: initial,
481
- spellCheck: false,
482
- onBlur: (e) => {
483
- const text = e.target.value.trim();
484
- if (text === "") {
485
- onChange(void 0);
486
- return;
487
- }
488
- try {
489
- onChange(JSON.parse(text));
490
- } catch {
483
+ const [text, setText] = useState(serialized);
484
+ const [error, setError] = useState(null);
485
+ const [touched, setTouched] = useState(false);
486
+ const lastProps = useRef(serialized);
487
+ const committed = useRef(null);
488
+ if (serialized !== lastProps.current) {
489
+ lastProps.current = serialized;
490
+ if (serialized !== committed.current) {
491
+ setText(serialized);
492
+ setError(null);
493
+ setTouched(false);
494
+ }
495
+ }
496
+ const commit = (next) => {
497
+ const trimmed = next.trim();
498
+ if (trimmed === "") {
499
+ committed.current = "";
500
+ onChange(void 0);
501
+ return null;
502
+ }
503
+ try {
504
+ const parsed = JSON.parse(trimmed);
505
+ committed.current = JSON.stringify(parsed, null, 2);
506
+ onChange(parsed);
507
+ return null;
508
+ } catch (e) {
509
+ return e instanceof Error ? e.message : "Invalid JSON";
510
+ }
511
+ };
512
+ const errorId = handle.id ? `${handle.id}-error` : void 0;
513
+ const showError = touched && error !== null;
514
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
515
+ /* @__PURE__ */ jsx(
516
+ "textarea",
517
+ {
518
+ ...handle,
519
+ className: "ff-panel__input ff-panel__input--json",
520
+ rows: rows ?? 6,
521
+ value: text,
522
+ spellCheck: false,
523
+ "aria-invalid": showError || void 0,
524
+ "aria-describedby": showError ? errorId : void 0,
525
+ onChange: (e) => {
526
+ setText(e.target.value);
527
+ setError(commit(e.target.value));
528
+ },
529
+ onBlur: (e) => {
530
+ setTouched(true);
531
+ setError(commit(e.target.value));
491
532
  }
492
533
  }
493
- }
494
- );
534
+ ),
535
+ showError && /* @__PURE__ */ jsxs("p", { className: "ff-panel__issue ff-panel__issue--field", id: errorId, role: "alert", children: [
536
+ "\u26A0 ",
537
+ error
538
+ ] })
539
+ ] });
495
540
  }
496
541
  function NodeConfigPanel({
497
542
  node,
@@ -810,6 +855,7 @@ function FlowEditorInner({
810
855
  metadata,
811
856
  showPalette = true,
812
857
  showPanel = true,
858
+ fieldRenderers,
813
859
  showFeed = true,
814
860
  extraToolbar,
815
861
  actions = [],
@@ -1369,6 +1415,7 @@ function FlowEditorInner({
1369
1415
  className: "ff-editor__panel",
1370
1416
  node: api.selected,
1371
1417
  onChange: api.updateNode,
1418
+ fieldRenderers,
1372
1419
  onDelete: builtins.delete === false ? void 0 : (n) => api.deleteNodes([n.id])
1373
1420
  }
1374
1421
  ),