@sveltejs/kit 1.0.0-next.289 → 1.0.0-next.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/app/navigation.js +11 -66
- package/assets/client/singletons.js +5 -13
- package/assets/client/start.js +765 -827
- package/dist/chunks/{build.js → constants.js} +6 -1
- package/dist/chunks/index.js +18 -7
- package/dist/chunks/index2.js +10 -8
- package/dist/chunks/index3.js +1 -1
- package/dist/chunks/index4.js +13 -16
- package/dist/chunks/index5.js +6 -16
- package/dist/chunks/index6.js +1 -1
- package/dist/chunks/tsconfig.js +9 -9
- package/dist/cli.js +11 -13
- package/package.json +1 -1
- package/types/index.d.ts +9 -2
- package/types/private.d.ts +2 -2
- package/assets/chunks/utils.js +0 -13
package/assets/client/start.js
CHANGED
|
@@ -1,476 +1,10 @@
|
|
|
1
|
-
import Root from '__GENERATED__/root.svelte';
|
|
2
|
-
import { fallback, routes } from '__GENERATED__/manifest.js';
|
|
3
1
|
import { onMount, tick } from 'svelte';
|
|
4
|
-
import { g as get_base_uri } from '../chunks/utils.js';
|
|
5
2
|
import { writable } from 'svelte/store';
|
|
6
3
|
import { base, set_paths } from '../paths.js';
|
|
4
|
+
import Root from '__GENERATED__/root.svelte';
|
|
5
|
+
import { routes, fallback } from '__GENERATED__/manifest.js';
|
|
7
6
|
import { init } from './singletons.js';
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} path
|
|
11
|
-
* @param {import('types').TrailingSlash} trailing_slash
|
|
12
|
-
*/
|
|
13
|
-
function normalize_path(path, trailing_slash) {
|
|
14
|
-
if (path === '/' || trailing_slash === 'ignore') return path;
|
|
15
|
-
|
|
16
|
-
if (trailing_slash === 'never') {
|
|
17
|
-
return path.endsWith('/') ? path.slice(0, -1) : path;
|
|
18
|
-
} else if (trailing_slash === 'always' && /\/[^./]+$/.test(path)) {
|
|
19
|
-
return path + '/';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return path;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// We track the scroll position associated with each history entry in sessionStorage,
|
|
26
|
-
// rather than on history.state itself, because when navigation is driven by
|
|
27
|
-
// popstate it's too late to update the scroll position associated with the
|
|
28
|
-
// state we're navigating from
|
|
29
|
-
const SCROLL_KEY = 'sveltekit:scroll';
|
|
30
|
-
|
|
31
|
-
/** @typedef {{ x: number, y: number }} ScrollPosition */
|
|
32
|
-
/** @type {Record<number, ScrollPosition>} */
|
|
33
|
-
let scroll_positions = {};
|
|
34
|
-
try {
|
|
35
|
-
scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
|
|
36
|
-
} catch {
|
|
37
|
-
// do nothing
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** @param {number} index */
|
|
41
|
-
function update_scroll_positions(index) {
|
|
42
|
-
scroll_positions[index] = scroll_state();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function scroll_state() {
|
|
46
|
-
return {
|
|
47
|
-
x: pageXOffset,
|
|
48
|
-
y: pageYOffset
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {Event} event
|
|
54
|
-
* @returns {HTMLAnchorElement | SVGAElement | undefined}
|
|
55
|
-
*/
|
|
56
|
-
function find_anchor(event) {
|
|
57
|
-
const node = event
|
|
58
|
-
.composedPath()
|
|
59
|
-
.find((e) => e instanceof Node && e.nodeName.toUpperCase() === 'A'); // SVG <a> elements have a lowercase name
|
|
60
|
-
return /** @type {HTMLAnchorElement | SVGAElement | undefined} */ (node);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @param {HTMLAnchorElement | SVGAElement} node
|
|
65
|
-
* @returns {URL}
|
|
66
|
-
*/
|
|
67
|
-
function get_href(node) {
|
|
68
|
-
return node instanceof SVGAElement
|
|
69
|
-
? new URL(node.href.baseVal, document.baseURI)
|
|
70
|
-
: new URL(node.href);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
class Router {
|
|
74
|
-
/**
|
|
75
|
-
* @param {{
|
|
76
|
-
* base: string;
|
|
77
|
-
* routes: import('types').CSRRoute[];
|
|
78
|
-
* trailing_slash: import('types').TrailingSlash;
|
|
79
|
-
* renderer: import('./renderer').Renderer;
|
|
80
|
-
* }} opts
|
|
81
|
-
*/
|
|
82
|
-
constructor({ base, routes, trailing_slash, renderer }) {
|
|
83
|
-
this.base = base;
|
|
84
|
-
this.routes = routes;
|
|
85
|
-
this.trailing_slash = trailing_slash;
|
|
86
|
-
/** Keeps tracks of multiple navigations caused by redirects during rendering */
|
|
87
|
-
this.navigating = 0;
|
|
88
|
-
|
|
89
|
-
/** @type {import('./renderer').Renderer} */
|
|
90
|
-
this.renderer = renderer;
|
|
91
|
-
renderer.router = this;
|
|
92
|
-
|
|
93
|
-
this.enabled = true;
|
|
94
|
-
this.initialized = false;
|
|
95
|
-
|
|
96
|
-
// keeping track of the history index in order to prevent popstate navigation events if needed
|
|
97
|
-
this.current_history_index = history.state?.['sveltekit:index'] ?? 0;
|
|
98
|
-
|
|
99
|
-
if (this.current_history_index === 0) {
|
|
100
|
-
// create initial history entry, so we can return here
|
|
101
|
-
history.replaceState({ ...history.state, 'sveltekit:index': 0 }, '', location.href);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// if we reload the page, or Cmd-Shift-T back to it,
|
|
105
|
-
// recover scroll position
|
|
106
|
-
const scroll = scroll_positions[this.current_history_index];
|
|
107
|
-
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
108
|
-
|
|
109
|
-
this.hash_navigating = false;
|
|
110
|
-
|
|
111
|
-
this.callbacks = {
|
|
112
|
-
/** @type {Array<({ from, to, cancel }: { from: URL, to: URL | null, cancel: () => void }) => void>} */
|
|
113
|
-
before_navigate: [],
|
|
114
|
-
|
|
115
|
-
/** @type {Array<({ from, to }: { from: URL | null, to: URL }) => void>} */
|
|
116
|
-
after_navigate: []
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
init_listeners() {
|
|
121
|
-
history.scrollRestoration = 'manual';
|
|
122
|
-
|
|
123
|
-
// Adopted from Nuxt.js
|
|
124
|
-
// Reset scrollRestoration to auto when leaving page, allowing page reload
|
|
125
|
-
// and back-navigation from other pages to use the browser to restore the
|
|
126
|
-
// scrolling position.
|
|
127
|
-
addEventListener('beforeunload', (e) => {
|
|
128
|
-
let should_block = false;
|
|
129
|
-
|
|
130
|
-
const intent = {
|
|
131
|
-
from: this.renderer.current.url,
|
|
132
|
-
to: null,
|
|
133
|
-
cancel: () => (should_block = true)
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
this.callbacks.before_navigate.forEach((fn) => fn(intent));
|
|
137
|
-
|
|
138
|
-
if (should_block) {
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
e.returnValue = '';
|
|
141
|
-
} else {
|
|
142
|
-
history.scrollRestoration = 'auto';
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
addEventListener('visibilitychange', () => {
|
|
147
|
-
if (document.visibilityState === 'hidden') {
|
|
148
|
-
update_scroll_positions(this.current_history_index);
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
|
|
152
|
-
} catch {
|
|
153
|
-
// do nothing
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
/** @param {Event} event */
|
|
159
|
-
const trigger_prefetch = (event) => {
|
|
160
|
-
const a = find_anchor(event);
|
|
161
|
-
if (a && a.href && a.hasAttribute('sveltekit:prefetch')) {
|
|
162
|
-
this.prefetch(get_href(a));
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/** @type {NodeJS.Timeout} */
|
|
167
|
-
let mousemove_timeout;
|
|
168
|
-
|
|
169
|
-
/** @param {MouseEvent|TouchEvent} event */
|
|
170
|
-
const handle_mousemove = (event) => {
|
|
171
|
-
clearTimeout(mousemove_timeout);
|
|
172
|
-
mousemove_timeout = setTimeout(() => {
|
|
173
|
-
// event.composedPath(), which is used in find_anchor, will be empty if the event is read in a timeout
|
|
174
|
-
// add a layer of indirection to address that
|
|
175
|
-
event.target?.dispatchEvent(
|
|
176
|
-
new CustomEvent('sveltekit:trigger_prefetch', { bubbles: true })
|
|
177
|
-
);
|
|
178
|
-
}, 20);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
addEventListener('touchstart', trigger_prefetch);
|
|
182
|
-
addEventListener('mousemove', handle_mousemove);
|
|
183
|
-
addEventListener('sveltekit:trigger_prefetch', trigger_prefetch);
|
|
184
|
-
|
|
185
|
-
/** @param {MouseEvent} event */
|
|
186
|
-
addEventListener('click', (event) => {
|
|
187
|
-
if (!this.enabled) return;
|
|
188
|
-
|
|
189
|
-
// Adapted from https://github.com/visionmedia/page.js
|
|
190
|
-
// MIT license https://github.com/visionmedia/page.js#license
|
|
191
|
-
if (event.button || event.which !== 1) return;
|
|
192
|
-
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
193
|
-
if (event.defaultPrevented) return;
|
|
194
|
-
|
|
195
|
-
const a = find_anchor(event);
|
|
196
|
-
if (!a) return;
|
|
197
|
-
|
|
198
|
-
if (!a.href) return;
|
|
199
|
-
|
|
200
|
-
const is_svg_a_element = a instanceof SVGAElement;
|
|
201
|
-
const url = get_href(a);
|
|
202
|
-
const url_string = url.toString();
|
|
203
|
-
if (url_string === location.href) {
|
|
204
|
-
if (!location.hash) event.preventDefault();
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Ignore if url does not have origin (e.g. `mailto:`, `tel:`.)
|
|
209
|
-
// MEMO: Without this condition, firefox will open mailer twice.
|
|
210
|
-
// See: https://github.com/sveltejs/kit/issues/4045
|
|
211
|
-
if (!is_svg_a_element && url.origin === 'null') return;
|
|
212
|
-
|
|
213
|
-
// Ignore if tag has
|
|
214
|
-
// 1. 'download' attribute
|
|
215
|
-
// 2. 'rel' attribute includes external
|
|
216
|
-
const rel = (a.getAttribute('rel') || '').split(/\s+/);
|
|
217
|
-
|
|
218
|
-
if (a.hasAttribute('download') || (rel && rel.includes('external'))) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Ignore if <a> has a target
|
|
223
|
-
if (is_svg_a_element ? a.target.baseVal : a.target) return;
|
|
224
|
-
|
|
225
|
-
// Check if new url only differs by hash and use the browser default behavior in that case
|
|
226
|
-
// This will ensure the `hashchange` event is fired
|
|
227
|
-
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
228
|
-
const [base, hash] = url.href.split('#');
|
|
229
|
-
if (hash !== undefined && base === location.href.split('#')[0]) {
|
|
230
|
-
// set this flag to distinguish between navigations triggered by
|
|
231
|
-
// clicking a hash link and those triggered by popstate
|
|
232
|
-
this.hash_navigating = true;
|
|
233
|
-
|
|
234
|
-
update_scroll_positions(this.current_history_index);
|
|
235
|
-
this.renderer.update_page_store(new URL(url.href));
|
|
236
|
-
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
this._navigate({
|
|
241
|
-
url,
|
|
242
|
-
scroll: a.hasAttribute('sveltekit:noscroll') ? scroll_state() : null,
|
|
243
|
-
keepfocus: false,
|
|
244
|
-
chain: [],
|
|
245
|
-
details: {
|
|
246
|
-
state: {},
|
|
247
|
-
replaceState: false
|
|
248
|
-
},
|
|
249
|
-
accepted: () => event.preventDefault(),
|
|
250
|
-
blocked: () => event.preventDefault()
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
addEventListener('popstate', (event) => {
|
|
255
|
-
if (event.state && this.enabled) {
|
|
256
|
-
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
257
|
-
// with history.go, which means we end up back here, hence this check
|
|
258
|
-
if (event.state['sveltekit:index'] === this.current_history_index) return;
|
|
259
|
-
|
|
260
|
-
this._navigate({
|
|
261
|
-
url: new URL(location.href),
|
|
262
|
-
scroll: scroll_positions[event.state['sveltekit:index']],
|
|
263
|
-
keepfocus: false,
|
|
264
|
-
chain: [],
|
|
265
|
-
details: null,
|
|
266
|
-
accepted: () => {
|
|
267
|
-
this.current_history_index = event.state['sveltekit:index'];
|
|
268
|
-
},
|
|
269
|
-
blocked: () => {
|
|
270
|
-
const delta = this.current_history_index - event.state['sveltekit:index'];
|
|
271
|
-
history.go(delta);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
addEventListener('hashchange', () => {
|
|
278
|
-
// if the hashchange happened as a result of clicking on a link,
|
|
279
|
-
// we need to update history, otherwise we have to leave it alone
|
|
280
|
-
if (this.hash_navigating) {
|
|
281
|
-
this.hash_navigating = false;
|
|
282
|
-
history.replaceState(
|
|
283
|
-
{ ...history.state, 'sveltekit:index': ++this.current_history_index },
|
|
284
|
-
'',
|
|
285
|
-
location.href
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
this.initialized = true;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Returns true if `url` has the same origin and basepath as the app
|
|
295
|
-
* @param {URL} url
|
|
296
|
-
*/
|
|
297
|
-
owns(url) {
|
|
298
|
-
return url.origin === location.origin && url.pathname.startsWith(this.base);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* @param {URL} url
|
|
303
|
-
* @returns {import('./types').NavigationInfo | undefined}
|
|
304
|
-
*/
|
|
305
|
-
parse(url) {
|
|
306
|
-
if (this.owns(url)) {
|
|
307
|
-
const path = decodeURI(url.pathname.slice(this.base.length) || '/');
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
id: url.pathname + url.search,
|
|
311
|
-
routes: this.routes.filter(([pattern]) => pattern.test(path)),
|
|
312
|
-
url,
|
|
313
|
-
path
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* @typedef {Parameters<typeof import('$app/navigation').goto>} GotoParams
|
|
320
|
-
*
|
|
321
|
-
* @param {GotoParams[0]} href
|
|
322
|
-
* @param {GotoParams[1]} opts
|
|
323
|
-
* @param {string[]} chain
|
|
324
|
-
*/
|
|
325
|
-
async goto(
|
|
326
|
-
href,
|
|
327
|
-
{ noscroll = false, replaceState = false, keepfocus = false, state = {} } = {},
|
|
328
|
-
chain
|
|
329
|
-
) {
|
|
330
|
-
const url = new URL(href, get_base_uri(document));
|
|
331
|
-
|
|
332
|
-
if (this.enabled) {
|
|
333
|
-
return this._navigate({
|
|
334
|
-
url,
|
|
335
|
-
scroll: noscroll ? scroll_state() : null,
|
|
336
|
-
keepfocus,
|
|
337
|
-
chain,
|
|
338
|
-
details: {
|
|
339
|
-
state,
|
|
340
|
-
replaceState
|
|
341
|
-
},
|
|
342
|
-
accepted: () => {},
|
|
343
|
-
blocked: () => {}
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
location.href = url.href;
|
|
348
|
-
return new Promise(() => {
|
|
349
|
-
/* never resolves */
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
enable() {
|
|
354
|
-
this.enabled = true;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
disable() {
|
|
358
|
-
this.enabled = false;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* @param {URL} url
|
|
363
|
-
* @returns {Promise<import('./types').NavigationResult | undefined>}
|
|
364
|
-
*/
|
|
365
|
-
async prefetch(url) {
|
|
366
|
-
const info = this.parse(url);
|
|
367
|
-
|
|
368
|
-
if (!info) {
|
|
369
|
-
throw new Error('Attempted to prefetch a URL that does not belong to this app');
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return this.renderer.load(info);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/** @param {({ from, to }: { from: URL | null, to: URL }) => void} fn */
|
|
376
|
-
after_navigate(fn) {
|
|
377
|
-
onMount(() => {
|
|
378
|
-
this.callbacks.after_navigate.push(fn);
|
|
379
|
-
|
|
380
|
-
return () => {
|
|
381
|
-
const i = this.callbacks.after_navigate.indexOf(fn);
|
|
382
|
-
this.callbacks.after_navigate.splice(i, 1);
|
|
383
|
-
};
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* @param {({ from, to, cancel }: { from: URL, to: URL | null, cancel: () => void }) => void} fn
|
|
389
|
-
*/
|
|
390
|
-
before_navigate(fn) {
|
|
391
|
-
onMount(() => {
|
|
392
|
-
this.callbacks.before_navigate.push(fn);
|
|
393
|
-
|
|
394
|
-
return () => {
|
|
395
|
-
const i = this.callbacks.before_navigate.indexOf(fn);
|
|
396
|
-
this.callbacks.before_navigate.splice(i, 1);
|
|
397
|
-
};
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* @param {{
|
|
403
|
-
* url: URL;
|
|
404
|
-
* scroll: { x: number, y: number } | null;
|
|
405
|
-
* keepfocus: boolean;
|
|
406
|
-
* chain: string[];
|
|
407
|
-
* details: {
|
|
408
|
-
* replaceState: boolean;
|
|
409
|
-
* state: any;
|
|
410
|
-
* } | null;
|
|
411
|
-
* accepted: () => void;
|
|
412
|
-
* blocked: () => void;
|
|
413
|
-
* }} opts
|
|
414
|
-
*/
|
|
415
|
-
async _navigate({ url, scroll, keepfocus, chain, details, accepted, blocked }) {
|
|
416
|
-
const from = this.renderer.current.url;
|
|
417
|
-
let should_block = false;
|
|
418
|
-
|
|
419
|
-
const intent = {
|
|
420
|
-
from,
|
|
421
|
-
to: url,
|
|
422
|
-
cancel: () => (should_block = true)
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
this.callbacks.before_navigate.forEach((fn) => fn(intent));
|
|
426
|
-
|
|
427
|
-
if (should_block) {
|
|
428
|
-
blocked();
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const info = this.parse(url);
|
|
433
|
-
if (!info) {
|
|
434
|
-
location.href = url.href;
|
|
435
|
-
return new Promise(() => {
|
|
436
|
-
// never resolves
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
update_scroll_positions(this.current_history_index);
|
|
441
|
-
|
|
442
|
-
accepted();
|
|
443
|
-
|
|
444
|
-
this.navigating++;
|
|
445
|
-
|
|
446
|
-
const pathname = normalize_path(url.pathname, this.trailing_slash);
|
|
447
|
-
|
|
448
|
-
info.url = new URL(url.origin + pathname + url.search + url.hash);
|
|
449
|
-
|
|
450
|
-
const token = (this.navigating_token = {});
|
|
451
|
-
|
|
452
|
-
await this.renderer.handle_navigation(info, chain, false, {
|
|
453
|
-
scroll,
|
|
454
|
-
keepfocus
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
this.navigating--;
|
|
458
|
-
|
|
459
|
-
// navigation was aborted
|
|
460
|
-
if (this.navigating_token !== token) return;
|
|
461
|
-
if (!this.navigating) {
|
|
462
|
-
const navigation = { from, to: url };
|
|
463
|
-
this.callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (details) {
|
|
467
|
-
const change = details.replaceState ? 0 : 1;
|
|
468
|
-
details.state['sveltekit:index'] = this.current_history_index += change;
|
|
469
|
-
history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', info.url);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
8
|
/**
|
|
475
9
|
* @param {unknown} err
|
|
476
10
|
* @return {Error}
|
|
@@ -482,23 +16,6 @@ function coalesce_to_error(err) {
|
|
|
482
16
|
: new Error(JSON.stringify(err));
|
|
483
17
|
}
|
|
484
18
|
|
|
485
|
-
/**
|
|
486
|
-
* Hash using djb2
|
|
487
|
-
* @param {import('types').StrictBody} value
|
|
488
|
-
*/
|
|
489
|
-
function hash(value) {
|
|
490
|
-
let hash = 5381;
|
|
491
|
-
let i = value.length;
|
|
492
|
-
|
|
493
|
-
if (typeof value === 'string') {
|
|
494
|
-
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
|
|
495
|
-
} else {
|
|
496
|
-
while (i) hash = (hash * 33) ^ value[--i];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
return (hash >>> 0).toString(36);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
19
|
/**
|
|
503
20
|
* @param {import('types').LoadOutput} loaded
|
|
504
21
|
* @returns {import('types').NormalizedLoadOutput}
|
|
@@ -565,9 +82,71 @@ function normalize(loaded) {
|
|
|
565
82
|
}
|
|
566
83
|
|
|
567
84
|
/**
|
|
568
|
-
* @
|
|
569
|
-
* @
|
|
85
|
+
* @param {string} path
|
|
86
|
+
* @param {import('types').TrailingSlash} trailing_slash
|
|
570
87
|
*/
|
|
88
|
+
function normalize_path(path, trailing_slash) {
|
|
89
|
+
if (path === '/' || trailing_slash === 'ignore') return path;
|
|
90
|
+
|
|
91
|
+
if (trailing_slash === 'never') {
|
|
92
|
+
return path.endsWith('/') ? path.slice(0, -1) : path;
|
|
93
|
+
} else if (trailing_slash === 'always' && /\/[^./]+$/.test(path)) {
|
|
94
|
+
return path + '/';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return path;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hash using djb2
|
|
102
|
+
* @param {import('types').StrictBody} value
|
|
103
|
+
*/
|
|
104
|
+
function hash(value) {
|
|
105
|
+
let hash = 5381;
|
|
106
|
+
let i = value.length;
|
|
107
|
+
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
|
|
110
|
+
} else {
|
|
111
|
+
while (i) hash = (hash * 33) ^ value[--i];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (hash >>> 0).toString(36);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @param {HTMLDocument} doc */
|
|
118
|
+
function get_base_uri(doc) {
|
|
119
|
+
let baseURI = doc.baseURI;
|
|
120
|
+
|
|
121
|
+
if (!baseURI) {
|
|
122
|
+
const baseTags = doc.getElementsByTagName('base');
|
|
123
|
+
baseURI = baseTags.length ? baseTags[0].href : doc.URL;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return baseURI;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function scroll_state() {
|
|
130
|
+
return {
|
|
131
|
+
x: pageXOffset,
|
|
132
|
+
y: pageYOffset
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** @param {Event} event */
|
|
137
|
+
function find_anchor(event) {
|
|
138
|
+
const node = event
|
|
139
|
+
.composedPath()
|
|
140
|
+
.find((e) => e instanceof Node && e.nodeName.toUpperCase() === 'A'); // SVG <a> elements have a lowercase name
|
|
141
|
+
return /** @type {HTMLAnchorElement | SVGAElement | undefined} */ (node);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** @param {HTMLAnchorElement | SVGAElement} node */
|
|
145
|
+
function get_href(node) {
|
|
146
|
+
return node instanceof SVGAElement
|
|
147
|
+
? new URL(node.href.baseVal, document.baseURI)
|
|
148
|
+
: new URL(node.href);
|
|
149
|
+
}
|
|
571
150
|
|
|
572
151
|
/** @param {any} value */
|
|
573
152
|
function notifiable_store(value) {
|
|
@@ -671,264 +250,240 @@ function initial_fetch(resource, opts) {
|
|
|
671
250
|
return fetch(resource, opts);
|
|
672
251
|
}
|
|
673
252
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
* @param {{
|
|
677
|
-
* Root: CSRComponent;
|
|
678
|
-
* fallback: [CSRComponent, CSRComponent];
|
|
679
|
-
* target: Node;
|
|
680
|
-
* session: any;
|
|
681
|
-
* }} opts
|
|
682
|
-
*/
|
|
683
|
-
constructor({ Root, fallback, target, session }) {
|
|
684
|
-
this.Root = Root;
|
|
685
|
-
this.fallback = fallback;
|
|
686
|
-
|
|
687
|
-
/** @type {import('./router').Router | undefined} */
|
|
688
|
-
this.router;
|
|
689
|
-
|
|
690
|
-
this.target = target;
|
|
691
|
-
|
|
692
|
-
this.started = false;
|
|
693
|
-
|
|
694
|
-
this.session_id = 1;
|
|
695
|
-
this.invalid = new Set();
|
|
696
|
-
this.invalidating = null;
|
|
697
|
-
this.autoscroll = true;
|
|
698
|
-
this.updating = false;
|
|
699
|
-
|
|
700
|
-
/** @type {import('./types').NavigationState} */
|
|
701
|
-
this.current = {
|
|
702
|
-
// @ts-ignore - we need the initial value to be null
|
|
703
|
-
url: null,
|
|
704
|
-
session_id: 0,
|
|
705
|
-
branch: []
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
/** @type {Map<string, import('./types').NavigationResult>} */
|
|
709
|
-
this.cache = new Map();
|
|
253
|
+
const SCROLL_KEY = 'sveltekit:scroll';
|
|
254
|
+
const INDEX_KEY = 'sveltekit:index';
|
|
710
255
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
};
|
|
256
|
+
// We track the scroll position associated with each history entry in sessionStorage,
|
|
257
|
+
// rather than on history.state itself, because when navigation is driven by
|
|
258
|
+
// popstate it's too late to update the scroll position associated with the
|
|
259
|
+
// state we're navigating from
|
|
716
260
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
261
|
+
/** @typedef {{ x: number, y: number }} ScrollPosition */
|
|
262
|
+
/** @type {Record<number, ScrollPosition>} */
|
|
263
|
+
let scroll_positions = {};
|
|
264
|
+
try {
|
|
265
|
+
scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
|
|
266
|
+
} catch {
|
|
267
|
+
// do nothing
|
|
268
|
+
}
|
|
724
269
|
|
|
725
|
-
|
|
270
|
+
/** @param {number} index */
|
|
271
|
+
function update_scroll_positions(index) {
|
|
272
|
+
scroll_positions[index] = scroll_state();
|
|
273
|
+
}
|
|
726
274
|
|
|
727
|
-
|
|
275
|
+
/**
|
|
276
|
+
* @param {{
|
|
277
|
+
* target: Element;
|
|
278
|
+
* session: App.Session;
|
|
279
|
+
* base: string;
|
|
280
|
+
* trailing_slash: import('types').TrailingSlash;
|
|
281
|
+
* }} opts
|
|
282
|
+
* @returns {import('./types').Client}
|
|
283
|
+
*/
|
|
284
|
+
function create_client({ target, session, base, trailing_slash }) {
|
|
285
|
+
/** @type {Map<string, import('./types').NavigationResult>} */
|
|
286
|
+
const cache = new Map();
|
|
287
|
+
|
|
288
|
+
/** @type {Set<string>} */
|
|
289
|
+
const invalidated = new Set();
|
|
290
|
+
|
|
291
|
+
const stores = {
|
|
292
|
+
url: notifiable_store({}),
|
|
293
|
+
page: notifiable_store({}),
|
|
294
|
+
navigating: writable(/** @type {import('types').Navigation | null} */ (null)),
|
|
295
|
+
session: writable(session),
|
|
296
|
+
updated: create_updated_store()
|
|
297
|
+
};
|
|
728
298
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
299
|
+
/** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
|
|
300
|
+
const load_cache = {
|
|
301
|
+
id: null,
|
|
302
|
+
promise: null
|
|
303
|
+
};
|
|
732
304
|
|
|
733
|
-
|
|
734
|
-
|
|
305
|
+
const callbacks = {
|
|
306
|
+
/** @type {Array<(opts: { from: URL, to: URL | null, cancel: () => void }) => void>} */
|
|
307
|
+
before_navigate: [],
|
|
735
308
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
ready = true;
|
|
740
|
-
}
|
|
309
|
+
/** @type {Array<(opts: { from: URL | null, to: URL }) => void>} */
|
|
310
|
+
after_navigate: []
|
|
311
|
+
};
|
|
741
312
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
313
|
+
/** @type {import('./types').NavigationState} */
|
|
314
|
+
let current = {
|
|
315
|
+
// @ts-ignore - we need the initial value to be null
|
|
316
|
+
url: null,
|
|
317
|
+
session_id: 0,
|
|
318
|
+
branch: []
|
|
319
|
+
};
|
|
746
320
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
321
|
+
let started = false;
|
|
322
|
+
let autoscroll = true;
|
|
323
|
+
let updating = false;
|
|
324
|
+
let session_id = 1;
|
|
751
325
|
|
|
752
|
-
/**
|
|
753
|
-
|
|
754
|
-
* status: number;
|
|
755
|
-
* error: Error;
|
|
756
|
-
* nodes: Array<Promise<CSRComponent>>;
|
|
757
|
-
* params: Record<string, string>;
|
|
758
|
-
* }} selected
|
|
759
|
-
*/
|
|
760
|
-
async start({ status, error, nodes, params }) {
|
|
761
|
-
const url = new URL(location.href);
|
|
326
|
+
/** @type {Promise<void> | null} */
|
|
327
|
+
let invalidating = null;
|
|
762
328
|
|
|
763
|
-
|
|
764
|
-
|
|
329
|
+
/** @type {import('svelte').SvelteComponent} */
|
|
330
|
+
let root;
|
|
765
331
|
|
|
766
|
-
|
|
767
|
-
|
|
332
|
+
/** @type {App.Session} */
|
|
333
|
+
let $session;
|
|
768
334
|
|
|
769
|
-
|
|
770
|
-
|
|
335
|
+
let ready = false;
|
|
336
|
+
stores.session.subscribe(async (value) => {
|
|
337
|
+
$session = value;
|
|
771
338
|
|
|
772
|
-
|
|
339
|
+
if (!ready) return;
|
|
340
|
+
session_id += 1;
|
|
773
341
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
342
|
+
const intent = get_navigation_intent(new URL(location.href));
|
|
343
|
+
update(intent, [], true);
|
|
344
|
+
});
|
|
345
|
+
ready = true;
|
|
777
346
|
|
|
778
|
-
|
|
347
|
+
/** Keeps tracks of multiple navigations caused by redirects during rendering */
|
|
348
|
+
let navigating = 0;
|
|
779
349
|
|
|
780
|
-
|
|
781
|
-
const serialized = document.querySelector('script[sveltekit\\:data-type="props"]');
|
|
782
|
-
if (serialized) {
|
|
783
|
-
props = JSON.parse(/** @type {string} */ (serialized.textContent));
|
|
784
|
-
}
|
|
785
|
-
}
|
|
350
|
+
let router_enabled = true;
|
|
786
351
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
url,
|
|
790
|
-
params,
|
|
791
|
-
stuff,
|
|
792
|
-
status: is_leaf ? status : undefined,
|
|
793
|
-
error: is_leaf ? error : undefined,
|
|
794
|
-
props
|
|
795
|
-
});
|
|
352
|
+
// keeping track of the history index in order to prevent popstate navigation events if needed
|
|
353
|
+
let current_history_index = history.state?.[INDEX_KEY] ?? 0;
|
|
796
354
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
355
|
+
if (current_history_index === 0) {
|
|
356
|
+
// create initial history entry, so we can return here
|
|
357
|
+
history.replaceState({ ...history.state, [INDEX_KEY]: 0 }, '', location.href);
|
|
358
|
+
}
|
|
801
359
|
|
|
802
|
-
|
|
360
|
+
// if we reload the page, or Cmd-Shift-T back to it,
|
|
361
|
+
// recover scroll position
|
|
362
|
+
const scroll = scroll_positions[current_history_index];
|
|
363
|
+
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
803
364
|
|
|
804
|
-
|
|
805
|
-
if (node.loaded.error) {
|
|
806
|
-
if (error) throw node.loaded.error;
|
|
807
|
-
error_args = {
|
|
808
|
-
status: node.loaded.status,
|
|
809
|
-
error: node.loaded.error,
|
|
810
|
-
url
|
|
811
|
-
};
|
|
812
|
-
} else if (node.loaded.stuff) {
|
|
813
|
-
stuff = {
|
|
814
|
-
...stuff,
|
|
815
|
-
...node.loaded.stuff
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
}
|
|
365
|
+
let hash_navigating = false;
|
|
820
366
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
: await this._get_navigation_result_from_branch({
|
|
824
|
-
url,
|
|
825
|
-
params,
|
|
826
|
-
stuff,
|
|
827
|
-
branch,
|
|
828
|
-
status,
|
|
829
|
-
error
|
|
830
|
-
});
|
|
831
|
-
} catch (e) {
|
|
832
|
-
if (error) throw e;
|
|
833
|
-
|
|
834
|
-
result = await this._load_error({
|
|
835
|
-
status: 500,
|
|
836
|
-
error: coalesce_to_error(e),
|
|
837
|
-
url
|
|
838
|
-
});
|
|
839
|
-
}
|
|
367
|
+
/** @type {import('types').Page} */
|
|
368
|
+
let page;
|
|
840
369
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
// a redirect but only in the browser
|
|
844
|
-
location.href = new URL(result.redirect, location.href).href;
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
370
|
+
/** @type {{}} */
|
|
371
|
+
let token;
|
|
847
372
|
|
|
848
|
-
|
|
849
|
-
|
|
373
|
+
/** @type {{}} */
|
|
374
|
+
let navigating_token;
|
|
850
375
|
|
|
851
376
|
/**
|
|
852
|
-
* @param {
|
|
853
|
-
* @param {
|
|
854
|
-
* @param {
|
|
855
|
-
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts]
|
|
377
|
+
* @param {string} href
|
|
378
|
+
* @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any }} opts
|
|
379
|
+
* @param {string[]} redirect_chain
|
|
856
380
|
*/
|
|
857
|
-
async
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
381
|
+
async function goto(
|
|
382
|
+
href,
|
|
383
|
+
{ noscroll = false, replaceState = false, keepfocus = false, state = {} },
|
|
384
|
+
redirect_chain
|
|
385
|
+
) {
|
|
386
|
+
const url = new URL(href, get_base_uri(document));
|
|
387
|
+
|
|
388
|
+
if (router_enabled) {
|
|
389
|
+
return navigate({
|
|
390
|
+
url,
|
|
391
|
+
scroll: noscroll ? scroll_state() : null,
|
|
392
|
+
keepfocus,
|
|
393
|
+
redirect_chain,
|
|
394
|
+
details: {
|
|
395
|
+
state,
|
|
396
|
+
replaceState
|
|
397
|
+
},
|
|
398
|
+
accepted: () => {},
|
|
399
|
+
blocked: () => {}
|
|
862
400
|
});
|
|
863
401
|
}
|
|
864
402
|
|
|
865
|
-
await
|
|
403
|
+
await native_navigation(url);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** @param {URL} url */
|
|
407
|
+
async function prefetch(url) {
|
|
408
|
+
if (!owns(url)) {
|
|
409
|
+
throw new Error('Attempted to prefetch a URL that does not belong to this app');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const intent = get_navigation_intent(url);
|
|
413
|
+
|
|
414
|
+
load_cache.promise = get_navigation_result(intent, false);
|
|
415
|
+
load_cache.id = intent.id;
|
|
416
|
+
|
|
417
|
+
return load_cache.promise;
|
|
866
418
|
}
|
|
867
419
|
|
|
868
420
|
/**
|
|
869
|
-
* @param {import('./types').
|
|
870
|
-
* @param {string[]}
|
|
421
|
+
* @param {import('./types').NavigationIntent} intent
|
|
422
|
+
* @param {string[]} redirect_chain
|
|
871
423
|
* @param {boolean} no_cache
|
|
872
424
|
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts]
|
|
873
425
|
*/
|
|
874
|
-
async update(
|
|
875
|
-
const
|
|
876
|
-
let navigation_result = await
|
|
877
|
-
|
|
878
|
-
if (!navigation_result &&
|
|
879
|
-
|
|
426
|
+
async function update(intent, redirect_chain, no_cache, opts) {
|
|
427
|
+
const current_token = (token = {});
|
|
428
|
+
let navigation_result = await get_navigation_result(intent, no_cache);
|
|
429
|
+
|
|
430
|
+
if (!navigation_result && intent.url.pathname === location.pathname) {
|
|
431
|
+
// this could happen in SPA fallback mode if the user navigated to
|
|
432
|
+
// `/non-existent-page`. if we fall back to reloading the page, it
|
|
433
|
+
// will create an infinite loop. so whereas we normally handle
|
|
434
|
+
// unknown routes by going to the server, in this special case
|
|
435
|
+
// we render a client-side error page instead
|
|
436
|
+
navigation_result = await load_root_error_page({
|
|
880
437
|
status: 404,
|
|
881
|
-
error: new Error(`Not found: ${
|
|
882
|
-
url:
|
|
438
|
+
error: new Error(`Not found: ${intent.url.pathname}`),
|
|
439
|
+
url: intent.url
|
|
883
440
|
});
|
|
884
441
|
}
|
|
885
442
|
|
|
886
443
|
if (!navigation_result) {
|
|
887
|
-
|
|
888
|
-
return;
|
|
444
|
+
await native_navigation(intent.url);
|
|
445
|
+
return; // unnecessary, but TypeScript prefers it this way
|
|
889
446
|
}
|
|
890
447
|
|
|
891
448
|
// abort if user navigated during update
|
|
892
|
-
if (token !==
|
|
449
|
+
if (token !== current_token) return;
|
|
893
450
|
|
|
894
|
-
|
|
451
|
+
invalidated.clear();
|
|
895
452
|
|
|
896
453
|
if (navigation_result.redirect) {
|
|
897
|
-
if (
|
|
898
|
-
navigation_result = await
|
|
454
|
+
if (redirect_chain.length > 10 || redirect_chain.includes(intent.url.pathname)) {
|
|
455
|
+
navigation_result = await load_root_error_page({
|
|
899
456
|
status: 500,
|
|
900
457
|
error: new Error('Redirect loop'),
|
|
901
|
-
url:
|
|
458
|
+
url: intent.url
|
|
902
459
|
});
|
|
903
460
|
} else {
|
|
904
|
-
if (
|
|
905
|
-
|
|
906
|
-
...
|
|
907
|
-
|
|
461
|
+
if (router_enabled) {
|
|
462
|
+
goto(new URL(navigation_result.redirect, intent.url).href, {}, [
|
|
463
|
+
...redirect_chain,
|
|
464
|
+
intent.url.pathname
|
|
908
465
|
]);
|
|
909
466
|
} else {
|
|
910
|
-
|
|
467
|
+
await native_navigation(new URL(navigation_result.redirect, location.href));
|
|
911
468
|
}
|
|
912
469
|
|
|
913
470
|
return;
|
|
914
471
|
}
|
|
915
472
|
} else if (navigation_result.props?.page?.status >= 400) {
|
|
916
|
-
const updated = await
|
|
473
|
+
const updated = await stores.updated.check();
|
|
917
474
|
if (updated) {
|
|
918
|
-
|
|
919
|
-
return;
|
|
475
|
+
await native_navigation(intent.url);
|
|
920
476
|
}
|
|
921
477
|
}
|
|
922
478
|
|
|
923
|
-
|
|
479
|
+
updating = true;
|
|
924
480
|
|
|
925
|
-
if (
|
|
926
|
-
|
|
481
|
+
if (started) {
|
|
482
|
+
current = navigation_result.state;
|
|
927
483
|
|
|
928
|
-
|
|
929
|
-
this.stores.navigating.set(null);
|
|
484
|
+
root.$set(navigation_result.props);
|
|
930
485
|
} else {
|
|
931
|
-
|
|
486
|
+
initialize(navigation_result);
|
|
932
487
|
}
|
|
933
488
|
|
|
934
489
|
// opts must be passed if we're navigating
|
|
@@ -937,18 +492,18 @@ class Renderer {
|
|
|
937
492
|
|
|
938
493
|
if (!keepfocus) {
|
|
939
494
|
// Reset page selection and focus
|
|
940
|
-
// We try to
|
|
941
|
-
//
|
|
942
|
-
// immediately cycle from the end of the page
|
|
495
|
+
// We try to mimic browsers' behaviour as closely as possible by targeting the
|
|
496
|
+
// first scrollable region, but unfortunately it's not a perfect match — e.g.
|
|
497
|
+
// shift-tabbing won't immediately cycle up from the end of the page on Chromium
|
|
943
498
|
// See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
|
|
944
|
-
const root = document.
|
|
499
|
+
const root = document.body;
|
|
945
500
|
const tabindex = root.getAttribute('tabindex');
|
|
946
501
|
|
|
947
502
|
getSelection()?.removeAllRanges();
|
|
948
503
|
root.tabIndex = -1;
|
|
949
504
|
root.focus();
|
|
950
505
|
|
|
951
|
-
// restore `tabindex` as to prevent
|
|
506
|
+
// restore `tabindex` as to prevent `root` from stealing input from elements
|
|
952
507
|
if (tabindex !== null) {
|
|
953
508
|
root.setAttribute('tabindex', tabindex);
|
|
954
509
|
} else {
|
|
@@ -959,8 +514,8 @@ class Renderer {
|
|
|
959
514
|
// need to render the DOM before we can scroll to the rendered elements
|
|
960
515
|
await tick();
|
|
961
516
|
|
|
962
|
-
if (
|
|
963
|
-
const deep_linked =
|
|
517
|
+
if (autoscroll) {
|
|
518
|
+
const deep_linked = intent.url.hash && document.getElementById(intent.url.hash.slice(1));
|
|
964
519
|
if (scroll) {
|
|
965
520
|
scrollTo(scroll.x, scroll.y);
|
|
966
521
|
} else if (deep_linked) {
|
|
@@ -977,102 +532,59 @@ class Renderer {
|
|
|
977
532
|
await tick();
|
|
978
533
|
}
|
|
979
534
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
535
|
+
load_cache.promise = null;
|
|
536
|
+
load_cache.id = null;
|
|
537
|
+
autoscroll = true;
|
|
538
|
+
updating = false;
|
|
984
539
|
|
|
985
540
|
if (navigation_result.props.page) {
|
|
986
|
-
|
|
541
|
+
page = navigation_result.props.page;
|
|
987
542
|
}
|
|
988
543
|
|
|
989
|
-
if (!this.router) return;
|
|
990
|
-
|
|
991
544
|
const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
|
|
992
|
-
|
|
993
|
-
this.router.disable();
|
|
994
|
-
} else {
|
|
995
|
-
this.router.enable();
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* @param {import('./types').NavigationInfo} info
|
|
1001
|
-
* @returns {Promise<import('./types').NavigationResult | undefined>}
|
|
1002
|
-
*/
|
|
1003
|
-
load(info) {
|
|
1004
|
-
this.loading.promise = this._get_navigation_result(info, false);
|
|
1005
|
-
this.loading.id = info.id;
|
|
1006
|
-
|
|
1007
|
-
return this.loading.promise;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
/** @param {string} href */
|
|
1011
|
-
invalidate(href) {
|
|
1012
|
-
this.invalid.add(href);
|
|
1013
|
-
|
|
1014
|
-
if (!this.invalidating) {
|
|
1015
|
-
this.invalidating = Promise.resolve().then(async () => {
|
|
1016
|
-
const info = this.router && this.router.parse(new URL(location.href));
|
|
1017
|
-
if (info) await this.update(info, [], true);
|
|
1018
|
-
|
|
1019
|
-
this.invalidating = null;
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
return this.invalidating;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/** @param {URL} url */
|
|
1027
|
-
update_page_store(url) {
|
|
1028
|
-
this.stores.page.set({ ...this.page, url });
|
|
1029
|
-
this.stores.page.notify();
|
|
545
|
+
router_enabled = leaf_node?.module.router !== false;
|
|
1030
546
|
}
|
|
1031
547
|
|
|
1032
548
|
/** @param {import('./types').NavigationResult} result */
|
|
1033
|
-
|
|
1034
|
-
|
|
549
|
+
function initialize(result) {
|
|
550
|
+
current = result.state;
|
|
1035
551
|
|
|
1036
552
|
const style = document.querySelector('style[data-svelte]');
|
|
1037
553
|
if (style) style.remove();
|
|
1038
554
|
|
|
1039
|
-
|
|
555
|
+
page = result.props.page;
|
|
1040
556
|
|
|
1041
|
-
|
|
1042
|
-
target
|
|
1043
|
-
props: {
|
|
1044
|
-
stores: this.stores,
|
|
1045
|
-
...result.props
|
|
1046
|
-
},
|
|
557
|
+
root = new Root({
|
|
558
|
+
target,
|
|
559
|
+
props: { ...result.props, stores },
|
|
1047
560
|
hydrate: true
|
|
1048
561
|
});
|
|
1049
562
|
|
|
1050
|
-
|
|
563
|
+
started = true;
|
|
1051
564
|
|
|
1052
|
-
if (
|
|
565
|
+
if (router_enabled) {
|
|
1053
566
|
const navigation = { from: null, to: new URL(location.href) };
|
|
1054
|
-
|
|
567
|
+
callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
1055
568
|
}
|
|
1056
569
|
}
|
|
1057
570
|
|
|
1058
571
|
/**
|
|
1059
|
-
* @param {import('./types').
|
|
572
|
+
* @param {import('./types').NavigationIntent} intent
|
|
1060
573
|
* @param {boolean} no_cache
|
|
1061
|
-
* @returns {Promise<import('./types').NavigationResult | undefined>}
|
|
1062
574
|
*/
|
|
1063
|
-
async
|
|
1064
|
-
if (
|
|
1065
|
-
return
|
|
575
|
+
async function get_navigation_result(intent, no_cache) {
|
|
576
|
+
if (load_cache.id === intent.id && load_cache.promise) {
|
|
577
|
+
return load_cache.promise;
|
|
1066
578
|
}
|
|
1067
579
|
|
|
1068
|
-
for (let i = 0; i <
|
|
1069
|
-
const route =
|
|
580
|
+
for (let i = 0; i < intent.routes.length; i += 1) {
|
|
581
|
+
const route = intent.routes[i];
|
|
1070
582
|
|
|
1071
583
|
// load code for subsequent routes immediately, if they are as
|
|
1072
584
|
// likely to match the current path/query as the current one
|
|
1073
585
|
let j = i + 1;
|
|
1074
|
-
while (j <
|
|
1075
|
-
const next =
|
|
586
|
+
while (j < intent.routes.length) {
|
|
587
|
+
const next = intent.routes[j];
|
|
1076
588
|
if (next[0].toString() === route[0].toString()) {
|
|
1077
589
|
next[1].forEach((loader) => loader());
|
|
1078
590
|
j += 1;
|
|
@@ -1081,13 +593,7 @@ class Renderer {
|
|
|
1081
593
|
}
|
|
1082
594
|
}
|
|
1083
595
|
|
|
1084
|
-
const result = await
|
|
1085
|
-
{
|
|
1086
|
-
route,
|
|
1087
|
-
info
|
|
1088
|
-
},
|
|
1089
|
-
no_cache
|
|
1090
|
-
);
|
|
596
|
+
const result = await load_route(route, intent, no_cache);
|
|
1091
597
|
if (result) return result;
|
|
1092
598
|
}
|
|
1093
599
|
}
|
|
@@ -1103,18 +609,18 @@ class Renderer {
|
|
|
1103
609
|
* error?: Error;
|
|
1104
610
|
* }} opts
|
|
1105
611
|
*/
|
|
1106
|
-
async
|
|
612
|
+
async function get_navigation_result_from_branch({ url, params, stuff, branch, status, error }) {
|
|
1107
613
|
const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
|
|
1108
|
-
const redirect = filtered.find((f) => f.loaded
|
|
614
|
+
const redirect = filtered.find((f) => f.loaded?.redirect);
|
|
1109
615
|
|
|
1110
616
|
/** @type {import('./types').NavigationResult} */
|
|
1111
617
|
const result = {
|
|
1112
|
-
redirect: redirect
|
|
618
|
+
redirect: redirect?.loaded?.redirect,
|
|
1113
619
|
state: {
|
|
1114
620
|
url,
|
|
1115
621
|
params,
|
|
1116
622
|
branch,
|
|
1117
|
-
session_id
|
|
623
|
+
session_id
|
|
1118
624
|
},
|
|
1119
625
|
props: {
|
|
1120
626
|
components: filtered.map((node) => node.module.default)
|
|
@@ -1126,7 +632,7 @@ class Renderer {
|
|
|
1126
632
|
result.props[`props_${i}`] = loaded ? await loaded.props : null;
|
|
1127
633
|
}
|
|
1128
634
|
|
|
1129
|
-
if (!
|
|
635
|
+
if (!current.url || url.href !== current.url.href) {
|
|
1130
636
|
result.props.page = { url, params, status, error, stuff };
|
|
1131
637
|
|
|
1132
638
|
// TODO remove this for 1.0
|
|
@@ -1155,8 +661,8 @@ class Renderer {
|
|
|
1155
661
|
let ready = false;
|
|
1156
662
|
|
|
1157
663
|
const clear = () => {
|
|
1158
|
-
if (
|
|
1159
|
-
|
|
664
|
+
if (cache.get(key) === result) {
|
|
665
|
+
cache.delete(key);
|
|
1160
666
|
}
|
|
1161
667
|
|
|
1162
668
|
unsubscribe();
|
|
@@ -1165,13 +671,13 @@ class Renderer {
|
|
|
1165
671
|
|
|
1166
672
|
const timeout = setTimeout(clear, maxage * 1000);
|
|
1167
673
|
|
|
1168
|
-
const unsubscribe =
|
|
674
|
+
const unsubscribe = stores.session.subscribe(() => {
|
|
1169
675
|
if (ready) clear();
|
|
1170
676
|
});
|
|
1171
677
|
|
|
1172
678
|
ready = true;
|
|
1173
679
|
|
|
1174
|
-
|
|
680
|
+
cache.set(key, result);
|
|
1175
681
|
}
|
|
1176
682
|
|
|
1177
683
|
return result;
|
|
@@ -1181,15 +687,14 @@ class Renderer {
|
|
|
1181
687
|
* @param {{
|
|
1182
688
|
* status?: number;
|
|
1183
689
|
* error?: Error;
|
|
1184
|
-
* module: CSRComponent;
|
|
690
|
+
* module: import('types').CSRComponent;
|
|
1185
691
|
* url: URL;
|
|
1186
692
|
* params: Record<string, string>;
|
|
1187
693
|
* stuff: Record<string, any>;
|
|
1188
694
|
* props?: Record<string, any>;
|
|
1189
695
|
* }} options
|
|
1190
|
-
* @returns
|
|
1191
696
|
*/
|
|
1192
|
-
async
|
|
697
|
+
async function load_node({ status, error, module, url, params, stuff, props }) {
|
|
1193
698
|
/** @type {import('./types').BranchNode} */
|
|
1194
699
|
const node = {
|
|
1195
700
|
module,
|
|
@@ -1221,11 +726,9 @@ class Renderer {
|
|
|
1221
726
|
});
|
|
1222
727
|
}
|
|
1223
728
|
|
|
1224
|
-
const session =
|
|
729
|
+
const session = $session;
|
|
1225
730
|
|
|
1226
731
|
if (module.load) {
|
|
1227
|
-
const { started } = this;
|
|
1228
|
-
|
|
1229
732
|
/** @type {import('types').LoadInput | import('types').ErrorLoadInput} */
|
|
1230
733
|
const load_input = {
|
|
1231
734
|
params: uses_params,
|
|
@@ -1281,15 +784,13 @@ class Renderer {
|
|
|
1281
784
|
}
|
|
1282
785
|
|
|
1283
786
|
/**
|
|
1284
|
-
* @param {import('
|
|
787
|
+
* @param {import('types').CSRRoute} route
|
|
788
|
+
* @param {import('./types').NavigationIntent} intent
|
|
1285
789
|
* @param {boolean} no_cache
|
|
1286
|
-
* @returns {Promise<import('./types').NavigationResult | undefined>} undefined if fallthrough
|
|
1287
790
|
*/
|
|
1288
|
-
async
|
|
1289
|
-
const key = url.pathname + url.search;
|
|
1290
|
-
|
|
791
|
+
async function load_route(route, { id, url, path }, no_cache) {
|
|
1291
792
|
if (!no_cache) {
|
|
1292
|
-
const cached =
|
|
793
|
+
const cached = cache.get(id);
|
|
1293
794
|
if (cached) return cached;
|
|
1294
795
|
}
|
|
1295
796
|
|
|
@@ -1299,10 +800,10 @@ class Renderer {
|
|
|
1299
800
|
get_params(/** @type {RegExpExecArray} */ (pattern.exec(path)))
|
|
1300
801
|
: {};
|
|
1301
802
|
|
|
1302
|
-
const changed =
|
|
1303
|
-
url:
|
|
1304
|
-
params: Object.keys(params).filter((key) =>
|
|
1305
|
-
session:
|
|
803
|
+
const changed = current.url && {
|
|
804
|
+
url: id !== current.url.pathname + current.url.search,
|
|
805
|
+
params: Object.keys(params).filter((key) => current.params[key] !== params[key]),
|
|
806
|
+
session: session_id !== current.session_id
|
|
1306
807
|
};
|
|
1307
808
|
|
|
1308
809
|
/** @type {Array<import('./types').BranchNode | undefined>} */
|
|
@@ -1329,7 +830,7 @@ class Renderer {
|
|
|
1329
830
|
if (!a[i]) continue;
|
|
1330
831
|
|
|
1331
832
|
const module = await a[i]();
|
|
1332
|
-
const previous =
|
|
833
|
+
const previous = current.branch[i];
|
|
1333
834
|
|
|
1334
835
|
const changed_since_last_render =
|
|
1335
836
|
!previous ||
|
|
@@ -1337,7 +838,7 @@ class Renderer {
|
|
|
1337
838
|
(changed.url && previous.uses.url) ||
|
|
1338
839
|
changed.params.some((param) => previous.uses.params.has(param)) ||
|
|
1339
840
|
(changed.session && previous.uses.session) ||
|
|
1340
|
-
Array.from(previous.uses.dependencies).some((dep) =>
|
|
841
|
+
Array.from(previous.uses.dependencies).some((dep) => invalidated.has(dep)) ||
|
|
1341
842
|
(stuff_changed && previous.uses.stuff);
|
|
1342
843
|
|
|
1343
844
|
if (changed_since_last_render) {
|
|
@@ -1363,7 +864,7 @@ class Renderer {
|
|
|
1363
864
|
return {
|
|
1364
865
|
redirect,
|
|
1365
866
|
props: {},
|
|
1366
|
-
state:
|
|
867
|
+
state: current
|
|
1367
868
|
};
|
|
1368
869
|
}
|
|
1369
870
|
|
|
@@ -1375,7 +876,7 @@ class Renderer {
|
|
|
1375
876
|
}
|
|
1376
877
|
|
|
1377
878
|
if (!error) {
|
|
1378
|
-
node = await
|
|
879
|
+
node = await load_node({
|
|
1379
880
|
module,
|
|
1380
881
|
url,
|
|
1381
882
|
params,
|
|
@@ -1402,7 +903,7 @@ class Renderer {
|
|
|
1402
903
|
return {
|
|
1403
904
|
redirect: node.loaded.redirect,
|
|
1404
905
|
props: {},
|
|
1405
|
-
state:
|
|
906
|
+
state: current
|
|
1406
907
|
};
|
|
1407
908
|
}
|
|
1408
909
|
|
|
@@ -1432,7 +933,7 @@ class Renderer {
|
|
|
1432
933
|
}
|
|
1433
934
|
|
|
1434
935
|
try {
|
|
1435
|
-
error_loaded = await
|
|
936
|
+
error_loaded = await load_node({
|
|
1436
937
|
status,
|
|
1437
938
|
error,
|
|
1438
939
|
module: await b[i](),
|
|
@@ -1441,11 +942,11 @@ class Renderer {
|
|
|
1441
942
|
stuff: node_loaded.stuff
|
|
1442
943
|
});
|
|
1443
944
|
|
|
1444
|
-
if (error_loaded
|
|
945
|
+
if (error_loaded?.loaded?.error) {
|
|
1445
946
|
continue;
|
|
1446
947
|
}
|
|
1447
948
|
|
|
1448
|
-
if (error_loaded
|
|
949
|
+
if (error_loaded?.loaded?.stuff) {
|
|
1449
950
|
stuff = {
|
|
1450
951
|
...stuff,
|
|
1451
952
|
...error_loaded.loaded.stuff
|
|
@@ -1460,13 +961,13 @@ class Renderer {
|
|
|
1460
961
|
}
|
|
1461
962
|
}
|
|
1462
963
|
|
|
1463
|
-
return await
|
|
964
|
+
return await load_root_error_page({
|
|
1464
965
|
status,
|
|
1465
966
|
error,
|
|
1466
967
|
url
|
|
1467
968
|
});
|
|
1468
969
|
} else {
|
|
1469
|
-
if (node
|
|
970
|
+
if (node?.loaded?.stuff) {
|
|
1470
971
|
stuff = {
|
|
1471
972
|
...stuff,
|
|
1472
973
|
...node.loaded.stuff
|
|
@@ -1477,7 +978,7 @@ class Renderer {
|
|
|
1477
978
|
}
|
|
1478
979
|
}
|
|
1479
980
|
|
|
1480
|
-
return await
|
|
981
|
+
return await get_navigation_result_from_branch({
|
|
1481
982
|
url,
|
|
1482
983
|
params,
|
|
1483
984
|
stuff,
|
|
@@ -1494,37 +995,480 @@ class Renderer {
|
|
|
1494
995
|
* url: URL;
|
|
1495
996
|
* }} opts
|
|
1496
997
|
*/
|
|
1497
|
-
async
|
|
998
|
+
async function load_root_error_page({ status, error, url }) {
|
|
1498
999
|
/** @type {Record<string, string>} */
|
|
1499
1000
|
const params = {}; // error page does not have params
|
|
1500
1001
|
|
|
1501
|
-
const
|
|
1502
|
-
module: await
|
|
1002
|
+
const root_layout = await load_node({
|
|
1003
|
+
module: await fallback[0],
|
|
1503
1004
|
url,
|
|
1504
1005
|
params,
|
|
1505
1006
|
stuff: {}
|
|
1506
1007
|
});
|
|
1507
|
-
|
|
1008
|
+
|
|
1009
|
+
const root_error = await load_node({
|
|
1508
1010
|
status,
|
|
1509
1011
|
error,
|
|
1510
|
-
module: await
|
|
1012
|
+
module: await fallback[1],
|
|
1511
1013
|
url,
|
|
1512
1014
|
params,
|
|
1513
|
-
stuff: (
|
|
1015
|
+
stuff: (root_layout && root_layout.loaded && root_layout.loaded.stuff) || {}
|
|
1514
1016
|
});
|
|
1515
1017
|
|
|
1516
|
-
|
|
1517
|
-
const stuff = { ...node?.loaded?.stuff, ...error_node?.loaded?.stuff };
|
|
1518
|
-
|
|
1519
|
-
return await this._get_navigation_result_from_branch({
|
|
1018
|
+
return await get_navigation_result_from_branch({
|
|
1520
1019
|
url,
|
|
1521
1020
|
params,
|
|
1522
|
-
stuff
|
|
1523
|
-
|
|
1021
|
+
stuff: {
|
|
1022
|
+
...root_layout?.loaded?.stuff,
|
|
1023
|
+
...root_error?.loaded?.stuff
|
|
1024
|
+
},
|
|
1025
|
+
branch: [root_layout, root_error],
|
|
1524
1026
|
status,
|
|
1525
1027
|
error
|
|
1526
1028
|
});
|
|
1527
1029
|
}
|
|
1030
|
+
|
|
1031
|
+
/** @param {URL} url */
|
|
1032
|
+
function owns(url) {
|
|
1033
|
+
return url.origin === location.origin && url.pathname.startsWith(base);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/** @param {URL} url */
|
|
1037
|
+
function get_navigation_intent(url) {
|
|
1038
|
+
const path = decodeURI(url.pathname.slice(base.length) || '/');
|
|
1039
|
+
|
|
1040
|
+
/** @type {import('./types').NavigationIntent} */
|
|
1041
|
+
const intent = {
|
|
1042
|
+
id: url.pathname + url.search,
|
|
1043
|
+
routes: routes.filter(([pattern]) => pattern.test(path)),
|
|
1044
|
+
url,
|
|
1045
|
+
path
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
return intent;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* @param {{
|
|
1053
|
+
* url: URL;
|
|
1054
|
+
* scroll: { x: number, y: number } | null;
|
|
1055
|
+
* keepfocus: boolean;
|
|
1056
|
+
* redirect_chain: string[];
|
|
1057
|
+
* details: {
|
|
1058
|
+
* replaceState: boolean;
|
|
1059
|
+
* state: any;
|
|
1060
|
+
* } | null;
|
|
1061
|
+
* accepted: () => void;
|
|
1062
|
+
* blocked: () => void;
|
|
1063
|
+
* }} opts
|
|
1064
|
+
*/
|
|
1065
|
+
async function navigate({ url, scroll, keepfocus, redirect_chain, details, accepted, blocked }) {
|
|
1066
|
+
const from = current.url;
|
|
1067
|
+
let should_block = false;
|
|
1068
|
+
|
|
1069
|
+
const navigation = {
|
|
1070
|
+
from,
|
|
1071
|
+
to: url,
|
|
1072
|
+
cancel: () => (should_block = true)
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
callbacks.before_navigate.forEach((fn) => fn(navigation));
|
|
1076
|
+
|
|
1077
|
+
if (should_block) {
|
|
1078
|
+
blocked();
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (!owns(url)) {
|
|
1083
|
+
await native_navigation(url);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const pathname = normalize_path(url.pathname, trailing_slash);
|
|
1087
|
+
url = new URL(url.origin + pathname + url.search + url.hash);
|
|
1088
|
+
|
|
1089
|
+
const intent = get_navigation_intent(url);
|
|
1090
|
+
|
|
1091
|
+
update_scroll_positions(current_history_index);
|
|
1092
|
+
|
|
1093
|
+
accepted();
|
|
1094
|
+
|
|
1095
|
+
navigating++;
|
|
1096
|
+
|
|
1097
|
+
const current_navigating_token = (navigating_token = {});
|
|
1098
|
+
|
|
1099
|
+
if (started) {
|
|
1100
|
+
stores.navigating.set({
|
|
1101
|
+
from: current.url,
|
|
1102
|
+
to: intent.url
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
await update(intent, redirect_chain, false, {
|
|
1107
|
+
scroll,
|
|
1108
|
+
keepfocus
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
navigating--;
|
|
1112
|
+
|
|
1113
|
+
// navigation was aborted
|
|
1114
|
+
if (navigating_token !== current_navigating_token) return;
|
|
1115
|
+
|
|
1116
|
+
if (!navigating) {
|
|
1117
|
+
const navigation = { from, to: url };
|
|
1118
|
+
callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
1119
|
+
|
|
1120
|
+
stores.navigating.set(null);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (details) {
|
|
1124
|
+
const change = details.replaceState ? 0 : 1;
|
|
1125
|
+
details.state[INDEX_KEY] = current_history_index += change;
|
|
1126
|
+
history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', intent.url);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Loads `href` the old-fashioned way, with a full page reload.
|
|
1132
|
+
* Returns a `Promise` that never resolves (to prevent any
|
|
1133
|
+
* subsequent work, e.g. history manipulation, from happening)
|
|
1134
|
+
* @param {URL} url
|
|
1135
|
+
*/
|
|
1136
|
+
function native_navigation(url) {
|
|
1137
|
+
location.href = url.href;
|
|
1138
|
+
return new Promise(() => {});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return {
|
|
1142
|
+
after_navigate: (fn) => {
|
|
1143
|
+
onMount(() => {
|
|
1144
|
+
callbacks.after_navigate.push(fn);
|
|
1145
|
+
|
|
1146
|
+
return () => {
|
|
1147
|
+
const i = callbacks.after_navigate.indexOf(fn);
|
|
1148
|
+
callbacks.after_navigate.splice(i, 1);
|
|
1149
|
+
};
|
|
1150
|
+
});
|
|
1151
|
+
},
|
|
1152
|
+
|
|
1153
|
+
before_navigate: (fn) => {
|
|
1154
|
+
onMount(() => {
|
|
1155
|
+
callbacks.before_navigate.push(fn);
|
|
1156
|
+
|
|
1157
|
+
return () => {
|
|
1158
|
+
const i = callbacks.before_navigate.indexOf(fn);
|
|
1159
|
+
callbacks.before_navigate.splice(i, 1);
|
|
1160
|
+
};
|
|
1161
|
+
});
|
|
1162
|
+
},
|
|
1163
|
+
|
|
1164
|
+
disable_scroll_handling: () => {
|
|
1165
|
+
if (import.meta.env.DEV && started && !updating) {
|
|
1166
|
+
throw new Error('Can only disable scroll handling during navigation');
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (updating || !started) {
|
|
1170
|
+
autoscroll = false;
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
|
|
1174
|
+
goto: (href, opts = {}) => goto(href, opts, []),
|
|
1175
|
+
|
|
1176
|
+
invalidate: (resource) => {
|
|
1177
|
+
const { href } = new URL(resource, location.href);
|
|
1178
|
+
|
|
1179
|
+
invalidated.add(href);
|
|
1180
|
+
|
|
1181
|
+
if (!invalidating) {
|
|
1182
|
+
invalidating = Promise.resolve().then(async () => {
|
|
1183
|
+
const intent = get_navigation_intent(new URL(location.href));
|
|
1184
|
+
await update(intent, [], true);
|
|
1185
|
+
|
|
1186
|
+
invalidating = null;
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
return invalidating;
|
|
1191
|
+
},
|
|
1192
|
+
|
|
1193
|
+
prefetch: async (href) => {
|
|
1194
|
+
const url = new URL(href, get_base_uri(document));
|
|
1195
|
+
await prefetch(url);
|
|
1196
|
+
},
|
|
1197
|
+
|
|
1198
|
+
// TODO rethink this API
|
|
1199
|
+
prefetch_routes: async (pathnames) => {
|
|
1200
|
+
const matching = pathnames
|
|
1201
|
+
? routes.filter((route) => pathnames.some((pathname) => route[0].test(pathname)))
|
|
1202
|
+
: routes;
|
|
1203
|
+
|
|
1204
|
+
const promises = matching.map((r) => Promise.all(r[1].map((load) => load())));
|
|
1205
|
+
|
|
1206
|
+
await Promise.all(promises);
|
|
1207
|
+
},
|
|
1208
|
+
|
|
1209
|
+
_start_router: () => {
|
|
1210
|
+
history.scrollRestoration = 'manual';
|
|
1211
|
+
|
|
1212
|
+
// Adopted from Nuxt.js
|
|
1213
|
+
// Reset scrollRestoration to auto when leaving page, allowing page reload
|
|
1214
|
+
// and back-navigation from other pages to use the browser to restore the
|
|
1215
|
+
// scrolling position.
|
|
1216
|
+
addEventListener('beforeunload', (e) => {
|
|
1217
|
+
let should_block = false;
|
|
1218
|
+
|
|
1219
|
+
const navigation = {
|
|
1220
|
+
from: current.url,
|
|
1221
|
+
to: null,
|
|
1222
|
+
cancel: () => (should_block = true)
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
callbacks.before_navigate.forEach((fn) => fn(navigation));
|
|
1226
|
+
|
|
1227
|
+
if (should_block) {
|
|
1228
|
+
e.preventDefault();
|
|
1229
|
+
e.returnValue = '';
|
|
1230
|
+
} else {
|
|
1231
|
+
history.scrollRestoration = 'auto';
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
addEventListener('visibilitychange', () => {
|
|
1236
|
+
if (document.visibilityState === 'hidden') {
|
|
1237
|
+
update_scroll_positions(current_history_index);
|
|
1238
|
+
|
|
1239
|
+
try {
|
|
1240
|
+
sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
|
|
1241
|
+
} catch {
|
|
1242
|
+
// do nothing
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
/** @param {Event} event */
|
|
1248
|
+
const trigger_prefetch = (event) => {
|
|
1249
|
+
const a = find_anchor(event);
|
|
1250
|
+
if (a && a.href && a.hasAttribute('sveltekit:prefetch')) {
|
|
1251
|
+
prefetch(get_href(a));
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
/** @type {NodeJS.Timeout} */
|
|
1256
|
+
let mousemove_timeout;
|
|
1257
|
+
|
|
1258
|
+
/** @param {MouseEvent|TouchEvent} event */
|
|
1259
|
+
const handle_mousemove = (event) => {
|
|
1260
|
+
clearTimeout(mousemove_timeout);
|
|
1261
|
+
mousemove_timeout = setTimeout(() => {
|
|
1262
|
+
// event.composedPath(), which is used in find_anchor, will be empty if the event is read in a timeout
|
|
1263
|
+
// add a layer of indirection to address that
|
|
1264
|
+
event.target?.dispatchEvent(
|
|
1265
|
+
new CustomEvent('sveltekit:trigger_prefetch', { bubbles: true })
|
|
1266
|
+
);
|
|
1267
|
+
}, 20);
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
addEventListener('touchstart', trigger_prefetch);
|
|
1271
|
+
addEventListener('mousemove', handle_mousemove);
|
|
1272
|
+
addEventListener('sveltekit:trigger_prefetch', trigger_prefetch);
|
|
1273
|
+
|
|
1274
|
+
/** @param {MouseEvent} event */
|
|
1275
|
+
addEventListener('click', (event) => {
|
|
1276
|
+
if (!router_enabled) return;
|
|
1277
|
+
|
|
1278
|
+
// Adapted from https://github.com/visionmedia/page.js
|
|
1279
|
+
// MIT license https://github.com/visionmedia/page.js#license
|
|
1280
|
+
if (event.button || event.which !== 1) return;
|
|
1281
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1282
|
+
if (event.defaultPrevented) return;
|
|
1283
|
+
|
|
1284
|
+
const a = find_anchor(event);
|
|
1285
|
+
if (!a) return;
|
|
1286
|
+
|
|
1287
|
+
if (!a.href) return;
|
|
1288
|
+
|
|
1289
|
+
const is_svg_a_element = a instanceof SVGAElement;
|
|
1290
|
+
const url = get_href(a);
|
|
1291
|
+
|
|
1292
|
+
// Ignore if url does not have origin (e.g. `mailto:`, `tel:`.)
|
|
1293
|
+
// MEMO: Without this condition, firefox will open mailer twice.
|
|
1294
|
+
// See: https://github.com/sveltejs/kit/issues/4045
|
|
1295
|
+
if (!is_svg_a_element && url.origin === 'null') return;
|
|
1296
|
+
|
|
1297
|
+
// Ignore if tag has
|
|
1298
|
+
// 1. 'download' attribute
|
|
1299
|
+
// 2. 'rel' attribute includes external
|
|
1300
|
+
const rel = (a.getAttribute('rel') || '').split(/\s+/);
|
|
1301
|
+
|
|
1302
|
+
if (a.hasAttribute('download') || rel.includes('external')) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// Ignore if <a> has a target
|
|
1307
|
+
if (is_svg_a_element ? a.target.baseVal : a.target) return;
|
|
1308
|
+
|
|
1309
|
+
if (url.href === location.href) {
|
|
1310
|
+
if (!location.hash) event.preventDefault();
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Check if new url only differs by hash and use the browser default behavior in that case
|
|
1315
|
+
// This will ensure the `hashchange` event is fired
|
|
1316
|
+
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1317
|
+
const [base, hash] = url.href.split('#');
|
|
1318
|
+
if (hash !== undefined && base === location.href.split('#')[0]) {
|
|
1319
|
+
// set this flag to distinguish between navigations triggered by
|
|
1320
|
+
// clicking a hash link and those triggered by popstate
|
|
1321
|
+
hash_navigating = true;
|
|
1322
|
+
|
|
1323
|
+
update_scroll_positions(current_history_index);
|
|
1324
|
+
|
|
1325
|
+
stores.page.set({ ...page, url });
|
|
1326
|
+
stores.page.notify();
|
|
1327
|
+
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
navigate({
|
|
1332
|
+
url,
|
|
1333
|
+
scroll: a.hasAttribute('sveltekit:noscroll') ? scroll_state() : null,
|
|
1334
|
+
keepfocus: false,
|
|
1335
|
+
redirect_chain: [],
|
|
1336
|
+
details: {
|
|
1337
|
+
state: {},
|
|
1338
|
+
replaceState: false
|
|
1339
|
+
},
|
|
1340
|
+
accepted: () => event.preventDefault(),
|
|
1341
|
+
blocked: () => event.preventDefault()
|
|
1342
|
+
});
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
addEventListener('popstate', (event) => {
|
|
1346
|
+
if (event.state && router_enabled) {
|
|
1347
|
+
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
1348
|
+
// with history.go, which means we end up back here, hence this check
|
|
1349
|
+
if (event.state[INDEX_KEY] === current_history_index) return;
|
|
1350
|
+
|
|
1351
|
+
navigate({
|
|
1352
|
+
url: new URL(location.href),
|
|
1353
|
+
scroll: scroll_positions[event.state[INDEX_KEY]],
|
|
1354
|
+
keepfocus: false,
|
|
1355
|
+
redirect_chain: [],
|
|
1356
|
+
details: null,
|
|
1357
|
+
accepted: () => {
|
|
1358
|
+
current_history_index = event.state[INDEX_KEY];
|
|
1359
|
+
},
|
|
1360
|
+
blocked: () => {
|
|
1361
|
+
const delta = current_history_index - event.state[INDEX_KEY];
|
|
1362
|
+
history.go(delta);
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
addEventListener('hashchange', () => {
|
|
1369
|
+
// if the hashchange happened as a result of clicking on a link,
|
|
1370
|
+
// we need to update history, otherwise we have to leave it alone
|
|
1371
|
+
if (hash_navigating) {
|
|
1372
|
+
hash_navigating = false;
|
|
1373
|
+
history.replaceState(
|
|
1374
|
+
{ ...history.state, [INDEX_KEY]: ++current_history_index },
|
|
1375
|
+
'',
|
|
1376
|
+
location.href
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
},
|
|
1381
|
+
|
|
1382
|
+
_hydrate: async ({ status, error, nodes, params }) => {
|
|
1383
|
+
const url = new URL(location.href);
|
|
1384
|
+
|
|
1385
|
+
/** @type {Array<import('./types').BranchNode | undefined>} */
|
|
1386
|
+
const branch = [];
|
|
1387
|
+
|
|
1388
|
+
/** @type {Record<string, any>} */
|
|
1389
|
+
let stuff = {};
|
|
1390
|
+
|
|
1391
|
+
/** @type {import('./types').NavigationResult | undefined} */
|
|
1392
|
+
let result;
|
|
1393
|
+
|
|
1394
|
+
let error_args;
|
|
1395
|
+
|
|
1396
|
+
try {
|
|
1397
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
1398
|
+
const is_leaf = i === nodes.length - 1;
|
|
1399
|
+
|
|
1400
|
+
let props;
|
|
1401
|
+
|
|
1402
|
+
if (is_leaf) {
|
|
1403
|
+
const serialized = document.querySelector('script[sveltekit\\:data-type="props"]');
|
|
1404
|
+
if (serialized) {
|
|
1405
|
+
props = JSON.parse(/** @type {string} */ (serialized.textContent));
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const node = await load_node({
|
|
1410
|
+
module: await nodes[i],
|
|
1411
|
+
url,
|
|
1412
|
+
params,
|
|
1413
|
+
stuff,
|
|
1414
|
+
status: is_leaf ? status : undefined,
|
|
1415
|
+
error: is_leaf ? error : undefined,
|
|
1416
|
+
props
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
if (props) {
|
|
1420
|
+
node.uses.dependencies.add(url.href);
|
|
1421
|
+
node.uses.url = true;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
branch.push(node);
|
|
1425
|
+
|
|
1426
|
+
if (node && node.loaded) {
|
|
1427
|
+
if (node.loaded.error) {
|
|
1428
|
+
if (error) throw node.loaded.error;
|
|
1429
|
+
error_args = {
|
|
1430
|
+
status: node.loaded.status,
|
|
1431
|
+
error: node.loaded.error,
|
|
1432
|
+
url
|
|
1433
|
+
};
|
|
1434
|
+
} else if (node.loaded.stuff) {
|
|
1435
|
+
stuff = {
|
|
1436
|
+
...stuff,
|
|
1437
|
+
...node.loaded.stuff
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
result = error_args
|
|
1444
|
+
? await load_root_error_page(error_args)
|
|
1445
|
+
: await get_navigation_result_from_branch({
|
|
1446
|
+
url,
|
|
1447
|
+
params,
|
|
1448
|
+
stuff,
|
|
1449
|
+
branch,
|
|
1450
|
+
status,
|
|
1451
|
+
error
|
|
1452
|
+
});
|
|
1453
|
+
} catch (e) {
|
|
1454
|
+
if (error) throw e;
|
|
1455
|
+
|
|
1456
|
+
result = await load_root_error_page({
|
|
1457
|
+
status: 500,
|
|
1458
|
+
error: coalesce_to_error(e),
|
|
1459
|
+
url
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
if (result.redirect) {
|
|
1464
|
+
// this is a real edge case — `load` would need to return
|
|
1465
|
+
// a redirect but only in the browser
|
|
1466
|
+
await native_navigation(new URL(result.redirect, location.href));
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
initialize(result);
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1528
1472
|
}
|
|
1529
1473
|
|
|
1530
1474
|
/**
|
|
@@ -1533,7 +1477,7 @@ class Renderer {
|
|
|
1533
1477
|
* assets: string;
|
|
1534
1478
|
* base: string;
|
|
1535
1479
|
* },
|
|
1536
|
-
* target:
|
|
1480
|
+
* target: Element;
|
|
1537
1481
|
* session: any;
|
|
1538
1482
|
* route: boolean;
|
|
1539
1483
|
* spa: boolean;
|
|
@@ -1547,29 +1491,23 @@ class Renderer {
|
|
|
1547
1491
|
* }} opts
|
|
1548
1492
|
*/
|
|
1549
1493
|
async function start({ paths, target, session, route, spa, trailing_slash, hydrate }) {
|
|
1550
|
-
const
|
|
1551
|
-
Root,
|
|
1552
|
-
fallback,
|
|
1494
|
+
const client = create_client({
|
|
1553
1495
|
target,
|
|
1554
|
-
session
|
|
1496
|
+
session,
|
|
1497
|
+
base: paths.base,
|
|
1498
|
+
trailing_slash
|
|
1555
1499
|
});
|
|
1556
1500
|
|
|
1557
|
-
|
|
1558
|
-
? new Router({
|
|
1559
|
-
base: paths.base,
|
|
1560
|
-
routes,
|
|
1561
|
-
trailing_slash,
|
|
1562
|
-
renderer
|
|
1563
|
-
})
|
|
1564
|
-
: null;
|
|
1565
|
-
|
|
1566
|
-
init({ router, renderer });
|
|
1501
|
+
init({ client });
|
|
1567
1502
|
set_paths(paths);
|
|
1568
1503
|
|
|
1569
|
-
if (hydrate)
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1504
|
+
if (hydrate) {
|
|
1505
|
+
await client._hydrate(hydrate);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
if (route) {
|
|
1509
|
+
if (spa) client.goto(location.href, { replaceState: true });
|
|
1510
|
+
client._start_router();
|
|
1573
1511
|
}
|
|
1574
1512
|
|
|
1575
1513
|
dispatchEvent(new CustomEvent('sveltekit:start'));
|