Skip to content

Commit c2ccd1c

Browse files
committed
fix(material-experimental/mdc-chips): prevent default space and enter
We support pressing space or enter on the remove icon of an MDC-based chip, but we don't `preventDefault` which means that we can end up submitting a form or scrolling the page down.
1 parent 09dc459 commit c2ccd1c

File tree

2 files changed

+68
-4
lines changed

2 files changed

+68
-4
lines changed

src/material-experimental/mdc-chips/chip-remove.spec.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import {createFakeEvent} from '@angular/cdk/testing/private';
1+
import {createFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing/private';
22
import {Component, DebugElement} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {SPACE, ENTER} from '@angular/cdk/keycodes';
56
import {MatChip, MatChipsModule} from './index';
67

7-
describe('MDC-based Chip Remove', () => {
8+
fdescribe('MDC-based Chip Remove', () => {
89
let fixture: ComponentFixture<TestChip>;
910
let testChip: TestChip;
1011
let chipDebugElement: DebugElement;
@@ -86,6 +87,56 @@ describe('MDC-based Chip Remove', () => {
8687
expect(buttonElement.hasAttribute('aria-hidden')).toBe(false);
8788
});
8889

90+
it('should prevent the default SPACE action', () => {
91+
const buttonElement = chipNativeElement.querySelector('button')!;
92+
93+
testChip.removable = true;
94+
fixture.detectChanges();
95+
96+
const event = dispatchKeyboardEvent(buttonElement, 'keydown', SPACE);
97+
fixture.detectChanges();
98+
99+
expect(event.defaultPrevented).toBe(true);
100+
});
101+
102+
it('should not prevent the default SPACE action when a modifier key is pressed', () => {
103+
const buttonElement = chipNativeElement.querySelector('button')!;
104+
105+
testChip.removable = true;
106+
fixture.detectChanges();
107+
108+
const event = dispatchKeyboardEvent(buttonElement, 'keydown', SPACE);
109+
Object.defineProperty(event, 'shiftKey', {get: () => true});
110+
fixture.detectChanges();
111+
112+
expect(event.defaultPrevented).toBe(false);
113+
});
114+
115+
it('should prevent the default ENTER action', () => {
116+
const buttonElement = chipNativeElement.querySelector('button')!;
117+
118+
testChip.removable = true;
119+
fixture.detectChanges();
120+
121+
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ENTER);
122+
fixture.detectChanges();
123+
124+
expect(event.defaultPrevented).toBe(true);
125+
});
126+
127+
it('should not prevent the default ENTER action when a modifier key is pressed', () => {
128+
const buttonElement = chipNativeElement.querySelector('button')!;
129+
130+
testChip.removable = true;
131+
fixture.detectChanges();
132+
133+
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ENTER);
134+
Object.defineProperty(event, 'shiftKey', {get: () => true});
135+
fixture.detectChanges();
136+
137+
expect(event.defaultPrevented).toBe(false);
138+
});
139+
89140
});
90141
});
91142

src/material-experimental/mdc-chips/chip.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
} from '@angular/material/core';
4747
import {MDCChipAdapter, MDCChipFoundation} from '@material/chips';
4848
import {numbers} from '@material/ripple';
49+
import {SPACE, ENTER, hasModifierKey} from '@angular/cdk/keycodes';
4950
import {Subject} from 'rxjs';
5051
import {takeUntil} from 'rxjs/operators';
5152
import {MatChipAvatar, MatChipTrailingIcon, MatChipRemove} from './chip-icons';
@@ -351,11 +352,23 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte
351352
// event, even ones it doesn't handle, so we want to avoid passing it keyboard events
352353
// for which we have a custom handler. Note that we assert the type of the event using
353354
// the `type`, because `instanceof KeyboardEvent` can throw during server-side rendering.
354-
if (this.disabled || (event.type.startsWith('key') &&
355-
this.HANDLED_KEYS.indexOf((event as KeyboardEvent).keyCode) !== -1)) {
355+
const isKeyboardEvent = event.type.startsWith('key');
356+
357+
if (this.disabled || (isKeyboardEvent &&
358+
this.HANDLED_KEYS.indexOf((event as KeyboardEvent).keyCode) !== -1)) {
356359
return;
357360
}
361+
358362
this._chipFoundation.handleTrailingIconInteraction(event);
363+
364+
if (isKeyboardEvent && !hasModifierKey(event as KeyboardEvent)) {
365+
const keyCode = (event as KeyboardEvent).keyCode;
366+
367+
// Prevent default space and enter presses so we don't scroll the page or submit forms.
368+
if (keyCode === SPACE || keyCode === ENTER) {
369+
event.preventDefault();
370+
}
371+
}
359372
});
360373
}
361374

0 commit comments

Comments
 (0)