vanilla-recycler-view
Version:
high performance UI rendering library for web browser
303 lines • 10.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VanillaRecyclerView = exports.DIRECTION = exports.DEFAULT_ITEM_SIZE = void 0;
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
require("../modules/zoom-listener");
const _1 = require("./");
exports.DEFAULT_ITEM_SIZE = 50;
/**
* @deprecated
*/
var DIRECTION;
(function (DIRECTION) {
DIRECTION["VERTICAL"] = "vertical";
DIRECTION["HORIZONTAL"] = "horizontal";
})(DIRECTION = exports.DIRECTION || (exports.DIRECTION = {}));
class VanillaRecyclerView {
constructor(root, options) {
/* all virtual elements */
this.virtualElements = [];
/* current mounted virtual elements */
this.mountedVirtualElements = [];
/* pending unmount */
this.pendingUnmount = [];
/* unmounted, reusable elements */
this.reusables = [];
/* allocate arguments */
this.root = root;
/* define default direction */
if (options.direction) {
this._direction = options.direction;
}
else {
this._direction = DIRECTION.VERTICAL;
}
/* define default preload */
if (options.preload) {
this._preload = options.preload;
}
else {
this._preload = exports.DEFAULT_ITEM_SIZE;
}
/* define default size */
if (options.size) {
this._size = options.size;
}
else {
this._size = exports.DEFAULT_ITEM_SIZE;
}
/* allocate renderer */
this._renderer = options.renderer;
/* apply class to element */
this.root.classList.add('recycler_view_root');
if (this._direction === DIRECTION.VERTICAL) {
this.root.classList.add(DIRECTION.VERTICAL);
}
else if (this._direction === DIRECTION.HORIZONTAL) {
this.root.classList.add(DIRECTION.HORIZONTAL);
}
/* create wrapper element for scroll area */
this.container = document.createElement('div');
this.container.classList.add('recycler_view_container');
/* create virtual element per data */
this.setData(options.data);
/* append element */
this.root.appendChild(this.container);
// bind scroll, zoom event
this.root.addEventListener('scroll', this.onScroll.bind(this));
document.body.addEventListener('zoom', () => this.onScroll());
}
onScroll() {
const { scrollTop, scrollLeft } = this.root;
const { height: screenHeight, width: screenWidth, } = this.root.getBoundingClientRect();
const max = this.getMaxScrollSize();
const maxScrollSize = (() => {
let clientSize = 0;
switch (this._direction) {
case DIRECTION.VERTICAL:
clientSize = this.root.clientHeight;
break;
case DIRECTION.HORIZONTAL:
clientSize = this.root.clientWidth;
break;
}
return max > clientSize ? max : clientSize;
})();
let currentScrollSize = 0;
let startSize = 0;
let endSize = 0;
/* calculate direction */
switch (this._direction) {
case DIRECTION.VERTICAL:
currentScrollSize = scrollTop + screenHeight;
startSize = scrollTop - this._preload;
endSize = scrollTop + screenHeight + this._preload;
break;
case DIRECTION.HORIZONTAL:
currentScrollSize = scrollLeft + screenWidth;
startSize = scrollLeft - this._preload;
endSize = scrollLeft + screenWidth + this._preload;
break;
}
// todo fix
// if (currentScrollSize > maxScrollSize) {
// switch (this._direction) {
// case DIRECTION.VERTICAL:
// this.root.scrollTop = maxScrollSize - screenHeight;
// break;
// case DIRECTION.HORIZONTAL:
// this.root.scrollLeft = maxScrollSize - screenWidth;
// break;
// }
// console.log(currentScrollSize, maxScrollSize);
// this.onScroll();
// return;
// }
/**
* todo
* use map to save some performance
*/
const shouldMount = this.virtualElements.filter((virtualDom) => {
return (virtualDom.start >= startSize &&
virtualDom.start + virtualDom.size <= endSize);
});
const mounted = [];
const toMount = [];
shouldMount.forEach((virtualDom) => {
if (this.mountedVirtualElements.indexOf(virtualDom) === -1) {
toMount.push(virtualDom);
}
else {
mounted.push(virtualDom);
}
});
const toUnmount = this.pendingUnmount.concat(this.mountedVirtualElements.filter((virtualDom) => shouldMount.indexOf(virtualDom) === -1));
/**
* unmount and save reusable dom
*/
toUnmount.forEach((virtualDom) => {
if (virtualDom.isMounted()) {
const reusable = virtualDom.unmountRenderer();
this.reusables.push(reusable);
}
});
/**
* mount
*/
toMount.forEach((virtualDom) => {
const reusable = (() => {
var _a;
const reusable = this.getNextReusable();
const refreshed = reusable &&
reusable.renderer.onMount &&
reusable.renderer.onMount({
api: this,
data: virtualDom.data,
index: virtualDom.index,
});
if (reusable && refreshed) {
return reusable;
}
else {
(_a = reusable === null || reusable === void 0 ? void 0 : reusable.wrapperElement.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(reusable.wrapperElement);
return this.createReusable(virtualDom);
}
})();
virtualDom.mountRenderer(reusable);
});
/**
* update mounted renderer position
*/
mounted.forEach((virtualDom) => {
virtualDom.updatePosition();
});
/**
* remove overflowed reusables
*/
this.reusables.forEach((reusable) => {
var _a;
let start = 0;
switch (this._direction) {
case DIRECTION.VERTICAL:
start = (0, _1.parsePx)(reusable.wrapperElement.style.top) + screenHeight;
break;
case DIRECTION.HORIZONTAL:
start = (0, _1.parsePx)(reusable.wrapperElement.style.left) + screenWidth;
break;
}
if (start > maxScrollSize) {
(_a = reusable.wrapperElement.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(reusable.wrapperElement);
}
});
this.pendingUnmount = [];
this.mountedVirtualElements = shouldMount;
}
/**
* parse size option
* @param data
*/
getSize(index, data) {
if (typeof this._size === 'function') {
return this._size({ api: this, data: data, index: index });
}
else {
return this._size;
}
}
createReusable(virtualElement) {
const container = document.createElement('div');
container.classList.add('recycler_view_item');
const renderer = new this._renderer();
renderer.initialize({
api: this,
data: virtualElement.data,
index: virtualElement.index,
});
const element = renderer.getLayout();
container.appendChild(element);
return {
wrapperElement: container,
renderer: renderer,
};
}
getNextReusable() {
if (this.reusables && this.reusables.length) {
return this.reusables.splice(0, 1)[0];
}
else {
return null;
}
}
setData(data) {
this.reusables = [];
this.pendingUnmount = this.virtualElements.slice();
this.virtualElements = [];
this.calculateSize();
this.onScroll();
this.virtualElements = data.map((data) => new _1.VirtualElement(this, data));
this.calculateSize();
this.onScroll();
}
calculateSize() {
const viewSize = this.virtualElements.reduce((start, virtualDom, index) => {
const currentSize = this.getSize(index, virtualDom.data);
virtualDom.setIndex(index);
virtualDom.setPosition(start, currentSize);
return start + currentSize;
}, 0);
switch (this._direction) {
case DIRECTION.VERTICAL:
this.container.style.height = (0, _1.toPx)(viewSize);
break;
case DIRECTION.HORIZONTAL:
this.container.style.width = (0, _1.toPx)(viewSize);
break;
default:
throw new Error('not supported direction');
}
}
getMaxScrollSize() {
switch (this._direction) {
case DIRECTION.VERTICAL:
return (0, _1.parsePx)(this.container.style.height);
case DIRECTION.HORIZONTAL:
return (0, _1.parsePx)(this.container.style.width);
}
}
/**
* @deprecated
*/
splice(start, end) {
if (end === undefined) {
this.pendingUnmount = this.virtualElements.splice(start);
}
else {
this.pendingUnmount = this.virtualElements.splice(start, end);
}
this.calculateSize();
this.onScroll();
}
/**
* @deprecated
*/
insert(index, ...data) {
const prefix = this.virtualElements.slice(0, index);
const surfix = this.virtualElements.slice(index);
const virtualElements = data.map((data) => new _1.VirtualElement(this, data));
this.virtualElements = [...prefix, ...virtualElements, ...surfix];
this.calculateSize();
this.onScroll();
}
/**
* @deprecated
*/
push(...items) {
const virtualElements = items.map((item) => new _1.VirtualElement(this, item));
this.virtualElements.push(...virtualElements);
this.calculateSize();
this.onScroll();
}
}
exports.VanillaRecyclerView = VanillaRecyclerView;
//# sourceMappingURL=recycler-view.js.map