Skip to content

Commit a2bab41

Browse files
committed
fix(autocomplete): reopening closed autocomplete when coming back to tab
Fixes a closed autocomplete being reopened, if the user moves to another tab and coming back to the current one, while the input is still focused. Fixes #12337.
1 parent e23709f commit a2bab41

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
135135
/** Subscription to viewport size changes. */
136136
private _viewportSubscription = Subscription.EMPTY;
137137

138+
/**
139+
* Whether the autocomplete can open the next time it is focused. Used to prevent a focused,
140+
* closed autocomplete from being reopened if the user switches to another tab and then
141+
* comes back.
142+
*/
143+
private _canOpenOnNextFocus = true;
144+
138145
/** Stream of keyboard events that can close the panel. */
139146
private readonly _closeKeyEventStream = new Subject<void>();
140147

@@ -178,9 +185,20 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
178185
@Optional() @Host() private _formField: MatFormField,
179186
@Optional() @Inject(DOCUMENT) private _document: any,
180187
// @deletion-target 7.0.0 Make `_viewportRuler` required.
181-
private _viewportRuler?: ViewportRuler) {}
188+
private _viewportRuler?: ViewportRuler) {
189+
190+
if (typeof window !== 'undefined') {
191+
_zone.runOutsideAngular(() => {
192+
window.addEventListener('blur', this._windowBlurHandler);
193+
});
194+
}
195+
}
182196

183197
ngOnDestroy() {
198+
if (typeof window !== 'undefined') {
199+
window.removeEventListener('blur', this._windowBlurHandler);
200+
}
201+
184202
this._viewportSubscription.unsubscribe();
185203
this._componentDestroyed = true;
186204
this._destroyPanel();
@@ -375,7 +393,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
375393
}
376394

377395
_handleFocus(): void {
378-
if (this._canOpen()) {
396+
if (!this._canOpenOnNextFocus) {
397+
this._canOpenOnNextFocus = true;
398+
} else if (this._canOpen()) {
379399
this._previousValue = this._element.nativeElement.value;
380400
this._attachOverlay();
381401
this._floatLabel(true);
@@ -613,4 +633,12 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
613633
return !element.readOnly && !element.disabled && !this._autocompleteDisabled;
614634
}
615635

636+
/**
637+
* Event handler for when the window is blurred. Needs to be an
638+
* arrow function in order to preserve the context.
639+
*/
640+
private _windowBlurHandler = () => {
641+
this._canOpenOnNextFocus =
642+
document.activeElement !== this._element.nativeElement || this.panelOpen;
643+
}
616644
}

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,6 +1952,36 @@ describe('MatAutocomplete', () => {
19521952
expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(500);
19531953
});
19541954

1955+
it('should not reopen a closed autocomplete when returning to a blurred tab', () => {
1956+
const fixture = createComponent(SimpleAutocomplete);
1957+
fixture.detectChanges();
1958+
1959+
const trigger = fixture.componentInstance.trigger;
1960+
const input = fixture.debugElement.query(By.css('input')).nativeElement;
1961+
1962+
input.focus();
1963+
fixture.detectChanges();
1964+
1965+
expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.');
1966+
1967+
trigger.closePanel();
1968+
fixture.detectChanges();
1969+
1970+
expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.');
1971+
1972+
// Simulate the user going to a different tab.
1973+
dispatchFakeEvent(window, 'blur');
1974+
input.blur();
1975+
fixture.detectChanges();
1976+
1977+
// Simulate the user coming back.
1978+
dispatchFakeEvent(window, 'focus');
1979+
input.focus();
1980+
fixture.detectChanges();
1981+
1982+
expect(trigger.panelOpen).toBe(false, 'Expected panel to remain closed.');
1983+
});
1984+
19551985
it('should update the panel width if the window is resized', fakeAsync(() => {
19561986
const widthFixture = createComponent(SimpleAutocomplete);
19571987

0 commit comments

Comments
 (0)