summaryrefslogtreecommitdiffstats
path: root/sdc-workflow-designer-ui/src/app/paletx/core/overlay/position/viewport-ruler.ts
blob: 298cd642414189d888e2493c7c0d8e7d544bdaab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
 * @license
 * Copyright Google Inc. 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
 */
/* tslint:disable:array-type member-access variable-name typedef
 only-arrow-functions directive-class-suffix component-class-suffix
 component-selector no-unnecessary-type-assertion arrow-parens*/
import {Injectable, Optional, SkipSelf} from '@angular/core';
import {ScrollDispatcher} from '../scroll/scroll-dispatcher';


/**
 * Simple utility for getting the bounds of the browser viewport.
 * @docs-private
 */
@Injectable()
export class ViewportRuler {
  /** Cached document client rectangle. */
  private _documentRect?: ClientRect;

  constructor(scrollDispatcher: ScrollDispatcher) {
    // Subscribe to scroll and resize events and update the document rectangle
    // on changes.
	scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry());
  }

  /** Gets a ClientRect for the viewport's bounds. */
  getViewportRect(documentRect = this._documentRect): ClientRect {
    // Cache the document bounding rect so that we don't recompute it for
    // multiple calls.
	if (!documentRect) {
		this._cacheViewportGeometry();
		documentRect = this._documentRect;
	}

    // 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(documentRect);
	const height = window.innerHeight;
	const width = window.innerWidth;

	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.
   * @param documentRect
   */
  getViewportScrollPosition(documentRect = this._documentRect) {
    // Cache the document bounding rect so that we don't recompute it for
    // multiple calls.
	if (!documentRect) {
		this._cacheViewportGeometry();
		documentRect = this._documentRect;
	}

    // 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 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};
  }

  /** Caches the latest client rectangle of the document element. */
  _cacheViewportGeometry() {
	this._documentRect = document.documentElement.getBoundingClientRect();
  }
}

export function VIEWPORT_RULER_PROVIDER_FACTORY(
	parentRuler: ViewportRuler, scrollDispatcher: ScrollDispatcher) {
  return parentRuler || new ViewportRuler(scrollDispatcher);
}

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], ScrollDispatcher],
  useFactory: VIEWPORT_RULER_PROVIDER_FACTORY
};