Skip to content

Commit 09df51d

Browse files
authored
fix(cdk/overlay): incorrectly dispatching outside click for shadow DOM (#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.
1 parent 5ba7a0e commit 09df51d

File tree

1 file changed

+22
-5
lines changed

1 file changed

+22
-5
lines changed

src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import type {OverlayRef} from '../overlay-ref';
2121
export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
2222
private _cursorOriginalValue: string;
2323
private _cursorStyleIsSet = false;
24-
private _pointerDownEventTarget: EventTarget | null;
24+
private _pointerDownEventTarget: HTMLElement | null;
2525

2626
constructor(
2727
@Inject(DOCUMENT) document: any,
@@ -89,12 +89,12 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
8989

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

9595
/** Click event listener that will be attached to the body propagate phase. */
9696
private _clickListener = (event: MouseEvent) => {
97-
const target = _getEventTarget(event);
97+
const target = _getEventTarget<HTMLElement>(event);
9898
// In case of a click event, we want to check the origin of the click
9999
// (e.g. in case where a user starts a click inside the overlay and
100100
// releases the click outside of it).
@@ -128,8 +128,8 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
128128
// If it's an outside click (both origin and target of the click) dispatch the mouse event,
129129
// and proceed with the next overlay
130130
if (
131-
overlayRef.overlayElement.contains(target as Node) ||
132-
overlayRef.overlayElement.contains(origin as Node)
131+
containsPierceShadowDom(overlayRef.overlayElement, target) ||
132+
containsPierceShadowDom(overlayRef.overlayElement, origin)
133133
) {
134134
break;
135135
}
@@ -144,3 +144,20 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
144144
}
145145
};
146146
}
147+
148+
/** Version of `Element.contains` that transcends shadow DOM boundaries. */
149+
function containsPierceShadowDom(parent: HTMLElement, child: HTMLElement | null): boolean {
150+
const supportsShadowRoot = typeof ShadowRoot !== 'undefined' && ShadowRoot;
151+
let current: Node | null = child;
152+
153+
while (current) {
154+
if (current === parent) {
155+
return true;
156+
}
157+
158+
current =
159+
supportsShadowRoot && current instanceof ShadowRoot ? current.host : current.parentNode;
160+
}
161+
162+
return false;
163+
}

0 commit comments

Comments
 (0)