diff --git a/src/cdk/testing/event-objects.ts b/src/cdk/testing/event-objects.ts index bc2358786208..040794e20142 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) { 0, /* 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 5467e4086e96..3bcb70486bd8 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 bb29ddd85984..73aab9251623 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 c4b6f2c93a3a..a794b3ed7ff2 100644 --- a/src/lib/core/ripple/ripple.spec.ts +++ b/src/lib/core/ripple/ripple.spec.ts @@ -1,12 +1,21 @@ -import {TestBed, ComponentFixture, fakeAsync, tick, inject} from '@angular/core/testing'; -import {Component, ViewChild} from '@angular/core'; import {Platform} from '@angular/cdk/platform'; -import {dispatchMouseEvent, dispatchTouchEvent} from '@angular/cdk/testing'; -import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer'; import { - MatRipple, MatRippleModule, MAT_RIPPLE_GLOBAL_OPTIONS, RippleState, RippleGlobalOptions -} from './index'; + createMouseEvent, + dispatchEvent, + dispatchMouseEvent, + dispatchTouchEvent, +} from '@angular/cdk/testing'; +import {Component, ViewChild} from '@angular/core'; +import {ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import { + MAT_RIPPLE_GLOBAL_OPTIONS, + MatRipple, + MatRippleModule, + RippleGlobalOptions, + RippleState, +} from './index'; +import {defaultRippleAnimationConfig, RippleAnimationConfig} from './ripple-renderer'; /** Shorthands for the enter and exit duration of ripples. */ const {enterDuration, exitDuration} = defaultRippleAnimationConfig; @@ -135,6 +144,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');