@ricsam/react-spreadsheets 0.0.7 → 0.0.9

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/dist/mjs/lib.mjs CHANGED
@@ -1349,6 +1349,124 @@ var withReadableTextColor = (style) => {
1349
1349
  return { ...style, color };
1350
1350
  };
1351
1351
  var hasBorderStyle = (style) => Boolean(style?.borderColor || style?.borderTopColor || style?.borderRightColor || style?.borderBottomColor || style?.borderLeftColor);
1352
+ var normalizeFiniteCount = (count) => {
1353
+ if (count === undefined || count === Infinity)
1354
+ return;
1355
+ if (!Number.isFinite(count))
1356
+ return 0;
1357
+ return Math.max(0, Math.floor(count));
1358
+ };
1359
+ var countToSelectionBound = (count) => count === undefined ? { type: "infinity" } : { type: "number", value: count };
1360
+ var alignScrollOffset = (current, itemStart, itemEnd, viewportSize, align) => {
1361
+ if (viewportSize <= 0)
1362
+ return current;
1363
+ if (align === "start")
1364
+ return itemStart;
1365
+ if (align === "end")
1366
+ return itemEnd - viewportSize;
1367
+ if (itemEnd - itemStart > viewportSize) {
1368
+ if (itemEnd <= current)
1369
+ return itemEnd - viewportSize;
1370
+ if (itemStart >= current + viewportSize)
1371
+ return itemStart;
1372
+ return current;
1373
+ }
1374
+ if (itemStart < current)
1375
+ return itemStart;
1376
+ if (itemEnd > current + viewportSize)
1377
+ return itemEnd - viewportSize;
1378
+ return current;
1379
+ };
1380
+ var normalizeScrollRangeAxis = (start, end, finiteCount) => {
1381
+ if (!Number.isFinite(start) || end.type === "number" && !Number.isFinite(end.value)) {
1382
+ return;
1383
+ }
1384
+ const normalizedStart = Math.max(0, Math.floor(start));
1385
+ const normalizedEnd = end.type === "infinity" ? finiteCount === undefined ? normalizedStart : Math.max(0, finiteCount - 1) : Math.max(0, Math.floor(end.value));
1386
+ const lower = Math.min(normalizedStart, normalizedEnd);
1387
+ const upper = Math.max(normalizedStart, normalizedEnd);
1388
+ if (finiteCount === undefined)
1389
+ return { start: lower, end: upper };
1390
+ if (finiteCount === 0)
1391
+ return;
1392
+ const lastIndex = finiteCount - 1;
1393
+ return {
1394
+ start: Math.min(lower, lastIndex),
1395
+ end: Math.min(upper, lastIndex)
1396
+ };
1397
+ };
1398
+ var hasCellData = (value) => value !== undefined && value !== "";
1399
+ var getSparseUsedRange = (cellData, rowCount, columnCount) => {
1400
+ let minRow = Infinity;
1401
+ let maxRow = -1;
1402
+ let minCol = Infinity;
1403
+ let maxCol = -1;
1404
+ for (const [reference, value] of cellData) {
1405
+ if (!hasCellData(value))
1406
+ continue;
1407
+ let rowIndex;
1408
+ let columnIndex;
1409
+ try {
1410
+ ({ rowIndex, columnIndex } = parseCellReference(reference));
1411
+ } catch {
1412
+ continue;
1413
+ }
1414
+ if (rowIndex < 0 || columnIndex < 0 || rowCount !== undefined && rowIndex >= rowCount || columnCount !== undefined && columnIndex >= columnCount) {
1415
+ continue;
1416
+ }
1417
+ minRow = Math.min(minRow, rowIndex);
1418
+ maxRow = Math.max(maxRow, rowIndex);
1419
+ minCol = Math.min(minCol, columnIndex);
1420
+ maxCol = Math.max(maxCol, columnIndex);
1421
+ }
1422
+ if (maxRow < 0 || maxCol < 0)
1423
+ return;
1424
+ return {
1425
+ start: { row: minRow, col: minCol },
1426
+ end: {
1427
+ row: { type: "number", value: maxRow },
1428
+ col: { type: "number", value: maxCol }
1429
+ }
1430
+ };
1431
+ };
1432
+ var resolveSparseNavigationTarget = (request, cellData) => {
1433
+ if (request.kind !== "jump" || request.table)
1434
+ return;
1435
+ const isVertical = request.direction === "up" || request.direction === "down";
1436
+ const movingForward = request.direction === "down" || request.direction === "right";
1437
+ const originAxis = isVertical ? request.origin.row : request.origin.col;
1438
+ const fixedAxis = isVertical ? request.origin.col : request.origin.row;
1439
+ const occupied = new Set;
1440
+ for (const [reference, value] of cellData) {
1441
+ if (!hasCellData(value))
1442
+ continue;
1443
+ let rowIndex;
1444
+ let columnIndex;
1445
+ try {
1446
+ ({ rowIndex, columnIndex } = parseCellReference(reference));
1447
+ } catch {
1448
+ continue;
1449
+ }
1450
+ if ((isVertical ? columnIndex : rowIndex) === fixedAxis) {
1451
+ occupied.add(isVertical ? rowIndex : columnIndex);
1452
+ }
1453
+ }
1454
+ const step = movingForward ? 1 : -1;
1455
+ let targetAxis;
1456
+ if (occupied.has(originAxis + step)) {
1457
+ targetAxis = originAxis + step;
1458
+ while (occupied.has(targetAxis + step))
1459
+ targetAxis += step;
1460
+ } else {
1461
+ const candidates = [...occupied].filter((index) => movingForward ? index > originAxis : index < originAxis);
1462
+ if (candidates.length > 0) {
1463
+ targetAxis = movingForward ? Math.min(...candidates) : Math.max(...candidates);
1464
+ }
1465
+ }
1466
+ if (targetAxis === undefined)
1467
+ return;
1468
+ return isVertical ? { row: targetAxis, col: request.origin.col } : { row: request.origin.row, col: targetAxis };
1469
+ };
1352
1470
  var CellComponent = React2.memo(({
1353
1471
  value,
1354
1472
  row,
@@ -1652,6 +1770,8 @@ var Spreadsheet = forwardRef(({
1652
1770
  cellData: controlledCellData,
1653
1771
  onCellDataChange,
1654
1772
  onCellDataChangeError,
1773
+ rowCount,
1774
+ columnCount,
1655
1775
  columnWidths: controlledColumnWidths,
1656
1776
  onColumnWidthsChange,
1657
1777
  rowHeights: controlledRowHeights,
@@ -1673,14 +1793,35 @@ var Spreadsheet = forwardRef(({
1673
1793
  const [containerDivRef, setContainerDivRef] = useState4(null);
1674
1794
  const colorScheme = useColorScheme(containerDivRef);
1675
1795
  const gridLineProbeRef = useRef3(null);
1796
+ const [internalCellData, setInternalCellData] = useState4(new Map);
1797
+ const [retryingEdit, setRetryingEdit] = useState4(null);
1798
+ const retryingEditVersionRef = useRef3(0);
1799
+ const cellData = controlledCellData ?? internalCellData;
1800
+ const cellDataRef = useRef3(cellData);
1801
+ cellDataRef.current = cellData;
1802
+ const finiteRowCount = normalizeFiniteCount(rowCount);
1803
+ const finiteColumnCount = normalizeFiniteCount(columnCount);
1804
+ const gridCountsRef = useRef3({ rows: finiteRowCount, columns: finiteColumnCount });
1805
+ gridCountsRef.current = { rows: finiteRowCount, columns: finiteColumnCount };
1806
+ const getNumRows = useCallback3(() => countToSelectionBound(gridCountsRef.current.rows), []);
1807
+ const getNumCols = useCallback3(() => countToSelectionBound(gridCountsRef.current.columns), []);
1808
+ const getDefaultUsedRange = useCallback3(() => getSparseUsedRange(cellDataRef.current, gridCountsRef.current.rows, gridCountsRef.current.columns), []);
1809
+ const defaultResolveTarget = useCallback3((request) => resolveSparseNavigationTarget(request, cellDataRef.current), []);
1810
+ const consumerNavigation = selection?.navigation;
1811
+ const navigation = useMemo(() => ({
1812
+ getUsedRange: consumerNavigation?.getUsedRange ?? getDefaultUsedRange,
1813
+ ...consumerNavigation?.getTableAt ? { getTableAt: consumerNavigation.getTableAt } : {},
1814
+ resolveTarget: consumerNavigation?.resolveTarget ?? defaultResolveTarget
1815
+ }), [consumerNavigation, defaultResolveTarget, getDefaultUsedRange]);
1676
1816
  const selectionManager = useInitializeSelectionManager({
1677
- getNumCols: () => ({ type: "infinity" }),
1678
- getNumRows: () => ({ type: "infinity" }),
1817
+ getNumCols,
1818
+ getNumRows,
1679
1819
  initialState: selection?.initialState,
1680
1820
  state: selection?.state,
1681
1821
  onStateChange: selection?.onStateChange,
1682
1822
  containerElement: containerDivRef,
1683
1823
  formats: selection?.formats,
1824
+ navigation,
1684
1825
  disableAutoClipboard: true
1685
1826
  });
1686
1827
  const effects = selection?.effects;
@@ -1689,12 +1830,6 @@ var Spreadsheet = forwardRef(({
1689
1830
  return effects(selectionManager);
1690
1831
  }
1691
1832
  }, [selectionManager, effects]);
1692
- const [internalCellData, setInternalCellData] = useState4(new Map);
1693
- const [retryingEdit, setRetryingEdit] = useState4(null);
1694
- const retryingEditVersionRef = useRef3(0);
1695
- const cellData = controlledCellData ?? internalCellData;
1696
- const cellDataRef = useRef3(cellData);
1697
- cellDataRef.current = cellData;
1698
1833
  const isControlled = controlledCellData !== undefined;
1699
1834
  const prevIsControlledRef = useRef3(isControlled);
1700
1835
  selectionManager.getCellsWithData = (area) => {
@@ -1870,7 +2005,7 @@ var Spreadsheet = forwardRef(({
1870
2005
  ctx.lineWidth = 1;
1871
2006
  let x = gridDims.headerWidth;
1872
2007
  let colIndex = 0;
1873
- while (x < viewport.scrollX + gridDims.headerWidth) {
2008
+ while (x < viewport.scrollX + gridDims.headerWidth && (finiteColumnCount === undefined || colIndex < finiteColumnCount)) {
1874
2009
  const colWidth = (columnWidths.get(indexToColumn(colIndex)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1875
2010
  if (x + colWidth > viewport.scrollX + gridDims.headerWidth) {
1876
2011
  break;
@@ -1878,7 +2013,7 @@ var Spreadsheet = forwardRef(({
1878
2013
  x += colWidth;
1879
2014
  colIndex++;
1880
2015
  }
1881
- while (x - viewport.scrollX < width) {
2016
+ while (x - viewport.scrollX < width && (finiteColumnCount === undefined || colIndex < finiteColumnCount)) {
1882
2017
  const drawX = x - viewport.scrollX;
1883
2018
  if (drawX >= gridDims.headerWidth && drawX <= width) {
1884
2019
  ctx.beginPath();
@@ -1892,7 +2027,7 @@ var Spreadsheet = forwardRef(({
1892
2027
  }
1893
2028
  let y = gridDims.headerHeight;
1894
2029
  let rowNum = 1;
1895
- while (y < viewport.scrollY + gridDims.headerHeight) {
2030
+ while (y < viewport.scrollY + gridDims.headerHeight && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
1896
2031
  const rowHeight = (rowHeights.get(rowNum) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
1897
2032
  if (y + rowHeight > viewport.scrollY + gridDims.headerHeight) {
1898
2033
  break;
@@ -1900,7 +2035,7 @@ var Spreadsheet = forwardRef(({
1900
2035
  y += rowHeight;
1901
2036
  rowNum++;
1902
2037
  }
1903
- while (y - viewport.scrollY < height) {
2038
+ while (y - viewport.scrollY < height && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
1904
2039
  const drawY = y - viewport.scrollY;
1905
2040
  if (drawY >= gridDims.headerHeight && drawY <= height) {
1906
2041
  ctx.beginPath();
@@ -1914,6 +2049,8 @@ var Spreadsheet = forwardRef(({
1914
2049
  }
1915
2050
  }, [
1916
2051
  columnWidths,
2052
+ finiteColumnCount,
2053
+ finiteRowCount,
1917
2054
  gridDims.headerHeight,
1918
2055
  gridDims.headerWidth,
1919
2056
  height,
@@ -1931,7 +2068,7 @@ var Spreadsheet = forwardRef(({
1931
2068
  const cells = [];
1932
2069
  let x = 0;
1933
2070
  let startCol = 0;
1934
- while (x < viewport.scrollX) {
2071
+ while (x < viewport.scrollX && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
1935
2072
  const colWidth = (columnWidths.get(indexToColumn(startCol)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1936
2073
  if (x + colWidth > viewport.scrollX) {
1937
2074
  break;
@@ -1941,7 +2078,7 @@ var Spreadsheet = forwardRef(({
1941
2078
  }
1942
2079
  let y = 0;
1943
2080
  let startRow = 0;
1944
- while (y < viewport.scrollY) {
2081
+ while (y < viewport.scrollY && (finiteRowCount === undefined || startRow < finiteRowCount)) {
1945
2082
  const rowHeight = (rowHeights.get(startRow + 1) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
1946
2083
  if (y + rowHeight > viewport.scrollY) {
1947
2084
  break;
@@ -1951,10 +2088,10 @@ var Spreadsheet = forwardRef(({
1951
2088
  }
1952
2089
  let rowY = y;
1953
2090
  let row = startRow;
1954
- while (rowY < viewport.scrollY + height) {
2091
+ while (rowY < viewport.scrollY + height && (finiteRowCount === undefined || row < finiteRowCount)) {
1955
2092
  let colX = x;
1956
2093
  let col = startCol;
1957
- while (colX < viewport.scrollX + width) {
2094
+ while (colX < viewport.scrollX + width && (finiteColumnCount === undefined || col < finiteColumnCount)) {
1958
2095
  const colKey = indexToColumn(col);
1959
2096
  const rowNum = getRowNumber(row);
1960
2097
  const cellKey = `${colKey}${rowNum}`;
@@ -1975,6 +2112,8 @@ var Spreadsheet = forwardRef(({
1975
2112
  }, [
1976
2113
  cellData,
1977
2114
  columnWidths,
2115
+ finiteColumnCount,
2116
+ finiteRowCount,
1978
2117
  height,
1979
2118
  rowHeights,
1980
2119
  viewport.scrollX,
@@ -2002,6 +2141,79 @@ var Spreadsheet = forwardRef(({
2002
2141
  }
2003
2142
  return { x, y };
2004
2143
  }, [columnWidths, rowHeights]);
2144
+ const clampViewportOffsets = useCallback3((scrollX, scrollY, zoom) => {
2145
+ const bodyWidth = Math.max(0, width - HEADER_WIDTH * zoom);
2146
+ const bodyHeight = Math.max(0, height - HEADER_HEIGHT * zoom);
2147
+ const maxScrollX = finiteColumnCount === undefined ? Infinity : Math.max(0, getColumnEdgePosition(finiteColumnCount, columnWidths) * zoom - bodyWidth);
2148
+ const maxScrollY = finiteRowCount === undefined ? Infinity : Math.max(0, getRowEdgePosition(finiteRowCount, rowHeights) * zoom - bodyHeight);
2149
+ return {
2150
+ scrollX: Math.min(Math.max(0, scrollX), maxScrollX),
2151
+ scrollY: Math.min(Math.max(0, scrollY), maxScrollY)
2152
+ };
2153
+ }, [columnWidths, finiteColumnCount, finiteRowCount, height, rowHeights, width]);
2154
+ const revealRange = useCallback3((range, options = {}) => {
2155
+ if (finiteRowCount === 0 || finiteColumnCount === 0)
2156
+ return;
2157
+ const rows = normalizeScrollRangeAxis(range.start.row, range.end.row, finiteRowCount);
2158
+ const columns = normalizeScrollRangeAxis(range.start.col, range.end.col, finiteColumnCount);
2159
+ if (!rows || !columns)
2160
+ return;
2161
+ setViewport((previous) => {
2162
+ const bodyWidth = Math.max(0, width - HEADER_WIDTH * previous.zoom);
2163
+ const bodyHeight = Math.max(0, height - HEADER_HEIGHT * previous.zoom);
2164
+ const left = getColumnEdgePosition(columns.start, columnWidths) * previous.zoom;
2165
+ const right = getColumnEdgePosition(columns.end + 1, columnWidths) * previous.zoom;
2166
+ const top = getRowEdgePosition(rows.start, rowHeights) * previous.zoom;
2167
+ const bottom = getRowEdgePosition(rows.end + 1, rowHeights) * previous.zoom;
2168
+ const desired = clampViewportOffsets(alignScrollOffset(previous.scrollX, left, right, bodyWidth, options.columnAlign ?? "nearest"), alignScrollOffset(previous.scrollY, top, bottom, bodyHeight, options.rowAlign ?? "nearest"), previous.zoom);
2169
+ if (desired.scrollX === previous.scrollX && desired.scrollY === previous.scrollY) {
2170
+ return previous;
2171
+ }
2172
+ return { ...previous, ...desired };
2173
+ });
2174
+ }, [
2175
+ clampViewportOffsets,
2176
+ columnWidths,
2177
+ finiteColumnCount,
2178
+ finiteRowCount,
2179
+ height,
2180
+ rowHeights,
2181
+ width
2182
+ ]);
2183
+ const revealCell = useCallback3((cell, options = {}) => {
2184
+ revealRange({
2185
+ start: { row: cell.row, col: cell.col },
2186
+ end: {
2187
+ row: { type: "number", value: cell.row },
2188
+ col: { type: "number", value: cell.col }
2189
+ }
2190
+ }, options);
2191
+ }, [revealRange]);
2192
+ const scrollToCell = useCallback3((cell, options = {}) => {
2193
+ const align = options.align ?? "nearest";
2194
+ revealCell(cell, { rowAlign: align, columnAlign: align });
2195
+ }, [revealCell]);
2196
+ const scrollToRange = useCallback3((range, options = {}) => {
2197
+ const align = options.align ?? "nearest";
2198
+ revealRange(range, { rowAlign: align, columnAlign: align });
2199
+ }, [revealRange]);
2200
+ useEffect3(() => selectionManager.listenToViewportRequest((request) => {
2201
+ if (request.type === "reveal-range") {
2202
+ scrollToRange(request.range, { align: request.align });
2203
+ return;
2204
+ }
2205
+ const isVertical = request.direction === "up" || request.direction === "down";
2206
+ revealCell(request.cell, {
2207
+ rowAlign: isVertical ? request.align : "nearest",
2208
+ columnAlign: isVertical ? "nearest" : request.align
2209
+ });
2210
+ }), [revealCell, scrollToRange, selectionManager]);
2211
+ useEffect3(() => {
2212
+ setViewport((previous) => {
2213
+ const next = clampViewportOffsets(previous.scrollX, previous.scrollY, previous.zoom);
2214
+ return next.scrollX === previous.scrollX && next.scrollY === previous.scrollY ? previous : { ...previous, ...next };
2215
+ });
2216
+ }, [clampViewportOffsets]);
2005
2217
  React2.useEffect(() => {
2006
2218
  return selectionManager.listenToUpdateData((updates) => {
2007
2219
  const editingState = selectionManager.isEditing;
@@ -2076,12 +2288,11 @@ var Spreadsheet = forwardRef(({
2076
2288
  const handleWheel = useCallback3((e) => {
2077
2289
  e.preventDefault();
2078
2290
  e.stopPropagation();
2079
- setViewport((prev) => ({
2080
- ...prev,
2081
- scrollX: Math.max(0, prev.scrollX + e.deltaX),
2082
- scrollY: Math.max(0, prev.scrollY + e.deltaY)
2291
+ setViewport((previous) => ({
2292
+ ...previous,
2293
+ ...clampViewportOffsets(previous.scrollX + e.deltaX, previous.scrollY + e.deltaY, previous.zoom)
2083
2294
  }));
2084
- }, []);
2295
+ }, [clampViewportOffsets]);
2085
2296
  const commitOverlayChildren = useCallback3((children) => {
2086
2297
  overlayChildrenRef.current = children;
2087
2298
  if (onOverlayChildrenChange) {
@@ -2500,7 +2711,7 @@ var Spreadsheet = forwardRef(({
2500
2711
  const headers = [];
2501
2712
  let x = 0;
2502
2713
  let startCol = 0;
2503
- while (x < viewport.scrollX) {
2714
+ while (x < viewport.scrollX && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2504
2715
  const col = indexToColumn(startCol);
2505
2716
  const colWidth = getColumnWidth(col) * viewport.zoom;
2506
2717
  if (x + colWidth > viewport.scrollX) {
@@ -2509,7 +2720,7 @@ var Spreadsheet = forwardRef(({
2509
2720
  x += colWidth;
2510
2721
  startCol++;
2511
2722
  }
2512
- while (x < viewport.scrollX + width) {
2723
+ while (x < viewport.scrollX + width && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2513
2724
  const col = indexToColumn(startCol);
2514
2725
  const colWidth = getColumnWidth(col) * viewport.zoom;
2515
2726
  headers.push({
@@ -2520,14 +2731,14 @@ var Spreadsheet = forwardRef(({
2520
2731
  startCol++;
2521
2732
  }
2522
2733
  return headers;
2523
- }, [width, viewport, getColumnWidth]);
2734
+ }, [finiteColumnCount, width, viewport, getColumnWidth]);
2524
2735
  const getVisibleRowHeaders = useCallback3(() => {
2525
2736
  if (!height)
2526
2737
  return [];
2527
2738
  const headers = [];
2528
2739
  let y = 0;
2529
2740
  let rowNum = 1;
2530
- while (y < viewport.scrollY) {
2741
+ while (y < viewport.scrollY && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2531
2742
  const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2532
2743
  if (y + rowHeight > viewport.scrollY) {
2533
2744
  break;
@@ -2535,7 +2746,7 @@ var Spreadsheet = forwardRef(({
2535
2746
  y += rowHeight;
2536
2747
  rowNum++;
2537
2748
  }
2538
- while (y < viewport.scrollY + height) {
2749
+ while (y < viewport.scrollY + height && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2539
2750
  const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2540
2751
  headers.push({
2541
2752
  row: rowNum,
@@ -2545,7 +2756,7 @@ var Spreadsheet = forwardRef(({
2545
2756
  rowNum++;
2546
2757
  }
2547
2758
  return headers;
2548
- }, [height, viewport, getRowHeight]);
2759
+ }, [finiteRowCount, height, viewport, getRowHeight]);
2549
2760
  const { visibleOverlays, overlaysToRender } = useMemo(() => {
2550
2761
  if (!width || !height || !overlayChildren) {
2551
2762
  return { visibleOverlays: [], overlaysToRender: [] };
@@ -2593,8 +2804,45 @@ var Spreadsheet = forwardRef(({
2593
2804
  const visibleColumnHeaders = React2.useMemo(() => getVisibleColumnHeaders(), [getVisibleColumnHeaders]);
2594
2805
  const visibleRowHeaders = React2.useMemo(() => getVisibleRowHeaders(), [getVisibleRowHeaders]);
2595
2806
  const hasFocus = useSelectionManager(selectionManager, () => selectionManager.hasFocus);
2807
+ const referenceSelection = useSelectionManager(selectionManager, () => selectionManager.referenceSelection);
2808
+ const referenceSelectionRect = useMemo(() => {
2809
+ if (referenceSelection.type === "none")
2810
+ return;
2811
+ if (finiteRowCount === 0 || finiteColumnCount === 0)
2812
+ return;
2813
+ const range = referenceSelection.range;
2814
+ const lastVisibleRow = visibleCells.reduce((maximum, cell) => Math.max(maximum, cell.row - 1), range.start.row);
2815
+ const lastVisibleCol = visibleCells.reduce((maximum, cell) => Math.max(maximum, columnToIndex(cell.col)), range.start.col);
2816
+ const rawEndRow = range.end.row.type === "number" ? range.end.row.value : finiteRowCount === undefined ? lastVisibleRow : finiteRowCount - 1;
2817
+ const rawEndCol = range.end.col.type === "number" ? range.end.col.value : finiteColumnCount === undefined ? lastVisibleCol : finiteColumnCount - 1;
2818
+ const maxRow = finiteRowCount === undefined ? Infinity : finiteRowCount - 1;
2819
+ const maxCol = finiteColumnCount === undefined ? Infinity : finiteColumnCount - 1;
2820
+ const startRow = Math.min(Math.max(0, Math.min(range.start.row, rawEndRow)), maxRow);
2821
+ const endRow = Math.min(Math.max(0, Math.max(range.start.row, rawEndRow)), maxRow);
2822
+ const startCol = Math.min(Math.max(0, Math.min(range.start.col, rawEndCol)), maxCol);
2823
+ const endCol = Math.min(Math.max(0, Math.max(range.start.col, rawEndCol)), maxCol);
2824
+ const rect = getRectFromCellSnapAnchor({
2825
+ startCol,
2826
+ startRow,
2827
+ endCol: endCol + 1,
2828
+ endRow: endRow + 1
2829
+ }, columnWidths, rowHeights);
2830
+ return {
2831
+ left: HEADER_WIDTH + rect.x,
2832
+ top: HEADER_HEIGHT + rect.y,
2833
+ width: rect.width,
2834
+ height: rect.height
2835
+ };
2836
+ }, [
2837
+ columnWidths,
2838
+ finiteColumnCount,
2839
+ finiteRowCount,
2840
+ referenceSelection,
2841
+ rowHeights,
2842
+ visibleCells
2843
+ ]);
2596
2844
  React2.useEffect(() => {
2597
- if (!containerDivRef || !hasFocus || !parentSelected) {
2845
+ if (!containerDivRef || !parentSelected) {
2598
2846
  return;
2599
2847
  }
2600
2848
  containerDivRef.addEventListener("wheel", handleWheel, {
@@ -2603,15 +2851,17 @@ var Spreadsheet = forwardRef(({
2603
2851
  return () => {
2604
2852
  containerDivRef.removeEventListener("wheel", handleWheel);
2605
2853
  };
2606
- }, [containerDivRef, handleWheel, hasFocus, parentSelected]);
2854
+ }, [containerDivRef, handleWheel, parentSelected]);
2607
2855
  useImperativeHandle(ref, () => ({
2608
2856
  focus: () => {
2609
2857
  selectionManager.focus();
2610
2858
  },
2611
2859
  blur: () => {
2612
2860
  selectionManager.blur();
2613
- }
2614
- }), [selectionManager]);
2861
+ },
2862
+ scrollToCell,
2863
+ scrollToRange
2864
+ }), [scrollToCell, scrollToRange, selectionManager]);
2615
2865
  return /* @__PURE__ */ jsx3("div", {
2616
2866
  className: cn("rsp-root", className),
2617
2867
  "data-rsp-scheme": colorScheme,
@@ -2715,28 +2965,38 @@ var Spreadsheet = forwardRef(({
2715
2965
  zIndex: 40
2716
2966
  }
2717
2967
  }),
2718
- /* @__PURE__ */ jsx3("div", {
2968
+ /* @__PURE__ */ jsxs2("div", {
2719
2969
  className: "rsp-cells",
2720
2970
  "data-testid": "spreadsheet-cells",
2721
2971
  style: {
2722
2972
  transform: `translate(${-viewport.scrollX}px, ${-viewport.scrollY}px) scale(${viewport.zoom})`,
2723
2973
  transformOrigin: "0 0"
2724
2974
  },
2725
- children: visibleCells.map((cell) => /* @__PURE__ */ jsx3(CellComponent, {
2726
- value: cellData.get(`${cell.col}${cell.row}`) ?? "",
2727
- row: cell.row,
2728
- col: cell.col,
2729
- selectionManager,
2730
- cellToPixelBase,
2731
- getColumnWidth,
2732
- getRowHeight,
2733
- customCellStyle,
2734
- customCellRenderer,
2735
- inputValue: cellData.get(`${cell.col}${cell.row}`) ?? "",
2736
- editInputVersion: retryingEdit?.rowIndex === cell.row - 1 && retryingEdit?.colIndex === columnToIndex(cell.col) ? retryingEdit.version : 0,
2737
- width,
2738
- height
2739
- }, `${cell.col}${cell.row}`))
2975
+ children: [
2976
+ visibleCells.map((cell) => /* @__PURE__ */ jsx3(CellComponent, {
2977
+ value: cellData.get(`${cell.col}${cell.row}`) ?? "",
2978
+ row: cell.row,
2979
+ col: cell.col,
2980
+ selectionManager,
2981
+ cellToPixelBase,
2982
+ getColumnWidth,
2983
+ getRowHeight,
2984
+ customCellStyle,
2985
+ customCellRenderer,
2986
+ inputValue: cellData.get(`${cell.col}${cell.row}`) ?? "",
2987
+ editInputVersion: retryingEdit?.rowIndex === cell.row - 1 && retryingEdit?.colIndex === columnToIndex(cell.col) ? retryingEdit.version : 0,
2988
+ width,
2989
+ height
2990
+ }, `${cell.col}${cell.row}`)),
2991
+ referenceSelectionRect && referenceSelection.type !== "none" && /* @__PURE__ */ jsx3("div", {
2992
+ "aria-hidden": "true",
2993
+ className: cn("rsp-reference-selection", `rsp-reference-selection-${referenceSelection.type}`),
2994
+ "data-testid": "spreadsheet-reference-selection",
2995
+ "data-reference-id": referenceSelection.id,
2996
+ "data-reference-phase": referenceSelection.type,
2997
+ style: referenceSelectionRect
2998
+ })
2999
+ ]
2740
3000
  }),
2741
3001
  components && overlayChildren && overlayChildren.length > 0 && /* @__PURE__ */ jsxs2("div", {
2742
3002
  ref: overlayContainerRef,
@@ -2821,7 +3081,7 @@ import {
2821
3081
  parseCellReference as parseCellReference2,
2822
3082
  getCellReference as getCellReference3
2823
3083
  } from "@ricsam/formula-engine";
2824
- import React3, { useCallback as useCallback4, useRef as useRef4, useState as useState6 } from "react";
3084
+ import React3, { useCallback as useCallback4, useMemo as useMemo2, useRef as useRef4, useState as useState6 } from "react";
2825
3085
 
2826
3086
  // src/utils/use-scheme-root.ts
2827
3087
  import { useState as useState5 } from "react";
@@ -2949,6 +3209,7 @@ class WorkbookSelectionManager {
2949
3209
  lastFocusedManager = null;
2950
3210
  listeners = new Set;
2951
3211
  cleanupFunctions = new Map;
3212
+ pendingRangeFocus = null;
2952
3213
  smAreaToSpreadsheetRange(area) {
2953
3214
  const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
2954
3215
  const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
@@ -2963,6 +3224,47 @@ class WorkbookSelectionManager {
2963
3224
  }
2964
3225
  };
2965
3226
  }
3227
+ spreadsheetRangeToSmArea(range) {
3228
+ return {
3229
+ start: { row: range.start.row, col: range.start.col },
3230
+ end: {
3231
+ row: range.end.row.type === "infinity" ? { type: "infinity" } : { type: "number", value: range.end.row.value },
3232
+ col: range.end.col.type === "infinity" ? { type: "infinity" } : { type: "number", value: range.end.col.value }
3233
+ }
3234
+ };
3235
+ }
3236
+ cloneRangeAddress(address) {
3237
+ return {
3238
+ workbookName: address.workbookName,
3239
+ sheetName: address.sheetName,
3240
+ range: this.smAreaToSpreadsheetRange(this.spreadsheetRangeToSmArea(address.range))
3241
+ };
3242
+ }
3243
+ matchesContext(address, context) {
3244
+ return address.workbookName === context.workbookName && address.sheetName === context.sheetName;
3245
+ }
3246
+ applyRangeFocus(manager, context, request) {
3247
+ const selection = this.spreadsheetRangeToSmArea(request.address.range);
3248
+ manager.setState({ selections: [selection] });
3249
+ manager.focus();
3250
+ manager.revealRange(selection, request.options);
3251
+ this.lastFocusedManager = { sm: manager, context };
3252
+ this.emitChange();
3253
+ }
3254
+ applyPendingRangeFocusOnMount(manager, context) {
3255
+ const pending = this.pendingRangeFocus;
3256
+ if (!pending || !this.matchesContext(pending.address, context))
3257
+ return;
3258
+ queueMicrotask(() => {
3259
+ if (this.pendingRangeFocus !== pending)
3260
+ return;
3261
+ const mountedContext = this.managers.get(manager);
3262
+ if (!mountedContext || !this.matchesContext(pending.address, mountedContext))
3263
+ return;
3264
+ this.pendingRangeFocus = null;
3265
+ this.applyRangeFocus(manager, mountedContext, pending);
3266
+ });
3267
+ }
2966
3268
  add(manager, context) {
2967
3269
  this.managers.set(manager, context);
2968
3270
  const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
@@ -2988,6 +3290,7 @@ class WorkbookSelectionManager {
2988
3290
  }
2989
3291
  };
2990
3292
  this.cleanupFunctions.set(manager, cleanup);
3293
+ this.applyPendingRangeFocusOnMount(manager, context);
2991
3294
  return cleanup;
2992
3295
  }
2993
3296
  getLastFocusedSelectionManager() {
@@ -3081,22 +3384,31 @@ class WorkbookSelectionManager {
3081
3384
  return false;
3082
3385
  }
3083
3386
  focusCell(workbookName, sheetName, row, col) {
3084
- for (const [manager, context] of this.managers) {
3085
- if (context.workbookName === workbookName && context.sheetName === sheetName) {
3086
- const selection = {
3087
- start: { row, col },
3088
- end: {
3089
- row: { type: "number", value: row },
3090
- col: { type: "number", value: col }
3091
- }
3092
- };
3093
- manager.setState({ selections: [selection] });
3094
- manager.focus();
3095
- this.lastFocusedManager = { sm: manager, context };
3096
- this.emitChange();
3097
- return true;
3387
+ return this.focusRange({
3388
+ workbookName,
3389
+ sheetName,
3390
+ range: {
3391
+ start: { row, col },
3392
+ end: {
3393
+ row: { type: "number", value: row },
3394
+ col: { type: "number", value: col }
3395
+ }
3098
3396
  }
3397
+ });
3398
+ }
3399
+ focusRange(address, options = {}) {
3400
+ const request = {
3401
+ address: this.cloneRangeAddress(address),
3402
+ options: { ...options }
3403
+ };
3404
+ for (const [manager, context] of this.managers) {
3405
+ if (!this.matchesContext(request.address, context))
3406
+ continue;
3407
+ this.pendingRangeFocus = null;
3408
+ this.applyRangeFocus(manager, context, request);
3409
+ return true;
3099
3410
  }
3411
+ this.pendingRangeFocus = request;
3100
3412
  return false;
3101
3413
  }
3102
3414
  emitChange() {
@@ -3345,6 +3657,42 @@ function FormulaSheet({
3345
3657
  const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3346
3658
  const state = useEngine(engine);
3347
3659
  const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
3660
+ const getEngineTableAt = useCallback4((cell) => {
3661
+ const table = engine.isCellInTable({
3662
+ workbookName,
3663
+ sheetName,
3664
+ rowIndex: cell.row,
3665
+ colIndex: cell.col
3666
+ });
3667
+ if (!table)
3668
+ return;
3669
+ const endRow = table.endRow.type === "number" ? { type: "number", value: table.endRow.value } : { type: "infinity" };
3670
+ const endCol = {
3671
+ type: "number",
3672
+ value: table.start.colIndex + table.headers.size - 1
3673
+ };
3674
+ const hasDataRows = endRow.type === "infinity" || endRow.value > table.start.rowIndex;
3675
+ return {
3676
+ id: `${table.workbookName}/${table.name}`,
3677
+ bounds: {
3678
+ start: { row: table.start.rowIndex, col: table.start.colIndex },
3679
+ end: { row: endRow, col: endCol }
3680
+ },
3681
+ ...hasDataRows ? {
3682
+ dataBounds: {
3683
+ start: {
3684
+ row: table.start.rowIndex + 1,
3685
+ col: table.start.colIndex
3686
+ },
3687
+ end: { row: endRow, col: endCol }
3688
+ }
3689
+ } : {}
3690
+ };
3691
+ }, [engine, sheetName, workbookName]);
3692
+ const formulaNavigation = useMemo2(() => ({
3693
+ ...selection?.navigation,
3694
+ getTableAt: selection?.navigation?.getTableAt ?? getEngineTableAt
3695
+ }), [getEngineTableAt, selection?.navigation]);
3348
3696
  const onCellDataChange = useCallback4((updatedSpreadsheet) => {
3349
3697
  const data = new Map(updatedSpreadsheet);
3350
3698
  data.forEach((value, key) => {
@@ -3518,6 +3866,7 @@ function FormulaSheet({
3518
3866
  parentSelected: isSelected,
3519
3867
  selection: {
3520
3868
  ...selection,
3869
+ navigation: formulaNavigation,
3521
3870
  effects: selectionManagerEffects
3522
3871
  },
3523
3872
  containerProps: isSelected && selectedArea ? {
@@ -4107,4 +4456,4 @@ export {
4107
4456
  useResizeObserver
4108
4457
  };
4109
4458
 
4110
- //# debugId=F4BFEDA101FB82C464756E2164756E21
4459
+ //# debugId=58E97FA1EA7F33CF64756E2164756E21