update_block_text_content() finds the first tag in innerHTML, keeps its name and attributes, and replaces everything inside with the new text.
That's correct when the first tag is the text container, like a paragraph or heading, but wrong whenever it's a block like core/button which renders a wrapping div surrounding the a tag. Calling replace_text() on a button to set the text of the link returns <div class="wp-block-button">Link Text</div> with no nested <a>. Also true for core accordion headings and pullquotes; and anything after the first tag's closing tag is dropped as well, so it also can't be safely used for sequential sibling blocks.
This doesn't throw an error so it's only visible when you see a validation warning in the editor.
We could probably fix by calling WP_HTML_Tag_Processor::set_modifiable_text() on the first #text token instead of rebuilding the tag wholesale. That said, it looks like set_modifiable_text() escapes and replace_text() doesn't, so anything that passes esc_html()-wrapped values would start double-escaping.
Therefore, a question: would we want to introduce a new method like replace_inner_text() (and formally document replace_text() as "first tag only, no escaping"), or are we OK with a breaking API change?
update_block_text_content() finds the first tag in innerHTML, keeps its name and attributes, and replaces everything inside with the new text.
That's correct when the first tag is the text container, like a paragraph or heading, but wrong whenever it's a block like
core/buttonwhich renders a wrappingdivsurrounding theatag. Callingreplace_text()on a button to set the text of the link returns<div class="wp-block-button">Link Text</div>with no nested<a>. Also true for core accordion headings and pullquotes; and anything after the first tag's closing tag is dropped as well, so it also can't be safely used for sequential sibling blocks.This doesn't throw an error so it's only visible when you see a validation warning in the editor.
We could probably fix by calling
WP_HTML_Tag_Processor::set_modifiable_text()on the first#texttoken instead of rebuilding the tag wholesale. That said, it looks likeset_modifiable_text()escapes andreplace_text()doesn't, so anything that passesesc_html()-wrapped values would start double-escaping.Therefore, a question: would we want to introduce a new method like
replace_inner_text()(and formally documentreplace_text()as "first tag only, no escaping"), or are we OK with a breaking API change?