-
-
Notifications
You must be signed in to change notification settings - Fork 259
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
[Bug]: Dialog component in combination with http-fetch in a watcher breaks autofocus #680
Comments
Hello! Thank you for looking into it! I've spend so much time today again on it and also saw some strange behavior when removing nextTick() completely. I often - not always - got an endless update loop. I tried three other solutions today
That way we can remember the activeElemement, which gets changed by the MutationObserver. But this might break something else, when during nextTick the focus gets changed.
That way we would rely on the implementation of the handleMutation, that it changes the focus to container on failure. Don't like it.
This would be actually my preferred way, as it is clear, that we don't want the MutationObserver to fix the focus, while we haven't init ourself during the watchEffect. As the watchEffect actually has the task to set the focus initially. And if some user-code sets the focus during nextTick somewhere else the init routine wouldn't mess with it, as hasFocusedCandidate would be true as a result. It is a counter, as the async function gets called twice before the first is actually finished. |
Sorry for spamming, I've optimized the 3rd option. I wrote this code here blind without IDE, might have still typos. function useAsyncExecuting<T>(promise: () => Promise<T>)
{
let counter = 0;
async function execute()
{
try
{
counter += 1;
return await promise();
}
finally
{
counter -= 1;
}
}
function isExecuting()
{
return counter > 0;
}
return {
execute,
isExecuting
}
}
const { execute: executeNextTick, isExecuting: isExecutingNextTick } = useAsyncExecuting(nextTick); function handleMutations(mutations: MutationRecord[]) {
// Do not handle mutations for the duration of nextTick during the initialization
// The initialization will take care of finding and focusing the first element
if(isExecutingNextTick())
return;
const isLastFocusedElementExist = container.contains(lastFocusedElementRef.value)
if (!isLastFocusedElementExist)
focus(container)
} During the async watchEffect: await executeNextTick(); |
Thanks for digging into this @heladrion ! Instead of using a workaround for this.. I might need to might the root cause of why the FIY: I've tried your suggest solution and it's breaking tests. So it might not be feasible. |
After testing for quite a bit, I believe your proposed solution |
I digged today into Vue itself with the SFC Playground, but couldn't find any differences between the calls. But it is super hard to debug. Yes I fixed version
to the following
which is actually more in line with radix-ui If someone wants to debug it deeper on Chrome, here is the Vue SFC Playground URL for localhost on port 5173. The files are encoded in the URL, so it should work. |
Environment
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-upttnb?file=src%2FApp.vue
Steps to reproduce
Using Chrome on Windows - Firefox worked! Don't know about Mac/Linux
=> The focus is on the first field
=> The focus is still on the first field
=> The autofocus of the first field is gone and will forever be gone. Doesn't matter how often you close and open it.
Describe the bug
After typing something into the edit field the autofocus in die Dialog component doesn't work anymore. The edit field itself triggers a watcher which fires an http-fetch.
To encounter the bug the fetch has to succeed. If the fetch fails the bug won't happen.
Expected behavior
The autofocus should focus the first element.
Context & Screenshots (if applicable)
Some things I noticed:
The await nextTick() fires the MutationObserver because the icon gets loaded not on mount, but delayed. During the MutationObserver handling the init isn't really done yet, so the focus gets reset to the dialog itself. After the nextTick the document.activeElement gets used, which is already changed to the dialog, due to the MutationObserver.
I mean, this could be circumvented with some flags, or saving the activeElement before nextTick. But why does it only happen after a fetch in a watcher. This is baffling. Maybe someone can figure it out. I'm on day 3 meanwhile.
Initially the error was in a bigger project.
There I had a teleport on the dialog
<!-- teleport start --><!-- teleport end -->
which triggered the MutationObserver while opening. And vue-query which fired the fetch in a watcher.The text was updated successfully, but these errors were encountered: