Skip to content

Commit

Permalink
fix(autocomplete): reopening closed autocomplete when coming back to …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
crisbeto authored and jelbourn committed Aug 23, 2018
1 parent 2830a64 commit c2b488e
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 3 deletions.
37 changes: 34 additions & 3 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,28 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
/** Subscription to viewport size changes. */
private _viewportSubscription = Subscription.EMPTY;

/**
* Whether the autocomplete can open the next time it is focused. Used to prevent a focused,
* closed autocomplete from being reopened if the user switches to another browser tab and then
* comes back.
*/
private _canOpenOnNextFocus = true;

/** Stream of keyboard events that can close the panel. */
private readonly _closeKeyEventStream = new Subject<void>();

/**
* Event handler for when the window is blurred. Needs to be an
* arrow function in order to preserve the context.
*/
private _windowBlurHandler = () => {
// If the user blurred the window while the autocomplete is focused, it means that it'll be
// refocused when they come back. In this case we want to skip the first focus event, if the
// pane was closed, in order to avoid reopening it unintentionally.
this._canOpenOnNextFocus =
document.activeElement !== this._element.nativeElement || this.panelOpen;
}

/** `View -> model callback called when value changes` */
_onChange: (value: any) => void = () => {};

Expand Down Expand Up @@ -179,9 +198,20 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
@Optional() @Host() private _formField: MatFormField,
@Optional() @Inject(DOCUMENT) private _document: any,
// @breaking-change 7.0.0 Make `_viewportRuler` required.
private _viewportRuler?: ViewportRuler) {}
private _viewportRuler?: ViewportRuler) {

if (typeof window !== 'undefined') {
_zone.runOutsideAngular(() => {
window.addEventListener('blur', this._windowBlurHandler);
});
}
}

ngOnDestroy() {
if (typeof window !== 'undefined') {
window.removeEventListener('blur', this._windowBlurHandler);
}

this._viewportSubscription.unsubscribe();
this._componentDestroyed = true;
this._destroyPanel();
Expand Down Expand Up @@ -386,7 +416,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
}

_handleFocus(): void {
if (this._canOpen()) {
if (!this._canOpenOnNextFocus) {
this._canOpenOnNextFocus = true;
} else if (this._canOpen()) {
this._previousValue = this._element.nativeElement.value;
this._attachOverlay();
this._floatLabel(true);
Expand Down Expand Up @@ -633,5 +665,4 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
const element: HTMLInputElement = this._element.nativeElement;
return !element.readOnly && !element.disabled && !this._autocompleteDisabled;
}

}
30 changes: 30 additions & 0 deletions src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,36 @@ describe('MatAutocomplete', () => {
expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(500);
});

it('should not reopen a closed autocomplete when returning to a blurred tab', () => {
const fixture = createComponent(SimpleAutocomplete);
fixture.detectChanges();

const trigger = fixture.componentInstance.trigger;
const input = fixture.debugElement.query(By.css('input')).nativeElement;

input.focus();
fixture.detectChanges();

expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.');

trigger.closePanel();
fixture.detectChanges();

expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.');

// Simulate the user going to a different tab.
dispatchFakeEvent(window, 'blur');
input.blur();
fixture.detectChanges();

// Simulate the user coming back.
dispatchFakeEvent(window, 'focus');
input.focus();
fixture.detectChanges();

expect(trigger.panelOpen).toBe(false, 'Expected panel to remain closed.');
});

it('should update the panel width if the window is resized', fakeAsync(() => {
const widthFixture = createComponent(SimpleAutocomplete);

Expand Down

0 comments on commit c2b488e

Please sign in to comment.