Skip to content

Commit 5cc50d2

Browse files
devversionkara
authored andcommitted
fix(ripple): fade-out-all should hide all ripples (#3400)
1 parent 0ac6e18 commit 5cc50d2

File tree

4 files changed

+104
-11
lines changed

4 files changed

+104
-11
lines changed

src/lib/core/ripple/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {VIEWPORT_RULER_PROVIDER} from '../overlay/position/viewport-ruler';
55
import {SCROLL_DISPATCHER_PROVIDER} from '../overlay/scroll/scroll-dispatcher';
66

77
export {MdRipple} from './ripple';
8-
export {RippleRef} from './ripple-ref';
8+
export {RippleRef, RippleState} from './ripple-ref';
99
export {RippleConfig} from './ripple-renderer';
1010

1111
@NgModule({

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import {RippleConfig, RippleRenderer} from './ripple-renderer';
22

3+
/** Possible states for a ripple element. */
4+
export enum RippleState {
5+
FADING_IN, VISIBLE, FADING_OUT, HIDDEN
6+
}
7+
38
/**
49
* Reference to a previously launched ripple element.
510
*/
611
export class RippleRef {
712

13+
/** Current state of the ripple reference. */
14+
state: RippleState = RippleState.HIDDEN;
15+
816
constructor(
917
private _renderer: RippleRenderer,
1018
public element: HTMLElement,

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {ElementRef, NgZone} from '@angular/core';
22
import {ViewportRuler} from '../overlay/position/viewport-ruler';
3-
import {RippleRef} from './ripple-ref';
3+
import {RippleRef, RippleState} from './ripple-ref';
44

55
/** Fade-in duration for the ripples. Can be modified with the speedFactor option. */
66
export const RIPPLE_FADE_IN_DURATION = 450;
@@ -101,12 +101,17 @@ export class RippleRenderer {
101101
// Exposed reference to the ripple that will be returned.
102102
let rippleRef = new RippleRef(this, ripple, config);
103103

104+
rippleRef.state = RippleState.FADING_IN;
105+
106+
// Add the ripple reference to the list of all active ripples.
107+
this._activeRipples.add(rippleRef);
108+
104109
// Wait for the ripple element to be completely faded in.
105110
// Once it's faded in, the ripple can be hidden immediately if the mouse is released.
106111
this.runTimeoutOutsideZone(() => {
107-
if (config.persistent || this._isMousedown) {
108-
this._activeRipples.add(rippleRef);
109-
} else {
112+
rippleRef.state = RippleState.VISIBLE;
113+
114+
if (!config.persistent && !this._isMousedown) {
110115
rippleRef.fadeOut();
111116
}
112117
}, duration);
@@ -115,16 +120,22 @@ export class RippleRenderer {
115120
}
116121

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

121-
this._activeRipples.delete(ripple);
129+
let rippleEl = rippleRef.element;
122130

123131
rippleEl.style.transitionDuration = `${RIPPLE_FADE_OUT_DURATION}ms`;
124132
rippleEl.style.opacity = '0';
125133

134+
rippleRef.state = RippleState.FADING_OUT;
135+
126136
// Once the ripple faded out, the ripple can be safely removed from the DOM.
127137
this.runTimeoutOutsideZone(() => {
138+
rippleRef.state = RippleState.HIDDEN;
128139
rippleEl.parentNode.removeChild(rippleEl);
129140
}, RIPPLE_FADE_OUT_DURATION);
130141
}
@@ -163,9 +174,9 @@ export class RippleRenderer {
163174
private onMouseup() {
164175
this._isMousedown = false;
165176

166-
// On mouseup, fade-out all ripples that are active and not persistent.
177+
// Fade-out all ripples that are completely visible and not persistent.
167178
this._activeRipples.forEach(ripple => {
168-
if (!ripple.config.persistent) {
179+
if (!ripple.config.persistent && ripple.state === RippleState.VISIBLE) {
169180
ripple.fadeOut();
170181
}
171182
});

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

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {TestBed, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
3-
import {MdRipple, MdRippleModule} from './index';
3+
import {MdRipple, MdRippleModule, RippleState} from './index';
44
import {ViewportRuler} from '../overlay/position/viewport-ruler';
55
import {RIPPLE_FADE_OUT_DURATION, RIPPLE_FADE_IN_DURATION} from './ripple-renderer';
66
import {dispatchMouseEvent} from '../testing/dispatch-events';
@@ -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,47 @@ 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+
323+
it('should properly set ripple states', fakeAsync(() => {
324+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
325+
326+
let rippleRef = rippleDirective.launch(0, 0, { persistent: true });
327+
328+
expect(rippleRef.state).toBe(RippleState.FADING_IN);
329+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
330+
331+
tick(RIPPLE_FADE_IN_DURATION);
332+
333+
expect(rippleRef.state).toBe(RippleState.VISIBLE);
334+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
335+
336+
rippleRef.fadeOut();
337+
338+
expect(rippleRef.state).toBe(RippleState.FADING_OUT);
339+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
340+
341+
tick(RIPPLE_FADE_OUT_DURATION);
342+
343+
expect(rippleRef.state).toBe(RippleState.HIDDEN);
344+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
345+
}));
346+
273347
});
274348

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

0 commit comments

Comments
 (0)