@burdenoff/fluidgrids-fe-sdk 2026.731.6 → 2026.801.2

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.
@@ -1,10 +1,10 @@
1
1
  import { Dialog as Dialog$1, DialogContent as DialogContent$1, DialogHeader as DialogHeader$1, DialogTitle as DialogTitle$1, DialogDescription as DialogDescription$1, Input as Input$1, DialogFooter } from './chunk-MRVS73S2.js';
2
2
  import { Badge as Badge$1, Button } from './chunk-DBOMRIP5.js';
3
- import { refetchStoreNodes, registerWorkflowNodeTypes, ensureNodeConfigsLoaded, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveWorkflow, STICKY_NOTE_TYPE, useNodeExecution, getNodeDetail, ConnectionSelector, mergeStickyNotes, buildStickyNode, NODES_METADATA, NODE_CONFIGS, isNodeConfigsInitialized, NODES, NodePropertyPanel } from './chunk-IVKT244I.js';
3
+ import { refetchStoreNodes, registerWorkflowNodeTypes, ensureNodeConfigsLoaded, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveWorkflow, STICKY_NOTE_TYPE, useNodeExecution, getNodeDetail, mergeStickyNotes, buildStickyNode, ConnectionSelector, nodeAccent, NODES_METADATA, isNodeConfigsInitialized, NODE_CONFIGS, NODES, NodePropertyPanel, resolveInputHandles } from './chunk-XBMHUJYN.js';
4
4
  import { createGlobalStoreClient, setGlobalStoreClient, resetGlobalStoreClient, getGlobalStoreClient } from './chunk-BQXHJIER.js';
5
5
  import { BrowseWorkflowTemplatesDocument, InstallWorkflowNodeDocument, InstallPurchasedNodeDocument, GetMyNodeInstallationsDocument, BrowseWorkflowNodesDocument, UninstallWorkflowNodeDocument, CreateStoreOrderDocument } from './chunk-PNOA7CQV.js';
6
6
  import { useLazyWorkflowDefinition, useCreateWorkflow, useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useDeactivateWorkflow } from './chunk-NMS2QUXG.js';
7
- import { useFlowStore } from './chunk-KU64CCSV.js';
7
+ import { useFlowStore } from './chunk-WSBRI47P.js';
8
8
  import { SdkPermissionButton, FLUIDGRIDS_PERMISSIONS } from './chunk-UIY2HBWA.js';
9
9
  import { cn } from './chunk-3ZYUGTIT.js';
10
10
  import { throwIfResultError, NodeSchemaIntrospectionDocument, EventRegistrationsDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument } from './chunk-XEYNX7YJ.js';
@@ -2868,6 +2868,57 @@ var UninstallNodeDialog = ({
2868
2868
  ] })
2869
2869
  ] }) });
2870
2870
  };
2871
+
2872
+ // src/builder/hooks/attach-node-helpers.ts
2873
+ var ATTACH_GAP_X = 120;
2874
+ var DEFAULT_NODE_WIDTH = 288;
2875
+ function computeAttachPosition(source) {
2876
+ if (!source) return void 0;
2877
+ const width = source.measured?.width ?? DEFAULT_NODE_WIDTH;
2878
+ return {
2879
+ x: source.position.x + width + ATTACH_GAP_X,
2880
+ y: source.position.y
2881
+ };
2882
+ }
2883
+ function firstInputHandleId(data) {
2884
+ return resolveInputHandles(data)[0]?.id ?? "target";
2885
+ }
2886
+
2887
+ // src/builder/hooks/use-attach-store-node.ts
2888
+ function useAddNodeFromStore() {
2889
+ const { getNode } = useReactFlow();
2890
+ const insertNode = useInsertNode();
2891
+ return useCallback(
2892
+ (nodeType, operationName) => {
2893
+ const store = useFlowStore.getState();
2894
+ const pending = store.workflow.pendingConnection;
2895
+ if (!pending) {
2896
+ insertNode(nodeType, void 0, operationName);
2897
+ return { attached: false };
2898
+ }
2899
+ const source = getNode(pending.nodeId);
2900
+ const created = insertNode(
2901
+ nodeType,
2902
+ computeAttachPosition(source),
2903
+ operationName
2904
+ );
2905
+ if (created?.id && source) {
2906
+ store.actions.edges.onConnect({
2907
+ source: pending.nodeId,
2908
+ sourceHandle: pending.handleId,
2909
+ target: created.id,
2910
+ targetHandle: firstInputHandleId(
2911
+ created.data
2912
+ )
2913
+ });
2914
+ }
2915
+ store.actions.pendingConnection.clear();
2916
+ store.actions.sidebar.setActivePanel("none");
2917
+ return { attached: true };
2918
+ },
2919
+ [getNode, insertNode]
2920
+ );
2921
+ }
2871
2922
  function isTriggerOperation(op) {
2872
2923
  return op.role === "trigger" || !!op.delivery || op.type === "subscription";
2873
2924
  }
@@ -2879,6 +2930,9 @@ function userSelectableOperations(ops) {
2879
2930
  function StoreNodeOperations({ nodeType }) {
2880
2931
  const [, bump] = useState(0);
2881
2932
  useEffect(() => onNodeTypesUpdate(() => bump((n) => n + 1)), []);
2933
+ const pendingConnection = useFlowStore((s) => s.workflow.pendingConnection);
2934
+ const addNodeFromStore = useAddNodeFromStore();
2935
+ const attachMode = !!pendingConnection;
2882
2936
  const config = nodeType ? NODE_CONFIGS.find((c) => c.type === nodeType) : void 0;
2883
2937
  const operations = userSelectableOperations(
2884
2938
  config?.operations
@@ -2893,6 +2947,13 @@ function StoreNodeOperations({ nodeType }) {
2893
2947
  },
2894
2948
  [nodeType]
2895
2949
  );
2950
+ const handleClick = useCallback(
2951
+ (opName) => {
2952
+ if (!nodeType) return;
2953
+ addNodeFromStore(nodeType, opName);
2954
+ },
2955
+ [nodeType, addNodeFromStore]
2956
+ );
2896
2957
  if (!nodeType) return null;
2897
2958
  if (operations.length === 0) {
2898
2959
  return /* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1", children: /* @__PURE__ */ jsxs(
@@ -2900,12 +2961,14 @@ function StoreNodeOperations({ nodeType }) {
2900
2961
  {
2901
2962
  draggable: true,
2902
2963
  onDragStart: (e) => handleDragStart(e),
2903
- title: "Drag to add to canvas",
2964
+ onClick: () => handleClick(),
2965
+ title: attachMode ? "Click to attach to the selected output" : "Click to add, or drag onto the canvas",
2904
2966
  className: "w-full flex items-center gap-2.5 px-2 py-2 rounded-md hover:bg-muted/60 transition-colors text-left group",
2905
- style: { cursor: "grab" },
2967
+ style: { cursor: attachMode ? "pointer" : "grab" },
2968
+ "data-testid": "store-op-add-to-canvas",
2906
2969
  children: [
2907
2970
  /* @__PURE__ */ jsx("div", { className: "size-7 shrink-0 rounded-md bg-muted flex items-center justify-center", children: /* @__PURE__ */ jsx(Icon, { icon: "mynaui:plus", className: "size-3.5 text-primary" }) }),
2908
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium text-foreground group-hover:text-primary transition-colors", children: "Add to canvas" }),
2971
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium text-foreground group-hover:text-primary transition-colors", children: attachMode ? "Attach to canvas" : "Add to canvas" }),
2909
2972
  /* @__PURE__ */ jsx(
2910
2973
  Icon,
2911
2974
  {
@@ -2918,17 +2981,22 @@ function StoreNodeOperations({ nodeType }) {
2918
2981
  ) });
2919
2982
  }
2920
2983
  return /* @__PURE__ */ jsxs("div", { className: "px-3 pt-3 pb-1", children: [
2921
- /* @__PURE__ */ jsx("p", { className: "px-1 pb-2 text-xs font-medium text-muted-foreground", children: "Operations \u2014 drag one onto the canvas" }),
2984
+ /* @__PURE__ */ jsx("p", { className: "px-1 pb-2 text-xs font-medium text-muted-foreground", children: attachMode ? "Operations \u2014 click one to attach it" : "Operations \u2014 click to add, or drag onto the canvas" }),
2922
2985
  /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: operations.map((op) => {
2923
2986
  const isTrigger = isTriggerOperation(op);
2987
+ const attachBlocked = attachMode && isTrigger;
2924
2988
  return /* @__PURE__ */ jsxs(
2925
2989
  "button",
2926
2990
  {
2927
2991
  draggable: true,
2928
2992
  onDragStart: (e) => handleDragStart(e, op.name),
2929
- title: `${op.name} \u2014 drag to add to canvas`,
2930
- className: "w-full flex items-center gap-2.5 px-2 py-2 rounded-md hover:bg-muted/60 transition-colors text-left group",
2931
- style: { cursor: "grab" },
2993
+ onClick: attachBlocked ? void 0 : () => handleClick(op.name),
2994
+ title: attachBlocked ? "Triggers start a flow \u2014 they can't be attached to an output" : attachMode ? `${op.name} \u2014 click to attach to the selected output` : `${op.name} \u2014 click to add, or drag onto the canvas`,
2995
+ className: `w-full flex items-center gap-2.5 px-2 py-2 rounded-md transition-colors text-left group ${attachBlocked ? "opacity-45" : "hover:bg-muted/60"}`,
2996
+ style: {
2997
+ cursor: attachBlocked ? "not-allowed" : attachMode ? "pointer" : "grab"
2998
+ },
2999
+ "data-testid": `store-op-${op.name}`,
2932
3000
  children: [
2933
3001
  /* @__PURE__ */ jsx("div", { className: "size-7 shrink-0 rounded-md bg-muted flex items-center justify-center", children: /* @__PURE__ */ jsx(
2934
3002
  Icon,
@@ -3345,6 +3413,65 @@ function StoreNodeDetail({ node, onBack }) {
3345
3413
  )
3346
3414
  ] });
3347
3415
  }
3416
+ function AttachModeBanner() {
3417
+ const pendingConnection = useFlowStore((s) => s.workflow.pendingConnection);
3418
+ const nodes = useFlowStore((s) => s.workflow.nodes);
3419
+ const clearPending = useFlowStore((s) => s.actions.pendingConnection.clear);
3420
+ if (!pendingConnection) return null;
3421
+ const source = nodes.find((n) => n.id === pendingConnection.nodeId);
3422
+ const data = source?.data;
3423
+ const sourceTitle = typeof data?.label === "string" && data.label || getNodeDetail(source?.type).title || pendingConnection.nodeId;
3424
+ const handleLabel = pendingConnection.handleId === "source" || pendingConnection.handleId === "output" ? void 0 : pendingConnection.handleId;
3425
+ return /* @__PURE__ */ jsx(
3426
+ "div",
3427
+ {
3428
+ className: "mx-3 mt-3 shrink-0 rounded-lg border px-3 py-2",
3429
+ style: {
3430
+ borderColor: "var(--color-status-info-border)",
3431
+ background: "var(--color-status-info-bgSubtle)"
3432
+ },
3433
+ "data-testid": "store-attach-banner",
3434
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3435
+ /* @__PURE__ */ jsx(
3436
+ Icon,
3437
+ {
3438
+ icon: "mynaui:git-branch",
3439
+ className: "size-4 shrink-0",
3440
+ style: { color: "var(--color-status-info-text)" }
3441
+ }
3442
+ ),
3443
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3444
+ /* @__PURE__ */ jsxs(
3445
+ "p",
3446
+ {
3447
+ className: "text-xs font-semibold leading-tight",
3448
+ style: { color: "var(--color-status-info-text)" },
3449
+ children: [
3450
+ "Attaching after \u201C",
3451
+ sourceTitle,
3452
+ "\u201D",
3453
+ handleLabel ? ` \u2192 ${handleLabel}` : ""
3454
+ ]
3455
+ }
3456
+ ),
3457
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "Click an operation below to add & connect it." })
3458
+ ] }),
3459
+ /* @__PURE__ */ jsx(
3460
+ "button",
3461
+ {
3462
+ type: "button",
3463
+ "aria-label": "Cancel attach",
3464
+ title: "Cancel attach",
3465
+ onClick: clearPending,
3466
+ className: "shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3467
+ "data-testid": "store-attach-cancel",
3468
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "size-3.5" })
3469
+ }
3470
+ )
3471
+ ] })
3472
+ }
3473
+ );
3474
+ }
3348
3475
  function StoreBrowsePanel() {
3349
3476
  const [view, setView] = useState({ type: "categories" });
3350
3477
  const [globalSearch, setGlobalSearch] = useState("");
@@ -3370,6 +3497,7 @@ function StoreBrowsePanel() {
3370
3497
  });
3371
3498
  }, []);
3372
3499
  return /* @__PURE__ */ jsxs(SidebarPanelWrapper, { children: [
3500
+ /* @__PURE__ */ jsx(AttachModeBanner, {}),
3373
3501
  view.type === "categories" && /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4 pb-2 shrink-0 flex items-center gap-2", children: [
3374
3502
  /* @__PURE__ */ jsx("div", { className: "size-7 flex items-center justify-center rounded-lg bg-primary", children: /* @__PURE__ */ jsx(Icon, { icon: "mynaui:store", className: "size-4 text-white" }) }),
3375
3503
  /* @__PURE__ */ jsxs("div", { children: [
@@ -4075,7 +4203,7 @@ function DesktopSidebarFragment({
4075
4203
  if (nodes) {
4076
4204
  const requiredNodeTypes = [];
4077
4205
  try {
4078
- const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-K5JPCH3D.js');
4206
+ const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-QKAWYAOT.js');
4079
4207
  await ensureNodeConfigsLoaded2().catch(() => {
4080
4208
  });
4081
4209
  const workflowNodeTypes = [
@@ -12629,6 +12757,8 @@ function useWebhookRegistration(workflowKey, triggerNodeId, enabled = true) {
12629
12757
  refetch: fetchData
12630
12758
  };
12631
12759
  }
12760
+
12761
+ // src/builder/components/ndv/use-node-config.ts
12632
12762
  function coerceNumber(type, raw) {
12633
12763
  if (raw.trim() === "") return void 0;
12634
12764
  const n = type === "integer" ? parseInt(raw, 10) : parseFloat(raw);
@@ -12673,66 +12803,32 @@ function nodeSubtitle(node) {
12673
12803
  const data = asRecord(node?.data);
12674
12804
  return typeof data?.operation_name === "string" ? data.operation_name : void 0;
12675
12805
  }
12676
- var SECTION_LABEL2 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
12677
- function NodeDetailView() {
12678
- const ndv = useFlowStore((s) => s.workflow.ndv);
12806
+ function useNodeConfig(target, options) {
12679
12807
  const nodes = useFlowStore((s) => s.workflow.nodes);
12680
12808
  const edges = useFlowStore((s) => s.workflow.edges);
12681
- const closeNdv = useFlowStore((s) => s.actions.ndv.close);
12682
- const showNodePropertiesOf = useFlowStore(
12683
- (s) => s.actions.sidebar.showNodePropertiesOf
12684
- );
12685
- const open = !!ndv;
12809
+ const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
12810
+ const nodeId = target?.nodeId;
12686
12811
  const node = useMemo(
12687
- () => ndv ? nodes.find((n) => n.id === ndv.nodeId) : void 0,
12688
- [ndv, nodes]
12812
+ () => nodeId ? nodes.find((n) => n.id === nodeId) : void 0,
12813
+ [nodeId, nodes]
12689
12814
  );
12690
12815
  const incoming = useMemo(
12691
- () => ndv ? edges.filter((e) => e.target === ndv.nodeId) : [],
12692
- [edges, ndv]
12816
+ () => nodeId ? edges.filter((e) => e.target === nodeId) : [],
12817
+ [edges, nodeId]
12693
12818
  );
12694
12819
  const sourceIds = useMemo(() => incoming.map((e) => e.source), [incoming]);
12695
12820
  const sourceKey = sourceIds.join(",");
12696
12821
  const [selectedSource, setSelectedSource] = useState();
12697
- const [tab, setTab] = useState("parameters");
12698
- const [outputView, setOutputView] = useState("schema");
12699
12822
  const [dragField, setDragField] = useState(null);
12700
12823
  const [modeByField, setModeByField] = useState({});
12701
- const panesRef = useRef(null);
12702
- const dragCleanupRef = useRef(null);
12703
- const [leftPct, setLeftPct] = useState(30);
12704
- const [rightPct, setRightPct] = useState(30);
12705
- const startDrag = (which) => (e) => {
12706
- e.preventDefault();
12707
- const el = panesRef.current;
12708
- if (!el) return;
12709
- const rect = el.getBoundingClientRect();
12710
- const onMove = (ev) => {
12711
- const pct = (ev.clientX - rect.left) / rect.width * 100;
12712
- if (which === "left") setLeftPct(Math.min(55, Math.max(15, pct)));
12713
- else setRightPct(Math.min(55, Math.max(15, 100 - pct)));
12714
- };
12715
- const cleanup = () => {
12716
- window.removeEventListener("mousemove", onMove);
12717
- window.removeEventListener("mouseup", cleanup);
12718
- dragCleanupRef.current = null;
12719
- };
12720
- dragCleanupRef.current = cleanup;
12721
- window.addEventListener("mousemove", onMove);
12722
- window.addEventListener("mouseup", cleanup);
12723
- };
12724
- useEffect(() => () => dragCleanupRef.current?.(), []);
12725
12824
  useEffect(() => {
12726
12825
  setSelectedSource(
12727
12826
  (prev) => prev && sourceIds.includes(prev) ? prev : sourceIds[0]
12728
12827
  );
12729
- }, [ndv?.nodeId, sourceKey, sourceIds]);
12730
- useEffect(() => {
12731
- if (open) setTab("parameters");
12732
- }, [open, ndv?.nodeId]);
12828
+ }, [nodeId, sourceKey, sourceIds]);
12733
12829
  useEffect(() => {
12734
12830
  setModeByField({});
12735
- }, [ndv?.nodeId, selectedSource]);
12831
+ }, [nodeId, selectedSource]);
12736
12832
  const sourceNode = useMemo(
12737
12833
  () => nodes.find((n) => n.id === selectedSource),
12738
12834
  [nodes, selectedSource]
@@ -12762,11 +12858,10 @@ function NodeDetailView() {
12762
12858
  () => !!asRecord(currentOp?.output_fields)?.webhookUrl,
12763
12859
  [currentOp]
12764
12860
  );
12765
- const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
12766
12861
  const { webhookUrl, loading: webhookUrlLoading } = useWebhookRegistration(
12767
12862
  workflowKey || void 0,
12768
12863
  node?.id,
12769
- isWebhookTrigger && open
12864
+ isWebhookTrigger && (options?.webhookEnabled ?? true)
12770
12865
  );
12771
12866
  const pickerFields = useMemo(() => flattenFields(inputFields), [inputFields]);
12772
12867
  const mapping = currentEdge?.mapping?.definition ?? {};
@@ -12799,10 +12894,10 @@ function NodeDetailView() {
12799
12894
  return fromList(args?.credentials, "id");
12800
12895
  }, [node]);
12801
12896
  const handleConnectionsChange = (connections) => {
12802
- if (!ndv) return;
12897
+ if (!nodeId) return;
12803
12898
  const store = useFlowStore.getState();
12804
12899
  const next = store.workflow.nodes.map((n) => {
12805
- if (n.id !== ndv.nodeId) return n;
12900
+ if (n.id !== nodeId) return n;
12806
12901
  const data = { ...asRecord(n.data) ?? {} };
12807
12902
  const args = { ...asRecord(data.args) ?? {} };
12808
12903
  delete args.credentials;
@@ -12828,19 +12923,19 @@ function NodeDetailView() {
12828
12923
  ...e,
12829
12924
  mapping: {
12830
12925
  definition: def,
12831
- comment: e.mapping?.comment ?? `Map ${selectedSource} \u2192 ${ndv?.nodeId ?? ""}`
12926
+ comment: e.mapping?.comment ?? `Map ${selectedSource} \u2192 ${nodeId ?? ""}`
12832
12927
  }
12833
12928
  };
12834
12929
  });
12835
12930
  store.actions.edges.setEdges(next);
12836
12931
  };
12837
12932
  const clearMappingFromAllIncoming = (field) => {
12838
- if (!ndv) return;
12933
+ if (!nodeId) return;
12839
12934
  const store = useFlowStore.getState();
12840
12935
  const fresh = store.workflow.edges;
12841
12936
  let changed = false;
12842
12937
  const next = fresh.map((e) => {
12843
- if (e.target !== ndv.nodeId || !e.mapping?.definition) return e;
12938
+ if (e.target !== nodeId || !e.mapping?.definition) return e;
12844
12939
  if (!(field in e.mapping.definition)) return e;
12845
12940
  const def = { ...e.mapping.definition };
12846
12941
  delete def[field];
@@ -12850,10 +12945,10 @@ function NodeDetailView() {
12850
12945
  if (changed) store.actions.edges.setEdges(next);
12851
12946
  };
12852
12947
  const writeFixed = (field, value) => {
12853
- if (!ndv) return;
12948
+ if (!nodeId) return;
12854
12949
  const store = useFlowStore.getState();
12855
12950
  const next = store.workflow.nodes.map((n) => {
12856
- if (n.id !== ndv.nodeId) return n;
12951
+ if (n.id !== nodeId) return n;
12857
12952
  const data = { ...asRecord(n.data) ?? {} };
12858
12953
  const args = { ...asRecord(data.args) ?? {} };
12859
12954
  const state = { ...asRecord(args.state) ?? {} };
@@ -12887,6 +12982,279 @@ function NodeDetailView() {
12887
12982
  setModeByField((m) => ({ ...m, [field]: "expression" }));
12888
12983
  }
12889
12984
  };
12985
+ return {
12986
+ node,
12987
+ nodes,
12988
+ incoming,
12989
+ sourceIds,
12990
+ selectedSource,
12991
+ setSelectedSource,
12992
+ currentEdge,
12993
+ currentOp,
12994
+ params,
12995
+ argsState,
12996
+ mapping,
12997
+ inputFields,
12998
+ outputFields,
12999
+ pickerFields,
13000
+ connectionAdapters,
13001
+ selectedConnections,
13002
+ isWebhookTrigger,
13003
+ webhookUrl,
13004
+ webhookUrlLoading,
13005
+ modeByField,
13006
+ dragField,
13007
+ setDragField,
13008
+ writeFixed,
13009
+ writeMapping,
13010
+ handleConnectionsChange,
13011
+ toggleMode,
13012
+ handleDropOnParam
13013
+ };
13014
+ }
13015
+ var SECTION_LABEL2 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
13016
+ function NodeParamsForm({
13017
+ config,
13018
+ nodeType,
13019
+ testIdPrefix = "ndv"
13020
+ }) {
13021
+ const {
13022
+ params,
13023
+ argsState,
13024
+ mapping,
13025
+ pickerFields,
13026
+ connectionAdapters,
13027
+ selectedConnections,
13028
+ isWebhookTrigger,
13029
+ webhookUrl,
13030
+ webhookUrlLoading,
13031
+ currentEdge,
13032
+ modeByField,
13033
+ dragField,
13034
+ setDragField,
13035
+ writeFixed,
13036
+ writeMapping,
13037
+ handleConnectionsChange,
13038
+ toggleMode,
13039
+ handleDropOnParam
13040
+ } = config;
13041
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
13042
+ isWebhookTrigger && /* @__PURE__ */ jsx(WebhookUrlField, { webhookUrl, loading: webhookUrlLoading }),
13043
+ connectionAdapters.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-connections`, children: [
13044
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL2, children: "Connections" }),
13045
+ /* @__PURE__ */ jsx(
13046
+ ConnectionSelector,
13047
+ {
13048
+ adapters: connectionAdapters,
13049
+ selectedConnections,
13050
+ onChange: handleConnectionsChange,
13051
+ compact: true
13052
+ }
13053
+ )
13054
+ ] }),
13055
+ !currentEdge && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: "No upstream node connected \u2014 set fixed values below, or connect a node to map fields with expressions." }),
13056
+ params.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-xs text-muted-foreground", children: "This operation has no input parameters." }) : params.map((p) => {
13057
+ if (p.type === "condition") {
13058
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
13059
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name === "conditionTree" ? "Conditions" : p.name }),
13060
+ /* @__PURE__ */ jsx(
13061
+ ConditionTreeBuilder,
13062
+ {
13063
+ value: argsState[p.name],
13064
+ fields: pickerFields,
13065
+ onChange: (tree) => writeFixed(p.name, tree)
13066
+ }
13067
+ ),
13068
+ p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
13069
+ ] }, p.name);
13070
+ }
13071
+ if (p.type === "switch-rules") {
13072
+ const rules = Array.isArray(argsState[p.name]) ? argsState[p.name] : [];
13073
+ const fallbackHandle = typeof argsState.fallbackHandle === "string" ? argsState.fallbackHandle : "default";
13074
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
13075
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: "Routing rules" }),
13076
+ /* @__PURE__ */ jsx(
13077
+ SwitchRulesEditor,
13078
+ {
13079
+ rules,
13080
+ fallbackHandle,
13081
+ fields: pickerFields,
13082
+ onChange: (next) => {
13083
+ writeFixed(p.name, next.rules);
13084
+ writeFixed("cases", casesFromRules(next.rules));
13085
+ writeFixed("fallbackHandle", next.fallbackHandle);
13086
+ }
13087
+ }
13088
+ )
13089
+ ] }, p.name);
13090
+ }
13091
+ const mode = modeByField[p.name] ?? (mapping[p.name] !== void 0 ? "expression" : "fixed");
13092
+ const exprValue = mapping[p.name] ?? "";
13093
+ const fixedValue = argsState[p.name];
13094
+ const isCodeParam = p.type === "code" || nodeType === "internal:code" && p.name === "code";
13095
+ const highlight = dragField === p.name;
13096
+ const inputCx = cn$1(
13097
+ "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors disabled:opacity-50",
13098
+ highlight ? "border-primary ring-1 ring-primary" : "border-input focus:border-primary"
13099
+ );
13100
+ return /* @__PURE__ */ jsxs(
13101
+ "div",
13102
+ {
13103
+ className: "flex flex-col gap-1.5",
13104
+ onDragOver: (e) => e.preventDefault(),
13105
+ onDragEnter: () => setDragField(p.name),
13106
+ onDragLeave: () => setDragField((f) => f === p.name ? null : f),
13107
+ onDrop: handleDropOnParam(p.name),
13108
+ children: [
13109
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
13110
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name }),
13111
+ p.required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" }),
13112
+ /* @__PURE__ */ jsx("span", { className: "ml-auto text-[10px] uppercase tracking-wide text-muted-foreground", children: p.type }),
13113
+ /* @__PURE__ */ jsx(
13114
+ "button",
13115
+ {
13116
+ type: "button",
13117
+ onClick: () => toggleMode(p.name, mode),
13118
+ title: mode === "expression" ? "Switch to a fixed value" : "Switch to an expression",
13119
+ "data-testid": `${testIdPrefix}-mode-${p.name}`,
13120
+ className: cn$1(
13121
+ "ml-1 rounded p-0.5 hover:text-foreground",
13122
+ mode === "expression" ? "text-primary" : "text-muted-foreground"
13123
+ ),
13124
+ children: /* @__PURE__ */ jsx(
13125
+ Icon,
13126
+ {
13127
+ icon: mode === "expression" ? "mdi:variable" : "mdi:form-textbox",
13128
+ className: "size-3.5"
13129
+ }
13130
+ )
13131
+ }
13132
+ )
13133
+ ] }),
13134
+ mode === "expression" ? /* @__PURE__ */ jsx(
13135
+ "input",
13136
+ {
13137
+ type: "text",
13138
+ value: exprValue,
13139
+ placeholder: currentEdge ? "Drop a field or type ${output.\u2026}" : "Connect an upstream node to use expressions",
13140
+ disabled: !currentEdge,
13141
+ onChange: (e) => writeMapping(p.name, e.target.value),
13142
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13143
+ className: cn$1(inputCx, "font-mono text-primary")
13144
+ }
13145
+ ) : isCodeParam ? /* @__PURE__ */ jsx(
13146
+ CodeEditorField,
13147
+ {
13148
+ value: typeof fixedValue === "string" ? fixedValue : "",
13149
+ language: normalizeCodeLanguage(
13150
+ argsState.runtime ?? params.find((f) => f.name === "runtime")?.defaultValue
13151
+ ),
13152
+ placeholder: typeof p.defaultValue === "string" ? p.defaultValue : p.description,
13153
+ onChange: (v) => writeFixed(p.name, v),
13154
+ "data-testid": `${testIdPrefix}-param-${p.name}`
13155
+ }
13156
+ ) : p.type === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
13157
+ /* @__PURE__ */ jsx(
13158
+ "input",
13159
+ {
13160
+ type: "checkbox",
13161
+ checked: fixedValue === true,
13162
+ onChange: (e) => writeFixed(p.name, e.target.checked),
13163
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13164
+ className: "size-4"
13165
+ }
13166
+ ),
13167
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: fixedValue === void 0 ? "Not set (default)" : fixedValue === true ? "true" : "false" })
13168
+ ] }) : p.options && p.options.length > 0 ? /* @__PURE__ */ jsxs(
13169
+ "select",
13170
+ {
13171
+ value: typeof fixedValue === "string" ? fixedValue : "",
13172
+ onChange: (e) => writeFixed(p.name, e.target.value),
13173
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13174
+ className: inputCx,
13175
+ children: [
13176
+ /* @__PURE__ */ jsx("option", { value: "", children: p.defaultValue !== void 0 ? `Default (${String(p.defaultValue)})` : "Select\u2026" }),
13177
+ p.options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
13178
+ ]
13179
+ }
13180
+ ) : p.type === "integer" || p.type === "number" ? /* @__PURE__ */ jsx(
13181
+ "input",
13182
+ {
13183
+ type: "number",
13184
+ step: p.type === "integer" ? "1" : "any",
13185
+ value: typeof fixedValue === "number" ? String(fixedValue) : "",
13186
+ placeholder: p.defaultValue !== void 0 ? `Default: ${String(p.defaultValue)}` : "Enter a number",
13187
+ onChange: (e) => writeFixed(p.name, coerceNumber(p.type, e.target.value)),
13188
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13189
+ className: inputCx
13190
+ }
13191
+ ) : /* @__PURE__ */ jsx(
13192
+ "input",
13193
+ {
13194
+ type: "text",
13195
+ value: fixedValue === void 0 || fixedValue === null ? "" : String(fixedValue),
13196
+ placeholder: p.defaultValue !== void 0 ? `Default: ${String(p.defaultValue)}` : "Enter a value",
13197
+ onChange: (e) => writeFixed(p.name, e.target.value),
13198
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13199
+ className: inputCx
13200
+ }
13201
+ ),
13202
+ p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
13203
+ ]
13204
+ },
13205
+ p.name
13206
+ );
13207
+ })
13208
+ ] });
13209
+ }
13210
+ var SECTION_LABEL3 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
13211
+ function NodeDetailView() {
13212
+ const ndv = useFlowStore((s) => s.workflow.ndv);
13213
+ const closeNdv = useFlowStore((s) => s.actions.ndv.close);
13214
+ const showNodePropertiesOf = useFlowStore(
13215
+ (s) => s.actions.sidebar.showNodePropertiesOf
13216
+ );
13217
+ const open = !!ndv;
13218
+ const config = useNodeConfig(ndv ?? null, { webhookEnabled: open });
13219
+ const {
13220
+ node,
13221
+ nodes,
13222
+ sourceIds,
13223
+ selectedSource,
13224
+ setSelectedSource,
13225
+ currentOp,
13226
+ inputFields,
13227
+ outputFields
13228
+ } = config;
13229
+ const [tab, setTab] = useState("parameters");
13230
+ const [outputView, setOutputView] = useState("schema");
13231
+ const panesRef = useRef(null);
13232
+ const dragCleanupRef = useRef(null);
13233
+ const [leftPct, setLeftPct] = useState(30);
13234
+ const [rightPct, setRightPct] = useState(30);
13235
+ const startDrag = (which) => (e) => {
13236
+ e.preventDefault();
13237
+ const el = panesRef.current;
13238
+ if (!el) return;
13239
+ const rect = el.getBoundingClientRect();
13240
+ const onMove = (ev) => {
13241
+ const pct = (ev.clientX - rect.left) / rect.width * 100;
13242
+ if (which === "left") setLeftPct(Math.min(55, Math.max(15, pct)));
13243
+ else setRightPct(Math.min(55, Math.max(15, 100 - pct)));
13244
+ };
13245
+ const cleanup = () => {
13246
+ window.removeEventListener("mousemove", onMove);
13247
+ window.removeEventListener("mouseup", cleanup);
13248
+ dragCleanupRef.current = null;
13249
+ };
13250
+ dragCleanupRef.current = cleanup;
13251
+ window.addEventListener("mousemove", onMove);
13252
+ window.addEventListener("mouseup", cleanup);
13253
+ };
13254
+ useEffect(() => () => dragCleanupRef.current?.(), []);
13255
+ useEffect(() => {
13256
+ if (open) setTab("parameters");
13257
+ }, [open, ndv?.nodeId]);
12890
13258
  const openClassicPanel = () => {
12891
13259
  if (!ndv) return;
12892
13260
  showNodePropertiesOf({
@@ -12944,7 +13312,7 @@ function NodeDetailView() {
12944
13312
  className: "flex min-h-0 min-w-[180px] flex-col bg-card/40",
12945
13313
  children: [
12946
13314
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-2.5", children: [
12947
- /* @__PURE__ */ jsx("span", { className: SECTION_LABEL2, children: "Input" }),
13315
+ /* @__PURE__ */ jsx("span", { className: SECTION_LABEL3, children: "Input" }),
12948
13316
  sourceIds.length > 0 && /* @__PURE__ */ jsx(
12949
13317
  "select",
12950
13318
  {
@@ -12997,188 +13365,19 @@ function NodeDetailView() {
12997
13365
  }
12998
13366
  )
12999
13367
  ] }),
13000
- tab === "parameters" ? /* @__PURE__ */ jsxs(
13368
+ tab === "parameters" ? /* @__PURE__ */ jsx(
13001
13369
  "div",
13002
13370
  {
13003
13371
  className: "min-h-0 flex-1 space-y-4 overflow-auto p-4",
13004
13372
  "data-testid": "ndv-params",
13005
- children: [
13006
- isWebhookTrigger && /* @__PURE__ */ jsx(
13007
- WebhookUrlField,
13008
- {
13009
- webhookUrl,
13010
- loading: webhookUrlLoading
13011
- }
13012
- ),
13013
- connectionAdapters.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "ndv-connections", children: [
13014
- /* @__PURE__ */ jsx("div", { className: SECTION_LABEL2, children: "Connections" }),
13015
- /* @__PURE__ */ jsx(
13016
- ConnectionSelector,
13017
- {
13018
- adapters: connectionAdapters,
13019
- selectedConnections,
13020
- onChange: handleConnectionsChange,
13021
- compact: true
13022
- }
13023
- )
13024
- ] }),
13025
- !currentEdge && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: "No upstream node connected \u2014 set fixed values below, or connect a node to map fields with expressions." }),
13026
- params.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-xs text-muted-foreground", children: "This operation has no input parameters." }) : params.map((p) => {
13027
- if (p.type === "condition") {
13028
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
13029
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name === "conditionTree" ? "Conditions" : p.name }),
13030
- /* @__PURE__ */ jsx(
13031
- ConditionTreeBuilder,
13032
- {
13033
- value: argsState[p.name],
13034
- fields: pickerFields,
13035
- onChange: (tree) => writeFixed(p.name, tree)
13036
- }
13037
- ),
13038
- p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
13039
- ] }, p.name);
13040
- }
13041
- if (p.type === "switch-rules") {
13042
- const rules = Array.isArray(argsState[p.name]) ? argsState[p.name] : [];
13043
- const fallbackHandle = typeof argsState.fallbackHandle === "string" ? argsState.fallbackHandle : "default";
13044
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
13045
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: "Routing rules" }),
13046
- /* @__PURE__ */ jsx(
13047
- SwitchRulesEditor,
13048
- {
13049
- rules,
13050
- fallbackHandle,
13051
- fields: pickerFields,
13052
- onChange: (next) => {
13053
- writeFixed(p.name, next.rules);
13054
- writeFixed("cases", casesFromRules(next.rules));
13055
- writeFixed("fallbackHandle", next.fallbackHandle);
13056
- }
13057
- }
13058
- )
13059
- ] }, p.name);
13060
- }
13061
- const mode = modeByField[p.name] ?? (mapping[p.name] !== void 0 ? "expression" : "fixed");
13062
- const exprValue = mapping[p.name] ?? "";
13063
- const fixedValue = argsState[p.name];
13064
- const isCodeParam = p.type === "code" || node?.type === "internal:code" && p.name === "code";
13065
- const highlight = dragField === p.name;
13066
- const inputCx = cn$1(
13067
- "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors disabled:opacity-50",
13068
- highlight ? "border-primary ring-1 ring-primary" : "border-input focus:border-primary"
13069
- );
13070
- return /* @__PURE__ */ jsxs(
13071
- "div",
13072
- {
13073
- className: "flex flex-col gap-1.5",
13074
- onDragOver: (e) => e.preventDefault(),
13075
- onDragEnter: () => setDragField(p.name),
13076
- onDragLeave: () => setDragField((f) => f === p.name ? null : f),
13077
- onDrop: handleDropOnParam(p.name),
13078
- children: [
13079
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
13080
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name }),
13081
- p.required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" }),
13082
- /* @__PURE__ */ jsx("span", { className: "ml-auto text-[10px] uppercase tracking-wide text-muted-foreground", children: p.type }),
13083
- /* @__PURE__ */ jsx(
13084
- "button",
13085
- {
13086
- type: "button",
13087
- onClick: () => toggleMode(p.name, mode),
13088
- title: mode === "expression" ? "Switch to a fixed value" : "Switch to an expression",
13089
- "data-testid": `ndv-mode-${p.name}`,
13090
- className: cn$1(
13091
- "ml-1 rounded p-0.5 hover:text-foreground",
13092
- mode === "expression" ? "text-primary" : "text-muted-foreground"
13093
- ),
13094
- children: /* @__PURE__ */ jsx(
13095
- Icon,
13096
- {
13097
- icon: mode === "expression" ? "mdi:variable" : "mdi:form-textbox",
13098
- className: "size-3.5"
13099
- }
13100
- )
13101
- }
13102
- )
13103
- ] }),
13104
- mode === "expression" ? /* @__PURE__ */ jsx(
13105
- "input",
13106
- {
13107
- type: "text",
13108
- value: exprValue,
13109
- placeholder: currentEdge ? "Drop a field or type ${output.\u2026}" : "Connect an upstream node to use expressions",
13110
- disabled: !currentEdge,
13111
- onChange: (e) => writeMapping(p.name, e.target.value),
13112
- "data-testid": `ndv-param-${p.name}`,
13113
- className: cn$1(inputCx, "font-mono text-primary")
13114
- }
13115
- ) : isCodeParam ? /* @__PURE__ */ jsx(
13116
- CodeEditorField,
13117
- {
13118
- value: typeof fixedValue === "string" ? fixedValue : "",
13119
- language: normalizeCodeLanguage(
13120
- argsState.runtime ?? params.find((f) => f.name === "runtime")?.defaultValue
13121
- ),
13122
- placeholder: typeof p.defaultValue === "string" ? p.defaultValue : p.description,
13123
- onChange: (v) => writeFixed(p.name, v),
13124
- "data-testid": `ndv-param-${p.name}`
13125
- }
13126
- ) : p.type === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
13127
- /* @__PURE__ */ jsx(
13128
- "input",
13129
- {
13130
- type: "checkbox",
13131
- checked: fixedValue === true,
13132
- onChange: (e) => writeFixed(p.name, e.target.checked),
13133
- "data-testid": `ndv-param-${p.name}`,
13134
- className: "size-4"
13135
- }
13136
- ),
13137
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: fixedValue === void 0 ? "Not set (default)" : fixedValue === true ? "true" : "false" })
13138
- ] }) : p.options && p.options.length > 0 ? /* @__PURE__ */ jsxs(
13139
- "select",
13140
- {
13141
- value: typeof fixedValue === "string" ? fixedValue : "",
13142
- onChange: (e) => writeFixed(p.name, e.target.value),
13143
- "data-testid": `ndv-param-${p.name}`,
13144
- className: inputCx,
13145
- children: [
13146
- /* @__PURE__ */ jsx("option", { value: "", children: p.defaultValue !== void 0 ? `Default (${String(p.defaultValue)})` : "Select\u2026" }),
13147
- p.options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
13148
- ]
13149
- }
13150
- ) : p.type === "integer" || p.type === "number" ? /* @__PURE__ */ jsx(
13151
- "input",
13152
- {
13153
- type: "number",
13154
- step: p.type === "integer" ? "1" : "any",
13155
- value: typeof fixedValue === "number" ? String(fixedValue) : "",
13156
- placeholder: p.defaultValue !== void 0 ? `Default: ${String(p.defaultValue)}` : "Enter a number",
13157
- onChange: (e) => writeFixed(
13158
- p.name,
13159
- coerceNumber(p.type, e.target.value)
13160
- ),
13161
- "data-testid": `ndv-param-${p.name}`,
13162
- className: inputCx
13163
- }
13164
- ) : /* @__PURE__ */ jsx(
13165
- "input",
13166
- {
13167
- type: "text",
13168
- value: fixedValue === void 0 || fixedValue === null ? "" : String(fixedValue),
13169
- placeholder: p.defaultValue !== void 0 ? `Default: ${String(p.defaultValue)}` : "Enter a value",
13170
- onChange: (e) => writeFixed(p.name, e.target.value),
13171
- "data-testid": `ndv-param-${p.name}`,
13172
- className: inputCx
13173
- }
13174
- ),
13175
- p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
13176
- ]
13177
- },
13178
- p.name
13179
- );
13180
- })
13181
- ]
13373
+ children: /* @__PURE__ */ jsx(
13374
+ NodeParamsForm,
13375
+ {
13376
+ config,
13377
+ nodeType: node?.type,
13378
+ testIdPrefix: "ndv"
13379
+ }
13380
+ )
13182
13381
  }
13183
13382
  ) : /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 text-xs text-muted-foreground", children: "No additional settings for this node." })
13184
13383
  ] }),
@@ -13190,7 +13389,7 @@ function NodeDetailView() {
13190
13389
  className: "flex min-h-0 min-w-[180px] flex-col bg-card/40",
13191
13390
  children: [
13192
13391
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-2.5", children: [
13193
- /* @__PURE__ */ jsx("span", { className: SECTION_LABEL2, children: "Output" }),
13392
+ /* @__PURE__ */ jsx("span", { className: SECTION_LABEL3, children: "Output" }),
13194
13393
  outputFields.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
13195
13394
  /* @__PURE__ */ jsx(
13196
13395
  ToggleChip,
@@ -13292,6 +13491,255 @@ function EmptyState({
13292
13491
  /* @__PURE__ */ jsx("div", { className: "max-w-[14rem] text-xs text-muted-foreground", children: hint })
13293
13492
  ] });
13294
13493
  }
13494
+ var PANEL_WIDTH = 380;
13495
+ var PANEL_EST_HEIGHT = 480;
13496
+ var MAPPER_WIDTH = 260;
13497
+ var VIEWPORT_MARGIN = 12;
13498
+ function clampToViewport(pos) {
13499
+ const maxX = window.innerWidth - PANEL_WIDTH - VIEWPORT_MARGIN;
13500
+ const maxY = window.innerHeight - PANEL_EST_HEIGHT - VIEWPORT_MARGIN;
13501
+ return {
13502
+ x: Math.min(
13503
+ Math.max(VIEWPORT_MARGIN, pos.x),
13504
+ Math.max(VIEWPORT_MARGIN, maxX)
13505
+ ),
13506
+ y: Math.min(
13507
+ Math.max(VIEWPORT_MARGIN, pos.y),
13508
+ Math.max(VIEWPORT_MARGIN, maxY)
13509
+ )
13510
+ };
13511
+ }
13512
+ function QuickConfigPanel() {
13513
+ const quickConfig = useFlowStore((s) => s.workflow.quickConfig);
13514
+ if (!quickConfig) return null;
13515
+ return (
13516
+ // Remount per node so panel position/flyout state reset naturally.
13517
+ /* @__PURE__ */ jsx(
13518
+ QuickConfigPanelInner,
13519
+ {
13520
+ nodeId: quickConfig.nodeId,
13521
+ type: quickConfig.type,
13522
+ anchor: quickConfig.anchor
13523
+ },
13524
+ quickConfig.nodeId
13525
+ )
13526
+ );
13527
+ }
13528
+ function QuickConfigPanelInner({
13529
+ nodeId,
13530
+ type,
13531
+ anchor
13532
+ }) {
13533
+ const close = useFlowStore((s) => s.actions.quickConfig.close);
13534
+ const openNdv = useFlowStore((s) => s.actions.ndv.open);
13535
+ const config = useNodeConfig({ nodeId});
13536
+ const {
13537
+ node,
13538
+ nodes,
13539
+ sourceIds,
13540
+ selectedSource,
13541
+ setSelectedSource,
13542
+ inputFields
13543
+ } = config;
13544
+ const [pos, setPos] = useState(
13545
+ () => clampToViewport(
13546
+ anchor ?? {
13547
+ x: window.innerWidth / 2 - PANEL_WIDTH / 2,
13548
+ y: window.innerHeight / 4
13549
+ }
13550
+ )
13551
+ );
13552
+ const [showMapper, setShowMapper] = useState(false);
13553
+ const dragStateRef = useRef(null);
13554
+ const handleHeaderPointerDown = useCallback(
13555
+ (e) => {
13556
+ if (e.target.closest("button")) return;
13557
+ dragStateRef.current = {
13558
+ startX: e.clientX,
13559
+ startY: e.clientY,
13560
+ baseX: pos.x,
13561
+ baseY: pos.y
13562
+ };
13563
+ const onMove = (ev) => {
13564
+ const s = dragStateRef.current;
13565
+ if (!s) return;
13566
+ setPos(
13567
+ clampToViewport({
13568
+ x: s.baseX + (ev.clientX - s.startX),
13569
+ y: s.baseY + (ev.clientY - s.startY)
13570
+ })
13571
+ );
13572
+ };
13573
+ const onUp = () => {
13574
+ dragStateRef.current = null;
13575
+ window.removeEventListener("pointermove", onMove);
13576
+ window.removeEventListener("pointerup", onUp);
13577
+ };
13578
+ window.addEventListener("pointermove", onMove);
13579
+ window.addEventListener("pointerup", onUp);
13580
+ },
13581
+ [pos.x, pos.y]
13582
+ );
13583
+ useEffect(() => {
13584
+ const onKey = (e) => {
13585
+ if (e.key === "Escape") close();
13586
+ };
13587
+ window.addEventListener("keydown", onKey);
13588
+ return () => window.removeEventListener("keydown", onKey);
13589
+ }, [close]);
13590
+ useEffect(() => {
13591
+ if (!nodes.some((n) => n.id === nodeId)) close();
13592
+ }, [nodes, nodeId, close]);
13593
+ if (!node) return null;
13594
+ const meta = getNodeDetail(node.type);
13595
+ const accent = nodeAccent(node.type || meta.title);
13596
+ const subtitle = nodeSubtitle(node);
13597
+ const expandToNdv = () => {
13598
+ openNdv({ nodeId, type });
13599
+ };
13600
+ return /* @__PURE__ */ jsxs(
13601
+ "div",
13602
+ {
13603
+ className: "fixed",
13604
+ style: { left: pos.x, top: pos.y, zIndex: 300, width: PANEL_WIDTH },
13605
+ "data-testid": "quick-config-panel",
13606
+ children: [
13607
+ showMapper && /* @__PURE__ */ jsxs(
13608
+ "div",
13609
+ {
13610
+ className: "absolute overflow-hidden rounded-xl border border-border bg-card shadow-xl",
13611
+ style: {
13612
+ right: "100%",
13613
+ marginRight: 10,
13614
+ top: 0,
13615
+ width: MAPPER_WIDTH,
13616
+ maxHeight: "min(60vh, 480px)",
13617
+ display: "flex",
13618
+ flexDirection: "column"
13619
+ },
13620
+ "data-testid": "quick-config-mapper",
13621
+ children: [
13622
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2", children: [
13623
+ /* @__PURE__ */ jsx(
13624
+ Icon,
13625
+ {
13626
+ icon: "mdi:tray-arrow-down",
13627
+ className: "size-3.5 text-muted-foreground"
13628
+ }
13629
+ ),
13630
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: "Input" }),
13631
+ sourceIds.length > 0 && /* @__PURE__ */ jsx(
13632
+ "select",
13633
+ {
13634
+ className: "ml-auto max-w-[55%] truncate rounded-md border border-input bg-background px-1.5 py-0.5 text-[11px] text-foreground outline-none focus:border-primary",
13635
+ value: selectedSource ?? "",
13636
+ onChange: (e) => setSelectedSource(e.target.value),
13637
+ "data-testid": "qc-source-selector",
13638
+ children: sourceIds.map((sid) => {
13639
+ const sn = nodes.find((n) => n.id === sid);
13640
+ return /* @__PURE__ */ jsx("option", { value: sid, children: nodeTitle(sn) }, sid);
13641
+ })
13642
+ }
13643
+ )
13644
+ ] }),
13645
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto px-2 py-2", children: inputFields.length > 0 ? /* @__PURE__ */ jsx(FieldTree, { fields: inputFields, draggable: true }) : /* @__PURE__ */ jsx("p", { className: "px-2 py-4 text-center text-xs text-muted-foreground", children: sourceIds.length === 0 ? "Connect an upstream node to map its fields." : "The selected source has no declared output fields." }) }),
13646
+ /* @__PURE__ */ jsx("p", { className: "shrink-0 border-t border-border px-3 py-1.5 text-[10px] leading-snug text-muted-foreground", children: "Drag a field onto a parameter to map it." })
13647
+ ]
13648
+ }
13649
+ ),
13650
+ /* @__PURE__ */ jsxs(
13651
+ "div",
13652
+ {
13653
+ className: "flex flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl animate-in fade-in zoom-in-95 duration-150",
13654
+ style: {
13655
+ maxHeight: "min(70vh, 560px)",
13656
+ borderTop: `3px solid ${accent}`
13657
+ },
13658
+ children: [
13659
+ /* @__PURE__ */ jsxs(
13660
+ "div",
13661
+ {
13662
+ className: "flex shrink-0 cursor-move items-center gap-2.5 border-b border-border px-3 py-2.5 select-none",
13663
+ onPointerDown: handleHeaderPointerDown,
13664
+ children: [
13665
+ /* @__PURE__ */ jsx(
13666
+ "div",
13667
+ {
13668
+ className: "flex size-7 shrink-0 items-center justify-center rounded-lg",
13669
+ style: {
13670
+ backgroundColor: `color-mix(in srgb, ${accent} 13%, transparent)`,
13671
+ color: accent,
13672
+ border: `1px solid color-mix(in srgb, ${accent} 26%, transparent)`
13673
+ },
13674
+ children: /* @__PURE__ */ jsx(Icon, { icon: meta.icon, className: "size-4" })
13675
+ }
13676
+ ),
13677
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
13678
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold leading-tight text-foreground", children: nodeTitle(node) }),
13679
+ subtitle && /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] leading-tight text-muted-foreground", children: subtitle })
13680
+ ] }),
13681
+ /* @__PURE__ */ jsx(
13682
+ "button",
13683
+ {
13684
+ type: "button",
13685
+ title: showMapper ? "Hide input data" : "Map data from input",
13686
+ "aria-label": "Toggle input mapper",
13687
+ onClick: () => setShowMapper((v) => !v),
13688
+ className: `rounded-md p-1.5 transition-colors hover:bg-muted ${showMapper ? "text-primary" : "text-muted-foreground"} hover:text-foreground`,
13689
+ "data-testid": "qc-toggle-mapper",
13690
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:tray-arrow-down", className: "size-4" })
13691
+ }
13692
+ ),
13693
+ /* @__PURE__ */ jsx(
13694
+ "button",
13695
+ {
13696
+ type: "button",
13697
+ title: "Open full editor",
13698
+ "aria-label": "Open full editor",
13699
+ onClick: expandToNdv,
13700
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
13701
+ "data-testid": "qc-expand",
13702
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:arrow-expand", className: "size-4" })
13703
+ }
13704
+ ),
13705
+ /* @__PURE__ */ jsx(
13706
+ "button",
13707
+ {
13708
+ type: "button",
13709
+ title: "Close",
13710
+ "aria-label": "Close",
13711
+ onClick: close,
13712
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
13713
+ "data-testid": "qc-close",
13714
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "size-4" })
13715
+ }
13716
+ )
13717
+ ]
13718
+ }
13719
+ ),
13720
+ /* @__PURE__ */ jsx(
13721
+ "div",
13722
+ {
13723
+ className: "min-h-0 flex-1 space-y-4 overflow-auto p-3",
13724
+ "data-testid": "qc-params",
13725
+ children: /* @__PURE__ */ jsx(
13726
+ NodeParamsForm,
13727
+ {
13728
+ config,
13729
+ nodeType: node.type,
13730
+ testIdPrefix: "qc"
13731
+ }
13732
+ )
13733
+ }
13734
+ ),
13735
+ /* @__PURE__ */ jsx("p", { className: "shrink-0 border-t border-border px-3 py-1.5 text-[10px] leading-snug text-muted-foreground", children: "Changes apply instantly \xB7 double-click the node for the full editor" })
13736
+ ]
13737
+ }
13738
+ )
13739
+ ]
13740
+ }
13741
+ );
13742
+ }
13295
13743
  var STATUS_STYLES = {
13296
13744
  DRAFT: { label: "Draft", variant: "secondary" },
13297
13745
  ACTIVE: { label: "Active", variant: "default" },
@@ -13786,6 +14234,7 @@ var FlowBuilder = ({ readOnly = false }) => {
13786
14234
  ];
13787
14235
  })
13788
14236
  );
14237
+ const closeQuickConfig = useFlowStore((s) => s.actions.quickConfig.close);
13789
14238
  const [nodeTypesVersion, setNodeTypesVersion] = useState(0);
13790
14239
  useEffect(() => {
13791
14240
  return onNodeTypesUpdate(() => setNodeTypesVersion((v) => v + 1));
@@ -14040,6 +14489,7 @@ var FlowBuilder = ({ readOnly = false }) => {
14040
14489
  onNodeDragStop: (_, node) => {
14041
14490
  autoAdjustNode(node);
14042
14491
  },
14492
+ onPaneClick: closeQuickConfig,
14043
14493
  onNodesDelete,
14044
14494
  isValidConnection,
14045
14495
  nodesDraggable: !isReadOnly,
@@ -14072,6 +14522,7 @@ var FlowBuilder = ({ readOnly = false }) => {
14072
14522
  /* @__PURE__ */ jsx(ChatPanel, {}),
14073
14523
  /* @__PURE__ */ jsx(LogsPanel, {}),
14074
14524
  /* @__PURE__ */ jsx(NodeDetailView, {}),
14525
+ /* @__PURE__ */ jsx(QuickConfigPanel, {}),
14075
14526
  /* @__PURE__ */ jsx(
14076
14527
  "div",
14077
14528
  {
@@ -14310,7 +14761,7 @@ function WorkflowItem({
14310
14761
  };
14311
14762
  const requiredNodeTypes = [];
14312
14763
  try {
14313
- const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-K5JPCH3D.js');
14764
+ const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-QKAWYAOT.js');
14314
14765
  await ensureNodeConfigsLoaded2().catch(() => {
14315
14766
  });
14316
14767
  const workflowNodeTypes = [
@@ -14608,5 +15059,5 @@ function WorkflowBuilder() {
14608
15059
  }
14609
15060
 
14610
15061
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, FlowBuilderPage, FluidGridsProvider, TemplateCard, WorkflowBuilder, useFluidGridsContext, useImportWorkflowTemplate, useWorkflowTemplates };
14611
- //# sourceMappingURL=chunk-2XMPJHCW.js.map
14612
- //# sourceMappingURL=chunk-2XMPJHCW.js.map
15062
+ //# sourceMappingURL=chunk-KOXWDM2E.js.map
15063
+ //# sourceMappingURL=chunk-KOXWDM2E.js.map