htmx.org 1.9.10 → 2.0.0-alpha1

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