release prep

This commit is contained in:
Carson Gross 2024-08-05 13:53:08 -06:00
parent b1d6135dca
commit 97b8c68dd3
14 changed files with 197 additions and 108 deletions

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
## [2.0.2] - 2024-07-12
## [2.0.1] - 2024-07-12 ## [2.0.1] - 2024-07-12
* Make the `/dist/htmx.esm.js` file the `main` file in `package.json` to make installing htmx smoother * Make the `/dist/htmx.esm.js` file the `main` file in `package.json` to make installing htmx smoother

View File

@ -32,7 +32,7 @@ By removing these arbitrary constraints htmx completes HTML as a
## quick start ## quick start
```html ```html
<script src="https://unpkg.com/htmx.org@2.0.1"></script> <script src="https://unpkg.com/htmx.org@2.0.2"></script>
<!-- have a button POST a click via AJAX --> <!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML"> <button hx-post="/clicked" hx-swap="outerHTML">
Click Me Click Me

71
dist/htmx.amd.js vendored
View File

@ -207,7 +207,7 @@ var htmx = (function() {
disableSelector: '[hx-disable], [data-hx-disable]', disableSelector: '[hx-disable], [data-hx-disable]',
/** /**
* @type {'auto' | 'instant' | 'smooth'} * @type {'auto' | 'instant' | 'smooth'}
* @default 'smooth' * @default 'instant'
*/ */
scrollBehavior: 'instant', scrollBehavior: 'instant',
/** /**
@ -278,7 +278,7 @@ var htmx = (function() {
parseInterval: null, parseInterval: null,
/** @type {typeof internalEval} */ /** @type {typeof internalEval} */
_: null, _: null,
version: '2.0.1' version: '2.0.2'
} }
// Tsc madness part 2 // Tsc madness part 2
htmx.onLoad = onLoadHelper htmx.onLoad = onLoadHelper
@ -773,13 +773,10 @@ var htmx = (function() {
function bodyContains(elt) { function bodyContains(elt) {
// IE Fix // IE Fix
const rootNode = elt.getRootNode && elt.getRootNode() const rootNode = elt.getRootNode && elt.getRootNode()
if (getDocument().body === null) {
return false;
}
if (rootNode && rootNode instanceof window.ShadowRoot) { if (rootNode && rootNode instanceof window.ShadowRoot) {
return getDocument().body.contains(rootNode.host); return getDocument().body.contains(rootNode.host)
} else { } else {
return getDocument().body.contains(elt); return getDocument().body.contains(elt)
} }
} }
@ -1643,13 +1640,13 @@ var htmx = (function() {
newElt = eltBeforeNewContent.nextSibling newElt = eltBeforeNewContent.nextSibling
} }
settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target }) settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target })
// scan through all newly added content and add all elements to the settle info so we trigger
// events properly on them
while (newElt && newElt !== target) { while (newElt && newElt !== target) {
if (newElt instanceof Element) { if (newElt instanceof Element) {
settleInfo.elts.push(newElt) settleInfo.elts.push(newElt)
newElt = newElt.nextElementSibling
} else {
newElt = null
} }
newElt = newElt.nextSibling
} }
cleanUpElement(target) cleanUpElement(target)
if (target instanceof Element) { if (target instanceof Element) {
@ -1757,7 +1754,7 @@ var htmx = (function() {
try { try {
const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo) const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo)
if (newElements) { if (newElements) {
if (typeof newElements.length !== 'undefined') { if (Array.isArray(newElements)) {
// if handleSwap returns an array (like) of elements, we handle them // if handleSwap returns an array (like) of elements, we handle them
for (let j = 0; j < newElements.length; j++) { for (let j = 0; j < newElements.length; j++) {
const child = newElements[j] const child = newElements[j]
@ -1785,7 +1782,8 @@ var htmx = (function() {
* @param {HtmxSettleInfo} settleInfo * @param {HtmxSettleInfo} settleInfo
*/ */
function findAndSwapOobElements(fragment, settleInfo) { function findAndSwapOobElements(fragment, settleInfo) {
forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function(oobElement) { var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
forEach(oobElts, function(oobElement) {
if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) { if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob') const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
if (oobValue != null) { if (oobValue != null) {
@ -1796,6 +1794,7 @@ var htmx = (function() {
oobElement.removeAttribute('data-hx-swap-oob') oobElement.removeAttribute('data-hx-swap-oob')
} }
}) })
return oobElts.length > 0
} }
/** /**
@ -1857,9 +1856,8 @@ var htmx = (function() {
// oob swaps // oob swaps
findAndSwapOobElements(fragment, settleInfo) findAndSwapOobElements(fragment, settleInfo)
forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) { forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
findAndSwapOobElements(template.content, settleInfo) if (findAndSwapOobElements(template.content, settleInfo)) {
if (template.content.childElementCount === 0 && template.content.textContent.trim() === '') { // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
// Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
template.remove() template.remove()
} }
}) })
@ -1956,7 +1954,10 @@ var htmx = (function() {
for (const eventName in triggers) { for (const eventName in triggers) {
if (triggers.hasOwnProperty(eventName)) { if (triggers.hasOwnProperty(eventName)) {
let detail = triggers[eventName] let detail = triggers[eventName]
if (!isRawObject(detail)) { if (isRawObject(detail)) {
// @ts-ignore
elt = detail.target !== undefined ? detail.target : elt
} else {
detail = { value: detail } detail = { value: detail }
} }
triggerEvent(elt, eventName, detail) triggerEvent(elt, eventName, detail)
@ -2277,7 +2278,7 @@ var htmx = (function() {
* @param {HtmxTriggerSpecification[]} triggerSpecs * @param {HtmxTriggerSpecification[]} triggerSpecs
*/ */
function boostElement(elt, nodeData, triggerSpecs) { function boostElement(elt, nodeData, triggerSpecs) {
if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || elt.tagName === 'FORM') { if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || (elt.tagName === 'FORM' && String(getRawAttribute(elt, 'method')).toLowerCase() !== 'dialog')) {
nodeData.boosted = true nodeData.boosted = true
let verb, path let verb, path
if (elt.tagName === 'A') { if (elt.tagName === 'A') {
@ -2439,13 +2440,17 @@ var htmx = (function() {
if (triggerSpec.throttle > 0) { if (triggerSpec.throttle > 0) {
if (!elementData.throttle) { if (!elementData.throttle) {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
elementData.throttle = getWindow().setTimeout(function() { elementData.throttle = getWindow().setTimeout(function() {
elementData.throttle = null elementData.throttle = null
}, triggerSpec.throttle) }, triggerSpec.throttle)
} }
} else if (triggerSpec.delay > 0) { } else if (triggerSpec.delay > 0) {
elementData.delayed = getWindow().setTimeout(function() { handler(elt, evt) }, triggerSpec.delay) elementData.delayed = getWindow().setTimeout(function() {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt)
}, triggerSpec.delay)
} else { } else {
triggerEvent(elt, 'htmx:trigger') triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
@ -3063,6 +3068,10 @@ var htmx = (function() {
forEach(findAll(clone, '.' + className), function(child) { forEach(findAll(clone, '.' + className), function(child) {
removeClassFromElement(child, className) removeClassFromElement(child, className)
}) })
// remove the disabled attribute for any element disabled due to an htmx request
forEach(findAll(clone, '[data-disabled-by-htmx]'), function(child) {
child.removeAttribute('disabled')
})
return clone.innerHTML return clone.innerHTML
} }
@ -3216,6 +3225,7 @@ var htmx = (function() {
const internalData = getInternalData(disabledElement) const internalData = getInternalData(disabledElement)
internalData.requestCount = (internalData.requestCount || 0) + 1 internalData.requestCount = (internalData.requestCount || 0) + 1
disabledElement.setAttribute('disabled', '') disabledElement.setAttribute('disabled', '')
disabledElement.setAttribute('data-disabled-by-htmx', '')
}) })
return disabledElts return disabledElts
} }
@ -3237,6 +3247,7 @@ var htmx = (function() {
internalData.requestCount = (internalData.requestCount || 0) - 1 internalData.requestCount = (internalData.requestCount || 0) - 1
if (internalData.requestCount === 0) { if (internalData.requestCount === 0) {
disabledElement.removeAttribute('disabled') disabledElement.removeAttribute('disabled')
disabledElement.removeAttribute('data-disabled-by-htmx')
} }
}) })
} }
@ -3386,10 +3397,10 @@ var htmx = (function() {
function overrideFormData(receiver, donor) { function overrideFormData(receiver, donor) {
for (const key of donor.keys()) { for (const key of donor.keys()) {
receiver.delete(key) receiver.delete(key)
donor.getAll(key).forEach(function(value) {
receiver.append(key, value)
})
} }
donor.forEach(function(value, key) {
receiver.append(key, value)
})
return receiver return receiver
} }
@ -3919,7 +3930,7 @@ var htmx = (function() {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
if (typeof obj[key].forEach === 'function') { if (typeof obj[key].forEach === 'function') {
obj[key].forEach(function(v) { formData.append(key, v) }) obj[key].forEach(function(v) { formData.append(key, v) })
} else if (typeof obj[key] === 'object') { } else if (typeof obj[key] === 'object' && !(obj[key] instanceof Blob)) {
formData.append(key, JSON.stringify(obj[key])) formData.append(key, JSON.stringify(obj[key]))
} else { } else {
formData.append(key, obj[key]) formData.append(key, obj[key])
@ -4012,6 +4023,8 @@ var htmx = (function() {
target.delete(name) target.delete(name)
if (typeof value.forEach === 'function') { if (typeof value.forEach === 'function') {
value.forEach(function(v) { target.append(name, v) }) value.forEach(function(v) { target.append(name, v) })
} else if (typeof value === 'object' && !(value instanceof Blob)) {
target.append(name, JSON.stringify(value))
} else { } else {
target.append(name, value) target.append(name, value)
} }
@ -4347,7 +4360,9 @@ var htmx = (function() {
const hierarchy = hierarchyForElt(elt) const hierarchy = hierarchyForElt(elt)
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr) responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
responseHandler(elt, responseInfo) responseHandler(elt, responseInfo)
removeRequestIndicators(indicators, disableElts) if (responseInfo.keepIndicators !== true) {
removeRequestIndicators(indicators, disableElts)
}
triggerEvent(elt, 'htmx:afterRequest', responseInfo) triggerEvent(elt, 'htmx:afterRequest', responseInfo)
triggerEvent(elt, 'htmx:afterOnLoad', responseInfo) triggerEvent(elt, 'htmx:afterOnLoad', responseInfo)
// if the body no longer contains the element, trigger the event on the closest parent // if the body no longer contains the element, trigger the event on the closest parent
@ -4587,12 +4602,14 @@ var htmx = (function() {
const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true' const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true'
if (hasHeader(xhr, /HX-Redirect:/i)) { if (hasHeader(xhr, /HX-Redirect:/i)) {
responseInfo.keepIndicators = true
location.href = xhr.getResponseHeader('HX-Redirect') location.href = xhr.getResponseHeader('HX-Redirect')
shouldRefresh && location.reload() shouldRefresh && location.reload()
return return
} }
if (shouldRefresh) { if (shouldRefresh) {
responseInfo.keepIndicators = true
location.reload() location.reload()
return return
} }
@ -5076,6 +5093,7 @@ var htmx = (function() {
* @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo * @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo
* @property {boolean} [failed] * @property {boolean} [failed]
* @property {boolean} [successful] * @property {boolean} [successful]
* @property {boolean} [keepIndicators]
*/ */
/** /**
@ -5125,14 +5143,15 @@ var htmx = (function() {
*/ */
/** /**
* @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md
* @typedef {Object} HtmxExtension * @typedef {Object} HtmxExtension
* @see https://htmx.org/extensions/#defining
* @property {(api: any) => void} init * @property {(api: any) => void} init
* @property {(name: string, event: Event|CustomEvent) => boolean} onEvent * @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
* @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse * @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
* @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap * @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
* @property {(swapStyle: HtmxSwapStyle, target: Element, fragment: Node, settleInfo: HtmxSettleInfo) => boolean} handleSwap * @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Element) => *|string|null} encodeParameters * @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Node) => *|string|null} encodeParameters
* @property {() => string[]|null} getSelectors
*/ */
return htmx return htmx
}) })

64
dist/htmx.cjs.js vendored
View File

@ -206,7 +206,7 @@ var htmx = (function() {
disableSelector: '[hx-disable], [data-hx-disable]', disableSelector: '[hx-disable], [data-hx-disable]',
/** /**
* @type {'auto' | 'instant' | 'smooth'} * @type {'auto' | 'instant' | 'smooth'}
* @default 'smooth' * @default 'instant'
*/ */
scrollBehavior: 'instant', scrollBehavior: 'instant',
/** /**
@ -277,7 +277,7 @@ var htmx = (function() {
parseInterval: null, parseInterval: null,
/** @type {typeof internalEval} */ /** @type {typeof internalEval} */
_: null, _: null,
version: '2.0.1' version: '2.0.2'
} }
// Tsc madness part 2 // Tsc madness part 2
htmx.onLoad = onLoadHelper htmx.onLoad = onLoadHelper
@ -1639,13 +1639,13 @@ var htmx = (function() {
newElt = eltBeforeNewContent.nextSibling newElt = eltBeforeNewContent.nextSibling
} }
settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target }) settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target })
// scan through all newly added content and add all elements to the settle info so we trigger
// events properly on them
while (newElt && newElt !== target) { while (newElt && newElt !== target) {
if (newElt instanceof Element) { if (newElt instanceof Element) {
settleInfo.elts.push(newElt) settleInfo.elts.push(newElt)
newElt = newElt.nextElementSibling
} else {
newElt = null
} }
newElt = newElt.nextSibling
} }
cleanUpElement(target) cleanUpElement(target)
if (target instanceof Element) { if (target instanceof Element) {
@ -1753,7 +1753,7 @@ var htmx = (function() {
try { try {
const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo) const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo)
if (newElements) { if (newElements) {
if (typeof newElements.length !== 'undefined') { if (Array.isArray(newElements)) {
// if handleSwap returns an array (like) of elements, we handle them // if handleSwap returns an array (like) of elements, we handle them
for (let j = 0; j < newElements.length; j++) { for (let j = 0; j < newElements.length; j++) {
const child = newElements[j] const child = newElements[j]
@ -1781,7 +1781,8 @@ var htmx = (function() {
* @param {HtmxSettleInfo} settleInfo * @param {HtmxSettleInfo} settleInfo
*/ */
function findAndSwapOobElements(fragment, settleInfo) { function findAndSwapOobElements(fragment, settleInfo) {
forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function(oobElement) { var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
forEach(oobElts, function(oobElement) {
if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) { if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob') const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
if (oobValue != null) { if (oobValue != null) {
@ -1792,6 +1793,7 @@ var htmx = (function() {
oobElement.removeAttribute('data-hx-swap-oob') oobElement.removeAttribute('data-hx-swap-oob')
} }
}) })
return oobElts.length > 0
} }
/** /**
@ -1853,9 +1855,8 @@ var htmx = (function() {
// oob swaps // oob swaps
findAndSwapOobElements(fragment, settleInfo) findAndSwapOobElements(fragment, settleInfo)
forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) { forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
findAndSwapOobElements(template.content, settleInfo) if (findAndSwapOobElements(template.content, settleInfo)) {
if (template.content.childElementCount === 0 && template.content.textContent.trim() === '') { // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
// Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
template.remove() template.remove()
} }
}) })
@ -1952,7 +1953,10 @@ var htmx = (function() {
for (const eventName in triggers) { for (const eventName in triggers) {
if (triggers.hasOwnProperty(eventName)) { if (triggers.hasOwnProperty(eventName)) {
let detail = triggers[eventName] let detail = triggers[eventName]
if (!isRawObject(detail)) { if (isRawObject(detail)) {
// @ts-ignore
elt = detail.target !== undefined ? detail.target : elt
} else {
detail = { value: detail } detail = { value: detail }
} }
triggerEvent(elt, eventName, detail) triggerEvent(elt, eventName, detail)
@ -2273,7 +2277,7 @@ var htmx = (function() {
* @param {HtmxTriggerSpecification[]} triggerSpecs * @param {HtmxTriggerSpecification[]} triggerSpecs
*/ */
function boostElement(elt, nodeData, triggerSpecs) { function boostElement(elt, nodeData, triggerSpecs) {
if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || elt.tagName === 'FORM') { if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || (elt.tagName === 'FORM' && String(getRawAttribute(elt, 'method')).toLowerCase() !== 'dialog')) {
nodeData.boosted = true nodeData.boosted = true
let verb, path let verb, path
if (elt.tagName === 'A') { if (elt.tagName === 'A') {
@ -2435,13 +2439,17 @@ var htmx = (function() {
if (triggerSpec.throttle > 0) { if (triggerSpec.throttle > 0) {
if (!elementData.throttle) { if (!elementData.throttle) {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
elementData.throttle = getWindow().setTimeout(function() { elementData.throttle = getWindow().setTimeout(function() {
elementData.throttle = null elementData.throttle = null
}, triggerSpec.throttle) }, triggerSpec.throttle)
} }
} else if (triggerSpec.delay > 0) { } else if (triggerSpec.delay > 0) {
elementData.delayed = getWindow().setTimeout(function() { handler(elt, evt) }, triggerSpec.delay) elementData.delayed = getWindow().setTimeout(function() {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt)
}, triggerSpec.delay)
} else { } else {
triggerEvent(elt, 'htmx:trigger') triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
@ -3059,6 +3067,10 @@ var htmx = (function() {
forEach(findAll(clone, '.' + className), function(child) { forEach(findAll(clone, '.' + className), function(child) {
removeClassFromElement(child, className) removeClassFromElement(child, className)
}) })
// remove the disabled attribute for any element disabled due to an htmx request
forEach(findAll(clone, '[data-disabled-by-htmx]'), function(child) {
child.removeAttribute('disabled')
})
return clone.innerHTML return clone.innerHTML
} }
@ -3212,6 +3224,7 @@ var htmx = (function() {
const internalData = getInternalData(disabledElement) const internalData = getInternalData(disabledElement)
internalData.requestCount = (internalData.requestCount || 0) + 1 internalData.requestCount = (internalData.requestCount || 0) + 1
disabledElement.setAttribute('disabled', '') disabledElement.setAttribute('disabled', '')
disabledElement.setAttribute('data-disabled-by-htmx', '')
}) })
return disabledElts return disabledElts
} }
@ -3233,6 +3246,7 @@ var htmx = (function() {
internalData.requestCount = (internalData.requestCount || 0) - 1 internalData.requestCount = (internalData.requestCount || 0) - 1
if (internalData.requestCount === 0) { if (internalData.requestCount === 0) {
disabledElement.removeAttribute('disabled') disabledElement.removeAttribute('disabled')
disabledElement.removeAttribute('data-disabled-by-htmx')
} }
}) })
} }
@ -3382,10 +3396,10 @@ var htmx = (function() {
function overrideFormData(receiver, donor) { function overrideFormData(receiver, donor) {
for (const key of donor.keys()) { for (const key of donor.keys()) {
receiver.delete(key) receiver.delete(key)
donor.getAll(key).forEach(function(value) {
receiver.append(key, value)
})
} }
donor.forEach(function(value, key) {
receiver.append(key, value)
})
return receiver return receiver
} }
@ -3915,7 +3929,7 @@ var htmx = (function() {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
if (typeof obj[key].forEach === 'function') { if (typeof obj[key].forEach === 'function') {
obj[key].forEach(function(v) { formData.append(key, v) }) obj[key].forEach(function(v) { formData.append(key, v) })
} else if (typeof obj[key] === 'object') { } else if (typeof obj[key] === 'object' && !(obj[key] instanceof Blob)) {
formData.append(key, JSON.stringify(obj[key])) formData.append(key, JSON.stringify(obj[key]))
} else { } else {
formData.append(key, obj[key]) formData.append(key, obj[key])
@ -4008,6 +4022,8 @@ var htmx = (function() {
target.delete(name) target.delete(name)
if (typeof value.forEach === 'function') { if (typeof value.forEach === 'function') {
value.forEach(function(v) { target.append(name, v) }) value.forEach(function(v) { target.append(name, v) })
} else if (typeof value === 'object' && !(value instanceof Blob)) {
target.append(name, JSON.stringify(value))
} else { } else {
target.append(name, value) target.append(name, value)
} }
@ -4343,7 +4359,9 @@ var htmx = (function() {
const hierarchy = hierarchyForElt(elt) const hierarchy = hierarchyForElt(elt)
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr) responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
responseHandler(elt, responseInfo) responseHandler(elt, responseInfo)
removeRequestIndicators(indicators, disableElts) if (responseInfo.keepIndicators !== true) {
removeRequestIndicators(indicators, disableElts)
}
triggerEvent(elt, 'htmx:afterRequest', responseInfo) triggerEvent(elt, 'htmx:afterRequest', responseInfo)
triggerEvent(elt, 'htmx:afterOnLoad', responseInfo) triggerEvent(elt, 'htmx:afterOnLoad', responseInfo)
// if the body no longer contains the element, trigger the event on the closest parent // if the body no longer contains the element, trigger the event on the closest parent
@ -4583,12 +4601,14 @@ var htmx = (function() {
const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true' const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true'
if (hasHeader(xhr, /HX-Redirect:/i)) { if (hasHeader(xhr, /HX-Redirect:/i)) {
responseInfo.keepIndicators = true
location.href = xhr.getResponseHeader('HX-Redirect') location.href = xhr.getResponseHeader('HX-Redirect')
shouldRefresh && location.reload() shouldRefresh && location.reload()
return return
} }
if (shouldRefresh) { if (shouldRefresh) {
responseInfo.keepIndicators = true
location.reload() location.reload()
return return
} }
@ -5072,6 +5092,7 @@ var htmx = (function() {
* @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo * @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo
* @property {boolean} [failed] * @property {boolean} [failed]
* @property {boolean} [successful] * @property {boolean} [successful]
* @property {boolean} [keepIndicators]
*/ */
/** /**
@ -5121,13 +5142,14 @@ var htmx = (function() {
*/ */
/** /**
* @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md
* @typedef {Object} HtmxExtension * @typedef {Object} HtmxExtension
* @see https://htmx.org/extensions/#defining
* @property {(api: any) => void} init * @property {(api: any) => void} init
* @property {(name: string, event: Event|CustomEvent) => boolean} onEvent * @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
* @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse * @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
* @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap * @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
* @property {(swapStyle: HtmxSwapStyle, target: Element, fragment: Node, settleInfo: HtmxSettleInfo) => boolean} handleSwap * @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Element) => *|string|null} encodeParameters * @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Node) => *|string|null} encodeParameters
* @property {() => string[]|null} getSelectors
*/ */
module.exports = htmx; module.exports = htmx;

1
dist/htmx.esm.d.ts vendored
View File

@ -95,6 +95,7 @@ export type HtmxResponseInfo = {
}; };
failed?: boolean; failed?: boolean;
successful?: boolean; successful?: boolean;
keepIndicators?: boolean;
}; };
export type HtmxAjaxEtc = { export type HtmxAjaxEtc = {
returnPromise?: boolean; returnPromise?: boolean;

64
dist/htmx.esm.js vendored
View File

@ -206,7 +206,7 @@ var htmx = (function() {
disableSelector: '[hx-disable], [data-hx-disable]', disableSelector: '[hx-disable], [data-hx-disable]',
/** /**
* @type {'auto' | 'instant' | 'smooth'} * @type {'auto' | 'instant' | 'smooth'}
* @default 'smooth' * @default 'instant'
*/ */
scrollBehavior: 'instant', scrollBehavior: 'instant',
/** /**
@ -277,7 +277,7 @@ var htmx = (function() {
parseInterval: null, parseInterval: null,
/** @type {typeof internalEval} */ /** @type {typeof internalEval} */
_: null, _: null,
version: '2.0.1' version: '2.0.2'
} }
// Tsc madness part 2 // Tsc madness part 2
htmx.onLoad = onLoadHelper htmx.onLoad = onLoadHelper
@ -1639,13 +1639,13 @@ var htmx = (function() {
newElt = eltBeforeNewContent.nextSibling newElt = eltBeforeNewContent.nextSibling
} }
settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target }) settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target })
// scan through all newly added content and add all elements to the settle info so we trigger
// events properly on them
while (newElt && newElt !== target) { while (newElt && newElt !== target) {
if (newElt instanceof Element) { if (newElt instanceof Element) {
settleInfo.elts.push(newElt) settleInfo.elts.push(newElt)
newElt = newElt.nextElementSibling
} else {
newElt = null
} }
newElt = newElt.nextSibling
} }
cleanUpElement(target) cleanUpElement(target)
if (target instanceof Element) { if (target instanceof Element) {
@ -1753,7 +1753,7 @@ var htmx = (function() {
try { try {
const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo) const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo)
if (newElements) { if (newElements) {
if (typeof newElements.length !== 'undefined') { if (Array.isArray(newElements)) {
// if handleSwap returns an array (like) of elements, we handle them // if handleSwap returns an array (like) of elements, we handle them
for (let j = 0; j < newElements.length; j++) { for (let j = 0; j < newElements.length; j++) {
const child = newElements[j] const child = newElements[j]
@ -1781,7 +1781,8 @@ var htmx = (function() {
* @param {HtmxSettleInfo} settleInfo * @param {HtmxSettleInfo} settleInfo
*/ */
function findAndSwapOobElements(fragment, settleInfo) { function findAndSwapOobElements(fragment, settleInfo) {
forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function(oobElement) { var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
forEach(oobElts, function(oobElement) {
if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) { if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob') const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
if (oobValue != null) { if (oobValue != null) {
@ -1792,6 +1793,7 @@ var htmx = (function() {
oobElement.removeAttribute('data-hx-swap-oob') oobElement.removeAttribute('data-hx-swap-oob')
} }
}) })
return oobElts.length > 0
} }
/** /**
@ -1853,9 +1855,8 @@ var htmx = (function() {
// oob swaps // oob swaps
findAndSwapOobElements(fragment, settleInfo) findAndSwapOobElements(fragment, settleInfo)
forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) { forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
findAndSwapOobElements(template.content, settleInfo) if (findAndSwapOobElements(template.content, settleInfo)) {
if (template.content.childElementCount === 0 && template.content.textContent.trim() === '') { // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
// Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
template.remove() template.remove()
} }
}) })
@ -1952,7 +1953,10 @@ var htmx = (function() {
for (const eventName in triggers) { for (const eventName in triggers) {
if (triggers.hasOwnProperty(eventName)) { if (triggers.hasOwnProperty(eventName)) {
let detail = triggers[eventName] let detail = triggers[eventName]
if (!isRawObject(detail)) { if (isRawObject(detail)) {
// @ts-ignore
elt = detail.target !== undefined ? detail.target : elt
} else {
detail = { value: detail } detail = { value: detail }
} }
triggerEvent(elt, eventName, detail) triggerEvent(elt, eventName, detail)
@ -2273,7 +2277,7 @@ var htmx = (function() {
* @param {HtmxTriggerSpecification[]} triggerSpecs * @param {HtmxTriggerSpecification[]} triggerSpecs
*/ */
function boostElement(elt, nodeData, triggerSpecs) { function boostElement(elt, nodeData, triggerSpecs) {
if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || elt.tagName === 'FORM') { if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || (elt.tagName === 'FORM' && String(getRawAttribute(elt, 'method')).toLowerCase() !== 'dialog')) {
nodeData.boosted = true nodeData.boosted = true
let verb, path let verb, path
if (elt.tagName === 'A') { if (elt.tagName === 'A') {
@ -2435,13 +2439,17 @@ var htmx = (function() {
if (triggerSpec.throttle > 0) { if (triggerSpec.throttle > 0) {
if (!elementData.throttle) { if (!elementData.throttle) {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
elementData.throttle = getWindow().setTimeout(function() { elementData.throttle = getWindow().setTimeout(function() {
elementData.throttle = null elementData.throttle = null
}, triggerSpec.throttle) }, triggerSpec.throttle)
} }
} else if (triggerSpec.delay > 0) { } else if (triggerSpec.delay > 0) {
elementData.delayed = getWindow().setTimeout(function() { handler(elt, evt) }, triggerSpec.delay) elementData.delayed = getWindow().setTimeout(function() {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt)
}, triggerSpec.delay)
} else { } else {
triggerEvent(elt, 'htmx:trigger') triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
@ -3059,6 +3067,10 @@ var htmx = (function() {
forEach(findAll(clone, '.' + className), function(child) { forEach(findAll(clone, '.' + className), function(child) {
removeClassFromElement(child, className) removeClassFromElement(child, className)
}) })
// remove the disabled attribute for any element disabled due to an htmx request
forEach(findAll(clone, '[data-disabled-by-htmx]'), function(child) {
child.removeAttribute('disabled')
})
return clone.innerHTML return clone.innerHTML
} }
@ -3212,6 +3224,7 @@ var htmx = (function() {
const internalData = getInternalData(disabledElement) const internalData = getInternalData(disabledElement)
internalData.requestCount = (internalData.requestCount || 0) + 1 internalData.requestCount = (internalData.requestCount || 0) + 1
disabledElement.setAttribute('disabled', '') disabledElement.setAttribute('disabled', '')
disabledElement.setAttribute('data-disabled-by-htmx', '')
}) })
return disabledElts return disabledElts
} }
@ -3233,6 +3246,7 @@ var htmx = (function() {
internalData.requestCount = (internalData.requestCount || 0) - 1 internalData.requestCount = (internalData.requestCount || 0) - 1
if (internalData.requestCount === 0) { if (internalData.requestCount === 0) {
disabledElement.removeAttribute('disabled') disabledElement.removeAttribute('disabled')
disabledElement.removeAttribute('data-disabled-by-htmx')
} }
}) })
} }
@ -3382,10 +3396,10 @@ var htmx = (function() {
function overrideFormData(receiver, donor) { function overrideFormData(receiver, donor) {
for (const key of donor.keys()) { for (const key of donor.keys()) {
receiver.delete(key) receiver.delete(key)
donor.getAll(key).forEach(function(value) {
receiver.append(key, value)
})
} }
donor.forEach(function(value, key) {
receiver.append(key, value)
})
return receiver return receiver
} }
@ -3915,7 +3929,7 @@ var htmx = (function() {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
if (typeof obj[key].forEach === 'function') { if (typeof obj[key].forEach === 'function') {
obj[key].forEach(function(v) { formData.append(key, v) }) obj[key].forEach(function(v) { formData.append(key, v) })
} else if (typeof obj[key] === 'object') { } else if (typeof obj[key] === 'object' && !(obj[key] instanceof Blob)) {
formData.append(key, JSON.stringify(obj[key])) formData.append(key, JSON.stringify(obj[key]))
} else { } else {
formData.append(key, obj[key]) formData.append(key, obj[key])
@ -4008,6 +4022,8 @@ var htmx = (function() {
target.delete(name) target.delete(name)
if (typeof value.forEach === 'function') { if (typeof value.forEach === 'function') {
value.forEach(function(v) { target.append(name, v) }) value.forEach(function(v) { target.append(name, v) })
} else if (typeof value === 'object' && !(value instanceof Blob)) {
target.append(name, JSON.stringify(value))
} else { } else {
target.append(name, value) target.append(name, value)
} }
@ -4343,7 +4359,9 @@ var htmx = (function() {
const hierarchy = hierarchyForElt(elt) const hierarchy = hierarchyForElt(elt)
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr) responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
responseHandler(elt, responseInfo) responseHandler(elt, responseInfo)
removeRequestIndicators(indicators, disableElts) if (responseInfo.keepIndicators !== true) {
removeRequestIndicators(indicators, disableElts)
}
triggerEvent(elt, 'htmx:afterRequest', responseInfo) triggerEvent(elt, 'htmx:afterRequest', responseInfo)
triggerEvent(elt, 'htmx:afterOnLoad', responseInfo) triggerEvent(elt, 'htmx:afterOnLoad', responseInfo)
// if the body no longer contains the element, trigger the event on the closest parent // if the body no longer contains the element, trigger the event on the closest parent
@ -4583,12 +4601,14 @@ var htmx = (function() {
const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true' const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true'
if (hasHeader(xhr, /HX-Redirect:/i)) { if (hasHeader(xhr, /HX-Redirect:/i)) {
responseInfo.keepIndicators = true
location.href = xhr.getResponseHeader('HX-Redirect') location.href = xhr.getResponseHeader('HX-Redirect')
shouldRefresh && location.reload() shouldRefresh && location.reload()
return return
} }
if (shouldRefresh) { if (shouldRefresh) {
responseInfo.keepIndicators = true
location.reload() location.reload()
return return
} }
@ -5072,6 +5092,7 @@ var htmx = (function() {
* @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo * @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo
* @property {boolean} [failed] * @property {boolean} [failed]
* @property {boolean} [successful] * @property {boolean} [successful]
* @property {boolean} [keepIndicators]
*/ */
/** /**
@ -5121,13 +5142,14 @@ var htmx = (function() {
*/ */
/** /**
* @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md
* @typedef {Object} HtmxExtension * @typedef {Object} HtmxExtension
* @see https://htmx.org/extensions/#defining
* @property {(api: any) => void} init * @property {(api: any) => void} init
* @property {(name: string, event: Event|CustomEvent) => boolean} onEvent * @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
* @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse * @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
* @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap * @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
* @property {(swapStyle: HtmxSwapStyle, target: Element, fragment: Node, settleInfo: HtmxSettleInfo) => boolean} handleSwap * @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Element) => *|string|null} encodeParameters * @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Node) => *|string|null} encodeParameters
* @property {() => string[]|null} getSelectors
*/ */
export default htmx export default htmx

64
dist/htmx.js vendored
View File

@ -206,7 +206,7 @@ var htmx = (function() {
disableSelector: '[hx-disable], [data-hx-disable]', disableSelector: '[hx-disable], [data-hx-disable]',
/** /**
* @type {'auto' | 'instant' | 'smooth'} * @type {'auto' | 'instant' | 'smooth'}
* @default 'smooth' * @default 'instant'
*/ */
scrollBehavior: 'instant', scrollBehavior: 'instant',
/** /**
@ -277,7 +277,7 @@ var htmx = (function() {
parseInterval: null, parseInterval: null,
/** @type {typeof internalEval} */ /** @type {typeof internalEval} */
_: null, _: null,
version: '2.0.1' version: '2.0.2'
} }
// Tsc madness part 2 // Tsc madness part 2
htmx.onLoad = onLoadHelper htmx.onLoad = onLoadHelper
@ -1639,13 +1639,13 @@ var htmx = (function() {
newElt = eltBeforeNewContent.nextSibling newElt = eltBeforeNewContent.nextSibling
} }
settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target }) settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target })
// scan through all newly added content and add all elements to the settle info so we trigger
// events properly on them
while (newElt && newElt !== target) { while (newElt && newElt !== target) {
if (newElt instanceof Element) { if (newElt instanceof Element) {
settleInfo.elts.push(newElt) settleInfo.elts.push(newElt)
newElt = newElt.nextElementSibling
} else {
newElt = null
} }
newElt = newElt.nextSibling
} }
cleanUpElement(target) cleanUpElement(target)
if (target instanceof Element) { if (target instanceof Element) {
@ -1753,7 +1753,7 @@ var htmx = (function() {
try { try {
const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo) const newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo)
if (newElements) { if (newElements) {
if (typeof newElements.length !== 'undefined') { if (Array.isArray(newElements)) {
// if handleSwap returns an array (like) of elements, we handle them // if handleSwap returns an array (like) of elements, we handle them
for (let j = 0; j < newElements.length; j++) { for (let j = 0; j < newElements.length; j++) {
const child = newElements[j] const child = newElements[j]
@ -1781,7 +1781,8 @@ var htmx = (function() {
* @param {HtmxSettleInfo} settleInfo * @param {HtmxSettleInfo} settleInfo
*/ */
function findAndSwapOobElements(fragment, settleInfo) { function findAndSwapOobElements(fragment, settleInfo) {
forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function(oobElement) { var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
forEach(oobElts, function(oobElement) {
if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) { if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob') const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
if (oobValue != null) { if (oobValue != null) {
@ -1792,6 +1793,7 @@ var htmx = (function() {
oobElement.removeAttribute('data-hx-swap-oob') oobElement.removeAttribute('data-hx-swap-oob')
} }
}) })
return oobElts.length > 0
} }
/** /**
@ -1853,9 +1855,8 @@ var htmx = (function() {
// oob swaps // oob swaps
findAndSwapOobElements(fragment, settleInfo) findAndSwapOobElements(fragment, settleInfo)
forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) { forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
findAndSwapOobElements(template.content, settleInfo) if (findAndSwapOobElements(template.content, settleInfo)) {
if (template.content.childElementCount === 0 && template.content.textContent.trim() === '') { // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
// Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
template.remove() template.remove()
} }
}) })
@ -1952,7 +1953,10 @@ var htmx = (function() {
for (const eventName in triggers) { for (const eventName in triggers) {
if (triggers.hasOwnProperty(eventName)) { if (triggers.hasOwnProperty(eventName)) {
let detail = triggers[eventName] let detail = triggers[eventName]
if (!isRawObject(detail)) { if (isRawObject(detail)) {
// @ts-ignore
elt = detail.target !== undefined ? detail.target : elt
} else {
detail = { value: detail } detail = { value: detail }
} }
triggerEvent(elt, eventName, detail) triggerEvent(elt, eventName, detail)
@ -2273,7 +2277,7 @@ var htmx = (function() {
* @param {HtmxTriggerSpecification[]} triggerSpecs * @param {HtmxTriggerSpecification[]} triggerSpecs
*/ */
function boostElement(elt, nodeData, triggerSpecs) { function boostElement(elt, nodeData, triggerSpecs) {
if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || elt.tagName === 'FORM') { if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || (elt.tagName === 'FORM' && String(getRawAttribute(elt, 'method')).toLowerCase() !== 'dialog')) {
nodeData.boosted = true nodeData.boosted = true
let verb, path let verb, path
if (elt.tagName === 'A') { if (elt.tagName === 'A') {
@ -2435,13 +2439,17 @@ var htmx = (function() {
if (triggerSpec.throttle > 0) { if (triggerSpec.throttle > 0) {
if (!elementData.throttle) { if (!elementData.throttle) {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
elementData.throttle = getWindow().setTimeout(function() { elementData.throttle = getWindow().setTimeout(function() {
elementData.throttle = null elementData.throttle = null
}, triggerSpec.throttle) }, triggerSpec.throttle)
} }
} else if (triggerSpec.delay > 0) { } else if (triggerSpec.delay > 0) {
elementData.delayed = getWindow().setTimeout(function() { handler(elt, evt) }, triggerSpec.delay) elementData.delayed = getWindow().setTimeout(function() {
triggerEvent(elt, 'htmx:trigger')
handler(elt, evt)
}, triggerSpec.delay)
} else { } else {
triggerEvent(elt, 'htmx:trigger') triggerEvent(elt, 'htmx:trigger')
handler(elt, evt) handler(elt, evt)
@ -3059,6 +3067,10 @@ var htmx = (function() {
forEach(findAll(clone, '.' + className), function(child) { forEach(findAll(clone, '.' + className), function(child) {
removeClassFromElement(child, className) removeClassFromElement(child, className)
}) })
// remove the disabled attribute for any element disabled due to an htmx request
forEach(findAll(clone, '[data-disabled-by-htmx]'), function(child) {
child.removeAttribute('disabled')
})
return clone.innerHTML return clone.innerHTML
} }
@ -3212,6 +3224,7 @@ var htmx = (function() {
const internalData = getInternalData(disabledElement) const internalData = getInternalData(disabledElement)
internalData.requestCount = (internalData.requestCount || 0) + 1 internalData.requestCount = (internalData.requestCount || 0) + 1
disabledElement.setAttribute('disabled', '') disabledElement.setAttribute('disabled', '')
disabledElement.setAttribute('data-disabled-by-htmx', '')
}) })
return disabledElts return disabledElts
} }
@ -3233,6 +3246,7 @@ var htmx = (function() {
internalData.requestCount = (internalData.requestCount || 0) - 1 internalData.requestCount = (internalData.requestCount || 0) - 1
if (internalData.requestCount === 0) { if (internalData.requestCount === 0) {
disabledElement.removeAttribute('disabled') disabledElement.removeAttribute('disabled')
disabledElement.removeAttribute('data-disabled-by-htmx')
} }
}) })
} }
@ -3382,10 +3396,10 @@ var htmx = (function() {
function overrideFormData(receiver, donor) { function overrideFormData(receiver, donor) {
for (const key of donor.keys()) { for (const key of donor.keys()) {
receiver.delete(key) receiver.delete(key)
donor.getAll(key).forEach(function(value) {
receiver.append(key, value)
})
} }
donor.forEach(function(value, key) {
receiver.append(key, value)
})
return receiver return receiver
} }
@ -3915,7 +3929,7 @@ var htmx = (function() {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
if (typeof obj[key].forEach === 'function') { if (typeof obj[key].forEach === 'function') {
obj[key].forEach(function(v) { formData.append(key, v) }) obj[key].forEach(function(v) { formData.append(key, v) })
} else if (typeof obj[key] === 'object') { } else if (typeof obj[key] === 'object' && !(obj[key] instanceof Blob)) {
formData.append(key, JSON.stringify(obj[key])) formData.append(key, JSON.stringify(obj[key]))
} else { } else {
formData.append(key, obj[key]) formData.append(key, obj[key])
@ -4008,6 +4022,8 @@ var htmx = (function() {
target.delete(name) target.delete(name)
if (typeof value.forEach === 'function') { if (typeof value.forEach === 'function') {
value.forEach(function(v) { target.append(name, v) }) value.forEach(function(v) { target.append(name, v) })
} else if (typeof value === 'object' && !(value instanceof Blob)) {
target.append(name, JSON.stringify(value))
} else { } else {
target.append(name, value) target.append(name, value)
} }
@ -4343,7 +4359,9 @@ var htmx = (function() {
const hierarchy = hierarchyForElt(elt) const hierarchy = hierarchyForElt(elt)
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr) responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
responseHandler(elt, responseInfo) responseHandler(elt, responseInfo)
removeRequestIndicators(indicators, disableElts) if (responseInfo.keepIndicators !== true) {
removeRequestIndicators(indicators, disableElts)
}
triggerEvent(elt, 'htmx:afterRequest', responseInfo) triggerEvent(elt, 'htmx:afterRequest', responseInfo)
triggerEvent(elt, 'htmx:afterOnLoad', responseInfo) triggerEvent(elt, 'htmx:afterOnLoad', responseInfo)
// if the body no longer contains the element, trigger the event on the closest parent // if the body no longer contains the element, trigger the event on the closest parent
@ -4583,12 +4601,14 @@ var htmx = (function() {
const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true' const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true'
if (hasHeader(xhr, /HX-Redirect:/i)) { if (hasHeader(xhr, /HX-Redirect:/i)) {
responseInfo.keepIndicators = true
location.href = xhr.getResponseHeader('HX-Redirect') location.href = xhr.getResponseHeader('HX-Redirect')
shouldRefresh && location.reload() shouldRefresh && location.reload()
return return
} }
if (shouldRefresh) { if (shouldRefresh) {
responseInfo.keepIndicators = true
location.reload() location.reload()
return return
} }
@ -5072,6 +5092,7 @@ var htmx = (function() {
* @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo * @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo
* @property {boolean} [failed] * @property {boolean} [failed]
* @property {boolean} [successful] * @property {boolean} [successful]
* @property {boolean} [keepIndicators]
*/ */
/** /**
@ -5121,12 +5142,13 @@ var htmx = (function() {
*/ */
/** /**
* @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md
* @typedef {Object} HtmxExtension * @typedef {Object} HtmxExtension
* @see https://htmx.org/extensions/#defining
* @property {(api: any) => void} init * @property {(api: any) => void} init
* @property {(name: string, event: Event|CustomEvent) => boolean} onEvent * @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
* @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse * @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
* @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap * @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
* @property {(swapStyle: HtmxSwapStyle, target: Element, fragment: Node, settleInfo: HtmxSettleInfo) => boolean} handleSwap * @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Element) => *|string|null} encodeParameters * @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Node) => *|string|null} encodeParameters
* @property {() => string[]|null} getSelectors
*/ */

2
dist/htmx.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/htmx.min.js.gz vendored

Binary file not shown.

View File

@ -5,7 +5,7 @@
"AJAX", "AJAX",
"HTML" "HTML"
], ],
"version": "2.0.1", "version": "2.0.2",
"homepage": "https://htmx.org/", "homepage": "https://htmx.org/",
"bugs": { "bugs": {
"url": "https://github.com/bigskysoftware/htmx/issues" "url": "https://github.com/bigskysoftware/htmx/issues"

View File

@ -277,7 +277,7 @@ var htmx = (function() {
parseInterval: null, parseInterval: null,
/** @type {typeof internalEval} */ /** @type {typeof internalEval} */
_: null, _: null,
version: '2.0.1' version: '2.0.2'
} }
// Tsc madness part 2 // Tsc madness part 2
htmx.onLoad = onLoadHelper htmx.onLoad = onLoadHelper
@ -1781,7 +1781,7 @@ var htmx = (function() {
* @param {HtmxSettleInfo} settleInfo * @param {HtmxSettleInfo} settleInfo
*/ */
function findAndSwapOobElements(fragment, settleInfo) { function findAndSwapOobElements(fragment, settleInfo) {
var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'); var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
forEach(oobElts, function(oobElement) { forEach(oobElts, function(oobElement) {
if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) { if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob') const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
@ -1793,7 +1793,7 @@ var htmx = (function() {
oobElement.removeAttribute('data-hx-swap-oob') oobElement.removeAttribute('data-hx-swap-oob')
} }
}) })
return oobElts.length > 0; return oobElts.length > 0
} }
/** /**
@ -3069,7 +3069,7 @@ var htmx = (function() {
}) })
// remove the disabled attribute for any element disabled due to an htmx request // remove the disabled attribute for any element disabled due to an htmx request
forEach(findAll(clone, '[data-disabled-by-htmx]'), function(child) { forEach(findAll(clone, '[data-disabled-by-htmx]'), function(child) {
child.removeAttribute('disabled'); child.removeAttribute('disabled')
}) })
return clone.innerHTML return clone.innerHTML
} }
@ -4360,7 +4360,7 @@ var htmx = (function() {
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr) responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
responseHandler(elt, responseInfo) responseHandler(elt, responseInfo)
if (responseInfo.keepIndicators !== true) { if (responseInfo.keepIndicators !== true) {
removeRequestIndicators(indicators, disableElts); removeRequestIndicators(indicators, disableElts)
} }
triggerEvent(elt, 'htmx:afterRequest', responseInfo) triggerEvent(elt, 'htmx:afterRequest', responseInfo)
triggerEvent(elt, 'htmx:afterOnLoad', responseInfo) triggerEvent(elt, 'htmx:afterOnLoad', responseInfo)
@ -4601,14 +4601,14 @@ var htmx = (function() {
const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true' const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true'
if (hasHeader(xhr, /HX-Redirect:/i)) { if (hasHeader(xhr, /HX-Redirect:/i)) {
responseInfo.keepIndicators = true; responseInfo.keepIndicators = true
location.href = xhr.getResponseHeader('HX-Redirect') location.href = xhr.getResponseHeader('HX-Redirect')
shouldRefresh && location.reload() shouldRefresh && location.reload()
return return
} }
if (shouldRefresh) { if (shouldRefresh) {
responseInfo.keepIndicators = true; responseInfo.keepIndicators = true
location.reload() location.reload()
return return
} }

View File

@ -181,11 +181,11 @@ describe('hx-swap-oob attribute', function() {
'Clicked<template><div hx-swap-oob="outerHTML" id="d1">Foo</div></template>') 'Clicked<template><div hx-swap-oob="outerHTML" id="d1">Foo</div></template>')
var div = make('<button hx-get="/test" id="b1">Click Me</button>' + var div = make('<button hx-get="/test" id="b1">Click Me</button>' +
'<div id="d1" ></div>') '<div id="d1" ></div>')
var btn = byId('b1'); var btn = byId('b1')
btn.click() btn.click()
this.server.respond() this.server.respond()
should.equal(byId('b1').innerHTML, "Clicked") should.equal(byId('b1').innerHTML, 'Clicked')
should.equal(byId('d1').innerHTML, "Foo") should.equal(byId('d1').innerHTML, 'Foo')
}) })
it('oob swap keeps templates not used for oob swap encapsulation', function() { it('oob swap keeps templates not used for oob swap encapsulation', function() {
@ -193,11 +193,11 @@ describe('hx-swap-oob attribute', function() {
'Clicked<template></template>') 'Clicked<template></template>')
var div = make('<button hx-get="/test" id="b1">Click Me</button>' + var div = make('<button hx-get="/test" id="b1">Click Me</button>' +
'<div id="d1" ></div>') '<div id="d1" ></div>')
var btn = byId('b1'); var btn = byId('b1')
btn.click() btn.click()
this.server.respond() this.server.respond()
should.equal(byId('b1').innerHTML, "Clicked<template></template>") should.equal(byId('b1').innerHTML, 'Clicked<template></template>')
should.equal(byId('d1').innerHTML, "") should.equal(byId('d1').innerHTML, '')
}) })
for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) {

View File

@ -119,7 +119,7 @@ By removing these constraints, htmx completes HTML as a [hypertext](https://en.w
<h2>quick start</h2> <h2>quick start</h2>
```html ```html
<script src="https://unpkg.com/htmx.org@2.0.1"></script> <script src="https://unpkg.com/htmx.org@2.0.2"></script>
<!-- have a button POST a click via AJAX --> <!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML"> <button hx-post="/clicked" hx-swap="outerHTML">
Click Me Click Me

View File

@ -121,13 +121,13 @@ The fastest way to get going with htmx is to load it via a CDN. You can simply a
your head tag and get going: your head tag and get going:
```html ```html
<script src="https://unpkg.com/htmx.org@2.0.1" integrity="sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/" crossorigin="anonymous"></script> <script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/" crossorigin="anonymous"></script>
``` ```
An unminified version is also available for debugging as well: An unminified version is also available for debugging as well:
```html ```html
<script src="https://unpkg.com/htmx.org@2.0.1/dist/htmx.js" integrity="sha384-gpIh5aLQ0qmX8kZdyhsd6jA24uKLkqIr1WAGtantR4KsS97l/NRBvh8/8OYGThAf" crossorigin="anonymous"></script> <script src="https://unpkg.com/htmx.org@2.02/dist/htmx.js" integrity="sha384-gpIh5aLQ0qmX8kZdyhsd6jA24uKLkqIr1WAGtantR4KsS97l/NRBvh8/8OYGThAf" crossorigin="anonymous"></script>
``` ```
While the CDN approach is extremely simple, you may want to consider While the CDN approach is extremely simple, you may want to consider