diff --git a/src/htmx.js b/src/htmx.js index 7ee3b0ea2..d9c9b4877 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -1347,10 +1347,11 @@ var htmx = (() => { } let swapStyle = swapSpec.style; if (swapStyle === 'none') return; - // full-page response: fragment has a
wrapper, so upgrade outerHTML to outerSync, strip for everything else + // Body fragment: strip wrapper unless outer* swap on document.body if (fragment.firstElementChild?.tagName === 'BODY') { - if (swapStyle === 'outerHTML') swapStyle = 'outerSync'; - else if (!swapStyle.startsWith('outer')) swapSpec.strip = true; + const keepBody = target === document.body && swapStyle.startsWith('outer') + if (keepBody && swapStyle === 'outerHTML') swapStyle = 'outerSync' + swapSpec.strip ??= !keepBody } if (swapSpec.strip && fragment.firstElementChild) { fragment = document.createDocumentFragment(); diff --git a/test/tests/attributes/hx-swap.js b/test/tests/attributes/hx-swap.js index 97234d843..93b22b01c 100644 --- a/test/tests/attributes/hx-swap.js +++ b/test/tests/attributes/hx-swap.js @@ -95,4 +95,40 @@ describe('hx-swap modifiers', function() { assert.equal(find('#target').className, 'test') assert.equal(find('#target').textContent, 'New Text') }) + + it('outerHTML with body fragment on non-body target strips body wrapper', async function () { + mockResponse('GET', '/test', 'New content
') + createProcessedHTML(', body wrapper stripped + assert.isUndefined(find('#target'), 'Target should be replaced') + let p = find('p') + assert.exists(p) + assert.equal(p.textContent, 'New content') + assert.equal(p.parentElement.id, 'test-playground') + }) + + it('outerHTML with full HTML document on non-body target strips body wrapper', async function () { + mockResponse('GET', '/test', '