Skip to content

Commit

Permalink
feat(chips): Make chip exit on trailing icon click optional (#2893)
Browse files Browse the repository at this point in the history
Add a `shouldRemoveOnTrailingIconClick` flag in each chip to indicate whether trailing icon interaction should trigger exit animation (defaults to true).
  • Loading branch information
bonniezhou committed Jun 15, 2018
1 parent c9f5560 commit 9178d46
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 14 deletions.
48 changes: 42 additions & 6 deletions demos/chips.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ <h2>Input Chips</h2>
<i class="material-icons mdc-chip__icon mdc-chip__icon--trailing" tabindex="0" role="button">cancel</i>
</div>
</div>

<div class="mdc-form-field">
<div class="mdc-checkbox">
<input type="checkbox" class="mdc-checkbox__native-control" id="toggle-trailing-icon" aria-labelledby="toggle-disabled-label">
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" stroke="white" d="M1.73,12.91 8.1,19.28 22.79,4.59">
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
</div>
<label for="toggle-trailing-icon" id="toggle-trailing-icon-label">Require confirmation before removing chip</label>
</div>
</section>

<section class="example">
Expand Down Expand Up @@ -243,12 +256,34 @@ <h2>Custom theme</h2>
var input = document.getElementById('input-chip-set-input');
var inputButton = document.getElementById('input-chip-set-button');
var deleteButton = document.getElementById('input-chip-set-delete-button');
var trailingIconToggle = document.getElementById('toggle-trailing-icon');

[].forEach.call(chipSets, function(chipSet) {
mdc.chips.MDCChipSet.attachTo(chipSet);
});
var inputChipSetComponent = mdc.chips.MDCChipSet.attachTo(inputChipSetEl);

inputButton.addEventListener('click', addInputChip);
input.addEventListener('keydown', addInputChip);
deleteButton.addEventListener('click', deleteLastChip);
inputChipSetEl.addEventListener('MDCChip:removal', removeChip);
trailingIconToggle.addEventListener('change', resetInputChipSet);

function resetInputChipSet() {
inputChipSetComponent.destroy();
if (this.checked) {
inputChipSetComponent = new mdc.chips.MDCChipSet(inputChipSetEl, undefined, function(chipEl) {
const chip = new mdc.chips.MDCChip(chipEl);
chip.shouldRemoveOnTrailingIconClick = false;
return chip;
});
inputChipSetEl.addEventListener('MDCChip:trailingIconInteraction', confirmChipRemoval);
} else {
inputChipSetComponent = mdc.chips.MDCChipSet.attachTo(inputChipSetEl);
inputChipSetEl.removeEventListener('MDCChip:trailingIconInteraction', confirmChipRemoval);
}
}

function createChipEl(text, leadingIcon, trailingIcon) {
const chipTextEl = document.createElement('div');
chipTextEl.classList.add('mdc-chip__text');
Expand All @@ -267,7 +302,7 @@ <h2>Custom theme</h2>
}

function addInputChip(evt) {
if ((evt.type === 'click' || evt.key === 'Enter' || evt.keyCode === 13) &&
if ((evt.type === 'click' || evt.key === 'Enter' || evt.keyCode === 13) &&
input.value !== '') {
var leadingIcon = document.querySelector('.input-leading-icon').cloneNode(true);
var trailingIcon = document.querySelector('.input-trailing-icon').cloneNode(true);
Expand All @@ -280,6 +315,12 @@ <h2>Custom theme</h2>
}
};

function confirmChipRemoval(evt) {
if (confirm('Are you sure you want to remove this chip?')) {
evt.detail.chip.beginExit();
}
}

function removeChip(evt) {
const root = evt.detail.root;
root && inputChipSetEl.removeChild(root);
Expand All @@ -290,11 +331,6 @@ <h2>Custom theme</h2>
const lastChip = inputChipSetComponent.chips[lastChipIndex];
lastChip.beginExit();
};

inputButton.addEventListener('click', addInputChip);
input.addEventListener('keydown', addInputChip);
deleteButton.addEventListener('click', deleteLastChip);
inputChipSetEl.addEventListener('MDCChip:removal', removeChip);
});
</script>
</body>
Expand Down
2 changes: 2 additions & 0 deletions demos/chips.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
@import "./common";
@import "../packages/mdc-chips/mdc-chips";
@import "../packages/mdc-button/mdc-button";
@import "../packages/mdc-checkbox/mdc-checkbox";
@import "../packages/mdc-form-field/mdc-form-field";

#input-chip-icon-clones {
display: none;
Expand Down
10 changes: 8 additions & 2 deletions packages/mdc-chips/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,17 @@ To use the `MDCChip` and `MDCChipSet` classes, [import](../../docs/importing-js.

Method Signature | Description
--- | ---
`get foundation() => MDCChipFoundation` | Returns the foundation
`isSelected() => boolean` | Proxies to the foundation's `isSelected` method
`beginExit() => void` | Begins the exit animation which leads to removal of the chip
`beginExit() => void` | Proxies to the foundation's `beginExit` method

Property | Value Type | Description
--- | --- | ---
`foundation` | MDCChipFoundation | The foundation
`shouldRemoveOnTrailingIconClick` | Boolean | Proxies to the foundation's `getShouldRemoveOnTrailingIconClick`/`setShouldRemoveOnTrailingIconClick` methods
`ripple` | `MDCRipple` | The `MDCRipple` instance for the root element that `MDCChip` initializes

>_NOTE_: If `shouldRemoveOnTrailingIconClick` is set to false, you must manually call `beginExit()` on the chip to remove it.
#### `MDCChipSet`

Method Signature | Description
Expand Down Expand Up @@ -276,6 +279,9 @@ Method Signature | Description
--- | ---
`isSelected() => boolean` | Returns true if the chip is selected
`setSelected(selected: boolean) => void` | Sets the chip's selected state
`getShouldRemoveOnTrailingIconClick() => boolean` | Returns whether a trailing icon click should trigger exit/removal of the chip
`setShouldRemoveOnTrailingIconClick(shouldRemove: boolean) => void` | Sets whether a trailing icon click should trigger exit/removal of the chip
`beginExit() => void` | Begins the exit animation which leads to removal of the chip

#### `MDCChipSetFoundation`

Expand Down
30 changes: 29 additions & 1 deletion packages/mdc-chips/chip/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ class MDCChipFoundation extends MDCFoundation {
constructor(adapter) {
super(Object.assign(MDCChipFoundation.defaultAdapter, adapter));

/**
* Whether a trailing icon click should immediately trigger exit/removal of the chip.
* @private {boolean}
* */
this.shouldRemoveOnTrailingIconClick_ = true;
/** @private {function(!Event): undefined} */
this.interactionHandler_ = (evt) => this.handleInteraction_(evt);
/** @private {function(!Event): undefined} */
Expand Down Expand Up @@ -112,6 +117,27 @@ class MDCChipFoundation extends MDCFoundation {
}
}

/**
* @return {boolean}
*/
getShouldRemoveOnTrailingIconClick() {
return this.shouldRemoveOnTrailingIconClick_;
}

/**
* @param {boolean} shouldRemove
*/
setShouldRemoveOnTrailingIconClick(shouldRemove) {
this.shouldRemoveOnTrailingIconClick_ = shouldRemove;
}

/**
* Begins the exit animation which leads to removal of the chip.
*/
beginExit() {
this.adapter_.addClass(cssClasses.CHIP_EXIT);
}

/**
* Handles an interaction event on the root element.
* @param {!Event} evt
Expand Down Expand Up @@ -175,7 +201,9 @@ class MDCChipFoundation extends MDCFoundation {
evt.stopPropagation();
if (evt.type === 'click' || evt.key === 'Enter' || evt.keyCode === 13) {
this.adapter_.notifyTrailingIconInteraction();
this.adapter_.addClass(cssClasses.CHIP_EXIT);
if (this.shouldRemoveOnTrailingIconClick_) {
this.beginExit();
}
}
}
}
Expand Down
20 changes: 18 additions & 2 deletions packages/mdc-chips/chip/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {MDCRipple, MDCRippleFoundation} from '@material/ripple/index';

import MDCChipAdapter from './adapter';
import MDCChipFoundation from './foundation';
import {strings, cssClasses} from './constants';
import {strings} from './constants';

/**
* @extends {MDCComponent<!MDCChipFoundation>}
Expand Down Expand Up @@ -86,7 +86,7 @@ class MDCChip extends MDCComponent {
* Begins the exit animation which leads to removal of the chip.
*/
beginExit() {
this.root_.classList.add(cssClasses.CHIP_EXIT);
this.foundation_.beginExit();
}

/**
Expand All @@ -96,6 +96,22 @@ class MDCChip extends MDCComponent {
return this.foundation_;
}

/**
* Returns whether a trailing icon click should trigger exit/removal of the chip.
* @return {boolean}
*/
get shouldRemoveOnTrailingIconClick() {
return this.foundation_.getShouldRemoveOnTrailingIconClick();
}

/**
* Sets whether a trailing icon click should trigger exit/removal of the chip.
* @param {boolean} shouldRemove
*/
set shouldRemoveOnTrailingIconClick(shouldRemove) {
return this.foundation_.setShouldRemoveOnTrailingIconClick(shouldRemove);
}

/**
* @return {!MDCChipFoundation}
*/
Expand Down
40 changes: 40 additions & 0 deletions test/unit/mdc-chips/mdc-chip.foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ test('#setSelected removes mdc-chip--selected class if false', () => {
td.verify(mockAdapter.removeClass(cssClasses.SELECTED));
});

test(`#beginExit adds ${cssClasses.CHIP_EXIT} class`, () => {
const {foundation, mockAdapter} = setupTest();
foundation.beginExit();
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT));
});

test('on click, emit custom event', () => {
const {foundation, mockAdapter} = setupTest();
const handlers = captureHandlers(mockAdapter, 'registerEventHandler');
Expand Down Expand Up @@ -234,6 +240,40 @@ test('on click in trailing icon, emit custom event', () => {
handlers.click(mockEvt);

td.verify(mockAdapter.notifyTrailingIconInteraction());
td.verify(mockEvt.stopPropagation());
});

test(`on click in trailing icon, add ${cssClasses.CHIP_EXIT} class by default`, () => {
const {foundation, mockAdapter} = setupTest();
const handlers = captureHandlers(mockAdapter, 'registerTrailingIconInteractionHandler');
const mockEvt = {
type: 'click',
stopPropagation: td.func('stopPropagation'),
};

foundation.init();
handlers.click(mockEvt);

assert.isTrue(foundation.getShouldRemoveOnTrailingIconClick());
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT));
td.verify(mockEvt.stopPropagation());
});

test(`on click in trailing icon, do not add ${cssClasses.CHIP_EXIT} class if shouldRemoveOnTrailingIconClick_ is false`,
() => {
const {foundation, mockAdapter} = setupTest();
const handlers = captureHandlers(mockAdapter, 'registerTrailingIconInteractionHandler');
const mockEvt = {
type: 'click',
stopPropagation: td.func('stopPropagation'),
};

foundation.init();
foundation.setShouldRemoveOnTrailingIconClick(false);
handlers.click(mockEvt);

assert.isFalse(foundation.getShouldRemoveOnTrailingIconClick());
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT), {times: 0});
td.verify(mockEvt.stopPropagation());
}
);
17 changes: 14 additions & 3 deletions test/unit/mdc-chips/mdc-chip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,19 @@ test('#isSelected proxies to foundation', () => {
td.verify(mockFoundation.isSelected());
});

test(`#beginExit adds ${MDCChipFoundation.cssClasses.CHIP_EXIT} class`, () => {
const {component, root} = setupMockFoundationTest();
test('#get shouldRemoveOnTrailingIconClick proxies to foundation', () => {
const {component, mockFoundation} = setupMockFoundationTest();
assert.equal(component.shouldRemoveOnTrailingIconClick, mockFoundation.getShouldRemoveOnTrailingIconClick());
});

test('#set shouldRemoveOnTrailingIconClick proxies to foundation', () => {
const {component, mockFoundation} = setupMockFoundationTest();
component.shouldRemoveOnTrailingIconClick = false;
td.verify(mockFoundation.setShouldRemoveOnTrailingIconClick(false));
});

test('#beginExit proxies to foundation', () => {
const {component, mockFoundation} = setupMockFoundationTest();
component.beginExit();
assert.isTrue(root.classList.contains(MDCChipFoundation.cssClasses.CHIP_EXIT));
td.verify(mockFoundation.beginExit());
});

0 comments on commit 9178d46

Please sign in to comment.