import { ThyAbstractOverlayContainer , ThyClickPositioner } from 'ngx-tethys/core' ; import { Observable } from 'rxjs' ; import { filter } from 'rxjs/operators' ; import { AnimationEvent } from '@angular/animations' ; import { FocusTrap , FocusTrapFactory } from '@angular/cdk/a11y' ; import { CdkPortalOutlet } from '@angular/cdk/portal' ; import { DOCUMENT } from '@angular/common' ; import { ChangeDetectionStrategy , ChangeDetectorRef , Component , ElementRef , EventEmitter , HostBinding , Inject , OnDestroy , ViewChild } from '@angular/core' ; import { thyDialogAnimations } from './dialog-animations' ; import { ThyDialogConfig } from './dialog.config' ; import { dialogAbstractOverlayOptions } from './dialog.options' ; @ Component ( { selector : 'thy-dialog-container' , template : `
        <ng-template cdkPortalOutlet></ng-template>
    ` , changeDetection : ChangeDetectionStrategy . Default , animations : [ thyDialogAnimations . dialogContainer ] , host : { class : 'thy-dialog-container' , tabindex : '-1' , 'aria-modal' : 'true' , '[attr.id]' : 'id' , '[attr.role]' : 'config.role' , '[attr.aria-labelledby]' : 'config.ariaLabel ? null : ariaLabelledBy' , '[attr.aria-label]' : 'config.ariaLabel' , '[attr.aria-describedby]' : 'config.ariaDescribedBy || null' , '[@dialogContainer]' : 'animationState' , '(@dialogContainer.start)' : 'onAnimationStart($event)' , '(@dialogContainer.done)' : 'onAnimationDone($event)' } } ) export class ThyDialogContainerComponent extends ThyAbstractOverlayContainer implements OnDestroy { animationOpeningDone : Observable < AnimationEvent > ; animationClosingDone : Observable < AnimationEvent > ; @ ViewChild ( CdkPortalOutlet , { static : true } ) portalOutlet : CdkPortalOutlet ; @ HostBinding ( `attr.id` ) id : string ; /** State of the dialog animation. */ animationState : 'void' | 'enter' | 'exit' = 'void' ; /** Emits when an animation state changes. */ animationStateChanged = new EventEmitter < AnimationEvent > ( ) ; /** ID of the element that should be considered as the dialog's label. */ ariaLabelledBy : string | null = null ; /** Element that was focused before the dialog was opened. Save this to restore upon close. */ private elementFocusedBeforeDialogWasOpened : HTMLElement | null = null ; /** The class that traps and manages focus within the dialog. */ private focusTrap : FocusTrap ; private savePreviouslyFocusedElement ( ) { if ( this . document ) { this . elementFocusedBeforeDialogWasOpened = this . document . activeElement as HTMLElement ; if ( this . elementRef . nativeElement . focus ) { Promise . resolve ( ) . then ( ( ) => this . elementRef . nativeElement . focus ( ) ) ; } } } /** Moves the focus inside the focus trap. */ private trapFocus ( ) { const element = this . elementRef . nativeElement ; if ( ! this . focusTrap ) { this . focusTrap = this . focusTrapFactory . create ( element ) ; } if ( this . config . autoFocus ) { this . focusTrap . focusInitialElementWhenReady ( ) ; } else { const activeElement = this . document . activeElement ; if ( activeElement !== element && ! element . contains ( activeElement ) ) { element . focus ( ) ; } } } private restoreFocus ( ) { const toFocus = this . elementFocusedBeforeDialogWasOpened ; if ( this . config . restoreFocus && toFocus && typeof toFocus . focus === 'function' ) { toFocus . focus ( ) ; } if ( this . focusTrap ) { this . focusTrap . destroy ( ) ; } } private setTransformOrigin ( ) { this . clickPositioner . runTaskUseLastPosition ( lastPosition => { if ( lastPosition ) { const containerElement = this . elementRef . nativeElement ; const transformOrigin = `${ lastPosition . x - containerElement . offsetLeft }px ${ lastPosition . y - containerElement . offsetTop }px 0px` ; containerElement . style [ 'transform-origin' ] = transformOrigin ; } this . animationState = 'enter' ; } ) ; } constructor ( private elementRef , @ Inject ( DOCUMENT ) private document , public config , changeDetectorRef , private clickPositioner , private focusTrapFactory ) { super ( dialogAbstractOverlayOptions , changeDetectorRef ) ; this . animationOpeningDone = this . animationStateChanged . pipe ( filter ( ( event ) => { return event . phaseName === 'done' && event . toState === 'void' ; } ) ) ; this . animationClosingDone = this . animationStateChanged . pipe ( filter ( ( event ) => { return event . phaseName === 'done' && event . toState === 'exit' ; } ) ) ; } beforeAttachPortal ( ) { this . setTransformOrigin ( ) ; this . savePreviouslyFocusedElement ( ) ; } /** Callback, invoked whenever an animation on the host completes. */ onAnimationDone ( event ) { if ( event . toState === 'void' ) { this . trapFocus ( ) ; } else if ( event . toState === 'exit' ) { this . restoreFocus ( ) ; } this . animationStateChanged . emit ( event ) ; } /** Callback, invoked when an animation on the host starts. */ onAnimationStart ( event ) { this . animationStateChanged . emit ( event ) ; } ngOnDestroy ( ) { super . destroy ( ) ; } }