Skip to content

Commit e97d4b1

Browse files
committed
fix(cdk/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 2d79ab7 commit e97d4b1

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

Diff for: src/cdk/a11y/key-manager/list-key-manager.spec.ts

+36
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,42 @@ describe('Key managers', () => {
9393
expect(keyManager.activeItem!.getLabel()).toBe('one');
9494
});
9595

96+
it('should update the active item if the current one is removed and there is ' +
97+
'a new one at the same index', () => {
98+
expect(keyManager.activeItemIndex).toBe(0);
99+
expect(keyManager.activeItem!.getLabel()).toBe('one');
100+
101+
itemList.items = [new FakeFocusable('new-0'), new FakeFocusable('new-1')];
102+
itemList.notifyOnChanges();
103+
104+
expect(keyManager.activeItemIndex).toBe(0);
105+
expect(keyManager.activeItem!.getLabel()).toBe('new-0');
106+
});
107+
108+
it('should clear the active item if nothing exists at the new index', () => {
109+
keyManager.setActiveItem(2);
110+
111+
expect(keyManager.activeItemIndex).toBe(2);
112+
expect(keyManager.activeItem!.getLabel()).toBe('three');
113+
114+
itemList.items = itemList.items.slice(0, 1);
115+
itemList.notifyOnChanges();
116+
117+
expect(keyManager.activeItemIndex).toBe(-1);
118+
expect(keyManager.activeItem).toBe(null);
119+
});
120+
121+
it('should clear the active item if the list is cleared', () => {
122+
expect(keyManager.activeItemIndex).toBe(0);
123+
expect(keyManager.activeItem!.getLabel()).toBe('one');
124+
125+
itemList.items = [];
126+
itemList.notifyOnChanges();
127+
128+
expect(keyManager.activeItemIndex).toBe(-1);
129+
expect(keyManager.activeItem).toBe(null);
130+
});
131+
96132
it('should start off the activeItem as null', () => {
97133
expect(new ListKeyManager([]).activeItem).toBeNull();
98134
});

Diff for: src/cdk/a11y/key-manager/list-key-manager.ts

+4-2
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
}
@@ -349,7 +351,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
349351

350352
// Explicitly check for `null` and `undefined` because other falsy values are valid.
351353
this._activeItem = activeItem == null ? null : activeItem;
352-
this._activeItemIndex = index;
354+
this._activeItemIndex = activeItem == null ? -1 : index;
353355
}
354356

355357
/**

0 commit comments

Comments
 (0)