@very-simple/ajax 1.0.0-beta.5 → 1.0.0-beta.7
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/README.md +13 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.js +46 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,6 +87,19 @@ start({
|
|
|
87
87
|
})
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
`morphHeads`, `executeScripts` and `viewTransitions` also accept a callback
|
|
91
|
+
receiving `{ from, to, isBackForward }` and returning a boolean. Combined with
|
|
92
|
+
`data-ajax-state` this allows deciding per link, e.g. to skip morphing the
|
|
93
|
+
`<head>` when loading a partial:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<a href="/partial" data-ajax-state='{"isPartial": true}'>Partial</a>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
start({ morphHeads: ({ to }) => !to.state?.isPartial })
|
|
101
|
+
```
|
|
102
|
+
|
|
90
103
|
### `visit(url, options?)`
|
|
91
104
|
|
|
92
105
|
A lower-level API for triggering a visit programmatically, e.g. in
|
package/dist/index.d.ts
CHANGED
|
@@ -20,12 +20,12 @@ type MergeStrategy = 'replace' | 'morph' | 'before' | 'after' | 'prepend' | 'app
|
|
|
20
20
|
interface Config {
|
|
21
21
|
/** Strategy used to merge the swapped content into the current DOM. */
|
|
22
22
|
merge?: MergeStrategy;
|
|
23
|
-
/** Morph the `<head>` instead of leaving it untouched. */
|
|
24
|
-
morphHeads?: boolean
|
|
25
|
-
/** Re-execute `<script>` tags found in the swapped content. */
|
|
26
|
-
executeScripts?: boolean
|
|
27
|
-
/** Wrap navigations in the View Transitions API, if supported. */
|
|
28
|
-
viewTransitions?: boolean
|
|
23
|
+
/** Morph the `<head>` instead of leaving it untouched. A callback decides per visit. */
|
|
24
|
+
morphHeads?: Resolvable<boolean>;
|
|
25
|
+
/** Re-execute `<script>` tags found in the swapped content. A callback decides per visit. */
|
|
26
|
+
executeScripts?: Resolvable<boolean>;
|
|
27
|
+
/** Wrap navigations in the View Transitions API, if supported. A callback decides per visit. */
|
|
28
|
+
viewTransitions?: Resolvable<boolean>;
|
|
29
29
|
/** Called instead of the default merge to render the new document. */
|
|
30
30
|
render?: (newDocument: Document) => Promise<void> | void;
|
|
31
31
|
/** Custom scroll handling, e.g. restoring position on back/forward visits. */
|
|
@@ -43,6 +43,12 @@ interface Config {
|
|
|
43
43
|
/** Called before an element is removed from the DOM by a merge. */
|
|
44
44
|
unmount?: (el: Element) => void;
|
|
45
45
|
}
|
|
46
|
+
/** A value, or a callback resolving it right before the swap. */
|
|
47
|
+
type Resolvable<T> = T | ((info: {
|
|
48
|
+
from: PageState;
|
|
49
|
+
to: PageState;
|
|
50
|
+
isBackForward: boolean;
|
|
51
|
+
}) => T);
|
|
46
52
|
type ScrollPosition = {
|
|
47
53
|
top: number;
|
|
48
54
|
};
|
|
@@ -95,6 +101,6 @@ declare const scrollPositions: Map<string, ScrollPosition>;
|
|
|
95
101
|
* Load a new page, merge the regions/bodies and add a new history entry
|
|
96
102
|
* according to the history mode.
|
|
97
103
|
*/
|
|
98
|
-
declare const visit: (url: string, { history: historyMode, isBackForward, autoFocus, request, state, regions, morphHeads, merge: mergeStrategy, executeScripts:
|
|
104
|
+
declare const visit: (url: string, { history: historyMode, isBackForward, autoFocus, request, state, regions, morphHeads, merge: mergeStrategy, executeScripts: executeScriptsOption, viewTransitions, loadingDelay, progressHideDelay, }?: VisitOptions) => Promise<void>;
|
|
99
105
|
|
|
100
|
-
export { type AjaxState, type Config, type EventMap, type LoadingOptions, type MergeStrategy, type PageState, type ScrollBehavior, type ScrollPosition, type StartOptions, type VisitOptions, scrollPositions, start, visit };
|
|
106
|
+
export { type AjaxState, type Config, type EventMap, type LoadingOptions, type MergeStrategy, type PageState, type Resolvable, type ScrollBehavior, type ScrollPosition, type StartOptions, type VisitOptions, scrollPositions, start, visit };
|
package/dist/index.js
CHANGED
|
@@ -13,14 +13,16 @@ var config = {
|
|
|
13
13
|
// src/load.ts
|
|
14
14
|
var parser = new DOMParser();
|
|
15
15
|
var currentLoadController;
|
|
16
|
+
var progressDelayTimeout;
|
|
16
17
|
var progress = 0;
|
|
17
18
|
var trickleInterval;
|
|
18
19
|
var emit = (type, payload) => document.dispatchEvent(new CustomEvent(`ajax:${type}`, { detail: payload }));
|
|
19
20
|
var cache = /* @__PURE__ */ new Map();
|
|
20
21
|
var load = async (url, regions, options) => {
|
|
21
|
-
let
|
|
22
|
+
let aborted = false;
|
|
22
23
|
try {
|
|
23
24
|
currentLoadController?.abort();
|
|
25
|
+
clearTimeout(progressDelayTimeout);
|
|
24
26
|
currentLoadController = new AbortController();
|
|
25
27
|
setProgress(0);
|
|
26
28
|
progressDelayTimeout = window.setTimeout(() => {
|
|
@@ -41,16 +43,18 @@ var load = async (url, regions, options) => {
|
|
|
41
43
|
const document2 = parseHtml(html);
|
|
42
44
|
return { document: document2, response };
|
|
43
45
|
} catch (e) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
aborted = e instanceof DOMException && e.name === "AbortError";
|
|
47
|
+
if (!aborted) {
|
|
48
|
+
setProgress(0);
|
|
47
49
|
window.location.reload();
|
|
48
50
|
}
|
|
49
51
|
} finally {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
if (!aborted) {
|
|
53
|
+
clearTimeout(progressDelayTimeout);
|
|
54
|
+
stopTrickle();
|
|
55
|
+
setProgress(1);
|
|
56
|
+
setTimeout(() => emit("progress-end", {}), options.progressHideDelay);
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
};
|
|
56
60
|
var parseHtml = (html) => parser.parseFromString(html, "text/html");
|
|
@@ -58,8 +62,14 @@ var trickle = () => {
|
|
|
58
62
|
const amount = -0.095 * progress + 0.1;
|
|
59
63
|
setProgress(progress + Math.random() * amount);
|
|
60
64
|
};
|
|
61
|
-
var startTrickle = () =>
|
|
62
|
-
|
|
65
|
+
var startTrickle = () => {
|
|
66
|
+
stopTrickle();
|
|
67
|
+
trickleInterval = window.setInterval(trickle, 300);
|
|
68
|
+
};
|
|
69
|
+
var stopTrickle = () => {
|
|
70
|
+
clearInterval(trickleInterval);
|
|
71
|
+
trickleInterval = void 0;
|
|
72
|
+
};
|
|
63
73
|
var setProgress = (value) => {
|
|
64
74
|
progress = value;
|
|
65
75
|
emit("progress", { progress: value });
|
|
@@ -76,7 +86,7 @@ var merge = (region, newRegion, strategy) => {
|
|
|
76
86
|
Array.from(
|
|
77
87
|
newRegion.querySelectorAll(`[${escapedPrefix}permanent][id]`)
|
|
78
88
|
).forEach((el) => {
|
|
79
|
-
const original = region.querySelector(`#${el.id}`);
|
|
89
|
+
const original = region.querySelector(`#${CSS.escape(el.id)}`);
|
|
80
90
|
if (original) {
|
|
81
91
|
permanents.set(el.id, original);
|
|
82
92
|
original.remove();
|
|
@@ -85,13 +95,12 @@ var merge = (region, newRegion, strategy) => {
|
|
|
85
95
|
unmount?.(region);
|
|
86
96
|
if (strategy === "replace") region.replaceWith(newRegion);
|
|
87
97
|
else region.replaceChildren(...Array.from(newRegion.children));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
).forEach((el) => {
|
|
98
|
+
const target = strategy === "replace" ? newRegion : region;
|
|
99
|
+
target.querySelectorAll(`[${escapedPrefix}permanent][id]`).forEach((el) => {
|
|
91
100
|
const original = permanents.get(el.id);
|
|
92
101
|
if (original) el.replaceWith(original);
|
|
93
102
|
});
|
|
94
|
-
mount?.(
|
|
103
|
+
mount?.(target);
|
|
95
104
|
} else if (strategy === "before") {
|
|
96
105
|
region.insertAdjacentElement("beforebegin", newRegion);
|
|
97
106
|
mount?.(newRegion);
|
|
@@ -108,6 +117,7 @@ var merge = (region, newRegion, strategy) => {
|
|
|
108
117
|
const result = morph(region, newRegion);
|
|
109
118
|
autoFocusEl = result.autoFocusEl;
|
|
110
119
|
}
|
|
120
|
+
if (strategy === "morph") return { autoFocusEl };
|
|
111
121
|
if (newRegion.hasAttribute(`${prefix}autofocus`) || newRegion.autofocus) {
|
|
112
122
|
autoFocusEl ??= newRegion;
|
|
113
123
|
}
|
|
@@ -117,7 +127,7 @@ var merge = (region, newRegion, strategy) => {
|
|
|
117
127
|
var morph = (container, newContainer) => {
|
|
118
128
|
const { prefix, mount, unmount } = config;
|
|
119
129
|
const manualReplacements = [];
|
|
120
|
-
|
|
130
|
+
const autoFocusEls = [];
|
|
121
131
|
let currentNodeKeepAttributes = [];
|
|
122
132
|
Idiomorph.morph(container, newContainer, {
|
|
123
133
|
callbacks: {
|
|
@@ -132,6 +142,7 @@ var morph = (container, newContainer) => {
|
|
|
132
142
|
const isSameComponent = oldIsComponent && newIsComponent && oldNode.getAttribute(`${prefix}component`) === newNode.getAttribute(`${prefix}component`);
|
|
133
143
|
if (!oldIsComponent || isSameComponent) return true;
|
|
134
144
|
manualReplacements.push([oldNode, newNode]);
|
|
145
|
+
return false;
|
|
135
146
|
},
|
|
136
147
|
beforeAttributeUpdated: (name) => {
|
|
137
148
|
return !currentNodeKeepAttributes.includes(name);
|
|
@@ -147,9 +158,9 @@ var morph = (container, newContainer) => {
|
|
|
147
158
|
mount?.(node);
|
|
148
159
|
}
|
|
149
160
|
},
|
|
150
|
-
afterNodeMorphed(
|
|
151
|
-
if (
|
|
152
|
-
autoFocusEls.push(
|
|
161
|
+
afterNodeMorphed(oldNode) {
|
|
162
|
+
if (oldNode instanceof HTMLElement && (oldNode.autofocus || oldNode.hasAttribute(`${prefix}autofocus`))) {
|
|
163
|
+
autoFocusEls.push(oldNode);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
166
|
}
|
|
@@ -207,7 +218,7 @@ var emit2 = (type, payload) => {
|
|
|
207
218
|
document.dispatchEvent(event);
|
|
208
219
|
if (waiting.length) return Promise.allSettled(waiting);
|
|
209
220
|
};
|
|
210
|
-
var currentUrl = window.location.pathname;
|
|
221
|
+
var currentUrl = window.location.pathname + window.location.search;
|
|
211
222
|
var currentState = history.state;
|
|
212
223
|
var currentVisitController;
|
|
213
224
|
var scrollPositions = /* @__PURE__ */ new Map();
|
|
@@ -220,7 +231,7 @@ var visit = async (url, {
|
|
|
220
231
|
regions = config.regions,
|
|
221
232
|
morphHeads = config.morphHeads,
|
|
222
233
|
merge: mergeStrategy = config.merge,
|
|
223
|
-
executeScripts:
|
|
234
|
+
executeScripts: executeScriptsOption = config.executeScripts,
|
|
224
235
|
viewTransitions = config.viewTransitions,
|
|
225
236
|
loadingDelay = config.loadingDelay,
|
|
226
237
|
progressHideDelay = config.progressHideDelay
|
|
@@ -254,6 +265,7 @@ var visit = async (url, {
|
|
|
254
265
|
}
|
|
255
266
|
}
|
|
256
267
|
if (!newDocument) return;
|
|
268
|
+
if (signal.aborted) return;
|
|
257
269
|
currentUrl = url;
|
|
258
270
|
currentState = state;
|
|
259
271
|
if (historyMode === "replace") history.replaceState(state, "", url);
|
|
@@ -262,6 +274,10 @@ var visit = async (url, {
|
|
|
262
274
|
const to = { url, state, document: newDocument };
|
|
263
275
|
await emit2("before-swap", { from, to, isBackForward, signal });
|
|
264
276
|
if (signal.aborted) return;
|
|
277
|
+
const resolve = (option) => typeof option === "function" ? option({ from, to, isBackForward }) : option;
|
|
278
|
+
const shouldMorphHeads = resolve(morphHeads);
|
|
279
|
+
const shouldExecuteScripts = resolve(executeScriptsOption);
|
|
280
|
+
const useViewTransitions = resolve(viewTransitions);
|
|
265
281
|
const getMergeStrategy = (oldEl, newEl) => newEl.getAttribute(`${config.prefix}merge`) || oldEl.getAttribute(`${config.prefix}merge`) || mergeStrategy;
|
|
266
282
|
let autoFocusEl;
|
|
267
283
|
const hasMatchingRegions = regions?.some(
|
|
@@ -272,8 +288,9 @@ var visit = async (url, {
|
|
|
272
288
|
if (hasMatchingRegions) {
|
|
273
289
|
for (const id of regions) {
|
|
274
290
|
const region = document.querySelector(id);
|
|
275
|
-
const
|
|
276
|
-
if (!region || !
|
|
291
|
+
const parsedRegion = newDocument.querySelector(id);
|
|
292
|
+
if (!region || !parsedRegion) continue;
|
|
293
|
+
const newRegion = document.importNode(parsedRegion, true);
|
|
277
294
|
const strategy = getMergeStrategy(region, newRegion);
|
|
278
295
|
const result = merge(region, newRegion, strategy);
|
|
279
296
|
autoFocusEl ??= result.autoFocusEl;
|
|
@@ -284,7 +301,7 @@ var visit = async (url, {
|
|
|
284
301
|
}
|
|
285
302
|
} else {
|
|
286
303
|
const region = document.body;
|
|
287
|
-
const newRegion = newDocument.body;
|
|
304
|
+
const newRegion = document.importNode(newDocument.body, true);
|
|
288
305
|
const strategy = getMergeStrategy(region, newRegion);
|
|
289
306
|
const result = merge(region, newRegion, strategy);
|
|
290
307
|
autoFocusEl = result.autoFocusEl;
|
|
@@ -307,17 +324,17 @@ var visit = async (url, {
|
|
|
307
324
|
if (position) window.scrollTo(position);
|
|
308
325
|
};
|
|
309
326
|
if (config.render) {
|
|
310
|
-
if (
|
|
327
|
+
if (shouldMorphHeads) Idiomorph2.morph(document.head, newDocument.head);
|
|
311
328
|
await config.render(newDocument);
|
|
312
|
-
} else if (
|
|
329
|
+
} else if (useViewTransitions && document.startViewTransition) {
|
|
313
330
|
await document.startViewTransition(async () => {
|
|
314
|
-
if (
|
|
331
|
+
if (shouldMorphHeads) Idiomorph2.morph(document.head, newDocument.head);
|
|
315
332
|
await mergeRegions();
|
|
316
333
|
await emit2("after-swap", { from, to, isBackForward, signal });
|
|
317
334
|
applyScrollBehavior();
|
|
318
|
-
}).
|
|
335
|
+
}).updateCallbackDone;
|
|
319
336
|
} else {
|
|
320
|
-
if (
|
|
337
|
+
if (shouldMorphHeads) Idiomorph2.morph(document.head, newDocument.head);
|
|
321
338
|
await mergeRegions();
|
|
322
339
|
await emit2("after-swap", { from, to, isBackForward, signal });
|
|
323
340
|
applyScrollBehavior();
|