Skip to content

Commit 4aaac44

Browse files
feat(a11y): Add optional home/end key support to ListKeyManager
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 298cdc0 commit 4aaac44

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
});
@@ -195,6 +199,30 @@ describe('Key managers', () => {
195199
expect(fakeKeyEvents.downArrow.defaultPrevented).toBe(false);
196200
});
197201

202+
describe('withHomeAndEnd', () => {
203+
beforeEach(() => {
204+
keyManager.withHomeAndEnd();
205+
});
206+
207+
it('should focus the first item when Home is pressed', () => {
208+
keyManager.setActiveItem(1);
209+
expect(keyManager.activeItemIndex).toBe(1);
210+
211+
keyManager.onKeydown(fakeKeyEvents.home);
212+
213+
expect(keyManager.activeItemIndex).toBe(0);
214+
});
215+
216+
it('should focus the last item when End is pressed', () => {
217+
keyManager.setActiveItem(0);
218+
expect(keyManager.activeItemIndex).toBe(0);
219+
220+
keyManager.onKeydown(fakeKeyEvents.end);
221+
222+
expect(keyManager.activeItemIndex).toBe(itemList.items.length - 1);
223+
});
224+
});
225+
198226
describe('with `vertical` direction', function(this: KeyEventTestContext) {
199227
beforeEach(() => {
200228
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
@@ -208,6 +208,7 @@ export declare class ListKeyManager<T extends ListKeyManagerOption> {
208208
updateActiveItem(index: number): void;
209209
updateActiveItem(item: T): void;
210210
withAllowedModifierKeys(keys: ListKeyManagerModifierKey[]): this;
211+
withHomeAndEnd(): this;
211212
withHorizontalOrientation(direction: 'ltr' | 'rtl' | null): this;
212213
withTypeAhead(debounceInterval?: number): this;
213214
withVerticalOrientation(enabled?: boolean): this;

0 commit comments

Comments
 (0)