@bian-womp/spark-workbench 0.2.55 → 0.2.57

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.
package/lib/esm/index.js CHANGED
@@ -30,13 +30,6 @@ class DefaultUIExtensionRegistry {
30
30
  }
31
31
  return result;
32
32
  }
33
- registerIconProvider(provider) {
34
- this.iconProvider = provider;
35
- return this;
36
- }
37
- getIconProvider() {
38
- return this.iconProvider;
39
- }
40
33
  // React Flow renderers
41
34
  registerConnectionLineRenderer(renderer) {
42
35
  this.connectionLineRenderer = renderer;
@@ -80,6 +73,35 @@ class DefaultUIExtensionRegistry {
80
73
  getNodeContextMenuRenderer() {
81
74
  return this.nodeContextMenuRenderer;
82
75
  }
76
+ // Layout function overrides
77
+ registerEstimateNodeSize(override) {
78
+ this.estimateNodeSizeOverride = override;
79
+ return this;
80
+ }
81
+ getEstimateNodeSize() {
82
+ return this.estimateNodeSizeOverride;
83
+ }
84
+ registerCreateHandleBounds(override) {
85
+ this.createHandleBoundsOverride = override;
86
+ return this;
87
+ }
88
+ getCreateHandleBounds() {
89
+ return this.createHandleBoundsOverride;
90
+ }
91
+ registerCreateHandleLayout(override) {
92
+ this.createHandleLayoutOverride = override;
93
+ return this;
94
+ }
95
+ getCreateHandleLayout() {
96
+ return this.createHandleLayoutOverride;
97
+ }
98
+ registerLayoutNode(override) {
99
+ this.layoutNodeOverride = override;
100
+ return this;
101
+ }
102
+ getLayoutNode() {
103
+ return this.layoutNodeOverride;
104
+ }
83
105
  }
84
106
 
85
107
  class AbstractWorkbench {
@@ -1412,7 +1434,8 @@ function summarizeDeep(value) {
1412
1434
 
1413
1435
  // Shared UI constants for node layout to keep mapping and rendering in sync
1414
1436
  const NODE_HEADER_HEIGHT_PX = 24;
1415
- const NODE_ROW_HEIGHT_PX = 22;
1437
+ const NODE_ROW_HEIGHT_PX = 18;
1438
+ const HANDLE_SIZE_PX = 12;
1416
1439
 
1417
1440
  function computeEffectiveHandles(node, registry) {
1418
1441
  const desc = registry.nodes.get(node.typeId);
@@ -1439,53 +1462,105 @@ function estimateNodeSize(args) {
1439
1462
  const baseWidth = showValues ? 320 : 240;
1440
1463
  const width = overrides?.width ?? baseWidth;
1441
1464
  const height = overrides?.height ?? NODE_HEADER_HEIGHT_PX + rows * NODE_ROW_HEIGHT_PX;
1442
- return { width, height, inputsCount, outputsCount, rowCount: rows };
1465
+ return { width, height };
1443
1466
  }
1444
- function layoutNode(args) {
1445
- const { node, registry, showValues, overrides } = args;
1467
+ /**
1468
+ * Calculate the Y position for handle layout (center of row).
1469
+ * Used for positioning handles in React Flow.
1470
+ */
1471
+ function getHandleLayoutY(rowIndex) {
1472
+ return (NODE_HEADER_HEIGHT_PX +
1473
+ rowIndex * NODE_ROW_HEIGHT_PX +
1474
+ NODE_ROW_HEIGHT_PX / 2);
1475
+ }
1476
+ /**
1477
+ * Calculate the Y position for handle bounds (top + centering offset).
1478
+ * Used for hit-testing and edge routing.
1479
+ */
1480
+ function getHandleBoundsY(rowIndex) {
1481
+ return (NODE_HEADER_HEIGHT_PX +
1482
+ rowIndex * NODE_ROW_HEIGHT_PX +
1483
+ (NODE_ROW_HEIGHT_PX - HANDLE_SIZE_PX) / 2 +
1484
+ 1);
1485
+ }
1486
+ /**
1487
+ * Calculate the X position for handle bounds based on position and node width.
1488
+ */
1489
+ function getHandleBoundsX(position, nodeWidth) {
1490
+ if (position === Position.Left) {
1491
+ return -HANDLE_SIZE_PX / 2 + 1;
1492
+ }
1493
+ else {
1494
+ return nodeWidth - HANDLE_SIZE_PX / 2 - 1;
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Create handle bounds object for hit-testing/edge routing.
1499
+ */
1500
+ function createHandleBounds(args) {
1501
+ return {
1502
+ id: args.id,
1503
+ type: args.type,
1504
+ position: args.position,
1505
+ x: getHandleBoundsX(args.position, args.nodeWidth),
1506
+ y: getHandleBoundsY(args.rowIndex),
1507
+ width: HANDLE_SIZE_PX,
1508
+ height: HANDLE_SIZE_PX,
1509
+ };
1510
+ }
1511
+ /**
1512
+ * Create handle layout object for React Flow rendering.
1513
+ */
1514
+ function createHandleLayout(args) {
1515
+ return {
1516
+ id: args.id,
1517
+ type: args.type,
1518
+ position: args.position,
1519
+ y: getHandleLayoutY(args.rowIndex),
1520
+ };
1521
+ }
1522
+ function layoutNode(args, overrides) {
1523
+ const { node, registry, showValues, overrides: sizeOverrides } = args;
1446
1524
  const { inputs, outputs } = computeEffectiveHandles(node, registry);
1447
1525
  const inputOrder = Object.keys(inputs).filter((k) => !isInputPrivate(inputs, k));
1448
1526
  const outputOrder = Object.keys(outputs);
1449
- const { width, height } = estimateNodeSize({
1527
+ const estimateNodeSizeFn = overrides?.estimateNodeSize ?? estimateNodeSize;
1528
+ const createHandleBoundsFn = overrides?.createHandleBounds ?? createHandleBounds;
1529
+ const createHandleLayoutFn = overrides?.createHandleLayout ?? createHandleLayout;
1530
+ const { width, height } = estimateNodeSizeFn({
1450
1531
  node,
1451
1532
  registry,
1452
1533
  showValues,
1453
- overrides,
1534
+ overrides: sizeOverrides,
1454
1535
  });
1455
- const HEADER = NODE_HEADER_HEIGHT_PX;
1456
- const ROW = NODE_ROW_HEIGHT_PX;
1457
1536
  const handles = [
1458
- ...inputOrder.map((id, i) => ({
1537
+ ...inputOrder.map((id, i) => createHandleBoundsFn({
1459
1538
  id,
1460
1539
  type: "target",
1461
1540
  position: Position.Left,
1462
- x: 0,
1463
- y: HEADER + i * ROW,
1464
- width: 1,
1465
- height: ROW + 2,
1541
+ rowIndex: i,
1542
+ nodeWidth: width,
1466
1543
  })),
1467
- ...outputOrder.map((id, i) => ({
1544
+ ...outputOrder.map((id, i) => createHandleBoundsFn({
1468
1545
  id,
1469
1546
  type: "source",
1470
1547
  position: Position.Right,
1471
- x: width - 1,
1472
- y: HEADER + i * ROW,
1473
- width: 1,
1474
- height: ROW + 2,
1548
+ rowIndex: i,
1549
+ nodeWidth: width,
1475
1550
  })),
1476
1551
  ];
1477
1552
  const handleLayout = [
1478
- ...inputOrder.map((id, i) => ({
1553
+ ...inputOrder.map((id, i) => createHandleLayoutFn({
1479
1554
  id,
1480
1555
  type: "target",
1481
1556
  position: Position.Left,
1482
- y: HEADER + i * ROW + ROW / 2,
1557
+ rowIndex: i,
1483
1558
  })),
1484
- ...outputOrder.map((id, i) => ({
1559
+ ...outputOrder.map((id, i) => createHandleLayoutFn({
1485
1560
  id,
1486
1561
  type: "source",
1487
1562
  position: Position.Right,
1488
- y: HEADER + i * ROW + ROW / 2,
1563
+ rowIndex: i,
1489
1564
  })),
1490
1565
  ];
1491
1566
  return { width, height, inputOrder, outputOrder, handles, handleLayout };
@@ -1774,15 +1849,32 @@ function toReactFlow(def, positions, registry, opts) {
1774
1849
  // This map is still used later for certain checks; align with valid handles
1775
1850
  const nodeHandleMap = {};
1776
1851
  Object.assign(nodeHandleMap, validHandleMap);
1852
+ // Get layout function overrides from UI registry
1853
+ const layoutNodeOverride = opts.ui?.getLayoutNode() ?? layoutNode;
1854
+ const createHandleBoundsFn = opts.ui?.getCreateHandleBounds() ?? createHandleBounds;
1855
+ const createHandleLayoutFn = opts.ui?.getCreateHandleLayout() ?? createHandleLayout;
1856
+ const estimateNodeSizeFn = opts.ui?.getEstimateNodeSize() ?? estimateNodeSize;
1777
1857
  const nodes = def.nodes.map((n) => {
1778
1858
  const { inputs: inputSource, outputs: outputSource } = computeEffectiveHandles(n, registry);
1779
1859
  const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
1780
- const geom = layoutNode({
1781
- node: n,
1782
- registry,
1783
- showValues: opts.showValues,
1784
- overrides: overrideSize,
1785
- });
1860
+ // If layoutNode is overridden, use it directly; otherwise use default with internal overrides
1861
+ const geom = layoutNodeOverride
1862
+ ? layoutNodeOverride({
1863
+ node: n,
1864
+ registry,
1865
+ showValues: opts.showValues,
1866
+ overrides: overrideSize,
1867
+ })
1868
+ : layoutNode({
1869
+ node: n,
1870
+ registry,
1871
+ showValues: opts.showValues,
1872
+ overrides: overrideSize,
1873
+ }, {
1874
+ estimateNodeSize: estimateNodeSizeFn,
1875
+ createHandleBounds: createHandleBoundsFn,
1876
+ createHandleLayout: createHandleLayoutFn,
1877
+ });
1786
1878
  const inputHandles = geom.inputOrder.map((id) => ({
1787
1879
  id,
1788
1880
  typeId: getInputTypeId(inputSource, id),
@@ -1800,20 +1892,22 @@ function toReactFlow(def, positions, registry, opts) {
1800
1892
  const baseRightCount = geom.outputOrder.length;
1801
1893
  const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
1802
1894
  const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
1803
- const HEADER = NODE_HEADER_HEIGHT_PX;
1804
- const ROW = NODE_ROW_HEIGHT_PX;
1805
1895
  const extraHandleLayoutLeft = extraInputs.map((id, i) => ({
1806
- id,
1807
- type: "target",
1808
- position: Position.Left,
1809
- y: HEADER + (baseLeftCount + i) * ROW + ROW / 2,
1896
+ ...createHandleLayoutFn({
1897
+ id,
1898
+ type: "target",
1899
+ position: Position.Left,
1900
+ rowIndex: baseLeftCount + i,
1901
+ }),
1810
1902
  missing: true,
1811
1903
  }));
1812
1904
  const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
1813
- id,
1814
- type: "source",
1815
- position: Position.Right,
1816
- y: HEADER + (baseRightCount + i) * ROW + ROW / 2,
1905
+ ...createHandleLayoutFn({
1906
+ id,
1907
+ type: "source",
1908
+ position: Position.Right,
1909
+ rowIndex: baseRightCount + i,
1910
+ }),
1817
1911
  missing: true,
1818
1912
  }));
1819
1913
  const handleLayout = [
@@ -1822,23 +1916,19 @@ function toReactFlow(def, positions, registry, opts) {
1822
1916
  ...extraHandleLayoutRight,
1823
1917
  ];
1824
1918
  // Precompute handle bounds (including missing) so edges can render immediately
1825
- const missingBoundsLeft = extraInputs.map((id, i) => ({
1919
+ const missingBoundsLeft = extraInputs.map((id, i) => createHandleBoundsFn({
1826
1920
  id,
1827
1921
  type: "target",
1828
1922
  position: Position.Left,
1829
- x: 0,
1830
- y: HEADER + (baseLeftCount + i) * ROW,
1831
- width: 1,
1832
- height: ROW + 2,
1923
+ rowIndex: baseLeftCount + i,
1924
+ nodeWidth: geom.width,
1833
1925
  }));
1834
- const missingBoundsRight = extraOutputs.map((id, i) => ({
1926
+ const missingBoundsRight = extraOutputs.map((id, i) => createHandleBoundsFn({
1835
1927
  id,
1836
1928
  type: "source",
1837
1929
  position: Position.Right,
1838
- x: geom.width - 1,
1839
- y: HEADER + (baseRightCount + i) * ROW,
1840
- width: 1,
1841
- height: ROW + 2,
1930
+ rowIndex: baseRightCount + i,
1931
+ nodeWidth: geom.width,
1842
1932
  }));
1843
1933
  const handles = [
1844
1934
  ...geom.handles,
@@ -1849,7 +1939,7 @@ function toReactFlow(def, positions, registry, opts) {
1849
1939
  const baseRows = Math.max(baseLeftCount, baseRightCount);
1850
1940
  const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
1851
1941
  const initialWidth = geom.width;
1852
- const initialHeight = geom.height + Math.max(0, newRows - baseRows) * ROW;
1942
+ const initialHeight = geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX;
1853
1943
  return {
1854
1944
  id: n.nodeId,
1855
1945
  data: {
@@ -1985,7 +2075,7 @@ function getHandleClassName(args) {
1985
2075
  else {
1986
2076
  borderColor = "!border-gray-500 dark:!border-gray-400";
1987
2077
  }
1988
- return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
2078
+ return cx("!w-3 !h-3 !bg-white/50 !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
1989
2079
  }
1990
2080
 
1991
2081
  function generateTimestamp() {
@@ -3057,7 +3147,7 @@ function NodeHandleItem({ kind, id, type, position, y, isConnectable, className,
3057
3147
  textOverflow: "ellipsis",
3058
3148
  }, children: renderLabel({ kind, id }) }))] }));
3059
3149
  }
3060
- function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray-600", outputClassName = "!w-2 !h-2 !bg-gray-600", getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
3150
+ function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
3061
3151
  const layout = data.handleLayout ?? [];
3062
3152
  const byId = React.useMemo(() => {
3063
3153
  const m = new Map();
@@ -3088,28 +3178,26 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
3088
3178
  const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
3089
3179
  const position = placed?.position ?? Position.Left;
3090
3180
  const y = placed?.y;
3091
- const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ??
3092
- inputClassName;
3181
+ const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
3093
3182
  return (jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
3094
3183
  }), missingInputs.map((h) => {
3095
3184
  const key = `missing-input:${h.id}`;
3096
3185
  const position = h.position ?? Position.Left;
3097
3186
  const y = h.y;
3098
3187
  const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500";
3099
- return (jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
3188
+ 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));
3100
3189
  }), (data.outputHandles ?? []).map((h) => {
3101
3190
  const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
3102
3191
  const position = placed?.position ?? Position.Right;
3103
3192
  const y = placed?.y;
3104
- const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ??
3105
- outputClassName;
3106
- return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
3193
+ const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
3194
+ return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
3107
3195
  }), missingOutputs.map((h) => {
3108
3196
  const key = `missing-output:${h.id}`;
3109
3197
  const position = h.position ?? Position.Right;
3110
3198
  const y = h.y;
3111
3199
  const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
3112
- return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
3200
+ return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
3113
3201
  })] }));
3114
3202
  }
3115
3203
 
@@ -3138,7 +3226,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
3138
3226
  status,
3139
3227
  validation,
3140
3228
  });
3141
- return (jsxs("div", { className: cx("rounded-lg bg-white/70 !dark:bg-stone-900", containerBorder), style: {
3229
+ return (jsxs("div", { className: cx("rounded-lg bg-white/50 !dark:bg-stone-900", containerBorder), style: {
3142
3230
  position: "relative",
3143
3231
  minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
3144
3232
  minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
@@ -3517,6 +3605,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3517
3605
  const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, } = useWorkbenchContext();
3518
3606
  const nodeValidation = validationByNode;
3519
3607
  const edgeValidation = validationByEdge.errors;
3608
+ const [registryVersion, setRegistryVersion] = useState(0);
3520
3609
  // Keep stable references for nodes/edges to avoid unnecessary updates
3521
3610
  const prevNodesRef = useRef([]);
3522
3611
  const prevEdgesRef = useRef([]);
@@ -3642,6 +3731,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3642
3731
  selectedNodeIds: new Set(sel.nodes),
3643
3732
  selectedEdgeIds: new Set(sel.edges),
3644
3733
  getDefaultNodeSize,
3734
+ ui,
3645
3735
  });
3646
3736
  // Retain references for unchanged items
3647
3737
  const stableNodes = retainStabilityById(prevNodesRef.current, out.nodes, isSameNode);
@@ -3787,7 +3877,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3787
3877
  const onCloseNodeMenu = useCallback(() => {
3788
3878
  setNodeMenuOpen(false);
3789
3879
  }, []);
3790
- const nodeIds = useMemo(() => Array.from(registry.nodes.keys()), [registry]);
3880
+ useEffect(() => {
3881
+ const off = runner.on("registry", () => {
3882
+ setRegistryVersion((v) => v + 1);
3883
+ });
3884
+ return () => off();
3885
+ }, [runner]);
3886
+ const nodeIds = useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
3791
3887
  const defaultContextMenuHandlers = useMemo(() => ({
3792
3888
  onAddNode: addNodeAt,
3793
3889
  onClose: onCloseMenu,
@@ -4390,5 +4486,5 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
4390
4486
  return (jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
4391
4487
  }
4392
4488
 
4393
- export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, computeEffectiveHandles, countVisibleHandles, download, estimateNodeSize, formatDataUrlAsLabel, formatDeclaredTypeSignature, getHandleClassName, getNodeBorderClassNames, layoutNode, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
4489
+ export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, computeEffectiveHandles, countVisibleHandles, createHandleBounds, createHandleLayout, download, estimateNodeSize, formatDataUrlAsLabel, formatDeclaredTypeSignature, getHandleBoundsX, getHandleBoundsY, getHandleClassName, getHandleLayoutY, getNodeBorderClassNames, layoutNode, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
4394
4490
  //# sourceMappingURL=index.js.map