UNPKG

ipsos-components

Version:

Material Design components for Angular

116 lines (95 loc) 4.06 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, Inject, InjectionToken, Optional, SkipSelf, OnDestroy} from '@angular/core'; import {OverlayRef} from '../overlay-ref'; import {Subscription} from 'rxjs/Subscription'; import {filter} from 'rxjs/operators/filter'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {DOCUMENT} from '@angular/common'; /** * Service for dispatching keyboard events that land on the body to appropriate overlay ref, * if any. It maintains a list of attached overlays to determine best suited overlay based * on event target and order of overlay opens. */ @Injectable() export class OverlayKeyboardDispatcher implements OnDestroy { /** Currently attached overlays in the order they were attached. */ _attachedOverlays: OverlayRef[] = []; private _keydownEventSubscription: Subscription | null; constructor(@Inject(DOCUMENT) private _document: any) {} ngOnDestroy() { this._unsubscribeFromKeydownEvents(); } /** Add a new overlay to the list of attached overlay refs. */ add(overlayRef: OverlayRef): void { // Lazily start dispatcher once first overlay is added if (!this._keydownEventSubscription) { this._subscribeToKeydownEvents(); } this._attachedOverlays.push(overlayRef); } /** Remove an overlay from the list of attached overlay refs. */ remove(overlayRef: OverlayRef): void { const index = this._attachedOverlays.indexOf(overlayRef); if (index > -1) { this._attachedOverlays.splice(index, 1); } // Remove the global listener once there are no more overlays. if (this._attachedOverlays.length === 0) { this._unsubscribeFromKeydownEvents(); } } /** * Subscribe to keydown events that land on the body and dispatch those * events to the appropriate overlay. */ private _subscribeToKeydownEvents(): void { const bodyKeydownEvents = fromEvent<KeyboardEvent>(this._document.body, 'keydown'); this._keydownEventSubscription = bodyKeydownEvents.pipe( filter(() => !!this._attachedOverlays.length) ).subscribe(event => { // Dispatch keydown event to correct overlay reference this._selectOverlayFromEvent(event)._keydownEvents.next(event); }); } /** Removes the global keydown subscription. */ private _unsubscribeFromKeydownEvents(): void { if (this._keydownEventSubscription) { this._keydownEventSubscription.unsubscribe(); this._keydownEventSubscription = null; } } /** Select the appropriate overlay from a keydown event. */ private _selectOverlayFromEvent(event: KeyboardEvent): OverlayRef { // Check if any overlays contain the event const targetedOverlay = this._attachedOverlays.find(overlay => { return overlay.overlayElement === event.target || overlay.overlayElement.contains(event.target as HTMLElement); }); // Use that overlay if it exists, otherwise choose the most recently attached one return targetedOverlay || this._attachedOverlays[this._attachedOverlays.length - 1]; } } /** @docs-private */ export function OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY( dispatcher: OverlayKeyboardDispatcher, _document: any) { return dispatcher || new OverlayKeyboardDispatcher(_document); } /** @docs-private */ export const OVERLAY_KEYBOARD_DISPATCHER_PROVIDER = { // If there is already an OverlayKeyboardDispatcher available, use that. // Otherwise, provide a new one. provide: OverlayKeyboardDispatcher, deps: [ [new Optional(), new SkipSelf(), OverlayKeyboardDispatcher], // Coerce to `InjectionToken` so that the `deps` match the "shape" // of the type expected by Angular DOCUMENT as InjectionToken<any> ], useFactory: OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY };