@particle-academy/fancy-flow 0.53.0 → 0.54.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.
@@ -89,4 +89,78 @@ type ConfigFieldRendererProps = {
89
89
  */
90
90
  declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, fieldRenderers, graph, nodeId, }: ConfigFieldRendererProps): react.JSX.Element | null;
91
91
 
92
- export { type ConfigFieldRenderFn as C, type ConfigFieldRenderContext as a, ConfigFieldRenderer as b, type ConfigFieldRendererProps as c };
92
+ /**
93
+ * The field vocabulary a `user_input` node declares, and the normalizer that
94
+ * turns whatever was actually written into something renderable.
95
+ *
96
+ * ## Why this is a separate module from `HumanPrompt.tsx`
97
+ *
98
+ * `src/registry/builtin.ts` needs `humanFieldType` for the `user_input` kind's
99
+ * `outputShape`, and `builtin.ts` is in the import graph of the `/engine`
100
+ * entry — the one `tests/core-nodes.test.ts` guards as React-free so a queue
101
+ * worker or a CLI can register kinds without dragging React in. Importing the
102
+ * modal's `.tsx` from there put a React module on that path and left the guard
103
+ * standing only because treeshaking happened to drop it.
104
+ *
105
+ * Pure functions over plain data, in a `.ts` file, so the headless path cannot
106
+ * regress on a future edit to the component.
107
+ */
108
+ /**
109
+ * The control a field renders as, after {@link humanInputFields} has resolved
110
+ * whatever the author, a peer runtime or an agent actually wrote.
111
+ *
112
+ * These are the CANONICAL names. The vocabulary an author may write is wider —
113
+ * see {@link HUMAN_FIELD_TYPE_ALIASES} — because a `fields` array arrives from
114
+ * three places (the config panel, a hand-written workflow JSON, and the PHP /
115
+ * Python runtimes) and each has its own habits for spelling "boolean".
116
+ */
117
+ type HumanFieldType = "text" | "textarea" | "number" | "select" | "switch" | "date" | "datetime" | "time" | "email" | "url" | "tel" | "password";
118
+ /** One choice in a `select` field, after normalization. */
119
+ type HumanFieldOption = {
120
+ value: string;
121
+ label: string;
122
+ };
123
+ /** A field the input modal renders. Mirrors a `user_input` `fields` row. */
124
+ type HumanField = {
125
+ key: string;
126
+ label?: string;
127
+ type?: HumanFieldType;
128
+ required?: boolean;
129
+ placeholder?: string;
130
+ options?: HumanFieldOption[];
131
+ default?: unknown;
132
+ };
133
+
134
+ /** What a host renderer is handed for one field. */
135
+ type HumanFieldRenderContext = {
136
+ field: HumanField;
137
+ /** The id the field's `<label>` points at — put it on your control. */
138
+ id: string;
139
+ value: unknown;
140
+ onChange: (v: unknown) => void;
141
+ /** Present only on the first field; attach it so the modal autofocuses. */
142
+ autoFocusRef?: React.RefObject<HTMLElement | null>;
143
+ };
144
+ /**
145
+ * Render one field, or return `null` to decline it.
146
+ *
147
+ * `null` means "not mine" and falls through to the built-in control. That is
148
+ * what makes a PARTIAL map safe to spread: a host handing over someone else's
149
+ * renderers does not silently lose every type that map does not cover.
150
+ */
151
+ type HumanFieldRenderFn = (ctx: HumanFieldRenderContext) => ReactNode | null;
152
+ /**
153
+ * Host overrides for pause-form controls, keyed by CANONICAL field type
154
+ * (`"switch"`, not `"boolean"` — aliases are normalised before the lookup, so
155
+ * one entry covers every spelling of that type).
156
+ *
157
+ * The built-ins are deliberately native `--ff-*`-themed elements rather than
158
+ * react-fancy primitives: react-fancy is an OPTIONAL peer and this modal ships
159
+ * in the main entry, so importing it here would break a standalone install and
160
+ * bypass the token layer a host themes `.ff-editor` with. This seam is how a
161
+ * host that HAS react-fancy gets Fancy controls anyway —
162
+ * `@particle-academy/fancy-flow/fields/react-fancy` exports a ready map.
163
+ */
164
+ type HumanFieldRenderers = Partial<Record<HumanFieldType, HumanFieldRenderFn>>;
165
+
166
+ export { type ConfigFieldRenderFn as C, type HumanFieldRenderers as H, type ConfigFieldRenderContext as a, ConfigFieldRenderer as b, type ConfigFieldRendererProps as c };
@@ -89,4 +89,78 @@ type ConfigFieldRendererProps = {
89
89
  */
90
90
  declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, fieldRenderers, graph, nodeId, }: ConfigFieldRendererProps): react.JSX.Element | null;
91
91
 
92
- export { type ConfigFieldRenderFn as C, type ConfigFieldRenderContext as a, ConfigFieldRenderer as b, type ConfigFieldRendererProps as c };
92
+ /**
93
+ * The field vocabulary a `user_input` node declares, and the normalizer that
94
+ * turns whatever was actually written into something renderable.
95
+ *
96
+ * ## Why this is a separate module from `HumanPrompt.tsx`
97
+ *
98
+ * `src/registry/builtin.ts` needs `humanFieldType` for the `user_input` kind's
99
+ * `outputShape`, and `builtin.ts` is in the import graph of the `/engine`
100
+ * entry — the one `tests/core-nodes.test.ts` guards as React-free so a queue
101
+ * worker or a CLI can register kinds without dragging React in. Importing the
102
+ * modal's `.tsx` from there put a React module on that path and left the guard
103
+ * standing only because treeshaking happened to drop it.
104
+ *
105
+ * Pure functions over plain data, in a `.ts` file, so the headless path cannot
106
+ * regress on a future edit to the component.
107
+ */
108
+ /**
109
+ * The control a field renders as, after {@link humanInputFields} has resolved
110
+ * whatever the author, a peer runtime or an agent actually wrote.
111
+ *
112
+ * These are the CANONICAL names. The vocabulary an author may write is wider —
113
+ * see {@link HUMAN_FIELD_TYPE_ALIASES} — because a `fields` array arrives from
114
+ * three places (the config panel, a hand-written workflow JSON, and the PHP /
115
+ * Python runtimes) and each has its own habits for spelling "boolean".
116
+ */
117
+ type HumanFieldType = "text" | "textarea" | "number" | "select" | "switch" | "date" | "datetime" | "time" | "email" | "url" | "tel" | "password";
118
+ /** One choice in a `select` field, after normalization. */
119
+ type HumanFieldOption = {
120
+ value: string;
121
+ label: string;
122
+ };
123
+ /** A field the input modal renders. Mirrors a `user_input` `fields` row. */
124
+ type HumanField = {
125
+ key: string;
126
+ label?: string;
127
+ type?: HumanFieldType;
128
+ required?: boolean;
129
+ placeholder?: string;
130
+ options?: HumanFieldOption[];
131
+ default?: unknown;
132
+ };
133
+
134
+ /** What a host renderer is handed for one field. */
135
+ type HumanFieldRenderContext = {
136
+ field: HumanField;
137
+ /** The id the field's `<label>` points at — put it on your control. */
138
+ id: string;
139
+ value: unknown;
140
+ onChange: (v: unknown) => void;
141
+ /** Present only on the first field; attach it so the modal autofocuses. */
142
+ autoFocusRef?: React.RefObject<HTMLElement | null>;
143
+ };
144
+ /**
145
+ * Render one field, or return `null` to decline it.
146
+ *
147
+ * `null` means "not mine" and falls through to the built-in control. That is
148
+ * what makes a PARTIAL map safe to spread: a host handing over someone else's
149
+ * renderers does not silently lose every type that map does not cover.
150
+ */
151
+ type HumanFieldRenderFn = (ctx: HumanFieldRenderContext) => ReactNode | null;
152
+ /**
153
+ * Host overrides for pause-form controls, keyed by CANONICAL field type
154
+ * (`"switch"`, not `"boolean"` — aliases are normalised before the lookup, so
155
+ * one entry covers every spelling of that type).
156
+ *
157
+ * The built-ins are deliberately native `--ff-*`-themed elements rather than
158
+ * react-fancy primitives: react-fancy is an OPTIONAL peer and this modal ships
159
+ * in the main entry, so importing it here would break a standalone install and
160
+ * bypass the token layer a host themes `.ff-editor` with. This seam is how a
161
+ * host that HAS react-fancy gets Fancy controls anyway —
162
+ * `@particle-academy/fancy-flow/fields/react-fancy` exports a ready map.
163
+ */
164
+ type HumanFieldRenderers = Partial<Record<HumanFieldType, HumanFieldRenderFn>>;
165
+
166
+ export { type ConfigFieldRenderFn as C, type HumanFieldRenderers as H, type ConfigFieldRenderContext as a, ConfigFieldRenderer as b, type ConfigFieldRendererProps as c };
@@ -33,7 +33,73 @@ var jsonFieldRenderer = ({
33
33
  var reactFancyFieldRenderers = {
34
34
  json: jsonFieldRenderer
35
35
  };
36
+ var humanFieldRenderers = {
37
+ text: ({ field, id, value, onChange, autoFocusRef }) => /* @__PURE__ */ jsxRuntime.jsx(
38
+ reactFancy.Input,
39
+ {
40
+ id,
41
+ "data-ff-field": field.key,
42
+ ref: autoFocusRef,
43
+ placeholder: field.placeholder,
44
+ value: String(value ?? ""),
45
+ onValueChange: onChange
46
+ }
47
+ ),
48
+ textarea: ({ field, id, value, onChange, autoFocusRef }) => /* @__PURE__ */ jsxRuntime.jsx(
49
+ reactFancy.Textarea,
50
+ {
51
+ id,
52
+ "data-ff-field": field.key,
53
+ ref: autoFocusRef,
54
+ placeholder: field.placeholder,
55
+ rows: 3,
56
+ value: String(value ?? ""),
57
+ onValueChange: onChange
58
+ }
59
+ ),
60
+ number: ({ field, id, value, onChange, autoFocusRef }) => /* @__PURE__ */ jsxRuntime.jsx(
61
+ reactFancy.Input,
62
+ {
63
+ id,
64
+ "data-ff-field": field.key,
65
+ ref: autoFocusRef,
66
+ type: "number",
67
+ placeholder: field.placeholder,
68
+ value: String(value ?? ""),
69
+ onValueChange: (v) => onChange(v === "" ? "" : Number(v))
70
+ }
71
+ ),
72
+ select: ({ field, id, value, onChange }) => /* @__PURE__ */ jsxRuntime.jsx(
73
+ reactFancy.Select,
74
+ {
75
+ id,
76
+ "data-ff-field": field.key,
77
+ list: (field.options ?? []).map((o) => ({ value: o.value, label: o.label })),
78
+ value: String(value ?? ""),
79
+ onValueChange: onChange
80
+ }
81
+ ),
82
+ switch: ({ field, id, value, onChange }) => /* @__PURE__ */ jsxRuntime.jsx(
83
+ reactFancy.Switch,
84
+ {
85
+ id,
86
+ "data-ff-field": field.key,
87
+ checked: !!value,
88
+ onCheckedChange: onChange
89
+ }
90
+ ),
91
+ date: ({ field, id, value, onChange }) => /* @__PURE__ */ jsxRuntime.jsx(
92
+ reactFancy.DatePicker,
93
+ {
94
+ id,
95
+ "data-ff-field": field.key,
96
+ value: String(value ?? ""),
97
+ onValueChange: onChange
98
+ }
99
+ )
100
+ };
36
101
 
102
+ exports.humanFieldRenderers = humanFieldRenderers;
37
103
  exports.jsonFieldRenderer = jsonFieldRenderer;
38
104
  exports.reactFancyFieldRenderers = reactFancyFieldRenderers;
39
105
  //# sourceMappingURL=react-fancy.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fields/react-fancy.tsx"],"names":["jsx","JsonEditor"],"mappings":";;;;;;AA6CO,IAAM,oBAAyC,CAAC;AAAA,EACrD,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,KAAgC;AAC9B,EAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,EAAQ,OAAO,IAAA;AAElC,EAAA;AAAA;AAAA;AAAA;AAAA,oBAIEA,cAAA;AAAA,MAACC,qBAAA;AAAA,MAAA;AAAA,QAIC,EAAA;AAAA,QACA,iBAAe,KAAA,CAAM,GAAA;AAAA,QACrB,KAAA,EAAQ,SAAS,EAAC;AAAA,QAClB,QAAA,EAAU,CAAC,IAAA,KAAoB,QAAA,CAAS,IAAI,CAAA;AAAA,QAI5C,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,IAAA,EAAK,MAAA;AAAA,QACL,IAAA,EAAK,IAAA;AAAA,QACL,WAAW,KAAA,CAAM;AAAA;AAAA;AACnB;AAEJ;AAKO,IAAM,wBAAA,GAAgE;AAAA,EAC3E,IAAA,EAAM;AACR","file":"react-fancy.cjs","sourcesContent":["/**\n * `@particle-academy/fancy-flow/fields/react-fancy` — richer config fields for\n * hosts that already run react-fancy.\n *\n * ```tsx\n * import { reactFancyFieldRenderers } from \"@particle-academy/fancy-flow/fields/react-fancy\";\n *\n * <FlowEditor … fieldRenderers={reactFancyFieldRenderers} />\n * ```\n *\n * ## Why an opt-in subpath rather than the panel itself\n *\n * fancy-flow themes every surface through a `--ff-*` custom-property layer that\n * a host overrides on `.ff-editor`. react-fancy's primitives are hardcoded\n * Tailwind palette classes and read no custom properties — so building them\n * into the panel would trade the theming contract for a nicer widget, which is\n * the trade `panel-labels.test.tsx` already decided against.\n *\n * Making it a subpath keeps both: react-fancy stays an OPTIONAL peer, a\n * standalone install pays nothing, and a Tailwind host opts in with one prop.\n * Same shape as `/llm/vercel-ai` and `/rich-input`.\n *\n * ## What it currently covers\n *\n * Just `json`, because that is the only built-in whose fallback is a raw\n * textarea. The others are already purpose-built controls; replacing them would\n * be churn. This is a map, so a host can spread it and add their own:\n *\n * ```tsx\n * fieldRenderers={{ ...reactFancyFieldRenderers, \"trigger-filters\": myRenderer }}\n * ```\n */\nimport { JsonEditor, type JsonValue } from \"@particle-academy/react-fancy\";\nimport type {\n ConfigFieldRenderContext,\n ConfigFieldRenderFn,\n} from \"../components/NodeConfigPanel/ConfigFieldRenderer\";\n\n/**\n * Render a `json` config field as a react-fancy `JsonEditor`.\n *\n * Returns `null` for every other field type — the seam treats `null` as \"not\n * mine\", so this can be handed over wholesale without claiming controls it has\n * no business replacing.\n */\nexport const jsonFieldRenderer: ConfigFieldRenderFn = ({\n field,\n value,\n onChange,\n id,\n}: ConfigFieldRenderContext) => {\n if (field.type !== \"json\") return null;\n\n return (\n // JSX rather than `createElement` for one concrete reason: `data-*` props\n // are special-cased by JSX but hit excess-property checking in a\n // `createElement` object literal, so the agent handle would not typecheck.\n <JsonEditor\n // The panel's `<label htmlFor>` points at this id, and the Human+\n // contract wants a stable handle an agent can target without guessing at\n // the DOM. Both reach the editor's root element via its rest spread.\n id={id}\n data-ff-field={field.key}\n value={(value ?? {}) as JsonValue}\n onChange={(next: JsonValue) => onChange(next)}\n // `keyMap` is what makes this more than a prettier textarea: it declares\n // a type per path, which picks the control and reports contradictions. A\n // string by design, so it survives an MCP round-trip.\n keyMap={field.keyMap}\n mode=\"edit\"\n size=\"sm\"\n rootLabel={field.label}\n />\n );\n};\n\n/**\n * The full renderer map. Spread it to add your own types alongside.\n */\nexport const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn> = {\n json: jsonFieldRenderer,\n};\n"]}
1
+ {"version":3,"sources":["../../src/fields/react-fancy.tsx"],"names":["jsx","JsonEditor","Input","Textarea","Select","Switch","DatePicker"],"mappings":"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","file":"react-fancy.cjs","sourcesContent":["/**\n * `@particle-academy/fancy-flow/fields/react-fancy` — richer config fields for\n * hosts that already run react-fancy.\n *\n * ```tsx\n * import { reactFancyFieldRenderers } from \"@particle-academy/fancy-flow/fields/react-fancy\";\n *\n * <FlowEditor … fieldRenderers={reactFancyFieldRenderers} />\n * ```\n *\n * ## Why an opt-in subpath rather than the panel itself\n *\n * fancy-flow themes every surface through a `--ff-*` custom-property layer that\n * a host overrides on `.ff-editor`. react-fancy's primitives are hardcoded\n * Tailwind palette classes and read no custom properties — so building them\n * into the panel would trade the theming contract for a nicer widget, which is\n * the trade `panel-labels.test.tsx` already decided against.\n *\n * Making it a subpath keeps both: react-fancy stays an OPTIONAL peer, a\n * standalone install pays nothing, and a Tailwind host opts in with one prop.\n * Same shape as `/llm/vercel-ai` and `/rich-input`.\n *\n * ## What it currently covers\n *\n * Just `json`, because that is the only built-in whose fallback is a raw\n * textarea. The others are already purpose-built controls; replacing them would\n * be churn. This is a map, so a host can spread it and add their own:\n *\n * ```tsx\n * fieldRenderers={{ ...reactFancyFieldRenderers, \"trigger-filters\": myRenderer }}\n * ```\n */\nimport {\n DatePicker,\n Input,\n JsonEditor,\n Select,\n Switch,\n Textarea,\n type JsonValue,\n} from \"@particle-academy/react-fancy\";\nimport type {\n ConfigFieldRenderContext,\n ConfigFieldRenderFn,\n} from \"../components/NodeConfigPanel/ConfigFieldRenderer\";\nimport type { HumanFieldRenderers } from \"../components/FlowEditor/HumanPrompt\";\n\n/**\n * Render a `json` config field as a react-fancy `JsonEditor`.\n *\n * Returns `null` for every other field type — the seam treats `null` as \"not\n * mine\", so this can be handed over wholesale without claiming controls it has\n * no business replacing.\n */\nexport const jsonFieldRenderer: ConfigFieldRenderFn = ({\n field,\n value,\n onChange,\n id,\n}: ConfigFieldRenderContext) => {\n if (field.type !== \"json\") return null;\n\n return (\n // JSX rather than `createElement` for one concrete reason: `data-*` props\n // are special-cased by JSX but hit excess-property checking in a\n // `createElement` object literal, so the agent handle would not typecheck.\n <JsonEditor\n // The panel's `<label htmlFor>` points at this id, and the Human+\n // contract wants a stable handle an agent can target without guessing at\n // the DOM. Both reach the editor's root element via its rest spread.\n id={id}\n data-ff-field={field.key}\n value={(value ?? {}) as JsonValue}\n onChange={(next: JsonValue) => onChange(next)}\n // `keyMap` is what makes this more than a prettier textarea: it declares\n // a type per path, which picks the control and reports contradictions. A\n // string by design, so it survives an MCP round-trip.\n keyMap={field.keyMap}\n mode=\"edit\"\n size=\"sm\"\n rootLabel={field.label}\n />\n );\n};\n\n/**\n * The full renderer map. Spread it to add your own types alongside.\n */\nexport const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn> = {\n json: jsonFieldRenderer,\n};\n\n/* ------------------------------------------------------------------------- *\n * Pause-form controls\n * ------------------------------------------------------------------------- */\n\n/**\n * react-fancy controls for the `user_input` / `human_approval` pause form.\n *\n * ```tsx\n * import { humanFieldRenderers } from \"@particle-academy/fancy-flow/fields/react-fancy\";\n *\n * <FlowEditor … humanFieldRenderers={humanFieldRenderers} />\n * ```\n *\n * ## Why this is opt-in rather than the default\n *\n * Rule 2 of the suite says our surfaces compose react-fancy rather than\n * hand-rolling what a primitive already covers, and the pause form's built-ins\n * are native elements. That is not a gap in the kit — react-fancy has every\n * primitive needed — it is a PACKAGING constraint: react-fancy is an OPTIONAL\n * peer, `HumanPrompt` ships in the main entry, and importing it there would\n * make a standalone `npm install @particle-academy/fancy-flow` fail to resolve\n * at import time while bypassing the `--ff-*` token layer hosts theme\n * `.ff-editor` with.\n *\n * This map is how a host that HAS react-fancy gets the Fancy controls anyway,\n * with one prop and no cost to anyone else. Same shape and same reasoning as\n * `reactFancyFieldRenderers` above.\n *\n * ## What it covers, and what it deliberately does not\n *\n * `datetime` is absent: react-fancy's `DatePicker` is date-only, so claiming\n * the type here would render a control that silently drops the time half of a\n * value the field promised to collect. It falls through to the built-in\n * `<input type=\"datetime-local\">`, which does collect it. An honest gap beats a\n * control that looks right and loses data.\n */\nexport const humanFieldRenderers: HumanFieldRenderers = {\n text: ({ field, id, value, onChange, autoFocusRef }) => (\n <Input\n id={id}\n data-ff-field={field.key}\n ref={autoFocusRef as React.RefObject<HTMLInputElement> | undefined}\n placeholder={field.placeholder}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n\n textarea: ({ field, id, value, onChange, autoFocusRef }) => (\n <Textarea\n id={id}\n data-ff-field={field.key}\n ref={autoFocusRef as React.RefObject<HTMLTextAreaElement> | undefined}\n placeholder={field.placeholder}\n rows={3}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n\n number: ({ field, id, value, onChange, autoFocusRef }) => (\n <Input\n id={id}\n data-ff-field={field.key}\n ref={autoFocusRef as React.RefObject<HTMLInputElement> | undefined}\n type=\"number\"\n placeholder={field.placeholder}\n value={String(value ?? \"\")}\n // The built-in resolves a number field to a NUMBER, so this must too --\n // the value here is what resumes the paused run, and handing the next\n // node \"41\" where it resolved 41 before is a silent type change that no\n // test downstream would attribute to a renderer swap.\n onValueChange={(v) => onChange(v === \"\" ? \"\" : Number(v))}\n />\n ),\n\n select: ({ field, id, value, onChange }) => (\n <Select\n id={id}\n data-ff-field={field.key}\n list={(field.options ?? []).map((o) => ({ value: o.value, label: o.label }))}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n\n switch: ({ field, id, value, onChange }) => (\n <Switch\n id={id}\n data-ff-field={field.key}\n checked={!!value}\n onCheckedChange={onChange}\n />\n ),\n\n date: ({ field, id, value, onChange }) => (\n <DatePicker\n id={id}\n data-ff-field={field.key}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n};\n\n/*\n * `time` and `datetime` are ABSENT on purpose, and for different reasons.\n *\n * `datetime`: react-fancy's `DatePicker` is date-only. Claiming the type here\n * would render a control that silently drops the time half of a value the field\n * promised to collect.\n *\n * `time`: `TimePicker` accepts NO `id` and no `data-*` passthrough --\n * `TimePickerProps` is a closed interface of nine props, none of them an\n * identifier. It therefore cannot satisfy the two handles every control in this\n * form owes: the `id` its `<label htmlFor>` points at, and the `data-ff-field`\n * an agent drives the surface by. Wrapping it in a handle-bearing div would put\n * the label on a non-focusable element, which is worse than not claiming it.\n *\n * Both fall through to the built-in `<input type=\"time\">` /\n * `<input type=\"datetime-local\">`, which do carry the handles and do collect\n * the whole value. An honest gap beats a control that looks right and loses\n * either data or its handle.\n *\n * The `TimePicker` half is a react-fancy FINDING rather than a fancy-flow\n * limitation -- per the suite's second rule, a missing primitive capability is\n * filed against the kit rather than routed around locally.\n */\n"]}
@@ -1,4 +1,4 @@
1
- import { C as ConfigFieldRenderFn } from '../ConfigFieldRenderer-Bc9Txnql.cjs';
1
+ import { H as HumanFieldRenderers, C as ConfigFieldRenderFn } from '../HumanPrompt-CPl-Spmp.cjs';
2
2
  import 'react';
3
3
  import '../types-JFYjPJAG.cjs';
4
4
  import '@xyflow/react';
@@ -17,5 +17,38 @@ declare const jsonFieldRenderer: ConfigFieldRenderFn;
17
17
  * The full renderer map. Spread it to add your own types alongside.
18
18
  */
19
19
  declare const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn>;
20
+ /**
21
+ * react-fancy controls for the `user_input` / `human_approval` pause form.
22
+ *
23
+ * ```tsx
24
+ * import { humanFieldRenderers } from "@particle-academy/fancy-flow/fields/react-fancy";
25
+ *
26
+ * <FlowEditor … humanFieldRenderers={humanFieldRenderers} />
27
+ * ```
28
+ *
29
+ * ## Why this is opt-in rather than the default
30
+ *
31
+ * Rule 2 of the suite says our surfaces compose react-fancy rather than
32
+ * hand-rolling what a primitive already covers, and the pause form's built-ins
33
+ * are native elements. That is not a gap in the kit — react-fancy has every
34
+ * primitive needed — it is a PACKAGING constraint: react-fancy is an OPTIONAL
35
+ * peer, `HumanPrompt` ships in the main entry, and importing it there would
36
+ * make a standalone `npm install @particle-academy/fancy-flow` fail to resolve
37
+ * at import time while bypassing the `--ff-*` token layer hosts theme
38
+ * `.ff-editor` with.
39
+ *
40
+ * This map is how a host that HAS react-fancy gets the Fancy controls anyway,
41
+ * with one prop and no cost to anyone else. Same shape and same reasoning as
42
+ * `reactFancyFieldRenderers` above.
43
+ *
44
+ * ## What it covers, and what it deliberately does not
45
+ *
46
+ * `datetime` is absent: react-fancy's `DatePicker` is date-only, so claiming
47
+ * the type here would render a control that silently drops the time half of a
48
+ * value the field promised to collect. It falls through to the built-in
49
+ * `<input type="datetime-local">`, which does collect it. An honest gap beats a
50
+ * control that looks right and loses data.
51
+ */
52
+ declare const humanFieldRenderers: HumanFieldRenderers;
20
53
 
21
- export { jsonFieldRenderer, reactFancyFieldRenderers };
54
+ export { humanFieldRenderers, jsonFieldRenderer, reactFancyFieldRenderers };
@@ -1,4 +1,4 @@
1
- import { C as ConfigFieldRenderFn } from '../ConfigFieldRenderer-Bt3RGT7Z.js';
1
+ import { H as HumanFieldRenderers, C as ConfigFieldRenderFn } from '../HumanPrompt-DlJSFi3F.js';
2
2
  import 'react';
3
3
  import '../types-JFYjPJAG.js';
4
4
  import '@xyflow/react';
@@ -17,5 +17,38 @@ declare const jsonFieldRenderer: ConfigFieldRenderFn;
17
17
  * The full renderer map. Spread it to add your own types alongside.
18
18
  */
19
19
  declare const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn>;
20
+ /**
21
+ * react-fancy controls for the `user_input` / `human_approval` pause form.
22
+ *
23
+ * ```tsx
24
+ * import { humanFieldRenderers } from "@particle-academy/fancy-flow/fields/react-fancy";
25
+ *
26
+ * <FlowEditor … humanFieldRenderers={humanFieldRenderers} />
27
+ * ```
28
+ *
29
+ * ## Why this is opt-in rather than the default
30
+ *
31
+ * Rule 2 of the suite says our surfaces compose react-fancy rather than
32
+ * hand-rolling what a primitive already covers, and the pause form's built-ins
33
+ * are native elements. That is not a gap in the kit — react-fancy has every
34
+ * primitive needed — it is a PACKAGING constraint: react-fancy is an OPTIONAL
35
+ * peer, `HumanPrompt` ships in the main entry, and importing it there would
36
+ * make a standalone `npm install @particle-academy/fancy-flow` fail to resolve
37
+ * at import time while bypassing the `--ff-*` token layer hosts theme
38
+ * `.ff-editor` with.
39
+ *
40
+ * This map is how a host that HAS react-fancy gets the Fancy controls anyway,
41
+ * with one prop and no cost to anyone else. Same shape and same reasoning as
42
+ * `reactFancyFieldRenderers` above.
43
+ *
44
+ * ## What it covers, and what it deliberately does not
45
+ *
46
+ * `datetime` is absent: react-fancy's `DatePicker` is date-only, so claiming
47
+ * the type here would render a control that silently drops the time half of a
48
+ * value the field promised to collect. It falls through to the built-in
49
+ * `<input type="datetime-local">`, which does collect it. An honest gap beats a
50
+ * control that looks right and loses data.
51
+ */
52
+ declare const humanFieldRenderers: HumanFieldRenderers;
20
53
 
21
- export { jsonFieldRenderer, reactFancyFieldRenderers };
54
+ export { humanFieldRenderers, jsonFieldRenderer, reactFancyFieldRenderers };
@@ -1,4 +1,4 @@
1
- import { JsonEditor } from '@particle-academy/react-fancy';
1
+ import { JsonEditor, DatePicker, Switch, Select, Input, Textarea } from '@particle-academy/react-fancy';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
 
4
4
  // src/fields/react-fancy.tsx
@@ -31,7 +31,72 @@ var jsonFieldRenderer = ({
31
31
  var reactFancyFieldRenderers = {
32
32
  json: jsonFieldRenderer
33
33
  };
34
+ var humanFieldRenderers = {
35
+ text: ({ field, id, value, onChange, autoFocusRef }) => /* @__PURE__ */ jsx(
36
+ Input,
37
+ {
38
+ id,
39
+ "data-ff-field": field.key,
40
+ ref: autoFocusRef,
41
+ placeholder: field.placeholder,
42
+ value: String(value ?? ""),
43
+ onValueChange: onChange
44
+ }
45
+ ),
46
+ textarea: ({ field, id, value, onChange, autoFocusRef }) => /* @__PURE__ */ jsx(
47
+ Textarea,
48
+ {
49
+ id,
50
+ "data-ff-field": field.key,
51
+ ref: autoFocusRef,
52
+ placeholder: field.placeholder,
53
+ rows: 3,
54
+ value: String(value ?? ""),
55
+ onValueChange: onChange
56
+ }
57
+ ),
58
+ number: ({ field, id, value, onChange, autoFocusRef }) => /* @__PURE__ */ jsx(
59
+ Input,
60
+ {
61
+ id,
62
+ "data-ff-field": field.key,
63
+ ref: autoFocusRef,
64
+ type: "number",
65
+ placeholder: field.placeholder,
66
+ value: String(value ?? ""),
67
+ onValueChange: (v) => onChange(v === "" ? "" : Number(v))
68
+ }
69
+ ),
70
+ select: ({ field, id, value, onChange }) => /* @__PURE__ */ jsx(
71
+ Select,
72
+ {
73
+ id,
74
+ "data-ff-field": field.key,
75
+ list: (field.options ?? []).map((o) => ({ value: o.value, label: o.label })),
76
+ value: String(value ?? ""),
77
+ onValueChange: onChange
78
+ }
79
+ ),
80
+ switch: ({ field, id, value, onChange }) => /* @__PURE__ */ jsx(
81
+ Switch,
82
+ {
83
+ id,
84
+ "data-ff-field": field.key,
85
+ checked: !!value,
86
+ onCheckedChange: onChange
87
+ }
88
+ ),
89
+ date: ({ field, id, value, onChange }) => /* @__PURE__ */ jsx(
90
+ DatePicker,
91
+ {
92
+ id,
93
+ "data-ff-field": field.key,
94
+ value: String(value ?? ""),
95
+ onValueChange: onChange
96
+ }
97
+ )
98
+ };
34
99
 
35
- export { jsonFieldRenderer, reactFancyFieldRenderers };
100
+ export { humanFieldRenderers, jsonFieldRenderer, reactFancyFieldRenderers };
36
101
  //# sourceMappingURL=react-fancy.js.map
37
102
  //# sourceMappingURL=react-fancy.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fields/react-fancy.tsx"],"names":[],"mappings":";;;;AA6CO,IAAM,oBAAyC,CAAC;AAAA,EACrD,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,KAAgC;AAC9B,EAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,EAAQ,OAAO,IAAA;AAElC,EAAA;AAAA;AAAA;AAAA;AAAA,oBAIE,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QAIC,EAAA;AAAA,QACA,iBAAe,KAAA,CAAM,GAAA;AAAA,QACrB,KAAA,EAAQ,SAAS,EAAC;AAAA,QAClB,QAAA,EAAU,CAAC,IAAA,KAAoB,QAAA,CAAS,IAAI,CAAA;AAAA,QAI5C,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,IAAA,EAAK,MAAA;AAAA,QACL,IAAA,EAAK,IAAA;AAAA,QACL,WAAW,KAAA,CAAM;AAAA;AAAA;AACnB;AAEJ;AAKO,IAAM,wBAAA,GAAgE;AAAA,EAC3E,IAAA,EAAM;AACR","file":"react-fancy.js","sourcesContent":["/**\n * `@particle-academy/fancy-flow/fields/react-fancy` — richer config fields for\n * hosts that already run react-fancy.\n *\n * ```tsx\n * import { reactFancyFieldRenderers } from \"@particle-academy/fancy-flow/fields/react-fancy\";\n *\n * <FlowEditor … fieldRenderers={reactFancyFieldRenderers} />\n * ```\n *\n * ## Why an opt-in subpath rather than the panel itself\n *\n * fancy-flow themes every surface through a `--ff-*` custom-property layer that\n * a host overrides on `.ff-editor`. react-fancy's primitives are hardcoded\n * Tailwind palette classes and read no custom properties — so building them\n * into the panel would trade the theming contract for a nicer widget, which is\n * the trade `panel-labels.test.tsx` already decided against.\n *\n * Making it a subpath keeps both: react-fancy stays an OPTIONAL peer, a\n * standalone install pays nothing, and a Tailwind host opts in with one prop.\n * Same shape as `/llm/vercel-ai` and `/rich-input`.\n *\n * ## What it currently covers\n *\n * Just `json`, because that is the only built-in whose fallback is a raw\n * textarea. The others are already purpose-built controls; replacing them would\n * be churn. This is a map, so a host can spread it and add their own:\n *\n * ```tsx\n * fieldRenderers={{ ...reactFancyFieldRenderers, \"trigger-filters\": myRenderer }}\n * ```\n */\nimport { JsonEditor, type JsonValue } from \"@particle-academy/react-fancy\";\nimport type {\n ConfigFieldRenderContext,\n ConfigFieldRenderFn,\n} from \"../components/NodeConfigPanel/ConfigFieldRenderer\";\n\n/**\n * Render a `json` config field as a react-fancy `JsonEditor`.\n *\n * Returns `null` for every other field type — the seam treats `null` as \"not\n * mine\", so this can be handed over wholesale without claiming controls it has\n * no business replacing.\n */\nexport const jsonFieldRenderer: ConfigFieldRenderFn = ({\n field,\n value,\n onChange,\n id,\n}: ConfigFieldRenderContext) => {\n if (field.type !== \"json\") return null;\n\n return (\n // JSX rather than `createElement` for one concrete reason: `data-*` props\n // are special-cased by JSX but hit excess-property checking in a\n // `createElement` object literal, so the agent handle would not typecheck.\n <JsonEditor\n // The panel's `<label htmlFor>` points at this id, and the Human+\n // contract wants a stable handle an agent can target without guessing at\n // the DOM. Both reach the editor's root element via its rest spread.\n id={id}\n data-ff-field={field.key}\n value={(value ?? {}) as JsonValue}\n onChange={(next: JsonValue) => onChange(next)}\n // `keyMap` is what makes this more than a prettier textarea: it declares\n // a type per path, which picks the control and reports contradictions. A\n // string by design, so it survives an MCP round-trip.\n keyMap={field.keyMap}\n mode=\"edit\"\n size=\"sm\"\n rootLabel={field.label}\n />\n );\n};\n\n/**\n * The full renderer map. Spread it to add your own types alongside.\n */\nexport const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn> = {\n json: jsonFieldRenderer,\n};\n"]}
1
+ {"version":3,"sources":["../../src/fields/react-fancy.tsx"],"names":[],"mappings":"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","file":"react-fancy.js","sourcesContent":["/**\n * `@particle-academy/fancy-flow/fields/react-fancy` — richer config fields for\n * hosts that already run react-fancy.\n *\n * ```tsx\n * import { reactFancyFieldRenderers } from \"@particle-academy/fancy-flow/fields/react-fancy\";\n *\n * <FlowEditor … fieldRenderers={reactFancyFieldRenderers} />\n * ```\n *\n * ## Why an opt-in subpath rather than the panel itself\n *\n * fancy-flow themes every surface through a `--ff-*` custom-property layer that\n * a host overrides on `.ff-editor`. react-fancy's primitives are hardcoded\n * Tailwind palette classes and read no custom properties — so building them\n * into the panel would trade the theming contract for a nicer widget, which is\n * the trade `panel-labels.test.tsx` already decided against.\n *\n * Making it a subpath keeps both: react-fancy stays an OPTIONAL peer, a\n * standalone install pays nothing, and a Tailwind host opts in with one prop.\n * Same shape as `/llm/vercel-ai` and `/rich-input`.\n *\n * ## What it currently covers\n *\n * Just `json`, because that is the only built-in whose fallback is a raw\n * textarea. The others are already purpose-built controls; replacing them would\n * be churn. This is a map, so a host can spread it and add their own:\n *\n * ```tsx\n * fieldRenderers={{ ...reactFancyFieldRenderers, \"trigger-filters\": myRenderer }}\n * ```\n */\nimport {\n DatePicker,\n Input,\n JsonEditor,\n Select,\n Switch,\n Textarea,\n type JsonValue,\n} from \"@particle-academy/react-fancy\";\nimport type {\n ConfigFieldRenderContext,\n ConfigFieldRenderFn,\n} from \"../components/NodeConfigPanel/ConfigFieldRenderer\";\nimport type { HumanFieldRenderers } from \"../components/FlowEditor/HumanPrompt\";\n\n/**\n * Render a `json` config field as a react-fancy `JsonEditor`.\n *\n * Returns `null` for every other field type — the seam treats `null` as \"not\n * mine\", so this can be handed over wholesale without claiming controls it has\n * no business replacing.\n */\nexport const jsonFieldRenderer: ConfigFieldRenderFn = ({\n field,\n value,\n onChange,\n id,\n}: ConfigFieldRenderContext) => {\n if (field.type !== \"json\") return null;\n\n return (\n // JSX rather than `createElement` for one concrete reason: `data-*` props\n // are special-cased by JSX but hit excess-property checking in a\n // `createElement` object literal, so the agent handle would not typecheck.\n <JsonEditor\n // The panel's `<label htmlFor>` points at this id, and the Human+\n // contract wants a stable handle an agent can target without guessing at\n // the DOM. Both reach the editor's root element via its rest spread.\n id={id}\n data-ff-field={field.key}\n value={(value ?? {}) as JsonValue}\n onChange={(next: JsonValue) => onChange(next)}\n // `keyMap` is what makes this more than a prettier textarea: it declares\n // a type per path, which picks the control and reports contradictions. A\n // string by design, so it survives an MCP round-trip.\n keyMap={field.keyMap}\n mode=\"edit\"\n size=\"sm\"\n rootLabel={field.label}\n />\n );\n};\n\n/**\n * The full renderer map. Spread it to add your own types alongside.\n */\nexport const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn> = {\n json: jsonFieldRenderer,\n};\n\n/* ------------------------------------------------------------------------- *\n * Pause-form controls\n * ------------------------------------------------------------------------- */\n\n/**\n * react-fancy controls for the `user_input` / `human_approval` pause form.\n *\n * ```tsx\n * import { humanFieldRenderers } from \"@particle-academy/fancy-flow/fields/react-fancy\";\n *\n * <FlowEditor … humanFieldRenderers={humanFieldRenderers} />\n * ```\n *\n * ## Why this is opt-in rather than the default\n *\n * Rule 2 of the suite says our surfaces compose react-fancy rather than\n * hand-rolling what a primitive already covers, and the pause form's built-ins\n * are native elements. That is not a gap in the kit — react-fancy has every\n * primitive needed — it is a PACKAGING constraint: react-fancy is an OPTIONAL\n * peer, `HumanPrompt` ships in the main entry, and importing it there would\n * make a standalone `npm install @particle-academy/fancy-flow` fail to resolve\n * at import time while bypassing the `--ff-*` token layer hosts theme\n * `.ff-editor` with.\n *\n * This map is how a host that HAS react-fancy gets the Fancy controls anyway,\n * with one prop and no cost to anyone else. Same shape and same reasoning as\n * `reactFancyFieldRenderers` above.\n *\n * ## What it covers, and what it deliberately does not\n *\n * `datetime` is absent: react-fancy's `DatePicker` is date-only, so claiming\n * the type here would render a control that silently drops the time half of a\n * value the field promised to collect. It falls through to the built-in\n * `<input type=\"datetime-local\">`, which does collect it. An honest gap beats a\n * control that looks right and loses data.\n */\nexport const humanFieldRenderers: HumanFieldRenderers = {\n text: ({ field, id, value, onChange, autoFocusRef }) => (\n <Input\n id={id}\n data-ff-field={field.key}\n ref={autoFocusRef as React.RefObject<HTMLInputElement> | undefined}\n placeholder={field.placeholder}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n\n textarea: ({ field, id, value, onChange, autoFocusRef }) => (\n <Textarea\n id={id}\n data-ff-field={field.key}\n ref={autoFocusRef as React.RefObject<HTMLTextAreaElement> | undefined}\n placeholder={field.placeholder}\n rows={3}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n\n number: ({ field, id, value, onChange, autoFocusRef }) => (\n <Input\n id={id}\n data-ff-field={field.key}\n ref={autoFocusRef as React.RefObject<HTMLInputElement> | undefined}\n type=\"number\"\n placeholder={field.placeholder}\n value={String(value ?? \"\")}\n // The built-in resolves a number field to a NUMBER, so this must too --\n // the value here is what resumes the paused run, and handing the next\n // node \"41\" where it resolved 41 before is a silent type change that no\n // test downstream would attribute to a renderer swap.\n onValueChange={(v) => onChange(v === \"\" ? \"\" : Number(v))}\n />\n ),\n\n select: ({ field, id, value, onChange }) => (\n <Select\n id={id}\n data-ff-field={field.key}\n list={(field.options ?? []).map((o) => ({ value: o.value, label: o.label }))}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n\n switch: ({ field, id, value, onChange }) => (\n <Switch\n id={id}\n data-ff-field={field.key}\n checked={!!value}\n onCheckedChange={onChange}\n />\n ),\n\n date: ({ field, id, value, onChange }) => (\n <DatePicker\n id={id}\n data-ff-field={field.key}\n value={String(value ?? \"\")}\n onValueChange={onChange}\n />\n ),\n};\n\n/*\n * `time` and `datetime` are ABSENT on purpose, and for different reasons.\n *\n * `datetime`: react-fancy's `DatePicker` is date-only. Claiming the type here\n * would render a control that silently drops the time half of a value the field\n * promised to collect.\n *\n * `time`: `TimePicker` accepts NO `id` and no `data-*` passthrough --\n * `TimePickerProps` is a closed interface of nine props, none of them an\n * identifier. It therefore cannot satisfy the two handles every control in this\n * form owes: the `id` its `<label htmlFor>` points at, and the `data-ff-field`\n * an agent drives the surface by. Wrapping it in a handle-bearing div would put\n * the label on a non-focusable element, which is worse than not claiming it.\n *\n * Both fall through to the built-in `<input type=\"time\">` /\n * `<input type=\"datetime-local\">`, which do carry the handles and do collect\n * the whole value. An honest gap beats a control that looks right and loses\n * either data or its handle.\n *\n * The `TimePicker` half is a react-fancy FINDING rather than a fancy-flow\n * limitation -- per the suite's second rule, a missing primitive capability is\n * filed against the kit rather than routed around locally.\n */\n"]}
package/dist/index.cjs CHANGED
@@ -14696,7 +14696,11 @@ function initialValues(fields) {
14696
14696
  }
14697
14697
  return v2;
14698
14698
  }
14699
- function HumanPrompt({ request, onCancel }) {
14699
+ function HumanPrompt({
14700
+ request,
14701
+ onCancel,
14702
+ fieldRenderers
14703
+ }) {
14700
14704
  const firstRef = ReactExports.useRef(null);
14701
14705
  ReactExports.useEffect(() => {
14702
14706
  firstRef.current?.focus();
@@ -14708,7 +14712,7 @@ function HumanPrompt({ request, onCancel }) {
14708
14712
  }, [onCancel]);
14709
14713
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt-overlay", role: "dialog", "aria-modal": "true", "aria-label": request.title, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-prompt", onClick: (e) => e.stopPropagation(), children: [
14710
14714
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt__title", children: request.title }),
14711
- request.kind === "approval" ? /* @__PURE__ */ jsxRuntime.jsx(ApprovalBody, { request, onCancel }) : /* @__PURE__ */ jsxRuntime.jsx(InputBody, { request, onCancel, firstRef })
14715
+ request.kind === "approval" ? /* @__PURE__ */ jsxRuntime.jsx(ApprovalBody, { request, onCancel }) : /* @__PURE__ */ jsxRuntime.jsx(InputBody, { request, onCancel, firstRef, fieldRenderers })
14712
14716
  ] }) });
14713
14717
  }
14714
14718
  function ApprovalBody({ request, onCancel }) {
@@ -14724,7 +14728,8 @@ function ApprovalBody({ request, onCancel }) {
14724
14728
  function InputBody({
14725
14729
  request,
14726
14730
  onCancel,
14727
- firstRef
14731
+ firstRef,
14732
+ fieldRenderers
14728
14733
  }) {
14729
14734
  const [values, setValues] = ReactExports.useState(() => initialValues(request.fields));
14730
14735
  const set3 = (k2, v2) => setValues((prev) => ({ ...prev, [k2]: v2 }));
@@ -14760,7 +14765,8 @@ function InputBody({
14760
14765
  id: fieldId(f.key),
14761
14766
  value: values[f.key],
14762
14767
  onChange: (v2) => set3(f.key, v2),
14763
- autoFocusRef: i === 0 ? firstRef : void 0
14768
+ autoFocusRef: i === 0 ? firstRef : void 0,
14769
+ renderers: fieldRenderers
14764
14770
  }
14765
14771
  )
14766
14772
  ] }, f.key)) }),
@@ -14777,8 +14783,14 @@ function FieldControl({
14777
14783
  id: id2,
14778
14784
  value,
14779
14785
  onChange,
14780
- autoFocusRef
14786
+ autoFocusRef,
14787
+ renderers
14781
14788
  }) {
14789
+ const override = renderers?.[field.type ?? "text"];
14790
+ if (override) {
14791
+ const rendered = override({ field, id: id2, value, onChange, autoFocusRef });
14792
+ if (rendered !== null && rendered !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: rendered });
14793
+ }
14782
14794
  const handle = { id: id2, "data-ff-field": field.key };
14783
14795
  if (field.type === "textarea") {
14784
14796
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -14866,6 +14878,7 @@ function FlowEditorInner({
14866
14878
  onDelete,
14867
14879
  onEdgeDelete,
14868
14880
  confirmDelete,
14881
+ humanFieldRenderers,
14869
14882
  apiRef
14870
14883
  }) {
14871
14884
  const internal = useFlowState(initial);
@@ -15402,10 +15415,17 @@ function FlowEditorInner({
15402
15415
  }
15403
15416
  ),
15404
15417
  showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsxRuntime.jsx(FlowRunFeed, { entries: runner.feed, running: api.running, className: "ff-editor__feed" })),
15405
- prompt && /* @__PURE__ */ jsxRuntime.jsx(HumanPrompt, { request: prompt, onCancel: () => {
15406
- setPrompt(null);
15407
- runner.cancel();
15408
- } })
15418
+ prompt && /* @__PURE__ */ jsxRuntime.jsx(
15419
+ HumanPrompt,
15420
+ {
15421
+ request: prompt,
15422
+ onCancel: () => {
15423
+ setPrompt(null);
15424
+ runner.cancel();
15425
+ },
15426
+ fieldRenderers: humanFieldRenderers
15427
+ }
15428
+ )
15409
15429
  ] }),
15410
15430
  showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-editor__panel-wrap", children: [
15411
15431
  /* @__PURE__ */ jsxRuntime.jsx(