@sveltejs/kit 3.0.0-next.11 → 3.0.0-next.13
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 +10 -10
- package/src/cli.js +8 -8
- package/src/constants.js +1 -1
- package/src/core/config/options.js +43 -35
- package/src/core/env.js +25 -11
- package/src/core/postbuild/prerender.js +3 -3
- package/src/core/sync/sync.js +11 -14
- package/src/core/sync/utils.js +21 -1
- package/src/core/sync/{write_non_ambient.js → write_app_types.js} +24 -23
- package/src/core/sync/write_client_manifest.js +4 -12
- package/src/core/sync/write_env.js +6 -4
- package/src/core/sync/write_server.js +10 -15
- package/src/core/sync/write_tsconfig/index.js +257 -0
- package/src/core/sync/write_tsconfig/utils.js +162 -0
- package/src/core/sync/write_types/index.js +80 -88
- package/src/exports/index.js +23 -12
- package/src/exports/internal/server/index.js +3 -1
- package/src/exports/internal/shared.js +4 -12
- package/src/exports/public.d.ts +87 -62
- package/src/exports/vite/build/remote.js +10 -12
- package/src/exports/vite/dev/index.js +113 -88
- package/src/exports/vite/index.js +452 -237
- package/src/exports/vite/module_ids.js +3 -1
- package/src/exports/vite/utils.js +1 -10
- package/src/runner.js +13 -0
- package/src/runtime/app/forms.js +4 -0
- package/src/runtime/app/manifest/index.js +1 -0
- package/src/runtime/app/paths/client.js +30 -30
- package/src/runtime/app/paths/internal/client.js +26 -0
- package/src/runtime/app/paths/server.js +22 -9
- package/src/runtime/app/paths/types.d.ts +11 -19
- package/src/runtime/app/server/remote/command.js +12 -7
- package/src/runtime/app/server/remote/form.js +37 -26
- package/src/runtime/app/server/remote/prerender.js +22 -11
- package/src/runtime/app/server/remote/query.js +5 -4
- package/src/runtime/app/server/remote/requested.js +4 -4
- package/src/runtime/app/server/remote/shared.js +48 -30
- package/src/runtime/app/service-worker/index.js +24 -0
- package/src/runtime/app/state/client.js +3 -0
- package/src/runtime/app/state/index.js +3 -3
- package/src/runtime/app/state/server.js +3 -0
- package/src/runtime/client/client.js +627 -311
- package/src/runtime/client/constants.js +2 -6
- package/src/runtime/client/fetcher.js +27 -17
- package/src/runtime/client/remote-functions/form.svelte.js +82 -58
- package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query/index.js +2 -2
- package/src/runtime/client/remote-functions/query/instance.svelte.js +2 -2
- package/src/runtime/client/remote-functions/query-batch.svelte.js +3 -4
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +2 -2
- package/src/runtime/client/remote-functions/query-live/iterator.js +15 -10
- package/src/runtime/client/remote-functions/shared.svelte.js +15 -11
- package/src/runtime/client/state.svelte.js +54 -33
- package/src/runtime/client/types.d.ts +1 -2
- package/src/runtime/client/utils.js +21 -14
- package/src/runtime/components/root.svelte +4 -14
- package/src/runtime/form-utils.js +89 -54
- package/src/runtime/props.svelte.js +71 -0
- package/src/runtime/server/cookie.js +1 -1
- package/src/runtime/server/data/index.js +31 -28
- package/src/runtime/server/errors.js +1 -1
- package/src/runtime/server/fetch.js +4 -6
- package/src/runtime/server/page/actions.js +3 -3
- package/src/runtime/server/page/csp.js +86 -103
- package/src/runtime/server/page/index.js +1 -2
- package/src/runtime/server/page/load_data.js +15 -5
- package/src/runtime/server/page/render.js +46 -52
- package/src/runtime/server/page/respond_with_error.js +1 -3
- package/src/runtime/server/remote-functions.js +66 -35
- package/src/runtime/server/respond.js +56 -16
- package/src/runtime/server/utils.js +10 -0
- package/src/types/ambient-private.d.ts +9 -1
- package/src/types/ambient.d.ts +23 -28
- package/src/types/global-private.d.ts +12 -0
- package/src/types/internal.d.ts +15 -10
- package/src/types/private.d.ts +3 -12
- package/src/utils/url.js +12 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +172 -155
- package/types/index.d.ts.map +5 -1
- package/src/core/sync/write_ambient.js +0 -18
- package/src/core/sync/write_tsconfig.js +0 -258
- package/src/runtime/types.d.ts +0 -8
- /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
/** @import { RouteId } from '$app/types' */
|
|
1
2
|
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
2
|
-
/** @import { NavigationIntent } from './types.js' */
|
|
3
|
-
/** @import { RenderNode } from '../types.js' */
|
|
3
|
+
/** @import { NavigationFinished, NavigationIntent } from './types.js' */
|
|
4
4
|
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
|
|
5
5
|
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
|
|
6
6
|
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
|
|
7
7
|
import { BROWSER, DEV } from 'esm-env';
|
|
8
|
-
import
|
|
8
|
+
import { settled, tick, fork, onMount, hydrate, mount } from 'svelte';
|
|
9
9
|
import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
10
10
|
import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
|
|
11
11
|
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
|
|
@@ -21,16 +21,13 @@ import {
|
|
|
21
21
|
scroll_state,
|
|
22
22
|
load_css
|
|
23
23
|
} from './utils.js';
|
|
24
|
-
import { base } from '$app/paths/internal/client';
|
|
24
|
+
import { base, set_match_implementation } from '$app/paths/internal/client';
|
|
25
25
|
import * as devalue from 'devalue';
|
|
26
26
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
HISTORY_INFO_KEY,
|
|
28
|
+
HISTORY_METADATA_KEY,
|
|
29
29
|
PRELOAD_PRIORITIES,
|
|
30
|
-
|
|
31
|
-
STATES_KEY,
|
|
32
|
-
SNAPSHOT_KEY,
|
|
33
|
-
PAGE_URL_KEY
|
|
30
|
+
SNAPSHOT_KEY
|
|
34
31
|
} from './constants.js';
|
|
35
32
|
import { validate_page_exports } from '../../utils/exports.js';
|
|
36
33
|
import { noop } from '../../utils/functions.js';
|
|
@@ -42,28 +39,29 @@ import {
|
|
|
42
39
|
validate_load_response
|
|
43
40
|
} from '../shared.js';
|
|
44
41
|
import { get_message, get_status } from '../../utils/error.js';
|
|
45
|
-
import { page,
|
|
42
|
+
import { page, navigating, updated, notify_version } from './state.svelte.js';
|
|
46
43
|
import { payload } from './payload.js';
|
|
47
44
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
48
45
|
import { noop_span } from '../telemetry/noop.js';
|
|
49
46
|
import { read_ndjson } from './ndjson.js';
|
|
50
|
-
import
|
|
51
|
-
import {
|
|
47
|
+
import Root from '../components/root.svelte';
|
|
48
|
+
import { Props, RenderNode } from '../props.svelte.js';
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {{
|
|
52
|
+
* historyIndex: number;
|
|
53
|
+
* navigationIndex: number;
|
|
54
|
+
* pageUrl?: string;
|
|
55
|
+
* state: Record<string, any>;
|
|
56
|
+
* persistState: boolean;
|
|
57
|
+
* resetIndex: number;
|
|
58
|
+
* }} HistoryMetadata
|
|
59
|
+
*/
|
|
54
60
|
|
|
55
61
|
export { load_css };
|
|
56
62
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
57
63
|
|
|
58
64
|
let errored = false;
|
|
59
|
-
/**
|
|
60
|
-
* Set via transformError, reset and read at the end of navigate.
|
|
61
|
-
* Necessary because a navigation might succeed loading but during rendering
|
|
62
|
-
* an error occurs, at which point the navigation result needs to be overridden with the error result.
|
|
63
|
-
* TODO this is all very hacky, rethink for SvelteKit 3 where we can assume Svelte 5 and do an overhaul of client.js
|
|
64
|
-
* @type {{ error: App.Error, status: number } | null}
|
|
65
|
-
*/
|
|
66
|
-
let rendering_error = null;
|
|
67
65
|
|
|
68
66
|
/**
|
|
69
67
|
* `reset` functions for `<svelte:boundary>`s in the generated root that have
|
|
@@ -72,19 +70,18 @@ let rendering_error = null;
|
|
|
72
70
|
* navigation away from a render error would leave the stale `+error.svelte`
|
|
73
71
|
* mounted. The boundary's `onerror` populates this array; `navigate` drains it
|
|
74
72
|
* after applying the new props. See sveltejs/kit#15694.
|
|
75
|
-
* @type {
|
|
73
|
+
* @type {Set<() => void>}
|
|
76
74
|
*/
|
|
77
|
-
const resetters =
|
|
75
|
+
const resetters = new Set();
|
|
78
76
|
|
|
79
|
-
// We track
|
|
77
|
+
// We track information associated with each history entry in sessionStorage,
|
|
80
78
|
// rather than on history.state itself, because when navigation is driven by
|
|
81
|
-
// popstate it's too late to update the
|
|
79
|
+
// popstate it's too late to access the options or update the focus position associated with the
|
|
82
80
|
// state we're navigating from
|
|
83
81
|
/**
|
|
84
|
-
*
|
|
85
|
-
* @type {Record<number, { x: number; y: number }>}
|
|
82
|
+
* @type {Record<number, { scroll?: { x: number; y: number }; resetIndex?: number }>}
|
|
86
83
|
*/
|
|
87
|
-
const
|
|
84
|
+
const history_info = storage.get(HISTORY_INFO_KEY) ?? {};
|
|
88
85
|
|
|
89
86
|
/**
|
|
90
87
|
* navigation index -> any
|
|
@@ -92,14 +89,14 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
|
92
89
|
*/
|
|
93
90
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
94
91
|
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
*/
|
|
98
|
-
let current_tree = /** @type {RenderNode} */ ({});
|
|
92
|
+
/** @type {Props} */
|
|
93
|
+
let props;
|
|
99
94
|
|
|
100
95
|
if (DEV && BROWSER) {
|
|
101
96
|
let warned = false;
|
|
102
97
|
|
|
98
|
+
const current_module_url = import.meta.url.split('?')[0]; // remove query params that vite adds to the URL when it is loaded from node_modules
|
|
99
|
+
|
|
103
100
|
const warn = () => {
|
|
104
101
|
if (warned) return;
|
|
105
102
|
|
|
@@ -112,14 +109,15 @@ if (DEV && BROWSER) {
|
|
|
112
109
|
// skip over `warn` and the place where `warn` was called
|
|
113
110
|
const frame = stack[2];
|
|
114
111
|
|
|
115
|
-
//
|
|
112
|
+
// Ignore calls that happen inside dependencies, including SvelteKit.
|
|
113
|
+
// The second condition is only relevant when developing SvelteKit and running it, as there's no node_modules in the stack then (but we still do it to not get repeatedly confused)
|
|
116
114
|
// `frame` can be falsy if we came from an anonymous function
|
|
117
|
-
if (frame?.includes('node_modules')) return;
|
|
115
|
+
if (frame?.includes('node_modules') || frame?.includes(current_module_url)) return;
|
|
118
116
|
|
|
119
117
|
warned = true;
|
|
120
118
|
|
|
121
119
|
console.warn(
|
|
122
|
-
"Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use
|
|
120
|
+
"Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use `goto(...)` from `$app/navigation` instead."
|
|
123
121
|
);
|
|
124
122
|
};
|
|
125
123
|
|
|
@@ -137,8 +135,60 @@ if (DEV && BROWSER) {
|
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
/** @param {number} index */
|
|
140
|
-
function
|
|
141
|
-
|
|
138
|
+
function capture_scroll(index) {
|
|
139
|
+
history_info[index].scroll = scroll_state();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {number} index
|
|
144
|
+
* @param {Pick<HistoryMetadata, 'resetIndex'>} options
|
|
145
|
+
*/
|
|
146
|
+
function set_history_options(index, options) {
|
|
147
|
+
history_info[index] = {
|
|
148
|
+
...history_info[index],
|
|
149
|
+
resetIndex: options.resetIndex
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @param {boolean} reset */
|
|
154
|
+
function blur_active_element(reset) {
|
|
155
|
+
if (
|
|
156
|
+
reset &&
|
|
157
|
+
document.activeElement instanceof HTMLElement &&
|
|
158
|
+
document.activeElement !== document.body
|
|
159
|
+
) {
|
|
160
|
+
document.activeElement.blur();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {URL} url
|
|
166
|
+
* @param {{ x: number; y: number } | null | undefined} scroll
|
|
167
|
+
* @param {boolean} reset
|
|
168
|
+
* @param {Element | null} active_element
|
|
169
|
+
*/
|
|
170
|
+
function reset_scroll_and_focus(url, scroll, reset, active_element) {
|
|
171
|
+
/** @type {Element | null} */
|
|
172
|
+
let deep_linked = null;
|
|
173
|
+
|
|
174
|
+
if (autoscroll) {
|
|
175
|
+
if (scroll) {
|
|
176
|
+
scrollTo(scroll.x, scroll.y);
|
|
177
|
+
} else if ((deep_linked = get_hash_element(url))) {
|
|
178
|
+
deep_linked.scrollIntoView();
|
|
179
|
+
} else {
|
|
180
|
+
scrollTo(0, 0);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const changed_focus =
|
|
185
|
+
document.activeElement !== active_element && document.activeElement !== document.body;
|
|
186
|
+
|
|
187
|
+
if (reset && !changed_focus) {
|
|
188
|
+
reset_focus(url, !deep_linked);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
autoscroll = true;
|
|
142
192
|
}
|
|
143
193
|
|
|
144
194
|
/**
|
|
@@ -149,8 +199,8 @@ function clear_onward_history(current_history_index, current_navigation_index) {
|
|
|
149
199
|
// if we navigated back, then pushed a new state, we can
|
|
150
200
|
// release memory by pruning the scroll/snapshot lookup
|
|
151
201
|
let i = current_history_index + 1;
|
|
152
|
-
while (
|
|
153
|
-
delete
|
|
202
|
+
while (history_info[i]) {
|
|
203
|
+
delete history_info[i];
|
|
154
204
|
i += 1;
|
|
155
205
|
}
|
|
156
206
|
|
|
@@ -224,14 +274,6 @@ export const prerender_responses = {};
|
|
|
224
274
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
225
275
|
const invalidated = [];
|
|
226
276
|
|
|
227
|
-
/**
|
|
228
|
-
* An array of the `+layout.svelte` and `+page.svelte` component instances
|
|
229
|
-
* that currently live on the page — used for capturing and restoring snapshots.
|
|
230
|
-
* It's updated/manipulated through `bind:this` in `Root.svelte`.
|
|
231
|
-
* @type {import('svelte').SvelteComponent[]}
|
|
232
|
-
*/
|
|
233
|
-
const components = [];
|
|
234
|
-
|
|
235
277
|
/** @type {{id: string, token: {}, promise: Promise<import('./types.js').NavigationResult>, fork: Promise<import('svelte').Fork | null> | null} | null} */
|
|
236
278
|
let load_cache = null;
|
|
237
279
|
|
|
@@ -281,21 +323,30 @@ let started = false;
|
|
|
281
323
|
let autoscroll = true;
|
|
282
324
|
let updating = false;
|
|
283
325
|
let is_navigating = false;
|
|
284
|
-
|
|
326
|
+
/** @type {HistoryMetadata | null} */
|
|
327
|
+
let hash_navigating = null;
|
|
285
328
|
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
286
329
|
let has_navigated = false;
|
|
287
330
|
|
|
288
331
|
let force_invalidation = false;
|
|
289
332
|
|
|
290
|
-
/** @type {import('svelte').SvelteComponent} */
|
|
291
|
-
let root;
|
|
292
|
-
|
|
293
333
|
/** @type {number} keeping track of the history index in order to prevent popstate navigation events if needed */
|
|
294
334
|
let current_history_index;
|
|
295
335
|
|
|
296
336
|
/** @type {number} */
|
|
297
337
|
let current_navigation_index;
|
|
298
338
|
|
|
339
|
+
/** @type {number} */
|
|
340
|
+
let current_reset_index;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* @param {any} [state]
|
|
344
|
+
* @returns {HistoryMetadata | undefined}
|
|
345
|
+
*/
|
|
346
|
+
function get_history_metadata(state = history.state) {
|
|
347
|
+
return state?.[HISTORY_METADATA_KEY];
|
|
348
|
+
}
|
|
349
|
+
|
|
299
350
|
/** @type {{}} Token for the latest navigation. Updated on new navigations */
|
|
300
351
|
let navigation_token;
|
|
301
352
|
|
|
@@ -318,7 +369,7 @@ let invalidation_token;
|
|
|
318
369
|
const preload_tokens = new Set();
|
|
319
370
|
|
|
320
371
|
/** @type {Promise<void> | null} */
|
|
321
|
-
|
|
372
|
+
let pending_invalidate;
|
|
322
373
|
|
|
323
374
|
/**
|
|
324
375
|
* @type {Map<string, Map<string, CacheEntry<Query<any>>>>}
|
|
@@ -332,12 +383,50 @@ export const query_map = new Map();
|
|
|
332
383
|
*/
|
|
333
384
|
export const live_query_map = new Map();
|
|
334
385
|
|
|
386
|
+
set_match_implementation(async (url) => {
|
|
387
|
+
if (typeof url === 'string') {
|
|
388
|
+
url = new URL(url, location.href);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const intent = await get_navigation_intent(url, false);
|
|
392
|
+
|
|
393
|
+
if (intent) {
|
|
394
|
+
return {
|
|
395
|
+
id: /** @type {RouteId} */ (intent.route.id),
|
|
396
|
+
params: intent.params
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return null;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
let embedded_start = Promise.resolve();
|
|
404
|
+
|
|
335
405
|
/**
|
|
406
|
+
* TODO this indirection is a grotesque workaround for the fact that multiple apps
|
|
407
|
+
* can operate on the same shared mutable state. This is fundamentally unsound,
|
|
408
|
+
* and the `start`/`_start` distinction does not fix it, but it does get the
|
|
409
|
+
* tests passing. We need to rethink this whole thing but not right now
|
|
336
410
|
* @param {import('./types.js').SvelteKitApp} _app
|
|
337
411
|
* @param {HTMLElement} _target
|
|
338
|
-
* @param {Parameters<typeof _hydrate>[1]} [
|
|
412
|
+
* @param {Parameters<typeof _hydrate>[1]} [data]
|
|
339
413
|
*/
|
|
340
|
-
export
|
|
414
|
+
export function start(_app, _target, data) {
|
|
415
|
+
if (__SVELTEKIT_EMBEDDED__) {
|
|
416
|
+
const start_promise = embedded_start.then(() => _start(_app, _target, data));
|
|
417
|
+
embedded_start = start_promise.catch(noop);
|
|
418
|
+
return start_promise;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return _start(_app, _target, data);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* @param {import('./types.js').SvelteKitApp} _app
|
|
426
|
+
* @param {HTMLElement} _target
|
|
427
|
+
* @param {Parameters<typeof _hydrate>[1]} [data]
|
|
428
|
+
*/
|
|
429
|
+
async function _start(_app, _target, data) {
|
|
341
430
|
if (DEV && _target === document.body) {
|
|
342
431
|
console.warn(
|
|
343
432
|
'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>'
|
|
@@ -375,31 +464,56 @@ export async function start(_app, _target, hydrate) {
|
|
|
375
464
|
// connectivity errors after initialisation don't nuke the app
|
|
376
465
|
default_layout_loader = _app.nodes[0];
|
|
377
466
|
default_error_loader = _app.nodes[1];
|
|
378
|
-
void default_layout_loader();
|
|
379
|
-
void default_error_loader();
|
|
380
467
|
|
|
381
|
-
|
|
382
|
-
|
|
468
|
+
const [root_layout, root_error] = await Promise.all([
|
|
469
|
+
default_layout_loader(),
|
|
470
|
+
default_error_loader()
|
|
471
|
+
]);
|
|
472
|
+
|
|
473
|
+
const tree = new RenderNode(root_layout.component, root_error.component);
|
|
474
|
+
|
|
475
|
+
props = new Props({
|
|
476
|
+
page,
|
|
477
|
+
tree,
|
|
478
|
+
form: undefined,
|
|
479
|
+
error: undefined,
|
|
480
|
+
onerror: (_, reset) => resetters.add(reset)
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const history_metadata = get_history_metadata();
|
|
484
|
+
current_history_index = history_metadata?.historyIndex ?? 0;
|
|
485
|
+
current_navigation_index = history_metadata?.navigationIndex ?? 0;
|
|
486
|
+
current_reset_index = history_metadata?.resetIndex ?? 0;
|
|
383
487
|
|
|
384
488
|
if (!current_history_index) {
|
|
385
489
|
// we use Date.now() as an offset so that cross-document navigations
|
|
386
490
|
// within the app don't result in data loss
|
|
387
|
-
current_history_index = current_navigation_index = Date.now();
|
|
491
|
+
current_history_index = current_navigation_index = current_reset_index = Date.now();
|
|
388
492
|
|
|
389
493
|
// create initial history entry, so we can return here
|
|
390
494
|
history.replaceState(
|
|
391
495
|
{
|
|
392
496
|
...history.state,
|
|
393
|
-
[
|
|
394
|
-
|
|
497
|
+
[HISTORY_METADATA_KEY]: {
|
|
498
|
+
historyIndex: current_history_index,
|
|
499
|
+
navigationIndex: current_navigation_index,
|
|
500
|
+
state: {},
|
|
501
|
+
persistState: false,
|
|
502
|
+
resetIndex: current_history_index
|
|
503
|
+
}
|
|
395
504
|
},
|
|
396
505
|
''
|
|
397
506
|
);
|
|
398
507
|
}
|
|
399
508
|
|
|
509
|
+
set_history_options(
|
|
510
|
+
current_history_index,
|
|
511
|
+
/** @type {HistoryMetadata} */ (get_history_metadata())
|
|
512
|
+
);
|
|
513
|
+
|
|
400
514
|
// if we reload the page, or Cmd-Shift-T back to it,
|
|
401
515
|
// recover scroll position
|
|
402
|
-
const scroll =
|
|
516
|
+
const scroll = history_info[current_history_index]?.scroll;
|
|
403
517
|
function restore_scroll() {
|
|
404
518
|
if (scroll) {
|
|
405
519
|
history.scrollRestoration = 'manual';
|
|
@@ -407,15 +521,17 @@ export async function start(_app, _target, hydrate) {
|
|
|
407
521
|
}
|
|
408
522
|
}
|
|
409
523
|
|
|
410
|
-
if (
|
|
524
|
+
if (data) {
|
|
411
525
|
restore_scroll();
|
|
412
526
|
|
|
413
|
-
await _hydrate(target,
|
|
527
|
+
await _hydrate(target, data);
|
|
414
528
|
} else {
|
|
415
529
|
await navigate({
|
|
416
530
|
type: 'enter',
|
|
417
531
|
url: resolve_url(app.hash ? decode_hash(new URL(location.href)) : location.href),
|
|
418
|
-
replace_state: true
|
|
532
|
+
replace_state: true,
|
|
533
|
+
state: history_metadata?.persistState ? history_metadata.state : {},
|
|
534
|
+
persist_state: history_metadata?.persistState ?? false
|
|
419
535
|
});
|
|
420
536
|
|
|
421
537
|
restore_scroll();
|
|
@@ -464,6 +580,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
464
580
|
}
|
|
465
581
|
|
|
466
582
|
const prev_state = page.state;
|
|
583
|
+
const prev_shallow = page.shallow;
|
|
467
584
|
const navigation_result = intent && (await load_route(intent));
|
|
468
585
|
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
|
|
469
586
|
return;
|
|
@@ -472,7 +589,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
472
589
|
if (navigation_result.type === 'redirect') {
|
|
473
590
|
return _goto(
|
|
474
591
|
new URL(navigation_result.location, current.url).href,
|
|
475
|
-
{
|
|
592
|
+
{ replace: true },
|
|
476
593
|
1,
|
|
477
594
|
token
|
|
478
595
|
);
|
|
@@ -488,11 +605,10 @@ async function _invalidate(reset_page_state = true) {
|
|
|
488
605
|
if (!reset_page_state) {
|
|
489
606
|
navigation_result.props.page.state = prev_state;
|
|
490
607
|
}
|
|
491
|
-
|
|
492
|
-
|
|
608
|
+
navigation_result.props.page.shallow = prev_shallow;
|
|
609
|
+
apply_navigation_result(navigation_result);
|
|
493
610
|
current = { ...navigation_result.state, nav: current.nav };
|
|
494
611
|
reset_invalidation();
|
|
495
|
-
root.$set(navigation_result.props);
|
|
496
612
|
|
|
497
613
|
// only wait for promises that are connected to queries that still exist
|
|
498
614
|
/** @type {Promise<any>[]} */
|
|
@@ -523,21 +639,21 @@ function reset_invalidation() {
|
|
|
523
639
|
|
|
524
640
|
/** @param {number} index */
|
|
525
641
|
function capture_snapshot(index) {
|
|
526
|
-
if (components.some((c) => c?.snapshot)) {
|
|
527
|
-
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
642
|
+
if (props.components.some((c) => c?.snapshot)) {
|
|
643
|
+
snapshots[index] = props.components.map((c) => c?.snapshot?.capture());
|
|
528
644
|
}
|
|
529
645
|
}
|
|
530
646
|
|
|
531
647
|
/** @param {number} index */
|
|
532
648
|
function restore_snapshot(index) {
|
|
533
649
|
snapshots[index]?.forEach((value, i) => {
|
|
534
|
-
components[i]?.snapshot?.restore(value);
|
|
650
|
+
props.components[i]?.snapshot?.restore(value);
|
|
535
651
|
});
|
|
536
652
|
}
|
|
537
653
|
|
|
538
654
|
function persist_state() {
|
|
539
|
-
|
|
540
|
-
storage.set(
|
|
655
|
+
capture_scroll(current_history_index);
|
|
656
|
+
storage.set(HISTORY_INFO_KEY, history_info);
|
|
541
657
|
|
|
542
658
|
capture_snapshot(current_navigation_index);
|
|
543
659
|
storage.set(SNAPSHOT_KEY, snapshots);
|
|
@@ -545,7 +661,7 @@ function persist_state() {
|
|
|
545
661
|
|
|
546
662
|
/**
|
|
547
663
|
* @param {string | URL} url
|
|
548
|
-
* @param {{
|
|
664
|
+
* @param {{ replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean }} options
|
|
549
665
|
* @param {number} redirect_count
|
|
550
666
|
* @param {{}} [nav_token]
|
|
551
667
|
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
|
|
@@ -566,10 +682,10 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
566
682
|
await navigate({
|
|
567
683
|
type: 'goto',
|
|
568
684
|
url: resolve_url(url),
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
replace_state: options.replaceState,
|
|
685
|
+
reset: options.reset,
|
|
686
|
+
replace_state: options.replace,
|
|
572
687
|
state: options.state,
|
|
688
|
+
persist_state: options.persistState,
|
|
573
689
|
redirect_count,
|
|
574
690
|
nav_token,
|
|
575
691
|
intent,
|
|
@@ -603,9 +719,8 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
603
719
|
if (options.refreshAll) {
|
|
604
720
|
// TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
|
|
605
721
|
// when a query in a layout that still exists after page change is refreshed earlier than this
|
|
606
|
-
void
|
|
607
|
-
.tick
|
|
608
|
-
.then(svelte.tick)
|
|
722
|
+
void tick()
|
|
723
|
+
.then(tick)
|
|
609
724
|
.then(() => {
|
|
610
725
|
for (const [id, entries] of query_map) {
|
|
611
726
|
for (const [payload, { resource }] of entries) {
|
|
@@ -647,7 +762,7 @@ async function _preload_data(intent) {
|
|
|
647
762
|
|
|
648
763
|
load_cache.promise.catch(discard_load_cache);
|
|
649
764
|
|
|
650
|
-
if (__SVELTEKIT_FORK_PRELOADS__
|
|
765
|
+
if (__SVELTEKIT_FORK_PRELOADS__) {
|
|
651
766
|
const lc = load_cache;
|
|
652
767
|
|
|
653
768
|
lc.fork = lc.promise.then((result) => {
|
|
@@ -655,10 +770,8 @@ async function _preload_data(intent) {
|
|
|
655
770
|
// resolve, bail rather than creating an orphan fork
|
|
656
771
|
if (lc === load_cache && result.type === 'loaded') {
|
|
657
772
|
try {
|
|
658
|
-
return
|
|
659
|
-
|
|
660
|
-
update(result.props.page);
|
|
661
|
-
current_tree = result.props.tree;
|
|
773
|
+
return fork(() => {
|
|
774
|
+
apply_navigation_result(result);
|
|
662
775
|
});
|
|
663
776
|
} catch {
|
|
664
777
|
// if it errors, it's because the experimental flag isn't enabled in Svelte
|
|
@@ -692,9 +805,9 @@ async function _preload_code(url) {
|
|
|
692
805
|
/**
|
|
693
806
|
* @param {import('./types.js').NavigationFinished} result
|
|
694
807
|
* @param {HTMLElement} target
|
|
695
|
-
* @param {boolean}
|
|
808
|
+
* @param {boolean} should_hydrate
|
|
696
809
|
*/
|
|
697
|
-
async function initialize(result, target,
|
|
810
|
+
async function initialize(result, target, should_hydrate) {
|
|
698
811
|
if (__SVELTEKIT_DEV__ && result.state.error && document.querySelector('vite-error-overlay'))
|
|
699
812
|
return;
|
|
700
813
|
|
|
@@ -716,21 +829,18 @@ async function initialize(result, target, hydrate) {
|
|
|
716
829
|
if (style) style.remove();
|
|
717
830
|
}
|
|
718
831
|
|
|
719
|
-
|
|
720
|
-
|
|
832
|
+
apply_navigation_result(result);
|
|
833
|
+
|
|
834
|
+
// TODO treeshake `hydrate` in csr mode
|
|
835
|
+
const render = should_hydrate ? hydrate : mount;
|
|
721
836
|
|
|
722
|
-
|
|
723
|
-
root = new Root({
|
|
837
|
+
render(Root, {
|
|
724
838
|
target,
|
|
725
|
-
props
|
|
726
|
-
hydrate,
|
|
727
|
-
// Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
|
|
728
|
-
sync: false,
|
|
839
|
+
props,
|
|
729
840
|
transformError: /** @param {unknown} e */ async (e) => {
|
|
730
841
|
const error = await handle_error(e, current.nav);
|
|
731
|
-
rendering_error = { error, status: error.status };
|
|
732
842
|
page.error = error;
|
|
733
|
-
page.status =
|
|
843
|
+
page.status = error.status;
|
|
734
844
|
return error;
|
|
735
845
|
}
|
|
736
846
|
});
|
|
@@ -739,16 +849,17 @@ async function initialize(result, target, hydrate) {
|
|
|
739
849
|
// which causes component script blocks to run asynchronously
|
|
740
850
|
void (await Promise.resolve());
|
|
741
851
|
|
|
742
|
-
if (
|
|
852
|
+
if (should_hydrate) {
|
|
743
853
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
744
854
|
const navigation = {
|
|
745
855
|
from: null,
|
|
746
856
|
to: {
|
|
747
857
|
...nav,
|
|
748
|
-
scroll:
|
|
858
|
+
scroll: history_info[current_history_index]?.scroll ?? scroll_state()
|
|
749
859
|
},
|
|
750
860
|
willUnload: false,
|
|
751
861
|
type: 'enter',
|
|
862
|
+
shallow: false,
|
|
752
863
|
complete: Promise.resolve()
|
|
753
864
|
};
|
|
754
865
|
|
|
@@ -830,7 +941,7 @@ async function get_navigation_result_from_branch({
|
|
|
830
941
|
let current_node = result.props.tree;
|
|
831
942
|
|
|
832
943
|
/** @type {RenderNode | undefined} */
|
|
833
|
-
let previous_node =
|
|
944
|
+
let previous_node = props.tree;
|
|
834
945
|
|
|
835
946
|
for (let i = 0; i < branch.length; i += 1) {
|
|
836
947
|
const node = branch[i];
|
|
@@ -838,11 +949,6 @@ async function get_navigation_result_from_branch({
|
|
|
838
949
|
|
|
839
950
|
if (!node) continue;
|
|
840
951
|
|
|
841
|
-
const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader;
|
|
842
|
-
|
|
843
|
-
current_node.error = (await error_loader()).component;
|
|
844
|
-
current_node.component = node.node.component;
|
|
845
|
-
|
|
846
952
|
if (
|
|
847
953
|
// if an ancestor node in this path changed, `data_changed` is already true and the
|
|
848
954
|
// accumulated `data` differs from the previous render, so we must re-merge
|
|
@@ -860,10 +966,24 @@ async function get_navigation_result_from_branch({
|
|
|
860
966
|
data = current_node.data;
|
|
861
967
|
|
|
862
968
|
if (i < branch.length - 1) {
|
|
863
|
-
|
|
864
|
-
|
|
969
|
+
let next_index = i + 1;
|
|
970
|
+
while (next_index < branch.length && !branch[next_index]) {
|
|
971
|
+
next_index += 1;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const next = branch[next_index];
|
|
865
975
|
|
|
866
|
-
|
|
976
|
+
if (next) {
|
|
977
|
+
const error_loader =
|
|
978
|
+
errors?.slice(0, next_index + 1).findLast((x) => x) ?? default_error_loader;
|
|
979
|
+
|
|
980
|
+
current_node = current_node.child = new RenderNode(
|
|
981
|
+
next.node.component,
|
|
982
|
+
(await error_loader()).component
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
previous_node = previous_node?.child;
|
|
986
|
+
}
|
|
867
987
|
}
|
|
868
988
|
}
|
|
869
989
|
|
|
@@ -882,6 +1002,7 @@ async function get_navigation_result_from_branch({
|
|
|
882
1002
|
id: route?.id ?? null
|
|
883
1003
|
},
|
|
884
1004
|
state: {},
|
|
1005
|
+
shallow: null,
|
|
885
1006
|
status: status ?? error?.status ?? 200,
|
|
886
1007
|
url: new URL(url),
|
|
887
1008
|
form: form ?? null,
|
|
@@ -1197,7 +1318,8 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1197
1318
|
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
1198
1319
|
let server_data = null;
|
|
1199
1320
|
const url_changed = current.url ? id !== get_page_key(current.url) : false;
|
|
1200
|
-
|
|
1321
|
+
// current.route is null after an error-page render, so a missing route counts as changed
|
|
1322
|
+
const route_changed = !current.route || route.id !== current.route.id;
|
|
1201
1323
|
const search_params_changed = diff_search_params(current.url, url);
|
|
1202
1324
|
|
|
1203
1325
|
let parent_invalid = false;
|
|
@@ -1279,7 +1401,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1279
1401
|
|
|
1280
1402
|
if (server_data_node?.type === 'error') {
|
|
1281
1403
|
// rethrow and catch below
|
|
1282
|
-
throw new HttpError(server_data_node.error
|
|
1404
|
+
throw new HttpError(server_data_node.error);
|
|
1283
1405
|
}
|
|
1284
1406
|
|
|
1285
1407
|
return load_node({
|
|
@@ -1636,12 +1758,14 @@ function get_page_key(url) {
|
|
|
1636
1758
|
* delta?: number;
|
|
1637
1759
|
* event?: PopStateEvent | MouseEvent;
|
|
1638
1760
|
* scroll?: { x: number, y: number };
|
|
1761
|
+
* shallow?: boolean;
|
|
1762
|
+
* target?: { params: Record<string, string> | null; route: { id: string } | null; url: URL };
|
|
1639
1763
|
* }} opts
|
|
1640
1764
|
*/
|
|
1641
|
-
function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
1765
|
+
function _before_navigate({ url, type, intent, delta, event, scroll, shallow = false, target }) {
|
|
1642
1766
|
let should_block = false;
|
|
1643
1767
|
|
|
1644
|
-
const nav = create_navigation(current, intent, url, type, scroll ?? null);
|
|
1768
|
+
const nav = create_navigation(current, intent, url, type, scroll ?? null, shallow, target);
|
|
1645
1769
|
|
|
1646
1770
|
if (nav.navigation.type === 'popstate' && delta !== undefined) {
|
|
1647
1771
|
nav.navigation.delta = delta;
|
|
@@ -1662,6 +1786,9 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1662
1786
|
|
|
1663
1787
|
if (!is_navigating) {
|
|
1664
1788
|
// Don't run the event during redirects
|
|
1789
|
+
// TODO this isn't fully right: if you do a goto(...) while another goto(...) is in progress,
|
|
1790
|
+
// or you click a link while a navigation is in progress, the beforNavigate calls are not triggered,
|
|
1791
|
+
// and maybe they should be?
|
|
1665
1792
|
before_navigate_callbacks.forEach((fn) => fn(cancellable));
|
|
1666
1793
|
}
|
|
1667
1794
|
|
|
@@ -1674,13 +1801,14 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1674
1801
|
* url: URL;
|
|
1675
1802
|
* popped?: {
|
|
1676
1803
|
* state: Record<string, any>;
|
|
1677
|
-
* scroll
|
|
1804
|
+
* scroll?: { x: number, y: number };
|
|
1678
1805
|
* delta: number;
|
|
1806
|
+
* shallow: { params: Record<string, string> | null; route: { id: string } | null; url: URL } | null;
|
|
1679
1807
|
* };
|
|
1680
|
-
*
|
|
1681
|
-
* noscroll?: boolean;
|
|
1808
|
+
* reset?: boolean;
|
|
1682
1809
|
* replace_state?: boolean;
|
|
1683
1810
|
* state?: Record<string, any>;
|
|
1811
|
+
* persist_state?: boolean;
|
|
1684
1812
|
* redirect_count?: number;
|
|
1685
1813
|
* nav_token?: {};
|
|
1686
1814
|
* accept?: () => void;
|
|
@@ -1694,10 +1822,10 @@ async function navigate({
|
|
|
1694
1822
|
type,
|
|
1695
1823
|
url,
|
|
1696
1824
|
popped,
|
|
1697
|
-
|
|
1698
|
-
noscroll,
|
|
1825
|
+
reset = true,
|
|
1699
1826
|
replace_state,
|
|
1700
1827
|
state = {},
|
|
1828
|
+
persist_state = false,
|
|
1701
1829
|
redirect_count = 0,
|
|
1702
1830
|
nav_token = {},
|
|
1703
1831
|
accept = noop,
|
|
@@ -1719,6 +1847,8 @@ async function navigate({
|
|
|
1719
1847
|
delta: popped?.delta,
|
|
1720
1848
|
intent,
|
|
1721
1849
|
scroll: popped?.scroll,
|
|
1850
|
+
shallow: !!popped?.shallow,
|
|
1851
|
+
target: popped?.shallow ?? undefined,
|
|
1722
1852
|
// @ts-ignore
|
|
1723
1853
|
event
|
|
1724
1854
|
});
|
|
@@ -1802,10 +1932,10 @@ async function navigate({
|
|
|
1802
1932
|
type,
|
|
1803
1933
|
url: new URL(navigation_result.location, url),
|
|
1804
1934
|
popped,
|
|
1805
|
-
|
|
1806
|
-
noscroll,
|
|
1935
|
+
reset,
|
|
1807
1936
|
replace_state,
|
|
1808
1937
|
state,
|
|
1938
|
+
persist_state,
|
|
1809
1939
|
redirect_count: redirect_count + 1,
|
|
1810
1940
|
nav_token
|
|
1811
1941
|
});
|
|
@@ -1839,7 +1969,7 @@ async function navigate({
|
|
|
1839
1969
|
|
|
1840
1970
|
updating = true;
|
|
1841
1971
|
|
|
1842
|
-
|
|
1972
|
+
capture_scroll(previous_history_index);
|
|
1843
1973
|
capture_snapshot(previous_navigation_index);
|
|
1844
1974
|
|
|
1845
1975
|
// ensure the url pathname matches the page's trailing slash option
|
|
@@ -1852,15 +1982,23 @@ async function navigate({
|
|
|
1852
1982
|
if (!popped) {
|
|
1853
1983
|
// this is a new navigation, rather than a popstate
|
|
1854
1984
|
const change = replace_state ? 0 : 1;
|
|
1985
|
+
if (type !== 'enter') {
|
|
1986
|
+
if (reset) current_reset_index += 1;
|
|
1987
|
+
}
|
|
1855
1988
|
|
|
1856
1989
|
const entry = {
|
|
1857
|
-
[
|
|
1858
|
-
|
|
1859
|
-
|
|
1990
|
+
[HISTORY_METADATA_KEY]: /** @satisfies {HistoryMetadata} */ ({
|
|
1991
|
+
historyIndex: (current_history_index += change),
|
|
1992
|
+
navigationIndex: (current_navigation_index += change),
|
|
1993
|
+
state,
|
|
1994
|
+
persistState: persist_state,
|
|
1995
|
+
resetIndex: current_reset_index
|
|
1996
|
+
})
|
|
1860
1997
|
};
|
|
1861
1998
|
|
|
1862
1999
|
const fn = replace_state ? history.replaceState : history.pushState;
|
|
1863
2000
|
fn.call(history, entry, '', url);
|
|
2001
|
+
set_history_options(current_history_index, entry[HISTORY_METADATA_KEY]);
|
|
1864
2002
|
|
|
1865
2003
|
if (!replace_state) {
|
|
1866
2004
|
clear_onward_history(current_history_index, current_navigation_index);
|
|
@@ -1879,6 +2017,7 @@ async function navigate({
|
|
|
1879
2017
|
}
|
|
1880
2018
|
|
|
1881
2019
|
navigation_result.props.page.state = state;
|
|
2020
|
+
navigation_result.props.page.shallow = popped?.shallow ?? null;
|
|
1882
2021
|
|
|
1883
2022
|
/**
|
|
1884
2023
|
* @type {Promise<void> | undefined}
|
|
@@ -1909,7 +2048,13 @@ async function navigate({
|
|
|
1909
2048
|
}
|
|
1910
2049
|
|
|
1911
2050
|
// Type-casts are save because we know this resolved a proper SvelteKit route
|
|
1912
|
-
const target =
|
|
2051
|
+
const target = popped?.shallow
|
|
2052
|
+
? {
|
|
2053
|
+
params: navigation_result.state.params,
|
|
2054
|
+
route: { id: navigation_result.state.route?.id ?? null },
|
|
2055
|
+
url: navigation_result.state.url
|
|
2056
|
+
}
|
|
2057
|
+
: /** @type {import('@sveltejs/kit').NavigationTarget} */ (nav.navigation.to);
|
|
1913
2058
|
current = {
|
|
1914
2059
|
...navigation_result.state,
|
|
1915
2060
|
nav: {
|
|
@@ -1926,13 +2071,7 @@ async function navigate({
|
|
|
1926
2071
|
|
|
1927
2072
|
// Remove focus before updating the component tree, so that blur/focusout
|
|
1928
2073
|
// handlers fire while the old component's data is still valid (#14575)
|
|
1929
|
-
|
|
1930
|
-
!keepfocus &&
|
|
1931
|
-
document.activeElement instanceof HTMLElement &&
|
|
1932
|
-
document.activeElement !== document.body
|
|
1933
|
-
) {
|
|
1934
|
-
document.activeElement.blur();
|
|
1935
|
-
}
|
|
2074
|
+
blur_active_element(reset);
|
|
1936
2075
|
|
|
1937
2076
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1938
2077
|
|
|
@@ -1941,28 +2080,22 @@ async function navigate({
|
|
|
1941
2080
|
// `fork.commit()` applies the preloaded state synchronously before the
|
|
1942
2081
|
// first `await`, so reset any previously-failed boundaries now so the
|
|
1943
2082
|
// stale `+error.svelte` is torn down. See sveltejs/kit#15694.
|
|
1944
|
-
for (const
|
|
1945
|
-
|
|
2083
|
+
for (const reset_boundary of resetters) {
|
|
2084
|
+
reset_boundary();
|
|
1946
2085
|
}
|
|
1947
|
-
resetters.
|
|
2086
|
+
resetters.clear();
|
|
1948
2087
|
} else {
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
current_tree = navigation_result.props.tree;
|
|
2088
|
+
apply_navigation_result(navigation_result);
|
|
2089
|
+
|
|
1952
2090
|
// Reset any boundaries that failed on a previous navigation now that the
|
|
1953
2091
|
// new props are applied, otherwise the stale `+error.svelte` stays
|
|
1954
2092
|
// mounted above the new route's content. See sveltejs/kit#15694.
|
|
1955
|
-
for (const
|
|
1956
|
-
|
|
2093
|
+
for (const reset_boundary of resetters) {
|
|
2094
|
+
reset_boundary();
|
|
1957
2095
|
}
|
|
1958
|
-
resetters.
|
|
1959
|
-
// Check for sync rendering error
|
|
1960
|
-
if (rendering_error) {
|
|
1961
|
-
Object.assign(navigation_result.props.page, rendering_error);
|
|
1962
|
-
}
|
|
1963
|
-
update(navigation_result.props.page);
|
|
2096
|
+
resetters.clear();
|
|
1964
2097
|
|
|
1965
|
-
commit_promise =
|
|
2098
|
+
commit_promise = settled();
|
|
1966
2099
|
}
|
|
1967
2100
|
|
|
1968
2101
|
has_navigated = true;
|
|
@@ -1972,7 +2105,7 @@ async function navigate({
|
|
|
1972
2105
|
|
|
1973
2106
|
const { activeElement } = document;
|
|
1974
2107
|
|
|
1975
|
-
await
|
|
2108
|
+
await commit_promise;
|
|
1976
2109
|
|
|
1977
2110
|
if (navigation_token !== nav_token) {
|
|
1978
2111
|
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
@@ -1980,44 +2113,7 @@ async function navigate({
|
|
|
1980
2113
|
return;
|
|
1981
2114
|
}
|
|
1982
2115
|
|
|
1983
|
-
|
|
1984
|
-
if (navigation_result.props.page && rendering_error) {
|
|
1985
|
-
Object.assign(navigation_result.props.page, rendering_error);
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
|
|
1989
|
-
/** @type {Element | null | ''} */
|
|
1990
|
-
let deep_linked = null;
|
|
1991
|
-
|
|
1992
|
-
if (autoscroll) {
|
|
1993
|
-
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
|
|
1994
|
-
if (scroll) {
|
|
1995
|
-
scrollTo(scroll.x, scroll.y);
|
|
1996
|
-
} else if ((deep_linked = url.hash && document.getElementById(get_id(url)))) {
|
|
1997
|
-
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
1998
|
-
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
1999
|
-
// CSS properties.
|
|
2000
|
-
deep_linked.scrollIntoView();
|
|
2001
|
-
} else {
|
|
2002
|
-
scrollTo(0, 0);
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
const changed_focus =
|
|
2007
|
-
// reset focus only if any manual focus management didn't override it
|
|
2008
|
-
document.activeElement !== activeElement &&
|
|
2009
|
-
// also refocus when activeElement is body already because the
|
|
2010
|
-
// focus event might not have been fired on it yet
|
|
2011
|
-
document.activeElement !== document.body;
|
|
2012
|
-
|
|
2013
|
-
if (!keepfocus && !changed_focus) {
|
|
2014
|
-
// We don't need to manually restore the scroll position if we're navigating
|
|
2015
|
-
// to a fragment identifier. It is automatically done for us when we set the
|
|
2016
|
-
// sequential navigation starting point with `location.replace`
|
|
2017
|
-
reset_focus(url, !deep_linked);
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
autoscroll = true;
|
|
2116
|
+
reset_scroll_and_focus(url, reset ? popped?.scroll : scroll_state(), reset, activeElement);
|
|
2021
2117
|
|
|
2022
2118
|
is_navigating = false;
|
|
2023
2119
|
|
|
@@ -2261,7 +2357,7 @@ export async function handle_error(error, event) {
|
|
|
2261
2357
|
* @param {T} callback
|
|
2262
2358
|
*/
|
|
2263
2359
|
function add_navigation_callback(callbacks, callback) {
|
|
2264
|
-
|
|
2360
|
+
onMount(() => {
|
|
2265
2361
|
callbacks.add(callback);
|
|
2266
2362
|
|
|
2267
2363
|
return () => {
|
|
@@ -2333,24 +2429,49 @@ export function disableScrollHandling() {
|
|
|
2333
2429
|
}
|
|
2334
2430
|
|
|
2335
2431
|
let warned_on_invalidate_all = false;
|
|
2432
|
+
let warned_on_replace_state = false;
|
|
2433
|
+
let warned_on_push_state = false;
|
|
2434
|
+
let warned_on_replace_state_function = false;
|
|
2435
|
+
|
|
2436
|
+
/**
|
|
2437
|
+
* @param {string | URL} url
|
|
2438
|
+
* @param {'goto' | 'pushState' | 'replaceState'} caller
|
|
2439
|
+
*/
|
|
2440
|
+
async function resolve_intent(url, caller) {
|
|
2441
|
+
const resolved = new URL(resolve_url(url));
|
|
2442
|
+
|
|
2443
|
+
if (resolved.origin !== origin) {
|
|
2444
|
+
throw new Error(
|
|
2445
|
+
DEV
|
|
2446
|
+
? `Cannot use \`${caller}\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
2447
|
+
: `${caller}: invalid URL`
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
const intent = await get_navigation_intent(resolved, false);
|
|
2452
|
+
|
|
2453
|
+
if (!intent) {
|
|
2454
|
+
throw new Error(
|
|
2455
|
+
DEV
|
|
2456
|
+
? `Cannot use \`${caller}\` with a URL that does not resolve to a route within the app. Use \`window.location = "${url}"\` instead`
|
|
2457
|
+
: `${caller}: invalid URL`
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
return intent;
|
|
2462
|
+
}
|
|
2336
2463
|
|
|
2337
2464
|
/**
|
|
2338
|
-
* Allows you to navigate programmatically to a given route, with
|
|
2339
|
-
*
|
|
2465
|
+
* Allows you to navigate programmatically to a given route, with control over details such as whether scroll and focus are reset
|
|
2466
|
+
* (as they would be with a regular navigation) or preserved.
|
|
2467
|
+
*
|
|
2468
|
+
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) or the state change has been applied.
|
|
2340
2469
|
*
|
|
2341
|
-
* `goto` is intended for navigations to routes that belong to the app.
|
|
2342
|
-
* If the URL does not resolve to a route within the app, the returned promise will reject.
|
|
2470
|
+
* `goto` is intended for navigations to routes that belong to the app, and will reject if a route cannot be resolved.
|
|
2343
2471
|
* For external URLs, use `window.location = url` to perform a full-page navigation instead of calling `goto(url)`.
|
|
2344
2472
|
*
|
|
2345
2473
|
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.paths.base`](https://svelte.dev/docs/kit/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
|
|
2346
|
-
* @param {
|
|
2347
|
-
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
2348
|
-
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
|
2349
|
-
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
|
2350
|
-
* @param {boolean} [opts.refreshAll] If `true`, all `load` functions and queries of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
|
|
2351
|
-
* @param {Array<string | URL | ((url: URL) => boolean)>} [opts.invalidate] Causes any load functions to re-run if they depend on one of the urls
|
|
2352
|
-
* @param {boolean} [opts.invalidateAll] Deprecated in favour of opts.refreshAll.
|
|
2353
|
-
* @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
|
|
2474
|
+
* @param {import('@sveltejs/kit').GotoOptions} [opts] Options related to the navigation
|
|
2354
2475
|
* @returns {Promise<void>}
|
|
2355
2476
|
*/
|
|
2356
2477
|
export async function goto(url, opts = {}) {
|
|
@@ -2358,23 +2479,35 @@ export async function goto(url, opts = {}) {
|
|
|
2358
2479
|
throw new Error('Cannot call goto(...) on the server');
|
|
2359
2480
|
}
|
|
2360
2481
|
|
|
2361
|
-
|
|
2482
|
+
if (DEV) {
|
|
2483
|
+
if ('replaceState' in opts && !warned_on_replace_state) {
|
|
2484
|
+
warned_on_replace_state = true;
|
|
2485
|
+
console.warn(
|
|
2486
|
+
`The \`goto(..., { replaceState: ${opts.replaceState} })\` option has been deprecated in favour of \`replace\``
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2362
2489
|
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
);
|
|
2490
|
+
if ('noScroll' in opts || 'keepFocus' in opts) {
|
|
2491
|
+
throw new Error(
|
|
2492
|
+
`The \`goto(..., { noScroll: true, keepFocus: true })\` options have been replaced by \`reset: false\``
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2369
2495
|
}
|
|
2370
2496
|
|
|
2371
|
-
const
|
|
2497
|
+
const replace = opts.replace ?? opts.replaceState ?? false;
|
|
2372
2498
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2499
|
+
const intent = await resolve_intent(url, 'goto');
|
|
2500
|
+
|
|
2501
|
+
if (opts.shallow) {
|
|
2502
|
+
return update_state(
|
|
2503
|
+
intent,
|
|
2504
|
+
opts.state ?? {},
|
|
2505
|
+
{
|
|
2506
|
+
replace,
|
|
2507
|
+
persist_state: opts.persistState ?? false,
|
|
2508
|
+
reset: opts.reset ?? false
|
|
2509
|
+
},
|
|
2510
|
+
'goto'
|
|
2378
2511
|
);
|
|
2379
2512
|
}
|
|
2380
2513
|
|
|
@@ -2385,8 +2518,13 @@ export async function goto(url, opts = {}) {
|
|
|
2385
2518
|
);
|
|
2386
2519
|
}
|
|
2387
2520
|
|
|
2388
|
-
|
|
2389
|
-
|
|
2521
|
+
return _goto(
|
|
2522
|
+
intent.url,
|
|
2523
|
+
{ ...opts, replace, refreshAll: opts.refreshAll ?? opts.invalidateAll },
|
|
2524
|
+
0,
|
|
2525
|
+
{},
|
|
2526
|
+
intent
|
|
2527
|
+
);
|
|
2390
2528
|
}
|
|
2391
2529
|
|
|
2392
2530
|
/**
|
|
@@ -2560,63 +2698,77 @@ export async function preloadCode(pathname) {
|
|
|
2560
2698
|
}
|
|
2561
2699
|
|
|
2562
2700
|
/**
|
|
2563
|
-
* Programmatically create a new history entry with the given `page.state`.
|
|
2701
|
+
* Programmatically create a new history entry with the given `page.state`. Used for [shallow routing](https://svelte.dev/docs/kit/shallow-routing).
|
|
2564
2702
|
*
|
|
2703
|
+
* @deprecated Use `goto(url, { state, shallow: true })` instead.
|
|
2565
2704
|
* @param {string | URL} url
|
|
2566
2705
|
* @param {App.PageState} state
|
|
2567
|
-
* @returns {void}
|
|
2706
|
+
* @returns {Promise<void>}
|
|
2568
2707
|
*/
|
|
2569
|
-
export function pushState(url, state) {
|
|
2708
|
+
export async function pushState(url, state) {
|
|
2570
2709
|
if (!BROWSER) {
|
|
2571
2710
|
throw new Error('Cannot call pushState(...) on the server');
|
|
2572
2711
|
}
|
|
2573
2712
|
|
|
2574
|
-
if (DEV) {
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
try {
|
|
2580
|
-
// use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
|
|
2581
|
-
devalue.stringify(state);
|
|
2582
|
-
} catch (error) {
|
|
2583
|
-
// @ts-expect-error
|
|
2584
|
-
throw new Error(`Could not serialize state${error.path}`, { cause: error });
|
|
2585
|
-
}
|
|
2713
|
+
if (DEV && !warned_on_push_state) {
|
|
2714
|
+
warned_on_push_state = true;
|
|
2715
|
+
console.warn(
|
|
2716
|
+
'`pushState(...)` is deprecated. Use `goto(url, { state, shallow: true })` instead.'
|
|
2717
|
+
);
|
|
2586
2718
|
}
|
|
2587
2719
|
|
|
2588
|
-
|
|
2720
|
+
const intent = await resolve_intent(url, 'pushState');
|
|
2589
2721
|
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
history.pushState(opts, '', resolve_url(url));
|
|
2598
|
-
has_navigated = true;
|
|
2599
|
-
|
|
2600
|
-
page.state = state;
|
|
2601
|
-
|
|
2602
|
-
clear_onward_history(current_history_index, current_navigation_index);
|
|
2722
|
+
await update_state(
|
|
2723
|
+
intent,
|
|
2724
|
+
state,
|
|
2725
|
+
{ replace: false, persist_state: false, reset: false },
|
|
2726
|
+
'pushState'
|
|
2727
|
+
);
|
|
2603
2728
|
}
|
|
2604
2729
|
|
|
2605
2730
|
/**
|
|
2606
|
-
* Programmatically replace the current history entry with the given `page.state`.
|
|
2731
|
+
* Programmatically replace the current history entry with the given `page.state`. Used for [shallow routing](https://svelte.dev/docs/kit/shallow-routing).
|
|
2607
2732
|
*
|
|
2733
|
+
* @deprecated Use `goto(url, { state, shallow: true, replace: true })` instead.
|
|
2608
2734
|
* @param {string | URL} url
|
|
2609
2735
|
* @param {App.PageState} state
|
|
2610
|
-
* @returns {void}
|
|
2736
|
+
* @returns {Promise<void>}
|
|
2611
2737
|
*/
|
|
2612
|
-
export function replaceState(url, state) {
|
|
2738
|
+
export async function replaceState(url, state) {
|
|
2613
2739
|
if (!BROWSER) {
|
|
2614
2740
|
throw new Error('Cannot call replaceState(...) on the server');
|
|
2615
2741
|
}
|
|
2616
2742
|
|
|
2743
|
+
if (DEV && !warned_on_replace_state_function) {
|
|
2744
|
+
warned_on_replace_state_function = true;
|
|
2745
|
+
console.warn(
|
|
2746
|
+
'`replaceState(...)` is deprecated. Use `goto(url, { state, shallow: true, replace: true })` instead.'
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
const intent = await resolve_intent(url, 'replaceState');
|
|
2751
|
+
|
|
2752
|
+
await update_state(
|
|
2753
|
+
intent,
|
|
2754
|
+
state,
|
|
2755
|
+
{ replace: true, persist_state: false, reset: false },
|
|
2756
|
+
'replaceState'
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/**
|
|
2761
|
+
* @param {NavigationIntent} intent
|
|
2762
|
+
* @param {App.PageState} state
|
|
2763
|
+
* @param {{ replace: boolean; persist_state: boolean; reset: boolean; }} options
|
|
2764
|
+
* @param {'goto' | 'pushState' | 'replaceState'} caller
|
|
2765
|
+
*/
|
|
2766
|
+
async function update_state(intent, state, { replace, persist_state, reset }, caller) {
|
|
2767
|
+
const url = intent.url;
|
|
2768
|
+
|
|
2617
2769
|
if (DEV) {
|
|
2618
2770
|
if (!started) {
|
|
2619
|
-
throw new Error(
|
|
2771
|
+
throw new Error(`Cannot call ${caller}(...) before router is initialized`);
|
|
2620
2772
|
}
|
|
2621
2773
|
|
|
2622
2774
|
try {
|
|
@@ -2628,16 +2780,100 @@ export function replaceState(url, state) {
|
|
|
2628
2780
|
}
|
|
2629
2781
|
}
|
|
2630
2782
|
|
|
2631
|
-
const
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2783
|
+
const nav =
|
|
2784
|
+
// For backwards compatibility we don't trigger navigation hooks etc for push/replaceState
|
|
2785
|
+
caller === 'goto' ? _before_navigate({ url, type: 'goto', intent, shallow: true }) : undefined;
|
|
2786
|
+
|
|
2787
|
+
if (!nav && caller === 'goto') return;
|
|
2788
|
+
|
|
2789
|
+
const nav_token = {};
|
|
2790
|
+
|
|
2791
|
+
if (nav) {
|
|
2792
|
+
navigation_token = invalidation_token = nav_token;
|
|
2793
|
+
is_navigating = true;
|
|
2794
|
+
navigating.current = nav.navigation;
|
|
2795
|
+
updating = true;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
if (!replace) capture_scroll(current_history_index);
|
|
2799
|
+
if (reset) current_reset_index += 1;
|
|
2800
|
+
|
|
2801
|
+
const entry = {
|
|
2802
|
+
[HISTORY_METADATA_KEY]: /** @satisfies {HistoryMetadata} */ ({
|
|
2803
|
+
historyIndex: (current_history_index += replace ? 0 : 1),
|
|
2804
|
+
navigationIndex: current_navigation_index,
|
|
2805
|
+
pageUrl: page.url.href,
|
|
2806
|
+
state,
|
|
2807
|
+
persistState: persist_state,
|
|
2808
|
+
resetIndex: current_reset_index
|
|
2809
|
+
})
|
|
2636
2810
|
};
|
|
2637
2811
|
|
|
2638
|
-
history.replaceState
|
|
2812
|
+
const fn = replace ? history.replaceState : history.pushState;
|
|
2813
|
+
fn.call(history, entry, '', url);
|
|
2814
|
+
set_history_options(current_history_index, entry[HISTORY_METADATA_KEY]);
|
|
2815
|
+
|
|
2816
|
+
if (!replace) {
|
|
2817
|
+
has_navigated = true;
|
|
2818
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
if (nav) {
|
|
2822
|
+
const after_navigate = (
|
|
2823
|
+
await Promise.all(
|
|
2824
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable -- we need to await because they can be asynchronous
|
|
2825
|
+
Array.from(on_navigate_callbacks, (fn) =>
|
|
2826
|
+
fn(/** @type {import('@sveltejs/kit').OnNavigate} */ (nav.navigation))
|
|
2827
|
+
)
|
|
2828
|
+
)
|
|
2829
|
+
).filter(/** @returns {value is () => void} */ (value) => typeof value === 'function');
|
|
2830
|
+
|
|
2831
|
+
if (after_navigate.length > 0) {
|
|
2832
|
+
function cleanup() {
|
|
2833
|
+
after_navigate.forEach((fn) => after_navigate_callbacks.delete(fn));
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
after_navigate.push(cleanup);
|
|
2837
|
+
after_navigate.forEach((fn) => after_navigate_callbacks.add(fn));
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
blur_active_element(reset);
|
|
2639
2842
|
|
|
2640
2843
|
page.state = state;
|
|
2844
|
+
page.shallow = {
|
|
2845
|
+
params: intent?.params ?? null,
|
|
2846
|
+
route: intent ? { id: intent.route.id } : null,
|
|
2847
|
+
url
|
|
2848
|
+
};
|
|
2849
|
+
|
|
2850
|
+
if (!nav) return;
|
|
2851
|
+
|
|
2852
|
+
const { activeElement } = document;
|
|
2853
|
+
|
|
2854
|
+
await settled();
|
|
2855
|
+
|
|
2856
|
+
if (navigation_token !== nav_token) {
|
|
2857
|
+
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
2858
|
+
nav.reject(new Error('navigation aborted'));
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
reset_scroll_and_focus(url, reset ? null : scroll_state(), reset, activeElement);
|
|
2863
|
+
|
|
2864
|
+
is_navigating = false;
|
|
2865
|
+
nav.fulfil(undefined);
|
|
2866
|
+
|
|
2867
|
+
if (nav.navigation.to) {
|
|
2868
|
+
nav.navigation.to.scroll = scroll_state();
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
after_navigate_callbacks.forEach((fn) =>
|
|
2872
|
+
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
2873
|
+
);
|
|
2874
|
+
|
|
2875
|
+
navigating.current = null;
|
|
2876
|
+
updating = false;
|
|
2641
2877
|
}
|
|
2642
2878
|
|
|
2643
2879
|
/**
|
|
@@ -2662,15 +2898,13 @@ export async function applyAction(result) {
|
|
|
2662
2898
|
page.status = result.status;
|
|
2663
2899
|
|
|
2664
2900
|
/** @type {Record<string, any>} */
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
form: null
|
|
2669
|
-
});
|
|
2901
|
+
// this brings Svelte's view of the world in line with SvelteKit's
|
|
2902
|
+
// after use:enhance reset the form....
|
|
2903
|
+
props.form = null;
|
|
2670
2904
|
|
|
2671
2905
|
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
2672
|
-
await
|
|
2673
|
-
|
|
2906
|
+
await tick();
|
|
2907
|
+
props.form = result.data;
|
|
2674
2908
|
|
|
2675
2909
|
if (result.type === 'success') {
|
|
2676
2910
|
reset_focus(/** @type {URL} */ (page.url));
|
|
@@ -2700,11 +2934,9 @@ export async function set_nearest_error_page(error) {
|
|
|
2700
2934
|
|
|
2701
2935
|
current = { ...navigation_result.state, nav: current.nav };
|
|
2702
2936
|
|
|
2703
|
-
|
|
2704
|
-
current_tree = navigation_result.props.tree;
|
|
2705
|
-
update(navigation_result.props.page);
|
|
2937
|
+
apply_navigation_result(navigation_result);
|
|
2706
2938
|
|
|
2707
|
-
void
|
|
2939
|
+
void tick().then(() => reset_focus(current.url));
|
|
2708
2940
|
}
|
|
2709
2941
|
}
|
|
2710
2942
|
|
|
@@ -2744,13 +2976,19 @@ function _start_router() {
|
|
|
2744
2976
|
history.scrollRestoration = 'auto';
|
|
2745
2977
|
}
|
|
2746
2978
|
});
|
|
2747
|
-
|
|
2748
2979
|
addEventListener('visibilitychange', () => {
|
|
2749
2980
|
if (document.visibilityState === 'hidden') {
|
|
2750
2981
|
persist_state();
|
|
2982
|
+
} else {
|
|
2983
|
+
// the tab just became visible — a good time to check for a new deployment
|
|
2984
|
+
void updated.check();
|
|
2751
2985
|
}
|
|
2752
2986
|
});
|
|
2753
2987
|
|
|
2988
|
+
addEventListener('focus', () => {
|
|
2989
|
+
void updated.check();
|
|
2990
|
+
});
|
|
2991
|
+
|
|
2754
2992
|
// @ts-expect-error this isn't supported everywhere yet
|
|
2755
2993
|
if (!navigator.connection?.saveData) {
|
|
2756
2994
|
setup_preload();
|
|
@@ -2841,17 +3079,21 @@ function _start_router() {
|
|
|
2841
3079
|
return;
|
|
2842
3080
|
}
|
|
2843
3081
|
// set this flag to distinguish between navigations triggered by
|
|
2844
|
-
// clicking a hash link and those triggered by popstate
|
|
2845
|
-
|
|
3082
|
+
// clicking a hash link and those triggered by popstate. We gotta retrieve
|
|
3083
|
+
// history metadata here because the hashchange event will occur after history.state was updated
|
|
3084
|
+
hash_navigating = {
|
|
3085
|
+
.../** @type {HistoryMetadata} */ (get_history_metadata()),
|
|
3086
|
+
resetIndex: current_reset_index + (options.reset ? 1 : 0)
|
|
3087
|
+
};
|
|
2846
3088
|
|
|
2847
|
-
|
|
3089
|
+
capture_scroll(current_history_index);
|
|
2848
3090
|
|
|
2849
3091
|
update_url(url);
|
|
2850
3092
|
|
|
2851
3093
|
if (!options.replace_state) return;
|
|
2852
3094
|
|
|
2853
3095
|
// hashchange event shouldn't occur if the router is replacing state.
|
|
2854
|
-
hash_navigating =
|
|
3096
|
+
hash_navigating = null;
|
|
2855
3097
|
}
|
|
2856
3098
|
|
|
2857
3099
|
event.preventDefault();
|
|
@@ -2869,8 +3111,7 @@ function _start_router() {
|
|
|
2869
3111
|
await navigate({
|
|
2870
3112
|
type: 'link',
|
|
2871
3113
|
url,
|
|
2872
|
-
|
|
2873
|
-
noscroll: options.noscroll,
|
|
3114
|
+
reset: options.reset,
|
|
2874
3115
|
replace_state: options.replace_state ?? url.href === location.href,
|
|
2875
3116
|
event
|
|
2876
3117
|
});
|
|
@@ -2916,8 +3157,7 @@ function _start_router() {
|
|
|
2916
3157
|
void navigate({
|
|
2917
3158
|
type: 'form',
|
|
2918
3159
|
url,
|
|
2919
|
-
|
|
2920
|
-
noscroll: options.noscroll,
|
|
3160
|
+
reset: options.reset,
|
|
2921
3161
|
replace_state: options.replace_state ?? url.href === location.href,
|
|
2922
3162
|
event
|
|
2923
3163
|
});
|
|
@@ -2926,53 +3166,82 @@ function _start_router() {
|
|
|
2926
3166
|
addEventListener('popstate', async (event) => {
|
|
2927
3167
|
if (resetting_focus) return;
|
|
2928
3168
|
|
|
2929
|
-
|
|
2930
|
-
|
|
3169
|
+
const history_metadata = get_history_metadata(event.state);
|
|
3170
|
+
|
|
3171
|
+
if (history_metadata?.historyIndex) {
|
|
3172
|
+
const history_index = history_metadata.historyIndex;
|
|
3173
|
+
const source_info = history_info[current_history_index];
|
|
2931
3174
|
navigation_token = invalidation_token = {};
|
|
2932
3175
|
|
|
2933
3176
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2934
3177
|
// with history.go, which means we end up back here, hence this check
|
|
2935
3178
|
if (history_index === current_history_index) return;
|
|
2936
3179
|
|
|
2937
|
-
const
|
|
2938
|
-
const
|
|
2939
|
-
const
|
|
2940
|
-
const
|
|
2941
|
-
const
|
|
3180
|
+
const delta = history_index - current_history_index;
|
|
3181
|
+
const reset_index = history_metadata.resetIndex;
|
|
3182
|
+
const reset = reset_index !== (source_info?.resetIndex ?? current_reset_index);
|
|
3183
|
+
const scroll = history_info[history_index]?.scroll;
|
|
3184
|
+
const state = history_metadata.state;
|
|
3185
|
+
const url = new URL(history_metadata.pageUrl ?? location.href);
|
|
3186
|
+
const navigation_index = history_metadata.navigationIndex;
|
|
3187
|
+
const is_hash_change =
|
|
3188
|
+
current.url && (location.href + current.url.href).includes('#') // check if even has a hash
|
|
3189
|
+
? strip_hash(location) === strip_hash(current.url)
|
|
3190
|
+
: false;
|
|
2942
3191
|
const shallow =
|
|
2943
|
-
navigation_index === current_navigation_index &&
|
|
3192
|
+
navigation_index === current_navigation_index &&
|
|
3193
|
+
((has_navigated &&
|
|
3194
|
+
(history_metadata.pageUrl === undefined || history_metadata.pageUrl === location.href)) ||
|
|
3195
|
+
is_hash_change);
|
|
3196
|
+
const shallow_url = history_metadata.pageUrl ? new URL(location.href) : null;
|
|
3197
|
+
const shallow_intent = shallow_url
|
|
3198
|
+
? await get_navigation_intent(shallow_url, false)
|
|
3199
|
+
: undefined;
|
|
3200
|
+
const shallow_target = shallow_url
|
|
3201
|
+
? {
|
|
3202
|
+
params: shallow_intent?.params ?? null,
|
|
3203
|
+
route: shallow_intent ? { id: shallow_intent.route.id } : null,
|
|
3204
|
+
url: shallow_url
|
|
3205
|
+
}
|
|
3206
|
+
: null;
|
|
2944
3207
|
|
|
2945
3208
|
if (shallow) {
|
|
2946
3209
|
// We don't need to navigate, we just need to update scroll and/or state.
|
|
2947
3210
|
// This happens with hash links and `pushState`/`replaceState`. The
|
|
2948
3211
|
// exception is if we haven't navigated yet, since we could have
|
|
2949
3212
|
// got here after a modal navigation then a reload
|
|
3213
|
+
|
|
3214
|
+
blur_active_element(reset);
|
|
3215
|
+
|
|
2950
3216
|
if (state !== page.state) {
|
|
2951
3217
|
page.state = state;
|
|
2952
3218
|
}
|
|
2953
3219
|
|
|
2954
|
-
|
|
3220
|
+
page.shallow = shallow_target;
|
|
2955
3221
|
|
|
2956
|
-
|
|
2957
|
-
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
3222
|
+
update_url(url);
|
|
2958
3223
|
|
|
3224
|
+
capture_scroll(current_history_index);
|
|
2959
3225
|
current_history_index = history_index;
|
|
3226
|
+
current_reset_index = reset_index;
|
|
3227
|
+
if (reset && scroll) scrollTo(scroll.x, scroll.y);
|
|
2960
3228
|
return;
|
|
2961
3229
|
}
|
|
2962
3230
|
|
|
2963
|
-
const delta = history_index - current_history_index;
|
|
2964
|
-
|
|
2965
3231
|
await navigate({
|
|
2966
3232
|
type: 'popstate',
|
|
2967
3233
|
url,
|
|
3234
|
+
reset,
|
|
2968
3235
|
popped: {
|
|
2969
3236
|
state,
|
|
2970
3237
|
scroll,
|
|
2971
|
-
delta
|
|
3238
|
+
delta,
|
|
3239
|
+
shallow: shallow_target
|
|
2972
3240
|
},
|
|
2973
3241
|
accept: () => {
|
|
2974
3242
|
current_history_index = history_index;
|
|
2975
3243
|
current_navigation_index = navigation_index;
|
|
3244
|
+
current_reset_index = reset_index;
|
|
2976
3245
|
},
|
|
2977
3246
|
block: () => {
|
|
2978
3247
|
history.go(-delta);
|
|
@@ -3001,16 +3270,22 @@ function _start_router() {
|
|
|
3001
3270
|
// if the hashchange happened as a result of clicking on a link,
|
|
3002
3271
|
// we need to update history, otherwise we have to leave it alone
|
|
3003
3272
|
if (hash_navigating) {
|
|
3004
|
-
|
|
3273
|
+
const history_metadata = hash_navigating;
|
|
3274
|
+
hash_navigating = null;
|
|
3275
|
+
current_reset_index = history_metadata.resetIndex;
|
|
3005
3276
|
history.replaceState(
|
|
3006
3277
|
{
|
|
3007
3278
|
...history.state,
|
|
3008
|
-
[
|
|
3009
|
-
|
|
3279
|
+
[HISTORY_METADATA_KEY]: {
|
|
3280
|
+
...history_metadata,
|
|
3281
|
+
historyIndex: ++current_history_index,
|
|
3282
|
+
navigationIndex: current_navigation_index
|
|
3283
|
+
}
|
|
3010
3284
|
},
|
|
3011
3285
|
'',
|
|
3012
3286
|
location.href
|
|
3013
3287
|
);
|
|
3288
|
+
set_history_options(current_history_index, history_metadata);
|
|
3014
3289
|
}
|
|
3015
3290
|
});
|
|
3016
3291
|
|
|
@@ -3077,7 +3352,7 @@ async function _hydrate(
|
|
|
3077
3352
|
|
|
3078
3353
|
/** @type {import('./types.js').NavigationFinished | undefined} */
|
|
3079
3354
|
let result;
|
|
3080
|
-
let
|
|
3355
|
+
let should_hydrate = true;
|
|
3081
3356
|
|
|
3082
3357
|
try {
|
|
3083
3358
|
const branch_promises = node_ids.map(async (n, i) => {
|
|
@@ -3143,7 +3418,7 @@ async function _hydrate(
|
|
|
3143
3418
|
});
|
|
3144
3419
|
|
|
3145
3420
|
target.textContent = '';
|
|
3146
|
-
|
|
3421
|
+
should_hydrate = false;
|
|
3147
3422
|
}
|
|
3148
3423
|
|
|
3149
3424
|
// Exit early when we encounter a redirect while loading the root error page.
|
|
@@ -3151,10 +3426,11 @@ async function _hydrate(
|
|
|
3151
3426
|
if (!result) return;
|
|
3152
3427
|
|
|
3153
3428
|
if (result.props.page) {
|
|
3154
|
-
|
|
3429
|
+
const history_metadata = get_history_metadata();
|
|
3430
|
+
result.props.page.state = history_metadata?.persistState ? history_metadata.state : {};
|
|
3155
3431
|
}
|
|
3156
3432
|
|
|
3157
|
-
await initialize(result, target,
|
|
3433
|
+
await initialize(result, target, should_hydrate);
|
|
3158
3434
|
}
|
|
3159
3435
|
|
|
3160
3436
|
/**
|
|
@@ -3177,21 +3453,23 @@ async function load_data(url, invalid) {
|
|
|
3177
3453
|
const fetcher = DEV ? dev_fetch : window.fetch;
|
|
3178
3454
|
const res = await fetcher(data_url.href, {});
|
|
3179
3455
|
|
|
3456
|
+
// detect new deployments from the response header
|
|
3457
|
+
notify_version(res.headers.get('x-sveltekit-version'));
|
|
3458
|
+
|
|
3180
3459
|
if (!res.ok) {
|
|
3181
|
-
// error message is a JSON-stringified string which devalue can't handle at the top level
|
|
3182
3460
|
// turn it into a HttpError to not call handleError on the client again (was already handled on the server)
|
|
3183
3461
|
// if `__data.json` doesn't exist or the server has an internal error,
|
|
3184
3462
|
// avoid parsing the HTML error page as a JSON
|
|
3185
|
-
/** @type {
|
|
3186
|
-
let message;
|
|
3463
|
+
/** @type {App.Error} */
|
|
3464
|
+
let error = { status: res.status, message: 'Internal Error' };
|
|
3465
|
+
|
|
3187
3466
|
if (res.headers.get('content-type')?.includes('application/json')) {
|
|
3188
|
-
|
|
3467
|
+
error = { status: res.status, ...(await res.json()) };
|
|
3189
3468
|
} else if (res.status === 404) {
|
|
3190
|
-
message = 'Not Found';
|
|
3191
|
-
} else if (res.status === 500) {
|
|
3192
|
-
message = 'Internal Error';
|
|
3469
|
+
error.message = 'Not Found';
|
|
3193
3470
|
}
|
|
3194
|
-
|
|
3471
|
+
|
|
3472
|
+
throw new HttpError(error);
|
|
3195
3473
|
}
|
|
3196
3474
|
|
|
3197
3475
|
return new Promise((resolve, reject) => {
|
|
@@ -3294,8 +3572,8 @@ function reset_focus(url, scroll = true) {
|
|
|
3294
3572
|
|
|
3295
3573
|
// Mimic the browsers' behaviour and set the sequential focus navigation
|
|
3296
3574
|
// starting point to the fragment identifier.
|
|
3297
|
-
const
|
|
3298
|
-
if (
|
|
3575
|
+
const element = get_hash_element(url);
|
|
3576
|
+
if (element) {
|
|
3299
3577
|
const { x, y } = scroll_state();
|
|
3300
3578
|
|
|
3301
3579
|
// `element.focus()` doesn't work on Safari and Firefox Ubuntu so we need
|
|
@@ -3304,7 +3582,7 @@ function reset_focus(url, scroll = true) {
|
|
|
3304
3582
|
const history_state = history.state;
|
|
3305
3583
|
|
|
3306
3584
|
resetting_focus = true;
|
|
3307
|
-
location.replace(new URL(`#${id}`, location.href));
|
|
3585
|
+
location.replace(new URL(`#${element.id}`, location.href));
|
|
3308
3586
|
|
|
3309
3587
|
// Firefox has a bug that sets the history state to `null` so we need to
|
|
3310
3588
|
// restore it after. See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
|
|
@@ -3384,8 +3662,18 @@ function reset_focus(url, scroll = true) {
|
|
|
3384
3662
|
* @param {URL | null} url
|
|
3385
3663
|
* @param {T} type
|
|
3386
3664
|
* @param {{ x: number, y: number } | null} [target_scroll] The scroll position for the target (for popstate navigations)
|
|
3665
|
+
* @param {boolean} [shallow]
|
|
3666
|
+
* @param {{ params: Record<string, string> | null; route: { id: string } | null; url: URL }} [target]
|
|
3387
3667
|
*/
|
|
3388
|
-
function create_navigation(
|
|
3668
|
+
function create_navigation(
|
|
3669
|
+
current,
|
|
3670
|
+
intent,
|
|
3671
|
+
url,
|
|
3672
|
+
type,
|
|
3673
|
+
target_scroll = null,
|
|
3674
|
+
shallow = false,
|
|
3675
|
+
target
|
|
3676
|
+
) {
|
|
3389
3677
|
/** @type {(value: any) => void} */
|
|
3390
3678
|
let fulfil;
|
|
3391
3679
|
|
|
@@ -3409,13 +3697,14 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
|
|
|
3409
3697
|
scroll: scroll_state()
|
|
3410
3698
|
},
|
|
3411
3699
|
to: url && {
|
|
3412
|
-
params: intent?.params ?? null,
|
|
3413
|
-
route: { id: intent?.route?.id ?? null },
|
|
3414
|
-
url,
|
|
3700
|
+
params: target ? target.params : (intent?.params ?? null),
|
|
3701
|
+
route: { id: target ? (target.route?.id ?? null) : (intent?.route?.id ?? null) },
|
|
3702
|
+
url: target?.url ?? url,
|
|
3415
3703
|
scroll: target_scroll
|
|
3416
3704
|
},
|
|
3417
|
-
willUnload: !intent,
|
|
3705
|
+
willUnload: !shallow && !intent,
|
|
3418
3706
|
type,
|
|
3707
|
+
shallow,
|
|
3419
3708
|
complete
|
|
3420
3709
|
});
|
|
3421
3710
|
|
|
@@ -3456,6 +3745,15 @@ function get_id(url) {
|
|
|
3456
3745
|
return decodeURIComponent(id);
|
|
3457
3746
|
}
|
|
3458
3747
|
|
|
3748
|
+
/**
|
|
3749
|
+
* @param {URL} url
|
|
3750
|
+
* @returns {Element | null}
|
|
3751
|
+
*/
|
|
3752
|
+
function get_hash_element(url) {
|
|
3753
|
+
const id = get_id(url);
|
|
3754
|
+
return id ? document.getElementById(id) : null;
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3459
3757
|
if (DEV) {
|
|
3460
3758
|
// Nasty hack to silence harmless warnings the user can do nothing about
|
|
3461
3759
|
const console_warn = console.warn;
|
|
@@ -3479,3 +3777,21 @@ if (DEV) {
|
|
|
3479
3777
|
});
|
|
3480
3778
|
}
|
|
3481
3779
|
}
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* @param {NavigationFinished} result
|
|
3783
|
+
*/
|
|
3784
|
+
function apply_navigation_result(result) {
|
|
3785
|
+
Object.assign(page, result.props.page);
|
|
3786
|
+
|
|
3787
|
+
props.tree.data = result.props.tree.data;
|
|
3788
|
+
props.tree.child = result.props.tree.child;
|
|
3789
|
+
|
|
3790
|
+
if ('form' in result.props) {
|
|
3791
|
+
props.form = result.props.form;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
if ('error' in result.props) {
|
|
3795
|
+
props.error = result.props.error;
|
|
3796
|
+
}
|
|
3797
|
+
}
|