@sveltejs/kit 1.0.0-next.275 → 1.0.0-next.279

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.
@@ -45,8 +45,8 @@ async function invalidate_(resource) {
45
45
  /**
46
46
  * @type {import('$app/navigation').prefetch}
47
47
  */
48
- function prefetch_(href) {
49
- return router.prefetch(new URL(href, get_base_uri(document)));
48
+ async function prefetch_(href) {
49
+ await router.prefetch(new URL(href, get_base_uri(document)));
50
50
  }
51
51
 
52
52
  /**
@@ -22,6 +22,21 @@ function normalize_path(path, trailing_slash) {
22
22
  return path;
23
23
  }
24
24
 
25
+ // We track the scroll position associated with each history entry in sessionStorage,
26
+ // rather than on history.state itself, because when navigation is driven by
27
+ // popstate it's too late to update the scroll position associated with the
28
+ // state we're navigating from
29
+ const SCROLL_KEY = 'sveltekit:scroll';
30
+
31
+ /** @typedef {{ x: number, y: number }} ScrollPosition */
32
+ /** @type {Record<number, ScrollPosition>} */
33
+ let scroll_positions = {};
34
+ try {
35
+ scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
36
+ } catch {
37
+ // do nothing
38
+ }
39
+
25
40
  function scroll_state() {
26
41
  return {
27
42
  x: pageXOffset,
@@ -54,8 +69,8 @@ class Router {
54
69
  /**
55
70
  * @param {{
56
71
  * base: string;
57
- * routes: import('types/internal').CSRRoute[];
58
- * trailing_slash: import('types/internal').TrailingSlash;
72
+ * routes: import('types').CSRRoute[];
73
+ * trailing_slash: import('types').TrailingSlash;
59
74
  * renderer: import('./renderer').Renderer
60
75
  * }} opts
61
76
  */
@@ -71,6 +86,7 @@ class Router {
71
86
  renderer.router = this;
72
87
 
73
88
  this.enabled = true;
89
+ this.initialized = false;
74
90
 
75
91
  // make it possible to reset focus
76
92
  document.body.setAttribute('tabindex', '-1');
@@ -83,6 +99,11 @@ class Router {
83
99
  history.replaceState({ ...history.state, 'sveltekit:index': 0 }, '', location.href);
84
100
  }
85
101
 
102
+ // if we reload the page, or Cmd-Shift-T back to it,
103
+ // recover scroll position
104
+ const scroll = scroll_positions[this.current_history_index];
105
+ if (scroll) scrollTo(scroll.x, scroll.y);
106
+
86
107
  this.hash_navigating = false;
87
108
 
88
109
  this.callbacks = {
@@ -95,9 +116,7 @@ class Router {
95
116
  }
96
117
 
97
118
  init_listeners() {
98
- if ('scrollRestoration' in history) {
99
- history.scrollRestoration = 'manual';
100
- }
119
+ history.scrollRestoration = 'manual';
101
120
 
102
121
  // Adopted from Nuxt.js
103
122
  // Reset scrollRestoration to auto when leaving page, allowing page reload
@@ -122,28 +141,16 @@ class Router {
122
141
  }
123
142
  });
124
143
 
125
- // Setting scrollRestoration to manual again when returning to this page.
126
- addEventListener('load', () => {
127
- history.scrollRestoration = 'manual';
128
- });
129
-
130
- // There's no API to capture the scroll location right before the user
131
- // hits the back/forward button, so we listen for scroll events
144
+ addEventListener('visibilitychange', () => {
145
+ if (document.visibilityState === 'hidden') {
146
+ this.#update_scroll_positions();
132
147
 
133
- /** @type {NodeJS.Timeout} */
134
- let scroll_timer;
135
- addEventListener('scroll', () => {
136
- clearTimeout(scroll_timer);
137
- scroll_timer = setTimeout(() => {
138
- // Store the scroll location in the history
139
- // This will persist even if we navigate away from the site and come back
140
- const new_state = {
141
- ...(history.state || {}),
142
- 'sveltekit:scroll': scroll_state()
143
- };
144
- history.replaceState(new_state, document.title, window.location.href);
145
- // iOS scroll event intervals happen between 30-150ms, sometimes around 200ms
146
- }, 200);
148
+ try {
149
+ sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
150
+ } catch {
151
+ // do nothing
152
+ }
153
+ }
147
154
  });
148
155
 
149
156
  /** @param {Event} event */
@@ -216,10 +223,9 @@ class Router {
216
223
  // clicking a hash link and those triggered by popstate
217
224
  this.hash_navigating = true;
218
225
 
219
- const info = this.parse(url);
220
- if (info) {
221
- return this.renderer.update(info, [], false);
222
- }
226
+ this.#update_scroll_positions();
227
+ this.renderer.update_page_store(new URL(url.href));
228
+
223
229
  return;
224
230
  }
225
231
 
@@ -245,7 +251,7 @@ class Router {
245
251
 
246
252
  this._navigate({
247
253
  url: new URL(location.href),
248
- scroll: event.state['sveltekit:scroll'],
254
+ scroll: scroll_positions[event.state['sveltekit:index']],
249
255
  keepfocus: false,
250
256
  chain: [],
251
257
  details: null,
@@ -272,6 +278,12 @@ class Router {
272
278
  );
273
279
  }
274
280
  });
281
+
282
+ this.initialized = true;
283
+ }
284
+
285
+ #update_scroll_positions() {
286
+ scroll_positions[this.current_history_index] = scroll_state();
275
287
  }
276
288
 
277
289
  /**
@@ -294,7 +306,8 @@ class Router {
294
306
  id: url.pathname + url.search,
295
307
  routes: this.routes.filter(([pattern]) => pattern.test(path)),
296
308
  url,
297
- path
309
+ path,
310
+ initial: !this.initialized
298
311
  };
299
312
  }
300
313
  }
@@ -344,7 +357,7 @@ class Router {
344
357
 
345
358
  /**
346
359
  * @param {URL} url
347
- * @returns {Promise<import('./types').NavigationResult>}
360
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
348
361
  */
349
362
  async prefetch(url) {
350
363
  const info = this.parse(url);
@@ -421,6 +434,8 @@ class Router {
421
434
  });
422
435
  }
423
436
 
437
+ this.#update_scroll_positions();
438
+
424
439
  accepted();
425
440
 
426
441
  if (!this.navigating) {
@@ -466,7 +481,7 @@ function coalesce_to_error(err) {
466
481
 
467
482
  /**
468
483
  * Hash using djb2
469
- * @param {import('types/hooks').StrictBody} value
484
+ * @param {import('types').StrictBody} value
470
485
  */
471
486
  function hash(value) {
472
487
  let hash = 5381;
@@ -482,8 +497,8 @@ function hash(value) {
482
497
  }
483
498
 
484
499
  /**
485
- * @param {import('types/page').LoadOutput} loaded
486
- * @returns {import('types/internal').NormalizedLoadOutput}
500
+ * @param {import('types').LoadOutput} loaded
501
+ * @returns {import('types').NormalizedLoadOutput}
487
502
  */
488
503
  function normalize(loaded) {
489
504
  const has_error_status =
@@ -543,11 +558,11 @@ function normalize(loaded) {
543
558
  );
544
559
  }
545
560
 
546
- return /** @type {import('types/internal').NormalizedLoadOutput} */ (loaded);
561
+ return /** @type {import('types').NormalizedLoadOutput} */ (loaded);
547
562
  }
548
563
 
549
564
  /**
550
- * @typedef {import('types/internal').CSRComponent} CSRComponent
565
+ * @typedef {import('types').CSRComponent} CSRComponent
551
566
  * @typedef {{ from: URL; to: URL }} Navigating
552
567
  */
553
568
 
@@ -690,7 +705,7 @@ class Renderer {
690
705
  /** @type {Map<string, import('./types').NavigationResult>} */
691
706
  this.cache = new Map();
692
707
 
693
- /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult> | null}} */
708
+ /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
694
709
  this.loading = {
695
710
  id: null,
696
711
  promise: null
@@ -857,6 +872,11 @@ class Renderer {
857
872
  const token = (this.token = {});
858
873
  let navigation_result = await this._get_navigation_result(info, no_cache);
859
874
 
875
+ if (!navigation_result) {
876
+ location.href = info.url.href;
877
+ return;
878
+ }
879
+
860
880
  // abort if user navigated during update
861
881
  if (token !== this.token) return;
862
882
 
@@ -936,6 +956,10 @@ class Renderer {
936
956
  this.autoscroll = true;
937
957
  this.updating = false;
938
958
 
959
+ if (navigation_result.props.page) {
960
+ this.page = navigation_result.props.page;
961
+ }
962
+
939
963
  if (!this.router) return;
940
964
 
941
965
  const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
@@ -948,7 +972,7 @@ class Renderer {
948
972
 
949
973
  /**
950
974
  * @param {import('./types').NavigationInfo} info
951
- * @returns {Promise<import('./types').NavigationResult>}
975
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
952
976
  */
953
977
  load(info) {
954
978
  this.loading.promise = this._get_navigation_result(info, false);
@@ -973,6 +997,12 @@ class Renderer {
973
997
  return this.invalidating;
974
998
  }
975
999
 
1000
+ /** @param {URL} url */
1001
+ update_page_store(url) {
1002
+ this.stores.page.set({ ...this.page, url });
1003
+ this.stores.page.notify();
1004
+ }
1005
+
976
1006
  /** @param {import('./types').NavigationResult} result */
977
1007
  _init(result) {
978
1008
  this.current = result.state;
@@ -980,6 +1010,8 @@ class Renderer {
980
1010
  const style = document.querySelector('style[data-svelte]');
981
1011
  if (style) style.remove();
982
1012
 
1013
+ this.page = result.props.page;
1014
+
983
1015
  this.root = new this.Root({
984
1016
  target: this.target,
985
1017
  props: {
@@ -1000,7 +1032,7 @@ class Renderer {
1000
1032
  /**
1001
1033
  * @param {import('./types').NavigationInfo} info
1002
1034
  * @param {boolean} no_cache
1003
- * @returns {Promise<import('./types').NavigationResult>}
1035
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
1004
1036
  */
1005
1037
  async _get_navigation_result(info, no_cache) {
1006
1038
  if (this.loading.id === info.id && this.loading.promise) {
@@ -1033,11 +1065,13 @@ class Renderer {
1033
1065
  if (result) return result;
1034
1066
  }
1035
1067
 
1036
- return await this._load_error({
1037
- status: 404,
1038
- error: new Error(`Not found: ${info.url.pathname}`),
1039
- url: info.url
1040
- });
1068
+ if (info.initial) {
1069
+ return await this._load_error({
1070
+ status: 404,
1071
+ error: new Error(`Not found: ${info.url.pathname}`),
1072
+ url: info.url
1073
+ });
1074
+ }
1041
1075
  }
1042
1076
 
1043
1077
  /**
@@ -1174,7 +1208,7 @@ class Renderer {
1174
1208
  if (module.load) {
1175
1209
  const { started } = this;
1176
1210
 
1177
- /** @type {import('types/page').LoadInput | import('types/page').ErrorLoadInput} */
1211
+ /** @type {import('types').LoadInput | import('types').ErrorLoadInput} */
1178
1212
  const load_input = {
1179
1213
  params: uses_params,
1180
1214
  props: props || {},
@@ -1209,8 +1243,8 @@ class Renderer {
1209
1243
  }
1210
1244
 
1211
1245
  if (error) {
1212
- /** @type {import('types/page').ErrorLoadInput} */ (load_input).status = status;
1213
- /** @type {import('types/page').ErrorLoadInput} */ (load_input).error = error;
1246
+ /** @type {import('types').ErrorLoadInput} */ (load_input).status = status;
1247
+ /** @type {import('types').ErrorLoadInput} */ (load_input).error = error;
1214
1248
  }
1215
1249
 
1216
1250
  const loaded = await module.load.call(null, load_input);
@@ -1485,11 +1519,11 @@ class Renderer {
1485
1519
  * session: any;
1486
1520
  * route: boolean;
1487
1521
  * spa: boolean;
1488
- * trailing_slash: import('types/internal').TrailingSlash;
1522
+ * trailing_slash: import('types').TrailingSlash;
1489
1523
  * hydrate: {
1490
1524
  * status: number;
1491
1525
  * error: Error;
1492
- * nodes: Array<Promise<import('types/internal').CSRComponent>>;
1526
+ * nodes: Array<Promise<import('types').CSRComponent>>;
1493
1527
  * params: Record<string, string>;
1494
1528
  * };
1495
1529
  * }} opts