swup 2.0.18 → 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.
- 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/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.modern.js +2 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/index.umd.js +3 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/src/config/version.d.ts +5 -0
- package/dist/src/helpers/Location.d.ts +31 -0
- package/dist/src/helpers/classify.d.ts +2 -0
- package/dist/src/helpers/cleanupAnimationClasses.d.ts +2 -0
- package/dist/src/helpers/createHistoryRecord.d.ts +2 -0
- package/dist/src/helpers/delegateEvent.d.ts +8 -0
- package/dist/src/helpers/fetch.d.ts +6 -0
- package/dist/src/helpers/getCurrentUrl.d.ts +4 -0
- package/dist/src/helpers/getDataFromHtml.d.ts +8 -0
- package/dist/src/helpers/index.d.ts +11 -0
- package/dist/src/helpers/markSwupElements.d.ts +2 -0
- package/dist/src/helpers/updateHistoryRecord.d.ts +2 -0
- package/dist/src/helpers/versionSatisfies.d.ts +12 -0
- package/dist/src/helpers.d.ts +1 -0
- package/dist/src/index.d.ts +97 -0
- package/dist/src/modules/Cache.d.ts +20 -0
- package/dist/src/modules/enterPage.d.ts +6 -0
- package/dist/src/modules/getAnchorElement.d.ts +2 -0
- package/dist/src/modules/getAnimationPromises.d.ts +7 -0
- package/dist/src/modules/getPageData.d.ts +7 -0
- package/dist/src/modules/leavePage.d.ts +7 -0
- package/dist/src/modules/loadPage.d.ts +4 -0
- package/dist/src/modules/off.d.ts +4 -0
- package/dist/src/modules/on.d.ts +4 -0
- package/dist/src/modules/plugins.d.ts +13 -0
- package/dist/src/modules/renderPage.d.ts +7 -0
- package/dist/src/modules/replaceContent.d.ts +17 -0
- package/dist/src/modules/triggerEvent.d.ts +4 -0
- package/dist/src/modules/updateTransition.d.ts +3 -0
- package/dist/src/src/config/version.d.ts +5 -0
- package/dist/src/src/helpers/Location.d.ts +31 -0
- package/dist/src/src/helpers/classify.d.ts +2 -0
- package/dist/src/src/helpers/cleanupAnimationClasses.d.ts +2 -0
- package/dist/src/src/helpers/createHistoryRecord.d.ts +2 -0
- package/dist/src/src/helpers/delegateEvent.d.ts +8 -0
- package/dist/src/src/helpers/fetch.d.ts +6 -0
- package/dist/src/src/helpers/getCurrentUrl.d.ts +4 -0
- package/dist/src/src/helpers/getDataFromHtml.d.ts +8 -0
- package/dist/src/src/helpers/index.d.ts +11 -0
- package/dist/src/src/helpers/markSwupElements.d.ts +2 -0
- package/dist/src/src/helpers/updateHistoryRecord.d.ts +2 -0
- package/dist/src/src/helpers/versionSatisfies.d.ts +12 -0
- package/dist/src/src/helpers.d.ts +1 -0
- package/dist/src/src/index.d.ts +103 -0
- package/dist/src/src/modules/Cache.d.ts +20 -0
- package/dist/src/src/modules/enterPage.d.ts +6 -0
- package/dist/src/src/modules/getAnchorElement.d.ts +2 -0
- package/dist/src/src/modules/getAnimationPromises.d.ts +7 -0
- package/dist/src/src/modules/getPageData.d.ts +7 -0
- package/dist/src/src/modules/leavePage.d.ts +7 -0
- package/dist/src/src/modules/loadPage.d.ts +7 -0
- package/dist/src/src/modules/off.d.ts +4 -0
- package/dist/src/src/modules/on.d.ts +6 -0
- package/dist/src/src/modules/plugins.d.ts +14 -0
- package/dist/src/src/modules/renderPage.d.ts +7 -0
- package/dist/src/src/modules/replaceContent.d.ts +17 -0
- package/dist/src/src/modules/triggerEvent.d.ts +4 -0
- package/dist/src/src/modules/updateTransition.d.ts +3 -0
- package/dist/src/src/utils/index.d.ts +5 -0
- package/dist/src/src/utils.d.ts +1 -0
- package/dist/src/types.d.ts +12 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/types.cjs +2 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.modern.js +2 -0
- package/dist/types.modern.js.map +1 -0
- package/dist/types.module.js +2 -0
- package/dist/types.module.js.map +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 +44 -21
- package/readme.md +52 -36
- 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 +50 -0
- package/src/helpers.ts +4 -0
- package/src/index.ts +369 -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 +176 -0
- package/src/modules/getPageData.ts +26 -0
- package/src/modules/leavePage.ts +33 -0
- package/src/modules/loadPage.ts +54 -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/cypress.config.js +0 -13
- package/dist/swup.js +0 -1519
- 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 -118
- 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/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
|
+
}
|