trigger htmx:before:request and htmx:after:request on target element, added events.js for testing events

fixes https://github.com/bigskysoftware/htmx/issues/3628
This commit is contained in:
Carson Gross
2026-01-20 14:25:54 -07:00
parent 54bf849fc9
commit 18f936d811
4 changed files with 96 additions and 4 deletions

View File

@@ -1300,7 +1300,7 @@ var htmx = (() => {
tasks.unshift(mainSwap);
}
if(!this.__trigger(document, "htmx:before:swap", {ctx, tasks})){
if(!this.__trigger(ctx.sourceElement, "htmx:before:swap", {ctx, tasks})){
return
}
@@ -1326,7 +1326,7 @@ var htmx = (() => {
await Promise.all(swapPromises);
this.__trigger(document, "htmx:after:swap", {ctx});
this.__trigger(ctx.sourceElement, "htmx:after:swap", {ctx});
if (ctx.title && !mainSwap?.swapSpec?.ignoreTitle) document.title = ctx.title;
this.__handleAnchorScroll(ctx);
}
@@ -1573,7 +1573,7 @@ var htmx = (() => {
composed: true,
originalTarget: on
});
let target = on.isConnected ? on : document;
let target = on?.isConnected ? on : document;
let result = !detail.cancelled && target.dispatchEvent(evt);
return result
}

View File

@@ -83,7 +83,7 @@
<!-- Test helpers -->
<script src="./lib/helpers.js"></script>
<script src="./tests/attributes/hx-sync.js"></script>
<script src="./tests/end2end/events.js"></script>
<script>
// Run tests on load

View File

@@ -167,6 +167,7 @@
<script src="./tests/end2end/basic-history.js"></script>
<script src="./tests/end2end/cancel-behavior.js"></script>
<script src="./tests/end2end/core.js"></script>
<script src="./tests/end2end/events.js"></script>
<script src="./tests/end2end/oob.js"></script>
<script src="./tests/end2end/sse.js"></script>
<script src="./tests/end2end/strip.js"></script>

View File

@@ -0,0 +1,91 @@
describe('htmx events', function() {
beforeEach(function() {
setupTest();
});
afterEach(function() {
cleanupTest();
});
it('htmx:before:request fires on sourceElement', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let firedOnSource = false
div.addEventListener('htmx:before:request', () => firedOnSource = true)
div.click();
await forRequest()
assert.isTrue(firedOnSource)
})
it('htmx:after:request fires on sourceElement', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let firedOnSource = false
div.addEventListener('htmx:after:request', () => firedOnSource = true)
div.click()
await forRequest()
assert.isTrue(firedOnSource)
})
it('htmx:before:swap fires on sourceElement', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test"></div>')
let firedOnSource = false
div.addEventListener('htmx:before:swap', () => firedOnSource = true)
div.click()
await forRequest()
assert.isTrue(firedOnSource)
})
it('htmx:after:swap fires on sourceElement', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test"></div>')
let firedOnSource = false
div.addEventListener('htmx:after:swap', () => firedOnSource = true)
div.click()
await forRequest()
assert.isTrue(firedOnSource)
})
it('htmx:after:swap does not fire on element removed from DOM by swap', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test" hx-swap="outerHTML"></div>')
let firedOnSource = false
div.addEventListener('htmx:after:swap', () => firedOnSource = true)
div.click()
await forRequest()
assert.isFalse(firedOnSource)
})
it('htmx:after:swap triggers on document when element is removed from DOM by swap', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test" hx-swap="outerHTML"></div>')
let firedOnDocument = false
document.addEventListener('htmx:after:swap', (e) => {
if (e.target === document) {
firedOnDocument = true
}
}, {once: true})
div.click()
await forRequest()
assert.isTrue(firedOnDocument)
})
it('events bubble from sourceElement to document', async function () {
mockResponse('GET', '/test', 'Response')
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let bubbledToDocument = false
document.addEventListener('htmx:before:request', (e) => {
if (e.target === div) {
bubbledToDocument = true
}
}, {once: true})
div.click()
await forRequest()
assert.isTrue(bubbledToDocument)
})
// TODO - verify shape of details in these events
})