UNPKG

react-virtualized

Version:

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

296 lines (288 loc) 11.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); /*:: import type {Alignment, CellSizeGetter, VisibleCellRange} from '../types';*/ /*:: type CellSizeAndPositionManagerParams = { cellCount: number, cellSizeGetter: CellSizeGetter, estimatedCellSize: number, };*/ /*:: type ConfigureParams = { cellCount: number, estimatedCellSize: number, cellSizeGetter: CellSizeGetter, };*/ /*:: type GetUpdatedOffsetForIndex = { align: Alignment, containerSize: number, currentOffset: number, targetIndex: number, };*/ /*:: type GetVisibleCellRangeParams = { containerSize: number, offset: number, };*/ /*:: type SizeAndPositionData = { offset: number, size: number, };*/ /** * Just-in-time calculates and caches size and position information for a collection of cells. */ var CellSizeAndPositionManager = exports["default"] = /*#__PURE__*/function () { function CellSizeAndPositionManager(_ref /*:: */) { var cellCount = _ref /*:: */.cellCount, cellSizeGetter = _ref /*:: */.cellSizeGetter, estimatedCellSize = _ref /*:: */.estimatedCellSize; (0, _classCallCheck2["default"])(this, CellSizeAndPositionManager); // Cache of size and position data for cells, mapped by cell index. // Note that invalid values may exist in this map so only rely on cells up to this._lastMeasuredIndex (0, _defineProperty2["default"])(this, "_cellSizeAndPositionData", {}); // Measurements for cells up to this index can be trusted; cells afterward should be estimated. (0, _defineProperty2["default"])(this, "_lastMeasuredIndex", -1); // Used in deferred mode to track which cells have been queued for measurement. (0, _defineProperty2["default"])(this, "_lastBatchedIndex", -1); (0, _defineProperty2["default"])(this, "_cellCount", void 0); (0, _defineProperty2["default"])(this, "_cellSizeGetter", void 0); (0, _defineProperty2["default"])(this, "_estimatedCellSize", void 0); this._cellSizeGetter = cellSizeGetter; this._cellCount = cellCount; this._estimatedCellSize = estimatedCellSize; } return (0, _createClass2["default"])(CellSizeAndPositionManager, [{ key: "areOffsetsAdjusted", value: function areOffsetsAdjusted() { return false; } }, { key: "configure", value: function configure(_ref2 /*:: */) { var cellCount = _ref2 /*:: */.cellCount, estimatedCellSize = _ref2 /*:: */.estimatedCellSize, cellSizeGetter = _ref2 /*:: */.cellSizeGetter; this._cellCount = cellCount; this._estimatedCellSize = estimatedCellSize; this._cellSizeGetter = cellSizeGetter; } }, { key: "getCellCount", value: function getCellCount() /*: number*/{ return this._cellCount; } }, { key: "getEstimatedCellSize", value: function getEstimatedCellSize() /*: number*/{ return this._estimatedCellSize; } }, { key: "getLastMeasuredIndex", value: function getLastMeasuredIndex() /*: number*/{ return this._lastMeasuredIndex; } }, { key: "getOffsetAdjustment", value: function getOffsetAdjustment() { return 0; } /** * This method returns the size and position for the cell at the specified index. * It just-in-time calculates (or used cached values) for cells leading up to the index. */ }, { key: "getSizeAndPositionOfCell", value: function getSizeAndPositionOfCell(index /*: number*/) /*: SizeAndPositionData*/{ if (index < 0 || index >= this._cellCount) { throw Error("Requested index ".concat(index, " is outside of range 0..").concat(this._cellCount)); } if (index > this._lastMeasuredIndex) { var lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell(); var offset = lastMeasuredCellSizeAndPosition.offset + lastMeasuredCellSizeAndPosition.size; for (var i = this._lastMeasuredIndex + 1; i <= index; i++) { var size = this._cellSizeGetter({ index: i }); // undefined or NaN probably means a logic error in the size getter. // null means we're using CellMeasurer and haven't yet measured a given index. if (size === undefined || isNaN(size)) { throw Error("Invalid size returned for cell ".concat(i, " of value ").concat(size)); } else if (size === null) { this._cellSizeAndPositionData[i] = { offset: offset, size: 0 }; this._lastBatchedIndex = index; } else { this._cellSizeAndPositionData[i] = { offset: offset, size: size }; offset += size; this._lastMeasuredIndex = index; } } } return this._cellSizeAndPositionData[index]; } }, { key: "getSizeAndPositionOfLastMeasuredCell", value: function getSizeAndPositionOfLastMeasuredCell() /*: SizeAndPositionData*/{ return this._lastMeasuredIndex >= 0 ? this._cellSizeAndPositionData[this._lastMeasuredIndex] : { offset: 0, size: 0 }; } /** * Total size of all cells being measured. * This value will be completely estimated initially. * As cells are measured, the estimate will be updated. */ }, { key: "getTotalSize", value: function getTotalSize() /*: number*/{ var lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell(); var totalSizeOfMeasuredCells = lastMeasuredCellSizeAndPosition.offset + lastMeasuredCellSizeAndPosition.size; var numUnmeasuredCells = this._cellCount - this._lastMeasuredIndex - 1; var totalSizeOfUnmeasuredCells = numUnmeasuredCells * this._estimatedCellSize; return totalSizeOfMeasuredCells + totalSizeOfUnmeasuredCells; } /** * Determines a new offset that ensures a certain cell is visible, given the current offset. * If the cell is already visible then the current offset will be returned. * If the current offset is too great or small, it will be adjusted just enough to ensure the specified index is visible. * * @param align Desired alignment within container; one of "auto" (default), "start", or "end" * @param containerSize Size (width or height) of the container viewport * @param currentOffset Container's current (x or y) offset * @param totalSize Total size (width or height) of all cells * @return Offset to use to ensure the specified cell is visible */ }, { key: "getUpdatedOffsetForIndex", value: function getUpdatedOffsetForIndex(_ref3 /*:: */) /*: number*/{ var _ref3$align = _ref3 /*:: */.align, align = _ref3$align === void 0 ? 'auto' : _ref3$align, containerSize = _ref3 /*:: */.containerSize, currentOffset = _ref3 /*:: */.currentOffset, targetIndex = _ref3 /*:: */.targetIndex; if (containerSize <= 0) { return 0; } var datum = this.getSizeAndPositionOfCell(targetIndex); var maxOffset = datum.offset; var minOffset = maxOffset - containerSize + datum.size; var idealOffset; switch (align) { case 'start': idealOffset = maxOffset; break; case 'end': idealOffset = minOffset; break; case 'center': idealOffset = maxOffset - (containerSize - datum.size) / 2; break; default: idealOffset = Math.max(minOffset, Math.min(maxOffset, currentOffset)); break; } var totalSize = this.getTotalSize(); return Math.max(0, Math.min(totalSize - containerSize, idealOffset)); } }, { key: "getVisibleCellRange", value: function getVisibleCellRange(params /*: GetVisibleCellRangeParams*/) /*: VisibleCellRange*/{ var containerSize = params.containerSize, offset = params.offset; var totalSize = this.getTotalSize(); if (totalSize === 0) { return {}; } var maxOffset = offset + containerSize; var start = this._findNearestCell(offset); var datum = this.getSizeAndPositionOfCell(start); offset = datum.offset + datum.size; var stop = start; while (offset < maxOffset && stop < this._cellCount - 1) { stop++; offset += this.getSizeAndPositionOfCell(stop).size; } return { start: start, stop: stop }; } /** * Clear all cached values for cells after the specified index. * This method should be called for any cell that has changed its size. * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionOfCell() is called. */ }, { key: "resetCell", value: function resetCell(index /*: number*/) /*: void*/{ this._lastMeasuredIndex = Math.min(this._lastMeasuredIndex, index - 1); } }, { key: "_binarySearch", value: function _binarySearch(high /*: number*/, low /*: number*/, offset /*: number*/) /*: number*/{ while (low <= high) { var middle = low + Math.floor((high - low) / 2); var currentOffset = this.getSizeAndPositionOfCell(middle).offset; if (currentOffset === offset) { return middle; } else if (currentOffset < offset) { low = middle + 1; } else if (currentOffset > offset) { high = middle - 1; } } if (low > 0) { return low - 1; } else { return 0; } } }, { key: "_exponentialSearch", value: function _exponentialSearch(index /*: number*/, offset /*: number*/) /*: number*/{ var interval = 1; while (index < this._cellCount && this.getSizeAndPositionOfCell(index).offset < offset) { index += interval; interval *= 2; } return this._binarySearch(Math.min(index, this._cellCount - 1), Math.floor(index / 2), offset); } /** * Searches for the cell (index) nearest the specified offset. * * If no exact match is found the next lowest cell index will be returned. * This allows partially visible cells (with offsets just before/above the fold) to be visible. */ }, { key: "_findNearestCell", value: function _findNearestCell(offset /*: number*/) /*: number*/{ if (isNaN(offset)) { throw Error("Invalid offset ".concat(offset, " specified")); } // Our search algorithms find the nearest match at or below the specified offset. // So make sure the offset is at least 0 or no match will be found. offset = Math.max(0, offset); var lastMeasuredCellSizeAndPosition = this.getSizeAndPositionOfLastMeasuredCell(); var lastMeasuredIndex = Math.max(0, this._lastMeasuredIndex); if (lastMeasuredCellSizeAndPosition.offset >= offset) { // If we've already measured cells within this range just use a binary search as it's faster. return this._binarySearch(lastMeasuredIndex, 0, offset); } else { // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. // The exponential search avoids pre-computing sizes for the full set of cells as a binary search would. // The overall complexity for this approach is O(log n). return this._exponentialSearch(lastMeasuredIndex, offset); } } }]); }();