Skip to content

Commit 266a159

Browse files
crisbetojelbourn
authored andcommitted
fix(ripple): don't launch ripple for fake mouse events (#11997)
When using a screen reader the ripples can get fake mouse events when pressing space or enter. With the following changes we skip launching the ripples in these cases, in order to align the experience with the non-screen-reader.
1 parent 3328808 commit 266a159

File tree

4 files changed

+34
-7
lines changed

4 files changed

+34
-7
lines changed

src/cdk/testing/event-objects.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export function createMouseEvent(type: string, x = 0, y = 0) {
2626
0, /* button */
2727
null /* relatedTarget */);
2828

29+
// `initMouseEvent` doesn't allow us to pass the `buttons` and
30+
// defaults it to 0 which looks like a fake event.
31+
Object.defineProperty(event, 'buttons', {get: () => 1});
32+
2933
return event;
3034
}
3135

src/lib/core/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ng_module(
1717
":option/optgroup.css",
1818
] + glob(["**/*.html"]),
1919
deps = [
20+
"//src/cdk/a11y",
2021
"//src/cdk/bidi",
2122
"//src/cdk/coercion",
2223
"//src/cdk/keycodes",

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import {ElementRef, NgZone} from '@angular/core';
99
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
10+
import {isFakeMousedownFromScreenReader} from '@angular/cdk/a11y';
1011
import {RippleRef, RippleState} from './ripple-ref';
1112

1213
export type RippleConfig = {
@@ -246,10 +247,13 @@ export class RippleRenderer {
246247

247248
/** Function being called whenever the trigger is being pressed using mouse. */
248249
private onMousedown = (event: MouseEvent) => {
250+
// Screen readers will fire fake mouse events for space/enter. Skip launching a
251+
// ripple in this case for consistency with the non-screen-reader experience.
252+
const isFakeMousedown = isFakeMousedownFromScreenReader(event);
249253
const isSyntheticEvent = this._lastTouchStartEvent &&
250254
Date.now() < this._lastTouchStartEvent + ignoreMouseEventsTimeout;
251255

252-
if (!this._target.rippleDisabled && !isSyntheticEvent) {
256+
if (!this._target.rippleDisabled && !isFakeMousedown && !isSyntheticEvent) {
253257
this._isPointerDown = true;
254258
this.fadeInRipple(event.clientX, event.clientY, this._target.rippleConfig);
255259
}

src/lib/core/ripple/ripple.spec.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
import {TestBed, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/testing';
2-
import {Component, ViewChild} from '@angular/core';
31
import {Platform} from '@angular/cdk/platform';
4-
import {dispatchMouseEvent, dispatchTouchEvent} from '@angular/cdk/testing';
5-
import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer';
62
import {
7-
MatRipple, MatRippleModule, MAT_RIPPLE_GLOBAL_OPTIONS, RippleState, RippleGlobalOptions
8-
} from './index';
3+
createMouseEvent,
4+
dispatchEvent,
5+
dispatchMouseEvent,
6+
dispatchTouchEvent,
7+
} from '@angular/cdk/testing';
8+
import {Component, ViewChild} from '@angular/core';
9+
import {ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
910
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
11+
import {
12+
MAT_RIPPLE_GLOBAL_OPTIONS,
13+
MatRipple,
14+
MatRippleModule,
15+
RippleGlobalOptions,
16+
RippleState,
17+
} from './index';
18+
import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer';
1019

1120
/** Shorthands for the enter and exit duration of ripples. */
1221
const {enterDuration, exitDuration} = defaultRippleAnimationConfig;
@@ -135,6 +144,15 @@ describe('MatRipple', () => {
135144
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
136145
}));
137146

147+
it('should ignore fake mouse events from screen readers', () => fakeAsync(() => {
148+
const event = createMouseEvent('mousedown');
149+
Object.defineProperty(event, 'buttons', {get: () => 0});
150+
151+
dispatchEvent(rippleTarget, event);
152+
tick(enterDuration);
153+
expect(rippleTarget.querySelector('.mat-ripple-element')).toBeFalsy();
154+
}));
155+
138156
it('removes ripple after timeout', fakeAsync(() => {
139157
dispatchMouseEvent(rippleTarget, 'mousedown');
140158
dispatchMouseEvent(rippleTarget, 'mouseup');

0 commit comments

Comments
 (0)