@particle-academy/fancy-flow 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -5,19 +5,19 @@ import { ConnectionValidatorOptions } from './registry/index.cjs';
5
5
  export { ANY_PORT_TYPE, BUILTIN_KINDS, PortCompatibility, RegistryNode, RichInputAdapter, RichInputPreview, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerBuiltinKinds, registerRichInputAdapter, resolveNodePorts, resolvePortSpec } from './registry/index.cjs';
6
6
  import { b as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-sOmpCitB.cjs';
7
7
  export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, a as FlowEdge, c as FlowNodeData, d as FlowNodeKind, N as NodeExecutor, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.cjs';
8
- import { C as ConfigFieldRenderFn } from './ConfigFieldRenderer-4xK9hzvd.cjs';
9
- export { a as ConfigFieldRenderContext, b as ConfigFieldRenderer, c as ConfigFieldRendererProps } from './ConfigFieldRenderer-4xK9hzvd.cjs';
8
+ import { C as ConfigFieldRenderFn } from './ConfigFieldRenderer-LotNHeG8.cjs';
9
+ export { a as ConfigFieldRenderContext, b as ConfigFieldRenderer, c as ConfigFieldRendererProps } from './ConfigFieldRenderer-LotNHeG8.cjs';
10
10
  import { WorkflowSchema, WorkflowMetadata } from './schema/index.cjs';
11
11
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.cjs';
12
12
  import { AutoLayoutOptions } from './layout/index.cjs';
13
13
  export { AutoLayoutDirection } from './layout/index.cjs';
14
- import { c as NodeCategory, d as NodeKindDefinition } from './types-cAdD1Og2.cjs';
15
- export { a as ConfigField, D as DocumentConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from './types-cAdD1Og2.cjs';
14
+ import { c as NodeCategory, d as NodeKindDefinition } from './types-B78Rm69S.cjs';
15
+ export { A as AvailableVariable, a as ConfigField, D as DocumentConfigField, z as ExpressionForm, B as ExpressionGrammarHelp, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, O as OutputField, F as OutputShape, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, G as availableVariables, H as baseVariables, m as decodePause, I as describeExpressionGrammar, n as encodePause, o as isPause, Q as outputFieldsFor, p as pauseForHuman } from './types-B78Rm69S.cjs';
16
16
  export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-nLqo7s2E.cjs';
17
17
  import { FlowRunFeedEntry } from './runtime/index.cjs';
18
18
  export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
19
19
  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';
20
- export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-BNyT66SS.cjs';
20
+ export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-C9KMVpn7.cjs';
21
21
  import './capabilities-BtJQTbnw.cjs';
22
22
 
23
23
  type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" | "height"> & {
@@ -98,6 +98,13 @@ type NodeConfigPanelProps = {
98
98
  */
99
99
  /** Host renderers keyed by field `type`. See {@link ConfigFieldRenderer}. */
100
100
  fieldRenderers?: Record<string, ConfigFieldRenderFn>;
101
+ /**
102
+ * The graph the node lives in. Supplying it is what makes the `{{ }}` variable
103
+ * picker context-aware — it reads the UPSTREAM node's declared `outputShape`
104
+ * to list what is actually reachable here (issue #5). Without it the picker
105
+ * still opens, but can only offer the whole input.
106
+ */
107
+ graph?: FlowGraph;
101
108
  renderDocumentField?: (props: {
102
109
  documentType?: string;
103
110
  value: unknown;
@@ -110,7 +117,7 @@ type NodeConfigPanelProps = {
110
117
  * NodeConfigPanel — schema-driven form for the selected node. Defers to
111
118
  * `kind.renderPanel` if the kind opts out of the auto-form.
112
119
  */
113
- declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, className, style, }: NodeConfigPanelProps): react.JSX.Element;
120
+ declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, graph, className, style, }: NodeConfigPanelProps): react.JSX.Element;
114
121
 
115
122
  /**
116
123
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
@@ -620,4 +627,38 @@ declare const flowKeys: {
620
627
  readonly run: (runId: string) => readonly ["flow", "runs", string];
621
628
  };
622
629
 
623
- export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigFieldRenderFn, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
630
+ type ExpressionFieldProps = {
631
+ value: string;
632
+ onChange: (next: string) => void;
633
+ placeholder?: string;
634
+ rows?: number;
635
+ /** The panel's field id + agent handle. */
636
+ handle: {
637
+ id?: string;
638
+ "data-ff-field": string;
639
+ };
640
+ /**
641
+ * The graph and the node being edited. Both are needed to answer "what can I
642
+ * write here" — without them the picker can still offer `{{ $json }}` but
643
+ * nothing node-specific, which is the static list issue #5 asked us not to
644
+ * ship.
645
+ */
646
+ graph?: FlowGraph;
647
+ nodeId?: string;
648
+ };
649
+ /**
650
+ * ExpressionField — a `{{ }}` field that tells the author what it accepts.
651
+ *
652
+ * Issue #5: the panel rendered a bare textarea with a placeholder and nothing
653
+ * else, so writing an expression required already knowing both the grammar and
654
+ * the exact shape of the upstream node's output. Two affordances close that:
655
+ * typing `{{ ` opens a picker of the variables actually reachable at THIS node,
656
+ * and a reference toggle explains the grammar.
657
+ *
658
+ * The picker deliberately lists only what the resolver can reach — see
659
+ * `availableVariables`. A suggestion that resolves to `null` at runtime is
660
+ * worse than no suggestion, because the author has no reason to doubt it.
661
+ */
662
+ declare function ExpressionField({ value, onChange, placeholder, rows, handle, graph, nodeId, }: ExpressionFieldProps): react.JSX.Element;
663
+
664
+ export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigFieldRenderFn, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, ExpressionField, type ExpressionFieldProps, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.d.ts CHANGED
@@ -5,19 +5,19 @@ import { ConnectionValidatorOptions } from './registry/index.js';
5
5
  export { ANY_PORT_TYPE, BUILTIN_KINDS, PortCompatibility, RegistryNode, RichInputAdapter, RichInputPreview, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerBuiltinKinds, registerRichInputAdapter, resolveNodePorts, resolvePortSpec } from './registry/index.js';
6
6
  import { b as FlowNode, F as FlowGraph, e as NodeRunStatus, E as ExecutorRegistry } from './types-sOmpCitB.js';
7
7
  export { A as ActionNodeData, B as BaseNodeData, D as DecisionNodeData, a as FlowEdge, c as FlowNodeData, d as FlowNodeKind, N as NodeExecutor, f as NoteNodeData, O as OutputNodeData, P as PortDescriptor, R as RunEvent, S as SubgraphNodeData, T as TriggerNodeData } from './types-sOmpCitB.js';
8
- import { C as ConfigFieldRenderFn } from './ConfigFieldRenderer-D2TejNY1.js';
9
- export { a as ConfigFieldRenderContext, b as ConfigFieldRenderer, c as ConfigFieldRendererProps } from './ConfigFieldRenderer-D2TejNY1.js';
8
+ import { C as ConfigFieldRenderFn } from './ConfigFieldRenderer-BHQDZer6.js';
9
+ export { a as ConfigFieldRenderContext, b as ConfigFieldRenderer, c as ConfigFieldRendererProps } from './ConfigFieldRenderer-BHQDZer6.js';
10
10
  import { WorkflowSchema, WorkflowMetadata } from './schema/index.js';
11
11
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.js';
12
12
  import { AutoLayoutOptions } from './layout/index.js';
13
13
  export { AutoLayoutDirection } from './layout/index.js';
14
- import { c as NodeCategory, d as NodeKindDefinition } from './types-Cf75IbxY.js';
15
- export { a as ConfigField, D as DocumentConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from './types-Cf75IbxY.js';
14
+ import { c as NodeCategory, d as NodeKindDefinition } from './types-JViq8reb.js';
15
+ export { A as AvailableVariable, a as ConfigField, D as DocumentConfigField, z as ExpressionForm, B as ExpressionGrammarHelp, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, O as OutputField, F as OutputShape, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, j as PortSpec, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, G as availableVariables, H as baseVariables, m as decodePause, I as describeExpressionGrammar, n as encodePause, o as isPause, Q as outputFieldsFor, p as pauseForHuman } from './types-JViq8reb.js';
16
16
  export { F as FlowNodeStatus, a as FlowViewer, b as FlowViewerClassNames, c as FlowViewerProps } from './FlowViewer-C4QAVgWv.js';
17
17
  import { FlowRunFeedEntry } from './runtime/index.js';
18
18
  export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
19
19
  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';
20
- export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-CS7kjMto.js';
20
+ export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, v as validateConfig } from './registry-FGCBprcn.js';
21
21
  import './capabilities-CkhKj_U_.js';
22
22
 
23
23
  type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" | "height"> & {
@@ -98,6 +98,13 @@ type NodeConfigPanelProps = {
98
98
  */
99
99
  /** Host renderers keyed by field `type`. See {@link ConfigFieldRenderer}. */
100
100
  fieldRenderers?: Record<string, ConfigFieldRenderFn>;
101
+ /**
102
+ * The graph the node lives in. Supplying it is what makes the `{{ }}` variable
103
+ * picker context-aware — it reads the UPSTREAM node's declared `outputShape`
104
+ * to list what is actually reachable here (issue #5). Without it the picker
105
+ * still opens, but can only offer the whole input.
106
+ */
107
+ graph?: FlowGraph;
101
108
  renderDocumentField?: (props: {
102
109
  documentType?: string;
103
110
  value: unknown;
@@ -110,7 +117,7 @@ type NodeConfigPanelProps = {
110
117
  * NodeConfigPanel — schema-driven form for the selected node. Defers to
111
118
  * `kind.renderPanel` if the kind opts out of the auto-form.
112
119
  */
113
- declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, className, style, }: NodeConfigPanelProps): react.JSX.Element;
120
+ declare function NodeConfigPanel({ node, onChange, onDelete, deleteLabel, header, renderCredentialField, renderDocumentField, fieldRenderers, graph, className, style, }: NodeConfigPanelProps): react.JSX.Element;
114
121
 
115
122
  /**
116
123
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
@@ -620,4 +627,38 @@ declare const flowKeys: {
620
627
  readonly run: (runId: string) => readonly ["flow", "runs", string];
621
628
  };
622
629
 
623
- export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigFieldRenderFn, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
630
+ type ExpressionFieldProps = {
631
+ value: string;
632
+ onChange: (next: string) => void;
633
+ placeholder?: string;
634
+ rows?: number;
635
+ /** The panel's field id + agent handle. */
636
+ handle: {
637
+ id?: string;
638
+ "data-ff-field": string;
639
+ };
640
+ /**
641
+ * The graph and the node being edited. Both are needed to answer "what can I
642
+ * write here" — without them the picker can still offer `{{ $json }}` but
643
+ * nothing node-specific, which is the static list issue #5 asked us not to
644
+ * ship.
645
+ */
646
+ graph?: FlowGraph;
647
+ nodeId?: string;
648
+ };
649
+ /**
650
+ * ExpressionField — a `{{ }}` field that tells the author what it accepts.
651
+ *
652
+ * Issue #5: the panel rendered a bare textarea with a placeholder and nothing
653
+ * else, so writing an expression required already knowing both the grammar and
654
+ * the exact shape of the upstream node's output. Two affordances close that:
655
+ * typing `{{ ` opens a picker of the variables actually reachable at THIS node,
656
+ * and a reference toggle explains the grammar.
657
+ *
658
+ * The picker deliberately lists only what the resolver can reach — see
659
+ * `availableVariables`. A suggestion that resolves to `null` at runtime is
660
+ * worse than no suggestion, because the author has no reason to doubt it.
661
+ */
662
+ declare function ExpressionField({ value, onChange, placeholder, rows, handle, graph, nodeId, }: ExpressionFieldProps): react.JSX.Element;
663
+
664
+ export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigFieldRenderFn, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, ExpressionField, type ExpressionFieldProps, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, flowKeys, flowLive, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { removeNodes, removeEdges, cloneSubgraph, alignNodes, distributeNodes, reconnectEdge, assignToLane, removeFromLane, duplicateNode, setEdgeLabel, FlowCanvas } from './chunk-WOO4B7M5.js';
2
- export { ActionNode, DecisionNode, FlowCanvas, FlowViewer, NodeShell, OutputNode, SubgraphNode, TriggerNode, alignNodes, cloneSubgraph, defaultNodeTypes, distributeNodes, reconnectEdge } from './chunk-WOO4B7M5.js';
1
+ import { removeNodes, removeEdges, cloneSubgraph, alignNodes, distributeNodes, reconnectEdge, assignToLane, removeFromLane, duplicateNode, setEdgeLabel, FlowCanvas } from './chunk-T3VQX3XB.js';
2
+ export { ActionNode, DecisionNode, FlowCanvas, FlowViewer, NodeShell, OutputNode, SubgraphNode, TriggerNode, alignNodes, cloneSubgraph, defaultNodeTypes, distributeNodes, reconnectEdge } from './chunk-T3VQX3XB.js';
3
3
  import { useFlowState, useFlowRun, useFlowHistory, applyOutputsToNodes, applyStatusesToNodes } from './chunk-A6RFLGWV.js';
4
4
  export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-A6RFLGWV.js';
5
5
  export { runCohort } from './chunk-QZTGV3ZL.js';
6
- import { buildNodeTypes, FlowEditorProvider, registerBuiltinKinds } from './chunk-UX65ML3J.js';
7
- export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional } from './chunk-UX65ML3J.js';
6
+ import { buildNodeTypes, FlowEditorProvider, registerBuiltinKinds } from './chunk-55KQS3LK.js';
7
+ export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional } from './chunk-55KQS3LK.js';
8
8
  import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle } from './chunk-OWENS2H5.js';
9
9
  export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
10
10
  import './chunk-USL4FMFU.js';
@@ -109,6 +109,230 @@ function paletteDropHandlers(onDrop) {
109
109
  }
110
110
  };
111
111
  }
112
+
113
+ // src/expressions/variables.ts
114
+ var ROOT = "$json";
115
+ function expr(path) {
116
+ return `{{ ${path} }}`;
117
+ }
118
+ function outputFieldsFor(node) {
119
+ const kindName = node.data?.kind ?? node.type;
120
+ if (!kindName) return [];
121
+ const kind = getNodeKind(kindName);
122
+ const shape = kind?.outputShape;
123
+ if (!shape) return [];
124
+ if (typeof shape === "function") {
125
+ try {
126
+ const config = node.data?.config ?? {};
127
+ return shape(config) ?? [];
128
+ } catch {
129
+ return [];
130
+ }
131
+ }
132
+ return shape;
133
+ }
134
+ function baseVariables() {
135
+ return [
136
+ { expression: expr(ROOT), path: ROOT, type: "unknown", description: "The whole incoming value." }
137
+ ];
138
+ }
139
+ function availableVariables(graph, nodeId) {
140
+ const out = baseVariables();
141
+ const seen = /* @__PURE__ */ new Set([ROOT]);
142
+ const byId = new Map(graph.nodes.map((n) => [n.id, n]));
143
+ const upstreamIds = graph.edges.filter((e) => e.target === nodeId).map((e) => e.source);
144
+ for (const id of upstreamIds) {
145
+ if (id === nodeId) continue;
146
+ const upstream = byId.get(id);
147
+ if (!upstream) continue;
148
+ const kindName = upstream.data?.kind ?? upstream.type;
149
+ const label = upstream.data?.label ?? (kindName ? getNodeKind(kindName)?.label : void 0);
150
+ for (const field of outputFieldsFor(upstream)) {
151
+ const path = `${ROOT}.${field.path}`;
152
+ if (seen.has(path)) continue;
153
+ seen.add(path);
154
+ out.push({
155
+ expression: expr(path),
156
+ path,
157
+ type: field.type,
158
+ description: field.description,
159
+ source: label
160
+ });
161
+ }
162
+ }
163
+ return out;
164
+ }
165
+ function describeExpressionGrammar() {
166
+ return {
167
+ forms: [
168
+ { syntax: "{{ $json }}", meaning: "The whole value arriving on this node's input." },
169
+ { syntax: "{{ $json.field }}", meaning: "One key of that value. Dot-paths nest: $json.user.email." },
170
+ { syntax: "{{ $input.field }}", meaning: "An alias for $json \u2014 the same value, either spelling." },
171
+ { syntax: "{{ in }}", meaning: "The input by its context key, rather than through the alias." }
172
+ ],
173
+ note: "Dot-paths only \u2014 no arithmetic, comparisons or function calls; anything else resolves to nothing. A field that is exactly one expression keeps the resolved value's type; mixed text interpolates as a string. fancy-flow's own runFlow does not interpolate: it hands config to your executor verbatim, and the fancy-flow-php runtime is what resolves these."
174
+ };
175
+ }
176
+ function findTrigger(text, caret) {
177
+ const before = text.slice(0, caret);
178
+ const open = before.lastIndexOf("{{");
179
+ if (open === -1) return null;
180
+ if (before.indexOf("}}", open) !== -1) return null;
181
+ return { open, query: before.slice(open + 2).trim() };
182
+ }
183
+ function filterVariables(vars, query) {
184
+ const q = query.trim().toLowerCase();
185
+ if (q === "") return vars;
186
+ return vars.filter((v) => v.path.toLowerCase().includes(q));
187
+ }
188
+ function applyCompletion(text, caret, trigger, variable) {
189
+ const head = text.slice(0, trigger.open);
190
+ const tail = text.slice(caret);
191
+ return { value: `${head}${variable.expression}${tail}`, caret: head.length + variable.expression.length };
192
+ }
193
+ function ExpressionField({
194
+ value,
195
+ onChange,
196
+ placeholder,
197
+ rows = 2,
198
+ handle,
199
+ graph,
200
+ nodeId
201
+ }) {
202
+ const ref = useRef(null);
203
+ const [trigger, setTrigger] = useState(null);
204
+ const [active, setActive] = useState(0);
205
+ const [showHelp, setShowHelp] = useState(false);
206
+ const variables = useMemo(
207
+ () => graph && nodeId ? availableVariables(graph, nodeId) : baseVariables(),
208
+ [graph, nodeId]
209
+ );
210
+ const matches = useMemo(
211
+ () => trigger ? filterVariables(variables, trigger.query) : [],
212
+ [trigger, variables]
213
+ );
214
+ const help = useMemo(() => describeExpressionGrammar(), []);
215
+ const open = trigger !== null && matches.length > 0;
216
+ const sync = (el) => {
217
+ const next = findTrigger(el.value, el.selectionStart ?? el.value.length);
218
+ setTrigger(next);
219
+ setActive(0);
220
+ };
221
+ const choose = (variable) => {
222
+ const el = ref.current;
223
+ if (!el || !trigger) return;
224
+ const { value: next, caret } = applyCompletion(
225
+ el.value,
226
+ el.selectionStart ?? el.value.length,
227
+ trigger,
228
+ variable
229
+ );
230
+ onChange(next);
231
+ setTrigger(null);
232
+ requestAnimationFrame(() => {
233
+ el.focus();
234
+ el.setSelectionRange(caret, caret);
235
+ });
236
+ };
237
+ const onKeyDown = (e) => {
238
+ if (!open) return;
239
+ if (e.key === "ArrowDown") {
240
+ e.preventDefault();
241
+ setActive((i) => (i + 1) % matches.length);
242
+ } else if (e.key === "ArrowUp") {
243
+ e.preventDefault();
244
+ setActive((i) => (i - 1 + matches.length) % matches.length);
245
+ } else if (e.key === "Enter" || e.key === "Tab") {
246
+ e.preventDefault();
247
+ choose(matches[active] ?? matches[0]);
248
+ } else if (e.key === "Escape") {
249
+ e.preventDefault();
250
+ setTrigger(null);
251
+ }
252
+ };
253
+ return /* @__PURE__ */ jsxs("div", { className: "ff-expression", "data-ff-expression": handle["data-ff-field"], children: [
254
+ /* @__PURE__ */ jsx(
255
+ "textarea",
256
+ {
257
+ ...handle,
258
+ ref,
259
+ className: "ff-panel__input ff-panel__input--expression",
260
+ rows,
261
+ value: value ?? "",
262
+ placeholder: placeholder ?? "{{ $json.field }}",
263
+ spellCheck: false,
264
+ role: "combobox",
265
+ "aria-expanded": open,
266
+ "aria-autocomplete": "list",
267
+ "aria-controls": open ? `${handle.id ?? handle["data-ff-field"]}-vars` : void 0,
268
+ onChange: (e) => {
269
+ onChange(e.target.value);
270
+ sync(e.target);
271
+ },
272
+ onKeyDown,
273
+ onClick: (e) => sync(e.currentTarget),
274
+ onBlur: () => {
275
+ window.setTimeout(() => setTrigger(null), 120);
276
+ }
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsxs("div", { className: "ff-expression__bar", children: [
280
+ /* @__PURE__ */ jsxs(
281
+ "button",
282
+ {
283
+ type: "button",
284
+ className: "ff-expression__helpbtn",
285
+ "aria-expanded": showHelp,
286
+ onClick: () => setShowHelp((s) => !s),
287
+ children: [
288
+ "{{ }}",
289
+ " reference"
290
+ ]
291
+ }
292
+ ),
293
+ variables.length > 1 && /* @__PURE__ */ jsxs("span", { className: "ff-expression__hint", children: [
294
+ "type ",
295
+ /* @__PURE__ */ jsx("code", { children: "{{" }),
296
+ " for ",
297
+ variables.length,
298
+ " variables"
299
+ ] })
300
+ ] }),
301
+ open && /* @__PURE__ */ jsx(
302
+ "ul",
303
+ {
304
+ className: "ff-expression__menu",
305
+ id: `${handle.id ?? handle["data-ff-field"]}-vars`,
306
+ role: "listbox",
307
+ "data-ff-expression-menu": handle["data-ff-field"],
308
+ children: matches.map((v, i) => /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": i === active, children: /* @__PURE__ */ jsxs(
309
+ "button",
310
+ {
311
+ type: "button",
312
+ className: `ff-expression__option${i === active ? " is-active" : ""}`,
313
+ "data-ff-expression-option": v.path,
314
+ onMouseDown: (e) => {
315
+ e.preventDefault();
316
+ choose(v);
317
+ },
318
+ children: [
319
+ /* @__PURE__ */ jsx("code", { className: "ff-expression__path", children: v.path }),
320
+ v.source && /* @__PURE__ */ jsx("span", { className: "ff-expression__source", children: v.source }),
321
+ v.description && /* @__PURE__ */ jsx("span", { className: "ff-expression__desc", children: v.description })
322
+ ]
323
+ }
324
+ ) }, v.expression))
325
+ }
326
+ ),
327
+ showHelp && /* @__PURE__ */ jsxs("div", { className: "ff-expression__help", role: "note", "data-ff-expression-help": true, children: [
328
+ /* @__PURE__ */ jsx("dl", { children: help.forms.map((f) => /* @__PURE__ */ jsxs("div", { children: [
329
+ /* @__PURE__ */ jsx("dt", { children: /* @__PURE__ */ jsx("code", { children: f.syntax }) }),
330
+ /* @__PURE__ */ jsx("dd", { children: f.meaning })
331
+ ] }, f.syntax)) }),
332
+ /* @__PURE__ */ jsx("p", { className: "ff-expression__note", children: help.note })
333
+ ] })
334
+ ] });
335
+ }
112
336
  function ConfigFieldRenderer({
113
337
  field,
114
338
  value,
@@ -116,7 +340,9 @@ function ConfigFieldRenderer({
116
340
  id,
117
341
  renderCredentialField,
118
342
  renderDocumentField,
119
- fieldRenderers
343
+ fieldRenderers,
344
+ graph,
345
+ nodeId
120
346
  }) {
121
347
  const handle = { id, "data-ff-field": field.key };
122
348
  const custom = fieldRenderers?.[field.type];
@@ -198,15 +424,14 @@ function ConfigFieldRenderer({
198
424
  return /* @__PURE__ */ jsx(JsonField, { value, onChange, rows: field.rows, handle });
199
425
  case "expression":
200
426
  return /* @__PURE__ */ jsx(
201
- "textarea",
427
+ ExpressionField,
202
428
  {
203
- ...handle,
204
- className: "ff-panel__input ff-panel__input--expression",
205
- rows: 2,
429
+ handle,
206
430
  value: value ?? "",
431
+ onChange,
207
432
  placeholder: field.example ?? "{{ $json.field }}",
208
- spellCheck: false,
209
- onChange: (e) => onChange(e.target.value)
433
+ graph,
434
+ nodeId
210
435
  }
211
436
  );
212
437
  case "credential":
@@ -233,7 +458,9 @@ function ConfigFieldRenderer({
233
458
  onChange,
234
459
  renderCredentialField,
235
460
  renderDocumentField,
236
- fieldRenderers
461
+ fieldRenderers,
462
+ graph,
463
+ nodeId
237
464
  }
238
465
  );
239
466
  case "keyvalue":
@@ -286,6 +513,8 @@ function RepeaterField({
286
513
  field,
287
514
  value,
288
515
  onChange,
516
+ graph,
517
+ nodeId,
289
518
  renderCredentialField,
290
519
  renderDocumentField,
291
520
  fieldRenderers
@@ -366,6 +595,8 @@ function RepeaterField({
366
595
  /* @__PURE__ */ jsx(
367
596
  ConfigFieldRenderer,
368
597
  {
598
+ graph,
599
+ nodeId,
369
600
  field: sub,
370
601
  value: row[sub.key],
371
602
  onChange: (cell) => setCell(i, sub.key, cell),
@@ -547,6 +778,7 @@ function NodeConfigPanel({
547
778
  renderCredentialField,
548
779
  renderDocumentField,
549
780
  fieldRenderers,
781
+ graph,
550
782
  className,
551
783
  style
552
784
  }) {
@@ -652,7 +884,9 @@ function NodeConfigPanel({
652
884
  onChange: (v) => setConfigValue(field.key, v),
653
885
  renderCredentialField,
654
886
  renderDocumentField: documentField,
655
- fieldRenderers
887
+ fieldRenderers,
888
+ graph,
889
+ nodeId: node.id
656
890
  }
657
891
  )
658
892
  ] }, field.key))
@@ -1416,6 +1650,7 @@ function FlowEditorInner({
1416
1650
  node: api.selected,
1417
1651
  onChange: api.updateNode,
1418
1652
  fieldRenderers,
1653
+ graph: flow,
1419
1654
  onDelete: builtins.delete === false ? void 0 : (n) => api.deleteNodes([n.id])
1420
1655
  }
1421
1656
  ),
@@ -1591,6 +1826,6 @@ var flowKeys = {
1591
1826
  // src/index.ts
1592
1827
  registerBuiltinKinds();
1593
1828
 
1594
- export { ConfigFieldRenderer, FlowEditor, FlowRunControls, FlowRunFeed, NodeConfigPanel, NodePalette, NodePort, defineNode, flowKeys, flowLive, paletteDropHandlers };
1829
+ export { ConfigFieldRenderer, ExpressionField, FlowEditor, FlowRunControls, FlowRunFeed, NodeConfigPanel, NodePalette, NodePort, availableVariables, baseVariables, defineNode, describeExpressionGrammar, flowKeys, flowLive, outputFieldsFor, paletteDropHandlers };
1595
1830
  //# sourceMappingURL=index.js.map
1596
1831
  //# sourceMappingURL=index.js.map