@ricsam/react-spreadsheets 0.0.13 → 0.0.15

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/dist/cjs/lib.cjs CHANGED
@@ -1893,6 +1893,7 @@ var Spreadsheet = import_react4.forwardRef(({
1893
1893
  "aria-describedby": ariaDescribedBy,
1894
1894
  cellData: controlledCellData,
1895
1895
  onCellDataChange,
1896
+ onCellDataPatch,
1896
1897
  onCellDataChangeError,
1897
1898
  rowCount,
1898
1899
  columnCount,
@@ -2097,6 +2098,7 @@ var Spreadsheet = import_react4.forwardRef(({
2097
2098
  }, [overlayPlaceholder, containerDivRef, viewport, gridDims, placeholderPos]);
2098
2099
  const effectsRefOb = {
2099
2100
  onCellDataChange,
2101
+ onCellDataPatch,
2100
2102
  onCellDataChangeError,
2101
2103
  controlledCellData,
2102
2104
  parseValue,
@@ -2343,13 +2345,13 @@ var Spreadsheet = import_react4.forwardRef(({
2343
2345
  return selectionManager.listenToUpdateData((updates) => {
2344
2346
  const editingState = selectionManager.isEditing;
2345
2347
  try {
2346
- const { cellData, onCellDataChange } = effectsRef.current;
2347
- const newData = new Map(cellData);
2348
+ const { cellData, onCellDataChange, onCellDataPatch, controlledCellData } = effectsRef.current;
2349
+ const patch = new Map;
2348
2350
  updates.forEach(({ value, rowIndex, colIndex }) => {
2349
2351
  const key = `${indexToColumn(colIndex)}${rowIndex + 1}`;
2350
2352
  const parseValue = effectsRef.current.parseValue;
2351
2353
  if (value === "") {
2352
- newData.delete(key);
2354
+ patch.set(key, undefined);
2353
2355
  } else {
2354
2356
  let parsedValue = value;
2355
2357
  if (parseValue && typeof value === "string") {
@@ -2369,10 +2371,23 @@ var Spreadsheet = import_react4.forwardRef(({
2369
2371
  });
2370
2372
  }
2371
2373
  }
2372
- newData.set(key, parsedValue);
2374
+ patch.set(key, parsedValue);
2373
2375
  }
2374
2376
  });
2375
- onCellDataChange?.(newData);
2377
+ if (onCellDataPatch) {
2378
+ onCellDataPatch(patch);
2379
+ if (controlledCellData !== undefined)
2380
+ return;
2381
+ }
2382
+ const newData = new Map(cellData);
2383
+ patch.forEach((value, key) => {
2384
+ if (value === undefined)
2385
+ newData.delete(key);
2386
+ else
2387
+ newData.set(key, value);
2388
+ });
2389
+ if (!onCellDataPatch)
2390
+ onCellDataChange?.(newData);
2376
2391
  setInternalCellData(newData);
2377
2392
  } catch (error) {
2378
2393
  console.error("Failed to update spreadsheet cell data:", error);
@@ -3248,12 +3263,370 @@ var Spreadsheet = import_react4.forwardRef(({
3248
3263
  Spreadsheet.displayName = "Spreadsheet";
3249
3264
  // src/workbook/workbook.tsx
3250
3265
  var import_formula_engine2 = require("@ricsam/formula-engine");
3251
- var import_react6 = __toESM(require("react"), 1);
3266
+ var import_react7 = __toESM(require("react"), 1);
3252
3267
 
3253
- // src/utils/use-scheme-root.ts
3268
+ // src/workbook/sheet-navigation.tsx
3254
3269
  var import_react5 = require("react");
3270
+ var jsx_runtime4 = require("react/jsx-runtime");
3271
+ function SheetNavigation({
3272
+ sheets,
3273
+ activeSheet,
3274
+ workbookName,
3275
+ panelId,
3276
+ onSelect,
3277
+ onAdd,
3278
+ onRename,
3279
+ onDelete,
3280
+ children
3281
+ }) {
3282
+ const id = import_react5.useId();
3283
+ const root = import_react5.useRef(null);
3284
+ const menuButton = import_react5.useRef(null);
3285
+ const menu = import_react5.useRef(null);
3286
+ const input = import_react5.useRef(null);
3287
+ const [menuOpen, setMenuOpen] = import_react5.useState(false);
3288
+ const [renaming, setRenaming] = import_react5.useState(null);
3289
+ const [name, setName] = import_react5.useState("");
3290
+ const [error, setError] = import_react5.useState("");
3291
+ const [notice, setNotice] = import_react5.useState("");
3292
+ const selected = sheets.includes(activeSheet) ? activeSheet : sheets[0];
3293
+ const focusTab = (sheet = selected) => {
3294
+ const tabs = root.current?.querySelectorAll('[role="tab"]');
3295
+ tabs?.[sheets.indexOf(sheet ?? "")]?.focus();
3296
+ };
3297
+ import_react5.useEffect(() => {
3298
+ const tab = root.current?.querySelector('[aria-selected="true"]');
3299
+ const strip = tab?.parentElement;
3300
+ if (!tab || !strip)
3301
+ return;
3302
+ const bounds = strip.getBoundingClientRect();
3303
+ const target = tab.getBoundingClientRect();
3304
+ if (target.left < bounds.left)
3305
+ strip.scrollLeft -= bounds.left - target.left;
3306
+ else if (target.right > bounds.right)
3307
+ strip.scrollLeft += target.right - bounds.right;
3308
+ }, [activeSheet, sheets.join("\x00")]);
3309
+ import_react5.useEffect(() => {
3310
+ if (renaming) {
3311
+ input.current?.focus();
3312
+ input.current?.select();
3313
+ }
3314
+ }, [renaming]);
3315
+ import_react5.useEffect(() => {
3316
+ if (!menuOpen)
3317
+ return;
3318
+ menu.current?.querySelector("button")?.focus();
3319
+ const outside = (event) => {
3320
+ if (!root.current?.contains(event.target))
3321
+ setMenuOpen(false);
3322
+ };
3323
+ document.addEventListener("pointerdown", outside);
3324
+ return () => document.removeEventListener("pointerdown", outside);
3325
+ }, [menuOpen]);
3326
+ import_react5.useEffect(() => {
3327
+ setMenuOpen(false);
3328
+ setRenaming(null);
3329
+ setError("");
3330
+ }, [workbookName]);
3331
+ import_react5.useEffect(() => {
3332
+ if (renaming && !sheets.includes(renaming)) {
3333
+ setRenaming(null);
3334
+ setError("");
3335
+ }
3336
+ }, [renaming, sheets.join("\x00")]);
3337
+ const run = (action) => {
3338
+ try {
3339
+ action();
3340
+ setError("");
3341
+ return true;
3342
+ } catch (error) {
3343
+ setError(error instanceof Error ? error.message : "Unable to update this sheet. Please try again.");
3344
+ return false;
3345
+ }
3346
+ };
3347
+ const startRename = (sheet) => {
3348
+ setMenuOpen(false);
3349
+ setError("");
3350
+ setRenaming(sheet);
3351
+ setName(sheet);
3352
+ };
3353
+ const cancelRename = () => {
3354
+ setRenaming(null);
3355
+ setError("");
3356
+ focusTab();
3357
+ };
3358
+ const saveRename = (event) => {
3359
+ event.preventDefault();
3360
+ if (!renaming)
3361
+ return;
3362
+ const next = name.trim();
3363
+ if (!next) {
3364
+ setError("Enter a sheet name.");
3365
+ input.current?.focus();
3366
+ return;
3367
+ }
3368
+ if (sheets.some((sheet) => sheet !== renaming && sheet.toLocaleLowerCase() === next.toLocaleLowerCase())) {
3369
+ setError("A sheet with this name already exists. Choose a different name.");
3370
+ input.current?.focus();
3371
+ return;
3372
+ }
3373
+ if (run(() => {
3374
+ if (next !== renaming)
3375
+ onRename(renaming, next);
3376
+ })) {
3377
+ setRenaming(null);
3378
+ setNotice(`Sheet renamed to ${next}.`);
3379
+ menuButton.current?.focus();
3380
+ }
3381
+ };
3382
+ return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
3383
+ ref: root,
3384
+ className: "rsp-sheet-navigation",
3385
+ onKeyDown: (event) => event.stopPropagation(),
3386
+ children: [
3387
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
3388
+ className: "rsp-sr-only",
3389
+ role: "status",
3390
+ children: notice
3391
+ }),
3392
+ renaming && /* @__PURE__ */ jsx_runtime4.jsxs("form", {
3393
+ className: "rsp-sheet-rename",
3394
+ onSubmit: saveRename,
3395
+ onKeyDown: (event) => {
3396
+ if (event.key === "Escape") {
3397
+ event.preventDefault();
3398
+ cancelRename();
3399
+ }
3400
+ },
3401
+ children: [
3402
+ /* @__PURE__ */ jsx_runtime4.jsx("label", {
3403
+ htmlFor: `${id}-name`,
3404
+ children: "Sheet name"
3405
+ }),
3406
+ /* @__PURE__ */ jsx_runtime4.jsx("input", {
3407
+ ref: input,
3408
+ id: `${id}-name`,
3409
+ className: "rsp-sheet-tab-input",
3410
+ value: name,
3411
+ onChange: (event) => {
3412
+ setName(event.target.value);
3413
+ setError("");
3414
+ },
3415
+ "aria-invalid": !!error,
3416
+ "aria-describedby": error ? `${id}-error` : undefined,
3417
+ "data-testid": `rename-sheet-input-${renaming}`
3418
+ }),
3419
+ /* @__PURE__ */ jsx_runtime4.jsx("button", {
3420
+ type: "submit",
3421
+ className: "rsp-sheet-action",
3422
+ children: "Save"
3423
+ }),
3424
+ /* @__PURE__ */ jsx_runtime4.jsx("button", {
3425
+ type: "button",
3426
+ className: "rsp-sheet-action",
3427
+ onClick: cancelRename,
3428
+ children: "Cancel"
3429
+ })
3430
+ ]
3431
+ }),
3432
+ error && /* @__PURE__ */ jsx_runtime4.jsx("div", {
3433
+ id: `${id}-error`,
3434
+ className: "rsp-sheet-error",
3435
+ role: "alert",
3436
+ children: error
3437
+ }),
3438
+ /* @__PURE__ */ jsx_runtime4.jsxs("div", {
3439
+ className: "rsp-sheet-tabs",
3440
+ children: [
3441
+ /* @__PURE__ */ jsx_runtime4.jsx("div", {
3442
+ className: "rsp-sheet-tabs-scroll",
3443
+ role: "tablist",
3444
+ "aria-label": `${workbookName} sheets`,
3445
+ children: sheets.map((sheet, index) => /* @__PURE__ */ jsx_runtime4.jsx("button", {
3446
+ type: "button",
3447
+ role: "tab",
3448
+ className: `rsp-sheet-tab${activeSheet === sheet ? " rsp-sheet-tab-active" : ""}`,
3449
+ "aria-controls": panelId,
3450
+ "aria-selected": activeSheet === sheet,
3451
+ tabIndex: sheet === selected ? 0 : -1,
3452
+ title: sheet,
3453
+ "data-testid": `sheet-tab-${sheet}`,
3454
+ onClick: () => {
3455
+ setMenuOpen(false);
3456
+ onSelect(sheet);
3457
+ },
3458
+ onDoubleClick: () => startRename(sheet),
3459
+ onKeyDown: (event) => {
3460
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
3461
+ let next;
3462
+ if (event.key === "ArrowRight")
3463
+ next = (index + (rtl ? -1 : 1) + sheets.length) % sheets.length;
3464
+ if (event.key === "ArrowLeft")
3465
+ next = (index + (rtl ? 1 : -1) + sheets.length) % sheets.length;
3466
+ if (event.key === "Home")
3467
+ next = 0;
3468
+ if (event.key === "End")
3469
+ next = sheets.length - 1;
3470
+ if (next !== undefined) {
3471
+ event.preventDefault();
3472
+ onSelect(sheets[next]);
3473
+ focusTab(sheets[next]);
3474
+ }
3475
+ if (event.key === "F2") {
3476
+ event.preventDefault();
3477
+ startRename(sheet);
3478
+ }
3479
+ },
3480
+ children: /* @__PURE__ */ jsx_runtime4.jsx("span", {
3481
+ children: sheet
3482
+ })
3483
+ }, sheet))
3484
+ }),
3485
+ /* @__PURE__ */ jsx_runtime4.jsx("button", {
3486
+ type: "button",
3487
+ className: "rsp-add-sheet-button",
3488
+ title: "Add Sheet",
3489
+ "aria-label": "Add Sheet",
3490
+ "data-testid": `add-sheet-${workbookName}`,
3491
+ onClick: () => {
3492
+ setMenuOpen(false);
3493
+ run(onAdd);
3494
+ },
3495
+ children: /* @__PURE__ */ jsx_runtime4.jsx("svg", {
3496
+ "aria-hidden": "true",
3497
+ width: "16",
3498
+ height: "16",
3499
+ viewBox: "0 0 24 24",
3500
+ fill: "none",
3501
+ stroke: "currentColor",
3502
+ strokeWidth: "2",
3503
+ strokeLinecap: "round",
3504
+ children: /* @__PURE__ */ jsx_runtime4.jsx("path", {
3505
+ d: "M12 5v14M5 12h14"
3506
+ })
3507
+ })
3508
+ }),
3509
+ /* @__PURE__ */ jsx_runtime4.jsx("button", {
3510
+ ref: menuButton,
3511
+ type: "button",
3512
+ className: "rsp-icon-button rsp-sheet-menu-trigger",
3513
+ title: "Sheet actions",
3514
+ "aria-label": selected ? `Sheet actions for ${selected}` : "Sheet actions",
3515
+ disabled: !selected,
3516
+ "aria-haspopup": "menu",
3517
+ "aria-expanded": menuOpen,
3518
+ "aria-controls": menuOpen ? `${id}-menu` : undefined,
3519
+ onClick: () => setMenuOpen((open) => !open),
3520
+ onKeyDown: (event) => {
3521
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3522
+ event.preventDefault();
3523
+ setMenuOpen(true);
3524
+ }
3525
+ },
3526
+ children: /* @__PURE__ */ jsx_runtime4.jsxs("svg", {
3527
+ "aria-hidden": "true",
3528
+ width: "16",
3529
+ height: "16",
3530
+ viewBox: "0 0 24 24",
3531
+ fill: "currentColor",
3532
+ children: [
3533
+ /* @__PURE__ */ jsx_runtime4.jsx("circle", {
3534
+ cx: "5",
3535
+ cy: "12",
3536
+ r: "2"
3537
+ }),
3538
+ /* @__PURE__ */ jsx_runtime4.jsx("circle", {
3539
+ cx: "12",
3540
+ cy: "12",
3541
+ r: "2"
3542
+ }),
3543
+ /* @__PURE__ */ jsx_runtime4.jsx("circle", {
3544
+ cx: "19",
3545
+ cy: "12",
3546
+ r: "2"
3547
+ })
3548
+ ]
3549
+ })
3550
+ }),
3551
+ children
3552
+ ]
3553
+ }),
3554
+ menuOpen && selected && /* @__PURE__ */ jsx_runtime4.jsxs("div", {
3555
+ ref: menu,
3556
+ id: `${id}-menu`,
3557
+ role: "menu",
3558
+ "aria-label": `Actions for ${selected}`,
3559
+ className: "rsp-sheet-menu",
3560
+ onBlur: (event) => {
3561
+ if (!event.currentTarget.contains(event.relatedTarget) && event.relatedTarget !== menuButton.current)
3562
+ setMenuOpen(false);
3563
+ },
3564
+ onKeyDown: (event) => {
3565
+ const items = Array.from(menu.current?.querySelectorAll("button") ?? []);
3566
+ const index = items.indexOf(document.activeElement);
3567
+ if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
3568
+ event.preventDefault();
3569
+ const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + items.length) % items.length;
3570
+ items[next]?.focus();
3571
+ }
3572
+ if (event.key === "Escape") {
3573
+ event.preventDefault();
3574
+ setMenuOpen(false);
3575
+ menuButton.current?.focus();
3576
+ }
3577
+ },
3578
+ children: [
3579
+ /* @__PURE__ */ jsx_runtime4.jsx("div", {
3580
+ className: "rsp-sheet-menu-name",
3581
+ title: selected,
3582
+ children: selected
3583
+ }),
3584
+ /* @__PURE__ */ jsx_runtime4.jsxs("button", {
3585
+ type: "button",
3586
+ role: "menuitem",
3587
+ onClick: () => startRename(selected),
3588
+ "data-testid": `rename-sheet-${selected}`,
3589
+ children: [
3590
+ "Rename sheet ",
3591
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
3592
+ "aria-hidden": "true",
3593
+ children: "F2"
3594
+ })
3595
+ ]
3596
+ }),
3597
+ /* @__PURE__ */ jsx_runtime4.jsx("button", {
3598
+ type: "button",
3599
+ role: "menuitem",
3600
+ "aria-disabled": sheets.length <= 1,
3601
+ className: "rsp-sheet-delete",
3602
+ "data-testid": `delete-sheet-${selected}`,
3603
+ onClick: () => {
3604
+ if (sheets.length <= 1)
3605
+ return;
3606
+ if (window.confirm(`Delete sheet "${selected}" and its contents?`)) {
3607
+ if (run(() => onDelete(selected))) {
3608
+ setMenuOpen(false);
3609
+ setNotice(`Sheet ${selected} deleted.`);
3610
+ menuButton.current?.focus();
3611
+ }
3612
+ }
3613
+ },
3614
+ children: "Delete sheet"
3615
+ }),
3616
+ sheets.length <= 1 && /* @__PURE__ */ jsx_runtime4.jsx("div", {
3617
+ className: "rsp-sheet-menu-hint",
3618
+ children: "Keep at least one sheet."
3619
+ })
3620
+ ]
3621
+ })
3622
+ ]
3623
+ });
3624
+ }
3625
+
3626
+ // src/utils/use-scheme-root.ts
3627
+ var import_react6 = require("react");
3255
3628
  function useSchemeRoot() {
3256
- const [element, setElement] = import_react5.useState(null);
3629
+ const [element, setElement] = import_react6.useState(null);
3257
3630
  const scheme = useColorScheme(element);
3258
3631
  return { ref: setElement, "data-rsp-scheme": scheme };
3259
3632
  }
@@ -3634,7 +4007,7 @@ class WorkbookSelectionManager {
3634
4007
  }
3635
4008
 
3636
4009
  // src/workbook/workbook.tsx
3637
- var jsx_runtime4 = require("react/jsx-runtime");
4010
+ var jsx_runtime5 = require("react/jsx-runtime");
3638
4011
  var getWrapCellStyle = (style) => style?.wrapText ? {
3639
4012
  whiteSpace: "pre-wrap",
3640
4013
  overflowWrap: "anywhere",
@@ -3665,7 +4038,7 @@ var getBorderCellStyle = (style) => (() => {
3665
4038
  ...borderSides.left && { borderLeftColor: style.borderColor }
3666
4039
  };
3667
4040
  })();
3668
- var Edit2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime4.jsxs("svg", {
4041
+ var PlusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime5.jsxs("svg", {
3669
4042
  xmlns: "http://www.w3.org/2000/svg",
3670
4043
  width: "16",
3671
4044
  height: "16",
@@ -3678,72 +4051,15 @@ var Edit2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime4.j
3678
4051
  className,
3679
4052
  ...props,
3680
4053
  children: [
3681
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
3682
- d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"
3683
- }),
3684
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
3685
- d: "m15 5 4 4"
3686
- })
3687
- ]
3688
- });
3689
- var Trash2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime4.jsxs("svg", {
3690
- xmlns: "http://www.w3.org/2000/svg",
3691
- width: "16",
3692
- height: "16",
3693
- viewBox: "0 0 24 24",
3694
- fill: "none",
3695
- stroke: "currentColor",
3696
- strokeWidth: "2",
3697
- strokeLinecap: "round",
3698
- strokeLinejoin: "round",
3699
- className,
3700
- ...props,
3701
- children: [
3702
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
3703
- d: "M3 6h18"
3704
- }),
3705
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
3706
- d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
3707
- }),
3708
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
3709
- d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
3710
- }),
3711
- /* @__PURE__ */ jsx_runtime4.jsx("line", {
3712
- x1: "10",
3713
- x2: "10",
3714
- y1: "11",
3715
- y2: "17"
3716
- }),
3717
- /* @__PURE__ */ jsx_runtime4.jsx("line", {
3718
- x1: "14",
3719
- x2: "14",
3720
- y1: "11",
3721
- y2: "17"
3722
- })
3723
- ]
3724
- });
3725
- var PlusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime4.jsxs("svg", {
3726
- xmlns: "http://www.w3.org/2000/svg",
3727
- width: "16",
3728
- height: "16",
3729
- viewBox: "0 0 24 24",
3730
- fill: "none",
3731
- stroke: "currentColor",
3732
- strokeWidth: "2",
3733
- strokeLinecap: "round",
3734
- strokeLinejoin: "round",
3735
- className,
3736
- ...props,
3737
- children: [
3738
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
4054
+ /* @__PURE__ */ jsx_runtime5.jsx("path", {
3739
4055
  d: "M5 12h14"
3740
4056
  }),
3741
- /* @__PURE__ */ jsx_runtime4.jsx("path", {
4057
+ /* @__PURE__ */ jsx_runtime5.jsx("path", {
3742
4058
  d: "M12 5v14"
3743
4059
  })
3744
4060
  ]
3745
4061
  });
3746
- var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime4.jsx("svg", {
4062
+ var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime5.jsx("svg", {
3747
4063
  xmlns: "http://www.w3.org/2000/svg",
3748
4064
  width: "16",
3749
4065
  height: "16",
@@ -3755,13 +4071,13 @@ var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx_runtime4.j
3755
4071
  strokeLinejoin: "round",
3756
4072
  className,
3757
4073
  ...props,
3758
- children: /* @__PURE__ */ jsx_runtime4.jsx("path", {
4074
+ children: /* @__PURE__ */ jsx_runtime5.jsx("path", {
3759
4075
  d: "M5 12h14"
3760
4076
  })
3761
4077
  });
3762
4078
  function useEngine(engine) {
3763
- const [state, setState] = import_react6.useState(() => engine.getState());
3764
- import_react6.default.useEffect(() => {
4079
+ const [state, setState] = import_react7.useState(() => engine.getState());
4080
+ import_react7.default.useEffect(() => {
3765
4081
  return engine.onUpdate(() => {
3766
4082
  setState(engine.getState());
3767
4083
  });
@@ -3867,11 +4183,11 @@ function FormulaSheet({
3867
4183
  ...rest
3868
4184
  }) {
3869
4185
  const schemeRoot = useSchemeRoot();
3870
- const [selectedArea, setSelectedArea] = import_react6.useState(null);
3871
- const clipboardManager = import_react6.default.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
4186
+ const [selectedArea, setSelectedArea] = import_react7.useState(null);
4187
+ const clipboardManager = import_react7.default.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3872
4188
  const state = useEngine(engine);
3873
4189
  const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
3874
- const getEngineTableAt = import_react6.useCallback((cell) => {
4190
+ const getEngineTableAt = import_react7.useCallback((cell) => {
3875
4191
  const table = engine.isCellInTable({
3876
4192
  workbookName,
3877
4193
  sheetName,
@@ -3903,27 +4219,21 @@ function FormulaSheet({
3903
4219
  } : {}
3904
4220
  };
3905
4221
  }, [engine, sheetName, workbookName]);
3906
- const formulaNavigation = import_react6.useMemo(() => ({
4222
+ const formulaNavigation = import_react7.useMemo(() => ({
3907
4223
  ...selection?.navigation,
3908
4224
  getTableAt: selection?.navigation?.getTableAt ?? getEngineTableAt
3909
4225
  }), [getEngineTableAt, selection?.navigation]);
3910
- const onCellDataChange = import_react6.useCallback((updatedSpreadsheet) => {
3911
- const data = new Map(updatedSpreadsheet);
3912
- data.forEach((value, key) => {
3913
- if (typeof value === "string") {
4226
+ const onCellDataPatch = import_react7.useCallback((patch) => {
4227
+ engine.transact(() => {
4228
+ patch.forEach((value, key) => {
3914
4229
  const { colIndex, rowIndex } = import_formula_engine2.parseCellReference(key);
3915
- const coercedValue = coerceCellInput(value, engine.getCellDataType({ workbookName, sheetName, colIndex, rowIndex }));
3916
- if (coercedValue === undefined) {
3917
- data.delete(key);
3918
- } else {
3919
- data.set(key, coercedValue);
3920
- }
3921
- }
4230
+ const address = { workbookName, sheetName, colIndex, rowIndex };
4231
+ engine.setCellContent(address, typeof value === "string" ? coerceCellInput(value, engine.getCellDataType(address)) : value);
4232
+ });
3922
4233
  });
3923
- engine.setSheetContent({ sheetName, workbookName }, data);
3924
4234
  }, [sheetName, engine, workbookName]);
3925
4235
  const passedSelectionEffects = selection?.effects;
3926
- const selectionManagerEffects = import_react6.useCallback((spreadsheetSelectionManager) => {
4236
+ const selectionManagerEffects = import_react7.useCallback((spreadsheetSelectionManager) => {
3927
4237
  const convertSmAreaToSpreadsheetRange = (area) => {
3928
4238
  const rowEnd = area.end.row.type === "infinity" ? {
3929
4239
  type: "infinity",
@@ -4047,30 +4357,210 @@ function FormulaSheet({
4047
4357
  viewId,
4048
4358
  workbookName
4049
4359
  ]);
4360
+ const renderCallbacks = import_react7.useMemo(() => {
4361
+ const metadata = engine.getWorkbookMetadata(workbookName);
4362
+ const defaults = metadata?.defaultCellStyle;
4363
+ const fontStyle = (style) => ({
4364
+ ...style?.fontFamily && { fontFamily: style.fontFamily },
4365
+ ...style?.bold !== undefined && { fontWeight: style.bold ? "bold" : "normal" },
4366
+ ...style?.italic !== undefined && { fontStyle: style.italic ? "italic" : "normal" },
4367
+ ...style?.underline !== undefined && { textDecoration: style.underline ? "underline" : "none" }
4368
+ });
4369
+ const styles = new Map;
4370
+ const getStyle = (cell) => {
4371
+ if (styles.has(cell.id))
4372
+ return styles.get(cell.id);
4373
+ const explicit = engine.getCellStyle({
4374
+ sheetName,
4375
+ workbookName,
4376
+ colIndex: cell.colIndex,
4377
+ rowIndex: cell.rowIndex
4378
+ });
4379
+ const style = defaults ? { ...defaults, ...explicit } : explicit;
4380
+ if (styles.size >= 4096)
4381
+ styles.clear();
4382
+ styles.set(cell.id, style);
4383
+ return style;
4384
+ };
4385
+ return {
4386
+ customCellStyle: (cell) => {
4387
+ const conditionalStyle = getStyle(cell);
4388
+ const tableInfo = engine.isCellInTable({
4389
+ sheetName,
4390
+ colIndex: cell.colIndex,
4391
+ rowIndex: cell.rowIndex,
4392
+ workbookName
4393
+ });
4394
+ if (!tableInfo) {
4395
+ const style = conditionalStyle ? {
4396
+ ...conditionalStyle.backgroundColor && {
4397
+ backgroundColor: conditionalStyle.backgroundColor
4398
+ },
4399
+ ...conditionalStyle.color && {
4400
+ color: conditionalStyle.color
4401
+ },
4402
+ ...conditionalStyle.fontSize && {
4403
+ fontSize: conditionalStyle.fontSize
4404
+ },
4405
+ ...fontStyle(conditionalStyle),
4406
+ ...getBorderCellStyle(conditionalStyle),
4407
+ ...getWrapCellStyle(conditionalStyle)
4408
+ } : {};
4409
+ return customCellStyle ? customCellStyle(cell, style) : style;
4410
+ }
4411
+ const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
4412
+ const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
4413
+ const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
4414
+ const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
4415
+ const style = {
4416
+ border: "1px solid var(--_rsp-table-border-light)"
4417
+ };
4418
+ if (isHeaderRow) {
4419
+ style.backgroundColor = "var(--_rsp-table-header-bg)";
4420
+ style.color = "var(--_rsp-table-header-color)";
4421
+ style.fontWeight = "bold";
4422
+ style.borderBottom = "2px solid var(--_rsp-table-header-border)";
4423
+ } else {
4424
+ const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
4425
+ if (dataRowIndex % 2 === 0) {
4426
+ style.backgroundColor = "var(--_rsp-table-even-row-bg)";
4427
+ } else {
4428
+ style.backgroundColor = "var(--_rsp-table-odd-row-bg)";
4429
+ }
4430
+ }
4431
+ if (isFirstColumn) {
4432
+ style.borderLeft = "2px solid var(--_rsp-table-border-color)";
4433
+ }
4434
+ if (isLastColumn) {
4435
+ style.borderRight = "2px solid var(--_rsp-table-border-color)";
4436
+ }
4437
+ if (isHeaderRow) {
4438
+ style.borderTop = "2px solid var(--_rsp-table-border-color)";
4439
+ }
4440
+ if (isLastRow) {
4441
+ style.borderBottom = "2px solid var(--_rsp-table-border-color)";
4442
+ }
4443
+ if (conditionalStyle?.backgroundColor) {
4444
+ style.backgroundColor = conditionalStyle.backgroundColor;
4445
+ }
4446
+ if (conditionalStyle?.color) {
4447
+ style.color = conditionalStyle.color;
4448
+ }
4449
+ if (conditionalStyle?.fontSize) {
4450
+ style.fontSize = conditionalStyle.fontSize;
4451
+ }
4452
+ Object.assign(style, fontStyle(conditionalStyle));
4453
+ Object.assign(style, getBorderCellStyle(conditionalStyle));
4454
+ Object.assign(style, getWrapCellStyle(conditionalStyle));
4455
+ if (customCellStyle) {
4456
+ return customCellStyle(cell, style);
4457
+ }
4458
+ return style;
4459
+ },
4460
+ customCellRenderer: (cell) => {
4461
+ const cellRef = import_formula_engine2.getCellReference({
4462
+ colIndex: cell.colIndex,
4463
+ rowIndex: cell.rowIndex
4464
+ });
4465
+ const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
4466
+ sheetName,
4467
+ workbookName,
4468
+ colIndex: cell.colIndex,
4469
+ rowIndex: cell.rowIndex
4470
+ }, verboseErrors);
4471
+ const conditionalStyle = getStyle(cell);
4472
+ const conditionalStyleProps = {};
4473
+ if (conditionalStyle?.backgroundColor) {
4474
+ conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
4475
+ }
4476
+ if (conditionalStyle?.color) {
4477
+ conditionalStyleProps.color = conditionalStyle.color;
4478
+ }
4479
+ if (conditionalStyle?.fontSize) {
4480
+ conditionalStyleProps.fontSize = conditionalStyle.fontSize;
4481
+ }
4482
+ Object.assign(conditionalStyleProps, fontStyle(conditionalStyle), getWrapContentStyle(conditionalStyle));
4483
+ let defaultElement;
4484
+ if (showFormulas) {
4485
+ defaultElement = /* @__PURE__ */ jsx_runtime5.jsx("div", {
4486
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4487
+ "data-conditional-text-color": conditionalStyle?.color,
4488
+ style: conditionalStyleProps,
4489
+ children: getCellDisplayText(value)
4490
+ });
4491
+ } else if (typeof value === "boolean") {
4492
+ defaultElement = /* @__PURE__ */ jsx_runtime5.jsx("div", {
4493
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4494
+ "data-conditional-text-color": conditionalStyle?.color,
4495
+ "data-type": "boolean",
4496
+ style: {
4497
+ ...conditionalStyleProps,
4498
+ width: "100%",
4499
+ textAlign: "center",
4500
+ fontFamily: conditionalStyle?.fontFamily ?? "'Courier New', monospace"
4501
+ },
4502
+ children: value ? "TRUE" : "FALSE"
4503
+ });
4504
+ } else if (typeof value === "number") {
4505
+ defaultElement = /* @__PURE__ */ jsx_runtime5.jsx("div", {
4506
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4507
+ "data-conditional-text-color": conditionalStyle?.color,
4508
+ "data-type": "number",
4509
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
4510
+ children: value.toLocaleString("en-US", {
4511
+ minimumFractionDigits: 0,
4512
+ maximumFractionDigits: 10
4513
+ })
4514
+ });
4515
+ } else {
4516
+ defaultElement = /* @__PURE__ */ jsx_runtime5.jsx("div", {
4517
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4518
+ "data-conditional-text-color": conditionalStyle?.color,
4519
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
4520
+ children: getCellDisplayText(value)
4521
+ });
4522
+ }
4523
+ if (customCellRenderer) {
4524
+ return customCellRenderer(cell, defaultElement);
4525
+ }
4526
+ return defaultElement;
4527
+ }
4528
+ };
4529
+ }, [
4530
+ engine,
4531
+ state,
4532
+ workbookName,
4533
+ sheetName,
4534
+ sheet,
4535
+ showFormulas,
4536
+ verboseErrors,
4537
+ customCellStyle,
4538
+ customCellRenderer
4539
+ ]);
4050
4540
  if (!sheet) {
4051
- return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4541
+ return /* @__PURE__ */ jsx_runtime5.jsxs("div", {
4052
4542
  className: "rsp-root rsp-workbook",
4053
4543
  ...schemeRoot,
4054
4544
  children: [
4055
4545
  toolbar,
4056
- /* @__PURE__ */ jsx_runtime4.jsx("div", {
4546
+ /* @__PURE__ */ jsx_runtime5.jsx("div", {
4057
4547
  style: { flex: 1 }
4058
4548
  })
4059
4549
  ]
4060
4550
  });
4061
4551
  }
4062
- return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4552
+ return /* @__PURE__ */ jsx_runtime5.jsxs("div", {
4063
4553
  className: "rsp-root rsp-workbook",
4064
4554
  ...schemeRoot,
4065
4555
  children: [
4066
4556
  toolbar,
4067
- /* @__PURE__ */ jsx_runtime4.jsx("div", {
4557
+ /* @__PURE__ */ jsx_runtime5.jsx("div", {
4068
4558
  className: "rsp-workbook-body",
4069
- children: /* @__PURE__ */ jsx_runtime4.jsx(Spreadsheet, {
4559
+ children: /* @__PURE__ */ jsx_runtime5.jsx(Spreadsheet, {
4070
4560
  disableClipboard: true,
4071
4561
  style: { height: "100%", width: "100%" },
4072
4562
  cellData: sheet?.content,
4073
- onCellDataChange,
4563
+ onCellDataPatch,
4074
4564
  onCellDataChangeError,
4075
4565
  ...rest,
4076
4566
  components,
@@ -4088,183 +4578,7 @@ function FormulaSheet({
4088
4578
  containerProps: isSelected && selectedArea ? {
4089
4579
  "data-grid-no-interaction": "true"
4090
4580
  } : {},
4091
- customCellStyle: (cell) => {
4092
- const conditionalStyle = engine.getCellStyle({
4093
- sheetName,
4094
- colIndex: cell.colIndex,
4095
- rowIndex: cell.rowIndex,
4096
- workbookName
4097
- });
4098
- const tableInfo = engine.isCellInTable({
4099
- sheetName,
4100
- colIndex: cell.colIndex,
4101
- rowIndex: cell.rowIndex,
4102
- workbookName
4103
- });
4104
- if (!tableInfo) {
4105
- const style = conditionalStyle ? {
4106
- ...conditionalStyle.backgroundColor && {
4107
- backgroundColor: conditionalStyle.backgroundColor
4108
- },
4109
- ...conditionalStyle.color && {
4110
- color: conditionalStyle.color
4111
- },
4112
- ...conditionalStyle.fontSize && {
4113
- fontSize: conditionalStyle.fontSize
4114
- },
4115
- ...conditionalStyle.bold && {
4116
- fontWeight: "bold"
4117
- },
4118
- ...conditionalStyle.italic && {
4119
- fontStyle: "italic"
4120
- },
4121
- ...conditionalStyle.underline && {
4122
- textDecoration: "underline"
4123
- },
4124
- ...getBorderCellStyle(conditionalStyle),
4125
- ...getWrapCellStyle(conditionalStyle)
4126
- } : {};
4127
- return customCellStyle ? customCellStyle(cell, style) : style;
4128
- }
4129
- const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
4130
- const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
4131
- const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
4132
- const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
4133
- const style = {
4134
- border: "1px solid var(--_rsp-table-border-light)"
4135
- };
4136
- if (isHeaderRow) {
4137
- style.backgroundColor = "var(--_rsp-table-header-bg)";
4138
- style.color = "var(--_rsp-table-header-color)";
4139
- style.fontWeight = "bold";
4140
- style.borderBottom = "2px solid var(--_rsp-table-header-border)";
4141
- } else {
4142
- const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
4143
- if (dataRowIndex % 2 === 0) {
4144
- style.backgroundColor = "var(--_rsp-table-even-row-bg)";
4145
- } else {
4146
- style.backgroundColor = "var(--_rsp-table-odd-row-bg)";
4147
- }
4148
- }
4149
- if (isFirstColumn) {
4150
- style.borderLeft = "2px solid var(--_rsp-table-border-color)";
4151
- }
4152
- if (isLastColumn) {
4153
- style.borderRight = "2px solid var(--_rsp-table-border-color)";
4154
- }
4155
- if (isHeaderRow) {
4156
- style.borderTop = "2px solid var(--_rsp-table-border-color)";
4157
- }
4158
- if (isLastRow) {
4159
- style.borderBottom = "2px solid var(--_rsp-table-border-color)";
4160
- }
4161
- if (conditionalStyle?.backgroundColor) {
4162
- style.backgroundColor = conditionalStyle.backgroundColor;
4163
- }
4164
- if (conditionalStyle?.color) {
4165
- style.color = conditionalStyle.color;
4166
- }
4167
- if (conditionalStyle?.fontSize) {
4168
- style.fontSize = conditionalStyle.fontSize;
4169
- }
4170
- if (conditionalStyle?.bold) {
4171
- style.fontWeight = "bold";
4172
- }
4173
- if (conditionalStyle?.italic) {
4174
- style.fontStyle = "italic";
4175
- }
4176
- if (conditionalStyle?.underline) {
4177
- style.textDecoration = "underline";
4178
- }
4179
- Object.assign(style, getBorderCellStyle(conditionalStyle));
4180
- Object.assign(style, getWrapCellStyle(conditionalStyle));
4181
- if (customCellStyle) {
4182
- return customCellStyle(cell, style);
4183
- }
4184
- return style;
4185
- },
4186
- customCellRenderer: (cell) => {
4187
- const cellRef = import_formula_engine2.getCellReference({
4188
- colIndex: cell.colIndex,
4189
- rowIndex: cell.rowIndex
4190
- });
4191
- const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
4192
- sheetName,
4193
- workbookName,
4194
- colIndex: cell.colIndex,
4195
- rowIndex: cell.rowIndex
4196
- }, verboseErrors);
4197
- const conditionalStyle = engine.getCellStyle({
4198
- sheetName,
4199
- colIndex: cell.colIndex,
4200
- rowIndex: cell.rowIndex,
4201
- workbookName
4202
- });
4203
- const conditionalStyleProps = {};
4204
- if (conditionalStyle?.backgroundColor) {
4205
- conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
4206
- }
4207
- if (conditionalStyle?.color) {
4208
- conditionalStyleProps.color = conditionalStyle.color;
4209
- }
4210
- if (conditionalStyle?.fontSize) {
4211
- conditionalStyleProps.fontSize = conditionalStyle.fontSize;
4212
- }
4213
- if (conditionalStyle?.bold) {
4214
- conditionalStyleProps.fontWeight = "bold";
4215
- }
4216
- if (conditionalStyle?.italic) {
4217
- conditionalStyleProps.fontStyle = "italic";
4218
- }
4219
- if (conditionalStyle?.underline) {
4220
- conditionalStyleProps.textDecoration = "underline";
4221
- }
4222
- Object.assign(conditionalStyleProps, getWrapContentStyle(conditionalStyle));
4223
- let defaultElement;
4224
- if (showFormulas) {
4225
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4226
- "data-conditional-background": conditionalStyle?.backgroundColor,
4227
- "data-conditional-text-color": conditionalStyle?.color,
4228
- style: conditionalStyleProps,
4229
- children: getCellDisplayText(value)
4230
- });
4231
- } else if (typeof value === "boolean") {
4232
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4233
- "data-conditional-background": conditionalStyle?.backgroundColor,
4234
- "data-conditional-text-color": conditionalStyle?.color,
4235
- "data-type": "boolean",
4236
- style: {
4237
- ...conditionalStyleProps,
4238
- width: "100%",
4239
- textAlign: "center",
4240
- fontFamily: "'Courier New', monospace"
4241
- },
4242
- children: value ? "TRUE" : "FALSE"
4243
- });
4244
- } else if (typeof value === "number") {
4245
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4246
- "data-conditional-background": conditionalStyle?.backgroundColor,
4247
- "data-conditional-text-color": conditionalStyle?.color,
4248
- "data-type": "number",
4249
- style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
4250
- children: value.toLocaleString("en-US", {
4251
- minimumFractionDigits: 0,
4252
- maximumFractionDigits: 10
4253
- })
4254
- });
4255
- } else {
4256
- defaultElement = /* @__PURE__ */ jsx_runtime4.jsx("div", {
4257
- "data-conditional-background": conditionalStyle?.backgroundColor,
4258
- "data-conditional-text-color": conditionalStyle?.color,
4259
- style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
4260
- children: getCellDisplayText(value)
4261
- });
4262
- }
4263
- if (customCellRenderer) {
4264
- return customCellRenderer(cell, defaultElement);
4265
- }
4266
- return defaultElement;
4267
- }
4581
+ ...renderCallbacks
4268
4582
  })
4269
4583
  })
4270
4584
  ]
@@ -4294,18 +4608,16 @@ function FormulaWorkbook({
4294
4608
  ...rest
4295
4609
  }) {
4296
4610
  const schemeRoot = useSchemeRoot();
4297
- const state = useEngine(engine);
4298
- const [renamingSheet, setRenamingSheet] = import_react6.useState(null);
4299
- const [newSheetName, setNewSheetName] = import_react6.useState("");
4300
- const [spreadsheetZoom, setSpreadsheetZoom] = import_react6.useState(1);
4301
- const renameCommittedRef = import_react6.useRef(false);
4302
- const clipboardManager = import_react6.default.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
4611
+ useEngine(engine);
4612
+ const panelId = import_react7.default.useId();
4613
+ const [spreadsheetZoom, setSpreadsheetZoom] = import_react7.useState(1);
4614
+ const clipboardManager = import_react7.default.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
4303
4615
  const sheetNames = engine.getOrderedSheetNames(workbookName);
4304
- const [internalActiveSheet, setInternalActiveSheet] = import_react6.useState(() => {
4616
+ const [internalActiveSheet, setInternalActiveSheet] = import_react7.useState(() => {
4305
4617
  return sheetNames[0] || "";
4306
4618
  });
4307
- const isControlledRef = import_react6.useRef(controlledActiveSheet !== undefined);
4308
- import_react6.default.useEffect(() => {
4619
+ const isControlledRef = import_react7.useRef(controlledActiveSheet !== undefined);
4620
+ import_react7.default.useEffect(() => {
4309
4621
  const isCurrentlyControlled = controlledActiveSheet !== undefined;
4310
4622
  if (isControlledRef.current !== isCurrentlyControlled) {
4311
4623
  console.error(`FormulaWorkbook: Component changed from ${isControlledRef.current ? "controlled" : "uncontrolled"} to ${isCurrentlyControlled ? "controlled" : "uncontrolled"} mode. ` + `A component should not switch between controlled and uncontrolled modes. ` + `Decide between using activeSheet and onActiveSheetChange props (controlled) or neither (uncontrolled) for the lifetime of the component.`);
@@ -4313,100 +4625,61 @@ function FormulaWorkbook({
4313
4625
  isControlledRef.current = isCurrentlyControlled;
4314
4626
  }, [controlledActiveSheet]);
4315
4627
  const activeSheet = controlledActiveSheet ?? internalActiveSheet;
4316
- const handleActiveSheetChange = import_react6.useCallback((sheetName) => {
4628
+ const handleActiveSheetChange = import_react7.useCallback((sheetName) => {
4317
4629
  if (onActiveSheetChange) {
4318
4630
  onActiveSheetChange(sheetName);
4319
- } else {
4320
- setInternalActiveSheet(sheetName);
4321
4631
  }
4322
- }, [onActiveSheetChange]);
4632
+ if (controlledActiveSheet === undefined)
4633
+ setInternalActiveSheet(sheetName);
4634
+ }, [onActiveSheetChange, controlledActiveSheet]);
4323
4635
  const activeSheetOverlayChildren = sheetOverlayChildren?.[activeSheet];
4324
4636
  const activeSheetSelectedOverlayId = sheetSelectedOverlayIds?.[activeSheet];
4325
4637
  const activeSheetColumnWidths = sheetLayouts?.[activeSheet]?.columnWidths;
4326
4638
  const activeSheetRowHeights = sheetLayouts?.[activeSheet]?.rowHeights;
4327
- const handleOverlaySelect = import_react6.useCallback((overlayId) => {
4639
+ const handleOverlaySelect = import_react7.useCallback((overlayId) => {
4328
4640
  onOverlaySelect?.(activeSheet, overlayId);
4329
4641
  }, [onOverlaySelect, activeSheet]);
4330
- const handleOverlayChildrenChange = import_react6.useCallback((children) => {
4642
+ const handleOverlayChildrenChange = import_react7.useCallback((children) => {
4331
4643
  onOverlayChildrenChange?.(activeSheet, children);
4332
4644
  }, [onOverlayChildrenChange, activeSheet]);
4333
- const handleColumnWidthsChange = import_react6.useCallback((columnWidths) => {
4645
+ const handleColumnWidthsChange = import_react7.useCallback((columnWidths) => {
4334
4646
  onSheetColumnWidthsChange?.(activeSheet, columnWidths);
4335
4647
  }, [activeSheet, onSheetColumnWidthsChange]);
4336
- const handleRowHeightsChange = import_react6.useCallback((rowHeights) => {
4648
+ const handleRowHeightsChange = import_react7.useCallback((rowHeights) => {
4337
4649
  onSheetRowHeightsChange?.(activeSheet, rowHeights);
4338
4650
  }, [activeSheet, onSheetRowHeightsChange]);
4339
4651
  const addSheet = () => {
4340
- try {
4341
- const newSheet = engine.createSheet({
4342
- workbookName
4343
- });
4344
- handleActiveSheetChange(newSheet.name);
4345
- } catch (error) {
4346
- console.error("Failed to add sheet:", error);
4347
- }
4348
- };
4349
- const startRenaming = (sheetName) => {
4350
- renameCommittedRef.current = false;
4351
- setRenamingSheet(sheetName);
4352
- setNewSheetName(sheetName);
4353
- };
4354
- const cancelRenaming = () => {
4355
- setRenamingSheet(null);
4356
- setNewSheetName("");
4652
+ const newSheet = engine.createSheet({ workbookName });
4653
+ handleActiveSheetChange(newSheet.name);
4357
4654
  };
4358
4655
  const renameSheet = (oldName, newName) => {
4359
- if (renameCommittedRef.current) {
4360
- cancelRenaming();
4361
- return;
4362
- }
4363
- if (!newName.trim() || newName.trim() === oldName) {
4364
- cancelRenaming();
4365
- return;
4366
- }
4367
- try {
4368
- renameCommittedRef.current = true;
4369
- engine.renameSheet({
4370
- workbookName,
4371
- sheetName: oldName,
4372
- newSheetName: newName.trim()
4373
- });
4374
- if (activeSheet === oldName) {
4375
- handleActiveSheetChange(newName.trim());
4376
- }
4377
- cancelRenaming();
4378
- } catch (error) {
4379
- console.error("Failed to rename sheet:", error);
4380
- cancelRenaming();
4381
- }
4656
+ engine.renameSheet({ workbookName, sheetName: oldName, newSheetName: newName });
4657
+ if (activeSheet === oldName)
4658
+ handleActiveSheetChange(newName);
4382
4659
  };
4383
4660
  const deleteSheet = (sheetName) => {
4384
4661
  if (sheetNames.length <= 1) {
4385
4662
  console.error("Cannot delete the last sheet");
4386
4663
  return;
4387
4664
  }
4388
- try {
4389
- engine.removeSheet({ workbookName, sheetName });
4390
- if (activeSheet === sheetName) {
4391
- const remainingSheets = sheetNames.filter((name) => name !== sheetName);
4392
- const nextSheet = remainingSheets[0];
4393
- if (nextSheet) {
4394
- handleActiveSheetChange(nextSheet);
4395
- }
4665
+ engine.removeSheet({ workbookName, sheetName });
4666
+ if (activeSheet === sheetName) {
4667
+ const remainingSheets = sheetNames.filter((name) => name !== sheetName);
4668
+ const nextSheet = remainingSheets[Math.min(sheetNames.indexOf(sheetName), remainingSheets.length - 1)];
4669
+ if (nextSheet) {
4670
+ handleActiveSheetChange(nextSheet);
4396
4671
  }
4397
- } catch (error) {
4398
- console.error("Failed to delete sheet:", error);
4399
4672
  }
4400
4673
  };
4401
- return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4674
+ return /* @__PURE__ */ jsx_runtime5.jsxs("div", {
4402
4675
  className: "rsp-root rsp-workbook",
4403
4676
  ...schemeRoot,
4404
4677
  children: [
4405
4678
  toolbar,
4406
- /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4679
+ /* @__PURE__ */ jsx_runtime5.jsxs("div", {
4407
4680
  className: "rsp-workbook-header",
4408
4681
  children: [
4409
- /* @__PURE__ */ jsx_runtime4.jsx("div", {
4682
+ /* @__PURE__ */ jsx_runtime5.jsx("div", {
4410
4683
  style: {
4411
4684
  display: "flex",
4412
4685
  alignItems: "center",
@@ -4415,13 +4688,13 @@ function FormulaWorkbook({
4415
4688
  overflow: "hidden",
4416
4689
  textOverflow: "ellipsis"
4417
4690
  },
4418
- children: /* @__PURE__ */ jsx_runtime4.jsx("span", {
4691
+ children: /* @__PURE__ */ jsx_runtime5.jsx("span", {
4419
4692
  className: "rsp-workbook-name",
4420
4693
  "data-testid": `workbook-name-${workbookName}`,
4421
4694
  children: workbookName
4422
4695
  })
4423
4696
  }),
4424
- /* @__PURE__ */ jsx_runtime4.jsxs("span", {
4697
+ /* @__PURE__ */ jsx_runtime5.jsxs("span", {
4425
4698
  className: "rsp-zoom-value",
4426
4699
  "data-testid": `sheet-count-${workbookName}`,
4427
4700
  children: [
@@ -4431,16 +4704,19 @@ function FormulaWorkbook({
4431
4704
  })
4432
4705
  ]
4433
4706
  }),
4434
- /* @__PURE__ */ jsx_runtime4.jsx("div", {
4435
- style: { flex: 1, overflow: "hidden" },
4436
- children: /* @__PURE__ */ jsx_runtime4.jsx("div", {
4707
+ /* @__PURE__ */ jsx_runtime5.jsx("div", {
4708
+ id: panelId,
4709
+ role: "tabpanel",
4710
+ "aria-label": activeSheet || "Sheet",
4711
+ style: { flex: 1, minHeight: 0, overflow: "hidden" },
4712
+ children: /* @__PURE__ */ jsx_runtime5.jsx("div", {
4437
4713
  style: {
4438
4714
  transform: spreadsheetZoom !== 1 ? `scale(${spreadsheetZoom})` : undefined,
4439
4715
  transformOrigin: "0 0",
4440
4716
  width: spreadsheetZoom !== 1 ? `${100 / spreadsheetZoom}%` : "100%",
4441
4717
  height: spreadsheetZoom !== 1 ? `${100 / spreadsheetZoom}%` : "100%"
4442
4718
  },
4443
- children: /* @__PURE__ */ jsx_runtime4.jsx(FormulaSheet, {
4719
+ children: /* @__PURE__ */ jsx_runtime5.jsx(FormulaSheet, {
4444
4720
  sheetName: activeSheet,
4445
4721
  workbookName,
4446
4722
  engine,
@@ -4464,138 +4740,53 @@ function FormulaWorkbook({
4464
4740
  }, `${workbookName}-${activeSheet}`)
4465
4741
  })
4466
4742
  }),
4467
- /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4468
- className: "rsp-sheet-tabs",
4469
- children: [
4470
- /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4471
- className: "rsp-sheet-tabs-scroll",
4472
- children: [
4473
- /* @__PURE__ */ jsx_runtime4.jsx("div", {
4474
- style: { display: "flex", alignItems: "center", gap: 4 },
4475
- children: sheetNames.map((sheetName) => {
4476
- const isRenaming = renamingSheet === sheetName;
4477
- const isActive = activeSheet === sheetName;
4478
- return /* @__PURE__ */ jsx_runtime4.jsx("div", {
4479
- children: isRenaming ? /* @__PURE__ */ jsx_runtime4.jsx("div", {
4480
- style: { display: "flex", alignItems: "center", gap: 4 },
4481
- children: /* @__PURE__ */ jsx_runtime4.jsx("input", {
4482
- className: "rsp-sheet-tab-input",
4483
- type: "text",
4484
- value: newSheetName,
4485
- onChange: (e) => setNewSheetName(e.target.value),
4486
- onKeyDown: (e) => {
4487
- if (e.key === "Enter") {
4488
- renameSheet(sheetName, newSheetName);
4489
- } else if (e.key === "Escape") {
4490
- cancelRenaming();
4491
- }
4492
- },
4493
- onBlur: () => {
4494
- if (newSheetName.trim() && newSheetName.trim() !== sheetName) {
4495
- renameSheet(sheetName, newSheetName);
4496
- } else {
4497
- cancelRenaming();
4498
- }
4499
- },
4500
- autoFocus: true,
4501
- "data-testid": `rename-sheet-input-${sheetName}`
4502
- })
4503
- }) : /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4504
- className: cn("rsp-sheet-tab", {
4505
- "rsp-sheet-tab-active": isActive
4506
- }),
4507
- onClick: () => handleActiveSheetChange(sheetName),
4508
- onDoubleClick: () => startRenaming(sheetName),
4509
- "data-testid": `sheet-tab-${sheetName}`,
4510
- children: [
4511
- /* @__PURE__ */ jsx_runtime4.jsx("span", {
4512
- children: sheetName
4513
- }),
4514
- /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4515
- className: "sheet-tab-actions",
4516
- children: [
4517
- /* @__PURE__ */ jsx_runtime4.jsx("button", {
4518
- type: "button",
4519
- className: "rsp-icon-button",
4520
- onClick: (e) => {
4521
- e.stopPropagation();
4522
- startRenaming(sheetName);
4523
- },
4524
- title: "Rename Sheet",
4525
- "data-testid": `rename-sheet-${sheetName}`,
4526
- children: /* @__PURE__ */ jsx_runtime4.jsx(Edit2Icon, {
4527
- style: { width: 12, height: 12 }
4528
- })
4529
- }),
4530
- sheetNames.length > 1 && /* @__PURE__ */ jsx_runtime4.jsx("button", {
4531
- type: "button",
4532
- className: "rsp-icon-button rsp-icon-button-delete",
4533
- onClick: (e) => {
4534
- e.stopPropagation();
4535
- if (confirm(`Are you sure you want to delete sheet "${sheetName}"?`)) {
4536
- deleteSheet(sheetName);
4537
- }
4538
- },
4539
- title: "Delete Sheet",
4540
- "data-testid": `delete-sheet-${sheetName}`,
4541
- children: /* @__PURE__ */ jsx_runtime4.jsx(Trash2Icon, {
4542
- style: { width: 12, height: 12 }
4543
- })
4544
- })
4545
- ]
4546
- })
4547
- ]
4548
- })
4549
- }, sheetName);
4550
- })
4551
- }),
4552
- /* @__PURE__ */ jsx_runtime4.jsx("button", {
4553
- type: "button",
4554
- className: "rsp-add-sheet-button",
4555
- onClick: addSheet,
4556
- title: "Add Sheet",
4557
- "data-testid": `add-sheet-${workbookName}`,
4558
- children: /* @__PURE__ */ jsx_runtime4.jsx(PlusIcon, {
4559
- style: { width: 12, height: 12 }
4560
- })
4743
+ /* @__PURE__ */ jsx_runtime5.jsx(SheetNavigation, {
4744
+ sheets: sheetNames,
4745
+ activeSheet,
4746
+ workbookName,
4747
+ panelId,
4748
+ onSelect: handleActiveSheetChange,
4749
+ onAdd: addSheet,
4750
+ onRename: renameSheet,
4751
+ onDelete: deleteSheet,
4752
+ children: /* @__PURE__ */ jsx_runtime5.jsxs("div", {
4753
+ className: "rsp-zoom-controls",
4754
+ children: [
4755
+ /* @__PURE__ */ jsx_runtime5.jsx("button", {
4756
+ type: "button",
4757
+ className: "rsp-icon-button",
4758
+ onClick: () => setSpreadsheetZoom(Math.max(0.5, Math.round((spreadsheetZoom - 0.1) * 10) / 10)),
4759
+ disabled: spreadsheetZoom <= 0.5,
4760
+ title: "Zoom Out",
4761
+ style: { opacity: spreadsheetZoom <= 0.5 ? 0.5 : 1 },
4762
+ children: /* @__PURE__ */ jsx_runtime5.jsx(MinusIcon, {
4763
+ style: { width: 12, height: 12 }
4561
4764
  })
4562
- ]
4563
- }),
4564
- /* @__PURE__ */ jsx_runtime4.jsxs("div", {
4565
- className: "rsp-zoom-controls",
4566
- children: [
4567
- /* @__PURE__ */ jsx_runtime4.jsx("button", {
4568
- type: "button",
4569
- className: "rsp-icon-button",
4570
- onClick: () => setSpreadsheetZoom(Math.max(0.5, spreadsheetZoom - 0.1)),
4571
- disabled: spreadsheetZoom <= 0.5,
4572
- title: "Zoom Out",
4573
- style: { opacity: spreadsheetZoom <= 0.5 ? 0.5 : 1 },
4574
- children: /* @__PURE__ */ jsx_runtime4.jsx(MinusIcon, {
4575
- style: { width: 12, height: 12 }
4576
- })
4577
- }),
4578
- /* @__PURE__ */ jsx_runtime4.jsxs("span", {
4579
- className: "rsp-zoom-value",
4580
- children: [
4581
- Math.round(spreadsheetZoom * 100),
4582
- "%"
4583
- ]
4584
- }),
4585
- /* @__PURE__ */ jsx_runtime4.jsx("button", {
4586
- type: "button",
4587
- className: "rsp-icon-button",
4588
- onClick: () => setSpreadsheetZoom(Math.min(2, spreadsheetZoom + 0.1)),
4589
- disabled: spreadsheetZoom >= 2,
4590
- title: "Zoom In",
4591
- style: { opacity: spreadsheetZoom >= 2 ? 0.5 : 1 },
4592
- children: /* @__PURE__ */ jsx_runtime4.jsx(PlusIcon, {
4593
- style: { width: 12, height: 12 }
4594
- })
4765
+ }),
4766
+ /* @__PURE__ */ jsx_runtime5.jsxs("button", {
4767
+ type: "button",
4768
+ className: "rsp-zoom-value rsp-zoom-reset",
4769
+ "aria-label": `Zoom ${Math.round(spreadsheetZoom * 100)}%. Reset to 100%`,
4770
+ title: "Reset zoom to 100%",
4771
+ onClick: () => setSpreadsheetZoom(1),
4772
+ children: [
4773
+ Math.round(spreadsheetZoom * 100),
4774
+ "%"
4775
+ ]
4776
+ }),
4777
+ /* @__PURE__ */ jsx_runtime5.jsx("button", {
4778
+ type: "button",
4779
+ className: "rsp-icon-button",
4780
+ onClick: () => setSpreadsheetZoom(Math.min(2, Math.round((spreadsheetZoom + 0.1) * 10) / 10)),
4781
+ disabled: spreadsheetZoom >= 2,
4782
+ title: "Zoom In",
4783
+ style: { opacity: spreadsheetZoom >= 2 ? 0.5 : 1 },
4784
+ children: /* @__PURE__ */ jsx_runtime5.jsx(PlusIcon, {
4785
+ style: { width: 12, height: 12 }
4595
4786
  })
4596
- ]
4597
- })
4598
- ]
4787
+ })
4788
+ ]
4789
+ })
4599
4790
  })
4600
4791
  ]
4601
4792
  });
@@ -4607,9 +4798,9 @@ var DEFAULT_ZOOM = 1;
4607
4798
  var MIN_ZOOM = 0.5;
4608
4799
  var MAX_ZOOM = 3;
4609
4800
  // src/utils/use-click-away.ts
4610
- var import_react7 = require("react");
4801
+ var import_react8 = require("react");
4611
4802
  var useClickAway = (ref, callback) => {
4612
- import_react7.useEffect(() => {
4803
+ import_react8.useEffect(() => {
4613
4804
  const handleClickAway = (event) => {
4614
4805
  if (ref) {
4615
4806
  const away = !ref.contains(event.target);
@@ -4623,4 +4814,4 @@ var useClickAway = (ref, callback) => {
4623
4814
  }, [ref, callback]);
4624
4815
  };
4625
4816
 
4626
- //# debugId=422BCC484554257364756E2164756E21
4817
+ //# debugId=0C8DF59ED93BC04F64756E2164756E21