import { OnDestroy , ElementRef , NgZone } from '@angular/core' ; import { OverlayRef } from '@angular/cdk/overlay' ; import { Subject , fromEvent } from 'rxjs' ; import { Platform } from '@angular/cdk/platform' ; import { FocusMonitor } from '@angular/cdk/a11y' ; import { takeUntil , take } from 'rxjs/operators' ; export type ThyOverlayTrigger = 'hover' | 'focus' | 'click' ; export abstract class ThyOverlayDirectiveBase { protected elementRef : ElementRef ; private initialized = false ; /** Trigger Overlay */ protected _trigger : ThyOverlayTrigger = 'click' ; public get trigger ( ) { return this . _trigger ; } public set trigger ( value ) { this . _trigger = value ; if ( this . initialized ) { this . clearEventListeners ( ) ; this . initialize ( ) ; } } protected overlayRef : OverlayRef ; protected manualListeners = new Map < string , EventListenerOrEventListenerObject > ( ) ; protected ngUnsubscribe$ = new Subject ( ) ; protected focusMonitor : FocusMonitor ; protected platform : Platform ; protected ngZone : NgZone ; protected showDelay ? = 0 ; protected hideDelay ? = 0 ; protected touchendHideDelay ? = 0 ; protected disabled = false ; protected showTimeoutId : number | null | any ; protected hideTimeoutId : number | null | any ; protected tooltipPin : boolean ; /** create overlay, you can use popover service or overlay*/ abstract createOverlay ( ) ; abstract show ( delay ? ) ; abstract hide ( delay ? ) ; private clearEventListeners ( ) { this . manualListeners . forEach ( ( listener , event ) => { this . elementRef . nativeElement . removeEventListener ( event , listener ) ; } ) ; this . manualListeners . clear ( ) ; this . focusMonitor . stopMonitoring ( this . elementRef ) ; } private clearTimer ( ) { if ( this . showTimeoutId ) { clearTimeout ( this . showTimeoutId ) ; } if ( this . hideTimeoutId ) { clearTimeout ( this . hideTimeoutId ) ; } } constructor ( elementRef , platform , focusMonitor , ngZone ) { this . elementRef = elementRef ; this . platform = platform ; this . focusMonitor = focusMonitor ; this . ngZone = ngZone ; } initialize ( ) { this . initialized = true ; const element = this . elementRef . nativeElement ; if ( ! this . platform . IOS && ! this . platform . ANDROID ) { if ( this . trigger === 'hover' ) { this . manualListeners . set ( 'mouseenter' , ( ) => { this . show ( ) ; } ) . set ( 'mouseleave' , ( event ) => { const overlayElement = this . overlayRef && this . overlayRef . overlayElement ; const toElement = event [ 'toElement' ] || event . relatedTarget ; if ( overlayElement && overlayElement . contains && overlayElement . contains ( toElement as Element ) && this . tooltipPin ) { fromEvent ( overlayElement , 'mouseleave' ) . pipe ( take ( 1 ) ) . subscribe ( ( ) => { this . hide ( ) ; } ) ; } else { this . hide ( ) ; } if ( ! this . overlayRef ) { this . clearTimer ( ) ; } } ) ; } else if ( this . trigger === 'focus' ) { this . focusMonitor . monitor ( this . elementRef ) . pipe ( takeUntil ( this . ngUnsubscribe$ ) ) . subscribe ( origin => { if ( ! origin ) { this . ngZone . run ( ( ) => this . hide ( 0 ) ) ; } else { this . ngZone . run ( ( ) => this . show ( ) ) ; } } ) ; } else if ( this . trigger === 'click' ) { this . manualListeners . set ( 'click' , ( ) => this . show ( ) ) ; } else { throw new Error ( `${ this . trigger } is not support, only support hover | focus | click` ) ; } } else { const touchendListener = ( ) => { setTimeout ( ( ) => { this . hide ( ) ; } , this . touchendHideDelay ) ; } ; this . manualListeners . set ( 'touchend' , touchendListener ) . set ( 'touchcancel' , touchendListener ) . set ( 'touchstart' , ( ) => { this . show ( ) ; } ) ; } this . manualListeners . forEach ( ( listener , event ) => element . addEventListener ( event , listener ) ) ; } dispose ( ) { this . ngUnsubscribe$ . next ( ) ; this . ngUnsubscribe$ . complete ( ) ; if ( this . overlayRef ) { this . overlayRef . dispose ( ) ; } this . clearEventListeners ( ) ; this . clearTimer ( ) ; } }