Skip to content

fix: support textContent swap style for hx-swap-oob (#3563)#3768

Open
manwithacat wants to merge 1 commit intobigskysoftware:devfrom
manwithacat:fix/hx-swap-oob-textContent
Open

fix: support textContent swap style for hx-swap-oob (#3563)#3768
manwithacat wants to merge 1 commit intobigskysoftware:devfrom
manwithacat:fix/hx-swap-oob-textContent

Conversation

@manwithacat
Copy link
Copy Markdown
Contributor

Fixes #3563.

The bug

swapWithStyle() (src/htmx.js:1799) has cases for every OOB swap style — none, outerHTML, afterbegin, beforebegin, beforeend, afterend, delete — but no case for textContent. Falling into the default branch eventually recurses with htmx.config.defaultSwapStyle (normally innerHTML), so an hx-swap-oob="textContent" swap silently behaves like an innerHTML swap.

This contradicts the documented textContent contract — hx-swap docs say:

textContent — Replace the text content of the target element, without parsing the response as HTML.

The primary-swap path already handles this correctly at src/htmx.js:1907 (target.textContent = content, before any HTML parsing). The OOB path was the outlier.

The fix

Add a case 'textContent' to swapWithStyle mirroring the primary-swap behavior:

case 'textContent':
  target.textContent = fragment.textContent
  return

For non-inline OOB swaps, oobSwap() sets fragment to the parsed OOB element clone (line 1492), so fragment.textContent is the concatenated text of all descendants — matching the OOB doc rule that "the encapsulating tag pair will be stripped for all strategies other than outerHTML".

3 lines in src/htmx.js.

Tests

Two new cases in test/attributes/hx-swap-oob.js:

  1. handles textContent response properly — verifies <h1>Swapped</h1> inside an OOB element becomes literal text Swapped in the target, not a parsed <h1> element.
  2. handles textContent oob swap with selector — verifies the textContent:#selector form works the same way.

Both fail before the fix (the <h1> is parsed and inserted as an element), pass after.

Full suite: 848 passed, 0 failed, 3 skipped, 99.9% coverage. ESLint clean.

Live before/after demo

https://manwithacat.github.io/htmx/demos/hx-swap-oob-textContent/

Side-by-side iframes load the same base commit (d53932d4) with and without this 3-line patch, using a mock XMLHttpRequest so htmx runs its real lifecycle without needing a server. The buggy panel renders the OOB response's <h1> as a real DOM element; the fixed panel renders it as literal text.

Notes for reviewers

  • I did not add cleanUpElement() calls for removed children before assigning target.textContent. The primary-swap textContent path at src/htmx.js:1908 doesn't either, and adding cleanup only on the OOB side would be inconsistent. Happy to follow up with a separate PR that adds it to both paths if that's wanted.
  • No docs change needed — hx-swap.md already lists textContent and hx-swap-oob.md already says any valid hx-swap value works. The docs were correct; the code was the outlier.

)

swapWithStyle() handled none, outerHTML, afterbegin, beforebegin,
beforeend, afterend, and delete, but had no case for textContent.
Falling through to the default branch caused textContent OOB swaps
to be treated as innerHTML, parsing the response body as HTML
instead of inserting it as literal text.

Mirrors the main-swap textContent handling, which sets
target.textContent directly without parsing as HTML.

Fixes bigskysoftware#3563

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant