Skip to content

Commit e0bf841

Browse files
committed
fix(ripple): don't launch ripple for fake mouse events
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 29d5173 commit e0bf841

File tree

4 files changed

+20
-1
lines changed

4 files changed

+20
-1
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, button = 0) {
2626
button, /* 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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createTouchEvent,
77
dispatchMouseEvent,
88
dispatchTouchEvent,
9+
createMouseEvent,
910
} from '@angular/cdk/testing';
1011
import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer';
1112
import {
@@ -166,6 +167,15 @@ describe('MatRipple', () => {
166167
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
167168
}));
168169

170+
it('should ignore fake mouse events from screen readers', () => fakeAsync(() => {
171+
const event = createMouseEvent('mousedown');
172+
Object.defineProperty(event, 'buttons', {get: () => 0});
173+
174+
dispatchEvent(rippleTarget, event);
175+
tick(enterDuration);
176+
expect(rippleTarget.querySelector('.mat-ripple-element')).toBeFalsy();
177+
}));
178+
169179
it('removes ripple after timeout', fakeAsync(() => {
170180
dispatchMouseEvent(rippleTarget, 'mousedown');
171181
dispatchMouseEvent(rippleTarget, 'mouseup');

0 commit comments

Comments
 (0)