htmx.org 2.0.2 → 2.0.3

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.js CHANGED
@@ -277,7 +277,7 @@ var htmx = (function() {
277
277
  parseInterval: null,
278
278
  /** @type {typeof internalEval} */
279
279
  _: null,
280
- version: '2.0.2'
280
+ version: '2.0.3'
281
281
  }
282
282
  // Tsc madness part 2
283
283
  htmx.onLoad = onLoadHelper
@@ -337,22 +337,10 @@ var htmx = (function() {
337
337
  return '[hx-' + verb + '], [data-hx-' + verb + ']'
338
338
  }).join(', ')
339
339
 
340
- const HEAD_TAG_REGEX = makeTagRegEx('head')
341
-
342
340
  //= ===================================================================
343
341
  // Utilities
344
342
  //= ===================================================================
345
343
 
346
- /**
347
- * @param {string} tag
348
- * @param {boolean} global
349
- * @returns {RegExp}
350
- */
351
- function makeTagRegEx(tag, global = false) {
352
- return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
353
- global ? 'gim' : 'im')
354
- }
355
-
356
344
  /**
357
345
  * Parses an interval string consistent with the way htmx does. Useful for plugins that have timing-related attributes.
358
346
  *
@@ -595,7 +583,7 @@ var htmx = (function() {
595
583
  */
596
584
  function makeFragment(response) {
597
585
  // strip head tag to determine shape of response we are dealing with
598
- const responseWithNoHead = response.replace(HEAD_TAG_REGEX, '')
586
+ const responseWithNoHead = response.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i, '')
599
587
  const startTag = getStartTag(responseWithNoHead)
600
588
  /** @type DocumentFragmentWithTitle */
601
589
  let fragment
@@ -695,7 +683,7 @@ var htmx = (function() {
695
683
  * @property {boolean} [triggeredOnce]
696
684
  * @property {number} [delayed]
697
685
  * @property {number|null} [throttle]
698
- * @property {string} [lastValue]
686
+ * @property {WeakMap<HtmxTriggerSpecification,WeakMap<EventTarget,string>>} [lastValue]
699
687
  * @property {boolean} [loaded]
700
688
  * @property {string} [path]
701
689
  * @property {string} [verb]
@@ -1161,6 +1149,8 @@ var htmx = (function() {
1161
1149
  return [document.body]
1162
1150
  } else if (selector === 'root') {
1163
1151
  return [getRootNode(elt, !!global)]
1152
+ } else if (selector === 'host') {
1153
+ return [(/** @type ShadowRoot */(elt.getRootNode())).host]
1164
1154
  } else if (selector.indexOf('global ') === 0) {
1165
1155
  return querySelectorAllExt(elt, selector.slice(7), true)
1166
1156
  } else {
@@ -1236,26 +1226,30 @@ var htmx = (function() {
1236
1226
  * @property {EventTarget} target
1237
1227
  * @property {AnyEventName} event
1238
1228
  * @property {EventListener} listener
1229
+ * @property {Object|boolean} options
1239
1230
  */
1240
1231
 
1241
1232
  /**
1242
1233
  * @param {EventTarget|AnyEventName} arg1
1243
1234
  * @param {AnyEventName|EventListener} arg2
1244
- * @param {EventListener} [arg3]
1235
+ * @param {EventListener|Object|boolean} [arg3]
1236
+ * @param {Object|boolean} [arg4]
1245
1237
  * @returns {EventArgs}
1246
1238
  */
1247
- function processEventArgs(arg1, arg2, arg3) {
1239
+ function processEventArgs(arg1, arg2, arg3, arg4) {
1248
1240
  if (isFunction(arg2)) {
1249
1241
  return {
1250
1242
  target: getDocument().body,
1251
1243
  event: asString(arg1),
1252
- listener: arg2
1244
+ listener: arg2,
1245
+ options: arg3
1253
1246
  }
1254
1247
  } else {
1255
1248
  return {
1256
1249
  target: resolveTarget(arg1),
1257
1250
  event: asString(arg2),
1258
- listener: arg3
1251
+ listener: arg3,
1252
+ options: arg4
1259
1253
  }
1260
1254
  }
1261
1255
  }
@@ -1267,13 +1261,14 @@ var htmx = (function() {
1267
1261
  *
1268
1262
  * @param {EventTarget|string} arg1 the element to add the listener to | the event name to add the listener for
1269
1263
  * @param {string|EventListener} arg2 the event name to add the listener for | the listener to add
1270
- * @param {EventListener} [arg3] the listener to add
1264
+ * @param {EventListener|Object|boolean} [arg3] the listener to add | options to add
1265
+ * @param {Object|boolean} [arg4] options to add
1271
1266
  * @returns {EventListener}
1272
1267
  */
1273
- function addEventListenerImpl(arg1, arg2, arg3) {
1268
+ function addEventListenerImpl(arg1, arg2, arg3, arg4) {
1274
1269
  ready(function() {
1275
- const eventArgs = processEventArgs(arg1, arg2, arg3)
1276
- eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener)
1270
+ const eventArgs = processEventArgs(arg1, arg2, arg3, arg4)
1271
+ eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener, eventArgs.options)
1277
1272
  })
1278
1273
  const b = isFunction(arg2)
1279
1274
  return b ? arg2 : arg3
@@ -1412,9 +1407,11 @@ var htmx = (function() {
1412
1407
  * @param {string} oobValue
1413
1408
  * @param {Element} oobElement
1414
1409
  * @param {HtmxSettleInfo} settleInfo
1410
+ * @param {Node|Document} [rootNode]
1415
1411
  * @returns
1416
1412
  */
1417
- function oobSwap(oobValue, oobElement, settleInfo) {
1413
+ function oobSwap(oobValue, oobElement, settleInfo, rootNode) {
1414
+ rootNode = rootNode || getDocument()
1418
1415
  let selector = '#' + getRawAttribute(oobElement, 'id')
1419
1416
  /** @type HtmxSwapStyle */
1420
1417
  let swapStyle = 'outerHTML'
@@ -1426,8 +1423,10 @@ var htmx = (function() {
1426
1423
  } else {
1427
1424
  swapStyle = oobValue
1428
1425
  }
1426
+ oobElement.removeAttribute('hx-swap-oob')
1427
+ oobElement.removeAttribute('data-hx-swap-oob')
1429
1428
 
1430
- const targets = getDocument().querySelectorAll(selector)
1429
+ const targets = querySelectorAllExt(rootNode, selector, false)
1431
1430
  if (targets) {
1432
1431
  forEach(
1433
1432
  targets,
@@ -1445,7 +1444,9 @@ var htmx = (function() {
1445
1444
 
1446
1445
  target = beforeSwapDetails.target // allow re-targeting
1447
1446
  if (beforeSwapDetails.shouldSwap) {
1447
+ handlePreservedElements(fragment)
1448
1448
  swapWithStyle(swapStyle, target, target, fragment, settleInfo)
1449
+ restorePreservedElements()
1449
1450
  }
1450
1451
  forEach(settleInfo.elts, function(elt) {
1451
1452
  triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails)
@@ -1460,15 +1461,39 @@ var htmx = (function() {
1460
1461
  return oobValue
1461
1462
  }
1462
1463
 
1464
+ function restorePreservedElements() {
1465
+ const pantry = find('#--htmx-preserve-pantry--')
1466
+ if (pantry) {
1467
+ for (const preservedElt of [...pantry.children]) {
1468
+ const existingElement = find('#' + preservedElt.id)
1469
+ // @ts-ignore - use proposed moveBefore feature
1470
+ existingElement.parentNode.moveBefore(preservedElt, existingElement)
1471
+ existingElement.remove()
1472
+ }
1473
+ pantry.remove()
1474
+ }
1475
+ }
1476
+
1463
1477
  /**
1464
- * @param {DocumentFragment} fragment
1478
+ * @param {DocumentFragment|ParentNode} fragment
1465
1479
  */
1466
1480
  function handlePreservedElements(fragment) {
1467
1481
  forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function(preservedElt) {
1468
1482
  const id = getAttributeValue(preservedElt, 'id')
1469
- const oldElt = getDocument().getElementById(id)
1470
- if (oldElt != null) {
1471
- preservedElt.parentNode.replaceChild(oldElt, preservedElt)
1483
+ const existingElement = getDocument().getElementById(id)
1484
+ if (existingElement != null) {
1485
+ if (preservedElt.moveBefore) { // if the moveBefore API exists, use it
1486
+ // get or create a storage spot for stuff
1487
+ let pantry = find('#--htmx-preserve-pantry--')
1488
+ if (pantry == null) {
1489
+ getDocument().body.insertAdjacentHTML('afterend', "<div id='--htmx-preserve-pantry--'></div>")
1490
+ pantry = find('#--htmx-preserve-pantry--')
1491
+ }
1492
+ // @ts-ignore - use proposed moveBefore feature
1493
+ pantry.moveBefore(existingElement, null)
1494
+ } else {
1495
+ preservedElt.parentNode.replaceChild(existingElement, preservedElt)
1496
+ }
1472
1497
  }
1473
1498
  })
1474
1499
  }
@@ -1632,9 +1657,13 @@ var htmx = (function() {
1632
1657
  /** @type {Node} */
1633
1658
  let newElt
1634
1659
  const eltBeforeNewContent = target.previousSibling
1635
- insertNodesBefore(parentElt(target), target, fragment, settleInfo)
1660
+ const parentNode = parentElt(target)
1661
+ if (!parentNode) { // when parent node disappears, we can't do anything
1662
+ return
1663
+ }
1664
+ insertNodesBefore(parentNode, target, fragment, settleInfo)
1636
1665
  if (eltBeforeNewContent == null) {
1637
- newElt = parentElt(target).firstChild
1666
+ newElt = parentNode.firstChild
1638
1667
  } else {
1639
1668
  newElt = eltBeforeNewContent.nextSibling
1640
1669
  }
@@ -1696,7 +1725,10 @@ var htmx = (function() {
1696
1725
  */
1697
1726
  function swapDelete(target) {
1698
1727
  cleanUpElement(target)
1699
- return parentElt(target).removeChild(target)
1728
+ const parent = parentElt(target)
1729
+ if (parent) {
1730
+ return parent.removeChild(target)
1731
+ }
1700
1732
  }
1701
1733
 
1702
1734
  /**
@@ -1779,14 +1811,15 @@ var htmx = (function() {
1779
1811
  /**
1780
1812
  * @param {DocumentFragment} fragment
1781
1813
  * @param {HtmxSettleInfo} settleInfo
1814
+ * @param {Node|Document} [rootNode]
1782
1815
  */
1783
- function findAndSwapOobElements(fragment, settleInfo) {
1816
+ function findAndSwapOobElements(fragment, settleInfo, rootNode) {
1784
1817
  var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
1785
1818
  forEach(oobElts, function(oobElement) {
1786
1819
  if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
1787
1820
  const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
1788
1821
  if (oobValue != null) {
1789
- oobSwap(oobValue, oobElement, settleInfo)
1822
+ oobSwap(oobValue, oobElement, settleInfo, rootNode)
1790
1823
  }
1791
1824
  } else {
1792
1825
  oobElement.removeAttribute('hx-swap-oob')
@@ -1810,6 +1843,7 @@ var htmx = (function() {
1810
1843
  }
1811
1844
 
1812
1845
  target = resolveTarget(target)
1846
+ const rootNode = swapOptions.contextElement ? getRootNode(swapOptions.contextElement, false) : getDocument()
1813
1847
 
1814
1848
  // preserve focus and selection
1815
1849
  const activeElt = document.activeElement
@@ -1848,14 +1882,14 @@ var htmx = (function() {
1848
1882
  const oobValue = oobSelectValue[1] || 'true'
1849
1883
  const oobElement = fragment.querySelector('#' + id)
1850
1884
  if (oobElement) {
1851
- oobSwap(oobValue, oobElement, settleInfo)
1885
+ oobSwap(oobValue, oobElement, settleInfo, rootNode)
1852
1886
  }
1853
1887
  }
1854
1888
  }
1855
1889
  // oob swaps
1856
- findAndSwapOobElements(fragment, settleInfo)
1890
+ findAndSwapOobElements(fragment, settleInfo, rootNode)
1857
1891
  forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
1858
- if (findAndSwapOobElements(template.content, settleInfo)) {
1892
+ if (findAndSwapOobElements(template.content, settleInfo, rootNode)) {
1859
1893
  // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
1860
1894
  template.remove()
1861
1895
  }
@@ -1871,6 +1905,7 @@ var htmx = (function() {
1871
1905
  }
1872
1906
  handlePreservedElements(fragment)
1873
1907
  swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
1908
+ restorePreservedElements()
1874
1909
  }
1875
1910
 
1876
1911
  // apply saved focus and selection information to swapped content
@@ -2141,8 +2176,8 @@ var htmx = (function() {
2141
2176
  if (eventFilter) {
2142
2177
  triggerSpec.eventFilter = eventFilter
2143
2178
  }
2179
+ consumeUntil(tokens, NOT_WHITESPACE)
2144
2180
  while (tokens.length > 0 && tokens[0] !== ',') {
2145
- consumeUntil(tokens, NOT_WHITESPACE)
2146
2181
  const token = tokens.shift()
2147
2182
  if (token === 'changed') {
2148
2183
  triggerSpec.changed = true
@@ -2187,6 +2222,7 @@ var htmx = (function() {
2187
2222
  } else {
2188
2223
  triggerErrorEvent(elt, 'htmx:syntax:error', { token: tokens.shift() })
2189
2224
  }
2225
+ consumeUntil(tokens, NOT_WHITESPACE)
2190
2226
  }
2191
2227
  triggerSpecs.push(triggerSpec)
2192
2228
  }
@@ -2281,14 +2317,15 @@ var htmx = (function() {
2281
2317
  nodeData.boosted = true
2282
2318
  let verb, path
2283
2319
  if (elt.tagName === 'A') {
2284
- verb = 'get'
2320
+ verb = (/** @type HttpVerb */('get'))
2285
2321
  path = getRawAttribute(elt, 'href')
2286
2322
  } else {
2287
2323
  const rawAttribute = getRawAttribute(elt, 'method')
2288
- verb = rawAttribute ? rawAttribute.toLowerCase() : 'get'
2289
- if (verb === 'get') {
2290
- }
2324
+ verb = (/** @type HttpVerb */(rawAttribute ? rawAttribute.toLowerCase() : 'get'))
2291
2325
  path = getRawAttribute(elt, 'action')
2326
+ if (verb === 'get' && path.includes('?')) {
2327
+ path = path.replace(/\?[^#]+/, '')
2328
+ }
2292
2329
  }
2293
2330
  triggerSpecs.forEach(function(triggerSpec) {
2294
2331
  addEventListener(elt, function(node, evt) {
@@ -2377,10 +2414,15 @@ var htmx = (function() {
2377
2414
  }
2378
2415
  // store the initial values of the elements, so we can tell if they change
2379
2416
  if (triggerSpec.changed) {
2417
+ if (!('lastValue' in elementData)) {
2418
+ elementData.lastValue = new WeakMap()
2419
+ }
2380
2420
  eltsToListenOn.forEach(function(eltToListenOn) {
2381
- const eltToListenOnData = getInternalData(eltToListenOn)
2421
+ if (!elementData.lastValue.has(triggerSpec)) {
2422
+ elementData.lastValue.set(triggerSpec, new WeakMap())
2423
+ }
2382
2424
  // @ts-ignore value will be undefined for non-input elements, which is fine
2383
- eltToListenOnData.lastValue = eltToListenOn.value
2425
+ elementData.lastValue.get(triggerSpec).set(eltToListenOn, eltToListenOn.value)
2384
2426
  })
2385
2427
  }
2386
2428
  forEach(eltsToListenOn, function(eltToListenOn) {
@@ -2422,13 +2464,14 @@ var htmx = (function() {
2422
2464
  }
2423
2465
  }
2424
2466
  if (triggerSpec.changed) {
2425
- const eltToListenOnData = getInternalData(eltToListenOn)
2467
+ const node = event.target
2426
2468
  // @ts-ignore value will be undefined for non-input elements, which is fine
2427
- const value = eltToListenOn.value
2428
- if (eltToListenOnData.lastValue === value) {
2469
+ const value = node.value
2470
+ const lastValue = elementData.lastValue.get(triggerSpec)
2471
+ if (lastValue.has(node) && lastValue.get(node) === value) {
2429
2472
  return
2430
2473
  }
2431
- eltToListenOnData.lastValue = value
2474
+ lastValue.set(node, value)
2432
2475
  }
2433
2476
  if (elementData.delayed) {
2434
2477
  clearTimeout(elementData.delayed)
@@ -2476,6 +2519,7 @@ var htmx = (function() {
2476
2519
  windowIsScrolling = true
2477
2520
  }
2478
2521
  window.addEventListener('scroll', scrollHandler)
2522
+ window.addEventListener('resize', scrollHandler)
2479
2523
  setInterval(function() {
2480
2524
  if (windowIsScrolling) {
2481
2525
  windowIsScrolling = false
@@ -2805,12 +2849,6 @@ var htmx = (function() {
2805
2849
 
2806
2850
  triggerEvent(elt, 'htmx:beforeProcessNode')
2807
2851
 
2808
- // @ts-ignore value will be undefined for non-input elements, which is fine
2809
- if (elt.value) {
2810
- // @ts-ignore
2811
- nodeData.lastValue = elt.value
2812
- }
2813
-
2814
2852
  const triggerSpecs = getTriggerSpecs(elt)
2815
2853
  const hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs)
2816
2854
 
@@ -3153,7 +3191,9 @@ var htmx = (function() {
3153
3191
  const settleInfo = makeSettleInfo(historyElement)
3154
3192
  handleTitle(fragment.title)
3155
3193
 
3194
+ handlePreservedElements(fragment)
3156
3195
  swapInnerHTML(historyElement, content, settleInfo)
3196
+ restorePreservedElements()
3157
3197
  settleImmediately(settleInfo.tasks)
3158
3198
  currentPathForHistory = path
3159
3199
  triggerEvent(getDocument().body, 'htmx:historyRestore', { path, cacheMiss: true, serverResponse: this.response })
@@ -3175,8 +3215,10 @@ var htmx = (function() {
3175
3215
  const fragment = makeFragment(cached.content)
3176
3216
  const historyElement = getHistoryElement()
3177
3217
  const settleInfo = makeSettleInfo(historyElement)
3178
- handleTitle(fragment.title)
3218
+ handleTitle(cached.title)
3219
+ handlePreservedElements(fragment)
3179
3220
  swapInnerHTML(historyElement, fragment, settleInfo)
3221
+ restorePreservedElements()
3180
3222
  settleImmediately(settleInfo.tasks)
3181
3223
  getWindow().setTimeout(function() {
3182
3224
  window.scrollTo(0, cached.scroll)
@@ -3234,16 +3276,18 @@ var htmx = (function() {
3234
3276
  * @param {Element[]} disabled
3235
3277
  */
3236
3278
  function removeRequestIndicators(indicators, disabled) {
3279
+ forEach(indicators.concat(disabled), function(ele) {
3280
+ const internalData = getInternalData(ele)
3281
+ internalData.requestCount = (internalData.requestCount || 1) - 1
3282
+ })
3237
3283
  forEach(indicators, function(ic) {
3238
3284
  const internalData = getInternalData(ic)
3239
- internalData.requestCount = (internalData.requestCount || 0) - 1
3240
3285
  if (internalData.requestCount === 0) {
3241
3286
  ic.classList.remove.call(ic.classList, htmx.config.requestClass)
3242
3287
  }
3243
3288
  })
3244
3289
  forEach(disabled, function(disabledElement) {
3245
3290
  const internalData = getInternalData(disabledElement)
3246
- internalData.requestCount = (internalData.requestCount || 0) - 1
3247
3291
  if (internalData.requestCount === 0) {
3248
3292
  disabledElement.removeAttribute('disabled')
3249
3293
  disabledElement.removeAttribute('data-disabled-by-htmx')
@@ -3856,16 +3900,22 @@ var htmx = (function() {
3856
3900
  if (context) {
3857
3901
  if (context instanceof Element || typeof context === 'string') {
3858
3902
  return issueAjaxRequest(verb, path, null, null, {
3859
- targetOverride: resolveTarget(context),
3903
+ targetOverride: resolveTarget(context) || DUMMY_ELT,
3860
3904
  returnPromise: true
3861
3905
  })
3862
3906
  } else {
3907
+ let resolvedTarget = resolveTarget(context.target)
3908
+ // If target is supplied but can't resolve OR both target and source can't be resolved
3909
+ // then use DUMMY_ELT to abort the request with htmx:targetError to avoid it replacing body by mistake
3910
+ if ((context.target && !resolvedTarget) || (!resolvedTarget && !resolveTarget(context.source))) {
3911
+ resolvedTarget = DUMMY_ELT
3912
+ }
3863
3913
  return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
3864
3914
  {
3865
3915
  handler: context.handler,
3866
3916
  headers: context.headers,
3867
3917
  values: context.values,
3868
- targetOverride: resolveTarget(context.target),
3918
+ targetOverride: resolvedTarget,
3869
3919
  swapOverride: context.swap,
3870
3920
  select: context.select,
3871
3921
  returnPromise: true
@@ -3927,7 +3977,7 @@ var htmx = (function() {
3927
3977
  const formData = new FormData()
3928
3978
  for (const key in obj) {
3929
3979
  if (obj.hasOwnProperty(key)) {
3930
- if (typeof obj[key].forEach === 'function') {
3980
+ if (obj[key] && typeof obj[key].forEach === 'function') {
3931
3981
  obj[key].forEach(function(v) { formData.append(key, v) })
3932
3982
  } else if (typeof obj[key] === 'object' && !(obj[key] instanceof Blob)) {
3933
3983
  formData.append(key, JSON.stringify(obj[key]))
@@ -4020,7 +4070,7 @@ var htmx = (function() {
4020
4070
  return false
4021
4071
  }
4022
4072
  target.delete(name)
4023
- if (typeof value.forEach === 'function') {
4073
+ if (value && typeof value.forEach === 'function') {
4024
4074
  value.forEach(function(v) { target.append(name, v) })
4025
4075
  } else if (typeof value === 'object' && !(value instanceof Blob)) {
4026
4076
  target.append(name, JSON.stringify(value))
@@ -4655,7 +4705,8 @@ var htmx = (function() {
4655
4705
  serverResponse,
4656
4706
  isError,
4657
4707
  ignoreTitle,
4658
- selectOverride
4708
+ selectOverride,
4709
+ swapOverride
4659
4710
  }, responseInfo)
4660
4711
 
4661
4712
  if (responseHandling.event && !triggerEvent(target, responseHandling.event, beforeSwapDetails)) return
@@ -4667,6 +4718,7 @@ var htmx = (function() {
4667
4718
  isError = beforeSwapDetails.isError // allow updating error
4668
4719
  ignoreTitle = beforeSwapDetails.ignoreTitle // allow updating ignoring title
4669
4720
  selectOverride = beforeSwapDetails.selectOverride // allow updating select override
4721
+ swapOverride = beforeSwapDetails.swapOverride // allow updating swap override
4670
4722
 
4671
4723
  responseInfo.target = target // Make updated target available to response events
4672
4724
  responseInfo.failed = isError // Make failed property available to response events
@@ -4686,9 +4738,6 @@ var htmx = (function() {
4686
4738
  saveCurrentPageToHistory()
4687
4739
  }
4688
4740
 
4689
- if (hasHeader(xhr, /HX-Reswap:/i)) {
4690
- swapOverride = xhr.getResponseHeader('HX-Reswap')
4691
- }
4692
4741
  var swapSpec = getSwapSpecification(elt, swapOverride)
4693
4742
 
4694
4743
  if (!swapSpec.hasOwnProperty('ignoreTitle')) {
@@ -5121,7 +5170,7 @@ var htmx = (function() {
5121
5170
  */
5122
5171
 
5123
5172
  /**
5124
- * @typedef {HtmxResponseInfo & {shouldSwap: boolean, serverResponse: any, isError: boolean, ignoreTitle: boolean, selectOverride:string}} HtmxBeforeSwapDetails
5173
+ * @typedef {HtmxResponseInfo & {shouldSwap: boolean, serverResponse: any, isError: boolean, ignoreTitle: boolean, selectOverride:string, swapOverride:string}} HtmxBeforeSwapDetails
5125
5174
  */
5126
5175
 
5127
5176
  /**