@sveltejs/kit 2.0.8 → 2.1.0
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/package.json +1 -1
- package/src/exports/vite/dev/index.js +1 -1
- package/src/exports/vite/index.js +3 -3
- package/src/runtime/app/forms.js +3 -17
- package/src/runtime/app/navigation.js +13 -142
- package/src/runtime/app/stores.js +5 -5
- package/src/runtime/client/client.js +1800 -1620
- package/src/runtime/client/entry.js +3 -0
- package/src/runtime/client/fetcher.js +5 -4
- package/src/runtime/client/types.d.ts +0 -41
- package/src/version.js +1 -1
- package/types/index.d.ts +55 -48
- package/types/index.d.ts.map +6 -6
- package/src/runtime/client/singletons.js +0 -59
- package/src/runtime/client/start.js +0 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEV } from 'esm-env';
|
|
1
|
+
import { BROWSER, DEV } from 'esm-env';
|
|
2
2
|
import { onMount, tick } from 'svelte';
|
|
3
3
|
import {
|
|
4
4
|
add_data_suffix,
|
|
@@ -24,9 +24,10 @@ import {
|
|
|
24
24
|
get_router_options,
|
|
25
25
|
is_external_url,
|
|
26
26
|
origin,
|
|
27
|
-
scroll_state
|
|
27
|
+
scroll_state,
|
|
28
|
+
notifiable_store,
|
|
29
|
+
create_updated_store
|
|
28
30
|
} from './utils.js';
|
|
29
|
-
|
|
30
31
|
import { base } from '__sveltekit/paths';
|
|
31
32
|
import * as devalue from 'devalue';
|
|
32
33
|
import {
|
|
@@ -42,39 +43,31 @@ import { validate_page_exports } from '../../utils/exports.js';
|
|
|
42
43
|
import { compact } from '../../utils/array.js';
|
|
43
44
|
import { HttpError, Redirect, SvelteKitError } from '../control.js';
|
|
44
45
|
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, validate_depends } from '../shared.js';
|
|
45
|
-
import { stores } from './singletons.js';
|
|
46
46
|
import { get_message, get_status } from '../../utils/error.js';
|
|
47
|
+
import { writable } from 'svelte/store';
|
|
47
48
|
|
|
48
49
|
let errored = false;
|
|
49
50
|
|
|
50
|
-
/** @typedef {{ x: number; y: number }} ScrollPosition */
|
|
51
|
-
|
|
52
51
|
// We track the scroll position associated with each history entry in sessionStorage,
|
|
53
52
|
// rather than on history.state itself, because when navigation is driven by
|
|
54
53
|
// popstate it's too late to update the scroll position associated with the
|
|
55
54
|
// state we're navigating from
|
|
56
55
|
/**
|
|
57
56
|
* history index -> { x, y }
|
|
58
|
-
* @type {Record<number,
|
|
57
|
+
* @type {Record<number, { x: number; y: number }>}
|
|
59
58
|
*/
|
|
60
59
|
const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
61
60
|
|
|
62
|
-
/**
|
|
63
|
-
* history index -> any
|
|
64
|
-
* @type {Record<string, Record<string, any>>}
|
|
65
|
-
*/
|
|
66
|
-
const states = storage.get(STATES_KEY, devalue.parse) ?? {};
|
|
67
|
-
|
|
68
61
|
/**
|
|
69
62
|
* navigation index -> any
|
|
70
63
|
* @type {Record<string, any[]>}
|
|
71
64
|
*/
|
|
72
65
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
73
66
|
|
|
74
|
-
const original_push_state = history.pushState;
|
|
75
|
-
const original_replace_state = history.replaceState;
|
|
67
|
+
const original_push_state = BROWSER ? history.pushState : () => {};
|
|
68
|
+
const original_replace_state = BROWSER ? history.replaceState : () => {};
|
|
76
69
|
|
|
77
|
-
if (DEV) {
|
|
70
|
+
if (DEV && BROWSER) {
|
|
78
71
|
let warned = false;
|
|
79
72
|
|
|
80
73
|
const warn = () => {
|
|
@@ -97,6 +90,15 @@ if (DEV) {
|
|
|
97
90
|
};
|
|
98
91
|
}
|
|
99
92
|
|
|
93
|
+
export const stores = {
|
|
94
|
+
url: /* @__PURE__ */ notifiable_store({}),
|
|
95
|
+
page: /* @__PURE__ */ notifiable_store({}),
|
|
96
|
+
navigating: /* @__PURE__ */ writable(
|
|
97
|
+
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
|
|
98
|
+
),
|
|
99
|
+
updated: /* @__PURE__ */ create_updated_store()
|
|
100
|
+
};
|
|
101
|
+
|
|
100
102
|
/** @param {number} index */
|
|
101
103
|
function update_scroll_positions(index) {
|
|
102
104
|
scroll_positions[index] = scroll_state();
|
|
@@ -135,78 +137,106 @@ function native_navigation(url) {
|
|
|
135
137
|
|
|
136
138
|
function noop() {}
|
|
137
139
|
|
|
140
|
+
/** @type {import('types').CSRRoute[]} */
|
|
141
|
+
let routes;
|
|
142
|
+
/** @type {import('types').CSRPageNodeLoader} */
|
|
143
|
+
let default_layout_loader;
|
|
144
|
+
/** @type {import('types').CSRPageNodeLoader} */
|
|
145
|
+
let default_error_loader;
|
|
146
|
+
/** @type {HTMLElement} */
|
|
147
|
+
let container;
|
|
148
|
+
/** @type {HTMLElement} */
|
|
149
|
+
let target;
|
|
150
|
+
/** @type {import('./types.js').SvelteKitApp} */
|
|
151
|
+
let app;
|
|
152
|
+
|
|
153
|
+
/** @type {Array<((url: URL) => boolean)>} */
|
|
154
|
+
const invalidated = [];
|
|
155
|
+
|
|
138
156
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
157
|
+
* An array of the `+layout.svelte` and `+page.svelte` component instances
|
|
158
|
+
* that currently live on the page — used for capturing and restoring snapshots.
|
|
159
|
+
* It's updated/manipulated through `bind:this` in `Root.svelte`.
|
|
160
|
+
* @type {import('svelte').SvelteComponent[]}
|
|
142
161
|
*/
|
|
143
|
-
|
|
144
|
-
const routes = parse(app);
|
|
162
|
+
const components = [];
|
|
145
163
|
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
/** @type {{id: string, promise: Promise<import('./types.js').NavigationResult>} | null} */
|
|
165
|
+
let load_cache = null;
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
default_layout_loader();
|
|
152
|
-
default_error_loader();
|
|
167
|
+
/** @type {Array<(navigation: import('@sveltejs/kit').BeforeNavigate) => void>} */
|
|
168
|
+
const before_navigate_callbacks = [];
|
|
153
169
|
|
|
154
|
-
|
|
170
|
+
/** @type {Array<(navigation: import('@sveltejs/kit').OnNavigate) => import('types').MaybePromise<(() => void) | void>>} */
|
|
171
|
+
const on_navigate_callbacks = [];
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
/** @type {Array<(navigation: import('@sveltejs/kit').AfterNavigate) => void>} */
|
|
174
|
+
let after_navigate_callbacks = [];
|
|
158
175
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
/** @type {import('./types.js').NavigationState} */
|
|
177
|
+
let current = {
|
|
178
|
+
branch: [],
|
|
179
|
+
error: null,
|
|
180
|
+
// @ts-ignore - we need the initial value to be null
|
|
181
|
+
url: null
|
|
182
|
+
};
|
|
166
183
|
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
/** this being true means we SSR'd */
|
|
185
|
+
let hydrated = false;
|
|
186
|
+
let started = false;
|
|
187
|
+
let autoscroll = true;
|
|
188
|
+
let updating = false;
|
|
189
|
+
let navigating = false;
|
|
190
|
+
let hash_navigating = false;
|
|
191
|
+
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
192
|
+
let has_navigated = false;
|
|
169
193
|
|
|
170
|
-
|
|
171
|
-
/** @type {Array<(navigation: import('@sveltejs/kit').BeforeNavigate) => void>} */
|
|
172
|
-
before_navigate: [],
|
|
194
|
+
let force_invalidation = false;
|
|
173
195
|
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
/** @type {import('svelte').SvelteComponent} */
|
|
197
|
+
let root;
|
|
176
198
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
};
|
|
199
|
+
/** @type {number} keeping track of the history index in order to prevent popstate navigation events if needed */
|
|
200
|
+
let current_history_index;
|
|
180
201
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
202
|
+
/** @type {number} */
|
|
203
|
+
let current_navigation_index;
|
|
204
|
+
|
|
205
|
+
/** @type {import('@sveltejs/kit').Page} */
|
|
206
|
+
let page;
|
|
207
|
+
|
|
208
|
+
/** @type {{}} */
|
|
209
|
+
let token;
|
|
188
210
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let started = false;
|
|
192
|
-
let autoscroll = true;
|
|
193
|
-
let updating = false;
|
|
194
|
-
let navigating = false;
|
|
195
|
-
let hash_navigating = false;
|
|
196
|
-
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
197
|
-
let has_navigated = false;
|
|
211
|
+
/** @type {Promise<void> | null} */
|
|
212
|
+
let pending_invalidate;
|
|
198
213
|
|
|
199
|
-
|
|
214
|
+
/**
|
|
215
|
+
* @param {import('./types.js').SvelteKitApp} _app
|
|
216
|
+
* @param {HTMLElement} _target
|
|
217
|
+
* @param {Parameters<typeof _hydrate>[1]} [hydrate]
|
|
218
|
+
*/
|
|
219
|
+
export async function start(_app, _target, hydrate) {
|
|
220
|
+
if (DEV && _target === document.body) {
|
|
221
|
+
console.warn(
|
|
222
|
+
'Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.\n\nConsider wrapping it in an element:\n\n<div style="display: contents">\n %sveltekit.body%\n</div>'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
200
225
|
|
|
201
|
-
|
|
202
|
-
|
|
226
|
+
app = _app;
|
|
227
|
+
routes = parse(_app);
|
|
228
|
+
container = __SVELTEKIT_EMBEDDED__ ? _target : document.documentElement;
|
|
229
|
+
target = _target;
|
|
203
230
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
231
|
+
// we import the root layout/error nodes eagerly, so that
|
|
232
|
+
// connectivity errors after initialisation don't nuke the app
|
|
233
|
+
default_layout_loader = _app.nodes[0];
|
|
234
|
+
default_error_loader = _app.nodes[1];
|
|
235
|
+
default_layout_loader();
|
|
236
|
+
default_error_loader();
|
|
207
237
|
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
current_history_index = history.state?.[HISTORY_INDEX];
|
|
239
|
+
current_navigation_index = history.state?.[NAVIGATION_INDEX];
|
|
210
240
|
|
|
211
241
|
if (!current_history_index) {
|
|
212
242
|
// we use Date.now() as an offset so that cross-document navigations
|
|
@@ -234,1876 +264,2026 @@ export function create_client(app, target) {
|
|
|
234
264
|
scrollTo(scroll.x, scroll.y);
|
|
235
265
|
}
|
|
236
266
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
if (hydrate) {
|
|
268
|
+
await _hydrate(target, hydrate);
|
|
269
|
+
} else {
|
|
270
|
+
goto(location.href, { replaceState: true });
|
|
271
|
+
}
|
|
242
272
|
|
|
243
|
-
|
|
244
|
-
|
|
273
|
+
_start_router();
|
|
274
|
+
}
|
|
245
275
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
276
|
+
async function _invalidate() {
|
|
277
|
+
// Accept all invalidations as they come, don't swallow any while another invalidation
|
|
278
|
+
// is running because subsequent invalidations may make earlier ones outdated,
|
|
279
|
+
// but batch multiple synchronous invalidations.
|
|
280
|
+
await (pending_invalidate ||= Promise.resolve());
|
|
281
|
+
if (!pending_invalidate) return;
|
|
282
|
+
pending_invalidate = null;
|
|
253
283
|
|
|
254
|
-
|
|
284
|
+
const intent = get_navigation_intent(current.url, true);
|
|
255
285
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
286
|
+
// Clear preload, it might be affected by the invalidation.
|
|
287
|
+
// Also solves an edge case where a preload is triggered, the navigation for it
|
|
288
|
+
// was then triggered and is still running while the invalidation kicks in,
|
|
289
|
+
// at which point the invalidation should take over and "win".
|
|
290
|
+
load_cache = null;
|
|
261
291
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
292
|
+
const nav_token = (token = {});
|
|
293
|
+
const navigation_result = intent && (await load_route(intent));
|
|
294
|
+
if (nav_token !== token) return;
|
|
265
295
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
root.$set(navigation_result.props);
|
|
296
|
+
if (navigation_result) {
|
|
297
|
+
if (navigation_result.type === 'redirect') {
|
|
298
|
+
await _goto(new URL(navigation_result.location, current.url).href, {}, 1, nav_token);
|
|
299
|
+
} else {
|
|
300
|
+
if (navigation_result.props.page !== undefined) {
|
|
301
|
+
page = navigation_result.props.page;
|
|
274
302
|
}
|
|
303
|
+
root.$set(navigation_result.props);
|
|
275
304
|
}
|
|
276
|
-
|
|
277
|
-
invalidated.length = 0;
|
|
278
305
|
}
|
|
279
306
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (components.some((c) => c?.snapshot)) {
|
|
283
|
-
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
284
|
-
}
|
|
285
|
-
}
|
|
307
|
+
invalidated.length = 0;
|
|
308
|
+
}
|
|
286
309
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
310
|
+
/** @param {number} index */
|
|
311
|
+
function capture_snapshot(index) {
|
|
312
|
+
if (components.some((c) => c?.snapshot)) {
|
|
313
|
+
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
292
314
|
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** @param {number} index */
|
|
318
|
+
function restore_snapshot(index) {
|
|
319
|
+
snapshots[index]?.forEach((value, i) => {
|
|
320
|
+
components[i]?.snapshot?.restore(value);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
293
323
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
324
|
+
function persist_state() {
|
|
325
|
+
update_scroll_positions(current_history_index);
|
|
326
|
+
storage.set(SCROLL_KEY, scroll_positions);
|
|
297
327
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
328
|
+
capture_snapshot(current_navigation_index);
|
|
329
|
+
storage.set(SNAPSHOT_KEY, snapshots);
|
|
330
|
+
}
|
|
302
331
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
332
|
+
/**
|
|
333
|
+
* @param {string | URL} url
|
|
334
|
+
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; state?: Record<string, any> }} options
|
|
335
|
+
* @param {number} redirect_count
|
|
336
|
+
* @param {{}} [nav_token]
|
|
337
|
+
*/
|
|
338
|
+
async function _goto(url, options, redirect_count, nav_token) {
|
|
339
|
+
return navigate({
|
|
340
|
+
type: 'goto',
|
|
341
|
+
url: resolve_url(url),
|
|
342
|
+
keepfocus: options.keepFocus,
|
|
343
|
+
noscroll: options.noScroll,
|
|
344
|
+
replace_state: options.replaceState,
|
|
345
|
+
state: options.state,
|
|
346
|
+
redirect_count,
|
|
347
|
+
nav_token,
|
|
348
|
+
accept: () => {
|
|
349
|
+
if (options.invalidateAll) {
|
|
350
|
+
force_invalidation = true;
|
|
323
351
|
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
326
355
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
356
|
+
/** @param {import('./types.js').NavigationIntent} intent */
|
|
357
|
+
async function _preload_data(intent) {
|
|
358
|
+
load_cache = {
|
|
359
|
+
id: intent.id,
|
|
360
|
+
promise: load_route(intent).then((result) => {
|
|
361
|
+
if (result.type === 'loaded' && result.state.error) {
|
|
362
|
+
// Don't cache errors, because they might be transient
|
|
363
|
+
load_cache = null;
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
})
|
|
367
|
+
};
|
|
339
368
|
|
|
340
|
-
|
|
341
|
-
|
|
369
|
+
return load_cache.promise;
|
|
370
|
+
}
|
|
342
371
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
372
|
+
/** @param {string} pathname */
|
|
373
|
+
async function _preload_code(pathname) {
|
|
374
|
+
const route = routes.find((route) => route.exec(get_url_path(pathname)));
|
|
346
375
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
376
|
+
if (route) {
|
|
377
|
+
await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
|
|
350
378
|
}
|
|
379
|
+
}
|
|
351
380
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
381
|
+
/**
|
|
382
|
+
* @param {import('./types.js').NavigationFinished} result
|
|
383
|
+
* @param {HTMLElement} target
|
|
384
|
+
*/
|
|
385
|
+
function initialize(result, target) {
|
|
386
|
+
if (DEV && result.state.error && document.querySelector('vite-error-overlay')) return;
|
|
355
387
|
|
|
356
|
-
|
|
388
|
+
current = result.state;
|
|
357
389
|
|
|
358
|
-
|
|
359
|
-
|
|
390
|
+
const style = document.querySelector('style[data-sveltekit]');
|
|
391
|
+
if (style) style.remove();
|
|
360
392
|
|
|
361
|
-
|
|
393
|
+
page = /** @type {import('@sveltejs/kit').Page} */ (result.props.page);
|
|
362
394
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
395
|
+
root = new app.root({
|
|
396
|
+
target,
|
|
397
|
+
props: { ...result.props, stores, components },
|
|
398
|
+
hydrate: true
|
|
399
|
+
});
|
|
368
400
|
|
|
369
|
-
|
|
401
|
+
restore_snapshot(current_navigation_index);
|
|
370
402
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
403
|
+
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
404
|
+
const navigation = {
|
|
405
|
+
from: null,
|
|
406
|
+
to: {
|
|
407
|
+
params: current.params,
|
|
408
|
+
route: { id: current.route?.id ?? null },
|
|
409
|
+
url: new URL(location.href)
|
|
410
|
+
},
|
|
411
|
+
willUnload: false,
|
|
412
|
+
type: 'enter',
|
|
413
|
+
complete: Promise.resolve()
|
|
414
|
+
};
|
|
384
415
|
|
|
385
|
-
|
|
386
|
-
}
|
|
416
|
+
after_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
387
417
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
type: 'loaded',
|
|
421
|
-
state: {
|
|
422
|
-
url,
|
|
423
|
-
params,
|
|
424
|
-
branch,
|
|
425
|
-
error,
|
|
426
|
-
route
|
|
427
|
-
},
|
|
428
|
-
props: {
|
|
429
|
-
// @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
|
|
430
|
-
constructors: compact(branch).map((branch_node) => branch_node.node.component),
|
|
431
|
-
page
|
|
432
|
-
}
|
|
433
|
-
};
|
|
418
|
+
started = true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
*
|
|
423
|
+
* @param {{
|
|
424
|
+
* url: URL;
|
|
425
|
+
* params: Record<string, string>;
|
|
426
|
+
* branch: Array<import('./types.js').BranchNode | undefined>;
|
|
427
|
+
* status: number;
|
|
428
|
+
* error: App.Error | null;
|
|
429
|
+
* route: import('types').CSRRoute | null;
|
|
430
|
+
* form?: Record<string, any> | null;
|
|
431
|
+
* }} opts
|
|
432
|
+
*/
|
|
433
|
+
async function get_navigation_result_from_branch({
|
|
434
|
+
url,
|
|
435
|
+
params,
|
|
436
|
+
branch,
|
|
437
|
+
status,
|
|
438
|
+
error,
|
|
439
|
+
route,
|
|
440
|
+
form
|
|
441
|
+
}) {
|
|
442
|
+
/** @type {import('types').TrailingSlash} */
|
|
443
|
+
let slash = 'never';
|
|
444
|
+
for (const node of branch) {
|
|
445
|
+
if (node?.slash !== undefined) slash = node.slash;
|
|
446
|
+
}
|
|
447
|
+
url.pathname = normalize_path(url.pathname, slash);
|
|
448
|
+
// eslint-disable-next-line
|
|
449
|
+
url.search = url.search; // turn `/?` into `/`
|
|
434
450
|
|
|
435
|
-
|
|
436
|
-
|
|
451
|
+
/** @type {import('./types.js').NavigationFinished} */
|
|
452
|
+
const result = {
|
|
453
|
+
type: 'loaded',
|
|
454
|
+
state: {
|
|
455
|
+
url,
|
|
456
|
+
params,
|
|
457
|
+
branch,
|
|
458
|
+
error,
|
|
459
|
+
route
|
|
460
|
+
},
|
|
461
|
+
props: {
|
|
462
|
+
// @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
|
|
463
|
+
constructors: compact(branch).map((branch_node) => branch_node.node.component),
|
|
464
|
+
page
|
|
437
465
|
}
|
|
466
|
+
};
|
|
438
467
|
|
|
439
|
-
|
|
440
|
-
|
|
468
|
+
if (form !== undefined) {
|
|
469
|
+
result.props.form = form;
|
|
470
|
+
}
|
|
441
471
|
|
|
442
|
-
|
|
472
|
+
let data = {};
|
|
473
|
+
let data_changed = !page;
|
|
443
474
|
|
|
444
|
-
|
|
445
|
-
const node = branch[i];
|
|
446
|
-
const prev = current.branch[i];
|
|
475
|
+
let p = 0;
|
|
447
476
|
|
|
448
|
-
|
|
449
|
-
|
|
477
|
+
for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) {
|
|
478
|
+
const node = branch[i];
|
|
479
|
+
const prev = current.branch[i];
|
|
450
480
|
|
|
451
|
-
|
|
481
|
+
if (node?.data !== prev?.data) data_changed = true;
|
|
482
|
+
if (!node) continue;
|
|
452
483
|
|
|
453
|
-
|
|
454
|
-
if (data_changed) {
|
|
455
|
-
result.props[`data_${p}`] = data;
|
|
456
|
-
}
|
|
484
|
+
data = { ...data, ...node.data };
|
|
457
485
|
|
|
458
|
-
|
|
486
|
+
// Only set props if the node actually updated. This prevents needless rerenders.
|
|
487
|
+
if (data_changed) {
|
|
488
|
+
result.props[`data_${p}`] = data;
|
|
459
489
|
}
|
|
460
490
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
url.href !== current.url.href ||
|
|
464
|
-
current.error !== error ||
|
|
465
|
-
(form !== undefined && form !== page.form) ||
|
|
466
|
-
data_changed;
|
|
491
|
+
p += 1;
|
|
492
|
+
}
|
|
467
493
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
},
|
|
475
|
-
state: {},
|
|
476
|
-
status,
|
|
477
|
-
url: new URL(url),
|
|
478
|
-
form: form ?? null,
|
|
479
|
-
// The whole page store is updated, but this way the object reference stays the same
|
|
480
|
-
data: data_changed ? data : page.data
|
|
481
|
-
};
|
|
482
|
-
}
|
|
494
|
+
const page_changed =
|
|
495
|
+
!current.url ||
|
|
496
|
+
url.href !== current.url.href ||
|
|
497
|
+
current.error !== error ||
|
|
498
|
+
(form !== undefined && form !== page.form) ||
|
|
499
|
+
data_changed;
|
|
483
500
|
|
|
484
|
-
|
|
501
|
+
if (page_changed) {
|
|
502
|
+
result.props.page = {
|
|
503
|
+
error,
|
|
504
|
+
params,
|
|
505
|
+
route: {
|
|
506
|
+
id: route?.id ?? null
|
|
507
|
+
},
|
|
508
|
+
state: {},
|
|
509
|
+
status,
|
|
510
|
+
url: new URL(url),
|
|
511
|
+
form: form ?? null,
|
|
512
|
+
// The whole page store is updated, but this way the object reference stays the same
|
|
513
|
+
data: data_changed ? data : page.data
|
|
514
|
+
};
|
|
485
515
|
}
|
|
486
516
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
* If `server_data` is passed, this is treated as the initial run and the page endpoint is not requested.
|
|
490
|
-
*
|
|
491
|
-
* @param {{
|
|
492
|
-
* loader: import('types').CSRPageNodeLoader;
|
|
493
|
-
* parent: () => Promise<Record<string, any>>;
|
|
494
|
-
* url: URL;
|
|
495
|
-
* params: Record<string, string>;
|
|
496
|
-
* route: { id: string | null };
|
|
497
|
-
* server_data_node: import('./types.js').DataNode | null;
|
|
498
|
-
* }} options
|
|
499
|
-
* @returns {Promise<import('./types.js').BranchNode>}
|
|
500
|
-
*/
|
|
501
|
-
async function load_node({ loader, parent, url, params, route, server_data_node }) {
|
|
502
|
-
/** @type {Record<string, any> | null} */
|
|
503
|
-
let data = null;
|
|
504
|
-
|
|
505
|
-
let is_tracking = true;
|
|
506
|
-
|
|
507
|
-
/** @type {import('types').Uses} */
|
|
508
|
-
const uses = {
|
|
509
|
-
dependencies: new Set(),
|
|
510
|
-
params: new Set(),
|
|
511
|
-
parent: false,
|
|
512
|
-
route: false,
|
|
513
|
-
url: false,
|
|
514
|
-
search_params: new Set()
|
|
515
|
-
};
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
516
519
|
|
|
517
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Call the load function of the given node, if it exists.
|
|
522
|
+
* If `server_data` is passed, this is treated as the initial run and the page endpoint is not requested.
|
|
523
|
+
*
|
|
524
|
+
* @param {{
|
|
525
|
+
* loader: import('types').CSRPageNodeLoader;
|
|
526
|
+
* parent: () => Promise<Record<string, any>>;
|
|
527
|
+
* url: URL;
|
|
528
|
+
* params: Record<string, string>;
|
|
529
|
+
* route: { id: string | null };
|
|
530
|
+
* server_data_node: import('./types.js').DataNode | null;
|
|
531
|
+
* }} options
|
|
532
|
+
* @returns {Promise<import('./types.js').BranchNode>}
|
|
533
|
+
*/
|
|
534
|
+
async function load_node({ loader, parent, url, params, route, server_data_node }) {
|
|
535
|
+
/** @type {Record<string, any> | null} */
|
|
536
|
+
let data = null;
|
|
537
|
+
|
|
538
|
+
let is_tracking = true;
|
|
539
|
+
|
|
540
|
+
/** @type {import('types').Uses} */
|
|
541
|
+
const uses = {
|
|
542
|
+
dependencies: new Set(),
|
|
543
|
+
params: new Set(),
|
|
544
|
+
parent: false,
|
|
545
|
+
route: false,
|
|
546
|
+
url: false,
|
|
547
|
+
search_params: new Set()
|
|
548
|
+
};
|
|
518
549
|
|
|
519
|
-
|
|
520
|
-
validate_page_exports(node.universal);
|
|
521
|
-
}
|
|
550
|
+
const node = await loader();
|
|
522
551
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
for (const dep of deps) {
|
|
527
|
-
if (DEV) validate_depends(/** @type {string} */ (route.id), dep);
|
|
552
|
+
if (DEV) {
|
|
553
|
+
validate_page_exports(node.universal);
|
|
554
|
+
}
|
|
528
555
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
556
|
+
if (node.universal?.load) {
|
|
557
|
+
/** @param {string[]} deps */
|
|
558
|
+
function depends(...deps) {
|
|
559
|
+
for (const dep of deps) {
|
|
560
|
+
if (DEV) validate_depends(/** @type {string} */ (route.id), dep);
|
|
533
561
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (is_tracking) {
|
|
539
|
-
uses.route = true;
|
|
540
|
-
}
|
|
541
|
-
return target[/** @type {'id'} */ (key)];
|
|
542
|
-
}
|
|
543
|
-
}),
|
|
544
|
-
params: new Proxy(params, {
|
|
545
|
-
get: (target, key) => {
|
|
546
|
-
if (is_tracking) {
|
|
547
|
-
uses.params.add(/** @type {string} */ (key));
|
|
548
|
-
}
|
|
549
|
-
return target[/** @type {string} */ (key)];
|
|
550
|
-
}
|
|
551
|
-
}),
|
|
552
|
-
data: server_data_node?.data ?? null,
|
|
553
|
-
url: make_trackable(
|
|
554
|
-
url,
|
|
555
|
-
() => {
|
|
556
|
-
if (is_tracking) {
|
|
557
|
-
uses.url = true;
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
(param) => {
|
|
561
|
-
if (is_tracking) {
|
|
562
|
-
uses.search_params.add(param);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
),
|
|
566
|
-
async fetch(resource, init) {
|
|
567
|
-
/** @type {URL | string} */
|
|
568
|
-
let requested;
|
|
569
|
-
|
|
570
|
-
if (resource instanceof Request) {
|
|
571
|
-
requested = resource.url;
|
|
572
|
-
|
|
573
|
-
// we're not allowed to modify the received `Request` object, so in order
|
|
574
|
-
// to fixup relative urls we create a new equivalent `init` object instead
|
|
575
|
-
init = {
|
|
576
|
-
// the request body must be consumed in memory until browsers
|
|
577
|
-
// implement streaming request bodies and/or the body getter
|
|
578
|
-
body:
|
|
579
|
-
resource.method === 'GET' || resource.method === 'HEAD'
|
|
580
|
-
? undefined
|
|
581
|
-
: await resource.blob(),
|
|
582
|
-
cache: resource.cache,
|
|
583
|
-
credentials: resource.credentials,
|
|
584
|
-
headers: resource.headers,
|
|
585
|
-
integrity: resource.integrity,
|
|
586
|
-
keepalive: resource.keepalive,
|
|
587
|
-
method: resource.method,
|
|
588
|
-
mode: resource.mode,
|
|
589
|
-
redirect: resource.redirect,
|
|
590
|
-
referrer: resource.referrer,
|
|
591
|
-
referrerPolicy: resource.referrerPolicy,
|
|
592
|
-
signal: resource.signal,
|
|
593
|
-
...init
|
|
594
|
-
};
|
|
595
|
-
} else {
|
|
596
|
-
requested = resource;
|
|
597
|
-
}
|
|
562
|
+
const { href } = new URL(dep, url);
|
|
563
|
+
uses.dependencies.add(href);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
598
566
|
|
|
599
|
-
|
|
600
|
-
|
|
567
|
+
/** @type {import('@sveltejs/kit').LoadEvent} */
|
|
568
|
+
const load_input = {
|
|
569
|
+
route: new Proxy(route, {
|
|
570
|
+
get: (target, key) => {
|
|
601
571
|
if (is_tracking) {
|
|
602
|
-
|
|
572
|
+
uses.route = true;
|
|
603
573
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
574
|
+
return target[/** @type {'id'} */ (key)];
|
|
575
|
+
}
|
|
576
|
+
}),
|
|
577
|
+
params: new Proxy(params, {
|
|
578
|
+
get: (target, key) => {
|
|
579
|
+
if (is_tracking) {
|
|
580
|
+
uses.params.add(/** @type {string} */ (key));
|
|
608
581
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
depends,
|
|
617
|
-
parent() {
|
|
582
|
+
return target[/** @type {string} */ (key)];
|
|
583
|
+
}
|
|
584
|
+
}),
|
|
585
|
+
data: server_data_node?.data ?? null,
|
|
586
|
+
url: make_trackable(
|
|
587
|
+
url,
|
|
588
|
+
() => {
|
|
618
589
|
if (is_tracking) {
|
|
619
|
-
uses.
|
|
590
|
+
uses.url = true;
|
|
620
591
|
}
|
|
621
|
-
return parent();
|
|
622
592
|
},
|
|
623
|
-
|
|
624
|
-
is_tracking
|
|
625
|
-
|
|
626
|
-
return fn();
|
|
627
|
-
} finally {
|
|
628
|
-
is_tracking = true;
|
|
593
|
+
(param) => {
|
|
594
|
+
if (is_tracking) {
|
|
595
|
+
uses.search_params.add(param);
|
|
629
596
|
}
|
|
630
597
|
}
|
|
631
|
-
|
|
598
|
+
),
|
|
599
|
+
async fetch(resource, init) {
|
|
600
|
+
/** @type {URL | string} */
|
|
601
|
+
let requested;
|
|
602
|
+
|
|
603
|
+
if (resource instanceof Request) {
|
|
604
|
+
requested = resource.url;
|
|
605
|
+
|
|
606
|
+
// we're not allowed to modify the received `Request` object, so in order
|
|
607
|
+
// to fixup relative urls we create a new equivalent `init` object instead
|
|
608
|
+
init = {
|
|
609
|
+
// the request body must be consumed in memory until browsers
|
|
610
|
+
// implement streaming request bodies and/or the body getter
|
|
611
|
+
body:
|
|
612
|
+
resource.method === 'GET' || resource.method === 'HEAD'
|
|
613
|
+
? undefined
|
|
614
|
+
: await resource.blob(),
|
|
615
|
+
cache: resource.cache,
|
|
616
|
+
credentials: resource.credentials,
|
|
617
|
+
headers: resource.headers,
|
|
618
|
+
integrity: resource.integrity,
|
|
619
|
+
keepalive: resource.keepalive,
|
|
620
|
+
method: resource.method,
|
|
621
|
+
mode: resource.mode,
|
|
622
|
+
redirect: resource.redirect,
|
|
623
|
+
referrer: resource.referrer,
|
|
624
|
+
referrerPolicy: resource.referrerPolicy,
|
|
625
|
+
signal: resource.signal,
|
|
626
|
+
...init
|
|
627
|
+
};
|
|
628
|
+
} else {
|
|
629
|
+
requested = resource;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// we must fixup relative urls so they are resolved from the target page
|
|
633
|
+
const resolved = new URL(requested, url);
|
|
634
|
+
if (is_tracking) {
|
|
635
|
+
depends(resolved.href);
|
|
636
|
+
}
|
|
632
637
|
|
|
633
|
-
|
|
638
|
+
// match ssr serialized data url, which is important to find cached responses
|
|
639
|
+
if (resolved.origin === url.origin) {
|
|
640
|
+
requested = resolved.href.slice(url.origin.length);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
|
|
644
|
+
return started
|
|
645
|
+
? subsequent_fetch(requested, resolved.href, init)
|
|
646
|
+
: initial_fetch(requested, init);
|
|
647
|
+
},
|
|
648
|
+
setHeaders: () => {}, // noop
|
|
649
|
+
depends,
|
|
650
|
+
parent() {
|
|
651
|
+
if (is_tracking) {
|
|
652
|
+
uses.parent = true;
|
|
653
|
+
}
|
|
654
|
+
return parent();
|
|
655
|
+
},
|
|
656
|
+
untrack(fn) {
|
|
657
|
+
is_tracking = false;
|
|
634
658
|
try {
|
|
635
|
-
|
|
636
|
-
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
637
|
-
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
|
|
638
|
-
throw new Error(
|
|
639
|
-
`a load function related to route '${route.id}' returned ${
|
|
640
|
-
typeof data !== 'object'
|
|
641
|
-
? `a ${typeof data}`
|
|
642
|
-
: data instanceof Response
|
|
643
|
-
? 'a Response object'
|
|
644
|
-
: Array.isArray(data)
|
|
645
|
-
? 'an array'
|
|
646
|
-
: 'a non-plain object'
|
|
647
|
-
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
|
648
|
-
);
|
|
649
|
-
}
|
|
659
|
+
return fn();
|
|
650
660
|
} finally {
|
|
651
|
-
|
|
661
|
+
is_tracking = true;
|
|
652
662
|
}
|
|
653
|
-
} else {
|
|
654
|
-
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
655
663
|
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return {
|
|
659
|
-
node,
|
|
660
|
-
loader,
|
|
661
|
-
server: server_data_node,
|
|
662
|
-
universal: node.universal?.load ? { type: 'data', data, uses } : null,
|
|
663
|
-
data: data ?? server_data_node?.data ?? null,
|
|
664
|
-
// if `paths.base === '/a/b/c`, then the root route is always `/a/b/c/`, regardless of
|
|
665
|
-
// the `trailingSlash` route option, so that relative paths to JS and CSS work
|
|
666
|
-
slash:
|
|
667
|
-
base && (url.pathname === base || url.pathname === base + '/')
|
|
668
|
-
? 'always'
|
|
669
|
-
: node.universal?.trailingSlash ?? server_data_node?.slash
|
|
670
664
|
};
|
|
671
|
-
}
|
|
672
665
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if (uses.url && url_changed) return true;
|
|
696
|
-
|
|
697
|
-
for (const tracked_params of uses.search_params) {
|
|
698
|
-
if (search_params_changed.has(tracked_params)) return true;
|
|
666
|
+
if (DEV) {
|
|
667
|
+
try {
|
|
668
|
+
lock_fetch();
|
|
669
|
+
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
670
|
+
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`a load function related to route '${route.id}' returned ${
|
|
673
|
+
typeof data !== 'object'
|
|
674
|
+
? `a ${typeof data}`
|
|
675
|
+
: data instanceof Response
|
|
676
|
+
? 'a Response object'
|
|
677
|
+
: Array.isArray(data)
|
|
678
|
+
? 'an array'
|
|
679
|
+
: 'a non-plain object'
|
|
680
|
+
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
} finally {
|
|
684
|
+
unlock_fetch();
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
699
688
|
}
|
|
689
|
+
}
|
|
700
690
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
691
|
+
return {
|
|
692
|
+
node,
|
|
693
|
+
loader,
|
|
694
|
+
server: server_data_node,
|
|
695
|
+
universal: node.universal?.load ? { type: 'data', data, uses } : null,
|
|
696
|
+
data: data ?? server_data_node?.data ?? null,
|
|
697
|
+
// if `paths.base === '/a/b/c`, then the root route is always `/a/b/c/`, regardless of
|
|
698
|
+
// the `trailingSlash` route option, so that relative paths to JS and CSS work
|
|
699
|
+
slash:
|
|
700
|
+
base && (url.pathname === base || url.pathname === base + '/')
|
|
701
|
+
? 'always'
|
|
702
|
+
: node.universal?.trailingSlash ?? server_data_node?.slash
|
|
703
|
+
};
|
|
704
|
+
}
|
|
704
705
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
706
|
+
/**
|
|
707
|
+
* @param {boolean} parent_changed
|
|
708
|
+
* @param {boolean} route_changed
|
|
709
|
+
* @param {boolean} url_changed
|
|
710
|
+
* @param {Set<string>} search_params_changed
|
|
711
|
+
* @param {import('types').Uses | undefined} uses
|
|
712
|
+
* @param {Record<string, string>} params
|
|
713
|
+
*/
|
|
714
|
+
function has_changed(
|
|
715
|
+
parent_changed,
|
|
716
|
+
route_changed,
|
|
717
|
+
url_changed,
|
|
718
|
+
search_params_changed,
|
|
719
|
+
uses,
|
|
720
|
+
params
|
|
721
|
+
) {
|
|
722
|
+
if (force_invalidation) return true;
|
|
723
|
+
|
|
724
|
+
if (!uses) return false;
|
|
725
|
+
|
|
726
|
+
if (uses.parent && parent_changed) return true;
|
|
727
|
+
if (uses.route && route_changed) return true;
|
|
728
|
+
if (uses.url && url_changed) return true;
|
|
729
|
+
|
|
730
|
+
for (const tracked_params of uses.search_params) {
|
|
731
|
+
if (search_params_changed.has(tracked_params)) return true;
|
|
732
|
+
}
|
|
708
733
|
|
|
709
|
-
|
|
734
|
+
for (const param of uses.params) {
|
|
735
|
+
if (params[param] !== current.params[param]) return true;
|
|
710
736
|
}
|
|
711
737
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
* @param {import('./types.js').DataNode | null} [previous]
|
|
715
|
-
* @returns {import('./types.js').DataNode | null}
|
|
716
|
-
*/
|
|
717
|
-
function create_data_node(node, previous) {
|
|
718
|
-
if (node?.type === 'data') return node;
|
|
719
|
-
if (node?.type === 'skip') return previous ?? null;
|
|
720
|
-
return null;
|
|
738
|
+
for (const href of uses.dependencies) {
|
|
739
|
+
if (invalidated.some((fn) => fn(new URL(href)))) return true;
|
|
721
740
|
}
|
|
722
741
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
* @param {URL | null} old_url
|
|
726
|
-
* @param {URL} new_url
|
|
727
|
-
*/
|
|
728
|
-
function diff_search_params(old_url, new_url) {
|
|
729
|
-
if (!old_url) return new Set(new_url.searchParams.keys());
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
730
744
|
|
|
731
|
-
|
|
745
|
+
/**
|
|
746
|
+
* @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | null} node
|
|
747
|
+
* @param {import('./types.js').DataNode | null} [previous]
|
|
748
|
+
* @returns {import('./types.js').DataNode | null}
|
|
749
|
+
*/
|
|
750
|
+
function create_data_node(node, previous) {
|
|
751
|
+
if (node?.type === 'data') return node;
|
|
752
|
+
if (node?.type === 'skip') return previous ?? null;
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
732
755
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
756
|
+
/**
|
|
757
|
+
*
|
|
758
|
+
* @param {URL | null} old_url
|
|
759
|
+
* @param {URL} new_url
|
|
760
|
+
*/
|
|
761
|
+
function diff_search_params(old_url, new_url) {
|
|
762
|
+
if (!old_url) return new Set(new_url.searchParams.keys());
|
|
736
763
|
|
|
737
|
-
|
|
738
|
-
old_values.every((value) => new_values.includes(value)) &&
|
|
739
|
-
new_values.every((value) => old_values.includes(value))
|
|
740
|
-
) {
|
|
741
|
-
changed.delete(key);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
764
|
+
const changed = new Set([...old_url.searchParams.keys(), ...new_url.searchParams.keys()]);
|
|
744
765
|
|
|
745
|
-
|
|
746
|
-
|
|
766
|
+
for (const key of changed) {
|
|
767
|
+
const old_values = old_url.searchParams.getAll(key);
|
|
768
|
+
const new_values = new_url.searchParams.getAll(key);
|
|
747
769
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (load_cache?.id === id) {
|
|
754
|
-
return load_cache.promise;
|
|
770
|
+
if (
|
|
771
|
+
old_values.every((value) => new_values.includes(value)) &&
|
|
772
|
+
new_values.every((value) => old_values.includes(value))
|
|
773
|
+
) {
|
|
774
|
+
changed.delete(key);
|
|
755
775
|
}
|
|
776
|
+
}
|
|
756
777
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const loaders = [...layouts, leaf];
|
|
760
|
-
|
|
761
|
-
// preload modules to avoid waterfall, but handle rejections
|
|
762
|
-
// so they don't get reported to Sentry et al (we don't need
|
|
763
|
-
// to act on the failures at this point)
|
|
764
|
-
errors.forEach((loader) => loader?.().catch(() => {}));
|
|
765
|
-
loaders.forEach((loader) => loader?.[1]().catch(() => {}));
|
|
766
|
-
|
|
767
|
-
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
768
|
-
let server_data = null;
|
|
769
|
-
const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
|
|
770
|
-
const route_changed = current.route ? route.id !== current.route.id : false;
|
|
771
|
-
const search_params_changed = diff_search_params(current.url, url);
|
|
772
|
-
|
|
773
|
-
let parent_invalid = false;
|
|
774
|
-
const invalid_server_nodes = loaders.map((loader, i) => {
|
|
775
|
-
const previous = current.branch[i];
|
|
776
|
-
|
|
777
|
-
const invalid =
|
|
778
|
-
!!loader?.[0] &&
|
|
779
|
-
(previous?.loader !== loader[1] ||
|
|
780
|
-
has_changed(
|
|
781
|
-
parent_invalid,
|
|
782
|
-
route_changed,
|
|
783
|
-
url_changed,
|
|
784
|
-
search_params_changed,
|
|
785
|
-
previous.server?.uses,
|
|
786
|
-
params
|
|
787
|
-
));
|
|
788
|
-
|
|
789
|
-
if (invalid) {
|
|
790
|
-
// For the next one
|
|
791
|
-
parent_invalid = true;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
return invalid;
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
if (invalid_server_nodes.some(Boolean)) {
|
|
798
|
-
try {
|
|
799
|
-
server_data = await load_data(url, invalid_server_nodes);
|
|
800
|
-
} catch (error) {
|
|
801
|
-
return load_root_error_page({
|
|
802
|
-
status: get_status(error),
|
|
803
|
-
error: await handle_error(error, { url, params, route: { id: route.id } }),
|
|
804
|
-
url,
|
|
805
|
-
route
|
|
806
|
-
});
|
|
807
|
-
}
|
|
778
|
+
return changed;
|
|
779
|
+
}
|
|
808
780
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
781
|
+
/**
|
|
782
|
+
* @param {import('./types.js').NavigationIntent} intent
|
|
783
|
+
* @returns {Promise<import('./types.js').NavigationResult>}
|
|
784
|
+
*/
|
|
785
|
+
async function load_route({ id, invalidating, url, params, route }) {
|
|
786
|
+
if (load_cache?.id === id) {
|
|
787
|
+
return load_cache.promise;
|
|
788
|
+
}
|
|
813
789
|
|
|
814
|
-
|
|
790
|
+
const { errors, layouts, leaf } = route;
|
|
815
791
|
|
|
816
|
-
|
|
792
|
+
const loaders = [...layouts, leaf];
|
|
817
793
|
|
|
818
|
-
|
|
819
|
-
|
|
794
|
+
// preload modules to avoid waterfall, but handle rejections
|
|
795
|
+
// so they don't get reported to Sentry et al (we don't need
|
|
796
|
+
// to act on the failures at this point)
|
|
797
|
+
errors.forEach((loader) => loader?.().catch(() => {}));
|
|
798
|
+
loaders.forEach((loader) => loader?.[1]().catch(() => {}));
|
|
820
799
|
|
|
821
|
-
|
|
822
|
-
|
|
800
|
+
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
801
|
+
let server_data = null;
|
|
802
|
+
const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
|
|
803
|
+
const route_changed = current.route ? route.id !== current.route.id : false;
|
|
804
|
+
const search_params_changed = diff_search_params(current.url, url);
|
|
823
805
|
|
|
824
|
-
|
|
806
|
+
let parent_invalid = false;
|
|
807
|
+
const invalid_server_nodes = loaders.map((loader, i) => {
|
|
808
|
+
const previous = current.branch[i];
|
|
825
809
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
parent_changed,
|
|
810
|
+
const invalid =
|
|
811
|
+
!!loader?.[0] &&
|
|
812
|
+
(previous?.loader !== loader[1] ||
|
|
813
|
+
has_changed(
|
|
814
|
+
parent_invalid,
|
|
832
815
|
route_changed,
|
|
833
816
|
url_changed,
|
|
834
817
|
search_params_changed,
|
|
835
|
-
previous.
|
|
818
|
+
previous.server?.uses,
|
|
836
819
|
params
|
|
837
|
-
);
|
|
838
|
-
if (valid) return previous;
|
|
820
|
+
));
|
|
839
821
|
|
|
840
|
-
|
|
822
|
+
if (invalid) {
|
|
823
|
+
// For the next one
|
|
824
|
+
parent_invalid = true;
|
|
825
|
+
}
|
|
841
826
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
throw server_data_node;
|
|
845
|
-
}
|
|
827
|
+
return invalid;
|
|
828
|
+
});
|
|
846
829
|
|
|
847
|
-
|
|
848
|
-
|
|
830
|
+
if (invalid_server_nodes.some(Boolean)) {
|
|
831
|
+
try {
|
|
832
|
+
server_data = await load_data(url, invalid_server_nodes);
|
|
833
|
+
} catch (error) {
|
|
834
|
+
return load_root_error_page({
|
|
835
|
+
status: get_status(error),
|
|
836
|
+
error: await handle_error(error, { url, params, route: { id: route.id } }),
|
|
849
837
|
url,
|
|
850
|
-
|
|
851
|
-
route,
|
|
852
|
-
parent: async () => {
|
|
853
|
-
const data = {};
|
|
854
|
-
for (let j = 0; j < i; j += 1) {
|
|
855
|
-
Object.assign(data, (await branch_promises[j])?.data);
|
|
856
|
-
}
|
|
857
|
-
return data;
|
|
858
|
-
},
|
|
859
|
-
server_data_node: create_data_node(
|
|
860
|
-
// server_data_node is undefined if it wasn't reloaded from the server;
|
|
861
|
-
// and if current loader uses server data, we want to reuse previous data.
|
|
862
|
-
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
|
|
863
|
-
loader[0] ? previous?.server : undefined
|
|
864
|
-
)
|
|
838
|
+
route
|
|
865
839
|
});
|
|
866
|
-
}
|
|
840
|
+
}
|
|
867
841
|
|
|
868
|
-
|
|
869
|
-
|
|
842
|
+
if (server_data.type === 'redirect') {
|
|
843
|
+
return server_data;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
870
846
|
|
|
871
|
-
|
|
872
|
-
const branch = [];
|
|
847
|
+
const server_data_nodes = server_data?.nodes;
|
|
873
848
|
|
|
874
|
-
|
|
875
|
-
if (loaders[i]) {
|
|
876
|
-
try {
|
|
877
|
-
branch.push(await branch_promises[i]);
|
|
878
|
-
} catch (err) {
|
|
879
|
-
if (err instanceof Redirect) {
|
|
880
|
-
return {
|
|
881
|
-
type: 'redirect',
|
|
882
|
-
location: err.location
|
|
883
|
-
};
|
|
884
|
-
}
|
|
849
|
+
let parent_changed = false;
|
|
885
850
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
let error;
|
|
889
|
-
|
|
890
|
-
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
891
|
-
// this is the server error rethrown above, reconstruct but don't invoke
|
|
892
|
-
// the client error handler; it should've already been handled on the server
|
|
893
|
-
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
894
|
-
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
895
|
-
} else if (err instanceof HttpError) {
|
|
896
|
-
error = err.body;
|
|
897
|
-
} else {
|
|
898
|
-
// Referenced node could have been removed due to redeploy, check
|
|
899
|
-
const updated = await stores.updated.check();
|
|
900
|
-
if (updated) {
|
|
901
|
-
return await native_navigation(url);
|
|
902
|
-
}
|
|
851
|
+
const branch_promises = loaders.map(async (loader, i) => {
|
|
852
|
+
if (!loader) return;
|
|
903
853
|
|
|
904
|
-
|
|
905
|
-
|
|
854
|
+
/** @type {import('./types.js').BranchNode | undefined} */
|
|
855
|
+
const previous = current.branch[i];
|
|
906
856
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
857
|
+
const server_data_node = server_data_nodes?.[i];
|
|
858
|
+
|
|
859
|
+
// re-use data from previous load if it's still valid
|
|
860
|
+
const valid =
|
|
861
|
+
(!server_data_node || server_data_node.type === 'skip') &&
|
|
862
|
+
loader[1] === previous?.loader &&
|
|
863
|
+
!has_changed(
|
|
864
|
+
parent_changed,
|
|
865
|
+
route_changed,
|
|
866
|
+
url_changed,
|
|
867
|
+
search_params_changed,
|
|
868
|
+
previous.universal?.uses,
|
|
869
|
+
params
|
|
870
|
+
);
|
|
871
|
+
if (valid) return previous;
|
|
872
|
+
|
|
873
|
+
parent_changed = true;
|
|
874
|
+
|
|
875
|
+
if (server_data_node?.type === 'error') {
|
|
876
|
+
// rethrow and catch below
|
|
877
|
+
throw server_data_node;
|
|
928
878
|
}
|
|
929
879
|
|
|
930
|
-
return
|
|
880
|
+
return load_node({
|
|
881
|
+
loader: loader[1],
|
|
931
882
|
url,
|
|
932
883
|
params,
|
|
933
|
-
branch,
|
|
934
|
-
status: 200,
|
|
935
|
-
error: null,
|
|
936
884
|
route,
|
|
937
|
-
|
|
938
|
-
|
|
885
|
+
parent: async () => {
|
|
886
|
+
const data = {};
|
|
887
|
+
for (let j = 0; j < i; j += 1) {
|
|
888
|
+
Object.assign(data, (await branch_promises[j])?.data);
|
|
889
|
+
}
|
|
890
|
+
return data;
|
|
891
|
+
},
|
|
892
|
+
server_data_node: create_data_node(
|
|
893
|
+
// server_data_node is undefined if it wasn't reloaded from the server;
|
|
894
|
+
// and if current loader uses server data, we want to reuse previous data.
|
|
895
|
+
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
|
|
896
|
+
loader[0] ? previous?.server : undefined
|
|
897
|
+
)
|
|
939
898
|
});
|
|
940
|
-
}
|
|
899
|
+
});
|
|
941
900
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
901
|
+
// if we don't do this, rejections will be unhandled
|
|
902
|
+
for (const p of branch_promises) p.catch(() => {});
|
|
903
|
+
|
|
904
|
+
/** @type {Array<import('./types.js').BranchNode | undefined>} */
|
|
905
|
+
const branch = [];
|
|
906
|
+
|
|
907
|
+
for (let i = 0; i < loaders.length; i += 1) {
|
|
908
|
+
if (loaders[i]) {
|
|
909
|
+
try {
|
|
910
|
+
branch.push(await branch_promises[i]);
|
|
911
|
+
} catch (err) {
|
|
912
|
+
if (err instanceof Redirect) {
|
|
954
913
|
return {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
|
|
958
|
-
loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
|
|
959
|
-
data: {},
|
|
960
|
-
server: null,
|
|
961
|
-
universal: null
|
|
962
|
-
}
|
|
914
|
+
type: 'redirect',
|
|
915
|
+
location: err.location
|
|
963
916
|
};
|
|
964
|
-
}
|
|
965
|
-
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
let status = get_status(err);
|
|
920
|
+
/** @type {App.Error} */
|
|
921
|
+
let error;
|
|
922
|
+
|
|
923
|
+
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
924
|
+
// this is the server error rethrown above, reconstruct but don't invoke
|
|
925
|
+
// the client error handler; it should've already been handled on the server
|
|
926
|
+
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
927
|
+
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
928
|
+
} else if (err instanceof HttpError) {
|
|
929
|
+
error = err.body;
|
|
930
|
+
} else {
|
|
931
|
+
// Referenced node could have been removed due to redeploy, check
|
|
932
|
+
const updated = await stores.updated.check();
|
|
933
|
+
if (updated) {
|
|
934
|
+
return await native_navigation(url);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
error = await handle_error(err, { params, url, route: { id: route.id } });
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const error_load = await load_nearest_error_page(i, branch, errors);
|
|
941
|
+
if (error_load) {
|
|
942
|
+
return await get_navigation_result_from_branch({
|
|
943
|
+
url,
|
|
944
|
+
params,
|
|
945
|
+
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
946
|
+
status,
|
|
947
|
+
error,
|
|
948
|
+
route
|
|
949
|
+
});
|
|
950
|
+
} else {
|
|
951
|
+
// if we get here, it's because the root `load` function failed,
|
|
952
|
+
// and we need to fall back to the server
|
|
953
|
+
return await server_fallback(url, { id: route.id }, error, status);
|
|
966
954
|
}
|
|
967
955
|
}
|
|
956
|
+
} else {
|
|
957
|
+
// push an empty slot so we can rewind past gaps to the
|
|
958
|
+
// layout that corresponds with an +error.svelte page
|
|
959
|
+
branch.push(undefined);
|
|
968
960
|
}
|
|
969
961
|
}
|
|
970
962
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
963
|
+
return await get_navigation_result_from_branch({
|
|
964
|
+
url,
|
|
965
|
+
params,
|
|
966
|
+
branch,
|
|
967
|
+
status: 200,
|
|
968
|
+
error: null,
|
|
969
|
+
route,
|
|
970
|
+
// Reset `form` on navigation, but not invalidation
|
|
971
|
+
form: invalidating ? undefined : null
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* @param {number} i Start index to backtrack from
|
|
977
|
+
* @param {Array<import('./types.js').BranchNode | undefined>} branch Branch to backtrack
|
|
978
|
+
* @param {Array<import('types').CSRPageNodeLoader | undefined>} errors All error pages for this branch
|
|
979
|
+
* @returns {Promise<{idx: number; node: import('./types.js').BranchNode} | undefined>}
|
|
980
|
+
*/
|
|
981
|
+
async function load_nearest_error_page(i, branch, errors) {
|
|
982
|
+
while (i--) {
|
|
983
|
+
if (errors[i]) {
|
|
984
|
+
let j = i;
|
|
985
|
+
while (!branch[j]) j -= 1;
|
|
986
|
+
try {
|
|
987
|
+
return {
|
|
988
|
+
idx: j + 1,
|
|
989
|
+
node: {
|
|
990
|
+
node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
|
|
991
|
+
loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
|
|
992
|
+
data: {},
|
|
993
|
+
server: null,
|
|
994
|
+
universal: null
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
} catch (e) {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
983
1003
|
|
|
984
|
-
|
|
985
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* @param {{
|
|
1006
|
+
* status: number;
|
|
1007
|
+
* error: App.Error;
|
|
1008
|
+
* url: URL;
|
|
1009
|
+
* route: { id: string | null }
|
|
1010
|
+
* }} opts
|
|
1011
|
+
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1012
|
+
*/
|
|
1013
|
+
async function load_root_error_page({ status, error, url, route }) {
|
|
1014
|
+
/** @type {Record<string, string>} */
|
|
1015
|
+
const params = {}; // error page does not have params
|
|
986
1016
|
|
|
987
|
-
|
|
1017
|
+
/** @type {import('types').ServerDataNode | null} */
|
|
1018
|
+
let server_data_node = null;
|
|
988
1019
|
|
|
989
|
-
|
|
990
|
-
// TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
|
|
991
|
-
// existing root layout data
|
|
992
|
-
try {
|
|
993
|
-
const server_data = await load_data(url, [true]);
|
|
1020
|
+
const default_layout_has_server_load = app.server_loads[0] === 0;
|
|
994
1021
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
}
|
|
1022
|
+
if (default_layout_has_server_load) {
|
|
1023
|
+
// TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
|
|
1024
|
+
// existing root layout data
|
|
1025
|
+
try {
|
|
1026
|
+
const server_data = await load_data(url, [true]);
|
|
1001
1027
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1028
|
+
if (
|
|
1029
|
+
server_data.type !== 'data' ||
|
|
1030
|
+
(server_data.nodes[0] && server_data.nodes[0].type !== 'data')
|
|
1031
|
+
) {
|
|
1032
|
+
throw 0;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
server_data_node = server_data.nodes[0] ?? null;
|
|
1036
|
+
} catch {
|
|
1037
|
+
// at this point we have no choice but to fall back to the server, if it wouldn't
|
|
1038
|
+
// bring us right back here, turning this into an endless loop
|
|
1039
|
+
if (url.origin !== origin || url.pathname !== location.pathname || hydrated) {
|
|
1040
|
+
await native_navigation(url);
|
|
1009
1041
|
}
|
|
1010
1042
|
}
|
|
1043
|
+
}
|
|
1011
1044
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1045
|
+
const root_layout = await load_node({
|
|
1046
|
+
loader: default_layout_loader,
|
|
1047
|
+
url,
|
|
1048
|
+
params,
|
|
1049
|
+
route,
|
|
1050
|
+
parent: () => Promise.resolve({}),
|
|
1051
|
+
server_data_node: create_data_node(server_data_node)
|
|
1052
|
+
});
|
|
1020
1053
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1054
|
+
/** @type {import('./types.js').BranchNode} */
|
|
1055
|
+
const root_error = {
|
|
1056
|
+
node: await default_error_loader(),
|
|
1057
|
+
loader: default_error_loader,
|
|
1058
|
+
universal: null,
|
|
1059
|
+
server: null,
|
|
1060
|
+
data: null
|
|
1061
|
+
};
|
|
1029
1062
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1063
|
+
return await get_navigation_result_from_branch({
|
|
1064
|
+
url,
|
|
1065
|
+
params,
|
|
1066
|
+
branch: [root_layout, root_error],
|
|
1067
|
+
status,
|
|
1068
|
+
error,
|
|
1069
|
+
route: null
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1039
1072
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1073
|
+
/**
|
|
1074
|
+
* @param {URL} url
|
|
1075
|
+
* @param {boolean} invalidating
|
|
1076
|
+
*/
|
|
1077
|
+
function get_navigation_intent(url, invalidating) {
|
|
1078
|
+
if (is_external_url(url, base)) return;
|
|
1046
1079
|
|
|
1047
|
-
|
|
1080
|
+
const path = get_url_path(url.pathname);
|
|
1048
1081
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1082
|
+
for (const route of routes) {
|
|
1083
|
+
const params = route.exec(path);
|
|
1051
1084
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1085
|
+
if (params) {
|
|
1086
|
+
const id = url.pathname + url.search;
|
|
1087
|
+
/** @type {import('./types.js').NavigationIntent} */
|
|
1088
|
+
const intent = { id, invalidating, route, params: decode_params(params), url };
|
|
1089
|
+
return intent;
|
|
1058
1090
|
}
|
|
1059
1091
|
}
|
|
1092
|
+
}
|
|
1060
1093
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1094
|
+
/** @param {string} pathname */
|
|
1095
|
+
function get_url_path(pathname) {
|
|
1096
|
+
return decode_pathname(pathname.slice(base.length) || '/');
|
|
1097
|
+
}
|
|
1065
1098
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1099
|
+
/**
|
|
1100
|
+
* @param {{
|
|
1101
|
+
* url: URL;
|
|
1102
|
+
* type: import('@sveltejs/kit').Navigation["type"];
|
|
1103
|
+
* intent?: import('./types.js').NavigationIntent;
|
|
1104
|
+
* delta?: number;
|
|
1105
|
+
* }} opts
|
|
1106
|
+
*/
|
|
1107
|
+
function _before_navigate({ url, type, intent, delta }) {
|
|
1108
|
+
let should_block = false;
|
|
1076
1109
|
|
|
1077
|
-
|
|
1110
|
+
const nav = create_navigation(current, intent, url, type);
|
|
1111
|
+
|
|
1112
|
+
if (delta !== undefined) {
|
|
1113
|
+
nav.navigation.delta = delta;
|
|
1114
|
+
}
|
|
1078
1115
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1116
|
+
const cancellable = {
|
|
1117
|
+
...nav.navigation,
|
|
1118
|
+
cancel: () => {
|
|
1119
|
+
should_block = true;
|
|
1120
|
+
nav.reject(new Error('navigation was cancelled'));
|
|
1081
1121
|
}
|
|
1122
|
+
};
|
|
1082
1123
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
nav.reject(new Error('navigation was cancelled'));
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1124
|
+
if (!navigating) {
|
|
1125
|
+
// Don't run the event during redirects
|
|
1126
|
+
before_navigate_callbacks.forEach((fn) => fn(cancellable));
|
|
1127
|
+
}
|
|
1090
1128
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
callbacks.before_navigate.forEach((fn) => fn(cancellable));
|
|
1094
|
-
}
|
|
1129
|
+
return should_block ? null : nav;
|
|
1130
|
+
}
|
|
1095
1131
|
|
|
1096
|
-
|
|
1132
|
+
/**
|
|
1133
|
+
* @param {{
|
|
1134
|
+
* type: import('@sveltejs/kit').Navigation["type"];
|
|
1135
|
+
* url: URL;
|
|
1136
|
+
* popped?: {
|
|
1137
|
+
* state: Record<string, any>;
|
|
1138
|
+
* scroll: { x: number, y: number };
|
|
1139
|
+
* delta: number;
|
|
1140
|
+
* };
|
|
1141
|
+
* keepfocus?: boolean;
|
|
1142
|
+
* noscroll?: boolean;
|
|
1143
|
+
* replace_state?: boolean;
|
|
1144
|
+
* state?: Record<string, any>;
|
|
1145
|
+
* redirect_count?: number;
|
|
1146
|
+
* nav_token?: {};
|
|
1147
|
+
* accept?: () => void;
|
|
1148
|
+
* block?: () => void;
|
|
1149
|
+
* }} opts
|
|
1150
|
+
*/
|
|
1151
|
+
async function navigate({
|
|
1152
|
+
type,
|
|
1153
|
+
url,
|
|
1154
|
+
popped,
|
|
1155
|
+
keepfocus,
|
|
1156
|
+
noscroll,
|
|
1157
|
+
replace_state,
|
|
1158
|
+
state = {},
|
|
1159
|
+
redirect_count = 0,
|
|
1160
|
+
nav_token = {},
|
|
1161
|
+
accept = noop,
|
|
1162
|
+
block = noop
|
|
1163
|
+
}) {
|
|
1164
|
+
const intent = get_navigation_intent(url, false);
|
|
1165
|
+
const nav = _before_navigate({ url, type, delta: popped?.delta, intent });
|
|
1166
|
+
|
|
1167
|
+
if (!nav) {
|
|
1168
|
+
block();
|
|
1169
|
+
return;
|
|
1097
1170
|
}
|
|
1098
1171
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
* state: Record<string, any>;
|
|
1105
|
-
* scroll: { x: number, y: number };
|
|
1106
|
-
* delta: number;
|
|
1107
|
-
* };
|
|
1108
|
-
* keepfocus?: boolean;
|
|
1109
|
-
* noscroll?: boolean;
|
|
1110
|
-
* replace_state?: boolean;
|
|
1111
|
-
* state?: Record<string, any>;
|
|
1112
|
-
* redirect_count?: number;
|
|
1113
|
-
* nav_token?: {};
|
|
1114
|
-
* accept?: () => void;
|
|
1115
|
-
* block?: () => void;
|
|
1116
|
-
* }} opts
|
|
1117
|
-
*/
|
|
1118
|
-
async function navigate({
|
|
1119
|
-
type,
|
|
1120
|
-
url,
|
|
1121
|
-
popped,
|
|
1122
|
-
keepfocus,
|
|
1123
|
-
noscroll,
|
|
1124
|
-
replace_state,
|
|
1125
|
-
state = {},
|
|
1126
|
-
redirect_count = 0,
|
|
1127
|
-
nav_token = {},
|
|
1128
|
-
accept = noop,
|
|
1129
|
-
block = noop
|
|
1130
|
-
}) {
|
|
1131
|
-
const intent = get_navigation_intent(url, false);
|
|
1132
|
-
const nav = before_navigate({ url, type, delta: popped?.delta, intent });
|
|
1133
|
-
|
|
1134
|
-
if (!nav) {
|
|
1135
|
-
block();
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1172
|
+
// store this before calling `accept()`, which may change the index
|
|
1173
|
+
const previous_history_index = current_history_index;
|
|
1174
|
+
const previous_navigation_index = current_navigation_index;
|
|
1175
|
+
|
|
1176
|
+
accept();
|
|
1138
1177
|
|
|
1139
|
-
|
|
1140
|
-
const previous_history_index = current_history_index;
|
|
1141
|
-
const previous_navigation_index = current_navigation_index;
|
|
1178
|
+
navigating = true;
|
|
1142
1179
|
|
|
1143
|
-
|
|
1180
|
+
if (started) {
|
|
1181
|
+
stores.navigating.set(nav.navigation);
|
|
1182
|
+
}
|
|
1144
1183
|
|
|
1145
|
-
|
|
1184
|
+
token = nav_token;
|
|
1185
|
+
let navigation_result = intent && (await load_route(intent));
|
|
1146
1186
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1187
|
+
if (!navigation_result) {
|
|
1188
|
+
if (is_external_url(url, base)) {
|
|
1189
|
+
return await native_navigation(url);
|
|
1149
1190
|
}
|
|
1191
|
+
navigation_result = await server_fallback(
|
|
1192
|
+
url,
|
|
1193
|
+
{ id: null },
|
|
1194
|
+
await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
|
|
1195
|
+
url,
|
|
1196
|
+
params: {},
|
|
1197
|
+
route: { id: null }
|
|
1198
|
+
}),
|
|
1199
|
+
404
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1150
1202
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1203
|
+
// if this is an internal navigation intent, use the normalized
|
|
1204
|
+
// URL for the rest of the function
|
|
1205
|
+
url = intent?.url || url;
|
|
1153
1206
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1207
|
+
// abort if user navigated during update
|
|
1208
|
+
if (token !== nav_token) {
|
|
1209
|
+
nav.reject(new Error('navigation was aborted'));
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (navigation_result.type === 'redirect') {
|
|
1214
|
+
// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
|
|
1215
|
+
if (redirect_count >= 20) {
|
|
1216
|
+
navigation_result = await load_root_error_page({
|
|
1217
|
+
status: 500,
|
|
1218
|
+
error: await handle_error(new Error('Redirect loop'), {
|
|
1162
1219
|
url,
|
|
1163
1220
|
params: {},
|
|
1164
1221
|
route: { id: null }
|
|
1165
1222
|
}),
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
// URL for the rest of the function
|
|
1172
|
-
url = intent?.url || url;
|
|
1173
|
-
|
|
1174
|
-
// abort if user navigated during update
|
|
1175
|
-
if (token !== nav_token) {
|
|
1176
|
-
nav.reject(new Error('navigation was aborted'));
|
|
1223
|
+
url,
|
|
1224
|
+
route: { id: null }
|
|
1225
|
+
});
|
|
1226
|
+
} else {
|
|
1227
|
+
_goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
|
|
1177
1228
|
return false;
|
|
1178
1229
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
navigation_result = await load_root_error_page({
|
|
1184
|
-
status: 500,
|
|
1185
|
-
error: await handle_error(new Error('Redirect loop'), {
|
|
1186
|
-
url,
|
|
1187
|
-
params: {},
|
|
1188
|
-
route: { id: null }
|
|
1189
|
-
}),
|
|
1190
|
-
url,
|
|
1191
|
-
route: { id: null }
|
|
1192
|
-
});
|
|
1193
|
-
} else {
|
|
1194
|
-
goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
|
|
1195
|
-
return false;
|
|
1196
|
-
}
|
|
1197
|
-
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1198
|
-
const updated = await stores.updated.check();
|
|
1199
|
-
if (updated) {
|
|
1200
|
-
await native_navigation(url);
|
|
1201
|
-
}
|
|
1230
|
+
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1231
|
+
const updated = await stores.updated.check();
|
|
1232
|
+
if (updated) {
|
|
1233
|
+
await native_navigation(url);
|
|
1202
1234
|
}
|
|
1235
|
+
}
|
|
1203
1236
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1237
|
+
// reset invalidation only after a finished navigation. If there are redirects or
|
|
1238
|
+
// additional invalidations, they should get the same invalidation treatment
|
|
1239
|
+
invalidated.length = 0;
|
|
1240
|
+
force_invalidation = false;
|
|
1208
1241
|
|
|
1209
|
-
|
|
1242
|
+
updating = true;
|
|
1210
1243
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1244
|
+
update_scroll_positions(previous_history_index);
|
|
1245
|
+
capture_snapshot(previous_navigation_index);
|
|
1213
1246
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1247
|
+
// ensure the url pathname matches the page's trailing slash option
|
|
1248
|
+
if (navigation_result.props.page.url.pathname !== url.pathname) {
|
|
1249
|
+
url.pathname = navigation_result.props.page.url.pathname;
|
|
1250
|
+
}
|
|
1218
1251
|
|
|
1219
|
-
|
|
1252
|
+
state = popped ? popped.state : state;
|
|
1220
1253
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1254
|
+
if (!popped) {
|
|
1255
|
+
// this is a new navigation, rather than a popstate
|
|
1256
|
+
const change = replace_state ? 0 : 1;
|
|
1224
1257
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1258
|
+
const entry = {
|
|
1259
|
+
[HISTORY_INDEX]: (current_history_index += change),
|
|
1260
|
+
[NAVIGATION_INDEX]: (current_navigation_index += change),
|
|
1261
|
+
[STATES_KEY]: state
|
|
1262
|
+
};
|
|
1229
1263
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1264
|
+
const fn = replace_state ? original_replace_state : original_push_state;
|
|
1265
|
+
fn.call(history, entry, '', url);
|
|
1232
1266
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1267
|
+
if (!replace_state) {
|
|
1268
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
1236
1269
|
}
|
|
1270
|
+
}
|
|
1237
1271
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
// reset preload synchronously after the history state has been set to avoid race conditions
|
|
1241
|
-
load_cache = null;
|
|
1272
|
+
// reset preload synchronously after the history state has been set to avoid race conditions
|
|
1273
|
+
load_cache = null;
|
|
1242
1274
|
|
|
1243
|
-
|
|
1275
|
+
navigation_result.props.page.state = state;
|
|
1244
1276
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1277
|
+
if (started) {
|
|
1278
|
+
current = navigation_result.state;
|
|
1247
1279
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1280
|
+
// reset url before updating page store
|
|
1281
|
+
if (navigation_result.props.page) {
|
|
1282
|
+
navigation_result.props.page.url = url;
|
|
1283
|
+
}
|
|
1252
1284
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
)
|
|
1285
|
+
const after_navigate = (
|
|
1286
|
+
await Promise.all(
|
|
1287
|
+
on_navigate_callbacks.map((fn) =>
|
|
1288
|
+
fn(/** @type {import('@sveltejs/kit').OnNavigate} */ (nav.navigation))
|
|
1258
1289
|
)
|
|
1259
|
-
)
|
|
1260
|
-
|
|
1261
|
-
if (after_navigate.length > 0) {
|
|
1262
|
-
function cleanup() {
|
|
1263
|
-
callbacks.after_navigate = callbacks.after_navigate.filter(
|
|
1264
|
-
// @ts-ignore
|
|
1265
|
-
(fn) => !after_navigate.includes(fn)
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
after_navigate.push(cleanup);
|
|
1290
|
+
)
|
|
1291
|
+
).filter((value) => typeof value === 'function');
|
|
1270
1292
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1293
|
+
if (after_navigate.length > 0) {
|
|
1294
|
+
function cleanup() {
|
|
1295
|
+
after_navigate_callbacks = after_navigate_callbacks.filter(
|
|
1296
|
+
// @ts-ignore
|
|
1297
|
+
(fn) => !after_navigate.includes(fn)
|
|
1298
|
+
);
|
|
1273
1299
|
}
|
|
1274
1300
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1301
|
+
after_navigate.push(cleanup);
|
|
1302
|
+
|
|
1303
|
+
// @ts-ignore
|
|
1304
|
+
callbacks.after_navigate.push(...after_navigate);
|
|
1279
1305
|
}
|
|
1280
1306
|
|
|
1281
|
-
|
|
1307
|
+
root.$set(navigation_result.props);
|
|
1308
|
+
has_navigated = true;
|
|
1309
|
+
} else {
|
|
1310
|
+
initialize(navigation_result, target);
|
|
1311
|
+
}
|
|
1282
1312
|
|
|
1283
|
-
|
|
1284
|
-
await tick();
|
|
1313
|
+
const { activeElement } = document;
|
|
1285
1314
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (autoscroll) {
|
|
1290
|
-
const deep_linked =
|
|
1291
|
-
url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
|
|
1292
|
-
if (scroll) {
|
|
1293
|
-
scrollTo(scroll.x, scroll.y);
|
|
1294
|
-
} else if (deep_linked) {
|
|
1295
|
-
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
1296
|
-
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
1297
|
-
// CSS properties.
|
|
1298
|
-
deep_linked.scrollIntoView();
|
|
1299
|
-
} else {
|
|
1300
|
-
scrollTo(0, 0);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1315
|
+
// need to render the DOM before we can scroll to the rendered elements and do focus management
|
|
1316
|
+
await tick();
|
|
1303
1317
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
document.activeElement !== activeElement &&
|
|
1307
|
-
// also refocus when activeElement is body already because the
|
|
1308
|
-
// focus event might not have been fired on it yet
|
|
1309
|
-
document.activeElement !== document.body;
|
|
1318
|
+
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
|
|
1319
|
+
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
|
|
1310
1320
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1321
|
+
if (autoscroll) {
|
|
1322
|
+
const deep_linked = url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
|
|
1323
|
+
if (scroll) {
|
|
1324
|
+
scrollTo(scroll.x, scroll.y);
|
|
1325
|
+
} else if (deep_linked) {
|
|
1326
|
+
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
1327
|
+
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
1328
|
+
// CSS properties.
|
|
1329
|
+
deep_linked.scrollIntoView();
|
|
1330
|
+
} else {
|
|
1331
|
+
scrollTo(0, 0);
|
|
1313
1332
|
}
|
|
1333
|
+
}
|
|
1314
1334
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1335
|
+
const changed_focus =
|
|
1336
|
+
// reset focus only if any manual focus management didn't override it
|
|
1337
|
+
document.activeElement !== activeElement &&
|
|
1338
|
+
// also refocus when activeElement is body already because the
|
|
1339
|
+
// focus event might not have been fired on it yet
|
|
1340
|
+
document.activeElement !== document.body;
|
|
1320
1341
|
|
|
1321
|
-
|
|
1342
|
+
if (!keepfocus && !changed_focus) {
|
|
1343
|
+
reset_focus();
|
|
1344
|
+
}
|
|
1322
1345
|
|
|
1323
|
-
|
|
1324
|
-
restore_snapshot(current_navigation_index);
|
|
1325
|
-
}
|
|
1346
|
+
autoscroll = true;
|
|
1326
1347
|
|
|
1327
|
-
|
|
1348
|
+
if (navigation_result.props.page) {
|
|
1349
|
+
page = navigation_result.props.page;
|
|
1350
|
+
}
|
|
1328
1351
|
|
|
1329
|
-
|
|
1330
|
-
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
1331
|
-
);
|
|
1332
|
-
stores.navigating.set(null);
|
|
1352
|
+
navigating = false;
|
|
1333
1353
|
|
|
1334
|
-
|
|
1354
|
+
if (type === 'popstate') {
|
|
1355
|
+
restore_snapshot(current_navigation_index);
|
|
1335
1356
|
}
|
|
1336
1357
|
|
|
1337
|
-
|
|
1338
|
-
* Does a full page reload if it wouldn't result in an endless loop in the SPA case
|
|
1339
|
-
* @param {URL} url
|
|
1340
|
-
* @param {{ id: string | null }} route
|
|
1341
|
-
* @param {App.Error} error
|
|
1342
|
-
* @param {number} status
|
|
1343
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1344
|
-
*/
|
|
1345
|
-
async function server_fallback(url, route, error, status) {
|
|
1346
|
-
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
1347
|
-
// We would reload the same page we're currently on, which isn't hydrated,
|
|
1348
|
-
// which means no SSR, which means we would end up in an endless loop
|
|
1349
|
-
return await load_root_error_page({
|
|
1350
|
-
status,
|
|
1351
|
-
error,
|
|
1352
|
-
url,
|
|
1353
|
-
route
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1358
|
+
nav.fulfil(undefined);
|
|
1356
1359
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
);
|
|
1360
|
+
after_navigate_callbacks.forEach((fn) =>
|
|
1361
|
+
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
1362
|
+
);
|
|
1361
1363
|
|
|
1362
|
-
|
|
1363
|
-
}
|
|
1364
|
+
stores.navigating.set(null);
|
|
1364
1365
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1366
|
+
updating = false;
|
|
1367
|
+
}
|
|
1367
1368
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1369
|
+
/**
|
|
1370
|
+
* Does a full page reload if it wouldn't result in an endless loop in the SPA case
|
|
1371
|
+
* @param {URL} url
|
|
1372
|
+
* @param {{ id: string | null }} route
|
|
1373
|
+
* @param {App.Error} error
|
|
1374
|
+
* @param {number} status
|
|
1375
|
+
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1376
|
+
*/
|
|
1377
|
+
async function server_fallback(url, route, error, status) {
|
|
1378
|
+
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
1379
|
+
// We would reload the same page we're currently on, which isn't hydrated,
|
|
1380
|
+
// which means no SSR, which means we would end up in an endless loop
|
|
1381
|
+
return await load_root_error_page({
|
|
1382
|
+
status,
|
|
1383
|
+
error,
|
|
1384
|
+
url,
|
|
1385
|
+
route
|
|
1371
1386
|
});
|
|
1372
1387
|
}
|
|
1373
1388
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1389
|
+
if (DEV && status !== 404) {
|
|
1390
|
+
console.error(
|
|
1391
|
+
'An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)'
|
|
1392
|
+
);
|
|
1377
1393
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1394
|
+
debugger; // eslint-disable-line
|
|
1395
|
+
}
|
|
1380
1396
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
preload(target, 2);
|
|
1384
|
-
}, 20);
|
|
1385
|
-
});
|
|
1397
|
+
return await native_navigation(url);
|
|
1398
|
+
}
|
|
1386
1399
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1400
|
+
if (import.meta.hot) {
|
|
1401
|
+
import.meta.hot.on('vite:beforeUpdate', () => {
|
|
1402
|
+
if (current.error) location.reload();
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1391
1405
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1406
|
+
function setup_preload() {
|
|
1407
|
+
/** @type {NodeJS.Timeout} */
|
|
1408
|
+
let mousemove_timeout;
|
|
1394
1409
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
for (const entry of entries) {
|
|
1398
|
-
if (entry.isIntersecting) {
|
|
1399
|
-
preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
|
|
1400
|
-
observer.unobserve(entry.target);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
},
|
|
1404
|
-
{ threshold: 0 }
|
|
1405
|
-
);
|
|
1410
|
+
container.addEventListener('mousemove', (event) => {
|
|
1411
|
+
const target = /** @type {Element} */ (event.target);
|
|
1406
1412
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
const a = find_anchor(element, container);
|
|
1413
|
-
if (!a) return;
|
|
1413
|
+
clearTimeout(mousemove_timeout);
|
|
1414
|
+
mousemove_timeout = setTimeout(() => {
|
|
1415
|
+
preload(target, 2);
|
|
1416
|
+
}, 20);
|
|
1417
|
+
});
|
|
1414
1418
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1419
|
+
/** @param {Event} event */
|
|
1420
|
+
function tap(event) {
|
|
1421
|
+
preload(/** @type {Element} */ (event.composedPath()[0]), 1);
|
|
1422
|
+
}
|
|
1417
1423
|
|
|
1418
|
-
|
|
1424
|
+
container.addEventListener('mousedown', tap);
|
|
1425
|
+
container.addEventListener('touchstart', tap, { passive: true });
|
|
1419
1426
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1427
|
+
const observer = new IntersectionObserver(
|
|
1428
|
+
(entries) => {
|
|
1429
|
+
for (const entry of entries) {
|
|
1430
|
+
if (entry.isIntersecting) {
|
|
1431
|
+
_preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
|
|
1432
|
+
observer.unobserve(entry.target);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1436
|
+
{ threshold: 0 }
|
|
1437
|
+
);
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* @param {Element} element
|
|
1441
|
+
* @param {number} priority
|
|
1442
|
+
*/
|
|
1443
|
+
function preload(element, priority) {
|
|
1444
|
+
const a = find_anchor(element, container);
|
|
1445
|
+
if (!a) return;
|
|
1446
|
+
|
|
1447
|
+
const { url, external, download } = get_link_info(a, base);
|
|
1448
|
+
if (external || download) return;
|
|
1449
|
+
|
|
1450
|
+
const options = get_router_options(a);
|
|
1451
|
+
|
|
1452
|
+
if (!options.reload) {
|
|
1453
|
+
if (priority <= options.preload_data) {
|
|
1454
|
+
const intent = get_navigation_intent(/** @type {URL} */ (url), false);
|
|
1455
|
+
if (intent) {
|
|
1456
|
+
if (DEV) {
|
|
1457
|
+
_preload_data(intent).then((result) => {
|
|
1458
|
+
if (result.type === 'loaded' && result.state.error) {
|
|
1459
|
+
console.warn(
|
|
1460
|
+
`Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
|
|
1461
|
+
'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
|
|
1462
|
+
'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
|
|
1463
|
+
'See https://kit.svelte.dev/docs/link-options for more info'
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
} else {
|
|
1468
|
+
_preload_data(intent);
|
|
1438
1469
|
}
|
|
1439
|
-
} else if (priority <= options.preload_code) {
|
|
1440
|
-
preload_code(/** @type {URL} */ (url).pathname);
|
|
1441
1470
|
}
|
|
1471
|
+
} else if (priority <= options.preload_code) {
|
|
1472
|
+
_preload_code(/** @type {URL} */ (url).pathname);
|
|
1442
1473
|
}
|
|
1443
1474
|
}
|
|
1475
|
+
}
|
|
1444
1476
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1477
|
+
function after_navigate() {
|
|
1478
|
+
observer.disconnect();
|
|
1447
1479
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1480
|
+
for (const a of container.querySelectorAll('a')) {
|
|
1481
|
+
const { url, external, download } = get_link_info(a, base);
|
|
1482
|
+
if (external || download) continue;
|
|
1451
1483
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1484
|
+
const options = get_router_options(a);
|
|
1485
|
+
if (options.reload) continue;
|
|
1454
1486
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1487
|
+
if (options.preload_code === PRELOAD_PRIORITIES.viewport) {
|
|
1488
|
+
observer.observe(a);
|
|
1489
|
+
}
|
|
1458
1490
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
}
|
|
1491
|
+
if (options.preload_code === PRELOAD_PRIORITIES.eager) {
|
|
1492
|
+
_preload_code(/** @type {URL} */ (url).pathname);
|
|
1462
1493
|
}
|
|
1463
1494
|
}
|
|
1464
|
-
|
|
1465
|
-
callbacks.after_navigate.push(after_navigate);
|
|
1466
|
-
after_navigate();
|
|
1467
1495
|
}
|
|
1468
1496
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
* @returns {import('types').MaybePromise<App.Error>}
|
|
1473
|
-
*/
|
|
1474
|
-
function handle_error(error, event) {
|
|
1475
|
-
if (error instanceof HttpError) {
|
|
1476
|
-
return error.body;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
if (DEV) {
|
|
1480
|
-
errored = true;
|
|
1481
|
-
console.warn('The next HMR update will cause the page to reload');
|
|
1482
|
-
}
|
|
1497
|
+
after_navigate_callbacks.push(after_navigate);
|
|
1498
|
+
after_navigate();
|
|
1499
|
+
}
|
|
1483
1500
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1501
|
+
/**
|
|
1502
|
+
* @param {unknown} error
|
|
1503
|
+
* @param {import('@sveltejs/kit').NavigationEvent} event
|
|
1504
|
+
* @returns {import('types').MaybePromise<App.Error>}
|
|
1505
|
+
*/
|
|
1506
|
+
function handle_error(error, event) {
|
|
1507
|
+
if (error instanceof HttpError) {
|
|
1508
|
+
return error.body;
|
|
1509
|
+
}
|
|
1486
1510
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
);
|
|
1511
|
+
if (DEV) {
|
|
1512
|
+
errored = true;
|
|
1513
|
+
console.warn('The next HMR update will cause the page to reload');
|
|
1490
1514
|
}
|
|
1491
1515
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
onMount(() => {
|
|
1495
|
-
callbacks.after_navigate.push(fn);
|
|
1516
|
+
const status = get_status(error);
|
|
1517
|
+
const message = get_message(error);
|
|
1496
1518
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
});
|
|
1502
|
-
},
|
|
1519
|
+
return (
|
|
1520
|
+
app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1503
1523
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1524
|
+
/**
|
|
1525
|
+
* @template {Function} T
|
|
1526
|
+
* @param {T[]} callbacks
|
|
1527
|
+
* @param {T} callback
|
|
1528
|
+
*/
|
|
1529
|
+
function add_navigation_callback(callbacks, callback) {
|
|
1530
|
+
onMount(() => {
|
|
1531
|
+
callbacks.push(callback);
|
|
1507
1532
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1533
|
+
return () => {
|
|
1534
|
+
const i = callbacks.indexOf(callback);
|
|
1535
|
+
callbacks.splice(i, 1);
|
|
1536
|
+
};
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1514
1539
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1540
|
+
/**
|
|
1541
|
+
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
|
|
1542
|
+
*
|
|
1543
|
+
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
1544
|
+
* @param {(navigation: import('@sveltejs/kit').AfterNavigate) => void} callback
|
|
1545
|
+
* @returns {void}
|
|
1546
|
+
*/
|
|
1547
|
+
export function afterNavigate(callback) {
|
|
1548
|
+
add_navigation_callback(after_navigate_callbacks, callback);
|
|
1549
|
+
}
|
|
1518
1550
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1551
|
+
/**
|
|
1552
|
+
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
|
|
1553
|
+
*
|
|
1554
|
+
* Calling `cancel()` will prevent the navigation from completing. If `navigation.type === 'leave'` — meaning the user is navigating away from the app (or closing the tab) — calling `cancel` will trigger the native browser unload confirmation dialog. In this case, the navigation may or may not be cancelled depending on the user's response.
|
|
1555
|
+
*
|
|
1556
|
+
* When a navigation isn't to a SvelteKit-owned route (and therefore controlled by SvelteKit's client-side router), `navigation.to.route.id` will be `null`.
|
|
1557
|
+
*
|
|
1558
|
+
* If the navigation will (if not cancelled) cause the document to unload — in other words `'leave'` navigations and `'link'` navigations where `navigation.to.route === null` — `navigation.willUnload` is `true`.
|
|
1559
|
+
*
|
|
1560
|
+
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
1561
|
+
* @param {(navigation: import('@sveltejs/kit').BeforeNavigate) => void} callback
|
|
1562
|
+
* @returns {void}
|
|
1563
|
+
*/
|
|
1564
|
+
export function beforeNavigate(callback) {
|
|
1565
|
+
add_navigation_callback(before_navigate_callbacks, callback);
|
|
1566
|
+
}
|
|
1525
1567
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1568
|
+
/**
|
|
1569
|
+
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL except during full-page navigations.
|
|
1570
|
+
*
|
|
1571
|
+
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
|
|
1572
|
+
*
|
|
1573
|
+
* If a function (or a `Promise` that resolves to a function) is returned from the callback, it will be called once the DOM has updated.
|
|
1574
|
+
*
|
|
1575
|
+
* `onNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
1576
|
+
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => import('types').MaybePromise<void>} callback
|
|
1577
|
+
* @returns {void}
|
|
1578
|
+
*/
|
|
1579
|
+
export function onNavigate(callback) {
|
|
1580
|
+
add_navigation_callback(on_navigate_callbacks, callback);
|
|
1581
|
+
}
|
|
1530
1582
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1583
|
+
/**
|
|
1584
|
+
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
|
|
1585
|
+
* This is generally discouraged, since it breaks user expectations.
|
|
1586
|
+
* @returns {void}
|
|
1587
|
+
*/
|
|
1588
|
+
export function disableScrollHandling() {
|
|
1589
|
+
if (!BROWSER) {
|
|
1590
|
+
throw new Error('Cannot call disableScrollHandling() on the server');
|
|
1591
|
+
}
|
|
1535
1592
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1593
|
+
if (DEV && started && !updating) {
|
|
1594
|
+
throw new Error('Can only disable scroll handling during navigation');
|
|
1595
|
+
}
|
|
1538
1596
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
1544
|
-
: 'goto: invalid URL'
|
|
1545
|
-
)
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1597
|
+
if (updating || !started) {
|
|
1598
|
+
autoscroll = false;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1548
1601
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1602
|
+
/**
|
|
1603
|
+
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
|
1604
|
+
* For external URLs, use `window.location = url` instead of calling `goto(url)`.
|
|
1605
|
+
*
|
|
1606
|
+
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
|
|
1607
|
+
* @param {Object} [opts] Options related to the navigation
|
|
1608
|
+
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
1609
|
+
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
|
1610
|
+
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
|
1611
|
+
* @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation.
|
|
1612
|
+
* @param {App.PageState} [opts.state] An optional object that will be available on the `$page.state` store
|
|
1613
|
+
* @returns {Promise<void>}
|
|
1614
|
+
*/
|
|
1615
|
+
export function goto(url, opts = {}) {
|
|
1616
|
+
if (!BROWSER) {
|
|
1617
|
+
throw new Error('Cannot call goto(...) on the server');
|
|
1618
|
+
}
|
|
1551
1619
|
|
|
1552
|
-
|
|
1553
|
-
if (typeof resource === 'function') {
|
|
1554
|
-
invalidated.push(resource);
|
|
1555
|
-
} else {
|
|
1556
|
-
const { href } = new URL(resource, location.href);
|
|
1557
|
-
invalidated.push((url) => url.href === href);
|
|
1558
|
-
}
|
|
1620
|
+
url = resolve_url(url);
|
|
1559
1621
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1622
|
+
if (url.origin !== origin) {
|
|
1623
|
+
return Promise.reject(
|
|
1624
|
+
new Error(
|
|
1625
|
+
DEV
|
|
1626
|
+
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
1627
|
+
: 'goto: invalid URL'
|
|
1628
|
+
)
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1562
1631
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
return invalidate();
|
|
1566
|
-
},
|
|
1632
|
+
return _goto(url, opts, 0);
|
|
1633
|
+
}
|
|
1567
1634
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1635
|
+
/**
|
|
1636
|
+
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated.
|
|
1637
|
+
*
|
|
1638
|
+
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters).
|
|
1639
|
+
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL.
|
|
1640
|
+
*
|
|
1641
|
+
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned.
|
|
1642
|
+
* This can be useful if you want to invalidate based on a pattern instead of a exact match.
|
|
1643
|
+
*
|
|
1644
|
+
* ```ts
|
|
1645
|
+
* // Example: Match '/path' regardless of the query parameters
|
|
1646
|
+
* import { invalidate } from '$app/navigation';
|
|
1647
|
+
*
|
|
1648
|
+
* invalidate((url) => url.pathname === '/path');
|
|
1649
|
+
* ```
|
|
1650
|
+
* @param {string | URL | ((url: URL) => boolean)} resource The invalidated URL
|
|
1651
|
+
* @returns {Promise<void>}
|
|
1652
|
+
*/
|
|
1653
|
+
export function invalidate(resource) {
|
|
1654
|
+
if (!BROWSER) {
|
|
1655
|
+
throw new Error('Cannot call invalidate(...) on the server');
|
|
1656
|
+
}
|
|
1571
1657
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1658
|
+
if (typeof resource === 'function') {
|
|
1659
|
+
invalidated.push(resource);
|
|
1660
|
+
} else {
|
|
1661
|
+
const { href } = new URL(resource, location.href);
|
|
1662
|
+
invalidated.push((url) => url.href === href);
|
|
1663
|
+
}
|
|
1575
1664
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
return {
|
|
1579
|
-
type: result.type,
|
|
1580
|
-
location: result.location
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1665
|
+
return _invalidate();
|
|
1666
|
+
}
|
|
1583
1667
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1668
|
+
/**
|
|
1669
|
+
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
|
|
1670
|
+
* @returns {Promise<void>}
|
|
1671
|
+
*/
|
|
1672
|
+
export function invalidateAll() {
|
|
1673
|
+
if (!BROWSER) {
|
|
1674
|
+
throw new Error('Cannot call invalidateAll() on the server');
|
|
1675
|
+
}
|
|
1587
1676
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
throw new Error(
|
|
1592
|
-
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
|
|
1593
|
-
);
|
|
1594
|
-
}
|
|
1677
|
+
force_invalidation = true;
|
|
1678
|
+
return _invalidate();
|
|
1679
|
+
}
|
|
1595
1680
|
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1681
|
+
/**
|
|
1682
|
+
* Programmatically preloads the given page, which means
|
|
1683
|
+
* 1. ensuring that the code for the page is loaded, and
|
|
1684
|
+
* 2. calling the page's load function with the appropriate options.
|
|
1685
|
+
*
|
|
1686
|
+
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-preload-data`.
|
|
1687
|
+
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
|
|
1688
|
+
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
|
|
1689
|
+
*
|
|
1690
|
+
* @param {string} href Page to preload
|
|
1691
|
+
* @returns {Promise<{ type: 'loaded'; status: number; data: Record<string, any> } | { type: 'redirect'; location: string }>}
|
|
1692
|
+
*/
|
|
1693
|
+
export async function preloadData(href) {
|
|
1694
|
+
if (!BROWSER) {
|
|
1695
|
+
throw new Error('Cannot call preloadData(...) on the server');
|
|
1696
|
+
}
|
|
1600
1697
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1698
|
+
const url = resolve_url(href);
|
|
1699
|
+
const intent = get_navigation_intent(url, false);
|
|
1603
1700
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
devalue.stringify(state);
|
|
1608
|
-
} catch (error) {
|
|
1609
|
-
// @ts-expect-error
|
|
1610
|
-
throw new Error(`Could not serialize state${error.path}`);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1701
|
+
if (!intent) {
|
|
1702
|
+
throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
|
|
1703
|
+
}
|
|
1613
1704
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1705
|
+
const result = await _preload_data(intent);
|
|
1706
|
+
if (result.type === 'redirect') {
|
|
1707
|
+
return {
|
|
1708
|
+
type: result.type,
|
|
1709
|
+
location: result.location
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1620
1712
|
|
|
1621
|
-
|
|
1713
|
+
const { status, data } = result.props.page ?? page;
|
|
1714
|
+
return { type: result.type, status, data };
|
|
1715
|
+
}
|
|
1622
1716
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1717
|
+
/**
|
|
1718
|
+
* Programmatically imports the code for routes that haven't yet been fetched.
|
|
1719
|
+
* Typically, you might call this to speed up subsequent navigation.
|
|
1720
|
+
*
|
|
1721
|
+
* You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
|
|
1722
|
+
*
|
|
1723
|
+
* Unlike `preloadData`, this won't call `load` functions.
|
|
1724
|
+
* Returns a Promise that resolves when the modules have been imported.
|
|
1725
|
+
*
|
|
1726
|
+
* @param {string} pathname
|
|
1727
|
+
* @returns {Promise<void>}
|
|
1728
|
+
*/
|
|
1729
|
+
export function preloadCode(pathname) {
|
|
1730
|
+
if (!BROWSER) {
|
|
1731
|
+
throw new Error('Cannot call preloadCode(...) on the server');
|
|
1732
|
+
}
|
|
1625
1733
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1734
|
+
if (DEV) {
|
|
1735
|
+
if (!pathname.startsWith(base)) {
|
|
1736
|
+
throw new Error(
|
|
1737
|
+
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1629
1740
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
} catch (error) {
|
|
1635
|
-
// @ts-expect-error
|
|
1636
|
-
throw new Error(`Could not serialize state${error.path}`);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1741
|
+
if (!routes.find((route) => route.exec(get_url_path(pathname)))) {
|
|
1742
|
+
throw new Error(`'${pathname}' did not match any routes`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1639
1745
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1643
|
-
[PAGE_URL_KEY]: page.url.href
|
|
1644
|
-
};
|
|
1746
|
+
return _preload_code(pathname);
|
|
1747
|
+
}
|
|
1645
1748
|
|
|
1646
|
-
|
|
1749
|
+
/**
|
|
1750
|
+
* Programmatically create a new history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing).
|
|
1751
|
+
*
|
|
1752
|
+
* @param {string | URL} url
|
|
1753
|
+
* @param {App.PageState} state
|
|
1754
|
+
* @returns {void}
|
|
1755
|
+
*/
|
|
1756
|
+
export function pushState(url, state) {
|
|
1757
|
+
if (!BROWSER) {
|
|
1758
|
+
throw new Error('Cannot call pushState(...) on the server');
|
|
1759
|
+
}
|
|
1647
1760
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1761
|
+
if (DEV) {
|
|
1762
|
+
try {
|
|
1763
|
+
// use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
|
|
1764
|
+
devalue.stringify(state);
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
// @ts-expect-error
|
|
1767
|
+
throw new Error(`Could not serialize state${error.path}`);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1650
1770
|
|
|
1651
|
-
|
|
1652
|
-
},
|
|
1771
|
+
update_scroll_positions(current_history_index);
|
|
1653
1772
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1773
|
+
const opts = {
|
|
1774
|
+
[HISTORY_INDEX]: (current_history_index += 1),
|
|
1775
|
+
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1776
|
+
[PAGE_URL_KEY]: page.url.href,
|
|
1777
|
+
[STATES_KEY]: state
|
|
1778
|
+
};
|
|
1657
1779
|
|
|
1658
|
-
|
|
1659
|
-
if (!route) return;
|
|
1780
|
+
original_push_state.call(history, opts, '', resolve_url(url));
|
|
1660
1781
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1782
|
+
page = { ...page, state };
|
|
1783
|
+
root.$set({ page });
|
|
1784
|
+
|
|
1785
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
/**
|
|
1789
|
+
* Programmatically replace the current history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing).
|
|
1790
|
+
*
|
|
1791
|
+
* @param {string | URL} url
|
|
1792
|
+
* @param {App.PageState} state
|
|
1793
|
+
* @returns {void}
|
|
1794
|
+
*/
|
|
1795
|
+
export function replaceState(url, state) {
|
|
1796
|
+
if (!BROWSER) {
|
|
1797
|
+
throw new Error('Cannot call replaceState(...) on the server');
|
|
1798
|
+
}
|
|
1675
1799
|
|
|
1676
|
-
|
|
1800
|
+
if (DEV) {
|
|
1801
|
+
try {
|
|
1802
|
+
// use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
|
|
1803
|
+
devalue.stringify(state);
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
// @ts-expect-error
|
|
1806
|
+
throw new Error(`Could not serialize state${error.path}`);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1677
1809
|
|
|
1678
|
-
|
|
1810
|
+
const opts = {
|
|
1811
|
+
[HISTORY_INDEX]: current_history_index,
|
|
1812
|
+
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1813
|
+
[PAGE_URL_KEY]: page.url.href,
|
|
1814
|
+
[STATES_KEY]: state
|
|
1815
|
+
};
|
|
1679
1816
|
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1682
|
-
} else if (result.type === 'redirect') {
|
|
1683
|
-
goto(result.location, { invalidateAll: true }, 0);
|
|
1684
|
-
} else {
|
|
1685
|
-
/** @type {Record<string, any>} */
|
|
1686
|
-
root.$set({
|
|
1687
|
-
// this brings Svelte's view of the world in line with SvelteKit's
|
|
1688
|
-
// after use:enhance reset the form....
|
|
1689
|
-
form: null,
|
|
1690
|
-
page: { ...page, form: result.data, status: result.status }
|
|
1691
|
-
});
|
|
1692
|
-
|
|
1693
|
-
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
1694
|
-
await tick();
|
|
1695
|
-
root.$set({ form: result.data });
|
|
1696
|
-
|
|
1697
|
-
if (result.type === 'success') {
|
|
1698
|
-
reset_focus();
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
},
|
|
1817
|
+
original_replace_state.call(history, opts, '', resolve_url(url));
|
|
1702
1818
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
// Adopted from Nuxt.js
|
|
1707
|
-
// Reset scrollRestoration to auto when leaving page, allowing page reload
|
|
1708
|
-
// and back-navigation from other pages to use the browser to restore the
|
|
1709
|
-
// scrolling position.
|
|
1710
|
-
addEventListener('beforeunload', (e) => {
|
|
1711
|
-
let should_block = false;
|
|
1712
|
-
|
|
1713
|
-
persist_state();
|
|
1714
|
-
|
|
1715
|
-
if (!navigating) {
|
|
1716
|
-
const nav = create_navigation(current, undefined, null, 'leave');
|
|
1717
|
-
|
|
1718
|
-
// If we're navigating, beforeNavigate was already called. If we end up in here during navigation,
|
|
1719
|
-
// it's due to an external or full-page-reload link, for which we don't want to call the hook again.
|
|
1720
|
-
/** @type {import('@sveltejs/kit').BeforeNavigate} */
|
|
1721
|
-
const navigation = {
|
|
1722
|
-
...nav.navigation,
|
|
1723
|
-
cancel: () => {
|
|
1724
|
-
should_block = true;
|
|
1725
|
-
nav.reject(new Error('navigation was cancelled'));
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1819
|
+
page = { ...page, state };
|
|
1820
|
+
root.$set({ page });
|
|
1821
|
+
}
|
|
1728
1822
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1823
|
+
/**
|
|
1824
|
+
* This action updates the `form` property of the current page with the given data and updates `$page.status`.
|
|
1825
|
+
* In case of an error, it redirects to the nearest error page.
|
|
1826
|
+
* @template {Record<string, unknown> | undefined} Success
|
|
1827
|
+
* @template {Record<string, unknown> | undefined} Failure
|
|
1828
|
+
* @param {import('@sveltejs/kit').ActionResult<Success, Failure>} result
|
|
1829
|
+
* @returns {Promise<void>}
|
|
1830
|
+
*/
|
|
1831
|
+
export async function applyAction(result) {
|
|
1832
|
+
if (!BROWSER) {
|
|
1833
|
+
throw new Error('Cannot call applyAction(...) on the server');
|
|
1834
|
+
}
|
|
1731
1835
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
e.returnValue = '';
|
|
1735
|
-
} else {
|
|
1736
|
-
history.scrollRestoration = 'auto';
|
|
1737
|
-
}
|
|
1738
|
-
});
|
|
1836
|
+
if (result.type === 'error') {
|
|
1837
|
+
const url = new URL(location.href);
|
|
1739
1838
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1839
|
+
const { branch, route } = current;
|
|
1840
|
+
if (!route) return;
|
|
1841
|
+
|
|
1842
|
+
const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
|
|
1843
|
+
if (error_load) {
|
|
1844
|
+
const navigation_result = await get_navigation_result_from_branch({
|
|
1845
|
+
url,
|
|
1846
|
+
params: current.params,
|
|
1847
|
+
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1848
|
+
status: result.status ?? 500,
|
|
1849
|
+
error: result.error,
|
|
1850
|
+
route
|
|
1744
1851
|
});
|
|
1745
1852
|
|
|
1746
|
-
|
|
1747
|
-
if (!navigator.connection?.saveData) {
|
|
1748
|
-
setup_preload();
|
|
1749
|
-
}
|
|
1853
|
+
current = navigation_result.state;
|
|
1750
1854
|
|
|
1751
|
-
|
|
1752
|
-
container.addEventListener('click', (event) => {
|
|
1753
|
-
// Adapted from https://github.com/visionmedia/page.js
|
|
1754
|
-
// MIT license https://github.com/visionmedia/page.js#license
|
|
1755
|
-
if (event.button || event.which !== 1) return;
|
|
1756
|
-
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1757
|
-
if (event.defaultPrevented) return;
|
|
1758
|
-
|
|
1759
|
-
const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
|
|
1760
|
-
if (!a) return;
|
|
1761
|
-
|
|
1762
|
-
const { url, external, target, download } = get_link_info(a, base);
|
|
1763
|
-
if (!url) return;
|
|
1764
|
-
|
|
1765
|
-
// bail out before `beforeNavigate` if link opens in a different tab
|
|
1766
|
-
if (target === '_parent' || target === '_top') {
|
|
1767
|
-
if (window.parent !== window) return;
|
|
1768
|
-
} else if (target && target !== '_self') {
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1855
|
+
root.$set(navigation_result.props);
|
|
1771
1856
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
!is_svg_a_element &&
|
|
1785
|
-
url.protocol !== location.protocol &&
|
|
1786
|
-
!(url.protocol === 'https:' || url.protocol === 'http:')
|
|
1787
|
-
)
|
|
1788
|
-
return;
|
|
1857
|
+
tick().then(reset_focus);
|
|
1858
|
+
}
|
|
1859
|
+
} else if (result.type === 'redirect') {
|
|
1860
|
+
_goto(result.location, { invalidateAll: true }, 0);
|
|
1861
|
+
} else {
|
|
1862
|
+
/** @type {Record<string, any>} */
|
|
1863
|
+
root.$set({
|
|
1864
|
+
// this brings Svelte's view of the world in line with SvelteKit's
|
|
1865
|
+
// after use:enhance reset the form....
|
|
1866
|
+
form: null,
|
|
1867
|
+
page: { ...page, form: result.data, status: result.status }
|
|
1868
|
+
});
|
|
1789
1869
|
|
|
1790
|
-
|
|
1870
|
+
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
1871
|
+
await tick();
|
|
1872
|
+
root.$set({ form: result.data });
|
|
1791
1873
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1874
|
+
if (result.type === 'success') {
|
|
1875
|
+
reset_focus();
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function _start_router() {
|
|
1881
|
+
history.scrollRestoration = 'manual';
|
|
1882
|
+
|
|
1883
|
+
// Adopted from Nuxt.js
|
|
1884
|
+
// Reset scrollRestoration to auto when leaving page, allowing page reload
|
|
1885
|
+
// and back-navigation from other pages to use the browser to restore the
|
|
1886
|
+
// scrolling position.
|
|
1887
|
+
addEventListener('beforeunload', (e) => {
|
|
1888
|
+
let should_block = false;
|
|
1889
|
+
|
|
1890
|
+
persist_state();
|
|
1801
1891
|
|
|
1802
|
-
|
|
1892
|
+
if (!navigating) {
|
|
1893
|
+
const nav = create_navigation(current, undefined, null, 'leave');
|
|
1894
|
+
|
|
1895
|
+
// If we're navigating, beforeNavigate was already called. If we end up in here during navigation,
|
|
1896
|
+
// it's due to an external or full-page-reload link, for which we don't want to call the hook again.
|
|
1897
|
+
/** @type {import('@sveltejs/kit').BeforeNavigate} */
|
|
1898
|
+
const navigation = {
|
|
1899
|
+
...nav.navigation,
|
|
1900
|
+
cancel: () => {
|
|
1901
|
+
should_block = true;
|
|
1902
|
+
nav.reject(new Error('navigation was cancelled'));
|
|
1803
1903
|
}
|
|
1904
|
+
};
|
|
1804
1905
|
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1808
|
-
const [nonhash, hash] = url.href.split('#');
|
|
1809
|
-
if (hash !== undefined && nonhash === strip_hash(location)) {
|
|
1810
|
-
// If we are trying to navigate to the same hash, we should only
|
|
1811
|
-
// attempt to scroll to that element and avoid any history changes.
|
|
1812
|
-
// Otherwise, this can cause Firefox to incorrectly assign a null
|
|
1813
|
-
// history state value without any signal that we can detect.
|
|
1814
|
-
const [, current_hash] = current.url.href.split('#');
|
|
1815
|
-
if (current_hash === hash) {
|
|
1816
|
-
event.preventDefault();
|
|
1817
|
-
|
|
1818
|
-
// We're already on /# and click on a link that goes to /#, or we're on
|
|
1819
|
-
// /#top and click on a link that goes to /#top. In those cases just go to
|
|
1820
|
-
// the top of the page, and avoid a history change.
|
|
1821
|
-
if (hash === '' || (hash === 'top' && a.ownerDocument.getElementById('top') === null)) {
|
|
1822
|
-
window.scrollTo({ top: 0 });
|
|
1823
|
-
} else {
|
|
1824
|
-
a.ownerDocument.getElementById(hash)?.scrollIntoView();
|
|
1825
|
-
}
|
|
1906
|
+
before_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
1907
|
+
}
|
|
1826
1908
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1909
|
+
if (should_block) {
|
|
1910
|
+
e.preventDefault();
|
|
1911
|
+
e.returnValue = '';
|
|
1912
|
+
} else {
|
|
1913
|
+
history.scrollRestoration = 'auto';
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1832
1916
|
|
|
1833
|
-
|
|
1917
|
+
addEventListener('visibilitychange', () => {
|
|
1918
|
+
if (document.visibilityState === 'hidden') {
|
|
1919
|
+
persist_state();
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1834
1922
|
|
|
1835
|
-
|
|
1923
|
+
// @ts-expect-error this isn't supported everywhere yet
|
|
1924
|
+
if (!navigator.connection?.saveData) {
|
|
1925
|
+
setup_preload();
|
|
1926
|
+
}
|
|
1836
1927
|
|
|
1837
|
-
|
|
1928
|
+
/** @param {MouseEvent} event */
|
|
1929
|
+
container.addEventListener('click', (event) => {
|
|
1930
|
+
// Adapted from https://github.com/visionmedia/page.js
|
|
1931
|
+
// MIT license https://github.com/visionmedia/page.js#license
|
|
1932
|
+
if (event.button || event.which !== 1) return;
|
|
1933
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1934
|
+
if (event.defaultPrevented) return;
|
|
1838
1935
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1936
|
+
const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
|
|
1937
|
+
if (!a) return;
|
|
1938
|
+
|
|
1939
|
+
const { url, external, target, download } = get_link_info(a, base);
|
|
1940
|
+
if (!url) return;
|
|
1941
|
+
|
|
1942
|
+
// bail out before `beforeNavigate` if link opens in a different tab
|
|
1943
|
+
if (target === '_parent' || target === '_top') {
|
|
1944
|
+
if (window.parent !== window) return;
|
|
1945
|
+
} else if (target && target !== '_self') {
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const options = get_router_options(a);
|
|
1950
|
+
const is_svg_a_element = a instanceof SVGAElement;
|
|
1951
|
+
|
|
1952
|
+
// Ignore URL protocols that differ to the current one and are not http(s) (e.g. `mailto:`, `tel:`, `myapp:`, etc.)
|
|
1953
|
+
// This may be wrong when the protocol is x: and the link goes to y:.. which should be treated as an external
|
|
1954
|
+
// navigation, but it's not clear how to handle that case and it's not likely to come up in practice.
|
|
1955
|
+
// MEMO: Without this condition, firefox will open mailer twice.
|
|
1956
|
+
// See:
|
|
1957
|
+
// - https://github.com/sveltejs/kit/issues/4045
|
|
1958
|
+
// - https://github.com/sveltejs/kit/issues/5725
|
|
1959
|
+
// - https://github.com/sveltejs/kit/issues/6496
|
|
1960
|
+
if (
|
|
1961
|
+
!is_svg_a_element &&
|
|
1962
|
+
url.protocol !== location.protocol &&
|
|
1963
|
+
!(url.protocol === 'https:' || url.protocol === 'http:')
|
|
1964
|
+
)
|
|
1965
|
+
return;
|
|
1966
|
+
|
|
1967
|
+
if (download) return;
|
|
1842
1968
|
|
|
1969
|
+
// Ignore the following but fire beforeNavigate
|
|
1970
|
+
if (external || options.reload) {
|
|
1971
|
+
if (_before_navigate({ url, type: 'link' })) {
|
|
1972
|
+
// set `navigating` to `true` to prevent `beforeNavigate` callbacks
|
|
1973
|
+
// being called when the page unloads
|
|
1974
|
+
navigating = true;
|
|
1975
|
+
} else {
|
|
1843
1976
|
event.preventDefault();
|
|
1977
|
+
}
|
|
1844
1978
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
url,
|
|
1848
|
-
keepfocus: options.keepfocus,
|
|
1849
|
-
noscroll: options.noscroll,
|
|
1850
|
-
replace_state: options.replace_state ?? url.href === location.href
|
|
1851
|
-
});
|
|
1852
|
-
});
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1853
1981
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1982
|
+
// Check if new url only differs by hash and use the browser default behavior in that case
|
|
1983
|
+
// This will ensure the `hashchange` event is fired
|
|
1984
|
+
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1985
|
+
const [nonhash, hash] = url.href.split('#');
|
|
1986
|
+
if (hash !== undefined && nonhash === strip_hash(location)) {
|
|
1987
|
+
// If we are trying to navigate to the same hash, we should only
|
|
1988
|
+
// attempt to scroll to that element and avoid any history changes.
|
|
1989
|
+
// Otherwise, this can cause Firefox to incorrectly assign a null
|
|
1990
|
+
// history state value without any signal that we can detect.
|
|
1991
|
+
const [, current_hash] = current.url.href.split('#');
|
|
1992
|
+
if (current_hash === hash) {
|
|
1993
|
+
event.preventDefault();
|
|
1856
1994
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1995
|
+
// We're already on /# and click on a link that goes to /#, or we're on
|
|
1996
|
+
// /#top and click on a link that goes to /#top. In those cases just go to
|
|
1997
|
+
// the top of the page, and avoid a history change.
|
|
1998
|
+
if (hash === '' || (hash === 'top' && a.ownerDocument.getElementById('top') === null)) {
|
|
1999
|
+
window.scrollTo({ top: 0 });
|
|
2000
|
+
} else {
|
|
2001
|
+
a.ownerDocument.getElementById(hash)?.scrollIntoView();
|
|
2002
|
+
}
|
|
1860
2003
|
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
// set this flag to distinguish between navigations triggered by
|
|
2007
|
+
// clicking a hash link and those triggered by popstate
|
|
2008
|
+
hash_navigating = true;
|
|
1864
2009
|
|
|
1865
|
-
|
|
2010
|
+
update_scroll_positions(current_history_index);
|
|
1866
2011
|
|
|
1867
|
-
|
|
2012
|
+
update_url(url);
|
|
1868
2013
|
|
|
1869
|
-
|
|
1870
|
-
(submitter?.hasAttribute('formaction') && submitter?.formAction) || form.action
|
|
1871
|
-
);
|
|
2014
|
+
if (!options.replace_state) return;
|
|
1872
2015
|
|
|
1873
|
-
|
|
2016
|
+
// hashchange event shouldn't occur if the router is replacing state.
|
|
2017
|
+
hash_navigating = false;
|
|
2018
|
+
}
|
|
1874
2019
|
|
|
1875
|
-
|
|
2020
|
+
event.preventDefault();
|
|
1876
2021
|
|
|
1877
|
-
|
|
1878
|
-
|
|
2022
|
+
navigate({
|
|
2023
|
+
type: 'link',
|
|
2024
|
+
url,
|
|
2025
|
+
keepfocus: options.keepfocus,
|
|
2026
|
+
noscroll: options.noscroll,
|
|
2027
|
+
replace_state: options.replace_state ?? url.href === location.href
|
|
2028
|
+
});
|
|
2029
|
+
});
|
|
1879
2030
|
|
|
1880
|
-
|
|
1881
|
-
|
|
2031
|
+
container.addEventListener('submit', (event) => {
|
|
2032
|
+
if (event.defaultPrevented) return;
|
|
1882
2033
|
|
|
1883
|
-
|
|
2034
|
+
const form = /** @type {HTMLFormElement} */ (
|
|
2035
|
+
HTMLFormElement.prototype.cloneNode.call(event.target)
|
|
2036
|
+
);
|
|
1884
2037
|
|
|
1885
|
-
|
|
1886
|
-
if (submitter_name) {
|
|
1887
|
-
data.append(submitter_name, submitter?.getAttribute('value') ?? '');
|
|
1888
|
-
}
|
|
2038
|
+
const submitter = /** @type {HTMLButtonElement | HTMLInputElement | null} */ (event.submitter);
|
|
1889
2039
|
|
|
1890
|
-
|
|
1891
|
-
url.search = new URLSearchParams(data).toString();
|
|
2040
|
+
const method = submitter?.formMethod || form.method;
|
|
1892
2041
|
|
|
1893
|
-
|
|
1894
|
-
type: 'form',
|
|
1895
|
-
url,
|
|
1896
|
-
keepfocus: options.keepfocus,
|
|
1897
|
-
noscroll: options.noscroll,
|
|
1898
|
-
replace_state: options.replace_state ?? url.href === location.href
|
|
1899
|
-
});
|
|
1900
|
-
});
|
|
2042
|
+
if (method !== 'get') return;
|
|
1901
2043
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
token = {};
|
|
1906
|
-
|
|
1907
|
-
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
1908
|
-
// with history.go, which means we end up back here, hence this check
|
|
1909
|
-
if (history_index === current_history_index) return;
|
|
1910
|
-
|
|
1911
|
-
const scroll = scroll_positions[history_index];
|
|
1912
|
-
const state = states[history_index] ?? {};
|
|
1913
|
-
const url = new URL(event.state[PAGE_URL_KEY] ?? location.href);
|
|
1914
|
-
const navigation_index = event.state[NAVIGATION_INDEX];
|
|
1915
|
-
const is_hash_change = strip_hash(location) === strip_hash(current.url);
|
|
1916
|
-
const shallow =
|
|
1917
|
-
navigation_index === current_navigation_index && (has_navigated || is_hash_change);
|
|
1918
|
-
|
|
1919
|
-
if (shallow) {
|
|
1920
|
-
// We don't need to navigate, we just need to update scroll and/or state.
|
|
1921
|
-
// This happens with hash links and `pushState`/`replaceState`. The
|
|
1922
|
-
// exception is if we haven't navigated yet, since we could have
|
|
1923
|
-
// got here after a modal navigation then a reload
|
|
1924
|
-
update_url(url);
|
|
1925
|
-
|
|
1926
|
-
scroll_positions[current_history_index] = scroll_state();
|
|
1927
|
-
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
1928
|
-
|
|
1929
|
-
if (state !== page.state) {
|
|
1930
|
-
page = { ...page, state };
|
|
1931
|
-
root.$set({ page });
|
|
1932
|
-
}
|
|
2044
|
+
const url = new URL(
|
|
2045
|
+
(submitter?.hasAttribute('formaction') && submitter?.formAction) || form.action
|
|
2046
|
+
);
|
|
1933
2047
|
|
|
1934
|
-
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
2048
|
+
if (is_external_url(url, base)) return;
|
|
1937
2049
|
|
|
1938
|
-
|
|
2050
|
+
const event_form = /** @type {HTMLFormElement} */ (event.target);
|
|
1939
2051
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
url,
|
|
1943
|
-
popped: {
|
|
1944
|
-
state,
|
|
1945
|
-
scroll,
|
|
1946
|
-
delta
|
|
1947
|
-
},
|
|
1948
|
-
accept: () => {
|
|
1949
|
-
current_history_index = history_index;
|
|
1950
|
-
current_navigation_index = navigation_index;
|
|
1951
|
-
},
|
|
1952
|
-
block: () => {
|
|
1953
|
-
history.go(-delta);
|
|
1954
|
-
},
|
|
1955
|
-
nav_token: token
|
|
1956
|
-
});
|
|
1957
|
-
} else {
|
|
1958
|
-
// since popstate event is also emitted when an anchor referencing the same
|
|
1959
|
-
// document is clicked, we have to check that the router isn't already handling
|
|
1960
|
-
// the navigation. otherwise we would be updating the page store twice.
|
|
1961
|
-
if (!hash_navigating) {
|
|
1962
|
-
const url = new URL(location.href);
|
|
1963
|
-
update_url(url);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
});
|
|
2052
|
+
const options = get_router_options(event_form);
|
|
2053
|
+
if (options.reload) return;
|
|
1967
2054
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
// we need to update history, otherwise we have to leave it alone
|
|
1971
|
-
if (hash_navigating) {
|
|
1972
|
-
hash_navigating = false;
|
|
1973
|
-
original_replace_state.call(
|
|
1974
|
-
history,
|
|
1975
|
-
{
|
|
1976
|
-
...history.state,
|
|
1977
|
-
[HISTORY_INDEX]: ++current_history_index,
|
|
1978
|
-
[NAVIGATION_INDEX]: current_navigation_index
|
|
1979
|
-
},
|
|
1980
|
-
'',
|
|
1981
|
-
location.href
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
});
|
|
2055
|
+
event.preventDefault();
|
|
2056
|
+
event.stopPropagation();
|
|
1985
2057
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2058
|
+
const data = new FormData(event_form);
|
|
2059
|
+
|
|
2060
|
+
const submitter_name = submitter?.getAttribute('name');
|
|
2061
|
+
if (submitter_name) {
|
|
2062
|
+
data.append(submitter_name, submitter?.getAttribute('value') ?? '');
|
|
2063
|
+
}
|
|
1992
2064
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2065
|
+
// @ts-expect-error `URLSearchParams(fd)` is kosher, but typescript doesn't know that
|
|
2066
|
+
url.search = new URLSearchParams(data).toString();
|
|
2067
|
+
|
|
2068
|
+
navigate({
|
|
2069
|
+
type: 'form',
|
|
2070
|
+
url,
|
|
2071
|
+
keepfocus: options.keepfocus,
|
|
2072
|
+
noscroll: options.noscroll,
|
|
2073
|
+
replace_state: options.replace_state ?? url.href === location.href
|
|
2074
|
+
});
|
|
2075
|
+
});
|
|
2076
|
+
|
|
2077
|
+
addEventListener('popstate', async (event) => {
|
|
2078
|
+
if (event.state?.[HISTORY_INDEX]) {
|
|
2079
|
+
const history_index = event.state[HISTORY_INDEX];
|
|
2080
|
+
token = {};
|
|
2081
|
+
|
|
2082
|
+
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2083
|
+
// with history.go, which means we end up back here, hence this check
|
|
2084
|
+
if (history_index === current_history_index) return;
|
|
2085
|
+
|
|
2086
|
+
const scroll = scroll_positions[history_index];
|
|
2087
|
+
const state = event.state[STATES_KEY] ?? {};
|
|
2088
|
+
const url = new URL(event.state[PAGE_URL_KEY] ?? location.href);
|
|
2089
|
+
const navigation_index = event.state[NAVIGATION_INDEX];
|
|
2090
|
+
const is_hash_change = strip_hash(location) === strip_hash(current.url);
|
|
2091
|
+
const shallow =
|
|
2092
|
+
navigation_index === current_navigation_index && (has_navigated || is_hash_change);
|
|
2093
|
+
|
|
2094
|
+
if (shallow) {
|
|
2095
|
+
// We don't need to navigate, we just need to update scroll and/or state.
|
|
2096
|
+
// This happens with hash links and `pushState`/`replaceState`. The
|
|
2097
|
+
// exception is if we haven't navigated yet, since we could have
|
|
2098
|
+
// got here after a modal navigation then a reload
|
|
2099
|
+
update_url(url);
|
|
2100
|
+
|
|
2101
|
+
scroll_positions[current_history_index] = scroll_state();
|
|
2102
|
+
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
2103
|
+
|
|
2104
|
+
if (state !== page.state) {
|
|
2105
|
+
page = { ...page, state };
|
|
2106
|
+
root.$set({ page });
|
|
2000
2107
|
}
|
|
2001
|
-
});
|
|
2002
2108
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
*/
|
|
2006
|
-
function update_url(url) {
|
|
2007
|
-
current.url = url;
|
|
2008
|
-
stores.page.set({ ...page, url });
|
|
2009
|
-
stores.page.notify();
|
|
2109
|
+
current_history_index = history_index;
|
|
2110
|
+
return;
|
|
2010
2111
|
}
|
|
2011
|
-
},
|
|
2012
|
-
|
|
2013
|
-
_hydrate: async ({
|
|
2014
|
-
status = 200,
|
|
2015
|
-
error,
|
|
2016
|
-
node_ids,
|
|
2017
|
-
params,
|
|
2018
|
-
route,
|
|
2019
|
-
data: server_data_nodes,
|
|
2020
|
-
form
|
|
2021
|
-
}) => {
|
|
2022
|
-
hydrated = true;
|
|
2023
2112
|
|
|
2024
|
-
const
|
|
2113
|
+
const delta = history_index - current_history_index;
|
|
2025
2114
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2115
|
+
await navigate({
|
|
2116
|
+
type: 'popstate',
|
|
2117
|
+
url,
|
|
2118
|
+
popped: {
|
|
2119
|
+
state,
|
|
2120
|
+
scroll,
|
|
2121
|
+
delta
|
|
2122
|
+
},
|
|
2123
|
+
accept: () => {
|
|
2124
|
+
current_history_index = history_index;
|
|
2125
|
+
current_navigation_index = navigation_index;
|
|
2126
|
+
},
|
|
2127
|
+
block: () => {
|
|
2128
|
+
history.go(-delta);
|
|
2129
|
+
},
|
|
2130
|
+
nav_token: token
|
|
2131
|
+
});
|
|
2132
|
+
} else {
|
|
2133
|
+
// since popstate event is also emitted when an anchor referencing the same
|
|
2134
|
+
// document is clicked, we have to check that the router isn't already handling
|
|
2135
|
+
// the navigation. otherwise we would be updating the page store twice.
|
|
2136
|
+
if (!hash_navigating) {
|
|
2137
|
+
const url = new URL(location.href);
|
|
2138
|
+
update_url(url);
|
|
2030
2139
|
}
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2031
2142
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2143
|
+
addEventListener('hashchange', () => {
|
|
2144
|
+
// if the hashchange happened as a result of clicking on a link,
|
|
2145
|
+
// we need to update history, otherwise we have to leave it alone
|
|
2146
|
+
if (hash_navigating) {
|
|
2147
|
+
hash_navigating = false;
|
|
2148
|
+
original_replace_state.call(
|
|
2149
|
+
history,
|
|
2150
|
+
{
|
|
2151
|
+
...history.state,
|
|
2152
|
+
[HISTORY_INDEX]: ++current_history_index,
|
|
2153
|
+
[NAVIGATION_INDEX]: current_navigation_index
|
|
2154
|
+
},
|
|
2155
|
+
'',
|
|
2156
|
+
location.href
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2034
2160
|
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
}
|
|
2161
|
+
// fix link[rel=icon], because browsers will occasionally try to load relative
|
|
2162
|
+
// URLs after a pushState/replaceState, resulting in a 404 — see
|
|
2163
|
+
// https://github.com/sveltejs/kit/issues/3748#issuecomment-1125980897
|
|
2164
|
+
for (const link of document.querySelectorAll('link')) {
|
|
2165
|
+
if (link.rel === 'icon') link.href = link.href; // eslint-disable-line
|
|
2166
|
+
}
|
|
2042
2167
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
}
|
|
2053
|
-
return data;
|
|
2054
|
-
},
|
|
2055
|
-
server_data_node: create_data_node(server_data_node)
|
|
2056
|
-
});
|
|
2057
|
-
});
|
|
2168
|
+
addEventListener('pageshow', (event) => {
|
|
2169
|
+
// If the user navigates to another site and then uses the back button and
|
|
2170
|
+
// bfcache hits, we need to set navigating to null, the site doesn't know
|
|
2171
|
+
// the navigation away from it was successful.
|
|
2172
|
+
// Info about bfcache here: https://web.dev/bfcache
|
|
2173
|
+
if (event.persisted) {
|
|
2174
|
+
stores.navigating.set(null);
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2058
2177
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2178
|
+
/**
|
|
2179
|
+
* @param {URL} url
|
|
2180
|
+
*/
|
|
2181
|
+
function update_url(url) {
|
|
2182
|
+
current.url = url;
|
|
2183
|
+
stores.page.set({ ...page, url });
|
|
2184
|
+
stores.page.notify();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2061
2187
|
|
|
2062
|
-
|
|
2188
|
+
/**
|
|
2189
|
+
* @param {HTMLElement} target
|
|
2190
|
+
* @param {{
|
|
2191
|
+
* status: number;
|
|
2192
|
+
* error: App.Error | null;
|
|
2193
|
+
* node_ids: number[];
|
|
2194
|
+
* params: Record<string, string>;
|
|
2195
|
+
* route: { id: string | null };
|
|
2196
|
+
* data: Array<import('types').ServerDataNode | null>;
|
|
2197
|
+
* form: Record<string, any> | null;
|
|
2198
|
+
* }} opts
|
|
2199
|
+
*/
|
|
2200
|
+
async function _hydrate(
|
|
2201
|
+
target,
|
|
2202
|
+
{ status = 200, error, node_ids, params, route, data: server_data_nodes, form }
|
|
2203
|
+
) {
|
|
2204
|
+
hydrated = true;
|
|
2205
|
+
|
|
2206
|
+
const url = new URL(location.href);
|
|
2207
|
+
|
|
2208
|
+
if (!__SVELTEKIT_EMBEDDED__) {
|
|
2209
|
+
// See https://github.com/sveltejs/kit/pull/4935#issuecomment-1328093358 for one motivation
|
|
2210
|
+
// of determining the params on the client side.
|
|
2211
|
+
({ params = {}, route = { id: null } } = get_navigation_intent(url, false) || {});
|
|
2212
|
+
}
|
|
2063
2213
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2214
|
+
/** @type {import('./types.js').NavigationFinished | undefined} */
|
|
2215
|
+
let result;
|
|
2216
|
+
|
|
2217
|
+
try {
|
|
2218
|
+
const branch_promises = node_ids.map(async (n, i) => {
|
|
2219
|
+
const server_data_node = server_data_nodes[i];
|
|
2220
|
+
// Type isn't completely accurate, we still need to deserialize uses
|
|
2221
|
+
if (server_data_node?.uses) {
|
|
2222
|
+
server_data_node.uses = deserialize_uses(server_data_node.uses);
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
return load_node({
|
|
2226
|
+
loader: app.nodes[n],
|
|
2227
|
+
url,
|
|
2228
|
+
params,
|
|
2229
|
+
route,
|
|
2230
|
+
parent: async () => {
|
|
2231
|
+
const data = {};
|
|
2232
|
+
for (let j = 0; j < i; j += 1) {
|
|
2233
|
+
Object.assign(data, (await branch_promises[j]).data);
|
|
2072
2234
|
}
|
|
2073
|
-
|
|
2235
|
+
return data;
|
|
2236
|
+
},
|
|
2237
|
+
server_data_node: create_data_node(server_data_node)
|
|
2238
|
+
});
|
|
2239
|
+
});
|
|
2074
2240
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
params,
|
|
2078
|
-
branch,
|
|
2079
|
-
status,
|
|
2080
|
-
error,
|
|
2081
|
-
form,
|
|
2082
|
-
route: parsed_route ?? null
|
|
2083
|
-
});
|
|
2084
|
-
} catch (error) {
|
|
2085
|
-
if (error instanceof Redirect) {
|
|
2086
|
-
// this is a real edge case — `load` would need to return
|
|
2087
|
-
// a redirect but only in the browser
|
|
2088
|
-
await native_navigation(new URL(error.location, location.href));
|
|
2089
|
-
return;
|
|
2090
|
-
}
|
|
2241
|
+
/** @type {Array<import('./types.js').BranchNode | undefined>} */
|
|
2242
|
+
const branch = await Promise.all(branch_promises);
|
|
2091
2243
|
|
|
2092
|
-
|
|
2093
|
-
status: get_status(error),
|
|
2094
|
-
error: await handle_error(error, { url, params, route }),
|
|
2095
|
-
url,
|
|
2096
|
-
route
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2244
|
+
const parsed_route = routes.find(({ id }) => id === route.id);
|
|
2099
2245
|
|
|
2100
|
-
|
|
2101
|
-
|
|
2246
|
+
// server-side will have compacted the branch, reinstate empty slots
|
|
2247
|
+
// so that error boundaries can be lined up correctly
|
|
2248
|
+
if (parsed_route) {
|
|
2249
|
+
const layouts = parsed_route.layouts;
|
|
2250
|
+
for (let i = 0; i < layouts.length; i++) {
|
|
2251
|
+
if (!layouts[i]) {
|
|
2252
|
+
branch.splice(i, 0, undefined);
|
|
2253
|
+
}
|
|
2102
2254
|
}
|
|
2255
|
+
}
|
|
2103
2256
|
|
|
2104
|
-
|
|
2257
|
+
result = await get_navigation_result_from_branch({
|
|
2258
|
+
url,
|
|
2259
|
+
params,
|
|
2260
|
+
branch,
|
|
2261
|
+
status,
|
|
2262
|
+
error,
|
|
2263
|
+
form,
|
|
2264
|
+
route: parsed_route ?? null
|
|
2265
|
+
});
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
if (error instanceof Redirect) {
|
|
2268
|
+
// this is a real edge case — `load` would need to return
|
|
2269
|
+
// a redirect but only in the browser
|
|
2270
|
+
await native_navigation(new URL(error.location, location.href));
|
|
2271
|
+
return;
|
|
2105
2272
|
}
|
|
2106
|
-
|
|
2273
|
+
|
|
2274
|
+
result = await load_root_error_page({
|
|
2275
|
+
status: get_status(error),
|
|
2276
|
+
error: await handle_error(error, { url, params, route }),
|
|
2277
|
+
url,
|
|
2278
|
+
route
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
if (result.props.page) {
|
|
2283
|
+
result.props.page.state = {};
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
initialize(result, target);
|
|
2107
2287
|
}
|
|
2108
2288
|
|
|
2109
2289
|
/**
|