swup 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/Swup.cjs +1 -1
  2. package/dist/Swup.cjs.map +1 -1
  3. package/dist/Swup.modern.js +1 -1
  4. package/dist/Swup.modern.js.map +1 -1
  5. package/dist/Swup.module.js +1 -1
  6. package/dist/Swup.module.js.map +1 -1
  7. package/dist/Swup.umd.js +1 -1
  8. package/dist/Swup.umd.js.map +1 -1
  9. package/dist/types/Swup.d.ts +119 -119
  10. package/dist/types/__test__/index.test.d.ts +1 -1
  11. package/dist/types/config/version.d.ts +5 -5
  12. package/dist/types/helpers/Location.d.ts +24 -24
  13. package/dist/types/helpers/__test__/matchPath.test.d.ts +1 -1
  14. package/dist/types/helpers/classify.d.ts +2 -2
  15. package/dist/types/helpers/createHistoryRecord.d.ts +9 -2
  16. package/dist/types/helpers/delegateEvent.d.ts +7 -7
  17. package/dist/types/helpers/getCurrentUrl.d.ts +4 -4
  18. package/dist/types/helpers/matchPath.d.ts +4 -4
  19. package/dist/types/helpers/updateHistoryRecord.d.ts +2 -2
  20. package/dist/types/helpers.d.ts +7 -7
  21. package/dist/types/index.d.ts +14 -14
  22. package/dist/types/modules/Cache.d.ts +34 -34
  23. package/dist/types/modules/Classes.d.ts +13 -13
  24. package/dist/types/modules/Hooks.d.ts +268 -250
  25. package/dist/types/modules/Visit.d.ts +80 -77
  26. package/dist/types/modules/__test__/cache.test.d.ts +1 -1
  27. package/dist/types/modules/__test__/{delegateEvent.d.ts → delegateEvent.test.d.ts} +1 -1
  28. package/dist/types/modules/__test__/hooks.test.d.ts +1 -1
  29. package/dist/types/modules/__test__/plugins.test.d.ts +1 -1
  30. package/dist/types/modules/__test__/replaceContent.test.d.ts +1 -1
  31. package/dist/types/modules/animatePageIn.d.ts +6 -6
  32. package/dist/types/modules/animatePageOut.d.ts +6 -6
  33. package/dist/types/modules/awaitAnimations.d.ts +19 -19
  34. package/dist/types/modules/fetchPage.d.ts +27 -29
  35. package/dist/types/modules/getAnchorElement.d.ts +9 -9
  36. package/dist/types/modules/navigate.d.ts +41 -35
  37. package/dist/types/modules/plugins.d.ts +26 -26
  38. package/dist/types/modules/renderPage.d.ts +7 -7
  39. package/dist/types/modules/replaceContent.d.ts +13 -13
  40. package/dist/types/modules/resolveUrl.d.ts +14 -14
  41. package/dist/types/modules/scrollToContent.d.ts +6 -6
  42. package/dist/types/utils/index.d.ts +20 -20
  43. package/dist/types/utils.d.ts +1 -1
  44. package/package.json +9 -3
  45. package/src/Swup.ts +26 -17
  46. package/src/config/version.ts +1 -2
  47. package/src/helpers/createHistoryRecord.ts +9 -1
  48. package/src/helpers/matchPath.ts +1 -1
  49. package/src/helpers/updateHistoryRecord.ts +4 -2
  50. package/src/modules/Cache.ts +2 -2
  51. package/src/modules/Classes.ts +1 -1
  52. package/src/modules/Hooks.ts +91 -39
  53. package/src/modules/Visit.ts +19 -21
  54. package/src/modules/__test__/cache.test.ts +3 -3
  55. package/src/modules/__test__/hooks.test.ts +12 -13
  56. package/src/modules/animatePageIn.ts +1 -1
  57. package/src/modules/animatePageOut.ts +2 -2
  58. package/src/modules/awaitAnimations.ts +1 -1
  59. package/src/modules/fetchPage.ts +7 -5
  60. package/src/modules/navigate.ts +23 -4
  61. package/src/modules/plugins.ts +3 -3
  62. package/src/modules/renderPage.ts +1 -5
  63. package/src/modules/replaceContent.ts +13 -0
  64. package/src/modules/scrollToContent.ts +1 -1
  65. package/src/utils/index.ts +5 -4
  66. /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: any) => boolean;
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, event } = {}) => !!el?.closest('[data-no-swup]'),
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?: any) => void = () => {};
105
+ log: (message: string, context?: unknown) => void = () => {};
105
106
 
106
107
  /** Navigate to a new URL */
107
108
  navigate = navigate;
@@ -166,6 +167,11 @@ export default class Swup {
166
167
 
167
168
  window.addEventListener('popstate', this.handlePopState);
168
169
 
170
+ // Set scroll restoration to manual if animating history visits
171
+ if (this.options.animateHistoryBrowsing) {
172
+ window.history.scrollRestoration = 'manual';
173
+ }
174
+
169
175
  // Initial save to cache
170
176
  if (this.options.cache) {
171
177
  // Disabled to avoid caching modified dom state: logic moved to preload plugin
@@ -296,7 +302,7 @@ export default class Swup {
296
302
  }
297
303
 
298
304
  protected handlePopState(event: PopStateEvent) {
299
- const href = event.state?.url ?? location.href;
305
+ const href: string = (event.state as HistoryState)?.url ?? location.href;
300
306
 
301
307
  // Exit early if this event should be ignored
302
308
  if (this.options.skipPopStateHandling(event)) {
@@ -309,27 +315,30 @@ export default class Swup {
309
315
  }
310
316
 
311
317
  const { url, hash } = Location.fromUrl(href);
312
- const animate = this.options.animateHistoryBrowsing;
313
- const resetScroll = this.options.animateHistoryBrowsing;
314
-
315
- this.visit = this.createVisit({
316
- to: url,
317
- hash,
318
- event,
319
- animate,
320
- resetScroll
321
- });
322
318
 
323
- // Mark as popstate visit
319
+ this.visit = this.createVisit({ to: url, hash, event });
320
+
321
+ // Mark as history visit
324
322
  this.visit.history.popstate = true;
325
323
 
326
324
  // Determine direction of history visit
327
- const index = Number(event.state?.index);
325
+ const index = Number((event.state as HistoryState)?.index);
328
326
  if (index) {
329
327
  const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards';
330
328
  this.visit.history.direction = direction;
331
329
  }
332
330
 
331
+ // Disable animation & scrolling for history visits
332
+ this.visit.animation.animate = false;
333
+ this.visit.scroll.reset = false;
334
+ this.visit.scroll.target = false;
335
+
336
+ // Animated history visit: re-enable animation & scroll reset
337
+ if (this.options.animateHistoryBrowsing) {
338
+ this.visit.animation.animate = true;
339
+ this.visit.scroll.reset = true;
340
+ }
341
+
333
342
  // Does this even do anything?
334
343
  // if (!hash) {
335
344
  // event.preventDefault();
@@ -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
- // Ignore next line in TypeScript as package.json is outside of rootDir
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',
@@ -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 data = {
10
- ...history.state,
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',
@@ -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: Record<string, any>) {
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. */
@@ -23,7 +23,7 @@ export class Classes {
23
23
 
24
24
  protected get targets(): HTMLElement[] {
25
25
  if (!this.selector.trim()) return [];
26
- return queryAll(this.selector) as HTMLElement[];
26
+ return queryAll(this.selector);
27
27
  }
28
28
 
29
29
  add(...classes: string[]): void {
@@ -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?: Handler<T>
49
- ) => Promise<any> | any;
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<T extends HookName> = {
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: Handler<T>;
74
- defaultHandler?: Handler<T>;
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
- on<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
187
- on<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
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: HookOptions = {}
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
- before<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
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
- replace<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
235
- replace<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
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: Handler<T>,
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
- once<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
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
- off<T extends HookName>(hook: T, handler: Handler<T>): void;
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?: HookArguments<T>,
293
- defaultHandler?: Handler<T>
294
- ): Promise<any> {
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?: HookArguments<T>,
314
- defaultHandler?: Handler<T>
315
- ): any {
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
- protected async run<T extends HookName>(
330
- registrations: HookRegistration<T>[],
331
- args?: HookArguments<T>
332
- ): Promise<any> {
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
- protected runSync<T extends HookName>(
350
- registrations: HookRegistration<T>[],
351
- args?: HookArguments<T>
352
- ): any[] {
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 as HookArguments<T>, defaultHandler);
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?: Handler<T>) {
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
- // This is an array to allow passing it into hooks.run() directly
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): Handler<T> | undefined => {
452
+ const createDefaultHandler = (index: number): DefaultHandler<T> | undefined => {
401
453
  const next = replace[index - 1];
402
454
  if (next) {
403
455
  return (visit, args) =>
@@ -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 */
@@ -50,7 +52,7 @@ export interface VisitScroll {
50
52
  /** Whether to reset the scroll position after the visit. Default: `true` */
51
53
  reset: boolean;
52
54
  /** Anchor element to scroll to on the next page. */
53
- target?: string;
55
+ target?: string | false;
54
56
  }
55
57
 
56
58
  export interface VisitTrigger {
@@ -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;
@@ -73,38 +82,23 @@ export interface VisitInitOptions {
73
82
  to: string;
74
83
  from?: string;
75
84
  hash?: string;
76
- animate?: boolean;
77
- animation?: string;
78
- targets?: string[];
79
85
  el?: Element;
80
86
  event?: Event;
81
- action?: HistoryAction;
82
- resetScroll?: boolean;
83
87
  }
84
88
 
85
89
  /** Create a new visit object. */
86
90
  export function createVisit(
87
91
  this: Swup,
88
- {
89
- to,
90
- from = this.currentPageUrl,
91
- hash,
92
- animate = true,
93
- animation: name,
94
- el,
95
- event,
96
- action = 'push',
97
- resetScroll: reset = true
98
- }: VisitInitOptions
92
+ { to, from = this.currentPageUrl, hash, el, event }: VisitInitOptions
99
93
  ): Visit {
100
94
  return {
101
95
  from: { url: from },
102
96
  to: { url: to, hash },
103
97
  containers: this.options.containers,
104
98
  animation: {
105
- animate,
99
+ animate: true,
106
100
  wait: false,
107
- name,
101
+ name: undefined,
108
102
  scope: this.options.animationScope,
109
103
  selector: this.options.animationSelector
110
104
  },
@@ -112,13 +106,17 @@ export function createVisit(
112
106
  el,
113
107
  event
114
108
  },
109
+ cache: {
110
+ read: this.options.cache,
111
+ write: this.options.cache
112
+ },
115
113
  history: {
116
- action,
114
+ action: 'push',
117
115
  popstate: false,
118
116
  direction: undefined
119
117
  },
120
118
  scroll: {
121
- reset,
119
+ reset: true,
122
120
  target: undefined
123
121
  }
124
122
  };
@@ -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 as AugmentedCacheData);
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') as AugmentedCacheData;
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 } as AugmentedCacheData);
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
  });
@@ -27,5 +27,5 @@ export const animatePageIn = async function (this: Swup) {
27
27
 
28
28
  await animation;
29
29
 
30
- await this.hooks.call('animation:in:end');
30
+ await this.hooks.call('animation:in:end', undefined);
31
31
  };
@@ -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: any): event is TransitionEvent | AnimationEvent {
153
+ function isTransitionOrAnimationEvent(event: Event): event is TransitionEvent | AnimationEvent {
154
154
  return [`${TRANSITION}end`, `${ANIMATION}end`].includes(event.type);
155
155
  }
156
156