Skip to content

Commit 11ad09f

Browse files
authored
fix(material/button-toggle): skip keyboard navigation when modifier key is pressed (#31651)
1 parent 94ab09a commit 11ad09f

File tree

2 files changed

+104
-4
lines changed

2 files changed

+104
-4
lines changed

src/material/button-toggle/button-toggle.spec.ts

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import {dispatchMouseEvent} from '@angular/cdk/testing/private';
1+
import {createKeyboardEvent, dispatchEvent, dispatchMouseEvent} from '@angular/cdk/testing/private';
2+
import {DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, UP_ARROW} from '@angular/cdk/keycodes';
23
import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angular/core';
3-
import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing';
4+
import {
5+
ComponentFixture,
6+
TestBed,
7+
fakeAsync,
8+
flush,
9+
tick,
10+
waitForAsync,
11+
} from '@angular/core/testing';
412
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
513
import {By} from '@angular/platform-browser';
614
import {
@@ -574,6 +582,90 @@ describe('MatButtonToggle without forms', () => {
574582
).length,
575583
).toBe(1);
576584
});
585+
586+
describe('keyboard events', () => {
587+
let LEFT_ARROW_EVENT: KeyboardEvent;
588+
let RIGHT_ARROW_EVENT: KeyboardEvent;
589+
let UP_ARROW_EVENT: KeyboardEvent;
590+
let DOWN_ARROW_EVENT: KeyboardEvent;
591+
592+
beforeEach(waitForAsync(async () => {
593+
LEFT_ARROW_EVENT = createKeyboardEvent('keydown', LEFT_ARROW);
594+
RIGHT_ARROW_EVENT = createKeyboardEvent('keydown', RIGHT_ARROW);
595+
UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW);
596+
DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
597+
}));
598+
599+
it('should not change selection on arrow key press with a modifier key', () => {
600+
expect(groupInstance.value).toBeFalsy();
601+
expect(buttonToggleInstances[0].checked).toBe(false);
602+
603+
[LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW].forEach(keyCode => {
604+
const event = createKeyboardEvent('keydown', keyCode, undefined, {alt: true});
605+
dispatchEvent(innerButtons[0], event);
606+
fixture.detectChanges();
607+
608+
// Nothing should happen and the default should not be prevented.
609+
expect(groupInstance.value)
610+
.withContext(`Expected no value change for ${keyCode}`)
611+
.toBeFalsy();
612+
expect(buttonToggleInstances[0].checked)
613+
.withContext(`Expected no checked change for ${keyCode}`)
614+
.toBe(false);
615+
expect(event.defaultPrevented)
616+
.withContext(`Expected no default prevention for ${keyCode}`)
617+
.toBe(false);
618+
});
619+
});
620+
621+
it('should change selection on RIGHT_ARROW press', () => {
622+
expect(groupInstance.value).toBeFalsy();
623+
624+
dispatchEvent(innerButtons[0], RIGHT_ARROW_EVENT);
625+
fixture.detectChanges();
626+
627+
expect(groupInstance.value).toBe('test2');
628+
expect(buttonToggleInstances[1].checked).toBe(true);
629+
expect(RIGHT_ARROW_EVENT.defaultPrevented).toBe(true);
630+
});
631+
632+
it('should change selection on LEFT_ARROW press', () => {
633+
innerButtons[1].click();
634+
fixture.detectChanges();
635+
expect(groupInstance.value).toBe('test2');
636+
637+
dispatchEvent(innerButtons[1], LEFT_ARROW_EVENT);
638+
fixture.detectChanges();
639+
640+
expect(groupInstance.value).toBe('test1');
641+
expect(buttonToggleInstances[0].checked).toBe(true);
642+
expect(LEFT_ARROW_EVENT.defaultPrevented).toBe(true);
643+
});
644+
645+
it('should change selection on DOWN_ARROW press', () => {
646+
expect(groupInstance.value).toBeFalsy();
647+
648+
dispatchEvent(innerButtons[0], DOWN_ARROW_EVENT);
649+
fixture.detectChanges();
650+
651+
expect(groupInstance.value).toBe('test2');
652+
expect(buttonToggleInstances[1].checked).toBe(true);
653+
expect(DOWN_ARROW_EVENT.defaultPrevented).toBe(true);
654+
});
655+
656+
it('should change selection on UP_ARROW press', () => {
657+
innerButtons[1].click();
658+
fixture.detectChanges();
659+
expect(groupInstance.value).toBe('test2');
660+
661+
dispatchEvent(innerButtons[1], UP_ARROW_EVENT);
662+
fixture.detectChanges();
663+
664+
expect(groupInstance.value).toBe('test1');
665+
expect(buttonToggleInstances[0].checked).toBe(true);
666+
expect(UP_ARROW_EVENT.defaultPrevented).toBe(true);
667+
});
668+
});
577669
});
578670

579671
describe('with initial value and change event', () => {

src/material/button-toggle/button-toggle.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
import {_IdGenerator, FocusMonitor} from '@angular/cdk/a11y';
1010
import {Direction, Directionality} from '@angular/cdk/bidi';
1111
import {SelectionModel} from '@angular/cdk/collections';
12-
import {DOWN_ARROW, ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE, UP_ARROW} from '@angular/cdk/keycodes';
12+
import {
13+
DOWN_ARROW,
14+
ENTER,
15+
LEFT_ARROW,
16+
RIGHT_ARROW,
17+
SPACE,
18+
UP_ARROW,
19+
hasModifierKey,
20+
} from '@angular/cdk/keycodes';
1321
import {_CdkPrivateStyleLoader} from '@angular/cdk/private';
1422
import {
1523
AfterContentInit,
@@ -331,7 +339,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
331339

332340
/** Handle keydown event calling to single-select button toggle. */
333341
protected _keydown(event: KeyboardEvent) {
334-
if (this.multiple || this.disabled) {
342+
if (this.multiple || this.disabled || hasModifierKey(event)) {
335343
return;
336344
}
337345

0 commit comments

Comments
 (0)