@@ -5,30 +5,50 @@ import {
55  ViewEncapsulation , 
66  NgZone , 
77  OnDestroy , 
8-   Renderer , 
8+   animate , 
9+   state , 
10+   style , 
11+   transition , 
12+   trigger , 
13+   AnimationTransitionEvent , 
14+   EventEmitter , 
915}  from  '@angular/core' ; 
1016import  { BasePortalHost ,  ComponentPortal ,  PortalHostDirective ,  TemplatePortal }  from  '../core' ; 
1117import  { MdDialogConfig }  from  './dialog-config' ; 
12- import  { MdDialogRef }  from  './dialog-ref' ; 
1318import  { MdDialogContentAlreadyAttachedError }  from  './dialog-errors' ; 
1419import  { FocusTrap }  from  '../core/a11y/focus-trap' ; 
1520import  'rxjs/add/operator/first' ; 
1621
1722
23+ /** Possible states for the dialog container animation. */ 
24+ export  type  MdDialogContainerAnimationState  =  'void'  |  'enter'  |  'exit'  |  'exit-start' ; 
25+ 
26+ 
1827/** 
1928 * Internal component that wraps user-provided dialog content. 
29+  * Animation is based on https://material.io/guidelines/motion/choreography.html. 
2030 * @docs -private 
2131 */ 
2232@Component ( { 
2333  moduleId : module . id , 
2434  selector : 'md-dialog-container, mat-dialog-container' , 
2535  templateUrl : 'dialog-container.html' , 
2636  styleUrls : [ 'dialog.css' ] , 
37+   encapsulation : ViewEncapsulation . None , 
38+   animations : [ 
39+     trigger ( 'slideDialog' ,  [ 
40+       state ( 'void' ,  style ( {  transform : 'translateY(25%) scale(0.9)' ,  opacity : 0  } ) ) , 
41+       state ( 'enter' ,  style ( {  transform : 'translateY(0%) scale(1)' ,  opacity : 1  } ) ) , 
42+       state ( 'exit' ,  style ( {  transform : 'translateY(25%)' ,  opacity : 0  } ) ) , 
43+       transition ( '* => *' ,  animate ( '400ms cubic-bezier(0.25, 0.8, 0.25, 1)' ) ) , 
44+     ] ) 
45+   ] , 
2746  host : { 
2847    '[class.mat-dialog-container]' : 'true' , 
2948    '[attr.role]' : 'dialogConfig?.role' , 
49+     '[@slideDialog]' : '_state' , 
50+     '(@slideDialog.done)' : '_onAnimationDone($event)' , 
3051  } , 
31-   encapsulation : ViewEncapsulation . None , 
3252} ) 
3353export  class  MdDialogContainer  extends  BasePortalHost  implements  OnDestroy  { 
3454  /** The portal host inside of this container into which the dialog content will be loaded. */ 
@@ -38,15 +58,18 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
3858  @ViewChild ( FocusTrap )  _focusTrap : FocusTrap ; 
3959
4060  /** Element that was focused before the dialog was opened. Save this to restore upon close. */ 
41-   private  _elementFocusedBeforeDialogWasOpened : Element  =  null ; 
61+   private  _elementFocusedBeforeDialogWasOpened : HTMLElement  =  null ; 
4262
4363  /** The dialog configuration. */ 
4464  dialogConfig : MdDialogConfig ; 
4565
46-   /** Reference to the open dialog. */ 
47-   dialogRef : MdDialogRef < any > ; 
66+   /** State of the dialog animation. */ 
67+   _state : MdDialogContainerAnimationState  =  'enter' ; 
68+ 
69+   /** Emits the current animation state whenever it changes. */ 
70+   _onAnimationStateChange  =  new  EventEmitter < MdDialogContainerAnimationState > ( ) ; 
4871
49-   constructor ( private  _ngZone : NgZone ,   private   _renderer :  Renderer )  { 
72+   constructor ( private  _ngZone : NgZone )  { 
5073    super ( ) ; 
5174  } 
5275
@@ -87,20 +110,43 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
87110    // ready in instances where change detection has to run first. To deal with this, we simply 
88111    // wait for the microtask queue to be empty. 
89112    this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  { 
90-       this . _elementFocusedBeforeDialogWasOpened  =  document . activeElement ; 
113+       this . _elementFocusedBeforeDialogWasOpened  =  document . activeElement   as   HTMLElement ; 
91114      this . _focusTrap . focusFirstTabbableElement ( ) ; 
92115    } ) ; 
93116  } 
94117
118+   /** 
119+    * Kicks off the leave animation. 
120+    * @docs -private 
121+    */ 
122+   _exit ( ) : void { 
123+     this . _state  =  'exit' ; 
124+     this . _onAnimationStateChange . emit ( 'exit-start' ) ; 
125+   } 
126+ 
127+   /** 
128+    * Callback, invoked whenever an animation on the host completes. 
129+    * @docs -private 
130+    */ 
131+   _onAnimationDone ( event : AnimationTransitionEvent )  { 
132+     this . _onAnimationStateChange . emit ( event . toState  as  MdDialogContainerAnimationState ) ; 
133+   } 
134+ 
95135  ngOnDestroy ( )  { 
96136    // When the dialog is destroyed, return focus to the element that originally had it before 
97137    // the dialog was opened. Wait for the DOM to finish settling before changing the focus so 
98138    // that it doesn't end up back on the <body>. Also note that we need the extra check, because 
99139    // IE can set the `activeElement` to null in some cases. 
100-     if  ( this . _elementFocusedBeforeDialogWasOpened )  { 
101-       this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  { 
102-         this . _renderer . invokeElementMethod ( this . _elementFocusedBeforeDialogWasOpened ,  'focus' ) ; 
103-       } ) ; 
104-     } 
140+     this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  { 
141+       let  toFocus  =  this . _elementFocusedBeforeDialogWasOpened  as  HTMLElement ; 
142+ 
143+       // We need to check whether the focus method exists at all, because IE seems to throw an 
144+       // exception, even if the element is the document.body. 
145+       if  ( toFocus  &&  'focus'  in  toFocus )  { 
146+         toFocus . focus ( ) ; 
147+       } 
148+ 
149+       this . _onAnimationStateChange . complete ( ) ; 
150+     } ) ; 
105151  } 
106152} 
0 commit comments