swup 3.1.1 → 4.0.0-rc.20

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 (79) hide show
  1. package/README.md +94 -0
  2. package/dist/Swup.cjs +1 -1
  3. package/dist/Swup.cjs.map +1 -1
  4. package/dist/Swup.modern.js +1 -1
  5. package/dist/Swup.modern.js.map +1 -1
  6. package/dist/Swup.module.js +1 -1
  7. package/dist/Swup.module.js.map +1 -1
  8. package/dist/Swup.umd.js +1 -1
  9. package/dist/Swup.umd.js.map +1 -1
  10. package/dist/types/Swup.d.ts +53 -45
  11. package/dist/types/helpers/Location.d.ts +10 -7
  12. package/dist/types/helpers/delegateEvent.d.ts +2 -2
  13. package/dist/types/helpers/matchPath.d.ts +3 -0
  14. package/dist/types/helpers.d.ts +1 -4
  15. package/dist/types/index.d.ts +7 -4
  16. package/dist/types/modules/Cache.d.ts +14 -14
  17. package/dist/types/modules/Classes.d.ts +13 -0
  18. package/dist/types/modules/Context.d.ts +73 -0
  19. package/dist/types/modules/Hooks.d.ts +241 -0
  20. package/dist/types/modules/__test__/cache.test.d.ts +1 -0
  21. package/dist/types/modules/__test__/hooks.test.d.ts +1 -0
  22. package/dist/types/modules/__test__/replaceContent.test.d.ts +1 -0
  23. package/dist/types/modules/awaitAnimations.d.ts +21 -0
  24. package/dist/types/modules/enterPage.d.ts +5 -2
  25. package/dist/types/modules/fetchPage.d.ts +23 -3
  26. package/dist/types/modules/getAnchorElement.d.ts +2 -1
  27. package/dist/types/modules/leavePage.d.ts +5 -2
  28. package/dist/types/modules/plugins.d.ts +7 -0
  29. package/dist/types/modules/renderPage.d.ts +6 -6
  30. package/dist/types/modules/replaceContent.d.ts +8 -11
  31. package/dist/types/modules/visit.d.ts +33 -0
  32. package/dist/types/utils/index.d.ts +3 -1
  33. package/package.json +13 -9
  34. package/src/Swup.ts +172 -182
  35. package/src/__test__/index.test.ts +8 -3
  36. package/src/helpers/Location.ts +12 -9
  37. package/src/helpers/__test__/matchPath.test.ts +54 -0
  38. package/src/helpers/delegateEvent.ts +3 -2
  39. package/src/helpers/matchPath.ts +22 -0
  40. package/src/helpers.ts +2 -5
  41. package/src/index.ts +36 -4
  42. package/src/modules/Cache.ts +43 -33
  43. package/src/modules/Classes.ts +48 -0
  44. package/src/modules/Context.ts +121 -0
  45. package/src/modules/Hooks.ts +413 -0
  46. package/src/modules/__test__/cache.test.ts +142 -0
  47. package/src/modules/__test__/hooks.test.ts +263 -0
  48. package/src/modules/__test__/replaceContent.test.ts +92 -0
  49. package/src/modules/awaitAnimations.ts +169 -0
  50. package/src/modules/enterPage.ts +23 -17
  51. package/src/modules/fetchPage.ts +74 -29
  52. package/src/modules/getAnchorElement.ts +2 -1
  53. package/src/modules/leavePage.ts +26 -20
  54. package/src/modules/plugins.ts +7 -2
  55. package/src/modules/renderPage.ts +52 -33
  56. package/src/modules/replaceContent.ts +33 -16
  57. package/src/modules/visit.ts +143 -0
  58. package/src/utils/index.ts +25 -5
  59. package/dist/types/helpers/cleanupAnimationClasses.d.ts +0 -2
  60. package/dist/types/helpers/fetch.d.ts +0 -5
  61. package/dist/types/helpers/getDataFromHtml.d.ts +0 -7
  62. package/dist/types/helpers/markSwupElements.d.ts +0 -1
  63. package/dist/types/modules/events.d.ts +0 -33
  64. package/dist/types/modules/getAnimationPromises.d.ts +0 -7
  65. package/dist/types/modules/getPageData.d.ts +0 -6
  66. package/dist/types/modules/loadPage.d.ts +0 -15
  67. package/dist/types/modules/transitions.d.ts +0 -6
  68. package/readme.md +0 -60
  69. package/src/helpers/cleanupAnimationClasses.ts +0 -8
  70. package/src/helpers/fetch.ts +0 -33
  71. package/src/helpers/getDataFromHtml.ts +0 -39
  72. package/src/helpers/markSwupElements.ts +0 -16
  73. package/src/modules/__test__/events.test.ts +0 -72
  74. package/src/modules/events.ts +0 -92
  75. package/src/modules/getAnimationPromises.ts +0 -183
  76. package/src/modules/getPageData.ts +0 -24
  77. package/src/modules/loadPage.ts +0 -81
  78. package/src/modules/transitions.ts +0 -10
  79. /package/dist/types/{modules/__test__/events.test.d.ts → helpers/__test__/matchPath.test.d.ts} +0 -0
@@ -1,39 +0,0 @@
1
- import { query, queryAll } from '../utils.js';
2
-
3
- export type PageHtmlData = {
4
- title: string;
5
- originalContent: string;
6
- blocks: string[];
7
- pageClass?: string;
8
- };
9
-
10
- export const getDataFromHtml = (html: string, containers: string[]): PageHtmlData => {
11
- let fakeDom = document.createElement('html');
12
- fakeDom.innerHTML = html;
13
- let blocks: string[] = [];
14
-
15
- containers.forEach((selector) => {
16
- if (query(selector, fakeDom) == null) {
17
- console.warn(`[swup] Container ${selector} not found on page.`);
18
- return null;
19
- } else {
20
- if (queryAll(selector).length !== queryAll(selector, fakeDom).length) {
21
- console.warn(`[swup] Mismatched number of containers found on new page.`);
22
- }
23
- queryAll(selector).forEach((item, index) => {
24
- queryAll(selector, fakeDom)[index].setAttribute('data-swup', String(blocks.length));
25
- blocks.push(queryAll(selector, fakeDom)[index].outerHTML);
26
- });
27
- }
28
- });
29
-
30
- const title = query('title', fakeDom)?.innerText || '';
31
- const pageClass = query('body', fakeDom)?.className;
32
-
33
- // to prevent memory leaks
34
- fakeDom.innerHTML = '';
35
- // @ts-ignore don't want to type it as possible null, since it's created at the top of the function always
36
- fakeDom = null;
37
-
38
- return { title, pageClass, blocks, originalContent: html };
39
- };
@@ -1,16 +0,0 @@
1
- import { query, queryAll } from '../utils.js';
2
-
3
- export const markSwupElements = (element: Element, containers: string[]): void => {
4
- let blocks = 0;
5
-
6
- containers.forEach((selector) => {
7
- if (query(selector, element) == null) {
8
- console.warn(`[swup] Container ${selector} not found on page.`);
9
- } else {
10
- queryAll(selector).forEach((item: Element, index: number) => {
11
- queryAll(selector, element)[index].setAttribute('data-swup', String(blocks));
12
- blocks++;
13
- });
14
- }
15
- });
16
- };
@@ -1,72 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import Swup from '../../Swup.js';
3
- import { Handler } from '../events.js';
4
-
5
- describe('events', () => {
6
- it('should add event handlers to handlers array', () => {
7
- const swup = new Swup();
8
- const handler = vi.fn();
9
-
10
- swup.on('enabled', handler);
11
-
12
- expect(swup._handlers.enabled.indexOf(handler)).toBe(0);
13
- });
14
-
15
- it('should remove event handlers from handlers array', () => {
16
- const swup = new Swup();
17
- const handler = vi.fn();
18
-
19
- swup.on('enabled', handler);
20
- swup.on('animationInDone', handler);
21
- swup.on('animationInStart', handler);
22
-
23
- expect(swup._handlers.enabled.indexOf(handler)).toBe(0);
24
- expect(swup._handlers.animationInDone.indexOf(handler)).toBe(0);
25
- expect(swup._handlers.animationInStart.indexOf(handler)).toBe(0);
26
-
27
- swup.off('enabled', handler);
28
- expect(swup._handlers.enabled.indexOf(handler)).toBe(-1);
29
-
30
- swup.off('animationInDone');
31
- expect(swup._handlers.animationInDone.indexOf(handler)).toBe(-1);
32
-
33
- swup.off();
34
- expect(swup._handlers.animationInStart.indexOf(handler)).toBe(-1);
35
- });
36
-
37
- it('should trigger event handler', () => {
38
- const swup = new Swup();
39
- const handler = vi.fn();
40
-
41
- swup.on('enabled', handler);
42
-
43
- swup.triggerEvent('enabled');
44
-
45
- expect(handler).toBeCalledTimes(1);
46
- });
47
-
48
- it('should trigger event handler with event', () => {
49
- const swup = new Swup();
50
- const handler: Handler<'popState'> = vi.fn();
51
- const event = new PopStateEvent('');
52
-
53
- swup.on('popState', handler);
54
- swup.triggerEvent('popState', event);
55
-
56
- expect(handler).toBeCalledWith(event);
57
- });
58
-
59
- it('types work and error when necessary', () => {
60
- const swup = new Swup();
61
-
62
- // @ts-expect-no-error
63
- swup.on('popState', (event: PopStateEvent) => {});
64
- // @ts-expect-no-error
65
- swup.triggerEvent('popState', new PopStateEvent(''));
66
-
67
- // @ts-expect-error
68
- swup.on('popState', (event: MouseEvent) => {});
69
- // @ts-expect-error
70
- swup.triggerEvent('popState', new MouseEvent(''));
71
- });
72
- });
@@ -1,92 +0,0 @@
1
- import Swup from '../Swup.js';
2
- import { DelegateEvent } from 'delegate-it';
3
-
4
- type HandlersEventMap = {
5
- animationInDone: undefined;
6
- animationInStart: undefined;
7
- animationOutDone: undefined;
8
- animationOutStart: undefined;
9
- animationSkipped: undefined;
10
- clickLink: DelegateEvent<MouseEvent>;
11
- contentReplaced: PopStateEvent | undefined;
12
- disabled: undefined;
13
- enabled: undefined;
14
- openPageInNewTab: DelegateEvent<MouseEvent>;
15
- pageLoaded: undefined;
16
- pageRetrievedFromCache: undefined;
17
- pageView: PopStateEvent | undefined;
18
- popState: PopStateEvent;
19
- samePage: DelegateEvent<MouseEvent>;
20
- samePageWithHash: DelegateEvent<MouseEvent>;
21
- serverError: undefined;
22
- transitionStart: PopStateEvent | undefined;
23
- transitionEnd: PopStateEvent | undefined;
24
- willReplaceContent: PopStateEvent | undefined;
25
- };
26
- type AvailableEventNames = keyof HandlersEventMap;
27
-
28
- export type Handler<T extends keyof HandlersEventMap> = (event: HandlersEventMap[T]) => void;
29
- export type Handlers = {
30
- [Key in keyof HandlersEventMap]: Handler<Key>[];
31
- };
32
-
33
- export function on<TEventType extends AvailableEventNames>(
34
- this: Swup,
35
- event: TEventType,
36
- handler: Handler<TEventType>
37
- ): void {
38
- const eventHandlers = this._handlers[event] as Handler<TEventType>[];
39
-
40
- if (eventHandlers) {
41
- eventHandlers.push(handler);
42
- } else {
43
- console.warn(`Unsupported event ${event}.`);
44
- }
45
- }
46
-
47
- export function off<TEventType extends AvailableEventNames>(
48
- this: Swup,
49
- event?: TEventType,
50
- handler?: Handler<TEventType>
51
- ) {
52
- if (event && handler) {
53
- const eventHandlers = this._handlers[event] as Handler<TEventType>[];
54
- // Remove specific handler
55
- if (eventHandlers.includes(handler)) {
56
- (this._handlers[event] as Handler<TEventType>[]) = eventHandlers.filter(
57
- (h) => h !== handler
58
- );
59
- } else {
60
- console.warn(`Handler for event '${event}' not found.`);
61
- }
62
- } else if (event) {
63
- // Remove all handlers for specific event
64
- this._handlers[event] = [];
65
- } else {
66
- // Remove all handlers for all events
67
- Object.keys(this._handlers).forEach((event) => {
68
- this._handlers[event as keyof HandlersEventMap] = [];
69
- });
70
- }
71
- }
72
-
73
- export function triggerEvent<TEventType extends AvailableEventNames>(
74
- this: Swup,
75
- eventName: TEventType,
76
- originalEvent?: HandlersEventMap[TEventType]
77
- ): void {
78
- const eventHandlers = this._handlers[eventName] as Handler<TEventType>[];
79
-
80
- // call saved handlers with "on" method and pass originalEvent object if available
81
- eventHandlers.forEach((handler) => {
82
- try {
83
- handler(originalEvent as HandlersEventMap[TEventType]);
84
- } catch (error) {
85
- console.error(error);
86
- }
87
- });
88
-
89
- // trigger event on document with prefix "swup:"
90
- const event = new CustomEvent(`swup:${eventName}`, { detail: eventName });
91
- document.dispatchEvent(event);
92
- }
@@ -1,183 +0,0 @@
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 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 elements match the animationSelector, but keep things going
39
- if (!animatedElements.length) {
40
- console.warn(`[swup] No elements found matching animationSelector \`${selector}\``);
41
- return [Promise.resolve()];
42
- }
43
-
44
- const animationPromises = animatedElements
45
- .map((element) => getAnimationPromiseForElement(element))
46
- .filter(Boolean) as Promise<void>[];
47
-
48
- if (!animationPromises.length) {
49
- console.warn(
50
- `[swup] No CSS animation duration defined on elements matching \`${selector}\``
51
- );
52
- return [Promise.resolve()];
53
- }
54
-
55
- return animationPromises;
56
- }
57
-
58
- const isTransitionOrAnimationEvent = (event: any): event is TransitionEvent | AnimationEvent =>
59
- [transitionEndEvent, animationEndEvent].includes(event.type);
60
-
61
- function getAnimationPromiseForElement(element: Element): Promise<void> | undefined {
62
- const { type, timeout, propCount } = getTransitionInfo(element);
63
-
64
- // Resolve immediately if no transition defined
65
- if (!type || !timeout) {
66
- return undefined;
67
- }
68
-
69
- return new Promise((resolve) => {
70
- const endEvent = type === 'transition' ? transitionEndEvent : animationEndEvent;
71
- const startTime = performance.now();
72
- let propsTransitioned = 0;
73
-
74
- const end = () => {
75
- element.removeEventListener(endEvent, onEnd);
76
- resolve();
77
- };
78
-
79
- const onEnd: EventListener = (event) => {
80
- // Skip transitions on child elements
81
- if (event.target !== element) {
82
- return;
83
- }
84
-
85
- if (!isTransitionOrAnimationEvent(event)) {
86
- throw new Error('Not a transition or animation event.');
87
- }
88
-
89
- // Skip transitions that happened before we started listening
90
- const elapsedTime = (performance.now() - startTime) / 1000;
91
- if (elapsedTime < event.elapsedTime) {
92
- return;
93
- }
94
-
95
- // End if all properties have transitioned
96
- if (++propsTransitioned >= propCount) {
97
- end();
98
- }
99
- };
100
-
101
- setTimeout(() => {
102
- if (propsTransitioned < propCount) {
103
- end();
104
- }
105
- }, timeout + 1);
106
-
107
- element.addEventListener(endEvent, onEnd);
108
- });
109
- }
110
-
111
- export function getTransitionInfo(
112
- element: Element,
113
- expectedType: 'animation' | 'transition' | null = null
114
- ) {
115
- const styles = window.getComputedStyle(element);
116
-
117
- // not sure what to do about the below mess other than casting, but it's a mess
118
- const transitionDelay = `${transitionProp}Delay` as keyof CSSStyleDeclaration;
119
- const transitionDuration = `${transitionProp}Duration` as keyof CSSStyleDeclaration;
120
- const animationDelay = `${animationProp}Delay` as keyof CSSStyleDeclaration;
121
- const animationDuration = `${animationProp}Duration` as keyof CSSStyleDeclaration;
122
-
123
- const transitionDelays = (
124
- styles[transitionDelay] as CSSStyleDeclaration['transitionDelay']
125
- ).split(', ');
126
- const transitionDurations = (
127
- (styles[transitionDuration] || '') as CSSStyleDeclaration['transitionDuration']
128
- ).split(', ');
129
- const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations);
130
-
131
- const animationDelays = (
132
- (styles[animationDelay] || '') as CSSStyleDeclaration['animationDelay']
133
- ).split(', ');
134
- const animationDurations = (
135
- (styles[animationDuration] || '') as CSSStyleDeclaration['animationDuration']
136
- ).split(', ');
137
- const animationTimeout = calculateTimeout(animationDelays, animationDurations);
138
-
139
- let type: string | null = '';
140
- let timeout = 0;
141
- let propCount = 0;
142
-
143
- if (expectedType === 'transition') {
144
- if (transitionTimeout > 0) {
145
- type = 'transition';
146
- timeout = transitionTimeout;
147
- propCount = transitionDurations.length;
148
- }
149
- } else if (expectedType === 'animation') {
150
- if (animationTimeout > 0) {
151
- type = 'animation';
152
- timeout = animationTimeout;
153
- propCount = animationDurations.length;
154
- }
155
- } else {
156
- timeout = Math.max(transitionTimeout, animationTimeout);
157
- type =
158
- timeout > 0
159
- ? transitionTimeout > animationTimeout
160
- ? 'transition'
161
- : 'animation'
162
- : null;
163
- propCount = type
164
- ? type === 'transition'
165
- ? transitionDurations.length
166
- : animationDurations.length
167
- : 0;
168
- }
169
-
170
- return {
171
- type,
172
- timeout,
173
- propCount
174
- };
175
- }
176
-
177
- function calculateTimeout(delays: string[], durations: string[]) {
178
- while (delays.length < durations.length) {
179
- delays = delays.concat(delays);
180
- }
181
-
182
- return Math.max(...durations.map((duration, i) => toMs(duration) + toMs(delays[i])));
183
- }
@@ -1,24 +0,0 @@
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
- export 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
- };
@@ -1,81 +0,0 @@
1
- import { classify, createHistoryRecord, updateHistoryRecord, getCurrentUrl } from '../helpers.js';
2
- import Swup from '../Swup.js';
3
- import { PageRecord } from './Cache.js';
4
-
5
- export type HistoryAction = 'push' | 'replace';
6
-
7
- export type TransitionOptions = {
8
- url: string;
9
- customTransition?: string;
10
- history?: HistoryAction;
11
- };
12
-
13
- export type PageLoadOptions = {
14
- url: string;
15
- customTransition?: string;
16
- history?: HistoryAction;
17
- event?: PopStateEvent;
18
- };
19
-
20
- export function loadPage(this: Swup, data: TransitionOptions) {
21
- const { url } = data;
22
-
23
- // Check if the visit should be ignored
24
- if (this.shouldIgnoreVisit(url)) {
25
- window.location.href = url;
26
- } else {
27
- this.performPageLoad(data);
28
- }
29
- }
30
-
31
- export function performPageLoad(this: Swup, data: PageLoadOptions) {
32
- const { url, event, customTransition, history: historyAction = 'push' } = data ?? {};
33
-
34
- const isHistoryVisit = event instanceof PopStateEvent;
35
- const skipTransition = this.shouldSkipTransition({ url, event });
36
-
37
- this.triggerEvent('transitionStart', event);
38
-
39
- // set transition object
40
- this.updateTransition(getCurrentUrl(), url, customTransition);
41
- if (customTransition != null) {
42
- document.documentElement.classList.add(`to-${classify(customTransition)}`);
43
- }
44
-
45
- // start/skip animation
46
- const animationPromises = this.leavePage({ event, skipTransition });
47
-
48
- // Load page data
49
- const fetchPromise = this.fetchPage(data);
50
-
51
- // create history record if this is not a popstate call (with or without anchor)
52
- if (!isHistoryVisit) {
53
- const historyUrl = url + (this.scrollToElement || '');
54
- if (historyAction === 'replace') {
55
- updateHistoryRecord(historyUrl);
56
- } else {
57
- createHistoryRecord(historyUrl);
58
- }
59
- }
60
-
61
- this.currentPageUrl = getCurrentUrl();
62
-
63
- // when everything is ready, render the page
64
- Promise.all<PageRecord | void>([fetchPromise, ...animationPromises])
65
- .then(([pageData]) => {
66
- this.renderPage(pageData as PageRecord, { event, skipTransition });
67
- })
68
- .catch((errorUrl) => {
69
- // Return early if errorUrl is not defined (probably aborted preload request)
70
- if (errorUrl === undefined) return;
71
-
72
- // Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed
73
- this.options.skipPopStateHandling = () => {
74
- window.location = errorUrl;
75
- return true;
76
- };
77
-
78
- // Go back to the actual page we're still at
79
- history.go(-1);
80
- });
81
- }
@@ -1,10 +0,0 @@
1
- import Swup from '../Swup.js';
2
-
3
- export function updateTransition(this: Swup, from: string, to: string, custom?: string): void {
4
- this.transition = { from, to, custom };
5
- }
6
-
7
- export function shouldSkipTransition(this: Swup, { event }: { url?: string; event?: Event }) {
8
- const isHistoryVisit = event instanceof PopStateEvent;
9
- return !!(isHistoryVisit && !this.options.animateHistoryBrowsing);
10
- }