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