swup 4.4.4 → 4.5.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.
Files changed (54) hide show
  1. package/dist/Swup.cjs +1 -1
  2. package/dist/Swup.cjs.map +1 -1
  3. package/dist/Swup.modern.js +1 -1
  4. package/dist/Swup.modern.js.map +1 -1
  5. package/dist/Swup.module.js +1 -1
  6. package/dist/Swup.module.js.map +1 -1
  7. package/dist/Swup.umd.js +1 -1
  8. package/dist/Swup.umd.js.map +1 -1
  9. package/dist/types/Swup.d.ts +8 -4
  10. package/dist/types/Swup.d.ts.map +1 -1
  11. package/dist/types/helpers/Location.d.ts.map +1 -1
  12. package/dist/types/helpers/history.d.ts +14 -0
  13. package/dist/types/helpers/history.d.ts.map +1 -0
  14. package/dist/types/helpers.d.ts +1 -2
  15. package/dist/types/helpers.d.ts.map +1 -1
  16. package/dist/types/modules/Classes.d.ts.map +1 -1
  17. package/dist/types/modules/Hooks.d.ts +16 -8
  18. package/dist/types/modules/Hooks.d.ts.map +1 -1
  19. package/dist/types/modules/Visit.d.ts +28 -22
  20. package/dist/types/modules/Visit.d.ts.map +1 -1
  21. package/dist/types/modules/animatePageIn.d.ts +2 -1
  22. package/dist/types/modules/animatePageIn.d.ts.map +1 -1
  23. package/dist/types/modules/animatePageOut.d.ts +2 -1
  24. package/dist/types/modules/animatePageOut.d.ts.map +1 -1
  25. package/dist/types/modules/fetchPage.d.ts.map +1 -1
  26. package/dist/types/modules/navigate.d.ts +2 -2
  27. package/dist/types/modules/navigate.d.ts.map +1 -1
  28. package/dist/types/modules/renderPage.d.ts +2 -1
  29. package/dist/types/modules/renderPage.d.ts.map +1 -1
  30. package/dist/types/modules/replaceContent.d.ts.map +1 -1
  31. package/dist/types/modules/scrollToContent.d.ts +2 -1
  32. package/dist/types/modules/scrollToContent.d.ts.map +1 -1
  33. package/package.json +2 -1
  34. package/src/Swup.ts +40 -37
  35. package/src/helpers/Location.ts +1 -0
  36. package/src/helpers/history.ts +37 -0
  37. package/src/helpers.ts +1 -2
  38. package/src/modules/Cache.ts +2 -2
  39. package/src/modules/Classes.ts +8 -1
  40. package/src/modules/Hooks.ts +77 -29
  41. package/src/modules/Visit.ts +86 -47
  42. package/src/modules/animatePageIn.ts +9 -8
  43. package/src/modules/animatePageOut.ts +7 -18
  44. package/src/modules/fetchPage.ts +9 -11
  45. package/src/modules/navigate.ts +78 -34
  46. package/src/modules/renderPage.ts +15 -13
  47. package/src/modules/replaceContent.ts +1 -0
  48. package/src/modules/scrollToContent.ts +6 -4
  49. package/dist/types/helpers/createHistoryRecord.d.ts +0 -10
  50. package/dist/types/helpers/createHistoryRecord.d.ts.map +0 -1
  51. package/dist/types/helpers/updateHistoryRecord.d.ts +0 -3
  52. package/dist/types/helpers/updateHistoryRecord.d.ts.map +0 -1
  53. package/src/helpers/createHistoryRecord.ts +0 -24
  54. package/src/helpers/updateHistoryRecord.ts +0 -19
@@ -1,7 +1,13 @@
1
1
  import type Swup from '../Swup.js';
2
- import { createHistoryRecord, updateHistoryRecord, getCurrentUrl, Location } from '../helpers.js';
3
2
  import { FetchError, type FetchOptions, type PageData } from './fetchPage.js';
4
- import type { VisitInitOptions } from './Visit.js';
3
+ import { type VisitInitOptions, type Visit, VisitState } from './Visit.js';
4
+ import {
5
+ createHistoryRecord,
6
+ updateHistoryRecord,
7
+ getCurrentUrl,
8
+ Location,
9
+ classify
10
+ } from '../helpers.js';
5
11
 
6
12
  export type HistoryAction = 'push' | 'replace';
7
13
  export type HistoryDirection = 'forwards' | 'backwards';
@@ -43,8 +49,9 @@ export function navigate(
43
49
  }
44
50
 
45
51
  const { url: to, hash } = Location.fromUrl(url);
46
- this.visit = this.createVisit({ ...init, to, hash });
47
- this.performNavigation(options);
52
+
53
+ const visit = this.createVisit({ ...init, to, hash });
54
+ this.performNavigation(visit, options);
48
55
  }
49
56
 
50
57
  /**
@@ -60,12 +67,24 @@ export function navigate(
60
67
  */
61
68
  export async function performNavigation(
62
69
  this: Swup,
70
+ visit: Visit,
63
71
  options: NavigationOptions & FetchOptions = {}
64
72
  ): Promise<void> {
73
+ if (this.navigating) {
74
+ if (this.visit.state >= VisitState.ENTERING) {
75
+ // Currently navigating and content already loaded? Finish and queue
76
+ visit.state = VisitState.QUEUED;
77
+ this.onVisitEnd = () => this.performNavigation(visit, options);
78
+ return;
79
+ } else {
80
+ // Currently navigating and content not loaded? Abort running visit
81
+ await this.hooks.call('visit:abort', this.visit, undefined);
82
+ this.visit.state = VisitState.ABORTED;
83
+ }
84
+ }
85
+
65
86
  this.navigating = true;
66
- // Save this localy to a) allow ignoring the visit if a new one was started in the meantime
67
- // and b) avoid unintended modifications to any newer visits
68
- const visit = this.visit;
87
+ this.visit = visit;
69
88
 
70
89
  const { el } = visit.trigger;
71
90
  options.referrer = options.referrer || this.currentPageUrl;
@@ -102,10 +121,11 @@ export async function performNavigation(
102
121
  delete options.cache;
103
122
 
104
123
  try {
105
- await this.hooks.call('visit:start', undefined);
124
+ await this.hooks.call('visit:start', visit, undefined);
125
+ visit.state = VisitState.STARTED;
106
126
 
107
127
  // Begin loading page
108
- const pagePromise = this.hooks.call('page:load', { options }, async (visit, args) => {
128
+ const page = this.hooks.call('page:load', visit, { options }, async (visit, args) => {
109
129
  // Read from cache
110
130
  let cachedPage: PageData | undefined;
111
131
  if (visit.cache.read) {
@@ -118,6 +138,12 @@ export async function performNavigation(
118
138
  return args.page;
119
139
  });
120
140
 
141
+ // When page loaded: mark visit as loaded, save html into visit object
142
+ page.then(({ html }) => {
143
+ visit.advance(VisitState.LOADED);
144
+ visit.to.html = html;
145
+ });
146
+
121
147
  // Create/update history record if this is not a popstate call or leads to the same URL
122
148
  if (!visit.history.popstate) {
123
149
  // Add the hash directly from the trigger element
@@ -132,48 +158,66 @@ export async function performNavigation(
132
158
 
133
159
  this.currentPageUrl = getCurrentUrl();
134
160
 
161
+ // Mark visit type with classes
162
+ if (visit.history.popstate) {
163
+ this.classes.add('is-popstate');
164
+ }
165
+ if (visit.animation.name) {
166
+ this.classes.add(`to-${classify(visit.animation.name)}`);
167
+ }
168
+
135
169
  // Wait for page before starting to animate out?
136
170
  if (visit.animation.wait) {
137
- const { html } = await pagePromise;
138
- visit.to.html = html;
171
+ await page;
139
172
  }
140
173
 
141
- // perform the actual transition: animate and replace content
142
- await this.hooks.call('visit:transition', undefined, async (visit) => {
143
- // Start leave animation
144
- const animationPromise = this.animatePageOut();
174
+ // Check if failed/aborted in the meantime
175
+ if (visit.done) return;
145
176
 
146
- // Wait for page to load and leave animation to finish
147
- const [page] = await Promise.all([pagePromise, animationPromise]);
148
-
149
- // Abort if another visit was started in the meantime
150
- if (visit.id !== this.visit.id) {
151
- return false;
177
+ // Perform the actual transition: animate and replace content
178
+ await this.hooks.call('visit:transition', visit, undefined, async () => {
179
+ // No animation? Just await page and render
180
+ if (!visit.animation.animate) {
181
+ await this.hooks.call('animation:skip', undefined);
182
+ await this.renderPage(visit, await page);
183
+ return;
152
184
  }
153
185
 
154
- // Render page: replace content and scroll to top/fragment
155
- await this.renderPage(page);
156
-
157
- // Wait for enter animation
158
- await this.animatePageIn();
159
-
160
- return true;
186
+ // Animate page out, render page, animate page in
187
+ visit.advance(VisitState.LEAVING);
188
+ await this.animatePageOut(visit);
189
+ if (visit.animation.native && document.startViewTransition) {
190
+ await document.startViewTransition(
191
+ async () => await this.renderPage(visit, await page)
192
+ ).finished;
193
+ } else {
194
+ await this.renderPage(visit, await page);
195
+ }
196
+ await this.animatePageIn(visit);
161
197
  });
162
198
 
163
- // Finalize visit
164
- await this.hooks.call('visit:end', undefined, () => this.classes.clear());
199
+ // Check if failed/aborted in the meantime
200
+ if (visit.done) return;
165
201
 
166
- // Reset visit info after finish?
167
- // if (visit.to && this.isSameResolvedUrl(visit.to.url, requestedUrl)) {
168
- // this.visit = this.createVisit({ to: undefined });
169
- // }
202
+ // Finalize visit
203
+ await this.hooks.call('visit:end', visit, undefined, () => this.classes.clear());
204
+ visit.state = VisitState.COMPLETED;
170
205
  this.navigating = false;
206
+
207
+ /** Run eventually queued function */
208
+ if (this.onVisitEnd) {
209
+ this.onVisitEnd();
210
+ this.onVisitEnd = undefined;
211
+ }
171
212
  } catch (error) {
172
213
  // Return early if error is undefined or signals an aborted request
173
214
  if (!error || (error as FetchError)?.aborted) {
215
+ visit.state = VisitState.ABORTED;
174
216
  return;
175
217
  }
176
218
 
219
+ visit.state = VisitState.FAILED;
220
+
177
221
  // Log to console as we swallow almost all hook errors
178
222
  console.error(error);
179
223
 
@@ -1,12 +1,18 @@
1
1
  import { updateHistoryRecord, getCurrentUrl, classify } from '../helpers.js';
2
2
  import type Swup from '../Swup.js';
3
3
  import type { PageData } from './fetchPage.js';
4
+ import { VisitState, type Visit } from './Visit.js';
4
5
 
5
6
  /**
6
7
  * Render the next page: replace the content and update scroll position.
7
8
  */
8
- export const renderPage = async function (this: Swup, page: PageData): Promise<void> {
9
- const { url, html } = page;
9
+ export const renderPage = async function (this: Swup, visit: Visit, page: PageData): Promise<void> {
10
+ // Check if failed/aborted in the meantime
11
+ if (visit.done) return;
12
+
13
+ visit.advance(VisitState.ENTERING);
14
+
15
+ const { url } = page;
10
16
 
11
17
  this.classes.remove('is-leaving');
12
18
 
@@ -14,26 +20,23 @@ export const renderPage = async function (this: Swup, page: PageData): Promise<v
14
20
  if (!this.isSameResolvedUrl(getCurrentUrl(), url)) {
15
21
  updateHistoryRecord(url);
16
22
  this.currentPageUrl = getCurrentUrl();
17
- this.visit.to.url = this.currentPageUrl;
23
+ visit.to.url = this.currentPageUrl;
18
24
  }
19
25
 
20
26
  // only add for animated page loads
21
- if (this.visit.animation.animate) {
27
+ if (visit.animation.animate) {
22
28
  this.classes.add('is-rendering');
23
29
  }
24
30
 
25
- // save html into visit context for easier retrieval
26
- this.visit.to.html = html;
27
-
28
31
  // replace content: allow handlers and plugins to overwrite paga data and containers
29
- await this.hooks.call('content:replace', { page }, (visit, { page }) => {
32
+ await this.hooks.call('content:replace', visit, { page }, (visit, { page }) => {
30
33
  const success = this.replaceContent(page, { containers: visit.containers });
31
34
  if (!success) {
32
35
  throw new Error('[swup] Container mismatch, aborting');
33
36
  }
34
37
  if (visit.animation.animate) {
35
38
  // Make sure to add these classes to new containers as well
36
- this.classes.add('is-animating', 'is-changing', 'is-rendering');
39
+ this.classes.add('is-changing', 'is-animating', 'is-rendering');
37
40
  if (visit.animation.name) {
38
41
  this.classes.add(`to-${classify(visit.animation.name)}`);
39
42
  }
@@ -41,10 +44,9 @@ export const renderPage = async function (this: Swup, page: PageData): Promise<v
41
44
  });
42
45
 
43
46
  // scroll into view: either anchor or top of page
44
- // @ts-ignore: not returning a promise is intentional to allow users to pause in handler
45
- await this.hooks.call('content:scroll', undefined, () => {
46
- return this.scrollToContent();
47
+ await this.hooks.call('content:scroll', visit, undefined, () => {
48
+ return this.scrollToContent(visit);
47
49
  });
48
50
 
49
- await this.hooks.call('page:view', { url: this.currentPageUrl, title: document.title });
51
+ await this.hooks.call('page:view', visit, { url: this.currentPageUrl, title: document.title });
50
52
  };
@@ -53,5 +53,6 @@ export const replaceContent = function (
53
53
  }
54
54
  });
55
55
 
56
+ // Return true if all containers were replaced
56
57
  return replaced.length === containers.length;
57
58
  };
@@ -1,19 +1,21 @@
1
1
  import type Swup from '../Swup.js';
2
+ import type { Visit } from './Visit.js';
2
3
 
3
4
  /**
4
5
  * Update the scroll position after page render.
5
6
  * @returns Promise<boolean>
6
7
  */
7
- export const scrollToContent = function (this: Swup): boolean {
8
+ export const scrollToContent = function (this: Swup, visit: Visit): boolean {
8
9
  const options: ScrollIntoViewOptions = { behavior: 'auto' };
9
- const { target, reset } = this.visit.scroll;
10
- const scrollTarget = target ?? this.visit.to.hash;
10
+ const { target, reset } = visit.scroll;
11
+ const scrollTarget = target ?? visit.to.hash;
11
12
 
12
13
  let scrolled = false;
13
14
 
14
15
  if (scrollTarget) {
15
16
  scrolled = this.hooks.callSync(
16
17
  'scroll:anchor',
18
+ visit,
17
19
  { hash: scrollTarget, options },
18
20
  (visit, { hash, options }) => {
19
21
  const anchor = this.getAnchorElement(hash);
@@ -26,7 +28,7 @@ export const scrollToContent = function (this: Swup): boolean {
26
28
  }
27
29
 
28
30
  if (reset && !scrolled) {
29
- scrolled = this.hooks.callSync('scroll:top', { options }, (visit, { options }) => {
31
+ scrolled = this.hooks.callSync('scroll:top', visit, { options }, (visit, { options }) => {
30
32
  window.scrollTo({ top: 0, left: 0, ...options });
31
33
  return true;
32
34
  });
@@ -1,10 +0,0 @@
1
- export interface HistoryState {
2
- url: string;
3
- source: 'swup';
4
- random: number;
5
- index?: number;
6
- [key: string]: unknown;
7
- }
8
- /** Create a new history record with a custom swup identifier. */
9
- export declare const createHistoryRecord: (url: string, customData?: Record<string, unknown>) => void;
10
- //# sourceMappingURL=createHistoryRecord.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createHistoryRecord.d.ts","sourceRoot":"","sources":["../../../src/helpers/createHistoryRecord.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,QAC1B,MAAM,eACC,OAAO,MAAM,EAAE,OAAO,CAAC,KACjC,IASF,CAAC"}
@@ -1,3 +0,0 @@
1
- /** Update the current history record with a custom swup identifier. */
2
- export declare const updateHistoryRecord: (url?: string | null, customData?: Record<string, unknown>) => void;
3
- //# sourceMappingURL=updateHistoryRecord.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"updateHistoryRecord.d.ts","sourceRoot":"","sources":["../../../src/helpers/updateHistoryRecord.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,SAC1B,MAAM,GAAG,IAAI,eACN,OAAO,MAAM,EAAE,OAAO,CAAC,KACjC,IAWF,CAAC"}
@@ -1,24 +0,0 @@
1
- import { getCurrentUrl } from './getCurrentUrl.js';
2
-
3
- export interface HistoryState {
4
- url: string;
5
- source: 'swup';
6
- random: number;
7
- index?: number;
8
- [key: string]: unknown;
9
- }
10
-
11
- /** Create a new history record with a custom swup identifier. */
12
- export const createHistoryRecord = (
13
- url: string,
14
- customData: Record<string, unknown> = {}
15
- ): void => {
16
- url = url || getCurrentUrl({ hash: true });
17
- const data: HistoryState = {
18
- url,
19
- random: Math.random(),
20
- source: 'swup',
21
- ...customData
22
- };
23
- history.pushState(data, '', url);
24
- };
@@ -1,19 +0,0 @@
1
- import type { HistoryState } from './createHistoryRecord.js';
2
- import { getCurrentUrl } from './getCurrentUrl.js';
3
-
4
- /** Update the current history record with a custom swup identifier. */
5
- export const updateHistoryRecord = (
6
- url: string | null = null,
7
- customData: Record<string, unknown> = {}
8
- ): void => {
9
- url = url || getCurrentUrl({ hash: true });
10
- const state = (history.state as HistoryState) || {};
11
- const data: HistoryState = {
12
- ...state,
13
- url,
14
- random: Math.random(),
15
- source: 'swup',
16
- ...customData
17
- };
18
- history.replaceState(data, '', url);
19
- };