Skip to content

Commit 97b7255

Browse files
committed
feat(cdk/a11y): Respond to reviewer feedback
1 parent 02c003f commit 97b7255

File tree

6 files changed

+63
-54
lines changed

6 files changed

+63
-54
lines changed

src/cdk/a11y/focus-monitor/focus-monitor.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import {Component, NgZone} from '@angular/core';
1212
import {ComponentFixture, fakeAsync, flush, inject, TestBed, tick} from '@angular/core/testing';
1313
import {By} from '@angular/platform-browser';
1414
import {A11yModule} from '../index';
15+
import {TOUCH_BUFFER_MS} from '../input-modality/input-modality-detector';
1516
import {
1617
FocusMonitor,
1718
FocusMonitorDetectionMode,
1819
FocusOrigin,
1920
FOCUS_MONITOR_DEFAULT_OPTIONS,
20-
TOUCH_BUFFER_MS,
2121
} from './focus-monitor';
2222

2323

src/cdk/a11y/focus-monitor/focus-monitor.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ import {
2727
isFakeMousedownFromScreenReader,
2828
isFakeTouchstartFromScreenReader,
2929
} from '../fake-event-detection';
30-
31-
32-
// This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
33-
// that a value of around 650ms seems appropriate.
34-
export const TOUCH_BUFFER_MS = 650;
30+
import {TOUCH_BUFFER_MS} from '../input-modality/input-modality-detector';
3531

3632

3733
export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null;

src/cdk/a11y/input-modality/input-modality-detector.spec.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {A, ALT, B, C, CONTROL, META, SHIFT} from '@angular/cdk/keycodes';
22
import {Platform} from '@angular/cdk/platform';
3-
import {PLATFORM_ID} from '@angular/core';
3+
import {NgZone, PLATFORM_ID} from '@angular/core';
44

55
import {
66
createMouseEvent,
@@ -11,14 +11,16 @@ import {
1111
createTouchEvent,
1212
} from '@angular/cdk/testing/private';
1313
import {fakeAsync, inject, tick} from '@angular/core/testing';
14-
import {InputModality, InputModalityDetector, TIME_AFTER_TOUCH_MS} from './input-modality-detector';
14+
import {InputModality, InputModalityDetector, TOUCH_BUFFER_MS} from './input-modality-detector';
1515

1616
describe('InputModalityDetector', () => {
1717
let platform: Platform;
18+
let ngZone: NgZone;
1819
let detector: InputModalityDetector;
1920

2021
beforeEach(inject([PLATFORM_ID], (platformId: Object) => {
2122
platform = new Platform(platformId);
23+
ngZone = new NgZone({});
2224
}));
2325

2426
afterEach(() => {
@@ -27,7 +29,7 @@ describe('InputModalityDetector', () => {
2729

2830
it('should do nothing on non-browser platforms', () => {
2931
platform.isBrowser = false;
30-
detector = new InputModalityDetector(platform, document);
32+
detector = new InputModalityDetector(platform, ngZone, document);
3133
expect(detector.inputModality).toBe(null);
3234

3335
dispatchKeyboardEvent(document, 'keydown');
@@ -41,25 +43,25 @@ describe('InputModalityDetector', () => {
4143
});
4244

4345
it('should detect keyboard input modality', () => {
44-
detector = new InputModalityDetector(platform, document);
46+
detector = new InputModalityDetector(platform, ngZone, document);
4547
dispatchKeyboardEvent(document, 'keydown');
4648
expect(detector.inputModality).toBe('keyboard');
4749
});
4850

4951
it('should detect mouse input modality', () => {
50-
detector = new InputModalityDetector(platform, document);
52+
detector = new InputModalityDetector(platform, ngZone, document);
5153
dispatchMouseEvent(document, 'mousedown');
5254
expect(detector.inputModality).toBe('mouse');
5355
});
5456

5557
it('should detect touch input modality', () => {
56-
detector = new InputModalityDetector(platform, document);
58+
detector = new InputModalityDetector(platform, ngZone, document);
5759
dispatchTouchEvent(document, 'touchstart');
5860
expect(detector.inputModality).toBe('touch');
5961
});
6062

6163
it('should detect changes in input modality', () => {
62-
detector = new InputModalityDetector(platform, document);
64+
detector = new InputModalityDetector(platform, ngZone, document);
6365

6466
dispatchKeyboardEvent(document, 'keydown');
6567
expect(detector.inputModality).toBe('keyboard');
@@ -75,7 +77,7 @@ describe('InputModalityDetector', () => {
7577
});
7678

7779
it('should emit changes in input modality', () => {
78-
detector = new InputModalityDetector(platform, document);
80+
detector = new InputModalityDetector(platform, ngZone, document);
7981
const emitted: InputModality[] = [];
8082
detector.inputModalityChange.subscribe((inputModality: InputModality) => {
8183
emitted.push(inputModality);
@@ -103,7 +105,7 @@ describe('InputModalityDetector', () => {
103105
});
104106

105107
it('should ignore fake screen-reader mouse events', () => {
106-
detector = new InputModalityDetector(platform, document);
108+
detector = new InputModalityDetector(platform, ngZone, document);
107109

108110
// Create a fake screen-reader mouse event.
109111
const event = createMouseEvent('mousedown');
@@ -114,7 +116,7 @@ describe('InputModalityDetector', () => {
114116
});
115117

116118
it('should ignore fake screen-reader touch events', () => {
117-
detector = new InputModalityDetector(platform, document);
119+
detector = new InputModalityDetector(platform, ngZone, document);
118120

119121
// Create a fake screen-reader touch event.
120122
const event = createTouchEvent('touchstart');
@@ -125,40 +127,40 @@ describe('InputModalityDetector', () => {
125127
});
126128

127129
it('should ignore certain modifier keys by default', () => {
128-
detector = new InputModalityDetector(platform, document);
130+
detector = new InputModalityDetector(platform, ngZone, document);
129131

130-
dispatchKeyboardEvent(document, 'keydown', ALT, 'Alt');
131-
dispatchKeyboardEvent(document, 'keydown', CONTROL, 'Control');
132-
dispatchKeyboardEvent(document, 'keydown', META, 'Meta');
133-
dispatchKeyboardEvent(document, 'keydown', SHIFT, 'Shift');
132+
dispatchKeyboardEvent(document, 'keydown', ALT);
133+
dispatchKeyboardEvent(document, 'keydown', CONTROL);
134+
dispatchKeyboardEvent(document, 'keydown', META);
135+
dispatchKeyboardEvent(document, 'keydown', SHIFT);
134136

135137
expect(detector.inputModality).toBe(null);
136138
});
137139

138140
it('should not ignore modifier keys if specified', () => {
139-
detector = new InputModalityDetector(platform, document, {ignoreKeys: []});
140-
dispatchKeyboardEvent(document, 'keydown', CONTROL, 'Control');
141+
detector = new InputModalityDetector(platform, ngZone, document, {ignoreKeys: []});
142+
dispatchKeyboardEvent(document, 'keydown', CONTROL);
141143
expect(detector.inputModality).toBe('keyboard');
142144
});
143145

144146
it('should ignore additional keys if specified', () => {
145-
detector = new InputModalityDetector(platform, document, {ignoreKeys: ['A', 'B', 'C']});
147+
detector = new InputModalityDetector(platform, ngZone, document, {ignoreKeys: [A, B, C]});
146148

147-
dispatchKeyboardEvent(document, 'keydown', A, 'A');
148-
dispatchKeyboardEvent(document, 'keydown', B, 'B');
149-
dispatchKeyboardEvent(document, 'keydown', C, 'C');
149+
dispatchKeyboardEvent(document, 'keydown', A);
150+
dispatchKeyboardEvent(document, 'keydown', B);
151+
dispatchKeyboardEvent(document, 'keydown', C);
150152

151153
expect(detector.inputModality).toBe(null);
152154
});
153155

154156
it('should ignore mouse events that occur too closely after a touch event', fakeAsync(() => {
155-
detector = new InputModalityDetector(platform, document);
157+
detector = new InputModalityDetector(platform, ngZone, document);
156158

157159
dispatchTouchEvent(document, 'touchstart');
158160
dispatchMouseEvent(document, 'mousedown');
159161
expect(detector.inputModality).toBe('touch');
160162

161-
tick(TIME_AFTER_TOUCH_MS);
163+
tick(TOUCH_BUFFER_MS);
162164
dispatchMouseEvent(document, 'mousedown');
163165
expect(detector.inputModality).toBe('mouse');
164166
}));

src/cdk/a11y/input-modality/input-modality-detector.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Inject, Injectable, InjectionToken, OnDestroy, Optional} from '@angular/core';
9+
import {ALT, CONTROL, META, SHIFT} from '@angular/cdk/keycodes';
10+
import {Inject, Injectable, InjectionToken, OnDestroy, Optional, NgZone} from '@angular/core';
1011
import {normalizePassiveListenerOptions, Platform} from '@angular/cdk/platform';
1112
import {DOCUMENT} from '@angular/common';
1213
import {Observable, ReplaySubject} from 'rxjs';
@@ -24,7 +25,7 @@ export type InputModality = 'keyboard' | 'mouse' | 'touch' | null;
2425
/** Options to configure the behavior of the InputModalityDetector. */
2526
export interface InputModalityDetectorOptions {
2627
/** Keys to ignore when detecting keyboard input modality. */
27-
ignoreKeys?: string[];
28+
ignoreKeys?: number[];
2829
}
2930

3031
/**
@@ -47,20 +48,23 @@ export const INPUT_MODALITY_DETECTOR_OPTIONS =
4748
* these keys so as to not update the input modality.
4849
*/
4950
export const INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS: InputModalityDetectorOptions = {
50-
ignoreKeys: ['Alt', 'Control', 'Meta', 'Shift'],
51+
ignoreKeys: [ALT, CONTROL, META, SHIFT],
5152
};
5253

5354
/**
5455
* The amount of time needed to pass after a touchstart event in order for a subsequent mousedown
5556
* event to be attributed as mouse and not touch.
57+
*
58+
* This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
59+
* that a value of around 650ms seems appropriate.
5660
*/
57-
export const TIME_AFTER_TOUCH_MS = 650;
61+
export const TOUCH_BUFFER_MS = 650;
5862

5963
/**
6064
* Event listener options that enable capturing and also mark the listener as passive if the browser
6165
* supports it.
6266
*/
63-
const opts = normalizePassiveListenerOptions({
67+
const modalityEventListenerOptions = normalizePassiveListenerOptions({
6468
passive: true,
6569
capture: true,
6670
});
@@ -85,7 +89,7 @@ export class InputModalityDetector implements OnDestroy {
8589
/** Emits when the input modality changes. */
8690
readonly inputModalityChange: Observable<InputModality>;
8791

88-
/** Returns the last detected / current input modality. */
92+
/** Returns the most recently detected input modality. */
8993
get inputModality(): InputModality {
9094
return this._inputModality;
9195
}
@@ -112,7 +116,7 @@ export class InputModalityDetector implements OnDestroy {
112116
private _onKeydown = (event: KeyboardEvent) => {
113117
// If this is one of the keys we should ignore, then ignore it and don't update the input
114118
// modality to keyboard.
115-
if (this._options?.ignoreKeys?.some(key => key === event.key)) { return; }
119+
if (this._options?.ignoreKeys?.some(keyCode => keyCode === event.keyCode)) { return; }
116120

117121
this._updateInputModality('keyboard');
118122
}
@@ -127,7 +131,7 @@ export class InputModalityDetector implements OnDestroy {
127131
// Touches trigger both touch and mouse events, so we need to distinguish between mouse events
128132
// that were triggered via mouse vs touch. To do so, check if the mouse event occurs closely
129133
// after the previous touch event.
130-
if (Date.now() - this._lastTouchMs < TIME_AFTER_TOUCH_MS) { return; }
134+
if (Date.now() - this._lastTouchMs < TOUCH_BUFFER_MS) { return; }
131135

132136
this._updateInputModality('mouse');
133137
}
@@ -147,33 +151,38 @@ export class InputModalityDetector implements OnDestroy {
147151
}
148152

149153
constructor(
150-
platform: Platform,
154+
private readonly _platform: Platform,
155+
ngZone: NgZone,
151156
@Inject(DOCUMENT) document: Document,
152157
@Optional()
153158
@Inject(INPUT_MODALITY_DETECTOR_OPTIONS)
154159
options?: InputModalityDetectorOptions,
155160
) {
156161
// If we're not in a browser, this service should do nothing, as there's no relevant input
157162
// modality to detect.
158-
if (!platform.isBrowser) { return; }
163+
if (!_platform.isBrowser) { return; }
159164

160165
this._options = {
161166
...INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS,
162167
...options,
163168
};
164169

165-
this.inputModalityChange = this._change.pipe(distinctUntilChanged());
170+
this.inputModalityChange = this._change.pipe(distinctUntilChanged());
166171

167172
// Add the event listeners used to detect the user's input modality.
168-
document.addEventListener('keydown', this._onKeydown, opts);
169-
document.addEventListener('mousedown', this._onMousedown, opts);
170-
document.addEventListener('touchstart', this._onTouchstart, opts);
173+
ngZone.runOutsideAngular(() => {
174+
document.addEventListener('keydown', this._onKeydown, modalityEventListenerOptions);
175+
document.addEventListener('mousedown', this._onMousedown, modalityEventListenerOptions);
176+
document.addEventListener('touchstart', this._onTouchstart, modalityEventListenerOptions);
177+
});
171178
}
172179

173180
ngOnDestroy() {
174-
document.removeEventListener('keydown', this._onKeydown, opts);
175-
document.removeEventListener('mousedown', this._onMousedown, opts);
176-
document.removeEventListener('touchstart', this._onTouchstart, opts);
181+
if (!this._platform.isBrowser) { return; }
182+
183+
document.removeEventListener('keydown', this._onKeydown, modalityEventListenerOptions);
184+
document.removeEventListener('mousedown', this._onMousedown, modalityEventListenerOptions);
185+
document.removeEventListener('touchstart', this._onTouchstart, modalityEventListenerOptions);
177186
}
178187

179188
/**

src/cdk/a11y/public-api.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ export * from './focus-trap/event-listener-inert-strategy';
1616
export * from './focus-trap/focus-trap';
1717
export * from './focus-trap/focus-trap-inert-strategy';
1818
export * from './interactivity-checker/interactivity-checker';
19-
export * from './input-modality/input-modality-detector';
19+
export {
20+
InputModality,
21+
InputModalityDetector,
22+
InputModalityDetectorOptions,
23+
INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS,
24+
INPUT_MODALITY_DETECTOR_OPTIONS,
25+
} from './input-modality/input-modality-detector';
2026
export * from './live-announcer/live-announcer';
2127
export * from './live-announcer/live-announcer-tokens';
2228
export * from './focus-monitor/focus-monitor';

tools/public_api_guard/cdk/a11y.d.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,14 @@ export declare type InputModality = 'keyboard' | 'mouse' | 'touch' | null;
195195
export declare class InputModalityDetector implements OnDestroy {
196196
get inputModality(): InputModality;
197197
readonly inputModalityChange: Observable<InputModality>;
198-
constructor(platform: Platform, document: Document, options?: InputModalityDetectorOptions);
198+
constructor(_platform: Platform, ngZone: NgZone, document: Document, options?: InputModalityDetectorOptions);
199199
ngOnDestroy(): void;
200-
static ɵfac: i0.ɵɵFactoryDef<InputModalityDetector, [null, null, { optional: true; }]>;
200+
static ɵfac: i0.ɵɵFactoryDef<InputModalityDetector, [null, null, null, { optional: true; }]>;
201201
static ɵprov: i0.ɵɵInjectableDef<InputModalityDetector>;
202202
}
203203

204204
export interface InputModalityDetectorOptions {
205-
ignoreKeys?: string[];
205+
ignoreKeys?: number[];
206206
}
207207

208208
export declare class InteractivityChecker {
@@ -284,7 +284,3 @@ export interface RegisteredMessage {
284284
messageElement: Element;
285285
referenceCount: number;
286286
}
287-
288-
export declare const TIME_AFTER_TOUCH_MS = 650;
289-
290-
export declare const TOUCH_BUFFER_MS = 650;

0 commit comments

Comments
 (0)