@particle-academy/fancy-sheets 0.6.1 → 0.6.2

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/index.cjs CHANGED
@@ -1,13 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React = require('react');
4
4
  var reactFancy = require('@particle-academy/react-fancy');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
7
11
  // src/components/Spreadsheet/Spreadsheet.tsx
8
- var SpreadsheetContext = react.createContext(null);
12
+ var SpreadsheetContext = React.createContext(null);
9
13
  function useSpreadsheet() {
10
- const ctx = react.useContext(SpreadsheetContext);
14
+ const ctx = React.useContext(SpreadsheetContext);
11
15
  if (!ctx) {
12
16
  throw new Error("useSpreadsheet must be used within a <Spreadsheet> component");
13
17
  }
@@ -1545,8 +1549,8 @@ function reducer(state, action) {
1545
1549
  }
1546
1550
  }
1547
1551
  function useSpreadsheetStore(initialData) {
1548
- const [state, dispatch] = react.useReducer(reducer, initialData, (data) => createInitialState(data));
1549
- const actions = react.useMemo(() => ({
1552
+ const [state, dispatch] = React.useReducer(reducer, initialData, (data) => createInitialState(data));
1553
+ const actions = React.useMemo(() => ({
1550
1554
  setCellValue: (address, value) => dispatch({ type: "SET_CELL_VALUE", address, value }),
1551
1555
  setCellFormat: (addresses, format) => dispatch({ type: "SET_CELL_FORMAT", addresses, format }),
1552
1556
  setSelection: (cell) => dispatch({ type: "SET_SELECTION", cell }),
@@ -1573,9 +1577,9 @@ function useSpreadsheetStore(initialData) {
1573
1577
  }
1574
1578
  function ColumnResizeHandle({ colIndex }) {
1575
1579
  const { resizeColumn, getColumnWidth } = useSpreadsheet();
1576
- const startX = react.useRef(0);
1577
- const startWidth = react.useRef(0);
1578
- const handlePointerDown = react.useCallback(
1580
+ const startX = React.useRef(0);
1581
+ const startWidth = React.useRef(0);
1582
+ const handlePointerDown = React.useCallback(
1579
1583
  (e) => {
1580
1584
  e.preventDefault();
1581
1585
  e.stopPropagation();
@@ -1608,7 +1612,7 @@ function ColumnResizeHandle({ colIndex }) {
1608
1612
  ColumnResizeHandle.displayName = "ColumnResizeHandle";
1609
1613
  function ColumnHeaders() {
1610
1614
  const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange, _isDragging } = useSpreadsheet();
1611
- const handleColumnMouseDown = react.useCallback(
1615
+ const handleColumnMouseDown = React.useCallback(
1612
1616
  (colIdx, e) => {
1613
1617
  if (e.button !== 0) return;
1614
1618
  if (e.shiftKey) {
@@ -1623,7 +1627,7 @@ function ColumnHeaders() {
1623
1627
  },
1624
1628
  [rowCount, selectRange, selection.activeCell, _isDragging]
1625
1629
  );
1626
- const handleColumnMouseEnter = react.useCallback(
1630
+ const handleColumnMouseEnter = React.useCallback(
1627
1631
  (colIdx) => {
1628
1632
  if (_isDragging.current) {
1629
1633
  const activeCol = parseAddress(selection.activeCell).col;
@@ -1634,7 +1638,7 @@ function ColumnHeaders() {
1634
1638
  },
1635
1639
  [rowCount, selection.activeCell, selectRange, _isDragging]
1636
1640
  );
1637
- const handleMouseUp = react.useCallback(() => {
1641
+ const handleMouseUp = React.useCallback(() => {
1638
1642
  _isDragging.current = false;
1639
1643
  }, [_isDragging]);
1640
1644
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1696,7 +1700,7 @@ function RowHeader({ rowIndex }) {
1696
1700
  const maxCol = Math.max(s.col, e.col);
1697
1701
  return rowIndex >= minRow && rowIndex <= maxRow && minCol === 0 && maxCol >= columnCount - 1;
1698
1702
  });
1699
- const handleMouseDown = react.useCallback(
1703
+ const handleMouseDown = React.useCallback(
1700
1704
  (e) => {
1701
1705
  if (e.button !== 0) return;
1702
1706
  if (e.shiftKey) {
@@ -1711,7 +1715,7 @@ function RowHeader({ rowIndex }) {
1711
1715
  },
1712
1716
  [rowIndex, columnCount, selectRange, selection.activeCell, _isDragging]
1713
1717
  );
1714
- const handleMouseEnter = react.useCallback(() => {
1718
+ const handleMouseEnter = React.useCallback(() => {
1715
1719
  if (_isDragging.current) {
1716
1720
  const activeRow = parseAddress(selection.activeCell).row;
1717
1721
  const minRow = Math.min(activeRow, rowIndex);
@@ -1719,7 +1723,7 @@ function RowHeader({ rowIndex }) {
1719
1723
  selectRange(toAddress(minRow, 0), toAddress(maxRow, columnCount - 1));
1720
1724
  }
1721
1725
  }, [rowIndex, columnCount, selection.activeCell, selectRange, _isDragging]);
1722
- const handleMouseUp = react.useCallback(() => {
1726
+ const handleMouseUp = React.useCallback(() => {
1723
1727
  _isDragging.current = false;
1724
1728
  }, [_isDragging]);
1725
1729
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1787,7 +1791,7 @@ function getCellDisplayValue2(cell) {
1787
1791
  const val = cell.formula && cell.computedValue !== void 0 ? cell.computedValue : cell.value;
1788
1792
  return formatCellValue(val, cell);
1789
1793
  }
1790
- var Cell = react.memo(function Cell2({ address, row, col }) {
1794
+ var Cell = React.memo(function Cell2({ address, row, col }) {
1791
1795
  const {
1792
1796
  activeSheet,
1793
1797
  selection,
@@ -1809,7 +1813,7 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
1809
1813
  const isEditing = editingCell === address;
1810
1814
  const displayValue = getCellDisplayValue2(cell);
1811
1815
  const width = getColumnWidth(col);
1812
- const handleMouseDown = react.useCallback(
1816
+ const handleMouseDown = React.useCallback(
1813
1817
  (e) => {
1814
1818
  if (e.button !== 0) return;
1815
1819
  if (e.shiftKey) {
@@ -1823,15 +1827,15 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
1823
1827
  },
1824
1828
  [address, setSelection, extendSelection, addSelection, _isDragging]
1825
1829
  );
1826
- const handleMouseEnter = react.useCallback(() => {
1830
+ const handleMouseEnter = React.useCallback(() => {
1827
1831
  if (_isDragging.current) {
1828
1832
  extendSelection(address);
1829
1833
  }
1830
1834
  }, [address, extendSelection, _isDragging]);
1831
- const handleMouseUp = react.useCallback(() => {
1835
+ const handleMouseUp = React.useCallback(() => {
1832
1836
  _isDragging.current = false;
1833
1837
  }, [_isDragging]);
1834
- const handleDoubleClick = react.useCallback(() => {
1838
+ const handleDoubleClick = React.useCallback(() => {
1835
1839
  if (readOnly) return;
1836
1840
  startEdit();
1837
1841
  }, [readOnly, startEdit]);
@@ -1864,7 +1868,7 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
1864
1868
  formatStyle.borderWidth = 1;
1865
1869
  formatStyle.borderStyle = "solid";
1866
1870
  }
1867
- const [showComment, setShowComment] = react.useState(false);
1871
+ const [showComment, setShowComment] = React.useState(false);
1868
1872
  return /* @__PURE__ */ jsxRuntime.jsxs(
1869
1873
  "div",
1870
1874
  {
@@ -1927,15 +1931,15 @@ function CellEditor() {
1927
1931
  getColumnWidth,
1928
1932
  rowHeight
1929
1933
  } = useSpreadsheet();
1930
- const inputRef = react.useRef(null);
1931
- const mountedAt = react.useRef(0);
1932
- react.useEffect(() => {
1934
+ const inputRef = React.useRef(null);
1935
+ const mountedAt = React.useRef(0);
1936
+ React.useEffect(() => {
1933
1937
  if (editingCell && inputRef.current) {
1934
1938
  mountedAt.current = Date.now();
1935
1939
  inputRef.current.focus();
1936
1940
  }
1937
1941
  }, [editingCell]);
1938
- const handleBlur = react.useCallback(() => {
1942
+ const handleBlur = React.useCallback(() => {
1939
1943
  if (Date.now() - mountedAt.current < 100) return;
1940
1944
  confirmEdit();
1941
1945
  }, [confirmEdit]);
@@ -1971,7 +1975,7 @@ function CellEditor() {
1971
1975
  CellEditor.displayName = "CellEditor";
1972
1976
  function SelectionOverlay() {
1973
1977
  const { selection, getColumnWidth, rowHeight } = useSpreadsheet();
1974
- const rects = react.useMemo(() => {
1978
+ const rects = React.useMemo(() => {
1975
1979
  return selection.ranges.map((range, i) => {
1976
1980
  const norm = normalizeRange(range.start, range.end);
1977
1981
  const s = parseAddress(norm.start);
@@ -2057,8 +2061,8 @@ function SpreadsheetGrid({ className }) {
2057
2061
  redo,
2058
2062
  contextMenuItems
2059
2063
  } = useSpreadsheet();
2060
- const containerRef = react.useRef(null);
2061
- const handleKeyDown = react.useCallback(
2064
+ const containerRef = React.useRef(null);
2065
+ const handleKeyDown = React.useCallback(
2062
2066
  (e) => {
2063
2067
  if (editingCell) return;
2064
2068
  if (e.key === "ArrowUp") {
@@ -2143,14 +2147,14 @@ function SpreadsheetGrid({ className }) {
2143
2147
  const top = row * rowHeight;
2144
2148
  return { left, top };
2145
2149
  })() : null;
2146
- const handleCopy = react.useCallback(() => {
2150
+ const handleCopy = React.useCallback(() => {
2147
2151
  const range = selection.ranges[0];
2148
2152
  if (range) {
2149
2153
  const tsv = cellsToTSV(activeSheet.cells, range);
2150
2154
  navigator.clipboard.writeText(tsv);
2151
2155
  }
2152
2156
  }, [selection, activeSheet]);
2153
- const handlePaste = react.useCallback(() => {
2157
+ const handlePaste = React.useCallback(() => {
2154
2158
  navigator.clipboard.readText().then((text) => {
2155
2159
  if (!text) return;
2156
2160
  const { values } = tsvToCells(text);
@@ -2162,7 +2166,7 @@ function SpreadsheetGrid({ className }) {
2162
2166
  }
2163
2167
  });
2164
2168
  }, [selection, setCellValue]);
2165
- const handleClearSelection = react.useCallback(() => {
2169
+ const handleClearSelection = React.useCallback(() => {
2166
2170
  const range = selection.ranges[0];
2167
2171
  if (!range) return;
2168
2172
  const { start, end } = range;
@@ -2270,9 +2274,10 @@ function SpreadsheetGrid({ className }) {
2270
2274
  ] });
2271
2275
  }
2272
2276
  SpreadsheetGrid.displayName = "SpreadsheetGrid";
2277
+ var ALL_BUTTONS = ["undo", "bold", "align", "freeze", "format", "decimals", "formulaBar"];
2273
2278
  var btnClass = "inline-flex items-center justify-center rounded px-2 py-1 text-[12px] font-medium text-zinc-600 transition-colors hover:bg-zinc-100 disabled:opacity-40 dark:text-zinc-300 dark:hover:bg-zinc-800";
2274
2279
  var activeBtnClass = "bg-zinc-200 dark:bg-zinc-700";
2275
- function DefaultToolbar({ extra }) {
2280
+ function DefaultToolbar({ extra, buttons }) {
2276
2281
  const {
2277
2282
  selection,
2278
2283
  activeSheet,
@@ -2322,105 +2327,97 @@ function DefaultToolbar({ extra }) {
2322
2327
  }
2323
2328
  };
2324
2329
  const formulaBarValue = editingCell ? editValue : cell?.formula ? "=" + cell.formula : cell?.value != null ? String(cell.value) : "";
2325
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2326
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 border-b border-zinc-200 px-1.5 py-1 dark:border-zinc-700", children: [
2327
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: btnClass, onClick: undo, disabled: !canUndo || readOnly, title: "Undo (Ctrl+Z)", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2328
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 4 1 10 7 10" }),
2329
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })
2330
- ] }) }),
2331
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: btnClass, onClick: redo, disabled: !canRedo || readOnly, title: "Redo (Ctrl+Y)", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2332
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "23 4 23 10 17 10" }),
2333
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
2334
- ] }) }),
2335
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2336
- /* @__PURE__ */ jsxRuntime.jsx(
2337
- "button",
2338
- {
2339
- className: reactFancy.cn(btnClass, isBold && activeBtnClass),
2340
- onClick: () => setCellFormat(selectedAddresses, { bold: !isBold }),
2341
- disabled: readOnly,
2342
- title: "Bold",
2343
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", children: "B" })
2344
- }
2345
- ),
2346
- /* @__PURE__ */ jsxRuntime.jsx(
2347
- "button",
2348
- {
2349
- className: reactFancy.cn(btnClass, isItalic && activeBtnClass),
2350
- onClick: () => setCellFormat(selectedAddresses, { italic: !isItalic }),
2351
- disabled: readOnly,
2352
- title: "Italic",
2353
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: "I" })
2354
- }
2355
- ),
2356
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2357
- ["left", "center", "right"].map((align) => /* @__PURE__ */ jsxRuntime.jsx(
2358
- "button",
2359
- {
2360
- className: reactFancy.cn(btnClass, textAlign === align && activeBtnClass),
2361
- onClick: () => setCellFormat(selectedAddresses, { textAlign: align }),
2362
- disabled: readOnly,
2363
- title: `Align ${align}`,
2364
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2365
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
2366
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: align === "left" ? "3" : align === "center" ? "6" : "9", y1: "12", x2: align === "left" ? "15" : align === "center" ? "18" : "21", y2: "12" }),
2367
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
2368
- ] })
2369
- },
2370
- align
2371
- )),
2372
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2373
- /* @__PURE__ */ jsxRuntime.jsx(
2374
- "button",
2375
- {
2376
- className: reactFancy.cn(btnClass, activeSheet.frozenRows > 0 && activeBtnClass),
2377
- onClick: () => {
2378
- if (activeSheet.frozenRows > 0) {
2379
- setFrozenRows(0);
2380
- } else {
2381
- const row = selection.activeCell.match(/\d+/);
2382
- setFrozenRows(row ? parseInt(row[0], 10) - 1 || 1 : 1);
2383
- }
2384
- },
2385
- disabled: readOnly,
2386
- title: activeSheet.frozenRows > 0 ? `Unfreeze rows (${activeSheet.frozenRows} frozen)` : "Freeze rows above current cell",
2387
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2388
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
2389
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "4", x2: "21", y2: "4" }),
2390
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "14", x2: "21", y2: "14", strokeDasharray: "3 3" }),
2391
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "19", x2: "21", y2: "19", strokeDasharray: "3 3" })
2392
- ] })
2393
- }
2394
- ),
2395
- /* @__PURE__ */ jsxRuntime.jsx(
2396
- "button",
2397
- {
2398
- className: reactFancy.cn(btnClass, activeSheet.frozenCols > 0 && activeBtnClass),
2399
- onClick: () => {
2400
- if (activeSheet.frozenCols > 0) {
2401
- setFrozenCols(0);
2402
- } else {
2403
- const colMatch = selection.activeCell.match(/^([A-Z]+)/);
2404
- if (colMatch) {
2405
- const col = colMatch[1].split("").reduce((acc, ch) => acc * 26 + ch.charCodeAt(0) - 64, 0) - 1;
2406
- setFrozenCols(col || 1);
2330
+ const divider = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" });
2331
+ const groups = [];
2332
+ if (buttons.has("undo")) {
2333
+ groups.push(
2334
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2335
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: btnClass, onClick: undo, disabled: !canUndo || readOnly, title: "Undo (Ctrl+Z)", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2336
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 4 1 10 7 10" }),
2337
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })
2338
+ ] }) }),
2339
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: btnClass, onClick: redo, disabled: !canRedo || readOnly, title: "Redo (Ctrl+Y)", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2340
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "23 4 23 10 17 10" }),
2341
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
2342
+ ] }) })
2343
+ ] }, "undo")
2344
+ );
2345
+ }
2346
+ if (buttons.has("bold")) {
2347
+ groups.push(
2348
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2349
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: reactFancy.cn(btnClass, isBold && activeBtnClass), onClick: () => setCellFormat(selectedAddresses, { bold: !isBold }), disabled: readOnly, title: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", children: "B" }) }),
2350
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: reactFancy.cn(btnClass, isItalic && activeBtnClass), onClick: () => setCellFormat(selectedAddresses, { italic: !isItalic }), disabled: readOnly, title: "Italic", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: "I" }) })
2351
+ ] }, "bold")
2352
+ );
2353
+ }
2354
+ if (buttons.has("align")) {
2355
+ groups.push(
2356
+ /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: ["left", "center", "right"].map((align) => /* @__PURE__ */ jsxRuntime.jsx("button", { className: reactFancy.cn(btnClass, textAlign === align && activeBtnClass), onClick: () => setCellFormat(selectedAddresses, { textAlign: align }), disabled: readOnly, title: `Align ${align}`, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2357
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
2358
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: align === "left" ? "3" : align === "center" ? "6" : "9", y1: "12", x2: align === "left" ? "15" : align === "center" ? "18" : "21", y2: "12" }),
2359
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
2360
+ ] }) }, align)) }, "align")
2361
+ );
2362
+ }
2363
+ if (buttons.has("freeze")) {
2364
+ groups.push(
2365
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2366
+ /* @__PURE__ */ jsxRuntime.jsx(
2367
+ "button",
2368
+ {
2369
+ className: reactFancy.cn(btnClass, activeSheet.frozenRows > 0 && activeBtnClass),
2370
+ onClick: () => {
2371
+ if (activeSheet.frozenRows > 0) {
2372
+ setFrozenRows(0);
2407
2373
  } else {
2408
- setFrozenCols(1);
2374
+ const row = selection.activeCell.match(/\d+/);
2375
+ setFrozenRows(row ? parseInt(row[0], 10) - 1 || 1 : 1);
2409
2376
  }
2410
- }
2411
- },
2412
- disabled: readOnly,
2413
- title: activeSheet.frozenCols > 0 ? `Unfreeze columns (${activeSheet.frozenCols} frozen)` : "Freeze columns left of current cell",
2414
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2415
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }),
2416
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "3", x2: "4", y2: "21" }),
2417
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14", y1: "3", x2: "14", y2: "21", strokeDasharray: "3 3" }),
2418
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
2419
- ] })
2420
- }
2421
- ),
2422
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2423
- /* @__PURE__ */ jsxRuntime.jsxs(
2377
+ },
2378
+ disabled: readOnly,
2379
+ title: activeSheet.frozenRows > 0 ? `Unfreeze rows (${activeSheet.frozenRows} frozen)` : "Freeze rows above current cell",
2380
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2381
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
2382
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "4", x2: "21", y2: "4" }),
2383
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "14", x2: "21", y2: "14", strokeDasharray: "3 3" }),
2384
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "19", x2: "21", y2: "19", strokeDasharray: "3 3" })
2385
+ ] })
2386
+ }
2387
+ ),
2388
+ /* @__PURE__ */ jsxRuntime.jsx(
2389
+ "button",
2390
+ {
2391
+ className: reactFancy.cn(btnClass, activeSheet.frozenCols > 0 && activeBtnClass),
2392
+ onClick: () => {
2393
+ if (activeSheet.frozenCols > 0) {
2394
+ setFrozenCols(0);
2395
+ } else {
2396
+ const colMatch = selection.activeCell.match(/^([A-Z]+)/);
2397
+ if (colMatch) {
2398
+ const col = colMatch[1].split("").reduce((acc, ch) => acc * 26 + ch.charCodeAt(0) - 64, 0) - 1;
2399
+ setFrozenCols(col || 1);
2400
+ } else {
2401
+ setFrozenCols(1);
2402
+ }
2403
+ }
2404
+ },
2405
+ disabled: readOnly,
2406
+ title: activeSheet.frozenCols > 0 ? `Unfreeze columns (${activeSheet.frozenCols} frozen)` : "Freeze columns left of current cell",
2407
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2408
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }),
2409
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "3", x2: "4", y2: "21" }),
2410
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14", y1: "3", x2: "14", y2: "21", strokeDasharray: "3 3" }),
2411
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
2412
+ ] })
2413
+ }
2414
+ )
2415
+ ] }, "freeze")
2416
+ );
2417
+ }
2418
+ if (buttons.has("format")) {
2419
+ groups.push(
2420
+ /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2424
2421
  "select",
2425
2422
  {
2426
2423
  className: "h-6 rounded border border-zinc-200 bg-transparent px-1 text-[11px] text-zinc-600 outline-none hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600",
@@ -2438,39 +2435,32 @@ function DefaultToolbar({ extra }) {
2438
2435
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "datetime", children: "Date & Time" })
2439
2436
  ]
2440
2437
  }
2441
- ),
2442
- /* @__PURE__ */ jsxRuntime.jsxs(
2443
- "button",
2444
- {
2445
- className: btnClass,
2446
- onClick: () => setCellFormat(selectedAddresses, { decimals: Math.max(0, currentDecimals - 1) }),
2447
- disabled: readOnly || currentDecimals <= 0,
2448
- title: `Decrease decimal places (currently ${currentDecimals})`,
2449
- children: [
2450
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".0" }),
2451
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2190" })
2452
- ]
2453
- }
2454
- ),
2455
- /* @__PURE__ */ jsxRuntime.jsxs(
2456
- "button",
2457
- {
2458
- className: btnClass,
2459
- onClick: () => setCellFormat(selectedAddresses, { decimals: currentDecimals + 1 }),
2460
- disabled: readOnly,
2461
- title: `Increase decimal places (currently ${currentDecimals})`,
2462
- children: [
2463
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".00" }),
2464
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2192" })
2465
- ]
2466
- }
2467
- ),
2468
- extra && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2469
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2470
- extra
2471
- ] })
2472
- ] }),
2473
- /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-fancy-sheets-formula-bar": "", className: "flex items-center gap-2 border-b border-zinc-200 px-2 py-1 dark:border-zinc-700", children: [
2438
+ ) }, "format")
2439
+ );
2440
+ }
2441
+ if (buttons.has("decimals")) {
2442
+ groups.push(
2443
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2444
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: btnClass, onClick: () => setCellFormat(selectedAddresses, { decimals: Math.max(0, currentDecimals - 1) }), disabled: readOnly || currentDecimals <= 0, title: `Decrease decimal places (currently ${currentDecimals})`, children: [
2445
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".0" }),
2446
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2190" })
2447
+ ] }),
2448
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: btnClass, onClick: () => setCellFormat(selectedAddresses, { decimals: currentDecimals + 1 }), disabled: readOnly, title: `Increase decimal places (currently ${currentDecimals})`, children: [
2449
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".00" }),
2450
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2192" })
2451
+ ] })
2452
+ ] }, "decimals")
2453
+ );
2454
+ }
2455
+ if (extra) {
2456
+ groups.push(/* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: extra }, "extra"));
2457
+ }
2458
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2459
+ groups.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5 border-b border-zinc-200 px-1.5 py-1 dark:border-zinc-700", children: groups.map((group, i) => /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2460
+ i > 0 && divider,
2461
+ group
2462
+ ] }, i)) }),
2463
+ buttons.has("formulaBar") && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-fancy-sheets-formula-bar": "", className: "flex items-center gap-2 border-b border-zinc-200 px-2 py-1 dark:border-zinc-700", children: [
2474
2464
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-12 shrink-0 text-center text-[11px] font-medium text-zinc-500 dark:text-zinc-400", children: selection.activeCell }),
2475
2465
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2476
2466
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2487,15 +2477,16 @@ function DefaultToolbar({ extra }) {
2487
2477
  ] })
2488
2478
  ] });
2489
2479
  }
2490
- function SpreadsheetToolbar({ children, className, extra }) {
2491
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fancy-sheets-toolbar": "", className: reactFancy.cn("", className), children: children ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultToolbar, { extra }) });
2480
+ function SpreadsheetToolbar({ children, className, extra, buttons: buttonsProp }) {
2481
+ const buttonsSet = new Set(buttonsProp ?? ALL_BUTTONS);
2482
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fancy-sheets-toolbar": "", className: reactFancy.cn("", className), children: children ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultToolbar, { extra, buttons: buttonsSet }) });
2492
2483
  }
2493
2484
  SpreadsheetToolbar.displayName = "SpreadsheetToolbar";
2494
2485
  function SpreadsheetSheetTabs({ className }) {
2495
2486
  const { workbook, setActiveSheet, addSheet, renameSheet, deleteSheet, readOnly } = useSpreadsheet();
2496
- const [renamingId, setRenamingId] = react.useState(null);
2497
- const [renameValue, setRenameValue] = react.useState("");
2498
- const handleDoubleClick = react.useCallback(
2487
+ const [renamingId, setRenamingId] = React.useState(null);
2488
+ const [renameValue, setRenameValue] = React.useState("");
2489
+ const handleDoubleClick = React.useCallback(
2499
2490
  (sheetId, name) => {
2500
2491
  if (readOnly) return;
2501
2492
  setRenamingId(sheetId);
@@ -2503,7 +2494,7 @@ function SpreadsheetSheetTabs({ className }) {
2503
2494
  },
2504
2495
  [readOnly]
2505
2496
  );
2506
- const handleRenameConfirm = react.useCallback(() => {
2497
+ const handleRenameConfirm = React.useCallback(() => {
2507
2498
  if (renamingId && renameValue.trim()) {
2508
2499
  renameSheet(renamingId, renameValue.trim());
2509
2500
  }
@@ -2589,18 +2580,18 @@ function SpreadsheetRoot({
2589
2580
  contextMenuItems
2590
2581
  }) {
2591
2582
  const { state, actions } = useSpreadsheetStore(data ?? defaultData);
2592
- const onChangeRef = react.useRef(onChange);
2593
- const isExternalSync = react.useRef(false);
2594
- const prevDataRef = react.useRef(data);
2583
+ const onChangeRef = React.useRef(onChange);
2584
+ const isExternalSync = React.useRef(false);
2585
+ const prevDataRef = React.useRef(data);
2595
2586
  onChangeRef.current = onChange;
2596
- react.useEffect(() => {
2587
+ React.useEffect(() => {
2597
2588
  if (data && data !== prevDataRef.current && data !== state.workbook) {
2598
2589
  isExternalSync.current = true;
2599
2590
  actions.setWorkbook(data);
2600
2591
  prevDataRef.current = data;
2601
2592
  }
2602
2593
  }, [data]);
2603
- react.useEffect(() => {
2594
+ React.useEffect(() => {
2604
2595
  if (isExternalSync.current) {
2605
2596
  isExternalSync.current = false;
2606
2597
  return;
@@ -2608,15 +2599,15 @@ function SpreadsheetRoot({
2608
2599
  prevDataRef.current = state.workbook;
2609
2600
  onChangeRef.current?.(state.workbook);
2610
2601
  }, [state.workbook]);
2611
- const activeSheet = react.useMemo(
2602
+ const activeSheet = React.useMemo(
2612
2603
  () => state.workbook.sheets.find((s) => s.id === state.workbook.activeSheetId),
2613
2604
  [state.workbook]
2614
2605
  );
2615
- const getColumnWidth = react.useCallback(
2606
+ const getColumnWidth = React.useCallback(
2616
2607
  (col) => activeSheet.columnWidths[col] ?? defaultColumnWidth,
2617
2608
  [activeSheet.columnWidths, defaultColumnWidth]
2618
2609
  );
2619
- const isCellSelected = react.useCallback(
2610
+ const isCellSelected = React.useCallback(
2620
2611
  (address) => {
2621
2612
  const target = parseAddress(address);
2622
2613
  return state.selection.ranges.some((range) => {
@@ -2628,12 +2619,12 @@ function SpreadsheetRoot({
2628
2619
  },
2629
2620
  [state.selection.ranges]
2630
2621
  );
2631
- const isCellActive = react.useCallback(
2622
+ const isCellActive = React.useCallback(
2632
2623
  (address) => state.selection.activeCell === address,
2633
2624
  [state.selection.activeCell]
2634
2625
  );
2635
- const isDraggingRef = react.useRef(false);
2636
- const ctx = react.useMemo(
2626
+ const isDraggingRef = React.useRef(false);
2627
+ const ctx = React.useMemo(
2637
2628
  () => ({
2638
2629
  workbook: state.workbook,
2639
2630
  activeSheet,
@@ -2672,11 +2663,11 @@ var Spreadsheet = Object.assign(SpreadsheetRoot, {
2672
2663
  SheetTabs: SpreadsheetSheetTabs
2673
2664
  });
2674
2665
  function Sheet({ data, onChange, contextMenuItems, ...props }) {
2675
- const workbook = react.useMemo(
2666
+ const workbook = React.useMemo(
2676
2667
  () => data ? { sheets: [data], activeSheetId: data.id } : void 0,
2677
2668
  [data]
2678
2669
  );
2679
- const handleChange = react.useMemo(() => {
2670
+ const handleChange = React.useMemo(() => {
2680
2671
  if (!onChange) return void 0;
2681
2672
  return (wb) => onChange(wb.sheets[0]);
2682
2673
  }, [onChange]);
@@ -2687,11 +2678,12 @@ function SheetWorkbook({
2687
2678
  hideToolbar = false,
2688
2679
  hideTabs = false,
2689
2680
  toolbarExtra,
2681
+ toolbarButtons,
2690
2682
  contextMenuItems,
2691
2683
  ...props
2692
2684
  }) {
2693
2685
  return /* @__PURE__ */ jsxRuntime.jsxs(Spreadsheet, { ...props, contextMenuItems, children: [
2694
- !hideToolbar && /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.Toolbar, { extra: toolbarExtra }),
2686
+ !hideToolbar && /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.Toolbar, { extra: toolbarExtra, buttons: toolbarButtons }),
2695
2687
  /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.Grid, {}),
2696
2688
  !hideTabs && /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.SheetTabs, {})
2697
2689
  ] });