@particle-academy/fancy-sheets 0.6.1 → 0.6.3

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);
@@ -2035,6 +2039,26 @@ function tsvToCells(tsv) {
2035
2039
  const cols = Math.max(...values.map((v) => v.length));
2036
2040
  return { values, rows, cols };
2037
2041
  }
2042
+ function renderMenuItems(items, activeCell) {
2043
+ return items.map((item, i) => {
2044
+ if (item.items && item.items.length > 0) {
2045
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
2046
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: item.label }),
2047
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubContent, { children: renderMenuItems(item.items, activeCell) })
2048
+ ] }, i);
2049
+ }
2050
+ return /* @__PURE__ */ jsxRuntime.jsx(
2051
+ reactFancy.ContextMenu.Item,
2052
+ {
2053
+ onClick: () => item.onClick?.(activeCell),
2054
+ disabled: typeof item.disabled === "function" ? item.disabled(activeCell) : item.disabled,
2055
+ danger: item.danger,
2056
+ children: item.label
2057
+ },
2058
+ i
2059
+ );
2060
+ });
2061
+ }
2038
2062
  function SpreadsheetGrid({ className }) {
2039
2063
  const {
2040
2064
  columnCount,
@@ -2057,8 +2081,8 @@ function SpreadsheetGrid({ className }) {
2057
2081
  redo,
2058
2082
  contextMenuItems
2059
2083
  } = useSpreadsheet();
2060
- const containerRef = react.useRef(null);
2061
- const handleKeyDown = react.useCallback(
2084
+ const containerRef = React.useRef(null);
2085
+ const handleKeyDown = React.useCallback(
2062
2086
  (e) => {
2063
2087
  if (editingCell) return;
2064
2088
  if (e.key === "ArrowUp") {
@@ -2143,14 +2167,14 @@ function SpreadsheetGrid({ className }) {
2143
2167
  const top = row * rowHeight;
2144
2168
  return { left, top };
2145
2169
  })() : null;
2146
- const handleCopy = react.useCallback(() => {
2170
+ const handleCopy = React.useCallback(() => {
2147
2171
  const range = selection.ranges[0];
2148
2172
  if (range) {
2149
2173
  const tsv = cellsToTSV(activeSheet.cells, range);
2150
2174
  navigator.clipboard.writeText(tsv);
2151
2175
  }
2152
2176
  }, [selection, activeSheet]);
2153
- const handlePaste = react.useCallback(() => {
2177
+ const handlePaste = React.useCallback(() => {
2154
2178
  navigator.clipboard.readText().then((text) => {
2155
2179
  if (!text) return;
2156
2180
  const { values } = tsvToCells(text);
@@ -2162,7 +2186,7 @@ function SpreadsheetGrid({ className }) {
2162
2186
  }
2163
2187
  });
2164
2188
  }, [selection, setCellValue]);
2165
- const handleClearSelection = react.useCallback(() => {
2189
+ const handleClearSelection = React.useCallback(() => {
2166
2190
  const range = selection.ranges[0];
2167
2191
  if (!range) return;
2168
2192
  const { start, end } = range;
@@ -2254,25 +2278,17 @@ function SpreadsheetGrid({ className }) {
2254
2278
  if (!items || items.length === 0) return null;
2255
2279
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2256
2280
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
2257
- items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
2258
- reactFancy.ContextMenu.Item,
2259
- {
2260
- onClick: () => item.onClick(selection.activeCell),
2261
- disabled: typeof item.disabled === "function" ? item.disabled(selection.activeCell) : item.disabled,
2262
- danger: item.danger,
2263
- children: item.label
2264
- },
2265
- i
2266
- ))
2281
+ renderMenuItems(items, selection.activeCell)
2267
2282
  ] });
2268
2283
  })()
2269
2284
  ] })
2270
2285
  ] });
2271
2286
  }
2272
2287
  SpreadsheetGrid.displayName = "SpreadsheetGrid";
2288
+ var ALL_BUTTONS = ["undo", "bold", "align", "freeze", "format", "decimals", "formulaBar"];
2273
2289
  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
2290
  var activeBtnClass = "bg-zinc-200 dark:bg-zinc-700";
2275
- function DefaultToolbar({ extra }) {
2291
+ function DefaultToolbar({ extra, buttons }) {
2276
2292
  const {
2277
2293
  selection,
2278
2294
  activeSheet,
@@ -2322,105 +2338,97 @@ function DefaultToolbar({ extra }) {
2322
2338
  }
2323
2339
  };
2324
2340
  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);
2341
+ const divider = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" });
2342
+ const groups = [];
2343
+ if (buttons.has("undo")) {
2344
+ groups.push(
2345
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2346
+ /* @__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: [
2347
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 4 1 10 7 10" }),
2348
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })
2349
+ ] }) }),
2350
+ /* @__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: [
2351
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "23 4 23 10 17 10" }),
2352
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
2353
+ ] }) })
2354
+ ] }, "undo")
2355
+ );
2356
+ }
2357
+ if (buttons.has("bold")) {
2358
+ groups.push(
2359
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2360
+ /* @__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" }) }),
2361
+ /* @__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" }) })
2362
+ ] }, "bold")
2363
+ );
2364
+ }
2365
+ if (buttons.has("align")) {
2366
+ groups.push(
2367
+ /* @__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: [
2368
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
2369
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: align === "left" ? "3" : align === "center" ? "6" : "9", y1: "12", x2: align === "left" ? "15" : align === "center" ? "18" : "21", y2: "12" }),
2370
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18" })
2371
+ ] }) }, align)) }, "align")
2372
+ );
2373
+ }
2374
+ if (buttons.has("freeze")) {
2375
+ groups.push(
2376
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2377
+ /* @__PURE__ */ jsxRuntime.jsx(
2378
+ "button",
2379
+ {
2380
+ className: reactFancy.cn(btnClass, activeSheet.frozenRows > 0 && activeBtnClass),
2381
+ onClick: () => {
2382
+ if (activeSheet.frozenRows > 0) {
2383
+ setFrozenRows(0);
2407
2384
  } else {
2408
- setFrozenCols(1);
2385
+ const row = selection.activeCell.match(/\d+/);
2386
+ setFrozenRows(row ? parseInt(row[0], 10) - 1 || 1 : 1);
2409
2387
  }
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(
2388
+ },
2389
+ disabled: readOnly,
2390
+ title: activeSheet.frozenRows > 0 ? `Unfreeze rows (${activeSheet.frozenRows} frozen)` : "Freeze rows above current cell",
2391
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2392
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
2393
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "4", x2: "21", y2: "4" }),
2394
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "14", x2: "21", y2: "14", strokeDasharray: "3 3" }),
2395
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "19", x2: "21", y2: "19", strokeDasharray: "3 3" })
2396
+ ] })
2397
+ }
2398
+ ),
2399
+ /* @__PURE__ */ jsxRuntime.jsx(
2400
+ "button",
2401
+ {
2402
+ className: reactFancy.cn(btnClass, activeSheet.frozenCols > 0 && activeBtnClass),
2403
+ onClick: () => {
2404
+ if (activeSheet.frozenCols > 0) {
2405
+ setFrozenCols(0);
2406
+ } else {
2407
+ const colMatch = selection.activeCell.match(/^([A-Z]+)/);
2408
+ if (colMatch) {
2409
+ const col = colMatch[1].split("").reduce((acc, ch) => acc * 26 + ch.charCodeAt(0) - 64, 0) - 1;
2410
+ setFrozenCols(col || 1);
2411
+ } else {
2412
+ setFrozenCols(1);
2413
+ }
2414
+ }
2415
+ },
2416
+ disabled: readOnly,
2417
+ title: activeSheet.frozenCols > 0 ? `Unfreeze columns (${activeSheet.frozenCols} frozen)` : "Freeze columns left of current cell",
2418
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2419
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }),
2420
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "3", x2: "4", y2: "21" }),
2421
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14", y1: "3", x2: "14", y2: "21", strokeDasharray: "3 3" }),
2422
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
2423
+ ] })
2424
+ }
2425
+ )
2426
+ ] }, "freeze")
2427
+ );
2428
+ }
2429
+ if (buttons.has("format")) {
2430
+ groups.push(
2431
+ /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2424
2432
  "select",
2425
2433
  {
2426
2434
  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 +2446,32 @@ function DefaultToolbar({ extra }) {
2438
2446
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "datetime", children: "Date & Time" })
2439
2447
  ]
2440
2448
  }
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: [
2449
+ ) }, "format")
2450
+ );
2451
+ }
2452
+ if (buttons.has("decimals")) {
2453
+ groups.push(
2454
+ /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
2455
+ /* @__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: [
2456
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".0" }),
2457
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2190" })
2458
+ ] }),
2459
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: btnClass, onClick: () => setCellFormat(selectedAddresses, { decimals: currentDecimals + 1 }), disabled: readOnly, title: `Increase decimal places (currently ${currentDecimals})`, children: [
2460
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".00" }),
2461
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2192" })
2462
+ ] })
2463
+ ] }, "decimals")
2464
+ );
2465
+ }
2466
+ if (extra) {
2467
+ groups.push(/* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: extra }, "extra"));
2468
+ }
2469
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2470
+ 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: [
2471
+ i > 0 && divider,
2472
+ group
2473
+ ] }, i)) }),
2474
+ 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
2475
  /* @__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
2476
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
2476
2477
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2487,15 +2488,16 @@ function DefaultToolbar({ extra }) {
2487
2488
  ] })
2488
2489
  ] });
2489
2490
  }
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 }) });
2491
+ function SpreadsheetToolbar({ children, className, extra, buttons: buttonsProp }) {
2492
+ const buttonsSet = new Set(buttonsProp ?? ALL_BUTTONS);
2493
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fancy-sheets-toolbar": "", className: reactFancy.cn("", className), children: children ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultToolbar, { extra, buttons: buttonsSet }) });
2492
2494
  }
2493
2495
  SpreadsheetToolbar.displayName = "SpreadsheetToolbar";
2494
2496
  function SpreadsheetSheetTabs({ className }) {
2495
2497
  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(
2498
+ const [renamingId, setRenamingId] = React.useState(null);
2499
+ const [renameValue, setRenameValue] = React.useState("");
2500
+ const handleDoubleClick = React.useCallback(
2499
2501
  (sheetId, name) => {
2500
2502
  if (readOnly) return;
2501
2503
  setRenamingId(sheetId);
@@ -2503,7 +2505,7 @@ function SpreadsheetSheetTabs({ className }) {
2503
2505
  },
2504
2506
  [readOnly]
2505
2507
  );
2506
- const handleRenameConfirm = react.useCallback(() => {
2508
+ const handleRenameConfirm = React.useCallback(() => {
2507
2509
  if (renamingId && renameValue.trim()) {
2508
2510
  renameSheet(renamingId, renameValue.trim());
2509
2511
  }
@@ -2589,18 +2591,18 @@ function SpreadsheetRoot({
2589
2591
  contextMenuItems
2590
2592
  }) {
2591
2593
  const { state, actions } = useSpreadsheetStore(data ?? defaultData);
2592
- const onChangeRef = react.useRef(onChange);
2593
- const isExternalSync = react.useRef(false);
2594
- const prevDataRef = react.useRef(data);
2594
+ const onChangeRef = React.useRef(onChange);
2595
+ const isExternalSync = React.useRef(false);
2596
+ const prevDataRef = React.useRef(data);
2595
2597
  onChangeRef.current = onChange;
2596
- react.useEffect(() => {
2598
+ React.useEffect(() => {
2597
2599
  if (data && data !== prevDataRef.current && data !== state.workbook) {
2598
2600
  isExternalSync.current = true;
2599
2601
  actions.setWorkbook(data);
2600
2602
  prevDataRef.current = data;
2601
2603
  }
2602
2604
  }, [data]);
2603
- react.useEffect(() => {
2605
+ React.useEffect(() => {
2604
2606
  if (isExternalSync.current) {
2605
2607
  isExternalSync.current = false;
2606
2608
  return;
@@ -2608,15 +2610,15 @@ function SpreadsheetRoot({
2608
2610
  prevDataRef.current = state.workbook;
2609
2611
  onChangeRef.current?.(state.workbook);
2610
2612
  }, [state.workbook]);
2611
- const activeSheet = react.useMemo(
2613
+ const activeSheet = React.useMemo(
2612
2614
  () => state.workbook.sheets.find((s) => s.id === state.workbook.activeSheetId),
2613
2615
  [state.workbook]
2614
2616
  );
2615
- const getColumnWidth = react.useCallback(
2617
+ const getColumnWidth = React.useCallback(
2616
2618
  (col) => activeSheet.columnWidths[col] ?? defaultColumnWidth,
2617
2619
  [activeSheet.columnWidths, defaultColumnWidth]
2618
2620
  );
2619
- const isCellSelected = react.useCallback(
2621
+ const isCellSelected = React.useCallback(
2620
2622
  (address) => {
2621
2623
  const target = parseAddress(address);
2622
2624
  return state.selection.ranges.some((range) => {
@@ -2628,12 +2630,12 @@ function SpreadsheetRoot({
2628
2630
  },
2629
2631
  [state.selection.ranges]
2630
2632
  );
2631
- const isCellActive = react.useCallback(
2633
+ const isCellActive = React.useCallback(
2632
2634
  (address) => state.selection.activeCell === address,
2633
2635
  [state.selection.activeCell]
2634
2636
  );
2635
- const isDraggingRef = react.useRef(false);
2636
- const ctx = react.useMemo(
2637
+ const isDraggingRef = React.useRef(false);
2638
+ const ctx = React.useMemo(
2637
2639
  () => ({
2638
2640
  workbook: state.workbook,
2639
2641
  activeSheet,
@@ -2672,11 +2674,11 @@ var Spreadsheet = Object.assign(SpreadsheetRoot, {
2672
2674
  SheetTabs: SpreadsheetSheetTabs
2673
2675
  });
2674
2676
  function Sheet({ data, onChange, contextMenuItems, ...props }) {
2675
- const workbook = react.useMemo(
2677
+ const workbook = React.useMemo(
2676
2678
  () => data ? { sheets: [data], activeSheetId: data.id } : void 0,
2677
2679
  [data]
2678
2680
  );
2679
- const handleChange = react.useMemo(() => {
2681
+ const handleChange = React.useMemo(() => {
2680
2682
  if (!onChange) return void 0;
2681
2683
  return (wb) => onChange(wb.sheets[0]);
2682
2684
  }, [onChange]);
@@ -2687,11 +2689,12 @@ function SheetWorkbook({
2687
2689
  hideToolbar = false,
2688
2690
  hideTabs = false,
2689
2691
  toolbarExtra,
2692
+ toolbarButtons,
2690
2693
  contextMenuItems,
2691
2694
  ...props
2692
2695
  }) {
2693
2696
  return /* @__PURE__ */ jsxRuntime.jsxs(Spreadsheet, { ...props, contextMenuItems, children: [
2694
- !hideToolbar && /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.Toolbar, { extra: toolbarExtra }),
2697
+ !hideToolbar && /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.Toolbar, { extra: toolbarExtra, buttons: toolbarButtons }),
2695
2698
  /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.Grid, {}),
2696
2699
  !hideTabs && /* @__PURE__ */ jsxRuntime.jsx(Spreadsheet.SheetTabs, {})
2697
2700
  ] });