UNPKG

ipsos-components

Version:

Material Design components for Angular

128 lines (107 loc) 5.22 kB
/** * @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 {Injectable, Optional, SkipSelf, NgZone, OnDestroy} from '@angular/core'; import {Platform} from '@angular/cdk/platform'; import {Observable} from 'rxjs/Observable'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {merge} from 'rxjs/observable/merge'; import {auditTime} from 'rxjs/operators/auditTime'; import {Subscription} from 'rxjs/Subscription'; import {of as observableOf} from 'rxjs/observable/of'; /** Time in ms to throttle the resize events by default. */ export const DEFAULT_RESIZE_TIME = 20; /** * Simple utility for getting the bounds of the browser viewport. * @docs-private */ @Injectable() export class ViewportRuler implements OnDestroy { /** Cached viewport dimensions. */ private _viewportSize: {width: number; height: number}; /** Stream of viewport change events. */ private _change: Observable<Event>; /** Subscription to streams that invalidate the cached viewport dimensions. */ private _invalidateCache: Subscription; constructor(platform: Platform, ngZone: NgZone) { this._change = platform.isBrowser ? ngZone.runOutsideAngular(() => { return merge<Event>(fromEvent(window, 'resize'), fromEvent(window, 'orientationchange')); }) : observableOf(); this._invalidateCache = this.change().subscribe(() => this._updateViewportSize()); } ngOnDestroy() { this._invalidateCache.unsubscribe(); } /** Returns the viewport's width and height. */ getViewportSize(): Readonly<{width: number, height: number}> { if (!this._viewportSize) { this._updateViewportSize(); } return {width: this._viewportSize.width, height: this._viewportSize.height}; } /** Gets a ClientRect for the viewport's bounds. */ getViewportRect(): ClientRect { // 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(). const scrollPosition = this.getViewportScrollPosition(); const {width, height} = this.getViewportSize(); return { top: scrollPosition.top, left: scrollPosition.left, bottom: scrollPosition.top + height, right: scrollPosition.left + width, height, width, }; } /** Gets the (top, left) scroll position of the viewport. */ getViewportScrollPosition() { // 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. const documentRect = document.documentElement.getBoundingClientRect(); const top = -documentRect.top || document.body.scrollTop || window.scrollY || document.documentElement.scrollTop || 0; const left = -documentRect.left || document.body.scrollLeft || window.scrollX || document.documentElement.scrollLeft || 0; return {top, left}; } /** * Returns a stream that emits whenever the size of the viewport changes. * @param throttle Time in milliseconds to throttle the stream. */ change(throttleTime: number = DEFAULT_RESIZE_TIME): Observable<Event> { return throttleTime > 0 ? this._change.pipe(auditTime(throttleTime)) : this._change; } /** Updates the cached viewport size. */ private _updateViewportSize() { this._viewportSize = {width: window.innerWidth, height: window.innerHeight}; } } /** @docs-private */ export function VIEWPORT_RULER_PROVIDER_FACTORY(parentRuler: ViewportRuler, platform: Platform, ngZone: NgZone) { return parentRuler || new ViewportRuler(platform, ngZone); } /** @docs-private */ export const 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 };