@burdenoff/fluidgrids-fe-sdk 2026.801.1 → 2026.801.3

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-AI5S3GO5.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';
@@ -2432,10 +2432,15 @@ function StoreCategoryList({
2432
2432
  searchTerm,
2433
2433
  onSearchChange,
2434
2434
  onSelectCategory,
2435
- onSelectNode
2435
+ onSelectNode,
2436
+ autoFocusSearch = false
2436
2437
  }) {
2437
2438
  const isSearching = searchTerm.trim().length > 0;
2438
2439
  const [showDeprecated, setShowDeprecated] = useState(false);
2440
+ const searchRef = useRef(null);
2441
+ useEffect(() => {
2442
+ if (autoFocusSearch) searchRef.current?.focus();
2443
+ }, [autoFocusSearch]);
2439
2444
  const { products, loading, hasMore, fetchMore } = useStoreNodes({
2440
2445
  filter: {
2441
2446
  type: "NODE",
@@ -2459,6 +2464,7 @@ function StoreCategoryList({
2459
2464
  /* @__PURE__ */ jsx(
2460
2465
  Input$1,
2461
2466
  {
2467
+ ref: searchRef,
2462
2468
  value: searchTerm,
2463
2469
  onChange: (e) => {
2464
2470
  onSearchChange(e.target.value);
@@ -2868,6 +2874,57 @@ var UninstallNodeDialog = ({
2868
2874
  ] })
2869
2875
  ] }) });
2870
2876
  };
2877
+
2878
+ // src/builder/hooks/attach-node-helpers.ts
2879
+ var ATTACH_GAP_X = 120;
2880
+ var DEFAULT_NODE_WIDTH = 288;
2881
+ function computeAttachPosition(source) {
2882
+ if (!source) return void 0;
2883
+ const width = source.measured?.width ?? DEFAULT_NODE_WIDTH;
2884
+ return {
2885
+ x: source.position.x + width + ATTACH_GAP_X,
2886
+ y: source.position.y
2887
+ };
2888
+ }
2889
+ function firstInputHandleId(data) {
2890
+ return resolveInputHandles(data)[0]?.id ?? "target";
2891
+ }
2892
+
2893
+ // src/builder/hooks/use-attach-store-node.ts
2894
+ function useAddNodeFromStore() {
2895
+ const { getNode } = useReactFlow();
2896
+ const insertNode = useInsertNode();
2897
+ return useCallback(
2898
+ (nodeType, operationName) => {
2899
+ const store = useFlowStore.getState();
2900
+ const pending = store.workflow.pendingConnection;
2901
+ if (!pending) {
2902
+ insertNode(nodeType, void 0, operationName);
2903
+ return { attached: false };
2904
+ }
2905
+ const source = getNode(pending.nodeId);
2906
+ const created = insertNode(
2907
+ nodeType,
2908
+ computeAttachPosition(source),
2909
+ operationName
2910
+ );
2911
+ if (created?.id && source) {
2912
+ store.actions.edges.onConnect({
2913
+ source: pending.nodeId,
2914
+ sourceHandle: pending.handleId,
2915
+ target: created.id,
2916
+ targetHandle: firstInputHandleId(
2917
+ created.data
2918
+ )
2919
+ });
2920
+ }
2921
+ store.actions.pendingConnection.clear();
2922
+ store.actions.sidebar.setActivePanel("none");
2923
+ return { attached: true };
2924
+ },
2925
+ [getNode, insertNode]
2926
+ );
2927
+ }
2871
2928
  function isTriggerOperation(op) {
2872
2929
  return op.role === "trigger" || !!op.delivery || op.type === "subscription";
2873
2930
  }
@@ -2879,6 +2936,9 @@ function userSelectableOperations(ops) {
2879
2936
  function StoreNodeOperations({ nodeType }) {
2880
2937
  const [, bump] = useState(0);
2881
2938
  useEffect(() => onNodeTypesUpdate(() => bump((n) => n + 1)), []);
2939
+ const pendingConnection = useFlowStore((s) => s.workflow.pendingConnection);
2940
+ const addNodeFromStore = useAddNodeFromStore();
2941
+ const attachMode = !!pendingConnection;
2882
2942
  const config = nodeType ? NODE_CONFIGS.find((c) => c.type === nodeType) : void 0;
2883
2943
  const operations = userSelectableOperations(
2884
2944
  config?.operations
@@ -2893,6 +2953,13 @@ function StoreNodeOperations({ nodeType }) {
2893
2953
  },
2894
2954
  [nodeType]
2895
2955
  );
2956
+ const handleClick = useCallback(
2957
+ (opName) => {
2958
+ if (!nodeType) return;
2959
+ addNodeFromStore(nodeType, opName);
2960
+ },
2961
+ [nodeType, addNodeFromStore]
2962
+ );
2896
2963
  if (!nodeType) return null;
2897
2964
  if (operations.length === 0) {
2898
2965
  return /* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1", children: /* @__PURE__ */ jsxs(
@@ -2900,12 +2967,14 @@ function StoreNodeOperations({ nodeType }) {
2900
2967
  {
2901
2968
  draggable: true,
2902
2969
  onDragStart: (e) => handleDragStart(e),
2903
- title: "Drag to add to canvas",
2970
+ onClick: () => handleClick(),
2971
+ title: attachMode ? "Click to attach to the selected output" : "Click to add, or drag onto the canvas",
2904
2972
  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" },
2973
+ style: { cursor: attachMode ? "pointer" : "grab" },
2974
+ "data-testid": "store-op-add-to-canvas",
2906
2975
  children: [
2907
2976
  /* @__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" }),
2977
+ /* @__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
2978
  /* @__PURE__ */ jsx(
2910
2979
  Icon,
2911
2980
  {
@@ -2918,17 +2987,22 @@ function StoreNodeOperations({ nodeType }) {
2918
2987
  ) });
2919
2988
  }
2920
2989
  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" }),
2990
+ /* @__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
2991
  /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: operations.map((op) => {
2923
2992
  const isTrigger = isTriggerOperation(op);
2993
+ const attachBlocked = attachMode && isTrigger;
2924
2994
  return /* @__PURE__ */ jsxs(
2925
2995
  "button",
2926
2996
  {
2927
2997
  draggable: true,
2928
2998
  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" },
2999
+ onClick: attachBlocked ? void 0 : () => handleClick(op.name),
3000
+ 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`,
3001
+ 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"}`,
3002
+ style: {
3003
+ cursor: attachBlocked ? "not-allowed" : attachMode ? "pointer" : "grab"
3004
+ },
3005
+ "data-testid": `store-op-${op.name}`,
2932
3006
  children: [
2933
3007
  /* @__PURE__ */ jsx("div", { className: "size-7 shrink-0 rounded-md bg-muted flex items-center justify-center", children: /* @__PURE__ */ jsx(
2934
3008
  Icon,
@@ -3345,10 +3419,70 @@ function StoreNodeDetail({ node, onBack }) {
3345
3419
  )
3346
3420
  ] });
3347
3421
  }
3422
+ function AttachModeBanner() {
3423
+ const pendingConnection = useFlowStore((s) => s.workflow.pendingConnection);
3424
+ const nodes = useFlowStore((s) => s.workflow.nodes);
3425
+ const clearPending = useFlowStore((s) => s.actions.pendingConnection.clear);
3426
+ if (!pendingConnection) return null;
3427
+ const source = nodes.find((n) => n.id === pendingConnection.nodeId);
3428
+ const data = source?.data;
3429
+ const sourceTitle = typeof data?.label === "string" && data.label || getNodeDetail(source?.type).title || pendingConnection.nodeId;
3430
+ const handleLabel = pendingConnection.handleId === "source" || pendingConnection.handleId === "output" ? void 0 : pendingConnection.handleId;
3431
+ return /* @__PURE__ */ jsx(
3432
+ "div",
3433
+ {
3434
+ className: "mx-3 mt-3 shrink-0 rounded-lg border px-3 py-2",
3435
+ style: {
3436
+ borderColor: "var(--color-status-info-border)",
3437
+ background: "var(--color-status-info-bgSubtle)"
3438
+ },
3439
+ "data-testid": "store-attach-banner",
3440
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3441
+ /* @__PURE__ */ jsx(
3442
+ Icon,
3443
+ {
3444
+ icon: "mynaui:git-branch",
3445
+ className: "size-4 shrink-0",
3446
+ style: { color: "var(--color-status-info-text)" }
3447
+ }
3448
+ ),
3449
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3450
+ /* @__PURE__ */ jsxs(
3451
+ "p",
3452
+ {
3453
+ className: "text-xs font-semibold leading-tight",
3454
+ style: { color: "var(--color-status-info-text)" },
3455
+ children: [
3456
+ "Attaching after \u201C",
3457
+ sourceTitle,
3458
+ "\u201D",
3459
+ handleLabel ? ` \u2192 ${handleLabel}` : ""
3460
+ ]
3461
+ }
3462
+ ),
3463
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "Click an operation below to add & connect it." })
3464
+ ] }),
3465
+ /* @__PURE__ */ jsx(
3466
+ "button",
3467
+ {
3468
+ type: "button",
3469
+ "aria-label": "Cancel attach",
3470
+ title: "Cancel attach",
3471
+ onClick: clearPending,
3472
+ className: "shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3473
+ "data-testid": "store-attach-cancel",
3474
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "size-3.5" })
3475
+ }
3476
+ )
3477
+ ] })
3478
+ }
3479
+ );
3480
+ }
3348
3481
  function StoreBrowsePanel() {
3349
3482
  const [view, setView] = useState({ type: "categories" });
3350
3483
  const [globalSearch, setGlobalSearch] = useState("");
3351
3484
  const [, setNodesReady] = useState(isNodeConfigsInitialized);
3485
+ const attachArmed = useFlowStore((s) => !!s.workflow.pendingConnection);
3352
3486
  useEffect(() => {
3353
3487
  void ensureNodeConfigsLoaded().then(() => setNodesReady(true));
3354
3488
  }, []);
@@ -3370,6 +3504,7 @@ function StoreBrowsePanel() {
3370
3504
  });
3371
3505
  }, []);
3372
3506
  return /* @__PURE__ */ jsxs(SidebarPanelWrapper, { children: [
3507
+ /* @__PURE__ */ jsx(AttachModeBanner, {}),
3373
3508
  view.type === "categories" && /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4 pb-2 shrink-0 flex items-center gap-2", children: [
3374
3509
  /* @__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
3510
  /* @__PURE__ */ jsxs("div", { children: [
@@ -3384,7 +3519,8 @@ function StoreBrowsePanel() {
3384
3519
  searchTerm: globalSearch,
3385
3520
  onSearchChange: setGlobalSearch,
3386
3521
  onSelectCategory: openCategory,
3387
- onSelectNode: openNodeDetail
3522
+ onSelectNode: openNodeDetail,
3523
+ autoFocusSearch: attachArmed
3388
3524
  }
3389
3525
  ),
3390
3526
  view.type === "category-nodes" && /* @__PURE__ */ jsx(
@@ -4075,7 +4211,7 @@ function DesktopSidebarFragment({
4075
4211
  if (nodes) {
4076
4212
  const requiredNodeTypes = [];
4077
4213
  try {
4078
- const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-K5JPCH3D.js');
4214
+ const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-5FEPZOHW.js');
4079
4215
  await ensureNodeConfigsLoaded2().catch(() => {
4080
4216
  });
4081
4217
  const workflowNodeTypes = [
@@ -12629,6 +12765,8 @@ function useWebhookRegistration(workflowKey, triggerNodeId, enabled = true) {
12629
12765
  refetch: fetchData
12630
12766
  };
12631
12767
  }
12768
+
12769
+ // src/builder/components/ndv/use-node-config.ts
12632
12770
  function coerceNumber(type, raw) {
12633
12771
  if (raw.trim() === "") return void 0;
12634
12772
  const n = type === "integer" ? parseInt(raw, 10) : parseFloat(raw);
@@ -12673,66 +12811,32 @@ function nodeSubtitle(node) {
12673
12811
  const data = asRecord(node?.data);
12674
12812
  return typeof data?.operation_name === "string" ? data.operation_name : void 0;
12675
12813
  }
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);
12814
+ function useNodeConfig(target, options) {
12679
12815
  const nodes = useFlowStore((s) => s.workflow.nodes);
12680
12816
  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;
12817
+ const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
12818
+ const nodeId = target?.nodeId;
12686
12819
  const node = useMemo(
12687
- () => ndv ? nodes.find((n) => n.id === ndv.nodeId) : void 0,
12688
- [ndv, nodes]
12820
+ () => nodeId ? nodes.find((n) => n.id === nodeId) : void 0,
12821
+ [nodeId, nodes]
12689
12822
  );
12690
12823
  const incoming = useMemo(
12691
- () => ndv ? edges.filter((e) => e.target === ndv.nodeId) : [],
12692
- [edges, ndv]
12824
+ () => nodeId ? edges.filter((e) => e.target === nodeId) : [],
12825
+ [edges, nodeId]
12693
12826
  );
12694
12827
  const sourceIds = useMemo(() => incoming.map((e) => e.source), [incoming]);
12695
12828
  const sourceKey = sourceIds.join(",");
12696
12829
  const [selectedSource, setSelectedSource] = useState();
12697
- const [tab, setTab] = useState("parameters");
12698
- const [outputView, setOutputView] = useState("schema");
12699
12830
  const [dragField, setDragField] = useState(null);
12700
12831
  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
12832
  useEffect(() => {
12726
12833
  setSelectedSource(
12727
12834
  (prev) => prev && sourceIds.includes(prev) ? prev : sourceIds[0]
12728
12835
  );
12729
- }, [ndv?.nodeId, sourceKey, sourceIds]);
12730
- useEffect(() => {
12731
- if (open) setTab("parameters");
12732
- }, [open, ndv?.nodeId]);
12836
+ }, [nodeId, sourceKey, sourceIds]);
12733
12837
  useEffect(() => {
12734
12838
  setModeByField({});
12735
- }, [ndv?.nodeId, selectedSource]);
12839
+ }, [nodeId, selectedSource]);
12736
12840
  const sourceNode = useMemo(
12737
12841
  () => nodes.find((n) => n.id === selectedSource),
12738
12842
  [nodes, selectedSource]
@@ -12762,11 +12866,10 @@ function NodeDetailView() {
12762
12866
  () => !!asRecord(currentOp?.output_fields)?.webhookUrl,
12763
12867
  [currentOp]
12764
12868
  );
12765
- const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
12766
12869
  const { webhookUrl, loading: webhookUrlLoading } = useWebhookRegistration(
12767
12870
  workflowKey || void 0,
12768
12871
  node?.id,
12769
- isWebhookTrigger && open
12872
+ isWebhookTrigger && (options?.webhookEnabled ?? true)
12770
12873
  );
12771
12874
  const pickerFields = useMemo(() => flattenFields(inputFields), [inputFields]);
12772
12875
  const mapping = currentEdge?.mapping?.definition ?? {};
@@ -12799,10 +12902,10 @@ function NodeDetailView() {
12799
12902
  return fromList(args?.credentials, "id");
12800
12903
  }, [node]);
12801
12904
  const handleConnectionsChange = (connections) => {
12802
- if (!ndv) return;
12905
+ if (!nodeId) return;
12803
12906
  const store = useFlowStore.getState();
12804
12907
  const next = store.workflow.nodes.map((n) => {
12805
- if (n.id !== ndv.nodeId) return n;
12908
+ if (n.id !== nodeId) return n;
12806
12909
  const data = { ...asRecord(n.data) ?? {} };
12807
12910
  const args = { ...asRecord(data.args) ?? {} };
12808
12911
  delete args.credentials;
@@ -12828,19 +12931,19 @@ function NodeDetailView() {
12828
12931
  ...e,
12829
12932
  mapping: {
12830
12933
  definition: def,
12831
- comment: e.mapping?.comment ?? `Map ${selectedSource} \u2192 ${ndv?.nodeId ?? ""}`
12934
+ comment: e.mapping?.comment ?? `Map ${selectedSource} \u2192 ${nodeId ?? ""}`
12832
12935
  }
12833
12936
  };
12834
12937
  });
12835
12938
  store.actions.edges.setEdges(next);
12836
12939
  };
12837
12940
  const clearMappingFromAllIncoming = (field) => {
12838
- if (!ndv) return;
12941
+ if (!nodeId) return;
12839
12942
  const store = useFlowStore.getState();
12840
12943
  const fresh = store.workflow.edges;
12841
12944
  let changed = false;
12842
12945
  const next = fresh.map((e) => {
12843
- if (e.target !== ndv.nodeId || !e.mapping?.definition) return e;
12946
+ if (e.target !== nodeId || !e.mapping?.definition) return e;
12844
12947
  if (!(field in e.mapping.definition)) return e;
12845
12948
  const def = { ...e.mapping.definition };
12846
12949
  delete def[field];
@@ -12850,10 +12953,10 @@ function NodeDetailView() {
12850
12953
  if (changed) store.actions.edges.setEdges(next);
12851
12954
  };
12852
12955
  const writeFixed = (field, value) => {
12853
- if (!ndv) return;
12956
+ if (!nodeId) return;
12854
12957
  const store = useFlowStore.getState();
12855
12958
  const next = store.workflow.nodes.map((n) => {
12856
- if (n.id !== ndv.nodeId) return n;
12959
+ if (n.id !== nodeId) return n;
12857
12960
  const data = { ...asRecord(n.data) ?? {} };
12858
12961
  const args = { ...asRecord(data.args) ?? {} };
12859
12962
  const state = { ...asRecord(args.state) ?? {} };
@@ -12887,6 +12990,279 @@ function NodeDetailView() {
12887
12990
  setModeByField((m) => ({ ...m, [field]: "expression" }));
12888
12991
  }
12889
12992
  };
12993
+ return {
12994
+ node,
12995
+ nodes,
12996
+ incoming,
12997
+ sourceIds,
12998
+ selectedSource,
12999
+ setSelectedSource,
13000
+ currentEdge,
13001
+ currentOp,
13002
+ params,
13003
+ argsState,
13004
+ mapping,
13005
+ inputFields,
13006
+ outputFields,
13007
+ pickerFields,
13008
+ connectionAdapters,
13009
+ selectedConnections,
13010
+ isWebhookTrigger,
13011
+ webhookUrl,
13012
+ webhookUrlLoading,
13013
+ modeByField,
13014
+ dragField,
13015
+ setDragField,
13016
+ writeFixed,
13017
+ writeMapping,
13018
+ handleConnectionsChange,
13019
+ toggleMode,
13020
+ handleDropOnParam
13021
+ };
13022
+ }
13023
+ var SECTION_LABEL2 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
13024
+ function NodeParamsForm({
13025
+ config,
13026
+ nodeType,
13027
+ testIdPrefix = "ndv"
13028
+ }) {
13029
+ const {
13030
+ params,
13031
+ argsState,
13032
+ mapping,
13033
+ pickerFields,
13034
+ connectionAdapters,
13035
+ selectedConnections,
13036
+ isWebhookTrigger,
13037
+ webhookUrl,
13038
+ webhookUrlLoading,
13039
+ currentEdge,
13040
+ modeByField,
13041
+ dragField,
13042
+ setDragField,
13043
+ writeFixed,
13044
+ writeMapping,
13045
+ handleConnectionsChange,
13046
+ toggleMode,
13047
+ handleDropOnParam
13048
+ } = config;
13049
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
13050
+ isWebhookTrigger && /* @__PURE__ */ jsx(WebhookUrlField, { webhookUrl, loading: webhookUrlLoading }),
13051
+ connectionAdapters.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-connections`, children: [
13052
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL2, children: "Connections" }),
13053
+ /* @__PURE__ */ jsx(
13054
+ ConnectionSelector,
13055
+ {
13056
+ adapters: connectionAdapters,
13057
+ selectedConnections,
13058
+ onChange: handleConnectionsChange,
13059
+ compact: true
13060
+ }
13061
+ )
13062
+ ] }),
13063
+ !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." }),
13064
+ 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) => {
13065
+ if (p.type === "condition") {
13066
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
13067
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name === "conditionTree" ? "Conditions" : p.name }),
13068
+ /* @__PURE__ */ jsx(
13069
+ ConditionTreeBuilder,
13070
+ {
13071
+ value: argsState[p.name],
13072
+ fields: pickerFields,
13073
+ onChange: (tree) => writeFixed(p.name, tree)
13074
+ }
13075
+ ),
13076
+ p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
13077
+ ] }, p.name);
13078
+ }
13079
+ if (p.type === "switch-rules") {
13080
+ const rules = Array.isArray(argsState[p.name]) ? argsState[p.name] : [];
13081
+ const fallbackHandle = typeof argsState.fallbackHandle === "string" ? argsState.fallbackHandle : "default";
13082
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
13083
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: "Routing rules" }),
13084
+ /* @__PURE__ */ jsx(
13085
+ SwitchRulesEditor,
13086
+ {
13087
+ rules,
13088
+ fallbackHandle,
13089
+ fields: pickerFields,
13090
+ onChange: (next) => {
13091
+ writeFixed(p.name, next.rules);
13092
+ writeFixed("cases", casesFromRules(next.rules));
13093
+ writeFixed("fallbackHandle", next.fallbackHandle);
13094
+ }
13095
+ }
13096
+ )
13097
+ ] }, p.name);
13098
+ }
13099
+ const mode = modeByField[p.name] ?? (mapping[p.name] !== void 0 ? "expression" : "fixed");
13100
+ const exprValue = mapping[p.name] ?? "";
13101
+ const fixedValue = argsState[p.name];
13102
+ const isCodeParam = p.type === "code" || nodeType === "internal:code" && p.name === "code";
13103
+ const highlight = dragField === p.name;
13104
+ const inputCx = cn$1(
13105
+ "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors disabled:opacity-50",
13106
+ highlight ? "border-primary ring-1 ring-primary" : "border-input focus:border-primary"
13107
+ );
13108
+ return /* @__PURE__ */ jsxs(
13109
+ "div",
13110
+ {
13111
+ className: "flex flex-col gap-1.5",
13112
+ onDragOver: (e) => e.preventDefault(),
13113
+ onDragEnter: () => setDragField(p.name),
13114
+ onDragLeave: () => setDragField((f) => f === p.name ? null : f),
13115
+ onDrop: handleDropOnParam(p.name),
13116
+ children: [
13117
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
13118
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: p.name }),
13119
+ p.required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" }),
13120
+ /* @__PURE__ */ jsx("span", { className: "ml-auto text-[10px] uppercase tracking-wide text-muted-foreground", children: p.type }),
13121
+ /* @__PURE__ */ jsx(
13122
+ "button",
13123
+ {
13124
+ type: "button",
13125
+ onClick: () => toggleMode(p.name, mode),
13126
+ title: mode === "expression" ? "Switch to a fixed value" : "Switch to an expression",
13127
+ "data-testid": `${testIdPrefix}-mode-${p.name}`,
13128
+ className: cn$1(
13129
+ "ml-1 rounded p-0.5 hover:text-foreground",
13130
+ mode === "expression" ? "text-primary" : "text-muted-foreground"
13131
+ ),
13132
+ children: /* @__PURE__ */ jsx(
13133
+ Icon,
13134
+ {
13135
+ icon: mode === "expression" ? "mdi:variable" : "mdi:form-textbox",
13136
+ className: "size-3.5"
13137
+ }
13138
+ )
13139
+ }
13140
+ )
13141
+ ] }),
13142
+ mode === "expression" ? /* @__PURE__ */ jsx(
13143
+ "input",
13144
+ {
13145
+ type: "text",
13146
+ value: exprValue,
13147
+ placeholder: currentEdge ? "Drop a field or type ${output.\u2026}" : "Connect an upstream node to use expressions",
13148
+ disabled: !currentEdge,
13149
+ onChange: (e) => writeMapping(p.name, e.target.value),
13150
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13151
+ className: cn$1(inputCx, "font-mono text-primary")
13152
+ }
13153
+ ) : isCodeParam ? /* @__PURE__ */ jsx(
13154
+ CodeEditorField,
13155
+ {
13156
+ value: typeof fixedValue === "string" ? fixedValue : "",
13157
+ language: normalizeCodeLanguage(
13158
+ argsState.runtime ?? params.find((f) => f.name === "runtime")?.defaultValue
13159
+ ),
13160
+ placeholder: typeof p.defaultValue === "string" ? p.defaultValue : p.description,
13161
+ onChange: (v) => writeFixed(p.name, v),
13162
+ "data-testid": `${testIdPrefix}-param-${p.name}`
13163
+ }
13164
+ ) : p.type === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
13165
+ /* @__PURE__ */ jsx(
13166
+ "input",
13167
+ {
13168
+ type: "checkbox",
13169
+ checked: fixedValue === true,
13170
+ onChange: (e) => writeFixed(p.name, e.target.checked),
13171
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13172
+ className: "size-4"
13173
+ }
13174
+ ),
13175
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: fixedValue === void 0 ? "Not set (default)" : fixedValue === true ? "true" : "false" })
13176
+ ] }) : p.options && p.options.length > 0 ? /* @__PURE__ */ jsxs(
13177
+ "select",
13178
+ {
13179
+ value: typeof fixedValue === "string" ? fixedValue : "",
13180
+ onChange: (e) => writeFixed(p.name, e.target.value),
13181
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13182
+ className: inputCx,
13183
+ children: [
13184
+ /* @__PURE__ */ jsx("option", { value: "", children: p.defaultValue !== void 0 ? `Default (${String(p.defaultValue)})` : "Select\u2026" }),
13185
+ p.options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
13186
+ ]
13187
+ }
13188
+ ) : p.type === "integer" || p.type === "number" ? /* @__PURE__ */ jsx(
13189
+ "input",
13190
+ {
13191
+ type: "number",
13192
+ step: p.type === "integer" ? "1" : "any",
13193
+ value: typeof fixedValue === "number" ? String(fixedValue) : "",
13194
+ placeholder: p.defaultValue !== void 0 ? `Default: ${String(p.defaultValue)}` : "Enter a number",
13195
+ onChange: (e) => writeFixed(p.name, coerceNumber(p.type, e.target.value)),
13196
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13197
+ className: inputCx
13198
+ }
13199
+ ) : /* @__PURE__ */ jsx(
13200
+ "input",
13201
+ {
13202
+ type: "text",
13203
+ value: fixedValue === void 0 || fixedValue === null ? "" : String(fixedValue),
13204
+ placeholder: p.defaultValue !== void 0 ? `Default: ${String(p.defaultValue)}` : "Enter a value",
13205
+ onChange: (e) => writeFixed(p.name, e.target.value),
13206
+ "data-testid": `${testIdPrefix}-param-${p.name}`,
13207
+ className: inputCx
13208
+ }
13209
+ ),
13210
+ p.description && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: p.description })
13211
+ ]
13212
+ },
13213
+ p.name
13214
+ );
13215
+ })
13216
+ ] });
13217
+ }
13218
+ var SECTION_LABEL3 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
13219
+ function NodeDetailView() {
13220
+ const ndv = useFlowStore((s) => s.workflow.ndv);
13221
+ const closeNdv = useFlowStore((s) => s.actions.ndv.close);
13222
+ const showNodePropertiesOf = useFlowStore(
13223
+ (s) => s.actions.sidebar.showNodePropertiesOf
13224
+ );
13225
+ const open = !!ndv;
13226
+ const config = useNodeConfig(ndv ?? null, { webhookEnabled: open });
13227
+ const {
13228
+ node,
13229
+ nodes,
13230
+ sourceIds,
13231
+ selectedSource,
13232
+ setSelectedSource,
13233
+ currentOp,
13234
+ inputFields,
13235
+ outputFields
13236
+ } = config;
13237
+ const [tab, setTab] = useState("parameters");
13238
+ const [outputView, setOutputView] = useState("schema");
13239
+ const panesRef = useRef(null);
13240
+ const dragCleanupRef = useRef(null);
13241
+ const [leftPct, setLeftPct] = useState(30);
13242
+ const [rightPct, setRightPct] = useState(30);
13243
+ const startDrag = (which) => (e) => {
13244
+ e.preventDefault();
13245
+ const el = panesRef.current;
13246
+ if (!el) return;
13247
+ const rect = el.getBoundingClientRect();
13248
+ const onMove = (ev) => {
13249
+ const pct = (ev.clientX - rect.left) / rect.width * 100;
13250
+ if (which === "left") setLeftPct(Math.min(55, Math.max(15, pct)));
13251
+ else setRightPct(Math.min(55, Math.max(15, 100 - pct)));
13252
+ };
13253
+ const cleanup = () => {
13254
+ window.removeEventListener("mousemove", onMove);
13255
+ window.removeEventListener("mouseup", cleanup);
13256
+ dragCleanupRef.current = null;
13257
+ };
13258
+ dragCleanupRef.current = cleanup;
13259
+ window.addEventListener("mousemove", onMove);
13260
+ window.addEventListener("mouseup", cleanup);
13261
+ };
13262
+ useEffect(() => () => dragCleanupRef.current?.(), []);
13263
+ useEffect(() => {
13264
+ if (open) setTab("parameters");
13265
+ }, [open, ndv?.nodeId]);
12890
13266
  const openClassicPanel = () => {
12891
13267
  if (!ndv) return;
12892
13268
  showNodePropertiesOf({
@@ -12944,7 +13320,7 @@ function NodeDetailView() {
12944
13320
  className: "flex min-h-0 min-w-[180px] flex-col bg-card/40",
12945
13321
  children: [
12946
13322
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-2.5", children: [
12947
- /* @__PURE__ */ jsx("span", { className: SECTION_LABEL2, children: "Input" }),
13323
+ /* @__PURE__ */ jsx("span", { className: SECTION_LABEL3, children: "Input" }),
12948
13324
  sourceIds.length > 0 && /* @__PURE__ */ jsx(
12949
13325
  "select",
12950
13326
  {
@@ -12997,188 +13373,19 @@ function NodeDetailView() {
12997
13373
  }
12998
13374
  )
12999
13375
  ] }),
13000
- tab === "parameters" ? /* @__PURE__ */ jsxs(
13376
+ tab === "parameters" ? /* @__PURE__ */ jsx(
13001
13377
  "div",
13002
13378
  {
13003
13379
  className: "min-h-0 flex-1 space-y-4 overflow-auto p-4",
13004
13380
  "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
- ]
13381
+ children: /* @__PURE__ */ jsx(
13382
+ NodeParamsForm,
13383
+ {
13384
+ config,
13385
+ nodeType: node?.type,
13386
+ testIdPrefix: "ndv"
13387
+ }
13388
+ )
13182
13389
  }
13183
13390
  ) : /* @__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
13391
  ] }),
@@ -13190,7 +13397,7 @@ function NodeDetailView() {
13190
13397
  className: "flex min-h-0 min-w-[180px] flex-col bg-card/40",
13191
13398
  children: [
13192
13399
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-2.5", children: [
13193
- /* @__PURE__ */ jsx("span", { className: SECTION_LABEL2, children: "Output" }),
13400
+ /* @__PURE__ */ jsx("span", { className: SECTION_LABEL3, children: "Output" }),
13194
13401
  outputFields.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
13195
13402
  /* @__PURE__ */ jsx(
13196
13403
  ToggleChip,
@@ -13292,6 +13499,261 @@ function EmptyState({
13292
13499
  /* @__PURE__ */ jsx("div", { className: "max-w-[14rem] text-xs text-muted-foreground", children: hint })
13293
13500
  ] });
13294
13501
  }
13502
+ var PANEL_WIDTH = 380;
13503
+ var PANEL_EST_HEIGHT = 480;
13504
+ var MAPPER_WIDTH = 260;
13505
+ var VIEWPORT_MARGIN = 12;
13506
+ function clampToViewport(pos) {
13507
+ const maxX = window.innerWidth - PANEL_WIDTH - VIEWPORT_MARGIN;
13508
+ const maxY = window.innerHeight - PANEL_EST_HEIGHT - VIEWPORT_MARGIN;
13509
+ return {
13510
+ x: Math.min(
13511
+ Math.max(VIEWPORT_MARGIN, pos.x),
13512
+ Math.max(VIEWPORT_MARGIN, maxX)
13513
+ ),
13514
+ y: Math.min(
13515
+ Math.max(VIEWPORT_MARGIN, pos.y),
13516
+ Math.max(VIEWPORT_MARGIN, maxY)
13517
+ )
13518
+ };
13519
+ }
13520
+ function QuickConfigPanel() {
13521
+ const quickConfig = useFlowStore((s) => s.workflow.quickConfig);
13522
+ if (!quickConfig) return null;
13523
+ return (
13524
+ // Remount per node so panel position/flyout state reset naturally.
13525
+ /* @__PURE__ */ jsx(
13526
+ QuickConfigPanelInner,
13527
+ {
13528
+ nodeId: quickConfig.nodeId,
13529
+ type: quickConfig.type,
13530
+ anchor: quickConfig.anchor
13531
+ },
13532
+ quickConfig.nodeId
13533
+ )
13534
+ );
13535
+ }
13536
+ function QuickConfigPanelInner({
13537
+ nodeId,
13538
+ type,
13539
+ anchor
13540
+ }) {
13541
+ const close = useFlowStore((s) => s.actions.quickConfig.close);
13542
+ const openNdv = useFlowStore((s) => s.actions.ndv.open);
13543
+ const config = useNodeConfig({ nodeId});
13544
+ const {
13545
+ node,
13546
+ nodes,
13547
+ sourceIds,
13548
+ selectedSource,
13549
+ setSelectedSource,
13550
+ inputFields
13551
+ } = config;
13552
+ const [pos, setPos] = useState(
13553
+ () => clampToViewport(
13554
+ anchor ?? {
13555
+ x: window.innerWidth / 2 - PANEL_WIDTH / 2,
13556
+ y: window.innerHeight / 4
13557
+ }
13558
+ )
13559
+ );
13560
+ const [showMapper, setShowMapper] = useState(false);
13561
+ const dragStateRef = useRef(null);
13562
+ const handleHeaderPointerDown = useCallback(
13563
+ (e) => {
13564
+ if (e.target.closest("button")) return;
13565
+ dragStateRef.current = {
13566
+ startX: e.clientX,
13567
+ startY: e.clientY,
13568
+ baseX: pos.x,
13569
+ baseY: pos.y
13570
+ };
13571
+ const onMove = (ev) => {
13572
+ const s = dragStateRef.current;
13573
+ if (!s) return;
13574
+ setPos(
13575
+ clampToViewport({
13576
+ x: s.baseX + (ev.clientX - s.startX),
13577
+ y: s.baseY + (ev.clientY - s.startY)
13578
+ })
13579
+ );
13580
+ };
13581
+ const onUp = () => {
13582
+ dragStateRef.current = null;
13583
+ window.removeEventListener("pointermove", onMove);
13584
+ window.removeEventListener("pointerup", onUp);
13585
+ };
13586
+ window.addEventListener("pointermove", onMove);
13587
+ window.addEventListener("pointerup", onUp);
13588
+ },
13589
+ [pos.x, pos.y]
13590
+ );
13591
+ useEffect(() => {
13592
+ const onKey = (e) => {
13593
+ if (e.key === "Escape") close();
13594
+ };
13595
+ window.addEventListener("keydown", onKey);
13596
+ return () => window.removeEventListener("keydown", onKey);
13597
+ }, [close]);
13598
+ useEffect(() => {
13599
+ if (!nodes.some((n) => n.id === nodeId)) close();
13600
+ }, [nodes, nodeId, close]);
13601
+ if (!node) return null;
13602
+ const meta = getNodeDetail(node.type);
13603
+ const accent = nodeAccent(node.type || meta.title);
13604
+ const subtitle = nodeSubtitle(node);
13605
+ const expandToNdv = () => {
13606
+ openNdv({ nodeId, type });
13607
+ };
13608
+ return /* @__PURE__ */ jsxs(
13609
+ "div",
13610
+ {
13611
+ className: "fixed",
13612
+ style: { left: pos.x, top: pos.y, zIndex: 300, width: PANEL_WIDTH },
13613
+ "data-testid": "quick-config-panel",
13614
+ children: [
13615
+ showMapper && /* @__PURE__ */ jsxs(
13616
+ "div",
13617
+ {
13618
+ className: "absolute overflow-hidden rounded-xl border border-border bg-card shadow-xl",
13619
+ style: {
13620
+ right: "100%",
13621
+ marginRight: 10,
13622
+ top: 0,
13623
+ width: MAPPER_WIDTH,
13624
+ maxHeight: "min(60vh, 480px)",
13625
+ display: "flex",
13626
+ flexDirection: "column"
13627
+ },
13628
+ "data-testid": "quick-config-mapper",
13629
+ children: [
13630
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2", children: [
13631
+ /* @__PURE__ */ jsx(
13632
+ Icon,
13633
+ {
13634
+ icon: "mdi:tray-arrow-down",
13635
+ className: "size-3.5 text-muted-foreground"
13636
+ }
13637
+ ),
13638
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: "Input" }),
13639
+ sourceIds.length > 0 && /* @__PURE__ */ jsx(
13640
+ "select",
13641
+ {
13642
+ 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",
13643
+ value: selectedSource ?? "",
13644
+ onChange: (e) => setSelectedSource(e.target.value),
13645
+ "data-testid": "qc-source-selector",
13646
+ children: sourceIds.map((sid) => {
13647
+ const sn = nodes.find((n) => n.id === sid);
13648
+ return /* @__PURE__ */ jsx("option", { value: sid, children: nodeTitle(sn) }, sid);
13649
+ })
13650
+ }
13651
+ )
13652
+ ] }),
13653
+ /* @__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." }) }),
13654
+ /* @__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." })
13655
+ ]
13656
+ }
13657
+ ),
13658
+ /* @__PURE__ */ jsxs(
13659
+ "div",
13660
+ {
13661
+ className: "flex flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl animate-in fade-in zoom-in-95 duration-150",
13662
+ style: {
13663
+ maxHeight: "min(70vh, 560px)",
13664
+ borderTop: `3px solid ${accent}`
13665
+ },
13666
+ children: [
13667
+ /* @__PURE__ */ jsxs(
13668
+ "div",
13669
+ {
13670
+ className: "flex shrink-0 cursor-move items-center gap-2.5 border-b border-border px-3 py-2.5 select-none",
13671
+ onPointerDown: handleHeaderPointerDown,
13672
+ children: [
13673
+ /* @__PURE__ */ jsx(
13674
+ "div",
13675
+ {
13676
+ className: "flex size-7 shrink-0 items-center justify-center rounded-lg",
13677
+ style: {
13678
+ backgroundColor: `color-mix(in srgb, ${accent} 13%, transparent)`,
13679
+ color: accent,
13680
+ border: `1px solid color-mix(in srgb, ${accent} 26%, transparent)`
13681
+ },
13682
+ children: /* @__PURE__ */ jsx(Icon, { icon: meta.icon, className: "size-4" })
13683
+ }
13684
+ ),
13685
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
13686
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold leading-tight text-foreground", children: nodeTitle(node) }),
13687
+ subtitle && /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] leading-tight text-muted-foreground", children: subtitle })
13688
+ ] }),
13689
+ /* @__PURE__ */ jsx(
13690
+ "button",
13691
+ {
13692
+ type: "button",
13693
+ title: showMapper ? "Hide input data" : "Map data from input",
13694
+ "aria-label": "Toggle input mapper",
13695
+ onClick: () => setShowMapper((v) => !v),
13696
+ className: `rounded-md p-1.5 transition-colors hover:bg-muted ${showMapper ? "text-primary" : "text-muted-foreground"} hover:text-foreground`,
13697
+ "data-testid": "qc-toggle-mapper",
13698
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:tray-arrow-down", className: "size-4" })
13699
+ }
13700
+ ),
13701
+ /* @__PURE__ */ jsx(
13702
+ "button",
13703
+ {
13704
+ type: "button",
13705
+ title: "Open full editor",
13706
+ "aria-label": "Open full editor",
13707
+ onClick: expandToNdv,
13708
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
13709
+ "data-testid": "qc-expand",
13710
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:arrow-expand", className: "size-4" })
13711
+ }
13712
+ ),
13713
+ /* @__PURE__ */ jsx(
13714
+ "button",
13715
+ {
13716
+ type: "button",
13717
+ title: "Close",
13718
+ "aria-label": "Close",
13719
+ onClick: close,
13720
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
13721
+ "data-testid": "qc-close",
13722
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "size-4" })
13723
+ }
13724
+ )
13725
+ ]
13726
+ }
13727
+ ),
13728
+ /* @__PURE__ */ jsx(
13729
+ "div",
13730
+ {
13731
+ className: "min-h-0 flex-1 space-y-4 overflow-auto p-3",
13732
+ "data-testid": "qc-params",
13733
+ onFocusCapture: (e) => {
13734
+ const tag = e.target.tagName;
13735
+ if (sourceIds.length > 0 && (tag === "INPUT" || tag === "SELECT" || tag === "TEXTAREA")) {
13736
+ setShowMapper(true);
13737
+ }
13738
+ },
13739
+ children: /* @__PURE__ */ jsx(
13740
+ NodeParamsForm,
13741
+ {
13742
+ config,
13743
+ nodeType: node.type,
13744
+ testIdPrefix: "qc"
13745
+ }
13746
+ )
13747
+ }
13748
+ ),
13749
+ /* @__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" })
13750
+ ]
13751
+ }
13752
+ )
13753
+ ]
13754
+ }
13755
+ );
13756
+ }
13295
13757
  var STATUS_STYLES = {
13296
13758
  DRAFT: { label: "Draft", variant: "secondary" },
13297
13759
  ACTIVE: { label: "Active", variant: "default" },
@@ -13786,6 +14248,7 @@ var FlowBuilder = ({ readOnly = false }) => {
13786
14248
  ];
13787
14249
  })
13788
14250
  );
14251
+ const closeQuickConfig = useFlowStore((s) => s.actions.quickConfig.close);
13789
14252
  const [nodeTypesVersion, setNodeTypesVersion] = useState(0);
13790
14253
  useEffect(() => {
13791
14254
  return onNodeTypesUpdate(() => setNodeTypesVersion((v) => v + 1));
@@ -14040,6 +14503,7 @@ var FlowBuilder = ({ readOnly = false }) => {
14040
14503
  onNodeDragStop: (_, node) => {
14041
14504
  autoAdjustNode(node);
14042
14505
  },
14506
+ onPaneClick: closeQuickConfig,
14043
14507
  onNodesDelete,
14044
14508
  isValidConnection,
14045
14509
  nodesDraggable: !isReadOnly,
@@ -14072,6 +14536,7 @@ var FlowBuilder = ({ readOnly = false }) => {
14072
14536
  /* @__PURE__ */ jsx(ChatPanel, {}),
14073
14537
  /* @__PURE__ */ jsx(LogsPanel, {}),
14074
14538
  /* @__PURE__ */ jsx(NodeDetailView, {}),
14539
+ /* @__PURE__ */ jsx(QuickConfigPanel, {}),
14075
14540
  /* @__PURE__ */ jsx(
14076
14541
  "div",
14077
14542
  {
@@ -14310,7 +14775,7 @@ function WorkflowItem({
14310
14775
  };
14311
14776
  const requiredNodeTypes = [];
14312
14777
  try {
14313
- const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-K5JPCH3D.js');
14778
+ const { NODE_CONFIGS: NODE_CONFIGS3, ensureNodeConfigsLoaded: ensureNodeConfigsLoaded2 } = await import('./blocks-5FEPZOHW.js');
14314
14779
  await ensureNodeConfigsLoaded2().catch(() => {
14315
14780
  });
14316
14781
  const workflowNodeTypes = [
@@ -14608,5 +15073,5 @@ function WorkflowBuilder() {
14608
15073
  }
14609
15074
 
14610
15075
  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
15076
+ //# sourceMappingURL=chunk-7WQUDC5E.js.map
15077
+ //# sourceMappingURL=chunk-7WQUDC5E.js.map