Skip to content

Commit 8617423

Browse files
crisbetojelbourn
authored andcommitted
fix(autocomplete): reopening closed autocomplete when coming back to tab (#12372)
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 bb9cfec commit 8617423

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,28 @@ 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 browser 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

148+
/**
149+
* Event handler for when the window is blurred. Needs to be an
150+
* arrow function in order to preserve the context.
151+
*/
152+
private _windowBlurHandler = () => {
153+
// If the user blurred the window while the autocomplete is focused, it means that it'll be
154+
// refocused when they come back. In this case we want to skip the first focus event, if the
155+
// pane was closed, in order to avoid reopening it unintentionally.
156+
this._canOpenOnNextFocus =
157+
document.activeElement !== this._element.nativeElement || this.panelOpen;
158+
}
159+
141160
/** `View -> model callback called when value changes` */
142161
_onChange: (value: any) => void = () => {};
143162

@@ -178,9 +197,20 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
178197
@Optional() @Host() private _formField: MatFormField,
179198
@Optional() @Inject(DOCUMENT) private _document: any,
180199
// @breaking-change 7.0.0 Make `_viewportRuler` required.
181-
private _viewportRuler?: ViewportRuler) {}
200+
private _viewportRuler?: ViewportRuler) {
201+
202+
if (typeof window !== 'undefined') {
203+
_zone.runOutsideAngular(() => {
204+
window.addEventListener('blur', this._windowBlurHandler);
205+
});
206+
}
207+
}
182208

183209
ngOnDestroy() {
210+
if (typeof window !== 'undefined') {
211+
window.removeEventListener('blur', this._windowBlurHandler);
212+
}
213+
184214
this._viewportSubscription.unsubscribe();
185215
this._componentDestroyed = true;
186216
this._destroyPanel();
@@ -375,7 +405,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
375405
}
376406

377407
_handleFocus(): void {
378-
if (this._canOpen()) {
408+
if (!this._canOpenOnNextFocus) {
409+
this._canOpenOnNextFocus = true;
410+
} else if (this._canOpen()) {
379411
this._previousValue = this._element.nativeElement.value;
380412
this._attachOverlay();
381413
this._floatLabel(true);
@@ -612,5 +644,4 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
612644
const element: HTMLInputElement = this._element.nativeElement;
613645
return !element.readOnly && !element.disabled && !this._autocompleteDisabled;
614646
}
615-
616647
}

src/lib/autocomplete/autocomplete.spec.ts

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

1964+
it('should not reopen a closed autocomplete when returning to a blurred tab', () => {
1965+
const fixture = createComponent(SimpleAutocomplete);
1966+
fixture.detectChanges();
1967+
1968+
const trigger = fixture.componentInstance.trigger;
1969+
const input = fixture.debugElement.query(By.css('input')).nativeElement;
1970+
1971+
input.focus();
1972+
fixture.detectChanges();
1973+
1974+
expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.');
1975+
1976+
trigger.closePanel();
1977+
fixture.detectChanges();
1978+
1979+
expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.');
1980+
1981+
// Simulate the user going to a different tab.
1982+
dispatchFakeEvent(window, 'blur');
1983+
input.blur();
1984+
fixture.detectChanges();
1985+
1986+
// Simulate the user coming back.
1987+
dispatchFakeEvent(window, 'focus');
1988+
input.focus();
1989+
fixture.detectChanges();
1990+
1991+
expect(trigger.panelOpen).toBe(false, 'Expected panel to remain closed.');
1992+
});
1993+
19641994
it('should update the panel width if the window is resized', fakeAsync(() => {
19651995
const widthFixture = createComponent(SimpleAutocomplete);
19661996

0 commit comments

Comments
 (0)