Blocks V3 inline text editing: the caret jumps to the start of the field after the preview re-renders

Open
#574 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

  • #575 by @wojtekpiskorz — closed without merging

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
52/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, php, react, wordpress

Research direction

Start with assets/src/js/pro/blocks-v3/components/block-edit.js and jsx-parser.js, then trace how the fetched preview is rendered through acf.parseJSX(). The reproduction and selection offsets show the caret resets after React commits the updated text. Confirm the fix by repeating the Playwright steps and checking that typing after the preview fetch preserves the caret and selection.

Written by the indexing model from the issue text.

Description

Environment

  • Secure Custom Fields 6.9.7, and trunk at 31ead85
  • WordPress 7.1.3 (wp-env), React 18.3.1 (the copy WordPress ships)
  • Chromium (Playwright 1.56). The cause is in the DOM spec, so other browsers should behave the same. Only Chromium was tested.

Steps to reproduce

  1. Register a Blocks V3 block whose template puts acf_inline_text_editing_attrs() on an element. Minimal block (the field group is one text field named title, location "Block is equal to acf/caret-repro"):

    block.json

    {
    	"apiVersion": 3,
    	"name": "acf/caret-repro",
    	"title": "Caret repro",
    	"category": "text",
    	"acf": { "blockVersion": 3, "mode": "preview", "renderTemplate": "render.php" }
    }
    

    render.php

    <h2 <?php echo acf_inline_text_editing_attrs( 'title' ); ?>><?php echo esc_html( get_field( 'title' ) ); ?></h2>
    
  2. Add the block to a post and give the title a value, e.g. Accessible.

  3. Click into the title in the editor canvas, press End and type AAA.

  4. Wait about 1 s, until the block preview has been fetched again (acf/ajax/fetch-block).

  5. Type BBB.

Expected

The title reads AccessibleAAABBB. The caret stays where you are typing.

Actual

The title reads BBBAccessibleAAA. When the new preview renders, the caret moves to offset 0 of the element, so the next keystrokes land at the start. This happens after every preview re-render: typing in the middle of the text, in a second inline field of the same block, or after emptying the field. A selected range is lost the same way.

Output of a Playwright script that does steps 3 to 5 and reads getSelection() in the canvas:

before         "Accessible" { offset: 10 }
typed          "AccessibleAAA" { offset: 13 }
after preview  "AccessibleAAA" { offset: 0 }
final          "BBBAccessibleAAA" { offset: 0 }

Cause

The browser edits the text of the contentEditable element directly, so React's virtual DOM still holds the text of the previous preview. Typing updates the field (the MutationObserver in BlockEditInner calls updateFieldValueFromContentEditable(), assets/src/js/pro/blocks-v3/components/block-edit.js lines 984 and 1025). After the 200 ms debounce (line 622), the block fetches the preview again, and setBlockPreviewHtml() (line 372) renders it through acf.parseJSX() (line 1496). The parser gives the element its text as a plain string child (jsx-parser.js lines 314 and 372 to 375).

The new string (AccessibleAAA) differs from the string React rendered last time (Accessible). So when React commits, it writes it into the text node, even though the DOM already shows that text. Setting nodeValue runs the DOM's "replace data" algorithm over the whole node, and that moves every selection boundary inside the node to offset 0.

Tracing the nodeValue setter in the canvas shows this is the only write, and it comes from React's commit:

Node.nodeValue = "AccessibleAAA"  caret 13 -> 0
    at Fc (wp-includes/js/dist/vendor/react-dom.min.js?ver=18.3.1.1:220:407)
    at Fi (wp-includes/js/dist/vendor/react-dom.min.js?ver=18.3.1.1:173:141)
    at Aa (wp-includes/js/dist/vendor/react-dom.min.js?ver=18.3.1.1:171:382)
    ...

The Blocks V3 sources say they are based on ACF PRO 6.7.0.2. The ACF changelog up to 6.8.10 lists no fix for this, so ACF PRO is probably affected too. That has not been checked against ACF PRO itself.

Dominant language
PHP
Stars
136
Forks
73
Avg merge
18h 21m
Merged PRs (30d)
21

Getting set up

  • No Dockerfile or Docker Compose file
  • Has a pull request template
  • No contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from WordPress/secure-custom-fields

All issues in WordPress/secure-custom-fields

Similar issues

More PHP issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.