swup 4.2.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 -119
- 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 -77
- 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 -35
- 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 +7 -6
- 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 +13 -0
- 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/navigate.ts +23 -4
- package/src/modules/plugins.ts +3 -3
- package/src/modules/renderPage.ts +1 -5
- package/src/modules/replaceContent.ts +13 -0
- package/src/utils/index.ts +5 -4
- /package/src/modules/__test__/{delegateEvent.ts → delegateEvent.test.ts} +0 -0
package/src/Swup.ts
CHANGED
|
@@ -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 = {
|
|
@@ -47,7 +48,7 @@ export type Options = {
|
|
|
47
48
|
/** Rewrite URLs before loading them. */
|
|
48
49
|
resolveUrl: (url: string) => string;
|
|
49
50
|
/** Callback for telling swup to ignore certain popstate events. */
|
|
50
|
-
skipPopStateHandling: (event:
|
|
51
|
+
skipPopStateHandling: (event: PopStateEvent) => boolean;
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
const defaults: Options = {
|
|
@@ -56,7 +57,7 @@ const defaults: Options = {
|
|
|
56
57
|
animationScope: 'html',
|
|
57
58
|
cache: true,
|
|
58
59
|
containers: ['#swup'],
|
|
59
|
-
ignoreVisit: (url, { el
|
|
60
|
+
ignoreVisit: (url, { el } = {}) => !!el?.closest('[data-no-swup]'),
|
|
60
61
|
linkSelector: 'a[href]',
|
|
61
62
|
linkToSelf: 'scroll',
|
|
62
63
|
plugins: [],
|
|
@@ -65,7 +66,7 @@ const defaults: Options = {
|
|
|
65
66
|
'X-Requested-With': 'swup',
|
|
66
67
|
'Accept': 'text/html, application/xhtml+xml'
|
|
67
68
|
},
|
|
68
|
-
skipPopStateHandling: (event) => event.state?.source !== 'swup'
|
|
69
|
+
skipPopStateHandling: (event) => (event.state as HistoryState)?.source !== 'swup'
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
/** Swup page transition library. */
|
|
@@ -101,7 +102,7 @@ export default class Swup {
|
|
|
101
102
|
findPlugin = findPlugin;
|
|
102
103
|
|
|
103
104
|
/** Log a message. Has no effect unless debug plugin is installed */
|
|
104
|
-
log: (message: string, context?:
|
|
105
|
+
log: (message: string, context?: unknown) => void = () => {};
|
|
105
106
|
|
|
106
107
|
/** Navigate to a new URL */
|
|
107
108
|
navigate = navigate;
|
|
@@ -296,7 +297,7 @@ export default class Swup {
|
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
protected handlePopState(event: PopStateEvent) {
|
|
299
|
-
const href = event.state?.url ?? location.href;
|
|
300
|
+
const href: string = (event.state as HistoryState)?.url ?? location.href;
|
|
300
301
|
|
|
301
302
|
// Exit early if this event should be ignored
|
|
302
303
|
if (this.options.skipPopStateHandling(event)) {
|
|
@@ -324,7 +325,7 @@ export default class Swup {
|
|
|
324
325
|
this.visit.history.popstate = true;
|
|
325
326
|
|
|
326
327
|
// Determine direction of history visit
|
|
327
|
-
const index = Number(event.state?.index);
|
|
328
|
+
const index = Number((event.state as HistoryState)?.index);
|
|
328
329
|
if (index) {
|
|
329
330
|
const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards';
|
|
330
331
|
this.visit.history.direction = direction;
|
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 */
|
|
@@ -60,6 +62,13 @@ export interface VisitTrigger {
|
|
|
60
62
|
event?: Event;
|
|
61
63
|
}
|
|
62
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
|
+
|
|
63
72
|
export interface VisitHistory {
|
|
64
73
|
/** History action to perform: `push` for creating a new history entry, `replace` for replacing the current entry. Default: `push` */
|
|
65
74
|
action: HistoryAction;
|
|
@@ -112,6 +121,10 @@ export function createVisit(
|
|
|
112
121
|
el,
|
|
113
122
|
event
|
|
114
123
|
},
|
|
124
|
+
cache: {
|
|
125
|
+
read: this.options.cache,
|
|
126
|
+
write: this.options.cache
|
|
127
|
+
},
|
|
115
128
|
history: {
|
|
116
129
|
action,
|
|
117
130
|
popstate: false,
|
|
@@ -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
|
});
|
|
@@ -7,7 +7,7 @@ import { classify } from '../helpers.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export const animatePageOut = async function (this: Swup) {
|
|
9
9
|
if (!this.visit.animation.animate) {
|
|
10
|
-
await this.hooks.call('animation:skip');
|
|
10
|
+
await this.hooks.call('animation:skip', undefined);
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -26,5 +26,5 @@ export const animatePageOut = async function (this: Swup) {
|
|
|
26
26
|
await this.awaitAnimations({ selector: visit.animation.selector });
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
await this.hooks.call('animation:out:end');
|
|
29
|
+
await this.hooks.call('animation:out:end', undefined);
|
|
30
30
|
};
|
|
@@ -150,7 +150,7 @@ export function getTransitionInfo(element: Element, expectedType?: AnimationType
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function isTransitionOrAnimationEvent(event:
|
|
153
|
+
function isTransitionOrAnimationEvent(event: Event): event is TransitionEvent | AnimationEvent {
|
|
154
154
|
return [`${TRANSITION}end`, `${ANIMATION}end`].includes(event.type);
|
|
155
155
|
}
|
|
156
156
|
|
package/src/modules/fetchPage.ts
CHANGED
|
@@ -10,13 +10,11 @@ export interface PageData {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** Define how a page is fetched. */
|
|
13
|
-
export interface FetchOptions extends RequestInit {
|
|
13
|
+
export interface FetchOptions extends Omit<RequestInit, 'cache'> {
|
|
14
14
|
/** The request method. */
|
|
15
15
|
method?: 'GET' | 'POST';
|
|
16
16
|
/** The body of the request: raw string, form data object or URL params. */
|
|
17
17
|
body?: string | FormData | URLSearchParams;
|
|
18
|
-
/** The headers of the request: key/value object. */
|
|
19
|
-
headers?: Record<string, string>;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export class FetchError extends Error {
|
|
@@ -66,8 +64,12 @@ export async function fetchPage(
|
|
|
66
64
|
const { url: finalUrl } = Location.fromUrl(responseUrl);
|
|
67
65
|
const page = { url: finalUrl, html };
|
|
68
66
|
|
|
69
|
-
//
|
|
70
|
-
if (
|
|
67
|
+
// Write to cache for safe methods and non-redirects
|
|
68
|
+
if (
|
|
69
|
+
this.visit.cache.write &&
|
|
70
|
+
(!options.method || options.method === 'GET') &&
|
|
71
|
+
url === finalUrl
|
|
72
|
+
) {
|
|
71
73
|
this.cache.set(page.url, page);
|
|
72
74
|
}
|
|
73
75
|
|
package/src/modules/navigate.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import Swup from '../Swup.js';
|
|
2
2
|
import { createHistoryRecord, updateHistoryRecord, getCurrentUrl, Location } from '../helpers.js';
|
|
3
|
-
import { FetchOptions } from './fetchPage.js';
|
|
3
|
+
import { FetchOptions, PageData } from './fetchPage.js';
|
|
4
4
|
import { VisitInitOptions } from './Visit.js';
|
|
5
5
|
|
|
6
6
|
export type HistoryAction = 'push' | 'replace';
|
|
7
7
|
export type HistoryDirection = 'forwards' | 'backwards';
|
|
8
8
|
export type NavigationToSelfAction = 'scroll' | 'navigate';
|
|
9
|
+
export type CacheControl = Partial<{ read: boolean; write: boolean }>;
|
|
9
10
|
|
|
10
11
|
/** Define how to navigate to a page. */
|
|
11
12
|
type NavigationOptions = {
|
|
@@ -15,6 +16,8 @@ type NavigationOptions = {
|
|
|
15
16
|
animation?: string;
|
|
16
17
|
/** History action to perform: `push` for creating a new history entry, `replace` for replacing the current entry. Default: `push` */
|
|
17
18
|
history?: HistoryAction;
|
|
19
|
+
/** Whether this visit should read from or write to the cache. */
|
|
20
|
+
cache?: CacheControl;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -83,14 +86,30 @@ export async function performNavigation(
|
|
|
83
86
|
this.visit.animation.name = animation;
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
// Sanitize cache option
|
|
90
|
+
if (typeof options.cache === 'object') {
|
|
91
|
+
this.visit.cache.read = options.cache.read ?? this.visit.cache.read;
|
|
92
|
+
this.visit.cache.write = options.cache.write ?? this.visit.cache.write;
|
|
93
|
+
} else if (options.cache !== undefined) {
|
|
94
|
+
this.visit.cache = { read: !!options.cache, write: !!options.cache };
|
|
95
|
+
}
|
|
96
|
+
// Delete this so that window.fetch doesn't mis-interpret it
|
|
97
|
+
delete options.cache;
|
|
98
|
+
|
|
86
99
|
try {
|
|
87
|
-
await this.hooks.call('visit:start');
|
|
100
|
+
await this.hooks.call('visit:start', undefined);
|
|
88
101
|
|
|
89
102
|
// Begin loading page
|
|
90
103
|
const pagePromise = this.hooks.call('page:load', { options }, async (visit, args) => {
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
// Read from cache
|
|
105
|
+
let cachedPage: PageData | undefined;
|
|
106
|
+
if (this.visit.cache.read) {
|
|
107
|
+
cachedPage = this.cache.get(visit.to.url);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
args.page = cachedPage || (await this.fetchPage(visit.to.url, args.options));
|
|
93
111
|
args.cache = !!cachedPage;
|
|
112
|
+
|
|
94
113
|
return args.page;
|
|
95
114
|
});
|
|
96
115
|
|