|
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