From 5b0736a16f5a574c563833701be974d92c0fccb7 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 19 Sep 2025 11:47:15 +0200 Subject: [PATCH 1/2] reset `touchAction` and `overscrollBehavior` on new `touchstart` Before, when you were scrolling or interacting with an open dialog on iOS, then the moment you scroll we check whether you're allowed to or not. Scrolling inside the Dialog is fine, outside is not. One thing we did is if you are outside, we use `touch-action: 'none'`. The problem is that we never reset this value while the dialog is open. This results in an issue where you cannot interacting with the Dialog later. This change makes sure that every `touchstart` event, we reset the `overscroll-behavior` and `touch-action` properties so we can start fresh. --- .../document-overflow/handle-ios-locking.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/@headlessui-react/src/hooks/document-overflow/handle-ios-locking.ts b/packages/@headlessui-react/src/hooks/document-overflow/handle-ios-locking.ts index b7e0d3327..e91ab5ce4 100644 --- a/packages/@headlessui-react/src/hooks/document-overflow/handle-ios-locking.ts +++ b/packages/@headlessui-react/src/hooks/document-overflow/handle-ios-locking.ts @@ -76,23 +76,27 @@ export function handleIOSLocking(): ScrollLockStep { ) // Rely on overscrollBehavior to prevent scrolling outside of the Dialog. - d.addEventListener(doc, 'touchstart', (e) => { - if (DOM.isHTMLorSVGElement(e.target) && DOM.hasInlineStyle(e.target)) { - if (inAllowedContainer(e.target)) { - // Find the root of the allowed containers - let rootContainer = e.target - while ( - rootContainer.parentElement && - inAllowedContainer(rootContainer.parentElement) - ) { - rootContainer = rootContainer.parentElement! - } + d.group((_d) => { + d.addEventListener(doc, 'touchstart', (e) => { + _d.dispose() + + if (DOM.isHTMLorSVGElement(e.target) && DOM.hasInlineStyle(e.target)) { + if (inAllowedContainer(e.target)) { + // Find the root of the allowed containers + let rootContainer = e.target + while ( + rootContainer.parentElement && + inAllowedContainer(rootContainer.parentElement) + ) { + rootContainer = rootContainer.parentElement! + } - d.style(rootContainer, 'overscrollBehavior', 'contain') - } else { - d.style(e.target, 'touchAction', 'none') + _d.style(rootContainer, 'overscrollBehavior', 'contain') + } else { + _d.style(e.target, 'touchAction', 'none') + } } - } + }) }) d.addEventListener( From 874f623045a2782da7ce90406c6c008d41dc3013 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 19 Sep 2025 12:38:54 +0200 Subject: [PATCH 2/2] update changelog --- packages/@headlessui-react/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 09ff39562..fb7a7dd84 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure sibling `Dialog` components are scrollable on mobile ([#3796](https://github.com/tailwindlabs/headlessui/pull/3796)) - Infer `Combobox` type based on `onChange` handler ([#3798](https://github.com/tailwindlabs/headlessui/pull/3798)) - Allow home/end key default behavior inside `ComboboxInput` when `Combobox` is closed ([#3798](https://github.com/tailwindlabs/headlessui/pull/3798)) +- Ensure interacting with a `Dialog` on iOS works after interacting with a disallowed area ([#3801](https://github.com/tailwindlabs/headlessui/pull/3801)) ## [2.2.8] - 2025-09-12