@hrforte/react-pivottable 2.2.2 → 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"); } }
@@ -31,6 +36,57 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
31
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 */
32
37
 
33
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
+ };
89
+
34
90
  // helper function for setting row/col-span in pivotTableRenderer
35
91
  var spanSize = function spanSize(arr, i, j) {
36
92
  var x = void 0;
@@ -350,18 +406,308 @@ function makeRenderer() {
350
406
  var TableRenderer = function (_React$PureComponent) {
351
407
  _inherits(TableRenderer, _React$PureComponent);
352
408
 
353
- function TableRenderer() {
409
+ function TableRenderer(props) {
354
410
  _classCallCheck(this, TableRenderer);
355
411
 
356
- 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;
357
443
  }
358
444
 
359
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
+ }, {
360
701
  key: 'render',
361
702
  value: function render() {
362
- var _this2 = this;
703
+ var _this5 = this;
363
704
 
364
- 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;
365
711
  var colAttrs = pivotData.props.cols;
366
712
  var rowAttrs = pivotData.props.rows;
367
713
  var aggregations = pivotData.getAggregations();
@@ -425,86 +771,96 @@ function makeRenderer() {
425
771
 
426
772
  var rowKeys = pivotData.getRowKeys();
427
773
  var baseRowKeys = hasMultipleAggregators && !isColumnAggregationMode && rowKeys.length === 0 ? [[]] : rowKeys;
428
- var renderedRows = hasMultipleAggregators && !isColumnAggregationMode ? baseRowKeys.reduce(function (acc, rowKey) {
429
- aggregations.forEach(function (agg) {
430
- acc.push({
431
- displayKey: rowKey.concat([agg.label || agg.aggregatorName]),
432
- actualKey: rowKey,
433
- aggregationKey: agg.key
434
- });
435
- });
436
- return acc;
437
- }, []) : baseRowKeys.map(function (rowKey) {
438
- return {
439
- displayKey: rowKey,
440
- actualKey: rowKey,
441
- aggregationKey: primaryAggregation.key
442
- };
443
- });
444
- var rowKeyValues = renderedRows.map(function (r) {
445
- return r.displayKey;
446
- });
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;
447
776
  var colKeys = pivotData.getColKeys();
448
777
  var baseColKeys = isColumnAggregationMode && colKeys.length === 0 ? [[]] : colKeys;
449
- var columnMeta = isColumnAggregationMode ? baseColKeys.reduce(function (acc, colKey) {
450
- aggregations.forEach(function (agg) {
451
- acc.push({
452
- 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]),
453
790
  actualKey: colKey,
454
- aggregationKey: agg.key
791
+ aggregationKey: aggregation.key,
792
+ absoluteIndex: columnIndex
455
793
  });
456
- });
457
- return acc;
458
- }, []) : colKeys.map(function (colKey) {
459
- return {
460
- displayKey: colKey,
461
- actualKey: colKey,
462
- aggregationKey: primaryAggregation.key
463
- };
464
- });
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
+ }
465
804
  var colKeyValues = columnMeta.map(function (col) {
466
805
  return col.displayKey;
467
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
+ };
468
818
 
469
819
  var renderAggregatorHeaderCells = function renderAggregatorHeaderCells() {
470
- return columnMeta.map(function (colMeta, idx) {
471
- var label = colMeta.displayKey[colMeta.displayKey.length - 1] || '';
472
- // Calculate colspan/rowspan so aggregator headers line up with column groups
473
- // aggregator index is the last element in displayKey (after column attrs)
474
- var aggIndex = colAttrsToRender.length;
475
- // When there are column attributes, each aggregator should appear once per column group,
476
- // so each aggregator header should have colSpan = 1.
477
- // When there are no column attributes, use spanSize to merge identical aggregator labels.
478
- var colSpan = void 0;
479
- if (hasColumnAttributes) {
480
- // Each aggregator appears once per column group, so colSpan is always 1
481
- // This ensures aggregator headers are listed in each column group and align
482
- // correctly with the column groups above (which have colSpan = number of aggregators)
483
- colSpan = 1;
484
- } else {
485
- // Use spanSize to merge identical aggregator labels when there are no column attributes
486
- colSpan = spanSize(colKeyValues, idx, aggIndex);
487
- // If spanSize returned -1 it means this cell is merged into a previous one
488
- if (colSpan === -1) {
489
- 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
+ }
490
845
  }
491
- }
492
846
 
493
- // When there are column attributes, rowSpan should account for the row header row
494
- // When there are no column attributes, rowSpan is 1
495
- 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;
496
850
 
497
- return _react2.default.createElement(
498
- 'th',
499
- {
500
- className: 'pvtColLabel',
501
- key: 'agg-label-' + idx,
502
- colSpan: colSpan,
503
- rowSpan: rowSpan
504
- },
505
- label
506
- );
507
- });
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
+ );
508
864
  };
509
865
 
510
866
  var valueCellColors = function valueCellColors() {};
@@ -595,18 +951,24 @@ function makeRenderer() {
595
951
  // Group label cells for the aggregation columns (column aggregation mode).
596
952
  // Each column renders its own <th>, so the label repeats on every column.
597
953
  var renderAggregatorGroupCells = function renderAggregatorGroupCells() {
598
- return columnMeta.map(function (colMeta, idx) {
599
- var group = getAggregationGroup(colMeta.aggregationKey);
600
- return _react2.default.createElement(
601
- 'th',
602
- {
603
- key: 'aggGrp' + idx
604
- // eslint-disable-next-line no-undefined
605
- , style: group ? groupLabelStyle : undefined
606
- },
607
- group || ''
608
- );
609
- });
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
+ );
610
972
  };
611
973
 
612
974
  // Helper function to get cell style with conditional formatting
@@ -756,41 +1118,41 @@ function makeRenderer() {
756
1118
 
757
1119
  var handleColSort = function handleColSort(attr, e) {
758
1120
  e.stopPropagation();
759
- if (_this2.props.onColSort) {
760
- var currentSort = _this2.props.colSorts && _this2.props.colSorts[attr];
1121
+ if (_this5.props.onColSort) {
1122
+ var currentSort = _this5.props.colSorts && _this5.props.colSorts[attr];
761
1123
  var nextSort = 'ASC';
762
1124
  if (currentSort === 'ASC') {
763
1125
  nextSort = 'DESC';
764
1126
  } else if (currentSort === 'DESC') {
765
1127
  nextSort = null;
766
1128
  }
767
- _this2.props.onColSort(attr, nextSort);
1129
+ _this5.props.onColSort(attr, nextSort);
768
1130
  }
769
1131
  };
770
1132
 
771
1133
  var handleRowSort = function handleRowSort(attr, e) {
772
1134
  e.stopPropagation();
773
- if (_this2.props.onRowSort) {
774
- var currentSort = _this2.props.rowSorts && _this2.props.rowSorts[attr];
1135
+ if (_this5.props.onRowSort) {
1136
+ var currentSort = _this5.props.rowSorts && _this5.props.rowSorts[attr];
775
1137
  var nextSort = 'ASC';
776
1138
  if (currentSort === 'ASC') {
777
1139
  nextSort = 'DESC';
778
1140
  } else if (currentSort === 'DESC') {
779
1141
  nextSort = null;
780
1142
  }
781
- _this2.props.onRowSort(attr, nextSort);
1143
+ _this5.props.onRowSort(attr, nextSort);
782
1144
  }
783
1145
  };
784
1146
 
785
1147
  var getSortButton = function getSortButton(attr, isCol) {
786
- 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];
787
1149
  var handleSort = isCol ? handleColSort : handleRowSort;
788
1150
  var sortIcon = '';
789
1151
  if (currentSort !== null) {
790
1152
  if (isCol) {
791
- sortIcon = _this2.props.colSortIcons[currentSort] || _this2.props.colSortIcons.DEFAULT;
1153
+ sortIcon = _this5.props.colSortIcons[currentSort] || _this5.props.colSortIcons.DEFAULT;
792
1154
  } else {
793
- sortIcon = _this2.props.rowSortIcons[currentSort] || _this2.props.rowSortIcons.DEFAULT;
1155
+ sortIcon = _this5.props.rowSortIcons[currentSort] || _this5.props.rowSortIcons.DEFAULT;
794
1156
  }
795
1157
  }
796
1158
  return _react2.default.createElement(
@@ -809,10 +1171,29 @@ function makeRenderer() {
809
1171
 
810
1172
  var totalRowAggregations = isColumnAggregationMode ? [primaryAggregation] : hasMultipleAggregators ? aggregations : [primaryAggregation];
811
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);
812
1185
 
813
1186
  return _react2.default.createElement(
814
1187
  'table',
815
- { 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
+ },
816
1197
  _react2.default.createElement(
817
1198
  'thead',
818
1199
  null,
@@ -831,6 +1212,7 @@ function makeRenderer() {
831
1212
  c,
832
1213
  colAttrs.includes(c) && getSortButton(c, true)
833
1214
  ) : null,
1215
+ renderColumnSpacer('left', leftColumnSpacerWidth),
834
1216
  colKeyValues.map(function (colKey, i) {
835
1217
  var x = spanSize(colKeyValues, i, j);
836
1218
 
@@ -843,13 +1225,14 @@ function makeRenderer() {
843
1225
  'th',
844
1226
  {
845
1227
  className: 'pvtColLabel',
846
- key: 'colKey' + i,
1228
+ key: 'colKey' + columnMeta[i].absoluteIndex,
847
1229
  colSpan: x,
848
1230
  rowSpan: j === colAttrsToRender.length - 1 && rowAttrsToRender.length !== 0 && !isColumnAggregationMode ? 2 : 1
849
1231
  },
850
1232
  formattedColLabel
851
1233
  );
852
1234
  }),
1235
+ renderColumnSpacer('right', rightColumnSpacerWidth),
853
1236
  showTotalsColumn && j === 0 && _react2.default.createElement(
854
1237
  'th',
855
1238
  {
@@ -912,20 +1295,61 @@ function makeRenderer() {
912
1295
  ),
913
1296
  _react2.default.createElement(
914
1297
  'tbody',
915
- null,
916
- renderedRows.map(function (rowMeta, i) {
917
- 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);
918
1342
  var displayKey = rowMeta.displayKey;
919
1343
  return _react2.default.createElement(
920
1344
  'tr',
921
- { key: 'rowKeyRow' + i },
1345
+ { className: 'pvtDataRow', key: 'rowKeyRow' + i },
922
1346
  hasNumberColumn && _react2.default.createElement(
923
1347
  'th',
924
1348
  { className: 'pvtRowNumber', key: 'rowNumber' + i },
925
1349
  i + 1
926
1350
  ),
927
1351
  displayKey.map(function (txt, j) {
928
- var x = spanSize(rowKeyValues, i, j);
1352
+ var x = rowMeta.spans[j];
929
1353
  if (x === -1) {
930
1354
  return null;
931
1355
  }
@@ -942,22 +1366,24 @@ function makeRenderer() {
942
1366
  formattedTxt
943
1367
  );
944
1368
  }),
945
- columnMeta.map(function (colMeta, j) {
1369
+ renderColumnSpacer('left', leftColumnSpacerWidth, 'td'),
1370
+ columnMeta.map(function (colMeta) {
946
1371
  var aggregationKeyForCell = isColumnAggregationMode ? colMeta.aggregationKey : rowMeta.aggregationKey;
947
- 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);
948
1373
  var value = aggregator.value();
949
1374
  var heatmapStyle = valueCellColors(rowMeta.actualKey, colMeta.actualKey, value) || {};
950
1375
  return _react2.default.createElement(
951
1376
  'td',
952
1377
  {
953
1378
  className: 'pvtVal',
954
- key: 'pvtVal' + i + '-' + j,
1379
+ key: 'pvtVal' + i + '-' + colMeta.absoluteIndex,
955
1380
  onClick: getClickHandler && getClickHandler(value, rowMeta.actualKey, colMeta.actualKey),
956
1381
  style: getCellStyle(value, heatmapStyle)
957
1382
  },
958
1383
  formatCellValue(value, aggregator, aggregationKeyForCell)
959
1384
  );
960
1385
  }),
1386
+ renderColumnSpacer('right', rightColumnSpacerWidth, 'td'),
961
1387
  showTotalsColumn && _react2.default.createElement(
962
1388
  'td',
963
1389
  {
@@ -969,6 +1395,18 @@ function makeRenderer() {
969
1395
  )
970
1396
  );
971
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
+ ),
972
1410
  totalRowAggregations.map(function (agg) {
973
1411
  var grandTotalAggregator = pivotData.getAggregator([], [], isColumnAggregationMode ? primaryAggregation.key : agg.key);
974
1412
  var totalLabel = hasMultipleAggregators && !isColumnAggregationMode ? 'Totals \u2013 ' + (agg.label || agg.aggregatorName) : totalsLabelText;
@@ -983,20 +1421,23 @@ function makeRenderer() {
983
1421
  },
984
1422
  totalLabel
985
1423
  ),
986
- columnMeta.map(function (colMeta, i) {
987
- 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);
988
1428
  var totalValue = totalAggregator.value();
989
1429
  return _react2.default.createElement(
990
1430
  'td',
991
1431
  {
992
1432
  className: 'pvtTotal',
993
- key: 'total' + agg.key + '-' + i,
1433
+ key: 'total' + agg.key + '-' + colMeta.absoluteIndex,
994
1434
  onClick: getClickHandler && getClickHandler(totalValue, [null], colMeta.actualKey),
995
1435
  style: getCellStyle(totalValue, rowTotalColors(totalValue) || {})
996
1436
  },
997
1437
  formatCellValue(totalValue, totalAggregator, agg.key)
998
1438
  );
999
1439
  }),
1440
+ renderColumnSpacer('right', rightColumnSpacerWidth, 'td'),
1000
1441
  showTotalsColumn && _react2.default.createElement(
1001
1442
  'td',
1002
1443
  {
@@ -1011,6 +1452,25 @@ function makeRenderer() {
1011
1452
  )
1012
1453
  );
1013
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
+ }
1014
1474
  }]);
1015
1475
 
1016
1476
  return TableRenderer;
@@ -1057,6 +1517,8 @@ function makeRenderer() {
1057
1517
  // rendered before the row attribute columns.
1058
1518
  TableRenderer.defaultProps.showNumericalOrder = false;
1059
1519
  TableRenderer.propTypes.showNumericalOrder = _propTypes2.default.bool;
1520
+ TableRenderer.defaultProps.virtualizeRows = false;
1521
+ TableRenderer.propTypes.virtualizeRows = _propTypes2.default.bool;
1060
1522
  return TableRenderer;
1061
1523
  }
1062
1524