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
#29249)

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 #29241.

(cherry picked from commit 09df51d)
  • Loading branch information
crisbeto committed Jun 13, 2024
1 parent 1dbd968 commit 8e7ac08
Showing 1 changed file with 22 additions and 5 deletions.
27 changes: 22 additions & 5 deletions src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 8e7ac08

Please sign in to comment.