@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.
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.getPivotTableProps = undefined;
6
7
 
7
8
  var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8
9
 
@@ -20,6 +21,10 @@ var _propTypes2 = _interopRequireDefault(_propTypes);
20
21
 
21
22
  var _Utilities = require('./Utilities');
22
23
 
24
+ var _pivotCompute = require('./pivotCompute');
25
+
26
+ var _PivotDataWorker = require('./PivotDataWorker');
27
+
23
28
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
29
 
25
30
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@@ -28,7 +33,59 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
28
33
 
29
34
  function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
30
35
 
31
- function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
36
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /* eslint-disable no-inline-comments */
37
+
38
+
39
+ var pivotTablePropsByElement = new WeakMap();
40
+
41
+ var getPivotTableProps = exports.getPivotTableProps = function getPivotTableProps(element) {
42
+ return pivotTablePropsByElement.get(element);
43
+ };
44
+
45
+ var VIRTUAL_ROW_HEIGHT = 32;
46
+ var VIRTUAL_ROW_OVERSCAN = 8;
47
+ var VIRTUAL_ROW_BLOCK = 32;
48
+ var VIRTUAL_COLUMN_WIDTH = 100;
49
+ var VIRTUAL_COLUMN_OVERSCAN = 4;
50
+ var VIRTUAL_COLUMN_BLOCK = 16;
51
+ var VIRTUAL_LOADING_PAINT_MS = 16;
52
+ var VIRTUAL_SCROLL_DEBOUNCE_MS = 500;
53
+ var INITIAL_ROW_WINDOW_END = 50;
54
+ var INITIAL_COLUMN_WINDOW_END = 32;
55
+ var FLAT_KEY_SEPARATOR = String.fromCharCode(0);
56
+
57
+ var _getViewportKeyForProps = function _getViewportKeyForProps(props) {
58
+ var precomputed = props.precomputedData;
59
+ return precomputed && typeof precomputed.viewportKey === 'number' && !(0, _pivotCompute.sortersHaveFunctions)(props.sorters) ? precomputed.viewportKey : null;
60
+ };
61
+
62
+ function PivotLoadingContent() {
63
+ return _react2.default.createElement(
64
+ _react2.default.Fragment,
65
+ null,
66
+ _react2.default.createElement('div', { className: 'pvtLoadingSpinner', 'aria-hidden': 'true' }),
67
+ _react2.default.createElement(
68
+ 'div',
69
+ { className: 'pvtLoadingText' },
70
+ 'Processing'
71
+ )
72
+ );
73
+ }
74
+
75
+ var getScrollParent = function getScrollParent(element) {
76
+ var parent = element.parentElement;
77
+ while (parent && parent !== document.body) {
78
+ var _window$getComputedSt = window.getComputedStyle(parent),
79
+ overflowX = _window$getComputedSt.overflowX,
80
+ overflowY = _window$getComputedSt.overflowY;
81
+
82
+ if (overflowX === 'auto' || overflowX === 'scroll' || overflowY === 'auto' || overflowY === 'scroll') {
83
+ return parent;
84
+ }
85
+ parent = parent.parentElement;
86
+ }
87
+ return window;
88
+ };
32
89
 
33
90
  // helper function for setting row/col-span in pivotTableRenderer
34
91
  var spanSize = function spanSize(arr, i, j) {
@@ -64,24 +121,31 @@ var spanSize = function spanSize(arr, i, j) {
64
121
  return len;
65
122
  };
66
123
 
67
- // ==> Calculate the positions for GroupSegments <==
68
- function buildHeaderGroupSegments(rowAttrs, rowGroupConfig) {
69
- var attrToGroup = {};
124
+ // ==> Build a lookup: name -> group label <==
125
+ // `match` can contain row attribute names, the synthetic 'Values' column
126
+ // (aggregations displayed as rows) and aggregation names/labels
127
+ // (aggregations displayed as columns).
128
+ function buildGroupLookup(rowGroupConfig) {
129
+ var lookup = {};
70
130
  (rowGroupConfig || []).forEach(function (_ref) {
71
131
  var label = _ref.label,
72
132
  match = _ref.match;
73
133
 
74
134
  match.forEach(function (name) {
75
- attrToGroup[name] = label;
135
+ lookup[name] = label;
76
136
  });
77
137
  });
138
+ return lookup;
139
+ }
140
+
141
+ // ==> Calculate the positions for GroupSegments <==
142
+ function buildHeaderGroupSegments(attrs, rowGroupConfig) {
143
+ var lookup = buildGroupLookup(rowGroupConfig);
78
144
 
79
145
  // Mỗi attribute luôn là 1 segment riêng — không gộp colSpan nữa,
80
146
  // để label nhóm (vd "AAA") hiện lặp lại trên từng cột con.
81
- return rowAttrs.map(function (name) {
82
- // eslint-disable-next-line no-undefined
83
- var group = attrToGroup[name] !== undefined ? attrToGroup[name] : null;
84
- return { group: group, span: 1 };
147
+ return attrs.map(function (name) {
148
+ return { group: lookup[name] || null, span: 1 };
85
149
  });
86
150
  }
87
151
 
@@ -342,18 +406,308 @@ function makeRenderer() {
342
406
  var TableRenderer = function (_React$PureComponent) {
343
407
  _inherits(TableRenderer, _React$PureComponent);
344
408
 
345
- function TableRenderer() {
409
+ function TableRenderer(props) {
346
410
  _classCallCheck(this, TableRenderer);
347
411
 
348
- return _possibleConstructorReturn(this, (TableRenderer.__proto__ || Object.getPrototypeOf(TableRenderer)).apply(this, arguments));
412
+ var _this = _possibleConstructorReturn(this, (TableRenderer.__proto__ || Object.getPrototypeOf(TableRenderer)).call(this, props));
413
+
414
+ _this.state = {
415
+ start: 0,
416
+ end: INITIAL_ROW_WINDOW_END,
417
+ rows: null,
418
+ columnStart: 0,
419
+ columnEnd: INITIAL_COLUMN_WINDOW_END,
420
+ columnTotals: null,
421
+ viewportKey: null,
422
+ sourceViewportKey: _getViewportKeyForProps(props),
423
+ scrollPending: false
424
+ };
425
+ _this.bodyRef = _react2.default.createRef();
426
+ _this.scrollParent = null;
427
+ _this.overlayBounds = null;
428
+ _this.frameId = null;
429
+ _this.scrollTimer = null;
430
+ _this.scrollPending = false;
431
+ _this.rowCount = 0;
432
+ _this.columnCount = 0;
433
+ _this.leadingColumnCount = 0;
434
+ _this.leadingColumnWidth = 0;
435
+ _this.pivotProps = null;
436
+ _this.pivotData = null;
437
+ _this.requestedWindow = null;
438
+ _this.viewportTimer = null;
439
+ _this.unmounted = false;
440
+ _this.scheduleUpdate = _this.scheduleUpdate.bind(_this);
441
+ _this.handleScroll = _this.handleScroll.bind(_this);
442
+ return _this;
349
443
  }
350
444
 
351
445
  _createClass(TableRenderer, [{
446
+ key: 'componentDidMount',
447
+ value: function componentDidMount() {
448
+ this.attachScroll();
449
+ this.updateWindow();
450
+ }
451
+ }, {
452
+ key: 'componentDidUpdate',
453
+ value: function componentDidUpdate(prevProps) {
454
+ if (prevProps === this.props) {
455
+ return;
456
+ }
457
+
458
+ var previousViewportKey = this.getViewportKeyForProps(prevProps);
459
+ var nextViewportKey = this.getViewportKey();
460
+ if (previousViewportKey !== nextViewportKey) {
461
+ this.invalidateViewportRequest();
462
+ if (this.scrollTimer !== null) {
463
+ clearTimeout(this.scrollTimer);
464
+ this.scrollTimer = null;
465
+ }
466
+ this.scrollPending = false;
467
+ }
468
+
469
+ this.attachScroll();
470
+ this.scheduleUpdate();
471
+ }
472
+ }, {
473
+ key: 'componentWillUnmount',
474
+ value: function componentWillUnmount() {
475
+ this.unmounted = true;
476
+ if (this.viewportTimer !== null) {
477
+ clearTimeout(this.viewportTimer);
478
+ }
479
+ if (this.scrollTimer !== null) {
480
+ clearTimeout(this.scrollTimer);
481
+ }
482
+ if (this.scrollParent) {
483
+ this.scrollParent.removeEventListener('scroll', this.handleScroll);
484
+ }
485
+ window.removeEventListener('resize', this.scheduleUpdate);
486
+ if (this.frameId !== null) {
487
+ if (typeof cancelAnimationFrame === 'function') {
488
+ cancelAnimationFrame(this.frameId);
489
+ } else {
490
+ clearTimeout(this.frameId);
491
+ }
492
+ }
493
+ }
494
+ }, {
495
+ key: 'attachScroll',
496
+ value: function attachScroll() {
497
+ if (!this.props.virtualizeRows) {
498
+ if (this.scrollParent) {
499
+ this.scrollParent.removeEventListener('scroll', this.handleScroll);
500
+ this.scrollParent = null;
501
+ window.removeEventListener('resize', this.scheduleUpdate);
502
+ }
503
+ if (this.scrollTimer !== null) {
504
+ clearTimeout(this.scrollTimer);
505
+ this.scrollTimer = null;
506
+ }
507
+ this.scrollPending = false;
508
+ this.invalidateViewportRequest();
509
+ return;
510
+ }
511
+ if (!this.bodyRef.current) {
512
+ return;
513
+ }
514
+ var parent = getScrollParent(this.bodyRef.current);
515
+ if (parent === this.scrollParent) {
516
+ return;
517
+ }
518
+ if (this.scrollParent) {
519
+ this.scrollParent.removeEventListener('scroll', this.handleScroll);
520
+ }
521
+ this.scrollParent = parent;
522
+ this.updateOverlayBounds();
523
+ parent.addEventListener('scroll', this.handleScroll, { passive: true });
524
+ window.addEventListener('resize', this.scheduleUpdate);
525
+ }
526
+ }, {
527
+ key: 'updateOverlayBounds',
528
+ value: function updateOverlayBounds() {
529
+ if (!this.scrollParent) {
530
+ return;
531
+ }
532
+ var bounds = this.scrollParent === window ? {
533
+ top: 0,
534
+ left: 0,
535
+ width: window.innerWidth,
536
+ height: window.innerHeight
537
+ } : this.scrollParent.getBoundingClientRect();
538
+ this.overlayBounds = {
539
+ top: Math.max(0, bounds.top),
540
+ left: Math.max(0, bounds.left),
541
+ width: Math.max(0, Math.min(window.innerWidth, bounds.left + bounds.width) - Math.max(0, bounds.left)),
542
+ height: Math.max(0, Math.min(window.innerHeight, bounds.top + bounds.height) - Math.max(0, bounds.top))
543
+ };
544
+ }
545
+ }, {
546
+ key: 'handleScroll',
547
+ value: function handleScroll() {
548
+ var _this2 = this;
549
+
550
+ if (this.scrollTimer !== null) {
551
+ clearTimeout(this.scrollTimer);
552
+ }
553
+ if (!this.scrollPending) {
554
+ this.scrollPending = true;
555
+ this.updateOverlayBounds();
556
+ this.setState({ scrollPending: true });
557
+ }
558
+ this.scrollTimer = setTimeout(function () {
559
+ _this2.scrollTimer = null;
560
+ _this2.scrollPending = false;
561
+ _this2.updateWindow();
562
+ if (!_this2.unmounted) {
563
+ _this2.setState({ scrollPending: false });
564
+ }
565
+ }, VIRTUAL_SCROLL_DEBOUNCE_MS);
566
+ }
567
+ }, {
568
+ key: 'scheduleUpdate',
569
+ value: function scheduleUpdate() {
570
+ var _this3 = this;
571
+
572
+ if (this.frameId !== null) {
573
+ return;
574
+ }
575
+ var schedule = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : function (callback) {
576
+ return setTimeout(callback, 16);
577
+ };
578
+ this.frameId = schedule(function () {
579
+ _this3.frameId = null;
580
+ _this3.updateOverlayBounds();
581
+ if (!_this3.props.virtualizeRows && _this3.state.scrollPending) {
582
+ _this3.setState({ scrollPending: false });
583
+ }
584
+ if (!_this3.scrollPending) {
585
+ _this3.updateWindow();
586
+ }
587
+ });
588
+ }
589
+ }, {
590
+ key: 'getViewportKeyForProps',
591
+ value: function getViewportKeyForProps(props) {
592
+ return _getViewportKeyForProps(props);
593
+ }
594
+ }, {
595
+ key: 'getViewportKey',
596
+ value: function getViewportKey() {
597
+ return this.getViewportKeyForProps(this.props);
598
+ }
599
+ }, {
600
+ key: 'invalidateViewportRequest',
601
+ value: function invalidateViewportRequest() {
602
+ this.requestedWindow = null;
603
+ if (this.viewportTimer !== null) {
604
+ clearTimeout(this.viewportTimer);
605
+ this.viewportTimer = null;
606
+ }
607
+ }
608
+ }, {
609
+ key: 'requestViewportWindow',
610
+ value: function requestViewportWindow(start, end, columnStart, columnEnd, viewportKey) {
611
+ var _this4 = this;
612
+
613
+ var columnMode = this.props.tableOptions && this.props.tableOptions.aggregationDisplayMode === 'column';
614
+ var requestKey = viewportKey + ':' + start + ':' + end + ':' + (columnStart + ':' + columnEnd + ':' + columnMode);
615
+ if (requestKey === this.requestedWindow) {
616
+ return;
617
+ }
618
+ this.requestedWindow = requestKey;
619
+ if (this.viewportTimer !== null) {
620
+ clearTimeout(this.viewportTimer);
621
+ }
622
+ this.setState({
623
+ start: start,
624
+ end: end,
625
+ columnStart: columnStart,
626
+ columnEnd: columnEnd,
627
+ rows: null,
628
+ columnTotals: null,
629
+ viewportKey: viewportKey
630
+ });
631
+ // Give the browser a frame to paint the loading row before dispatching.
632
+ this.viewportTimer = setTimeout(function () {
633
+ _this4.viewportTimer = null;
634
+ (0, _PivotDataWorker.computePivotViewportInWorker)(viewportKey, start, end, columnMode, columnStart, columnEnd).catch(function (error) {
635
+ if (error && error.code === 'PIVOT_VIEWPORT_CANCELLED') {
636
+ return null;
637
+ }
638
+ return (0, _pivotCompute.computePivotViewport)(_this4.props.precomputedData, start, end, columnMode, columnStart, columnEnd);
639
+ }).then(function (viewport) {
640
+ if (!viewport || _this4.unmounted || _this4.requestedWindow !== requestKey) {
641
+ return;
642
+ }
643
+ _this4.setState({
644
+ rows: viewport.rows,
645
+ columnTotals: viewport.colTotals || {}
646
+ });
647
+ });
648
+ }, VIRTUAL_LOADING_PAINT_MS);
649
+ }
650
+ }, {
651
+ key: 'measureLeadingColumnWidth',
652
+ value: function measureLeadingColumnWidth() {
653
+ var body = this.bodyRef.current;
654
+ var table = body && body.parentElement;
655
+ var firstColumn = table && table.querySelector('thead .pvtColLabel');
656
+ if (!table || !firstColumn) {
657
+ return;
658
+ }
659
+ var tableBounds = table.getBoundingClientRect();
660
+ var firstColumnBounds = firstColumn.getBoundingClientRect();
661
+ var leftSpacer = table.querySelector('thead .pvtVirtualColumnSpacer-left');
662
+ var leftSpacerWidth = leftSpacer ? leftSpacer.getBoundingClientRect().width : 0;
663
+ var measuredWidth = firstColumnBounds.left - tableBounds.left - leftSpacerWidth;
664
+ if (Number.isFinite(measuredWidth) && measuredWidth > 0) {
665
+ this.leadingColumnWidth = measuredWidth;
666
+ }
667
+ }
668
+ }, {
669
+ key: 'updateWindow',
670
+ value: function updateWindow() {
671
+ if (!this.scrollParent || !this.bodyRef.current || !this.props.virtualizeRows) {
672
+ return;
673
+ }
674
+ var viewportTop = this.scrollParent === window ? 0 : this.scrollParent.getBoundingClientRect().top;
675
+ var viewportHeight = this.scrollParent === window ? window.innerHeight : this.scrollParent.clientHeight;
676
+ var viewportLeft = this.scrollParent === window ? 0 : this.scrollParent.getBoundingClientRect().left;
677
+ var viewportWidth = this.scrollParent === window ? window.innerWidth : this.scrollParent.clientWidth;
678
+ var bodyBounds = this.bodyRef.current.getBoundingClientRect();
679
+ var bodyTop = Number.isFinite(bodyBounds.top) ? bodyBounds.top : 0;
680
+ var bodyLeft = Number.isFinite(bodyBounds.left) ? bodyBounds.left : 0;
681
+ this.measureLeadingColumnWidth();
682
+ var offset = viewportTop - bodyTop;
683
+ var requestedStart = Math.max(0, Math.min(this.rowCount, Math.floor(offset / VIRTUAL_ROW_HEIGHT) - VIRTUAL_ROW_OVERSCAN));
684
+ var start = Math.floor(requestedStart / VIRTUAL_ROW_BLOCK) * VIRTUAL_ROW_BLOCK;
685
+ var end = Math.min(this.rowCount, start + Math.ceil(viewportHeight / VIRTUAL_ROW_HEIGHT) + VIRTUAL_ROW_BLOCK + VIRTUAL_ROW_OVERSCAN * 2);
686
+ var horizontalOffset = Math.max(0, viewportLeft - bodyLeft - (this.leadingColumnWidth || this.leadingColumnCount * VIRTUAL_COLUMN_WIDTH));
687
+ var requestedColumnStart = Math.max(0, Math.min(this.columnCount, Math.floor(horizontalOffset / VIRTUAL_COLUMN_WIDTH) - VIRTUAL_COLUMN_OVERSCAN));
688
+ var columnStart = Math.floor(requestedColumnStart / VIRTUAL_COLUMN_BLOCK) * VIRTUAL_COLUMN_BLOCK;
689
+ var columnEnd = Math.min(this.columnCount, columnStart + Math.ceil(viewportWidth / VIRTUAL_COLUMN_WIDTH) + VIRTUAL_COLUMN_BLOCK + VIRTUAL_COLUMN_OVERSCAN * 2);
690
+ var viewportKey = this.getViewportKey();
691
+ if (viewportKey !== null && (this.rowCount > _pivotCompute.VIRTUAL_ROW_THRESHOLD || this.columnCount > _pivotCompute.VIRTUAL_COLUMN_THRESHOLD)) {
692
+ this.requestViewportWindow(start, end, columnStart, columnEnd, viewportKey);
693
+ return;
694
+ }
695
+ this.invalidateViewportRequest();
696
+ if (start !== this.state.start || end !== this.state.end || columnStart !== this.state.columnStart || columnEnd !== this.state.columnEnd) {
697
+ this.setState({ start: start, end: end, columnStart: columnStart, columnEnd: columnEnd });
698
+ }
699
+ }
700
+ }, {
352
701
  key: 'render',
353
702
  value: function render() {
354
- var _this2 = this;
703
+ var _this5 = this;
355
704
 
356
- var pivotData = new _Utilities.PivotData(this.props);
705
+ if (this.pivotProps !== this.props) {
706
+ var pivotProps = !this.props.virtualizeRows && this.props.precomputedData && this.props.precomputedData.virtualized ? Object.assign({}, this.props, { precomputedData: null }) : this.props;
707
+ this.pivotData = new _Utilities.PivotData(pivotProps);
708
+ this.pivotProps = this.props;
709
+ }
710
+ var pivotData = this.pivotData;
357
711
  var colAttrs = pivotData.props.cols;
358
712
  var rowAttrs = pivotData.props.rows;
359
713
  var aggregations = pivotData.getAggregations();
@@ -369,16 +723,15 @@ function makeRenderer() {
369
723
  var shouldRenderRowHeaderPlaceholder = rowAttrsToRender.length !== 0;
370
724
 
371
725
  // --- Row group header (rowGroupConfig) ---
726
+ // `match` may reference row attribute names, 'Values' (aggregations shown
727
+ // as rows) and aggregation names/labels (aggregations shown as columns).
728
+ // Every column gets its own <th>, so the group label is repeated on each.
372
729
  var rowGroupConfig = this.props.rowGroupConfig;
373
730
  var hasHeaderGroup = Boolean(rowGroupConfig && rowGroupConfig.length);
374
- var headerGroupSegments = hasHeaderGroup ? buildHeaderGroupSegments(rowAttrs, rowGroupConfig) : [];
375
- // extraSpan accounts for the synthetic "Values" column added when there
376
- // are multiple aggregators shown as rows; it isn't part of any group.
377
- var headerGroupExtraSpan = rowAttrsToRender.length - rowAttrs.length;
731
+ var groupLookup = buildGroupLookup(rowGroupConfig);
732
+ var headerGroupSegments = hasHeaderGroup ? buildHeaderGroupSegments(rowAttrsToRender, rowGroupConfig) : [];
378
733
  var renderGroupSegments = function renderGroupSegments(rowSpan) {
379
- var extraSpan = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
380
-
381
- var cells = headerGroupSegments.map(function (seg, idx) {
734
+ return headerGroupSegments.map(function (seg, idx) {
382
735
  return _react2.default.createElement(
383
736
  'th',
384
737
  {
@@ -391,10 +744,6 @@ function makeRenderer() {
391
744
  seg.group || ''
392
745
  );
393
746
  });
394
- if (extraSpan > 0) {
395
- cells.push(_react2.default.createElement('th', { key: 'hgSegExtra', colSpan: extraSpan, rowSpan: rowSpan }));
396
- }
397
- return cells;
398
747
  };
399
748
  // --- End row group header ---
400
749
 
@@ -408,7 +757,7 @@ function makeRenderer() {
408
757
  // The "No." header cell is rendered in the first header row and spans
409
758
  // every header row, so the repeated header rows (column attributes,
410
759
  // aggregator labels, row group labels) stay aligned with the body.
411
- var numberColumnHeaderRowSpan = colAttrsToRender.length + (renderedAggregationHeaderRow ? 1 : 0) + (rowAttrsToRender.length !== 0 && !renderedAggregationHeaderRow ? 1 : 0) + (hasHeaderGroup && colAttrsToRender.length === 0 && rowAttrsToRender.length !== 0 ? 1 : 0);
760
+ var numberColumnHeaderRowSpan = colAttrsToRender.length + (renderedAggregationHeaderRow ? 1 : 0) + (rowAttrsToRender.length !== 0 && !renderedAggregationHeaderRow ? 1 : 0) + (hasHeaderGroup && colAttrsToRender.length === 0 && rowAttrsToRender.length !== 0 ? 1 : 0) + (renderedAggregationHeaderRow && hasHeaderGroup ? 1 : 0);
412
761
  var numberColumnInGroupRow = hasHeaderGroup && colAttrsToRender.length === 0 && rowAttrsToRender.length !== 0;
413
762
  var renderNumberHeaderCell = function renderNumberHeaderCell() {
414
763
  var rowSpan = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : numberColumnHeaderRowSpan;
@@ -422,86 +771,96 @@ function makeRenderer() {
422
771
 
423
772
  var rowKeys = pivotData.getRowKeys();
424
773
  var baseRowKeys = hasMultipleAggregators && !isColumnAggregationMode && rowKeys.length === 0 ? [[]] : rowKeys;
425
- var renderedRows = hasMultipleAggregators && !isColumnAggregationMode ? baseRowKeys.reduce(function (acc, rowKey) {
426
- aggregations.forEach(function (agg) {
427
- acc.push({
428
- displayKey: rowKey.concat([agg.label || agg.aggregatorName]),
429
- actualKey: rowKey,
430
- aggregationKey: agg.key
431
- });
432
- });
433
- return acc;
434
- }, []) : baseRowKeys.map(function (rowKey) {
435
- return {
436
- displayKey: rowKey,
437
- actualKey: rowKey,
438
- aggregationKey: primaryAggregation.key
439
- };
440
- });
441
- var rowKeyValues = renderedRows.map(function (r) {
442
- return r.displayKey;
443
- });
774
+ var totalRowCount = this.props.precomputedData && this.props.precomputedData.virtualized && typeof this.props.precomputedData.totalRows === 'number' ? this.props.precomputedData.totalRows : baseRowKeys.length * (hasMultipleAggregators && !isColumnAggregationMode ? aggregations.length : 1);
775
+ var isRowVirtualized = Boolean(this.props.virtualizeRows) && totalRowCount > _pivotCompute.VIRTUAL_ROW_THRESHOLD;
444
776
  var colKeys = pivotData.getColKeys();
445
777
  var baseColKeys = isColumnAggregationMode && colKeys.length === 0 ? [[]] : colKeys;
446
- var columnMeta = isColumnAggregationMode ? baseColKeys.reduce(function (acc, colKey) {
447
- aggregations.forEach(function (agg) {
448
- acc.push({
449
- displayKey: colKey.concat([agg.label || agg.aggregatorName]),
778
+ var totalColumnCount = isColumnAggregationMode ? baseColKeys.length * aggregations.length : colKeys.length;
779
+ var isColumnVirtualized = Boolean(this.props.virtualizeRows) && totalColumnCount > _pivotCompute.VIRTUAL_COLUMN_THRESHOLD;
780
+ var isVirtualized = isRowVirtualized || isColumnVirtualized;
781
+ var visibleColumnStart = isColumnVirtualized ? Math.min(this.state.columnStart, totalColumnCount) : 0;
782
+ var visibleColumnEnd = isColumnVirtualized ? Math.min(Math.max(this.state.columnEnd, visibleColumnStart), totalColumnCount) : totalColumnCount;
783
+ var columnMeta = [];
784
+ for (var columnIndex = visibleColumnStart; columnIndex < visibleColumnEnd; columnIndex++) {
785
+ if (isColumnAggregationMode) {
786
+ var colKey = baseColKeys[Math.floor(columnIndex / aggregations.length)];
787
+ var aggregation = aggregations[columnIndex % aggregations.length];
788
+ columnMeta.push({
789
+ displayKey: colKey.concat([aggregation.label || aggregation.aggregatorName]),
450
790
  actualKey: colKey,
451
- aggregationKey: agg.key
791
+ aggregationKey: aggregation.key,
792
+ absoluteIndex: columnIndex
452
793
  });
453
- });
454
- return acc;
455
- }, []) : colKeys.map(function (colKey) {
456
- return {
457
- displayKey: colKey,
458
- actualKey: colKey,
459
- aggregationKey: primaryAggregation.key
460
- };
461
- });
794
+ } else {
795
+ var _colKey = colKeys[columnIndex];
796
+ columnMeta.push({
797
+ displayKey: _colKey,
798
+ actualKey: _colKey,
799
+ aggregationKey: primaryAggregation.key,
800
+ absoluteIndex: columnIndex
801
+ });
802
+ }
803
+ }
462
804
  var colKeyValues = columnMeta.map(function (col) {
463
805
  return col.displayKey;
464
806
  });
807
+ var leftColumnSpacerWidth = isColumnVirtualized ? visibleColumnStart * VIRTUAL_COLUMN_WIDTH : 0;
808
+ var rightColumnSpacerWidth = isColumnVirtualized ? (totalColumnCount - visibleColumnEnd) * VIRTUAL_COLUMN_WIDTH : 0;
809
+ var renderColumnSpacer = function renderColumnSpacer(side, width) {
810
+ var Cell = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'th';
811
+ return width > 0 ? _react2.default.createElement(Cell, {
812
+ 'aria-hidden': 'true',
813
+ className: 'pvtVirtualColumnSpacer pvtVirtualColumnSpacer-' + side,
814
+ key: 'virtual-column-spacer-' + side,
815
+ style: { width: width, minWidth: width, maxWidth: width }
816
+ }) : null;
817
+ };
465
818
 
466
819
  var renderAggregatorHeaderCells = function renderAggregatorHeaderCells() {
467
- return columnMeta.map(function (colMeta, idx) {
468
- var label = colMeta.displayKey[colMeta.displayKey.length - 1] || '';
469
- // Calculate colspan/rowspan so aggregator headers line up with column groups
470
- // aggregator index is the last element in displayKey (after column attrs)
471
- var aggIndex = colAttrsToRender.length;
472
- // When there are column attributes, each aggregator should appear once per column group,
473
- // so each aggregator header should have colSpan = 1.
474
- // When there are no column attributes, use spanSize to merge identical aggregator labels.
475
- var colSpan = void 0;
476
- if (hasColumnAttributes) {
477
- // Each aggregator appears once per column group, so colSpan is always 1
478
- // This ensures aggregator headers are listed in each column group and align
479
- // correctly with the column groups above (which have colSpan = number of aggregators)
480
- colSpan = 1;
481
- } else {
482
- // Use spanSize to merge identical aggregator labels when there are no column attributes
483
- colSpan = spanSize(colKeyValues, idx, aggIndex);
484
- // If spanSize returned -1 it means this cell is merged into a previous one
485
- if (colSpan === -1) {
486
- return null;
820
+ return _react2.default.createElement(
821
+ _react2.default.Fragment,
822
+ null,
823
+ renderColumnSpacer('left', leftColumnSpacerWidth),
824
+ columnMeta.map(function (colMeta, idx) {
825
+ var label = colMeta.displayKey[colMeta.displayKey.length - 1] || '';
826
+ // Calculate colspan/rowspan so aggregator headers line up with column groups
827
+ // aggregator index is the last element in displayKey (after column attrs)
828
+ var aggIndex = colAttrsToRender.length;
829
+ // When there are column attributes, each aggregator should appear once per column group,
830
+ // so each aggregator header should have colSpan = 1.
831
+ // When there are no column attributes, use spanSize to merge identical aggregator labels.
832
+ var colSpan = void 0;
833
+ if (hasColumnAttributes) {
834
+ // Each aggregator appears once per column group, so colSpan is always 1
835
+ // This ensures aggregator headers are listed in each column group and align
836
+ // correctly with the column groups above (which have colSpan = number of aggregators)
837
+ colSpan = 1;
838
+ } else {
839
+ // Use spanSize to merge identical aggregator labels when there are no column attributes
840
+ colSpan = spanSize(colKeyValues, idx, aggIndex);
841
+ // If spanSize returned -1 it means this cell is merged into a previous one
842
+ if (colSpan === -1) {
843
+ return null;
844
+ }
487
845
  }
488
- }
489
846
 
490
- // When there are column attributes, rowSpan should account for the row header row
491
- // When there are no column attributes, rowSpan is 1
492
- var rowSpan = hasColumnAttributes && rowAttrsToRender.length !== 0 ? 1 : 1;
847
+ // When there are column attributes, rowSpan should account for the row header row
848
+ // When there are no column attributes, rowSpan is 1
849
+ var rowSpan = hasColumnAttributes && rowAttrsToRender.length !== 0 ? 1 : 1;
493
850
 
494
- return _react2.default.createElement(
495
- 'th',
496
- {
497
- className: 'pvtColLabel',
498
- key: 'agg-label-' + idx,
499
- colSpan: colSpan,
500
- rowSpan: rowSpan
501
- },
502
- label
503
- );
504
- });
851
+ return _react2.default.createElement(
852
+ 'th',
853
+ {
854
+ className: 'pvtColLabel',
855
+ key: 'agg-label-' + colMeta.absoluteIndex,
856
+ colSpan: colSpan,
857
+ rowSpan: rowSpan
858
+ },
859
+ label
860
+ );
861
+ }),
862
+ renderColumnSpacer('right', rightColumnSpacerWidth)
863
+ );
505
864
  };
506
865
 
507
866
  var valueCellColors = function valueCellColors() {};
@@ -566,6 +925,52 @@ function makeRenderer() {
566
925
  aggregationNameLookup[agg.key] = agg.aggregatorName;
567
926
  });
568
927
 
928
+ // Lookup aggregationKey -> aggregation definition (to read `vals`)
929
+ var aggregationByKey = {};
930
+ aggregations.forEach(function (agg) {
931
+ aggregationByKey[agg.key] = agg;
932
+ });
933
+
934
+ // Find the group label for an aggregation. rowGroupConfig `match` is
935
+ // compared against the aggregation's `vals` (the field names it works
936
+ // on, e.g. ['Employee Ref']). Falls back to label / aggregatorName.
937
+ var getAggregationGroup = function getAggregationGroup(aggregationKey) {
938
+ var agg = aggregationByKey[aggregationKey];
939
+ if (!agg) {
940
+ return null;
941
+ }
942
+ var vals = Array.isArray(agg.vals) ? agg.vals : [];
943
+ for (var k = 0; k < vals.length; k++) {
944
+ if (groupLookup[vals[k]]) {
945
+ return groupLookup[vals[k]];
946
+ }
947
+ }
948
+ return groupLookup[agg.label] || groupLookup[agg.aggregatorName] || null;
949
+ };
950
+
951
+ // Group label cells for the aggregation columns (column aggregation mode).
952
+ // Each column renders its own <th>, so the label repeats on every column.
953
+ var renderAggregatorGroupCells = function renderAggregatorGroupCells() {
954
+ return _react2.default.createElement(
955
+ _react2.default.Fragment,
956
+ null,
957
+ renderColumnSpacer('left', leftColumnSpacerWidth),
958
+ columnMeta.map(function (colMeta) {
959
+ var group = getAggregationGroup(colMeta.aggregationKey);
960
+ return _react2.default.createElement(
961
+ 'th',
962
+ {
963
+ key: 'aggGrp' + colMeta.absoluteIndex
964
+ // eslint-disable-next-line no-undefined
965
+ , style: group ? groupLabelStyle : undefined
966
+ },
967
+ group || ''
968
+ );
969
+ }),
970
+ renderColumnSpacer('right', rightColumnSpacerWidth)
971
+ );
972
+ };
973
+
569
974
  // Helper function to get cell style with conditional formatting
570
975
  var getCellStyle = function getCellStyle(value) {
571
976
  var heatmapStyle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -713,41 +1118,41 @@ function makeRenderer() {
713
1118
 
714
1119
  var handleColSort = function handleColSort(attr, e) {
715
1120
  e.stopPropagation();
716
- if (_this2.props.onColSort) {
717
- var currentSort = _this2.props.colSorts && _this2.props.colSorts[attr];
1121
+ if (_this5.props.onColSort) {
1122
+ var currentSort = _this5.props.colSorts && _this5.props.colSorts[attr];
718
1123
  var nextSort = 'ASC';
719
1124
  if (currentSort === 'ASC') {
720
1125
  nextSort = 'DESC';
721
1126
  } else if (currentSort === 'DESC') {
722
1127
  nextSort = null;
723
1128
  }
724
- _this2.props.onColSort(attr, nextSort);
1129
+ _this5.props.onColSort(attr, nextSort);
725
1130
  }
726
1131
  };
727
1132
 
728
1133
  var handleRowSort = function handleRowSort(attr, e) {
729
1134
  e.stopPropagation();
730
- if (_this2.props.onRowSort) {
731
- var currentSort = _this2.props.rowSorts && _this2.props.rowSorts[attr];
1135
+ if (_this5.props.onRowSort) {
1136
+ var currentSort = _this5.props.rowSorts && _this5.props.rowSorts[attr];
732
1137
  var nextSort = 'ASC';
733
1138
  if (currentSort === 'ASC') {
734
1139
  nextSort = 'DESC';
735
1140
  } else if (currentSort === 'DESC') {
736
1141
  nextSort = null;
737
1142
  }
738
- _this2.props.onRowSort(attr, nextSort);
1143
+ _this5.props.onRowSort(attr, nextSort);
739
1144
  }
740
1145
  };
741
1146
 
742
1147
  var getSortButton = function getSortButton(attr, isCol) {
743
- var currentSort = isCol ? _this2.props.colSorts && _this2.props.colSorts[attr] : _this2.props.rowSorts && _this2.props.rowSorts[attr];
1148
+ var currentSort = isCol ? _this5.props.colSorts && _this5.props.colSorts[attr] : _this5.props.rowSorts && _this5.props.rowSorts[attr];
744
1149
  var handleSort = isCol ? handleColSort : handleRowSort;
745
1150
  var sortIcon = '';
746
1151
  if (currentSort !== null) {
747
1152
  if (isCol) {
748
- sortIcon = _this2.props.colSortIcons[currentSort] || _this2.props.colSortIcons.DEFAULT;
1153
+ sortIcon = _this5.props.colSortIcons[currentSort] || _this5.props.colSortIcons.DEFAULT;
749
1154
  } else {
750
- sortIcon = _this2.props.rowSortIcons[currentSort] || _this2.props.rowSortIcons.DEFAULT;
1155
+ sortIcon = _this5.props.rowSortIcons[currentSort] || _this5.props.rowSortIcons.DEFAULT;
751
1156
  }
752
1157
  }
753
1158
  return _react2.default.createElement(
@@ -766,10 +1171,29 @@ function makeRenderer() {
766
1171
 
767
1172
  var totalRowAggregations = isColumnAggregationMode ? [primaryAggregation] : hasMultipleAggregators ? aggregations : [primaryAggregation];
768
1173
  var totalsLabelText = 'Totals';
1174
+ this.rowCount = totalRowCount;
1175
+ this.columnCount = totalColumnCount;
1176
+ this.leadingColumnCount = rowAttrsToRender.length + (hasNumberColumn ? 1 : 0) + (showTotalsColumn && colAttrsToRender.length !== 0 && rowAttrsToRender.length !== 0 ? 1 : 0);
1177
+ var visibleStart = isRowVirtualized ? Math.min(this.state.start, totalRowCount) : 0;
1178
+ var visibleEnd = isRowVirtualized ? Math.min(Math.max(this.state.end, visibleStart), totalRowCount) : totalRowCount;
1179
+ var viewportKey = isVirtualized ? this.getViewportKey() : null;
1180
+ var viewportReady = viewportKey !== null && this.state.viewportKey === viewportKey && this.state.start === visibleStart && this.state.end === visibleEnd && this.state.columnStart === visibleColumnStart && this.state.columnEnd === visibleColumnEnd && this.state.columnTotals !== null && this.state.rows !== null;
1181
+ var isViewportLoading = viewportKey !== null && !viewportReady;
1182
+ var viewportColumnTotals = this.state.columnTotals || {};
1183
+ var visibleRows = isViewportLoading ? [] : viewportKey !== null ? this.state.rows : (0, _pivotCompute.computePivotViewport)({ rowKeys: baseRowKeys, aggregations: aggregations }, visibleStart, visibleEnd, isColumnAggregationMode, visibleColumnStart, visibleColumnEnd).rows;
1184
+ var virtualColumnCount = rowAttrsToRender.length + columnMeta.length + (leftColumnSpacerWidth > 0 ? 1 : 0) + (rightColumnSpacerWidth > 0 ? 1 : 0) + (hasNumberColumn ? 1 : 0) + (showTotalsColumn ? 1 : 0);
769
1185
 
770
1186
  return _react2.default.createElement(
771
1187
  'table',
772
- { className: 'pvtTable' },
1188
+ {
1189
+ className: 'pvtTable' + (isVirtualized ? ' pvtVirtualized' : '') + ('' + (isColumnVirtualized ? ' pvtVirtualColumns' : '')),
1190
+ 'data-pvt-virtualized': isVirtualized ? 'true' : null,
1191
+ ref: function ref(element) {
1192
+ if (element) {
1193
+ pivotTablePropsByElement.set(element, _this5.props);
1194
+ }
1195
+ }
1196
+ },
773
1197
  _react2.default.createElement(
774
1198
  'thead',
775
1199
  null,
@@ -778,7 +1202,7 @@ function makeRenderer() {
778
1202
  'tr',
779
1203
  { key: 'colAttr' + j },
780
1204
  hasNumberColumn && j === 0 && renderNumberHeaderCell(),
781
- shouldRenderRowHeaderPlaceholder && (isColumnAggregationMode || j === 0) && !(isColumnAggregationMode && hasColumnAttributes && rowAttrsToRender.length === 1) && (hasHeaderGroup && j === 0 && !isColumnAggregationMode ? renderGroupSegments(colAttrsToRender.length, headerGroupExtraSpan) : _react2.default.createElement('th', {
1205
+ shouldRenderRowHeaderPlaceholder && (isColumnAggregationMode || j === 0) && !(isColumnAggregationMode && hasColumnAttributes && rowAttrsToRender.length === 1) && (hasHeaderGroup && j === 0 && !isColumnAggregationMode ? renderGroupSegments(colAttrsToRender.length) : _react2.default.createElement('th', {
782
1206
  colSpan: isColumnAggregationMode && hasColumnAttributes ? rowAttrsToRender.length - 1 : rowAttrsToRender.length,
783
1207
  rowSpan: isColumnAggregationMode && hasColumnAttributes ? 1 : colAttrsToRender.length
784
1208
  })),
@@ -788,6 +1212,7 @@ function makeRenderer() {
788
1212
  c,
789
1213
  colAttrs.includes(c) && getSortButton(c, true)
790
1214
  ) : null,
1215
+ renderColumnSpacer('left', leftColumnSpacerWidth),
791
1216
  colKeyValues.map(function (colKey, i) {
792
1217
  var x = spanSize(colKeyValues, i, j);
793
1218
 
@@ -800,13 +1225,14 @@ function makeRenderer() {
800
1225
  'th',
801
1226
  {
802
1227
  className: 'pvtColLabel',
803
- key: 'colKey' + i,
1228
+ key: 'colKey' + columnMeta[i].absoluteIndex,
804
1229
  colSpan: x,
805
1230
  rowSpan: j === colAttrsToRender.length - 1 && rowAttrsToRender.length !== 0 && !isColumnAggregationMode ? 2 : 1
806
1231
  },
807
1232
  formattedColLabel
808
1233
  );
809
1234
  }),
1235
+ renderColumnSpacer('right', rightColumnSpacerWidth),
810
1236
  showTotalsColumn && j === 0 && _react2.default.createElement(
811
1237
  'th',
812
1238
  {
@@ -817,12 +1243,18 @@ function makeRenderer() {
817
1243
  )
818
1244
  );
819
1245
  }),
1246
+ isColumnAggregationMode && hasColumnAttributes && hasHeaderGroup && _react2.default.createElement(
1247
+ 'tr',
1248
+ null,
1249
+ renderGroupSegments(1),
1250
+ renderAggregatorGroupCells()
1251
+ ),
820
1252
  hasHeaderGroup && colAttrsToRender.length === 0 && rowAttrsToRender.length !== 0 && _react2.default.createElement(
821
1253
  'tr',
822
1254
  null,
823
1255
  hasNumberColumn && renderNumberHeaderCell(),
824
- renderGroupSegments(1, headerGroupExtraSpan),
825
- _react2.default.createElement('th', null)
1256
+ renderGroupSegments(1),
1257
+ isColumnAggregationMode ? renderAggregatorGroupCells() : _react2.default.createElement('th', null)
826
1258
  ),
827
1259
  isColumnAggregationMode && hasColumnAttributes && _react2.default.createElement(
828
1260
  'tr',
@@ -863,20 +1295,61 @@ function makeRenderer() {
863
1295
  ),
864
1296
  _react2.default.createElement(
865
1297
  'tbody',
866
- null,
867
- renderedRows.map(function (rowMeta, i) {
868
- var totalAggregator = pivotData.getAggregator(rowMeta.actualKey, [], rowMeta.aggregationKey);
1298
+ { ref: this.bodyRef },
1299
+ isRowVirtualized && visibleStart > 0 && _react2.default.createElement(
1300
+ 'tr',
1301
+ { className: 'pvtVirtualSpacer', 'aria-hidden': 'true' },
1302
+ _react2.default.createElement('td', {
1303
+ colSpan: virtualColumnCount,
1304
+ style: {
1305
+ height: visibleStart * VIRTUAL_ROW_HEIGHT,
1306
+ padding: 0,
1307
+ border: 0
1308
+ }
1309
+ })
1310
+ ),
1311
+ isViewportLoading && _react2.default.createElement(
1312
+ 'tr',
1313
+ { className: 'pvtVirtualLoading', 'aria-hidden': 'true' },
1314
+ _react2.default.createElement('td', {
1315
+ colSpan: virtualColumnCount,
1316
+ style: {
1317
+ height: (visibleEnd - visibleStart) * VIRTUAL_ROW_HEIGHT
1318
+ }
1319
+ })
1320
+ ),
1321
+ isVirtualized && this.overlayBounds && (this.state.scrollPending || isViewportLoading) && _react2.default.createElement(
1322
+ 'tr',
1323
+ { className: 'pvtVirtualOverlayRow' },
1324
+ _react2.default.createElement(
1325
+ 'td',
1326
+ { colSpan: virtualColumnCount },
1327
+ _react2.default.createElement(
1328
+ 'div',
1329
+ {
1330
+ className: 'pvtLoadingOverlay pvtVirtualScrollOverlay',
1331
+ role: 'status',
1332
+ style: this.overlayBounds
1333
+ },
1334
+ _react2.default.createElement(PivotLoadingContent, null)
1335
+ )
1336
+ )
1337
+ ),
1338
+ visibleRows.map(function (rowMeta, visibleIndex) {
1339
+ var i = visibleStart + visibleIndex;
1340
+ var hasViewportValues = rowMeta.cells !== null;
1341
+ var totalAggregator = hasViewportValues ? pivotData.getPrecomputedAggregator(rowMeta.rowTotal, rowMeta.aggregationKey) : pivotData.getAggregator(rowMeta.actualKey, [], rowMeta.aggregationKey);
869
1342
  var displayKey = rowMeta.displayKey;
870
1343
  return _react2.default.createElement(
871
1344
  'tr',
872
- { key: 'rowKeyRow' + i },
1345
+ { className: 'pvtDataRow', key: 'rowKeyRow' + i },
873
1346
  hasNumberColumn && _react2.default.createElement(
874
1347
  'th',
875
1348
  { className: 'pvtRowNumber', key: 'rowNumber' + i },
876
1349
  i + 1
877
1350
  ),
878
1351
  displayKey.map(function (txt, j) {
879
- var x = spanSize(rowKeyValues, i, j);
1352
+ var x = rowMeta.spans[j];
880
1353
  if (x === -1) {
881
1354
  return null;
882
1355
  }
@@ -893,22 +1366,24 @@ function makeRenderer() {
893
1366
  formattedTxt
894
1367
  );
895
1368
  }),
896
- columnMeta.map(function (colMeta, j) {
1369
+ renderColumnSpacer('left', leftColumnSpacerWidth, 'td'),
1370
+ columnMeta.map(function (colMeta) {
897
1371
  var aggregationKeyForCell = isColumnAggregationMode ? colMeta.aggregationKey : rowMeta.aggregationKey;
898
- var aggregator = pivotData.getAggregator(rowMeta.actualKey, colMeta.actualKey, aggregationKeyForCell);
1372
+ var aggregator = hasViewportValues ? pivotData.getPrecomputedAggregator(rowMeta.cells[colMeta.actualKey.join(FLAT_KEY_SEPARATOR)], aggregationKeyForCell) : pivotData.getAggregator(rowMeta.actualKey, colMeta.actualKey, aggregationKeyForCell);
899
1373
  var value = aggregator.value();
900
1374
  var heatmapStyle = valueCellColors(rowMeta.actualKey, colMeta.actualKey, value) || {};
901
1375
  return _react2.default.createElement(
902
1376
  'td',
903
1377
  {
904
1378
  className: 'pvtVal',
905
- key: 'pvtVal' + i + '-' + j,
1379
+ key: 'pvtVal' + i + '-' + colMeta.absoluteIndex,
906
1380
  onClick: getClickHandler && getClickHandler(value, rowMeta.actualKey, colMeta.actualKey),
907
1381
  style: getCellStyle(value, heatmapStyle)
908
1382
  },
909
1383
  formatCellValue(value, aggregator, aggregationKeyForCell)
910
1384
  );
911
1385
  }),
1386
+ renderColumnSpacer('right', rightColumnSpacerWidth, 'td'),
912
1387
  showTotalsColumn && _react2.default.createElement(
913
1388
  'td',
914
1389
  {
@@ -920,6 +1395,18 @@ function makeRenderer() {
920
1395
  )
921
1396
  );
922
1397
  }),
1398
+ isRowVirtualized && visibleEnd < totalRowCount && _react2.default.createElement(
1399
+ 'tr',
1400
+ { className: 'pvtVirtualSpacer', 'aria-hidden': 'true' },
1401
+ _react2.default.createElement('td', {
1402
+ colSpan: virtualColumnCount,
1403
+ style: {
1404
+ height: (totalRowCount - visibleEnd) * VIRTUAL_ROW_HEIGHT,
1405
+ padding: 0,
1406
+ border: 0
1407
+ }
1408
+ })
1409
+ ),
923
1410
  totalRowAggregations.map(function (agg) {
924
1411
  var grandTotalAggregator = pivotData.getAggregator([], [], isColumnAggregationMode ? primaryAggregation.key : agg.key);
925
1412
  var totalLabel = hasMultipleAggregators && !isColumnAggregationMode ? 'Totals \u2013 ' + (agg.label || agg.aggregatorName) : totalsLabelText;
@@ -934,20 +1421,23 @@ function makeRenderer() {
934
1421
  },
935
1422
  totalLabel
936
1423
  ),
937
- columnMeta.map(function (colMeta, i) {
938
- var totalAggregator = pivotData.getAggregator([], colMeta.actualKey, isColumnAggregationMode ? colMeta.aggregationKey : agg.key);
1424
+ renderColumnSpacer('left', leftColumnSpacerWidth, 'td'),
1425
+ columnMeta.map(function (colMeta) {
1426
+ var flatColKey = colMeta.actualKey.join(FLAT_KEY_SEPARATOR);
1427
+ var totalAggregator = viewportKey !== null ? pivotData.getPrecomputedAggregator(viewportColumnTotals[flatColKey], isColumnAggregationMode ? colMeta.aggregationKey : agg.key) : pivotData.getAggregator([], colMeta.actualKey, isColumnAggregationMode ? colMeta.aggregationKey : agg.key);
939
1428
  var totalValue = totalAggregator.value();
940
1429
  return _react2.default.createElement(
941
1430
  'td',
942
1431
  {
943
1432
  className: 'pvtTotal',
944
- key: 'total' + agg.key + '-' + i,
1433
+ key: 'total' + agg.key + '-' + colMeta.absoluteIndex,
945
1434
  onClick: getClickHandler && getClickHandler(totalValue, [null], colMeta.actualKey),
946
1435
  style: getCellStyle(totalValue, rowTotalColors(totalValue) || {})
947
1436
  },
948
1437
  formatCellValue(totalValue, totalAggregator, agg.key)
949
1438
  );
950
1439
  }),
1440
+ renderColumnSpacer('right', rightColumnSpacerWidth, 'td'),
951
1441
  showTotalsColumn && _react2.default.createElement(
952
1442
  'td',
953
1443
  {
@@ -962,6 +1452,25 @@ function makeRenderer() {
962
1452
  )
963
1453
  );
964
1454
  }
1455
+ }], [{
1456
+ key: 'getDerivedStateFromProps',
1457
+ value: function getDerivedStateFromProps(nextProps, previousState) {
1458
+ var nextViewportKey = _getViewportKeyForProps(nextProps);
1459
+ if (nextViewportKey === previousState.sourceViewportKey) {
1460
+ return null;
1461
+ }
1462
+ return {
1463
+ start: 0,
1464
+ end: INITIAL_ROW_WINDOW_END,
1465
+ rows: null,
1466
+ columnStart: 0,
1467
+ columnEnd: INITIAL_COLUMN_WINDOW_END,
1468
+ columnTotals: null,
1469
+ viewportKey: null,
1470
+ sourceViewportKey: nextViewportKey,
1471
+ scrollPending: false
1472
+ };
1473
+ }
965
1474
  }]);
966
1475
 
967
1476
  return TableRenderer;
@@ -1008,6 +1517,8 @@ function makeRenderer() {
1008
1517
  // rendered before the row attribute columns.
1009
1518
  TableRenderer.defaultProps.showNumericalOrder = false;
1010
1519
  TableRenderer.propTypes.showNumericalOrder = _propTypes2.default.bool;
1520
+ TableRenderer.defaultProps.virtualizeRows = false;
1521
+ TableRenderer.propTypes.virtualizeRows = _propTypes2.default.bool;
1011
1522
  return TableRenderer;
1012
1523
  }
1013
1524