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