mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-03 15:55:39 +00:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
8ec48d9d29
49
src/htmx.js
49
src/htmx.js
@ -1347,6 +1347,16 @@ var htmx = (function() {
|
|||||||
return [findThisElement(elt, attrName)]
|
return [findThisElement(elt, attrName)]
|
||||||
} else {
|
} else {
|
||||||
const result = querySelectorAllExt(elt, attrTarget)
|
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) {
|
if (result.length === 0) {
|
||||||
logError('The selector "' + attrTarget + '" on ' + attrName + ' returned no matches!')
|
logError('The selector "' + attrTarget + '" on ' + attrName + ' returned no matches!')
|
||||||
return [DUMMY_ELT]
|
return [DUMMY_ELT]
|
||||||
@ -1850,6 +1860,30 @@ var htmx = (function() {
|
|||||||
return oobElts.length > 0
|
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,
|
* Implements complete swapping pipeline, including: focus and selection preservation,
|
||||||
* title updates, scroll, OOB swapping, normal swapping and settling
|
* title updates, scroll, OOB swapping, normal swapping and settling
|
||||||
@ -1858,7 +1892,7 @@ var htmx = (function() {
|
|||||||
* @param {HtmxSwapSpecification} swapSpec
|
* @param {HtmxSwapSpecification} swapSpec
|
||||||
* @param {SwapOptions} [swapOptions]
|
* @param {SwapOptions} [swapOptions]
|
||||||
*/
|
*/
|
||||||
function swap(target, content, swapSpec, swapOptions) {
|
function runSwap(target, content, swapSpec, swapOptions) {
|
||||||
if (!swapOptions) {
|
if (!swapOptions) {
|
||||||
swapOptions = {}
|
swapOptions = {}
|
||||||
}
|
}
|
||||||
@ -4168,7 +4202,7 @@ var htmx = (function() {
|
|||||||
}
|
}
|
||||||
const target = etc.targetOverride || asElement(getTarget(elt))
|
const target = etc.targetOverride || asElement(getTarget(elt))
|
||||||
if (target == null || target == DUMMY_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)
|
maybeCall(reject)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
@ -4790,8 +4824,6 @@ var htmx = (function() {
|
|||||||
swapSpec.ignoreTitle = ignoreTitle
|
swapSpec.ignoreTitle = ignoreTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
target.classList.add(htmx.config.swappingClass)
|
|
||||||
|
|
||||||
// optional transition API promise callbacks
|
// optional transition API promise callbacks
|
||||||
let settleResolve = null
|
let settleResolve = null
|
||||||
let settleReject = null
|
let settleReject = null
|
||||||
@ -4822,7 +4854,7 @@ var htmx = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swap(target, serverResponse, swapSpec, {
|
swap(target, serverResponse, swapSpec, {
|
||||||
select: selectOverride || select,
|
select: selectOverride === 'unset' ? null : selectOverride || select,
|
||||||
selectOOB,
|
selectOOB,
|
||||||
eventInfo: responseInfo,
|
eventInfo: responseInfo,
|
||||||
anchor: responseInfo.pathInfo.anchor,
|
anchor: responseInfo.pathInfo.anchor,
|
||||||
@ -4878,12 +4910,7 @@ var htmx = (function() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
doSwap()
|
||||||
if (swapSpec.swapDelay > 0) {
|
|
||||||
getWindow().setTimeout(doSwap, swapSpec.swapDelay)
|
|
||||||
} else {
|
|
||||||
doSwap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isError) {
|
if (isError) {
|
||||||
triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({ error: 'Response Status Error Code ' + xhr.status + ' from ' + responseInfo.pathInfo.requestPath }, responseInfo))
|
triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({ error: 'Response Status Error Code ' + xhr.status + ' from ' + responseInfo.pathInfo.requestPath }, responseInfo))
|
||||||
|
@ -434,4 +434,88 @@ describe('hx-include attribute', function() {
|
|||||||
this.server.respond()
|
this.server.respond()
|
||||||
btn.innerHTML.should.equal('Clicked!')
|
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('<div hx-include="#i1">' +
|
||||||
|
' <button id="btn" hx-include="inherit, #i2" hx-post="/include"></button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<input id="i1" name="i1" value="test1"/>' +
|
||||||
|
'<input id="i2" name="i2" value="test2"/>')
|
||||||
|
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('<div hx-include="#i1">' +
|
||||||
|
' <div hx-include="inherit, #i2">' +
|
||||||
|
' <button id="btn" hx-include="inherit, #i3" hx-post="/include"></button>' +
|
||||||
|
' </div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<input id="i1" name="i1" value="test1"/>' +
|
||||||
|
'<input id="i2" name="i2" value="test2"/>' +
|
||||||
|
'<input id="i3" name="i3" value="test3"/>')
|
||||||
|
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('<div hx-include="#i1">' +
|
||||||
|
' <div hx-include="#i2">' +
|
||||||
|
' <button id="btn" hx-include="inherit, #i3" hx-post="/include"></button>' +
|
||||||
|
' </div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<input id="i1" name="i1" value="test1"/>' +
|
||||||
|
'<input id="i2" name="i2" value="test2"/>' +
|
||||||
|
'<input id="i3" name="i3" value="test3"/>')
|
||||||
|
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('<div hx-include="#i1">' +
|
||||||
|
' <div hx-include="#i2, inherit,.nonexistent-class">' +
|
||||||
|
' <button id="btn" hx-include="customtag,inherit , #i3" hx-post="/include"></button>' +
|
||||||
|
' </div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<input id="i1" name="i1" value="test1"/>' +
|
||||||
|
'<input id="i2" name="i2" value="test2"/>' +
|
||||||
|
'<input id="i3" name="i3" value="test3"/>')
|
||||||
|
var btn = byId('btn')
|
||||||
|
btn.click()
|
||||||
|
this.server.respond()
|
||||||
|
btn.innerHTML.should.equal('Clicked!')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -123,4 +123,68 @@ describe('hx-indicator attribute', function() {
|
|||||||
b2.classList.contains('htmx-request').should.equal(false)
|
b2.classList.contains('htmx-request').should.equal(false)
|
||||||
a1.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('<div hx-indicator="#a1">' +
|
||||||
|
' <button id="btn" hx-get="/test" hx-indicator="inherit, #a2">Click Me!</button>' +
|
||||||
|
'</div>')
|
||||||
|
var btn = byId('btn')
|
||||||
|
var a1 = make('<a id="a1"></a>')
|
||||||
|
var a2 = make('<a id="a2"></a>')
|
||||||
|
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('<div hx-indicator="#a1">' +
|
||||||
|
' <div hx-indicator="inherit, #a2">' +
|
||||||
|
' <button id="btn" hx-get="/test" hx-indicator="inherit, #a3">Click Me!</button>' +
|
||||||
|
' </div>' +
|
||||||
|
'</div>')
|
||||||
|
var btn = byId('btn')
|
||||||
|
var a1 = make('<a id="a1"></a>')
|
||||||
|
var a2 = make('<a id="a2"></a>')
|
||||||
|
var a3 = make('<a id="a3"></a>')
|
||||||
|
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('<div hx-indicator="#a1">' +
|
||||||
|
' <div hx-indicator="#a2">' +
|
||||||
|
' <button id="btn" hx-get="/test" hx-indicator="inherit, #a3">Click Me!</button>' +
|
||||||
|
' </div>' +
|
||||||
|
'</div>')
|
||||||
|
var btn = byId('btn')
|
||||||
|
var a1 = make('<a id="a1"></a>')
|
||||||
|
var a2 = make('<a id="a2"></a>')
|
||||||
|
var a3 = make('<a id="a3"></a>')
|
||||||
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -483,6 +483,17 @@ describe('Core htmx API test', function() {
|
|||||||
output.innerHTML.should.be.equal('<div>Swapped!</div>')
|
output.innerHTML.should.be.equal('<div>Swapped!</div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('swap works with a swap delay', function(done) {
|
||||||
|
var div = make("<div hx-get='/test'></div>")
|
||||||
|
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() {
|
it('swaps content properly (with select)', function() {
|
||||||
var output = make('<output id="output"/>')
|
var output = make('<output id="output"/>')
|
||||||
htmx.swap('#output', '<div><p id="select-me">Swapped!</p></div>', { swapStyle: 'innerHTML' }, { select: '#select-me' })
|
htmx.swap('#output', '<div><p id="select-me">Swapped!</p></div>', { swapStyle: 'innerHTML' }, { select: '#select-me' })
|
||||||
|
@ -738,4 +738,36 @@ describe('Core htmx Events', function() {
|
|||||||
htmx.off('htmx:afterSwap', afterSwapHandler)
|
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 hx-post="/test" hx-target="#non-existent"></div>')
|
||||||
|
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('<div hx-target="#parent-target"><div id="child" hx-post="/test"></div></div>')
|
||||||
|
byId('child').click()
|
||||||
|
this.server.respond()
|
||||||
|
target.should.equal('#parent-target')
|
||||||
|
} finally {
|
||||||
|
htmx.off('htmx:targetError', handler)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -299,6 +299,16 @@ describe('Core htmx AJAX headers', function() {
|
|||||||
div.innerHTML.should.equal('<div id="d2">bar</div>')
|
div.innerHTML.should.equal('<div id="d2">bar</div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should handle HX-Reselect unset', function() {
|
||||||
|
this.server.respondWith('GET', '/test', [200, { 'HX-Reselect': 'unset' }, 'bar'])
|
||||||
|
|
||||||
|
var div = make('<div hx-get="/test" hx-select="#d2"></div>')
|
||||||
|
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() {
|
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' }, ''])
|
this.server.respondWith('GET', '/test', [200, { 'HX-Trigger-After-Swap': 'foo' }, ''])
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user