@ricsam/react-spreadsheets 0.0.9 → 0.0.11

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
@@ -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:
@@ -316,6 +339,11 @@ Per-cell styling is done in JS:
316
339
  | `selection` | Selection state, navigation model, and `effects(selectionManager)` for copy/paste/fill/reference hooks. |
317
340
  | `components` + `overlayChildren` | Floating overlays anchored to the grid, optionally snapped to cell edges. |
318
341
 
342
+ Wheel input over the grid remains available when its cell selection is blurred.
343
+ Wheel events originating inside a floating overlay are left to that overlay, so
344
+ scrollable editors and other interactive overlay content keep their native
345
+ scroll behavior.
346
+
319
347
  ## Development
320
348
 
321
349
  ```bash
package/dist/cjs/lib.cjs CHANGED
@@ -1434,6 +1434,7 @@ var resizeCellSnapAnchorToNearestEdges = (anchor, rect, handle, columnWidths, ro
1434
1434
 
1435
1435
  // src/spreadsheet/spreadsheet.tsx
1436
1436
  var jsx_runtime3 = require("react/jsx-runtime");
1437
+ var SPREADSHEET_OVERLAY_SELECTOR = "[data-rsp-overlay]";
1437
1438
  var resolveGridLineColor = (probe, colorScheme) => {
1438
1439
  const fallback = colorScheme === "dark" ? "#232c3d" : "#e8ebf1";
1439
1440
  if (!probe || typeof getComputedStyle !== "function") {
@@ -2394,6 +2395,12 @@ var Spreadsheet = import_react4.forwardRef(({
2394
2395
  });
2395
2396
  }, [selectionManager, clipboardManager, disableClipboard]);
2396
2397
  const handleWheel = import_react4.useCallback((e) => {
2398
+ const target = e.target;
2399
+ const currentTarget = e.currentTarget;
2400
+ const overlay = target instanceof Element ? target.closest(SPREADSHEET_OVERLAY_SELECTOR) : null;
2401
+ if (overlay && currentTarget instanceof Node && currentTarget.contains(overlay)) {
2402
+ return;
2403
+ }
2397
2404
  e.preventDefault();
2398
2405
  e.stopPropagation();
2399
2406
  setViewport((previous) => ({
@@ -3119,6 +3126,7 @@ var Spreadsheet = import_react4.forwardRef(({
3119
3126
  const isTextBox = child.component === "RichText";
3120
3127
  const isMinimized = Boolean(child.minimized && !isTextBox);
3121
3128
  return /* @__PURE__ */ jsx_runtime3.jsxs("div", {
3129
+ "data-rsp-overlay": child.id,
3122
3130
  style: {
3123
3131
  position: "absolute",
3124
3132
  width: child.width,
@@ -3312,9 +3320,24 @@ var import_formula_engine = require("@ricsam/formula-engine");
3312
3320
  class WorkbookSelectionManager {
3313
3321
  managers = new Map;
3314
3322
  lastFocusedManager = null;
3323
+ lastSelections = [];
3324
+ selectionsByView = new Map;
3315
3325
  listeners = new Set;
3316
3326
  cleanupFunctions = new Map;
3317
3327
  pendingRangeFocus = null;
3328
+ applyingRangeFocus = new Set;
3329
+ getViewKey(context) {
3330
+ return JSON.stringify([context.workbookName, context.sheetName, context.viewId ?? null]);
3331
+ }
3332
+ cloneSmArea(area) {
3333
+ return {
3334
+ start: { row: area.start.row, col: area.start.col },
3335
+ end: {
3336
+ row: area.end.row.type === "infinity" ? { type: "infinity" } : { type: "number", value: area.end.row.value },
3337
+ col: area.end.col.type === "infinity" ? { type: "infinity" } : { type: "number", value: area.end.col.value }
3338
+ }
3339
+ };
3340
+ }
3318
3341
  smAreaToSpreadsheetRange(area) {
3319
3342
  const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
3320
3343
  const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
@@ -3345,43 +3368,69 @@ class WorkbookSelectionManager {
3345
3368
  range: this.smAreaToSpreadsheetRange(this.spreadsheetRangeToSmArea(address.range))
3346
3369
  };
3347
3370
  }
3348
- matchesContext(address, context) {
3349
- return address.workbookName === context.workbookName && address.sheetName === context.sheetName;
3371
+ areasToRangeAddresses(areas, context) {
3372
+ return areas.map((area) => ({
3373
+ workbookName: context.workbookName,
3374
+ sheetName: context.sheetName,
3375
+ range: this.smAreaToSpreadsheetRange(area)
3376
+ }));
3377
+ }
3378
+ rememberSelections(manager, context, areas = manager.getState().selections) {
3379
+ const cachedAreas = areas.map((area) => this.cloneSmArea(area));
3380
+ this.selectionsByView.set(this.getViewKey(context), cachedAreas);
3381
+ this.lastFocusedManager = { sm: manager, context };
3382
+ this.lastSelections = this.areasToRangeAddresses(cachedAreas, context);
3383
+ }
3384
+ matchesContext(address, context, viewId) {
3385
+ return address.workbookName === context.workbookName && address.sheetName === context.sheetName && (viewId === undefined || context.viewId === viewId);
3350
3386
  }
3351
3387
  applyRangeFocus(manager, context, request) {
3352
3388
  const selection = this.spreadsheetRangeToSmArea(request.address.range);
3353
- manager.setState({ selections: [selection] });
3354
- manager.focus();
3389
+ this.applyingRangeFocus.add(manager);
3390
+ try {
3391
+ manager.replaceSelections([selection]);
3392
+ manager.focus();
3393
+ } finally {
3394
+ this.applyingRangeFocus.delete(manager);
3395
+ }
3355
3396
  manager.revealRange(selection, request.options);
3356
- this.lastFocusedManager = { sm: manager, context };
3397
+ this.rememberSelections(manager, context, [selection]);
3357
3398
  this.emitChange();
3358
3399
  }
3359
3400
  applyPendingRangeFocusOnMount(manager, context) {
3360
3401
  const pending = this.pendingRangeFocus;
3361
- if (!pending || !this.matchesContext(pending.address, context))
3402
+ if (!pending || !this.matchesContext(pending.address, context, pending.options.viewId))
3362
3403
  return;
3363
3404
  queueMicrotask(() => {
3364
3405
  if (this.pendingRangeFocus !== pending)
3365
3406
  return;
3366
3407
  const mountedContext = this.managers.get(manager);
3367
- if (!mountedContext || !this.matchesContext(pending.address, mountedContext))
3408
+ if (!mountedContext || !this.matchesContext(pending.address, mountedContext, pending.options.viewId)) {
3368
3409
  return;
3410
+ }
3369
3411
  this.pendingRangeFocus = null;
3370
3412
  this.applyRangeFocus(manager, mountedContext, pending);
3371
3413
  });
3372
3414
  }
3373
3415
  add(manager, context) {
3374
- this.managers.set(manager, context);
3416
+ const registeredContext = { ...context };
3417
+ this.managers.set(manager, registeredContext);
3418
+ const cachedSelections = this.selectionsByView.get(this.getViewKey(registeredContext));
3419
+ if (cachedSelections && manager.getState().selections.length === 0) {
3420
+ manager.replaceSelections(cachedSelections);
3421
+ }
3375
3422
  const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
3376
- if (hasFocus) {
3377
- this.lastFocusedManager = { sm: manager, context };
3423
+ if (hasFocus && !this.applyingRangeFocus.has(manager)) {
3424
+ this.rememberSelections(manager, registeredContext);
3378
3425
  this.emitChange();
3379
3426
  }
3380
3427
  });
3381
3428
  const selectionCleanup = manager.observeStateChange((state) => JSON.stringify(state.selections), (selectionsJson) => {
3429
+ if (this.applyingRangeFocus.has(manager))
3430
+ return;
3382
3431
  const selections = JSON.parse(selectionsJson);
3383
3432
  if (selections && selections.length > 0) {
3384
- this.lastFocusedManager = { sm: manager, context };
3433
+ this.rememberSelections(manager, registeredContext, selections);
3385
3434
  this.emitChange();
3386
3435
  }
3387
3436
  });
@@ -3391,27 +3440,19 @@ class WorkbookSelectionManager {
3391
3440
  this.managers.delete(manager);
3392
3441
  this.cleanupFunctions.delete(manager);
3393
3442
  if (this.lastFocusedManager?.sm === manager) {
3443
+ this.rememberSelections(manager, registeredContext);
3394
3444
  this.lastFocusedManager = null;
3395
3445
  }
3396
3446
  };
3397
3447
  this.cleanupFunctions.set(manager, cleanup);
3398
- this.applyPendingRangeFocusOnMount(manager, context);
3448
+ this.applyPendingRangeFocusOnMount(manager, registeredContext);
3399
3449
  return cleanup;
3400
3450
  }
3401
3451
  getLastFocusedSelectionManager() {
3402
3452
  return this.lastFocusedManager;
3403
3453
  }
3404
3454
  getSelections() {
3405
- if (!this.lastFocusedManager) {
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
- }));
3455
+ return this.lastSelections.map((selection) => this.cloneRangeAddress(selection));
3415
3456
  }
3416
3457
  getLastSelection() {
3417
3458
  const selections = this.getSelections();
@@ -3460,35 +3501,37 @@ class WorkbookSelectionManager {
3460
3501
  return `[${workbookName}]${sheetRef}!${rangeStr}`;
3461
3502
  }
3462
3503
  blur() {
3463
- this.managers.forEach((context, manager) => {
3504
+ this.managers.forEach((_context, manager) => {
3464
3505
  manager.clearSelections();
3465
3506
  manager.blur();
3466
3507
  });
3508
+ this.selectionsByView.clear();
3467
3509
  this.lastFocusedManager = null;
3510
+ this.lastSelections = [];
3468
3511
  this.emitChange();
3469
3512
  }
3470
- focusWorkbook(workbookName) {
3513
+ focusWorkbook(workbookName, viewId) {
3471
3514
  for (const [manager, context] of this.managers) {
3472
- if (context.workbookName === workbookName) {
3515
+ if (context.workbookName === workbookName && (viewId === undefined || context.viewId === viewId)) {
3473
3516
  manager.focus();
3474
- this.lastFocusedManager = { sm: manager, context };
3517
+ this.rememberSelections(manager, context);
3475
3518
  this.emitChange();
3476
3519
  return;
3477
3520
  }
3478
3521
  }
3479
3522
  }
3480
- focusSheet(workbookName, sheetName) {
3523
+ focusSheet(workbookName, sheetName, viewId) {
3481
3524
  for (const [manager, context] of this.managers) {
3482
- if (context.workbookName === workbookName && context.sheetName === sheetName) {
3525
+ if (context.workbookName === workbookName && context.sheetName === sheetName && (viewId === undefined || context.viewId === viewId)) {
3483
3526
  manager.focus();
3484
- this.lastFocusedManager = { sm: manager, context };
3527
+ this.rememberSelections(manager, context);
3485
3528
  this.emitChange();
3486
3529
  return true;
3487
3530
  }
3488
3531
  }
3489
3532
  return false;
3490
3533
  }
3491
- focusCell(workbookName, sheetName, row, col) {
3534
+ focusCell(workbookName, sheetName, row, col, options = {}) {
3492
3535
  return this.focusRange({
3493
3536
  workbookName,
3494
3537
  sheetName,
@@ -3499,7 +3542,7 @@ class WorkbookSelectionManager {
3499
3542
  col: { type: "number", value: col }
3500
3543
  }
3501
3544
  }
3502
- });
3545
+ }, options);
3503
3546
  }
3504
3547
  focusRange(address, options = {}) {
3505
3548
  const request = {
@@ -3507,7 +3550,7 @@ class WorkbookSelectionManager {
3507
3550
  options: { ...options }
3508
3551
  };
3509
3552
  for (const [manager, context] of this.managers) {
3510
- if (!this.matchesContext(request.address, context))
3553
+ if (!this.matchesContext(request.address, context, request.options.viewId))
3511
3554
  continue;
3512
3555
  this.pendingRangeFocus = null;
3513
3556
  this.applyRangeFocus(manager, context, request);
@@ -3516,6 +3559,9 @@ class WorkbookSelectionManager {
3516
3559
  this.pendingRangeFocus = request;
3517
3560
  return false;
3518
3561
  }
3562
+ cancelPendingFocusRange() {
3563
+ this.pendingRangeFocus = null;
3564
+ }
3519
3565
  emitChange() {
3520
3566
  const currentSelection = this.getLastSelection();
3521
3567
  const selections = currentSelection ? [currentSelection] : [];
@@ -3742,6 +3788,7 @@ function FormulaSheet({
3742
3788
  verboseErrors = false,
3743
3789
  selection,
3744
3790
  selectionManager,
3791
+ viewId,
3745
3792
  customCellStyle,
3746
3793
  customCellRenderer,
3747
3794
  onCellDataChangeError = queueCellEditError,
@@ -3913,7 +3960,8 @@ function FormulaSheet({
3913
3960
  if (selectionManager) {
3914
3961
  const workbookManagerCleanup = selectionManager.add(spreadsheetSelectionManager, {
3915
3962
  workbookName,
3916
- sheetName
3963
+ sheetName,
3964
+ ...viewId === undefined ? {} : { viewId }
3917
3965
  });
3918
3966
  cleanups.push(workbookManagerCleanup);
3919
3967
  }
@@ -3934,6 +3982,7 @@ function FormulaSheet({
3934
3982
  selectionManager,
3935
3983
  sheetName,
3936
3984
  showFormulas,
3985
+ viewId,
3937
3986
  workbookName
3938
3987
  ]);
3939
3988
  if (!sheet) {
@@ -4167,6 +4216,7 @@ function FormulaWorkbook({
4167
4216
  onActiveSheetChange,
4168
4217
  verboseErrors = false,
4169
4218
  selectionManager,
4219
+ viewId,
4170
4220
  components,
4171
4221
  sheetOverlayChildren,
4172
4222
  sheetSelectedOverlayIds,
@@ -4334,6 +4384,7 @@ function FormulaWorkbook({
4334
4384
  engine,
4335
4385
  verboseErrors,
4336
4386
  selectionManager,
4387
+ viewId,
4337
4388
  ...rest,
4338
4389
  components,
4339
4390
  overlayChildren: activeSheetOverlayChildren,
@@ -4510,4 +4561,4 @@ var useClickAway = (ref, callback) => {
4510
4561
  }, [ref, callback]);
4511
4562
  };
4512
4563
 
4513
- //# debugId=81701030D90BCDFB64756E2164756E21
4564
+ //# debugId=64201658718498BD64756E2164756E21