@sveltejs/kit 3.0.0-next.7 → 3.0.0-next.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/core/config/options.js +1 -1
- package/src/core/env.js +1 -0
- package/src/core/sync/create_manifest_data/index.js +1 -1
- package/src/core/sync/sync.js +0 -2
- package/src/core/sync/write_client_manifest.js +0 -7
- package/src/core/sync/write_non_ambient.js +2 -2
- package/src/core/sync/write_server.js +0 -3
- package/src/core/sync/write_types/index.js +17 -14
- package/src/core/utils.js +2 -2
- package/src/exports/node/index.js +4 -8
- package/src/exports/public.d.ts +21 -14
- package/src/exports/vite/dev/index.js +3 -3
- package/src/exports/vite/index.js +64 -44
- package/src/exports/vite/preview/index.js +13 -14
- package/src/exports/vite/utils.js +5 -5
- package/src/runtime/app/env/internal.js +4 -4
- package/src/runtime/app/forms.js +2 -2
- package/src/runtime/app/paths/internal/client.js +4 -2
- package/src/runtime/app/paths/internal/server.js +2 -23
- package/src/runtime/app/paths/server.js +2 -2
- package/src/runtime/client/bundle.js +1 -1
- package/src/runtime/client/client-entry.js +3 -0
- package/src/runtime/client/client.js +216 -171
- package/src/runtime/client/entry.js +24 -3
- package/src/runtime/client/payload.js +17 -0
- package/src/runtime/client/remote-functions/form.svelte.js +6 -6
- package/src/runtime/client/types.d.ts +2 -6
- package/src/runtime/components/root.svelte +66 -0
- package/src/runtime/form-utils.js +1 -4
- package/src/runtime/server/cookie.js +17 -7
- package/src/runtime/server/page/index.js +7 -14
- package/src/runtime/server/page/render.js +71 -78
- package/src/runtime/server/remote.js +2 -1
- package/src/runtime/server/utils.js +28 -5
- package/src/runtime/types.d.ts +8 -0
- package/src/types/global-private.d.ts +10 -17
- package/src/types/internal.d.ts +21 -25
- package/src/utils/import.js +6 -1
- package/src/utils/routing.js +6 -6
- package/src/version.js +1 -1
- package/types/index.d.ts +34 -43
- package/types/index.d.ts.map +3 -2
- package/src/core/sync/write_root.js +0 -127
|
@@ -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 (
|
|
327
|
-
const { q = {}, p = {}, l = {}, f = {} } =
|
|
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,7 +424,7 @@ export async function start(_app, _target, hydrate) {
|
|
|
403
424
|
_start_router();
|
|
404
425
|
}
|
|
405
426
|
|
|
406
|
-
async function _invalidate(
|
|
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.
|
|
@@ -442,39 +463,36 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
442
463
|
}
|
|
443
464
|
}
|
|
444
465
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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;
|
|
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
|
|
560
|
+
// Clear preload cache when refreshAll is true to ensure fresh data
|
|
543
561
|
// after form submissions or explicit invalidations
|
|
544
|
-
if (options.
|
|
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.
|
|
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.
|
|
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 }).
|
|
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
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
801
|
-
|
|
814
|
+
page,
|
|
815
|
+
tree: /** @type {RenderNode} */ ({})
|
|
802
816
|
}
|
|
803
817
|
};
|
|
804
818
|
|
|
805
|
-
if (
|
|
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
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
2167
|
+
* @param {HTMLAnchorElement | SVGAElement} a
|
|
2132
2168
|
* @param {PreloadDataPriority} priority
|
|
2133
2169
|
*/
|
|
2134
|
-
async function preload(
|
|
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).
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
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.
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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, {
|
|
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
|