Skip to content

Commit

Permalink
fix(cdk/overlay): incorrectly dispatching outside click for shadow DOM
Browse files Browse the repository at this point in the history
Fixes that if an overlay contains a component using shadow DOM encapsulation, the clicks within it will be incorrectly picked up as outside clicks.

Fixes angular#29241.
  • Loading branch information
crisbeto committed Jun 13, 2024
1 parent 00022d9 commit 79bcc03
Showing 1 changed file with 23 additions and 6 deletions.
29 changes: 23 additions & 6 deletions src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {DOCUMENT} from '@angular/common';
import {Inject, Injectable, NgZone, Optional} from '@angular/core';
import {Platform, _getEventTarget} from '@angular/cdk/platform';
import {Platform, _getEventTarget, _getShadowRoot} from '@angular/cdk/platform';
import {BaseOverlayDispatcher} from './base-overlay-dispatcher';
import type {OverlayRef} from '../overlay-ref';

Expand All @@ -21,7 +21,7 @@ import type {OverlayRef} from '../overlay-ref';
export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
private _cursorOriginalValue: string;
private _cursorStyleIsSet = false;
private _pointerDownEventTarget: EventTarget | null;
private _pointerDownEventTarget: HTMLElement | null;

constructor(
@Inject(DOCUMENT) document: any,
Expand Down Expand Up @@ -89,12 +89,12 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {

/** Store pointerdown event target to track origin of click. */
private _pointerDownListener = (event: PointerEvent) => {
this._pointerDownEventTarget = _getEventTarget(event);
this._pointerDownEventTarget = _getEventTarget<HTMLElement>(event);
};

/** Click event listener that will be attached to the body propagate phase. */
private _clickListener = (event: MouseEvent) => {
const target = _getEventTarget(event);
const target = _getEventTarget<HTMLElement>(event);
// In case of a click event, we want to check the origin of the click
// (e.g. in case where a user starts a click inside the overlay and
// releases the click outside of it).
Expand Down Expand Up @@ -128,8 +128,8 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
// If it's an outside click (both origin and target of the click) dispatch the mouse event,
// and proceed with the next overlay
if (
overlayRef.overlayElement.contains(target as Node) ||
overlayRef.overlayElement.contains(origin as Node)
containsPierceShadowDom(overlayRef.overlayElement, target) ||
containsPierceShadowDom(overlayRef.overlayElement, origin)
) {
break;
}
Expand All @@ -144,3 +144,20 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
}
};
}

/** Version of `Element.contains` that transcends shadow DOM boundaries. */
function containsPierceShadowDom(parent: HTMLElement, child: HTMLElement | null): boolean {
const supportsShadowRoot = typeof ShadowRoot !== 'undefined' && ShadowRoot;
let current: Node | null = child;

while (current) {
if (current === parent) {
return true;
}

current =
supportsShadowRoot && current instanceof ShadowRoot ? current.host : current.parentNode;
}

return false;
}

0 comments on commit 79bcc03

Please sign in to comment.