Skip to content

Commit

Permalink
fix(ripple): don't launch ripple for fake mouse events (#11997)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
crisbeto authored and jelbourn committed Aug 29, 2018
1 parent 3328808 commit 266a159
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/cdk/testing/event-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ng_module(
":option/optgroup.css",
] + glob(["**/*.html"]),
deps = [
"//src/cdk/a11y",
"//src/cdk/bidi",
"//src/cdk/coercion",
"//src/cdk/keycodes",
Expand Down
6 changes: 5 additions & 1 deletion src/lib/core/ripple/ripple-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
}
Expand Down
30 changes: 24 additions & 6 deletions src/lib/core/ripple/ripple.spec.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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');
Expand Down

0 comments on commit 266a159

Please sign in to comment.