@jdcfe/yep-react
Version:
一套移动端的React组件库
292 lines (245 loc) • 9.5 kB
JavaScript
"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;