-
Notifications
You must be signed in to change notification settings - Fork 63
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
Focus jumps to body when updating content inside FocusTrap #962
Comments
You're welcome! 😄
All the callback options provided by I starred at your recording and your code for a while (thank you; very useful!) and the best explanation I can come up with is that the focus escapes because there is a short gap of time where no trap exists. Since clicking on the "Go to variant X" button in either variant changes a state variable in the app itself, it should be causing the But what bothers me in this is that if I'm correct, then why are we not seeing your The variants of the modal only concern its content, not the entire modal, I would recommend you move that If you're not satisfied by that theory, then what would be helpful is to see if you can replicate this using only https://github.com/focus-trap/focus-trap, eliminating React and focus-trap-react's wrapper from the equation. That would help further isolate the issue. Note that The following section of |
@stefcameron Appreciate the thorough followup.
I have tried to do this in a forked version of the sandbox: https://codesandbox.io/s/focus-trap-react-body-focus-jump-v2-t0m12f?file=/src/App.js. Unfortunately it does not seem to make any difference regarding the problem.
These are my thoughts exactly.
I agree that is a reasonable next step. |
I have tried to recreate the setup in pure JavaScript with the core With that in mind I tried to dig a bit more into the situation and found this:
Source: https://amberwilson.co.uk/blog/where-did-the-focus-go/ Note that in my example no event is fired when focus changes to the body. With that and the above explanation in mind one approach to mitigate focus escape in this example might be to attach a |
@kasperg Thanks for testing this in focus-trap, and starting that PR over there for an eventual fix, much appreciated!
Nice find in that blog post. The But the concept of what you're proposing could work well. Since that blog post is using the I'm not immediately sure which branch of the Looking at this code (which predates my involvement as a Maintainer; so it's been there for a long time!) again, I'm suddenly intrigued by the fact that if Seems like we should treat that as an escape. Also, in light of your use case (focused element gets removed from the DOM), I think we'll need to add a check for Something like this inspired by: https://github.com/focus-trap/tabbable/blob/master/src/index.js#L314 } else {
// escaped! pull it back in to where it just left
e.stopImmediatePropagation();
if (state.mostRecentlyFocusedNode && isFocusable(state.mostRecentlyFocusedNode, config.tabbableOptions)) {
tryFocus(state.mostRecentlyFocusedNode);
} else {
// either we don't have a most-recently-focused node (strange) or it's likely no longer in the DOM
tryFocus(getInitialFocusNode());
} WDYT? |
@stefcameron, I agree that this would be a preferable fix as it builds on constructs already used within the project. I do not think it will work though. A breakpoint placed within the focus-trap's I also updated the sandbox with an example |
@kasperg I see what you mean... I found another blog post about monitoring the element that gets focus, and that one indicated that So I tried console logging the
When the button is clicked and removed, nothing fires. 🤯 Going back to the DevTools, I'm starting to think that the So if we end-up in this situation, has focus really escaped the trap? It seems to be nowhere more than actually being somewhere... Clicking around in this state will just cause the trap to prevent clicks if they're outside of it (or deactivate the trap if that's configured). Pressing tab will immediately discover focus isn't in the trap (because It all seems pretty seamless. If we add the Does the fact that |
@stefcameron The problem originates in a bug report where a colleague has been checking the accessibility of our application. They use NVDA and in this case they claim it is a problem referring to WCAG 2.1 success criterium 2.4.3 regarding focus order. NVDA is only available on Windows and being on a Mac myself it is not easy to verify. I have tried testing the sandbox using NVDA a remote machine and as far as I can tell it is also a problem here. react-focus-trap-nvda.movAs I see it:
I had expected 5) to be that the NVDA viewer prints Loading and then prints the "Another button" and "Close dialog" buttons. I also tried checking the sandbox using OSX VoiceOver. I find it harder to recreate the results here but as I see it the process is as follows:
Based on the behavior by VoiceOver in 2) and 7) by immediately saying the name of the currently focused element I had expected VoiceOver to either say "Loading" and then "Another button button", "Close dialog button" or "Another button button".
I think the right way to go would be to set focus back to the first element in the trap if the currently focused element is removed. One way to do so might be to reactivate the trap containing the removed element. |
The ally.js project has a nice writeup and playground on what happens across browsers when you remove/detach the currently focused element: https://allyjs.io/tutorials/mutating-active-element.html. |
Very useful writeup -- as well as all your testing with NVDA remote and macOS Voice Over!
If it weren't for the screen reader reading "Open dialog.... Some button" as soon as the element is removed, I'd lean more toward saying this isn't technically a focus escape (because a focused But from the disabled user's point of view, it is. So I'm back leaning the other way, and I'm not sure how else to address it other than with a The new code would have to be careful to check if the API exists before using it (for browser backward compatibility), and upon discovering the We would probably have to add a check in As for the observer, I think there are two possible approaches there:
I'm not sure how the observer reacts when the very node it's watching gets removed. Approach (1) might be the only way. WDYT about this solution? |
I also think that approach 1 is the way to go. Your description above along with the test in focus-trap/focus-trap#932 should be helpful when implementing a fix. |
@all-contributors add @kasperg for bug |
I've put up a pull request to add @kasperg! 🎉 |
Published in focus-trap-react v10.1.4 |
Thanks for sharing
focus-trap
.I am trying to use it to retain focus inside a
<dialog>
with a<FocusTrap>
inside but when I update content inside that<dialog>
clicking on a<button>
the active element jumps to the<body>
element. When usingtab
focus moves back within the focus trap but I had expected it to stay within the focus trap during the entire process as it contains another focusable element during the entire proces.Based on my testing none of the callbacks provided by focus trap are called either.
I have tried to show the process here and in this sandbox:
Monosnap.screencast.2023-03-26.14-51-25.mp4
https://codesandbox.io/s/focus-trap-react-body-focus-jump-khjkr8?file=/src/App.js
I am not sure whether this behavior is a bug or actually to be expected based on the implementation.
The text was updated successfully, but these errors were encountered: