@particle-academy/fancy-flow 0.40.4 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { a as ConfigField } from './types-cAdD1Og2.cjs';
4
+
5
+ /** What a host renderer is handed. */
6
+ type ConfigFieldRenderContext = {
7
+ field: ConfigField;
8
+ value: unknown;
9
+ onChange: (next: unknown) => void;
10
+ /** The id the panel's `<label>` points at. Put it on your control. */
11
+ id?: string;
12
+ };
13
+ /**
14
+ * Render one field. Return `null` to fall back to the package's own rendering,
15
+ * so a host can claim a type conditionally instead of reimplementing every case.
16
+ *
17
+ * Named `...Fn` because `ConfigFieldRenderer` is already the component this
18
+ * module exports; two things with one name in a public API is a paper cut a
19
+ * consumer pays for, not us.
20
+ */
21
+ type ConfigFieldRenderFn = (ctx: ConfigFieldRenderContext) => ReactNode;
22
+ type ConfigFieldRendererProps = {
23
+ field: ConfigField;
24
+ value: unknown;
25
+ onChange: (value: unknown) => void;
26
+ /**
27
+ * DOM id for the control this renders.
28
+ *
29
+ * Supplied by the caller rather than generated here so the panel's `<label>`
30
+ * can point at it — a label beside a control is not a label attached to one.
31
+ * Also the agent-facing handle: the Human+ contract asks that every
32
+ * interactive element have a stable identity so an agent targets it instead
33
+ * of guessing at the DOM, and until this existed nothing in the editor had
34
+ * one.
35
+ */
36
+ id?: string;
37
+ renderCredentialField?: (props: {
38
+ credentialType: string;
39
+ value: unknown;
40
+ onChange: (next: unknown) => void;
41
+ }) => ReactNode;
42
+ /**
43
+ * Editor for `document` fields. fancy-flow stores the document but never
44
+ * interprets it — the host owns the editing surface, exactly as it does for
45
+ * `credential`. This is the seam for rich human-input steps (authored pages,
46
+ * required reading, multi-section forms) without the package depending on
47
+ * any particular document model.
48
+ */
49
+ renderDocumentField?: (props: {
50
+ documentType?: string;
51
+ value: unknown;
52
+ onChange: (next: unknown) => void;
53
+ }) => ReactNode;
54
+ /**
55
+ * Host renderers keyed by field `type`.
56
+ *
57
+ * The generic form of `renderDocumentField` / `renderCredentialField`: those
58
+ * cover two types the package deliberately does not interpret, this covers
59
+ * any type at all — including one the package has never heard of.
60
+ *
61
+ * Without it, a richer field had to be rendered OUTSIDE the panel, so that
62
+ * node's config stopped living where every other field does. An unknown type
63
+ * also fell through to `default:` and rendered nothing, so the schema said the
64
+ * field existed and the panel showed empty space.
65
+ *
66
+ * Consulted BEFORE the built-in switch, so a host can also replace a built-in
67
+ * (react-fancy inputs, say) through the same seam rather than a second one.
68
+ */
69
+ fieldRenderers?: Record<string, ConfigFieldRenderFn>;
70
+ };
71
+ /**
72
+ * ConfigFieldRenderer — dispatches to the right input element per field type.
73
+ *
74
+ * Plain HTML inputs styled via the package's CSS so the package stays
75
+ * standalone (no react-fancy import required) and every surface stays themeable
76
+ * through the `--ff-*` token layer. Hosts that want react-fancy form components
77
+ * can supply their own field renderers via the kind's `renderPanel`.
78
+ *
79
+ * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
80
+ * the field, so a label can point at it and an agent can find it by name.
81
+ */
82
+ declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, fieldRenderers, }: ConfigFieldRendererProps): react.JSX.Element | null;
83
+
84
+ export { type ConfigFieldRenderFn as C, type ConfigFieldRenderContext as a, ConfigFieldRenderer as b, type ConfigFieldRendererProps as c };
@@ -0,0 +1,84 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { a as ConfigField } from './types-Cf75IbxY.js';
4
+
5
+ /** What a host renderer is handed. */
6
+ type ConfigFieldRenderContext = {
7
+ field: ConfigField;
8
+ value: unknown;
9
+ onChange: (next: unknown) => void;
10
+ /** The id the panel's `<label>` points at. Put it on your control. */
11
+ id?: string;
12
+ };
13
+ /**
14
+ * Render one field. Return `null` to fall back to the package's own rendering,
15
+ * so a host can claim a type conditionally instead of reimplementing every case.
16
+ *
17
+ * Named `...Fn` because `ConfigFieldRenderer` is already the component this
18
+ * module exports; two things with one name in a public API is a paper cut a
19
+ * consumer pays for, not us.
20
+ */
21
+ type ConfigFieldRenderFn = (ctx: ConfigFieldRenderContext) => ReactNode;
22
+ type ConfigFieldRendererProps = {
23
+ field: ConfigField;
24
+ value: unknown;
25
+ onChange: (value: unknown) => void;
26
+ /**
27
+ * DOM id for the control this renders.
28
+ *
29
+ * Supplied by the caller rather than generated here so the panel's `<label>`
30
+ * can point at it — a label beside a control is not a label attached to one.
31
+ * Also the agent-facing handle: the Human+ contract asks that every
32
+ * interactive element have a stable identity so an agent targets it instead
33
+ * of guessing at the DOM, and until this existed nothing in the editor had
34
+ * one.
35
+ */
36
+ id?: string;
37
+ renderCredentialField?: (props: {
38
+ credentialType: string;
39
+ value: unknown;
40
+ onChange: (next: unknown) => void;
41
+ }) => ReactNode;
42
+ /**
43
+ * Editor for `document` fields. fancy-flow stores the document but never
44
+ * interprets it — the host owns the editing surface, exactly as it does for
45
+ * `credential`. This is the seam for rich human-input steps (authored pages,
46
+ * required reading, multi-section forms) without the package depending on
47
+ * any particular document model.
48
+ */
49
+ renderDocumentField?: (props: {
50
+ documentType?: string;
51
+ value: unknown;
52
+ onChange: (next: unknown) => void;
53
+ }) => ReactNode;
54
+ /**
55
+ * Host renderers keyed by field `type`.
56
+ *
57
+ * The generic form of `renderDocumentField` / `renderCredentialField`: those
58
+ * cover two types the package deliberately does not interpret, this covers
59
+ * any type at all — including one the package has never heard of.
60
+ *
61
+ * Without it, a richer field had to be rendered OUTSIDE the panel, so that
62
+ * node's config stopped living where every other field does. An unknown type
63
+ * also fell through to `default:` and rendered nothing, so the schema said the
64
+ * field existed and the panel showed empty space.
65
+ *
66
+ * Consulted BEFORE the built-in switch, so a host can also replace a built-in
67
+ * (react-fancy inputs, say) through the same seam rather than a second one.
68
+ */
69
+ fieldRenderers?: Record<string, ConfigFieldRenderFn>;
70
+ };
71
+ /**
72
+ * ConfigFieldRenderer — dispatches to the right input element per field type.
73
+ *
74
+ * Plain HTML inputs styled via the package's CSS so the package stays
75
+ * standalone (no react-fancy import required) and every surface stays themeable
76
+ * through the `--ff-*` token layer. Hosts that want react-fancy form components
77
+ * can supply their own field renderers via the kind's `renderPanel`.
78
+ *
79
+ * Each control carries the caller's `id` and a `data-ff-field` handle keyed by
80
+ * the field, so a label can point at it and an agent can find it by name.
81
+ */
82
+ declare function ConfigFieldRenderer({ field, value, onChange, id, renderCredentialField, renderDocumentField, fieldRenderers, }: ConfigFieldRendererProps): react.JSX.Element | null;
83
+
84
+ export { type ConfigFieldRenderFn as C, type ConfigFieldRenderContext as a, ConfigFieldRenderer as b, type ConfigFieldRendererProps as c };
package/dist/engine.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, R as RunOptions, d as RunResult, r as runCohort, e as runFlow } from './run-cohort-Bjv2ZU6g.cjs';
2
2
  import { F as FlowGraph, R as RunEvent, N as NodeExecutor } from './types-sOmpCitB.cjs';
3
3
  export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, a as FlowEdge, b as FlowNode, c as FlowNodeData, d as FlowNodeKind, e as NodeRunStatus, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.cjs';
4
- export { d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, r as registerNodeKind, a as resolveKindId, v as validateConfig } from './registry-BADFn97n.cjs';
5
- import { P as PauseAwaiting } from './types-D8Dezh9v.cjs';
6
- export { C as CapabilityRequirement, a as ConfigField, L as LEGACY_PAUSE_PREFIXES, M as ManifestProblem, b as ManifestValidation, N as NODE_MANIFEST_SCHEMA_VERSION, c as NodeCategory, d as NodeKindDefinition, e as NodePackageManifest, f as NodeRuntimeId, g as NodeRuntimeSpec, h as PAUSE_PREFIX, i as PauseSignal, j as PortSpec, S as SideEffects, k as checkCapabilities, l as checkRuntimeSupport, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman, s as satisfiesRange, v as validateNodeManifest } from './types-D8Dezh9v.cjs';
4
+ export { d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, r as registerNodeKind, a as resolveKindId, v as validateConfig } from './registry-BNyT66SS.cjs';
5
+ import { P as PauseAwaiting } from './types-cAdD1Og2.cjs';
6
+ export { C as CapabilityRequirement, a as ConfigField, L as LEGACY_PAUSE_PREFIXES, M as ManifestProblem, b as ManifestValidation, N as NODE_MANIFEST_SCHEMA_VERSION, c as NodeCategory, d as NodeKindDefinition, e as NodePackageManifest, f as NodeRuntimeId, g as NodeRuntimeSpec, h as PAUSE_PREFIX, i as PauseSignal, j as PortSpec, S as SideEffects, k as checkCapabilities, l as checkRuntimeSupport, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman, s as satisfiesRange, v as validateNodeManifest } from './types-cAdD1Og2.cjs';
7
7
  import '@xyflow/react';
8
8
  import 'react';
9
9
 
package/dist/engine.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, R as RunOptions, d as RunResult, r as runCohort, e as runFlow } from './run-cohort-BnP6ErHi.js';
2
2
  import { F as FlowGraph, R as RunEvent, N as NodeExecutor } from './types-sOmpCitB.js';
3
3
  export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, E as ExecutorRegistry, a as FlowEdge, b as FlowNode, c as FlowNodeData, d as FlowNodeKind, e as NodeRunStatus, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.js';
4
- export { d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, r as registerNodeKind, a as resolveKindId, v as validateConfig } from './registry-DYbFLEcW.js';
5
- import { P as PauseAwaiting } from './types-D6lsIGoe.js';
6
- export { C as CapabilityRequirement, a as ConfigField, L as LEGACY_PAUSE_PREFIXES, M as ManifestProblem, b as ManifestValidation, N as NODE_MANIFEST_SCHEMA_VERSION, c as NodeCategory, d as NodeKindDefinition, e as NodePackageManifest, f as NodeRuntimeId, g as NodeRuntimeSpec, h as PAUSE_PREFIX, i as PauseSignal, j as PortSpec, S as SideEffects, k as checkCapabilities, l as checkRuntimeSupport, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman, s as satisfiesRange, v as validateNodeManifest } from './types-D6lsIGoe.js';
4
+ export { d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, r as registerNodeKind, a as resolveKindId, v as validateConfig } from './registry-CS7kjMto.js';
5
+ import { P as PauseAwaiting } from './types-Cf75IbxY.js';
6
+ export { C as CapabilityRequirement, a as ConfigField, L as LEGACY_PAUSE_PREFIXES, M as ManifestProblem, b as ManifestValidation, N as NODE_MANIFEST_SCHEMA_VERSION, c as NodeCategory, d as NodeKindDefinition, e as NodePackageManifest, f as NodeRuntimeId, g as NodeRuntimeSpec, h as PAUSE_PREFIX, i as PauseSignal, j as PortSpec, S as SideEffects, k as checkCapabilities, l as checkRuntimeSupport, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman, s as satisfiesRange, v as validateNodeManifest } from './types-Cf75IbxY.js';
7
7
  import '@xyflow/react';
8
8
  import 'react';
9
9
 
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var reactFancy = require('@particle-academy/react-fancy');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/fields/react-fancy.tsx
7
+ var jsonFieldRenderer = ({
8
+ field,
9
+ value,
10
+ onChange,
11
+ id
12
+ }) => {
13
+ if (field.type !== "json") return null;
14
+ return (
15
+ // JSX rather than `createElement` for one concrete reason: `data-*` props
16
+ // are special-cased by JSX but hit excess-property checking in a
17
+ // `createElement` object literal, so the agent handle would not typecheck.
18
+ /* @__PURE__ */ jsxRuntime.jsx(
19
+ reactFancy.JsonEditor,
20
+ {
21
+ id,
22
+ "data-ff-field": field.key,
23
+ value: value ?? {},
24
+ onChange: (next) => onChange(next),
25
+ keyMap: field.keyMap,
26
+ mode: "edit",
27
+ size: "sm",
28
+ rootLabel: field.label
29
+ }
30
+ )
31
+ );
32
+ };
33
+ var reactFancyFieldRenderers = {
34
+ json: jsonFieldRenderer
35
+ };
36
+
37
+ exports.jsonFieldRenderer = jsonFieldRenderer;
38
+ exports.reactFancyFieldRenderers = reactFancyFieldRenderers;
39
+ //# sourceMappingURL=react-fancy.cjs.map
40
+ //# sourceMappingURL=react-fancy.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,20 @@
1
+ import { C as ConfigFieldRenderFn } from '../ConfigFieldRenderer-4xK9hzvd.cjs';
2
+ import 'react';
3
+ import '../types-cAdD1Og2.cjs';
4
+ import '@xyflow/react';
5
+ import '../types-sOmpCitB.cjs';
6
+
7
+ /**
8
+ * Render a `json` config field as a react-fancy `JsonEditor`.
9
+ *
10
+ * Returns `null` for every other field type — the seam treats `null` as "not
11
+ * mine", so this can be handed over wholesale without claiming controls it has
12
+ * no business replacing.
13
+ */
14
+ declare const jsonFieldRenderer: ConfigFieldRenderFn;
15
+ /**
16
+ * The full renderer map. Spread it to add your own types alongside.
17
+ */
18
+ declare const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn>;
19
+
20
+ export { jsonFieldRenderer, reactFancyFieldRenderers };
@@ -0,0 +1,20 @@
1
+ import { C as ConfigFieldRenderFn } from '../ConfigFieldRenderer-D2TejNY1.js';
2
+ import 'react';
3
+ import '../types-Cf75IbxY.js';
4
+ import '@xyflow/react';
5
+ import '../types-sOmpCitB.js';
6
+
7
+ /**
8
+ * Render a `json` config field as a react-fancy `JsonEditor`.
9
+ *
10
+ * Returns `null` for every other field type — the seam treats `null` as "not
11
+ * mine", so this can be handed over wholesale without claiming controls it has
12
+ * no business replacing.
13
+ */
14
+ declare const jsonFieldRenderer: ConfigFieldRenderFn;
15
+ /**
16
+ * The full renderer map. Spread it to add your own types alongside.
17
+ */
18
+ declare const reactFancyFieldRenderers: Record<string, ConfigFieldRenderFn>;
19
+
20
+ export { jsonFieldRenderer, reactFancyFieldRenderers };
@@ -0,0 +1,37 @@
1
+ import { JsonEditor } from '@particle-academy/react-fancy';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/fields/react-fancy.tsx
5
+ var jsonFieldRenderer = ({
6
+ field,
7
+ value,
8
+ onChange,
9
+ id
10
+ }) => {
11
+ if (field.type !== "json") return null;
12
+ return (
13
+ // JSX rather than `createElement` for one concrete reason: `data-*` props
14
+ // are special-cased by JSX but hit excess-property checking in a
15
+ // `createElement` object literal, so the agent handle would not typecheck.
16
+ /* @__PURE__ */ jsx(
17
+ JsonEditor,
18
+ {
19
+ id,
20
+ "data-ff-field": field.key,
21
+ value: value ?? {},
22
+ onChange: (next) => onChange(next),
23
+ keyMap: field.keyMap,
24
+ mode: "edit",
25
+ size: "sm",
26
+ rootLabel: field.label
27
+ }
28
+ )
29
+ );
30
+ };
31
+ var reactFancyFieldRenderers = {
32
+ json: jsonFieldRenderer
33
+ };
34
+
35
+ export { jsonFieldRenderer, reactFancyFieldRenderers };
36
+ //# sourceMappingURL=react-fancy.js.map
37
+ //# sourceMappingURL=react-fancy.js.map
@@ -0,0 +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"]}
package/dist/index.cjs CHANGED
@@ -12897,7 +12897,7 @@ function ConfigFieldRenderer({
12897
12897
  }
12898
12898
  );
12899
12899
  case "json":
12900
- return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { value, onChange, rows: field.rows });
12900
+ return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { value, onChange, rows: field.rows, handle });
12901
12901
  case "expression":
12902
12902
  return /* @__PURE__ */ jsxRuntime.jsx(
12903
12903
  "textarea",
@@ -12929,6 +12929,7 @@ function ConfigFieldRenderer({
12929
12929
  return /* @__PURE__ */ jsxRuntime.jsx(
12930
12930
  RepeaterField,
12931
12931
  {
12932
+ id: id2,
12932
12933
  field,
12933
12934
  value,
12934
12935
  onChange,
@@ -12938,7 +12939,7 @@ function ConfigFieldRenderer({
12938
12939
  }
12939
12940
  );
12940
12941
  case "keyvalue":
12941
- return /* @__PURE__ */ jsxRuntime.jsx(KeyValueField, { field, value, onChange });
12942
+ return /* @__PURE__ */ jsxRuntime.jsx(KeyValueField, { id: id2, field, value, onChange });
12942
12943
  case "document":
12943
12944
  if (renderDocumentField) {
12944
12945
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderDocumentField({ documentType: field.documentType, value, onChange }) });
@@ -12983,6 +12984,7 @@ function ChoiceField({
12983
12984
  );
12984
12985
  }
12985
12986
  function RepeaterField({
12987
+ id: id2,
12986
12988
  field,
12987
12989
  value,
12988
12990
  onChange,
@@ -13017,7 +13019,7 @@ function RepeaterField({
13017
13019
  if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
13018
13020
  return `Item ${i + 1}`;
13019
13021
  };
13020
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater", "data-ff-repeater": field.key, children: [
13022
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: id2, role: "group", className: "ff-repeater", "data-ff-repeater": field.key, "data-ff-field": field.key, children: [
13021
13023
  rows.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ff-repeater__empty", children: "None yet." }),
13022
13024
  rows.map((row, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater__row", "data-ff-repeater-row": i, children: [
13023
13025
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-repeater__row-head", children: [
@@ -13092,6 +13094,7 @@ function RepeaterField({
13092
13094
  ] });
13093
13095
  }
13094
13096
  function KeyValueField({
13097
+ id: id2,
13095
13098
  field,
13096
13099
  value,
13097
13100
  onChange
@@ -13110,7 +13113,7 @@ function KeyValueField({
13110
13113
  const setVal = (i, val) => commit(entries.map(([k2, v2], idx) => idx === i ? [k2, val] : [k2, v2]));
13111
13114
  const remove2 = (i) => commit(entries.filter((_2, idx) => idx !== i));
13112
13115
  const add = () => commit([...entries, ["", ""]]);
13113
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-keyvalue", "data-ff-keyvalue": field.key, children: [
13116
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: id2, role: "group", className: "ff-keyvalue", "data-ff-keyvalue": field.key, "data-ff-field": field.key, children: [
13114
13117
  entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-keyvalue__head", children: [
13115
13118
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: field.keyLabel ?? "Key" }),
13116
13119
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: field.valueLabel ?? "Value" }),
@@ -13166,34 +13169,76 @@ function KeyValueField({
13166
13169
  ] })
13167
13170
  ] });
13168
13171
  }
13169
- function JsonField({ value, onChange, rows }) {
13170
- const initial = ReactExports.useMemo(() => {
13172
+ function JsonField({
13173
+ value,
13174
+ onChange,
13175
+ rows,
13176
+ handle
13177
+ }) {
13178
+ const serialized = ReactExports.useMemo(() => {
13171
13179
  try {
13172
13180
  return value === void 0 ? "" : JSON.stringify(value, null, 2);
13173
13181
  } catch {
13174
13182
  return "";
13175
13183
  }
13176
13184
  }, [value]);
13177
- return /* @__PURE__ */ jsxRuntime.jsx(
13178
- "textarea",
13179
- {
13180
- className: "ff-panel__input ff-panel__input--json",
13181
- rows: rows ?? 6,
13182
- defaultValue: initial,
13183
- spellCheck: false,
13184
- onBlur: (e) => {
13185
- const text = e.target.value.trim();
13186
- if (text === "") {
13187
- onChange(void 0);
13188
- return;
13189
- }
13190
- try {
13191
- onChange(JSON.parse(text));
13192
- } catch {
13185
+ const [text, setText] = ReactExports.useState(serialized);
13186
+ const [error, setError] = ReactExports.useState(null);
13187
+ const [touched, setTouched] = ReactExports.useState(false);
13188
+ const lastProps = ReactExports.useRef(serialized);
13189
+ const committed = ReactExports.useRef(null);
13190
+ if (serialized !== lastProps.current) {
13191
+ lastProps.current = serialized;
13192
+ if (serialized !== committed.current) {
13193
+ setText(serialized);
13194
+ setError(null);
13195
+ setTouched(false);
13196
+ }
13197
+ }
13198
+ const commit = (next) => {
13199
+ const trimmed = next.trim();
13200
+ if (trimmed === "") {
13201
+ committed.current = "";
13202
+ onChange(void 0);
13203
+ return null;
13204
+ }
13205
+ try {
13206
+ const parsed = JSON.parse(trimmed);
13207
+ committed.current = JSON.stringify(parsed, null, 2);
13208
+ onChange(parsed);
13209
+ return null;
13210
+ } catch (e) {
13211
+ return e instanceof Error ? e.message : "Invalid JSON";
13212
+ }
13213
+ };
13214
+ const errorId = handle.id ? `${handle.id}-error` : void 0;
13215
+ const showError = touched && error !== null;
13216
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
13217
+ /* @__PURE__ */ jsxRuntime.jsx(
13218
+ "textarea",
13219
+ {
13220
+ ...handle,
13221
+ className: "ff-panel__input ff-panel__input--json",
13222
+ rows: rows ?? 6,
13223
+ value: text,
13224
+ spellCheck: false,
13225
+ "aria-invalid": showError || void 0,
13226
+ "aria-describedby": showError ? errorId : void 0,
13227
+ onChange: (e) => {
13228
+ setText(e.target.value);
13229
+ setError(commit(e.target.value));
13230
+ },
13231
+ onBlur: (e) => {
13232
+ setTouched(true);
13233
+ setError(commit(e.target.value));
13193
13234
  }
13194
13235
  }
13195
- }
13196
- );
13236
+ ),
13237
+ showError && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "ff-panel__issue ff-panel__issue--field", id: errorId, role: "alert", children: [
13238
+ "\u26A0 ",
13239
+ error
13240
+ ] })
13241
+ ] });
13197
13242
  }
13198
13243
  function NodeConfigPanel({
13199
13244
  node,
@@ -14061,6 +14106,7 @@ function FlowEditorInner({
14061
14106
  metadata,
14062
14107
  showPalette = true,
14063
14108
  showPanel = true,
14109
+ fieldRenderers,
14064
14110
  showFeed = true,
14065
14111
  extraToolbar,
14066
14112
  actions = [],
@@ -14620,6 +14666,7 @@ function FlowEditorInner({
14620
14666
  className: "ff-editor__panel",
14621
14667
  node: api.selected,
14622
14668
  onChange: api.updateNode,
14669
+ fieldRenderers,
14623
14670
  onDelete: builtins.delete === false ? void 0 : (n) => api.deleteNodes([n.id])
14624
14671
  }
14625
14672
  ),