UNPKG

@jdcfe/yep-react

Version:

一套移动端的React组件库

292 lines (245 loc) 9.5 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 _constants = require("./constants"); var SizeAndPositionManager = /*#__PURE__*/function () { function SizeAndPositionManager(_ref) { var itemCount = _ref.itemCount, itemSizeGetter = _ref.itemSizeGetter, estimatedItemSize = _ref.estimatedItemSize; (0, _classCallCheck2.default)(this, SizeAndPositionManager); this.itemSizeGetter = itemSizeGetter; this.itemCount = itemCount; this.estimatedItemSize = estimatedItemSize; // Cache of size and position data for items, mapped by item index. this.itemSizeAndPositionData = {}; // Measurements for items up to this index can be trusted; items afterward should be estimated. this.lastMeasuredIndex = -1; } (0, _createClass2.default)(SizeAndPositionManager, [{ key: "updateConfig", value: function updateConfig(_ref2) { var itemCount = _ref2.itemCount, itemSizeGetter = _ref2.itemSizeGetter, estimatedItemSize = _ref2.estimatedItemSize; if (itemCount != null) { this.itemCount = itemCount; } if (estimatedItemSize != null) { this.estimatedItemSize = estimatedItemSize; } if (itemSizeGetter != null) { this.itemSizeGetter = itemSizeGetter; } } }, { key: "getLastMeasuredIndex", value: function getLastMeasuredIndex() { return this.lastMeasuredIndex; } /** * This method returns the size and position for the item at the specified index. * It just-in-time calculates (or used cached values) for items leading up to the index. */ }, { key: "getSizeAndPositionForIndex", value: function getSizeAndPositionForIndex(index) { if (index < 0 || index >= this.itemCount) { throw Error("Requested index ".concat(index, " is outside of range 0..").concat(this.itemCount)); } if (index > this.lastMeasuredIndex) { var lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem(); var offset = lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size; for (var i = this.lastMeasuredIndex + 1; i <= index; i++) { var size = this.itemSizeGetter(i); if (size == null || isNaN(size)) { throw Error("Invalid size returned for index ".concat(i, " of value ").concat(size)); } this.itemSizeAndPositionData[i] = { offset: offset, size: size }; offset += size; } this.lastMeasuredIndex = index; } return this.itemSizeAndPositionData[index]; } }, { key: "getSizeAndPositionOfLastMeasuredItem", value: function getSizeAndPositionOfLastMeasuredItem() { return this.lastMeasuredIndex >= 0 ? this.itemSizeAndPositionData[this.lastMeasuredIndex] : { offset: 0, size: 0 }; } /** * Total size of all items being measured. * This value will be completedly estimated initially. * As items as measured the estimate will be updated. */ }, { key: "getTotalSize", value: function getTotalSize() { var lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem(); return lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size + (this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize; } /** * Determines a new offset that ensures a certain item is visible, given the alignment. * * @param align Desired alignment within container; one of "start" (default), "center", or "end" * @param containerSize Size (width or height) of the container viewport * @return Offset to use to ensure the specified item is visible */ }, { key: "getUpdatedOffsetForIndex", value: function getUpdatedOffsetForIndex(_ref3) { var _ref3$align = _ref3.align, align = _ref3$align === void 0 ? _constants.ALIGNMENT.START : _ref3$align, containerSize = _ref3.containerSize, currentOffset = _ref3.currentOffset, targetIndex = _ref3.targetIndex; if (containerSize <= 0) { return 0; } var datum = this.getSizeAndPositionForIndex(targetIndex); var maxOffset = datum.offset; var minOffset = maxOffset - containerSize + datum.size; var idealOffset; switch (align) { case _constants.ALIGNMENT.END: idealOffset = minOffset; break; case _constants.ALIGNMENT.CENTER: idealOffset = maxOffset - (containerSize - datum.size) / 2; break; case _constants.ALIGNMENT.START: idealOffset = maxOffset; break; default: idealOffset = Math.max(minOffset, Math.min(maxOffset, currentOffset)); } var totalSize = this.getTotalSize(); return Math.max(0, Math.min(totalSize - containerSize, idealOffset)); } }, { key: "getVisibleRange", value: function getVisibleRange(_ref4) { var containerSize = _ref4.containerSize, offset = _ref4.offset, overscanCount = _ref4.overscanCount; var totalSize = this.getTotalSize(); if (totalSize === 0) { return {}; } var maxOffset = offset + containerSize; var start = this.findNearestItem(offset); if (typeof start === 'undefined') { throw Error("Invalid offset ".concat(offset, " specified")); } var datum = this.getSizeAndPositionForIndex(start); offset = datum.offset + datum.size; var stop = start; while (offset < maxOffset && stop < this.itemCount - 1) { stop++; offset += this.getSizeAndPositionForIndex(stop).size; } if (overscanCount) { start = Math.max(0, start - overscanCount); stop = Math.min(stop + overscanCount, this.itemCount - 1); } return { start: start, stop: stop }; } /** * Clear all cached values for items after the specified index. * This method should be called for any item that has changed its size. * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionForIndex() is called. */ }, { key: "resetItem", value: function resetItem(index) { this.lastMeasuredIndex = Math.min(this.lastMeasuredIndex, index - 1); } /** * Searches for the item (index) nearest the specified offset. * * If no exact match is found the next lowest item index will be returned. * This allows partially visible items (with offsets just before/above the fold) to be visible. */ }, { key: "findNearestItem", value: function findNearestItem(offset) { 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 lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem(); var lastMeasuredIndex = Math.max(0, this.lastMeasuredIndex); if (lastMeasuredSizeAndPosition.offset >= offset) { // If we've already measured items within this range just use a binary search as it's faster. return this.binarySearch({ high: lastMeasuredIndex, low: 0, offset: 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 items as a binary search would. // The overall complexity for this approach is O(log n). return this.exponentialSearch({ index: lastMeasuredIndex, offset: offset }); } } }, { key: "binarySearch", value: function binarySearch(_ref5) { var low = _ref5.low, high = _ref5.high, offset = _ref5.offset; var middle = 0; var currentOffset = 0; while (low <= high) { middle = low + Math.floor((high - low) / 2); currentOffset = this.getSizeAndPositionForIndex(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; } return 0; } }, { key: "exponentialSearch", value: function exponentialSearch(_ref6) { var index = _ref6.index, offset = _ref6.offset; var interval = 1; while (index < this.itemCount && this.getSizeAndPositionForIndex(index).offset < offset) { index += interval; interval *= 2; } return this.binarySearch({ high: Math.min(index, this.itemCount - 1), low: Math.floor(index / 2), offset: offset }); } }]); return SizeAndPositionManager; }(); exports.default = SizeAndPositionManager;