@lokascript/core 1.1.1 → 1.1.2
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/behaviors/index.js +1151 -0
- package/dist/behaviors/index.js.map +1 -0
- package/dist/behaviors/index.mjs +1141 -0
- package/dist/behaviors/index.mjs.map +1 -0
- package/dist/commands/index.js +9080 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/index.mjs +8922 -0
- package/dist/commands/index.mjs.map +1 -0
- package/dist/expressions/index.js +6484 -0
- package/dist/expressions/index.js.map +1 -0
- package/dist/expressions/index.mjs +6466 -0
- package/dist/expressions/index.mjs.map +1 -0
- package/dist/metadata.js +378 -0
- package/dist/metadata.js.map +1 -0
- package/dist/metadata.mjs +368 -0
- package/dist/metadata.mjs.map +1 -0
- package/dist/parser/full-parser.js +6408 -0
- package/dist/parser/full-parser.js.map +1 -0
- package/dist/parser/full-parser.mjs +6405 -0
- package/dist/parser/full-parser.mjs.map +1 -0
- package/dist/parser/regex-parser.js +412 -0
- package/dist/parser/regex-parser.js.map +1 -0
- package/dist/parser/regex-parser.mjs +409 -0
- package/dist/parser/regex-parser.mjs.map +1 -0
- package/dist/reference/index.js +602 -0
- package/dist/reference/index.js.map +1 -0
- package/dist/reference/index.mjs +593 -0
- package/dist/reference/index.mjs.map +1 -0
- package/dist/registry/browser-types.js +81 -0
- package/dist/registry/browser-types.js.map +1 -0
- package/dist/registry/browser-types.mjs +76 -0
- package/dist/registry/browser-types.mjs.map +1 -0
- package/dist/registry/environment.js +36 -0
- package/dist/registry/environment.js.map +1 -0
- package/dist/registry/environment.mjs +30 -0
- package/dist/registry/environment.mjs.map +1 -0
- package/dist/registry/index.js +7111 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/index.mjs +7087 -0
- package/dist/registry/index.mjs.map +1 -0
- package/dist/registry/universal-types.js +91 -0
- package/dist/registry/universal-types.js.map +1 -0
- package/dist/registry/universal-types.mjs +86 -0
- package/dist/registry/universal-types.mjs.map +1 -0
- package/package.json +2 -2
- package/src/expressions/properties/impl/index.test.ts +3 -2
|
@@ -0,0 +1,1151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/morphlex.ts
|
|
4
|
+
var SUPPORTS_MOVE_BEFORE = "moveBefore" in Element.prototype;
|
|
5
|
+
var ELEMENT_NODE_TYPE = 1;
|
|
6
|
+
var TEXT_NODE_TYPE = 3;
|
|
7
|
+
var IS_PARENT_NODE_TYPE = [
|
|
8
|
+
0,
|
|
9
|
+
1,
|
|
10
|
+
0,
|
|
11
|
+
0,
|
|
12
|
+
0,
|
|
13
|
+
0,
|
|
14
|
+
0,
|
|
15
|
+
0,
|
|
16
|
+
0,
|
|
17
|
+
1,
|
|
18
|
+
0,
|
|
19
|
+
1,
|
|
20
|
+
0
|
|
21
|
+
];
|
|
22
|
+
var Operation = {
|
|
23
|
+
EqualNode: 0,
|
|
24
|
+
SameElement: 1,
|
|
25
|
+
SameNode: 2
|
|
26
|
+
};
|
|
27
|
+
var candidateNodes = new Set;
|
|
28
|
+
var candidateElements = new Set;
|
|
29
|
+
var unmatchedNodes = new Set;
|
|
30
|
+
var unmatchedElements = new Set;
|
|
31
|
+
var whitespaceNodes = new Set;
|
|
32
|
+
function morph(from, to, options = {}) {
|
|
33
|
+
if (typeof to === "string")
|
|
34
|
+
to = parseFragment(to).childNodes;
|
|
35
|
+
if (!options.preserveChanges && isParentNode(from))
|
|
36
|
+
flagDirtyInputs(from);
|
|
37
|
+
new Morph(options).morph(from, to);
|
|
38
|
+
}
|
|
39
|
+
function morphInner(from, to, options = {}) {
|
|
40
|
+
if (typeof to === "string") {
|
|
41
|
+
const fragment = parseFragment(to);
|
|
42
|
+
if (fragment.firstChild && fragment.childNodes.length === 1 && fragment.firstChild.nodeType === ELEMENT_NODE_TYPE) {
|
|
43
|
+
to = fragment.firstChild;
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error("[Morphlex] The string was not a valid HTML element.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (from.nodeType === ELEMENT_NODE_TYPE && to.nodeType === ELEMENT_NODE_TYPE && from.localName === to.localName) {
|
|
49
|
+
if (isParentNode(from))
|
|
50
|
+
flagDirtyInputs(from);
|
|
51
|
+
new Morph(options).visitChildNodes(from, to);
|
|
52
|
+
} else {
|
|
53
|
+
throw new Error("[Morphlex] You can only do an inner morph with matching elements.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function flagDirtyInputs(node) {
|
|
57
|
+
for (const input of node.querySelectorAll("input")) {
|
|
58
|
+
if (input.name && input.value !== input.defaultValue || input.checked !== input.defaultChecked) {
|
|
59
|
+
input.setAttribute("morphlex-dirty", "");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const element of node.querySelectorAll("option")) {
|
|
63
|
+
if (element.value && element.selected !== element.defaultSelected) {
|
|
64
|
+
element.setAttribute("morphlex-dirty", "");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const element of node.querySelectorAll("textarea")) {
|
|
68
|
+
if (element.value !== element.defaultValue) {
|
|
69
|
+
element.setAttribute("morphlex-dirty", "");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function parseFragment(string) {
|
|
74
|
+
const template = document.createElement("template");
|
|
75
|
+
template.innerHTML = string.trim();
|
|
76
|
+
return template.content;
|
|
77
|
+
}
|
|
78
|
+
function moveBefore(parent, node, insertionPoint) {
|
|
79
|
+
if (node === insertionPoint)
|
|
80
|
+
return;
|
|
81
|
+
if (node.parentNode === parent) {
|
|
82
|
+
if (node.nextSibling === insertionPoint)
|
|
83
|
+
return;
|
|
84
|
+
if (SUPPORTS_MOVE_BEFORE) {
|
|
85
|
+
parent.moveBefore(node, insertionPoint);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
parent.insertBefore(node, insertionPoint);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class Morph {
|
|
93
|
+
#idArrayMap = new WeakMap;
|
|
94
|
+
#idSetMap = new WeakMap;
|
|
95
|
+
#options;
|
|
96
|
+
constructor(options = {}) {
|
|
97
|
+
this.#options = options;
|
|
98
|
+
}
|
|
99
|
+
morph(from, to) {
|
|
100
|
+
if (isParentNode(from)) {
|
|
101
|
+
this.#mapIdSets(from);
|
|
102
|
+
}
|
|
103
|
+
if (to instanceof NodeList) {
|
|
104
|
+
this.#mapIdArraysForEach(to);
|
|
105
|
+
this.#morphOneToMany(from, to);
|
|
106
|
+
} else if (isParentNode(to)) {
|
|
107
|
+
this.#mapIdArrays(to);
|
|
108
|
+
this.#morphOneToOne(from, to);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
#morphOneToMany(from, to) {
|
|
112
|
+
const length = to.length;
|
|
113
|
+
if (length === 0) {
|
|
114
|
+
this.#removeNode(from);
|
|
115
|
+
} else if (length === 1) {
|
|
116
|
+
this.#morphOneToOne(from, to[0]);
|
|
117
|
+
} else if (length > 1) {
|
|
118
|
+
const newNodes = [...to];
|
|
119
|
+
this.#morphOneToOne(from, newNodes.shift());
|
|
120
|
+
const insertionPoint = from.nextSibling;
|
|
121
|
+
const parent = from.parentNode || document;
|
|
122
|
+
for (let i = 0;i < newNodes.length; i++) {
|
|
123
|
+
const newNode = newNodes[i];
|
|
124
|
+
if (this.#options.beforeNodeAdded?.(parent, newNode, insertionPoint) ?? true) {
|
|
125
|
+
parent.insertBefore(newNode, insertionPoint);
|
|
126
|
+
this.#options.afterNodeAdded?.(newNode);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
#morphOneToOne(from, to) {
|
|
132
|
+
if (from === to)
|
|
133
|
+
return;
|
|
134
|
+
if (from.isEqualNode(to))
|
|
135
|
+
return;
|
|
136
|
+
if (from.nodeType === ELEMENT_NODE_TYPE && to.nodeType === ELEMENT_NODE_TYPE) {
|
|
137
|
+
if (from.localName === to.localName) {
|
|
138
|
+
this.#morphMatchingElements(from, to);
|
|
139
|
+
} else {
|
|
140
|
+
this.#morphNonMatchingElements(from, to);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
this.#morphOtherNode(from, to);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
#morphMatchingElements(from, to) {
|
|
147
|
+
if (!(this.#options.beforeNodeVisited?.(from, to) ?? true))
|
|
148
|
+
return;
|
|
149
|
+
if (from.hasAttributes() || to.hasAttributes()) {
|
|
150
|
+
this.#visitAttributes(from, to);
|
|
151
|
+
}
|
|
152
|
+
if (from.localName === "textarea" && to.localName === "textarea") {
|
|
153
|
+
this.#visitTextArea(from, to);
|
|
154
|
+
} else if (from.hasChildNodes() || to.hasChildNodes()) {
|
|
155
|
+
this.visitChildNodes(from, to);
|
|
156
|
+
}
|
|
157
|
+
this.#options.afterNodeVisited?.(from, to);
|
|
158
|
+
}
|
|
159
|
+
#morphNonMatchingElements(from, to) {
|
|
160
|
+
if (!(this.#options.beforeNodeVisited?.(from, to) ?? true))
|
|
161
|
+
return;
|
|
162
|
+
this.#replaceNode(from, to);
|
|
163
|
+
this.#options.afterNodeVisited?.(from, to);
|
|
164
|
+
}
|
|
165
|
+
#morphOtherNode(from, to) {
|
|
166
|
+
if (!(this.#options.beforeNodeVisited?.(from, to) ?? true))
|
|
167
|
+
return;
|
|
168
|
+
if (from.nodeType === to.nodeType && from.nodeValue !== null && to.nodeValue !== null) {
|
|
169
|
+
from.nodeValue = to.nodeValue;
|
|
170
|
+
} else {
|
|
171
|
+
this.#replaceNode(from, to);
|
|
172
|
+
}
|
|
173
|
+
this.#options.afterNodeVisited?.(from, to);
|
|
174
|
+
}
|
|
175
|
+
#visitAttributes(from, to) {
|
|
176
|
+
if (from.hasAttribute("morphlex-dirty")) {
|
|
177
|
+
from.removeAttribute("morphlex-dirty");
|
|
178
|
+
}
|
|
179
|
+
for (const { name, value } of to.attributes) {
|
|
180
|
+
if (name === "value") {
|
|
181
|
+
if (isInputElement(from) && from.value !== value) {
|
|
182
|
+
if (!this.#options.preserveChanges || from.value === from.defaultValue) {
|
|
183
|
+
from.value = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (name === "selected") {
|
|
188
|
+
if (isOptionElement(from) && !from.selected) {
|
|
189
|
+
if (!this.#options.preserveChanges || from.selected === from.defaultSelected) {
|
|
190
|
+
from.selected = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (name === "checked") {
|
|
195
|
+
if (isInputElement(from) && !from.checked) {
|
|
196
|
+
if (!this.#options.preserveChanges || from.checked === from.defaultChecked) {
|
|
197
|
+
from.checked = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const oldValue = from.getAttribute(name);
|
|
202
|
+
if (oldValue !== value && (this.#options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
|
|
203
|
+
from.setAttribute(name, value);
|
|
204
|
+
this.#options.afterAttributeUpdated?.(from, name, oldValue);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (const { name, value } of from.attributes) {
|
|
208
|
+
if (!to.hasAttribute(name)) {
|
|
209
|
+
if (name === "selected") {
|
|
210
|
+
if (isOptionElement(from) && from.selected) {
|
|
211
|
+
if (!this.#options.preserveChanges || from.selected === from.defaultSelected) {
|
|
212
|
+
from.selected = false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (name === "checked") {
|
|
217
|
+
if (isInputElement(from) && from.checked) {
|
|
218
|
+
if (!this.#options.preserveChanges || from.checked === from.defaultChecked) {
|
|
219
|
+
from.checked = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (this.#options.beforeAttributeUpdated?.(from, name, null) ?? true) {
|
|
224
|
+
from.removeAttribute(name);
|
|
225
|
+
this.#options.afterAttributeUpdated?.(from, name, value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
#visitTextArea(from, to) {
|
|
231
|
+
const newTextContent = to.textContent || "";
|
|
232
|
+
const isModified = from.value !== from.defaultValue;
|
|
233
|
+
if (from.textContent !== newTextContent) {
|
|
234
|
+
from.textContent = newTextContent;
|
|
235
|
+
}
|
|
236
|
+
if (this.#options.preserveChanges && isModified)
|
|
237
|
+
return;
|
|
238
|
+
from.value = from.defaultValue;
|
|
239
|
+
}
|
|
240
|
+
visitChildNodes(from, to) {
|
|
241
|
+
if (!(this.#options.beforeChildrenVisited?.(from) ?? true))
|
|
242
|
+
return;
|
|
243
|
+
const parent = from;
|
|
244
|
+
const fromChildNodes = [...from.childNodes];
|
|
245
|
+
const toChildNodes = [...to.childNodes];
|
|
246
|
+
candidateNodes.clear();
|
|
247
|
+
candidateElements.clear();
|
|
248
|
+
unmatchedNodes.clear();
|
|
249
|
+
unmatchedElements.clear();
|
|
250
|
+
whitespaceNodes.clear();
|
|
251
|
+
const matches = [];
|
|
252
|
+
const op = [];
|
|
253
|
+
const nodeTypeMap = [];
|
|
254
|
+
const candidateNodeTypeMap = [];
|
|
255
|
+
const localNameMap = [];
|
|
256
|
+
const candidateLocalNameMap = [];
|
|
257
|
+
for (let i = 0;i < fromChildNodes.length; i++) {
|
|
258
|
+
const candidate = fromChildNodes[i];
|
|
259
|
+
const nodeType = candidate.nodeType;
|
|
260
|
+
candidateNodeTypeMap[i] = nodeType;
|
|
261
|
+
if (nodeType === ELEMENT_NODE_TYPE) {
|
|
262
|
+
candidateLocalNameMap[i] = candidate.localName;
|
|
263
|
+
candidateElements.add(i);
|
|
264
|
+
} else if (nodeType === TEXT_NODE_TYPE && candidate.textContent?.trim() === "") {
|
|
265
|
+
whitespaceNodes.add(i);
|
|
266
|
+
} else {
|
|
267
|
+
candidateNodes.add(i);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (let i = 0;i < toChildNodes.length; i++) {
|
|
271
|
+
const node = toChildNodes[i];
|
|
272
|
+
const nodeType = node.nodeType;
|
|
273
|
+
nodeTypeMap[i] = nodeType;
|
|
274
|
+
if (nodeType === ELEMENT_NODE_TYPE) {
|
|
275
|
+
localNameMap[i] = node.localName;
|
|
276
|
+
unmatchedElements.add(i);
|
|
277
|
+
} else if (nodeType === TEXT_NODE_TYPE && node.textContent?.trim() === "") {
|
|
278
|
+
continue;
|
|
279
|
+
} else {
|
|
280
|
+
unmatchedNodes.add(i);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const unmatchedIndex of unmatchedElements) {
|
|
284
|
+
const localName = localNameMap[unmatchedIndex];
|
|
285
|
+
const element = toChildNodes[unmatchedIndex];
|
|
286
|
+
for (const candidateIndex of candidateElements) {
|
|
287
|
+
if (localName !== candidateLocalNameMap[candidateIndex])
|
|
288
|
+
continue;
|
|
289
|
+
const candidate = fromChildNodes[candidateIndex];
|
|
290
|
+
if (candidate.isEqualNode(element)) {
|
|
291
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
292
|
+
op[unmatchedIndex] = Operation.EqualNode;
|
|
293
|
+
candidateElements.delete(candidateIndex);
|
|
294
|
+
unmatchedElements.delete(unmatchedIndex);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const unmatchedIndex of unmatchedElements) {
|
|
300
|
+
const element = toChildNodes[unmatchedIndex];
|
|
301
|
+
const id = element.id;
|
|
302
|
+
const idArray = this.#idArrayMap.get(element);
|
|
303
|
+
if (id === "" && !idArray)
|
|
304
|
+
continue;
|
|
305
|
+
candidateLoop:
|
|
306
|
+
for (const candidateIndex of candidateElements) {
|
|
307
|
+
const candidate = fromChildNodes[candidateIndex];
|
|
308
|
+
if (localNameMap[unmatchedIndex] === candidateLocalNameMap[candidateIndex]) {
|
|
309
|
+
if (id !== "" && id === candidate.id) {
|
|
310
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
311
|
+
op[unmatchedIndex] = Operation.SameElement;
|
|
312
|
+
candidateElements.delete(candidateIndex);
|
|
313
|
+
unmatchedElements.delete(unmatchedIndex);
|
|
314
|
+
break candidateLoop;
|
|
315
|
+
}
|
|
316
|
+
if (idArray) {
|
|
317
|
+
const candidateIdSet = this.#idSetMap.get(candidate);
|
|
318
|
+
if (candidateIdSet) {
|
|
319
|
+
for (let i = 0;i < idArray.length; i++) {
|
|
320
|
+
const arrayId = idArray[i];
|
|
321
|
+
if (candidateIdSet.has(arrayId)) {
|
|
322
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
323
|
+
op[unmatchedIndex] = Operation.SameElement;
|
|
324
|
+
candidateElements.delete(candidateIndex);
|
|
325
|
+
unmatchedElements.delete(unmatchedIndex);
|
|
326
|
+
break candidateLoop;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const unmatchedIndex of unmatchedElements) {
|
|
335
|
+
const element = toChildNodes[unmatchedIndex];
|
|
336
|
+
const name = element.getAttribute("name");
|
|
337
|
+
const href = element.getAttribute("href");
|
|
338
|
+
const src = element.getAttribute("src");
|
|
339
|
+
for (const candidateIndex of candidateElements) {
|
|
340
|
+
const candidate = fromChildNodes[candidateIndex];
|
|
341
|
+
if (localNameMap[unmatchedIndex] === candidateLocalNameMap[candidateIndex] && (name && name === candidate.getAttribute("name") || href && href === candidate.getAttribute("href") || src && src === candidate.getAttribute("src"))) {
|
|
342
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
343
|
+
op[unmatchedIndex] = Operation.SameElement;
|
|
344
|
+
candidateElements.delete(candidateIndex);
|
|
345
|
+
unmatchedElements.delete(unmatchedIndex);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
for (const unmatchedIndex of unmatchedElements) {
|
|
351
|
+
const element = toChildNodes[unmatchedIndex];
|
|
352
|
+
const localName = localNameMap[unmatchedIndex];
|
|
353
|
+
for (const candidateIndex of candidateElements) {
|
|
354
|
+
const candidate = fromChildNodes[candidateIndex];
|
|
355
|
+
const candidateLocalName = candidateLocalNameMap[candidateIndex];
|
|
356
|
+
if (localName === candidateLocalName) {
|
|
357
|
+
if (localName === "input" && candidate.type !== element.type) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
361
|
+
op[unmatchedIndex] = Operation.SameElement;
|
|
362
|
+
candidateElements.delete(candidateIndex);
|
|
363
|
+
unmatchedElements.delete(unmatchedIndex);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
for (const unmatchedIndex of unmatchedNodes) {
|
|
369
|
+
const node = toChildNodes[unmatchedIndex];
|
|
370
|
+
for (const candidateIndex of candidateNodes) {
|
|
371
|
+
const candidate = fromChildNodes[candidateIndex];
|
|
372
|
+
if (candidate.isEqualNode(node)) {
|
|
373
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
374
|
+
op[unmatchedIndex] = Operation.EqualNode;
|
|
375
|
+
candidateNodes.delete(candidateIndex);
|
|
376
|
+
unmatchedNodes.delete(unmatchedIndex);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const unmatchedIndex of unmatchedNodes) {
|
|
382
|
+
const nodeType = nodeTypeMap[unmatchedIndex];
|
|
383
|
+
for (const candidateIndex of candidateNodes) {
|
|
384
|
+
if (nodeType === candidateNodeTypeMap[candidateIndex]) {
|
|
385
|
+
matches[unmatchedIndex] = candidateIndex;
|
|
386
|
+
op[unmatchedIndex] = Operation.SameNode;
|
|
387
|
+
candidateNodes.delete(candidateIndex);
|
|
388
|
+
unmatchedNodes.delete(unmatchedIndex);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const i of candidateNodes)
|
|
394
|
+
this.#removeNode(fromChildNodes[i]);
|
|
395
|
+
for (const i of whitespaceNodes)
|
|
396
|
+
this.#removeNode(fromChildNodes[i]);
|
|
397
|
+
for (const i of candidateElements)
|
|
398
|
+
this.#removeNode(fromChildNodes[i]);
|
|
399
|
+
const lisIndices = longestIncreasingSubsequence(matches);
|
|
400
|
+
const shouldNotMove = new Array(fromChildNodes.length);
|
|
401
|
+
for (let i = 0;i < lisIndices.length; i++) {
|
|
402
|
+
shouldNotMove[matches[lisIndices[i]]] = true;
|
|
403
|
+
}
|
|
404
|
+
let insertionPoint = parent.firstChild;
|
|
405
|
+
for (let i = 0;i < toChildNodes.length; i++) {
|
|
406
|
+
const node = toChildNodes[i];
|
|
407
|
+
const matchInd = matches[i];
|
|
408
|
+
if (matchInd !== undefined) {
|
|
409
|
+
const match = fromChildNodes[matchInd];
|
|
410
|
+
const operation = op[i];
|
|
411
|
+
if (!shouldNotMove[matchInd]) {
|
|
412
|
+
moveBefore(parent, match, insertionPoint);
|
|
413
|
+
}
|
|
414
|
+
if (operation === Operation.EqualNode) ; else if (operation === Operation.SameElement) {
|
|
415
|
+
this.#morphMatchingElements(match, node);
|
|
416
|
+
} else {
|
|
417
|
+
this.#morphOneToOne(match, node);
|
|
418
|
+
}
|
|
419
|
+
insertionPoint = match.nextSibling;
|
|
420
|
+
} else {
|
|
421
|
+
if (this.#options.beforeNodeAdded?.(parent, node, insertionPoint) ?? true) {
|
|
422
|
+
parent.insertBefore(node, insertionPoint);
|
|
423
|
+
this.#options.afterNodeAdded?.(node);
|
|
424
|
+
insertionPoint = node.nextSibling;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
this.#options.afterChildrenVisited?.(from);
|
|
429
|
+
}
|
|
430
|
+
#replaceNode(node, newNode) {
|
|
431
|
+
const parent = node.parentNode || document;
|
|
432
|
+
const insertionPoint = node;
|
|
433
|
+
if ((this.#options.beforeNodeRemoved?.(node) ?? true) && (this.#options.beforeNodeAdded?.(parent, newNode, insertionPoint) ?? true)) {
|
|
434
|
+
parent.insertBefore(newNode, insertionPoint);
|
|
435
|
+
this.#options.afterNodeAdded?.(newNode);
|
|
436
|
+
node.remove();
|
|
437
|
+
this.#options.afterNodeRemoved?.(node);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
#removeNode(node) {
|
|
441
|
+
if (this.#options.beforeNodeRemoved?.(node) ?? true) {
|
|
442
|
+
node.remove();
|
|
443
|
+
this.#options.afterNodeRemoved?.(node);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
#mapIdArraysForEach(nodeList) {
|
|
447
|
+
for (const childNode of nodeList) {
|
|
448
|
+
if (isParentNode(childNode)) {
|
|
449
|
+
this.#mapIdArrays(childNode);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
#mapIdArrays(node) {
|
|
454
|
+
const idArrayMap = this.#idArrayMap;
|
|
455
|
+
for (const element of node.querySelectorAll("[id]")) {
|
|
456
|
+
const id = element.id;
|
|
457
|
+
if (id === "")
|
|
458
|
+
continue;
|
|
459
|
+
let currentElement = element;
|
|
460
|
+
while (currentElement) {
|
|
461
|
+
const idArray = idArrayMap.get(currentElement);
|
|
462
|
+
if (idArray) {
|
|
463
|
+
idArray.push(id);
|
|
464
|
+
} else {
|
|
465
|
+
idArrayMap.set(currentElement, [id]);
|
|
466
|
+
}
|
|
467
|
+
if (currentElement === node)
|
|
468
|
+
break;
|
|
469
|
+
currentElement = currentElement.parentElement;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
#mapIdSets(node) {
|
|
474
|
+
const idSetMap = this.#idSetMap;
|
|
475
|
+
for (const element of node.querySelectorAll("[id]")) {
|
|
476
|
+
const id = element.id;
|
|
477
|
+
if (id === "")
|
|
478
|
+
continue;
|
|
479
|
+
let currentElement = element;
|
|
480
|
+
while (currentElement) {
|
|
481
|
+
const idSet = idSetMap.get(currentElement);
|
|
482
|
+
if (idSet) {
|
|
483
|
+
idSet.add(id);
|
|
484
|
+
} else {
|
|
485
|
+
idSetMap.set(currentElement, new Set([id]));
|
|
486
|
+
}
|
|
487
|
+
if (currentElement === node)
|
|
488
|
+
break;
|
|
489
|
+
currentElement = currentElement.parentElement;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function isInputElement(element) {
|
|
495
|
+
return element.localName === "input";
|
|
496
|
+
}
|
|
497
|
+
function isOptionElement(element) {
|
|
498
|
+
return element.localName === "option";
|
|
499
|
+
}
|
|
500
|
+
function isParentNode(node) {
|
|
501
|
+
return !!IS_PARENT_NODE_TYPE[node.nodeType];
|
|
502
|
+
}
|
|
503
|
+
function longestIncreasingSubsequence(sequence) {
|
|
504
|
+
const n = sequence.length;
|
|
505
|
+
if (n === 0)
|
|
506
|
+
return [];
|
|
507
|
+
const smallestEnding = [];
|
|
508
|
+
const indices = [];
|
|
509
|
+
const prev = new Array(n);
|
|
510
|
+
for (let i = 0;i < n; i++) {
|
|
511
|
+
const val = sequence[i];
|
|
512
|
+
if (val === undefined)
|
|
513
|
+
continue;
|
|
514
|
+
let left = 0;
|
|
515
|
+
let right = smallestEnding.length;
|
|
516
|
+
while (left < right) {
|
|
517
|
+
const mid = Math.floor((left + right) / 2);
|
|
518
|
+
if (smallestEnding[mid] < val)
|
|
519
|
+
left = mid + 1;
|
|
520
|
+
else
|
|
521
|
+
right = mid;
|
|
522
|
+
}
|
|
523
|
+
prev[i] = left > 0 ? indices[left - 1] : -1;
|
|
524
|
+
if (left === smallestEnding.length) {
|
|
525
|
+
smallestEnding.push(val);
|
|
526
|
+
indices.push(i);
|
|
527
|
+
} else {
|
|
528
|
+
smallestEnding[left] = val;
|
|
529
|
+
indices[left] = i;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const result = [];
|
|
533
|
+
if (indices.length === 0)
|
|
534
|
+
return result;
|
|
535
|
+
let curr = indices[indices.length - 1];
|
|
536
|
+
while (curr !== undefined && curr !== -1) {
|
|
537
|
+
result.unshift(curr);
|
|
538
|
+
curr = prev[curr];
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
//# debugId=744BF12CDB5DA17964756E2164756E21
|
|
544
|
+
|
|
545
|
+
const morphlexMorph = morph;
|
|
546
|
+
const morphlexMorphInner = morphInner;
|
|
547
|
+
function toMorphlexOptions(options) {
|
|
548
|
+
if (!options) {
|
|
549
|
+
return { preserveChanges: true };
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
preserveChanges: options.preserveChanges ?? true,
|
|
553
|
+
beforeNodeVisited: options.beforeNodeVisited,
|
|
554
|
+
afterNodeVisited: options.afterNodeVisited,
|
|
555
|
+
beforeNodeAdded: options.beforeNodeAdded
|
|
556
|
+
? (node) => options.beforeNodeAdded(node.parentNode, node, null)
|
|
557
|
+
: undefined,
|
|
558
|
+
afterNodeAdded: options.afterNodeAdded,
|
|
559
|
+
beforeNodeRemoved: options.beforeNodeRemoved,
|
|
560
|
+
afterNodeRemoved: options.afterNodeRemoved,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const morphlexEngine = {
|
|
564
|
+
morph(target, content, options) {
|
|
565
|
+
const morphlexOpts = toMorphlexOptions(options);
|
|
566
|
+
morphlexMorph(target, content, morphlexOpts);
|
|
567
|
+
},
|
|
568
|
+
morphInner(target, content, options) {
|
|
569
|
+
const morphlexOpts = toMorphlexOptions(options);
|
|
570
|
+
let contentEl;
|
|
571
|
+
if (typeof content === 'string') {
|
|
572
|
+
const wrapper = document.createElement(target.tagName);
|
|
573
|
+
wrapper.innerHTML = content;
|
|
574
|
+
contentEl = wrapper;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
contentEl = content;
|
|
578
|
+
}
|
|
579
|
+
morphlexMorphInner(target, contentEl, morphlexOpts);
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
let currentEngine = morphlexEngine;
|
|
583
|
+
const morphAdapter = {
|
|
584
|
+
morph(target, content, options) {
|
|
585
|
+
currentEngine.morph(target, content, options);
|
|
586
|
+
},
|
|
587
|
+
morphInner(target, content, options) {
|
|
588
|
+
currentEngine.morphInner(target, content, options);
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
function isViewTransitionsSupported() {
|
|
593
|
+
return (typeof document !== 'undefined' && typeof document.startViewTransition === 'function');
|
|
594
|
+
}
|
|
595
|
+
let config = {
|
|
596
|
+
enabled: isViewTransitionsSupported(),
|
|
597
|
+
defaultTimeout: 5000};
|
|
598
|
+
let transitionQueue = [];
|
|
599
|
+
let isProcessingQueue = false;
|
|
600
|
+
async function processQueue() {
|
|
601
|
+
if (isProcessingQueue || transitionQueue.length === 0) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
isProcessingQueue = true;
|
|
605
|
+
while (transitionQueue.length > 0) {
|
|
606
|
+
const item = transitionQueue.shift();
|
|
607
|
+
try {
|
|
608
|
+
await executeTransition(item.callback, item.options);
|
|
609
|
+
item.resolve();
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
item.reject(error);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
isProcessingQueue = false;
|
|
616
|
+
}
|
|
617
|
+
async function executeTransition(callback, options) {
|
|
618
|
+
const { skipTransition = false, timeout = config.defaultTimeout } = options;
|
|
619
|
+
if (!config.enabled || !isViewTransitionsSupported() || skipTransition) {
|
|
620
|
+
await callback();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const transition = document.startViewTransition(async () => {
|
|
624
|
+
await callback();
|
|
625
|
+
});
|
|
626
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
627
|
+
setTimeout(() => reject(new Error('View transition timed out')), timeout);
|
|
628
|
+
});
|
|
629
|
+
try {
|
|
630
|
+
await Promise.race([transition.finished, timeoutPromise]);
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function withViewTransition(callback, options = {}) {
|
|
636
|
+
return new Promise((resolve, reject) => {
|
|
637
|
+
transitionQueue.push({
|
|
638
|
+
callback,
|
|
639
|
+
options,
|
|
640
|
+
resolve,
|
|
641
|
+
reject,
|
|
642
|
+
});
|
|
643
|
+
processQueue().catch(reject);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function isHTMLElement(value) {
|
|
648
|
+
return (value !== null &&
|
|
649
|
+
typeof value === 'object' &&
|
|
650
|
+
'nodeType' in value &&
|
|
651
|
+
value.nodeType === 1 &&
|
|
652
|
+
'tagName' in value &&
|
|
653
|
+
typeof value.tagName === 'string' &&
|
|
654
|
+
'classList' in value);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function isDevMode() {
|
|
658
|
+
if (typeof window !== 'undefined' && window.__HYPERFIXI_PROD__) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
({
|
|
667
|
+
enabled: isDevMode()});
|
|
668
|
+
|
|
669
|
+
function executeSwap(target, content, strategy, morphOptions) {
|
|
670
|
+
const contentStr = content !== null && !isHTMLElement(content) ? content : '';
|
|
671
|
+
const contentEl = isHTMLElement(content) ? content : null;
|
|
672
|
+
switch (strategy) {
|
|
673
|
+
case 'morph':
|
|
674
|
+
if (content !== null) {
|
|
675
|
+
try {
|
|
676
|
+
morphAdapter.morphInner(target, contentEl || contentStr, morphOptions);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
console.warn('[HyperFixi] Morph failed, falling back to innerHTML:', error);
|
|
680
|
+
if (contentEl) {
|
|
681
|
+
target.innerHTML = '';
|
|
682
|
+
target.appendChild(contentEl);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
target.innerHTML = contentStr;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
break;
|
|
690
|
+
case 'morphOuter':
|
|
691
|
+
if (content !== null) {
|
|
692
|
+
try {
|
|
693
|
+
morphAdapter.morph(target, contentEl || contentStr, morphOptions);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
console.warn('[HyperFixi] Morph failed, falling back to outerHTML:', error);
|
|
697
|
+
if (contentEl) {
|
|
698
|
+
target.replaceWith(contentEl);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
target.outerHTML = contentStr;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
break;
|
|
706
|
+
case 'innerHTML':
|
|
707
|
+
if (contentEl) {
|
|
708
|
+
target.innerHTML = '';
|
|
709
|
+
target.appendChild(contentEl);
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
target.innerHTML = contentStr;
|
|
713
|
+
}
|
|
714
|
+
break;
|
|
715
|
+
case 'outerHTML':
|
|
716
|
+
if (contentEl) {
|
|
717
|
+
target.replaceWith(contentEl);
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
target.outerHTML = contentStr;
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
case 'beforeBegin':
|
|
724
|
+
if (contentEl) {
|
|
725
|
+
target.parentElement?.insertBefore(contentEl, target);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
target.insertAdjacentHTML('beforebegin', contentStr);
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
case 'afterBegin':
|
|
732
|
+
if (contentEl) {
|
|
733
|
+
target.insertBefore(contentEl, target.firstChild);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
target.insertAdjacentHTML('afterbegin', contentStr);
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
case 'beforeEnd':
|
|
740
|
+
if (contentEl) {
|
|
741
|
+
target.appendChild(contentEl);
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
target.insertAdjacentHTML('beforeend', contentStr);
|
|
745
|
+
}
|
|
746
|
+
break;
|
|
747
|
+
case 'afterEnd':
|
|
748
|
+
if (contentEl) {
|
|
749
|
+
target.parentElement?.insertBefore(contentEl, target.nextSibling);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
target.insertAdjacentHTML('afterend', contentStr);
|
|
753
|
+
}
|
|
754
|
+
break;
|
|
755
|
+
case 'delete':
|
|
756
|
+
target.remove();
|
|
757
|
+
break;
|
|
758
|
+
case 'none':
|
|
759
|
+
break;
|
|
760
|
+
default:
|
|
761
|
+
throw new Error(`[HyperFixi] swap: unknown strategy "${strategy}"`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function createCustomEvent(eventName, detail, options = {}) {
|
|
766
|
+
return new CustomEvent(eventName, {
|
|
767
|
+
detail: detail !== undefined ? detail : {},
|
|
768
|
+
bubbles: options.bubbles !== undefined ? options.bubbles : true,
|
|
769
|
+
cancelable: options.cancelable !== undefined ? options.cancelable : true,
|
|
770
|
+
composed: options.composed !== undefined ? options.composed : false,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
function dispatchCustomEvent(target, eventName, detail = {}, options = {}) {
|
|
774
|
+
const event = createCustomEvent(eventName, detail, options);
|
|
775
|
+
target.dispatchEvent(event);
|
|
776
|
+
return event;
|
|
777
|
+
}
|
|
778
|
+
function dispatchLokaScriptEvent(target, eventName, detail = {}, options = {}) {
|
|
779
|
+
const lokascriptEvent = dispatchCustomEvent(target, `lokascript:${eventName}`, detail, options);
|
|
780
|
+
const hyperfixiEvent = dispatchCustomEvent(target, `hyperfixi:${eventName}`, detail, options);
|
|
781
|
+
return {
|
|
782
|
+
lokascript: lokascriptEvent,
|
|
783
|
+
hyperfixi: hyperfixiEvent,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function createHistorySwap(config) {
|
|
788
|
+
const { target, strategy = 'morph', useViewTransition = false, fetchOptions = {}, transformUrl, onBeforeFetch, onAfterSwap, onError, } = config;
|
|
789
|
+
const resolveTarget = () => {
|
|
790
|
+
if (typeof target === 'string') {
|
|
791
|
+
const element = document.querySelector(target);
|
|
792
|
+
return isHTMLElement(element) ? element : null;
|
|
793
|
+
}
|
|
794
|
+
return isHTMLElement(target) ? target : null;
|
|
795
|
+
};
|
|
796
|
+
const handlePopstate = async (event) => {
|
|
797
|
+
const targetElement = resolveTarget();
|
|
798
|
+
if (!targetElement) {
|
|
799
|
+
console.warn(`HistorySwap: target "${target}" not found`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
let url = window.location.href;
|
|
803
|
+
if (transformUrl) {
|
|
804
|
+
url = transformUrl(url);
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
if (onBeforeFetch) {
|
|
808
|
+
await onBeforeFetch(url);
|
|
809
|
+
}
|
|
810
|
+
targetElement.classList.add('hx-swapping');
|
|
811
|
+
const response = await fetch(url, {
|
|
812
|
+
method: 'GET',
|
|
813
|
+
headers: {
|
|
814
|
+
Accept: 'text/html',
|
|
815
|
+
'HX-Request': 'true',
|
|
816
|
+
'HX-History-Restore-Request': 'true',
|
|
817
|
+
},
|
|
818
|
+
...fetchOptions,
|
|
819
|
+
});
|
|
820
|
+
if (!response.ok) {
|
|
821
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
822
|
+
}
|
|
823
|
+
const html = await response.text();
|
|
824
|
+
const performSwap = () => {
|
|
825
|
+
executeSwap(targetElement, html, strategy);
|
|
826
|
+
};
|
|
827
|
+
if (useViewTransition && isViewTransitionsSupported()) {
|
|
828
|
+
await withViewTransition(performSwap);
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
performSwap();
|
|
832
|
+
}
|
|
833
|
+
targetElement.classList.remove('hx-swapping');
|
|
834
|
+
if (onAfterSwap) {
|
|
835
|
+
await onAfterSwap(url, html);
|
|
836
|
+
}
|
|
837
|
+
dispatchLokaScriptEvent(window, 'historyswap', { url, strategy, target });
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
targetElement.classList.remove('hx-swapping');
|
|
841
|
+
if (onError) {
|
|
842
|
+
onError(error, url);
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
console.error('HistorySwap fetch failed:', error);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
window.addEventListener('popstate', handlePopstate);
|
|
850
|
+
return {
|
|
851
|
+
destroy: () => {
|
|
852
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
853
|
+
},
|
|
854
|
+
config,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const HistorySwapBehavior = {
|
|
858
|
+
name: 'HistorySwap',
|
|
859
|
+
init(element, params = {}) {
|
|
860
|
+
const config = {
|
|
861
|
+
target: params.target || element,
|
|
862
|
+
strategy: params.strategy || 'morph',
|
|
863
|
+
useViewTransition: Boolean(params.useViewTransition),
|
|
864
|
+
};
|
|
865
|
+
return createHistorySwap(config);
|
|
866
|
+
},
|
|
867
|
+
destroy(instance) {
|
|
868
|
+
instance.destroy();
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
function registerHistorySwap(registry) {
|
|
872
|
+
if (registry instanceof Map) {
|
|
873
|
+
registry.set('HistorySwap', HistorySwapBehavior);
|
|
874
|
+
}
|
|
875
|
+
else if (registry && typeof registry.set === 'function') {
|
|
876
|
+
registry.set('HistorySwap', HistorySwapBehavior);
|
|
877
|
+
}
|
|
878
|
+
else if (registry && typeof registry === 'object') {
|
|
879
|
+
registry['HistorySwap'] = HistorySwapBehavior;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const historySwapHyperscript = `
|
|
883
|
+
behavior HistorySwap(target, strategy)
|
|
884
|
+
init
|
|
885
|
+
if no target then set target to me end
|
|
886
|
+
if no strategy then set strategy to 'morph' end
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
on popstate from window
|
|
890
|
+
add .hx-swapping to target
|
|
891
|
+
fetch location.href as html
|
|
892
|
+
if strategy is 'morph' then
|
|
893
|
+
swap target with it
|
|
894
|
+
else if strategy is 'innerHTML' then
|
|
895
|
+
swap innerHTML of target with it
|
|
896
|
+
else
|
|
897
|
+
swap target with it
|
|
898
|
+
end
|
|
899
|
+
remove .hx-swapping from target
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
`;
|
|
903
|
+
|
|
904
|
+
function isExternalUrl(url) {
|
|
905
|
+
try {
|
|
906
|
+
const parsed = new URL(url, window.location.origin);
|
|
907
|
+
return parsed.origin !== window.location.origin;
|
|
908
|
+
}
|
|
909
|
+
catch {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function shouldBoostLink(link) {
|
|
915
|
+
if (isExternalUrl(link.href)) {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
if (link.target && link.target !== '_self') {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
if (link.hasAttribute('download')) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
if (link.hasAttribute('data-no-boost') || link.hasAttribute('hx-boost-off')) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
if (link.protocol === 'javascript:' || link.protocol === 'mailto:') {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
function shouldHandleClick(event) {
|
|
933
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
if (event.button !== 0) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
function createBoosted(config) {
|
|
942
|
+
const { container, target, linkSelector = 'a[href]', formSelector = 'form', boostForms = false, strategy = 'morph', pushUrl = true, useViewTransition = false, fetchOptions = {}, onBeforeFetch, onAfterSwap, onError, } = config;
|
|
943
|
+
const resolveTarget = () => {
|
|
944
|
+
if (!target) {
|
|
945
|
+
return document.body;
|
|
946
|
+
}
|
|
947
|
+
if (typeof target === 'string') {
|
|
948
|
+
const element = document.querySelector(target);
|
|
949
|
+
return isHTMLElement(element) ? element : null;
|
|
950
|
+
}
|
|
951
|
+
return isHTMLElement(target) ? target : null;
|
|
952
|
+
};
|
|
953
|
+
const boost = async (url, method = 'GET', body = null) => {
|
|
954
|
+
const targetElement = resolveTarget();
|
|
955
|
+
if (!targetElement) {
|
|
956
|
+
console.warn(`Boosted: target "${target}" not found`);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
if (onBeforeFetch) {
|
|
961
|
+
const result = await onBeforeFetch(url, method);
|
|
962
|
+
if (result === false) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
targetElement.classList.add('hx-swapping');
|
|
967
|
+
container.classList.add('hx-boosting');
|
|
968
|
+
const requestInit = {
|
|
969
|
+
method,
|
|
970
|
+
headers: {
|
|
971
|
+
Accept: 'text/html',
|
|
972
|
+
'HX-Request': 'true',
|
|
973
|
+
'HX-Boosted': 'true',
|
|
974
|
+
},
|
|
975
|
+
...fetchOptions,
|
|
976
|
+
};
|
|
977
|
+
if (body && method !== 'GET') {
|
|
978
|
+
requestInit.body = body;
|
|
979
|
+
}
|
|
980
|
+
const response = await fetch(url, requestInit);
|
|
981
|
+
if (!response.ok) {
|
|
982
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
983
|
+
}
|
|
984
|
+
const html = await response.text();
|
|
985
|
+
const performSwap = () => {
|
|
986
|
+
executeSwap(targetElement, html, strategy);
|
|
987
|
+
};
|
|
988
|
+
if (useViewTransition && isViewTransitionsSupported()) {
|
|
989
|
+
await withViewTransition(performSwap);
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
performSwap();
|
|
993
|
+
}
|
|
994
|
+
targetElement.classList.remove('hx-swapping');
|
|
995
|
+
container.classList.remove('hx-boosting');
|
|
996
|
+
if (pushUrl && method === 'GET') {
|
|
997
|
+
window.history.pushState(null, '', url);
|
|
998
|
+
}
|
|
999
|
+
if (onAfterSwap) {
|
|
1000
|
+
await onAfterSwap(url, html);
|
|
1001
|
+
}
|
|
1002
|
+
dispatchLokaScriptEvent(window, 'boosted', { url, method, strategy, target });
|
|
1003
|
+
}
|
|
1004
|
+
catch (error) {
|
|
1005
|
+
targetElement.classList.remove('hx-swapping');
|
|
1006
|
+
container.classList.remove('hx-boosting');
|
|
1007
|
+
if (onError) {
|
|
1008
|
+
onError(error, url);
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
console.error('Boosted fetch failed:', error);
|
|
1012
|
+
window.location.href = url;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
const handleClick = async (event) => {
|
|
1017
|
+
const link = event.target.closest(linkSelector);
|
|
1018
|
+
if (!link) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (!shouldHandleClick(event)) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (!shouldBoostLink(link)) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
event.preventDefault();
|
|
1028
|
+
event.stopPropagation();
|
|
1029
|
+
await boost(link.href);
|
|
1030
|
+
};
|
|
1031
|
+
const handleSubmit = async (event) => {
|
|
1032
|
+
if (!boostForms) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
const form = event.target.closest(formSelector);
|
|
1036
|
+
if (!form) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (form.hasAttribute('data-no-boost') || form.hasAttribute('hx-boost-off')) {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (form.target && form.target !== '_self') {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
event.preventDefault();
|
|
1046
|
+
event.stopPropagation();
|
|
1047
|
+
const method = (form.method || 'GET').toUpperCase();
|
|
1048
|
+
const formData = new FormData(form);
|
|
1049
|
+
let url;
|
|
1050
|
+
let body = null;
|
|
1051
|
+
if (method === 'GET') {
|
|
1052
|
+
const params = new URLSearchParams();
|
|
1053
|
+
formData.forEach((value, key) => {
|
|
1054
|
+
params.append(key, String(value));
|
|
1055
|
+
});
|
|
1056
|
+
url = `${form.action || window.location.pathname}?${params.toString()}`;
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
url = form.action || window.location.pathname;
|
|
1060
|
+
body = formData;
|
|
1061
|
+
}
|
|
1062
|
+
await boost(url, method, body);
|
|
1063
|
+
};
|
|
1064
|
+
container.addEventListener('click', handleClick);
|
|
1065
|
+
if (boostForms) {
|
|
1066
|
+
container.addEventListener('submit', handleSubmit);
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
destroy: () => {
|
|
1070
|
+
container.removeEventListener('click', handleClick);
|
|
1071
|
+
container.removeEventListener('submit', handleSubmit);
|
|
1072
|
+
},
|
|
1073
|
+
config,
|
|
1074
|
+
boost,
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
const BoostedBehavior = {
|
|
1078
|
+
name: 'Boosted',
|
|
1079
|
+
init(element, params = {}) {
|
|
1080
|
+
const config = {
|
|
1081
|
+
container: element,
|
|
1082
|
+
target: params.target,
|
|
1083
|
+
linkSelector: params.linkSelector || 'a[href]',
|
|
1084
|
+
formSelector: params.formSelector || 'form',
|
|
1085
|
+
boostForms: Boolean(params.boostForms),
|
|
1086
|
+
strategy: params.strategy || 'morph',
|
|
1087
|
+
pushUrl: params.pushUrl !== false,
|
|
1088
|
+
useViewTransition: Boolean(params.useViewTransition),
|
|
1089
|
+
};
|
|
1090
|
+
return createBoosted(config);
|
|
1091
|
+
},
|
|
1092
|
+
destroy(instance) {
|
|
1093
|
+
instance.destroy();
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1096
|
+
function registerBoosted(registry) {
|
|
1097
|
+
if (registry instanceof Map) {
|
|
1098
|
+
registry.set('Boosted', BoostedBehavior);
|
|
1099
|
+
}
|
|
1100
|
+
else if (registry && typeof registry.set === 'function') {
|
|
1101
|
+
registry.set('Boosted', BoostedBehavior);
|
|
1102
|
+
}
|
|
1103
|
+
else if (registry && typeof registry === 'object') {
|
|
1104
|
+
registry['Boosted'] = BoostedBehavior;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
const boostedHyperscript = `
|
|
1108
|
+
behavior Boosted(target, pushHistory)
|
|
1109
|
+
init
|
|
1110
|
+
if no target then set target to document.body end
|
|
1111
|
+
if no pushHistory then set pushHistory to true end
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
on click from <a[href]/> in me
|
|
1115
|
+
-- Skip if modifier keys pressed
|
|
1116
|
+
if event.metaKey or event.ctrlKey or event.shiftKey or event.altKey then exit end
|
|
1117
|
+
|
|
1118
|
+
-- Skip external links
|
|
1119
|
+
set href to the @href of the target
|
|
1120
|
+
if href starts with 'http' and not (href starts with location.origin) then exit end
|
|
1121
|
+
|
|
1122
|
+
-- Skip links with target attribute
|
|
1123
|
+
if the target has @target then exit end
|
|
1124
|
+
|
|
1125
|
+
halt the event
|
|
1126
|
+
add .hx-swapping to target
|
|
1127
|
+
fetch href as html
|
|
1128
|
+
swap target with it
|
|
1129
|
+
remove .hx-swapping from target
|
|
1130
|
+
if pushHistory then push url href end
|
|
1131
|
+
end
|
|
1132
|
+
end
|
|
1133
|
+
`;
|
|
1134
|
+
|
|
1135
|
+
function registerAllBehaviors(registry) {
|
|
1136
|
+
const { registerHistorySwap } = require('./history-swap');
|
|
1137
|
+
const { registerBoosted } = require('./boosted');
|
|
1138
|
+
registerHistorySwap(registry);
|
|
1139
|
+
registerBoosted(registry);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
exports.BoostedBehavior = BoostedBehavior;
|
|
1143
|
+
exports.HistorySwapBehavior = HistorySwapBehavior;
|
|
1144
|
+
exports.boostedHyperscript = boostedHyperscript;
|
|
1145
|
+
exports.createBoosted = createBoosted;
|
|
1146
|
+
exports.createHistorySwap = createHistorySwap;
|
|
1147
|
+
exports.historySwapHyperscript = historySwapHyperscript;
|
|
1148
|
+
exports.registerAllBehaviors = registerAllBehaviors;
|
|
1149
|
+
exports.registerBoosted = registerBoosted;
|
|
1150
|
+
exports.registerHistorySwap = registerHistorySwap;
|
|
1151
|
+
//# sourceMappingURL=index.js.map
|