vue3-virtual-scroll-list
Version:
A vue3 component support big amount data list with high scroll performance.
1,095 lines (950 loc) • 30.7 kB
JavaScript
/**
* vue3-virtual-scroll-list v0.2.1
* open source under the MIT license
* https://github.com/reactjser/vue3-virtual-scroll-list#readme
*/
import { defineComponent, ref, createVNode, computed, onMounted, onUpdated, onUnmounted, watch, onBeforeMount, onActivated } from 'vue';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a 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);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
/**
* virtual list core calculating center
*/
var DIRECTION_TYPE = {
FRONT: 'FRONT',
// scroll up or left
BEHIND: 'BEHIND' // scroll down or right
};
var CALC_TYPE = {
INIT: 'INIT',
FIXED: 'FIXED',
DYNAMIC: 'DYNAMIC'
};
var LEADING_BUFFER = 2;
var Virtual = /*#__PURE__*/function () {
function Virtual(param, callUpdate) {
_classCallCheck(this, Virtual);
this.init(param, callUpdate);
}
_createClass(Virtual, [{
key: "init",
value: function init(param, callUpdate) {
// param data
this.param = param;
this.callUpdate = callUpdate; // size data
this.sizes = new Map();
this.firstRangeTotalSize = 0;
this.firstRangeAverageSize = 0;
this.lastCalcIndex = 0;
this.fixedSizeValue = 0;
this.calcType = CALC_TYPE.INIT; // scroll data
this.offset = 0;
this.direction = ''; // range data
this.range = Object.create(null);
if (param) {
this.checkRange(0, param.keeps - 1);
} // benchmark test data
// this.__bsearchCalls = 0
// this.__getIndexOffsetCalls = 0
}
}, {
key: "destroy",
value: function destroy() {
this.init(null, null);
} // return current render range
}, {
key: "getRange",
value: function getRange() {
var range = Object.create(null);
range.start = this.range.start;
range.end = this.range.end;
range.padFront = this.range.padFront;
range.padBehind = this.range.padBehind;
return range;
}
}, {
key: "isBehind",
value: function isBehind() {
return this.direction === DIRECTION_TYPE.BEHIND;
}
}, {
key: "isFront",
value: function isFront() {
return this.direction === DIRECTION_TYPE.FRONT;
} // return start index offset
}, {
key: "getOffset",
value: function getOffset(start) {
return (start < 1 ? 0 : this.getIndexOffset(start)) + this.param.slotHeaderSize;
}
}, {
key: "updateParam",
value: function updateParam(key, value) {
var _this = this;
if (this.param && key in this.param) {
// if uniqueIds change, find out deleted id and remove from size map
if (key === 'uniqueIds') {
this.sizes.forEach(function (v, key) {
if (!value.includes(key)) {
_this.sizes["delete"](key);
}
});
}
this.param[key] = value;
}
} // save each size map by id
}, {
key: "saveSize",
value: function saveSize(id, size) {
this.sizes.set(id, size); // we assume size type is fixed at the beginning and remember first size value
// if there is no size value different from this at next comming saving
// we think it's a fixed size list, otherwise is dynamic size list
if (this.calcType === CALC_TYPE.INIT) {
this.fixedSizeValue = size;
this.calcType = CALC_TYPE.FIXED;
} else if (this.calcType === CALC_TYPE.FIXED && this.fixedSizeValue !== size) {
this.calcType = CALC_TYPE.DYNAMIC; // it's no use at all
delete this.fixedSizeValue;
} // calculate the average size only in the first range
if (this.calcType !== CALC_TYPE.FIXED && typeof this.firstRangeTotalSize !== 'undefined') {
if (this.sizes.size < Math.min(this.param.keeps, this.param.uniqueIds.length)) {
this.firstRangeTotalSize = _toConsumableArray(this.sizes.values()).reduce(function (acc, val) {
return acc + val;
}, 0);
this.firstRangeAverageSize = Math.round(this.firstRangeTotalSize / this.sizes.size);
} else {
// it's done using
delete this.firstRangeTotalSize;
}
}
} // in some special situation (e.g. length change) we need to update in a row
// try goiong to render next range by a leading buffer according to current direction
}, {
key: "handleDataSourcesChange",
value: function handleDataSourcesChange() {
var start = this.range.start;
if (this.isFront()) {
start = start - LEADING_BUFFER;
} else if (this.isBehind()) {
start = start + LEADING_BUFFER;
}
start = Math.max(start, 0);
this.updateRange(this.range.start, this.getEndByStart(start));
} // when slot size change, we also need force update
}, {
key: "handleSlotSizeChange",
value: function handleSlotSizeChange() {
this.handleDataSourcesChange();
} // calculating range on scroll
}, {
key: "handleScroll",
value: function handleScroll(offset) {
this.direction = offset < this.offset ? DIRECTION_TYPE.FRONT : DIRECTION_TYPE.BEHIND;
this.offset = offset;
if (!this.param) {
return;
}
if (this.direction === DIRECTION_TYPE.FRONT) {
this.handleFront();
} else if (this.direction === DIRECTION_TYPE.BEHIND) {
this.handleBehind();
}
} // ----------- public method end -----------
}, {
key: "handleFront",
value: function handleFront() {
var overs = this.getScrollOvers(); // should not change range if start doesn't exceed overs
if (overs > this.range.start) {
return;
} // move up start by a buffer length, and make sure its safety
var start = Math.max(overs - this.param.buffer, 0);
this.checkRange(start, this.getEndByStart(start));
}
}, {
key: "handleBehind",
value: function handleBehind() {
var overs = this.getScrollOvers(); // range should not change if scroll overs within buffer
if (overs < this.range.start + this.param.buffer) {
return;
}
this.checkRange(overs, this.getEndByStart(overs));
} // return the pass overs according to current scroll offset
}, {
key: "getScrollOvers",
value: function getScrollOvers() {
// if slot header exist, we need subtract its size
var offset = this.offset - this.param.slotHeaderSize;
if (offset <= 0) {
return 0;
} // if is fixed type, that can be easily
if (this.isFixedType()) {
return Math.floor(offset / this.fixedSizeValue);
}
var low = 0;
var middle = 0;
var middleOffset = 0;
var high = this.param.uniqueIds.length;
while (low <= high) {
// this.__bsearchCalls++
middle = low + Math.floor((high - low) / 2);
middleOffset = this.getIndexOffset(middle);
if (middleOffset === offset) {
return middle;
} else if (middleOffset < offset) {
low = middle + 1;
} else if (middleOffset > offset) {
high = middle - 1;
}
}
return low > 0 ? --low : 0;
} // return a scroll offset from given index, can efficiency be improved more here?
// although the call frequency is very high, its only a superposition of numbers
}, {
key: "getIndexOffset",
value: function getIndexOffset(givenIndex) {
if (!givenIndex) {
return 0;
}
var offset = 0;
var indexSize = 0;
for (var index = 0; index < givenIndex; index++) {
// this.__getIndexOffsetCalls++
indexSize = this.sizes.get(this.param.uniqueIds[index]);
offset = offset + (typeof indexSize === 'number' ? indexSize : this.getEstimateSize());
} // remember last calculate index
this.lastCalcIndex = Math.max(this.lastCalcIndex, givenIndex - 1);
this.lastCalcIndex = Math.min(this.lastCalcIndex, this.getLastIndex());
return offset;
} // is fixed size type
}, {
key: "isFixedType",
value: function isFixedType() {
return this.calcType === CALC_TYPE.FIXED;
} // return the real last index
}, {
key: "getLastIndex",
value: function getLastIndex() {
return this.param.uniqueIds.length - 1;
} // in some conditions range is broke, we need correct it
// and then decide whether need update to next range
}, {
key: "checkRange",
value: function checkRange(start, end) {
var keeps = this.param.keeps;
var total = this.param.uniqueIds.length; // datas less than keeps, render all
if (total <= keeps) {
start = 0;
end = this.getLastIndex();
} else if (end - start < keeps - 1) {
// if range length is less than keeps, corrent it base on end
start = end - keeps + 1;
}
if (this.range.start !== start) {
this.updateRange(start, end);
}
} // setting to a new range and rerender
}, {
key: "updateRange",
value: function updateRange(start, end) {
this.range.start = start;
this.range.end = end;
this.range.padFront = this.getPadFront();
this.range.padBehind = this.getPadBehind();
this.callUpdate(this.getRange());
} // return end base on start
}, {
key: "getEndByStart",
value: function getEndByStart(start) {
var theoryEnd = start + this.param.keeps - 1;
var truelyEnd = Math.min(theoryEnd, this.getLastIndex());
return truelyEnd;
} // return total front offset
}, {
key: "getPadFront",
value: function getPadFront() {
if (this.isFixedType()) {
return this.fixedSizeValue * this.range.start;
} else {
return this.getIndexOffset(this.range.start);
}
} // return total behind offset
}, {
key: "getPadBehind",
value: function getPadBehind() {
var end = this.range.end;
var lastIndex = this.getLastIndex();
if (this.isFixedType()) {
return (lastIndex - end) * this.fixedSizeValue;
} // if it's all calculated, return the exactly offset
if (this.lastCalcIndex === lastIndex) {
return this.getIndexOffset(lastIndex) - this.getIndexOffset(end);
} else {
// if not, use a estimated value
return (lastIndex - end) * this.getEstimateSize();
}
} // get the item estimate size
}, {
key: "getEstimateSize",
value: function getEstimateSize() {
return this.isFixedType() ? this.fixedSizeValue : this.firstRangeAverageSize || this.param.estimateSize;
}
}]);
return Virtual;
}();
/**
* props declaration for default, item and slot component
*/
var VirtualProps = {
dataKey: {
type: [String, Function],
required: true
},
dataSources: {
type: Array,
required: true,
"default": function _default() {
return [];
}
},
dataComponent: {
type: [Object, Function],
required: true
},
keeps: {
type: Number,
"default": 30
},
extraProps: {
type: Object
},
estimateSize: {
type: Number,
"default": 50
},
direction: {
type: String,
"default": 'vertical' // the other value is horizontal
},
start: {
type: Number,
"default": 0
},
offset: {
type: Number,
"default": 0
},
topThreshold: {
type: Number,
"default": 0
},
bottomThreshold: {
type: Number,
"default": 0
},
pageMode: {
type: Boolean,
"default": false
},
rootTag: {
type: String,
"default": 'div'
},
wrapTag: {
type: String,
"default": 'div'
},
wrapClass: {
type: String,
"default": 'wrap'
},
wrapStyle: {
type: Object
},
itemTag: {
type: String,
"default": 'div'
},
itemClass: {
type: String,
"default": ''
},
itemClassAdd: {
type: Function
},
itemStyle: {
type: Object
},
headerTag: {
type: String,
"default": 'div'
},
headerClass: {
type: String,
"default": ''
},
headerStyle: {
type: Object
},
footerTag: {
type: String,
"default": 'div'
},
footerClass: {
type: String,
"default": ''
},
footerStyle: {
type: Object
},
itemScopedSlots: {
type: Object
}
};
var ItemProps = {
index: {
type: Number
},
event: {
type: String
},
tag: {
type: String
},
horizontal: {
type: Boolean
},
source: {
type: Object
},
component: {
type: [Object, Function]
},
uniqueKey: {
type: [String, Number]
},
extraProps: {
type: Object
},
scopedSlots: {
type: Object
}
};
var SlotProps = {
event: {
type: String
},
uniqueKey: {
type: String
},
tag: {
type: String
},
horizontal: {
type: Boolean
}
};
var useResizeChange = function useResizeChange(props, rootRef, emit) {
var resizeObserver = null;
var shapeKey = computed(function () {
return props.horizontal ? 'offsetWidth' : 'offsetHeight';
});
var getCurrentSize = function getCurrentSize() {
return rootRef.value ? rootRef.value[shapeKey.value] : 0;
}; // tell parent current size identify by unqiue key
var dispatchSizeChange = function dispatchSizeChange() {
var event = props.event,
uniqueKey = props.uniqueKey,
hasInitial = props.hasInitial;
emit(event, uniqueKey, getCurrentSize(), hasInitial);
};
onMounted(function () {
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(function () {
dispatchSizeChange();
});
rootRef.value && resizeObserver.observe(rootRef.value);
}
});
onUpdated(function () {
dispatchSizeChange();
});
onUnmounted(function () {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
};
var Item = defineComponent({
name: 'VirtualListItem',
props: ItemProps,
emits: ['itemResize'],
setup: function setup(props, _ref) {
var emit = _ref.emit;
var rootRef = ref(null);
useResizeChange(props, rootRef, emit);
return function () {
var Tag = props.tag,
Comp = props.component,
_props$extraProps = props.extraProps,
extraProps = _props$extraProps === void 0 ? {} : _props$extraProps,
index = props.index,
source = props.source,
_props$scopedSlots = props.scopedSlots,
scopedSlots = _props$scopedSlots === void 0 ? {} : _props$scopedSlots,
uniqueKey = props.uniqueKey;
var mergedProps = _objectSpread2(_objectSpread2({}, extraProps), {}, {
source: source,
index: index
});
return createVNode(Tag, {
"key": uniqueKey,
"ref": rootRef
}, {
"default": function _default() {
return [createVNode(Comp, _objectSpread2(_objectSpread2({}, mergedProps), {}, {
"scopedSlots": scopedSlots
}), null)];
}
});
};
}
});
var Slot = defineComponent({
name: 'VirtualListSlot',
props: SlotProps,
emits: ['slotResize'],
setup: function setup(props, _ref2) {
var slots = _ref2.slots,
emit = _ref2.emit;
var rootRef = ref(null);
useResizeChange(props, rootRef, emit);
return function () {
var _slots$default;
var Tag = props.tag,
uniqueKey = props.uniqueKey;
return createVNode(Tag, {
"ref": rootRef,
"key": uniqueKey
}, {
"default": function _default() {
return [(_slots$default = slots["default"]) === null || _slots$default === void 0 ? void 0 : _slots$default.call(slots)];
}
});
};
}
});
var EVENT_TYPE;
(function (EVENT_TYPE) {
EVENT_TYPE["ITEM"] = "itemResize";
EVENT_TYPE["SLOT"] = "slotResize";
})(EVENT_TYPE || (EVENT_TYPE = {}));
var SLOT_TYPE;
(function (SLOT_TYPE) {
SLOT_TYPE["HEADER"] = "thead";
SLOT_TYPE["FOOTER"] = "tfoot";
})(SLOT_TYPE || (SLOT_TYPE = {}));
var VirtualList = defineComponent({
name: 'VirtualList',
props: VirtualProps,
setup: function setup(props, _ref) {
var emit = _ref.emit,
slots = _ref.slots,
expose = _ref.expose;
var isHorizontal = props.direction === 'horizontal';
var directionKey = isHorizontal ? 'scrollLeft' : 'scrollTop';
var range = ref(null);
var root = ref();
var shepherd = ref(null);
var virtual;
/**
* watch
*/
watch(function () {
return props.dataSources.length;
}, function () {
virtual.updateParam('uniqueIds', getUniqueIdFromDataSources());
virtual.handleDataSourcesChange();
});
watch(function () {
return props.keeps;
}, function (newValue) {
virtual.updateParam('keeps', newValue);
virtual.handleSlotSizeChange();
});
watch(function () {
return props.start;
}, function (newValue) {
scrollToIndex(newValue);
});
watch(function () {
return props.offset;
}, function (newValue) {
return scrollToOffset(newValue);
});
/**
* methods
*/
// get item size by id
var getSize = function getSize(id) {
return virtual.sizes.get(id);
};
var getOffset = function getOffset() {
if (props.pageMode) {
return document.documentElement[directionKey] || document.body[directionKey];
} else {
return root.value ? Math.ceil(root.value[directionKey]) : 0;
}
}; // return client viewport size
var getClientSize = function getClientSize() {
var key = isHorizontal ? 'clientWidth' : 'clientHeight';
if (props.pageMode) {
return document.documentElement[key] || document.body[key];
} else {
return root.value ? Math.ceil(root.value[key]) : 0;
}
}; // return all scroll size
var getScrollSize = function getScrollSize() {
var key = isHorizontal ? 'scrollWidth' : 'scrollHeight';
if (props.pageMode) {
return document.documentElement[key] || document.body[key];
} else {
return root.value ? Math.ceil(root.value[key]) : 0;
}
};
var emitEvent = function emitEvent(offset, clientSize, scrollSize, evt) {
emit('scroll', evt, virtual.getRange());
if (virtual.isFront() && !!props.dataSources.length && offset - props.topThreshold <= 0) {
emit('totop');
} else if (virtual.isBehind() && offset + clientSize + props.bottomThreshold >= scrollSize) {
emit('tobottom');
}
};
var onScroll = function onScroll(evt) {
var offset = getOffset();
var clientSize = getClientSize();
var scrollSize = getScrollSize(); // iOS scroll-spring-back behavior will make direction mistake
if (offset < 0 || offset + clientSize > scrollSize + 1 || !scrollSize) {
return;
}
virtual.handleScroll(offset);
emitEvent(offset, clientSize, scrollSize, evt);
};
var getUniqueIdFromDataSources = function getUniqueIdFromDataSources() {
var dataKey = props.dataKey,
_props$dataSources = props.dataSources,
dataSources = _props$dataSources === void 0 ? [] : _props$dataSources;
return dataSources.map(function (dataSource) {
return typeof dataKey === 'function' ? dataKey(dataSource) : dataSource[dataKey];
});
};
var onRangeChanged = function onRangeChanged(newRange) {
range.value = newRange;
};
var installVirtual = function installVirtual() {
virtual = new Virtual({
slotHeaderSize: 0,
slotFooterSize: 0,
keeps: props.keeps,
estimateSize: props.estimateSize,
buffer: Math.round(props.keeps / 3),
// recommend for a third of keeps
uniqueIds: getUniqueIdFromDataSources()
}, onRangeChanged); // sync initial range
range.value = virtual.getRange();
}; // set current scroll position to a expectant index
var scrollToIndex = function scrollToIndex(index) {
// scroll to bottom
if (index >= props.dataSources.length - 1) {
scrollToBottom();
} else {
var offset = virtual.getOffset(index);
scrollToOffset(offset);
}
}; // set current scroll position to a expectant offset
var scrollToOffset = function scrollToOffset(offset) {
if (props.pageMode) {
document.body[directionKey] = offset;
document.documentElement[directionKey] = offset;
} else {
if (root.value) {
root.value[directionKey] = offset;
}
}
}; // get the real render slots based on range data
// in-place patch strategy will try to reuse components as possible
// so those components that are reused will not trigger lifecycle mounted
var getRenderSlots = function getRenderSlots() {
var slots = [];
var _range$value = range.value,
start = _range$value.start,
end = _range$value.end;
var dataSources = props.dataSources,
dataKey = props.dataKey,
itemClass = props.itemClass,
itemTag = props.itemTag,
itemStyle = props.itemStyle,
extraProps = props.extraProps,
dataComponent = props.dataComponent,
itemScopedSlots = props.itemScopedSlots;
for (var index = start; index <= end; index++) {
var dataSource = dataSources[index];
if (dataSource) {
var uniqueKey = typeof dataKey === 'function' ? dataKey(dataSource) : dataSource[dataKey];
if (typeof uniqueKey === 'string' || typeof uniqueKey === 'number') {
slots.push(createVNode(Item, {
"index": index,
"tag": itemTag,
"event": EVENT_TYPE.ITEM,
"horizontal": isHorizontal,
"uniqueKey": uniqueKey,
"source": dataSource,
"extraProps": extraProps,
"component": dataComponent,
"scopedSlots": itemScopedSlots,
"style": itemStyle,
"class": "".concat(itemClass).concat(props.itemClassAdd ? ' ' + props.itemClassAdd(index) : ''),
"onItemResize": onItemResized
}, null));
} else {
console.warn("Cannot get the data-key '".concat(dataKey, "' from data-sources."));
}
} else {
console.warn("Cannot get the index '".concat(index, "' from data-sources."));
}
}
return slots;
}; // event called when each item mounted or size changed
var onItemResized = function onItemResized(id, size) {
virtual.saveSize(id, size);
emit('resized', id, size);
}; // event called when slot mounted or size changed
var onSlotResized = function onSlotResized(type, size, hasInit) {
if (type === SLOT_TYPE.HEADER) {
virtual.updateParam('slotHeaderSize', size);
} else if (type === SLOT_TYPE.FOOTER) {
virtual.updateParam('slotFooterSize', size);
}
if (hasInit) {
virtual.handleSlotSizeChange();
}
}; // set current scroll position to bottom
var scrollToBottom = function scrollToBottom() {
if (shepherd.value) {
var offset = shepherd.value[isHorizontal ? 'offsetLeft' : 'offsetTop'];
scrollToOffset(offset); // check if it's really scrolled to the bottom
// maybe list doesn't render and calculate to last range
// so we need retry in next event loop until it really at bottom
setTimeout(function () {
if (getOffset() + getClientSize() < getScrollSize()) {
scrollToBottom();
}
}, 3);
}
}; // when using page mode we need update slot header size manually
// taking root offset relative to the browser as slot header size
var updatePageModeFront = function updatePageModeFront() {
if (root.value) {
var rect = root.value.getBoundingClientRect();
var defaultView = root.value.ownerDocument.defaultView;
var offsetFront = isHorizontal ? rect.left + defaultView.pageXOffset : rect.top + defaultView.pageYOffset;
virtual.updateParam('slotHeaderSize', offsetFront);
}
}; // get the total number of stored (rendered) items
var getSizes = function getSizes() {
return virtual.sizes.size;
};
/**
* life cycles
*/
onBeforeMount(function () {
installVirtual();
}); // set back offset when awake from keep-alive
onActivated(function () {
scrollToOffset(virtual.offset);
});
onMounted(function () {
// set position
if (props.start) {
scrollToIndex(props.start);
} else if (props.offset) {
scrollToOffset(props.offset);
} // in page mode we bind scroll event to document
if (props.pageMode) {
updatePageModeFront();
document.addEventListener('scroll', onScroll, {
passive: false
});
}
});
onUnmounted(function () {
virtual.destroy();
if (props.pageMode) {
document.removeEventListener('scroll', onScroll);
}
});
/**
* public methods
*/
expose({
scrollToBottom: scrollToBottom,
getSizes: getSizes,
getSize: getSize,
getOffset: getOffset,
getScrollSize: getScrollSize,
getClientSize: getClientSize,
scrollToOffset: scrollToOffset,
scrollToIndex: scrollToIndex
});
return function () {
var pageMode = props.pageMode,
RootTag = props.rootTag,
WrapTag = props.wrapTag,
wrapClass = props.wrapClass,
wrapStyle = props.wrapStyle,
headerTag = props.headerTag,
headerClass = props.headerClass,
headerStyle = props.headerStyle,
footerTag = props.footerTag,
footerClass = props.footerClass,
footerStyle = props.footerStyle;
var _ref2 = range.value,
padFront = _ref2.padFront,
padBehind = _ref2.padBehind;
var paddingStyle = {
padding: isHorizontal ? "0px ".concat(padBehind, "px 0px ").concat(padFront, "px") : "".concat(padFront, "px 0px ").concat(padBehind, "px")
};
var wrapperStyle = wrapStyle ? Object.assign({}, wrapStyle, paddingStyle) : paddingStyle;
var header = slots.header,
footer = slots.footer;
return createVNode(RootTag, {
"ref": root,
"onScroll": !pageMode && onScroll
}, {
"default": function _default() {
return [header && createVNode(Slot, {
"class": headerClass,
"style": headerStyle,
"tag": headerTag,
"event": EVENT_TYPE.SLOT,
"uniqueKey": SLOT_TYPE.HEADER,
"onSlotResize": onSlotResized
}, {
"default": function _default() {
return [header()];
}
}), createVNode(WrapTag, {
"class": wrapClass,
"style": wrapperStyle
}, {
"default": function _default() {
return [getRenderSlots()];
}
}), footer && createVNode(Slot, {
"class": footerClass,
"style": footerStyle,
"tag": footerTag,
"event": EVENT_TYPE.SLOT,
"uniqueKey": SLOT_TYPE.FOOTER,
"onSlotResize": onSlotResized
}, {
"default": function _default() {
return [footer()];
}
}), createVNode("div", {
"ref": shepherd,
"style": {
width: isHorizontal ? '0px' : '100%',
height: isHorizontal ? '100%' : '0px'
}
}, null)];
}
});
};
}
});
export { VirtualList as default };