UNPKG

@kelvininc/ui-components

Version:
229 lines (228 loc) 8.72 kB
import { Host, h } from "@stencil/core"; import { debounce, throttle } from "lodash-es"; import { buildElement } from "./virtualized-list.helper"; export class KvVirtualizedList { constructor() { /** @inheritdoc */ this.overscanCount = 5; // create an Observer instance this.resizeObserver = new ResizeObserver(() => this.debounceResize()); this.totalHeight = 1; this.scrollTop = 0; this.debounceResize = debounce(() => this.updateVisibleElements(), 100); this.throttledScroll = throttle(() => { this.updateScrollTop(); this.updateVisibleElements(); }, 16, { trailing: true }); this.updateScrollTop = () => { var _a, _b; this.scrollTop = (_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0; }; this.updateVisibleElements = () => { this.elements = this.getVisibleElements(); }; this.getVisibleElements = () => { var _a, _b; const containerHeight = (_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0; const elements = []; let accumulator = 0; const overscanHeight = this.overscanCount * this.itemHeight; for (let index = 0; index < this.itemCount; index++) { let itemkey = this.getItemKey(index); // Check if the item is above the viewport if (this.scrollTop - overscanHeight > accumulator + this.itemHeight) { accumulator += this.itemHeight; continue; } // Check if the item is below the viewport if (this.scrollTop + containerHeight + overscanHeight < accumulator) { break; } elements.push(buildElement(index, itemkey, this.itemHeight, accumulator, this.renderItem)); accumulator += this.itemHeight; } return elements; }; } updateTotalHeight() { this.totalHeight = this.itemHeight * this.itemCount; } renderItemHandle() { this.updateVisibleElements(); } componentDidLoad() { this.updateTotalHeight(); this.updateVisibleElements(); } componentDidRender() { if (!this.containerElement) return; // Only attach listeners if not already attached if (!this.containerElement.onscroll) { this.containerElement.addEventListener('scroll', this.throttledScroll); } // ResizeObserver will just ignore if already observing this.resizeObserver.observe(this.containerElement); } disconnectedCallback() { if (this.containerElement) { this.containerElement.removeEventListener('scroll', this.throttledScroll); this.resizeObserver.unobserve(this.containerElement); } } render() { return (h(Host, { key: '30f68c78571661844c8015e58a423d9324532152' }, h("div", { key: '10c8a72bb521a89e1ada05e04304c88f2a35526e', class: "container", ref: el => (this.containerElement = el) }, h("div", { key: 'f70bccef7d6c25e484c2b1639059b60afb3d677d', class: "inner", style: { height: `${this.totalHeight}px` } }, this.elements)))); } static get is() { return "kv-virtualized-list"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["virtualized-list.scss"] }; } static get styleUrls() { return { "$": ["virtualized-list.css"] }; } static get properties() { return { "itemCount": { "type": "number", "attribute": "item-count", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "inheritdoc", "text": undefined }], "text": "(required) Defines the total number of items rendered in the list" }, "getter": false, "setter": false, "reflect": true }, "itemHeight": { "type": "number", "attribute": "item-height", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "inheritdoc", "text": undefined }], "text": "(required) Defines the estimated height in pixels of an item" }, "getter": false, "setter": false, "reflect": true }, "getItemKey": { "type": "unknown", "attribute": "get-item-key", "mutable": false, "complexType": { "original": "(index: number) => string", "resolved": "(index: number) => string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "inheritdoc", "text": undefined }], "text": "(required) Defines the given item key" }, "getter": false, "setter": false }, "renderItem": { "type": "unknown", "attribute": "render-item", "mutable": false, "complexType": { "original": "RenderItemFunc", "resolved": "(index: number) => ChildType | Promise<ChildType> | ChildType[] | Promise<ChildType[]> | Promise<ChildType>[]", "references": { "RenderItemFunc": { "location": "import", "path": "./virtualized-list.types", "id": "src/components/virtualized-list/virtualized-list.types.ts::RenderItemFunc" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "inheritdoc", "text": undefined }], "text": "(required) Defines the item render function" }, "getter": false, "setter": false }, "overscanCount": { "type": "number", "attribute": "overscan-count", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "inheritdoc", "text": undefined }], "text": "(optional) The number of items outside the viewport that are rendered. Default: `5`" }, "getter": false, "setter": false, "reflect": true, "defaultValue": "5" } }; } static get states() { return { "totalHeight": {}, "scrollTop": {}, "elements": {} }; } static get watchers() { return [{ "propName": "itemHeight", "methodName": "updateTotalHeight" }, { "propName": "itemCount", "methodName": "updateTotalHeight" }, { "propName": "renderItem", "methodName": "renderItemHandle" }]; } }