swup 4.0.0-rc.14 → 4.0.0-rc.21

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 (83) hide show
  1. package/README.md +100 -0
  2. package/dist/Swup.cjs +1 -1
  3. package/dist/Swup.cjs.map +1 -1
  4. package/dist/Swup.modern.js +1 -1
  5. package/dist/Swup.modern.js.map +1 -1
  6. package/dist/Swup.module.js +1 -1
  7. package/dist/Swup.module.js.map +1 -1
  8. package/dist/Swup.umd.js +1 -1
  9. package/dist/Swup.umd.js.map +1 -1
  10. package/dist/types/Swup.d.ts +62 -54
  11. package/dist/types/helpers/Location.d.ts +10 -7
  12. package/dist/types/helpers/delegateEvent.d.ts +3 -5
  13. package/dist/types/helpers/matchPath.d.ts +3 -0
  14. package/dist/types/helpers.d.ts +7 -10
  15. package/dist/types/index.d.ts +9 -6
  16. package/dist/types/modules/Cache.d.ts +15 -15
  17. package/dist/types/modules/Classes.d.ts +13 -0
  18. package/dist/types/modules/Context.d.ts +73 -0
  19. package/dist/types/modules/Hooks.d.ts +241 -0
  20. package/dist/types/modules/__test__/hooks.test.d.ts +1 -0
  21. package/dist/types/modules/__test__/replaceContent.test.d.ts +1 -0
  22. package/dist/types/modules/awaitAnimations.d.ts +21 -0
  23. package/dist/types/modules/enterPage.d.ts +6 -3
  24. package/dist/types/modules/fetchPage.d.ts +24 -4
  25. package/dist/types/modules/getAnchorElement.d.ts +8 -0
  26. package/dist/types/modules/leavePage.d.ts +6 -3
  27. package/dist/types/modules/plugins.d.ts +12 -5
  28. package/dist/types/modules/renderPage.d.ts +7 -7
  29. package/dist/types/modules/replaceContent.d.ts +8 -11
  30. package/dist/types/modules/visit.d.ts +33 -0
  31. package/dist/types/utils/index.d.ts +3 -1
  32. package/dist/types/utils.d.ts +1 -1
  33. package/package.json +7 -6
  34. package/src/Swup.ts +83 -65
  35. package/src/__test__/index.test.ts +3 -3
  36. package/src/helpers/Location.ts +2 -2
  37. package/src/helpers/delegateEvent.ts +2 -2
  38. package/src/helpers.ts +0 -1
  39. package/src/index.ts +34 -4
  40. package/src/modules/Cache.ts +2 -2
  41. package/src/modules/Classes.ts +48 -0
  42. package/src/modules/Context.ts +49 -19
  43. package/src/modules/Hooks.ts +103 -83
  44. package/src/modules/__test__/cache.test.ts +6 -6
  45. package/src/modules/__test__/hooks.test.ts +111 -40
  46. package/src/modules/__test__/replaceContent.test.ts +92 -0
  47. package/src/modules/{getAnimationPromises.ts → awaitAnimations.ts} +13 -18
  48. package/src/modules/enterPage.ts +21 -17
  49. package/src/modules/fetchPage.ts +12 -12
  50. package/src/modules/getAnchorElement.ts +2 -1
  51. package/src/modules/leavePage.ts +16 -12
  52. package/src/modules/plugins.ts +11 -8
  53. package/src/modules/renderPage.ts +28 -18
  54. package/src/modules/replaceContent.ts +24 -16
  55. package/src/modules/visit.ts +143 -0
  56. package/src/utils/index.ts +1 -2
  57. package/dist/types/helpers/cleanupAnimationClasses.d.ts +0 -2
  58. package/dist/types/helpers/fetch.d.ts +0 -5
  59. package/dist/types/helpers/getDataFromHtml.d.ts +0 -7
  60. package/dist/types/helpers/markSwupElements.d.ts +0 -1
  61. package/dist/types/modules/destroy.d.ts +0 -2
  62. package/dist/types/modules/enable.d.ts +0 -2
  63. package/dist/types/modules/events.d.ts +0 -33
  64. package/dist/types/modules/getAnimationPromises.d.ts +0 -7
  65. package/dist/types/modules/getPageData.d.ts +0 -6
  66. package/dist/types/modules/handleLinkToSamePage.d.ts +0 -2
  67. package/dist/types/modules/isSameResolvedUrl.d.ts +0 -8
  68. package/dist/types/modules/linkClickHandler.d.ts +0 -3
  69. package/dist/types/modules/loadPage.d.ts +0 -12
  70. package/dist/types/modules/off.d.ts +0 -3
  71. package/dist/types/modules/on.d.ts +0 -5
  72. package/dist/types/modules/popStateHandler.d.ts +0 -2
  73. package/dist/types/modules/resolveUrl.d.ts +0 -7
  74. package/dist/types/modules/shouldIgnoreVisit.d.ts +0 -4
  75. package/dist/types/modules/transitions.d.ts +0 -6
  76. package/dist/types/modules/triggerEvent.d.ts +0 -3
  77. package/dist/types/modules/triggerWillOpenNewWindow.d.ts +0 -2
  78. package/dist/types/modules/updateTransition.d.ts +0 -2
  79. package/readme.md +0 -78
  80. package/src/helpers/cleanupAnimationClasses.ts +0 -8
  81. package/src/modules/loadPage.ts +0 -99
  82. /package/dist/types/{modules/__test__/events.test.d.ts → helpers/__test__/matchPath.test.d.ts} +0 -0
  83. /package/dist/types/modules/__test__/{fetchPage.test.d.ts → cache.test.d.ts} +0 -0
package/src/Swup.ts CHANGED
@@ -2,89 +2,93 @@ import { DelegateEvent } from 'delegate-it';
2
2
 
3
3
  import version from './config/version.js';
4
4
 
5
- import {
6
- cleanupAnimationClasses,
7
- delegateEvent,
8
- getCurrentUrl,
9
- Location,
10
- updateHistoryRecord
11
- } from './helpers.js';
12
- import { Unsubscribe } from './helpers/delegateEvent.js';
5
+ import { delegateEvent, getCurrentUrl, Location, updateHistoryRecord } from './helpers.js';
6
+ import { DelegateEventUnsubscribe } from './helpers/delegateEvent.js';
13
7
 
14
8
  import { Cache } from './modules/Cache.js';
9
+ import { Classes } from './modules/Classes.js';
15
10
  import { Context, createContext } from './modules/Context.js';
16
- import { enterPage } from './modules/enterPage.js';
11
+ import { Hooks } from './modules/Hooks.js';
17
12
  import { getAnchorElement } from './modules/getAnchorElement.js';
18
- import { getAnimationPromises } from './modules/getAnimationPromises.js';
13
+ import { awaitAnimations } from './modules/awaitAnimations.js';
14
+ import { visit, performVisit, HistoryAction } from './modules/visit.js';
19
15
  import { fetchPage } from './modules/fetchPage.js';
20
16
  import { leavePage } from './modules/leavePage.js';
21
- import { HistoryAction, loadPage, performPageLoad } from './modules/loadPage.js';
22
17
  import { replaceContent } from './modules/replaceContent.js';
23
- import { Handler, HookName, Hooks } from './modules/Hooks.js';
24
- import { use, unuse, findPlugin, Plugin } from './modules/plugins.js';
18
+ import { enterPage } from './modules/enterPage.js';
25
19
  import { renderPage } from './modules/renderPage.js';
26
-
27
- export type Transition = {
28
- from?: string;
29
- to?: string;
30
- custom?: string;
31
- };
32
-
33
- type DelegatedListeners = {
34
- click?: Unsubscribe;
35
- };
20
+ import { use, unuse, findPlugin, Plugin } from './modules/plugins.js';
21
+ import { nextTick } from './utils.js';
36
22
 
37
23
  export type Options = {
24
+ /** Whether history visits are animated. Default: `false` */
38
25
  animateHistoryBrowsing: boolean;
26
+ /** Selector for detecting animation timing. Default: `[class*="transition-"]` */
39
27
  animationSelector: string | false;
40
- linkSelector: string;
28
+ /** Elements on which to add animation classes. Default: `html` element */
29
+ animationScope: 'html' | 'containers';
30
+ /** Enable in-memory page cache. Default: `true` */
41
31
  cache: boolean;
32
+ /** Content containers to be replaced on page visits. Default: `['#swup']` */
42
33
  containers: string[];
43
- requestHeaders: Record<string, string>;
44
- plugins: Plugin[];
45
- skipPopStateHandling: (event: any) => boolean;
34
+ /** Callback for ignoring visits. Receives the element and event that triggered the visit. */
46
35
  ignoreVisit: (url: string, { el, event }: { el?: Element; event?: Event }) => boolean;
36
+ /** Selector for links that trigger visits. Default: `'a[href]'` */
37
+ linkSelector: string;
38
+ /** Plugins to register on startup. */
39
+ plugins: Plugin[];
40
+ /** Custom headers sent along with fetch requests. */
41
+ requestHeaders: Record<string, string>;
42
+ /** Rewrite URLs before loading them. */
47
43
  resolveUrl: (url: string) => string;
44
+ /** Callback for telling swup to ignore certain popstate events. */
45
+ skipPopStateHandling: (event: any) => boolean;
48
46
  };
49
47
 
50
48
  export default class Swup {
49
+ /** Library version */
51
50
  version: string = version;
52
- // variable for save options
51
+ /** Options passed into the instance */
53
52
  options: Options;
54
- // running plugin instances
53
+ /** Registered plugin instances */
55
54
  plugins: Plugin[] = [];
56
- // context data
55
+ /** Global context of the current visit */
57
56
  context: Context;
58
- // cache instance
57
+ /** Cache instance */
59
58
  cache: Cache;
60
- // hook registry
59
+ /** Hook registry */
61
60
  hooks: Hooks;
62
- // variable for keeping event listeners from "delegate"
63
- delegatedListeners: DelegatedListeners = {};
64
- // allows us to compare the current and new path inside popStateHandler
61
+ /** Animation class manager */
62
+ classes: Classes;
63
+ /** URL of the currently visible page */
65
64
  currentPageUrl = getCurrentUrl();
65
+ /** Index of the current history entry */
66
+ currentHistoryIndex = 1;
67
+ /** Delegated event subscription handle */
68
+ private clickDelegate?: DelegateEventUnsubscribe;
66
69
 
67
- loadPage = loadPage;
68
- performPageLoad = performPageLoad;
70
+ visit = visit;
71
+ performVisit = performVisit;
69
72
  leavePage = leavePage;
70
73
  renderPage = renderPage;
71
74
  replaceContent = replaceContent;
72
75
  enterPage = enterPage;
73
76
  delegateEvent = delegateEvent;
74
- getAnimationPromises = getAnimationPromises;
75
77
  fetchPage = fetchPage;
78
+ awaitAnimations = awaitAnimations;
76
79
  getAnchorElement = getAnchorElement;
77
- log: (message: string, context?: any) => void = () => {}; // here so it can be used by plugins
78
80
  use = use;
79
81
  unuse = unuse;
80
82
  findPlugin = findPlugin;
81
83
  getCurrentUrl = getCurrentUrl;
82
- cleanupAnimationClasses = cleanupAnimationClasses;
83
84
  createContext = createContext;
85
+ log: (message: string, context?: any) => void = () => {}; // here so it can be used by plugins
84
86
 
87
+ /** Default options before merging user options */
85
88
  defaults: Options = {
86
89
  animateHistoryBrowsing: false,
87
90
  animationSelector: '[class*="transition-"]',
91
+ animationScope: 'html',
88
92
  cache: true,
89
93
  containers: ['#swup'],
90
94
  ignoreVisit: (url, { el, event } = {}) => !!el?.closest('[data-no-swup]'),
@@ -93,7 +97,7 @@ export default class Swup {
93
97
  resolveUrl: (url) => url,
94
98
  requestHeaders: {
95
99
  'X-Requested-With': 'swup',
96
- Accept: 'text/html, application/xhtml+xml'
100
+ 'Accept': 'text/html, application/xhtml+xml'
97
101
  },
98
102
  skipPopStateHandling: (event) => event.state?.source !== 'swup'
99
103
  };
@@ -106,6 +110,7 @@ export default class Swup {
106
110
  this.popStateHandler = this.popStateHandler.bind(this);
107
111
 
108
112
  this.cache = new Cache(this);
113
+ this.classes = new Classes(this);
109
114
  this.hooks = new Hooks(this);
110
115
  this.context = this.createContext({ to: undefined });
111
116
 
@@ -125,9 +130,9 @@ export default class Swup {
125
130
  }
126
131
 
127
132
  async enable() {
128
- // Add event listeners
133
+ // Add event listener
129
134
  const { linkSelector } = this.options;
130
- this.delegatedListeners.click = delegateEvent(linkSelector, 'click', this.linkClickHandler);
135
+ this.clickDelegate = this.delegateEvent(linkSelector, 'click', this.linkClickHandler);
131
136
 
132
137
  window.addEventListener('popstate', this.popStateHandler);
133
138
 
@@ -141,21 +146,23 @@ export default class Swup {
141
146
  this.options.plugins.forEach((plugin) => this.use(plugin));
142
147
 
143
148
  // Modify initial history record
144
- updateHistoryRecord();
149
+ updateHistoryRecord(null, { index: 1 });
145
150
 
146
- // Trigger enabled event
147
- await this.hooks.trigger('enabled', undefined, () => {
151
+ // Trigger enable hook
152
+ await this.hooks.trigger('enable', undefined, () => {
148
153
  // Add swup-enabled class to html tag
149
154
  document.documentElement.classList.add('swup-enabled');
150
155
  });
151
156
 
152
- // Trigger page view event
153
- await this.hooks.trigger('pageView', { url: this.currentPageUrl, title: document.title });
157
+ await nextTick();
158
+
159
+ // Trigger page view hook
160
+ await this.hooks.trigger('page:view', { url: this.currentPageUrl, title: document.title });
154
161
  }
155
162
 
156
163
  async destroy() {
157
- // remove delegated listeners
158
- this.delegatedListeners.click!.destroy();
164
+ // remove delegated listener
165
+ this.clickDelegate!.destroy();
159
166
 
160
167
  // remove popstate listener
161
168
  window.removeEventListener('popstate', this.popStateHandler);
@@ -166,8 +173,8 @@ export default class Swup {
166
173
  // unmount plugins
167
174
  this.options.plugins.forEach((plugin) => this.unuse(plugin));
168
175
 
169
- // trigger disable event
170
- await this.hooks.trigger('disabled', undefined, () => {
176
+ // trigger disable hook
177
+ await this.hooks.trigger('disable', undefined, () => {
171
178
  // remove swup-enabled class from html tag
172
179
  document.documentElement.classList.remove('swup-enabled');
173
180
  });
@@ -202,8 +209,8 @@ export default class Swup {
202
209
  const el = event.delegateTarget as HTMLAnchorElement;
203
210
  const { href, url, hash } = Location.fromElement(el);
204
211
 
205
- // Get the transition name, if specified
206
- const transition = el.getAttribute('data-swup-transition') || undefined;
212
+ // Get the animation name, if specified
213
+ const animation = el.getAttribute('data-swup-animation') || undefined;
207
214
 
208
215
  // Get the history action, if specified
209
216
  let historyAction: HistoryAction | undefined;
@@ -220,7 +227,7 @@ export default class Swup {
220
227
  this.context = this.createContext({
221
228
  to: url,
222
229
  hash,
223
- transition,
230
+ animation,
224
231
  el,
225
232
  event,
226
233
  action: historyAction
@@ -228,7 +235,7 @@ export default class Swup {
228
235
 
229
236
  // Exit early if control key pressed
230
237
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
231
- this.hooks.trigger('openPageInNewTab', { href });
238
+ this.hooks.trigger('link:newtab', { href });
232
239
  return;
233
240
  }
234
241
 
@@ -237,8 +244,8 @@ export default class Swup {
237
244
  return;
238
245
  }
239
246
 
240
- this.hooks.triggerSync('clickLink', { el, event }, () => {
241
- const from = this.context.from?.url ?? '';
247
+ this.hooks.triggerSync('link:click', { el, event }, () => {
248
+ const from = this.context.from.url ?? '';
242
249
 
243
250
  event.preventDefault();
244
251
 
@@ -247,7 +254,7 @@ export default class Swup {
247
254
  if (hash) {
248
255
  updateHistoryRecord(url + hash);
249
256
  this.hooks.triggerSync(
250
- 'samePageWithHash',
257
+ 'link:anchor',
251
258
  { hash, options: { behavior: 'auto' } },
252
259
  (context, { hash, options }) => {
253
260
  const target = this.getAnchorElement(hash);
@@ -257,7 +264,8 @@ export default class Swup {
257
264
  }
258
265
  );
259
266
  } else {
260
- this.hooks.triggerSync('samePage', undefined, () => {
267
+ this.hooks.triggerSync('link:self', undefined, (context) => {
268
+ if (!context.scroll.reset) return;
261
269
  window.scroll({ top: 0, left: 0, behavior: 'auto' });
262
270
  });
263
271
  }
@@ -270,7 +278,7 @@ export default class Swup {
270
278
  }
271
279
 
272
280
  // Finally, proceed with loading the page
273
- this.performPageLoad(url, { transition, history: historyAction });
281
+ this.performVisit(url);
274
282
  });
275
283
  }
276
284
 
@@ -302,22 +310,32 @@ export default class Swup {
302
310
  const { url, hash } = Location.fromUrl(href);
303
311
  const animate = this.options.animateHistoryBrowsing;
304
312
  const resetScroll = this.options.animateHistoryBrowsing;
313
+
305
314
  this.context = this.createContext({
306
315
  to: url,
307
316
  hash,
308
317
  event,
309
318
  animate,
310
- resetScroll,
311
- popstate: true
319
+ resetScroll
312
320
  });
313
321
 
322
+ // Mark as popstate visit
323
+ this.context.history.popstate = true;
324
+
325
+ // Determine direction of history visit
326
+ const index = Number(event.state?.index);
327
+ if (index) {
328
+ const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards';
329
+ this.context.history.direction = direction;
330
+ }
331
+
314
332
  // Does this even do anything?
315
333
  // if (!hash) {
316
334
  // event.preventDefault();
317
335
  // }
318
336
 
319
- this.hooks.triggerSync('popState', { event }, () => {
320
- this.performPageLoad(url);
337
+ this.hooks.triggerSync('history:popstate', { event }, () => {
338
+ this.performVisit(url);
321
339
  });
322
340
  }
323
341
 
@@ -38,7 +38,7 @@ describe('Exports', () => {
38
38
  resolveUrl: (url) => url,
39
39
  requestHeaders: {
40
40
  'X-Requested-With': 'swup',
41
- Accept: 'text/html, application/xhtml+xml'
41
+ 'Accept': 'text/html, application/xhtml+xml'
42
42
  },
43
43
  skipPopStateHandling: (event) => event.state?.source !== 'swup'
44
44
  };
@@ -84,10 +84,10 @@ describe('ignoreVisit', () => {
84
84
  );
85
85
  });
86
86
 
87
- it('should be called from loadPage method', () => {
87
+ it('should be called from visit method', () => {
88
88
  const ignoreVisit = vi.fn(() => true);
89
89
  const swup = new Swup({ ignoreVisit });
90
- swup.loadPage('/path/');
90
+ swup.visit('/path/');
91
91
 
92
92
  expect(ignoreVisit.mock.calls).toHaveLength(1);
93
93
  });
@@ -19,7 +19,7 @@ export class Location extends URL {
19
19
  /**
20
20
  * Instantiate a Location from an element's href attribute
21
21
  * @param el
22
- * @return new Location instance
22
+ * @returns new Location instance
23
23
  */
24
24
  static fromElement(el: Element): Location {
25
25
  const href = el.getAttribute('href') || el.getAttribute('xlink:href') || '';
@@ -29,7 +29,7 @@ export class Location extends URL {
29
29
  /**
30
30
  * Instantiate a Location from a URL object or string
31
31
  * @param url
32
- * @return new Location instance
32
+ * @returns new Location instance
33
33
  */
34
34
  static fromUrl(url: URL | string): Location {
35
35
  return new Location(url);
@@ -1,7 +1,7 @@
1
1
  import delegate, { DelegateEventHandler, DelegateOptions, EventType } from 'delegate-it';
2
2
  import { ParseSelector } from 'typed-query-selector/parser.js';
3
3
 
4
- export type Unsubscribe = {
4
+ export type DelegateEventUnsubscribe = {
5
5
  destroy: () => void;
6
6
  };
7
7
 
@@ -10,7 +10,7 @@ export const delegateEvent = <Selector extends string, TEvent extends EventType>
10
10
  type: TEvent,
11
11
  callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEvent]>,
12
12
  options?: DelegateOptions
13
- ): Unsubscribe => {
13
+ ): DelegateEventUnsubscribe => {
14
14
  const controller = new AbortController();
15
15
  options = { ...options, signal: controller.signal };
16
16
  delegate<string, ParseSelector<Selector, HTMLElement>, TEvent>(
package/src/helpers.ts CHANGED
@@ -7,5 +7,4 @@ export { updateHistoryRecord } from './helpers/updateHistoryRecord.js';
7
7
  export { delegateEvent } from './helpers/delegateEvent.js';
8
8
  export { getCurrentUrl } from './helpers/getCurrentUrl.js';
9
9
  export { Location } from './helpers/Location.js';
10
- export { cleanupAnimationClasses } from './helpers/cleanupAnimationClasses.js';
11
10
  export { matchPath } from './helpers/matchPath.js';
package/src/index.ts CHANGED
@@ -1,13 +1,43 @@
1
+ import type { Path } from 'path-to-regexp';
2
+
1
3
  import Swup, { type Options } from './Swup.js';
2
4
  import type { CacheData } from './modules/Cache.js';
3
- import type { Context, PageContext } from './modules/Context.js';
5
+ import type {
6
+ Context,
7
+ FromContext,
8
+ ToContext,
9
+ AnimationContext,
10
+ ScrollContext,
11
+ HistoryContext
12
+ } from './modules/Context.js';
13
+ import type {
14
+ HookDefinitions,
15
+ HookName,
16
+ HookOptions,
17
+ HookUnregister,
18
+ Handler
19
+ } from './modules/Hooks.js';
4
20
  import type { Plugin } from './modules/plugins.js';
5
- import type { HookDefinitions, Handler } from './modules/Hooks.js';
6
- import type { Path } from 'path-to-regexp';
7
21
 
8
22
  export default Swup;
9
23
 
10
24
  export * from './helpers.js';
11
25
  export * from './utils.js';
12
26
 
13
- export type { Options, Plugin, CacheData, Context, PageContext, HookDefinitions, Handler, Path };
27
+ export type {
28
+ Options,
29
+ Plugin,
30
+ CacheData,
31
+ Context,
32
+ FromContext,
33
+ ToContext,
34
+ AnimationContext,
35
+ ScrollContext,
36
+ HistoryContext,
37
+ HookDefinitions,
38
+ HookName,
39
+ HookOptions,
40
+ HookUnregister,
41
+ Handler,
42
+ Path
43
+ };
@@ -32,7 +32,7 @@ export class Cache {
32
32
  url = this.resolve(url);
33
33
  page = { ...page, url };
34
34
  this.pages.set(url, page);
35
- this.swup.hooks.triggerSync('pageCached', { page });
35
+ this.swup.hooks.triggerSync('cache:set', { page });
36
36
  }
37
37
 
38
38
  public update(url: string, page: CacheData) {
@@ -47,7 +47,7 @@ export class Cache {
47
47
 
48
48
  public clear(): void {
49
49
  this.pages.clear();
50
- this.swup.hooks.triggerSync('cacheCleared');
50
+ this.swup.hooks.triggerSync('cache:clear');
51
51
  }
52
52
 
53
53
  public prune(predicate: (url: string, page: CacheData) => boolean): void {
@@ -0,0 +1,48 @@
1
+ import Swup from '../Swup.js';
2
+ import { queryAll } from '../utils.js';
3
+
4
+ export class Classes {
5
+ public swup: Swup;
6
+
7
+ swupClasses = ['to-', 'is-changing', 'is-rendering', 'is-popstate', 'is-animating'];
8
+
9
+ constructor(swup: Swup) {
10
+ this.swup = swup;
11
+ }
12
+
13
+ get selectors(): string[] {
14
+ const { scope } = this.swup.context.animation;
15
+ if (scope === 'containers') return this.swup.context.containers;
16
+ if (scope === 'html') return ['html'];
17
+ if (Array.isArray(scope)) return scope;
18
+ return [];
19
+ }
20
+
21
+ get selector(): string {
22
+ return this.selectors.join(',');
23
+ }
24
+
25
+ get targets(): HTMLElement[] {
26
+ if (!this.selector.trim()) return [];
27
+ return queryAll(this.selector) as HTMLElement[];
28
+ }
29
+
30
+ public add(...classes: string[]): void {
31
+ this.targets.forEach((target) => target.classList.add(...classes));
32
+ }
33
+
34
+ public remove(...classes: string[]): void {
35
+ this.targets.forEach((target) => target.classList.remove(...classes));
36
+ }
37
+
38
+ public clear(): void {
39
+ this.targets.forEach((target) => {
40
+ const remove = target.className.split(' ').filter((c) => this.isSwupClass(c));
41
+ target.classList.remove(...remove);
42
+ });
43
+ }
44
+
45
+ private isSwupClass(className: string): boolean {
46
+ return this.swupClasses.some((c) => className.startsWith(c));
47
+ }
48
+ }
@@ -1,51 +1,80 @@
1
1
  import Swup, { Options } from '../Swup.js';
2
- import { HistoryAction } from './loadPage.js';
2
+ import { HistoryAction, HistoryDirection } from './visit.js';
3
3
 
4
4
  export interface Context<TEvent = Event> {
5
- from: PageContext;
6
- to?: PageContext;
5
+ /** The previous page, about to leave */
6
+ from: FromContext;
7
+ /** The next page, about to enter */
8
+ to: ToContext;
9
+ /** The content containers, about to be replaced */
7
10
  containers: Options['containers'];
8
- transition: TransitionContext;
11
+ /** Information about animated page transitions */
12
+ animation: AnimationContext;
13
+ /** What triggered this visit */
9
14
  trigger: TriggerContext<TEvent>;
15
+ /** Browser history behavior on this visit */
10
16
  history: HistoryContext;
17
+ /** Scroll behavior on this visit */
11
18
  scroll: ScrollContext;
12
19
  }
13
20
 
14
- export interface PageContext {
21
+ export interface FromContext {
22
+ /** The URL of the previous page */
15
23
  url: string;
16
24
  }
17
25
 
18
- export interface TransitionContext {
26
+ export interface ToContext {
27
+ /** The URL of the next page */
28
+ url?: string;
29
+ /** The HTML content of the next page */
30
+ html?: string;
31
+ }
32
+
33
+ export interface AnimationContext {
34
+ /** Whether this visit is animated. Default: `true` */
19
35
  animate: boolean;
36
+ /** Whether to wait for the next page to load before starting the animation. Default: `false` */
37
+ wait: boolean;
38
+ /** Name of a custom animation to run. */
20
39
  name?: string;
40
+ /** Elements on which to add animation classes. Default: `html` element */
41
+ scope: 'html' | 'containers' | string[];
42
+ /** Selector for detecting animation timing. Default: `[class*="transition-"]` */
43
+ selector: Options['animationSelector'];
21
44
  }
22
45
 
23
46
  export interface ScrollContext {
47
+ /** Whether to reset the scroll position after the visit. Default: `true` */
24
48
  reset: boolean;
49
+ /** Anchor element to scroll to on the next page. */
25
50
  target?: string;
26
51
  }
27
52
 
28
53
  export interface TriggerContext<TEvent = Event> {
54
+ /** DOM element that triggered this visit. */
29
55
  el?: Element;
56
+ /** DOM event that triggered this visit. */
30
57
  event?: TEvent;
31
58
  }
32
59
 
33
60
  export interface HistoryContext {
61
+ /** History action to perform: `push` for creating a new history entry, `replace` for replacing the current entry. Default: `push` */
34
62
  action: HistoryAction;
63
+ /** Whether this visit was triggered by a browser history navigation. */
35
64
  popstate: boolean;
36
- // direction: 'forward' | 'backward' | undefined
65
+ /** The direction of travel in case of a browser history navigation: backward or forward. */
66
+ direction: HistoryDirection | undefined;
37
67
  }
38
68
 
39
69
  export interface ContextInitOptions {
40
70
  to: string | undefined;
41
71
  from?: string;
42
72
  hash?: string;
43
- containers?: Options['containers'];
44
73
  animate?: boolean;
45
- transition?: string;
74
+ animation?: string;
75
+ targets?: string[];
46
76
  el?: Element;
47
77
  event?: Event;
48
- popstate?: boolean;
49
78
  action?: HistoryAction;
50
79
  resetScroll?: boolean;
51
80
  }
@@ -56,23 +85,24 @@ export function createContext(
56
85
  to,
57
86
  from = this.currentPageUrl,
58
87
  hash: target,
59
- containers = this.options.containers,
60
88
  animate = true,
61
- transition,
89
+ animation: name,
62
90
  el,
63
91
  event,
64
- popstate = false,
65
92
  action = 'push',
66
93
  resetScroll: reset = true
67
94
  }: ContextInitOptions
68
95
  ): Context {
69
96
  return {
70
97
  from: { url: from },
71
- to: to !== undefined ? { url: to } : undefined,
72
- containers,
73
- transition: {
98
+ to: { url: to },
99
+ containers: this.options.containers,
100
+ animation: {
74
101
  animate,
75
- name: transition
102
+ wait: false,
103
+ name,
104
+ scope: this.options.animationScope,
105
+ selector: this.options.animationSelector
76
106
  },
77
107
  trigger: {
78
108
  el,
@@ -80,8 +110,8 @@ export function createContext(
80
110
  },
81
111
  history: {
82
112
  action,
83
- popstate
84
- // direction: undefined
113
+ popstate: false,
114
+ direction: undefined
85
115
  },
86
116
  scroll: {
87
117
  reset,