@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
114 lines (113 loc) • 4.13 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { isDocumentAvailable } from '@progress/kendo-angular-common';
import { Observable, BehaviorSubject } from 'rxjs';
/**
* @hidden
*/
export class ScrollAction {
offset;
constructor(offset) {
this.offset = offset;
}
}
/**
* @hidden
*/
export class PageAction {
skip;
take;
constructor(skip, take) {
this.skip = skip;
this.take = take;
}
}
/**
* @hidden
*/
export class ScrollBottomAction {
}
const SCROLL_BOTTOM_THRESHOLD = 2;
/**
* @hidden
*/
export class ScrollerService {
scrollObservable;
firstLoaded = 0;
lastLoaded;
lastScrollTop;
take;
total;
rowHeightService;
scrollSubscription;
subscription;
constructor(scrollObservable) {
this.scrollObservable = scrollObservable;
}
create(rowHeightService, skip, take, total) {
this.rowHeightService = rowHeightService;
this.firstLoaded = skip;
this.lastLoaded = skip + take;
this.take = take;
this.total = total;
this.lastScrollTop = 0;
const subject = new BehaviorSubject(new ScrollAction(this.rowHeightService.offset(skip)));
this.subscription = Observable.create(observer => {
this.unsubscribe();
this.scrollSubscription = this.scrollObservable.subscribe(x => this.onScroll(x, observer));
}).subscribe(x => subject.next(x));
return subject;
}
destroy() {
this.unsubscribe();
if (this.subscription) {
this.subscription.unsubscribe();
}
}
onScroll({ scrollTop, offsetHeight, scrollHeight, clientHeight }, observer) {
if (!isDocumentAvailable() || (this.lastScrollTop === scrollTop)) {
return;
}
const up = this.lastScrollTop >= scrollTop;
this.lastScrollTop = scrollTop;
let firstItemIndex = this.rowHeightService.index(scrollTop);
let firstItemOffset = this.rowHeightService.offset(firstItemIndex);
const lastItemIndex = this.rowHeightService.index(scrollTop + offsetHeight);
if (!up) {
if (lastItemIndex >= this.lastLoaded && this.lastLoaded < this.total) {
const overflow = (firstItemIndex + this.take) - this.total;
if (overflow > 0) {
firstItemIndex = firstItemIndex - overflow;
firstItemOffset = this.rowHeightService.offset(firstItemIndex);
}
this.firstLoaded = firstItemIndex;
observer.next(new ScrollAction(firstItemOffset));
let nextTake = this.firstLoaded + this.take;
this.lastLoaded = Math.min(nextTake, this.total);
nextTake = nextTake > this.total ? this.total - this.firstLoaded : this.take;
observer.next(new PageAction(this.firstLoaded, this.take));
}
else {
const atBottom = scrollHeight - clientHeight - scrollTop < SCROLL_BOTTOM_THRESHOLD;
if (atBottom) {
observer.next(new ScrollBottomAction());
}
}
}
if (up && firstItemIndex < this.firstLoaded) {
const nonVisibleBuffer = Math.floor(this.take * 0.3);
this.firstLoaded = Math.max(firstItemIndex - nonVisibleBuffer, 0);
observer.next(new ScrollAction(this.rowHeightService.offset(this.firstLoaded)));
this.lastLoaded = Math.min(this.firstLoaded + this.take, this.total);
observer.next(new PageAction(this.firstLoaded, this.take));
}
}
unsubscribe() {
if (this.scrollSubscription) {
this.scrollSubscription.unsubscribe();
this.scrollSubscription = undefined;
}
}
}