-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
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
[TrapFocus] Fix portal support #21610
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic looks solid, I couldn't find anything better.
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js
Outdated
Show resolved
Hide resolved
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js
Outdated
Show resolved
Hide resolved
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.test.js
Outdated
Show resolved
Hide resolved
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js
Outdated
Show resolved
Hide resolved
Co-authored-by: Olivier Tassinari <olivier.tassinari@gmail.com>
….test.js Co-authored-by: Olivier Tassinari <olivier.tassinari@gmail.com>
Co-authored-by: Olivier Tassinari <olivier.tassinari@gmail.com>
I think that it would be great to check that the changes solve this downstream issue mui/material-ui-pickers#1852. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not easy to hold different trees in your head where focus events propagate differently. I think it's important for knowledge sharing especially to go into more details for this change.
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js
Outdated
Show resolved
Hide resolved
@@ -73,7 +75,7 @@ function Unstable_TrapFocus(props) { | |||
rootRef.current.focus(); | |||
} | |||
|
|||
const contain = () => { | |||
const contain = (event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a native DOM event or a synthetic React event? As long as we're untyped I'd suggest using *event
for react events and *nativeEvent
for native DOM events to make it clear what we're dealing with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From past experience, such distinction is also useful for using event.key
vs nativeEvent.keyCode
in regard to how React polyfill IE 11.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to nativeEvent
👍
@@ -85,6 +87,18 @@ function Unstable_TrapFocus(props) { | |||
} | |||
|
|||
if (rootRef.current && !rootRef.current.contains(doc.activeElement)) { | |||
// if the focus event is different than the last syntheticEvent from the children, reset |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a hard time understanding this branch. Could you explain with JSX and an event order when we reach this branch? Ideally point to an existing test or add a new one for this branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition is trying to capture whether between the time we saved the last focused event target and this invocation of the function, it has changed. We have two scenarios we need to check:
- the focus event was invoked with different target (that is the first part of the condition)
- the element that was focused was removed and the interval for the function run out (that is the second part of the condition)
The 2 condition is captured already in the test where an element is removed and the focus moves to body, I will add a test for the other use-case (it may be hard to make sure which exact of these two condition will be caught because the function is invoked in an interval, but let me try to deal with it)
Does this clarifies the use case? I will also try to update the comment to better reflect this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On this note, I would like to test whether just the second part of the condition will be enough, as the interval would anyway invoke this function, but it may have a bit of delay, so it may not be the best idea..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newly added trest together with the previous covers all use casees I believe - focusing an dom outisde the react tree, as well focusing nodes inside portal
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js
Outdated
Show resolved
Hide resolved
@eps1lon I've updated the PR description, hopefully this clarifies things. Let me know what do you think |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really clean!
packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js
Outdated
Show resolved
Hide resolved
Tested in mui/material-ui-pickers#1852 (comment), we are good 👌 |
chore: Refactor to single variable
This PR enables portals support inside
TrapFocus
.What is the problem?
When there are portals rendered inside the
TrapFocus
, they are rendered outside of the that elements dom tree. This creates the following problem: by nature theTrapFocus
traps the focus whenever the focus is not inside it's dom tree. That means if the focus goes inside the portal, it is "stealed" by theTrapFocus
and it's put on the root element (an example of this can be find in #15694)Solution
The solution to this problem is, we need to somehow figure out if the focusing event is happening inside the React tree, not just the dom tree. I have experiment with different approaches of how this can be solved, but settled on the event propagation, as the most stable one (there was a different one that uses React's internal, you can find the link in the end of the PR description).
What we need to do is, add an
onFocus
event on the children of theTrapFocus
and set some internal ref to the last target that was focused, so that we can check that it is indeed the element focused when thecontain
method is invoked - it is a dom listener on the document for the focus event. This is necessary because we may run up to some inconsistency, when some element outside of the TrapFocus' React tree is focused.Fixes #15694
For people interested in an alternative that uses React's internals: d8c75e5.