|
1 | | -import { afterNavigate, beforeNavigate } from '$app/navigation'; |
| 1 | +import { beforeNavigate } from '$app/navigation'; |
| 2 | +import { navigating } from '$app/stores'; |
2 | 3 | import { onDestroy } from 'svelte'; |
3 | | -import { writable } from 'svelte/store'; |
4 | 4 | import reducedMotion from './reduced-motion'; |
5 | 5 |
|
| 6 | +function getNavigationStore() { |
| 7 | + /** @type {((val?: any) => void)[]} */ |
| 8 | + let callbacks = []; |
| 9 | + |
| 10 | + const navigation = { |
| 11 | + ...navigating, |
| 12 | + complete: async () => { |
| 13 | + await new Promise((res, _) => { |
| 14 | + callbacks.push(res); |
| 15 | + }); |
| 16 | + } |
| 17 | + }; |
| 18 | + |
| 19 | + // This used to subscribe inside the callback, but that resolved the promise too early |
| 20 | + const unsub = navigating.subscribe((n) => { |
| 21 | + if (n === null) { |
| 22 | + while (callbacks.length > 0) { |
| 23 | + const res = callbacks.pop(); |
| 24 | + res(); |
| 25 | + } |
| 26 | + } |
| 27 | + }); |
| 28 | + |
| 29 | + onDestroy(() => { |
| 30 | + unsub(); |
| 31 | + }); |
| 32 | + |
| 33 | + return navigation; |
| 34 | +} |
| 35 | + |
6 | 36 | export const preparePageTransition = () => { |
7 | | - const transitionStore = writable({}); |
8 | | - let unsub; |
| 37 | + const navigation = getNavigationStore(); |
9 | 38 | let isReducedMotionEnabled; |
10 | 39 |
|
11 | 40 | let unsubReducedMotion = reducedMotion.subscribe((val) => (isReducedMotionEnabled = val)); |
12 | 41 |
|
13 | | - function updateStore(key, value) { |
14 | | - transitionStore.update((current) => ({ |
15 | | - ...current, |
16 | | - [key]: value |
17 | | - })); |
18 | | - } |
19 | | - |
20 | 42 | // before navigating, start a new transition |
21 | | - beforeNavigate(({ to }) => { |
22 | | - unsub?.(); // clean up previous subscription |
23 | | - |
| 43 | + beforeNavigate(() => { |
24 | 44 | // Feature detection |
25 | 45 | if (!document.createDocumentTransition || isReducedMotionEnabled) { |
26 | 46 | return; |
27 | 47 | } |
28 | 48 |
|
29 | | - const transitionKey = to.pathname; |
30 | | - const transition = document.createDocumentTransition(); |
31 | | - transition.start(async () => { |
32 | | - // set transition data for afterNavigate hook to pick up |
33 | | - await new Promise((resolver) => { |
34 | | - updateStore(transitionKey, { transition, resolver }); |
| 49 | + try { |
| 50 | + const transition = document.createDocumentTransition(); |
| 51 | + // init before transition.start so the promise doesn't resolve early |
| 52 | + const navigationComplete = navigation.complete(); |
| 53 | + transition.start(async () => { |
| 54 | + await navigationComplete; |
35 | 55 | }); |
36 | | - updateStore(transitionKey, null); |
37 | | - }); |
38 | | - }); |
39 | | - |
40 | | - afterNavigate(({ to }) => { |
41 | | - const transitionKey = to.pathname; |
42 | | - // we need to subscribe to prevent race conditions |
43 | | - // sometimes this runs before the store is updated with the new transition |
44 | | - unsub = transitionStore.subscribe((transitions) => { |
45 | | - const transition = transitions[transitionKey]; |
46 | | - if (!transition) { |
47 | | - return; |
48 | | - } |
49 | | - const { resolver } = transition; |
50 | | - resolver(); |
51 | | - }); |
| 56 | + } catch (e) { |
| 57 | + // without the catch, we could throw in beforeNavigate and prevent navigation |
| 58 | + console.error(e); |
| 59 | + } |
52 | 60 | }); |
53 | 61 |
|
54 | 62 | onDestroy(() => { |
55 | | - unsub?.(); |
56 | 63 | unsubReducedMotion(); |
57 | 64 | }); |
58 | 65 | }; |
0 commit comments