import { ThyAbstractOverlayContainer , ThyClickDispatcher } from 'ngx-tethys/core' ; import { Observable , timer } from 'rxjs' ; import { filter , takeUntil } from 'rxjs/operators' ; import { AnimationEvent } from '@angular/animations' ; import { CdkPortalOutlet } from '@angular/cdk/portal' ; import { DOCUMENT } from '@angular/common' ; import { AfterViewInit , ChangeDetectorRef , Component , ElementRef , EventEmitter , HostListener , Inject , NgZone , OnDestroy , ViewChild } from '@angular/core' ; import { thyPopoverAnimations } from './popover-animations' ; import { ThyPopoverConfig } from './popover.config' ; import { popoverAbstractOverlayOptions } from './popover.options' ; @ Component ( { selector : 'thy-popover-container' , templateUrl : './popover-container.component.html' , animations : [ thyPopoverAnimations . popoverContainer ] , host : { class : 'thy-popover-container' , tabindex : '-1' , '[attr.role]' : `'popover'` , '[@popoverContainer]' : 'animationState' , '(@popoverContainer.start)' : 'onAnimationStart($event)' , '(@popoverContainer.done)' : 'onAnimationDone($event)' } } ) export class ThyPopoverContainerComponent < TData = unknown > extends ThyAbstractOverlayContainer < TData > implements AfterViewInit , OnDestroy { @ ViewChild ( CdkPortalOutlet , { static : true } ) portalOutlet : CdkPortalOutlet ; /** State of the popover animation. */ animationState : 'void' | 'enter' | 'exit' = 'enter' ; /** Emits when an animation state changes. */ animationStateChanged = new EventEmitter < AnimationEvent > ( ) ; animationOpeningDone : Observable < AnimationEvent > ; animationClosingDone : Observable < AnimationEvent > ; insideClicked = new EventEmitter ( ) ; outsideClicked = new EventEmitter ( ) ; beforeAttachPortal ( ) { } constructor ( private elementRef , public config , changeDetectorRef , private thyClickDispatcher , private ngZone ) { super ( popoverAbstractOverlayOptions , changeDetectorRef ) ; this . animationOpeningDone = this . animationStateChanged . pipe ( filter ( ( event ) => { return event . phaseName === 'done' && event . toState === 'enter' ; } ) ) ; this . animationClosingDone = this . animationStateChanged . pipe ( filter ( ( event ) => { return event . phaseName === 'done' && event . toState === 'exit' ; } ) ) ; } ngAfterViewInit ( ) { if ( this . config . outsideClosable && ! this . config . hasBackdrop ) { timer ( ) . subscribe ( ( ) => { this . thyClickDispatcher . clicked ( ) . pipe ( takeUntil ( this . animationClosingDone ) ) . subscribe ( ( event ) => { if ( ! this . elementRef . nativeElement . contains ( event . target as HTMLElement ) ) { this . ngZone . run ( ( ) => { this . outsideClicked . emit ( ) ; } ) ; } } ) ; } ) ; } } /** Callback, invoked whenever an animation on the host completes. */ onAnimationDone ( event ) { this . animationStateChanged . emit ( event ) ; } /** Callback, invoked when an animation on the host starts. */ onAnimationStart ( event ) { this . animationStateChanged . emit ( event ) ; } startExitAnimation ( ) { this . animationState = 'exit' ; this . changeDetectorRef . markForCheck ( ) ; } @ HostListener ( 'click' , [ ] ) onInsideClick ( ) { if ( this . config . insideClosable ) { this . insideClicked . emit ( ) ; } } ngOnDestroy ( ) { super . destroy ( ) ; } }