UNPKG

listine

Version:

Angular virtual scroll component supporting variable item heights.

1 lines 13.8 kB
{"version":3,"file":"listine.mjs","sources":["../../../projects/listine/src/lib/listtine-virtual/variable-virtual-scroll/listine-virtual-scroll.component.ts","../../../projects/listine/src/lib/listtine-virtual/variable-virtual-scroll/listine-virtual-scroll.component.html","../../../projects/listine/src/public-api.ts","../../../projects/listine/src/listine.ts"],"sourcesContent":["import {\n Component,\n Input,\n TemplateRef,\n ViewChild,\n ElementRef,\n AfterViewInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n ViewChildren,\n QueryList,\n EventEmitter,\n Output,\n OnChanges,\n SimpleChanges,\n OnInit,\n OnDestroy,\n NgZone,\n} from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\n\n/**\n * `s73-variable-virtual-scroll`\n *\n * A lightweight, customizable virtual scroll component that supports variable item heights.\n * Efficiently renders only visible items to improve performance for large lists.\n *\n * ## Inputs:\n * - `items`: List of data items to display\n * - `viewportHeight`: Height of the scrollable container (default: 400px)\n * - `buffer`: Number of extra items rendered above and below the viewport for smooth scrolling (default: 5)\n * - `itemTemplate`: Angular template for rendering each item\n * - `initialItemHeight`: Default item height before measurement (default: 50px)\n *\n * ## Outputs:\n * - `scrollEmitter`: Emits scroll position on every scroll event\n */\n@Component({\n selector: \"listine-variable-virtual-scroll\",\n standalone: true,\n imports: [CommonModule],\n templateUrl: \"./listine-virtual-scroll.component.html\",\n styleUrls: [\"./listine-virtual-scroll.component.scss\"],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ListineVirtualScrollModule\n implements OnInit, AfterViewInit, OnChanges, OnDestroy\n{\n /** List of all items to render */\n @Input() items: any[] = [];\n\n /** Height of the scrollable viewport in pixels */\n @Input() viewportHeight = 400;\n\n /** Number of extra items to render above and below the viewport */\n @Input() buffer = 5;\n\n /** Template reference for rendering each item */\n @Input() itemTemplate: TemplateRef<any>;\n\n /** Flag to track if panel is open */\n @Input() panelOpen: boolean = false;\n\n /** Initial estimated height of each item before actual measurement */\n @Input() initialItemHeight = 50;\n\n /** Emits scroll position whenever user scrolls */\n @Output() scrollEmitter = new EventEmitter<any>();\n\n /** Emits when user scrolls to the end of the list */\n @Output() scrollToEnd = new EventEmitter<void>();\n\n /** Reference to the scrolling container */\n @ViewChild(\"scroller\", { static: true }) scrollerRef!: ElementRef;\n\n /** QueryList of rendered item elements (used for height measurement) */\n @ViewChildren(\"itemElement\", { read: ElementRef })\n itemElements!: QueryList<ElementRef>;\n\n /** Stores measured heights of items */\n itemHeights: number[] = [];\n\n /** Stores calculated top offset for each item */\n itemTops: number[] = [];\n\n /** Total height of all items (used to simulate full scrollable area) */\n totalContentHeight = 0;\n\n /** Index of the first visible item (including buffer) */\n visibleStart = 0;\n\n /** Index of the last visible item (including buffer) */\n visibleEnd = 0;\n\n /** Items currently visible in the viewport */\n visibleItems: any[] = [];\n\n /** ResizeObserver to watch for item height changes */\n private resizeObserver = new ResizeObserver(() => {\n this.ngZone.run(() => {\n this.measureItemHeights();\n });\n });\n\n /** Previous scroll position to detect actual scrolling */\n private previousScrollTop = 0;\n\n constructor(private cdr: ChangeDetectorRef, private ngZone: NgZone) {}\n\n /** Initialize item heights on component initialization */\n ngOnInit(): void {\n this.initializeHeights();\n }\n\n /**\n * Respond to changes in input bindings (mainly the `items` array)\n * @param changes - input property changes\n */\n ngOnChanges(changes: SimpleChanges): void {\n if (changes[\"items\"]) {\n this.items = changes[\"items\"].currentValue;\n this.initializeHeights();\n\n setTimeout(() => {\n this.calculateHeights();\n this.onScroll();\n });\n }\n if (changes[\"panelOpen\"] && changes[\"panelOpen\"]?.currentValue) {\n this.panelOpen = changes[\"panelOpen\"]?.currentValue;\n this.onScroll();\n }\n }\n\n /**\n * Called after view initialization. Triggers initial height calculation.\n */\n ngAfterViewInit() {\n setTimeout(() => {\n this.calculateHeights();\n this.onScroll();\n });\n }\n\n /**\n * Disconnects ResizeObserver on component destroy\n */\n ngOnDestroy(): void {\n this.resizeObserver.disconnect();\n }\n\n /** Initializes item height and position tracking arrays */\n initializeHeights() {\n this.itemHeights = this.items.map(() => this.initialItemHeight);\n this.updateItemTops();\n }\n\n /** Recalculates item top positions and content height */\n calculateHeights() {\n this.updateItemTops();\n }\n\n /** Updates top positions of all items based on their current heights */\n updateItemTops() {\n this.itemTops = [];\n let top = 0;\n for (const height of this.itemHeights) {\n this.itemTops.push(top);\n top += height;\n }\n this.totalContentHeight = top;\n }\n\n /**\n * Returns the top position of an item by index\n * @param index - index of the item\n */\n getItemTop(index: number): number {\n return this.itemTops[index] || 0;\n }\n\n /** Scroll event handler: calculates which items should be visible */\n onScroll() {\n const scrollTop = this.scrollerRef.nativeElement.scrollTop;\n const viewportBottom = scrollTop + this.viewportHeight;\n\n let start = 0;\n while (\n start < this.items.length &&\n this.getItemTop(start + 1) < scrollTop\n ) {\n start++;\n }\n\n let end = start;\n while (end < this.items.length && this.getItemTop(end) < viewportBottom) {\n end++;\n }\n this.visibleStart = Math.max(0, start - this.buffer);\n this.visibleEnd = Math.min(this.items.length, end + this.buffer);\n this.updateVisibleItems();\n this.scrollEmitter.emit(scrollTop);\n\n // Check if scrolled to the end\n const scrollHeight = this.scrollerRef.nativeElement.scrollHeight;\n const clientHeight = this.scrollerRef.nativeElement.clientHeight;\n const isAtEnd = scrollTop + clientHeight >= scrollHeight - 1;\n\n if (isAtEnd && scrollTop > this.previousScrollTop && scrollTop > 0) {\n this.scrollToEnd.emit();\n }\n\n this.previousScrollTop = scrollTop;\n }\n\n /** Updates the list of items to be rendered based on scroll position */\n updateVisibleItems() {\n this.visibleItems = this.items.slice(this.visibleStart, this.visibleEnd);\n\n // Wait for DOM render before measuring heights\n setTimeout(() => {\n this.measureItemHeights();\n });\n }\n\n /** Measures actual heights of rendered DOM elements and updates tracking */\n measureItemHeights() {\n this.itemElements.forEach((el, idx) => {\n const index = this.visibleStart + idx;\n const height = el.nativeElement.offsetHeight;\n if (this.itemHeights[index] !== height) {\n this.itemHeights[index] = height;\n this.resizeObserver.observe(el.nativeElement);\n }\n });\n this.updateItemTops();\n this.cdr.markForCheck();\n }\n}\n","<div\n #scroller\n class=\"scroll-container\"\n (scroll)=\"onScroll()\"\n [style.height.px]=\"viewportHeight\"\n>\n <div\n class=\"total-height\"\n [style.height.px]=\"totalContentHeight - viewportHeight\"\n >\n <ng-container *ngFor=\"let item of visibleItems; let i = index\">\n <div\n #itemElement\n class=\"item\"\n [style.transform]=\"'translateY(' + getItemTop(visibleStart + i) + 'px)'\"\n >\n <ng-container\n *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n","/*\n * Public API Surface of Listine\n */\n\nexport * from \"./lib/listtine-virtual/variable-virtual-scroll/listine-virtual-scroll.component\";\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}