Skip to content

fix: treat contenteditable="plaintext-only" as editable - #1330

Open
cpruijsen wants to merge 3 commits into
testing-library:mainfrom
cpruijsen:fix/issue-1197
Open

cpruijsen wants to merge 3 commits into
testing-library:mainfrom
cpruijsen:fix/issue-1197

Conversation

@cpruijsen

@cpruijsen cpruijsen commented Sep 11, 2026 •

Copy link
Copy Markdown

What

Treat contenteditable="plaintext-only" as an editing host so user.type() and the other edit/focus paths that share the same checks insert text the same way they already do for contenteditable="true" / contenteditable.

Fixes #1197.

Why

The HTML spec lists plaintext-only as a contenteditable keyword. An editing host is an element in the true or plaintext-only state. jsdom does not implement HTMLElement.isContentEditable, and this library's polyfill only treated true and the empty attribute as editable, so type() clicked a non-focusable node and never dispatched input.

How

  • Extend isContentEditable() with the plaintext-only attribute value.
  • Include [contenteditable="plaintext-only"] in FOCUSABLE_SELECTOR so click/tab focus the host.
  • Resolve the editing host in getContentEditable() by walking ancestors with isContentEditable() so the allow-list lives in one place.

Walk with isContentEditable instead of adding a third element.closest('[contenteditable="plaintext-only"]'). Chained closest() returned a farther ancestor instead of the nearest editing host, which affected contenteditable="true" as well as plaintext-only. Both are covered by tests.

Enter on plaintext-only reports insertLineBreak. A plaintext-only host has no paragraphs to break, and editContenteditable inserts \n for either inputType, so this corrects the reported type rather than the content.

Checklist

  • [N/A] Documentation (no API change)
  • Tests
  • Ready to be merged

Browsers treat plaintext-only as an editing host. The jsdom polyfill only
recognized true/empty, so type() never inserted text.

@snowystinger snowystinger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, can you provide a test for

Chained closest() can return a farther true ancestor instead of a nearer plaintext-only host

I imagine it'd be something like this setup

<div contenteditable="">
  <div contenteditable="true">
    <span>

It'd also be good to include a paste test for "plaintext-only".

Enter on plaintext-only still uses insertParagraph (same as contenteditable="true"). No plaintext-only special case for insertLineBreak. Can add that as a follow-up.

I don't think this should be follow-up, code will start going through here as soon as it's released, so we should have this complete before releasing. I can take a look later if you don't have time.

Add the nearest-editing-host cases for the ancestor walk that replaced the
chained closest() calls. The contenteditable="true" case fails on the old
implementation too: closest('[contenteditable=""]') matched a farther
ancestor before the nearer host was considered.

Add a paste test covering contenteditable="true" and "plaintext-only".
A plaintext-only editing host has no paragraphs to break, so Enter is a
line break. editContenteditable inserts "\n" for either inputType, so
this corrects the reported type rather than the resulting content.
@cpruijsen

Copy link
Copy Markdown
Author

Pushed the tests and the Enter change in 1c33ed9.

The chained closest() case fails on contenteditable="true" too, not only plaintext-only: closest('[contenteditable=""]') matched a farther ancestor before the nearer host was considered. The test covers both values.

For Enter I went with insertLineBreak on plaintext-only rather than asking which browser to follow. A plaintext-only host has no paragraphs to break, and editContenteditable inserts \n for either inputType, so this corrects the reported type rather than the resulting content.

@snowystinger

Copy link
Copy Markdown
Contributor

For Enter I went with insertLineBreak on plaintext-only rather than asking which browser to follow

Ah, didn't realise it was a difference between browsers. Does the spec say anything more official? The other way would've tried to insert a <br />? that shouldn't be a difference between browsers.
What would be the followup here?
Thanks for your patience with my questions, just trying to make sure I understand what's going on here.

This branch has not been deployed

No deployments
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.

type does not work with contenteditable="plaintext-only"

2 participants