@@ -28,15 +28,20 @@ import {
28
28
} from '@angular/core' ;
29
29
import { MatSnackBarConfig , _SnackBarContainer } from '@angular/material/snack-bar' ;
30
30
import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
31
- import { MDCSnackbarAdapter , MDCSnackbarFoundation , cssClasses } from '@material/snackbar' ;
32
31
import { Platform } from '@angular/cdk/platform' ;
33
32
import { Observable , Subject } from 'rxjs' ;
34
33
35
- /**
36
- * The MDC label class that should wrap the label content of the snack bar.
37
- * @docs -private
38
- */
39
- const MDC_SNACKBAR_LABEL_CLASS = 'mdc-snackbar__label' ;
34
+ /** The MDC label class that should wrap the label content of the snack bar. */
35
+ const LABEL_CLASS = 'mdc-snackbar__label' ;
36
+
37
+ /** Class added after the snack bar is done opening. */
38
+ const OPEN_CLASS = 'mdc-snackbar--open' ;
39
+
40
+ /** Class added while the snack bar is opening. */
41
+ const OPENING_CLASS = 'mdc-snackbar--opening' ;
42
+
43
+ /** Class added while the snack bar is closing. */
44
+ const CLOSING_CLASS = 'mdc-snackbar--closing' ;
40
45
41
46
/**
42
47
* Internal component that wraps user-provided snack bar content.
@@ -72,6 +77,15 @@ export class MatSnackBarContainer
72
77
/** The timeout for announcing the snack bar's content. */
73
78
private _announceTimeoutId : number ;
74
79
80
+ /** ID of the timeout for the opening/closing animation. */
81
+ private _animationTimeoutId : number ;
82
+
83
+ /** ID of the animation frame that is used to delay the opening sequence. */
84
+ private _animationFrameId : number ;
85
+
86
+ /** Whether the component is in the process of being opened. */
87
+ private _isOpening = false ;
88
+
75
89
/** Subject for notifying that the snack bar has announced to screen readers. */
76
90
readonly _onAnnounce : Subject < void > = new Subject ( ) ;
77
91
@@ -93,24 +107,9 @@ export class MatSnackBarContainer
93
107
*/
94
108
_role ?: 'status' | 'alert' ;
95
109
96
- private _mdcAdapter : MDCSnackbarAdapter = {
97
- addClass : ( className : string ) => this . _setClass ( className , true ) ,
98
- removeClass : ( className : string ) => this . _setClass ( className , false ) ,
99
- announce : ( ) => { } ,
100
- notifyClosed : ( ) => this . _finishExit ( ) ,
101
- notifyClosing : ( ) => { } ,
102
- notifyOpened : ( ) => this . _onEnter . next ( ) ,
103
- notifyOpening : ( ) => { } ,
104
- } ;
105
-
106
- _mdcFoundation = new MDCSnackbarFoundation ( this . _mdcAdapter ) ;
107
-
108
110
/** The portal outlet inside of this container into which the snack bar content will be loaded. */
109
111
@ViewChild ( CdkPortalOutlet , { static : true } ) _portalOutlet : CdkPortalOutlet ;
110
112
111
- /** Element that acts as the MDC surface container which should contain the label and actions. */
112
- @ViewChild ( 'surface' , { static : true } ) _surface : ElementRef ;
113
-
114
113
/**
115
114
* Element that will have the `mdc-snackbar__label` class applied if the attached component
116
115
* or template does not have it. This ensures that the appropriate structure, typography, and
@@ -147,50 +146,66 @@ export class MatSnackBarContainer
147
146
this . _role = 'alert' ;
148
147
}
149
148
}
150
-
151
- // `MatSnackBar` will use the config's timeout to determine when the snack bar should be closed.
152
- // Set this to `-1` to mark it as indefinitely open so that MDC does not close itself.
153
- this . _mdcFoundation . setTimeoutMs ( - 1 ) ;
154
- }
155
-
156
- /** Makes sure the exit callbacks have been invoked when the element is destroyed. */
157
- ngOnDestroy ( ) {
158
- this . _mdcFoundation . close ( ) ;
159
149
}
160
150
161
151
enter ( ) {
162
- // MDC uses some browser APIs that will throw during server-side rendering.
163
- if ( this . _platform . isBrowser ) {
164
- this . _ngZone . run ( ( ) => {
165
- this . _mdcFoundation . open ( ) ;
166
- this . _screenReaderAnnounce ( ) ;
167
- } ) ;
152
+ if ( typeof requestAnimationFrame !== 'function' ) {
153
+ return ;
168
154
}
155
+
156
+ this . _isOpening = true ;
157
+ clearTimeout ( this . _animationTimeoutId ) ;
158
+ cancelAnimationFrame ( this . _animationFrameId ) ;
159
+ this . _setClass ( CLOSING_CLASS , false ) ;
160
+ this . _setClass ( OPENING_CLASS , true ) ;
161
+ this . _screenReaderAnnounce ( ) ;
162
+
163
+ // Wait a frame once display is no longer "none", to establish basis for animation
164
+ this . _animationFrameId = requestAnimationFrame ( ( ) => {
165
+ this . _setClass ( OPEN_CLASS , true ) ;
166
+
167
+ this . _animationTimeoutId = setTimeout ( ( ) => {
168
+ this . _setClass ( OPENING_CLASS , false ) ;
169
+ this . _ngZone . run ( ( ) => this . _onEnter . next ( ) ) ;
170
+ this . _isOpening = false ;
171
+ } , 150 ) ;
172
+ } ) ;
169
173
}
170
174
171
175
exit ( ) : Observable < void > {
172
- const classList = this . _elementRef . nativeElement . classList ;
176
+ // If the snack bar hasn't been announced by the time it exits it wouldn't have been open
177
+ // long enough to visually read it either, so clear the timeout for announcing.
178
+ clearTimeout ( this . _announceTimeoutId ) ;
179
+ clearTimeout ( this . _animationTimeoutId ) ;
173
180
174
- // MDC won't complete the closing sequence if it starts while opening hasn't finished.
175
- // If that's the case, destroy immediately to ensure that our stream emits as expected.
176
- if ( classList . contains ( cssClasses . OPENING ) || ! classList . contains ( cssClasses . OPEN ) ) {
181
+ if ( typeof cancelAnimationFrame === 'function' ) {
182
+ cancelAnimationFrame ( this . _animationFrameId ) ;
183
+ }
184
+
185
+ // If the opening sequence is still in progress, close immediately.
186
+ if ( this . _isOpening ) {
177
187
this . _finishExit ( ) ;
178
188
} else {
179
189
// It's common for snack bars to be opened by random outside calls like HTTP requests or
180
190
// errors. Run inside the NgZone to ensure that it functions correctly.
181
191
this . _ngZone . run ( ( ) => {
182
192
this . _exiting = true ;
183
- this . _mdcFoundation . close ( ) ;
193
+ this . _setClass ( CLOSING_CLASS , true ) ;
194
+ this . _setClass ( OPEN_CLASS , false ) ;
195
+ this . _setClass ( OPENING_CLASS , false ) ;
196
+ this . _animationTimeoutId = setTimeout ( ( ) => this . _finishExit ( ) , 75 ) ;
184
197
} ) ;
185
198
}
186
199
187
- // If the snack bar hasn't been announced by the time it exits it wouldn't have been open
188
- // long enough to visually read it either, so clear the timeout for announcing.
189
- clearTimeout ( this . _announceTimeoutId ) ;
190
-
191
200
return this . _onExit ;
192
201
}
193
202
203
+ ngOnDestroy ( ) {
204
+ if ( ! this . _exiting ) {
205
+ this . exit ( ) ;
206
+ }
207
+ }
208
+
194
209
/** Attach a component portal as content to this snack bar container. */
195
210
attachComponentPortal < T > ( portal : ComponentPortal < T > ) : ComponentRef < T > {
196
211
this . _assertNotAttached ( ) ;
@@ -235,12 +250,9 @@ export class MatSnackBarContainer
235
250
236
251
/** Finishes the exit sequence of the container. */
237
252
private _finishExit ( ) {
253
+ clearTimeout ( this . _animationTimeoutId ) ;
238
254
this . _onExit . next ( ) ;
239
255
this . _onExit . complete ( ) ;
240
-
241
- if ( this . _platform . isBrowser ) {
242
- this . _mdcFoundation . destroy ( ) ;
243
- }
244
256
}
245
257
246
258
/**
@@ -284,10 +296,10 @@ export class MatSnackBarContainer
284
296
// component's label container, which will apply MDC's label styles to the attached view.
285
297
const label = this . _label . nativeElement ;
286
298
287
- if ( ! label . querySelector ( `.${ MDC_SNACKBAR_LABEL_CLASS } ` ) ) {
288
- label . classList . add ( MDC_SNACKBAR_LABEL_CLASS ) ;
299
+ if ( ! label . querySelector ( `.${ LABEL_CLASS } ` ) ) {
300
+ label . classList . add ( LABEL_CLASS ) ;
289
301
} else {
290
- label . classList . remove ( MDC_SNACKBAR_LABEL_CLASS ) ;
302
+ label . classList . remove ( LABEL_CLASS ) ;
291
303
}
292
304
}
293
305
}
0 commit comments