htmx.org 4.0.0-alpha2 → 4.0.0-alpha4
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/ext/hx-compat.js +37 -0
- package/dist/ext/hx-compat.min.js +1 -0
- package/dist/ext/hx-compat.min.js.map +1 -0
- package/dist/ext/hx-optimistic.js +1 -1
- package/dist/ext/hx-optimistic.min.js +1 -1
- package/dist/ext/hx-optimistic.min.js.map +1 -1
- package/dist/ext/hx-preload.js +1 -1
- package/dist/ext/hx-preload.min.js +1 -1
- package/dist/ext/hx-preload.min.js.map +1 -1
- package/dist/ext/hx-ws.js +588 -0
- package/dist/ext/hx-ws.min.js +1 -0
- package/dist/ext/hx-ws.min.js.map +1 -0
- package/dist/htmx.d.ts +52 -0
- package/dist/htmx.esm.js +177 -274
- package/dist/htmx.esm.min.js +1 -1
- package/dist/htmx.esm.min.js.map +1 -1
- package/dist/htmx.js +175 -273
- package/dist/htmx.min.js +1 -1
- package/dist/htmx.min.js.map +1 -1
- package/package.json +9 -8
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,12 +103,13 @@ var htmx = (() => {
|
|
|
103
103
|
includeIndicatorCSS: true,
|
|
104
104
|
defaultTimeout: 60000, /* 60 second default timeout */
|
|
105
105
|
extensions: '',
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
sse: {
|
|
107
|
+
reconnect: false,
|
|
108
|
+
reconnectDelay: 500,
|
|
109
|
+
reconnectMaxDelay: 60000,
|
|
110
|
+
reconnectMaxAttempts: 10,
|
|
111
|
+
reconnectJitter: 0.3,
|
|
112
|
+
pauseInBackground: false
|
|
112
113
|
},
|
|
113
114
|
morphIgnore: ["data-htmx-powered"],
|
|
114
115
|
noSwap: [204, 304],
|
|
@@ -116,7 +117,8 @@ var htmx = (() => {
|
|
|
116
117
|
}
|
|
117
118
|
let metaConfig = document.querySelector('meta[name="htmx:config"]');
|
|
118
119
|
if (metaConfig) {
|
|
119
|
-
let
|
|
120
|
+
let content = metaConfig.content;
|
|
121
|
+
let overrides = this.#parseConfig(content);
|
|
120
122
|
// Deep merge nested config objects
|
|
121
123
|
for (let key in overrides) {
|
|
122
124
|
let val = overrides[key];
|
|
@@ -146,7 +148,7 @@ var htmx = (() => {
|
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
registerExtension(name, extension) {
|
|
150
152
|
if (this.#approvedExt && !this.#approvedExt.split(/,\s*/).includes(name)) return false;
|
|
151
153
|
if (this.#registeredExt.has(name)) return false;
|
|
152
154
|
this.#registeredExt.add(name);
|
|
@@ -215,62 +217,29 @@ var htmx = (() => {
|
|
|
215
217
|
return returnElt ? elt : defaultVal;
|
|
216
218
|
}
|
|
217
219
|
|
|
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;
|
|
220
|
+
#parseConfig(configString) {
|
|
221
|
+
if (configString[0] === '{') return JSON.parse(configString);
|
|
222
|
+
let configPattern = /([^\s,]+?)(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
|
223
|
+
return [...configString.matchAll(configPattern)].reduce((result, match) => {
|
|
224
|
+
let keyPath = match[1].split('.');
|
|
225
|
+
let value = (match[2] ?? match[3] ?? match[4] ?? match[5] ?? 'true').trim();
|
|
226
|
+
if (value === 'true') value = true;
|
|
227
|
+
else if (value === 'false') value = false;
|
|
228
|
+
else if (/^\d+$/.test(value)) value = parseInt(value);
|
|
229
|
+
keyPath.slice(0, -1).reduce((obj, key) => obj[key] ??= {}, result)[keyPath.at(-1)] = value;
|
|
230
|
+
return result;
|
|
231
|
+
}, {});
|
|
247
232
|
}
|
|
248
233
|
|
|
249
234
|
#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;
|
|
235
|
+
return spec.split(',').map(s => {
|
|
236
|
+
let m = s.match(/^\s*(\S+\[[^\]]*\]|\S+)\s*(.*?)\s*$/);
|
|
237
|
+
if (!m || !m[1]) return null;
|
|
238
|
+
if (m[1].includes('[') && !m[1].includes(']')) throw "unterminated:" + m[1];
|
|
239
|
+
let result = m[2] ? this.#parseConfig(m[2]) : {};
|
|
240
|
+
result.name = m[1];
|
|
241
|
+
return result;
|
|
242
|
+
}).filter(s => s);
|
|
274
243
|
}
|
|
275
244
|
|
|
276
245
|
#determineMethodAndAction(elt, evt) {
|
|
@@ -309,7 +278,6 @@ var htmx = (() => {
|
|
|
309
278
|
elt._htmx = {eventHandler: this.#createHtmxEventHandler(elt)}
|
|
310
279
|
elt.setAttribute('data-htmx-powered', 'true');
|
|
311
280
|
this.#initializeTriggers(elt);
|
|
312
|
-
this.#initializeStreamConfig(elt);
|
|
313
281
|
this.#initializeAbortListener(elt)
|
|
314
282
|
this.#trigger(elt, "htmx:after:init", {}, true)
|
|
315
283
|
this.#trigger(elt, "load", {}, false)
|
|
@@ -329,45 +297,52 @@ var htmx = (() => {
|
|
|
329
297
|
|
|
330
298
|
#createRequestContext(sourceElement, sourceEvent) {
|
|
331
299
|
let {action, method} = this.#determineMethodAndAction(sourceElement, sourceEvent);
|
|
300
|
+
let [fullAction, anchor] = (action || '').split('#');
|
|
301
|
+
let ac = new AbortController();
|
|
332
302
|
let ctx = {
|
|
333
303
|
sourceElement,
|
|
334
304
|
sourceEvent,
|
|
335
305
|
status: "created",
|
|
336
306
|
select: this.#attributeValue(sourceElement, "hx-select"),
|
|
337
307
|
selectOOB: this.#attributeValue(sourceElement, "hx-select-oob"),
|
|
338
|
-
target: this.#attributeValue(sourceElement, "hx-target"),
|
|
308
|
+
target: this.#resolveTarget(sourceElement, this.#attributeValue(sourceElement, "hx-target")),
|
|
339
309
|
swap: this.#attributeValue(sourceElement, "hx-swap", this.config.defaultSwap),
|
|
340
310
|
push: this.#attributeValue(sourceElement, "hx-push-url"),
|
|
341
311
|
replace: this.#attributeValue(sourceElement, "hx-replace-url"),
|
|
342
312
|
transition: this.config.transitions,
|
|
313
|
+
confirm: this.#attributeValue(sourceElement, "hx-confirm"),
|
|
343
314
|
request: {
|
|
344
315
|
validate: "true" === this.#attributeValue(sourceElement, "hx-validate", sourceElement.matches('form') ? "true" : "false"),
|
|
345
|
-
action,
|
|
316
|
+
action: fullAction,
|
|
317
|
+
anchor,
|
|
346
318
|
method,
|
|
347
|
-
headers: this.#determineHeaders(sourceElement)
|
|
319
|
+
headers: this.#determineHeaders(sourceElement),
|
|
320
|
+
abort: ac.abort.bind(ac),
|
|
321
|
+
credentials: "same-origin",
|
|
322
|
+
signal: ac.signal,
|
|
323
|
+
mode: this.config.mode
|
|
348
324
|
}
|
|
349
325
|
};
|
|
350
326
|
|
|
351
327
|
// Apply hx-config overrides
|
|
352
328
|
let configAttr = this.#attributeValue(sourceElement, "hx-config");
|
|
353
329
|
if (configAttr) {
|
|
354
|
-
let configOverrides =
|
|
355
|
-
let
|
|
330
|
+
let configOverrides = this.#parseConfig(configAttr);
|
|
331
|
+
let req = ctx.request;
|
|
356
332
|
for (let key in configOverrides) {
|
|
357
333
|
if (key.startsWith('+')) {
|
|
358
334
|
let actualKey = key.substring(1);
|
|
359
|
-
if (
|
|
360
|
-
Object.assign(
|
|
335
|
+
if (req[actualKey] && typeof req[actualKey] === 'object') {
|
|
336
|
+
Object.assign(req[actualKey], configOverrides[key]);
|
|
361
337
|
} else {
|
|
362
|
-
|
|
338
|
+
req[actualKey] = configOverrides[key];
|
|
363
339
|
}
|
|
364
340
|
} else {
|
|
365
|
-
|
|
341
|
+
req[key] = configOverrides[key];
|
|
366
342
|
}
|
|
367
343
|
}
|
|
368
|
-
if (
|
|
369
|
-
sourceElement._htmx ||= {}
|
|
370
|
-
sourceElement._htmx.etag ||= requestConfig.etag
|
|
344
|
+
if (req.etag) {
|
|
345
|
+
(sourceElement._htmx ||= {}).etag ||= req.etag
|
|
371
346
|
}
|
|
372
347
|
}
|
|
373
348
|
if (sourceElement._htmx?.etag) {
|
|
@@ -379,6 +354,8 @@ var htmx = (() => {
|
|
|
379
354
|
#determineHeaders(elt) {
|
|
380
355
|
let headers = {
|
|
381
356
|
"HX-Request": "true",
|
|
357
|
+
"HX-Source": elt.id || elt.name,
|
|
358
|
+
"HX-Current-URL": location.href,
|
|
382
359
|
"Accept": "text/html, text/event-stream"
|
|
383
360
|
};
|
|
384
361
|
if (this.#isBoosted(elt)) {
|
|
@@ -386,7 +363,7 @@ var htmx = (() => {
|
|
|
386
363
|
}
|
|
387
364
|
let headersAttribute = this.#attributeValue(elt, "hx-headers");
|
|
388
365
|
if (headersAttribute) {
|
|
389
|
-
Object.assign(headers,
|
|
366
|
+
Object.assign(headers, this.#parseConfig(headersAttribute));
|
|
390
367
|
}
|
|
391
368
|
return headers;
|
|
392
369
|
}
|
|
@@ -418,13 +395,11 @@ var htmx = (() => {
|
|
|
418
395
|
|
|
419
396
|
if (this.#shouldCancel(evt)) evt.preventDefault()
|
|
420
397
|
|
|
421
|
-
// Resolve swap target
|
|
422
|
-
ctx.target = this.#resolveTarget(elt, ctx.target);
|
|
423
|
-
|
|
424
398
|
// Build request body
|
|
425
399
|
let form = elt.form || elt.closest("form")
|
|
426
400
|
let body = this.#collectFormData(elt, form, evt.submitter)
|
|
427
|
-
this.#handleHxVals(elt, body)
|
|
401
|
+
let valsResult = this.#handleHxVals(elt, body)
|
|
402
|
+
if (valsResult) await valsResult // Only await if it returned a promise
|
|
428
403
|
if (ctx.values) {
|
|
429
404
|
for (let k in ctx.values) {
|
|
430
405
|
body.delete(k);
|
|
@@ -432,20 +407,11 @@ var htmx = (() => {
|
|
|
432
407
|
}
|
|
433
408
|
}
|
|
434
409
|
|
|
435
|
-
// Setup
|
|
436
|
-
let ac = new AbortController()
|
|
437
|
-
let action = ctx.request.action.replace?.(/#.*$/, '')
|
|
438
|
-
// TODO - consider how this works with hx-config, move most to #createRequestContext?
|
|
410
|
+
// Setup event-dependent request details
|
|
439
411
|
Object.assign(ctx.request, {
|
|
440
|
-
originalAction: ctx.request.action,
|
|
441
|
-
action,
|
|
442
412
|
form,
|
|
443
413
|
submitter: evt.submitter,
|
|
444
|
-
|
|
445
|
-
body,
|
|
446
|
-
credentials: "same-origin",
|
|
447
|
-
signal: ac.signal,
|
|
448
|
-
mode: this.config.mode
|
|
414
|
+
body
|
|
449
415
|
})
|
|
450
416
|
|
|
451
417
|
if (!this.#trigger(elt, "htmx:config:request", {ctx: ctx})) return
|
|
@@ -453,14 +419,22 @@ var htmx = (() => {
|
|
|
453
419
|
if (ctx.request.validate && ctx.request.form && !ctx.request.form.reportValidity()) return
|
|
454
420
|
|
|
455
421
|
let javascriptContent = this.#extractJavascriptContent(ctx.request.action);
|
|
456
|
-
if (javascriptContent) {
|
|
422
|
+
if (javascriptContent != null) {
|
|
457
423
|
let data = Object.fromEntries(ctx.request.body);
|
|
458
424
|
await this.#executeJavaScriptAsync(ctx.sourceElement, data, javascriptContent, false);
|
|
459
425
|
return
|
|
460
426
|
} else if (/GET|DELETE/.test(ctx.request.method)) {
|
|
461
|
-
let
|
|
462
|
-
|
|
463
|
-
ctx.request.body
|
|
427
|
+
let url = new URL(ctx.request.action, document.baseURI);
|
|
428
|
+
|
|
429
|
+
for (let key of ctx.request.body.keys()) {
|
|
430
|
+
url.searchParams.delete(key);
|
|
431
|
+
}
|
|
432
|
+
for (let [key, value] of ctx.request.body) {
|
|
433
|
+
url.searchParams.append(key, value);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
ctx.request.action = url.pathname + url.search;
|
|
437
|
+
ctx.request.body = null;
|
|
464
438
|
} else if (this.#attributeValue(elt, "hx-encoding") !== "multipart/form-data") {
|
|
465
439
|
ctx.request.body = new URLSearchParams(ctx.request.body);
|
|
466
440
|
}
|
|
@@ -484,21 +458,19 @@ var htmx = (() => {
|
|
|
484
458
|
let disableElements = this.#disableElements(elt, disableSelector);
|
|
485
459
|
|
|
486
460
|
try {
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
let
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
|
|
461
|
+
// Handle confirmation
|
|
462
|
+
if (ctx.confirm) {
|
|
463
|
+
let issueRequest = null;
|
|
464
|
+
let confirmed = await new Promise(resolve => {
|
|
465
|
+
issueRequest = resolve;
|
|
466
|
+
if (this.#trigger(elt, "htmx:confirm", {ctx, issueRequest: (skip) => issueRequest?.(skip !== false)})) {
|
|
467
|
+
let js = this.#extractJavascriptContent(ctx.confirm);
|
|
468
|
+
resolve(js ? this.#executeJavaScriptAsync(elt, {}, js, true) : window.confirm(ctx.confirm));
|
|
494
469
|
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
470
|
+
});
|
|
471
|
+
if (!confirmed) return;
|
|
500
472
|
}
|
|
501
|
-
|
|
473
|
+
|
|
502
474
|
ctx.fetch ||= window.fetch.bind(window)
|
|
503
475
|
if (!this.#trigger(elt, "htmx:before:request", {ctx})) return;
|
|
504
476
|
|
|
@@ -527,7 +499,7 @@ var htmx = (() => {
|
|
|
527
499
|
} else {
|
|
528
500
|
// HTTP response
|
|
529
501
|
if (ctx.status === "issuing") {
|
|
530
|
-
if (ctx.hx.retarget) ctx.target =
|
|
502
|
+
if (ctx.hx.retarget) ctx.target = ctx.hx.retarget;
|
|
531
503
|
if (ctx.hx.reswap) ctx.swap = ctx.hx.reswap;
|
|
532
504
|
if (ctx.hx.reselect) ctx.select = ctx.hx.reselect;
|
|
533
505
|
ctx.status = "response received";
|
|
@@ -578,8 +550,8 @@ var htmx = (() => {
|
|
|
578
550
|
}
|
|
579
551
|
if (ctx.hx.location) {
|
|
580
552
|
let path = ctx.hx.location, opts = {};
|
|
581
|
-
if (path[0] === '{') {
|
|
582
|
-
opts =
|
|
553
|
+
if (path[0] === '{' || /[\s,]/.test(path)) {
|
|
554
|
+
opts = this.#parseConfig(path);
|
|
583
555
|
path = opts.path;
|
|
584
556
|
delete opts.path;
|
|
585
557
|
}
|
|
@@ -594,7 +566,7 @@ var htmx = (() => {
|
|
|
594
566
|
}
|
|
595
567
|
|
|
596
568
|
async #handleSSE(ctx, elt, response) {
|
|
597
|
-
let config =
|
|
569
|
+
let config = {...this.config.sse, ...ctx.request.sse}
|
|
598
570
|
|
|
599
571
|
let waitForVisible = () => new Promise(r => {
|
|
600
572
|
let onVisible = () => !document.hidden && (document.removeEventListener('visibilitychange', onVisible), r());
|
|
@@ -606,14 +578,19 @@ var htmx = (() => {
|
|
|
606
578
|
while (elt.isConnected) {
|
|
607
579
|
// Handle reconnection for subsequent iterations
|
|
608
580
|
if (attempt > 0) {
|
|
609
|
-
if (config.
|
|
581
|
+
if (!config.reconnect || attempt > config.reconnectMaxAttempts) break;
|
|
610
582
|
|
|
611
|
-
if (config.
|
|
583
|
+
if (config.pauseInBackground && document.hidden) {
|
|
612
584
|
await waitForVisible();
|
|
613
585
|
if (!elt.isConnected) break;
|
|
614
586
|
}
|
|
615
587
|
|
|
616
|
-
let delay = Math.min(config.
|
|
588
|
+
let delay = Math.min(this.parseInterval(config.reconnectDelay) * Math.pow(2, attempt - 1), this.parseInterval(config.reconnectMaxDelay));
|
|
589
|
+
if (config.reconnectJitter > 0) {
|
|
590
|
+
let jitterRange = delay * config.reconnectJitter;
|
|
591
|
+
let jitter = (Math.random() * 2 - 1) * jitterRange;
|
|
592
|
+
delay = Math.max(0, delay + jitter);
|
|
593
|
+
}
|
|
617
594
|
let reconnect = {attempt, delay, lastEventId, cancelled: false};
|
|
618
595
|
|
|
619
596
|
ctx.status = "reconnecting to stream";
|
|
@@ -646,7 +623,7 @@ var htmx = (() => {
|
|
|
646
623
|
for await (const sseMessage of this.#parseSSE(currentResponse)) {
|
|
647
624
|
if (!elt.isConnected) break;
|
|
648
625
|
|
|
649
|
-
if (config.
|
|
626
|
+
if (config.pauseInBackground && document.hidden) {
|
|
650
627
|
await waitForVisible();
|
|
651
628
|
if (!elt.isConnected) break;
|
|
652
629
|
}
|
|
@@ -744,7 +721,7 @@ var htmx = (() => {
|
|
|
744
721
|
#initTimeout(ctx) {
|
|
745
722
|
let timeoutInterval;
|
|
746
723
|
if (ctx.request.timeout) {
|
|
747
|
-
timeoutInterval =
|
|
724
|
+
timeoutInterval = this.parseInterval(ctx.request.timeout);
|
|
748
725
|
} else {
|
|
749
726
|
timeoutInterval = this.config.defaultTimeout;
|
|
750
727
|
}
|
|
@@ -943,36 +920,6 @@ var htmx = (() => {
|
|
|
943
920
|
}
|
|
944
921
|
}
|
|
945
922
|
|
|
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
|
-
|
|
972
|
-
if (!elt._htmx) elt._htmx = {};
|
|
973
|
-
elt._htmx.streamConfig = streamConfig;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
923
|
#extractFilter(str) {
|
|
977
924
|
let match = str.match(/^([^\[]*)\[([^\]]*)]/);
|
|
978
925
|
if (!match) return [str, null];
|
|
@@ -981,7 +928,7 @@ var htmx = (() => {
|
|
|
981
928
|
|
|
982
929
|
#handleTriggerHeader(value, elt) {
|
|
983
930
|
if (value[0] === '{') {
|
|
984
|
-
let triggers =
|
|
931
|
+
let triggers = this.#parseConfig(value);
|
|
985
932
|
for (let name in triggers) {
|
|
986
933
|
let detail = triggers[name];
|
|
987
934
|
if (detail?.target) elt = this.find(detail.target) || elt;
|
|
@@ -1150,7 +1097,7 @@ var htmx = (() => {
|
|
|
1150
1097
|
}
|
|
1151
1098
|
|
|
1152
1099
|
#makeFragment(text) {
|
|
1153
|
-
let response = text.replace(/<hx-
|
|
1100
|
+
let response = text.replace(/<hx-([a-z]+)(\s+|>)/gi, '<template hx type="$1"$2').replace(/<\/hx-[a-z]+>/gi, '</template>');
|
|
1154
1101
|
let title = '';
|
|
1155
1102
|
response = response.replace(/<title[^>]*>[\s\S]*?<\/title>/i, m => (title = this.#parseHTML(m).title, ''));
|
|
1156
1103
|
let responseWithNoHead = response.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i, '');
|
|
@@ -1223,52 +1170,36 @@ var htmx = (() => {
|
|
|
1223
1170
|
}
|
|
1224
1171
|
|
|
1225
1172
|
#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
|
-
}
|
|
1173
|
+
swapStr = swapStr.trim();
|
|
1174
|
+
let style = this.config.defaultSwap
|
|
1175
|
+
if (swapStr && !/^\S*:/.test(swapStr)) {
|
|
1176
|
+
let m = swapStr.match(/^(\S+)\s*(.*)$/);
|
|
1177
|
+
style = m[1];
|
|
1178
|
+
swapStr = m[2];
|
|
1254
1179
|
}
|
|
1255
|
-
return
|
|
1180
|
+
return {style: this.#normalizeSwapStyle(style), ...this.#parseConfig(swapStr)};
|
|
1256
1181
|
}
|
|
1257
1182
|
|
|
1258
|
-
#processPartials(fragment,
|
|
1183
|
+
#processPartials(fragment, ctx) {
|
|
1259
1184
|
let tasks = [];
|
|
1260
1185
|
|
|
1261
|
-
for (let
|
|
1262
|
-
let
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1186
|
+
for (let templateElt of fragment.querySelectorAll('template[hx]')) {
|
|
1187
|
+
let type = templateElt.getAttribute('type');
|
|
1188
|
+
|
|
1189
|
+
if (type === 'partial') {
|
|
1190
|
+
let swapSpec = this.#parseSwapSpec(templateElt.getAttribute(this.#prefix('hx-swap')) || this.config.defaultSwap);
|
|
1191
|
+
|
|
1192
|
+
tasks.push({
|
|
1193
|
+
type: 'partial',
|
|
1194
|
+
fragment: templateElt.content.cloneNode(true),
|
|
1195
|
+
target: templateElt.getAttribute(this.#prefix('hx-target')),
|
|
1196
|
+
swapSpec,
|
|
1197
|
+
sourceElement: ctx.sourceElement
|
|
1198
|
+
});
|
|
1199
|
+
} else {
|
|
1200
|
+
this.#triggerExtensions(templateElt, 'htmx:process:' + type, { ctx, tasks });
|
|
1201
|
+
}
|
|
1202
|
+
templateElt.remove();
|
|
1272
1203
|
}
|
|
1273
1204
|
|
|
1274
1205
|
return tasks;
|
|
@@ -1281,37 +1212,22 @@ var htmx = (() => {
|
|
|
1281
1212
|
|
|
1282
1213
|
#handleScroll(task) {
|
|
1283
1214
|
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') {
|
|
1215
|
+
let target = task.swapSpec.scrollTarget ? this.#findExt(task.swapSpec.scrollTarget) : task.target;
|
|
1216
|
+
if (task.swapSpec.scroll === 'top') {
|
|
1293
1217
|
target.scrollTop = 0;
|
|
1294
|
-
} else if (
|
|
1218
|
+
} else if (task.swapSpec.scroll === 'bottom'){
|
|
1295
1219
|
target.scrollTop = target.scrollHeight;
|
|
1296
1220
|
}
|
|
1297
1221
|
}
|
|
1298
1222
|
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')
|
|
1223
|
+
let target = task.swapSpec.showTarget ? this.#findExt(task.swapSpec.showTarget) : task.target;
|
|
1224
|
+
target.scrollIntoView(task.swapSpec.show === 'top')
|
|
1308
1225
|
}
|
|
1309
1226
|
}
|
|
1310
1227
|
|
|
1311
1228
|
#handleAnchorScroll(ctx) {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
document.getElementById(anchor)?.scrollIntoView({block: 'start', behavior: 'auto'});
|
|
1229
|
+
if (ctx.request?.anchor) {
|
|
1230
|
+
document.getElementById(ctx.request.anchor)?.scrollIntoView({block: 'start', behavior: 'auto'});
|
|
1315
1231
|
}
|
|
1316
1232
|
}
|
|
1317
1233
|
|
|
@@ -1342,7 +1258,7 @@ var htmx = (() => {
|
|
|
1342
1258
|
|
|
1343
1259
|
// Process OOB and partials
|
|
1344
1260
|
let oobTasks = this.#processOOB(fragment, ctx.sourceElement, ctx.selectOOB);
|
|
1345
|
-
let partialTasks = this.#processPartials(fragment, ctx
|
|
1261
|
+
let partialTasks = this.#processPartials(fragment, ctx);
|
|
1346
1262
|
tasks.push(...oobTasks, ...partialTasks);
|
|
1347
1263
|
|
|
1348
1264
|
// Process main swap
|
|
@@ -1364,8 +1280,8 @@ var htmx = (() => {
|
|
|
1364
1280
|
|
|
1365
1281
|
// insert non-transition tasks immediately or with delay
|
|
1366
1282
|
for (let task of nonTransitionTasks) {
|
|
1367
|
-
if (task.swapSpec?.
|
|
1368
|
-
setTimeout(() => this.#insertContent(task), task.swapSpec.
|
|
1283
|
+
if (task.swapSpec?.swap) {
|
|
1284
|
+
setTimeout(() => this.#insertContent(task), this.parseInterval(task.swapSpec.swap));
|
|
1369
1285
|
} else {
|
|
1370
1286
|
this.#insertContent(task)
|
|
1371
1287
|
}
|
|
@@ -1412,7 +1328,7 @@ var htmx = (() => {
|
|
|
1412
1328
|
let mainSwap = {
|
|
1413
1329
|
type: 'main',
|
|
1414
1330
|
fragment,
|
|
1415
|
-
target: swapSpec.target || ctx.target,
|
|
1331
|
+
target: this.#resolveTarget(ctx.sourceElement || document.body, swapSpec.target || ctx.target),
|
|
1416
1332
|
swapSpec,
|
|
1417
1333
|
sourceElement: ctx.sourceElement,
|
|
1418
1334
|
transition: (ctx.transition !== false) && (swapSpec.transition !== false)
|
|
@@ -1492,7 +1408,7 @@ var htmx = (() => {
|
|
|
1492
1408
|
console.log(eventName, detail, on)
|
|
1493
1409
|
}
|
|
1494
1410
|
on = this.#normalizeElement(on)
|
|
1495
|
-
this.#triggerExtensions(on,
|
|
1411
|
+
this.#triggerExtensions(on, eventName, detail);
|
|
1496
1412
|
return this.trigger(on, eventName, detail, bubbles)
|
|
1497
1413
|
}
|
|
1498
1414
|
|
|
@@ -1511,9 +1427,7 @@ var htmx = (() => {
|
|
|
1511
1427
|
}
|
|
1512
1428
|
|
|
1513
1429
|
timeout(time) {
|
|
1514
|
-
|
|
1515
|
-
time = this.parseInterval(time)
|
|
1516
|
-
}
|
|
1430
|
+
time = this.parseInterval(time);
|
|
1517
1431
|
if (time > 0) {
|
|
1518
1432
|
return new Promise(resolve => setTimeout(resolve, time));
|
|
1519
1433
|
}
|
|
@@ -1536,7 +1450,7 @@ var htmx = (() => {
|
|
|
1536
1450
|
}
|
|
1537
1451
|
|
|
1538
1452
|
onLoad(callback) {
|
|
1539
|
-
this.on("htmx:after:
|
|
1453
|
+
this.on("htmx:after:process", (evt) => {
|
|
1540
1454
|
callback(evt.target)
|
|
1541
1455
|
})
|
|
1542
1456
|
}
|
|
@@ -1571,6 +1485,7 @@ var htmx = (() => {
|
|
|
1571
1485
|
}
|
|
1572
1486
|
|
|
1573
1487
|
parseInterval(str) {
|
|
1488
|
+
if (typeof str === 'number') return str;
|
|
1574
1489
|
let m = {ms: 1, s: 1000, m: 60000};
|
|
1575
1490
|
let [, n, u] = str?.match(/^([\d.]+)(ms|s|m)?$/) || [];
|
|
1576
1491
|
let v = parseFloat(n) * (m[u] || 1);
|
|
@@ -1600,20 +1515,21 @@ var htmx = (() => {
|
|
|
1600
1515
|
let sourceElt = typeof context.source === 'string' ?
|
|
1601
1516
|
document.querySelector(context.source) : context.source;
|
|
1602
1517
|
|
|
1603
|
-
//
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1518
|
+
// If source selector was provided but didn't match, reject
|
|
1519
|
+
if (typeof context.source === 'string' && !sourceElt) {
|
|
1520
|
+
return Promise.reject(new Error('Source not found'));
|
|
1521
|
+
}
|
|
1607
1522
|
|
|
1608
|
-
if
|
|
1523
|
+
// Resolve target, defaulting to body only if no source or target provided
|
|
1524
|
+
let target = this.#resolveTarget(document.body, context.target || sourceElt);
|
|
1525
|
+
if (!target) {
|
|
1609
1526
|
return Promise.reject(new Error('Target not found'));
|
|
1610
1527
|
}
|
|
1611
1528
|
|
|
1612
|
-
|
|
1613
|
-
sourceElt ||= targetElt || document.body;
|
|
1529
|
+
sourceElt ||= target;
|
|
1614
1530
|
|
|
1615
1531
|
let ctx = this.#createRequestContext(sourceElt, context.event || {});
|
|
1616
|
-
Object.assign(ctx, context, {target
|
|
1532
|
+
Object.assign(ctx, context, {target});
|
|
1617
1533
|
Object.assign(ctx.request, {action: path, method: verb.toUpperCase()});
|
|
1618
1534
|
if (context.headers) Object.assign(ctx.request.headers, context.headers);
|
|
1619
1535
|
|
|
@@ -1676,10 +1592,10 @@ var htmx = (() => {
|
|
|
1676
1592
|
}
|
|
1677
1593
|
|
|
1678
1594
|
let path = push || replace;
|
|
1679
|
-
if (!path || path === 'false') return;
|
|
1595
|
+
if (!path || path === 'false' || path === false) return;
|
|
1680
1596
|
|
|
1681
1597
|
if (path === 'true') {
|
|
1682
|
-
path = ctx.request.
|
|
1598
|
+
path = ctx.request.action + (ctx.request.anchor ? '#' + ctx.request.anchor : '');
|
|
1683
1599
|
}
|
|
1684
1600
|
|
|
1685
1601
|
let type = push ? 'push' : 'replace';
|
|
@@ -1766,11 +1682,9 @@ var htmx = (() => {
|
|
|
1766
1682
|
}
|
|
1767
1683
|
|
|
1768
1684
|
#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) {
|
|
1685
|
+
let formData = form ? new FormData(form) : new FormData()
|
|
1686
|
+
let included = form ? new Set(form.elements) : new Set()
|
|
1687
|
+
if (!form && elt.name) {
|
|
1774
1688
|
formData.append(elt.name, elt.value)
|
|
1775
1689
|
included.add(elt);
|
|
1776
1690
|
}
|
|
@@ -1792,21 +1706,21 @@ var htmx = (() => {
|
|
|
1792
1706
|
let inputs = this.#queryEltAndDescendants(elt, 'input:not([disabled]), select:not([disabled]), textarea:not([disabled])');
|
|
1793
1707
|
|
|
1794
1708
|
for (let input of inputs) {
|
|
1795
|
-
// Skip elements without a name or already seen
|
|
1796
1709
|
if (!input.name || included.has(input)) continue;
|
|
1797
1710
|
included.add(input);
|
|
1798
1711
|
|
|
1799
|
-
|
|
1712
|
+
let type = input.type;
|
|
1713
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
1800
1714
|
// Only add if checked
|
|
1801
1715
|
if (input.checked) {
|
|
1802
1716
|
formData.append(input.name, input.value);
|
|
1803
1717
|
}
|
|
1804
|
-
} else if (
|
|
1718
|
+
} else if (type === 'file') {
|
|
1805
1719
|
// Add all selected files
|
|
1806
1720
|
for (let file of input.files) {
|
|
1807
1721
|
formData.append(input.name, file);
|
|
1808
1722
|
}
|
|
1809
|
-
} else if (
|
|
1723
|
+
} else if (type === 'select-multiple') {
|
|
1810
1724
|
// Add all selected options
|
|
1811
1725
|
for (let option of input.selectedOptions) {
|
|
1812
1726
|
formData.append(input.name, option.value);
|
|
@@ -1821,12 +1735,20 @@ var htmx = (() => {
|
|
|
1821
1735
|
#handleHxVals(elt, body) {
|
|
1822
1736
|
let hxValsValue = this.#attributeValue(elt, "hx-vals");
|
|
1823
1737
|
if (hxValsValue) {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1738
|
+
let javascriptContent = this.#extractJavascriptContent(hxValsValue);
|
|
1739
|
+
if (javascriptContent) {
|
|
1740
|
+
// Return promise for async evaluation
|
|
1741
|
+
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true).then(obj => {
|
|
1742
|
+
for (let key in obj) {
|
|
1743
|
+
body.append(key, obj[key])
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
} else {
|
|
1747
|
+
// Synchronous path
|
|
1748
|
+
let obj = this.#parseConfig(hxValsValue);
|
|
1749
|
+
for (let key in obj) {
|
|
1750
|
+
body.append(key, obj[key])
|
|
1751
|
+
}
|
|
1830
1752
|
}
|
|
1831
1753
|
}
|
|
1832
1754
|
}
|
|
@@ -1837,11 +1759,13 @@ var htmx = (() => {
|
|
|
1837
1759
|
}
|
|
1838
1760
|
|
|
1839
1761
|
#findAllExt(eltOrSelector, maybeSelector, global) {
|
|
1840
|
-
let
|
|
1762
|
+
let selector = maybeSelector ?? eltOrSelector;
|
|
1763
|
+
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
|
|
1841
1764
|
if (selector.startsWith('global ')) {
|
|
1842
1765
|
return this.#findAllExt(elt, selector.slice(7), true);
|
|
1843
1766
|
}
|
|
1844
|
-
let parts =
|
|
1767
|
+
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
|
|
1768
|
+
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
|
|
1845
1769
|
let result = []
|
|
1846
1770
|
let unprocessedParts = []
|
|
1847
1771
|
for (const part of parts) {
|
|
@@ -1887,28 +1811,6 @@ var htmx = (() => {
|
|
|
1887
1811
|
return result
|
|
1888
1812
|
}
|
|
1889
1813
|
|
|
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
1814
|
#scanForwardQuery(start, match, global) {
|
|
1913
1815
|
return this.#scanUntilComparison(this.#getRootNode(start, global).querySelectorAll(match), start, Node.DOCUMENT_POSITION_PRECEDING);
|
|
1914
1816
|
}
|
|
@@ -2167,13 +2069,13 @@ var htmx = (() => {
|
|
|
2167
2069
|
let noSwapStrings = this.config.noSwap.map(x => x + "");
|
|
2168
2070
|
let str = status + ""
|
|
2169
2071
|
for (let pattern of [str, str.slice(0, 2) + 'x', str[0] + 'xx']) {
|
|
2170
|
-
let swap = this.#attributeValue(ctx.sourceElement, "hx-status:" + pattern);
|
|
2171
2072
|
if (noSwapStrings.includes(pattern)) {
|
|
2172
2073
|
ctx.swap = "none";
|
|
2173
2074
|
return
|
|
2174
2075
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2076
|
+
let statusValue = this.#attributeValue(ctx.sourceElement, "hx-status:" + pattern);
|
|
2077
|
+
if (statusValue) {
|
|
2078
|
+
Object.assign(ctx, this.#parseConfig(statusValue));
|
|
2177
2079
|
return;
|
|
2178
2080
|
}
|
|
2179
2081
|
}
|