Skip to content

Commit 544e335

Browse files
feat(a11y): Add optional home/end key support to ListKeyManager (#19834)
For some components like menus and listboxes, WAI a11y recommends that pressing home or end should focus the first or last element respectively. Add support for home/end to ListKeyManager.
1 parent b441bdf commit 544e335

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes';
1+
import {DOWN_ARROW, END, HOME, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes';
22
import {createKeyboardEvent} from '@angular/cdk/testing/private';
33
import {QueryList} from '@angular/core';
44
import {fakeAsync, tick} from '@angular/core/testing';
@@ -51,6 +51,8 @@ describe('Key managers', () => {
5151
leftArrow: KeyboardEvent,
5252
rightArrow: KeyboardEvent,
5353
tab: KeyboardEvent,
54+
home: KeyboardEvent,
55+
end: KeyboardEvent,
5456
unsupported: KeyboardEvent
5557
};
5658

@@ -62,6 +64,8 @@ describe('Key managers', () => {
6264
leftArrow: createKeyboardEvent('keydown', LEFT_ARROW),
6365
rightArrow: createKeyboardEvent('keydown', RIGHT_ARROW),
6466
tab: createKeyboardEvent('keydown', TAB),
67+
home: createKeyboardEvent('keydown', HOME),
68+
end: createKeyboardEvent('keydown', END),
6569
unsupported: createKeyboardEvent('keydown', 192) // corresponds to the tilde character (~)
6670
};
6771
});
@@ -207,6 +211,30 @@ describe('Key managers', () => {
207211
expect(fakeKeyEvents.downArrow.defaultPrevented).toBe(false);
208212
});
209213

214+
describe('withHomeAndEnd', () => {
215+
beforeEach(() => {
216+
keyManager.withHomeAndEnd();
217+
});
218+
219+
it('should focus the first item when Home is pressed', () => {
220+
keyManager.setActiveItem(1);
221+
expect(keyManager.activeItemIndex).toBe(1);
222+
223+
keyManager.onKeydown(fakeKeyEvents.home);
224+
225+
expect(keyManager.activeItemIndex).toBe(0);
226+
});
227+
228+
it('should focus the last item when End is pressed', () => {
229+
keyManager.setActiveItem(0);
230+
expect(keyManager.activeItemIndex).toBe(0);
231+
232+
keyManager.onKeydown(fakeKeyEvents.end);
233+
234+
expect(keyManager.activeItemIndex).toBe(itemList.items.length - 1);
235+
});
236+
});
237+
210238
describe('with `vertical` direction', function(this: KeyEventTestContext) {
211239
beforeEach(() => {
212240
keyManager.withVerticalOrientation();

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
ZERO,
2020
NINE,
2121
hasModifierKey,
22+
HOME,
23+
END,
2224
} from '@angular/cdk/keycodes';
2325
import {debounceTime, filter, map, tap} from 'rxjs/operators';
2426

@@ -47,6 +49,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
4749
private _vertical = true;
4850
private _horizontal: 'ltr' | 'rtl' | null;
4951
private _allowedModifierKeys: ListKeyManagerModifierKey[] = [];
52+
private _homeAndEnd = false;
5053

5154
/**
5255
* Predicate function that can be used to check whether an item should be skipped
@@ -174,6 +177,15 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
174177
return this;
175178
}
176179

180+
/**
181+
* Configures the key manager to focus the first and last items
182+
* respectively when the Home key and End Key are pressed.
183+
*/
184+
withHomeAndEnd(): this {
185+
this._homeAndEnd = true;
186+
return this;
187+
}
188+
177189
/**
178190
* Sets the active item to the item at the index specified.
179191
* @param index The index of the item to be set as active.
@@ -244,6 +256,22 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
244256
return;
245257
}
246258

259+
case HOME:
260+
if (this._homeAndEnd && isModifierAllowed) {
261+
this.setFirstItemActive();
262+
break;
263+
} else {
264+
return;
265+
}
266+
267+
case END:
268+
if (this._homeAndEnd && isModifierAllowed) {
269+
this.setLastItemActive();
270+
break;
271+
} else {
272+
return;
273+
}
274+
247275
default:
248276
if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) {
249277
// Attempt to use the `event.key` which also maps it to the user's keyboard language,

tools/public_api_guard/cdk/a11y.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export declare class ListKeyManager<T extends ListKeyManagerOption> {
213213
updateActiveItem(index: number): void;
214214
updateActiveItem(item: T): void;
215215
withAllowedModifierKeys(keys: ListKeyManagerModifierKey[]): this;
216+
withHomeAndEnd(): this;
216217
withHorizontalOrientation(direction: 'ltr' | 'rtl' | null): this;
217218
withTypeAhead(debounceInterval?: number): this;
218219
withVerticalOrientation(enabled?: boolean): this;

0 commit comments

Comments
 (0)