diff --git a/src/material/chips/chip-grid.spec.ts b/src/material/chips/chip-grid.spec.ts index f6804835a64f..2fae033dcc22 100644 --- a/src/material/chips/chip-grid.spec.ts +++ b/src/material/chips/chip-grid.spec.ts @@ -180,6 +180,25 @@ describe('MatChipGrid', () => { expect(chipGridNativeElement.getAttribute('tabindex')).toBe('-1'); }); + + it('should clear the active item in key manager when the last focused chip is destroyed', fakeAsync(() => { + const fixture = createComponent(StandardChipGrid); + fixture.detectChanges(); + + // Focus a chip + chips.first.focus(); + fixture.detectChanges(); + + // Remove ALL chips + fixture.componentInstance.foods = []; // Clear the bound data array + fixture.detectChanges(); + flush(); + + // Verify key manager is reset to prevent stale references + expect(chipGridInstance._keyManager.activeItemIndex).toBe(-1); + })); + + describe('on chip destroy', () => { it('should focus the next item', () => { const fixture = createComponent(StandardChipGrid); diff --git a/src/material/chips/chip-grid.ts b/src/material/chips/chip-grid.ts index 828e2e7ea779..4c4befd4d818 100644 --- a/src/material/chips/chip-grid.ts +++ b/src/material/chips/chip-grid.ts @@ -492,6 +492,21 @@ export class MatChipGrid this.stateChanges.next(); } + protected override _redirectDestroyedChipFocus() { + if (this._lastDestroyedFocusedChipIndex === null) { + return; + } + + super._redirectDestroyedChipFocus(); + + // If there are no chips left, or the set focuses the input, + // clear the active item silently to prevent stale references. + if (!this._chips.length || + (this._chips.length === 1 && this._chips.first.disabled)) { + this._keyManager.updateActiveItem(-1); + } + } + _focusLastChip() { if (this._chips.length) { this._chips.last.focus(); diff --git a/src/material/chips/chip-set.ts b/src/material/chips/chip-set.ts index d0e6e16136d4..b0682239d1d5 100644 --- a/src/material/chips/chip-set.ts +++ b/src/material/chips/chip-set.ts @@ -53,7 +53,7 @@ export class MatChipSet implements AfterViewInit, OnDestroy { private _dir = inject(Directionality, {optional: true}); /** Index of the last destroyed chip that had focus. */ - private _lastDestroyedFocusedChipIndex: number | null = null; + protected _lastDestroyedFocusedChipIndex: number | null = null; /** Used to manage focus within the chip list. */ protected _keyManager!: FocusKeyManager; @@ -310,7 +310,7 @@ export class MatChipSet implements AfterViewInit, OnDestroy { * Finds the next appropriate chip to move focus to, * if the currently-focused chip is destroyed. */ - private _redirectDestroyedChipFocus() { + protected _redirectDestroyedChipFocus() { if (this._lastDestroyedFocusedChipIndex == null) { return; }