diff --git a/src/cdk/keycodes/BUILD.bazel b/src/cdk/keycodes/BUILD.bazel index 5a47d0e50e5d..da35d5b73e29 100644 --- a/src/cdk/keycodes/BUILD.bazel +++ b/src/cdk/keycodes/BUILD.bazel @@ -1,10 +1,26 @@ package(default_visibility=["//visibility:public"]) -load("//tools:defaults.bzl", "ts_library") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") ts_library( name = "keycodes", - module_name = "@angular/cdk/keycodes", srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]), + module_name = "@angular/cdk/keycodes", deps = ["@matdeps//tslib"], ) + +ts_library( + name = "keycodes_test_sources", + srcs = glob(["**/*.spec.ts"]), + deps = [ + "@matdeps//@types/jasmine", + "//src/cdk/testing", + ":keycodes" + ], + testonly = 1, +) + +ts_web_test_suite( + name = "unit_tests", + deps = [":keycodes_test_sources"], +) diff --git a/src/cdk/keycodes/modifiers.spec.ts b/src/cdk/keycodes/modifiers.spec.ts new file mode 100644 index 000000000000..9d14269e72c2 --- /dev/null +++ b/src/cdk/keycodes/modifiers.spec.ts @@ -0,0 +1,56 @@ +import {createKeyboardEvent} from '@angular/cdk/testing'; +import {hasModifierKey} from './modifiers'; + +describe('keyboard modifiers', () => { + it('should check whether the alt key is pressed', () => { + const event = createKeyboardEvent('keydown', 0); + + expect(hasModifierKey(event)).toBe(false); + Object.defineProperty(event, 'altKey', {get: () => true}); + expect(hasModifierKey(event)).toBe(true); + }); + + it('should check whether the shift key is pressed', () => { + const event = createKeyboardEvent('keydown', 0); + + expect(hasModifierKey(event)).toBe(false); + Object.defineProperty(event, 'shiftKey', {get: () => true}); + expect(hasModifierKey(event)).toBe(true); + }); + + it('should check whether the meta key is pressed', () => { + const event = createKeyboardEvent('keydown', 0); + + expect(hasModifierKey(event)).toBe(false); + Object.defineProperty(event, 'metaKey', {get: () => true}); + expect(hasModifierKey(event)).toBe(true); + }); + + it('should check whether the ctrl key is pressed', () => { + const event = createKeyboardEvent('keydown', 0); + + expect(hasModifierKey(event)).toBe(false); + Object.defineProperty(event, 'ctrlKey', {get: () => true}); + expect(hasModifierKey(event)).toBe(true); + }); + + it('should check if a particular modifier key is pressed', () => { + const event = createKeyboardEvent('keydown', 0); + Object.defineProperty(event, 'ctrlKey', {get: () => true}); + + expect(hasModifierKey(event, 'altKey')).toBe(false); + Object.defineProperty(event, 'altKey', {get: () => true}); + expect(hasModifierKey(event, 'altKey')).toBe(true); + }); + + it('should check if multiple specific modifier keys are pressed', () => { + const event = createKeyboardEvent('keydown', 0); + Object.defineProperty(event, 'ctrlKey', {get: () => true}); + + expect(hasModifierKey(event, 'altKey', 'shiftKey')).toBe(false); + Object.defineProperty(event, 'altKey', {get: () => true}); + Object.defineProperty(event, 'shiftKey', {get: () => true}); + expect(hasModifierKey(event, 'altKey', 'shiftKey')).toBe(true); + }); + +}); diff --git a/src/cdk/keycodes/modifiers.ts b/src/cdk/keycodes/modifiers.ts new file mode 100644 index 000000000000..7f52e999428c --- /dev/null +++ b/src/cdk/keycodes/modifiers.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey' | 'metaKey'; + +/** + * Checks whether a modifier key is pressed. + * @param event Event to be checked. + */ +export function hasModifierKey(event: KeyboardEvent, ...modifiers: ModifierKey[]): boolean { + if (modifiers.length) { + return modifiers.some(modifier => event[modifier]); + } + + return event.altKey || event.shiftKey || event.ctrlKey || event.metaKey; +} diff --git a/src/cdk/keycodes/public-api.ts b/src/cdk/keycodes/public-api.ts index 35f62cbde084..5cf6f7fa9f9c 100644 --- a/src/cdk/keycodes/public-api.ts +++ b/src/cdk/keycodes/public-api.ts @@ -7,3 +7,4 @@ */ export * from './keycodes'; +export * from './modifiers'; diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 7c94963542f8..86e2fd34e98e 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -9,7 +9,7 @@ import {FocusableOption, FocusKeyManager} from '@angular/cdk/a11y'; import {Direction, Directionality} from '@angular/cdk/bidi'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {END, ENTER, HOME, SPACE} from '@angular/cdk/keycodes'; +import {END, ENTER, HOME, SPACE, hasModifierKey} from '@angular/cdk/keycodes'; import { AfterViewInit, ChangeDetectionStrategy, @@ -442,9 +442,7 @@ 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 hasModifier = hasModifierKey(event); const keyCode = event.keyCode; const manager = this._keyManager; diff --git a/src/lib/expansion/expansion-panel-header.ts b/src/lib/expansion/expansion-panel-header.ts index 26a3665f4509..7426b0465b69 100644 --- a/src/lib/expansion/expansion-panel-header.ts +++ b/src/lib/expansion/expansion-panel-header.ts @@ -7,7 +7,7 @@ */ import {FocusMonitor, FocusableOption, FocusOrigin} from '@angular/cdk/a11y'; -import {ENTER, SPACE} from '@angular/cdk/keycodes'; +import {ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -140,7 +140,7 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption { // Toggle for space and enter keys. case SPACE: case ENTER: - if (!event.altKey && !event.metaKey && !event.shiftKey && !event.ctrlKey) { + if (!hasModifierKey(event)) { event.preventDefault(); this._toggle(); } diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 94bab46181b3..f76cb976c2ec 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -75,7 +75,14 @@ def ng_test_library(deps = [], tsconfig = None, **kwargs): **kwargs ) -def ng_web_test_suite(deps = [], srcs = [], static_css = [], bootstrap = [], **kwargs): +def ts_web_test_suite(srcs = [], **kwargs): + _ts_web_test_suite( + # Required for running the compiled ng modules that use TypeScript import helpers. + srcs = ["@matdeps//node_modules/tslib:tslib.js"] + srcs, + **kwargs + ) + +def ng_web_test_suite(deps = [], static_css = [], bootstrap = [], **kwargs): # Always include a prebuilt theme in the test suite because otherwise tests, which depend on CSS # that is needed for measuring, will unexpectedly fail. Also always adding a prebuilt theme # reduces the amount of setup that is needed to create a test suite Bazel target. Note that the @@ -109,9 +116,7 @@ def ng_web_test_suite(deps = [], srcs = [], static_css = [], bootstrap = [], **k """ % css_label ) - _ts_web_test_suite( - # Required for running the compiled ng modules that use TypeScript import helpers. - srcs = ["@matdeps//node_modules/tslib:tslib.js"] + srcs, + ts_web_test_suite( # Depend on our custom test initialization script. This needs to be the first dependency. deps = ["//test:angular_test_init"] + deps, bootstrap = [