@if (this.heading !== "" && getHeadingAsTemplate() !== null) {
@@ -102,7 +102,8 @@ export class GoabModal implements OnInit {
return this.heading instanceof TemplateRef ? this.heading : null;
}
- _onClose() {
+ _onClose(event: Event) {
+ if (event.target !== event.currentTarget) return;
this.onClose.emit();
}
}
diff --git a/libs/angular-components/src/lib/components/push-drawer/push-drawer.ts b/libs/angular-components/src/lib/components/push-drawer/push-drawer.ts
index 1adaeb09cf..d869859df3 100644
--- a/libs/angular-components/src/lib/components/push-drawer/push-drawer.ts
+++ b/libs/angular-components/src/lib/components/push-drawer/push-drawer.ts
@@ -23,7 +23,7 @@ import {
[attr.testid]="testId"
[attr.width]="width"
[attr.version]="version"
- (_close)="_onClose()"
+ (_close)="_onClose($event)"
>
@@ -66,7 +66,8 @@ export class GoabPushDrawer implements OnInit {
}, 0);
}
- _onClose() {
+ _onClose(event: Event) {
+ if (event.target !== event.currentTarget) return;
this.onClose.emit();
}
diff --git a/libs/react-components/specs/popover.browser.spec.tsx b/libs/react-components/specs/popover.browser.spec.tsx
index 7db4ad54f2..83941ab205 100644
--- a/libs/react-components/specs/popover.browser.spec.tsx
+++ b/libs/react-components/specs/popover.browser.spec.tsx
@@ -202,6 +202,69 @@ describe("Popover", () => {
expect(aboveWidth!).toBe(belowWidth!);
});
+ it("should not close parent popover when child popover closes", async () => {
+ const Component = () => {
+ return (
+
+ Open parent
+
+ }
+ >
+ Parent content
+
+ Open child
+
+ }
+ >
+ Child content
+
+ Close child
+
+
+
+ );
+ };
+
+ const result = render(
);
+ const parentTarget = result.getByTestId("parent-target");
+ const childTarget = result.getByTestId("child-target");
+ const parentContent = result.getByTestId("parent-content");
+ const childContent = result.getByTestId("child-content");
+ const childClose = result.getByTestId("child-close");
+
+ // Open parent popover
+ await parentTarget.click();
+ await vi.waitFor(() => {
+ expect(parentContent).toBeVisible();
+ });
+
+ // Open child popover
+ await childTarget.click();
+ await vi.waitFor(() => {
+ expect(childContent).toBeVisible();
+ });
+
+ // Close child popover using close button
+ await childClose.click();
+
+ // Parent popover should still be open
+ await vi.waitFor(() => {
+ expect(childContent).not.toBeVisible();
+ expect(parentContent).toBeVisible();
+ });
+ });
+
it("should align popover to the right edge of the target when there is not enough space on the right", async () => {
const Component = () => {
return (
diff --git a/libs/react-components/src/lib/drawer/drawer.tsx b/libs/react-components/src/lib/drawer/drawer.tsx
index c1eda6568d..9c0fca7e34 100644
--- a/libs/react-components/src/lib/drawer/drawer.tsx
+++ b/libs/react-components/src/lib/drawer/drawer.tsx
@@ -56,9 +56,16 @@ export function GoabDrawer({
if (!el?.current || !onClose) {
return;
}
- el.current?.addEventListener("_close", onClose);
+ const current = el.current;
+ const listener = (e: Event) => {
+ if (e.target !== current) {
+ return;
+ }
+ onClose();
+ };
+ current.addEventListener("_close", listener);
return () => {
- el.current?.removeEventListener("_close", onClose);
+ current.removeEventListener("_close", listener);
};
}, [el, onClose]);
diff --git a/libs/react-components/src/lib/modal/modal.tsx b/libs/react-components/src/lib/modal/modal.tsx
index 38604c5d83..4b97f47b13 100644
--- a/libs/react-components/src/lib/modal/modal.tsx
+++ b/libs/react-components/src/lib/modal/modal.tsx
@@ -76,7 +76,10 @@ export function GoabModal({
return;
}
const current = el.current;
- const listener = () => {
+ const listener = (e: Event) => {
+ if (e.target !== current) {
+ return;
+ }
onClose?.();
};
diff --git a/libs/react-components/src/lib/push-drawer/push-drawer.tsx b/libs/react-components/src/lib/push-drawer/push-drawer.tsx
index 681add6e08..ec3d4e7b96 100644
--- a/libs/react-components/src/lib/push-drawer/push-drawer.tsx
+++ b/libs/react-components/src/lib/push-drawer/push-drawer.tsx
@@ -51,9 +51,16 @@ export function GoabPushDrawer({
if (!el?.current || !onClose) {
return;
}
- el.current?.addEventListener("_close", onClose);
+ const current = el.current;
+ const listener = (e: Event) => {
+ if (e.target !== current) {
+ return;
+ }
+ onClose();
+ };
+ current.addEventListener("_close", listener);
return () => {
- el.current?.removeEventListener("_close", onClose);
+ current.removeEventListener("_close", listener);
};
}, [el, onClose]);
diff --git a/libs/web-components/src/components/dropdown/Dropdown.svelte b/libs/web-components/src/components/dropdown/Dropdown.svelte
index e92422814c..5bcffc0813 100644
--- a/libs/web-components/src/components/dropdown/Dropdown.svelte
+++ b/libs/web-components/src/components/dropdown/Dropdown.svelte
@@ -210,6 +210,7 @@
});
_popoverEl?.addEventListener("_close", (e) => {
+ e.stopPropagation();
_isMenuVisible = false;
});
}
diff --git a/libs/web-components/src/components/popover/Popover.svelte b/libs/web-components/src/components/popover/Popover.svelte
index 0b9fa6d436..a18e255d17 100644
--- a/libs/web-components/src/components/popover/Popover.svelte
+++ b/libs/web-components/src/components/popover/Popover.svelte
@@ -200,6 +200,44 @@
}
}
+ function isInPopoverComposedTree(node: EventTarget | null): boolean {
+ if (!_popoverEl || !(node instanceof Node)) {
+ return false;
+ }
+
+ if (_popoverEl.contains(node)) {
+ return true;
+ }
+
+ // Walk across slot and shadow boundaries to detect nested/slotted descendants.
+ let current: Node | null = node;
+ while (current) {
+ if (current === _popoverEl) {
+ return true;
+ }
+
+ if (current instanceof Element && current.assignedSlot) {
+ current = current.assignedSlot;
+ continue;
+ }
+
+ if (current.parentNode) {
+ current = current.parentNode;
+ continue;
+ }
+
+ const rootNode = current.getRootNode?.();
+ if (rootNode instanceof ShadowRoot) {
+ current = rootNode.host;
+ continue;
+ }
+
+ current = null;
+ }
+
+ return false;
+ }
+
// When one popover is opened it dispatches a `goa:closePopover` to the document.body element, so adding a listener
// here will allow any other popover that is currently open to be closed
function addGlobalCloseListener() {
@@ -213,7 +251,10 @@
// the popover that is being opened will, at that time have the an open state, so we need to prevent
// that one that is being opened be immediately closed.
if (target !== _targetEl) {
- closePopover();
+ // Don't close if the target is a child popover (descendant of this popover's content)
+ if (target && !isInPopoverComposedTree(target)) {
+ closePopover();
+ }
}
});
}