@@ -174,21 +174,19 @@ export class RippleRenderer {
174
174
this . _mostRecentTransientRipple = rippleRef ;
175
175
}
176
176
177
- // Wait for the ripple element to be completely faded in.
178
- // Once it's faded in, the ripple can be hidden immediately if the mouse is released.
179
- this . runTimeoutOutsideZone ( ( ) => {
180
- const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
181
-
182
- rippleRef . state = RippleState . VISIBLE ;
183
-
184
- // When the timer runs out while the user has kept their pointer down, we want to
185
- // keep only the persistent ripples and the latest transient ripple. We do this,
186
- // because we don't want stacked transient ripples to appear after their enter
187
- // animation has finished.
188
- if ( ! config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
189
- rippleRef . fadeOut ( ) ;
190
- }
191
- } , duration ) ;
177
+ // Do not register the transition event listener if the fade-in and fade-out duration
178
+ // are set to zero because the event won't be fired at all.
179
+ if ( duration || animationConfig . exitDuration ) {
180
+ this . _ngZone . runOutsideAngular ( ( ) => {
181
+ ripple . addEventListener ( 'transitionend' , ( ) => this . _finishRippleTransition ( rippleRef ) ) ;
182
+ } ) ;
183
+ }
184
+
185
+ // In case there is no fade-in transition duration, we need to manually call the
186
+ // transition end listener because `transitionend` doesn't fire if there is no transition.
187
+ if ( ! duration ) {
188
+ this . _finishRippleTransition ( rippleRef ) ;
189
+ }
192
190
193
191
return rippleRef ;
194
192
}
@@ -214,15 +212,17 @@ export class RippleRenderer {
214
212
const rippleEl = rippleRef . element ;
215
213
const animationConfig = { ...defaultRippleAnimationConfig , ...rippleRef . config . animation } ;
216
214
215
+ // This starts the fade-out transition and will fire the transition end listener that
216
+ // removes the ripple element from the DOM.
217
217
rippleEl . style . transitionDuration = `${ animationConfig . exitDuration } ms` ;
218
218
rippleEl . style . opacity = '0' ;
219
219
rippleRef . state = RippleState . FADING_OUT ;
220
220
221
- // Once the ripple faded out, the ripple can be safely removed from the DOM.
222
- this . runTimeoutOutsideZone ( ( ) => {
223
- rippleRef . state = RippleState . HIDDEN ;
224
- rippleEl . parentNode ! . removeChild ( rippleEl ) ;
225
- } , animationConfig . exitDuration ) ;
221
+ // In case there is no fade- out transition duration, we need to manually call the
222
+ // transition end listener because `transitionend` doesn't fire if there is no transition.
223
+ if ( ! animationConfig . exitDuration ) {
224
+ this . _finishRippleTransition ( rippleRef ) ;
225
+ }
226
226
}
227
227
228
228
/** Fades out all currently active ripples. */
@@ -247,6 +247,39 @@ export class RippleRenderer {
247
247
this . _triggerElement = element ;
248
248
}
249
249
250
+ /** Method that will be called if the fade-in or fade-in transition completed. */
251
+ private _finishRippleTransition ( rippleRef : RippleRef ) {
252
+ if ( rippleRef . state === RippleState . FADING_IN ) {
253
+ this . _startFadeOutTransition ( rippleRef ) ;
254
+ } else if ( rippleRef . state === RippleState . FADING_OUT ) {
255
+ this . _destroyRipple ( rippleRef ) ;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Starts the fade-out transition of the given ripple if it's not persistent and the pointer
261
+ * is not held down anymore.
262
+ */
263
+ private _startFadeOutTransition ( rippleRef : RippleRef ) {
264
+ const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
265
+
266
+ rippleRef . state = RippleState . VISIBLE ;
267
+
268
+ // When the timer runs out while the user has kept their pointer down, we want to
269
+ // keep only the persistent ripples and the latest transient ripple. We do this,
270
+ // because we don't want stacked transient ripples to appear after their enter
271
+ // animation has finished.
272
+ if ( ! rippleRef . config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
273
+ rippleRef . fadeOut ( ) ;
274
+ }
275
+ }
276
+
277
+ /** Destroys the given ripple by removing it from the DOM and updating its state. */
278
+ private _destroyRipple ( rippleRef : RippleRef ) {
279
+ rippleRef . state = RippleState . HIDDEN ;
280
+ rippleRef . element . parentNode ! . removeChild ( rippleRef . element ) ;
281
+ }
282
+
250
283
/** Function being called whenever the trigger is being pressed using mouse. */
251
284
private onMousedown = ( event : MouseEvent ) => {
252
285
// Screen readers will fire fake mouse events for space/enter. Skip launching a
@@ -301,11 +334,6 @@ export class RippleRenderer {
301
334
} ) ;
302
335
}
303
336
304
- /** Runs a timeout outside of the Angular zone to avoid triggering the change detection. */
305
- private runTimeoutOutsideZone ( fn : Function , delay = 0 ) {
306
- this . _ngZone . runOutsideAngular ( ( ) => setTimeout ( fn , delay ) ) ;
307
- }
308
-
309
337
/** Removes previously registered event listeners from the trigger element. */
310
338
_removeTriggerEvents ( ) {
311
339
if ( this . _triggerElement ) {
0 commit comments