@sveltejs/kit 3.0.0-next.11 → 3.0.0-next.13

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