mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(menu): support focus first/last item via home/end keys (#14896)
Adds support for jumping to the first/last item using the home/end keys.
This commit is contained in:
committed by
Jeremy Elbourn
parent
677db8cfc1
commit
0185dd1bcc
@@ -9,7 +9,16 @@
|
||||
import {FocusKeyManager, FocusOrigin} from '@angular/cdk/a11y';
|
||||
import {Direction} from '@angular/cdk/bidi';
|
||||
import {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
import {ESCAPE, LEFT_ARROW, RIGHT_ARROW, DOWN_ARROW, UP_ARROW} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
ESCAPE,
|
||||
LEFT_ARROW,
|
||||
RIGHT_ARROW,
|
||||
DOWN_ARROW,
|
||||
UP_ARROW,
|
||||
HOME,
|
||||
END,
|
||||
hasModifierKey,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
@@ -268,6 +277,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
|
||||
/** Handle a keyboard event from the menu, delegating to the appropriate action. */
|
||||
_handleKeydown(event: KeyboardEvent) {
|
||||
const keyCode = event.keyCode;
|
||||
const manager = this._keyManager;
|
||||
|
||||
switch (keyCode) {
|
||||
case ESCAPE:
|
||||
@@ -283,12 +293,19 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
|
||||
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) {
|
||||
this._keyManager.setFocusOrigin('keyboard');
|
||||
manager.setFocusOrigin('keyboard');
|
||||
}
|
||||
|
||||
this._keyManager.onKeydown(event);
|
||||
manager.onKeydown(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {OverlayContainer, Overlay} from '@angular/cdk/overlay';
|
||||
import {ESCAPE, LEFT_ARROW, RIGHT_ARROW, DOWN_ARROW, TAB} from '@angular/cdk/keycodes';
|
||||
import {ESCAPE, LEFT_ARROW, RIGHT_ARROW, DOWN_ARROW, TAB, HOME, END} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
MAT_MENU_DEFAULT_OPTIONS,
|
||||
MatMenu,
|
||||
@@ -624,6 +624,104 @@ describe('MatMenu', () => {
|
||||
expect(overlayContainerElement.textContent).toBe('');
|
||||
}));
|
||||
|
||||
it('should focus the first item when pressing home', fakeAsync(() => {
|
||||
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.openMenu();
|
||||
fixture.detectChanges();
|
||||
|
||||
const panel = overlayContainerElement.querySelector('.mat-menu-panel')!;
|
||||
const items = Array.from(panel.querySelectorAll('.mat-menu-item')) as HTMLElement[];
|
||||
items.forEach(patchElementFocus);
|
||||
|
||||
// Focus the last item since focus starts from the first one.
|
||||
items[items.length - 1].focus();
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(items[0], 'focus').and.callThrough();
|
||||
|
||||
const event = dispatchKeyboardEvent(panel, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(items[0].focus).toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should not focus the first item when pressing home with a modifier key', fakeAsync(() => {
|
||||
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.openMenu();
|
||||
fixture.detectChanges();
|
||||
|
||||
const panel = overlayContainerElement.querySelector('.mat-menu-panel')!;
|
||||
const items = Array.from(panel.querySelectorAll('.mat-menu-item')) as HTMLElement[];
|
||||
items.forEach(patchElementFocus);
|
||||
|
||||
// Focus the last item since focus starts from the first one.
|
||||
items[items.length - 1].focus();
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(items[0], 'focus').and.callThrough();
|
||||
|
||||
const event = createKeyboardEvent('keydown', HOME);
|
||||
Object.defineProperty(event, 'altKey', {get: () => true});
|
||||
|
||||
dispatchEvent(panel, event);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(items[0].focus).not.toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should focus the last item when pressing end', fakeAsync(() => {
|
||||
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.openMenu();
|
||||
fixture.detectChanges();
|
||||
|
||||
const panel = overlayContainerElement.querySelector('.mat-menu-panel')!;
|
||||
const items = Array.from(panel.querySelectorAll('.mat-menu-item')) as HTMLElement[];
|
||||
items.forEach(patchElementFocus);
|
||||
|
||||
spyOn(items[items.length - 1], 'focus').and.callThrough();
|
||||
|
||||
const event = dispatchKeyboardEvent(panel, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(items[items.length - 1].focus).toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should not focus the last item when pressing end with a modifier key', fakeAsync(() => {
|
||||
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.trigger.openMenu();
|
||||
fixture.detectChanges();
|
||||
|
||||
const panel = overlayContainerElement.querySelector('.mat-menu-panel')!;
|
||||
const items = Array.from(panel.querySelectorAll('.mat-menu-item')) as HTMLElement[];
|
||||
items.forEach(patchElementFocus);
|
||||
|
||||
spyOn(items[items.length - 1], 'focus').and.callThrough();
|
||||
|
||||
const event = createKeyboardEvent('keydown', END);
|
||||
Object.defineProperty(event, 'altKey', {get: () => true});
|
||||
|
||||
dispatchEvent(panel, event);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(items[items.length - 1].focus).not.toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
flush();
|
||||
}));
|
||||
|
||||
describe('lazy rendering', () => {
|
||||
it('should be able to render the menu content lazily', fakeAsync(() => {
|
||||
const fixture = createComponent(SimpleLazyMenu);
|
||||
|
||||
Reference in New Issue
Block a user