@@ -5,30 +5,50 @@ import {
5
5
ViewEncapsulation ,
6
6
NgZone ,
7
7
OnDestroy ,
8
- Renderer ,
8
+ animate ,
9
+ state ,
10
+ style ,
11
+ transition ,
12
+ trigger ,
13
+ AnimationTransitionEvent ,
14
+ EventEmitter ,
9
15
} from '@angular/core' ;
10
16
import { BasePortalHost , ComponentPortal , PortalHostDirective , TemplatePortal } from '../core' ;
11
17
import { MdDialogConfig } from './dialog-config' ;
12
- import { MdDialogRef } from './dialog-ref' ;
13
18
import { MdDialogContentAlreadyAttachedError } from './dialog-errors' ;
14
19
import { FocusTrap } from '../core/a11y/focus-trap' ;
15
20
import 'rxjs/add/operator/first' ;
16
21
17
22
23
+ /** Possible states for the dialog container animation. */
24
+ export type MdDialogContainerAnimationState = 'void' | 'enter' | 'exit' | 'exit-start' ;
25
+
26
+
18
27
/**
19
28
* Internal component that wraps user-provided dialog content.
29
+ * Animation is based on https://material.io/guidelines/motion/choreography.html.
20
30
* @docs -private
21
31
*/
22
32
@Component ( {
23
33
moduleId : module . id ,
24
34
selector : 'md-dialog-container, mat-dialog-container' ,
25
35
templateUrl : 'dialog-container.html' ,
26
36
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
+ ] ,
27
46
host : {
28
47
'[class.mat-dialog-container]' : 'true' ,
29
48
'[attr.role]' : 'dialogConfig?.role' ,
49
+ '[@slideDialog]' : '_state' ,
50
+ '(@slideDialog.done)' : '_onAnimationDone($event)' ,
30
51
} ,
31
- encapsulation : ViewEncapsulation . None ,
32
52
} )
33
53
export class MdDialogContainer extends BasePortalHost implements OnDestroy {
34
54
/** 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 {
38
58
@ViewChild ( FocusTrap ) _focusTrap : FocusTrap ;
39
59
40
60
/** 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 ;
42
62
43
63
/** The dialog configuration. */
44
64
dialogConfig : MdDialogConfig ;
45
65
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 > ( ) ;
48
71
49
- constructor ( private _ngZone : NgZone , private _renderer : Renderer ) {
72
+ constructor ( private _ngZone : NgZone ) {
50
73
super ( ) ;
51
74
}
52
75
@@ -87,20 +110,43 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
87
110
// ready in instances where change detection has to run first. To deal with this, we simply
88
111
// wait for the microtask queue to be empty.
89
112
this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( ) => {
90
- this . _elementFocusedBeforeDialogWasOpened = document . activeElement ;
113
+ this . _elementFocusedBeforeDialogWasOpened = document . activeElement as HTMLElement ;
91
114
this . _focusTrap . focusFirstTabbableElement ( ) ;
92
115
} ) ;
93
116
}
94
117
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
+
95
135
ngOnDestroy ( ) {
96
136
// When the dialog is destroyed, return focus to the element that originally had it before
97
137
// the dialog was opened. Wait for the DOM to finish settling before changing the focus so
98
138
// that it doesn't end up back on the <body>. Also note that we need the extra check, because
99
139
// 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
+ } ) ;
105
151
}
106
152
}
0 commit comments