@bian-womp/spark-workbench 0.2.55 → 0.2.56

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/cjs/index.cjs CHANGED
@@ -1414,7 +1414,8 @@ function summarizeDeep(value) {
1414
1414
 
1415
1415
  // Shared UI constants for node layout to keep mapping and rendering in sync
1416
1416
  const NODE_HEADER_HEIGHT_PX = 24;
1417
- const NODE_ROW_HEIGHT_PX = 22;
1417
+ const NODE_ROW_HEIGHT_PX = 18;
1418
+ const HANDLE_SIZE_PX = 12;
1418
1419
 
1419
1420
  function computeEffectiveHandles(node, registry) {
1420
1421
  const desc = registry.nodes.get(node.typeId);
@@ -1443,6 +1444,61 @@ function estimateNodeSize(args) {
1443
1444
  const height = overrides?.height ?? NODE_HEADER_HEIGHT_PX + rows * NODE_ROW_HEIGHT_PX;
1444
1445
  return { width, height, inputsCount, outputsCount, rowCount: rows };
1445
1446
  }
1447
+ /**
1448
+ * Calculate the Y position for handle layout (center of row).
1449
+ * Used for positioning handles in React Flow.
1450
+ */
1451
+ function getHandleLayoutY(rowIndex) {
1452
+ return (NODE_HEADER_HEIGHT_PX +
1453
+ rowIndex * NODE_ROW_HEIGHT_PX +
1454
+ NODE_ROW_HEIGHT_PX / 2);
1455
+ }
1456
+ /**
1457
+ * Calculate the Y position for handle bounds (top + centering offset).
1458
+ * Used for hit-testing and edge routing.
1459
+ */
1460
+ function getHandleBoundsY(rowIndex) {
1461
+ return (NODE_HEADER_HEIGHT_PX +
1462
+ rowIndex * NODE_ROW_HEIGHT_PX +
1463
+ (NODE_ROW_HEIGHT_PX - HANDLE_SIZE_PX) / 2 +
1464
+ 1);
1465
+ }
1466
+ /**
1467
+ * Calculate the X position for handle bounds based on position and node width.
1468
+ */
1469
+ function getHandleBoundsX(position, nodeWidth) {
1470
+ if (position === react.Position.Left) {
1471
+ return -HANDLE_SIZE_PX / 2 + 1;
1472
+ }
1473
+ else {
1474
+ return nodeWidth - HANDLE_SIZE_PX / 2 - 1;
1475
+ }
1476
+ }
1477
+ /**
1478
+ * Create handle bounds object for hit-testing/edge routing.
1479
+ */
1480
+ function createHandleBounds(args) {
1481
+ return {
1482
+ id: args.id,
1483
+ type: args.type,
1484
+ position: args.position,
1485
+ x: getHandleBoundsX(args.position, args.nodeWidth),
1486
+ y: getHandleBoundsY(args.rowIndex),
1487
+ width: HANDLE_SIZE_PX,
1488
+ height: HANDLE_SIZE_PX,
1489
+ };
1490
+ }
1491
+ /**
1492
+ * Create handle layout object for React Flow rendering.
1493
+ */
1494
+ function createHandleLayout(args) {
1495
+ return {
1496
+ id: args.id,
1497
+ type: args.type,
1498
+ position: args.position,
1499
+ y: getHandleLayoutY(args.rowIndex),
1500
+ };
1501
+ }
1446
1502
  function layoutNode(args) {
1447
1503
  const { node, registry, showValues, overrides } = args;
1448
1504
  const { inputs, outputs } = computeEffectiveHandles(node, registry);
@@ -1454,40 +1510,34 @@ function layoutNode(args) {
1454
1510
  showValues,
1455
1511
  overrides,
1456
1512
  });
1457
- const HEADER = NODE_HEADER_HEIGHT_PX;
1458
- const ROW = NODE_ROW_HEIGHT_PX;
1459
1513
  const handles = [
1460
- ...inputOrder.map((id, i) => ({
1514
+ ...inputOrder.map((id, i) => createHandleBounds({
1461
1515
  id,
1462
1516
  type: "target",
1463
1517
  position: react.Position.Left,
1464
- x: 0,
1465
- y: HEADER + i * ROW,
1466
- width: 1,
1467
- height: ROW + 2,
1518
+ rowIndex: i,
1519
+ nodeWidth: width,
1468
1520
  })),
1469
- ...outputOrder.map((id, i) => ({
1521
+ ...outputOrder.map((id, i) => createHandleBounds({
1470
1522
  id,
1471
1523
  type: "source",
1472
1524
  position: react.Position.Right,
1473
- x: width - 1,
1474
- y: HEADER + i * ROW,
1475
- width: 1,
1476
- height: ROW + 2,
1525
+ rowIndex: i,
1526
+ nodeWidth: width,
1477
1527
  })),
1478
1528
  ];
1479
1529
  const handleLayout = [
1480
- ...inputOrder.map((id, i) => ({
1530
+ ...inputOrder.map((id, i) => createHandleLayout({
1481
1531
  id,
1482
1532
  type: "target",
1483
1533
  position: react.Position.Left,
1484
- y: HEADER + i * ROW + ROW / 2,
1534
+ rowIndex: i,
1485
1535
  })),
1486
- ...outputOrder.map((id, i) => ({
1536
+ ...outputOrder.map((id, i) => createHandleLayout({
1487
1537
  id,
1488
1538
  type: "source",
1489
1539
  position: react.Position.Right,
1490
- y: HEADER + i * ROW + ROW / 2,
1540
+ rowIndex: i,
1491
1541
  })),
1492
1542
  ];
1493
1543
  return { width, height, inputOrder, outputOrder, handles, handleLayout };
@@ -1802,20 +1852,22 @@ function toReactFlow(def, positions, registry, opts) {
1802
1852
  const baseRightCount = geom.outputOrder.length;
1803
1853
  const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
1804
1854
  const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
1805
- const HEADER = NODE_HEADER_HEIGHT_PX;
1806
- const ROW = NODE_ROW_HEIGHT_PX;
1807
1855
  const extraHandleLayoutLeft = extraInputs.map((id, i) => ({
1808
- id,
1809
- type: "target",
1810
- position: react.Position.Left,
1811
- y: HEADER + (baseLeftCount + i) * ROW + ROW / 2,
1856
+ ...createHandleLayout({
1857
+ id,
1858
+ type: "target",
1859
+ position: react.Position.Left,
1860
+ rowIndex: baseLeftCount + i,
1861
+ }),
1812
1862
  missing: true,
1813
1863
  }));
1814
1864
  const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
1815
- id,
1816
- type: "source",
1817
- position: react.Position.Right,
1818
- y: HEADER + (baseRightCount + i) * ROW + ROW / 2,
1865
+ ...createHandleLayout({
1866
+ id,
1867
+ type: "source",
1868
+ position: react.Position.Right,
1869
+ rowIndex: baseRightCount + i,
1870
+ }),
1819
1871
  missing: true,
1820
1872
  }));
1821
1873
  const handleLayout = [
@@ -1824,23 +1876,19 @@ function toReactFlow(def, positions, registry, opts) {
1824
1876
  ...extraHandleLayoutRight,
1825
1877
  ];
1826
1878
  // Precompute handle bounds (including missing) so edges can render immediately
1827
- const missingBoundsLeft = extraInputs.map((id, i) => ({
1879
+ const missingBoundsLeft = extraInputs.map((id, i) => createHandleBounds({
1828
1880
  id,
1829
1881
  type: "target",
1830
1882
  position: react.Position.Left,
1831
- x: 0,
1832
- y: HEADER + (baseLeftCount + i) * ROW,
1833
- width: 1,
1834
- height: ROW + 2,
1883
+ rowIndex: baseLeftCount + i,
1884
+ nodeWidth: geom.width,
1835
1885
  }));
1836
- const missingBoundsRight = extraOutputs.map((id, i) => ({
1886
+ const missingBoundsRight = extraOutputs.map((id, i) => createHandleBounds({
1837
1887
  id,
1838
1888
  type: "source",
1839
1889
  position: react.Position.Right,
1840
- x: geom.width - 1,
1841
- y: HEADER + (baseRightCount + i) * ROW,
1842
- width: 1,
1843
- height: ROW + 2,
1890
+ rowIndex: baseRightCount + i,
1891
+ nodeWidth: geom.width,
1844
1892
  }));
1845
1893
  const handles = [
1846
1894
  ...geom.handles,
@@ -1851,7 +1899,7 @@ function toReactFlow(def, positions, registry, opts) {
1851
1899
  const baseRows = Math.max(baseLeftCount, baseRightCount);
1852
1900
  const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
1853
1901
  const initialWidth = geom.width;
1854
- const initialHeight = geom.height + Math.max(0, newRows - baseRows) * ROW;
1902
+ const initialHeight = geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX;
1855
1903
  return {
1856
1904
  id: n.nodeId,
1857
1905
  data: {
@@ -1987,7 +2035,7 @@ function getHandleClassName(args) {
1987
2035
  else {
1988
2036
  borderColor = "!border-gray-500 dark:!border-gray-400";
1989
2037
  }
1990
- return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
2038
+ return cx("!w-3 !h-3 !bg-white/50 !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
1991
2039
  }
1992
2040
 
1993
2041
  function generateTimestamp() {
@@ -3059,7 +3107,7 @@ function NodeHandleItem({ kind, id, type, position, y, isConnectable, className,
3059
3107
  textOverflow: "ellipsis",
3060
3108
  }, children: renderLabel({ kind, id }) }))] }));
3061
3109
  }
3062
- 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", }) {
3110
+ function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
3063
3111
  const layout = data.handleLayout ?? [];
3064
3112
  const byId = React.useMemo(() => {
3065
3113
  const m = new Map();
@@ -3090,28 +3138,26 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
3090
3138
  const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
3091
3139
  const position = placed?.position ?? react.Position.Left;
3092
3140
  const y = placed?.y;
3093
- const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ??
3094
- inputClassName;
3141
+ const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
3095
3142
  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));
3096
3143
  }), missingInputs.map((h) => {
3097
3144
  const key = `missing-input:${h.id}`;
3098
3145
  const position = h.position ?? react.Position.Left;
3099
3146
  const y = h.y;
3100
3147
  const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500";
3101
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
3148
+ 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));
3102
3149
  }), (data.outputHandles ?? []).map((h) => {
3103
3150
  const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
3104
3151
  const position = placed?.position ?? react.Position.Right;
3105
3152
  const y = placed?.y;
3106
- const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ??
3107
- outputClassName;
3108
- return (jsxRuntime.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));
3153
+ const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
3154
+ 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));
3109
3155
  }), missingOutputs.map((h) => {
3110
3156
  const key = `missing-output:${h.id}`;
3111
3157
  const position = h.position ?? react.Position.Right;
3112
3158
  const y = h.y;
3113
3159
  const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
3114
- return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
3160
+ return (jsxRuntime.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));
3115
3161
  })] }));
3116
3162
  }
3117
3163
 
@@ -3140,7 +3186,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
3140
3186
  status,
3141
3187
  validation,
3142
3188
  });
3143
- return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/70 !dark:bg-stone-900", containerBorder), style: {
3189
+ return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/50 !dark:bg-stone-900", containerBorder), style: {
3144
3190
  position: "relative",
3145
3191
  minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
3146
3192
  minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
@@ -3519,6 +3565,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3519
3565
  const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, } = useWorkbenchContext();
3520
3566
  const nodeValidation = validationByNode;
3521
3567
  const edgeValidation = validationByEdge.errors;
3568
+ const [registryVersion, setRegistryVersion] = React.useState(0);
3522
3569
  // Keep stable references for nodes/edges to avoid unnecessary updates
3523
3570
  const prevNodesRef = React.useRef([]);
3524
3571
  const prevEdgesRef = React.useRef([]);
@@ -3789,7 +3836,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3789
3836
  const onCloseNodeMenu = React.useCallback(() => {
3790
3837
  setNodeMenuOpen(false);
3791
3838
  }, []);
3792
- const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry]);
3839
+ React.useEffect(() => {
3840
+ const off = runner.on("registry", () => {
3841
+ setRegistryVersion((v) => v + 1);
3842
+ });
3843
+ return () => off();
3844
+ }, [runner]);
3845
+ const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
3793
3846
  const defaultContextMenuHandlers = React.useMemo(() => ({
3794
3847
  onAddNode: addNodeAt,
3795
3848
  onClose: onCloseMenu,
@@ -4409,11 +4462,16 @@ exports.WorkbenchProvider = WorkbenchProvider;
4409
4462
  exports.WorkbenchStudio = WorkbenchStudio;
4410
4463
  exports.computeEffectiveHandles = computeEffectiveHandles;
4411
4464
  exports.countVisibleHandles = countVisibleHandles;
4465
+ exports.createHandleBounds = createHandleBounds;
4466
+ exports.createHandleLayout = createHandleLayout;
4412
4467
  exports.download = download;
4413
4468
  exports.estimateNodeSize = estimateNodeSize;
4414
4469
  exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
4415
4470
  exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
4471
+ exports.getHandleBoundsX = getHandleBoundsX;
4472
+ exports.getHandleBoundsY = getHandleBoundsY;
4416
4473
  exports.getHandleClassName = getHandleClassName;
4474
+ exports.getHandleLayoutY = getHandleLayoutY;
4417
4475
  exports.getNodeBorderClassNames = getNodeBorderClassNames;
4418
4476
  exports.layoutNode = layoutNode;
4419
4477
  exports.preformatValueForDisplay = preformatValueForDisplay;