Material-ui: [TrapFocus] Steals focus from nested portal

Created on 14 May 2019  ·  19Comments  ·  Source: mui-org/material-ui

  • [x] This is not a v0.x issue.
  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 🤔

TrapFocus ensures focus is kept on elements within its React element spanning tree.

Current Behavior 😯

Since TrapFocus keeps the focus on elements within its DOM hierarchy, focusable elements within its React hierarchy but outside its DOM hierarchy (e.g. inside of a Popper or a portal) can't be focused.

Steps to Reproduce 🕹

Example:
https://codesandbox.io/s/llv0m7r5jq

Click on the [Open Dialog] button, then on the [Open Popper] button. In the console, you'll see that the TextField within the Popper is focused (as it should since it's autoFocus), then instantly blurred back by the TrapFocus.

Context 🔦

I'm building an autocomplete component with a "popout" behaviour similar to the one demonstrated at the bottom of react-select's Advanced doc.

The autocomplete button itself is rendered within a dialog, and I'm using a popper to display the text input and options. Right now I'm disabling the portal behaviour so it's kept inside the modal's DOM hierarchy, but this causes issues with the select's option list overflowing from the modal. My final solution seems very hacky and prone to unexpected failure, so I'd love this to be handled by MUI itself.

Your Environment 🌎

For the stack, see codesandbox.

| Tech | Version |
|--------------|---------|
| Browser | Chrome 74.0.3729.131 |

Duplicates

bug 🐛 TrapFocus

Most helpful comment

@verbart Thanks, it looks like the same problem than the author of this issue has. disabledPortal allows to partially work around the problem, as well as disableEnforceFocus.

Do we have a better solution than to wait for the release of the <FocusScope> component?

elements nested in portals will traverse as expected via the React fiber tree rather than the DOM tree.

https://github.com/facebook/react/pull/15487

All 19 comments

Try a html dialog mayb?

@alexkirsz The behavior you are describing sounds correct to me. TrapFocus only cares about the DOM structure. The logic isn't applied at the React level. I'm not sure the upcoming FocusScope component in React handles this case either: https://github.com/facebook/react/pull/15487.

I would challenge your problem in the first place. Why do you need to render the input outside of the dialog? Did you consider using a Popover instead of a Popper? This component uses FocusTrap internally. I will handle the case correctly.

This makes me think of a https://github.com/mui-org/material-ui/issues/15450#issuecomment-486197946 where the problem is about interoperability. We have a couple of ways to work around the problem. I'm interested in finding the best option.

@alexkirsz The behavior you are describing sounds correct to me. TrapFocus only cares about the DOM structure. The logic isn't applied at the React level. I'm not sure the upcoming FocusScope component in React handles this case either: facebook/react#15487.

@oliviertassinari Thanks for the FocusScope link, I wasn't aware of the ongoing efforts with regards to focus handling in React!

elements nested in portals will traverse as expected via the React fiber tree rather than the DOM tree.

This certainly sounds like it would fix my particular issue! :)

I would challenge your problem in the first place. Why do you need to render the input outside of the dialog? Did you consider using a Popover instead of a Popper? This component uses FocusTrap internally. I will handle the case correctly.

I'm having to apply some custom positioning logic as I want the dropdown to extend to the bottom of the window, but no further. Since I believe both Modal and DialogContent define an overflowY, the dropdowns ends up half-hidden and causes a scrollbar to appear. Popper.js also doesn't have an option for height constraints so I implemented my own modifier instance. I think I initially tried with a Popover, but I can't remember why I would have moved away so I'll probably end up trying it again.

FYI, the component currently looks like this:

image

The option list uses react-virtualized under the hood since I often need to display very large lists of items.

@alexkirsz Thanks for providing more details. Looking at the visual output. I believe you should be using the Popper component for rendering the list of items, not the input element. So I think that we can close the issue.

Regarding the component you have built, it something Material-UI should provide directly. I don't think that we should ask our users to do it every single time. We will work on providing a better solution. If you can share the source, once it meets your expectation, it would be awesome :).

@oliviertassinari Do you mean replacing the “Select engine” button with the TextField? Thing is, the TextField is only used for filtering between the different options, but its value doesn’t fully represent an option. Hence the “popout” behaviour.

I'm sorry, I don't follow you. My points were 1. Your codesandbox can be solved with a different approach. 2. Going forward, Material-UI, as a library, wants to support this problem with a built-in component.

I was referring to the following:

Looking at the visual output. I believe you should be using the Popper component for rendering the list of items, not the input element.

The specifics of the component require the TextField component to be nested within a portal, to achieve the "popout" behaviour wherein you can click on a button and a popper will appear with both a filter input and the filtered option list. So I don't see another way of achieving the same visual output without rendering both components within a portal :/

I tried both react-select and downshift, but I had issues with customizing my component in both. The biggest issue I had with downshift was the fact that it was re-rendering the whole component on every single state change, which caused serious performance issues with large lists.

@alexkirsz Interesting.

Have same problem - form fields doesn't have focus when use it in Popper has opened from Dialog

@oliviertassinari No duplication, ESM modules only

If Popper has disablePortal prop, form fields inside it works good

@verbart Thanks, it looks like the same problem than the author of this issue has. disabledPortal allows to partially work around the problem, as well as disableEnforceFocus.

Do we have a better solution than to wait for the release of the <FocusScope> component?

elements nested in portals will traverse as expected via the React fiber tree rather than the DOM tree.

https://github.com/facebook/react/pull/15487

@oliviertassinari Thanks, did not know about the "disableEnforceFocus" property. While it best saves the situation.

Sorry if wrong issue but I think what I'm experiencing is related.

We have a Drawer (which renders Modal -> Portal -> TrapFocus).
In the Drawer we have a link that opens LiveChat dialog (technically it's an iframe rendered in an absolutely positioned div).
Now it's impossible to type in the LiveChat inputs because the focus is instantly stolen. If I close the drawer then LiveChat works just fine.

@emirotin That sounds like #17497

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mb-copart picture mb-copart  ·  3Comments

ryanflorence picture ryanflorence  ·  3Comments

rbozan picture rbozan  ·  3Comments

ghost picture ghost  ·  3Comments

newoga picture newoga  ·  3Comments