diff --git a/src/cdk/a11y/key-manager/list-key-manager.spec.ts b/src/cdk/a11y/key-manager/list-key-manager.spec.ts index df4bbe10379d..38760f42ded1 100644 --- a/src/cdk/a11y/key-manager/list-key-manager.spec.ts +++ b/src/cdk/a11y/key-manager/list-key-manager.spec.ts @@ -1,4 +1,4 @@ -import {DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes'; +import {DOWN_ARROW, END, HOME, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes'; import {createKeyboardEvent} from '@angular/cdk/testing/private'; import {QueryList} from '@angular/core'; import {fakeAsync, tick} from '@angular/core/testing'; @@ -51,6 +51,8 @@ describe('Key managers', () => { leftArrow: KeyboardEvent, rightArrow: KeyboardEvent, tab: KeyboardEvent, + home: KeyboardEvent, + end: KeyboardEvent, unsupported: KeyboardEvent }; @@ -62,6 +64,8 @@ describe('Key managers', () => { leftArrow: createKeyboardEvent('keydown', LEFT_ARROW), rightArrow: createKeyboardEvent('keydown', RIGHT_ARROW), tab: createKeyboardEvent('keydown', TAB), + home: createKeyboardEvent('keydown', HOME), + end: createKeyboardEvent('keydown', END), unsupported: createKeyboardEvent('keydown', 192) // corresponds to the tilde character (~) }; }); @@ -195,6 +199,30 @@ describe('Key managers', () => { expect(fakeKeyEvents.downArrow.defaultPrevented).toBe(false); }); + describe('withHomeAndEnd', () => { + beforeEach(() => { + keyManager.withHomeAndEnd(); + }); + + it('should focus the first item when Home is pressed', () => { + keyManager.setActiveItem(1); + expect(keyManager.activeItemIndex).toBe(1); + + keyManager.onKeydown(fakeKeyEvents.home); + + expect(keyManager.activeItemIndex).toBe(0); + }); + + it('should focus the last item when End is pressed', () => { + keyManager.setActiveItem(0); + expect(keyManager.activeItemIndex).toBe(0); + + keyManager.onKeydown(fakeKeyEvents.end); + + expect(keyManager.activeItemIndex).toBe(itemList.items.length - 1); + }); + }); + describe('with `vertical` direction', function(this: KeyEventTestContext) { beforeEach(() => { keyManager.withVerticalOrientation(); diff --git a/src/cdk/a11y/key-manager/list-key-manager.ts b/src/cdk/a11y/key-manager/list-key-manager.ts index b7440157fb76..ec984a912bdf 100644 --- a/src/cdk/a11y/key-manager/list-key-manager.ts +++ b/src/cdk/a11y/key-manager/list-key-manager.ts @@ -19,6 +19,8 @@ import { ZERO, NINE, hasModifierKey, + HOME, + END, } from '@angular/cdk/keycodes'; import {debounceTime, filter, map, tap} from 'rxjs/operators'; @@ -47,6 +49,7 @@ export class ListKeyManager { private _vertical = true; private _horizontal: 'ltr' | 'rtl' | null; private _allowedModifierKeys: ListKeyManagerModifierKey[] = []; + private _homeAndEnd = false; /** * Predicate function that can be used to check whether an item should be skipped @@ -174,6 +177,15 @@ export class ListKeyManager { return this; } + /** + * Configures the key manager to focus the first and last items + * respectively when the Home key and End Key are pressed. + */ + withHomeAndEnd(): this { + this._homeAndEnd = true; + return this; + } + /** * Sets the active item to the item at the index specified. * @param index The index of the item to be set as active. @@ -244,6 +256,22 @@ export class ListKeyManager { return; } + case HOME: + if (this._homeAndEnd && isModifierAllowed) { + this.setFirstItemActive(); + break; + } else { + return; + } + + case END: + if (this._homeAndEnd && isModifierAllowed) { + this.setLastItemActive(); + break; + } else { + return; + } + default: if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) { // Attempt to use the `event.key` which also maps it to the user's keyboard language, diff --git a/tools/public_api_guard/cdk/a11y.d.ts b/tools/public_api_guard/cdk/a11y.d.ts index 76d55766a2c7..f9e43a21ba80 100644 --- a/tools/public_api_guard/cdk/a11y.d.ts +++ b/tools/public_api_guard/cdk/a11y.d.ts @@ -208,6 +208,7 @@ export declare class ListKeyManager { updateActiveItem(index: number): void; updateActiveItem(item: T): void; withAllowedModifierKeys(keys: ListKeyManagerModifierKey[]): this; + withHomeAndEnd(): this; withHorizontalOrientation(direction: 'ltr' | 'rtl' | null): this; withTypeAhead(debounceInterval?: number): this; withVerticalOrientation(enabled?: boolean): this;