htmx.org 4.0.0-alpha2 → 4.0.0-alpha3
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/htmx.esm.js +134 -239
- package/dist/htmx.esm.min.js +1 -1
- package/dist/htmx.esm.min.js.map +1 -1
- package/dist/htmx.js +134 -239
- package/dist/htmx.min.js +1 -1
- package/dist/htmx.min.js.map +1 -1
- package/package.json +1 -3
package/dist/htmx.js
CHANGED
|
@@ -90,7 +90,7 @@ var htmx = (() => {
|
|
|
90
90
|
|
|
91
91
|
#initHtmxConfig() {
|
|
92
92
|
this.config = {
|
|
93
|
-
version: '4.0.0-
|
|
93
|
+
version: '4.0.0-alpha3',
|
|
94
94
|
logAll: false,
|
|
95
95
|
prefix: "",
|
|
96
96
|
transitions: true,
|
|
@@ -103,7 +103,7 @@ var htmx = (() => {
|
|
|
103
103
|
includeIndicatorCSS: true,
|
|
104
104
|
defaultTimeout: 60000, /* 60 second default timeout */
|
|
105
105
|
extensions: '',
|
|
106
|
-
|
|
106
|
+
sse: {
|
|
107
107
|
mode: 'once',
|
|
108
108
|
maxRetries: Infinity,
|
|
109
109
|
initialDelay: 500,
|
|
@@ -116,7 +116,8 @@ var htmx = (() => {
|
|
|
116
116
|
}
|
|
117
117
|
let metaConfig = document.querySelector('meta[name="htmx:config"]');
|
|
118
118
|
if (metaConfig) {
|
|
119
|
-
let
|
|
119
|
+
let content = metaConfig.content;
|
|
120
|
+
let overrides = this.#parseConfig(content);
|
|
120
121
|
// Deep merge nested config objects
|
|
121
122
|
for (let key in overrides) {
|
|
122
123
|
let val = overrides[key];
|
|
@@ -215,62 +216,29 @@ var htmx = (() => {
|
|
|
215
216
|
return returnElt ? elt : defaultVal;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
#
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
s += str[i];
|
|
231
|
-
i++;
|
|
232
|
-
} else if (c === q) break;
|
|
233
|
-
}
|
|
234
|
-
tokens.push(s);
|
|
235
|
-
} else if (/\s/.test(c)) {
|
|
236
|
-
while (i < str.length && /\s/.test(str[i])) i++;
|
|
237
|
-
} else if (c === ':' || c === ',') {
|
|
238
|
-
tokens.push(c);
|
|
239
|
-
i++;
|
|
240
|
-
} else {
|
|
241
|
-
let t = '';
|
|
242
|
-
while (i < str.length && !/[\s"':,]/.test(str[i])) t += str[i++];
|
|
243
|
-
tokens.push(t);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return tokens;
|
|
219
|
+
#parseConfig(configString) {
|
|
220
|
+
if (configString[0] === '{') return JSON.parse(configString);
|
|
221
|
+
let configPattern = /([^\s,]+?)(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
|
222
|
+
return [...configString.matchAll(configPattern)].reduce((result, match) => {
|
|
223
|
+
let keyPath = match[1].split('.');
|
|
224
|
+
let value = (match[2] ?? match[3] ?? match[4] ?? match[5] ?? 'true').trim();
|
|
225
|
+
if (value === 'true') value = true;
|
|
226
|
+
else if (value === 'false') value = false;
|
|
227
|
+
else if (/^\d+$/.test(value)) value = parseInt(value);
|
|
228
|
+
keyPath.slice(0, -1).reduce((obj, key) => obj[key] ??= {}, result)[keyPath.at(-1)] = value;
|
|
229
|
+
return result;
|
|
230
|
+
}, {});
|
|
247
231
|
}
|
|
248
232
|
|
|
249
233
|
#parseTriggerSpecs(spec) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
let
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
while (token.includes("[") && !token.includes("]") && i + 1 < tokens.length) {
|
|
259
|
-
token += tokens[++i];
|
|
260
|
-
}
|
|
261
|
-
if (token.includes("[") && !token.includes("]")) {
|
|
262
|
-
throw "unterminated:" + token;
|
|
263
|
-
}
|
|
264
|
-
currentSpec = {name: token};
|
|
265
|
-
specs.push(currentSpec);
|
|
266
|
-
} else if (tokens[i + 1] === ":") {
|
|
267
|
-
currentSpec[token] = tokens[i += 2];
|
|
268
|
-
} else {
|
|
269
|
-
currentSpec[token] = true;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return specs;
|
|
234
|
+
return spec.split(',').map(s => {
|
|
235
|
+
let m = s.match(/^\s*(\S+\[[^\]]*\]|\S+)\s*(.*?)\s*$/);
|
|
236
|
+
if (!m || !m[1]) return null;
|
|
237
|
+
if (m[1].includes('[') && !m[1].includes(']')) throw "unterminated:" + m[1];
|
|
238
|
+
let result = m[2] ? this.#parseConfig(m[2]) : {};
|
|
239
|
+
result.name = m[1];
|
|
240
|
+
return result;
|
|
241
|
+
}).filter(s => s);
|
|
274
242
|
}
|
|
275
243
|
|
|
276
244
|
#determineMethodAndAction(elt, evt) {
|
|
@@ -309,7 +277,6 @@ var htmx = (() => {
|
|
|
309
277
|
elt._htmx = {eventHandler: this.#createHtmxEventHandler(elt)}
|
|
310
278
|
elt.setAttribute('data-htmx-powered', 'true');
|
|
311
279
|
this.#initializeTriggers(elt);
|
|
312
|
-
this.#initializeStreamConfig(elt);
|
|
313
280
|
this.#initializeAbortListener(elt)
|
|
314
281
|
this.#trigger(elt, "htmx:after:init", {}, true)
|
|
315
282
|
this.#trigger(elt, "load", {}, false)
|
|
@@ -335,11 +302,12 @@ var htmx = (() => {
|
|
|
335
302
|
status: "created",
|
|
336
303
|
select: this.#attributeValue(sourceElement, "hx-select"),
|
|
337
304
|
selectOOB: this.#attributeValue(sourceElement, "hx-select-oob"),
|
|
338
|
-
target: this.#attributeValue(sourceElement, "hx-target"),
|
|
305
|
+
target: this.#resolveTarget(sourceElement, this.#attributeValue(sourceElement, "hx-target")),
|
|
339
306
|
swap: this.#attributeValue(sourceElement, "hx-swap", this.config.defaultSwap),
|
|
340
307
|
push: this.#attributeValue(sourceElement, "hx-push-url"),
|
|
341
308
|
replace: this.#attributeValue(sourceElement, "hx-replace-url"),
|
|
342
309
|
transition: this.config.transitions,
|
|
310
|
+
confirm: this.#attributeValue(sourceElement, "hx-confirm"),
|
|
343
311
|
request: {
|
|
344
312
|
validate: "true" === this.#attributeValue(sourceElement, "hx-validate", sourceElement.matches('form') ? "true" : "false"),
|
|
345
313
|
action,
|
|
@@ -351,23 +319,22 @@ var htmx = (() => {
|
|
|
351
319
|
// Apply hx-config overrides
|
|
352
320
|
let configAttr = this.#attributeValue(sourceElement, "hx-config");
|
|
353
321
|
if (configAttr) {
|
|
354
|
-
let configOverrides =
|
|
355
|
-
let
|
|
322
|
+
let configOverrides = this.#parseConfig(configAttr);
|
|
323
|
+
let req = ctx.request;
|
|
356
324
|
for (let key in configOverrides) {
|
|
357
325
|
if (key.startsWith('+')) {
|
|
358
326
|
let actualKey = key.substring(1);
|
|
359
|
-
if (
|
|
360
|
-
Object.assign(
|
|
327
|
+
if (req[actualKey] && typeof req[actualKey] === 'object') {
|
|
328
|
+
Object.assign(req[actualKey], configOverrides[key]);
|
|
361
329
|
} else {
|
|
362
|
-
|
|
330
|
+
req[actualKey] = configOverrides[key];
|
|
363
331
|
}
|
|
364
332
|
} else {
|
|
365
|
-
|
|
333
|
+
req[key] = configOverrides[key];
|
|
366
334
|
}
|
|
367
335
|
}
|
|
368
|
-
if (
|
|
369
|
-
sourceElement._htmx ||= {}
|
|
370
|
-
sourceElement._htmx.etag ||= requestConfig.etag
|
|
336
|
+
if (req.etag) {
|
|
337
|
+
(sourceElement._htmx ||= {}).etag ||= req.etag
|
|
371
338
|
}
|
|
372
339
|
}
|
|
373
340
|
if (sourceElement._htmx?.etag) {
|
|
@@ -379,6 +346,8 @@ var htmx = (() => {
|
|
|
379
346
|
#determineHeaders(elt) {
|
|
380
347
|
let headers = {
|
|
381
348
|
"HX-Request": "true",
|
|
349
|
+
"HX-Source": elt.id || elt.name,
|
|
350
|
+
"HX-Current-URL": location.href,
|
|
382
351
|
"Accept": "text/html, text/event-stream"
|
|
383
352
|
};
|
|
384
353
|
if (this.#isBoosted(elt)) {
|
|
@@ -386,7 +355,7 @@ var htmx = (() => {
|
|
|
386
355
|
}
|
|
387
356
|
let headersAttribute = this.#attributeValue(elt, "hx-headers");
|
|
388
357
|
if (headersAttribute) {
|
|
389
|
-
Object.assign(headers,
|
|
358
|
+
Object.assign(headers, this.#parseConfig(headersAttribute));
|
|
390
359
|
}
|
|
391
360
|
return headers;
|
|
392
361
|
}
|
|
@@ -418,13 +387,11 @@ var htmx = (() => {
|
|
|
418
387
|
|
|
419
388
|
if (this.#shouldCancel(evt)) evt.preventDefault()
|
|
420
389
|
|
|
421
|
-
// Resolve swap target
|
|
422
|
-
ctx.target = this.#resolveTarget(elt, ctx.target);
|
|
423
|
-
|
|
424
390
|
// Build request body
|
|
425
391
|
let form = elt.form || elt.closest("form")
|
|
426
392
|
let body = this.#collectFormData(elt, form, evt.submitter)
|
|
427
|
-
this.#handleHxVals(elt, body)
|
|
393
|
+
let valsResult = this.#handleHxVals(elt, body)
|
|
394
|
+
if (valsResult) await valsResult // Only await if it returned a promise
|
|
428
395
|
if (ctx.values) {
|
|
429
396
|
for (let k in ctx.values) {
|
|
430
397
|
body.delete(k);
|
|
@@ -484,21 +451,19 @@ var htmx = (() => {
|
|
|
484
451
|
let disableElements = this.#disableElements(elt, disableSelector);
|
|
485
452
|
|
|
486
453
|
try {
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
let
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
} else {
|
|
496
|
-
if (!window.confirm(confirmVal)) {
|
|
497
|
-
return;
|
|
454
|
+
// Handle confirmation
|
|
455
|
+
if (ctx.confirm) {
|
|
456
|
+
let issueRequest = null;
|
|
457
|
+
let confirmed = await new Promise(resolve => {
|
|
458
|
+
issueRequest = resolve;
|
|
459
|
+
if (this.#trigger(elt, "htmx:confirm", {ctx, issueRequest: (skip) => issueRequest?.(skip !== false)})) {
|
|
460
|
+
let js = this.#extractJavascriptContent(ctx.confirm);
|
|
461
|
+
resolve(js ? this.#executeJavaScriptAsync(elt, {}, js, true) : window.confirm(ctx.confirm));
|
|
498
462
|
}
|
|
499
|
-
}
|
|
463
|
+
});
|
|
464
|
+
if (!confirmed) return;
|
|
500
465
|
}
|
|
501
|
-
|
|
466
|
+
|
|
502
467
|
ctx.fetch ||= window.fetch.bind(window)
|
|
503
468
|
if (!this.#trigger(elt, "htmx:before:request", {ctx})) return;
|
|
504
469
|
|
|
@@ -527,7 +492,7 @@ var htmx = (() => {
|
|
|
527
492
|
} else {
|
|
528
493
|
// HTTP response
|
|
529
494
|
if (ctx.status === "issuing") {
|
|
530
|
-
if (ctx.hx.retarget) ctx.target =
|
|
495
|
+
if (ctx.hx.retarget) ctx.target = ctx.hx.retarget;
|
|
531
496
|
if (ctx.hx.reswap) ctx.swap = ctx.hx.reswap;
|
|
532
497
|
if (ctx.hx.reselect) ctx.select = ctx.hx.reselect;
|
|
533
498
|
ctx.status = "response received";
|
|
@@ -578,8 +543,8 @@ var htmx = (() => {
|
|
|
578
543
|
}
|
|
579
544
|
if (ctx.hx.location) {
|
|
580
545
|
let path = ctx.hx.location, opts = {};
|
|
581
|
-
if (path[0] === '{') {
|
|
582
|
-
opts =
|
|
546
|
+
if (path[0] === '{' || /[\s,]/.test(path)) {
|
|
547
|
+
opts = this.#parseConfig(path);
|
|
583
548
|
path = opts.path;
|
|
584
549
|
delete opts.path;
|
|
585
550
|
}
|
|
@@ -594,7 +559,9 @@ var htmx = (() => {
|
|
|
594
559
|
}
|
|
595
560
|
|
|
596
561
|
async #handleSSE(ctx, elt, response) {
|
|
597
|
-
let config =
|
|
562
|
+
let config = {...this.config.sse, ...ctx.request.sse};
|
|
563
|
+
if (config.once) config.mode = 'once';
|
|
564
|
+
if (config.continuous) config.mode = 'continuous';
|
|
598
565
|
|
|
599
566
|
let waitForVisible = () => new Promise(r => {
|
|
600
567
|
let onVisible = () => !document.hidden && (document.removeEventListener('visibilitychange', onVisible), r());
|
|
@@ -613,7 +580,7 @@ var htmx = (() => {
|
|
|
613
580
|
if (!elt.isConnected) break;
|
|
614
581
|
}
|
|
615
582
|
|
|
616
|
-
let delay = Math.min(config.initialDelay * Math.pow(2, attempt - 1), config.maxDelay);
|
|
583
|
+
let delay = Math.min(this.parseInterval(config.initialDelay) * Math.pow(2, attempt - 1), this.parseInterval(config.maxDelay));
|
|
617
584
|
let reconnect = {attempt, delay, lastEventId, cancelled: false};
|
|
618
585
|
|
|
619
586
|
ctx.status = "reconnecting to stream";
|
|
@@ -744,7 +711,7 @@ var htmx = (() => {
|
|
|
744
711
|
#initTimeout(ctx) {
|
|
745
712
|
let timeoutInterval;
|
|
746
713
|
if (ctx.request.timeout) {
|
|
747
|
-
timeoutInterval =
|
|
714
|
+
timeoutInterval = this.parseInterval(ctx.request.timeout);
|
|
748
715
|
} else {
|
|
749
716
|
timeoutInterval = this.config.defaultTimeout;
|
|
750
717
|
}
|
|
@@ -943,35 +910,7 @@ var htmx = (() => {
|
|
|
943
910
|
}
|
|
944
911
|
}
|
|
945
912
|
|
|
946
|
-
#initializeStreamConfig(elt) {
|
|
947
|
-
let streamSpec = this.#attributeValue(elt, 'hx-stream');
|
|
948
|
-
if (!streamSpec) return;
|
|
949
|
-
|
|
950
|
-
// Start with global defaults
|
|
951
|
-
let streamConfig = {...this.config.streams};
|
|
952
|
-
let tokens = this.#tokenize(streamSpec);
|
|
953
|
-
|
|
954
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
955
|
-
let token = tokens[i];
|
|
956
|
-
// Main value: once or continuous
|
|
957
|
-
if (token === 'once' || token === 'continuous') {
|
|
958
|
-
streamConfig.mode = token;
|
|
959
|
-
} else if (token === 'pauseHidden') {
|
|
960
|
-
streamConfig.pauseHidden = true;
|
|
961
|
-
} else if (tokens[i + 1] === ':') {
|
|
962
|
-
let key = token, value = tokens[i + 2];
|
|
963
|
-
if (key === 'mode') streamConfig.mode = value;
|
|
964
|
-
else if (key === 'maxRetries') streamConfig.maxRetries = parseInt(value);
|
|
965
|
-
else if (key === 'initialDelay') streamConfig.initialDelay = this.parseInterval(value);
|
|
966
|
-
else if (key === 'maxDelay') streamConfig.maxDelay = this.parseInterval(value);
|
|
967
|
-
else if (key === 'pauseHidden') streamConfig.pauseHidden = value === 'true';
|
|
968
|
-
i += 2;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
913
|
|
|
972
|
-
if (!elt._htmx) elt._htmx = {};
|
|
973
|
-
elt._htmx.streamConfig = streamConfig;
|
|
974
|
-
}
|
|
975
914
|
|
|
976
915
|
#extractFilter(str) {
|
|
977
916
|
let match = str.match(/^([^\[]*)\[([^\]]*)]/);
|
|
@@ -981,7 +920,7 @@ var htmx = (() => {
|
|
|
981
920
|
|
|
982
921
|
#handleTriggerHeader(value, elt) {
|
|
983
922
|
if (value[0] === '{') {
|
|
984
|
-
let triggers =
|
|
923
|
+
let triggers = this.#parseConfig(value);
|
|
985
924
|
for (let name in triggers) {
|
|
986
925
|
let detail = triggers[name];
|
|
987
926
|
if (detail?.target) elt = this.find(detail.target) || elt;
|
|
@@ -1150,7 +1089,7 @@ var htmx = (() => {
|
|
|
1150
1089
|
}
|
|
1151
1090
|
|
|
1152
1091
|
#makeFragment(text) {
|
|
1153
|
-
let response = text.replace(/<hx-
|
|
1092
|
+
let response = text.replace(/<hx-([a-z]+)(\s+|>)/gi, '<template hx type="$1"$2').replace(/<\/hx-[a-z]+>/gi, '</template>');
|
|
1154
1093
|
let title = '';
|
|
1155
1094
|
response = response.replace(/<title[^>]*>[\s\S]*?<\/title>/i, m => (title = this.#parseHTML(m).title, ''));
|
|
1156
1095
|
let responseWithNoHead = response.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i, '');
|
|
@@ -1223,52 +1162,36 @@ var htmx = (() => {
|
|
|
1223
1162
|
}
|
|
1224
1163
|
|
|
1225
1164
|
#parseSwapSpec(swapStr) {
|
|
1226
|
-
|
|
1227
|
-
let
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
if (tokens[i + 1] === ':') {
|
|
1233
|
-
let key = tokens[i], value = tokens[i = i + 2];
|
|
1234
|
-
if (key === 'swap') config.swapDelay = this.parseInterval(value);
|
|
1235
|
-
else if (key === 'transition' || key === 'ignoreTitle' || key === 'strip') config[key] = value === 'true';
|
|
1236
|
-
else if (key === 'focus-scroll') config.focusScroll = value === 'true';
|
|
1237
|
-
else if (key === 'scroll' || key === 'show') {
|
|
1238
|
-
let parts = [value];
|
|
1239
|
-
while (tokens[i + 1] === ':') {
|
|
1240
|
-
parts.push(tokens[i + 2]);
|
|
1241
|
-
i += 2;
|
|
1242
|
-
}
|
|
1243
|
-
config[key] = parts.length === 1 ? parts[0] : parts.pop();
|
|
1244
|
-
if (parts.length > 1) config[key + 'Target'] = parts.join(':');
|
|
1245
|
-
} else if (key === 'target') {
|
|
1246
|
-
let parts = [value];
|
|
1247
|
-
while (i + 1 < tokens.length && tokens[i + 1] !== ':' && tokens[i + 2] !== ':') {
|
|
1248
|
-
parts.push(tokens[i + 1]);
|
|
1249
|
-
i++;
|
|
1250
|
-
}
|
|
1251
|
-
config[key] = parts.join(' ');
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1165
|
+
swapStr = swapStr.trim();
|
|
1166
|
+
let style = this.config.defaultSwap
|
|
1167
|
+
if (swapStr && !/^\S*:/.test(swapStr)) {
|
|
1168
|
+
let m = swapStr.match(/^(\S+)\s*(.*)$/);
|
|
1169
|
+
style = m[1];
|
|
1170
|
+
swapStr = m[2];
|
|
1254
1171
|
}
|
|
1255
|
-
return
|
|
1172
|
+
return {style: this.#normalizeSwapStyle(style), ...this.#parseConfig(swapStr)};
|
|
1256
1173
|
}
|
|
1257
1174
|
|
|
1258
|
-
#processPartials(fragment,
|
|
1175
|
+
#processPartials(fragment, ctx) {
|
|
1259
1176
|
let tasks = [];
|
|
1260
1177
|
|
|
1261
|
-
for (let
|
|
1262
|
-
let
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1178
|
+
for (let templateElt of fragment.querySelectorAll('template[hx]')) {
|
|
1179
|
+
let type = templateElt.getAttribute('type');
|
|
1180
|
+
|
|
1181
|
+
if (type === 'partial') {
|
|
1182
|
+
let swapSpec = this.#parseSwapSpec(templateElt.getAttribute(this.#prefix('hx-swap')) || this.config.defaultSwap);
|
|
1183
|
+
|
|
1184
|
+
tasks.push({
|
|
1185
|
+
type: 'partial',
|
|
1186
|
+
fragment: templateElt.content.cloneNode(true),
|
|
1187
|
+
target: templateElt.getAttribute(this.#prefix('hx-target')),
|
|
1188
|
+
swapSpec,
|
|
1189
|
+
sourceElement: ctx.sourceElement
|
|
1190
|
+
});
|
|
1191
|
+
} else {
|
|
1192
|
+
this.#triggerExtensions(templateElt, 'htmx:process:' + type, { ctx, tasks });
|
|
1193
|
+
}
|
|
1194
|
+
templateElt.remove();
|
|
1272
1195
|
}
|
|
1273
1196
|
|
|
1274
1197
|
return tasks;
|
|
@@ -1281,30 +1204,16 @@ var htmx = (() => {
|
|
|
1281
1204
|
|
|
1282
1205
|
#handleScroll(task) {
|
|
1283
1206
|
if (task.swapSpec.scroll) {
|
|
1284
|
-
let target;
|
|
1285
|
-
|
|
1286
|
-
if (value) {
|
|
1287
|
-
target = this.#findExt(selectorOrValue);
|
|
1288
|
-
} else {
|
|
1289
|
-
target = task.target;
|
|
1290
|
-
value = selectorOrValue
|
|
1291
|
-
}
|
|
1292
|
-
if (value === 'top') {
|
|
1207
|
+
let target = task.swapSpec.scrollTarget ? this.#findExt(task.swapSpec.scrollTarget) : task.target;
|
|
1208
|
+
if (task.swapSpec.scroll === 'top') {
|
|
1293
1209
|
target.scrollTop = 0;
|
|
1294
|
-
} else if (
|
|
1210
|
+
} else if (task.swapSpec.scroll === 'bottom'){
|
|
1295
1211
|
target.scrollTop = target.scrollHeight;
|
|
1296
1212
|
}
|
|
1297
1213
|
}
|
|
1298
1214
|
if (task.swapSpec.show) {
|
|
1299
|
-
let target;
|
|
1300
|
-
|
|
1301
|
-
if (value) {
|
|
1302
|
-
target = this.#findExt(selectorOrValue);
|
|
1303
|
-
} else {
|
|
1304
|
-
target = task.target;
|
|
1305
|
-
value = selectorOrValue
|
|
1306
|
-
}
|
|
1307
|
-
target.scrollIntoView(value === 'top')
|
|
1215
|
+
let target = task.swapSpec.showTarget ? this.#findExt(task.swapSpec.showTarget) : task.target;
|
|
1216
|
+
target.scrollIntoView(task.swapSpec.show === 'top')
|
|
1308
1217
|
}
|
|
1309
1218
|
}
|
|
1310
1219
|
|
|
@@ -1342,7 +1251,7 @@ var htmx = (() => {
|
|
|
1342
1251
|
|
|
1343
1252
|
// Process OOB and partials
|
|
1344
1253
|
let oobTasks = this.#processOOB(fragment, ctx.sourceElement, ctx.selectOOB);
|
|
1345
|
-
let partialTasks = this.#processPartials(fragment, ctx
|
|
1254
|
+
let partialTasks = this.#processPartials(fragment, ctx);
|
|
1346
1255
|
tasks.push(...oobTasks, ...partialTasks);
|
|
1347
1256
|
|
|
1348
1257
|
// Process main swap
|
|
@@ -1364,8 +1273,8 @@ var htmx = (() => {
|
|
|
1364
1273
|
|
|
1365
1274
|
// insert non-transition tasks immediately or with delay
|
|
1366
1275
|
for (let task of nonTransitionTasks) {
|
|
1367
|
-
if (task.swapSpec?.
|
|
1368
|
-
setTimeout(() => this.#insertContent(task), task.swapSpec.
|
|
1276
|
+
if (task.swapSpec?.swap) {
|
|
1277
|
+
setTimeout(() => this.#insertContent(task), this.parseInterval(task.swapSpec.swap));
|
|
1369
1278
|
} else {
|
|
1370
1279
|
this.#insertContent(task)
|
|
1371
1280
|
}
|
|
@@ -1412,7 +1321,7 @@ var htmx = (() => {
|
|
|
1412
1321
|
let mainSwap = {
|
|
1413
1322
|
type: 'main',
|
|
1414
1323
|
fragment,
|
|
1415
|
-
target: swapSpec.target || ctx.target,
|
|
1324
|
+
target: this.#resolveTarget(ctx.sourceElement || document.body, swapSpec.target || ctx.target),
|
|
1416
1325
|
swapSpec,
|
|
1417
1326
|
sourceElement: ctx.sourceElement,
|
|
1418
1327
|
transition: (ctx.transition !== false) && (swapSpec.transition !== false)
|
|
@@ -1511,9 +1420,7 @@ var htmx = (() => {
|
|
|
1511
1420
|
}
|
|
1512
1421
|
|
|
1513
1422
|
timeout(time) {
|
|
1514
|
-
|
|
1515
|
-
time = this.parseInterval(time)
|
|
1516
|
-
}
|
|
1423
|
+
time = this.parseInterval(time);
|
|
1517
1424
|
if (time > 0) {
|
|
1518
1425
|
return new Promise(resolve => setTimeout(resolve, time));
|
|
1519
1426
|
}
|
|
@@ -1571,6 +1478,7 @@ var htmx = (() => {
|
|
|
1571
1478
|
}
|
|
1572
1479
|
|
|
1573
1480
|
parseInterval(str) {
|
|
1481
|
+
if (typeof str === 'number') return str;
|
|
1574
1482
|
let m = {ms: 1, s: 1000, m: 60000};
|
|
1575
1483
|
let [, n, u] = str?.match(/^([\d.]+)(ms|s|m)?$/) || [];
|
|
1576
1484
|
let v = parseFloat(n) * (m[u] || 1);
|
|
@@ -1600,20 +1508,21 @@ var htmx = (() => {
|
|
|
1600
1508
|
let sourceElt = typeof context.source === 'string' ?
|
|
1601
1509
|
document.querySelector(context.source) : context.source;
|
|
1602
1510
|
|
|
1603
|
-
//
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1511
|
+
// If source selector was provided but didn't match, reject
|
|
1512
|
+
if (typeof context.source === 'string' && !sourceElt) {
|
|
1513
|
+
return Promise.reject(new Error('Source not found'));
|
|
1514
|
+
}
|
|
1607
1515
|
|
|
1608
|
-
if
|
|
1516
|
+
// Resolve target, defaulting to body only if no source or target provided
|
|
1517
|
+
let target = this.#resolveTarget(document.body, context.target || sourceElt);
|
|
1518
|
+
if (!target) {
|
|
1609
1519
|
return Promise.reject(new Error('Target not found'));
|
|
1610
1520
|
}
|
|
1611
1521
|
|
|
1612
|
-
|
|
1613
|
-
sourceElt ||= targetElt || document.body;
|
|
1522
|
+
sourceElt ||= target;
|
|
1614
1523
|
|
|
1615
1524
|
let ctx = this.#createRequestContext(sourceElt, context.event || {});
|
|
1616
|
-
Object.assign(ctx, context, {target
|
|
1525
|
+
Object.assign(ctx, context, {target});
|
|
1617
1526
|
Object.assign(ctx.request, {action: path, method: verb.toUpperCase()});
|
|
1618
1527
|
if (context.headers) Object.assign(ctx.request.headers, context.headers);
|
|
1619
1528
|
|
|
@@ -1676,7 +1585,7 @@ var htmx = (() => {
|
|
|
1676
1585
|
}
|
|
1677
1586
|
|
|
1678
1587
|
let path = push || replace;
|
|
1679
|
-
if (!path || path === 'false') return;
|
|
1588
|
+
if (!path || path === 'false' || path === false) return;
|
|
1680
1589
|
|
|
1681
1590
|
if (path === 'true') {
|
|
1682
1591
|
path = ctx.request.originalAction;
|
|
@@ -1766,11 +1675,9 @@ var htmx = (() => {
|
|
|
1766
1675
|
}
|
|
1767
1676
|
|
|
1768
1677
|
#collectFormData(elt, form, submitter) {
|
|
1769
|
-
let formData = new FormData()
|
|
1770
|
-
let included = new Set()
|
|
1771
|
-
if (form) {
|
|
1772
|
-
this.#addInputValues(form, included, formData)
|
|
1773
|
-
} else if (elt.name) {
|
|
1678
|
+
let formData = form ? new FormData(form) : new FormData()
|
|
1679
|
+
let included = form ? new Set(form.elements) : new Set()
|
|
1680
|
+
if (!form && elt.name) {
|
|
1774
1681
|
formData.append(elt.name, elt.value)
|
|
1775
1682
|
included.add(elt);
|
|
1776
1683
|
}
|
|
@@ -1792,21 +1699,21 @@ var htmx = (() => {
|
|
|
1792
1699
|
let inputs = this.#queryEltAndDescendants(elt, 'input:not([disabled]), select:not([disabled]), textarea:not([disabled])');
|
|
1793
1700
|
|
|
1794
1701
|
for (let input of inputs) {
|
|
1795
|
-
// Skip elements without a name or already seen
|
|
1796
1702
|
if (!input.name || included.has(input)) continue;
|
|
1797
1703
|
included.add(input);
|
|
1798
1704
|
|
|
1799
|
-
|
|
1705
|
+
let type = input.type;
|
|
1706
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
1800
1707
|
// Only add if checked
|
|
1801
1708
|
if (input.checked) {
|
|
1802
1709
|
formData.append(input.name, input.value);
|
|
1803
1710
|
}
|
|
1804
|
-
} else if (
|
|
1711
|
+
} else if (type === 'file') {
|
|
1805
1712
|
// Add all selected files
|
|
1806
1713
|
for (let file of input.files) {
|
|
1807
1714
|
formData.append(input.name, file);
|
|
1808
1715
|
}
|
|
1809
|
-
} else if (
|
|
1716
|
+
} else if (type === 'select-multiple') {
|
|
1810
1717
|
// Add all selected options
|
|
1811
1718
|
for (let option of input.selectedOptions) {
|
|
1812
1719
|
formData.append(input.name, option.value);
|
|
@@ -1821,12 +1728,20 @@ var htmx = (() => {
|
|
|
1821
1728
|
#handleHxVals(elt, body) {
|
|
1822
1729
|
let hxValsValue = this.#attributeValue(elt, "hx-vals");
|
|
1823
1730
|
if (hxValsValue) {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1731
|
+
let javascriptContent = this.#extractJavascriptContent(hxValsValue);
|
|
1732
|
+
if (javascriptContent) {
|
|
1733
|
+
// Return promise for async evaluation
|
|
1734
|
+
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true).then(obj => {
|
|
1735
|
+
for (let key in obj) {
|
|
1736
|
+
body.append(key, obj[key])
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
} else {
|
|
1740
|
+
// Synchronous path
|
|
1741
|
+
let obj = this.#parseConfig(hxValsValue);
|
|
1742
|
+
for (let key in obj) {
|
|
1743
|
+
body.append(key, obj[key])
|
|
1744
|
+
}
|
|
1830
1745
|
}
|
|
1831
1746
|
}
|
|
1832
1747
|
}
|
|
@@ -1837,11 +1752,13 @@ var htmx = (() => {
|
|
|
1837
1752
|
}
|
|
1838
1753
|
|
|
1839
1754
|
#findAllExt(eltOrSelector, maybeSelector, global) {
|
|
1840
|
-
let
|
|
1755
|
+
let selector = maybeSelector ?? eltOrSelector;
|
|
1756
|
+
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
|
|
1841
1757
|
if (selector.startsWith('global ')) {
|
|
1842
1758
|
return this.#findAllExt(elt, selector.slice(7), true);
|
|
1843
1759
|
}
|
|
1844
|
-
let parts =
|
|
1760
|
+
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
|
|
1761
|
+
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
|
|
1845
1762
|
let result = []
|
|
1846
1763
|
let unprocessedParts = []
|
|
1847
1764
|
for (const part of parts) {
|
|
@@ -1887,28 +1804,6 @@ var htmx = (() => {
|
|
|
1887
1804
|
return result
|
|
1888
1805
|
}
|
|
1889
1806
|
|
|
1890
|
-
#normalizeElementAndSelector(eltOrSelector, selector) {
|
|
1891
|
-
if (selector === undefined) {
|
|
1892
|
-
return [document, eltOrSelector];
|
|
1893
|
-
} else {
|
|
1894
|
-
return [this.#normalizeElement(eltOrSelector), selector];
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
#tokenizeExtendedSelector(selector) {
|
|
1899
|
-
let parts = [], depth = 0, start = 0;
|
|
1900
|
-
for (let i = 0; i <= selector.length; i++) {
|
|
1901
|
-
let c = selector[i];
|
|
1902
|
-
if (c === '<') depth++;
|
|
1903
|
-
else if (c === '/' && selector[i + 1] === '>') depth--;
|
|
1904
|
-
else if ((c === ',' && !depth) || i === selector.length) {
|
|
1905
|
-
if (i > start) parts.push(selector.substring(start, i));
|
|
1906
|
-
start = i + 1;
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
return parts;
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
1807
|
#scanForwardQuery(start, match, global) {
|
|
1913
1808
|
return this.#scanUntilComparison(this.#getRootNode(start, global).querySelectorAll(match), start, Node.DOCUMENT_POSITION_PRECEDING);
|
|
1914
1809
|
}
|
|
@@ -2167,13 +2062,13 @@ var htmx = (() => {
|
|
|
2167
2062
|
let noSwapStrings = this.config.noSwap.map(x => x + "");
|
|
2168
2063
|
let str = status + ""
|
|
2169
2064
|
for (let pattern of [str, str.slice(0, 2) + 'x', str[0] + 'xx']) {
|
|
2170
|
-
let swap = this.#attributeValue(ctx.sourceElement, "hx-status:" + pattern);
|
|
2171
2065
|
if (noSwapStrings.includes(pattern)) {
|
|
2172
2066
|
ctx.swap = "none";
|
|
2173
2067
|
return
|
|
2174
2068
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2069
|
+
let statusValue = this.#attributeValue(ctx.sourceElement, "hx-status:" + pattern);
|
|
2070
|
+
if (statusValue) {
|
|
2071
|
+
Object.assign(ctx, this.#parseConfig(statusValue));
|
|
2177
2072
|
return;
|
|
2178
2073
|
}
|
|
2179
2074
|
}
|