@smallwebco/tinypivot-react 1.0.27 → 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 CHANGED
@@ -464,9 +464,7 @@ function usePivotTable(data) {
464
464
  }
465
465
  }, [availableFields, requirePro]);
466
466
  const addCalculatedField = (0, import_react3.useCallback)((field) => {
467
- console.log("[usePivotTable] addCalculatedField called with:", field);
468
467
  setCalculatedFields((prev) => {
469
- console.log("[usePivotTable] Previous calculatedFields:", prev);
470
468
  const existing = prev.findIndex((f) => f.id === field.id);
471
469
  let updated;
472
470
  if (existing >= 0) {
@@ -474,7 +472,6 @@ function usePivotTable(data) {
474
472
  } else {
475
473
  updated = [...prev, field];
476
474
  }
477
- console.log("[usePivotTable] Updated calculatedFields:", updated);
478
475
  (0, import_tinypivot_core3.saveCalculatedFields)(updated);
479
476
  return updated;
480
477
  });
@@ -1044,15 +1041,11 @@ function CalculatedFieldModal({
1044
1041
  });
1045
1042
  }, []);
1046
1043
  const handleSave = (0, import_react6.useCallback)(() => {
1047
- console.log("[CalculatedFieldModal] handleSave called");
1048
- console.log("[CalculatedFieldModal] name:", name, "formula:", formula);
1049
- console.log("[CalculatedFieldModal] availableFields:", availableFields);
1050
1044
  if (!name.trim()) {
1051
1045
  setError("Name is required");
1052
1046
  return;
1053
1047
  }
1054
1048
  const validationResult = (0, import_tinypivot_core5.validateSimpleFormula)(formula, availableFields);
1055
- console.log("[CalculatedFieldModal] validationResult:", validationResult);
1056
1049
  if (validationResult) {
1057
1050
  setError(validationResult);
1058
1051
  return;
@@ -1064,7 +1057,6 @@ function CalculatedFieldModal({
1064
1057
  formatAs,
1065
1058
  decimals
1066
1059
  };
1067
- console.log("[CalculatedFieldModal] Calling onSave with:", field);
1068
1060
  onSave(field);
1069
1061
  onClose();
1070
1062
  }, [name, formula, formatAs, decimals, existingField, availableFields, onSave, onClose]);
@@ -1225,9 +1217,8 @@ function PivotConfig({
1225
1217
  [availableFields]
1226
1218
  );
1227
1219
  const calculatedFieldsAsStats = (0, import_react7.useMemo)(() => {
1228
- console.log("[PivotConfig] calculatedFields prop:", calculatedFields);
1229
1220
  if (!calculatedFields) return [];
1230
- const stats = calculatedFields.map((calc) => ({
1221
+ return calculatedFields.map((calc) => ({
1231
1222
  field: `calc:${calc.id}`,
1232
1223
  type: "number",
1233
1224
  uniqueCount: 0,
@@ -1237,17 +1228,11 @@ function PivotConfig({
1237
1228
  calcName: calc.name,
1238
1229
  calcFormula: calc.formula
1239
1230
  }));
1240
- console.log("[PivotConfig] calculatedFieldsAsStats:", stats);
1241
- return stats;
1242
1231
  }, [calculatedFields]);
1243
- const allAvailableFields = (0, import_react7.useMemo)(() => {
1244
- const all = [
1245
- ...availableFields.map((f) => ({ ...f, isCalculated: false })),
1246
- ...calculatedFieldsAsStats
1247
- ];
1248
- console.log("[PivotConfig] allAvailableFields:", all.length, "items, calc fields:", calculatedFieldsAsStats.length);
1249
- return all;
1250
- }, [availableFields, calculatedFieldsAsStats]);
1232
+ const allAvailableFields = (0, import_react7.useMemo)(() => [
1233
+ ...availableFields.map((f) => ({ ...f, isCalculated: false })),
1234
+ ...calculatedFieldsAsStats
1235
+ ], [availableFields, calculatedFieldsAsStats]);
1251
1236
  const assignedFields = (0, import_react7.useMemo)(() => {
1252
1237
  const rowSet = new Set(rowFields);
1253
1238
  const colSet = new Set(columnFields);
@@ -1329,14 +1314,9 @@ function PivotConfig({
1329
1314
  setShowCalcModal(true);
1330
1315
  }, []);
1331
1316
  const handleSaveCalcField = (0, import_react7.useCallback)((field) => {
1332
- console.log("[PivotConfig] handleSaveCalcField called with:", field);
1333
- console.log("[PivotConfig] editingCalcField:", editingCalcField);
1334
- console.log("[PivotConfig] onAddCalculatedField exists:", !!onAddCalculatedField);
1335
1317
  if (editingCalcField && onUpdateCalculatedField) {
1336
- console.log("[PivotConfig] Calling onUpdateCalculatedField");
1337
1318
  onUpdateCalculatedField(field);
1338
1319
  } else if (onAddCalculatedField) {
1339
- console.log("[PivotConfig] Calling onAddCalculatedField");
1340
1320
  onAddCalculatedField(field);
1341
1321
  }
1342
1322
  setShowCalcModal(false);
@@ -1588,6 +1568,7 @@ function PivotSkeleton({
1588
1568
  rowFields,
1589
1569
  columnFields,
1590
1570
  valueFields,
1571
+ calculatedFields,
1591
1572
  isConfigured,
1592
1573
  draggingField,
1593
1574
  pivotResult,
@@ -1605,6 +1586,17 @@ function PivotSkeleton({
1605
1586
  onReorderColumnFields
1606
1587
  }) {
1607
1588
  const { showWatermark, canUsePivot, isDemo } = useLicense();
1589
+ const getValueFieldDisplayName = (0, import_react8.useCallback)((field) => {
1590
+ if (field.startsWith("calc:")) {
1591
+ const calcId = field.replace("calc:", "");
1592
+ const calcField = calculatedFields?.find((c) => c.id === calcId);
1593
+ return calcField?.name || field;
1594
+ }
1595
+ return field;
1596
+ }, [calculatedFields]);
1597
+ const isCalculatedField = (0, import_react8.useCallback)((field) => {
1598
+ return field.startsWith("calc:");
1599
+ }, []);
1608
1600
  const [dragOverArea, setDragOverArea] = (0, import_react8.useState)(null);
1609
1601
  const [reorderDragSource, setReorderDragSource] = (0, import_react8.useState)(null);
1610
1602
  const [reorderDropTarget, setReorderDropTarget] = (0, import_react8.useState)(null);
@@ -1618,6 +1610,58 @@ function PivotSkeleton({
1618
1610
  setSortDirection("asc");
1619
1611
  }
1620
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
+ }, []);
1621
1665
  const sortedRowIndices = (0, import_react8.useMemo)(() => {
1622
1666
  if (!pivotResult) return [];
1623
1667
  const indices = pivotResult.rowHeaders.map((_, i) => i);
@@ -1642,11 +1686,83 @@ function PivotSkeleton({
1642
1686
  });
1643
1687
  return indices;
1644
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
+ }, []);
1645
1761
  const columnHeaderCells = (0, import_react8.useMemo)(() => {
1646
1762
  if (!pivotResult || pivotResult.headers.length === 0) {
1647
1763
  return [
1648
1764
  valueFields.map((vf) => ({
1649
- label: `${vf.field} (${(0, import_tinypivot_core7.getAggregationLabel)(vf.aggregation)})`,
1765
+ label: `${getValueFieldDisplayName(vf.field)} (${(0, import_tinypivot_core7.getAggregationLabel)(vf.aggregation)})`,
1650
1766
  colspan: 1
1651
1767
  }))
1652
1768
  ];
@@ -1798,6 +1914,10 @@ function PivotSkeleton({
1798
1914
  {
1799
1915
  className: `vpg-pivot-skeleton vpg-font-${currentFontSize} ${draggingField ? "vpg-is-dragging" : ""}`,
1800
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
+ ] }),
1801
1921
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-skeleton-header", children: [
1802
1922
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-skeleton-title", children: [
1803
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)(
@@ -2014,10 +2134,10 @@ function PivotSkeleton({
2014
2134
  valueFields.map((vf) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2015
2135
  "div",
2016
2136
  {
2017
- className: "vpg-mini-chip vpg-value-chip",
2137
+ className: `vpg-mini-chip vpg-value-chip${isCalculatedField(vf.field) ? " vpg-calc-chip" : ""}`,
2018
2138
  children: [
2019
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-agg-symbol", children: (0, import_tinypivot_core7.getAggregationSymbol)(vf.aggregation) }),
2020
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-mini-name", children: vf.field }),
2139
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-agg-symbol", children: isCalculatedField(vf.field) ? "\u0192" : (0, import_tinypivot_core7.getAggregationSymbol)(vf.aggregation) }),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-mini-name", children: getValueFieldDisplayName(vf.field) }),
2021
2141
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2022
2142
  "button",
2023
2143
  {
@@ -2099,14 +2219,19 @@ function PivotSkeleton({
2099
2219
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tbody", { children: [
2100
2220
  sortedRowIndices.map((sortedIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "vpg-data-row", children: [
2101
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)) }),
2102
- pivotResult.data[sortedIdx].map((cell, colIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2103
- "td",
2104
- {
2105
- className: `vpg-data-cell ${cell.value === null ? "vpg-is-null" : ""}`,
2106
- children: cell.formattedValue
2107
- },
2108
- colIdx
2109
- )),
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
+ }),
2110
2235
  pivotResult.rowTotals[sortedIdx] && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "vpg-data-cell vpg-total-cell", children: pivotResult.rowTotals[sortedIdx].formattedValue })
2111
2236
  ] }, sortedIdx)),
2112
2237
  pivotResult.columnTotals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "vpg-totals-row", children: [
@@ -2116,12 +2241,32 @@ function PivotSkeleton({
2116
2241
  ] })
2117
2242
  ] })
2118
2243
  ] }) }),
2119
- isConfigured && pivotResult && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "vpg-skeleton-footer", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
2120
- pivotResult.rowHeaders.length,
2121
- " rows \xD7 ",
2122
- pivotResult.data[0]?.length || 0,
2123
- " columns"
2124
- ] }) })
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
+ ] })
2125
2270
  ] }),
2126
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: [
2127
2272
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-demo-badge", children: "DEMO" }),
@@ -3054,6 +3199,7 @@ function DataGrid({
3054
3199
  rowFields: pivotRowFields,
3055
3200
  columnFields: pivotColumnFields,
3056
3201
  valueFields: pivotValueFields,
3202
+ calculatedFields: pivotCalculatedFields,
3057
3203
  isConfigured: pivotIsConfigured,
3058
3204
  draggingField,
3059
3205
  pivotResult,