import { Directive , ElementRef , HostBinding , HostListener , Inject , Input , OnChanges , OnDestroy , OnInit , Renderer2 , TemplateRef , ViewContainerRef } from '@angular/core' ; import { DOCUMENT } from '@angular/common' ; import { Instance , Options , Placement , createPopper } from '@popperjs/core' ; import { race } from 'rxjs' ; import { filter , first } from 'rxjs/operators' ; import { defaultConfig } from './popover.model' ; import { ObGlobalEventsService } from '../global-events/global-events.service' ; import { obOutsideFilter } from '../global-events/outsideFilter' ; @ Directive ( { selector : '[obPopover]' , exportAs : 'obPopover' , host : { class : 'ob-popover' } } ) export class ObPopoverDirective implements OnInit , OnChanges , OnDestroy { @ Input ( 'obPopover' ) target : TemplateRef < HTMLElement > ; @ Input ( ) placement : Placement = 'auto' ; @ Input ( ) popperOptions : Options = { } as Options ; @ Input ( ) id : string ; @ HostBinding ( 'attr.aria-describedby' ) idContent : string ; private static idCount = 0 ; private readonly body : HTMLElement ; private readonly host : HTMLElement ; private instance : Instance ; private popover : HTMLDivElement ; constructor ( el , private readonly renderer , @ Inject ( DOCUMENT ) document , private readonly globalEventsService , private readonly viewContainerRef ) { this . body = document . body ; this . host = el . nativeElement ; } ngOnInit ( ) { this . id = this . id || `popover-${ ObPopoverDirective . idCount ++ }` ; this . idContent = `${ this . id }-content` ; } ngOnChanges ( ) { this . setPopperOptionsAndUpdate ( ) ; } ngOnDestroy ( ) { this . close ( ) ; } @ HostListener ( 'click' ) toggle ( ) { if ( this . popover ) { this . close ( ) ; } else { this . open ( ) ; } } close ( ) { this . popover ?. remove ( ) ; this . popover = undefined ; this . instance ?. destroy ( ) ; this . instance = undefined ; } open ( ) { this . popover = this . buildPopover ( ) ; this . listenForCloseEvent ( ) ; setTimeout ( ( ) => { this . renderer . appendChild ( this . body , this . popover ) ; this . instance = createPopper ( this . host , this . popover , defaultConfig ) ; this . setPopperOptionsAndUpdate ( ) ; } ) ; } private setPopperOptionsAndUpdate ( ) { this . instance ?. setOptions ( { ... this . popperOptions , placement : this . placement } ) ; this . instance ?. update ( ) ; } private buildPopover ( ) { const popover = this . renderer . createElement ( 'div' ) ; this . viewContainerRef . createEmbeddedView < HTMLElement > ( this . target ) . rootNodes . forEach ( node => this . renderer . appendChild ( popover , node ) ) ; this . renderer . addClass ( popover , 'ob-popover-content' ) ; this . renderer . setAttribute ( popover , 'role' , 'tooltip' ) ; this . renderer . setAttribute ( popover , 'id' , this . idContent ) ; const arrow = this . renderer . createElement ( 'div' ) ; this . renderer . addClass ( arrow , 'ob-popover-arrow' ) ; this . renderer . appendChild ( popover , arrow ) ; return popover ; } private listenForCloseEvent ( ) { race ( this . globalEventsService . click$ . pipe ( obOutsideFilter ( this . host , this . popover ) ) , this . globalEventsService . keyDown$ . pipe ( filter ( evt => evt . key === 'Escape' ) ) ) . pipe ( first ( ) ) . subscribe ( ( ) => this . close ( ) ) ; } }