swup 3.1.0 → 4.0.0-rc.14
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 +1 -1
- package/dist/Swup.cjs.map +1 -1
- package/dist/Swup.modern.js +1 -1
- package/dist/Swup.modern.js.map +1 -1
- package/dist/Swup.module.js +1 -1
- package/dist/Swup.module.js.map +1 -1
- package/dist/Swup.umd.js +1 -1
- package/dist/Swup.umd.js.map +1 -1
- package/dist/types/Swup.d.ts +20 -20
- package/dist/types/helpers/delegateEvent.d.ts +4 -2
- package/dist/types/helpers/fetch.d.ts +2 -2
- package/dist/types/helpers.d.ts +10 -10
- package/dist/types/index.d.ts +5 -5
- package/dist/types/modules/Cache.d.ts +2 -2
- package/dist/types/modules/__test__/fetchPage.test.d.ts +1 -0
- package/dist/types/modules/destroy.d.ts +2 -0
- package/dist/types/modules/enable.d.ts +2 -0
- package/dist/types/modules/enterPage.d.ts +2 -2
- package/dist/types/modules/events.d.ts +6 -6
- package/dist/types/modules/fetchPage.d.ts +3 -3
- package/dist/types/modules/getAnchorElement.d.ts +0 -7
- package/dist/types/modules/getAnimationPromises.d.ts +1 -1
- package/dist/types/modules/getPageData.d.ts +2 -2
- package/dist/types/modules/handleLinkToSamePage.d.ts +2 -0
- package/dist/types/modules/isSameResolvedUrl.d.ts +8 -0
- package/dist/types/modules/leavePage.d.ts +2 -2
- package/dist/types/modules/linkClickHandler.d.ts +3 -0
- package/dist/types/modules/loadPage.d.ts +2 -5
- package/dist/types/modules/off.d.ts +3 -0
- package/dist/types/modules/on.d.ts +5 -0
- package/dist/types/modules/plugins.d.ts +1 -1
- package/dist/types/modules/popStateHandler.d.ts +2 -0
- package/dist/types/modules/renderPage.d.ts +2 -2
- package/dist/types/modules/resolveUrl.d.ts +7 -0
- package/dist/types/modules/shouldIgnoreVisit.d.ts +4 -0
- package/dist/types/modules/transitions.d.ts +1 -1
- package/dist/types/modules/triggerEvent.d.ts +3 -0
- package/dist/types/modules/triggerWillOpenNewWindow.d.ts +2 -0
- package/dist/types/modules/updateTransition.d.ts +2 -0
- package/dist/types/utils.d.ts +1 -1
- package/package.json +8 -5
- package/readme.md +30 -12
- package/src/Swup.ts +115 -143
- package/src/__test__/index.test.ts +6 -1
- package/src/helpers/Location.ts +10 -7
- package/src/helpers/__test__/matchPath.test.ts +54 -0
- package/src/helpers/delegateEvent.ts +1 -0
- package/src/helpers/matchPath.ts +22 -0
- package/src/helpers.ts +2 -4
- package/src/index.ts +7 -4
- package/src/modules/Cache.ts +43 -33
- package/src/modules/Context.ts +91 -0
- package/src/modules/Hooks.ts +393 -0
- package/src/modules/__test__/cache.test.ts +142 -0
- package/src/modules/__test__/hooks.test.ts +192 -0
- package/src/modules/enterPage.ts +19 -17
- package/src/modules/fetchPage.ts +74 -29
- package/src/modules/getAnchorElement.ts +8 -4
- package/src/modules/getAnimationPromises.ts +66 -75
- package/src/modules/leavePage.ts +22 -20
- package/src/modules/loadPage.ts +72 -54
- package/src/modules/renderPage.ts +41 -32
- package/src/modules/replaceContent.ts +26 -17
- package/src/utils/index.ts +24 -3
- package/src/helpers/fetch.ts +0 -33
- package/src/helpers/getDataFromHtml.ts +0 -39
- package/src/helpers/markSwupElements.ts +0 -16
- package/src/modules/__test__/events.test.ts +0 -72
- package/src/modules/events.ts +0 -92
- package/src/modules/getPageData.ts +0 -24
- package/src/modules/transitions.ts +0 -10
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import Swup from '../../Swup.js';
|
|
3
|
+
import { Handler, Hooks } from '../Hooks.js';
|
|
4
|
+
import { Context } from '../Context.js';
|
|
5
|
+
|
|
6
|
+
describe('Hook registry', () => {
|
|
7
|
+
it('should add custom handlers', () => {
|
|
8
|
+
const swup = new Swup();
|
|
9
|
+
const handler = vi.fn();
|
|
10
|
+
|
|
11
|
+
// Make private fields public for this test
|
|
12
|
+
const HooksWithAccess = class extends Hooks {
|
|
13
|
+
getRegistry() {
|
|
14
|
+
return this.registry;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const hooks = new HooksWithAccess(swup);
|
|
18
|
+
|
|
19
|
+
hooks.on('enabled', handler);
|
|
20
|
+
const ledger = hooks.getRegistry().get('enabled');
|
|
21
|
+
|
|
22
|
+
expect(ledger).toBeDefined();
|
|
23
|
+
expect(ledger).toBeInstanceOf(Map);
|
|
24
|
+
expect(ledger!.size).toBe(1);
|
|
25
|
+
|
|
26
|
+
const registrations = Array.from(ledger!.values());
|
|
27
|
+
const registration = registrations.find((reg) => reg.handler === handler);
|
|
28
|
+
|
|
29
|
+
expect(registration?.handler).toEqual(handler);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return the passed handler', () => {
|
|
33
|
+
const swup = new Swup();
|
|
34
|
+
const handler = vi.fn();
|
|
35
|
+
|
|
36
|
+
const handlerReturned = swup.hooks.on('enabled', handler);
|
|
37
|
+
|
|
38
|
+
expect(handlerReturned).toEqual(handler);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should trigger custom handlers', async () => {
|
|
42
|
+
const swup = new Swup();
|
|
43
|
+
const handler = vi.fn();
|
|
44
|
+
|
|
45
|
+
swup.hooks.on('enabled', handler);
|
|
46
|
+
|
|
47
|
+
await swup.hooks.trigger('enabled');
|
|
48
|
+
|
|
49
|
+
expect(handler).toBeCalledTimes(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should only trigger custom handlers once if requested', async () => {
|
|
53
|
+
const swup = new Swup();
|
|
54
|
+
const handler = vi.fn();
|
|
55
|
+
|
|
56
|
+
swup.hooks.on('enabled', handler, { once: true });
|
|
57
|
+
|
|
58
|
+
await swup.hooks.trigger('enabled', undefined, () => {});
|
|
59
|
+
await swup.hooks.trigger('enabled', undefined, () => {});
|
|
60
|
+
|
|
61
|
+
expect(handler).toBeCalledTimes(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should only trigger custom handlers once if using alias', async () => {
|
|
65
|
+
const swup = new Swup();
|
|
66
|
+
const handler = vi.fn();
|
|
67
|
+
|
|
68
|
+
swup.hooks.once('enabled', handler);
|
|
69
|
+
|
|
70
|
+
await swup.hooks.trigger('enabled', undefined, () => {});
|
|
71
|
+
await swup.hooks.trigger('enabled', undefined, () => {});
|
|
72
|
+
|
|
73
|
+
expect(handler).toBeCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should trigger original handlers', async () => {
|
|
77
|
+
const swup = new Swup();
|
|
78
|
+
const handler = vi.fn();
|
|
79
|
+
|
|
80
|
+
await swup.hooks.trigger('enabled', undefined, handler);
|
|
81
|
+
|
|
82
|
+
expect(handler).toBeCalledTimes(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should allow triggering custom handlers before original handler', async () => {
|
|
86
|
+
const swup = new Swup();
|
|
87
|
+
|
|
88
|
+
let called: Array<string> = [];
|
|
89
|
+
const handlers = {
|
|
90
|
+
before: () => {
|
|
91
|
+
called.push('before');
|
|
92
|
+
},
|
|
93
|
+
original: () => {
|
|
94
|
+
called.push('original');
|
|
95
|
+
},
|
|
96
|
+
normal: () => {
|
|
97
|
+
called.push('normal');
|
|
98
|
+
},
|
|
99
|
+
after: () => {
|
|
100
|
+
called.push('after');
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
swup.hooks.on('disabled', handlers.before, { before: true });
|
|
105
|
+
swup.hooks.on('disabled', handlers.normal, {});
|
|
106
|
+
swup.hooks.on('disabled', handlers.after, {});
|
|
107
|
+
|
|
108
|
+
await swup.hooks.trigger('disabled', undefined, handlers.original);
|
|
109
|
+
|
|
110
|
+
expect(called).toEqual(['before', 'original', 'normal', 'after']);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should sort custom handlers by priority', async () => {
|
|
114
|
+
const swup = new Swup();
|
|
115
|
+
|
|
116
|
+
let called: Array<number> = [];
|
|
117
|
+
const handlers = {
|
|
118
|
+
1: () => {
|
|
119
|
+
called.push(1);
|
|
120
|
+
},
|
|
121
|
+
2: () => {
|
|
122
|
+
called.push(2);
|
|
123
|
+
},
|
|
124
|
+
3: () => {
|
|
125
|
+
called.push(3);
|
|
126
|
+
},
|
|
127
|
+
4: () => {
|
|
128
|
+
called.push(4);
|
|
129
|
+
},
|
|
130
|
+
5: () => {
|
|
131
|
+
called.push(5);
|
|
132
|
+
},
|
|
133
|
+
6: () => {
|
|
134
|
+
called.push(6);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
swup.hooks.on('disabled', handlers['1'], { priority: 1, before: true });
|
|
139
|
+
swup.hooks.on('disabled', handlers['2'], { priority: 2, before: true });
|
|
140
|
+
swup.hooks.on('disabled', handlers['4'], { priority: 5 });
|
|
141
|
+
swup.hooks.on('disabled', handlers['6'], { priority: 4 });
|
|
142
|
+
swup.hooks.on('disabled', handlers['5'], { priority: 4 });
|
|
143
|
+
|
|
144
|
+
await swup.hooks.trigger('disabled', undefined, handlers['3']);
|
|
145
|
+
|
|
146
|
+
expect(called).toEqual([2, 1, 3, 4, 6, 5]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should allow replacing original handlers', async () => {
|
|
150
|
+
const swup = new Swup();
|
|
151
|
+
const listener = vi.fn();
|
|
152
|
+
const handler = vi.fn();
|
|
153
|
+
|
|
154
|
+
swup.hooks.on('enabled', listener, { replace: true });
|
|
155
|
+
|
|
156
|
+
await swup.hooks.trigger('enabled', undefined, handler);
|
|
157
|
+
|
|
158
|
+
expect(handler).toBeCalledTimes(0);
|
|
159
|
+
expect(listener).toBeCalledTimes(1);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should trigger event handler with context and args', async () => {
|
|
163
|
+
const swup = new Swup();
|
|
164
|
+
const handler: Handler<'popState'> = vi.fn();
|
|
165
|
+
const ctx = swup.context;
|
|
166
|
+
const args = { event: new PopStateEvent('') };
|
|
167
|
+
|
|
168
|
+
swup.hooks.on('popState', handler);
|
|
169
|
+
await swup.hooks.trigger('popState', args);
|
|
170
|
+
|
|
171
|
+
expect(handler).toBeCalledTimes(1);
|
|
172
|
+
expect(handler).toBeCalledWith(ctx, args);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('Types', () => {
|
|
177
|
+
it('error when necessary', async () => {
|
|
178
|
+
const swup = new Swup();
|
|
179
|
+
|
|
180
|
+
// @ts-expect-no-error
|
|
181
|
+
swup.hooks.on('popState', (ctx: Context, { event }: { event: PopStateEvent }) => {});
|
|
182
|
+
// @ts-expect-no-error
|
|
183
|
+
await swup.hooks.trigger('popState', { event: new PopStateEvent('') });
|
|
184
|
+
|
|
185
|
+
// @ts-expect-error
|
|
186
|
+
swup.hooks.on('popState', ({ event: MouseEvent }) => {});
|
|
187
|
+
// @ts-expect-error
|
|
188
|
+
swup.hooks.on('popState', (ctx: Context, { event }: { event: MouseEvent }) => {});
|
|
189
|
+
// @ts-expect-error
|
|
190
|
+
await swup.hooks.trigger('popState', { event: new MouseEvent('') });
|
|
191
|
+
});
|
|
192
|
+
});
|
package/src/modules/enterPage.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { nextTick } from '../utils.js';
|
|
2
1
|
import Swup from '../Swup.js';
|
|
3
|
-
import {
|
|
2
|
+
import { nextTick } from '../utils.js';
|
|
4
3
|
|
|
5
|
-
export const enterPage = function (this: Swup
|
|
6
|
-
if (
|
|
7
|
-
this.
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
export const enterPage = async function (this: Swup) {
|
|
5
|
+
if (this.context.transition.animate) {
|
|
6
|
+
const animation = this.hooks.trigger(
|
|
7
|
+
'awaitAnimation',
|
|
8
|
+
{ selector: this.options.animationSelector, direction: 'in' },
|
|
9
|
+
async (context, { selector, direction }) => {
|
|
10
|
+
await Promise.all(this.getAnimationPromises({ selector, direction }));
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
await nextTick();
|
|
14
|
+
await this.hooks.trigger('animationInStart', undefined, () => {
|
|
15
|
+
document.documentElement.classList.remove('is-animating');
|
|
16
|
+
});
|
|
17
|
+
await animation;
|
|
18
|
+
await this.hooks.trigger('animationInDone');
|
|
10
19
|
}
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
this.triggerEvent('animationInStart');
|
|
14
|
-
document.documentElement.classList.remove('is-animating');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const animationPromises = this.getAnimationPromises('in');
|
|
18
|
-
Promise.all(animationPromises).then(() => {
|
|
19
|
-
this.triggerEvent('animationInDone');
|
|
20
|
-
this.triggerEvent('transitionEnd', event);
|
|
21
|
+
await this.hooks.trigger('transitionEnd', undefined, () => {
|
|
21
22
|
this.cleanupAnimationClasses();
|
|
22
23
|
});
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
this.context = this.createContext({ to: undefined });
|
|
24
26
|
};
|
package/src/modules/fetchPage.ts
CHANGED
|
@@ -1,35 +1,80 @@
|
|
|
1
1
|
import Swup from '../Swup.js';
|
|
2
|
-
import {
|
|
3
|
-
import { TransitionOptions } from './loadPage.js';
|
|
4
|
-
import { PageRecord } from './Cache.js';
|
|
2
|
+
import { Location } from '../helpers.js';
|
|
5
3
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export interface PageData {
|
|
5
|
+
url: string;
|
|
6
|
+
html: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface FetchOptions extends RequestInit {
|
|
10
|
+
method?: 'GET' | 'POST';
|
|
11
|
+
body?: string | FormData | URLSearchParams;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class FetchError extends Error {
|
|
16
|
+
url: string;
|
|
17
|
+
status: number;
|
|
18
|
+
constructor(message: string, details: { url: string; status: number }) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'FetchError';
|
|
21
|
+
this.url = details.url;
|
|
22
|
+
this.status = details.status;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fetch a page from the server, return it and cache it.
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchPage(
|
|
30
|
+
this: Swup,
|
|
31
|
+
url: URL | string,
|
|
32
|
+
options: FetchOptions & { triggerHooks?: boolean } = {}
|
|
33
|
+
): Promise<PageData> {
|
|
34
|
+
const { url: requestUrl } = Location.fromUrl(url);
|
|
35
|
+
|
|
36
|
+
if (this.cache.has(requestUrl)) {
|
|
37
|
+
const page = this.cache.get(requestUrl) as PageData;
|
|
38
|
+
if (options.triggerHooks !== false) {
|
|
39
|
+
await this.hooks.trigger('pageLoaded', { page, cache: true });
|
|
40
|
+
}
|
|
41
|
+
return page;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const headers = { ...this.options.requestHeaders, ...options.headers };
|
|
45
|
+
options = { ...options, headers };
|
|
46
|
+
|
|
47
|
+
// Allow hooking before this and returning a custom response-like object (e.g. custom fetch implementation)
|
|
48
|
+
const response = await this.hooks.trigger(
|
|
49
|
+
'fetchPage',
|
|
50
|
+
{ url: requestUrl, options },
|
|
51
|
+
async (context, { url, options, response }) => await (response || fetch(url, options))
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const { status, url: responseUrl } = response;
|
|
55
|
+
const html = await response.text();
|
|
56
|
+
|
|
57
|
+
if (status === 500) {
|
|
58
|
+
this.hooks.trigger('serverError', { status, response, url: responseUrl });
|
|
59
|
+
throw new FetchError(`Server error: ${responseUrl}`, { status, url: responseUrl });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!html) {
|
|
63
|
+
throw new FetchError(`Empty response: ${responseUrl}`, { status, url: responseUrl });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Resolve real url after potential redirect
|
|
67
|
+
const { url: finalUrl } = new Location(responseUrl);
|
|
68
|
+
const page = { url: finalUrl, html };
|
|
69
|
+
|
|
70
|
+
// Only save cache entry for non-redirects
|
|
71
|
+
if (requestUrl === finalUrl) {
|
|
72
|
+
this.cache.set(page.url, page);
|
|
73
|
+
}
|
|
9
74
|
|
|
10
|
-
if (
|
|
11
|
-
this.
|
|
12
|
-
return Promise.resolve(this.cache.getPage(url));
|
|
75
|
+
if (options.triggerHooks !== false) {
|
|
76
|
+
await this.hooks.trigger('pageLoaded', { page, cache: false });
|
|
13
77
|
}
|
|
14
78
|
|
|
15
|
-
return
|
|
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
|
-
});
|
|
79
|
+
return page;
|
|
35
80
|
}
|
|
@@ -17,11 +17,15 @@ export const getAnchorElement = (hash: string): Element | null => {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const decoded = decodeURIComponent(hash);
|
|
20
|
-
|
|
21
|
-
return (
|
|
20
|
+
let element =
|
|
22
21
|
document.getElementById(hash) ||
|
|
23
22
|
document.getElementById(decoded) ||
|
|
24
23
|
query(`a[name='${escape(hash)}']`) ||
|
|
25
|
-
query(`a[name='${escape(decoded)}']`)
|
|
26
|
-
|
|
24
|
+
query(`a[name='${escape(decoded)}']`);
|
|
25
|
+
|
|
26
|
+
if (!element && hash === 'top') {
|
|
27
|
+
element = document.body;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return element;
|
|
27
31
|
};
|
|
@@ -1,44 +1,51 @@
|
|
|
1
1
|
import { queryAll, toMs } from '../utils.js';
|
|
2
|
-
import Swup from '../Swup.js';
|
|
2
|
+
import Swup, { Options } from '../Swup.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let transitionEndEvent = 'transitionend';
|
|
7
|
-
let animationProp = 'animation';
|
|
8
|
-
let animationEndEvent = 'animationend';
|
|
4
|
+
const TRANSITION = 'transition';
|
|
5
|
+
const ANIMATION = 'animation';
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
type AnimationTypes = typeof TRANSITION | typeof ANIMATION;
|
|
8
|
+
type AnimationProperties = 'Delay' | 'Duration';
|
|
9
|
+
type AnimationStyleKeys = `${AnimationTypes}${AnimationProperties}` | 'transitionProperty';
|
|
10
|
+
type AnimationStyleDeclarations = Pick<CSSStyleDeclaration, AnimationStyleKeys>;
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
animationProp = 'WebkitAnimation';
|
|
17
|
-
animationEndEvent = 'webkitAnimationEnd';
|
|
18
|
-
}
|
|
12
|
+
export type AnimationDirection = 'in' | 'out';
|
|
19
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Get an array of Promises that resolve when all animations are done on the page.
|
|
16
|
+
* @note We don't make use of the `direction` argument, but it's required by JS plugin
|
|
17
|
+
*/
|
|
20
18
|
export function getAnimationPromises(
|
|
21
19
|
this: Swup,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
{
|
|
21
|
+
elements,
|
|
22
|
+
selector
|
|
23
|
+
}: {
|
|
24
|
+
selector: Options['animationSelector'];
|
|
25
|
+
elements?: NodeListOf<HTMLElement> | HTMLElement[];
|
|
26
|
+
direction?: AnimationDirection;
|
|
27
|
+
}
|
|
26
28
|
): Promise<void>[] {
|
|
27
|
-
|
|
29
|
+
// Use array of a single resolved promise instead of an empty array to allow
|
|
30
|
+
// possible future use with Promise.race() which requires an actual value
|
|
31
|
+
const resolved = [Promise.resolve()];
|
|
28
32
|
|
|
29
33
|
// Allow usage of swup without animations
|
|
30
|
-
if (selector === false) {
|
|
31
|
-
|
|
32
|
-
// possible future use with Promise.race() which requires an actual value
|
|
33
|
-
return [Promise.resolve()];
|
|
34
|
+
if (selector === false && !elements) {
|
|
35
|
+
return resolved;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
// Allow passing in elements
|
|
39
|
+
let animatedElements: HTMLElement[] = [];
|
|
40
|
+
if (elements) {
|
|
41
|
+
animatedElements = Array.from(elements);
|
|
42
|
+
} else if (selector) {
|
|
43
|
+
animatedElements = queryAll(selector, document.body);
|
|
44
|
+
// Warn if no elements match the selector, but keep things going
|
|
45
|
+
if (!animatedElements.length) {
|
|
46
|
+
console.warn(`[swup] No elements found matching animationSelector \`${selector}\``);
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
const animationPromises = animatedElements
|
|
@@ -46,18 +53,17 @@ export function getAnimationPromises(
|
|
|
46
53
|
.filter(Boolean) as Promise<void>[];
|
|
47
54
|
|
|
48
55
|
if (!animationPromises.length) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
if (selector) {
|
|
57
|
+
console.warn(
|
|
58
|
+
`[swup] No CSS animation duration defined on elements matching \`${selector}\``
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return resolved;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
return animationPromises;
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
const isTransitionOrAnimationEvent = (event: any): event is TransitionEvent | AnimationEvent =>
|
|
59
|
-
[transitionEndEvent, animationEndEvent].includes(event.type);
|
|
60
|
-
|
|
61
67
|
function getAnimationPromiseForElement(element: Element): Promise<void> | undefined {
|
|
62
68
|
const { type, timeout, propCount } = getTransitionInfo(element);
|
|
63
69
|
|
|
@@ -67,7 +73,7 @@ function getAnimationPromiseForElement(element: Element): Promise<void> | undefi
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
return new Promise((resolve) => {
|
|
70
|
-
const endEvent = type
|
|
76
|
+
const endEvent = `${type}end`;
|
|
71
77
|
const startTime = performance.now();
|
|
72
78
|
let propsTransitioned = 0;
|
|
73
79
|
|
|
@@ -108,60 +114,37 @@ function getAnimationPromiseForElement(element: Element): Promise<void> | undefi
|
|
|
108
114
|
});
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
export function getTransitionInfo(
|
|
112
|
-
element
|
|
113
|
-
expectedType: 'animation' | 'transition' | null = null
|
|
114
|
-
) {
|
|
115
|
-
const styles = window.getComputedStyle(element);
|
|
116
|
-
|
|
117
|
-
// not sure what to do about the below mess other than casting, but it's a mess
|
|
118
|
-
const transitionDelay = `${transitionProp}Delay` as keyof CSSStyleDeclaration;
|
|
119
|
-
const transitionDuration = `${transitionProp}Duration` as keyof CSSStyleDeclaration;
|
|
120
|
-
const animationDelay = `${animationProp}Delay` as keyof CSSStyleDeclaration;
|
|
121
|
-
const animationDuration = `${animationProp}Duration` as keyof CSSStyleDeclaration;
|
|
122
|
-
|
|
123
|
-
const transitionDelays = (
|
|
124
|
-
styles[transitionDelay] as CSSStyleDeclaration['transitionDelay']
|
|
125
|
-
).split(', ');
|
|
126
|
-
const transitionDurations = (
|
|
127
|
-
(styles[transitionDuration] || '') as CSSStyleDeclaration['transitionDuration']
|
|
128
|
-
).split(', ');
|
|
129
|
-
const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations);
|
|
117
|
+
export function getTransitionInfo(element: Element, expectedType?: AnimationTypes) {
|
|
118
|
+
const styles = window.getComputedStyle(element) as AnimationStyleDeclarations;
|
|
130
119
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
).split(', ');
|
|
120
|
+
const transitionDelays = getStyleProperties(styles, `${TRANSITION}Delay`);
|
|
121
|
+
const transitionDurations = getStyleProperties(styles, `${TRANSITION}Duration`);
|
|
122
|
+
const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations);
|
|
123
|
+
const animationDelays = getStyleProperties(styles, `${ANIMATION}Delay`);
|
|
124
|
+
const animationDurations = getStyleProperties(styles, `${ANIMATION}Duration`);
|
|
137
125
|
const animationTimeout = calculateTimeout(animationDelays, animationDurations);
|
|
138
126
|
|
|
139
|
-
let type:
|
|
127
|
+
let type: AnimationTypes | null = null;
|
|
140
128
|
let timeout = 0;
|
|
141
129
|
let propCount = 0;
|
|
142
130
|
|
|
143
|
-
if (expectedType ===
|
|
131
|
+
if (expectedType === TRANSITION) {
|
|
144
132
|
if (transitionTimeout > 0) {
|
|
145
|
-
type =
|
|
133
|
+
type = TRANSITION;
|
|
146
134
|
timeout = transitionTimeout;
|
|
147
135
|
propCount = transitionDurations.length;
|
|
148
136
|
}
|
|
149
|
-
} else if (expectedType ===
|
|
137
|
+
} else if (expectedType === ANIMATION) {
|
|
150
138
|
if (animationTimeout > 0) {
|
|
151
|
-
type =
|
|
139
|
+
type = ANIMATION;
|
|
152
140
|
timeout = animationTimeout;
|
|
153
141
|
propCount = animationDurations.length;
|
|
154
142
|
}
|
|
155
143
|
} else {
|
|
156
144
|
timeout = Math.max(transitionTimeout, animationTimeout);
|
|
157
|
-
type =
|
|
158
|
-
timeout > 0
|
|
159
|
-
? transitionTimeout > animationTimeout
|
|
160
|
-
? 'transition'
|
|
161
|
-
: 'animation'
|
|
162
|
-
: null;
|
|
145
|
+
type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null;
|
|
163
146
|
propCount = type
|
|
164
|
-
? type ===
|
|
147
|
+
? type === TRANSITION
|
|
165
148
|
? transitionDurations.length
|
|
166
149
|
: animationDurations.length
|
|
167
150
|
: 0;
|
|
@@ -174,7 +157,15 @@ export function getTransitionInfo(
|
|
|
174
157
|
};
|
|
175
158
|
}
|
|
176
159
|
|
|
177
|
-
function
|
|
160
|
+
function isTransitionOrAnimationEvent(event: any): event is TransitionEvent | AnimationEvent {
|
|
161
|
+
return [`${TRANSITION}end`, `${ANIMATION}end`].includes(event.type);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getStyleProperties(styles: AnimationStyleDeclarations, key: AnimationStyleKeys): string[] {
|
|
165
|
+
return (styles[key] || '').split(', ');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function calculateTimeout(delays: string[], durations: string[]): number {
|
|
178
169
|
while (delays.length < durations.length) {
|
|
179
170
|
delays = delays.concat(delays);
|
|
180
171
|
}
|
package/src/modules/leavePage.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import Swup from '../Swup.js';
|
|
2
|
-
import {
|
|
2
|
+
import { classify } from '../helpers.js';
|
|
3
3
|
|
|
4
|
-
export const leavePage = function (this: Swup
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.triggerEvent('animationSkipped');
|
|
9
|
-
return [Promise.resolve()];
|
|
4
|
+
export const leavePage = async function (this: Swup) {
|
|
5
|
+
if (!this.context.transition.animate) {
|
|
6
|
+
await this.hooks.trigger('animationSkipped');
|
|
7
|
+
return;
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// animation promise stuff
|
|
21
|
-
const animationPromises: Promise<void>[] = this.getAnimationPromises('out');
|
|
22
|
-
Promise.all(animationPromises).then(() => {
|
|
23
|
-
this.triggerEvent('animationOutDone');
|
|
10
|
+
await this.hooks.trigger('animationOutStart', undefined, () => {
|
|
11
|
+
document.documentElement.classList.add('is-changing', 'is-leaving', 'is-animating');
|
|
12
|
+
if (this.context.history.popstate) {
|
|
13
|
+
document.documentElement.classList.add('is-popstate');
|
|
14
|
+
}
|
|
15
|
+
if (this.context.transition.name) {
|
|
16
|
+
document.documentElement.classList.add(`to-${classify(this.context.transition.name)}`);
|
|
17
|
+
}
|
|
24
18
|
});
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
await this.hooks.trigger(
|
|
21
|
+
'awaitAnimation',
|
|
22
|
+
{ selector: this.options.animationSelector, direction: 'out' },
|
|
23
|
+
async (context, { selector, direction }) => {
|
|
24
|
+
await Promise.all(this.getAnimationPromises({ selector, direction }));
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
await this.hooks.trigger('animationOutDone');
|
|
27
29
|
};
|