Skip to content

Commit 7b7e633

Browse files
crisbetommalerba
authored andcommitted
fix(material-experimental/mdc-chips): prevent default space an… (#18084)
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 3b4e496 commit 7b7e633

File tree

2 files changed

+74
-3
lines changed

2 files changed

+74
-3
lines changed

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import {createFakeEvent} from '@angular/cdk/testing/private';
1+
import {
2+
createFakeEvent,
3+
dispatchKeyboardEvent,
4+
createKeyboardEvent,
5+
dispatchEvent,
6+
} from '@angular/cdk/testing/private';
27
import {Component, DebugElement} from '@angular/core';
38
import {By} from '@angular/platform-browser';
49
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
10+
import {SPACE, ENTER} from '@angular/cdk/keycodes';
511
import {MatChip, MatChipsModule} from './index';
612

713
describe('MDC-based Chip Remove', () => {
@@ -92,6 +98,58 @@ describe('MDC-based Chip Remove', () => {
9298
expect(buttonElement.hasAttribute('aria-hidden')).toBe(false);
9399
});
94100

101+
it('should prevent the default SPACE action', () => {
102+
const buttonElement = chipNativeElement.querySelector('button')!;
103+
104+
testChip.removable = true;
105+
fixture.detectChanges();
106+
107+
const event = dispatchKeyboardEvent(buttonElement, 'keydown', SPACE);
108+
fixture.detectChanges();
109+
110+
expect(event.defaultPrevented).toBe(true);
111+
});
112+
113+
it('should not prevent the default SPACE action when a modifier key is pressed', () => {
114+
const buttonElement = chipNativeElement.querySelector('button')!;
115+
116+
testChip.removable = true;
117+
fixture.detectChanges();
118+
119+
const event = createKeyboardEvent('keydown', SPACE);
120+
Object.defineProperty(event, 'shiftKey', {get: () => true});
121+
dispatchEvent(buttonElement, event);
122+
fixture.detectChanges();
123+
124+
expect(event.defaultPrevented).toBe(false);
125+
});
126+
127+
it('should prevent the default ENTER action', () => {
128+
const buttonElement = chipNativeElement.querySelector('button')!;
129+
130+
testChip.removable = true;
131+
fixture.detectChanges();
132+
133+
const event = dispatchKeyboardEvent(buttonElement, 'keydown', ENTER);
134+
fixture.detectChanges();
135+
136+
expect(event.defaultPrevented).toBe(true);
137+
});
138+
139+
it('should not prevent the default ENTER action when a modifier key is pressed', () => {
140+
const buttonElement = chipNativeElement.querySelector('button')!;
141+
142+
testChip.removable = true;
143+
fixture.detectChanges();
144+
145+
const event = createKeyboardEvent('keydown', ENTER);
146+
Object.defineProperty(event, 'shiftKey', {get: () => true});
147+
dispatchEvent(buttonElement, event);
148+
fixture.detectChanges();
149+
150+
expect(event.defaultPrevented).toBe(false);
151+
});
152+
95153
});
96154
});
97155

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)