@kelvininc/ui-components
Version:
Kelvin UI Components
229 lines (228 loc) • 8.72 kB
JavaScript
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"
}];
}
}