Skip to content

Commit 032c317

Browse files
crisbetommalerba
authored andcommitted
fix(material/autocomplete): activate first enabled option with autoActiveFirstOption (#21513)
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. (cherry picked from commit f4436e2)
1 parent a0da1d3 commit 032c317

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
@@ -2013,6 +2013,21 @@ describe('MDC-based MatAutocomplete', () => {
20132013
.toContain('mat-mdc-option-active', 'Expected first option to be highlighted.');
20142014
}));
20152015

2016+
it('should skip to the next enabled option if the first one is disabled ' +
2017+
'when using `autoActiveFirstOption`', fakeAsync(() => {
2018+
const testComponent = fixture.componentInstance;
2019+
testComponent.trigger.autocomplete.autoActiveFirstOption = true;
2020+
testComponent.states[0].disabled = true;
2021+
testComponent.states[1].disabled = true;
2022+
testComponent.trigger.openPanel();
2023+
fixture.detectChanges();
2024+
zone.simulateZoneExit();
2025+
fixture.detectChanges();
2026+
2027+
expect(overlayContainerElement.querySelectorAll('mat-option')[2].classList)
2028+
.toContain('mat-mdc-option-active', 'Expected third option to be highlighted.');
2029+
}));
2030+
20162031
it('should remove aria-activedescendant when panel is closed with autoActiveFirstOption',
20172032
fakeAsync(() => {
20182033
const input: HTMLElement = fixture.nativeElement.querySelector('input');
@@ -2763,7 +2778,8 @@ const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
27632778
<mat-option
27642779
*ngFor="let state of filteredStates"
27652780
[value]="state"
2766-
[style.height.px]="state.height">
2781+
[style.height.px]="state.height"
2782+
[disabled]="state.disabled">
27672783
<span>{{ state.code }}: {{ state.name }}</span>
27682784
</mat-option>
27692785
</mat-autocomplete>
@@ -2790,7 +2806,7 @@ class SimpleAutocomplete implements OnDestroy {
27902806
@ViewChild(MatFormField) formField: MatFormField;
27912807
@ViewChildren(MatOption) options: QueryList<MatOption>;
27922808

2793-
states: {code: string, name: string, height?: number}[] = [
2809+
states: {code: string, name: string, height?: number, disabled?: boolean}[] = [
27942810
{code: 'AL', name: 'Alabama'},
27952811
{code: 'CA', name: 'California'},
27962812
{code: 'FL', name: 'Florida'},

src/material/autocomplete/autocomplete-trigger.ts

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

723731
/** 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
@@ -1997,6 +1997,21 @@ describe('MatAutocomplete', () => {
19971997
.toContain('mat-active', 'Expected first option to be highlighted.');
19981998
}));
19991999

2000+
it('should skip to the next enabled option if the first one is disabled ' +
2001+
'when using `autoActiveFirstOption`', fakeAsync(() => {
2002+
const testComponent = fixture.componentInstance;
2003+
testComponent.trigger.autocomplete.autoActiveFirstOption = true;
2004+
testComponent.states[0].disabled = true;
2005+
testComponent.states[1].disabled = true;
2006+
testComponent.trigger.openPanel();
2007+
fixture.detectChanges();
2008+
zone.simulateZoneExit();
2009+
fixture.detectChanges();
2010+
2011+
expect(overlayContainerElement.querySelectorAll('mat-option')[2].classList)
2012+
.toContain('mat-active', 'Expected third option to be highlighted.');
2013+
}));
2014+
20002015
it('should remove aria-activedescendant when panel is closed with autoActiveFirstOption',
20012016
fakeAsync(() => {
20022017
const input: HTMLElement = fixture.nativeElement.querySelector('input');
@@ -2760,7 +2775,8 @@ const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
27602775
<mat-option
27612776
*ngFor="let state of filteredStates"
27622777
[value]="state"
2763-
[style.height.px]="state.height">
2778+
[style.height.px]="state.height"
2779+
[disabled]="state.disabled">
27642780
<span>{{ state.code }}: {{ state.name }}</span>
27652781
</mat-option>
27662782
</mat-autocomplete>
@@ -2787,7 +2803,7 @@ class SimpleAutocomplete implements OnDestroy {
27872803
@ViewChild(MatFormField) formField: MatFormField;
27882804
@ViewChildren(MatOption) options: QueryList<MatOption>;
27892805

2790-
states: {code: string, name: string, height?: number}[] = [
2806+
states: {code: string, name: string, height?: number, disabled?: boolean}[] = [
27912807
{code: 'AL', name: 'Alabama'},
27922808
{code: 'CA', name: 'California'},
27932809
{code: 'FL', name: 'Florida'},

0 commit comments

Comments
 (0)