diff --git a/src/cdk/testing/event-objects.ts b/src/cdk/testing/event-objects.ts index e02fbabdbf8..8806e5b5adc 100644 --- a/src/cdk/testing/event-objects.ts +++ b/src/cdk/testing/event-objects.ts @@ -26,6 +26,10 @@ export function createMouseEvent(type: string, x = 0, y = 0, button = 0) { button, /* button */ null /* relatedTarget */); + // `initMouseEvent` doesn't allow us to pass the `buttons` and + // defaults it to 0 which looks like a fake event. + Object.defineProperty(event, 'buttons', {get: () => 1}); + return event; } diff --git a/src/lib/core/BUILD.bazel b/src/lib/core/BUILD.bazel index 5467e4086e9..3bcb70486bd 100644 --- a/src/lib/core/BUILD.bazel +++ b/src/lib/core/BUILD.bazel @@ -17,6 +17,7 @@ ng_module( ":option/optgroup.css", ] + glob(["**/*.html"]), deps = [ + "//src/cdk/a11y", "//src/cdk/bidi", "//src/cdk/coercion", "//src/cdk/keycodes", diff --git a/src/lib/core/ripple/ripple-renderer.ts b/src/lib/core/ripple/ripple-renderer.ts index 24edac673fe..f2406a9fce9 100644 --- a/src/lib/core/ripple/ripple-renderer.ts +++ b/src/lib/core/ripple/ripple-renderer.ts @@ -7,6 +7,7 @@ */ import {ElementRef, NgZone} from '@angular/core'; import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform'; +import {isFakeMousedownFromScreenReader} from '@angular/cdk/a11y'; import {RippleRef, RippleState} from './ripple-ref'; export type RippleConfig = { @@ -246,10 +247,13 @@ export class RippleRenderer { /** Function being called whenever the trigger is being pressed using mouse. */ private onMousedown = (event: MouseEvent) => { + // Screen readers will fire fake mouse events for space/enter. Skip launching a + // ripple in this case for consistency with the non-screen-reader experience. + const isFakeMousedown = isFakeMousedownFromScreenReader(event); const isSyntheticEvent = this._lastTouchStartEvent && Date.now() < this._lastTouchStartEvent + ignoreMouseEventsTimeout; - if (!this._target.rippleDisabled && !isSyntheticEvent) { + if (!this._target.rippleDisabled && !isFakeMousedown && !isSyntheticEvent) { this._isPointerDown = true; this.fadeInRipple(event.clientX, event.clientY, this._target.rippleConfig); } diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts index 29033e0610e..3390ea36923 100644 --- a/src/lib/core/ripple/ripple.spec.ts +++ b/src/lib/core/ripple/ripple.spec.ts @@ -6,6 +6,7 @@ import { createTouchEvent, dispatchMouseEvent, dispatchTouchEvent, + createMouseEvent, } from '@angular/cdk/testing'; import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer'; import { @@ -166,6 +167,15 @@ describe('MatRipple', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0); })); + it('should ignore fake mouse events from screen readers', () => fakeAsync(() => { + const event = createMouseEvent('mousedown'); + Object.defineProperty(event, 'buttons', {get: () => 0}); + + dispatchEvent(rippleTarget, event); + tick(enterDuration); + expect(rippleTarget.querySelector('.mat-ripple-element')).toBeFalsy(); + })); + it('removes ripple after timeout', fakeAsync(() => { dispatchMouseEvent(rippleTarget, 'mousedown'); dispatchMouseEvent(rippleTarget, 'mouseup');