UNPKG

vanilla-recycler-view

Version:

high performance UI rendering library for web browser

303 lines 10.8 kB
"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