swup 3.1.0 → 4.0.0-rc.14

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 (71) 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 +20 -20
  10. package/dist/types/helpers/delegateEvent.d.ts +4 -2
  11. package/dist/types/helpers/fetch.d.ts +2 -2
  12. package/dist/types/helpers.d.ts +10 -10
  13. package/dist/types/index.d.ts +5 -5
  14. package/dist/types/modules/Cache.d.ts +2 -2
  15. package/dist/types/modules/__test__/fetchPage.test.d.ts +1 -0
  16. package/dist/types/modules/destroy.d.ts +2 -0
  17. package/dist/types/modules/enable.d.ts +2 -0
  18. package/dist/types/modules/enterPage.d.ts +2 -2
  19. package/dist/types/modules/events.d.ts +6 -6
  20. package/dist/types/modules/fetchPage.d.ts +3 -3
  21. package/dist/types/modules/getAnchorElement.d.ts +0 -7
  22. package/dist/types/modules/getAnimationPromises.d.ts +1 -1
  23. package/dist/types/modules/getPageData.d.ts +2 -2
  24. package/dist/types/modules/handleLinkToSamePage.d.ts +2 -0
  25. package/dist/types/modules/isSameResolvedUrl.d.ts +8 -0
  26. package/dist/types/modules/leavePage.d.ts +2 -2
  27. package/dist/types/modules/linkClickHandler.d.ts +3 -0
  28. package/dist/types/modules/loadPage.d.ts +2 -5
  29. package/dist/types/modules/off.d.ts +3 -0
  30. package/dist/types/modules/on.d.ts +5 -0
  31. package/dist/types/modules/plugins.d.ts +1 -1
  32. package/dist/types/modules/popStateHandler.d.ts +2 -0
  33. package/dist/types/modules/renderPage.d.ts +2 -2
  34. package/dist/types/modules/resolveUrl.d.ts +7 -0
  35. package/dist/types/modules/shouldIgnoreVisit.d.ts +4 -0
  36. package/dist/types/modules/transitions.d.ts +1 -1
  37. package/dist/types/modules/triggerEvent.d.ts +3 -0
  38. package/dist/types/modules/triggerWillOpenNewWindow.d.ts +2 -0
  39. package/dist/types/modules/updateTransition.d.ts +2 -0
  40. package/dist/types/utils.d.ts +1 -1
  41. package/package.json +8 -5
  42. package/readme.md +30 -12
  43. package/src/Swup.ts +115 -143
  44. package/src/__test__/index.test.ts +6 -1
  45. package/src/helpers/Location.ts +10 -7
  46. package/src/helpers/__test__/matchPath.test.ts +54 -0
  47. package/src/helpers/delegateEvent.ts +1 -0
  48. package/src/helpers/matchPath.ts +22 -0
  49. package/src/helpers.ts +2 -4
  50. package/src/index.ts +7 -4
  51. package/src/modules/Cache.ts +43 -33
  52. package/src/modules/Context.ts +91 -0
  53. package/src/modules/Hooks.ts +393 -0
  54. package/src/modules/__test__/cache.test.ts +142 -0
  55. package/src/modules/__test__/hooks.test.ts +192 -0
  56. package/src/modules/enterPage.ts +19 -17
  57. package/src/modules/fetchPage.ts +74 -29
  58. package/src/modules/getAnchorElement.ts +8 -4
  59. package/src/modules/getAnimationPromises.ts +66 -75
  60. package/src/modules/leavePage.ts +22 -20
  61. package/src/modules/loadPage.ts +72 -54
  62. package/src/modules/renderPage.ts +41 -32
  63. package/src/modules/replaceContent.ts +26 -17
  64. package/src/utils/index.ts +24 -3
  65. package/src/helpers/fetch.ts +0 -33
  66. package/src/helpers/getDataFromHtml.ts +0 -39
  67. package/src/helpers/markSwupElements.ts +0 -16
  68. package/src/modules/__test__/events.test.ts +0 -72
  69. package/src/modules/events.ts +0 -92
  70. package/src/modules/getPageData.ts +0 -24
  71. package/src/modules/transitions.ts +0 -10
@@ -0,0 +1,3 @@
1
+ import Swup from '../Swup';
2
+ import { EventType, Handler } from './on';
3
+ export declare const off: (this: Swup, event?: EventType, handler?: Handler) => void;
@@ -0,0 +1,5 @@
1
+ import Swup from '../Swup';
2
+ export type EventType = 'animationInDone' | 'animationInStart' | 'animationOutDone' | 'animationOutStart' | 'animationSkipped' | 'clickLink' | 'contentReplaced' | 'disabled' | 'enabled' | 'openPageInNewTab' | 'pageLoaded' | 'pageRetrievedFromCache' | 'pageView' | 'popState' | 'samePage' | 'samePageWithHash' | 'serverError' | 'transitionStart' | 'transitionEnd' | 'willReplaceContent';
3
+ export type Handler = (event?: Event) => void;
4
+ export type Handlers = Record<EventType, Handler[]>;
5
+ export declare const on: (this: Swup, event: EventType, handler: Handler) => void;
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import Swup from '../Swup';
2
2
  export type Plugin = {
3
3
  name: string;
4
4
  isSwupPlugin: true;
@@ -0,0 +1,2 @@
1
+ import { InternalSwup } from '../Swup';
2
+ export declare const popStateHandler: (this: InternalSwup, event: PopStateEvent) => void;
@@ -1,5 +1,5 @@
1
- import Swup from '../Swup.js';
2
- import { PageRecord } from './Cache.js';
1
+ import Swup from '../Swup';
2
+ import { PageRecord } from './Cache';
3
3
  export type PageRenderOptions = {
4
4
  event?: PopStateEvent;
5
5
  skipTransition?: boolean;
@@ -0,0 +1,7 @@
1
+ import { InternalSwup } from '../Swup';
2
+ /**
3
+ * Utility function to validate and run the global option 'resolveUrl'
4
+ * @param {string} url
5
+ * @returns {string} the resolved url
6
+ */
7
+ export declare const resolveUrl: (this: InternalSwup, url: string) => string;
@@ -0,0 +1,4 @@
1
+ import { InternalSwup } from '../Swup';
2
+ export declare const shouldIgnoreVisit: (this: InternalSwup, href: string, { el }?: {
3
+ el?: Element | undefined;
4
+ }) => boolean;
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import Swup from '../Swup';
2
2
  export declare function updateTransition(this: Swup, from: string, to: string, custom?: string): void;
3
3
  export declare function shouldSkipTransition(this: Swup, { event }: {
4
4
  url?: string;
@@ -0,0 +1,3 @@
1
+ import { EventType } from './on';
2
+ import Swup from '../Swup';
3
+ export declare const triggerEvent: (this: Swup, eventName: EventType, originalEvent?: PopStateEvent | MouseEvent) => void;
@@ -0,0 +1,2 @@
1
+ import { InternalSwup } from '../Swup';
2
+ export declare const triggerWillOpenNewWindow: (this: InternalSwup, triggerEl: Element) => boolean;
@@ -0,0 +1,2 @@
1
+ import Swup from '../Swup';
2
+ export declare const updateTransition: (this: Swup, from: string, to: string, custom?: string) => void;
@@ -1 +1 @@
1
- export * from './utils/index.js';
1
+ export * from './utils/index';
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "swup",
3
3
  "amdName": "Swup",
4
- "version": "3.1.0",
5
- "description": "Complete, flexible, extensible, and easy-to-use page transition library for your server-side rendered website.",
4
+ "version": "4.0.0-rc.14",
5
+ "description": "Versatile and extensible page transition library for server-side rendered websites",
6
6
  "type": "module",
7
7
  "source": "./src/Swup.ts",
8
8
  "main": "./dist/Swup.cjs",
@@ -34,6 +34,7 @@
34
34
  "test": "npm run test:unit && npm run test:e2e",
35
35
  "test:ci": "npm run test:unit && npm run test:e2e:ci",
36
36
  "test:unit": "vitest run --config ./vitest/vitest.config.ts",
37
+ "test:unit:watch": "vitest --config ./vitest/vitest.config.ts",
37
38
  "test:e2e": "start-server-and-test test:e2e:start 8274 cy:run",
38
39
  "test:e2e:ci": "start-server-and-test test:e2e:start 8274 cy:run:record",
39
40
  "test:e2e:dev": "start-server-and-test test:e2e:start 8274 cy:open",
@@ -59,7 +60,8 @@
59
60
  ],
60
61
  "dependencies": {
61
62
  "delegate-it": "^6.0.0",
62
- "opencollective-postinstall": "^2.0.2"
63
+ "opencollective-postinstall": "^2.0.2",
64
+ "path-to-regexp": "^6.2.1"
63
65
  },
64
66
  "devDependencies": {
65
67
  "@babel/preset-typescript": "^7.18.6",
@@ -70,11 +72,12 @@
70
72
  "http-server": "^14.1.1",
71
73
  "husky": "^8.0.0",
72
74
  "istanbul-lib-coverage": "^3.2.0",
75
+ "jsdom": "^22.1.0",
73
76
  "microbundle": "^0.15.0",
74
77
  "nyc": "^15.1.0",
75
78
  "prettier": "^2.8.2",
76
- "start-server-and-test": "^1.14.0",
77
- "vitest": "^0.29.8"
79
+ "start-server-and-test": "^2.0.0",
80
+ "vitest": "^0.31.2"
78
81
  },
79
82
  "collective": {
80
83
  "type": "opencollective",
package/readme.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  # Swup
18
18
 
19
- Complete, flexible, extensible, and easy-to-use page transition library for your server-side rendered website.
19
+ Versatile and extensible page transition library for server-side rendered websites
20
20
 
21
21
  [Features](#features) •
22
22
  [Documentation](https://swup.js.org/getting-started) •
@@ -26,21 +26,39 @@ Complete, flexible, extensible, and easy-to-use page transition library for your
26
26
 
27
27
  ## Overview
28
28
 
29
- Swup is a library that helps you add page transitions to server-side rendered websites. It handles
30
- the complete lifecycle of a page visit by intercepting link clicks, loading the new page in the
31
- background, replacing the content and transitioning between the old and the new page.
29
+ Swup adds page transitions to server-side rendered websites. It manages the complete lifecycle of a
30
+ page visit by intercepting link clicks, loading the new page in the background and smoothly
31
+ transitioning between the old and new content.
32
32
 
33
- Its goal is to make adding transitions to a site as simple as possible, while providing lots of
34
- other quality-of-life improvements.
33
+ Its goal is to make it effortless to add page transitions to a site, while providing lots of other
34
+ quality-of-life improvements.
35
35
 
36
36
  ## Features
37
37
 
38
- - Auto-detects [CSS transitions](https://swup.js.org/getting-started/how-it-works) for perfect timing
39
- - 🔗 Updates URLs and preserves native browser history behavior
40
- - 📦 Uses a [cache](https://swup.js.org/api/cache) to speed up subsequent page loads
41
- - 💡 Offers [events](https://swup.js.org/events) for hooking into the lifecycle
42
- - 🔌 Has a powerful [plugin system](https://swup.js.org/plugins) and many official and third-party plugins
43
- - 🎨 Provides ready-to-go [themes](https://swup.js.org/themes) to get started quickly
38
+ - ✏️ Works out of the box with [minimal markup](https://swup.js.org/getting-started/example/)
39
+ - Auto-detects [CSS transitions](https://swup.js.org/getting-started/how-it-works/) for perfect timing
40
+ - 🔗 Updates URLs and preserves native [browser history](https://swup.js.org/options/#animatehistorybrowsing)
41
+ - 🏓 Manages scroll position between pages and anchor jump links
42
+ - 🚀 Uses a [cache](https://swup.js.org/api/cache/) to speed up subsequent page loads
43
+ - 📡 Offers [hooks](https://swup.js.org/hooks/) to customize and extend the page load lifecycle
44
+ - 🔌 Has a powerful [plugin system](https://swup.js.org/plugins/) and many official and third-party plugins
45
+ - 🎨 Provides ready-to-go [themes](https://swup.js.org/themes/) to get started quickly
46
+
47
+ ## Plugins
48
+
49
+ Swup is small by design. Extended features can be added via [plugins](https://swup.js.org/plugins/):
50
+
51
+ - Display a [progress bar](https://swup.js.org/plugins/progress-plugin/) while loading
52
+ - Enable [smooth scrolling](https://swup.js.org/plugins/scroll-plugin/) between visits
53
+ - Update [meta tags and stylesheets](https://swup.js.org/plugins/head-plugin/) after page loads
54
+ - Add support for [preloading pages](https://swup.js.org/plugins/preload-plugin/) in the background
55
+ - Improve [accessibility](https://swup.js.org/plugins/a11y-plugin/) for screen readers
56
+ - Perform your [animations in JS](https://swup.js.org/plugins/js-plugin/) instead of CSS transitions
57
+ - Animate [form submissions](https://swup.js.org/plugins/forms-plugin/)
58
+ - Get help in [debug mode](https://swup.js.org/plugins/debug-plugin/)
59
+
60
+ Check out the list of [all official plugins](https://swup.js.org/plugins/) as well as
61
+ [third-party integrations](https://swup.js.org/third-party-integrations/).
44
62
 
45
63
  ## Examples
46
64
 
package/src/Swup.ts CHANGED
@@ -7,26 +7,22 @@ import {
7
7
  delegateEvent,
8
8
  getCurrentUrl,
9
9
  Location,
10
- markSwupElements,
11
10
  updateHistoryRecord
12
11
  } from './helpers.js';
13
12
  import { Unsubscribe } from './helpers/delegateEvent.js';
14
13
 
15
14
  import { Cache } from './modules/Cache.js';
15
+ import { Context, createContext } from './modules/Context.js';
16
16
  import { enterPage } from './modules/enterPage.js';
17
17
  import { getAnchorElement } from './modules/getAnchorElement.js';
18
18
  import { getAnimationPromises } from './modules/getAnimationPromises.js';
19
- import { getPageData } from './modules/getPageData.js';
20
19
  import { fetchPage } from './modules/fetchPage.js';
21
20
  import { leavePage } from './modules/leavePage.js';
22
21
  import { HistoryAction, loadPage, performPageLoad } from './modules/loadPage.js';
23
22
  import { replaceContent } from './modules/replaceContent.js';
24
- import { on, off, triggerEvent, Handlers } from './modules/events.js';
23
+ import { Handler, HookName, Hooks } from './modules/Hooks.js';
25
24
  import { use, unuse, findPlugin, Plugin } from './modules/plugins.js';
26
25
  import { renderPage } from './modules/renderPage.js';
27
- import { updateTransition, shouldSkipTransition } from './modules/transitions.js';
28
-
29
- import { queryAll } from './utils.js';
30
26
 
31
27
  export type Transition = {
32
28
  from?: string;
@@ -52,47 +48,21 @@ export type Options = {
52
48
  };
53
49
 
54
50
  export default class Swup {
55
- version = version;
56
-
57
- _handlers: Handlers = {
58
- animationInDone: [],
59
- animationInStart: [],
60
- animationOutDone: [],
61
- animationOutStart: [],
62
- animationSkipped: [],
63
- clickLink: [],
64
- contentReplaced: [],
65
- disabled: [],
66
- enabled: [],
67
- openPageInNewTab: [],
68
- pageLoaded: [],
69
- pageRetrievedFromCache: [],
70
- pageView: [],
71
- popState: [],
72
- samePage: [],
73
- samePageWithHash: [],
74
- serverError: [],
75
- transitionStart: [],
76
- transitionEnd: [],
77
- willReplaceContent: []
78
- };
79
-
80
- // variable for anchor to scroll to after render
81
- scrollToElement: string | null = null;
51
+ version: string = version;
82
52
  // variable for save options
83
53
  options: Options;
84
54
  // running plugin instances
85
55
  plugins: Plugin[] = [];
86
- // variable for current transition info object
87
- transition: Transition = {};
56
+ // context data
57
+ context: Context;
88
58
  // cache instance
89
59
  cache: Cache;
90
- // allows us to compare the current and new path inside popStateHandler
91
- currentPageUrl = getCurrentUrl();
60
+ // hook registry
61
+ hooks: Hooks;
92
62
  // variable for keeping event listeners from "delegate"
93
63
  delegatedListeners: DelegatedListeners = {};
94
- // so we are able to remove the listener
95
- boundPopStateHandler: (event: PopStateEvent) => void;
64
+ // allows us to compare the current and new path inside popStateHandler
65
+ currentPageUrl = getCurrentUrl();
96
66
 
97
67
  loadPage = loadPage;
98
68
  performPageLoad = performPageLoad;
@@ -100,14 +70,8 @@ export default class Swup {
100
70
  renderPage = renderPage;
101
71
  replaceContent = replaceContent;
102
72
  enterPage = enterPage;
103
- triggerEvent = triggerEvent;
104
73
  delegateEvent = delegateEvent;
105
- on = on;
106
- off = off;
107
- updateTransition = updateTransition;
108
- shouldSkipTransition = shouldSkipTransition;
109
74
  getAnimationPromises = getAnimationPromises;
110
- getPageData = getPageData;
111
75
  fetchPage = fetchPage;
112
76
  getAnchorElement = getAnchorElement;
113
77
  log: (message: string, context?: any) => void = () => {}; // here so it can be used by plugins
@@ -116,6 +80,7 @@ export default class Swup {
116
80
  findPlugin = findPlugin;
117
81
  getCurrentUrl = getCurrentUrl;
118
82
  cleanupAnimationClasses = cleanupAnimationClasses;
83
+ createContext = createContext;
119
84
 
120
85
  defaults: Options = {
121
86
  animateHistoryBrowsing: false,
@@ -137,28 +102,34 @@ export default class Swup {
137
102
  // Merge defaults and options
138
103
  this.options = { ...this.defaults, ...options };
139
104
 
140
- this.boundPopStateHandler = this.popStateHandler.bind(this);
105
+ this.linkClickHandler = this.linkClickHandler.bind(this);
106
+ this.popStateHandler = this.popStateHandler.bind(this);
141
107
 
142
108
  this.cache = new Cache(this);
109
+ this.hooks = new Hooks(this);
110
+ this.context = this.createContext({ to: undefined });
111
+
112
+ if (!this.checkRequirements()) {
113
+ return;
114
+ }
143
115
 
144
116
  this.enable();
145
117
  }
146
118
 
147
- enable() {
148
- // Check for Promise support
119
+ checkRequirements() {
149
120
  if (typeof Promise === 'undefined') {
150
121
  console.warn('Promise is not supported');
151
- return;
122
+ return false;
152
123
  }
124
+ return true;
125
+ }
153
126
 
127
+ async enable() {
154
128
  // Add event listeners
155
- this.delegatedListeners.click = delegateEvent(
156
- this.options.linkSelector,
157
- 'click',
158
- this.linkClickHandler.bind(this)
159
- );
129
+ const { linkSelector } = this.options;
130
+ this.delegatedListeners.click = delegateEvent(linkSelector, 'click', this.linkClickHandler);
160
131
 
161
- window.addEventListener('popstate', this.boundPopStateHandler);
132
+ window.addEventListener('popstate', this.popStateHandler);
162
133
 
163
134
  // Initial save to cache
164
135
  if (this.options.cache) {
@@ -166,9 +137,6 @@ export default class Swup {
166
137
  // https://github.com/swup/swup/issues/475
167
138
  }
168
139
 
169
- // Mark swup blocks in html
170
- markSwupElements(document.documentElement, this.options.containers);
171
-
172
140
  // Mount plugins
173
141
  this.options.plugins.forEach((plugin) => this.use(plugin));
174
142
 
@@ -176,43 +144,36 @@ export default class Swup {
176
144
  updateHistoryRecord();
177
145
 
178
146
  // Trigger enabled event
179
- this.triggerEvent('enabled');
180
-
181
- // Add swup-enabled class to html tag
182
- document.documentElement.classList.add('swup-enabled');
147
+ await this.hooks.trigger('enabled', undefined, () => {
148
+ // Add swup-enabled class to html tag
149
+ document.documentElement.classList.add('swup-enabled');
150
+ });
183
151
 
184
152
  // Trigger page view event
185
- this.triggerEvent('pageView');
153
+ await this.hooks.trigger('pageView', { url: this.currentPageUrl, title: document.title });
186
154
  }
187
155
 
188
- destroy() {
156
+ async destroy() {
189
157
  // remove delegated listeners
190
158
  this.delegatedListeners.click!.destroy();
191
159
 
192
160
  // remove popstate listener
193
- window.removeEventListener('popstate', this.boundPopStateHandler);
161
+ window.removeEventListener('popstate', this.popStateHandler);
194
162
 
195
163
  // empty cache
196
- this.cache.empty();
164
+ this.cache.clear();
197
165
 
198
166
  // unmount plugins
199
- this.options.plugins.forEach((plugin) => {
200
- this.unuse(plugin);
201
- });
167
+ this.options.plugins.forEach((plugin) => this.unuse(plugin));
202
168
 
203
- // remove swup data atributes from blocks
204
- queryAll('[data-swup]').forEach((element) => {
205
- element.removeAttribute('data-swup');
169
+ // trigger disable event
170
+ await this.hooks.trigger('disabled', undefined, () => {
171
+ // remove swup-enabled class from html tag
172
+ document.documentElement.classList.remove('swup-enabled');
206
173
  });
207
174
 
208
175
  // remove handlers
209
- this.off();
210
-
211
- // trigger disable event
212
- this.triggerEvent('disabled');
213
-
214
- // remove swup-enabled class from html tag
215
- document.documentElement.classList.remove('swup-enabled');
176
+ this.hooks.clear();
216
177
  }
217
178
 
218
179
  shouldIgnoreVisit(href: string, { el, event }: { el?: Element; event?: Event } = {}) {
@@ -238,17 +199,36 @@ export default class Swup {
238
199
  }
239
200
 
240
201
  linkClickHandler(event: DelegateEvent<MouseEvent>) {
241
- const linkEl = event.delegateTarget;
242
- const { href, url, hash } = Location.fromElement(linkEl as HTMLAnchorElement);
202
+ const el = event.delegateTarget as HTMLAnchorElement;
203
+ const { href, url, hash } = Location.fromElement(el);
204
+
205
+ // Get the transition name, if specified
206
+ const transition = el.getAttribute('data-swup-transition') || undefined;
207
+
208
+ // Get the history action, if specified
209
+ let historyAction: HistoryAction | undefined;
210
+ const historyAttr = el.getAttribute('data-swup-history');
211
+ if (historyAttr && ['push', 'replace'].includes(historyAttr)) {
212
+ historyAction = historyAttr as HistoryAction;
213
+ }
243
214
 
244
215
  // Exit early if the link should be ignored
245
- if (this.shouldIgnoreVisit(href, { el: linkEl, event })) {
216
+ if (this.shouldIgnoreVisit(href, { el, event })) {
246
217
  return;
247
218
  }
248
219
 
220
+ this.context = this.createContext({
221
+ to: url,
222
+ hash,
223
+ transition,
224
+ el,
225
+ event,
226
+ action: historyAction
227
+ });
228
+
249
229
  // Exit early if control key pressed
250
230
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
251
- this.triggerEvent('openPageInNewTab', event);
231
+ this.hooks.trigger('openPageInNewTab', { href });
252
232
  return;
253
233
  }
254
234
 
@@ -257,53 +237,41 @@ export default class Swup {
257
237
  return;
258
238
  }
259
239
 
260
- this.triggerEvent('clickLink', event);
261
- event.preventDefault();
262
-
263
- // Handle links to the same page and exit early, where applicable
264
- if (!url || url === getCurrentUrl()) {
265
- this.handleLinkToSamePage(url, hash, event);
266
- return;
267
- }
240
+ this.hooks.triggerSync('clickLink', { el, event }, () => {
241
+ const from = this.context.from?.url ?? '';
268
242
 
269
- // Exit early if the resolved path hasn't changed
270
- if (this.isSameResolvedUrl(url, getCurrentUrl())) return;
271
-
272
- // Store the element that should be scrolled to after loading the next page
273
- this.scrollToElement = hash || null;
274
-
275
- // Get the custom transition name, if present
276
- const customTransition = linkEl.getAttribute('data-swup-transition') || undefined;
277
-
278
- // Get the history action, if set
279
- let history: HistoryAction | undefined;
280
- const historyAttr = linkEl.getAttribute('data-swup-history');
281
- if (historyAttr && ['push', 'replace'].includes(historyAttr)) {
282
- history = historyAttr as HistoryAction;
283
- }
284
-
285
- // Finally, proceed with loading the page
286
- this.performPageLoad({ url, customTransition, history });
287
- }
288
-
289
- handleLinkToSamePage(url: string, hash: string, event: DelegateEvent<MouseEvent>) {
290
- // Emit event and exit early if the url points to the same page without hash
291
- if (!hash) {
292
- this.triggerEvent('samePage', event);
293
- return;
294
- }
295
-
296
- // link to the same URL with hash
297
- this.triggerEvent('samePageWithHash', event);
298
-
299
- const element = getAnchorElement(hash);
300
-
301
- // Warn and exit early if no matching element was found for the hash
302
- if (!element) {
303
- return console.warn(`Element for offset not found (#${hash})`);
304
- }
243
+ event.preventDefault();
305
244
 
306
- updateHistoryRecord(url + hash);
245
+ // Handle links to the same page: with or without hash
246
+ if (!url || url === from) {
247
+ if (hash) {
248
+ updateHistoryRecord(url + hash);
249
+ this.hooks.triggerSync(
250
+ 'samePageWithHash',
251
+ { hash, options: { behavior: 'auto' } },
252
+ (context, { hash, options }) => {
253
+ const target = this.getAnchorElement(hash);
254
+ if (target) {
255
+ target.scrollIntoView(options);
256
+ }
257
+ }
258
+ );
259
+ } else {
260
+ this.hooks.triggerSync('samePage', undefined, () => {
261
+ window.scroll({ top: 0, left: 0, behavior: 'auto' });
262
+ });
263
+ }
264
+ return;
265
+ }
266
+
267
+ // Exit early if the resolved path hasn't changed
268
+ if (this.isSameResolvedUrl(url, from)) {
269
+ return;
270
+ }
271
+
272
+ // Finally, proceed with loading the page
273
+ this.performPageLoad(url, { transition, history: historyAction });
274
+ });
307
275
  }
308
276
 
309
277
  triggerWillOpenNewWindow(triggerEl: Element) {
@@ -314,6 +282,8 @@ export default class Swup {
314
282
  }
315
283
 
316
284
  popStateHandler(event: PopStateEvent) {
285
+ const href = event.state?.url ?? location.href;
286
+
317
287
  // Exit early if this event should be ignored
318
288
  if (this.options.skipPopStateHandling(event)) {
319
289
  return;
@@ -324,29 +294,31 @@ export default class Swup {
324
294
  return;
325
295
  }
326
296
 
327
- const href = event.state?.url ?? location.href;
328
-
329
297
  // Exit early if the link should be ignored
330
298
  if (this.shouldIgnoreVisit(href, { event })) {
331
299
  return;
332
300
  }
333
301
 
334
302
  const { url, hash } = Location.fromUrl(href);
303
+ const animate = this.options.animateHistoryBrowsing;
304
+ const resetScroll = this.options.animateHistoryBrowsing;
305
+ this.context = this.createContext({
306
+ to: url,
307
+ hash,
308
+ event,
309
+ animate,
310
+ resetScroll,
311
+ popstate: true
312
+ });
335
313
 
336
- if (hash) {
337
- this.scrollToElement = hash;
338
- } else {
339
- event.preventDefault();
340
- }
341
-
342
- this.triggerEvent('popState', event);
343
-
344
- if (!this.options.animateHistoryBrowsing) {
345
- document.documentElement.classList.remove('is-animating');
346
- cleanupAnimationClasses();
347
- }
314
+ // Does this even do anything?
315
+ // if (!hash) {
316
+ // event.preventDefault();
317
+ // }
348
318
 
349
- this.performPageLoad({ url, event });
319
+ this.hooks.triggerSync('popState', { event }, () => {
320
+ this.performPageLoad(url);
321
+ });
350
322
  }
351
323
 
352
324
  /**
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import pckg from '../../package.json';
5
5
  import Swup, { Options, Plugin } from '../index.js';
6
+ import * as SwupTS from '../Swup.js';
6
7
 
7
8
  const baseUrl = window.location.origin;
8
9
 
@@ -51,6 +52,10 @@ describe('Exports', () => {
51
52
  expect(swup.version).not.toBeUndefined();
52
53
  expect(swup.version).toEqual(pckg.version);
53
54
  });
55
+
56
+ it('UMD compatibility: Swup.ts should only have a default export', () => {
57
+ expect(Object.keys(SwupTS)).toEqual(['default']);
58
+ });
54
59
  });
55
60
 
56
61
  describe('ignoreVisit', () => {
@@ -82,7 +87,7 @@ describe('ignoreVisit', () => {
82
87
  it('should be called from loadPage method', () => {
83
88
  const ignoreVisit = vi.fn(() => true);
84
89
  const swup = new Swup({ ignoreVisit });
85
- swup.loadPage({ url: '/path/' });
90
+ swup.loadPage('/path/');
86
91
 
87
92
  expect(ignoreVisit.mock.calls).toHaveLength(1);
88
93
  });
@@ -5,30 +5,33 @@
5
5
  */
6
6
 
7
7
  export class Location extends URL {
8
- constructor(url: string, base: string = document.baseURI) {
8
+ constructor(url: URL | string, base: string = document.baseURI) {
9
9
  super(url.toString(), base);
10
10
  }
11
11
 
12
- get url() {
12
+ /**
13
+ * The full local path including query params.
14
+ */
15
+ get url(): string {
13
16
  return this.pathname + this.search;
14
17
  }
15
18
 
16
19
  /**
17
20
  * Instantiate a Location from an element's href attribute
18
- * @param {Element} el
21
+ * @param el
19
22
  * @return new Location instance
20
23
  */
21
- static fromElement(el: HTMLAnchorElement): Location {
22
- const href = el.getAttribute('href') || el.getAttribute('xlink:href');
24
+ static fromElement(el: Element): Location {
25
+ const href = el.getAttribute('href') || el.getAttribute('xlink:href') || '';
23
26
  return new Location(href!);
24
27
  }
25
28
 
26
29
  /**
27
30
  * Instantiate a Location from a URL object or string
28
- * @param {URL|string} url
31
+ * @param url
29
32
  * @return new Location instance
30
33
  */
31
- static fromUrl(url: string): Location {
34
+ static fromUrl(url: URL | string): Location {
32
35
  return new Location(url);
33
36
  }
34
37
  }