Skip to content

Commit 8806d31

Browse files
committed
fix(material/autocomplete): mark control as touched once panel is closed
Currently we mark the autocomplete control as touched on `blur`. The problem is that the `blur` event happens a split second before the panel is closed which can cause the error styling to show up and disappear quickly which looks glitchy. These changes change it so that the control is marked as touched once the panel is closed. Also makes a couple of underscored properties private since they weren't used anywhere in the view. Fixes #18313.
1 parent b90d942 commit 8806d31

File tree

5 files changed

+129
-6
lines changed

5 files changed

+129
-6
lines changed

src/material-experimental/mdc-autocomplete/autocomplete-trigger.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
3838
// Note: we use `focusin`, as opposed to `focus`, in order to open the panel
3939
// a little earlier. This avoids issues where IE delays the focusing of the input.
4040
'(focusin)': '_handleFocus()',
41-
'(blur)': '_onTouched()',
41+
'(blur)': '_handleBlur($event)',
4242
'(input)': '_handleInput($event)',
4343
'(keydown)': '_handleKeydown($event)',
4444
'(click)': '_handleClick()',

src/material-experimental/mdc-autocomplete/autocomplete.spec.ts

+61-2
Original file line numberDiff line numberDiff line change
@@ -917,8 +917,6 @@ describe('MDC-based MatAutocomplete', () => {
917917
});
918918

919919
it('should mark the autocomplete control as touched on blur', () => {
920-
fixture.componentInstance.trigger.openPanel();
921-
fixture.detectChanges();
922920
expect(fixture.componentInstance.stateCtrl.touched)
923921
.withContext(`Expected control to start out untouched.`)
924922
.toBe(false);
@@ -931,6 +929,67 @@ describe('MDC-based MatAutocomplete', () => {
931929
.toBe(true);
932930
});
933931

932+
it('should mark the autocomplete control as touched when the panel is closed via the keyboard', fakeAsync(() => {
933+
fixture.componentInstance.trigger.openPanel();
934+
fixture.detectChanges();
935+
zone.simulateZoneExit();
936+
937+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
938+
false,
939+
`Expected control to start out untouched.`,
940+
);
941+
942+
dispatchKeyboardEvent(input, 'keydown', TAB);
943+
fixture.detectChanges();
944+
945+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
946+
true,
947+
`Expected control to become touched on blur.`,
948+
);
949+
}));
950+
951+
it('should mark the autocomplete control as touched when the panel is closed by clicking away', fakeAsync(() => {
952+
fixture.componentInstance.trigger.openPanel();
953+
fixture.detectChanges();
954+
zone.simulateZoneExit();
955+
956+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
957+
false,
958+
`Expected control to start out untouched.`,
959+
);
960+
961+
dispatchFakeEvent(document, 'click');
962+
fixture.detectChanges();
963+
964+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
965+
true,
966+
`Expected control to become touched on blur.`,
967+
);
968+
}));
969+
970+
it(
971+
'should not mark the autocomplete control as touched when the panel is closed ' +
972+
'programmatically',
973+
fakeAsync(() => {
974+
fixture.componentInstance.trigger.openPanel();
975+
fixture.detectChanges();
976+
zone.simulateZoneExit();
977+
978+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
979+
false,
980+
`Expected control to start out untouched.`,
981+
);
982+
983+
fixture.componentInstance.trigger.closePanel();
984+
fixture.detectChanges();
985+
986+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
987+
false,
988+
`Expected control to stay untouched.`,
989+
);
990+
}),
991+
);
992+
934993
it('should disable the input when used with a value accessor and without `matInput`', () => {
935994
fixture.destroy();
936995
TestBed.resetTestingModule();

src/material/autocomplete/autocomplete-trigger.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,12 @@ export abstract class _MatAutocompleteTriggerBase
480480
}
481481
}
482482

483+
_handleBlur(event: FocusEvent) {
484+
if (!this.panelOpen || !event.isTrusted) {
485+
this._onTouched();
486+
}
487+
}
488+
483489
/**
484490
* In "auto" mode, the label will animate down as soon as focus is lost.
485491
* This causes the value to jump when selecting an option with the mouse.
@@ -600,6 +606,7 @@ export abstract class _MatAutocompleteTriggerBase
600606
this._element.nativeElement.focus();
601607
}
602608

609+
this._onTouched();
603610
this.closePanel();
604611
}
605612

@@ -835,8 +842,8 @@ export abstract class _MatAutocompleteTriggerBase
835842
// Note: we use `focusin`, as opposed to `focus`, in order to open the panel
836843
// a little earlier. This avoids issues where IE delays the focusing of the input.
837844
'(focusin)': '_handleFocus()',
838-
'(blur)': '_onTouched()',
839845
'(input)': '_handleInput($event)',
846+
'(blur)': '_handleBlur($event)',
840847
'(keydown)': '_handleKeydown($event)',
841848
'(click)': '_handleClick()',
842849
},

src/material/autocomplete/autocomplete.spec.ts

+57-2
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,6 @@ describe('MatAutocomplete', () => {
912912
});
913913

914914
it('should mark the autocomplete control as touched on blur', () => {
915-
fixture.componentInstance.trigger.openPanel();
916-
fixture.detectChanges();
917915
expect(fixture.componentInstance.stateCtrl.touched)
918916
.withContext(`Expected control to start out untouched.`)
919917
.toBe(false);
@@ -926,6 +924,63 @@ describe('MatAutocomplete', () => {
926924
.toBe(true);
927925
});
928926

927+
it('should mark the autocomplete control as touched when the panel is closed via the keyboard', fakeAsync(() => {
928+
fixture.componentInstance.trigger.openPanel();
929+
fixture.detectChanges();
930+
zone.simulateZoneExit();
931+
932+
expect(fixture.componentInstance.stateCtrl.touched)
933+
.withContext(`Expected control to start out untouched.`)
934+
.toBe(false);
935+
936+
dispatchKeyboardEvent(input, 'keydown', TAB);
937+
fixture.detectChanges();
938+
939+
expect(fixture.componentInstance.stateCtrl.touched)
940+
.withContext(`Expected control to become touched on blur.`)
941+
.toBe(true);
942+
}));
943+
944+
it('should mark the autocomplete control as touched when the panel is closed by clicking away', fakeAsync(() => {
945+
fixture.componentInstance.trigger.openPanel();
946+
fixture.detectChanges();
947+
zone.simulateZoneExit();
948+
949+
expect(fixture.componentInstance.stateCtrl.touched)
950+
.withContext(`Expected control to start out untouched.`)
951+
.toBe(false);
952+
953+
dispatchFakeEvent(document, 'click');
954+
fixture.detectChanges();
955+
956+
expect(fixture.componentInstance.stateCtrl.touched)
957+
.withContext(`Expected control to become touched on blur.`)
958+
.toBe(true);
959+
}));
960+
961+
it(
962+
'should not mark the autocomplete control as touched when the panel is closed ' +
963+
'programmatically',
964+
fakeAsync(() => {
965+
fixture.componentInstance.trigger.openPanel();
966+
fixture.detectChanges();
967+
zone.simulateZoneExit();
968+
969+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
970+
false,
971+
`Expected control to start out untouched.`,
972+
);
973+
974+
fixture.componentInstance.trigger.closePanel();
975+
fixture.detectChanges();
976+
977+
expect(fixture.componentInstance.stateCtrl.touched).toBe(
978+
false,
979+
`Expected control to stay untouched.`,
980+
);
981+
}),
982+
);
983+
929984
it('should disable the input when used with a value accessor and without `matInput`', () => {
930985
overlayContainer.ngOnDestroy();
931986
fixture.destroy();

tools/public_api_guard/material/autocomplete.md

+2
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ export abstract class _MatAutocompleteTriggerBase implements ControlValueAccesso
201201
closePanel(): void;
202202
connectedTo: _MatAutocompleteOriginBase;
203203
// (undocumented)
204+
_handleBlur(event: FocusEvent): void;
205+
// (undocumented)
204206
_handleClick(): void;
205207
// (undocumented)
206208
_handleFocus(): void;

0 commit comments

Comments
 (0)