Skip to content

fix(cdk/overlay): incorrectly dispatching outside click for shadow DOM #29249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Loading