@bian-womp/spark-workbench 0.2.82 → 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 +293 -212
  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 +295 -214
  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]);
@@ -2292,8 +2334,9 @@ function useWorkbenchBridge(wb) {
2292
2334
  function useWorkbenchGraphTick(wb) {
2293
2335
  const [tick, setTick] = React.useState(0);
2294
2336
  React.useEffect(() => {
2295
- const bump = () => setTick((t) => t + 1);
2296
- const off = wb.on("graphChanged", bump);
2337
+ const off = wb.on("graphChanged", () => {
2338
+ setTick((t) => t + 1);
2339
+ });
2297
2340
  return () => off();
2298
2341
  }, [wb]);
2299
2342
  return tick;
@@ -2301,8 +2344,11 @@ function useWorkbenchGraphTick(wb) {
2301
2344
  function useWorkbenchGraphUiTick(wb) {
2302
2345
  const [tick, setTick] = React.useState(0);
2303
2346
  React.useEffect(() => {
2304
- const bump = () => setTick((t) => t + 1);
2305
- const off = wb.on("graphUiChanged", bump);
2347
+ const off = wb.on("graphUiChanged", (evt) => {
2348
+ if (evt.change?.type === "viewport")
2349
+ return;
2350
+ setTick((t) => t + 1);
2351
+ });
2306
2352
  return () => off();
2307
2353
  }, [wb]);
2308
2354
  return tick;
@@ -2426,7 +2472,7 @@ function useQueryParamString(key, defaultValue) {
2426
2472
  return [val, set];
2427
2473
  }
2428
2474
 
2429
- function toReactFlow(def, positions, registry, opts) {
2475
+ function toReactFlow(def, positions, sizes, registry, opts) {
2430
2476
  const EDGE_STYLE_MISSING = { stroke: "#f59e0b", strokeWidth: 2 }; // amber-500
2431
2477
  const EDGE_STYLE_ERROR = { stroke: "#ef4444", strokeWidth: 2 };
2432
2478
  const EDGE_STYLE_RUNNING = { stroke: "#3b82f6" };
@@ -2470,45 +2516,41 @@ function toReactFlow(def, positions, registry, opts) {
2470
2516
  const createHandleBoundsFn = opts.ui?.getCreateHandleBounds() ?? createHandleBounds;
2471
2517
  const createHandleLayoutFn = opts.ui?.getCreateHandleLayout() ?? createHandleLayout;
2472
2518
  const estimateNodeSizeFn = opts.ui?.getEstimateNodeSize() ?? estimateNodeSize;
2473
- const nodes = def.nodes.map((n) => {
2474
- const { inputs: inputSource, outputs: outputSource } = computeEffectiveHandles(n, registry);
2475
- const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
2476
- // If layoutNode is overridden, use it directly; otherwise use default with internal overrides
2477
- const geom = layoutNodeOverride
2519
+ const computeLayout = (node, overrides) => {
2520
+ return layoutNodeOverride
2478
2521
  ? layoutNodeOverride({
2479
- node: n,
2522
+ node,
2480
2523
  registry,
2481
2524
  showValues: opts.showValues,
2482
- overrides: overrideSize,
2525
+ overrides,
2483
2526
  })
2484
2527
  : layoutNode({
2485
- node: n,
2528
+ node,
2486
2529
  registry,
2487
2530
  showValues: opts.showValues,
2488
- overrides: overrideSize,
2531
+ overrides,
2489
2532
  }, {
2490
2533
  estimateNodeSize: estimateNodeSizeFn,
2491
2534
  createHandleBounds: createHandleBoundsFn,
2492
2535
  createHandleLayout: createHandleLayoutFn,
2493
2536
  });
2494
- const inputHandles = geom.inputOrder.map((id) => ({
2495
- id,
2496
- typeId: sparkGraph.getInputTypeId(inputSource, id),
2497
- }));
2498
- const outputHandles = geom.outputOrder.map((id) => ({
2499
- id,
2500
- typeId: formatDeclaredTypeSignature(outputSource[id]),
2501
- }));
2502
- nodeHandleMap[n.nodeId] = {
2503
- inputs: new Set(inputHandles.map((h) => h.id)),
2504
- outputs: new Set(outputHandles.map((h) => h.id)),
2505
- };
2506
- // Append placeholder entries for any missing handles (below valid ones)
2537
+ };
2538
+ const calculateDimensionsWithMissingHandles = (geom, extraInputs, extraOutputs) => {
2507
2539
  const baseLeftCount = geom.inputOrder.length;
2508
2540
  const baseRightCount = geom.outputOrder.length;
2509
- const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
2510
- const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
2511
- 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) => ({
2512
2554
  ...createHandleLayoutFn({
2513
2555
  id,
2514
2556
  type: "target",
@@ -2517,7 +2559,7 @@ function toReactFlow(def, positions, registry, opts) {
2517
2559
  }),
2518
2560
  missing: true,
2519
2561
  }));
2520
- const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
2562
+ const right = extraOutputs.map((id, i) => ({
2521
2563
  ...createHandleLayoutFn({
2522
2564
  id,
2523
2565
  type: "source",
@@ -2526,36 +2568,58 @@ function toReactFlow(def, positions, registry, opts) {
2526
2568
  }),
2527
2569
  missing: true,
2528
2570
  }));
2529
- const handleLayout = [
2530
- ...geom.handleLayout,
2531
- ...extraHandleLayoutLeft,
2532
- ...extraHandleLayoutRight,
2533
- ];
2534
- // Precompute handle bounds (including missing) so edges can render immediately
2535
- 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({
2536
2575
  id,
2537
2576
  type: "target",
2538
2577
  position: react.Position.Left,
2539
2578
  rowIndex: baseLeftCount + i,
2540
- nodeWidth: geom.width,
2579
+ nodeWidth,
2541
2580
  }));
2542
- const missingBoundsRight = extraOutputs.map((id, i) => createHandleBoundsFn({
2581
+ const right = extraOutputs.map((id, i) => createHandleBoundsFn({
2543
2582
  id,
2544
2583
  type: "source",
2545
2584
  position: react.Position.Right,
2546
2585
  rowIndex: baseRightCount + i,
2547
- nodeWidth: geom.width,
2586
+ nodeWidth,
2548
2587
  }));
2549
- const handles = [
2550
- ...geom.handles,
2551
- ...missingBoundsLeft,
2552
- ...missingBoundsRight,
2553
- ];
2554
- // Adjust node height to accommodate missing handle rows
2555
- const baseRows = Math.max(baseLeftCount, baseRightCount);
2556
- const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
2557
- const initialWidth = geom.width;
2558
- 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];
2559
2623
  return {
2560
2624
  id: n.nodeId,
2561
2625
  data: {
@@ -2569,8 +2633,10 @@ function toReactFlow(def, positions, registry, opts) {
2569
2633
  ])),
2570
2634
  handleLayout,
2571
2635
  showValues: opts.showValues,
2572
- renderWidth: initialWidth,
2573
- renderHeight: initialHeight,
2636
+ renderWidth,
2637
+ renderHeight,
2638
+ initialWidth: initialDims.width,
2639
+ initialHeight: initialDims.height,
2574
2640
  inputValues: opts.inputs?.[n.nodeId],
2575
2641
  inputDefaults: opts.inputDefaults?.[n.nodeId],
2576
2642
  outputValues: opts.outputs?.[n.nodeId],
@@ -2588,11 +2654,13 @@ function toReactFlow(def, positions, registry, opts) {
2588
2654
  selected: opts.selectedNodeIds
2589
2655
  ? opts.selectedNodeIds.has(n.nodeId)
2590
2656
  : undefined,
2591
- initialWidth,
2592
- initialHeight,
2657
+ measured: {
2658
+ width: renderWidth,
2659
+ height: renderHeight,
2660
+ },
2593
2661
  handles,
2594
- width: initialWidth,
2595
- height: initialHeight,
2662
+ width: renderWidth,
2663
+ height: renderHeight,
2596
2664
  };
2597
2665
  });
2598
2666
  const edges = def.edges.map((e) => {
@@ -2799,22 +2867,30 @@ async function upload(parsed, wb, runner) {
2799
2867
  /**
2800
2868
  * Merge UI state from source into target, remapping node IDs using nodeIdMap.
2801
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.
2802
2871
  */
2803
- function mergeUIState(targetUI, sourceUI, nodeIdMap) {
2872
+ function mergeUIState(targetUI, sourceUI, nodeIdMap, anchorPos, sourceDef) {
2804
2873
  const result = {
2805
2874
  ...targetUI,
2806
2875
  };
2807
2876
  if (!sourceUI)
2808
2877
  return result;
2809
- // Merge positions (already handled by mergeSnapshotData, but included for completeness)
2878
+ // Merge positions with optional offset
2810
2879
  if (sourceUI.positions) {
2811
- result.positions = {
2812
- ...(targetUI?.positions || {}),
2813
- ...Object.fromEntries(Object.entries(sourceUI.positions).map(([oldId, pos]) => [
2814
- nodeIdMap[oldId] || oldId,
2815
- pos,
2816
- ])),
2817
- };
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
+ }
2818
2894
  }
2819
2895
  // Merge selection: remap node IDs and edge IDs
2820
2896
  if (sourceUI.selection) {
@@ -2837,6 +2913,15 @@ function mergeUIState(targetUI, sourceUI, nodeIdMap) {
2837
2913
  ])),
2838
2914
  };
2839
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
+ }
2840
2925
  return result;
2841
2926
  }
2842
2927
 
@@ -3094,7 +3179,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3094
3179
  (type === "graphChanged" || type === "graphUiChanged")) {
3095
3180
  const changeType = payload
3096
3181
  .change?.type;
3097
- if (changeType === "moveNode" || changeType === "moveNodes")
3182
+ if (changeType === "moveNode" ||
3183
+ changeType === "moveNodes" ||
3184
+ changeType === "resizeNodes")
3098
3185
  return prev;
3099
3186
  }
3100
3187
  const nextNo = prev.length > 0 ? (prev[0]?.no ?? 0) + 1 : 1;
@@ -3503,6 +3590,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
3503
3590
  else if (changeType === "moveNodes") {
3504
3591
  reason = "move-nodes";
3505
3592
  }
3593
+ else if (changeType === "resizeNodes") {
3594
+ reason = "resize-nodes";
3595
+ }
3506
3596
  else if (changeType === "selection") {
3507
3597
  reason = "selection";
3508
3598
  }
@@ -4260,103 +4350,6 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
4260
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 }) }))] }));
4261
4351
  }
4262
4352
 
4263
- function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
4264
- 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: {
4265
- top: (y ?? 0) - 8,
4266
- ...(kind === "input"
4267
- ? { right: "50%" }
4268
- : { left: "50%", textAlign: "right" }),
4269
- whiteSpace: "nowrap",
4270
- overflow: "hidden",
4271
- textOverflow: "ellipsis",
4272
- }, children: renderLabel({ kind, id }) }))] }));
4273
- }
4274
- function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
4275
- const layout = data.handleLayout ?? [];
4276
- const byId = React.useMemo(() => {
4277
- const m = new Map();
4278
- for (const h of layout) {
4279
- // Prefer namespaced key to disambiguate inputs/outputs that share id
4280
- m.set(`${h.type}:${h.id}`, {
4281
- position: h.position,
4282
- y: h.y,
4283
- type: h.type,
4284
- missing: h.missing,
4285
- });
4286
- // Back-compat: also store by id-only if not already set
4287
- if (!m.has(h.id))
4288
- m.set(h.id, {
4289
- position: h.position,
4290
- y: h.y,
4291
- type: h.type,
4292
- missing: h.missing,
4293
- });
4294
- }
4295
- return m;
4296
- }, [layout]);
4297
- const inputIds = React.useMemo(() => new Set((data.inputHandles ?? []).map((h) => h.id)), [data.inputHandles]);
4298
- const outputIds = React.useMemo(() => new Set((data.outputHandles ?? []).map((h) => h.id)), [data.outputHandles]);
4299
- const missingInputs = React.useMemo(() => (layout || []).filter((h) => h.type === "target" && (!inputIds.has(h.id) || h.missing)), [layout, inputIds]);
4300
- const missingOutputs = React.useMemo(() => (layout || []).filter((h) => h.type === "source" && (!outputIds.has(h.id) || h.missing)), [layout, outputIds]);
4301
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(data.inputHandles ?? []).map((h) => {
4302
- const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
4303
- const position = placed?.position ?? react.Position.Left;
4304
- const y = placed?.y;
4305
- const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
4306
- 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));
4307
- }), missingInputs.map((h) => {
4308
- const key = `missing-input:${h.id}`;
4309
- const position = h.position ?? react.Position.Left;
4310
- const y = h.y;
4311
- const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 wb-nodrag wb-nowheel";
4312
- 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));
4313
- }), (data.outputHandles ?? []).map((h) => {
4314
- const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
4315
- const position = placed?.position ?? react.Position.Right;
4316
- const y = placed?.y;
4317
- const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
4318
- 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));
4319
- }), missingOutputs.map((h) => {
4320
- const key = `missing-output:${h.id}`;
4321
- const position = h.position ?? react.Position.Right;
4322
- const y = h.y;
4323
- const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
4324
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
4325
- })] }));
4326
- }
4327
-
4328
- const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable, }) {
4329
- const updateNodeInternals = react.useUpdateNodeInternals();
4330
- const { typeId, showValues } = data;
4331
- const inputEntries = data.inputHandles ?? [];
4332
- const outputEntries = data.outputHandles ?? [];
4333
- React.useEffect(() => {
4334
- updateNodeInternals(id);
4335
- }, [
4336
- id,
4337
- inputEntries.length,
4338
- outputEntries.length,
4339
- showValues,
4340
- updateNodeInternals,
4341
- ]);
4342
- const status = data.status ?? { activeRuns: 0 };
4343
- const validation = data.validation ?? {
4344
- inputs: [],
4345
- outputs: [],
4346
- issues: [],
4347
- };
4348
- const containerBorder = getNodeBorderClassNames({
4349
- selected,
4350
- status,
4351
- validation,
4352
- });
4353
- return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/50 !dark:bg-stone-900", containerBorder), style: {
4354
- position: "relative",
4355
- minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
4356
- minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
4357
- }, children: [jsxRuntime.jsx(DefaultNodeHeader, { id: id, typeId: typeId, validation: validation, showId: data.showValues }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
4358
- });
4359
- DefaultNode.displayName = "DefaultNode";
4360
4353
  function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInvalidate, }) {
4361
4354
  const ctx = useWorkbenchContext();
4362
4355
  const [isEditing, setIsEditing] = React.useState(false);
@@ -4440,6 +4433,72 @@ function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInv
4440
4433
  .map((v) => `${v.code}: ${v.message}`)
4441
4434
  .join("; ") })), showId && jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
4442
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
+
4443
4502
  function DefaultNodeContent({ data, isConnectable, }) {
4444
4503
  const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
4445
4504
  const inputEntries = data.inputHandles ?? [];
@@ -4492,6 +4551,24 @@ function DefaultNodeContent({ data, isConnectable, }) {
4492
4551
  } })] }));
4493
4552
  }
4494
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
+
4495
4572
  // Helper to format shortcut for current platform
4496
4573
  function formatShortcut(shortcut) {
4497
4574
  const isMac = typeof navigator !== "undefined" &&
@@ -4772,8 +4849,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4772
4849
  position: n.position,
4773
4850
  type: n.type,
4774
4851
  selected: n.selected,
4775
- initialWidth: n.initialWidth,
4776
- initialHeight: n.initialHeight,
4852
+ measured: n.measured,
4777
4853
  data: n.data && {
4778
4854
  typeId: n.data.typeId,
4779
4855
  inputHandles: n.data.inputHandles,
@@ -4807,11 +4883,18 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4807
4883
  React.useImperativeHandle(ref, () => ({
4808
4884
  fitView: () => {
4809
4885
  try {
4810
- rfInstanceRef.current?.fitView({ padding: 0.2 });
4886
+ rfInstanceRef.current?.fitView();
4887
+ }
4888
+ catch (err) {
4889
+ console.warn("Failed to fit view", err);
4811
4890
  }
4812
- catch { }
4813
4891
  },
4814
- }));
4892
+ setViewport: (viewport) => {
4893
+ if (rfInstanceRef.current) {
4894
+ rfInstanceRef.current.setViewport(lod.clone(viewport));
4895
+ }
4896
+ },
4897
+ }), []);
4815
4898
  const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb);
4816
4899
  const ui = wb.getUI();
4817
4900
  const { nodeTypes, resolveNodeType } = React.useMemo(() => {
@@ -4857,7 +4940,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
4857
4940
  inputsWithDefaults[n.nodeId] = merged;
4858
4941
  }
4859
4942
  }
4860
- const out = toReactFlow(wb.def, wb.getPositions(), registry, {
4943
+ const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), registry, {
4861
4944
  showValues,
4862
4945
  inputs: inputsWithDefaults,
4863
4946
  inputDefaults: inputDefaultsMap,
@@ -5329,55 +5412,53 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
5329
5412
  showToast,
5330
5413
  ]);
5331
5414
  // Get custom renderers from UI extension registry (reactive to uiVersion changes)
5332
- const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
5415
+ const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, SelectionContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
5333
5416
  return {
5334
5417
  BackgroundRenderer: ui.getBackgroundRenderer(),
5335
5418
  MinimapRenderer: ui.getMinimapRenderer(),
5336
5419
  ControlsRenderer: ui.getControlsRenderer(),
5337
5420
  DefaultContextMenuRenderer: ui.getDefaultContextMenuRenderer(),
5338
5421
  NodeContextMenuRenderer: ui.getNodeContextMenuRenderer(),
5422
+ SelectionContextMenuRenderer: ui.getSelectionContextMenuRenderer(),
5339
5423
  connectionLineRenderer: ui.getConnectionLineRenderer(),
5340
5424
  };
5341
5425
  }, [ui, uiVersion]);
5342
5426
  const onMoveEnd = React.useCallback(() => {
5343
5427
  if (rfInstanceRef.current) {
5344
5428
  const viewport = rfInstanceRef.current.getViewport();
5345
- const viewportData = lod.pick(viewport, ["x", "y", "zoom"]);
5429
+ const viewportData = lod.clone(viewport);
5346
5430
  wb.setViewport(viewportData);
5347
5431
  }
5348
5432
  }, [wb]);
5349
- const viewportRef = React.useRef(null);
5433
+ // Sync viewport when workbench fires graphUiChanged with viewport event
5350
5434
  React.useEffect(() => {
5351
- if (!rfInstanceRef.current)
5352
- return;
5353
- const currentViewport = wb.getViewport();
5354
- if (currentViewport &&
5355
- (!viewportRef.current ||
5356
- viewportRef.current.x !== currentViewport.x ||
5357
- viewportRef.current.y !== currentViewport.y ||
5358
- viewportRef.current.zoom !== currentViewport.zoom)) {
5359
- viewportRef.current = currentViewport;
5360
- rfInstanceRef.current.setViewport({
5361
- x: currentViewport.x,
5362
- y: currentViewport.y,
5363
- zoom: currentViewport.zoom,
5364
- });
5365
- }
5366
- });
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]);
5367
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) => {
5368
5448
  rfInstanceRef.current = inst;
5369
5449
  const savedViewport = wb.getViewport();
5370
5450
  if (savedViewport) {
5371
- viewportRef.current = savedViewport;
5372
- inst.setViewport(lod.pick(savedViewport, ["x", "y", "zoom"]));
5451
+ inst.setViewport(lod.clone(savedViewport));
5373
5452
  }
5374
- }, 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
5375
5454
  ? { enableKeyboardShortcuts, keyboardShortcuts }
5376
5455
  : {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })), !!nodeAtMenu &&
5377
5456
  nodeContextMenuHandlers &&
5378
5457
  (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs, ...(enableKeyboardShortcuts !== false
5379
5458
  ? { enableKeyboardShortcuts, keyboardShortcuts }
5380
- : {}) })) : (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))] }));
5381
5462
  });
5382
5463
 
5383
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, }) {