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.
- package/dist/Swup.cjs +2 -0
- package/dist/Swup.cjs.map +1 -0
- package/dist/Swup.modern.js +2 -0
- package/dist/Swup.modern.js.map +1 -0
- package/dist/Swup.module.js +2 -0
- package/dist/Swup.module.js.map +1 -0
- package/dist/Swup.umd.js +2 -0
- package/dist/Swup.umd.js.map +1 -0
- package/dist/helpers.cjs +2 -0
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.modern.js +2 -0
- package/dist/helpers.modern.js.map +1 -0
- package/dist/helpers.module.js +2 -0
- package/dist/helpers.module.js.map +1 -0
- package/dist/types/Swup.d.ts +102 -0
- package/dist/types/config/version.d.ts +5 -0
- package/dist/types/helpers/Location.d.ts +31 -0
- package/dist/types/helpers/classify.d.ts +2 -0
- package/dist/types/helpers/cleanupAnimationClasses.d.ts +2 -0
- package/dist/types/helpers/createHistoryRecord.d.ts +2 -0
- package/dist/types/helpers/delegateEvent.d.ts +8 -0
- package/dist/types/helpers/fetch.d.ts +6 -0
- package/dist/types/helpers/getCurrentUrl.d.ts +4 -0
- package/dist/types/helpers/getDataFromHtml.d.ts +8 -0
- package/dist/types/helpers/index.d.ts +11 -0
- package/dist/types/helpers/markSwupElements.d.ts +2 -0
- package/dist/types/helpers/updateHistoryRecord.d.ts +2 -0
- package/dist/types/helpers/versionSatisfies.d.ts +12 -0
- package/dist/types/helpers.d.ts +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/modules/Cache.d.ts +20 -0
- package/dist/types/modules/enterPage.d.ts +6 -0
- package/dist/types/modules/fetchPage.d.ts +4 -0
- package/dist/types/modules/getAnchorElement.d.ts +2 -0
- package/dist/types/modules/getAnimationPromises.d.ts +7 -0
- package/dist/types/modules/getPageData.d.ts +7 -0
- package/dist/types/modules/leavePage.d.ts +7 -0
- package/dist/types/modules/loadPage.d.ts +7 -0
- package/dist/types/modules/off.d.ts +4 -0
- package/dist/types/modules/on.d.ts +6 -0
- package/dist/types/modules/plugins.d.ts +14 -0
- package/dist/types/modules/renderPage.d.ts +7 -0
- package/dist/types/modules/replaceContent.d.ts +17 -0
- package/dist/types/modules/triggerEvent.d.ts +4 -0
- package/dist/types/modules/updateTransition.d.ts +3 -0
- package/dist/types/utils/index.d.ts +5 -0
- package/dist/types/utils.d.ts +1 -0
- package/dist/utils.cjs +2 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.modern.js +2 -0
- package/dist/utils.modern.js.map +1 -0
- package/dist/utils.module.js +2 -0
- package/dist/utils.module.js.map +1 -0
- package/package.json +30 -23
- package/readme.md +52 -36
- package/src/Swup.ts +369 -0
- package/src/config/version.ts +13 -0
- package/src/helpers/Location.ts +44 -0
- package/src/helpers/classify.ts +13 -0
- package/src/helpers/cleanupAnimationClasses.ts +10 -0
- package/src/helpers/createHistoryRecord.ts +14 -0
- package/src/helpers/delegateEvent.ts +23 -0
- package/src/helpers/fetch.ts +35 -0
- package/src/helpers/getCurrentUrl.ts +5 -0
- package/src/helpers/getDataFromHtml.ts +41 -0
- package/src/helpers/index.ts +11 -0
- package/src/helpers/markSwupElements.ts +18 -0
- package/src/helpers/updateHistoryRecord.ts +18 -0
- package/src/helpers/versionSatisfies.ts +46 -0
- package/src/helpers.ts +4 -0
- package/src/index.ts +8 -0
- package/src/modules/Cache.ts +57 -0
- package/src/modules/enterPage.ts +28 -0
- package/src/modules/fetchPage.ts +35 -0
- package/src/modules/getAnchorElement.ts +19 -0
- package/src/modules/getAnimationPromises.ts +179 -0
- package/src/modules/getPageData.ts +26 -0
- package/src/modules/leavePage.ts +33 -0
- package/src/modules/loadPage.ts +55 -0
- package/src/modules/off.ts +23 -0
- package/src/modules/on.ts +35 -0
- package/src/modules/plugins.ts +58 -0
- package/src/modules/renderPage.ts +52 -0
- package/src/modules/replaceContent.ts +28 -0
- package/src/modules/triggerEvent.ts +23 -0
- package/src/modules/updateTransition.ts +7 -0
- package/src/utils/index.ts +32 -0
- package/src/utils.ts +4 -0
- package/.editorconfig +0 -19
- package/cypress.config.js +0 -14
- package/dist/swup.js +0 -1524
- package/dist/swup.min.js +0 -1
- package/lib/helpers/Link.js +0 -56
- package/lib/helpers/classify.js +0 -18
- package/lib/helpers/cleanupAnimationClasses.js +0 -18
- package/lib/helpers/createHistoryRecord.js +0 -14
- package/lib/helpers/fetch.js +0 -41
- package/lib/helpers/getCurrentUrl.js +0 -10
- package/lib/helpers/getDataFromHtml.js +0 -43
- package/lib/helpers/index.js +0 -64
- package/lib/helpers/markSwupElements.js +0 -24
- package/lib/helpers/normalizeUrl.js +0 -17
- package/lib/helpers/transitionEnd.js +0 -14
- package/lib/helpers/transitionProperty.js +0 -14
- package/lib/index.js +0 -305
- package/lib/modules/Cache.js +0 -66
- package/lib/modules/getAnchorElement.js +0 -25
- package/lib/modules/getAnimationPromises.js +0 -43
- package/lib/modules/getPageData.js +0 -26
- package/lib/modules/loadPage.js +0 -123
- package/lib/modules/off.js +0 -34
- package/lib/modules/on.js +0 -14
- package/lib/modules/plugins.js +0 -54
- package/lib/modules/renderPage.js +0 -76
- package/lib/modules/triggerEvent.js +0 -21
- package/lib/modules/updateTransition.js +0 -15
- 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,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