Skip to content

Commit 7319721

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

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-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

+85
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
dispatchEvent,
66
dispatchFakeEvent,
77
dispatchKeyboardEvent,
8+
dispatchMouseEvent,
89
} from '@angular/cdk/testing/private';
910
import {Component, DebugElement, ElementRef, ViewChild} from '@angular/core';
1011
import {ComponentFixture, TestBed, fakeAsync, flush, waitForAsync} from '@angular/core/testing';
@@ -245,6 +246,90 @@ describe('Row Chips', () => {
245246
fixture.detectChanges();
246247
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeTruthy();
247248
});
249+
250+
it('should not begin editing on single click', () => {
251+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
252+
dispatchMouseEvent(chipNativeElement, 'click');
253+
fixture.detectChanges();
254+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
255+
});
256+
257+
it('should begin editing on single click when focused', fakeAsync(() => {
258+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
259+
chipNativeElement.focus();
260+
261+
// Need to also simulate the mousedown as that sets the already focused flag.
262+
dispatchMouseEvent(chipNativeElement, 'mousedown');
263+
dispatchMouseEvent(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+
chipNativeElement.focus();
292+
293+
// Need to also simulate the mousedown as that sets the already focused flag.
294+
dispatchMouseEvent(chipNativeElement, 'mousedown');
295+
dispatchMouseEvent(chipNativeElement, 'click');
296+
fixture.detectChanges();
297+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
298+
}));
299+
});
300+
301+
describe('when not editable', () => {
302+
beforeEach(() => {
303+
testComponent.editable = false;
304+
fixture.changeDetectorRef.markForCheck();
305+
fixture.detectChanges();
306+
});
307+
308+
it('should not begin editing on double click', () => {
309+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
310+
dispatchFakeEvent(chipNativeElement, 'dblclick');
311+
fixture.detectChanges();
312+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
313+
});
314+
315+
it('should not begin editing on ENTER', () => {
316+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
317+
dispatchKeyboardEvent(chipNativeElement, 'keydown', ENTER);
318+
fixture.detectChanges();
319+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
320+
});
321+
322+
it('should not begin editing on single click when focused', fakeAsync(() => {
323+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
324+
chipNativeElement.focus();
325+
326+
// Need to also simulate the mousedown as that sets the already focused flag.
327+
dispatchMouseEvent(chipNativeElement, 'mousedown');
328+
dispatchMouseEvent(chipNativeElement, 'click');
329+
fixture.detectChanges();
330+
expect(chipNativeElement.querySelector('.mat-chip-edit-input')).toBeFalsy();
331+
}));
332+
});
248333
});
249334

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

src/material/chips/chip-row.ts

+23
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export interface MatChipEditedEvent extends MatChipEvent {
6060
'[attr.aria-description]': 'null',
6161
'[attr.role]': 'role',
6262
'(focus)': '_handleFocus()',
63+
'(mousedown)': '_handleMouseDown($event)',
64+
'(click)': '_handleClick($event)',
6365
'(dblclick)': '_handleDoubleclick($event)',
6466
},
6567
providers: [
@@ -92,6 +94,15 @@ export class MatChipRow extends MatChip implements AfterViewInit {
9294
/** The projected chip edit input. */
9395
@ContentChild(MatChipEditInput) contentEditInput?: MatChipEditInput;
9496

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

97108
constructor(...args: unknown[]);
@@ -104,6 +115,7 @@ export class MatChipRow extends MatChip implements AfterViewInit {
104115
if (this._isEditing && !this._editStartPending) {
105116
this._onEditFinish();
106117
}
118+
this._alreadyFocused = false;
107119
});
108120
}
109121

@@ -135,6 +147,17 @@ export class MatChipRow extends MatChip implements AfterViewInit {
135147
}
136148
}
137149

150+
/** Sets _alreadyFocused (ahead of click) when chip already has focus. */
151+
_handleMouseDown(event: MouseEvent) {
152+
this._alreadyFocused = this._hasFocus();
153+
}
154+
155+
_handleClick(event: MouseEvent) {
156+
if (!this.disabled && this.editable && !this._isEditing && this._alreadyFocused) {
157+
this._startEditing(event);
158+
}
159+
}
160+
138161
_handleDoubleclick(event: MouseEvent) {
139162
if (!this.disabled && this.editable) {
140163
this._startEditing(event);

0 commit comments

Comments
 (0)