@particle-academy/fancy-sheets 0.4.0 → 0.4.2
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 +139 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +139 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1601,9 +1601,10 @@ function ColumnResizeHandle({ colIndex }) {
|
|
|
1601
1601
|
}
|
|
1602
1602
|
ColumnResizeHandle.displayName = "ColumnResizeHandle";
|
|
1603
1603
|
function ColumnHeaders() {
|
|
1604
|
-
const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange } = useSpreadsheet();
|
|
1605
|
-
const
|
|
1604
|
+
const { columnCount, rowCount, rowHeight, getColumnWidth, selection, selectRange, _isDragging, isCellSelected } = useSpreadsheet();
|
|
1605
|
+
const handleColumnMouseDown = react.useCallback(
|
|
1606
1606
|
(colIdx, e) => {
|
|
1607
|
+
if (e.button !== 0) return;
|
|
1607
1608
|
if (e.shiftKey) {
|
|
1608
1609
|
const activeCol = parseAddress(selection.activeCell).col;
|
|
1609
1610
|
const minCol = Math.min(activeCol, colIdx);
|
|
@@ -1612,9 +1613,24 @@ function ColumnHeaders() {
|
|
|
1612
1613
|
} else {
|
|
1613
1614
|
selectRange(toAddress(0, colIdx), toAddress(rowCount - 1, colIdx));
|
|
1614
1615
|
}
|
|
1616
|
+
_isDragging.current = true;
|
|
1615
1617
|
},
|
|
1616
|
-
[rowCount, selectRange, selection.activeCell]
|
|
1618
|
+
[rowCount, selectRange, selection.activeCell, _isDragging]
|
|
1617
1619
|
);
|
|
1620
|
+
const handleColumnMouseEnter = react.useCallback(
|
|
1621
|
+
(colIdx) => {
|
|
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
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
[rowCount, selection.activeCell, selectRange, _isDragging]
|
|
1630
|
+
);
|
|
1631
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1632
|
+
_isDragging.current = false;
|
|
1633
|
+
}, [_isDragging]);
|
|
1618
1634
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1619
1635
|
"div",
|
|
1620
1636
|
{
|
|
@@ -1629,28 +1645,38 @@ function ColumnHeaders() {
|
|
|
1629
1645
|
style: { width: 48, minWidth: 48 }
|
|
1630
1646
|
}
|
|
1631
1647
|
),
|
|
1632
|
-
Array.from({ length: columnCount }, (_, i) =>
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1648
|
+
Array.from({ length: columnCount }, (_, i) => {
|
|
1649
|
+
const isColSelected = isCellSelected(toAddress(0, i));
|
|
1650
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1651
|
+
"div",
|
|
1652
|
+
{
|
|
1653
|
+
className: reactFancy.cn(
|
|
1654
|
+
"relative flex shrink-0 cursor-pointer items-center justify-center border-r border-zinc-300 text-[11px] font-medium select-none hover:bg-zinc-200 dark:border-zinc-600 dark:hover:bg-zinc-700",
|
|
1655
|
+
isColSelected ? "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" : "text-zinc-500 dark:text-zinc-400"
|
|
1656
|
+
),
|
|
1657
|
+
style: { width: getColumnWidth(i), minWidth: getColumnWidth(i) },
|
|
1658
|
+
onMouseDown: (e) => handleColumnMouseDown(i, e),
|
|
1659
|
+
onMouseEnter: () => handleColumnMouseEnter(i),
|
|
1660
|
+
onMouseUp: handleMouseUp,
|
|
1661
|
+
children: [
|
|
1662
|
+
columnToLetter(i),
|
|
1663
|
+
/* @__PURE__ */ jsxRuntime.jsx(ColumnResizeHandle, { colIndex: i })
|
|
1664
|
+
]
|
|
1665
|
+
},
|
|
1666
|
+
i
|
|
1667
|
+
);
|
|
1668
|
+
})
|
|
1645
1669
|
]
|
|
1646
1670
|
}
|
|
1647
1671
|
);
|
|
1648
1672
|
}
|
|
1649
1673
|
ColumnHeaders.displayName = "ColumnHeaders";
|
|
1650
1674
|
function RowHeader({ rowIndex }) {
|
|
1651
|
-
const { rowHeight, columnCount, selection, selectRange,
|
|
1652
|
-
const
|
|
1675
|
+
const { rowHeight, columnCount, selection, selectRange, _isDragging, isCellSelected } = useSpreadsheet();
|
|
1676
|
+
const isRowSelected = isCellSelected(toAddress(rowIndex, 0));
|
|
1677
|
+
const handleMouseDown = react.useCallback(
|
|
1653
1678
|
(e) => {
|
|
1679
|
+
if (e.button !== 0) return;
|
|
1654
1680
|
if (e.shiftKey) {
|
|
1655
1681
|
const activeRow = parseAddress(selection.activeCell).row;
|
|
1656
1682
|
const minRow = Math.min(activeRow, rowIndex);
|
|
@@ -1659,16 +1685,33 @@ function RowHeader({ rowIndex }) {
|
|
|
1659
1685
|
} else {
|
|
1660
1686
|
selectRange(toAddress(rowIndex, 0), toAddress(rowIndex, columnCount - 1));
|
|
1661
1687
|
}
|
|
1688
|
+
_isDragging.current = true;
|
|
1662
1689
|
},
|
|
1663
|
-
[rowIndex, columnCount, selectRange, selection.activeCell]
|
|
1690
|
+
[rowIndex, columnCount, selectRange, selection.activeCell, _isDragging]
|
|
1664
1691
|
);
|
|
1692
|
+
const handleMouseEnter = react.useCallback(() => {
|
|
1693
|
+
if (_isDragging.current) {
|
|
1694
|
+
const activeRow = parseAddress(selection.activeCell).row;
|
|
1695
|
+
const minRow = Math.min(activeRow, rowIndex);
|
|
1696
|
+
const maxRow = Math.max(activeRow, rowIndex);
|
|
1697
|
+
selectRange(toAddress(minRow, 0), toAddress(maxRow, columnCount - 1));
|
|
1698
|
+
}
|
|
1699
|
+
}, [rowIndex, columnCount, selection.activeCell, selectRange, _isDragging]);
|
|
1700
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1701
|
+
_isDragging.current = false;
|
|
1702
|
+
}, [_isDragging]);
|
|
1665
1703
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1666
1704
|
"div",
|
|
1667
1705
|
{
|
|
1668
1706
|
"data-fancy-sheets-row-header": "",
|
|
1669
|
-
className:
|
|
1707
|
+
className: reactFancy.cn(
|
|
1708
|
+
"flex shrink-0 cursor-pointer items-center justify-center border-r border-b border-zinc-300 text-[11px] font-medium select-none hover:bg-zinc-200 dark:border-zinc-600 dark:hover:bg-zinc-700",
|
|
1709
|
+
isRowSelected ? "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" : "bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400"
|
|
1710
|
+
),
|
|
1670
1711
|
style: { width: 48, minWidth: 48, height: rowHeight },
|
|
1671
|
-
|
|
1712
|
+
onMouseDown: handleMouseDown,
|
|
1713
|
+
onMouseEnter: handleMouseEnter,
|
|
1714
|
+
onMouseUp: handleMouseUp,
|
|
1672
1715
|
children: rowIndex + 1
|
|
1673
1716
|
}
|
|
1674
1717
|
);
|
|
@@ -1693,21 +1736,24 @@ function serialToDateTimeStr(serial) {
|
|
|
1693
1736
|
}
|
|
1694
1737
|
function isDateFormula(formula) {
|
|
1695
1738
|
if (!formula) return false;
|
|
1696
|
-
const f = formula.toUpperCase();
|
|
1697
|
-
return /^(TODAY|NOW|DATE|EDATE)\
|
|
1739
|
+
const f = formula.trim().toUpperCase();
|
|
1740
|
+
return /^(TODAY|NOW|DATE|EDATE)\s*\(/.test(f);
|
|
1698
1741
|
}
|
|
1699
1742
|
function formatCellValue(val, cell) {
|
|
1700
1743
|
if (val === null || val === void 0) return "";
|
|
1701
1744
|
const fmt = cell?.format?.displayFormat;
|
|
1702
1745
|
if (typeof val === "number") {
|
|
1746
|
+
const dec = cell?.format?.decimals;
|
|
1703
1747
|
if (fmt === "date") return serialToDateStr(val);
|
|
1704
1748
|
if (fmt === "datetime") return serialToDateTimeStr(val);
|
|
1705
|
-
if (fmt === "percentage") return (val * 100).toFixed(1) + "%";
|
|
1706
|
-
if (fmt === "currency") return "$" + val.toFixed(2);
|
|
1749
|
+
if (fmt === "percentage") return (val * 100).toFixed(dec ?? 1) + "%";
|
|
1750
|
+
if (fmt === "currency") return "$" + val.toFixed(dec ?? 2);
|
|
1751
|
+
if (fmt === "number" && dec !== void 0) return val.toFixed(dec);
|
|
1707
1752
|
if (fmt === "auto" || !fmt) {
|
|
1708
1753
|
if (cell?.formula && isDateFormula(cell.formula)) {
|
|
1709
1754
|
return val % 1 === 0 ? serialToDateStr(val) : serialToDateTimeStr(val);
|
|
1710
1755
|
}
|
|
1756
|
+
if (dec !== void 0) return val.toFixed(dec);
|
|
1711
1757
|
}
|
|
1712
1758
|
}
|
|
1713
1759
|
if (typeof val === "boolean") return val ? "TRUE" : "FALSE";
|
|
@@ -1731,7 +1777,8 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1731
1777
|
rowHeight,
|
|
1732
1778
|
getColumnWidth,
|
|
1733
1779
|
isCellSelected,
|
|
1734
|
-
isCellActive
|
|
1780
|
+
isCellActive,
|
|
1781
|
+
_isDragging
|
|
1735
1782
|
} = useSpreadsheet();
|
|
1736
1783
|
const cell = activeSheet.cells[address];
|
|
1737
1784
|
const isActive = isCellActive(address);
|
|
@@ -1741,6 +1788,7 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1741
1788
|
const width = getColumnWidth(col);
|
|
1742
1789
|
const handleMouseDown = react.useCallback(
|
|
1743
1790
|
(e) => {
|
|
1791
|
+
if (e.button !== 0) return;
|
|
1744
1792
|
if (e.shiftKey) {
|
|
1745
1793
|
extendSelection(address);
|
|
1746
1794
|
} else if (e.ctrlKey || e.metaKey) {
|
|
@@ -1748,9 +1796,18 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1748
1796
|
} else {
|
|
1749
1797
|
setSelection(address);
|
|
1750
1798
|
}
|
|
1799
|
+
_isDragging.current = true;
|
|
1751
1800
|
},
|
|
1752
|
-
[address, setSelection, extendSelection, addSelection]
|
|
1801
|
+
[address, setSelection, extendSelection, addSelection, _isDragging]
|
|
1753
1802
|
);
|
|
1803
|
+
const handleMouseEnter = react.useCallback(() => {
|
|
1804
|
+
if (_isDragging.current) {
|
|
1805
|
+
extendSelection(address);
|
|
1806
|
+
}
|
|
1807
|
+
}, [address, extendSelection, _isDragging]);
|
|
1808
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1809
|
+
_isDragging.current = false;
|
|
1810
|
+
}, [_isDragging]);
|
|
1754
1811
|
const handleDoubleClick = react.useCallback(() => {
|
|
1755
1812
|
if (readOnly) return;
|
|
1756
1813
|
startEdit();
|
|
@@ -1767,12 +1824,14 @@ var Cell = react.memo(function Cell2({ address, row, col }) {
|
|
|
1767
1824
|
"data-active": isActive || void 0,
|
|
1768
1825
|
role: "gridcell",
|
|
1769
1826
|
className: reactFancy.cn(
|
|
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",
|
|
1827
|
+
"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",
|
|
1771
1828
|
isActive && "ring-2 ring-inset ring-blue-500",
|
|
1772
1829
|
isSelected && !isActive && "bg-blue-50 dark:bg-blue-950/40"
|
|
1773
1830
|
),
|
|
1774
1831
|
style: { width, minWidth: width, height: rowHeight, ...formatStyle },
|
|
1775
1832
|
onMouseDown: handleMouseDown,
|
|
1833
|
+
onMouseEnter: handleMouseEnter,
|
|
1834
|
+
onMouseUp: handleMouseUp,
|
|
1776
1835
|
onDoubleClick: handleDoubleClick,
|
|
1777
1836
|
children: !isEditing && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: displayValue })
|
|
1778
1837
|
}
|
|
@@ -1914,6 +1973,7 @@ function SpreadsheetGrid({ className }) {
|
|
|
1914
1973
|
setCellValue,
|
|
1915
1974
|
setFrozenRows,
|
|
1916
1975
|
setFrozenCols,
|
|
1976
|
+
extendSelection,
|
|
1917
1977
|
undo,
|
|
1918
1978
|
redo
|
|
1919
1979
|
} = useSpreadsheet();
|
|
@@ -2137,6 +2197,8 @@ function DefaultToolbar() {
|
|
|
2137
2197
|
const isBold = cell?.format?.bold ?? false;
|
|
2138
2198
|
const isItalic = cell?.format?.italic ?? false;
|
|
2139
2199
|
const textAlign = cell?.format?.textAlign ?? "left";
|
|
2200
|
+
const displayFormat = cell?.format?.displayFormat ?? "auto";
|
|
2201
|
+
const decimals = cell?.format?.decimals;
|
|
2140
2202
|
const selectedAddresses = [selection.activeCell];
|
|
2141
2203
|
const handleFormulaBarChange = (e) => {
|
|
2142
2204
|
if (editingCell) {
|
|
@@ -2248,6 +2310,52 @@ function DefaultToolbar() {
|
|
|
2248
2310
|
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "3", x2: "19", y2: "21", strokeDasharray: "3 3" })
|
|
2249
2311
|
] })
|
|
2250
2312
|
}
|
|
2313
|
+
),
|
|
2314
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700" }),
|
|
2315
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2316
|
+
"select",
|
|
2317
|
+
{
|
|
2318
|
+
className: "h-6 rounded border border-zinc-200 bg-transparent px-1 text-[11px] text-zinc-600 outline-none hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600",
|
|
2319
|
+
value: displayFormat,
|
|
2320
|
+
onChange: (e) => setCellFormat(selectedAddresses, { displayFormat: e.target.value }),
|
|
2321
|
+
disabled: readOnly,
|
|
2322
|
+
title: "Cell format",
|
|
2323
|
+
children: [
|
|
2324
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "auto", children: "Auto" }),
|
|
2325
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: "Text" }),
|
|
2326
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: "Number" }),
|
|
2327
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "currency", children: "Currency ($)" }),
|
|
2328
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "percentage", children: "Percentage (%)" }),
|
|
2329
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "date", children: "Date" }),
|
|
2330
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "datetime", children: "Date & Time" })
|
|
2331
|
+
]
|
|
2332
|
+
}
|
|
2333
|
+
),
|
|
2334
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2335
|
+
"button",
|
|
2336
|
+
{
|
|
2337
|
+
className: btnClass,
|
|
2338
|
+
onClick: () => setCellFormat(selectedAddresses, { decimals: Math.max(0, (decimals ?? 0) - 1) }),
|
|
2339
|
+
disabled: readOnly || (decimals ?? 0) <= 0,
|
|
2340
|
+
title: "Decrease decimal places",
|
|
2341
|
+
children: [
|
|
2342
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".0" }),
|
|
2343
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2190" })
|
|
2344
|
+
]
|
|
2345
|
+
}
|
|
2346
|
+
),
|
|
2347
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2348
|
+
"button",
|
|
2349
|
+
{
|
|
2350
|
+
className: btnClass,
|
|
2351
|
+
onClick: () => setCellFormat(selectedAddresses, { decimals: (decimals ?? 0) + 1 }),
|
|
2352
|
+
disabled: readOnly,
|
|
2353
|
+
title: "Increase decimal places",
|
|
2354
|
+
children: [
|
|
2355
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px]", children: ".00" }),
|
|
2356
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px]", children: "\u2192" })
|
|
2357
|
+
]
|
|
2358
|
+
}
|
|
2251
2359
|
)
|
|
2252
2360
|
] }),
|
|
2253
2361
|
/* @__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: [
|
|
@@ -2411,6 +2519,7 @@ function SpreadsheetRoot({
|
|
|
2411
2519
|
(address) => state.selection.activeCell === address,
|
|
2412
2520
|
[state.selection.activeCell]
|
|
2413
2521
|
);
|
|
2522
|
+
const isDraggingRef = react.useRef(false);
|
|
2414
2523
|
const ctx = react.useMemo(
|
|
2415
2524
|
() => ({
|
|
2416
2525
|
workbook: state.workbook,
|
|
@@ -2428,7 +2537,8 @@ function SpreadsheetRoot({
|
|
|
2428
2537
|
canRedo: state.redoStack.length > 0,
|
|
2429
2538
|
getColumnWidth,
|
|
2430
2539
|
isCellSelected,
|
|
2431
|
-
isCellActive
|
|
2540
|
+
isCellActive,
|
|
2541
|
+
_isDragging: isDraggingRef
|
|
2432
2542
|
}),
|
|
2433
2543
|
[state, activeSheet, columnCount, rowCount, defaultColumnWidth, rowHeight, readOnly, actions, getColumnWidth, isCellSelected, isCellActive]
|
|
2434
2544
|
);
|