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 { }