@hrforte/react-pivottable 2.2.1 → 3.0.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/PivotDataWorker.js +219 -13
- package/dist/PivotDataWorker.js.map +1 -1
- package/dist/PivotTable.js +2 -1
- package/dist/PivotTable.js.map +1 -1
- package/dist/PivotTableUI.js +145 -48
- package/dist/PivotTableUI.js.map +1 -1
- package/dist/TableRenderers.js +629 -118
- package/dist/TableRenderers.js.map +1 -1
- package/dist/Utilities.js +104 -179
- package/dist/Utilities.js.map +1 -1
- package/dist/__tests__/PivotDataWorker-test.js +437 -7
- package/dist/__tests__/PivotDataWorker-test.js.map +1 -1
- package/dist/__tests__/PivotTableUI-processing-test.js +42 -3
- package/dist/__tests__/PivotTableUI-processing-test.js.map +1 -1
- package/dist/__tests__/PivotTableUI-rerender-test.js +314 -0
- package/dist/__tests__/PivotTableUI-rerender-test.js.map +1 -0
- package/dist/__tests__/TableRenderers-showNumericalOrder-test.js +449 -0
- package/dist/__tests__/TableRenderers-showNumericalOrder-test.js.map +1 -1
- package/dist/__tests__/pivotCompute-test.js +137 -0
- package/dist/__tests__/pivotCompute-test.js.map +1 -1
- package/dist/pivot-worker.js +510 -201
- package/dist/pivotCompute.js +238 -12
- package/dist/pivotCompute.js.map +1 -1
- package/dist/pivotWorker.js +165 -9
- package/dist/pivotWorker.js.map +1 -1
- package/dist/pivottable.css +277 -235
- package/package.json +1 -1
package/dist/PivotTableUI.js
CHANGED
|
@@ -54,6 +54,12 @@ var AGG_KEY_RADIX = 36;
|
|
|
54
54
|
var AGG_KEY_OFFSET = 2;
|
|
55
55
|
var AGG_KEY_LENGTH = 7;
|
|
56
56
|
|
|
57
|
+
// Shared, identity-stable fallback for the `colSorts`/`rowSorts` props. An
|
|
58
|
+
// inline `{}` literal would be a new object on every render, which makes the
|
|
59
|
+
// rendered table re-render even when nothing about the pivot changed.
|
|
60
|
+
var EMPTY_SORTS = {};
|
|
61
|
+
var EMPTY_ATTR_VALUES = {};
|
|
62
|
+
|
|
57
63
|
/* eslint-disable react/prop-types */
|
|
58
64
|
// eslint can't see inherited propTypes!
|
|
59
65
|
|
|
@@ -243,6 +249,7 @@ function DraggableAttribute(props) {
|
|
|
243
249
|
}
|
|
244
250
|
|
|
245
251
|
DraggableAttribute.defaultProps = {
|
|
252
|
+
attrValues: EMPTY_ATTR_VALUES,
|
|
246
253
|
valueFilter: {}
|
|
247
254
|
};
|
|
248
255
|
|
|
@@ -1127,7 +1134,7 @@ function PivotLoadingOverlay() {
|
|
|
1127
1134
|
_react2.default.createElement(
|
|
1128
1135
|
'div',
|
|
1129
1136
|
{ className: 'pvtLoadingText' },
|
|
1130
|
-
'Processing
|
|
1137
|
+
'Processing'
|
|
1131
1138
|
)
|
|
1132
1139
|
);
|
|
1133
1140
|
}
|
|
@@ -1212,6 +1219,14 @@ function PivotTableUI(props) {
|
|
|
1212
1219
|
instanceIdRef.current = 'pvt-' + pivotWorkerInstanceCounter++;
|
|
1213
1220
|
}
|
|
1214
1221
|
|
|
1222
|
+
// Latest props, readable from identity-stable event handlers. Layout effects
|
|
1223
|
+
// run synchronously after every commit, so this is always up to date before
|
|
1224
|
+
// the next user event is dispatched (unlike a render-time assignment).
|
|
1225
|
+
var propsRef = (0, _react.useRef)(props);
|
|
1226
|
+
(0, _react.useLayoutEffect)(function () {
|
|
1227
|
+
propsRef.current = props;
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1215
1230
|
var workerEligible = (0, _react.useMemo)(function () {
|
|
1216
1231
|
return (0, _pivotCompute.canComputeInWorker)(props, {
|
|
1217
1232
|
enabled: props.enableWorker,
|
|
@@ -1221,7 +1236,26 @@ function PivotTableUI(props) {
|
|
|
1221
1236
|
|
|
1222
1237
|
var computeSignature = (0, _react.useMemo)(function () {
|
|
1223
1238
|
return (0, _pivotCompute.createComputeSignature)(props);
|
|
1224
|
-
}, [props.rows, props.cols, props.aggregatorName, props.aggregations, props.vals, props.valueFilter, props.sorters, props.rowOrder, props.colOrder, props.rowSorts, props.colSorts]);
|
|
1239
|
+
}, [props.rows, props.cols, props.aggregatorName, props.aggregations, props.vals, props.valueFilter, props.sorters, props.rowOrder, props.colOrder, props.rowSorts, props.colSorts, props.virtualizeRows, props.rendererName, props.tableOptions && props.tableOptions.aggregationDisplayMode]);
|
|
1240
|
+
|
|
1241
|
+
// Version the dataset during render instead of inside the compute effect.
|
|
1242
|
+
// The effect runs *after* the render, so bumping the version there left one
|
|
1243
|
+
// render in which the new props were combined with the previous precomputed
|
|
1244
|
+
// result: a full (and wasted) re-render of the whole table on every change.
|
|
1245
|
+
if (versionedDataRef.current !== props.data) {
|
|
1246
|
+
versionedDataRef.current = props.data;
|
|
1247
|
+
dataVersionRef.current += 1;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Signature of the pivot result the current props + data require, and whether
|
|
1251
|
+
// the precomputed result we hold actually belongs to it.
|
|
1252
|
+
var currentSignature = computeSignature + '#' + dataVersionRef.current;
|
|
1253
|
+
var precomputedMatches = Boolean(precomputedRef.current && precomputedRef.current.signature === currentSignature);
|
|
1254
|
+
|
|
1255
|
+
// Worker-eligible props whose result has not arrived yet. While this is true
|
|
1256
|
+
// the previous, consistent table stays on screen (never a mix of new props
|
|
1257
|
+
// with old results), so rendering and worker computation never overlap.
|
|
1258
|
+
var pendingCompute = workerEligible && !precomputedMatches;
|
|
1225
1259
|
|
|
1226
1260
|
var materializeInput = function materializeInput(nextData) {
|
|
1227
1261
|
if (lastDataRef.current === nextData) {
|
|
@@ -1247,11 +1281,20 @@ function PivotTableUI(props) {
|
|
|
1247
1281
|
}, [props.data, workerEligible]);
|
|
1248
1282
|
|
|
1249
1283
|
(0, _react.useEffect)(function () {
|
|
1284
|
+
(0, _PivotDataWorker.retainPivotWorker)(instanceIdRef.current);
|
|
1250
1285
|
return function () {
|
|
1251
1286
|
unmountedRef.current = true;
|
|
1287
|
+
(0, _PivotDataWorker.releasePivotWorker)(instanceIdRef.current);
|
|
1252
1288
|
};
|
|
1253
1289
|
}, []);
|
|
1254
1290
|
|
|
1291
|
+
(0, _react.useEffect)(function () {
|
|
1292
|
+
var viewportKey = workerEligible && precomputed && precomputed.data ? precomputed.data.viewportKey : null;
|
|
1293
|
+
return function () {
|
|
1294
|
+
return (0, _PivotDataWorker.releasePivotInWorker)(viewportKey);
|
|
1295
|
+
};
|
|
1296
|
+
}, [precomputed, workerEligible]);
|
|
1297
|
+
|
|
1255
1298
|
// Forward the `workerDebug` prop to the worker client so the whole
|
|
1256
1299
|
// worker pipeline can be traced from the browser console.
|
|
1257
1300
|
(0, _react.useEffect)(function () {
|
|
@@ -1262,10 +1305,12 @@ function PivotTableUI(props) {
|
|
|
1262
1305
|
// match the currently applied precomputed result. While a new computation
|
|
1263
1306
|
// runs, this snapshot is rendered underneath the loading overlay so the
|
|
1264
1307
|
// user still sees the previous table (dimmed) instead of a blank area.
|
|
1308
|
+
// `pendingCompute` keeps an inconsistent pair (new props, old result) out of
|
|
1309
|
+
// the snapshot: it may only ever hold a view that was actually rendered.
|
|
1265
1310
|
var lastGoodViewRef = (0, _react.useRef)(null);
|
|
1266
1311
|
|
|
1267
1312
|
(0, _react.useEffect)(function () {
|
|
1268
|
-
if (!isProcessing && !computeError) {
|
|
1313
|
+
if (!isProcessing && !computeError && !pendingCompute) {
|
|
1269
1314
|
lastGoodViewRef.current = {
|
|
1270
1315
|
props: props,
|
|
1271
1316
|
materializedInput: materializedInput,
|
|
@@ -1293,13 +1338,25 @@ function PivotTableUI(props) {
|
|
|
1293
1338
|
// result is never applied to the new configuration or dataset.
|
|
1294
1339
|
activeRequestRef.current += 1;
|
|
1295
1340
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1341
|
+
// Classic main-thread path (small dataset, worker disabled or unavailable):
|
|
1342
|
+
// the renderer builds its own PivotData synchronously, so precomputing the
|
|
1343
|
+
// same aggregation here would only duplicate the work and keep the loading
|
|
1344
|
+
// overlay on screen for a result nothing is going to read. `attrValues` and
|
|
1345
|
+
// `materializedInput` are handled by the effect above.
|
|
1346
|
+
if (!workerEligible) {
|
|
1347
|
+
if (precomputedRef.current) {
|
|
1348
|
+
precomputedRef.current = null;
|
|
1349
|
+
setPrecomputed(null);
|
|
1350
|
+
}
|
|
1351
|
+
setIsProcessing(false);
|
|
1352
|
+
setResultReady(false);
|
|
1353
|
+
setComputeError(null);
|
|
1354
|
+
return undefined;
|
|
1299
1355
|
}
|
|
1300
1356
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1357
|
+
// The result for the current props + data is already available (the data
|
|
1358
|
+
// version was bumped during render, see above).
|
|
1359
|
+
if (precomputedMatches) {
|
|
1303
1360
|
setIsProcessing(false);
|
|
1304
1361
|
return undefined;
|
|
1305
1362
|
}
|
|
@@ -1312,14 +1369,21 @@ function PivotTableUI(props) {
|
|
|
1312
1369
|
var timer = setTimeout(function () {
|
|
1313
1370
|
var requestId = activeRequestRef.current;
|
|
1314
1371
|
var dataKey = instanceIdRef.current + '-data-' + dataVersionRef.current;
|
|
1315
|
-
(0, _PivotDataWorker.computePivotWithFallback)(props.data, dataKey, (0, _pivotCompute.createWorkerConfig)(props)).then(function (result) {
|
|
1372
|
+
(0, _PivotDataWorker.computePivotWithFallback)(props.data, dataKey, (0, _pivotCompute.createWorkerConfig)(props), instanceIdRef.current).then(function (result) {
|
|
1316
1373
|
if (unmountedRef.current || requestId !== activeRequestRef.current) {
|
|
1374
|
+
(0, _PivotDataWorker.releasePivotInWorker)(result && result.viewportKey);
|
|
1317
1375
|
return;
|
|
1318
1376
|
}
|
|
1319
|
-
precomputedRef.current = { signature:
|
|
1377
|
+
precomputedRef.current = { signature: currentSignature, data: result };
|
|
1320
1378
|
setPrecomputed(precomputedRef.current);
|
|
1321
|
-
|
|
1322
|
-
|
|
1379
|
+
if (result.attrValues) {
|
|
1380
|
+
setAttrValues(result.attrValues);
|
|
1381
|
+
}
|
|
1382
|
+
(0, _PivotDataWorker.releaseDatasetInWorker)(dataKey);
|
|
1383
|
+
// The worker received this exact array when the request was staged.
|
|
1384
|
+
// Reusing it avoids structured-cloning the full dataset back to the
|
|
1385
|
+
// main thread with every result.
|
|
1386
|
+
setMaterializedInput(props.data);
|
|
1323
1387
|
setComputeError(null);
|
|
1324
1388
|
// Keep the overlay up for one more render: `resultReady` makes the
|
|
1325
1389
|
// fresh table render underneath, and the layout effect above turns
|
|
@@ -1336,11 +1400,16 @@ function PivotTableUI(props) {
|
|
|
1336
1400
|
return function () {
|
|
1337
1401
|
clearTimeout(timer);
|
|
1338
1402
|
};
|
|
1339
|
-
}, [props.data, computeSignature]);
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1403
|
+
}, [props.data, computeSignature, workerEligible]);
|
|
1404
|
+
|
|
1405
|
+
// Event handlers are referentially stable (they read the latest props from
|
|
1406
|
+
// `propsRef`): a new handler identity on every render would defeat
|
|
1407
|
+
// PivotTable's PureComponent check and re-render the entire table - which is
|
|
1408
|
+
// what makes drag and drop feel frozen on large datasets.
|
|
1409
|
+
var sendPropUpdate = (0, _react.useCallback)(function (command) {
|
|
1410
|
+
var currentProps = propsRef.current;
|
|
1411
|
+
currentProps.onChange((0, _immutabilityHelper2.default)(currentProps, command));
|
|
1412
|
+
}, []);
|
|
1344
1413
|
|
|
1345
1414
|
var propUpdater = function propUpdater(key) {
|
|
1346
1415
|
return function (value) {
|
|
@@ -1526,23 +1595,23 @@ function PivotTableUI(props) {
|
|
|
1526
1595
|
});
|
|
1527
1596
|
};
|
|
1528
1597
|
|
|
1529
|
-
var handleColSort = function
|
|
1598
|
+
var handleColSort = (0, _react.useCallback)(function (attr, sortDir) {
|
|
1530
1599
|
// Only allow sorting 1 column at a time - clear all other column sorts
|
|
1531
1600
|
var colSorts = {};
|
|
1532
1601
|
if (sortDir !== null) {
|
|
1533
1602
|
colSorts[attr] = sortDir;
|
|
1534
1603
|
}
|
|
1535
1604
|
sendPropUpdate({ colSorts: { $set: colSorts } });
|
|
1536
|
-
};
|
|
1605
|
+
}, [sendPropUpdate]);
|
|
1537
1606
|
|
|
1538
|
-
var handleRowSort = function
|
|
1607
|
+
var handleRowSort = (0, _react.useCallback)(function (attr, sortDir) {
|
|
1539
1608
|
// Only allow sorting 1 row at a time - clear all other row sorts
|
|
1540
1609
|
var rowSorts = {};
|
|
1541
1610
|
if (sortDir !== null) {
|
|
1542
1611
|
rowSorts[attr] = sortDir;
|
|
1543
1612
|
}
|
|
1544
1613
|
sendPropUpdate({ rowSorts: { $set: rowSorts } });
|
|
1545
|
-
};
|
|
1614
|
+
}, [sendPropUpdate]);
|
|
1546
1615
|
|
|
1547
1616
|
var isOpen = function isOpen(dropdown) {
|
|
1548
1617
|
return openDropdown === dropdown;
|
|
@@ -1597,9 +1666,9 @@ function PivotTableUI(props) {
|
|
|
1597
1666
|
return _react2.default.createElement(DraggableAttribute, {
|
|
1598
1667
|
name: x,
|
|
1599
1668
|
key: x,
|
|
1600
|
-
attrValues: attrValues[x],
|
|
1669
|
+
attrValues: attrValues[x] || EMPTY_ATTR_VALUES,
|
|
1601
1670
|
valueFilter: props.valueFilter[x] || {},
|
|
1602
|
-
sorter: (0, _Utilities.getSort)(props.sorters, x, attrValues[x]),
|
|
1671
|
+
sorter: (0, _Utilities.getSort)(props.sorters, x, attrValues[x] || EMPTY_ATTR_VALUES),
|
|
1603
1672
|
menuLimit: props.menuLimit,
|
|
1604
1673
|
setValuesInFilter: setValuesInFilter,
|
|
1605
1674
|
addValuesToFilter: addValuesToFilter,
|
|
@@ -1820,11 +1889,47 @@ function PivotTableUI(props) {
|
|
|
1820
1889
|
|
|
1821
1890
|
// `showProcessing` is a testing hook: when true it forces the loading
|
|
1822
1891
|
// overlay on top of the table regardless of whether a worker computation
|
|
1823
|
-
// is actually running.
|
|
1824
|
-
|
|
1892
|
+
// is actually running. `pendingCompute` means the props/data on screen
|
|
1893
|
+
// already changed while the worker result is still on its way, so the
|
|
1894
|
+
// previous table has to stay visible (with the overlay) until it arrives.
|
|
1895
|
+
var showProcessing = (isProcessing || pendingCompute || props.showProcessing) && !computeError;
|
|
1825
1896
|
// While recomputing, re-render the last valid table (old props matched to
|
|
1826
1897
|
// their own precomputed result) so the overlay sits on top of real content.
|
|
1827
|
-
|
|
1898
|
+
// `pendingCompute` - and not the leftover `resultReady` flag of the previous
|
|
1899
|
+
// computation - is what decides this. Relying on `resultReady` let the render
|
|
1900
|
+
// that received new props fall through to the live branch and draw the whole
|
|
1901
|
+
// table with the *previous* result, only to throw it away again on the next
|
|
1902
|
+
// render: two extra full re-renders per change on every drag event.
|
|
1903
|
+
// The element is memoized on purpose: re-creating it on every parent render
|
|
1904
|
+
// would make React re-render every cell of the (potentially huge) table on
|
|
1905
|
+
// each drag event and keystroke, which is what made drag & drop feel frozen.
|
|
1906
|
+
var staleView = pendingCompute ? lastGoodViewRef.current : null;
|
|
1907
|
+
var staleViewElement = (0, _react.useMemo)(function () {
|
|
1908
|
+
if (!staleView) {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
return _react2.default.createElement(_PivotTable2.default, _extends({}, staleView.props, {
|
|
1912
|
+
data: staleView.materializedInput,
|
|
1913
|
+
precomputedData: staleView.precomputed ? staleView.precomputed.data : null,
|
|
1914
|
+
colSorts: staleView.props.colSorts || EMPTY_SORTS,
|
|
1915
|
+
rowSorts: staleView.props.rowSorts || EMPTY_SORTS,
|
|
1916
|
+
onColSort: handleColSort,
|
|
1917
|
+
onRowSort: handleRowSort
|
|
1918
|
+
}));
|
|
1919
|
+
}, [staleView, handleColSort, handleRowSort]);
|
|
1920
|
+
|
|
1921
|
+
// Stable identities for the sort maps: an inline `{}` fallback would be a new
|
|
1922
|
+
// object on every render and invalidate PivotTable's PureComponent check.
|
|
1923
|
+
var colSorts = props.colSorts || EMPTY_SORTS;
|
|
1924
|
+
var rowSorts = props.rowSorts || EMPTY_SORTS;
|
|
1925
|
+
var liveViewElement = _react2.default.createElement(_PivotTable2.default, _extends({}, props, {
|
|
1926
|
+
data: materializedInput,
|
|
1927
|
+
precomputedData: workerEligible && precomputed ? precomputed.data : props.precomputedData || null,
|
|
1928
|
+
colSorts: colSorts,
|
|
1929
|
+
rowSorts: rowSorts,
|
|
1930
|
+
onColSort: handleColSort,
|
|
1931
|
+
onRowSort: handleRowSort
|
|
1932
|
+
}));
|
|
1828
1933
|
|
|
1829
1934
|
var outputContent = void 0;
|
|
1830
1935
|
if (computeError) {
|
|
@@ -1843,28 +1948,18 @@ function PivotTableUI(props) {
|
|
|
1843
1948
|
)
|
|
1844
1949
|
);
|
|
1845
1950
|
} else if (staleView) {
|
|
1846
|
-
outputContent =
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
rowSorts: staleView.props.rowSorts || {},
|
|
1851
|
-
onColSort: handleColSort,
|
|
1852
|
-
onRowSort: handleRowSort
|
|
1853
|
-
}));
|
|
1854
|
-
} else if (showProcessing && !resultReady) {
|
|
1855
|
-
// First computation: no previous table to keep visible yet.
|
|
1951
|
+
outputContent = staleViewElement;
|
|
1952
|
+
} else if (pendingCompute) {
|
|
1953
|
+
// First computation, or a change without any previous table to keep
|
|
1954
|
+
// visible: nothing consistent exists yet, so leave the area empty.
|
|
1856
1955
|
outputContent = _react2.default.createElement('div', { className: 'pvtOutputPlaceholder' });
|
|
1857
1956
|
} else {
|
|
1858
|
-
outputContent =
|
|
1859
|
-
data: materializedInput,
|
|
1860
|
-
precomputedData: workerEligible && precomputed ? precomputed.data : props.precomputedData || null,
|
|
1861
|
-
colSorts: props.colSorts || {},
|
|
1862
|
-
rowSorts: props.rowSorts || {},
|
|
1863
|
-
onColSort: handleColSort,
|
|
1864
|
-
onRowSort: handleRowSort
|
|
1865
|
-
}));
|
|
1957
|
+
outputContent = liveViewElement;
|
|
1866
1958
|
}
|
|
1867
1959
|
|
|
1960
|
+
// Keep the processing layer scoped to the output cell. Rendering it next to
|
|
1961
|
+
// the table made its absolute inset resolve against a much larger ancestor,
|
|
1962
|
+
// which intercepted pointer events across the surrounding application UI.
|
|
1868
1963
|
var outputCell = _react2.default.createElement(
|
|
1869
1964
|
'td',
|
|
1870
1965
|
{ className: 'pvtOutput' },
|
|
@@ -1873,8 +1968,10 @@ function PivotTableUI(props) {
|
|
|
1873
1968
|
|
|
1874
1969
|
if (horizUnused) {
|
|
1875
1970
|
return _react2.default.createElement(
|
|
1876
|
-
|
|
1877
|
-
|
|
1971
|
+
'div',
|
|
1972
|
+
{
|
|
1973
|
+
style: { borderRadius: 5, width: 'max-content', height: 'max-content' }
|
|
1974
|
+
},
|
|
1878
1975
|
showProcessing ? _react2.default.createElement(PivotLoadingOverlay, null) : null,
|
|
1879
1976
|
_react2.default.createElement(
|
|
1880
1977
|
'table',
|
|
@@ -1908,8 +2005,8 @@ function PivotTableUI(props) {
|
|
|
1908
2005
|
}
|
|
1909
2006
|
|
|
1910
2007
|
return _react2.default.createElement(
|
|
1911
|
-
|
|
1912
|
-
|
|
2008
|
+
'div',
|
|
2009
|
+
{ style: { borderRadius: 5, width: 'max-content', height: 'max-content' } },
|
|
1913
2010
|
showProcessing ? _react2.default.createElement(PivotLoadingOverlay, null) : null,
|
|
1914
2011
|
_react2.default.createElement(
|
|
1915
2012
|
'table',
|