Skip to content

Commit d7b670d

Browse files
committed
fix(ripple): fade-out-all should hide all ripples
* When calling `fadeOutAll()`, all currently fading / animating ripples should hide as well. * Adds some extra tests that ensure that ripples properly fade-in and fade-out (See angular#3398)
1 parent 4e4c6a6 commit d7b670d

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

src/lib/core/ripple/ripple-ref.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {RippleConfig, RippleRenderer} from './ripple-renderer';
55
*/
66
export class RippleRef {
77

8+
/** Whether the ripple finished it's fade-in animation. */
9+
fadedIn: boolean = false;
10+
811
constructor(
912
private _renderer: RippleRenderer,
1013
public element: HTMLElement,

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,15 @@ export class RippleRenderer {
101101
// Exposed reference to the ripple that will be returned.
102102
let rippleRef = new RippleRef(this, ripple, config);
103103

104+
// Add the ripple reference to the list of all active ripples.
105+
this._activeRipples.add(rippleRef);
106+
104107
// Wait for the ripple element to be completely faded in.
105108
// Once it's faded in, the ripple can be hidden immediately if the mouse is released.
106109
this.runTimeoutOutsideZone(() => {
107-
if (config.persistent || this._isMousedown) {
108-
this._activeRipples.add(rippleRef);
109-
} else {
110+
rippleRef.fadedIn = true;
111+
112+
if (!config.persistent && !this._isMousedown) {
110113
rippleRef.fadeOut();
111114
}
112115
}, duration);
@@ -116,9 +119,12 @@ export class RippleRenderer {
116119

117120
/** Fades out a ripple reference. */
118121
fadeOutRipple(ripple: RippleRef) {
119-
let rippleEl = ripple.element;
122+
// For ripples that are not active anymore, don't re-un the fade-out animation.
123+
if (!this._activeRipples.delete(ripple)) {
124+
return;
125+
}
120126

121-
this._activeRipples.delete(ripple);
127+
let rippleEl = ripple.element;
122128

123129
rippleEl.style.transitionDuration = `${RIPPLE_FADE_OUT_DURATION}ms`;
124130
rippleEl.style.opacity = '0';
@@ -163,9 +169,9 @@ export class RippleRenderer {
163169
private onMouseup() {
164170
this._isMousedown = false;
165171

166-
// On mouseup, fade-out all ripples that are active and not persistent.
172+
// Fade-out all ripples that are completely faded-in and not persistent.
167173
this._activeRipples.forEach(ripple => {
168-
if (!ripple.config.persistent) {
174+
if (!ripple.config.persistent && ripple.fadedIn) {
169175
ripple.fadeOut();
170176
}
171177
});

src/lib/core/ripple/ripple.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,39 @@ describe('MdRipple', () => {
7777
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
7878
}));
7979

80+
it('should remove ripples after mouseup', fakeAsync(() => {
81+
dispatchMouseEvent(rippleTarget, 'mousedown');
82+
83+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
84+
85+
// Fakes the duration of fading-in and fading-out normal ripples.
86+
// The fade-out duration has been added to ensure that didn't start fading out.
87+
tick(RIPPLE_FADE_IN_DURATION + RIPPLE_FADE_OUT_DURATION);
88+
89+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
90+
91+
dispatchMouseEvent(rippleTarget, 'mouseup');
92+
tick(RIPPLE_FADE_OUT_DURATION);
93+
94+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
95+
}));
96+
97+
it('should not hide ripples while animating.', fakeAsync(() => {
98+
// Calculates the duration for fading-in and fading-out the ripple.
99+
let hideDuration = RIPPLE_FADE_IN_DURATION + RIPPLE_FADE_OUT_DURATION;
100+
101+
dispatchMouseEvent(rippleTarget, 'mousedown');
102+
dispatchMouseEvent(rippleTarget, 'mouseup');
103+
104+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
105+
106+
tick(hideDuration - 10);
107+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
108+
109+
tick(10);
110+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
111+
}));
112+
80113
it('creates ripples when manually triggered', () => {
81114
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
82115

@@ -270,6 +303,23 @@ describe('MdRipple', () => {
270303
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
271304
}));
272305

306+
it('should remove ripples that are not done fading-in', fakeAsync(() => {
307+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
308+
309+
rippleDirective.launch(0, 0);
310+
311+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
312+
313+
tick(RIPPLE_FADE_IN_DURATION / 2);
314+
315+
rippleDirective.fadeOutAll();
316+
317+
tick(RIPPLE_FADE_OUT_DURATION);
318+
319+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
320+
.toBe(0, 'Expected no ripples to be active after calling fadeOutAll.');
321+
}));
322+
273323
});
274324

275325
describe('configuring behavior', () => {

0 commit comments

Comments
 (0)