diff --git a/src/htmx.js b/src/htmx.js
index 7b1d9b57..25e51245 100644
--- a/src/htmx.js
+++ b/src/htmx.js
@@ -1347,6 +1347,16 @@ var htmx = (function() {
return [findThisElement(elt, attrName)]
} else {
const result = querySelectorAllExt(elt, attrTarget)
+ // find `inherit` whole word in value, make sure it's surrounded by commas or is at the start/end of string
+ const shouldInherit = /(^|,)(\s*)inherit(\s*)($|,)/.test(attrTarget)
+ if (shouldInherit) {
+ const eltToInheritFrom = asElement(getClosestMatch(elt, function(parent) {
+ return parent !== elt && hasAttribute(asElement(parent), attrName)
+ }))
+ if (eltToInheritFrom) {
+ result.push(...findAttributeTargets(eltToInheritFrom, attrName))
+ }
+ }
if (result.length === 0) {
logError('The selector "' + attrTarget + '" on ' + attrName + ' returned no matches!')
return [DUMMY_ELT]
@@ -1850,6 +1860,30 @@ var htmx = (function() {
return oobElts.length > 0
}
+ /**
+ * Apply swapping class and then execute the swap with optional delay
+ * @param {string|Element} target
+ * @param {string} content
+ * @param {HtmxSwapSpecification} swapSpec
+ * @param {SwapOptions} [swapOptions]
+ */
+ function swap(target, content, swapSpec, swapOptions) {
+ if (!swapOptions) {
+ swapOptions = {}
+ }
+
+ target = resolveTarget(target)
+ target.classList.add(htmx.config.swappingClass)
+ const localSwap = function() {
+ runSwap(target, content, swapSpec, swapOptions)
+ }
+ if (swapSpec?.swapDelay && swapSpec.swapDelay > 0) {
+ getWindow().setTimeout(localSwap, swapSpec.swapDelay)
+ } else {
+ localSwap()
+ }
+ }
+
/**
* Implements complete swapping pipeline, including: focus and selection preservation,
* title updates, scroll, OOB swapping, normal swapping and settling
@@ -1858,7 +1892,7 @@ var htmx = (function() {
* @param {HtmxSwapSpecification} swapSpec
* @param {SwapOptions} [swapOptions]
*/
- function swap(target, content, swapSpec, swapOptions) {
+ function runSwap(target, content, swapSpec, swapOptions) {
if (!swapOptions) {
swapOptions = {}
}
@@ -4168,7 +4202,7 @@ var htmx = (function() {
}
const target = etc.targetOverride || asElement(getTarget(elt))
if (target == null || target == DUMMY_ELT) {
- triggerErrorEvent(elt, 'htmx:targetError', { target: getAttributeValue(elt, 'hx-target') })
+ triggerErrorEvent(elt, 'htmx:targetError', { target: getClosestAttributeValue(elt, 'hx-target') })
maybeCall(reject)
return promise
}
@@ -4790,8 +4824,6 @@ var htmx = (function() {
swapSpec.ignoreTitle = ignoreTitle
}
- target.classList.add(htmx.config.swappingClass)
-
// optional transition API promise callbacks
let settleResolve = null
let settleReject = null
@@ -4822,7 +4854,7 @@ var htmx = (function() {
}
swap(target, serverResponse, swapSpec, {
- select: selectOverride || select,
+ select: selectOverride === 'unset' ? null : selectOverride || select,
selectOOB,
eventInfo: responseInfo,
anchor: responseInfo.pathInfo.anchor,
@@ -4878,12 +4910,7 @@ var htmx = (function() {
})
}
}
-
- if (swapSpec.swapDelay > 0) {
- getWindow().setTimeout(doSwap, swapSpec.swapDelay)
- } else {
- doSwap()
- }
+ doSwap()
}
if (isError) {
triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({ error: 'Response Status Error Code ' + xhr.status + ' from ' + responseInfo.pathInfo.requestPath }, responseInfo))
diff --git a/test/attributes/hx-include.js b/test/attributes/hx-include.js
index d3b5bd1d..3617c130 100644
--- a/test/attributes/hx-include.js
+++ b/test/attributes/hx-include.js
@@ -434,4 +434,88 @@ describe('hx-include attribute', function() {
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})
+
+ it('`inherit` can be used to expand parent hx-include', function() {
+ this.server.respondWith('POST', '/include', function(xhr) {
+ var params = getParameters(xhr)
+ params.i1.should.equal('test1')
+ params.i2.should.equal('test2')
+ xhr.respond(200, {}, 'Clicked!')
+ })
+ make('
' +
+ ' ' +
+ '
' +
+ '' +
+ '')
+ var btn = byId('btn')
+ btn.click()
+ this.server.respond()
+ btn.innerHTML.should.equal('Clicked!')
+ })
+
+ it('`inherit` can be used to expand multiple parents hx-include', function() {
+ this.server.respondWith('POST', '/include', function(xhr) {
+ var params = getParameters(xhr)
+ params.i1.should.equal('test1')
+ params.i2.should.equal('test2')
+ params.i3.should.equal('test3')
+ xhr.respond(200, {}, 'Clicked!')
+ })
+ make('' +
+ '
' +
+ ' ' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '')
+ var btn = byId('btn')
+ btn.click()
+ this.server.respond()
+ btn.innerHTML.should.equal('Clicked!')
+ })
+
+ it('`inherit` chain breaks properly', function() {
+ this.server.respondWith('POST', '/include', function(xhr) {
+ var params = getParameters(xhr)
+ should.not.exist(params.i1)
+ params.i2.should.equal('test2')
+ params.i3.should.equal('test3')
+ xhr.respond(200, {}, 'Clicked!')
+ })
+ make('' +
+ '
' +
+ ' ' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '')
+ var btn = byId('btn')
+ btn.click()
+ this.server.respond()
+ btn.innerHTML.should.equal('Clicked!')
+ })
+
+ it('`inherit` syntax regex properly catches keyword', function() {
+ this.server.respondWith('POST', '/include', function(xhr) {
+ var params = getParameters(xhr)
+ params.i1.should.equal('test1')
+ params.i2.should.equal('test2')
+ params.i3.should.equal('test3')
+ xhr.respond(200, {}, 'Clicked!')
+ })
+ make('' +
+ '
' +
+ ' ' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '')
+ var btn = byId('btn')
+ btn.click()
+ this.server.respond()
+ btn.innerHTML.should.equal('Clicked!')
+ })
})
diff --git a/test/attributes/hx-indicator.js b/test/attributes/hx-indicator.js
index 2d5afc12..5e4e3e5f 100644
--- a/test/attributes/hx-indicator.js
+++ b/test/attributes/hx-indicator.js
@@ -123,4 +123,68 @@ describe('hx-indicator attribute', function() {
b2.classList.contains('htmx-request').should.equal(false)
a1.classList.contains('htmx-request').should.equal(false)
})
+
+ it('`inherit` can be used to expand parent hx-indicator', function() {
+ this.server.respondWith('GET', '/test', 'Clicked!')
+ make('' +
+ ' ' +
+ '
')
+ var btn = byId('btn')
+ var a1 = make('')
+ var a2 = make('')
+ btn.click()
+ btn.classList.contains('htmx-request').should.equal(false)
+ a1.classList.contains('htmx-request').should.equal(true)
+ a2.classList.contains('htmx-request').should.equal(true)
+ this.server.respond()
+ btn.classList.contains('htmx-request').should.equal(false)
+ a1.classList.contains('htmx-request').should.equal(false)
+ a2.classList.contains('htmx-request').should.equal(false)
+ })
+
+ it('`inherit` can be used to expand multiple parents hx-indicator', function() {
+ this.server.respondWith('GET', '/test', 'Clicked!')
+ make('' +
+ '
' +
+ ' ' +
+ '
' +
+ '
')
+ var btn = byId('btn')
+ var a1 = make('')
+ var a2 = make('')
+ var a3 = make('')
+ btn.click()
+ btn.classList.contains('htmx-request').should.equal(false)
+ a1.classList.contains('htmx-request').should.equal(true)
+ a2.classList.contains('htmx-request').should.equal(true)
+ a3.classList.contains('htmx-request').should.equal(true)
+ this.server.respond()
+ btn.classList.contains('htmx-request').should.equal(false)
+ a1.classList.contains('htmx-request').should.equal(false)
+ a2.classList.contains('htmx-request').should.equal(false)
+ a3.classList.contains('htmx-request').should.equal(false)
+ })
+
+ it('`inherit` chain breaks properly', function() {
+ this.server.respondWith('GET', '/test', 'Clicked!')
+ make('' +
+ '
' +
+ ' ' +
+ '
' +
+ '
')
+ var btn = byId('btn')
+ var a1 = make('')
+ var a2 = make('')
+ var a3 = make('')
+ btn.click()
+ btn.classList.contains('htmx-request').should.equal(false)
+ a1.classList.contains('htmx-request').should.equal(false)
+ a2.classList.contains('htmx-request').should.equal(true)
+ a3.classList.contains('htmx-request').should.equal(true)
+ this.server.respond()
+ btn.classList.contains('htmx-request').should.equal(false)
+ a1.classList.contains('htmx-request').should.equal(false)
+ a2.classList.contains('htmx-request').should.equal(false)
+ a3.classList.contains('htmx-request').should.equal(false)
+ })
})
diff --git a/test/core/api.js b/test/core/api.js
index 707f262d..f566b8b1 100644
--- a/test/core/api.js
+++ b/test/core/api.js
@@ -483,6 +483,17 @@ describe('Core htmx API test', function() {
output.innerHTML.should.be.equal('Swapped!
')
})
+ it('swap works with a swap delay', function(done) {
+ var div = make("")
+ div.innerText.should.equal('')
+ htmx.swap(div, 'jsswapped', { swapDelay: 10 })
+ div.innerText.should.equal('')
+ setTimeout(function() {
+ div.innerText.should.equal('jsswapped')
+ done()
+ }, 30)
+ })
+
it('swaps content properly (with select)', function() {
var output = make('')
htmx.swap('#output', '', { swapStyle: 'innerHTML' }, { select: '#select-me' })
diff --git a/test/core/events.js b/test/core/events.js
index bd3eb7d7..c220fabd 100644
--- a/test/core/events.js
+++ b/test/core/events.js
@@ -738,4 +738,36 @@ describe('Core htmx Events', function() {
htmx.off('htmx:afterSwap', afterSwapHandler)
}
})
+
+ it('htmx:targetError should include the hx-target value', function() {
+ var target = null
+ var handler = htmx.on('htmx:targetError', function(evt) {
+ target = evt.detail.target
+ })
+ try {
+ this.server.respondWith('GET', '/test', '')
+ var div = make('')
+ div.click()
+ this.server.respond()
+ target.should.equal('#non-existent')
+ } finally {
+ htmx.off('htmx:targetError', handler)
+ }
+ })
+
+ it('htmx:targetError can include an inherited hx-target value', function() {
+ var target = null
+ var handler = htmx.on('htmx:targetError', function(evt) {
+ target = evt.detail.target
+ })
+ try {
+ this.server.respondWith('GET', '/test', '')
+ make('')
+ byId('child').click()
+ this.server.respond()
+ target.should.equal('#parent-target')
+ } finally {
+ htmx.off('htmx:targetError', handler)
+ }
+ })
})
diff --git a/test/core/headers.js b/test/core/headers.js
index dd500ae1..466f2f8f 100644
--- a/test/core/headers.js
+++ b/test/core/headers.js
@@ -299,6 +299,16 @@ describe('Core htmx AJAX headers', function() {
div.innerHTML.should.equal('bar
')
})
+ it('should handle HX-Reselect unset', function() {
+ this.server.respondWith('GET', '/test', [200, { 'HX-Reselect': 'unset' }, 'bar'])
+
+ var div = make('')
+ div.click()
+ this.server.respond()
+
+ div.innerHTML.should.equal('bar')
+ })
+
it('should handle simple string HX-Trigger-After-Swap response header properly w/ outerHTML swap', function() {
this.server.respondWith('GET', '/test', [200, { 'HX-Trigger-After-Swap': 'foo' }, ''])