htmx.org 2.0.0-alpha1 → 2.0.0-alpha2

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