@particle-academy/fancy-flow 0.52.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.
Files changed (49) hide show
  1. package/dist/{ConfigFieldRenderer-Bc9Txnql.d.cts → HumanPrompt-CPl-Spmp.d.cts} +75 -1
  2. package/dist/{ConfigFieldRenderer-Bt3RGT7Z.d.ts → HumanPrompt-DlJSFi3F.d.ts} +75 -1
  3. package/dist/{chunk-JOXMAWAI.js → chunk-CIBW5ATA.js} +148 -5
  4. package/dist/chunk-CIBW5ATA.js.map +1 -0
  5. package/dist/{chunk-PEX3ZGCO.js → chunk-FG3C7LIG.js} +3 -3
  6. package/dist/{chunk-PEX3ZGCO.js.map → chunk-FG3C7LIG.js.map} +1 -1
  7. package/dist/{chunk-PU3CLTAG.js → chunk-FH6MWLKD.js} +3 -3
  8. package/dist/{chunk-PU3CLTAG.js.map → chunk-FH6MWLKD.js.map} +1 -1
  9. package/dist/{chunk-PWIHOR57.js → chunk-FTGVVKWX.js} +4 -4
  10. package/dist/{chunk-PWIHOR57.js.map → chunk-FTGVVKWX.js.map} +1 -1
  11. package/dist/{chunk-I7ZDPY7C.js → chunk-VWANLNL5.js} +3 -3
  12. package/dist/{chunk-I7ZDPY7C.js.map → chunk-VWANLNL5.js.map} +1 -1
  13. package/dist/{chunk-RR7A6IUW.js → chunk-WCFRKXH5.js} +3 -3
  14. package/dist/{chunk-RR7A6IUW.js.map → chunk-WCFRKXH5.js.map} +1 -1
  15. package/dist/durable.cjs +103 -2
  16. package/dist/durable.cjs.map +1 -1
  17. package/dist/durable.js +1 -1
  18. package/dist/engine.cjs +103 -2
  19. package/dist/engine.cjs.map +1 -1
  20. package/dist/engine.js +3 -3
  21. package/dist/fields/react-fancy.cjs +66 -0
  22. package/dist/fields/react-fancy.cjs.map +1 -1
  23. package/dist/fields/react-fancy.d.cts +35 -2
  24. package/dist/fields/react-fancy.d.ts +35 -2
  25. package/dist/fields/react-fancy.js +67 -2
  26. package/dist/fields/react-fancy.js.map +1 -1
  27. package/dist/index.cjs +245 -44
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +13 -2
  30. package/dist/index.d.ts +13 -2
  31. package/dist/index.js +111 -53
  32. package/dist/index.js.map +1 -1
  33. package/dist/registry.cjs +103 -2
  34. package/dist/registry.cjs.map +1 -1
  35. package/dist/registry.js +2 -2
  36. package/dist/runtime.cjs +103 -2
  37. package/dist/runtime.cjs.map +1 -1
  38. package/dist/runtime.js +3 -3
  39. package/dist/schema.cjs +103 -2
  40. package/dist/schema.cjs.map +1 -1
  41. package/dist/schema.js +2 -2
  42. package/dist/screens.cjs +103 -2
  43. package/dist/screens.cjs.map +1 -1
  44. package/dist/screens.js +4 -4
  45. package/dist/ux.cjs +103 -2
  46. package/dist/ux.cjs.map +1 -1
  47. package/dist/ux.js +1 -1
  48. package/package.json +1 -1
  49. package/dist/chunk-JOXMAWAI.js.map +0 -1
@@ -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 };
@@ -9037,6 +9037,101 @@ function NodeToolbar({ nodeId, children: children2, className, style: style2, is
9037
9037
  };
9038
9038
  return jsx(NodeToolbarPortal, { children: jsx("div", { style: wrapperStyle2, className: cc(["react-flow__node-toolbar", className]), ...rest, "data-id": nodesArray.reduce((acc, node) => `${acc}${node.id} `, "").trim(), children: children2 }) });
9039
9039
  }
9040
+
9041
+ // src/components/FlowEditor/human-fields.ts
9042
+ var HUMAN_FIELD_TYPE_ALIASES = {
9043
+ text: "text",
9044
+ string: "text",
9045
+ str: "text",
9046
+ input: "text",
9047
+ textarea: "textarea",
9048
+ long_text: "textarea",
9049
+ longtext: "textarea",
9050
+ "long-text": "textarea",
9051
+ multiline: "textarea",
9052
+ paragraph: "textarea",
9053
+ markdown: "textarea",
9054
+ number: "number",
9055
+ numeric: "number",
9056
+ integer: "number",
9057
+ int: "number",
9058
+ float: "number",
9059
+ decimal: "number",
9060
+ select: "select",
9061
+ enum: "select",
9062
+ choice: "select",
9063
+ choices: "select",
9064
+ dropdown: "select",
9065
+ options: "select",
9066
+ radio: "select",
9067
+ switch: "switch",
9068
+ bool: "switch",
9069
+ boolean: "switch",
9070
+ checkbox: "switch",
9071
+ toggle: "switch",
9072
+ date: "date",
9073
+ datetime: "datetime",
9074
+ "datetime-local": "datetime",
9075
+ datetimelocal: "datetime",
9076
+ timestamp: "datetime",
9077
+ time: "time",
9078
+ email: "email",
9079
+ "e-mail": "email",
9080
+ url: "url",
9081
+ uri: "url",
9082
+ link: "url",
9083
+ tel: "tel",
9084
+ phone: "tel",
9085
+ telephone: "tel",
9086
+ password: "password",
9087
+ secret: "password"
9088
+ };
9089
+ function humanFieldType(raw) {
9090
+ if (typeof raw !== "string") return "text";
9091
+ return HUMAN_FIELD_TYPE_ALIASES[raw.trim().toLowerCase()] ?? "text";
9092
+ }
9093
+ function humanFieldOptions(raw) {
9094
+ const entries = [];
9095
+ if (Array.isArray(raw)) {
9096
+ for (const item of raw) {
9097
+ if (typeof item === "string" || typeof item === "number") {
9098
+ const value = String(item);
9099
+ if (value !== "") entries.push({ value, label: value });
9100
+ continue;
9101
+ }
9102
+ if (item && typeof item === "object") {
9103
+ const value = item.value;
9104
+ if (value === void 0 || value === null || value === "") continue;
9105
+ const label = item.label;
9106
+ entries.push({
9107
+ value: String(value),
9108
+ label: typeof label === "string" && label !== "" ? label : String(value)
9109
+ });
9110
+ }
9111
+ }
9112
+ } else if (raw && typeof raw === "object") {
9113
+ for (const [value, label] of Object.entries(raw)) {
9114
+ if (value === "") continue;
9115
+ entries.push({ value, label: typeof label === "string" && label !== "" ? label : value });
9116
+ }
9117
+ }
9118
+ return entries.length ? entries : void 0;
9119
+ }
9120
+ function humanInputFields(config) {
9121
+ const raw = Array.isArray(config?.fields) ? config.fields : [];
9122
+ const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
9123
+ key: f.key,
9124
+ label: typeof f.label === "string" && f.label ? f.label : f.key,
9125
+ type: humanFieldType(f.type),
9126
+ required: !!f.required,
9127
+ placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
9128
+ options: humanFieldOptions(f.options ?? f.choices),
9129
+ default: f.default
9130
+ }));
9131
+ if (fields.length) return fields;
9132
+ const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
9133
+ return [{ key: "value", label: title, type: "textarea", required: true }];
9134
+ }
9040
9135
  var FlowEditorContext = createContext(null);
9041
9136
  var FlowEditorProvider = FlowEditorContext.Provider;
9042
9137
  function useFlowEditor() {
@@ -9651,6 +9746,20 @@ var subflowExecutor = async (ctx) => {
9651
9746
  };
9652
9747
 
9653
9748
  // src/registry/builtin.ts
9749
+ var HUMAN_FIELD_OUTPUT_TYPE = {
9750
+ text: "string",
9751
+ textarea: "string",
9752
+ select: "string",
9753
+ date: "string",
9754
+ datetime: "string",
9755
+ time: "string",
9756
+ email: "string",
9757
+ url: "string",
9758
+ tel: "string",
9759
+ password: "string",
9760
+ number: "number",
9761
+ switch: "boolean"
9762
+ };
9654
9763
  function casePorts(cases) {
9655
9764
  const byPort = /* @__PURE__ */ new Map();
9656
9765
  if (cases && typeof cases === "object" && !Array.isArray(cases)) {
@@ -9748,7 +9857,17 @@ var KINDS = [
9748
9857
  name: "@particle-academy/user_input",
9749
9858
  // The keys an author declared on THIS node — the case a static list cannot
9750
9859
  // express, and the one issue #5 named.
9751
- outputShape: (config) => (config.fields ?? []).filter((f) => typeof f.key === "string" && f.key !== "").map((f) => ({ path: f.key, type: "string", description: f.label })),
9860
+ //
9861
+ // The TYPE comes from the same normalizer the form renders with, so the
9862
+ // variable picker agrees with what the run actually resolves. It used to say
9863
+ // `string` for every field, which was wrong the moment a field was a number
9864
+ // or a switch — the picker told an author `{{ $json.age }}` was text while
9865
+ // the form handed the next node a number.
9866
+ outputShape: (config) => (config.fields ?? []).filter((f) => typeof f.key === "string" && f.key !== "").map((f) => ({
9867
+ path: f.key,
9868
+ type: HUMAN_FIELD_OUTPUT_TYPE[humanFieldType(f.type)],
9869
+ description: f.label
9870
+ })),
9752
9871
  aliases: ["user_input", "@fancy/user_input"],
9753
9872
  pausesForHuman: "input",
9754
9873
  category: "human",
@@ -9775,14 +9894,38 @@ var KINDS = [
9775
9894
  key: "type",
9776
9895
  label: "Type",
9777
9896
  default: "text",
9897
+ // Every control `HumanPrompt` can render. A type the form supports
9898
+ // but the panel cannot select is reachable only by hand-editing the
9899
+ // workflow JSON — which is the audience this panel exists for.
9778
9900
  options: [
9779
9901
  { value: "text", label: "Text" },
9780
9902
  { value: "textarea", label: "Long text" },
9781
9903
  { value: "number", label: "Number" },
9782
9904
  { value: "select", label: "Select" },
9783
- { value: "switch", label: "Switch" }
9905
+ { value: "switch", label: "Switch (yes / no)" },
9906
+ { value: "date", label: "Date" },
9907
+ { value: "datetime", label: "Date + time" },
9908
+ { value: "time", label: "Time" },
9909
+ { value: "email", label: "Email" },
9910
+ { value: "url", label: "URL" },
9911
+ { value: "tel", label: "Phone" },
9912
+ { value: "password", label: "Password" }
9784
9913
  ]
9785
9914
  },
9915
+ {
9916
+ // Without this a panel-authored select had nothing to choose from,
9917
+ // so it rendered an empty dropdown — the type was declarable and
9918
+ // unusable in the same breath.
9919
+ type: "keyvalue",
9920
+ key: "options",
9921
+ label: "Choices",
9922
+ description: "Select fields only. Left is the value the flow receives, right is what the person sees.",
9923
+ keyLabel: "Value",
9924
+ valueLabel: "Label",
9925
+ keyPlaceholder: "small",
9926
+ valuePlaceholder: "Small",
9927
+ addLabel: "Add choice"
9928
+ },
9786
9929
  { type: "switch", key: "required", label: "Required", default: false }
9787
9930
  ],
9788
9931
  default: [{ key: "answer", label: "Your answer", type: "textarea", required: true }]
@@ -10704,6 +10847,6 @@ function LaneNodeInner(props) {
10704
10847
  }
10705
10848
  var LaneNode = memo(LaneNodeInner);
10706
10849
 
10707
- export { BUILTIN_KINDS, Background, BackgroundVariant, Controls, DEFAULT_MAX_DEPTH, FlowEditorProvider, Handle, LaneNode, MiniMap, NodeResizer, NodeToolbar, NoteNode, Position, ReactFlowProvider, RunIdentity, ViewportPortal, addEdge2 as addEdge, applyEdgeChanges, applyNodeChanges, categoryAccent, clearNodeKindOverrides, declaredRoutes, defaultConfigFor, ensureBuiltinKinds, escapeSegment, getNodeKind, index, kindIds, listNodeKinds, llmRouterExecutor, nodeConfig, onNodeKindsChanged, overrideNodeKind, reconnectEdge2 as reconnectEdge, registerBuiltinKinds, registerNodeKind, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, runFlow, subflowExecutor, subflowMode, subflowPorts, useFlowEditor, useFlowEditorOptional, useReactFlow, validateConfig };
10708
- //# sourceMappingURL=chunk-JOXMAWAI.js.map
10709
- //# sourceMappingURL=chunk-JOXMAWAI.js.map
10850
+ export { BUILTIN_KINDS, Background, BackgroundVariant, Controls, DEFAULT_MAX_DEPTH, FlowEditorProvider, Handle, LaneNode, MiniMap, NodeResizer, NodeToolbar, NoteNode, Position, ReactFlowProvider, RunIdentity, ViewportPortal, addEdge2 as addEdge, applyEdgeChanges, applyNodeChanges, categoryAccent, clearNodeKindOverrides, declaredRoutes, defaultConfigFor, ensureBuiltinKinds, escapeSegment, getNodeKind, humanInputFields, index, kindIds, listNodeKinds, llmRouterExecutor, nodeConfig, onNodeKindsChanged, overrideNodeKind, reconnectEdge2 as reconnectEdge, registerBuiltinKinds, registerNodeKind, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, runFlow, subflowExecutor, subflowMode, subflowPorts, useFlowEditor, useFlowEditorOptional, useReactFlow, validateConfig };
10851
+ //# sourceMappingURL=chunk-CIBW5ATA.js.map
10852
+ //# sourceMappingURL=chunk-CIBW5ATA.js.map