Skip to content

Commit

Permalink
fix(ripple): Prevent ancestors of nested ripple surfaces from activat…
Browse files Browse the repository at this point in the history
…ing (#2123)
  • Loading branch information
kfranqueiro committed Jan 29, 2018
1 parent 022e692 commit 0a83568
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 2 deletions.
9 changes: 8 additions & 1 deletion packages/mdc-ripple/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ const ACTIVATION_EVENT_TYPES = ['touchstart', 'pointerdown', 'mousedown', 'keydo
// Deactivation events registered on documentElement when a pointer-related down event occurs
const POINTER_DEACTIVATION_EVENT_TYPES = ['touchend', 'pointerup', 'mouseup'];

// Tracks whether an activation has occurred on the current frame, to avoid multiple nested activations
let isActivating = false;

/**
* @extends {MDCFoundation<!MDCRippleAdapter>}
*/
Expand Down Expand Up @@ -281,7 +284,7 @@ class MDCRippleFoundation extends MDCFoundation {
* @private
*/
activate_(e) {
if (this.adapter_.isSurfaceDisabled()) {
if (isActivating || this.adapter_.isSurfaceDisabled()) {
return;
}

Expand All @@ -308,6 +311,7 @@ class MDCRippleFoundation extends MDCFoundation {
this.registerDeactivationHandlers_(e);
}

isActivating = true;
requestAnimationFrame(() => {
// This needs to be wrapped in an rAF call b/c web browsers
// report active states inconsistently when they're called within
Expand All @@ -321,6 +325,9 @@ class MDCRippleFoundation extends MDCFoundation {
// Reset activation state immediately if element was not made active.
this.activationState_ = this.defaultActivationState_();
}

// Reset flag on next frame to avoid any ancestors from also triggering ripple from the same interaction
isActivating = false;
});
}

Expand Down
21 changes: 20 additions & 1 deletion test/unit/mdc-ripple/foundation-activation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import td from 'testdouble';

import {captureHandlers} from '../helpers/foundation';
import {testFoundation} from './helpers';
import {setupTest, testFoundation} from './helpers';
import {cssClasses, strings, numbers} from '../../../packages/mdc-ripple/constants';

suite('MDCRippleFoundation - Activation Logic');
Expand Down Expand Up @@ -318,6 +318,25 @@ testFoundation('removes deactivation classes on activate to ensure ripples can b
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));
});

testFoundation('will not activate multiple ripples on same frame',
({foundation, adapter, mockRaf}) => {
const secondRipple = setupTest();
const firstHandlers = captureHandlers(adapter, 'registerInteractionHandler');
const secondHandlers = captureHandlers(secondRipple.adapter, 'registerInteractionHandler');
foundation.init();
secondRipple.foundation.init();
mockRaf.flush();

// Simulate use case where a child and parent are both ripple surfaces, and the same event propagates to the
// parent after being handled on the child
firstHandlers.mousedown();
secondHandlers.mousedown();
mockRaf.flush();

td.verify(adapter.addClass(cssClasses.FG_ACTIVATION));
td.verify(secondRipple.adapter.addClass(cssClasses.FG_ACTIVATION), {times: 0});
});

testFoundation('displays the foreground ripple on activation when unbounded', ({foundation, adapter, mockRaf}) => {
const handlers = captureHandlers(adapter, 'registerInteractionHandler');
td.when(adapter.computeBoundingRect()).thenReturn({width: 100, height: 100, left: 0, top: 0});
Expand Down

0 comments on commit 0a83568

Please sign in to comment.