@sveltejs/kit 3.0.0-next.12 → 3.0.0-next.14
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 +11 -11
- package/src/cli.js +2 -2
- package/src/core/adapt/builder.js +8 -5
- package/src/core/generate_manifest/index.js +6 -0
- package/src/core/postbuild/entities.js +8 -2
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +15 -6
- package/src/core/sync/create_manifest_data/conflict.js +1 -1
- package/src/core/sync/create_manifest_data/index.js +33 -2
- package/src/core/sync/write_app_types.js +73 -29
- package/src/core/sync/write_tsconfig/index.js +15 -2
- package/src/core/sync/write_types/index.js +5 -2
- package/src/exports/index.js +23 -12
- package/src/exports/internal/shared.js +4 -12
- package/src/exports/node/index.js +2 -7
- package/src/exports/public.d.ts +61 -12
- package/src/exports/vite/build/remote.js +10 -12
- package/src/exports/vite/dev/index.js +32 -19
- package/src/exports/vite/index.js +162 -133
- package/src/exports/vite/utils.js +1 -7
- 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/query.js +0 -6
- 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 +731 -342
- package/src/runtime/client/constants.js +2 -6
- package/src/runtime/client/fetcher.js +24 -25
- package/src/runtime/client/remote-functions/form.svelte.js +42 -14
- package/src/runtime/client/remote-functions/prerender.svelte.js +2 -2
- package/src/runtime/client/remote-functions/query/index.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 +2 -2
- 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/query-live/proxy.js +0 -10
- package/src/runtime/client/remote-functions/shared.svelte.js +12 -11
- package/src/runtime/client/state.svelte.js +10 -22
- package/src/runtime/client/types.d.ts +1 -2
- package/src/runtime/client/utils.js +22 -14
- package/src/runtime/components/root.svelte +4 -14
- package/src/runtime/form-utils.js +4 -6
- package/src/runtime/pathname.js +37 -0
- package/src/runtime/props.svelte.js +71 -0
- package/src/runtime/server/fetch.js +9 -14
- package/src/runtime/server/index.js +10 -24
- package/src/runtime/server/page/crypto.js +2 -2
- package/src/runtime/server/page/csp.js +88 -104
- package/src/runtime/server/page/index.js +1 -2
- package/src/runtime/server/page/load_data.js +16 -33
- package/src/runtime/server/page/render.js +46 -25
- package/src/runtime/server/page/respond_with_error.js +1 -3
- package/src/runtime/server/page/serialize_data.js +2 -13
- package/src/runtime/server/page/server_routing.js +58 -9
- package/src/runtime/server/respond.js +20 -29
- package/src/runtime/server/state.js +43 -0
- package/src/runtime/shared.js +4 -2
- package/src/runtime/utils.js +3 -0
- package/src/types/ambient-private.d.ts +2 -2
- package/src/types/ambient.d.ts +59 -8
- package/src/types/global-private.d.ts +1 -1
- package/src/types/internal.d.ts +4 -8
- package/src/types/private.d.ts +3 -12
- package/src/utils/hash.js +21 -0
- package/src/utils/http.js +8 -7
- package/src/utils/import.js +2 -1
- package/src/utils/params.js +1 -1
- package/src/utils/regex.js +9 -0
- package/src/utils/routing.js +52 -27
- package/src/utils/url.js +1 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +165 -66
- package/types/index.d.ts.map +2 -1
- package/src/exports/node/polyfills.js +0 -30
- 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';
|
|
@@ -22,16 +21,13 @@ import {
|
|
|
22
21
|
scroll_state,
|
|
23
22
|
load_css
|
|
24
23
|
} from './utils.js';
|
|
25
|
-
import { base, set_match_implementation } from '$app/paths/internal/client';
|
|
24
|
+
import { base, app_dir, 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,33 @@ 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
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
add_data_suffix,
|
|
46
|
+
add_resolution_suffix,
|
|
47
|
+
route_id_resolution_pathname
|
|
48
|
+
} from '../pathname.js';
|
|
49
49
|
import { noop_span } from '../telemetry/noop.js';
|
|
50
50
|
import { read_ndjson } from './ndjson.js';
|
|
51
|
-
import
|
|
52
|
-
import {
|
|
51
|
+
import Root from '../components/root.svelte';
|
|
52
|
+
import { Props, RenderNode } from '../props.svelte.js';
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {{
|
|
56
|
+
* historyIndex: number;
|
|
57
|
+
* navigationIndex: number;
|
|
58
|
+
* pageUrl?: string;
|
|
59
|
+
* state: Record<string, any>;
|
|
60
|
+
* persistState: boolean;
|
|
61
|
+
* resetIndex: number;
|
|
62
|
+
* }} HistoryMetadata
|
|
63
|
+
*/
|
|
55
64
|
|
|
56
65
|
export { load_css };
|
|
57
66
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
58
67
|
|
|
59
68
|
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
69
|
|
|
69
70
|
/**
|
|
70
71
|
* `reset` functions for `<svelte:boundary>`s in the generated root that have
|
|
@@ -73,19 +74,18 @@ let rendering_error = null;
|
|
|
73
74
|
* navigation away from a render error would leave the stale `+error.svelte`
|
|
74
75
|
* mounted. The boundary's `onerror` populates this array; `navigate` drains it
|
|
75
76
|
* after applying the new props. See sveltejs/kit#15694.
|
|
76
|
-
* @type {
|
|
77
|
+
* @type {Set<() => void>}
|
|
77
78
|
*/
|
|
78
|
-
const resetters =
|
|
79
|
+
const resetters = new Set();
|
|
79
80
|
|
|
80
|
-
// We track
|
|
81
|
+
// We track information associated with each history entry in sessionStorage,
|
|
81
82
|
// rather than on history.state itself, because when navigation is driven by
|
|
82
|
-
// popstate it's too late to update the
|
|
83
|
+
// popstate it's too late to access the options or update the focus position associated with the
|
|
83
84
|
// state we're navigating from
|
|
84
85
|
/**
|
|
85
|
-
*
|
|
86
|
-
* @type {Record<number, { x: number; y: number }>}
|
|
86
|
+
* @type {Record<number, { scroll?: { x: number; y: number }; resetIndex?: number }>}
|
|
87
87
|
*/
|
|
88
|
-
const
|
|
88
|
+
const history_info = storage.get(HISTORY_INFO_KEY) ?? {};
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* navigation index -> any
|
|
@@ -93,14 +93,14 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
|
93
93
|
*/
|
|
94
94
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
95
95
|
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
*/
|
|
99
|
-
let current_tree = /** @type {RenderNode} */ ({});
|
|
96
|
+
/** @type {Props} */
|
|
97
|
+
let props;
|
|
100
98
|
|
|
101
99
|
if (DEV && BROWSER) {
|
|
102
100
|
let warned = false;
|
|
103
101
|
|
|
102
|
+
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
|
|
103
|
+
|
|
104
104
|
const warn = () => {
|
|
105
105
|
if (warned) return;
|
|
106
106
|
|
|
@@ -113,14 +113,15 @@ if (DEV && BROWSER) {
|
|
|
113
113
|
// skip over `warn` and the place where `warn` was called
|
|
114
114
|
const frame = stack[2];
|
|
115
115
|
|
|
116
|
-
//
|
|
116
|
+
// Ignore calls that happen inside dependencies, including SvelteKit.
|
|
117
|
+
// 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
118
|
// `frame` can be falsy if we came from an anonymous function
|
|
118
|
-
if (frame?.includes('node_modules')) return;
|
|
119
|
+
if (frame?.includes('node_modules') || frame?.includes(current_module_url)) return;
|
|
119
120
|
|
|
120
121
|
warned = true;
|
|
121
122
|
|
|
122
123
|
console.warn(
|
|
123
|
-
"Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use
|
|
124
|
+
"Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use `goto(...)` from `$app/navigation` instead."
|
|
124
125
|
);
|
|
125
126
|
};
|
|
126
127
|
|
|
@@ -138,8 +139,60 @@ if (DEV && BROWSER) {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
/** @param {number} index */
|
|
141
|
-
function
|
|
142
|
-
|
|
142
|
+
function capture_scroll(index) {
|
|
143
|
+
history_info[index].scroll = scroll_state();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {number} index
|
|
148
|
+
* @param {Pick<HistoryMetadata, 'resetIndex'>} options
|
|
149
|
+
*/
|
|
150
|
+
function set_history_options(index, options) {
|
|
151
|
+
history_info[index] = {
|
|
152
|
+
...history_info[index],
|
|
153
|
+
resetIndex: options.resetIndex
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @param {boolean} reset */
|
|
158
|
+
function blur_active_element(reset) {
|
|
159
|
+
if (
|
|
160
|
+
reset &&
|
|
161
|
+
document.activeElement instanceof HTMLElement &&
|
|
162
|
+
document.activeElement !== document.body
|
|
163
|
+
) {
|
|
164
|
+
document.activeElement.blur();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {URL} url
|
|
170
|
+
* @param {{ x: number; y: number } | null | undefined} scroll
|
|
171
|
+
* @param {boolean} reset
|
|
172
|
+
* @param {Element | null} active_element
|
|
173
|
+
*/
|
|
174
|
+
function reset_scroll_and_focus(url, scroll, reset, active_element) {
|
|
175
|
+
/** @type {Element | null} */
|
|
176
|
+
let deep_linked = null;
|
|
177
|
+
|
|
178
|
+
if (autoscroll) {
|
|
179
|
+
if (scroll) {
|
|
180
|
+
scrollTo(scroll.x, scroll.y);
|
|
181
|
+
} else if ((deep_linked = get_hash_element(url))) {
|
|
182
|
+
deep_linked.scrollIntoView();
|
|
183
|
+
} else {
|
|
184
|
+
scrollTo(0, 0);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const changed_focus =
|
|
189
|
+
document.activeElement !== active_element && document.activeElement !== document.body;
|
|
190
|
+
|
|
191
|
+
if (reset && !changed_focus) {
|
|
192
|
+
reset_focus(url, !deep_linked);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
autoscroll = true;
|
|
143
196
|
}
|
|
144
197
|
|
|
145
198
|
/**
|
|
@@ -150,8 +203,8 @@ function clear_onward_history(current_history_index, current_navigation_index) {
|
|
|
150
203
|
// if we navigated back, then pushed a new state, we can
|
|
151
204
|
// release memory by pruning the scroll/snapshot lookup
|
|
152
205
|
let i = current_history_index + 1;
|
|
153
|
-
while (
|
|
154
|
-
delete
|
|
206
|
+
while (history_info[i]) {
|
|
207
|
+
delete history_info[i];
|
|
155
208
|
i += 1;
|
|
156
209
|
}
|
|
157
210
|
|
|
@@ -225,14 +278,6 @@ export const prerender_responses = {};
|
|
|
225
278
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
226
279
|
const invalidated = [];
|
|
227
280
|
|
|
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
281
|
/** @type {{id: string, token: {}, promise: Promise<import('./types.js').NavigationResult>, fork: Promise<import('svelte').Fork | null> | null} | null} */
|
|
237
282
|
let load_cache = null;
|
|
238
283
|
|
|
@@ -253,6 +298,35 @@ function discard_load_cache() {
|
|
|
253
298
|
*/
|
|
254
299
|
const reroute_cache = new Map();
|
|
255
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Sentinel for a route that exists but has no code to preload, i.e. a `+server.js` with no
|
|
303
|
+
* `+page`. Cached like a parsed route so that repeated `preloadCode(id)` calls for the same
|
|
304
|
+
* id don't re-request it.
|
|
305
|
+
*/
|
|
306
|
+
const ENDPOINT_ONLY = Symbol('endpoint only');
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Cache of route ID -> parsed route for server-side route resolution.
|
|
310
|
+
* Populated whenever a server route resolution occurs (`match`, link preloading,
|
|
311
|
+
* hydration), so that `preloadCode(id)` doesn't need an extra server round trip.
|
|
312
|
+
* Lives until full page reload.
|
|
313
|
+
* @type {Map<string, import('types').CSRRoute | typeof ENDPOINT_ONLY>}
|
|
314
|
+
*/
|
|
315
|
+
const route_id_cache = new Map();
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Parse a server-provided route and record it in `route_id_cache`, so that a subsequent
|
|
319
|
+
* `preloadCode(id)` for the same route doesn't need another server round trip. Always use
|
|
320
|
+
* this rather than calling `parse_server_route` directly.
|
|
321
|
+
* @param {import('types').CSRRouteServer} server_route
|
|
322
|
+
* @returns {import('types').CSRRoute}
|
|
323
|
+
*/
|
|
324
|
+
function parse_and_cache_server_route(server_route) {
|
|
325
|
+
const route = parse_server_route(server_route, app.nodes);
|
|
326
|
+
route_id_cache.set(route.id, route);
|
|
327
|
+
return route;
|
|
328
|
+
}
|
|
329
|
+
|
|
256
330
|
/**
|
|
257
331
|
* Note on before_navigate_callbacks, on_navigate_callbacks and after_navigate_callbacks:
|
|
258
332
|
* do not re-assign as some closures keep references to these Sets
|
|
@@ -282,21 +356,30 @@ let started = false;
|
|
|
282
356
|
let autoscroll = true;
|
|
283
357
|
let updating = false;
|
|
284
358
|
let is_navigating = false;
|
|
285
|
-
|
|
359
|
+
/** @type {HistoryMetadata | null} */
|
|
360
|
+
let hash_navigating = null;
|
|
286
361
|
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
287
362
|
let has_navigated = false;
|
|
288
363
|
|
|
289
364
|
let force_invalidation = false;
|
|
290
365
|
|
|
291
|
-
/** @type {import('svelte').SvelteComponent} */
|
|
292
|
-
let root;
|
|
293
|
-
|
|
294
366
|
/** @type {number} keeping track of the history index in order to prevent popstate navigation events if needed */
|
|
295
367
|
let current_history_index;
|
|
296
368
|
|
|
297
369
|
/** @type {number} */
|
|
298
370
|
let current_navigation_index;
|
|
299
371
|
|
|
372
|
+
/** @type {number} */
|
|
373
|
+
let current_reset_index;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @param {any} [state]
|
|
377
|
+
* @returns {HistoryMetadata | undefined}
|
|
378
|
+
*/
|
|
379
|
+
function get_history_metadata(state = history.state) {
|
|
380
|
+
return state?.[HISTORY_METADATA_KEY];
|
|
381
|
+
}
|
|
382
|
+
|
|
300
383
|
/** @type {{}} Token for the latest navigation. Updated on new navigations */
|
|
301
384
|
let navigation_token;
|
|
302
385
|
|
|
@@ -319,7 +402,7 @@ let invalidation_token;
|
|
|
319
402
|
const preload_tokens = new Set();
|
|
320
403
|
|
|
321
404
|
/** @type {Promise<void> | null} */
|
|
322
|
-
|
|
405
|
+
let pending_invalidate;
|
|
323
406
|
|
|
324
407
|
/**
|
|
325
408
|
* @type {Map<string, Map<string, CacheEntry<Query<any>>>>}
|
|
@@ -350,12 +433,33 @@ set_match_implementation(async (url) => {
|
|
|
350
433
|
return null;
|
|
351
434
|
});
|
|
352
435
|
|
|
436
|
+
let embedded_start = Promise.resolve();
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* TODO this indirection is a grotesque workaround for the fact that multiple apps
|
|
440
|
+
* can operate on the same shared mutable state. This is fundamentally unsound,
|
|
441
|
+
* and the `start`/`_start` distinction does not fix it, but it does get the
|
|
442
|
+
* tests passing. We need to rethink this whole thing but not right now
|
|
443
|
+
* @param {import('./types.js').SvelteKitApp} _app
|
|
444
|
+
* @param {HTMLElement} _target
|
|
445
|
+
* @param {Parameters<typeof _hydrate>[1]} [data]
|
|
446
|
+
*/
|
|
447
|
+
export function start(_app, _target, data) {
|
|
448
|
+
if (__SVELTEKIT_EMBEDDED__) {
|
|
449
|
+
const start_promise = embedded_start.then(() => _start(_app, _target, data));
|
|
450
|
+
embedded_start = start_promise.catch(noop);
|
|
451
|
+
return start_promise;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return _start(_app, _target, data);
|
|
455
|
+
}
|
|
456
|
+
|
|
353
457
|
/**
|
|
354
458
|
* @param {import('./types.js').SvelteKitApp} _app
|
|
355
459
|
* @param {HTMLElement} _target
|
|
356
|
-
* @param {Parameters<typeof _hydrate>[1]} [
|
|
460
|
+
* @param {Parameters<typeof _hydrate>[1]} [data]
|
|
357
461
|
*/
|
|
358
|
-
|
|
462
|
+
async function _start(_app, _target, data) {
|
|
359
463
|
if (DEV && _target === document.body) {
|
|
360
464
|
console.warn(
|
|
361
465
|
'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 +497,56 @@ export async function start(_app, _target, hydrate) {
|
|
|
393
497
|
// connectivity errors after initialisation don't nuke the app
|
|
394
498
|
default_layout_loader = _app.nodes[0];
|
|
395
499
|
default_error_loader = _app.nodes[1];
|
|
396
|
-
void default_layout_loader();
|
|
397
|
-
void default_error_loader();
|
|
398
500
|
|
|
399
|
-
|
|
400
|
-
|
|
501
|
+
const [root_layout, root_error] = await Promise.all([
|
|
502
|
+
default_layout_loader(),
|
|
503
|
+
default_error_loader()
|
|
504
|
+
]);
|
|
505
|
+
|
|
506
|
+
const tree = new RenderNode(root_layout.component, root_error.component);
|
|
507
|
+
|
|
508
|
+
props = new Props({
|
|
509
|
+
page,
|
|
510
|
+
tree,
|
|
511
|
+
form: undefined,
|
|
512
|
+
error: undefined,
|
|
513
|
+
onerror: (_, reset) => resetters.add(reset)
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const history_metadata = get_history_metadata();
|
|
517
|
+
current_history_index = history_metadata?.historyIndex ?? 0;
|
|
518
|
+
current_navigation_index = history_metadata?.navigationIndex ?? 0;
|
|
519
|
+
current_reset_index = history_metadata?.resetIndex ?? 0;
|
|
401
520
|
|
|
402
521
|
if (!current_history_index) {
|
|
403
522
|
// we use Date.now() as an offset so that cross-document navigations
|
|
404
523
|
// within the app don't result in data loss
|
|
405
|
-
current_history_index = current_navigation_index = Date.now();
|
|
524
|
+
current_history_index = current_navigation_index = current_reset_index = Date.now();
|
|
406
525
|
|
|
407
526
|
// create initial history entry, so we can return here
|
|
408
527
|
history.replaceState(
|
|
409
528
|
{
|
|
410
529
|
...history.state,
|
|
411
|
-
[
|
|
412
|
-
|
|
530
|
+
[HISTORY_METADATA_KEY]: {
|
|
531
|
+
historyIndex: current_history_index,
|
|
532
|
+
navigationIndex: current_navigation_index,
|
|
533
|
+
state: {},
|
|
534
|
+
persistState: false,
|
|
535
|
+
resetIndex: current_history_index
|
|
536
|
+
}
|
|
413
537
|
},
|
|
414
538
|
''
|
|
415
539
|
);
|
|
416
540
|
}
|
|
417
541
|
|
|
542
|
+
set_history_options(
|
|
543
|
+
current_history_index,
|
|
544
|
+
/** @type {HistoryMetadata} */ (get_history_metadata())
|
|
545
|
+
);
|
|
546
|
+
|
|
418
547
|
// if we reload the page, or Cmd-Shift-T back to it,
|
|
419
548
|
// recover scroll position
|
|
420
|
-
const scroll =
|
|
549
|
+
const scroll = history_info[current_history_index]?.scroll;
|
|
421
550
|
function restore_scroll() {
|
|
422
551
|
if (scroll) {
|
|
423
552
|
history.scrollRestoration = 'manual';
|
|
@@ -425,15 +554,17 @@ export async function start(_app, _target, hydrate) {
|
|
|
425
554
|
}
|
|
426
555
|
}
|
|
427
556
|
|
|
428
|
-
if (
|
|
557
|
+
if (data) {
|
|
429
558
|
restore_scroll();
|
|
430
559
|
|
|
431
|
-
await _hydrate(target,
|
|
560
|
+
await _hydrate(target, data);
|
|
432
561
|
} else {
|
|
433
562
|
await navigate({
|
|
434
563
|
type: 'enter',
|
|
435
564
|
url: resolve_url(app.hash ? decode_hash(new URL(location.href)) : location.href),
|
|
436
|
-
replace_state: true
|
|
565
|
+
replace_state: true,
|
|
566
|
+
state: history_metadata?.persistState ? history_metadata.state : {},
|
|
567
|
+
persist_state: history_metadata?.persistState ?? false
|
|
437
568
|
});
|
|
438
569
|
|
|
439
570
|
restore_scroll();
|
|
@@ -482,6 +613,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
482
613
|
}
|
|
483
614
|
|
|
484
615
|
const prev_state = page.state;
|
|
616
|
+
const prev_shallow = page.shallow;
|
|
485
617
|
const navigation_result = intent && (await load_route(intent));
|
|
486
618
|
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
|
|
487
619
|
return;
|
|
@@ -490,7 +622,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
490
622
|
if (navigation_result.type === 'redirect') {
|
|
491
623
|
return _goto(
|
|
492
624
|
new URL(navigation_result.location, current.url).href,
|
|
493
|
-
{
|
|
625
|
+
{ replace: true },
|
|
494
626
|
1,
|
|
495
627
|
token
|
|
496
628
|
);
|
|
@@ -506,11 +638,10 @@ async function _invalidate(reset_page_state = true) {
|
|
|
506
638
|
if (!reset_page_state) {
|
|
507
639
|
navigation_result.props.page.state = prev_state;
|
|
508
640
|
}
|
|
509
|
-
|
|
510
|
-
|
|
641
|
+
navigation_result.props.page.shallow = prev_shallow;
|
|
642
|
+
apply_navigation_result(navigation_result);
|
|
511
643
|
current = { ...navigation_result.state, nav: current.nav };
|
|
512
644
|
reset_invalidation();
|
|
513
|
-
root.$set(navigation_result.props);
|
|
514
645
|
|
|
515
646
|
// only wait for promises that are connected to queries that still exist
|
|
516
647
|
/** @type {Promise<any>[]} */
|
|
@@ -541,21 +672,21 @@ function reset_invalidation() {
|
|
|
541
672
|
|
|
542
673
|
/** @param {number} index */
|
|
543
674
|
function capture_snapshot(index) {
|
|
544
|
-
if (components.some((c) => c?.snapshot)) {
|
|
545
|
-
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
675
|
+
if (props.components.some((c) => c?.snapshot)) {
|
|
676
|
+
snapshots[index] = props.components.map((c) => c?.snapshot?.capture());
|
|
546
677
|
}
|
|
547
678
|
}
|
|
548
679
|
|
|
549
680
|
/** @param {number} index */
|
|
550
681
|
function restore_snapshot(index) {
|
|
551
682
|
snapshots[index]?.forEach((value, i) => {
|
|
552
|
-
components[i]?.snapshot?.restore(value);
|
|
683
|
+
props.components[i]?.snapshot?.restore(value);
|
|
553
684
|
});
|
|
554
685
|
}
|
|
555
686
|
|
|
556
687
|
function persist_state() {
|
|
557
|
-
|
|
558
|
-
storage.set(
|
|
688
|
+
capture_scroll(current_history_index);
|
|
689
|
+
storage.set(HISTORY_INFO_KEY, history_info);
|
|
559
690
|
|
|
560
691
|
capture_snapshot(current_navigation_index);
|
|
561
692
|
storage.set(SNAPSHOT_KEY, snapshots);
|
|
@@ -563,13 +694,13 @@ function persist_state() {
|
|
|
563
694
|
|
|
564
695
|
/**
|
|
565
696
|
* @param {string | URL} url
|
|
566
|
-
* @param {{
|
|
567
|
-
* @param {number} redirect_count
|
|
697
|
+
* @param {{ type?: import('@sveltejs/kit').NavigationType; replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean; event?: Event }} [options]
|
|
698
|
+
* @param {number} [redirect_count]
|
|
568
699
|
* @param {{}} [nav_token]
|
|
569
700
|
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
|
|
570
701
|
* @returns {Promise<void>}
|
|
571
702
|
*/
|
|
572
|
-
export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
703
|
+
export async function _goto(url, options = {}, redirect_count = 0, nav_token = {}, intent) {
|
|
573
704
|
/** @type {Set<string>} */
|
|
574
705
|
let query_keys;
|
|
575
706
|
/** @type {Set<string>} */
|
|
@@ -582,12 +713,13 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
582
713
|
}
|
|
583
714
|
|
|
584
715
|
await navigate({
|
|
585
|
-
type: 'goto',
|
|
716
|
+
type: options.type ?? 'goto',
|
|
586
717
|
url: resolve_url(url),
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
replace_state: options.replaceState,
|
|
718
|
+
reset: options.reset,
|
|
719
|
+
replace_state: options.replace,
|
|
590
720
|
state: options.state,
|
|
721
|
+
persist_state: options.persistState,
|
|
722
|
+
event: options.event,
|
|
591
723
|
redirect_count,
|
|
592
724
|
nav_token,
|
|
593
725
|
intent,
|
|
@@ -621,9 +753,8 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
621
753
|
if (options.refreshAll) {
|
|
622
754
|
// TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
|
|
623
755
|
// 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)
|
|
756
|
+
void tick()
|
|
757
|
+
.then(tick)
|
|
627
758
|
.then(() => {
|
|
628
759
|
for (const [id, entries] of query_map) {
|
|
629
760
|
for (const [payload, { resource }] of entries) {
|
|
@@ -665,7 +796,7 @@ async function _preload_data(intent) {
|
|
|
665
796
|
|
|
666
797
|
load_cache.promise.catch(discard_load_cache);
|
|
667
798
|
|
|
668
|
-
if (__SVELTEKIT_FORK_PRELOADS__
|
|
799
|
+
if (__SVELTEKIT_FORK_PRELOADS__) {
|
|
669
800
|
const lc = load_cache;
|
|
670
801
|
|
|
671
802
|
lc.fork = lc.promise.then((result) => {
|
|
@@ -673,10 +804,8 @@ async function _preload_data(intent) {
|
|
|
673
804
|
// resolve, bail rather than creating an orphan fork
|
|
674
805
|
if (lc === load_cache && result.type === 'loaded') {
|
|
675
806
|
try {
|
|
676
|
-
return
|
|
677
|
-
|
|
678
|
-
update(result.props.page);
|
|
679
|
-
current_tree = result.props.tree;
|
|
807
|
+
return fork(() => {
|
|
808
|
+
apply_navigation_result(result);
|
|
680
809
|
});
|
|
681
810
|
} catch {
|
|
682
811
|
// if it errors, it's because the experimental flag isn't enabled in Svelte
|
|
@@ -691,6 +820,53 @@ async function _preload_data(intent) {
|
|
|
691
820
|
return load_cache.promise;
|
|
692
821
|
}
|
|
693
822
|
|
|
823
|
+
/**
|
|
824
|
+
* Fetch and parse the route with the given ID from the server-side route resolution endpoint.
|
|
825
|
+
* Returns `ENDPOINT_ONLY` if the route exists but has no code to preload, or `undefined` if
|
|
826
|
+
* there is no such route. Only used when `router.resolution === 'server'`.
|
|
827
|
+
* @param {string} id
|
|
828
|
+
* @returns {Promise<import('types').CSRRoute | typeof ENDPOINT_ONLY | undefined>}
|
|
829
|
+
*/
|
|
830
|
+
async function load_route_by_id(id) {
|
|
831
|
+
/** @type {{ route?: import('types').CSRRouteServer, endpoint_only?: boolean }} */
|
|
832
|
+
let module;
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
module = await import(
|
|
836
|
+
/* @vite-ignore */
|
|
837
|
+
base + route_id_resolution_pathname(app_dir, id)
|
|
838
|
+
);
|
|
839
|
+
} catch {
|
|
840
|
+
// if there's no module at that path the response is a 404 (or, on a static
|
|
841
|
+
// host, fallback HTML with the wrong MIME type) and the import rejects —
|
|
842
|
+
// treat it the same as an unknown route rather than surfacing a cryptic error
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (module.endpoint_only) {
|
|
847
|
+
// The route exists, it just has no code to preload.
|
|
848
|
+
route_id_cache.set(id, ENDPOINT_ONLY);
|
|
849
|
+
return ENDPOINT_ONLY;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (!module.route) return;
|
|
853
|
+
|
|
854
|
+
return parse_and_cache_server_route(module.route);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Import the modules for a route's layout and leaf nodes, without running `load` functions.
|
|
859
|
+
* @param {import('types').CSRRoute} route
|
|
860
|
+
* @returns {Promise<void>}
|
|
861
|
+
*/
|
|
862
|
+
async function load_route_nodes(route) {
|
|
863
|
+
await Promise.all(
|
|
864
|
+
/** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
|
|
865
|
+
[...route.layouts, route.leaf].filter(Boolean)
|
|
866
|
+
).map(([, node_loader]) => node_loader())
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
694
870
|
/**
|
|
695
871
|
* @param {URL} url
|
|
696
872
|
* @returns {Promise<void>}
|
|
@@ -699,20 +875,16 @@ async function _preload_code(url) {
|
|
|
699
875
|
const route = (await get_navigation_intent(url, false))?.route;
|
|
700
876
|
|
|
701
877
|
if (route) {
|
|
702
|
-
await
|
|
703
|
-
/** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
|
|
704
|
-
[...route.layouts, route.leaf].filter(Boolean)
|
|
705
|
-
).map((load) => load[1]())
|
|
706
|
-
);
|
|
878
|
+
await load_route_nodes(route);
|
|
707
879
|
}
|
|
708
880
|
}
|
|
709
881
|
|
|
710
882
|
/**
|
|
711
883
|
* @param {import('./types.js').NavigationFinished} result
|
|
712
884
|
* @param {HTMLElement} target
|
|
713
|
-
* @param {boolean}
|
|
885
|
+
* @param {boolean} should_hydrate
|
|
714
886
|
*/
|
|
715
|
-
async function initialize(result, target,
|
|
887
|
+
async function initialize(result, target, should_hydrate) {
|
|
716
888
|
if (__SVELTEKIT_DEV__ && result.state.error && document.querySelector('vite-error-overlay'))
|
|
717
889
|
return;
|
|
718
890
|
|
|
@@ -734,21 +906,18 @@ async function initialize(result, target, hydrate) {
|
|
|
734
906
|
if (style) style.remove();
|
|
735
907
|
}
|
|
736
908
|
|
|
737
|
-
|
|
738
|
-
current_tree = result.props.tree;
|
|
909
|
+
apply_navigation_result(result);
|
|
739
910
|
|
|
740
|
-
// TODO
|
|
741
|
-
|
|
911
|
+
// TODO treeshake `hydrate` in csr mode
|
|
912
|
+
const render = should_hydrate ? hydrate : mount;
|
|
913
|
+
|
|
914
|
+
render(Root, {
|
|
742
915
|
target,
|
|
743
|
-
props
|
|
744
|
-
hydrate,
|
|
745
|
-
// Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
|
|
746
|
-
sync: false,
|
|
916
|
+
props,
|
|
747
917
|
transformError: /** @param {unknown} e */ async (e) => {
|
|
748
918
|
const error = await handle_error(e, current.nav);
|
|
749
|
-
rendering_error = { error, status: error.status };
|
|
750
919
|
page.error = error;
|
|
751
|
-
page.status =
|
|
920
|
+
page.status = error.status;
|
|
752
921
|
return error;
|
|
753
922
|
}
|
|
754
923
|
});
|
|
@@ -757,16 +926,17 @@ async function initialize(result, target, hydrate) {
|
|
|
757
926
|
// which causes component script blocks to run asynchronously
|
|
758
927
|
void (await Promise.resolve());
|
|
759
928
|
|
|
760
|
-
if (
|
|
929
|
+
if (should_hydrate) {
|
|
761
930
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
762
931
|
const navigation = {
|
|
763
932
|
from: null,
|
|
764
933
|
to: {
|
|
765
934
|
...nav,
|
|
766
|
-
scroll:
|
|
935
|
+
scroll: history_info[current_history_index]?.scroll ?? scroll_state()
|
|
767
936
|
},
|
|
768
937
|
willUnload: false,
|
|
769
938
|
type: 'enter',
|
|
939
|
+
shallow: false,
|
|
770
940
|
complete: Promise.resolve()
|
|
771
941
|
};
|
|
772
942
|
|
|
@@ -848,7 +1018,7 @@ async function get_navigation_result_from_branch({
|
|
|
848
1018
|
let current_node = result.props.tree;
|
|
849
1019
|
|
|
850
1020
|
/** @type {RenderNode | undefined} */
|
|
851
|
-
let previous_node =
|
|
1021
|
+
let previous_node = props.tree;
|
|
852
1022
|
|
|
853
1023
|
for (let i = 0; i < branch.length; i += 1) {
|
|
854
1024
|
const node = branch[i];
|
|
@@ -856,11 +1026,6 @@ async function get_navigation_result_from_branch({
|
|
|
856
1026
|
|
|
857
1027
|
if (!node) continue;
|
|
858
1028
|
|
|
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
1029
|
if (
|
|
865
1030
|
// if an ancestor node in this path changed, `data_changed` is already true and the
|
|
866
1031
|
// accumulated `data` differs from the previous render, so we must re-merge
|
|
@@ -878,10 +1043,24 @@ async function get_navigation_result_from_branch({
|
|
|
878
1043
|
data = current_node.data;
|
|
879
1044
|
|
|
880
1045
|
if (i < branch.length - 1) {
|
|
881
|
-
|
|
882
|
-
|
|
1046
|
+
let next_index = i + 1;
|
|
1047
|
+
while (next_index < branch.length && !branch[next_index]) {
|
|
1048
|
+
next_index += 1;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const next = branch[next_index];
|
|
1052
|
+
|
|
1053
|
+
if (next) {
|
|
1054
|
+
const error_loader =
|
|
1055
|
+
errors?.slice(0, next_index + 1).findLast((x) => x) ?? default_error_loader;
|
|
1056
|
+
|
|
1057
|
+
current_node = current_node.child = new RenderNode(
|
|
1058
|
+
next.node.component,
|
|
1059
|
+
(await error_loader()).component
|
|
1060
|
+
);
|
|
883
1061
|
|
|
884
|
-
|
|
1062
|
+
previous_node = previous_node?.child;
|
|
1063
|
+
}
|
|
885
1064
|
}
|
|
886
1065
|
}
|
|
887
1066
|
|
|
@@ -900,6 +1079,7 @@ async function get_navigation_result_from_branch({
|
|
|
900
1079
|
id: route?.id ?? null
|
|
901
1080
|
},
|
|
902
1081
|
state: {},
|
|
1082
|
+
shallow: null,
|
|
903
1083
|
status: status ?? error?.status ?? 200,
|
|
904
1084
|
url: new URL(url),
|
|
905
1085
|
form: form ?? null,
|
|
@@ -1298,7 +1478,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1298
1478
|
|
|
1299
1479
|
if (server_data_node?.type === 'error') {
|
|
1300
1480
|
// rethrow and catch below
|
|
1301
|
-
throw new HttpError(server_data_node.error
|
|
1481
|
+
throw new HttpError(server_data_node.error);
|
|
1302
1482
|
}
|
|
1303
1483
|
|
|
1304
1484
|
return load_node({
|
|
@@ -1504,7 +1684,7 @@ async function load_root_error_page({ error, url, route }) {
|
|
|
1504
1684
|
// client-side navigation if the root layout loader throws a redirect while
|
|
1505
1685
|
// rendering the default error page
|
|
1506
1686
|
if (error instanceof Redirect) {
|
|
1507
|
-
await _goto(new URL(error.location, location.href)
|
|
1687
|
+
await _goto(new URL(error.location, location.href));
|
|
1508
1688
|
return;
|
|
1509
1689
|
}
|
|
1510
1690
|
|
|
@@ -1626,7 +1806,7 @@ export async function get_navigation_intent(url, invalidating) {
|
|
|
1626
1806
|
return {
|
|
1627
1807
|
id: get_page_key(url),
|
|
1628
1808
|
invalidating,
|
|
1629
|
-
route:
|
|
1809
|
+
route: parse_and_cache_server_route(route),
|
|
1630
1810
|
params,
|
|
1631
1811
|
url
|
|
1632
1812
|
};
|
|
@@ -1655,12 +1835,14 @@ function get_page_key(url) {
|
|
|
1655
1835
|
* delta?: number;
|
|
1656
1836
|
* event?: PopStateEvent | MouseEvent;
|
|
1657
1837
|
* scroll?: { x: number, y: number };
|
|
1838
|
+
* shallow?: boolean;
|
|
1839
|
+
* target?: { params: Record<string, string> | null; route: { id: string } | null; url: URL };
|
|
1658
1840
|
* }} opts
|
|
1659
1841
|
*/
|
|
1660
|
-
function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
1842
|
+
function _before_navigate({ url, type, intent, delta, event, scroll, shallow = false, target }) {
|
|
1661
1843
|
let should_block = false;
|
|
1662
1844
|
|
|
1663
|
-
const nav = create_navigation(current, intent, url, type, scroll ?? null);
|
|
1845
|
+
const nav = create_navigation(current, intent, url, type, scroll ?? null, shallow, target);
|
|
1664
1846
|
|
|
1665
1847
|
if (nav.navigation.type === 'popstate' && delta !== undefined) {
|
|
1666
1848
|
nav.navigation.delta = delta;
|
|
@@ -1681,6 +1863,9 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1681
1863
|
|
|
1682
1864
|
if (!is_navigating) {
|
|
1683
1865
|
// Don't run the event during redirects
|
|
1866
|
+
// TODO this isn't fully right: if you do a goto(...) while another goto(...) is in progress,
|
|
1867
|
+
// or you click a link while a navigation is in progress, the beforNavigate calls are not triggered,
|
|
1868
|
+
// and maybe they should be?
|
|
1684
1869
|
before_navigate_callbacks.forEach((fn) => fn(cancellable));
|
|
1685
1870
|
}
|
|
1686
1871
|
|
|
@@ -1693,13 +1878,14 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1693
1878
|
* url: URL;
|
|
1694
1879
|
* popped?: {
|
|
1695
1880
|
* state: Record<string, any>;
|
|
1696
|
-
* scroll
|
|
1881
|
+
* scroll?: { x: number, y: number };
|
|
1697
1882
|
* delta: number;
|
|
1883
|
+
* shallow: { params: Record<string, string> | null; route: { id: string } | null; url: URL } | null;
|
|
1698
1884
|
* };
|
|
1699
|
-
*
|
|
1700
|
-
* noscroll?: boolean;
|
|
1885
|
+
* reset?: boolean;
|
|
1701
1886
|
* replace_state?: boolean;
|
|
1702
1887
|
* state?: Record<string, any>;
|
|
1888
|
+
* persist_state?: boolean;
|
|
1703
1889
|
* redirect_count?: number;
|
|
1704
1890
|
* nav_token?: {};
|
|
1705
1891
|
* accept?: () => void;
|
|
@@ -1713,10 +1899,10 @@ async function navigate({
|
|
|
1713
1899
|
type,
|
|
1714
1900
|
url,
|
|
1715
1901
|
popped,
|
|
1716
|
-
|
|
1717
|
-
noscroll,
|
|
1902
|
+
reset = true,
|
|
1718
1903
|
replace_state,
|
|
1719
1904
|
state = {},
|
|
1905
|
+
persist_state = false,
|
|
1720
1906
|
redirect_count = 0,
|
|
1721
1907
|
nav_token = {},
|
|
1722
1908
|
accept = noop,
|
|
@@ -1738,6 +1924,8 @@ async function navigate({
|
|
|
1738
1924
|
delta: popped?.delta,
|
|
1739
1925
|
intent,
|
|
1740
1926
|
scroll: popped?.scroll,
|
|
1927
|
+
shallow: !!popped?.shallow,
|
|
1928
|
+
target: popped?.shallow ?? undefined,
|
|
1741
1929
|
// @ts-ignore
|
|
1742
1930
|
event
|
|
1743
1931
|
});
|
|
@@ -1821,10 +2009,10 @@ async function navigate({
|
|
|
1821
2009
|
type,
|
|
1822
2010
|
url: new URL(navigation_result.location, url),
|
|
1823
2011
|
popped,
|
|
1824
|
-
|
|
1825
|
-
noscroll,
|
|
2012
|
+
reset,
|
|
1826
2013
|
replace_state,
|
|
1827
2014
|
state,
|
|
2015
|
+
persist_state,
|
|
1828
2016
|
redirect_count: redirect_count + 1,
|
|
1829
2017
|
nav_token
|
|
1830
2018
|
});
|
|
@@ -1858,7 +2046,7 @@ async function navigate({
|
|
|
1858
2046
|
|
|
1859
2047
|
updating = true;
|
|
1860
2048
|
|
|
1861
|
-
|
|
2049
|
+
capture_scroll(previous_history_index);
|
|
1862
2050
|
capture_snapshot(previous_navigation_index);
|
|
1863
2051
|
|
|
1864
2052
|
// ensure the url pathname matches the page's trailing slash option
|
|
@@ -1871,15 +2059,23 @@ async function navigate({
|
|
|
1871
2059
|
if (!popped) {
|
|
1872
2060
|
// this is a new navigation, rather than a popstate
|
|
1873
2061
|
const change = replace_state ? 0 : 1;
|
|
2062
|
+
if (type !== 'enter') {
|
|
2063
|
+
if (reset) current_reset_index += 1;
|
|
2064
|
+
}
|
|
1874
2065
|
|
|
1875
2066
|
const entry = {
|
|
1876
|
-
[
|
|
1877
|
-
|
|
1878
|
-
|
|
2067
|
+
[HISTORY_METADATA_KEY]: /** @satisfies {HistoryMetadata} */ ({
|
|
2068
|
+
historyIndex: (current_history_index += change),
|
|
2069
|
+
navigationIndex: (current_navigation_index += change),
|
|
2070
|
+
state,
|
|
2071
|
+
persistState: persist_state,
|
|
2072
|
+
resetIndex: current_reset_index
|
|
2073
|
+
})
|
|
1879
2074
|
};
|
|
1880
2075
|
|
|
1881
2076
|
const fn = replace_state ? history.replaceState : history.pushState;
|
|
1882
2077
|
fn.call(history, entry, '', url);
|
|
2078
|
+
set_history_options(current_history_index, entry[HISTORY_METADATA_KEY]);
|
|
1883
2079
|
|
|
1884
2080
|
if (!replace_state) {
|
|
1885
2081
|
clear_onward_history(current_history_index, current_navigation_index);
|
|
@@ -1898,6 +2094,7 @@ async function navigate({
|
|
|
1898
2094
|
}
|
|
1899
2095
|
|
|
1900
2096
|
navigation_result.props.page.state = state;
|
|
2097
|
+
navigation_result.props.page.shallow = popped?.shallow ?? null;
|
|
1901
2098
|
|
|
1902
2099
|
/**
|
|
1903
2100
|
* @type {Promise<void> | undefined}
|
|
@@ -1928,7 +2125,13 @@ async function navigate({
|
|
|
1928
2125
|
}
|
|
1929
2126
|
|
|
1930
2127
|
// Type-casts are save because we know this resolved a proper SvelteKit route
|
|
1931
|
-
const target =
|
|
2128
|
+
const target = popped?.shallow
|
|
2129
|
+
? {
|
|
2130
|
+
params: navigation_result.state.params,
|
|
2131
|
+
route: { id: navigation_result.state.route?.id ?? null },
|
|
2132
|
+
url: navigation_result.state.url
|
|
2133
|
+
}
|
|
2134
|
+
: /** @type {import('@sveltejs/kit').NavigationTarget} */ (nav.navigation.to);
|
|
1932
2135
|
current = {
|
|
1933
2136
|
...navigation_result.state,
|
|
1934
2137
|
nav: {
|
|
@@ -1945,13 +2148,7 @@ async function navigate({
|
|
|
1945
2148
|
|
|
1946
2149
|
// Remove focus before updating the component tree, so that blur/focusout
|
|
1947
2150
|
// 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
|
-
}
|
|
2151
|
+
blur_active_element(reset);
|
|
1955
2152
|
|
|
1956
2153
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1957
2154
|
|
|
@@ -1960,28 +2157,22 @@ async function navigate({
|
|
|
1960
2157
|
// `fork.commit()` applies the preloaded state synchronously before the
|
|
1961
2158
|
// first `await`, so reset any previously-failed boundaries now so the
|
|
1962
2159
|
// stale `+error.svelte` is torn down. See sveltejs/kit#15694.
|
|
1963
|
-
for (const
|
|
1964
|
-
|
|
2160
|
+
for (const reset_boundary of resetters) {
|
|
2161
|
+
reset_boundary();
|
|
1965
2162
|
}
|
|
1966
|
-
resetters.
|
|
2163
|
+
resetters.clear();
|
|
1967
2164
|
} else {
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
current_tree = navigation_result.props.tree;
|
|
2165
|
+
apply_navigation_result(navigation_result);
|
|
2166
|
+
|
|
1971
2167
|
// Reset any boundaries that failed on a previous navigation now that the
|
|
1972
2168
|
// new props are applied, otherwise the stale `+error.svelte` stays
|
|
1973
2169
|
// mounted above the new route's content. See sveltejs/kit#15694.
|
|
1974
|
-
for (const
|
|
1975
|
-
|
|
1976
|
-
}
|
|
1977
|
-
resetters.length = 0;
|
|
1978
|
-
// Check for sync rendering error
|
|
1979
|
-
if (rendering_error) {
|
|
1980
|
-
Object.assign(navigation_result.props.page, rendering_error);
|
|
2170
|
+
for (const reset_boundary of resetters) {
|
|
2171
|
+
reset_boundary();
|
|
1981
2172
|
}
|
|
1982
|
-
|
|
2173
|
+
resetters.clear();
|
|
1983
2174
|
|
|
1984
|
-
commit_promise =
|
|
2175
|
+
commit_promise = settled();
|
|
1985
2176
|
}
|
|
1986
2177
|
|
|
1987
2178
|
has_navigated = true;
|
|
@@ -1991,7 +2182,7 @@ async function navigate({
|
|
|
1991
2182
|
|
|
1992
2183
|
const { activeElement } = document;
|
|
1993
2184
|
|
|
1994
|
-
await
|
|
2185
|
+
await commit_promise;
|
|
1995
2186
|
|
|
1996
2187
|
if (navigation_token !== nav_token) {
|
|
1997
2188
|
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
@@ -1999,44 +2190,7 @@ async function navigate({
|
|
|
1999
2190
|
return;
|
|
2000
2191
|
}
|
|
2001
2192
|
|
|
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;
|
|
2193
|
+
reset_scroll_and_focus(url, reset ? popped?.scroll : scroll_state(), reset, activeElement);
|
|
2040
2194
|
|
|
2041
2195
|
is_navigating = false;
|
|
2042
2196
|
|
|
@@ -2280,7 +2434,7 @@ export async function handle_error(error, event) {
|
|
|
2280
2434
|
* @param {T} callback
|
|
2281
2435
|
*/
|
|
2282
2436
|
function add_navigation_callback(callbacks, callback) {
|
|
2283
|
-
|
|
2437
|
+
onMount(() => {
|
|
2284
2438
|
callbacks.add(callback);
|
|
2285
2439
|
|
|
2286
2440
|
return () => {
|
|
@@ -2352,24 +2506,49 @@ export function disableScrollHandling() {
|
|
|
2352
2506
|
}
|
|
2353
2507
|
|
|
2354
2508
|
let warned_on_invalidate_all = false;
|
|
2509
|
+
let warned_on_replace_state = false;
|
|
2510
|
+
let warned_on_push_state = false;
|
|
2511
|
+
let warned_on_replace_state_function = false;
|
|
2355
2512
|
|
|
2356
2513
|
/**
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2514
|
+
* @param {string | URL} url
|
|
2515
|
+
* @param {'goto' | 'pushState' | 'replaceState'} caller
|
|
2516
|
+
*/
|
|
2517
|
+
async function resolve_intent(url, caller) {
|
|
2518
|
+
const resolved = new URL(resolve_url(url));
|
|
2519
|
+
|
|
2520
|
+
if (resolved.origin !== origin) {
|
|
2521
|
+
throw new Error(
|
|
2522
|
+
DEV
|
|
2523
|
+
? `Cannot use \`${caller}\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
2524
|
+
: `${caller}: invalid URL`
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
const intent = await get_navigation_intent(resolved, false);
|
|
2529
|
+
|
|
2530
|
+
if (!intent) {
|
|
2531
|
+
throw new Error(
|
|
2532
|
+
DEV
|
|
2533
|
+
? `Cannot use \`${caller}\` with a URL that does not resolve to a route within the app. Use \`window.location = "${url}"\` instead`
|
|
2534
|
+
: `${caller}: invalid URL`
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
return intent;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
/**
|
|
2542
|
+
* Allows you to navigate programmatically to a given route, with control over details such as whether scroll and focus are reset
|
|
2543
|
+
* (as they would be with a regular navigation) or preserved.
|
|
2359
2544
|
*
|
|
2360
|
-
*
|
|
2361
|
-
*
|
|
2545
|
+
* 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.
|
|
2546
|
+
*
|
|
2547
|
+
* `goto` is intended for navigations to routes that belong to the app, and will reject if a route cannot be resolved.
|
|
2362
2548
|
* For external URLs, use `window.location = url` to perform a full-page navigation instead of calling `goto(url)`.
|
|
2363
2549
|
*
|
|
2364
2550
|
* @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`
|
|
2551
|
+
* @param {import('@sveltejs/kit').GotoOptions} [opts] Options related to the navigation
|
|
2373
2552
|
* @returns {Promise<void>}
|
|
2374
2553
|
*/
|
|
2375
2554
|
export async function goto(url, opts = {}) {
|
|
@@ -2377,23 +2556,35 @@ export async function goto(url, opts = {}) {
|
|
|
2377
2556
|
throw new Error('Cannot call goto(...) on the server');
|
|
2378
2557
|
}
|
|
2379
2558
|
|
|
2380
|
-
|
|
2559
|
+
if (DEV) {
|
|
2560
|
+
if ('replaceState' in opts && !warned_on_replace_state) {
|
|
2561
|
+
warned_on_replace_state = true;
|
|
2562
|
+
console.warn(
|
|
2563
|
+
`The \`goto(..., { replaceState: ${opts.replaceState} })\` option has been deprecated in favour of \`replace\``
|
|
2564
|
+
);
|
|
2565
|
+
}
|
|
2381
2566
|
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
);
|
|
2567
|
+
if ('noScroll' in opts || 'keepFocus' in opts) {
|
|
2568
|
+
throw new Error(
|
|
2569
|
+
`The \`goto(..., { noScroll: true, keepFocus: true })\` options have been replaced by \`reset: false\``
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2388
2572
|
}
|
|
2389
2573
|
|
|
2390
|
-
const
|
|
2574
|
+
const replace = opts.replace ?? opts.replaceState ?? false;
|
|
2391
2575
|
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2576
|
+
const intent = await resolve_intent(url, 'goto');
|
|
2577
|
+
|
|
2578
|
+
if (opts.shallow) {
|
|
2579
|
+
return update_state(
|
|
2580
|
+
intent,
|
|
2581
|
+
opts.state ?? {},
|
|
2582
|
+
{
|
|
2583
|
+
replace,
|
|
2584
|
+
persist_state: opts.persistState ?? false,
|
|
2585
|
+
reset: opts.reset ?? false
|
|
2586
|
+
},
|
|
2587
|
+
'goto'
|
|
2397
2588
|
);
|
|
2398
2589
|
}
|
|
2399
2590
|
|
|
@@ -2404,8 +2595,13 @@ export async function goto(url, opts = {}) {
|
|
|
2404
2595
|
);
|
|
2405
2596
|
}
|
|
2406
2597
|
|
|
2407
|
-
|
|
2408
|
-
|
|
2598
|
+
return _goto(
|
|
2599
|
+
intent.url,
|
|
2600
|
+
{ ...opts, replace, refreshAll: opts.refreshAll ?? opts.invalidateAll },
|
|
2601
|
+
0,
|
|
2602
|
+
{},
|
|
2603
|
+
intent
|
|
2604
|
+
);
|
|
2409
2605
|
}
|
|
2410
2606
|
|
|
2411
2607
|
/**
|
|
@@ -2537,105 +2733,150 @@ export async function preloadData(href) {
|
|
|
2537
2733
|
* Programmatically imports the code for routes that haven't yet been fetched.
|
|
2538
2734
|
* Typically, you might call this to speed up subsequent navigation.
|
|
2539
2735
|
*
|
|
2540
|
-
*
|
|
2736
|
+
* Takes a route ID such as `/about` or `/blog/[slug]`. Unlike pathnames, route IDs
|
|
2737
|
+
* are never prefixed with the app's [base path](https://svelte.dev/docs/kit/configuration#paths).
|
|
2738
|
+
* If you have a pathname rather than a route ID, you can convert it with
|
|
2739
|
+
* [`match`](https://svelte.dev/docs/kit/$app-paths#match) from `$app/paths`:
|
|
2740
|
+
*
|
|
2741
|
+
* ```js
|
|
2742
|
+
* import { match } from '$app/paths';
|
|
2743
|
+
* import { preloadCode } from '$app/navigation';
|
|
2744
|
+
*
|
|
2745
|
+
* const matched = await match('/blog/hello-world');
|
|
2746
|
+
* if (matched) await preloadCode(matched.id);
|
|
2747
|
+
* ```
|
|
2541
2748
|
*
|
|
2542
2749
|
* Unlike `preloadData`, this won't call `load` functions.
|
|
2543
2750
|
* Returns a Promise that resolves when the modules have been imported.
|
|
2544
2751
|
*
|
|
2545
|
-
* @param {
|
|
2752
|
+
* @param {RouteId} id
|
|
2546
2753
|
* @returns {Promise<void>}
|
|
2547
2754
|
*/
|
|
2548
|
-
export async function preloadCode(
|
|
2755
|
+
export async function preloadCode(id) {
|
|
2549
2756
|
if (!BROWSER) {
|
|
2550
2757
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
2551
2758
|
}
|
|
2552
2759
|
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2760
|
+
if (DEV && id[0] !== '/') {
|
|
2761
|
+
throw new Error(
|
|
2762
|
+
`argument passed to preloadCode must be a route ID (i.e. "/blog/[slug]" rather than "blog/[slug]")`
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2556
2765
|
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
'argument passed to preloadCode must be a pathname (i.e. "/about" rather than "http://example.com/about"'
|
|
2561
|
-
);
|
|
2562
|
-
}
|
|
2766
|
+
const route = __SVELTEKIT_CLIENT_ROUTING__
|
|
2767
|
+
? routes.find((r) => r.id === id)
|
|
2768
|
+
: (route_id_cache.get(id) ?? (await load_route_by_id(id)));
|
|
2563
2769
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2770
|
+
if (route === ENDPOINT_ONLY) {
|
|
2771
|
+
if (DEV) {
|
|
2772
|
+
console.warn(
|
|
2773
|
+
`'${id}' has no \`+page\`, so there is no code to preload. If you meant to warm up an ` +
|
|
2774
|
+
`endpoint, request it with \`fetch\` instead.`
|
|
2567
2775
|
);
|
|
2568
2776
|
}
|
|
2569
2777
|
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
if (!route) {
|
|
2782
|
+
if (DEV) {
|
|
2783
|
+
// warn rather than throw, since under client routing an endpoint-only route id is
|
|
2784
|
+
// indistinguishable from a typo — the client manifest only contains routes with a `+page`
|
|
2785
|
+
let message = `'${id}' did not match any route`;
|
|
2786
|
+
|
|
2787
|
+
if (__SVELTEKIT_CLIENT_ROUTING__) {
|
|
2788
|
+
message += ` (note that routes without a \`+page\` have no code to preload)`;
|
|
2789
|
+
|
|
2790
|
+
// the most common migration mistake is passing a pathname, which used to work
|
|
2791
|
+
const candidates = [id];
|
|
2792
|
+
if (base && id.startsWith(base)) candidates.push(id.slice(base.length) || '/');
|
|
2793
|
+
|
|
2794
|
+
if (candidates.some((path) => routes.some((r) => r.exec(path)))) {
|
|
2795
|
+
message += `. It does match as a pathname — use \`match(...)\` from \`$app/paths\` to convert a pathname into a route ID`;
|
|
2796
|
+
}
|
|
2574
2797
|
}
|
|
2798
|
+
|
|
2799
|
+
console.warn(message);
|
|
2575
2800
|
}
|
|
2801
|
+
|
|
2802
|
+
return;
|
|
2576
2803
|
}
|
|
2577
2804
|
|
|
2578
|
-
|
|
2805
|
+
await load_route_nodes(route);
|
|
2579
2806
|
}
|
|
2580
2807
|
|
|
2581
2808
|
/**
|
|
2582
|
-
* Programmatically create a new history entry with the given `page.state`.
|
|
2809
|
+
* Programmatically create a new history entry with the given `page.state`. Used for [shallow routing](https://svelte.dev/docs/kit/shallow-routing).
|
|
2583
2810
|
*
|
|
2811
|
+
* @deprecated Use `goto(url, { state, shallow: true })` instead.
|
|
2584
2812
|
* @param {string | URL} url
|
|
2585
2813
|
* @param {App.PageState} state
|
|
2586
|
-
* @returns {void}
|
|
2814
|
+
* @returns {Promise<void>}
|
|
2587
2815
|
*/
|
|
2588
|
-
export function pushState(url, state) {
|
|
2816
|
+
export async function pushState(url, state) {
|
|
2589
2817
|
if (!BROWSER) {
|
|
2590
2818
|
throw new Error('Cannot call pushState(...) on the server');
|
|
2591
2819
|
}
|
|
2592
2820
|
|
|
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
|
-
}
|
|
2821
|
+
if (DEV && !warned_on_push_state) {
|
|
2822
|
+
warned_on_push_state = true;
|
|
2823
|
+
console.warn(
|
|
2824
|
+
'`pushState(...)` is deprecated. Use `goto(url, { state, shallow: true })` instead.'
|
|
2825
|
+
);
|
|
2605
2826
|
}
|
|
2606
2827
|
|
|
2607
|
-
|
|
2828
|
+
const intent = await resolve_intent(url, 'pushState');
|
|
2608
2829
|
|
|
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);
|
|
2830
|
+
await update_state(
|
|
2831
|
+
intent,
|
|
2832
|
+
state,
|
|
2833
|
+
{ replace: false, persist_state: false, reset: false },
|
|
2834
|
+
'pushState'
|
|
2835
|
+
);
|
|
2622
2836
|
}
|
|
2623
2837
|
|
|
2624
2838
|
/**
|
|
2625
|
-
* Programmatically replace the current history entry with the given `page.state`.
|
|
2839
|
+
* Programmatically replace the current history entry with the given `page.state`. Used for [shallow routing](https://svelte.dev/docs/kit/shallow-routing).
|
|
2626
2840
|
*
|
|
2841
|
+
* @deprecated Use `goto(url, { state, shallow: true, replace: true })` instead.
|
|
2627
2842
|
* @param {string | URL} url
|
|
2628
2843
|
* @param {App.PageState} state
|
|
2629
|
-
* @returns {void}
|
|
2844
|
+
* @returns {Promise<void>}
|
|
2630
2845
|
*/
|
|
2631
|
-
export function replaceState(url, state) {
|
|
2846
|
+
export async function replaceState(url, state) {
|
|
2632
2847
|
if (!BROWSER) {
|
|
2633
2848
|
throw new Error('Cannot call replaceState(...) on the server');
|
|
2634
2849
|
}
|
|
2635
2850
|
|
|
2851
|
+
if (DEV && !warned_on_replace_state_function) {
|
|
2852
|
+
warned_on_replace_state_function = true;
|
|
2853
|
+
console.warn(
|
|
2854
|
+
'`replaceState(...)` is deprecated. Use `goto(url, { state, shallow: true, replace: true })` instead.'
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
const intent = await resolve_intent(url, 'replaceState');
|
|
2859
|
+
|
|
2860
|
+
await update_state(
|
|
2861
|
+
intent,
|
|
2862
|
+
state,
|
|
2863
|
+
{ replace: true, persist_state: false, reset: false },
|
|
2864
|
+
'replaceState'
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
/**
|
|
2869
|
+
* @param {NavigationIntent} intent
|
|
2870
|
+
* @param {App.PageState} state
|
|
2871
|
+
* @param {{ replace: boolean; persist_state: boolean; reset: boolean; }} options
|
|
2872
|
+
* @param {'goto' | 'pushState' | 'replaceState'} caller
|
|
2873
|
+
*/
|
|
2874
|
+
async function update_state(intent, state, { replace, persist_state, reset }, caller) {
|
|
2875
|
+
const url = intent.url;
|
|
2876
|
+
|
|
2636
2877
|
if (DEV) {
|
|
2637
2878
|
if (!started) {
|
|
2638
|
-
throw new Error(
|
|
2879
|
+
throw new Error(`Cannot call ${caller}(...) before router is initialized`);
|
|
2639
2880
|
}
|
|
2640
2881
|
|
|
2641
2882
|
try {
|
|
@@ -2647,16 +2888,100 @@ export function replaceState(url, state) {
|
|
|
2647
2888
|
}
|
|
2648
2889
|
}
|
|
2649
2890
|
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2891
|
+
const nav =
|
|
2892
|
+
// For backwards compatibility we don't trigger navigation hooks etc for push/replaceState
|
|
2893
|
+
caller === 'goto' ? _before_navigate({ url, type: 'goto', intent, shallow: true }) : undefined;
|
|
2894
|
+
|
|
2895
|
+
if (!nav && caller === 'goto') return;
|
|
2896
|
+
|
|
2897
|
+
const nav_token = {};
|
|
2898
|
+
|
|
2899
|
+
if (nav) {
|
|
2900
|
+
navigation_token = invalidation_token = nav_token;
|
|
2901
|
+
is_navigating = true;
|
|
2902
|
+
navigating.current = nav.navigation;
|
|
2903
|
+
updating = true;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
if (!replace) capture_scroll(current_history_index);
|
|
2907
|
+
if (reset) current_reset_index += 1;
|
|
2908
|
+
|
|
2909
|
+
const entry = {
|
|
2910
|
+
[HISTORY_METADATA_KEY]: /** @satisfies {HistoryMetadata} */ ({
|
|
2911
|
+
historyIndex: (current_history_index += replace ? 0 : 1),
|
|
2912
|
+
navigationIndex: current_navigation_index,
|
|
2913
|
+
pageUrl: page.url.href,
|
|
2914
|
+
state,
|
|
2915
|
+
persistState: persist_state,
|
|
2916
|
+
resetIndex: current_reset_index
|
|
2917
|
+
})
|
|
2655
2918
|
};
|
|
2656
2919
|
|
|
2657
|
-
history.replaceState
|
|
2920
|
+
const fn = replace ? history.replaceState : history.pushState;
|
|
2921
|
+
fn.call(history, entry, '', url);
|
|
2922
|
+
set_history_options(current_history_index, entry[HISTORY_METADATA_KEY]);
|
|
2923
|
+
|
|
2924
|
+
if (!replace) {
|
|
2925
|
+
has_navigated = true;
|
|
2926
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
if (nav) {
|
|
2930
|
+
const after_navigate = (
|
|
2931
|
+
await Promise.all(
|
|
2932
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable -- we need to await because they can be asynchronous
|
|
2933
|
+
Array.from(on_navigate_callbacks, (fn) =>
|
|
2934
|
+
fn(/** @type {import('@sveltejs/kit').OnNavigate} */ (nav.navigation))
|
|
2935
|
+
)
|
|
2936
|
+
)
|
|
2937
|
+
).filter(/** @returns {value is () => void} */ (value) => typeof value === 'function');
|
|
2938
|
+
|
|
2939
|
+
if (after_navigate.length > 0) {
|
|
2940
|
+
function cleanup() {
|
|
2941
|
+
after_navigate.forEach((fn) => after_navigate_callbacks.delete(fn));
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
after_navigate.push(cleanup);
|
|
2945
|
+
after_navigate.forEach((fn) => after_navigate_callbacks.add(fn));
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
blur_active_element(reset);
|
|
2658
2950
|
|
|
2659
2951
|
page.state = state;
|
|
2952
|
+
page.shallow = {
|
|
2953
|
+
params: intent?.params ?? null,
|
|
2954
|
+
route: intent ? { id: intent.route.id } : null,
|
|
2955
|
+
url
|
|
2956
|
+
};
|
|
2957
|
+
|
|
2958
|
+
if (!nav) return;
|
|
2959
|
+
|
|
2960
|
+
const { activeElement } = document;
|
|
2961
|
+
|
|
2962
|
+
await settled();
|
|
2963
|
+
|
|
2964
|
+
if (navigation_token !== nav_token) {
|
|
2965
|
+
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
2966
|
+
nav.reject(new Error('navigation aborted'));
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
reset_scroll_and_focus(url, reset ? null : scroll_state(), reset, activeElement);
|
|
2971
|
+
|
|
2972
|
+
is_navigating = false;
|
|
2973
|
+
nav.fulfil(undefined);
|
|
2974
|
+
|
|
2975
|
+
if (nav.navigation.to) {
|
|
2976
|
+
nav.navigation.to.scroll = scroll_state();
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
after_navigate_callbacks.forEach((fn) =>
|
|
2980
|
+
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
2981
|
+
);
|
|
2982
|
+
|
|
2983
|
+
navigating.current = null;
|
|
2984
|
+
updating = false;
|
|
2660
2985
|
}
|
|
2661
2986
|
|
|
2662
2987
|
/**
|
|
@@ -2675,21 +3000,19 @@ export async function applyAction(result) {
|
|
|
2675
3000
|
if (result.type === 'error') {
|
|
2676
3001
|
await set_nearest_error_page(result.error);
|
|
2677
3002
|
} else if (result.type === 'redirect') {
|
|
2678
|
-
await _goto(result.location, { refreshAll: true }
|
|
3003
|
+
await _goto(result.location, { refreshAll: true });
|
|
2679
3004
|
} else {
|
|
2680
3005
|
page.form = result.data;
|
|
2681
3006
|
page.status = result.status;
|
|
2682
3007
|
|
|
2683
3008
|
/** @type {Record<string, any>} */
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
form: null
|
|
2688
|
-
});
|
|
3009
|
+
// this brings Svelte's view of the world in line with SvelteKit's
|
|
3010
|
+
// after use:enhance reset the form....
|
|
3011
|
+
props.form = null;
|
|
2689
3012
|
|
|
2690
3013
|
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
2691
|
-
await
|
|
2692
|
-
|
|
3014
|
+
await tick();
|
|
3015
|
+
props.form = result.data;
|
|
2693
3016
|
|
|
2694
3017
|
if (result.type === 'success') {
|
|
2695
3018
|
reset_focus(/** @type {URL} */ (page.url));
|
|
@@ -2719,11 +3042,9 @@ export async function set_nearest_error_page(error) {
|
|
|
2719
3042
|
|
|
2720
3043
|
current = { ...navigation_result.state, nav: current.nav };
|
|
2721
3044
|
|
|
2722
|
-
|
|
2723
|
-
current_tree = navigation_result.props.tree;
|
|
2724
|
-
update(navigation_result.props.page);
|
|
3045
|
+
apply_navigation_result(navigation_result);
|
|
2725
3046
|
|
|
2726
|
-
void
|
|
3047
|
+
void tick().then(() => reset_focus(current.url));
|
|
2727
3048
|
}
|
|
2728
3049
|
}
|
|
2729
3050
|
|
|
@@ -2866,17 +3187,21 @@ function _start_router() {
|
|
|
2866
3187
|
return;
|
|
2867
3188
|
}
|
|
2868
3189
|
// set this flag to distinguish between navigations triggered by
|
|
2869
|
-
// clicking a hash link and those triggered by popstate
|
|
2870
|
-
|
|
3190
|
+
// clicking a hash link and those triggered by popstate. We gotta retrieve
|
|
3191
|
+
// history metadata here because the hashchange event will occur after history.state was updated
|
|
3192
|
+
hash_navigating = {
|
|
3193
|
+
.../** @type {HistoryMetadata} */ (get_history_metadata()),
|
|
3194
|
+
resetIndex: current_reset_index + (options.reset ? 1 : 0)
|
|
3195
|
+
};
|
|
2871
3196
|
|
|
2872
|
-
|
|
3197
|
+
capture_scroll(current_history_index);
|
|
2873
3198
|
|
|
2874
3199
|
update_url(url);
|
|
2875
3200
|
|
|
2876
3201
|
if (!options.replace_state) return;
|
|
2877
3202
|
|
|
2878
3203
|
// hashchange event shouldn't occur if the router is replacing state.
|
|
2879
|
-
hash_navigating =
|
|
3204
|
+
hash_navigating = null;
|
|
2880
3205
|
}
|
|
2881
3206
|
|
|
2882
3207
|
event.preventDefault();
|
|
@@ -2891,12 +3216,13 @@ function _start_router() {
|
|
|
2891
3216
|
setTimeout(fulfil, 100); // fallback for edge case where rAF doesn't fire because e.g. tab was backgrounded
|
|
2892
3217
|
});
|
|
2893
3218
|
|
|
2894
|
-
|
|
3219
|
+
const changed = url.href !== location.href;
|
|
3220
|
+
|
|
3221
|
+
await _goto(url, {
|
|
2895
3222
|
type: 'link',
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
replace_state: options.replace_state ?? url.href === location.href,
|
|
3223
|
+
reset: options.reset,
|
|
3224
|
+
replace: options.replace_state ?? !changed,
|
|
3225
|
+
refreshAll: !changed,
|
|
2900
3226
|
event
|
|
2901
3227
|
});
|
|
2902
3228
|
});
|
|
@@ -2941,8 +3267,7 @@ function _start_router() {
|
|
|
2941
3267
|
void navigate({
|
|
2942
3268
|
type: 'form',
|
|
2943
3269
|
url,
|
|
2944
|
-
|
|
2945
|
-
noscroll: options.noscroll,
|
|
3270
|
+
reset: options.reset,
|
|
2946
3271
|
replace_state: options.replace_state ?? url.href === location.href,
|
|
2947
3272
|
event
|
|
2948
3273
|
});
|
|
@@ -2951,53 +3276,82 @@ function _start_router() {
|
|
|
2951
3276
|
addEventListener('popstate', async (event) => {
|
|
2952
3277
|
if (resetting_focus) return;
|
|
2953
3278
|
|
|
2954
|
-
|
|
2955
|
-
|
|
3279
|
+
const history_metadata = get_history_metadata(event.state);
|
|
3280
|
+
|
|
3281
|
+
if (history_metadata?.historyIndex) {
|
|
3282
|
+
const history_index = history_metadata.historyIndex;
|
|
3283
|
+
const source_info = history_info[current_history_index];
|
|
2956
3284
|
navigation_token = invalidation_token = {};
|
|
2957
3285
|
|
|
2958
3286
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2959
3287
|
// with history.go, which means we end up back here, hence this check
|
|
2960
3288
|
if (history_index === current_history_index) return;
|
|
2961
3289
|
|
|
2962
|
-
const
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
2965
|
-
const
|
|
2966
|
-
const
|
|
3290
|
+
const delta = history_index - current_history_index;
|
|
3291
|
+
const reset_index = history_metadata.resetIndex;
|
|
3292
|
+
const reset = reset_index !== (source_info?.resetIndex ?? current_reset_index);
|
|
3293
|
+
const scroll = history_info[history_index]?.scroll;
|
|
3294
|
+
const state = history_metadata.state;
|
|
3295
|
+
const url = new URL(history_metadata.pageUrl ?? location.href);
|
|
3296
|
+
const navigation_index = history_metadata.navigationIndex;
|
|
3297
|
+
const is_hash_change =
|
|
3298
|
+
current.url && (location.href + current.url.href).includes('#') // check if even has a hash
|
|
3299
|
+
? strip_hash(location) === strip_hash(current.url)
|
|
3300
|
+
: false;
|
|
2967
3301
|
const shallow =
|
|
2968
|
-
navigation_index === current_navigation_index &&
|
|
3302
|
+
navigation_index === current_navigation_index &&
|
|
3303
|
+
((has_navigated &&
|
|
3304
|
+
(history_metadata.pageUrl === undefined || history_metadata.pageUrl === location.href)) ||
|
|
3305
|
+
is_hash_change);
|
|
3306
|
+
const shallow_url = history_metadata.pageUrl ? new URL(location.href) : null;
|
|
3307
|
+
const shallow_intent = shallow_url
|
|
3308
|
+
? await get_navigation_intent(shallow_url, false)
|
|
3309
|
+
: undefined;
|
|
3310
|
+
const shallow_target = shallow_url
|
|
3311
|
+
? {
|
|
3312
|
+
params: shallow_intent?.params ?? null,
|
|
3313
|
+
route: shallow_intent ? { id: shallow_intent.route.id } : null,
|
|
3314
|
+
url: shallow_url
|
|
3315
|
+
}
|
|
3316
|
+
: null;
|
|
2969
3317
|
|
|
2970
3318
|
if (shallow) {
|
|
2971
3319
|
// We don't need to navigate, we just need to update scroll and/or state.
|
|
2972
3320
|
// This happens with hash links and `pushState`/`replaceState`. The
|
|
2973
3321
|
// exception is if we haven't navigated yet, since we could have
|
|
2974
3322
|
// got here after a modal navigation then a reload
|
|
3323
|
+
|
|
3324
|
+
blur_active_element(reset);
|
|
3325
|
+
|
|
2975
3326
|
if (state !== page.state) {
|
|
2976
3327
|
page.state = state;
|
|
2977
3328
|
}
|
|
2978
3329
|
|
|
2979
|
-
|
|
3330
|
+
page.shallow = shallow_target;
|
|
2980
3331
|
|
|
2981
|
-
|
|
2982
|
-
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
3332
|
+
update_url(url);
|
|
2983
3333
|
|
|
3334
|
+
capture_scroll(current_history_index);
|
|
2984
3335
|
current_history_index = history_index;
|
|
3336
|
+
current_reset_index = reset_index;
|
|
3337
|
+
if (reset && scroll) scrollTo(scroll.x, scroll.y);
|
|
2985
3338
|
return;
|
|
2986
3339
|
}
|
|
2987
3340
|
|
|
2988
|
-
const delta = history_index - current_history_index;
|
|
2989
|
-
|
|
2990
3341
|
await navigate({
|
|
2991
3342
|
type: 'popstate',
|
|
2992
3343
|
url,
|
|
3344
|
+
reset,
|
|
2993
3345
|
popped: {
|
|
2994
3346
|
state,
|
|
2995
3347
|
scroll,
|
|
2996
|
-
delta
|
|
3348
|
+
delta,
|
|
3349
|
+
shallow: shallow_target
|
|
2997
3350
|
},
|
|
2998
3351
|
accept: () => {
|
|
2999
3352
|
current_history_index = history_index;
|
|
3000
3353
|
current_navigation_index = navigation_index;
|
|
3354
|
+
current_reset_index = reset_index;
|
|
3001
3355
|
},
|
|
3002
3356
|
block: () => {
|
|
3003
3357
|
history.go(-delta);
|
|
@@ -3026,16 +3380,22 @@ function _start_router() {
|
|
|
3026
3380
|
// if the hashchange happened as a result of clicking on a link,
|
|
3027
3381
|
// we need to update history, otherwise we have to leave it alone
|
|
3028
3382
|
if (hash_navigating) {
|
|
3029
|
-
|
|
3383
|
+
const history_metadata = hash_navigating;
|
|
3384
|
+
hash_navigating = null;
|
|
3385
|
+
current_reset_index = history_metadata.resetIndex;
|
|
3030
3386
|
history.replaceState(
|
|
3031
3387
|
{
|
|
3032
3388
|
...history.state,
|
|
3033
|
-
[
|
|
3034
|
-
|
|
3389
|
+
[HISTORY_METADATA_KEY]: {
|
|
3390
|
+
...history_metadata,
|
|
3391
|
+
historyIndex: ++current_history_index,
|
|
3392
|
+
navigationIndex: current_navigation_index
|
|
3393
|
+
}
|
|
3035
3394
|
},
|
|
3036
3395
|
'',
|
|
3037
3396
|
location.href
|
|
3038
3397
|
);
|
|
3398
|
+
set_history_options(current_history_index, history_metadata);
|
|
3039
3399
|
}
|
|
3040
3400
|
});
|
|
3041
3401
|
|
|
@@ -3093,7 +3453,7 @@ async function _hydrate(
|
|
|
3093
3453
|
} else {
|
|
3094
3454
|
// undefined in case of 404
|
|
3095
3455
|
if (server_route) {
|
|
3096
|
-
parsed_route = route =
|
|
3456
|
+
parsed_route = route = parse_and_cache_server_route(server_route);
|
|
3097
3457
|
} else {
|
|
3098
3458
|
route = { id: null };
|
|
3099
3459
|
params = {};
|
|
@@ -3102,7 +3462,7 @@ async function _hydrate(
|
|
|
3102
3462
|
|
|
3103
3463
|
/** @type {import('./types.js').NavigationFinished | undefined} */
|
|
3104
3464
|
let result;
|
|
3105
|
-
let
|
|
3465
|
+
let should_hydrate = true;
|
|
3106
3466
|
|
|
3107
3467
|
try {
|
|
3108
3468
|
const branch_promises = node_ids.map(async (n, i) => {
|
|
@@ -3168,7 +3528,7 @@ async function _hydrate(
|
|
|
3168
3528
|
});
|
|
3169
3529
|
|
|
3170
3530
|
target.textContent = '';
|
|
3171
|
-
|
|
3531
|
+
should_hydrate = false;
|
|
3172
3532
|
}
|
|
3173
3533
|
|
|
3174
3534
|
// Exit early when we encounter a redirect while loading the root error page.
|
|
@@ -3176,10 +3536,11 @@ async function _hydrate(
|
|
|
3176
3536
|
if (!result) return;
|
|
3177
3537
|
|
|
3178
3538
|
if (result.props.page) {
|
|
3179
|
-
|
|
3539
|
+
const history_metadata = get_history_metadata();
|
|
3540
|
+
result.props.page.state = history_metadata?.persistState ? history_metadata.state : {};
|
|
3180
3541
|
}
|
|
3181
3542
|
|
|
3182
|
-
await initialize(result, target,
|
|
3543
|
+
await initialize(result, target, should_hydrate);
|
|
3183
3544
|
}
|
|
3184
3545
|
|
|
3185
3546
|
/**
|
|
@@ -3206,20 +3567,19 @@ async function load_data(url, invalid) {
|
|
|
3206
3567
|
notify_version(res.headers.get('x-sveltekit-version'));
|
|
3207
3568
|
|
|
3208
3569
|
if (!res.ok) {
|
|
3209
|
-
// error message is a JSON-stringified string which devalue can't handle at the top level
|
|
3210
3570
|
// turn it into a HttpError to not call handleError on the client again (was already handled on the server)
|
|
3211
3571
|
// if `__data.json` doesn't exist or the server has an internal error,
|
|
3212
3572
|
// avoid parsing the HTML error page as a JSON
|
|
3213
|
-
/** @type {
|
|
3214
|
-
let message;
|
|
3573
|
+
/** @type {App.Error} */
|
|
3574
|
+
let error = { status: res.status, message: 'Internal Error' };
|
|
3575
|
+
|
|
3215
3576
|
if (res.headers.get('content-type')?.includes('application/json')) {
|
|
3216
|
-
|
|
3577
|
+
error = { status: res.status, ...(await res.json()) };
|
|
3217
3578
|
} else if (res.status === 404) {
|
|
3218
|
-
message = 'Not Found';
|
|
3219
|
-
} else if (res.status === 500) {
|
|
3220
|
-
message = 'Internal Error';
|
|
3579
|
+
error.message = 'Not Found';
|
|
3221
3580
|
}
|
|
3222
|
-
|
|
3581
|
+
|
|
3582
|
+
throw new HttpError(error);
|
|
3223
3583
|
}
|
|
3224
3584
|
|
|
3225
3585
|
return new Promise((resolve, reject) => {
|
|
@@ -3412,8 +3772,18 @@ function reset_focus(url, scroll = true) {
|
|
|
3412
3772
|
* @param {URL | null} url
|
|
3413
3773
|
* @param {T} type
|
|
3414
3774
|
* @param {{ x: number, y: number } | null} [target_scroll] The scroll position for the target (for popstate navigations)
|
|
3775
|
+
* @param {boolean} [shallow]
|
|
3776
|
+
* @param {{ params: Record<string, string> | null; route: { id: string } | null; url: URL }} [target]
|
|
3415
3777
|
*/
|
|
3416
|
-
function create_navigation(
|
|
3778
|
+
function create_navigation(
|
|
3779
|
+
current,
|
|
3780
|
+
intent,
|
|
3781
|
+
url,
|
|
3782
|
+
type,
|
|
3783
|
+
target_scroll = null,
|
|
3784
|
+
shallow = false,
|
|
3785
|
+
target
|
|
3786
|
+
) {
|
|
3417
3787
|
/** @type {(value: any) => void} */
|
|
3418
3788
|
let fulfil;
|
|
3419
3789
|
|
|
@@ -3437,13 +3807,14 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
|
|
|
3437
3807
|
scroll: scroll_state()
|
|
3438
3808
|
},
|
|
3439
3809
|
to: url && {
|
|
3440
|
-
params: intent?.params ?? null,
|
|
3441
|
-
route: { id: intent?.route?.id ?? null },
|
|
3442
|
-
url,
|
|
3810
|
+
params: target ? target.params : (intent?.params ?? null),
|
|
3811
|
+
route: { id: target ? (target.route?.id ?? null) : (intent?.route?.id ?? null) },
|
|
3812
|
+
url: target?.url ?? url,
|
|
3443
3813
|
scroll: target_scroll
|
|
3444
3814
|
},
|
|
3445
|
-
willUnload: !intent,
|
|
3815
|
+
willUnload: !shallow && !intent,
|
|
3446
3816
|
type,
|
|
3817
|
+
shallow,
|
|
3447
3818
|
complete
|
|
3448
3819
|
});
|
|
3449
3820
|
|
|
@@ -3516,3 +3887,21 @@ if (DEV) {
|
|
|
3516
3887
|
});
|
|
3517
3888
|
}
|
|
3518
3889
|
}
|
|
3890
|
+
|
|
3891
|
+
/**
|
|
3892
|
+
* @param {NavigationFinished} result
|
|
3893
|
+
*/
|
|
3894
|
+
function apply_navigation_result(result) {
|
|
3895
|
+
Object.assign(page, result.props.page);
|
|
3896
|
+
|
|
3897
|
+
props.tree.data = result.props.tree.data;
|
|
3898
|
+
props.tree.child = result.props.tree.child;
|
|
3899
|
+
|
|
3900
|
+
if ('form' in result.props) {
|
|
3901
|
+
props.form = result.props.form;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
if ('error' in result.props) {
|
|
3905
|
+
props.error = result.props.error;
|
|
3906
|
+
}
|
|
3907
|
+
}
|