-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
Describe the bug: Attachment callback is running before the pending UI updates which is an unexpected behaviour / bug. They should run after any pending UI updates, just like how standalone $effects behave.
https://svelte.dev/playground/195d38aeb8904649befaac64f0a856c4?version=5.36.2
How to reproduce?
- Play the TicTacToe game in the provided playground link WITH KEYBOARD ONLY until the game is finished (either winning or draw). Manage focus and navigate the focus on the cells using the Tab key and press the Enter key to make a move.
- After the game is over, navigate to the "Reset History" button by shifting the focus using the Tab key.
- Press Enter on the "Reset History" button.
The expected outcome is the focus should automatically shift from the "Reset History" button back to the first cell in the game. But that does not happen. The reason is the {@attach} attachment callback responsible for focus management is NOT behaving as expected.
- The
captureFocusmethod on App class handles shifting of focus to the desired element referenced bythis.nextEleToFocusstate variable. - This
captureFocusmethod runs automatically afterresetHistorymethod (the event handler of "Reset History" button) is executed, becausecaptureFocusis run inside an implicit effect, since it is an{@attach}attachment on.containerelement. The state mutations caused byresetHistoryresets all the state used in the component, which results in all cells being back to active, which were previously in disabled state. - The problem is
captureFocusmethod is executed before all the queued pending UI updates (caused by state mutations of theresetHistorymethod) are committed to the DOM. The DOM whencaptureFocusis invoked is in a stale/pre-mature state, where all the game cells are indisabledstate i.e the history reset is not yet committed to the DOM, hence setting focus on the first cell does not work as expected.
When the same captureFocus function is used inside a standalone $effect instead of an attachment, this bug is not seen. The standalone $effect waits before all the pending UI updates caused by state mutations from resetHistory method are committed to the DOM, before running the effect callback. The focus is correctly shifted to the first cell after pressing the Enter key on "Reset History" button.
As a quickfix, Im using the tick() function to manually wait for pending UI updates inside the captureFocus method (when used as an attachment) before firing the focus() method on the desired element. This quickfix is not needed for the standalone effect approach.
class App {
...
resetHistory = () => {
this.history.reset()
this.setFocusTo(this.selector.cell(0))
}
...
captureFocus = async (container) => {
if (!this.nextEleToFocus) return
// await tick() // tick should not be needed but used as a quickfix.
container.querySelector(this.nextEleToFocus.selector).focus()
}
...
}
const app = new App()
// let container <- bind this to the '.container' element, uncomment these lines, and remove the {@attach} code on '.container' element to test the $standalone effect behaviour.
// $effect(() => app.captureFocus(container));
...Reproduction
https://svelte.dev/playground/195d38aeb8904649befaac64f0a856c4?version=5.36.2
System Info
Playground env version=5.36.2Severity
annoyance