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