@sveltejs/kit 3.0.0-next.7 → 3.0.0-next.9

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 (61) hide show
  1. package/package.json +6 -3
  2. package/src/core/config/index.js +1 -2
  3. package/src/core/config/options.js +5 -2
  4. package/src/core/env.js +1 -0
  5. package/src/core/sync/create_manifest_data/index.js +1 -1
  6. package/src/core/sync/sync.js +0 -2
  7. package/src/core/sync/write_client_manifest.js +0 -7
  8. package/src/core/sync/write_non_ambient.js +2 -2
  9. package/src/core/sync/write_server.js +0 -3
  10. package/src/core/sync/write_tsconfig.js +14 -4
  11. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  12. package/src/core/sync/write_types/index.js +17 -14
  13. package/src/core/utils.js +2 -2
  14. package/src/exports/hooks/sequence.js +3 -2
  15. package/src/exports/index.js +1 -1
  16. package/src/exports/node/index.js +4 -8
  17. package/src/exports/public.d.ts +22 -23
  18. package/src/exports/vite/dev/index.js +4 -7
  19. package/src/exports/vite/index.js +110 -59
  20. package/src/exports/vite/preview/index.js +13 -14
  21. package/src/exports/vite/utils.js +14 -14
  22. package/src/runtime/app/env/internal.js +4 -4
  23. package/src/runtime/app/environment/index.js +3 -3
  24. package/src/runtime/app/forms.js +2 -2
  25. package/src/runtime/app/paths/internal/client.js +4 -2
  26. package/src/runtime/app/paths/internal/server.js +2 -23
  27. package/src/runtime/app/paths/server.js +2 -2
  28. package/src/runtime/app/server/remote/prerender.js +1 -2
  29. package/src/runtime/client/bundle.js +1 -1
  30. package/src/runtime/client/client-entry.js +3 -0
  31. package/src/runtime/client/client.js +217 -172
  32. package/src/runtime/client/entry.js +24 -3
  33. package/src/runtime/client/payload.js +17 -0
  34. package/src/runtime/client/remote-functions/form.svelte.js +6 -6
  35. package/src/runtime/client/state.svelte.js +0 -1
  36. package/src/runtime/client/types.d.ts +2 -6
  37. package/src/runtime/components/root.svelte +66 -0
  38. package/src/runtime/env/dynamic/private.js +7 -0
  39. package/src/runtime/env/dynamic/public.js +7 -0
  40. package/src/runtime/env/static/private.js +6 -0
  41. package/src/runtime/env/static/public.js +6 -0
  42. package/src/runtime/form-utils.js +1 -4
  43. package/src/runtime/server/cookie.js +51 -23
  44. package/src/runtime/server/csrf.js +1 -1
  45. package/src/runtime/server/data/index.js +8 -12
  46. package/src/runtime/server/page/index.js +7 -14
  47. package/src/runtime/server/page/render.js +71 -78
  48. package/src/runtime/server/remote.js +2 -1
  49. package/src/runtime/server/respond.js +1 -1
  50. package/src/runtime/server/utils.js +28 -5
  51. package/src/runtime/types.d.ts +8 -0
  52. package/src/types/global-private.d.ts +10 -17
  53. package/src/types/internal.d.ts +25 -26
  54. package/src/utils/import.js +6 -1
  55. package/src/utils/imports.js +83 -0
  56. package/src/utils/routing.js +6 -6
  57. package/src/version.js +1 -1
  58. package/types/index.d.ts +34 -285
  59. package/types/index.d.ts.map +3 -26
  60. package/src/core/sync/write_root.js +0 -127
  61. package/src/types/synthetic/$lib.md +0 -5
@@ -1,5 +1,6 @@
1
1
  /** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
2
2
  /** @import { NavigationIntent } from './types.js' */
3
+ /** @import { RenderNode } from '../types.js' */
3
4
  /** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
4
5
  /** @import { Query } from './remote-functions/query/instance.svelte.js' */
5
6
  /** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
@@ -33,7 +34,6 @@ import {
33
34
  } from './constants.js';
34
35
  import { validate_page_exports } from '../../utils/exports.js';
35
36
  import { noop } from '../../utils/functions.js';
36
- import { compact } from '../../utils/array.js';
37
37
  import {
38
38
  INVALIDATED_PARAM,
39
39
  TRAILING_SLASH_PARAM,
@@ -43,9 +43,14 @@ import {
43
43
  } from '../shared.js';
44
44
  import { get_message, get_status } from '../../utils/error.js';
45
45
  import { page, update, navigating, updated } from './state.svelte.js';
46
+ import { payload } from './payload.js';
46
47
  import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
47
48
  import { noop_span } from '../telemetry/noop.js';
48
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);
49
54
 
50
55
  export { load_css };
51
56
  const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
@@ -60,6 +65,17 @@ let errored = false;
60
65
  */
61
66
  let rendering_error = null;
62
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
+
63
79
  // We track the scroll position associated with each history entry in sessionStorage,
64
80
  // rather than on history.state itself, because when navigation is driven by
65
81
  // popstate it's too late to update the scroll position associated with the
@@ -76,6 +92,11 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
76
92
  */
77
93
  const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
78
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
+
79
100
  if (DEV && BROWSER) {
80
101
  let warned = false;
81
102
 
@@ -323,8 +344,8 @@ export async function start(_app, _target, hydrate) {
323
344
  );
324
345
  }
325
346
 
326
- if (__SVELTEKIT_PAYLOAD__.data) {
327
- const { q = {}, p = {}, l = {}, f = {} } = __SVELTEKIT_PAYLOAD__.data;
347
+ if (payload.data) {
348
+ const { q = {}, p = {}, l = {}, f = {} } = payload.data;
328
349
 
329
350
  // store the whole nodes — error records seed the corresponding
330
351
  // resources in a failed state when they are created during hydration
@@ -403,12 +424,12 @@ export async function start(_app, _target, hydrate) {
403
424
  _start_router();
404
425
  }
405
426
 
406
- async function _invalidate(include_load_functions = true, reset_page_state = true) {
427
+ async function _invalidate(reset_page_state = true) {
407
428
  // Accept all invalidations as they come, don't swallow any while another invalidation
408
429
  // is running because subsequent invalidations may make earlier ones outdated,
409
430
  // but batch multiple synchronous invalidations.
410
431
  await (pending_invalidate ||= Promise.resolve());
411
- if (!pending_invalidate) return;
432
+ if (pending_invalidate === null) return;
412
433
  pending_invalidate = null;
413
434
 
414
435
  const token = (invalidation_token = {});
@@ -442,39 +463,36 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
442
463
  }
443
464
  }
444
465
 
445
- if (include_load_functions) {
446
- const prev_state = page.state;
447
- const navigation_result = intent && (await load_route(intent));
448
- if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
449
- return;
450
- }
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
+ }
451
471
 
452
- if (navigation_result.type === 'redirect') {
453
- return _goto(
454
- new URL(navigation_result.location, current.url).href,
455
- { replaceState: true },
456
- 1,
457
- token
458
- );
459
- }
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
+ }
460
480
 
461
- // A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
462
- // hence it likely contains outdated data now, so we ignore it.
463
- if (navigating && !is_navigating) {
464
- return;
465
- }
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;
485
+ }
466
486
 
467
- // This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
468
- if (!reset_page_state) {
469
- navigation_result.props.page.state = prev_state;
470
- }
471
- update(navigation_result.props.page);
472
- current = { ...navigation_result.state, nav: current.nav };
473
- reset_invalidation();
474
- root.$set(navigation_result.props);
475
- } else {
476
- reset_invalidation();
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;
477
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);
478
496
 
479
497
  // only wait for promises that are connected to queries that still exist
480
498
  /** @type {Promise<any>[]} */
@@ -527,7 +545,7 @@ function persist_state() {
527
545
 
528
546
  /**
529
547
  * @param {string | URL} url
530
- * @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
531
549
  * @param {number} redirect_count
532
550
  * @param {{}} [nav_token]
533
551
  * @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
@@ -539,9 +557,9 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
539
557
  /** @type {Set<string>} */
540
558
  let live_query_keys;
541
559
 
542
- // Clear preload cache when invalidateAll is true to ensure fresh data
560
+ // Clear preload cache when refreshAll is true to ensure fresh data
543
561
  // after form submissions or explicit invalidations
544
- if (options.invalidateAll) {
562
+ if (options.refreshAll) {
545
563
  discard_load_cache();
546
564
  }
547
565
 
@@ -556,7 +574,7 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
556
574
  nav_token,
557
575
  intent,
558
576
  accept: () => {
559
- if (options.invalidateAll) {
577
+ if (options.refreshAll) {
560
578
  force_invalidation = true;
561
579
  query_keys = new Set();
562
580
  for (const [id, entries] of query_map) {
@@ -582,7 +600,7 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
582
600
  }
583
601
  });
584
602
 
585
- if (options.invalidateAll) {
603
+ if (options.refreshAll) {
586
604
  // TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
587
605
  // when a query in a layout that still exists after page change is refreshed earlier than this
588
606
  void svelte
@@ -621,17 +639,14 @@ async function _preload_data(intent) {
621
639
  load_cache = {
622
640
  id: intent.id,
623
641
  token: preload,
624
- promise: load_route({ ...intent, preload }).then((result) => {
642
+ promise: load_route({ ...intent, preload }).finally(() => {
625
643
  preload_tokens.delete(preload);
626
- if (result.type === 'loaded' && result.state.error) {
627
- // Don't cache errors, because they might be transient
628
- discard_load_cache();
629
- }
630
- return result;
631
644
  }),
632
645
  fork: null
633
646
  };
634
647
 
648
+ load_cache.promise.catch(discard_load_cache);
649
+
635
650
  if (__SVELTEKIT_FORK_PRELOADS__ && svelte.fork) {
636
651
  const lc = load_cache;
637
652
 
@@ -643,6 +658,7 @@ async function _preload_data(intent) {
643
658
  return svelte.fork(() => {
644
659
  root.$set(result.props);
645
660
  update(result.props.page);
661
+ current_tree = result.props.tree;
646
662
  });
647
663
  } catch {
648
664
  // if it errors, it's because the experimental flag isn't enabled in Svelte
@@ -701,24 +717,22 @@ async function initialize(result, target, hydrate) {
701
717
  }
702
718
 
703
719
  update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
720
+ current_tree = result.props.tree;
704
721
 
705
722
  // TODO: use mount()
706
- root = new app.root({
723
+ root = new Root({
707
724
  target,
708
- props: { ...result.props, components },
725
+ props: { ...result.props, components, resetters },
709
726
  hydrate,
710
727
  // Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
711
728
  sync: false,
712
- // Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
713
- transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
714
- ? /** @param {unknown} e */ async (e) => {
715
- const error = await handle_error(e, current.nav);
716
- rendering_error = { error, status: error.status };
717
- page.error = error;
718
- page.status = rendering_error.status;
719
- return error;
720
- }
721
- : 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
+ }
722
736
  });
723
737
 
724
738
  // Wait for a microtask in case svelte experimental async is enabled,
@@ -797,34 +811,12 @@ async function get_navigation_result_from_branch({
797
811
  route
798
812
  },
799
813
  props: {
800
- constructors: compact(branch).map((branch_node) => branch_node.node.component),
801
- page
814
+ page,
815
+ tree: /** @type {RenderNode} */ ({})
802
816
  }
803
817
  };
804
818
 
805
- if (errors && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
806
- let last_idx = -1;
807
- result.props.errors = await Promise.all(
808
- // eslint-disable-next-line @typescript-eslint/await-thenable
809
- branch
810
- .map((b, i) => {
811
- if (i === 0) return undefined; // root layout wraps root error component, not the other way around
812
- if (!b) return null;
813
-
814
- i--;
815
- // Find the closest error component up to the previous branch
816
- while (i > last_idx + 1 && !errors[i]) i -= 1;
817
- last_idx = i;
818
- return errors[i]?.()
819
- .then((e) => e.component)
820
- .catch(() => undefined);
821
- })
822
- // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
823
- .filter((e) => e !== null)
824
- );
825
- }
826
-
827
- if (error && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
819
+ if (error) {
828
820
  result.props.error = error;
829
821
  }
830
822
 
@@ -835,23 +827,44 @@ async function get_navigation_result_from_branch({
835
827
  let data = {};
836
828
  let data_changed = !page;
837
829
 
838
- let p = 0;
830
+ let current_node = result.props.tree;
831
+
832
+ /** @type {RenderNode | undefined} */
833
+ let previous_node = current_tree;
839
834
 
840
- for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) {
835
+ for (let i = 0; i < branch.length; i += 1) {
841
836
  const node = branch[i];
842
837
  const prev = current.branch[i];
843
838
 
844
- if (node?.data !== prev?.data) data_changed = true;
845
839
  if (!node) continue;
846
840
 
847
- data = { ...data, ...node.data };
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;
848
845
 
849
- // Only set props if the node actually updated. This prevents needless rerenders.
850
- if (data_changed) {
851
- result.props[`data_${p}`] = data;
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;
852
858
  }
853
859
 
854
- 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
+ }
855
868
  }
856
869
 
857
870
  const page_changed =
@@ -872,8 +885,7 @@ async function get_navigation_result_from_branch({
872
885
  status: status ?? error?.status ?? 200,
873
886
  url: new URL(url),
874
887
  form: form ?? null,
875
- // The whole page store is updated, but this way the object reference stays the same
876
- data: data_changed ? data : page.data
888
+ data
877
889
  };
878
890
  }
879
891
 
@@ -1151,32 +1163,6 @@ function diff_search_params(old_url, new_url) {
1151
1163
  return changed;
1152
1164
  }
1153
1165
 
1154
- /**
1155
- * @param {Omit<import('./types.js').NavigationFinished['state'], 'branch'> & { error: App.Error }} opts
1156
- * @returns {import('./types.js').NavigationFinished}
1157
- */
1158
- function preload_error({ error, url, route, params }) {
1159
- return {
1160
- type: 'loaded',
1161
- state: {
1162
- error,
1163
- url,
1164
- route,
1165
- params,
1166
- branch: []
1167
- },
1168
- props: {
1169
- page: {
1170
- // we skipped loading the error page, so we have to use the current page
1171
- // store, but update the status received while preloading
1172
- ...page,
1173
- status: error.status
1174
- },
1175
- constructors: []
1176
- }
1177
- };
1178
- }
1179
-
1180
1166
  /**
1181
1167
  * @overload
1182
1168
  * @param {import('./types.js').NavigationIntent} intent
@@ -1247,7 +1233,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1247
1233
  const handled_error = await handle_error(error, { url, params, route: { id } });
1248
1234
 
1249
1235
  if (preload && preload_tokens.has(preload)) {
1250
- return preload_error({ error: handled_error, url, params, route });
1236
+ throw handled_error;
1251
1237
  }
1252
1238
 
1253
1239
  return load_root_error_page({
@@ -1337,13 +1323,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
1337
1323
  }
1338
1324
 
1339
1325
  if (preload && preload_tokens.has(preload)) {
1340
- const error = await handle_error(err, { params, url, route: { id: route.id } });
1341
- return preload_error({
1342
- error,
1343
- url,
1344
- params,
1345
- route
1346
- });
1326
+ throw await handle_error(err, { params, url, route: { id: route.id } });
1347
1327
  }
1348
1328
 
1349
1329
  /** @type {App.Error} */
@@ -1958,9 +1938,24 @@ async function navigate({
1958
1938
 
1959
1939
  if (fork) {
1960
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;
1961
1948
  } else {
1962
1949
  rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
1963
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;
1964
1959
  // Check for sync rendering error
1965
1960
  if (rendering_error) {
1966
1961
  Object.assign(navigation_result.props.page, rendering_error);
@@ -2094,22 +2089,63 @@ let current_a = { element: undefined, href: undefined };
2094
2089
  function setup_preload() {
2095
2090
  /** @type {NodeJS.Timeout} */
2096
2091
  let mousemove_timeout;
2092
+ /** @type {HTMLAnchorElement | SVGAElement | undefined} */
2093
+ let hovered_a;
2097
2094
  /** @type {PreloadDataPriority} */
2098
2095
  let current_priority;
2099
2096
 
2100
- container.addEventListener('mousemove', (event) => {
2101
- 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
+ }
2102
2103
 
2104
+ function start_hover_preload() {
2103
2105
  clearTimeout(mousemove_timeout);
2104
2106
  mousemove_timeout = setTimeout(() => {
2105
- void preload(target, PRELOAD_PRIORITIES.hover);
2107
+ if (!hovered_a) return;
2108
+ void preload(hovered_a, PRELOAD_PRIORITIES.hover);
2106
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();
2107
2139
  });
2108
2140
 
2109
2141
  /** @param {Event} event */
2110
2142
  function tap(event) {
2111
2143
  if (event.defaultPrevented) return;
2112
- 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);
2113
2149
  }
2114
2150
 
2115
2151
  container.addEventListener('mousedown', tap);
@@ -2128,12 +2164,10 @@ function setup_preload() {
2128
2164
  );
2129
2165
 
2130
2166
  /**
2131
- * @param {Element} element
2167
+ * @param {HTMLAnchorElement | SVGAElement} a
2132
2168
  * @param {PreloadDataPriority} priority
2133
2169
  */
2134
- async function preload(element, priority) {
2135
- const a = find_anchor(element, container);
2136
-
2170
+ async function preload(a, priority) {
2137
2171
  // we don't want to preload data again if the user has already hovered/tapped
2138
2172
  const interacted =
2139
2173
  a === current_a.element && a?.href === current_a.href && priority >= current_priority;
@@ -2157,15 +2191,13 @@ function setup_preload() {
2157
2191
  if (!intent) return;
2158
2192
 
2159
2193
  if (DEV) {
2160
- void _preload_data(intent).then((result) => {
2161
- if (result.type === 'loaded' && result.state.error) {
2162
- console.warn(
2163
- `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
2164
- 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
2165
- 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
2166
- 'See https://svelte.dev/docs/kit/link-options for more info'
2167
- );
2168
- }
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
+ );
2169
2201
  });
2170
2202
  } else {
2171
2203
  void _preload_data(intent);
@@ -2300,6 +2332,8 @@ export function disableScrollHandling() {
2300
2332
  }
2301
2333
  }
2302
2334
 
2335
+ let warned_on_invalidate_all = false;
2336
+
2303
2337
  /**
2304
2338
  * Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
2305
2339
  * Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
@@ -2313,8 +2347,9 @@ export function disableScrollHandling() {
2313
2347
  * @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
2314
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
2315
2349
  * @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
2316
- * @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.
2317
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.
2318
2353
  * @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
2319
2354
  * @returns {Promise<void>}
2320
2355
  */
@@ -2343,6 +2378,14 @@ export async function goto(url, opts = {}) {
2343
2378
  );
2344
2379
  }
2345
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;
2346
2389
  return _goto(url, opts, 0, {}, intent);
2347
2390
  }
2348
2391
 
@@ -2362,16 +2405,17 @@ export async function goto(url, opts = {}) {
2362
2405
  * invalidate((url) => url.pathname === '/path');
2363
2406
  * ```
2364
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.
2365
2409
  * @returns {Promise<void>}
2366
2410
  */
2367
- export function invalidate(resource) {
2411
+ export function invalidate(resource, keepState = false) {
2368
2412
  if (!BROWSER) {
2369
2413
  throw new Error('Cannot call invalidate(...) on the server');
2370
2414
  }
2371
2415
 
2372
2416
  push_invalidated(resource);
2373
2417
 
2374
- return _invalidate();
2418
+ return _invalidate(!keepState);
2375
2419
  }
2376
2420
 
2377
2421
  /**
@@ -2388,6 +2432,10 @@ function push_invalidated(resource) {
2388
2432
 
2389
2433
  /**
2390
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`.
2391
2439
  * @returns {Promise<void>}
2392
2440
  */
2393
2441
  export function invalidateAll() {
@@ -2400,18 +2448,17 @@ export function invalidateAll() {
2400
2448
  }
2401
2449
 
2402
2450
  /**
2403
- * 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.
2404
2452
  * Returns a `Promise` that resolves when the page is subsequently updated.
2405
- * @param {{ includeLoadFunctions?: boolean }} [options]
2406
2453
  * @returns {Promise<void>}
2407
2454
  */
2408
- export function refreshAll({ includeLoadFunctions = true } = {}) {
2455
+ export function refreshAll() {
2409
2456
  if (!BROWSER) {
2410
2457
  throw new Error('Cannot call refreshAll() on the server');
2411
2458
  }
2412
2459
 
2413
2460
  force_invalidation = true;
2414
- return _invalidate(includeLoadFunctions, false);
2461
+ return _invalidate(false);
2415
2462
  }
2416
2463
 
2417
2464
  /**
@@ -2438,7 +2485,22 @@ export async function preloadData(href) {
2438
2485
  throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
2439
2486
  }
2440
2487
 
2441
- 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
+
2442
2504
  if (result.type === 'redirect') {
2443
2505
  return {
2444
2506
  type: result.type,
@@ -2449,14 +2511,6 @@ export async function preloadData(href) {
2449
2511
 
2450
2512
  const { status, data } = result.props.page ?? page;
2451
2513
 
2452
- if (result.type === 'loaded' && result.state.error) {
2453
- return {
2454
- type: 'error',
2455
- status,
2456
- error: result.state.error
2457
- };
2458
- }
2459
-
2460
2514
  return { type: result.type, status, data };
2461
2515
  }
2462
2516
 
@@ -2544,9 +2598,6 @@ export function pushState(url, state) {
2544
2598
  has_navigated = true;
2545
2599
 
2546
2600
  page.state = state;
2547
- root.$set({
2548
- page
2549
- });
2550
2601
 
2551
2602
  clear_onward_history(current_history_index, current_navigation_index);
2552
2603
  }
@@ -2587,9 +2638,6 @@ export function replaceState(url, state) {
2587
2638
  history.replaceState(opts, '', resolve_url(url));
2588
2639
 
2589
2640
  page.state = state;
2590
- root.$set({
2591
- page
2592
- });
2593
2641
  }
2594
2642
 
2595
2643
  /**
@@ -2608,7 +2656,7 @@ export async function applyAction(result) {
2608
2656
  if (result.type === 'error') {
2609
2657
  await set_nearest_error_page(result.error);
2610
2658
  } else if (result.type === 'redirect') {
2611
- await _goto(result.location, { invalidateAll: true }, 0);
2659
+ await _goto(result.location, { refreshAll: true }, 0);
2612
2660
  } else {
2613
2661
  page.form = result.data;
2614
2662
  page.status = result.status;
@@ -2617,8 +2665,7 @@ export async function applyAction(result) {
2617
2665
  root.$set({
2618
2666
  // this brings Svelte's view of the world in line with SvelteKit's
2619
2667
  // after use:enhance reset the form....
2620
- form: null,
2621
- page
2668
+ form: null
2622
2669
  });
2623
2670
 
2624
2671
  // ...so that setting the `form` prop takes effect and isn't ignored
@@ -2626,7 +2673,7 @@ export async function applyAction(result) {
2626
2673
  root.$set({ form: result.data });
2627
2674
 
2628
2675
  if (result.type === 'success') {
2629
- reset_focus(page.url);
2676
+ reset_focus(/** @type {URL} */ (page.url));
2630
2677
  }
2631
2678
  }
2632
2679
  }
@@ -2654,6 +2701,7 @@ export async function set_nearest_error_page(error) {
2654
2701
  current = { ...navigation_result.state, nav: current.nav };
2655
2702
 
2656
2703
  root.$set(navigation_result.props);
2704
+ current_tree = navigation_result.props.tree;
2657
2705
  update(navigation_result.props.page);
2658
2706
 
2659
2707
  void svelte.tick().then(() => reset_focus(current.url));
@@ -3278,9 +3326,6 @@ function reset_focus(url, scroll = true) {
3278
3326
  const tabindex = root.getAttribute('tabindex');
3279
3327
 
3280
3328
  root.tabIndex = -1;
3281
- // TODO: remove this when we switch to TypeScript 6
3282
- // @ts-ignore options.focusVisible is only typed in TypeScript 6
3283
- // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#browser_compatibility
3284
3329
  root.focus({ preventScroll: true, focusVisible: false });
3285
3330
 
3286
3331
  // restore `tabindex` as to prevent `root` from stealing input from elements