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

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 (144) hide show
  1. package/package.json +33 -21
  2. package/src/constants.js +2 -0
  3. package/src/core/adapt/builder.js +32 -49
  4. package/src/core/adapt/index.js +1 -4
  5. package/src/core/config/index.js +137 -71
  6. package/src/core/config/options.js +236 -244
  7. package/src/core/config/types.d.ts +1 -1
  8. package/src/core/env.js +121 -5
  9. package/src/core/generate_manifest/index.js +12 -15
  10. package/src/core/postbuild/analyse.js +7 -19
  11. package/src/core/postbuild/crawl.js +22 -6
  12. package/src/core/postbuild/fallback.js +2 -1
  13. package/src/core/postbuild/prerender.js +114 -48
  14. package/src/core/postbuild/queue.js +2 -1
  15. package/src/core/sync/create_manifest_data/index.js +53 -50
  16. package/src/core/sync/sync.js +0 -2
  17. package/src/core/sync/write_client_manifest.js +8 -15
  18. package/src/core/sync/write_env.js +2 -1
  19. package/src/core/sync/write_non_ambient.js +12 -9
  20. package/src/core/sync/write_server.js +14 -15
  21. package/src/core/sync/write_tsconfig.js +16 -8
  22. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  23. package/src/core/sync/write_types/index.js +28 -24
  24. package/src/core/utils.js +30 -5
  25. package/src/exports/env/index.js +77 -0
  26. package/src/exports/hooks/index.js +3 -9
  27. package/src/exports/hooks/sequence.js +3 -2
  28. package/src/exports/index.js +35 -13
  29. package/src/exports/internal/client.js +5 -0
  30. package/src/exports/internal/env.js +8 -5
  31. package/src/exports/internal/index.js +1 -90
  32. package/src/exports/internal/{event.js → server/event.js} +1 -2
  33. package/src/exports/internal/server/index.js +33 -0
  34. package/src/exports/internal/shared.js +89 -0
  35. package/src/exports/node/index.js +62 -15
  36. package/src/exports/params.js +68 -0
  37. package/src/exports/public.d.ts +363 -189
  38. package/src/exports/url.js +86 -0
  39. package/src/exports/vite/build/build_server.js +53 -59
  40. package/src/exports/vite/build/remote.js +18 -11
  41. package/src/exports/vite/build/utils.js +0 -8
  42. package/src/exports/vite/dev/index.js +92 -50
  43. package/src/exports/vite/index.js +903 -681
  44. package/src/exports/vite/module_ids.js +0 -2
  45. package/src/exports/vite/preview/index.js +32 -24
  46. package/src/exports/vite/static_analysis/index.js +2 -4
  47. package/src/exports/vite/static_analysis/types.d.ts +14 -0
  48. package/src/exports/vite/utils.js +40 -35
  49. package/src/runtime/app/env/internal.js +4 -4
  50. package/src/runtime/app/env/types.d.ts +1 -1
  51. package/src/runtime/app/environment/index.js +3 -3
  52. package/src/runtime/app/forms.js +24 -7
  53. package/src/runtime/app/paths/client.js +4 -10
  54. package/src/runtime/app/paths/internal/client.js +4 -2
  55. package/src/runtime/app/paths/internal/server.js +2 -23
  56. package/src/runtime/app/paths/public.d.ts +0 -28
  57. package/src/runtime/app/paths/server.js +10 -6
  58. package/src/runtime/app/server/index.js +1 -1
  59. package/src/runtime/app/server/remote/command.js +0 -3
  60. package/src/runtime/app/server/remote/form.js +27 -15
  61. package/src/runtime/app/server/remote/prerender.js +29 -36
  62. package/src/runtime/app/server/remote/query.js +101 -99
  63. package/src/runtime/app/server/remote/requested.js +22 -14
  64. package/src/runtime/app/server/remote/shared.js +30 -26
  65. package/src/runtime/app/state/client.js +1 -2
  66. package/src/runtime/app/stores.js +13 -76
  67. package/src/runtime/client/bundle.js +1 -1
  68. package/src/runtime/client/client-entry.js +3 -0
  69. package/src/runtime/client/client.js +411 -318
  70. package/src/runtime/client/entry.js +24 -3
  71. package/src/runtime/client/fetcher.js +3 -2
  72. package/src/runtime/client/ndjson.js +6 -33
  73. package/src/runtime/client/payload.js +17 -0
  74. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  75. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  76. package/src/runtime/client/remote-functions/form.svelte.js +183 -112
  77. package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
  78. package/src/runtime/client/remote-functions/query/index.js +7 -14
  79. package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
  80. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  81. package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
  82. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
  83. package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
  84. package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
  85. package/src/runtime/client/sse.js +32 -0
  86. package/src/runtime/client/state.svelte.js +65 -49
  87. package/src/runtime/client/stream.js +39 -0
  88. package/src/runtime/client/types.d.ts +11 -7
  89. package/src/runtime/client/utils.js +0 -96
  90. package/src/runtime/components/root.svelte +66 -0
  91. package/src/runtime/env/dynamic/private.js +7 -0
  92. package/src/runtime/env/dynamic/public.js +7 -0
  93. package/src/runtime/env/static/private.js +6 -0
  94. package/src/runtime/env/static/public.js +6 -0
  95. package/src/runtime/form-utils.js +100 -22
  96. package/src/runtime/server/cookie.js +69 -52
  97. package/src/runtime/server/csrf.js +65 -0
  98. package/src/runtime/server/data/index.js +15 -19
  99. package/src/runtime/server/env_module.js +0 -5
  100. package/src/runtime/server/errors.js +135 -0
  101. package/src/runtime/server/fetch.js +1 -1
  102. package/src/runtime/server/index.js +22 -12
  103. package/src/runtime/server/internal.js +25 -0
  104. package/src/runtime/server/page/actions.js +43 -27
  105. package/src/runtime/server/page/data_serializer.js +10 -10
  106. package/src/runtime/server/page/index.js +12 -17
  107. package/src/runtime/server/page/load_data.js +2 -2
  108. package/src/runtime/server/page/render.js +114 -193
  109. package/src/runtime/server/page/respond_with_error.js +11 -11
  110. package/src/runtime/server/page/server_routing.js +27 -18
  111. package/src/runtime/server/remote-functions.js +596 -0
  112. package/src/runtime/server/respond.js +100 -68
  113. package/src/runtime/server/sourcemaps.js +183 -0
  114. package/src/runtime/server/utils.js +2 -134
  115. package/src/runtime/shared.js +83 -13
  116. package/src/runtime/telemetry/otel.js +1 -1
  117. package/src/runtime/types.d.ts +8 -0
  118. package/src/types/ambient-private.d.ts +2 -0
  119. package/src/types/ambient.d.ts +10 -5
  120. package/src/types/global-private.d.ts +13 -25
  121. package/src/types/internal.d.ts +95 -80
  122. package/src/types/private.d.ts +41 -1
  123. package/src/utils/error.js +24 -4
  124. package/src/utils/escape.js +9 -25
  125. package/src/utils/features.js +1 -1
  126. package/src/utils/fork.js +7 -2
  127. package/src/utils/import.js +6 -1
  128. package/src/utils/imports.js +83 -0
  129. package/src/utils/mime.js +9 -0
  130. package/src/utils/page_nodes.js +3 -5
  131. package/src/utils/params.js +66 -0
  132. package/src/utils/routing.js +90 -44
  133. package/src/utils/shared-iterator.js +5 -0
  134. package/src/utils/streaming.js +14 -4
  135. package/src/utils/url.js +20 -2
  136. package/src/version.js +1 -1
  137. package/types/index.d.ts +537 -557
  138. package/types/index.d.ts.map +24 -39
  139. package/src/core/sync/write_root.js +0 -148
  140. package/src/exports/internal/server.js +0 -22
  141. package/src/runtime/server/remote.js +0 -457
  142. package/src/runtime/shared-server.js +0 -7
  143. package/src/types/synthetic/$lib.md +0 -5
  144. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -1,20 +1,13 @@
1
- /** @import { ServerNodesResponse, ServerRedirectNode } from 'types' */
1
+ /** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
2
+ /** @import { NavigationIntent } from './types.js' */
3
+ /** @import { RenderNode } from '../types.js' */
2
4
  /** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
3
5
  /** @import { Query } from './remote-functions/query/instance.svelte.js' */
4
6
  /** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
5
7
  import { BROWSER, DEV } from 'esm-env';
6
8
  import * as svelte from 'svelte';
7
9
  import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
8
- const { onMount, tick } = svelte;
9
- // Svelte 4 and under don't have `untrack`, so we have to fallback if `untrack` is not exported
10
- const untrack = svelte.untrack ?? ((value) => value());
11
- import {
12
- decode_params,
13
- decode_pathname,
14
- strip_hash,
15
- make_trackable,
16
- normalize_path
17
- } from '../../utils/url.js';
10
+ import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
18
11
  import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
19
12
  import { parse, parse_server_route } from './parse.js';
20
13
  import * as storage from './session-storage.js';
@@ -26,11 +19,9 @@ import {
26
19
  is_external_url,
27
20
  origin,
28
21
  scroll_state,
29
- notifiable_store,
30
- create_updated_store,
31
22
  load_css
32
23
  } from './utils.js';
33
- import { base } from '$app/paths';
24
+ import { base } from '$app/paths/internal/client';
34
25
  import * as devalue from 'devalue';
35
26
  import {
36
27
  HISTORY_INDEX,
@@ -43,7 +34,6 @@ import {
43
34
  } from './constants.js';
44
35
  import { validate_page_exports } from '../../utils/exports.js';
45
36
  import { noop } from '../../utils/functions.js';
46
- import { compact } from '../../utils/array.js';
47
37
  import {
48
38
  INVALIDATED_PARAM,
49
39
  TRAILING_SLASH_PARAM,
@@ -52,11 +42,15 @@ import {
52
42
  validate_load_response
53
43
  } from '../shared.js';
54
44
  import { get_message, get_status } from '../../utils/error.js';
55
- import { writable } from 'svelte/store';
56
- import { page, update, navigating } from './state.svelte.js';
45
+ import { page, update, navigating, updated } from './state.svelte.js';
46
+ import { payload } from './payload.js';
57
47
  import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
58
48
  import { noop_span } from '../telemetry/noop.js';
59
49
  import { read_ndjson } from './ndjson.js';
50
+ import RootModern from '../components/root.svelte';
51
+ import { asClassComponent } from 'svelte/legacy';
52
+
53
+ const Root = asClassComponent(RootModern);
60
54
 
61
55
  export { load_css };
62
56
  const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
@@ -71,6 +65,17 @@ let errored = false;
71
65
  */
72
66
  let rendering_error = null;
73
67
 
68
+ /**
69
+ * `reset` functions for `<svelte:boundary>`s in the generated root that have
70
+ * failed. A failed boundary stays failed until `reset()` is called — prop
71
+ * updates alone don't re-render its content — so without resetting, a client
72
+ * navigation away from a render error would leave the stale `+error.svelte`
73
+ * mounted. The boundary's `onerror` populates this array; `navigate` drains it
74
+ * after applying the new props. See sveltejs/kit#15694.
75
+ * @type {Array<(() => void) | undefined>}
76
+ */
77
+ const resetters = [];
78
+
74
79
  // We track the scroll position associated with each history entry in sessionStorage,
75
80
  // rather than on history.state itself, because when navigation is driven by
76
81
  // popstate it's too late to update the scroll position associated with the
@@ -87,11 +92,14 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
87
92
  */
88
93
  const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
89
94
 
95
+ /**
96
+ * @deprecated this is a temporary measure to avoid a regression, replace with nested `RenderNode` classes
97
+ */
98
+ let current_tree = /** @type {RenderNode} */ ({});
99
+
90
100
  if (DEV && BROWSER) {
91
101
  let warned = false;
92
102
 
93
- 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
94
-
95
103
  const warn = () => {
96
104
  if (warned) return;
97
105
 
@@ -100,9 +108,13 @@ if (DEV && BROWSER) {
100
108
  let stack = new Error().stack?.split('\n');
101
109
  if (!stack) return;
102
110
  if (!stack[0].includes('https:') && !stack[0].includes('http:')) stack = stack.slice(1); // Chrome includes the error message in the stack
103
- stack = stack.slice(2); // remove `warn` and the place where `warn` was called
104
- // Can be falsy if was called directly from an anonymous function
105
- if (stack[0]?.includes(current_module_url)) return;
111
+
112
+ // skip over `warn` and the place where `warn` was called
113
+ const frame = stack[2];
114
+
115
+ // ignore calls that happen inside dependencies, including SvelteKit.
116
+ // `frame` can be falsy if we came from an anonymous function
117
+ if (frame?.includes('node_modules')) return;
106
118
 
107
119
  warned = true;
108
120
 
@@ -124,15 +136,6 @@ if (DEV && BROWSER) {
124
136
  };
125
137
  }
126
138
 
127
- export const stores = {
128
- url: /* @__PURE__ */ notifiable_store({}),
129
- page: /* @__PURE__ */ notifiable_store({}),
130
- navigating: /* @__PURE__ */ writable(
131
- /** @type {import('@sveltejs/kit').Navigation | null} */ (null)
132
- ),
133
- updated: /* @__PURE__ */ create_updated_store()
134
- };
135
-
136
139
  /** @param {number} index */
137
140
  function update_scroll_positions(index) {
138
141
  scroll_positions[index] = scroll_state();
@@ -163,7 +166,8 @@ function clear_onward_history(current_history_index, current_navigation_index) {
163
166
  * Returns a `Promise` that never resolves (to prevent any
164
167
  * subsequent work, e.g. history manipulation, from happening)
165
168
  * @param {URL} url
166
- * @param {boolean} [replace] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
169
+ * @param {boolean} [replace] if `true`, will replace the current `history` entry rather than creating a new one with `pushState`
170
+ * @returns {Promise<any>} a promise that never resolves
167
171
  */
168
172
  function native_navigation(url, replace = false) {
169
173
  if (replace) {
@@ -187,7 +191,7 @@ async function update_service_worker() {
187
191
  }
188
192
  }
189
193
 
190
- /** @type {import('types').CSRRoute[]} All routes of the app. Only available when kit.router.resolution=client */
194
+ /** @type {import('types').CSRRoute[]} All routes of the app. Only available when router.resolution=client */
191
195
  let routes;
192
196
  /** @type {import('types').CSRPageNodeLoader} */
193
197
  let default_layout_loader;
@@ -202,18 +206,20 @@ let target;
202
206
  export let app;
203
207
 
204
208
  /**
205
- * Data that was serialized during SSR for queries/forms/commands.
206
- * This is cleared before client-side loads run.
207
- * @type {Record<string, any>}
209
+ * Data that was serialized during SSR for queries/forms/commands, stored as
210
+ * `{ v }` (value) or `{ e }` (error) nodes so that failed states survive hydration.
211
+ * Entries are deleted as they are consumed (when the corresponding resource is created).
212
+ * @type {Record<string, RemoteFunctionDataNode>}
208
213
  */
209
- export let query_responses = {};
214
+ export const query_responses = {};
210
215
 
211
216
  /**
212
- * Data that was serialized during SSR for prerender functions.
217
+ * Data that was serialized during SSR for prerender functions, stored as
218
+ * `{ v }` (value) or `{ e }` (error) nodes.
213
219
  * This persists across client-side navigations.
214
- * @type {Record<string, any>}
220
+ * @type {Record<string, RemoteFunctionDataNode>}
215
221
  */
216
- export let prerender_responses = {};
222
+ export const prerender_responses = {};
217
223
 
218
224
  /** @type {Array<((url: URL) => boolean)>} */
219
225
  const invalidated = [];
@@ -290,8 +296,17 @@ let current_history_index;
290
296
  /** @type {number} */
291
297
  let current_navigation_index;
292
298
 
293
- /** @type {{}} */
294
- let token;
299
+ /** @type {{}} Token for the latest navigation. Updated on new navigations */
300
+ let navigation_token;
301
+
302
+ /**
303
+ * @type {{}}
304
+ * The latest invalidate(All) token. Superseeded by both later invalidate(All)s and navigations.
305
+ * This is separate to navigation_token because an invalidate(All) might be triggered while a navigation
306
+ * is in progress, and we want to be able to finish this navigation (unless the invalidation finishes before
307
+ * it and redirects, in which case we will do the redirect triggered by the invalidation).
308
+ */
309
+ let invalidation_token;
295
310
 
296
311
  /**
297
312
  * A set of tokens which are associated to current preloads.
@@ -299,6 +314,7 @@ let token;
299
314
  * If a preload token is in the set and the preload errors, the error
300
315
  * handling logic (for example reloading) is skipped.
301
316
  */
317
+ /** @type {Set<{}>} */
302
318
  const preload_tokens = new Set();
303
319
 
304
320
  /** @type {Promise<void> | null} */
@@ -328,9 +344,15 @@ export async function start(_app, _target, hydrate) {
328
344
  );
329
345
  }
330
346
 
331
- if (__SVELTEKIT_PAYLOAD__) {
332
- query_responses = __SVELTEKIT_PAYLOAD__.query ?? {};
333
- prerender_responses = __SVELTEKIT_PAYLOAD__.prerender ?? {};
347
+ if (payload.data) {
348
+ const { q = {}, p = {}, l = {}, f = {} } = payload.data;
349
+
350
+ // store the whole nodes — error records seed the corresponding
351
+ // resources in a failed state when they are created during hydration
352
+ for (const k in q) query_responses[k] = q[k];
353
+ for (const k in l) query_responses[k] = l[k];
354
+ for (const k in f) query_responses[k] = f[k];
355
+ for (const k in p) prerender_responses[k] = p[k];
334
356
  }
335
357
 
336
358
  // detect basic auth credentials in the current URL
@@ -402,15 +424,17 @@ export async function start(_app, _target, hydrate) {
402
424
  _start_router();
403
425
  }
404
426
 
405
- async function _invalidate(include_load_functions = true, reset_page_state = true) {
427
+ async function _invalidate(reset_page_state = true) {
406
428
  // Accept all invalidations as they come, don't swallow any while another invalidation
407
429
  // is running because subsequent invalidations may make earlier ones outdated,
408
430
  // but batch multiple synchronous invalidations.
409
431
  await (pending_invalidate ||= Promise.resolve());
410
- if (!pending_invalidate) return;
432
+ if (pending_invalidate === null) return;
411
433
  pending_invalidate = null;
412
434
 
413
- const nav_token = (token = {});
435
+ const token = (invalidation_token = {});
436
+ const nav_token = navigation_token;
437
+ const navigating = is_navigating;
414
438
  const intent = await get_navigation_intent(current.url, true);
415
439
 
416
440
  // Clear preload, it might be affected by the invalidation.
@@ -439,32 +463,37 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
439
463
  }
440
464
  }
441
465
 
442
- if (include_load_functions) {
443
- const prev_state = page.state;
444
- const navigation_result = intent && (await load_route(intent));
445
- if (!navigation_result || nav_token !== token) return;
466
+ const prev_state = page.state;
467
+ const navigation_result = intent && (await load_route(intent));
468
+ if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
469
+ return;
470
+ }
446
471
 
447
- if (navigation_result.type === 'redirect') {
448
- return _goto(
449
- new URL(navigation_result.location, current.url).href,
450
- { replaceState: true },
451
- 1,
452
- nav_token
453
- );
454
- }
472
+ if (navigation_result.type === 'redirect') {
473
+ return _goto(
474
+ new URL(navigation_result.location, current.url).href,
475
+ { replaceState: true },
476
+ 1,
477
+ token
478
+ );
479
+ }
455
480
 
456
- // This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
457
- if (!reset_page_state) {
458
- navigation_result.props.page.state = prev_state;
459
- }
460
- update(navigation_result.props.page);
461
- current = { ...navigation_result.state, nav: current.nav };
462
- reset_invalidation();
463
- root.$set(navigation_result.props);
464
- } else {
465
- reset_invalidation();
481
+ // A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
482
+ // hence it likely contains outdated data now, so we ignore it.
483
+ if (navigating && !is_navigating) {
484
+ return;
466
485
  }
467
486
 
487
+ // Preserve `page.state` when invalidating without resetting it (e.g. `refresh`/`refreshAll`)
488
+ if (!reset_page_state) {
489
+ navigation_result.props.page.state = prev_state;
490
+ }
491
+ update(navigation_result.props.page);
492
+ current_tree = navigation_result.props.tree;
493
+ current = { ...navigation_result.state, nav: current.nav };
494
+ reset_invalidation();
495
+ root.$set(navigation_result.props);
496
+
468
497
  // only wait for promises that are connected to queries that still exist
469
498
  /** @type {Promise<any>[]} */
470
499
  const promises = [];
@@ -516,19 +545,21 @@ function persist_state() {
516
545
 
517
546
  /**
518
547
  * @param {string | URL} url
519
- * @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
548
+ * @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
520
549
  * @param {number} redirect_count
521
550
  * @param {{}} [nav_token]
551
+ * @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
552
+ * @returns {Promise<void>}
522
553
  */
523
- export async function _goto(url, options, redirect_count, nav_token) {
554
+ export async function _goto(url, options, redirect_count, nav_token, intent) {
524
555
  /** @type {Set<string>} */
525
556
  let query_keys;
526
557
  /** @type {Set<string>} */
527
558
  let live_query_keys;
528
559
 
529
- // Clear preload cache when invalidateAll is true to ensure fresh data
560
+ // Clear preload cache when refreshAll is true to ensure fresh data
530
561
  // after form submissions or explicit invalidations
531
- if (options.invalidateAll) {
562
+ if (options.refreshAll) {
532
563
  discard_load_cache();
533
564
  }
534
565
 
@@ -541,12 +572,17 @@ export async function _goto(url, options, redirect_count, nav_token) {
541
572
  state: options.state,
542
573
  redirect_count,
543
574
  nav_token,
575
+ intent,
544
576
  accept: () => {
545
- if (options.invalidateAll) {
577
+ if (options.refreshAll) {
546
578
  force_invalidation = true;
547
579
  query_keys = new Set();
548
580
  for (const [id, entries] of query_map) {
549
- for (const payload of entries.keys()) {
581
+ for (const [payload, entry] of entries) {
582
+ // don't refresh yet, as some queries will be unrendered,
583
+ // but clear caches so that newly rendered queries
584
+ // don't use stale data. TODO same for `live_query_map`
585
+ entry.resource?.reset();
550
586
  query_keys.add(create_remote_key(id, payload));
551
587
  }
552
588
  }
@@ -564,7 +600,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
564
600
  }
565
601
  });
566
602
 
567
- if (options.invalidateAll) {
603
+ if (options.refreshAll) {
568
604
  // TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
569
605
  // when a query in a layout that still exists after page change is refreshed earlier than this
570
606
  void svelte
@@ -574,7 +610,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
574
610
  for (const [id, entries] of query_map) {
575
611
  for (const [payload, { resource }] of entries) {
576
612
  if (query_keys?.has(create_remote_key(id, payload))) {
577
- void resource.refresh();
613
+ void resource.start();
578
614
  }
579
615
  }
580
616
  }
@@ -603,17 +639,14 @@ async function _preload_data(intent) {
603
639
  load_cache = {
604
640
  id: intent.id,
605
641
  token: preload,
606
- promise: load_route({ ...intent, preload }).then((result) => {
642
+ promise: load_route({ ...intent, preload }).finally(() => {
607
643
  preload_tokens.delete(preload);
608
- if (result.type === 'loaded' && result.state.error) {
609
- // Don't cache errors, because they might be transient
610
- discard_load_cache();
611
- }
612
- return result;
613
644
  }),
614
645
  fork: null
615
646
  };
616
647
 
648
+ load_cache.promise.catch(discard_load_cache);
649
+
617
650
  if (__SVELTEKIT_FORK_PRELOADS__ && svelte.fork) {
618
651
  const lc = load_cache;
619
652
 
@@ -625,6 +658,7 @@ async function _preload_data(intent) {
625
658
  return svelte.fork(() => {
626
659
  root.$set(result.props);
627
660
  update(result.props.page);
661
+ current_tree = result.props.tree;
628
662
  });
629
663
  } catch {
630
664
  // if it errors, it's because the experimental flag isn't enabled in Svelte
@@ -683,31 +717,28 @@ async function initialize(result, target, hydrate) {
683
717
  }
684
718
 
685
719
  update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
720
+ current_tree = result.props.tree;
686
721
 
687
- root = new app.root({
722
+ // TODO: use mount()
723
+ root = new Root({
688
724
  target,
689
- props: { ...result.props, stores, components },
725
+ props: { ...result.props, components, resetters },
690
726
  hydrate,
691
- // @ts-ignore Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
727
+ // Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
692
728
  sync: false,
693
- // @ts-ignore Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
694
- transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
695
- ? /** @param {unknown} e */ async (e) => {
696
- const error = await handle_error(e, current.nav);
697
- rendering_error = { error, status: get_status(e) };
698
- page.error = error;
699
- page.status = rendering_error.status;
700
- return error;
701
- }
702
- : undefined
729
+ transformError: /** @param {unknown} e */ async (e) => {
730
+ const error = await handle_error(e, current.nav);
731
+ rendering_error = { error, status: error.status };
732
+ page.error = error;
733
+ page.status = rendering_error.status;
734
+ return error;
735
+ }
703
736
  });
704
737
 
705
738
  // Wait for a microtask in case svelte experimental async is enabled,
706
739
  // which causes component script blocks to run asynchronously
707
740
  void (await Promise.resolve());
708
741
 
709
- restore_snapshot(current_navigation_index);
710
-
711
742
  if (hydrate) {
712
743
  /** @type {import('@sveltejs/kit').AfterNavigate} */
713
744
  const navigation = {
@@ -724,6 +755,8 @@ async function initialize(result, target, hydrate) {
724
755
  after_navigate_callbacks.forEach((fn) => fn(navigation));
725
756
  }
726
757
 
758
+ restore_snapshot(current_navigation_index);
759
+
727
760
  started = true;
728
761
  }
729
762
 
@@ -734,7 +767,7 @@ async function initialize(result, target, hydrate) {
734
767
  * params: Record<string, string>;
735
768
  * branch: Array<import('./types.js').BranchNode | undefined>;
736
769
  * errors?: Array<import('types').CSRPageNodeLoader | undefined>;
737
- * status: number;
770
+ * status?: number;
738
771
  * error: App.Error | null;
739
772
  * route: import('types').CSRRoute | null;
740
773
  * form?: Record<string, any> | null;
@@ -778,35 +811,12 @@ async function get_navigation_result_from_branch({
778
811
  route
779
812
  },
780
813
  props: {
781
- // @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
782
- constructors: compact(branch).map((branch_node) => branch_node.node.component),
783
- page: clone_page(page)
814
+ page,
815
+ tree: /** @type {RenderNode} */ ({})
784
816
  }
785
817
  };
786
818
 
787
- if (errors && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
788
- let last_idx = -1;
789
- result.props.errors = await Promise.all(
790
- // eslint-disable-next-line @typescript-eslint/await-thenable
791
- branch
792
- .map((b, i) => {
793
- if (i === 0) return undefined; // root layout wraps root error component, not the other way around
794
- if (!b) return null;
795
-
796
- i--;
797
- // Find the closest error component up to the previous branch
798
- while (i > last_idx + 1 && !errors[i]) i -= 1;
799
- last_idx = i;
800
- return errors[i]?.()
801
- .then((e) => e.component)
802
- .catch(() => undefined);
803
- })
804
- // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
805
- .filter((e) => e !== null)
806
- );
807
- }
808
-
809
- if (error && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
819
+ if (error) {
810
820
  result.props.error = error;
811
821
  }
812
822
 
@@ -817,23 +827,44 @@ async function get_navigation_result_from_branch({
817
827
  let data = {};
818
828
  let data_changed = !page;
819
829
 
820
- let p = 0;
830
+ let current_node = result.props.tree;
831
+
832
+ /** @type {RenderNode | undefined} */
833
+ let previous_node = current_tree;
821
834
 
822
- for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) {
835
+ for (let i = 0; i < branch.length; i += 1) {
823
836
  const node = branch[i];
824
837
  const prev = current.branch[i];
825
838
 
826
- if (node?.data !== prev?.data) data_changed = true;
827
839
  if (!node) continue;
828
840
 
829
- data = { ...data, ...node.data };
841
+ const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader;
830
842
 
831
- // Only set props if the node actually updated. This prevents needless rerenders.
832
- if (data_changed) {
833
- result.props[`data_${p}`] = data;
843
+ current_node.error = (await error_loader()).component;
844
+ current_node.component = node.node.component;
845
+
846
+ if (
847
+ // if an ancestor node in this path changed, `data_changed` is already true and the
848
+ // accumulated `data` differs from the previous render, so we must re-merge
849
+ data_changed ||
850
+ !previous_node ||
851
+ node?.data !== prev?.data
852
+ ) {
853
+ current_node.data = { ...data, ...node.data };
854
+ data_changed = true;
855
+ } else {
856
+ // use existing object — prevents effects re-running unnecessarily
857
+ current_node.data = previous_node.data;
834
858
  }
835
859
 
836
- p += 1;
860
+ data = current_node.data;
861
+
862
+ if (i < branch.length - 1) {
863
+ current_node.child = /** @type {import('../types.js').RenderNode} */ ({});
864
+ current_node = current_node.child;
865
+
866
+ previous_node = previous_node?.child;
867
+ }
837
868
  }
838
869
 
839
870
  const page_changed =
@@ -851,11 +882,10 @@ async function get_navigation_result_from_branch({
851
882
  id: route?.id ?? null
852
883
  },
853
884
  state: {},
854
- status,
885
+ status: status ?? error?.status ?? 200,
855
886
  url: new URL(url),
856
887
  form: form ?? null,
857
- // The whole page store is updated, but this way the object reference stays the same
858
- data: data_changed ? data : page.data
888
+ data
859
889
  };
860
890
  }
861
891
 
@@ -1134,30 +1164,19 @@ function diff_search_params(old_url, new_url) {
1134
1164
  }
1135
1165
 
1136
1166
  /**
1137
- * @param {Omit<import('./types.js').NavigationFinished['state'], 'branch'> & { error: App.Error }} opts
1138
- * @returns {import('./types.js').NavigationFinished}
1167
+ * @overload
1168
+ * @param {import('./types.js').NavigationIntent} intent
1169
+ * @returns {Promise<import('./types.js').NavigationResult | undefined>}
1139
1170
  */
1140
- function preload_error({ error, url, route, params }) {
1141
- return {
1142
- type: 'loaded',
1143
- state: {
1144
- error,
1145
- url,
1146
- route,
1147
- params,
1148
- branch: []
1149
- },
1150
- props: {
1151
- page: clone_page(page),
1152
- constructors: []
1153
- }
1154
- };
1155
- }
1156
-
1157
1171
  /**
1158
- * @param {import('./types.js').NavigationIntent & { preload?: {} }} intent
1172
+ * @overload
1173
+ * @param {import('./types.js').NavigationIntent & { preload: {} }} intent
1159
1174
  * @returns {Promise<import('./types.js').NavigationResult>}
1160
1175
  */
1176
+ /**
1177
+ * @param {import('./types.js').NavigationIntent & { preload?: {} }} intent
1178
+ * @returns {Promise<import('./types.js').NavigationResult | undefined>}
1179
+ */
1161
1180
  async function load_route({ id, invalidating, url, params, route, preload }) {
1162
1181
  if (load_cache?.id === id) {
1163
1182
  // the preload becomes the real navigation
@@ -1213,12 +1232,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1213
1232
  } catch (error) {
1214
1233
  const handled_error = await handle_error(error, { url, params, route: { id } });
1215
1234
 
1216
- if (preload_tokens.has(preload)) {
1217
- return preload_error({ error: handled_error, url, params, route });
1235
+ if (preload && preload_tokens.has(preload)) {
1236
+ throw handled_error;
1218
1237
  }
1219
1238
 
1220
1239
  return load_root_error_page({
1221
- status: get_status(error),
1222
1240
  error: handled_error,
1223
1241
  url,
1224
1242
  route
@@ -1261,7 +1279,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1261
1279
 
1262
1280
  if (server_data_node?.type === 'error') {
1263
1281
  // rethrow and catch below
1264
- throw server_data_node;
1282
+ throw new HttpError(server_data_node.error.status, server_data_node.error);
1265
1283
  }
1266
1284
 
1267
1285
  return load_node({
@@ -1299,34 +1317,27 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1299
1317
  if (err instanceof Redirect) {
1300
1318
  return {
1301
1319
  type: 'redirect',
1320
+ status: err.status,
1302
1321
  location: err.location
1303
1322
  };
1304
1323
  }
1305
1324
 
1306
- if (preload_tokens.has(preload)) {
1307
- return preload_error({
1308
- error: await handle_error(err, { params, url, route: { id: route.id } }),
1309
- url,
1310
- params,
1311
- route
1312
- });
1325
+ if (preload && preload_tokens.has(preload)) {
1326
+ throw await handle_error(err, { params, url, route: { id: route.id } });
1313
1327
  }
1314
1328
 
1315
- let status = get_status(err);
1316
1329
  /** @type {App.Error} */
1317
1330
  let error;
1318
1331
 
1319
1332
  if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
1320
1333
  // this is the server error rethrown above, reconstruct but don't invoke
1321
1334
  // the client error handler; it should've already been handled on the server
1322
- status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
1323
1335
  error = /** @type {import('types').ServerErrorNode} */ (err).error;
1324
1336
  } else if (err instanceof HttpError) {
1325
1337
  error = err.body;
1326
1338
  } else {
1327
1339
  // Referenced node could have been removed due to redeploy, check
1328
- const updated = await stores.updated.check();
1329
- if (updated) {
1340
+ if (await updated.check()) {
1330
1341
  // Before reloading, try to update the service worker if it exists
1331
1342
  await update_service_worker();
1332
1343
  return await native_navigation(url);
@@ -1342,12 +1353,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1342
1353
  params,
1343
1354
  branch: branch.slice(0, error_load.idx).concat(error_load.node),
1344
1355
  errors,
1345
- status,
1346
1356
  error,
1347
1357
  route
1348
1358
  });
1349
1359
  } else {
1350
- return await server_fallback(url, { id: route.id }, error, status);
1360
+ return await server_fallback(url, { id: route.id }, error);
1351
1361
  }
1352
1362
  }
1353
1363
  } else {
@@ -1362,7 +1372,6 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1362
1372
  params,
1363
1373
  branch,
1364
1374
  errors,
1365
- status: 200,
1366
1375
  error: null,
1367
1376
  route,
1368
1377
  // Reset `form` on navigation, but not invalidation
@@ -1401,14 +1410,13 @@ async function load_nearest_error_page(i, branch, errors) {
1401
1410
 
1402
1411
  /**
1403
1412
  * @param {{
1404
- * status: number;
1405
1413
  * error: App.Error;
1406
1414
  * url: URL;
1407
1415
  * route: { id: string | null }
1408
1416
  * }} opts
1409
- * @returns {Promise<import('./types.js').NavigationFinished>}
1417
+ * @returns {Promise<import('./types.js').NavigationFinished | undefined>} returns `undefined` in case of a redirect
1410
1418
  */
1411
- async function load_root_error_page({ status, error, url, route }) {
1419
+ async function load_root_error_page({ error, url, route }) {
1412
1420
  /** @type {Record<string, string>} */
1413
1421
  const params = {}; // error page does not have params
1414
1422
 
@@ -1432,11 +1440,15 @@ async function load_root_error_page({ status, error, url, route }) {
1432
1440
  }
1433
1441
 
1434
1442
  server_data_node = server_data.nodes[0] ?? null;
1435
- } catch {
1443
+ } catch (e) {
1436
1444
  // at this point we have no choice but to fall back to the server, if it wouldn't
1437
- // bring us right back here, turning this into an endless loop
1438
- if (url.origin !== origin || url.pathname !== location.pathname || hydrated) {
1439
- await native_navigation(url);
1445
+ // bring us right back here, turning this into an endless loop.
1446
+ // if __data.json returned 404, the route doesn't exist don't reload or we loop
1447
+ if (
1448
+ !(e instanceof HttpError && e.status === 404) &&
1449
+ (url.origin !== origin || url.pathname !== location.pathname || hydrated)
1450
+ ) {
1451
+ return await native_navigation(url);
1440
1452
  }
1441
1453
  }
1442
1454
  }
@@ -1465,18 +1477,30 @@ async function load_root_error_page({ status, error, url, route }) {
1465
1477
  url,
1466
1478
  params,
1467
1479
  branch: [root_layout, root_error],
1468
- status,
1469
1480
  error,
1470
1481
  errors: [],
1471
1482
  route: null
1472
1483
  });
1473
1484
  } catch (error) {
1485
+ // client-side navigation if the root layout loader throws a redirect while
1486
+ // rendering the default error page
1474
1487
  if (error instanceof Redirect) {
1475
- // @ts-expect-error TODO investigate this
1476
- return _goto(new URL(error.location, location.href), {}, 0);
1488
+ await _goto(new URL(error.location, location.href), {}, 0);
1489
+ return;
1477
1490
  }
1478
1491
 
1479
- // TODO: this falls back to the server when a server exists, but what about SPA mode?
1492
+ // otherwise, render the static error page
1493
+ const error_template = await app.get_error_template();
1494
+ const handled = await handle_error(error, { url, params, route });
1495
+ const message = String(handled?.message ?? '')
1496
+ .replace(/&/g, '&amp;')
1497
+ .replace(/</g, '&lt;')
1498
+ .replace(/>/g, '&gt;');
1499
+ const html = error_template({ status: handled.status, message });
1500
+ const parsed = new DOMParser().parseFromString(html, 'text/html');
1501
+ document.documentElement.replaceChild(document.adoptNode(parsed.head), document.head);
1502
+ document.documentElement.replaceChild(document.adoptNode(parsed.body), document.body);
1503
+
1480
1504
  throw error;
1481
1505
  }
1482
1506
  }
@@ -1566,7 +1590,7 @@ export async function get_navigation_intent(url, invalidating) {
1566
1590
  id: get_page_key(url),
1567
1591
  invalidating,
1568
1592
  route,
1569
- params: decode_params(params),
1593
+ params,
1570
1594
  url
1571
1595
  };
1572
1596
  }
@@ -1661,8 +1685,10 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
1661
1685
  * nav_token?: {};
1662
1686
  * accept?: () => void;
1663
1687
  * block?: () => void;
1664
- * event?: Event
1688
+ * event?: Event;
1689
+ * intent?: NavigationIntent | undefined
1665
1690
  * }} opts
1691
+ * @returns {Promise<void>}
1666
1692
  */
1667
1693
  async function navigate({
1668
1694
  type,
@@ -1676,12 +1702,14 @@ async function navigate({
1676
1702
  nav_token = {},
1677
1703
  accept = noop,
1678
1704
  block = noop,
1679
- event
1705
+ event,
1706
+ intent
1680
1707
  }) {
1681
- const prev_token = token;
1682
- token = nav_token;
1708
+ const prev_token = navigation_token;
1709
+ const prev_invalidation_token = invalidation_token;
1710
+ navigation_token = invalidation_token = nav_token;
1683
1711
 
1684
- const intent = await get_navigation_intent(url, false);
1712
+ intent ??= await get_navigation_intent(url, false);
1685
1713
  const nav =
1686
1714
  type === 'enter'
1687
1715
  ? create_navigation(current, intent, url, type)
@@ -1697,7 +1725,8 @@ async function navigate({
1697
1725
 
1698
1726
  if (!nav) {
1699
1727
  block();
1700
- if (token === nav_token) token = prev_token;
1728
+ if (navigation_token === nav_token) navigation_token = prev_token;
1729
+ if (invalidation_token === nav_token) invalidation_token = prev_invalidation_token;
1701
1730
  return;
1702
1731
  }
1703
1732
 
@@ -1710,7 +1739,7 @@ async function navigate({
1710
1739
  is_navigating = true;
1711
1740
 
1712
1741
  if (started && nav.navigation.type !== 'enter') {
1713
- stores.navigating.set((navigating.current = nav.navigation));
1742
+ navigating.current = nav.navigation;
1714
1743
  }
1715
1744
 
1716
1745
  let navigation_result = intent && (await load_route(intent));
@@ -1735,7 +1764,6 @@ async function navigate({
1735
1764
  route: { id: null }
1736
1765
  }
1737
1766
  ),
1738
- 404,
1739
1767
  replace_state
1740
1768
  );
1741
1769
  } else {
@@ -1750,7 +1778,6 @@ async function navigate({
1750
1778
  params: {},
1751
1779
  route: { id: null }
1752
1780
  }),
1753
- 404,
1754
1781
  replace_state
1755
1782
  );
1756
1783
  }
@@ -1761,11 +1788,13 @@ async function navigate({
1761
1788
  url = intent?.url || url;
1762
1789
 
1763
1790
  // abort if user navigated during update
1764
- if (token !== nav_token) {
1791
+ if (navigation_token !== nav_token) {
1765
1792
  nav.reject(new Error('navigation aborted'));
1766
- return false;
1793
+ return;
1767
1794
  }
1768
1795
 
1796
+ if (!navigation_result) return;
1797
+
1769
1798
  if (navigation_result.type === 'redirect') {
1770
1799
  // whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
1771
1800
  if (redirect_count < 20) {
@@ -1786,7 +1815,6 @@ async function navigate({
1786
1815
  }
1787
1816
 
1788
1817
  navigation_result = await load_root_error_page({
1789
- status: 500,
1790
1818
  error: await handle_error(new Error('Redirect loop'), {
1791
1819
  url,
1792
1820
  params: {},
@@ -1795,12 +1823,13 @@ async function navigate({
1795
1823
  url,
1796
1824
  route: { id: null }
1797
1825
  });
1826
+
1827
+ if (!navigation_result) return;
1798
1828
  } else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
1799
- const updated = await stores.updated.check();
1800
- if (updated) {
1829
+ if (await updated.check()) {
1801
1830
  // Before reloading, try to update the service worker if it exists
1802
1831
  await update_service_worker();
1803
- await native_navigation(url, replace_state);
1832
+ return await native_navigation(url, replace_state);
1804
1833
  }
1805
1834
  }
1806
1835
 
@@ -1895,13 +1924,38 @@ async function navigate({
1895
1924
  navigation_result.props.page.url = url;
1896
1925
  }
1897
1926
 
1927
+ // Remove focus before updating the component tree, so that blur/focusout
1928
+ // 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
+ }
1936
+
1898
1937
  const fork = load_cache_fork && (await load_cache_fork);
1899
1938
 
1900
1939
  if (fork) {
1901
1940
  commit_promise = fork.commit();
1941
+ // `fork.commit()` applies the preloaded state synchronously before the
1942
+ // first `await`, so reset any previously-failed boundaries now so the
1943
+ // stale `+error.svelte` is torn down. See sveltejs/kit#15694.
1944
+ for (const reset of resetters) {
1945
+ reset?.();
1946
+ }
1947
+ resetters.length = 0;
1902
1948
  } else {
1903
1949
  rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
1904
1950
  root.$set(navigation_result.props);
1951
+ current_tree = navigation_result.props.tree;
1952
+ // Reset any boundaries that failed on a previous navigation now that the
1953
+ // new props are applied, otherwise the stale `+error.svelte` stays
1954
+ // mounted above the new route's content. See sveltejs/kit#15694.
1955
+ for (const reset of resetters) {
1956
+ reset?.();
1957
+ }
1958
+ resetters.length = 0;
1905
1959
  // Check for sync rendering error
1906
1960
  if (rendering_error) {
1907
1961
  Object.assign(navigation_result.props.page, rendering_error);
@@ -1918,18 +1972,12 @@ async function navigate({
1918
1972
 
1919
1973
  const { activeElement } = document;
1920
1974
 
1921
- await commit_promise;
1922
-
1923
- // TODO 3.0 remote — the double tick is probably necessary because
1924
- // of some store shenanigans. `settled()` and `f.commit()`
1925
- // should resolve after DOM updates in newer versions
1926
- await svelte.tick();
1927
- await svelte.tick();
1975
+ await (commit_promise ?? svelte.tick());
1928
1976
 
1929
- if (token !== nav_token) {
1977
+ if (navigation_token !== nav_token) {
1930
1978
  // a new navigation happened while we were waiting for the DOM to update, so abort
1931
1979
  nav.reject(new Error('navigation aborted'));
1932
- return false;
1980
+ return;
1933
1981
  }
1934
1982
 
1935
1983
  // Check for async rendering error
@@ -1973,10 +2021,6 @@ async function navigate({
1973
2021
 
1974
2022
  is_navigating = false;
1975
2023
 
1976
- if (type === 'popstate') {
1977
- restore_snapshot(current_navigation_index);
1978
- }
1979
-
1980
2024
  nav.fulfil(undefined);
1981
2025
 
1982
2026
  // Update to.scroll to the actual scroll position after navigation completed
@@ -1988,7 +2032,11 @@ async function navigate({
1988
2032
  fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
1989
2033
  );
1990
2034
 
1991
- stores.navigating.set((navigating.current = null));
2035
+ if (type === 'popstate') {
2036
+ restore_snapshot(current_navigation_index);
2037
+ }
2038
+
2039
+ navigating.current = null;
1992
2040
 
1993
2041
  updating = false;
1994
2042
  }
@@ -1998,23 +2046,21 @@ async function navigate({
1998
2046
  * @param {URL} url
1999
2047
  * @param {{ id: string | null }} route
2000
2048
  * @param {App.Error} error
2001
- * @param {number} status
2002
2049
  * @param {boolean} [replace_state]
2003
- * @returns {Promise<import('./types.js').NavigationFinished>}
2050
+ * @returns {Promise<import('./types.js').NavigationFinished | undefined>}
2004
2051
  */
2005
- async function server_fallback(url, route, error, status, replace_state) {
2052
+ async function server_fallback(url, route, error, replace_state) {
2006
2053
  if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
2007
2054
  // We would reload the same page we're currently on, which isn't hydrated,
2008
2055
  // which means no SSR, which means we would end up in an endless loop
2009
2056
  return await load_root_error_page({
2010
- status,
2011
2057
  error,
2012
2058
  url,
2013
2059
  route
2014
2060
  });
2015
2061
  }
2016
2062
 
2017
- if (DEV && status !== 404) {
2063
+ if (DEV && error.status !== 404) {
2018
2064
  console.error(
2019
2065
  'An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)'
2020
2066
  );
@@ -2043,22 +2089,63 @@ let current_a = { element: undefined, href: undefined };
2043
2089
  function setup_preload() {
2044
2090
  /** @type {NodeJS.Timeout} */
2045
2091
  let mousemove_timeout;
2092
+ /** @type {HTMLAnchorElement | SVGAElement | undefined} */
2093
+ let hovered_a;
2046
2094
  /** @type {PreloadDataPriority} */
2047
2095
  let current_priority;
2048
2096
 
2049
- container.addEventListener('mousemove', (event) => {
2050
- const target = /** @type {Element} */ (event.target);
2097
+ function clear_hover_preload() {
2098
+ clearTimeout(mousemove_timeout);
2099
+ hovered_a?.removeEventListener('mousemove', start_hover_preload);
2100
+ hovered_a?.removeEventListener('mouseleave', clear_hover_preload);
2101
+ hovered_a = undefined;
2102
+ }
2051
2103
 
2104
+ function start_hover_preload() {
2052
2105
  clearTimeout(mousemove_timeout);
2053
2106
  mousemove_timeout = setTimeout(() => {
2054
- void preload(target, PRELOAD_PRIORITIES.hover);
2107
+ if (!hovered_a) return;
2108
+ void preload(hovered_a, PRELOAD_PRIORITIES.hover);
2055
2109
  }, 20);
2110
+ }
2111
+
2112
+ // Use mouseover initially instead of mousemove to avoid cluttering the event queue
2113
+ container.addEventListener('mouseover', (event) => {
2114
+ if (!(event.target instanceof Element)) return;
2115
+
2116
+ const a = find_anchor(event.target, container);
2117
+ if (!a || a === hovered_a) return;
2118
+
2119
+ clear_hover_preload();
2120
+ hovered_a = a;
2121
+
2122
+ const options = get_router_options(a);
2123
+
2124
+ if (
2125
+ options.preload_code < PRELOAD_PRIORITIES.hover &&
2126
+ options.preload_data < PRELOAD_PRIORITIES.hover
2127
+ ) {
2128
+ // don't add event listeners if no preloading will happen
2129
+ return;
2130
+ }
2131
+
2132
+ // Instead of just preloading right away, we start a mousemove listener to implement
2133
+ // "mouse comes to a rest" behavior. This avoid false positives when you just move
2134
+ // your mouse across the screen and happen to pass over a link.
2135
+ a.addEventListener('mousemove', start_hover_preload);
2136
+ a.addEventListener('mouseleave', clear_hover_preload, { once: true });
2137
+
2138
+ start_hover_preload();
2056
2139
  });
2057
2140
 
2058
2141
  /** @param {Event} event */
2059
2142
  function tap(event) {
2060
2143
  if (event.defaultPrevented) return;
2061
- void preload(/** @type {Element} */ (event.composedPath()[0]), PRELOAD_PRIORITIES.tap);
2144
+
2145
+ const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
2146
+ if (!a) return;
2147
+
2148
+ void preload(a, PRELOAD_PRIORITIES.tap);
2062
2149
  }
2063
2150
 
2064
2151
  container.addEventListener('mousedown', tap);
@@ -2077,12 +2164,10 @@ function setup_preload() {
2077
2164
  );
2078
2165
 
2079
2166
  /**
2080
- * @param {Element} element
2167
+ * @param {HTMLAnchorElement | SVGAElement} a
2081
2168
  * @param {PreloadDataPriority} priority
2082
2169
  */
2083
- async function preload(element, priority) {
2084
- const a = find_anchor(element, container);
2085
-
2170
+ async function preload(a, priority) {
2086
2171
  // we don't want to preload data again if the user has already hovered/tapped
2087
2172
  const interacted =
2088
2173
  a === current_a.element && a?.href === current_a.href && priority >= current_priority;
@@ -2106,15 +2191,13 @@ function setup_preload() {
2106
2191
  if (!intent) return;
2107
2192
 
2108
2193
  if (DEV) {
2109
- void _preload_data(intent).then((result) => {
2110
- if (result.type === 'loaded' && result.state.error) {
2111
- console.warn(
2112
- `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
2113
- 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
2114
- 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
2115
- 'See https://svelte.dev/docs/kit/link-options for more info'
2116
- );
2117
- }
2194
+ void _preload_data(intent).catch((error) => {
2195
+ console.warn(
2196
+ `Preloading data for ${intent.url.pathname} failed with the following error: ${error.message}\n` +
2197
+ 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
2198
+ 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
2199
+ 'See https://svelte.dev/docs/kit/link-options for more info'
2200
+ );
2118
2201
  });
2119
2202
  } else {
2120
2203
  void _preload_data(intent);
@@ -2153,9 +2236,9 @@ function setup_preload() {
2153
2236
  /**
2154
2237
  * @param {unknown} error
2155
2238
  * @param {import('@sveltejs/kit').NavigationEvent} event
2156
- * @returns {import('types').MaybePromise<App.Error>}
2239
+ * @returns {Promise<App.Error>}
2157
2240
  */
2158
- function handle_error(error, event) {
2241
+ export async function handle_error(error, event) {
2159
2242
  if (error instanceof HttpError) {
2160
2243
  return error.body;
2161
2244
  }
@@ -2167,10 +2250,9 @@ function handle_error(error, event) {
2167
2250
 
2168
2251
  const status = get_status(error);
2169
2252
  const message = get_message(error);
2253
+ const app_error = (await app.hooks.handleError({ error, event, status, message })) ?? { message };
2170
2254
 
2171
- return (
2172
- app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
2173
- );
2255
+ return { ...app_error, status: get_status(app_error, error) };
2174
2256
  }
2175
2257
 
2176
2258
  /**
@@ -2179,7 +2261,7 @@ function handle_error(error, event) {
2179
2261
  * @param {T} callback
2180
2262
  */
2181
2263
  function add_navigation_callback(callbacks, callback) {
2182
- onMount(() => {
2264
+ svelte.onMount(() => {
2183
2265
  callbacks.add(callback);
2184
2266
 
2185
2267
  return () => {
@@ -2250,23 +2332,28 @@ export function disableScrollHandling() {
2250
2332
  }
2251
2333
  }
2252
2334
 
2335
+ let warned_on_invalidate_all = false;
2336
+
2253
2337
  /**
2254
2338
  * Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
2255
2339
  * Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
2256
2340
  *
2257
- * For external URLs, use `window.location = url` instead of calling `goto(url)`.
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.
2343
+ * For external URLs, use `window.location = url` to perform a full-page navigation instead of calling `goto(url)`.
2258
2344
  *
2259
- * @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.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.
2345
+ * @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.
2260
2346
  * @param {Object} [opts] Options related to the navigation
2261
2347
  * @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
2262
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
2263
2349
  * @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
2264
- * @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
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.
2265
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.
2266
2353
  * @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
2267
2354
  * @returns {Promise<void>}
2268
2355
  */
2269
- export function goto(url, opts = {}) {
2356
+ export async function goto(url, opts = {}) {
2270
2357
  if (!BROWSER) {
2271
2358
  throw new Error('Cannot call goto(...) on the server');
2272
2359
  }
@@ -2274,16 +2361,32 @@ export function goto(url, opts = {}) {
2274
2361
  url = new URL(resolve_url(url));
2275
2362
 
2276
2363
  if (url.origin !== origin) {
2277
- return Promise.reject(
2278
- new Error(
2279
- DEV
2280
- ? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
2281
- : 'goto: invalid URL'
2282
- )
2364
+ throw new Error(
2365
+ DEV
2366
+ ? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
2367
+ : 'goto: invalid URL'
2283
2368
  );
2284
2369
  }
2285
2370
 
2286
- return _goto(url, opts, 0);
2371
+ const intent = await get_navigation_intent(url, false);
2372
+
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'
2378
+ );
2379
+ }
2380
+
2381
+ if (DEV && 'invalidateAll' in opts && !warned_on_invalidate_all) {
2382
+ warned_on_invalidate_all = true;
2383
+ console.warn(
2384
+ `The \`goto(..., { invalidateAll: ${opts.invalidateAll} })\` option has been deprecated in favour of \`refreshAll\``
2385
+ );
2386
+ }
2387
+
2388
+ opts.refreshAll = opts.refreshAll ?? opts.invalidateAll;
2389
+ return _goto(url, opts, 0, {}, intent);
2287
2390
  }
2288
2391
 
2289
2392
  /**
@@ -2302,16 +2405,17 @@ export function goto(url, opts = {}) {
2302
2405
  * invalidate((url) => url.pathname === '/path');
2303
2406
  * ```
2304
2407
  * @param {string | URL | ((url: URL) => boolean)} resource The invalidated URL
2408
+ * @param {boolean} [keepState] If `true`, the current `page.state` will be preserved. Otherwise, it will be reset to an empty object. `false` by default.
2305
2409
  * @returns {Promise<void>}
2306
2410
  */
2307
- export function invalidate(resource) {
2411
+ export function invalidate(resource, keepState = false) {
2308
2412
  if (!BROWSER) {
2309
2413
  throw new Error('Cannot call invalidate(...) on the server');
2310
2414
  }
2311
2415
 
2312
2416
  push_invalidated(resource);
2313
2417
 
2314
- return _invalidate();
2418
+ return _invalidate(!keepState);
2315
2419
  }
2316
2420
 
2317
2421
  /**
@@ -2328,6 +2432,10 @@ function push_invalidated(resource) {
2328
2432
 
2329
2433
  /**
2330
2434
  * Causes all `load` and `query` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
2435
+ *
2436
+ * Note that this resets `page.state` to an empty object. If you want to preserve `page.state` (for example when using [shallow routing](https://svelte.dev/docs/kit/shallow-routing)), use `refreshAll` instead.
2437
+ *
2438
+ * @deprecated Use [`refreshAll`](https://svelte.dev/docs/kit/$app-navigation#refreshAll) instead. Unlike `invalidateAll`, `refreshAll` does not reset `page.state`.
2331
2439
  * @returns {Promise<void>}
2332
2440
  */
2333
2441
  export function invalidateAll() {
@@ -2340,18 +2448,17 @@ export function invalidateAll() {
2340
2448
  }
2341
2449
 
2342
2450
  /**
2343
- * Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run (unless disabled via the option argument).
2451
+ * Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run.
2344
2452
  * Returns a `Promise` that resolves when the page is subsequently updated.
2345
- * @param {{ includeLoadFunctions?: boolean }} [options]
2346
2453
  * @returns {Promise<void>}
2347
2454
  */
2348
- export function refreshAll({ includeLoadFunctions = true } = {}) {
2455
+ export function refreshAll() {
2349
2456
  if (!BROWSER) {
2350
2457
  throw new Error('Cannot call refreshAll() on the server');
2351
2458
  }
2352
2459
 
2353
2460
  force_invalidation = true;
2354
- return _invalidate(includeLoadFunctions, false);
2461
+ return _invalidate(false);
2355
2462
  }
2356
2463
 
2357
2464
  /**
@@ -2364,7 +2471,7 @@ export function refreshAll({ includeLoadFunctions = true } = {}) {
2364
2471
  * Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
2365
2472
  *
2366
2473
  * @param {string} href Page to preload
2367
- * @returns {Promise<{ type: 'loaded'; status: number; data: Record<string, any> } | { type: 'redirect'; location: string }>}
2474
+ * @returns {Promise<({ type: 'loaded'; data: Record<string, any> } | { type: 'redirect'; location: string } | { type: 'error'; error: App.Error }) & { status: number; }>}
2368
2475
  */
2369
2476
  export async function preloadData(href) {
2370
2477
  if (!BROWSER) {
@@ -2378,15 +2485,32 @@ export async function preloadData(href) {
2378
2485
  throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
2379
2486
  }
2380
2487
 
2381
- const result = await _preload_data(intent);
2488
+ /** @type {Awaited<ReturnType<typeof _preload_data>>} */
2489
+ let result;
2490
+
2491
+ try {
2492
+ result = await _preload_data(intent);
2493
+ } catch (error) {
2494
+ // `load_route` throws the handled error (an `App.Error` with a `status`)
2495
+ // when a preload fails, so surface it in the documented `{ type: 'error' }` shape
2496
+ const handled = /** @type {App.Error & { status?: number }} */ (error);
2497
+ return {
2498
+ type: 'error',
2499
+ status: handled?.status ?? 500,
2500
+ error: handled
2501
+ };
2502
+ }
2503
+
2382
2504
  if (result.type === 'redirect') {
2383
2505
  return {
2384
2506
  type: result.type,
2507
+ status: result.status,
2385
2508
  location: result.location
2386
2509
  };
2387
2510
  }
2388
2511
 
2389
2512
  const { status, data } = result.props.page ?? page;
2513
+
2390
2514
  return { type: result.type, status, data };
2391
2515
  }
2392
2516
 
@@ -2407,7 +2531,9 @@ export async function preloadCode(pathname) {
2407
2531
  throw new Error('Cannot call preloadCode(...) on the server');
2408
2532
  }
2409
2533
 
2410
- const url = new URL(pathname, current.url);
2534
+ // `current.url` is null until the first navigation/hydration completes, so fall back
2535
+ // to `location` to support calling `preloadCode` during initial page load (#13297)
2536
+ const url = new URL(pathname, current.url ?? location.href);
2411
2537
 
2412
2538
  if (DEV) {
2413
2539
  if (!pathname.startsWith('/')) {
@@ -2472,10 +2598,6 @@ export function pushState(url, state) {
2472
2598
  has_navigated = true;
2473
2599
 
2474
2600
  page.state = state;
2475
- root.$set({
2476
- // we need to assign a new page object so that subscribers are correctly notified
2477
- page: untrack(() => clone_page(page))
2478
- });
2479
2601
 
2480
2602
  clear_onward_history(current_history_index, current_navigation_index);
2481
2603
  }
@@ -2516,9 +2638,6 @@ export function replaceState(url, state) {
2516
2638
  history.replaceState(opts, '', resolve_url(url));
2517
2639
 
2518
2640
  page.state = state;
2519
- root.$set({
2520
- page: untrack(() => clone_page(page))
2521
- });
2522
2641
  }
2523
2642
 
2524
2643
  /**
@@ -2535,9 +2654,9 @@ export async function applyAction(result) {
2535
2654
  }
2536
2655
 
2537
2656
  if (result.type === 'error') {
2538
- await set_nearest_error_page(result.error, result.status);
2657
+ await set_nearest_error_page(result.error);
2539
2658
  } else if (result.type === 'redirect') {
2540
- await _goto(result.location, { invalidateAll: true }, 0);
2659
+ await _goto(result.location, { refreshAll: true }, 0);
2541
2660
  } else {
2542
2661
  page.form = result.data;
2543
2662
  page.status = result.status;
@@ -2546,25 +2665,23 @@ export async function applyAction(result) {
2546
2665
  root.$set({
2547
2666
  // this brings Svelte's view of the world in line with SvelteKit's
2548
2667
  // after use:enhance reset the form....
2549
- form: null,
2550
- page: clone_page(page)
2668
+ form: null
2551
2669
  });
2552
2670
 
2553
2671
  // ...so that setting the `form` prop takes effect and isn't ignored
2554
- await tick();
2672
+ await svelte.tick();
2555
2673
  root.$set({ form: result.data });
2556
2674
 
2557
2675
  if (result.type === 'success') {
2558
- reset_focus(page.url);
2676
+ reset_focus(/** @type {URL} */ (page.url));
2559
2677
  }
2560
2678
  }
2561
2679
  }
2562
2680
 
2563
2681
  /**
2564
2682
  * @param {App.Error} error
2565
- * @param {number} status
2566
2683
  */
2567
- export async function set_nearest_error_page(error, status = 500) {
2684
+ export async function set_nearest_error_page(error) {
2568
2685
  const url = new URL(location.href);
2569
2686
 
2570
2687
  const { branch, route } = current;
@@ -2576,7 +2693,6 @@ export async function set_nearest_error_page(error, status = 500) {
2576
2693
  url,
2577
2694
  params: current.params,
2578
2695
  branch: branch.slice(0, error_load.idx).concat(error_load.node),
2579
- status,
2580
2696
  error,
2581
2697
  // do not set errors, we haven't changed the page so the previous ones are still current
2582
2698
  route
@@ -2585,9 +2701,10 @@ export async function set_nearest_error_page(error, status = 500) {
2585
2701
  current = { ...navigation_result.state, nav: current.nav };
2586
2702
 
2587
2703
  root.$set(navigation_result.props);
2704
+ current_tree = navigation_result.props.tree;
2588
2705
  update(navigation_result.props.page);
2589
2706
 
2590
- void tick().then(() => reset_focus(current.url));
2707
+ void svelte.tick().then(() => reset_focus(current.url));
2591
2708
  }
2592
2709
  }
2593
2710
 
@@ -2811,7 +2928,7 @@ function _start_router() {
2811
2928
 
2812
2929
  if (event.state?.[HISTORY_INDEX]) {
2813
2930
  const history_index = event.state[HISTORY_INDEX];
2814
- token = {};
2931
+ navigation_token = invalidation_token = {};
2815
2932
 
2816
2933
  // if a popstate-driven navigation is cancelled, we need to counteract it
2817
2934
  // with history.go, which means we end up back here, hence this check
@@ -2860,7 +2977,7 @@ function _start_router() {
2860
2977
  block: () => {
2861
2978
  history.go(-delta);
2862
2979
  },
2863
- nav_token: token,
2980
+ nav_token: navigation_token,
2864
2981
  event
2865
2982
  });
2866
2983
  } else {
@@ -2912,7 +3029,7 @@ function _start_router() {
2912
3029
  // the navigation away from it was successful.
2913
3030
  // Info about bfcache here: https://web.dev/bfcache
2914
3031
  if (event.persisted) {
2915
- stores.navigating.set((navigating.current = null));
3032
+ navigating.current = null;
2916
3033
  }
2917
3034
  });
2918
3035
 
@@ -2921,18 +3038,17 @@ function _start_router() {
2921
3038
  */
2922
3039
  function update_url(url) {
2923
3040
  current.url = page.url = url;
2924
- stores.page.set(clone_page(page));
2925
- stores.page.notify();
2926
3041
  }
2927
3042
  }
2928
3043
 
2929
3044
  /**
2930
3045
  * @param {HTMLElement} target
2931
3046
  * @param {import('./types.js').HydrateOptions} opts
3047
+ * @returns {Promise<void>}
2932
3048
  */
2933
3049
  async function _hydrate(
2934
3050
  target,
2935
- { status = 200, error, node_ids, params, route, server_route, data: server_data_nodes, form }
3051
+ { status, error, node_ids, params, route, server_route, data: server_data_nodes, form }
2936
3052
  ) {
2937
3053
  hydrated = true;
2938
3054
 
@@ -3015,23 +3131,25 @@ async function _hydrate(
3015
3131
  if (error instanceof Redirect) {
3016
3132
  // this is a real edge case — `load` would need to return
3017
3133
  // a redirect but only in the browser
3018
- await native_navigation(new URL(error.location, location.href));
3019
- return;
3134
+ return await native_navigation(new URL(error.location, location.href));
3020
3135
  }
3021
3136
 
3137
+ const handled_error = await handle_error(error, { url, params, route });
3138
+
3022
3139
  result = await load_root_error_page({
3023
- status: get_status(error),
3024
- error: await handle_error(error, { url, params, route }),
3140
+ error: handled_error,
3025
3141
  url,
3026
3142
  route
3027
3143
  });
3028
3144
 
3029
3145
  target.textContent = '';
3030
3146
  hydrate = false;
3031
- } finally {
3032
- query_responses = {};
3033
3147
  }
3034
3148
 
3149
+ // Exit early when we encounter a redirect while loading the root error page.
3150
+ // In this case, `initialize` will be called later on
3151
+ if (!result) return;
3152
+
3035
3153
  if (result.props.page) {
3036
3154
  result.props.page.state = {};
3037
3155
  }
@@ -3208,9 +3326,6 @@ function reset_focus(url, scroll = true) {
3208
3326
  const tabindex = root.getAttribute('tabindex');
3209
3327
 
3210
3328
  root.tabIndex = -1;
3211
- // TODO: remove this when we switch to TypeScript 6
3212
- // @ts-ignore options.focusVisible is only typed in TypeScript 6
3213
- // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#browser_compatibility
3214
3329
  root.focus({ preventScroll: true, focusVisible: false });
3215
3330
 
3216
3331
  // restore `tabindex` as to prevent `root` from stealing input from elements
@@ -3313,28 +3428,6 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
3313
3428
  };
3314
3429
  }
3315
3430
 
3316
- /**
3317
- * TODO: remove this in 3.0 when the page store is also removed
3318
- *
3319
- * We need to assign a new page object so that subscribers are correctly notified.
3320
- * However, spreading `{ ...page }` returns an empty object so we manually
3321
- * assign to each property instead.
3322
- *
3323
- * @param {import('@sveltejs/kit').Page} page
3324
- */
3325
- function clone_page(page) {
3326
- return {
3327
- data: page.data,
3328
- error: page.error,
3329
- form: page.form,
3330
- params: page.params,
3331
- route: page.route,
3332
- state: page.state,
3333
- status: page.status,
3334
- url: page.url
3335
- };
3336
- }
3337
-
3338
3431
  /**
3339
3432
  * @param {URL} url
3340
3433
  * @returns {URL}