htmx.org 2.0.4 → 2.0.6

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
@@ -82,7 +82,7 @@ var htmx = (function() {
82
82
  */
83
83
  historyEnabled: true,
84
84
  /**
85
- * The number of pages to keep in **localStorage** for history support.
85
+ * The number of pages to keep in **sessionStorage** for history support.
86
86
  * @type number
87
87
  * @default 10
88
88
  */
@@ -271,13 +271,25 @@ var htmx = (function() {
271
271
  * @type boolean
272
272
  * @default true
273
273
  */
274
- allowNestedOobSwaps: true
274
+ allowNestedOobSwaps: true,
275
+ /**
276
+ * Whether to treat history cache miss full page reload requests as a "HX-Request" by returning this response header
277
+ * This should always be disabled when using HX-Request header to optionally return partial responses
278
+ * @type boolean
279
+ * @default true
280
+ */
281
+ historyRestoreAsHxRequest: true
275
282
  },
276
283
  /** @type {typeof parseInterval} */
277
284
  parseInterval: null,
285
+ /**
286
+ * proxy of window.location used for page reload functions
287
+ * @type location
288
+ */
289
+ location,
278
290
  /** @type {typeof internalEval} */
279
291
  _: null,
280
- version: '2.0.4'
292
+ version: '2.0.6'
281
293
  }
282
294
  // Tsc madness part 2
283
295
  htmx.onLoad = onLoadHelper
@@ -484,10 +496,7 @@ var htmx = (function() {
484
496
  * @returns {boolean}
485
497
  */
486
498
  function matches(elt, selector) {
487
- // @ts-ignore: non-standard properties for browser compatibility
488
- // noinspection JSUnresolvedVariable
489
- const matchesFunction = elt instanceof Element && (elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector)
490
- return !!matchesFunction && matchesFunction.call(elt, selector)
499
+ return elt instanceof Element && elt.matches(selector)
491
500
  }
492
501
 
493
502
  /**
@@ -810,10 +819,10 @@ var htmx = (function() {
810
819
  * @returns {boolean}
811
820
  */
812
821
  function canAccessLocalStorage() {
813
- const test = 'htmx:localStorageTest'
822
+ const test = 'htmx:sessionStorageTest'
814
823
  try {
815
- localStorage.setItem(test, test)
816
- localStorage.removeItem(test)
824
+ sessionStorage.setItem(test, test)
825
+ sessionStorage.removeItem(test)
817
826
  return true
818
827
  } catch (e) {
819
828
  return false
@@ -825,20 +834,16 @@ var htmx = (function() {
825
834
  * @returns {string}
826
835
  */
827
836
  function normalizePath(path) {
828
- try {
829
- const url = new URL(path)
830
- if (url) {
831
- path = url.pathname + url.search
832
- }
833
- // remove trailing slash, unless index page
834
- if (!(/^\/$/.test(path))) {
835
- path = path.replace(/\/+$/, '')
836
- }
837
- return path
838
- } catch (e) {
839
- // be kind to IE11, which doesn't support URL()
840
- return path
837
+ // use dummy base URL to allow normalize on path only
838
+ const url = new URL(path, 'http://x')
839
+ if (url) {
840
+ path = url.pathname + url.search
841
841
  }
842
+ // remove trailing slash, unless index page
843
+ if (path != '/') {
844
+ path = path.replace(/\/+$/, '')
845
+ }
846
+ return path
842
847
  }
843
848
 
844
849
  //= =========================================================================================
@@ -1074,18 +1079,10 @@ var htmx = (function() {
1074
1079
  */
1075
1080
  function closest(elt, selector) {
1076
1081
  elt = asElement(resolveTarget(elt))
1077
- if (elt && elt.closest) {
1082
+ if (elt) {
1078
1083
  return elt.closest(selector)
1079
- } else {
1080
- // TODO remove when IE goes away
1081
- do {
1082
- if (elt == null || matches(elt, selector)) {
1083
- return elt
1084
- }
1085
- }
1086
- while (elt = elt && asElement(parentElt(elt)))
1087
- return null
1088
1084
  }
1085
+ return null
1089
1086
  }
1090
1087
 
1091
1088
  /**
@@ -1160,17 +1157,17 @@ var htmx = (function() {
1160
1157
  const selector = normalizeSelector(parts.shift())
1161
1158
  let item
1162
1159
  if (selector.indexOf('closest ') === 0) {
1163
- item = closest(asElement(elt), normalizeSelector(selector.substr(8)))
1160
+ item = closest(asElement(elt), normalizeSelector(selector.slice(8)))
1164
1161
  } else if (selector.indexOf('find ') === 0) {
1165
- item = find(asParentNode(elt), normalizeSelector(selector.substr(5)))
1162
+ item = find(asParentNode(elt), normalizeSelector(selector.slice(5)))
1166
1163
  } else if (selector === 'next' || selector === 'nextElementSibling') {
1167
1164
  item = asElement(elt).nextElementSibling
1168
1165
  } else if (selector.indexOf('next ') === 0) {
1169
- item = scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)
1166
+ item = scanForwardQuery(elt, normalizeSelector(selector.slice(5)), !!global)
1170
1167
  } else if (selector === 'previous' || selector === 'previousElementSibling') {
1171
1168
  item = asElement(elt).previousElementSibling
1172
1169
  } else if (selector.indexOf('previous ') === 0) {
1173
- item = scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)
1170
+ item = scanBackwardsQuery(elt, normalizeSelector(selector.slice(9)), !!global)
1174
1171
  } else if (selector === 'document') {
1175
1172
  item = document
1176
1173
  } else if (selector === 'window') {
@@ -1350,6 +1347,16 @@ var htmx = (function() {
1350
1347
  return [findThisElement(elt, attrName)]
1351
1348
  } else {
1352
1349
  const result = querySelectorAllExt(elt, attrTarget)
1350
+ // find `inherit` whole word in value, make sure it's surrounded by commas or is at the start/end of string
1351
+ const shouldInherit = /(^|,)(\s*)inherit(\s*)($|,)/.test(attrTarget)
1352
+ if (shouldInherit) {
1353
+ const eltToInheritFrom = asElement(getClosestMatch(elt, function(parent) {
1354
+ return parent !== elt && hasAttribute(asElement(parent), attrName)
1355
+ }))
1356
+ if (eltToInheritFrom) {
1357
+ result.push(...findAttributeTargets(eltToInheritFrom, attrName))
1358
+ }
1359
+ }
1353
1360
  if (result.length === 0) {
1354
1361
  logError('The selector "' + attrTarget + '" on ' + attrName + ' returned no matches!')
1355
1362
  return [DUMMY_ELT]
@@ -1398,13 +1405,7 @@ var htmx = (function() {
1398
1405
  * @returns {boolean}
1399
1406
  */
1400
1407
  function shouldSettleAttribute(name) {
1401
- const attributesToSettle = htmx.config.attributesToSettle
1402
- for (let i = 0; i < attributesToSettle.length; i++) {
1403
- if (name === attributesToSettle[i]) {
1404
- return true
1405
- }
1406
- }
1407
- return false
1408
+ return htmx.config.attributesToSettle.includes(name)
1408
1409
  }
1409
1410
 
1410
1411
  /**
@@ -1453,7 +1454,7 @@ var htmx = (function() {
1453
1454
  */
1454
1455
  function oobSwap(oobValue, oobElement, settleInfo, rootNode) {
1455
1456
  rootNode = rootNode || getDocument()
1456
- let selector = '#' + getRawAttribute(oobElement, 'id')
1457
+ let selector = '#' + CSS.escape(getRawAttribute(oobElement, 'id'))
1457
1458
  /** @type HtmxSwapStyle */
1458
1459
  let swapStyle = 'outerHTML'
1459
1460
  if (oobValue === 'true') {
@@ -1468,7 +1469,7 @@ var htmx = (function() {
1468
1469
  oobElement.removeAttribute('data-hx-swap-oob')
1469
1470
 
1470
1471
  const targets = querySelectorAllExt(rootNode, selector, false)
1471
- if (targets) {
1472
+ if (targets.length) {
1472
1473
  forEach(
1473
1474
  targets,
1474
1475
  function(target) {
@@ -1626,14 +1627,11 @@ var htmx = (function() {
1626
1627
  */
1627
1628
  function attributeHash(elt) {
1628
1629
  let hash = 0
1629
- // IE fix
1630
- if (elt.attributes) {
1631
- for (let i = 0; i < elt.attributes.length; i++) {
1632
- const attribute = elt.attributes[i]
1633
- if (attribute.value) { // only include attributes w/ actual values (empty is same as non-existent)
1634
- hash = stringHash(attribute.name, hash)
1635
- hash = stringHash(attribute.value, hash)
1636
- }
1630
+ for (let i = 0; i < elt.attributes.length; i++) {
1631
+ const attribute = elt.attributes[i]
1632
+ if (attribute.value) { // only include attributes w/ actual values (empty is same as non-existent)
1633
+ hash = stringHash(attribute.name, hash)
1634
+ hash = stringHash(attribute.value, hash)
1637
1635
  }
1638
1636
  }
1639
1637
  return hash
@@ -1678,21 +1676,17 @@ var htmx = (function() {
1678
1676
  function cleanUpElement(element) {
1679
1677
  triggerEvent(element, 'htmx:beforeCleanupElement')
1680
1678
  deInitNode(element)
1681
- // @ts-ignore IE11 code
1682
- // noinspection JSUnresolvedReference
1683
- if (element.children) { // IE
1684
- // @ts-ignore
1685
- forEach(element.children, function(child) { cleanUpElement(child) })
1686
- }
1679
+ // @ts-ignore
1680
+ forEach(element.children, function(child) { cleanUpElement(child) })
1687
1681
  }
1688
1682
 
1689
1683
  /**
1690
- * @param {Node} target
1684
+ * @param {Element} target
1691
1685
  * @param {ParentNode} fragment
1692
1686
  * @param {HtmxSettleInfo} settleInfo
1693
1687
  */
1694
1688
  function swapOuterHTML(target, fragment, settleInfo) {
1695
- if (target instanceof Element && target.tagName === 'BODY') { // special case the body to innerHTML because DocumentFragments can't contain a body elt unfortunately
1689
+ if (target.tagName === 'BODY') { // special case the body to innerHTML because DocumentFragments can't contain a body elt unfortunately
1696
1690
  return swapInnerHTML(target, fragment, settleInfo)
1697
1691
  }
1698
1692
  /** @type {Node} */
@@ -1718,15 +1712,11 @@ var htmx = (function() {
1718
1712
  newElt = newElt.nextSibling
1719
1713
  }
1720
1714
  cleanUpElement(target)
1721
- if (target instanceof Element) {
1722
- target.remove()
1723
- } else {
1724
- target.parentNode.removeChild(target)
1725
- }
1715
+ target.remove()
1726
1716
  }
1727
1717
 
1728
1718
  /**
1729
- * @param {Node} target
1719
+ * @param {Element} target
1730
1720
  * @param {ParentNode} fragment
1731
1721
  * @param {HtmxSettleInfo} settleInfo
1732
1722
  */
@@ -1735,7 +1725,7 @@ var htmx = (function() {
1735
1725
  }
1736
1726
 
1737
1727
  /**
1738
- * @param {Node} target
1728
+ * @param {Element} target
1739
1729
  * @param {ParentNode} fragment
1740
1730
  * @param {HtmxSettleInfo} settleInfo
1741
1731
  */
@@ -1744,7 +1734,7 @@ var htmx = (function() {
1744
1734
  }
1745
1735
 
1746
1736
  /**
1747
- * @param {Node} target
1737
+ * @param {Element} target
1748
1738
  * @param {ParentNode} fragment
1749
1739
  * @param {HtmxSettleInfo} settleInfo
1750
1740
  */
@@ -1753,7 +1743,7 @@ var htmx = (function() {
1753
1743
  }
1754
1744
 
1755
1745
  /**
1756
- * @param {Node} target
1746
+ * @param {Element} target
1757
1747
  * @param {ParentNode} fragment
1758
1748
  * @param {HtmxSettleInfo} settleInfo
1759
1749
  */
@@ -1762,7 +1752,7 @@ var htmx = (function() {
1762
1752
  }
1763
1753
 
1764
1754
  /**
1765
- * @param {Node} target
1755
+ * @param {Element} target
1766
1756
  */
1767
1757
  function swapDelete(target) {
1768
1758
  cleanUpElement(target)
@@ -1773,7 +1763,7 @@ var htmx = (function() {
1773
1763
  }
1774
1764
 
1775
1765
  /**
1776
- * @param {Node} target
1766
+ * @param {Element} target
1777
1767
  * @param {ParentNode} fragment
1778
1768
  * @param {HtmxSettleInfo} settleInfo
1779
1769
  */
@@ -1793,7 +1783,7 @@ var htmx = (function() {
1793
1783
  /**
1794
1784
  * @param {HtmxSwapStyle} swapStyle
1795
1785
  * @param {Element} elt
1796
- * @param {Node} target
1786
+ * @param {Element} target
1797
1787
  * @param {ParentNode} fragment
1798
1788
  * @param {HtmxSettleInfo} settleInfo
1799
1789
  */
@@ -1871,7 +1861,7 @@ var htmx = (function() {
1871
1861
  }
1872
1862
 
1873
1863
  /**
1874
- * Implements complete swapping pipeline, including: focus and selection preservation,
1864
+ * Implements complete swapping pipeline, including: delay, view transitions, focus and selection preservation,
1875
1865
  * title updates, scroll, OOB swapping, normal swapping and settling
1876
1866
  * @param {string|Element} target
1877
1867
  * @param {string} content
@@ -1882,14 +1872,19 @@ var htmx = (function() {
1882
1872
  if (!swapOptions) {
1883
1873
  swapOptions = {}
1884
1874
  }
1875
+ // optional transition API promise callbacks
1876
+ let settleResolve = null
1877
+ let settleReject = null
1885
1878
 
1886
- target = resolveTarget(target)
1887
- const rootNode = swapOptions.contextElement ? getRootNode(swapOptions.contextElement, false) : getDocument()
1879
+ let doSwap = function() {
1880
+ maybeCall(swapOptions.beforeSwapCallback)
1888
1881
 
1889
- // preserve focus and selection
1890
- const activeElt = document.activeElement
1891
- let selectionInfo = {}
1892
- try {
1882
+ target = resolveTarget(target)
1883
+ const rootNode = swapOptions.contextElement ? getRootNode(swapOptions.contextElement, false) : getDocument()
1884
+
1885
+ // preserve focus and selection
1886
+ const activeElt = document.activeElement
1887
+ let selectionInfo = {}
1893
1888
  selectionInfo = {
1894
1889
  elt: activeElt,
1895
1890
  // @ts-ignore
@@ -1897,123 +1892,160 @@ var htmx = (function() {
1897
1892
  // @ts-ignore
1898
1893
  end: activeElt ? activeElt.selectionEnd : null
1899
1894
  }
1900
- } catch (e) {
1901
- // safari issue - see https://github.com/microsoft/playwright/issues/5894
1902
- }
1903
- const settleInfo = makeSettleInfo(target)
1895
+ const settleInfo = makeSettleInfo(target)
1904
1896
 
1905
- // For text content swaps, don't parse the response as HTML, just insert it
1906
- if (swapSpec.swapStyle === 'textContent') {
1907
- target.textContent = content
1908
- // Otherwise, make the fragment and process it
1909
- } else {
1910
- let fragment = makeFragment(content)
1911
-
1912
- settleInfo.title = fragment.title
1913
-
1914
- // select-oob swaps
1915
- if (swapOptions.selectOOB) {
1916
- const oobSelectValues = swapOptions.selectOOB.split(',')
1917
- for (let i = 0; i < oobSelectValues.length; i++) {
1918
- const oobSelectValue = oobSelectValues[i].split(':', 2)
1919
- let id = oobSelectValue[0].trim()
1920
- if (id.indexOf('#') === 0) {
1921
- id = id.substring(1)
1897
+ // For text content swaps, don't parse the response as HTML, just insert it
1898
+ if (swapSpec.swapStyle === 'textContent') {
1899
+ target.textContent = content
1900
+ // Otherwise, make the fragment and process it
1901
+ } else {
1902
+ let fragment = makeFragment(content)
1903
+
1904
+ settleInfo.title = swapOptions.title || fragment.title
1905
+ if (swapOptions.historyRequest) {
1906
+ // @ts-ignore fragment can be a parentNode Element
1907
+ fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment
1908
+ }
1909
+
1910
+ // select-oob swaps
1911
+ if (swapOptions.selectOOB) {
1912
+ const oobSelectValues = swapOptions.selectOOB.split(',')
1913
+ for (let i = 0; i < oobSelectValues.length; i++) {
1914
+ const oobSelectValue = oobSelectValues[i].split(':', 2)
1915
+ let id = oobSelectValue[0].trim()
1916
+ if (id.indexOf('#') === 0) {
1917
+ id = id.substring(1)
1918
+ }
1919
+ const oobValue = oobSelectValue[1] || 'true'
1920
+ const oobElement = fragment.querySelector('#' + id)
1921
+ if (oobElement) {
1922
+ oobSwap(oobValue, oobElement, settleInfo, rootNode)
1923
+ }
1922
1924
  }
1923
- const oobValue = oobSelectValue[1] || 'true'
1924
- const oobElement = fragment.querySelector('#' + id)
1925
- if (oobElement) {
1926
- oobSwap(oobValue, oobElement, settleInfo, rootNode)
1925
+ }
1926
+ // oob swaps
1927
+ findAndSwapOobElements(fragment, settleInfo, rootNode)
1928
+ forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
1929
+ if (template.content && findAndSwapOobElements(template.content, settleInfo, rootNode)) {
1930
+ // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
1931
+ template.remove()
1927
1932
  }
1933
+ })
1934
+
1935
+ // normal swap
1936
+ if (swapOptions.select) {
1937
+ const newFragment = getDocument().createDocumentFragment()
1938
+ forEach(fragment.querySelectorAll(swapOptions.select), function(node) {
1939
+ newFragment.appendChild(node)
1940
+ })
1941
+ fragment = newFragment
1928
1942
  }
1943
+ handlePreservedElements(fragment)
1944
+ swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
1945
+ restorePreservedElements()
1929
1946
  }
1930
- // oob swaps
1931
- findAndSwapOobElements(fragment, settleInfo, rootNode)
1932
- forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
1933
- if (template.content && findAndSwapOobElements(template.content, settleInfo, rootNode)) {
1934
- // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
1935
- template.remove()
1947
+
1948
+ // apply saved focus and selection information to swapped content
1949
+ if (selectionInfo.elt &&
1950
+ !bodyContains(selectionInfo.elt) &&
1951
+ getRawAttribute(selectionInfo.elt, 'id')) {
1952
+ const newActiveElt = document.getElementById(getRawAttribute(selectionInfo.elt, 'id'))
1953
+ const focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll }
1954
+ if (newActiveElt) {
1955
+ // @ts-ignore
1956
+ if (selectionInfo.start && newActiveElt.setSelectionRange) {
1957
+ try {
1958
+ // @ts-ignore
1959
+ newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end)
1960
+ } catch (e) {
1961
+ // the setSelectionRange method is present on fields that don't support it, so just let this fail
1962
+ }
1963
+ }
1964
+ newActiveElt.focus(focusOptions)
1936
1965
  }
1966
+ }
1967
+
1968
+ target.classList.remove(htmx.config.swappingClass)
1969
+ forEach(settleInfo.elts, function(elt) {
1970
+ if (elt.classList) {
1971
+ elt.classList.add(htmx.config.settlingClass)
1972
+ }
1973
+ triggerEvent(elt, 'htmx:afterSwap', swapOptions.eventInfo)
1937
1974
  })
1975
+ maybeCall(swapOptions.afterSwapCallback)
1938
1976
 
1939
- // normal swap
1940
- if (swapOptions.select) {
1941
- const newFragment = getDocument().createDocumentFragment()
1942
- forEach(fragment.querySelectorAll(swapOptions.select), function(node) {
1943
- newFragment.appendChild(node)
1944
- })
1945
- fragment = newFragment
1977
+ // merge in new title after swap but before settle
1978
+ if (!swapSpec.ignoreTitle) {
1979
+ handleTitle(settleInfo.title)
1946
1980
  }
1947
- handlePreservedElements(fragment)
1948
- swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
1949
- restorePreservedElements()
1950
- }
1951
1981
 
1952
- // apply saved focus and selection information to swapped content
1953
- if (selectionInfo.elt &&
1954
- !bodyContains(selectionInfo.elt) &&
1955
- getRawAttribute(selectionInfo.elt, 'id')) {
1956
- const newActiveElt = document.getElementById(getRawAttribute(selectionInfo.elt, 'id'))
1957
- const focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll }
1958
- if (newActiveElt) {
1959
- // @ts-ignore
1960
- if (selectionInfo.start && newActiveElt.setSelectionRange) {
1961
- try {
1962
- // @ts-ignore
1963
- newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end)
1964
- } catch (e) {
1965
- // the setSelectionRange method is present on fields that don't support it, so just let this fail
1982
+ // settle
1983
+ const doSettle = function() {
1984
+ forEach(settleInfo.tasks, function(task) {
1985
+ task.call()
1986
+ })
1987
+ forEach(settleInfo.elts, function(elt) {
1988
+ if (elt.classList) {
1989
+ elt.classList.remove(htmx.config.settlingClass)
1990
+ }
1991
+ triggerEvent(elt, 'htmx:afterSettle', swapOptions.eventInfo)
1992
+ })
1993
+
1994
+ if (swapOptions.anchor) {
1995
+ const anchorTarget = asElement(resolveTarget('#' + swapOptions.anchor))
1996
+ if (anchorTarget) {
1997
+ anchorTarget.scrollIntoView({ block: 'start', behavior: 'auto' })
1966
1998
  }
1967
1999
  }
1968
- newActiveElt.focus(focusOptions)
2000
+
2001
+ updateScrollState(settleInfo.elts, swapSpec)
2002
+ maybeCall(swapOptions.afterSettleCallback)
2003
+ maybeCall(settleResolve)
1969
2004
  }
1970
- }
1971
2005
 
1972
- target.classList.remove(htmx.config.swappingClass)
1973
- forEach(settleInfo.elts, function(elt) {
1974
- if (elt.classList) {
1975
- elt.classList.add(htmx.config.settlingClass)
2006
+ if (swapSpec.settleDelay > 0) {
2007
+ getWindow().setTimeout(doSettle, swapSpec.settleDelay)
2008
+ } else {
2009
+ doSettle()
1976
2010
  }
1977
- triggerEvent(elt, 'htmx:afterSwap', swapOptions.eventInfo)
1978
- })
1979
- if (swapOptions.afterSwapCallback) {
1980
- swapOptions.afterSwapCallback()
1981
2011
  }
1982
-
1983
- // merge in new title after swap but before settle
1984
- if (!swapSpec.ignoreTitle) {
1985
- handleTitle(settleInfo.title)
2012
+ let shouldTransition = htmx.config.globalViewTransitions
2013
+ if (swapSpec.hasOwnProperty('transition')) {
2014
+ shouldTransition = swapSpec.transition
1986
2015
  }
1987
2016
 
1988
- // settle
1989
- const doSettle = function() {
1990
- forEach(settleInfo.tasks, function(task) {
1991
- task.call()
1992
- })
1993
- forEach(settleInfo.elts, function(elt) {
1994
- if (elt.classList) {
1995
- elt.classList.remove(htmx.config.settlingClass)
1996
- }
1997
- triggerEvent(elt, 'htmx:afterSettle', swapOptions.eventInfo)
1998
- })
1999
-
2000
- if (swapOptions.anchor) {
2001
- const anchorTarget = asElement(resolveTarget('#' + swapOptions.anchor))
2002
- if (anchorTarget) {
2003
- anchorTarget.scrollIntoView({ block: 'start', behavior: 'auto' })
2004
- }
2005
- }
2017
+ const elt = swapOptions.contextElement || getDocument()
2006
2018
 
2007
- updateScrollState(settleInfo.elts, swapSpec)
2008
- if (swapOptions.afterSettleCallback) {
2009
- swapOptions.afterSettleCallback()
2019
+ if (shouldTransition &&
2020
+ triggerEvent(elt, 'htmx:beforeTransition', swapOptions.eventInfo) &&
2021
+ typeof Promise !== 'undefined' &&
2022
+ // @ts-ignore experimental feature atm
2023
+ document.startViewTransition) {
2024
+ const settlePromise = new Promise(function(_resolve, _reject) {
2025
+ settleResolve = _resolve
2026
+ settleReject = _reject
2027
+ })
2028
+ // wrap the original doSwap() in a call to startViewTransition()
2029
+ const innerDoSwap = doSwap
2030
+ doSwap = function() {
2031
+ // @ts-ignore experimental feature atm
2032
+ document.startViewTransition(function() {
2033
+ innerDoSwap()
2034
+ return settlePromise
2035
+ })
2010
2036
  }
2011
2037
  }
2012
2038
 
2013
- if (swapSpec.settleDelay > 0) {
2014
- getWindow().setTimeout(doSettle, swapSpec.settleDelay)
2015
- } else {
2016
- doSettle()
2039
+ try {
2040
+ if (swapSpec?.swapDelay && swapSpec.swapDelay > 0) {
2041
+ getWindow().setTimeout(doSwap, swapSpec.swapDelay)
2042
+ } else {
2043
+ doSwap()
2044
+ }
2045
+ } catch (e) {
2046
+ triggerErrorEvent(elt, 'htmx:swapError', swapOptions.eventInfo)
2047
+ maybeCall(settleReject)
2048
+ throw e
2017
2049
  }
2018
2050
  }
2019
2051
 
@@ -2367,7 +2399,7 @@ var htmx = (function() {
2367
2399
  if (path == null || path === '') {
2368
2400
  // if there is no action attribute on the form set path to current href before the
2369
2401
  // following logic to properly clear parameters on a GET (not on a POST!)
2370
- path = getDocument().location.href
2402
+ path = location.href
2371
2403
  }
2372
2404
  if (verb === 'get' && path.includes('?')) {
2373
2405
  path = path.replace(/\?[^#]+/, '')
@@ -2388,23 +2420,24 @@ var htmx = (function() {
2388
2420
 
2389
2421
  /**
2390
2422
  * @param {Event} evt
2391
- * @param {Node} node
2423
+ * @param {Element} elt
2392
2424
  * @returns {boolean}
2393
2425
  */
2394
- function shouldCancel(evt, node) {
2395
- const elt = asElement(node)
2396
- if (!elt) {
2397
- return false
2398
- }
2426
+ function shouldCancel(evt, elt) {
2399
2427
  if (evt.type === 'submit' || evt.type === 'click') {
2428
+ // use elt from event that was submitted/clicked where possible to determining if default form/link behavior should be canceled
2429
+ elt = asElement(evt.target) || elt
2400
2430
  if (elt.tagName === 'FORM') {
2401
2431
  return true
2402
2432
  }
2403
- if (matches(elt, 'input[type="submit"], button') &&
2404
- (matches(elt, '[form]') || closest(elt, 'form') !== null)) {
2433
+ // @ts-ignore Do not cancel on buttons that 1) don't have a related form or 2) have a type attribute of 'reset'/'button'.
2434
+ // The properties will resolve to undefined for elements that don't define 'type' or 'form', which is fine
2435
+ if (elt.form && elt.type === 'submit') {
2405
2436
  return true
2406
2437
  }
2407
- if (elt instanceof HTMLAnchorElement && elt.href &&
2438
+ elt = elt.closest('a')
2439
+ // @ts-ignore check for a link wrapping the event elt or if elt is a link. elt will be link so href check is fine
2440
+ if (elt && elt.href &&
2408
2441
  (elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf('#') !== 0)) {
2409
2442
  return true
2410
2443
  }
@@ -2444,7 +2477,7 @@ var htmx = (function() {
2444
2477
  }
2445
2478
 
2446
2479
  /**
2447
- * @param {Node} elt
2480
+ * @param {Element} elt
2448
2481
  * @param {TriggerHandler} handler
2449
2482
  * @param {HtmxNodeInternalData} nodeData
2450
2483
  * @param {HtmxTriggerSpecification} triggerSpec
@@ -2511,7 +2544,7 @@ var htmx = (function() {
2511
2544
  }
2512
2545
  }
2513
2546
  if (triggerSpec.changed) {
2514
- const node = event.target
2547
+ const node = evt.target
2515
2548
  // @ts-ignore value will be undefined for non-input elements, which is fine
2516
2549
  const value = node.value
2517
2550
  const lastValue = elementData.lastValue.get(triggerSpec)
@@ -2634,7 +2667,7 @@ var htmx = (function() {
2634
2667
  triggerSpecs.forEach(function(triggerSpec) {
2635
2668
  addTriggerHandler(elt, triggerSpec, nodeData, function(node, evt) {
2636
2669
  const elt = asElement(node)
2637
- if (closest(elt, htmx.config.disableSelector)) {
2670
+ if (eltIsDisabled(elt)) {
2638
2671
  cleanUpElement(elt)
2639
2672
  return
2640
2673
  }
@@ -2648,12 +2681,12 @@ var htmx = (function() {
2648
2681
 
2649
2682
  /**
2650
2683
  * @callback TriggerHandler
2651
- * @param {Node} elt
2684
+ * @param {Element} elt
2652
2685
  * @param {Event} [evt]
2653
2686
  */
2654
2687
 
2655
2688
  /**
2656
- * @param {Node} elt
2689
+ * @param {Element} elt
2657
2690
  * @param {HtmxTriggerSpecification} triggerSpec
2658
2691
  * @param {HtmxNodeInternalData} nodeData
2659
2692
  * @param {TriggerHandler} handler
@@ -2778,7 +2811,7 @@ var htmx = (function() {
2778
2811
  * @param {Event} evt
2779
2812
  */
2780
2813
  function maybeSetLastButtonClicked(evt) {
2781
- const elt = /** @type {HTMLButtonElement|HTMLInputElement} */ (closest(asElement(evt.target), "button, input[type='submit']"))
2814
+ const elt = getTargetButton(evt.target)
2782
2815
  const internalData = getRelatedFormData(evt)
2783
2816
  if (internalData) {
2784
2817
  internalData.lastButtonClicked = elt
@@ -2795,19 +2828,33 @@ var htmx = (function() {
2795
2828
  }
2796
2829
  }
2797
2830
 
2831
+ /**
2832
+ * @param {EventTarget} target
2833
+ * @returns {HTMLButtonElement|HTMLInputElement|null}
2834
+ */
2835
+ function getTargetButton(target) {
2836
+ return /** @type {HTMLButtonElement|HTMLInputElement|null} */ (closest(asElement(target), "button, input[type='submit']"))
2837
+ }
2838
+
2839
+ /**
2840
+ * @param {Element} elt
2841
+ * @returns {HTMLFormElement|null}
2842
+ */
2843
+ function getRelatedForm(elt) {
2844
+ // @ts-ignore Get the related form if available, else find the closest parent form
2845
+ return elt.form || closest(elt, 'form')
2846
+ }
2847
+
2798
2848
  /**
2799
2849
  * @param {Event} evt
2800
2850
  * @returns {HtmxNodeInternalData|undefined}
2801
2851
  */
2802
2852
  function getRelatedFormData(evt) {
2803
- const elt = closest(asElement(evt.target), "button, input[type='submit']")
2853
+ const elt = getTargetButton(evt.target)
2804
2854
  if (!elt) {
2805
2855
  return
2806
2856
  }
2807
- const form = resolveTarget('#' + getRawAttribute(elt, 'form'), elt.getRootNode()) || closest(elt, 'form')
2808
- if (!form) {
2809
- return
2810
- }
2857
+ const form = getRelatedForm(elt)
2811
2858
  return getInternalData(form)
2812
2859
  }
2813
2860
 
@@ -2884,44 +2931,52 @@ var htmx = (function() {
2884
2931
  * @param {Element|HTMLInputElement} elt
2885
2932
  */
2886
2933
  function initNode(elt) {
2887
- if (closest(elt, htmx.config.disableSelector)) {
2888
- cleanUpElement(elt)
2889
- return
2890
- }
2891
- const nodeData = getInternalData(elt)
2892
- const attrHash = attributeHash(elt)
2893
- if (nodeData.initHash !== attrHash) {
2894
- // clean up any previously processed info
2895
- deInitNode(elt)
2934
+ triggerEvent(elt, 'htmx:beforeProcessNode')
2896
2935
 
2897
- nodeData.initHash = attrHash
2898
-
2899
- triggerEvent(elt, 'htmx:beforeProcessNode')
2900
-
2901
- const triggerSpecs = getTriggerSpecs(elt)
2902
- const hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs)
2936
+ const nodeData = getInternalData(elt)
2937
+ const triggerSpecs = getTriggerSpecs(elt)
2938
+ const hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs)
2903
2939
 
2904
- if (!hasExplicitHttpAction) {
2905
- if (getClosestAttributeValue(elt, 'hx-boost') === 'true') {
2906
- boostElement(elt, nodeData, triggerSpecs)
2907
- } else if (hasAttribute(elt, 'hx-trigger')) {
2908
- triggerSpecs.forEach(function(triggerSpec) {
2909
- // For "naked" triggers, don't do anything at all
2910
- addTriggerHandler(elt, triggerSpec, nodeData, function() {
2911
- })
2940
+ if (!hasExplicitHttpAction) {
2941
+ if (getClosestAttributeValue(elt, 'hx-boost') === 'true') {
2942
+ boostElement(elt, nodeData, triggerSpecs)
2943
+ } else if (hasAttribute(elt, 'hx-trigger')) {
2944
+ triggerSpecs.forEach(function(triggerSpec) {
2945
+ // For "naked" triggers, don't do anything at all
2946
+ addTriggerHandler(elt, triggerSpec, nodeData, function() {
2912
2947
  })
2913
- }
2948
+ })
2914
2949
  }
2950
+ }
2915
2951
 
2916
- // Handle submit buttons/inputs that have the form attribute set
2917
- // see https://developer.mozilla.org/docs/Web/HTML/Element/button
2918
- if (elt.tagName === 'FORM' || (getRawAttribute(elt, 'type') === 'submit' && hasAttribute(elt, 'form'))) {
2919
- initButtonTracking(elt)
2920
- }
2952
+ // Handle submit buttons/inputs that have the form attribute set
2953
+ // see https://developer.mozilla.org/docs/Web/HTML/Element/button
2954
+ if (elt.tagName === 'FORM' || (getRawAttribute(elt, 'type') === 'submit' && hasAttribute(elt, 'form'))) {
2955
+ initButtonTracking(elt)
2956
+ }
2957
+
2958
+ nodeData.firstInitCompleted = true
2959
+ triggerEvent(elt, 'htmx:afterProcessNode')
2960
+ }
2921
2961
 
2922
- nodeData.firstInitCompleted = true
2923
- triggerEvent(elt, 'htmx:afterProcessNode')
2962
+ /**
2963
+ * @param {Element} elt
2964
+ * @returns {boolean}
2965
+ */
2966
+ function maybeDeInitAndHash(elt) {
2967
+ // Ensure only valid Elements and not shadow DOM roots are inited
2968
+ if (!(elt instanceof Element)) {
2969
+ return false
2970
+ }
2971
+
2972
+ const nodeData = getInternalData(elt)
2973
+ const hash = attributeHash(elt)
2974
+ if (nodeData.initHash !== hash) {
2975
+ deInitNode(elt)
2976
+ nodeData.initHash = hash
2977
+ return true
2924
2978
  }
2979
+ return false
2925
2980
  }
2926
2981
 
2927
2982
  /**
@@ -2933,13 +2988,27 @@ var htmx = (function() {
2933
2988
  */
2934
2989
  function processNode(elt) {
2935
2990
  elt = resolveTarget(elt)
2936
- if (closest(elt, htmx.config.disableSelector)) {
2991
+ if (eltIsDisabled(elt)) {
2937
2992
  cleanUpElement(elt)
2938
2993
  return
2939
2994
  }
2940
- initNode(elt)
2941
- forEach(findElementsToProcess(elt), function(child) { initNode(child) })
2995
+
2996
+ const elementsToInit = []
2997
+ if (maybeDeInitAndHash(elt)) {
2998
+ elementsToInit.push(elt)
2999
+ }
3000
+ forEach(findElementsToProcess(elt), function(child) {
3001
+ if (eltIsDisabled(child)) {
3002
+ cleanUpElement(child)
3003
+ return
3004
+ }
3005
+ if (maybeDeInitAndHash(child)) {
3006
+ elementsToInit.push(child)
3007
+ }
3008
+ })
3009
+
2942
3010
  forEach(findHxOnWildcardElements(elt), processHxOnWildcard)
3011
+ forEach(elementsToInit, initNode)
2943
3012
  }
2944
3013
 
2945
3014
  //= ===================================================================
@@ -2960,16 +3029,9 @@ var htmx = (function() {
2960
3029
  * @returns {CustomEvent}
2961
3030
  */
2962
3031
  function makeEvent(eventName, detail) {
2963
- let evt
2964
- if (window.CustomEvent && typeof window.CustomEvent === 'function') {
2965
- // TODO: `composed: true` here is a hack to make global event handlers work with events in shadow DOM
2966
- // This breaks expected encapsulation but needs to be here until decided otherwise by core devs
2967
- evt = new CustomEvent(eventName, { bubbles: true, cancelable: true, composed: true, detail })
2968
- } else {
2969
- evt = getDocument().createEvent('CustomEvent')
2970
- evt.initCustomEvent(eventName, true, true, detail)
2971
- }
2972
- return evt
3032
+ // TODO: `composed: true` here is a hack to make global event handlers work with events in shadow DOM
3033
+ // This breaks expected encapsulation but needs to be here until decided otherwise by core devs
3034
+ return new CustomEvent(eventName, { bubbles: true, cancelable: true, composed: true, detail })
2973
3035
  }
2974
3036
 
2975
3037
  /**
@@ -2991,15 +3053,17 @@ var htmx = (function() {
2991
3053
 
2992
3054
  /**
2993
3055
  * `withExtensions` locates all active extensions for a provided element, then
2994
- * executes the provided function using each of the active extensions. It should
3056
+ * executes the provided function using each of the active extensions. You can filter
3057
+ * the element's extensions by giving it a list of extensions to ignore. It should
2995
3058
  * be called internally at every extendable execution point in htmx.
2996
3059
  *
2997
3060
  * @param {Element} elt
2998
3061
  * @param {(extension:HtmxExtension) => void} toDo
3062
+ * @param {string[]=} extensionsToIgnore
2999
3063
  * @returns void
3000
3064
  */
3001
- function withExtensions(elt, toDo) {
3002
- forEach(getExtensions(elt), function(extension) {
3065
+ function withExtensions(elt, toDo, extensionsToIgnore) {
3066
+ forEach(getExtensions(elt, [], extensionsToIgnore), function(extension) {
3003
3067
  try {
3004
3068
  toDo(extension)
3005
3069
  } catch (e) {
@@ -3009,11 +3073,7 @@ var htmx = (function() {
3009
3073
  }
3010
3074
 
3011
3075
  function logError(msg) {
3012
- if (console.error) {
3013
- console.error(msg)
3014
- } else if (console.log) {
3015
- console.log('ERROR: ', msg)
3016
- }
3076
+ console.error(msg)
3017
3077
  }
3018
3078
 
3019
3079
  /**
@@ -3057,6 +3117,16 @@ var htmx = (function() {
3057
3117
  //= ===================================================================
3058
3118
  let currentPathForHistory = location.pathname + location.search
3059
3119
 
3120
+ /**
3121
+ * @param {string} path
3122
+ */
3123
+ function setCurrentPathForHistory(path) {
3124
+ currentPathForHistory = path
3125
+ if (canAccessLocalStorage()) {
3126
+ sessionStorage.setItem('htmx-current-path-for-history', path)
3127
+ }
3128
+ }
3129
+
3060
3130
  /**
3061
3131
  * @returns {Element}
3062
3132
  */
@@ -3081,13 +3151,13 @@ var htmx = (function() {
3081
3151
 
3082
3152
  if (htmx.config.historyCacheSize <= 0) {
3083
3153
  // make sure that an eventually already existing cache is purged
3084
- localStorage.removeItem('htmx-history-cache')
3154
+ sessionStorage.removeItem('htmx-history-cache')
3085
3155
  return
3086
3156
  }
3087
3157
 
3088
3158
  url = normalizePath(url)
3089
3159
 
3090
- const historyCache = parseJSON(localStorage.getItem('htmx-history-cache')) || []
3160
+ const historyCache = parseJSON(sessionStorage.getItem('htmx-history-cache')) || []
3091
3161
  for (let i = 0; i < historyCache.length; i++) {
3092
3162
  if (historyCache[i].url === url) {
3093
3163
  historyCache.splice(i, 1)
@@ -3108,7 +3178,7 @@ var htmx = (function() {
3108
3178
  // keep trying to save the cache until it succeeds or is empty
3109
3179
  while (historyCache.length > 0) {
3110
3180
  try {
3111
- localStorage.setItem('htmx-history-cache', JSON.stringify(historyCache))
3181
+ sessionStorage.setItem('htmx-history-cache', JSON.stringify(historyCache))
3112
3182
  break
3113
3183
  } catch (e) {
3114
3184
  triggerErrorEvent(getDocument().body, 'htmx:historyCacheError', { cause: e, cache: historyCache })
@@ -3136,7 +3206,7 @@ var htmx = (function() {
3136
3206
 
3137
3207
  url = normalizePath(url)
3138
3208
 
3139
- const historyCache = parseJSON(localStorage.getItem('htmx-history-cache')) || []
3209
+ const historyCache = parseJSON(sessionStorage.getItem('htmx-history-cache')) || []
3140
3210
  for (let i = 0; i < historyCache.length; i++) {
3141
3211
  if (historyCache[i].url === url) {
3142
3212
  return historyCache[i]
@@ -3164,26 +3234,24 @@ var htmx = (function() {
3164
3234
 
3165
3235
  function saveCurrentPageToHistory() {
3166
3236
  const elt = getHistoryElement()
3167
- const path = currentPathForHistory || location.pathname + location.search
3237
+ let path = currentPathForHistory
3238
+ if (canAccessLocalStorage()) {
3239
+ path = sessionStorage.getItem('htmx-current-path-for-history')
3240
+ }
3241
+ path = path || location.pathname + location.search
3168
3242
 
3169
3243
  // Allow history snapshot feature to be disabled where hx-history="false"
3170
3244
  // is present *anywhere* in the current document we're about to save,
3171
3245
  // so we can prevent privileged data entering the cache.
3172
3246
  // The page will still be reachable as a history entry, but htmx will fetch it
3173
- // live from the server onpopstate rather than look in the localStorage cache
3174
- let disableHistoryCache
3175
- try {
3176
- disableHistoryCache = getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')
3177
- } catch (e) {
3178
- // IE11: insensitive modifier not supported so fallback to case sensitive selector
3179
- disableHistoryCache = getDocument().querySelector('[hx-history="false"],[data-hx-history="false"]')
3180
- }
3247
+ // live from the server onpopstate rather than look in the sessionStorage cache
3248
+ const disableHistoryCache = getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')
3181
3249
  if (!disableHistoryCache) {
3182
3250
  triggerEvent(getDocument().body, 'htmx:beforeHistorySave', { path, historyElt: elt })
3183
3251
  saveToHistoryCache(path, elt)
3184
3252
  }
3185
3253
 
3186
- if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, getDocument().title, window.location.href)
3254
+ if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, getDocument().title, location.href)
3187
3255
  }
3188
3256
 
3189
3257
  /**
@@ -3200,7 +3268,7 @@ var htmx = (function() {
3200
3268
  if (htmx.config.historyEnabled) {
3201
3269
  history.pushState({ htmx: true }, '', path)
3202
3270
  }
3203
- currentPathForHistory = path
3271
+ setCurrentPathForHistory(path)
3204
3272
  }
3205
3273
 
3206
3274
  /**
@@ -3208,7 +3276,7 @@ var htmx = (function() {
3208
3276
  */
3209
3277
  function replaceUrlInHistory(path) {
3210
3278
  if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, '', path)
3211
- currentPathForHistory = path
3279
+ setCurrentPathForHistory(path)
3212
3280
  }
3213
3281
 
3214
3282
  /**
@@ -3225,33 +3293,31 @@ var htmx = (function() {
3225
3293
  */
3226
3294
  function loadHistoryFromServer(path) {
3227
3295
  const request = new XMLHttpRequest()
3228
- const details = { path, xhr: request }
3229
- triggerEvent(getDocument().body, 'htmx:historyCacheMiss', details)
3296
+ const swapSpec = { swapStyle: 'innerHTML', swapDelay: 0, settleDelay: 0 }
3297
+ const details = { path, xhr: request, historyElt: getHistoryElement(), swapSpec }
3230
3298
  request.open('GET', path, true)
3231
- request.setRequestHeader('HX-Request', 'true')
3299
+ if (htmx.config.historyRestoreAsHxRequest) {
3300
+ request.setRequestHeader('HX-Request', 'true')
3301
+ }
3232
3302
  request.setRequestHeader('HX-History-Restore-Request', 'true')
3233
- request.setRequestHeader('HX-Current-URL', getDocument().location.href)
3303
+ request.setRequestHeader('HX-Current-URL', location.href)
3234
3304
  request.onload = function() {
3235
3305
  if (this.status >= 200 && this.status < 400) {
3306
+ details.response = this.response
3236
3307
  triggerEvent(getDocument().body, 'htmx:historyCacheMissLoad', details)
3237
- const fragment = makeFragment(this.response)
3238
- /** @type ParentNode */
3239
- const content = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment
3240
- const historyElement = getHistoryElement()
3241
- const settleInfo = makeSettleInfo(historyElement)
3242
- handleTitle(fragment.title)
3243
-
3244
- handlePreservedElements(fragment)
3245
- swapInnerHTML(historyElement, content, settleInfo)
3246
- restorePreservedElements()
3247
- settleImmediately(settleInfo.tasks)
3248
- currentPathForHistory = path
3249
- triggerEvent(getDocument().body, 'htmx:historyRestore', { path, cacheMiss: true, serverResponse: this.response })
3308
+ swap(details.historyElt, details.response, swapSpec, {
3309
+ contextElement: details.historyElt,
3310
+ historyRequest: true
3311
+ })
3312
+ setCurrentPathForHistory(details.path)
3313
+ triggerEvent(getDocument().body, 'htmx:historyRestore', { path, cacheMiss: true, serverResponse: details.response })
3250
3314
  } else {
3251
3315
  triggerErrorEvent(getDocument().body, 'htmx:historyCacheMissLoadError', details)
3252
3316
  }
3253
3317
  }
3254
- request.send()
3318
+ if (triggerEvent(getDocument().body, 'htmx:historyCacheMiss', details)) {
3319
+ request.send() // only send request if event not prevented
3320
+ }
3255
3321
  }
3256
3322
 
3257
3323
  /**
@@ -3262,24 +3328,21 @@ var htmx = (function() {
3262
3328
  path = path || location.pathname + location.search
3263
3329
  const cached = getCachedHistory(path)
3264
3330
  if (cached) {
3265
- const fragment = makeFragment(cached.content)
3266
- const historyElement = getHistoryElement()
3267
- const settleInfo = makeSettleInfo(historyElement)
3268
- handleTitle(cached.title)
3269
- handlePreservedElements(fragment)
3270
- swapInnerHTML(historyElement, fragment, settleInfo)
3271
- restorePreservedElements()
3272
- settleImmediately(settleInfo.tasks)
3273
- getWindow().setTimeout(function() {
3274
- window.scrollTo(0, cached.scroll)
3275
- }, 0) // next 'tick', so browser has time to render layout
3276
- currentPathForHistory = path
3277
- triggerEvent(getDocument().body, 'htmx:historyRestore', { path, item: cached })
3331
+ const swapSpec = { swapStyle: 'innerHTML', swapDelay: 0, settleDelay: 0, scroll: cached.scroll }
3332
+ const details = { path, item: cached, historyElt: getHistoryElement(), swapSpec }
3333
+ if (triggerEvent(getDocument().body, 'htmx:historyCacheHit', details)) {
3334
+ swap(details.historyElt, cached.content, swapSpec, {
3335
+ contextElement: details.historyElt,
3336
+ title: cached.title
3337
+ })
3338
+ setCurrentPathForHistory(details.path)
3339
+ triggerEvent(getDocument().body, 'htmx:historyRestore', details)
3340
+ }
3278
3341
  } else {
3279
3342
  if (htmx.config.refreshOnHistoryMiss) {
3280
3343
  // @ts-ignore: optional parameter in reload() function throws error
3281
3344
  // noinspection JSUnresolvedReference
3282
- window.location.reload(true)
3345
+ htmx.location.reload(true)
3283
3346
  } else {
3284
3347
  loadHistoryFromServer(path)
3285
3348
  }
@@ -3384,7 +3447,8 @@ var htmx = (function() {
3384
3447
  return true
3385
3448
  }
3386
3449
 
3387
- /** @param {string} name
3450
+ /**
3451
+ * @param {string} name
3388
3452
  * @param {string|Array|FormDataEntryValue} value
3389
3453
  * @param {FormData} formData */
3390
3454
  function addValueToFormData(name, value, formData) {
@@ -3397,7 +3461,8 @@ var htmx = (function() {
3397
3461
  }
3398
3462
  }
3399
3463
 
3400
- /** @param {string} name
3464
+ /**
3465
+ * @param {string} name
3401
3466
  * @param {string|Array} value
3402
3467
  * @param {FormData} formData */
3403
3468
  function removeValueFromFormData(name, value, formData) {
@@ -3413,6 +3478,22 @@ var htmx = (function() {
3413
3478
  }
3414
3479
  }
3415
3480
 
3481
+ /**
3482
+ * @param {Element} elt
3483
+ * @returns {string|Array}
3484
+ */
3485
+ function getValueFromInput(elt) {
3486
+ if (elt instanceof HTMLSelectElement && elt.multiple) {
3487
+ return toArray(elt.querySelectorAll('option:checked')).map(function(e) { return (/** @type HTMLOptionElement */(e)).value })
3488
+ }
3489
+ // include file inputs
3490
+ if (elt instanceof HTMLInputElement && elt.files) {
3491
+ return toArray(elt.files)
3492
+ }
3493
+ // @ts-ignore value will be undefined for non-input elements, which is fine
3494
+ return elt.value
3495
+ }
3496
+
3416
3497
  /**
3417
3498
  * @param {Element[]} processed
3418
3499
  * @param {FormData} formData
@@ -3428,16 +3509,7 @@ var htmx = (function() {
3428
3509
  }
3429
3510
  if (shouldInclude(elt)) {
3430
3511
  const name = getRawAttribute(elt, 'name')
3431
- // @ts-ignore value will be undefined for non-input elements, which is fine
3432
- let value = elt.value
3433
- if (elt instanceof HTMLSelectElement && elt.multiple) {
3434
- value = toArray(elt.querySelectorAll('option:checked')).map(function(e) { return (/** @type HTMLOptionElement */(e)).value })
3435
- }
3436
- // include file inputs
3437
- if (elt instanceof HTMLInputElement && elt.files) {
3438
- value = toArray(elt.files)
3439
- }
3440
- addValueToFormData(name, value, formData)
3512
+ addValueToFormData(name, getValueFromInput(elt), formData)
3441
3513
  if (validate) {
3442
3514
  validateElement(elt, errors)
3443
3515
  }
@@ -3448,7 +3520,7 @@ var htmx = (function() {
3448
3520
  // The input has already been processed and added to the values, but the FormData that will be
3449
3521
  // constructed right after on the form, will include it once again. So remove that input's value
3450
3522
  // now to avoid duplicates
3451
- removeValueFromFormData(input.name, input.value, formData)
3523
+ removeValueFromFormData(input.name, getValueFromInput(input), formData)
3452
3524
  } else {
3453
3525
  processed.push(input)
3454
3526
  }
@@ -3466,7 +3538,6 @@ var htmx = (function() {
3466
3538
  }
3467
3539
 
3468
3540
  /**
3469
- *
3470
3541
  * @param {Element} elt
3471
3542
  * @param {HtmxElementValidationError[]} errors
3472
3543
  */
@@ -3521,9 +3592,9 @@ var htmx = (function() {
3521
3592
  validate = validate && internalData.lastButtonClicked.formNoValidate !== true
3522
3593
  }
3523
3594
 
3524
- // for a non-GET include the closest form
3595
+ // for a non-GET include the related form, which may or may not be a parent element of elt
3525
3596
  if (verb !== 'get') {
3526
- processInputValue(processed, priorityFormData, errors, closest(elt, 'form'), validate)
3597
+ processInputValue(processed, priorityFormData, errors, getRelatedForm(elt), validate)
3527
3598
  }
3528
3599
 
3529
3600
  // include the element itself
@@ -3603,7 +3674,7 @@ var htmx = (function() {
3603
3674
  'HX-Trigger': getRawAttribute(elt, 'id'),
3604
3675
  'HX-Trigger-Name': getRawAttribute(elt, 'name'),
3605
3676
  'HX-Target': getAttributeValue(target, 'id'),
3606
- 'HX-Current-URL': getDocument().location.href
3677
+ 'HX-Current-URL': location.href
3607
3678
  }
3608
3679
  getValuesForElement(elt, 'hx-headers', false, headers)
3609
3680
  if (prompt !== undefined) {
@@ -3781,6 +3852,11 @@ var htmx = (function() {
3781
3852
  target = target || last
3782
3853
  target.scrollTop = target.scrollHeight
3783
3854
  }
3855
+ if (typeof swapSpec.scroll === 'number') {
3856
+ getWindow().setTimeout(function() {
3857
+ window.scrollTo(0, /** @type number */ (swapSpec.scroll))
3858
+ }, 0) // next 'tick', so browser has time to render layout
3859
+ }
3784
3860
  }
3785
3861
  if (swapSpec.show) {
3786
3862
  var target = null
@@ -3809,9 +3885,10 @@ var htmx = (function() {
3809
3885
  * @param {string} attr
3810
3886
  * @param {boolean=} evalAsDefault
3811
3887
  * @param {Object=} values
3888
+ * @param {Event=} event
3812
3889
  * @returns {Object}
3813
3890
  */
3814
- function getValuesForElement(elt, attr, evalAsDefault, values) {
3891
+ function getValuesForElement(elt, attr, evalAsDefault, values, event) {
3815
3892
  if (values == null) {
3816
3893
  values = {}
3817
3894
  }
@@ -3837,7 +3914,13 @@ var htmx = (function() {
3837
3914
  }
3838
3915
  let varsValues
3839
3916
  if (evaluateValue) {
3840
- varsValues = maybeEval(elt, function() { return Function('return (' + str + ')')() }, {})
3917
+ varsValues = maybeEval(elt, function() {
3918
+ if (event) {
3919
+ return Function('event', 'return (' + str + ')').call(elt, event)
3920
+ } else { // allow window.event to be accessible
3921
+ return Function('return (' + str + ')').call(elt)
3922
+ }
3923
+ }, {})
3841
3924
  } else {
3842
3925
  varsValues = parseJSON(str)
3843
3926
  }
@@ -3849,7 +3932,7 @@ var htmx = (function() {
3849
3932
  }
3850
3933
  }
3851
3934
  }
3852
- return getValuesForElement(asElement(parentElt(elt)), attr, evalAsDefault, values)
3935
+ return getValuesForElement(asElement(parentElt(elt)), attr, evalAsDefault, values, event)
3853
3936
  }
3854
3937
 
3855
3938
  /**
@@ -3869,28 +3952,31 @@ var htmx = (function() {
3869
3952
 
3870
3953
  /**
3871
3954
  * @param {Element} elt
3872
- * @param {*?} expressionVars
3955
+ * @param {Event=} event
3956
+ * @param {*?=} expressionVars
3873
3957
  * @returns
3874
3958
  */
3875
- function getHXVarsForElement(elt, expressionVars) {
3876
- return getValuesForElement(elt, 'hx-vars', true, expressionVars)
3959
+ function getHXVarsForElement(elt, event, expressionVars) {
3960
+ return getValuesForElement(elt, 'hx-vars', true, expressionVars, event)
3877
3961
  }
3878
3962
 
3879
3963
  /**
3880
3964
  * @param {Element} elt
3881
- * @param {*?} expressionVars
3965
+ * @param {Event=} event
3966
+ * @param {*?=} expressionVars
3882
3967
  * @returns
3883
3968
  */
3884
- function getHXValsForElement(elt, expressionVars) {
3885
- return getValuesForElement(elt, 'hx-vals', false, expressionVars)
3969
+ function getHXValsForElement(elt, event, expressionVars) {
3970
+ return getValuesForElement(elt, 'hx-vals', false, expressionVars, event)
3886
3971
  }
3887
3972
 
3888
3973
  /**
3889
3974
  * @param {Element} elt
3975
+ * @param {Event=} event
3890
3976
  * @returns {FormData}
3891
3977
  */
3892
- function getExpressionVars(elt) {
3893
- return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt))
3978
+ function getExpressionVars(elt, event) {
3979
+ return mergeObjects(getHXVarsForElement(elt, event), getHXValsForElement(elt, event))
3894
3980
  }
3895
3981
 
3896
3982
  /**
@@ -3915,8 +4001,7 @@ var htmx = (function() {
3915
4001
  * @return {string}
3916
4002
  */
3917
4003
  function getPathFromResponse(xhr) {
3918
- // NB: IE11 does not support this stuff
3919
- if (xhr.responseURL && typeof (URL) !== 'undefined') {
4004
+ if (xhr.responseURL) {
3920
4005
  try {
3921
4006
  const url = new URL(xhr.responseURL)
3922
4007
  return url.pathname + url.search
@@ -3998,17 +4083,9 @@ var htmx = (function() {
3998
4083
  * @return {boolean}
3999
4084
  */
4000
4085
  function verifyPath(elt, path, requestConfig) {
4001
- let sameHost
4002
- let url
4003
- if (typeof URL === 'function') {
4004
- url = new URL(path, document.location.href)
4005
- const origin = document.location.origin
4006
- sameHost = origin === url.origin
4007
- } else {
4008
- // IE11 doesn't support URL
4009
- url = path
4010
- sameHost = startsWith(path, document.location.origin)
4011
- }
4086
+ const url = new URL(path, location.protocol !== 'about:' ? location.href : window.origin)
4087
+ const origin = location.protocol !== 'about:' ? location.origin : window.origin
4088
+ const sameHost = origin === url.origin
4012
4089
 
4013
4090
  if (htmx.config.selfRequestsOnly) {
4014
4091
  if (!sameHost) {
@@ -4109,8 +4186,6 @@ var htmx = (function() {
4109
4186
  return function() {
4110
4187
  return formData[name].apply(formData, arguments)
4111
4188
  }
4112
- } else {
4113
- return target[name]
4114
4189
  }
4115
4190
  }
4116
4191
  const array = formData.getAll(name)
@@ -4185,7 +4260,7 @@ var htmx = (function() {
4185
4260
  }
4186
4261
  const target = etc.targetOverride || asElement(getTarget(elt))
4187
4262
  if (target == null || target == DUMMY_ELT) {
4188
- triggerErrorEvent(elt, 'htmx:targetError', { target: getAttributeValue(elt, 'hx-target') })
4263
+ triggerErrorEvent(elt, 'htmx:targetError', { target: getClosestAttributeValue(elt, 'hx-target') })
4189
4264
  maybeCall(reject)
4190
4265
  return promise
4191
4266
  }
@@ -4201,9 +4276,11 @@ var htmx = (function() {
4201
4276
 
4202
4277
  const buttonVerb = getRawAttribute(submitter, 'formmethod')
4203
4278
  if (buttonVerb != null) {
4204
- // ignore buttons with formmethod="dialog"
4205
- if (buttonVerb.toLowerCase() !== 'dialog') {
4279
+ if (VERBS.includes(buttonVerb.toLowerCase())) {
4206
4280
  verb = (/** @type HttpVerb */(buttonVerb))
4281
+ } else {
4282
+ maybeCall(resolve)
4283
+ return promise
4207
4284
  }
4208
4285
  }
4209
4286
  }
@@ -4338,7 +4415,7 @@ var htmx = (function() {
4338
4415
  if (etc.values) {
4339
4416
  overrideFormData(rawFormData, formDataFromObject(etc.values))
4340
4417
  }
4341
- const expressionVars = formDataFromObject(getExpressionVars(elt))
4418
+ const expressionVars = formDataFromObject(getExpressionVars(elt, event))
4342
4419
  const allFormData = overrideFormData(rawFormData, expressionVars)
4343
4420
  let filteredFormData = filterValues(allFormData, elt)
4344
4421
 
@@ -4348,7 +4425,7 @@ var htmx = (function() {
4348
4425
 
4349
4426
  // behavior of anchors w/ empty href is to use the current URL
4350
4427
  if (path == null || path === '') {
4351
- path = getDocument().location.href
4428
+ path = location.href
4352
4429
  }
4353
4430
 
4354
4431
  /**
@@ -4372,6 +4449,7 @@ var htmx = (function() {
4372
4449
  unfilteredFormData: allFormData,
4373
4450
  unfilteredParameters: formDataProxy(allFormData),
4374
4451
  headers,
4452
+ elt,
4375
4453
  target,
4376
4454
  verb,
4377
4455
  errors,
@@ -4426,6 +4504,7 @@ var htmx = (function() {
4426
4504
  if (!verifyPath(elt, finalPath, requestConfig)) {
4427
4505
  triggerErrorEvent(elt, 'htmx:invalidPath', requestConfig)
4428
4506
  maybeCall(reject)
4507
+ endRequestLock()
4429
4508
  return promise
4430
4509
  }
4431
4510
 
@@ -4488,10 +4567,11 @@ var htmx = (function() {
4488
4567
  }
4489
4568
  }
4490
4569
  maybeCall(resolve)
4491
- endRequestLock()
4492
4570
  } catch (e) {
4493
4571
  triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({ error: e }, responseInfo))
4494
4572
  throw e
4573
+ } finally {
4574
+ endRequestLock()
4495
4575
  }
4496
4576
  }
4497
4577
  xhr.onerror = function() {
@@ -4666,13 +4746,31 @@ var htmx = (function() {
4666
4746
  if (title) {
4667
4747
  const titleElt = find('title')
4668
4748
  if (titleElt) {
4669
- titleElt.innerHTML = title
4749
+ titleElt.textContent = title
4670
4750
  } else {
4671
4751
  window.document.title = title
4672
4752
  }
4673
4753
  }
4674
4754
  }
4675
4755
 
4756
+ /**
4757
+ * Resove the Retarget selector and throw if not found
4758
+ * @param {Element} elt
4759
+ * @param {String} target
4760
+ * @returns {Element}
4761
+ */
4762
+ function resolveRetarget(elt, target) {
4763
+ if (target === 'this') {
4764
+ return elt
4765
+ }
4766
+ const resolvedTarget = asElement(querySelectorExt(elt, target))
4767
+ if (resolvedTarget == null) {
4768
+ triggerErrorEvent(elt, 'htmx:targetError', { target })
4769
+ throw new Error(`Invalid re-target ${target}`)
4770
+ }
4771
+ return resolvedTarget
4772
+ }
4773
+
4676
4774
  /**
4677
4775
  * @param {Element} elt
4678
4776
  * @param {HtmxResponseInfo} responseInfo
@@ -4710,25 +4808,17 @@ var htmx = (function() {
4710
4808
 
4711
4809
  if (hasHeader(xhr, /HX-Redirect:/i)) {
4712
4810
  responseInfo.keepIndicators = true
4713
- location.href = xhr.getResponseHeader('HX-Redirect')
4714
- shouldRefresh && location.reload()
4811
+ htmx.location.href = xhr.getResponseHeader('HX-Redirect')
4812
+ shouldRefresh && htmx.location.reload()
4715
4813
  return
4716
4814
  }
4717
4815
 
4718
4816
  if (shouldRefresh) {
4719
4817
  responseInfo.keepIndicators = true
4720
- location.reload()
4818
+ htmx.location.reload()
4721
4819
  return
4722
4820
  }
4723
4821
 
4724
- if (hasHeader(xhr, /HX-Retarget:/i)) {
4725
- if (xhr.getResponseHeader('HX-Retarget') === 'this') {
4726
- responseInfo.target = elt
4727
- } else {
4728
- responseInfo.target = asElement(querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget')))
4729
- }
4730
- }
4731
-
4732
4822
  const historyUpdate = determineHistoryUpdates(elt, responseInfo)
4733
4823
 
4734
4824
  const responseHandling = resolveResponseHandling(xhr)
@@ -4737,7 +4827,7 @@ var htmx = (function() {
4737
4827
  let ignoreTitle = htmx.config.ignoreTitle || responseHandling.ignoreTitle
4738
4828
  let selectOverride = responseHandling.select
4739
4829
  if (responseHandling.target) {
4740
- responseInfo.target = asElement(querySelectorExt(elt, responseHandling.target))
4830
+ responseInfo.target = resolveRetarget(elt, responseHandling.target)
4741
4831
  }
4742
4832
  var swapOverride = etc.swapOverride
4743
4833
  if (swapOverride == null && responseHandling.swapOverride) {
@@ -4746,12 +4836,9 @@ var htmx = (function() {
4746
4836
 
4747
4837
  // response headers override response handling config
4748
4838
  if (hasHeader(xhr, /HX-Retarget:/i)) {
4749
- if (xhr.getResponseHeader('HX-Retarget') === 'this') {
4750
- responseInfo.target = elt
4751
- } else {
4752
- responseInfo.target = asElement(querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget')))
4753
- }
4839
+ responseInfo.target = resolveRetarget(elt, xhr.getResponseHeader('HX-Retarget'))
4754
4840
  }
4841
+
4755
4842
  if (hasHeader(xhr, /HX-Reswap:/i)) {
4756
4843
  swapOverride = xhr.getResponseHeader('HX-Reswap')
4757
4844
  }
@@ -4804,10 +4891,6 @@ var htmx = (function() {
4804
4891
 
4805
4892
  target.classList.add(htmx.config.swappingClass)
4806
4893
 
4807
- // optional transition API promise callbacks
4808
- let settleResolve = null
4809
- let settleReject = null
4810
-
4811
4894
  if (responseInfoSelect) {
4812
4895
  selectOverride = responseInfoSelect
4813
4896
  }
@@ -4819,8 +4902,31 @@ var htmx = (function() {
4819
4902
  const selectOOB = getClosestAttributeValue(elt, 'hx-select-oob')
4820
4903
  const select = getClosestAttributeValue(elt, 'hx-select')
4821
4904
 
4822
- let doSwap = function() {
4823
- try {
4905
+ swap(target, serverResponse, swapSpec, {
4906
+ select: selectOverride === 'unset' ? null : selectOverride || select,
4907
+ selectOOB,
4908
+ eventInfo: responseInfo,
4909
+ anchor: responseInfo.pathInfo.anchor,
4910
+ contextElement: elt,
4911
+ afterSwapCallback: function() {
4912
+ if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
4913
+ let finalElt = elt
4914
+ if (!bodyContains(elt)) {
4915
+ finalElt = getDocument().body
4916
+ }
4917
+ handleTriggerHeader(xhr, 'HX-Trigger-After-Swap', finalElt)
4918
+ }
4919
+ },
4920
+ afterSettleCallback: function() {
4921
+ if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
4922
+ let finalElt = elt
4923
+ if (!bodyContains(elt)) {
4924
+ finalElt = getDocument().body
4925
+ }
4926
+ handleTriggerHeader(xhr, 'HX-Trigger-After-Settle', finalElt)
4927
+ }
4928
+ },
4929
+ beforeSwapCallback: function() {
4824
4930
  // if we need to save history, do so, before swapping so that relative resources have the correct base URL
4825
4931
  if (historyUpdate.type) {
4826
4932
  triggerEvent(getDocument().body, 'htmx:beforeHistoryUpdate', mergeObjects({ history: historyUpdate }, responseInfo))
@@ -4832,70 +4938,8 @@ var htmx = (function() {
4832
4938
  triggerEvent(getDocument().body, 'htmx:replacedInHistory', { path: historyUpdate.path })
4833
4939
  }
4834
4940
  }
4835
-
4836
- swap(target, serverResponse, swapSpec, {
4837
- select: selectOverride || select,
4838
- selectOOB,
4839
- eventInfo: responseInfo,
4840
- anchor: responseInfo.pathInfo.anchor,
4841
- contextElement: elt,
4842
- afterSwapCallback: function() {
4843
- if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
4844
- let finalElt = elt
4845
- if (!bodyContains(elt)) {
4846
- finalElt = getDocument().body
4847
- }
4848
- handleTriggerHeader(xhr, 'HX-Trigger-After-Swap', finalElt)
4849
- }
4850
- },
4851
- afterSettleCallback: function() {
4852
- if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
4853
- let finalElt = elt
4854
- if (!bodyContains(elt)) {
4855
- finalElt = getDocument().body
4856
- }
4857
- handleTriggerHeader(xhr, 'HX-Trigger-After-Settle', finalElt)
4858
- }
4859
- maybeCall(settleResolve)
4860
- }
4861
- })
4862
- } catch (e) {
4863
- triggerErrorEvent(elt, 'htmx:swapError', responseInfo)
4864
- maybeCall(settleReject)
4865
- throw e
4866
4941
  }
4867
- }
4868
-
4869
- let shouldTransition = htmx.config.globalViewTransitions
4870
- if (swapSpec.hasOwnProperty('transition')) {
4871
- shouldTransition = swapSpec.transition
4872
- }
4873
-
4874
- if (shouldTransition &&
4875
- triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
4876
- typeof Promise !== 'undefined' &&
4877
- // @ts-ignore experimental feature atm
4878
- document.startViewTransition) {
4879
- const settlePromise = new Promise(function(_resolve, _reject) {
4880
- settleResolve = _resolve
4881
- settleReject = _reject
4882
- })
4883
- // wrap the original doSwap() in a call to startViewTransition()
4884
- const innerDoSwap = doSwap
4885
- doSwap = function() {
4886
- // @ts-ignore experimental feature atm
4887
- document.startViewTransition(function() {
4888
- innerDoSwap()
4889
- return settlePromise
4890
- })
4891
- }
4892
- }
4893
-
4894
- if (swapSpec.swapDelay > 0) {
4895
- getWindow().setTimeout(doSwap, swapSpec.swapDelay)
4896
- } else {
4897
- doSwap()
4898
- }
4942
+ })
4899
4943
  }
4900
4944
  if (isError) {
4901
4945
  triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({ error: 'Response Status Error Code ' + xhr.status + ' from ' + responseInfo.pathInfo.requestPath }, responseInfo))
@@ -5096,6 +5140,9 @@ var htmx = (function() {
5096
5140
  * @property {Element} [contextElement]
5097
5141
  * @property {swapCallback} [afterSwapCallback]
5098
5142
  * @property {swapCallback} [afterSettleCallback]
5143
+ * @property {swapCallback} [beforeSwapCallback]
5144
+ * @property {string} [title]
5145
+ * @property {boolean} [historyRequest]
5099
5146
  */
5100
5147
 
5101
5148
  /**
@@ -5114,7 +5161,7 @@ var htmx = (function() {
5114
5161
  * @property {boolean} [transition]
5115
5162
  * @property {boolean} [ignoreTitle]
5116
5163
  * @property {string} [head]
5117
- * @property {'top' | 'bottom'} [scroll]
5164
+ * @property {'top' | 'bottom' | number } [scroll]
5118
5165
  * @property {string} [scrollTarget]
5119
5166
  * @property {string} [show]
5120
5167
  * @property {string} [showTarget]
@@ -5159,7 +5206,8 @@ var htmx = (function() {
5159
5206
  * @property {'true'} [HX-History-Restore-Request]
5160
5207
  */
5161
5208
 
5162
- /** @typedef HtmxAjaxHelperContext
5209
+ /**
5210
+ * @typedef HtmxAjaxHelperContext
5163
5211
  * @property {Element|string} [source]
5164
5212
  * @property {Event} [event]
5165
5213
  * @property {HtmxAjaxHandler} [handler]
@@ -5179,6 +5227,7 @@ var htmx = (function() {
5179
5227
  * @property {FormData} unfilteredFormData
5180
5228
  * @property {Object} unfilteredParameters unfilteredFormData proxy
5181
5229
  * @property {HtmxHeaderSpecification} headers
5230
+ * @property {Element} elt
5182
5231
  * @property {Element} target
5183
5232
  * @property {HttpVerb} verb
5184
5233
  * @property {HtmxElementValidationError[]} errors
@@ -5252,7 +5301,7 @@ var htmx = (function() {
5252
5301
  * @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md
5253
5302
  * @typedef {Object} HtmxExtension
5254
5303
  * @property {(api: any) => void} init
5255
- * @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
5304
+ * @property {(name: string, event: CustomEvent) => boolean} onEvent
5256
5305
  * @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
5257
5306
  * @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
5258
5307
  * @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap