htmx.org 1.9.10 → 2.0.0-alpha2

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.
@@ -0,0 +1,3919 @@
1
+ define(() => {
2
+ var htmx = (function() {
3
+ 'use strict'
4
+
5
+ // Public API
6
+ //* * @type {import("./htmx").HtmxApi} */
7
+ const htmx = {
8
+ /* Event processing */
9
+ onLoad: onLoadHelper,
10
+ process: processNode,
11
+ on: addEventListenerImpl,
12
+ off: removeEventListenerImpl,
13
+ trigger: triggerEvent,
14
+ ajax: ajaxHelper,
15
+ /* DOM querying helpers */
16
+ find,
17
+ findAll,
18
+ closest,
19
+ values: function(elt, type) {
20
+ const inputValues = getInputValues(elt, type || 'post')
21
+ return inputValues.values
22
+ },
23
+ /* DOM manipulation helpers */
24
+ remove: removeElement,
25
+ addClass: addClassToElement,
26
+ removeClass: removeClassFromElement,
27
+ toggleClass: toggleClassOnElement,
28
+ takeClass: takeClassForElement,
29
+ swap,
30
+ /* Extension entrypoints */
31
+ defineExtension,
32
+ removeExtension,
33
+ /* Debugging */
34
+ logAll,
35
+ logNone,
36
+ logger: null,
37
+ config: {
38
+ historyEnabled: true,
39
+ historyCacheSize: 10,
40
+ refreshOnHistoryMiss: false,
41
+ defaultSwapStyle: 'innerHTML',
42
+ defaultSwapDelay: 0,
43
+ defaultSettleDelay: 20,
44
+ includeIndicatorStyles: true,
45
+ indicatorClass: 'htmx-indicator',
46
+ requestClass: 'htmx-request',
47
+ addedClass: 'htmx-added',
48
+ settlingClass: 'htmx-settling',
49
+ swappingClass: 'htmx-swapping',
50
+ allowEval: true,
51
+ allowScriptTags: true,
52
+ inlineScriptNonce: '',
53
+ attributesToSettle: ['class', 'style', 'width', 'height'],
54
+ withCredentials: false,
55
+ timeout: 0,
56
+ wsReconnectDelay: 'full-jitter',
57
+ wsBinaryType: 'blob',
58
+ disableSelector: '[hx-disable], [data-hx-disable]',
59
+ scrollBehavior: 'instant',
60
+ defaultFocusScroll: false,
61
+ getCacheBusterParam: false,
62
+ globalViewTransitions: false,
63
+ methodsThatUseUrlParams: ['get', 'delete'],
64
+ selfRequestsOnly: true,
65
+ ignoreTitle: false,
66
+ scrollIntoViewOnBoost: true,
67
+ triggerSpecsCache: null,
68
+ disableInheritance: false,
69
+ responseHandling: [
70
+ { code: '204', swap: false },
71
+ { code: '[23]..', swap: true },
72
+ { code: '[45]..', swap: false, error: true }
73
+ ]
74
+ },
75
+ parseInterval,
76
+ _: internalEval,
77
+ version: '2.0a'
78
+ }
79
+
80
+ /** @type {import("./htmx").HtmxInternalApi} */
81
+ const internalAPI = {
82
+ addTriggerHandler,
83
+ bodyContains,
84
+ canAccessLocalStorage,
85
+ findThisElement,
86
+ filterValues,
87
+ swap,
88
+ hasAttribute,
89
+ getAttributeValue,
90
+ getClosestAttributeValue,
91
+ getClosestMatch,
92
+ getExpressionVars,
93
+ getHeaders,
94
+ getInputValues,
95
+ getInternalData,
96
+ getSwapSpecification,
97
+ getTriggerSpecs,
98
+ getTarget,
99
+ makeFragment,
100
+ mergeObjects,
101
+ makeSettleInfo,
102
+ oobSwap,
103
+ querySelectorExt,
104
+ settleImmediately,
105
+ shouldCancel,
106
+ triggerEvent,
107
+ triggerErrorEvent,
108
+ withExtensions
109
+ }
110
+
111
+ const VERBS = ['get', 'post', 'put', 'delete', 'patch']
112
+ const VERB_SELECTOR = VERBS.map(function(verb) {
113
+ return '[hx-' + verb + '], [data-hx-' + verb + ']'
114
+ }).join(', ')
115
+
116
+ const HEAD_TAG_REGEX = makeTagRegEx('head')
117
+
118
+ //= ===================================================================
119
+ // Utilities
120
+ //= ===================================================================
121
+
122
+ /**
123
+ * @param {string} tag
124
+ * @param {boolean} global
125
+ * @returns {RegExp}
126
+ */
127
+ function makeTagRegEx(tag, global = false) {
128
+ return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
129
+ global ? 'gim' : 'im')
130
+ }
131
+
132
+ function parseInterval(str) {
133
+ if (str == undefined) {
134
+ return undefined
135
+ }
136
+
137
+ let interval = NaN
138
+ if (str.slice(-2) == 'ms') {
139
+ interval = parseFloat(str.slice(0, -2))
140
+ } else if (str.slice(-1) == 's') {
141
+ interval = parseFloat(str.slice(0, -1)) * 1000
142
+ } else if (str.slice(-1) == 'm') {
143
+ interval = parseFloat(str.slice(0, -1)) * 1000 * 60
144
+ } else {
145
+ interval = parseFloat(str)
146
+ }
147
+ return isNaN(interval) ? undefined : interval
148
+ }
149
+
150
+ /**
151
+ * @param {Element} elt
152
+ * @param {string} name
153
+ * @returns {(string | null)}
154
+ */
155
+ function getRawAttribute(elt, name) {
156
+ return elt.getAttribute && elt.getAttribute(name)
157
+ }
158
+
159
+ // resolve with both hx and data-hx prefixes
160
+ function hasAttribute(elt, qualifiedName) {
161
+ return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
162
+ elt.hasAttribute('data-' + qualifiedName))
163
+ }
164
+
165
+ /**
166
+ *
167
+ * @param {HTMLElement} elt
168
+ * @param {string} qualifiedName
169
+ * @returns {(string | null)}
170
+ */
171
+ function getAttributeValue(elt, qualifiedName) {
172
+ return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, 'data-' + qualifiedName)
173
+ }
174
+
175
+ /**
176
+ * @param {HTMLElement} elt
177
+ * @returns {HTMLElement | ShadowRoot | null}
178
+ */
179
+ function parentElt(elt) {
180
+ const parent = elt.parentElement
181
+ if (!parent && elt.parentNode instanceof ShadowRoot) return elt.parentNode
182
+ return parent
183
+ }
184
+
185
+ /**
186
+ * @returns {Document}
187
+ */
188
+ function getDocument() {
189
+ return document
190
+ }
191
+
192
+ /**
193
+ * @returns {Document | ShadowRoot}
194
+ */
195
+ function getRootNode(elt, global) {
196
+ return elt.getRootNode ? elt.getRootNode({ composed: global }) : getDocument()
197
+ }
198
+
199
+ /**
200
+ * @param {HTMLElement} elt
201
+ * @param {(e:HTMLElement) => boolean} condition
202
+ * @returns {HTMLElement | null}
203
+ */
204
+ function getClosestMatch(elt, condition) {
205
+ while (elt && !condition(elt)) {
206
+ elt = parentElt(elt)
207
+ }
208
+
209
+ return elt || null
210
+ }
211
+
212
+ function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName) {
213
+ const attributeValue = getAttributeValue(ancestor, attributeName)
214
+ const disinherit = getAttributeValue(ancestor, 'hx-disinherit')
215
+ var inherit = getAttributeValue(ancestor, 'hx-inherit')
216
+ if (initialElement !== ancestor) {
217
+ if (htmx.config.disableInheritance) {
218
+ if (inherit && (inherit === '*' || inherit.split(' ').indexOf(attributeName) >= 0)) {
219
+ return attributeValue
220
+ } else {
221
+ return null
222
+ }
223
+ }
224
+ if (disinherit && (disinherit === '*' || disinherit.split(' ').indexOf(attributeName) >= 0)) {
225
+ return 'unset'
226
+ }
227
+ }
228
+ return attributeValue
229
+ }
230
+
231
+ /**
232
+ * @param {HTMLElement} elt
233
+ * @param {string} attributeName
234
+ * @returns {string | null}
235
+ */
236
+ function getClosestAttributeValue(elt, attributeName) {
237
+ let closestAttr = null
238
+ getClosestMatch(elt, function(e) {
239
+ return closestAttr = getAttributeValueWithDisinheritance(elt, e, attributeName)
240
+ })
241
+ if (closestAttr !== 'unset') {
242
+ return closestAttr
243
+ }
244
+ }
245
+
246
+ /**
247
+ * @param {HTMLElement} elt
248
+ * @param {string} selector
249
+ * @returns {boolean}
250
+ */
251
+ function matches(elt, selector) {
252
+ // @ts-ignore: non-standard properties for browser compatibility
253
+ // noinspection JSUnresolvedVariable
254
+ const matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector
255
+ return matchesFunction && matchesFunction.call(elt, selector)
256
+ }
257
+
258
+ /**
259
+ * @param {string} str
260
+ * @returns {string}
261
+ */
262
+ function getStartTag(str) {
263
+ const tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
264
+ const match = tagMatcher.exec(str)
265
+ if (match) {
266
+ return match[1].toLowerCase()
267
+ } else {
268
+ return ''
269
+ }
270
+ }
271
+
272
+ /**
273
+ *
274
+ * @param {string} resp
275
+ * @param {number} depth
276
+ * @returns {Document}
277
+ */
278
+ function parseHTML(resp) {
279
+ const parser = new DOMParser()
280
+ return parser.parseFromString(resp, 'text/html')
281
+ }
282
+
283
+ function takeChildrenFor(fragment, elt) {
284
+ while (elt.childNodes.length > 0) {
285
+ fragment.append(elt.childNodes[0])
286
+ }
287
+ }
288
+
289
+ function duplicateScript(script) {
290
+ const newScript = getDocument().createElement('script')
291
+ forEach(script.attributes, function(attr) {
292
+ newScript.setAttribute(attr.name, attr.value)
293
+ })
294
+ newScript.textContent = script.textContent
295
+ newScript.async = false
296
+ if (htmx.config.inlineScriptNonce) {
297
+ newScript.nonce = htmx.config.inlineScriptNonce
298
+ }
299
+ return newScript
300
+ }
301
+
302
+ function isJavaScriptScriptNode(script) {
303
+ return script.matches('script') && (script.type === 'text/javascript' || script.type === 'module' || script.type === '')
304
+ }
305
+
306
+ // we have to make new copies of script tags that we are going to insert because
307
+ // SOME browsers (not saying who, but it involves an element and an animal) don't
308
+ // execute scripts created in <template> tags when they are inserted into the DOM
309
+ // and all the others do lmao
310
+ function normalizeScriptTags(fragment) {
311
+ Array.from(fragment.querySelectorAll('script')).forEach((script) => {
312
+ if (isJavaScriptScriptNode(script)) {
313
+ const newScript = duplicateScript(script)
314
+ const parent = script.parentNode
315
+ try {
316
+ parent.insertBefore(newScript, script)
317
+ } catch (e) {
318
+ logError(e)
319
+ } finally {
320
+ script.remove()
321
+ }
322
+ }
323
+ })
324
+ }
325
+
326
+ /**
327
+ * @param {string} response HTML
328
+ * @returns {DocumentFragment & {title: string}} a document fragment representing the response HTML, including
329
+ * a `title` property for any title information found
330
+ */
331
+ function makeFragment(response) {
332
+ // strip head tag to determine shape of response we are dealing with
333
+ const responseWithNoHead = response.replace(HEAD_TAG_REGEX, '')
334
+ const startTag = getStartTag(responseWithNoHead)
335
+ let fragment = null
336
+ if (startTag === 'html') {
337
+ // if it is a full document, parse it and return the body
338
+ fragment = new DocumentFragment()
339
+ const doc = parseHTML(response)
340
+ takeChildrenFor(fragment, doc.body)
341
+ fragment.title = doc.title
342
+ } else if (startTag === 'body') {
343
+ // parse body w/o wrapping in template
344
+ fragment = new DocumentFragment()
345
+ const doc = parseHTML(responseWithNoHead)
346
+ takeChildrenFor(fragment, doc.body)
347
+ fragment.title = doc.title
348
+ } else {
349
+ // otherwise we have non-body partial HTML content, so wrap it in a template to maximize parsing flexibility
350
+ const doc = parseHTML('<body><template class="internal-htmx-wrapper">' + responseWithNoHead + '</template></body>')
351
+ fragment = doc.querySelector('template').content
352
+ // extract title into fragment for later processing
353
+ fragment.title = doc.title
354
+
355
+ // for legacy reasons we support a title tag at the root level of non-body responses, so we need to handle it
356
+ var titleElement = fragment.querySelector('title')
357
+ if (titleElement && titleElement.parentNode === fragment) {
358
+ titleElement.remove()
359
+ fragment.title = titleElement.innerText
360
+ }
361
+ }
362
+ if (fragment) {
363
+ if (htmx.config.allowScriptTags) {
364
+ normalizeScriptTags(fragment)
365
+ } else {
366
+ // remove all script tags if scripts are disabled
367
+ fragment.querySelectorAll('script').forEach((script) => script.remove())
368
+ }
369
+ }
370
+ return fragment
371
+ }
372
+
373
+ /**
374
+ * @param {Function} func
375
+ */
376
+ function maybeCall(func) {
377
+ if (func) {
378
+ func()
379
+ }
380
+ }
381
+
382
+ /**
383
+ * @param {any} o
384
+ * @param {string} type
385
+ * @returns
386
+ */
387
+ function isType(o, type) {
388
+ return Object.prototype.toString.call(o) === '[object ' + type + ']'
389
+ }
390
+
391
+ /**
392
+ * @param {*} o
393
+ * @returns {o is Function}
394
+ */
395
+ function isFunction(o) {
396
+ return isType(o, 'Function')
397
+ }
398
+
399
+ /**
400
+ * @param {*} o
401
+ * @returns {o is Object}
402
+ */
403
+ function isRawObject(o) {
404
+ return isType(o, 'Object')
405
+ }
406
+
407
+ /**
408
+ * getInternalData retrieves "private" data stored by htmx within an element
409
+ * @param {HTMLElement} elt
410
+ * @returns {*}
411
+ */
412
+ function getInternalData(elt) {
413
+ const dataProp = 'htmx-internal-data'
414
+ let data = elt[dataProp]
415
+ if (!data) {
416
+ data = elt[dataProp] = {}
417
+ }
418
+ return data
419
+ }
420
+
421
+ /**
422
+ * toArray converts an ArrayLike object into a real array.
423
+ * @param {ArrayLike} arr
424
+ * @returns {any[]}
425
+ */
426
+ function toArray(arr) {
427
+ const returnArr = []
428
+ if (arr) {
429
+ for (let i = 0; i < arr.length; i++) {
430
+ returnArr.push(arr[i])
431
+ }
432
+ }
433
+ return returnArr
434
+ }
435
+
436
+ /**
437
+ * @template T
438
+ * @callback forEachCallback
439
+ * @param {T} value
440
+ */
441
+ /**
442
+ * @template T
443
+ * @param {{[index: number]: T, length: number}} arr
444
+ * @param {forEachCallback<T>} func
445
+ */
446
+ function forEach(arr, func) {
447
+ if (arr) {
448
+ for (let i = 0; i < arr.length; i++) {
449
+ func(arr[i])
450
+ }
451
+ }
452
+ }
453
+
454
+ function isScrolledIntoView(el) {
455
+ const rect = el.getBoundingClientRect()
456
+ const elemTop = rect.top
457
+ const elemBottom = rect.bottom
458
+ return elemTop < window.innerHeight && elemBottom >= 0
459
+ }
460
+
461
+ function bodyContains(elt) {
462
+ // IE Fix
463
+ if (elt.getRootNode && elt.getRootNode() instanceof window.ShadowRoot) {
464
+ return getDocument().body.contains(elt.getRootNode().host)
465
+ } else {
466
+ return getDocument().body.contains(elt)
467
+ }
468
+ }
469
+
470
+ function splitOnWhitespace(trigger) {
471
+ return trigger.trim().split(/\s+/)
472
+ }
473
+
474
+ /**
475
+ * mergeObjects takes all of the keys from
476
+ * obj2 and duplicates them into obj1
477
+ * @param {Object} obj1
478
+ * @param {Object} obj2
479
+ * @returns {Object}
480
+ */
481
+ function mergeObjects(obj1, obj2) {
482
+ for (const key in obj2) {
483
+ if (obj2.hasOwnProperty(key)) {
484
+ obj1[key] = obj2[key]
485
+ }
486
+ }
487
+ return obj1
488
+ }
489
+
490
+ function parseJSON(jString) {
491
+ try {
492
+ return JSON.parse(jString)
493
+ } catch (error) {
494
+ logError(error)
495
+ return null
496
+ }
497
+ }
498
+
499
+ function canAccessLocalStorage() {
500
+ const test = 'htmx:localStorageTest'
501
+ try {
502
+ localStorage.setItem(test, test)
503
+ localStorage.removeItem(test)
504
+ return true
505
+ } catch (e) {
506
+ return false
507
+ }
508
+ }
509
+
510
+ function normalizePath(path) {
511
+ try {
512
+ const url = new URL(path)
513
+ if (url) {
514
+ path = url.pathname + url.search
515
+ }
516
+ // remove trailing slash, unless index page
517
+ if (!(/^\/$/.test(path))) {
518
+ path = path.replace(/\/+$/, '')
519
+ }
520
+ return path
521
+ } catch (e) {
522
+ // be kind to IE11, which doesn't support URL()
523
+ return path
524
+ }
525
+ }
526
+
527
+ //= =========================================================================================
528
+ // public API
529
+ //= =========================================================================================
530
+
531
+ function internalEval(str) {
532
+ return maybeEval(getDocument().body, function() {
533
+ return eval(str)
534
+ })
535
+ }
536
+
537
+ function onLoadHelper(callback) {
538
+ const value = htmx.on('htmx:load', function(evt) {
539
+ callback(evt.detail.elt)
540
+ })
541
+ return value
542
+ }
543
+
544
+ function logAll() {
545
+ htmx.logger = function(elt, event, data) {
546
+ if (console) {
547
+ console.log(event, elt, data)
548
+ }
549
+ }
550
+ }
551
+
552
+ function logNone() {
553
+ htmx.logger = null
554
+ }
555
+
556
+ function find(eltOrSelector, selector) {
557
+ if (selector) {
558
+ return eltOrSelector.querySelector(selector)
559
+ } else {
560
+ return find(getDocument(), eltOrSelector)
561
+ }
562
+ }
563
+
564
+ function findAll(eltOrSelector, selector) {
565
+ if (selector) {
566
+ return eltOrSelector.querySelectorAll(selector)
567
+ } else {
568
+ return findAll(getDocument(), eltOrSelector)
569
+ }
570
+ }
571
+
572
+ function removeElement(elt, delay) {
573
+ elt = resolveTarget(elt)
574
+ if (delay) {
575
+ setTimeout(function() {
576
+ removeElement(elt)
577
+ elt = null
578
+ }, delay)
579
+ } else {
580
+ parentElt(elt).removeChild(elt)
581
+ }
582
+ }
583
+
584
+ function addClassToElement(elt, clazz, delay) {
585
+ elt = resolveTarget(elt)
586
+ if (delay) {
587
+ setTimeout(function() {
588
+ addClassToElement(elt, clazz)
589
+ elt = null
590
+ }, delay)
591
+ } else {
592
+ elt.classList && elt.classList.add(clazz)
593
+ }
594
+ }
595
+
596
+ function removeClassFromElement(elt, clazz, delay) {
597
+ elt = resolveTarget(elt)
598
+ if (delay) {
599
+ setTimeout(function() {
600
+ removeClassFromElement(elt, clazz)
601
+ elt = null
602
+ }, delay)
603
+ } else {
604
+ if (elt.classList) {
605
+ elt.classList.remove(clazz)
606
+ // if there are no classes left, remove the class attribute
607
+ if (elt.classList.length === 0) {
608
+ elt.removeAttribute('class')
609
+ }
610
+ }
611
+ }
612
+ }
613
+
614
+ function toggleClassOnElement(elt, clazz) {
615
+ elt = resolveTarget(elt)
616
+ elt.classList.toggle(clazz)
617
+ }
618
+
619
+ function takeClassForElement(elt, clazz) {
620
+ elt = resolveTarget(elt)
621
+ forEach(elt.parentElement.children, function(child) {
622
+ removeClassFromElement(child, clazz)
623
+ })
624
+ addClassToElement(elt, clazz)
625
+ }
626
+
627
+ function closest(elt, selector) {
628
+ elt = resolveTarget(elt)
629
+ if (elt.closest) {
630
+ return elt.closest(selector)
631
+ } else {
632
+ // TODO remove when IE goes away
633
+ do {
634
+ if (elt == null || matches(elt, selector)) {
635
+ return elt
636
+ }
637
+ }
638
+ while (elt = elt && parentElt(elt))
639
+ return null
640
+ }
641
+ }
642
+
643
+ function startsWith(str, prefix) {
644
+ return str.substring(0, prefix.length) === prefix
645
+ }
646
+
647
+ function endsWith(str, suffix) {
648
+ return str.substring(str.length - suffix.length) === suffix
649
+ }
650
+
651
+ function normalizeSelector(selector) {
652
+ const trimmedSelector = selector.trim()
653
+ if (startsWith(trimmedSelector, '<') && endsWith(trimmedSelector, '/>')) {
654
+ return trimmedSelector.substring(1, trimmedSelector.length - 2)
655
+ } else {
656
+ return trimmedSelector
657
+ }
658
+ }
659
+
660
+ function querySelectorAllExt(elt, selector, global) {
661
+ if (selector.indexOf('closest ') === 0) {
662
+ return [closest(elt, normalizeSelector(selector.substr(8)))]
663
+ } else if (selector.indexOf('find ') === 0) {
664
+ return [find(elt, normalizeSelector(selector.substr(5)))]
665
+ } else if (selector === 'next') {
666
+ return [elt.nextElementSibling]
667
+ } else if (selector.indexOf('next ') === 0) {
668
+ return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)]
669
+ } else if (selector === 'previous') {
670
+ return [elt.previousElementSibling]
671
+ } else if (selector.indexOf('previous ') === 0) {
672
+ return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)]
673
+ } else if (selector === 'document') {
674
+ return [document]
675
+ } else if (selector === 'window') {
676
+ return [window]
677
+ } else if (selector === 'body') {
678
+ return [document.body]
679
+ } else if (selector === 'root') {
680
+ return [getRootNode(elt, !!global)]
681
+ } else if (selector.indexOf('global ') === 0) {
682
+ return querySelectorAllExt(elt, selector.slice(7), true)
683
+ } else {
684
+ return getRootNode(elt, !!global).querySelectorAll(normalizeSelector(selector))
685
+ }
686
+ }
687
+
688
+ var scanForwardQuery = function(start, match, global) {
689
+ const results = getRootNode(start, global).querySelectorAll(match)
690
+ for (let i = 0; i < results.length; i++) {
691
+ const elt = results[i]
692
+ if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) {
693
+ return elt
694
+ }
695
+ }
696
+ }
697
+
698
+ var scanBackwardsQuery = function(start, match, global) {
699
+ const results = getRootNode(start, global).querySelectorAll(match)
700
+ for (let i = results.length - 1; i >= 0; i--) {
701
+ const elt = results[i]
702
+ if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) {
703
+ return elt
704
+ }
705
+ }
706
+ }
707
+
708
+ function querySelectorExt(eltOrSelector, selector) {
709
+ if (selector) {
710
+ return querySelectorAllExt(eltOrSelector, selector)[0]
711
+ } else {
712
+ return querySelectorAllExt(getDocument().body, eltOrSelector)[0]
713
+ }
714
+ }
715
+
716
+ /**
717
+ *
718
+ * @param {string|Element} arg2
719
+ * @param {Element} [context]
720
+ * @returns {Element}
721
+ */
722
+ function resolveTarget(arg2, context) {
723
+ if (isType(arg2, 'String')) {
724
+ return find(context || document, arg2)
725
+ } else {
726
+ // @ts-ignore
727
+ return arg2
728
+ }
729
+ }
730
+
731
+ function processEventArgs(arg1, arg2, arg3) {
732
+ if (isFunction(arg2)) {
733
+ return {
734
+ target: getDocument().body,
735
+ event: arg1,
736
+ listener: arg2
737
+ }
738
+ } else {
739
+ return {
740
+ target: resolveTarget(arg1),
741
+ event: arg2,
742
+ listener: arg3
743
+ }
744
+ }
745
+ }
746
+
747
+ function addEventListenerImpl(arg1, arg2, arg3) {
748
+ ready(function() {
749
+ const eventArgs = processEventArgs(arg1, arg2, arg3)
750
+ eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener)
751
+ })
752
+ const b = isFunction(arg2)
753
+ return b ? arg2 : arg3
754
+ }
755
+
756
+ function removeEventListenerImpl(arg1, arg2, arg3) {
757
+ ready(function() {
758
+ const eventArgs = processEventArgs(arg1, arg2, arg3)
759
+ eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener)
760
+ })
761
+ return isFunction(arg2) ? arg2 : arg3
762
+ }
763
+
764
+ //= ===================================================================
765
+ // Node processing
766
+ //= ===================================================================
767
+
768
+ const DUMMY_ELT = getDocument().createElement('output') // dummy element for bad selectors
769
+ function findAttributeTargets(elt, attrName) {
770
+ const attrTarget = getClosestAttributeValue(elt, attrName)
771
+ if (attrTarget) {
772
+ if (attrTarget === 'this') {
773
+ return [findThisElement(elt, attrName)]
774
+ } else {
775
+ const result = querySelectorAllExt(elt, attrTarget)
776
+ if (result.length === 0) {
777
+ logError('The selector "' + attrTarget + '" on ' + attrName + ' returned no matches!')
778
+ return [DUMMY_ELT]
779
+ } else {
780
+ return result
781
+ }
782
+ }
783
+ }
784
+ }
785
+
786
+ function findThisElement(elt, attribute) {
787
+ return getClosestMatch(elt, function(elt) {
788
+ return getAttributeValue(elt, attribute) != null
789
+ })
790
+ }
791
+
792
+ function getTarget(elt) {
793
+ const targetStr = getClosestAttributeValue(elt, 'hx-target')
794
+ if (targetStr) {
795
+ if (targetStr === 'this') {
796
+ return findThisElement(elt, 'hx-target')
797
+ } else {
798
+ return querySelectorExt(elt, targetStr)
799
+ }
800
+ } else {
801
+ const data = getInternalData(elt)
802
+ if (data.boosted) {
803
+ return getDocument().body
804
+ } else {
805
+ return elt
806
+ }
807
+ }
808
+ }
809
+
810
+ function shouldSettleAttribute(name) {
811
+ const attributesToSettle = htmx.config.attributesToSettle
812
+ for (let i = 0; i < attributesToSettle.length; i++) {
813
+ if (name === attributesToSettle[i]) {
814
+ return true
815
+ }
816
+ }
817
+ return false
818
+ }
819
+
820
+ function cloneAttributes(mergeTo, mergeFrom) {
821
+ forEach(mergeTo.attributes, function(attr) {
822
+ if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
823
+ mergeTo.removeAttribute(attr.name)
824
+ }
825
+ })
826
+ forEach(mergeFrom.attributes, function(attr) {
827
+ if (shouldSettleAttribute(attr.name)) {
828
+ mergeTo.setAttribute(attr.name, attr.value)
829
+ }
830
+ })
831
+ }
832
+
833
+ function isInlineSwap(swapStyle, target) {
834
+ const extensions = getExtensions(target)
835
+ for (let i = 0; i < extensions.length; i++) {
836
+ const extension = extensions[i]
837
+ try {
838
+ if (extension.isInlineSwap(swapStyle)) {
839
+ return true
840
+ }
841
+ } catch (e) {
842
+ logError(e)
843
+ }
844
+ }
845
+ return swapStyle === 'outerHTML'
846
+ }
847
+
848
+ /**
849
+ *
850
+ * @param {string} oobValue
851
+ * @param {Element} oobElement
852
+ * @param {*} settleInfo
853
+ * @returns
854
+ */
855
+ function oobSwap(oobValue, oobElement, settleInfo) {
856
+ let selector = '#' + getRawAttribute(oobElement, 'id')
857
+ let swapStyle = 'outerHTML'
858
+ if (oobValue === 'true') {
859
+ // do nothing
860
+ } else if (oobValue.indexOf(':') > 0) {
861
+ swapStyle = oobValue.substr(0, oobValue.indexOf(':'))
862
+ selector = oobValue.substr(oobValue.indexOf(':') + 1, oobValue.length)
863
+ } else {
864
+ swapStyle = oobValue
865
+ }
866
+
867
+ const targets = getDocument().querySelectorAll(selector)
868
+ if (targets) {
869
+ forEach(
870
+ targets,
871
+ function(target) {
872
+ let fragment
873
+ const oobElementClone = oobElement.cloneNode(true)
874
+ fragment = getDocument().createDocumentFragment()
875
+ fragment.appendChild(oobElementClone)
876
+ if (!isInlineSwap(swapStyle, target)) {
877
+ fragment = oobElementClone // if this is not an inline swap, we use the content of the node, not the node itself
878
+ }
879
+
880
+ const beforeSwapDetails = { shouldSwap: true, target, fragment }
881
+ if (!triggerEvent(target, 'htmx:oobBeforeSwap', beforeSwapDetails)) return
882
+
883
+ target = beforeSwapDetails.target // allow re-targeting
884
+ if (beforeSwapDetails.shouldSwap) {
885
+ swapWithStyle(swapStyle, target, target, fragment, settleInfo)
886
+ }
887
+ forEach(settleInfo.elts, function(elt) {
888
+ triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails)
889
+ })
890
+ }
891
+ )
892
+ oobElement.parentNode.removeChild(oobElement)
893
+ } else {
894
+ oobElement.parentNode.removeChild(oobElement)
895
+ triggerErrorEvent(getDocument().body, 'htmx:oobErrorNoTarget', { content: oobElement })
896
+ }
897
+ return oobValue
898
+ }
899
+
900
+ function handlePreservedElements(fragment) {
901
+ forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function(preservedElt) {
902
+ const id = getAttributeValue(preservedElt, 'id')
903
+ const oldElt = getDocument().getElementById(id)
904
+ if (oldElt != null) {
905
+ preservedElt.parentNode.replaceChild(oldElt, preservedElt)
906
+ }
907
+ })
908
+ }
909
+
910
+ function handleAttributes(parentNode, fragment, settleInfo) {
911
+ forEach(fragment.querySelectorAll('[id]'), function(newNode) {
912
+ const id = getRawAttribute(newNode, 'id')
913
+ if (id && id.length > 0) {
914
+ const normalizedId = id.replace("'", "\\'")
915
+ const normalizedTag = newNode.tagName.replace(':', '\\:')
916
+ const oldNode = parentNode.querySelector(normalizedTag + "[id='" + normalizedId + "']")
917
+ if (oldNode && oldNode !== parentNode) {
918
+ const newAttributes = newNode.cloneNode()
919
+ cloneAttributes(newNode, oldNode)
920
+ settleInfo.tasks.push(function() {
921
+ cloneAttributes(newNode, newAttributes)
922
+ })
923
+ }
924
+ }
925
+ })
926
+ }
927
+
928
+ function makeAjaxLoadTask(child) {
929
+ return function() {
930
+ removeClassFromElement(child, htmx.config.addedClass)
931
+ processNode(child)
932
+ processFocus(child)
933
+ triggerEvent(child, 'htmx:load')
934
+ }
935
+ }
936
+
937
+ function processFocus(child) {
938
+ const autofocus = '[autofocus]'
939
+ const autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
940
+ if (autoFocusedElt != null) {
941
+ autoFocusedElt.focus()
942
+ }
943
+ }
944
+
945
+ function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
946
+ handleAttributes(parentNode, fragment, settleInfo)
947
+ while (fragment.childNodes.length > 0) {
948
+ const child = fragment.firstChild
949
+ addClassToElement(child, htmx.config.addedClass)
950
+ parentNode.insertBefore(child, insertBefore)
951
+ if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
952
+ settleInfo.tasks.push(makeAjaxLoadTask(child))
953
+ }
954
+ }
955
+ }
956
+
957
+ // based on https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0,
958
+ // derived from Java's string hashcode implementation
959
+ function stringHash(string, hash) {
960
+ let char = 0
961
+ while (char < string.length) {
962
+ hash = (hash << 5) - hash + string.charCodeAt(char++) | 0 // bitwise or ensures we have a 32-bit int
963
+ }
964
+ return hash
965
+ }
966
+
967
+ function attributeHash(elt) {
968
+ let hash = 0
969
+ // IE fix
970
+ if (elt.attributes) {
971
+ for (let i = 0; i < elt.attributes.length; i++) {
972
+ const attribute = elt.attributes[i]
973
+ if (attribute.value) { // only include attributes w/ actual values (empty is same as non-existent)
974
+ hash = stringHash(attribute.name, hash)
975
+ hash = stringHash(attribute.value, hash)
976
+ }
977
+ }
978
+ }
979
+ return hash
980
+ }
981
+
982
+ function deInitOnHandlers(elt) {
983
+ const internalData = getInternalData(elt)
984
+ if (internalData.onHandlers) {
985
+ for (let i = 0; i < internalData.onHandlers.length; i++) {
986
+ const handlerInfo = internalData.onHandlers[i]
987
+ elt.removeEventListener(handlerInfo.event, handlerInfo.listener)
988
+ }
989
+ delete internalData.onHandlers
990
+ }
991
+ }
992
+
993
+ function deInitNode(element) {
994
+ const internalData = getInternalData(element)
995
+ if (internalData.timeout) {
996
+ clearTimeout(internalData.timeout)
997
+ }
998
+ if (internalData.listenerInfos) {
999
+ forEach(internalData.listenerInfos, function(info) {
1000
+ if (info.on) {
1001
+ info.on.removeEventListener(info.trigger, info.listener)
1002
+ }
1003
+ })
1004
+ }
1005
+ deInitOnHandlers(element)
1006
+ forEach(Object.keys(internalData), function(key) { delete internalData[key] })
1007
+ }
1008
+
1009
+ function cleanUpElement(element) {
1010
+ triggerEvent(element, 'htmx:beforeCleanupElement')
1011
+ deInitNode(element)
1012
+ if (element.children) { // IE
1013
+ forEach(element.children, function(child) { cleanUpElement(child) })
1014
+ }
1015
+ }
1016
+
1017
+ function swapOuterHTML(target, fragment, settleInfo) {
1018
+ // @type {HTMLElement}
1019
+ let newElt
1020
+ const eltBeforeNewContent = target.previousSibling
1021
+ insertNodesBefore(parentElt(target), target, fragment, settleInfo)
1022
+ if (eltBeforeNewContent == null) {
1023
+ newElt = parentElt(target).firstChild
1024
+ } else {
1025
+ newElt = eltBeforeNewContent.nextSibling
1026
+ }
1027
+ settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target })
1028
+ while (newElt && newElt !== target) {
1029
+ if (newElt.nodeType === Node.ELEMENT_NODE) {
1030
+ settleInfo.elts.push(newElt)
1031
+ }
1032
+ newElt = newElt.nextElementSibling
1033
+ }
1034
+ cleanUpElement(target)
1035
+ target.remove()
1036
+ }
1037
+
1038
+ function swapAfterBegin(target, fragment, settleInfo) {
1039
+ return insertNodesBefore(target, target.firstChild, fragment, settleInfo)
1040
+ }
1041
+
1042
+ function swapBeforeBegin(target, fragment, settleInfo) {
1043
+ return insertNodesBefore(parentElt(target), target, fragment, settleInfo)
1044
+ }
1045
+
1046
+ function swapBeforeEnd(target, fragment, settleInfo) {
1047
+ return insertNodesBefore(target, null, fragment, settleInfo)
1048
+ }
1049
+
1050
+ function swapAfterEnd(target, fragment, settleInfo) {
1051
+ return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo)
1052
+ }
1053
+ function swapDelete(target, fragment, settleInfo) {
1054
+ cleanUpElement(target)
1055
+ return parentElt(target).removeChild(target)
1056
+ }
1057
+
1058
+ function swapInnerHTML(target, fragment, settleInfo) {
1059
+ const firstChild = target.firstChild
1060
+ insertNodesBefore(target, firstChild, fragment, settleInfo)
1061
+ if (firstChild) {
1062
+ while (firstChild.nextSibling) {
1063
+ cleanUpElement(firstChild.nextSibling)
1064
+ target.removeChild(firstChild.nextSibling)
1065
+ }
1066
+ cleanUpElement(firstChild)
1067
+ target.removeChild(firstChild)
1068
+ }
1069
+ }
1070
+
1071
+ /**
1072
+ * @param {string} swapStyle
1073
+ * @param {HTMLElement} elt
1074
+ * @param {HTMLElement} target
1075
+ * @param {Node} fragment
1076
+ * @param {{ tasks: (() => void)[]; }} settleInfo
1077
+ */
1078
+ function swapWithStyle(swapStyle, elt, target, fragment, settleInfo) {
1079
+ switch (swapStyle) {
1080
+ case 'none':
1081
+ return
1082
+ case 'outerHTML':
1083
+ swapOuterHTML(target, fragment, settleInfo)
1084
+ return
1085
+ case 'afterbegin':
1086
+ swapAfterBegin(target, fragment, settleInfo)
1087
+ return
1088
+ case 'beforebegin':
1089
+ swapBeforeBegin(target, fragment, settleInfo)
1090
+ return
1091
+ case 'beforeend':
1092
+ swapBeforeEnd(target, fragment, settleInfo)
1093
+ return
1094
+ case 'afterend':
1095
+ swapAfterEnd(target, fragment, settleInfo)
1096
+ return
1097
+ case 'delete':
1098
+ swapDelete(target, fragment, settleInfo)
1099
+ return
1100
+ default:
1101
+ var extensions = getExtensions(elt)
1102
+ for (let i = 0; i < extensions.length; i++) {
1103
+ const ext = extensions[i]
1104
+ try {
1105
+ const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo)
1106
+ if (newElements) {
1107
+ if (typeof newElements.length !== 'undefined') {
1108
+ // if handleSwap returns an array (like) of elements, we handle them
1109
+ for (let j = 0; j < newElements.length; j++) {
1110
+ const child = newElements[j]
1111
+ if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
1112
+ settleInfo.tasks.push(makeAjaxLoadTask(child))
1113
+ }
1114
+ }
1115
+ }
1116
+ return
1117
+ }
1118
+ } catch (e) {
1119
+ logError(e)
1120
+ }
1121
+ }
1122
+ if (swapStyle === 'innerHTML') {
1123
+ swapInnerHTML(target, fragment, settleInfo)
1124
+ } else {
1125
+ swapWithStyle(htmx.config.defaultSwapStyle, elt, target, fragment, settleInfo)
1126
+ }
1127
+ }
1128
+ }
1129
+
1130
+ function findAndSwapOobElements(fragment, settleInfo) {
1131
+ forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function(oobElement) {
1132
+ const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
1133
+ if (oobValue != null) {
1134
+ oobSwap(oobValue, oobElement, settleInfo)
1135
+ }
1136
+ })
1137
+ }
1138
+
1139
+ /**
1140
+ * @callback swapCallback
1141
+ */
1142
+
1143
+ /**
1144
+ * @typedef {Object} SwapOptions
1145
+ * @property {?string} select
1146
+ * @property {?string} selectOOB
1147
+ * @property {?*} eventInfo
1148
+ * @property {?*} anchor
1149
+ * @property {?HTMLElement} contextElement
1150
+ * @property {?swapCallback} afterSwapCallback
1151
+ * @property {?swapCallback} afterSettleCallback
1152
+ */
1153
+
1154
+ /**
1155
+ * Implements complete swapping pipeline, including: focus and selection preservation,
1156
+ * title updates, scroll, OOB swapping, normal swapping and settling
1157
+ * @param {string|Element} target
1158
+ * @param {string} content
1159
+ * @param {import("./htmx").HtmxSwapSpecification} swapSpec
1160
+ * @param {SwapOptions} swapOptions
1161
+ */
1162
+ function swap(target, content, swapSpec, swapOptions) {
1163
+ if (!swapOptions) {
1164
+ swapOptions = {}
1165
+ }
1166
+
1167
+ target = resolveTarget(target)
1168
+
1169
+ // preserve focus and selection
1170
+ const activeElt = document.activeElement
1171
+ let selectionInfo = {}
1172
+ try {
1173
+ selectionInfo = {
1174
+ elt: activeElt,
1175
+ // @ts-ignore
1176
+ start: activeElt ? activeElt.selectionStart : null,
1177
+ // @ts-ignore
1178
+ end: activeElt ? activeElt.selectionEnd : null
1179
+ }
1180
+ } catch (e) {
1181
+ // safari issue - see https://github.com/microsoft/playwright/issues/5894
1182
+ }
1183
+ const settleInfo = makeSettleInfo(target)
1184
+
1185
+ let fragment = makeFragment(content)
1186
+
1187
+ settleInfo.title = fragment.title
1188
+
1189
+ // select-oob swaps
1190
+ if (swapOptions.selectOOB) {
1191
+ const oobSelectValues = swapOptions.selectOOB.split(',')
1192
+ for (let i = 0; i < oobSelectValues.length; i++) {
1193
+ const oobSelectValue = oobSelectValues[i].split(':', 2)
1194
+ let id = oobSelectValue[0].trim()
1195
+ if (id.indexOf('#') === 0) {
1196
+ id = id.substring(1)
1197
+ }
1198
+ const oobValue = oobSelectValue[1] || 'true'
1199
+ const oobElement = fragment.querySelector('#' + id)
1200
+ if (oobElement) {
1201
+ oobSwap(oobValue, oobElement, settleInfo)
1202
+ }
1203
+ }
1204
+ }
1205
+ // oob swaps
1206
+ findAndSwapOobElements(fragment, settleInfo)
1207
+ forEach(findAll(fragment, 'template'), function(template) {
1208
+ findAndSwapOobElements(template.content, settleInfo)
1209
+ if (template.content.childElementCount === 0) {
1210
+ // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
1211
+ template.remove()
1212
+ }
1213
+ })
1214
+
1215
+ // normal swap
1216
+ if (swapOptions.select) {
1217
+ const newFragment = getDocument().createDocumentFragment()
1218
+ forEach(fragment.querySelectorAll(swapOptions.select), function(node) {
1219
+ newFragment.appendChild(node)
1220
+ })
1221
+ fragment = newFragment
1222
+ }
1223
+ handlePreservedElements(fragment)
1224
+ swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
1225
+
1226
+ // apply saved focus and selection information to swapped content
1227
+ if (selectionInfo.elt &&
1228
+ !bodyContains(selectionInfo.elt) &&
1229
+ getRawAttribute(selectionInfo.elt, 'id')) {
1230
+ const newActiveElt = document.getElementById(getRawAttribute(selectionInfo.elt, 'id'))
1231
+ const focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll }
1232
+ if (newActiveElt) {
1233
+ // @ts-ignore
1234
+ if (selectionInfo.start && newActiveElt.setSelectionRange) {
1235
+ try {
1236
+ // @ts-ignore
1237
+ newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end)
1238
+ } catch (e) {
1239
+ // the setSelectionRange method is present on fields that don't support it, so just let this fail
1240
+ }
1241
+ }
1242
+ newActiveElt.focus(focusOptions)
1243
+ }
1244
+ }
1245
+
1246
+ target.classList.remove(htmx.config.swappingClass)
1247
+ forEach(settleInfo.elts, function(elt) {
1248
+ if (elt.classList) {
1249
+ elt.classList.add(htmx.config.settlingClass)
1250
+ }
1251
+ triggerEvent(elt, 'htmx:afterSwap', swapOptions.eventInfo)
1252
+ })
1253
+ if (swapOptions.afterSwapCallback) {
1254
+ swapOptions.afterSwapCallback()
1255
+ }
1256
+
1257
+ // merge in new title after swap but before settle
1258
+ if (!swapSpec.ignoreTitle) {
1259
+ handleTitle(settleInfo.title)
1260
+ }
1261
+
1262
+ // settle
1263
+ const doSettle = function() {
1264
+ forEach(settleInfo.tasks, function(task) {
1265
+ task.call()
1266
+ })
1267
+ forEach(settleInfo.elts, function(elt) {
1268
+ if (elt.classList) {
1269
+ elt.classList.remove(htmx.config.settlingClass)
1270
+ }
1271
+ triggerEvent(elt, 'htmx:afterSettle', swapOptions.eventInfo)
1272
+ })
1273
+
1274
+ if (swapOptions.anchor) {
1275
+ const anchorTarget = resolveTarget('#' + swapOptions.anchor)
1276
+ if (anchorTarget) {
1277
+ anchorTarget.scrollIntoView({ block: 'start', behavior: 'auto' })
1278
+ }
1279
+ }
1280
+
1281
+ updateScrollState(settleInfo.elts, swapSpec)
1282
+ if (swapOptions.afterSettleCallback) {
1283
+ swapOptions.afterSettleCallback()
1284
+ }
1285
+ }
1286
+
1287
+ if (swapSpec.settleDelay > 0) {
1288
+ setTimeout(doSettle, swapSpec.settleDelay)
1289
+ } else {
1290
+ doSettle()
1291
+ }
1292
+ }
1293
+
1294
+ function handleTriggerHeader(xhr, header, elt) {
1295
+ const triggerBody = xhr.getResponseHeader(header)
1296
+ if (triggerBody.indexOf('{') === 0) {
1297
+ const triggers = parseJSON(triggerBody)
1298
+ for (const eventName in triggers) {
1299
+ if (triggers.hasOwnProperty(eventName)) {
1300
+ let detail = triggers[eventName]
1301
+ if (!isRawObject(detail)) {
1302
+ detail = { value: detail }
1303
+ }
1304
+ triggerEvent(elt, eventName, detail)
1305
+ }
1306
+ }
1307
+ } else {
1308
+ const eventNames = triggerBody.split(',')
1309
+ for (let i = 0; i < eventNames.length; i++) {
1310
+ triggerEvent(elt, eventNames[i].trim(), [])
1311
+ }
1312
+ }
1313
+ }
1314
+
1315
+ const WHITESPACE = /\s/
1316
+ const WHITESPACE_OR_COMMA = /[\s,]/
1317
+ const SYMBOL_START = /[_$a-zA-Z]/
1318
+ const SYMBOL_CONT = /[_$a-zA-Z0-9]/
1319
+ const STRINGISH_START = ['"', "'", '/']
1320
+ const NOT_WHITESPACE = /[^\s]/
1321
+ const COMBINED_SELECTOR_START = /[{(]/
1322
+ const COMBINED_SELECTOR_END = /[})]/
1323
+ function tokenizeString(str) {
1324
+ const tokens = []
1325
+ let position = 0
1326
+ while (position < str.length) {
1327
+ if (SYMBOL_START.exec(str.charAt(position))) {
1328
+ var startPosition = position
1329
+ while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
1330
+ position++
1331
+ }
1332
+ tokens.push(str.substr(startPosition, position - startPosition + 1))
1333
+ } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
1334
+ const startChar = str.charAt(position)
1335
+ var startPosition = position
1336
+ position++
1337
+ while (position < str.length && str.charAt(position) !== startChar) {
1338
+ if (str.charAt(position) === '\\') {
1339
+ position++
1340
+ }
1341
+ position++
1342
+ }
1343
+ tokens.push(str.substr(startPosition, position - startPosition + 1))
1344
+ } else {
1345
+ const symbol = str.charAt(position)
1346
+ tokens.push(symbol)
1347
+ }
1348
+ position++
1349
+ }
1350
+ return tokens
1351
+ }
1352
+
1353
+ function isPossibleRelativeReference(token, last, paramName) {
1354
+ return SYMBOL_START.exec(token.charAt(0)) &&
1355
+ token !== 'true' &&
1356
+ token !== 'false' &&
1357
+ token !== 'this' &&
1358
+ token !== paramName &&
1359
+ last !== '.'
1360
+ }
1361
+
1362
+ function maybeGenerateConditional(elt, tokens, paramName) {
1363
+ if (tokens[0] === '[') {
1364
+ tokens.shift()
1365
+ let bracketCount = 1
1366
+ let conditionalSource = ' return (function(' + paramName + '){ return ('
1367
+ let last = null
1368
+ while (tokens.length > 0) {
1369
+ const token = tokens[0]
1370
+ if (token === ']') {
1371
+ bracketCount--
1372
+ if (bracketCount === 0) {
1373
+ if (last === null) {
1374
+ conditionalSource = conditionalSource + 'true'
1375
+ }
1376
+ tokens.shift()
1377
+ conditionalSource += ')})'
1378
+ try {
1379
+ const conditionFunction = maybeEval(elt, function() {
1380
+ return Function(conditionalSource)()
1381
+ },
1382
+ function() { return true })
1383
+ conditionFunction.source = conditionalSource
1384
+ return conditionFunction
1385
+ } catch (e) {
1386
+ triggerErrorEvent(getDocument().body, 'htmx:syntax:error', { error: e, source: conditionalSource })
1387
+ return null
1388
+ }
1389
+ }
1390
+ } else if (token === '[') {
1391
+ bracketCount++
1392
+ }
1393
+ if (isPossibleRelativeReference(token, last, paramName)) {
1394
+ conditionalSource += '((' + paramName + '.' + token + ') ? (' + paramName + '.' + token + ') : (window.' + token + '))'
1395
+ } else {
1396
+ conditionalSource = conditionalSource + token
1397
+ }
1398
+ last = tokens.shift()
1399
+ }
1400
+ }
1401
+ }
1402
+
1403
+ function consumeUntil(tokens, match) {
1404
+ let result = ''
1405
+ while (tokens.length > 0 && !match.test(tokens[0])) {
1406
+ result += tokens.shift()
1407
+ }
1408
+ return result
1409
+ }
1410
+
1411
+ function consumeCSSSelector(tokens) {
1412
+ let result
1413
+ if (tokens.length > 0 && COMBINED_SELECTOR_START.test(tokens[0])) {
1414
+ tokens.shift()
1415
+ result = consumeUntil(tokens, COMBINED_SELECTOR_END).trim()
1416
+ tokens.shift()
1417
+ } else {
1418
+ result = consumeUntil(tokens, WHITESPACE_OR_COMMA)
1419
+ }
1420
+ return result
1421
+ }
1422
+
1423
+ const INPUT_SELECTOR = 'input, textarea, select'
1424
+
1425
+ /**
1426
+ * @param {HTMLElement} elt
1427
+ * @param {string} explicitTrigger
1428
+ * @param {cache} cache for trigger specs
1429
+ * @returns {import("./htmx").HtmxTriggerSpecification[]}
1430
+ */
1431
+ function parseAndCacheTrigger(elt, explicitTrigger, cache) {
1432
+ const triggerSpecs = []
1433
+ const tokens = tokenizeString(explicitTrigger)
1434
+ do {
1435
+ consumeUntil(tokens, NOT_WHITESPACE)
1436
+ const initialLength = tokens.length
1437
+ const trigger = consumeUntil(tokens, /[,\[\s]/)
1438
+ if (trigger !== '') {
1439
+ if (trigger === 'every') {
1440
+ const every = { trigger: 'every' }
1441
+ consumeUntil(tokens, NOT_WHITESPACE)
1442
+ every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/))
1443
+ consumeUntil(tokens, NOT_WHITESPACE)
1444
+ var eventFilter = maybeGenerateConditional(elt, tokens, 'event')
1445
+ if (eventFilter) {
1446
+ every.eventFilter = eventFilter
1447
+ }
1448
+ triggerSpecs.push(every)
1449
+ } else {
1450
+ const triggerSpec = { trigger }
1451
+ var eventFilter = maybeGenerateConditional(elt, tokens, 'event')
1452
+ if (eventFilter) {
1453
+ triggerSpec.eventFilter = eventFilter
1454
+ }
1455
+ while (tokens.length > 0 && tokens[0] !== ',') {
1456
+ consumeUntil(tokens, NOT_WHITESPACE)
1457
+ const token = tokens.shift()
1458
+ if (token === 'changed') {
1459
+ triggerSpec.changed = true
1460
+ } else if (token === 'once') {
1461
+ triggerSpec.once = true
1462
+ } else if (token === 'consume') {
1463
+ triggerSpec.consume = true
1464
+ } else if (token === 'delay' && tokens[0] === ':') {
1465
+ tokens.shift()
1466
+ triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA))
1467
+ } else if (token === 'from' && tokens[0] === ':') {
1468
+ tokens.shift()
1469
+ if (COMBINED_SELECTOR_START.test(tokens[0])) {
1470
+ var from_arg = consumeCSSSelector(tokens)
1471
+ } else {
1472
+ var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA)
1473
+ if (from_arg === 'closest' || from_arg === 'find' || from_arg === 'next' || from_arg === 'previous') {
1474
+ tokens.shift()
1475
+ const selector = consumeCSSSelector(tokens)
1476
+ // `next` and `previous` allow a selector-less syntax
1477
+ if (selector.length > 0) {
1478
+ from_arg += ' ' + selector
1479
+ }
1480
+ }
1481
+ }
1482
+ triggerSpec.from = from_arg
1483
+ } else if (token === 'target' && tokens[0] === ':') {
1484
+ tokens.shift()
1485
+ triggerSpec.target = consumeCSSSelector(tokens)
1486
+ } else if (token === 'throttle' && tokens[0] === ':') {
1487
+ tokens.shift()
1488
+ triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA))
1489
+ } else if (token === 'queue' && tokens[0] === ':') {
1490
+ tokens.shift()
1491
+ triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA)
1492
+ } else if (token === 'root' && tokens[0] === ':') {
1493
+ tokens.shift()
1494
+ triggerSpec[token] = consumeCSSSelector(tokens)
1495
+ } else if (token === 'threshold' && tokens[0] === ':') {
1496
+ tokens.shift()
1497
+ triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA)
1498
+ } else {
1499
+ triggerErrorEvent(elt, 'htmx:syntax:error', { token: tokens.shift() })
1500
+ }
1501
+ }
1502
+ triggerSpecs.push(triggerSpec)
1503
+ }
1504
+ }
1505
+ if (tokens.length === initialLength) {
1506
+ triggerErrorEvent(elt, 'htmx:syntax:error', { token: tokens.shift() })
1507
+ }
1508
+ consumeUntil(tokens, NOT_WHITESPACE)
1509
+ } while (tokens[0] === ',' && tokens.shift())
1510
+ if (cache) {
1511
+ cache[explicitTrigger] = triggerSpecs
1512
+ }
1513
+ return triggerSpecs
1514
+ }
1515
+
1516
+ /**
1517
+ * @param {HTMLElement} elt
1518
+ * @returns {import("./htmx").HtmxTriggerSpecification[]}
1519
+ */
1520
+ function getTriggerSpecs(elt) {
1521
+ const explicitTrigger = getAttributeValue(elt, 'hx-trigger')
1522
+ let triggerSpecs = []
1523
+ if (explicitTrigger) {
1524
+ const cache = htmx.config.triggerSpecsCache
1525
+ triggerSpecs = (cache && cache[explicitTrigger]) || parseAndCacheTrigger(elt, explicitTrigger, cache)
1526
+ }
1527
+
1528
+ if (triggerSpecs.length > 0) {
1529
+ return triggerSpecs
1530
+ } else if (matches(elt, 'form')) {
1531
+ return [{ trigger: 'submit' }]
1532
+ } else if (matches(elt, 'input[type="button"], input[type="submit"]')) {
1533
+ return [{ trigger: 'click' }]
1534
+ } else if (matches(elt, INPUT_SELECTOR)) {
1535
+ return [{ trigger: 'change' }]
1536
+ } else {
1537
+ return [{ trigger: 'click' }]
1538
+ }
1539
+ }
1540
+
1541
+ function cancelPolling(elt) {
1542
+ getInternalData(elt).cancelled = true
1543
+ }
1544
+
1545
+ function processPolling(elt, handler, spec) {
1546
+ const nodeData = getInternalData(elt)
1547
+ nodeData.timeout = setTimeout(function() {
1548
+ if (bodyContains(elt) && nodeData.cancelled !== true) {
1549
+ if (!maybeFilterEvent(spec, elt, makeEvent('hx:poll:trigger', {
1550
+ triggerSpec: spec,
1551
+ target: elt
1552
+ }))) {
1553
+ handler(elt)
1554
+ }
1555
+ processPolling(elt, handler, spec)
1556
+ }
1557
+ }, spec.pollInterval)
1558
+ }
1559
+
1560
+ function isLocalLink(elt) {
1561
+ return location.hostname === elt.hostname &&
1562
+ getRawAttribute(elt, 'href') &&
1563
+ getRawAttribute(elt, 'href').indexOf('#') !== 0
1564
+ }
1565
+
1566
+ function boostElement(elt, nodeData, triggerSpecs) {
1567
+ if ((elt.tagName === 'A' && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || elt.tagName === 'FORM') {
1568
+ nodeData.boosted = true
1569
+ let verb, path
1570
+ if (elt.tagName === 'A') {
1571
+ verb = 'get'
1572
+ path = getRawAttribute(elt, 'href')
1573
+ } else {
1574
+ const rawAttribute = getRawAttribute(elt, 'method')
1575
+ verb = rawAttribute ? rawAttribute.toLowerCase() : 'get'
1576
+ if (verb === 'get') {
1577
+ }
1578
+ path = getRawAttribute(elt, 'action')
1579
+ }
1580
+ triggerSpecs.forEach(function(triggerSpec) {
1581
+ addEventListener(elt, function(elt, evt) {
1582
+ if (closest(elt, htmx.config.disableSelector)) {
1583
+ cleanUpElement(elt)
1584
+ return
1585
+ }
1586
+ issueAjaxRequest(verb, path, elt, evt)
1587
+ }, nodeData, triggerSpec, true)
1588
+ })
1589
+ }
1590
+ }
1591
+
1592
+ /**
1593
+ *
1594
+ * @param {Event} evt
1595
+ * @param {HTMLElement} elt
1596
+ * @returns
1597
+ */
1598
+ function shouldCancel(evt, elt) {
1599
+ if (evt.type === 'submit' || evt.type === 'click') {
1600
+ if (elt.tagName === 'FORM') {
1601
+ return true
1602
+ }
1603
+ if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) {
1604
+ return true
1605
+ }
1606
+ if (elt.tagName === 'A' && elt.href &&
1607
+ (elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf('#') !== 0)) {
1608
+ return true
1609
+ }
1610
+ }
1611
+ return false
1612
+ }
1613
+
1614
+ function ignoreBoostedAnchorCtrlClick(elt, evt) {
1615
+ return getInternalData(elt).boosted && elt.tagName === 'A' && evt.type === 'click' && (evt.ctrlKey || evt.metaKey)
1616
+ }
1617
+
1618
+ function maybeFilterEvent(triggerSpec, elt, evt) {
1619
+ const eventFilter = triggerSpec.eventFilter
1620
+ if (eventFilter) {
1621
+ try {
1622
+ return eventFilter.call(elt, evt) !== true
1623
+ } catch (e) {
1624
+ triggerErrorEvent(getDocument().body, 'htmx:eventFilter:error', { error: e, source: eventFilter.source })
1625
+ return true
1626
+ }
1627
+ }
1628
+ return false
1629
+ }
1630
+
1631
+ function addEventListener(elt, handler, nodeData, triggerSpec, explicitCancel) {
1632
+ const elementData = getInternalData(elt)
1633
+ let eltsToListenOn
1634
+ if (triggerSpec.from) {
1635
+ eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from)
1636
+ } else {
1637
+ eltsToListenOn = [elt]
1638
+ }
1639
+ // store the initial values of the elements, so we can tell if they change
1640
+ if (triggerSpec.changed) {
1641
+ eltsToListenOn.forEach(function(eltToListenOn) {
1642
+ const eltToListenOnData = getInternalData(eltToListenOn)
1643
+ eltToListenOnData.lastValue = eltToListenOn.value
1644
+ })
1645
+ }
1646
+ forEach(eltsToListenOn, function(eltToListenOn) {
1647
+ const eventListener = function(evt) {
1648
+ if (!bodyContains(elt)) {
1649
+ eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener)
1650
+ return
1651
+ }
1652
+ if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
1653
+ return
1654
+ }
1655
+ if (explicitCancel || shouldCancel(evt, elt)) {
1656
+ evt.preventDefault()
1657
+ }
1658
+ if (maybeFilterEvent(triggerSpec, elt, evt)) {
1659
+ return
1660
+ }
1661
+ const eventData = getInternalData(evt)
1662
+ eventData.triggerSpec = triggerSpec
1663
+ if (eventData.handledFor == null) {
1664
+ eventData.handledFor = []
1665
+ }
1666
+ if (eventData.handledFor.indexOf(elt) < 0) {
1667
+ eventData.handledFor.push(elt)
1668
+ if (triggerSpec.consume) {
1669
+ evt.stopPropagation()
1670
+ }
1671
+ if (triggerSpec.target && evt.target) {
1672
+ if (!matches(evt.target, triggerSpec.target)) {
1673
+ return
1674
+ }
1675
+ }
1676
+ if (triggerSpec.once) {
1677
+ if (elementData.triggeredOnce) {
1678
+ return
1679
+ } else {
1680
+ elementData.triggeredOnce = true
1681
+ }
1682
+ }
1683
+ if (triggerSpec.changed) {
1684
+ const eltToListenOnData = getInternalData(eltToListenOn)
1685
+ if (eltToListenOnData.lastValue === eltToListenOn.value) {
1686
+ return
1687
+ }
1688
+ eltToListenOnData.lastValue = eltToListenOn.value
1689
+ }
1690
+ if (elementData.delayed) {
1691
+ clearTimeout(elementData.delayed)
1692
+ }
1693
+ if (elementData.throttle) {
1694
+ return
1695
+ }
1696
+
1697
+ if (triggerSpec.throttle > 0) {
1698
+ if (!elementData.throttle) {
1699
+ handler(elt, evt)
1700
+ elementData.throttle = setTimeout(function() {
1701
+ elementData.throttle = null
1702
+ }, triggerSpec.throttle)
1703
+ }
1704
+ } else if (triggerSpec.delay > 0) {
1705
+ elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay)
1706
+ } else {
1707
+ triggerEvent(elt, 'htmx:trigger')
1708
+ handler(elt, evt)
1709
+ }
1710
+ }
1711
+ }
1712
+ if (nodeData.listenerInfos == null) {
1713
+ nodeData.listenerInfos = []
1714
+ }
1715
+ nodeData.listenerInfos.push({
1716
+ trigger: triggerSpec.trigger,
1717
+ listener: eventListener,
1718
+ on: eltToListenOn
1719
+ })
1720
+ eltToListenOn.addEventListener(triggerSpec.trigger, eventListener)
1721
+ })
1722
+ }
1723
+
1724
+ let windowIsScrolling = false // used by initScrollHandler
1725
+ let scrollHandler = null
1726
+ function initScrollHandler() {
1727
+ if (!scrollHandler) {
1728
+ scrollHandler = function() {
1729
+ windowIsScrolling = true
1730
+ }
1731
+ window.addEventListener('scroll', scrollHandler)
1732
+ setInterval(function() {
1733
+ if (windowIsScrolling) {
1734
+ windowIsScrolling = false
1735
+ forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"), function(elt) {
1736
+ maybeReveal(elt)
1737
+ })
1738
+ }
1739
+ }, 200)
1740
+ }
1741
+ }
1742
+
1743
+ function maybeReveal(elt) {
1744
+ if (!hasAttribute(elt, 'data-hx-revealed') && isScrolledIntoView(elt)) {
1745
+ elt.setAttribute('data-hx-revealed', 'true')
1746
+ const nodeData = getInternalData(elt)
1747
+ if (nodeData.initHash) {
1748
+ triggerEvent(elt, 'revealed')
1749
+ } else {
1750
+ // if the node isn't initialized, wait for it before triggering the request
1751
+ elt.addEventListener('htmx:afterProcessNode', function(evt) { triggerEvent(elt, 'revealed') }, { once: true })
1752
+ }
1753
+ }
1754
+ }
1755
+
1756
+ //= ===================================================================
1757
+
1758
+ function loadImmediately(elt, handler, nodeData, delay) {
1759
+ const load = function() {
1760
+ if (!nodeData.loaded) {
1761
+ nodeData.loaded = true
1762
+ handler(elt)
1763
+ }
1764
+ }
1765
+ if (delay > 0) {
1766
+ setTimeout(load, delay)
1767
+ } else {
1768
+ load()
1769
+ }
1770
+ }
1771
+
1772
+ function processVerbs(elt, nodeData, triggerSpecs) {
1773
+ let explicitAction = false
1774
+ forEach(VERBS, function(verb) {
1775
+ if (hasAttribute(elt, 'hx-' + verb)) {
1776
+ const path = getAttributeValue(elt, 'hx-' + verb)
1777
+ explicitAction = true
1778
+ nodeData.path = path
1779
+ nodeData.verb = verb
1780
+ triggerSpecs.forEach(function(triggerSpec) {
1781
+ addTriggerHandler(elt, triggerSpec, nodeData, function(elt, evt) {
1782
+ if (closest(elt, htmx.config.disableSelector)) {
1783
+ cleanUpElement(elt)
1784
+ return
1785
+ }
1786
+ issueAjaxRequest(verb, path, elt, evt)
1787
+ })
1788
+ })
1789
+ }
1790
+ })
1791
+ return explicitAction
1792
+ }
1793
+
1794
+ function addTriggerHandler(elt, triggerSpec, nodeData, handler) {
1795
+ if (triggerSpec.trigger === 'revealed') {
1796
+ initScrollHandler()
1797
+ addEventListener(elt, handler, nodeData, triggerSpec)
1798
+ maybeReveal(elt)
1799
+ } else if (triggerSpec.trigger === 'intersect') {
1800
+ const observerOptions = {}
1801
+ if (triggerSpec.root) {
1802
+ observerOptions.root = querySelectorExt(elt, triggerSpec.root)
1803
+ }
1804
+ if (triggerSpec.threshold) {
1805
+ observerOptions.threshold = parseFloat(triggerSpec.threshold)
1806
+ }
1807
+ const observer = new IntersectionObserver(function(entries) {
1808
+ for (let i = 0; i < entries.length; i++) {
1809
+ const entry = entries[i]
1810
+ if (entry.isIntersecting) {
1811
+ triggerEvent(elt, 'intersect')
1812
+ break
1813
+ }
1814
+ }
1815
+ }, observerOptions)
1816
+ observer.observe(elt)
1817
+ addEventListener(elt, handler, nodeData, triggerSpec)
1818
+ } else if (triggerSpec.trigger === 'load') {
1819
+ if (!maybeFilterEvent(triggerSpec, elt, makeEvent('load', { elt }))) {
1820
+ loadImmediately(elt, handler, nodeData, triggerSpec.delay)
1821
+ }
1822
+ } else if (triggerSpec.pollInterval > 0) {
1823
+ nodeData.polling = true
1824
+ processPolling(elt, handler, triggerSpec)
1825
+ } else {
1826
+ addEventListener(elt, handler, nodeData, triggerSpec)
1827
+ }
1828
+ }
1829
+
1830
+ function shouldProcessHxOn(elt) {
1831
+ const attributes = elt.attributes
1832
+ for (let j = 0; j < attributes.length; j++) {
1833
+ const attrName = attributes[j].name
1834
+ if (startsWith(attrName, 'hx-on:') || startsWith(attrName, 'data-hx-on:') ||
1835
+ startsWith(attrName, 'hx-on-') || startsWith(attrName, 'data-hx-on-')) {
1836
+ return true
1837
+ }
1838
+ }
1839
+ return false
1840
+ }
1841
+
1842
+ function findHxOnWildcardElements(elt) {
1843
+ let node = null
1844
+ const elements = []
1845
+
1846
+ if (!(elt instanceof ShadowRoot)) {
1847
+ if (shouldProcessHxOn(elt)) {
1848
+ elements.push(elt)
1849
+ }
1850
+
1851
+ const iter = document.evaluate('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or' +
1852
+ ' starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]', elt)
1853
+ while (node = iter.iterateNext()) elements.push(node)
1854
+ }
1855
+ return elements
1856
+ }
1857
+
1858
+ function findElementsToProcess(elt) {
1859
+ if (elt.querySelectorAll) {
1860
+ const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
1861
+ const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
1862
+ ' [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]')
1863
+ return results
1864
+ } else {
1865
+ return []
1866
+ }
1867
+ }
1868
+
1869
+ // Handle submit buttons/inputs that have the form attribute set
1870
+ // see https://developer.mozilla.org/docs/Web/HTML/Element/button
1871
+ function maybeSetLastButtonClicked(evt) {
1872
+ const elt = closest(evt.target, "button, input[type='submit']")
1873
+ const internalData = getRelatedFormData(evt)
1874
+ if (internalData) {
1875
+ internalData.lastButtonClicked = elt
1876
+ }
1877
+ };
1878
+ function maybeUnsetLastButtonClicked(evt) {
1879
+ const internalData = getRelatedFormData(evt)
1880
+ if (internalData) {
1881
+ internalData.lastButtonClicked = null
1882
+ }
1883
+ }
1884
+ function getRelatedFormData(evt) {
1885
+ const elt = closest(evt.target, "button, input[type='submit']")
1886
+ if (!elt) {
1887
+ return
1888
+ }
1889
+ const form = resolveTarget('#' + getRawAttribute(elt, 'form'), elt.getRootNode()) || closest(elt, 'form')
1890
+ if (!form) {
1891
+ return
1892
+ }
1893
+ return getInternalData(form)
1894
+ }
1895
+ function initButtonTracking(elt) {
1896
+ // need to handle both click and focus in:
1897
+ // focusin - in case someone tabs in to a button and hits the space bar
1898
+ // click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724
1899
+ elt.addEventListener('click', maybeSetLastButtonClicked)
1900
+ elt.addEventListener('focusin', maybeSetLastButtonClicked)
1901
+ elt.addEventListener('focusout', maybeUnsetLastButtonClicked)
1902
+ }
1903
+
1904
+ function countCurlies(line) {
1905
+ const tokens = tokenizeString(line)
1906
+ let netCurlies = 0
1907
+ for (let i = 0; i < tokens.length; i++) {
1908
+ const token = tokens[i]
1909
+ if (token === '{') {
1910
+ netCurlies++
1911
+ } else if (token === '}') {
1912
+ netCurlies--
1913
+ }
1914
+ }
1915
+ return netCurlies
1916
+ }
1917
+
1918
+ function addHxOnEventHandler(elt, eventName, code) {
1919
+ const nodeData = getInternalData(elt)
1920
+ if (!Array.isArray(nodeData.onHandlers)) {
1921
+ nodeData.onHandlers = []
1922
+ }
1923
+ let func
1924
+ const listener = function(e) {
1925
+ return maybeEval(elt, function() {
1926
+ if (!func) {
1927
+ func = new Function('event', code)
1928
+ }
1929
+ func.call(elt, e)
1930
+ })
1931
+ }
1932
+ elt.addEventListener(eventName, listener)
1933
+ nodeData.onHandlers.push({ event: eventName, listener })
1934
+ }
1935
+
1936
+ function processHxOnWildcard(elt) {
1937
+ // wipe any previous on handlers so that this function takes precedence
1938
+ deInitOnHandlers(elt)
1939
+
1940
+ for (let i = 0; i < elt.attributes.length; i++) {
1941
+ const name = elt.attributes[i].name
1942
+ const value = elt.attributes[i].value
1943
+ if (startsWith(name, 'hx-on') || startsWith(name, 'data-hx-on')) {
1944
+ const afterOnPosition = name.indexOf('-on') + 3
1945
+ const nextChar = name.slice(afterOnPosition, afterOnPosition + 1)
1946
+ if (nextChar === '-' || nextChar === ':') {
1947
+ let eventName = name.slice(afterOnPosition + 1)
1948
+ // if the eventName starts with a colon or dash, prepend "htmx" for shorthand support
1949
+ if (startsWith(eventName, ':')) {
1950
+ eventName = 'htmx' + eventName
1951
+ } else if (startsWith(eventName, '-')) {
1952
+ eventName = 'htmx:' + eventName.slice(1)
1953
+ } else if (startsWith(eventName, 'htmx-')) {
1954
+ eventName = 'htmx:' + eventName.slice(5)
1955
+ }
1956
+
1957
+ addHxOnEventHandler(elt, eventName, value)
1958
+ }
1959
+ }
1960
+ }
1961
+ }
1962
+
1963
+ function initNode(elt) {
1964
+ if (closest(elt, htmx.config.disableSelector)) {
1965
+ cleanUpElement(elt)
1966
+ return
1967
+ }
1968
+ const nodeData = getInternalData(elt)
1969
+ if (nodeData.initHash !== attributeHash(elt)) {
1970
+ // clean up any previously processed info
1971
+ deInitNode(elt)
1972
+
1973
+ nodeData.initHash = attributeHash(elt)
1974
+
1975
+ triggerEvent(elt, 'htmx:beforeProcessNode')
1976
+
1977
+ if (elt.value) {
1978
+ nodeData.lastValue = elt.value
1979
+ }
1980
+
1981
+ const triggerSpecs = getTriggerSpecs(elt)
1982
+ const hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs)
1983
+
1984
+ if (!hasExplicitHttpAction) {
1985
+ if (getClosestAttributeValue(elt, 'hx-boost') === 'true') {
1986
+ boostElement(elt, nodeData, triggerSpecs)
1987
+ } else if (hasAttribute(elt, 'hx-trigger')) {
1988
+ triggerSpecs.forEach(function(triggerSpec) {
1989
+ // For "naked" triggers, don't do anything at all
1990
+ addTriggerHandler(elt, triggerSpec, nodeData, function() {
1991
+ })
1992
+ })
1993
+ }
1994
+ }
1995
+
1996
+ // Handle submit buttons/inputs that have the form attribute set
1997
+ // see https://developer.mozilla.org/docs/Web/HTML/Element/button
1998
+ if (elt.tagName === 'FORM' || (getRawAttribute(elt, 'type') === 'submit' && hasAttribute(elt, 'form'))) {
1999
+ initButtonTracking(elt)
2000
+ }
2001
+
2002
+ triggerEvent(elt, 'htmx:afterProcessNode')
2003
+ }
2004
+ }
2005
+
2006
+ function processNode(elt) {
2007
+ elt = resolveTarget(elt)
2008
+ if (closest(elt, htmx.config.disableSelector)) {
2009
+ cleanUpElement(elt)
2010
+ return
2011
+ }
2012
+ initNode(elt)
2013
+ forEach(findElementsToProcess(elt), function(child) { initNode(child) })
2014
+ forEach(findHxOnWildcardElements(elt), processHxOnWildcard)
2015
+ }
2016
+
2017
+ //= ===================================================================
2018
+ // Event/Log Support
2019
+ //= ===================================================================
2020
+
2021
+ function kebabEventName(str) {
2022
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
2023
+ }
2024
+
2025
+ function makeEvent(eventName, detail) {
2026
+ let evt
2027
+ if (window.CustomEvent && typeof window.CustomEvent === 'function') {
2028
+ // TODO: `composed: true` here is a hack to make global event handlers work with events in shadow DOM
2029
+ // This breaks expected encapsulation but needs to be here until decided otherwise by core devs
2030
+ evt = new CustomEvent(eventName, { bubbles: true, cancelable: true, composed: true, detail })
2031
+ } else {
2032
+ evt = getDocument().createEvent('CustomEvent')
2033
+ evt.initCustomEvent(eventName, true, true, detail)
2034
+ }
2035
+ return evt
2036
+ }
2037
+
2038
+ function triggerErrorEvent(elt, eventName, detail) {
2039
+ triggerEvent(elt, eventName, mergeObjects({ error: eventName }, detail))
2040
+ }
2041
+
2042
+ function ignoreEventForLogging(eventName) {
2043
+ return eventName === 'htmx:afterProcessNode'
2044
+ }
2045
+
2046
+ /**
2047
+ * `withExtensions` locates all active extensions for a provided element, then
2048
+ * executes the provided function using each of the active extensions. It should
2049
+ * be called internally at every extendable execution point in htmx.
2050
+ *
2051
+ * @param {HTMLElement} elt
2052
+ * @param {(extension:import("./htmx").HtmxExtension) => void} toDo
2053
+ * @returns void
2054
+ */
2055
+ function withExtensions(elt, toDo) {
2056
+ forEach(getExtensions(elt), function(extension) {
2057
+ try {
2058
+ toDo(extension)
2059
+ } catch (e) {
2060
+ logError(e)
2061
+ }
2062
+ })
2063
+ }
2064
+
2065
+ function logError(msg) {
2066
+ if (console.error) {
2067
+ console.error(msg)
2068
+ } else if (console.log) {
2069
+ console.log('ERROR: ', msg)
2070
+ }
2071
+ }
2072
+
2073
+ function triggerEvent(elt, eventName, detail) {
2074
+ elt = resolveTarget(elt)
2075
+ if (detail == null) {
2076
+ detail = {}
2077
+ }
2078
+ detail.elt = elt
2079
+ const event = makeEvent(eventName, detail)
2080
+ if (htmx.logger && !ignoreEventForLogging(eventName)) {
2081
+ htmx.logger(elt, eventName, detail)
2082
+ }
2083
+ if (detail.error) {
2084
+ logError(detail.error)
2085
+ triggerEvent(elt, 'htmx:error', { errorInfo: detail })
2086
+ }
2087
+ let eventResult = elt.dispatchEvent(event)
2088
+ const kebabName = kebabEventName(eventName)
2089
+ if (eventResult && kebabName !== eventName) {
2090
+ const kebabedEvent = makeEvent(kebabName, event.detail)
2091
+ eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
2092
+ }
2093
+ withExtensions(elt, function(extension) {
2094
+ eventResult = eventResult && (extension.onEvent(eventName, event) !== false && !event.defaultPrevented)
2095
+ })
2096
+ return eventResult
2097
+ }
2098
+
2099
+ //= ===================================================================
2100
+ // History Support
2101
+ //= ===================================================================
2102
+ let currentPathForHistory = location.pathname + location.search
2103
+
2104
+ function getHistoryElement() {
2105
+ const historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]')
2106
+ return historyElt || getDocument().body
2107
+ }
2108
+
2109
+ function saveToHistoryCache(url, rootElt) {
2110
+ if (!canAccessLocalStorage()) {
2111
+ return
2112
+ }
2113
+
2114
+ // get state to save
2115
+ const innerHTML = cleanInnerHtmlForHistory(rootElt)
2116
+ const title = getDocument().title
2117
+ const scroll = window.scrollY
2118
+
2119
+ if (htmx.config.historyCacheSize <= 0) {
2120
+ // make sure that an eventually already existing cache is purged
2121
+ localStorage.removeItem('htmx-history-cache')
2122
+ return
2123
+ }
2124
+
2125
+ url = normalizePath(url)
2126
+
2127
+ const historyCache = parseJSON(localStorage.getItem('htmx-history-cache')) || []
2128
+ for (let i = 0; i < historyCache.length; i++) {
2129
+ if (historyCache[i].url === url) {
2130
+ historyCache.splice(i, 1)
2131
+ break
2132
+ }
2133
+ }
2134
+
2135
+ const newHistoryItem = { url, content: innerHTML, title, scroll }
2136
+
2137
+ triggerEvent(getDocument().body, 'htmx:historyItemCreated', { item: newHistoryItem, cache: historyCache })
2138
+
2139
+ historyCache.push(newHistoryItem)
2140
+ while (historyCache.length > htmx.config.historyCacheSize) {
2141
+ historyCache.shift()
2142
+ }
2143
+
2144
+ // keep trying to save the cache until it succeeds or is empty
2145
+ while (historyCache.length > 0) {
2146
+ try {
2147
+ localStorage.setItem('htmx-history-cache', JSON.stringify(historyCache))
2148
+ break
2149
+ } catch (e) {
2150
+ triggerErrorEvent(getDocument().body, 'htmx:historyCacheError', { cause: e, cache: historyCache })
2151
+ historyCache.shift() // shrink the cache and retry
2152
+ }
2153
+ }
2154
+ }
2155
+
2156
+ function getCachedHistory(url) {
2157
+ if (!canAccessLocalStorage()) {
2158
+ return null
2159
+ }
2160
+
2161
+ url = normalizePath(url)
2162
+
2163
+ const historyCache = parseJSON(localStorage.getItem('htmx-history-cache')) || []
2164
+ for (let i = 0; i < historyCache.length; i++) {
2165
+ if (historyCache[i].url === url) {
2166
+ return historyCache[i]
2167
+ }
2168
+ }
2169
+ return null
2170
+ }
2171
+
2172
+ function cleanInnerHtmlForHistory(elt) {
2173
+ const className = htmx.config.requestClass
2174
+ const clone = elt.cloneNode(true)
2175
+ forEach(findAll(clone, '.' + className), function(child) {
2176
+ removeClassFromElement(child, className)
2177
+ })
2178
+ return clone.innerHTML
2179
+ }
2180
+
2181
+ function saveCurrentPageToHistory() {
2182
+ const elt = getHistoryElement()
2183
+ const path = currentPathForHistory || location.pathname + location.search
2184
+
2185
+ // Allow history snapshot feature to be disabled where hx-history="false"
2186
+ // is present *anywhere* in the current document we're about to save,
2187
+ // so we can prevent privileged data entering the cache.
2188
+ // The page will still be reachable as a history entry, but htmx will fetch it
2189
+ // live from the server onpopstate rather than look in the localStorage cache
2190
+ let disableHistoryCache
2191
+ try {
2192
+ disableHistoryCache = getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')
2193
+ } catch (e) {
2194
+ // IE11: insensitive modifier not supported so fallback to case sensitive selector
2195
+ disableHistoryCache = getDocument().querySelector('[hx-history="false"],[data-hx-history="false"]')
2196
+ }
2197
+ if (!disableHistoryCache) {
2198
+ triggerEvent(getDocument().body, 'htmx:beforeHistorySave', { path, historyElt: elt })
2199
+ saveToHistoryCache(path, elt)
2200
+ }
2201
+
2202
+ if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, getDocument().title, window.location.href)
2203
+ }
2204
+
2205
+ function pushUrlIntoHistory(path) {
2206
+ // remove the cache buster parameter, if any
2207
+ if (htmx.config.getCacheBusterParam) {
2208
+ path = path.replace(/org\.htmx\.cache-buster=[^&]*&?/, '')
2209
+ if (endsWith(path, '&') || endsWith(path, '?')) {
2210
+ path = path.slice(0, -1)
2211
+ }
2212
+ }
2213
+ if (htmx.config.historyEnabled) {
2214
+ history.pushState({ htmx: true }, '', path)
2215
+ }
2216
+ currentPathForHistory = path
2217
+ }
2218
+
2219
+ function replaceUrlInHistory(path) {
2220
+ if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, '', path)
2221
+ currentPathForHistory = path
2222
+ }
2223
+
2224
+ function settleImmediately(tasks) {
2225
+ forEach(tasks, function(task) {
2226
+ task.call()
2227
+ })
2228
+ }
2229
+
2230
+ function loadHistoryFromServer(path) {
2231
+ const request = new XMLHttpRequest()
2232
+ const details = { path, xhr: request }
2233
+ triggerEvent(getDocument().body, 'htmx:historyCacheMiss', details)
2234
+ request.open('GET', path, true)
2235
+ request.setRequestHeader('HX-Request', 'true')
2236
+ request.setRequestHeader('HX-History-Restore-Request', 'true')
2237
+ request.setRequestHeader('HX-Current-URL', getDocument().location.href)
2238
+ request.onload = function() {
2239
+ if (this.status >= 200 && this.status < 400) {
2240
+ triggerEvent(getDocument().body, 'htmx:historyCacheMissLoad', details)
2241
+ const fragment = makeFragment(this.response)
2242
+ // @ts-ignore
2243
+ const content = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment
2244
+ const historyElement = getHistoryElement()
2245
+ const settleInfo = makeSettleInfo(historyElement)
2246
+ handleTitle(fragment.title)
2247
+
2248
+ // @ts-ignore
2249
+ swapInnerHTML(historyElement, content, settleInfo)
2250
+ settleImmediately(settleInfo.tasks)
2251
+ currentPathForHistory = path
2252
+ triggerEvent(getDocument().body, 'htmx:historyRestore', { path, cacheMiss: true, serverResponse: this.response })
2253
+ } else {
2254
+ triggerErrorEvent(getDocument().body, 'htmx:historyCacheMissLoadError', details)
2255
+ }
2256
+ }
2257
+ request.send()
2258
+ }
2259
+
2260
+ function restoreHistory(path) {
2261
+ saveCurrentPageToHistory()
2262
+ path = path || location.pathname + location.search
2263
+ const cached = getCachedHistory(path)
2264
+ if (cached) {
2265
+ const fragment = makeFragment(cached.content)
2266
+ const historyElement = getHistoryElement()
2267
+ const settleInfo = makeSettleInfo(historyElement)
2268
+ handleTitle(fragment.title)
2269
+ swapInnerHTML(historyElement, fragment, settleInfo)
2270
+ settleImmediately(settleInfo.tasks)
2271
+ setTimeout(function() {
2272
+ window.scrollTo(0, cached.scroll)
2273
+ }, 0) // next 'tick', so browser has time to render layout
2274
+ currentPathForHistory = path
2275
+ triggerEvent(getDocument().body, 'htmx:historyRestore', { path, item: cached })
2276
+ } else {
2277
+ if (htmx.config.refreshOnHistoryMiss) {
2278
+ // @ts-ignore: optional parameter in reload() function throws error
2279
+ window.location.reload(true)
2280
+ } else {
2281
+ loadHistoryFromServer(path)
2282
+ }
2283
+ }
2284
+ }
2285
+
2286
+ function addRequestIndicatorClasses(elt) {
2287
+ let indicators = findAttributeTargets(elt, 'hx-indicator')
2288
+ if (indicators == null) {
2289
+ indicators = [elt]
2290
+ }
2291
+ forEach(indicators, function(ic) {
2292
+ const internalData = getInternalData(ic)
2293
+ internalData.requestCount = (internalData.requestCount || 0) + 1
2294
+ ic.classList.add.call(ic.classList, htmx.config.requestClass)
2295
+ })
2296
+ return indicators
2297
+ }
2298
+
2299
+ function disableElements(elt) {
2300
+ let disabledElts = findAttributeTargets(elt, 'hx-disabled-elt')
2301
+ if (disabledElts == null) {
2302
+ disabledElts = []
2303
+ }
2304
+ forEach(disabledElts, function(disabledElement) {
2305
+ const internalData = getInternalData(disabledElement)
2306
+ internalData.requestCount = (internalData.requestCount || 0) + 1
2307
+ disabledElement.setAttribute('disabled', '')
2308
+ })
2309
+ return disabledElts
2310
+ }
2311
+
2312
+ function removeRequestIndicators(indicators, disabled) {
2313
+ forEach(indicators, function(ic) {
2314
+ const internalData = getInternalData(ic)
2315
+ internalData.requestCount = (internalData.requestCount || 0) - 1
2316
+ if (internalData.requestCount === 0) {
2317
+ ic.classList.remove.call(ic.classList, htmx.config.requestClass)
2318
+ }
2319
+ })
2320
+ forEach(disabled, function(disabledElement) {
2321
+ const internalData = getInternalData(disabledElement)
2322
+ internalData.requestCount = (internalData.requestCount || 0) - 1
2323
+ if (internalData.requestCount === 0) {
2324
+ disabledElement.removeAttribute('disabled')
2325
+ }
2326
+ })
2327
+ }
2328
+
2329
+ //= ===================================================================
2330
+ // Input Value Processing
2331
+ //= ===================================================================
2332
+
2333
+ /**
2334
+ * @param {HTMLElement[]} processed
2335
+ * @param {HTMLElement} elt
2336
+ * @returns {boolean}
2337
+ */
2338
+ function haveSeenNode(processed, elt) {
2339
+ for (let i = 0; i < processed.length; i++) {
2340
+ const node = processed[i]
2341
+ if (node.isSameNode(elt)) {
2342
+ return true
2343
+ }
2344
+ }
2345
+ return false
2346
+ }
2347
+
2348
+ function shouldInclude(elt) {
2349
+ if (elt.name === '' || elt.name == null || elt.disabled || closest(elt, 'fieldset[disabled]')) {
2350
+ return false
2351
+ }
2352
+ // ignore "submitter" types (see jQuery src/serialize.js)
2353
+ if (elt.type === 'button' || elt.type === 'submit' || elt.tagName === 'image' || elt.tagName === 'reset' || elt.tagName === 'file') {
2354
+ return false
2355
+ }
2356
+ if (elt.type === 'checkbox' || elt.type === 'radio') {
2357
+ return elt.checked
2358
+ }
2359
+ return true
2360
+ }
2361
+
2362
+ /** @param {string} name
2363
+ * @param {string|Array} value
2364
+ * @param {FormData} formData */
2365
+ function addValueToFormData(name, value, formData) {
2366
+ if (name != null && value != null) {
2367
+ if (Array.isArray(value)) {
2368
+ value.forEach(function(v) { formData.append(name, v) })
2369
+ } else {
2370
+ formData.append(name, value)
2371
+ }
2372
+ }
2373
+ }
2374
+
2375
+ /** @param {string} name
2376
+ * @param {string|Array} value
2377
+ * @param {FormData} formData */
2378
+ function removeValueFromFormData(name, value, formData) {
2379
+ if (name != null && value != null) {
2380
+ let values = formData.getAll(name)
2381
+ if (Array.isArray(value)) {
2382
+ values = values.filter(v => value.indexOf(v) < 0)
2383
+ } else {
2384
+ values = values.filter(v => v !== value)
2385
+ }
2386
+ formData.delete(name)
2387
+ forEach(values, v => formData.append(name, v))
2388
+ }
2389
+ }
2390
+
2391
+ /**
2392
+ * @param {HTMLElement[]} processed
2393
+ * @param {FormData} formData
2394
+ * @param {HtmxElementValidationError[]} errors
2395
+ * @param {HTMLElement|HTMLInputElement|HTMLFormElement} elt
2396
+ * @param {boolean} validate
2397
+ */
2398
+ function processInputValue(processed, formData, errors, elt, validate) {
2399
+ if (elt == null || haveSeenNode(processed, elt)) {
2400
+ return
2401
+ } else {
2402
+ processed.push(elt)
2403
+ }
2404
+ if (shouldInclude(elt)) {
2405
+ const name = getRawAttribute(elt, 'name')
2406
+ let value = elt.value
2407
+ if (elt.multiple && elt.tagName === 'SELECT') {
2408
+ value = toArray(elt.querySelectorAll('option:checked')).map(function(e) { return e.value })
2409
+ }
2410
+ // include file inputs
2411
+ if (elt.files) {
2412
+ value = toArray(elt.files)
2413
+ }
2414
+ addValueToFormData(name, value, formData)
2415
+ if (validate) {
2416
+ validateElement(elt, errors)
2417
+ }
2418
+ }
2419
+ if (matches(elt, 'form')) {
2420
+ forEach(elt.elements, function(input) {
2421
+ if (processed.indexOf(input) >= 0) {
2422
+ // The input has already been processed and added to the values, but the FormData that will be
2423
+ // constructed right after on the form, will include it once again. So remove that input's value
2424
+ // now to avoid duplicates
2425
+ removeValueFromFormData(input.name, input.value, formData)
2426
+ } else {
2427
+ processed.push(input)
2428
+ }
2429
+ if (validate) {
2430
+ validateElement(input, errors)
2431
+ }
2432
+ })
2433
+ new FormData(elt).forEach(function(value, name) {
2434
+ addValueToFormData(name, value, formData)
2435
+ })
2436
+ }
2437
+ }
2438
+
2439
+ /**
2440
+ * @typedef {{elt: HTMLElement, message: string, validity: ValidityState}} HtmxElementValidationError
2441
+ */
2442
+
2443
+ /**
2444
+ *
2445
+ * @param {HTMLElement|HTMLObjectElement} element
2446
+ * @param {HtmxElementValidationError[]} errors
2447
+ */
2448
+ function validateElement(element, errors) {
2449
+ if (element.willValidate) {
2450
+ triggerEvent(element, 'htmx:validation:validate')
2451
+ if (!element.checkValidity()) {
2452
+ errors.push({ elt: element, message: element.validationMessage, validity: element.validity })
2453
+ triggerEvent(element, 'htmx:validation:failed', { message: element.validationMessage, validity: element.validity })
2454
+ }
2455
+ }
2456
+ }
2457
+
2458
+ /**
2459
+ * Override values in the one FormData with those from another.
2460
+ * @param {FormData} receiver the formdata that will be mutated
2461
+ * @param {FormData} donor the formdata that will provide the overriding values
2462
+ * @returns {FormData} the {@linkcode receiver}
2463
+ */
2464
+ function overrideFormData(receiver, donor) {
2465
+ for (const key of donor.keys()) {
2466
+ receiver.delete(key)
2467
+ donor.getAll(key).forEach(function(value) {
2468
+ receiver.append(key, value)
2469
+ })
2470
+ }
2471
+ return receiver
2472
+ }
2473
+
2474
+ /**
2475
+ * @param {HTMLElement|HTMLFormElement} elt
2476
+ * @param {string} verb
2477
+ * @returns {{errors: HtmxElementValidationError[], formData: FormData, values: Object}}
2478
+ */
2479
+ function getInputValues(elt, verb) {
2480
+ /** @type HTMLElement[] */
2481
+ const processed = []
2482
+ const formData = new FormData()
2483
+ const priorityFormData = new FormData()
2484
+ /** @type HtmxElementValidationError[] */
2485
+ const errors = []
2486
+ const internalData = getInternalData(elt)
2487
+ if (internalData.lastButtonClicked && !bodyContains(internalData.lastButtonClicked)) {
2488
+ internalData.lastButtonClicked = null
2489
+ }
2490
+
2491
+ // only validate when form is directly submitted and novalidate or formnovalidate are not set
2492
+ // or if the element has an explicit hx-validate="true" on it
2493
+ let validate = (matches(elt, 'form') && elt.noValidate !== true) || getAttributeValue(elt, 'hx-validate') === 'true'
2494
+ if (internalData.lastButtonClicked) {
2495
+ validate = validate && internalData.lastButtonClicked.formNoValidate !== true
2496
+ }
2497
+
2498
+ // for a non-GET include the closest form
2499
+ if (verb !== 'get') {
2500
+ processInputValue(processed, priorityFormData, errors, closest(elt, 'form'), validate)
2501
+ }
2502
+
2503
+ // include the element itself
2504
+ processInputValue(processed, formData, errors, elt, validate)
2505
+
2506
+ // if a button or submit was clicked last, include its value
2507
+ if (internalData.lastButtonClicked || elt.tagName === 'BUTTON' ||
2508
+ (elt.tagName === 'INPUT' && getRawAttribute(elt, 'type') === 'submit')) {
2509
+ const button = internalData.lastButtonClicked || elt
2510
+ const name = getRawAttribute(button, 'name')
2511
+ addValueToFormData(name, button.value, priorityFormData)
2512
+ }
2513
+
2514
+ // include any explicit includes
2515
+ const includes = findAttributeTargets(elt, 'hx-include')
2516
+ forEach(includes, function(node) {
2517
+ processInputValue(processed, formData, errors, node, validate)
2518
+ // if a non-form is included, include any input values within it
2519
+ if (!matches(node, 'form')) {
2520
+ forEach(node.querySelectorAll(INPUT_SELECTOR), function(descendant) {
2521
+ processInputValue(processed, formData, errors, descendant, validate)
2522
+ })
2523
+ }
2524
+ })
2525
+
2526
+ // values from a <form> take precedence, overriding the regular values
2527
+ overrideFormData(formData, priorityFormData)
2528
+
2529
+ return { errors, formData, values: formDataProxy(formData) }
2530
+ }
2531
+
2532
+ /**
2533
+ * @param {string} returnStr
2534
+ * @param {string} name
2535
+ * @param {any} realValue
2536
+ * @returns {string}
2537
+ */
2538
+ function appendParam(returnStr, name, realValue) {
2539
+ if (returnStr !== '') {
2540
+ returnStr += '&'
2541
+ }
2542
+ if (String(realValue) === '[object Object]') {
2543
+ realValue = JSON.stringify(realValue)
2544
+ }
2545
+ const s = encodeURIComponent(realValue)
2546
+ returnStr += encodeURIComponent(name) + '=' + s
2547
+ return returnStr
2548
+ }
2549
+
2550
+ /**
2551
+ * @param {FormData|Object} values
2552
+ * @returns string
2553
+ */
2554
+ function urlEncode(values) {
2555
+ values = formDataFromObject(values)
2556
+ let returnStr = ''
2557
+ values.forEach(function(value, key) {
2558
+ returnStr = appendParam(returnStr, key, value)
2559
+ })
2560
+ return returnStr
2561
+ }
2562
+
2563
+ //= ===================================================================
2564
+ // Ajax
2565
+ //= ===================================================================
2566
+
2567
+ /**
2568
+ * @param {HTMLElement} elt
2569
+ * @param {HTMLElement} target
2570
+ * @param {string} prompt
2571
+ * @returns {Object} // TODO: Define/Improve HtmxHeaderSpecification
2572
+ */
2573
+ function getHeaders(elt, target, prompt) {
2574
+ const headers = {
2575
+ 'HX-Request': 'true',
2576
+ 'HX-Trigger': getRawAttribute(elt, 'id'),
2577
+ 'HX-Trigger-Name': getRawAttribute(elt, 'name'),
2578
+ 'HX-Target': getAttributeValue(target, 'id'),
2579
+ 'HX-Current-URL': getDocument().location.href
2580
+ }
2581
+ getValuesForElement(elt, 'hx-headers', false, headers)
2582
+ if (prompt !== undefined) {
2583
+ headers['HX-Prompt'] = prompt
2584
+ }
2585
+ if (getInternalData(elt).boosted) {
2586
+ headers['HX-Boosted'] = 'true'
2587
+ }
2588
+ return headers
2589
+ }
2590
+
2591
+ /**
2592
+ * filterValues takes an object containing form input values
2593
+ * and returns a new object that only contains keys that are
2594
+ * specified by the closest "hx-params" attribute
2595
+ * @param {FormData} inputValues
2596
+ * @param {HTMLElement} elt
2597
+ * @returns {FormData}
2598
+ */
2599
+ function filterValues(inputValues, elt) {
2600
+ const paramsValue = getClosestAttributeValue(elt, 'hx-params')
2601
+ if (paramsValue) {
2602
+ if (paramsValue === 'none') {
2603
+ return new FormData()
2604
+ } else if (paramsValue === '*') {
2605
+ return inputValues
2606
+ } else if (paramsValue.indexOf('not ') === 0) {
2607
+ forEach(paramsValue.substr(4).split(','), function(name) {
2608
+ name = name.trim()
2609
+ inputValues.delete(name)
2610
+ })
2611
+ return inputValues
2612
+ } else {
2613
+ const newValues = new FormData()
2614
+ forEach(paramsValue.split(','), function(name) {
2615
+ name = name.trim()
2616
+ if (inputValues.has(name)) {
2617
+ inputValues.getAll(name).forEach(function(value) { newValues.append(name, value) })
2618
+ }
2619
+ })
2620
+ return newValues
2621
+ }
2622
+ } else {
2623
+ return inputValues
2624
+ }
2625
+ }
2626
+
2627
+ function isAnchorLink(elt) {
2628
+ return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf('#') >= 0
2629
+ }
2630
+
2631
+ /**
2632
+ *
2633
+ * @param {HTMLElement} elt
2634
+ * @param {import("./htmx").HtmxSwapStyle} swapInfoOverride
2635
+ * @returns {import("./htmx").HtmxSwapSpecification}
2636
+ */
2637
+ function getSwapSpecification(elt, swapInfoOverride) {
2638
+ const swapInfo = swapInfoOverride || getClosestAttributeValue(elt, 'hx-swap')
2639
+ /** @type import("./htmx").HtmxSwapSpecification */
2640
+ const swapSpec = {
2641
+ swapStyle: getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
2642
+ swapDelay: htmx.config.defaultSwapDelay,
2643
+ settleDelay: htmx.config.defaultSettleDelay
2644
+ }
2645
+ if (htmx.config.scrollIntoViewOnBoost && getInternalData(elt).boosted && !isAnchorLink(elt)) {
2646
+ swapSpec.show = 'top'
2647
+ }
2648
+ if (swapInfo) {
2649
+ const split = splitOnWhitespace(swapInfo)
2650
+ if (split.length > 0) {
2651
+ for (let i = 0; i < split.length; i++) {
2652
+ const value = split[i]
2653
+ if (value.indexOf('swap:') === 0) {
2654
+ swapSpec.swapDelay = parseInterval(value.substr(5))
2655
+ } else if (value.indexOf('settle:') === 0) {
2656
+ swapSpec.settleDelay = parseInterval(value.substr(7))
2657
+ } else if (value.indexOf('transition:') === 0) {
2658
+ swapSpec.transition = value.substr(11) === 'true'
2659
+ } else if (value.indexOf('ignoreTitle:') === 0) {
2660
+ swapSpec.ignoreTitle = value.substr(12) === 'true'
2661
+ } else if (value.indexOf('scroll:') === 0) {
2662
+ const scrollSpec = value.substr(7)
2663
+ var splitSpec = scrollSpec.split(':')
2664
+ const scrollVal = splitSpec.pop()
2665
+ var selectorVal = splitSpec.length > 0 ? splitSpec.join(':') : null
2666
+ swapSpec.scroll = scrollVal
2667
+ swapSpec.scrollTarget = selectorVal
2668
+ } else if (value.indexOf('show:') === 0) {
2669
+ const showSpec = value.substr(5)
2670
+ var splitSpec = showSpec.split(':')
2671
+ const showVal = splitSpec.pop()
2672
+ var selectorVal = splitSpec.length > 0 ? splitSpec.join(':') : null
2673
+ swapSpec.show = showVal
2674
+ swapSpec.showTarget = selectorVal
2675
+ } else if (value.indexOf('focus-scroll:') === 0) {
2676
+ const focusScrollVal = value.substr('focus-scroll:'.length)
2677
+ swapSpec.focusScroll = focusScrollVal == 'true'
2678
+ } else if (i == 0) {
2679
+ swapSpec.swapStyle = value
2680
+ } else {
2681
+ logError('Unknown modifier in hx-swap: ' + value)
2682
+ }
2683
+ }
2684
+ }
2685
+ }
2686
+ return swapSpec
2687
+ }
2688
+
2689
+ function usesFormData(elt) {
2690
+ return getClosestAttributeValue(elt, 'hx-encoding') === 'multipart/form-data' ||
2691
+ (matches(elt, 'form') && getRawAttribute(elt, 'enctype') === 'multipart/form-data')
2692
+ }
2693
+
2694
+ /**
2695
+ * @param {XMLHttpRequest} xhr
2696
+ * @param {HTMLElement} elt
2697
+ * @param {FormData} filteredParameters
2698
+ * @returns {*|string|null}
2699
+ */
2700
+ function encodeParamsForBody(xhr, elt, filteredParameters) {
2701
+ let encodedParameters = null
2702
+ withExtensions(elt, function(extension) {
2703
+ if (encodedParameters == null) {
2704
+ encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt)
2705
+ }
2706
+ })
2707
+ if (encodedParameters != null) {
2708
+ return encodedParameters
2709
+ } else {
2710
+ if (usesFormData(elt)) {
2711
+ return formDataFromObject(filteredParameters)
2712
+ } else {
2713
+ return urlEncode(filteredParameters)
2714
+ }
2715
+ }
2716
+ }
2717
+
2718
+ /**
2719
+ *
2720
+ * @param {Element} target
2721
+ * @returns {import("./htmx").HtmxSettleInfo}
2722
+ */
2723
+ function makeSettleInfo(target) {
2724
+ return { tasks: [], elts: [target] }
2725
+ }
2726
+
2727
+ function updateScrollState(content, swapSpec) {
2728
+ const first = content[0]
2729
+ const last = content[content.length - 1]
2730
+ if (swapSpec.scroll) {
2731
+ var target = null
2732
+ if (swapSpec.scrollTarget) {
2733
+ target = querySelectorExt(first, swapSpec.scrollTarget)
2734
+ }
2735
+ if (swapSpec.scroll === 'top' && (first || target)) {
2736
+ target = target || first
2737
+ target.scrollTop = 0
2738
+ }
2739
+ if (swapSpec.scroll === 'bottom' && (last || target)) {
2740
+ target = target || last
2741
+ target.scrollTop = target.scrollHeight
2742
+ }
2743
+ }
2744
+ if (swapSpec.show) {
2745
+ var target = null
2746
+ if (swapSpec.showTarget) {
2747
+ let targetStr = swapSpec.showTarget
2748
+ if (swapSpec.showTarget === 'window') {
2749
+ targetStr = 'body'
2750
+ }
2751
+ target = querySelectorExt(first, targetStr)
2752
+ }
2753
+ if (swapSpec.show === 'top' && (first || target)) {
2754
+ target = target || first
2755
+ target.scrollIntoView({ block: 'start', behavior: htmx.config.scrollBehavior })
2756
+ }
2757
+ if (swapSpec.show === 'bottom' && (last || target)) {
2758
+ target = target || last
2759
+ target.scrollIntoView({ block: 'end', behavior: htmx.config.scrollBehavior })
2760
+ }
2761
+ }
2762
+ }
2763
+
2764
+ /**
2765
+ * @param {HTMLElement} elt
2766
+ * @param {string} attr
2767
+ * @param {boolean=} evalAsDefault
2768
+ * @param {Object=} values
2769
+ * @returns {Object}
2770
+ */
2771
+ function getValuesForElement(elt, attr, evalAsDefault, values) {
2772
+ if (values == null) {
2773
+ values = {}
2774
+ }
2775
+ if (elt == null) {
2776
+ return values
2777
+ }
2778
+ const attributeValue = getAttributeValue(elt, attr)
2779
+ if (attributeValue) {
2780
+ let str = attributeValue.trim()
2781
+ let evaluateValue = evalAsDefault
2782
+ if (str === 'unset') {
2783
+ return null
2784
+ }
2785
+ if (str.indexOf('javascript:') === 0) {
2786
+ str = str.substr(11)
2787
+ evaluateValue = true
2788
+ } else if (str.indexOf('js:') === 0) {
2789
+ str = str.substr(3)
2790
+ evaluateValue = true
2791
+ }
2792
+ if (str.indexOf('{') !== 0) {
2793
+ str = '{' + str + '}'
2794
+ }
2795
+ let varsValues
2796
+ if (evaluateValue) {
2797
+ varsValues = maybeEval(elt, function() { return Function('return (' + str + ')')() }, {})
2798
+ } else {
2799
+ varsValues = parseJSON(str)
2800
+ }
2801
+ for (const key in varsValues) {
2802
+ if (varsValues.hasOwnProperty(key)) {
2803
+ if (values[key] == null) {
2804
+ values[key] = varsValues[key]
2805
+ }
2806
+ }
2807
+ }
2808
+ }
2809
+ return getValuesForElement(parentElt(elt), attr, evalAsDefault, values)
2810
+ }
2811
+
2812
+ function maybeEval(elt, toEval, defaultVal) {
2813
+ if (htmx.config.allowEval) {
2814
+ return toEval()
2815
+ } else {
2816
+ triggerErrorEvent(elt, 'htmx:evalDisallowedError')
2817
+ return defaultVal
2818
+ }
2819
+ }
2820
+
2821
+ /**
2822
+ * @param {HTMLElement} elt
2823
+ * @param {*?} expressionVars
2824
+ * @returns
2825
+ */
2826
+ function getHXVarsForElement(elt, expressionVars) {
2827
+ return getValuesForElement(elt, 'hx-vars', true, expressionVars)
2828
+ }
2829
+
2830
+ /**
2831
+ * @param {HTMLElement} elt
2832
+ * @param {*?} expressionVars
2833
+ * @returns
2834
+ */
2835
+ function getHXValsForElement(elt, expressionVars) {
2836
+ return getValuesForElement(elt, 'hx-vals', false, expressionVars)
2837
+ }
2838
+
2839
+ /**
2840
+ * @param {HTMLElement} elt
2841
+ * @returns {FormData}
2842
+ */
2843
+ function getExpressionVars(elt) {
2844
+ return formDataFromObject(mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt)))
2845
+ }
2846
+
2847
+ function safelySetHeaderValue(xhr, header, headerValue) {
2848
+ if (headerValue !== null) {
2849
+ try {
2850
+ xhr.setRequestHeader(header, headerValue)
2851
+ } catch (e) {
2852
+ // On an exception, try to set the header URI encoded instead
2853
+ xhr.setRequestHeader(header, encodeURIComponent(headerValue))
2854
+ xhr.setRequestHeader(header + '-URI-AutoEncoded', 'true')
2855
+ }
2856
+ }
2857
+ }
2858
+
2859
+ function getPathFromResponse(xhr) {
2860
+ // NB: IE11 does not support this stuff
2861
+ if (xhr.responseURL && typeof (URL) !== 'undefined') {
2862
+ try {
2863
+ const url = new URL(xhr.responseURL)
2864
+ return url.pathname + url.search
2865
+ } catch (e) {
2866
+ triggerErrorEvent(getDocument().body, 'htmx:badResponseUrl', { url: xhr.responseURL })
2867
+ }
2868
+ }
2869
+ }
2870
+
2871
+ function hasHeader(xhr, regexp) {
2872
+ return regexp.test(xhr.getAllResponseHeaders())
2873
+ }
2874
+
2875
+ function ajaxHelper(verb, path, context) {
2876
+ verb = verb.toLowerCase()
2877
+ if (context) {
2878
+ if (context instanceof Element || isType(context, 'String')) {
2879
+ return issueAjaxRequest(verb, path, null, null, {
2880
+ targetOverride: resolveTarget(context),
2881
+ returnPromise: true
2882
+ })
2883
+ } else {
2884
+ return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
2885
+ {
2886
+ handler: context.handler,
2887
+ headers: context.headers,
2888
+ values: context.values,
2889
+ targetOverride: resolveTarget(context.target),
2890
+ swapOverride: context.swap,
2891
+ select: context.select,
2892
+ returnPromise: true
2893
+ })
2894
+ }
2895
+ } else {
2896
+ return issueAjaxRequest(verb, path, null, null, {
2897
+ returnPromise: true
2898
+ })
2899
+ }
2900
+ }
2901
+
2902
+ function hierarchyForElt(elt) {
2903
+ const arr = []
2904
+ while (elt) {
2905
+ arr.push(elt)
2906
+ elt = elt.parentElement
2907
+ }
2908
+ return arr
2909
+ }
2910
+
2911
+ function verifyPath(elt, path, requestConfig) {
2912
+ let sameHost
2913
+ let url
2914
+ if (typeof URL === 'function') {
2915
+ url = new URL(path, document.location.href)
2916
+ const origin = document.location.origin
2917
+ sameHost = origin === url.origin
2918
+ } else {
2919
+ // IE11 doesn't support URL
2920
+ url = path
2921
+ sameHost = startsWith(path, document.location.origin)
2922
+ }
2923
+
2924
+ if (htmx.config.selfRequestsOnly) {
2925
+ if (!sameHost) {
2926
+ return false
2927
+ }
2928
+ }
2929
+ return triggerEvent(elt, 'htmx:validateUrl', mergeObjects({ url, sameHost }, requestConfig))
2930
+ }
2931
+
2932
+ function formDataFromObject(obj) {
2933
+ if (obj instanceof FormData) return obj
2934
+ const formData = new FormData()
2935
+ for (const key in obj) {
2936
+ if (obj.hasOwnProperty(key)) {
2937
+ if (typeof obj[key].forEach === 'function') {
2938
+ obj[key].forEach(function(v) { formData.append(key, v) })
2939
+ } else if (typeof obj[key] === 'object') {
2940
+ formData.append(key, JSON.stringify(obj[key]))
2941
+ } else {
2942
+ formData.append(key, obj[key])
2943
+ }
2944
+ }
2945
+ }
2946
+ return formData
2947
+ }
2948
+
2949
+ /**
2950
+ * @param {FormData} formData
2951
+ * @param {string|Symbol} name
2952
+ * @param {Array} array
2953
+ * @returns {Array}
2954
+ */
2955
+ function formDataArrayProxy(formData, name, array) {
2956
+ // mutating the array should mutate the underlying form data
2957
+ return new Proxy(array, {
2958
+ get: function(target, key) {
2959
+ if (typeof key === 'number') return target[key]
2960
+ if (key === 'length') return target.length
2961
+ if (key === 'push') {
2962
+ return function(value) {
2963
+ target.push(value)
2964
+ formData.append(name, value)
2965
+ }
2966
+ }
2967
+ if (typeof target[key] === 'function') {
2968
+ return function() {
2969
+ target[key].apply(target, arguments)
2970
+ formData.delete(name)
2971
+ target.forEach(function(v) { formData.append(name, v) })
2972
+ }
2973
+ }
2974
+
2975
+ if (target[key] && target[key].length === 1) {
2976
+ return target[key][0]
2977
+ } else {
2978
+ return target[key]
2979
+ }
2980
+ },
2981
+ set: function(target, index, value) {
2982
+ target[index] = value
2983
+ formData.delete(name)
2984
+ target.forEach(function(v) { formData.append(name, v) })
2985
+ return true
2986
+ }
2987
+ })
2988
+ }
2989
+
2990
+ /**
2991
+ * @param {FormData} formData
2992
+ * @returns {Object}
2993
+ */
2994
+ function formDataProxy(formData) {
2995
+ return new Proxy(formData, {
2996
+ get: function(target, name) {
2997
+ if (typeof name === 'symbol') {
2998
+ // Forward symbol calls to the FormData itself directly
2999
+ return Reflect.get(...arguments)
3000
+ }
3001
+ if (name === 'toJSON') {
3002
+ // Support JSON.stringify call on proxy
3003
+ return () => Object.fromEntries(formData)
3004
+ }
3005
+ if (name in target) {
3006
+ // Wrap in function with apply to correctly bind the FormData context, as a direct call would result in an illegal invocation error
3007
+ if (typeof target[name] === 'function') {
3008
+ return function() {
3009
+ return formData[name].apply(formData, arguments)
3010
+ }
3011
+ } else {
3012
+ return target[name]
3013
+ }
3014
+ }
3015
+ const array = formData.getAll(name)
3016
+ // Those 2 undefined & single value returns are for retro-compatibility as we weren't using FormData before
3017
+ if (array.length === 0) {
3018
+ return undefined
3019
+ } else if (array.length === 1) {
3020
+ return array[0]
3021
+ } else {
3022
+ return formDataArrayProxy(target, name, array)
3023
+ }
3024
+ },
3025
+ set: function(target, name, value) {
3026
+ target.delete(name)
3027
+ if (typeof value.forEach === 'function') {
3028
+ value.forEach(function(v) { target.append(name, v) })
3029
+ } else {
3030
+ target.append(name, value)
3031
+ }
3032
+ return true
3033
+ },
3034
+ deleteProperty: function(target, name) {
3035
+ target.delete(name)
3036
+ return true
3037
+ },
3038
+ // Support Object.assign call from proxy
3039
+ ownKeys: function(target) {
3040
+ return Reflect.ownKeys(Object.fromEntries(target))
3041
+ },
3042
+ getOwnPropertyDescriptor: function(target, prop) {
3043
+ return Reflect.getOwnPropertyDescriptor(Object.fromEntries(target), prop)
3044
+ }
3045
+ })
3046
+ }
3047
+
3048
+ function issueAjaxRequest(verb, path, elt, event, etc, confirmed) {
3049
+ let resolve = null
3050
+ let reject = null
3051
+ etc = etc != null ? etc : {}
3052
+ if (etc.returnPromise && typeof Promise !== 'undefined') {
3053
+ var promise = new Promise(function(_resolve, _reject) {
3054
+ resolve = _resolve
3055
+ reject = _reject
3056
+ })
3057
+ }
3058
+ if (elt == null) {
3059
+ elt = getDocument().body
3060
+ }
3061
+ const responseHandler = etc.handler || handleAjaxResponse
3062
+ const select = etc.select || null
3063
+
3064
+ if (!bodyContains(elt)) {
3065
+ // do not issue requests for elements removed from the DOM
3066
+ maybeCall(resolve)
3067
+ return promise
3068
+ }
3069
+ const target = etc.targetOverride || getTarget(elt)
3070
+ if (target == null || target == DUMMY_ELT) {
3071
+ triggerErrorEvent(elt, 'htmx:targetError', { target: getAttributeValue(elt, 'hx-target') })
3072
+ maybeCall(reject)
3073
+ return promise
3074
+ }
3075
+
3076
+ let eltData = getInternalData(elt)
3077
+ const submitter = eltData.lastButtonClicked
3078
+
3079
+ if (submitter) {
3080
+ const buttonPath = getRawAttribute(submitter, 'formaction')
3081
+ if (buttonPath != null) {
3082
+ path = buttonPath
3083
+ }
3084
+
3085
+ const buttonVerb = getRawAttribute(submitter, 'formmethod')
3086
+ if (buttonVerb != null) {
3087
+ // ignore buttons with formmethod="dialog"
3088
+ if (buttonVerb.toLowerCase() !== 'dialog') {
3089
+ verb = buttonVerb
3090
+ }
3091
+ }
3092
+ }
3093
+
3094
+ const confirmQuestion = getClosestAttributeValue(elt, 'hx-confirm')
3095
+ // allow event-based confirmation w/ a callback
3096
+ if (confirmed === undefined) {
3097
+ const issueRequest = function(skipConfirmation) {
3098
+ return issueAjaxRequest(verb, path, elt, event, etc, !!skipConfirmation)
3099
+ }
3100
+ const confirmDetails = { target, elt, path, verb, triggeringEvent: event, etc, issueRequest, question: confirmQuestion }
3101
+ if (triggerEvent(elt, 'htmx:confirm', confirmDetails) === false) {
3102
+ maybeCall(resolve)
3103
+ return promise
3104
+ }
3105
+ }
3106
+
3107
+ let syncElt = elt
3108
+ let syncStrategy = getClosestAttributeValue(elt, 'hx-sync')
3109
+ let queueStrategy = null
3110
+ let abortable = false
3111
+ if (syncStrategy) {
3112
+ const syncStrings = syncStrategy.split(':')
3113
+ const selector = syncStrings[0].trim()
3114
+ if (selector === 'this') {
3115
+ syncElt = findThisElement(elt, 'hx-sync')
3116
+ } else {
3117
+ syncElt = querySelectorExt(elt, selector)
3118
+ }
3119
+ // default to the drop strategy
3120
+ syncStrategy = (syncStrings[1] || 'drop').trim()
3121
+ eltData = getInternalData(syncElt)
3122
+ if (syncStrategy === 'drop' && eltData.xhr && eltData.abortable !== true) {
3123
+ maybeCall(resolve)
3124
+ return promise
3125
+ } else if (syncStrategy === 'abort') {
3126
+ if (eltData.xhr) {
3127
+ maybeCall(resolve)
3128
+ return promise
3129
+ } else {
3130
+ abortable = true
3131
+ }
3132
+ } else if (syncStrategy === 'replace') {
3133
+ triggerEvent(syncElt, 'htmx:abort') // abort the current request and continue
3134
+ } else if (syncStrategy.indexOf('queue') === 0) {
3135
+ const queueStrArray = syncStrategy.split(' ')
3136
+ queueStrategy = (queueStrArray[1] || 'last').trim()
3137
+ }
3138
+ }
3139
+
3140
+ if (eltData.xhr) {
3141
+ if (eltData.abortable) {
3142
+ triggerEvent(syncElt, 'htmx:abort') // abort the current request and continue
3143
+ } else {
3144
+ if (queueStrategy == null) {
3145
+ if (event) {
3146
+ const eventData = getInternalData(event)
3147
+ if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
3148
+ queueStrategy = eventData.triggerSpec.queue
3149
+ }
3150
+ }
3151
+ if (queueStrategy == null) {
3152
+ queueStrategy = 'last'
3153
+ }
3154
+ }
3155
+ if (eltData.queuedRequests == null) {
3156
+ eltData.queuedRequests = []
3157
+ }
3158
+ if (queueStrategy === 'first' && eltData.queuedRequests.length === 0) {
3159
+ eltData.queuedRequests.push(function() {
3160
+ issueAjaxRequest(verb, path, elt, event, etc)
3161
+ })
3162
+ } else if (queueStrategy === 'all') {
3163
+ eltData.queuedRequests.push(function() {
3164
+ issueAjaxRequest(verb, path, elt, event, etc)
3165
+ })
3166
+ } else if (queueStrategy === 'last') {
3167
+ eltData.queuedRequests = [] // dump existing queue
3168
+ eltData.queuedRequests.push(function() {
3169
+ issueAjaxRequest(verb, path, elt, event, etc)
3170
+ })
3171
+ }
3172
+ maybeCall(resolve)
3173
+ return promise
3174
+ }
3175
+ }
3176
+
3177
+ const xhr = new XMLHttpRequest()
3178
+ eltData.xhr = xhr
3179
+ eltData.abortable = abortable
3180
+ const endRequestLock = function() {
3181
+ eltData.xhr = null
3182
+ eltData.abortable = false
3183
+ if (eltData.queuedRequests != null &&
3184
+ eltData.queuedRequests.length > 0) {
3185
+ const queuedRequest = eltData.queuedRequests.shift()
3186
+ queuedRequest()
3187
+ }
3188
+ }
3189
+ const promptQuestion = getClosestAttributeValue(elt, 'hx-prompt')
3190
+ if (promptQuestion) {
3191
+ var promptResponse = prompt(promptQuestion)
3192
+ // prompt returns null if cancelled and empty string if accepted with no entry
3193
+ if (promptResponse === null ||
3194
+ !triggerEvent(elt, 'htmx:prompt', { prompt: promptResponse, target })) {
3195
+ maybeCall(resolve)
3196
+ endRequestLock()
3197
+ return promise
3198
+ }
3199
+ }
3200
+
3201
+ if (confirmQuestion && !confirmed) {
3202
+ if (!confirm(confirmQuestion)) {
3203
+ maybeCall(resolve)
3204
+ endRequestLock()
3205
+ return promise
3206
+ }
3207
+ }
3208
+
3209
+ let headers = getHeaders(elt, target, promptResponse)
3210
+
3211
+ if (verb !== 'get' && !usesFormData(elt)) {
3212
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
3213
+ }
3214
+
3215
+ if (etc.headers) {
3216
+ headers = mergeObjects(headers, etc.headers)
3217
+ }
3218
+ const results = getInputValues(elt, verb)
3219
+ let errors = results.errors
3220
+ const rawFormData = results.formData
3221
+ if (etc.values) {
3222
+ overrideFormData(rawFormData, formDataFromObject(etc.values))
3223
+ }
3224
+ const expressionVars = getExpressionVars(elt)
3225
+ const allFormData = overrideFormData(rawFormData, expressionVars)
3226
+ let filteredFormData = filterValues(allFormData, elt)
3227
+
3228
+ if (htmx.config.getCacheBusterParam && verb === 'get') {
3229
+ filteredFormData.set('org.htmx.cache-buster', getRawAttribute(target, 'id') || 'true')
3230
+ }
3231
+
3232
+ // behavior of anchors w/ empty href is to use the current URL
3233
+ if (path == null || path === '') {
3234
+ path = getDocument().location.href
3235
+ }
3236
+
3237
+ const requestAttrValues = getValuesForElement(elt, 'hx-request')
3238
+
3239
+ const eltIsBoosted = getInternalData(elt).boosted
3240
+
3241
+ let useUrlParams = htmx.config.methodsThatUseUrlParams.indexOf(verb) >= 0
3242
+
3243
+ const requestConfig = {
3244
+ boosted: eltIsBoosted,
3245
+ useUrlParams,
3246
+ formData: filteredFormData,
3247
+ parameters: formDataProxy(filteredFormData),
3248
+ unfilteredFormData: allFormData,
3249
+ unfilteredParameters: formDataProxy(allFormData),
3250
+ headers,
3251
+ target,
3252
+ verb,
3253
+ errors,
3254
+ withCredentials: etc.credentials || requestAttrValues.credentials || htmx.config.withCredentials,
3255
+ timeout: etc.timeout || requestAttrValues.timeout || htmx.config.timeout,
3256
+ path,
3257
+ triggeringEvent: event
3258
+ }
3259
+
3260
+ if (!triggerEvent(elt, 'htmx:configRequest', requestConfig)) {
3261
+ maybeCall(resolve)
3262
+ endRequestLock()
3263
+ return promise
3264
+ }
3265
+
3266
+ // copy out in case the object was overwritten
3267
+ path = requestConfig.path
3268
+ verb = requestConfig.verb
3269
+ headers = requestConfig.headers
3270
+ filteredFormData = formDataFromObject(requestConfig.parameters)
3271
+ errors = requestConfig.errors
3272
+ useUrlParams = requestConfig.useUrlParams
3273
+
3274
+ if (errors && errors.length > 0) {
3275
+ triggerEvent(elt, 'htmx:validation:halted', requestConfig)
3276
+ maybeCall(resolve)
3277
+ endRequestLock()
3278
+ return promise
3279
+ }
3280
+
3281
+ const splitPath = path.split('#')
3282
+ const pathNoAnchor = splitPath[0]
3283
+ const anchor = splitPath[1]
3284
+
3285
+ let finalPath = path
3286
+ if (useUrlParams) {
3287
+ finalPath = pathNoAnchor
3288
+ const hasValues = !filteredFormData.keys().next().done
3289
+ if (hasValues) {
3290
+ if (finalPath.indexOf('?') < 0) {
3291
+ finalPath += '?'
3292
+ } else {
3293
+ finalPath += '&'
3294
+ }
3295
+ finalPath += urlEncode(filteredFormData)
3296
+ if (anchor) {
3297
+ finalPath += '#' + anchor
3298
+ }
3299
+ }
3300
+ }
3301
+
3302
+ if (!verifyPath(elt, finalPath, requestConfig)) {
3303
+ triggerErrorEvent(elt, 'htmx:invalidPath', requestConfig)
3304
+ maybeCall(reject)
3305
+ return promise
3306
+ };
3307
+
3308
+ xhr.open(verb.toUpperCase(), finalPath, true)
3309
+ xhr.overrideMimeType('text/html')
3310
+ xhr.withCredentials = requestConfig.withCredentials
3311
+ xhr.timeout = requestConfig.timeout
3312
+
3313
+ // request headers
3314
+ if (requestAttrValues.noHeaders) {
3315
+ // ignore all headers
3316
+ } else {
3317
+ for (const header in headers) {
3318
+ if (headers.hasOwnProperty(header)) {
3319
+ const headerValue = headers[header]
3320
+ safelySetHeaderValue(xhr, header, headerValue)
3321
+ }
3322
+ }
3323
+ }
3324
+
3325
+ const responseInfo = {
3326
+ xhr,
3327
+ target,
3328
+ requestConfig,
3329
+ etc,
3330
+ boosted: eltIsBoosted,
3331
+ select,
3332
+ pathInfo: {
3333
+ requestPath: path,
3334
+ finalRequestPath: finalPath,
3335
+ anchor
3336
+ }
3337
+ }
3338
+
3339
+ xhr.onload = function() {
3340
+ try {
3341
+ const hierarchy = hierarchyForElt(elt)
3342
+ responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
3343
+ responseHandler(elt, responseInfo)
3344
+ removeRequestIndicators(indicators, disableElts)
3345
+ triggerEvent(elt, 'htmx:afterRequest', responseInfo)
3346
+ triggerEvent(elt, 'htmx:afterOnLoad', responseInfo)
3347
+ // if the body no longer contains the element, trigger the event on the closest parent
3348
+ // remaining in the DOM
3349
+ if (!bodyContains(elt)) {
3350
+ let secondaryTriggerElt = null
3351
+ while (hierarchy.length > 0 && secondaryTriggerElt == null) {
3352
+ const parentEltInHierarchy = hierarchy.shift()
3353
+ if (bodyContains(parentEltInHierarchy)) {
3354
+ secondaryTriggerElt = parentEltInHierarchy
3355
+ }
3356
+ }
3357
+ if (secondaryTriggerElt) {
3358
+ triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo)
3359
+ triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo)
3360
+ }
3361
+ }
3362
+ maybeCall(resolve)
3363
+ endRequestLock()
3364
+ } catch (e) {
3365
+ triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({ error: e }, responseInfo))
3366
+ throw e
3367
+ }
3368
+ }
3369
+ xhr.onerror = function() {
3370
+ removeRequestIndicators(indicators, disableElts)
3371
+ triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo)
3372
+ triggerErrorEvent(elt, 'htmx:sendError', responseInfo)
3373
+ maybeCall(reject)
3374
+ endRequestLock()
3375
+ }
3376
+ xhr.onabort = function() {
3377
+ removeRequestIndicators(indicators, disableElts)
3378
+ triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo)
3379
+ triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo)
3380
+ maybeCall(reject)
3381
+ endRequestLock()
3382
+ }
3383
+ xhr.ontimeout = function() {
3384
+ removeRequestIndicators(indicators, disableElts)
3385
+ triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo)
3386
+ triggerErrorEvent(elt, 'htmx:timeout', responseInfo)
3387
+ maybeCall(reject)
3388
+ endRequestLock()
3389
+ }
3390
+ if (!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)) {
3391
+ maybeCall(resolve)
3392
+ endRequestLock()
3393
+ return promise
3394
+ }
3395
+ var indicators = addRequestIndicatorClasses(elt)
3396
+ var disableElts = disableElements(elt)
3397
+
3398
+ forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
3399
+ forEach([xhr, xhr.upload], function(target) {
3400
+ target.addEventListener(eventName, function(event) {
3401
+ triggerEvent(elt, 'htmx:xhr:' + eventName, {
3402
+ lengthComputable: event.lengthComputable,
3403
+ loaded: event.loaded,
3404
+ total: event.total
3405
+ })
3406
+ })
3407
+ })
3408
+ })
3409
+ triggerEvent(elt, 'htmx:beforeSend', responseInfo)
3410
+ const params = useUrlParams ? null : encodeParamsForBody(xhr, elt, filteredFormData)
3411
+ xhr.send(params)
3412
+ return promise
3413
+ }
3414
+
3415
+ function determineHistoryUpdates(elt, responseInfo) {
3416
+ const xhr = responseInfo.xhr
3417
+
3418
+ //= ==========================================
3419
+ // First consult response headers
3420
+ //= ==========================================
3421
+ let pathFromHeaders = null
3422
+ let typeFromHeaders = null
3423
+ if (hasHeader(xhr, /HX-Push:/i)) {
3424
+ pathFromHeaders = xhr.getResponseHeader('HX-Push')
3425
+ typeFromHeaders = 'push'
3426
+ } else if (hasHeader(xhr, /HX-Push-Url:/i)) {
3427
+ pathFromHeaders = xhr.getResponseHeader('HX-Push-Url')
3428
+ typeFromHeaders = 'push'
3429
+ } else if (hasHeader(xhr, /HX-Replace-Url:/i)) {
3430
+ pathFromHeaders = xhr.getResponseHeader('HX-Replace-Url')
3431
+ typeFromHeaders = 'replace'
3432
+ }
3433
+
3434
+ // if there was a response header, that has priority
3435
+ if (pathFromHeaders) {
3436
+ if (pathFromHeaders === 'false') {
3437
+ return {}
3438
+ } else {
3439
+ return {
3440
+ type: typeFromHeaders,
3441
+ path: pathFromHeaders
3442
+ }
3443
+ }
3444
+ }
3445
+
3446
+ //= ==========================================
3447
+ // Next resolve via DOM values
3448
+ //= ==========================================
3449
+ const requestPath = responseInfo.pathInfo.finalRequestPath
3450
+ const responsePath = responseInfo.pathInfo.responsePath
3451
+
3452
+ const pushUrl = getClosestAttributeValue(elt, 'hx-push-url')
3453
+ const replaceUrl = getClosestAttributeValue(elt, 'hx-replace-url')
3454
+ const elementIsBoosted = getInternalData(elt).boosted
3455
+
3456
+ let saveType = null
3457
+ let path = null
3458
+
3459
+ if (pushUrl) {
3460
+ saveType = 'push'
3461
+ path = pushUrl
3462
+ } else if (replaceUrl) {
3463
+ saveType = 'replace'
3464
+ path = replaceUrl
3465
+ } else if (elementIsBoosted) {
3466
+ saveType = 'push'
3467
+ path = responsePath || requestPath // if there is no response path, go with the original request path
3468
+ }
3469
+
3470
+ if (path) {
3471
+ // false indicates no push, return empty object
3472
+ if (path === 'false') {
3473
+ return {}
3474
+ }
3475
+
3476
+ // true indicates we want to follow wherever the server ended up sending us
3477
+ if (path === 'true') {
3478
+ path = responsePath || requestPath // if there is no response path, go with the original request path
3479
+ }
3480
+
3481
+ // restore any anchor associated with the request
3482
+ if (responseInfo.pathInfo.anchor && path.indexOf('#') === -1) {
3483
+ path = path + '#' + responseInfo.pathInfo.anchor
3484
+ }
3485
+
3486
+ return {
3487
+ type: saveType,
3488
+ path
3489
+ }
3490
+ } else {
3491
+ return {}
3492
+ }
3493
+ }
3494
+
3495
+ function codeMatches(responseHandlingConfig, status) {
3496
+ var regExp = new RegExp(responseHandlingConfig.code)
3497
+ return regExp.test(status)
3498
+ }
3499
+
3500
+ function resolveResponseHandling(xhr) {
3501
+ for (var i = 0; i < htmx.config.responseHandling.length; i++) {
3502
+ var responseHandlingElement = htmx.config.responseHandling[i]
3503
+ if (codeMatches(responseHandlingElement, xhr.status)) {
3504
+ return responseHandlingElement
3505
+ }
3506
+ }
3507
+ // no matches, return no swap
3508
+ return {
3509
+ swap: false
3510
+ }
3511
+ }
3512
+
3513
+ function handleTitle(title) {
3514
+ if (title) {
3515
+ const titleElt = find('title')
3516
+ if (titleElt) {
3517
+ titleElt.innerHTML = title
3518
+ } else {
3519
+ window.document.title = title
3520
+ }
3521
+ }
3522
+ }
3523
+
3524
+ function handleAjaxResponse(elt, responseInfo) {
3525
+ const xhr = responseInfo.xhr
3526
+ let target = responseInfo.target
3527
+ const etc = responseInfo.etc
3528
+ const responseInfoSelect = responseInfo.select
3529
+
3530
+ if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return
3531
+
3532
+ if (hasHeader(xhr, /HX-Trigger:/i)) {
3533
+ handleTriggerHeader(xhr, 'HX-Trigger', elt)
3534
+ }
3535
+
3536
+ if (hasHeader(xhr, /HX-Location:/i)) {
3537
+ saveCurrentPageToHistory()
3538
+ let redirectPath = xhr.getResponseHeader('HX-Location')
3539
+ var swapSpec
3540
+ if (redirectPath.indexOf('{') === 0) {
3541
+ swapSpec = parseJSON(redirectPath)
3542
+ // what's the best way to throw an error if the user didn't include this
3543
+ redirectPath = swapSpec.path
3544
+ delete swapSpec.path
3545
+ }
3546
+ ajaxHelper('GET', redirectPath, swapSpec).then(function() {
3547
+ pushUrlIntoHistory(redirectPath)
3548
+ })
3549
+ return
3550
+ }
3551
+
3552
+ const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true'
3553
+
3554
+ if (hasHeader(xhr, /HX-Redirect:/i)) {
3555
+ location.href = xhr.getResponseHeader('HX-Redirect')
3556
+ shouldRefresh && location.reload()
3557
+ return
3558
+ }
3559
+
3560
+ if (shouldRefresh) {
3561
+ location.reload()
3562
+ return
3563
+ }
3564
+
3565
+ if (hasHeader(xhr, /HX-Retarget:/i)) {
3566
+ if (xhr.getResponseHeader('HX-Retarget') === 'this') {
3567
+ responseInfo.target = elt
3568
+ } else {
3569
+ responseInfo.target = querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget'))
3570
+ }
3571
+ }
3572
+
3573
+ const historyUpdate = determineHistoryUpdates(elt, responseInfo)
3574
+
3575
+ const responseHandling = resolveResponseHandling(xhr)
3576
+ const shouldSwap = responseHandling.swap
3577
+ let isError = !!responseHandling.error
3578
+ let ignoreTitle = htmx.config.ignoreTitle || responseHandling.ignoreTitle
3579
+ let selectOverride = responseHandling.select
3580
+ if (responseHandling.target) {
3581
+ responseInfo.target = querySelectorExt(elt, responseHandling.target)
3582
+ }
3583
+ var swapOverride = etc.swapOverride
3584
+ if (swapOverride == null && responseHandling.swapOverride) {
3585
+ swapOverride = responseHandling.swapOverride
3586
+ }
3587
+
3588
+ // response headers override response handling config
3589
+ if (hasHeader(xhr, /HX-Retarget:/i)) {
3590
+ if (xhr.getResponseHeader('HX-Retarget') === 'this') {
3591
+ responseInfo.target = elt
3592
+ } else {
3593
+ responseInfo.target = querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget'))
3594
+ }
3595
+ }
3596
+ if (hasHeader(xhr, /HX-Reswap:/i)) {
3597
+ swapOverride = xhr.getResponseHeader('HX-Reswap')
3598
+ }
3599
+
3600
+ var serverResponse = xhr.response
3601
+ var beforeSwapDetails = mergeObjects({
3602
+ shouldSwap,
3603
+ serverResponse,
3604
+ isError,
3605
+ ignoreTitle,
3606
+ selectOverride
3607
+ }, responseInfo)
3608
+
3609
+ if (responseHandling.event && !triggerEvent(target, responseHandling.event, beforeSwapDetails)) return
3610
+
3611
+ if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return
3612
+
3613
+ target = beforeSwapDetails.target // allow re-targeting
3614
+ serverResponse = beforeSwapDetails.serverResponse // allow updating content
3615
+ isError = beforeSwapDetails.isError // allow updating error
3616
+ ignoreTitle = beforeSwapDetails.ignoreTitle // allow updating ignoring title
3617
+ selectOverride = beforeSwapDetails.selectOverride // allow updating select override
3618
+
3619
+ responseInfo.target = target // Make updated target available to response events
3620
+ responseInfo.failed = isError // Make failed property available to response events
3621
+ responseInfo.successful = !isError // Make successful property available to response events
3622
+
3623
+ if (beforeSwapDetails.shouldSwap) {
3624
+ if (xhr.status === 286) {
3625
+ cancelPolling(elt)
3626
+ }
3627
+
3628
+ withExtensions(elt, function(extension) {
3629
+ serverResponse = extension.transformResponse(serverResponse, xhr, elt)
3630
+ })
3631
+
3632
+ // Save current page if there will be a history update
3633
+ if (historyUpdate.type) {
3634
+ saveCurrentPageToHistory()
3635
+ }
3636
+
3637
+ if (hasHeader(xhr, /HX-Reswap:/i)) {
3638
+ swapOverride = xhr.getResponseHeader('HX-Reswap')
3639
+ }
3640
+ var swapSpec = getSwapSpecification(elt, swapOverride)
3641
+
3642
+ if (!swapSpec.hasOwnProperty('ignoreTitle')) {
3643
+ swapSpec.ignoreTitle = ignoreTitle
3644
+ }
3645
+
3646
+ target.classList.add(htmx.config.swappingClass)
3647
+
3648
+ // optional transition API promise callbacks
3649
+ let settleResolve = null
3650
+ let settleReject = null
3651
+
3652
+ if (responseInfoSelect) {
3653
+ selectOverride = responseInfoSelect
3654
+ }
3655
+
3656
+ if (hasHeader(xhr, /HX-Reselect:/i)) {
3657
+ selectOverride = xhr.getResponseHeader('HX-Reselect')
3658
+ }
3659
+
3660
+ const selectOOB = getClosestAttributeValue(elt, 'hx-select-oob')
3661
+ const select = getClosestAttributeValue(elt, 'hx-select')
3662
+
3663
+ let doSwap = function() {
3664
+ try {
3665
+ // if we need to save history, do so, before swapping so that relative resources have the correct base URL
3666
+ if (historyUpdate.type) {
3667
+ triggerEvent(getDocument().body, 'htmx:beforeHistoryUpdate', mergeObjects({ history: historyUpdate }, responseInfo))
3668
+ if (historyUpdate.type === 'push') {
3669
+ pushUrlIntoHistory(historyUpdate.path)
3670
+ triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', { path: historyUpdate.path })
3671
+ } else {
3672
+ replaceUrlInHistory(historyUpdate.path)
3673
+ triggerEvent(getDocument().body, 'htmx:replacedInHistory', { path: historyUpdate.path })
3674
+ }
3675
+ }
3676
+
3677
+ swap(target, serverResponse, swapSpec, {
3678
+ select: selectOverride || select,
3679
+ selectOOB,
3680
+ eventInfo: responseInfo,
3681
+ anchor: responseInfo.pathInfo.anchor,
3682
+ contextElement: elt,
3683
+ afterSwapCallback: function() {
3684
+ if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
3685
+ let finalElt = elt
3686
+ if (!bodyContains(elt)) {
3687
+ finalElt = getDocument().body
3688
+ }
3689
+ handleTriggerHeader(xhr, 'HX-Trigger-After-Swap', finalElt)
3690
+ }
3691
+ },
3692
+ afterSettleCallback: function() {
3693
+ if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
3694
+ let finalElt = elt
3695
+ if (!bodyContains(elt)) {
3696
+ finalElt = getDocument().body
3697
+ }
3698
+ handleTriggerHeader(xhr, 'HX-Trigger-After-Settle', finalElt)
3699
+ }
3700
+ maybeCall(settleResolve)
3701
+ }
3702
+ })
3703
+ } catch (e) {
3704
+ triggerErrorEvent(elt, 'htmx:swapError', responseInfo)
3705
+ maybeCall(settleReject)
3706
+ throw e
3707
+ }
3708
+ }
3709
+
3710
+ let shouldTransition = htmx.config.globalViewTransitions
3711
+ if (swapSpec.hasOwnProperty('transition')) {
3712
+ shouldTransition = swapSpec.transition
3713
+ }
3714
+
3715
+ if (shouldTransition &&
3716
+ triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
3717
+ typeof Promise !== 'undefined' && document.startViewTransition) {
3718
+ const settlePromise = new Promise(function(_resolve, _reject) {
3719
+ settleResolve = _resolve
3720
+ settleReject = _reject
3721
+ })
3722
+ // wrap the original doSwap() in a call to startViewTransition()
3723
+ const innerDoSwap = doSwap
3724
+ doSwap = function() {
3725
+ document.startViewTransition(function() {
3726
+ innerDoSwap()
3727
+ return settlePromise
3728
+ })
3729
+ }
3730
+ }
3731
+
3732
+ if (swapSpec.swapDelay > 0) {
3733
+ setTimeout(doSwap, swapSpec.swapDelay)
3734
+ } else {
3735
+ doSwap()
3736
+ }
3737
+ }
3738
+ if (isError) {
3739
+ triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({ error: 'Response Status Error Code ' + xhr.status + ' from ' + responseInfo.pathInfo.requestPath }, responseInfo))
3740
+ }
3741
+ }
3742
+
3743
+ //= ===================================================================
3744
+ // Extensions API
3745
+ //= ===================================================================
3746
+
3747
+ /** @type {Object<string, import("./htmx").HtmxExtension>} */
3748
+ const extensions = {}
3749
+
3750
+ /**
3751
+ * extensionBase defines the default functions for all extensions.
3752
+ * @returns {import("./htmx").HtmxExtension}
3753
+ */
3754
+ function extensionBase() {
3755
+ return {
3756
+ init: function(api) { return null },
3757
+ onEvent: function(name, evt) { return true },
3758
+ transformResponse: function(text, xhr, elt) { return text },
3759
+ isInlineSwap: function(swapStyle) { return false },
3760
+ handleSwap: function(swapStyle, target, fragment, settleInfo) { return false },
3761
+ encodeParameters: function(xhr, parameters, elt) { return null }
3762
+ }
3763
+ }
3764
+
3765
+ /**
3766
+ * defineExtension initializes the extension and adds it to the htmx registry
3767
+ *
3768
+ * @param {string} name
3769
+ * @param {import("./htmx").HtmxExtension} extension
3770
+ */
3771
+ function defineExtension(name, extension) {
3772
+ if (extension.init) {
3773
+ extension.init(internalAPI)
3774
+ }
3775
+ extensions[name] = mergeObjects(extensionBase(), extension)
3776
+ }
3777
+
3778
+ /**
3779
+ * removeExtension removes an extension from the htmx registry
3780
+ *
3781
+ * @param {string} name
3782
+ */
3783
+ function removeExtension(name) {
3784
+ delete extensions[name]
3785
+ }
3786
+
3787
+ /**
3788
+ * getExtensions searches up the DOM tree to return all extensions that can be applied to a given element
3789
+ *
3790
+ * @param {HTMLElement} elt
3791
+ * @param {import("./htmx").HtmxExtension[]=} extensionsToReturn
3792
+ * @param {import("./htmx").HtmxExtension[]=} extensionsToIgnore
3793
+ */
3794
+ function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
3795
+ if (extensionsToReturn == undefined) {
3796
+ extensionsToReturn = []
3797
+ }
3798
+ if (elt == undefined) {
3799
+ return extensionsToReturn
3800
+ }
3801
+ if (extensionsToIgnore == undefined) {
3802
+ extensionsToIgnore = []
3803
+ }
3804
+ const extensionsForElement = getAttributeValue(elt, 'hx-ext')
3805
+ if (extensionsForElement) {
3806
+ forEach(extensionsForElement.split(','), function(extensionName) {
3807
+ extensionName = extensionName.replace(/ /g, '')
3808
+ if (extensionName.slice(0, 7) == 'ignore:') {
3809
+ extensionsToIgnore.push(extensionName.slice(7))
3810
+ return
3811
+ }
3812
+ if (extensionsToIgnore.indexOf(extensionName) < 0) {
3813
+ const extension = extensions[extensionName]
3814
+ if (extension && extensionsToReturn.indexOf(extension) < 0) {
3815
+ extensionsToReturn.push(extension)
3816
+ }
3817
+ }
3818
+ })
3819
+ }
3820
+ return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore)
3821
+ }
3822
+
3823
+ //= ===================================================================
3824
+ // Initialization
3825
+ //= ===================================================================
3826
+ var isReady = false
3827
+ getDocument().addEventListener('DOMContentLoaded', function() {
3828
+ isReady = true
3829
+ })
3830
+
3831
+ /**
3832
+ * Execute a function now if DOMContentLoaded has fired, otherwise listen for it.
3833
+ *
3834
+ * This function uses isReady because there is no realiable way to ask the browswer whether
3835
+ * the DOMContentLoaded event has already been fired; there's a gap between DOMContentLoaded
3836
+ * firing and readystate=complete.
3837
+ */
3838
+ function ready(fn) {
3839
+ // Checking readyState here is a failsafe in case the htmx script tag entered the DOM by
3840
+ // some means other than the initial page load.
3841
+ if (isReady || getDocument().readyState === 'complete') {
3842
+ fn()
3843
+ } else {
3844
+ getDocument().addEventListener('DOMContentLoaded', fn)
3845
+ }
3846
+ }
3847
+
3848
+ function insertIndicatorStyles() {
3849
+ if (htmx.config.includeIndicatorStyles !== false) {
3850
+ getDocument().head.insertAdjacentHTML('beforeend',
3851
+ '<style>\
3852
+ .' + htmx.config.indicatorClass + '{opacity:0}\
3853
+ .' + htmx.config.requestClass + ' .' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
3854
+ .' + htmx.config.requestClass + '.' + htmx.config.indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
3855
+ </style>')
3856
+ }
3857
+ }
3858
+
3859
+ function getMetaConfig() {
3860
+ const element = getDocument().querySelector('meta[name="htmx-config"]')
3861
+ if (element) {
3862
+ // @ts-ignore
3863
+ return parseJSON(element.content)
3864
+ } else {
3865
+ return null
3866
+ }
3867
+ }
3868
+
3869
+ function mergeMetaConfig() {
3870
+ const metaConfig = getMetaConfig()
3871
+ if (metaConfig) {
3872
+ htmx.config = mergeObjects(htmx.config, metaConfig)
3873
+ }
3874
+ }
3875
+
3876
+ // initialize the document
3877
+ ready(function() {
3878
+ mergeMetaConfig()
3879
+ insertIndicatorStyles()
3880
+ let body = getDocument().body
3881
+ processNode(body)
3882
+ const restoredElts = getDocument().querySelectorAll(
3883
+ "[hx-trigger='restored'],[data-hx-trigger='restored']"
3884
+ )
3885
+ body.addEventListener('htmx:abort', function(evt) {
3886
+ const target = evt.target
3887
+ const internalData = getInternalData(target)
3888
+ if (internalData && internalData.xhr) {
3889
+ internalData.xhr.abort()
3890
+ }
3891
+ })
3892
+ /** @type {(ev: PopStateEvent) => any} */
3893
+ const originalPopstate = window.onpopstate ? window.onpopstate.bind(window) : null
3894
+ /** @type {(ev: PopStateEvent) => any} */
3895
+ window.onpopstate = function(event) {
3896
+ if (event.state && event.state.htmx) {
3897
+ restoreHistory()
3898
+ forEach(restoredElts, function(elt) {
3899
+ triggerEvent(elt, 'htmx:restored', {
3900
+ document: getDocument(),
3901
+ triggerEvent
3902
+ })
3903
+ })
3904
+ } else {
3905
+ if (originalPopstate) {
3906
+ originalPopstate(event)
3907
+ }
3908
+ }
3909
+ }
3910
+ setTimeout(function() {
3911
+ triggerEvent(body, 'htmx:load', {}) // give ready handlers a chance to load up before firing this event
3912
+ body = null // kill reference for gc
3913
+ }, 0)
3914
+ })
3915
+
3916
+ return htmx
3917
+ })()
3918
+ return htmx
3919
+ })