swup 2.0.19 → 3.0.0-rc.2

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 (117) hide show
  1. package/dist/Swup.cjs +2 -0
  2. package/dist/Swup.cjs.map +1 -0
  3. package/dist/Swup.modern.js +2 -0
  4. package/dist/Swup.modern.js.map +1 -0
  5. package/dist/Swup.module.js +2 -0
  6. package/dist/Swup.module.js.map +1 -0
  7. package/dist/Swup.umd.js +2 -0
  8. package/dist/Swup.umd.js.map +1 -0
  9. package/dist/helpers.cjs +2 -0
  10. package/dist/helpers.cjs.map +1 -0
  11. package/dist/helpers.modern.js +2 -0
  12. package/dist/helpers.modern.js.map +1 -0
  13. package/dist/helpers.module.js +2 -0
  14. package/dist/helpers.module.js.map +1 -0
  15. package/dist/types/Swup.d.ts +102 -0
  16. package/dist/types/config/version.d.ts +5 -0
  17. package/dist/types/helpers/Location.d.ts +31 -0
  18. package/dist/types/helpers/classify.d.ts +2 -0
  19. package/dist/types/helpers/cleanupAnimationClasses.d.ts +2 -0
  20. package/dist/types/helpers/createHistoryRecord.d.ts +2 -0
  21. package/dist/types/helpers/delegateEvent.d.ts +8 -0
  22. package/dist/types/helpers/fetch.d.ts +6 -0
  23. package/dist/types/helpers/getCurrentUrl.d.ts +4 -0
  24. package/dist/types/helpers/getDataFromHtml.d.ts +8 -0
  25. package/dist/types/helpers/index.d.ts +11 -0
  26. package/dist/types/helpers/markSwupElements.d.ts +2 -0
  27. package/dist/types/helpers/updateHistoryRecord.d.ts +2 -0
  28. package/dist/types/helpers/versionSatisfies.d.ts +12 -0
  29. package/dist/types/helpers.d.ts +1 -0
  30. package/dist/types/index.d.ts +8 -0
  31. package/dist/types/modules/Cache.d.ts +20 -0
  32. package/dist/types/modules/enterPage.d.ts +6 -0
  33. package/dist/types/modules/fetchPage.d.ts +4 -0
  34. package/dist/types/modules/getAnchorElement.d.ts +2 -0
  35. package/dist/types/modules/getAnimationPromises.d.ts +7 -0
  36. package/dist/types/modules/getPageData.d.ts +7 -0
  37. package/dist/types/modules/leavePage.d.ts +7 -0
  38. package/dist/types/modules/loadPage.d.ts +7 -0
  39. package/dist/types/modules/off.d.ts +4 -0
  40. package/dist/types/modules/on.d.ts +6 -0
  41. package/dist/types/modules/plugins.d.ts +14 -0
  42. package/dist/types/modules/renderPage.d.ts +7 -0
  43. package/dist/types/modules/replaceContent.d.ts +17 -0
  44. package/dist/types/modules/triggerEvent.d.ts +4 -0
  45. package/dist/types/modules/updateTransition.d.ts +3 -0
  46. package/dist/types/utils/index.d.ts +5 -0
  47. package/dist/types/utils.d.ts +1 -0
  48. package/dist/utils.cjs +2 -0
  49. package/dist/utils.cjs.map +1 -0
  50. package/dist/utils.modern.js +2 -0
  51. package/dist/utils.modern.js.map +1 -0
  52. package/dist/utils.module.js +2 -0
  53. package/dist/utils.module.js.map +1 -0
  54. package/package.json +30 -23
  55. package/readme.md +52 -36
  56. package/src/Swup.ts +369 -0
  57. package/src/config/version.ts +13 -0
  58. package/src/helpers/Location.ts +44 -0
  59. package/src/helpers/classify.ts +13 -0
  60. package/src/helpers/cleanupAnimationClasses.ts +10 -0
  61. package/src/helpers/createHistoryRecord.ts +14 -0
  62. package/src/helpers/delegateEvent.ts +23 -0
  63. package/src/helpers/fetch.ts +35 -0
  64. package/src/helpers/getCurrentUrl.ts +5 -0
  65. package/src/helpers/getDataFromHtml.ts +41 -0
  66. package/src/helpers/index.ts +11 -0
  67. package/src/helpers/markSwupElements.ts +18 -0
  68. package/src/helpers/updateHistoryRecord.ts +18 -0
  69. package/src/helpers/versionSatisfies.ts +46 -0
  70. package/src/helpers.ts +4 -0
  71. package/src/index.ts +8 -0
  72. package/src/modules/Cache.ts +57 -0
  73. package/src/modules/enterPage.ts +28 -0
  74. package/src/modules/fetchPage.ts +35 -0
  75. package/src/modules/getAnchorElement.ts +19 -0
  76. package/src/modules/getAnimationPromises.ts +179 -0
  77. package/src/modules/getPageData.ts +26 -0
  78. package/src/modules/leavePage.ts +33 -0
  79. package/src/modules/loadPage.ts +55 -0
  80. package/src/modules/off.ts +23 -0
  81. package/src/modules/on.ts +35 -0
  82. package/src/modules/plugins.ts +58 -0
  83. package/src/modules/renderPage.ts +52 -0
  84. package/src/modules/replaceContent.ts +28 -0
  85. package/src/modules/triggerEvent.ts +23 -0
  86. package/src/modules/updateTransition.ts +7 -0
  87. package/src/utils/index.ts +32 -0
  88. package/src/utils.ts +4 -0
  89. package/.editorconfig +0 -19
  90. package/cypress.config.js +0 -14
  91. package/dist/swup.js +0 -1524
  92. package/dist/swup.min.js +0 -1
  93. package/lib/helpers/Link.js +0 -56
  94. package/lib/helpers/classify.js +0 -18
  95. package/lib/helpers/cleanupAnimationClasses.js +0 -18
  96. package/lib/helpers/createHistoryRecord.js +0 -14
  97. package/lib/helpers/fetch.js +0 -41
  98. package/lib/helpers/getCurrentUrl.js +0 -10
  99. package/lib/helpers/getDataFromHtml.js +0 -43
  100. package/lib/helpers/index.js +0 -64
  101. package/lib/helpers/markSwupElements.js +0 -24
  102. package/lib/helpers/normalizeUrl.js +0 -17
  103. package/lib/helpers/transitionEnd.js +0 -14
  104. package/lib/helpers/transitionProperty.js +0 -14
  105. package/lib/index.js +0 -305
  106. package/lib/modules/Cache.js +0 -66
  107. package/lib/modules/getAnchorElement.js +0 -25
  108. package/lib/modules/getAnimationPromises.js +0 -43
  109. package/lib/modules/getPageData.js +0 -26
  110. package/lib/modules/loadPage.js +0 -123
  111. package/lib/modules/off.js +0 -34
  112. package/lib/modules/on.js +0 -14
  113. package/lib/modules/plugins.js +0 -54
  114. package/lib/modules/renderPage.js +0 -76
  115. package/lib/modules/triggerEvent.js +0 -21
  116. package/lib/modules/updateTransition.js +0 -15
  117. package/lib/utils/index.js +0 -32
@@ -0,0 +1,57 @@
1
+ import { getCurrentUrl, Location } from '../helpers.js';
2
+ import Swup from '../Swup.js';
3
+ import { PageData } from './getPageData.js';
4
+
5
+ export interface PageRecord extends PageData {
6
+ url: string;
7
+ responseURL: string;
8
+ }
9
+ export class Cache {
10
+ pages: Record<string, PageRecord> = {};
11
+ last: PageRecord | null = null;
12
+ swup: Swup;
13
+
14
+ constructor(swup: Swup) {
15
+ this.swup = swup;
16
+ }
17
+
18
+ getCacheUrl(url: string): string {
19
+ return this.swup.resolveUrl(Location.fromUrl(url).url);
20
+ }
21
+
22
+ cacheUrl(page: PageRecord) {
23
+ page.url = this.getCacheUrl(page.url);
24
+ if (page.url in this.pages === false) {
25
+ this.pages[page.url] = page;
26
+ }
27
+ page.responseURL = this.getCacheUrl(page.responseURL);
28
+ this.last = this.pages[page.url];
29
+ this.swup.log(`Cache (${Object.keys(this.pages).length})`, this.pages);
30
+ }
31
+
32
+ getPage(url: string): PageRecord {
33
+ url = this.getCacheUrl(url);
34
+ return this.pages[url];
35
+ }
36
+
37
+ getCurrentPage(): PageRecord {
38
+ return this.getPage(getCurrentUrl());
39
+ }
40
+
41
+ exists(url: string): boolean {
42
+ url = this.getCacheUrl(url);
43
+ return url in this.pages;
44
+ }
45
+
46
+ empty(): void {
47
+ this.pages = {};
48
+ this.last = null;
49
+ this.swup.log('Cache cleared');
50
+ }
51
+
52
+ remove(url: string): void {
53
+ delete this.pages[this.getCacheUrl(url)];
54
+ }
55
+ }
56
+
57
+ export default Cache;
@@ -0,0 +1,28 @@
1
+ import { nextTick } from '../utils.js';
2
+ import Swup from '../Swup.js';
3
+
4
+ const enterPage = function (
5
+ this: Swup,
6
+ { popstate, skipTransition }: { popstate?: PopStateEvent; skipTransition?: boolean }
7
+ ) {
8
+ if (skipTransition) {
9
+ this.triggerEvent('transitionEnd', popstate);
10
+ this.cleanupAnimationClasses();
11
+ return [Promise.resolve()];
12
+ }
13
+
14
+ nextTick(() => {
15
+ this.triggerEvent('animationInStart');
16
+ document.documentElement.classList.remove('is-animating');
17
+ });
18
+
19
+ const animationPromises = this.getAnimationPromises('in');
20
+ Promise.all(animationPromises).then(() => {
21
+ this.triggerEvent('animationInDone');
22
+ this.triggerEvent('transitionEnd', popstate);
23
+ this.cleanupAnimationClasses();
24
+ });
25
+ return animationPromises;
26
+ };
27
+
28
+ export default enterPage;
@@ -0,0 +1,35 @@
1
+ import Swup from '../Swup.js';
2
+ import { fetch } from '../helpers.js';
3
+ import { TransitionOptions } from './loadPage.js';
4
+ import { PageRecord } from './Cache.js';
5
+
6
+ export default function fetchPage(this: Swup, data: TransitionOptions): Promise<PageRecord> {
7
+ const headers = this.options.requestHeaders;
8
+ const { url } = data;
9
+
10
+ if (this.cache.exists(url)) {
11
+ this.triggerEvent('pageRetrievedFromCache');
12
+ return Promise.resolve(this.cache.getPage(url));
13
+ }
14
+
15
+ return new Promise((resolve, reject) => {
16
+ fetch({ ...data, headers }, (response) => {
17
+ if (response.status === 500) {
18
+ this.triggerEvent('serverError');
19
+ reject(url);
20
+ return;
21
+ }
22
+ // get json data
23
+ const page = this.getPageData(response);
24
+ if (!page || !page.blocks.length) {
25
+ reject(url);
26
+ return;
27
+ }
28
+ // render page
29
+ const cacheablePageData = { ...page, url };
30
+ this.cache.cacheUrl(cacheablePageData);
31
+ this.triggerEvent('pageLoaded');
32
+ resolve(cacheablePageData);
33
+ });
34
+ });
35
+ }
@@ -0,0 +1,19 @@
1
+ import { escapeCssIdentifier, query } from '../utils.js';
2
+
3
+ const getAnchorElement = (hash: string): Element | null => {
4
+ if (!hash) {
5
+ return null;
6
+ }
7
+
8
+ if (hash.charAt(0) === '#') {
9
+ hash = hash.substring(1);
10
+ }
11
+
12
+ hash = decodeURIComponent(hash);
13
+ hash = escapeCssIdentifier(hash);
14
+
15
+ // https://html.spec.whatwg.org/#find-a-potential-indicated-element
16
+ return query(`#${hash}`) || query(`a[name='${hash}']`);
17
+ };
18
+
19
+ export default getAnchorElement;
@@ -0,0 +1,179 @@
1
+ import { queryAll, toMs } from '../utils.js';
2
+ import Swup from '../Swup.js';
3
+
4
+ // Transition property/event sniffing
5
+ let transitionProp = 'transition';
6
+ let transitionEndEvent = 'transitionend';
7
+ let animationProp = 'animation';
8
+ let animationEndEvent = 'animationend';
9
+
10
+ if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
11
+ transitionProp = 'WebkitTransition';
12
+ transitionEndEvent = 'webkitTransitionEnd';
13
+ }
14
+
15
+ if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
16
+ animationProp = 'WebkitAnimation';
17
+ animationEndEvent = 'webkitAnimationEnd';
18
+ }
19
+
20
+ export default function getAnimationPromises(
21
+ this: Swup,
22
+ // we don't use this argument, but JS plugin depends on it with
23
+ // its own version of getAnimationPromises, so it must be specified when
24
+ // getAnimationPromises is being used
25
+ animationType: 'in' | 'out'
26
+ ): Promise<void>[] {
27
+ const selector = this.options.animationSelector;
28
+
29
+ // Allow usage of swup without animations
30
+ if (selector === false) {
31
+ // Use array of a single resolved promise instead of an empty array to allow
32
+ // possible future use with Promise.race() which requires an actual value
33
+ return [Promise.resolve()];
34
+ }
35
+
36
+ const animatedElements = queryAll(selector, document.body);
37
+
38
+ // Warn if no animated containers found on page, but keep things going
39
+ if (!animatedElements.length) {
40
+ console.warn(`[swup] No animated elements found by selector ${selector}`);
41
+ return [Promise.resolve()];
42
+ }
43
+
44
+ return animatedElements.map((element) => getAnimationPromiseForElement(element, selector));
45
+ }
46
+
47
+ const isTransitionOrAnimationEvent = (event: any): event is TransitionEvent | AnimationEvent =>
48
+ !!event.elapsedTime;
49
+
50
+ function getAnimationPromiseForElement(
51
+ element: Element,
52
+ selector: string,
53
+ expectedType: 'animation' | 'transition' | null = null
54
+ ): Promise<void> {
55
+ const { type, timeout, propCount } = getTransitionInfo(element, expectedType);
56
+
57
+ // Resolve immediately if no transition defined
58
+ if (!type || !timeout) {
59
+ console.warn(
60
+ `[swup] No CSS transition duration defined for element of selector ${selector}`
61
+ );
62
+ return Promise.resolve();
63
+ }
64
+
65
+ return new Promise((resolve) => {
66
+ const endEvent = type === 'transition' ? transitionEndEvent : animationEndEvent;
67
+ const startTime = performance.now();
68
+ let propsTransitioned = 0;
69
+
70
+ const end = () => {
71
+ element.removeEventListener(endEvent, onEnd);
72
+ resolve();
73
+ };
74
+
75
+ const onEnd: EventListener = (event) => {
76
+ // Skip transitions on child elements
77
+ if (event.target !== element) {
78
+ return;
79
+ }
80
+
81
+ if (!isTransitionOrAnimationEvent(event)) {
82
+ throw new Error('Not a transition or animation event.');
83
+ }
84
+
85
+ // Skip transitions that happened before we started listening
86
+ const elapsedTime = (performance.now() - startTime) / 1000;
87
+ if (elapsedTime < event.elapsedTime) {
88
+ return;
89
+ }
90
+
91
+ // End if all properties have transitioned
92
+ if (++propsTransitioned >= propCount) {
93
+ end();
94
+ }
95
+ };
96
+
97
+ setTimeout(() => {
98
+ if (propsTransitioned < propCount) {
99
+ end();
100
+ }
101
+ }, timeout + 1);
102
+
103
+ element.addEventListener(endEvent, onEnd);
104
+ });
105
+ }
106
+
107
+ export function getTransitionInfo(
108
+ element: Element,
109
+ expectedType: 'animation' | 'transition' | null = null
110
+ ) {
111
+ const styles = window.getComputedStyle(element);
112
+
113
+ // not sure what to do about the below mess other than casting, but it's a mess
114
+ const transitionDelay = `${transitionProp}Delay` as keyof CSSStyleDeclaration;
115
+ const transitionDuration = `${transitionProp}Duration` as keyof CSSStyleDeclaration;
116
+ const animationDelay = `${animationProp}Delay` as keyof CSSStyleDeclaration;
117
+ const animationDuration = `${animationProp}Duration` as keyof CSSStyleDeclaration;
118
+
119
+ const transitionDelays = (
120
+ styles[transitionDelay] as CSSStyleDeclaration['transitionDelay']
121
+ ).split(', ');
122
+ const transitionDurations = (
123
+ (styles[transitionDuration] || '') as CSSStyleDeclaration['transitionDuration']
124
+ ).split(', ');
125
+ const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations);
126
+
127
+ const animationDelays = (
128
+ (styles[animationDelay] || '') as CSSStyleDeclaration['animationDelay']
129
+ ).split(', ');
130
+ const animationDurations = (
131
+ (styles[animationDuration] || '') as CSSStyleDeclaration['animationDuration']
132
+ ).split(', ');
133
+ const animationTimeout = calculateTimeout(animationDelays, animationDurations);
134
+
135
+ let type: string | null = '';
136
+ let timeout = 0;
137
+ let propCount = 0;
138
+
139
+ if (expectedType === 'transition') {
140
+ if (transitionTimeout > 0) {
141
+ type = 'transition';
142
+ timeout = transitionTimeout;
143
+ propCount = transitionDurations.length;
144
+ }
145
+ } else if (expectedType === 'animation') {
146
+ if (animationTimeout > 0) {
147
+ type = 'animation';
148
+ timeout = animationTimeout;
149
+ propCount = animationDurations.length;
150
+ }
151
+ } else {
152
+ timeout = Math.max(transitionTimeout, animationTimeout);
153
+ type =
154
+ timeout > 0
155
+ ? transitionTimeout > animationTimeout
156
+ ? 'transition'
157
+ : 'animation'
158
+ : null;
159
+ propCount = type
160
+ ? type === 'transition'
161
+ ? transitionDurations.length
162
+ : animationDurations.length
163
+ : 0;
164
+ }
165
+
166
+ return {
167
+ type,
168
+ timeout,
169
+ propCount
170
+ };
171
+ }
172
+
173
+ function calculateTimeout(delays: string[], durations: string[]) {
174
+ while (delays.length < durations.length) {
175
+ delays = delays.concat(delays);
176
+ }
177
+
178
+ return Math.max(...durations.map((duration, i) => toMs(duration) + toMs(delays[i])));
179
+ }
@@ -0,0 +1,26 @@
1
+ import { getDataFromHtml } from '../helpers.js';
2
+ import Swup from '../Swup.js';
3
+ import { PageHtmlData } from '../helpers/getDataFromHtml.js';
4
+
5
+ export type PageData = PageHtmlData & {
6
+ responseURL: string;
7
+ };
8
+ const getPageData = function (this: Swup, request: XMLHttpRequest): PageData | null {
9
+ // this method can be replaced in case other content than html is expected to be received from server
10
+ // this function should always return { title, pageClass, originalContent, blocks, responseURL }
11
+ // in case page has invalid structure - return null
12
+ const html = request.responseText;
13
+ const pageHtmlData = getDataFromHtml(html, this.options.containers);
14
+
15
+ if (!pageHtmlData) {
16
+ console.warn('[swup] Received page is invalid.');
17
+ return null;
18
+ }
19
+
20
+ return {
21
+ ...pageHtmlData,
22
+ responseURL: request.responseURL || window.location.href
23
+ };
24
+ };
25
+
26
+ export default getPageData;
@@ -0,0 +1,33 @@
1
+ import Swup from '../Swup.js';
2
+ import { TransitionOptions } from './loadPage.js';
3
+
4
+ const leavePage = function (
5
+ this: Swup,
6
+ data: TransitionOptions,
7
+ { popstate, skipTransition }: { popstate: PopStateEvent | null; skipTransition?: boolean } = {
8
+ popstate: null
9
+ }
10
+ ) {
11
+ if (skipTransition) {
12
+ this.triggerEvent('animationSkipped');
13
+ return [Promise.resolve()];
14
+ }
15
+
16
+ this.triggerEvent('animationOutStart');
17
+
18
+ // handle classes
19
+ document.documentElement.classList.add('is-changing', 'is-leaving', 'is-animating');
20
+ if (popstate) {
21
+ document.documentElement.classList.add('is-popstate');
22
+ }
23
+
24
+ // animation promise stuff
25
+ const animationPromises: Promise<void>[] = this.getAnimationPromises('out');
26
+ Promise.all(animationPromises).then(() => {
27
+ this.triggerEvent('animationOutDone');
28
+ });
29
+
30
+ return animationPromises;
31
+ };
32
+
33
+ export default leavePage;
@@ -0,0 +1,55 @@
1
+ import { classify, createHistoryRecord, getCurrentUrl } from '../helpers.js';
2
+ import Swup from '../Swup.js';
3
+ import { PageRecord } from './Cache.js';
4
+
5
+ export type TransitionOptions = {
6
+ url: string;
7
+ customTransition?: string;
8
+ };
9
+
10
+ const loadPage = function (this: Swup, data: TransitionOptions, popstate: PopStateEvent | null) {
11
+ const { url, customTransition } = data;
12
+ const skipTransition = !!(popstate && !this.options.animateHistoryBrowsing);
13
+
14
+ this.triggerEvent('transitionStart', popstate || undefined);
15
+
16
+ // set transition object
17
+ this.updateTransition(getCurrentUrl(), url, customTransition);
18
+ if (customTransition != null) {
19
+ document.documentElement.classList.add(`to-${classify(customTransition)}`);
20
+ }
21
+
22
+ // start/skip animation
23
+ const animationPromises = this.leavePage(data, { popstate, skipTransition });
24
+
25
+ // create history record if this is not a popstate call (with or without anchor)
26
+ if (!popstate) {
27
+ createHistoryRecord(url + (this.scrollToElement || ''));
28
+ }
29
+
30
+ this.currentPageUrl = getCurrentUrl();
31
+
32
+ // Load page data
33
+ const fetchPromise = this.fetchPage(data);
34
+
35
+ // when everything is ready, render the page
36
+ Promise.all<PageRecord | void>([fetchPromise, ...animationPromises])
37
+ .then(([pageData]) => {
38
+ this.renderPage(pageData as PageRecord, { popstate, skipTransition });
39
+ })
40
+ .catch((errorUrl) => {
41
+ // Return early if errorUrl is not defined (probably aborted preload request)
42
+ if (errorUrl === undefined) return;
43
+
44
+ // Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
45
+ this.options.skipPopStateHandling = () => {
46
+ window.location = errorUrl;
47
+ return true;
48
+ };
49
+
50
+ // Go back to the actual page we're still at
51
+ history.go(-1);
52
+ });
53
+ };
54
+
55
+ export default loadPage;
@@ -0,0 +1,23 @@
1
+ import Swup from '../Swup.js';
2
+ import { EventType, Handler } from './on.js';
3
+
4
+ const off = function off(this: Swup, event?: EventType, handler?: Handler) {
5
+ if (event && handler) {
6
+ // Remove specific handler
7
+ if (this._handlers[event].includes(handler)) {
8
+ this._handlers[event] = this._handlers[event].filter((h) => h !== handler);
9
+ } else {
10
+ console.warn(`Handler for event '${event}' not found.`);
11
+ }
12
+ } else if (event) {
13
+ // Remove all handlers for specific event
14
+ this._handlers[event] = [];
15
+ } else {
16
+ // Remove all handlers for all events
17
+ Object.keys(this._handlers).forEach((event) => {
18
+ this._handlers[event as EventType] = [];
19
+ });
20
+ }
21
+ };
22
+
23
+ export default off;
@@ -0,0 +1,35 @@
1
+ import Swup from '../Swup.js';
2
+
3
+ export type EventType =
4
+ | 'animationInDone'
5
+ | 'animationInStart'
6
+ | 'animationOutDone'
7
+ | 'animationOutStart'
8
+ | 'animationSkipped'
9
+ | 'clickLink'
10
+ | 'contentReplaced'
11
+ | 'disabled'
12
+ | 'enabled'
13
+ | 'openPageInNewTab'
14
+ | 'pageLoaded'
15
+ | 'pageRetrievedFromCache'
16
+ | 'pageView'
17
+ | 'popState'
18
+ | 'samePage'
19
+ | 'samePageWithHash'
20
+ | 'serverError'
21
+ | 'transitionStart'
22
+ | 'transitionEnd'
23
+ | 'willReplaceContent';
24
+ export type Handler = (event?: Event) => void;
25
+ export type Handlers = Record<EventType, Handler[]>;
26
+
27
+ const on = function on(this: Swup, event: EventType, handler: Handler) {
28
+ if (this._handlers[event]) {
29
+ this._handlers[event].push(handler);
30
+ } else {
31
+ console.warn(`Unsupported event ${event}.`);
32
+ }
33
+ };
34
+
35
+ export default on;
@@ -0,0 +1,58 @@
1
+ import Swup from '../Swup.js';
2
+
3
+ // this should probably just be imported from @swup/plugin, but it doesn't have type defs now
4
+ export type Plugin = {
5
+ name: string;
6
+ mount: () => void;
7
+ unmount: () => void;
8
+ isSwupPlugin: true;
9
+ swup?: Swup;
10
+
11
+ // these are possibly undefined for backward compatibility
12
+ _beforeMount?: () => void;
13
+ _afterUnmount?: () => void;
14
+ _checkRequirements?: () => boolean;
15
+ };
16
+
17
+ export const use = function (this: Swup, plugin: Plugin) {
18
+ if (!plugin.isSwupPlugin) {
19
+ console.error('Not a swup plugin instance', plugin);
20
+ return;
21
+ }
22
+
23
+ plugin.swup = this;
24
+ if (plugin._checkRequirements) {
25
+ if (!plugin._checkRequirements()) {
26
+ return;
27
+ }
28
+ }
29
+ if (plugin._beforeMount) {
30
+ plugin._beforeMount();
31
+ }
32
+ plugin.mount();
33
+
34
+ this.plugins.push(plugin);
35
+
36
+ return this.plugins;
37
+ };
38
+
39
+ export function unuse(this: Swup, pluginOrName: Plugin | string) {
40
+ const plugin = this.findPlugin(pluginOrName);
41
+ if (!plugin) {
42
+ console.error('No such plugin', plugin);
43
+ return;
44
+ }
45
+
46
+ plugin.unmount();
47
+ if (plugin._afterUnmount) {
48
+ plugin._afterUnmount();
49
+ }
50
+
51
+ this.plugins = this.plugins.filter((p) => p !== plugin);
52
+
53
+ return this.plugins;
54
+ }
55
+
56
+ export function findPlugin(this: Swup, pluginOrName: Plugin | string) {
57
+ return this.plugins.find((plugin) => plugin === pluginOrName || plugin.name === pluginOrName);
58
+ }
@@ -0,0 +1,52 @@
1
+ import { Location, updateHistoryRecord, getCurrentUrl } from '../helpers.js';
2
+ import Swup from '../Swup.js';
3
+ import { PageRecord } from './Cache.js';
4
+
5
+ const renderPage = function (
6
+ this: Swup,
7
+ page: PageRecord,
8
+ { popstate, skipTransition }: { popstate: PopStateEvent | null; skipTransition?: boolean } = {
9
+ popstate: null
10
+ }
11
+ ) {
12
+ document.documentElement.classList.remove('is-leaving');
13
+
14
+ // do nothing if another page was requested in the meantime
15
+ if (!this.isSameResolvedUrl(getCurrentUrl(), page.url)) {
16
+ return;
17
+ }
18
+
19
+ const { url } = Location.fromUrl(page.responseURL);
20
+
21
+ // update cache and state if the url was redirected
22
+ if (!this.isSameResolvedUrl(getCurrentUrl(), url)) {
23
+ this.cache.cacheUrl({ ...page, url });
24
+ this.currentPageUrl = getCurrentUrl();
25
+ updateHistoryRecord(url);
26
+ }
27
+
28
+ // only add for page loads with transitions
29
+ if (!skipTransition) {
30
+ document.documentElement.classList.add('is-rendering');
31
+ }
32
+
33
+ this.triggerEvent('willReplaceContent', popstate || undefined);
34
+
35
+ this.replaceContent(page).then(() => {
36
+ this.triggerEvent('contentReplaced', popstate || undefined);
37
+ this.triggerEvent('pageView', popstate || undefined);
38
+
39
+ // empty cache if it's disabled (in case preload plugin filled it)
40
+ if (!this.options.cache) {
41
+ this.cache.empty();
42
+ }
43
+
44
+ // Perform in transition
45
+ this.enterPage({ popstate: popstate || undefined, skipTransition });
46
+
47
+ // reset scroll-to element
48
+ this.scrollToElement = null;
49
+ });
50
+ };
51
+
52
+ export default renderPage;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Perform the replacement of content after loading a page.
3
+ *
4
+ * This method can be replaced or augmented by plugins to allow pausing.
5
+ *
6
+ * It takes an object with the page data as return from `getPageData` and has to
7
+ * return a Promise that resolves once all content has been replaced and the
8
+ * site is ready to start animating in the new page.
9
+ *
10
+ * @param {object} page The page object
11
+ * @returns Promise
12
+ */
13
+ const replaceContent = function ({ blocks, title }: { blocks: string[]; title: string }) {
14
+ // Replace content blocks
15
+ blocks.forEach((html, i) => {
16
+ // we know the block exists at this point
17
+ const block = document.body.querySelector(`[data-swup="${i}"]`)!;
18
+ block.outerHTML = html;
19
+ });
20
+
21
+ // Update browser title
22
+ document.title = title;
23
+
24
+ // Return a Promise to allow plugins to defer
25
+ return Promise.resolve();
26
+ };
27
+
28
+ export default replaceContent;
@@ -0,0 +1,23 @@
1
+ import { EventType } from './on.js';
2
+ import Swup from '../Swup.js';
3
+
4
+ const triggerEvent = function (
5
+ this: Swup,
6
+ eventName: EventType,
7
+ originalEvent?: PopStateEvent | MouseEvent
8
+ ): void {
9
+ // call saved handlers with "on" method and pass originalEvent object if available
10
+ this._handlers[eventName].forEach((handler) => {
11
+ try {
12
+ handler(originalEvent);
13
+ } catch (error) {
14
+ console.error(error);
15
+ }
16
+ });
17
+
18
+ // trigger event on document with prefix "swup:"
19
+ const event = new CustomEvent(`swup:${eventName}`, { detail: eventName });
20
+ document.dispatchEvent(event);
21
+ };
22
+
23
+ export default triggerEvent;
@@ -0,0 +1,7 @@
1
+ import Swup from '../Swup.js';
2
+
3
+ const updateTransition = function (this: Swup, from: string, to: string, custom?: string): void {
4
+ this.transition = { from, to, custom };
5
+ };
6
+
7
+ export default updateTransition;