From 72e245d5251d32a0d5df56f4446e3fe494c22c3f Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 2 Feb 2017 22:11:08 +0100 Subject: [PATCH 1/3] feat(select): allow focusing items by typing Allows for users to skip to a select item by typing, similar to the native select. Fixes #2668. --- src/cdk/a11y/activedescendant-key-manager.ts | 4 +- src/cdk/a11y/focus-key-manager.ts | 4 +- src/cdk/a11y/list-key-manager.spec.ts | 112 +++++++++++++----- src/cdk/a11y/list-key-manager.ts | 76 ++++++++---- src/cdk/keyboard/keycodes.ts | 2 + .../core/a11y/activedescendant-key-manager.ts | 4 +- src/lib/core/a11y/focus-key-manager.ts | 15 +-- src/lib/core/a11y/list-key-manager.ts | 4 +- src/lib/core/keyboard/keycodes.ts | 4 +- src/lib/core/option/option.ts | 5 + src/lib/select/select.ts | 2 +- 11 files changed, 157 insertions(+), 75 deletions(-) diff --git a/src/cdk/a11y/activedescendant-key-manager.ts b/src/cdk/a11y/activedescendant-key-manager.ts index ec5d19235cfd..7bc9ea4cdc7b 100644 --- a/src/cdk/a11y/activedescendant-key-manager.ts +++ b/src/cdk/a11y/activedescendant-key-manager.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ListKeyManager, CanDisable} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; /** * This is the interface for highlightable items (used by the ActiveDescendantKeyManager). * Each item must know how to style itself as active or inactive and whether or not it is * currently disabled. */ -export interface Highlightable extends CanDisable { +export interface Highlightable extends ListKeyManagerItem { setActiveStyles(): void; setInactiveStyles(): void; } diff --git a/src/cdk/a11y/focus-key-manager.ts b/src/cdk/a11y/focus-key-manager.ts index 8473b84560ea..d5f2361a48bd 100644 --- a/src/cdk/a11y/focus-key-manager.ts +++ b/src/cdk/a11y/focus-key-manager.ts @@ -7,13 +7,13 @@ */ import {QueryList} from '@angular/core'; -import {ListKeyManager, CanDisable} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; /** * This is the interface for focusable items (used by the FocusKeyManager). * Each item must know how to focus itself and whether or not it is currently disabled. */ -export interface Focusable extends CanDisable { +export interface Focusable extends ListKeyManagerItem { focus(): void; } diff --git a/src/cdk/a11y/list-key-manager.spec.ts b/src/cdk/a11y/list-key-manager.spec.ts index 29e887a02b21..26a32ca1cd70 100644 --- a/src/cdk/a11y/list-key-manager.spec.ts +++ b/src/cdk/a11y/list-key-manager.spec.ts @@ -9,8 +9,10 @@ import {first} from '../rxjs/index'; class FakeFocusable { + constructor(private _label = '') { } disabled = false; focus() {} + getLabel() { return this._label; } } class FakeHighlightable { @@ -25,6 +27,7 @@ class FakeQueryList extends QueryList { toArray() { return this.items; } + get first() { return this.items[0]; } } @@ -43,7 +46,7 @@ describe('Key managers', () => { downArrow: createKeyboardEvent('keydown', DOWN_ARROW), upArrow: createKeyboardEvent('keydown', UP_ARROW), tab: createKeyboardEvent('keydown', TAB), - unsupported: createKeyboardEvent('keydown', 65) // corresponds to the letter "a" + unsupported: createKeyboardEvent('keydown', 192) // corresponds to the tilde character (~) }; }); @@ -52,7 +55,11 @@ describe('Key managers', () => { let keyManager: ListKeyManager; beforeEach(() => { - itemList.items = [new FakeFocusable(), new FakeFocusable(), new FakeFocusable()]; + itemList.items = [ + new FakeFocusable('one'), + new FakeFocusable('two'), + new FakeFocusable('three') + ]; keyManager = new ListKeyManager(itemList); // first item is already focused @@ -383,6 +390,55 @@ describe('Key managers', () => { }); + describe('typeahead mode', () => { + const debounceInterval = 300; + + beforeEach(() => { + keyManager.withTypeAhead(debounceInterval); + keyManager.setActiveItem(-1); + }); + + it('should debounce the input key presses', fakeAsync(() => { + keyManager.onKeydown(createKeyboardEvent('keydown', 79)); // types "o" + keyManager.onKeydown(createKeyboardEvent('keydown', 78)); // types "n" + keyManager.onKeydown(createKeyboardEvent('keydown', 69)); // types "e" + + expect(keyManager.activeItem).not.toBe(itemList.items[0]); + + tick(debounceInterval); + + expect(keyManager.activeItem).toBe(itemList.items[0]); + })); + + it('should focus the first item that starts with a letter', fakeAsync(() => { + keyManager.onKeydown(createKeyboardEvent('keydown', 84)); // types "t" + + tick(debounceInterval); + + expect(keyManager.activeItem).toBe(itemList.items[1]); + })); + + it('should focus the first item that starts with sequence of letters', fakeAsync(() => { + keyManager.onKeydown(createKeyboardEvent('keydown', 84)); // types "t" + keyManager.onKeydown(createKeyboardEvent('keydown', 72)); // types "h" + + tick(debounceInterval); + + expect(keyManager.activeItem).toBe(itemList.items[2]); + })); + + it('should cancel any pending timers if a navigation key is pressed', fakeAsync(() => { + keyManager.onKeydown(createKeyboardEvent('keydown', 84)); // types "t" + keyManager.onKeydown(createKeyboardEvent('keydown', 72)); // types "h" + keyManager.onKeydown(fakeKeyEvents.downArrow); + + tick(debounceInterval); + + expect(keyManager.activeItem).toBe(itemList.items[0]); + })); + + }); + }); describe('FocusKeyManager', () => { @@ -400,40 +456,40 @@ describe('Key managers', () => { spyOn(itemList.items[2], 'focus'); }); - it('should focus subsequent items when down arrow is pressed', () => { - keyManager.onKeydown(fakeKeyEvents.downArrow); + it('should focus subsequent items when down arrow is pressed', () => { + keyManager.onKeydown(fakeKeyEvents.downArrow); - expect(itemList.items[0].focus).not.toHaveBeenCalled(); - expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); - expect(itemList.items[2].focus).not.toHaveBeenCalled(); + expect(itemList.items[0].focus).not.toHaveBeenCalled(); + expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); + expect(itemList.items[2].focus).not.toHaveBeenCalled(); - keyManager.onKeydown(fakeKeyEvents.downArrow); - expect(itemList.items[0].focus).not.toHaveBeenCalled(); - expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); - expect(itemList.items[2].focus).toHaveBeenCalledTimes(1); - }); + keyManager.onKeydown(fakeKeyEvents.downArrow); + expect(itemList.items[0].focus).not.toHaveBeenCalled(); + expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); + expect(itemList.items[2].focus).toHaveBeenCalledTimes(1); + }); - it('should focus previous items when up arrow is pressed', () => { - keyManager.onKeydown(fakeKeyEvents.downArrow); + it('should focus previous items when up arrow is pressed', () => { + keyManager.onKeydown(fakeKeyEvents.downArrow); - expect(itemList.items[0].focus).not.toHaveBeenCalled(); - expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); + expect(itemList.items[0].focus).not.toHaveBeenCalled(); + expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); - keyManager.onKeydown(fakeKeyEvents.upArrow); + keyManager.onKeydown(fakeKeyEvents.upArrow); - expect(itemList.items[0].focus).toHaveBeenCalledTimes(1); - expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); - }); + expect(itemList.items[0].focus).toHaveBeenCalledTimes(1); + expect(itemList.items[1].focus).toHaveBeenCalledTimes(1); + }); - it('should allow setting the focused item without calling focus', () => { - expect(keyManager.activeItemIndex) - .toBe(0, `Expected first item of the list to be active.`); + it('should allow setting the focused item without calling focus', () => { + expect(keyManager.activeItemIndex) + .toBe(0, `Expected first item of the list to be active.`); - keyManager.updateActiveItemIndex(1); - expect(keyManager.activeItemIndex) - .toBe(1, `Expected activeItemIndex to update after calling updateActiveItemIndex().`); - expect(itemList.items[1].focus).not.toHaveBeenCalledTimes(1); - }); + keyManager.updateActiveItemIndex(1); + expect(keyManager.activeItemIndex) + .toBe(1, `Expected activeItemIndex to update after calling updateActiveItemIndex().`); + expect(itemList.items[1].focus).not.toHaveBeenCalledTimes(1); + }); }); diff --git a/src/cdk/a11y/list-key-manager.ts b/src/cdk/a11y/list-key-manager.ts index 0a57b6484034..9f77414b18ec 100644 --- a/src/cdk/a11y/list-key-manager.ts +++ b/src/cdk/a11y/list-key-manager.ts @@ -9,42 +9,77 @@ import {QueryList} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; -import {UP_ARROW, DOWN_ARROW, TAB} from '@angular/cdk/keyboard'; +import {Subscription} from 'rxjs/Subscription'; +import {UP_ARROW, DOWN_ARROW, TAB, A, Z} from '@angular/cdk/keyboard'; +import {RxChain, debounceTime, filter, map, doOperator} from '@angular/cdk/rxjs'; /** - * This interface is for items that can be disabled. The type passed into - * ListKeyManager must extend this interface. + * This interface is for items that can be passed to a ListKeyManager. */ -export interface CanDisable { +export interface ListKeyManagerItem { disabled?: boolean; + getLabel?(): string; } /** * This class manages keyboard events for selectable lists. If you pass it a query list * of items, it will set the active item correctly when arrow events occur. */ -export class ListKeyManager { - private _activeItemIndex: number = -1; +export class ListKeyManager { + private _activeItemIndex = -1; private _activeItem: T; - private _tabOut = new Subject(); - private _wrap: boolean = false; + private _wrap = false; + private _pressedInputKeys: number[] = []; + private _nonNavigationKeyStream = new Subject(); + private _typeaheadSubscription: Subscription; constructor(private _items: QueryList) { } /** * Turns on wrapping mode, which ensures that the active item will wrap to * the other end of list when there are no more items in the given direction. - * - * @returns The ListKeyManager that the method was called on. */ withWrap(): this { this._wrap = true; return this; } + /** + * Turns on typeahead mode which allows users to set the active item by typing. + * @param debounceInterval Time to wait after the last keystroke before setting the active item. + */ + withTypeAhead(debounceInterval = 200): this { + if (this._items.length && this._items.some(item => typeof item.getLabel !== 'function')) { + throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.'); + } + + if (this._typeaheadSubscription) { + this._typeaheadSubscription.unsubscribe(); + } + + this._typeaheadSubscription = RxChain.from(this._nonNavigationKeyStream) + .call(filter, keyCode => keyCode >= A && keyCode <= Z) + .call(doOperator, keyCode => this._pressedInputKeys.push(keyCode)) + .call(debounceTime, debounceInterval) + .call(filter, () => this._pressedInputKeys.length > 0) + .call(map, () => String.fromCharCode(...this._pressedInputKeys)) + .subscribe(inputString => { + const activeItemIndex = this._items.toArray().findIndex(item => { + return item.getLabel!().toUpperCase().trim().startsWith(inputString); + }); + + if (activeItemIndex > -1) { + this.setActiveItem(activeItemIndex); + } + + this._pressedInputKeys = []; + }); + + 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. */ setActiveItem(index: number): void { @@ -58,20 +93,12 @@ export class ListKeyManager { */ onKeydown(event: KeyboardEvent): void { switch (event.keyCode) { - case DOWN_ARROW: - this.setNextItemActive(); - break; - case UP_ARROW: - this.setPreviousItemActive(); - break; - case TAB: - // Note that we shouldn't prevent the default action on tab. - this._tabOut.next(); - return; - default: - return; + case DOWN_ARROW: this.setNextItemActive(); break; + case UP_ARROW: this.setPreviousItemActive(); break; + default: this._nonNavigationKeyStream.next(event.keyCode); return; } + this._pressedInputKeys = []; event.preventDefault(); } @@ -119,7 +146,7 @@ export class ListKeyManager { * when focus is shifted off of the list. */ get tabOut(): Observable { - return this._tabOut.asObservable(); + return filter.call(this._nonNavigationKeyStream, keyCode => keyCode === TAB); } /** @@ -173,5 +200,4 @@ export class ListKeyManager { } this.setActiveItem(index); } - } diff --git a/src/cdk/keyboard/keycodes.ts b/src/cdk/keyboard/keycodes.ts index 31735087d7a7..312174fbd3de 100644 --- a/src/cdk/keyboard/keycodes.ts +++ b/src/cdk/keyboard/keycodes.ts @@ -20,3 +20,5 @@ export const TAB = 9; export const ESCAPE = 27; export const BACKSPACE = 8; export const DELETE = 46; +export const A = 65; +export const Z = 90; diff --git a/src/lib/core/a11y/activedescendant-key-manager.ts b/src/lib/core/a11y/activedescendant-key-manager.ts index ec5d19235cfd..7bc9ea4cdc7b 100644 --- a/src/lib/core/a11y/activedescendant-key-manager.ts +++ b/src/lib/core/a11y/activedescendant-key-manager.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ListKeyManager, CanDisable} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; /** * This is the interface for highlightable items (used by the ActiveDescendantKeyManager). * Each item must know how to style itself as active or inactive and whether or not it is * currently disabled. */ -export interface Highlightable extends CanDisable { +export interface Highlightable extends ListKeyManagerItem { setActiveStyles(): void; setInactiveStyles(): void; } diff --git a/src/lib/core/a11y/focus-key-manager.ts b/src/lib/core/a11y/focus-key-manager.ts index 8473b84560ea..94b86f9d947d 100644 --- a/src/lib/core/a11y/focus-key-manager.ts +++ b/src/lib/core/a11y/focus-key-manager.ts @@ -6,24 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {QueryList} from '@angular/core'; -import {ListKeyManager, CanDisable} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; /** * This is the interface for focusable items (used by the FocusKeyManager). - * Each item must know how to focus itself and whether or not it is currently disabled. + * Each item must know how to focus itself, whether or not it is currently disabled + * and be able to supply it's label. */ -export interface Focusable extends CanDisable { +export interface Focusable extends ListKeyManagerItem { focus(): void; } - export class FocusKeyManager extends ListKeyManager { - - constructor(items: QueryList) { - super(items); - } - /** * This method sets the active item to the item at the specified index. * It also adds focuses the newly active item. @@ -35,5 +29,4 @@ export class FocusKeyManager extends ListKeyManager { this.activeItem.focus(); } } - } diff --git a/src/lib/core/a11y/list-key-manager.ts b/src/lib/core/a11y/list-key-manager.ts index c02fd7f8bb1e..e626c17bbf1a 100644 --- a/src/lib/core/a11y/list-key-manager.ts +++ b/src/lib/core/a11y/list-key-manager.ts @@ -6,6 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -export {CanDisable, ListKeyManager} from '@angular/cdk/a11y'; - - +export {ListKeyManagerItem, ListKeyManager} from '@angular/cdk/a11y'; diff --git a/src/lib/core/keyboard/keycodes.ts b/src/lib/core/keyboard/keycodes.ts index 97deda36b866..ae272e4d6f19 100644 --- a/src/lib/core/keyboard/keycodes.ts +++ b/src/lib/core/keyboard/keycodes.ts @@ -21,5 +21,7 @@ export { TAB, ESCAPE, BACKSPACE, - DELETE + DELETE, + A, + Z, } from '@angular/cdk/keyboard'; diff --git a/src/lib/core/option/option.ts b/src/lib/core/option/option.ts index f1820d346582..9b07ecee008e 100644 --- a/src/lib/core/option/option.ts +++ b/src/lib/core/option/option.ts @@ -174,6 +174,11 @@ export class MdOption { } } + /** Fetches the label to be used when determining whether the option should be focused. */ + getLabel(): string { + return this.viewValue; + } + /** Ensures the option is selected when activated from the keyboard. */ _handleKeydown(event: KeyboardEvent): void { if (event.keyCode === ENTER || event.keyCode === SPACE) { diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 7a53b46b53c9..6483c60c11ca 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -719,7 +719,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On /** Sets up a key manager to listen to keyboard events on the overlay panel. */ private _initKeyManager() { - this._keyManager = new FocusKeyManager(this.options); + this._keyManager = new FocusKeyManager(this.options).withTypeAhead(); this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close()); } From 5d40226a4096161a984b58257e622a985c9b2bab Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 7 Aug 2017 22:52:51 +0200 Subject: [PATCH 2/3] refactor: address feedback --- src/cdk/a11y/activedescendant-key-manager.ts | 4 +-- src/cdk/a11y/focus-key-manager.ts | 17 ++++--------- src/cdk/a11y/list-key-manager.spec.ts | 17 ++++++++++--- src/cdk/a11y/list-key-manager.ts | 25 +++++++++++++------ src/lib/chips/chip.ts | 4 +-- .../core/a11y/activedescendant-key-manager.ts | 4 +-- src/lib/core/a11y/focus-key-manager.ts | 6 ++--- src/lib/core/a11y/list-key-manager.ts | 2 +- src/lib/core/option/option.ts | 4 +-- src/lib/menu/menu-item.ts | 4 +-- 10 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/cdk/a11y/activedescendant-key-manager.ts b/src/cdk/a11y/activedescendant-key-manager.ts index 7bc9ea4cdc7b..07e60243ef1e 100644 --- a/src/cdk/a11y/activedescendant-key-manager.ts +++ b/src/cdk/a11y/activedescendant-key-manager.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerOption} from './list-key-manager'; /** * This is the interface for highlightable items (used by the ActiveDescendantKeyManager). * Each item must know how to style itself as active or inactive and whether or not it is * currently disabled. */ -export interface Highlightable extends ListKeyManagerItem { +export interface Highlightable extends ListKeyManagerOption { setActiveStyles(): void; setInactiveStyles(): void; } diff --git a/src/cdk/a11y/focus-key-manager.ts b/src/cdk/a11y/focus-key-manager.ts index d5f2361a48bd..b1b2256991b3 100644 --- a/src/cdk/a11y/focus-key-manager.ts +++ b/src/cdk/a11y/focus-key-manager.ts @@ -6,24 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {QueryList} from '@angular/core'; -import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerOption} from './list-key-manager'; /** * This is the interface for focusable items (used by the FocusKeyManager). - * Each item must know how to focus itself and whether or not it is currently disabled. + * Each item must know how to focus itself, whether or not it is currently disabled + * and be able to supply it's label. */ -export interface Focusable extends ListKeyManagerItem { +export interface FocusableOption extends ListKeyManagerOption { focus(): void; } - -export class FocusKeyManager extends ListKeyManager { - - constructor(items: QueryList) { - super(items); - } - +export class FocusKeyManager extends ListKeyManager { /** * This method sets the active item to the item at the specified index. * It also adds focuses the newly active item. @@ -35,5 +29,4 @@ export class FocusKeyManager extends ListKeyManager { this.activeItem.focus(); } } - } diff --git a/src/cdk/a11y/list-key-manager.spec.ts b/src/cdk/a11y/list-key-manager.spec.ts index 26a32ca1cd70..b6d444f7156b 100644 --- a/src/cdk/a11y/list-key-manager.spec.ts +++ b/src/cdk/a11y/list-key-manager.spec.ts @@ -22,12 +22,11 @@ class FakeHighlightable { } class FakeQueryList extends QueryList { - get length() { return this.items.length; } items: T[]; - toArray() { - return this.items; - } + get length() { return this.items.length; } get first() { return this.items[0]; } + toArray() { return this.items; } + some() { return this.items.some.apply(this.items, arguments); } } @@ -398,6 +397,16 @@ describe('Key managers', () => { keyManager.setActiveItem(-1); }); + it('should throw if the items do not implement the getLabel method', () => { + const invalidQueryList = new FakeQueryList(); + + invalidQueryList.items = [{ disabled: false }]; + + const invalidManager = new ListKeyManager(invalidQueryList); + + expect(() => invalidManager.withTypeAhead()).toThrowError(/must implement/); + }); + it('should debounce the input key presses', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 79)); // types "o" keyManager.onKeydown(createKeyboardEvent('keydown', 78)); // types "n" diff --git a/src/cdk/a11y/list-key-manager.ts b/src/cdk/a11y/list-key-manager.ts index 9f77414b18ec..ef01a084f571 100644 --- a/src/cdk/a11y/list-key-manager.ts +++ b/src/cdk/a11y/list-key-manager.ts @@ -16,7 +16,7 @@ import {RxChain, debounceTime, filter, map, doOperator} from '@angular/cdk/rxjs' /** * This interface is for items that can be passed to a ListKeyManager. */ -export interface ListKeyManagerItem { +export interface ListKeyManagerOption { disabled?: boolean; getLabel?(): string; } @@ -25,14 +25,16 @@ export interface ListKeyManagerItem { * This class manages keyboard events for selectable lists. If you pass it a query list * of items, it will set the active item correctly when arrow events occur. */ -export class ListKeyManager { +export class ListKeyManager { private _activeItemIndex = -1; private _activeItem: T; private _wrap = false; - private _pressedInputKeys: number[] = []; private _nonNavigationKeyStream = new Subject(); private _typeaheadSubscription: Subscription; + // Buffer for the letters that the user has pressed when the typeahead option is turned on. + private _pressedInputKeys: number[] = []; + constructor(private _items: QueryList) { } /** @@ -57,6 +59,9 @@ export class ListKeyManager { this._typeaheadSubscription.unsubscribe(); } + // Debounce the presses of non-navigational keys, collect the ones that correspond to letters + // and convert those letters back into a string. Afterwards find the first item that starts + // with that string and select it. this._typeaheadSubscription = RxChain.from(this._nonNavigationKeyStream) .call(filter, keyCode => keyCode >= A && keyCode <= Z) .call(doOperator, keyCode => this._pressedInputKeys.push(keyCode)) @@ -64,12 +69,13 @@ export class ListKeyManager { .call(filter, () => this._pressedInputKeys.length > 0) .call(map, () => String.fromCharCode(...this._pressedInputKeys)) .subscribe(inputString => { - const activeItemIndex = this._items.toArray().findIndex(item => { - return item.getLabel!().toUpperCase().trim().startsWith(inputString); - }); + const items = this._items.toArray(); - if (activeItemIndex > -1) { - this.setActiveItem(activeItemIndex); + for (let i = 0; i < items.length; i++) { + if (items[i].getLabel!().toUpperCase().trim().indexOf(inputString) === 0) { + this.setActiveItem(i); + break; + } } this._pressedInputKeys = []; @@ -95,6 +101,9 @@ export class ListKeyManager { switch (event.keyCode) { case DOWN_ARROW: this.setNextItemActive(); break; case UP_ARROW: this.setPreviousItemActive(); break; + + // Note that we return here, in order to avoid preventing + // the default action of unsupported keys. default: this._nonNavigationKeyStream.next(event.keyCode); return; } diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index c287b7534031..7db02430edef 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -18,7 +18,7 @@ import { forwardRef, } from '@angular/core'; -import {Focusable} from '../core/a11y/focus-key-manager'; +import {FocusableOption} from '../core/a11y/focus-key-manager'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {CanColor, mixinColor} from '../core/common-behaviors/color'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; @@ -68,7 +68,7 @@ export class MdBasicChip { } '(blur)': '_hasFocus = false', } }) -export class MdChip extends _MdChipMixinBase implements Focusable, OnDestroy, CanColor, CanDisable { +export class MdChip extends _MdChipMixinBase implements FocusableOption, OnDestroy, CanColor, CanDisable { @ContentChild(forwardRef(() => MdChipRemove)) _chipRemove: MdChipRemove; diff --git a/src/lib/core/a11y/activedescendant-key-manager.ts b/src/lib/core/a11y/activedescendant-key-manager.ts index 7bc9ea4cdc7b..07e60243ef1e 100644 --- a/src/lib/core/a11y/activedescendant-key-manager.ts +++ b/src/lib/core/a11y/activedescendant-key-manager.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerOption} from './list-key-manager'; /** * This is the interface for highlightable items (used by the ActiveDescendantKeyManager). * Each item must know how to style itself as active or inactive and whether or not it is * currently disabled. */ -export interface Highlightable extends ListKeyManagerItem { +export interface Highlightable extends ListKeyManagerOption { setActiveStyles(): void; setInactiveStyles(): void; } diff --git a/src/lib/core/a11y/focus-key-manager.ts b/src/lib/core/a11y/focus-key-manager.ts index 94b86f9d947d..b1b2256991b3 100644 --- a/src/lib/core/a11y/focus-key-manager.ts +++ b/src/lib/core/a11y/focus-key-manager.ts @@ -6,18 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {ListKeyManager, ListKeyManagerItem} from './list-key-manager'; +import {ListKeyManager, ListKeyManagerOption} from './list-key-manager'; /** * This is the interface for focusable items (used by the FocusKeyManager). * Each item must know how to focus itself, whether or not it is currently disabled * and be able to supply it's label. */ -export interface Focusable extends ListKeyManagerItem { +export interface FocusableOption extends ListKeyManagerOption { focus(): void; } -export class FocusKeyManager extends ListKeyManager { +export class FocusKeyManager extends ListKeyManager { /** * This method sets the active item to the item at the specified index. * It also adds focuses the newly active item. diff --git a/src/lib/core/a11y/list-key-manager.ts b/src/lib/core/a11y/list-key-manager.ts index e626c17bbf1a..8710826427ee 100644 --- a/src/lib/core/a11y/list-key-manager.ts +++ b/src/lib/core/a11y/list-key-manager.ts @@ -6,4 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -export {ListKeyManagerItem, ListKeyManager} from '@angular/cdk/a11y'; +export {ListKeyManagerOption, ListKeyManager} from '@angular/cdk/a11y'; diff --git a/src/lib/core/option/option.ts b/src/lib/core/option/option.ts index 9b07ecee008e..09a88598f476 100644 --- a/src/lib/core/option/option.ts +++ b/src/lib/core/option/option.ts @@ -174,7 +174,7 @@ export class MdOption { } } - /** Fetches the label to be used when determining whether the option should be focused. */ + /** Gets the label to be used when determining whether the option should be focused. */ getLabel(): string { return this.viewValue; } @@ -206,7 +206,7 @@ export class MdOption { return this.disabled ? '-1' : '0'; } - /** Fetches the host DOM element. */ + /** Gets the host DOM element. */ _getHostElement(): HTMLElement { return this._element.nativeElement; } diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index f00b62cca332..7c2e29b9b2a5 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -7,7 +7,7 @@ */ import {Component, ElementRef, OnDestroy, ChangeDetectionStrategy} from '@angular/core'; -import {Focusable} from '../core/a11y/focus-key-manager'; +import {FocusableOption} from '../core/a11y/focus-key-manager'; import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; import {Subject} from 'rxjs/Subject'; @@ -39,7 +39,7 @@ export const _MdMenuItemMixinBase = mixinDisabled(MdMenuItemBase); templateUrl: 'menu-item.html', exportAs: 'mdMenuItem', }) -export class MdMenuItem extends _MdMenuItemMixinBase implements Focusable, CanDisable, OnDestroy { +export class MdMenuItem extends _MdMenuItemMixinBase implements FocusableOption, CanDisable, OnDestroy { /** Stream that emits when the menu item is hovered. */ hover: Subject = new Subject(); From 49fecc1b09b14656214d0a0176321361a61e61e4 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Tue, 8 Aug 2017 07:28:18 +0200 Subject: [PATCH 3/3] chore: linting errors --- src/lib/chips/chip.ts | 3 ++- src/lib/menu/menu-item.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 7db02430edef..c150f572a24f 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -68,7 +68,8 @@ export class MdBasicChip { } '(blur)': '_hasFocus = false', } }) -export class MdChip extends _MdChipMixinBase implements FocusableOption, OnDestroy, CanColor, CanDisable { +export class MdChip extends _MdChipMixinBase implements FocusableOption, OnDestroy, CanColor, + CanDisable { @ContentChild(forwardRef(() => MdChipRemove)) _chipRemove: MdChipRemove; diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index 7c2e29b9b2a5..cabe77a97dad 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -39,7 +39,9 @@ export const _MdMenuItemMixinBase = mixinDisabled(MdMenuItemBase); templateUrl: 'menu-item.html', exportAs: 'mdMenuItem', }) -export class MdMenuItem extends _MdMenuItemMixinBase implements FocusableOption, CanDisable, OnDestroy { +export class MdMenuItem extends _MdMenuItemMixinBase implements FocusableOption, CanDisable, + OnDestroy { + /** Stream that emits when the menu item is hovered. */ hover: Subject = new Subject();