aboutsummaryrefslogtreecommitdiffstats
path: root/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts
diff options
context:
space:
mode:
Diffstat (limited to 'sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts')
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts174
1 files changed, 174 insertions, 0 deletions
diff --git a/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts
new file mode 100644
index 00000000..2c145af5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/core/overlay/scroll/scroll-dispatcher.ts
@@ -0,0 +1,174 @@
+/**
+ * @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*/
+import {Platform} from '@angular/cdk';
+import {auditTime} from 'rxjs/operator/auditTime';
+import {ElementRef, Injectable, NgZone, Optional, SkipSelf} from '@angular/core';
+import {fromEvent} from 'rxjs/observable/fromEvent';
+import {merge} from 'rxjs/observable/merge';
+import {Subject} from 'rxjs/Subject';
+import {Subscription} from 'rxjs/Subscription';
+
+import {Scrollable} from './scrollable';
+
+
+/** Time in ms to throttle the scrolling events by default. */
+export const 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.
+ */
+@Injectable()
+export class ScrollDispatcher {
+ /** Subject for notifying that a registered scrollable reference element has
+ * been scrolled. */
+ _scrolled: Subject<void> = new Subject<void>();
+
+ /** Keeps track of the global `scroll` and `resize` subscriptions. */
+ _globalSubscription: Subscription|null = null;
+
+ /** Keeps track of the amount of subscriptions to `scrolled`. Used for
+ * cleaning up afterwards. */
+ private _scrolledCount = 0;
+
+ /**
+ * Map of all the scrollable references that are registered with the service
+ * and their scroll event subscriptions.
+ */
+ scrollableReferences: Map<Scrollable, Subscription> = new Map();
+
+ constructor(private _ngZone: NgZone, private _platform: Platform) {}
+
+ /**
+ * Registers a Scrollable with the service and listens for its scrolled
+ * events. When the scrollable is scrolled, the service emits the event in its
+ * scrolled observable.
+ * @param scrollable Scrollable instance to be registered.
+ */
+ register(scrollable: Scrollable): void {
+ const scrollSubscription =
+ scrollable.elementScrolled().subscribe(() => this._notify());
+
+ this.scrollableReferences.set(scrollable, scrollSubscription);
+ }
+
+ /**
+ * Deregisters a Scrollable reference and unsubscribes from its scroll event
+ * observable.
+ * @param scrollable Scrollable instance to be deregistered.
+ */
+ deregister(scrollable: Scrollable): void {
+ const scrollableReference = this.scrollableReferences.get(scrollable);
+
+ if (scrollableReference) {
+ scrollableReference.unsubscribe();
+ this.scrollableReferences.delete(scrollable);
+ }
+ }
+
+ /**
+ * Subscribes to 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.
+ */
+ scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME, callback: () => any):
+ Subscription {
+ // Scroll events can only happen on the browser, so do nothing if we're not
+ // on the browser.
+ if (!this._platform.isBrowser) {
+ return Subscription.EMPTY;
+ }
+
+ // In the case of a 0ms delay, use an observable without auditTime
+ // since it does add a perceptible delay in processing overhead.
+ const observable = auditTimeInMs > 0 ?
+ auditTime.call(this._scrolled.asObservable(), auditTimeInMs) :
+ this._scrolled.asObservable();
+
+ this._scrolledCount++;
+
+ if (!this._globalSubscription) {
+ this._globalSubscription = this._ngZone.runOutsideAngular(() => {
+ return merge(
+ fromEvent(window.document, 'scroll'),
+ fromEvent(window, 'resize'))
+ .subscribe(() => this._notify());
+ });
+ }
+
+ // Note that we need to do the subscribing from here, in order to be able to
+ // remove the global event listeners once there are no more subscriptions.
+ const subscription = observable.subscribe(callback);
+
+ subscription.add(() => {
+ this._scrolledCount--;
+
+ if (this._globalSubscription && !this.scrollableReferences.size &&
+ !this._scrolledCount) {
+ this._globalSubscription.unsubscribe();
+ this._globalSubscription = null;
+ }
+ });
+
+ return subscription;
+ }
+
+ /** Returns all registered Scrollables that contain the provided element. */
+ getScrollContainers(elementRef: ElementRef): Scrollable[] {
+ const scrollingContainers: Scrollable[] = [];
+
+ this.scrollableReferences.forEach(
+ (_subscription: Subscription, scrollable: Scrollable) => {
+ if (this.scrollableContainsElement(scrollable, elementRef)) {
+ scrollingContainers.push(scrollable);
+ }
+ });
+
+ return scrollingContainers;
+ }
+
+ /** Returns true if the element is contained within the provided Scrollable.
+ */
+ scrollableContainsElement(scrollable: Scrollable, elementRef: ElementRef):
+ boolean {
+ let element = elementRef.nativeElement;
+ const 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 = element.parentElement);
+
+ return false;
+ }
+
+ /** Sends a notification that a scroll event has been fired. */
+ _notify() {
+ this._scrolled.next();
+ }
+}
+
+export function SCROLL_DISPATCHER_PROVIDER_FACTORY(
+ parentDispatcher: ScrollDispatcher, ngZone: NgZone, platform: Platform) {
+ return parentDispatcher || new ScrollDispatcher(ngZone, platform);
+}
+
+export const 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
+};