From a5ad288bffb063cc27bc562df62a824e57968d2f Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 9 May 2024 18:20:52 +0200 Subject: [PATCH] fix(material/core): ripple loader not working in shadow DOM (#29015) Fixes the following issues that prevented the button ripples from being loaded in the shadow DOM: * We weren't resolving the event target properly. * We were using `click` as a fallback which happens too late for the very first ripple. These changes switch to `mousedown`. Fixes #29011. (cherry picked from commit 7553e1c55d4bc51abf4fd1e1d226909a0680d196) --- src/material/button/button.spec.ts | 2 +- src/material/core/private/ripple-loader.ts | 34 +++++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/material/button/button.spec.ts b/src/material/button/button.spec.ts index 5f51cf624532..517772730751 100644 --- a/src/material/button/button.spec.ts +++ b/src/material/button/button.spec.ts @@ -333,7 +333,7 @@ describe('MDC-based MatButton', () => { }); // Ensure each of these events triggers the initialization of the button ripple. - for (const event of ['click', 'touchstart', 'mouseenter', 'focus']) { + for (const event of ['mousedown', 'touchstart', 'mouseenter', 'focus']) { it(`should render the ripple once a button has received a "${event}" event`, () => { const fab = fixture.debugElement.query(By.css('button[mat-fab]'))!; let ripple = fab.nativeElement.querySelector('.mat-mdc-button-ripple'); diff --git a/src/material/core/private/ripple-loader.ts b/src/material/core/private/ripple-loader.ts index 953e1b603793..6c2501a27620 100644 --- a/src/material/core/private/ripple-loader.ts +++ b/src/material/core/private/ripple-loader.ts @@ -16,13 +16,17 @@ import { inject, } from '@angular/core'; import {MAT_RIPPLE_GLOBAL_OPTIONS, MatRipple} from '../ripple'; -import {Platform} from '@angular/cdk/platform'; +import {Platform, _getEventTarget} from '@angular/cdk/platform'; /** The options for the MatRippleLoader's event listeners. */ const eventListenerOptions = {capture: true}; -/** The events that should trigger the initialization of the ripple. */ -const rippleInteractionEvents = ['focus', 'click', 'mouseenter', 'touchstart']; +/** + * The events that should trigger the initialization of the ripple. + * Note that we use `mousedown`, rather than `click`, for mouse devices because + * we can't rely on `mouseenter` in the shadow DOM and `click` happens too late. + */ +const rippleInteractionEvents = ['focus', 'mousedown', 'mouseenter', 'touchstart']; /** The attribute attached to a component whose ripple has not yet been initialized. */ const matRippleUninitialized = 'mat-ripple-loader-uninitialized'; @@ -130,20 +134,22 @@ export class MatRippleLoader implements OnDestroy { } } - /** Handles creating and attaching component internals when a component it is initially interacted with. */ + /** + * Handles creating and attaching component internals + * when a component is initially interacted with. + */ private _onInteraction = (event: Event) => { - if (!(event.target instanceof HTMLElement)) { - return; - } - const eventTarget = event.target as HTMLElement; + const eventTarget = _getEventTarget(event); - // TODO(wagnermaciel): Consider batching these events to improve runtime performance. + if (eventTarget instanceof HTMLElement) { + // TODO(wagnermaciel): Consider batching these events to improve runtime performance. + const element = eventTarget.closest( + `[${matRippleUninitialized}="${this._globalRippleOptions?.namespace ?? ''}"]`, + ); - const element = eventTarget.closest( - `[${matRippleUninitialized}="${this._globalRippleOptions?.namespace ?? ''}"]`, - ); - if (element) { - this._createRipple(element as HTMLElement); + if (element) { + this._createRipple(element as HTMLElement); + } } };