From 266a159035dbf3eb5f008c423cf3d354f14986c2 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 27 Aug 2018 19:04:12 +0200 Subject: [PATCH] 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. --- src/cdk/testing/event-objects.ts | 4 ++++ src/lib/core/BUILD.bazel | 1 + src/lib/core/ripple/ripple-renderer.ts | 6 +++++- src/lib/core/ripple/ripple.spec.ts | 30 ++++++++++++++++++++------ 4 files changed, 34 insertions(+), 7 deletions(-) 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');