@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/cjs/lib.cjs CHANGED
@@ -1457,6 +1457,124 @@ var withReadableTextColor = (style) => {
1457
1457
  return { ...style, color };
1458
1458
  };
1459
1459
  var hasBorderStyle = (style) => Boolean(style?.borderColor || style?.borderTopColor || style?.borderRightColor || style?.borderBottomColor || style?.borderLeftColor);
1460
+ var normalizeFiniteCount = (count) => {
1461
+ if (count === undefined || count === Infinity)
1462
+ return;
1463
+ if (!Number.isFinite(count))
1464
+ return 0;
1465
+ return Math.max(0, Math.floor(count));
1466
+ };
1467
+ var countToSelectionBound = (count) => count === undefined ? { type: "infinity" } : { type: "number", value: count };
1468
+ var alignScrollOffset = (current, itemStart, itemEnd, viewportSize, align) => {
1469
+ if (viewportSize <= 0)
1470
+ return current;
1471
+ if (align === "start")
1472
+ return itemStart;
1473
+ if (align === "end")
1474
+ return itemEnd - viewportSize;
1475
+ if (itemEnd - itemStart > viewportSize) {
1476
+ if (itemEnd <= current)
1477
+ return itemEnd - viewportSize;
1478
+ if (itemStart >= current + viewportSize)
1479
+ return itemStart;
1480
+ return current;
1481
+ }
1482
+ if (itemStart < current)
1483
+ return itemStart;
1484
+ if (itemEnd > current + viewportSize)
1485
+ return itemEnd - viewportSize;
1486
+ return current;
1487
+ };
1488
+ var normalizeScrollRangeAxis = (start, end, finiteCount) => {
1489
+ if (!Number.isFinite(start) || end.type === "number" && !Number.isFinite(end.value)) {
1490
+ return;
1491
+ }
1492
+ const normalizedStart = Math.max(0, Math.floor(start));
1493
+ const normalizedEnd = end.type === "infinity" ? finiteCount === undefined ? normalizedStart : Math.max(0, finiteCount - 1) : Math.max(0, Math.floor(end.value));
1494
+ const lower = Math.min(normalizedStart, normalizedEnd);
1495
+ const upper = Math.max(normalizedStart, normalizedEnd);
1496
+ if (finiteCount === undefined)
1497
+ return { start: lower, end: upper };
1498
+ if (finiteCount === 0)
1499
+ return;
1500
+ const lastIndex = finiteCount - 1;
1501
+ return {
1502
+ start: Math.min(lower, lastIndex),
1503
+ end: Math.min(upper, lastIndex)
1504
+ };
1505
+ };
1506
+ var hasCellData = (value) => value !== undefined && value !== "";
1507
+ var getSparseUsedRange = (cellData, rowCount, columnCount) => {
1508
+ let minRow = Infinity;
1509
+ let maxRow = -1;
1510
+ let minCol = Infinity;
1511
+ let maxCol = -1;
1512
+ for (const [reference, value] of cellData) {
1513
+ if (!hasCellData(value))
1514
+ continue;
1515
+ let rowIndex;
1516
+ let columnIndex;
1517
+ try {
1518
+ ({ rowIndex, columnIndex } = parseCellReference(reference));
1519
+ } catch {
1520
+ continue;
1521
+ }
1522
+ if (rowIndex < 0 || columnIndex < 0 || rowCount !== undefined && rowIndex >= rowCount || columnCount !== undefined && columnIndex >= columnCount) {
1523
+ continue;
1524
+ }
1525
+ minRow = Math.min(minRow, rowIndex);
1526
+ maxRow = Math.max(maxRow, rowIndex);
1527
+ minCol = Math.min(minCol, columnIndex);
1528
+ maxCol = Math.max(maxCol, columnIndex);
1529
+ }
1530
+ if (maxRow < 0 || maxCol < 0)
1531
+ return;
1532
+ return {
1533
+ start: { row: minRow, col: minCol },
1534
+ end: {
1535
+ row: { type: "number", value: maxRow },
1536
+ col: { type: "number", value: maxCol }
1537
+ }
1538
+ };
1539
+ };
1540
+ var resolveSparseNavigationTarget = (request, cellData) => {
1541
+ if (request.kind !== "jump" || request.table)
1542
+ return;
1543
+ const isVertical = request.direction === "up" || request.direction === "down";
1544
+ const movingForward = request.direction === "down" || request.direction === "right";
1545
+ const originAxis = isVertical ? request.origin.row : request.origin.col;
1546
+ const fixedAxis = isVertical ? request.origin.col : request.origin.row;
1547
+ const occupied = new Set;
1548
+ for (const [reference, value] of cellData) {
1549
+ if (!hasCellData(value))
1550
+ continue;
1551
+ let rowIndex;
1552
+ let columnIndex;
1553
+ try {
1554
+ ({ rowIndex, columnIndex } = parseCellReference(reference));
1555
+ } catch {
1556
+ continue;
1557
+ }
1558
+ if ((isVertical ? columnIndex : rowIndex) === fixedAxis) {
1559
+ occupied.add(isVertical ? rowIndex : columnIndex);
1560
+ }
1561
+ }
1562
+ const step = movingForward ? 1 : -1;
1563
+ let targetAxis;
1564
+ if (occupied.has(originAxis + step)) {
1565
+ targetAxis = originAxis + step;
1566
+ while (occupied.has(targetAxis + step))
1567
+ targetAxis += step;
1568
+ } else {
1569
+ const candidates = [...occupied].filter((index) => movingForward ? index > originAxis : index < originAxis);
1570
+ if (candidates.length > 0) {
1571
+ targetAxis = movingForward ? Math.min(...candidates) : Math.max(...candidates);
1572
+ }
1573
+ }
1574
+ if (targetAxis === undefined)
1575
+ return;
1576
+ return isVertical ? { row: targetAxis, col: request.origin.col } : { row: request.origin.row, col: targetAxis };
1577
+ };
1460
1578
  var CellComponent = import_react4.default.memo(({
1461
1579
  value,
1462
1580
  row,
@@ -1760,6 +1878,8 @@ var Spreadsheet = import_react4.forwardRef(({
1760
1878
  cellData: controlledCellData,
1761
1879
  onCellDataChange,
1762
1880
  onCellDataChangeError,
1881
+ rowCount,
1882
+ columnCount,
1763
1883
  columnWidths: controlledColumnWidths,
1764
1884
  onColumnWidthsChange,
1765
1885
  rowHeights: controlledRowHeights,
@@ -1781,14 +1901,35 @@ var Spreadsheet = import_react4.forwardRef(({
1781
1901
  const [containerDivRef, setContainerDivRef] = import_react4.useState(null);
1782
1902
  const colorScheme = useColorScheme(containerDivRef);
1783
1903
  const gridLineProbeRef = import_react4.useRef(null);
1904
+ const [internalCellData, setInternalCellData] = import_react4.useState(new Map);
1905
+ const [retryingEdit, setRetryingEdit] = import_react4.useState(null);
1906
+ const retryingEditVersionRef = import_react4.useRef(0);
1907
+ const cellData = controlledCellData ?? internalCellData;
1908
+ const cellDataRef = import_react4.useRef(cellData);
1909
+ cellDataRef.current = cellData;
1910
+ const finiteRowCount = normalizeFiniteCount(rowCount);
1911
+ const finiteColumnCount = normalizeFiniteCount(columnCount);
1912
+ const gridCountsRef = import_react4.useRef({ rows: finiteRowCount, columns: finiteColumnCount });
1913
+ gridCountsRef.current = { rows: finiteRowCount, columns: finiteColumnCount };
1914
+ const getNumRows = import_react4.useCallback(() => countToSelectionBound(gridCountsRef.current.rows), []);
1915
+ const getNumCols = import_react4.useCallback(() => countToSelectionBound(gridCountsRef.current.columns), []);
1916
+ const getDefaultUsedRange = import_react4.useCallback(() => getSparseUsedRange(cellDataRef.current, gridCountsRef.current.rows, gridCountsRef.current.columns), []);
1917
+ const defaultResolveTarget = import_react4.useCallback((request) => resolveSparseNavigationTarget(request, cellDataRef.current), []);
1918
+ const consumerNavigation = selection?.navigation;
1919
+ const navigation = import_react4.useMemo(() => ({
1920
+ getUsedRange: consumerNavigation?.getUsedRange ?? getDefaultUsedRange,
1921
+ ...consumerNavigation?.getTableAt ? { getTableAt: consumerNavigation.getTableAt } : {},
1922
+ resolveTarget: consumerNavigation?.resolveTarget ?? defaultResolveTarget
1923
+ }), [consumerNavigation, defaultResolveTarget, getDefaultUsedRange]);
1784
1924
  const selectionManager = import_selection_manager.useInitializeSelectionManager({
1785
- getNumCols: () => ({ type: "infinity" }),
1786
- getNumRows: () => ({ type: "infinity" }),
1925
+ getNumCols,
1926
+ getNumRows,
1787
1927
  initialState: selection?.initialState,
1788
1928
  state: selection?.state,
1789
1929
  onStateChange: selection?.onStateChange,
1790
1930
  containerElement: containerDivRef,
1791
1931
  formats: selection?.formats,
1932
+ navigation,
1792
1933
  disableAutoClipboard: true
1793
1934
  });
1794
1935
  const effects = selection?.effects;
@@ -1797,12 +1938,6 @@ var Spreadsheet = import_react4.forwardRef(({
1797
1938
  return effects(selectionManager);
1798
1939
  }
1799
1940
  }, [selectionManager, effects]);
1800
- const [internalCellData, setInternalCellData] = import_react4.useState(new Map);
1801
- const [retryingEdit, setRetryingEdit] = import_react4.useState(null);
1802
- const retryingEditVersionRef = import_react4.useRef(0);
1803
- const cellData = controlledCellData ?? internalCellData;
1804
- const cellDataRef = import_react4.useRef(cellData);
1805
- cellDataRef.current = cellData;
1806
1941
  const isControlled = controlledCellData !== undefined;
1807
1942
  const prevIsControlledRef = import_react4.useRef(isControlled);
1808
1943
  selectionManager.getCellsWithData = (area) => {
@@ -1978,7 +2113,7 @@ var Spreadsheet = import_react4.forwardRef(({
1978
2113
  ctx.lineWidth = 1;
1979
2114
  let x = gridDims.headerWidth;
1980
2115
  let colIndex = 0;
1981
- while (x < viewport.scrollX + gridDims.headerWidth) {
2116
+ while (x < viewport.scrollX + gridDims.headerWidth && (finiteColumnCount === undefined || colIndex < finiteColumnCount)) {
1982
2117
  const colWidth = (columnWidths.get(indexToColumn(colIndex)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1983
2118
  if (x + colWidth > viewport.scrollX + gridDims.headerWidth) {
1984
2119
  break;
@@ -1986,7 +2121,7 @@ var Spreadsheet = import_react4.forwardRef(({
1986
2121
  x += colWidth;
1987
2122
  colIndex++;
1988
2123
  }
1989
- while (x - viewport.scrollX < width) {
2124
+ while (x - viewport.scrollX < width && (finiteColumnCount === undefined || colIndex < finiteColumnCount)) {
1990
2125
  const drawX = x - viewport.scrollX;
1991
2126
  if (drawX >= gridDims.headerWidth && drawX <= width) {
1992
2127
  ctx.beginPath();
@@ -2000,7 +2135,7 @@ var Spreadsheet = import_react4.forwardRef(({
2000
2135
  }
2001
2136
  let y = gridDims.headerHeight;
2002
2137
  let rowNum = 1;
2003
- while (y < viewport.scrollY + gridDims.headerHeight) {
2138
+ while (y < viewport.scrollY + gridDims.headerHeight && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2004
2139
  const rowHeight = (rowHeights.get(rowNum) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
2005
2140
  if (y + rowHeight > viewport.scrollY + gridDims.headerHeight) {
2006
2141
  break;
@@ -2008,7 +2143,7 @@ var Spreadsheet = import_react4.forwardRef(({
2008
2143
  y += rowHeight;
2009
2144
  rowNum++;
2010
2145
  }
2011
- while (y - viewport.scrollY < height) {
2146
+ while (y - viewport.scrollY < height && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2012
2147
  const drawY = y - viewport.scrollY;
2013
2148
  if (drawY >= gridDims.headerHeight && drawY <= height) {
2014
2149
  ctx.beginPath();
@@ -2022,6 +2157,8 @@ var Spreadsheet = import_react4.forwardRef(({
2022
2157
  }
2023
2158
  }, [
2024
2159
  columnWidths,
2160
+ finiteColumnCount,
2161
+ finiteRowCount,
2025
2162
  gridDims.headerHeight,
2026
2163
  gridDims.headerWidth,
2027
2164
  height,
@@ -2039,7 +2176,7 @@ var Spreadsheet = import_react4.forwardRef(({
2039
2176
  const cells = [];
2040
2177
  let x = 0;
2041
2178
  let startCol = 0;
2042
- while (x < viewport.scrollX) {
2179
+ while (x < viewport.scrollX && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2043
2180
  const colWidth = (columnWidths.get(indexToColumn(startCol)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
2044
2181
  if (x + colWidth > viewport.scrollX) {
2045
2182
  break;
@@ -2049,7 +2186,7 @@ var Spreadsheet = import_react4.forwardRef(({
2049
2186
  }
2050
2187
  let y = 0;
2051
2188
  let startRow = 0;
2052
- while (y < viewport.scrollY) {
2189
+ while (y < viewport.scrollY && (finiteRowCount === undefined || startRow < finiteRowCount)) {
2053
2190
  const rowHeight = (rowHeights.get(startRow + 1) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
2054
2191
  if (y + rowHeight > viewport.scrollY) {
2055
2192
  break;
@@ -2059,10 +2196,10 @@ var Spreadsheet = import_react4.forwardRef(({
2059
2196
  }
2060
2197
  let rowY = y;
2061
2198
  let row = startRow;
2062
- while (rowY < viewport.scrollY + height) {
2199
+ while (rowY < viewport.scrollY + height && (finiteRowCount === undefined || row < finiteRowCount)) {
2063
2200
  let colX = x;
2064
2201
  let col = startCol;
2065
- while (colX < viewport.scrollX + width) {
2202
+ while (colX < viewport.scrollX + width && (finiteColumnCount === undefined || col < finiteColumnCount)) {
2066
2203
  const colKey = indexToColumn(col);
2067
2204
  const rowNum = getRowNumber(row);
2068
2205
  const cellKey = `${colKey}${rowNum}`;
@@ -2083,6 +2220,8 @@ var Spreadsheet = import_react4.forwardRef(({
2083
2220
  }, [
2084
2221
  cellData,
2085
2222
  columnWidths,
2223
+ finiteColumnCount,
2224
+ finiteRowCount,
2086
2225
  height,
2087
2226
  rowHeights,
2088
2227
  viewport.scrollX,
@@ -2110,6 +2249,79 @@ var Spreadsheet = import_react4.forwardRef(({
2110
2249
  }
2111
2250
  return { x, y };
2112
2251
  }, [columnWidths, rowHeights]);
2252
+ const clampViewportOffsets = import_react4.useCallback((scrollX, scrollY, zoom) => {
2253
+ const bodyWidth = Math.max(0, width - HEADER_WIDTH * zoom);
2254
+ const bodyHeight = Math.max(0, height - HEADER_HEIGHT * zoom);
2255
+ const maxScrollX = finiteColumnCount === undefined ? Infinity : Math.max(0, getColumnEdgePosition(finiteColumnCount, columnWidths) * zoom - bodyWidth);
2256
+ const maxScrollY = finiteRowCount === undefined ? Infinity : Math.max(0, getRowEdgePosition(finiteRowCount, rowHeights) * zoom - bodyHeight);
2257
+ return {
2258
+ scrollX: Math.min(Math.max(0, scrollX), maxScrollX),
2259
+ scrollY: Math.min(Math.max(0, scrollY), maxScrollY)
2260
+ };
2261
+ }, [columnWidths, finiteColumnCount, finiteRowCount, height, rowHeights, width]);
2262
+ const revealRange = import_react4.useCallback((range, options = {}) => {
2263
+ if (finiteRowCount === 0 || finiteColumnCount === 0)
2264
+ return;
2265
+ const rows = normalizeScrollRangeAxis(range.start.row, range.end.row, finiteRowCount);
2266
+ const columns = normalizeScrollRangeAxis(range.start.col, range.end.col, finiteColumnCount);
2267
+ if (!rows || !columns)
2268
+ return;
2269
+ setViewport((previous) => {
2270
+ const bodyWidth = Math.max(0, width - HEADER_WIDTH * previous.zoom);
2271
+ const bodyHeight = Math.max(0, height - HEADER_HEIGHT * previous.zoom);
2272
+ const left = getColumnEdgePosition(columns.start, columnWidths) * previous.zoom;
2273
+ const right = getColumnEdgePosition(columns.end + 1, columnWidths) * previous.zoom;
2274
+ const top = getRowEdgePosition(rows.start, rowHeights) * previous.zoom;
2275
+ const bottom = getRowEdgePosition(rows.end + 1, rowHeights) * previous.zoom;
2276
+ const desired = clampViewportOffsets(alignScrollOffset(previous.scrollX, left, right, bodyWidth, options.columnAlign ?? "nearest"), alignScrollOffset(previous.scrollY, top, bottom, bodyHeight, options.rowAlign ?? "nearest"), previous.zoom);
2277
+ if (desired.scrollX === previous.scrollX && desired.scrollY === previous.scrollY) {
2278
+ return previous;
2279
+ }
2280
+ return { ...previous, ...desired };
2281
+ });
2282
+ }, [
2283
+ clampViewportOffsets,
2284
+ columnWidths,
2285
+ finiteColumnCount,
2286
+ finiteRowCount,
2287
+ height,
2288
+ rowHeights,
2289
+ width
2290
+ ]);
2291
+ const revealCell = import_react4.useCallback((cell, options = {}) => {
2292
+ revealRange({
2293
+ start: { row: cell.row, col: cell.col },
2294
+ end: {
2295
+ row: { type: "number", value: cell.row },
2296
+ col: { type: "number", value: cell.col }
2297
+ }
2298
+ }, options);
2299
+ }, [revealRange]);
2300
+ const scrollToCell = import_react4.useCallback((cell, options = {}) => {
2301
+ const align = options.align ?? "nearest";
2302
+ revealCell(cell, { rowAlign: align, columnAlign: align });
2303
+ }, [revealCell]);
2304
+ const scrollToRange = import_react4.useCallback((range, options = {}) => {
2305
+ const align = options.align ?? "nearest";
2306
+ revealRange(range, { rowAlign: align, columnAlign: align });
2307
+ }, [revealRange]);
2308
+ import_react4.useEffect(() => selectionManager.listenToViewportRequest((request) => {
2309
+ if (request.type === "reveal-range") {
2310
+ scrollToRange(request.range, { align: request.align });
2311
+ return;
2312
+ }
2313
+ const isVertical = request.direction === "up" || request.direction === "down";
2314
+ revealCell(request.cell, {
2315
+ rowAlign: isVertical ? request.align : "nearest",
2316
+ columnAlign: isVertical ? "nearest" : request.align
2317
+ });
2318
+ }), [revealCell, scrollToRange, selectionManager]);
2319
+ import_react4.useEffect(() => {
2320
+ setViewport((previous) => {
2321
+ const next = clampViewportOffsets(previous.scrollX, previous.scrollY, previous.zoom);
2322
+ return next.scrollX === previous.scrollX && next.scrollY === previous.scrollY ? previous : { ...previous, ...next };
2323
+ });
2324
+ }, [clampViewportOffsets]);
2113
2325
  import_react4.default.useEffect(() => {
2114
2326
  return selectionManager.listenToUpdateData((updates) => {
2115
2327
  const editingState = selectionManager.isEditing;
@@ -2184,12 +2396,11 @@ var Spreadsheet = import_react4.forwardRef(({
2184
2396
  const handleWheel = import_react4.useCallback((e) => {
2185
2397
  e.preventDefault();
2186
2398
  e.stopPropagation();
2187
- setViewport((prev) => ({
2188
- ...prev,
2189
- scrollX: Math.max(0, prev.scrollX + e.deltaX),
2190
- scrollY: Math.max(0, prev.scrollY + e.deltaY)
2399
+ setViewport((previous) => ({
2400
+ ...previous,
2401
+ ...clampViewportOffsets(previous.scrollX + e.deltaX, previous.scrollY + e.deltaY, previous.zoom)
2191
2402
  }));
2192
- }, []);
2403
+ }, [clampViewportOffsets]);
2193
2404
  const commitOverlayChildren = import_react4.useCallback((children) => {
2194
2405
  overlayChildrenRef.current = children;
2195
2406
  if (onOverlayChildrenChange) {
@@ -2608,7 +2819,7 @@ var Spreadsheet = import_react4.forwardRef(({
2608
2819
  const headers = [];
2609
2820
  let x = 0;
2610
2821
  let startCol = 0;
2611
- while (x < viewport.scrollX) {
2822
+ while (x < viewport.scrollX && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2612
2823
  const col = indexToColumn(startCol);
2613
2824
  const colWidth = getColumnWidth(col) * viewport.zoom;
2614
2825
  if (x + colWidth > viewport.scrollX) {
@@ -2617,7 +2828,7 @@ var Spreadsheet = import_react4.forwardRef(({
2617
2828
  x += colWidth;
2618
2829
  startCol++;
2619
2830
  }
2620
- while (x < viewport.scrollX + width) {
2831
+ while (x < viewport.scrollX + width && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2621
2832
  const col = indexToColumn(startCol);
2622
2833
  const colWidth = getColumnWidth(col) * viewport.zoom;
2623
2834
  headers.push({
@@ -2628,14 +2839,14 @@ var Spreadsheet = import_react4.forwardRef(({
2628
2839
  startCol++;
2629
2840
  }
2630
2841
  return headers;
2631
- }, [width, viewport, getColumnWidth]);
2842
+ }, [finiteColumnCount, width, viewport, getColumnWidth]);
2632
2843
  const getVisibleRowHeaders = import_react4.useCallback(() => {
2633
2844
  if (!height)
2634
2845
  return [];
2635
2846
  const headers = [];
2636
2847
  let y = 0;
2637
2848
  let rowNum = 1;
2638
- while (y < viewport.scrollY) {
2849
+ while (y < viewport.scrollY && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2639
2850
  const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2640
2851
  if (y + rowHeight > viewport.scrollY) {
2641
2852
  break;
@@ -2643,7 +2854,7 @@ var Spreadsheet = import_react4.forwardRef(({
2643
2854
  y += rowHeight;
2644
2855
  rowNum++;
2645
2856
  }
2646
- while (y < viewport.scrollY + height) {
2857
+ while (y < viewport.scrollY + height && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2647
2858
  const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2648
2859
  headers.push({
2649
2860
  row: rowNum,
@@ -2653,7 +2864,7 @@ var Spreadsheet = import_react4.forwardRef(({
2653
2864
  rowNum++;
2654
2865
  }
2655
2866
  return headers;
2656
- }, [height, viewport, getRowHeight]);
2867
+ }, [finiteRowCount, height, viewport, getRowHeight]);
2657
2868
  const { visibleOverlays, overlaysToRender } = import_react4.useMemo(() => {
2658
2869
  if (!width || !height || !overlayChildren) {
2659
2870
  return { visibleOverlays: [], overlaysToRender: [] };
@@ -2701,8 +2912,45 @@ var Spreadsheet = import_react4.forwardRef(({
2701
2912
  const visibleColumnHeaders = import_react4.default.useMemo(() => getVisibleColumnHeaders(), [getVisibleColumnHeaders]);
2702
2913
  const visibleRowHeaders = import_react4.default.useMemo(() => getVisibleRowHeaders(), [getVisibleRowHeaders]);
2703
2914
  const hasFocus = import_selection_manager.useSelectionManager(selectionManager, () => selectionManager.hasFocus);
2915
+ const referenceSelection = import_selection_manager.useSelectionManager(selectionManager, () => selectionManager.referenceSelection);
2916
+ const referenceSelectionRect = import_react4.useMemo(() => {
2917
+ if (referenceSelection.type === "none")
2918
+ return;
2919
+ if (finiteRowCount === 0 || finiteColumnCount === 0)
2920
+ return;
2921
+ const range = referenceSelection.range;
2922
+ const lastVisibleRow = visibleCells.reduce((maximum, cell) => Math.max(maximum, cell.row - 1), range.start.row);
2923
+ const lastVisibleCol = visibleCells.reduce((maximum, cell) => Math.max(maximum, columnToIndex(cell.col)), range.start.col);
2924
+ const rawEndRow = range.end.row.type === "number" ? range.end.row.value : finiteRowCount === undefined ? lastVisibleRow : finiteRowCount - 1;
2925
+ const rawEndCol = range.end.col.type === "number" ? range.end.col.value : finiteColumnCount === undefined ? lastVisibleCol : finiteColumnCount - 1;
2926
+ const maxRow = finiteRowCount === undefined ? Infinity : finiteRowCount - 1;
2927
+ const maxCol = finiteColumnCount === undefined ? Infinity : finiteColumnCount - 1;
2928
+ const startRow = Math.min(Math.max(0, Math.min(range.start.row, rawEndRow)), maxRow);
2929
+ const endRow = Math.min(Math.max(0, Math.max(range.start.row, rawEndRow)), maxRow);
2930
+ const startCol = Math.min(Math.max(0, Math.min(range.start.col, rawEndCol)), maxCol);
2931
+ const endCol = Math.min(Math.max(0, Math.max(range.start.col, rawEndCol)), maxCol);
2932
+ const rect = getRectFromCellSnapAnchor({
2933
+ startCol,
2934
+ startRow,
2935
+ endCol: endCol + 1,
2936
+ endRow: endRow + 1
2937
+ }, columnWidths, rowHeights);
2938
+ return {
2939
+ left: HEADER_WIDTH + rect.x,
2940
+ top: HEADER_HEIGHT + rect.y,
2941
+ width: rect.width,
2942
+ height: rect.height
2943
+ };
2944
+ }, [
2945
+ columnWidths,
2946
+ finiteColumnCount,
2947
+ finiteRowCount,
2948
+ referenceSelection,
2949
+ rowHeights,
2950
+ visibleCells
2951
+ ]);
2704
2952
  import_react4.default.useEffect(() => {
2705
- if (!containerDivRef || !hasFocus || !parentSelected) {
2953
+ if (!containerDivRef || !parentSelected) {
2706
2954
  return;
2707
2955
  }
2708
2956
  containerDivRef.addEventListener("wheel", handleWheel, {
@@ -2711,15 +2959,17 @@ var Spreadsheet = import_react4.forwardRef(({
2711
2959
  return () => {
2712
2960
  containerDivRef.removeEventListener("wheel", handleWheel);
2713
2961
  };
2714
- }, [containerDivRef, handleWheel, hasFocus, parentSelected]);
2962
+ }, [containerDivRef, handleWheel, parentSelected]);
2715
2963
  import_react4.useImperativeHandle(ref, () => ({
2716
2964
  focus: () => {
2717
2965
  selectionManager.focus();
2718
2966
  },
2719
2967
  blur: () => {
2720
2968
  selectionManager.blur();
2721
- }
2722
- }), [selectionManager]);
2969
+ },
2970
+ scrollToCell,
2971
+ scrollToRange
2972
+ }), [scrollToCell, scrollToRange, selectionManager]);
2723
2973
  return /* @__PURE__ */ jsx_runtime3.jsx("div", {
2724
2974
  className: cn("rsp-root", className),
2725
2975
  "data-rsp-scheme": colorScheme,
@@ -2823,28 +3073,38 @@ var Spreadsheet = import_react4.forwardRef(({
2823
3073
  zIndex: 40
2824
3074
  }
2825
3075
  }),
2826
- /* @__PURE__ */ jsx_runtime3.jsx("div", {
3076
+ /* @__PURE__ */ jsx_runtime3.jsxs("div", {
2827
3077
  className: "rsp-cells",
2828
3078
  "data-testid": "spreadsheet-cells",
2829
3079
  style: {
2830
3080
  transform: `translate(${-viewport.scrollX}px, ${-viewport.scrollY}px) scale(${viewport.zoom})`,
2831
3081
  transformOrigin: "0 0"
2832
3082
  },
2833
- children: visibleCells.map((cell) => /* @__PURE__ */ jsx_runtime3.jsx(CellComponent, {
2834
- value: cellData.get(`${cell.col}${cell.row}`) ?? "",
2835
- row: cell.row,
2836
- col: cell.col,
2837
- selectionManager,
2838
- cellToPixelBase,
2839
- getColumnWidth,
2840
- getRowHeight,
2841
- customCellStyle,
2842
- customCellRenderer,
2843
- inputValue: cellData.get(`${cell.col}${cell.row}`) ?? "",
2844
- editInputVersion: retryingEdit?.rowIndex === cell.row - 1 && retryingEdit?.colIndex === columnToIndex(cell.col) ? retryingEdit.version : 0,
2845
- width,
2846
- height
2847
- }, `${cell.col}${cell.row}`))
3083
+ children: [
3084
+ visibleCells.map((cell) => /* @__PURE__ */ jsx_runtime3.jsx(CellComponent, {
3085
+ value: cellData.get(`${cell.col}${cell.row}`) ?? "",
3086
+ row: cell.row,
3087
+ col: cell.col,
3088
+ selectionManager,
3089
+ cellToPixelBase,
3090
+ getColumnWidth,
3091
+ getRowHeight,
3092
+ customCellStyle,
3093
+ customCellRenderer,
3094
+ inputValue: cellData.get(`${cell.col}${cell.row}`) ?? "",
3095
+ editInputVersion: retryingEdit?.rowIndex === cell.row - 1 && retryingEdit?.colIndex === columnToIndex(cell.col) ? retryingEdit.version : 0,
3096
+ width,
3097
+ height
3098
+ }, `${cell.col}${cell.row}`)),
3099
+ referenceSelectionRect && referenceSelection.type !== "none" && /* @__PURE__ */ jsx_runtime3.jsx("div", {
3100
+ "aria-hidden": "true",
3101
+ className: cn("rsp-reference-selection", `rsp-reference-selection-${referenceSelection.type}`),
3102
+ "data-testid": "spreadsheet-reference-selection",
3103
+ "data-reference-id": referenceSelection.id,
3104
+ "data-reference-phase": referenceSelection.type,
3105
+ style: referenceSelectionRect
3106
+ })
3107
+ ]
2848
3108
  }),
2849
3109
  components && overlayChildren && overlayChildren.length > 0 && /* @__PURE__ */ jsx_runtime3.jsxs("div", {
2850
3110
  ref: overlayContainerRef,
@@ -3054,6 +3314,7 @@ class WorkbookSelectionManager {
3054
3314
  lastFocusedManager = null;
3055
3315
  listeners = new Set;
3056
3316
  cleanupFunctions = new Map;
3317
+ pendingRangeFocus = null;
3057
3318
  smAreaToSpreadsheetRange(area) {
3058
3319
  const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
3059
3320
  const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
@@ -3068,6 +3329,47 @@ class WorkbookSelectionManager {
3068
3329
  }
3069
3330
  };
3070
3331
  }
3332
+ spreadsheetRangeToSmArea(range) {
3333
+ return {
3334
+ start: { row: range.start.row, col: range.start.col },
3335
+ end: {
3336
+ row: range.end.row.type === "infinity" ? { type: "infinity" } : { type: "number", value: range.end.row.value },
3337
+ col: range.end.col.type === "infinity" ? { type: "infinity" } : { type: "number", value: range.end.col.value }
3338
+ }
3339
+ };
3340
+ }
3341
+ cloneRangeAddress(address) {
3342
+ return {
3343
+ workbookName: address.workbookName,
3344
+ sheetName: address.sheetName,
3345
+ range: this.smAreaToSpreadsheetRange(this.spreadsheetRangeToSmArea(address.range))
3346
+ };
3347
+ }
3348
+ matchesContext(address, context) {
3349
+ return address.workbookName === context.workbookName && address.sheetName === context.sheetName;
3350
+ }
3351
+ applyRangeFocus(manager, context, request) {
3352
+ const selection = this.spreadsheetRangeToSmArea(request.address.range);
3353
+ manager.setState({ selections: [selection] });
3354
+ manager.focus();
3355
+ manager.revealRange(selection, request.options);
3356
+ this.lastFocusedManager = { sm: manager, context };
3357
+ this.emitChange();
3358
+ }
3359
+ applyPendingRangeFocusOnMount(manager, context) {
3360
+ const pending = this.pendingRangeFocus;
3361
+ if (!pending || !this.matchesContext(pending.address, context))
3362
+ return;
3363
+ queueMicrotask(() => {
3364
+ if (this.pendingRangeFocus !== pending)
3365
+ return;
3366
+ const mountedContext = this.managers.get(manager);
3367
+ if (!mountedContext || !this.matchesContext(pending.address, mountedContext))
3368
+ return;
3369
+ this.pendingRangeFocus = null;
3370
+ this.applyRangeFocus(manager, mountedContext, pending);
3371
+ });
3372
+ }
3071
3373
  add(manager, context) {
3072
3374
  this.managers.set(manager, context);
3073
3375
  const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
@@ -3093,6 +3395,7 @@ class WorkbookSelectionManager {
3093
3395
  }
3094
3396
  };
3095
3397
  this.cleanupFunctions.set(manager, cleanup);
3398
+ this.applyPendingRangeFocusOnMount(manager, context);
3096
3399
  return cleanup;
3097
3400
  }
3098
3401
  getLastFocusedSelectionManager() {
@@ -3186,22 +3489,31 @@ class WorkbookSelectionManager {
3186
3489
  return false;
3187
3490
  }
3188
3491
  focusCell(workbookName, sheetName, row, col) {
3189
- for (const [manager, context] of this.managers) {
3190
- if (context.workbookName === workbookName && context.sheetName === sheetName) {
3191
- const selection = {
3192
- start: { row, col },
3193
- end: {
3194
- row: { type: "number", value: row },
3195
- col: { type: "number", value: col }
3196
- }
3197
- };
3198
- manager.setState({ selections: [selection] });
3199
- manager.focus();
3200
- this.lastFocusedManager = { sm: manager, context };
3201
- this.emitChange();
3202
- return true;
3492
+ return this.focusRange({
3493
+ workbookName,
3494
+ sheetName,
3495
+ range: {
3496
+ start: { row, col },
3497
+ end: {
3498
+ row: { type: "number", value: row },
3499
+ col: { type: "number", value: col }
3500
+ }
3203
3501
  }
3502
+ });
3503
+ }
3504
+ focusRange(address, options = {}) {
3505
+ const request = {
3506
+ address: this.cloneRangeAddress(address),
3507
+ options: { ...options }
3508
+ };
3509
+ for (const [manager, context] of this.managers) {
3510
+ if (!this.matchesContext(request.address, context))
3511
+ continue;
3512
+ this.pendingRangeFocus = null;
3513
+ this.applyRangeFocus(manager, context, request);
3514
+ return true;
3204
3515
  }
3516
+ this.pendingRangeFocus = request;
3205
3517
  return false;
3206
3518
  }
3207
3519
  emitChange() {
@@ -3450,6 +3762,42 @@ function FormulaSheet({
3450
3762
  const clipboardManager = import_react6.default.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3451
3763
  const state = useEngine(engine);
3452
3764
  const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
3765
+ const getEngineTableAt = import_react6.useCallback((cell) => {
3766
+ const table = engine.isCellInTable({
3767
+ workbookName,
3768
+ sheetName,
3769
+ rowIndex: cell.row,
3770
+ colIndex: cell.col
3771
+ });
3772
+ if (!table)
3773
+ return;
3774
+ const endRow = table.endRow.type === "number" ? { type: "number", value: table.endRow.value } : { type: "infinity" };
3775
+ const endCol = {
3776
+ type: "number",
3777
+ value: table.start.colIndex + table.headers.size - 1
3778
+ };
3779
+ const hasDataRows = endRow.type === "infinity" || endRow.value > table.start.rowIndex;
3780
+ return {
3781
+ id: `${table.workbookName}/${table.name}`,
3782
+ bounds: {
3783
+ start: { row: table.start.rowIndex, col: table.start.colIndex },
3784
+ end: { row: endRow, col: endCol }
3785
+ },
3786
+ ...hasDataRows ? {
3787
+ dataBounds: {
3788
+ start: {
3789
+ row: table.start.rowIndex + 1,
3790
+ col: table.start.colIndex
3791
+ },
3792
+ end: { row: endRow, col: endCol }
3793
+ }
3794
+ } : {}
3795
+ };
3796
+ }, [engine, sheetName, workbookName]);
3797
+ const formulaNavigation = import_react6.useMemo(() => ({
3798
+ ...selection?.navigation,
3799
+ getTableAt: selection?.navigation?.getTableAt ?? getEngineTableAt
3800
+ }), [getEngineTableAt, selection?.navigation]);
3453
3801
  const onCellDataChange = import_react6.useCallback((updatedSpreadsheet) => {
3454
3802
  const data = new Map(updatedSpreadsheet);
3455
3803
  data.forEach((value, key) => {
@@ -3623,6 +3971,7 @@ function FormulaSheet({
3623
3971
  parentSelected: isSelected,
3624
3972
  selection: {
3625
3973
  ...selection,
3974
+ navigation: formulaNavigation,
3626
3975
  effects: selectionManagerEffects
3627
3976
  },
3628
3977
  containerProps: isSelected && selectedArea ? {
@@ -4161,4 +4510,4 @@ var useClickAway = (ref, callback) => {
4161
4510
  }, [ref, callback]);
4162
4511
  };
4163
4512
 
4164
- //# debugId=4874F9DF8699906864756E2164756E21
4513
+ //# debugId=81701030D90BCDFB64756E2164756E21