@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.
@@ -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\u2026'
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
- if (versionedDataRef.current !== props.data) {
1297
- versionedDataRef.current = props.data;
1298
- dataVersionRef.current += 1;
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
- var signature = computeSignature + '#' + dataVersionRef.current;
1302
- if (precomputedRef.current && precomputedRef.current.signature === signature) {
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: signature, data: result };
1377
+ precomputedRef.current = { signature: currentSignature, data: result };
1320
1378
  setPrecomputed(precomputedRef.current);
1321
- setAttrValues(result.attrValues);
1322
- setMaterializedInput(result.materializedInput);
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
- var sendPropUpdate = function sendPropUpdate(command) {
1342
- props.onChange((0, _immutabilityHelper2.default)(props, command));
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 handleColSort(attr, sortDir) {
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 handleRowSort(attr, sortDir) {
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
- var showProcessing = (isProcessing || props.showProcessing) && !computeError;
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
- var staleView = showProcessing && !resultReady ? lastGoodViewRef.current : null;
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 = _react2.default.createElement(_PivotTable2.default, _extends({}, staleView.props, {
1847
- data: staleView.materializedInput,
1848
- precomputedData: staleView.precomputed ? staleView.precomputed.data : null,
1849
- colSorts: staleView.props.colSorts || {},
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 = _react2.default.createElement(_PivotTable2.default, _extends({}, props, {
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
- _react2.default.Fragment,
1877
- null,
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
- _react2.default.Fragment,
1912
- null,
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',