Skip to content

Commit 8e17112

Browse files
committed
fix(material/chips): focus escape not working consistently
Fixes that the chip grid was using change detection to allow focus to escape on tab presses. That's unreliable, because change detection might not be executed immediately. These changes fix the issue by changing the DOM node directly.
1 parent 2861a30 commit 8e17112

File tree

3 files changed

+19
-19
lines changed

3 files changed

+19
-19
lines changed

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -396,37 +396,37 @@ describe('MatChipGrid', () => {
396396

397397
dispatchKeyboardEvent(firstNativeChip, 'keydown', TAB);
398398

399-
expect(chipGridInstance.tabIndex)
399+
expect(chipGridNativeElement.tabIndex)
400400
.withContext('Expected tabIndex to be set to -1 temporarily.')
401401
.toBe(-1);
402402

403403
flush();
404404

405-
expect(chipGridInstance.tabIndex)
405+
expect(chipGridNativeElement.tabIndex)
406406
.withContext('Expected tabIndex to be reset back to 0')
407407
.toBe(0);
408408
}));
409409

410-
it(`should use user defined tabIndex`, fakeAsync(() => {
410+
it('should use user defined tabIndex', fakeAsync(() => {
411411
chipGridInstance.tabIndex = 4;
412412
fixture.changeDetectorRef.markForCheck();
413413
fixture.detectChanges();
414414

415-
expect(chipGridInstance.tabIndex)
415+
expect(chipGridNativeElement.tabIndex)
416416
.withContext('Expected tabIndex to be set to user defined value 4.')
417417
.toBe(4);
418418

419419
let nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row');
420420
let firstNativeChip = nativeChips[0] as HTMLElement;
421421

422422
dispatchKeyboardEvent(firstNativeChip, 'keydown', TAB);
423-
expect(chipGridInstance.tabIndex)
423+
expect(chipGridNativeElement.tabIndex)
424424
.withContext('Expected tabIndex to be set to -1 temporarily.')
425425
.toBe(-1);
426426

427427
flush();
428428

429-
expect(chipGridInstance.tabIndex)
429+
expect(chipGridNativeElement.tabIndex)
430430
.withContext('Expected tabIndex to be reset back to 4')
431431
.toBe(4);
432432
}));

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -367,13 +367,13 @@ describe('MatChipListbox', () => {
367367
it('should allow focus to escape when tabbing away', fakeAsync(() => {
368368
dispatchKeyboardEvent(chipListboxNativeElement, 'keydown', TAB);
369369

370-
expect(chipListboxInstance.tabIndex)
370+
expect(chipListboxNativeElement.tabIndex)
371371
.withContext('Expected tabIndex to be set to -1 temporarily.')
372372
.toBe(-1);
373373

374374
flush();
375375

376-
expect(chipListboxInstance.tabIndex)
376+
expect(chipListboxNativeElement.tabIndex)
377377
.withContext('Expected tabIndex to be reset back to 0')
378378
.toBe(0);
379379
}));
@@ -384,19 +384,19 @@ describe('MatChipListbox', () => {
384384

385385
fixture.detectChanges();
386386

387-
expect(chipListboxInstance.tabIndex)
387+
expect(chipListboxNativeElement.tabIndex)
388388
.withContext('Expected tabIndex to be set to user defined value 4.')
389389
.toBe(4);
390390

391391
dispatchKeyboardEvent(chipListboxNativeElement, 'keydown', TAB);
392392

393-
expect(chipListboxInstance.tabIndex)
393+
expect(chipListboxNativeElement.tabIndex)
394394
.withContext('Expected tabIndex to be set to -1 temporarily.')
395395
.toBe(-1);
396396

397397
flush();
398398

399-
expect(chipListboxInstance.tabIndex)
399+
expect(chipListboxNativeElement.tabIndex)
400400
.withContext('Expected tabIndex to be reset back to 4')
401401
.toBe(4);
402402
}));

src/material/chips/chip-set.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,17 @@ export class MatChipSet implements AfterViewInit, OnDestroy {
192192
* it back to the first chip, creating a focus trap, if it user tries to tab away.
193193
*/
194194
protected _allowFocusEscape() {
195-
if (this.tabIndex !== -1) {
196-
const previousTabIndex = this.tabIndex;
197-
this.tabIndex = -1;
198-
this._changeDetectorRef.markForCheck();
195+
const previous = this._elementRef.nativeElement.tabIndex;
196+
197+
if (previous !== -1) {
198+
// Set the tabindex directly on the element, instead of going through
199+
// the data binding, because we aren't guaranteed that change detection
200+
// will run quickly enough to allow focus to escape.
201+
this._elementRef.nativeElement.tabIndex = -1;
199202

200203
// Note that this needs to be a `setTimeout`, because a `Promise.resolve`
201204
// doesn't allow enough time for the focus to escape.
202-
setTimeout(() => {
203-
this.tabIndex = previousTabIndex;
204-
this._changeDetectorRef.markForCheck();
205-
});
205+
setTimeout(() => (this._elementRef.nativeElement.tabIndex = previous));
206206
}
207207
}
208208

0 commit comments

Comments
 (0)