@ricsam/react-spreadsheets 0.0.8 → 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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/react-spreadsheets",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "type": "commonjs"
5
5
  }
package/dist/mjs/lib.mjs CHANGED
@@ -1364,12 +1364,37 @@ var alignScrollOffset = (current, itemStart, itemEnd, viewportSize, align) => {
1364
1364
  return itemStart;
1365
1365
  if (align === "end")
1366
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
+ }
1367
1374
  if (itemStart < current)
1368
1375
  return itemStart;
1369
1376
  if (itemEnd > current + viewportSize)
1370
1377
  return itemEnd - viewportSize;
1371
1378
  return current;
1372
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
+ };
1373
1398
  var hasCellData = (value) => value !== undefined && value !== "";
1374
1399
  var getSparseUsedRange = (cellData, rowCount, columnCount) => {
1375
1400
  let minRow = Infinity;
@@ -2126,22 +2151,20 @@ var Spreadsheet = forwardRef(({
2126
2151
  scrollY: Math.min(Math.max(0, scrollY), maxScrollY)
2127
2152
  };
2128
2153
  }, [columnWidths, finiteColumnCount, finiteRowCount, height, rowHeights, width]);
2129
- const revealCell = useCallback3((cell, options = {}) => {
2154
+ const revealRange = useCallback3((range, options = {}) => {
2130
2155
  if (finiteRowCount === 0 || finiteColumnCount === 0)
2131
2156
  return;
2132
- if (!Number.isFinite(cell.row) || !Number.isFinite(cell.col))
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)
2133
2160
  return;
2134
- const requestedRow = Math.max(0, Math.floor(cell.row));
2135
- const requestedCol = Math.max(0, Math.floor(cell.col));
2136
- const row = finiteRowCount === undefined ? requestedRow : Math.min(requestedRow, finiteRowCount - 1);
2137
- const col = finiteColumnCount === undefined ? requestedCol : Math.min(requestedCol, finiteColumnCount - 1);
2138
2161
  setViewport((previous) => {
2139
2162
  const bodyWidth = Math.max(0, width - HEADER_WIDTH * previous.zoom);
2140
2163
  const bodyHeight = Math.max(0, height - HEADER_HEIGHT * previous.zoom);
2141
- const left = getColumnEdgePosition(col, columnWidths) * previous.zoom;
2142
- const right = getColumnEdgePosition(col + 1, columnWidths) * previous.zoom;
2143
- const top = getRowEdgePosition(row, rowHeights) * previous.zoom;
2144
- const bottom = getRowEdgePosition(row + 1, rowHeights) * 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;
2145
2168
  const desired = clampViewportOffsets(alignScrollOffset(previous.scrollX, left, right, bodyWidth, options.columnAlign ?? "nearest"), alignScrollOffset(previous.scrollY, top, bottom, bodyHeight, options.rowAlign ?? "nearest"), previous.zoom);
2146
2169
  if (desired.scrollX === previous.scrollX && desired.scrollY === previous.scrollY) {
2147
2170
  return previous;
@@ -2157,17 +2180,34 @@ var Spreadsheet = forwardRef(({
2157
2180
  rowHeights,
2158
2181
  width
2159
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]);
2160
2192
  const scrollToCell = useCallback3((cell, options = {}) => {
2161
2193
  const align = options.align ?? "nearest";
2162
2194
  revealCell(cell, { rowAlign: align, columnAlign: align });
2163
2195
  }, [revealCell]);
2196
+ const scrollToRange = useCallback3((range, options = {}) => {
2197
+ const align = options.align ?? "nearest";
2198
+ revealRange(range, { rowAlign: align, columnAlign: align });
2199
+ }, [revealRange]);
2164
2200
  useEffect3(() => selectionManager.listenToViewportRequest((request) => {
2201
+ if (request.type === "reveal-range") {
2202
+ scrollToRange(request.range, { align: request.align });
2203
+ return;
2204
+ }
2165
2205
  const isVertical = request.direction === "up" || request.direction === "down";
2166
2206
  revealCell(request.cell, {
2167
2207
  rowAlign: isVertical ? request.align : "nearest",
2168
2208
  columnAlign: isVertical ? "nearest" : request.align
2169
2209
  });
2170
- }), [revealCell, selectionManager]);
2210
+ }), [revealCell, scrollToRange, selectionManager]);
2171
2211
  useEffect3(() => {
2172
2212
  setViewport((previous) => {
2173
2213
  const next = clampViewportOffsets(previous.scrollX, previous.scrollY, previous.zoom);
@@ -2802,7 +2842,7 @@ var Spreadsheet = forwardRef(({
2802
2842
  visibleCells
2803
2843
  ]);
2804
2844
  React2.useEffect(() => {
2805
- if (!containerDivRef || !hasFocus || !parentSelected) {
2845
+ if (!containerDivRef || !parentSelected) {
2806
2846
  return;
2807
2847
  }
2808
2848
  containerDivRef.addEventListener("wheel", handleWheel, {
@@ -2811,7 +2851,7 @@ var Spreadsheet = forwardRef(({
2811
2851
  return () => {
2812
2852
  containerDivRef.removeEventListener("wheel", handleWheel);
2813
2853
  };
2814
- }, [containerDivRef, handleWheel, hasFocus, parentSelected]);
2854
+ }, [containerDivRef, handleWheel, parentSelected]);
2815
2855
  useImperativeHandle(ref, () => ({
2816
2856
  focus: () => {
2817
2857
  selectionManager.focus();
@@ -2819,8 +2859,9 @@ var Spreadsheet = forwardRef(({
2819
2859
  blur: () => {
2820
2860
  selectionManager.blur();
2821
2861
  },
2822
- scrollToCell
2823
- }), [scrollToCell, selectionManager]);
2862
+ scrollToCell,
2863
+ scrollToRange
2864
+ }), [scrollToCell, scrollToRange, selectionManager]);
2824
2865
  return /* @__PURE__ */ jsx3("div", {
2825
2866
  className: cn("rsp-root", className),
2826
2867
  "data-rsp-scheme": colorScheme,
@@ -3168,6 +3209,7 @@ class WorkbookSelectionManager {
3168
3209
  lastFocusedManager = null;
3169
3210
  listeners = new Set;
3170
3211
  cleanupFunctions = new Map;
3212
+ pendingRangeFocus = null;
3171
3213
  smAreaToSpreadsheetRange(area) {
3172
3214
  const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
3173
3215
  const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
@@ -3182,6 +3224,47 @@ class WorkbookSelectionManager {
3182
3224
  }
3183
3225
  };
3184
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
+ }
3185
3268
  add(manager, context) {
3186
3269
  this.managers.set(manager, context);
3187
3270
  const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
@@ -3207,6 +3290,7 @@ class WorkbookSelectionManager {
3207
3290
  }
3208
3291
  };
3209
3292
  this.cleanupFunctions.set(manager, cleanup);
3293
+ this.applyPendingRangeFocusOnMount(manager, context);
3210
3294
  return cleanup;
3211
3295
  }
3212
3296
  getLastFocusedSelectionManager() {
@@ -3300,22 +3384,31 @@ class WorkbookSelectionManager {
3300
3384
  return false;
3301
3385
  }
3302
3386
  focusCell(workbookName, sheetName, row, col) {
3303
- for (const [manager, context] of this.managers) {
3304
- if (context.workbookName === workbookName && context.sheetName === sheetName) {
3305
- const selection = {
3306
- start: { row, col },
3307
- end: {
3308
- row: { type: "number", value: row },
3309
- col: { type: "number", value: col }
3310
- }
3311
- };
3312
- manager.setState({ selections: [selection] });
3313
- manager.focus();
3314
- this.lastFocusedManager = { sm: manager, context };
3315
- this.emitChange();
3316
- 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
+ }
3317
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;
3318
3410
  }
3411
+ this.pendingRangeFocus = request;
3319
3412
  return false;
3320
3413
  }
3321
3414
  emitChange() {
@@ -4363,4 +4456,4 @@ export {
4363
4456
  useResizeObserver
4364
4457
  };
4365
4458
 
4366
- //# debugId=22D97CA826E5512F64756E2164756E21
4459
+ //# debugId=58E97FA1EA7F33CF64756E2164756E21