Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
feat(ripple): Add programmatic ripple activation/deactivation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sérgio Gomes authored and sgomes committed Feb 10, 2017
1 parent c25d387 commit acccc9e
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 6 deletions.
17 changes: 17 additions & 0 deletions packages/mdc-ripple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,23 @@ ripple.
MDCRipple.attachTo(document.querySelector('.surface'));
```

### Ripple API

The component allows for programmatic activation / deactivation of the ripple, for interdependent interaction between
components. This is used for making form field labels trigger the ripples in their corresponding input elements, for
example.

#### MDCRipple.activate()

Triggers an activation of the ripple (the first stage, which happens when the ripple surface is engaged via interaction,
such as a `mousedown` or a `pointerdown` event). It expands from the center.

#### MDCRipple.deactivate()

Triggers a deactivation of the ripple (the second stage, which happens when the ripple surface is engaged via
interaction, such as a `mouseup` or a `pointerup` event). It expands from the center.


### Unbounded Ripples

If you'd like to use _unbounded_ ripples, such as those used for checkboxes and radio buttons, you
Expand Down
25 changes: 20 additions & 5 deletions packages/mdc-ripple/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ export default class MDCRippleFoundation extends MDCFoundation {
}

activationState.isActivated = true;
activationState.isProgrammatic = e === null;
activationState.activationEvent = e;
activationState.wasActivatedByPointer = (
activationState.wasActivatedByPointer = activationState.isProgrammatic ? false : (
e.type === 'mousedown' || e.type === 'touchstart' || e.type === 'pointerdown'
);

Expand All @@ -157,7 +158,7 @@ export default class MDCRippleFoundation extends MDCFoundation {
// event handling code:
// - https://bugs.chromium.org/p/chromium/issues/detail?id=635971
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1293741
activationState.wasElementMadeActive = e.type === 'keydown' ? this.adapter_.isSurfaceActive() : true;
activationState.wasElementMadeActive = (e && e.type === 'keydown') ? this.adapter_.isSurfaceActive() : true;
if (activationState.wasElementMadeActive) {
this.animateActivation_();
} else {
Expand All @@ -167,6 +168,10 @@ export default class MDCRippleFoundation extends MDCFoundation {
});
}

activate() {
this.activate_(null);
}

animateActivation_() {
const {
BG_ACTIVE, BG_BOUNDED_ACTIVE_FILL,
Expand Down Expand Up @@ -204,6 +209,12 @@ export default class MDCRippleFoundation extends MDCFoundation {
if (!activationState.isActivated) {
return;
}
// Programmatic deactivation.
if (activationState.isProgrammatic) {
requestAnimationFrame(() => this.animateDeactivation_(null, Object.assign({}, activationState)));
this.activationState_ = this.defaultActivationState_();
return;
}
const actualActivationType = DEACTIVATION_ACTIVATION_PAIRS[e.type];
const expectedActivationType = activationState.activationEvent.type;
// NOTE: Pointer events are tricky - https://patrickhlauke.github.io/touch/tests/results/
Expand All @@ -216,7 +227,7 @@ export default class MDCRippleFoundation extends MDCFoundation {
needsActualDeactivation = e.type === 'mouseup';
}

const state = Object.assign({}, this.activationState_);
const state = Object.assign({}, activationState);
if (needsDeactivationUX) {
requestAnimationFrame(() => this.animateDeactivation_(e, state));
}
Expand All @@ -225,11 +236,15 @@ export default class MDCRippleFoundation extends MDCFoundation {
}
}

animateDeactivation_(e, {wasActivatedByPointer, wasElementMadeActive, activationStartTime}) {
deactivate() {
this.deactivate_(null);
}

animateDeactivation_(e, {wasActivatedByPointer, wasElementMadeActive, activationStartTime, isProgrammatic}) {
const {BG_ACTIVE} = MDCRippleFoundation.cssClasses;
if (wasActivatedByPointer || wasElementMadeActive) {
this.adapter_.removeClass(BG_ACTIVE);
const isPointerEvent = (
const isPointerEvent = isProgrammatic ? false : (
e.type === 'touchend' || e.type === 'pointerup' || e.type === 'mouseup'
);
if (this.adapter_.isUnbounded()) {
Expand Down
8 changes: 8 additions & 0 deletions packages/mdc-ripple/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export class MDCRipple extends MDCComponent {
}
}

activate() {
this.foundation_.activate();
}

deactivate() {
this.foundation_.deactivate();
}

getDefaultFoundation() {
return new MDCRippleFoundation(MDCRipple.createAdapter(this));
}
Expand Down
14 changes: 13 additions & 1 deletion test/unit/mdc-ripple/foundation-activation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ testFoundation(`adds ${cssClasses.BG_ACTIVE} on keydown when surface is made act
t.end();
});

testFoundation(`adds ${cssClasses.BG_ACTIVE} on public activate() call`, (t) => {
const {foundation, adapter, mockRaf} = t.data;
td.when(adapter.isSurfaceActive()).thenReturn(true);
foundation.init();
mockRaf.flush();

foundation.activate();
mockRaf.flush();

t.doesNotThrow(() => td.verify(adapter.addClass(cssClasses.BG_ACTIVE)));
t.end();
});

testFoundation('does not redundantly add classes on touchstart followed by mousedown', (t) => {
const {foundation, adapter, mockRaf} = t.data;
const handlers = captureHandlers(adapter);
Expand Down Expand Up @@ -130,4 +143,3 @@ testFoundation('displays the foreground ripple on activation when unbounded', (t
t.doesNotThrow(() => td.verify(adapter.addClass(cssClasses.FG_UNBOUNDED_ACTIVATION)));
t.end();
});

16 changes: 16 additions & 0 deletions test/unit/mdc-ripple/foundation-deactivation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ testFoundation('runs deactivation UX on mouseup after mousedown', (t) => {
t.end();
});

testFoundation('runs deactivation UX on public deactivate() call', (t) => {
const {foundation, adapter, mockRaf} = t.data;
foundation.init();
mockRaf.flush();

foundation.activate();
mockRaf.flush();
foundation.deactivate();
mockRaf.flush();

t.doesNotThrow(() => td.verify(adapter.removeClass(cssClasses.BG_ACTIVE)));
t.doesNotThrow(() => td.verify(adapter.addClass(cssClasses.BG_BOUNDED_ACTIVE_FILL)));
t.doesNotThrow(() => td.verify(adapter.addClass(cssClasses.FG_BOUNDED_ACTIVE_FILL)));
t.end();
});

testFoundation('only re-activates when there are no additional pointer events to be processed', (t) => {
const {foundation, adapter, mockRaf} = t.data;
const handlers = captureHandlers(adapter);
Expand Down
16 changes: 16 additions & 0 deletions test/unit/mdc-ripple/mdc-ripple.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ test(`set unbounded() removes ${cssClasses.UNBOUNDED} when falsy`, (t) => {
t.end();
});

test('activate() delegates to the foundation', (t) => {
const {component} = setupTest();
component.foundation_.activate = td.function();
component.activate();
t.doesNotThrow(() => td.verify(component.foundation_.activate()));
t.end();
});

test('deactivate() delegates to the foundation', (t) => {
const {component} = setupTest();
component.foundation_.deactivate = td.function();
component.deactivate();
t.doesNotThrow(() => td.verify(component.foundation_.deactivate()));
t.end();
});

test('adapter#browserSupportsCssVars delegates to util', (t) => {
const {component} = setupTest();
t.equal(
Expand Down

0 comments on commit acccc9e

Please sign in to comment.