@ricsam/react-spreadsheets 0.0.7 → 0.0.8

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/README.md CHANGED
@@ -138,6 +138,68 @@ keyboard and edit selection state independently. For `FormulaWorkbook`, pass a
138
138
  controlled `activeSheet` if the editor should reveal a reference on another
139
139
  sheet; the same `customCellStyle` prop is forwarded to its active sheet.
140
140
 
141
+ For the range currently being inserted into a formula, use selection-manager's
142
+ reference-picking mode instead. It preserves the primary/editing selection and
143
+ the grid renders the picked range with an independent animated border:
144
+
145
+ ```tsx
146
+ <Spreadsheet
147
+ rowCount={1_000}
148
+ columnCount={50}
149
+ selection={{
150
+ effects(manager) {
151
+ const stop = manager.listenToReferenceSelection((event) => {
152
+ if (event.phase === "start" || event.phase === "change") {
153
+ updateFormulaReference(event.range);
154
+ }
155
+ });
156
+
157
+ manager.beginReferenceSelection({
158
+ editedRange: {
159
+ start: { row: 1, col: 3 },
160
+ end: {
161
+ row: { type: "number", value: 1 },
162
+ col: { type: "number", value: 3 },
163
+ },
164
+ },
165
+ });
166
+ return () => {
167
+ stop();
168
+ manager.endReferenceSelection();
169
+ };
170
+ },
171
+ }}
172
+ />
173
+ ```
174
+
175
+ ### Data-aware keyboard navigation
176
+
177
+ `Cmd/Ctrl + Arrow` jumps through populated cells and reveals the destination;
178
+ adding Shift extends the primary selection. Plain `Spreadsheet` derives its
179
+ used range and jump targets from non-empty `cellData`. `FormulaSheet` also
180
+ supplies table bounds from the formula engine, so navigation inside a table
181
+ respects its data body.
182
+
183
+ Override any part of that behavior through `selection.navigation`:
184
+
185
+ ```tsx
186
+ <Spreadsheet
187
+ rowCount={100_000}
188
+ columnCount={200}
189
+ selection={{
190
+ navigation: {
191
+ getUsedRange: () => model.usedRange,
192
+ getTableAt: (cell) => model.tableAt(cell),
193
+ resolveTarget: (request) => model.resolveJump(request),
194
+ },
195
+ }}
196
+ />
197
+ ```
198
+
199
+ The imperative `SpreadsheetRef.scrollToCell({ row, col }, { align })` reveals a
200
+ cell without changing selection. Alignment is `"nearest"` by default, with
201
+ `"start"` and `"end"` available for explicit positioning.
202
+
141
203
  ## Theming
142
204
 
143
205
  All colors, fonts and sizes are CSS custom properties. The package only ever
@@ -227,11 +289,12 @@ Per-cell styling is done in JS:
227
289
  | --- | --- |
228
290
  | `cellData` | `Map<string, SerializedCellValue>` keyed by A1 reference. Omit for uncontrolled mode. |
229
291
  | `onCellDataChange` | Called with the next map after an edit, paste or fill. |
292
+ | `rowCount` / `columnCount` | Finite grid dimensions. Omit either dimension for an unbounded axis. |
230
293
  | `columnWidths` / `rowHeights` | Controlled sizing, keyed by column letter / 1-based row. |
231
294
  | `customCellStyle` | Per-cell `CSSProperties`. |
232
295
  | `customCellRenderer` | Per-cell React node. |
233
296
  | `parseValue` | Coerce raw editor strings (e.g. text → number) before storing. |
234
- | `selection` | Selection state, callbacks and `effects(selectionManager)` for copy/paste/fill hooks. |
297
+ | `selection` | Selection state, navigation model, and `effects(selectionManager)` for copy/paste/fill/reference hooks. |
235
298
  | `components` + `overlayChildren` | Floating overlays anchored to the grid, optionally snapped to cell edges. |
236
299
 
237
300
  ## Development
package/dist/cjs/lib.cjs CHANGED
@@ -1457,6 +1457,99 @@ 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 (itemStart < current)
1476
+ return itemStart;
1477
+ if (itemEnd > current + viewportSize)
1478
+ return itemEnd - viewportSize;
1479
+ return current;
1480
+ };
1481
+ var hasCellData = (value) => value !== undefined && value !== "";
1482
+ var getSparseUsedRange = (cellData, rowCount, columnCount) => {
1483
+ let minRow = Infinity;
1484
+ let maxRow = -1;
1485
+ let minCol = Infinity;
1486
+ let maxCol = -1;
1487
+ for (const [reference, value] of cellData) {
1488
+ if (!hasCellData(value))
1489
+ continue;
1490
+ let rowIndex;
1491
+ let columnIndex;
1492
+ try {
1493
+ ({ rowIndex, columnIndex } = parseCellReference(reference));
1494
+ } catch {
1495
+ continue;
1496
+ }
1497
+ if (rowIndex < 0 || columnIndex < 0 || rowCount !== undefined && rowIndex >= rowCount || columnCount !== undefined && columnIndex >= columnCount) {
1498
+ continue;
1499
+ }
1500
+ minRow = Math.min(minRow, rowIndex);
1501
+ maxRow = Math.max(maxRow, rowIndex);
1502
+ minCol = Math.min(minCol, columnIndex);
1503
+ maxCol = Math.max(maxCol, columnIndex);
1504
+ }
1505
+ if (maxRow < 0 || maxCol < 0)
1506
+ return;
1507
+ return {
1508
+ start: { row: minRow, col: minCol },
1509
+ end: {
1510
+ row: { type: "number", value: maxRow },
1511
+ col: { type: "number", value: maxCol }
1512
+ }
1513
+ };
1514
+ };
1515
+ var resolveSparseNavigationTarget = (request, cellData) => {
1516
+ if (request.kind !== "jump" || request.table)
1517
+ return;
1518
+ const isVertical = request.direction === "up" || request.direction === "down";
1519
+ const movingForward = request.direction === "down" || request.direction === "right";
1520
+ const originAxis = isVertical ? request.origin.row : request.origin.col;
1521
+ const fixedAxis = isVertical ? request.origin.col : request.origin.row;
1522
+ const occupied = new Set;
1523
+ for (const [reference, value] of cellData) {
1524
+ if (!hasCellData(value))
1525
+ continue;
1526
+ let rowIndex;
1527
+ let columnIndex;
1528
+ try {
1529
+ ({ rowIndex, columnIndex } = parseCellReference(reference));
1530
+ } catch {
1531
+ continue;
1532
+ }
1533
+ if ((isVertical ? columnIndex : rowIndex) === fixedAxis) {
1534
+ occupied.add(isVertical ? rowIndex : columnIndex);
1535
+ }
1536
+ }
1537
+ const step = movingForward ? 1 : -1;
1538
+ let targetAxis;
1539
+ if (occupied.has(originAxis + step)) {
1540
+ targetAxis = originAxis + step;
1541
+ while (occupied.has(targetAxis + step))
1542
+ targetAxis += step;
1543
+ } else {
1544
+ const candidates = [...occupied].filter((index) => movingForward ? index > originAxis : index < originAxis);
1545
+ if (candidates.length > 0) {
1546
+ targetAxis = movingForward ? Math.min(...candidates) : Math.max(...candidates);
1547
+ }
1548
+ }
1549
+ if (targetAxis === undefined)
1550
+ return;
1551
+ return isVertical ? { row: targetAxis, col: request.origin.col } : { row: request.origin.row, col: targetAxis };
1552
+ };
1460
1553
  var CellComponent = import_react4.default.memo(({
1461
1554
  value,
1462
1555
  row,
@@ -1760,6 +1853,8 @@ var Spreadsheet = import_react4.forwardRef(({
1760
1853
  cellData: controlledCellData,
1761
1854
  onCellDataChange,
1762
1855
  onCellDataChangeError,
1856
+ rowCount,
1857
+ columnCount,
1763
1858
  columnWidths: controlledColumnWidths,
1764
1859
  onColumnWidthsChange,
1765
1860
  rowHeights: controlledRowHeights,
@@ -1781,14 +1876,35 @@ var Spreadsheet = import_react4.forwardRef(({
1781
1876
  const [containerDivRef, setContainerDivRef] = import_react4.useState(null);
1782
1877
  const colorScheme = useColorScheme(containerDivRef);
1783
1878
  const gridLineProbeRef = import_react4.useRef(null);
1879
+ const [internalCellData, setInternalCellData] = import_react4.useState(new Map);
1880
+ const [retryingEdit, setRetryingEdit] = import_react4.useState(null);
1881
+ const retryingEditVersionRef = import_react4.useRef(0);
1882
+ const cellData = controlledCellData ?? internalCellData;
1883
+ const cellDataRef = import_react4.useRef(cellData);
1884
+ cellDataRef.current = cellData;
1885
+ const finiteRowCount = normalizeFiniteCount(rowCount);
1886
+ const finiteColumnCount = normalizeFiniteCount(columnCount);
1887
+ const gridCountsRef = import_react4.useRef({ rows: finiteRowCount, columns: finiteColumnCount });
1888
+ gridCountsRef.current = { rows: finiteRowCount, columns: finiteColumnCount };
1889
+ const getNumRows = import_react4.useCallback(() => countToSelectionBound(gridCountsRef.current.rows), []);
1890
+ const getNumCols = import_react4.useCallback(() => countToSelectionBound(gridCountsRef.current.columns), []);
1891
+ const getDefaultUsedRange = import_react4.useCallback(() => getSparseUsedRange(cellDataRef.current, gridCountsRef.current.rows, gridCountsRef.current.columns), []);
1892
+ const defaultResolveTarget = import_react4.useCallback((request) => resolveSparseNavigationTarget(request, cellDataRef.current), []);
1893
+ const consumerNavigation = selection?.navigation;
1894
+ const navigation = import_react4.useMemo(() => ({
1895
+ getUsedRange: consumerNavigation?.getUsedRange ?? getDefaultUsedRange,
1896
+ ...consumerNavigation?.getTableAt ? { getTableAt: consumerNavigation.getTableAt } : {},
1897
+ resolveTarget: consumerNavigation?.resolveTarget ?? defaultResolveTarget
1898
+ }), [consumerNavigation, defaultResolveTarget, getDefaultUsedRange]);
1784
1899
  const selectionManager = import_selection_manager.useInitializeSelectionManager({
1785
- getNumCols: () => ({ type: "infinity" }),
1786
- getNumRows: () => ({ type: "infinity" }),
1900
+ getNumCols,
1901
+ getNumRows,
1787
1902
  initialState: selection?.initialState,
1788
1903
  state: selection?.state,
1789
1904
  onStateChange: selection?.onStateChange,
1790
1905
  containerElement: containerDivRef,
1791
1906
  formats: selection?.formats,
1907
+ navigation,
1792
1908
  disableAutoClipboard: true
1793
1909
  });
1794
1910
  const effects = selection?.effects;
@@ -1797,12 +1913,6 @@ var Spreadsheet = import_react4.forwardRef(({
1797
1913
  return effects(selectionManager);
1798
1914
  }
1799
1915
  }, [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
1916
  const isControlled = controlledCellData !== undefined;
1807
1917
  const prevIsControlledRef = import_react4.useRef(isControlled);
1808
1918
  selectionManager.getCellsWithData = (area) => {
@@ -1978,7 +2088,7 @@ var Spreadsheet = import_react4.forwardRef(({
1978
2088
  ctx.lineWidth = 1;
1979
2089
  let x = gridDims.headerWidth;
1980
2090
  let colIndex = 0;
1981
- while (x < viewport.scrollX + gridDims.headerWidth) {
2091
+ while (x < viewport.scrollX + gridDims.headerWidth && (finiteColumnCount === undefined || colIndex < finiteColumnCount)) {
1982
2092
  const colWidth = (columnWidths.get(indexToColumn(colIndex)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1983
2093
  if (x + colWidth > viewport.scrollX + gridDims.headerWidth) {
1984
2094
  break;
@@ -1986,7 +2096,7 @@ var Spreadsheet = import_react4.forwardRef(({
1986
2096
  x += colWidth;
1987
2097
  colIndex++;
1988
2098
  }
1989
- while (x - viewport.scrollX < width) {
2099
+ while (x - viewport.scrollX < width && (finiteColumnCount === undefined || colIndex < finiteColumnCount)) {
1990
2100
  const drawX = x - viewport.scrollX;
1991
2101
  if (drawX >= gridDims.headerWidth && drawX <= width) {
1992
2102
  ctx.beginPath();
@@ -2000,7 +2110,7 @@ var Spreadsheet = import_react4.forwardRef(({
2000
2110
  }
2001
2111
  let y = gridDims.headerHeight;
2002
2112
  let rowNum = 1;
2003
- while (y < viewport.scrollY + gridDims.headerHeight) {
2113
+ while (y < viewport.scrollY + gridDims.headerHeight && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2004
2114
  const rowHeight = (rowHeights.get(rowNum) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
2005
2115
  if (y + rowHeight > viewport.scrollY + gridDims.headerHeight) {
2006
2116
  break;
@@ -2008,7 +2118,7 @@ var Spreadsheet = import_react4.forwardRef(({
2008
2118
  y += rowHeight;
2009
2119
  rowNum++;
2010
2120
  }
2011
- while (y - viewport.scrollY < height) {
2121
+ while (y - viewport.scrollY < height && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2012
2122
  const drawY = y - viewport.scrollY;
2013
2123
  if (drawY >= gridDims.headerHeight && drawY <= height) {
2014
2124
  ctx.beginPath();
@@ -2022,6 +2132,8 @@ var Spreadsheet = import_react4.forwardRef(({
2022
2132
  }
2023
2133
  }, [
2024
2134
  columnWidths,
2135
+ finiteColumnCount,
2136
+ finiteRowCount,
2025
2137
  gridDims.headerHeight,
2026
2138
  gridDims.headerWidth,
2027
2139
  height,
@@ -2039,7 +2151,7 @@ var Spreadsheet = import_react4.forwardRef(({
2039
2151
  const cells = [];
2040
2152
  let x = 0;
2041
2153
  let startCol = 0;
2042
- while (x < viewport.scrollX) {
2154
+ while (x < viewport.scrollX && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2043
2155
  const colWidth = (columnWidths.get(indexToColumn(startCol)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
2044
2156
  if (x + colWidth > viewport.scrollX) {
2045
2157
  break;
@@ -2049,7 +2161,7 @@ var Spreadsheet = import_react4.forwardRef(({
2049
2161
  }
2050
2162
  let y = 0;
2051
2163
  let startRow = 0;
2052
- while (y < viewport.scrollY) {
2164
+ while (y < viewport.scrollY && (finiteRowCount === undefined || startRow < finiteRowCount)) {
2053
2165
  const rowHeight = (rowHeights.get(startRow + 1) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
2054
2166
  if (y + rowHeight > viewport.scrollY) {
2055
2167
  break;
@@ -2059,10 +2171,10 @@ var Spreadsheet = import_react4.forwardRef(({
2059
2171
  }
2060
2172
  let rowY = y;
2061
2173
  let row = startRow;
2062
- while (rowY < viewport.scrollY + height) {
2174
+ while (rowY < viewport.scrollY + height && (finiteRowCount === undefined || row < finiteRowCount)) {
2063
2175
  let colX = x;
2064
2176
  let col = startCol;
2065
- while (colX < viewport.scrollX + width) {
2177
+ while (colX < viewport.scrollX + width && (finiteColumnCount === undefined || col < finiteColumnCount)) {
2066
2178
  const colKey = indexToColumn(col);
2067
2179
  const rowNum = getRowNumber(row);
2068
2180
  const cellKey = `${colKey}${rowNum}`;
@@ -2083,6 +2195,8 @@ var Spreadsheet = import_react4.forwardRef(({
2083
2195
  }, [
2084
2196
  cellData,
2085
2197
  columnWidths,
2198
+ finiteColumnCount,
2199
+ finiteRowCount,
2086
2200
  height,
2087
2201
  rowHeights,
2088
2202
  viewport.scrollX,
@@ -2110,6 +2224,64 @@ var Spreadsheet = import_react4.forwardRef(({
2110
2224
  }
2111
2225
  return { x, y };
2112
2226
  }, [columnWidths, rowHeights]);
2227
+ const clampViewportOffsets = import_react4.useCallback((scrollX, scrollY, zoom) => {
2228
+ const bodyWidth = Math.max(0, width - HEADER_WIDTH * zoom);
2229
+ const bodyHeight = Math.max(0, height - HEADER_HEIGHT * zoom);
2230
+ const maxScrollX = finiteColumnCount === undefined ? Infinity : Math.max(0, getColumnEdgePosition(finiteColumnCount, columnWidths) * zoom - bodyWidth);
2231
+ const maxScrollY = finiteRowCount === undefined ? Infinity : Math.max(0, getRowEdgePosition(finiteRowCount, rowHeights) * zoom - bodyHeight);
2232
+ return {
2233
+ scrollX: Math.min(Math.max(0, scrollX), maxScrollX),
2234
+ scrollY: Math.min(Math.max(0, scrollY), maxScrollY)
2235
+ };
2236
+ }, [columnWidths, finiteColumnCount, finiteRowCount, height, rowHeights, width]);
2237
+ const revealCell = import_react4.useCallback((cell, options = {}) => {
2238
+ if (finiteRowCount === 0 || finiteColumnCount === 0)
2239
+ return;
2240
+ if (!Number.isFinite(cell.row) || !Number.isFinite(cell.col))
2241
+ return;
2242
+ const requestedRow = Math.max(0, Math.floor(cell.row));
2243
+ const requestedCol = Math.max(0, Math.floor(cell.col));
2244
+ const row = finiteRowCount === undefined ? requestedRow : Math.min(requestedRow, finiteRowCount - 1);
2245
+ const col = finiteColumnCount === undefined ? requestedCol : Math.min(requestedCol, finiteColumnCount - 1);
2246
+ setViewport((previous) => {
2247
+ const bodyWidth = Math.max(0, width - HEADER_WIDTH * previous.zoom);
2248
+ const bodyHeight = Math.max(0, height - HEADER_HEIGHT * previous.zoom);
2249
+ const left = getColumnEdgePosition(col, columnWidths) * previous.zoom;
2250
+ const right = getColumnEdgePosition(col + 1, columnWidths) * previous.zoom;
2251
+ const top = getRowEdgePosition(row, rowHeights) * previous.zoom;
2252
+ const bottom = getRowEdgePosition(row + 1, rowHeights) * previous.zoom;
2253
+ const desired = clampViewportOffsets(alignScrollOffset(previous.scrollX, left, right, bodyWidth, options.columnAlign ?? "nearest"), alignScrollOffset(previous.scrollY, top, bottom, bodyHeight, options.rowAlign ?? "nearest"), previous.zoom);
2254
+ if (desired.scrollX === previous.scrollX && desired.scrollY === previous.scrollY) {
2255
+ return previous;
2256
+ }
2257
+ return { ...previous, ...desired };
2258
+ });
2259
+ }, [
2260
+ clampViewportOffsets,
2261
+ columnWidths,
2262
+ finiteColumnCount,
2263
+ finiteRowCount,
2264
+ height,
2265
+ rowHeights,
2266
+ width
2267
+ ]);
2268
+ const scrollToCell = import_react4.useCallback((cell, options = {}) => {
2269
+ const align = options.align ?? "nearest";
2270
+ revealCell(cell, { rowAlign: align, columnAlign: align });
2271
+ }, [revealCell]);
2272
+ import_react4.useEffect(() => selectionManager.listenToViewportRequest((request) => {
2273
+ const isVertical = request.direction === "up" || request.direction === "down";
2274
+ revealCell(request.cell, {
2275
+ rowAlign: isVertical ? request.align : "nearest",
2276
+ columnAlign: isVertical ? "nearest" : request.align
2277
+ });
2278
+ }), [revealCell, selectionManager]);
2279
+ import_react4.useEffect(() => {
2280
+ setViewport((previous) => {
2281
+ const next = clampViewportOffsets(previous.scrollX, previous.scrollY, previous.zoom);
2282
+ return next.scrollX === previous.scrollX && next.scrollY === previous.scrollY ? previous : { ...previous, ...next };
2283
+ });
2284
+ }, [clampViewportOffsets]);
2113
2285
  import_react4.default.useEffect(() => {
2114
2286
  return selectionManager.listenToUpdateData((updates) => {
2115
2287
  const editingState = selectionManager.isEditing;
@@ -2184,12 +2356,11 @@ var Spreadsheet = import_react4.forwardRef(({
2184
2356
  const handleWheel = import_react4.useCallback((e) => {
2185
2357
  e.preventDefault();
2186
2358
  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)
2359
+ setViewport((previous) => ({
2360
+ ...previous,
2361
+ ...clampViewportOffsets(previous.scrollX + e.deltaX, previous.scrollY + e.deltaY, previous.zoom)
2191
2362
  }));
2192
- }, []);
2363
+ }, [clampViewportOffsets]);
2193
2364
  const commitOverlayChildren = import_react4.useCallback((children) => {
2194
2365
  overlayChildrenRef.current = children;
2195
2366
  if (onOverlayChildrenChange) {
@@ -2608,7 +2779,7 @@ var Spreadsheet = import_react4.forwardRef(({
2608
2779
  const headers = [];
2609
2780
  let x = 0;
2610
2781
  let startCol = 0;
2611
- while (x < viewport.scrollX) {
2782
+ while (x < viewport.scrollX && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2612
2783
  const col = indexToColumn(startCol);
2613
2784
  const colWidth = getColumnWidth(col) * viewport.zoom;
2614
2785
  if (x + colWidth > viewport.scrollX) {
@@ -2617,7 +2788,7 @@ var Spreadsheet = import_react4.forwardRef(({
2617
2788
  x += colWidth;
2618
2789
  startCol++;
2619
2790
  }
2620
- while (x < viewport.scrollX + width) {
2791
+ while (x < viewport.scrollX + width && (finiteColumnCount === undefined || startCol < finiteColumnCount)) {
2621
2792
  const col = indexToColumn(startCol);
2622
2793
  const colWidth = getColumnWidth(col) * viewport.zoom;
2623
2794
  headers.push({
@@ -2628,14 +2799,14 @@ var Spreadsheet = import_react4.forwardRef(({
2628
2799
  startCol++;
2629
2800
  }
2630
2801
  return headers;
2631
- }, [width, viewport, getColumnWidth]);
2802
+ }, [finiteColumnCount, width, viewport, getColumnWidth]);
2632
2803
  const getVisibleRowHeaders = import_react4.useCallback(() => {
2633
2804
  if (!height)
2634
2805
  return [];
2635
2806
  const headers = [];
2636
2807
  let y = 0;
2637
2808
  let rowNum = 1;
2638
- while (y < viewport.scrollY) {
2809
+ while (y < viewport.scrollY && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2639
2810
  const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2640
2811
  if (y + rowHeight > viewport.scrollY) {
2641
2812
  break;
@@ -2643,7 +2814,7 @@ var Spreadsheet = import_react4.forwardRef(({
2643
2814
  y += rowHeight;
2644
2815
  rowNum++;
2645
2816
  }
2646
- while (y < viewport.scrollY + height) {
2817
+ while (y < viewport.scrollY + height && (finiteRowCount === undefined || rowNum <= finiteRowCount)) {
2647
2818
  const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2648
2819
  headers.push({
2649
2820
  row: rowNum,
@@ -2653,7 +2824,7 @@ var Spreadsheet = import_react4.forwardRef(({
2653
2824
  rowNum++;
2654
2825
  }
2655
2826
  return headers;
2656
- }, [height, viewport, getRowHeight]);
2827
+ }, [finiteRowCount, height, viewport, getRowHeight]);
2657
2828
  const { visibleOverlays, overlaysToRender } = import_react4.useMemo(() => {
2658
2829
  if (!width || !height || !overlayChildren) {
2659
2830
  return { visibleOverlays: [], overlaysToRender: [] };
@@ -2701,6 +2872,43 @@ var Spreadsheet = import_react4.forwardRef(({
2701
2872
  const visibleColumnHeaders = import_react4.default.useMemo(() => getVisibleColumnHeaders(), [getVisibleColumnHeaders]);
2702
2873
  const visibleRowHeaders = import_react4.default.useMemo(() => getVisibleRowHeaders(), [getVisibleRowHeaders]);
2703
2874
  const hasFocus = import_selection_manager.useSelectionManager(selectionManager, () => selectionManager.hasFocus);
2875
+ const referenceSelection = import_selection_manager.useSelectionManager(selectionManager, () => selectionManager.referenceSelection);
2876
+ const referenceSelectionRect = import_react4.useMemo(() => {
2877
+ if (referenceSelection.type === "none")
2878
+ return;
2879
+ if (finiteRowCount === 0 || finiteColumnCount === 0)
2880
+ return;
2881
+ const range = referenceSelection.range;
2882
+ const lastVisibleRow = visibleCells.reduce((maximum, cell) => Math.max(maximum, cell.row - 1), range.start.row);
2883
+ const lastVisibleCol = visibleCells.reduce((maximum, cell) => Math.max(maximum, columnToIndex(cell.col)), range.start.col);
2884
+ const rawEndRow = range.end.row.type === "number" ? range.end.row.value : finiteRowCount === undefined ? lastVisibleRow : finiteRowCount - 1;
2885
+ const rawEndCol = range.end.col.type === "number" ? range.end.col.value : finiteColumnCount === undefined ? lastVisibleCol : finiteColumnCount - 1;
2886
+ const maxRow = finiteRowCount === undefined ? Infinity : finiteRowCount - 1;
2887
+ const maxCol = finiteColumnCount === undefined ? Infinity : finiteColumnCount - 1;
2888
+ const startRow = Math.min(Math.max(0, Math.min(range.start.row, rawEndRow)), maxRow);
2889
+ const endRow = Math.min(Math.max(0, Math.max(range.start.row, rawEndRow)), maxRow);
2890
+ const startCol = Math.min(Math.max(0, Math.min(range.start.col, rawEndCol)), maxCol);
2891
+ const endCol = Math.min(Math.max(0, Math.max(range.start.col, rawEndCol)), maxCol);
2892
+ const rect = getRectFromCellSnapAnchor({
2893
+ startCol,
2894
+ startRow,
2895
+ endCol: endCol + 1,
2896
+ endRow: endRow + 1
2897
+ }, columnWidths, rowHeights);
2898
+ return {
2899
+ left: HEADER_WIDTH + rect.x,
2900
+ top: HEADER_HEIGHT + rect.y,
2901
+ width: rect.width,
2902
+ height: rect.height
2903
+ };
2904
+ }, [
2905
+ columnWidths,
2906
+ finiteColumnCount,
2907
+ finiteRowCount,
2908
+ referenceSelection,
2909
+ rowHeights,
2910
+ visibleCells
2911
+ ]);
2704
2912
  import_react4.default.useEffect(() => {
2705
2913
  if (!containerDivRef || !hasFocus || !parentSelected) {
2706
2914
  return;
@@ -2718,8 +2926,9 @@ var Spreadsheet = import_react4.forwardRef(({
2718
2926
  },
2719
2927
  blur: () => {
2720
2928
  selectionManager.blur();
2721
- }
2722
- }), [selectionManager]);
2929
+ },
2930
+ scrollToCell
2931
+ }), [scrollToCell, selectionManager]);
2723
2932
  return /* @__PURE__ */ jsx_runtime3.jsx("div", {
2724
2933
  className: cn("rsp-root", className),
2725
2934
  "data-rsp-scheme": colorScheme,
@@ -2823,28 +3032,38 @@ var Spreadsheet = import_react4.forwardRef(({
2823
3032
  zIndex: 40
2824
3033
  }
2825
3034
  }),
2826
- /* @__PURE__ */ jsx_runtime3.jsx("div", {
3035
+ /* @__PURE__ */ jsx_runtime3.jsxs("div", {
2827
3036
  className: "rsp-cells",
2828
3037
  "data-testid": "spreadsheet-cells",
2829
3038
  style: {
2830
3039
  transform: `translate(${-viewport.scrollX}px, ${-viewport.scrollY}px) scale(${viewport.zoom})`,
2831
3040
  transformOrigin: "0 0"
2832
3041
  },
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}`))
3042
+ children: [
3043
+ visibleCells.map((cell) => /* @__PURE__ */ jsx_runtime3.jsx(CellComponent, {
3044
+ value: cellData.get(`${cell.col}${cell.row}`) ?? "",
3045
+ row: cell.row,
3046
+ col: cell.col,
3047
+ selectionManager,
3048
+ cellToPixelBase,
3049
+ getColumnWidth,
3050
+ getRowHeight,
3051
+ customCellStyle,
3052
+ customCellRenderer,
3053
+ inputValue: cellData.get(`${cell.col}${cell.row}`) ?? "",
3054
+ editInputVersion: retryingEdit?.rowIndex === cell.row - 1 && retryingEdit?.colIndex === columnToIndex(cell.col) ? retryingEdit.version : 0,
3055
+ width,
3056
+ height
3057
+ }, `${cell.col}${cell.row}`)),
3058
+ referenceSelectionRect && referenceSelection.type !== "none" && /* @__PURE__ */ jsx_runtime3.jsx("div", {
3059
+ "aria-hidden": "true",
3060
+ className: cn("rsp-reference-selection", `rsp-reference-selection-${referenceSelection.type}`),
3061
+ "data-testid": "spreadsheet-reference-selection",
3062
+ "data-reference-id": referenceSelection.id,
3063
+ "data-reference-phase": referenceSelection.type,
3064
+ style: referenceSelectionRect
3065
+ })
3066
+ ]
2848
3067
  }),
2849
3068
  components && overlayChildren && overlayChildren.length > 0 && /* @__PURE__ */ jsx_runtime3.jsxs("div", {
2850
3069
  ref: overlayContainerRef,
@@ -3450,6 +3669,42 @@ function FormulaSheet({
3450
3669
  const clipboardManager = import_react6.default.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3451
3670
  const state = useEngine(engine);
3452
3671
  const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
3672
+ const getEngineTableAt = import_react6.useCallback((cell) => {
3673
+ const table = engine.isCellInTable({
3674
+ workbookName,
3675
+ sheetName,
3676
+ rowIndex: cell.row,
3677
+ colIndex: cell.col
3678
+ });
3679
+ if (!table)
3680
+ return;
3681
+ const endRow = table.endRow.type === "number" ? { type: "number", value: table.endRow.value } : { type: "infinity" };
3682
+ const endCol = {
3683
+ type: "number",
3684
+ value: table.start.colIndex + table.headers.size - 1
3685
+ };
3686
+ const hasDataRows = endRow.type === "infinity" || endRow.value > table.start.rowIndex;
3687
+ return {
3688
+ id: `${table.workbookName}/${table.name}`,
3689
+ bounds: {
3690
+ start: { row: table.start.rowIndex, col: table.start.colIndex },
3691
+ end: { row: endRow, col: endCol }
3692
+ },
3693
+ ...hasDataRows ? {
3694
+ dataBounds: {
3695
+ start: {
3696
+ row: table.start.rowIndex + 1,
3697
+ col: table.start.colIndex
3698
+ },
3699
+ end: { row: endRow, col: endCol }
3700
+ }
3701
+ } : {}
3702
+ };
3703
+ }, [engine, sheetName, workbookName]);
3704
+ const formulaNavigation = import_react6.useMemo(() => ({
3705
+ ...selection?.navigation,
3706
+ getTableAt: selection?.navigation?.getTableAt ?? getEngineTableAt
3707
+ }), [getEngineTableAt, selection?.navigation]);
3453
3708
  const onCellDataChange = import_react6.useCallback((updatedSpreadsheet) => {
3454
3709
  const data = new Map(updatedSpreadsheet);
3455
3710
  data.forEach((value, key) => {
@@ -3623,6 +3878,7 @@ function FormulaSheet({
3623
3878
  parentSelected: isSelected,
3624
3879
  selection: {
3625
3880
  ...selection,
3881
+ navigation: formulaNavigation,
3626
3882
  effects: selectionManagerEffects
3627
3883
  },
3628
3884
  containerProps: isSelected && selectedArea ? {
@@ -4161,4 +4417,4 @@ var useClickAway = (ref, callback) => {
4161
4417
  }, [ref, callback]);
4162
4418
  };
4163
4419
 
4164
- //# debugId=4874F9DF8699906864756E2164756E21
4420
+ //# debugId=5EB66D4C826F4D2464756E2164756E21