@burdenoff/fluidgrids-fe-sdk 2026.922.1 → 2026.922.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.
Files changed (27) hide show
  1. package/dist/{WorkflowBuilder-RG4QXBZN.js → WorkflowBuilder-XZPLGBCS.js} +5 -5
  2. package/dist/{WorkflowBuilder-RG4QXBZN.js.map → WorkflowBuilder-XZPLGBCS.js.map} +1 -1
  3. package/dist/{WorkflowsList-ZBMSG2WP.js → WorkflowsList-J6IDS3SR.js} +4 -4
  4. package/dist/{WorkflowsList-ZBMSG2WP.js.map → WorkflowsList-J6IDS3SR.js.map} +1 -1
  5. package/dist/{chunk-XASNLRFQ.js → chunk-3RWKQIV4.js} +544 -28
  6. package/dist/chunk-3RWKQIV4.js.map +1 -0
  7. package/dist/chunk-I3WUYESA.js +668 -0
  8. package/dist/chunk-I3WUYESA.js.map +1 -0
  9. package/dist/{chunk-YAADZDGX.js → chunk-K4U4WX4R.js} +3 -3
  10. package/dist/{chunk-YAADZDGX.js.map → chunk-K4U4WX4R.js.map} +1 -1
  11. package/dist/{chunk-GCUAXFMW.js → chunk-SU6UYGRW.js} +4 -4
  12. package/dist/{chunk-GCUAXFMW.js.map → chunk-SU6UYGRW.js.map} +1 -1
  13. package/dist/{chunk-EFLYJTQI.js → chunk-TKUCCNEO.js} +4 -4
  14. package/dist/{chunk-EFLYJTQI.js.map → chunk-TKUCCNEO.js.map} +1 -1
  15. package/dist/{chunk-ND4ACNAW.js → chunk-Y4AM4RTD.js} +4 -4
  16. package/dist/{chunk-ND4ACNAW.js.map → chunk-Y4AM4RTD.js.map} +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js +10 -10
  19. package/dist/templates.js +3 -3
  20. package/dist/{workflow-builder-JKkdIJoP.d.ts → workflow-builder-BNWKK8C7.d.ts} +53 -2
  21. package/dist/workflow-builder.d.ts +1 -1
  22. package/dist/workflow-builder.js +4 -4
  23. package/dist/workflows-list.js +2 -2
  24. package/package.json +1 -1
  25. package/dist/chunk-KT72XMAB.js +0 -379
  26. package/dist/chunk-KT72XMAB.js.map +0 -1
  27. package/dist/chunk-XASNLRFQ.js.map +0 -1
@@ -4,7 +4,7 @@ import { Badge as Badge$1, Button as Button$1 } from './chunk-DBOMRIP5.js';
4
4
  import { getGlobalStoreClient, createGlobalStoreClient, setGlobalStoreClient, resetGlobalStoreClient } from './chunk-BQXHJIER.js';
5
5
  import { GetMyNodeInstallationsDocument, BrowseWorkflowTemplatesDocument, InstallWorkflowNodeDocument, InstallPurchasedNodeDocument, BrowseWorkflowNodesDocument, UninstallWorkflowNodeDocument, CreateStoreOrderDocument, GetNodeInstallableVersionsDocument } from './chunk-6LXMUIR3.js';
6
6
  import { deepClone, applyTemplateInputs } from './chunk-XQP33UJR.js';
7
- import { useFlowStore, QuotaExhaustedDialog } from './chunk-KT72XMAB.js';
7
+ import { useFlowStore, QuotaExhaustedDialog } from './chunk-I3WUYESA.js';
8
8
  import { SdkPermissionGate, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton } from './chunk-UIY2HBWA.js';
9
9
  import { cn as cn$1 } from './chunk-3ZYUGTIT.js';
10
10
  import { useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useDeactivateWorkflow } from './chunk-ELB3LXRJ.js';
@@ -37,7 +37,7 @@ import { ApolloProvider, useQuery, useMutation } from '@apollo/client/react';
37
37
  import { TagBadge, TagsWidget } from '@burdenoff/fe-libs/tag';
38
38
  import { gql as gql$1 } from '@apollo/client/core';
39
39
  import 'jszip';
40
- import { Sparkles, Star, Download, Eye, History, Loader2, Play, RotateCcw, Trash2, Maximize2, LayoutGrid, Plus, GitBranch, AlertCircle, Search, RefreshCw, CheckCircle2, Package, Info, Copy, BadgeCheck, DollarSign, Type, Code, Hash, List, Braces, Workflow, PlusCircle, ChevronDown, ChevronRight, Layers, X, AlertTriangle } from 'lucide-react';
40
+ import { Sparkles, Star, Download, Eye, History, Loader2, Play, RotateCcw, Trash2, Maximize2, Undo2, Redo2, LayoutGrid, Keyboard, Plus, GitBranch, AlertCircle, Search, RefreshCw, CheckCircle2, Package, Info, Copy, BadgeCheck, DollarSign, Type, Code, Hash, List, Braces, Workflow, PlusCircle, ChevronDown, ChevronRight, Layers, X, AlertTriangle } from 'lucide-react';
41
41
  import { produce } from 'immer';
42
42
  import { isEmpty } from 'radash';
43
43
  import { animate, easeOut } from 'popmotion';
@@ -4409,6 +4409,72 @@ function nodeAccent(key) {
4409
4409
  function tint(hex, pct = 14) {
4410
4410
  return `color-mix(in srgb, ${hex} ${pct}%, transparent)`;
4411
4411
  }
4412
+ function InlineEditableLabel({
4413
+ value,
4414
+ placeholder,
4415
+ editing,
4416
+ onStartEdit,
4417
+ onCommit,
4418
+ onCancel,
4419
+ className,
4420
+ inputClassName
4421
+ }) {
4422
+ const tr = useTr();
4423
+ const [draft, setDraft] = React2.useState(value);
4424
+ const inputRef = React2.useRef(null);
4425
+ React2.useEffect(() => {
4426
+ if (!editing) return;
4427
+ setDraft(value);
4428
+ const raf = requestAnimationFrame(() => {
4429
+ inputRef.current?.focus();
4430
+ inputRef.current?.select();
4431
+ });
4432
+ return () => cancelAnimationFrame(raf);
4433
+ }, [editing, value]);
4434
+ if (editing) {
4435
+ return /* @__PURE__ */ jsx(
4436
+ "input",
4437
+ {
4438
+ ref: inputRef,
4439
+ value: draft,
4440
+ onChange: (e) => setDraft(e.target.value),
4441
+ onClick: (e) => e.stopPropagation(),
4442
+ onDoubleClick: (e) => e.stopPropagation(),
4443
+ onMouseDown: (e) => e.stopPropagation(),
4444
+ onKeyDown: (e) => {
4445
+ e.stopPropagation();
4446
+ if (e.key === "Enter") {
4447
+ e.preventDefault();
4448
+ onCommit(draft.trim());
4449
+ } else if (e.key === "Escape") {
4450
+ e.preventDefault();
4451
+ onCancel();
4452
+ }
4453
+ },
4454
+ onBlur: () => onCommit(draft.trim()),
4455
+ className: cn(
4456
+ "min-w-0 flex-1 rounded border border-primary/50 bg-card px-1 text-inherit outline-none",
4457
+ inputClassName
4458
+ )
4459
+ }
4460
+ );
4461
+ }
4462
+ return /* @__PURE__ */ jsx(
4463
+ "span",
4464
+ {
4465
+ className: cn("cursor-text truncate", className),
4466
+ title: tr(
4467
+ "fluidgrids.builder.nodeCard.renameTooltip",
4468
+ "Double-click or press F2 to rename"
4469
+ ),
4470
+ onDoubleClick: (e) => {
4471
+ e.stopPropagation();
4472
+ onStartEdit();
4473
+ },
4474
+ children: value || placeholder
4475
+ }
4476
+ );
4477
+ }
4412
4478
  var NodeCard = React2.forwardRef(
4413
4479
  ({
4414
4480
  className,
@@ -4422,6 +4488,8 @@ var NodeCard = React2.forwardRef(
4422
4488
  onDelete,
4423
4489
  isExecuting = false,
4424
4490
  infoButton,
4491
+ disabled = false,
4492
+ onToggleDisabled,
4425
4493
  children,
4426
4494
  ...props
4427
4495
  }, ref) => {
@@ -4440,7 +4508,7 @@ var NodeCard = React2.forwardRef(
4440
4508
  " ",
4441
4509
  itemCount !== 1 ? tr("fluidgrids.builder.nodeCard.itemCountOther", "items") : tr("fluidgrids.builder.nodeCard.itemCountOne", "item")
4442
4510
  ] }) }),
4443
- (onSettings || onExecute || onDelete || infoButton) && /* @__PURE__ */ jsxs(
4511
+ (onSettings || onExecute || onDelete || onToggleDisabled || infoButton) && /* @__PURE__ */ jsxs(
4444
4512
  "div",
4445
4513
  {
4446
4514
  className: cn(
@@ -4494,6 +4562,29 @@ var NodeCard = React2.forwardRef(
4494
4562
  }
4495
4563
  ),
4496
4564
  infoButton,
4565
+ onToggleDisabled && /* @__PURE__ */ jsx(
4566
+ ToolbarButton2,
4567
+ {
4568
+ title: disabled ? tr(
4569
+ "fluidgrids.builder.nodeCard.enableTooltip",
4570
+ "Enable node"
4571
+ ) : tr(
4572
+ "fluidgrids.builder.nodeCard.disableTooltip",
4573
+ "Disable node (skip on run)"
4574
+ ),
4575
+ onClick: onToggleDisabled,
4576
+ children: /* @__PURE__ */ jsx(
4577
+ Icon,
4578
+ {
4579
+ icon: disabled ? "mdi:power" : "mdi:power-off",
4580
+ className: cn(
4581
+ "size-4",
4582
+ disabled ? "text-[var(--color-status-warning-text)]" : "text-muted-foreground"
4583
+ )
4584
+ }
4585
+ )
4586
+ }
4587
+ ),
4497
4588
  onDelete && /* @__PURE__ */ jsx(
4498
4589
  ToolbarButton2,
4499
4590
  {
@@ -4514,6 +4605,10 @@ var NodeCard = React2.forwardRef(
4514
4605
  ]
4515
4606
  }
4516
4607
  ),
4608
+ disabled && /* @__PURE__ */ jsx("div", { className: "absolute -top-2 right-2 z-10", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-full border border-[var(--color-status-warning-border)] bg-[var(--color-status-warning-bg)] px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-[var(--color-status-warning-text)] shadow-sm", children: [
4609
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:power-off", className: "size-2.5" }),
4610
+ tr("fluidgrids.builder.nodeCard.disabledBadge", "Disabled")
4611
+ ] }) }),
4517
4612
  /* @__PURE__ */ jsxs(
4518
4613
  "div",
4519
4614
  {
@@ -4523,6 +4618,7 @@ var NodeCard = React2.forwardRef(
4523
4618
  "relative w-72 rounded-xl border border-border bg-card shadow-sm will-change-transform",
4524
4619
  statusRing,
4525
4620
  executionStatus === "success" && "animate-success-glow",
4621
+ disabled && "opacity-50 grayscale-[0.6]",
4526
4622
  className
4527
4623
  ),
4528
4624
  style: {
@@ -4609,7 +4705,17 @@ function StatusCorner({
4609
4705
  ) });
4610
4706
  }
4611
4707
  var NodeCardHeader = React2.forwardRef(
4612
- ({ icon, appName, accentKey, missingCredentials = false }, ref) => {
4708
+ ({
4709
+ icon,
4710
+ appName,
4711
+ accentKey,
4712
+ missingCredentials = false,
4713
+ label,
4714
+ isRenaming = false,
4715
+ onStartRename,
4716
+ onCommitRename,
4717
+ onCancelRename
4718
+ }, ref) => {
4613
4719
  const tr = useTr();
4614
4720
  const accent = nodeAccent(accentKey);
4615
4721
  return /* @__PURE__ */ jsxs("div", { ref, className: "flex items-center gap-2.5 px-3 pb-3 pt-4", children: [
@@ -4625,7 +4731,19 @@ var NodeCardHeader = React2.forwardRef(
4625
4731
  children: /* @__PURE__ */ jsx(NodeTypeIcon, { icon, alt: appName, className: "size-[18px]" })
4626
4732
  }
4627
4733
  ),
4628
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: appName }),
4734
+ onStartRename && onCommitRename && onCancelRename ? /* @__PURE__ */ jsx(
4735
+ InlineEditableLabel,
4736
+ {
4737
+ value: label ?? "",
4738
+ placeholder: appName,
4739
+ editing: isRenaming,
4740
+ onStartEdit: onStartRename,
4741
+ onCommit: onCommitRename,
4742
+ onCancel: onCancelRename,
4743
+ className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground",
4744
+ inputClassName: "text-[11px] font-semibold uppercase tracking-wide"
4745
+ }
4746
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: label || appName }),
4629
4747
  missingCredentials && /* @__PURE__ */ jsx(
4630
4748
  "span",
4631
4749
  {
@@ -4995,7 +5113,13 @@ function LogicNodeCard({
4995
5113
  onSettings,
4996
5114
  onExecute,
4997
5115
  onDelete,
4998
- infoButton
5116
+ infoButton,
5117
+ disabled = false,
5118
+ onToggleDisabled,
5119
+ isRenaming = false,
5120
+ onStartRename,
5121
+ onCommitRename,
5122
+ onCancelRename
4999
5123
  }) {
5000
5124
  const minHeight = Math.max(
5001
5125
  64,
@@ -5018,6 +5142,8 @@ function LogicNodeCard({
5018
5142
  onExecute,
5019
5143
  onDelete,
5020
5144
  infoButton,
5145
+ disabled,
5146
+ onToggleDisabled,
5021
5147
  children: [
5022
5148
  /* @__PURE__ */ jsxs(
5023
5149
  "div",
@@ -5026,7 +5152,19 @@ function LogicNodeCard({
5026
5152
  style: { minHeight },
5027
5153
  children: [
5028
5154
  /* @__PURE__ */ jsx("div", { className: "flex size-9 items-center justify-center rounded-lg bg-muted", children: /* @__PURE__ */ jsx(Icon, { icon, className: "size-5", style: { color: accent } }) }),
5029
- /* @__PURE__ */ jsx("span", { className: "max-w-full truncate text-sm font-semibold leading-tight", children: title }),
5155
+ onStartRename && onCommitRename && onCancelRename ? /* @__PURE__ */ jsx(
5156
+ InlineEditableLabel,
5157
+ {
5158
+ value: title,
5159
+ placeholder: title,
5160
+ editing: isRenaming,
5161
+ onStartEdit: onStartRename,
5162
+ onCommit: onCommitRename,
5163
+ onCancel: onCancelRename,
5164
+ className: "max-w-full truncate text-sm font-semibold leading-tight",
5165
+ inputClassName: "text-sm font-semibold text-center"
5166
+ }
5167
+ ) : /* @__PURE__ */ jsx("span", { className: "max-w-full truncate text-sm font-semibold leading-tight", children: title }),
5030
5168
  subtitle && /* @__PURE__ */ jsx(
5031
5169
  "span",
5032
5170
  {
@@ -6315,14 +6453,24 @@ var getNodeMetadata = (value) => {
6315
6453
  executingNodeId,
6316
6454
  nodeStatuses,
6317
6455
  nodeItemCounts,
6318
- zustandSetNodes
6456
+ zustandSetNodes,
6457
+ renamingNodeId,
6458
+ toggleNodeDisabled,
6459
+ renameNode,
6460
+ startRename,
6461
+ endRename
6319
6462
  ] = useFlowStore(
6320
6463
  useShallow((s) => [
6321
6464
  s.execution.isExecuting,
6322
6465
  s.execution.executingNodeId,
6323
6466
  s.execution.nodeStatuses,
6324
6467
  s.execution.nodeItemCounts,
6325
- s.actions.nodes.setNodes
6468
+ s.actions.nodes.setNodes,
6469
+ s.workflow.renamingNodeId,
6470
+ s.actions.nodes.toggleDisabled,
6471
+ s.actions.nodes.renameNode,
6472
+ s.actions.nodes.startRename,
6473
+ s.actions.nodes.endRename
6326
6474
  ])
6327
6475
  );
6328
6476
  useEffect(() => {
@@ -6346,6 +6494,22 @@ var getNodeMetadata = (value) => {
6346
6494
  const handleDeleteNode = () => {
6347
6495
  deleteNode(id);
6348
6496
  };
6497
+ const handleToggleDisabled = useCallback(() => {
6498
+ toggleNodeDisabled(id);
6499
+ }, [id, toggleNodeDisabled]);
6500
+ const isRenamingThisNode = renamingNodeId === id;
6501
+ const handleStartRename = useCallback(() => {
6502
+ startRename(id);
6503
+ }, [id, startRename]);
6504
+ const handleCommitRename = useCallback(
6505
+ (value2) => {
6506
+ renameNode(id, value2);
6507
+ },
6508
+ [id, renameNode]
6509
+ );
6510
+ const handleCancelRename = useCallback(() => {
6511
+ endRename();
6512
+ }, [endRename]);
6349
6513
  const openNdv = useFlowStore((s) => s.actions.ndv.open);
6350
6514
  const openQuickConfig = useFlowStore((s) => s.actions.quickConfig.open);
6351
6515
  const clickTimerRef = useRef(null);
@@ -6408,6 +6572,8 @@ var getNodeMetadata = (value) => {
6408
6572
  const nodeStatus = nodeStatuses[id] || "idle";
6409
6573
  const executionStatus = nodeStatus === "success" ? "success" : nodeStatus === "error" ? "error" : nodeStatus === "running" ? "running" : "idle";
6410
6574
  const itemCount = nodeItemCounts[id];
6575
+ const isDisabled = data?.disabled === true;
6576
+ const nodeLabel = data?.label || "";
6411
6577
  const nodeKinds = data?.nodeData?.kind;
6412
6578
  const pinnedOpType = (data?.operation_type || "").toLowerCase();
6413
6579
  const isTrigger = pinnedOpType === "trigger" || (value?.type || "").toLowerCase().includes("trigger") || // Kind-based detection only when NO operation is pinned: dual-role
@@ -6472,7 +6638,13 @@ var getNodeMetadata = (value) => {
6472
6638
  onSettings: handleOpenNdv,
6473
6639
  onExecute: handleExecuteStep,
6474
6640
  onDelete: handleDeleteNode,
6475
- infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata })
6641
+ infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata }),
6642
+ disabled: isDisabled,
6643
+ onToggleDisabled: handleToggleDisabled,
6644
+ isRenaming: isRenamingThisNode,
6645
+ onStartRename: handleStartRename,
6646
+ onCommitRename: handleCommitRename,
6647
+ onCancelRename: handleCancelRename
6476
6648
  }
6477
6649
  ),
6478
6650
  /* @__PURE__ */ jsx(
@@ -6501,6 +6673,8 @@ var getNodeMetadata = (value) => {
6501
6673
  onExecute: handleExecuteStep,
6502
6674
  onDelete: handleDeleteNode,
6503
6675
  infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata }),
6676
+ disabled: isDisabled,
6677
+ onToggleDisabled: handleToggleDisabled,
6504
6678
  children: [
6505
6679
  /* @__PURE__ */ jsx(
6506
6680
  NodeCardHeader,
@@ -6508,7 +6682,12 @@ var getNodeMetadata = (value) => {
6508
6682
  icon: meta.icon,
6509
6683
  appName,
6510
6684
  accentKey: value?.type || meta.title,
6511
- missingCredentials
6685
+ missingCredentials,
6686
+ label: nodeLabel,
6687
+ isRenaming: isRenamingThisNode,
6688
+ onStartRename: handleStartRename,
6689
+ onCommitRename: handleCommitRename,
6690
+ onCancelRename: handleCancelRename
6512
6691
  }
6513
6692
  ),
6514
6693
  operationDisplay === "execute" && (value?.type || "").startsWith("internal:") || isTrigger ? (
@@ -17906,6 +18085,10 @@ function CustomDeletableEdge(props) {
17906
18085
  const nodeStatuses = useFlowStore(
17907
18086
  useShallow((s) => s.execution.nodeStatuses)
17908
18087
  );
18088
+ const nodeItemCounts = useFlowStore(
18089
+ useShallow((s) => s.execution.nodeItemCounts)
18090
+ );
18091
+ const sourceItemCount = nodeItemCounts[source];
17909
18092
  const sourceNodeStatus = nodeStatuses[source] || "idle";
17910
18093
  const isRunning = sourceNodeStatus === "running";
17911
18094
  const isSuccess = sourceNodeStatus === "success";
@@ -18805,6 +18988,26 @@ function CustomDeletableEdge(props) {
18805
18988
  }
18806
18989
  ),
18807
18990
  /* @__PURE__ */ jsxs(EdgeLabelRenderer, { children: [
18991
+ sourceItemCount !== void 0 && sourceItemCount > 0 && /* @__PURE__ */ jsx(
18992
+ "div",
18993
+ {
18994
+ className: "pointer-events-none absolute z-[999]",
18995
+ style: {
18996
+ transform: `translate(${labelX}px, ${labelY}px) translate(-50%, -100%) translateY(-10px)`
18997
+ },
18998
+ children: /* @__PURE__ */ jsxs("div", { className: "rounded-full border border-border bg-card px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm", children: [
18999
+ sourceItemCount,
19000
+ " ",
19001
+ sourceItemCount !== 1 ? tr(
19002
+ "fluidgrids.builder.edges.customDeletableEdge.itemCountOther",
19003
+ "items"
19004
+ ) : tr(
19005
+ "fluidgrids.builder.edges.customDeletableEdge.itemCountOne",
19006
+ "item"
19007
+ )
19008
+ ] })
19009
+ }
19010
+ ),
18808
19011
  /* @__PURE__ */ jsxs(
18809
19012
  "div",
18810
19013
  {
@@ -19197,6 +19400,276 @@ function computeMinimapRect(panelRect, bounds, margin) {
19197
19400
  top = Math.min(Math.max(top, bounds.top + margin), maxTop);
19198
19401
  return { left, top };
19199
19402
  }
19403
+ var SHORTCUT_ROWS = [
19404
+ {
19405
+ labelKey: "fluidgrids.builder.shortcutsHelp.undo",
19406
+ labelDefault: "Undo",
19407
+ mac: "\u2318Z",
19408
+ other: "Ctrl+Z"
19409
+ },
19410
+ {
19411
+ labelKey: "fluidgrids.builder.shortcutsHelp.redo",
19412
+ labelDefault: "Redo",
19413
+ mac: "\u2318\u21E7Z",
19414
+ other: "Ctrl+Shift+Z / Ctrl+Y"
19415
+ },
19416
+ {
19417
+ labelKey: "fluidgrids.builder.shortcutsHelp.copy",
19418
+ labelDefault: "Copy selected node(s)",
19419
+ mac: "\u2318C",
19420
+ other: "Ctrl+C"
19421
+ },
19422
+ {
19423
+ labelKey: "fluidgrids.builder.shortcutsHelp.cut",
19424
+ labelDefault: "Cut selected node(s)",
19425
+ mac: "\u2318X",
19426
+ other: "Ctrl+X"
19427
+ },
19428
+ {
19429
+ labelKey: "fluidgrids.builder.shortcutsHelp.paste",
19430
+ labelDefault: "Paste \u2014 works across workflows/tabs",
19431
+ mac: "\u2318V",
19432
+ other: "Ctrl+V"
19433
+ },
19434
+ {
19435
+ labelKey: "fluidgrids.builder.shortcutsHelp.duplicate",
19436
+ labelDefault: "Duplicate selected node(s)",
19437
+ mac: "\u2318D",
19438
+ other: "Ctrl+D"
19439
+ },
19440
+ {
19441
+ labelKey: "fluidgrids.builder.shortcutsHelp.delete",
19442
+ labelDefault: "Delete selected node(s)/connection(s)",
19443
+ mac: "\u232B / Delete",
19444
+ other: "Backspace / Delete"
19445
+ },
19446
+ {
19447
+ labelKey: "fluidgrids.builder.shortcutsHelp.rename",
19448
+ labelDefault: "Rename the selected node",
19449
+ mac: "F2 or double-click its title",
19450
+ other: "F2 or double-click its title"
19451
+ },
19452
+ {
19453
+ labelKey: "fluidgrids.builder.shortcutsHelp.multiSelectClick",
19454
+ labelDefault: "Add a node to the selection",
19455
+ mac: "\u2318-click / \u2303-click",
19456
+ other: "Ctrl-click"
19457
+ },
19458
+ {
19459
+ labelKey: "fluidgrids.builder.shortcutsHelp.multiSelectBox",
19460
+ labelDefault: "Box-select multiple nodes",
19461
+ mac: "\u21E7-drag on empty canvas",
19462
+ other: "Shift-drag on empty canvas"
19463
+ },
19464
+ {
19465
+ labelKey: "fluidgrids.builder.shortcutsHelp.stickyNote",
19466
+ labelDefault: "Add a sticky note",
19467
+ mac: "\u21E7S",
19468
+ other: "Shift+S"
19469
+ },
19470
+ {
19471
+ labelKey: "fluidgrids.builder.shortcutsHelp.cancel",
19472
+ labelDefault: "Close a panel / cancel an edit",
19473
+ mac: "Esc",
19474
+ other: "Esc"
19475
+ },
19476
+ {
19477
+ labelKey: "fluidgrids.builder.shortcutsHelp.help",
19478
+ labelDefault: "Open this shortcuts panel",
19479
+ mac: "?",
19480
+ other: "?"
19481
+ }
19482
+ ];
19483
+ function Kbd({ children }) {
19484
+ return /* @__PURE__ */ jsx("kbd", { className: "rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[11px] font-medium text-muted-foreground", children });
19485
+ }
19486
+ function ShortcutsHelpPanel({
19487
+ open,
19488
+ onOpenChange
19489
+ }) {
19490
+ const tr = useTr();
19491
+ const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "");
19492
+ return /* @__PURE__ */ jsx(Dialog$1, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent$1, { className: "max-w-lg", children: [
19493
+ /* @__PURE__ */ jsxs(DialogHeader$1, { children: [
19494
+ /* @__PURE__ */ jsx(DialogTitle$1, { children: tr("fluidgrids.builder.shortcutsHelp.title", "Keyboard shortcuts") }),
19495
+ /* @__PURE__ */ jsx(DialogDescription$1, { children: tr(
19496
+ "fluidgrids.builder.shortcutsHelp.description",
19497
+ "Speed up editing the workflow canvas."
19498
+ ) })
19499
+ ] }),
19500
+ /* @__PURE__ */ jsx("div", { className: "max-h-[60vh] space-y-0.5 overflow-y-auto pr-1", children: SHORTCUT_ROWS.map((row) => /* @__PURE__ */ jsxs(
19501
+ "div",
19502
+ {
19503
+ className: "flex items-center justify-between gap-4 rounded-md px-2 py-1.5 text-sm hover:bg-muted/50",
19504
+ children: [
19505
+ /* @__PURE__ */ jsx("span", { className: "text-foreground/80", children: tr(row.labelKey, row.labelDefault) }),
19506
+ /* @__PURE__ */ jsx(Kbd, { children: isMac ? row.mac : row.other })
19507
+ ]
19508
+ },
19509
+ row.labelKey
19510
+ )) })
19511
+ ] }) });
19512
+ }
19513
+
19514
+ // src/builder/lib/clipboard-payload.ts
19515
+ var CLIPBOARD_MARKER = "fluidgrids/flow-clipboard@1";
19516
+ function buildClipboardPayload(nodes, edges) {
19517
+ const selected = nodes.filter((n) => n.selected);
19518
+ const idSet = new Set(selected.map((n) => n.id));
19519
+ const internalEdges = edges.filter(
19520
+ (e) => idSet.has(e.source) && idSet.has(e.target)
19521
+ );
19522
+ return { marker: CLIPBOARD_MARKER, nodes: selected, edges: internalEdges };
19523
+ }
19524
+ function parseClipboardPayload(text) {
19525
+ if (!text) return null;
19526
+ let parsed;
19527
+ try {
19528
+ parsed = JSON.parse(text);
19529
+ } catch {
19530
+ return null;
19531
+ }
19532
+ if (!parsed || typeof parsed !== "object") return null;
19533
+ const rec = parsed;
19534
+ if (rec.marker !== CLIPBOARD_MARKER || !Array.isArray(rec.nodes)) {
19535
+ return null;
19536
+ }
19537
+ return {
19538
+ marker: CLIPBOARD_MARKER,
19539
+ nodes: rec.nodes,
19540
+ edges: Array.isArray(rec.edges) ? rec.edges : []
19541
+ };
19542
+ }
19543
+
19544
+ // src/builder/hooks/use-flow-clipboard.ts
19545
+ function useFlowClipboard() {
19546
+ const { getNodes, getEdges } = useReactFlow();
19547
+ const pasteNodes = useFlowStore((s) => s.actions.nodes.pasteNodes);
19548
+ const deleteNodes = useFlowStore((s) => s.actions.nodes.deleteNodes);
19549
+ const copySelection = useCallback(async () => {
19550
+ const payload = buildClipboardPayload(getNodes(), getEdges());
19551
+ if (payload.nodes.length === 0) return false;
19552
+ try {
19553
+ await navigator.clipboard.writeText(JSON.stringify(payload));
19554
+ return true;
19555
+ } catch {
19556
+ return false;
19557
+ }
19558
+ }, [getNodes, getEdges]);
19559
+ const cutSelection = useCallback(async () => {
19560
+ const selected = getNodes().filter((n) => n.selected);
19561
+ if (selected.length === 0) return false;
19562
+ const copied = await copySelection();
19563
+ if (!copied) return false;
19564
+ deleteNodes(selected);
19565
+ return true;
19566
+ }, [copySelection, deleteNodes, getNodes]);
19567
+ const pasteFromClipboard = useCallback(async () => {
19568
+ let text;
19569
+ try {
19570
+ text = await navigator.clipboard.readText();
19571
+ } catch {
19572
+ return false;
19573
+ }
19574
+ const payload = parseClipboardPayload(text);
19575
+ if (!payload || payload.nodes.length === 0) return false;
19576
+ pasteNodes({ nodes: payload.nodes, edges: payload.edges });
19577
+ return true;
19578
+ }, [pasteNodes]);
19579
+ return { copySelection, cutSelection, pasteFromClipboard };
19580
+ }
19581
+
19582
+ // src/builder/hooks/use-editor-shortcuts.ts
19583
+ function isEditableTarget(target) {
19584
+ const el = target;
19585
+ if (!el) return false;
19586
+ return el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT" || el.isContentEditable;
19587
+ }
19588
+ function hasTextSelection() {
19589
+ const sel = typeof window !== "undefined" ? window.getSelection?.() : null;
19590
+ return !!sel && sel.toString().length > 0;
19591
+ }
19592
+ function useEditorShortcuts(options) {
19593
+ const readOnly = options?.readOnly ?? false;
19594
+ const onOpenHelp = options?.onOpenHelp;
19595
+ const { getNodes } = useReactFlow();
19596
+ const undo = useFlowStore((s) => s.actions.history.undo);
19597
+ const redo = useFlowStore((s) => s.actions.history.redo);
19598
+ const duplicateNodes = useFlowStore((s) => s.actions.nodes.duplicateNodes);
19599
+ const startRename = useFlowStore((s) => s.actions.nodes.startRename);
19600
+ const { copySelection, cutSelection, pasteFromClipboard } = useFlowClipboard();
19601
+ const insertStickyNote = useInsertStickyNote();
19602
+ useEffect(() => {
19603
+ if (readOnly) return;
19604
+ const onKeyDown = (e) => {
19605
+ const editable = isEditableTarget(e.target);
19606
+ const mod = e.metaKey || e.ctrlKey;
19607
+ if (!mod && !e.altKey && e.shiftKey && e.key.toLowerCase() === "s" && !editable) {
19608
+ e.preventDefault();
19609
+ insertStickyNote();
19610
+ return;
19611
+ }
19612
+ if (!mod && !editable && onOpenHelp && (e.key === "?" || e.shiftKey && e.key === "/")) {
19613
+ e.preventDefault();
19614
+ onOpenHelp();
19615
+ return;
19616
+ }
19617
+ if (!mod && !editable && e.key === "F2") {
19618
+ const selected = getNodes().filter((n) => n.selected);
19619
+ if (selected.length === 1 && selected[0]) {
19620
+ e.preventDefault();
19621
+ startRename(selected[0].id);
19622
+ }
19623
+ return;
19624
+ }
19625
+ if (!mod || editable) return;
19626
+ const key = e.key.toLowerCase();
19627
+ if (key === "z") {
19628
+ e.preventDefault();
19629
+ if (e.shiftKey) redo();
19630
+ else undo();
19631
+ return;
19632
+ }
19633
+ if (key === "y") {
19634
+ e.preventDefault();
19635
+ redo();
19636
+ return;
19637
+ }
19638
+ if ((key === "c" || key === "x") && !hasTextSelection()) {
19639
+ const hasSelection = getNodes().some((n) => n.selected);
19640
+ if (!hasSelection) return;
19641
+ e.preventDefault();
19642
+ void (key === "c" ? copySelection() : cutSelection());
19643
+ return;
19644
+ }
19645
+ if (key === "v") {
19646
+ e.preventDefault();
19647
+ void pasteFromClipboard();
19648
+ return;
19649
+ }
19650
+ if (key === "d") {
19651
+ const selectedIds = getNodes().filter((n) => n.selected).map((n) => n.id);
19652
+ if (selectedIds.length === 0) return;
19653
+ e.preventDefault();
19654
+ duplicateNodes(selectedIds);
19655
+ }
19656
+ };
19657
+ window.addEventListener("keydown", onKeyDown);
19658
+ return () => window.removeEventListener("keydown", onKeyDown);
19659
+ }, [
19660
+ readOnly,
19661
+ getNodes,
19662
+ undo,
19663
+ redo,
19664
+ duplicateNodes,
19665
+ startRename,
19666
+ copySelection,
19667
+ cutSelection,
19668
+ pasteFromClipboard,
19669
+ insertStickyNote,
19670
+ onOpenHelp
19671
+ ]);
19672
+ }
19200
19673
  var ZOOM_DURATION = 500;
19201
19674
  var NODE_WIDTH = 220;
19202
19675
  var NODE_HEIGHT = 80;
@@ -19257,6 +19730,7 @@ function CanvasControls() {
19257
19730
  const isReadOnly = searchParams.get("readonly") === "true";
19258
19731
  const [showMinimap, setShowMinimap] = useState(false);
19259
19732
  const [edgesHidden, setEdgesHidden] = useState(false);
19733
+ const [shortcutsHelpOpen, setShortcutsHelpOpen] = useState(false);
19260
19734
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
19261
19735
  const [isDragging, setIsDragging] = useState(false);
19262
19736
  const panelRef = useRef(null);
@@ -19266,6 +19740,18 @@ function CanvasControls() {
19266
19740
  shallow
19267
19741
  );
19268
19742
  const { zoomIn, zoomOut, fitView, setEdges, getEdges, getNodes, setNodes } = useReactFlow();
19743
+ const [canUndo, canRedo, undo, redo] = useFlowStore(
19744
+ useShallow((s) => [
19745
+ s.history.past.length > 0,
19746
+ s.history.future.length > 0,
19747
+ s.actions.history.undo,
19748
+ s.actions.history.redo
19749
+ ])
19750
+ );
19751
+ useEditorShortcuts({
19752
+ readOnly: isReadOnly,
19753
+ onOpenHelp: () => setShortcutsHelpOpen(true)
19754
+ });
19269
19755
  const toggleEdges = useCallback(() => {
19270
19756
  setEdgesHidden((prev) => {
19271
19757
  const next = !prev;
@@ -19274,20 +19760,6 @@ function CanvasControls() {
19274
19760
  });
19275
19761
  }, [setEdges]);
19276
19762
  const insertStickyNote = useInsertStickyNote();
19277
- useEffect(() => {
19278
- if (isReadOnly) return;
19279
- const onKeyDown = (e) => {
19280
- if (!e.shiftKey || e.key.toLowerCase() !== "s") return;
19281
- if (e.metaKey || e.ctrlKey || e.altKey) return;
19282
- const t = e.target;
19283
- if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable))
19284
- return;
19285
- e.preventDefault();
19286
- insertStickyNote();
19287
- };
19288
- window.addEventListener("keydown", onKeyDown);
19289
- return () => window.removeEventListener("keydown", onKeyDown);
19290
- }, [insertStickyNote, isReadOnly]);
19291
19763
  const tidyUp = useCallback(() => {
19292
19764
  const nodes = getNodes().filter((n) => n.type !== STICKY_NOTE_TYPE);
19293
19765
  const edges = getEdges();
@@ -19489,6 +19961,31 @@ function CanvasControls() {
19489
19961
  }
19490
19962
  ),
19491
19963
  !isReadOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
19964
+ /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-card-foreground/10 mx-0.5" }),
19965
+ /* @__PURE__ */ jsx(
19966
+ ControlBtn,
19967
+ {
19968
+ title: tr(
19969
+ "fluidgrids.builder.canvasControls.undo",
19970
+ "Undo (Ctrl/Cmd+Z)"
19971
+ ),
19972
+ disabled: !canUndo,
19973
+ onClick: undo,
19974
+ children: /* @__PURE__ */ jsx(Undo2, { className: "size-4" })
19975
+ }
19976
+ ),
19977
+ /* @__PURE__ */ jsx(
19978
+ ControlBtn,
19979
+ {
19980
+ title: tr(
19981
+ "fluidgrids.builder.canvasControls.redo",
19982
+ "Redo (Ctrl/Cmd+Shift+Z)"
19983
+ ),
19984
+ disabled: !canRedo,
19985
+ onClick: redo,
19986
+ children: /* @__PURE__ */ jsx(Redo2, { className: "size-4" })
19987
+ }
19988
+ ),
19492
19989
  /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-card-foreground/10 mx-0.5" }),
19493
19990
  /* @__PURE__ */ jsx(
19494
19991
  ControlBtn,
@@ -19532,10 +20029,29 @@ function CanvasControls() {
19532
20029
  onClick: insertStickyNote,
19533
20030
  children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:note-plus-outline", className: "size-4" })
19534
20031
  }
20032
+ ),
20033
+ /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-card-foreground/10 mx-0.5" }),
20034
+ /* @__PURE__ */ jsx(
20035
+ ControlBtn,
20036
+ {
20037
+ title: tr(
20038
+ "fluidgrids.builder.canvasControls.shortcutsHelp",
20039
+ "Keyboard shortcuts (?)"
20040
+ ),
20041
+ onClick: () => setShortcutsHelpOpen(true),
20042
+ children: /* @__PURE__ */ jsx(Keyboard, { className: "size-4" })
20043
+ }
19535
20044
  )
19536
20045
  ] })
19537
20046
  ]
19538
20047
  }
20048
+ ),
20049
+ !isReadOnly && /* @__PURE__ */ jsx(
20050
+ ShortcutsHelpPanel,
20051
+ {
20052
+ open: shortcutsHelpOpen,
20053
+ onOpenChange: setShortcutsHelpOpen
20054
+ }
19539
20055
  )
19540
20056
  ] });
19541
20057
  }
@@ -23698,7 +24214,7 @@ var FlowBuilder = ({ readOnly = false }) => {
23698
24214
  nodesDraggable: !isReadOnly,
23699
24215
  nodesConnectable: !isReadOnly,
23700
24216
  elementsSelectable: !isReadOnly,
23701
- multiSelectionKeyCode: null,
24217
+ multiSelectionKeyCode: isReadOnly ? null : ["Meta", "Control"],
23702
24218
  deleteKeyCode: isReadOnly ? null : deleteKeyCode,
23703
24219
  snapGrid: [16, 16],
23704
24220
  snapToGrid: true,
@@ -23824,5 +24340,5 @@ var FlowBuilderPage = ({
23824
24340
  };
23825
24341
 
23826
24342
  export { Card2 as Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ConnectionFormDialog, ConnectionSelector, EntityTagPicker, FLUIDGRIDS_ENTITY_TYPES, FlowBuilderPage, FluidGridsProvider, ProviderPicker, SchemaConnectionForm, ScopePicker, TemplateCard, buildNodeCheck, buildRequiredNodeTypes, listCustomBlocks, mergeStickyNotes, refetchStoreNodes, registerCustomBlock, resolveUiSpec, stripSubmitActions, tagLabel, useAvailableTags, useConnection, useConnectionDialog, useConnections, useConnectionsByProvider, useCreateConnection, useCreateTag, useDraftEntityTags, useEntityTags, useFluidGridsContext, useImportWorkflowTemplate, useInitiateOAuth, useInstalledNodes, useNodeExecution, useProviderByKey, useProviderCatalog, useProviders, useReplaceEntityTags, useSearchTags, useTagEntity, useUntagEntity, useWorkflowTemplates };
23827
- //# sourceMappingURL=chunk-XASNLRFQ.js.map
23828
- //# sourceMappingURL=chunk-XASNLRFQ.js.map
24343
+ //# sourceMappingURL=chunk-3RWKQIV4.js.map
24344
+ //# sourceMappingURL=chunk-3RWKQIV4.js.map