Skip to content

refactor: handle home/end presses through key manager #19955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions src/cdk-experimental/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,7 @@ import {
QueryList
} from '@angular/core';
import {ActiveDescendantKeyManager, Highlightable, ListKeyManagerOption} from '@angular/cdk/a11y';
import {
DOWN_ARROW,
END,
ENTER,
HOME,
LEFT_ARROW,
RIGHT_ARROW,
SPACE,
UP_ARROW
} from '@angular/cdk/keycodes';
import {DOWN_ARROW, ENTER, SPACE, UP_ARROW, LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
import {BooleanInput, coerceBooleanProperty, coerceArray} from '@angular/cdk/coercion';
import {SelectionChange, SelectionModel} from '@angular/cdk/collections';
import {defer, merge, Observable, Subject} from 'rxjs';
Expand Down Expand Up @@ -340,6 +331,7 @@ export class CdkListbox<T> implements AfterContentInit, OnDestroy, OnInit, Contr
this._listKeyManager = new ActiveDescendantKeyManager(this._options)
.withWrap()
.withTypeAhead()
.withHomeAndEnd()
.withAllowedModifierKeys(['shiftKey']);

if (this.orientation === 'vertical') {
Expand Down Expand Up @@ -376,11 +368,7 @@ export class CdkListbox<T> implements AfterContentInit, OnDestroy, OnInit, Contr
const {keyCode} = event;
const previousActiveIndex = manager.activeItemIndex;

if (keyCode === HOME || keyCode === END) {
event.preventDefault();
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();

} else if (keyCode === SPACE || keyCode === ENTER) {
if (keyCode === SPACE || keyCode === ENTER) {
if (manager.activeItem && !manager.isTyping()) {
this._toggleActiveOption();
}
Expand Down
9 changes: 5 additions & 4 deletions src/cdk/a11y/key-manager/list-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,12 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
}

/**
* Configures the key manager to focus the first and last items
* respectively when the Home key and End Key are pressed.
* Configures the key manager to activate the first and last items
* respectively when the Home or End key is pressed.
* @param enabled Whether pressing the Home or End key activates the first/last item.
*/
withHomeAndEnd(): this {
this._homeAndEnd = true;
withHomeAndEnd(enabled: boolean = true): this {
this._homeAndEnd = enabled;
return this;
}

Expand Down
9 changes: 2 additions & 7 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
coerceNumberProperty,
NumberInput
} from '@angular/cdk/coercion';
import {END, ENTER, hasModifierKey, HOME, SPACE} from '@angular/cdk/keycodes';
import {ENTER, hasModifierKey, SPACE} from '@angular/cdk/keycodes';
import {DOCUMENT} from '@angular/common';
import {
AfterViewInit,
Expand Down Expand Up @@ -349,6 +349,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
// AfterViewInit so we're guaranteed for both view and content children to be defined.
this._keyManager = new FocusKeyManager<FocusableOption>(this._stepHeader)
.withWrap()
.withHomeAndEnd()
.withVerticalOrientation(this._orientation === 'vertical');

(this._dir ? (this._dir.change as Observable<Direction>) : observableOf<Direction>())
Expand Down Expand Up @@ -486,12 +487,6 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
(keyCode === SPACE || keyCode === ENTER)) {
this.selectedIndex = manager.activeItemIndex;
event.preventDefault();
} else if (keyCode === HOME) {
manager.setFirstItemActive();
event.preventDefault();
} else if (keyCode === END) {
manager.setLastItemActive();
event.preventDefault();
} else {
manager.onKeydown(event);
}
Expand Down
13 changes: 3 additions & 10 deletions src/material-experimental/mdc-chips/chip-grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {Directionality} from '@angular/cdk/bidi';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {BACKSPACE, TAB, HOME, END} from '@angular/cdk/keycodes';
import {BACKSPACE, TAB} from '@angular/cdk/keycodes';
import {
AfterContentInit,
AfterViewInit,
Expand Down Expand Up @@ -419,15 +419,7 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn
}
event.preventDefault();
} else if (this._originatesFromChip(event)) {
if (keyCode === HOME) {
manager.setFirstCellActive();
event.preventDefault();
} else if (keyCode === END) {
manager.setLastCellActive();
event.preventDefault();
} else {
manager.onKeydown(event);
}
manager.onKeydown(event);
}
this.stateChanges.next();
}
Expand Down Expand Up @@ -456,6 +448,7 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn
/** Initializes the key manager to manage focus. */
private _initKeyManager() {
this._keyManager = new GridFocusKeyManager(this._chips)
.withHomeAndEnd()
.withDirectionality(this._dir ? this._dir.value : 'ltr');

if (this._dir) {
Expand Down
12 changes: 2 additions & 10 deletions src/material-experimental/mdc-chips/chip-listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import {FocusKeyManager} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {END, HOME} from '@angular/cdk/keycodes';
import {
AfterContentInit,
ChangeDetectionStrategy,
Expand Down Expand Up @@ -354,15 +353,7 @@ export class MatChipListbox extends MatChipSet implements AfterContentInit, Cont
*/
_keydown(event: KeyboardEvent) {
if (this._originatesFromChip(event)) {
if (event.keyCode === HOME) {
this._keyManager.setFirstItemActive();
event.preventDefault();
} else if (event.keyCode === END) {
this._keyManager.setLastItemActive();
event.preventDefault();
} else {
this._keyManager.onKeydown(event);
}
this._keyManager.onKeydown(event);
}
}

Expand Down Expand Up @@ -457,6 +448,7 @@ export class MatChipListbox extends MatChipSet implements AfterContentInit, Cont
this._keyManager = new FocusKeyManager<MatChip>(this._chips)
.withWrap()
.withVerticalOrientation()
.withHomeAndEnd()
.withHorizontalOrientation(this._dir ? this._dir.value : 'ltr');

if (this._dir) {
Expand Down
29 changes: 29 additions & 0 deletions src/material-experimental/mdc-chips/grid-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
DOWN_ARROW,
LEFT_ARROW,
RIGHT_ARROW,
HOME,
END,
} from '@angular/cdk/keycodes';


Expand All @@ -37,6 +39,7 @@ export class GridKeyManager<T> {
private _activeRow: GridKeyManagerRow<T> | null = null;
private _activeCell: T | null = null;
private _dir: 'ltr' | 'rtl' = 'ltr';
private _homeAndEnd = false;

constructor(private _rows: QueryList<GridKeyManagerRow<T>> | GridKeyManagerRow<T>[]) {
// We allow for the rows to be an array because, in some cases, the consumer may
Expand Down Expand Up @@ -93,6 +96,16 @@ export class GridKeyManager<T> {
}
}

/**
* Configures the key manager to activate the first and last items
* respectively when the Home or End key is pressed.
* @param enabled Whether pressing the Home or End key activates the first/last item.
*/
withHomeAndEnd(enabled: boolean = true): this {
this._homeAndEnd = enabled;
return this;
}

/**
* Sets the active cell depending on the key event passed in.
* @param event Keyboard event to be used for determining which element should be active.
Expand All @@ -117,6 +130,22 @@ export class GridKeyManager<T> {
this._dir === 'rtl' ? this.setNextColumnActive() : this.setPreviousColumnActive();
break;

case HOME:
if (this._homeAndEnd) {
this.setFirstCellActive();
break;
} else {
return;
}

case END:
if (this._homeAndEnd) {
this.setLastCellActive();
break;
} else {
return;
}

default:
// Note that we return here, in order to avoid preventing
// the default action of non-navigational keys.
Expand Down
14 changes: 3 additions & 11 deletions src/material/chips/chip-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {FocusKeyManager} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {SelectionModel} from '@angular/cdk/collections';
import {BACKSPACE, END, HOME} from '@angular/cdk/keycodes';
import {BACKSPACE} from '@angular/cdk/keycodes';
import {
AfterContentInit,
ChangeDetectionStrategy,
Expand Down Expand Up @@ -351,6 +351,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
this._keyManager = new FocusKeyManager<MatChip>(this.chips)
.withWrap()
.withVerticalOrientation()
.withHomeAndEnd()
.withHorizontalOrientation(this._dir ? this._dir.value : 'ltr');

if (this._dir) {
Expand Down Expand Up @@ -503,16 +504,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
this._keyManager.setLastItemActive();
event.preventDefault();
} else if (target && target.classList.contains('mat-chip')) {
if (event.keyCode === HOME) {
this._keyManager.setFirstItemActive();
event.preventDefault();
} else if (event.keyCode === END) {
this._keyManager.setLastItemActive();
event.preventDefault();
} else {
this._keyManager.onKeydown(event);
}

this._keyManager.onKeydown(event);
this.stateChanges.next();
}
}
Expand Down
20 changes: 2 additions & 18 deletions src/material/expansion/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {Directive, Input, ContentChildren, QueryList, AfterContentInit} from '@a
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {CdkAccordion} from '@angular/cdk/accordion';
import {FocusKeyManager} from '@angular/cdk/a11y';
import {HOME, END, hasModifierKey} from '@angular/cdk/keycodes';
import {startWith} from 'rxjs/operators';
import {
MAT_ACCORDION,
Expand Down Expand Up @@ -75,27 +74,12 @@ export class MatAccordion extends CdkAccordion implements MatAccordionBase, Afte
this._ownHeaders.notifyOnChanges();
});

this._keyManager = new FocusKeyManager(this._ownHeaders).withWrap();
this._keyManager = new FocusKeyManager(this._ownHeaders).withWrap().withHomeAndEnd();
}

/** Handles keyboard events coming in from the panel headers. */
_handleHeaderKeydown(event: KeyboardEvent) {
const {keyCode} = event;
const manager = this._keyManager;

if (keyCode === HOME) {
if (!hasModifierKey(event)) {
manager.setFirstItemActive();
event.preventDefault();
}
} else if (keyCode === END) {
if (!hasModifierKey(event)) {
manager.setLastItemActive();
event.preventDefault();
}
} else {
this._keyManager.onKeydown(event);
}
this._keyManager.onKeydown(event);
}

_handleHeaderFocus(header: MatExpansionPanelHeader) {
Expand Down
4 changes: 2 additions & 2 deletions src/material/list/selection-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ describe('MatSelectionList without forms', () => {
const manager = selectionList.componentInstance._keyManager;
expect(manager.activeItemIndex).toBe(-1);

const event = createKeyboardEvent('keydown', HOME, undefined, {shift: true});
const event = createKeyboardEvent('keydown', HOME, undefined, {alt: true});

dispatchEvent(selectionList.nativeElement, event);
fixture.detectChanges();
Expand All @@ -477,7 +477,7 @@ describe('MatSelectionList without forms', () => {
const manager = selectionList.componentInstance._keyManager;
expect(manager.activeItemIndex).toBe(-1);

const event = createKeyboardEvent('keydown', END, undefined, {shift: true});
const event = createKeyboardEvent('keydown', END, undefined, {alt: true});

dispatchEvent(selectionList.nativeElement, event);
fixture.detectChanges();
Expand Down
10 changes: 1 addition & 9 deletions src/material/list/selection-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import {SelectionModel} from '@angular/cdk/collections';
import {
A,
DOWN_ARROW,
END,
ENTER,
hasModifierKey,
HOME,
SPACE,
UP_ARROW,
} from '@angular/cdk/keycodes';
Expand Down Expand Up @@ -427,6 +425,7 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
this._keyManager = new FocusKeyManager<MatListOption>(this.options)
.withWrap()
.withTypeAhead()
.withHomeAndEnd()
// Allow disabled items to be focusable. For accessibility reasons, there must be a way for
// screenreader users, that allows reading the different options of the list.
.skipPredicate(() => false)
Expand Down Expand Up @@ -533,13 +532,6 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
event.preventDefault();
}
break;
case HOME:
case END:
if (!hasModifier) {
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
event.preventDefault();
}
break;
default:
// The "A" key gets special treatment, because it's used for the "select all" functionality.
if (keyCode === A && this.multiple && hasModifierKey(event, 'ctrlKey') &&
Expand Down
14 changes: 4 additions & 10 deletions src/material/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import {
RIGHT_ARROW,
DOWN_ARROW,
UP_ARROW,
HOME,
END,
hasModifierKey,
} from '@angular/cdk/keycodes';
import {
Expand Down Expand Up @@ -265,7 +263,10 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>

ngAfterContentInit() {
this._updateDirectDescendants();
this._keyManager = new FocusKeyManager(this._directDescendantItems).withWrap().withTypeAhead();
this._keyManager = new FocusKeyManager(this._directDescendantItems)
.withWrap()
.withTypeAhead()
.withHomeAndEnd();
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.closed.emit('tab'));

// If a user manually (programatically) focuses a menu item, we need to reflect that focus
Expand Down Expand Up @@ -331,13 +332,6 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>
this.closed.emit('keydown');
}
break;
case HOME:
case END:
if (!hasModifierKey(event)) {
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
event.preventDefault();
}
break;
default:
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
manager.setFocusOrigin('keyboard');
Expand Down
Loading