@particle-academy/fancy-sheets 0.3.1 → 0.4.0

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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, memo, useCallback, useContext, useRef, useEffect, useMemo, useState, useReducer } from 'react';
2
- import { cn } from '@particle-academy/react-fancy';
2
+ import { cn, ContextMenu } from '@particle-academy/react-fancy';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/components/Spreadsheet/Spreadsheet.tsx
@@ -1599,14 +1599,19 @@ function ColumnResizeHandle({ colIndex }) {
1599
1599
  }
1600
1600
  ColumnResizeHandle.displayName = "ColumnResizeHandle";
1601
1601
  function ColumnHeaders() {
1602
- const { columnCount, rowCount, rowHeight, getColumnWidth, selectRange } = useSpreadsheet();
1602
+ const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange } = useSpreadsheet();
1603
1603
  const handleColumnClick = useCallback(
1604
- (colIdx) => {
1605
- const start = toAddress(0, colIdx);
1606
- const end = toAddress(rowCount - 1, colIdx);
1607
- selectRange(start, end);
1604
+ (colIdx, e) => {
1605
+ if (e.shiftKey) {
1606
+ const activeCol = parseAddress(selection.activeCell).col;
1607
+ const minCol = Math.min(activeCol, colIdx);
1608
+ const maxCol = Math.max(activeCol, colIdx);
1609
+ selectRange(toAddress(0, minCol), toAddress(rowCount - 1, maxCol));
1610
+ } else {
1611
+ selectRange(toAddress(0, colIdx), toAddress(rowCount - 1, colIdx));
1612
+ }
1608
1613
  },
1609
- [rowCount, selectRange]
1614
+ [rowCount, selectRange, selection.activeCell]
1610
1615
  );
1611
1616
  return /* @__PURE__ */ jsxs(
1612
1617
  "div",
@@ -1627,7 +1632,7 @@ function ColumnHeaders() {
1627
1632
  {
1628
1633
  className: "relative flex shrink-0 cursor-pointer items-center justify-center border-r border-zinc-300 text-[11px] font-medium text-zinc-500 select-none hover:bg-zinc-200 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-700",
1629
1634
  style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
1630
- onClick: () => handleColumnClick(i),
1635
+ onClick: (e) => handleColumnClick(i, e),
1631
1636
  children: [
1632
1637
  columnToLetter(i),
1633
1638
  /* @__PURE__ */ jsx(ColumnResizeHandle, { colIndex: i })
@@ -1641,12 +1646,20 @@ function ColumnHeaders() {
1641
1646
  }
1642
1647
  ColumnHeaders.displayName = "ColumnHeaders";
1643
1648
  function RowHeader({ rowIndex }) {
1644
- const { rowHeight, columnCount, selectRange } = useSpreadsheet();
1645
- const handleClick = useCallback(() => {
1646
- const start = toAddress(rowIndex, 0);
1647
- const end = toAddress(rowIndex, columnCount - 1);
1648
- selectRange(start, end);
1649
- }, [rowIndex, columnCount, selectRange]);
1649
+ const { rowHeight, columnCount, selection, selectRange, extendSelection } = useSpreadsheet();
1650
+ const handleClick = useCallback(
1651
+ (e) => {
1652
+ if (e.shiftKey) {
1653
+ const activeRow = parseAddress(selection.activeCell).row;
1654
+ const minRow = Math.min(activeRow, rowIndex);
1655
+ const maxRow = Math.max(activeRow, rowIndex);
1656
+ selectRange(toAddress(minRow, 0), toAddress(maxRow, columnCount - 1));
1657
+ } else {
1658
+ selectRange(toAddress(rowIndex, 0), toAddress(rowIndex, columnCount - 1));
1659
+ }
1660
+ },
1661
+ [rowIndex, columnCount, selectRange, selection.activeCell]
1662
+ );
1650
1663
  return /* @__PURE__ */ jsx(
1651
1664
  "div",
1652
1665
  {
@@ -1752,9 +1765,9 @@ var Cell = memo(function Cell2({ address, row, col }) {
1752
1765
  "data-active": isActive || void 0,
1753
1766
  role: "gridcell",
1754
1767
  className: cn(
1755
- "relative flex items-center truncate border-r border-b border-zinc-200 px-1.5 text-[13px] dark:border-zinc-700",
1768
+ "relative flex items-center truncate border-r border-b border-zinc-200 bg-white px-1.5 text-[13px] dark:border-zinc-700 dark:bg-zinc-900",
1756
1769
  isActive && "ring-2 ring-inset ring-blue-500",
1757
- isSelected && !isActive && "bg-blue-500/10"
1770
+ isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40"
1758
1771
  ),
1759
1772
  style: { width, minWidth: width, height: rowHeight, ...formatStyle },
1760
1773
  onMouseDown: handleMouseDown,
@@ -1897,6 +1910,8 @@ function SpreadsheetGrid({ className }) {
1897
1910
  confirmEdit,
1898
1911
  cancelEdit,
1899
1912
  setCellValue,
1913
+ setFrozenRows,
1914
+ setFrozenCols,
1900
1915
  undo,
1901
1916
  redo
1902
1917
  } = useSpreadsheet();
@@ -1986,66 +2001,114 @@ function SpreadsheetGrid({ className }) {
1986
2001
  const top = row * rowHeight;
1987
2002
  return { left, top };
1988
2003
  })() : null;
1989
- return /* @__PURE__ */ jsxs(
1990
- "div",
1991
- {
1992
- ref: containerRef,
1993
- "data-fancy-sheets-grid": "",
1994
- className: cn("relative min-h-0 flex-1 overflow-auto bg-white focus:outline-none dark:bg-zinc-900", className),
1995
- tabIndex: 0,
1996
- onKeyDown: handleKeyDown,
1997
- children: [
1998
- /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10", children: /* @__PURE__ */ jsx(ColumnHeaders, {}) }),
1999
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2000
- Array.from({ length: rowCount }, (_, rowIdx) => {
2001
- const isFrozenRow = rowIdx < activeSheet.frozenRows;
2002
- return /* @__PURE__ */ jsxs(
2004
+ const handleCopy = useCallback(() => {
2005
+ const range = selection.ranges[0];
2006
+ if (range) {
2007
+ const tsv = cellsToTSV(activeSheet.cells, range);
2008
+ navigator.clipboard.writeText(tsv);
2009
+ }
2010
+ }, [selection, activeSheet]);
2011
+ const handlePaste = useCallback(() => {
2012
+ navigator.clipboard.readText().then((text) => {
2013
+ if (!text) return;
2014
+ const { values } = tsvToCells(text);
2015
+ const { row: startRow, col: startCol } = parseAddress(selection.activeCell);
2016
+ for (let r = 0; r < values.length; r++) {
2017
+ for (let c = 0; c < values[r].length; c++) {
2018
+ setCellValue(toAddress(startRow + r, startCol + c), values[r][c]);
2019
+ }
2020
+ }
2021
+ });
2022
+ }, [selection, setCellValue]);
2023
+ const handleClearSelection = useCallback(() => {
2024
+ const range = selection.ranges[0];
2025
+ if (!range) return;
2026
+ const { start, end } = range;
2027
+ const s = parseAddress(start);
2028
+ const e = parseAddress(end);
2029
+ const minR = Math.min(s.row, e.row), maxR = Math.max(s.row, e.row);
2030
+ const minC = Math.min(s.col, e.col), maxC = Math.max(s.col, e.col);
2031
+ for (let r = minR; r <= maxR; r++) {
2032
+ for (let c = minC; c <= maxC; c++) {
2033
+ setCellValue(toAddress(r, c), "");
2034
+ }
2035
+ }
2036
+ }, [selection, setCellValue]);
2037
+ return /* @__PURE__ */ jsxs(ContextMenu, { children: [
2038
+ /* @__PURE__ */ jsx(ContextMenu.Trigger, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxs(
2039
+ "div",
2040
+ {
2041
+ ref: containerRef,
2042
+ "data-fancy-sheets-grid": "",
2043
+ className: cn("relative h-full overflow-auto bg-white focus:outline-none dark:bg-zinc-900", className),
2044
+ tabIndex: 0,
2045
+ onKeyDown: handleKeyDown,
2046
+ children: [
2047
+ /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10", children: /* @__PURE__ */ jsx(ColumnHeaders, {}) }),
2048
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2049
+ Array.from({ length: rowCount }, (_, rowIdx) => {
2050
+ const isFrozenRow = rowIdx < activeSheet.frozenRows;
2051
+ return /* @__PURE__ */ jsxs(
2052
+ "div",
2053
+ {
2054
+ className: "flex",
2055
+ style: isFrozenRow ? {
2056
+ position: "sticky",
2057
+ top: rowHeight + rowIdx * rowHeight,
2058
+ zIndex: 8
2059
+ } : void 0,
2060
+ children: [
2061
+ /* @__PURE__ */ jsx("div", { className: "sticky left-0 z-[5]", children: /* @__PURE__ */ jsx(RowHeader, { rowIndex: rowIdx }) }),
2062
+ Array.from({ length: columnCount }, (_2, colIdx) => {
2063
+ const addr = toAddress(rowIdx, colIdx);
2064
+ const isFrozenCol = colIdx < activeSheet.frozenCols;
2065
+ return /* @__PURE__ */ jsx(
2066
+ "div",
2067
+ {
2068
+ style: isFrozenCol ? {
2069
+ position: "sticky",
2070
+ left: 48 + Array.from({ length: colIdx }, (_3, c) => getColumnWidth(c)).reduce((a, b) => a + b, 0),
2071
+ zIndex: isFrozenRow ? 9 : 6
2072
+ } : void 0,
2073
+ children: /* @__PURE__ */ jsx(Cell, { address: addr, row: rowIdx, col: colIdx })
2074
+ },
2075
+ addr
2076
+ );
2077
+ })
2078
+ ]
2079
+ },
2080
+ rowIdx
2081
+ );
2082
+ }),
2083
+ /* @__PURE__ */ jsx(SelectionOverlay, {}),
2084
+ editorPosition && /* @__PURE__ */ jsx(
2003
2085
  "div",
2004
2086
  {
2005
- className: "flex",
2006
- style: isFrozenRow ? {
2007
- position: "sticky",
2008
- top: rowHeight + rowIdx * rowHeight,
2009
- zIndex: 8,
2010
- backgroundColor: "inherit"
2011
- } : void 0,
2012
- children: [
2013
- /* @__PURE__ */ jsx("div", { className: "sticky left-0 z-[5]", children: /* @__PURE__ */ jsx(RowHeader, { rowIndex: rowIdx }) }),
2014
- Array.from({ length: columnCount }, (_2, colIdx) => {
2015
- const addr = toAddress(rowIdx, colIdx);
2016
- const isFrozenCol = colIdx < activeSheet.frozenCols;
2017
- return /* @__PURE__ */ jsx(
2018
- "div",
2019
- {
2020
- style: isFrozenCol ? {
2021
- position: "sticky",
2022
- left: 48 + Array.from({ length: colIdx }, (_3, c) => getColumnWidth(c)).reduce((a, b) => a + b, 0),
2023
- zIndex: isFrozenRow ? 9 : 6,
2024
- backgroundColor: "inherit"
2025
- } : void 0,
2026
- children: /* @__PURE__ */ jsx(Cell, { address: addr, row: rowIdx, col: colIdx })
2027
- },
2028
- addr
2029
- );
2030
- })
2031
- ]
2032
- },
2033
- rowIdx
2034
- );
2035
- }),
2036
- /* @__PURE__ */ jsx(SelectionOverlay, {}),
2037
- editorPosition && /* @__PURE__ */ jsx(
2038
- "div",
2039
- {
2040
- className: "absolute z-20",
2041
- style: { left: editorPosition.left, top: editorPosition.top },
2042
- children: /* @__PURE__ */ jsx(CellEditor, {})
2043
- }
2044
- )
2045
- ] })
2046
- ]
2047
- }
2048
- );
2087
+ className: "absolute z-20",
2088
+ style: { left: editorPosition.left, top: editorPosition.top },
2089
+ children: /* @__PURE__ */ jsx(CellEditor, {})
2090
+ }
2091
+ )
2092
+ ] })
2093
+ ]
2094
+ }
2095
+ ) }),
2096
+ /* @__PURE__ */ jsxs(ContextMenu.Content, { children: [
2097
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: handleCopy, children: "Copy" }),
2098
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: handlePaste, disabled: readOnly, children: "Paste" }),
2099
+ /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
2100
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: handleClearSelection, disabled: readOnly, children: "Clear cells" }),
2101
+ /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
2102
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => {
2103
+ const row = parseAddress(selection.activeCell).row;
2104
+ setFrozenRows(activeSheet.frozenRows > 0 ? 0 : row);
2105
+ }, disabled: readOnly, children: activeSheet.frozenRows > 0 ? "Unfreeze rows" : "Freeze rows above" }),
2106
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => {
2107
+ const col = parseAddress(selection.activeCell).col;
2108
+ setFrozenCols(activeSheet.frozenCols > 0 ? 0 : col);
2109
+ }, disabled: readOnly, children: activeSheet.frozenCols > 0 ? "Unfreeze columns" : "Freeze columns left" })
2110
+ ] })
2111
+ ] });
2049
2112
  }
2050
2113
  SpreadsheetGrid.displayName = "SpreadsheetGrid";
2051
2114
  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";