| /******************************************************************************** |
| * Copyright (c) 2020 Contributors to the Eclipse Foundation |
| * |
| * See the NOTICE file(s) distributed with this work for additional |
| * information regarding copyright ownership. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| ********************************************************************************/ |
| |
| import {Overlay, OverlayConfig, OverlayRef, PositionStrategy} from "@angular/cdk/overlay"; |
| import {TemplatePortal} from "@angular/cdk/portal"; |
| import {DOCUMENT} from "@angular/common"; |
| import {Directive, ElementRef, Inject, Input, OnDestroy, TemplateRef, ViewContainerRef} from "@angular/core"; |
| import {fromEvent, merge, Subscription, timer} from "rxjs"; |
| import {take} from "rxjs/operators"; |
| import {WINDOW} from "../../../core/dom/window.token"; |
| |
| @Directive({ |
| selector: "[appDropDown]", |
| exportAs: "appDropDown" |
| }) |
| export class DropDownDirective<C> implements OnDestroy { |
| |
| @Input() |
| public appDropDown: TemplateRef<C>; |
| |
| public isOpen = false; |
| |
| private overlayRef: OverlayRef; |
| |
| private subscriptionToClose: Subscription; |
| |
| constructor( |
| private readonly elementRef: ElementRef<HTMLElement>, |
| private readonly viewContainerRef: ViewContainerRef, |
| private readonly overlay: Overlay, |
| @Inject(DOCUMENT) private readonly document: Document, |
| @Inject(WINDOW) private readonly window: Window |
| ) { |
| |
| } |
| |
| public ngOnDestroy(): void { |
| this.close(); |
| } |
| |
| public toggle() { |
| return this.overlayRef == null ? this.open() : this.close(); |
| } |
| |
| public async open() { |
| await timer(0).toPromise(); |
| |
| if (this.appDropDown == null) { |
| return; |
| } |
| |
| if (this.overlayRef == null) { |
| this.overlayRef = this.overlay.create(this.getOverlayConfig()); |
| const portal = new TemplatePortal(this.appDropDown, this.viewContainerRef); |
| this.overlayRef.attach(portal); |
| this.overlayRef.detachments().pipe(take(1)).subscribe(() => this.close(), () => this.close()); |
| this.isOpen = true; |
| this.subscribeToClose(); |
| } |
| } |
| |
| public close() { |
| if (this.overlayRef != null) { |
| this.unsubscribeToClose(); |
| this.overlayRef.detach(); |
| this.overlayRef = null; |
| this.isOpen = false; |
| } |
| } |
| |
| private subscribeToClose() { |
| this.unsubscribeToClose(); |
| if (this.overlayRef != null) { |
| this.subscriptionToClose = merge( |
| this.overlayRef.detachments(), |
| fromEvent(this.document, "click"), |
| fromEvent(this.window, "resize"), |
| fromEvent(this.elementRef.nativeElement, "resize") |
| ).subscribe(() => this.close()); |
| } |
| } |
| |
| private unsubscribeToClose() { |
| if (this.subscriptionToClose != null) { |
| this.subscriptionToClose.unsubscribe(); |
| } |
| } |
| |
| private getOverlayConfig(): OverlayConfig { |
| const origin = this.elementRef.nativeElement; |
| const rect = origin.getBoundingClientRect(); |
| return new OverlayConfig({ |
| width: rect.width, |
| positionStrategy: this.getOverlayPosition(origin), |
| scrollStrategy: this.overlay.scrollStrategies.close() |
| }); |
| } |
| |
| private getOverlayPosition(origin: HTMLElement): PositionStrategy { |
| return this.overlay.position() |
| .flexibleConnectedTo(origin) |
| .withPositions([{ |
| originX: "center", |
| originY: "bottom", |
| overlayX: "center", |
| overlayY: "top" |
| }]) |
| .withFlexibleDimensions(false) |
| .withPush(false); |
| } |
| |
| } |