@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.
- package/README.md +22 -3
- package/dist/cjs/lib.cjs +123 -30
- package/dist/cjs/lib.cjs.map +4 -4
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/lib.mjs +123 -30
- package/dist/mjs/lib.mjs.map +4 -4
- package/dist/mjs/package.json +1 -1
- package/dist/types/lib.d.ts +2 -1
- package/dist/types/spreadsheet/spreadsheet.d.ts +6 -1
- package/dist/types/workbook/workbook-selection-manager.d.ts +23 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -134,9 +134,23 @@ function ReferenceAwareSheet({ highlight }: { highlight?: Highlight }) {
|
|
|
134
134
|
|
|
135
135
|
Update `highlight` from the formula editor's caret/reference analysis. Clearing
|
|
136
136
|
it removes the decoration, while the selection manager continues to own mouse,
|
|
137
|
-
keyboard and edit selection state independently.
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
keyboard and edit selection state independently. The same `customCellStyle`
|
|
138
|
+
prop is forwarded to a `FormulaWorkbook`'s active sheet.
|
|
139
|
+
|
|
140
|
+
To focus and reveal the reference itself, share a `WorkbookSelectionManager`
|
|
141
|
+
with the formula sheets and pass the resolved `RangeAddress` to `focusRange`:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
const workbookSelection = new WorkbookSelectionManager();
|
|
145
|
+
|
|
146
|
+
workbookSelection.focusRange(referenceAddress, { align: "nearest" });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The call selects the complete target range, focuses its grid and asks the grid
|
|
150
|
+
to reveal it. It returns `true` when the target sheet is already mounted. If a
|
|
151
|
+
controlled `FormulaWorkbook` still needs to switch `activeSheet`, it returns
|
|
152
|
+
`false` and queues the latest request; mounting that sheet applies the focus and
|
|
153
|
+
reveal automatically.
|
|
140
154
|
|
|
141
155
|
For the range currently being inserted into a formula, use selection-manager's
|
|
142
156
|
reference-picking mode instead. It preserves the primary/editing selection and
|
|
@@ -200,6 +214,11 @@ The imperative `SpreadsheetRef.scrollToCell({ row, col }, { align })` reveals a
|
|
|
200
214
|
cell without changing selection. Alignment is `"nearest"` by default, with
|
|
201
215
|
`"start"` and `"end"` available for explicit positioning.
|
|
202
216
|
|
|
217
|
+
`SpreadsheetRef.scrollToRange(range, { align })` does the same for an `SMArea`.
|
|
218
|
+
Finite ranges may be supplied in either direction and are clamped to finite grid
|
|
219
|
+
bounds. An open-ended axis reaches the final row or column of a finite grid; on
|
|
220
|
+
an unbounded grid its finite start is used as the reveal anchor.
|
|
221
|
+
|
|
203
222
|
## Theming
|
|
204
223
|
|
|
205
224
|
All colors, fonts and sizes are CSS custom properties. The package only ever
|
package/dist/cjs/lib.cjs
CHANGED
|
@@ -1472,12 +1472,37 @@ var alignScrollOffset = (current, itemStart, itemEnd, viewportSize, align) => {
|
|
|
1472
1472
|
return itemStart;
|
|
1473
1473
|
if (align === "end")
|
|
1474
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
|
+
}
|
|
1475
1482
|
if (itemStart < current)
|
|
1476
1483
|
return itemStart;
|
|
1477
1484
|
if (itemEnd > current + viewportSize)
|
|
1478
1485
|
return itemEnd - viewportSize;
|
|
1479
1486
|
return current;
|
|
1480
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
|
+
};
|
|
1481
1506
|
var hasCellData = (value) => value !== undefined && value !== "";
|
|
1482
1507
|
var getSparseUsedRange = (cellData, rowCount, columnCount) => {
|
|
1483
1508
|
let minRow = Infinity;
|
|
@@ -2234,22 +2259,20 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
2234
2259
|
scrollY: Math.min(Math.max(0, scrollY), maxScrollY)
|
|
2235
2260
|
};
|
|
2236
2261
|
}, [columnWidths, finiteColumnCount, finiteRowCount, height, rowHeights, width]);
|
|
2237
|
-
const
|
|
2262
|
+
const revealRange = import_react4.useCallback((range, options = {}) => {
|
|
2238
2263
|
if (finiteRowCount === 0 || finiteColumnCount === 0)
|
|
2239
2264
|
return;
|
|
2240
|
-
|
|
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)
|
|
2241
2268
|
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
2269
|
setViewport((previous) => {
|
|
2247
2270
|
const bodyWidth = Math.max(0, width - HEADER_WIDTH * previous.zoom);
|
|
2248
2271
|
const bodyHeight = Math.max(0, height - HEADER_HEIGHT * previous.zoom);
|
|
2249
|
-
const left = getColumnEdgePosition(
|
|
2250
|
-
const right = getColumnEdgePosition(
|
|
2251
|
-
const top = getRowEdgePosition(
|
|
2252
|
-
const bottom = getRowEdgePosition(
|
|
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;
|
|
2253
2276
|
const desired = clampViewportOffsets(alignScrollOffset(previous.scrollX, left, right, bodyWidth, options.columnAlign ?? "nearest"), alignScrollOffset(previous.scrollY, top, bottom, bodyHeight, options.rowAlign ?? "nearest"), previous.zoom);
|
|
2254
2277
|
if (desired.scrollX === previous.scrollX && desired.scrollY === previous.scrollY) {
|
|
2255
2278
|
return previous;
|
|
@@ -2265,17 +2288,34 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
2265
2288
|
rowHeights,
|
|
2266
2289
|
width
|
|
2267
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]);
|
|
2268
2300
|
const scrollToCell = import_react4.useCallback((cell, options = {}) => {
|
|
2269
2301
|
const align = options.align ?? "nearest";
|
|
2270
2302
|
revealCell(cell, { rowAlign: align, columnAlign: align });
|
|
2271
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]);
|
|
2272
2308
|
import_react4.useEffect(() => selectionManager.listenToViewportRequest((request) => {
|
|
2309
|
+
if (request.type === "reveal-range") {
|
|
2310
|
+
scrollToRange(request.range, { align: request.align });
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2273
2313
|
const isVertical = request.direction === "up" || request.direction === "down";
|
|
2274
2314
|
revealCell(request.cell, {
|
|
2275
2315
|
rowAlign: isVertical ? request.align : "nearest",
|
|
2276
2316
|
columnAlign: isVertical ? "nearest" : request.align
|
|
2277
2317
|
});
|
|
2278
|
-
}), [revealCell, selectionManager]);
|
|
2318
|
+
}), [revealCell, scrollToRange, selectionManager]);
|
|
2279
2319
|
import_react4.useEffect(() => {
|
|
2280
2320
|
setViewport((previous) => {
|
|
2281
2321
|
const next = clampViewportOffsets(previous.scrollX, previous.scrollY, previous.zoom);
|
|
@@ -2910,7 +2950,7 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
2910
2950
|
visibleCells
|
|
2911
2951
|
]);
|
|
2912
2952
|
import_react4.default.useEffect(() => {
|
|
2913
|
-
if (!containerDivRef || !
|
|
2953
|
+
if (!containerDivRef || !parentSelected) {
|
|
2914
2954
|
return;
|
|
2915
2955
|
}
|
|
2916
2956
|
containerDivRef.addEventListener("wheel", handleWheel, {
|
|
@@ -2919,7 +2959,7 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
2919
2959
|
return () => {
|
|
2920
2960
|
containerDivRef.removeEventListener("wheel", handleWheel);
|
|
2921
2961
|
};
|
|
2922
|
-
}, [containerDivRef, handleWheel,
|
|
2962
|
+
}, [containerDivRef, handleWheel, parentSelected]);
|
|
2923
2963
|
import_react4.useImperativeHandle(ref, () => ({
|
|
2924
2964
|
focus: () => {
|
|
2925
2965
|
selectionManager.focus();
|
|
@@ -2927,8 +2967,9 @@ var Spreadsheet = import_react4.forwardRef(({
|
|
|
2927
2967
|
blur: () => {
|
|
2928
2968
|
selectionManager.blur();
|
|
2929
2969
|
},
|
|
2930
|
-
scrollToCell
|
|
2931
|
-
|
|
2970
|
+
scrollToCell,
|
|
2971
|
+
scrollToRange
|
|
2972
|
+
}), [scrollToCell, scrollToRange, selectionManager]);
|
|
2932
2973
|
return /* @__PURE__ */ jsx_runtime3.jsx("div", {
|
|
2933
2974
|
className: cn("rsp-root", className),
|
|
2934
2975
|
"data-rsp-scheme": colorScheme,
|
|
@@ -3273,6 +3314,7 @@ class WorkbookSelectionManager {
|
|
|
3273
3314
|
lastFocusedManager = null;
|
|
3274
3315
|
listeners = new Set;
|
|
3275
3316
|
cleanupFunctions = new Map;
|
|
3317
|
+
pendingRangeFocus = null;
|
|
3276
3318
|
smAreaToSpreadsheetRange(area) {
|
|
3277
3319
|
const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
|
|
3278
3320
|
const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
|
|
@@ -3287,6 +3329,47 @@ class WorkbookSelectionManager {
|
|
|
3287
3329
|
}
|
|
3288
3330
|
};
|
|
3289
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
|
+
}
|
|
3290
3373
|
add(manager, context) {
|
|
3291
3374
|
this.managers.set(manager, context);
|
|
3292
3375
|
const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
|
|
@@ -3312,6 +3395,7 @@ class WorkbookSelectionManager {
|
|
|
3312
3395
|
}
|
|
3313
3396
|
};
|
|
3314
3397
|
this.cleanupFunctions.set(manager, cleanup);
|
|
3398
|
+
this.applyPendingRangeFocusOnMount(manager, context);
|
|
3315
3399
|
return cleanup;
|
|
3316
3400
|
}
|
|
3317
3401
|
getLastFocusedSelectionManager() {
|
|
@@ -3405,22 +3489,31 @@ class WorkbookSelectionManager {
|
|
|
3405
3489
|
return false;
|
|
3406
3490
|
}
|
|
3407
3491
|
focusCell(workbookName, sheetName, row, col) {
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
manager.setState({ selections: [selection] });
|
|
3418
|
-
manager.focus();
|
|
3419
|
-
this.lastFocusedManager = { sm: manager, context };
|
|
3420
|
-
this.emitChange();
|
|
3421
|
-
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
|
+
}
|
|
3422
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;
|
|
3423
3515
|
}
|
|
3516
|
+
this.pendingRangeFocus = request;
|
|
3424
3517
|
return false;
|
|
3425
3518
|
}
|
|
3426
3519
|
emitChange() {
|
|
@@ -4417,4 +4510,4 @@ var useClickAway = (ref, callback) => {
|
|
|
4417
4510
|
}, [ref, callback]);
|
|
4418
4511
|
};
|
|
4419
4512
|
|
|
4420
|
-
//# debugId=
|
|
4513
|
+
//# debugId=81701030D90BCDFB64756E2164756E21
|