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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getCurrentUrl, Location } from '../helpers.js';
|
|
2
|
+
import Swup from '../Swup.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 '../Swup.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 '../Swup.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,179 @@
|
|
|
1
|
+
import { queryAll, toMs } from '../utils.js';
|
|
2
|
+
import Swup from '../Swup.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 = (
|
|
120
|
+
styles[transitionDelay] as CSSStyleDeclaration['transitionDelay']
|
|
121
|
+
).split(', ');
|
|
122
|
+
const transitionDurations = (
|
|
123
|
+
(styles[transitionDuration] || '') as CSSStyleDeclaration['transitionDuration']
|
|
124
|
+
).split(', ');
|
|
125
|
+
const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations);
|
|
126
|
+
|
|
127
|
+
const animationDelays = (
|
|
128
|
+
(styles[animationDelay] || '') as CSSStyleDeclaration['animationDelay']
|
|
129
|
+
).split(', ');
|
|
130
|
+
const animationDurations = (
|
|
131
|
+
(styles[animationDuration] || '') as CSSStyleDeclaration['animationDuration']
|
|
132
|
+
).split(', ');
|
|
133
|
+
const animationTimeout = calculateTimeout(animationDelays, animationDurations);
|
|
134
|
+
|
|
135
|
+
let type: string | null = '';
|
|
136
|
+
let timeout = 0;
|
|
137
|
+
let propCount = 0;
|
|
138
|
+
|
|
139
|
+
if (expectedType === 'transition') {
|
|
140
|
+
if (transitionTimeout > 0) {
|
|
141
|
+
type = 'transition';
|
|
142
|
+
timeout = transitionTimeout;
|
|
143
|
+
propCount = transitionDurations.length;
|
|
144
|
+
}
|
|
145
|
+
} else if (expectedType === 'animation') {
|
|
146
|
+
if (animationTimeout > 0) {
|
|
147
|
+
type = 'animation';
|
|
148
|
+
timeout = animationTimeout;
|
|
149
|
+
propCount = animationDurations.length;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
timeout = Math.max(transitionTimeout, animationTimeout);
|
|
153
|
+
type =
|
|
154
|
+
timeout > 0
|
|
155
|
+
? transitionTimeout > animationTimeout
|
|
156
|
+
? 'transition'
|
|
157
|
+
: 'animation'
|
|
158
|
+
: null;
|
|
159
|
+
propCount = type
|
|
160
|
+
? type === 'transition'
|
|
161
|
+
? transitionDurations.length
|
|
162
|
+
: animationDurations.length
|
|
163
|
+
: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
type,
|
|
168
|
+
timeout,
|
|
169
|
+
propCount
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function calculateTimeout(delays: string[], durations: string[]) {
|
|
174
|
+
while (delays.length < durations.length) {
|
|
175
|
+
delays = delays.concat(delays);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return Math.max(...durations.map((duration, i) => toMs(duration) + toMs(delays[i])));
|
|
179
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getDataFromHtml } from '../helpers.js';
|
|
2
|
+
import Swup from '../Swup.js';
|
|
3
|
+
import { PageHtmlData } from '../helpers/getDataFromHtml.js';
|
|
4
|
+
|
|
5
|
+
export type PageData = PageHtmlData & {
|
|
6
|
+
responseURL: string;
|
|
7
|
+
};
|
|
8
|
+
const getPageData = function (this: Swup, request: XMLHttpRequest): PageData | null {
|
|
9
|
+
// this method can be replaced in case other content than html is expected to be received from server
|
|
10
|
+
// this function should always return { title, pageClass, originalContent, blocks, responseURL }
|
|
11
|
+
// in case page has invalid structure - return null
|
|
12
|
+
const html = request.responseText;
|
|
13
|
+
const pageHtmlData = getDataFromHtml(html, this.options.containers);
|
|
14
|
+
|
|
15
|
+
if (!pageHtmlData) {
|
|
16
|
+
console.warn('[swup] Received page is invalid.');
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
...pageHtmlData,
|
|
22
|
+
responseURL: request.responseURL || window.location.href
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default getPageData;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Swup from '../Swup.js';
|
|
2
|
+
import { TransitionOptions } from './loadPage.js';
|
|
3
|
+
|
|
4
|
+
const leavePage = function (
|
|
5
|
+
this: Swup,
|
|
6
|
+
data: TransitionOptions,
|
|
7
|
+
{ popstate, skipTransition }: { popstate: PopStateEvent | null; skipTransition?: boolean } = {
|
|
8
|
+
popstate: null
|
|
9
|
+
}
|
|
10
|
+
) {
|
|
11
|
+
if (skipTransition) {
|
|
12
|
+
this.triggerEvent('animationSkipped');
|
|
13
|
+
return [Promise.resolve()];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.triggerEvent('animationOutStart');
|
|
17
|
+
|
|
18
|
+
// handle classes
|
|
19
|
+
document.documentElement.classList.add('is-changing', 'is-leaving', 'is-animating');
|
|
20
|
+
if (popstate) {
|
|
21
|
+
document.documentElement.classList.add('is-popstate');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// animation promise stuff
|
|
25
|
+
const animationPromises: Promise<void>[] = this.getAnimationPromises('out');
|
|
26
|
+
Promise.all(animationPromises).then(() => {
|
|
27
|
+
this.triggerEvent('animationOutDone');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return animationPromises;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default leavePage;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { classify, createHistoryRecord, getCurrentUrl } from '../helpers.js';
|
|
2
|
+
import Swup from '../Swup.js';
|
|
3
|
+
import { PageRecord } from './Cache.js';
|
|
4
|
+
|
|
5
|
+
export type TransitionOptions = {
|
|
6
|
+
url: string;
|
|
7
|
+
customTransition?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const loadPage = function (this: Swup, data: TransitionOptions, popstate: PopStateEvent | null) {
|
|
11
|
+
const { url, customTransition } = data;
|
|
12
|
+
const skipTransition = !!(popstate && !this.options.animateHistoryBrowsing);
|
|
13
|
+
|
|
14
|
+
this.triggerEvent('transitionStart', popstate || undefined);
|
|
15
|
+
|
|
16
|
+
// set transition object
|
|
17
|
+
this.updateTransition(getCurrentUrl(), url, customTransition);
|
|
18
|
+
if (customTransition != null) {
|
|
19
|
+
document.documentElement.classList.add(`to-${classify(customTransition)}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// start/skip animation
|
|
23
|
+
const animationPromises = this.leavePage(data, { popstate, skipTransition });
|
|
24
|
+
|
|
25
|
+
// create history record if this is not a popstate call (with or without anchor)
|
|
26
|
+
if (!popstate) {
|
|
27
|
+
createHistoryRecord(url + (this.scrollToElement || ''));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.currentPageUrl = getCurrentUrl();
|
|
31
|
+
|
|
32
|
+
// Load page data
|
|
33
|
+
const fetchPromise = this.fetchPage(data);
|
|
34
|
+
|
|
35
|
+
// when everything is ready, render the page
|
|
36
|
+
Promise.all<PageRecord | void>([fetchPromise, ...animationPromises])
|
|
37
|
+
.then(([pageData]) => {
|
|
38
|
+
this.renderPage(pageData as PageRecord, { popstate, skipTransition });
|
|
39
|
+
})
|
|
40
|
+
.catch((errorUrl) => {
|
|
41
|
+
// Return early if errorUrl is not defined (probably aborted preload request)
|
|
42
|
+
if (errorUrl === undefined) return;
|
|
43
|
+
|
|
44
|
+
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
|
|
45
|
+
this.options.skipPopStateHandling = () => {
|
|
46
|
+
window.location = errorUrl;
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Go back to the actual page we're still at
|
|
51
|
+
history.go(-1);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default loadPage;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Swup from '../Swup.js';
|
|
2
|
+
import { EventType, Handler } from './on.js';
|
|
3
|
+
|
|
4
|
+
const off = function off(this: Swup, event?: EventType, handler?: Handler) {
|
|
5
|
+
if (event && handler) {
|
|
6
|
+
// Remove specific handler
|
|
7
|
+
if (this._handlers[event].includes(handler)) {
|
|
8
|
+
this._handlers[event] = this._handlers[event].filter((h) => h !== handler);
|
|
9
|
+
} else {
|
|
10
|
+
console.warn(`Handler for event '${event}' not found.`);
|
|
11
|
+
}
|
|
12
|
+
} else if (event) {
|
|
13
|
+
// Remove all handlers for specific event
|
|
14
|
+
this._handlers[event] = [];
|
|
15
|
+
} else {
|
|
16
|
+
// Remove all handlers for all events
|
|
17
|
+
Object.keys(this._handlers).forEach((event) => {
|
|
18
|
+
this._handlers[event as EventType] = [];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default off;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Swup from '../Swup.js';
|
|
2
|
+
|
|
3
|
+
export type EventType =
|
|
4
|
+
| 'animationInDone'
|
|
5
|
+
| 'animationInStart'
|
|
6
|
+
| 'animationOutDone'
|
|
7
|
+
| 'animationOutStart'
|
|
8
|
+
| 'animationSkipped'
|
|
9
|
+
| 'clickLink'
|
|
10
|
+
| 'contentReplaced'
|
|
11
|
+
| 'disabled'
|
|
12
|
+
| 'enabled'
|
|
13
|
+
| 'openPageInNewTab'
|
|
14
|
+
| 'pageLoaded'
|
|
15
|
+
| 'pageRetrievedFromCache'
|
|
16
|
+
| 'pageView'
|
|
17
|
+
| 'popState'
|
|
18
|
+
| 'samePage'
|
|
19
|
+
| 'samePageWithHash'
|
|
20
|
+
| 'serverError'
|
|
21
|
+
| 'transitionStart'
|
|
22
|
+
| 'transitionEnd'
|
|
23
|
+
| 'willReplaceContent';
|
|
24
|
+
export type Handler = (event?: Event) => void;
|
|
25
|
+
export type Handlers = Record<EventType, Handler[]>;
|
|
26
|
+
|
|
27
|
+
const on = function on(this: Swup, event: EventType, handler: Handler) {
|
|
28
|
+
if (this._handlers[event]) {
|
|
29
|
+
this._handlers[event].push(handler);
|
|
30
|
+
} else {
|
|
31
|
+
console.warn(`Unsupported event ${event}.`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default on;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Swup from '../Swup.js';
|
|
2
|
+
|
|
3
|
+
// this should probably just be imported from @swup/plugin, but it doesn't have type defs now
|
|
4
|
+
export type Plugin = {
|
|
5
|
+
name: string;
|
|
6
|
+
mount: () => void;
|
|
7
|
+
unmount: () => void;
|
|
8
|
+
isSwupPlugin: true;
|
|
9
|
+
swup?: Swup;
|
|
10
|
+
|
|
11
|
+
// these are possibly undefined for backward compatibility
|
|
12
|
+
_beforeMount?: () => void;
|
|
13
|
+
_afterUnmount?: () => void;
|
|
14
|
+
_checkRequirements?: () => boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const use = function (this: Swup, plugin: Plugin) {
|
|
18
|
+
if (!plugin.isSwupPlugin) {
|
|
19
|
+
console.error('Not a swup plugin instance', plugin);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
plugin.swup = this;
|
|
24
|
+
if (plugin._checkRequirements) {
|
|
25
|
+
if (!plugin._checkRequirements()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (plugin._beforeMount) {
|
|
30
|
+
plugin._beforeMount();
|
|
31
|
+
}
|
|
32
|
+
plugin.mount();
|
|
33
|
+
|
|
34
|
+
this.plugins.push(plugin);
|
|
35
|
+
|
|
36
|
+
return this.plugins;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function unuse(this: Swup, pluginOrName: Plugin | string) {
|
|
40
|
+
const plugin = this.findPlugin(pluginOrName);
|
|
41
|
+
if (!plugin) {
|
|
42
|
+
console.error('No such plugin', plugin);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
plugin.unmount();
|
|
47
|
+
if (plugin._afterUnmount) {
|
|
48
|
+
plugin._afterUnmount();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.plugins = this.plugins.filter((p) => p !== plugin);
|
|
52
|
+
|
|
53
|
+
return this.plugins;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function findPlugin(this: Swup, pluginOrName: Plugin | string) {
|
|
57
|
+
return this.plugins.find((plugin) => plugin === pluginOrName || plugin.name === pluginOrName);
|
|
58
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Location, updateHistoryRecord, getCurrentUrl } from '../helpers.js';
|
|
2
|
+
import Swup from '../Swup.js';
|
|
3
|
+
import { PageRecord } from './Cache.js';
|
|
4
|
+
|
|
5
|
+
const renderPage = function (
|
|
6
|
+
this: Swup,
|
|
7
|
+
page: PageRecord,
|
|
8
|
+
{ popstate, skipTransition }: { popstate: PopStateEvent | null; skipTransition?: boolean } = {
|
|
9
|
+
popstate: null
|
|
10
|
+
}
|
|
11
|
+
) {
|
|
12
|
+
document.documentElement.classList.remove('is-leaving');
|
|
13
|
+
|
|
14
|
+
// do nothing if another page was requested in the meantime
|
|
15
|
+
if (!this.isSameResolvedUrl(getCurrentUrl(), page.url)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { url } = Location.fromUrl(page.responseURL);
|
|
20
|
+
|
|
21
|
+
// update cache and state if the url was redirected
|
|
22
|
+
if (!this.isSameResolvedUrl(getCurrentUrl(), url)) {
|
|
23
|
+
this.cache.cacheUrl({ ...page, url });
|
|
24
|
+
this.currentPageUrl = getCurrentUrl();
|
|
25
|
+
updateHistoryRecord(url);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// only add for page loads with transitions
|
|
29
|
+
if (!skipTransition) {
|
|
30
|
+
document.documentElement.classList.add('is-rendering');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.triggerEvent('willReplaceContent', popstate || undefined);
|
|
34
|
+
|
|
35
|
+
this.replaceContent(page).then(() => {
|
|
36
|
+
this.triggerEvent('contentReplaced', popstate || undefined);
|
|
37
|
+
this.triggerEvent('pageView', popstate || undefined);
|
|
38
|
+
|
|
39
|
+
// empty cache if it's disabled (in case preload plugin filled it)
|
|
40
|
+
if (!this.options.cache) {
|
|
41
|
+
this.cache.empty();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Perform in transition
|
|
45
|
+
this.enterPage({ popstate: popstate || undefined, skipTransition });
|
|
46
|
+
|
|
47
|
+
// reset scroll-to element
|
|
48
|
+
this.scrollToElement = null;
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default renderPage;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perform the replacement of content after loading a page.
|
|
3
|
+
*
|
|
4
|
+
* This method can be replaced or augmented by plugins to allow pausing.
|
|
5
|
+
*
|
|
6
|
+
* It takes an object with the page data as return from `getPageData` and has to
|
|
7
|
+
* return a Promise that resolves once all content has been replaced and the
|
|
8
|
+
* site is ready to start animating in the new page.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} page The page object
|
|
11
|
+
* @returns Promise
|
|
12
|
+
*/
|
|
13
|
+
const replaceContent = function ({ blocks, title }: { blocks: string[]; title: string }) {
|
|
14
|
+
// Replace content blocks
|
|
15
|
+
blocks.forEach((html, i) => {
|
|
16
|
+
// we know the block exists at this point
|
|
17
|
+
const block = document.body.querySelector(`[data-swup="${i}"]`)!;
|
|
18
|
+
block.outerHTML = html;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Update browser title
|
|
22
|
+
document.title = title;
|
|
23
|
+
|
|
24
|
+
// Return a Promise to allow plugins to defer
|
|
25
|
+
return Promise.resolve();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default replaceContent;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { EventType } from './on.js';
|
|
2
|
+
import Swup from '../Swup.js';
|
|
3
|
+
|
|
4
|
+
const triggerEvent = function (
|
|
5
|
+
this: Swup,
|
|
6
|
+
eventName: EventType,
|
|
7
|
+
originalEvent?: PopStateEvent | MouseEvent
|
|
8
|
+
): void {
|
|
9
|
+
// call saved handlers with "on" method and pass originalEvent object if available
|
|
10
|
+
this._handlers[eventName].forEach((handler) => {
|
|
11
|
+
try {
|
|
12
|
+
handler(originalEvent);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error(error);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// trigger event on document with prefix "swup:"
|
|
19
|
+
const event = new CustomEvent(`swup:${eventName}`, { detail: eventName });
|
|
20
|
+
document.dispatchEvent(event);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default triggerEvent;
|