@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.cjs
CHANGED
|
@@ -1601,7 +1601,20 @@ function ColumnResizeHandle({ colIndex }) {
|
|
|
1601
1601
|
}
|
|
1602
1602
|
ColumnResizeHandle.displayName = "ColumnResizeHandle";
|
|
1603
1603
|
function ColumnHeaders() {
|
|
1604
|
-
const { columnCount, rowHeight, getColumnWidth } = useSpreadsheet();
|
|
1604
|
+
const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange } = useSpreadsheet();
|
|
1605
|
+
const handleColumnClick = react.useCallback(
|
|
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
|
+
}
|
|
1615
|
+
},
|
|
1616
|
+
[rowCount, selectRange, selection.activeCell]
|
|
1617
|
+
);
|
|
1605
1618
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1606
1619
|
"div",
|
|
1607
1620
|
{
|
|
@@ -1619,8 +1632,9 @@ function ColumnHeaders() {
|
|
|
1619
1632
|
Array.from({ length: columnCount }, (_, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1620
1633
|
"div",
|
|
1621
1634
|
{
|
|
1622
|
-
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",
|
|
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",
|
|
1623
1636
|
style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
|
|
1637
|
+
onClick: (e) => handleColumnClick(i, e),
|
|
1624
1638
|
children: [
|
|
1625
1639
|
columnToLetter(i),
|
|
1626
1640
|
/* @__PURE__ */ jsxRuntime.jsx(ColumnResizeHandle, { colIndex: i })
|
|
@@ -1634,13 +1648,27 @@ function ColumnHeaders() {
|
|
|
1634
1648
|
}
|
|
1635
1649
|
ColumnHeaders.displayName = "ColumnHeaders";
|
|
1636
1650
|
function RowHeader({ rowIndex }) {
|
|
1637
|
-
const { rowHeight } = useSpreadsheet();
|
|
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
|
+
);
|
|
1638
1665
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1639
1666
|
"div",
|
|
1640
1667
|
{
|
|
1641
1668
|
"data-fancy-sheets-row-header": "",
|
|
1642
|
-
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",
|
|
1669
|
+
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",
|
|
1643
1670
|
style: { width: 48, minWidth: 48, height: rowHeight },
|
|
1671
|
+
onClick: handleClick,
|
|
1644
1672
|
children: rowIndex + 1
|
|
1645
1673
|
}
|
|
1646
1674
|
);
|
|
@@ -1739,9 +1767,9 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1739
1767
|
"data-active": isActive || void 0,
|
|
1740
1768
|
role: "gridcell",
|
|
1741
1769
|
className: reactFancy.cn(
|
|
1742
|
-
"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",
|
|
1743
1771
|
isActive && "ring-2 ring-inset ring-blue-500",
|
|
1744
|
-
isSelected && !isActive && "bg-blue-
|
|
1772
|
+
isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40"
|
|
1745
1773
|
),
|
|
1746
1774
|
style: { width, minWidth: width, height: rowHeight, ...formatStyle },
|
|
1747
1775
|
onMouseDown: handleMouseDown,
|
|
@@ -1884,6 +1912,8 @@ function SpreadsheetGrid({ className }) {
|
|
|
1884
1912
|
confirmEdit,
|
|
1885
1913
|
cancelEdit,
|
|
1886
1914
|
setCellValue,
|
|
1915
|
+
setFrozenRows,
|
|
1916
|
+
setFrozenCols,
|
|
1887
1917
|
undo,
|
|
1888
1918
|
redo
|
|
1889
1919
|
} = useSpreadsheet();
|
|
@@ -1973,66 +2003,114 @@ function SpreadsheetGrid({ className }) {
|
|
|
1973
2003
|
const top = row * rowHeight;
|
|
1974
2004
|
return { left, top };
|
|
1975
2005
|
})() : null;
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
{
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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(
|
|
1990
2087
|
"div",
|
|
1991
2088
|
{
|
|
1992
|
-
className: "
|
|
1993
|
-
style:
|
|
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
|
-
]
|
|
2019
|
-
},
|
|
2020
|
-
rowIdx
|
|
2021
|
-
);
|
|
2022
|
-
}),
|
|
2023
|
-
/* @__PURE__ */ jsxRuntime.jsx(SelectionOverlay, {}),
|
|
2024
|
-
editorPosition && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2025
|
-
"div",
|
|
2026
|
-
{
|
|
2027
|
-
className: "absolute z-20",
|
|
2028
|
-
style: { left: editorPosition.left, top: editorPosition.top },
|
|
2029
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(CellEditor, {})
|
|
2030
|
-
}
|
|
2031
|
-
)
|
|
2032
|
-
] })
|
|
2033
|
-
]
|
|
2034
|
-
}
|
|
2035
|
-
);
|
|
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
|
+
] });
|
|
2036
2114
|
}
|
|
2037
2115
|
SpreadsheetGrid.displayName = "SpreadsheetGrid";
|
|
2038
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";
|
|
@@ -2047,6 +2125,8 @@ function DefaultToolbar() {
|
|
|
2047
2125
|
confirmEdit,
|
|
2048
2126
|
startEdit,
|
|
2049
2127
|
setCellFormat,
|
|
2128
|
+
setFrozenRows,
|
|
2129
|
+
setFrozenCols,
|
|
2050
2130
|
undo,
|
|
2051
2131
|
redo,
|
|
2052
2132
|
canUndo,
|
|
@@ -2118,7 +2198,57 @@ function DefaultToolbar() {
|
|
|
2118
2198
|
] })
|
|
2119
2199
|
},
|
|
2120
2200
|
align
|
|
2121
|
-
))
|
|
2201
|
+
)),
|
|
2202
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
|
|
2203
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2204
|
+
"button",
|
|
2205
|
+
{
|
|
2206
|
+
className: reactFancy.cn(btnClass, activeSheet.frozenRows > 0 && activeBtnClass),
|
|
2207
|
+
onClick: () => {
|
|
2208
|
+
if (activeSheet.frozenRows > 0) {
|
|
2209
|
+
setFrozenRows(0);
|
|
2210
|
+
} else {
|
|
2211
|
+
const row = selection.activeCell.match(/\d+/);
|
|
2212
|
+
setFrozenRows(row ? parseInt(row[0], 10) - 1 || 1 : 1);
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
disabled: readOnly,
|
|
2216
|
+
title: activeSheet.frozenRows > 0 ? `Unfreeze rows (${activeSheet.frozenRows} frozen)` : "Freeze rows above current cell",
|
|
2217
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
2218
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
|
|
2219
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "4", x2: "21", y2: "4" }),
|
|
2220
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "14", x2: "21", y2: "14", strokeDasharray: "3 3" }),
|
|
2221
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "19", x2: "21", y2: "19", strokeDasharray: "3 3" })
|
|
2222
|
+
] })
|
|
2223
|
+
}
|
|
2224
|
+
),
|
|
2225
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2226
|
+
"button",
|
|
2227
|
+
{
|
|
2228
|
+
className: reactFancy.cn(btnClass, activeSheet.frozenCols > 0 && activeBtnClass),
|
|
2229
|
+
onClick: () => {
|
|
2230
|
+
if (activeSheet.frozenCols > 0) {
|
|
2231
|
+
setFrozenCols(0);
|
|
2232
|
+
} else {
|
|
2233
|
+
const colMatch = selection.activeCell.match(/^([A-Z]+)/);
|
|
2234
|
+
if (colMatch) {
|
|
2235
|
+
const col = colMatch[1].split("").reduce((acc, ch) => acc * 26 + ch.charCodeAt(0) - 64, 0) - 1;
|
|
2236
|
+
setFrozenCols(col || 1);
|
|
2237
|
+
} else {
|
|
2238
|
+
setFrozenCols(1);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
disabled: readOnly,
|
|
2243
|
+
title: activeSheet.frozenCols > 0 ? `Unfreeze columns (${activeSheet.frozenCols} frozen)` : "Freeze columns left of current cell",
|
|
2244
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
2245
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }),
|
|
2246
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "3", x2: "4", y2: "21" }),
|
|
2247
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14", y1: "3", x2: "14", y2: "21", strokeDasharray: "3 3" }),
|
|
2248
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
|
|
2249
|
+
] })
|
|
2250
|
+
}
|
|
2251
|
+
)
|
|
2122
2252
|
] }),
|
|
2123
2253
|
/* @__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: [
|
|
2124
2254
|
/* @__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 }),
|