swup 2.0.19 → 3.0.0-rc.1

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 (152) hide show
  1. package/dist/helpers.cjs +2 -0
  2. package/dist/helpers.cjs.map +1 -0
  3. package/dist/helpers.modern.js +2 -0
  4. package/dist/helpers.modern.js.map +1 -0
  5. package/dist/helpers.module.js +2 -0
  6. package/dist/helpers.module.js.map +1 -0
  7. package/dist/index.cjs +2 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.modern.js +2 -0
  10. package/dist/index.modern.js.map +1 -0
  11. package/dist/index.module.js +2 -0
  12. package/dist/index.module.js.map +1 -0
  13. package/dist/index.umd.js +3 -0
  14. package/dist/index.umd.js.map +1 -0
  15. package/dist/src/config/version.d.ts +5 -0
  16. package/dist/src/helpers/Location.d.ts +31 -0
  17. package/dist/src/helpers/classify.d.ts +2 -0
  18. package/dist/src/helpers/cleanupAnimationClasses.d.ts +2 -0
  19. package/dist/src/helpers/createHistoryRecord.d.ts +2 -0
  20. package/dist/src/helpers/delegateEvent.d.ts +8 -0
  21. package/dist/src/helpers/fetch.d.ts +6 -0
  22. package/dist/src/helpers/getCurrentUrl.d.ts +4 -0
  23. package/dist/src/helpers/getDataFromHtml.d.ts +8 -0
  24. package/dist/src/helpers/index.d.ts +11 -0
  25. package/dist/src/helpers/markSwupElements.d.ts +2 -0
  26. package/dist/src/helpers/updateHistoryRecord.d.ts +2 -0
  27. package/dist/src/helpers/versionSatisfies.d.ts +12 -0
  28. package/dist/src/helpers.d.ts +1 -0
  29. package/dist/src/index.d.ts +97 -0
  30. package/dist/src/modules/Cache.d.ts +20 -0
  31. package/dist/src/modules/enterPage.d.ts +6 -0
  32. package/dist/src/modules/getAnchorElement.d.ts +2 -0
  33. package/dist/src/modules/getAnimationPromises.d.ts +7 -0
  34. package/dist/src/modules/getPageData.d.ts +7 -0
  35. package/dist/src/modules/leavePage.d.ts +7 -0
  36. package/dist/src/modules/loadPage.d.ts +4 -0
  37. package/dist/src/modules/off.d.ts +4 -0
  38. package/dist/src/modules/on.d.ts +4 -0
  39. package/dist/src/modules/plugins.d.ts +13 -0
  40. package/dist/src/modules/renderPage.d.ts +7 -0
  41. package/dist/src/modules/replaceContent.d.ts +17 -0
  42. package/dist/src/modules/triggerEvent.d.ts +4 -0
  43. package/dist/src/modules/updateTransition.d.ts +3 -0
  44. package/dist/src/src/config/version.d.ts +5 -0
  45. package/dist/src/src/helpers/Location.d.ts +31 -0
  46. package/dist/src/src/helpers/classify.d.ts +2 -0
  47. package/dist/src/src/helpers/cleanupAnimationClasses.d.ts +2 -0
  48. package/dist/src/src/helpers/createHistoryRecord.d.ts +2 -0
  49. package/dist/src/src/helpers/delegateEvent.d.ts +8 -0
  50. package/dist/src/src/helpers/fetch.d.ts +6 -0
  51. package/dist/src/src/helpers/getCurrentUrl.d.ts +4 -0
  52. package/dist/src/src/helpers/getDataFromHtml.d.ts +8 -0
  53. package/dist/src/src/helpers/index.d.ts +11 -0
  54. package/dist/src/src/helpers/markSwupElements.d.ts +2 -0
  55. package/dist/src/src/helpers/updateHistoryRecord.d.ts +2 -0
  56. package/dist/src/src/helpers/versionSatisfies.d.ts +12 -0
  57. package/dist/src/src/helpers.d.ts +1 -0
  58. package/dist/src/src/index.d.ts +103 -0
  59. package/dist/src/src/modules/Cache.d.ts +20 -0
  60. package/dist/src/src/modules/enterPage.d.ts +6 -0
  61. package/dist/src/src/modules/getAnchorElement.d.ts +2 -0
  62. package/dist/src/src/modules/getAnimationPromises.d.ts +7 -0
  63. package/dist/src/src/modules/getPageData.d.ts +7 -0
  64. package/dist/src/src/modules/leavePage.d.ts +7 -0
  65. package/dist/src/src/modules/loadPage.d.ts +7 -0
  66. package/dist/src/src/modules/off.d.ts +4 -0
  67. package/dist/src/src/modules/on.d.ts +6 -0
  68. package/dist/src/src/modules/plugins.d.ts +14 -0
  69. package/dist/src/src/modules/renderPage.d.ts +7 -0
  70. package/dist/src/src/modules/replaceContent.d.ts +17 -0
  71. package/dist/src/src/modules/triggerEvent.d.ts +4 -0
  72. package/dist/src/src/modules/updateTransition.d.ts +3 -0
  73. package/dist/src/src/utils/index.d.ts +5 -0
  74. package/dist/src/src/utils.d.ts +1 -0
  75. package/dist/src/types.d.ts +12 -0
  76. package/dist/src/utils/index.d.ts +5 -0
  77. package/dist/src/utils.d.ts +1 -0
  78. package/dist/types.cjs +2 -0
  79. package/dist/types.cjs.map +1 -0
  80. package/dist/types.modern.js +2 -0
  81. package/dist/types.modern.js.map +1 -0
  82. package/dist/types.module.js +2 -0
  83. package/dist/types.module.js.map +1 -0
  84. package/dist/utils.cjs +2 -0
  85. package/dist/utils.cjs.map +1 -0
  86. package/dist/utils.modern.js +2 -0
  87. package/dist/utils.modern.js.map +1 -0
  88. package/dist/utils.module.js +2 -0
  89. package/dist/utils.module.js.map +1 -0
  90. package/package.json +41 -23
  91. package/readme.md +52 -36
  92. package/src/config/version.ts +13 -0
  93. package/src/helpers/Location.ts +44 -0
  94. package/src/helpers/classify.ts +13 -0
  95. package/src/helpers/cleanupAnimationClasses.ts +10 -0
  96. package/src/helpers/createHistoryRecord.ts +14 -0
  97. package/src/helpers/delegateEvent.ts +23 -0
  98. package/src/helpers/fetch.ts +35 -0
  99. package/src/helpers/getCurrentUrl.ts +5 -0
  100. package/src/helpers/getDataFromHtml.ts +41 -0
  101. package/src/helpers/index.ts +11 -0
  102. package/src/helpers/markSwupElements.ts +18 -0
  103. package/src/helpers/updateHistoryRecord.ts +18 -0
  104. package/src/helpers/versionSatisfies.ts +50 -0
  105. package/src/helpers.ts +4 -0
  106. package/src/index.ts +369 -0
  107. package/src/modules/Cache.ts +57 -0
  108. package/src/modules/enterPage.ts +28 -0
  109. package/src/modules/fetchPage.ts +35 -0
  110. package/src/modules/getAnchorElement.ts +19 -0
  111. package/src/modules/getAnimationPromises.ts +176 -0
  112. package/src/modules/getPageData.ts +26 -0
  113. package/src/modules/leavePage.ts +33 -0
  114. package/src/modules/loadPage.ts +54 -0
  115. package/src/modules/off.ts +23 -0
  116. package/src/modules/on.ts +35 -0
  117. package/src/modules/plugins.ts +58 -0
  118. package/src/modules/renderPage.ts +52 -0
  119. package/src/modules/replaceContent.ts +28 -0
  120. package/src/modules/triggerEvent.ts +23 -0
  121. package/src/modules/updateTransition.ts +7 -0
  122. package/src/utils/index.ts +32 -0
  123. package/src/utils.ts +4 -0
  124. package/.editorconfig +0 -19
  125. package/cypress.config.js +0 -14
  126. package/dist/swup.js +0 -1524
  127. package/dist/swup.min.js +0 -1
  128. package/lib/helpers/Link.js +0 -56
  129. package/lib/helpers/classify.js +0 -18
  130. package/lib/helpers/cleanupAnimationClasses.js +0 -18
  131. package/lib/helpers/createHistoryRecord.js +0 -14
  132. package/lib/helpers/fetch.js +0 -41
  133. package/lib/helpers/getCurrentUrl.js +0 -10
  134. package/lib/helpers/getDataFromHtml.js +0 -43
  135. package/lib/helpers/index.js +0 -64
  136. package/lib/helpers/markSwupElements.js +0 -24
  137. package/lib/helpers/normalizeUrl.js +0 -17
  138. package/lib/helpers/transitionEnd.js +0 -14
  139. package/lib/helpers/transitionProperty.js +0 -14
  140. package/lib/index.js +0 -305
  141. package/lib/modules/Cache.js +0 -66
  142. package/lib/modules/getAnchorElement.js +0 -25
  143. package/lib/modules/getAnimationPromises.js +0 -43
  144. package/lib/modules/getPageData.js +0 -26
  145. package/lib/modules/loadPage.js +0 -123
  146. package/lib/modules/off.js +0 -34
  147. package/lib/modules/on.js +0 -14
  148. package/lib/modules/plugins.js +0 -54
  149. package/lib/modules/renderPage.js +0 -76
  150. package/lib/modules/triggerEvent.js +0 -21
  151. package/lib/modules/updateTransition.js +0 -15
  152. package/lib/utils/index.js +0 -32
package/src/index.ts ADDED
@@ -0,0 +1,369 @@
1
+ import delegate from 'delegate-it';
2
+
3
+ import version from './config/version.js';
4
+
5
+ import {
6
+ cleanupAnimationClasses,
7
+ delegateEvent,
8
+ getCurrentUrl,
9
+ Location,
10
+ markSwupElements,
11
+ updateHistoryRecord
12
+ } from './helpers.js';
13
+ import { Unsubscribe } from './helpers/delegateEvent.js';
14
+
15
+ import Cache from './modules/Cache.js';
16
+ import enterPage from './modules/enterPage.js';
17
+ import getAnchorElement from './modules/getAnchorElement.js';
18
+ import getAnimationPromises from './modules/getAnimationPromises.js';
19
+ import getPageData from './modules/getPageData.js';
20
+ import fetchPage from './modules/fetchPage.js';
21
+ import leavePage from './modules/leavePage.js';
22
+ import loadPage from './modules/loadPage.js';
23
+ import replaceContent from './modules/replaceContent.js';
24
+ import off from './modules/off.js';
25
+ import on, { Handlers } from './modules/on.js';
26
+ import { use, unuse, findPlugin, Plugin } from './modules/plugins.js';
27
+ import renderPage from './modules/renderPage.js';
28
+ import triggerEvent from './modules/triggerEvent.js';
29
+ import updateTransition from './modules/updateTransition.js';
30
+
31
+ import { queryAll } from './utils.js';
32
+
33
+ export type Transition = {
34
+ from?: string;
35
+ to?: string;
36
+ custom?: string;
37
+ };
38
+
39
+ type DelegatedListeners = {
40
+ click?: Unsubscribe;
41
+ };
42
+
43
+ export type Options = {
44
+ animateHistoryBrowsing: boolean;
45
+ animationSelector: string | false;
46
+ linkSelector: string;
47
+ cache: boolean;
48
+ containers: string[];
49
+ requestHeaders: Record<string, string>;
50
+ plugins: Plugin[];
51
+ skipPopStateHandling: (event: any) => boolean;
52
+ ignoreVisit: (href: string, { el }: { el?: Element }) => boolean;
53
+ resolveUrl: (url: string) => string;
54
+ };
55
+
56
+ export default class Swup {
57
+ version = version;
58
+
59
+ _handlers: Handlers = {
60
+ animationInDone: [],
61
+ animationInStart: [],
62
+ animationOutDone: [],
63
+ animationOutStart: [],
64
+ animationSkipped: [],
65
+ clickLink: [],
66
+ contentReplaced: [],
67
+ disabled: [],
68
+ enabled: [],
69
+ openPageInNewTab: [],
70
+ pageLoaded: [],
71
+ pageRetrievedFromCache: [],
72
+ pageView: [],
73
+ popState: [],
74
+ samePage: [],
75
+ samePageWithHash: [],
76
+ serverError: [],
77
+ transitionStart: [],
78
+ transitionEnd: [],
79
+ willReplaceContent: []
80
+ };
81
+
82
+ // variable for anchor to scroll to after render
83
+ scrollToElement: string | null = null;
84
+ // variable for save options
85
+ options: Options;
86
+ // running plugin instances
87
+ plugins: Plugin[] = [];
88
+ // variable for current transition info object
89
+ transition: Transition = {};
90
+ // cache instance
91
+ cache: Cache;
92
+ // allows us to compare the current and new path inside popStateHandler
93
+ currentPageUrl = getCurrentUrl();
94
+ // variable for keeping event listeners from "delegate"
95
+ delegatedListeners: DelegatedListeners = {};
96
+ // so we are able to remove the listener
97
+ boundPopStateHandler: (event: PopStateEvent) => void;
98
+
99
+ loadPage = loadPage;
100
+ leavePage = leavePage;
101
+ renderPage = renderPage;
102
+ replaceContent = replaceContent;
103
+ enterPage = enterPage;
104
+ triggerEvent = triggerEvent;
105
+ delegateEvent = delegateEvent;
106
+ on = on;
107
+ off = off;
108
+ updateTransition = updateTransition;
109
+ getAnimationPromises = getAnimationPromises;
110
+ getPageData = getPageData;
111
+ fetchPage = fetchPage;
112
+ getAnchorElement = getAnchorElement;
113
+ log: (message: string, context?: any) => void = () => {}; // here so it can be used by plugins
114
+ use = use;
115
+ unuse = unuse;
116
+ findPlugin = findPlugin;
117
+ getCurrentUrl = getCurrentUrl;
118
+ cleanupAnimationClasses = cleanupAnimationClasses;
119
+
120
+ defaults: Options = {
121
+ animateHistoryBrowsing: false,
122
+ animationSelector: '[class*="transition-"]',
123
+ cache: true,
124
+ containers: ['#swup'],
125
+ ignoreVisit: (href, { el } = {}) => !!el?.closest('[data-no-swup]'),
126
+ linkSelector: 'a[href]',
127
+ plugins: [],
128
+ resolveUrl: (url) => url,
129
+ requestHeaders: {
130
+ 'X-Requested-With': 'swup',
131
+ Accept: 'text/html, application/xhtml+xml'
132
+ },
133
+ skipPopStateHandling: (event) => event.state?.source !== 'swup'
134
+ };
135
+
136
+ constructor(options: Partial<Options> = {}) {
137
+ // Merge defaults and options
138
+ this.options = { ...this.defaults, ...options };
139
+
140
+ this.boundPopStateHandler = this.popStateHandler.bind(this);
141
+
142
+ this.cache = new Cache(this);
143
+
144
+ this.enable();
145
+ }
146
+
147
+ enable() {
148
+ // Check for Promise support
149
+ if (typeof Promise === 'undefined') {
150
+ console.warn('Promise is not supported');
151
+ return;
152
+ }
153
+
154
+ // Add event listeners
155
+ this.delegatedListeners.click = delegateEvent(
156
+ this.options.linkSelector,
157
+ 'click',
158
+ this.linkClickHandler.bind(this)
159
+ );
160
+
161
+ window.addEventListener('popstate', this.boundPopStateHandler);
162
+
163
+ // Initial save to cache
164
+ if (this.options.cache) {
165
+ // Disabled to avoid caching modified dom state: logic moved to preload plugin
166
+ // https://github.com/swup/swup/issues/475
167
+ }
168
+
169
+ // Mark swup blocks in html
170
+ markSwupElements(document.documentElement, this.options.containers);
171
+
172
+ // Mount plugins
173
+ this.options.plugins.forEach((plugin) => this.use(plugin));
174
+
175
+ // Modify initial history record
176
+ updateHistoryRecord();
177
+
178
+ // Trigger enabled event
179
+ this.triggerEvent('enabled');
180
+
181
+ // Add swup-enabled class to html tag
182
+ document.documentElement.classList.add('swup-enabled');
183
+
184
+ // Trigger page view event
185
+ this.triggerEvent('pageView');
186
+ }
187
+
188
+ destroy() {
189
+ // remove delegated listeners
190
+ this.delegatedListeners.click!.destroy();
191
+
192
+ // remove popstate listener
193
+ window.removeEventListener('popstate', this.boundPopStateHandler);
194
+
195
+ // empty cache
196
+ this.cache.empty();
197
+
198
+ // unmount plugins
199
+ this.options.plugins.forEach((plugin) => {
200
+ this.unuse(plugin);
201
+ });
202
+
203
+ // remove swup data atributes from blocks
204
+ queryAll('[data-swup]').forEach((element) => {
205
+ element.removeAttribute('data-swup');
206
+ });
207
+
208
+ // remove handlers
209
+ this.off();
210
+
211
+ // trigger disable event
212
+ this.triggerEvent('disabled');
213
+
214
+ // remove swup-enabled class from html tag
215
+ document.documentElement.classList.remove('swup-enabled');
216
+ }
217
+
218
+ shouldIgnoreVisit(href: string, { el }: { el?: Element } = {}) {
219
+ const { origin } = Location.fromUrl(href);
220
+
221
+ // Ignore if the new URL's origin doesn't match the current one
222
+ if (origin !== window.location.origin) {
223
+ return true;
224
+ }
225
+
226
+ // Ignore if the link/form would open a new window (or none at all)
227
+ if (el && this.triggerWillOpenNewWindow(el)) {
228
+ return true;
229
+ }
230
+
231
+ // Ignore if the visit should be ignored as per user options
232
+ if (this.options.ignoreVisit(href, { el })) {
233
+ return true;
234
+ }
235
+
236
+ // Finally, allow the visit
237
+ return false;
238
+ }
239
+
240
+ linkClickHandler(event: delegate.Event<MouseEvent>) {
241
+ const linkEl = event.delegateTarget;
242
+ const { href, url, hash } = Location.fromElement(linkEl as HTMLAnchorElement);
243
+
244
+ // Exit early if the link should be ignored
245
+ if (this.shouldIgnoreVisit(href, { el: linkEl })) {
246
+ return;
247
+ }
248
+
249
+ // Exit early if control key pressed
250
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
251
+ this.triggerEvent('openPageInNewTab', event);
252
+ return;
253
+ }
254
+
255
+ // Exit early if other than left mouse button
256
+ if (event.button !== 0) {
257
+ return;
258
+ }
259
+
260
+ this.triggerEvent('clickLink', event);
261
+ event.preventDefault();
262
+
263
+ // Handle links to the same page and exit early, where applicable
264
+ if (!url || url === getCurrentUrl()) {
265
+ this.handleLinkToSamePage(url, hash, event);
266
+ return;
267
+ }
268
+
269
+ // Exit early if the resolved path hasn't changed
270
+ if (this.isSameResolvedUrl(url, getCurrentUrl())) return;
271
+
272
+ // Store the element that should be scrolled to after loading the next page
273
+ this.scrollToElement = hash || null;
274
+
275
+ // Get the custom transition name, if present
276
+ const customTransition = linkEl.getAttribute('data-swup-transition') || undefined;
277
+
278
+ // Finally, proceed with loading the page
279
+ this.loadPage({ url, customTransition }, null);
280
+ }
281
+
282
+ handleLinkToSamePage(url: string, hash: string, event: MouseEvent) {
283
+ // Emit event and exit early if the url points to the same page without hash
284
+ if (!hash) {
285
+ this.triggerEvent('samePage', event);
286
+ return;
287
+ }
288
+
289
+ // link to the same URL with hash
290
+ this.triggerEvent('samePageWithHash', event);
291
+
292
+ const element = getAnchorElement(hash);
293
+
294
+ // Warn and exit early if no matching element was found for the hash
295
+ if (!element) {
296
+ return console.warn(`Element for offset not found (#${hash})`);
297
+ }
298
+
299
+ updateHistoryRecord(url + hash);
300
+ }
301
+
302
+ triggerWillOpenNewWindow(triggerEl: Element) {
303
+ if (triggerEl.matches('[download], [target="_blank"]')) {
304
+ return true;
305
+ }
306
+ return false;
307
+ }
308
+
309
+ popStateHandler(event: PopStateEvent) {
310
+ // Exit early if this event should be ignored
311
+ if (this.options.skipPopStateHandling(event)) {
312
+ return;
313
+ }
314
+
315
+ // Exit early if the resolved path hasn't changed
316
+ if (this.isSameResolvedUrl(getCurrentUrl(), this.currentPageUrl)) {
317
+ return;
318
+ }
319
+
320
+ const { url, hash } = Location.fromUrl(event.state?.url ?? location.href);
321
+
322
+ if (hash) {
323
+ this.scrollToElement = hash;
324
+ } else {
325
+ event.preventDefault();
326
+ }
327
+
328
+ this.triggerEvent('popState', event);
329
+
330
+ if (!this.options.animateHistoryBrowsing) {
331
+ document.documentElement.classList.remove('is-animating');
332
+ cleanupAnimationClasses();
333
+ }
334
+
335
+ this.loadPage({ url }, event);
336
+ }
337
+
338
+ /**
339
+ * Utility function to validate and run the global option 'resolveUrl'
340
+ * @param {string} url
341
+ * @returns {string} the resolved url
342
+ */
343
+ resolveUrl(url: string) {
344
+ if (typeof this.options.resolveUrl !== 'function') {
345
+ console.warn(`[swup] options.resolveUrl expects a callback function.`);
346
+ return url;
347
+ }
348
+ const result = this.options.resolveUrl(url);
349
+ if (!result || typeof result !== 'string') {
350
+ console.warn(`[swup] options.resolveUrl needs to return a url`);
351
+ return url;
352
+ }
353
+ if (result.startsWith('//') || result.startsWith('http')) {
354
+ console.warn(`[swup] options.resolveUrl needs to return a relative url`);
355
+ return url;
356
+ }
357
+ return result;
358
+ }
359
+
360
+ /**
361
+ * Compares the resolved version of two paths and returns true if they are the same
362
+ * @param {string} url1
363
+ * @param {string} url2
364
+ * @returns {boolean}
365
+ */
366
+ isSameResolvedUrl(url1: string, url2: string) {
367
+ return this.resolveUrl(url1) === this.resolveUrl(url2);
368
+ }
369
+ }
@@ -0,0 +1,57 @@
1
+ import { getCurrentUrl, Location } from '../helpers.js';
2
+ import Swup from '../index.js';
3
+ import { PageData } from './getPageData.js';
4
+
5
+ export interface PageRecord extends PageData {
6
+ url: string;
7
+ responseURL: string;
8
+ }
9
+ export class Cache {
10
+ pages: Record<string, PageRecord> = {};
11
+ last: PageRecord | null = null;
12
+ swup: Swup;
13
+
14
+ constructor(swup: Swup) {
15
+ this.swup = swup;
16
+ }
17
+
18
+ getCacheUrl(url: string): string {
19
+ return this.swup.resolveUrl(Location.fromUrl(url).url);
20
+ }
21
+
22
+ cacheUrl(page: PageRecord) {
23
+ page.url = this.getCacheUrl(page.url);
24
+ if (page.url in this.pages === false) {
25
+ this.pages[page.url] = page;
26
+ }
27
+ page.responseURL = this.getCacheUrl(page.responseURL);
28
+ this.last = this.pages[page.url];
29
+ this.swup.log(`Cache (${Object.keys(this.pages).length})`, this.pages);
30
+ }
31
+
32
+ getPage(url: string): PageRecord {
33
+ url = this.getCacheUrl(url);
34
+ return this.pages[url];
35
+ }
36
+
37
+ getCurrentPage(): PageRecord {
38
+ return this.getPage(getCurrentUrl());
39
+ }
40
+
41
+ exists(url: string): boolean {
42
+ url = this.getCacheUrl(url);
43
+ return url in this.pages;
44
+ }
45
+
46
+ empty(): void {
47
+ this.pages = {};
48
+ this.last = null;
49
+ this.swup.log('Cache cleared');
50
+ }
51
+
52
+ remove(url: string): void {
53
+ delete this.pages[this.getCacheUrl(url)];
54
+ }
55
+ }
56
+
57
+ export default Cache;
@@ -0,0 +1,28 @@
1
+ import { nextTick } from '../utils.js';
2
+ import Swup from '../index.js';
3
+
4
+ const enterPage = function(
5
+ this: Swup,
6
+ { popstate, skipTransition }: { popstate?: PopStateEvent; skipTransition?: boolean }
7
+ ) {
8
+ if (skipTransition) {
9
+ this.triggerEvent('transitionEnd', popstate);
10
+ this.cleanupAnimationClasses();
11
+ return [Promise.resolve()];
12
+ }
13
+
14
+ nextTick(() => {
15
+ this.triggerEvent('animationInStart');
16
+ document.documentElement.classList.remove('is-animating');
17
+ });
18
+
19
+ const animationPromises = this.getAnimationPromises('in');
20
+ Promise.all(animationPromises).then(() => {
21
+ this.triggerEvent('animationInDone');
22
+ this.triggerEvent('transitionEnd', popstate);
23
+ this.cleanupAnimationClasses();
24
+ });
25
+ return animationPromises;
26
+ };
27
+
28
+ export default enterPage;
@@ -0,0 +1,35 @@
1
+ import Swup from '../index.js';
2
+ import { fetch } from '../helpers.js';
3
+ import { TransitionOptions } from './loadPage.js';
4
+ import { PageRecord } from './Cache.js';
5
+
6
+ export default function fetchPage(this: Swup, data: TransitionOptions): Promise<PageRecord> {
7
+ const headers = this.options.requestHeaders;
8
+ const { url } = data;
9
+
10
+ if (this.cache.exists(url)) {
11
+ this.triggerEvent('pageRetrievedFromCache');
12
+ return Promise.resolve(this.cache.getPage(url));
13
+ }
14
+
15
+ return new Promise((resolve, reject) => {
16
+ fetch({ ...data, headers }, (response) => {
17
+ if (response.status === 500) {
18
+ this.triggerEvent('serverError');
19
+ reject(url);
20
+ return;
21
+ }
22
+ // get json data
23
+ const page = this.getPageData(response);
24
+ if (!page || !page.blocks.length) {
25
+ reject(url);
26
+ return;
27
+ }
28
+ // render page
29
+ const cacheablePageData = { ...page, url };
30
+ this.cache.cacheUrl(cacheablePageData);
31
+ this.triggerEvent('pageLoaded');
32
+ resolve(cacheablePageData);
33
+ });
34
+ });
35
+ }
@@ -0,0 +1,19 @@
1
+ import { escapeCssIdentifier, query } from '../utils.js';
2
+
3
+ const getAnchorElement = (hash: string): Element | null => {
4
+ if (!hash) {
5
+ return null;
6
+ }
7
+
8
+ if (hash.charAt(0) === '#') {
9
+ hash = hash.substring(1);
10
+ }
11
+
12
+ hash = decodeURIComponent(hash);
13
+ hash = escapeCssIdentifier(hash);
14
+
15
+ // https://html.spec.whatwg.org/#find-a-potential-indicated-element
16
+ return query(`#${hash}`) || query(`a[name='${hash}']`);
17
+ };
18
+
19
+ export default getAnchorElement;
@@ -0,0 +1,176 @@
1
+ import { queryAll, toMs } from '../utils.js';
2
+ import Swup from '../index.js';
3
+
4
+ // Transition property/event sniffing
5
+ let transitionProp = 'transition';
6
+ let transitionEndEvent = 'transitionend';
7
+ let animationProp = 'animation';
8
+ let animationEndEvent = 'animationend';
9
+
10
+ if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
11
+ transitionProp = 'WebkitTransition';
12
+ transitionEndEvent = 'webkitTransitionEnd';
13
+ }
14
+
15
+ if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
16
+ animationProp = 'WebkitAnimation';
17
+ animationEndEvent = 'webkitAnimationEnd';
18
+ }
19
+
20
+ export default function getAnimationPromises(
21
+ this: Swup,
22
+ // we don't use this argument, but JS plugin depends on it with
23
+ // its own version of getAnimationPromises, so it must be specified when
24
+ // getAnimationPromises is being used
25
+ animationType: 'in' | 'out'
26
+ ): Promise<void>[] {
27
+ const selector = this.options.animationSelector;
28
+
29
+ // Allow usage of swup without animations
30
+ if (selector === false) {
31
+ // Use array of a single resolved promise instead of an empty array to allow
32
+ // possible future use with Promise.race() which requires an actual value
33
+ return [Promise.resolve()];
34
+ }
35
+
36
+ const animatedElements = queryAll(selector, document.body);
37
+
38
+ // Warn if no animated containers found on page, but keep things going
39
+ if (!animatedElements.length) {
40
+ console.warn(`[swup] No animated elements found by selector ${selector}`);
41
+ return [Promise.resolve()];
42
+ }
43
+
44
+ return animatedElements.map((element) => getAnimationPromiseForElement(element, selector));
45
+ }
46
+
47
+ const isTransitionOrAnimationEvent = (event: any): event is TransitionEvent | AnimationEvent =>
48
+ !!event.elapsedTime;
49
+
50
+ function getAnimationPromiseForElement(
51
+ element: Element,
52
+ selector: string,
53
+ expectedType: 'animation' | 'transition' | null = null
54
+ ): Promise<void> {
55
+ const { type, timeout, propCount } = getTransitionInfo(element, expectedType);
56
+
57
+ // Resolve immediately if no transition defined
58
+ if (!type || !timeout) {
59
+ console.warn(
60
+ `[swup] No CSS transition duration defined for element of selector ${selector}`
61
+ );
62
+ return Promise.resolve();
63
+ }
64
+
65
+ return new Promise((resolve) => {
66
+ const endEvent = type === 'transition' ? transitionEndEvent : animationEndEvent;
67
+ const startTime = performance.now();
68
+ let propsTransitioned = 0;
69
+
70
+ const end = () => {
71
+ element.removeEventListener(endEvent, onEnd);
72
+ resolve();
73
+ };
74
+
75
+ const onEnd: EventListener = (event) => {
76
+ // Skip transitions on child elements
77
+ if (event.target !== element) {
78
+ return;
79
+ }
80
+
81
+ if (!isTransitionOrAnimationEvent(event)) {
82
+ throw new Error('Not a transition or animation event.');
83
+ }
84
+
85
+ // Skip transitions that happened before we started listening
86
+ const elapsedTime = (performance.now() - startTime) / 1000;
87
+ if (elapsedTime < event.elapsedTime) {
88
+ return;
89
+ }
90
+
91
+ // End if all properties have transitioned
92
+ if (++propsTransitioned >= propCount) {
93
+ end();
94
+ }
95
+ };
96
+
97
+ setTimeout(() => {
98
+ if (propsTransitioned < propCount) {
99
+ end();
100
+ }
101
+ }, timeout + 1);
102
+
103
+ element.addEventListener(endEvent, onEnd);
104
+ });
105
+ }
106
+
107
+ export function getTransitionInfo(
108
+ element: Element,
109
+ expectedType: 'animation' | 'transition' | null = null
110
+ ) {
111
+ const styles = window.getComputedStyle(element);
112
+
113
+ // not sure what to do about the below mess other than casting, but it's a mess
114
+ const transitionDelay = `${transitionProp}Delay` as keyof CSSStyleDeclaration;
115
+ const transitionDuration = `${transitionProp}Duration` as keyof CSSStyleDeclaration;
116
+ const animationDelay = `${animationProp}Delay` as keyof CSSStyleDeclaration;
117
+ const animationDuration = `${animationProp}Duration` as keyof CSSStyleDeclaration;
118
+
119
+ const transitionDelays = (styles[
120
+ transitionDelay
121
+ ] as CSSStyleDeclaration['transitionDelay']).split(', ');
122
+ const transitionDurations = ((styles[transitionDuration] ||
123
+ '') as CSSStyleDeclaration['transitionDuration']).split(', ');
124
+ const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations);
125
+
126
+ const animationDelays = ((styles[animationDelay] ||
127
+ '') as CSSStyleDeclaration['animationDelay']).split(', ');
128
+ const animationDurations = ((styles[animationDuration] ||
129
+ '') as CSSStyleDeclaration['animationDuration']).split(', ');
130
+ const animationTimeout = calculateTimeout(animationDelays, animationDurations);
131
+
132
+ let type: string | null = '';
133
+ let timeout = 0;
134
+ let propCount = 0;
135
+
136
+ if (expectedType === 'transition') {
137
+ if (transitionTimeout > 0) {
138
+ type = 'transition';
139
+ timeout = transitionTimeout;
140
+ propCount = transitionDurations.length;
141
+ }
142
+ } else if (expectedType === 'animation') {
143
+ if (animationTimeout > 0) {
144
+ type = 'animation';
145
+ timeout = animationTimeout;
146
+ propCount = animationDurations.length;
147
+ }
148
+ } else {
149
+ timeout = Math.max(transitionTimeout, animationTimeout);
150
+ type =
151
+ timeout > 0
152
+ ? transitionTimeout > animationTimeout
153
+ ? 'transition'
154
+ : 'animation'
155
+ : null;
156
+ propCount = type
157
+ ? type === 'transition'
158
+ ? transitionDurations.length
159
+ : animationDurations.length
160
+ : 0;
161
+ }
162
+
163
+ return {
164
+ type,
165
+ timeout,
166
+ propCount
167
+ };
168
+ }
169
+
170
+ function calculateTimeout(delays: string[], durations: string[]) {
171
+ while (delays.length < durations.length) {
172
+ delays = delays.concat(delays);
173
+ }
174
+
175
+ return Math.max(...durations.map((duration, i) => toMs(duration) + toMs(delays[i])));
176
+ }