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

Commit 5e45d77

Browse files
authored
revert: feat(chips): Consolidate interaction event handlers (#5251) (#5301)
1 parent ad9dfe7 commit 5e45d77

File tree

5 files changed

+107
-122
lines changed

5 files changed

+107
-122
lines changed

packages/mdc-chips/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,10 @@ Method Signature | Description
445445
`setShouldRemoveOnTrailingIconClick(shouldRemove: boolean) => void` | Sets whether a trailing icon click should trigger exit/removal of the chip
446446
`getDimensions() => ClientRect` | Returns the dimensions of the chip. This is used for applying ripple to the chip.
447447
`beginExit() => void` | Begins the exit animation which leads to removal of the chip
448-
`handleClick(evt: Event) => void` | Handles a click event on the root element
449-
`handleKeydown(evt: Event) => void` | Handles a keydown event on the root element
448+
`handleInteraction(evt: Event) => void` | Handles an interaction event on the root element
450449
`handleTransitionEnd(evt: Event) => void` | Handles a transition end event on the root element
450+
`handleTrailingIconInteraction(evt: Event) => void` | Handles an interaction event on the trailing icon element
451+
`handleKeydown(evt: Event) => void` | Handles a keydown event on the root element
451452
`removeFocus() => void` | Removes focusability from the chip
452453

453454
#### `MDCChipFoundation` Event Handlers
@@ -456,9 +457,10 @@ When wrapping the Chip foundation, the following events must be bound to the ind
456457

457458
Events | Element Selector | Foundation Handler
458459
--- | --- | ---
459-
`click` | `.mdc-chip` (root) | `handleClick()`
460-
`keydown` | `.mdc-chip` (root) | `handleKeydown()`
460+
`click`, `keydown` | `.mdc-chip` (root) | `handleInteraction()`
461+
`click`, `keydown` | `.mdc-chip__icon--trailing` (if present) | `handleTrailingIconInteraction()`
461462
`transitionend` | `.mdc-chip` (root) | `handleTransitionEnd()`
463+
`keydown` | `.mdc-chip` (root) | `handleKeydown()`
462464

463465
#### `MDCChipSetFoundation`
464466

packages/mdc-chips/chip/component.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ import {MDCChipFoundation} from './foundation';
3333
import {MDCChipInteractionEventDetail, MDCChipNavigationEventDetail, MDCChipRemovalEventDetail,
3434
MDCChipSelectionEventDetail} from './types';
3535

36+
type InteractionType = 'click' | 'keydown';
37+
38+
const INTERACTION_EVENTS: InteractionType[] = ['click', 'keydown'];
39+
3640
export type MDCChipFactory = (el: Element, foundation?: MDCChipFoundation) => MDCChip;
3741

3842
export class MDCChip extends MDCComponent<MDCChipFoundation> implements MDCRippleCapableSurface {
@@ -80,17 +84,20 @@ export class MDCChip extends MDCComponent<MDCChipFoundation> implements MDCRippl
8084
root_!: HTMLElement; // assigned in MDCComponent constructor
8185

8286
private leadingIcon_!: Element | null; // assigned in initialize()
87+
private trailingIcon_!: Element | null; // assigned in initialize()
8388
private checkmark_!: Element | null; // assigned in initialize()
8489
private ripple_!: MDCRipple; // assigned in initialize()
8590
private primaryAction_!: Element | null; // assigned in initialize()
8691
private trailingAction_!: Element | null; // assigned in initialize()
8792

88-
private handleClick_!: SpecificEventListener<'click'>; // assigned in initialSyncWithDOM()
93+
private handleInteraction_!: SpecificEventListener<InteractionType>; // assigned in initialSyncWithDOM()
8994
private handleTransitionEnd_!: SpecificEventListener<'transitionend'>; // assigned in initialSyncWithDOM()
95+
private handleTrailingIconInteraction_!: SpecificEventListener<InteractionType>; // assigned in initialSyncWithDOM()
9096
private handleKeydown_!: SpecificEventListener<'keydown'>; // assigned in initialSyncWithDOM()
9197

9298
initialize(rippleFactory: MDCRippleFactory = (el, foundation) => new MDCRipple(el, foundation)) {
9399
this.leadingIcon_ = this.root_.querySelector(strings.LEADING_ICON_SELECTOR);
100+
this.trailingIcon_ = this.root_.querySelector(strings.TRAILING_ICON_SELECTOR);
94101
this.checkmark_ = this.root_.querySelector(strings.CHECKMARK_SELECTOR);
95102
this.primaryAction_ = this.root_.querySelector(strings.PRIMARY_ACTION_SELECTOR);
96103
this.trailingAction_ = this.root_.querySelector(strings.TRAILING_ACTION_SELECTOR);
@@ -105,21 +112,40 @@ export class MDCChip extends MDCComponent<MDCChipFoundation> implements MDCRippl
105112
}
106113

107114
initialSyncWithDOM() {
108-
this.handleClick_ = (evt: MouseEvent) => this.foundation_.handleClick(evt);
115+
this.handleInteraction_ = (evt: MouseEvent | KeyboardEvent) => this.foundation_.handleInteraction(evt);
109116
this.handleTransitionEnd_ = (evt: TransitionEvent) => this.foundation_.handleTransitionEnd(evt);
117+
this.handleTrailingIconInteraction_ = (evt: MouseEvent | KeyboardEvent) =>
118+
this.foundation_.handleTrailingIconInteraction(evt);
110119
this.handleKeydown_ = (evt: KeyboardEvent) => this.foundation_.handleKeydown(evt);
111120

112-
this.listen('click', this.handleClick_);
121+
INTERACTION_EVENTS.forEach((evtType) => {
122+
this.listen(evtType, this.handleInteraction_);
123+
});
113124
this.listen('transitionend', this.handleTransitionEnd_);
114125
this.listen('keydown', this.handleKeydown_);
126+
127+
if (this.trailingIcon_) {
128+
INTERACTION_EVENTS.forEach((evtType) => {
129+
this.trailingIcon_!.addEventListener(evtType, this.handleTrailingIconInteraction_ as EventListener);
130+
});
131+
}
115132
}
116133

117134
destroy() {
118135
this.ripple_.destroy();
119-
this.unlisten('click', this.handleClick_);
136+
137+
INTERACTION_EVENTS.forEach((evtType) => {
138+
this.unlisten(evtType, this.handleInteraction_);
139+
});
120140
this.unlisten('transitionend', this.handleTransitionEnd_);
121141
this.unlisten('keydown', this.handleKeydown_);
122142

143+
if (this.trailingIcon_) {
144+
INTERACTION_EVENTS.forEach((evtType) => {
145+
this.trailingIcon_!.removeEventListener(evtType, this.handleTrailingIconInteraction_ as EventListener);
146+
});
147+
}
148+
123149
super.destroy();
124150
}
125151

packages/mdc-chips/chip/foundation.ts

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,11 @@ export class MDCChipFoundation extends MDCFoundation<MDCChipAdapter> {
141141
/**
142142
* Handles an interaction event on the root element.
143143
*/
144-
handleClick(evt: MouseEvent) {
145-
const trailingIconIsSource = this.adapter_.eventTargetHasClass(evt.target, cssClasses.TRAILING_ICON);
146-
if (trailingIconIsSource) {
147-
return this.notifyTrailingIconInteractionAndRemove_(evt);
144+
handleInteraction(evt: MouseEvent | KeyboardEvent) {
145+
if (this.shouldHandleInteraction_(evt)) {
146+
this.adapter_.notifyInteraction();
147+
this.focusPrimaryAction_();
148148
}
149-
150-
this.notifyInteractionAndFocus_();
151149
}
152150

153151
/**
@@ -205,24 +203,27 @@ export class MDCChipFoundation extends MDCFoundation<MDCChipAdapter> {
205203
}
206204

207205
/**
208-
* Handles a keydown event from the root element.
206+
* Handles an interaction event on the trailing icon element. This is used to
207+
* prevent the ripple from activating on interaction with the trailing icon.
209208
*/
210-
handleKeydown(evt: KeyboardEvent) {
211-
const trailingIconIsSource = this.adapter_.eventTargetHasClass(evt.target, cssClasses.TRAILING_ICON);
212-
if (trailingIconIsSource && this.shouldProcessKeydownAsClick_(evt)) {
213-
return this.notifyTrailingIconInteractionAndRemove_(evt);
214-
}
215-
216-
if (this.shouldProcessKeydownAsClick_(evt)) {
217-
return this.notifyInteractionAndFocus_();
209+
handleTrailingIconInteraction(evt: MouseEvent | KeyboardEvent) {
210+
if (this.shouldHandleInteraction_(evt)) {
211+
this.adapter_.notifyTrailingIconInteraction();
212+
this.removeChip_(evt);
218213
}
214+
}
219215

216+
/**
217+
* Handles a keydown event from the root element.
218+
*/
219+
handleKeydown(evt: KeyboardEvent) {
220220
if (this.shouldRemoveChip_(evt)) {
221221
return this.removeChip_(evt);
222222
}
223223

224+
const key = evt.key;
224225
// Early exit if the key is not usable
225-
if (!navigationKeys.has(evt.key)) {
226+
if (!navigationKeys.has(key)) {
226227
return;
227228
}
228229

@@ -307,20 +308,20 @@ export class MDCChipFoundation extends MDCFoundation<MDCChipAdapter> {
307308
this.adapter_.setPrimaryActionAttr(strings.TAB_INDEX, '-1');
308309
}
309310

310-
private removeChip_(evt: Event) {
311+
private removeChip_(evt: MouseEvent|KeyboardEvent) {
311312
evt.stopPropagation();
312313
if (this.shouldRemoveOnTrailingIconClick_) {
313314
this.beginExit();
314315
}
315316
}
316317

317-
private notifyTrailingIconInteractionAndRemove_(evt: Event) {
318-
this.adapter_.notifyTrailingIconInteraction();
319-
this.removeChip_(evt);
320-
}
318+
private shouldHandleInteraction_(evt: MouseEvent|KeyboardEvent): boolean {
319+
if (evt.type === 'click') {
320+
return true;
321+
}
321322

322-
private shouldProcessKeydownAsClick_(evt: KeyboardEvent): boolean {
323-
return evt.key === strings.ENTER_KEY || evt.key === strings.SPACEBAR_KEY;
323+
const keyEvt = evt as KeyboardEvent;
324+
return keyEvt.key === strings.ENTER_KEY || keyEvt.key === strings.SPACEBAR_KEY;
324325
}
325326

326327
private shouldRemoveChip_(evt: KeyboardEvent): boolean {
@@ -345,11 +346,6 @@ export class MDCChipFoundation extends MDCFoundation<MDCChipAdapter> {
345346
private notifyIgnoredSelection_(selected: boolean) {
346347
this.adapter_.notifySelection(selected, true);
347348
}
348-
349-
private notifyInteractionAndFocus_() {
350-
this.adapter_.notifyInteraction();
351-
this.focusPrimaryAction_();
352-
}
353349
}
354350

355351
// tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.

test/unit/mdc-chips/mdc-chip.foundation.test.js

Lines changed: 26 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,21 @@ test(`#beginExit adds ${cssClasses.CHIP_EXIT} class`, () => {
149149
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT));
150150
});
151151

152-
test('#handleKeydown does not emit event on invalid key', () => {
152+
test('#handleInteraction does not emit event on invalid key', () => {
153153
const {foundation, mockAdapter} = setupTest();
154-
const mockKeydown = {
154+
const mockEvt = {
155155
type: 'keydown',
156156
key: 'Shift',
157157
};
158158

159-
foundation.handleKeydown(mockKeydown);
159+
foundation.handleInteraction(mockEvt);
160160
td.verify(mockAdapter.notifyInteraction(), {times: 0});
161161
});
162162

163163
const validEvents = [
164164
{
165+
type: 'click',
166+
}, {
165167
type: 'keydown',
166168
key: 'Enter',
167169
}, {
@@ -171,39 +173,23 @@ const validEvents = [
171173
];
172174

173175
validEvents.forEach((evt) => {
174-
test(`#handleKeydown(${evt}) notifies interaction`, () => {
176+
test(`#handleInteraction(${evt}) notifies interaction`, () => {
175177
const {foundation, mockAdapter} = setupTest();
176178

177-
foundation.handleKeydown(evt);
179+
foundation.handleInteraction(evt);
178180
td.verify(mockAdapter.notifyInteraction());
179181
});
180182

181-
test(`#handleKeydown(${evt}) focuses the primary action`, () => {
183+
test(`#handleInteraction(${evt}) focuses the primary action`, () => {
182184
const {foundation, mockAdapter} = setupTest();
183185

184-
foundation.handleKeydown(evt);
186+
foundation.handleInteraction(evt);
185187
td.verify(mockAdapter.setPrimaryActionAttr(strings.TAB_INDEX, '0'));
186188
td.verify(mockAdapter.setTrailingActionAttr(strings.TAB_INDEX, '-1'));
187189
td.verify(mockAdapter.focusPrimaryAction());
188190
});
189191
});
190192

191-
test('#handleClick(evt) notifies interaction', () => {
192-
const {foundation, mockAdapter} = setupTest();
193-
194-
foundation.handleClick({type: 'click'});
195-
td.verify(mockAdapter.notifyInteraction());
196-
});
197-
198-
test('#handleClick(evt) focuses the primary action', () => {
199-
const {foundation, mockAdapter} = setupTest();
200-
201-
foundation.handleClick({type: 'click'});
202-
td.verify(mockAdapter.setPrimaryActionAttr(strings.TAB_INDEX, '0'));
203-
td.verify(mockAdapter.setTrailingActionAttr(strings.TAB_INDEX, '-1'));
204-
td.verify(mockAdapter.focusPrimaryAction());
205-
});
206-
207193
test('#handleTransitionEnd notifies removal of chip on width transition end', () => {
208194
const {foundation, mockAdapter} = setupTest();
209195
const mockEvt = {
@@ -318,106 +304,62 @@ test('#handleTransitionEnd does nothing for width property when not exiting', ()
318304
td.verify(mockAdapter.removeClassFromLeadingIcon(cssClasses.HIDDEN_LEADING_ICON), {times: 0});
319305
});
320306

321-
test('#handleKeydown emits no custom event on invalid keys', () => {
307+
test('#handleTrailingIconInteraction emits no event on invalid keys', () => {
322308
const {foundation, mockAdapter} = setupTest();
323309
const mockEvt = {
324-
type: 'keydown',
310+
type: 'keydowb',
325311
key: 'Shift',
326312
stopPropagation: td.func('stopPropagation'),
327-
target: {},
328313
};
329314

330-
td.when(mockAdapter.eventTargetHasClass(mockEvt.target, cssClasses.TRAILING_ICON)).thenReturn(true);
331-
332-
foundation.handleKeydown(mockEvt);
315+
foundation.handleTrailingIconInteraction(mockEvt);
333316
td.verify(mockAdapter.notifyTrailingIconInteraction(), {times: 0});
334317
});
335318

336-
const validKeys = [
337-
' ', // Space
338-
'Enter',
339-
];
340-
341-
validKeys.forEach((key) => {
342-
test(`#handleKeydown() from trailing icon emits custom event on "${key}"`, () => {
343-
const {foundation, mockAdapter} = setupTest();
344-
const mockEvt = {
345-
type: 'keydown',
346-
stopPropagation: td.func('stopPropagation'),
347-
target: {},
348-
key,
349-
};
350-
351-
td.when(mockAdapter.eventTargetHasClass(mockEvt.target, cssClasses.TRAILING_ICON)).thenReturn(true);
352-
353-
foundation.handleKeydown(mockEvt);
354-
td.verify(mockAdapter.notifyTrailingIconInteraction(), {times: 1});
355-
});
356-
});
357-
358-
test('#handleClick() from trailing icon emits custom event', () => {
319+
test('#handleTrailingIconInteraction emits custom event on click or enter key in trailing icon', () => {
359320
const {foundation, mockAdapter} = setupTest();
360321
const mockEvt = {
361322
type: 'click',
362323
stopPropagation: td.func('stopPropagation'),
363-
target: {},
364324
};
365325

366-
td.when(mockAdapter.eventTargetHasClass(mockEvt.target, cssClasses.TRAILING_ICON)).thenReturn(true);
367-
368-
foundation.handleClick(mockEvt);
326+
foundation.handleTrailingIconInteraction(mockEvt);
369327
td.verify(mockAdapter.notifyTrailingIconInteraction(), {times: 1});
370328
td.verify(mockEvt.stopPropagation(), {times: 1});
329+
330+
foundation.handleTrailingIconInteraction(Object.assign(mockEvt, {type: 'keydown', key: ' '}));
331+
td.verify(mockAdapter.notifyTrailingIconInteraction(), {times: 2});
332+
td.verify(mockEvt.stopPropagation(), {times: 2});
333+
334+
foundation.handleTrailingIconInteraction(Object.assign(mockEvt, {type: 'keydown', key: 'Enter'}));
335+
td.verify(mockAdapter.notifyTrailingIconInteraction(), {times: 3});
336+
td.verify(mockEvt.stopPropagation(), {times: 3});
371337
});
372338

373-
test(`#handleClick() from trailing icon adds ${cssClasses.CHIP_EXIT} class by default`, () => {
339+
test(`#handleTrailingIconInteraction adds ${cssClasses.CHIP_EXIT} class by default on click in trailing icon`, () => {
374340
const {foundation, mockAdapter} = setupTest();
375341
const mockEvt = {
376342
type: 'click',
377343
stopPropagation: td.func('stopPropagation'),
378-
target: {},
379344
};
380345

381-
td.when(mockAdapter.eventTargetHasClass(mockEvt.target, cssClasses.TRAILING_ICON)).thenReturn(true);
346+
foundation.handleTrailingIconInteraction(mockEvt);
382347

383-
foundation.handleClick(mockEvt);
384348
assert.isTrue(foundation.getShouldRemoveOnTrailingIconClick());
385349
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT));
386350
td.verify(mockEvt.stopPropagation());
387351
});
388352

389-
validKeys.forEach((key) => {
390-
test(`#handleKeydown({key: "${key}"}) from trailing icon adds ${cssClasses.CHIP_EXIT} class by default`, () => {
391-
const {foundation, mockAdapter} = setupTest();
392-
const mockEvt = {
393-
type: 'keydown',
394-
stopPropagation: td.func('stopPropagation'),
395-
target: {},
396-
key,
397-
};
398-
399-
td.when(mockAdapter.eventTargetHasClass(mockEvt.target, cssClasses.TRAILING_ICON)).thenReturn(true);
400-
401-
foundation.handleKeydown(mockEvt);
402-
assert.isTrue(foundation.getShouldRemoveOnTrailingIconClick());
403-
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT));
404-
td.verify(mockEvt.stopPropagation());
405-
});
406-
});
407-
408-
test(`#handleClick() from trailing icon does not add ${cssClasses.CHIP_EXIT} class to trailing icon ` +
353+
test(`#handleTrailingIconInteraction does not add ${cssClasses.CHIP_EXIT} class on click in trailing icon ` +
409354
'if shouldRemoveOnTrailingIconClick_ is false', () => {
410355
const {foundation, mockAdapter} = setupTest();
411356
const mockEvt = {
412357
type: 'click',
413358
stopPropagation: td.func('stopPropagation'),
414-
target: {},
415359
};
416360

417-
td.when(mockAdapter.eventTargetHasClass(mockEvt.target, cssClasses.TRAILING_ICON)).thenReturn(true);
418-
419361
foundation.setShouldRemoveOnTrailingIconClick(false);
420-
foundation.handleClick(mockEvt);
362+
foundation.handleTrailingIconInteraction(mockEvt);
421363

422364
assert.isFalse(foundation.getShouldRemoveOnTrailingIconClick());
423365
td.verify(mockAdapter.addClass(cssClasses.CHIP_EXIT), {times: 0});

0 commit comments

Comments
 (0)