@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/mjs/lib.mjs CHANGED
@@ -1787,6 +1787,7 @@ var Spreadsheet = forwardRef(({
1787
1787
  "aria-describedby": ariaDescribedBy,
1788
1788
  cellData: controlledCellData,
1789
1789
  onCellDataChange,
1790
+ onCellDataPatch,
1790
1791
  onCellDataChangeError,
1791
1792
  rowCount,
1792
1793
  columnCount,
@@ -1991,6 +1992,7 @@ var Spreadsheet = forwardRef(({
1991
1992
  }, [overlayPlaceholder, containerDivRef, viewport, gridDims, placeholderPos]);
1992
1993
  const effectsRefOb = {
1993
1994
  onCellDataChange,
1995
+ onCellDataPatch,
1994
1996
  onCellDataChangeError,
1995
1997
  controlledCellData,
1996
1998
  parseValue,
@@ -2237,13 +2239,13 @@ var Spreadsheet = forwardRef(({
2237
2239
  return selectionManager.listenToUpdateData((updates) => {
2238
2240
  const editingState = selectionManager.isEditing;
2239
2241
  try {
2240
- const { cellData, onCellDataChange } = effectsRef.current;
2241
- const newData = new Map(cellData);
2242
+ const { cellData, onCellDataChange, onCellDataPatch, controlledCellData } = effectsRef.current;
2243
+ const patch = new Map;
2242
2244
  updates.forEach(({ value, rowIndex, colIndex }) => {
2243
2245
  const key = `${indexToColumn(colIndex)}${rowIndex + 1}`;
2244
2246
  const parseValue = effectsRef.current.parseValue;
2245
2247
  if (value === "") {
2246
- newData.delete(key);
2248
+ patch.set(key, undefined);
2247
2249
  } else {
2248
2250
  let parsedValue = value;
2249
2251
  if (parseValue && typeof value === "string") {
@@ -2263,10 +2265,23 @@ var Spreadsheet = forwardRef(({
2263
2265
  });
2264
2266
  }
2265
2267
  }
2266
- newData.set(key, parsedValue);
2268
+ patch.set(key, parsedValue);
2267
2269
  }
2268
2270
  });
2269
- onCellDataChange?.(newData);
2271
+ if (onCellDataPatch) {
2272
+ onCellDataPatch(patch);
2273
+ if (controlledCellData !== undefined)
2274
+ return;
2275
+ }
2276
+ const newData = new Map(cellData);
2277
+ patch.forEach((value, key) => {
2278
+ if (value === undefined)
2279
+ newData.delete(key);
2280
+ else
2281
+ newData.set(key, value);
2282
+ });
2283
+ if (!onCellDataPatch)
2284
+ onCellDataChange?.(newData);
2270
2285
  setInternalCellData(newData);
2271
2286
  } catch (error) {
2272
2287
  console.error("Failed to update spreadsheet cell data:", error);
@@ -3145,12 +3160,370 @@ import {
3145
3160
  parseCellReference as parseCellReference2,
3146
3161
  getCellReference as getCellReference2
3147
3162
  } from "@ricsam/formula-engine";
3148
- import React3, { useCallback as useCallback4, useMemo as useMemo2, useRef as useRef4, useState as useState6 } from "react";
3163
+ import React4, { useCallback as useCallback4, useMemo as useMemo2, useRef as useRef5, useState as useState7 } from "react";
3164
+
3165
+ // src/workbook/sheet-navigation.tsx
3166
+ import { useEffect as useEffect4, useId as useId2, useRef as useRef4, useState as useState5 } from "react";
3167
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3168
+ function SheetNavigation({
3169
+ sheets,
3170
+ activeSheet,
3171
+ workbookName,
3172
+ panelId,
3173
+ onSelect,
3174
+ onAdd,
3175
+ onRename,
3176
+ onDelete,
3177
+ children
3178
+ }) {
3179
+ const id = useId2();
3180
+ const root = useRef4(null);
3181
+ const menuButton = useRef4(null);
3182
+ const menu = useRef4(null);
3183
+ const input = useRef4(null);
3184
+ const [menuOpen, setMenuOpen] = useState5(false);
3185
+ const [renaming, setRenaming] = useState5(null);
3186
+ const [name, setName] = useState5("");
3187
+ const [error, setError] = useState5("");
3188
+ const [notice, setNotice] = useState5("");
3189
+ const selected = sheets.includes(activeSheet) ? activeSheet : sheets[0];
3190
+ const focusTab = (sheet = selected) => {
3191
+ const tabs = root.current?.querySelectorAll('[role="tab"]');
3192
+ tabs?.[sheets.indexOf(sheet ?? "")]?.focus();
3193
+ };
3194
+ useEffect4(() => {
3195
+ const tab = root.current?.querySelector('[aria-selected="true"]');
3196
+ const strip = tab?.parentElement;
3197
+ if (!tab || !strip)
3198
+ return;
3199
+ const bounds = strip.getBoundingClientRect();
3200
+ const target = tab.getBoundingClientRect();
3201
+ if (target.left < bounds.left)
3202
+ strip.scrollLeft -= bounds.left - target.left;
3203
+ else if (target.right > bounds.right)
3204
+ strip.scrollLeft += target.right - bounds.right;
3205
+ }, [activeSheet, sheets.join("\x00")]);
3206
+ useEffect4(() => {
3207
+ if (renaming) {
3208
+ input.current?.focus();
3209
+ input.current?.select();
3210
+ }
3211
+ }, [renaming]);
3212
+ useEffect4(() => {
3213
+ if (!menuOpen)
3214
+ return;
3215
+ menu.current?.querySelector("button")?.focus();
3216
+ const outside = (event) => {
3217
+ if (!root.current?.contains(event.target))
3218
+ setMenuOpen(false);
3219
+ };
3220
+ document.addEventListener("pointerdown", outside);
3221
+ return () => document.removeEventListener("pointerdown", outside);
3222
+ }, [menuOpen]);
3223
+ useEffect4(() => {
3224
+ setMenuOpen(false);
3225
+ setRenaming(null);
3226
+ setError("");
3227
+ }, [workbookName]);
3228
+ useEffect4(() => {
3229
+ if (renaming && !sheets.includes(renaming)) {
3230
+ setRenaming(null);
3231
+ setError("");
3232
+ }
3233
+ }, [renaming, sheets.join("\x00")]);
3234
+ const run = (action) => {
3235
+ try {
3236
+ action();
3237
+ setError("");
3238
+ return true;
3239
+ } catch (error) {
3240
+ setError(error instanceof Error ? error.message : "Unable to update this sheet. Please try again.");
3241
+ return false;
3242
+ }
3243
+ };
3244
+ const startRename = (sheet) => {
3245
+ setMenuOpen(false);
3246
+ setError("");
3247
+ setRenaming(sheet);
3248
+ setName(sheet);
3249
+ };
3250
+ const cancelRename = () => {
3251
+ setRenaming(null);
3252
+ setError("");
3253
+ focusTab();
3254
+ };
3255
+ const saveRename = (event) => {
3256
+ event.preventDefault();
3257
+ if (!renaming)
3258
+ return;
3259
+ const next = name.trim();
3260
+ if (!next) {
3261
+ setError("Enter a sheet name.");
3262
+ input.current?.focus();
3263
+ return;
3264
+ }
3265
+ if (sheets.some((sheet) => sheet !== renaming && sheet.toLocaleLowerCase() === next.toLocaleLowerCase())) {
3266
+ setError("A sheet with this name already exists. Choose a different name.");
3267
+ input.current?.focus();
3268
+ return;
3269
+ }
3270
+ if (run(() => {
3271
+ if (next !== renaming)
3272
+ onRename(renaming, next);
3273
+ })) {
3274
+ setRenaming(null);
3275
+ setNotice(`Sheet renamed to ${next}.`);
3276
+ menuButton.current?.focus();
3277
+ }
3278
+ };
3279
+ return /* @__PURE__ */ jsxs3("div", {
3280
+ ref: root,
3281
+ className: "rsp-sheet-navigation",
3282
+ onKeyDown: (event) => event.stopPropagation(),
3283
+ children: [
3284
+ /* @__PURE__ */ jsx4("span", {
3285
+ className: "rsp-sr-only",
3286
+ role: "status",
3287
+ children: notice
3288
+ }),
3289
+ renaming && /* @__PURE__ */ jsxs3("form", {
3290
+ className: "rsp-sheet-rename",
3291
+ onSubmit: saveRename,
3292
+ onKeyDown: (event) => {
3293
+ if (event.key === "Escape") {
3294
+ event.preventDefault();
3295
+ cancelRename();
3296
+ }
3297
+ },
3298
+ children: [
3299
+ /* @__PURE__ */ jsx4("label", {
3300
+ htmlFor: `${id}-name`,
3301
+ children: "Sheet name"
3302
+ }),
3303
+ /* @__PURE__ */ jsx4("input", {
3304
+ ref: input,
3305
+ id: `${id}-name`,
3306
+ className: "rsp-sheet-tab-input",
3307
+ value: name,
3308
+ onChange: (event) => {
3309
+ setName(event.target.value);
3310
+ setError("");
3311
+ },
3312
+ "aria-invalid": !!error,
3313
+ "aria-describedby": error ? `${id}-error` : undefined,
3314
+ "data-testid": `rename-sheet-input-${renaming}`
3315
+ }),
3316
+ /* @__PURE__ */ jsx4("button", {
3317
+ type: "submit",
3318
+ className: "rsp-sheet-action",
3319
+ children: "Save"
3320
+ }),
3321
+ /* @__PURE__ */ jsx4("button", {
3322
+ type: "button",
3323
+ className: "rsp-sheet-action",
3324
+ onClick: cancelRename,
3325
+ children: "Cancel"
3326
+ })
3327
+ ]
3328
+ }),
3329
+ error && /* @__PURE__ */ jsx4("div", {
3330
+ id: `${id}-error`,
3331
+ className: "rsp-sheet-error",
3332
+ role: "alert",
3333
+ children: error
3334
+ }),
3335
+ /* @__PURE__ */ jsxs3("div", {
3336
+ className: "rsp-sheet-tabs",
3337
+ children: [
3338
+ /* @__PURE__ */ jsx4("div", {
3339
+ className: "rsp-sheet-tabs-scroll",
3340
+ role: "tablist",
3341
+ "aria-label": `${workbookName} sheets`,
3342
+ children: sheets.map((sheet, index) => /* @__PURE__ */ jsx4("button", {
3343
+ type: "button",
3344
+ role: "tab",
3345
+ className: `rsp-sheet-tab${activeSheet === sheet ? " rsp-sheet-tab-active" : ""}`,
3346
+ "aria-controls": panelId,
3347
+ "aria-selected": activeSheet === sheet,
3348
+ tabIndex: sheet === selected ? 0 : -1,
3349
+ title: sheet,
3350
+ "data-testid": `sheet-tab-${sheet}`,
3351
+ onClick: () => {
3352
+ setMenuOpen(false);
3353
+ onSelect(sheet);
3354
+ },
3355
+ onDoubleClick: () => startRename(sheet),
3356
+ onKeyDown: (event) => {
3357
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
3358
+ let next;
3359
+ if (event.key === "ArrowRight")
3360
+ next = (index + (rtl ? -1 : 1) + sheets.length) % sheets.length;
3361
+ if (event.key === "ArrowLeft")
3362
+ next = (index + (rtl ? 1 : -1) + sheets.length) % sheets.length;
3363
+ if (event.key === "Home")
3364
+ next = 0;
3365
+ if (event.key === "End")
3366
+ next = sheets.length - 1;
3367
+ if (next !== undefined) {
3368
+ event.preventDefault();
3369
+ onSelect(sheets[next]);
3370
+ focusTab(sheets[next]);
3371
+ }
3372
+ if (event.key === "F2") {
3373
+ event.preventDefault();
3374
+ startRename(sheet);
3375
+ }
3376
+ },
3377
+ children: /* @__PURE__ */ jsx4("span", {
3378
+ children: sheet
3379
+ })
3380
+ }, sheet))
3381
+ }),
3382
+ /* @__PURE__ */ jsx4("button", {
3383
+ type: "button",
3384
+ className: "rsp-add-sheet-button",
3385
+ title: "Add Sheet",
3386
+ "aria-label": "Add Sheet",
3387
+ "data-testid": `add-sheet-${workbookName}`,
3388
+ onClick: () => {
3389
+ setMenuOpen(false);
3390
+ run(onAdd);
3391
+ },
3392
+ children: /* @__PURE__ */ jsx4("svg", {
3393
+ "aria-hidden": "true",
3394
+ width: "16",
3395
+ height: "16",
3396
+ viewBox: "0 0 24 24",
3397
+ fill: "none",
3398
+ stroke: "currentColor",
3399
+ strokeWidth: "2",
3400
+ strokeLinecap: "round",
3401
+ children: /* @__PURE__ */ jsx4("path", {
3402
+ d: "M12 5v14M5 12h14"
3403
+ })
3404
+ })
3405
+ }),
3406
+ /* @__PURE__ */ jsx4("button", {
3407
+ ref: menuButton,
3408
+ type: "button",
3409
+ className: "rsp-icon-button rsp-sheet-menu-trigger",
3410
+ title: "Sheet actions",
3411
+ "aria-label": selected ? `Sheet actions for ${selected}` : "Sheet actions",
3412
+ disabled: !selected,
3413
+ "aria-haspopup": "menu",
3414
+ "aria-expanded": menuOpen,
3415
+ "aria-controls": menuOpen ? `${id}-menu` : undefined,
3416
+ onClick: () => setMenuOpen((open) => !open),
3417
+ onKeyDown: (event) => {
3418
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3419
+ event.preventDefault();
3420
+ setMenuOpen(true);
3421
+ }
3422
+ },
3423
+ children: /* @__PURE__ */ jsxs3("svg", {
3424
+ "aria-hidden": "true",
3425
+ width: "16",
3426
+ height: "16",
3427
+ viewBox: "0 0 24 24",
3428
+ fill: "currentColor",
3429
+ children: [
3430
+ /* @__PURE__ */ jsx4("circle", {
3431
+ cx: "5",
3432
+ cy: "12",
3433
+ r: "2"
3434
+ }),
3435
+ /* @__PURE__ */ jsx4("circle", {
3436
+ cx: "12",
3437
+ cy: "12",
3438
+ r: "2"
3439
+ }),
3440
+ /* @__PURE__ */ jsx4("circle", {
3441
+ cx: "19",
3442
+ cy: "12",
3443
+ r: "2"
3444
+ })
3445
+ ]
3446
+ })
3447
+ }),
3448
+ children
3449
+ ]
3450
+ }),
3451
+ menuOpen && selected && /* @__PURE__ */ jsxs3("div", {
3452
+ ref: menu,
3453
+ id: `${id}-menu`,
3454
+ role: "menu",
3455
+ "aria-label": `Actions for ${selected}`,
3456
+ className: "rsp-sheet-menu",
3457
+ onBlur: (event) => {
3458
+ if (!event.currentTarget.contains(event.relatedTarget) && event.relatedTarget !== menuButton.current)
3459
+ setMenuOpen(false);
3460
+ },
3461
+ onKeyDown: (event) => {
3462
+ const items = Array.from(menu.current?.querySelectorAll("button") ?? []);
3463
+ const index = items.indexOf(document.activeElement);
3464
+ if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
3465
+ event.preventDefault();
3466
+ const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + items.length) % items.length;
3467
+ items[next]?.focus();
3468
+ }
3469
+ if (event.key === "Escape") {
3470
+ event.preventDefault();
3471
+ setMenuOpen(false);
3472
+ menuButton.current?.focus();
3473
+ }
3474
+ },
3475
+ children: [
3476
+ /* @__PURE__ */ jsx4("div", {
3477
+ className: "rsp-sheet-menu-name",
3478
+ title: selected,
3479
+ children: selected
3480
+ }),
3481
+ /* @__PURE__ */ jsxs3("button", {
3482
+ type: "button",
3483
+ role: "menuitem",
3484
+ onClick: () => startRename(selected),
3485
+ "data-testid": `rename-sheet-${selected}`,
3486
+ children: [
3487
+ "Rename sheet ",
3488
+ /* @__PURE__ */ jsx4("span", {
3489
+ "aria-hidden": "true",
3490
+ children: "F2"
3491
+ })
3492
+ ]
3493
+ }),
3494
+ /* @__PURE__ */ jsx4("button", {
3495
+ type: "button",
3496
+ role: "menuitem",
3497
+ "aria-disabled": sheets.length <= 1,
3498
+ className: "rsp-sheet-delete",
3499
+ "data-testid": `delete-sheet-${selected}`,
3500
+ onClick: () => {
3501
+ if (sheets.length <= 1)
3502
+ return;
3503
+ if (window.confirm(`Delete sheet "${selected}" and its contents?`)) {
3504
+ if (run(() => onDelete(selected))) {
3505
+ setMenuOpen(false);
3506
+ setNotice(`Sheet ${selected} deleted.`);
3507
+ menuButton.current?.focus();
3508
+ }
3509
+ }
3510
+ },
3511
+ children: "Delete sheet"
3512
+ }),
3513
+ sheets.length <= 1 && /* @__PURE__ */ jsx4("div", {
3514
+ className: "rsp-sheet-menu-hint",
3515
+ children: "Keep at least one sheet."
3516
+ })
3517
+ ]
3518
+ })
3519
+ ]
3520
+ });
3521
+ }
3149
3522
 
3150
3523
  // src/utils/use-scheme-root.ts
3151
- import { useState as useState5 } from "react";
3524
+ import { useState as useState6 } from "react";
3152
3525
  function useSchemeRoot() {
3153
- const [element, setElement] = useState5(null);
3526
+ const [element, setElement] = useState6(null);
3154
3527
  const scheme = useColorScheme(element);
3155
3528
  return { ref: setElement, "data-rsp-scheme": scheme };
3156
3529
  }
@@ -3531,7 +3904,7 @@ class WorkbookSelectionManager {
3531
3904
  }
3532
3905
 
3533
3906
  // src/workbook/workbook.tsx
3534
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3907
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3535
3908
  var getWrapCellStyle = (style) => style?.wrapText ? {
3536
3909
  whiteSpace: "pre-wrap",
3537
3910
  overflowWrap: "anywhere",
@@ -3562,64 +3935,7 @@ var getBorderCellStyle = (style) => (() => {
3562
3935
  ...borderSides.left && { borderLeftColor: style.borderColor }
3563
3936
  };
3564
3937
  })();
3565
- var Edit2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3566
- xmlns: "http://www.w3.org/2000/svg",
3567
- width: "16",
3568
- height: "16",
3569
- viewBox: "0 0 24 24",
3570
- fill: "none",
3571
- stroke: "currentColor",
3572
- strokeWidth: "2",
3573
- strokeLinecap: "round",
3574
- strokeLinejoin: "round",
3575
- className,
3576
- ...props,
3577
- children: [
3578
- /* @__PURE__ */ jsx4("path", {
3579
- d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"
3580
- }),
3581
- /* @__PURE__ */ jsx4("path", {
3582
- d: "m15 5 4 4"
3583
- })
3584
- ]
3585
- });
3586
- var Trash2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3587
- xmlns: "http://www.w3.org/2000/svg",
3588
- width: "16",
3589
- height: "16",
3590
- viewBox: "0 0 24 24",
3591
- fill: "none",
3592
- stroke: "currentColor",
3593
- strokeWidth: "2",
3594
- strokeLinecap: "round",
3595
- strokeLinejoin: "round",
3596
- className,
3597
- ...props,
3598
- children: [
3599
- /* @__PURE__ */ jsx4("path", {
3600
- d: "M3 6h18"
3601
- }),
3602
- /* @__PURE__ */ jsx4("path", {
3603
- d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
3604
- }),
3605
- /* @__PURE__ */ jsx4("path", {
3606
- d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
3607
- }),
3608
- /* @__PURE__ */ jsx4("line", {
3609
- x1: "10",
3610
- x2: "10",
3611
- y1: "11",
3612
- y2: "17"
3613
- }),
3614
- /* @__PURE__ */ jsx4("line", {
3615
- x1: "14",
3616
- x2: "14",
3617
- y1: "11",
3618
- y2: "17"
3619
- })
3620
- ]
3621
- });
3622
- var PlusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3938
+ var PlusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs4("svg", {
3623
3939
  xmlns: "http://www.w3.org/2000/svg",
3624
3940
  width: "16",
3625
3941
  height: "16",
@@ -3632,15 +3948,15 @@ var PlusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3632
3948
  className,
3633
3949
  ...props,
3634
3950
  children: [
3635
- /* @__PURE__ */ jsx4("path", {
3951
+ /* @__PURE__ */ jsx5("path", {
3636
3952
  d: "M5 12h14"
3637
3953
  }),
3638
- /* @__PURE__ */ jsx4("path", {
3954
+ /* @__PURE__ */ jsx5("path", {
3639
3955
  d: "M12 5v14"
3640
3956
  })
3641
3957
  ]
3642
3958
  });
3643
- var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx4("svg", {
3959
+ var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx5("svg", {
3644
3960
  xmlns: "http://www.w3.org/2000/svg",
3645
3961
  width: "16",
3646
3962
  height: "16",
@@ -3652,13 +3968,13 @@ var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx4("svg", {
3652
3968
  strokeLinejoin: "round",
3653
3969
  className,
3654
3970
  ...props,
3655
- children: /* @__PURE__ */ jsx4("path", {
3971
+ children: /* @__PURE__ */ jsx5("path", {
3656
3972
  d: "M5 12h14"
3657
3973
  })
3658
3974
  });
3659
3975
  function useEngine(engine) {
3660
- const [state, setState] = useState6(() => engine.getState());
3661
- React3.useEffect(() => {
3976
+ const [state, setState] = useState7(() => engine.getState());
3977
+ React4.useEffect(() => {
3662
3978
  return engine.onUpdate(() => {
3663
3979
  setState(engine.getState());
3664
3980
  });
@@ -3764,8 +4080,8 @@ function FormulaSheet({
3764
4080
  ...rest
3765
4081
  }) {
3766
4082
  const schemeRoot = useSchemeRoot();
3767
- const [selectedArea, setSelectedArea] = useState6(null);
3768
- const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
4083
+ const [selectedArea, setSelectedArea] = useState7(null);
4084
+ const clipboardManager = React4.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3769
4085
  const state = useEngine(engine);
3770
4086
  const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
3771
4087
  const getEngineTableAt = useCallback4((cell) => {
@@ -3804,20 +4120,14 @@ function FormulaSheet({
3804
4120
  ...selection?.navigation,
3805
4121
  getTableAt: selection?.navigation?.getTableAt ?? getEngineTableAt
3806
4122
  }), [getEngineTableAt, selection?.navigation]);
3807
- const onCellDataChange = useCallback4((updatedSpreadsheet) => {
3808
- const data = new Map(updatedSpreadsheet);
3809
- data.forEach((value, key) => {
3810
- if (typeof value === "string") {
4123
+ const onCellDataPatch = useCallback4((patch) => {
4124
+ engine.transact(() => {
4125
+ patch.forEach((value, key) => {
3811
4126
  const { colIndex, rowIndex } = parseCellReference2(key);
3812
- const coercedValue = coerceCellInput(value, engine.getCellDataType({ workbookName, sheetName, colIndex, rowIndex }));
3813
- if (coercedValue === undefined) {
3814
- data.delete(key);
3815
- } else {
3816
- data.set(key, coercedValue);
3817
- }
3818
- }
4127
+ const address = { workbookName, sheetName, colIndex, rowIndex };
4128
+ engine.setCellContent(address, typeof value === "string" ? coerceCellInput(value, engine.getCellDataType(address)) : value);
4129
+ });
3819
4130
  });
3820
- engine.setSheetContent({ sheetName, workbookName }, data);
3821
4131
  }, [sheetName, engine, workbookName]);
3822
4132
  const passedSelectionEffects = selection?.effects;
3823
4133
  const selectionManagerEffects = useCallback4((spreadsheetSelectionManager) => {
@@ -3944,30 +4254,210 @@ function FormulaSheet({
3944
4254
  viewId,
3945
4255
  workbookName
3946
4256
  ]);
4257
+ const renderCallbacks = useMemo2(() => {
4258
+ const metadata = engine.getWorkbookMetadata(workbookName);
4259
+ const defaults = metadata?.defaultCellStyle;
4260
+ const fontStyle = (style) => ({
4261
+ ...style?.fontFamily && { fontFamily: style.fontFamily },
4262
+ ...style?.bold !== undefined && { fontWeight: style.bold ? "bold" : "normal" },
4263
+ ...style?.italic !== undefined && { fontStyle: style.italic ? "italic" : "normal" },
4264
+ ...style?.underline !== undefined && { textDecoration: style.underline ? "underline" : "none" }
4265
+ });
4266
+ const styles = new Map;
4267
+ const getStyle = (cell) => {
4268
+ if (styles.has(cell.id))
4269
+ return styles.get(cell.id);
4270
+ const explicit = engine.getCellStyle({
4271
+ sheetName,
4272
+ workbookName,
4273
+ colIndex: cell.colIndex,
4274
+ rowIndex: cell.rowIndex
4275
+ });
4276
+ const style = defaults ? { ...defaults, ...explicit } : explicit;
4277
+ if (styles.size >= 4096)
4278
+ styles.clear();
4279
+ styles.set(cell.id, style);
4280
+ return style;
4281
+ };
4282
+ return {
4283
+ customCellStyle: (cell) => {
4284
+ const conditionalStyle = getStyle(cell);
4285
+ const tableInfo = engine.isCellInTable({
4286
+ sheetName,
4287
+ colIndex: cell.colIndex,
4288
+ rowIndex: cell.rowIndex,
4289
+ workbookName
4290
+ });
4291
+ if (!tableInfo) {
4292
+ const style = conditionalStyle ? {
4293
+ ...conditionalStyle.backgroundColor && {
4294
+ backgroundColor: conditionalStyle.backgroundColor
4295
+ },
4296
+ ...conditionalStyle.color && {
4297
+ color: conditionalStyle.color
4298
+ },
4299
+ ...conditionalStyle.fontSize && {
4300
+ fontSize: conditionalStyle.fontSize
4301
+ },
4302
+ ...fontStyle(conditionalStyle),
4303
+ ...getBorderCellStyle(conditionalStyle),
4304
+ ...getWrapCellStyle(conditionalStyle)
4305
+ } : {};
4306
+ return customCellStyle ? customCellStyle(cell, style) : style;
4307
+ }
4308
+ const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
4309
+ const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
4310
+ const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
4311
+ const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
4312
+ const style = {
4313
+ border: "1px solid var(--_rsp-table-border-light)"
4314
+ };
4315
+ if (isHeaderRow) {
4316
+ style.backgroundColor = "var(--_rsp-table-header-bg)";
4317
+ style.color = "var(--_rsp-table-header-color)";
4318
+ style.fontWeight = "bold";
4319
+ style.borderBottom = "2px solid var(--_rsp-table-header-border)";
4320
+ } else {
4321
+ const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
4322
+ if (dataRowIndex % 2 === 0) {
4323
+ style.backgroundColor = "var(--_rsp-table-even-row-bg)";
4324
+ } else {
4325
+ style.backgroundColor = "var(--_rsp-table-odd-row-bg)";
4326
+ }
4327
+ }
4328
+ if (isFirstColumn) {
4329
+ style.borderLeft = "2px solid var(--_rsp-table-border-color)";
4330
+ }
4331
+ if (isLastColumn) {
4332
+ style.borderRight = "2px solid var(--_rsp-table-border-color)";
4333
+ }
4334
+ if (isHeaderRow) {
4335
+ style.borderTop = "2px solid var(--_rsp-table-border-color)";
4336
+ }
4337
+ if (isLastRow) {
4338
+ style.borderBottom = "2px solid var(--_rsp-table-border-color)";
4339
+ }
4340
+ if (conditionalStyle?.backgroundColor) {
4341
+ style.backgroundColor = conditionalStyle.backgroundColor;
4342
+ }
4343
+ if (conditionalStyle?.color) {
4344
+ style.color = conditionalStyle.color;
4345
+ }
4346
+ if (conditionalStyle?.fontSize) {
4347
+ style.fontSize = conditionalStyle.fontSize;
4348
+ }
4349
+ Object.assign(style, fontStyle(conditionalStyle));
4350
+ Object.assign(style, getBorderCellStyle(conditionalStyle));
4351
+ Object.assign(style, getWrapCellStyle(conditionalStyle));
4352
+ if (customCellStyle) {
4353
+ return customCellStyle(cell, style);
4354
+ }
4355
+ return style;
4356
+ },
4357
+ customCellRenderer: (cell) => {
4358
+ const cellRef = getCellReference2({
4359
+ colIndex: cell.colIndex,
4360
+ rowIndex: cell.rowIndex
4361
+ });
4362
+ const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
4363
+ sheetName,
4364
+ workbookName,
4365
+ colIndex: cell.colIndex,
4366
+ rowIndex: cell.rowIndex
4367
+ }, verboseErrors);
4368
+ const conditionalStyle = getStyle(cell);
4369
+ const conditionalStyleProps = {};
4370
+ if (conditionalStyle?.backgroundColor) {
4371
+ conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
4372
+ }
4373
+ if (conditionalStyle?.color) {
4374
+ conditionalStyleProps.color = conditionalStyle.color;
4375
+ }
4376
+ if (conditionalStyle?.fontSize) {
4377
+ conditionalStyleProps.fontSize = conditionalStyle.fontSize;
4378
+ }
4379
+ Object.assign(conditionalStyleProps, fontStyle(conditionalStyle), getWrapContentStyle(conditionalStyle));
4380
+ let defaultElement;
4381
+ if (showFormulas) {
4382
+ defaultElement = /* @__PURE__ */ jsx5("div", {
4383
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4384
+ "data-conditional-text-color": conditionalStyle?.color,
4385
+ style: conditionalStyleProps,
4386
+ children: getCellDisplayText(value)
4387
+ });
4388
+ } else if (typeof value === "boolean") {
4389
+ defaultElement = /* @__PURE__ */ jsx5("div", {
4390
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4391
+ "data-conditional-text-color": conditionalStyle?.color,
4392
+ "data-type": "boolean",
4393
+ style: {
4394
+ ...conditionalStyleProps,
4395
+ width: "100%",
4396
+ textAlign: "center",
4397
+ fontFamily: conditionalStyle?.fontFamily ?? "'Courier New', monospace"
4398
+ },
4399
+ children: value ? "TRUE" : "FALSE"
4400
+ });
4401
+ } else if (typeof value === "number") {
4402
+ defaultElement = /* @__PURE__ */ jsx5("div", {
4403
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4404
+ "data-conditional-text-color": conditionalStyle?.color,
4405
+ "data-type": "number",
4406
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
4407
+ children: value.toLocaleString("en-US", {
4408
+ minimumFractionDigits: 0,
4409
+ maximumFractionDigits: 10
4410
+ })
4411
+ });
4412
+ } else {
4413
+ defaultElement = /* @__PURE__ */ jsx5("div", {
4414
+ "data-conditional-background": conditionalStyle?.backgroundColor,
4415
+ "data-conditional-text-color": conditionalStyle?.color,
4416
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
4417
+ children: getCellDisplayText(value)
4418
+ });
4419
+ }
4420
+ if (customCellRenderer) {
4421
+ return customCellRenderer(cell, defaultElement);
4422
+ }
4423
+ return defaultElement;
4424
+ }
4425
+ };
4426
+ }, [
4427
+ engine,
4428
+ state,
4429
+ workbookName,
4430
+ sheetName,
4431
+ sheet,
4432
+ showFormulas,
4433
+ verboseErrors,
4434
+ customCellStyle,
4435
+ customCellRenderer
4436
+ ]);
3947
4437
  if (!sheet) {
3948
- return /* @__PURE__ */ jsxs3("div", {
4438
+ return /* @__PURE__ */ jsxs4("div", {
3949
4439
  className: "rsp-root rsp-workbook",
3950
4440
  ...schemeRoot,
3951
4441
  children: [
3952
4442
  toolbar,
3953
- /* @__PURE__ */ jsx4("div", {
4443
+ /* @__PURE__ */ jsx5("div", {
3954
4444
  style: { flex: 1 }
3955
4445
  })
3956
4446
  ]
3957
4447
  });
3958
4448
  }
3959
- return /* @__PURE__ */ jsxs3("div", {
4449
+ return /* @__PURE__ */ jsxs4("div", {
3960
4450
  className: "rsp-root rsp-workbook",
3961
4451
  ...schemeRoot,
3962
4452
  children: [
3963
4453
  toolbar,
3964
- /* @__PURE__ */ jsx4("div", {
4454
+ /* @__PURE__ */ jsx5("div", {
3965
4455
  className: "rsp-workbook-body",
3966
- children: /* @__PURE__ */ jsx4(Spreadsheet, {
4456
+ children: /* @__PURE__ */ jsx5(Spreadsheet, {
3967
4457
  disableClipboard: true,
3968
4458
  style: { height: "100%", width: "100%" },
3969
4459
  cellData: sheet?.content,
3970
- onCellDataChange,
4460
+ onCellDataPatch,
3971
4461
  onCellDataChangeError,
3972
4462
  ...rest,
3973
4463
  components,
@@ -3985,183 +4475,7 @@ function FormulaSheet({
3985
4475
  containerProps: isSelected && selectedArea ? {
3986
4476
  "data-grid-no-interaction": "true"
3987
4477
  } : {},
3988
- customCellStyle: (cell) => {
3989
- const conditionalStyle = engine.getCellStyle({
3990
- sheetName,
3991
- colIndex: cell.colIndex,
3992
- rowIndex: cell.rowIndex,
3993
- workbookName
3994
- });
3995
- const tableInfo = engine.isCellInTable({
3996
- sheetName,
3997
- colIndex: cell.colIndex,
3998
- rowIndex: cell.rowIndex,
3999
- workbookName
4000
- });
4001
- if (!tableInfo) {
4002
- const style = conditionalStyle ? {
4003
- ...conditionalStyle.backgroundColor && {
4004
- backgroundColor: conditionalStyle.backgroundColor
4005
- },
4006
- ...conditionalStyle.color && {
4007
- color: conditionalStyle.color
4008
- },
4009
- ...conditionalStyle.fontSize && {
4010
- fontSize: conditionalStyle.fontSize
4011
- },
4012
- ...conditionalStyle.bold && {
4013
- fontWeight: "bold"
4014
- },
4015
- ...conditionalStyle.italic && {
4016
- fontStyle: "italic"
4017
- },
4018
- ...conditionalStyle.underline && {
4019
- textDecoration: "underline"
4020
- },
4021
- ...getBorderCellStyle(conditionalStyle),
4022
- ...getWrapCellStyle(conditionalStyle)
4023
- } : {};
4024
- return customCellStyle ? customCellStyle(cell, style) : style;
4025
- }
4026
- const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
4027
- const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
4028
- const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
4029
- const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
4030
- const style = {
4031
- border: "1px solid var(--_rsp-table-border-light)"
4032
- };
4033
- if (isHeaderRow) {
4034
- style.backgroundColor = "var(--_rsp-table-header-bg)";
4035
- style.color = "var(--_rsp-table-header-color)";
4036
- style.fontWeight = "bold";
4037
- style.borderBottom = "2px solid var(--_rsp-table-header-border)";
4038
- } else {
4039
- const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
4040
- if (dataRowIndex % 2 === 0) {
4041
- style.backgroundColor = "var(--_rsp-table-even-row-bg)";
4042
- } else {
4043
- style.backgroundColor = "var(--_rsp-table-odd-row-bg)";
4044
- }
4045
- }
4046
- if (isFirstColumn) {
4047
- style.borderLeft = "2px solid var(--_rsp-table-border-color)";
4048
- }
4049
- if (isLastColumn) {
4050
- style.borderRight = "2px solid var(--_rsp-table-border-color)";
4051
- }
4052
- if (isHeaderRow) {
4053
- style.borderTop = "2px solid var(--_rsp-table-border-color)";
4054
- }
4055
- if (isLastRow) {
4056
- style.borderBottom = "2px solid var(--_rsp-table-border-color)";
4057
- }
4058
- if (conditionalStyle?.backgroundColor) {
4059
- style.backgroundColor = conditionalStyle.backgroundColor;
4060
- }
4061
- if (conditionalStyle?.color) {
4062
- style.color = conditionalStyle.color;
4063
- }
4064
- if (conditionalStyle?.fontSize) {
4065
- style.fontSize = conditionalStyle.fontSize;
4066
- }
4067
- if (conditionalStyle?.bold) {
4068
- style.fontWeight = "bold";
4069
- }
4070
- if (conditionalStyle?.italic) {
4071
- style.fontStyle = "italic";
4072
- }
4073
- if (conditionalStyle?.underline) {
4074
- style.textDecoration = "underline";
4075
- }
4076
- Object.assign(style, getBorderCellStyle(conditionalStyle));
4077
- Object.assign(style, getWrapCellStyle(conditionalStyle));
4078
- if (customCellStyle) {
4079
- return customCellStyle(cell, style);
4080
- }
4081
- return style;
4082
- },
4083
- customCellRenderer: (cell) => {
4084
- const cellRef = getCellReference2({
4085
- colIndex: cell.colIndex,
4086
- rowIndex: cell.rowIndex
4087
- });
4088
- const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
4089
- sheetName,
4090
- workbookName,
4091
- colIndex: cell.colIndex,
4092
- rowIndex: cell.rowIndex
4093
- }, verboseErrors);
4094
- const conditionalStyle = engine.getCellStyle({
4095
- sheetName,
4096
- colIndex: cell.colIndex,
4097
- rowIndex: cell.rowIndex,
4098
- workbookName
4099
- });
4100
- const conditionalStyleProps = {};
4101
- if (conditionalStyle?.backgroundColor) {
4102
- conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
4103
- }
4104
- if (conditionalStyle?.color) {
4105
- conditionalStyleProps.color = conditionalStyle.color;
4106
- }
4107
- if (conditionalStyle?.fontSize) {
4108
- conditionalStyleProps.fontSize = conditionalStyle.fontSize;
4109
- }
4110
- if (conditionalStyle?.bold) {
4111
- conditionalStyleProps.fontWeight = "bold";
4112
- }
4113
- if (conditionalStyle?.italic) {
4114
- conditionalStyleProps.fontStyle = "italic";
4115
- }
4116
- if (conditionalStyle?.underline) {
4117
- conditionalStyleProps.textDecoration = "underline";
4118
- }
4119
- Object.assign(conditionalStyleProps, getWrapContentStyle(conditionalStyle));
4120
- let defaultElement;
4121
- if (showFormulas) {
4122
- defaultElement = /* @__PURE__ */ jsx4("div", {
4123
- "data-conditional-background": conditionalStyle?.backgroundColor,
4124
- "data-conditional-text-color": conditionalStyle?.color,
4125
- style: conditionalStyleProps,
4126
- children: getCellDisplayText(value)
4127
- });
4128
- } else if (typeof value === "boolean") {
4129
- defaultElement = /* @__PURE__ */ jsx4("div", {
4130
- "data-conditional-background": conditionalStyle?.backgroundColor,
4131
- "data-conditional-text-color": conditionalStyle?.color,
4132
- "data-type": "boolean",
4133
- style: {
4134
- ...conditionalStyleProps,
4135
- width: "100%",
4136
- textAlign: "center",
4137
- fontFamily: "'Courier New', monospace"
4138
- },
4139
- children: value ? "TRUE" : "FALSE"
4140
- });
4141
- } else if (typeof value === "number") {
4142
- defaultElement = /* @__PURE__ */ jsx4("div", {
4143
- "data-conditional-background": conditionalStyle?.backgroundColor,
4144
- "data-conditional-text-color": conditionalStyle?.color,
4145
- "data-type": "number",
4146
- style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
4147
- children: value.toLocaleString("en-US", {
4148
- minimumFractionDigits: 0,
4149
- maximumFractionDigits: 10
4150
- })
4151
- });
4152
- } else {
4153
- defaultElement = /* @__PURE__ */ jsx4("div", {
4154
- "data-conditional-background": conditionalStyle?.backgroundColor,
4155
- "data-conditional-text-color": conditionalStyle?.color,
4156
- style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
4157
- children: getCellDisplayText(value)
4158
- });
4159
- }
4160
- if (customCellRenderer) {
4161
- return customCellRenderer(cell, defaultElement);
4162
- }
4163
- return defaultElement;
4164
- }
4478
+ ...renderCallbacks
4165
4479
  })
4166
4480
  })
4167
4481
  ]
@@ -4191,18 +4505,16 @@ function FormulaWorkbook({
4191
4505
  ...rest
4192
4506
  }) {
4193
4507
  const schemeRoot = useSchemeRoot();
4194
- const state = useEngine(engine);
4195
- const [renamingSheet, setRenamingSheet] = useState6(null);
4196
- const [newSheetName, setNewSheetName] = useState6("");
4197
- const [spreadsheetZoom, setSpreadsheetZoom] = useState6(1);
4198
- const renameCommittedRef = useRef4(false);
4199
- const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
4508
+ useEngine(engine);
4509
+ const panelId = React4.useId();
4510
+ const [spreadsheetZoom, setSpreadsheetZoom] = useState7(1);
4511
+ const clipboardManager = React4.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
4200
4512
  const sheetNames = engine.getOrderedSheetNames(workbookName);
4201
- const [internalActiveSheet, setInternalActiveSheet] = useState6(() => {
4513
+ const [internalActiveSheet, setInternalActiveSheet] = useState7(() => {
4202
4514
  return sheetNames[0] || "";
4203
4515
  });
4204
- const isControlledRef = useRef4(controlledActiveSheet !== undefined);
4205
- React3.useEffect(() => {
4516
+ const isControlledRef = useRef5(controlledActiveSheet !== undefined);
4517
+ React4.useEffect(() => {
4206
4518
  const isCurrentlyControlled = controlledActiveSheet !== undefined;
4207
4519
  if (isControlledRef.current !== isCurrentlyControlled) {
4208
4520
  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.`);
@@ -4213,10 +4525,10 @@ function FormulaWorkbook({
4213
4525
  const handleActiveSheetChange = useCallback4((sheetName) => {
4214
4526
  if (onActiveSheetChange) {
4215
4527
  onActiveSheetChange(sheetName);
4216
- } else {
4217
- setInternalActiveSheet(sheetName);
4218
4528
  }
4219
- }, [onActiveSheetChange]);
4529
+ if (controlledActiveSheet === undefined)
4530
+ setInternalActiveSheet(sheetName);
4531
+ }, [onActiveSheetChange, controlledActiveSheet]);
4220
4532
  const activeSheetOverlayChildren = sheetOverlayChildren?.[activeSheet];
4221
4533
  const activeSheetSelectedOverlayId = sheetSelectedOverlayIds?.[activeSheet];
4222
4534
  const activeSheetColumnWidths = sheetLayouts?.[activeSheet]?.columnWidths;
@@ -4234,76 +4546,37 @@ function FormulaWorkbook({
4234
4546
  onSheetRowHeightsChange?.(activeSheet, rowHeights);
4235
4547
  }, [activeSheet, onSheetRowHeightsChange]);
4236
4548
  const addSheet = () => {
4237
- try {
4238
- const newSheet = engine.createSheet({
4239
- workbookName
4240
- });
4241
- handleActiveSheetChange(newSheet.name);
4242
- } catch (error) {
4243
- console.error("Failed to add sheet:", error);
4244
- }
4245
- };
4246
- const startRenaming = (sheetName) => {
4247
- renameCommittedRef.current = false;
4248
- setRenamingSheet(sheetName);
4249
- setNewSheetName(sheetName);
4250
- };
4251
- const cancelRenaming = () => {
4252
- setRenamingSheet(null);
4253
- setNewSheetName("");
4549
+ const newSheet = engine.createSheet({ workbookName });
4550
+ handleActiveSheetChange(newSheet.name);
4254
4551
  };
4255
4552
  const renameSheet = (oldName, newName) => {
4256
- if (renameCommittedRef.current) {
4257
- cancelRenaming();
4258
- return;
4259
- }
4260
- if (!newName.trim() || newName.trim() === oldName) {
4261
- cancelRenaming();
4262
- return;
4263
- }
4264
- try {
4265
- renameCommittedRef.current = true;
4266
- engine.renameSheet({
4267
- workbookName,
4268
- sheetName: oldName,
4269
- newSheetName: newName.trim()
4270
- });
4271
- if (activeSheet === oldName) {
4272
- handleActiveSheetChange(newName.trim());
4273
- }
4274
- cancelRenaming();
4275
- } catch (error) {
4276
- console.error("Failed to rename sheet:", error);
4277
- cancelRenaming();
4278
- }
4553
+ engine.renameSheet({ workbookName, sheetName: oldName, newSheetName: newName });
4554
+ if (activeSheet === oldName)
4555
+ handleActiveSheetChange(newName);
4279
4556
  };
4280
4557
  const deleteSheet = (sheetName) => {
4281
4558
  if (sheetNames.length <= 1) {
4282
4559
  console.error("Cannot delete the last sheet");
4283
4560
  return;
4284
4561
  }
4285
- try {
4286
- engine.removeSheet({ workbookName, sheetName });
4287
- if (activeSheet === sheetName) {
4288
- const remainingSheets = sheetNames.filter((name) => name !== sheetName);
4289
- const nextSheet = remainingSheets[0];
4290
- if (nextSheet) {
4291
- handleActiveSheetChange(nextSheet);
4292
- }
4562
+ engine.removeSheet({ workbookName, sheetName });
4563
+ if (activeSheet === sheetName) {
4564
+ const remainingSheets = sheetNames.filter((name) => name !== sheetName);
4565
+ const nextSheet = remainingSheets[Math.min(sheetNames.indexOf(sheetName), remainingSheets.length - 1)];
4566
+ if (nextSheet) {
4567
+ handleActiveSheetChange(nextSheet);
4293
4568
  }
4294
- } catch (error) {
4295
- console.error("Failed to delete sheet:", error);
4296
4569
  }
4297
4570
  };
4298
- return /* @__PURE__ */ jsxs3("div", {
4571
+ return /* @__PURE__ */ jsxs4("div", {
4299
4572
  className: "rsp-root rsp-workbook",
4300
4573
  ...schemeRoot,
4301
4574
  children: [
4302
4575
  toolbar,
4303
- /* @__PURE__ */ jsxs3("div", {
4576
+ /* @__PURE__ */ jsxs4("div", {
4304
4577
  className: "rsp-workbook-header",
4305
4578
  children: [
4306
- /* @__PURE__ */ jsx4("div", {
4579
+ /* @__PURE__ */ jsx5("div", {
4307
4580
  style: {
4308
4581
  display: "flex",
4309
4582
  alignItems: "center",
@@ -4312,13 +4585,13 @@ function FormulaWorkbook({
4312
4585
  overflow: "hidden",
4313
4586
  textOverflow: "ellipsis"
4314
4587
  },
4315
- children: /* @__PURE__ */ jsx4("span", {
4588
+ children: /* @__PURE__ */ jsx5("span", {
4316
4589
  className: "rsp-workbook-name",
4317
4590
  "data-testid": `workbook-name-${workbookName}`,
4318
4591
  children: workbookName
4319
4592
  })
4320
4593
  }),
4321
- /* @__PURE__ */ jsxs3("span", {
4594
+ /* @__PURE__ */ jsxs4("span", {
4322
4595
  className: "rsp-zoom-value",
4323
4596
  "data-testid": `sheet-count-${workbookName}`,
4324
4597
  children: [
@@ -4328,16 +4601,19 @@ function FormulaWorkbook({
4328
4601
  })
4329
4602
  ]
4330
4603
  }),
4331
- /* @__PURE__ */ jsx4("div", {
4332
- style: { flex: 1, overflow: "hidden" },
4333
- children: /* @__PURE__ */ jsx4("div", {
4604
+ /* @__PURE__ */ jsx5("div", {
4605
+ id: panelId,
4606
+ role: "tabpanel",
4607
+ "aria-label": activeSheet || "Sheet",
4608
+ style: { flex: 1, minHeight: 0, overflow: "hidden" },
4609
+ children: /* @__PURE__ */ jsx5("div", {
4334
4610
  style: {
4335
4611
  transform: spreadsheetZoom !== 1 ? `scale(${spreadsheetZoom})` : undefined,
4336
4612
  transformOrigin: "0 0",
4337
4613
  width: spreadsheetZoom !== 1 ? `${100 / spreadsheetZoom}%` : "100%",
4338
4614
  height: spreadsheetZoom !== 1 ? `${100 / spreadsheetZoom}%` : "100%"
4339
4615
  },
4340
- children: /* @__PURE__ */ jsx4(FormulaSheet, {
4616
+ children: /* @__PURE__ */ jsx5(FormulaSheet, {
4341
4617
  sheetName: activeSheet,
4342
4618
  workbookName,
4343
4619
  engine,
@@ -4361,138 +4637,53 @@ function FormulaWorkbook({
4361
4637
  }, `${workbookName}-${activeSheet}`)
4362
4638
  })
4363
4639
  }),
4364
- /* @__PURE__ */ jsxs3("div", {
4365
- className: "rsp-sheet-tabs",
4366
- children: [
4367
- /* @__PURE__ */ jsxs3("div", {
4368
- className: "rsp-sheet-tabs-scroll",
4369
- children: [
4370
- /* @__PURE__ */ jsx4("div", {
4371
- style: { display: "flex", alignItems: "center", gap: 4 },
4372
- children: sheetNames.map((sheetName) => {
4373
- const isRenaming = renamingSheet === sheetName;
4374
- const isActive = activeSheet === sheetName;
4375
- return /* @__PURE__ */ jsx4("div", {
4376
- children: isRenaming ? /* @__PURE__ */ jsx4("div", {
4377
- style: { display: "flex", alignItems: "center", gap: 4 },
4378
- children: /* @__PURE__ */ jsx4("input", {
4379
- className: "rsp-sheet-tab-input",
4380
- type: "text",
4381
- value: newSheetName,
4382
- onChange: (e) => setNewSheetName(e.target.value),
4383
- onKeyDown: (e) => {
4384
- if (e.key === "Enter") {
4385
- renameSheet(sheetName, newSheetName);
4386
- } else if (e.key === "Escape") {
4387
- cancelRenaming();
4388
- }
4389
- },
4390
- onBlur: () => {
4391
- if (newSheetName.trim() && newSheetName.trim() !== sheetName) {
4392
- renameSheet(sheetName, newSheetName);
4393
- } else {
4394
- cancelRenaming();
4395
- }
4396
- },
4397
- autoFocus: true,
4398
- "data-testid": `rename-sheet-input-${sheetName}`
4399
- })
4400
- }) : /* @__PURE__ */ jsxs3("div", {
4401
- className: cn("rsp-sheet-tab", {
4402
- "rsp-sheet-tab-active": isActive
4403
- }),
4404
- onClick: () => handleActiveSheetChange(sheetName),
4405
- onDoubleClick: () => startRenaming(sheetName),
4406
- "data-testid": `sheet-tab-${sheetName}`,
4407
- children: [
4408
- /* @__PURE__ */ jsx4("span", {
4409
- children: sheetName
4410
- }),
4411
- /* @__PURE__ */ jsxs3("div", {
4412
- className: "sheet-tab-actions",
4413
- children: [
4414
- /* @__PURE__ */ jsx4("button", {
4415
- type: "button",
4416
- className: "rsp-icon-button",
4417
- onClick: (e) => {
4418
- e.stopPropagation();
4419
- startRenaming(sheetName);
4420
- },
4421
- title: "Rename Sheet",
4422
- "data-testid": `rename-sheet-${sheetName}`,
4423
- children: /* @__PURE__ */ jsx4(Edit2Icon, {
4424
- style: { width: 12, height: 12 }
4425
- })
4426
- }),
4427
- sheetNames.length > 1 && /* @__PURE__ */ jsx4("button", {
4428
- type: "button",
4429
- className: "rsp-icon-button rsp-icon-button-delete",
4430
- onClick: (e) => {
4431
- e.stopPropagation();
4432
- if (confirm(`Are you sure you want to delete sheet "${sheetName}"?`)) {
4433
- deleteSheet(sheetName);
4434
- }
4435
- },
4436
- title: "Delete Sheet",
4437
- "data-testid": `delete-sheet-${sheetName}`,
4438
- children: /* @__PURE__ */ jsx4(Trash2Icon, {
4439
- style: { width: 12, height: 12 }
4440
- })
4441
- })
4442
- ]
4443
- })
4444
- ]
4445
- })
4446
- }, sheetName);
4447
- })
4448
- }),
4449
- /* @__PURE__ */ jsx4("button", {
4450
- type: "button",
4451
- className: "rsp-add-sheet-button",
4452
- onClick: addSheet,
4453
- title: "Add Sheet",
4454
- "data-testid": `add-sheet-${workbookName}`,
4455
- children: /* @__PURE__ */ jsx4(PlusIcon, {
4456
- style: { width: 12, height: 12 }
4457
- })
4640
+ /* @__PURE__ */ jsx5(SheetNavigation, {
4641
+ sheets: sheetNames,
4642
+ activeSheet,
4643
+ workbookName,
4644
+ panelId,
4645
+ onSelect: handleActiveSheetChange,
4646
+ onAdd: addSheet,
4647
+ onRename: renameSheet,
4648
+ onDelete: deleteSheet,
4649
+ children: /* @__PURE__ */ jsxs4("div", {
4650
+ className: "rsp-zoom-controls",
4651
+ children: [
4652
+ /* @__PURE__ */ jsx5("button", {
4653
+ type: "button",
4654
+ className: "rsp-icon-button",
4655
+ onClick: () => setSpreadsheetZoom(Math.max(0.5, Math.round((spreadsheetZoom - 0.1) * 10) / 10)),
4656
+ disabled: spreadsheetZoom <= 0.5,
4657
+ title: "Zoom Out",
4658
+ style: { opacity: spreadsheetZoom <= 0.5 ? 0.5 : 1 },
4659
+ children: /* @__PURE__ */ jsx5(MinusIcon, {
4660
+ style: { width: 12, height: 12 }
4458
4661
  })
4459
- ]
4460
- }),
4461
- /* @__PURE__ */ jsxs3("div", {
4462
- className: "rsp-zoom-controls",
4463
- children: [
4464
- /* @__PURE__ */ jsx4("button", {
4465
- type: "button",
4466
- className: "rsp-icon-button",
4467
- onClick: () => setSpreadsheetZoom(Math.max(0.5, spreadsheetZoom - 0.1)),
4468
- disabled: spreadsheetZoom <= 0.5,
4469
- title: "Zoom Out",
4470
- style: { opacity: spreadsheetZoom <= 0.5 ? 0.5 : 1 },
4471
- children: /* @__PURE__ */ jsx4(MinusIcon, {
4472
- style: { width: 12, height: 12 }
4473
- })
4474
- }),
4475
- /* @__PURE__ */ jsxs3("span", {
4476
- className: "rsp-zoom-value",
4477
- children: [
4478
- Math.round(spreadsheetZoom * 100),
4479
- "%"
4480
- ]
4481
- }),
4482
- /* @__PURE__ */ jsx4("button", {
4483
- type: "button",
4484
- className: "rsp-icon-button",
4485
- onClick: () => setSpreadsheetZoom(Math.min(2, spreadsheetZoom + 0.1)),
4486
- disabled: spreadsheetZoom >= 2,
4487
- title: "Zoom In",
4488
- style: { opacity: spreadsheetZoom >= 2 ? 0.5 : 1 },
4489
- children: /* @__PURE__ */ jsx4(PlusIcon, {
4490
- style: { width: 12, height: 12 }
4491
- })
4662
+ }),
4663
+ /* @__PURE__ */ jsxs4("button", {
4664
+ type: "button",
4665
+ className: "rsp-zoom-value rsp-zoom-reset",
4666
+ "aria-label": `Zoom ${Math.round(spreadsheetZoom * 100)}%. Reset to 100%`,
4667
+ title: "Reset zoom to 100%",
4668
+ onClick: () => setSpreadsheetZoom(1),
4669
+ children: [
4670
+ Math.round(spreadsheetZoom * 100),
4671
+ "%"
4672
+ ]
4673
+ }),
4674
+ /* @__PURE__ */ jsx5("button", {
4675
+ type: "button",
4676
+ className: "rsp-icon-button",
4677
+ onClick: () => setSpreadsheetZoom(Math.min(2, Math.round((spreadsheetZoom + 0.1) * 10) / 10)),
4678
+ disabled: spreadsheetZoom >= 2,
4679
+ title: "Zoom In",
4680
+ style: { opacity: spreadsheetZoom >= 2 ? 0.5 : 1 },
4681
+ children: /* @__PURE__ */ jsx5(PlusIcon, {
4682
+ style: { width: 12, height: 12 }
4492
4683
  })
4493
- ]
4494
- })
4495
- ]
4684
+ })
4685
+ ]
4686
+ })
4496
4687
  })
4497
4688
  ]
4498
4689
  });
@@ -4504,9 +4695,9 @@ var DEFAULT_ZOOM = 1;
4504
4695
  var MIN_ZOOM = 0.5;
4505
4696
  var MAX_ZOOM = 3;
4506
4697
  // src/utils/use-click-away.ts
4507
- import { useEffect as useEffect4 } from "react";
4698
+ import { useEffect as useEffect5 } from "react";
4508
4699
  var useClickAway = (ref, callback) => {
4509
- useEffect4(() => {
4700
+ useEffect5(() => {
4510
4701
  const handleClickAway = (event) => {
4511
4702
  if (ref) {
4512
4703
  const away = !ref.contains(event.target);
@@ -4571,4 +4762,4 @@ export {
4571
4762
  useResizeObserver
4572
4763
  };
4573
4764
 
4574
- //# debugId=A4984B6C177B85D364756E2164756E21
4765
+ //# debugId=66ABFE3983D95F6964756E2164756E21