@bian-womp/spark-workbench 0.2.83 → 0.2.84

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 +285 -208
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/InMemoryWorkbench.d.ts +16 -25
  4. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
  5. package/lib/cjs/src/core/contracts.d.ts +4 -1
  6. package/lib/cjs/src/core/contracts.d.ts.map +1 -1
  7. package/lib/cjs/src/core/ui-extensions.d.ts +16 -7
  8. package/lib/cjs/src/core/ui-extensions.d.ts.map +1 -1
  9. package/lib/cjs/src/index.d.ts +2 -0
  10. package/lib/cjs/src/index.d.ts.map +1 -1
  11. package/lib/cjs/src/misc/DefaultNode.d.ts +0 -14
  12. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  13. package/lib/cjs/src/misc/DefaultNodeContent.d.ts +4 -0
  14. package/lib/cjs/src/misc/DefaultNodeContent.d.ts.map +1 -0
  15. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts +15 -0
  16. package/lib/cjs/src/misc/DefaultNodeHeader.d.ts.map +1 -0
  17. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts +2 -0
  18. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  19. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  20. package/lib/cjs/src/misc/hooks.d.ts.map +1 -1
  21. package/lib/cjs/src/misc/mapping.d.ts +9 -2
  22. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  23. package/lib/cjs/src/misc/merge-utils.d.ts +6 -1
  24. package/lib/cjs/src/misc/merge-utils.d.ts.map +1 -1
  25. package/lib/cjs/src/misc/types.d.ts +4 -0
  26. package/lib/cjs/src/misc/types.d.ts.map +1 -1
  27. package/lib/esm/index.js +287 -210
  28. package/lib/esm/index.js.map +1 -1
  29. package/lib/esm/src/core/InMemoryWorkbench.d.ts +16 -25
  30. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
  31. package/lib/esm/src/core/contracts.d.ts +4 -1
  32. package/lib/esm/src/core/contracts.d.ts.map +1 -1
  33. package/lib/esm/src/core/ui-extensions.d.ts +16 -7
  34. package/lib/esm/src/core/ui-extensions.d.ts.map +1 -1
  35. package/lib/esm/src/index.d.ts +2 -0
  36. package/lib/esm/src/index.d.ts.map +1 -1
  37. package/lib/esm/src/misc/DefaultNode.d.ts +0 -14
  38. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  39. package/lib/esm/src/misc/DefaultNodeContent.d.ts +4 -0
  40. package/lib/esm/src/misc/DefaultNodeContent.d.ts.map +1 -0
  41. package/lib/esm/src/misc/DefaultNodeHeader.d.ts +15 -0
  42. package/lib/esm/src/misc/DefaultNodeHeader.d.ts.map +1 -0
  43. package/lib/esm/src/misc/WorkbenchCanvas.d.ts +2 -0
  44. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  45. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  46. package/lib/esm/src/misc/hooks.d.ts.map +1 -1
  47. package/lib/esm/src/misc/mapping.d.ts +9 -2
  48. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  49. package/lib/esm/src/misc/merge-utils.d.ts +6 -1
  50. package/lib/esm/src/misc/merge-utils.d.ts.map +1 -1
  51. package/lib/esm/src/misc/types.d.ts +4 -0
  52. package/lib/esm/src/misc/types.d.ts.map +1 -1
  53. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -75,6 +75,13 @@ class DefaultUIExtensionRegistry {
75
75
  getNodeContextMenuRenderer() {
76
76
  return this.nodeContextMenuRenderer;
77
77
  }
78
+ registerSelectionContextMenuRenderer(renderer) {
79
+ this.selectionContextMenuRenderer = renderer;
80
+ return this;
81
+ }
82
+ getSelectionContextMenuRenderer() {
83
+ return this.selectionContextMenuRenderer;
84
+ }
78
85
  // Layout function overrides
79
86
  registerEstimateNodeSize(override) {
80
87
  this.estimateNodeSizeOverride = override;
@@ -126,6 +133,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
126
133
  this._def = { nodes: [], edges: [] };
127
134
  this.listeners = new Map();
128
135
  this.positions = {};
136
+ this.sizes = {};
129
137
  this.selection = {
130
138
  nodes: [],
131
139
  edges: [],
@@ -287,27 +295,36 @@ class InMemoryWorkbench extends AbstractWorkbench {
287
295
  });
288
296
  }
289
297
  // Position and selection APIs for React Flow bridge
290
- setPositions(map, options) {
291
- this.positions = { ...this.positions, ...map };
298
+ setPositions(positions, options) {
299
+ this.positions = { ...this.positions, ...positions };
300
+ this.emit("graphUiChanged", { change: { type: "moveNodes" }, ...options });
301
+ }
302
+ getPositions() {
303
+ return { ...this.positions };
304
+ }
305
+ setSizes(sizes, options) {
306
+ for (const [nodeId, size] of Object.entries(sizes)) {
307
+ if (size) {
308
+ this.sizes = { ...this.sizes, [nodeId]: size };
309
+ }
310
+ else {
311
+ this.sizes = lod.omit(this.sizes, nodeId);
312
+ }
313
+ }
292
314
  this.emit("graphUiChanged", {
293
- def: this._def,
294
- change: { type: "moveNodes" },
315
+ change: { type: "resizeNodes" },
295
316
  ...options,
296
317
  });
297
318
  }
298
- getPositions() {
299
- return { ...this.positions };
319
+ getSizes() {
320
+ return { ...this.sizes };
300
321
  }
301
322
  setSelectionInternal(sel, options) {
302
323
  if (lod.isEqual(this.selection, sel))
303
324
  return;
304
325
  this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
305
326
  this.emit("selectionChanged", this.selection);
306
- this.emit("graphUiChanged", {
307
- def: this._def,
308
- change: { type: "selection" },
309
- ...options,
310
- });
327
+ this.emit("graphUiChanged", { change: { type: "selection" }, ...options });
311
328
  }
312
329
  setSelection(sel, options) {
313
330
  this.setSelectionInternal(sel, options);
@@ -338,10 +355,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
338
355
  if (lod.isEqual(this.viewport, viewport))
339
356
  return;
340
357
  this.viewport = { ...viewport };
341
- this.emit("graphUiChanged", {
342
- def: this._def,
343
- change: { type: "viewport" },
344
- });
358
+ this.emit("graphUiChanged", { change: { type: "viewport" } });
345
359
  }
346
360
  getViewport() {
347
361
  return this.viewport ? { ...this.viewport } : null;
@@ -353,6 +367,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
353
367
  const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
354
368
  const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
355
369
  const filteredNodeNames = Object.fromEntries(Object.entries(this.nodeNames).filter(([id]) => defNodeIds.has(id)));
370
+ const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
356
371
  return {
357
372
  positions: Object.keys(filteredPositions).length > 0
358
373
  ? filteredPositions
@@ -367,6 +382,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
367
382
  nodeNames: Object.keys(filteredNodeNames).length > 0
368
383
  ? filteredNodeNames
369
384
  : undefined,
385
+ sizes: Object.keys(filteredSizes).length > 0 ? filteredSizes : undefined,
370
386
  };
371
387
  }
372
388
  setUIState(ui) {
@@ -381,12 +397,27 @@ class InMemoryWorkbench extends AbstractWorkbench {
381
397
  edges: [...ui.selection.edges],
382
398
  };
383
399
  this.emit("selectionChanged", this.selection);
400
+ this.emit("graphUiChanged", {
401
+ change: { type: "selection" },
402
+ init: true,
403
+ });
384
404
  }
385
405
  if (ui.viewport) {
386
406
  this.viewport = { ...ui.viewport };
407
+ this.emit("graphUiChanged", { change: { type: "viewport" }, init: true });
387
408
  }
388
409
  if (ui.nodeNames !== undefined) {
389
410
  this.nodeNames = { ...ui.nodeNames };
411
+ for (const [nodeId, name] of Object.entries(ui.nodeNames)) {
412
+ this.emit("graphUiChanged", {
413
+ change: { type: "nodeName", nodeId, name },
414
+ init: true,
415
+ });
416
+ }
417
+ }
418
+ if (ui.sizes !== undefined) {
419
+ const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
420
+ this.sizes = Object.fromEntries(Object.entries(ui.sizes).filter(([id]) => defNodeIds.has(id)));
390
421
  }
391
422
  }
392
423
  getRuntimeState() {
@@ -775,8 +806,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
775
806
  this.nodeNames[nodeId] = name.trim();
776
807
  }
777
808
  this.emit("graphUiChanged", {
778
- def: this._def,
779
- change: { type: "nodeName", nodeId },
809
+ change: { type: "nodeName", nodeId, name },
780
810
  ...options,
781
811
  });
782
812
  }
@@ -2196,7 +2226,6 @@ function useWorkbenchBridge(wb) {
2196
2226
  }, { commit: true });
2197
2227
  }, [wb]);
2198
2228
  const onNodesChange = React.useCallback((changes) => {
2199
- // Apply position updates continuously, but mark commit only on drag end
2200
2229
  const positions = {};
2201
2230
  let commit = false;
2202
2231
  changes.forEach((c) => {
@@ -2206,6 +2235,14 @@ function useWorkbenchBridge(wb) {
2206
2235
  commit = true;
2207
2236
  }
2208
2237
  });
2238
+ const sizes = {};
2239
+ changes.forEach((c) => {
2240
+ if (c.type === "dimensions" && c.dimensions) {
2241
+ sizes[c.id] = c.dimensions;
2242
+ if (!c.resizing)
2243
+ commit = true;
2244
+ }
2245
+ });
2209
2246
  // Derive next node selection from change set
2210
2247
  const current = wb.getSelection();
2211
2248
  const nextNodeIds = new Set(current.nodes);
@@ -2240,7 +2277,12 @@ function useWorkbenchBridge(wb) {
2240
2277
  });
2241
2278
  }
2242
2279
  if (Object.keys(positions).length > 0) {
2243
- wb.setPositions(positions, { commit });
2280
+ wb.setPositions(positions, {
2281
+ commit: commit && !Object.keys(sizes).length,
2282
+ });
2283
+ }
2284
+ if (Object.keys(sizes).length > 0) {
2285
+ wb.setSizes(sizes, { commit });
2244
2286
  }
2245
2287
  }, [wb]);
2246
2288
  const onEdgesDelete = React.useCallback((edges) => edges.forEach((e, idx) => wb.disconnect(e.id, { commit: idx === edges.length - 1 })), [wb]);
@@ -2430,7 +2472,7 @@ function useQueryParamString(key, defaultValue) {
2430
2472
  return [val, set];
2431
2473
  }
2432
2474
 
2433
- function toReactFlow(def, positions, registry, opts) {
2475
+ function toReactFlow(def, positions, sizes, registry, opts) {
2434
2476
  const EDGE_STYLE_MISSING = { stroke: "#f59e0b", strokeWidth: 2 }; // amber-500
2435
2477
  const EDGE_STYLE_ERROR = { stroke: "#ef4444", strokeWidth: 2 };
2436
2478
  const EDGE_STYLE_RUNNING = { stroke: "#3b82f6" };
@@ -2474,45 +2516,41 @@ function toReactFlow(def, positions, registry, opts) {
2474
2516
  const createHandleBoundsFn = opts.ui?.getCreateHandleBounds() ?? createHandleBounds;
2475
2517
  const createHandleLayoutFn = opts.ui?.getCreateHandleLayout() ?? createHandleLayout;
2476
2518
  const estimateNodeSizeFn = opts.ui?.getEstimateNodeSize() ?? estimateNodeSize;
2477
- const nodes = def.nodes.map((n) => {
2478
- const { inputs: inputSource, outputs: outputSource } = computeEffectiveHandles(n, registry);
2479
- const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
2480
- // If layoutNode is overridden, use it directly; otherwise use default with internal overrides
2481
- const geom = layoutNodeOverride
2519
+ const computeLayout = (node, overrides) => {
2520
+ return layoutNodeOverride
2482
2521
  ? layoutNodeOverride({
2483
- node: n,
2522
+ node,
2484
2523
  registry,
2485
2524
  showValues: opts.showValues,
2486
- overrides: overrideSize,
2525
+ overrides,
2487
2526
  })
2488
2527
  : layoutNode({
2489
- node: n,
2528
+ node,
2490
2529
  registry,
2491
2530
  showValues: opts.showValues,
2492
- overrides: overrideSize,
2531
+ overrides,
2493
2532
  }, {
2494
2533
  estimateNodeSize: estimateNodeSizeFn,
2495
2534
  createHandleBounds: createHandleBoundsFn,
2496
2535
  createHandleLayout: createHandleLayoutFn,
2497
2536
  });
2498
- const inputHandles = geom.inputOrder.map((id) => ({
2499
- id,
2500
- typeId: sparkGraph.getInputTypeId(inputSource, id),
2501
- }));
2502
- const outputHandles = geom.outputOrder.map((id) => ({
2503
- id,
2504
- typeId: formatDeclaredTypeSignature(outputSource[id]),
2505
- }));
2506
- nodeHandleMap[n.nodeId] = {
2507
- inputs: new Set(inputHandles.map((h) => h.id)),
2508
- outputs: new Set(outputHandles.map((h) => h.id)),
2509
- };
2510
- // Append placeholder entries for any missing handles (below valid ones)
2537
+ };
2538
+ const calculateDimensionsWithMissingHandles = (geom, extraInputs, extraOutputs) => {
2511
2539
  const baseLeftCount = geom.inputOrder.length;
2512
2540
  const baseRightCount = geom.outputOrder.length;
2513
- const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
2514
- const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
2515
- const extraHandleLayoutLeft = extraInputs.map((id, i) => ({
2541
+ const baseRows = Math.max(baseLeftCount, baseRightCount);
2542
+ const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
2543
+ return {
2544
+ baseLeftCount,
2545
+ baseRightCount,
2546
+ baseRows,
2547
+ newRows,
2548
+ width: geom.width,
2549
+ height: geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX,
2550
+ };
2551
+ };
2552
+ const createMissingHandleLayouts = (extraInputs, extraOutputs, baseLeftCount, baseRightCount) => {
2553
+ const left = extraInputs.map((id, i) => ({
2516
2554
  ...createHandleLayoutFn({
2517
2555
  id,
2518
2556
  type: "target",
@@ -2521,7 +2559,7 @@ function toReactFlow(def, positions, registry, opts) {
2521
2559
  }),
2522
2560
  missing: true,
2523
2561
  }));
2524
- const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
2562
+ const right = extraOutputs.map((id, i) => ({
2525
2563
  ...createHandleLayoutFn({
2526
2564
  id,
2527
2565
  type: "source",
@@ -2530,36 +2568,58 @@ function toReactFlow(def, positions, registry, opts) {
2530
2568
  }),
2531
2569
  missing: true,
2532
2570
  }));
2533
- const handleLayout = [
2534
- ...geom.handleLayout,
2535
- ...extraHandleLayoutLeft,
2536
- ...extraHandleLayoutRight,
2537
- ];
2538
- // Precompute handle bounds (including missing) so edges can render immediately
2539
- const missingBoundsLeft = extraInputs.map((id, i) => createHandleBoundsFn({
2571
+ return [...left, ...right];
2572
+ };
2573
+ const createMissingHandleBounds = (extraInputs, extraOutputs, baseLeftCount, baseRightCount, nodeWidth) => {
2574
+ const left = extraInputs.map((id, i) => createHandleBoundsFn({
2540
2575
  id,
2541
2576
  type: "target",
2542
2577
  position: react.Position.Left,
2543
2578
  rowIndex: baseLeftCount + i,
2544
- nodeWidth: geom.width,
2579
+ nodeWidth,
2545
2580
  }));
2546
- const missingBoundsRight = extraOutputs.map((id, i) => createHandleBoundsFn({
2581
+ const right = extraOutputs.map((id, i) => createHandleBoundsFn({
2547
2582
  id,
2548
2583
  type: "source",
2549
2584
  position: react.Position.Right,
2550
2585
  rowIndex: baseRightCount + i,
2551
- nodeWidth: geom.width,
2586
+ nodeWidth,
2552
2587
  }));
2553
- const handles = [
2554
- ...geom.handles,
2555
- ...missingBoundsLeft,
2556
- ...missingBoundsRight,
2557
- ];
2558
- // Adjust node height to accommodate missing handle rows
2559
- const baseRows = Math.max(baseLeftCount, baseRightCount);
2560
- const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
2561
- const initialWidth = geom.width;
2562
- const initialHeight = geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX;
2588
+ return [...left, ...right];
2589
+ };
2590
+ const nodes = def.nodes.map((n) => {
2591
+ const { inputs: inputSource, outputs: outputSource } = computeEffectiveHandles(n, registry);
2592
+ const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
2593
+ const customSize = sizes?.[n.nodeId];
2594
+ const sizeOverrides = customSize
2595
+ ? { ...overrideSize, width: customSize.width, height: customSize.height }
2596
+ : overrideSize;
2597
+ const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
2598
+ const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
2599
+ const geom = computeLayout(n, sizeOverrides);
2600
+ const finalDims = calculateDimensionsWithMissingHandles(geom, extraInputs, extraOutputs);
2601
+ const renderWidth = customSize?.width ?? finalDims.width;
2602
+ const renderHeight = customSize?.height ?? finalDims.height;
2603
+ const initialGeom = customSize ? computeLayout(n, overrideSize) : geom;
2604
+ const initialDims = customSize
2605
+ ? calculateDimensionsWithMissingHandles(initialGeom, extraInputs, extraOutputs)
2606
+ : finalDims;
2607
+ const inputHandles = geom.inputOrder.map((id) => ({
2608
+ id,
2609
+ typeId: sparkGraph.getInputTypeId(inputSource, id),
2610
+ }));
2611
+ const outputHandles = geom.outputOrder.map((id) => ({
2612
+ id,
2613
+ typeId: formatDeclaredTypeSignature(outputSource[id]),
2614
+ }));
2615
+ nodeHandleMap[n.nodeId] = {
2616
+ inputs: new Set(inputHandles.map((h) => h.id)),
2617
+ outputs: new Set(outputHandles.map((h) => h.id)),
2618
+ };
2619
+ const missingHandleLayouts = createMissingHandleLayouts(extraInputs, extraOutputs, finalDims.baseLeftCount, finalDims.baseRightCount);
2620
+ const handleLayout = [...geom.handleLayout, ...missingHandleLayouts];
2621
+ const missingHandleBounds = createMissingHandleBounds(extraInputs, extraOutputs, finalDims.baseLeftCount, finalDims.baseRightCount, renderWidth);
2622
+ const handles = [...geom.handles, ...missingHandleBounds];
2563
2623
  return {
2564
2624
  id: n.nodeId,
2565
2625
  data: {
@@ -2573,8 +2633,10 @@ function toReactFlow(def, positions, registry, opts) {
2573
2633
  ])),
2574
2634
  handleLayout,
2575
2635
  showValues: opts.showValues,
2576
- renderWidth: initialWidth,
2577
- renderHeight: initialHeight,
2636
+ renderWidth,
2637
+ renderHeight,
2638
+ initialWidth: initialDims.width,
2639
+ initialHeight: initialDims.height,
2578
2640
  inputValues: opts.inputs?.[n.nodeId],
2579
2641
  inputDefaults: opts.inputDefaults?.[n.nodeId],
2580
2642
  outputValues: opts.outputs?.[n.nodeId],
@@ -2592,11 +2654,13 @@ function toReactFlow(def, positions, registry, opts) {
2592
2654
  selected: opts.selectedNodeIds
2593
2655
  ? opts.selectedNodeIds.has(n.nodeId)
2594
2656
  : undefined,
2595
- initialWidth,
2596
- initialHeight,
2657
+ measured: {
2658
+ width: renderWidth,
2659
+ height: renderHeight,
2660
+ },
2597
2661
  handles,
2598
- width: initialWidth,
2599
- height: initialHeight,
2662
+ width: renderWidth,
2663
+ height: renderHeight,
2600
2664
  };
2601
2665
  });
2602
2666
  const edges = def.edges.map((e) => {
@@ -2803,22 +2867,30 @@ async function upload(parsed, wb, runner) {
2803
2867
  /**
2804
2868
  * Merge UI state from source into target, remapping node IDs using nodeIdMap.
2805
2869
  * Preserves target state and adds/updates source state with remapped IDs.
2870
+ * If anchorPos and sourceDef are provided, positions will be offset relative to anchorPos.
2806
2871
  */
2807
- function mergeUIState(targetUI, sourceUI, nodeIdMap) {
2872
+ function mergeUIState(targetUI, sourceUI, nodeIdMap, anchorPos, sourceDef) {
2808
2873
  const result = {
2809
2874
  ...targetUI,
2810
2875
  };
2811
2876
  if (!sourceUI)
2812
2877
  return result;
2813
- // Merge positions (already handled by mergeSnapshotData, but included for completeness)
2878
+ // Merge positions with optional offset
2814
2879
  if (sourceUI.positions) {
2815
- result.positions = {
2816
- ...(targetUI?.positions || {}),
2817
- ...Object.fromEntries(Object.entries(sourceUI.positions).map(([oldId, pos]) => [
2818
- nodeIdMap[oldId] || oldId,
2819
- pos,
2820
- ])),
2821
- };
2880
+ if (anchorPos && sourceDef) {
2881
+ // Apply offset when anchorPos and sourceDef are provided
2882
+ result.positions = sparkGraph.offsetImportedPositions(targetUI?.positions ?? {}, sourceUI.positions, sourceDef, nodeIdMap, anchorPos);
2883
+ }
2884
+ else {
2885
+ // Simple remapping without offset
2886
+ result.positions = {
2887
+ ...(targetUI?.positions || {}),
2888
+ ...Object.fromEntries(Object.entries(sourceUI.positions).map(([oldId, pos]) => [
2889
+ nodeIdMap[oldId] || oldId,
2890
+ pos,
2891
+ ])),
2892
+ };
2893
+ }
2822
2894
  }
2823
2895
  // Merge selection: remap node IDs and edge IDs
2824
2896
  if (sourceUI.selection) {
@@ -2841,6 +2913,15 @@ function mergeUIState(targetUI, sourceUI, nodeIdMap) {
2841
2913
  ])),
2842
2914
  };
2843
2915
  }
2916
+ if (sourceUI.sizes) {
2917
+ result.sizes = {
2918
+ ...(targetUI?.sizes || {}),
2919
+ ...Object.fromEntries(Object.entries(sourceUI.sizes).map(([oldId, size]) => [
2920
+ nodeIdMap[oldId] || oldId,
2921
+ size,
2922
+ ])),
2923
+ };
2924
+ }
2844
2925
  return result;
2845
2926
  }
2846
2927
 
@@ -3098,7 +3179,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3098
3179
  (type === "graphChanged" || type === "graphUiChanged")) {
3099
3180
  const changeType = payload
3100
3181
  .change?.type;
3101
- if (changeType === "moveNode" || changeType === "moveNodes")
3182
+ if (changeType === "moveNode" ||
3183
+ changeType === "moveNodes" ||
3184
+ changeType === "resizeNodes")
3102
3185
  return prev;
3103
3186
  }
3104
3187
  const nextNo = prev.length > 0 ? (prev[0]?.no ?? 0) + 1 : 1;
@@ -3507,6 +3590,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3507
3590
  else if (changeType === "moveNodes") {
3508
3591
  reason = "move-nodes";
3509
3592
  }
3593
+ else if (changeType === "resizeNodes") {
3594
+ reason = "resize-nodes";
3595
+ }
3510
3596
  else if (changeType === "selection") {
3511
3597
  reason = "selection";
3512
3598
  }
@@ -4264,103 +4350,6 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
4264
4350
  }, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) })] }), debug && (jsxRuntime.jsx("div", { className: "mt-3 flex-none min-h-0 h-[50%]", children: jsxRuntime.jsx(DebugEvents, { autoScroll: !!autoScroll, hideWorkbench: !!hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange }) }))] }));
4265
4351
  }
4266
4352
 
4267
- function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
4268
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Handle, { id: id, type: type, position: position, isConnectable: isConnectable, className: className, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsxRuntime.jsx("div", { className: labelClassName + (kind === "input" ? " left-2" : " right-2"), style: {
4269
- top: (y ?? 0) - 8,
4270
- ...(kind === "input"
4271
- ? { right: "50%" }
4272
- : { left: "50%", textAlign: "right" }),
4273
- whiteSpace: "nowrap",
4274
- overflow: "hidden",
4275
- textOverflow: "ellipsis",
4276
- }, children: renderLabel({ kind, id }) }))] }));
4277
- }
4278
- function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
4279
- const layout = data.handleLayout ?? [];
4280
- const byId = React.useMemo(() => {
4281
- const m = new Map();
4282
- for (const h of layout) {
4283
- // Prefer namespaced key to disambiguate inputs/outputs that share id
4284
- m.set(`${h.type}:${h.id}`, {
4285
- position: h.position,
4286
- y: h.y,
4287
- type: h.type,
4288
- missing: h.missing,
4289
- });
4290
- // Back-compat: also store by id-only if not already set
4291
- if (!m.has(h.id))
4292
- m.set(h.id, {
4293
- position: h.position,
4294
- y: h.y,
4295
- type: h.type,
4296
- missing: h.missing,
4297
- });
4298
- }
4299
- return m;
4300
- }, [layout]);
4301
- const inputIds = React.useMemo(() => new Set((data.inputHandles ?? []).map((h) => h.id)), [data.inputHandles]);
4302
- const outputIds = React.useMemo(() => new Set((data.outputHandles ?? []).map((h) => h.id)), [data.outputHandles]);
4303
- const missingInputs = React.useMemo(() => (layout || []).filter((h) => h.type === "target" && (!inputIds.has(h.id) || h.missing)), [layout, inputIds]);
4304
- const missingOutputs = React.useMemo(() => (layout || []).filter((h) => h.type === "source" && (!outputIds.has(h.id) || h.missing)), [layout, outputIds]);
4305
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(data.inputHandles ?? []).map((h) => {
4306
- const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
4307
- const position = placed?.position ?? react.Position.Left;
4308
- const y = placed?.y;
4309
- const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
4310
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
4311
- }), missingInputs.map((h) => {
4312
- const key = `missing-input:${h.id}`;
4313
- const position = h.position ?? react.Position.Left;
4314
- const y = h.y;
4315
- const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 wb-nodrag wb-nowheel";
4316
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
4317
- }), (data.outputHandles ?? []).map((h) => {
4318
- const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
4319
- const position = placed?.position ?? react.Position.Right;
4320
- const y = placed?.y;
4321
- const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
4322
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
4323
- }), missingOutputs.map((h) => {
4324
- const key = `missing-output:${h.id}`;
4325
- const position = h.position ?? react.Position.Right;
4326
- const y = h.y;
4327
- const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
4328
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
4329
- })] }));
4330
- }
4331
-
4332
- const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable, }) {
4333
- const updateNodeInternals = react.useUpdateNodeInternals();
4334
- const { typeId, showValues } = data;
4335
- const inputEntries = data.inputHandles ?? [];
4336
- const outputEntries = data.outputHandles ?? [];
4337
- React.useEffect(() => {
4338
- updateNodeInternals(id);
4339
- }, [
4340
- id,
4341
- inputEntries.length,
4342
- outputEntries.length,
4343
- showValues,
4344
- updateNodeInternals,
4345
- ]);
4346
- const status = data.status ?? { activeRuns: 0 };
4347
- const validation = data.validation ?? {
4348
- inputs: [],
4349
- outputs: [],
4350
- issues: [],
4351
- };
4352
- const containerBorder = getNodeBorderClassNames({
4353
- selected,
4354
- status,
4355
- validation,
4356
- });
4357
- return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/50 !dark:bg-stone-900", containerBorder), style: {
4358
- position: "relative",
4359
- minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
4360
- minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
4361
- }, children: [jsxRuntime.jsx(DefaultNodeHeader, { id: id, typeId: typeId, validation: validation, showId: data.showValues }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
4362
- });
4363
- DefaultNode.displayName = "DefaultNode";
4364
4353
  function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInvalidate, }) {
4365
4354
  const ctx = useWorkbenchContext();
4366
4355
  const [isEditing, setIsEditing] = React.useState(false);
@@ -4444,6 +4433,72 @@ function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInv
4444
4433
  .map((v) => `${v.code}: ${v.message}`)
4445
4434
  .join("; ") })), showId && jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
4446
4435
  }
4436
+
4437
+ function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
4438
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Handle, { id: id, type: type, position: position, isConnectable: isConnectable, className: className, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsxRuntime.jsx("div", { className: labelClassName + (kind === "input" ? " left-2" : " right-2"), style: {
4439
+ top: (y ?? 0) - 8,
4440
+ ...(kind === "input"
4441
+ ? { right: "50%" }
4442
+ : { left: "50%", textAlign: "right" }),
4443
+ whiteSpace: "nowrap",
4444
+ overflow: "hidden",
4445
+ textOverflow: "ellipsis",
4446
+ }, children: renderLabel({ kind, id }) }))] }));
4447
+ }
4448
+ function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
4449
+ const layout = data.handleLayout ?? [];
4450
+ const byId = React.useMemo(() => {
4451
+ const m = new Map();
4452
+ for (const h of layout) {
4453
+ // Prefer namespaced key to disambiguate inputs/outputs that share id
4454
+ m.set(`${h.type}:${h.id}`, {
4455
+ position: h.position,
4456
+ y: h.y,
4457
+ type: h.type,
4458
+ missing: h.missing,
4459
+ });
4460
+ // Back-compat: also store by id-only if not already set
4461
+ if (!m.has(h.id))
4462
+ m.set(h.id, {
4463
+ position: h.position,
4464
+ y: h.y,
4465
+ type: h.type,
4466
+ missing: h.missing,
4467
+ });
4468
+ }
4469
+ return m;
4470
+ }, [layout]);
4471
+ const inputIds = React.useMemo(() => new Set((data.inputHandles ?? []).map((h) => h.id)), [data.inputHandles]);
4472
+ const outputIds = React.useMemo(() => new Set((data.outputHandles ?? []).map((h) => h.id)), [data.outputHandles]);
4473
+ const missingInputs = React.useMemo(() => (layout || []).filter((h) => h.type === "target" && (!inputIds.has(h.id) || h.missing)), [layout, inputIds]);
4474
+ const missingOutputs = React.useMemo(() => (layout || []).filter((h) => h.type === "source" && (!outputIds.has(h.id) || h.missing)), [layout, outputIds]);
4475
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(data.inputHandles ?? []).map((h) => {
4476
+ const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
4477
+ const position = placed?.position ?? react.Position.Left;
4478
+ const y = placed?.y;
4479
+ const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
4480
+ return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
4481
+ }), missingInputs.map((h) => {
4482
+ const key = `missing-input:${h.id}`;
4483
+ const position = h.position ?? react.Position.Left;
4484
+ const y = h.y;
4485
+ const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 wb-nodrag wb-nowheel";
4486
+ return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
4487
+ }), (data.outputHandles ?? []).map((h) => {
4488
+ const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
4489
+ const position = placed?.position ?? react.Position.Right;
4490
+ const y = placed?.y;
4491
+ const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
4492
+ return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
4493
+ }), missingOutputs.map((h) => {
4494
+ const key = `missing-output:${h.id}`;
4495
+ const position = h.position ?? react.Position.Right;
4496
+ const y = h.y;
4497
+ const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
4498
+ return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
4499
+ })] }));
4500
+ }
4501
+
4447
4502
  function DefaultNodeContent({ data, isConnectable, }) {
4448
4503
  const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
4449
4504
  const inputEntries = data.inputHandles ?? [];
@@ -4496,6 +4551,24 @@ function DefaultNodeContent({ data, isConnectable, }) {
4496
4551
  } })] }));
4497
4552
  }
4498
4553
 
4554
+ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable, }) {
4555
+ const nodeRef = React.useRef(null);
4556
+ const { typeId } = data;
4557
+ const status = data.status ?? { activeRuns: 0 };
4558
+ const validation = data.validation ?? {
4559
+ inputs: [],
4560
+ outputs: [],
4561
+ issues: [],
4562
+ };
4563
+ const containerBorder = getNodeBorderClassNames({
4564
+ selected,
4565
+ status,
4566
+ validation,
4567
+ });
4568
+ return (jsxRuntime.jsxs("div", { ref: nodeRef, className: cx("rounded-lg bg-white/50 !dark:bg-stone-900 w-full h-full relative", containerBorder), children: [jsxRuntime.jsx(react.NodeResizer, { isVisible: selected, minWidth: data.initialWidth, minHeight: data.initialHeight }), jsxRuntime.jsx(DefaultNodeHeader, { id: id, typeId: typeId, validation: validation, showId: data.showValues }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
4569
+ });
4570
+ DefaultNode.displayName = "DefaultNode";
4571
+
4499
4572
  // Helper to format shortcut for current platform
4500
4573
  function formatShortcut(shortcut) {
4501
4574
  const isMac = typeof navigator !== "undefined" &&
@@ -4776,8 +4849,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4776
4849
  position: n.position,
4777
4850
  type: n.type,
4778
4851
  selected: n.selected,
4779
- initialWidth: n.initialWidth,
4780
- initialHeight: n.initialHeight,
4852
+ measured: n.measured,
4781
4853
  data: n.data && {
4782
4854
  typeId: n.data.typeId,
4783
4855
  inputHandles: n.data.inputHandles,
@@ -4811,11 +4883,18 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4811
4883
  React.useImperativeHandle(ref, () => ({
4812
4884
  fitView: () => {
4813
4885
  try {
4814
- rfInstanceRef.current?.fitView({ padding: 0.2 });
4886
+ rfInstanceRef.current?.fitView();
4887
+ }
4888
+ catch (err) {
4889
+ console.warn("Failed to fit view", err);
4815
4890
  }
4816
- catch { }
4817
4891
  },
4818
- }));
4892
+ setViewport: (viewport) => {
4893
+ if (rfInstanceRef.current) {
4894
+ rfInstanceRef.current.setViewport(lod.clone(viewport));
4895
+ }
4896
+ },
4897
+ }), []);
4819
4898
  const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb);
4820
4899
  const ui = wb.getUI();
4821
4900
  const { nodeTypes, resolveNodeType } = React.useMemo(() => {
@@ -4861,7 +4940,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4861
4940
  inputsWithDefaults[n.nodeId] = merged;
4862
4941
  }
4863
4942
  }
4864
- const out = toReactFlow(wb.def, wb.getPositions(), registry, {
4943
+ const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), registry, {
4865
4944
  showValues,
4866
4945
  inputs: inputsWithDefaults,
4867
4946
  inputDefaults: inputDefaultsMap,
@@ -5333,55 +5412,53 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5333
5412
  showToast,
5334
5413
  ]);
5335
5414
  // Get custom renderers from UI extension registry (reactive to uiVersion changes)
5336
- const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
5415
+ const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, SelectionContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
5337
5416
  return {
5338
5417
  BackgroundRenderer: ui.getBackgroundRenderer(),
5339
5418
  MinimapRenderer: ui.getMinimapRenderer(),
5340
5419
  ControlsRenderer: ui.getControlsRenderer(),
5341
5420
  DefaultContextMenuRenderer: ui.getDefaultContextMenuRenderer(),
5342
5421
  NodeContextMenuRenderer: ui.getNodeContextMenuRenderer(),
5422
+ SelectionContextMenuRenderer: ui.getSelectionContextMenuRenderer(),
5343
5423
  connectionLineRenderer: ui.getConnectionLineRenderer(),
5344
5424
  };
5345
5425
  }, [ui, uiVersion]);
5346
5426
  const onMoveEnd = React.useCallback(() => {
5347
5427
  if (rfInstanceRef.current) {
5348
5428
  const viewport = rfInstanceRef.current.getViewport();
5349
- const viewportData = lod.pick(viewport, ["x", "y", "zoom"]);
5429
+ const viewportData = lod.clone(viewport);
5350
5430
  wb.setViewport(viewportData);
5351
5431
  }
5352
5432
  }, [wb]);
5353
- const viewportRef = React.useRef(null);
5433
+ // Sync viewport when workbench fires graphUiChanged with viewport event
5354
5434
  React.useEffect(() => {
5355
- if (!rfInstanceRef.current)
5356
- return;
5357
- const currentViewport = wb.getViewport();
5358
- if (currentViewport &&
5359
- (!viewportRef.current ||
5360
- viewportRef.current.x !== currentViewport.x ||
5361
- viewportRef.current.y !== currentViewport.y ||
5362
- viewportRef.current.zoom !== currentViewport.zoom)) {
5363
- viewportRef.current = currentViewport;
5364
- rfInstanceRef.current.setViewport({
5365
- x: currentViewport.x,
5366
- y: currentViewport.y,
5367
- zoom: currentViewport.zoom,
5368
- });
5369
- }
5370
- });
5435
+ const off = wb.on("graphUiChanged", (event) => {
5436
+ if (event.change?.type === "viewport" &&
5437
+ rfInstanceRef.current &&
5438
+ event.init) {
5439
+ const viewport = wb.getViewport();
5440
+ if (viewport) {
5441
+ rfInstanceRef.current.setViewport(lod.clone(viewport));
5442
+ }
5443
+ }
5444
+ });
5445
+ return () => off();
5446
+ }, [wb]);
5371
5447
  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) => {
5372
5448
  rfInstanceRef.current = inst;
5373
5449
  const savedViewport = wb.getViewport();
5374
5450
  if (savedViewport) {
5375
- viewportRef.current = savedViewport;
5376
- inst.setViewport(lod.pick(savedViewport, ["x", "y", "zoom"]));
5451
+ inst.setViewport(lod.clone(savedViewport));
5377
5452
  }
5378
- }, 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
5453
+ }, 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", 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
5379
5454
  ? { enableKeyboardShortcuts, keyboardShortcuts }
5380
5455
  : {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
5381
5456
  nodeContextMenuHandlers &&
5382
5457
  (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, ...(enableKeyboardShortcuts !== false
5383
5458
  ? { enableKeyboardShortcuts, keyboardShortcuts }
5384
- : {}) })) : (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))] }));
5459
+ : {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))), selectionMenuOpen &&
5460
+ selectionMenuPos &&
5461
+ (SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }) }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
5385
5462
  });
5386
5463
 
5387
5464
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {