Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure moving focus within a Portal component, does not close the Popover component #2492

Merged
merged 12 commits into from May 19, 2023

Conversation

RobinMalfait
Copy link
Collaborator

@RobinMalfait RobinMalfait commented May 11, 2023

The Popover component closes automatically if the focus is moved outside of the Popover.Panel. However, this also happens if you render a Portal component inside the Popover.Panel and move focus within that Portal component.

From a DOM perspective, the focus is not in the Popover.Panel anymore, so therefore it will close. This PR fixes that issue and will allow focus in Portal components that originate from within the Popover.Panel component. We can use the React tree (using the Context API) to see if a certain DOM element is still nested from a React's perspective.

This PR also makes sure that only Portal components that originate from the Popover component will be marked as a valid Portal location. If you trigger the opening of a sibling Dialog component from within the Popover.Panel component, then focus moving into the Dialog will close the Popover component.

Fixes: #2481

@vercel
Copy link

vercel bot commented May 11, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
headlessui-react ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 19, 2023 11:01am
headlessui-vue ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 19, 2023 11:01am

Comment on lines +9 to 17
'Portal',
'Combobox',
'Dialog',
'Disclosure',
'FocusTrap',
'Listbox',
'Menu',
'Popover',
'Portal',
'RadioGroup',

Choose a reason for hiding this comment

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

Should be sorted alphabetically? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They are, but the exports are sorted alphabetically and the export for the portal itself changed which is now hoisted to the top in the test.

https://github.com/tailwindlabs/headlessui/pull/2492/files#diff-1532aa9678ff2e077efc3e5259963ecd0cb1725bb4dabf53ffabf87662f19594

This way we can reuse it in other components when needed.
This allows us to find all the `Portal` components that are nested in a
given component without manually adding refs to every `Portal` component
itself.

This will come in handy in the `Popover` component where we will allow
focus in the child `Portal` components otherwise a focus outside of the
`Popover` will close the it. In other components we often crawl the DOM
directly using `[data-headlessui-portal]` data attributes, however this
will fetch _all_ the `Portal` components, not the ones that started in
the current component.
The `Popover` component will close by default if focus is moved outside
of it. However, if you use a `Portal` comopnent inside the
`Popover.Panel` then from a DOM perspective you are moving the focus
outside of the `Popover.Panel`. This prevents the closing, and allows
the focus into the `Portal`.

It currently only allows for `Portal` components that originated from
the `Popover` component. This means that if you open a `Dialog`
component from within the `Popover` component, the `Dialog` already
renders a `Portal` but since this is part of the `Dialog` and not the
`Popover` it will close the `Popover` when focus is moved to the
`Dialog` component.
This ensures that if you have a structure like this:

```jsx
<Dialog> {/* Renders a portal internally */}
   <Popover>
      <Portal> {/* First level */}
         <Popover.Panel>
            <Menu>
               <Portal> {/* Second level */}
                  <Menu.Items>
                  {/* ... */}
                  </Menu.Items>
               </Portal>
            </Menu>
         </Popover.Panel>
      </Portal>
   </Popover>
</Dialog>
```

That working with the `Menu` doesn't close the `Popover` or the `Dialog`.
This will allow you to pass in portal elements as well. + cleanup of
the resolving of all DOM nodes.
Shorthand to check if any of the root containers contains the given
element.
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.

Clicking on a Combobox with portal inside of a Popover with portal closes the Popover
2 participants