@particle-academy/fancy-sheets 0.3.0 → 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 +195 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +196 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -110,6 +110,7 @@ interface SpreadsheetContextValue {
|
|
|
110
110
|
setSelection: (cell: string) => void;
|
|
111
111
|
extendSelection: (cell: string) => void;
|
|
112
112
|
addSelection: (cell: string) => void;
|
|
113
|
+
selectRange: (start: string, end: string) => void;
|
|
113
114
|
navigate: (direction: "up" | "down" | "left" | "right", extend?: boolean) => void;
|
|
114
115
|
startEdit: (value?: string) => void;
|
|
115
116
|
updateEdit: (value: string) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -110,6 +110,7 @@ interface SpreadsheetContextValue {
|
|
|
110
110
|
setSelection: (cell: string) => void;
|
|
111
111
|
extendSelection: (cell: string) => void;
|
|
112
112
|
addSelection: (cell: string) => void;
|
|
113
|
+
selectRange: (start: string, end: string) => void;
|
|
113
114
|
navigate: (direction: "up" | "down" | "left" | "right", extend?: boolean) => void;
|
|
114
115
|
startEdit: (value?: string) => void;
|
|
115
116
|
updateEdit: (value: string) => void;
|
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,7 +1599,20 @@ function ColumnResizeHandle({ colIndex }) {
|
|
|
1599
1599
|
}
|
|
1600
1600
|
ColumnResizeHandle.displayName = "ColumnResizeHandle";
|
|
1601
1601
|
function ColumnHeaders() {
|
|
1602
|
-
const { columnCount, rowHeight, getColumnWidth } = useSpreadsheet();
|
|
1602
|
+
const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange } = useSpreadsheet();
|
|
1603
|
+
const handleColumnClick = useCallback(
|
|
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
|
+
}
|
|
1613
|
+
},
|
|
1614
|
+
[rowCount, selectRange, selection.activeCell]
|
|
1615
|
+
);
|
|
1603
1616
|
return /* @__PURE__ */ jsxs(
|
|
1604
1617
|
"div",
|
|
1605
1618
|
{
|
|
@@ -1617,8 +1630,9 @@ function ColumnHeaders() {
|
|
|
1617
1630
|
Array.from({ length: columnCount }, (_, i) => /* @__PURE__ */ jsxs(
|
|
1618
1631
|
"div",
|
|
1619
1632
|
{
|
|
1620
|
-
className: "relative flex shrink-0 items-center justify-center border-r border-zinc-300 text-[11px] font-medium text-zinc-500 select-none dark:border-zinc-600 dark:text-zinc-400",
|
|
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",
|
|
1621
1634
|
style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
|
|
1635
|
+
onClick: (e) => handleColumnClick(i, e),
|
|
1622
1636
|
children: [
|
|
1623
1637
|
columnToLetter(i),
|
|
1624
1638
|
/* @__PURE__ */ jsx(ColumnResizeHandle, { colIndex: i })
|
|
@@ -1632,13 +1646,27 @@ function ColumnHeaders() {
|
|
|
1632
1646
|
}
|
|
1633
1647
|
ColumnHeaders.displayName = "ColumnHeaders";
|
|
1634
1648
|
function RowHeader({ rowIndex }) {
|
|
1635
|
-
const { rowHeight } = useSpreadsheet();
|
|
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
|
+
);
|
|
1636
1663
|
return /* @__PURE__ */ jsx(
|
|
1637
1664
|
"div",
|
|
1638
1665
|
{
|
|
1639
1666
|
"data-fancy-sheets-row-header": "",
|
|
1640
|
-
className: "flex shrink-0 items-center justify-center border-r border-b border-zinc-300 bg-zinc-100 text-[11px] font-medium text-zinc-500 select-none dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
|
|
1667
|
+
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",
|
|
1641
1668
|
style: { width: 48, minWidth: 48, height: rowHeight },
|
|
1669
|
+
onClick: handleClick,
|
|
1642
1670
|
children: rowIndex + 1
|
|
1643
1671
|
}
|
|
1644
1672
|
);
|
|
@@ -1737,9 +1765,9 @@ var Cell = memo(function Cell2({ address, row, col }) {
|
|
|
1737
1765
|
"data-active": isActive || void 0,
|
|
1738
1766
|
role: "gridcell",
|
|
1739
1767
|
className: cn(
|
|
1740
|
-
"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",
|
|
1741
1769
|
isActive && "ring-2 ring-inset ring-blue-500",
|
|
1742
|
-
isSelected && !isActive && "bg-blue-
|
|
1770
|
+
isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40"
|
|
1743
1771
|
),
|
|
1744
1772
|
style: { width, minWidth: width, height: rowHeight, ...formatStyle },
|
|
1745
1773
|
onMouseDown: handleMouseDown,
|
|
@@ -1882,6 +1910,8 @@ function SpreadsheetGrid({ className }) {
|
|
|
1882
1910
|
confirmEdit,
|
|
1883
1911
|
cancelEdit,
|
|
1884
1912
|
setCellValue,
|
|
1913
|
+
setFrozenRows,
|
|
1914
|
+
setFrozenCols,
|
|
1885
1915
|
undo,
|
|
1886
1916
|
redo
|
|
1887
1917
|
} = useSpreadsheet();
|
|
@@ -1971,66 +2001,114 @@ function SpreadsheetGrid({ className }) {
|
|
|
1971
2001
|
const top = row * rowHeight;
|
|
1972
2002
|
return { left, top };
|
|
1973
2003
|
})() : null;
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
{
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
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(
|
|
1988
2085
|
"div",
|
|
1989
2086
|
{
|
|
1990
|
-
className: "
|
|
1991
|
-
style:
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
})
|
|
2016
|
-
]
|
|
2017
|
-
},
|
|
2018
|
-
rowIdx
|
|
2019
|
-
);
|
|
2020
|
-
}),
|
|
2021
|
-
/* @__PURE__ */ jsx(SelectionOverlay, {}),
|
|
2022
|
-
editorPosition && /* @__PURE__ */ jsx(
|
|
2023
|
-
"div",
|
|
2024
|
-
{
|
|
2025
|
-
className: "absolute z-20",
|
|
2026
|
-
style: { left: editorPosition.left, top: editorPosition.top },
|
|
2027
|
-
children: /* @__PURE__ */ jsx(CellEditor, {})
|
|
2028
|
-
}
|
|
2029
|
-
)
|
|
2030
|
-
] })
|
|
2031
|
-
]
|
|
2032
|
-
}
|
|
2033
|
-
);
|
|
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
|
+
] });
|
|
2034
2112
|
}
|
|
2035
2113
|
SpreadsheetGrid.displayName = "SpreadsheetGrid";
|
|
2036
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";
|
|
@@ -2045,6 +2123,8 @@ function DefaultToolbar() {
|
|
|
2045
2123
|
confirmEdit,
|
|
2046
2124
|
startEdit,
|
|
2047
2125
|
setCellFormat,
|
|
2126
|
+
setFrozenRows,
|
|
2127
|
+
setFrozenCols,
|
|
2048
2128
|
undo,
|
|
2049
2129
|
redo,
|
|
2050
2130
|
canUndo,
|
|
@@ -2116,7 +2196,57 @@ function DefaultToolbar() {
|
|
|
2116
2196
|
] })
|
|
2117
2197
|
},
|
|
2118
2198
|
align
|
|
2119
|
-
))
|
|
2199
|
+
)),
|
|
2200
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
|
|
2201
|
+
/* @__PURE__ */ jsx(
|
|
2202
|
+
"button",
|
|
2203
|
+
{
|
|
2204
|
+
className: cn(btnClass, activeSheet.frozenRows > 0 && activeBtnClass),
|
|
2205
|
+
onClick: () => {
|
|
2206
|
+
if (activeSheet.frozenRows > 0) {
|
|
2207
|
+
setFrozenRows(0);
|
|
2208
|
+
} else {
|
|
2209
|
+
const row = selection.activeCell.match(/\d+/);
|
|
2210
|
+
setFrozenRows(row ? parseInt(row[0], 10) - 1 || 1 : 1);
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
disabled: readOnly,
|
|
2214
|
+
title: activeSheet.frozenRows > 0 ? `Unfreeze rows (${activeSheet.frozenRows} frozen)` : "Freeze rows above current cell",
|
|
2215
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
2216
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
|
|
2217
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "4", x2: "21", y2: "4" }),
|
|
2218
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "14", x2: "21", y2: "14", strokeDasharray: "3 3" }),
|
|
2219
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "19", x2: "21", y2: "19", strokeDasharray: "3 3" })
|
|
2220
|
+
] })
|
|
2221
|
+
}
|
|
2222
|
+
),
|
|
2223
|
+
/* @__PURE__ */ jsx(
|
|
2224
|
+
"button",
|
|
2225
|
+
{
|
|
2226
|
+
className: cn(btnClass, activeSheet.frozenCols > 0 && activeBtnClass),
|
|
2227
|
+
onClick: () => {
|
|
2228
|
+
if (activeSheet.frozenCols > 0) {
|
|
2229
|
+
setFrozenCols(0);
|
|
2230
|
+
} else {
|
|
2231
|
+
const colMatch = selection.activeCell.match(/^([A-Z]+)/);
|
|
2232
|
+
if (colMatch) {
|
|
2233
|
+
const col = colMatch[1].split("").reduce((acc, ch) => acc * 26 + ch.charCodeAt(0) - 64, 0) - 1;
|
|
2234
|
+
setFrozenCols(col || 1);
|
|
2235
|
+
} else {
|
|
2236
|
+
setFrozenCols(1);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
},
|
|
2240
|
+
disabled: readOnly,
|
|
2241
|
+
title: activeSheet.frozenCols > 0 ? `Unfreeze columns (${activeSheet.frozenCols} frozen)` : "Freeze columns left of current cell",
|
|
2242
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
2243
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }),
|
|
2244
|
+
/* @__PURE__ */ jsx("line", { x1: "4", y1: "3", x2: "4", y2: "21" }),
|
|
2245
|
+
/* @__PURE__ */ jsx("line", { x1: "14", y1: "3", x2: "14", y2: "21", strokeDasharray: "3 3" }),
|
|
2246
|
+
/* @__PURE__ */ jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
|
|
2247
|
+
] })
|
|
2248
|
+
}
|
|
2249
|
+
)
|
|
2120
2250
|
] }),
|
|
2121
2251
|
/* @__PURE__ */ 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: [
|
|
2122
2252
|
/* @__PURE__ */ jsx("span", { className: "w-12 shrink-0 text-center text-[11px] font-medium text-zinc-500 dark:text-zinc-400", children: selection.activeCell }),
|