swup 4.4.3 → 4.5.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.
Files changed (57) 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 +8 -4
  10. package/dist/types/Swup.d.ts.map +1 -1
  11. package/dist/types/helpers/Location.d.ts.map +1 -1
  12. package/dist/types/helpers/history.d.ts +14 -0
  13. package/dist/types/helpers/history.d.ts.map +1 -0
  14. package/dist/types/helpers.d.ts +1 -2
  15. package/dist/types/helpers.d.ts.map +1 -1
  16. package/dist/types/index.d.ts +2 -2
  17. package/dist/types/index.d.ts.map +1 -1
  18. package/dist/types/modules/Classes.d.ts.map +1 -1
  19. package/dist/types/modules/Hooks.d.ts +22 -8
  20. package/dist/types/modules/Hooks.d.ts.map +1 -1
  21. package/dist/types/modules/Visit.d.ts +28 -22
  22. package/dist/types/modules/Visit.d.ts.map +1 -1
  23. package/dist/types/modules/animatePageIn.d.ts +2 -1
  24. package/dist/types/modules/animatePageIn.d.ts.map +1 -1
  25. package/dist/types/modules/animatePageOut.d.ts +2 -1
  26. package/dist/types/modules/animatePageOut.d.ts.map +1 -1
  27. package/dist/types/modules/fetchPage.d.ts.map +1 -1
  28. package/dist/types/modules/navigate.d.ts +2 -2
  29. package/dist/types/modules/navigate.d.ts.map +1 -1
  30. package/dist/types/modules/renderPage.d.ts +2 -1
  31. package/dist/types/modules/renderPage.d.ts.map +1 -1
  32. package/dist/types/modules/replaceContent.d.ts.map +1 -1
  33. package/dist/types/modules/scrollToContent.d.ts +2 -1
  34. package/dist/types/modules/scrollToContent.d.ts.map +1 -1
  35. package/package.json +2 -1
  36. package/src/Swup.ts +40 -37
  37. package/src/helpers/Location.ts +1 -0
  38. package/src/helpers/history.ts +37 -0
  39. package/src/helpers.ts +1 -2
  40. package/src/index.ts +3 -1
  41. package/src/modules/Cache.ts +2 -2
  42. package/src/modules/Classes.ts +8 -1
  43. package/src/modules/Hooks.ts +91 -30
  44. package/src/modules/Visit.ts +86 -47
  45. package/src/modules/animatePageIn.ts +9 -8
  46. package/src/modules/animatePageOut.ts +7 -18
  47. package/src/modules/fetchPage.ts +9 -11
  48. package/src/modules/navigate.ts +78 -34
  49. package/src/modules/renderPage.ts +15 -13
  50. package/src/modules/replaceContent.ts +1 -0
  51. package/src/modules/scrollToContent.ts +6 -4
  52. package/dist/types/helpers/createHistoryRecord.d.ts +0 -10
  53. package/dist/types/helpers/createHistoryRecord.d.ts.map +0 -1
  54. package/dist/types/helpers/updateHistoryRecord.d.ts +0 -3
  55. package/dist/types/helpers/updateHistoryRecord.d.ts.map +0 -1
  56. package/src/helpers/createHistoryRecord.ts +0 -24
  57. package/src/helpers/updateHistoryRecord.ts +0 -19
package/src/Swup.ts CHANGED
@@ -21,7 +21,7 @@ import { renderPage } from './modules/renderPage.js';
21
21
  import { use, unuse, findPlugin, type Plugin } from './modules/plugins.js';
22
22
  import { isSameResolvedUrl, resolveUrl } from './modules/resolveUrl.js';
23
23
  import { nextTick } from './utils.js';
24
- import { type HistoryState } from './helpers/createHistoryRecord.js';
24
+ import { type HistoryState } from './helpers/history.js';
25
25
 
26
26
  /** Options for customizing swup's behavior. */
27
27
  export type Options = {
@@ -41,6 +41,8 @@ export type Options = {
41
41
  linkSelector: string;
42
42
  /** How swup handles links to the same page. Default: `scroll` */
43
43
  linkToSelf: NavigationToSelfAction;
44
+ /** Enable native animations using the View Transitions API. */
45
+ native: boolean;
44
46
  /** Plugins to register on startup. */
45
47
  plugins: Plugin[];
46
48
  /** Custom headers sent along with fetch requests. */
@@ -62,6 +64,7 @@ const defaults: Options = {
62
64
  ignoreVisit: (url, { el } = {}) => !!el?.closest('[data-no-swup]'),
63
65
  linkSelector: 'a[href]',
64
66
  linkToSelf: 'scroll',
67
+ native: false,
65
68
  plugins: [],
66
69
  resolveUrl: (url) => url,
67
70
  requestHeaders: {
@@ -98,6 +101,8 @@ export default class Swup {
98
101
  protected clickDelegate?: DelegateEventUnsubscribe;
99
102
  /** Navigation status */
100
103
  protected navigating: boolean = false;
104
+ /** Run anytime a visit ends */
105
+ protected onVisitEnd?: () => Promise<unknown>;
101
106
 
102
107
  /** Install a plugin */
103
108
  use = use;
@@ -185,6 +190,9 @@ export default class Swup {
185
190
  // https://github.com/swup/swup/issues/475
186
191
  }
187
192
 
193
+ // Sanitize/check native option
194
+ this.options.native = this.options.native && !!document.startViewTransition;
195
+
188
196
  // Mount plugins
189
197
  this.options.plugins.forEach((plugin) => this.use(plugin));
190
198
 
@@ -197,9 +205,10 @@ export default class Swup {
197
205
  await nextTick();
198
206
 
199
207
  // Trigger enable hook
200
- await this.hooks.call('enable', undefined, () => {
201
- // Add swup-enabled class to html tag
202
- document.documentElement.classList.add('swup-enabled');
208
+ await this.hooks.call('enable', undefined, undefined, () => {
209
+ const html = document.documentElement;
210
+ html.classList.add('swup-enabled');
211
+ html.classList.toggle('swup-native', this.options.native);
203
212
  });
204
213
  }
205
214
 
@@ -218,9 +227,10 @@ export default class Swup {
218
227
  this.options.plugins.forEach((plugin) => this.unuse(plugin));
219
228
 
220
229
  // trigger disable hook
221
- await this.hooks.call('disable', undefined, () => {
222
- // remove swup-enabled class from html tag
223
- document.documentElement.classList.remove('swup-enabled');
230
+ await this.hooks.call('disable', undefined, undefined, () => {
231
+ const html = document.documentElement;
232
+ html.classList.remove('swup-enabled');
233
+ html.classList.remove('swup-native');
224
234
  });
225
235
 
226
236
  // remove handlers
@@ -265,11 +275,11 @@ export default class Swup {
265
275
  return;
266
276
  }
267
277
 
268
- this.visit = this.createVisit({ to: url, hash, el, event });
278
+ const visit = this.createVisit({ to: url, hash, el, event });
269
279
 
270
280
  // Exit early if control key pressed
271
281
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
272
- this.hooks.call('link:newtab', { href });
282
+ this.hooks.callSync('link:newtab', visit, { href });
273
283
  return;
274
284
  }
275
285
 
@@ -278,8 +288,8 @@ export default class Swup {
278
288
  return;
279
289
  }
280
290
 
281
- this.hooks.callSync('link:click', { el, event }, () => {
282
- const from = this.visit.from.url ?? '';
291
+ this.hooks.callSync('link:click', visit, { el, event }, () => {
292
+ const from = visit.from.url ?? '';
283
293
 
284
294
  event.preventDefault();
285
295
 
@@ -287,20 +297,18 @@ export default class Swup {
287
297
  if (!url || url === from) {
288
298
  if (hash) {
289
299
  // With hash: scroll to anchor
290
- this.hooks.callSync('link:anchor', { hash }, () => {
300
+ this.hooks.callSync('link:anchor', visit, { hash }, () => {
291
301
  updateHistoryRecord(url + hash);
292
- this.scrollToContent();
302
+ this.scrollToContent(visit);
293
303
  });
294
304
  } else {
295
305
  // Without hash: scroll to top or load/reload page
296
- this.hooks.callSync('link:self', undefined, () => {
297
- switch (this.options.linkToSelf) {
298
- case 'navigate':
299
- return this.performNavigation();
300
- case 'scroll':
301
- default:
302
- updateHistoryRecord(url);
303
- return this.scrollToContent();
306
+ this.hooks.callSync('link:self', visit, undefined, () => {
307
+ if (this.options.linkToSelf === 'navigate') {
308
+ this.performNavigation(visit);
309
+ } else {
310
+ updateHistoryRecord(url);
311
+ this.scrollToContent(visit);
304
312
  }
305
313
  });
306
314
  }
@@ -313,7 +321,7 @@ export default class Swup {
313
321
  }
314
322
 
315
323
  // Finally, proceed with loading the page
316
- this.performNavigation();
324
+ this.performNavigation(visit);
317
325
  });
318
326
  }
319
327
 
@@ -332,37 +340,32 @@ export default class Swup {
332
340
 
333
341
  const { url, hash } = Location.fromUrl(href);
334
342
 
335
- this.visit = this.createVisit({ to: url, hash, event });
343
+ const visit = this.createVisit({ to: url, hash, event });
336
344
 
337
345
  // Mark as history visit
338
- this.visit.history.popstate = true;
346
+ visit.history.popstate = true;
339
347
 
340
348
  // Determine direction of history visit
341
349
  const index = (event.state as HistoryState)?.index ?? 0;
342
350
  if (index && index !== this.currentHistoryIndex) {
343
351
  const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards';
344
- this.visit.history.direction = direction;
352
+ visit.history.direction = direction;
345
353
  this.currentHistoryIndex = index;
346
354
  }
347
355
 
348
356
  // Disable animation & scrolling for history visits
349
- this.visit.animation.animate = false;
350
- this.visit.scroll.reset = false;
351
- this.visit.scroll.target = false;
357
+ visit.animation.animate = false;
358
+ visit.scroll.reset = false;
359
+ visit.scroll.target = false;
352
360
 
353
361
  // Animated history visit: re-enable animation & scroll reset
354
362
  if (this.options.animateHistoryBrowsing) {
355
- this.visit.animation.animate = true;
356
- this.visit.scroll.reset = true;
363
+ visit.animation.animate = true;
364
+ visit.scroll.reset = true;
357
365
  }
358
366
 
359
- // Does this even do anything?
360
- // if (!hash) {
361
- // event.preventDefault();
362
- // }
363
-
364
- this.hooks.callSync('history:popstate', { event }, () => {
365
- this.performNavigation();
367
+ this.hooks.callSync('history:popstate', visit, { event }, () => {
368
+ this.performNavigation(visit);
366
369
  });
367
370
  }
368
371
 
@@ -6,6 +6,7 @@
6
6
  export class Location extends URL {
7
7
  constructor(url: URL | string, base: string = document.baseURI) {
8
8
  super(url.toString(), base);
9
+ // Fix Safari bug with extending native classes
9
10
  Object.setPrototypeOf(this, Location.prototype);
10
11
  }
11
12
 
@@ -0,0 +1,37 @@
1
+ import { getCurrentUrl } from './getCurrentUrl.js';
2
+
3
+ export interface HistoryState {
4
+ url: string;
5
+ source: 'swup';
6
+ random: number;
7
+ index?: number;
8
+ [key: string]: unknown;
9
+ }
10
+
11
+ type HistoryData = Record<string, unknown>;
12
+
13
+ /** Create a new history record with a custom swup identifier. */
14
+ export const createHistoryRecord = (url: string, data: HistoryData = {}): void => {
15
+ url = url || getCurrentUrl({ hash: true });
16
+ const state: HistoryState = {
17
+ url,
18
+ random: Math.random(),
19
+ source: 'swup',
20
+ ...data
21
+ };
22
+ history.pushState(state, '', url);
23
+ };
24
+
25
+ /** Update the current history record with a custom swup identifier. */
26
+ export const updateHistoryRecord = (url: string | null = null, data: HistoryData = {}): void => {
27
+ url = url || getCurrentUrl({ hash: true });
28
+ const currentState = (history.state as HistoryState) || {};
29
+ const state: HistoryState = {
30
+ ...currentState,
31
+ url,
32
+ random: Math.random(),
33
+ source: 'swup',
34
+ ...data
35
+ };
36
+ history.replaceState(state, '', url);
37
+ };
package/src/helpers.ts CHANGED
@@ -2,8 +2,7 @@
2
2
  // e.g. import { updateHistoryRecord } from 'swup'
3
3
 
4
4
  export { classify } from './helpers/classify.js';
5
- export { createHistoryRecord } from './helpers/createHistoryRecord.js';
6
- export { updateHistoryRecord } from './helpers/updateHistoryRecord.js';
5
+ export { createHistoryRecord, updateHistoryRecord } from './helpers/history.js';
7
6
  export { delegateEvent } from './helpers/delegateEvent.js';
8
7
  export { getCurrentUrl } from './helpers/getCurrentUrl.js';
9
8
  export { Location } from './helpers/Location.js';
package/src/index.ts CHANGED
@@ -21,7 +21,8 @@ import type {
21
21
  HookHandler,
22
22
  HookDefaultHandler,
23
23
  HookOptions,
24
- HookUnregister
24
+ HookUnregister,
25
+ HookEvent
25
26
  } from './modules/Hooks.js';
26
27
  import type { Plugin } from './modules/plugins.js';
27
28
 
@@ -50,6 +51,7 @@ export type {
50
51
  HookDefaultHandler,
51
52
  HookOptions,
52
53
  HookUnregister,
54
+ HookEvent,
53
55
  DelegateEvent,
54
56
  DelegateEventHandler,
55
57
  DelegateEventUnsubscribe
@@ -49,7 +49,7 @@ export class Cache {
49
49
  url = this.resolve(url);
50
50
  page = { ...page, url };
51
51
  this.pages.set(url, page);
52
- this.swup.hooks.callSync('cache:set', { page });
52
+ this.swup.hooks.callSync('cache:set', undefined, { page });
53
53
  }
54
54
 
55
55
  /** Update a cache record, overwriting or adding custom data. */
@@ -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', undefined);
70
+ this.swup.hooks.callSync('cache:clear', undefined, undefined);
71
71
  }
72
72
 
73
73
  /** Remove all cache entries that return true for a given predicate function. */
@@ -3,7 +3,14 @@ import { queryAll } from '../utils.js';
3
3
 
4
4
  export class Classes {
5
5
  protected swup: Swup;
6
- protected swupClasses = ['to-', 'is-changing', 'is-rendering', 'is-popstate', 'is-animating'];
6
+ protected swupClasses = [
7
+ 'to-',
8
+ 'is-changing',
9
+ 'is-rendering',
10
+ 'is-popstate',
11
+ 'is-animating',
12
+ 'is-leaving'
13
+ ];
7
14
 
8
15
  constructor(swup: Swup) {
9
16
  this.swup = swup;
@@ -2,7 +2,7 @@ import type { DelegateEvent } from 'delegate-it';
2
2
 
3
3
  import type Swup from '../Swup.js';
4
4
  import { isPromise, runAsPromise } from '../utils.js';
5
- import type { Visit } from './Visit.js';
5
+ import { Visit } from './Visit.js';
6
6
  import type { FetchOptions, PageData } from './fetchPage.js';
7
7
 
8
8
  export interface HookDefinitions {
@@ -33,16 +33,16 @@ export interface HookDefinitions {
33
33
  'scroll:anchor': { hash: string; options: ScrollIntoViewOptions };
34
34
  'visit:start': undefined;
35
35
  'visit:transition': undefined;
36
+ 'visit:abort': undefined;
36
37
  'visit:end': undefined;
37
38
  }
38
39
 
39
40
  export interface HookReturnValues {
40
- 'content:scroll': Promise<boolean>;
41
+ 'content:scroll': Promise<boolean> | boolean;
41
42
  'fetch:request': Promise<Response>;
42
43
  'page:load': Promise<PageData>;
43
44
  'scroll:top': boolean;
44
45
  'scroll:anchor': boolean;
45
- 'visit:transition': Promise<boolean>;
46
46
  }
47
47
 
48
48
  export type HookArguments<T extends HookName> = HookDefinitions[T];
@@ -96,6 +96,14 @@ export type HookRegistration<
96
96
  defaultHandler?: HookDefaultHandler<T>;
97
97
  } & HookOptions;
98
98
 
99
+ type HookEventDetail = {
100
+ hook: HookName;
101
+ args: unknown;
102
+ visit: Visit;
103
+ };
104
+
105
+ export type HookEvent = CustomEvent<HookEventDetail>;
106
+
99
107
  type HookLedger<T extends HookName> = Map<HookHandler<T>, HookRegistration<T>>;
100
108
 
101
109
  interface HookRegistry extends Map<HookName, HookLedger<HookName>> {
@@ -146,6 +154,7 @@ export class Hooks {
146
154
  'scroll:anchor',
147
155
  'visit:start',
148
156
  'visit:transition',
157
+ 'visit:abort',
149
158
  'visit:end'
150
159
  ];
151
160
 
@@ -325,20 +334,29 @@ export class Hooks {
325
334
  * Trigger a hook asynchronously, executing its default handler and all registered handlers.
326
335
  * Will execute all handlers in order and `await` any `Promise`s they return.
327
336
  * @param hook Name of the hook to trigger
337
+ * @param visit The visit object this hook belongs to
328
338
  * @param args Arguments to pass to the handler
329
339
  * @param defaultHandler A default implementation of this hook to execute
330
340
  * @returns The resolved return value of the executed default handler
331
341
  */
342
+ // Overload: default order of arguments
343
+ async call<T extends HookName>(hook: T, visit: Visit | undefined, args: HookArguments<T>, defaultHandler?: HookDefaultHandler<T>): Promise<Awaited<ReturnType<HookDefaultHandler<T>>>>; // prettier-ignore
344
+ // Overload: legacy order of arguments, with visit missing
345
+ async call<T extends HookName>(hook: T, args: HookArguments<T>, defaultHandler?: HookDefaultHandler<T>): Promise<Awaited<ReturnType<HookDefaultHandler<T>>>>; // prettier-ignore
346
+ // Implementation
332
347
  async call<T extends HookName>(
333
348
  hook: T,
334
- args: HookArguments<T>,
335
- defaultHandler?: HookDefaultHandler<T>
349
+ arg1: Visit | HookArguments<T>,
350
+ arg2: HookArguments<T> | HookDefaultHandler<T>,
351
+ arg3?: HookDefaultHandler<T>
336
352
  ): Promise<Awaited<ReturnType<HookDefaultHandler<T>>>> {
353
+ const [visit, args, defaultHandler] = this.parseCallArgs(hook, arg1, arg2, arg3);
354
+
337
355
  const { before, handler, after } = this.getHandlers(hook, defaultHandler);
338
- await this.run(before, args);
339
- const [result] = await this.run(handler, args);
340
- await this.run(after, args);
341
- this.dispatchDomEvent(hook, args);
356
+ await this.run(before, visit, args);
357
+ const [result] = await this.run(handler, visit, args);
358
+ await this.run(after, visit, args);
359
+ this.dispatchDomEvent(hook, visit, args);
342
360
  return result;
343
361
  }
344
362
 
@@ -346,23 +364,51 @@ export class Hooks {
346
364
  * Trigger a hook synchronously, executing its default handler and all registered handlers.
347
365
  * Will execute all handlers in order, but will **not** `await` any `Promise`s they return.
348
366
  * @param hook Name of the hook to trigger
367
+ * @param visit The visit object this hook belongs to
349
368
  * @param args Arguments to pass to the handler
350
369
  * @param defaultHandler A default implementation of this hook to execute
351
370
  * @returns The (possibly unresolved) return value of the executed default handler
352
371
  */
372
+ // Overload: default order of arguments
373
+ callSync<T extends HookName>(hook: T, visit: Visit | undefined, args: HookArguments<T>, defaultHandler?: HookDefaultHandler<T>): ReturnType<HookDefaultHandler<T>>; // prettier-ignore
374
+ // Overload: legacy order of arguments, with visit missing
375
+ callSync<T extends HookName>(hook: T, args: HookArguments<T>, defaultHandler?: HookDefaultHandler<T>): ReturnType<HookDefaultHandler<T>>; // prettier-ignore
376
+ // Implementation
353
377
  callSync<T extends HookName>(
354
378
  hook: T,
355
- args: HookArguments<T>,
356
- defaultHandler?: HookDefaultHandler<T>
379
+ arg1: Visit | HookArguments<T>,
380
+ arg2: HookArguments<T> | HookDefaultHandler<T>,
381
+ arg3?: HookDefaultHandler<T>
357
382
  ): ReturnType<HookDefaultHandler<T>> {
383
+ const [visit, args, defaultHandler] = this.parseCallArgs(hook, arg1, arg2, arg3);
358
384
  const { before, handler, after } = this.getHandlers(hook, defaultHandler);
359
- this.runSync(before, args);
360
- const [result] = this.runSync(handler, args);
361
- this.runSync(after, args);
362
- this.dispatchDomEvent(hook, args);
385
+ this.runSync(before, visit, args);
386
+ const [result] = this.runSync(handler, visit, args);
387
+ this.runSync(after, visit, args);
388
+ this.dispatchDomEvent(hook, visit, args);
363
389
  return result;
364
390
  }
365
391
 
392
+ /**
393
+ * Parse the call arguments for call() and callSync() to allow legacy argument order.
394
+ */
395
+ protected parseCallArgs<T extends HookName>(
396
+ hook: T,
397
+ arg1: Visit | HookArguments<T> | undefined,
398
+ arg2: HookArguments<T> | HookDefaultHandler<T>,
399
+ arg3?: HookDefaultHandler<T>
400
+ ): [Visit | undefined, HookArguments<T>, HookDefaultHandler<T> | undefined] {
401
+ const isLegacyOrder =
402
+ !(arg1 instanceof Visit) && (typeof arg1 === 'object' || typeof arg2 === 'function');
403
+ if (isLegacyOrder) {
404
+ // Legacy positioning: arguments in second or handler passed in third place
405
+ return [undefined, arg1 as HookArguments<T>, arg2 as HookDefaultHandler<T>];
406
+ } else {
407
+ // Default positioning: visit passed in as first argument
408
+ return [arg1, arg2 as HookArguments<T>, arg3];
409
+ }
410
+ }
411
+
366
412
  /**
367
413
  * Execute the handlers for a hook, in order, as `Promise`s that will be `await`ed.
368
414
  * @param registrations The registrations (handler + options) to execute
@@ -370,21 +416,25 @@ export class Hooks {
370
416
  */
371
417
 
372
418
  // Overload: running HookDefaultHandler: expect HookDefaultHandler return type
373
- protected async run<T extends HookName>(registrations: HookRegistration<T, HookDefaultHandler<T>>[], args: HookArguments<T>): Promise<Awaited<ReturnType<HookDefaultHandler<T>>>[]>; // prettier-ignore
419
+ protected async run<T extends HookName>(registrations: HookRegistration<T, HookDefaultHandler<T>>[], visit: Visit | undefined, args: HookArguments<T>): Promise<Awaited<ReturnType<HookDefaultHandler<T>>>[]>; // prettier-ignore
374
420
  // Overload: running user handler: expect no specific type
375
- protected async run<T extends HookName>(registrations: HookRegistration<T>[], args: HookArguments<T>): Promise<unknown[]>; // prettier-ignore
421
+ protected async run<T extends HookName>(registrations: HookRegistration<T>[], visit: Visit | undefined, args: HookArguments<T>): Promise<unknown[]>; // prettier-ignore
376
422
  // Implementation
377
423
  protected async run<T extends HookName, R extends HookRegistration<T>[]>(
378
424
  registrations: R,
425
+ visit: Visit | undefined,
379
426
  args: HookArguments<T>
380
427
  ): Promise<Awaited<ReturnType<HookDefaultHandler<T>>> | unknown[]> {
381
428
  const results = [];
382
429
  for (const { hook, handler, defaultHandler, once } of registrations) {
383
- const result = await runAsPromise(handler, [this.swup.visit, args, defaultHandler]);
430
+ if (visit?.done) continue;
431
+ if (once) this.off(hook, handler);
432
+ const result = await runAsPromise(handler, [
433
+ visit || this.swup.visit,
434
+ args,
435
+ defaultHandler
436
+ ]);
384
437
  results.push(result);
385
- if (once) {
386
- this.off(hook, handler);
387
- }
388
438
  }
389
439
  return results;
390
440
  }
@@ -396,17 +446,20 @@ export class Hooks {
396
446
  */
397
447
 
398
448
  // Overload: running HookDefaultHandler: expect HookDefaultHandler return type
399
- protected runSync<T extends HookName>(registrations: HookRegistration<T, HookDefaultHandler<T>>[], args: HookArguments<T> ): ReturnType<HookDefaultHandler<T>>[]; // prettier-ignore
449
+ protected runSync<T extends HookName>(registrations: HookRegistration<T, HookDefaultHandler<T>>[], visit: Visit | undefined, args: HookArguments<T> ): ReturnType<HookDefaultHandler<T>>[]; // prettier-ignore
400
450
  // Overload: running user handler: expect no specific type
401
- protected runSync<T extends HookName>(registrations: HookRegistration<T>[], args: HookArguments<T>): unknown[]; // prettier-ignore
451
+ protected runSync<T extends HookName>(registrations: HookRegistration<T>[], visit: Visit | undefined, args: HookArguments<T>): unknown[]; // prettier-ignore
402
452
  // Implementation
403
453
  protected runSync<T extends HookName, R extends HookRegistration<T>[]>(
404
454
  registrations: R,
455
+ visit: Visit | undefined,
405
456
  args: HookArguments<T>
406
457
  ): (ReturnType<HookDefaultHandler<T>> | unknown)[] {
407
458
  const results = [];
408
459
  for (const { hook, handler, defaultHandler, once } of registrations) {
409
- const result = (handler as HookDefaultHandler<T>)(this.swup.visit, args, defaultHandler); // prettier-ignore
460
+ if (visit?.done) continue;
461
+ if (once) this.off(hook, handler);
462
+ const result = (handler as HookDefaultHandler<T>)(visit || this.swup.visit, args, defaultHandler); // prettier-ignore
410
463
  results.push(result);
411
464
  if (isPromise(result)) {
412
465
  console.warn(
@@ -414,9 +467,6 @@ export class Hooks {
414
467
  `Swup will not wait for it to resolve.`
415
468
  );
416
469
  }
417
- if (once) {
418
- this.off(hook, handler);
419
- }
420
470
  }
421
471
  return results;
422
472
  }
@@ -492,8 +542,19 @@ export class Hooks {
492
542
  * Dispatch a custom event on the `document` for a hook. Prefixed with `swup:`
493
543
  * @param hook Name of the hook.
494
544
  */
495
- protected dispatchDomEvent<T extends HookName>(hook: T, args?: HookArguments<T>): void {
496
- const detail = { hook, args, visit: this.swup.visit };
497
- document.dispatchEvent(new CustomEvent(`swup:${hook}`, { detail }));
545
+ protected dispatchDomEvent<T extends HookName>(
546
+ hook: T,
547
+ visit: Visit | undefined,
548
+ args?: HookArguments<T>
549
+ ): void {
550
+ if (visit?.done) return;
551
+
552
+ const detail: HookEventDetail = { hook, args, visit: visit || this.swup.visit };
553
+ document.dispatchEvent(
554
+ new CustomEvent<HookEventDetail>(`swup:any`, { detail, bubbles: true })
555
+ );
556
+ document.dispatchEvent(
557
+ new CustomEvent<HookEventDetail>(`swup:${hook}`, { detail, bubbles: true })
558
+ );
498
559
  }
499
560
  }