diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index c7de42141bf1..15b76e7bcb7a 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -912,6 +912,25 @@ describe('MatSelect', () => { expect(fixture.componentInstance.select.panelOpen).toBe(false); })); + it('should restore focus to the host before tabbing away', fakeAsync(() => { + const select = fixture.nativeElement.querySelector('.mat-select'); + + trigger.click(); + fixture.detectChanges(); + flush(); + + expect(fixture.componentInstance.select.panelOpen).toBe(true); + + // Use a spy since focus can be flaky in unit tests. + spyOn(select, 'focus').and.callThrough(); + + dispatchKeyboardEvent(trigger, 'keydown', TAB); + fixture.detectChanges(); + flush(); + + expect(select.focus).toHaveBeenCalled(); + })); + it('should close when tabbing out from inside the panel', fakeAsync(() => { trigger.click(); fixture.detectChanges(); diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index a37141ebfd2e..3d79d8bd75dd 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -826,7 +826,13 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, .withVerticalOrientation() .withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr'); - this._keyManager.tabOut.pipe(takeUntil(this._destroy)).subscribe(() => this.close()); + this._keyManager.tabOut.pipe(takeUntil(this._destroy)).subscribe(() => { + // Restore focus to the trigger before closing. Ensures that the focus + // position won't be lost if the user got focus into the overlay. + this.focus(); + this.close(); + }); + this._keyManager.change.pipe(takeUntil(this._destroy)).subscribe(() => { if (this._panelOpen && this.panel) { this._scrollActiveOptionIntoView();