UNPKG

react-virtualized

Version:

React components for efficiently rendering large, scrollable lists and tabular data

209 lines (170 loc) 7.74 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); 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; }; }(); exports.isRangeVisible = isRangeVisible; exports.scanForUnloadedRanges = scanForUnloadedRanges; var _react = require('react'); var _reactAddonsShallowCompare = require('react-addons-shallow-compare'); var _reactAddonsShallowCompare2 = _interopRequireDefault(_reactAddonsShallowCompare); 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; } /** * Higher-order component that manages lazy-loading for "infinite" data. * This component decorates a virtual component and just-in-time prefetches rows as a user scrolls. * It is intended as a convenience component; fork it if you'd like finer-grained control over data-loading. */ var InfiniteLoader = function (_Component) { _inherits(InfiniteLoader, _Component); function InfiniteLoader(props, context) { _classCallCheck(this, InfiniteLoader); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(InfiniteLoader).call(this, props, context)); _this._onRowsRendered = _this._onRowsRendered.bind(_this); _this._registerChild = _this._registerChild.bind(_this); return _this; } _createClass(InfiniteLoader, [{ key: 'render', value: function render() { var children = this.props.children; return children({ onRowsRendered: this._onRowsRendered, registerChild: this._registerChild }); } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState) { return (0, _reactAddonsShallowCompare2.default)(this, nextProps, nextState); } }, { key: '_onRowsRendered', value: function _onRowsRendered(_ref) { var _this2 = this; var startIndex = _ref.startIndex; var stopIndex = _ref.stopIndex; var _props = this.props; var isRowLoaded = _props.isRowLoaded; var loadMoreRows = _props.loadMoreRows; var rowsCount = _props.rowsCount; var threshold = _props.threshold; this._lastRenderedStartIndex = startIndex; this._lastRenderedStopIndex = stopIndex; var unloadedRanges = scanForUnloadedRanges({ isRowLoaded: isRowLoaded, startIndex: Math.max(0, startIndex - threshold), stopIndex: Math.min(rowsCount, stopIndex + threshold) }); unloadedRanges.forEach(function (unloadedRange) { var promise = loadMoreRows(unloadedRange); if (promise) { promise.then(function () { // Refresh the visible rows if any of them have just been loaded. // Otherwise they will remain in their unloaded visual state. if (isRangeVisible({ lastRenderedStartIndex: _this2._lastRenderedStartIndex, lastRenderedStopIndex: _this2._lastRenderedStopIndex, startIndex: unloadedRange.startIndex, stopIndex: unloadedRange.stopIndex })) { if (_this2._registeredChild) { _this2._registeredChild.forceUpdate(); } } }); } }); } }, { key: '_registerChild', value: function _registerChild(registeredChild) { this._registeredChild = registeredChild; } }]); return InfiniteLoader; }(_react.Component); /** * Determines if the specified start/stop range is visible based on the most recently rendered range. */ InfiniteLoader.propTypes = { /** * Function respondible for rendering a virtualized component. * This function should implement the following signature: * ({ onRowsRendered, registerChild }) => PropTypes.element * * The specified :onRowsRendered function should be passed through to the child's :onRowsRendered property. * The :registerChild callback should be set as the virtualized component's :ref. */ children: _react.PropTypes.func.isRequired, /** * Function responsible for tracking the loaded state of each row. * It should implement the following signature: (index: number): boolean */ isRowLoaded: _react.PropTypes.func.isRequired, /** * Callback to be invoked when more rows must be loaded. * It should implement the following signature: ({ startIndex, stopIndex }): Promise * The returned Promise should be resolved once row data has finished loading. * It will be used to determine when to refresh the list with the newly-loaded data. * This callback may be called multiple times in reaction to a single scroll event. */ loadMoreRows: _react.PropTypes.func.isRequired, /** * Number of rows in list; can be arbitrary high number if actual number is unknown. */ rowsCount: _react.PropTypes.number.isRequired, /** * Threshold at which to pre-fetch data. * A threshold X means that data will start loading when a user scrolls within X rows. * This value defaults to 15. */ threshold: _react.PropTypes.number.isRequired }; InfiniteLoader.defaultProps = { rowsCount: 0, threshold: 15 }; exports.default = InfiniteLoader; function isRangeVisible(_ref2) { var lastRenderedStartIndex = _ref2.lastRenderedStartIndex; var lastRenderedStopIndex = _ref2.lastRenderedStopIndex; var startIndex = _ref2.startIndex; var stopIndex = _ref2.stopIndex; return !(startIndex > lastRenderedStopIndex || stopIndex < lastRenderedStartIndex); } /** * Returns all of the ranges within a larger range that contain unloaded rows. */ function scanForUnloadedRanges(_ref3) { var isRowLoaded = _ref3.isRowLoaded; var startIndex = _ref3.startIndex; var stopIndex = _ref3.stopIndex; var unloadedRanges = []; var rangeStartIndex = null; var rangeStopIndex = null; for (var i = startIndex; i <= stopIndex; i++) { var loaded = isRowLoaded(i); if (!loaded) { rangeStopIndex = i; if (rangeStartIndex === null) { rangeStartIndex = i; } } else if (rangeStopIndex !== null) { unloadedRanges.push({ startIndex: rangeStartIndex, stopIndex: rangeStopIndex }); rangeStartIndex = rangeStopIndex = null; } } if (rangeStopIndex !== null) { unloadedRanges.push({ startIndex: rangeStartIndex, stopIndex: rangeStopIndex }); } return unloadedRanges; }