UNPKG

ngx-infinite-scroll

Version:
787 lines (764 loc) 21.8 kB
import { Directive, ElementRef, EventEmitter, Input, NgModule, NgZone, Output } from '@angular/core'; import { fromEvent, of } from 'rxjs'; import { filter, map, mergeMap, sampleTime, tap } from 'rxjs/operators'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @param {?} selector * @param {?} scrollWindow * @param {?} defaultElement * @param {?} fromRoot * @return {?} */ function resolveContainerElement(selector, scrollWindow, defaultElement, fromRoot) { /** @type {?} */ const hasWindow = window && !!window.document && window.document.documentElement; /** @type {?} */ let container = hasWindow && scrollWindow ? window : defaultElement; if (selector) { /** @type {?} */ const containerIsString = selector && hasWindow && typeof selector === 'string'; container = containerIsString ? findElement(selector, defaultElement.nativeElement, fromRoot) : selector; if (!container) { throw new Error('ngx-infinite-scroll {resolveContainerElement()}: selector for'); } } return container; } /** * @param {?} selector * @param {?} customRoot * @param {?} fromRoot * @return {?} */ function findElement(selector, customRoot, fromRoot) { /** @type {?} */ const rootEl = fromRoot ? window.document : customRoot; return rootEl.querySelector(selector); } /** * @param {?} prop * @return {?} */ function inputPropChanged(prop) { return prop && !prop.firstChange; } /** * @return {?} */ function hasWindowDefined() { return typeof window !== 'undefined'; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const VerticalProps = { clientHeight: "clientHeight", offsetHeight: "offsetHeight", scrollHeight: "scrollHeight", pageYOffset: "pageYOffset", offsetTop: "offsetTop", scrollTop: "scrollTop", top: "top" }; /** @type {?} */ const HorizontalProps = { clientHeight: "clientWidth", offsetHeight: "offsetWidth", scrollHeight: "scrollWidth", pageYOffset: "pageXOffset", offsetTop: "offsetLeft", scrollTop: "scrollLeft", top: "left" }; class AxisResolver { /** * @param {?=} vertical */ constructor(vertical = true) { this.vertical = vertical; this.propsMap = vertical ? VerticalProps : HorizontalProps; } /** * @return {?} */ clientHeightKey() { return this.propsMap.clientHeight; } /** * @return {?} */ offsetHeightKey() { return this.propsMap.offsetHeight; } /** * @return {?} */ scrollHeightKey() { return this.propsMap.scrollHeight; } /** * @return {?} */ pageYOffsetKey() { return this.propsMap.pageYOffset; } /** * @return {?} */ offsetTopKey() { return this.propsMap.offsetTop; } /** * @return {?} */ scrollTopKey() { return this.propsMap.scrollTop; } /** * @return {?} */ topKey() { return this.propsMap.top; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @record */ /** * @record */ /** * @record */ /** * @record */ /** * @param {?} alwaysCallback * @param {?} shouldFireScrollEvent * @param {?} isTriggeredCurrentTotal * @return {?} */ function shouldTriggerEvents(alwaysCallback, shouldFireScrollEvent, isTriggeredCurrentTotal) { if (alwaysCallback && shouldFireScrollEvent) { return true; } if (!isTriggeredCurrentTotal && shouldFireScrollEvent) { return true; } return false; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @param {?} __0 * @return {?} */ function createResolver({ windowElement, axis }) { return createResolverWithContainer({ axis, isWindow: isElementWindow(windowElement) }, windowElement); } /** * @param {?} resolver * @param {?} windowElement * @return {?} */ function createResolverWithContainer(resolver, windowElement) { /** @type {?} */ const container = resolver.isWindow || (windowElement && !windowElement.nativeElement) ? windowElement : windowElement.nativeElement; return Object.assign(Object.assign({}, resolver), { container }); } /** * @param {?} windowElement * @return {?} */ function isElementWindow(windowElement) { /** @type {?} */ const isWindow = ['Window', 'global'].some((/** * @param {?} obj * @return {?} */ (obj) => Object.prototype.toString.call(windowElement).includes(obj))); return isWindow; } /** * @param {?} isContainerWindow * @param {?} windowElement * @return {?} */ function getDocumentElement(isContainerWindow, windowElement) { return isContainerWindow ? windowElement.document.documentElement : null; } /** * @param {?} element * @param {?} resolver * @return {?} */ function calculatePoints(element, resolver) { /** @type {?} */ const height = extractHeightForElement(resolver); return resolver.isWindow ? calculatePointsForWindow(height, element, resolver) : calculatePointsForElement(height, element, resolver); } /** * @param {?} height * @param {?} element * @param {?} resolver * @return {?} */ function calculatePointsForWindow(height, element, resolver) { const { axis, container, isWindow } = resolver; const { offsetHeightKey, clientHeightKey } = extractHeightPropKeys(axis); // scrolled until now / current y point /** @type {?} */ const scrolled = height + getElementPageYOffset(getDocumentElement(isWindow, container), axis, isWindow); // total height / most bottom y point /** @type {?} */ const nativeElementHeight = getElementHeight(element.nativeElement, isWindow, offsetHeightKey, clientHeightKey); /** @type {?} */ const totalToScroll = getElementOffsetTop(element.nativeElement, axis, isWindow) + nativeElementHeight; return { height, scrolled, totalToScroll, isWindow }; } /** * @param {?} height * @param {?} element * @param {?} resolver * @return {?} */ function calculatePointsForElement(height, element, resolver) { const { axis, container } = resolver; // perhaps use container.offsetTop instead of 'scrollTop' /** @type {?} */ const scrolled = container[axis.scrollTopKey()]; /** @type {?} */ const totalToScroll = container[axis.scrollHeightKey()]; return { height, scrolled, totalToScroll, isWindow: false }; } /** * @param {?} axis * @return {?} */ function extractHeightPropKeys(axis) { return { offsetHeightKey: axis.offsetHeightKey(), clientHeightKey: axis.clientHeightKey() }; } /** * @param {?} __0 * @return {?} */ function extractHeightForElement({ container, isWindow, axis }) { const { offsetHeightKey, clientHeightKey } = extractHeightPropKeys(axis); return getElementHeight(container, isWindow, offsetHeightKey, clientHeightKey); } /** * @param {?} elem * @param {?} isWindow * @param {?} offsetHeightKey * @param {?} clientHeightKey * @return {?} */ function getElementHeight(elem, isWindow, offsetHeightKey, clientHeightKey) { if (isNaN(elem[offsetHeightKey])) { /** @type {?} */ const docElem = getDocumentElement(isWindow, elem); return docElem ? docElem[clientHeightKey] : 0; } else { return elem[offsetHeightKey]; } } /** * @param {?} elem * @param {?} axis * @param {?} isWindow * @return {?} */ function getElementOffsetTop(elem, axis, isWindow) { /** @type {?} */ const topKey = axis.topKey(); // elem = elem.nativeElement; if (!elem.getBoundingClientRect) { // || elem.css('none')) { return; } return (elem.getBoundingClientRect()[topKey] + getElementPageYOffset(elem, axis, isWindow)); } /** * @param {?} elem * @param {?} axis * @param {?} isWindow * @return {?} */ function getElementPageYOffset(elem, axis, isWindow) { /** @type {?} */ const pageYOffset = axis.pageYOffsetKey(); /** @type {?} */ const scrollTop = axis.scrollTopKey(); /** @type {?} */ const offsetTop = axis.offsetTopKey(); if (isNaN(window.pageYOffset)) { return getDocumentElement(isWindow, elem)[scrollTop]; } else if (elem.ownerDocument) { return elem.ownerDocument.defaultView[pageYOffset]; } else { return elem[offsetTop]; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @param {?} container * @param {?} distance * @param {?} scrollingDown * @return {?} */ function shouldFireScrollEvent(container, distance, scrollingDown) { /** @type {?} */ let remaining; /** @type {?} */ let containerBreakpoint; if (container.totalToScroll <= 0) { return false; } /** @type {?} */ const scrolledUntilNow = container.isWindow ? container.scrolled : container.height + container.scrolled; if (scrollingDown) { remaining = (container.totalToScroll - scrolledUntilNow) / container.totalToScroll; containerBreakpoint = distance.down / 10; } else { /** @type {?} */ const totalHiddenContentHeight = container.scrolled + (container.totalToScroll - scrolledUntilNow); remaining = container.scrolled / totalHiddenContentHeight; containerBreakpoint = distance.up / 10; } /** @type {?} */ const shouldFireEvent = remaining <= containerBreakpoint; return shouldFireEvent; } /** * @param {?} lastScrollPosition * @param {?} container * @return {?} */ function isScrollingDownwards(lastScrollPosition, container) { return lastScrollPosition < container.scrolled; } /** * @param {?} lastScrollPosition * @param {?} container * @param {?} distance * @return {?} */ function getScrollStats(lastScrollPosition, container, distance) { /** @type {?} */ const scrollDown = isScrollingDownwards(lastScrollPosition, container); return { fire: shouldFireScrollEvent(container, distance, scrollDown), scrollDown }; } /** * @param {?} position * @param {?} scrollState * @return {?} */ /** * @param {?} totalToScroll * @param {?} scrollState * @return {?} */ /** * @param {?} scrollState * @return {?} */ /** * @param {?} scroll * @param {?} scrollState * @param {?} triggered * @param {?} isScrollingDown * @return {?} */ /** * @param {?} totalToScroll * @param {?} scrollState * @param {?} isScrollingDown * @return {?} */ /** * @param {?} scrollState * @param {?} scrolledUntilNow * @param {?} totalToScroll * @return {?} */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class ScrollState { /** * @param {?} __0 */ constructor({ totalToScroll }) { this.lastScrollPosition = 0; this.lastTotalToScroll = 0; this.totalToScroll = 0; this.triggered = { down: 0, up: 0 }; this.totalToScroll = totalToScroll; } /** * @param {?} position * @return {?} */ updateScrollPosition(position) { return (this.lastScrollPosition = position); } /** * @param {?} totalToScroll * @return {?} */ updateTotalToScroll(totalToScroll) { if (this.lastTotalToScroll !== totalToScroll) { this.lastTotalToScroll = this.totalToScroll; this.totalToScroll = totalToScroll; } } /** * @param {?} scrolledUntilNow * @param {?} totalToScroll * @return {?} */ updateScroll(scrolledUntilNow, totalToScroll) { this.updateScrollPosition(scrolledUntilNow); this.updateTotalToScroll(totalToScroll); } /** * @param {?} scroll * @param {?} isScrollingDown * @return {?} */ updateTriggeredFlag(scroll, isScrollingDown) { if (isScrollingDown) { this.triggered.down = scroll; } else { this.triggered.up = scroll; } } /** * @param {?} totalToScroll * @param {?} isScrollingDown * @return {?} */ isTriggeredScroll(totalToScroll, isScrollingDown) { return isScrollingDown ? this.triggered.down === totalToScroll : this.triggered.up === totalToScroll; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @param {?} config * @return {?} */ function createScroller(config) { const { scrollContainer, scrollWindow, element, fromRoot } = config; /** @type {?} */ const resolver = createResolver({ axis: new AxisResolver(!config.horizontal), windowElement: resolveContainerElement(scrollContainer, scrollWindow, element, fromRoot) }); /** @type {?} */ const scrollState = new ScrollState({ totalToScroll: calculatePoints(element, resolver) }); /** @type {?} */ const options = { container: resolver.container, throttle: config.throttle }; /** @type {?} */ const distance = { up: config.upDistance, down: config.downDistance }; return attachScrollEvent(options).pipe(mergeMap((/** * @return {?} */ () => of(calculatePoints(element, resolver)))), map((/** * @param {?} positionStats * @return {?} */ (positionStats) => toInfiniteScrollParams(scrollState.lastScrollPosition, positionStats, distance))), tap((/** * @param {?} __0 * @return {?} */ ({ stats }) => scrollState.updateScroll(stats.scrolled, stats.totalToScroll))), filter((/** * @param {?} __0 * @return {?} */ ({ fire, scrollDown, stats: { totalToScroll } }) => shouldTriggerEvents(config.alwaysCallback, fire, scrollState.isTriggeredScroll(totalToScroll, scrollDown)))), tap((/** * @param {?} __0 * @return {?} */ ({ scrollDown, stats: { totalToScroll } }) => { scrollState.updateTriggeredFlag(totalToScroll, scrollDown); })), map(toInfiniteScrollAction)); } /** * @param {?} options * @return {?} */ function attachScrollEvent(options) { /** @type {?} */ let obs = fromEvent(options.container, 'scroll'); // For an unknown reason calling `sampleTime()` causes trouble for many users, even with `options.throttle = 0`. // Let's avoid calling the function unless needed. // See https://github.com/orizens/ngx-infinite-scroll/issues/198 if (options.throttle) { obs = obs.pipe(sampleTime(options.throttle)); } return obs; } /** * @param {?} lastScrollPosition * @param {?} stats * @param {?} distance * @return {?} */ function toInfiniteScrollParams(lastScrollPosition, stats, distance) { const { scrollDown, fire } = getScrollStats(lastScrollPosition, stats, distance); return { scrollDown, fire, stats }; } /** @type {?} */ const InfiniteScrollActions = { DOWN: '[NGX_ISE] DOWN', UP: '[NGX_ISE] UP' }; /** * @param {?} response * @return {?} */ function toInfiniteScrollAction(response) { const { scrollDown, stats: { scrolled: currentScrollPosition } } = response; return { type: scrollDown ? InfiniteScrollActions.DOWN : InfiniteScrollActions.UP, payload: { currentScrollPosition } }; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class InfiniteScrollDirective { /** * @param {?} element * @param {?} zone */ constructor(element, zone) { this.element = element; this.zone = zone; this.scrolled = new EventEmitter(); this.scrolledUp = new EventEmitter(); this.infiniteScrollDistance = 2; this.infiniteScrollUpDistance = 1.5; this.infiniteScrollThrottle = 150; this.infiniteScrollDisabled = false; this.infiniteScrollContainer = null; this.scrollWindow = true; this.immediateCheck = false; this.horizontal = false; this.alwaysCallback = false; this.fromRoot = false; } /** * @return {?} */ ngAfterViewInit() { if (!this.infiniteScrollDisabled) { this.setup(); } } /** * @param {?} __0 * @return {?} */ ngOnChanges({ infiniteScrollContainer, infiniteScrollDisabled, infiniteScrollDistance }) { /** @type {?} */ const containerChanged = inputPropChanged(infiniteScrollContainer); /** @type {?} */ const disabledChanged = inputPropChanged(infiniteScrollDisabled); /** @type {?} */ const distanceChanged = inputPropChanged(infiniteScrollDistance); /** @type {?} */ const shouldSetup = (!disabledChanged && !this.infiniteScrollDisabled) || (disabledChanged && !infiniteScrollDisabled.currentValue) || distanceChanged; if (containerChanged || disabledChanged || distanceChanged) { this.destroyScroller(); if (shouldSetup) { this.setup(); } } } /** * @return {?} */ setup() { if (hasWindowDefined()) { this.zone.runOutsideAngular((/** * @return {?} */ () => { this.disposeScroller = createScroller({ fromRoot: this.fromRoot, alwaysCallback: this.alwaysCallback, disable: this.infiniteScrollDisabled, downDistance: this.infiniteScrollDistance, element: this.element, horizontal: this.horizontal, scrollContainer: this.infiniteScrollContainer, scrollWindow: this.scrollWindow, throttle: this.infiniteScrollThrottle, upDistance: this.infiniteScrollUpDistance }).subscribe((/** * @param {?} payload * @return {?} */ (payload) => this.zone.run((/** * @return {?} */ () => this.handleOnScroll(payload))))); })); } } /** * @param {?} __0 * @return {?} */ handleOnScroll({ type, payload }) { switch (type) { case InfiniteScrollActions.DOWN: return this.scrolled.emit(payload); case InfiniteScrollActions.UP: return this.scrolledUp.emit(payload); default: return; } } /** * @return {?} */ ngOnDestroy() { this.destroyScroller(); } /** * @return {?} */ destroyScroller() { if (this.disposeScroller) { this.disposeScroller.unsubscribe(); } } } InfiniteScrollDirective.decorators = [ { type: Directive, args: [{ selector: '[infiniteScroll], [infinite-scroll], [data-infinite-scroll]' },] }, ]; /** @nocollapse */ InfiniteScrollDirective.ctorParameters = () => [ { type: ElementRef }, { type: NgZone } ]; InfiniteScrollDirective.propDecorators = { scrolled: [{ type: Output }], scrolledUp: [{ type: Output }], infiniteScrollDistance: [{ type: Input }], infiniteScrollUpDistance: [{ type: Input }], infiniteScrollThrottle: [{ type: Input }], infiniteScrollDisabled: [{ type: Input }], infiniteScrollContainer: [{ type: Input }], scrollWindow: [{ type: Input }], immediateCheck: [{ type: Input }], horizontal: [{ type: Input }], alwaysCallback: [{ type: Input }], fromRoot: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class InfiniteScrollModule { } InfiniteScrollModule.decorators = [ { type: NgModule, args: [{ declarations: [InfiniteScrollDirective], exports: [InfiniteScrollDirective], imports: [], providers: [] },] }, ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Angular library starter. * Build an Angular library compatible with AoT compilation & Tree shaking. * Written by Roberto Simonetti. * MIT license. * https://github.com/robisim74/angular-library-starter */ /** * Entry point for all public APIs of the package. */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Generated bundle index. Do not edit. */ export { InfiniteScrollDirective, InfiniteScrollModule }; //# sourceMappingURL=ngx-infinite-scroll.js.map