@bian-womp/spark-workbench 0.2.73 → 0.2.75

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 (53) hide show
  1. package/lib/cjs/index.cjs +257 -47
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/InMemoryWorkbench.d.ts +0 -3
  4. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
  5. package/lib/cjs/src/index.d.ts +1 -0
  6. package/lib/cjs/src/index.d.ts.map +1 -1
  7. package/lib/cjs/src/misc/KeyboardShortcutToast.d.ts +16 -0
  8. package/lib/cjs/src/misc/KeyboardShortcutToast.d.ts.map +1 -0
  9. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  10. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +3 -11
  11. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  12. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  13. package/lib/cjs/src/misc/context-menu/ContextMenuHandlers.d.ts +5 -0
  14. package/lib/cjs/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -1
  15. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts +1 -1
  16. package/lib/cjs/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  17. package/lib/cjs/src/misc/context-menu/DefaultContextMenu.d.ts.map +1 -1
  18. package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  19. package/lib/cjs/src/misc/context-menu/SelectionContextMenu.d.ts.map +1 -1
  20. package/lib/cjs/src/misc/load.d.ts.map +1 -1
  21. package/lib/cjs/src/misc/viewport-utils.d.ts +9 -0
  22. package/lib/cjs/src/misc/viewport-utils.d.ts.map +1 -0
  23. package/lib/cjs/src/runtime/IGraphRunner.d.ts +13 -1
  24. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  25. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +5 -0
  26. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  27. package/lib/esm/index.js +256 -48
  28. package/lib/esm/index.js.map +1 -1
  29. package/lib/esm/src/core/InMemoryWorkbench.d.ts +0 -3
  30. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
  31. package/lib/esm/src/index.d.ts +1 -0
  32. package/lib/esm/src/index.d.ts.map +1 -1
  33. package/lib/esm/src/misc/KeyboardShortcutToast.d.ts +16 -0
  34. package/lib/esm/src/misc/KeyboardShortcutToast.d.ts.map +1 -0
  35. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  36. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +3 -11
  37. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  38. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  39. package/lib/esm/src/misc/context-menu/ContextMenuHandlers.d.ts +5 -0
  40. package/lib/esm/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -1
  41. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts +1 -1
  42. package/lib/esm/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -1
  43. package/lib/esm/src/misc/context-menu/DefaultContextMenu.d.ts.map +1 -1
  44. package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
  45. package/lib/esm/src/misc/context-menu/SelectionContextMenu.d.ts.map +1 -1
  46. package/lib/esm/src/misc/load.d.ts.map +1 -1
  47. package/lib/esm/src/misc/viewport-utils.d.ts +9 -0
  48. package/lib/esm/src/misc/viewport-utils.d.ts.map +1 -0
  49. package/lib/esm/src/runtime/IGraphRunner.d.ts +13 -1
  50. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  51. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +5 -0
  52. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  53. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -8,7 +8,6 @@ var React = require('react');
8
8
  var cx = require('classnames');
9
9
  var jsxRuntime = require('react/jsx-runtime');
10
10
  var react$1 = require('@phosphor-icons/react');
11
- var isEqual = require('lodash/isEqual');
12
11
 
13
12
  class DefaultUIExtensionRegistry {
14
13
  constructor() {
@@ -280,7 +279,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
280
279
  }
281
280
  // Position and selection APIs for React Flow bridge
282
281
  setPositions(map, options) {
283
- this.positions = { ...map };
282
+ this.positions = { ...this.positions, ...map };
284
283
  this.emit("graphUiChanged", {
285
284
  def: this.def,
286
285
  change: { type: "moveNodes" },
@@ -323,12 +322,13 @@ class InMemoryWorkbench extends AbstractWorkbench {
323
322
  // Clear selection
324
323
  this.setSelection({ nodes: [], edges: [] }, options);
325
324
  }
326
- setViewport(viewport, options) {
325
+ setViewport(viewport) {
326
+ if (lod.isEqual(this.viewport, viewport))
327
+ return;
327
328
  this.viewport = { ...viewport };
328
329
  this.emit("graphUiChanged", {
329
330
  def: this.def,
330
331
  change: { type: "viewport" },
331
- ...options,
332
332
  });
333
333
  }
334
334
  getViewport() {
@@ -971,6 +971,24 @@ class LocalGraphRunner extends AbstractGraphRunner {
971
971
  }
972
972
  }
973
973
 
974
+ function isValidViewport(viewport) {
975
+ return (viewport !== null &&
976
+ typeof viewport === "object" &&
977
+ "x" in viewport &&
978
+ "y" in viewport &&
979
+ "zoom" in viewport &&
980
+ typeof viewport.x === "number" &&
981
+ typeof viewport.y === "number" &&
982
+ typeof viewport.zoom === "number");
983
+ }
984
+ function excludeViewportFromUIState(uiState) {
985
+ if (!uiState) {
986
+ return {};
987
+ }
988
+ const { viewport: _ignoredViewport, ...rest } = uiState;
989
+ return rest;
990
+ }
991
+
974
992
  // Counter for generating readable runner IDs
975
993
  let remoteRunnerCounter = 0;
976
994
  class RemoteGraphRunner extends AbstractGraphRunner {
@@ -1142,9 +1160,26 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1142
1160
  this.clientPromise = (async () => {
1143
1161
  // Build client config from backend config
1144
1162
  const clientConfig = this.buildClientConfig(backend);
1145
- // Create client with custom event handler if provided
1163
+ // Wrap custom event handler to intercept flow-viewport events and emit viewport event
1164
+ const wrappedOnCustomEvent = (event) => {
1165
+ const msg = event?.message;
1166
+ if (msg &&
1167
+ typeof msg === "object" &&
1168
+ "type" in msg &&
1169
+ msg.type === "flow-viewport") {
1170
+ const viewport = msg.payload?.viewport;
1171
+ if (isValidViewport(viewport)) {
1172
+ this.emit("viewport", { viewport });
1173
+ }
1174
+ }
1175
+ // Call original handler if provided
1176
+ if (backend.onCustomEvent) {
1177
+ backend.onCustomEvent(event);
1178
+ }
1179
+ };
1180
+ // Create client with wrapped custom event handler
1146
1181
  const client = new sparkRemote.RuntimeApiClient(clientConfig, {
1147
- onCustomEvent: backend.onCustomEvent,
1182
+ onCustomEvent: wrappedOnCustomEvent,
1148
1183
  runnerId: this.runnerId,
1149
1184
  });
1150
1185
  // Setup event subscriptions
@@ -1419,6 +1454,20 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1419
1454
  const client = await this.ensureClient();
1420
1455
  await client.copyOutputs(fromNodeId, toNodeId, options);
1421
1456
  }
1457
+ async setViewport(viewport) {
1458
+ const client = await this.ensureClient();
1459
+ const transport = client.transport;
1460
+ if (transport && transport.send) {
1461
+ transport.send({
1462
+ seq: Date.now(),
1463
+ ts: Date.now(),
1464
+ message: {
1465
+ type: "SetViewport",
1466
+ payload: { viewport },
1467
+ },
1468
+ });
1469
+ }
1470
+ }
1422
1471
  triggerExternal(nodeId, event, options) {
1423
1472
  // If engine exists, call directly; otherwise ensure client (fire-and-forget)
1424
1473
  if (this.engine) {
@@ -2436,7 +2485,8 @@ function isSnapshotPayload(parsed) {
2436
2485
  async function download(wb, runner) {
2437
2486
  try {
2438
2487
  const def = wb.export();
2439
- const uiState = wb.getUIState();
2488
+ const fullUiState = wb.getUIState();
2489
+ const uiState = excludeViewportFromUIState(fullUiState);
2440
2490
  const runtimeState = wb.getRuntimeState();
2441
2491
  let snapshot;
2442
2492
  if (runner.isRunning()) {
@@ -2446,7 +2496,7 @@ async function download(wb, runner) {
2446
2496
  def,
2447
2497
  extData: {
2448
2498
  ...(fullSnapshot.extData || {}),
2449
- ui: uiState,
2499
+ ui: Object.keys(uiState || {}).length > 0 ? uiState : undefined,
2450
2500
  runtime: runtimeState || undefined,
2451
2501
  },
2452
2502
  };
@@ -2458,7 +2508,10 @@ async function download(wb, runner) {
2458
2508
  inputs,
2459
2509
  outputs: {},
2460
2510
  environment: {},
2461
- extData: { ui: uiState, runtime: runtimeState || undefined },
2511
+ extData: {
2512
+ ui: Object.keys(uiState || {}).length > 0 ? uiState : undefined,
2513
+ runtime: runtimeState || undefined,
2514
+ },
2462
2515
  };
2463
2516
  }
2464
2517
  downloadJSON(snapshot, `spark-snapshot-${generateTimestamp()}.json`);
@@ -2482,7 +2535,8 @@ async function upload(parsed, wb, runner) {
2482
2535
  }
2483
2536
  await wb.load(def);
2484
2537
  if (extData.ui && typeof extData.ui === "object") {
2485
- wb.setUIState(extData.ui);
2538
+ const uiWithoutViewport = excludeViewportFromUIState(extData.ui);
2539
+ wb.setUIState(uiWithoutViewport);
2486
2540
  }
2487
2541
  if (extData.runtime && typeof extData.runtime === "object") {
2488
2542
  wb.setRuntimeState(extData.runtime);
@@ -2729,11 +2783,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2729
2783
  }
2730
2784
  // Save cleaned metadata to workbench state
2731
2785
  wb.setRuntimeState(metadata);
2732
- // Get UI state
2733
- const uiState = wb.getUIState();
2734
- // Save both runtime and UI state to extData (merge to preserve both)
2786
+ const fullUiState = wb.getUIState();
2787
+ const uiWithoutViewport = excludeViewportFromUIState(fullUiState);
2735
2788
  await runner.setExtData?.({
2736
- ...(uiState ? { ui: uiState } : {}),
2789
+ ...(Object.keys(uiWithoutViewport || {}).length > 0
2790
+ ? { ui: uiWithoutViewport }
2791
+ : {}),
2737
2792
  runtime: metadata,
2738
2793
  });
2739
2794
  }
@@ -3167,6 +3222,16 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3167
3222
  }
3168
3223
  });
3169
3224
  const offWbGraphUiChanged = wb.on("graphUiChanged", async (event) => {
3225
+ // Handle viewport changes separately (send via SetViewport command, not commit)
3226
+ if (event.change?.type === "viewport") {
3227
+ const viewport = wb.getViewport();
3228
+ if (viewport && runner.setViewport) {
3229
+ runner.setViewport(viewport).catch((err) => {
3230
+ console.warn("[WorkbenchContext] Failed to send viewport update:", err);
3231
+ });
3232
+ }
3233
+ return;
3234
+ }
3170
3235
  // Only commit if commit flag is true (e.g., drag end, not during dragging)
3171
3236
  if (event.commit) {
3172
3237
  // Build detailed reason from change type
@@ -3182,9 +3247,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3182
3247
  else if (changeType === "selection") {
3183
3248
  reason = "selection";
3184
3249
  }
3185
- else if (changeType === "viewport") {
3186
- reason = "viewport";
3187
- }
3188
3250
  }
3189
3251
  await saveUiRuntimeMetadata();
3190
3252
  const history = await runner
@@ -3216,7 +3278,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3216
3278
  console.error("Failed to handle registry changed event");
3217
3279
  }
3218
3280
  });
3219
- // Handle transport changes: reset runtime status when connection is lost
3281
+ const offFlowViewport = runner.on("viewport", (event) => {
3282
+ const viewport = event.viewport;
3283
+ if (isValidViewport(viewport)) {
3284
+ wb.setViewport(viewport);
3285
+ }
3286
+ });
3220
3287
  const offRunnerTransport = runner.on("transport", (t) => {
3221
3288
  if (t.state === "disconnected") {
3222
3289
  console.info("[WorkbenchContext] Transport disconnected, resetting node status");
@@ -3256,6 +3323,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3256
3323
  offWbSelectionChanged();
3257
3324
  offRunnerRegistry();
3258
3325
  offRunnerTransport();
3326
+ offFlowViewport();
3259
3327
  };
3260
3328
  }, [runner, wb, setRegistry]);
3261
3329
  const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
@@ -3614,6 +3682,47 @@ function createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyRes
3614
3682
  * Creates base selection context menu handlers.
3615
3683
  */
3616
3684
  function createSelectionContextMenuHandlers(wb, onClose, getDefaultNodeSize, onCopyResult, runner) {
3685
+ const onDuplicate = runner
3686
+ ? () => {
3687
+ const selection = wb.getSelection();
3688
+ if (selection.nodes.length === 0) {
3689
+ onClose();
3690
+ return;
3691
+ }
3692
+ const def = wb.export();
3693
+ const positions = wb.getPositions();
3694
+ const newNodes = [];
3695
+ // Duplicate each selected node
3696
+ for (const nodeId of selection.nodes) {
3697
+ const n = def.nodes.find((n) => n.nodeId === nodeId);
3698
+ if (!n)
3699
+ continue;
3700
+ const pos = positions[nodeId] || { x: 0, y: 0 };
3701
+ // Get inputs without bindings (literal values only)
3702
+ const allInputs = runner.getInputs(def)[nodeId] || {};
3703
+ const inboundHandles = new Set(def.edges
3704
+ .filter((e) => e.target.nodeId === nodeId)
3705
+ .map((e) => e.target.handle));
3706
+ const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
3707
+ const newNodeId = wb.addNode({
3708
+ typeId: n.typeId,
3709
+ params: n.params,
3710
+ position: { x: pos.x + 24, y: pos.y + 24 },
3711
+ resolvedHandles: n.resolvedHandles,
3712
+ }, {
3713
+ inputs: inputsWithoutBindings,
3714
+ copyOutputsFrom: nodeId,
3715
+ dry: true,
3716
+ });
3717
+ newNodes.push(newNodeId);
3718
+ }
3719
+ // Select all newly duplicated nodes
3720
+ if (newNodes.length > 0) {
3721
+ wb.setSelection({ nodes: newNodes, edges: [] }, { commit: true, reason: "duplicate-selection" });
3722
+ }
3723
+ onClose();
3724
+ }
3725
+ : undefined;
3617
3726
  return {
3618
3727
  onCopy: runner
3619
3728
  ? createCopyHandler(wb, runner, getDefaultNodeSize, onCopyResult)
@@ -3625,13 +3734,14 @@ function createSelectionContextMenuHandlers(wb, onClose, getDefaultNodeSize, onC
3625
3734
  wb.deleteSelection({ commit: true, reason: "delete-selection" });
3626
3735
  onClose();
3627
3736
  },
3737
+ onDuplicate,
3628
3738
  onClose,
3629
3739
  };
3630
3740
  }
3631
3741
  /**
3632
3742
  * Creates base default context menu handlers.
3633
3743
  */
3634
- function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, getCopiedData, clearCopiedData, history) {
3744
+ function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, getCopiedData, clearCopiedData, history, wb) {
3635
3745
  // Wrap paste handler to clear storage after paste
3636
3746
  const wrappedOnPaste = onPaste && getCopiedData && clearCopiedData
3637
3747
  ? (position) => {
@@ -3642,12 +3752,22 @@ function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, g
3642
3752
  const hasPasteData = getCopiedData ? () => !!getCopiedData() : undefined;
3643
3753
  const canUndo = history ? history.undoCount > 0 : undefined;
3644
3754
  const canRedo = history ? history.redoCount > 0 : undefined;
3755
+ const onSelectAll = wb
3756
+ ? () => {
3757
+ const def = wb.export();
3758
+ const allNodeIds = def.nodes.map((n) => n.nodeId);
3759
+ const allEdgeIds = def.edges.map((e) => e.id);
3760
+ wb.setSelection({ nodes: allNodeIds, edges: allEdgeIds }, { commit: true, reason: "select-all" });
3761
+ onClose();
3762
+ }
3763
+ : undefined;
3645
3764
  return {
3646
3765
  onAddNode,
3647
3766
  onPaste: wrappedOnPaste,
3648
3767
  hasPasteData,
3649
3768
  onUndo: runner ? () => runner.undo().then(() => onClose()) : undefined,
3650
3769
  onRedo: runner ? () => runner.redo().then(() => onClose()) : undefined,
3770
+ onSelectAll,
3651
3771
  canUndo,
3652
3772
  canRedo,
3653
3773
  onClose,
@@ -4187,6 +4307,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
4187
4307
  undo: "⌘/Ctrl + Z",
4188
4308
  redo: "⌘/Ctrl + Shift + Z",
4189
4309
  paste: "⌘/Ctrl + V",
4310
+ selectAll: "⌘/Ctrl + A",
4190
4311
  }, }) {
4191
4312
  const rf = react.useReactFlow();
4192
4313
  const [query, setQuery] = React.useState("");
@@ -4289,7 +4410,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
4289
4410
  return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
4290
4411
  e.preventDefault();
4291
4412
  e.stopPropagation();
4292
- }, children: [hasPasteData && handlers.onPaste && (jsxRuntime.jsx(ContextMenuButton, { label: "Paste", onClick: handlePaste, shortcut: keyboardShortcuts.paste, enableKeyboardShortcuts: enableKeyboardShortcuts })), (handlers.onUndo || handlers.onRedo) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasPasteData && handlers.onPaste && (jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" })), handlers.onUndo && (jsxRuntime.jsx(ContextMenuButton, { label: "Undo", onClick: handlers.onUndo, disabled: !canUndo, shortcut: keyboardShortcuts.undo, enableKeyboardShortcuts: enableKeyboardShortcuts })), handlers.onRedo && (jsxRuntime.jsx(ContextMenuButton, { label: "Redo", onClick: handlers.onRedo, disabled: !canRedo, shortcut: keyboardShortcuts.redo, enableKeyboardShortcuts: enableKeyboardShortcuts }))] })), hasPasteData &&
4413
+ }, children: [hasPasteData && handlers.onPaste && (jsxRuntime.jsx(ContextMenuButton, { label: "Paste", onClick: handlePaste, shortcut: keyboardShortcuts.paste, enableKeyboardShortcuts: enableKeyboardShortcuts })), (handlers.onUndo || handlers.onRedo || handlers.onSelectAll) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasPasteData && handlers.onPaste && (jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" })), handlers.onSelectAll && (jsxRuntime.jsx(ContextMenuButton, { label: "Select All", onClick: handlers.onSelectAll, shortcut: keyboardShortcuts.selectAll, enableKeyboardShortcuts: enableKeyboardShortcuts })), handlers.onSelectAll && (handlers.onUndo || handlers.onRedo) && (jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" })), handlers.onUndo && (jsxRuntime.jsx(ContextMenuButton, { label: "Undo", onClick: handlers.onUndo, disabled: !canUndo, shortcut: keyboardShortcuts.undo, enableKeyboardShortcuts: enableKeyboardShortcuts })), handlers.onRedo && (jsxRuntime.jsx(ContextMenuButton, { label: "Redo", onClick: handlers.onRedo, disabled: !canRedo, shortcut: keyboardShortcuts.redo, enableKeyboardShortcuts: enableKeyboardShortcuts })), (handlers.onUndo || handlers.onRedo) && (jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }))] })), hasPasteData &&
4293
4414
  handlers.onPaste &&
4294
4415
  !handlers.onUndo &&
4295
4416
  !handlers.onRedo && jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 rounded px-2 py-1 text-sm outline-none focus:border-gray-400 select-text", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
@@ -4297,7 +4418,8 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
4297
4418
 
4298
4419
  function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, enableKeyboardShortcuts = true, keyboardShortcuts = {
4299
4420
  copy: "⌘/Ctrl + C",
4300
- duplicate: "⌘/Ctrl + D",
4421
+ duplicate: "⌘/Ctrl + E",
4422
+ duplicateWithEdges: "⌘/Ctrl + Shift + E",
4301
4423
  delete: "Delete",
4302
4424
  }, }) {
4303
4425
  const ref = React.useRef(null);
@@ -4337,11 +4459,12 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
4337
4459
  return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
4338
4460
  e.preventDefault();
4339
4461
  e.stopPropagation();
4340
- }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicateWithEdges, children: "Duplicate with edges" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
4462
+ }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
4341
4463
  }
4342
4464
 
4343
4465
  function SelectionContextMenu({ open, clientPos, handlers, enableKeyboardShortcuts = true, keyboardShortcuts = {
4344
4466
  copy: "⌘/Ctrl + C",
4467
+ duplicate: "⌘/Ctrl + E",
4345
4468
  delete: "Delete",
4346
4469
  }, }) {
4347
4470
  const ref = React.useRef(null);
@@ -4381,7 +4504,45 @@ function SelectionContextMenu({ open, clientPos, handlers, enableKeyboardShortcu
4381
4504
  return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
4382
4505
  e.preventDefault();
4383
4506
  e.stopPropagation();
4384
- }, children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Selection" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts })] }));
4507
+ }, children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Selection" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), handlers.onDuplicate && (jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts })), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts })] }));
4508
+ }
4509
+
4510
+ function KeyboardShortcutToast({ message, duration = 1000, onClose, }) {
4511
+ const [isVisible, setIsVisible] = React.useState(true);
4512
+ const onCloseRef = React.useRef(onClose);
4513
+ const fadeOutTimerRef = React.useRef(null);
4514
+ // Keep onClose ref up to date
4515
+ React.useEffect(() => {
4516
+ onCloseRef.current = onClose;
4517
+ }, [onClose]);
4518
+ React.useEffect(() => {
4519
+ const timer = setTimeout(() => {
4520
+ setIsVisible(false);
4521
+ // Wait for fade-out animation before calling onClose
4522
+ fadeOutTimerRef.current = setTimeout(() => {
4523
+ onCloseRef.current();
4524
+ }, 300);
4525
+ }, duration);
4526
+ return () => {
4527
+ clearTimeout(timer);
4528
+ if (fadeOutTimerRef.current) {
4529
+ clearTimeout(fadeOutTimerRef.current);
4530
+ }
4531
+ };
4532
+ }, [duration]);
4533
+ return (jsxRuntime.jsx("div", { className: `fixed top-4 left-1/2 -translate-x-1/2 z-[2000] pointer-events-none transition-opacity duration-300 ${isVisible ? "opacity-100" : "opacity-0"}`, children: jsxRuntime.jsx("div", { className: "bg-white border border-gray-300 rounded-lg shadow-lg px-2 py-1 text-sm text-gray-700 font-medium whitespace-nowrap", children: message }) }));
4534
+ }
4535
+ // Hook to manage toast state
4536
+ function useKeyboardShortcutToast() {
4537
+ const [toast, setToast] = React.useState(null);
4538
+ const showToast = (message) => {
4539
+ const id = Date.now();
4540
+ setToast({ message, id });
4541
+ };
4542
+ const hideToast = () => {
4543
+ setToast(null);
4544
+ };
4545
+ return { toast, showToast, hideToast };
4385
4546
  }
4386
4547
 
4387
4548
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
@@ -4426,7 +4587,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4426
4587
  inputConnected: n.data.inputConnected,
4427
4588
  },
4428
4589
  });
4429
- return isEqual(pick(a), pick(b));
4590
+ return lod.isEqual(pick(a), pick(b));
4430
4591
  };
4431
4592
  const isSameEdge = (a, b) => {
4432
4593
  const pick = (e) => ({
@@ -4440,7 +4601,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4440
4601
  label: e.label,
4441
4602
  type: e.type,
4442
4603
  });
4443
- return isEqual(pick(a), pick(b));
4604
+ return lod.isEqual(pick(a), pick(b));
4444
4605
  };
4445
4606
  // Expose imperative API
4446
4607
  const rfInstanceRef = React.useRef(null);
@@ -4756,7 +4917,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4756
4917
  return;
4757
4918
  wb.pasteCopiedData(data, position, { commit: true, reason: "paste" });
4758
4919
  onCloseMenu();
4759
- }, runner, () => storage.get(), () => storage.set(null), historyState);
4920
+ }, runner, () => storage.get(), () => storage.set(null), historyState, wb);
4760
4921
  if (overrides?.getDefaultContextMenuHandlers) {
4761
4922
  return overrides.getDefaultContextMenuHandlers(wb, baseHandlers);
4762
4923
  }
@@ -4821,9 +4982,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4821
4982
  redo: "⌘/Ctrl + Shift + Z",
4822
4983
  copy: "⌘/Ctrl + C",
4823
4984
  paste: "⌘/Ctrl + V",
4824
- duplicate: "⌘/Ctrl + D",
4985
+ duplicate: "⌘/Ctrl + E",
4986
+ duplicateWithEdges: "⌘/Ctrl + Shift + E",
4987
+ selectAll: "⌘/Ctrl + A",
4825
4988
  delete: "Delete",
4826
4989
  };
4990
+ // Toast notification for keyboard shortcuts
4991
+ const { toast, showToast, hideToast } = useKeyboardShortcutToast();
4827
4992
  // Keyboard shortcut handler
4828
4993
  React.useEffect(() => {
4829
4994
  if (!enableKeyboardShortcuts)
@@ -4849,6 +5014,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4849
5014
  defaultContextMenuHandlers.onUndo &&
4850
5015
  defaultContextMenuHandlers.canUndo) {
4851
5016
  if (defaultContextMenuHandlers.canUndo) {
5017
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5018
+ showToast(`Undo (${modKeyLabel} + Z)`);
4852
5019
  defaultContextMenuHandlers.onUndo();
4853
5020
  }
4854
5021
  }
@@ -4862,6 +5029,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4862
5029
  defaultContextMenuHandlers.onRedo &&
4863
5030
  defaultContextMenuHandlers.canRedo) {
4864
5031
  if (defaultContextMenuHandlers.canRedo) {
5032
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5033
+ showToast(`Redo (${modKeyLabel} + Shift + Z)`);
4865
5034
  defaultContextMenuHandlers.onRedo();
4866
5035
  }
4867
5036
  }
@@ -4872,6 +5041,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4872
5041
  const selection = wb.getSelection();
4873
5042
  if (selection.nodes.length > 0 || selection.edges.length > 0) {
4874
5043
  e.preventDefault();
5044
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5045
+ showToast(`Copy (${modKeyLabel} + C)`);
4875
5046
  // If single node selected, use node context menu handler; otherwise use selection handler
4876
5047
  if (selection.nodes.length === 1 && nodeContextMenuHandlers?.onCopy) {
4877
5048
  nodeContextMenuHandlers.onCopy();
@@ -4882,14 +5053,37 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4882
5053
  }
4883
5054
  return;
4884
5055
  }
4885
- // Duplicate: Cmd/Ctrl + D
4886
- if (modKey && key === "d" && !e.shiftKey && !e.altKey) {
5056
+ // Duplicate: Cmd/Ctrl + E
5057
+ if (modKey && key === "e" && !e.shiftKey && !e.altKey) {
4887
5058
  const selection = wb.getSelection();
4888
5059
  if (selection.nodes.length === 1 &&
4889
5060
  nodeContextMenuHandlers?.onDuplicate) {
5061
+ // Single node selected - use node context menu handler
4890
5062
  e.preventDefault();
5063
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5064
+ showToast(`Duplicate (${modKeyLabel} + E)`);
4891
5065
  nodeContextMenuHandlers.onDuplicate();
4892
5066
  }
5067
+ else if (selection.nodes.length > 1 &&
5068
+ selectionContextMenuHandlers.onDuplicate) {
5069
+ // Multiple nodes selected - use selection context menu handler
5070
+ e.preventDefault();
5071
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5072
+ showToast(`Duplicate (${modKeyLabel} + E)`);
5073
+ selectionContextMenuHandlers.onDuplicate();
5074
+ }
5075
+ return;
5076
+ }
5077
+ // Duplicate with edges: Cmd/Ctrl + Shift + E
5078
+ if (modKey && key === "e" && e.shiftKey && !e.altKey) {
5079
+ const selection = wb.getSelection();
5080
+ if (selection.nodes.length === 1 &&
5081
+ nodeContextMenuHandlers?.onDuplicateWithEdges) {
5082
+ e.preventDefault();
5083
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5084
+ showToast(`Duplicate with edges (${modKeyLabel} + Shift + E)`);
5085
+ nodeContextMenuHandlers.onDuplicateWithEdges();
5086
+ }
4893
5087
  return;
4894
5088
  }
4895
5089
  // Paste: Cmd/Ctrl + V
@@ -4900,6 +5094,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4900
5094
  defaultContextMenuHandlers.hasPasteData() &&
4901
5095
  "onPaste" in defaultContextMenuHandlers &&
4902
5096
  defaultContextMenuHandlers.onPaste) {
5097
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5098
+ showToast(`Paste (${modKeyLabel} + V)`);
4903
5099
  const center = rfInstanceRef.current?.screenToFlowPosition({
4904
5100
  x: window.innerWidth / 2,
4905
5101
  y: window.innerHeight / 2,
@@ -4908,6 +5104,16 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4908
5104
  }
4909
5105
  return;
4910
5106
  }
5107
+ // Select All: Cmd/Ctrl + A
5108
+ if (modKey && key === "a" && !e.shiftKey && !e.altKey) {
5109
+ e.preventDefault();
5110
+ if (defaultContextMenuHandlers.onSelectAll) {
5111
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
5112
+ showToast(`Select All (${modKeyLabel} + A)`);
5113
+ defaultContextMenuHandlers.onSelectAll();
5114
+ }
5115
+ return;
5116
+ }
4911
5117
  // Note: Delete/Backspace is handled by ReactFlow's deleteKeyCode prop
4912
5118
  // which triggers onNodesDelete/onEdgesDelete, so we don't need to handle it here
4913
5119
  };
@@ -4923,6 +5129,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4923
5129
  selectionContextMenuHandlers,
4924
5130
  nodeContextMenuHandlers,
4925
5131
  rfInstanceRef,
5132
+ showToast,
4926
5133
  ]);
4927
5134
  // Get custom renderers from UI extension registry (reactive to uiVersion changes)
4928
5135
  const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
@@ -4938,7 +5145,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4938
5145
  const onMoveEnd = React.useCallback(() => {
4939
5146
  if (rfInstanceRef.current) {
4940
5147
  const viewport = rfInstanceRef.current.getViewport();
4941
- wb.setViewport({ x: viewport.x, y: viewport.y, zoom: viewport.zoom });
5148
+ const viewportData = lod.pick(viewport, ["x", "y", "zoom"]);
5149
+ wb.setViewport(viewportData);
4942
5150
  }
4943
5151
  }, [wb]);
4944
5152
  const viewportRef = React.useRef(null);
@@ -4959,24 +5167,24 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4959
5167
  });
4960
5168
  }
4961
5169
  });
4962
- return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, connectionLineComponent: connectionLineRenderer, selectionOnDrag: true, onInit: (inst) => {
4963
- rfInstanceRef.current = inst;
4964
- const savedViewport = wb.getViewport();
4965
- if (savedViewport) {
4966
- viewportRef.current = savedViewport;
4967
- inst.setViewport({
4968
- x: savedViewport.x,
4969
- y: savedViewport.y,
4970
- zoom: savedViewport.zoom,
4971
- });
4972
- }
4973
- }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [BackgroundRenderer ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
4974
- ? { enableKeyboardShortcuts, keyboardShortcuts }
4975
- : {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
4976
- nodeContextMenuHandlers &&
4977
- (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, ...(enableKeyboardShortcuts !== false
5170
+ return (jsxRuntime.jsxs("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: [jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, connectionLineComponent: connectionLineRenderer, selectionOnDrag: true, onInit: (inst) => {
5171
+ rfInstanceRef.current = inst;
5172
+ const savedViewport = wb.getViewport();
5173
+ if (savedViewport) {
5174
+ viewportRef.current = savedViewport;
5175
+ inst.setViewport({
5176
+ x: savedViewport.x,
5177
+ y: savedViewport.y,
5178
+ zoom: savedViewport.zoom,
5179
+ });
5180
+ }
5181
+ }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [BackgroundRenderer ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
4978
5182
  ? { enableKeyboardShortcuts, keyboardShortcuts }
4979
- : {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))), selectionMenuOpen && selectionMenuPos && (jsxRuntime.jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))] }) }) }));
5183
+ : {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
5184
+ nodeContextMenuHandlers &&
5185
+ (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, ...(enableKeyboardShortcuts !== false
5186
+ ? { enableKeyboardShortcuts, keyboardShortcuts }
5187
+ : {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))), selectionMenuOpen && selectionMenuPos && (jsxRuntime.jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))] }) }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
4980
5188
  });
4981
5189
 
4982
5190
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
@@ -5539,6 +5747,7 @@ exports.createNodeCopyHandler = createNodeCopyHandler;
5539
5747
  exports.createSelectionContextMenuHandlers = createSelectionContextMenuHandlers;
5540
5748
  exports.download = download;
5541
5749
  exports.estimateNodeSize = estimateNodeSize;
5750
+ exports.excludeViewportFromUIState = excludeViewportFromUIState;
5542
5751
  exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
5543
5752
  exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
5544
5753
  exports.getBakeableOutputs = getBakeableOutputs;
@@ -5547,6 +5756,7 @@ exports.getHandleBoundsY = getHandleBoundsY;
5547
5756
  exports.getHandleClassName = getHandleClassName;
5548
5757
  exports.getHandleLayoutY = getHandleLayoutY;
5549
5758
  exports.getNodeBorderClassNames = getNodeBorderClassNames;
5759
+ exports.isValidViewport = isValidViewport;
5550
5760
  exports.layoutNode = layoutNode;
5551
5761
  exports.preformatValueForDisplay = preformatValueForDisplay;
5552
5762
  exports.prettyHandle = prettyHandle;