react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
775 lines (668 loc) • 27.4 kB
JavaScript
import { computeCellMetadataAndUpdateScrollOffsetHelper, createCallbackMemoizer, getOverscanIndices, getUpdatedOffsetForIndex, getVisibleCellIndices, initCellMetadata, updateScrollIndexHelper } from './GridUtils';
import cn from 'classnames';
import raf from 'raf';
import getScrollbarSize from 'dom-helpers/util/scrollbarSize';
import React, { Component, PropTypes } from 'react';
import shallowCompare from 'react-addons-shallow-compare';
/**
* 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 IS_SCROLLING_TIMEOUT = 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) {
babelHelpers.inherits(Grid, _Component);
function Grid(props, context) {
babelHelpers.classCallCheck(this, Grid);
var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Grid).call(this, props, context));
_this.state = {
computeGridMetadataOnNextUpdate: false,
isScrolling: false,
scrollLeft: 0,
scrollTop: 0
};
// Invokes onSectionRendered callback only when start/stop row or column indices change
_this._onGridRenderedMemoizer = createCallbackMemoizer();
_this._onScrollMemoizer = createCallbackMemoizer(false);
// Bind functions to instance so they don't lose context when passed around
_this._computeGridMetadata = _this._computeGridMetadata.bind(_this);
_this._invokeOnGridRenderedHelper = _this._invokeOnGridRenderedHelper.bind(_this);
_this._onScroll = _this._onScroll.bind(_this);
_this._updateScrollLeftForScrollToColumn = _this._updateScrollLeftForScrollToColumn.bind(_this);
_this._updateScrollTopForScrollToRow = _this._updateScrollTopForScrollToRow.bind(_this);
return _this;
}
/**
* 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 :columnsCount and :rowsCount it has no way of detecting when the underlying data changes.
*/
babelHelpers.createClass(Grid, [{
key: 'recomputeGridSize',
value: function recomputeGridSize() {
this.setState({
computeGridMetadataOnNextUpdate: true
});
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props;
var scrollLeft = _props.scrollLeft;
var scrollToColumn = _props.scrollToColumn;
var scrollTop = _props.scrollTop;
var scrollToRow = _props.scrollToRow;
this._scrollbarSize = getScrollbarSize();
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._getTotalColumnsWidth(),
totalRowsHeight: this._getTotalRowsHeight()
});
}
/**
* @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 _props2 = this.props;
var columnsCount = _props2.columnsCount;
var columnWidth = _props2.columnWidth;
var height = _props2.height;
var rowHeight = _props2.rowHeight;
var rowsCount = _props2.rowsCount;
var scrollToColumn = _props2.scrollToColumn;
var scrollToRow = _props2.scrollToRow;
var width = _props2.width;
var _state = this.state;
var scrollLeft = _state.scrollLeft;
var scrollPositionChangeReason = _state.scrollPositionChangeReason;
var scrollTop = _state.scrollTop;
// 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.refs.scrollingContainer.scrollLeft) {
this.refs.scrollingContainer.scrollLeft = scrollLeft;
}
if (scrollTop >= 0 && scrollTop !== prevState.scrollTop && scrollTop !== this.refs.scrollingContainer.scrollTop) {
this.refs.scrollingContainer.scrollTop = scrollTop;
}
}
// Update scroll offsets if the current :scrollToColumn or :scrollToRow values requires it
updateScrollIndexHelper({
cellsCount: columnsCount,
cellMetadata: this._columnMetadata,
cellSize: columnWidth,
previousCellsCount: prevProps.columnsCount,
previousCellSize: prevProps.columnWidth,
previousScrollToIndex: prevProps.scrollToColumn,
previousSize: prevProps.width,
scrollOffset: scrollLeft,
scrollToIndex: scrollToColumn,
size: width,
updateScrollIndexCallback: this._updateScrollLeftForScrollToColumn
});
updateScrollIndexHelper({
cellsCount: rowsCount,
cellMetadata: this._rowMetadata,
cellSize: rowHeight,
previousCellsCount: prevProps.rowsCount,
previousCellSize: prevProps.rowHeight,
previousScrollToIndex: prevProps.scrollToRow,
previousSize: prevProps.height,
scrollOffset: scrollTop,
scrollToIndex: scrollToRow,
size: height,
updateScrollIndexCallback: this._updateScrollTopForScrollToRow
});
// Update onRowsRendered callback if start/stop indices have changed
this._invokeOnGridRenderedHelper();
}
}, {
key: 'componentWillMount',
value: function componentWillMount() {
this._computeGridMetadata(this.props);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (this._disablePointerEventsTimeoutId) {
clearTimeout(this._disablePointerEventsTimeoutId);
}
if (this._setNextStateAnimationFrameId) {
raf.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) {
if (nextProps.columnsCount === 0 && nextState.scrollLeft !== 0 || nextProps.rowsCount === 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
});
}
// Update scroll offsets if the size or number of cells have changed, invalidating the previous value
computeCellMetadataAndUpdateScrollOffsetHelper({
cellsCount: this.props.columnsCount,
cellSize: this.props.columnWidth,
computeMetadataCallback: this._computeGridMetadata,
computeMetadataCallbackProps: nextProps,
computeMetadataOnNextUpdate: nextState.computeGridMetadataOnNextUpdate,
nextCellsCount: nextProps.columnsCount,
nextCellSize: nextProps.columnWidth,
nextScrollToIndex: nextProps.scrollToColumn,
scrollToIndex: this.props.scrollToColumn,
updateScrollOffsetForScrollToIndex: this._updateScrollLeftForScrollToColumn
});
computeCellMetadataAndUpdateScrollOffsetHelper({
cellsCount: this.props.rowsCount,
cellSize: this.props.rowHeight,
computeMetadataCallback: this._computeGridMetadata,
computeMetadataCallbackProps: nextProps,
computeMetadataOnNextUpdate: nextState.computeGridMetadataOnNextUpdate,
nextCellsCount: nextProps.rowsCount,
nextCellSize: nextProps.rowHeight,
nextScrollToIndex: nextProps.scrollToRow,
scrollToIndex: this.props.scrollToRow,
updateScrollOffsetForScrollToIndex: this._updateScrollTopForScrollToRow
});
this.setState({
computeGridMetadataOnNextUpdate: false
});
}
}, {
key: 'render',
value: function render() {
var _props3 = this.props;
var className = _props3.className;
var columnsCount = _props3.columnsCount;
var height = _props3.height;
var noContentRenderer = _props3.noContentRenderer;
var overscanColumnsCount = _props3.overscanColumnsCount;
var overscanRowsCount = _props3.overscanRowsCount;
var renderCell = _props3.renderCell;
var rowsCount = _props3.rowsCount;
var width = _props3.width;
var _state2 = this.state;
var isScrolling = _state2.isScrolling;
var scrollLeft = _state2.scrollLeft;
var scrollTop = _state2.scrollTop;
var childrenToDisplay = [];
// Render only enough columns and rows to cover the visible area of the grid.
if (height > 0 && width > 0) {
var visibleColumnIndices = getVisibleCellIndices({
cellsCount: columnsCount,
cellMetadata: this._columnMetadata,
containerSize: width,
currentOffset: scrollLeft
});
var visibleRowIndices = getVisibleCellIndices({
cellsCount: rowsCount,
cellMetadata: this._rowMetadata,
containerSize: height,
currentOffset: scrollTop
});
// Store for _invokeOnGridRenderedHelper()
this._renderedColumnStartIndex = visibleColumnIndices.start;
this._renderedColumnStopIndex = visibleColumnIndices.stop;
this._renderedRowStartIndex = visibleRowIndices.start;
this._renderedRowStopIndex = visibleRowIndices.stop;
var overscanColumnIndices = getOverscanIndices({
cellsCount: columnsCount,
overscanCellsCount: overscanColumnsCount,
startIndex: this._renderedColumnStartIndex,
stopIndex: this._renderedColumnStopIndex
});
var overscanRowIndices = getOverscanIndices({
cellsCount: rowsCount,
overscanCellsCount: overscanRowsCount,
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;
for (var rowIndex = this._rowStartIndex; rowIndex <= this._rowStopIndex; rowIndex++) {
var rowDatum = this._rowMetadata[rowIndex];
for (var columnIndex = this._columnStartIndex; columnIndex <= this._columnStopIndex; columnIndex++) {
var columnDatum = this._columnMetadata[columnIndex];
var renderedCell = renderCell({ columnIndex: columnIndex, rowIndex: rowIndex });
var key = rowIndex + '-' + columnIndex;
// any other falsey value will be rendered
// as a text node by React
if (renderedCell == null || renderedCell === false) {
continue;
}
var child = React.createElement(
'div',
{
key: key,
className: 'Grid__cell',
style: {
height: this._getRowHeight(rowIndex),
left: columnDatum.offset + 'px',
top: rowDatum.offset + 'px',
width: this._getColumnWidth(columnIndex)
}
},
renderedCell
);
childrenToDisplay.push(child);
}
}
}
var gridStyle = {
height: height,
width: width
};
var totalColumnsWidth = this._getTotalColumnsWidth();
var totalRowsHeight = this._getTotalRowsHeight();
// 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
if (totalColumnsWidth <= width) {
gridStyle.overflowX = 'hidden';
}
if (totalRowsHeight <= height) {
gridStyle.overflowY = 'hidden';
}
return React.createElement(
'div',
{
ref: 'scrollingContainer',
className: cn('Grid', className),
onScroll: this._onScroll,
tabIndex: 0,
style: gridStyle
},
childrenToDisplay.length > 0 && React.createElement(
'div',
{
className: 'Grid__innerScrollContainer',
style: {
width: totalColumnsWidth,
height: totalRowsHeight,
maxWidth: totalColumnsWidth,
maxHeight: totalRowsHeight,
pointerEvents: isScrolling ? 'none' : 'auto'
}
},
childrenToDisplay
),
childrenToDisplay.length === 0 && noContentRenderer()
);
}
}, {
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
/* ---------------------------- Helper methods ---------------------------- */
}, {
key: '_computeGridMetadata',
value: function _computeGridMetadata(props) {
var columnsCount = props.columnsCount;
var columnWidth = props.columnWidth;
var rowHeight = props.rowHeight;
var rowsCount = props.rowsCount;
this._columnMetadata = initCellMetadata({
cellsCount: columnsCount,
size: columnWidth
});
this._rowMetadata = initCellMetadata({
cellsCount: rowsCount,
size: rowHeight
});
}
/**
* 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 _this2 = this;
if (this._disablePointerEventsTimeoutId) {
clearTimeout(this._disablePointerEventsTimeoutId);
}
this._disablePointerEventsTimeoutId = setTimeout(function () {
_this2._disablePointerEventsTimeoutId = null;
_this2.setState({
isScrolling: false
});
}, IS_SCROLLING_TIMEOUT);
}
}, {
key: '_getColumnWidth',
value: function _getColumnWidth(index) {
var columnWidth = this.props.columnWidth;
return columnWidth instanceof Function ? columnWidth(index) : columnWidth;
}
}, {
key: '_getRowHeight',
value: function _getRowHeight(index) {
var rowHeight = this.props.rowHeight;
return rowHeight instanceof Function ? rowHeight(index) : rowHeight;
}
}, {
key: '_getTotalColumnsWidth',
value: function _getTotalColumnsWidth() {
if (this._columnMetadata.length === 0) {
return 0;
}
var datum = this._columnMetadata[this._columnMetadata.length - 1];
return datum.offset + datum.size;
}
}, {
key: '_getTotalRowsHeight',
value: function _getTotalRowsHeight() {
if (this._rowMetadata.length === 0) {
return 0;
}
var datum = this._rowMetadata[this._rowMetadata.length - 1];
return datum.offset + datum.size;
}
}, {
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(_ref) {
var scrollLeft = _ref.scrollLeft;
var scrollTop = _ref.scrollTop;
var totalColumnsWidth = _ref.totalColumnsWidth;
var totalRowsHeight = _ref.totalRowsHeight;
var _props4 = this.props;
var height = _props4.height;
var onScroll = _props4.onScroll;
var width = _props4.width;
this._onScrollMemoizer({
callback: function callback(_ref2) {
var scrollLeft = _ref2.scrollLeft;
var scrollTop = _ref2.scrollTop;
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) {
var _this3 = this;
if (this._setNextStateAnimationFrameId) {
raf.cancel(this._setNextStateAnimationFrameId);
}
this._setNextStateAnimationFrameId = raf(function () {
_this3._setNextStateAnimationFrameId = null;
_this3.setState(state);
});
}
}, {
key: '_setScrollPosition',
value: function _setScrollPosition(_ref3) {
var scrollLeft = _ref3.scrollLeft;
var scrollTop = _ref3.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: '_updateScrollLeftForScrollToColumn',
value: function _updateScrollLeftForScrollToColumn(scrollToColumnOverride) {
var scrollToColumn = scrollToColumnOverride != null ? scrollToColumnOverride : this.props.scrollToColumn;
var width = this.props.width;
var scrollLeft = this.state.scrollLeft;
if (scrollToColumn >= 0) {
var calculatedScrollLeft = getUpdatedOffsetForIndex({
cellMetadata: this._columnMetadata,
containerSize: width,
currentOffset: scrollLeft,
targetIndex: scrollToColumn
});
if (scrollLeft !== calculatedScrollLeft) {
this._setScrollPosition({
scrollLeft: calculatedScrollLeft
});
}
}
}
}, {
key: '_updateScrollTopForScrollToRow',
value: function _updateScrollTopForScrollToRow(scrollToRowOverride) {
var scrollToRow = scrollToRowOverride != null ? scrollToRowOverride : this.props.scrollToRow;
var height = this.props.height;
var scrollTop = this.state.scrollTop;
if (scrollToRow >= 0) {
var calculatedScrollTop = getUpdatedOffsetForIndex({
cellMetadata: this._rowMetadata,
containerSize: height,
currentOffset: scrollTop,
targetIndex: scrollToRow
});
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.refs.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 _props5 = this.props;
var height = _props5.height;
var width = _props5.width;
var scrollbarSize = this._scrollbarSize;
var totalRowsHeight = this._getTotalRowsHeight();
var totalColumnsWidth = this._getTotalColumnsWidth();
var scrollLeft = Math.min(totalColumnsWidth - width + scrollbarSize, event.target.scrollLeft);
var scrollTop = Math.min(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;
if (!this.state.isScrolling) {
this.setState({
isScrolling: true
});
}
this._setNextState({
isScrolling: true,
scrollLeft: scrollLeft,
scrollPositionChangeReason: scrollPositionChangeReason,
scrollTop: scrollTop
});
}
this._invokeOnScrollMemoizer({ scrollLeft: scrollLeft, scrollTop: scrollTop, totalColumnsWidth: totalColumnsWidth, totalRowsHeight: totalRowsHeight });
}
}]);
return Grid;
}(Component);
Grid.propTypes = {
/**
* Optional custom CSS class name to attach to root Grid element.
*/
className: PropTypes.string,
/**
* Number of columns in grid.
*/
columnsCount: 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: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
/**
* Height of Grid; this property determines the number of visible (vs virtualized) rows.
*/
height: PropTypes.number.isRequired,
/**
* Optional renderer to be used in place of rows when either :rowsCount or :columnsCount is 0.
*/
noContentRenderer: 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: PropTypes.func.isRequired,
/**
* Callback invoked with information about the section of the Grid that was just rendered.
* ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }): void
*/
onSectionRendered: 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.
*/
overscanColumnsCount: 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.
*/
overscanRowsCount: PropTypes.number.isRequired,
/**
* Responsible for rendering a cell given an row and column index.
* Should implement the following interface: ({ columnIndex: number, rowIndex: number }): PropTypes.node
*/
renderCell: PropTypes.func.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: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
/**
* Number of rows in grid.
*/
rowsCount: PropTypes.number.isRequired,
/** Horizontal offset. */
scrollLeft: PropTypes.number,
/**
* Column index to ensure visible (by forcefully scrolling if necessary)
*/
scrollToColumn: PropTypes.number,
/** Vertical offset. */
scrollTop: PropTypes.number,
/**
* Row index to ensure visible (by forcefully scrolling if necessary)
*/
scrollToRow: PropTypes.number,
/**
* Width of Grid; this property determines the number of visible (vs virtualized) columns.
*/
width: PropTypes.number.isRequired
};
Grid.defaultProps = {
noContentRenderer: function noContentRenderer() {
return null;
},
onScroll: function onScroll() {
return null;
},
onSectionRendered: function onSectionRendered() {
return null;
},
overscanColumnsCount: 0,
overscanRowsCount: 10
};
export default Grid;