@sveltejs/kit 3.0.0-next.12 → 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/core/postbuild/prerender.js +3 -3
- package/src/core/sync/write_app_types.js +1 -2
- package/src/core/sync/write_tsconfig/index.js +13 -1
- package/src/exports/index.js +23 -12
- package/src/exports/internal/shared.js +4 -12
- package/src/exports/public.d.ts +59 -11
- package/src/exports/vite/build/remote.js +10 -12
- package/src/exports/vite/dev/index.js +32 -19
- package/src/exports/vite/index.js +118 -123
- package/src/runtime/app/server/remote/form.js +9 -1
- package/src/runtime/app/server/remote/prerender.js +13 -9
- package/src/runtime/app/server/remote/requested.js +4 -4
- package/src/runtime/app/state/client.js +3 -0
- package/src/runtime/app/state/index.js +2 -2
- package/src/runtime/app/state/server.js +3 -0
- package/src/runtime/client/client.js +584 -305
- 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 +39 -7
- package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query/instance.svelte.js +2 -2
- package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +2 -2
- package/src/runtime/client/remote-functions/query-live/iterator.js +10 -8
- package/src/runtime/client/remote-functions/shared.svelte.js +11 -10
- package/src/runtime/client/state.svelte.js +10 -22
- 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/props.svelte.js +71 -0
- package/src/runtime/server/fetch.js +4 -6
- 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 +27 -24
- package/src/runtime/server/page/respond_with_error.js +1 -3
- package/src/runtime/server/respond.js +0 -2
- package/src/types/ambient-private.d.ts +1 -1
- package/src/types/ambient.d.ts +1 -1
- package/src/types/internal.d.ts +2 -7
- package/src/types/private.d.ts +3 -12
- package/src/version.js +1 -1
- package/types/index.d.ts +92 -57
- package/types/index.d.ts.map +2 -1
- package/src/runtime/types.d.ts +0 -8
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/** @import { RouteId } from '$app/types' */
|
|
2
2
|
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
3
|
-
/** @import { NavigationIntent } from './types.js' */
|
|
4
|
-
/** @import { RenderNode } from '../types.js' */
|
|
3
|
+
/** @import { NavigationFinished, NavigationIntent } from './types.js' */
|
|
5
4
|
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
|
|
6
5
|
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
|
|
7
6
|
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
|
|
8
7
|
import { BROWSER, DEV } from 'esm-env';
|
|
9
|
-
import
|
|
8
|
+
import { settled, tick, fork, onMount, hydrate, mount } from 'svelte';
|
|
10
9
|
import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
11
10
|
import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
|
|
12
11
|
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
|
|
@@ -25,13 +24,10 @@ import {
|
|
|
25
24
|
import { base, set_match_implementation } from '$app/paths/internal/client';
|
|
26
25
|
import * as devalue from 'devalue';
|
|
27
26
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
HISTORY_INFO_KEY,
|
|
28
|
+
HISTORY_METADATA_KEY,
|
|
30
29
|
PRELOAD_PRIORITIES,
|
|
31
|
-
|
|
32
|
-
STATES_KEY,
|
|
33
|
-
SNAPSHOT_KEY,
|
|
34
|
-
PAGE_URL_KEY
|
|
30
|
+
SNAPSHOT_KEY
|
|
35
31
|
} from './constants.js';
|
|
36
32
|
import { validate_page_exports } from '../../utils/exports.js';
|
|
37
33
|
import { noop } from '../../utils/functions.js';
|
|
@@ -43,28 +39,29 @@ import {
|
|
|
43
39
|
validate_load_response
|
|
44
40
|
} from '../shared.js';
|
|
45
41
|
import { get_message, get_status } from '../../utils/error.js';
|
|
46
|
-
import { page,
|
|
42
|
+
import { page, navigating, updated, notify_version } from './state.svelte.js';
|
|
47
43
|
import { payload } from './payload.js';
|
|
48
44
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
49
45
|
import { noop_span } from '../telemetry/noop.js';
|
|
50
46
|
import { read_ndjson } from './ndjson.js';
|
|
51
|
-
import
|
|
52
|
-
import {
|
|
47
|
+
import Root from '../components/root.svelte';
|
|
48
|
+
import { Props, RenderNode } from '../props.svelte.js';
|
|
53
49
|
|
|
54
|
-
|
|
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
|
+
*/
|
|
55
60
|
|
|
56
61
|
export { load_css };
|
|
57
62
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
58
63
|
|
|
59
64
|
let errored = false;
|
|
60
|
-
/**
|
|
61
|
-
* Set via transformError, reset and read at the end of navigate.
|
|
62
|
-
* Necessary because a navigation might succeed loading but during rendering
|
|
63
|
-
* an error occurs, at which point the navigation result needs to be overridden with the error result.
|
|
64
|
-
* TODO this is all very hacky, rethink for SvelteKit 3 where we can assume Svelte 5 and do an overhaul of client.js
|
|
65
|
-
* @type {{ error: App.Error, status: number } | null}
|
|
66
|
-
*/
|
|
67
|
-
let rendering_error = null;
|
|
68
65
|
|
|
69
66
|
/**
|
|
70
67
|
* `reset` functions for `<svelte:boundary>`s in the generated root that have
|
|
@@ -73,19 +70,18 @@ let rendering_error = null;
|
|
|
73
70
|
* navigation away from a render error would leave the stale `+error.svelte`
|
|
74
71
|
* mounted. The boundary's `onerror` populates this array; `navigate` drains it
|
|
75
72
|
* after applying the new props. See sveltejs/kit#15694.
|
|
76
|
-
* @type {
|
|
73
|
+
* @type {Set<() => void>}
|
|
77
74
|
*/
|
|
78
|
-
const resetters =
|
|
75
|
+
const resetters = new Set();
|
|
79
76
|
|
|
80
|
-
// We track
|
|
77
|
+
// We track information associated with each history entry in sessionStorage,
|
|
81
78
|
// rather than on history.state itself, because when navigation is driven by
|
|
82
|
-
// 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
|
|
83
80
|
// state we're navigating from
|
|
84
81
|
/**
|
|
85
|
-
*
|
|
86
|
-
* @type {Record<number, { x: number; y: number }>}
|
|
82
|
+
* @type {Record<number, { scroll?: { x: number; y: number }; resetIndex?: number }>}
|
|
87
83
|
*/
|
|
88
|
-
const
|
|
84
|
+
const history_info = storage.get(HISTORY_INFO_KEY) ?? {};
|
|
89
85
|
|
|
90
86
|
/**
|
|
91
87
|
* navigation index -> any
|
|
@@ -93,14 +89,14 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
|
93
89
|
*/
|
|
94
90
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
95
91
|
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
*/
|
|
99
|
-
let current_tree = /** @type {RenderNode} */ ({});
|
|
92
|
+
/** @type {Props} */
|
|
93
|
+
let props;
|
|
100
94
|
|
|
101
95
|
if (DEV && BROWSER) {
|
|
102
96
|
let warned = false;
|
|
103
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
|
+
|
|
104
100
|
const warn = () => {
|
|
105
101
|
if (warned) return;
|
|
106
102
|
|
|
@@ -113,14 +109,15 @@ if (DEV && BROWSER) {
|
|
|
113
109
|
// skip over `warn` and the place where `warn` was called
|
|
114
110
|
const frame = stack[2];
|
|
115
111
|
|
|
116
|
-
//
|
|
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)
|
|
117
114
|
// `frame` can be falsy if we came from an anonymous function
|
|
118
|
-
if (frame?.includes('node_modules')) return;
|
|
115
|
+
if (frame?.includes('node_modules') || frame?.includes(current_module_url)) return;
|
|
119
116
|
|
|
120
117
|
warned = true;
|
|
121
118
|
|
|
122
119
|
console.warn(
|
|
123
|
-
"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."
|
|
124
121
|
);
|
|
125
122
|
};
|
|
126
123
|
|
|
@@ -138,8 +135,60 @@ if (DEV && BROWSER) {
|
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
/** @param {number} index */
|
|
141
|
-
function
|
|
142
|
-
|
|
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;
|
|
143
192
|
}
|
|
144
193
|
|
|
145
194
|
/**
|
|
@@ -150,8 +199,8 @@ function clear_onward_history(current_history_index, current_navigation_index) {
|
|
|
150
199
|
// if we navigated back, then pushed a new state, we can
|
|
151
200
|
// release memory by pruning the scroll/snapshot lookup
|
|
152
201
|
let i = current_history_index + 1;
|
|
153
|
-
while (
|
|
154
|
-
delete
|
|
202
|
+
while (history_info[i]) {
|
|
203
|
+
delete history_info[i];
|
|
155
204
|
i += 1;
|
|
156
205
|
}
|
|
157
206
|
|
|
@@ -225,14 +274,6 @@ export const prerender_responses = {};
|
|
|
225
274
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
226
275
|
const invalidated = [];
|
|
227
276
|
|
|
228
|
-
/**
|
|
229
|
-
* An array of the `+layout.svelte` and `+page.svelte` component instances
|
|
230
|
-
* that currently live on the page — used for capturing and restoring snapshots.
|
|
231
|
-
* It's updated/manipulated through `bind:this` in `Root.svelte`.
|
|
232
|
-
* @type {import('svelte').SvelteComponent[]}
|
|
233
|
-
*/
|
|
234
|
-
const components = [];
|
|
235
|
-
|
|
236
277
|
/** @type {{id: string, token: {}, promise: Promise<import('./types.js').NavigationResult>, fork: Promise<import('svelte').Fork | null> | null} | null} */
|
|
237
278
|
let load_cache = null;
|
|
238
279
|
|
|
@@ -282,21 +323,30 @@ let started = false;
|
|
|
282
323
|
let autoscroll = true;
|
|
283
324
|
let updating = false;
|
|
284
325
|
let is_navigating = false;
|
|
285
|
-
|
|
326
|
+
/** @type {HistoryMetadata | null} */
|
|
327
|
+
let hash_navigating = null;
|
|
286
328
|
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
287
329
|
let has_navigated = false;
|
|
288
330
|
|
|
289
331
|
let force_invalidation = false;
|
|
290
332
|
|
|
291
|
-
/** @type {import('svelte').SvelteComponent} */
|
|
292
|
-
let root;
|
|
293
|
-
|
|
294
333
|
/** @type {number} keeping track of the history index in order to prevent popstate navigation events if needed */
|
|
295
334
|
let current_history_index;
|
|
296
335
|
|
|
297
336
|
/** @type {number} */
|
|
298
337
|
let current_navigation_index;
|
|
299
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
|
+
|
|
300
350
|
/** @type {{}} Token for the latest navigation. Updated on new navigations */
|
|
301
351
|
let navigation_token;
|
|
302
352
|
|
|
@@ -319,7 +369,7 @@ let invalidation_token;
|
|
|
319
369
|
const preload_tokens = new Set();
|
|
320
370
|
|
|
321
371
|
/** @type {Promise<void> | null} */
|
|
322
|
-
|
|
372
|
+
let pending_invalidate;
|
|
323
373
|
|
|
324
374
|
/**
|
|
325
375
|
* @type {Map<string, Map<string, CacheEntry<Query<any>>>>}
|
|
@@ -350,12 +400,33 @@ set_match_implementation(async (url) => {
|
|
|
350
400
|
return null;
|
|
351
401
|
});
|
|
352
402
|
|
|
403
|
+
let embedded_start = Promise.resolve();
|
|
404
|
+
|
|
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
|
|
410
|
+
* @param {import('./types.js').SvelteKitApp} _app
|
|
411
|
+
* @param {HTMLElement} _target
|
|
412
|
+
* @param {Parameters<typeof _hydrate>[1]} [data]
|
|
413
|
+
*/
|
|
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
|
+
|
|
353
424
|
/**
|
|
354
425
|
* @param {import('./types.js').SvelteKitApp} _app
|
|
355
426
|
* @param {HTMLElement} _target
|
|
356
|
-
* @param {Parameters<typeof _hydrate>[1]} [
|
|
427
|
+
* @param {Parameters<typeof _hydrate>[1]} [data]
|
|
357
428
|
*/
|
|
358
|
-
|
|
429
|
+
async function _start(_app, _target, data) {
|
|
359
430
|
if (DEV && _target === document.body) {
|
|
360
431
|
console.warn(
|
|
361
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>'
|
|
@@ -393,31 +464,56 @@ export async function start(_app, _target, hydrate) {
|
|
|
393
464
|
// connectivity errors after initialisation don't nuke the app
|
|
394
465
|
default_layout_loader = _app.nodes[0];
|
|
395
466
|
default_error_loader = _app.nodes[1];
|
|
396
|
-
void default_layout_loader();
|
|
397
|
-
void default_error_loader();
|
|
398
467
|
|
|
399
|
-
|
|
400
|
-
|
|
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;
|
|
401
487
|
|
|
402
488
|
if (!current_history_index) {
|
|
403
489
|
// we use Date.now() as an offset so that cross-document navigations
|
|
404
490
|
// within the app don't result in data loss
|
|
405
|
-
current_history_index = current_navigation_index = Date.now();
|
|
491
|
+
current_history_index = current_navigation_index = current_reset_index = Date.now();
|
|
406
492
|
|
|
407
493
|
// create initial history entry, so we can return here
|
|
408
494
|
history.replaceState(
|
|
409
495
|
{
|
|
410
496
|
...history.state,
|
|
411
|
-
[
|
|
412
|
-
|
|
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
|
+
}
|
|
413
504
|
},
|
|
414
505
|
''
|
|
415
506
|
);
|
|
416
507
|
}
|
|
417
508
|
|
|
509
|
+
set_history_options(
|
|
510
|
+
current_history_index,
|
|
511
|
+
/** @type {HistoryMetadata} */ (get_history_metadata())
|
|
512
|
+
);
|
|
513
|
+
|
|
418
514
|
// if we reload the page, or Cmd-Shift-T back to it,
|
|
419
515
|
// recover scroll position
|
|
420
|
-
const scroll =
|
|
516
|
+
const scroll = history_info[current_history_index]?.scroll;
|
|
421
517
|
function restore_scroll() {
|
|
422
518
|
if (scroll) {
|
|
423
519
|
history.scrollRestoration = 'manual';
|
|
@@ -425,15 +521,17 @@ export async function start(_app, _target, hydrate) {
|
|
|
425
521
|
}
|
|
426
522
|
}
|
|
427
523
|
|
|
428
|
-
if (
|
|
524
|
+
if (data) {
|
|
429
525
|
restore_scroll();
|
|
430
526
|
|
|
431
|
-
await _hydrate(target,
|
|
527
|
+
await _hydrate(target, data);
|
|
432
528
|
} else {
|
|
433
529
|
await navigate({
|
|
434
530
|
type: 'enter',
|
|
435
531
|
url: resolve_url(app.hash ? decode_hash(new URL(location.href)) : location.href),
|
|
436
|
-
replace_state: true
|
|
532
|
+
replace_state: true,
|
|
533
|
+
state: history_metadata?.persistState ? history_metadata.state : {},
|
|
534
|
+
persist_state: history_metadata?.persistState ?? false
|
|
437
535
|
});
|
|
438
536
|
|
|
439
537
|
restore_scroll();
|
|
@@ -482,6 +580,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
482
580
|
}
|
|
483
581
|
|
|
484
582
|
const prev_state = page.state;
|
|
583
|
+
const prev_shallow = page.shallow;
|
|
485
584
|
const navigation_result = intent && (await load_route(intent));
|
|
486
585
|
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
|
|
487
586
|
return;
|
|
@@ -490,7 +589,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
490
589
|
if (navigation_result.type === 'redirect') {
|
|
491
590
|
return _goto(
|
|
492
591
|
new URL(navigation_result.location, current.url).href,
|
|
493
|
-
{
|
|
592
|
+
{ replace: true },
|
|
494
593
|
1,
|
|
495
594
|
token
|
|
496
595
|
);
|
|
@@ -506,11 +605,10 @@ async function _invalidate(reset_page_state = true) {
|
|
|
506
605
|
if (!reset_page_state) {
|
|
507
606
|
navigation_result.props.page.state = prev_state;
|
|
508
607
|
}
|
|
509
|
-
|
|
510
|
-
|
|
608
|
+
navigation_result.props.page.shallow = prev_shallow;
|
|
609
|
+
apply_navigation_result(navigation_result);
|
|
511
610
|
current = { ...navigation_result.state, nav: current.nav };
|
|
512
611
|
reset_invalidation();
|
|
513
|
-
root.$set(navigation_result.props);
|
|
514
612
|
|
|
515
613
|
// only wait for promises that are connected to queries that still exist
|
|
516
614
|
/** @type {Promise<any>[]} */
|
|
@@ -541,21 +639,21 @@ function reset_invalidation() {
|
|
|
541
639
|
|
|
542
640
|
/** @param {number} index */
|
|
543
641
|
function capture_snapshot(index) {
|
|
544
|
-
if (components.some((c) => c?.snapshot)) {
|
|
545
|
-
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());
|
|
546
644
|
}
|
|
547
645
|
}
|
|
548
646
|
|
|
549
647
|
/** @param {number} index */
|
|
550
648
|
function restore_snapshot(index) {
|
|
551
649
|
snapshots[index]?.forEach((value, i) => {
|
|
552
|
-
components[i]?.snapshot?.restore(value);
|
|
650
|
+
props.components[i]?.snapshot?.restore(value);
|
|
553
651
|
});
|
|
554
652
|
}
|
|
555
653
|
|
|
556
654
|
function persist_state() {
|
|
557
|
-
|
|
558
|
-
storage.set(
|
|
655
|
+
capture_scroll(current_history_index);
|
|
656
|
+
storage.set(HISTORY_INFO_KEY, history_info);
|
|
559
657
|
|
|
560
658
|
capture_snapshot(current_navigation_index);
|
|
561
659
|
storage.set(SNAPSHOT_KEY, snapshots);
|
|
@@ -563,7 +661,7 @@ function persist_state() {
|
|
|
563
661
|
|
|
564
662
|
/**
|
|
565
663
|
* @param {string | URL} url
|
|
566
|
-
* @param {{
|
|
664
|
+
* @param {{ replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean }} options
|
|
567
665
|
* @param {number} redirect_count
|
|
568
666
|
* @param {{}} [nav_token]
|
|
569
667
|
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
|
|
@@ -584,10 +682,10 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
584
682
|
await navigate({
|
|
585
683
|
type: 'goto',
|
|
586
684
|
url: resolve_url(url),
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
replace_state: options.replaceState,
|
|
685
|
+
reset: options.reset,
|
|
686
|
+
replace_state: options.replace,
|
|
590
687
|
state: options.state,
|
|
688
|
+
persist_state: options.persistState,
|
|
591
689
|
redirect_count,
|
|
592
690
|
nav_token,
|
|
593
691
|
intent,
|
|
@@ -621,9 +719,8 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
621
719
|
if (options.refreshAll) {
|
|
622
720
|
// TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
|
|
623
721
|
// when a query in a layout that still exists after page change is refreshed earlier than this
|
|
624
|
-
void
|
|
625
|
-
.tick
|
|
626
|
-
.then(svelte.tick)
|
|
722
|
+
void tick()
|
|
723
|
+
.then(tick)
|
|
627
724
|
.then(() => {
|
|
628
725
|
for (const [id, entries] of query_map) {
|
|
629
726
|
for (const [payload, { resource }] of entries) {
|
|
@@ -665,7 +762,7 @@ async function _preload_data(intent) {
|
|
|
665
762
|
|
|
666
763
|
load_cache.promise.catch(discard_load_cache);
|
|
667
764
|
|
|
668
|
-
if (__SVELTEKIT_FORK_PRELOADS__
|
|
765
|
+
if (__SVELTEKIT_FORK_PRELOADS__) {
|
|
669
766
|
const lc = load_cache;
|
|
670
767
|
|
|
671
768
|
lc.fork = lc.promise.then((result) => {
|
|
@@ -673,10 +770,8 @@ async function _preload_data(intent) {
|
|
|
673
770
|
// resolve, bail rather than creating an orphan fork
|
|
674
771
|
if (lc === load_cache && result.type === 'loaded') {
|
|
675
772
|
try {
|
|
676
|
-
return
|
|
677
|
-
|
|
678
|
-
update(result.props.page);
|
|
679
|
-
current_tree = result.props.tree;
|
|
773
|
+
return fork(() => {
|
|
774
|
+
apply_navigation_result(result);
|
|
680
775
|
});
|
|
681
776
|
} catch {
|
|
682
777
|
// if it errors, it's because the experimental flag isn't enabled in Svelte
|
|
@@ -710,9 +805,9 @@ async function _preload_code(url) {
|
|
|
710
805
|
/**
|
|
711
806
|
* @param {import('./types.js').NavigationFinished} result
|
|
712
807
|
* @param {HTMLElement} target
|
|
713
|
-
* @param {boolean}
|
|
808
|
+
* @param {boolean} should_hydrate
|
|
714
809
|
*/
|
|
715
|
-
async function initialize(result, target,
|
|
810
|
+
async function initialize(result, target, should_hydrate) {
|
|
716
811
|
if (__SVELTEKIT_DEV__ && result.state.error && document.querySelector('vite-error-overlay'))
|
|
717
812
|
return;
|
|
718
813
|
|
|
@@ -734,21 +829,18 @@ async function initialize(result, target, hydrate) {
|
|
|
734
829
|
if (style) style.remove();
|
|
735
830
|
}
|
|
736
831
|
|
|
737
|
-
|
|
738
|
-
current_tree = result.props.tree;
|
|
832
|
+
apply_navigation_result(result);
|
|
739
833
|
|
|
740
|
-
// TODO
|
|
741
|
-
|
|
834
|
+
// TODO treeshake `hydrate` in csr mode
|
|
835
|
+
const render = should_hydrate ? hydrate : mount;
|
|
836
|
+
|
|
837
|
+
render(Root, {
|
|
742
838
|
target,
|
|
743
|
-
props
|
|
744
|
-
hydrate,
|
|
745
|
-
// Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
|
|
746
|
-
sync: false,
|
|
839
|
+
props,
|
|
747
840
|
transformError: /** @param {unknown} e */ async (e) => {
|
|
748
841
|
const error = await handle_error(e, current.nav);
|
|
749
|
-
rendering_error = { error, status: error.status };
|
|
750
842
|
page.error = error;
|
|
751
|
-
page.status =
|
|
843
|
+
page.status = error.status;
|
|
752
844
|
return error;
|
|
753
845
|
}
|
|
754
846
|
});
|
|
@@ -757,16 +849,17 @@ async function initialize(result, target, hydrate) {
|
|
|
757
849
|
// which causes component script blocks to run asynchronously
|
|
758
850
|
void (await Promise.resolve());
|
|
759
851
|
|
|
760
|
-
if (
|
|
852
|
+
if (should_hydrate) {
|
|
761
853
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
762
854
|
const navigation = {
|
|
763
855
|
from: null,
|
|
764
856
|
to: {
|
|
765
857
|
...nav,
|
|
766
|
-
scroll:
|
|
858
|
+
scroll: history_info[current_history_index]?.scroll ?? scroll_state()
|
|
767
859
|
},
|
|
768
860
|
willUnload: false,
|
|
769
861
|
type: 'enter',
|
|
862
|
+
shallow: false,
|
|
770
863
|
complete: Promise.resolve()
|
|
771
864
|
};
|
|
772
865
|
|
|
@@ -848,7 +941,7 @@ async function get_navigation_result_from_branch({
|
|
|
848
941
|
let current_node = result.props.tree;
|
|
849
942
|
|
|
850
943
|
/** @type {RenderNode | undefined} */
|
|
851
|
-
let previous_node =
|
|
944
|
+
let previous_node = props.tree;
|
|
852
945
|
|
|
853
946
|
for (let i = 0; i < branch.length; i += 1) {
|
|
854
947
|
const node = branch[i];
|
|
@@ -856,11 +949,6 @@ async function get_navigation_result_from_branch({
|
|
|
856
949
|
|
|
857
950
|
if (!node) continue;
|
|
858
951
|
|
|
859
|
-
const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader;
|
|
860
|
-
|
|
861
|
-
current_node.error = (await error_loader()).component;
|
|
862
|
-
current_node.component = node.node.component;
|
|
863
|
-
|
|
864
952
|
if (
|
|
865
953
|
// if an ancestor node in this path changed, `data_changed` is already true and the
|
|
866
954
|
// accumulated `data` differs from the previous render, so we must re-merge
|
|
@@ -878,10 +966,24 @@ async function get_navigation_result_from_branch({
|
|
|
878
966
|
data = current_node.data;
|
|
879
967
|
|
|
880
968
|
if (i < branch.length - 1) {
|
|
881
|
-
|
|
882
|
-
|
|
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];
|
|
975
|
+
|
|
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
|
+
);
|
|
883
984
|
|
|
884
|
-
|
|
985
|
+
previous_node = previous_node?.child;
|
|
986
|
+
}
|
|
885
987
|
}
|
|
886
988
|
}
|
|
887
989
|
|
|
@@ -900,6 +1002,7 @@ async function get_navigation_result_from_branch({
|
|
|
900
1002
|
id: route?.id ?? null
|
|
901
1003
|
},
|
|
902
1004
|
state: {},
|
|
1005
|
+
shallow: null,
|
|
903
1006
|
status: status ?? error?.status ?? 200,
|
|
904
1007
|
url: new URL(url),
|
|
905
1008
|
form: form ?? null,
|
|
@@ -1298,7 +1401,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1298
1401
|
|
|
1299
1402
|
if (server_data_node?.type === 'error') {
|
|
1300
1403
|
// rethrow and catch below
|
|
1301
|
-
throw new HttpError(server_data_node.error
|
|
1404
|
+
throw new HttpError(server_data_node.error);
|
|
1302
1405
|
}
|
|
1303
1406
|
|
|
1304
1407
|
return load_node({
|
|
@@ -1655,12 +1758,14 @@ function get_page_key(url) {
|
|
|
1655
1758
|
* delta?: number;
|
|
1656
1759
|
* event?: PopStateEvent | MouseEvent;
|
|
1657
1760
|
* scroll?: { x: number, y: number };
|
|
1761
|
+
* shallow?: boolean;
|
|
1762
|
+
* target?: { params: Record<string, string> | null; route: { id: string } | null; url: URL };
|
|
1658
1763
|
* }} opts
|
|
1659
1764
|
*/
|
|
1660
|
-
function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
1765
|
+
function _before_navigate({ url, type, intent, delta, event, scroll, shallow = false, target }) {
|
|
1661
1766
|
let should_block = false;
|
|
1662
1767
|
|
|
1663
|
-
const nav = create_navigation(current, intent, url, type, scroll ?? null);
|
|
1768
|
+
const nav = create_navigation(current, intent, url, type, scroll ?? null, shallow, target);
|
|
1664
1769
|
|
|
1665
1770
|
if (nav.navigation.type === 'popstate' && delta !== undefined) {
|
|
1666
1771
|
nav.navigation.delta = delta;
|
|
@@ -1681,6 +1786,9 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1681
1786
|
|
|
1682
1787
|
if (!is_navigating) {
|
|
1683
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?
|
|
1684
1792
|
before_navigate_callbacks.forEach((fn) => fn(cancellable));
|
|
1685
1793
|
}
|
|
1686
1794
|
|
|
@@ -1693,13 +1801,14 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1693
1801
|
* url: URL;
|
|
1694
1802
|
* popped?: {
|
|
1695
1803
|
* state: Record<string, any>;
|
|
1696
|
-
* scroll
|
|
1804
|
+
* scroll?: { x: number, y: number };
|
|
1697
1805
|
* delta: number;
|
|
1806
|
+
* shallow: { params: Record<string, string> | null; route: { id: string } | null; url: URL } | null;
|
|
1698
1807
|
* };
|
|
1699
|
-
*
|
|
1700
|
-
* noscroll?: boolean;
|
|
1808
|
+
* reset?: boolean;
|
|
1701
1809
|
* replace_state?: boolean;
|
|
1702
1810
|
* state?: Record<string, any>;
|
|
1811
|
+
* persist_state?: boolean;
|
|
1703
1812
|
* redirect_count?: number;
|
|
1704
1813
|
* nav_token?: {};
|
|
1705
1814
|
* accept?: () => void;
|
|
@@ -1713,10 +1822,10 @@ async function navigate({
|
|
|
1713
1822
|
type,
|
|
1714
1823
|
url,
|
|
1715
1824
|
popped,
|
|
1716
|
-
|
|
1717
|
-
noscroll,
|
|
1825
|
+
reset = true,
|
|
1718
1826
|
replace_state,
|
|
1719
1827
|
state = {},
|
|
1828
|
+
persist_state = false,
|
|
1720
1829
|
redirect_count = 0,
|
|
1721
1830
|
nav_token = {},
|
|
1722
1831
|
accept = noop,
|
|
@@ -1738,6 +1847,8 @@ async function navigate({
|
|
|
1738
1847
|
delta: popped?.delta,
|
|
1739
1848
|
intent,
|
|
1740
1849
|
scroll: popped?.scroll,
|
|
1850
|
+
shallow: !!popped?.shallow,
|
|
1851
|
+
target: popped?.shallow ?? undefined,
|
|
1741
1852
|
// @ts-ignore
|
|
1742
1853
|
event
|
|
1743
1854
|
});
|
|
@@ -1821,10 +1932,10 @@ async function navigate({
|
|
|
1821
1932
|
type,
|
|
1822
1933
|
url: new URL(navigation_result.location, url),
|
|
1823
1934
|
popped,
|
|
1824
|
-
|
|
1825
|
-
noscroll,
|
|
1935
|
+
reset,
|
|
1826
1936
|
replace_state,
|
|
1827
1937
|
state,
|
|
1938
|
+
persist_state,
|
|
1828
1939
|
redirect_count: redirect_count + 1,
|
|
1829
1940
|
nav_token
|
|
1830
1941
|
});
|
|
@@ -1858,7 +1969,7 @@ async function navigate({
|
|
|
1858
1969
|
|
|
1859
1970
|
updating = true;
|
|
1860
1971
|
|
|
1861
|
-
|
|
1972
|
+
capture_scroll(previous_history_index);
|
|
1862
1973
|
capture_snapshot(previous_navigation_index);
|
|
1863
1974
|
|
|
1864
1975
|
// ensure the url pathname matches the page's trailing slash option
|
|
@@ -1871,15 +1982,23 @@ async function navigate({
|
|
|
1871
1982
|
if (!popped) {
|
|
1872
1983
|
// this is a new navigation, rather than a popstate
|
|
1873
1984
|
const change = replace_state ? 0 : 1;
|
|
1985
|
+
if (type !== 'enter') {
|
|
1986
|
+
if (reset) current_reset_index += 1;
|
|
1987
|
+
}
|
|
1874
1988
|
|
|
1875
1989
|
const entry = {
|
|
1876
|
-
[
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
+
})
|
|
1879
1997
|
};
|
|
1880
1998
|
|
|
1881
1999
|
const fn = replace_state ? history.replaceState : history.pushState;
|
|
1882
2000
|
fn.call(history, entry, '', url);
|
|
2001
|
+
set_history_options(current_history_index, entry[HISTORY_METADATA_KEY]);
|
|
1883
2002
|
|
|
1884
2003
|
if (!replace_state) {
|
|
1885
2004
|
clear_onward_history(current_history_index, current_navigation_index);
|
|
@@ -1898,6 +2017,7 @@ async function navigate({
|
|
|
1898
2017
|
}
|
|
1899
2018
|
|
|
1900
2019
|
navigation_result.props.page.state = state;
|
|
2020
|
+
navigation_result.props.page.shallow = popped?.shallow ?? null;
|
|
1901
2021
|
|
|
1902
2022
|
/**
|
|
1903
2023
|
* @type {Promise<void> | undefined}
|
|
@@ -1928,7 +2048,13 @@ async function navigate({
|
|
|
1928
2048
|
}
|
|
1929
2049
|
|
|
1930
2050
|
// Type-casts are save because we know this resolved a proper SvelteKit route
|
|
1931
|
-
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);
|
|
1932
2058
|
current = {
|
|
1933
2059
|
...navigation_result.state,
|
|
1934
2060
|
nav: {
|
|
@@ -1945,13 +2071,7 @@ async function navigate({
|
|
|
1945
2071
|
|
|
1946
2072
|
// Remove focus before updating the component tree, so that blur/focusout
|
|
1947
2073
|
// handlers fire while the old component's data is still valid (#14575)
|
|
1948
|
-
|
|
1949
|
-
!keepfocus &&
|
|
1950
|
-
document.activeElement instanceof HTMLElement &&
|
|
1951
|
-
document.activeElement !== document.body
|
|
1952
|
-
) {
|
|
1953
|
-
document.activeElement.blur();
|
|
1954
|
-
}
|
|
2074
|
+
blur_active_element(reset);
|
|
1955
2075
|
|
|
1956
2076
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1957
2077
|
|
|
@@ -1960,28 +2080,22 @@ async function navigate({
|
|
|
1960
2080
|
// `fork.commit()` applies the preloaded state synchronously before the
|
|
1961
2081
|
// first `await`, so reset any previously-failed boundaries now so the
|
|
1962
2082
|
// stale `+error.svelte` is torn down. See sveltejs/kit#15694.
|
|
1963
|
-
for (const
|
|
1964
|
-
|
|
2083
|
+
for (const reset_boundary of resetters) {
|
|
2084
|
+
reset_boundary();
|
|
1965
2085
|
}
|
|
1966
|
-
resetters.
|
|
2086
|
+
resetters.clear();
|
|
1967
2087
|
} else {
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
current_tree = navigation_result.props.tree;
|
|
2088
|
+
apply_navigation_result(navigation_result);
|
|
2089
|
+
|
|
1971
2090
|
// Reset any boundaries that failed on a previous navigation now that the
|
|
1972
2091
|
// new props are applied, otherwise the stale `+error.svelte` stays
|
|
1973
2092
|
// mounted above the new route's content. See sveltejs/kit#15694.
|
|
1974
|
-
for (const
|
|
1975
|
-
|
|
2093
|
+
for (const reset_boundary of resetters) {
|
|
2094
|
+
reset_boundary();
|
|
1976
2095
|
}
|
|
1977
|
-
resetters.
|
|
1978
|
-
// Check for sync rendering error
|
|
1979
|
-
if (rendering_error) {
|
|
1980
|
-
Object.assign(navigation_result.props.page, rendering_error);
|
|
1981
|
-
}
|
|
1982
|
-
update(navigation_result.props.page);
|
|
2096
|
+
resetters.clear();
|
|
1983
2097
|
|
|
1984
|
-
commit_promise =
|
|
2098
|
+
commit_promise = settled();
|
|
1985
2099
|
}
|
|
1986
2100
|
|
|
1987
2101
|
has_navigated = true;
|
|
@@ -1991,7 +2105,7 @@ async function navigate({
|
|
|
1991
2105
|
|
|
1992
2106
|
const { activeElement } = document;
|
|
1993
2107
|
|
|
1994
|
-
await
|
|
2108
|
+
await commit_promise;
|
|
1995
2109
|
|
|
1996
2110
|
if (navigation_token !== nav_token) {
|
|
1997
2111
|
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
@@ -1999,44 +2113,7 @@ async function navigate({
|
|
|
1999
2113
|
return;
|
|
2000
2114
|
}
|
|
2001
2115
|
|
|
2002
|
-
|
|
2003
|
-
if (navigation_result.props.page && rendering_error) {
|
|
2004
|
-
Object.assign(navigation_result.props.page, rendering_error);
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
|
|
2008
|
-
/** @type {Element | null} */
|
|
2009
|
-
let deep_linked = null;
|
|
2010
|
-
|
|
2011
|
-
if (autoscroll) {
|
|
2012
|
-
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
|
|
2013
|
-
if (scroll) {
|
|
2014
|
-
scrollTo(scroll.x, scroll.y);
|
|
2015
|
-
} else if ((deep_linked = get_hash_element(url))) {
|
|
2016
|
-
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
2017
|
-
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
2018
|
-
// CSS properties.
|
|
2019
|
-
deep_linked.scrollIntoView();
|
|
2020
|
-
} else {
|
|
2021
|
-
scrollTo(0, 0);
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
const changed_focus =
|
|
2026
|
-
// reset focus only if any manual focus management didn't override it
|
|
2027
|
-
document.activeElement !== activeElement &&
|
|
2028
|
-
// also refocus when activeElement is body already because the
|
|
2029
|
-
// focus event might not have been fired on it yet
|
|
2030
|
-
document.activeElement !== document.body;
|
|
2031
|
-
|
|
2032
|
-
if (!keepfocus && !changed_focus) {
|
|
2033
|
-
// We don't need to manually restore the scroll position if we're navigating
|
|
2034
|
-
// to a fragment identifier. It is automatically done for us when we set the
|
|
2035
|
-
// sequential navigation starting point with `location.replace`
|
|
2036
|
-
reset_focus(url, !deep_linked);
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
autoscroll = true;
|
|
2116
|
+
reset_scroll_and_focus(url, reset ? popped?.scroll : scroll_state(), reset, activeElement);
|
|
2040
2117
|
|
|
2041
2118
|
is_navigating = false;
|
|
2042
2119
|
|
|
@@ -2280,7 +2357,7 @@ export async function handle_error(error, event) {
|
|
|
2280
2357
|
* @param {T} callback
|
|
2281
2358
|
*/
|
|
2282
2359
|
function add_navigation_callback(callbacks, callback) {
|
|
2283
|
-
|
|
2360
|
+
onMount(() => {
|
|
2284
2361
|
callbacks.add(callback);
|
|
2285
2362
|
|
|
2286
2363
|
return () => {
|
|
@@ -2352,24 +2429,49 @@ export function disableScrollHandling() {
|
|
|
2352
2429
|
}
|
|
2353
2430
|
|
|
2354
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
|
+
}
|
|
2355
2463
|
|
|
2356
2464
|
/**
|
|
2357
|
-
* Allows you to navigate programmatically to a given route, with
|
|
2358
|
-
*
|
|
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.
|
|
2359
2469
|
*
|
|
2360
|
-
* `goto` is intended for navigations to routes that belong to the app.
|
|
2361
|
-
* 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.
|
|
2362
2471
|
* For external URLs, use `window.location = url` to perform a full-page navigation instead of calling `goto(url)`.
|
|
2363
2472
|
*
|
|
2364
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.
|
|
2365
|
-
* @param {
|
|
2366
|
-
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
2367
|
-
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
|
2368
|
-
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
|
2369
|
-
* @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.
|
|
2370
|
-
* @param {Array<string | URL | ((url: URL) => boolean)>} [opts.invalidate] Causes any load functions to re-run if they depend on one of the urls
|
|
2371
|
-
* @param {boolean} [opts.invalidateAll] Deprecated in favour of opts.refreshAll.
|
|
2372
|
-
* @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
|
|
2373
2475
|
* @returns {Promise<void>}
|
|
2374
2476
|
*/
|
|
2375
2477
|
export async function goto(url, opts = {}) {
|
|
@@ -2377,23 +2479,35 @@ export async function goto(url, opts = {}) {
|
|
|
2377
2479
|
throw new Error('Cannot call goto(...) on the server');
|
|
2378
2480
|
}
|
|
2379
2481
|
|
|
2380
|
-
|
|
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
|
+
}
|
|
2381
2489
|
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
);
|
|
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
|
+
}
|
|
2388
2495
|
}
|
|
2389
2496
|
|
|
2390
|
-
const
|
|
2497
|
+
const replace = opts.replace ?? opts.replaceState ?? false;
|
|
2391
2498
|
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
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'
|
|
2397
2511
|
);
|
|
2398
2512
|
}
|
|
2399
2513
|
|
|
@@ -2404,8 +2518,13 @@ export async function goto(url, opts = {}) {
|
|
|
2404
2518
|
);
|
|
2405
2519
|
}
|
|
2406
2520
|
|
|
2407
|
-
|
|
2408
|
-
|
|
2521
|
+
return _goto(
|
|
2522
|
+
intent.url,
|
|
2523
|
+
{ ...opts, replace, refreshAll: opts.refreshAll ?? opts.invalidateAll },
|
|
2524
|
+
0,
|
|
2525
|
+
{},
|
|
2526
|
+
intent
|
|
2527
|
+
);
|
|
2409
2528
|
}
|
|
2410
2529
|
|
|
2411
2530
|
/**
|
|
@@ -2579,63 +2698,77 @@ export async function preloadCode(pathname) {
|
|
|
2579
2698
|
}
|
|
2580
2699
|
|
|
2581
2700
|
/**
|
|
2582
|
-
* 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).
|
|
2583
2702
|
*
|
|
2703
|
+
* @deprecated Use `goto(url, { state, shallow: true })` instead.
|
|
2584
2704
|
* @param {string | URL} url
|
|
2585
2705
|
* @param {App.PageState} state
|
|
2586
|
-
* @returns {void}
|
|
2706
|
+
* @returns {Promise<void>}
|
|
2587
2707
|
*/
|
|
2588
|
-
export function pushState(url, state) {
|
|
2708
|
+
export async function pushState(url, state) {
|
|
2589
2709
|
if (!BROWSER) {
|
|
2590
2710
|
throw new Error('Cannot call pushState(...) on the server');
|
|
2591
2711
|
}
|
|
2592
2712
|
|
|
2593
|
-
if (DEV) {
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
try {
|
|
2599
|
-
// use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
|
|
2600
|
-
devalue.stringify(state);
|
|
2601
|
-
} catch (error) {
|
|
2602
|
-
// @ts-expect-error
|
|
2603
|
-
throw new Error(`Could not serialize state${error.path}`, { cause: error });
|
|
2604
|
-
}
|
|
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
|
+
);
|
|
2605
2718
|
}
|
|
2606
2719
|
|
|
2607
|
-
|
|
2720
|
+
const intent = await resolve_intent(url, 'pushState');
|
|
2608
2721
|
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
history.pushState(opts, '', resolve_url(url));
|
|
2617
|
-
has_navigated = true;
|
|
2618
|
-
|
|
2619
|
-
page.state = state;
|
|
2620
|
-
|
|
2621
|
-
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
|
+
);
|
|
2622
2728
|
}
|
|
2623
2729
|
|
|
2624
2730
|
/**
|
|
2625
|
-
* 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).
|
|
2626
2732
|
*
|
|
2733
|
+
* @deprecated Use `goto(url, { state, shallow: true, replace: true })` instead.
|
|
2627
2734
|
* @param {string | URL} url
|
|
2628
2735
|
* @param {App.PageState} state
|
|
2629
|
-
* @returns {void}
|
|
2736
|
+
* @returns {Promise<void>}
|
|
2630
2737
|
*/
|
|
2631
|
-
export function replaceState(url, state) {
|
|
2738
|
+
export async function replaceState(url, state) {
|
|
2632
2739
|
if (!BROWSER) {
|
|
2633
2740
|
throw new Error('Cannot call replaceState(...) on the server');
|
|
2634
2741
|
}
|
|
2635
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
|
+
|
|
2636
2769
|
if (DEV) {
|
|
2637
2770
|
if (!started) {
|
|
2638
|
-
throw new Error(
|
|
2771
|
+
throw new Error(`Cannot call ${caller}(...) before router is initialized`);
|
|
2639
2772
|
}
|
|
2640
2773
|
|
|
2641
2774
|
try {
|
|
@@ -2647,16 +2780,100 @@ export function replaceState(url, state) {
|
|
|
2647
2780
|
}
|
|
2648
2781
|
}
|
|
2649
2782
|
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
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
|
+
})
|
|
2655
2810
|
};
|
|
2656
2811
|
|
|
2657
|
-
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);
|
|
2658
2842
|
|
|
2659
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;
|
|
2660
2877
|
}
|
|
2661
2878
|
|
|
2662
2879
|
/**
|
|
@@ -2681,15 +2898,13 @@ export async function applyAction(result) {
|
|
|
2681
2898
|
page.status = result.status;
|
|
2682
2899
|
|
|
2683
2900
|
/** @type {Record<string, any>} */
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
form: null
|
|
2688
|
-
});
|
|
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;
|
|
2689
2904
|
|
|
2690
2905
|
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
2691
|
-
await
|
|
2692
|
-
|
|
2906
|
+
await tick();
|
|
2907
|
+
props.form = result.data;
|
|
2693
2908
|
|
|
2694
2909
|
if (result.type === 'success') {
|
|
2695
2910
|
reset_focus(/** @type {URL} */ (page.url));
|
|
@@ -2719,11 +2934,9 @@ export async function set_nearest_error_page(error) {
|
|
|
2719
2934
|
|
|
2720
2935
|
current = { ...navigation_result.state, nav: current.nav };
|
|
2721
2936
|
|
|
2722
|
-
|
|
2723
|
-
current_tree = navigation_result.props.tree;
|
|
2724
|
-
update(navigation_result.props.page);
|
|
2937
|
+
apply_navigation_result(navigation_result);
|
|
2725
2938
|
|
|
2726
|
-
void
|
|
2939
|
+
void tick().then(() => reset_focus(current.url));
|
|
2727
2940
|
}
|
|
2728
2941
|
}
|
|
2729
2942
|
|
|
@@ -2866,17 +3079,21 @@ function _start_router() {
|
|
|
2866
3079
|
return;
|
|
2867
3080
|
}
|
|
2868
3081
|
// set this flag to distinguish between navigations triggered by
|
|
2869
|
-
// clicking a hash link and those triggered by popstate
|
|
2870
|
-
|
|
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
|
+
};
|
|
2871
3088
|
|
|
2872
|
-
|
|
3089
|
+
capture_scroll(current_history_index);
|
|
2873
3090
|
|
|
2874
3091
|
update_url(url);
|
|
2875
3092
|
|
|
2876
3093
|
if (!options.replace_state) return;
|
|
2877
3094
|
|
|
2878
3095
|
// hashchange event shouldn't occur if the router is replacing state.
|
|
2879
|
-
hash_navigating =
|
|
3096
|
+
hash_navigating = null;
|
|
2880
3097
|
}
|
|
2881
3098
|
|
|
2882
3099
|
event.preventDefault();
|
|
@@ -2894,8 +3111,7 @@ function _start_router() {
|
|
|
2894
3111
|
await navigate({
|
|
2895
3112
|
type: 'link',
|
|
2896
3113
|
url,
|
|
2897
|
-
|
|
2898
|
-
noscroll: options.noscroll,
|
|
3114
|
+
reset: options.reset,
|
|
2899
3115
|
replace_state: options.replace_state ?? url.href === location.href,
|
|
2900
3116
|
event
|
|
2901
3117
|
});
|
|
@@ -2941,8 +3157,7 @@ function _start_router() {
|
|
|
2941
3157
|
void navigate({
|
|
2942
3158
|
type: 'form',
|
|
2943
3159
|
url,
|
|
2944
|
-
|
|
2945
|
-
noscroll: options.noscroll,
|
|
3160
|
+
reset: options.reset,
|
|
2946
3161
|
replace_state: options.replace_state ?? url.href === location.href,
|
|
2947
3162
|
event
|
|
2948
3163
|
});
|
|
@@ -2951,53 +3166,82 @@ function _start_router() {
|
|
|
2951
3166
|
addEventListener('popstate', async (event) => {
|
|
2952
3167
|
if (resetting_focus) return;
|
|
2953
3168
|
|
|
2954
|
-
|
|
2955
|
-
|
|
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];
|
|
2956
3174
|
navigation_token = invalidation_token = {};
|
|
2957
3175
|
|
|
2958
3176
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2959
3177
|
// with history.go, which means we end up back here, hence this check
|
|
2960
3178
|
if (history_index === current_history_index) return;
|
|
2961
3179
|
|
|
2962
|
-
const
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
2965
|
-
const
|
|
2966
|
-
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;
|
|
2967
3191
|
const shallow =
|
|
2968
|
-
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;
|
|
2969
3207
|
|
|
2970
3208
|
if (shallow) {
|
|
2971
3209
|
// We don't need to navigate, we just need to update scroll and/or state.
|
|
2972
3210
|
// This happens with hash links and `pushState`/`replaceState`. The
|
|
2973
3211
|
// exception is if we haven't navigated yet, since we could have
|
|
2974
3212
|
// got here after a modal navigation then a reload
|
|
3213
|
+
|
|
3214
|
+
blur_active_element(reset);
|
|
3215
|
+
|
|
2975
3216
|
if (state !== page.state) {
|
|
2976
3217
|
page.state = state;
|
|
2977
3218
|
}
|
|
2978
3219
|
|
|
2979
|
-
|
|
3220
|
+
page.shallow = shallow_target;
|
|
2980
3221
|
|
|
2981
|
-
|
|
2982
|
-
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
3222
|
+
update_url(url);
|
|
2983
3223
|
|
|
3224
|
+
capture_scroll(current_history_index);
|
|
2984
3225
|
current_history_index = history_index;
|
|
3226
|
+
current_reset_index = reset_index;
|
|
3227
|
+
if (reset && scroll) scrollTo(scroll.x, scroll.y);
|
|
2985
3228
|
return;
|
|
2986
3229
|
}
|
|
2987
3230
|
|
|
2988
|
-
const delta = history_index - current_history_index;
|
|
2989
|
-
|
|
2990
3231
|
await navigate({
|
|
2991
3232
|
type: 'popstate',
|
|
2992
3233
|
url,
|
|
3234
|
+
reset,
|
|
2993
3235
|
popped: {
|
|
2994
3236
|
state,
|
|
2995
3237
|
scroll,
|
|
2996
|
-
delta
|
|
3238
|
+
delta,
|
|
3239
|
+
shallow: shallow_target
|
|
2997
3240
|
},
|
|
2998
3241
|
accept: () => {
|
|
2999
3242
|
current_history_index = history_index;
|
|
3000
3243
|
current_navigation_index = navigation_index;
|
|
3244
|
+
current_reset_index = reset_index;
|
|
3001
3245
|
},
|
|
3002
3246
|
block: () => {
|
|
3003
3247
|
history.go(-delta);
|
|
@@ -3026,16 +3270,22 @@ function _start_router() {
|
|
|
3026
3270
|
// if the hashchange happened as a result of clicking on a link,
|
|
3027
3271
|
// we need to update history, otherwise we have to leave it alone
|
|
3028
3272
|
if (hash_navigating) {
|
|
3029
|
-
|
|
3273
|
+
const history_metadata = hash_navigating;
|
|
3274
|
+
hash_navigating = null;
|
|
3275
|
+
current_reset_index = history_metadata.resetIndex;
|
|
3030
3276
|
history.replaceState(
|
|
3031
3277
|
{
|
|
3032
3278
|
...history.state,
|
|
3033
|
-
[
|
|
3034
|
-
|
|
3279
|
+
[HISTORY_METADATA_KEY]: {
|
|
3280
|
+
...history_metadata,
|
|
3281
|
+
historyIndex: ++current_history_index,
|
|
3282
|
+
navigationIndex: current_navigation_index
|
|
3283
|
+
}
|
|
3035
3284
|
},
|
|
3036
3285
|
'',
|
|
3037
3286
|
location.href
|
|
3038
3287
|
);
|
|
3288
|
+
set_history_options(current_history_index, history_metadata);
|
|
3039
3289
|
}
|
|
3040
3290
|
});
|
|
3041
3291
|
|
|
@@ -3102,7 +3352,7 @@ async function _hydrate(
|
|
|
3102
3352
|
|
|
3103
3353
|
/** @type {import('./types.js').NavigationFinished | undefined} */
|
|
3104
3354
|
let result;
|
|
3105
|
-
let
|
|
3355
|
+
let should_hydrate = true;
|
|
3106
3356
|
|
|
3107
3357
|
try {
|
|
3108
3358
|
const branch_promises = node_ids.map(async (n, i) => {
|
|
@@ -3168,7 +3418,7 @@ async function _hydrate(
|
|
|
3168
3418
|
});
|
|
3169
3419
|
|
|
3170
3420
|
target.textContent = '';
|
|
3171
|
-
|
|
3421
|
+
should_hydrate = false;
|
|
3172
3422
|
}
|
|
3173
3423
|
|
|
3174
3424
|
// Exit early when we encounter a redirect while loading the root error page.
|
|
@@ -3176,10 +3426,11 @@ async function _hydrate(
|
|
|
3176
3426
|
if (!result) return;
|
|
3177
3427
|
|
|
3178
3428
|
if (result.props.page) {
|
|
3179
|
-
|
|
3429
|
+
const history_metadata = get_history_metadata();
|
|
3430
|
+
result.props.page.state = history_metadata?.persistState ? history_metadata.state : {};
|
|
3180
3431
|
}
|
|
3181
3432
|
|
|
3182
|
-
await initialize(result, target,
|
|
3433
|
+
await initialize(result, target, should_hydrate);
|
|
3183
3434
|
}
|
|
3184
3435
|
|
|
3185
3436
|
/**
|
|
@@ -3206,20 +3457,19 @@ async function load_data(url, invalid) {
|
|
|
3206
3457
|
notify_version(res.headers.get('x-sveltekit-version'));
|
|
3207
3458
|
|
|
3208
3459
|
if (!res.ok) {
|
|
3209
|
-
// error message is a JSON-stringified string which devalue can't handle at the top level
|
|
3210
3460
|
// turn it into a HttpError to not call handleError on the client again (was already handled on the server)
|
|
3211
3461
|
// if `__data.json` doesn't exist or the server has an internal error,
|
|
3212
3462
|
// avoid parsing the HTML error page as a JSON
|
|
3213
|
-
/** @type {
|
|
3214
|
-
let message;
|
|
3463
|
+
/** @type {App.Error} */
|
|
3464
|
+
let error = { status: res.status, message: 'Internal Error' };
|
|
3465
|
+
|
|
3215
3466
|
if (res.headers.get('content-type')?.includes('application/json')) {
|
|
3216
|
-
|
|
3467
|
+
error = { status: res.status, ...(await res.json()) };
|
|
3217
3468
|
} else if (res.status === 404) {
|
|
3218
|
-
message = 'Not Found';
|
|
3219
|
-
} else if (res.status === 500) {
|
|
3220
|
-
message = 'Internal Error';
|
|
3469
|
+
error.message = 'Not Found';
|
|
3221
3470
|
}
|
|
3222
|
-
|
|
3471
|
+
|
|
3472
|
+
throw new HttpError(error);
|
|
3223
3473
|
}
|
|
3224
3474
|
|
|
3225
3475
|
return new Promise((resolve, reject) => {
|
|
@@ -3412,8 +3662,18 @@ function reset_focus(url, scroll = true) {
|
|
|
3412
3662
|
* @param {URL | null} url
|
|
3413
3663
|
* @param {T} type
|
|
3414
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]
|
|
3415
3667
|
*/
|
|
3416
|
-
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
|
+
) {
|
|
3417
3677
|
/** @type {(value: any) => void} */
|
|
3418
3678
|
let fulfil;
|
|
3419
3679
|
|
|
@@ -3437,13 +3697,14 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
|
|
|
3437
3697
|
scroll: scroll_state()
|
|
3438
3698
|
},
|
|
3439
3699
|
to: url && {
|
|
3440
|
-
params: intent?.params ?? null,
|
|
3441
|
-
route: { id: intent?.route?.id ?? null },
|
|
3442
|
-
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,
|
|
3443
3703
|
scroll: target_scroll
|
|
3444
3704
|
},
|
|
3445
|
-
willUnload: !intent,
|
|
3705
|
+
willUnload: !shallow && !intent,
|
|
3446
3706
|
type,
|
|
3707
|
+
shallow,
|
|
3447
3708
|
complete
|
|
3448
3709
|
});
|
|
3449
3710
|
|
|
@@ -3516,3 +3777,21 @@ if (DEV) {
|
|
|
3516
3777
|
});
|
|
3517
3778
|
}
|
|
3518
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
|
+
}
|