Skip to content

Commit 1f088c1

Browse files
committed
fix(a11y): clear active item in key manager if it is removed from the list
The active item in the `ListKeyManager` gets updated on keyboard events, but if the list changes between them, we may end up in a state where it's pointing to an item that's not in the DOM anymore. This can cause something like `aria-activedescendant` to point to an invalid element. These changes add some logic that clear the active element if it's removed from the list and there's nothing at its new index.
1 parent 49de56c commit 1f088c1

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

src/cdk/a11y/key-manager/list-key-manager.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,42 @@ describe('Key managers', () => {
105105
expect(keyManager.activeItem!.getLabel()).toBe('one');
106106
});
107107

108+
it('should update the active item if the current one is removed and there is ' +
109+
'a new one at the same index', () => {
110+
expect(keyManager.activeItemIndex).toBe(0);
111+
expect(keyManager.activeItem!.getLabel()).toBe('one');
112+
113+
itemList.items = [new FakeFocusable('new-0'), new FakeFocusable('new-1')];
114+
itemList.notifyOnChanges();
115+
116+
expect(keyManager.activeItemIndex).toBe(0);
117+
expect(keyManager.activeItem!.getLabel()).toBe('new-0');
118+
});
119+
120+
it('should clear the active item if nothing exists at the new index', () => {
121+
keyManager.setActiveItem(2);
122+
123+
expect(keyManager.activeItemIndex).toBe(2);
124+
expect(keyManager.activeItem!.getLabel()).toBe('three');
125+
126+
itemList.items = itemList.items.slice(0, 1);
127+
itemList.notifyOnChanges();
128+
129+
expect(keyManager.activeItemIndex).toBe(-1);
130+
expect(keyManager.activeItem).toBe(null);
131+
});
132+
133+
it('should clear the active item if the list is cleared', () => {
134+
expect(keyManager.activeItemIndex).toBe(0);
135+
expect(keyManager.activeItem!.getLabel()).toBe('one');
136+
137+
itemList.items = [];
138+
itemList.notifyOnChanges();
139+
140+
expect(keyManager.activeItemIndex).toBe(-1);
141+
expect(keyManager.activeItem).toBe(null);
142+
});
143+
108144
it('should start off the activeItem as null', () => {
109145
expect(new ListKeyManager([]).activeItem).toBeNull();
110146
});

src/cdk/a11y/key-manager/list-key-manager.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
7070
const itemArray = newItems.toArray();
7171
const newIndex = itemArray.indexOf(this._activeItem);
7272

73-
if (newIndex > -1 && newIndex !== this._activeItemIndex) {
73+
if (newIndex === -1) {
74+
this.updateActiveItem(this._activeItemIndex);
75+
} else if (newIndex !== this._activeItemIndex) {
7476
this._activeItemIndex = newIndex;
7577
}
7678
}
@@ -347,7 +349,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
347349

348350
// Explicitly check for `null` and `undefined` because other falsy values are valid.
349351
this._activeItem = activeItem == null ? null : activeItem;
350-
this._activeItemIndex = index;
352+
this._activeItemIndex = activeItem == null ? -1 : index;
351353
}
352354

353355
/**

0 commit comments

Comments
 (0)