license import { animate , AnimationEvent , state , style , transition , trigger } from '@angular/animations' ; import { FocusTrapFactory , InteractivityChecker } from '@angular/cdk/a11y' ; import { _getFocusedElementPierceShadowDom } from '@angular/cdk/platform' ; import { BasePortalOutlet , CdkPortalOutlet , ComponentPortal , DomPortal , TemplatePortal , } from '@angular/cdk/portal' ; import { DOCUMENT } from '@angular/common' ; import { ChangeDetectionStrategy , ChangeDetectorRef , Component , ComponentRef , ElementRef , EmbeddedViewRef , Inject , NgZone , OnDestroy , Optional , ViewChild , ViewEncapsulation , } from '@angular/core' ; import { Subject } from 'rxjs' ; import { distinctUntilChanged } from 'rxjs/operators' ; import { DialogConfig } from './dialog-config' ; export function throwDialogContentAlreadyAttachedError ( ) { throw Error ( 'Attempting to attach dialog content after content is already attached' ) ; } docs-private @ Component ( { selector : 'cdk-dialog-container' , templateUrl : './dialog-container.html' , styleUrls : [ 'dialog-container.css' ] , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . Default , animations : [ trigger ( 'dialog' , [ state ( 'enter' , style ( { opacity : 1 } ) ) , state ( 'exit, void' , style ( { opacity : 0 } ) ) , transition ( '* => enter' , animate ( '{{enterAnimationDuration}}' ) ) , transition ( '* => exit, * => void' , animate ( '{{exitAnimationDuration}}' ) ) , ] ) , ] , host : { '[@dialog]' : `{
      value: _state,
      params: {
        enterAnimationDuration: _config.enterAnimationDuration,
        exitAnimationDuration: _config.exitAnimationDuration
      }
    }` , '(@dialog.start)' : '_onAnimationStart($event)' , '(@dialog.done)' : '_animationDone.next($event)' , 'tabindex' : '-1' , '[attr.role]' : '_config.role' , 'aria-modal' : 'true' , '[attr.aria-label]' : '_config.ariaLabel || null' , '[attr.aria-describedby]' : '_config.ariaDescribedBy' , } , } ) export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy { private readonly _document : Document ; /** State of the dialog animation. */ _state : 'void' | 'enter' | 'exit' = 'enter' ; /** 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 = this . _focusTrapFactory . create ( this . _elementRef . nativeElement ) ; /** The portal host inside of this container into which the dialog content will be loaded. */ @ ViewChild ( CdkPortalOutlet , { static : true } ) _portalHost : CdkPortalOutlet ; /** A subject emitting before the dialog enters the view. */ readonly _beforeEnter = new Subject < void > ( ) ; /** A subject emitting after the dialog enters the view. */ readonly _afterEnter = new Subject < void > ( ) ; /** A subject emitting before the dialog exits the view. */ readonly _beforeExit = new Subject < void > ( ) ; /** A subject emitting after the dialog exits the view. */ readonly _afterExit = new Subject < void > ( ) ; /** Stream of animation `done` events. */ readonly _animationDone = new Subject < AnimationEvent > ( ) ; constructor ( private _elementRef , private _focusTrapFactory , private _changeDetectorRef , private readonly _interactivityChecker , private readonly _ngZone , @ Optional ( ) @ Inject ( DOCUMENT ) _document , /** The dialog configuration. */ public _config ) { super ( ) ; this . _document = _document ; this . _animationDone . pipe ( distinctUntilChanged ( ( x , y ) => { return x . fromState === y . fromState && x . toState === y . toState ; } ) ) . subscribe ( event => { if ( event . toState === 'enter' ) { this . _autoFocus ( ) ; this . _afterEnter . next ( ) ; this . _afterEnter . complete ( ) ; } if ( event . fromState === 'enter' && ( event . toState === 'void' || event . toState === 'exit' ) ) { this . _returnFocusAfterDialog ( ) ; this . _afterExit . next ( ) ; this . _afterExit . complete ( ) ; } } ) ; } /** Initializes the dialog container with the attached content. */ _initializeWithAttachedContent ( ) { this . _savePreviouslyFocusedElement ( ) ; this . _focusDialogContainer ( ) ; } /** Destroy focus trap to place focus back to the element focused before the dialog opened. */ ngOnDestroy ( ) { this . _focusTrap . destroy ( ) ; this . _animationDone . complete ( ) ; } param portal attachComponentPortal < T > ( portal ) { if ( this . _portalHost . hasAttached ( ) && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) { throwDialogContentAlreadyAttachedError ( ) ; } return this . _portalHost . attachComponentPortal ( portal ) ; } param portal attachTemplatePortal < C > ( portal ) { if ( this . _portalHost . hasAttached ( ) && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) { throwDialogContentAlreadyAttachedError ( ) ; } return this . _portalHost . attachTemplatePortal ( portal ) ; } param portal deprecated breaking-change override attachDomPortal = ( portal ) => { if ( this . _portalHost . hasAttached ( ) && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) { throwDialogContentAlreadyAttachedError ( ) ; } return this . _portalHost . attachDomPortal ( portal ) ; } ; /** Emit lifecycle events based on animation `start` callback. */ _onAnimationStart ( event ) { if ( event . toState === 'enter' ) { this . _beforeEnter . next ( ) ; this . _beforeEnter . complete ( ) ; } if ( event . fromState === 'enter' && ( event . toState === 'void' || event . toState === 'exit' ) ) { this . _beforeExit . next ( ) ; this . _beforeExit . complete ( ) ; } } /** Starts the dialog exit animation. */ _startExiting ( ) { this . _state = 'exit' ; this . _changeDetectorRef . markForCheck ( ) ; } /** Saves a reference to the element that was focused before the dialog was opened. */ private _savePreviouslyFocusedElement ( ) { if ( this . _document ) { this . _elementFocusedBeforeDialogWasOpened = _getFocusedElementPierceShadowDom ( ) ; } } /** Focuses the dialog container. */ private _focusDialogContainer ( ) { if ( this . _elementRef . nativeElement . focus ) { this . _elementRef . nativeElement . focus ( ) ; } } param element private _forceFocus ( element , options ? ) { if ( ! this . _interactivityChecker . isFocusable ( element ) ) { element . tabIndex = - 1 ; this . _ngZone . runOutsideAngular ( ( ) => { element . addEventListener ( 'blur' , ( ) => element . removeAttribute ( 'tabindex' ) ) ; element . addEventListener ( 'mousedown' , ( ) => element . removeAttribute ( 'tabindex' ) ) ; } ) ; } element . focus ( options ) ; } param selector private _focusByCssSelector ( selector , options ? ) { let elementToFocus = this . _elementRef . nativeElement . querySelector ( selector ) as HTMLElement | null ; if ( elementToFocus ) { this . _forceFocus ( elementToFocus , options ) ; } } /**
     * Autofocus the element specified by the autoFocus field. When autoFocus is not 'dialog', if
     * for some reason the element cannot be focused, the dialog container will be focused.
     */ private _autoFocus ( ) { const element = this . _elementRef . nativeElement ; switch ( this . _config . autoFocus ) { case false : case 'dialog' : const activeElement = _getFocusedElementPierceShadowDom ( ) ; if ( activeElement !== element && ! element . contains ( activeElement ) ) { element . focus ( ) ; } break ; case true : case 'first-tabbable' : this . _focusTrap . focusInitialElementWhenReady ( ) . then ( hasMovedFocus => { if ( ! hasMovedFocus ) { element . focus ( ) ; } } ) ; break ; case 'first-heading' : this . _focusByCssSelector ( 'h1, h2, h3, h4, h5, h6, [role="heading"]' ) ; break ; default : this . _focusByCssSelector ( this . _config . autoFocus ! ) ; break ; } } /** Returns the focus to the element focused before the dialog was open. */ private _returnFocusAfterDialog ( ) { const toFocus = this . _elementFocusedBeforeDialogWasOpened ; if ( toFocus && typeof toFocus . focus === 'function' ) { const activeElement = _getFocusedElementPierceShadowDom ( ) ; const element = this . _elementRef . nativeElement ; if ( ! activeElement || activeElement === this . _document . body || activeElement === element || element . contains ( activeElement ) ) { toFocus . focus ( ) ; } } } }