From 20b3b65e76fe5ebeba2fb6c2505f0d3ec03492aa Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 21 Dec 2023 02:49:06 +0100 Subject: [PATCH] Further fine tune scroll locking on iOS (#2891) * further fine tune scroll locking on iOS * update CHANGELOG --- packages/@headlessui-react/CHANGELOG.md | 4 ++- .../document-overflow/handle-ios-locking.ts | 36 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index ea183915a..f9eacb201 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Further fine tune scroll locking on iOS ([#2891](https://github.com/tailwindlabs/headlessui/pull/2891)) ## [2.0.0-alpha.2] - 2023-12-20 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 24b4f177a..2c577e340 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 @@ -95,30 +95,26 @@ export function handleIOSLocking(): ScrollLockStep { // Check if we are scrolling inside any of the allowed containers, if not let's cancel the event! if (e.target instanceof HTMLElement) { if (inAllowedContainer(e.target as HTMLElement)) { - // We are in an allowed container, however on iOS the page can still scroll in - // certain scenarios... - let rootContainer = e.target - while ( - rootContainer.parentElement && - inAllowedContainer(rootContainer.parentElement) - ) { - rootContainer = rootContainer.parentElement! - } - - let scrollableParent = rootContainer + // Even if we are in an allowed container, on iOS the main page can still scroll, we + // have to make sure that we `event.preventDefault()` this event to prevent that. + // + // However, if we happen to scroll on an element that is overflowing, or any of its + // parents are overflowing, then we should not call `event.preventDefault()` because + // otherwise we are preventing the user from scrolling inside that container which + // is not what we want. + let scrollableParent = e.target while ( scrollableParent.parentElement && - // Assumption that we are always used in a Headless UI Portal. Once we reach the - // portal, its over. + // Assumption: We are always used in a Headless UI Portal. Once we reach the + // portal itself, we can stop crawling up the tree. scrollableParent.dataset.headlessuiPortal !== '' ) { - // Verify that we are in a scrollable container (which may or may not overflow yet) - let css = window.getComputedStyle(scrollableParent) - if (/(auto|scroll)/.test(css.overflow + css.overflowY + css.overflowX)) { - break - } - - // Check if the scrollable container is already overflowing + // Check if the scrollable container is overflowing or not. + // + // NOTE: we could check the `overflow`, `overflow-y` and `overflow-x` properties + // but when there is no overflow happening then the `overscrollBehavior` doesn't + // seem to work and the main page will still scroll. So instead we check if the + // scrollable container is overflowing or not and use that heuristic instead. if ( scrollableParent.scrollHeight > scrollableParent.clientHeight || scrollableParent.scrollWidth > scrollableParent.clientWidth