@hrforte/react-pivottable 2.2.0 → 2.2.2
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/README.md +13 -1
- package/dist/PivotTableUI.js +163 -39
- package/dist/PivotTableUI.js.map +1 -1
- package/dist/TableRenderers.js +107 -24
- package/dist/TableRenderers.js.map +1 -1
- package/dist/__tests__/PivotTableUI-processing-test.js +5 -5
- 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 +178 -0
- package/dist/__tests__/TableRenderers-showNumericalOrder-test.js.map +1 -0
- package/dist/pivot-worker.js +1 -0
- package/dist/pivotCompute.js +1 -0
- package/dist/pivotCompute.js.map +1 -1
- package/dist/pivottable.css +41 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,10 +127,22 @@ but the main thread is used. Verify with `workerDebug`:
|
|
|
127
127
|
`[pivottable worker]` prefix. Logs emitted inside the worker thread are
|
|
128
128
|
forwarded to the page console as well. Can also be enabled globally with
|
|
129
129
|
`window.__PVT_WORKER_DEBUG__ = true`.
|
|
130
|
-
- `
|
|
130
|
+
- `showProcessing` (`bool`, default `false`): testing/debugging hook. When
|
|
131
131
|
`true` the loading overlay is forced on top of the table even if nothing is
|
|
132
132
|
being computed, so the `PivotLoadingOverlay` can be inspected without
|
|
133
133
|
generating a large dataset. The overlay shows only a spinner and a label;
|
|
134
134
|
during real computations it is turned off only after the worker has posted
|
|
135
135
|
its result event **and** PivotTableUI has rendered the new table (the
|
|
136
136
|
removal happens before the browser paints, so no empty frame is shown).
|
|
137
|
+
- `showNumericalOrder` (`bool`, default `false`): adds a "No." column in front
|
|
138
|
+
of the row attributes with the 1-based index of every data row (`1`, `2`,
|
|
139
|
+
`3`, …). The "No." header cell spans the header rows (row group labels,
|
|
140
|
+
column attribute rows, aggregator header rows), the row label columns keep
|
|
141
|
+
their `colSpan`/`rowSpan` alignment and the total rows extend their label
|
|
142
|
+
over the new column. When multiple aggregations are displayed as rows each
|
|
143
|
+
rendered row is numbered, so a record listing `Count` and `Sum` consumes two
|
|
144
|
+
numbers.
|
|
145
|
+
|
|
146
|
+
```jsx
|
|
147
|
+
<PivotTableUI data={rows} rows={['name']} showNumericalOrder />
|
|
148
|
+
```
|
package/dist/PivotTableUI.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
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:
|
|
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
|
-
|
|
1342
|
-
|
|
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
|
|
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
|
|
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;
|
|
@@ -1818,13 +1919,49 @@ function PivotTableUI(props) {
|
|
|
1818
1919
|
return setAxisFromDrag('rows', order);
|
|
1819
1920
|
}, 'pvtAxisContainer pvtVertList pvtRows');
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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 =
|
|
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(
|
|
@@ -1951,7 +2075,7 @@ PivotTableUI.propTypes = Object.assign({}, _PivotTable2.default.propTypes, {
|
|
|
1951
2075
|
workerThreshold: _propTypes2.default.number,
|
|
1952
2076
|
workerUrl: _propTypes2.default.string,
|
|
1953
2077
|
workerDebug: _propTypes2.default.bool,
|
|
1954
|
-
|
|
2078
|
+
showProcessing: _propTypes2.default.bool
|
|
1955
2079
|
});
|
|
1956
2080
|
|
|
1957
2081
|
PivotTableUI.defaultProps = Object.assign({}, _PivotTable2.default.defaultProps, {
|
|
@@ -1966,7 +2090,7 @@ PivotTableUI.defaultProps = Object.assign({}, _PivotTable2.default.defaultProps,
|
|
|
1966
2090
|
workerThreshold: _pivotCompute.DEFAULT_WORKER_THRESHOLD,
|
|
1967
2091
|
workerUrl: _PivotDataWorker.DEFAULT_WORKER_URL,
|
|
1968
2092
|
workerDebug: false,
|
|
1969
|
-
|
|
2093
|
+
showProcessing: false
|
|
1970
2094
|
});
|
|
1971
2095
|
|
|
1972
2096
|
exports.default = PivotTableUI;
|