Skip to content

Commit 75eda1c

Browse files
Rich-Harrispaoloricciutiteemingc
authored
fix: update DOM before running navigate callbacks (#14829)
* fix: also await for `settled` in case there's no fork but there's async work * await tick() twice * update changeset * add test * format * lint --------- Co-authored-by: paoloricciuti <[email protected]> Co-authored-by: Tee Ming <[email protected]> Co-authored-by: Tee Ming Chew <[email protected]>
1 parent 4991df5 commit 75eda1c

File tree

7 files changed

+79
-9
lines changed

7 files changed

+79
-9
lines changed

.changeset/loud-items-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: update DOM before running navigate callbacks

packages/kit/src/runtime/client/client.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ async function _preload_data(intent) {
540540
try {
541541
return svelte.fork(() => {
542542
root.$set(result.props);
543+
update(result.props.page);
543544
});
544545
} catch {
545546
// if it errors, it's because the experimental flag isn't enabled
@@ -1729,25 +1730,25 @@ async function navigate({
17291730
commit_promise = fork.commit();
17301731
} else {
17311732
root.$set(navigation_result.props);
1733+
update(navigation_result.props.page);
1734+
1735+
commit_promise = svelte.settled?.();
17321736
}
17331737

1734-
update(navigation_result.props.page);
17351738
has_navigated = true;
17361739
} else {
17371740
initialize(navigation_result, target, false);
17381741
}
17391742

17401743
const { activeElement } = document;
17411744

1742-
const promises = [tick()];
1745+
await commit_promise;
17431746

1744-
// need to render the DOM before we can scroll to the rendered elements and do focus management
1745-
// so we wait for the commit if there's one
1746-
if (commit_promise) {
1747-
promises.push(commit_promise);
1748-
}
1749-
// we still need to await tick everytime because if there's no async work settled resolves immediately
1750-
await Promise.all(promises);
1747+
// TODO 3.0 remote — the double tick is probably necessary because
1748+
// of some store shenanigans. `settled()` and `f.commit()`
1749+
// should resolve after DOM updates in newer versions
1750+
await svelte.tick();
1751+
await svelte.tick();
17511752

17521753
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
17531754
let scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
import { onNavigate } from '$app/navigation';
3+
import { resolve } from '$app/paths';
4+
5+
let { children } = $props();
6+
7+
onNavigate((navigation) => {
8+
if (!document.startViewTransition || navigation.willUnload) return;
9+
10+
return new Promise((resolve) => {
11+
document.startViewTransition(async () => {
12+
resolve();
13+
await navigation.complete;
14+
console.log('navigated');
15+
});
16+
});
17+
});
18+
</script>
19+
20+
<ul>
21+
<li><a href={resolve('/on-navigate/a')}>a</a></li>
22+
<li><a href={resolve('/on-navigate/b')}>b</a></li>
23+
</ul>
24+
25+
{@render children()}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>Page A</h1>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
4+
// @ts-ignore i guess svelte-check doesn't see the right config?
5+
await new Promise((resolve) => setTimeout(resolve, 500));
6+
7+
onMount(() => {
8+
console.log('mounted');
9+
});
10+
</script>
11+
12+
<h1>Page B</h1>

packages/kit/test/apps/options/svelte.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ const config = {
4141
router: {
4242
resolution: /** @type {'client' | 'server'} */ (process.env.ROUTER_RESOLUTION) || 'client'
4343
}
44+
},
45+
compilerOptions: {
46+
experimental: {
47+
async: true
48+
}
4449
}
4550
};
4651

packages/kit/test/apps/options/test/test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,24 @@ test.describe('Routing', () => {
351351
await expect(page.locator('h2')).toHaveText('target: 0');
352352
});
353353
});
354+
355+
test.describe('Async', () => {
356+
test("updates the DOM before onNavigate's promise is resolved", async ({
357+
page,
358+
javaScriptEnabled
359+
}) => {
360+
test.skip(!javaScriptEnabled);
361+
362+
await page.goto('/path-base/on-navigate/a');
363+
364+
const logs = [];
365+
page.on('console', (msg) => {
366+
logs.push(msg.text());
367+
});
368+
369+
await page.getByRole('link', { name: 'b' }).click();
370+
371+
await expect(page.locator('h1', { hasText: 'Page B' })).toBeVisible();
372+
expect(logs).toEqual(['mounted', 'navigated']);
373+
});
374+
});

0 commit comments

Comments
 (0)