Skip to content

Commit

Permalink
feat(chips): Add animation for entry chips (#2543)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: layout() method added to MDCChipAdapter.
  • Loading branch information
bonniezhou committed Apr 13, 2018
1 parent f750ec7 commit 68006fb
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 9 deletions.
5 changes: 2 additions & 3 deletions demos/chips.html
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,8 @@ <h2>Custom theme</h2>
entryInput.value = '';
}
};
['click', 'keydown'].forEach(function(evtType) {
entryButton.addEventListener(evtType, addChip);
});
entryButton.addEventListener('click', addChip);
entryInput.addEventListener('keydown', addChip);
});
</script>
</body>
Expand Down
2 changes: 2 additions & 0 deletions packages/mdc-chips/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ To pre-select filter chips that have a leading icon, also add the class `mdc-chi
CSS Class | Description
--- | ---
`mdc-chip-set` | Mandatory. Indicates the set that the chip belongs to.
`mdc-chip-set--entry` | Optional. Indicates that the chips in the set are entry chips, which enable user input by converting text into chips.
`mdc-chip-set--choice` | Optional. Indicates that the chips in the set are choice chips, which allow a single selection from a set of options.
`mdc-chip-set--filter` | Optional. Indicates that the chips in the set are filter chips, which allow multiple selection from a set of options.
`mdc-chip` | Mandatory.
Expand Down Expand Up @@ -216,6 +217,7 @@ Method Signature | Description
`deregisterTrailingIconInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the trailing icon element
`notifyInteraction() => void` | Emits a custom event `MDCChip:interaction` denoting the chip has been interacted with
`notifyTrailingIconInteraction() => void` | Emits a custom event `MDCChip:trailingIconInteraction` denoting the chip's trailing icon has been interacted with
`layout() => void` | Recomputes all dimensions and positions for the ripple element

> _NOTE_: The custom events emitted by `notifyInteraction` and `notifyTrailingIconInteraction` must pass along the target chip in its event `detail`, as well as bubble to the parent `mdc-chip-set` element.
Expand Down
17 changes: 17 additions & 0 deletions packages/mdc-chips/chip-set/mdc-chip-set.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,29 @@
// limitations under the License.
//

@import "@material/animation/variables";
@import "../mixins";

@keyframes mdc-chip-entry {
from {
transform: scale(.8);
opacity: .4;
}

to {
transform: scale(1);
opacity: 1;
}
}

.mdc-chip-set {
@include mdc-chip-set-spacing(8px);

display: flex;
flex-wrap: wrap;
box-sizing: border-box;
}

.mdc-chip-set--entry .mdc-chip {
animation: mdc-chip-entry 100ms $mdc-animation-deceleration-curve-timing-function;
}
5 changes: 5 additions & 0 deletions packages/mdc-chips/chip/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ class MDCChipAdapter {
* interacted with (typically on click or keydown).
*/
notifyTrailingIconInteraction() {}

/**
* Recomputes all dimensions and positions for the ripple element.
*/
layout() {}
}

export default MDCChipAdapter;
3 changes: 2 additions & 1 deletion packages/mdc-chips/chip/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

/** @enum {string} */
const strings = {
ENTRY_ANIMATION_NAME: 'mdc-chip-entry',
INTERACTION_EVENT: 'MDCChip:interaction',
TRAILING_ICON_INTERACTION_EVENT: 'MDCChip:trailingIconInteraction',
CHECKMARK_SELECTOR: '.mdc-chip__checkmark',
LEADING_ICON_SELECTOR: '.mdc-chip__icon--leading',
TRAILING_ICON_INTERACTION_EVENT: 'MDCChip:trailingIconInteraction',
TRAILING_ICON_SELECTOR: '.mdc-chip__icon--trailing',
};

Expand Down
16 changes: 16 additions & 0 deletions packages/mdc-chips/chip/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class MDCChipFoundation extends MDCFoundation {
deregisterTrailingIconInteractionHandler: () => {},
notifyInteraction: () => {},
notifyTrailingIconInteraction: () => {},
layout: () => {},
});
}

Expand All @@ -68,6 +69,8 @@ class MDCChipFoundation extends MDCFoundation {
/** @private {function(!Event): undefined} */
this.transitionEndHandler_ = (evt) => this.handleTransitionEnd_(evt);
/** @private {function(!Event): undefined} */
this.animationEndHandler_ = (evt) => this.handleAnimationEnd_(evt);
/** @private {function(!Event): undefined} */
this.trailingIconInteractionHandler_ = (evt) => this.handleTrailingIconInteraction_(evt);
}

Expand All @@ -76,6 +79,7 @@ class MDCChipFoundation extends MDCFoundation {
this.adapter_.registerEventHandler(evtType, this.interactionHandler_);
});
this.adapter_.registerEventHandler('transitionend', this.transitionEndHandler_);
this.adapter_.registerEventHandler('animationend', this.animationEndHandler_);
['click', 'keydown', 'touchstart', 'pointerdown', 'mousedown'].forEach((evtType) => {
this.adapter_.registerTrailingIconInteractionHandler(evtType, this.trailingIconInteractionHandler_);
});
Expand All @@ -86,6 +90,7 @@ class MDCChipFoundation extends MDCFoundation {
this.adapter_.deregisterEventHandler(evtType, this.interactionHandler_);
});
this.adapter_.deregisterEventHandler('transitionend', this.transitionEndHandler_);
this.adapter_.deregisterEventHandler('animationend', this.animationEndHandler_);
['click', 'keydown', 'touchstart', 'pointerdown', 'mousedown'].forEach((evtType) => {
this.adapter_.deregisterTrailingIconInteractionHandler(evtType, this.trailingIconInteractionHandler_);
});
Expand Down Expand Up @@ -138,6 +143,17 @@ class MDCChipFoundation extends MDCFoundation {
}
}

/**
* Handles an animation end event on the root element.
* @param {!Event} evt
*/
handleAnimationEnd_(evt) {
// The chip's ripple size must be recalculated after the entry animation.
if (evt.animationName === strings.ENTRY_ANIMATION_NAME) {
this.adapter_.layout();
}
}

/**
* Handles an interaction event on the trailing icon element. This is used to
* prevent the ripple from activating on interaction with the trailing icon.
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-chips/chip/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class MDCChip extends MDCComponent {
notifyInteraction: () => this.emit(strings.INTERACTION_EVENT, {chip: this}, true /* shouldBubble */),
notifyTrailingIconInteraction: () => this.emit(
strings.TRAILING_ICON_INTERACTION_EVENT, {chip: this}, true /* shouldBubble */),
layout: () => this.ripple_.layout(),
})));
}

Expand Down
18 changes: 14 additions & 4 deletions test/unit/mdc-chips/mdc-chip-set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ class FakeChip {
test('#constructor instantiates child chip components', () => {
const root = getFixture();
const component = new MDCChipSet(root, undefined, (el) => new FakeChip(el));
assert.isOk(component.chips.length === 3 &&
component.chips[0] instanceof FakeChip &&
component.chips[1] instanceof FakeChip &&
component.chips[2] instanceof FakeChip);
assert.equal(component.chips.length, 3);
assert.instanceOf(component.chips[0], FakeChip);
assert.instanceOf(component.chips[1], FakeChip);
assert.instanceOf(component.chips[2], FakeChip);
});

test('#destroy cleans up child chip components', () => {
Expand All @@ -66,6 +66,16 @@ test('#destroy cleans up child chip components', () => {
td.verify(component.chips[2].destroy());
});

test('#addChip creates and adds a new chip to the DOM', () => {
const root = getFixture();
const component = new MDCChipSet(root, undefined, (el) => new FakeChip(el));
component.initialSyncWithDOM();
component.addChip('hello world');
td.verify(component.foundation_.addChip('hello world'));
assert.equal(component.chips.length, 4);
assert.instanceOf(component.chips[3], FakeChip);
});

class FakeSelectedChip {
constructor() {
this.foundation = td.object({
Expand Down
16 changes: 15 additions & 1 deletion test/unit/mdc-chips/mdc-chip.foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test('defaultAdapter returns a complete adapter implementation', () => {
'addClass', 'removeClass', 'hasClass', 'addClassToLeadingIcon', 'removeClassFromLeadingIcon',
'eventTargetHasClass', 'registerEventHandler', 'deregisterEventHandler',
'registerTrailingIconInteractionHandler', 'deregisterTrailingIconInteractionHandler',
'notifyInteraction', 'notifyTrailingIconInteraction',
'notifyInteraction', 'notifyTrailingIconInteraction', 'layout',
]);
});

Expand Down Expand Up @@ -109,6 +109,20 @@ test('on click, emit custom event', () => {
td.verify(mockAdapter.notifyInteraction());
});

test('on entry animation end, call layout', () => {
const {foundation, mockAdapter} = setupTest();
const handlers = captureHandlers(mockAdapter, 'registerEventHandler');
const mockEvt = {
type: 'animationend',
animationName: 'mdc-chip-entry',
};

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

td.verify(mockAdapter.layout());
});

test(`on leading icon opacity transition end, add ${cssClasses.HIDDEN_LEADING_ICON}` +
'class to leading icon if chip is selected', () => {
const {foundation, mockAdapter} = setupTest();
Expand Down

0 comments on commit 68006fb

Please sign in to comment.