swup 4.1.0 → 4.3.0
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 +119 -117
- package/dist/types/__test__/index.test.d.ts +1 -1
- package/dist/types/config/version.d.ts +5 -5
- package/dist/types/helpers/Location.d.ts +24 -24
- package/dist/types/helpers/__test__/matchPath.test.d.ts +1 -1
- package/dist/types/helpers/classify.d.ts +2 -2
- package/dist/types/helpers/createHistoryRecord.d.ts +9 -2
- package/dist/types/helpers/delegateEvent.d.ts +7 -7
- package/dist/types/helpers/getCurrentUrl.d.ts +4 -4
- package/dist/types/helpers/matchPath.d.ts +4 -4
- package/dist/types/helpers/updateHistoryRecord.d.ts +2 -2
- package/dist/types/helpers.d.ts +7 -7
- package/dist/types/index.d.ts +14 -14
- package/dist/types/modules/Cache.d.ts +34 -34
- package/dist/types/modules/Classes.d.ts +13 -13
- package/dist/types/modules/Hooks.d.ts +268 -250
- package/dist/types/modules/Visit.d.ts +85 -75
- package/dist/types/modules/__test__/cache.test.d.ts +1 -1
- package/dist/types/modules/__test__/{delegateEvent.d.ts → delegateEvent.test.d.ts} +1 -1
- package/dist/types/modules/__test__/hooks.test.d.ts +1 -1
- package/dist/types/modules/__test__/plugins.test.d.ts +1 -1
- package/dist/types/modules/__test__/replaceContent.test.d.ts +1 -1
- package/dist/types/modules/animatePageIn.d.ts +6 -6
- package/dist/types/modules/animatePageOut.d.ts +6 -6
- package/dist/types/modules/awaitAnimations.d.ts +19 -19
- package/dist/types/modules/fetchPage.d.ts +27 -29
- package/dist/types/modules/getAnchorElement.d.ts +9 -9
- package/dist/types/modules/navigate.d.ts +41 -34
- package/dist/types/modules/plugins.d.ts +26 -26
- package/dist/types/modules/renderPage.d.ts +7 -7
- package/dist/types/modules/replaceContent.d.ts +13 -13
- package/dist/types/modules/resolveUrl.d.ts +14 -14
- package/dist/types/modules/scrollToContent.d.ts +6 -6
- package/dist/types/utils/index.d.ts +20 -20
- package/dist/types/utils.d.ts +1 -1
- package/package.json +9 -3
- package/src/Swup.ts +24 -17
- package/src/config/version.ts +1 -2
- package/src/helpers/createHistoryRecord.ts +9 -1
- package/src/helpers/matchPath.ts +1 -1
- package/src/helpers/updateHistoryRecord.ts +4 -2
- package/src/modules/Cache.ts +2 -2
- package/src/modules/Classes.ts +1 -1
- package/src/modules/Hooks.ts +91 -39
- package/src/modules/Visit.ts +20 -5
- package/src/modules/__test__/cache.test.ts +3 -3
- package/src/modules/__test__/hooks.test.ts +12 -13
- package/src/modules/animatePageIn.ts +1 -1
- package/src/modules/animatePageOut.ts +2 -2
- package/src/modules/awaitAnimations.ts +1 -1
- package/src/modules/fetchPage.ts +7 -5
- package/src/modules/getAnchorElement.ts +1 -1
- package/src/modules/navigate.ts +37 -15
- package/src/modules/plugins.ts +3 -3
- package/src/modules/renderPage.ts +1 -5
- package/src/modules/replaceContent.ts +13 -0
- package/src/modules/scrollToContent.ts +5 -3
- package/src/utils/index.ts +5 -4
- /package/src/modules/__test__/{delegateEvent.ts → delegateEvent.test.ts} +0 -0
package/src/Swup.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Visit, createVisit } from './modules/Visit.js';
|
|
|
11
11
|
import { Hooks } from './modules/Hooks.js';
|
|
12
12
|
import { getAnchorElement } from './modules/getAnchorElement.js';
|
|
13
13
|
import { awaitAnimations } from './modules/awaitAnimations.js';
|
|
14
|
-
import { navigate, performNavigation } from './modules/navigate.js';
|
|
14
|
+
import { navigate, performNavigation, NavigationToSelfAction } from './modules/navigate.js';
|
|
15
15
|
import { fetchPage } from './modules/fetchPage.js';
|
|
16
16
|
import { animatePageOut } from './modules/animatePageOut.js';
|
|
17
17
|
import { replaceContent } from './modules/replaceContent.js';
|
|
@@ -21,6 +21,7 @@ import { renderPage } from './modules/renderPage.js';
|
|
|
21
21
|
import { use, unuse, findPlugin, Plugin } from './modules/plugins.js';
|
|
22
22
|
import { isSameResolvedUrl, resolveUrl } from './modules/resolveUrl.js';
|
|
23
23
|
import { nextTick } from './utils.js';
|
|
24
|
+
import { HistoryState } from './helpers/createHistoryRecord.js';
|
|
24
25
|
|
|
25
26
|
/** Options for customizing swup's behavior. */
|
|
26
27
|
export type Options = {
|
|
@@ -38,6 +39,8 @@ export type Options = {
|
|
|
38
39
|
ignoreVisit: (url: string, { el, event }: { el?: Element; event?: Event }) => boolean;
|
|
39
40
|
/** Selector for links that trigger visits. Default: `'a[href]'` */
|
|
40
41
|
linkSelector: string;
|
|
42
|
+
/** How swup handles links to the same page. Default: `scroll` */
|
|
43
|
+
linkToSelf: NavigationToSelfAction;
|
|
41
44
|
/** Plugins to register on startup. */
|
|
42
45
|
plugins: Plugin[];
|
|
43
46
|
/** Custom headers sent along with fetch requests. */
|
|
@@ -45,7 +48,7 @@ export type Options = {
|
|
|
45
48
|
/** Rewrite URLs before loading them. */
|
|
46
49
|
resolveUrl: (url: string) => string;
|
|
47
50
|
/** Callback for telling swup to ignore certain popstate events. */
|
|
48
|
-
skipPopStateHandling: (event:
|
|
51
|
+
skipPopStateHandling: (event: PopStateEvent) => boolean;
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
const defaults: Options = {
|
|
@@ -54,15 +57,16 @@ const defaults: Options = {
|
|
|
54
57
|
animationScope: 'html',
|
|
55
58
|
cache: true,
|
|
56
59
|
containers: ['#swup'],
|
|
57
|
-
ignoreVisit: (url, { el
|
|
60
|
+
ignoreVisit: (url, { el } = {}) => !!el?.closest('[data-no-swup]'),
|
|
58
61
|
linkSelector: 'a[href]',
|
|
62
|
+
linkToSelf: 'scroll',
|
|
59
63
|
plugins: [],
|
|
60
64
|
resolveUrl: (url) => url,
|
|
61
65
|
requestHeaders: {
|
|
62
66
|
'X-Requested-With': 'swup',
|
|
63
67
|
'Accept': 'text/html, application/xhtml+xml'
|
|
64
68
|
},
|
|
65
|
-
skipPopStateHandling: (event) => event.state?.source !== 'swup'
|
|
69
|
+
skipPopStateHandling: (event) => (event.state as HistoryState)?.source !== 'swup'
|
|
66
70
|
};
|
|
67
71
|
|
|
68
72
|
/** Swup page transition library. */
|
|
@@ -98,7 +102,7 @@ export default class Swup {
|
|
|
98
102
|
findPlugin = findPlugin;
|
|
99
103
|
|
|
100
104
|
/** Log a message. Has no effect unless debug plugin is installed */
|
|
101
|
-
log: (message: string, context?:
|
|
105
|
+
log: (message: string, context?: unknown) => void = () => {};
|
|
102
106
|
|
|
103
107
|
/** Navigate to a new URL */
|
|
104
108
|
navigate = navigate;
|
|
@@ -138,7 +142,7 @@ export default class Swup {
|
|
|
138
142
|
this.cache = new Cache(this);
|
|
139
143
|
this.classes = new Classes(this);
|
|
140
144
|
this.hooks = new Hooks(this);
|
|
141
|
-
this.visit = this.createVisit({ to:
|
|
145
|
+
this.visit = this.createVisit({ to: '' });
|
|
142
146
|
|
|
143
147
|
if (!this.checkRequirements()) {
|
|
144
148
|
return;
|
|
@@ -259,16 +263,24 @@ export default class Swup {
|
|
|
259
263
|
|
|
260
264
|
event.preventDefault();
|
|
261
265
|
|
|
262
|
-
// Handle links to the same page
|
|
266
|
+
// Handle links to the same page
|
|
263
267
|
if (!url || url === from) {
|
|
264
268
|
if (hash) {
|
|
269
|
+
// With hash: scroll to anchor
|
|
265
270
|
this.hooks.callSync('link:anchor', { hash }, () => {
|
|
266
271
|
updateHistoryRecord(url + hash);
|
|
267
272
|
this.scrollToContent();
|
|
268
273
|
});
|
|
269
274
|
} else {
|
|
275
|
+
// Without hash: scroll to top or load/reload page
|
|
270
276
|
this.hooks.callSync('link:self', undefined, () => {
|
|
271
|
-
this.
|
|
277
|
+
switch (this.options.linkToSelf) {
|
|
278
|
+
case 'navigate':
|
|
279
|
+
return this.performNavigation();
|
|
280
|
+
case 'scroll':
|
|
281
|
+
default:
|
|
282
|
+
return this.scrollToContent();
|
|
283
|
+
}
|
|
272
284
|
});
|
|
273
285
|
}
|
|
274
286
|
return;
|
|
@@ -280,12 +292,12 @@ export default class Swup {
|
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
// Finally, proceed with loading the page
|
|
283
|
-
this.performNavigation(
|
|
295
|
+
this.performNavigation();
|
|
284
296
|
});
|
|
285
297
|
}
|
|
286
298
|
|
|
287
299
|
protected handlePopState(event: PopStateEvent) {
|
|
288
|
-
const href = event.state?.url ?? location.href;
|
|
300
|
+
const href: string = (event.state as HistoryState)?.url ?? location.href;
|
|
289
301
|
|
|
290
302
|
// Exit early if this event should be ignored
|
|
291
303
|
if (this.options.skipPopStateHandling(event)) {
|
|
@@ -297,11 +309,6 @@ export default class Swup {
|
|
|
297
309
|
return;
|
|
298
310
|
}
|
|
299
311
|
|
|
300
|
-
// Exit early if the link should be ignored
|
|
301
|
-
if (this.shouldIgnoreVisit(href, { event })) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
312
|
const { url, hash } = Location.fromUrl(href);
|
|
306
313
|
const animate = this.options.animateHistoryBrowsing;
|
|
307
314
|
const resetScroll = this.options.animateHistoryBrowsing;
|
|
@@ -318,7 +325,7 @@ export default class Swup {
|
|
|
318
325
|
this.visit.history.popstate = true;
|
|
319
326
|
|
|
320
327
|
// Determine direction of history visit
|
|
321
|
-
const index = Number(event.state?.index);
|
|
328
|
+
const index = Number((event.state as HistoryState)?.index);
|
|
322
329
|
if (index) {
|
|
323
330
|
const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards';
|
|
324
331
|
this.visit.history.direction = direction;
|
|
@@ -330,7 +337,7 @@ export default class Swup {
|
|
|
330
337
|
// }
|
|
331
338
|
|
|
332
339
|
this.hooks.callSync('history:popstate', { event }, () => {
|
|
333
|
-
this.performNavigation(
|
|
340
|
+
this.performNavigation();
|
|
334
341
|
});
|
|
335
342
|
}
|
|
336
343
|
|
package/src/config/version.ts
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// export { version as default } from '../../package.json';
|
|
7
7
|
|
|
8
8
|
// This will work in microbundle + webpack 5, but won't treeshake in webpack 4
|
|
9
|
-
//
|
|
10
|
-
// @ts-ignore
|
|
9
|
+
// @ts-ignore: package.json is outside of rootDir
|
|
11
10
|
import pckg from '../../package.json';
|
|
12
11
|
|
|
13
12
|
export default pckg.version;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { getCurrentUrl } from './getCurrentUrl.js';
|
|
2
2
|
|
|
3
|
+
export interface HistoryState {
|
|
4
|
+
url: string;
|
|
5
|
+
source: 'swup';
|
|
6
|
+
random: number;
|
|
7
|
+
index?: number;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
/** Create a new history record with a custom swup identifier. */
|
|
4
12
|
export const createHistoryRecord = (
|
|
5
13
|
url: string,
|
|
6
14
|
customData: Record<string, unknown> = {}
|
|
7
15
|
): void => {
|
|
8
16
|
url = url || getCurrentUrl({ hash: true });
|
|
9
|
-
const data = {
|
|
17
|
+
const data: HistoryState = {
|
|
10
18
|
url,
|
|
11
19
|
random: Math.random(),
|
|
12
20
|
source: 'swup',
|
package/src/helpers/matchPath.ts
CHANGED
|
@@ -18,6 +18,6 @@ export const matchPath = <P extends object = object>(
|
|
|
18
18
|
try {
|
|
19
19
|
return match<P>(path, options);
|
|
20
20
|
} catch (error) {
|
|
21
|
-
throw new Error(`[swup] Error parsing path "${path}":\n${error}`);
|
|
21
|
+
throw new Error(`[swup] Error parsing path "${String(path)}":\n${String(error)}`);
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HistoryState } from './createHistoryRecord.js';
|
|
1
2
|
import { getCurrentUrl } from './getCurrentUrl.js';
|
|
2
3
|
|
|
3
4
|
/** Update the current history record with a custom swup identifier. */
|
|
@@ -6,8 +7,9 @@ export const updateHistoryRecord = (
|
|
|
6
7
|
customData: Record<string, unknown> = {}
|
|
7
8
|
): void => {
|
|
8
9
|
url = url || getCurrentUrl({ hash: true });
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
const state = (history.state as HistoryState) || {};
|
|
11
|
+
const data: HistoryState = {
|
|
12
|
+
...state,
|
|
11
13
|
url,
|
|
12
14
|
random: Math.random(),
|
|
13
15
|
source: 'swup',
|
package/src/modules/Cache.ts
CHANGED
|
@@ -53,7 +53,7 @@ export class Cache {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** Update a cache record, overwriting or adding custom data. */
|
|
56
|
-
update(url: string, payload:
|
|
56
|
+
update(url: string, payload: object) {
|
|
57
57
|
url = this.resolve(url);
|
|
58
58
|
const page = { ...this.get(url), ...payload, url } as CacheData;
|
|
59
59
|
this.pages.set(url, page);
|
|
@@ -67,7 +67,7 @@ export class Cache {
|
|
|
67
67
|
/** Empty the cache. */
|
|
68
68
|
clear(): void {
|
|
69
69
|
this.pages.clear();
|
|
70
|
-
this.swup.hooks.callSync('cache:clear');
|
|
70
|
+
this.swup.hooks.callSync('cache:clear', undefined);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/** Remove all cache entries that return true for a given predicate function. */
|
package/src/modules/Classes.ts
CHANGED
package/src/modules/Hooks.ts
CHANGED
|
@@ -34,19 +34,35 @@ export interface HookDefinitions {
|
|
|
34
34
|
'visit:end': undefined;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export interface HookReturnValues {
|
|
38
|
+
'content:scroll': Promise<boolean>;
|
|
39
|
+
'fetch:request': Promise<Response>;
|
|
40
|
+
'page:load': Promise<PageData>;
|
|
41
|
+
'scroll:top': boolean;
|
|
42
|
+
'scroll:anchor': boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
export type HookArguments<T extends HookName> = HookDefinitions[T];
|
|
38
46
|
|
|
39
47
|
export type HookName = keyof HookDefinitions;
|
|
40
48
|
|
|
41
|
-
/** A hook handler. */
|
|
49
|
+
/** A generic hook handler. */
|
|
42
50
|
export type Handler<T extends HookName> = (
|
|
51
|
+
/** Context about the current visit. */
|
|
52
|
+
visit: Visit,
|
|
53
|
+
/** Local arguments passed into the handler. */
|
|
54
|
+
args: HookArguments<T>
|
|
55
|
+
) => Promise<unknown> | unknown;
|
|
56
|
+
|
|
57
|
+
/** A default hook handler with an expected return type. */
|
|
58
|
+
export type DefaultHandler<T extends HookName> = (
|
|
43
59
|
/** Context about the current visit. */
|
|
44
60
|
visit: Visit,
|
|
45
61
|
/** Local arguments passed into the handler. */
|
|
46
62
|
args: HookArguments<T>,
|
|
47
63
|
/** Default handler to be executed. Available if replacing an internal hook handler. */
|
|
48
|
-
defaultHandler?:
|
|
49
|
-
) => Promise<
|
|
64
|
+
defaultHandler?: DefaultHandler<T>
|
|
65
|
+
) => T extends keyof HookReturnValues ? HookReturnValues[T] : Promise<unknown> | unknown;
|
|
50
66
|
|
|
51
67
|
export type Handlers = {
|
|
52
68
|
[K in HookName]: Handler<K>[];
|
|
@@ -67,11 +83,14 @@ export type HookOptions = {
|
|
|
67
83
|
replace?: boolean;
|
|
68
84
|
};
|
|
69
85
|
|
|
70
|
-
export type HookRegistration<
|
|
86
|
+
export type HookRegistration<
|
|
87
|
+
T extends HookName,
|
|
88
|
+
H extends Handler<T> | DefaultHandler<T> = Handler<T>
|
|
89
|
+
> = {
|
|
71
90
|
id: number;
|
|
72
91
|
hook: T;
|
|
73
|
-
handler:
|
|
74
|
-
defaultHandler?:
|
|
92
|
+
handler: H;
|
|
93
|
+
defaultHandler?: DefaultHandler<T>;
|
|
75
94
|
} & HookOptions;
|
|
76
95
|
|
|
77
96
|
type HookLedger<T extends HookName> = Map<Handler<T>, HookRegistration<T>>;
|
|
@@ -183,12 +202,18 @@ export class Hooks {
|
|
|
183
202
|
* - `replace`: Replace the default handler with this handler
|
|
184
203
|
* @returns A function to unregister the handler
|
|
185
204
|
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
on<T extends HookName>(
|
|
205
|
+
|
|
206
|
+
// Overload: replacing default handler
|
|
207
|
+
on<T extends HookName, O extends HookOptions>(hook: T, handler: DefaultHandler<T>, options: O & { replace: true }): HookUnregister; // prettier-ignore
|
|
208
|
+
// Overload: passed in handler options
|
|
209
|
+
on<T extends HookName, O extends HookOptions>(hook: T, handler: Handler<T>, options: O): HookUnregister; // prettier-ignore
|
|
210
|
+
// Overload: no handler options
|
|
211
|
+
on<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister; // prettier-ignore
|
|
212
|
+
// Implementation
|
|
213
|
+
on<T extends HookName, O extends HookOptions>(
|
|
189
214
|
hook: T,
|
|
190
|
-
handler: Handler<T>,
|
|
191
|
-
options:
|
|
215
|
+
handler: O['replace'] extends true ? DefaultHandler<T> : Handler<T>,
|
|
216
|
+
options: Partial<O> = {}
|
|
192
217
|
): HookUnregister {
|
|
193
218
|
const ledger = this.get(hook);
|
|
194
219
|
if (!ledger) {
|
|
@@ -212,8 +237,11 @@ export class Hooks {
|
|
|
212
237
|
* @returns A function to unregister the handler
|
|
213
238
|
* @see on
|
|
214
239
|
*/
|
|
215
|
-
|
|
240
|
+
// Overload: passed in handler options
|
|
216
241
|
before<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
|
|
242
|
+
// Overload: no handler options
|
|
243
|
+
before<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
|
|
244
|
+
// Implementation
|
|
217
245
|
before<T extends HookName>(
|
|
218
246
|
hook: T,
|
|
219
247
|
handler: Handler<T>,
|
|
@@ -231,11 +259,14 @@ export class Hooks {
|
|
|
231
259
|
* @returns A function to unregister the handler
|
|
232
260
|
* @see on
|
|
233
261
|
*/
|
|
234
|
-
|
|
235
|
-
replace<T extends HookName>(hook: T, handler:
|
|
262
|
+
// Overload: passed in handler options
|
|
263
|
+
replace<T extends HookName>(hook: T, handler: DefaultHandler<T>, options: HookOptions): HookUnregister; // prettier-ignore
|
|
264
|
+
// Overload: no handler options
|
|
265
|
+
replace<T extends HookName>(hook: T, handler: DefaultHandler<T>): HookUnregister; // prettier-ignore
|
|
266
|
+
// Implementation
|
|
236
267
|
replace<T extends HookName>(
|
|
237
268
|
hook: T,
|
|
238
|
-
handler:
|
|
269
|
+
handler: DefaultHandler<T>,
|
|
239
270
|
options: HookOptions = {}
|
|
240
271
|
): HookUnregister {
|
|
241
272
|
return this.on(hook, handler, { ...options, replace: true });
|
|
@@ -249,8 +280,11 @@ export class Hooks {
|
|
|
249
280
|
* @param options Any other event options (see `hooks.on()` for details)
|
|
250
281
|
* @see on
|
|
251
282
|
*/
|
|
252
|
-
|
|
283
|
+
// Overload: passed in handler options
|
|
253
284
|
once<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
|
|
285
|
+
// Overload: no handler options
|
|
286
|
+
once<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
|
|
287
|
+
// Implementation
|
|
254
288
|
once<T extends HookName>(
|
|
255
289
|
hook: T,
|
|
256
290
|
handler: Handler<T>,
|
|
@@ -265,9 +299,12 @@ export class Hooks {
|
|
|
265
299
|
* @param handler The handler function that was registered.
|
|
266
300
|
* If omitted, all handlers for the hook will be removed.
|
|
267
301
|
*/
|
|
302
|
+
// Overload: unregister a specific handler
|
|
303
|
+
off<T extends HookName>(hook: T, handler: Handler<T> | DefaultHandler<T>): void;
|
|
304
|
+
// Overload: unregister all handlers
|
|
268
305
|
off<T extends HookName>(hook: T): void;
|
|
269
|
-
|
|
270
|
-
off<T extends HookName>(hook: T, handler?: Handler<T>): void {
|
|
306
|
+
// Implementation
|
|
307
|
+
off<T extends HookName>(hook: T, handler?: Handler<T> | DefaultHandler<T>): void {
|
|
271
308
|
const ledger = this.get(hook);
|
|
272
309
|
if (ledger && handler) {
|
|
273
310
|
const deleted = ledger.delete(handler);
|
|
@@ -289,9 +326,9 @@ export class Hooks {
|
|
|
289
326
|
*/
|
|
290
327
|
async call<T extends HookName>(
|
|
291
328
|
hook: T,
|
|
292
|
-
args
|
|
293
|
-
defaultHandler?:
|
|
294
|
-
): Promise<
|
|
329
|
+
args: HookArguments<T>,
|
|
330
|
+
defaultHandler?: DefaultHandler<T>
|
|
331
|
+
): Promise<Awaited<ReturnType<DefaultHandler<T>>>> {
|
|
295
332
|
const { before, handler, after } = this.getHandlers(hook, defaultHandler);
|
|
296
333
|
await this.run(before, args);
|
|
297
334
|
const [result] = await this.run(handler, args);
|
|
@@ -310,9 +347,9 @@ export class Hooks {
|
|
|
310
347
|
*/
|
|
311
348
|
callSync<T extends HookName>(
|
|
312
349
|
hook: T,
|
|
313
|
-
args
|
|
314
|
-
defaultHandler?:
|
|
315
|
-
):
|
|
350
|
+
args: HookArguments<T>,
|
|
351
|
+
defaultHandler?: DefaultHandler<T>
|
|
352
|
+
): ReturnType<DefaultHandler<T>> {
|
|
316
353
|
const { before, handler, after } = this.getHandlers(hook, defaultHandler);
|
|
317
354
|
this.runSync(before, args);
|
|
318
355
|
const [result] = this.runSync(handler, args);
|
|
@@ -326,10 +363,16 @@ export class Hooks {
|
|
|
326
363
|
* @param registrations The registrations (handler + options) to execute
|
|
327
364
|
* @param args Arguments to pass to the handler
|
|
328
365
|
*/
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
366
|
+
|
|
367
|
+
// Overload: running DefaultHandler: expect DefaultHandler return type
|
|
368
|
+
protected async run<T extends HookName>(registrations: HookRegistration<T, DefaultHandler<T>>[], args: HookArguments<T>): Promise<Awaited<ReturnType<DefaultHandler<T>>>[]>; // prettier-ignore
|
|
369
|
+
// Overload: running user handler: expect no specific type
|
|
370
|
+
protected async run<T extends HookName>(registrations: HookRegistration<T>[], args: HookArguments<T>): Promise<unknown[]>; // prettier-ignore
|
|
371
|
+
// Implementation
|
|
372
|
+
protected async run<T extends HookName, R extends HookRegistration<T>[]>(
|
|
373
|
+
registrations: R,
|
|
374
|
+
args: HookArguments<T>
|
|
375
|
+
): Promise<Awaited<ReturnType<DefaultHandler<T>>> | unknown[]> {
|
|
333
376
|
const results = [];
|
|
334
377
|
for (const { hook, handler, defaultHandler, once } of registrations) {
|
|
335
378
|
const result = await runAsPromise(handler, [this.swup.visit, args, defaultHandler]);
|
|
@@ -346,13 +389,19 @@ export class Hooks {
|
|
|
346
389
|
* @param registrations The registrations (handler + options) to execute
|
|
347
390
|
* @param args Arguments to pass to the handler
|
|
348
391
|
*/
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
392
|
+
|
|
393
|
+
// Overload: running DefaultHandler: expect DefaultHandler return type
|
|
394
|
+
protected runSync<T extends HookName>(registrations: HookRegistration<T, DefaultHandler<T>>[], args: HookArguments<T> ): ReturnType<DefaultHandler<T>>[]; // prettier-ignore
|
|
395
|
+
// Overload: running user handler: expect no specific type
|
|
396
|
+
protected runSync<T extends HookName>(registrations: HookRegistration<T>[], args: HookArguments<T>): unknown[]; // prettier-ignore
|
|
397
|
+
// Implementation
|
|
398
|
+
protected runSync<T extends HookName, R extends HookRegistration<T>[]>(
|
|
399
|
+
registrations: R,
|
|
400
|
+
args: HookArguments<T>
|
|
401
|
+
): (ReturnType<DefaultHandler<T>> | unknown)[] {
|
|
353
402
|
const results = [];
|
|
354
403
|
for (const { hook, handler, defaultHandler, once } of registrations) {
|
|
355
|
-
const result = handler(this.swup.visit, args
|
|
404
|
+
const result = (handler as DefaultHandler<T>)(this.swup.visit, args, defaultHandler);
|
|
356
405
|
results.push(result);
|
|
357
406
|
if (isPromise(result)) {
|
|
358
407
|
console.warn(
|
|
@@ -374,30 +423,33 @@ export class Hooks {
|
|
|
374
423
|
* @returns An object with the handlers sorted into `before` and `after` arrays,
|
|
375
424
|
* as well as a flag indicating if the original handler was replaced
|
|
376
425
|
*/
|
|
377
|
-
protected getHandlers<T extends HookName>(hook: T, defaultHandler?:
|
|
426
|
+
protected getHandlers<T extends HookName>(hook: T, defaultHandler?: DefaultHandler<T>) {
|
|
378
427
|
const ledger = this.get(hook);
|
|
379
428
|
if (!ledger) {
|
|
380
429
|
return { found: false, before: [], handler: [], after: [], replaced: false };
|
|
381
430
|
}
|
|
382
431
|
|
|
383
|
-
const sort = this.sortRegistrations;
|
|
384
432
|
const registrations = Array.from(ledger.values());
|
|
385
433
|
|
|
434
|
+
// Let TypeScript know that replaced handlers are default handlers by filtering to true
|
|
435
|
+
const def = (T: HookRegistration<T>): T is HookRegistration<T, DefaultHandler<T>> => true;
|
|
436
|
+
const sort = this.sortRegistrations;
|
|
437
|
+
|
|
386
438
|
// Filter into before, after, and replace handlers
|
|
387
439
|
const before = registrations.filter(({ before, replace }) => before && !replace).sort(sort);
|
|
388
|
-
const replace = registrations.filter(({ replace }) => replace).sort(sort);
|
|
440
|
+
const replace = registrations.filter(({ replace }) => replace).filter(def).sort(sort); // prettier-ignore
|
|
389
441
|
const after = registrations.filter(({ before, replace }) => !before && !replace).sort(sort);
|
|
390
442
|
const replaced = replace.length > 0;
|
|
391
443
|
|
|
392
444
|
// Define main handler registration
|
|
393
|
-
//
|
|
394
|
-
let handler: HookRegistration<T
|
|
445
|
+
// Created as HookRegistration[] array to allow passing it into hooks.run() directly
|
|
446
|
+
let handler: HookRegistration<T, DefaultHandler<T>>[] = [];
|
|
395
447
|
if (defaultHandler) {
|
|
396
448
|
handler = [{ id: 0, hook, handler: defaultHandler }];
|
|
397
449
|
if (replaced) {
|
|
398
450
|
const index = replace.length - 1;
|
|
399
451
|
const replacingHandler = replace[index].handler;
|
|
400
|
-
const createDefaultHandler = (index: number):
|
|
452
|
+
const createDefaultHandler = (index: number): DefaultHandler<T> | undefined => {
|
|
401
453
|
const next = replace[index - 1];
|
|
402
454
|
if (next) {
|
|
403
455
|
return (visit, args) =>
|
package/src/modules/Visit.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface Visit {
|
|
|
13
13
|
animation: VisitAnimation;
|
|
14
14
|
/** What triggered this visit */
|
|
15
15
|
trigger: VisitTrigger;
|
|
16
|
+
/** Cache behavior for this visit */
|
|
17
|
+
cache: VisitCache;
|
|
16
18
|
/** Browser history behavior on this visit */
|
|
17
19
|
history: VisitHistory;
|
|
18
20
|
/** Scroll behavior on this visit */
|
|
@@ -26,7 +28,9 @@ export interface VisitFrom {
|
|
|
26
28
|
|
|
27
29
|
export interface VisitTo {
|
|
28
30
|
/** The URL of the next page */
|
|
29
|
-
url
|
|
31
|
+
url: string;
|
|
32
|
+
/** The hash of the next page */
|
|
33
|
+
hash?: string;
|
|
30
34
|
/** The HTML content of the next page */
|
|
31
35
|
html?: string;
|
|
32
36
|
}
|
|
@@ -58,6 +62,13 @@ export interface VisitTrigger {
|
|
|
58
62
|
event?: Event;
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
export interface VisitCache {
|
|
66
|
+
/** Whether this visit will try to load the requested page from cache. */
|
|
67
|
+
read: boolean;
|
|
68
|
+
/** Whether this visit will save the loaded page in cache. */
|
|
69
|
+
write: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
export interface VisitHistory {
|
|
62
73
|
/** History action to perform: `push` for creating a new history entry, `replace` for replacing the current entry. Default: `push` */
|
|
63
74
|
action: HistoryAction;
|
|
@@ -68,7 +79,7 @@ export interface VisitHistory {
|
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
export interface VisitInitOptions {
|
|
71
|
-
to: string
|
|
82
|
+
to: string;
|
|
72
83
|
from?: string;
|
|
73
84
|
hash?: string;
|
|
74
85
|
animate?: boolean;
|
|
@@ -86,7 +97,7 @@ export function createVisit(
|
|
|
86
97
|
{
|
|
87
98
|
to,
|
|
88
99
|
from = this.currentPageUrl,
|
|
89
|
-
hash
|
|
100
|
+
hash,
|
|
90
101
|
animate = true,
|
|
91
102
|
animation: name,
|
|
92
103
|
el,
|
|
@@ -97,7 +108,7 @@ export function createVisit(
|
|
|
97
108
|
): Visit {
|
|
98
109
|
return {
|
|
99
110
|
from: { url: from },
|
|
100
|
-
to: { url: to },
|
|
111
|
+
to: { url: to, hash },
|
|
101
112
|
containers: this.options.containers,
|
|
102
113
|
animation: {
|
|
103
114
|
animate,
|
|
@@ -110,6 +121,10 @@ export function createVisit(
|
|
|
110
121
|
el,
|
|
111
122
|
event
|
|
112
123
|
},
|
|
124
|
+
cache: {
|
|
125
|
+
read: this.options.cache,
|
|
126
|
+
write: this.options.cache
|
|
127
|
+
},
|
|
113
128
|
history: {
|
|
114
129
|
action,
|
|
115
130
|
popstate: false,
|
|
@@ -117,7 +132,7 @@ export function createVisit(
|
|
|
117
132
|
},
|
|
118
133
|
scroll: {
|
|
119
134
|
reset,
|
|
120
|
-
target
|
|
135
|
+
target: undefined
|
|
121
136
|
}
|
|
122
137
|
};
|
|
123
138
|
}
|
|
@@ -90,19 +90,19 @@ describe('Cache', () => {
|
|
|
90
90
|
|
|
91
91
|
swup.hooks.on('cache:set', (_, { page }) => {
|
|
92
92
|
const ttl: CacheTtlData = { ttl: 1000, created: now };
|
|
93
|
-
cache.update(page.url, ttl
|
|
93
|
+
cache.update(page.url, ttl);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
cache.set('/page', { url: '/page', html: '' });
|
|
97
97
|
|
|
98
|
-
const page = cache.get('/page')
|
|
98
|
+
const page = cache.get('/page');
|
|
99
99
|
|
|
100
100
|
expect(page).toEqual({ url: '/page', html: '', ttl: 1000, created: now });
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('should allow manual pruning', () => {
|
|
104
104
|
swup.hooks.on('cache:set', (_, { page }) => {
|
|
105
|
-
cache.update(page.url, { index: cache.size }
|
|
105
|
+
cache.update(page.url, { index: cache.size });
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
cache.set(page1.url, page1);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import Swup from '../../Swup.js';
|
|
3
|
-
import { Handler, Hooks } from '../Hooks.js';
|
|
3
|
+
import { DefaultHandler, Handler, Hooks } from '../Hooks.js';
|
|
4
4
|
import { Visit } from '../Visit.js';
|
|
5
5
|
|
|
6
6
|
describe('Hook registry', () => {
|
|
@@ -37,14 +37,14 @@ describe('Hook registry', () => {
|
|
|
37
37
|
swup.hooks.on('enable', handler1);
|
|
38
38
|
swup.hooks.on('enable', handler2);
|
|
39
39
|
|
|
40
|
-
await swup.hooks.call('enable');
|
|
40
|
+
await swup.hooks.call('enable', undefined);
|
|
41
41
|
|
|
42
42
|
expect(handler1).toBeCalledTimes(1);
|
|
43
43
|
expect(handler2).toBeCalledTimes(1);
|
|
44
44
|
|
|
45
45
|
swup.hooks.off('enable', handler2);
|
|
46
46
|
|
|
47
|
-
await swup.hooks.call('enable');
|
|
47
|
+
await swup.hooks.call('enable', undefined);
|
|
48
48
|
|
|
49
49
|
expect(handler1).toBeCalledTimes(2);
|
|
50
50
|
expect(handler2).toBeCalledTimes(1);
|
|
@@ -60,14 +60,14 @@ describe('Hook registry', () => {
|
|
|
60
60
|
|
|
61
61
|
expect(unregister1).toBeTypeOf('function');
|
|
62
62
|
|
|
63
|
-
await swup.hooks.call('enable');
|
|
63
|
+
await swup.hooks.call('enable', undefined);
|
|
64
64
|
|
|
65
65
|
expect(handler1).toBeCalledTimes(1);
|
|
66
66
|
expect(handler2).toBeCalledTimes(1);
|
|
67
67
|
|
|
68
68
|
unregister2();
|
|
69
69
|
|
|
70
|
-
await swup.hooks.call('enable');
|
|
70
|
+
await swup.hooks.call('enable', undefined);
|
|
71
71
|
|
|
72
72
|
expect(handler1).toBeCalledTimes(2);
|
|
73
73
|
expect(handler2).toBeCalledTimes(1);
|
|
@@ -79,7 +79,7 @@ describe('Hook registry', () => {
|
|
|
79
79
|
|
|
80
80
|
swup.hooks.on('enable', handler);
|
|
81
81
|
|
|
82
|
-
await swup.hooks.call('enable');
|
|
82
|
+
await swup.hooks.call('enable', undefined);
|
|
83
83
|
|
|
84
84
|
expect(handler).toBeCalledTimes(1);
|
|
85
85
|
});
|
|
@@ -303,18 +303,17 @@ describe('Types', () => {
|
|
|
303
303
|
const swup = new Swup();
|
|
304
304
|
|
|
305
305
|
// @ts-expect-no-error
|
|
306
|
-
swup.hooks.on(
|
|
307
|
-
'history:popstate',
|
|
308
|
-
(visit: Visit, { event }: { event: PopStateEvent }) => {}
|
|
309
|
-
);
|
|
306
|
+
swup.hooks.on('history:popstate', (visit: Visit, { event }: { event: PopStateEvent }) => {});
|
|
310
307
|
// @ts-expect-no-error
|
|
311
308
|
await swup.hooks.call('history:popstate', { event: new PopStateEvent('') });
|
|
312
309
|
|
|
313
|
-
// @ts-expect-error
|
|
310
|
+
// @ts-expect-error: first arg must be Visit object
|
|
314
311
|
swup.hooks.on('history:popstate', ({ event: MouseEvent }) => {});
|
|
315
|
-
// @ts-expect-error
|
|
312
|
+
// @ts-expect-error: event arg must be PopStateEvent
|
|
316
313
|
swup.hooks.on('history:popstate', (visit: Visit, { event }: { event: MouseEvent }) => {});
|
|
317
|
-
// @ts-expect-error
|
|
314
|
+
// @ts-expect-error: event arg must be PopStateEvent
|
|
318
315
|
await swup.hooks.call('history:popstate', { event: new MouseEvent('') });
|
|
316
|
+
// @ts-expect-error: handler arg must be optional: handler?
|
|
317
|
+
swup.hooks.replace('enable', (visit: Visit, args: undefined, handler: DefaultHandler<'enable'>) => {});
|
|
319
318
|
});
|
|
320
319
|
});
|