@@ -23,6 +23,12 @@ export interface RippleTarget {
23
23
rippleDisabled : boolean ;
24
24
}
25
25
26
+ /** Interfaces the defines ripple element transition event listeners. */
27
+ interface RippleEventListeners {
28
+ onTransitionEnd : EventListener ;
29
+ onTransitionCancel : EventListener ;
30
+ }
31
+
26
32
// TODO: import these values from `@material/ripple` eventually.
27
33
/**
28
34
* Default ripple animation configuration for ripples without an explicit
@@ -65,8 +71,13 @@ export class RippleRenderer implements EventListenerObject {
65
71
/** Whether the pointer is currently down or not. */
66
72
private _isPointerDown = false ;
67
73
68
- /** Set of currently active ripple references. */
69
- private _activeRipples = new Set < RippleRef > ( ) ;
74
+ /**
75
+ * Map of currently active ripple references.
76
+ * The ripple reference is mapped to its element event listeners.
77
+ * The reason why `| null` is used is that event listeners are added only
78
+ * when the condition is truthy (see the `_startFadeOutTransition` method).
79
+ */
80
+ private _activeRipples = new Map < RippleRef , RippleEventListeners | null > ( ) ;
70
81
71
82
/** Latest non-persistent ripple that was triggered. */
72
83
private _mostRecentTransientRipple : RippleRef | null ;
@@ -164,25 +175,30 @@ export class RippleRenderer implements EventListenerObject {
164
175
165
176
rippleRef . state = RippleState . FADING_IN ;
166
177
167
- // Add the ripple reference to the list of all active ripples.
168
- this . _activeRipples . add ( rippleRef ) ;
169
-
170
178
if ( ! config . persistent ) {
171
179
this . _mostRecentTransientRipple = rippleRef ;
172
180
}
173
181
182
+ let eventListeners : RippleEventListeners | null = null ;
183
+
174
184
// Do not register the `transition` event listener if fade-in and fade-out duration
175
185
// are set to zero. The events won't fire anyway and we can save resources here.
176
186
if ( ! animationForciblyDisabledThroughCss && ( enterDuration || animationConfig . exitDuration ) ) {
177
187
this . _ngZone . runOutsideAngular ( ( ) => {
178
- ripple . addEventListener ( 'transitionend' , ( ) => this . _finishRippleTransition ( rippleRef ) ) ;
188
+ const onTransitionEnd = ( ) => this . _finishRippleTransition ( rippleRef ) ;
189
+ const onTransitionCancel = ( ) => this . _destroyRipple ( rippleRef ) ;
190
+ ripple . addEventListener ( 'transitionend' , onTransitionEnd ) ;
179
191
// If the transition is cancelled (e.g. due to DOM removal), we destroy the ripple
180
192
// directly as otherwise we would keep it part of the ripple container forever.
181
193
// https://www.w3.org/TR/css-transitions-1/#:~:text=no%20longer%20in%20the%20document.
182
- ripple . addEventListener ( 'transitioncancel' , ( ) => this . _destroyRipple ( rippleRef ) ) ;
194
+ ripple . addEventListener ( 'transitioncancel' , onTransitionCancel ) ;
195
+ eventListeners = { onTransitionEnd, onTransitionCancel} ;
183
196
} ) ;
184
197
}
185
198
199
+ // Add the ripple reference to the list of all active ripples.
200
+ this . _activeRipples . set ( rippleRef , eventListeners ) ;
201
+
186
202
// In case there is no fade-in transition duration, we need to manually call the transition
187
203
// end listener because `transitionend` doesn't fire if there is no transition.
188
204
if ( animationForciblyDisabledThroughCss || ! enterDuration ) {
@@ -217,12 +233,12 @@ export class RippleRenderer implements EventListenerObject {
217
233
218
234
/** Fades out all currently active ripples. */
219
235
fadeOutAll ( ) {
220
- this . _activeRipples . forEach ( ripple => ripple . fadeOut ( ) ) ;
236
+ this . _getActiveRipples ( ) . forEach ( ripple => ripple . fadeOut ( ) ) ;
221
237
}
222
238
223
239
/** Fades out all currently active non-persistent ripples. */
224
240
fadeOutAllNonPersistent ( ) {
225
- this . _activeRipples . forEach ( ripple => {
241
+ this . _getActiveRipples ( ) . forEach ( ripple => {
226
242
if ( ! ripple . config . persistent ) {
227
243
ripple . fadeOut ( ) ;
228
244
}
@@ -296,6 +312,7 @@ export class RippleRenderer implements EventListenerObject {
296
312
297
313
/** Destroys the given ripple by removing it from the DOM and updating its state. */
298
314
private _destroyRipple ( rippleRef : RippleRef ) {
315
+ const eventListeners = this . _activeRipples . get ( rippleRef ) ?? null ;
299
316
this . _activeRipples . delete ( rippleRef ) ;
300
317
301
318
// Clear out the cached bounding rect if we have no more ripples.
@@ -310,6 +327,10 @@ export class RippleRenderer implements EventListenerObject {
310
327
}
311
328
312
329
rippleRef . state = RippleState . HIDDEN ;
330
+ if ( eventListeners !== null ) {
331
+ rippleRef . element . removeEventListener ( 'transitionend' , eventListeners . onTransitionEnd ) ;
332
+ rippleRef . element . removeEventListener ( 'transitioncancel' , eventListeners . onTransitionCancel ) ;
333
+ }
313
334
rippleRef . element . remove ( ) ;
314
335
}
315
336
@@ -356,7 +377,7 @@ export class RippleRenderer implements EventListenerObject {
356
377
this . _isPointerDown = false ;
357
378
358
379
// Fade-out all ripples that are visible and not persistent.
359
- this . _activeRipples . forEach ( ripple => {
380
+ this . _getActiveRipples ( ) . forEach ( ripple => {
360
381
// By default, only ripples that are completely visible will fade out on pointer release.
361
382
// If the `terminateOnPointerUp` option is set, ripples that still fade in will also fade out.
362
383
const isVisible =
@@ -378,6 +399,10 @@ export class RippleRenderer implements EventListenerObject {
378
399
} ) ;
379
400
}
380
401
402
+ private _getActiveRipples ( ) : RippleRef [ ] {
403
+ return Array . from ( this . _activeRipples . keys ( ) ) ;
404
+ }
405
+
381
406
/** Removes previously registered event listeners from the trigger element. */
382
407
_removeTriggerEvents ( ) {
383
408
if ( this . _triggerElement ) {
0 commit comments