blob: 5542c878e675a1e7c4c12c081fbb69ab407d0ade [file] [log] [blame]
/********************************************************************************
* 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);
}
}