[FocusScope] Fix bug where focus is moved to container when it shouldn't be #3115
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
Fixes bug described in #2436 where focus was moved to the container unnecessarily. The bug occurs when an element is removed via an
onBlur
callback.Before
before.mov
After
after.mov
More technical details
Let's say we have some error text "This field is required" that is removed when the field is filled in and blurred. The current code checks if any node is removed in the mutation observer (not just if the focused element is removed), and if so focuses the container. The text getting removed triggers this condition:
primitives/packages/react/focus-scope/src/FocusScope.tsx
Line 115 in 660060a
Additionally, the early return is not triggered here:
primitives/packages/react/focus-scope/src/FocusScope.tsx
Line 113 in 660060a
Because during any blur, the activeElement is document.body. This means checking
document.activeElement === document.body
is not a robust way of checking if the focus was lost.The fix was to prevent focusing the container if the last focused element is still in the container:
primitives/packages/react/focus-scope/src/FocusScope.tsx
Lines 124 to 131 in a3c1e51
I left the previous checks in place because I was a bit scared to remove them, but it may not be required. The vue fork fixed this by replacing the existing checks: https://github.com/radix-vue/radix-vue/pull/519/files