swup 2.0.19 → 3.0.0-rc.2

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 (117) hide show
  1. package/dist/Swup.cjs +2 -0
  2. package/dist/Swup.cjs.map +1 -0
  3. package/dist/Swup.modern.js +2 -0
  4. package/dist/Swup.modern.js.map +1 -0
  5. package/dist/Swup.module.js +2 -0
  6. package/dist/Swup.module.js.map +1 -0
  7. package/dist/Swup.umd.js +2 -0
  8. package/dist/Swup.umd.js.map +1 -0
  9. package/dist/helpers.cjs +2 -0
  10. package/dist/helpers.cjs.map +1 -0
  11. package/dist/helpers.modern.js +2 -0
  12. package/dist/helpers.modern.js.map +1 -0
  13. package/dist/helpers.module.js +2 -0
  14. package/dist/helpers.module.js.map +1 -0
  15. package/dist/types/Swup.d.ts +102 -0
  16. package/dist/types/config/version.d.ts +5 -0
  17. package/dist/types/helpers/Location.d.ts +31 -0
  18. package/dist/types/helpers/classify.d.ts +2 -0
  19. package/dist/types/helpers/cleanupAnimationClasses.d.ts +2 -0
  20. package/dist/types/helpers/createHistoryRecord.d.ts +2 -0
  21. package/dist/types/helpers/delegateEvent.d.ts +8 -0
  22. package/dist/types/helpers/fetch.d.ts +6 -0
  23. package/dist/types/helpers/getCurrentUrl.d.ts +4 -0
  24. package/dist/types/helpers/getDataFromHtml.d.ts +8 -0
  25. package/dist/types/helpers/index.d.ts +11 -0
  26. package/dist/types/helpers/markSwupElements.d.ts +2 -0
  27. package/dist/types/helpers/updateHistoryRecord.d.ts +2 -0
  28. package/dist/types/helpers/versionSatisfies.d.ts +12 -0
  29. package/dist/types/helpers.d.ts +1 -0
  30. package/dist/types/index.d.ts +8 -0
  31. package/dist/types/modules/Cache.d.ts +20 -0
  32. package/dist/types/modules/enterPage.d.ts +6 -0
  33. package/dist/types/modules/fetchPage.d.ts +4 -0
  34. package/dist/types/modules/getAnchorElement.d.ts +2 -0
  35. package/dist/types/modules/getAnimationPromises.d.ts +7 -0
  36. package/dist/types/modules/getPageData.d.ts +7 -0
  37. package/dist/types/modules/leavePage.d.ts +7 -0
  38. package/dist/types/modules/loadPage.d.ts +7 -0
  39. package/dist/types/modules/off.d.ts +4 -0
  40. package/dist/types/modules/on.d.ts +6 -0
  41. package/dist/types/modules/plugins.d.ts +14 -0
  42. package/dist/types/modules/renderPage.d.ts +7 -0
  43. package/dist/types/modules/replaceContent.d.ts +17 -0
  44. package/dist/types/modules/triggerEvent.d.ts +4 -0
  45. package/dist/types/modules/updateTransition.d.ts +3 -0
  46. package/dist/types/utils/index.d.ts +5 -0
  47. package/dist/types/utils.d.ts +1 -0
  48. package/dist/utils.cjs +2 -0
  49. package/dist/utils.cjs.map +1 -0
  50. package/dist/utils.modern.js +2 -0
  51. package/dist/utils.modern.js.map +1 -0
  52. package/dist/utils.module.js +2 -0
  53. package/dist/utils.module.js.map +1 -0
  54. package/package.json +30 -23
  55. package/readme.md +52 -36
  56. package/src/Swup.ts +369 -0
  57. package/src/config/version.ts +13 -0
  58. package/src/helpers/Location.ts +44 -0
  59. package/src/helpers/classify.ts +13 -0
  60. package/src/helpers/cleanupAnimationClasses.ts +10 -0
  61. package/src/helpers/createHistoryRecord.ts +14 -0
  62. package/src/helpers/delegateEvent.ts +23 -0
  63. package/src/helpers/fetch.ts +35 -0
  64. package/src/helpers/getCurrentUrl.ts +5 -0
  65. package/src/helpers/getDataFromHtml.ts +41 -0
  66. package/src/helpers/index.ts +11 -0
  67. package/src/helpers/markSwupElements.ts +18 -0
  68. package/src/helpers/updateHistoryRecord.ts +18 -0
  69. package/src/helpers/versionSatisfies.ts +46 -0
  70. package/src/helpers.ts +4 -0
  71. package/src/index.ts +8 -0
  72. package/src/modules/Cache.ts +57 -0
  73. package/src/modules/enterPage.ts +28 -0
  74. package/src/modules/fetchPage.ts +35 -0
  75. package/src/modules/getAnchorElement.ts +19 -0
  76. package/src/modules/getAnimationPromises.ts +179 -0
  77. package/src/modules/getPageData.ts +26 -0
  78. package/src/modules/leavePage.ts +33 -0
  79. package/src/modules/loadPage.ts +55 -0
  80. package/src/modules/off.ts +23 -0
  81. package/src/modules/on.ts +35 -0
  82. package/src/modules/plugins.ts +58 -0
  83. package/src/modules/renderPage.ts +52 -0
  84. package/src/modules/replaceContent.ts +28 -0
  85. package/src/modules/triggerEvent.ts +23 -0
  86. package/src/modules/updateTransition.ts +7 -0
  87. package/src/utils/index.ts +32 -0
  88. package/src/utils.ts +4 -0
  89. package/.editorconfig +0 -19
  90. package/cypress.config.js +0 -14
  91. package/dist/swup.js +0 -1524
  92. package/dist/swup.min.js +0 -1
  93. package/lib/helpers/Link.js +0 -56
  94. package/lib/helpers/classify.js +0 -18
  95. package/lib/helpers/cleanupAnimationClasses.js +0 -18
  96. package/lib/helpers/createHistoryRecord.js +0 -14
  97. package/lib/helpers/fetch.js +0 -41
  98. package/lib/helpers/getCurrentUrl.js +0 -10
  99. package/lib/helpers/getDataFromHtml.js +0 -43
  100. package/lib/helpers/index.js +0 -64
  101. package/lib/helpers/markSwupElements.js +0 -24
  102. package/lib/helpers/normalizeUrl.js +0 -17
  103. package/lib/helpers/transitionEnd.js +0 -14
  104. package/lib/helpers/transitionProperty.js +0 -14
  105. package/lib/index.js +0 -305
  106. package/lib/modules/Cache.js +0 -66
  107. package/lib/modules/getAnchorElement.js +0 -25
  108. package/lib/modules/getAnimationPromises.js +0 -43
  109. package/lib/modules/getPageData.js +0 -26
  110. package/lib/modules/loadPage.js +0 -123
  111. package/lib/modules/off.js +0 -34
  112. package/lib/modules/on.js +0 -14
  113. package/lib/modules/plugins.js +0 -54
  114. package/lib/modules/renderPage.js +0 -76
  115. package/lib/modules/triggerEvent.js +0 -21
  116. package/lib/modules/updateTransition.js +0 -15
  117. package/lib/utils/index.js +0 -32
package/src/Swup.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,13 @@
1
+ /**
2
+ * Import swup's current version from package.json
3
+ */
4
+
5
+ // This will only work in webpack 4
6
+ // export { version as default } from '../../package.json';
7
+
8
+ // This will work in microbundle + webpack 5, but won't treeshake in webpack 4
9
+ // Ignore next line in TypeScript as package.json is outside of rootDir
10
+ // @ts-ignore
11
+ import pckg from '../../package.json';
12
+
13
+ export default pckg.version;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A helper for creating a Location from either an element
3
+ * or a URL object/string
4
+ *
5
+ * Note: this could be implemented as a class inheriting from URL
6
+ * Except: Babel will add tons of boilerplate for ES6 classes + getters
7
+ * So for now it's implemented as an augmented URL object with custom getter
8
+ *
9
+ * class Location extends URL {
10
+ * get url() {
11
+ * return this.pathname + this.search;
12
+ * }
13
+ * }
14
+ *
15
+ */
16
+
17
+ export default class Location extends URL {
18
+ constructor(url: string, base: string = document.baseURI) {
19
+ super(url.toString(), base);
20
+ }
21
+
22
+ get url() {
23
+ return this.pathname + this.search;
24
+ }
25
+
26
+ /**
27
+ * Instantiate a Location from an element's href attribute
28
+ * @param {Element} el
29
+ * @return new Location instance
30
+ */
31
+ static fromElement(el: HTMLAnchorElement): Location {
32
+ const href = el.getAttribute('href') || el.getAttribute('xlink:href');
33
+ return new Location(href!);
34
+ }
35
+
36
+ /**
37
+ * Instantiate a Location from a URL object or string
38
+ * @param {URL|string} url
39
+ * @return new Location instance
40
+ */
41
+ static fromUrl(url: string): Location {
42
+ return new Location(url);
43
+ }
44
+ }
@@ -0,0 +1,13 @@
1
+ const classify = (text: string, fallback?: string): string => {
2
+ const output = String(text)
3
+ .toLowerCase()
4
+ // .normalize('NFD') // split an accented letter in the base letter and the acent
5
+ // .replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
6
+ .replace(/[\s/_.]+/g, '-') // replace spaces and _./ with '-'
7
+ .replace(/[^\w-]+/g, '') // remove all non-word chars
8
+ .replace(/--+/g, '-') // replace repeating '-' with single '-'
9
+ .replace(/^-+|-+$/, ''); // trim '-' from edges
10
+ return output || fallback || '';
11
+ };
12
+
13
+ export default classify;
@@ -0,0 +1,10 @@
1
+ const isSwupClass = (className: string): boolean =>
2
+ /^to-/.test(className) || ['is-changing', 'is-rendering', 'is-popstate'].includes(className);
3
+
4
+ const cleanupAnimationClasses = (): void => {
5
+ const htmlClasses = document.documentElement.className.split(' ');
6
+ const removeClasses = htmlClasses.filter(isSwupClass);
7
+ document.documentElement.classList.remove(...removeClasses);
8
+ };
9
+
10
+ export default cleanupAnimationClasses;
@@ -0,0 +1,14 @@
1
+ import { default as getCurrentUrl } from './getCurrentUrl.js';
2
+
3
+ const createHistoryRecord = (url: string, customData: Record<string, unknown> = {}): void => {
4
+ url = url || getCurrentUrl({ hash: true });
5
+ const data = {
6
+ url,
7
+ random: Math.random(),
8
+ source: 'swup',
9
+ ...customData
10
+ };
11
+ history.pushState(data, '', url);
12
+ };
13
+
14
+ export default createHistoryRecord;
@@ -0,0 +1,23 @@
1
+ import delegate, { EventType } from 'delegate-it';
2
+ import { ParseSelector } from 'typed-query-selector/parser';
3
+
4
+ export type Unsubscribe = {
5
+ destroy: () => void;
6
+ };
7
+ const delegateEvent = <Selector extends string, TEvent extends EventType>(
8
+ selector: Selector,
9
+ type: TEvent,
10
+ callback: delegate.EventHandler<GlobalEventHandlersEventMap[TEvent]>,
11
+ { base = document, ...eventOptions } = {}
12
+ ): Unsubscribe => {
13
+ const delegation = delegate<string, ParseSelector<Selector, HTMLElement>, TEvent>(
14
+ base,
15
+ selector,
16
+ type,
17
+ callback,
18
+ eventOptions
19
+ );
20
+ return { destroy: () => delegation.destroy() };
21
+ };
22
+
23
+ export default delegateEvent;
@@ -0,0 +1,35 @@
1
+ import { TransitionOptions } from '../modules/loadPage.js';
2
+ import { Options } from '../Swup.js';
3
+
4
+ const fetch = (
5
+ options: TransitionOptions & { headers: Options['requestHeaders'] },
6
+ callback: (request: XMLHttpRequest) => void
7
+ ): XMLHttpRequest => {
8
+ const defaults = {
9
+ url: window.location.pathname + window.location.search,
10
+ method: 'GET',
11
+ data: null,
12
+ headers: {}
13
+ };
14
+
15
+ const { url, method, headers, data } = { ...defaults, ...options };
16
+
17
+ const request = new XMLHttpRequest();
18
+
19
+ request.onreadystatechange = function () {
20
+ if (request.readyState === 4) {
21
+ // if (request.status === 500) {} else {}
22
+ callback(request);
23
+ }
24
+ };
25
+
26
+ request.open(method, url, true);
27
+ Object.entries(headers).forEach(([key, header]) => {
28
+ request.setRequestHeader(key, header);
29
+ });
30
+ request.send(data);
31
+
32
+ return request;
33
+ };
34
+
35
+ export default fetch;
@@ -0,0 +1,5 @@
1
+ const getCurrentUrl = ({ hash }: { hash?: boolean } = {}): string => {
2
+ return location.pathname + location.search + (hash ? location.hash : '');
3
+ };
4
+
5
+ export default getCurrentUrl;
@@ -0,0 +1,41 @@
1
+ import { query, queryAll } from '../utils.js';
2
+
3
+ export type PageHtmlData = {
4
+ title: string;
5
+ originalContent: string;
6
+ blocks: string[];
7
+ pageClass?: string;
8
+ };
9
+
10
+ const getDataFromHtml = (html: string, containers: string[]): PageHtmlData => {
11
+ let fakeDom = document.createElement('html');
12
+ fakeDom.innerHTML = html;
13
+ let blocks: string[] = [];
14
+
15
+ containers.forEach((selector) => {
16
+ if (query(selector, fakeDom) == null) {
17
+ console.warn(`[swup] Container ${selector} not found on page.`);
18
+ return null;
19
+ } else {
20
+ if (queryAll(selector).length !== queryAll(selector, fakeDom).length) {
21
+ console.warn(`[swup] Mismatched number of containers found on new page.`);
22
+ }
23
+ queryAll(selector).forEach((item, index) => {
24
+ queryAll(selector, fakeDom)[index].setAttribute('data-swup', String(blocks.length));
25
+ blocks.push(queryAll(selector, fakeDom)[index].outerHTML);
26
+ });
27
+ }
28
+ });
29
+
30
+ const title = query('title', fakeDom)?.innerText || '';
31
+ const pageClass = query('body', fakeDom)?.className;
32
+
33
+ // to prevent memory leaks
34
+ fakeDom.innerHTML = '';
35
+ // @ts-ignore don't want to type it as possible null, since it's created at the top of the function always
36
+ fakeDom = null;
37
+
38
+ return { title, pageClass, blocks, originalContent: html };
39
+ };
40
+
41
+ export default getDataFromHtml;
@@ -0,0 +1,11 @@
1
+ export { default as classify } from './classify.js';
2
+ export { default as createHistoryRecord } from './createHistoryRecord.js';
3
+ export { default as updateHistoryRecord } from './updateHistoryRecord.js';
4
+ export { default as delegateEvent } from './delegateEvent.js';
5
+ export { default as getDataFromHtml } from './getDataFromHtml.js';
6
+ export { default as fetch } from './fetch.js';
7
+ export { default as getCurrentUrl } from './getCurrentUrl.js';
8
+ export { default as Location } from './Location.js';
9
+ export { default as markSwupElements } from './markSwupElements.js';
10
+ export { default as cleanupAnimationClasses } from './cleanupAnimationClasses.js';
11
+ export { default as versionSatisfies } from './versionSatisfies.js';
@@ -0,0 +1,18 @@
1
+ import { query, queryAll } from '../utils.js';
2
+
3
+ const markSwupElements = (element: Element, containers: string[]): void => {
4
+ let blocks = 0;
5
+
6
+ containers.forEach((selector) => {
7
+ if (query(selector, element) == null) {
8
+ console.warn(`[swup] Container ${selector} not found on page.`);
9
+ } else {
10
+ queryAll(selector).forEach((item: Element, index: number) => {
11
+ queryAll(selector, element)[index].setAttribute('data-swup', String(blocks));
12
+ blocks++;
13
+ });
14
+ }
15
+ });
16
+ };
17
+
18
+ export default markSwupElements;
@@ -0,0 +1,18 @@
1
+ import { default as getCurrentUrl } from './getCurrentUrl.js';
2
+
3
+ const updateHistoryRecord = (
4
+ url: string | null = null,
5
+ customData: Record<string, unknown> = {}
6
+ ): void => {
7
+ url = url || getCurrentUrl({ hash: true });
8
+ const data = {
9
+ ...history.state,
10
+ url,
11
+ random: Math.random(),
12
+ source: 'swup',
13
+ ...customData
14
+ };
15
+ history.replaceState(data, '', url);
16
+ };
17
+
18
+ export default updateHistoryRecord;
@@ -0,0 +1,46 @@
1
+ type Comparator = '>' | '>=' | '<' | '<=';
2
+
3
+ // Fill versions to exactly 3 decimals
4
+ const normalizeVersion = (version: string): string => {
5
+ return String(version).split('.').concat(['0', '0']).slice(0, 3).join('.');
6
+ };
7
+
8
+ // Numerically compare version strings after normalizing them
9
+ const compareVersion = (a: string, b: string): number => {
10
+ a = normalizeVersion(a);
11
+ b = normalizeVersion(b);
12
+ return a.localeCompare(b, undefined, { numeric: true });
13
+ };
14
+
15
+ // Apply a comparator (equals, greater-than, etc) by its symbol to a sort comparison
16
+ const applyComparator = (comparisonResult: number, comparator: Comparator) => {
17
+ const comparators = {
18
+ '': (r: number) => r === 0,
19
+ '>': (r: number) => r > 0,
20
+ '>=': (r: number) => r >= 0,
21
+ '<': (r: number) => r < 0,
22
+ '<=': (r: number) => r <= 0
23
+ };
24
+ const comparatorFn = comparators[comparator] || comparators[''];
25
+ return comparatorFn(comparisonResult);
26
+ };
27
+
28
+ /**
29
+ * Check if a version satisfies all given version requirements
30
+ *
31
+ * versionSatisfies('2.1.0', ['>=2', '<4']) // true
32
+ * versionSatisfies('2.1.0', ['5']) // false
33
+ *
34
+ * @param {string} installed Installed version
35
+ * @param {Array.<string>} requirements Array of requirements that must be satisfied
36
+ * @returns boolean
37
+ */
38
+ export const versionSatisfies = (installed: string, requirements: string[]) => {
39
+ return requirements.every((required) => {
40
+ const [, comparator, version] = required.match(/^([\D]+)?(.*)$/) || [];
41
+ const comparisonResult = compareVersion(installed, version);
42
+ return applyComparator(comparisonResult, (comparator as Comparator) || '>=');
43
+ });
44
+ };
45
+
46
+ export default versionSatisfies;
package/src/helpers.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Re-export all helpers to allow custom package export path
2
+ // e.g. import { getPageData } from 'swup/helpers'
3
+
4
+ export * from './helpers/index.js';
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import Swup, { Options } from './Swup.js';
2
+
3
+ export default Swup;
4
+
5
+ export * from './helpers.js';
6
+ export * from './utils.js';
7
+
8
+ export type { Options };