Skip to content

Conversation

RobinMalfait
Copy link
Member

This PR fixes an issue where opening a sibling dialog doesn't allow you to scroll the second (top most) dialog on iOS anymore.

The issue here is that on iOS we have to do a lot of annoying work to make the dialog behave like a dialog and prevent scrolling outside of the dialog.

To make this all work, we PUSH and POP some information to know what the top-most dialog is. The moment there is 1 open dialog, and as long as there is 1 dialog we scroll lock the page, the moment there are 0 dialogs again we unlock the page again.

Each dialog also has a set of "allowed containers", these are just container DOM nodes where we allow scrolling.

But the issue is that we were dealing with stale data. These are the events you see when we open dialogs:

[Log] Open dialog #1
[Log] PUSH
[Log] SCROLL_PREVENT  → We start preventing scroll because there is 1 dialog
[Log] Open dialog #2
[Log] PUSH            → We opened a second dialog, but the `SCROLL_PREVENT`
                      → was already active and doesn't re-evaluate
[Log] POP             → POP from dialog #1 because not the top-most anymore

Any time we PUSH we also track new meta data with the allowed containers. But
we were already preventing scroll, so we had stale meta data.

To solve this, I can think of 2 solutions:

  1. The moment we POP, we also SCROLL_ALLOW again, but realize that we still have 1 dialog open, so we SCROLL_PREVENT again. This just feels wrong to me though.
  2. Any time we PUSH / POP, we re-evaluate the meta data, and when receive the meta data in the SCROLL_PREVENT event listeners, we call the function to get the latest meta data not the old stale data.

Test plan

In both scenario's I first open the dialog, then scroll.

before:

ScreenRecording_09-17-2025.17-18-13_1.MP4

after:

ScreenRecording_09-17-2025.17-18-35_1.MP4

Fixes: #3378

This is just a very very small optimization, but wanted to optimize it
since I noticed it.

Essentially `containers.flatMap(resolve => resolve())` is a bit wasteful
if we you have `n` containers, but you already found a match in the
  first container.

  If that's the case, then we resolved `n-1` containers for nothing.
  Depending on what we actually do during this resolve step it could be
  a lot of unnecessary work (e.g.: querying the DOM).
Otherwise we have to build it on every scroll event and that would be
very expensive.
Copy link

vercel bot commented Sep 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
headlessui-react Ready Ready Preview Comment Sep 17, 2025 3:22pm
headlessui-vue Ready Ready Preview Comment Sep 17, 2025 3:22pm

Comment on lines +77 to +79
meta() {
return entry.computedMeta
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered making this a getter, but then you have to use entry.meta instead of destructuring it to prevent the meta to be evaluated already.

// that happens later can update the meta information. Otherwise we would
// use stale meta information.
meta() {
return entry.computedMeta
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used entry instead of destructuring the computedMeta to ensure we are looking at the latest data. Alternatively we could use this.get(doc).computedMeta or something

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems fine 🤷‍♂️

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this kind of sounds like the opposite of the above message, but the difference is a getter here vs destructuring in another spot so the connection might not be as clear.

@RobinMalfait RobinMalfait merged commit b0615ad into main Sep 17, 2025
8 checks passed
@RobinMalfait RobinMalfait deleted the fix/issue-3378 branch September 17, 2025 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sibling Dialogs can't touch-scroll on mobile

2 participants