From 55eeee35163d5392703e02f8de044598e74cc6f5 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 13 Feb 2024 22:40:01 +0100 Subject: [PATCH] fix(cdk/listbox): unable to tab in if active option is removed (#28583) Fixes that the listbox didn't allow focus back in when its active option is removed. Fixes #28557. (cherry picked from commit d766b14b1ae2820dc189870de40b06ba66891bd7) --- src/cdk/listbox/listbox.spec.ts | 35 +++++++++++++++++++++++++++------ src/cdk/listbox/listbox.ts | 11 +++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/cdk/listbox/listbox.spec.ts b/src/cdk/listbox/listbox.spec.ts index a7a2c9837ce1..e8eef7b82dc5 100644 --- a/src/cdk/listbox/listbox.spec.ts +++ b/src/cdk/listbox/listbox.spec.ts @@ -104,6 +104,26 @@ describe('CdkOption and CdkListbox', () => { expect(optionEls[0].getAttribute('tabindex')).toBe('10'); }); + + it('should reset the tabindex if the active option is destroyed', () => { + const {fixture, listbox, listboxEl} = setupComponent(ListboxWithOptions); + let options = fixture.nativeElement.querySelectorAll('.cdk-option'); + expect(listboxEl.getAttribute('tabindex')).toBe('0'); + expect(options[0].getAttribute('tabindex')).toBe('-1'); + + listbox.focus(); + fixture.detectChanges(); + + expect(listboxEl.getAttribute('tabindex')).toBe('-1'); + expect(options[0].getAttribute('tabindex')).toBe('0'); + + fixture.componentInstance.appleRendered = false; + fixture.detectChanges(); + options = fixture.nativeElement.querySelectorAll('.cdk-option'); + + expect(listboxEl.getAttribute('tabindex')).toBe('0'); + expect(options[0].getAttribute('tabindex')).toBe('-1'); + }); }); describe('selection', () => { @@ -943,12 +963,14 @@ describe('CdkOption and CdkListbox', () => { [cdkListboxNavigatesDisabledOptions]="!navigationSkipsDisabled" [cdkListboxValue]="selectedValue" (cdkListboxValueChange)="onSelectionChange($event)"> -
- Apple -
+ @if (appleRendered) { +
+ Apple +
+ }
Orange
Banana
@@ -965,6 +987,7 @@ class ListboxWithOptions { isActiveDescendant = false; navigationWraps = true; navigationSkipsDisabled = true; + appleRendered = true; listboxId: string; listboxTabindex: number; appleId: string; diff --git a/src/cdk/listbox/listbox.ts b/src/cdk/listbox/listbox.ts index a0de8ec65c6b..fa6782e10b52 100644 --- a/src/cdk/listbox/listbox.ts +++ b/src/cdk/listbox/listbox.ts @@ -831,6 +831,17 @@ export class CdkListbox implements AfterContentInit, OnDestroy, Con } this.listKeyManager.change.subscribe(() => this._focusActiveOption()); + + this.options.changes.pipe(takeUntil(this.destroyed)).subscribe(() => { + const activeOption = this.listKeyManager.activeItem; + + // If the active option was deleted, we need to reset + // the key manager so it can allow focus back in. + if (activeOption && !this.options.find(option => option === activeOption)) { + this.listKeyManager.setActiveItem(-1); + this.changeDetectorRef.markForCheck(); + } + }); } /** Focus the active option. */