Skip to content

Commit 2cc60ae

Browse files
committed
fix(material/autocomplete): activate first enabled option with autoActiveFirstOption
Currently the autocomplete trigger will activate the first option when `autoActiveFirstOption`, no matter whether it is disabled. These changes use a different API which will allow us to pick the first enabled option. Fixes #21498.
1 parent 27e60e8 commit 2cc60ae

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,21 @@ describe('MDC-based MatAutocomplete', () => {
19941994
.toContain('mat-mdc-option-active', 'Expected first option to be highlighted.');
19951995
}));
19961996

1997+
it('should skip to the next enabled option if the first one is disabled ' +
1998+
'when using `autoActiveFirstOption`', fakeAsync(() => {
1999+
const testComponent = fixture.componentInstance;
2000+
testComponent.trigger.autocomplete.autoActiveFirstOption = true;
2001+
testComponent.states[0].disabled = true;
2002+
testComponent.states[1].disabled = true;
2003+
testComponent.trigger.openPanel();
2004+
fixture.detectChanges();
2005+
zone.simulateZoneExit();
2006+
fixture.detectChanges();
2007+
2008+
expect(overlayContainerElement.querySelectorAll('mat-option')[2].classList)
2009+
.toContain('mat-mdc-option-active', 'Expected third option to be highlighted.');
2010+
}));
2011+
19972012
it('should remove aria-activedescendant when panel is closed with autoActiveFirstOption',
19982013
fakeAsync(() => {
19992014
const input: HTMLElement = fixture.nativeElement.querySelector('input');
@@ -2744,7 +2759,8 @@ const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
27442759
<mat-option
27452760
*ngFor="let state of filteredStates"
27462761
[value]="state"
2747-
[style.height.px]="state.height">
2762+
[style.height.px]="state.height"
2763+
[disabled]="state.disabled">
27482764
<span>{{ state.code }}: {{ state.name }}</span>
27492765
</mat-option>
27502766
</mat-autocomplete>
@@ -2771,7 +2787,7 @@ class SimpleAutocomplete implements OnDestroy {
27712787
@ViewChild(MatFormField) formField: MatFormField;
27722788
@ViewChildren(MatOption) options: QueryList<MatOption>;
27732789

2774-
states: {code: string, name: string, height?: number}[] = [
2790+
states: {code: string, name: string, height?: number, disabled?: boolean}[] = [
27752791
{code: 'AL', name: 'Alabama'},
27762792
{code: 'CA', name: 'California'},
27772793
{code: 'FL', name: 'Florida'},

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,15 @@ export abstract class _MatAutocompleteTriggerBase implements ControlValueAccesso
716716
* correct options, or to 0 if the consumer opted into it.
717717
*/
718718
private _resetActiveItem(): void {
719-
this.autocomplete._keyManager.setActiveItem(this.autocomplete.autoActiveFirstOption ? 0 : -1);
719+
const autocomplete = this.autocomplete;
720+
721+
if (autocomplete.autoActiveFirstOption) {
722+
// Note that we go through `setFirstItemActive`, rather than `setActiveItem(0)`, because
723+
// the former will find the next enabled option, if the first one is disabled.
724+
autocomplete._keyManager.setFirstItemActive();
725+
} else {
726+
autocomplete._keyManager.setActiveItem(-1);
727+
}
720728
}
721729

722730
/** Determines whether the panel can be opened. */

src/material/autocomplete/autocomplete.spec.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,21 @@ describe('MatAutocomplete', () => {
19851985
.toContain('mat-active', 'Expected first option to be highlighted.');
19861986
}));
19871987

1988+
it('should skip to the next enabled option if the first one is disabled ' +
1989+
'when using `autoActiveFirstOption`', fakeAsync(() => {
1990+
const testComponent = fixture.componentInstance;
1991+
testComponent.trigger.autocomplete.autoActiveFirstOption = true;
1992+
testComponent.states[0].disabled = true;
1993+
testComponent.states[1].disabled = true;
1994+
testComponent.trigger.openPanel();
1995+
fixture.detectChanges();
1996+
zone.simulateZoneExit();
1997+
fixture.detectChanges();
1998+
1999+
expect(overlayContainerElement.querySelectorAll('mat-option')[2].classList)
2000+
.toContain('mat-active', 'Expected third option to be highlighted.');
2001+
}));
2002+
19882003
it('should remove aria-activedescendant when panel is closed with autoActiveFirstOption',
19892004
fakeAsync(() => {
19902005
const input: HTMLElement = fixture.nativeElement.querySelector('input');
@@ -2748,7 +2763,8 @@ const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
27482763
<mat-option
27492764
*ngFor="let state of filteredStates"
27502765
[value]="state"
2751-
[style.height.px]="state.height">
2766+
[style.height.px]="state.height"
2767+
[disabled]="state.disabled">
27522768
<span>{{ state.code }}: {{ state.name }}</span>
27532769
</mat-option>
27542770
</mat-autocomplete>
@@ -2775,7 +2791,7 @@ class SimpleAutocomplete implements OnDestroy {
27752791
@ViewChild(MatFormField) formField: MatFormField;
27762792
@ViewChildren(MatOption) options: QueryList<MatOption>;
27772793

2778-
states: {code: string, name: string, height?: number}[] = [
2794+
states: {code: string, name: string, height?: number, disabled?: boolean}[] = [
27792795
{code: 'AL', name: 'Alabama'},
27802796
{code: 'CA', name: 'California'},
27812797
{code: 'FL', name: 'Florida'},

0 commit comments

Comments
 (0)