react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
1,020 lines (858 loc) • 41.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DEFAULT_SCROLLING_RESET_TIME_INTERVAL = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
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; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _calculateSizeAndPositionDataAndUpdateScrollOffset = require('./utils/calculateSizeAndPositionDataAndUpdateScrollOffset');
var _calculateSizeAndPositionDataAndUpdateScrollOffset2 = _interopRequireDefault(_calculateSizeAndPositionDataAndUpdateScrollOffset);
var _ScalingCellSizeAndPositionManager = require('./utils/ScalingCellSizeAndPositionManager');
var _ScalingCellSizeAndPositionManager2 = _interopRequireDefault(_ScalingCellSizeAndPositionManager);
var _createCallbackMemoizer = require('../utils/createCallbackMemoizer');
var _createCallbackMemoizer2 = _interopRequireDefault(_createCallbackMemoizer);
var _getOverscanIndices = require('./utils/getOverscanIndices');
var _getOverscanIndices2 = _interopRequireDefault(_getOverscanIndices);
var _scrollbarSize = require('dom-helpers/util/scrollbarSize');
var _scrollbarSize2 = _interopRequireDefault(_scrollbarSize);
var _raf = require('raf');
var _raf2 = _interopRequireDefault(_raf);
var _reactAddonsShallowCompare = require('react-addons-shallow-compare');
var _reactAddonsShallowCompare2 = _interopRequireDefault(_reactAddonsShallowCompare);
var _updateScrollIndexHelper = require('./utils/updateScrollIndexHelper');
var _updateScrollIndexHelper2 = _interopRequireDefault(_updateScrollIndexHelper);
var _defaultCellRangeRenderer = require('./defaultCellRangeRenderer');
var _defaultCellRangeRenderer2 = _interopRequireDefault(_defaultCellRangeRenderer);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
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; }
/**
* Specifies the number of miliseconds during which to disable pointer events while a scroll is in progress.
* This improves performance and makes scrolling smoother.
*/
var DEFAULT_SCROLLING_RESET_TIME_INTERVAL = exports.DEFAULT_SCROLLING_RESET_TIME_INTERVAL = 150;
/**
* Controls whether the Grid updates the DOM element's scrollLeft/scrollTop based on the current state or just observes it.
* This prevents Grid from interrupting mouse-wheel animations (see issue #2).
*/
var SCROLL_POSITION_CHANGE_REASONS = {
OBSERVED: 'observed',
REQUESTED: 'requested'
};
/**
* Renders tabular data with virtualization along the vertical and horizontal axes.
* Row heights and column widths must be known ahead of time and specified as properties.
*/
var Grid = function (_Component) {
_inherits(Grid, _Component);
function Grid(props, context) {
_classCallCheck(this, Grid);
var _this = _possibleConstructorReturn(this, (Grid.__proto__ || Object.getPrototypeOf(Grid)).call(this, props, context));
_this.state = {
isScrolling: false,
scrollDirectionHorizontal: _getOverscanIndices.SCROLL_DIRECTION_FIXED,
scrollDirectionVertical: _getOverscanIndices.SCROLL_DIRECTION_FIXED,
scrollLeft: 0,
scrollTop: 0
};
// Invokes onSectionRendered callback only when start/stop row or column indices change
_this._onGridRenderedMemoizer = (0, _createCallbackMemoizer2.default)();
_this._onScrollMemoizer = (0, _createCallbackMemoizer2.default)(false);
// Bind functions to instance so they don't lose context when passed around
_this._enablePointerEventsAfterDelayCallback = _this._enablePointerEventsAfterDelayCallback.bind(_this);
_this._invokeOnGridRenderedHelper = _this._invokeOnGridRenderedHelper.bind(_this);
_this._onScroll = _this._onScroll.bind(_this);
_this._setNextStateCallback = _this._setNextStateCallback.bind(_this);
_this._updateScrollLeftForScrollToColumn = _this._updateScrollLeftForScrollToColumn.bind(_this);
_this._updateScrollTopForScrollToRow = _this._updateScrollTopForScrollToRow.bind(_this);
_this._columnWidthGetter = _this._wrapSizeGetter(props.columnWidth);
_this._rowHeightGetter = _this._wrapSizeGetter(props.rowHeight);
_this._columnSizeAndPositionManager = new _ScalingCellSizeAndPositionManager2.default({
cellCount: props.columnCount,
cellSizeGetter: function cellSizeGetter(index) {
return _this._columnWidthGetter(index);
},
estimatedCellSize: _this._getEstimatedColumnSize(props)
});
_this._rowSizeAndPositionManager = new _ScalingCellSizeAndPositionManager2.default({
cellCount: props.rowCount,
cellSizeGetter: function cellSizeGetter(index) {
return _this._rowHeightGetter(index);
},
estimatedCellSize: _this._getEstimatedRowSize(props)
});
// See defaultCellRangeRenderer() for more information on the usage of this cache
_this._cellCache = {};
return _this;
}
/**
* Pre-measure all columns and rows in a Grid.
* Typically cells are only measured as needed and estimated sizes are used for cells that have not yet been measured.
* This method ensures that the next call to getTotalSize() returns an exact size (as opposed to just an estimated one).
*/
_createClass(Grid, [{
key: 'measureAllCells',
value: function measureAllCells() {
var _props = this.props;
var columnCount = _props.columnCount;
var rowCount = _props.rowCount;
this._columnSizeAndPositionManager.getSizeAndPositionOfCell(columnCount - 1);
this._rowSizeAndPositionManager.getSizeAndPositionOfCell(rowCount - 1);
}
/**
* Forced recompute of row heights and column widths.
* This function should be called if dynamic column or row sizes have changed but nothing else has.
* Since Grid only receives :columnCount and :rowCount it has no way of detecting when the underlying data changes.
*/
}, {
key: 'recomputeGridSize',
value: function recomputeGridSize() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$columnIndex = _ref.columnIndex;
var columnIndex = _ref$columnIndex === undefined ? 0 : _ref$columnIndex;
var _ref$rowIndex = _ref.rowIndex;
var rowIndex = _ref$rowIndex === undefined ? 0 : _ref$rowIndex;
this._columnSizeAndPositionManager.resetCell(columnIndex);
this._rowSizeAndPositionManager.resetCell(rowIndex);
// Clear cell cache in case we are scrolling;
// Invalid row heights likely mean invalid cached content as well.
this._cellCache = {};
this.forceUpdate();
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _props2 = this.props;
var scrollLeft = _props2.scrollLeft;
var scrollToColumn = _props2.scrollToColumn;
var scrollTop = _props2.scrollTop;
var scrollToRow = _props2.scrollToRow;
// If this component was first rendered server-side, scrollbar size will be undefined.
// In that event we need to remeasure.
if (!this._scrollbarSizeMeasured) {
this._scrollbarSize = (0, _scrollbarSize2.default)();
this._scrollbarSizeMeasured = true;
this.setState({});
}
if (scrollLeft >= 0 || scrollTop >= 0) {
this._setScrollPosition({ scrollLeft: scrollLeft, scrollTop: scrollTop });
}
if (scrollToColumn >= 0 || scrollToRow >= 0) {
this._updateScrollLeftForScrollToColumn();
this._updateScrollTopForScrollToRow();
}
// Update onRowsRendered callback
this._invokeOnGridRenderedHelper();
// Initialize onScroll callback
this._invokeOnScrollMemoizer({
scrollLeft: scrollLeft || 0,
scrollTop: scrollTop || 0,
totalColumnsWidth: this._columnSizeAndPositionManager.getTotalSize(),
totalRowsHeight: this._rowSizeAndPositionManager.getTotalSize()
});
}
/**
* @private
* This method updates scrollLeft/scrollTop in state for the following conditions:
* 1) New scroll-to-cell props have been set
*/
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var _this2 = this;
var _props3 = this.props;
var autoHeight = _props3.autoHeight;
var columnCount = _props3.columnCount;
var height = _props3.height;
var rowCount = _props3.rowCount;
var scrollToAlignment = _props3.scrollToAlignment;
var scrollToColumn = _props3.scrollToColumn;
var scrollToRow = _props3.scrollToRow;
var width = _props3.width;
var _state = this.state;
var scrollLeft = _state.scrollLeft;
var scrollPositionChangeReason = _state.scrollPositionChangeReason;
var scrollTop = _state.scrollTop;
// Handle edge case where column or row count has only just increased over 0.
// In this case we may have to restore a previously-specified scroll offset.
// For more info see bvaughn/react-virtualized/issues/218
var columnOrRowCountJustIncreasedFromZero = columnCount > 0 && prevProps.columnCount === 0 || rowCount > 0 && prevProps.rowCount === 0;
// Make sure requested changes to :scrollLeft or :scrollTop get applied.
// Assigning to scrollLeft/scrollTop tells the browser to interrupt any running scroll animations,
// And to discard any pending async changes to the scroll position that may have happened in the meantime (e.g. on a separate scrolling thread).
// So we only set these when we require an adjustment of the scroll position.
// See issue #2 for more information.
if (scrollPositionChangeReason === SCROLL_POSITION_CHANGE_REASONS.REQUESTED) {
if (scrollLeft >= 0 && (scrollLeft !== prevState.scrollLeft && scrollLeft !== this._scrollingContainer.scrollLeft || columnOrRowCountJustIncreasedFromZero)) {
this._scrollingContainer.scrollLeft = scrollLeft;
}
// @TRICKY :autoHeight property instructs Grid to leave :scrollTop management to an external HOC (eg WindowScroller).
// In this case we should avoid checking scrollingContainer.scrollTop since it forces layout/flow.
if (!autoHeight && scrollTop >= 0 && (scrollTop !== prevState.scrollTop && scrollTop !== this._scrollingContainer.scrollTop || columnOrRowCountJustIncreasedFromZero)) {
this._scrollingContainer.scrollTop = scrollTop;
}
}
// Update scroll offsets if the current :scrollToColumn or :scrollToRow values requires it
// @TODO Do we also need this check or can the one in componentWillUpdate() suffice?
(0, _updateScrollIndexHelper2.default)({
cellSizeAndPositionManager: this._columnSizeAndPositionManager,
previousCellsCount: prevProps.columnCount,
previousCellSize: prevProps.columnWidth,
previousScrollToAlignment: prevProps.scrollToAlignment,
previousScrollToIndex: prevProps.scrollToColumn,
previousSize: prevProps.width,
scrollOffset: scrollLeft,
scrollToAlignment: scrollToAlignment,
scrollToIndex: scrollToColumn,
size: width,
updateScrollIndexCallback: function updateScrollIndexCallback(scrollToColumn) {
return _this2._updateScrollLeftForScrollToColumn(_extends({}, _this2.props, { scrollToColumn: scrollToColumn }));
}
});
(0, _updateScrollIndexHelper2.default)({
cellSizeAndPositionManager: this._rowSizeAndPositionManager,
previousCellsCount: prevProps.rowCount,
previousCellSize: prevProps.rowHeight,
previousScrollToAlignment: prevProps.scrollToAlignment,
previousScrollToIndex: prevProps.scrollToRow,
previousSize: prevProps.height,
scrollOffset: scrollTop,
scrollToAlignment: scrollToAlignment,
scrollToIndex: scrollToRow,
size: height,
updateScrollIndexCallback: function updateScrollIndexCallback(scrollToRow) {
return _this2._updateScrollTopForScrollToRow(_extends({}, _this2.props, { scrollToRow: scrollToRow }));
}
});
// Update onRowsRendered callback if start/stop indices have changed
this._invokeOnGridRenderedHelper();
}
}, {
key: 'componentWillMount',
value: function componentWillMount() {
// If this component is being rendered server-side, getScrollbarSize() will return undefined.
// We handle this case in componentDidMount()
this._scrollbarSize = (0, _scrollbarSize2.default)();
if (this._scrollbarSize === undefined) {
this._scrollbarSizeMeasured = false;
this._scrollbarSize = 0;
} else {
this._scrollbarSizeMeasured = true;
}
this._calculateChildrenToRender();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (this._disablePointerEventsTimeoutId) {
clearTimeout(this._disablePointerEventsTimeoutId);
}
if (this._setNextStateAnimationFrameId) {
_raf2.default.cancel(this._setNextStateAnimationFrameId);
}
}
/**
* @private
* This method updates scrollLeft/scrollTop in state for the following conditions:
* 1) Empty content (0 rows or columns)
* 2) New scroll props overriding the current state
* 3) Cells-count or cells-size has changed, making previous scroll offsets invalid
*/
}, {
key: 'componentWillUpdate',
value: function componentWillUpdate(nextProps, nextState) {
var _this3 = this;
if (nextProps.columnCount === 0 && nextState.scrollLeft !== 0 || nextProps.rowCount === 0 && nextState.scrollTop !== 0) {
this._setScrollPosition({
scrollLeft: 0,
scrollTop: 0
});
} else if (nextProps.scrollLeft !== this.props.scrollLeft || nextProps.scrollTop !== this.props.scrollTop) {
this._setScrollPosition({
scrollLeft: nextProps.scrollLeft,
scrollTop: nextProps.scrollTop
});
}
this._columnWidthGetter = this._wrapSizeGetter(nextProps.columnWidth);
this._rowHeightGetter = this._wrapSizeGetter(nextProps.rowHeight);
this._columnSizeAndPositionManager.configure({
cellCount: nextProps.columnCount,
estimatedCellSize: this._getEstimatedColumnSize(nextProps)
});
this._rowSizeAndPositionManager.configure({
cellCount: nextProps.rowCount,
estimatedCellSize: this._getEstimatedRowSize(nextProps)
});
// Update scroll offsets if the size or number of cells have changed, invalidating the previous value
(0, _calculateSizeAndPositionDataAndUpdateScrollOffset2.default)({
cellCount: this.props.columnCount,
cellSize: this.props.columnWidth,
computeMetadataCallback: function computeMetadataCallback() {
return _this3._columnSizeAndPositionManager.resetCell(0);
},
computeMetadataCallbackProps: nextProps,
nextCellsCount: nextProps.columnCount,
nextCellSize: nextProps.columnWidth,
nextScrollToIndex: nextProps.scrollToColumn,
scrollToIndex: this.props.scrollToColumn,
updateScrollOffsetForScrollToIndex: function updateScrollOffsetForScrollToIndex() {
return _this3._updateScrollLeftForScrollToColumn(nextProps, nextState);
}
});
(0, _calculateSizeAndPositionDataAndUpdateScrollOffset2.default)({
cellCount: this.props.rowCount,
cellSize: this.props.rowHeight,
computeMetadataCallback: function computeMetadataCallback() {
return _this3._rowSizeAndPositionManager.resetCell(0);
},
computeMetadataCallbackProps: nextProps,
nextCellsCount: nextProps.rowCount,
nextCellSize: nextProps.rowHeight,
nextScrollToIndex: nextProps.scrollToRow,
scrollToIndex: this.props.scrollToRow,
updateScrollOffsetForScrollToIndex: function updateScrollOffsetForScrollToIndex() {
return _this3._updateScrollTopForScrollToRow(nextProps, nextState);
}
});
this._calculateChildrenToRender(nextProps, nextState);
}
}, {
key: 'render',
value: function render() {
var _this4 = this;
var _props4 = this.props;
var autoContainerWidth = _props4.autoContainerWidth;
var autoHeight = _props4.autoHeight;
var className = _props4.className;
var height = _props4.height;
var noContentRenderer = _props4.noContentRenderer;
var style = _props4.style;
var tabIndex = _props4.tabIndex;
var width = _props4.width;
var isScrolling = this.state.isScrolling;
var gridStyle = {
height: autoHeight ? 'auto' : height,
width: width
};
var totalColumnsWidth = this._columnSizeAndPositionManager.getTotalSize();
var totalRowsHeight = this._rowSizeAndPositionManager.getTotalSize();
// Force browser to hide scrollbars when we know they aren't necessary.
// Otherwise once scrollbars appear they may not disappear again.
// For more info see issue #116
var verticalScrollBarSize = totalRowsHeight > height ? this._scrollbarSize : 0;
var horizontalScrollBarSize = totalColumnsWidth > width ? this._scrollbarSize : 0;
// Also explicitly init styles to 'auto' if scrollbars are required.
// This works around an obscure edge case where external CSS styles have not yet been loaded,
// But an initial scroll index of offset is set as an external prop.
// Without this style, Grid would render the correct range of cells but would NOT update its internal offset.
// This was originally reported via clauderic/react-infinite-calendar/issues/23
gridStyle.overflowX = totalColumnsWidth + verticalScrollBarSize <= width ? 'hidden' : 'auto';
gridStyle.overflowY = totalRowsHeight + horizontalScrollBarSize <= height ? 'hidden' : 'auto';
var childrenToDisplay = this._childrenToDisplay;
var showNoContentRenderer = childrenToDisplay.length === 0 && height > 0 && width > 0;
return _react2.default.createElement(
'div',
{
ref: function ref(_ref2) {
_this4._scrollingContainer = _ref2;
},
'aria-label': this.props['aria-label'],
className: (0, _classnames2.default)('ReactVirtualized__Grid', className),
onScroll: this._onScroll,
role: 'grid',
style: _extends({}, gridStyle, style),
tabIndex: tabIndex
},
childrenToDisplay.length > 0 && _react2.default.createElement(
'div',
{
className: 'ReactVirtualized__Grid__innerScrollContainer',
style: {
width: autoContainerWidth ? 'auto' : totalColumnsWidth,
height: totalRowsHeight,
maxWidth: totalColumnsWidth,
maxHeight: totalRowsHeight,
pointerEvents: isScrolling ? 'none' : ''
}
},
childrenToDisplay
),
showNoContentRenderer && noContentRenderer()
);
}
}, {
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate(nextProps, nextState) {
return (0, _reactAddonsShallowCompare2.default)(this, nextProps, nextState);
}
/* ---------------------------- Helper methods ---------------------------- */
}, {
key: '_calculateChildrenToRender',
value: function _calculateChildrenToRender() {
var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
var state = arguments.length <= 1 || arguments[1] === undefined ? this.state : arguments[1];
var cellRenderer = props.cellRenderer;
var cellRangeRenderer = props.cellRangeRenderer;
var columnCount = props.columnCount;
var height = props.height;
var overscanColumnCount = props.overscanColumnCount;
var overscanRowCount = props.overscanRowCount;
var rowCount = props.rowCount;
var width = props.width;
var isScrolling = state.isScrolling;
var scrollDirectionHorizontal = state.scrollDirectionHorizontal;
var scrollDirectionVertical = state.scrollDirectionVertical;
var scrollLeft = state.scrollLeft;
var scrollTop = state.scrollTop;
this._childrenToDisplay = [];
// Render only enough columns and rows to cover the visible area of the grid.
if (height > 0 && width > 0) {
var visibleColumnIndices = this._columnSizeAndPositionManager.getVisibleCellRange({
containerSize: width,
offset: scrollLeft
});
var visibleRowIndices = this._rowSizeAndPositionManager.getVisibleCellRange({
containerSize: height,
offset: scrollTop
});
var horizontalOffsetAdjustment = this._columnSizeAndPositionManager.getOffsetAdjustment({
containerSize: width,
offset: scrollLeft
});
var verticalOffsetAdjustment = this._rowSizeAndPositionManager.getOffsetAdjustment({
containerSize: height,
offset: scrollTop
});
// Store for _invokeOnGridRenderedHelper()
this._renderedColumnStartIndex = visibleColumnIndices.start;
this._renderedColumnStopIndex = visibleColumnIndices.stop;
this._renderedRowStartIndex = visibleRowIndices.start;
this._renderedRowStopIndex = visibleRowIndices.stop;
var overscanColumnIndices = (0, _getOverscanIndices2.default)({
cellCount: columnCount,
overscanCellsCount: overscanColumnCount,
scrollDirection: scrollDirectionHorizontal,
startIndex: this._renderedColumnStartIndex,
stopIndex: this._renderedColumnStopIndex
});
var overscanRowIndices = (0, _getOverscanIndices2.default)({
cellCount: rowCount,
overscanCellsCount: overscanRowCount,
scrollDirection: scrollDirectionVertical,
startIndex: this._renderedRowStartIndex,
stopIndex: this._renderedRowStopIndex
});
// Store for _invokeOnGridRenderedHelper()
this._columnStartIndex = overscanColumnIndices.overscanStartIndex;
this._columnStopIndex = overscanColumnIndices.overscanStopIndex;
this._rowStartIndex = overscanRowIndices.overscanStartIndex;
this._rowStopIndex = overscanRowIndices.overscanStopIndex;
this._childrenToDisplay = cellRangeRenderer({
cellCache: this._cellCache,
cellRenderer: cellRenderer,
columnSizeAndPositionManager: this._columnSizeAndPositionManager,
columnStartIndex: this._columnStartIndex,
columnStopIndex: this._columnStopIndex,
horizontalOffsetAdjustment: horizontalOffsetAdjustment,
isScrolling: isScrolling,
rowSizeAndPositionManager: this._rowSizeAndPositionManager,
rowStartIndex: this._rowStartIndex,
rowStopIndex: this._rowStopIndex,
scrollLeft: scrollLeft,
scrollTop: scrollTop,
verticalOffsetAdjustment: verticalOffsetAdjustment
});
}
}
/**
* Sets an :isScrolling flag for a small window of time.
* This flag is used to disable pointer events on the scrollable portion of the Grid.
* This prevents jerky/stuttery mouse-wheel scrolling.
*/
}, {
key: '_enablePointerEventsAfterDelay',
value: function _enablePointerEventsAfterDelay() {
var scrollingResetTimeInterval = this.props.scrollingResetTimeInterval;
if (this._disablePointerEventsTimeoutId) {
clearTimeout(this._disablePointerEventsTimeoutId);
}
this._disablePointerEventsTimeoutId = setTimeout(this._enablePointerEventsAfterDelayCallback, scrollingResetTimeInterval);
}
}, {
key: '_enablePointerEventsAfterDelayCallback',
value: function _enablePointerEventsAfterDelayCallback() {
this._disablePointerEventsTimeoutId = null;
// Throw away cell cache once scrolling is complete
this._cellCache = {};
this.setState({
isScrolling: false,
scrollDirectionHorizontal: _getOverscanIndices.SCROLL_DIRECTION_FIXED,
scrollDirectionVertical: _getOverscanIndices.SCROLL_DIRECTION_FIXED
});
}
}, {
key: '_getEstimatedColumnSize',
value: function _getEstimatedColumnSize(props) {
return typeof props.columnWidth === 'number' ? props.columnWidth : props.estimatedColumnSize;
}
}, {
key: '_getEstimatedRowSize',
value: function _getEstimatedRowSize(props) {
return typeof props.rowHeight === 'number' ? props.rowHeight : props.estimatedRowSize;
}
}, {
key: '_invokeOnGridRenderedHelper',
value: function _invokeOnGridRenderedHelper() {
var onSectionRendered = this.props.onSectionRendered;
this._onGridRenderedMemoizer({
callback: onSectionRendered,
indices: {
columnOverscanStartIndex: this._columnStartIndex,
columnOverscanStopIndex: this._columnStopIndex,
columnStartIndex: this._renderedColumnStartIndex,
columnStopIndex: this._renderedColumnStopIndex,
rowOverscanStartIndex: this._rowStartIndex,
rowOverscanStopIndex: this._rowStopIndex,
rowStartIndex: this._renderedRowStartIndex,
rowStopIndex: this._renderedRowStopIndex
}
});
}
}, {
key: '_invokeOnScrollMemoizer',
value: function _invokeOnScrollMemoizer(_ref3) {
var _this5 = this;
var scrollLeft = _ref3.scrollLeft;
var scrollTop = _ref3.scrollTop;
var totalColumnsWidth = _ref3.totalColumnsWidth;
var totalRowsHeight = _ref3.totalRowsHeight;
this._onScrollMemoizer({
callback: function callback(_ref4) {
var scrollLeft = _ref4.scrollLeft;
var scrollTop = _ref4.scrollTop;
var _props5 = _this5.props;
var height = _props5.height;
var onScroll = _props5.onScroll;
var width = _props5.width;
onScroll({
clientHeight: height,
clientWidth: width,
scrollHeight: totalRowsHeight,
scrollLeft: scrollLeft,
scrollTop: scrollTop,
scrollWidth: totalColumnsWidth
});
},
indices: {
scrollLeft: scrollLeft,
scrollTop: scrollTop
}
});
}
/**
* Updates the state during the next animation frame.
* Use this method to avoid multiple renders in a small span of time.
* This helps performance for bursty events (like onScroll).
*/
}, {
key: '_setNextState',
value: function _setNextState(state) {
this._nextState = state;
if (!this._setNextStateAnimationFrameId) {
this._setNextStateAnimationFrameId = (0, _raf2.default)(this._setNextStateCallback);
}
}
}, {
key: '_setNextStateCallback',
value: function _setNextStateCallback() {
var state = this._nextState;
this._setNextStateAnimationFrameId = null;
this._nextState = null;
this.setState(state);
}
}, {
key: '_setScrollPosition',
value: function _setScrollPosition(_ref5) {
var scrollLeft = _ref5.scrollLeft;
var scrollTop = _ref5.scrollTop;
var newState = {
scrollPositionChangeReason: SCROLL_POSITION_CHANGE_REASONS.REQUESTED
};
if (scrollLeft >= 0) {
newState.scrollLeft = scrollLeft;
}
if (scrollTop >= 0) {
newState.scrollTop = scrollTop;
}
if (scrollLeft >= 0 && scrollLeft !== this.state.scrollLeft || scrollTop >= 0 && scrollTop !== this.state.scrollTop) {
this.setState(newState);
}
}
}, {
key: '_wrapPropertyGetter',
value: function _wrapPropertyGetter(value) {
return value instanceof Function ? value : function () {
return value;
};
}
}, {
key: '_wrapSizeGetter',
value: function _wrapSizeGetter(size) {
return this._wrapPropertyGetter(size);
}
}, {
key: '_updateScrollLeftForScrollToColumn',
value: function _updateScrollLeftForScrollToColumn() {
var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
var state = arguments.length <= 1 || arguments[1] === undefined ? this.state : arguments[1];
var columnCount = props.columnCount;
var scrollToAlignment = props.scrollToAlignment;
var scrollToColumn = props.scrollToColumn;
var width = props.width;
var scrollLeft = state.scrollLeft;
if (scrollToColumn >= 0 && columnCount > 0) {
var targetIndex = Math.max(0, Math.min(columnCount - 1, scrollToColumn));
var calculatedScrollLeft = this._columnSizeAndPositionManager.getUpdatedOffsetForIndex({
align: scrollToAlignment,
containerSize: width,
currentOffset: scrollLeft,
targetIndex: targetIndex
});
if (scrollLeft !== calculatedScrollLeft) {
this._setScrollPosition({
scrollLeft: calculatedScrollLeft
});
}
}
}
}, {
key: '_updateScrollTopForScrollToRow',
value: function _updateScrollTopForScrollToRow() {
var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
var state = arguments.length <= 1 || arguments[1] === undefined ? this.state : arguments[1];
var height = props.height;
var rowCount = props.rowCount;
var scrollToAlignment = props.scrollToAlignment;
var scrollToRow = props.scrollToRow;
var scrollTop = state.scrollTop;
if (scrollToRow >= 0 && rowCount > 0) {
var targetIndex = Math.max(0, Math.min(rowCount - 1, scrollToRow));
var calculatedScrollTop = this._rowSizeAndPositionManager.getUpdatedOffsetForIndex({
align: scrollToAlignment,
containerSize: height,
currentOffset: scrollTop,
targetIndex: targetIndex
});
if (scrollTop !== calculatedScrollTop) {
this._setScrollPosition({
scrollTop: calculatedScrollTop
});
}
}
}
}, {
key: '_onScroll',
value: function _onScroll(event) {
// In certain edge-cases React dispatches an onScroll event with an invalid target.scrollLeft / target.scrollTop.
// This invalid event can be detected by comparing event.target to this component's scrollable DOM element.
// See issue #404 for more information.
if (event.target !== this._scrollingContainer) {
return;
}
// Prevent pointer events from interrupting a smooth scroll
this._enablePointerEventsAfterDelay();
// When this component is shrunk drastically, React dispatches a series of back-to-back scroll events,
// Gradually converging on a scrollTop that is within the bounds of the new, smaller height.
// This causes a series of rapid renders that is slow for long lists.
// We can avoid that by doing some simple bounds checking to ensure that scrollTop never exceeds the total height.
var _props6 = this.props;
var height = _props6.height;
var width = _props6.width;
var scrollbarSize = this._scrollbarSize;
var totalRowsHeight = this._rowSizeAndPositionManager.getTotalSize();
var totalColumnsWidth = this._columnSizeAndPositionManager.getTotalSize();
var scrollLeft = Math.min(Math.max(0, totalColumnsWidth - width + scrollbarSize), event.target.scrollLeft);
var scrollTop = Math.min(Math.max(0, totalRowsHeight - height + scrollbarSize), event.target.scrollTop);
// Certain devices (like Apple touchpad) rapid-fire duplicate events.
// Don't force a re-render if this is the case.
// The mouse may move faster then the animation frame does.
// Use requestAnimationFrame to avoid over-updating.
if (this.state.scrollLeft !== scrollLeft || this.state.scrollTop !== scrollTop) {
// Browsers with cancelable scroll events (eg. Firefox) interrupt scrolling animations if scrollTop/scrollLeft is set.
// Other browsers (eg. Safari) don't scroll as well without the help under certain conditions (DOM or style changes during scrolling).
// All things considered, this seems to be the best current work around that I'm aware of.
// For more information see https://github.com/bvaughn/react-virtualized/pull/124
var scrollPositionChangeReason = event.cancelable ? SCROLL_POSITION_CHANGE_REASONS.OBSERVED : SCROLL_POSITION_CHANGE_REASONS.REQUESTED;
// Track scrolling direction so we can more efficiently overscan rows to reduce empty space around the edges while scrolling.
var scrollDirectionVertical = scrollTop > this.state.scrollTop ? _getOverscanIndices.SCROLL_DIRECTION_FORWARD : _getOverscanIndices.SCROLL_DIRECTION_BACKWARD;
var scrollDirectionHorizontal = scrollLeft > this.state.scrollLeft ? _getOverscanIndices.SCROLL_DIRECTION_FORWARD : _getOverscanIndices.SCROLL_DIRECTION_BACKWARD;
if (!this.state.isScrolling) {
this.setState({
isScrolling: true
});
}
this._setNextState({
isScrolling: true,
scrollDirectionHorizontal: scrollDirectionHorizontal,
scrollDirectionVertical: scrollDirectionVertical,
scrollLeft: scrollLeft,
scrollPositionChangeReason: scrollPositionChangeReason,
scrollTop: scrollTop
});
}
this._invokeOnScrollMemoizer({ scrollLeft: scrollLeft, scrollTop: scrollTop, totalColumnsWidth: totalColumnsWidth, totalRowsHeight: totalRowsHeight });
}
}]);
return Grid;
}(_react.Component);
Grid.propTypes = {
'aria-label': _react.PropTypes.string,
/**
* Set the width of the inner scrollable container to 'auto'.
* This is useful for single-column Grids to ensure that the column doesn't extend below a vertical scrollbar.
*/
autoContainerWidth: _react.PropTypes.bool,
/**
* Removes fixed height from the scrollingContainer so that the total height
* of rows can stretch the window. Intended for use with WindowScroller
*/
autoHeight: _react.PropTypes.bool,
/**
* Responsible for rendering a cell given an row and column index.
* Should implement the following interface: ({ columnIndex: number, rowIndex: number }): PropTypes.node
*/
cellRenderer: _react.PropTypes.func.isRequired,
/**
* Responsible for rendering a group of cells given their index ranges.
* Should implement the following interface: ({
* cellCache: Map,
* cellRenderer: Function,
* columnSizeAndPositionManager: CellSizeAndPositionManager,
* columnStartIndex: number,
* columnStopIndex: number,
* isScrolling: boolean,
* rowSizeAndPositionManager: CellSizeAndPositionManager,
* rowStartIndex: number,
* rowStopIndex: number,
* scrollLeft: number,
* scrollTop: number
* }): Array<PropTypes.node>
*/
cellRangeRenderer: _react.PropTypes.func.isRequired,
/**
* Optional custom CSS class name to attach to root Grid element.
*/
className: _react.PropTypes.string,
/**
* Number of columns in grid.
*/
columnCount: _react.PropTypes.number.isRequired,
/**
* Either a fixed column width (number) or a function that returns the width of a column given its index.
* Should implement the following interface: (index: number): number
*/
columnWidth: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.func]).isRequired,
/**
* Used to estimate the total width of a Grid before all of its columns have actually been measured.
* The estimated total width is adjusted as columns are rendered.
*/
estimatedColumnSize: _react.PropTypes.number.isRequired,
/**
* Used to estimate the total height of a Grid before all of its rows have actually been measured.
* The estimated total height is adjusted as rows are rendered.
*/
estimatedRowSize: _react.PropTypes.number.isRequired,
/**
* Height of Grid; this property determines the number of visible (vs virtualized) rows.
*/
height: _react.PropTypes.number.isRequired,
/**
* Optional renderer to be used in place of rows when either :rowCount or :columnCount is 0.
*/
noContentRenderer: _react.PropTypes.func.isRequired,
/**
* Callback invoked whenever the scroll offset changes within the inner scrollable region.
* This callback can be used to sync scrolling between lists, tables, or grids.
* ({ clientHeight, clientWidth, scrollHeight, scrollLeft, scrollTop, scrollWidth }): void
*/
onScroll: _react.PropTypes.func.isRequired,
/**
* Callback invoked with information about the section of the Grid that was just rendered.
* ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }): void
*/
onSectionRendered: _react.PropTypes.func.isRequired,
/**
* Number of columns to render before/after the visible section of the grid.
* These columns can help for smoother scrolling on touch devices or browsers that send scroll events infrequently.
*/
overscanColumnCount: _react.PropTypes.number.isRequired,
/**
* Number of rows to render above/below the visible section of the grid.
* These rows can help for smoother scrolling on touch devices or browsers that send scroll events infrequently.
*/
overscanRowCount: _react.PropTypes.number.isRequired,
/**
* Either a fixed row height (number) or a function that returns the height of a row given its index.
* Should implement the following interface: ({ index: number }): number
*/
rowHeight: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.func]).isRequired,
/**
* Number of rows in grid.
*/
rowCount: _react.PropTypes.number.isRequired,
/** Wait this amount of time after the last scroll event before resetting Grid `pointer-events`. */
scrollingResetTimeInterval: _react.PropTypes.number,
/** Horizontal offset. */
scrollLeft: _react.PropTypes.number,
/**
* Controls scroll-to-cell behavior of the Grid.
* The default ("auto") scrolls the least amount possible to ensure that the specified cell is fully visible.
* Use "start" to align cells to the top/left of the Grid and "end" to align bottom/right.
*/
scrollToAlignment: _react.PropTypes.oneOf(['auto', 'end', 'start', 'center']).isRequired,
/**
* Column index to ensure visible (by forcefully scrolling if necessary)
*/
scrollToColumn: _react.PropTypes.number,
/** Vertical offset. */
scrollTop: _react.PropTypes.number,
/**
* Row index to ensure visible (by forcefully scrolling if necessary)
*/
scrollToRow: _react.PropTypes.number,
/** Optional inline style */
style: _react.PropTypes.object,
/** Tab index for focus */
tabIndex: _react.PropTypes.number,
/**
* Width of Grid; this property determines the number of visible (vs virtualized) columns.
*/
width: _react.PropTypes.number.isRequired
};
Grid.defaultProps = {
'aria-label': 'grid',
cellRangeRenderer: _defaultCellRangeRenderer2.default,
estimatedColumnSize: 100,
estimatedRowSize: 30,
noContentRenderer: function noContentRenderer() {
return null;
},
onScroll: function onScroll() {
return null;
},
onSectionRendered: function onSectionRendered() {
return null;
},
overscanColumnCount: 0,
overscanRowCount: 10,
scrollingResetTimeInterval: DEFAULT_SCROLLING_RESET_TIME_INTERVAL,
scrollToAlignment: 'auto',
style: {},
tabIndex: 0
};
exports.default = Grid;