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