Skip to content

Commit 514146e

Browse files
committed
fix(material/chips): provide ability to edit for all screen readers with a click on already focused chip
1 parent 9f249d0 commit 514146e

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

goldens/material/chips/index.api.md

+4
Original file line numberDiff line numberDiff line change
@@ -416,13 +416,17 @@ export class MatChipRemove extends MatChipAction {
416416
export class MatChipRow extends MatChip implements AfterViewInit {
417417
constructor(...args: unknown[]);
418418
// (undocumented)
419+
_alreadyFocused: boolean;
420+
// (undocumented)
419421
protected basicChipAttrName: string;
420422
contentEditInput?: MatChipEditInput;
421423
defaultEditInput?: MatChipEditInput;
422424
// (undocumented)
423425
editable: boolean;
424426
readonly edited: EventEmitter<MatChipEditedEvent>;
425427
// (undocumented)
428+
_handleClick(event: MouseEvent): void;
429+
// (undocumented)
426430
_handleDoubleclick(event: MouseEvent): void;
427431
_handleFocus(): void;
428432
// (undocumented)

src/material/chips/chip-row.spec.ts

+87
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,93 @@ describe('Row Chips', () => {
245245
fixture.detectChanges();
246246
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeTruthy();
247247
});
248+
249+
it('should not begin editing on single click', () => {
250+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
251+
dispatchKeyboardEvent(chipNativeElement, 'click');
252+
fixture.detectChanges();
253+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
254+
});
255+
256+
it('should begin editing on single click when focused', fakeAsync(() => {
257+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
258+
259+
chipNativeElement.focus();
260+
flush();
261+
fixture.detectChanges();
262+
263+
dispatchKeyboardEvent(chipNativeElement, 'click');
264+
fixture.detectChanges();
265+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeTruthy();
266+
}));
267+
268+
describe('when disabled', () => {
269+
beforeEach(() => {
270+
testComponent.disabled = true;
271+
fixture.changeDetectorRef.markForCheck();
272+
fixture.detectChanges();
273+
});
274+
275+
it('should not begin editing on double click', () => {
276+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
277+
dispatchFakeEvent(chipNativeElement, 'dblclick');
278+
fixture.detectChanges();
279+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
280+
});
281+
282+
it('should not begin editing on ENTER', () => {
283+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
284+
dispatchKeyboardEvent(chipNativeElement, 'keydown', ENTER);
285+
fixture.detectChanges();
286+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
287+
});
288+
289+
it('should not begin editing on single click when focused', fakeAsync(() => {
290+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
291+
292+
chipNativeElement.focus();
293+
flush();
294+
fixture.detectChanges();
295+
296+
dispatchKeyboardEvent(chipNativeElement, 'click');
297+
fixture.detectChanges();
298+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
299+
}));
300+
});
301+
302+
describe('when not editable', () => {
303+
beforeEach(() => {
304+
testComponent.editable = false;
305+
fixture.changeDetectorRef.markForCheck();
306+
fixture.detectChanges();
307+
});
308+
309+
it('should not begin editing on double click', () => {
310+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
311+
dispatchFakeEvent(chipNativeElement, 'dblclick');
312+
fixture.detectChanges();
313+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
314+
});
315+
316+
it('should not begin editing on ENTER', () => {
317+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
318+
dispatchKeyboardEvent(chipNativeElement, 'keydown', ENTER);
319+
fixture.detectChanges();
320+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
321+
});
322+
323+
it('should not begin editing on single click when focused', fakeAsync(() => {
324+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
325+
326+
chipNativeElement.focus();
327+
flush();
328+
fixture.detectChanges();
329+
330+
dispatchKeyboardEvent(chipNativeElement, 'click');
331+
fixture.detectChanges();
332+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
333+
}));
334+
});
248335
});
249336

250337
describe('editing behavior', () => {

src/material/chips/chip-row.ts

+25
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface MatChipEditedEvent extends MatChipEvent {
6060
'[attr.aria-description]': 'null',
6161
'[attr.role]': 'role',
6262
'(focus)': '_handleFocus()',
63+
'(click)': '_handleClick($event)',
6364
'(dblclick)': '_handleDoubleclick($event)',
6465
},
6566
providers: [
@@ -92,6 +93,15 @@ export class MatChipRow extends MatChip implements AfterViewInit {
9293
/** The projected chip edit input. */
9394
@ContentChild(MatChipEditInput) contentEditInput?: MatChipEditInput;
9495

96+
/**
97+
* Set with a timeout when the chips has been focused via mouse or keyboard.
98+
*
99+
* This allows us to ensure chip is already focused when deciding whether to enter the
100+
* edit mode on a subsequent click. Otherwise, the chip appears focused when handling the
101+
* first click event.
102+
*/
103+
_alreadyFocused = false;
104+
95105
_isEditing = false;
96106

97107
constructor(...args: unknown[]);
@@ -104,6 +114,15 @@ export class MatChipRow extends MatChip implements AfterViewInit {
104114
if (this._isEditing && !this._editStartPending) {
105115
this._onEditFinish();
106116
}
117+
this._alreadyFocused = false;
118+
});
119+
this._onFocus.pipe(takeUntil(this.destroyed)).subscribe(() => {
120+
if (!this._isEditing && !this.disabled) {
121+
// Need a timeout to ensure flag not set while handling the first click.
122+
this._ngZone.runOutsideAngular(() => {
123+
setTimeout(() => (this._alreadyFocused = true), 100);
124+
});
125+
}
107126
});
108127
}
109128

@@ -135,6 +154,12 @@ export class MatChipRow extends MatChip implements AfterViewInit {
135154
}
136155
}
137156

157+
_handleClick(event: MouseEvent) {
158+
if (!this.disabled && this.editable && !this._isEditing && this._alreadyFocused) {
159+
this._startEditing(event);
160+
}
161+
}
162+
138163
_handleDoubleclick(event: MouseEvent) {
139164
if (!this.disabled && this.editable) {
140165
this._startEditing(event);

0 commit comments

Comments
 (0)