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