@particle-academy/fancy-flow 0.39.0 → 0.40.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
@@ -347,47 +347,23 @@ declare function paletteDropHandlers(onDrop: (kindName: string, evt: React.DragE
347
347
  onDrop: (e: React.DragEvent) => void;
348
348
  };
349
349
 
350
- type NodeConfigPanelProps = {
351
- /** Currently-selected node — pass null to render the empty state. */
352
- node: FlowNode | null;
353
- /** Called when the user edits the node label, description, or config. */
354
- onChange: (next: FlowNode) => void;
355
- /**
356
- * Called when the user deletes the node from the panel. When provided, the
357
- * panel renders a "Delete node" button while a node is selected — so the
358
- * delete affordance lives WITH the panel (a dev composing their own editor
359
- * gets it for free), rather than in a host toolbar it has to re-implement.
360
- */
361
- onDelete?: (node: FlowNode) => void;
362
- /** Label for the delete button. Default "Delete node". */
363
- deleteLabel?: string;
364
- /** Optional header content (e.g. close button). */
365
- header?: ReactNode;
366
- /** Optional credential picker hook — host renders the picker. */
367
- renderCredentialField?: (props: {
368
- credentialType: string;
369
- value: unknown;
370
- onChange: (next: unknown) => void;
371
- }) => ReactNode;
372
- /**
373
- * Optional document editor hook — host renders the editor for `document`
374
- * fields. Lets rich authored content live in node config without fancy-flow
375
- * taking on a document model.
376
- */
377
- renderDocumentField?: (props: {
378
- documentType?: string;
379
- value: unknown;
380
- onChange: (next: unknown) => void;
381
- }) => ReactNode;
382
- className?: string;
383
- style?: React.CSSProperties;
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;
384
357
  };
385
358
  /**
386
- * NodeConfigPanel schema-driven form for the selected node. Defers to
387
- * `kind.renderPanel` if the kind opts out of the auto-form.
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.
388
365
  */
389
- declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, className, style, }: NodeConfigPanelProps): react.JSX.Element;
390
-
366
+ type ConfigFieldRenderFn = (ctx: ConfigFieldRenderContext) => ReactNode;
391
367
  type ConfigFieldRendererProps = {
392
368
  field: ConfigField;
393
369
  value: unknown;
@@ -420,6 +396,22 @@ type ConfigFieldRendererProps = {
420
396
  value: unknown;
421
397
  onChange: (next: unknown) => void;
422
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>;
423
415
  };
424
416
  /**
425
417
  * ConfigFieldRenderer — dispatches to the right input element per field type.
@@ -432,7 +424,50 @@ type ConfigFieldRendererProps = {
432
424
  * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
433
425
  * the field, so a label can point at it and an agent can find it by name.
434
426
  */
435
- declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, }: ConfigFieldRendererProps): react.JSX.Element | null;
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;
436
471
 
437
472
  /** Entry-point node — outputs only, no inputs. */
438
473
  declare function TriggerNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
@@ -648,4 +683,4 @@ declare const flowKeys: {
648
683
  readonly run: (runId: string) => readonly ["flow", "runs", string];
649
684
  };
650
685
 
651
- 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, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
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 };
package/dist/index.d.ts CHANGED
@@ -347,47 +347,23 @@ declare function paletteDropHandlers(onDrop: (kindName: string, evt: React.DragE
347
347
  onDrop: (e: React.DragEvent) => void;
348
348
  };
349
349
 
350
- type NodeConfigPanelProps = {
351
- /** Currently-selected node — pass null to render the empty state. */
352
- node: FlowNode | null;
353
- /** Called when the user edits the node label, description, or config. */
354
- onChange: (next: FlowNode) => void;
355
- /**
356
- * Called when the user deletes the node from the panel. When provided, the
357
- * panel renders a "Delete node" button while a node is selected — so the
358
- * delete affordance lives WITH the panel (a dev composing their own editor
359
- * gets it for free), rather than in a host toolbar it has to re-implement.
360
- */
361
- onDelete?: (node: FlowNode) => void;
362
- /** Label for the delete button. Default "Delete node". */
363
- deleteLabel?: string;
364
- /** Optional header content (e.g. close button). */
365
- header?: ReactNode;
366
- /** Optional credential picker hook — host renders the picker. */
367
- renderCredentialField?: (props: {
368
- credentialType: string;
369
- value: unknown;
370
- onChange: (next: unknown) => void;
371
- }) => ReactNode;
372
- /**
373
- * Optional document editor hook — host renders the editor for `document`
374
- * fields. Lets rich authored content live in node config without fancy-flow
375
- * taking on a document model.
376
- */
377
- renderDocumentField?: (props: {
378
- documentType?: string;
379
- value: unknown;
380
- onChange: (next: unknown) => void;
381
- }) => ReactNode;
382
- className?: string;
383
- style?: React.CSSProperties;
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;
384
357
  };
385
358
  /**
386
- * NodeConfigPanel schema-driven form for the selected node. Defers to
387
- * `kind.renderPanel` if the kind opts out of the auto-form.
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.
388
365
  */
389
- declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, className, style, }: NodeConfigPanelProps): react.JSX.Element;
390
-
366
+ type ConfigFieldRenderFn = (ctx: ConfigFieldRenderContext) => ReactNode;
391
367
  type ConfigFieldRendererProps = {
392
368
  field: ConfigField;
393
369
  value: unknown;
@@ -420,6 +396,22 @@ type ConfigFieldRendererProps = {
420
396
  value: unknown;
421
397
  onChange: (next: unknown) => void;
422
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>;
423
415
  };
424
416
  /**
425
417
  * ConfigFieldRenderer — dispatches to the right input element per field type.
@@ -432,7 +424,50 @@ type ConfigFieldRendererProps = {
432
424
  * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
433
425
  * the field, so a label can point at it and an agent can find it by name.
434
426
  */
435
- declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, }: ConfigFieldRendererProps): react.JSX.Element | null;
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;
436
471
 
437
472
  /** Entry-point node — outputs only, no inputs. */
438
473
  declare function TriggerNodeInner(props: NodeProps<FlowNode>): react.JSX.Element;
@@ -648,4 +683,4 @@ declare const flowKeys: {
648
683
  readonly run: (runId: string) => readonly ["flow", "runs", string];
649
684
  };
650
685
 
651
- 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, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
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 };
package/dist/index.js CHANGED
@@ -115,9 +115,15 @@ function ConfigFieldRenderer({
115
115
  onChange,
116
116
  id,
117
117
  renderCredentialField,
118
- renderDocumentField
118
+ renderDocumentField,
119
+ fieldRenderers
119
120
  }) {
120
121
  const handle = { id, "data-ff-field": field.key };
122
+ const custom = fieldRenderers?.[field.type];
123
+ if (custom) {
124
+ const rendered = custom({ field, value, onChange, id });
125
+ if (rendered !== null && rendered !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: rendered });
126
+ }
121
127
  switch (field.type) {
122
128
  case "text": {
123
129
  if (field.choices?.length) {
@@ -225,7 +231,8 @@ function ConfigFieldRenderer({
225
231
  value,
226
232
  onChange,
227
233
  renderCredentialField,
228
- renderDocumentField
234
+ renderDocumentField,
235
+ fieldRenderers
229
236
  }
230
237
  );
231
238
  case "keyvalue":
@@ -278,7 +285,8 @@ function RepeaterField({
278
285
  value,
279
286
  onChange,
280
287
  renderCredentialField,
281
- renderDocumentField
288
+ renderDocumentField,
289
+ fieldRenderers
282
290
  }) {
283
291
  const rows = Array.isArray(value) ? value : [];
284
292
  const max = field.maxItems ?? Infinity;
@@ -360,7 +368,8 @@ function RepeaterField({
360
368
  value: row[sub.key],
361
369
  onChange: (cell) => setCell(i, sub.key, cell),
362
370
  renderCredentialField,
363
- renderDocumentField
371
+ renderDocumentField,
372
+ fieldRenderers
364
373
  }
365
374
  )
366
375
  ] }, sub.key))
@@ -492,6 +501,7 @@ function NodeConfigPanel({
492
501
  header,
493
502
  renderCredentialField,
494
503
  renderDocumentField,
504
+ fieldRenderers,
495
505
  className,
496
506
  style
497
507
  }) {
@@ -596,7 +606,8 @@ function NodeConfigPanel({
596
606
  value: config[field.key],
597
607
  onChange: (v) => setConfigValue(field.key, v),
598
608
  renderCredentialField,
599
- renderDocumentField: documentField
609
+ renderDocumentField: documentField,
610
+ fieldRenderers
600
611
  }
601
612
  )
602
613
  ] }, field.key))