swup 4.3.2 → 4.3.4
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 +1 -1
- package/dist/types/modules/Visit.d.ts +2 -0
- package/dist/types/modules/__test__/visit.test.d.ts +1 -0
- package/dist/types/modules/renderPage.d.ts +1 -2
- package/package.json +1 -1
- package/src/Swup.ts +10 -5
- package/src/modules/Visit.ts +3 -0
- package/src/modules/__test__/visit.test.ts +92 -0
- package/src/modules/navigate.ts +30 -24
- package/src/modules/renderPage.ts +1 -7
package/src/Swup.ts
CHANGED
|
@@ -90,7 +90,7 @@ export default class Swup {
|
|
|
90
90
|
/** URL of the currently visible page */
|
|
91
91
|
currentPageUrl: string = getCurrentUrl();
|
|
92
92
|
/** Index of the current history entry */
|
|
93
|
-
protected currentHistoryIndex
|
|
93
|
+
protected currentHistoryIndex: number;
|
|
94
94
|
/** Delegated event subscription handle */
|
|
95
95
|
protected clickDelegate?: DelegateEventUnsubscribe;
|
|
96
96
|
|
|
@@ -144,6 +144,8 @@ export default class Swup {
|
|
|
144
144
|
this.hooks = new Hooks(this);
|
|
145
145
|
this.visit = this.createVisit({ to: '' });
|
|
146
146
|
|
|
147
|
+
this.currentHistoryIndex = (history.state as HistoryState)?.index ?? 1;
|
|
148
|
+
|
|
147
149
|
if (!this.checkRequirements()) {
|
|
148
150
|
return;
|
|
149
151
|
}
|
|
@@ -181,8 +183,10 @@ export default class Swup {
|
|
|
181
183
|
// Mount plugins
|
|
182
184
|
this.options.plugins.forEach((plugin) => this.use(plugin));
|
|
183
185
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
+
// Create initial history record
|
|
187
|
+
if ((history.state as HistoryState)?.source !== 'swup') {
|
|
188
|
+
updateHistoryRecord(null, { index: this.currentHistoryIndex });
|
|
189
|
+
}
|
|
186
190
|
|
|
187
191
|
// Give consumers a chance to hook into enable
|
|
188
192
|
await nextTick();
|
|
@@ -323,10 +327,11 @@ export default class Swup {
|
|
|
323
327
|
this.visit.history.popstate = true;
|
|
324
328
|
|
|
325
329
|
// Determine direction of history visit
|
|
326
|
-
const index =
|
|
327
|
-
if (index) {
|
|
330
|
+
const index = (event.state as HistoryState)?.index ?? 0;
|
|
331
|
+
if (index && index !== this.currentHistoryIndex) {
|
|
328
332
|
const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards';
|
|
329
333
|
this.visit.history.direction = direction;
|
|
334
|
+
this.currentHistoryIndex = index;
|
|
330
335
|
}
|
|
331
336
|
|
|
332
337
|
// Disable animation & scrolling for history visits
|
package/src/modules/Visit.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { HistoryAction, HistoryDirection } from './navigate.js';
|
|
|
3
3
|
|
|
4
4
|
/** An object holding details about the current visit. */
|
|
5
5
|
export interface Visit {
|
|
6
|
+
/** A unique ID to identify this visit */
|
|
7
|
+
id: number;
|
|
6
8
|
/** The previous page, about to leave */
|
|
7
9
|
from: VisitFrom;
|
|
8
10
|
/** The next page, about to enter */
|
|
@@ -92,6 +94,7 @@ export function createVisit(
|
|
|
92
94
|
{ to, from = this.currentPageUrl, hash, el, event }: VisitInitOptions
|
|
93
95
|
): Visit {
|
|
94
96
|
return {
|
|
97
|
+
id: Math.random(),
|
|
95
98
|
from: { url: from },
|
|
96
99
|
to: { url: to, hash },
|
|
97
100
|
containers: this.options.containers,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import Swup from '../../Swup.js';
|
|
3
|
+
import { Visit, createVisit } from '../Visit.js';
|
|
4
|
+
|
|
5
|
+
class SwupWithPublicVisitMethods extends Swup {
|
|
6
|
+
public createVisit = createVisit;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const swup = new SwupWithPublicVisitMethods();
|
|
10
|
+
let visit: Visit;
|
|
11
|
+
|
|
12
|
+
describe('Visit', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
visit = swup.createVisit({ to: '' });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('is an object', () => {
|
|
18
|
+
expect(visit).to.be.an('object');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('has an id', () => {
|
|
22
|
+
expect(visit.id).to.be.a('number');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('generates unique ids', () => {
|
|
26
|
+
let id = visit.id;
|
|
27
|
+
visit = swup.createVisit({ to: '' });
|
|
28
|
+
expect(visit.id).to.not.equal(id);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('has a from object with the current URL', () => {
|
|
32
|
+
expect(visit.from).to.be.an('object');
|
|
33
|
+
expect(visit.from.url).to.be.a('string');
|
|
34
|
+
visit = swup.createVisit({ to: '', from: '/from' });
|
|
35
|
+
expect(visit.from).toMatchObject({ url: '/from' });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('has a to object with the next URL', () => {
|
|
39
|
+
expect(visit.to).to.be.an('object');
|
|
40
|
+
expect(visit.to.url).to.be.a('string');
|
|
41
|
+
visit = swup.createVisit({ to: '/to' });
|
|
42
|
+
expect(visit.to).toMatchObject({ url: '/to' });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('has an animation object', () => {
|
|
46
|
+
expect(visit.animation).to.be.an('object');
|
|
47
|
+
expect(visit.animation).toMatchObject({
|
|
48
|
+
animate: true,
|
|
49
|
+
name: undefined,
|
|
50
|
+
scope: swup.options.animationScope,
|
|
51
|
+
selector: swup.options.animationSelector
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('has a container array', () => {
|
|
56
|
+
expect(visit.containers).to.be.an('array');
|
|
57
|
+
expect(visit.containers).toEqual(swup.options.containers);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('has a trigger object', () => {
|
|
61
|
+
expect(visit.trigger).to.be.an('object');
|
|
62
|
+
expect(visit.trigger).toMatchObject({
|
|
63
|
+
el: undefined,
|
|
64
|
+
event: undefined
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('has a cache object', () => {
|
|
69
|
+
expect(visit.cache).to.be.an('object');
|
|
70
|
+
expect(visit.cache).toEqual({
|
|
71
|
+
read: swup.options.cache,
|
|
72
|
+
write: swup.options.cache
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('has a history object', () => {
|
|
77
|
+
expect(visit.history).to.be.an('object');
|
|
78
|
+
expect(visit.history).toEqual({
|
|
79
|
+
action: 'push',
|
|
80
|
+
popstate: false,
|
|
81
|
+
direction: undefined
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('has a scroll object', () => {
|
|
86
|
+
expect(visit.scroll).to.be.an('object');
|
|
87
|
+
expect(visit.scroll).toEqual({
|
|
88
|
+
reset: true,
|
|
89
|
+
target: undefined
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/modules/navigate.ts
CHANGED
|
@@ -61,37 +61,41 @@ export function navigate(
|
|
|
61
61
|
export async function performNavigation(
|
|
62
62
|
this: Swup,
|
|
63
63
|
options: NavigationOptions & FetchOptions = {}
|
|
64
|
-
) {
|
|
65
|
-
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
// Save this localy to a) allow ignoring the visit if a new one was started in the meantime
|
|
66
|
+
// and b) avoid unintended modifications to any newer visits
|
|
67
|
+
const visit = this.visit;
|
|
68
|
+
|
|
69
|
+
const { el } = visit.trigger;
|
|
66
70
|
options.referrer = options.referrer || this.currentPageUrl;
|
|
67
71
|
|
|
68
72
|
if (options.animate === false) {
|
|
69
|
-
|
|
73
|
+
visit.animation.animate = false;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
// Clean up old animation classes
|
|
73
|
-
if (!
|
|
77
|
+
if (!visit.animation.animate) {
|
|
74
78
|
this.classes.clear();
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
// Get history action from option or attribute on trigger element
|
|
78
82
|
const history = options.history || el?.getAttribute('data-swup-history') || undefined;
|
|
79
83
|
if (history && ['push', 'replace'].includes(history)) {
|
|
80
|
-
|
|
84
|
+
visit.history.action = history as HistoryAction;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// Get custom animation name from option or attribute on trigger element
|
|
84
88
|
const animation = options.animation || el?.getAttribute('data-swup-animation') || undefined;
|
|
85
89
|
if (animation) {
|
|
86
|
-
|
|
90
|
+
visit.animation.name = animation;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
// Sanitize cache option
|
|
90
94
|
if (typeof options.cache === 'object') {
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
visit.cache.read = options.cache.read ?? visit.cache.read;
|
|
96
|
+
visit.cache.write = options.cache.write ?? visit.cache.write;
|
|
93
97
|
} else if (options.cache !== undefined) {
|
|
94
|
-
|
|
98
|
+
visit.cache = { read: !!options.cache, write: !!options.cache };
|
|
95
99
|
}
|
|
96
100
|
// Delete this so that window.fetch doesn't mis-interpret it
|
|
97
101
|
delete options.cache;
|
|
@@ -103,7 +107,7 @@ export async function performNavigation(
|
|
|
103
107
|
const pagePromise = this.hooks.call('page:load', { options }, async (visit, args) => {
|
|
104
108
|
// Read from cache
|
|
105
109
|
let cachedPage: PageData | undefined;
|
|
106
|
-
if (
|
|
110
|
+
if (visit.cache.read) {
|
|
107
111
|
cachedPage = this.cache.get(visit.to.url);
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -114,34 +118,36 @@ export async function performNavigation(
|
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
// Create/update history record if this is not a popstate call or leads to the same URL
|
|
117
|
-
if (!
|
|
121
|
+
if (!visit.history.popstate) {
|
|
118
122
|
// Add the hash directly from the trigger element
|
|
119
|
-
const newUrl =
|
|
120
|
-
if (
|
|
121
|
-
this.visit.history.action === 'replace' ||
|
|
122
|
-
this.visit.to.url === this.currentPageUrl
|
|
123
|
-
) {
|
|
123
|
+
const newUrl = visit.to.url + visit.to.hash;
|
|
124
|
+
if (visit.history.action === 'replace' || visit.to.url === this.currentPageUrl) {
|
|
124
125
|
updateHistoryRecord(newUrl);
|
|
125
126
|
} else {
|
|
126
|
-
|
|
127
|
-
createHistoryRecord(newUrl, { index });
|
|
127
|
+
this.currentHistoryIndex++;
|
|
128
|
+
createHistoryRecord(newUrl, { index: this.currentHistoryIndex });
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
this.currentPageUrl = getCurrentUrl();
|
|
132
133
|
|
|
133
134
|
// Wait for page before starting to animate out?
|
|
134
|
-
if (
|
|
135
|
+
if (visit.animation.wait) {
|
|
135
136
|
const { html } = await pagePromise;
|
|
136
|
-
|
|
137
|
+
visit.to.html = html;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
// Wait for page to load and leave animation to finish
|
|
140
141
|
const animationPromise = this.animatePageOut();
|
|
141
142
|
const [page] = await Promise.all([pagePromise, animationPromise]);
|
|
142
143
|
|
|
144
|
+
// Abort if another visit was started in the meantime
|
|
145
|
+
if (visit.id !== this.visit.id) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
143
149
|
// Render page: replace content and scroll to top/fragment
|
|
144
|
-
await this.renderPage(
|
|
150
|
+
await this.renderPage(page);
|
|
145
151
|
|
|
146
152
|
// Wait for enter animation
|
|
147
153
|
await this.animatePageIn();
|
|
@@ -150,8 +156,8 @@ export async function performNavigation(
|
|
|
150
156
|
await this.hooks.call('visit:end', undefined, () => this.classes.clear());
|
|
151
157
|
|
|
152
158
|
// Reset visit info after finish?
|
|
153
|
-
// if (
|
|
154
|
-
// this.createVisit({ to: undefined });
|
|
159
|
+
// if (visit.to && this.isSameResolvedUrl(visit.to.url, requestedUrl)) {
|
|
160
|
+
// this.visit = this.createVisit({ to: undefined });
|
|
155
161
|
// }
|
|
156
162
|
} catch (error: unknown) {
|
|
157
163
|
// Return early if error is undefined (probably aborted preload request)
|
|
@@ -164,7 +170,7 @@ export async function performNavigation(
|
|
|
164
170
|
|
|
165
171
|
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
|
|
166
172
|
this.options.skipPopStateHandling = () => {
|
|
167
|
-
window.location.href =
|
|
173
|
+
window.location.href = visit.to.url + visit.to.hash;
|
|
168
174
|
return true;
|
|
169
175
|
};
|
|
170
176
|
|
|
@@ -4,18 +4,12 @@ import { PageData } from './fetchPage.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Render the next page: replace the content and update scroll position.
|
|
7
|
-
* @returns Promise<void>
|
|
8
7
|
*/
|
|
9
|
-
export const renderPage = async function (this: Swup,
|
|
8
|
+
export const renderPage = async function (this: Swup, page: PageData): Promise<void> {
|
|
10
9
|
const { url, html } = page;
|
|
11
10
|
|
|
12
11
|
this.classes.remove('is-leaving');
|
|
13
12
|
|
|
14
|
-
// do nothing if another page was requested in the meantime
|
|
15
|
-
if (!this.isSameResolvedUrl(getCurrentUrl(), requestedUrl)) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
// update state if the url was redirected
|
|
20
14
|
if (!this.isSameResolvedUrl(getCurrentUrl(), url)) {
|
|
21
15
|
updateHistoryRecord(url);
|