Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork4.6k
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
captureFocus
method on App class handles shifting of focus to the desired element referenced bythis.nextEleToFocus
state variable. - This
captureFocus
method runs automatically afterresetHistory
method (the event handler of "Reset History" button) is executed, becausecaptureFocus
is run inside an implicit effect, since it is an{@attach}
attachment on.container
element. The state mutations caused byresetHistory
resets 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
captureFocus
method is executed before all the queued pending UI updates (caused by state mutations of theresetHistory
method) are committed to the DOM. The DOM whencaptureFocus
is invoked is in a stale/pre-mature state, where all the game cells are indisabled
state 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 samecaptureFocus
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 fromresetHistory
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 thetick()
function to manually wait for pending UI updates inside thecaptureFocus
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.
classApp{ ...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()}...}constapp=newApp()// 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.2
Severity
annoyance