@sveltejs/kit 1.30.2 → 2.0.0
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 +24 -24
- package/src/core/adapt/builder.js +8 -1
- package/src/core/config/index.js +9 -1
- package/src/core/config/options.js +1 -12
- package/src/core/postbuild/analyse.js +98 -80
- package/src/core/postbuild/prerender.js +11 -9
- package/src/core/sync/sync.js +2 -0
- package/src/core/sync/write_non_ambient.js +42 -0
- package/src/core/sync/write_server.js +3 -3
- package/src/core/sync/write_tsconfig.js +27 -78
- package/src/core/sync/write_types/index.js +1 -1
- package/src/exports/hooks/sequence.js +1 -1
- package/src/exports/index.js +88 -71
- package/src/exports/node/index.js +21 -24
- package/src/exports/node/polyfills.js +5 -34
- package/src/exports/public.d.ts +82 -61
- package/src/exports/vite/dev/index.js +11 -19
- package/src/exports/vite/graph_analysis/index.js +2 -4
- package/src/exports/vite/index.js +73 -14
- package/src/exports/vite/module_ids.js +7 -0
- package/src/exports/vite/preview/index.js +56 -130
- package/src/runtime/app/forms.js +2 -35
- package/src/runtime/app/navigation.js +33 -18
- package/src/runtime/app/paths.js +2 -29
- package/src/runtime/client/client.js +449 -199
- package/src/runtime/client/constants.js +5 -1
- package/src/runtime/client/session-storage.js +7 -5
- package/src/runtime/client/singletons.js +7 -1
- package/src/runtime/client/types.d.ts +6 -2
- package/src/runtime/client/utils.js +12 -10
- package/src/runtime/control.js +16 -8
- package/src/runtime/server/cookie.js +38 -61
- package/src/runtime/server/data/index.js +6 -4
- package/src/runtime/server/env_module.js +29 -0
- package/src/runtime/server/fetch.js +7 -6
- package/src/runtime/server/index.js +23 -20
- package/src/runtime/server/page/actions.js +24 -15
- package/src/runtime/server/page/index.js +6 -8
- package/src/runtime/server/page/load_data.js +58 -40
- package/src/runtime/server/page/render.js +12 -7
- package/src/runtime/server/page/respond_with_error.js +4 -4
- package/src/runtime/server/page/types.d.ts +1 -1
- package/src/runtime/server/respond.js +14 -12
- package/src/runtime/server/utils.js +11 -8
- package/src/runtime/shared-server.js +19 -2
- package/src/types/ambient.d.ts +7 -1
- package/src/types/internal.d.ts +4 -1
- package/src/types/synthetic/$env+dynamic+private.md +2 -0
- package/src/types/synthetic/$env+dynamic+public.md +2 -0
- package/src/utils/error.js +17 -1
- package/src/utils/routing.js +47 -1
- package/src/utils/url.js +45 -27
- package/src/version.js +1 -1
- package/types/index.d.ts +171 -118
- package/types/index.d.ts.map +9 -5
- package/src/utils/platform.js +0 -1
- package/src/utils/promises.js +0 -61
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
add_data_suffix,
|
|
5
5
|
decode_params,
|
|
6
6
|
decode_pathname,
|
|
7
|
+
strip_hash,
|
|
7
8
|
make_trackable,
|
|
8
9
|
normalize_path
|
|
9
10
|
} from '../../utils/url.js';
|
|
@@ -18,43 +19,109 @@ import { parse } from './parse.js';
|
|
|
18
19
|
import * as storage from './session-storage.js';
|
|
19
20
|
import {
|
|
20
21
|
find_anchor,
|
|
21
|
-
|
|
22
|
+
resolve_url,
|
|
22
23
|
get_link_info,
|
|
23
24
|
get_router_options,
|
|
24
25
|
is_external_url,
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
origin,
|
|
27
|
+
scroll_state
|
|
27
28
|
} from './utils.js';
|
|
28
29
|
|
|
29
30
|
import { base } from '__sveltekit/paths';
|
|
30
31
|
import * as devalue from 'devalue';
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
HISTORY_INDEX,
|
|
34
|
+
NAVIGATION_INDEX,
|
|
35
|
+
PRELOAD_PRIORITIES,
|
|
36
|
+
SCROLL_KEY,
|
|
37
|
+
STATES_KEY,
|
|
38
|
+
SNAPSHOT_KEY,
|
|
39
|
+
PAGE_URL_KEY
|
|
40
|
+
} from './constants.js';
|
|
32
41
|
import { validate_page_exports } from '../../utils/exports.js';
|
|
33
|
-
import {
|
|
34
|
-
import { HttpError, Redirect,
|
|
42
|
+
import { compact } from '../../utils/array.js';
|
|
43
|
+
import { HttpError, Redirect, SvelteKitError } from '../control.js';
|
|
35
44
|
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, validate_depends } from '../shared.js';
|
|
36
|
-
import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
|
|
37
45
|
import { stores } from './singletons.js';
|
|
46
|
+
import { get_message, get_status } from '../../utils/error.js';
|
|
38
47
|
|
|
39
48
|
let errored = false;
|
|
40
49
|
|
|
50
|
+
/** @typedef {{ x: number; y: number }} ScrollPosition */
|
|
51
|
+
|
|
41
52
|
// We track the scroll position associated with each history entry in sessionStorage,
|
|
42
53
|
// rather than on history.state itself, because when navigation is driven by
|
|
43
54
|
// popstate it's too late to update the scroll position associated with the
|
|
44
55
|
// state we're navigating from
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
/**
|
|
57
|
+
* history index -> { x, y }
|
|
58
|
+
* @type {Record<number, ScrollPosition>}
|
|
59
|
+
*/
|
|
48
60
|
const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
49
61
|
|
|
50
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* history index -> any
|
|
64
|
+
* @type {Record<string, Record<string, any>>}
|
|
65
|
+
*/
|
|
66
|
+
const states = storage.get(STATES_KEY, devalue.parse) ?? {};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* navigation index -> any
|
|
70
|
+
* @type {Record<string, any[]>}
|
|
71
|
+
*/
|
|
51
72
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
52
73
|
|
|
74
|
+
const original_push_state = history.pushState;
|
|
75
|
+
const original_replace_state = history.replaceState;
|
|
76
|
+
|
|
77
|
+
if (DEV) {
|
|
78
|
+
let warned = false;
|
|
79
|
+
|
|
80
|
+
const warn = () => {
|
|
81
|
+
if (warned) return;
|
|
82
|
+
warned = true;
|
|
83
|
+
|
|
84
|
+
console.warn(
|
|
85
|
+
"Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use the `pushState` and `replaceState` imports from `$app/navigation` instead."
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
history.pushState = (...args) => {
|
|
90
|
+
warn();
|
|
91
|
+
return original_push_state.apply(history, args);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
history.replaceState = (...args) => {
|
|
95
|
+
warn();
|
|
96
|
+
return original_replace_state.apply(history, args);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
53
100
|
/** @param {number} index */
|
|
54
101
|
function update_scroll_positions(index) {
|
|
55
102
|
scroll_positions[index] = scroll_state();
|
|
56
103
|
}
|
|
57
104
|
|
|
105
|
+
/**
|
|
106
|
+
* @param {number} current_history_index
|
|
107
|
+
* @param {number} current_navigation_index
|
|
108
|
+
*/
|
|
109
|
+
function clear_onward_history(current_history_index, current_navigation_index) {
|
|
110
|
+
// if we navigated back, then pushed a new state, we can
|
|
111
|
+
// release memory by pruning the scroll/snapshot lookup
|
|
112
|
+
let i = current_history_index + 1;
|
|
113
|
+
while (scroll_positions[i]) {
|
|
114
|
+
delete scroll_positions[i];
|
|
115
|
+
i += 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
i = current_navigation_index + 1;
|
|
119
|
+
while (snapshots[i]) {
|
|
120
|
+
delete snapshots[i];
|
|
121
|
+
i += 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
58
125
|
/**
|
|
59
126
|
* Loads `href` the old-fashioned way, with a full page reload.
|
|
60
127
|
* Returns a `Promise` that never resolves (to prevent any
|
|
@@ -66,6 +133,8 @@ function native_navigation(url) {
|
|
|
66
133
|
return new Promise(() => {});
|
|
67
134
|
}
|
|
68
135
|
|
|
136
|
+
function noop() {}
|
|
137
|
+
|
|
69
138
|
/**
|
|
70
139
|
* @param {import('./types.js').SvelteKitApp} app
|
|
71
140
|
* @param {HTMLElement} target
|
|
@@ -83,6 +152,7 @@ export function create_client(app, target) {
|
|
|
83
152
|
default_error_loader();
|
|
84
153
|
|
|
85
154
|
const container = __SVELTEKIT_EMBEDDED__ ? target : document.documentElement;
|
|
155
|
+
|
|
86
156
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
87
157
|
const invalidated = [];
|
|
88
158
|
|
|
@@ -123,6 +193,8 @@ export function create_client(app, target) {
|
|
|
123
193
|
let updating = false;
|
|
124
194
|
let navigating = false;
|
|
125
195
|
let hash_navigating = false;
|
|
196
|
+
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
197
|
+
let has_navigated = false;
|
|
126
198
|
|
|
127
199
|
let force_invalidation = false;
|
|
128
200
|
|
|
@@ -130,16 +202,25 @@ export function create_client(app, target) {
|
|
|
130
202
|
let root;
|
|
131
203
|
|
|
132
204
|
// keeping track of the history index in order to prevent popstate navigation events if needed
|
|
133
|
-
|
|
205
|
+
/** @type {number} */
|
|
206
|
+
let current_history_index = history.state?.[HISTORY_INDEX];
|
|
207
|
+
|
|
208
|
+
/** @type {number} */
|
|
209
|
+
let current_navigation_index = history.state?.[NAVIGATION_INDEX];
|
|
134
210
|
|
|
135
211
|
if (!current_history_index) {
|
|
136
212
|
// we use Date.now() as an offset so that cross-document navigations
|
|
137
213
|
// within the app don't result in data loss
|
|
138
|
-
current_history_index = Date.now();
|
|
214
|
+
current_history_index = current_navigation_index = Date.now();
|
|
139
215
|
|
|
140
216
|
// create initial history entry, so we can return here
|
|
141
|
-
|
|
142
|
-
|
|
217
|
+
original_replace_state.call(
|
|
218
|
+
history,
|
|
219
|
+
{
|
|
220
|
+
...history.state,
|
|
221
|
+
[HISTORY_INDEX]: current_history_index,
|
|
222
|
+
[NAVIGATION_INDEX]: current_navigation_index
|
|
223
|
+
},
|
|
143
224
|
'',
|
|
144
225
|
location.href
|
|
145
226
|
);
|
|
@@ -166,13 +247,12 @@ export function create_client(app, target) {
|
|
|
166
247
|
// Accept all invalidations as they come, don't swallow any while another invalidation
|
|
167
248
|
// is running because subsequent invalidations may make earlier ones outdated,
|
|
168
249
|
// but batch multiple synchronous invalidations.
|
|
169
|
-
|
|
170
|
-
await pending_invalidate;
|
|
250
|
+
await (pending_invalidate ||= Promise.resolve());
|
|
171
251
|
if (!pending_invalidate) return;
|
|
172
252
|
pending_invalidate = null;
|
|
173
253
|
|
|
174
|
-
const
|
|
175
|
-
|
|
254
|
+
const intent = get_navigation_intent(current.url, true);
|
|
255
|
+
|
|
176
256
|
// Clear preload, it might be affected by the invalidation.
|
|
177
257
|
// Also solves an edge case where a preload is triggered, the navigation for it
|
|
178
258
|
// was then triggered and is still running while the invalidation kicks in,
|
|
@@ -185,7 +265,7 @@ export function create_client(app, target) {
|
|
|
185
265
|
|
|
186
266
|
if (navigation_result) {
|
|
187
267
|
if (navigation_result.type === 'redirect') {
|
|
188
|
-
|
|
268
|
+
await goto(new URL(navigation_result.location, current.url).href, {}, 1, nav_token);
|
|
189
269
|
} else {
|
|
190
270
|
if (navigation_result.props.page !== undefined) {
|
|
191
271
|
page = navigation_result.props.page;
|
|
@@ -193,6 +273,8 @@ export function create_client(app, target) {
|
|
|
193
273
|
root.$set(navigation_result.props);
|
|
194
274
|
}
|
|
195
275
|
}
|
|
276
|
+
|
|
277
|
+
invalidated.length = 0;
|
|
196
278
|
}
|
|
197
279
|
|
|
198
280
|
/** @param {number} index */
|
|
@@ -213,49 +295,31 @@ export function create_client(app, target) {
|
|
|
213
295
|
update_scroll_positions(current_history_index);
|
|
214
296
|
storage.set(SCROLL_KEY, scroll_positions);
|
|
215
297
|
|
|
216
|
-
capture_snapshot(
|
|
298
|
+
capture_snapshot(current_navigation_index);
|
|
217
299
|
storage.set(SNAPSHOT_KEY, snapshots);
|
|
300
|
+
storage.set(STATES_KEY, states, devalue.stringify);
|
|
218
301
|
}
|
|
219
302
|
|
|
220
303
|
/**
|
|
221
304
|
* @param {string | URL} url
|
|
222
|
-
* @param {{
|
|
305
|
+
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; }} options
|
|
223
306
|
* @param {number} redirect_count
|
|
224
307
|
* @param {{}} [nav_token]
|
|
225
308
|
*/
|
|
226
|
-
async function goto(
|
|
227
|
-
url,
|
|
228
|
-
{
|
|
229
|
-
noScroll = false,
|
|
230
|
-
replaceState = false,
|
|
231
|
-
keepFocus = false,
|
|
232
|
-
state = {},
|
|
233
|
-
invalidateAll = false
|
|
234
|
-
},
|
|
235
|
-
redirect_count,
|
|
236
|
-
nav_token
|
|
237
|
-
) {
|
|
238
|
-
if (typeof url === 'string') {
|
|
239
|
-
url = new URL(url, get_base_uri(document));
|
|
240
|
-
}
|
|
241
|
-
|
|
309
|
+
async function goto(url, options, redirect_count, nav_token) {
|
|
242
310
|
return navigate({
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
keepfocus: keepFocus,
|
|
311
|
+
type: 'goto',
|
|
312
|
+
url: resolve_url(url),
|
|
313
|
+
keepfocus: options.keepFocus,
|
|
314
|
+
noscroll: options.noScroll,
|
|
315
|
+
replace_state: options.replaceState,
|
|
246
316
|
redirect_count,
|
|
247
|
-
details: {
|
|
248
|
-
state,
|
|
249
|
-
replaceState
|
|
250
|
-
},
|
|
251
317
|
nav_token,
|
|
252
|
-
|
|
253
|
-
if (invalidateAll) {
|
|
318
|
+
accept: () => {
|
|
319
|
+
if (options.invalidateAll) {
|
|
254
320
|
force_invalidation = true;
|
|
255
321
|
}
|
|
256
|
-
}
|
|
257
|
-
blocked: () => {},
|
|
258
|
-
type: 'goto'
|
|
322
|
+
}
|
|
259
323
|
});
|
|
260
324
|
}
|
|
261
325
|
|
|
@@ -275,19 +339,13 @@ export function create_client(app, target) {
|
|
|
275
339
|
return load_cache.promise;
|
|
276
340
|
}
|
|
277
341
|
|
|
278
|
-
/** @param {
|
|
279
|
-
async function preload_code(
|
|
280
|
-
|
|
281
|
-
console.warn('Calling `preloadCode` with multiple arguments is deprecated');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const matching = routes.filter((route) => pathnames.some((pathname) => route.exec(pathname)));
|
|
285
|
-
|
|
286
|
-
const promises = matching.map((r) => {
|
|
287
|
-
return Promise.all([...r.layouts, r.leaf].map((load) => load?.[1]()));
|
|
288
|
-
});
|
|
342
|
+
/** @param {string} pathname */
|
|
343
|
+
async function preload_code(pathname) {
|
|
344
|
+
const route = routes.find((route) => route.exec(get_url_path(pathname)));
|
|
289
345
|
|
|
290
|
-
|
|
346
|
+
if (route) {
|
|
347
|
+
await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
|
|
348
|
+
}
|
|
291
349
|
}
|
|
292
350
|
|
|
293
351
|
/** @param {import('./types.js').NavigationFinished} result */
|
|
@@ -307,7 +365,7 @@ export function create_client(app, target) {
|
|
|
307
365
|
hydrate: true
|
|
308
366
|
});
|
|
309
367
|
|
|
310
|
-
restore_snapshot(
|
|
368
|
+
restore_snapshot(current_navigation_index);
|
|
311
369
|
|
|
312
370
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
313
371
|
const navigation = {
|
|
@@ -368,7 +426,8 @@ export function create_client(app, target) {
|
|
|
368
426
|
},
|
|
369
427
|
props: {
|
|
370
428
|
// @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
|
|
371
|
-
constructors: compact(branch).map((branch_node) => branch_node.node.component)
|
|
429
|
+
constructors: compact(branch).map((branch_node) => branch_node.node.component),
|
|
430
|
+
page
|
|
372
431
|
}
|
|
373
432
|
};
|
|
374
433
|
|
|
@@ -412,6 +471,7 @@ export function create_client(app, target) {
|
|
|
412
471
|
route: {
|
|
413
472
|
id: route?.id ?? null
|
|
414
473
|
},
|
|
474
|
+
state: {},
|
|
415
475
|
status,
|
|
416
476
|
url: new URL(url),
|
|
417
477
|
form: form ?? null,
|
|
@@ -441,13 +501,16 @@ export function create_client(app, target) {
|
|
|
441
501
|
/** @type {Record<string, any> | null} */
|
|
442
502
|
let data = null;
|
|
443
503
|
|
|
504
|
+
let is_tracking = true;
|
|
505
|
+
|
|
444
506
|
/** @type {import('types').Uses} */
|
|
445
507
|
const uses = {
|
|
446
508
|
dependencies: new Set(),
|
|
447
509
|
params: new Set(),
|
|
448
510
|
parent: false,
|
|
449
511
|
route: false,
|
|
450
|
-
url: false
|
|
512
|
+
url: false,
|
|
513
|
+
search_params: new Set()
|
|
451
514
|
};
|
|
452
515
|
|
|
453
516
|
const node = await loader();
|
|
@@ -471,20 +534,34 @@ export function create_client(app, target) {
|
|
|
471
534
|
const load_input = {
|
|
472
535
|
route: new Proxy(route, {
|
|
473
536
|
get: (target, key) => {
|
|
474
|
-
|
|
537
|
+
if (is_tracking) {
|
|
538
|
+
uses.route = true;
|
|
539
|
+
}
|
|
475
540
|
return target[/** @type {'id'} */ (key)];
|
|
476
541
|
}
|
|
477
542
|
}),
|
|
478
543
|
params: new Proxy(params, {
|
|
479
544
|
get: (target, key) => {
|
|
480
|
-
|
|
545
|
+
if (is_tracking) {
|
|
546
|
+
uses.params.add(/** @type {string} */ (key));
|
|
547
|
+
}
|
|
481
548
|
return target[/** @type {string} */ (key)];
|
|
482
549
|
}
|
|
483
550
|
}),
|
|
484
551
|
data: server_data_node?.data ?? null,
|
|
485
|
-
url: make_trackable(
|
|
486
|
-
|
|
487
|
-
|
|
552
|
+
url: make_trackable(
|
|
553
|
+
url,
|
|
554
|
+
() => {
|
|
555
|
+
if (is_tracking) {
|
|
556
|
+
uses.url = true;
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
(param) => {
|
|
560
|
+
if (is_tracking) {
|
|
561
|
+
uses.search_params.add(param);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
),
|
|
488
565
|
async fetch(resource, init) {
|
|
489
566
|
/** @type {URL | string} */
|
|
490
567
|
let requested;
|
|
@@ -520,7 +597,9 @@ export function create_client(app, target) {
|
|
|
520
597
|
|
|
521
598
|
// we must fixup relative urls so they are resolved from the target page
|
|
522
599
|
const resolved = new URL(requested, url);
|
|
523
|
-
|
|
600
|
+
if (is_tracking) {
|
|
601
|
+
depends(resolved.href);
|
|
602
|
+
}
|
|
524
603
|
|
|
525
604
|
// match ssr serialized data url, which is important to find cached responses
|
|
526
605
|
if (resolved.origin === url.origin) {
|
|
@@ -535,8 +614,18 @@ export function create_client(app, target) {
|
|
|
535
614
|
setHeaders: () => {}, // noop
|
|
536
615
|
depends,
|
|
537
616
|
parent() {
|
|
538
|
-
|
|
617
|
+
if (is_tracking) {
|
|
618
|
+
uses.parent = true;
|
|
619
|
+
}
|
|
539
620
|
return parent();
|
|
621
|
+
},
|
|
622
|
+
untrack(fn) {
|
|
623
|
+
is_tracking = false;
|
|
624
|
+
try {
|
|
625
|
+
return fn();
|
|
626
|
+
} finally {
|
|
627
|
+
is_tracking = true;
|
|
628
|
+
}
|
|
540
629
|
}
|
|
541
630
|
};
|
|
542
631
|
|
|
@@ -550,10 +639,10 @@ export function create_client(app, target) {
|
|
|
550
639
|
typeof data !== 'object'
|
|
551
640
|
? `a ${typeof data}`
|
|
552
641
|
: data instanceof Response
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
642
|
+
? 'a Response object'
|
|
643
|
+
: Array.isArray(data)
|
|
644
|
+
? 'an array'
|
|
645
|
+
: 'a non-plain object'
|
|
557
646
|
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
|
558
647
|
);
|
|
559
648
|
}
|
|
@@ -563,7 +652,6 @@ export function create_client(app, target) {
|
|
|
563
652
|
} else {
|
|
564
653
|
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
565
654
|
}
|
|
566
|
-
data = data ? await unwrap_promises(data, route.id) : null;
|
|
567
655
|
}
|
|
568
656
|
|
|
569
657
|
return {
|
|
@@ -585,10 +673,18 @@ export function create_client(app, target) {
|
|
|
585
673
|
* @param {boolean} parent_changed
|
|
586
674
|
* @param {boolean} route_changed
|
|
587
675
|
* @param {boolean} url_changed
|
|
676
|
+
* @param {Set<string>} search_params_changed
|
|
588
677
|
* @param {import('types').Uses | undefined} uses
|
|
589
678
|
* @param {Record<string, string>} params
|
|
590
679
|
*/
|
|
591
|
-
function has_changed(
|
|
680
|
+
function has_changed(
|
|
681
|
+
parent_changed,
|
|
682
|
+
route_changed,
|
|
683
|
+
url_changed,
|
|
684
|
+
search_params_changed,
|
|
685
|
+
uses,
|
|
686
|
+
params
|
|
687
|
+
) {
|
|
592
688
|
if (force_invalidation) return true;
|
|
593
689
|
|
|
594
690
|
if (!uses) return false;
|
|
@@ -597,6 +693,10 @@ export function create_client(app, target) {
|
|
|
597
693
|
if (uses.route && route_changed) return true;
|
|
598
694
|
if (uses.url && url_changed) return true;
|
|
599
695
|
|
|
696
|
+
for (const tracked_params of uses.search_params) {
|
|
697
|
+
if (search_params_changed.has(tracked_params)) return true;
|
|
698
|
+
}
|
|
699
|
+
|
|
600
700
|
for (const param of uses.params) {
|
|
601
701
|
if (params[param] !== current.params[param]) return true;
|
|
602
702
|
}
|
|
@@ -619,6 +719,31 @@ export function create_client(app, target) {
|
|
|
619
719
|
return null;
|
|
620
720
|
}
|
|
621
721
|
|
|
722
|
+
/**
|
|
723
|
+
*
|
|
724
|
+
* @param {URL | null} old_url
|
|
725
|
+
* @param {URL} new_url
|
|
726
|
+
*/
|
|
727
|
+
function diff_search_params(old_url, new_url) {
|
|
728
|
+
if (!old_url) return new Set(new_url.searchParams.keys());
|
|
729
|
+
|
|
730
|
+
const changed = new Set([...old_url.searchParams.keys(), ...new_url.searchParams.keys()]);
|
|
731
|
+
|
|
732
|
+
for (const key of changed) {
|
|
733
|
+
const old_values = old_url.searchParams.getAll(key);
|
|
734
|
+
const new_values = new_url.searchParams.getAll(key);
|
|
735
|
+
|
|
736
|
+
if (
|
|
737
|
+
old_values.every((value) => new_values.includes(value)) &&
|
|
738
|
+
new_values.every((value) => old_values.includes(value))
|
|
739
|
+
) {
|
|
740
|
+
changed.delete(key);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return changed;
|
|
745
|
+
}
|
|
746
|
+
|
|
622
747
|
/**
|
|
623
748
|
* @param {import('./types.js').NavigationIntent} intent
|
|
624
749
|
* @returns {Promise<import('./types.js').NavigationResult>}
|
|
@@ -640,9 +765,9 @@ export function create_client(app, target) {
|
|
|
640
765
|
|
|
641
766
|
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
642
767
|
let server_data = null;
|
|
643
|
-
|
|
644
768
|
const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
|
|
645
769
|
const route_changed = current.route ? route.id !== current.route.id : false;
|
|
770
|
+
const search_params_changed = diff_search_params(current.url, url);
|
|
646
771
|
|
|
647
772
|
let parent_invalid = false;
|
|
648
773
|
const invalid_server_nodes = loaders.map((loader, i) => {
|
|
@@ -651,7 +776,14 @@ export function create_client(app, target) {
|
|
|
651
776
|
const invalid =
|
|
652
777
|
!!loader?.[0] &&
|
|
653
778
|
(previous?.loader !== loader[1] ||
|
|
654
|
-
has_changed(
|
|
779
|
+
has_changed(
|
|
780
|
+
parent_invalid,
|
|
781
|
+
route_changed,
|
|
782
|
+
url_changed,
|
|
783
|
+
search_params_changed,
|
|
784
|
+
previous.server?.uses,
|
|
785
|
+
params
|
|
786
|
+
));
|
|
655
787
|
|
|
656
788
|
if (invalid) {
|
|
657
789
|
// For the next one
|
|
@@ -666,7 +798,7 @@ export function create_client(app, target) {
|
|
|
666
798
|
server_data = await load_data(url, invalid_server_nodes);
|
|
667
799
|
} catch (error) {
|
|
668
800
|
return load_root_error_page({
|
|
669
|
-
status: error
|
|
801
|
+
status: get_status(error),
|
|
670
802
|
error: await handle_error(error, { url, params, route: { id: route.id } }),
|
|
671
803
|
url,
|
|
672
804
|
route
|
|
@@ -694,7 +826,14 @@ export function create_client(app, target) {
|
|
|
694
826
|
const valid =
|
|
695
827
|
(!server_data_node || server_data_node.type === 'skip') &&
|
|
696
828
|
loader[1] === previous?.loader &&
|
|
697
|
-
!has_changed(
|
|
829
|
+
!has_changed(
|
|
830
|
+
parent_changed,
|
|
831
|
+
route_changed,
|
|
832
|
+
url_changed,
|
|
833
|
+
search_params_changed,
|
|
834
|
+
previous.universal?.uses,
|
|
835
|
+
params
|
|
836
|
+
);
|
|
698
837
|
if (valid) return previous;
|
|
699
838
|
|
|
700
839
|
parent_changed = true;
|
|
@@ -743,7 +882,7 @@ export function create_client(app, target) {
|
|
|
743
882
|
};
|
|
744
883
|
}
|
|
745
884
|
|
|
746
|
-
let status =
|
|
885
|
+
let status = get_status(err);
|
|
747
886
|
/** @type {App.Error} */
|
|
748
887
|
let error;
|
|
749
888
|
|
|
@@ -753,7 +892,6 @@ export function create_client(app, target) {
|
|
|
753
892
|
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
754
893
|
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
755
894
|
} else if (err instanceof HttpError) {
|
|
756
|
-
status = err.status;
|
|
757
895
|
error = err.body;
|
|
758
896
|
} else {
|
|
759
897
|
// Referenced node could have been removed due to redeploy, check
|
|
@@ -905,7 +1043,7 @@ export function create_client(app, target) {
|
|
|
905
1043
|
function get_navigation_intent(url, invalidating) {
|
|
906
1044
|
if (is_external_url(url, base)) return;
|
|
907
1045
|
|
|
908
|
-
const path = get_url_path(url);
|
|
1046
|
+
const path = get_url_path(url.pathname);
|
|
909
1047
|
|
|
910
1048
|
for (const route of routes) {
|
|
911
1049
|
const params = route.exec(path);
|
|
@@ -919,9 +1057,9 @@ export function create_client(app, target) {
|
|
|
919
1057
|
}
|
|
920
1058
|
}
|
|
921
1059
|
|
|
922
|
-
/** @param {
|
|
923
|
-
function get_url_path(
|
|
924
|
-
return decode_pathname(
|
|
1060
|
+
/** @param {string} pathname */
|
|
1061
|
+
function get_url_path(pathname) {
|
|
1062
|
+
return decode_pathname(pathname.slice(base.length) || '/');
|
|
925
1063
|
}
|
|
926
1064
|
|
|
927
1065
|
/**
|
|
@@ -959,45 +1097,47 @@ export function create_client(app, target) {
|
|
|
959
1097
|
|
|
960
1098
|
/**
|
|
961
1099
|
* @param {{
|
|
962
|
-
* url: URL;
|
|
963
|
-
* scroll: { x: number, y: number } | null;
|
|
964
|
-
* keepfocus: boolean;
|
|
965
|
-
* redirect_count: number;
|
|
966
|
-
* details: {
|
|
967
|
-
* replaceState: boolean;
|
|
968
|
-
* state: any;
|
|
969
|
-
* } | null;
|
|
970
1100
|
* type: import('@sveltejs/kit').Navigation["type"];
|
|
971
|
-
*
|
|
1101
|
+
* url: URL;
|
|
1102
|
+
* popped?: {
|
|
1103
|
+
* state: Record<string, any>;
|
|
1104
|
+
* scroll: { x: number, y: number };
|
|
1105
|
+
* delta: number;
|
|
1106
|
+
* };
|
|
1107
|
+
* keepfocus?: boolean;
|
|
1108
|
+
* noscroll?: boolean;
|
|
1109
|
+
* replace_state?: boolean;
|
|
1110
|
+
* redirect_count?: number;
|
|
972
1111
|
* nav_token?: {};
|
|
973
|
-
*
|
|
974
|
-
*
|
|
1112
|
+
* accept?: () => void;
|
|
1113
|
+
* block?: () => void;
|
|
975
1114
|
* }} opts
|
|
976
1115
|
*/
|
|
977
1116
|
async function navigate({
|
|
1117
|
+
type,
|
|
978
1118
|
url,
|
|
979
|
-
|
|
1119
|
+
popped,
|
|
980
1120
|
keepfocus,
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
delta,
|
|
1121
|
+
noscroll,
|
|
1122
|
+
replace_state,
|
|
1123
|
+
redirect_count = 0,
|
|
985
1124
|
nav_token = {},
|
|
986
|
-
|
|
987
|
-
|
|
1125
|
+
accept = noop,
|
|
1126
|
+
block = noop
|
|
988
1127
|
}) {
|
|
989
1128
|
const intent = get_navigation_intent(url, false);
|
|
990
|
-
const nav = before_navigate({ url, type, delta, intent });
|
|
1129
|
+
const nav = before_navigate({ url, type, delta: popped?.delta, intent });
|
|
991
1130
|
|
|
992
1131
|
if (!nav) {
|
|
993
|
-
|
|
1132
|
+
block();
|
|
994
1133
|
return;
|
|
995
1134
|
}
|
|
996
1135
|
|
|
997
|
-
// store this before calling `
|
|
1136
|
+
// store this before calling `accept()`, which may change the index
|
|
998
1137
|
const previous_history_index = current_history_index;
|
|
1138
|
+
const previous_navigation_index = current_navigation_index;
|
|
999
1139
|
|
|
1000
|
-
|
|
1140
|
+
accept();
|
|
1001
1141
|
|
|
1002
1142
|
navigating = true;
|
|
1003
1143
|
|
|
@@ -1015,7 +1155,7 @@ export function create_client(app, target) {
|
|
|
1015
1155
|
navigation_result = await server_fallback(
|
|
1016
1156
|
url,
|
|
1017
1157
|
{ id: null },
|
|
1018
|
-
await handle_error(new
|
|
1158
|
+
await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
|
|
1019
1159
|
url,
|
|
1020
1160
|
params: {},
|
|
1021
1161
|
route: { id: null }
|
|
@@ -1051,7 +1191,7 @@ export function create_client(app, target) {
|
|
|
1051
1191
|
goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
|
|
1052
1192
|
return false;
|
|
1053
1193
|
}
|
|
1054
|
-
} else if (/** @type {number} */ (navigation_result.props.page
|
|
1194
|
+
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1055
1195
|
const updated = await stores.updated.check();
|
|
1056
1196
|
if (updated) {
|
|
1057
1197
|
await native_navigation(url);
|
|
@@ -1066,36 +1206,39 @@ export function create_client(app, target) {
|
|
|
1066
1206
|
updating = true;
|
|
1067
1207
|
|
|
1068
1208
|
update_scroll_positions(previous_history_index);
|
|
1069
|
-
capture_snapshot(
|
|
1209
|
+
capture_snapshot(previous_navigation_index);
|
|
1070
1210
|
|
|
1071
1211
|
// ensure the url pathname matches the page's trailing slash option
|
|
1072
|
-
if (
|
|
1073
|
-
navigation_result.props.page
|
|
1074
|
-
navigation_result.props.page.url.pathname !== url.pathname
|
|
1075
|
-
) {
|
|
1076
|
-
url.pathname = navigation_result.props.page?.url.pathname;
|
|
1212
|
+
if (navigation_result.props.page.url.pathname !== url.pathname) {
|
|
1213
|
+
url.pathname = navigation_result.props.page.url.pathname;
|
|
1077
1214
|
}
|
|
1078
1215
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1216
|
+
const state = popped ? popped.state : {};
|
|
1217
|
+
|
|
1218
|
+
if (!popped) {
|
|
1219
|
+
// this is a new navigation, rather than a popstate
|
|
1220
|
+
const change = replace_state ? 0 : 1;
|
|
1221
|
+
|
|
1222
|
+
const entry = {
|
|
1223
|
+
[HISTORY_INDEX]: (current_history_index += change),
|
|
1224
|
+
[NAVIGATION_INDEX]: (current_navigation_index += change)
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
const fn = replace_state ? original_replace_state : original_push_state;
|
|
1228
|
+
fn.call(history, entry, '', url);
|
|
1229
|
+
|
|
1230
|
+
if (!replace_state) {
|
|
1231
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
1093
1232
|
}
|
|
1094
1233
|
}
|
|
1095
1234
|
|
|
1235
|
+
states[current_history_index] = state;
|
|
1236
|
+
|
|
1096
1237
|
// reset preload synchronously after the history state has been set to avoid race conditions
|
|
1097
1238
|
load_cache = null;
|
|
1098
1239
|
|
|
1240
|
+
navigation_result.props.page.state = state;
|
|
1241
|
+
|
|
1099
1242
|
if (started) {
|
|
1100
1243
|
current = navigation_result.state;
|
|
1101
1244
|
|
|
@@ -1127,6 +1270,7 @@ export function create_client(app, target) {
|
|
|
1127
1270
|
}
|
|
1128
1271
|
|
|
1129
1272
|
root.$set(navigation_result.props);
|
|
1273
|
+
has_navigated = true;
|
|
1130
1274
|
} else {
|
|
1131
1275
|
initialize(navigation_result);
|
|
1132
1276
|
}
|
|
@@ -1137,6 +1281,8 @@ export function create_client(app, target) {
|
|
|
1137
1281
|
await tick();
|
|
1138
1282
|
|
|
1139
1283
|
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
|
|
1284
|
+
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
|
|
1285
|
+
|
|
1140
1286
|
if (autoscroll) {
|
|
1141
1287
|
const deep_linked =
|
|
1142
1288
|
url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
|
|
@@ -1172,7 +1318,7 @@ export function create_client(app, target) {
|
|
|
1172
1318
|
navigating = false;
|
|
1173
1319
|
|
|
1174
1320
|
if (type === 'popstate') {
|
|
1175
|
-
restore_snapshot(
|
|
1321
|
+
restore_snapshot(current_navigation_index);
|
|
1176
1322
|
}
|
|
1177
1323
|
|
|
1178
1324
|
nav.fulfil(undefined);
|
|
@@ -1247,9 +1393,7 @@ export function create_client(app, target) {
|
|
|
1247
1393
|
(entries) => {
|
|
1248
1394
|
for (const entry of entries) {
|
|
1249
1395
|
if (entry.isIntersecting) {
|
|
1250
|
-
preload_code(
|
|
1251
|
-
get_url_path(new URL(/** @type {HTMLAnchorElement} */ (entry.target).href))
|
|
1252
|
-
);
|
|
1396
|
+
preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
|
|
1253
1397
|
observer.unobserve(entry.target);
|
|
1254
1398
|
}
|
|
1255
1399
|
}
|
|
@@ -1290,7 +1434,7 @@ export function create_client(app, target) {
|
|
|
1290
1434
|
}
|
|
1291
1435
|
}
|
|
1292
1436
|
} else if (priority <= options.preload_code) {
|
|
1293
|
-
preload_code(
|
|
1437
|
+
preload_code(/** @type {URL} */ (url).pathname);
|
|
1294
1438
|
}
|
|
1295
1439
|
}
|
|
1296
1440
|
}
|
|
@@ -1310,7 +1454,7 @@ export function create_client(app, target) {
|
|
|
1310
1454
|
}
|
|
1311
1455
|
|
|
1312
1456
|
if (options.preload_code === PRELOAD_PRIORITIES.eager) {
|
|
1313
|
-
preload_code(
|
|
1457
|
+
preload_code(/** @type {URL} */ (url).pathname);
|
|
1314
1458
|
}
|
|
1315
1459
|
}
|
|
1316
1460
|
}
|
|
@@ -1334,12 +1478,11 @@ export function create_client(app, target) {
|
|
|
1334
1478
|
console.warn('The next HMR update will cause the page to reload');
|
|
1335
1479
|
}
|
|
1336
1480
|
|
|
1481
|
+
const status = get_status(error);
|
|
1482
|
+
const message = get_message(error);
|
|
1483
|
+
|
|
1337
1484
|
return (
|
|
1338
|
-
app.hooks.handleError({ error, event }) ??
|
|
1339
|
-
/** @type {any} */ ({
|
|
1340
|
-
message:
|
|
1341
|
-
event.route.id === null && error instanceof NotFound ? 'Not Found' : 'Internal Error'
|
|
1342
|
-
})
|
|
1485
|
+
app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
|
|
1343
1486
|
);
|
|
1344
1487
|
}
|
|
1345
1488
|
|
|
@@ -1387,8 +1530,28 @@ export function create_client(app, target) {
|
|
|
1387
1530
|
}
|
|
1388
1531
|
},
|
|
1389
1532
|
|
|
1390
|
-
goto: (
|
|
1391
|
-
|
|
1533
|
+
goto: (url, opts = {}) => {
|
|
1534
|
+
url = resolve_url(url);
|
|
1535
|
+
|
|
1536
|
+
// @ts-expect-error
|
|
1537
|
+
if (DEV && opts.state) {
|
|
1538
|
+
// TOOD 3.0 remove
|
|
1539
|
+
throw new Error(
|
|
1540
|
+
'Passing `state` to `goto` is no longer supported. Use `pushState` and `replaceState` from `$app/navigation` instead.'
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (url.origin !== origin) {
|
|
1545
|
+
return Promise.reject(
|
|
1546
|
+
new Error(
|
|
1547
|
+
DEV
|
|
1548
|
+
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
1549
|
+
: 'goto: invalid URL'
|
|
1550
|
+
)
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
return goto(url, opts, 0);
|
|
1392
1555
|
},
|
|
1393
1556
|
|
|
1394
1557
|
invalidate: (resource) => {
|
|
@@ -1408,17 +1571,89 @@ export function create_client(app, target) {
|
|
|
1408
1571
|
},
|
|
1409
1572
|
|
|
1410
1573
|
preload_data: async (href) => {
|
|
1411
|
-
const url =
|
|
1574
|
+
const url = resolve_url(href);
|
|
1412
1575
|
const intent = get_navigation_intent(url, false);
|
|
1413
1576
|
|
|
1414
1577
|
if (!intent) {
|
|
1415
1578
|
throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
|
|
1416
1579
|
}
|
|
1417
1580
|
|
|
1418
|
-
await preload_data(intent);
|
|
1581
|
+
const result = await preload_data(intent);
|
|
1582
|
+
if (result.type === 'redirect') {
|
|
1583
|
+
return {
|
|
1584
|
+
type: result.type,
|
|
1585
|
+
location: result.location
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
const { status, data } = result.props.page ?? page;
|
|
1590
|
+
return { type: result.type, status, data };
|
|
1419
1591
|
},
|
|
1420
1592
|
|
|
1421
|
-
preload_code
|
|
1593
|
+
preload_code: (pathname) => {
|
|
1594
|
+
if (DEV) {
|
|
1595
|
+
if (!pathname.startsWith(base)) {
|
|
1596
|
+
throw new Error(
|
|
1597
|
+
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
if (!routes.find((route) => route.exec(get_url_path(pathname)))) {
|
|
1602
|
+
throw new Error(`'${pathname}' did not match any routes`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
return preload_code(pathname);
|
|
1607
|
+
},
|
|
1608
|
+
|
|
1609
|
+
push_state: (url, state) => {
|
|
1610
|
+
if (DEV) {
|
|
1611
|
+
try {
|
|
1612
|
+
devalue.stringify(state);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
// @ts-expect-error
|
|
1615
|
+
throw new Error(`Could not serialize state${error.path}`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
const opts = {
|
|
1620
|
+
[HISTORY_INDEX]: (current_history_index += 1),
|
|
1621
|
+
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1622
|
+
[PAGE_URL_KEY]: page.url.href
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
original_push_state.call(history, opts, '', resolve_url(url));
|
|
1626
|
+
|
|
1627
|
+
page = { ...page, state };
|
|
1628
|
+
root.$set({ page });
|
|
1629
|
+
|
|
1630
|
+
states[current_history_index] = state;
|
|
1631
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
1632
|
+
},
|
|
1633
|
+
|
|
1634
|
+
replace_state: (url, state) => {
|
|
1635
|
+
if (DEV) {
|
|
1636
|
+
try {
|
|
1637
|
+
devalue.stringify(state);
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
// @ts-expect-error
|
|
1640
|
+
throw new Error(`Could not serialize state${error.path}`);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
const opts = {
|
|
1645
|
+
[HISTORY_INDEX]: current_history_index,
|
|
1646
|
+
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1647
|
+
[PAGE_URL_KEY]: page.url.href
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
original_replace_state.call(history, opts, '', resolve_url(url));
|
|
1651
|
+
|
|
1652
|
+
page = { ...page, state };
|
|
1653
|
+
root.$set({ page });
|
|
1654
|
+
|
|
1655
|
+
states[current_history_index] = state;
|
|
1656
|
+
},
|
|
1422
1657
|
|
|
1423
1658
|
apply_action: async (result) => {
|
|
1424
1659
|
if (result.type === 'error') {
|
|
@@ -1575,7 +1810,7 @@ export function create_client(app, target) {
|
|
|
1575
1810
|
// This will ensure the `hashchange` event is fired
|
|
1576
1811
|
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1577
1812
|
const [nonhash, hash] = url.href.split('#');
|
|
1578
|
-
if (hash !== undefined && nonhash === location
|
|
1813
|
+
if (hash !== undefined && nonhash === strip_hash(location)) {
|
|
1579
1814
|
// If we are trying to navigate to the same hash, we should only
|
|
1580
1815
|
// attempt to scroll to that element and avoid any history changes.
|
|
1581
1816
|
// Otherwise, this can cause Firefox to incorrectly assign a null
|
|
@@ -1597,21 +1832,16 @@ export function create_client(app, target) {
|
|
|
1597
1832
|
|
|
1598
1833
|
// hashchange event shouldn't occur if the router is replacing state.
|
|
1599
1834
|
hash_navigating = false;
|
|
1600
|
-
event.preventDefault();
|
|
1601
1835
|
}
|
|
1602
1836
|
|
|
1837
|
+
event.preventDefault();
|
|
1838
|
+
|
|
1603
1839
|
navigate({
|
|
1840
|
+
type: 'link',
|
|
1604
1841
|
url,
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
details: {
|
|
1609
|
-
state: {},
|
|
1610
|
-
replaceState: options.replace_state ?? url.href === location.href
|
|
1611
|
-
},
|
|
1612
|
-
accepted: () => event.preventDefault(),
|
|
1613
|
-
blocked: () => event.preventDefault(),
|
|
1614
|
-
type: 'link'
|
|
1842
|
+
keepfocus: options.keepfocus,
|
|
1843
|
+
noscroll: options.noscroll,
|
|
1844
|
+
replace_state: options.replace_state ?? url.href === location.href
|
|
1615
1845
|
});
|
|
1616
1846
|
});
|
|
1617
1847
|
|
|
@@ -1638,8 +1868,8 @@ export function create_client(app, target) {
|
|
|
1638
1868
|
|
|
1639
1869
|
const event_form = /** @type {HTMLFormElement} */ (event.target);
|
|
1640
1870
|
|
|
1641
|
-
const
|
|
1642
|
-
if (reload) return;
|
|
1871
|
+
const options = get_router_options(event_form);
|
|
1872
|
+
if (options.reload) return;
|
|
1643
1873
|
|
|
1644
1874
|
event.preventDefault();
|
|
1645
1875
|
event.stopPropagation();
|
|
@@ -1655,57 +1885,67 @@ export function create_client(app, target) {
|
|
|
1655
1885
|
url.search = new URLSearchParams(data).toString();
|
|
1656
1886
|
|
|
1657
1887
|
navigate({
|
|
1888
|
+
type: 'form',
|
|
1658
1889
|
url,
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
details: {
|
|
1663
|
-
state: {},
|
|
1664
|
-
replaceState: replace_state ?? url.href === location.href
|
|
1665
|
-
},
|
|
1666
|
-
nav_token: {},
|
|
1667
|
-
accepted: () => {},
|
|
1668
|
-
blocked: () => {},
|
|
1669
|
-
type: 'form'
|
|
1890
|
+
keepfocus: options.keepfocus,
|
|
1891
|
+
noscroll: options.noscroll,
|
|
1892
|
+
replace_state: options.replace_state ?? url.href === location.href
|
|
1670
1893
|
});
|
|
1671
1894
|
});
|
|
1672
1895
|
|
|
1673
1896
|
addEventListener('popstate', async (event) => {
|
|
1674
|
-
|
|
1675
|
-
|
|
1897
|
+
if (event.state?.[HISTORY_INDEX]) {
|
|
1898
|
+
const history_index = event.state[HISTORY_INDEX];
|
|
1899
|
+
token = {};
|
|
1900
|
+
|
|
1676
1901
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
1677
1902
|
// with history.go, which means we end up back here, hence this check
|
|
1678
|
-
if (
|
|
1679
|
-
|
|
1680
|
-
const scroll = scroll_positions[
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1903
|
+
if (history_index === current_history_index) return;
|
|
1904
|
+
|
|
1905
|
+
const scroll = scroll_positions[history_index];
|
|
1906
|
+
const state = states[history_index] ?? {};
|
|
1907
|
+
const url = new URL(event.state[PAGE_URL_KEY] ?? location.href);
|
|
1908
|
+
const navigation_index = event.state[NAVIGATION_INDEX];
|
|
1909
|
+
const is_hash_change = strip_hash(location) === strip_hash(current.url);
|
|
1910
|
+
const shallow =
|
|
1911
|
+
navigation_index === current_navigation_index && (has_navigated || is_hash_change);
|
|
1912
|
+
|
|
1913
|
+
if (shallow) {
|
|
1914
|
+
// We don't need to navigate, we just need to update scroll and/or state.
|
|
1915
|
+
// This happens with hash links and `pushState`/`replaceState`. The
|
|
1916
|
+
// exception is if we haven't navigated yet, since we could have
|
|
1917
|
+
// got here after a modal navigation then a reload
|
|
1686
1918
|
update_url(url);
|
|
1919
|
+
|
|
1687
1920
|
scroll_positions[current_history_index] = scroll_state();
|
|
1688
|
-
|
|
1689
|
-
|
|
1921
|
+
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
1922
|
+
|
|
1923
|
+
if (state !== page.state) {
|
|
1924
|
+
page = { ...page, state };
|
|
1925
|
+
root.$set({ page });
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
current_history_index = history_index;
|
|
1690
1929
|
return;
|
|
1691
1930
|
}
|
|
1692
1931
|
|
|
1693
|
-
const delta =
|
|
1932
|
+
const delta = history_index - current_history_index;
|
|
1694
1933
|
|
|
1695
1934
|
await navigate({
|
|
1935
|
+
type: 'popstate',
|
|
1696
1936
|
url,
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1937
|
+
popped: {
|
|
1938
|
+
state,
|
|
1939
|
+
scroll,
|
|
1940
|
+
delta
|
|
1941
|
+
},
|
|
1942
|
+
accept: () => {
|
|
1943
|
+
current_history_index = history_index;
|
|
1944
|
+
current_navigation_index = navigation_index;
|
|
1703
1945
|
},
|
|
1704
|
-
|
|
1946
|
+
block: () => {
|
|
1705
1947
|
history.go(-delta);
|
|
1706
1948
|
},
|
|
1707
|
-
type: 'popstate',
|
|
1708
|
-
delta,
|
|
1709
1949
|
nav_token: token
|
|
1710
1950
|
});
|
|
1711
1951
|
} else {
|
|
@@ -1724,8 +1964,13 @@ export function create_client(app, target) {
|
|
|
1724
1964
|
// we need to update history, otherwise we have to leave it alone
|
|
1725
1965
|
if (hash_navigating) {
|
|
1726
1966
|
hash_navigating = false;
|
|
1727
|
-
|
|
1728
|
-
|
|
1967
|
+
original_replace_state.call(
|
|
1968
|
+
history,
|
|
1969
|
+
{
|
|
1970
|
+
...history.state,
|
|
1971
|
+
[HISTORY_INDEX]: ++current_history_index,
|
|
1972
|
+
[NAVIGATION_INDEX]: current_navigation_index
|
|
1973
|
+
},
|
|
1729
1974
|
'',
|
|
1730
1975
|
location.href
|
|
1731
1976
|
);
|
|
@@ -1839,13 +2084,17 @@ export function create_client(app, target) {
|
|
|
1839
2084
|
}
|
|
1840
2085
|
|
|
1841
2086
|
result = await load_root_error_page({
|
|
1842
|
-
status: error
|
|
2087
|
+
status: get_status(error),
|
|
1843
2088
|
error: await handle_error(error, { url, params, route }),
|
|
1844
2089
|
url,
|
|
1845
2090
|
route
|
|
1846
2091
|
});
|
|
1847
2092
|
}
|
|
1848
2093
|
|
|
2094
|
+
if (result.props.page) {
|
|
2095
|
+
result.props.page.state = {};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
1849
2098
|
initialize(result);
|
|
1850
2099
|
}
|
|
1851
2100
|
};
|
|
@@ -1966,7 +2215,8 @@ function deserialize_uses(uses) {
|
|
|
1966
2215
|
params: new Set(uses?.params ?? []),
|
|
1967
2216
|
parent: !!uses?.parent,
|
|
1968
2217
|
route: !!uses?.route,
|
|
1969
|
-
url: !!uses?.url
|
|
2218
|
+
url: !!uses?.url,
|
|
2219
|
+
search_params: new Set(uses?.search_params ?? [])
|
|
1970
2220
|
};
|
|
1971
2221
|
}
|
|
1972
2222
|
|