import { AnimationEvent , state , style , trigger } from '@angular/animations' ; import { FocusTrap , FocusTrapFactory } from '@angular/cdk/a11y' ; import { BasePortalOutlet , ComponentPortal , PortalHostDirective , TemplatePortal } from '@angular/cdk/portal' ; import { DOCUMENT } from '@angular/common' ; import { ChangeDetectorRef , Component , ComponentRef , ElementRef , EmbeddedViewRef , HostListener , Inject , OnDestroy , ViewChild , ViewEncapsulation , } from '@angular/core' ; import { Subject } from 'rxjs' ; import { DialogConfig } from './dialog-config' ; @ Component ( { selector : 'gd-dialog-container' , templateUrl : './dialog-container.component.html' , styleUrls : [ './dialog-container.component.scss' ] , encapsulation : ViewEncapsulation . None , animations : [ trigger ( 'dialog' , [ state ( 'enter' , style ( { opacity : 1 } ) ) , state ( 'exit, void' , style ( { opacity : 0 } ) ) , ] ) , ] , host : { '[attr.tabindex]' : '-1' , '[@dialog]' : '_state' , '[attr.role]' : 'config.role' , '[attr.aria-labelledby]' : '_ariaLabelledBy || null' , } , } ) export class DialogContainerComponent extends BasePortalOutlet implements OnDestroy { @ ViewChild ( PortalHostDirective ) _portalHost : PortalHostDirective ; _beforeEnter = new Subject < void > ( ) ; _afterEnter = new Subject < void > ( ) ; _beforeExit = new Subject < void > ( ) ; _afterExit = new Subject < void > ( ) ; /** State of the dialog animation. */ _state : 'void' | 'enter' | 'exit' = 'enter' ; _ariaLabelledBy : string ; private _focusTrap : FocusTrap ; private _elementFocusedBeforeDialogWasOpened : HTMLElement | null = null ; constructor ( private elementRef , private focusTrapFactory , private changeDetectorRef , @ Inject ( DOCUMENT ) private document , public config ) { super ( ) ; } ngOnDestroy ( ) { this . _focusTrap . destroy ( ) ; } attachComponentPortal < T > ( portal ) { if ( this . _portalHost . hasAttached ( ) ) { throw new Error ( 'Already dialog content attached!' ) ; } this . savePreviouslyFocusedElement ( ) ; return this . _portalHost . attachComponentPortal ( portal ) ; } attachTemplatePortal < C > ( portal ) { throw new Error ( 'We do not use template portal.' ) ; } _startExiting ( ) { this . _state = 'exit' ; this . changeDetectorRef . markForCheck ( ) ; } @ HostListener ( '@dialog.start' , [ '$event' ] ) _onAnimationStart ( event ) { if ( event . toState === 'enter' ) { this . _beforeEnter . next ( ) ; } if ( event . fromState === 'enter' && ( event . toState === 'void' || event . toState === 'exit' ) ) { this . _beforeExit . next ( ) ; } } @ HostListener ( '@dialog.done' , [ '$event' ] ) _onAnimationDone ( event ) { if ( event . toState === 'enter' ) { this . trapFocus ( ) ; this . _afterEnter . next ( ) ; } if ( event . fromState === 'enter' && ( event . toState === 'void' || event . toState === 'exit' ) ) { this . returnFocusAfterDialog ( ) ; this . _afterExit . next ( ) ; } } private trapFocus ( ) { if ( ! this . _focusTrap ) { this . _focusTrap = this . focusTrapFactory . create ( this . elementRef . nativeElement ) ; } if ( this . config . autoFocus ) { this . _focusTrap . focusInitialElementWhenReady ( ) . then ( ( hasMovedFocus ) => { if ( ! hasMovedFocus ) { this . elementRef . nativeElement . focus ( ) ; } } ) ; } } private savePreviouslyFocusedElement ( ) { this . _elementFocusedBeforeDialogWasOpened = this . document . activeElement as HTMLElement ; Promise . resolve ( ) . then ( ( ) => this . elementRef . nativeElement . focus ( ) ) ; } private returnFocusAfterDialog ( ) { const toFocus = this . _elementFocusedBeforeDialogWasOpened ; if ( toFocus && typeof toFocus . focus === 'function' ) { toFocus . focus ( ) ; } } }