@particle-academy/fancy-sheets 0.3.1 → 0.4.1
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 +189 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +190 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1601,15 +1601,36 @@ function ColumnResizeHandle({ colIndex }) {
|
|
|
1601
1601
|
}
|
|
1602
1602
|
ColumnResizeHandle.displayName = "ColumnResizeHandle";
|
|
1603
1603
|
function ColumnHeaders() {
|
|
1604
|
-
const { columnCount, rowCount, rowHeight, getColumnWidth, selectRange } = useSpreadsheet();
|
|
1605
|
-
const
|
|
1604
|
+
const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange, _isDragging } = useSpreadsheet();
|
|
1605
|
+
const handleColumnMouseDown = react.useCallback(
|
|
1606
|
+
(colIdx, e) => {
|
|
1607
|
+
if (e.button !== 0) return;
|
|
1608
|
+
if (e.shiftKey) {
|
|
1609
|
+
const activeCol = parseAddress(selection.activeCell).col;
|
|
1610
|
+
const minCol = Math.min(activeCol, colIdx);
|
|
1611
|
+
const maxCol = Math.max(activeCol, colIdx);
|
|
1612
|
+
selectRange(toAddress(0, minCol), toAddress(rowCount - 1, maxCol));
|
|
1613
|
+
} else {
|
|
1614
|
+
selectRange(toAddress(0, colIdx), toAddress(rowCount - 1, colIdx));
|
|
1615
|
+
}
|
|
1616
|
+
_isDragging.current = true;
|
|
1617
|
+
},
|
|
1618
|
+
[rowCount, selectRange, selection.activeCell, _isDragging]
|
|
1619
|
+
);
|
|
1620
|
+
const handleColumnMouseEnter = react.useCallback(
|
|
1606
1621
|
(colIdx) => {
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1622
|
+
if (_isDragging.current) {
|
|
1623
|
+
const activeCol = parseAddress(selection.activeCell).col;
|
|
1624
|
+
const minCol = Math.min(activeCol, colIdx);
|
|
1625
|
+
const maxCol = Math.max(activeCol, colIdx);
|
|
1626
|
+
selectRange(toAddress(0, minCol), toAddress(rowCount - 1, maxCol));
|
|
1627
|
+
}
|
|
1610
1628
|
},
|
|
1611
|
-
[rowCount, selectRange]
|
|
1629
|
+
[rowCount, selection.activeCell, selectRange, _isDragging]
|
|
1612
1630
|
);
|
|
1631
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1632
|
+
_isDragging.current = false;
|
|
1633
|
+
}, [_isDragging]);
|
|
1613
1634
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1614
1635
|
"div",
|
|
1615
1636
|
{
|
|
@@ -1629,7 +1650,9 @@ function ColumnHeaders() {
|
|
|
1629
1650
|
{
|
|
1630
1651
|
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
1652
|
style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
|
|
1632
|
-
|
|
1653
|
+
onMouseDown: (e) => handleColumnMouseDown(i, e),
|
|
1654
|
+
onMouseEnter: () => handleColumnMouseEnter(i),
|
|
1655
|
+
onMouseUp: handleMouseUp,
|
|
1633
1656
|
children: [
|
|
1634
1657
|
columnToLetter(i),
|
|
1635
1658
|
/* @__PURE__ */ jsxRuntime.jsx(ColumnResizeHandle, { colIndex: i })
|
|
@@ -1643,19 +1666,42 @@ function ColumnHeaders() {
|
|
|
1643
1666
|
}
|
|
1644
1667
|
ColumnHeaders.displayName = "ColumnHeaders";
|
|
1645
1668
|
function RowHeader({ rowIndex }) {
|
|
1646
|
-
const { rowHeight, columnCount, selectRange } = useSpreadsheet();
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1669
|
+
const { rowHeight, columnCount, selection, selectRange, _isDragging } = useSpreadsheet();
|
|
1670
|
+
const handleMouseDown = react.useCallback(
|
|
1671
|
+
(e) => {
|
|
1672
|
+
if (e.button !== 0) return;
|
|
1673
|
+
if (e.shiftKey) {
|
|
1674
|
+
const activeRow = parseAddress(selection.activeCell).row;
|
|
1675
|
+
const minRow = Math.min(activeRow, rowIndex);
|
|
1676
|
+
const maxRow = Math.max(activeRow, rowIndex);
|
|
1677
|
+
selectRange(toAddress(minRow, 0), toAddress(maxRow, columnCount - 1));
|
|
1678
|
+
} else {
|
|
1679
|
+
selectRange(toAddress(rowIndex, 0), toAddress(rowIndex, columnCount - 1));
|
|
1680
|
+
}
|
|
1681
|
+
_isDragging.current = true;
|
|
1682
|
+
},
|
|
1683
|
+
[rowIndex, columnCount, selectRange, selection.activeCell, _isDragging]
|
|
1684
|
+
);
|
|
1685
|
+
const handleMouseEnter = react.useCallback(() => {
|
|
1686
|
+
if (_isDragging.current) {
|
|
1687
|
+
const activeRow = parseAddress(selection.activeCell).row;
|
|
1688
|
+
const minRow = Math.min(activeRow, rowIndex);
|
|
1689
|
+
const maxRow = Math.max(activeRow, rowIndex);
|
|
1690
|
+
selectRange(toAddress(minRow, 0), toAddress(maxRow, columnCount - 1));
|
|
1691
|
+
}
|
|
1692
|
+
}, [rowIndex, columnCount, selection.activeCell, selectRange, _isDragging]);
|
|
1693
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1694
|
+
_isDragging.current = false;
|
|
1695
|
+
}, [_isDragging]);
|
|
1652
1696
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1653
1697
|
"div",
|
|
1654
1698
|
{
|
|
1655
1699
|
"data-fancy-sheets-row-header": "",
|
|
1656
1700
|
className: "flex shrink-0 cursor-pointer items-center justify-center border-r border-b border-zinc-300 bg-zinc-100 text-[11px] font-medium text-zinc-500 select-none hover:bg-zinc-200 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700",
|
|
1657
1701
|
style: { width: 48, minWidth: 48, height: rowHeight },
|
|
1658
|
-
|
|
1702
|
+
onMouseDown: handleMouseDown,
|
|
1703
|
+
onMouseEnter: handleMouseEnter,
|
|
1704
|
+
onMouseUp: handleMouseUp,
|
|
1659
1705
|
children: rowIndex + 1
|
|
1660
1706
|
}
|
|
1661
1707
|
);
|
|
@@ -1718,7 +1764,8 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1718
1764
|
rowHeight,
|
|
1719
1765
|
getColumnWidth,
|
|
1720
1766
|
isCellSelected,
|
|
1721
|
-
isCellActive
|
|
1767
|
+
isCellActive,
|
|
1768
|
+
_isDragging
|
|
1722
1769
|
} = useSpreadsheet();
|
|
1723
1770
|
const cell = activeSheet.cells[address];
|
|
1724
1771
|
const isActive = isCellActive(address);
|
|
@@ -1728,6 +1775,7 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1728
1775
|
const width = getColumnWidth(col);
|
|
1729
1776
|
const handleMouseDown = react.useCallback(
|
|
1730
1777
|
(e) => {
|
|
1778
|
+
if (e.button !== 0) return;
|
|
1731
1779
|
if (e.shiftKey) {
|
|
1732
1780
|
extendSelection(address);
|
|
1733
1781
|
} else if (e.ctrlKey || e.metaKey) {
|
|
@@ -1735,9 +1783,18 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1735
1783
|
} else {
|
|
1736
1784
|
setSelection(address);
|
|
1737
1785
|
}
|
|
1786
|
+
_isDragging.current = true;
|
|
1738
1787
|
},
|
|
1739
|
-
[address, setSelection, extendSelection, addSelection]
|
|
1788
|
+
[address, setSelection, extendSelection, addSelection, _isDragging]
|
|
1740
1789
|
);
|
|
1790
|
+
const handleMouseEnter = react.useCallback(() => {
|
|
1791
|
+
if (_isDragging.current) {
|
|
1792
|
+
extendSelection(address);
|
|
1793
|
+
}
|
|
1794
|
+
}, [address, extendSelection, _isDragging]);
|
|
1795
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1796
|
+
_isDragging.current = false;
|
|
1797
|
+
}, [_isDragging]);
|
|
1741
1798
|
const handleDoubleClick = react.useCallback(() => {
|
|
1742
1799
|
if (readOnly) return;
|
|
1743
1800
|
startEdit();
|
|
@@ -1754,12 +1811,14 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1754
1811
|
"data-active": isActive || void 0,
|
|
1755
1812
|
role: "gridcell",
|
|
1756
1813
|
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",
|
|
1814
|
+
"relative flex items-center truncate border-r border-b border-zinc-200 bg-white px-1.5 text-[13px] select-none dark:border-zinc-700 dark:bg-zinc-900",
|
|
1758
1815
|
isActive && "ring-2 ring-inset ring-blue-500",
|
|
1759
|
-
isSelected && !isActive && "bg-blue-
|
|
1816
|
+
isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40"
|
|
1760
1817
|
),
|
|
1761
1818
|
style: { width, minWidth: width, height: rowHeight, ...formatStyle },
|
|
1762
1819
|
onMouseDown: handleMouseDown,
|
|
1820
|
+
onMouseEnter: handleMouseEnter,
|
|
1821
|
+
onMouseUp: handleMouseUp,
|
|
1763
1822
|
onDoubleClick: handleDoubleClick,
|
|
1764
1823
|
children: !isEditing && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: displayValue })
|
|
1765
1824
|
}
|
|
@@ -1899,6 +1958,9 @@ function SpreadsheetGrid({ className }) {
|
|
|
1899
1958
|
confirmEdit,
|
|
1900
1959
|
cancelEdit,
|
|
1901
1960
|
setCellValue,
|
|
1961
|
+
setFrozenRows,
|
|
1962
|
+
setFrozenCols,
|
|
1963
|
+
extendSelection,
|
|
1902
1964
|
undo,
|
|
1903
1965
|
redo
|
|
1904
1966
|
} = useSpreadsheet();
|
|
@@ -1988,66 +2050,114 @@ function SpreadsheetGrid({ className }) {
|
|
|
1988
2050
|
const top = row * rowHeight;
|
|
1989
2051
|
return { left, top };
|
|
1990
2052
|
})() : null;
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
{
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2053
|
+
const handleCopy = react.useCallback(() => {
|
|
2054
|
+
const range = selection.ranges[0];
|
|
2055
|
+
if (range) {
|
|
2056
|
+
const tsv = cellsToTSV(activeSheet.cells, range);
|
|
2057
|
+
navigator.clipboard.writeText(tsv);
|
|
2058
|
+
}
|
|
2059
|
+
}, [selection, activeSheet]);
|
|
2060
|
+
const handlePaste = react.useCallback(() => {
|
|
2061
|
+
navigator.clipboard.readText().then((text) => {
|
|
2062
|
+
if (!text) return;
|
|
2063
|
+
const { values } = tsvToCells(text);
|
|
2064
|
+
const { row: startRow, col: startCol } = parseAddress(selection.activeCell);
|
|
2065
|
+
for (let r = 0; r < values.length; r++) {
|
|
2066
|
+
for (let c = 0; c < values[r].length; c++) {
|
|
2067
|
+
setCellValue(toAddress(startRow + r, startCol + c), values[r][c]);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
}, [selection, setCellValue]);
|
|
2072
|
+
const handleClearSelection = react.useCallback(() => {
|
|
2073
|
+
const range = selection.ranges[0];
|
|
2074
|
+
if (!range) return;
|
|
2075
|
+
const { start, end } = range;
|
|
2076
|
+
const s = parseAddress(start);
|
|
2077
|
+
const e = parseAddress(end);
|
|
2078
|
+
const minR = Math.min(s.row, e.row), maxR = Math.max(s.row, e.row);
|
|
2079
|
+
const minC = Math.min(s.col, e.col), maxC = Math.max(s.col, e.col);
|
|
2080
|
+
for (let r = minR; r <= maxR; r++) {
|
|
2081
|
+
for (let c = minC; c <= maxC; c++) {
|
|
2082
|
+
setCellValue(toAddress(r, c), "");
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
}, [selection, setCellValue]);
|
|
2086
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu, { children: [
|
|
2087
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Trigger, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2088
|
+
"div",
|
|
2089
|
+
{
|
|
2090
|
+
ref: containerRef,
|
|
2091
|
+
"data-fancy-sheets-grid": "",
|
|
2092
|
+
className: reactFancy.cn("relative h-full overflow-auto bg-white focus:outline-none dark:bg-zinc-900", className),
|
|
2093
|
+
tabIndex: 0,
|
|
2094
|
+
onKeyDown: handleKeyDown,
|
|
2095
|
+
children: [
|
|
2096
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(ColumnHeaders, {}) }),
|
|
2097
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
2098
|
+
Array.from({ length: rowCount }, (_, rowIdx) => {
|
|
2099
|
+
const isFrozenRow = rowIdx < activeSheet.frozenRows;
|
|
2100
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2101
|
+
"div",
|
|
2102
|
+
{
|
|
2103
|
+
className: "flex",
|
|
2104
|
+
style: isFrozenRow ? {
|
|
2105
|
+
position: "sticky",
|
|
2106
|
+
top: rowHeight + rowIdx * rowHeight,
|
|
2107
|
+
zIndex: 8
|
|
2108
|
+
} : void 0,
|
|
2109
|
+
children: [
|
|
2110
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky left-0 z-[5]", children: /* @__PURE__ */ jsxRuntime.jsx(RowHeader, { rowIndex: rowIdx }) }),
|
|
2111
|
+
Array.from({ length: columnCount }, (_2, colIdx) => {
|
|
2112
|
+
const addr = toAddress(rowIdx, colIdx);
|
|
2113
|
+
const isFrozenCol = colIdx < activeSheet.frozenCols;
|
|
2114
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2115
|
+
"div",
|
|
2116
|
+
{
|
|
2117
|
+
style: isFrozenCol ? {
|
|
2118
|
+
position: "sticky",
|
|
2119
|
+
left: 48 + Array.from({ length: colIdx }, (_3, c) => getColumnWidth(c)).reduce((a, b) => a + b, 0),
|
|
2120
|
+
zIndex: isFrozenRow ? 9 : 6
|
|
2121
|
+
} : void 0,
|
|
2122
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Cell, { address: addr, row: rowIdx, col: colIdx })
|
|
2123
|
+
},
|
|
2124
|
+
addr
|
|
2125
|
+
);
|
|
2126
|
+
})
|
|
2127
|
+
]
|
|
2128
|
+
},
|
|
2129
|
+
rowIdx
|
|
2130
|
+
);
|
|
2131
|
+
}),
|
|
2132
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectionOverlay, {}),
|
|
2133
|
+
editorPosition && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2005
2134
|
"div",
|
|
2006
2135
|
{
|
|
2007
|
-
className: "
|
|
2008
|
-
style:
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
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
|
-
);
|
|
2136
|
+
className: "absolute z-20",
|
|
2137
|
+
style: { left: editorPosition.left, top: editorPosition.top },
|
|
2138
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CellEditor, {})
|
|
2139
|
+
}
|
|
2140
|
+
)
|
|
2141
|
+
] })
|
|
2142
|
+
]
|
|
2143
|
+
}
|
|
2144
|
+
) }),
|
|
2145
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Content, { children: [
|
|
2146
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: handleCopy, children: "Copy" }),
|
|
2147
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: handlePaste, disabled: readOnly, children: "Paste" }),
|
|
2148
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
2149
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: handleClearSelection, disabled: readOnly, children: "Clear cells" }),
|
|
2150
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
2151
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => {
|
|
2152
|
+
const row = parseAddress(selection.activeCell).row;
|
|
2153
|
+
setFrozenRows(activeSheet.frozenRows > 0 ? 0 : row);
|
|
2154
|
+
}, disabled: readOnly, children: activeSheet.frozenRows > 0 ? "Unfreeze rows" : "Freeze rows above" }),
|
|
2155
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => {
|
|
2156
|
+
const col = parseAddress(selection.activeCell).col;
|
|
2157
|
+
setFrozenCols(activeSheet.frozenCols > 0 ? 0 : col);
|
|
2158
|
+
}, disabled: readOnly, children: activeSheet.frozenCols > 0 ? "Unfreeze columns" : "Freeze columns left" })
|
|
2159
|
+
] })
|
|
2160
|
+
] });
|
|
2051
2161
|
}
|
|
2052
2162
|
SpreadsheetGrid.displayName = "SpreadsheetGrid";
|
|
2053
2163
|
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";
|
|
@@ -2348,6 +2458,7 @@ function SpreadsheetRoot({
|
|
|
2348
2458
|
(address) => state.selection.activeCell === address,
|
|
2349
2459
|
[state.selection.activeCell]
|
|
2350
2460
|
);
|
|
2461
|
+
const isDraggingRef = react.useRef(false);
|
|
2351
2462
|
const ctx = react.useMemo(
|
|
2352
2463
|
() => ({
|
|
2353
2464
|
workbook: state.workbook,
|
|
@@ -2365,7 +2476,8 @@ function SpreadsheetRoot({
|
|
|
2365
2476
|
canRedo: state.redoStack.length > 0,
|
|
2366
2477
|
getColumnWidth,
|
|
2367
2478
|
isCellSelected,
|
|
2368
|
-
isCellActive
|
|
2479
|
+
isCellActive,
|
|
2480
|
+
_isDragging: isDraggingRef
|
|
2369
2481
|
}),
|
|
2370
2482
|
[state, activeSheet, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, actions, getColumnWidth, isCellSelected, isCellActive]
|
|
2371
2483
|
);
|