From 9ecd8dd9261e3f474b3961f2a2f03d43c3197180 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Feb 2023 13:22:29 +0100 Subject: [PATCH] Fix `Dialog` cleanup when the `Dialog` becomes hidden (#2303) * use the Dialog's parent as the root for the Intersection observer We have some code that allows us to auto-close the dialog the moment it gets hidden. This is useful if you use a dialog for a mobile menu and you resizet he browser. If you wrap the dialog in a `md:hidden` then it auto closes. If we don't do this, then the dialog is still locking the scrolling, keeping the focus in the dialog, ... but it is not visible. To solve this we use an `IntersectionObserver` to verify that the `boundingClientRect` is "gone" (x = 0, y = 0, width = 0 and height = 0). However, the intersection observer is not always triggered. This happens if the main content is scrollable. Setting the `root` of the `IntersectionObserver` to the parent of the `Dialog` does seem to solve it. Not 100% sure what causes this behaviour exactly. * use a `ResizeObserver` instead of `IntersectionObserver` * implement a `ResizeObserver` for the tests * update changelog --- packages/@headlessui-react/CHANGELOG.md | 1 + .../src/components/dialog/dialog.test.tsx | 2 +- .../@headlessui-react/src/components/dialog/dialog.tsx | 10 +++------- packages/@headlessui-vue/CHANGELOG.md | 1 + .../src/components/dialog/dialog.test.ts | 2 +- .../@headlessui-vue/src/components/dialog/dialog.ts | 10 +++------- 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 76b634e17b..3bf62ea600 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure the main tree and parent `Dialog` components are marked as `inert` ([#2290](https://github.com/tailwindlabs/headlessui/pull/2290)) - Fix nested `Popover` components not opening ([#2293](https://github.com/tailwindlabs/headlessui/pull/2293)) - Make React types more compatible with other libraries ([#2282](https://github.com/tailwindlabs/headlessui/pull/2282)) +- Fix `Dialog` cleanup when the `Dialog` becomes hidden ([#2303](https://github.com/tailwindlabs/headlessui/pull/2303)) ## [1.7.11] - 2023-02-15 diff --git a/packages/@headlessui-react/src/components/dialog/dialog.test.tsx b/packages/@headlessui-react/src/components/dialog/dialog.test.tsx index 535bf66c89..a36bb4cb16 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.test.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.test.tsx @@ -30,7 +30,7 @@ import { OpenClosedProvider, State } from '../../internal/open-closed' jest.mock('../../hooks/use-id') // @ts-expect-error -global.IntersectionObserver = class FakeIntersectionObserver { +global.ResizeObserver = class FakeResizeObserver { observe() {} disconnect() {} } diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index 7c810602ae..7f31f17724 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -309,14 +309,10 @@ function DialogFn( if (dialogState !== DialogStates.Open) return if (!internalDialogRef.current) return - let observer = new IntersectionObserver((entries) => { + let observer = new ResizeObserver((entries) => { for (let entry of entries) { - if ( - entry.boundingClientRect.x === 0 && - entry.boundingClientRect.y === 0 && - entry.boundingClientRect.width === 0 && - entry.boundingClientRect.height === 0 - ) { + let rect = entry.target.getBoundingClientRect() + if (rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0) { close() } } diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index f7e641bafe..c5c618015b 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure the main tree and parent `Dialog` components are marked as `inert` ([#2290](https://github.com/tailwindlabs/headlessui/pull/2290)) - Fix nested `Popover` components not opening ([#2293](https://github.com/tailwindlabs/headlessui/pull/2293)) - Fix `change` event incorrectly getting called on `blur` ([#2296](https://github.com/tailwindlabs/headlessui/pull/2296)) +- Fix `Dialog` cleanup when the `Dialog` becomes hidden ([#2303](https://github.com/tailwindlabs/headlessui/pull/2303)) ## [1.7.10] - 2023-02-15 diff --git a/packages/@headlessui-vue/src/components/dialog/dialog.test.ts b/packages/@headlessui-vue/src/components/dialog/dialog.test.ts index 9f6307cf0b..0ffc735119 100644 --- a/packages/@headlessui-vue/src/components/dialog/dialog.test.ts +++ b/packages/@headlessui-vue/src/components/dialog/dialog.test.ts @@ -44,7 +44,7 @@ import { html } from '../../test-utils/html' import { useOpenClosedProvider, State } from '../../internal/open-closed' // @ts-expect-error -global.IntersectionObserver = class FakeIntersectionObserver { +global.ResizeObserver = class FakeResizeObserver { observe() {} disconnect() {} } diff --git a/packages/@headlessui-vue/src/components/dialog/dialog.ts b/packages/@headlessui-vue/src/components/dialog/dialog.ts index f3865966e2..2c5272d97f 100644 --- a/packages/@headlessui-vue/src/components/dialog/dialog.ts +++ b/packages/@headlessui-vue/src/components/dialog/dialog.ts @@ -280,14 +280,10 @@ export let Dialog = defineComponent({ let container = dom(internalDialogRef) if (!container) return - let observer = new IntersectionObserver((entries) => { + let observer = new ResizeObserver((entries) => { for (let entry of entries) { - if ( - entry.boundingClientRect.x === 0 && - entry.boundingClientRect.y === 0 && - entry.boundingClientRect.width === 0 && - entry.boundingClientRect.height === 0 - ) { + let rect = entry.target.getBoundingClientRect() + if (rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0) { api.close() } }