Skip to content

Commit

Permalink
feat(material/select): add page down/up button functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
forsti0506 committed Aug 21, 2022
1 parent 8f11370 commit 55f3e82
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/cdk/a11y/key-manager/list-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
hasModifierKey,
HOME,
END,
PAGE_UP,
PAGE_DOWN,
} from '@angular/cdk/keycodes';
import {debounceTime, filter, map, tap} from 'rxjs/operators';

Expand Down Expand Up @@ -280,6 +282,22 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
return;
}

case PAGE_UP:
if (isModifierAllowed) {
this.setFirstItemActive();
break;
} else {
return;
}

case PAGE_DOWN:
if (isModifierAllowed) {
this.setLastItemActive();
break;
} else {
return;
}

default:
if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) {
// Attempt to use the `event.key` which also maps it to the user's keyboard language,
Expand Down
90 changes: 90 additions & 0 deletions src/material/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
UP_ARROW,
A,
ESCAPE,
PAGE_DOWN,
PAGE_UP,
} from '@angular/cdk/keycodes';
import {OverlayContainer} from '@angular/cdk/overlay';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
Expand Down Expand Up @@ -421,6 +423,36 @@ describe('MDC-based MatSelect', () => {
flush();
}));

it('should select first/last options via the PAGE_DOWN/PAGE_UP keys on a closed select', fakeAsync(() => {
const formControl = fixture.componentInstance.control;
const firstOption = fixture.componentInstance.options.first;
const lastOption = fixture.componentInstance.options.last;

expect(formControl.value).withContext('Expected no initial value.').toBeFalsy();

const endEvent = dispatchKeyboardEvent(select, 'keydown', PAGE_DOWN);

expect(endEvent.defaultPrevented).toBe(true);
expect(lastOption.selected)
.withContext('Expected last option to be selected.')
.toBe(true);
expect(formControl.value)
.withContext('Expected value from last option to have been set on the model.')
.toBe(lastOption.value);

const homeEvent = dispatchKeyboardEvent(select, 'keydown', PAGE_UP);

expect(homeEvent.defaultPrevented).toBe(true);
expect(firstOption.selected)
.withContext('Expected first option to be selected.')
.toBe(true);
expect(formControl.value)
.withContext('Expected value from first option to have been set on the model.')
.toBe(firstOption.value);

flush();
}));

it('should resume focus from selected item after selecting via click', fakeAsync(() => {
const formControl = fixture.componentInstance.control;
const options = fixture.componentInstance.options.toArray();
Expand Down Expand Up @@ -1492,6 +1524,36 @@ describe('MDC-based MatSelect', () => {
expect(event.defaultPrevented).toBe(true);
}));

it('should focus the last option when pressing PAGE_DOWN', fakeAsync(() => {
fixture.componentInstance.control.setValue('pizza-1');
fixture.detectChanges();

trigger.click();
fixture.detectChanges();
flush();

const event = dispatchKeyboardEvent(trigger, 'keydown', PAGE_DOWN);
fixture.detectChanges();

expect(fixture.componentInstance.select._keyManager.activeItemIndex).toBe(7);
expect(event.defaultPrevented).toBe(true);
}));

it('should focus the first option when pressing PAGE_UP', fakeAsync(() => {
fixture.componentInstance.control.setValue('pizza-1');
fixture.detectChanges();

trigger.click();
fixture.detectChanges();
flush();

const event = dispatchKeyboardEvent(trigger, 'keydown', PAGE_UP);
fixture.detectChanges();

expect(fixture.componentInstance.select._keyManager.activeItemIndex).toBe(0);
expect(event.defaultPrevented).toBe(true);
}));

it('should be able to set extra classes on the panel', fakeAsync(() => {
trigger.click();
fixture.detectChanges();
Expand Down Expand Up @@ -2349,6 +2411,34 @@ describe('MDC-based MatSelect', () => {
.toBe(1173);
}));

it('should scroll to the top when pressing PAGE_UP', fakeAsync(() => {
for (let i = 0; i < 20; i++) {
dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW);
fixture.detectChanges();
}

expect(panel.scrollTop)
.withContext('Expected panel to be scrolled down.')
.toBeGreaterThan(0);

dispatchKeyboardEvent(host, 'keydown', PAGE_UP);
fixture.detectChanges();

// 8px is the top padding of the panel.
expect(panel.scrollTop).withContext('Expected panel to be scrolled to the top').toBe(8);
}));

it('should scroll to the bottom of the panel when pressing PAGE_DOWN', fakeAsync(() => {
dispatchKeyboardEvent(host, 'keydown', PAGE_DOWN);
fixture.detectChanges();

// <top padding> + <option amount> * <option height> - <panel height> =
// 8 + 30 * 48 - 275 = 1173
expect(panel.scrollTop)
.withContext('Expected panel to be scrolled to the bottom')
.toBe(1173);
}));

it('should scroll to the active option when typing', fakeAsync(() => {
for (let i = 0; i < 15; i++) {
// Press the letter 'o' 15 times since all the options are named 'Option <index>'
Expand Down

0 comments on commit 55f3e82

Please sign in to comment.