@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
package/dist/index.cjs CHANGED
@@ -1719,6 +1719,101 @@ var init_layout = __esm({
1719
1719
  init_dagre_esm();
1720
1720
  }
1721
1721
  });
1722
+
1723
+ // src/components/FlowEditor/human-fields.ts
1724
+ var HUMAN_FIELD_TYPE_ALIASES = {
1725
+ text: "text",
1726
+ string: "text",
1727
+ str: "text",
1728
+ input: "text",
1729
+ textarea: "textarea",
1730
+ long_text: "textarea",
1731
+ longtext: "textarea",
1732
+ "long-text": "textarea",
1733
+ multiline: "textarea",
1734
+ paragraph: "textarea",
1735
+ markdown: "textarea",
1736
+ number: "number",
1737
+ numeric: "number",
1738
+ integer: "number",
1739
+ int: "number",
1740
+ float: "number",
1741
+ decimal: "number",
1742
+ select: "select",
1743
+ enum: "select",
1744
+ choice: "select",
1745
+ choices: "select",
1746
+ dropdown: "select",
1747
+ options: "select",
1748
+ radio: "select",
1749
+ switch: "switch",
1750
+ bool: "switch",
1751
+ boolean: "switch",
1752
+ checkbox: "switch",
1753
+ toggle: "switch",
1754
+ date: "date",
1755
+ datetime: "datetime",
1756
+ "datetime-local": "datetime",
1757
+ datetimelocal: "datetime",
1758
+ timestamp: "datetime",
1759
+ time: "time",
1760
+ email: "email",
1761
+ "e-mail": "email",
1762
+ url: "url",
1763
+ uri: "url",
1764
+ link: "url",
1765
+ tel: "tel",
1766
+ phone: "tel",
1767
+ telephone: "tel",
1768
+ password: "password",
1769
+ secret: "password"
1770
+ };
1771
+ function humanFieldType(raw) {
1772
+ if (typeof raw !== "string") return "text";
1773
+ return HUMAN_FIELD_TYPE_ALIASES[raw.trim().toLowerCase()] ?? "text";
1774
+ }
1775
+ function humanFieldOptions(raw) {
1776
+ const entries = [];
1777
+ if (Array.isArray(raw)) {
1778
+ for (const item of raw) {
1779
+ if (typeof item === "string" || typeof item === "number") {
1780
+ const value = String(item);
1781
+ if (value !== "") entries.push({ value, label: value });
1782
+ continue;
1783
+ }
1784
+ if (item && typeof item === "object") {
1785
+ const value = item.value;
1786
+ if (value === void 0 || value === null || value === "") continue;
1787
+ const label = item.label;
1788
+ entries.push({
1789
+ value: String(value),
1790
+ label: typeof label === "string" && label !== "" ? label : String(value)
1791
+ });
1792
+ }
1793
+ }
1794
+ } else if (raw && typeof raw === "object") {
1795
+ for (const [value, label] of Object.entries(raw)) {
1796
+ if (value === "") continue;
1797
+ entries.push({ value, label: typeof label === "string" && label !== "" ? label : value });
1798
+ }
1799
+ }
1800
+ return entries.length ? entries : void 0;
1801
+ }
1802
+ function humanInputFields(config) {
1803
+ const raw = Array.isArray(config?.fields) ? config.fields : [];
1804
+ const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
1805
+ key: f.key,
1806
+ label: typeof f.label === "string" && f.label ? f.label : f.key,
1807
+ type: humanFieldType(f.type),
1808
+ required: !!f.required,
1809
+ placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
1810
+ options: humanFieldOptions(f.options ?? f.choices),
1811
+ default: f.default
1812
+ }));
1813
+ if (fields.length) return fields;
1814
+ const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
1815
+ return [{ key: "value", label: title, type: "textarea", required: true }];
1816
+ }
1722
1817
  var adapter = null;
1723
1818
  var listeners = /* @__PURE__ */ new Set();
1724
1819
  function registerRichInputAdapter(next) {
@@ -11448,6 +11543,20 @@ var subflowExecutor = async (ctx) => {
11448
11543
  };
11449
11544
 
11450
11545
  // src/registry/builtin.ts
11546
+ var HUMAN_FIELD_OUTPUT_TYPE = {
11547
+ text: "string",
11548
+ textarea: "string",
11549
+ select: "string",
11550
+ date: "string",
11551
+ datetime: "string",
11552
+ time: "string",
11553
+ email: "string",
11554
+ url: "string",
11555
+ tel: "string",
11556
+ password: "string",
11557
+ number: "number",
11558
+ switch: "boolean"
11559
+ };
11451
11560
  function casePorts(cases) {
11452
11561
  const byPort = /* @__PURE__ */ new Map();
11453
11562
  if (cases && typeof cases === "object" && !Array.isArray(cases)) {
@@ -11545,7 +11654,17 @@ var KINDS = [
11545
11654
  name: "@particle-academy/user_input",
11546
11655
  // The keys an author declared on THIS node — the case a static list cannot
11547
11656
  // express, and the one issue #5 named.
11548
- outputShape: (config) => (config.fields ?? []).filter((f) => typeof f.key === "string" && f.key !== "").map((f) => ({ path: f.key, type: "string", description: f.label })),
11657
+ //
11658
+ // The TYPE comes from the same normalizer the form renders with, so the
11659
+ // variable picker agrees with what the run actually resolves. It used to say
11660
+ // `string` for every field, which was wrong the moment a field was a number
11661
+ // or a switch — the picker told an author `{{ $json.age }}` was text while
11662
+ // the form handed the next node a number.
11663
+ outputShape: (config) => (config.fields ?? []).filter((f) => typeof f.key === "string" && f.key !== "").map((f) => ({
11664
+ path: f.key,
11665
+ type: HUMAN_FIELD_OUTPUT_TYPE[humanFieldType(f.type)],
11666
+ description: f.label
11667
+ })),
11549
11668
  aliases: ["user_input", "@fancy/user_input"],
11550
11669
  pausesForHuman: "input",
11551
11670
  category: "human",
@@ -11572,14 +11691,38 @@ var KINDS = [
11572
11691
  key: "type",
11573
11692
  label: "Type",
11574
11693
  default: "text",
11694
+ // Every control `HumanPrompt` can render. A type the form supports
11695
+ // but the panel cannot select is reachable only by hand-editing the
11696
+ // workflow JSON — which is the audience this panel exists for.
11575
11697
  options: [
11576
11698
  { value: "text", label: "Text" },
11577
11699
  { value: "textarea", label: "Long text" },
11578
11700
  { value: "number", label: "Number" },
11579
11701
  { value: "select", label: "Select" },
11580
- { value: "switch", label: "Switch" }
11702
+ { value: "switch", label: "Switch (yes / no)" },
11703
+ { value: "date", label: "Date" },
11704
+ { value: "datetime", label: "Date + time" },
11705
+ { value: "time", label: "Time" },
11706
+ { value: "email", label: "Email" },
11707
+ { value: "url", label: "URL" },
11708
+ { value: "tel", label: "Phone" },
11709
+ { value: "password", label: "Password" }
11581
11710
  ]
11582
11711
  },
11712
+ {
11713
+ // Without this a panel-authored select had nothing to choose from,
11714
+ // so it rendered an empty dropdown — the type was declarable and
11715
+ // unusable in the same breath.
11716
+ type: "keyvalue",
11717
+ key: "options",
11718
+ label: "Choices",
11719
+ description: "Select fields only. Left is the value the flow receives, right is what the person sees.",
11720
+ keyLabel: "Value",
11721
+ valueLabel: "Label",
11722
+ keyPlaceholder: "small",
11723
+ valuePlaceholder: "Small",
11724
+ addLabel: "Add choice"
11725
+ },
11583
11726
  { type: "switch", key: "required", label: "Required", default: false }
11584
11727
  ],
11585
11728
  default: [{ key: "answer", label: "Your answer", type: "textarea", required: true }]
@@ -14529,27 +14672,35 @@ function buildNodeTypes() {
14529
14672
  }
14530
14673
  return map;
14531
14674
  }
14532
- function humanInputFields(config) {
14533
- const raw = Array.isArray(config?.fields) ? config.fields : [];
14534
- const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
14535
- key: f.key,
14536
- label: typeof f.label === "string" && f.label ? f.label : f.key,
14537
- type: ["text", "textarea", "number", "select", "switch"].includes(f.type) ? f.type : "text",
14538
- required: !!f.required,
14539
- placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
14540
- options: Array.isArray(f.options) ? f.options : void 0,
14541
- default: f.default
14542
- }));
14543
- if (fields.length) return fields;
14544
- const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
14545
- return [{ key: "value", label: title, type: "textarea", required: true }];
14546
- }
14675
+ var INPUT_TYPE = {
14676
+ text: "text",
14677
+ number: "number",
14678
+ date: "date",
14679
+ datetime: "datetime-local",
14680
+ time: "time",
14681
+ email: "email",
14682
+ url: "url",
14683
+ tel: "tel",
14684
+ password: "password"
14685
+ };
14547
14686
  function initialValues(fields) {
14548
14687
  const v2 = {};
14549
- for (const f of fields) v2[f.key] = f.type === "switch" ? !!f.default : f.default ?? "";
14688
+ for (const f of fields) {
14689
+ if (f.type === "switch") {
14690
+ v2[f.key] = !!f.default;
14691
+ } else if (f.type === "number") {
14692
+ v2[f.key] = typeof f.default === "number" ? f.default : f.default === void 0 || f.default === "" ? "" : Number(f.default);
14693
+ } else {
14694
+ v2[f.key] = f.default ?? "";
14695
+ }
14696
+ }
14550
14697
  return v2;
14551
14698
  }
14552
- function HumanPrompt({ request, onCancel }) {
14699
+ function HumanPrompt({
14700
+ request,
14701
+ onCancel,
14702
+ fieldRenderers
14703
+ }) {
14553
14704
  const firstRef = ReactExports.useRef(null);
14554
14705
  ReactExports.useEffect(() => {
14555
14706
  firstRef.current?.focus();
@@ -14561,7 +14712,7 @@ function HumanPrompt({ request, onCancel }) {
14561
14712
  }, [onCancel]);
14562
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: [
14563
14714
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt__title", children: request.title }),
14564
- 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 })
14565
14716
  ] }) });
14566
14717
  }
14567
14718
  function ApprovalBody({ request, onCancel }) {
@@ -14577,10 +14728,13 @@ function ApprovalBody({ request, onCancel }) {
14577
14728
  function InputBody({
14578
14729
  request,
14579
14730
  onCancel,
14580
- firstRef
14731
+ firstRef,
14732
+ fieldRenderers
14581
14733
  }) {
14582
14734
  const [values, setValues] = ReactExports.useState(() => initialValues(request.fields));
14583
14735
  const set3 = (k2, v2) => setValues((prev) => ({ ...prev, [k2]: v2 }));
14736
+ const uid = ReactExports.useId();
14737
+ const fieldId = (key) => `${uid}-${key}`;
14584
14738
  const missing = request.fields.filter((f) => f.required && (values[f.key] === void 0 || values[f.key] === ""));
14585
14739
  const submit = () => {
14586
14740
  if (missing.length === 0) request.resolve(values);
@@ -14599,12 +14753,22 @@ function InputBody({
14599
14753
  }
14600
14754
  },
14601
14755
  children: [
14602
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt__fields", children: request.fields.map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ff-prompt__field", children: [
14603
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ff-prompt__label", children: [
14756
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt__fields", children: request.fields.map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-prompt__field", children: [
14757
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ff-prompt__label", htmlFor: fieldId(f.key), children: [
14604
14758
  f.label ?? f.key,
14605
- f.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-prompt__req", children: " *" })
14759
+ f.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-prompt__req", "aria-hidden": "true", children: " *" })
14606
14760
  ] }),
14607
- renderControl(f, values[f.key], (v2) => set3(f.key, v2), i === 0 ? firstRef : void 0)
14761
+ /* @__PURE__ */ jsxRuntime.jsx(
14762
+ FieldControl,
14763
+ {
14764
+ field: f,
14765
+ id: fieldId(f.key),
14766
+ value: values[f.key],
14767
+ onChange: (v2) => set3(f.key, v2),
14768
+ autoFocusRef: i === 0 ? firstRef : void 0,
14769
+ renderers: fieldRenderers
14770
+ }
14771
+ )
14608
14772
  ] }, f.key)) }),
14609
14773
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-prompt__actions", children: [
14610
14774
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--ghost", onClick: onCancel, children: "Cancel" }),
@@ -14614,45 +14778,74 @@ function InputBody({
14614
14778
  }
14615
14779
  );
14616
14780
  }
14617
- function renderControl(f, value, onChange, ref) {
14618
- const common = { className: "ff-prompt__input", placeholder: f.placeholder };
14619
- if (f.type === "textarea") {
14781
+ function FieldControl({
14782
+ field,
14783
+ id: id2,
14784
+ value,
14785
+ onChange,
14786
+ autoFocusRef,
14787
+ renderers
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
+ }
14794
+ const handle = { id: id2, "data-ff-field": field.key };
14795
+ if (field.type === "textarea") {
14620
14796
  return /* @__PURE__ */ jsxRuntime.jsx(
14621
14797
  "textarea",
14622
14798
  {
14623
- ...common,
14624
- ref,
14799
+ ...handle,
14800
+ className: "ff-prompt__input",
14801
+ placeholder: field.placeholder,
14802
+ ref: autoFocusRef,
14625
14803
  rows: 3,
14626
14804
  value: String(value ?? ""),
14627
14805
  onChange: (e) => onChange(e.target.value)
14628
14806
  }
14629
14807
  );
14630
14808
  }
14631
- if (f.type === "switch") {
14809
+ if (field.type === "switch") {
14632
14810
  return /* @__PURE__ */ jsxRuntime.jsx(
14633
14811
  "input",
14634
14812
  {
14813
+ ...handle,
14635
14814
  type: "checkbox",
14636
14815
  className: "ff-prompt__switch",
14816
+ ref: autoFocusRef,
14637
14817
  checked: !!value,
14638
14818
  onChange: (e) => onChange(e.target.checked)
14639
14819
  }
14640
14820
  );
14641
14821
  }
14642
- if (f.type === "select" && f.options && f.options.length) {
14643
- return /* @__PURE__ */ jsxRuntime.jsxs("select", { className: "ff-prompt__input", value: String(value ?? ""), onChange: (e) => onChange(e.target.value), children: [
14644
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Choose\u2026" }),
14645
- f.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
14646
- ] });
14822
+ if (field.type === "select") {
14823
+ const options = field.options ?? [];
14824
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14825
+ "select",
14826
+ {
14827
+ ...handle,
14828
+ className: "ff-prompt__input",
14829
+ ref: autoFocusRef,
14830
+ value: String(value ?? ""),
14831
+ onChange: (e) => onChange(e.target.value),
14832
+ children: [
14833
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: options.length ? "Choose\u2026" : "No choices configured" }),
14834
+ options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
14835
+ ]
14836
+ }
14837
+ );
14647
14838
  }
14648
14839
  return /* @__PURE__ */ jsxRuntime.jsx(
14649
14840
  "input",
14650
14841
  {
14651
- ...common,
14652
- ref,
14653
- type: f.type === "number" ? "number" : "text",
14842
+ ...handle,
14843
+ className: "ff-prompt__input",
14844
+ placeholder: field.placeholder,
14845
+ ref: autoFocusRef,
14846
+ type: INPUT_TYPE[field.type ?? "text"] ?? "text",
14654
14847
  value: String(value ?? ""),
14655
- onChange: (e) => onChange(f.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
14848
+ onChange: (e) => onChange(field.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
14656
14849
  }
14657
14850
  );
14658
14851
  }
@@ -14685,6 +14878,7 @@ function FlowEditorInner({
14685
14878
  onDelete,
14686
14879
  onEdgeDelete,
14687
14880
  confirmDelete,
14881
+ humanFieldRenderers,
14688
14882
  apiRef
14689
14883
  }) {
14690
14884
  const internal = useFlowState(initial);
@@ -15221,10 +15415,17 @@ function FlowEditorInner({
15221
15415
  }
15222
15416
  ),
15223
15417
  showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsxRuntime.jsx(FlowRunFeed, { entries: runner.feed, running: api.running, className: "ff-editor__feed" })),
15224
- prompt && /* @__PURE__ */ jsxRuntime.jsx(HumanPrompt, { request: prompt, onCancel: () => {
15225
- setPrompt(null);
15226
- runner.cancel();
15227
- } })
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
+ )
15228
15429
  ] }),
15229
15430
  showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-editor__panel-wrap", children: [
15230
15431
  /* @__PURE__ */ jsxRuntime.jsx(