@smallwebco/tinypivot-react 1.0.28 → 1.0.29
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 +167 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +175 -22
- package/dist/index.js.map +1 -1
- package/dist/style.css +109 -16
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/DataGrid.tsx
|
|
2
|
-
import { useState as useState9, useMemo as useMemo9, useCallback as useCallback9, useEffect as
|
|
2
|
+
import { useState as useState9, useMemo as useMemo9, useCallback as useCallback9, useEffect as useEffect6, useRef as useRef2 } from "react";
|
|
3
3
|
import { createPortal as createPortal2 } from "react-dom";
|
|
4
4
|
|
|
5
5
|
// src/hooks/useExcelGrid.ts
|
|
@@ -1546,7 +1546,7 @@ function PivotConfig({
|
|
|
1546
1546
|
}
|
|
1547
1547
|
|
|
1548
1548
|
// src/components/PivotSkeleton.tsx
|
|
1549
|
-
import { useState as useState8, useMemo as useMemo8, useCallback as useCallback8 } from "react";
|
|
1549
|
+
import { useState as useState8, useMemo as useMemo8, useCallback as useCallback8, useEffect as useEffect5 } from "react";
|
|
1550
1550
|
import { getAggregationLabel as getAggregationLabel2, getAggregationSymbol as getAggregationSymbol2 } from "@smallwebco/tinypivot-core";
|
|
1551
1551
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1552
1552
|
function PivotSkeleton({
|
|
@@ -1595,6 +1595,58 @@ function PivotSkeleton({
|
|
|
1595
1595
|
setSortDirection("asc");
|
|
1596
1596
|
}
|
|
1597
1597
|
}, [sortTarget]);
|
|
1598
|
+
const [selectedCell, setSelectedCell] = useState8(null);
|
|
1599
|
+
const [selectionStart, setSelectionStart] = useState8(null);
|
|
1600
|
+
const [selectionEnd, setSelectionEnd] = useState8(null);
|
|
1601
|
+
const [isSelecting, setIsSelecting] = useState8(false);
|
|
1602
|
+
const [showCopyToast, setShowCopyToast] = useState8(false);
|
|
1603
|
+
const [copyToastMessage, setCopyToastMessage] = useState8("");
|
|
1604
|
+
const selectionBounds = useMemo8(() => {
|
|
1605
|
+
if (!selectionStart || !selectionEnd) return null;
|
|
1606
|
+
return {
|
|
1607
|
+
minRow: Math.min(selectionStart.row, selectionEnd.row),
|
|
1608
|
+
maxRow: Math.max(selectionStart.row, selectionEnd.row),
|
|
1609
|
+
minCol: Math.min(selectionStart.col, selectionEnd.col),
|
|
1610
|
+
maxCol: Math.max(selectionStart.col, selectionEnd.col)
|
|
1611
|
+
};
|
|
1612
|
+
}, [selectionStart, selectionEnd]);
|
|
1613
|
+
const handleCellMouseDown = useCallback8(
|
|
1614
|
+
(rowIndex, colIndex, event) => {
|
|
1615
|
+
event.preventDefault();
|
|
1616
|
+
if (event.shiftKey && selectedCell) {
|
|
1617
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1618
|
+
} else {
|
|
1619
|
+
setSelectedCell({ row: rowIndex, col: colIndex });
|
|
1620
|
+
setSelectionStart({ row: rowIndex, col: colIndex });
|
|
1621
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1622
|
+
setIsSelecting(true);
|
|
1623
|
+
}
|
|
1624
|
+
},
|
|
1625
|
+
[selectedCell]
|
|
1626
|
+
);
|
|
1627
|
+
const handleCellMouseEnter = useCallback8(
|
|
1628
|
+
(rowIndex, colIndex) => {
|
|
1629
|
+
if (isSelecting) {
|
|
1630
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1631
|
+
}
|
|
1632
|
+
},
|
|
1633
|
+
[isSelecting]
|
|
1634
|
+
);
|
|
1635
|
+
const isCellSelected = useCallback8(
|
|
1636
|
+
(rowIndex, colIndex) => {
|
|
1637
|
+
if (!selectionBounds) {
|
|
1638
|
+
return selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
|
|
1639
|
+
}
|
|
1640
|
+
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
1641
|
+
return rowIndex >= minRow && rowIndex <= maxRow && colIndex >= minCol && colIndex <= maxCol;
|
|
1642
|
+
},
|
|
1643
|
+
[selectionBounds, selectedCell]
|
|
1644
|
+
);
|
|
1645
|
+
useEffect5(() => {
|
|
1646
|
+
const handleMouseUp = () => setIsSelecting(false);
|
|
1647
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1648
|
+
return () => document.removeEventListener("mouseup", handleMouseUp);
|
|
1649
|
+
}, []);
|
|
1598
1650
|
const sortedRowIndices = useMemo8(() => {
|
|
1599
1651
|
if (!pivotResult) return [];
|
|
1600
1652
|
const indices = pivotResult.rowHeaders.map((_, i) => i);
|
|
@@ -1619,6 +1671,78 @@ function PivotSkeleton({
|
|
|
1619
1671
|
});
|
|
1620
1672
|
return indices;
|
|
1621
1673
|
}, [pivotResult, sortTarget, sortDirection]);
|
|
1674
|
+
const copySelectionToClipboard = useCallback8(() => {
|
|
1675
|
+
if (!selectionBounds || !pivotResult) return;
|
|
1676
|
+
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
1677
|
+
const lines = [];
|
|
1678
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
1679
|
+
const sortedIdx = sortedRowIndices[r];
|
|
1680
|
+
if (sortedIdx === void 0) continue;
|
|
1681
|
+
const rowValues = [];
|
|
1682
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
1683
|
+
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
1684
|
+
rowValues.push(cell?.formattedValue ?? "");
|
|
1685
|
+
}
|
|
1686
|
+
lines.push(rowValues.join(" "));
|
|
1687
|
+
}
|
|
1688
|
+
const text = lines.join("\n");
|
|
1689
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
1690
|
+
const cellCount = (maxRow - minRow + 1) * (maxCol - minCol + 1);
|
|
1691
|
+
setCopyToastMessage(`Copied ${cellCount} cell${cellCount > 1 ? "s" : ""}`);
|
|
1692
|
+
setShowCopyToast(true);
|
|
1693
|
+
setTimeout(() => setShowCopyToast(false), 2e3);
|
|
1694
|
+
}).catch((err) => {
|
|
1695
|
+
console.error("Copy failed:", err);
|
|
1696
|
+
});
|
|
1697
|
+
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
1698
|
+
useEffect5(() => {
|
|
1699
|
+
const handleKeydown = (event) => {
|
|
1700
|
+
if (!selectionBounds) return;
|
|
1701
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "c") {
|
|
1702
|
+
event.preventDefault();
|
|
1703
|
+
copySelectionToClipboard();
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (event.key === "Escape") {
|
|
1707
|
+
setSelectedCell(null);
|
|
1708
|
+
setSelectionStart(null);
|
|
1709
|
+
setSelectionEnd(null);
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
document.addEventListener("keydown", handleKeydown);
|
|
1713
|
+
return () => document.removeEventListener("keydown", handleKeydown);
|
|
1714
|
+
}, [selectionBounds, copySelectionToClipboard]);
|
|
1715
|
+
const selectionStats = useMemo8(() => {
|
|
1716
|
+
if (!selectionBounds || !pivotResult) return null;
|
|
1717
|
+
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
1718
|
+
const values = [];
|
|
1719
|
+
let count = 0;
|
|
1720
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
1721
|
+
const sortedIdx = sortedRowIndices[r];
|
|
1722
|
+
if (sortedIdx === void 0) continue;
|
|
1723
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
1724
|
+
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
1725
|
+
count++;
|
|
1726
|
+
if (cell?.value !== null && cell?.value !== void 0 && typeof cell.value === "number") {
|
|
1727
|
+
values.push(cell.value);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (count <= 1) return null;
|
|
1732
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
1733
|
+
const avg = values.length > 0 ? sum / values.length : 0;
|
|
1734
|
+
return {
|
|
1735
|
+
count,
|
|
1736
|
+
numericCount: values.length,
|
|
1737
|
+
sum,
|
|
1738
|
+
avg
|
|
1739
|
+
};
|
|
1740
|
+
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
1741
|
+
const formatStatValue = useCallback8((val) => {
|
|
1742
|
+
if (Math.abs(val) >= 1e6) return `${(val / 1e6).toFixed(2)}M`;
|
|
1743
|
+
if (Math.abs(val) >= 1e3) return `${(val / 1e3).toFixed(2)}K`;
|
|
1744
|
+
return val.toFixed(2);
|
|
1745
|
+
}, []);
|
|
1622
1746
|
const columnHeaderCells = useMemo8(() => {
|
|
1623
1747
|
if (!pivotResult || pivotResult.headers.length === 0) {
|
|
1624
1748
|
return [
|
|
@@ -1775,6 +1899,10 @@ function PivotSkeleton({
|
|
|
1775
1899
|
{
|
|
1776
1900
|
className: `vpg-pivot-skeleton vpg-font-${currentFontSize} ${draggingField ? "vpg-is-dragging" : ""}`,
|
|
1777
1901
|
children: [
|
|
1902
|
+
showCopyToast && /* @__PURE__ */ jsxs4("div", { className: "vpg-toast", children: [
|
|
1903
|
+
/* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
|
|
1904
|
+
copyToastMessage
|
|
1905
|
+
] }),
|
|
1778
1906
|
/* @__PURE__ */ jsxs4("div", { className: "vpg-skeleton-header", children: [
|
|
1779
1907
|
/* @__PURE__ */ jsxs4("div", { className: "vpg-skeleton-title", children: [
|
|
1780
1908
|
/* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
|
|
@@ -2076,14 +2204,19 @@ function PivotSkeleton({
|
|
|
2076
2204
|
/* @__PURE__ */ jsxs4("tbody", { children: [
|
|
2077
2205
|
sortedRowIndices.map((sortedIdx) => /* @__PURE__ */ jsxs4("tr", { className: "vpg-data-row", children: [
|
|
2078
2206
|
/* @__PURE__ */ jsx4("th", { className: "vpg-row-header-cell", children: pivotResult.rowHeaders[sortedIdx].map((val, idx) => /* @__PURE__ */ jsx4("span", { className: "vpg-row-value", children: val }, idx)) }),
|
|
2079
|
-
pivotResult.data[sortedIdx].map((cell, colIdx) =>
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2207
|
+
pivotResult.data[sortedIdx].map((cell, colIdx) => {
|
|
2208
|
+
const displayRowIdx = sortedRowIndices.indexOf(sortedIdx);
|
|
2209
|
+
return /* @__PURE__ */ jsx4(
|
|
2210
|
+
"td",
|
|
2211
|
+
{
|
|
2212
|
+
className: `vpg-data-cell ${isCellSelected(displayRowIdx, colIdx) ? "selected" : ""} ${cell.value === null ? "vpg-is-null" : ""}`,
|
|
2213
|
+
onMouseDown: (e) => handleCellMouseDown(displayRowIdx, colIdx, e),
|
|
2214
|
+
onMouseEnter: () => handleCellMouseEnter(displayRowIdx, colIdx),
|
|
2215
|
+
children: cell.formattedValue
|
|
2216
|
+
},
|
|
2217
|
+
colIdx
|
|
2218
|
+
);
|
|
2219
|
+
}),
|
|
2087
2220
|
pivotResult.rowTotals[sortedIdx] && /* @__PURE__ */ jsx4("td", { className: "vpg-data-cell vpg-total-cell", children: pivotResult.rowTotals[sortedIdx].formattedValue })
|
|
2088
2221
|
] }, sortedIdx)),
|
|
2089
2222
|
pivotResult.columnTotals.length > 0 && /* @__PURE__ */ jsxs4("tr", { className: "vpg-totals-row", children: [
|
|
@@ -2093,12 +2226,32 @@ function PivotSkeleton({
|
|
|
2093
2226
|
] })
|
|
2094
2227
|
] })
|
|
2095
2228
|
] }) }),
|
|
2096
|
-
isConfigured && pivotResult && /* @__PURE__ */
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2229
|
+
isConfigured && pivotResult && /* @__PURE__ */ jsxs4("div", { className: "vpg-skeleton-footer", children: [
|
|
2230
|
+
/* @__PURE__ */ jsxs4("span", { className: "vpg-footer-info", children: [
|
|
2231
|
+
pivotResult.rowHeaders.length,
|
|
2232
|
+
" rows \xD7 ",
|
|
2233
|
+
pivotResult.data[0]?.length || 0,
|
|
2234
|
+
" columns"
|
|
2235
|
+
] }),
|
|
2236
|
+
selectionStats && selectionStats.count > 1 && /* @__PURE__ */ jsxs4("div", { className: "vpg-selection-stats", children: [
|
|
2237
|
+
/* @__PURE__ */ jsxs4("span", { className: "vpg-stat", children: [
|
|
2238
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-label", children: "Count:" }),
|
|
2239
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-value", children: selectionStats.count })
|
|
2240
|
+
] }),
|
|
2241
|
+
selectionStats.numericCount > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2242
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-divider", children: "|" }),
|
|
2243
|
+
/* @__PURE__ */ jsxs4("span", { className: "vpg-stat", children: [
|
|
2244
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-label", children: "Sum:" }),
|
|
2245
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-value", children: formatStatValue(selectionStats.sum) })
|
|
2246
|
+
] }),
|
|
2247
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-divider", children: "|" }),
|
|
2248
|
+
/* @__PURE__ */ jsxs4("span", { className: "vpg-stat", children: [
|
|
2249
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-label", children: "Avg:" }),
|
|
2250
|
+
/* @__PURE__ */ jsx4("span", { className: "vpg-stat-value", children: formatStatValue(selectionStats.avg) })
|
|
2251
|
+
] })
|
|
2252
|
+
] })
|
|
2253
|
+
] })
|
|
2254
|
+
] })
|
|
2102
2255
|
] }),
|
|
2103
2256
|
showWatermark && canUsePivot && /* @__PURE__ */ jsx4("div", { className: `vpg-watermark ${isDemo ? "vpg-demo-mode" : ""}`, children: isDemo ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2104
2257
|
/* @__PURE__ */ jsx4("span", { className: "vpg-demo-badge", children: "DEMO" }),
|
|
@@ -2263,7 +2416,7 @@ function DataGrid({
|
|
|
2263
2416
|
const end = start + pageSize;
|
|
2264
2417
|
return searchFilteredData.slice(start, end);
|
|
2265
2418
|
}, [enablePagination, searchFilteredData, currentPage, pageSize]);
|
|
2266
|
-
|
|
2419
|
+
useEffect6(() => {
|
|
2267
2420
|
setCurrentPage(1);
|
|
2268
2421
|
}, [columnFilters, globalSearchTerm]);
|
|
2269
2422
|
const selectionBounds = useMemo9(() => {
|
|
@@ -2301,7 +2454,7 @@ function DataGrid({
|
|
|
2301
2454
|
const avg = sum / values.length;
|
|
2302
2455
|
return { count, sum, avg, numericCount: values.length };
|
|
2303
2456
|
}, [selectionBounds, rows, columnKeys]);
|
|
2304
|
-
|
|
2457
|
+
useEffect6(() => {
|
|
2305
2458
|
if (data.length === 0) return;
|
|
2306
2459
|
const widths = {};
|
|
2307
2460
|
const sampleSize = Math.min(100, data.length);
|
|
@@ -2332,7 +2485,7 @@ function DataGrid({
|
|
|
2332
2485
|
},
|
|
2333
2486
|
[enableColumnResize, columnWidths]
|
|
2334
2487
|
);
|
|
2335
|
-
|
|
2488
|
+
useEffect6(() => {
|
|
2336
2489
|
if (!resizingColumnId) return;
|
|
2337
2490
|
const handleResizeMove = (event) => {
|
|
2338
2491
|
const diff = event.clientX - resizeStartX;
|
|
@@ -2362,7 +2515,7 @@ function DataGrid({
|
|
|
2362
2515
|
},
|
|
2363
2516
|
[enableVerticalResize, gridHeight]
|
|
2364
2517
|
);
|
|
2365
|
-
|
|
2518
|
+
useEffect6(() => {
|
|
2366
2519
|
if (!isResizingVertically) return;
|
|
2367
2520
|
const handleVerticalResizeMove = (event) => {
|
|
2368
2521
|
const diff = event.clientY - verticalResizeStartY;
|
|
@@ -2480,12 +2633,12 @@ function DataGrid({
|
|
|
2480
2633
|
},
|
|
2481
2634
|
[isSelecting]
|
|
2482
2635
|
);
|
|
2483
|
-
|
|
2636
|
+
useEffect6(() => {
|
|
2484
2637
|
const handleMouseUp = () => setIsSelecting(false);
|
|
2485
2638
|
document.addEventListener("mouseup", handleMouseUp);
|
|
2486
2639
|
return () => document.removeEventListener("mouseup", handleMouseUp);
|
|
2487
2640
|
}, []);
|
|
2488
|
-
|
|
2641
|
+
useEffect6(() => {
|
|
2489
2642
|
const handleKeydown = (event) => {
|
|
2490
2643
|
if ((event.ctrlKey || event.metaKey) && event.key === "c" && selectionBounds) {
|
|
2491
2644
|
event.preventDefault();
|