Repository navigation
Blocks V3: keep the caret in inline text editing when the preview re-renders - #575
wojtekpiskorz wants to merge 1 commit into
Conversation
…renders Typing into an inline editable field fetches the block preview again. React still holds the previous preview's text for the contentEditable element, so the commit writes the new text into its text node, and setting nodeValue moves the selection inside it to offset 0: the next keystrokes land at the start of the field. Wrap the parsed preview in PreserveContentEditableSelection, which reads the selection of the focused inline editable element of the block as character offsets in getSnapshotBeforeUpdate() and restores it in componentDidUpdate(). Adds Jest tests in block-edit.test.js and an E2E test with a fixture block using acf_inline_text_editing_attrs(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @wojtekpiskorz. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Thanks for the contribution and the effort here! 🙏 A heads-up on how this repo works: a large part of SCF's codebase (field types, Flexible Content, Blocks v3, local fields, forms, value formatting, and most of includes/ and assets/src/js/) is regularly synced from an upstream codebase. Any change we make there, whether a bug fix or a new feature, gets overwritten or causes hard merge conflicts on the next sync. That includes features behind a beta flag. Fixes for those areas arrive through the sync itself, so we can't accept PRs that touch that code, even good ones. |
When you type into an inline editable field (
acf_inline_text_editing_attrs()), the block fetches its preview again. When that preview renders, the caret jumps to the start of the field, so the next keystrokes land there: typingAAA, pausing, then typingBBBat the end ofAccessiblegivesBBBAccessibleAAA.Cause. The browser edits the contentEditable text itself, so React still holds the text of the previous preview. When the new preview commits, React writes the new text into the text node, even though the DOM already shows it. Setting a text node's
nodeValuemoves any selection inside it to offset 0 (DOM "replace data"). The full analysis is in the issue.What changed
PreserveContentEditableSelectioncomponent (assets/src/js/pro/blocks-v3/components/preserve-content-editable-selection.js) wraps the parsed preview inBlockEditInner.getSnapshotBeforeUpdate()), it reads the selection as character offsets, but only when focus is on an inline editable element ([data-acf-inline-contenteditable]) inside this block.componentDidUpdate()), it puts the selection back if that element still has focus.It is a class component because
getSnapshotBeforeUpdate()is the only way to read the DOM after React renders but before it writes; hooks have no equivalent.I considered keeping React from rewriting the focused element, and rejected it. React rewrites any text it manages, so this would mean taking the element's text out of React's control. While it has focus, the element would then not show what the server renders, for example a value a render template transforms (such as
wptexturize()curling quotes).How to test
tests/e2e/plugins/scf-test-inline-editing-block.phpand add the "SCF Inline Editing Block" to a post.AAA, wait about a second, typeBBB. Before this change:BBBHelloAAA. After:HelloAAABBB.Tests
tests/js/blocks-v3/block-edit.test.js, "inline text editing"): three cases (end of text, middle of text, server text shorter than typed). All three fail on trunk with the caret at offset 0 and pass with this change. Full suite: 38 suites, 962 tests pass.tests/e2e/block-v3-inline-editing.spec.ts, new fixture blockscf/inline-editing-block): typing at the end of one field, and in the middle of another. On trunk they fail withBBBHelloAAAandYWoXrld. With this change they pass on WordPress 7.1.3 (five runs in a row), 6.9 and trunk (7.2-beta1). The full E2E suite passes on 7.1.3 (260 tests); only Chromium was run.The Blocks V3 sources are a readable port of ACF PRO's code, so a later ACF backport that replaces
block-edit.jshas to keep the wrapper.Closes #574
The Blocks V3 code is based on ACF PRO 6.7.0.2, and the ACF changelog up to 6.8.10 lists no fix, so ACF PRO probably has the same bug. Not checked against ACF PRO itself.
Use of AI Tools
This PR was written with Claude Code (Claude Opus 5.5): the root-cause analysis, code, tests and this description. I reviewed the changes and take responsibility for them.
🤖 Generated with Claude Code