import { Directive , ElementRef , EventEmitter , HostListener , Input , NgZone , OnDestroy , Output , Renderer2 , TemplateRef , ViewContainerRef , ComponentRef } from '@angular/core' ; import { coerceBooleanProperty , coerceNumberProperty } from '@angular/cdk/coercion' ; import { PlacementTypes } from '../../utils/position/placement-type.enum' ; import { AlignmentTypes } from '../../utils/position/alignment-types.enum' ; import { ShowTypes } from './show-types.enum' ; import { StyleTypes } from './style-types.enum' ; import { TooltipService } from './tooltip.service' ; import { TooltipContentComponent } from './tooltip.component' ; @ Directive ( { selector : '[ngx-tooltip]' , exportAs : 'ngxTooltip' } ) export class TooltipDirective implements OnDestroy { @ Input ( ) tooltipCssClass = '' ; @ Input ( ) tooltipTitle = '' ; @ Input ( ) tooltipPlacement : PlacementTypes = PlacementTypes . top ; @ Input ( ) tooltipAlignment : AlignmentTypes = AlignmentTypes . center ; @ Input ( ) tooltipType : StyleTypes = StyleTypes . popover ; @ Input ( ) tooltipTemplate : TemplateRef < any > ; @ Input ( ) tooltipShowEvent : ShowTypes = ShowTypes . all ; @ Input ( ) tooltipContext : any ; get tooltipSpacing ( ) : number { return this . _tooltipSpacing ; } @ Input ( ) set tooltipSpacing ( val ) { this . _tooltipSpacing = coerceNumberProperty ( val ) ; } get tooltipDisabled ( ) : boolean { return this . _tooltipDisabled ; } @ Input ( ) set tooltipDisabled ( val ) { this . _tooltipDisabled = coerceBooleanProperty ( val ) ; } get tooltipShowCaret ( ) : boolean { return this . _tooltipShowCaret ; } @ Input ( ) set tooltipShowCaret ( val ) { this . _tooltipShowCaret = coerceBooleanProperty ( val ) ; } get tooltipCloseOnClickOutside ( ) : boolean { return this . _tooltipCloseOnClickOutside ; } @ Input ( ) set tooltipCloseOnClickOutside ( val ) { this . _tooltipCloseOnClickOutside = coerceBooleanProperty ( val ) ; } get tooltipCloseOnMouseLeave ( ) : boolean { return this . _tooltipCloseOnMouseLeave ; } @ Input ( ) set tooltipCloseOnMouseLeave ( val ) { this . _tooltipCloseOnMouseLeave = coerceBooleanProperty ( val ) ; } get tooltipHideTimeout ( ) : number { return this . _tooltipHideTimeout ; } @ Input ( ) set tooltipHideTimeout ( val ) { this . _tooltipHideTimeout = coerceNumberProperty ( val , 300 ) ; } get tooltipShowTimeout ( ) : number { return this . _tooltipShowTimeout ; } @ Input ( ) set tooltipShowTimeout ( val ) { this . _tooltipShowTimeout = coerceNumberProperty ( val , 100 ) ; } @ Output ( ) show = new EventEmitter < boolean > ( ) ; @ Output ( ) hide = new EventEmitter < boolean > ( ) ; private _tooltipSpacing = 10 ; private _tooltipDisabled = false ; private _tooltipShowCaret = true ; private _tooltipCloseOnClickOutside = true ; private _tooltipCloseOnMouseLeave = true ; private _tooltipHideTimeout = 300 ; private _tooltipShowTimeout = 100 ; private get listensForFocus ( ) : boolean { return this . tooltipShowEvent === ShowTypes . all || this . tooltipShowEvent === ShowTypes . focus ; } private get listensForHover ( ) : boolean { return this . tooltipShowEvent === ShowTypes . all || this . tooltipShowEvent === ShowTypes . mouseover ; } private component : ComponentRef < TooltipContentComponent > ; private timeout : any ; private mouseLeaveContentEvent : ( ) => void ; private mouseEnterContentEvent : ( ) => void ; private documentClickEvent : ( ) => void ; constructor ( private readonly ngZone , private readonly tooltipService , private readonly viewContainerRef , private readonly renderer , private readonly element ) { } ngOnDestroy ( ) { this . hideTooltip ( true ) ; } @ HostListener ( 'focusin' ) onFocus ( ) { if ( this . listensForFocus ) { this . showTooltip ( ) ; } } @ HostListener ( 'blur' ) onBlur ( ) { if ( this . listensForFocus ) { this . hideTooltip ( true ) ; } } @ HostListener ( 'mouseenter' ) onMouseEnter ( ) { if ( this . listensForHover ) { this . showTooltip ( ) ; } } @ HostListener ( 'mouseleave' , [ '$event' ] ) onMouseLeave ( event ) { if ( this . listensForHover && this . tooltipCloseOnMouseLeave ) { clearTimeout ( this . timeout ) ; if ( this . component ) { const contentDom = this . component . instance . element . nativeElement ; const contains = contentDom . contains ( event . toElement ) ; if ( contains ) return ; } this . hideTooltip ( ) ; } } @ HostListener ( 'click' ) onMouseClick ( ) { if ( this . tooltipShowEvent === ShowTypes . mouseover ) { this . hideTooltip ( true ) ; } } showTooltip ( immediate ? ) { if ( this . component || this . tooltipDisabled ) return ; const time = immediate ? 0 : this . tooltipShowTimeout ; this . ngZone . run ( ( ) => { clearTimeout ( this . timeout ) ; this . timeout = setTimeout ( ( ) => { this . tooltipService . destroyAll ( ) ; const options = this . createBoundOptions ( ) ; this . component = this . tooltipService . create ( options ) ; setTimeout ( ( ) => { if ( this . component && this . component . instance && this . component . instance . element ) { this . addHideListeners ( this . component . instance . element . nativeElement ) ; } } , 10 ) ; this . show . emit ( true ) ; } , time ) ; } ) ; } hideTooltip ( immediate ? ) { if ( ! this . component ) return ; const destroyFn = ( ) => { if ( this . mouseLeaveContentEvent ) this . mouseLeaveContentEvent ( ) ; if ( this . mouseEnterContentEvent ) this . mouseEnterContentEvent ( ) ; if ( this . documentClickEvent ) this . documentClickEvent ( ) ; this . hide . emit ( true ) ; this . tooltipService . destroy ( this . component ) ; this . component = undefined ; } ; clearTimeout ( this . timeout ) ; if ( ! immediate ) { this . timeout = setTimeout ( destroyFn , this . tooltipHideTimeout ) ; } else { destroyFn ( ) ; } } addHideListeners ( tooltip ) { this . mouseEnterContentEvent = this . renderer . listen ( tooltip , 'mouseenter' , ( ) => { clearTimeout ( this . timeout ) ; } ) ; if ( this . tooltipCloseOnMouseLeave ) { this . mouseLeaveContentEvent = this . renderer . listen ( tooltip , 'mouseleave' , ( event ) => { const contentDom = this . element . nativeElement ; const contains = contentDom . contains ( event . toElement ) ; if ( contains ) return ; this . hideTooltip ( ) ; } ) ; } if ( this . tooltipCloseOnClickOutside ) { this . documentClickEvent = this . renderer . listen ( document , 'click' , event => { const tooltipContains = tooltip . contains ( event . target ) ; const parentContains = this . element . nativeElement . contains ( event . target ) ; if ( ! tooltipContains && ! parentContains ) { this . hideTooltip ( ) ; } } ) ; } } private createBoundOptions ( ) { return { title : this . tooltipTitle , template : this . tooltipTemplate , host : this . viewContainerRef . element , placement : this . tooltipPlacement , alignment : this . tooltipAlignment , type : this . tooltipType , showCaret : this . tooltipShowCaret , cssClass : this . tooltipCssClass , spacing : this . tooltipSpacing , context : this . tooltipContext } ; } }