swup 4.1.0 → 4.3.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.
- package/dist/Swup.cjs +1 -1
- package/dist/Swup.cjs.map +1 -1
- package/dist/Swup.modern.js +1 -1
- package/dist/Swup.modern.js.map +1 -1
- package/dist/Swup.module.js +1 -1
- package/dist/Swup.module.js.map +1 -1
- package/dist/Swup.umd.js +1 -1
- package/dist/Swup.umd.js.map +1 -1
- package/dist/types/Swup.d.ts +119 -117
- package/dist/types/__test__/index.test.d.ts +1 -1
- package/dist/types/config/version.d.ts +5 -5
- package/dist/types/helpers/Location.d.ts +24 -24
- package/dist/types/helpers/__test__/matchPath.test.d.ts +1 -1
- package/dist/types/helpers/classify.d.ts +2 -2
- package/dist/types/helpers/createHistoryRecord.d.ts +9 -2
- package/dist/types/helpers/delegateEvent.d.ts +7 -7
- package/dist/types/helpers/getCurrentUrl.d.ts +4 -4
- package/dist/types/helpers/matchPath.d.ts +4 -4
- package/dist/types/helpers/updateHistoryRecord.d.ts +2 -2
- package/dist/types/helpers.d.ts +7 -7
- package/dist/types/index.d.ts +14 -14
- package/dist/types/modules/Cache.d.ts +34 -34
- package/dist/types/modules/Classes.d.ts +13 -13
- package/dist/types/modules/Hooks.d.ts +268 -250
- package/dist/types/modules/Visit.d.ts +85 -75
- package/dist/types/modules/__test__/cache.test.d.ts +1 -1
- package/dist/types/modules/__test__/{delegateEvent.d.ts → delegateEvent.test.d.ts} +1 -1
- package/dist/types/modules/__test__/hooks.test.d.ts +1 -1
- package/dist/types/modules/__test__/plugins.test.d.ts +1 -1
- package/dist/types/modules/__test__/replaceContent.test.d.ts +1 -1
- package/dist/types/modules/animatePageIn.d.ts +6 -6
- package/dist/types/modules/animatePageOut.d.ts +6 -6
- package/dist/types/modules/awaitAnimations.d.ts +19 -19
- package/dist/types/modules/fetchPage.d.ts +27 -29
- package/dist/types/modules/getAnchorElement.d.ts +9 -9
- package/dist/types/modules/navigate.d.ts +41 -34
- package/dist/types/modules/plugins.d.ts +26 -26
- package/dist/types/modules/renderPage.d.ts +7 -7
- package/dist/types/modules/replaceContent.d.ts +13 -13
- package/dist/types/modules/resolveUrl.d.ts +14 -14
- package/dist/types/modules/scrollToContent.d.ts +6 -6
- package/dist/types/utils/index.d.ts +20 -20
- package/dist/types/utils.d.ts +1 -1
- package/package.json +9 -3
- package/src/Swup.ts +24 -17
- package/src/config/version.ts +1 -2
- package/src/helpers/createHistoryRecord.ts +9 -1
- package/src/helpers/matchPath.ts +1 -1
- package/src/helpers/updateHistoryRecord.ts +4 -2
- package/src/modules/Cache.ts +2 -2
- package/src/modules/Classes.ts +1 -1
- package/src/modules/Hooks.ts +91 -39
- package/src/modules/Visit.ts +20 -5
- package/src/modules/__test__/cache.test.ts +3 -3
- package/src/modules/__test__/hooks.test.ts +12 -13
- package/src/modules/animatePageIn.ts +1 -1
- package/src/modules/animatePageOut.ts +2 -2
- package/src/modules/awaitAnimations.ts +1 -1
- package/src/modules/fetchPage.ts +7 -5
- package/src/modules/getAnchorElement.ts +1 -1
- package/src/modules/navigate.ts +37 -15
- package/src/modules/plugins.ts +3 -3
- package/src/modules/renderPage.ts +1 -5
- package/src/modules/replaceContent.ts +13 -0
- package/src/modules/scrollToContent.ts +5 -3
- package/src/utils/index.ts +5 -4
- /package/src/modules/__test__/{delegateEvent.ts → delegateEvent.test.ts} +0 -0
|
@@ -7,7 +7,7 @@ import { classify } from '../helpers.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export const animatePageOut = async function (this: Swup) {
|
|
9
9
|
if (!this.visit.animation.animate) {
|
|
10
|
-
await this.hooks.call('animation:skip');
|
|
10
|
+
await this.hooks.call('animation:skip', undefined);
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -26,5 +26,5 @@ export const animatePageOut = async function (this: Swup) {
|
|
|
26
26
|
await this.awaitAnimations({ selector: visit.animation.selector });
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
await this.hooks.call('animation:out:end');
|
|
29
|
+
await this.hooks.call('animation:out:end', undefined);
|
|
30
30
|
};
|
|
@@ -150,7 +150,7 @@ export function getTransitionInfo(element: Element, expectedType?: AnimationType
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function isTransitionOrAnimationEvent(event:
|
|
153
|
+
function isTransitionOrAnimationEvent(event: Event): event is TransitionEvent | AnimationEvent {
|
|
154
154
|
return [`${TRANSITION}end`, `${ANIMATION}end`].includes(event.type);
|
|
155
155
|
}
|
|
156
156
|
|
package/src/modules/fetchPage.ts
CHANGED
|
@@ -10,13 +10,11 @@ export interface PageData {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** Define how a page is fetched. */
|
|
13
|
-
export interface FetchOptions extends RequestInit {
|
|
13
|
+
export interface FetchOptions extends Omit<RequestInit, 'cache'> {
|
|
14
14
|
/** The request method. */
|
|
15
15
|
method?: 'GET' | 'POST';
|
|
16
16
|
/** The body of the request: raw string, form data object or URL params. */
|
|
17
17
|
body?: string | FormData | URLSearchParams;
|
|
18
|
-
/** The headers of the request: key/value object. */
|
|
19
|
-
headers?: Record<string, string>;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export class FetchError extends Error {
|
|
@@ -66,8 +64,12 @@ export async function fetchPage(
|
|
|
66
64
|
const { url: finalUrl } = Location.fromUrl(responseUrl);
|
|
67
65
|
const page = { url: finalUrl, html };
|
|
68
66
|
|
|
69
|
-
//
|
|
70
|
-
if (
|
|
67
|
+
// Write to cache for safe methods and non-redirects
|
|
68
|
+
if (
|
|
69
|
+
this.visit.cache.write &&
|
|
70
|
+
(!options.method || options.method === 'GET') &&
|
|
71
|
+
url === finalUrl
|
|
72
|
+
) {
|
|
71
73
|
this.cache.set(page.url, page);
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -8,7 +8,7 @@ import { escapeCssIdentifier as escape, query } from '../utils.js';
|
|
|
8
8
|
*
|
|
9
9
|
* @see https://html.spec.whatwg.org/#find-a-potential-indicated-element
|
|
10
10
|
*/
|
|
11
|
-
export const getAnchorElement = (hash
|
|
11
|
+
export const getAnchorElement = (hash?: string): Element | null => {
|
|
12
12
|
if (hash && hash.charAt(0) === '#') {
|
|
13
13
|
hash = hash.substring(1);
|
|
14
14
|
}
|
package/src/modules/navigate.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import Swup from '../Swup.js';
|
|
2
2
|
import { createHistoryRecord, updateHistoryRecord, getCurrentUrl, Location } from '../helpers.js';
|
|
3
|
-
import { FetchOptions } from './fetchPage.js';
|
|
3
|
+
import { FetchOptions, PageData } from './fetchPage.js';
|
|
4
4
|
import { VisitInitOptions } from './Visit.js';
|
|
5
5
|
|
|
6
6
|
export type HistoryAction = 'push' | 'replace';
|
|
7
7
|
export type HistoryDirection = 'forwards' | 'backwards';
|
|
8
|
+
export type NavigationToSelfAction = 'scroll' | 'navigate';
|
|
9
|
+
export type CacheControl = Partial<{ read: boolean; write: boolean }>;
|
|
8
10
|
|
|
9
11
|
/** Define how to navigate to a page. */
|
|
10
12
|
type NavigationOptions = {
|
|
@@ -14,6 +16,8 @@ type NavigationOptions = {
|
|
|
14
16
|
animation?: string;
|
|
15
17
|
/** History action to perform: `push` for creating a new history entry, `replace` for replacing the current entry. Default: `push` */
|
|
16
18
|
history?: HistoryAction;
|
|
19
|
+
/** Whether this visit should read from or write to the cache. */
|
|
20
|
+
cache?: CacheControl;
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -28,6 +32,10 @@ export function navigate(
|
|
|
28
32
|
options: NavigationOptions & FetchOptions = {},
|
|
29
33
|
init: Omit<VisitInitOptions, 'to'> = {}
|
|
30
34
|
) {
|
|
35
|
+
if (typeof url !== 'string') {
|
|
36
|
+
throw new Error(`swup.navigate() requires a URL parameter`);
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
// Check if the visit should be ignored
|
|
32
40
|
if (this.shouldIgnoreVisit(url, { el: init.el, event: init.event })) {
|
|
33
41
|
window.location.href = url;
|
|
@@ -36,7 +44,7 @@ export function navigate(
|
|
|
36
44
|
|
|
37
45
|
const { url: to, hash } = Location.fromUrl(url);
|
|
38
46
|
this.visit = this.createVisit({ ...init, to, hash });
|
|
39
|
-
this.performNavigation(
|
|
47
|
+
this.performNavigation(options);
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/**
|
|
@@ -52,15 +60,9 @@ export function navigate(
|
|
|
52
60
|
*/
|
|
53
61
|
export async function performNavigation(
|
|
54
62
|
this: Swup,
|
|
55
|
-
url: string,
|
|
56
63
|
options: NavigationOptions & FetchOptions = {}
|
|
57
64
|
) {
|
|
58
|
-
if (typeof url !== 'string') {
|
|
59
|
-
throw new Error(`swup.navigate() requires a URL parameter`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
65
|
const { el } = this.visit.trigger;
|
|
63
|
-
this.visit.to.url = Location.fromUrl(url).url;
|
|
64
66
|
options.referrer = options.referrer || this.currentPageUrl;
|
|
65
67
|
|
|
66
68
|
if (options.animate === false) {
|
|
@@ -84,21 +86,41 @@ export async function performNavigation(
|
|
|
84
86
|
this.visit.animation.name = animation;
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
// Sanitize cache option
|
|
90
|
+
if (typeof options.cache === 'object') {
|
|
91
|
+
this.visit.cache.read = options.cache.read ?? this.visit.cache.read;
|
|
92
|
+
this.visit.cache.write = options.cache.write ?? this.visit.cache.write;
|
|
93
|
+
} else if (options.cache !== undefined) {
|
|
94
|
+
this.visit.cache = { read: !!options.cache, write: !!options.cache };
|
|
95
|
+
}
|
|
96
|
+
// Delete this so that window.fetch doesn't mis-interpret it
|
|
97
|
+
delete options.cache;
|
|
98
|
+
|
|
87
99
|
try {
|
|
88
|
-
await this.hooks.call('visit:start');
|
|
100
|
+
await this.hooks.call('visit:start', undefined);
|
|
89
101
|
|
|
90
102
|
// Begin loading page
|
|
91
103
|
const pagePromise = this.hooks.call('page:load', { options }, async (visit, args) => {
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
// Read from cache
|
|
105
|
+
let cachedPage: PageData | undefined;
|
|
106
|
+
if (this.visit.cache.read) {
|
|
107
|
+
cachedPage = this.cache.get(visit.to.url);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
args.page = cachedPage || (await this.fetchPage(visit.to.url, args.options));
|
|
94
111
|
args.cache = !!cachedPage;
|
|
112
|
+
|
|
95
113
|
return args.page;
|
|
96
114
|
});
|
|
97
115
|
|
|
98
|
-
// Create history record if this is not a popstate call
|
|
116
|
+
// Create/update history record if this is not a popstate call or leads to the same URL
|
|
99
117
|
if (!this.visit.history.popstate) {
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
// Add the hash directly from the trigger element
|
|
119
|
+
const newUrl = this.visit.to.url + this.visit.to.hash;
|
|
120
|
+
if (
|
|
121
|
+
this.visit.history.action === 'replace' ||
|
|
122
|
+
this.visit.to.url === this.currentPageUrl
|
|
123
|
+
) {
|
|
102
124
|
updateHistoryRecord(newUrl);
|
|
103
125
|
} else {
|
|
104
126
|
const index = this.currentHistoryIndex + 1;
|
|
@@ -142,7 +164,7 @@ export async function performNavigation(
|
|
|
142
164
|
|
|
143
165
|
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
|
|
144
166
|
this.options.skipPopStateHandling = () => {
|
|
145
|
-
window.location.href = this.visit.to.url
|
|
167
|
+
window.location.href = this.visit.to.url + this.visit.to.hash;
|
|
146
168
|
return true;
|
|
147
169
|
};
|
|
148
170
|
|
package/src/modules/plugins.ts
CHANGED
|
@@ -21,8 +21,8 @@ export type Plugin = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const isSwupPlugin = (maybeInvalidPlugin: unknown): maybeInvalidPlugin is Plugin => {
|
|
24
|
-
// @ts-ignore
|
|
25
|
-
return maybeInvalidPlugin?.isSwupPlugin;
|
|
24
|
+
// @ts-ignore: this might be anything, object or no
|
|
25
|
+
return Boolean(maybeInvalidPlugin?.isSwupPlugin);
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
/** Install a plugin. */
|
|
@@ -72,6 +72,6 @@ export function findPlugin(this: Swup, pluginOrName: Plugin | string) {
|
|
|
72
72
|
(plugin) =>
|
|
73
73
|
plugin === pluginOrName ||
|
|
74
74
|
plugin.name === pluginOrName ||
|
|
75
|
-
plugin.name === `Swup${pluginOrName}`
|
|
75
|
+
plugin.name === `Swup${String(pluginOrName)}`
|
|
76
76
|
);
|
|
77
77
|
}
|
|
@@ -47,14 +47,10 @@ export const renderPage = async function (this: Swup, requestedUrl: string, page
|
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
// scroll into view: either anchor or top of page
|
|
50
|
+
// @ts-ignore: not returning a promise is intentional to allow users to pause in handler
|
|
50
51
|
await this.hooks.call('content:scroll', undefined, () => {
|
|
51
52
|
return this.scrollToContent();
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
await this.hooks.call('page:view', { url: this.currentPageUrl, title: document.title });
|
|
55
|
-
|
|
56
|
-
// empty cache if it's disabled (in case preload plugin filled it)
|
|
57
|
-
if (!this.options.cache) {
|
|
58
|
-
this.cache.clear();
|
|
59
|
-
}
|
|
60
56
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Swup, { Options } from '../Swup.js';
|
|
2
|
+
import { query, queryAll } from '../utils.js';
|
|
2
3
|
import { PageData } from './fetchPage.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -20,6 +21,9 @@ export const replaceContent = function (
|
|
|
20
21
|
const title = incomingDocument.querySelector('title')?.innerText || '';
|
|
21
22
|
document.title = title;
|
|
22
23
|
|
|
24
|
+
// Save persisted elements
|
|
25
|
+
const persistedElements = queryAll('[data-swup-persist]:not([data-swup-persist=""])');
|
|
26
|
+
|
|
23
27
|
// Update content containers
|
|
24
28
|
const replaced = containers
|
|
25
29
|
.map((selector) => {
|
|
@@ -39,5 +43,14 @@ export const replaceContent = function (
|
|
|
39
43
|
})
|
|
40
44
|
.filter(Boolean);
|
|
41
45
|
|
|
46
|
+
// Restore persisted elements
|
|
47
|
+
persistedElements.forEach((existing) => {
|
|
48
|
+
const key = existing.getAttribute('data-swup-persist');
|
|
49
|
+
const replacement = query(`[data-swup-persist="${key}"]`);
|
|
50
|
+
if (replacement && replacement !== existing) {
|
|
51
|
+
replacement.replaceWith(existing);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
42
55
|
return replaced.length === containers.length;
|
|
43
56
|
};
|
|
@@ -7,14 +7,16 @@ import Swup from '../Swup.js';
|
|
|
7
7
|
export const scrollToContent = function (this: Swup): boolean {
|
|
8
8
|
const options: ScrollIntoViewOptions = { behavior: 'auto' };
|
|
9
9
|
const { target, reset } = this.visit.scroll;
|
|
10
|
+
const scrollTarget = target || this.visit.to.hash;
|
|
11
|
+
|
|
10
12
|
let scrolled = false;
|
|
11
13
|
|
|
12
|
-
if (
|
|
14
|
+
if (scrollTarget) {
|
|
13
15
|
scrolled = this.hooks.callSync(
|
|
14
16
|
'scroll:anchor',
|
|
15
|
-
{ hash:
|
|
17
|
+
{ hash: scrollTarget, options },
|
|
16
18
|
(visit, { hash, options }) => {
|
|
17
|
-
const anchor = this.getAnchorElement(hash
|
|
19
|
+
const anchor = this.getAnchorElement(hash);
|
|
18
20
|
if (anchor) {
|
|
19
21
|
anchor.scrollIntoView(options);
|
|
20
22
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -23,18 +23,19 @@ export const nextTick = (): Promise<void> => {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
/** Check if an object is a Promise or a Thenable */
|
|
26
|
-
export function isPromise<T>(obj:
|
|
26
|
+
export function isPromise<T>(obj: unknown): obj is PromiseLike<T> {
|
|
27
27
|
return (
|
|
28
28
|
!!obj &&
|
|
29
29
|
(typeof obj === 'object' || typeof obj === 'function') &&
|
|
30
|
-
typeof obj.then === 'function'
|
|
30
|
+
typeof (obj as Record<string, unknown>).then === 'function'
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Call a function as a Promise. Resolves with the returned Promsise or immediately. */
|
|
35
|
-
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
|
|
36
|
+
export function runAsPromise(func: Function, args: unknown[] = []): Promise<unknown> {
|
|
36
37
|
return new Promise((resolve, reject) => {
|
|
37
|
-
const result = func(...args);
|
|
38
|
+
const result: unknown = func(...args);
|
|
38
39
|
if (isPromise(result)) {
|
|
39
40
|
result.then(resolve, reject);
|
|
40
41
|
} else {
|
|
File without changes
|