diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index d2af09b354eb..f768c5071d95 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -434,19 +434,24 @@ export class CdkStepper implements AfterViewInit, OnDestroy { } _onKeydown(event: KeyboardEvent) { + // TODO(crisbeto): move into a CDK utility once + // the similar PRs for other components are merged in. + const hasModifier = event.altKey || event.shiftKey || event.ctrlKey || event.metaKey; const keyCode = event.keyCode; + const manager = this._keyManager; - if (this._keyManager.activeItemIndex != null && (keyCode === SPACE || keyCode === ENTER)) { - this.selectedIndex = this._keyManager.activeItemIndex; + if (manager.activeItemIndex != null && !hasModifier && + (keyCode === SPACE || keyCode === ENTER)) { + this.selectedIndex = manager.activeItemIndex; event.preventDefault(); } else if (keyCode === HOME) { - this._keyManager.setFirstItemActive(); + manager.setFirstItemActive(); event.preventDefault(); } else if (keyCode === END) { - this._keyManager.setLastItemActive(); + manager.setLastItemActive(); event.preventDefault(); } else { - this._keyManager.onKeydown(event); + manager.onKeydown(event); } } diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index de5d8f033fc6..69993394e177 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -10,7 +10,7 @@ import { UP_ARROW, } from '@angular/cdk/keycodes'; import {StepperOrientation, MAT_STEPPER_GLOBAL_OPTIONS, STEP_STATE} from '@angular/cdk/stepper'; -import {dispatchKeyboardEvent} from '@angular/cdk/testing'; +import {dispatchKeyboardEvent, createKeyboardEvent, dispatchEvent} from '@angular/cdk/testing'; import {Component, DebugElement, EventEmitter, OnInit, Type, Provider} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing'; import { @@ -345,6 +345,16 @@ describe('MatStepper', () => { expect(stepperComponent.selectedIndex).toBe(1); }); + + it('should not do anything when pressing the ENTER key with a modifier', () => { + const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); + assertSelectKeyWithModifierInteraction(fixture, stepHeaders, 'vertical', ENTER); + }); + + it('should not do anything when pressing the SPACE key with a modifier', () => { + const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); + assertSelectKeyWithModifierInteraction(fixture, stepHeaders, 'vertical', SPACE); + }); }); describe('basic stepper when attempting to set the selected step too early', () => { @@ -1063,6 +1073,38 @@ function assertArrowKeyInteractionInRtl(fixture: ComponentFixture, expect(stepperComponent._getFocusIndex()).toBe(0); } +/** Asserts that keyboard interaction works correctly when the user is pressing a modifier key. */ +function assertSelectKeyWithModifierInteraction(fixture: ComponentFixture, + stepHeaders: DebugElement[], + orientation: StepperOrientation, + selectionKey: number) { + const stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance; + const modifiers = ['altKey', 'shiftKey', 'ctrlKey', 'metaKey']; + + expect(stepperComponent._getFocusIndex()).toBe(0); + expect(stepperComponent.selectedIndex).toBe(0); + + dispatchKeyboardEvent(stepHeaders[0].nativeElement, 'keydown', + orientation === 'vertical' ? DOWN_ARROW : RIGHT_ARROW); + fixture.detectChanges(); + + expect(stepperComponent._getFocusIndex()) + .toBe(1, 'Expected index of focused step to increase by 1 after pressing the next key.'); + expect(stepperComponent.selectedIndex) + .toBe(0, 'Expected index of selected step to remain unchanged after pressing the next key.'); + + modifiers.forEach(modifier => { + const event: KeyboardEvent = createKeyboardEvent('keydown', selectionKey); + Object.defineProperty(event, modifier, {get: () => true}); + dispatchEvent(stepHeaders[1].nativeElement, event); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(0, `Expected selected index to remain unchanged ` + + `when pressing the selection key with ${modifier} modifier.`); + expect(event.defaultPrevented).toBe(false); + }); +} + function asyncValidator(minLength: number, validationTrigger: Subject): AsyncValidatorFn { return (control: AbstractControl): Observable => { return validationTrigger.pipe(