import _extends from 'babel-runtime/helpers/extends'; import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties'; import _Object$getPrototypeOf from 'babel-runtime/core-js/object/get-prototype-of'; import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import _createClass from 'babel-runtime/helpers/createClass'; import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn'; import _inherits from 'babel-runtime/helpers/inherits'; import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; import CellMeasurerCacheDecorator from './CellMeasurerCacheDecorator'; import Grid from '../Grid'; var SCROLLBAR_SIZE_BUFFER = 20; /** * Renders 1, 2, or 4 Grids depending on configuration. * A main (body) Grid will always be rendered. * Optionally, 1-2 Grids for sticky header rows will also be rendered. * If no sticky columns, only 1 sticky header Grid will be rendered. * If sticky columns, 2 sticky header Grids will be rendered. */ var MultiGrid = function (_PureComponent) { _inherits(MultiGrid, _PureComponent); function MultiGrid(props, context) { _classCallCheck(this, MultiGrid); var _this = _possibleConstructorReturn(this, (MultiGrid.__proto__ || _Object$getPrototypeOf(MultiGrid)).call(this, props, context)); _this.state = { scrollLeft: 0, scrollTop: 0 }; _this._deferredInvalidateColumnIndex = null; _this._deferredInvalidateRowIndex = null; _this._bottomLeftGridRef = _this._bottomLeftGridRef.bind(_this); _this._bottomRightGridRef = _this._bottomRightGridRef.bind(_this); _this._cellRendererBottomLeftGrid = _this._cellRendererBottomLeftGrid.bind(_this); _this._cellRendererBottomRightGrid = _this._cellRendererBottomRightGrid.bind(_this); _this._cellRendererTopRightGrid = _this._cellRendererTopRightGrid.bind(_this); _this._columnWidthRightGrid = _this._columnWidthRightGrid.bind(_this); _this._onScroll = _this._onScroll.bind(_this); _this._rowHeightBottomGrid = _this._rowHeightBottomGrid.bind(_this); _this._topLeftGridRef = _this._topLeftGridRef.bind(_this); _this._topRightGridRef = _this._topRightGridRef.bind(_this); return _this; } _createClass(MultiGrid, [{ key: 'forceUpdateGrids', value: function forceUpdateGrids() { this._bottomLeftGrid && this._bottomLeftGrid.forceUpdate(); this._bottomRightGrid && this._bottomRightGrid.forceUpdate(); this._topLeftGrid && this._topLeftGrid.forceUpdate(); this._topRightGrid && this._topRightGrid.forceUpdate(); } /** See Grid#invalidateCellSizeAfterRender */ }, { key: 'invalidateCellSizeAfterRender', value: function invalidateCellSizeAfterRender() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$columnIndex = _ref.columnIndex, columnIndex = _ref$columnIndex === undefined ? 0 : _ref$columnIndex, _ref$rowIndex = _ref.rowIndex, rowIndex = _ref$rowIndex === undefined ? 0 : _ref$rowIndex; this._deferredInvalidateColumnIndex = typeof this._deferredInvalidateColumnIndex === 'number' ? Math.min(this._deferredInvalidateColumnIndex, columnIndex) : columnIndex; this._deferredInvalidateRowIndex = typeof this._deferredInvalidateRowIndex === 'number' ? Math.min(this._deferredInvalidateRowIndex, rowIndex) : rowIndex; } /** See Grid#measureAllCells */ }, { key: 'measureAllCells', value: function measureAllCells() { this._bottomLeftGrid && this._bottomLeftGrid.measureAllCells(); this._bottomRightGrid && this._bottomRightGrid.measureAllCells(); this._topLeftGrid && this._topLeftGrid.measureAllCells(); this._topRightGrid && this._topRightGrid.measureAllCells(); } /** See Grid#recomputeGridSize */ }, { key: 'recomputeGridSize', value: function recomputeGridSize() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref2$columnIndex = _ref2.columnIndex, columnIndex = _ref2$columnIndex === undefined ? 0 : _ref2$columnIndex, _ref2$rowIndex = _ref2.rowIndex, rowIndex = _ref2$rowIndex === undefined ? 0 : _ref2$rowIndex; var _props = this.props, fixedColumnCount = _props.fixedColumnCount, fixedRowCount = _props.fixedRowCount; var adjustedColumnIndex = Math.max(0, columnIndex - fixedColumnCount); var adjustedRowIndex = Math.max(0, rowIndex - fixedRowCount); this._bottomLeftGrid && this._bottomLeftGrid.recomputeGridSize({ columnIndex: columnIndex, rowIndex: adjustedRowIndex }); this._bottomRightGrid && this._bottomRightGrid.recomputeGridSize({ columnIndex: adjustedColumnIndex, rowIndex: adjustedRowIndex }); this._topLeftGrid && this._topLeftGrid.recomputeGridSize({ columnIndex: columnIndex, rowIndex: rowIndex }); this._topRightGrid && this._topRightGrid.recomputeGridSize({ columnIndex: adjustedColumnIndex, rowIndex: rowIndex }); this._leftGridWidth = null; this._topGridHeight = null; this._maybeCalculateCachedStyles(null, this.props, null, this.state); } }, { key: 'componentDidMount', value: function componentDidMount() { var _props2 = this.props, scrollLeft = _props2.scrollLeft, scrollTop = _props2.scrollTop; if (scrollLeft > 0 || scrollTop > 0) { var newState = {}; if (scrollLeft > 0) { newState.scrollLeft = scrollLeft; } if (scrollTop > 0) { newState.scrollTop = scrollTop; } this.setState(newState); } this._handleInvalidatedGridSize(); } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps, prevState) { this._handleInvalidatedGridSize(); } }, { key: 'componentWillMount', value: function componentWillMount() { var _props3 = this.props, deferredMeasurementCache = _props3.deferredMeasurementCache, fixedColumnCount = _props3.fixedColumnCount, fixedRowCount = _props3.fixedRowCount; this._maybeCalculateCachedStyles(null, this.props, null, this.state); if (deferredMeasurementCache) { this._deferredMeasurementCacheBottomLeftGrid = fixedRowCount > 0 ? new CellMeasurerCacheDecorator({ cellMeasurerCache: deferredMeasurementCache, columnIndexOffset: 0, rowIndexOffset: fixedRowCount }) : deferredMeasurementCache; this._deferredMeasurementCacheBottomRightGrid = fixedColumnCount > 0 || fixedRowCount > 0 ? new CellMeasurerCacheDecorator({ cellMeasurerCache: deferredMeasurementCache, columnIndexOffset: fixedColumnCount, rowIndexOffset: fixedRowCount }) : deferredMeasurementCache; this._deferredMeasurementCacheTopRightGrid = fixedColumnCount > 0 ? new CellMeasurerCacheDecorator({ cellMeasurerCache: deferredMeasurementCache, columnIndexOffset: fixedColumnCount, rowIndexOffset: 0 }) : deferredMeasurementCache; } } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps, nextState) { var _props4 = this.props, columnWidth = _props4.columnWidth, fixedColumnCount = _props4.fixedColumnCount, fixedRowCount = _props4.fixedRowCount, rowHeight = _props4.rowHeight; if (columnWidth !== nextProps.columnWidth || fixedColumnCount !== nextProps.fixedColumnCount) { this._leftGridWidth = null; } if (fixedRowCount !== nextProps.fixedRowCount || rowHeight !== nextProps.rowHeight) { this._topGridHeight = null; } if (nextProps.scrollLeft !== this.props.scrollLeft || nextProps.scrollTop !== this.props.scrollTop) { var newState = {}; if (nextProps.scrollLeft != null && nextProps.scrollLeft >= 0) { newState.scrollLeft = nextProps.scrollLeft; } if (nextProps.scrollTop != null && nextProps.scrollTop >= 0) { newState.scrollTop = nextProps.scrollTop; } this.setState(newState); } this._maybeCalculateCachedStyles(this.props, nextProps, this.state, nextState); } }, { key: 'render', value: function render() { var _props5 = this.props, onScroll = _props5.onScroll, onSectionRendered = _props5.onSectionRendered, scrollLeftProp = _props5.scrollLeft, scrollToColumn = _props5.scrollToColumn, scrollTopProp = _props5.scrollTop, scrollToRow = _props5.scrollToRow, rest = _objectWithoutProperties(_props5, ['onScroll', 'onSectionRendered', 'scrollLeft', 'scrollToColumn', 'scrollTop', 'scrollToRow']); // Don't render any of our Grids if there are no cells. // This mirrors what Grid does, // And prevents us from recording inaccurage measurements when used with CellMeasurer. if (this.props.width === 0 || this.props.height === 0) { return null; } // scrollTop and scrollLeft props are explicitly filtered out and ignored var _state = this.state, scrollLeft = _state.scrollLeft, scrollTop = _state.scrollTop; return React.createElement( 'div', { style: this._containerOuterStyle }, React.createElement( 'div', { style: this._containerTopStyle }, this._renderTopLeftGrid(rest), this._renderTopRightGrid(_extends({}, rest, { scrollLeft: scrollLeft })) ), React.createElement( 'div', { style: this._containerBottomStyle }, this._renderBottomLeftGrid(_extends({}, rest, { scrollTop: scrollTop })), this._renderBottomRightGrid(_extends({}, rest, { onScroll: onScroll, onSectionRendered: onSectionRendered, scrollLeft: scrollLeft, scrollToColumn: scrollToColumn, scrollToRow: scrollToRow, scrollTop: scrollTop })) ) ); } }, { key: '_bottomLeftGridRef', value: function _bottomLeftGridRef(ref) { this._bottomLeftGrid = ref; } }, { key: '_bottomRightGridRef', value: function _bottomRightGridRef(ref) { this._bottomRightGrid = ref; } }, { key: '_cellRendererBottomLeftGrid', value: function _cellRendererBottomLeftGrid(_ref3) { var rowIndex = _ref3.rowIndex, rest = _objectWithoutProperties(_ref3, ['rowIndex']); var _props6 = this.props, cellRenderer = _props6.cellRenderer, fixedRowCount = _props6.fixedRowCount, rowCount = _props6.rowCount; if (rowIndex === rowCount - fixedRowCount) { return React.createElement('div', { key: rest.key, style: _extends({}, rest.style, { height: SCROLLBAR_SIZE_BUFFER }) }); } else { return cellRenderer(_extends({}, rest, { parent: this, rowIndex: rowIndex + fixedRowCount })); } } }, { key: '_cellRendererBottomRightGrid', value: function _cellRendererBottomRightGrid(_ref4) { var columnIndex = _ref4.columnIndex, rowIndex = _ref4.rowIndex, rest = _objectWithoutProperties(_ref4, ['columnIndex', 'rowIndex']); var _props7 = this.props, cellRenderer = _props7.cellRenderer, fixedColumnCount = _props7.fixedColumnCount, fixedRowCount = _props7.fixedRowCount; return cellRenderer(_extends({}, rest, { columnIndex: columnIndex + fixedColumnCount, parent: this, rowIndex: rowIndex + fixedRowCount })); } }, { key: '_cellRendererTopRightGrid', value: function _cellRendererTopRightGrid(_ref5) { var columnIndex = _ref5.columnIndex, rest = _objectWithoutProperties(_ref5, ['columnIndex']); var _props8 = this.props, cellRenderer = _props8.cellRenderer, columnCount = _props8.columnCount, fixedColumnCount = _props8.fixedColumnCount; if (columnIndex === columnCount - fixedColumnCount) { return React.createElement('div', { key: rest.key, style: _extends({}, rest.style, { width: SCROLLBAR_SIZE_BUFFER }) }); } else { return cellRenderer(_extends({}, rest, { columnIndex: columnIndex + fixedColumnCount, parent: this })); } } }, { key: '_columnWidthRightGrid', value: function _columnWidthRightGrid(_ref6) { var index = _ref6.index; var _props9 = this.props, columnCount = _props9.columnCount, fixedColumnCount = _props9.fixedColumnCount, columnWidth = _props9.columnWidth; // An extra cell is added to the count // This gives the smaller Grid extra room for offset, // In case the main (bottom right) Grid has a scrollbar // If no scrollbar, the extra space is overflow:hidden anyway if (index === columnCount - fixedColumnCount) { return SCROLLBAR_SIZE_BUFFER; } return typeof columnWidth === 'function' ? columnWidth({ index: index + fixedColumnCount }) : columnWidth; } }, { key: '_getBottomGridHeight', value: function _getBottomGridHeight(props) { var height = props.height; var topGridHeight = this._getTopGridHeight(props); return height - topGridHeight; } }, { key: '_getLeftGridWidth', value: function _getLeftGridWidth(props) { var fixedColumnCount = props.fixedColumnCount, columnWidth = props.columnWidth; if (this._leftGridWidth == null) { if (typeof columnWidth === 'function') { var leftGridWidth = 0; for (var index = 0; index < fixedColumnCount; index++) { leftGridWidth += columnWidth({ index: index }); } this._leftGridWidth = leftGridWidth; } else { this._leftGridWidth = columnWidth * fixedColumnCount; } } return this._leftGridWidth; } }, { key: '_getRightGridWidth', value: function _getRightGridWidth(props) { var width = props.width; var leftGridWidth = this._getLeftGridWidth(props); return width - leftGridWidth; } }, { key: '_getTopGridHeight', value: function _getTopGridHeight(props) { var fixedRowCount = props.fixedRowCount, rowHeight = props.rowHeight; if (this._topGridHeight == null) { if (typeof rowHeight === 'function') { var topGridHeight = 0; for (var index = 0; index < fixedRowCount; index++) { topGridHeight += rowHeight({ index: index }); } this._topGridHeight = topGridHeight; } else { this._topGridHeight = rowHeight * fixedRowCount; } } return this._topGridHeight; } }, { key: '_handleInvalidatedGridSize', value: function _handleInvalidatedGridSize() { if (typeof this._deferredInvalidateColumnIndex === 'number') { var columnIndex = this._deferredInvalidateColumnIndex; var rowIndex = this._deferredInvalidateRowIndex; this._deferredInvalidateColumnIndex = null; this._deferredInvalidateRowIndex = null; this.recomputeGridSize({ columnIndex: columnIndex, rowIndex: rowIndex }); this.forceUpdate(); } } /** * Avoid recreating inline styles each render; this bypasses Grid's shallowCompare. * This method recalculates styles only when specific props change. */ }, { key: '_maybeCalculateCachedStyles', value: function _maybeCalculateCachedStyles(prevProps, props, prevState, state) { var columnWidth = props.columnWidth, height = props.height, fixedColumnCount = props.fixedColumnCount, fixedRowCount = props.fixedRowCount, rowHeight = props.rowHeight, style = props.style, styleBottomLeftGrid = props.styleBottomLeftGrid, styleBottomRightGrid = props.styleBottomRightGrid, styleTopLeftGrid = props.styleTopLeftGrid, styleTopRightGrid = props.styleTopRightGrid, width = props.width; var firstRender = !prevProps; var sizeChange = firstRender || height !== prevProps.height || width !== prevProps.width; var leftSizeChange = firstRender || columnWidth !== prevProps.columnWidth || fixedColumnCount !== prevProps.fixedColumnCount; var topSizeChange = firstRender || fixedRowCount !== prevProps.fixedRowCount || rowHeight !== prevProps.rowHeight; if (firstRender || sizeChange || style !== prevProps.style) { this._containerOuterStyle = _extends({ height: height, overflow: 'visible', // Let :focus outline show through width: width }, style); } if (firstRender || sizeChange || topSizeChange) { this._containerTopStyle = { height: this._getTopGridHeight(props), position: 'relative', width: width }; this._containerBottomStyle = { height: height - this._getTopGridHeight(props), overflow: 'visible', // Let :focus outline show through position: 'relative', width: width }; } if (firstRender || styleBottomLeftGrid !== prevProps.styleBottomLeftGrid) { this._bottomLeftGridStyle = _extends({ left: 0, overflowX: 'hidden', overflowY: 'hidden', position: 'absolute' }, styleBottomLeftGrid); } if (firstRender || leftSizeChange || styleBottomRightGrid !== prevProps.styleBottomRightGrid) { this._bottomRightGridStyle = _extends({ left: this._getLeftGridWidth(props), position: 'absolute' }, styleBottomRightGrid); } if (firstRender || styleTopLeftGrid !== prevProps.styleTopLeftGrid) { this._topLeftGridStyle = _extends({ left: 0, overflowX: 'hidden', overflowY: 'hidden', position: 'absolute', top: 0 }, styleTopLeftGrid); } if (firstRender || leftSizeChange || styleTopRightGrid !== prevProps.styleTopRightGrid) { this._topRightGridStyle = _extends({ left: this._getLeftGridWidth(props), overflowX: 'hidden', overflowY: 'hidden', position: 'absolute', top: 0 }, styleTopRightGrid); } } }, { key: '_onScroll', value: function _onScroll(scrollInfo) { var scrollLeft = scrollInfo.scrollLeft, scrollTop = scrollInfo.scrollTop; this.setState({ scrollLeft: scrollLeft, scrollTop: scrollTop }); var onScroll = this.props.onScroll; if (onScroll) { onScroll(scrollInfo); } } }, { key: '_renderBottomLeftGrid', value: function _renderBottomLeftGrid(props) { var fixedColumnCount = props.fixedColumnCount, fixedRowCount = props.fixedRowCount, rowCount = props.rowCount, scrollTop = props.scrollTop; if (!fixedColumnCount) { return null; } return React.createElement(Grid, _extends({}, props, { cellRenderer: this._cellRendererBottomLeftGrid, columnCount: fixedColumnCount, deferredMeasurementCache: this._deferredMeasurementCacheBottomLeftGrid, height: this._getBottomGridHeight(props), ref: this._bottomLeftGridRef, rowCount: Math.max(0, rowCount - fixedRowCount) + 1 /* See _rowHeightBottomGrid */, rowHeight: this._rowHeightBottomGrid, scrollTop: scrollTop, style: this._bottomLeftGridStyle, tabIndex: null, width: this._getLeftGridWidth(props) })); } }, { key: '_renderBottomRightGrid', value: function _renderBottomRightGrid(props) { var columnCount = props.columnCount, fixedColumnCount = props.fixedColumnCount, fixedRowCount = props.fixedRowCount, rowCount = props.rowCount, scrollToColumn = props.scrollToColumn, scrollToRow = props.scrollToRow; return React.createElement(Grid, _extends({}, props, { cellRenderer: this._cellRendererBottomRightGrid, columnCount: Math.max(0, columnCount - fixedColumnCount), columnWidth: this._columnWidthRightGrid, deferredMeasurementCache: this._deferredMeasurementCacheBottomRightGrid, height: this._getBottomGridHeight(props), onScroll: this._onScroll, ref: this._bottomRightGridRef, rowCount: Math.max(0, rowCount - fixedRowCount), rowHeight: this._rowHeightBottomGrid, scrollToColumn: scrollToColumn - fixedColumnCount, scrollToRow: scrollToRow - fixedRowCount, style: this._bottomRightGridStyle, width: this._getRightGridWidth(props) })); } }, { key: '_renderTopLeftGrid', value: function _renderTopLeftGrid(props) { var fixedColumnCount = props.fixedColumnCount, fixedRowCount = props.fixedRowCount; if (!fixedColumnCount || !fixedRowCount) { return null; } return React.createElement(Grid, _extends({}, props, { columnCount: fixedColumnCount, height: this._getTopGridHeight(props), ref: this._topLeftGridRef, rowCount: fixedRowCount, style: this._topLeftGridStyle, tabIndex: null, width: this._getLeftGridWidth(props) })); } }, { key: '_renderTopRightGrid', value: function _renderTopRightGrid(props) { var columnCount = props.columnCount, fixedColumnCount = props.fixedColumnCount, fixedRowCount = props.fixedRowCount, scrollLeft = props.scrollLeft; if (!fixedRowCount) { return null; } return React.createElement(Grid, _extends({}, props, { cellRenderer: this._cellRendererTopRightGrid, columnCount: Math.max(0, columnCount - fixedColumnCount) + 1 /* See _columnWidthRightGrid */, columnWidth: this._columnWidthRightGrid, deferredMeasurementCache: this._deferredMeasurementCacheTopRightGrid, height: this._getTopGridHeight(props), ref: this._topRightGridRef, rowCount: fixedRowCount, scrollLeft: scrollLeft, style: this._topRightGridStyle, tabIndex: null, width: this._getRightGridWidth(props) })); } }, { key: '_rowHeightBottomGrid', value: function _rowHeightBottomGrid(_ref7) { var index = _ref7.index; var _props10 = this.props, fixedRowCount = _props10.fixedRowCount, rowCount = _props10.rowCount, rowHeight = _props10.rowHeight; // An extra cell is added to the count // This gives the smaller Grid extra room for offset, // In case the main (bottom right) Grid has a scrollbar // If no scrollbar, the extra space is overflow:hidden anyway if (index === rowCount - fixedRowCount) { return SCROLLBAR_SIZE_BUFFER; } return typeof rowHeight === 'function' ? rowHeight({ index: index + fixedRowCount }) : rowHeight; } }, { key: '_topLeftGridRef', value: function _topLeftGridRef(ref) { this._topLeftGrid = ref; } }, { key: '_topRightGridRef', value: function _topRightGridRef(ref) { this._topRightGrid = ref; } }]); return MultiGrid; }(PureComponent); MultiGrid.defaultProps = { fixedColumnCount: 0, fixedRowCount: 0, style: {}, styleBottomLeftGrid: {}, styleBottomRightGrid: {}, styleTopLeftGrid: {}, styleTopRightGrid: {} }; export default MultiGrid; process.env.NODE_ENV !== "production" ? MultiGrid.propTypes = { fixedColumnCount: PropTypes.number.isRequired, fixedRowCount: PropTypes.number.isRequired, style: PropTypes.object.isRequired, styleBottomLeftGrid: PropTypes.object.isRequired, styleBottomRightGrid: PropTypes.object.isRequired, styleTopLeftGrid: PropTypes.object.isRequired, styleTopRightGrid: PropTypes.object.isRequired } : void 0;