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