@hrforte/react-pivottable 2.2.1 → 2.2.3

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,11 @@ 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
+
57
62
  /* eslint-disable react/prop-types */
58
63
  // eslint can't see inherited propTypes!
59
64
 
@@ -1127,7 +1132,61 @@ function PivotLoadingOverlay() {
1127
1132
  _react2.default.createElement(
1128
1133
  'div',
1129
1134
  { className: 'pvtLoadingText' },
1130
- 'Processing\u2026'
1135
+ _react2.default.createElement(
1136
+ 'span',
1137
+ null,
1138
+ 'P'
1139
+ ),
1140
+ _react2.default.createElement(
1141
+ 'span',
1142
+ null,
1143
+ 'r'
1144
+ ),
1145
+ _react2.default.createElement(
1146
+ 'span',
1147
+ null,
1148
+ 'o'
1149
+ ),
1150
+ _react2.default.createElement(
1151
+ 'span',
1152
+ null,
1153
+ 'c'
1154
+ ),
1155
+ _react2.default.createElement(
1156
+ 'span',
1157
+ null,
1158
+ 'e'
1159
+ ),
1160
+ _react2.default.createElement(
1161
+ 'span',
1162
+ null,
1163
+ 's'
1164
+ ),
1165
+ _react2.default.createElement(
1166
+ 'span',
1167
+ null,
1168
+ 's'
1169
+ ),
1170
+ _react2.default.createElement(
1171
+ 'span',
1172
+ null,
1173
+ 'i'
1174
+ ),
1175
+ _react2.default.createElement(
1176
+ 'span',
1177
+ null,
1178
+ 'n'
1179
+ ),
1180
+ _react2.default.createElement(
1181
+ 'span',
1182
+ null,
1183
+ 'g'
1184
+ ),
1185
+ _react2.default.createElement(
1186
+ 'span',
1187
+ null,
1188
+ '\u2026'
1189
+ )
1131
1190
  )
1132
1191
  );
1133
1192
  }
@@ -1212,6 +1271,14 @@ function PivotTableUI(props) {
1212
1271
  instanceIdRef.current = 'pvt-' + pivotWorkerInstanceCounter++;
1213
1272
  }
1214
1273
 
1274
+ // Latest props, readable from identity-stable event handlers. Layout effects
1275
+ // run synchronously after every commit, so this is always up to date before
1276
+ // the next user event is dispatched (unlike a render-time assignment).
1277
+ var propsRef = (0, _react.useRef)(props);
1278
+ (0, _react.useLayoutEffect)(function () {
1279
+ propsRef.current = props;
1280
+ });
1281
+
1215
1282
  var workerEligible = (0, _react.useMemo)(function () {
1216
1283
  return (0, _pivotCompute.canComputeInWorker)(props, {
1217
1284
  enabled: props.enableWorker,
@@ -1223,6 +1290,25 @@ function PivotTableUI(props) {
1223
1290
  return (0, _pivotCompute.createComputeSignature)(props);
1224
1291
  }, [props.rows, props.cols, props.aggregatorName, props.aggregations, props.vals, props.valueFilter, props.sorters, props.rowOrder, props.colOrder, props.rowSorts, props.colSorts]);
1225
1292
 
1293
+ // Version the dataset during render instead of inside the compute effect.
1294
+ // The effect runs *after* the render, so bumping the version there left one
1295
+ // render in which the new props were combined with the previous precomputed
1296
+ // result: a full (and wasted) re-render of the whole table on every change.
1297
+ if (versionedDataRef.current !== props.data) {
1298
+ versionedDataRef.current = props.data;
1299
+ dataVersionRef.current += 1;
1300
+ }
1301
+
1302
+ // Signature of the pivot result the current props + data require, and whether
1303
+ // the precomputed result we hold actually belongs to it.
1304
+ var currentSignature = computeSignature + '#' + dataVersionRef.current;
1305
+ var precomputedMatches = Boolean(precomputedRef.current && precomputedRef.current.signature === currentSignature);
1306
+
1307
+ // Worker-eligible props whose result has not arrived yet. While this is true
1308
+ // the previous, consistent table stays on screen (never a mix of new props
1309
+ // with old results), so rendering and worker computation never overlap.
1310
+ var pendingCompute = workerEligible && !precomputedMatches;
1311
+
1226
1312
  var materializeInput = function materializeInput(nextData) {
1227
1313
  if (lastDataRef.current === nextData) {
1228
1314
  return;
@@ -1262,10 +1348,12 @@ function PivotTableUI(props) {
1262
1348
  // match the currently applied precomputed result. While a new computation
1263
1349
  // runs, this snapshot is rendered underneath the loading overlay so the
1264
1350
  // user still sees the previous table (dimmed) instead of a blank area.
1351
+ // `pendingCompute` keeps an inconsistent pair (new props, old result) out of
1352
+ // the snapshot: it may only ever hold a view that was actually rendered.
1265
1353
  var lastGoodViewRef = (0, _react.useRef)(null);
1266
1354
 
1267
1355
  (0, _react.useEffect)(function () {
1268
- if (!isProcessing && !computeError) {
1356
+ if (!isProcessing && !computeError && !pendingCompute) {
1269
1357
  lastGoodViewRef.current = {
1270
1358
  props: props,
1271
1359
  materializedInput: materializedInput,
@@ -1293,13 +1381,21 @@ function PivotTableUI(props) {
1293
1381
  // result is never applied to the new configuration or dataset.
1294
1382
  activeRequestRef.current += 1;
1295
1383
 
1296
- if (versionedDataRef.current !== props.data) {
1297
- versionedDataRef.current = props.data;
1298
- dataVersionRef.current += 1;
1384
+ // Classic main-thread path (small dataset, worker disabled or unavailable):
1385
+ // the renderer builds its own PivotData synchronously, so precomputing the
1386
+ // same aggregation here would only duplicate the work and keep the loading
1387
+ // overlay on screen for a result nothing is going to read. `attrValues` and
1388
+ // `materializedInput` are handled by the effect above.
1389
+ if (!workerEligible) {
1390
+ setIsProcessing(false);
1391
+ setResultReady(false);
1392
+ setComputeError(null);
1393
+ return undefined;
1299
1394
  }
1300
1395
 
1301
- var signature = computeSignature + '#' + dataVersionRef.current;
1302
- if (precomputedRef.current && precomputedRef.current.signature === signature) {
1396
+ // The result for the current props + data is already available (the data
1397
+ // version was bumped during render, see above).
1398
+ if (precomputedMatches) {
1303
1399
  setIsProcessing(false);
1304
1400
  return undefined;
1305
1401
  }
@@ -1316,7 +1412,7 @@ function PivotTableUI(props) {
1316
1412
  if (unmountedRef.current || requestId !== activeRequestRef.current) {
1317
1413
  return;
1318
1414
  }
1319
- precomputedRef.current = { signature: signature, data: result };
1415
+ precomputedRef.current = { signature: currentSignature, data: result };
1320
1416
  setPrecomputed(precomputedRef.current);
1321
1417
  setAttrValues(result.attrValues);
1322
1418
  setMaterializedInput(result.materializedInput);
@@ -1338,9 +1434,14 @@ function PivotTableUI(props) {
1338
1434
  };
1339
1435
  }, [props.data, computeSignature]);
1340
1436
 
1341
- var sendPropUpdate = function sendPropUpdate(command) {
1342
- props.onChange((0, _immutabilityHelper2.default)(props, command));
1343
- };
1437
+ // Event handlers are referentially stable (they read the latest props from
1438
+ // `propsRef`): a new handler identity on every render would defeat
1439
+ // PivotTable's PureComponent check and re-render the entire table - which is
1440
+ // what makes drag and drop feel frozen on large datasets.
1441
+ var sendPropUpdate = (0, _react.useCallback)(function (command) {
1442
+ var currentProps = propsRef.current;
1443
+ currentProps.onChange((0, _immutabilityHelper2.default)(currentProps, command));
1444
+ }, []);
1344
1445
 
1345
1446
  var propUpdater = function propUpdater(key) {
1346
1447
  return function (value) {
@@ -1526,23 +1627,23 @@ function PivotTableUI(props) {
1526
1627
  });
1527
1628
  };
1528
1629
 
1529
- var handleColSort = function handleColSort(attr, sortDir) {
1630
+ var handleColSort = (0, _react.useCallback)(function (attr, sortDir) {
1530
1631
  // Only allow sorting 1 column at a time - clear all other column sorts
1531
1632
  var colSorts = {};
1532
1633
  if (sortDir !== null) {
1533
1634
  colSorts[attr] = sortDir;
1534
1635
  }
1535
1636
  sendPropUpdate({ colSorts: { $set: colSorts } });
1536
- };
1637
+ }, [sendPropUpdate]);
1537
1638
 
1538
- var handleRowSort = function handleRowSort(attr, sortDir) {
1639
+ var handleRowSort = (0, _react.useCallback)(function (attr, sortDir) {
1539
1640
  // Only allow sorting 1 row at a time - clear all other row sorts
1540
1641
  var rowSorts = {};
1541
1642
  if (sortDir !== null) {
1542
1643
  rowSorts[attr] = sortDir;
1543
1644
  }
1544
1645
  sendPropUpdate({ rowSorts: { $set: rowSorts } });
1545
- };
1646
+ }, [sendPropUpdate]);
1546
1647
 
1547
1648
  var isOpen = function isOpen(dropdown) {
1548
1649
  return openDropdown === dropdown;
@@ -1820,11 +1921,47 @@ function PivotTableUI(props) {
1820
1921
 
1821
1922
  // `showProcessing` is a testing hook: when true it forces the loading
1822
1923
  // overlay on top of the table regardless of whether a worker computation
1823
- // is actually running.
1824
- var showProcessing = (isProcessing || props.showProcessing) && !computeError;
1924
+ // is actually running. `pendingCompute` means the props/data on screen
1925
+ // already changed while the worker result is still on its way, so the
1926
+ // previous table has to stay visible (with the overlay) until it arrives.
1927
+ var showProcessing = (isProcessing || pendingCompute || props.showProcessing) && !computeError;
1825
1928
  // While recomputing, re-render the last valid table (old props matched to
1826
1929
  // their own precomputed result) so the overlay sits on top of real content.
1827
- var staleView = showProcessing && !resultReady ? lastGoodViewRef.current : null;
1930
+ // `pendingCompute` - and not the leftover `resultReady` flag of the previous
1931
+ // computation - is what decides this. Relying on `resultReady` let the render
1932
+ // that received new props fall through to the live branch and draw the whole
1933
+ // table with the *previous* result, only to throw it away again on the next
1934
+ // render: two extra full re-renders per change on every drag event.
1935
+ // The element is memoized on purpose: re-creating it on every parent render
1936
+ // would make React re-render every cell of the (potentially huge) table on
1937
+ // each drag event and keystroke, which is what made drag & drop feel frozen.
1938
+ var staleView = pendingCompute ? lastGoodViewRef.current : null;
1939
+ var staleViewElement = (0, _react.useMemo)(function () {
1940
+ if (!staleView) {
1941
+ return null;
1942
+ }
1943
+ return _react2.default.createElement(_PivotTable2.default, _extends({}, staleView.props, {
1944
+ data: staleView.materializedInput,
1945
+ precomputedData: staleView.precomputed ? staleView.precomputed.data : null,
1946
+ colSorts: staleView.props.colSorts || EMPTY_SORTS,
1947
+ rowSorts: staleView.props.rowSorts || EMPTY_SORTS,
1948
+ onColSort: handleColSort,
1949
+ onRowSort: handleRowSort
1950
+ }));
1951
+ }, [staleView, handleColSort, handleRowSort]);
1952
+
1953
+ // Stable identities for the sort maps: an inline `{}` fallback would be a new
1954
+ // object on every render and invalidate PivotTable's PureComponent check.
1955
+ var colSorts = props.colSorts || EMPTY_SORTS;
1956
+ var rowSorts = props.rowSorts || EMPTY_SORTS;
1957
+ var liveViewElement = _react2.default.createElement(_PivotTable2.default, _extends({}, props, {
1958
+ data: materializedInput,
1959
+ precomputedData: workerEligible && precomputed ? precomputed.data : props.precomputedData || null,
1960
+ colSorts: colSorts,
1961
+ rowSorts: rowSorts,
1962
+ onColSort: handleColSort,
1963
+ onRowSort: handleRowSort
1964
+ }));
1828
1965
 
1829
1966
  var outputContent = void 0;
1830
1967
  if (computeError) {
@@ -1843,26 +1980,13 @@ function PivotTableUI(props) {
1843
1980
  )
1844
1981
  );
1845
1982
  } 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.
1983
+ outputContent = staleViewElement;
1984
+ } else if (pendingCompute) {
1985
+ // First computation, or a change without any previous table to keep
1986
+ // visible: nothing consistent exists yet, so leave the area empty.
1856
1987
  outputContent = _react2.default.createElement('div', { className: 'pvtOutputPlaceholder' });
1857
1988
  } 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
- }));
1989
+ outputContent = liveViewElement;
1866
1990
  }
1867
1991
 
1868
1992
  var outputCell = _react2.default.createElement(