@@ -169,21 +169,19 @@ export class RippleRenderer {
169169 this . _mostRecentTransientRipple = rippleRef ;
170170 }
171171
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+ }
187185
188186 return rippleRef ;
189187 }
@@ -209,15 +207,17 @@ export class RippleRenderer {
209207 const rippleEl = rippleRef . element ;
210208 const animationConfig = { ...defaultRippleAnimationConfig , ...rippleRef . config . animation } ;
211209
210+ // This starts the fade-out transition and will fire the transition end listener that
211+ // removes the ripple element from the DOM.
212212 rippleEl . style . transitionDuration = `${ animationConfig . exitDuration } ms` ;
213213 rippleEl . style . opacity = '0' ;
214214 rippleRef . state = RippleState . FADING_OUT ;
215215
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+ }
221221 }
222222
223223 /** Fades out all currently active ripples. */
@@ -242,6 +242,39 @@ export class RippleRenderer {
242242 this . _triggerElement = element ;
243243 }
244244
245+ /** Method that will be called if the fade-in or fade-in transition completed. */
246+ private _finishRippleTransition ( rippleRef : RippleRef ) {
247+ if ( rippleRef . state === RippleState . FADING_IN ) {
248+ this . _startFadeOutTransition ( rippleRef ) ;
249+ } else if ( rippleRef . state === RippleState . FADING_OUT ) {
250+ this . _destroyRipple ( rippleRef ) ;
251+ }
252+ }
253+
254+ /**
255+ * Starts the fade-out transition of the given ripple if it's not persistent and the pointer
256+ * is not held down anymore.
257+ */
258+ private _startFadeOutTransition ( rippleRef : RippleRef ) {
259+ const isMostRecentTransientRipple = rippleRef === this . _mostRecentTransientRipple ;
260+
261+ rippleRef . state = RippleState . VISIBLE ;
262+
263+ // When the timer runs out while the user has kept their pointer down, we want to
264+ // keep only the persistent ripples and the latest transient ripple. We do this,
265+ // because we don't want stacked transient ripples to appear after their enter
266+ // animation has finished.
267+ if ( ! rippleRef . config . persistent && ( ! isMostRecentTransientRipple || ! this . _isPointerDown ) ) {
268+ rippleRef . fadeOut ( ) ;
269+ }
270+ }
271+
272+ /** Destroys the given ripple by removing it from the DOM and updating its state. */
273+ private _destroyRipple ( rippleRef : RippleRef ) {
274+ rippleRef . state = RippleState . HIDDEN ;
275+ rippleRef . element . parentNode ! . removeChild ( rippleRef . element ) ;
276+ }
277+
245278 /** Function being called whenever the trigger is being pressed using mouse. */
246279 private onMousedown = ( event : MouseEvent ) => {
247280 // Screen readers will fire fake mouse events for space/enter. Skip launching a
@@ -296,11 +329,6 @@ export class RippleRenderer {
296329 } ) ;
297330 }
298331
299- /** Runs a timeout outside of the Angular zone to avoid triggering the change detection. */
300- private runTimeoutOutsideZone ( fn : Function , delay = 0 ) {
301- this . _ngZone . runOutsideAngular ( ( ) => setTimeout ( fn , delay ) ) ;
302- }
303-
304332 /** Removes previously registered event listeners from the trigger element. */
305333 _removeTriggerEvents ( ) {
306334 if ( this . _triggerElement ) {
0 commit comments