swup 4.0.1 → 4.2.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 +5 -3
- package/dist/types/helpers/delegateEvent.d.ts +2 -1
- package/dist/types/modules/Cache.d.ts +3 -3
- package/dist/types/modules/Visit.d.ts +5 -3
- package/dist/types/modules/__test__/delegateEvent.d.ts +1 -0
- package/dist/types/modules/getAnchorElement.d.ts +1 -1
- package/dist/types/modules/navigate.d.ts +2 -1
- package/package.json +3 -3
- package/src/Swup.ts +17 -11
- package/src/helpers/delegateEvent.ts +7 -8
- package/src/modules/Cache.ts +11 -5
- package/src/modules/Visit.ts +7 -5
- package/src/modules/__test__/cache.test.ts +17 -0
- package/src/modules/__test__/delegateEvent.ts +36 -0
- package/src/modules/getAnchorElement.ts +1 -1
- package/src/modules/navigate.ts +14 -11
- package/src/modules/scrollToContent.ts +5 -3
|
@@ -3,6 +3,7 @@ import { FetchOptions } from './fetchPage.js';
|
|
|
3
3
|
import { VisitInitOptions } from './Visit.js';
|
|
4
4
|
export type HistoryAction = 'push' | 'replace';
|
|
5
5
|
export type HistoryDirection = 'forwards' | 'backwards';
|
|
6
|
+
export type NavigationToSelfAction = 'scroll' | 'navigate';
|
|
6
7
|
/** Define how to navigate to a page. */
|
|
7
8
|
type NavigationOptions = {
|
|
8
9
|
/** Whether this visit is animated. Default: `true` */
|
|
@@ -30,5 +31,5 @@ export declare function navigate(this: Swup, url: string, options?: NavigationOp
|
|
|
30
31
|
* @param options Options for how to perform this visit.
|
|
31
32
|
* @returns Promise<void>
|
|
32
33
|
*/
|
|
33
|
-
export declare function performNavigation(this: Swup,
|
|
34
|
+
export declare function performNavigation(this: Swup, options?: NavigationOptions & FetchOptions): Promise<void>;
|
|
34
35
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swup",
|
|
3
3
|
"amdName": "Swup",
|
|
4
|
-
"version": "4.0
|
|
4
|
+
"version": "4.2.0",
|
|
5
5
|
"description": "Versatile and extensible page transition library for server-rendered websites",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"source": "./src/Swup.ts",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"types": "./dist/types/index.d.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"
|
|
14
|
+
"types": "./dist/types/index.d.ts",
|
|
15
15
|
"import": "./dist/Swup.modern.js",
|
|
16
|
-
"
|
|
16
|
+
"require": "./dist/Swup.cjs"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
package/src/Swup.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Visit, createVisit } from './modules/Visit.js';
|
|
|
11
11
|
import { Hooks } from './modules/Hooks.js';
|
|
12
12
|
import { getAnchorElement } from './modules/getAnchorElement.js';
|
|
13
13
|
import { awaitAnimations } from './modules/awaitAnimations.js';
|
|
14
|
-
import { navigate, performNavigation } from './modules/navigate.js';
|
|
14
|
+
import { navigate, performNavigation, NavigationToSelfAction } from './modules/navigate.js';
|
|
15
15
|
import { fetchPage } from './modules/fetchPage.js';
|
|
16
16
|
import { animatePageOut } from './modules/animatePageOut.js';
|
|
17
17
|
import { replaceContent } from './modules/replaceContent.js';
|
|
@@ -38,6 +38,8 @@ export type Options = {
|
|
|
38
38
|
ignoreVisit: (url: string, { el, event }: { el?: Element; event?: Event }) => boolean;
|
|
39
39
|
/** Selector for links that trigger visits. Default: `'a[href]'` */
|
|
40
40
|
linkSelector: string;
|
|
41
|
+
/** How swup handles links to the same page. Default: `scroll` */
|
|
42
|
+
linkToSelf: NavigationToSelfAction;
|
|
41
43
|
/** Plugins to register on startup. */
|
|
42
44
|
plugins: Plugin[];
|
|
43
45
|
/** Custom headers sent along with fetch requests. */
|
|
@@ -56,6 +58,7 @@ const defaults: Options = {
|
|
|
56
58
|
containers: ['#swup'],
|
|
57
59
|
ignoreVisit: (url, { el, event } = {}) => !!el?.closest('[data-no-swup]'),
|
|
58
60
|
linkSelector: 'a[href]',
|
|
61
|
+
linkToSelf: 'scroll',
|
|
59
62
|
plugins: [],
|
|
60
63
|
resolveUrl: (url) => url,
|
|
61
64
|
requestHeaders: {
|
|
@@ -138,7 +141,7 @@ export default class Swup {
|
|
|
138
141
|
this.cache = new Cache(this);
|
|
139
142
|
this.classes = new Classes(this);
|
|
140
143
|
this.hooks = new Hooks(this);
|
|
141
|
-
this.visit = this.createVisit({ to:
|
|
144
|
+
this.visit = this.createVisit({ to: '' });
|
|
142
145
|
|
|
143
146
|
if (!this.checkRequirements()) {
|
|
144
147
|
return;
|
|
@@ -259,16 +262,24 @@ export default class Swup {
|
|
|
259
262
|
|
|
260
263
|
event.preventDefault();
|
|
261
264
|
|
|
262
|
-
// Handle links to the same page
|
|
265
|
+
// Handle links to the same page
|
|
263
266
|
if (!url || url === from) {
|
|
264
267
|
if (hash) {
|
|
268
|
+
// With hash: scroll to anchor
|
|
265
269
|
this.hooks.callSync('link:anchor', { hash }, () => {
|
|
266
270
|
updateHistoryRecord(url + hash);
|
|
267
271
|
this.scrollToContent();
|
|
268
272
|
});
|
|
269
273
|
} else {
|
|
274
|
+
// Without hash: scroll to top or load/reload page
|
|
270
275
|
this.hooks.callSync('link:self', undefined, () => {
|
|
271
|
-
this.
|
|
276
|
+
switch (this.options.linkToSelf) {
|
|
277
|
+
case 'navigate':
|
|
278
|
+
return this.performNavigation();
|
|
279
|
+
case 'scroll':
|
|
280
|
+
default:
|
|
281
|
+
return this.scrollToContent();
|
|
282
|
+
}
|
|
272
283
|
});
|
|
273
284
|
}
|
|
274
285
|
return;
|
|
@@ -280,7 +291,7 @@ export default class Swup {
|
|
|
280
291
|
}
|
|
281
292
|
|
|
282
293
|
// Finally, proceed with loading the page
|
|
283
|
-
this.performNavigation(
|
|
294
|
+
this.performNavigation();
|
|
284
295
|
});
|
|
285
296
|
}
|
|
286
297
|
|
|
@@ -297,11 +308,6 @@ export default class Swup {
|
|
|
297
308
|
return;
|
|
298
309
|
}
|
|
299
310
|
|
|
300
|
-
// Exit early if the link should be ignored
|
|
301
|
-
if (this.shouldIgnoreVisit(href, { event })) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
311
|
const { url, hash } = Location.fromUrl(href);
|
|
306
312
|
const animate = this.options.animateHistoryBrowsing;
|
|
307
313
|
const resetScroll = this.options.animateHistoryBrowsing;
|
|
@@ -330,7 +336,7 @@ export default class Swup {
|
|
|
330
336
|
// }
|
|
331
337
|
|
|
332
338
|
this.hooks.callSync('history:popstate', { event }, () => {
|
|
333
|
-
this.performNavigation(
|
|
339
|
+
this.performNavigation();
|
|
334
340
|
});
|
|
335
341
|
}
|
|
336
342
|
|
|
@@ -6,19 +6,18 @@ export type DelegateEventUnsubscribe = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
/** Register a delegated event listener. */
|
|
9
|
-
export const delegateEvent = <
|
|
9
|
+
export const delegateEvent = <
|
|
10
|
+
Selector extends string,
|
|
11
|
+
TElement extends Element = ParseSelector<Selector, HTMLElement>,
|
|
12
|
+
TEvent extends EventType = EventType
|
|
13
|
+
>(
|
|
10
14
|
selector: Selector,
|
|
11
15
|
type: TEvent,
|
|
12
|
-
callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEvent]>,
|
|
16
|
+
callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEvent], TElement>,
|
|
13
17
|
options?: DelegateOptions
|
|
14
18
|
): DelegateEventUnsubscribe => {
|
|
15
19
|
const controller = new AbortController();
|
|
16
20
|
options = { ...options, signal: controller.signal };
|
|
17
|
-
delegate<
|
|
18
|
-
selector,
|
|
19
|
-
type,
|
|
20
|
-
callback,
|
|
21
|
-
options
|
|
22
|
-
);
|
|
21
|
+
delegate<Selector, TElement, TEvent>(selector, type, callback, options);
|
|
23
22
|
return { destroy: () => controller.abort() };
|
|
24
23
|
};
|
package/src/modules/Cache.ts
CHANGED
|
@@ -25,7 +25,11 @@ export class Cache {
|
|
|
25
25
|
|
|
26
26
|
/** All cached pages. */
|
|
27
27
|
get all() {
|
|
28
|
-
|
|
28
|
+
const copy = new Map();
|
|
29
|
+
this.pages.forEach((page, key) => {
|
|
30
|
+
copy.set(key, { ...page });
|
|
31
|
+
});
|
|
32
|
+
return copy;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
/** Check if the given URL has been cached. */
|
|
@@ -33,9 +37,11 @@ export class Cache {
|
|
|
33
37
|
return this.pages.has(this.resolve(url));
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
/** Return the cached page object if
|
|
40
|
+
/** Return a shallow copy of the cached page object if available. */
|
|
37
41
|
get(url: string): CacheData | undefined {
|
|
38
|
-
|
|
42
|
+
const result = this.pages.get(this.resolve(url));
|
|
43
|
+
if (!result) return result;
|
|
44
|
+
return { ...result };
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/** Create a cache record for the specified URL. */
|
|
@@ -47,9 +53,9 @@ export class Cache {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/** Update a cache record, overwriting or adding custom data. */
|
|
50
|
-
update(url: string,
|
|
56
|
+
update(url: string, payload: Record<string, any>) {
|
|
51
57
|
url = this.resolve(url);
|
|
52
|
-
page = { ...this.get(url), ...
|
|
58
|
+
const page = { ...this.get(url), ...payload, url } as CacheData;
|
|
53
59
|
this.pages.set(url, page);
|
|
54
60
|
}
|
|
55
61
|
|
package/src/modules/Visit.ts
CHANGED
|
@@ -26,7 +26,9 @@ export interface VisitFrom {
|
|
|
26
26
|
|
|
27
27
|
export interface VisitTo {
|
|
28
28
|
/** The URL of the next page */
|
|
29
|
-
url
|
|
29
|
+
url: string;
|
|
30
|
+
/** The hash of the next page */
|
|
31
|
+
hash?: string;
|
|
30
32
|
/** The HTML content of the next page */
|
|
31
33
|
html?: string;
|
|
32
34
|
}
|
|
@@ -68,7 +70,7 @@ export interface VisitHistory {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export interface VisitInitOptions {
|
|
71
|
-
to: string
|
|
73
|
+
to: string;
|
|
72
74
|
from?: string;
|
|
73
75
|
hash?: string;
|
|
74
76
|
animate?: boolean;
|
|
@@ -86,7 +88,7 @@ export function createVisit(
|
|
|
86
88
|
{
|
|
87
89
|
to,
|
|
88
90
|
from = this.currentPageUrl,
|
|
89
|
-
hash
|
|
91
|
+
hash,
|
|
90
92
|
animate = true,
|
|
91
93
|
animation: name,
|
|
92
94
|
el,
|
|
@@ -97,7 +99,7 @@ export function createVisit(
|
|
|
97
99
|
): Visit {
|
|
98
100
|
return {
|
|
99
101
|
from: { url: from },
|
|
100
|
-
to: { url: to },
|
|
102
|
+
to: { url: to, hash },
|
|
101
103
|
containers: this.options.containers,
|
|
102
104
|
animation: {
|
|
103
105
|
animate,
|
|
@@ -117,7 +119,7 @@ export function createVisit(
|
|
|
117
119
|
},
|
|
118
120
|
scroll: {
|
|
119
121
|
reset,
|
|
120
|
-
target
|
|
122
|
+
target: undefined
|
|
121
123
|
}
|
|
122
124
|
};
|
|
123
125
|
}
|
|
@@ -116,6 +116,23 @@ describe('Cache', () => {
|
|
|
116
116
|
expect(cache.has(page2.url)).toBe(true);
|
|
117
117
|
expect(cache.has(page3.url)).toBe(false);
|
|
118
118
|
});
|
|
119
|
+
|
|
120
|
+
it('should return a copy from cache.get()', () => {
|
|
121
|
+
cache.set(page1.url, page1);
|
|
122
|
+
const page = cache.get(page1.url);
|
|
123
|
+
page!.html = 'new';
|
|
124
|
+
expect(cache.get(page1.url)?.html).toEqual(page1.html);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return a new Map with shallow copies from cache.all', () => {
|
|
128
|
+
cache.set(page1.url, page1);
|
|
129
|
+
cache.set(page2.url, page2);
|
|
130
|
+
|
|
131
|
+
const all = cache.all;
|
|
132
|
+
all.get(page1.url)!.html = 'new';
|
|
133
|
+
|
|
134
|
+
expect(cache.get(page1.url)?.html).toEqual(page1.html);
|
|
135
|
+
});
|
|
119
136
|
});
|
|
120
137
|
|
|
121
138
|
describe('Types', () => {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DelegateEvent } from 'delegate-it';
|
|
2
|
+
import { describe, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { delegateEvent } from '../../helpers/delegateEvent.js';
|
|
5
|
+
|
|
6
|
+
describe('delegateEvent', () => {
|
|
7
|
+
it('should return correct types', () => {
|
|
8
|
+
delegateEvent('form', 'submit', (event) => {});
|
|
9
|
+
|
|
10
|
+
// @ts-expect-no-error
|
|
11
|
+
delegateEvent('form', 'submit', (event: SubmitEvent) => {});
|
|
12
|
+
// @ts-expect-error
|
|
13
|
+
delegateEvent('form', 'submit', (event: MouseEvent) => {});
|
|
14
|
+
|
|
15
|
+
// @ts-expect-no-error
|
|
16
|
+
delegateEvent('form', 'submit', (event: DelegateEvent<SubmitEvent>) => {});
|
|
17
|
+
// @ts-expect-error
|
|
18
|
+
delegateEvent('form', 'submit', (event: DelegateEvent<MouseEvent>) => {});
|
|
19
|
+
|
|
20
|
+
// @ts-expect-no-error
|
|
21
|
+
delegateEvent('form', 'submit', (event: DelegateEvent<SubmitEvent, HTMLFormElement>) => {});
|
|
22
|
+
delegateEvent(
|
|
23
|
+
'form',
|
|
24
|
+
'submit',
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
(event: DelegateEvent<MouseEvent, HTMLAnchorElement>) => {}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
delegateEvent('form', 'submit', (event) => {
|
|
30
|
+
// @ts-expect-no-error
|
|
31
|
+
const el: HTMLFormElement = event.delegateTarget;
|
|
32
|
+
});
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
delegateEvent('form', 'submit', (event: MouseEvent) => {});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -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
|
@@ -5,6 +5,7 @@ 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';
|
|
8
9
|
|
|
9
10
|
/** Define how to navigate to a page. */
|
|
10
11
|
type NavigationOptions = {
|
|
@@ -28,6 +29,10 @@ export function navigate(
|
|
|
28
29
|
options: NavigationOptions & FetchOptions = {},
|
|
29
30
|
init: Omit<VisitInitOptions, 'to'> = {}
|
|
30
31
|
) {
|
|
32
|
+
if (typeof url !== 'string') {
|
|
33
|
+
throw new Error(`swup.navigate() requires a URL parameter`);
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
// Check if the visit should be ignored
|
|
32
37
|
if (this.shouldIgnoreVisit(url, { el: init.el, event: init.event })) {
|
|
33
38
|
window.location.href = url;
|
|
@@ -36,7 +41,7 @@ export function navigate(
|
|
|
36
41
|
|
|
37
42
|
const { url: to, hash } = Location.fromUrl(url);
|
|
38
43
|
this.visit = this.createVisit({ ...init, to, hash });
|
|
39
|
-
this.performNavigation(
|
|
44
|
+
this.performNavigation(options);
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
/**
|
|
@@ -52,15 +57,9 @@ export function navigate(
|
|
|
52
57
|
*/
|
|
53
58
|
export async function performNavigation(
|
|
54
59
|
this: Swup,
|
|
55
|
-
url: string,
|
|
56
60
|
options: NavigationOptions & FetchOptions = {}
|
|
57
61
|
) {
|
|
58
|
-
if (typeof url !== 'string') {
|
|
59
|
-
throw new Error(`swup.navigate() requires a URL parameter`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
62
|
const { el } = this.visit.trigger;
|
|
63
|
-
this.visit.to.url = Location.fromUrl(url).url;
|
|
64
63
|
options.referrer = options.referrer || this.currentPageUrl;
|
|
65
64
|
|
|
66
65
|
if (options.animate === false) {
|
|
@@ -95,10 +94,14 @@ export async function performNavigation(
|
|
|
95
94
|
return args.page;
|
|
96
95
|
});
|
|
97
96
|
|
|
98
|
-
// Create history record if this is not a popstate call
|
|
97
|
+
// Create/update history record if this is not a popstate call or leads to the same URL
|
|
99
98
|
if (!this.visit.history.popstate) {
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// Add the hash directly from the trigger element
|
|
100
|
+
const newUrl = this.visit.to.url + this.visit.to.hash;
|
|
101
|
+
if (
|
|
102
|
+
this.visit.history.action === 'replace' ||
|
|
103
|
+
this.visit.to.url === this.currentPageUrl
|
|
104
|
+
) {
|
|
102
105
|
updateHistoryRecord(newUrl);
|
|
103
106
|
} else {
|
|
104
107
|
const index = this.currentHistoryIndex + 1;
|
|
@@ -142,7 +145,7 @@ export async function performNavigation(
|
|
|
142
145
|
|
|
143
146
|
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
|
|
144
147
|
this.options.skipPopStateHandling = () => {
|
|
145
|
-
window.location.href = this.visit.to.url
|
|
148
|
+
window.location.href = this.visit.to.url + this.visit.to.hash;
|
|
146
149
|
return true;
|
|
147
150
|
};
|
|
148
151
|
|
|
@@ -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
|
}
|