Skip to content

Commit

Permalink
refactor: handle home/end presses through key manager (#19955)
Browse files Browse the repository at this point in the history
Based on top of #19834, changes the places where we were handling home/end to go through the key manager.

Also allows `withHomeAndEnd` to be turned off, similarly to what we're doing with the other methods.
  • Loading branch information
crisbeto committed Aug 21, 2020
1 parent 3345a9a commit d96de48
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 120 deletions.
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 @@ -179,11 +179,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 @@ -350,6 +350,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 @@ -487,12 +488,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 @@ -477,7 +477,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 @@ -501,7 +501,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 @@ -431,6 +429,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 @@ -556,13 +555,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

0 comments on commit d96de48

Please sign in to comment.