Skip to content

Commit 4d37216

Browse files
committed
refactor: handle home/end presses through key manager
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.
1 parent 544e335 commit 4d37216

File tree

14 files changed

+62
-112
lines changed

14 files changed

+62
-112
lines changed

src/cdk-experimental/listbox/listbox.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
QueryList
1717
} from '@angular/core';
1818
import {ActiveDescendantKeyManager, Highlightable, ListKeyManagerOption} from '@angular/cdk/a11y';
19-
import {END, ENTER, HOME, SPACE} from '@angular/cdk/keycodes';
19+
import {ENTER, SPACE} from '@angular/cdk/keycodes';
2020
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
2121

2222
let nextId = 0;
@@ -145,7 +145,7 @@ export class CdkListbox implements AfterContentInit, OnDestroy {
145145

146146
ngAfterContentInit() {
147147
this._listKeyManager = new ActiveDescendantKeyManager(this._options)
148-
.withWrap().withVerticalOrientation().withTypeAhead();
148+
.withWrap().withVerticalOrientation().withTypeAhead().withHomeAndEnd();
149149
}
150150

151151
ngOnDestroy() {
@@ -160,11 +160,7 @@ export class CdkListbox implements AfterContentInit, OnDestroy {
160160
const manager = this._listKeyManager;
161161
const keyCode = event.keyCode;
162162

163-
if (keyCode === HOME || keyCode === END) {
164-
event.preventDefault();
165-
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
166-
167-
} else if (keyCode === SPACE || keyCode === ENTER) {
163+
if (keyCode === SPACE || keyCode === ENTER) {
168164
if (manager.activeItem && !manager.isTyping()) {
169165
this._toggleActiveOption();
170166
}

src/cdk/a11y/key-manager/list-key-manager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,12 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
178178
}
179179

180180
/**
181-
* Configures the key manager to focus the first and last items
182-
* respectively when the Home key and End Key are pressed.
181+
* Configures the key manager to activate the first and last items
182+
* respectively when the Home or End key is pressed.
183+
* @param enabled Whether pressing the Home or End key activates the first/last item.
183184
*/
184-
withHomeAndEnd(): this {
185-
this._homeAndEnd = true;
185+
withHomeAndEnd(enabled: boolean = true): this {
186+
this._homeAndEnd = enabled;
186187
return this;
187188
}
188189

src/cdk/stepper/stepper.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
coerceNumberProperty,
1515
NumberInput
1616
} from '@angular/cdk/coercion';
17-
import {END, ENTER, hasModifierKey, HOME, SPACE} from '@angular/cdk/keycodes';
17+
import {ENTER, hasModifierKey, SPACE} from '@angular/cdk/keycodes';
1818
import {DOCUMENT} from '@angular/common';
1919
import {
2020
AfterViewInit,
@@ -345,6 +345,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
345345
// AfterViewInit so we're guaranteed for both view and content children to be defined.
346346
this._keyManager = new FocusKeyManager<FocusableOption>(this._stepHeader)
347347
.withWrap()
348+
.withHomeAndEnd()
348349
.withVerticalOrientation(this._orientation === 'vertical');
349350

350351
(this._dir ? (this._dir.change as Observable<Direction>) : observableOf<Direction>())
@@ -480,12 +481,6 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
480481
(keyCode === SPACE || keyCode === ENTER)) {
481482
this.selectedIndex = manager.activeItemIndex;
482483
event.preventDefault();
483-
} else if (keyCode === HOME) {
484-
manager.setFirstItemActive();
485-
event.preventDefault();
486-
} else if (keyCode === END) {
487-
manager.setLastItemActive();
488-
event.preventDefault();
489484
} else {
490485
manager.onKeydown(event);
491486
}

src/material-experimental/mdc-chips/chip-grid.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Directionality} from '@angular/cdk/bidi';
1010
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
11-
import {BACKSPACE, TAB, HOME, END} from '@angular/cdk/keycodes';
11+
import {BACKSPACE, TAB} from '@angular/cdk/keycodes';
1212
import {
1313
AfterContentInit,
1414
AfterViewInit,
@@ -416,15 +416,7 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn
416416
}
417417
event.preventDefault();
418418
} else if (this._originatesFromChip(event)) {
419-
if (keyCode === HOME) {
420-
manager.setFirstCellActive();
421-
event.preventDefault();
422-
} else if (keyCode === END) {
423-
manager.setLastCellActive();
424-
event.preventDefault();
425-
} else {
426-
manager.onKeydown(event);
427-
}
419+
manager.onKeydown(event);
428420
}
429421
this.stateChanges.next();
430422
}
@@ -453,6 +445,7 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn
453445
/** Initializes the key manager to manage focus. */
454446
private _initKeyManager() {
455447
this._keyManager = new GridFocusKeyManager(this._chips)
448+
.withHomeAndEnd()
456449
.withDirectionality(this._dir ? this._dir.value : 'ltr');
457450

458451
if (this._dir) {

src/material-experimental/mdc-chips/chip-listbox.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import {FocusKeyManager} from '@angular/cdk/a11y';
1010
import {Directionality} from '@angular/cdk/bidi';
1111
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
12-
import {END, HOME} from '@angular/cdk/keycodes';
1312
import {
1413
AfterContentInit,
1514
ChangeDetectionStrategy,
@@ -354,15 +353,7 @@ export class MatChipListbox extends MatChipSet implements AfterContentInit, Cont
354353
*/
355354
_keydown(event: KeyboardEvent) {
356355
if (this._originatesFromChip(event)) {
357-
if (event.keyCode === HOME) {
358-
this._keyManager.setFirstItemActive();
359-
event.preventDefault();
360-
} else if (event.keyCode === END) {
361-
this._keyManager.setLastItemActive();
362-
event.preventDefault();
363-
} else {
364-
this._keyManager.onKeydown(event);
365-
}
356+
this._keyManager.onKeydown(event);
366357
}
367358
}
368359

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

462454
if (this._dir) {

src/material-experimental/mdc-chips/grid-key-manager.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
DOWN_ARROW,
1414
LEFT_ARROW,
1515
RIGHT_ARROW,
16+
HOME,
17+
END,
1618
} from '@angular/cdk/keycodes';
1719

1820

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

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

99+
/**
100+
* Configures the key manager to activate the first and last items
101+
* respectively when the Home or End key is pressed.
102+
* @param enabled Whether pressing the Home or End key activates the first/last item.
103+
*/
104+
withHomeAndEnd(enabled: boolean = true): this {
105+
this._homeAndEnd = enabled;
106+
return this;
107+
}
108+
96109
/**
97110
* Sets the active cell depending on the key event passed in.
98111
* @param event Keyboard event to be used for determining which element should be active.
@@ -117,6 +130,22 @@ export class GridKeyManager<T> {
117130
this._dir === 'rtl' ? this.setNextColumnActive() : this.setPreviousColumnActive();
118131
break;
119132

133+
case HOME:
134+
if (this._homeAndEnd) {
135+
this.setFirstCellActive();
136+
break;
137+
} else {
138+
return;
139+
}
140+
141+
case END:
142+
if (this._homeAndEnd) {
143+
this.setLastCellActive();
144+
break;
145+
} else {
146+
return;
147+
}
148+
120149
default:
121150
// Note that we return here, in order to avoid preventing
122151
// the default action of non-navigational keys.

src/material/chips/chip-list.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {FocusKeyManager} from '@angular/cdk/a11y';
1010
import {Directionality} from '@angular/cdk/bidi';
1111
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1212
import {SelectionModel} from '@angular/cdk/collections';
13-
import {BACKSPACE, END, HOME} from '@angular/cdk/keycodes';
13+
import {BACKSPACE} from '@angular/cdk/keycodes';
1414
import {
1515
AfterContentInit,
1616
ChangeDetectionStrategy,
@@ -351,6 +351,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
351351
this._keyManager = new FocusKeyManager<MatChip>(this.chips)
352352
.withWrap()
353353
.withVerticalOrientation()
354+
.withHomeAndEnd()
354355
.withHorizontalOrientation(this._dir ? this._dir.value : 'ltr');
355356

356357
if (this._dir) {
@@ -499,16 +500,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
499500
this._keyManager.setLastItemActive();
500501
event.preventDefault();
501502
} else if (target && target.classList.contains('mat-chip')) {
502-
if (event.keyCode === HOME) {
503-
this._keyManager.setFirstItemActive();
504-
event.preventDefault();
505-
} else if (event.keyCode === END) {
506-
this._keyManager.setLastItemActive();
507-
event.preventDefault();
508-
} else {
509-
this._keyManager.onKeydown(event);
510-
}
511-
503+
this._keyManager.onKeydown(event);
512504
this.stateChanges.next();
513505
}
514506
}

src/material/expansion/accordion.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {Directive, Input, ContentChildren, QueryList, AfterContentInit} from '@a
1010
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {CdkAccordion} from '@angular/cdk/accordion';
1212
import {FocusKeyManager} from '@angular/cdk/a11y';
13-
import {HOME, END, hasModifierKey} from '@angular/cdk/keycodes';
1413
import {startWith} from 'rxjs/operators';
1514
import {
1615
MAT_ACCORDION,
@@ -75,27 +74,12 @@ export class MatAccordion extends CdkAccordion implements MatAccordionBase, Afte
7574
this._ownHeaders.notifyOnChanges();
7675
});
7776

78-
this._keyManager = new FocusKeyManager(this._ownHeaders).withWrap();
77+
this._keyManager = new FocusKeyManager(this._ownHeaders).withWrap().withHomeAndEnd();
7978
}
8079

8180
/** Handles keyboard events coming in from the panel headers. */
8281
_handleHeaderKeydown(event: KeyboardEvent) {
83-
const {keyCode} = event;
84-
const manager = this._keyManager;
85-
86-
if (keyCode === HOME) {
87-
if (!hasModifierKey(event)) {
88-
manager.setFirstItemActive();
89-
event.preventDefault();
90-
}
91-
} else if (keyCode === END) {
92-
if (!hasModifierKey(event)) {
93-
manager.setLastItemActive();
94-
event.preventDefault();
95-
}
96-
} else {
97-
this._keyManager.onKeydown(event);
98-
}
82+
this._keyManager.onKeydown(event);
9983
}
10084

10185
_handleHeaderFocus(header: MatExpansionPanelHeader) {

src/material/list/selection-list.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ describe('MatSelectionList without forms', () => {
459459
expect(manager.activeItemIndex).toBe(-1);
460460

461461
const event = createKeyboardEvent('keydown', HOME);
462-
Object.defineProperty(event, 'shiftKey', { get: () => true });
462+
Object.defineProperty(event, 'altKey', { get: () => true });
463463

464464
dispatchEvent(selectionList.nativeElement, event);
465465
fixture.detectChanges();
@@ -484,7 +484,7 @@ describe('MatSelectionList without forms', () => {
484484
expect(manager.activeItemIndex).toBe(-1);
485485

486486
const event = createKeyboardEvent('keydown', END);
487-
Object.defineProperty(event, 'shiftKey', { get: () => true });
487+
Object.defineProperty(event, 'altKey', { get: () => true });
488488

489489
dispatchEvent(selectionList.nativeElement, event);
490490
fixture.detectChanges();

src/material/list/selection-list.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ import {SelectionModel} from '@angular/cdk/collections';
1212
import {
1313
A,
1414
DOWN_ARROW,
15-
END,
1615
ENTER,
1716
hasModifierKey,
18-
HOME,
1917
SPACE,
2018
UP_ARROW,
2119
} from '@angular/cdk/keycodes';
@@ -427,6 +425,7 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
427425
this._keyManager = new FocusKeyManager<MatListOption>(this.options)
428426
.withWrap()
429427
.withTypeAhead()
428+
.withHomeAndEnd()
430429
// Allow disabled items to be focusable. For accessibility reasons, there must be a way for
431430
// screenreader users, that allows reading the different options of the list.
432431
.skipPredicate(() => false)
@@ -533,13 +532,6 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
533532
event.preventDefault();
534533
}
535534
break;
536-
case HOME:
537-
case END:
538-
if (!hasModifier) {
539-
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
540-
event.preventDefault();
541-
}
542-
break;
543535
default:
544536
// The "A" key gets special treatment, because it's used for the "select all" functionality.
545537
if (keyCode === A && this.multiple && hasModifierKey(event, 'ctrlKey') &&

src/material/menu/menu.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import {
1515
RIGHT_ARROW,
1616
DOWN_ARROW,
1717
UP_ARROW,
18-
HOME,
19-
END,
2018
hasModifierKey,
2119
} from '@angular/cdk/keycodes';
2220
import {
@@ -264,7 +262,10 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>
264262

265263
ngAfterContentInit() {
266264
this._updateDirectDescendants();
267-
this._keyManager = new FocusKeyManager(this._directDescendantItems).withWrap().withTypeAhead();
265+
this._keyManager = new FocusKeyManager(this._directDescendantItems)
266+
.withWrap()
267+
.withTypeAhead()
268+
.withHomeAndEnd();
268269
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.closed.emit('tab'));
269270

270271
// If a user manually (programatically) focuses a menu item, we need to reflect that focus
@@ -330,13 +331,6 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>
330331
this.closed.emit('keydown');
331332
}
332333
break;
333-
case HOME:
334-
case END:
335-
if (!hasModifierKey(event)) {
336-
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
337-
event.preventDefault();
338-
}
339-
break;
340334
default:
341335
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
342336
manager.setFocusOrigin('keyboard');

0 commit comments

Comments
 (0)