UNPKG

react-virtualized

Version:

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

274 lines (268 loc) 12.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; exports.forceUpdateReactVirtualizedComponent = forceUpdateReactVirtualizedComponent; exports.isRangeVisible = isRangeVisible; exports.scanForUnloadedRanges = scanForUnloadedRanges; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var React = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _createCallbackMemoizer = _interopRequireDefault(require("../utils/createCallbackMemoizer")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2["default"])(o), (0, _possibleConstructorReturn2["default"])(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2["default"])(t).constructor) : o.apply(t, e)); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /** * 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 = exports["default"] = /*#__PURE__*/function (_React$PureComponent) { function InfiniteLoader(props, context) { var _this; (0, _classCallCheck2["default"])(this, InfiniteLoader); _this = _callSuper(this, InfiniteLoader, [props, context]); _this._loadMoreRowsMemoizer = (0, _createCallbackMemoizer["default"])(); _this._onRowsRendered = _this._onRowsRendered.bind(_this); _this._registerChild = _this._registerChild.bind(_this); return _this; } (0, _inherits2["default"])(InfiniteLoader, _React$PureComponent); return (0, _createClass2["default"])(InfiniteLoader, [{ key: "resetLoadMoreRowsCache", value: function resetLoadMoreRowsCache(autoReload) { this._loadMoreRowsMemoizer = (0, _createCallbackMemoizer["default"])(); if (autoReload) { this._doStuff(this._lastRenderedStartIndex, this._lastRenderedStopIndex); } } }, { key: "render", value: function render() { var children = this.props.children; return children({ onRowsRendered: this._onRowsRendered, registerChild: this._registerChild }); } }, { key: "_loadUnloadedRanges", value: function _loadUnloadedRanges(unloadedRanges) { var _this2 = this; var loadMoreRows = this.props.loadMoreRows; 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) { forceUpdateReactVirtualizedComponent(_this2._registeredChild, _this2._lastRenderedStartIndex); } } }); } }); } }, { key: "_onRowsRendered", value: function _onRowsRendered(_ref) { var startIndex = _ref.startIndex, stopIndex = _ref.stopIndex; this._lastRenderedStartIndex = startIndex; this._lastRenderedStopIndex = stopIndex; this._doStuff(startIndex, stopIndex); } }, { key: "_doStuff", value: function _doStuff(startIndex, stopIndex) { var _ref2, _this3 = this; var _this$props = this.props, isRowLoaded = _this$props.isRowLoaded, minimumBatchSize = _this$props.minimumBatchSize, rowCount = _this$props.rowCount, threshold = _this$props.threshold; var unloadedRanges = scanForUnloadedRanges({ isRowLoaded: isRowLoaded, minimumBatchSize: minimumBatchSize, rowCount: rowCount, startIndex: Math.max(0, startIndex - threshold), stopIndex: Math.min(rowCount - 1, stopIndex + threshold) }); // For memoize comparison var squashedUnloadedRanges = (_ref2 = []).concat.apply(_ref2, (0, _toConsumableArray2["default"])(unloadedRanges.map(function (_ref3) { var startIndex = _ref3.startIndex, stopIndex = _ref3.stopIndex; return [startIndex, stopIndex]; }))); this._loadMoreRowsMemoizer({ callback: function callback() { _this3._loadUnloadedRanges(unloadedRanges); }, indices: { squashedUnloadedRanges: squashedUnloadedRanges } }); } }, { key: "_registerChild", value: function _registerChild(registeredChild) { this._registeredChild = registeredChild; } }]); }(React.PureComponent); /** * Determines if the specified start/stop range is visible based on the most recently rendered range. */ (0, _defineProperty2["default"])(InfiniteLoader, "defaultProps", { minimumBatchSize: 10, rowCount: 0, threshold: 15 }); InfiniteLoader.propTypes = process.env.NODE_ENV !== "production" ? { /** * Function responsible 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: _propTypes["default"].func.isRequired, /** * Function responsible for tracking the loaded state of each row. * It should implement the following signature: ({ index: number }): boolean */ isRowLoaded: _propTypes["default"].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: _propTypes["default"].func.isRequired, /** * Minimum number of rows to be loaded at a time. * This property can be used to batch requests to reduce HTTP requests. */ minimumBatchSize: _propTypes["default"].number.isRequired, /** * Number of rows in list; can be arbitrary high number if actual number is unknown. */ rowCount: _propTypes["default"].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: _propTypes["default"].number.isRequired } : {}; function isRangeVisible(_ref4) { var lastRenderedStartIndex = _ref4.lastRenderedStartIndex, lastRenderedStopIndex = _ref4.lastRenderedStopIndex, startIndex = _ref4.startIndex, stopIndex = _ref4.stopIndex; return !(startIndex > lastRenderedStopIndex || stopIndex < lastRenderedStartIndex); } /** * Returns all of the ranges within a larger range that contain unloaded rows. */ function scanForUnloadedRanges(_ref5) { var isRowLoaded = _ref5.isRowLoaded, minimumBatchSize = _ref5.minimumBatchSize, rowCount = _ref5.rowCount, startIndex = _ref5.startIndex, stopIndex = _ref5.stopIndex; var unloadedRanges = []; var rangeStartIndex = null; var rangeStopIndex = null; for (var index = startIndex; index <= stopIndex; index++) { var loaded = isRowLoaded({ index: index }); if (!loaded) { rangeStopIndex = index; if (rangeStartIndex === null) { rangeStartIndex = index; } } else if (rangeStopIndex !== null) { unloadedRanges.push({ startIndex: rangeStartIndex, stopIndex: rangeStopIndex }); rangeStartIndex = rangeStopIndex = null; } } // If :rangeStopIndex is not null it means we haven't ran out of unloaded rows. // Scan forward to try filling our :minimumBatchSize. if (rangeStopIndex !== null) { var potentialStopIndex = Math.min(Math.max(rangeStopIndex, rangeStartIndex + minimumBatchSize - 1), rowCount - 1); for (var _index = rangeStopIndex + 1; _index <= potentialStopIndex; _index++) { if (!isRowLoaded({ index: _index })) { rangeStopIndex = _index; } else { break; } } unloadedRanges.push({ startIndex: rangeStartIndex, stopIndex: rangeStopIndex }); } // Check to see if our first range ended prematurely. // In this case we should scan backwards to try filling our :minimumBatchSize. if (unloadedRanges.length) { var firstUnloadedRange = unloadedRanges[0]; while (firstUnloadedRange.stopIndex - firstUnloadedRange.startIndex + 1 < minimumBatchSize && firstUnloadedRange.startIndex > 0) { var _index2 = firstUnloadedRange.startIndex - 1; if (!isRowLoaded({ index: _index2 })) { firstUnloadedRange.startIndex = _index2; } else { break; } } } return unloadedRanges; } /** * Since RV components use shallowCompare we need to force a render (even though props haven't changed). * However InfiniteLoader may wrap a Grid or it may wrap a Table or List. * In the first case the built-in React forceUpdate() method is sufficient to force a re-render, * But in the latter cases we need to use the RV-specific forceUpdateGrid() method. * Else the inner Grid will not be re-rendered and visuals may be stale. * * Additionally, while a Grid is scrolling the cells can be cached, * So it's important to invalidate that cache by recalculating sizes * before forcing a rerender. */ function forceUpdateReactVirtualizedComponent(component) { var currentIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var recomputeSize = typeof component.recomputeGridSize === 'function' ? component.recomputeGridSize : component.recomputeRowHeights; if (recomputeSize) { recomputeSize.call(component, currentIndex); } else { component.forceUpdate(); } }