@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.cjs
CHANGED
|
@@ -1610,6 +1610,58 @@ function PivotSkeleton({
|
|
|
1610
1610
|
setSortDirection("asc");
|
|
1611
1611
|
}
|
|
1612
1612
|
}, [sortTarget]);
|
|
1613
|
+
const [selectedCell, setSelectedCell] = (0, import_react8.useState)(null);
|
|
1614
|
+
const [selectionStart, setSelectionStart] = (0, import_react8.useState)(null);
|
|
1615
|
+
const [selectionEnd, setSelectionEnd] = (0, import_react8.useState)(null);
|
|
1616
|
+
const [isSelecting, setIsSelecting] = (0, import_react8.useState)(false);
|
|
1617
|
+
const [showCopyToast, setShowCopyToast] = (0, import_react8.useState)(false);
|
|
1618
|
+
const [copyToastMessage, setCopyToastMessage] = (0, import_react8.useState)("");
|
|
1619
|
+
const selectionBounds = (0, import_react8.useMemo)(() => {
|
|
1620
|
+
if (!selectionStart || !selectionEnd) return null;
|
|
1621
|
+
return {
|
|
1622
|
+
minRow: Math.min(selectionStart.row, selectionEnd.row),
|
|
1623
|
+
maxRow: Math.max(selectionStart.row, selectionEnd.row),
|
|
1624
|
+
minCol: Math.min(selectionStart.col, selectionEnd.col),
|
|
1625
|
+
maxCol: Math.max(selectionStart.col, selectionEnd.col)
|
|
1626
|
+
};
|
|
1627
|
+
}, [selectionStart, selectionEnd]);
|
|
1628
|
+
const handleCellMouseDown = (0, import_react8.useCallback)(
|
|
1629
|
+
(rowIndex, colIndex, event) => {
|
|
1630
|
+
event.preventDefault();
|
|
1631
|
+
if (event.shiftKey && selectedCell) {
|
|
1632
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1633
|
+
} else {
|
|
1634
|
+
setSelectedCell({ row: rowIndex, col: colIndex });
|
|
1635
|
+
setSelectionStart({ row: rowIndex, col: colIndex });
|
|
1636
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1637
|
+
setIsSelecting(true);
|
|
1638
|
+
}
|
|
1639
|
+
},
|
|
1640
|
+
[selectedCell]
|
|
1641
|
+
);
|
|
1642
|
+
const handleCellMouseEnter = (0, import_react8.useCallback)(
|
|
1643
|
+
(rowIndex, colIndex) => {
|
|
1644
|
+
if (isSelecting) {
|
|
1645
|
+
setSelectionEnd({ row: rowIndex, col: colIndex });
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
[isSelecting]
|
|
1649
|
+
);
|
|
1650
|
+
const isCellSelected = (0, import_react8.useCallback)(
|
|
1651
|
+
(rowIndex, colIndex) => {
|
|
1652
|
+
if (!selectionBounds) {
|
|
1653
|
+
return selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
|
|
1654
|
+
}
|
|
1655
|
+
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
1656
|
+
return rowIndex >= minRow && rowIndex <= maxRow && colIndex >= minCol && colIndex <= maxCol;
|
|
1657
|
+
},
|
|
1658
|
+
[selectionBounds, selectedCell]
|
|
1659
|
+
);
|
|
1660
|
+
(0, import_react8.useEffect)(() => {
|
|
1661
|
+
const handleMouseUp = () => setIsSelecting(false);
|
|
1662
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1663
|
+
return () => document.removeEventListener("mouseup", handleMouseUp);
|
|
1664
|
+
}, []);
|
|
1613
1665
|
const sortedRowIndices = (0, import_react8.useMemo)(() => {
|
|
1614
1666
|
if (!pivotResult) return [];
|
|
1615
1667
|
const indices = pivotResult.rowHeaders.map((_, i) => i);
|
|
@@ -1634,6 +1686,78 @@ function PivotSkeleton({
|
|
|
1634
1686
|
});
|
|
1635
1687
|
return indices;
|
|
1636
1688
|
}, [pivotResult, sortTarget, sortDirection]);
|
|
1689
|
+
const copySelectionToClipboard = (0, import_react8.useCallback)(() => {
|
|
1690
|
+
if (!selectionBounds || !pivotResult) return;
|
|
1691
|
+
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
1692
|
+
const lines = [];
|
|
1693
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
1694
|
+
const sortedIdx = sortedRowIndices[r];
|
|
1695
|
+
if (sortedIdx === void 0) continue;
|
|
1696
|
+
const rowValues = [];
|
|
1697
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
1698
|
+
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
1699
|
+
rowValues.push(cell?.formattedValue ?? "");
|
|
1700
|
+
}
|
|
1701
|
+
lines.push(rowValues.join(" "));
|
|
1702
|
+
}
|
|
1703
|
+
const text = lines.join("\n");
|
|
1704
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
1705
|
+
const cellCount = (maxRow - minRow + 1) * (maxCol - minCol + 1);
|
|
1706
|
+
setCopyToastMessage(`Copied ${cellCount} cell${cellCount > 1 ? "s" : ""}`);
|
|
1707
|
+
setShowCopyToast(true);
|
|
1708
|
+
setTimeout(() => setShowCopyToast(false), 2e3);
|
|
1709
|
+
}).catch((err) => {
|
|
1710
|
+
console.error("Copy failed:", err);
|
|
1711
|
+
});
|
|
1712
|
+
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
1713
|
+
(0, import_react8.useEffect)(() => {
|
|
1714
|
+
const handleKeydown = (event) => {
|
|
1715
|
+
if (!selectionBounds) return;
|
|
1716
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "c") {
|
|
1717
|
+
event.preventDefault();
|
|
1718
|
+
copySelectionToClipboard();
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
if (event.key === "Escape") {
|
|
1722
|
+
setSelectedCell(null);
|
|
1723
|
+
setSelectionStart(null);
|
|
1724
|
+
setSelectionEnd(null);
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
document.addEventListener("keydown", handleKeydown);
|
|
1728
|
+
return () => document.removeEventListener("keydown", handleKeydown);
|
|
1729
|
+
}, [selectionBounds, copySelectionToClipboard]);
|
|
1730
|
+
const selectionStats = (0, import_react8.useMemo)(() => {
|
|
1731
|
+
if (!selectionBounds || !pivotResult) return null;
|
|
1732
|
+
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
1733
|
+
const values = [];
|
|
1734
|
+
let count = 0;
|
|
1735
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
1736
|
+
const sortedIdx = sortedRowIndices[r];
|
|
1737
|
+
if (sortedIdx === void 0) continue;
|
|
1738
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
1739
|
+
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
1740
|
+
count++;
|
|
1741
|
+
if (cell?.value !== null && cell?.value !== void 0 && typeof cell.value === "number") {
|
|
1742
|
+
values.push(cell.value);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (count <= 1) return null;
|
|
1747
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
1748
|
+
const avg = values.length > 0 ? sum / values.length : 0;
|
|
1749
|
+
return {
|
|
1750
|
+
count,
|
|
1751
|
+
numericCount: values.length,
|
|
1752
|
+
sum,
|
|
1753
|
+
avg
|
|
1754
|
+
};
|
|
1755
|
+
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
1756
|
+
const formatStatValue = (0, import_react8.useCallback)((val) => {
|
|
1757
|
+
if (Math.abs(val) >= 1e6) return `${(val / 1e6).toFixed(2)}M`;
|
|
1758
|
+
if (Math.abs(val) >= 1e3) return `${(val / 1e3).toFixed(2)}K`;
|
|
1759
|
+
return val.toFixed(2);
|
|
1760
|
+
}, []);
|
|
1637
1761
|
const columnHeaderCells = (0, import_react8.useMemo)(() => {
|
|
1638
1762
|
if (!pivotResult || pivotResult.headers.length === 0) {
|
|
1639
1763
|
return [
|
|
@@ -1790,6 +1914,10 @@ function PivotSkeleton({
|
|
|
1790
1914
|
{
|
|
1791
1915
|
className: `vpg-pivot-skeleton vpg-font-${currentFontSize} ${draggingField ? "vpg-is-dragging" : ""}`,
|
|
1792
1916
|
children: [
|
|
1917
|
+
showCopyToast && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-toast", children: [
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
|
|
1919
|
+
copyToastMessage
|
|
1920
|
+
] }),
|
|
1793
1921
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-skeleton-header", children: [
|
|
1794
1922
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-skeleton-title", children: [
|
|
1795
1923
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -2091,14 +2219,19 @@ function PivotSkeleton({
|
|
|
2091
2219
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tbody", { children: [
|
|
2092
2220
|
sortedRowIndices.map((sortedIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "vpg-data-row", children: [
|
|
2093
2221
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { className: "vpg-row-header-cell", children: pivotResult.rowHeaders[sortedIdx].map((val, idx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-row-value", children: val }, idx)) }),
|
|
2094
|
-
pivotResult.data[sortedIdx].map((cell, colIdx) =>
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2222
|
+
pivotResult.data[sortedIdx].map((cell, colIdx) => {
|
|
2223
|
+
const displayRowIdx = sortedRowIndices.indexOf(sortedIdx);
|
|
2224
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2225
|
+
"td",
|
|
2226
|
+
{
|
|
2227
|
+
className: `vpg-data-cell ${isCellSelected(displayRowIdx, colIdx) ? "selected" : ""} ${cell.value === null ? "vpg-is-null" : ""}`,
|
|
2228
|
+
onMouseDown: (e) => handleCellMouseDown(displayRowIdx, colIdx, e),
|
|
2229
|
+
onMouseEnter: () => handleCellMouseEnter(displayRowIdx, colIdx),
|
|
2230
|
+
children: cell.formattedValue
|
|
2231
|
+
},
|
|
2232
|
+
colIdx
|
|
2233
|
+
);
|
|
2234
|
+
}),
|
|
2102
2235
|
pivotResult.rowTotals[sortedIdx] && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "vpg-data-cell vpg-total-cell", children: pivotResult.rowTotals[sortedIdx].formattedValue })
|
|
2103
2236
|
] }, sortedIdx)),
|
|
2104
2237
|
pivotResult.columnTotals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "vpg-totals-row", children: [
|
|
@@ -2108,12 +2241,32 @@ function PivotSkeleton({
|
|
|
2108
2241
|
] })
|
|
2109
2242
|
] })
|
|
2110
2243
|
] }) }),
|
|
2111
|
-
isConfigured && pivotResult && /* @__PURE__ */ (0, import_jsx_runtime4.
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2244
|
+
isConfigured && pivotResult && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-skeleton-footer", children: [
|
|
2245
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "vpg-footer-info", children: [
|
|
2246
|
+
pivotResult.rowHeaders.length,
|
|
2247
|
+
" rows \xD7 ",
|
|
2248
|
+
pivotResult.data[0]?.length || 0,
|
|
2249
|
+
" columns"
|
|
2250
|
+
] }),
|
|
2251
|
+
selectionStats && selectionStats.count > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-selection-stats", children: [
|
|
2252
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "vpg-stat", children: [
|
|
2253
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-label", children: "Count:" }),
|
|
2254
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-value", children: selectionStats.count })
|
|
2255
|
+
] }),
|
|
2256
|
+
selectionStats.numericCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2257
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-divider", children: "|" }),
|
|
2258
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "vpg-stat", children: [
|
|
2259
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-label", children: "Sum:" }),
|
|
2260
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-value", children: formatStatValue(selectionStats.sum) })
|
|
2261
|
+
] }),
|
|
2262
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-divider", children: "|" }),
|
|
2263
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "vpg-stat", children: [
|
|
2264
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-label", children: "Avg:" }),
|
|
2265
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-stat-value", children: formatStatValue(selectionStats.avg) })
|
|
2266
|
+
] })
|
|
2267
|
+
] })
|
|
2268
|
+
] })
|
|
2269
|
+
] })
|
|
2117
2270
|
] }),
|
|
2118
2271
|
showWatermark && canUsePivot && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `vpg-watermark ${isDemo ? "vpg-demo-mode" : ""}`, children: isDemo ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2119
2272
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-demo-badge", children: "DEMO" }),
|