-
Notifications
You must be signed in to change notification settings - Fork 0
/
page-transition.ts
127 lines (107 loc) · 3.08 KB
/
page-transition.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { beforeNavigate, Navigating, navigating } from "./navigating";
import { onDestroy } from "svelte";
import reducedMotion from "./reduced-motion";
type Payload = {
from?: URL;
to?: URL;
type: string | null;
};
// type PayloadCB = (payload: Payload) => void;
function getNavigationStore() {
/** @type {((val?: any) => void)[]} */
let callbacks: any[] = [];
const navigation = {
...navigating,
complete: async () => {
await new Promise((res, _) => {
callbacks.push(res);
});
},
};
// This used to subscribe inside the callback, but that resolved the promise too early
const unsubscribe = navigating.subscribe((n) => {
if (n === null) {
while (callbacks.length > 0) {
const res = callbacks.pop();
res?.();
}
}
});
onDestroy(() => {
unsubscribe();
});
return navigation;
}
/**
* @callback pageTransitionCallback
* @param {{ from: URL, to: URL, type: TransitionType}} nav
*/
const beforeCallbacks = new Set(); // before transition starts
const afterCallbacks = new Set(); // after transition has completed
const incomingCallbacks = new Set(); // when new page is loaded but transition has not completed
/**
* @param {pageTransitionCallback} fn
*/
export const beforePageTransition = (fn) => {
beforeCallbacks.add(fn);
onDestroy(() => {
beforeCallbacks.delete(fn);
});
};
/**
* @param {pageTransitionCallback} fn
*/
export const whileIncomingTransition = (fn) => {
incomingCallbacks.add(fn);
onDestroy(() => {
incomingCallbacks.delete(fn);
});
};
/**
* @param {pageTransitionCallback} fn
*/
export const afterPageTransition = (fn) => {
afterCallbacks.add(fn);
onDestroy(() => {
afterCallbacks.delete(fn);
});
};
/**
* @param {(from: string, to: string) => string?} getType
*/
export const preparePageTransition = (getType = (_from, _to) => null) => {
const navigation = getNavigationStore();
let isReducedMotionEnabled = false;
let unsubscribeReducedMotion = reducedMotion.subscribe(
(val) => (isReducedMotionEnabled = val)
);
// before navigating, start a new transition
beforeNavigate(({ from, to }: Navigating) => {
// Feature detection
if (!(document as any).createDocumentTransition || isReducedMotionEnabled) {
return;
}
const type = getType(from?.pathname, to?.pathname ?? "");
try {
const transition = (document as any).createDocumentTransition();
const payload: Payload = { from, to, type };
beforeCallbacks.forEach((fn: any) => fn(payload));
// init before transition.start so the promise doesn't resolve early
const navigationComplete = navigation.complete();
transition
.start(async () => {
await navigationComplete;
incomingCallbacks.forEach((fn: any) => fn(payload));
})
.then(() => {
afterCallbacks.forEach((fn: any) => fn(payload));
});
} catch (e) {
// without the catch, we could throw in beforeNavigate and prevent navigation
console.error(e);
}
});
onDestroy(() => {
unsubscribeReducedMotion();
});
};