@ricsam/react-spreadsheets 0.0.9 → 0.0.10
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 +23 -0
- package/dist/cjs/lib.cjs +77 -34
- package/dist/cjs/lib.cjs.map +4 -4
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/lib.mjs +80 -37
- package/dist/mjs/lib.mjs.map +4 -4
- package/dist/mjs/package.json +1 -1
- package/dist/types/lib.d.ts +2 -2
- package/dist/types/workbook/workbook-selection-manager.d.ts +22 -7
- package/dist/types/workbook/workbook.d.ts +8 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -152,6 +152,29 @@ controlled `FormulaWorkbook` still needs to switch `activeSheet`, it returns
|
|
|
152
152
|
`false` and queues the latest request; mounting that sheet applies the focus and
|
|
153
153
|
reveal automatically.
|
|
154
154
|
|
|
155
|
+
If the same workbook can be mounted in more than one place, give each rendered
|
|
156
|
+
view a stable ID and target that ID when navigating. Selection caches are also
|
|
157
|
+
scoped to the view, so switching sheets or remounting one pane cannot restore a
|
|
158
|
+
selection from its duplicate:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<FormulaWorkbook
|
|
162
|
+
viewId="left-pane"
|
|
163
|
+
workbookName="Forecast"
|
|
164
|
+
engine={engine}
|
|
165
|
+
selectionManager={workbookSelection}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
workbookSelection.focusRange(referenceAddress, {
|
|
169
|
+
align: "nearest",
|
|
170
|
+
viewId: "left-pane",
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Omitting `viewId` preserves the single-view API and targets the first matching
|
|
175
|
+
mounted workbook/sheet. Call `cancelPendingFocusRange()` if an app-level sheet
|
|
176
|
+
or pane switch is abandoned before the queued target mounts.
|
|
177
|
+
|
|
155
178
|
For the range currently being inserted into a formula, use selection-manager's
|
|
156
179
|
reference-picking mode instead. It preserves the primary/editing selection and
|
|
157
180
|
the grid renders the picked range with an independent animated border:
|
package/dist/cjs/lib.cjs
CHANGED
|
@@ -3312,9 +3312,24 @@ var import_formula_engine = require("@ricsam/formula-engine");
|
|
|
3312
3312
|
class WorkbookSelectionManager {
|
|
3313
3313
|
managers = new Map;
|
|
3314
3314
|
lastFocusedManager = null;
|
|
3315
|
+
lastSelections = [];
|
|
3316
|
+
selectionsByView = new Map;
|
|
3315
3317
|
listeners = new Set;
|
|
3316
3318
|
cleanupFunctions = new Map;
|
|
3317
3319
|
pendingRangeFocus = null;
|
|
3320
|
+
applyingRangeFocus = new Set;
|
|
3321
|
+
getViewKey(context) {
|
|
3322
|
+
return JSON.stringify([context.workbookName, context.sheetName, context.viewId ?? null]);
|
|
3323
|
+
}
|
|
3324
|
+
cloneSmArea(area) {
|
|
3325
|
+
return {
|
|
3326
|
+
start: { row: area.start.row, col: area.start.col },
|
|
3327
|
+
end: {
|
|
3328
|
+
row: area.end.row.type === "infinity" ? { type: "infinity" } : { type: "number", value: area.end.row.value },
|
|
3329
|
+
col: area.end.col.type === "infinity" ? { type: "infinity" } : { type: "number", value: area.end.col.value }
|
|
3330
|
+
}
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3318
3333
|
smAreaToSpreadsheetRange(area) {
|
|
3319
3334
|
const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
|
|
3320
3335
|
const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
|
|
@@ -3345,43 +3360,69 @@ class WorkbookSelectionManager {
|
|
|
3345
3360
|
range: this.smAreaToSpreadsheetRange(this.spreadsheetRangeToSmArea(address.range))
|
|
3346
3361
|
};
|
|
3347
3362
|
}
|
|
3348
|
-
|
|
3349
|
-
return
|
|
3363
|
+
areasToRangeAddresses(areas, context) {
|
|
3364
|
+
return areas.map((area) => ({
|
|
3365
|
+
workbookName: context.workbookName,
|
|
3366
|
+
sheetName: context.sheetName,
|
|
3367
|
+
range: this.smAreaToSpreadsheetRange(area)
|
|
3368
|
+
}));
|
|
3369
|
+
}
|
|
3370
|
+
rememberSelections(manager, context, areas = manager.getState().selections) {
|
|
3371
|
+
const cachedAreas = areas.map((area) => this.cloneSmArea(area));
|
|
3372
|
+
this.selectionsByView.set(this.getViewKey(context), cachedAreas);
|
|
3373
|
+
this.lastFocusedManager = { sm: manager, context };
|
|
3374
|
+
this.lastSelections = this.areasToRangeAddresses(cachedAreas, context);
|
|
3375
|
+
}
|
|
3376
|
+
matchesContext(address, context, viewId) {
|
|
3377
|
+
return address.workbookName === context.workbookName && address.sheetName === context.sheetName && (viewId === undefined || context.viewId === viewId);
|
|
3350
3378
|
}
|
|
3351
3379
|
applyRangeFocus(manager, context, request) {
|
|
3352
3380
|
const selection = this.spreadsheetRangeToSmArea(request.address.range);
|
|
3353
|
-
|
|
3354
|
-
|
|
3381
|
+
this.applyingRangeFocus.add(manager);
|
|
3382
|
+
try {
|
|
3383
|
+
manager.replaceSelections([selection]);
|
|
3384
|
+
manager.focus();
|
|
3385
|
+
} finally {
|
|
3386
|
+
this.applyingRangeFocus.delete(manager);
|
|
3387
|
+
}
|
|
3355
3388
|
manager.revealRange(selection, request.options);
|
|
3356
|
-
this.
|
|
3389
|
+
this.rememberSelections(manager, context, [selection]);
|
|
3357
3390
|
this.emitChange();
|
|
3358
3391
|
}
|
|
3359
3392
|
applyPendingRangeFocusOnMount(manager, context) {
|
|
3360
3393
|
const pending = this.pendingRangeFocus;
|
|
3361
|
-
if (!pending || !this.matchesContext(pending.address, context))
|
|
3394
|
+
if (!pending || !this.matchesContext(pending.address, context, pending.options.viewId))
|
|
3362
3395
|
return;
|
|
3363
3396
|
queueMicrotask(() => {
|
|
3364
3397
|
if (this.pendingRangeFocus !== pending)
|
|
3365
3398
|
return;
|
|
3366
3399
|
const mountedContext = this.managers.get(manager);
|
|
3367
|
-
if (!mountedContext || !this.matchesContext(pending.address, mountedContext))
|
|
3400
|
+
if (!mountedContext || !this.matchesContext(pending.address, mountedContext, pending.options.viewId)) {
|
|
3368
3401
|
return;
|
|
3402
|
+
}
|
|
3369
3403
|
this.pendingRangeFocus = null;
|
|
3370
3404
|
this.applyRangeFocus(manager, mountedContext, pending);
|
|
3371
3405
|
});
|
|
3372
3406
|
}
|
|
3373
3407
|
add(manager, context) {
|
|
3374
|
-
|
|
3408
|
+
const registeredContext = { ...context };
|
|
3409
|
+
this.managers.set(manager, registeredContext);
|
|
3410
|
+
const cachedSelections = this.selectionsByView.get(this.getViewKey(registeredContext));
|
|
3411
|
+
if (cachedSelections && manager.getState().selections.length === 0) {
|
|
3412
|
+
manager.replaceSelections(cachedSelections);
|
|
3413
|
+
}
|
|
3375
3414
|
const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
|
|
3376
|
-
if (hasFocus) {
|
|
3377
|
-
this.
|
|
3415
|
+
if (hasFocus && !this.applyingRangeFocus.has(manager)) {
|
|
3416
|
+
this.rememberSelections(manager, registeredContext);
|
|
3378
3417
|
this.emitChange();
|
|
3379
3418
|
}
|
|
3380
3419
|
});
|
|
3381
3420
|
const selectionCleanup = manager.observeStateChange((state) => JSON.stringify(state.selections), (selectionsJson) => {
|
|
3421
|
+
if (this.applyingRangeFocus.has(manager))
|
|
3422
|
+
return;
|
|
3382
3423
|
const selections = JSON.parse(selectionsJson);
|
|
3383
3424
|
if (selections && selections.length > 0) {
|
|
3384
|
-
this.
|
|
3425
|
+
this.rememberSelections(manager, registeredContext, selections);
|
|
3385
3426
|
this.emitChange();
|
|
3386
3427
|
}
|
|
3387
3428
|
});
|
|
@@ -3391,27 +3432,19 @@ class WorkbookSelectionManager {
|
|
|
3391
3432
|
this.managers.delete(manager);
|
|
3392
3433
|
this.cleanupFunctions.delete(manager);
|
|
3393
3434
|
if (this.lastFocusedManager?.sm === manager) {
|
|
3435
|
+
this.rememberSelections(manager, registeredContext);
|
|
3394
3436
|
this.lastFocusedManager = null;
|
|
3395
3437
|
}
|
|
3396
3438
|
};
|
|
3397
3439
|
this.cleanupFunctions.set(manager, cleanup);
|
|
3398
|
-
this.applyPendingRangeFocusOnMount(manager,
|
|
3440
|
+
this.applyPendingRangeFocusOnMount(manager, registeredContext);
|
|
3399
3441
|
return cleanup;
|
|
3400
3442
|
}
|
|
3401
3443
|
getLastFocusedSelectionManager() {
|
|
3402
3444
|
return this.lastFocusedManager;
|
|
3403
3445
|
}
|
|
3404
3446
|
getSelections() {
|
|
3405
|
-
|
|
3406
|
-
return [];
|
|
3407
|
-
}
|
|
3408
|
-
const { sm, context } = this.lastFocusedManager;
|
|
3409
|
-
const state = sm.getState();
|
|
3410
|
-
return state.selections.map((area) => ({
|
|
3411
|
-
workbookName: context.workbookName,
|
|
3412
|
-
sheetName: context.sheetName,
|
|
3413
|
-
range: this.smAreaToSpreadsheetRange(area)
|
|
3414
|
-
}));
|
|
3447
|
+
return this.lastSelections.map((selection) => this.cloneRangeAddress(selection));
|
|
3415
3448
|
}
|
|
3416
3449
|
getLastSelection() {
|
|
3417
3450
|
const selections = this.getSelections();
|
|
@@ -3460,35 +3493,37 @@ class WorkbookSelectionManager {
|
|
|
3460
3493
|
return `[${workbookName}]${sheetRef}!${rangeStr}`;
|
|
3461
3494
|
}
|
|
3462
3495
|
blur() {
|
|
3463
|
-
this.managers.forEach((
|
|
3496
|
+
this.managers.forEach((_context, manager) => {
|
|
3464
3497
|
manager.clearSelections();
|
|
3465
3498
|
manager.blur();
|
|
3466
3499
|
});
|
|
3500
|
+
this.selectionsByView.clear();
|
|
3467
3501
|
this.lastFocusedManager = null;
|
|
3502
|
+
this.lastSelections = [];
|
|
3468
3503
|
this.emitChange();
|
|
3469
3504
|
}
|
|
3470
|
-
focusWorkbook(workbookName) {
|
|
3505
|
+
focusWorkbook(workbookName, viewId) {
|
|
3471
3506
|
for (const [manager, context] of this.managers) {
|
|
3472
|
-
if (context.workbookName === workbookName) {
|
|
3507
|
+
if (context.workbookName === workbookName && (viewId === undefined || context.viewId === viewId)) {
|
|
3473
3508
|
manager.focus();
|
|
3474
|
-
this.
|
|
3509
|
+
this.rememberSelections(manager, context);
|
|
3475
3510
|
this.emitChange();
|
|
3476
3511
|
return;
|
|
3477
3512
|
}
|
|
3478
3513
|
}
|
|
3479
3514
|
}
|
|
3480
|
-
focusSheet(workbookName, sheetName) {
|
|
3515
|
+
focusSheet(workbookName, sheetName, viewId) {
|
|
3481
3516
|
for (const [manager, context] of this.managers) {
|
|
3482
|
-
if (context.workbookName === workbookName && context.sheetName === sheetName) {
|
|
3517
|
+
if (context.workbookName === workbookName && context.sheetName === sheetName && (viewId === undefined || context.viewId === viewId)) {
|
|
3483
3518
|
manager.focus();
|
|
3484
|
-
this.
|
|
3519
|
+
this.rememberSelections(manager, context);
|
|
3485
3520
|
this.emitChange();
|
|
3486
3521
|
return true;
|
|
3487
3522
|
}
|
|
3488
3523
|
}
|
|
3489
3524
|
return false;
|
|
3490
3525
|
}
|
|
3491
|
-
focusCell(workbookName, sheetName, row, col) {
|
|
3526
|
+
focusCell(workbookName, sheetName, row, col, options = {}) {
|
|
3492
3527
|
return this.focusRange({
|
|
3493
3528
|
workbookName,
|
|
3494
3529
|
sheetName,
|
|
@@ -3499,7 +3534,7 @@ class WorkbookSelectionManager {
|
|
|
3499
3534
|
col: { type: "number", value: col }
|
|
3500
3535
|
}
|
|
3501
3536
|
}
|
|
3502
|
-
});
|
|
3537
|
+
}, options);
|
|
3503
3538
|
}
|
|
3504
3539
|
focusRange(address, options = {}) {
|
|
3505
3540
|
const request = {
|
|
@@ -3507,7 +3542,7 @@ class WorkbookSelectionManager {
|
|
|
3507
3542
|
options: { ...options }
|
|
3508
3543
|
};
|
|
3509
3544
|
for (const [manager, context] of this.managers) {
|
|
3510
|
-
if (!this.matchesContext(request.address, context))
|
|
3545
|
+
if (!this.matchesContext(request.address, context, request.options.viewId))
|
|
3511
3546
|
continue;
|
|
3512
3547
|
this.pendingRangeFocus = null;
|
|
3513
3548
|
this.applyRangeFocus(manager, context, request);
|
|
@@ -3516,6 +3551,9 @@ class WorkbookSelectionManager {
|
|
|
3516
3551
|
this.pendingRangeFocus = request;
|
|
3517
3552
|
return false;
|
|
3518
3553
|
}
|
|
3554
|
+
cancelPendingFocusRange() {
|
|
3555
|
+
this.pendingRangeFocus = null;
|
|
3556
|
+
}
|
|
3519
3557
|
emitChange() {
|
|
3520
3558
|
const currentSelection = this.getLastSelection();
|
|
3521
3559
|
const selections = currentSelection ? [currentSelection] : [];
|
|
@@ -3742,6 +3780,7 @@ function FormulaSheet({
|
|
|
3742
3780
|
verboseErrors = false,
|
|
3743
3781
|
selection,
|
|
3744
3782
|
selectionManager,
|
|
3783
|
+
viewId,
|
|
3745
3784
|
customCellStyle,
|
|
3746
3785
|
customCellRenderer,
|
|
3747
3786
|
onCellDataChangeError = queueCellEditError,
|
|
@@ -3913,7 +3952,8 @@ function FormulaSheet({
|
|
|
3913
3952
|
if (selectionManager) {
|
|
3914
3953
|
const workbookManagerCleanup = selectionManager.add(spreadsheetSelectionManager, {
|
|
3915
3954
|
workbookName,
|
|
3916
|
-
sheetName
|
|
3955
|
+
sheetName,
|
|
3956
|
+
...viewId === undefined ? {} : { viewId }
|
|
3917
3957
|
});
|
|
3918
3958
|
cleanups.push(workbookManagerCleanup);
|
|
3919
3959
|
}
|
|
@@ -3934,6 +3974,7 @@ function FormulaSheet({
|
|
|
3934
3974
|
selectionManager,
|
|
3935
3975
|
sheetName,
|
|
3936
3976
|
showFormulas,
|
|
3977
|
+
viewId,
|
|
3937
3978
|
workbookName
|
|
3938
3979
|
]);
|
|
3939
3980
|
if (!sheet) {
|
|
@@ -4167,6 +4208,7 @@ function FormulaWorkbook({
|
|
|
4167
4208
|
onActiveSheetChange,
|
|
4168
4209
|
verboseErrors = false,
|
|
4169
4210
|
selectionManager,
|
|
4211
|
+
viewId,
|
|
4170
4212
|
components,
|
|
4171
4213
|
sheetOverlayChildren,
|
|
4172
4214
|
sheetSelectedOverlayIds,
|
|
@@ -4334,6 +4376,7 @@ function FormulaWorkbook({
|
|
|
4334
4376
|
engine,
|
|
4335
4377
|
verboseErrors,
|
|
4336
4378
|
selectionManager,
|
|
4379
|
+
viewId,
|
|
4337
4380
|
...rest,
|
|
4338
4381
|
components,
|
|
4339
4382
|
overlayChildren: activeSheetOverlayChildren,
|
|
@@ -4510,4 +4553,4 @@ var useClickAway = (ref, callback) => {
|
|
|
4510
4553
|
}, [ref, callback]);
|
|
4511
4554
|
};
|
|
4512
4555
|
|
|
4513
|
-
//# debugId=
|
|
4556
|
+
//# debugId=E584D6343578997564756E2164756E21
|