diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index dd032e9f6625..a2346b4df5c3 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -47,7 +47,7 @@ import { } from '@angular/material/core'; import {MatFormField} from '@angular/material/form-field'; import {defer, fromEvent, merge, Observable, of as observableOf, Subject, Subscription} from 'rxjs'; -import {delay, filter, map, switchMap, take, tap} from 'rxjs/operators'; +import {delay, filter, map, switchMap, take, tap, takeUntil} from 'rxjs/operators'; import {MatAutocomplete} from './autocomplete'; import {MatAutocompleteOrigin} from './autocomplete-origin'; @@ -116,7 +116,7 @@ export function getMatAutocompleteMissingPanelError(): Error { // Note: we use `focusin`, as opposed to `focus`, in order to open the panel // a little earlier. This avoids issues where IE delays the focusing of the input. '(focusin)': '_handleFocus()', - '(blur)': '_onTouched()', + '(blur)': '_handleBlur()', '(input)': '_handleInput($event)', '(keydown)': '_handleKeydown($event)', }, @@ -460,6 +460,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn } } + _handleBlur(): void { + if (this.panelOpen) { + this.autocomplete.closed + .pipe(take(1), takeUntil(this.autocomplete.opened.asObservable())) + .subscribe(() => this._onTouched()); + } else { + this._onTouched(); + } + } + /** * In "auto" mode, the label will animate down as soon as focus is lost. * This causes the value to jump when selecting an option with the mouse. diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index 3f2e27c691d7..5496468d652e 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -814,15 +814,48 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); expect(fixture.componentInstance.stateCtrl.touched) - .toBe(false, `Expected control to start out untouched.`); + .toBe(false, 'Expected control to start out untouched.'); dispatchFakeEvent(input, 'blur'); fixture.detectChanges(); + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); expect(fixture.componentInstance.stateCtrl.touched) - .toBe(true, `Expected control to become touched on blur.`); + .toBe(true, 'Expected control to become touched on blur.'); }); + it('should mark the autocomplete control as touched on blur if the panel is closed', () => { + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(false, 'Expected control to start out untouched.'); + + dispatchFakeEvent(input, 'blur'); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(true, 'Expected control to become touched on blur.'); + }); + + it('should not mark the autocomplete control as touched if the input was blurred while ' + + 'the panel is open', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(false, 'Expected control to start out untouched.'); + + dispatchFakeEvent(input, 'blur'); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(false, 'Expected control to remain untouched.'); + + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(true, 'Expected control to be touched once the panel is closed.'); + }); + it('should disable the input when used with a value accessor and without `matInput`', () => { overlayContainer.ngOnDestroy(); fixture.destroy(); diff --git a/tools/public_api_guard/material/autocomplete.d.ts b/tools/public_api_guard/material/autocomplete.d.ts index 99e734c8d3e9..73512a887f83 100644 --- a/tools/public_api_guard/material/autocomplete.d.ts +++ b/tools/public_api_guard/material/autocomplete.d.ts @@ -82,6 +82,7 @@ export declare class MatAutocompleteTrigger implements ControlValueAccessor, Aft readonly panelOpen: boolean; position: 'auto' | 'above' | 'below'; constructor(_element: ElementRef, _overlay: Overlay, _viewContainerRef: ViewContainerRef, _zone: NgZone, _changeDetectorRef: ChangeDetectorRef, scrollStrategy: any, _dir: Directionality, _formField: MatFormField, _document: any, _viewportRuler?: ViewportRuler | undefined); + _handleBlur(): void; _handleFocus(): void; _handleInput(event: KeyboardEvent): void; _handleKeydown(event: KeyboardEvent): void;