import {NgModule,Component,Input,Output,OnDestroy,EventEmitter,Renderer2,ElementRef,ChangeDetectorRef,NgZone,
ContentChildren,TemplateRef,AfterContentInit,QueryList,ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {CommonModule} from '@angular/common';
import {DomHandler} from 'mirra-ui/dom';
import {PrimeTemplate} from 'mirra-ui/api';
import {RippleModule} from 'mirra-ui/ripple';
import {trigger,state,style,transition,animate,AnimationEvent} from '@angular/animations';
@Component({
selector: 'p-overlayPanel',
template: `
`,
animations: [
trigger('animation', [
state('void', style({
transform: 'scaleY(0.8)',
opacity: 0
})),
state('close', style({
opacity: 0
})),
state('open', style({
transform: 'translateY(0)',
opacity: 1
})),
transition('void => open', animate('{{showTransitionParams}}')),
transition('open => close', animate('{{hideTransitionParams}}')),
])
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
styleUrls: ['./overlaypanel.scss']
})
export class OverlayPanel implements AfterContentInit, OnDestroy {
@Input() dismissable: boolean = true;
@Input() showCloseIcon: boolean;
@Input() style: any;
@Input() styleClass: string;
@Input() appendTo: any;
@Input() autoZIndex: boolean = true;
@Input() ariaCloseLabel: string;
@Input() baseZIndex: number = 0;
@Input() focusOnShow: boolean = true;
@Input() showTransitionOptions: string = '.12s cubic-bezier(0, 0, 0.2, 1)';
@Input() hideTransitionOptions: string = '.1s linear';
@Output() onShow: EventEmitter = new EventEmitter();
@Output() onHide: EventEmitter = new EventEmitter();
@ContentChildren(PrimeTemplate) templates: QueryList;
container: HTMLDivElement;
overlayVisible: boolean = false;
render: boolean = false;
isContainerClicked: boolean = true;
documentClickListener: any;
target: any;
willHide: boolean;
documentResizeListener: any;
contentTemplate: TemplateRef;
destroyCallback: Function;
constructor(public el: ElementRef, public renderer: Renderer2, private cd: ChangeDetectorRef, private zone: NgZone) {}
ngAfterContentInit() {
this.templates.forEach((item) => {
switch(item.getType()) {
case 'content':
this.contentTemplate = item.template;
break;
default:
this.contentTemplate = item.template;
break;
}
this.cd.markForCheck();
});
}
onContainerClick() {
this.isContainerClicked = true;
}
bindDocumentClickListener() {
if (!this.documentClickListener && this.dismissable) {
this.zone.runOutsideAngular(() => {
let documentEvent = DomHandler.isIOS() ? 'touchstart' : 'click';
this.documentClickListener = this.renderer.listen('document', documentEvent, (event) => {
if (!this.container.contains(event.target) && this.target !== event.target && !this.target.contains(event.target) && !this.isContainerClicked) {
this.zone.run(() => {
this.hide();
});
}
this.isContainerClicked = false;
this.cd.markForCheck();
});
});
}
}
unbindDocumentClickListener() {
if (this.documentClickListener) {
this.documentClickListener();
this.documentClickListener = null;
}
}
toggle(event, target?) {
if (this.overlayVisible) {
if (this.hasTargetChanged(event, target)) {
this.destroyCallback = () => {
this.show(null, (target||event.currentTarget||event.target));
};
}
this.hide();
}
else {
this.show(event, target);
}
}
show(event, target?) {
this.target = target||event.currentTarget||event.target;
this.overlayVisible = true;
this.render = true;
this.cd.markForCheck();
}
hasTargetChanged(event, target) {
return this.target != null && this.target !== (target||event.currentTarget||event.target);
}
appendContainer() {
if (this.appendTo) {
if (this.appendTo === 'body')
document.body.appendChild(this.container);
else
DomHandler.appendChild(this.container, this.appendTo);
}
}
restoreAppend() {
if (this.container && this.appendTo) {
this.el.nativeElement.appendChild(this.container);
}
}
align() {
if (this.autoZIndex) {
this.container.style.zIndex = String(this.baseZIndex + (++DomHandler.zindex));
}
DomHandler.absolutePosition(this.container, this.target);
if (DomHandler.getOffset(this.container).top < DomHandler.getOffset(this.target).top) {
DomHandler.addClass(this.container, 'p-overlaypanel-flipped');
}
if (Math.floor(DomHandler.getOffset(this.container).left) < Math.floor(DomHandler.getOffset(this.target).left) &&
DomHandler.getOffset(this.container).left > 0) {
DomHandler.addClass(this.container, 'p-overlaypanel-shifted');
}
}
onAnimationStart(event: AnimationEvent) {
if (event.toState === 'open') {
this.container = event.element;
this.onShow.emit(null);
this.appendContainer();
this.align();
this.bindDocumentClickListener();
this.bindDocumentResizeListener();
if (this.focusOnShow) {
this.focus();
}
}
}
onAnimationEnd(event: AnimationEvent) {
switch (event.toState) {
case 'void':
if (this.destroyCallback) {
this.destroyCallback();
this.destroyCallback = null;
}
break;
case 'close':
this.onContainerDestroy();
this.onHide.emit({});
this.render = false;
break;
}
}
focus() {
let focusable = DomHandler.findSingle(this.container, '[autofocus]');
if (focusable) {
this.zone.runOutsideAngular(() => {
setTimeout(() => focusable.focus(), 5);
});
}
}
hide() {
this.overlayVisible = false;
this.cd.markForCheck();
}
onCloseClick(event) {
this.hide();
event.preventDefault();
}
onWindowResize(event) {
this.hide();
}
bindDocumentResizeListener() {
this.documentResizeListener = this.onWindowResize.bind(this);
window.addEventListener('resize', this.documentResizeListener);
}
unbindDocumentResizeListener() {
if (this.documentResizeListener) {
window.removeEventListener('resize', this.documentResizeListener);
this.documentResizeListener = null;
}
}
onContainerDestroy() {
this.target = null;
this.unbindDocumentClickListener();
this.unbindDocumentResizeListener();
}
ngOnDestroy() {
this.target = null;
this.destroyCallback = null;
if (this.container) {
this.restoreAppend();
this.onContainerDestroy();
}
}
}
@NgModule({
imports: [CommonModule,RippleModule],
exports: [OverlayPanel],
declarations: [OverlayPanel]
})
export class OverlayPanelModule { }