@angular/cdk
Version:
Angular Material Component Development Kit
569 lines (561 loc) • 22.3 kB
JavaScript
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Platform, PlatformModule } from '@angular/cdk/platform';
import { Injectable, NgZone, Optional, SkipSelf, Directive, ElementRef, NgModule, defineInjectable, inject } from '@angular/core';
import { fromEvent, of, Subject, Observable, merge } from 'rxjs';
import { auditTime, filter } from 'rxjs/operators';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Time in ms to throttle the scrolling events by default.
*/
var /** @type {?} */ DEFAULT_SCROLL_TIME = 20;
/**
* Service contained all registered Scrollable references and emits an event when any one of the
* Scrollable references emit a scrolled event.
*/
var ScrollDispatcher = /** @class */ (function () {
function ScrollDispatcher(_ngZone, _platform) {
this._ngZone = _ngZone;
this._platform = _platform;
/**
* Subject for notifying that a registered scrollable reference element has been scrolled.
*/
this._scrolled = new Subject();
/**
* Keeps track of the global `scroll` and `resize` subscriptions.
*/
this._globalSubscription = null;
/**
* Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards.
*/
this._scrolledCount = 0;
/**
* Map of all the scrollable references that are registered with the service and their
* scroll event subscriptions.
*/
this.scrollContainers = new Map();
}
/**
* Registers a scrollable instance with the service and listens for its scrolled events. When the
* scrollable is scrolled, the service emits the event to its scrolled observable.
* @param scrollable Scrollable instance to be registered.
*/
/**
* Registers a scrollable instance with the service and listens for its scrolled events. When the
* scrollable is scrolled, the service emits the event to its scrolled observable.
* @param {?} scrollable Scrollable instance to be registered.
* @return {?}
*/
ScrollDispatcher.prototype.register = /**
* Registers a scrollable instance with the service and listens for its scrolled events. When the
* scrollable is scrolled, the service emits the event to its scrolled observable.
* @param {?} scrollable Scrollable instance to be registered.
* @return {?}
*/
function (scrollable) {
var _this = this;
var /** @type {?} */ scrollSubscription = scrollable.elementScrolled()
.subscribe(function () { return _this._scrolled.next(scrollable); });
this.scrollContainers.set(scrollable, scrollSubscription);
};
/**
* Deregisters a Scrollable reference and unsubscribes from its scroll event observable.
* @param scrollable Scrollable instance to be deregistered.
*/
/**
* Deregisters a Scrollable reference and unsubscribes from its scroll event observable.
* @param {?} scrollable Scrollable instance to be deregistered.
* @return {?}
*/
ScrollDispatcher.prototype.deregister = /**
* Deregisters a Scrollable reference and unsubscribes from its scroll event observable.
* @param {?} scrollable Scrollable instance to be deregistered.
* @return {?}
*/
function (scrollable) {
var /** @type {?} */ scrollableReference = this.scrollContainers.get(scrollable);
if (scrollableReference) {
scrollableReference.unsubscribe();
this.scrollContainers.delete(scrollable);
}
};
/**
* Returns an observable that emits an event whenever any of the registered Scrollable
* references (or window, document, or body) fire a scrolled event. Can provide a time in ms
* to override the default "throttle" time.
*
* **Note:** in order to avoid hitting change detection for every scroll event,
* all of the events emitted from this stream will be run outside the Angular zone.
* If you need to update any data bindings as a result of a scroll event, you have
* to run the callback using `NgZone.run`.
*/
/**
* Returns an observable that emits an event whenever any of the registered Scrollable
* references (or window, document, or body) fire a scrolled event. Can provide a time in ms
* to override the default "throttle" time.
*
* **Note:** in order to avoid hitting change detection for every scroll event,
* all of the events emitted from this stream will be run outside the Angular zone.
* If you need to update any data bindings as a result of a scroll event, you have
* to run the callback using `NgZone.run`.
* @param {?=} auditTimeInMs
* @return {?}
*/
ScrollDispatcher.prototype.scrolled = /**
* Returns an observable that emits an event whenever any of the registered Scrollable
* references (or window, document, or body) fire a scrolled event. Can provide a time in ms
* to override the default "throttle" time.
*
* **Note:** in order to avoid hitting change detection for every scroll event,
* all of the events emitted from this stream will be run outside the Angular zone.
* If you need to update any data bindings as a result of a scroll event, you have
* to run the callback using `NgZone.run`.
* @param {?=} auditTimeInMs
* @return {?}
*/
function (auditTimeInMs) {
var _this = this;
if (auditTimeInMs === void 0) { auditTimeInMs = DEFAULT_SCROLL_TIME; }
return this._platform.isBrowser ? Observable.create(function (observer) {
if (!_this._globalSubscription) {
_this._addGlobalListener();
}
// In the case of a 0ms delay, use an observable without auditTime
// since it does add a perceptible delay in processing overhead.
var /** @type {?} */ subscription = auditTimeInMs > 0 ?
_this._scrolled.pipe(auditTime(auditTimeInMs)).subscribe(observer) :
_this._scrolled.subscribe(observer);
_this._scrolledCount++;
return function () {
subscription.unsubscribe();
_this._scrolledCount--;
if (!_this._scrolledCount) {
_this._removeGlobalListener();
}
};
}) : of();
};
/**
* @return {?}
*/
ScrollDispatcher.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
var _this = this;
this._removeGlobalListener();
this.scrollContainers.forEach(function (_, container) { return _this.deregister(container); });
this._scrolled.complete();
};
/**
* Returns an observable that emits whenever any of the
* scrollable ancestors of an element are scrolled.
* @param elementRef Element whose ancestors to listen for.
* @param auditTimeInMs Time to throttle the scroll events.
*/
/**
* Returns an observable that emits whenever any of the
* scrollable ancestors of an element are scrolled.
* @param {?} elementRef Element whose ancestors to listen for.
* @param {?=} auditTimeInMs Time to throttle the scroll events.
* @return {?}
*/
ScrollDispatcher.prototype.ancestorScrolled = /**
* Returns an observable that emits whenever any of the
* scrollable ancestors of an element are scrolled.
* @param {?} elementRef Element whose ancestors to listen for.
* @param {?=} auditTimeInMs Time to throttle the scroll events.
* @return {?}
*/
function (elementRef, auditTimeInMs) {
var /** @type {?} */ ancestors = this.getAncestorScrollContainers(elementRef);
return this.scrolled(auditTimeInMs).pipe(filter(function (target) {
return !target || ancestors.indexOf(target) > -1;
}));
};
/** Returns all registered Scrollables that contain the provided element. */
/**
* Returns all registered Scrollables that contain the provided element.
* @param {?} elementRef
* @return {?}
*/
ScrollDispatcher.prototype.getAncestorScrollContainers = /**
* Returns all registered Scrollables that contain the provided element.
* @param {?} elementRef
* @return {?}
*/
function (elementRef) {
var _this = this;
var /** @type {?} */ scrollingContainers = [];
this.scrollContainers.forEach(function (_subscription, scrollable) {
if (_this._scrollableContainsElement(scrollable, elementRef)) {
scrollingContainers.push(scrollable);
}
});
return scrollingContainers;
};
/**
* Returns true if the element is contained within the provided Scrollable.
* @param {?} scrollable
* @param {?} elementRef
* @return {?}
*/
ScrollDispatcher.prototype._scrollableContainsElement = /**
* Returns true if the element is contained within the provided Scrollable.
* @param {?} scrollable
* @param {?} elementRef
* @return {?}
*/
function (scrollable, elementRef) {
var /** @type {?} */ element = elementRef.nativeElement;
var /** @type {?} */ scrollableElement = scrollable.getElementRef().nativeElement;
// Traverse through the element parents until we reach null, checking if any of the elements
// are the scrollable's element.
do {
if (element == scrollableElement) {
return true;
}
} while (element = /** @type {?} */ ((element)).parentElement);
return false;
};
/**
* Sets up the global scroll listeners.
* @return {?}
*/
ScrollDispatcher.prototype._addGlobalListener = /**
* Sets up the global scroll listeners.
* @return {?}
*/
function () {
var _this = this;
this._globalSubscription = this._ngZone.runOutsideAngular(function () {
return fromEvent(window.document, 'scroll').subscribe(function () { return _this._scrolled.next(); });
});
};
/**
* Cleans up the global scroll listener.
* @return {?}
*/
ScrollDispatcher.prototype._removeGlobalListener = /**
* Cleans up the global scroll listener.
* @return {?}
*/
function () {
if (this._globalSubscription) {
this._globalSubscription.unsubscribe();
this._globalSubscription = null;
}
};
ScrollDispatcher.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] },
];
/** @nocollapse */
ScrollDispatcher.ctorParameters = function () { return [
{ type: NgZone, },
{ type: Platform, },
]; };
/** @nocollapse */ ScrollDispatcher.ngInjectableDef = defineInjectable({ factory: function ScrollDispatcher_Factory() { return new ScrollDispatcher(inject(NgZone), inject(Platform)); }, token: ScrollDispatcher, providedIn: "root" });
return ScrollDispatcher;
}());
/**
* \@docs-private \@deprecated \@breaking-change 7.0.0
* @param {?} parentDispatcher
* @param {?} ngZone
* @param {?} platform
* @return {?}
*/
function SCROLL_DISPATCHER_PROVIDER_FACTORY(parentDispatcher, ngZone, platform) {
return parentDispatcher || new ScrollDispatcher(ngZone, platform);
}
/**
* \@docs-private \@deprecated \@breaking-change 7.0.0
*/
var /** @type {?} */ SCROLL_DISPATCHER_PROVIDER = {
// If there is already a ScrollDispatcher available, use that. Otherwise, provide a new one.
provide: ScrollDispatcher,
deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone, Platform],
useFactory: SCROLL_DISPATCHER_PROVIDER_FACTORY
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Sends an event when the directive's element is scrolled. Registers itself with the
* ScrollDispatcher service to include itself as part of its collection of scrolling events that it
* can be listened to through the service.
*/
var CdkScrollable = /** @class */ (function () {
function CdkScrollable(_elementRef, _scroll, _ngZone) {
var _this = this;
this._elementRef = _elementRef;
this._scroll = _scroll;
this._ngZone = _ngZone;
this._elementScrolled = new Subject();
this._scrollListener = function (event) { return _this._elementScrolled.next(event); };
}
/**
* @return {?}
*/
CdkScrollable.prototype.ngOnInit = /**
* @return {?}
*/
function () {
var _this = this;
this._ngZone.runOutsideAngular(function () {
_this.getElementRef().nativeElement.addEventListener('scroll', _this._scrollListener);
});
this._scroll.register(this);
};
/**
* @return {?}
*/
CdkScrollable.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
this._scroll.deregister(this);
if (this._scrollListener) {
this.getElementRef().nativeElement.removeEventListener('scroll', this._scrollListener);
}
this._elementScrolled.complete();
};
/**
* Returns observable that emits when a scroll event is fired on the host element.
*/
/**
* Returns observable that emits when a scroll event is fired on the host element.
* @return {?}
*/
CdkScrollable.prototype.elementScrolled = /**
* Returns observable that emits when a scroll event is fired on the host element.
* @return {?}
*/
function () {
return this._elementScrolled.asObservable();
};
/**
* @return {?}
*/
CdkScrollable.prototype.getElementRef = /**
* @return {?}
*/
function () {
return this._elementRef;
};
CdkScrollable.decorators = [
{ type: Directive, args: [{
selector: '[cdk-scrollable], [cdkScrollable]'
},] },
];
/** @nocollapse */
CdkScrollable.ctorParameters = function () { return [
{ type: ElementRef, },
{ type: ScrollDispatcher, },
{ type: NgZone, },
]; };
return CdkScrollable;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Time in ms to throttle the resize events by default.
*/
var /** @type {?} */ DEFAULT_RESIZE_TIME = 20;
/**
* Simple utility for getting the bounds of the browser viewport.
* \@docs-private
*/
var ViewportRuler = /** @class */ (function () {
function ViewportRuler(_platform, ngZone) {
var _this = this;
this._platform = _platform;
this._change = _platform.isBrowser ? ngZone.runOutsideAngular(function () {
return merge(fromEvent(window, 'resize'), fromEvent(window, 'orientationchange'));
}) : of();
this._invalidateCache = this.change().subscribe(function () { return _this._updateViewportSize(); });
}
/**
* @return {?}
*/
ViewportRuler.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
this._invalidateCache.unsubscribe();
};
/** Returns the viewport's width and height. */
/**
* Returns the viewport's width and height.
* @return {?}
*/
ViewportRuler.prototype.getViewportSize = /**
* Returns the viewport's width and height.
* @return {?}
*/
function () {
if (!this._viewportSize) {
this._updateViewportSize();
}
var /** @type {?} */ output = { width: this._viewportSize.width, height: this._viewportSize.height };
// If we're not on a browser, don't cache the size since it'll be mocked out anyway.
if (!this._platform.isBrowser) {
this._viewportSize = /** @type {?} */ ((null));
}
return output;
};
/** Gets a ClientRect for the viewport's bounds. */
/**
* Gets a ClientRect for the viewport's bounds.
* @return {?}
*/
ViewportRuler.prototype.getViewportRect = /**
* Gets a ClientRect for the viewport's bounds.
* @return {?}
*/
function () {
// Use the document element's bounding rect rather than the window scroll properties
// (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll
// properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different
// conceptual viewports. Under most circumstances these viewports are equivalent, but they
// can disagree when the page is pinch-zoomed (on devices that support touch).
// See https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4
// We use the documentElement instead of the body because, by default (without a css reset)
// browsers typically give the document body an 8px margin, which is not included in
// getBoundingClientRect().
var /** @type {?} */ scrollPosition = this.getViewportScrollPosition();
var _a = this.getViewportSize(), width = _a.width, height = _a.height;
return {
top: scrollPosition.top,
left: scrollPosition.left,
bottom: scrollPosition.top + height,
right: scrollPosition.left + width,
height: height,
width: width,
};
};
/** Gets the (top, left) scroll position of the viewport. */
/**
* Gets the (top, left) scroll position of the viewport.
* @return {?}
*/
ViewportRuler.prototype.getViewportScrollPosition = /**
* Gets the (top, left) scroll position of the viewport.
* @return {?}
*/
function () {
// While we can get a reference to the fake document
// during SSR, it doesn't have getBoundingClientRect.
if (!this._platform.isBrowser) {
return { top: 0, left: 0 };
}
// The top-left-corner of the viewport is determined by the scroll position of the document
// body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about
// whether `document.body` or `document.documentElement` is the scrolled element, so reading
// `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding rect of
// `document.documentElement` works consistently, where the `top` and `left` values will
// equal negative the scroll position.
var /** @type {?} */ documentRect = document.documentElement.getBoundingClientRect();
var /** @type {?} */ top = -documentRect.top || document.body.scrollTop || window.scrollY ||
document.documentElement.scrollTop || 0;
var /** @type {?} */ left = -documentRect.left || document.body.scrollLeft || window.scrollX ||
document.documentElement.scrollLeft || 0;
return { top: top, left: left };
};
/**
* Returns a stream that emits whenever the size of the viewport changes.
* @param throttleTime Time in milliseconds to throttle the stream.
*/
/**
* Returns a stream that emits whenever the size of the viewport changes.
* @param {?=} throttleTime Time in milliseconds to throttle the stream.
* @return {?}
*/
ViewportRuler.prototype.change = /**
* Returns a stream that emits whenever the size of the viewport changes.
* @param {?=} throttleTime Time in milliseconds to throttle the stream.
* @return {?}
*/
function (throttleTime) {
if (throttleTime === void 0) { throttleTime = DEFAULT_RESIZE_TIME; }
return throttleTime > 0 ? this._change.pipe(auditTime(throttleTime)) : this._change;
};
/**
* Updates the cached viewport size.
* @return {?}
*/
ViewportRuler.prototype._updateViewportSize = /**
* Updates the cached viewport size.
* @return {?}
*/
function () {
this._viewportSize = this._platform.isBrowser ?
{ width: window.innerWidth, height: window.innerHeight } :
{ width: 0, height: 0 };
};
ViewportRuler.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] },
];
/** @nocollapse */
ViewportRuler.ctorParameters = function () { return [
{ type: Platform, },
{ type: NgZone, },
]; };
/** @nocollapse */ ViewportRuler.ngInjectableDef = defineInjectable({ factory: function ViewportRuler_Factory() { return new ViewportRuler(inject(Platform), inject(NgZone)); }, token: ViewportRuler, providedIn: "root" });
return ViewportRuler;
}());
/**
* \@docs-private \@deprecated \@breaking-change 7.0.0
* @param {?} parentRuler
* @param {?} platform
* @param {?} ngZone
* @return {?}
*/
function VIEWPORT_RULER_PROVIDER_FACTORY(parentRuler, platform, ngZone) {
return parentRuler || new ViewportRuler(platform, ngZone);
}
/**
* \@docs-private \@deprecated \@breaking-change 7.0.0
*/
var /** @type {?} */ VIEWPORT_RULER_PROVIDER = {
// If there is already a ViewportRuler available, use that. Otherwise, provide a new one.
provide: ViewportRuler,
deps: [[new Optional(), new SkipSelf(), ViewportRuler], Platform, NgZone],
useFactory: VIEWPORT_RULER_PROVIDER_FACTORY
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
var ScrollDispatchModule = /** @class */ (function () {
function ScrollDispatchModule() {
}
ScrollDispatchModule.decorators = [
{ type: NgModule, args: [{
imports: [PlatformModule],
exports: [CdkScrollable],
declarations: [CdkScrollable],
},] },
];
return ScrollDispatchModule;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
export { DEFAULT_SCROLL_TIME, ScrollDispatcher, SCROLL_DISPATCHER_PROVIDER_FACTORY, SCROLL_DISPATCHER_PROVIDER, CdkScrollable, DEFAULT_RESIZE_TIME, ViewportRuler, VIEWPORT_RULER_PROVIDER_FACTORY, VIEWPORT_RULER_PROVIDER, ScrollDispatchModule };
//# sourceMappingURL=scrolling.es5.js.map