ngx-infinite-scroll
Version:
An infinite scroll directive for Angular
760 lines (755 loc) • 23.7 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('rxjs/operators')) :
typeof define === 'function' && define.amd ? define(['exports', '@angular/core', 'rxjs', 'rxjs/operators'], factory) :
(factory((global.ng = global.ng || {}, global.ng.ngxInfiniteScroll = global.ng.ngxInfiniteScroll || {}),global.ng.core,global.rxjs,global.rxjs_operators));
}(this, (function (exports,_angular_core,rxjs,rxjs_operators) { 'use strict';
/**
* @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 {?} */
var hasWindow = window && !!window.document && window.document.documentElement;
/** @type {?} */
var container = hasWindow && scrollWindow ? window : defaultElement;
if (selector) {
/** @type {?} */
var 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 {?} */
var 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 {?} */
var VerticalProps = {
clientHeight: "clientHeight",
offsetHeight: "offsetHeight",
scrollHeight: "scrollHeight",
pageYOffset: "pageYOffset",
offsetTop: "offsetTop",
scrollTop: "scrollTop",
top: "top"
};
/** @type {?} */
var HorizontalProps = {
clientHeight: "clientWidth",
offsetHeight: "offsetWidth",
scrollHeight: "scrollWidth",
pageYOffset: "pageXOffset",
offsetTop: "offsetLeft",
scrollTop: "scrollLeft",
top: "left"
};
var AxisResolver = /** @class */ (function () {
/**
* @param {?=} vertical
*/
function AxisResolver(vertical) {
if (vertical === void 0) { vertical = true; }
this.vertical = vertical;
this.propsMap = vertical ? VerticalProps : HorizontalProps;
}
/**
* @return {?}
*/
AxisResolver.prototype.clientHeightKey = function () {
return this.propsMap.clientHeight;
};
/**
* @return {?}
*/
AxisResolver.prototype.offsetHeightKey = function () {
return this.propsMap.offsetHeight;
};
/**
* @return {?}
*/
AxisResolver.prototype.scrollHeightKey = function () {
return this.propsMap.scrollHeight;
};
/**
* @return {?}
*/
AxisResolver.prototype.pageYOffsetKey = function () {
return this.propsMap.pageYOffset;
};
/**
* @return {?}
*/
AxisResolver.prototype.offsetTopKey = function () {
return this.propsMap.offsetTop;
};
/**
* @return {?}
*/
AxisResolver.prototype.scrollTopKey = function () {
return this.propsMap.scrollTop;
};
/**
* @return {?}
*/
AxisResolver.prototype.topKey = function () {
return this.propsMap.top;
};
return AxisResolver;
}());
/**
* @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(_a) {
var windowElement = _a.windowElement, axis = _a.axis;
return createResolverWithContainer({ axis: axis, isWindow: isElementWindow(windowElement) }, windowElement);
}
/**
* @param {?} resolver
* @param {?} windowElement
* @return {?}
*/
function createResolverWithContainer(resolver, windowElement) {
/** @type {?} */
var container = resolver.isWindow || (windowElement && !windowElement.nativeElement)
? windowElement
: windowElement.nativeElement;
return Object.assign(Object.assign({}, resolver), { container: container });
}
/**
* @param {?} windowElement
* @return {?}
*/
function isElementWindow(windowElement) {
/** @type {?} */
var isWindow = ['Window', 'global'].some(( /**
* @param {?} obj
* @return {?}
*/function (obj) { return 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 {?} */
var 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) {
var axis = resolver.axis, container = resolver.container, isWindow = resolver.isWindow;
var _a = extractHeightPropKeys(axis), offsetHeightKey = _a.offsetHeightKey, clientHeightKey = _a.clientHeightKey;
// scrolled until now / current y point
/** @type {?} */
var scrolled = height +
getElementPageYOffset(getDocumentElement(isWindow, container), axis, isWindow);
// total height / most bottom y point
/** @type {?} */
var nativeElementHeight = getElementHeight(element.nativeElement, isWindow, offsetHeightKey, clientHeightKey);
/** @type {?} */
var totalToScroll = getElementOffsetTop(element.nativeElement, axis, isWindow) +
nativeElementHeight;
return { height: height, scrolled: scrolled, totalToScroll: totalToScroll, isWindow: isWindow };
}
/**
* @param {?} height
* @param {?} element
* @param {?} resolver
* @return {?}
*/
function calculatePointsForElement(height, element, resolver) {
var axis = resolver.axis, container = resolver.container;
// perhaps use container.offsetTop instead of 'scrollTop'
/** @type {?} */
var scrolled = container[axis.scrollTopKey()];
/** @type {?} */
var totalToScroll = container[axis.scrollHeightKey()];
return { height: height, scrolled: scrolled, totalToScroll: totalToScroll, isWindow: false };
}
/**
* @param {?} axis
* @return {?}
*/
function extractHeightPropKeys(axis) {
return {
offsetHeightKey: axis.offsetHeightKey(),
clientHeightKey: axis.clientHeightKey()
};
}
/**
* @param {?} __0
* @return {?}
*/
function extractHeightForElement(_a) {
var container = _a.container, isWindow = _a.isWindow, axis = _a.axis;
var _b = extractHeightPropKeys(axis), offsetHeightKey = _b.offsetHeightKey, clientHeightKey = _b.clientHeightKey;
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 {?} */
var 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 {?} */
var 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 {?} */
var pageYOffset = axis.pageYOffsetKey();
/** @type {?} */
var scrollTop = axis.scrollTopKey();
/** @type {?} */
var 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 {?} */
var remaining;
/** @type {?} */
var containerBreakpoint;
if (container.totalToScroll <= 0) {
return false;
}
/** @type {?} */
var scrolledUntilNow = container.isWindow ? container.scrolled : container.height + container.scrolled;
if (scrollingDown) {
remaining =
(container.totalToScroll - scrolledUntilNow) / container.totalToScroll;
containerBreakpoint = distance.down / 10;
}
else {
/** @type {?} */
var totalHiddenContentHeight = container.scrolled + (container.totalToScroll - scrolledUntilNow);
remaining = container.scrolled / totalHiddenContentHeight;
containerBreakpoint = distance.up / 10;
}
/** @type {?} */
var 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 {?} */
var scrollDown = isScrollingDownwards(lastScrollPosition, container);
return {
fire: shouldFireScrollEvent(container, distance, scrollDown),
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
*/
var ScrollState = /** @class */ (function () {
/**
* @param {?} __0
*/
function ScrollState(_a) {
var totalToScroll = _a.totalToScroll;
this.lastScrollPosition = 0;
this.lastTotalToScroll = 0;
this.totalToScroll = 0;
this.triggered = {
down: 0,
up: 0
};
this.totalToScroll = totalToScroll;
}
/**
* @param {?} position
* @return {?}
*/
ScrollState.prototype.updateScrollPosition = function (position) {
return (this.lastScrollPosition = position);
};
/**
* @param {?} totalToScroll
* @return {?}
*/
ScrollState.prototype.updateTotalToScroll = function (totalToScroll) {
if (this.lastTotalToScroll !== totalToScroll) {
this.lastTotalToScroll = this.totalToScroll;
this.totalToScroll = totalToScroll;
}
};
/**
* @param {?} scrolledUntilNow
* @param {?} totalToScroll
* @return {?}
*/
ScrollState.prototype.updateScroll = function (scrolledUntilNow, totalToScroll) {
this.updateScrollPosition(scrolledUntilNow);
this.updateTotalToScroll(totalToScroll);
};
/**
* @param {?} scroll
* @param {?} isScrollingDown
* @return {?}
*/
ScrollState.prototype.updateTriggeredFlag = function (scroll, isScrollingDown) {
if (isScrollingDown) {
this.triggered.down = scroll;
}
else {
this.triggered.up = scroll;
}
};
/**
* @param {?} totalToScroll
* @param {?} isScrollingDown
* @return {?}
*/
ScrollState.prototype.isTriggeredScroll = function (totalToScroll, isScrollingDown) {
return isScrollingDown
? this.triggered.down === totalToScroll
: this.triggered.up === totalToScroll;
};
return ScrollState;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?} config
* @return {?}
*/
function createScroller(config) {
var scrollContainer = config.scrollContainer, scrollWindow = config.scrollWindow, element = config.element, fromRoot = config.fromRoot;
/** @type {?} */
var resolver = createResolver({
axis: new AxisResolver(!config.horizontal),
windowElement: resolveContainerElement(scrollContainer, scrollWindow, element, fromRoot)
});
/** @type {?} */
var scrollState = new ScrollState({
totalToScroll: calculatePoints(element, resolver)
});
/** @type {?} */
var options = {
container: resolver.container,
throttle: config.throttle
};
/** @type {?} */
var distance = {
up: config.upDistance,
down: config.downDistance
};
return attachScrollEvent(options).pipe(rxjs_operators.mergeMap(( /**
* @return {?}
*/function () { return rxjs.of(calculatePoints(element, resolver)); })), rxjs_operators.map(( /**
* @param {?} positionStats
* @return {?}
*/function (positionStats) { return toInfiniteScrollParams(scrollState.lastScrollPosition, positionStats, distance); })), rxjs_operators.tap(( /**
* @param {?} __0
* @return {?}
*/function (_a) {
var stats = _a.stats;
return scrollState.updateScroll(stats.scrolled, stats.totalToScroll);
})), rxjs_operators.filter(( /**
* @param {?} __0
* @return {?}
*/function (_a) {
var fire = _a.fire, scrollDown = _a.scrollDown, totalToScroll = _a.stats.totalToScroll;
return shouldTriggerEvents(config.alwaysCallback, fire, scrollState.isTriggeredScroll(totalToScroll, scrollDown));
})), rxjs_operators.tap(( /**
* @param {?} __0
* @return {?}
*/function (_a) {
var scrollDown = _a.scrollDown, totalToScroll = _a.stats.totalToScroll;
scrollState.updateTriggeredFlag(totalToScroll, scrollDown);
})), rxjs_operators.map(toInfiniteScrollAction));
}
/**
* @param {?} options
* @return {?}
*/
function attachScrollEvent(options) {
/** @type {?} */
var obs = rxjs.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(rxjs_operators.sampleTime(options.throttle));
}
return obs;
}
/**
* @param {?} lastScrollPosition
* @param {?} stats
* @param {?} distance
* @return {?}
*/
function toInfiniteScrollParams(lastScrollPosition, stats, distance) {
var _a = getScrollStats(lastScrollPosition, stats, distance), scrollDown = _a.scrollDown, fire = _a.fire;
return {
scrollDown: scrollDown,
fire: fire,
stats: stats
};
}
/** @type {?} */
var InfiniteScrollActions = {
DOWN: '[NGX_ISE] DOWN',
UP: '[NGX_ISE] UP'
};
/**
* @param {?} response
* @return {?}
*/
function toInfiniteScrollAction(response) {
var scrollDown = response.scrollDown, currentScrollPosition = response.stats.scrolled;
return {
type: scrollDown ? InfiniteScrollActions.DOWN : InfiniteScrollActions.UP,
payload: {
currentScrollPosition: currentScrollPosition
}
};
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var InfiniteScrollDirective = /** @class */ (function () {
/**
* @param {?} element
* @param {?} zone
*/
function InfiniteScrollDirective(element, zone) {
this.element = element;
this.zone = zone;
this.scrolled = new _angular_core.EventEmitter();
this.scrolledUp = new _angular_core.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 {?}
*/
InfiniteScrollDirective.prototype.ngAfterViewInit = function () {
if (!this.infiniteScrollDisabled) {
this.setup();
}
};
/**
* @param {?} __0
* @return {?}
*/
InfiniteScrollDirective.prototype.ngOnChanges = function (_a) {
var infiniteScrollContainer = _a.infiniteScrollContainer, infiniteScrollDisabled = _a.infiniteScrollDisabled, infiniteScrollDistance = _a.infiniteScrollDistance;
/** @type {?} */
var containerChanged = inputPropChanged(infiniteScrollContainer);
/** @type {?} */
var disabledChanged = inputPropChanged(infiniteScrollDisabled);
/** @type {?} */
var distanceChanged = inputPropChanged(infiniteScrollDistance);
/** @type {?} */
var shouldSetup = (!disabledChanged && !this.infiniteScrollDisabled) ||
(disabledChanged && !infiniteScrollDisabled.currentValue) || distanceChanged;
if (containerChanged || disabledChanged || distanceChanged) {
this.destroyScroller();
if (shouldSetup) {
this.setup();
}
}
};
/**
* @return {?}
*/
InfiniteScrollDirective.prototype.setup = function () {
var _this = this;
if (hasWindowDefined()) {
this.zone.runOutsideAngular(( /**
* @return {?}
*/function () {
_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 {?}
*/function (payload) { return _this.zone.run(( /**
* @return {?}
*/function () { return _this.handleOnScroll(payload); })); }));
}));
}
};
/**
* @param {?} __0
* @return {?}
*/
InfiniteScrollDirective.prototype.handleOnScroll = function (_a) {
var type = _a.type, payload = _a.payload;
switch (type) {
case InfiniteScrollActions.DOWN:
return this.scrolled.emit(payload);
case InfiniteScrollActions.UP:
return this.scrolledUp.emit(payload);
default:
return;
}
};
/**
* @return {?}
*/
InfiniteScrollDirective.prototype.ngOnDestroy = function () {
this.destroyScroller();
};
/**
* @return {?}
*/
InfiniteScrollDirective.prototype.destroyScroller = function () {
if (this.disposeScroller) {
this.disposeScroller.unsubscribe();
}
};
return InfiniteScrollDirective;
}());
InfiniteScrollDirective.decorators = [
{ type: _angular_core.Directive, args: [{
selector: '[infiniteScroll], [infinite-scroll], [data-infinite-scroll]'
},] },
];
/** @nocollapse */
InfiniteScrollDirective.ctorParameters = function () { return [
{ type: _angular_core.ElementRef },
{ type: _angular_core.NgZone }
]; };
InfiniteScrollDirective.propDecorators = {
scrolled: [{ type: _angular_core.Output }],
scrolledUp: [{ type: _angular_core.Output }],
infiniteScrollDistance: [{ type: _angular_core.Input }],
infiniteScrollUpDistance: [{ type: _angular_core.Input }],
infiniteScrollThrottle: [{ type: _angular_core.Input }],
infiniteScrollDisabled: [{ type: _angular_core.Input }],
infiniteScrollContainer: [{ type: _angular_core.Input }],
scrollWindow: [{ type: _angular_core.Input }],
immediateCheck: [{ type: _angular_core.Input }],
horizontal: [{ type: _angular_core.Input }],
alwaysCallback: [{ type: _angular_core.Input }],
fromRoot: [{ type: _angular_core.Input }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var InfiniteScrollModule = /** @class */ (function () {
function InfiniteScrollModule() {
}
return InfiniteScrollModule;
}());
InfiniteScrollModule.decorators = [
{ type: _angular_core.NgModule, args: [{
declarations: [InfiniteScrollDirective],
exports: [InfiniteScrollDirective],
imports: [],
providers: []
},] },
];
exports.InfiniteScrollDirective = InfiniteScrollDirective;
exports.InfiniteScrollModule = InfiniteScrollModule;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=ngx-infinite-scroll.umd.js.map