From b9c60fd3d71351f1020750df9ae091f0f2243880 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 3 Jun 2022 16:12:23 +0200 Subject: [PATCH] ensure outside click properly works with Poratl components Usually this works out of the box, however our Portal components will render inside the Dialog component "root" to ensure that it is inside the non-inert tree and is inside the Dialog visually. This means that the Portal is not in a separate container and technically outside of the `Dialog.Panel` which means that it will close when you click on a non-interactive item inside that Portal... This fixes that and allows all Portal components. --- .../src/components/dialog/dialog.tsx | 16 ++++++++-------- .../src/components/portal/portal.test.tsx | 2 +- .../src/components/portal/portal.tsx | 1 + .../src/components/dialog/dialog.ts | 2 +- .../src/components/portal/portal.test.ts | 2 +- .../src/components/portal/portal.ts | 1 + 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index 4f3d0ed6ff..351c28ad25 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -198,14 +198,14 @@ let DialogRoot = forwardRefWithAs(function Dialog< useOutsideClick( () => { // Third party roots - let rootContainers = Array.from(ownerDocument?.querySelectorAll('body > *') ?? []).filter( - (container) => { - if (!(container instanceof HTMLElement)) return false // Skip non-HTMLElements - if (container.contains(mainTreeNode.current)) return false // Skip if it is the main app - if (state.panelRef.current && container.contains(state.panelRef.current)) return false - return true // Keep - } - ) + let rootContainers = Array.from( + ownerDocument?.querySelectorAll('body > *, [data-headlessui-portal]') ?? [] + ).filter((container) => { + if (!(container instanceof HTMLElement)) return false // Skip non-HTMLElements + if (container.contains(mainTreeNode.current)) return false // Skip if it is the main app + if (state.panelRef.current && container.contains(state.panelRef.current)) return false + return true // Keep + }) return [ ...rootContainers, diff --git a/packages/@headlessui-react/src/components/portal/portal.test.tsx b/packages/@headlessui-react/src/components/portal/portal.test.tsx index 6b21ea7a5c..d434dd6084 100644 --- a/packages/@headlessui-react/src/components/portal/portal.test.tsx +++ b/packages/@headlessui-react/src/components/portal/portal.test.tsx @@ -299,6 +299,6 @@ it('should be possible to force the Portal into a specific element using Portal. render() expect(document.body.innerHTML).toMatchInlineSnapshot( - `"
B
I am in the portal root
"` + `"
B
I am in the portal root
"` ) }) diff --git a/packages/@headlessui-react/src/components/portal/portal.tsx b/packages/@headlessui-react/src/components/portal/portal.tsx index c6b55d8171..86bdba171b 100644 --- a/packages/@headlessui-react/src/components/portal/portal.tsx +++ b/packages/@headlessui-react/src/components/portal/portal.tsx @@ -95,6 +95,7 @@ let PortalRoot = forwardRefWithAs(function Portal< // Element already exists in target, always calling target.appendChild(element) will cause a // brief unmount/remount. if (!target.contains(element)) { + element.setAttribute('data-headlessui-portal', '') target.appendChild(element) } diff --git a/packages/@headlessui-vue/src/components/dialog/dialog.ts b/packages/@headlessui-vue/src/components/dialog/dialog.ts index 217ea2f873..dd1b24c7f3 100644 --- a/packages/@headlessui-vue/src/components/dialog/dialog.ts +++ b/packages/@headlessui-vue/src/components/dialog/dialog.ts @@ -179,7 +179,7 @@ export let Dialog = defineComponent({ () => { // Third party roots let rootContainers = Array.from( - ownerDocument.value?.querySelectorAll('body > *') ?? [] + ownerDocument.value?.querySelectorAll('body > *, [data-headlessui-portal]') ?? [] ).filter((container) => { if (!(container instanceof HTMLElement)) return false // Skip non-HTMLElements if (container.contains(dom(mainTreeNode))) return false // Skip if it is the main app diff --git a/packages/@headlessui-vue/src/components/portal/portal.test.ts b/packages/@headlessui-vue/src/components/portal/portal.test.ts index e0baae06d0..266f3dc987 100644 --- a/packages/@headlessui-vue/src/components/portal/portal.test.ts +++ b/packages/@headlessui-vue/src/components/portal/portal.test.ts @@ -403,6 +403,6 @@ it('should be possible to force the Portal into a specific element using PortalG await new Promise(nextTick) expect(document.body.innerHTML).toMatchInlineSnapshot( - `"
B
"` + `"
B
"` ) }) diff --git a/packages/@headlessui-vue/src/components/portal/portal.ts b/packages/@headlessui-vue/src/components/portal/portal.ts index 3ee164d0bf..fc8e1aabe5 100644 --- a/packages/@headlessui-vue/src/components/portal/portal.ts +++ b/packages/@headlessui-vue/src/components/portal/portal.ts @@ -79,6 +79,7 @@ export let Portal = defineComponent({ let ourProps = { ref: element, + 'data-headlessui-portal': '', } return h(