Skip to content

Commit 7899863

Browse files
crisbetovivian-hu-zz
authored andcommitted
feat(keycodes): add utilities for checking modifier keys (#13933)
Based off of the conversation in #13790, these changes add some utilities for dealing with modifier keys on keyboard events.
1 parent 31bffb3 commit 7899863

File tree

7 files changed

+109
-12
lines changed

7 files changed

+109
-12
lines changed

src/cdk/keycodes/BUILD.bazel

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
package(default_visibility=["//visibility:public"])
22

3-
load("//tools:defaults.bzl", "ts_library")
3+
load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite")
44

55
ts_library(
66
name = "keycodes",
7-
module_name = "@angular/cdk/keycodes",
87
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
8+
module_name = "@angular/cdk/keycodes",
99
deps = ["@matdeps//tslib"],
1010
)
11+
12+
ts_library(
13+
name = "keycodes_test_sources",
14+
srcs = glob(["**/*.spec.ts"]),
15+
deps = [
16+
"@matdeps//@types/jasmine",
17+
"//src/cdk/testing",
18+
":keycodes"
19+
],
20+
testonly = 1,
21+
)
22+
23+
ts_web_test_suite(
24+
name = "unit_tests",
25+
deps = [":keycodes_test_sources"],
26+
)

src/cdk/keycodes/modifiers.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {createKeyboardEvent} from '@angular/cdk/testing';
2+
import {hasModifierKey} from './modifiers';
3+
4+
describe('keyboard modifiers', () => {
5+
it('should check whether the alt key is pressed', () => {
6+
const event = createKeyboardEvent('keydown', 0);
7+
8+
expect(hasModifierKey(event)).toBe(false);
9+
Object.defineProperty(event, 'altKey', {get: () => true});
10+
expect(hasModifierKey(event)).toBe(true);
11+
});
12+
13+
it('should check whether the shift key is pressed', () => {
14+
const event = createKeyboardEvent('keydown', 0);
15+
16+
expect(hasModifierKey(event)).toBe(false);
17+
Object.defineProperty(event, 'shiftKey', {get: () => true});
18+
expect(hasModifierKey(event)).toBe(true);
19+
});
20+
21+
it('should check whether the meta key is pressed', () => {
22+
const event = createKeyboardEvent('keydown', 0);
23+
24+
expect(hasModifierKey(event)).toBe(false);
25+
Object.defineProperty(event, 'metaKey', {get: () => true});
26+
expect(hasModifierKey(event)).toBe(true);
27+
});
28+
29+
it('should check whether the ctrl key is pressed', () => {
30+
const event = createKeyboardEvent('keydown', 0);
31+
32+
expect(hasModifierKey(event)).toBe(false);
33+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
34+
expect(hasModifierKey(event)).toBe(true);
35+
});
36+
37+
it('should check if a particular modifier key is pressed', () => {
38+
const event = createKeyboardEvent('keydown', 0);
39+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
40+
41+
expect(hasModifierKey(event, 'altKey')).toBe(false);
42+
Object.defineProperty(event, 'altKey', {get: () => true});
43+
expect(hasModifierKey(event, 'altKey')).toBe(true);
44+
});
45+
46+
it('should check if multiple specific modifier keys are pressed', () => {
47+
const event = createKeyboardEvent('keydown', 0);
48+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
49+
50+
expect(hasModifierKey(event, 'altKey', 'shiftKey')).toBe(false);
51+
Object.defineProperty(event, 'altKey', {get: () => true});
52+
Object.defineProperty(event, 'shiftKey', {get: () => true});
53+
expect(hasModifierKey(event, 'altKey', 'shiftKey')).toBe(true);
54+
});
55+
56+
});

src/cdk/keycodes/modifiers.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey' | 'metaKey';
10+
11+
/**
12+
* Checks whether a modifier key is pressed.
13+
* @param event Event to be checked.
14+
*/
15+
export function hasModifierKey(event: KeyboardEvent, ...modifiers: ModifierKey[]): boolean {
16+
if (modifiers.length) {
17+
return modifiers.some(modifier => event[modifier]);
18+
}
19+
20+
return event.altKey || event.shiftKey || event.ctrlKey || event.metaKey;
21+
}

src/cdk/keycodes/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
*/
88

99
export * from './keycodes';
10+
export * from './modifiers';

src/cdk/stepper/stepper.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {FocusableOption, FocusKeyManager} from '@angular/cdk/a11y';
1010
import {Direction, Directionality} from '@angular/cdk/bidi';
1111
import {coerceBooleanProperty} from '@angular/cdk/coercion';
12-
import {END, ENTER, HOME, SPACE} from '@angular/cdk/keycodes';
12+
import {END, ENTER, HOME, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
1313
import {
1414
AfterViewInit,
1515
ChangeDetectionStrategy,
@@ -442,9 +442,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
442442
}
443443

444444
_onKeydown(event: KeyboardEvent) {
445-
// TODO(crisbeto): move into a CDK utility once
446-
// the similar PRs for other components are merged in.
447-
const hasModifier = event.altKey || event.shiftKey || event.ctrlKey || event.metaKey;
445+
const hasModifier = hasModifierKey(event);
448446
const keyCode = event.keyCode;
449447
const manager = this._keyManager;
450448

src/lib/expansion/expansion-panel-header.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {FocusMonitor, FocusableOption, FocusOrigin} from '@angular/cdk/a11y';
10-
import {ENTER, SPACE} from '@angular/cdk/keycodes';
10+
import {ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
1111
import {
1212
ChangeDetectionStrategy,
1313
ChangeDetectorRef,
@@ -140,7 +140,7 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
140140
// Toggle for space and enter keys.
141141
case SPACE:
142142
case ENTER:
143-
if (!event.altKey && !event.metaKey && !event.shiftKey && !event.ctrlKey) {
143+
if (!hasModifierKey(event)) {
144144
event.preventDefault();
145145
this._toggle();
146146
}

tools/defaults.bzl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,14 @@ def ng_test_library(deps = [], tsconfig = None, **kwargs):
7575
**kwargs
7676
)
7777

78-
def ng_web_test_suite(deps = [], srcs = [], static_css = [], bootstrap = [], **kwargs):
78+
def ts_web_test_suite(srcs = [], **kwargs):
79+
_ts_web_test_suite(
80+
# Required for running the compiled ng modules that use TypeScript import helpers.
81+
srcs = ["@matdeps//node_modules/tslib:tslib.js"] + srcs,
82+
**kwargs
83+
)
84+
85+
def ng_web_test_suite(deps = [], static_css = [], bootstrap = [], **kwargs):
7986
# Always include a prebuilt theme in the test suite because otherwise tests, which depend on CSS
8087
# that is needed for measuring, will unexpectedly fail. Also always adding a prebuilt theme
8188
# 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
109116
""" % css_label
110117
)
111118

112-
_ts_web_test_suite(
113-
# Required for running the compiled ng modules that use TypeScript import helpers.
114-
srcs = ["@matdeps//node_modules/tslib:tslib.js"] + srcs,
119+
ts_web_test_suite(
115120
# Depend on our custom test initialization script. This needs to be the first dependency.
116121
deps = ["//test:angular_test_init"] + deps,
117122
bootstrap = [

0 commit comments

Comments
 (0)