[lexical-utils][lexical-playground] Bug Fix: Double paragraph creation when exiting a nested code block#8684
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
potatowagon
left a comment
There was a problem hiding this comment.
Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.
Assessment: LGTM ✅ — Bug fix in @lexical/utils preventing double paragraph creation when arrow-key escaping from nested code blocks (e.g. code block inside a layout column).
What I checked:
-
Core fix (
packages/lexical-utils/src/index.ts): Removes.selectEnd()from both$onEscapeUpand$onEscapeDownhelpers. Previously,containerNode.insertBefore($createParagraphNode()).selectEnd()was creating a paragraph AND moving selection to it. But the calling code (e.g. layout column's escape handler) would ALSO create/select a paragraph — resulting in double paragraphs. The fix returnstrue(event consumed) without moving selection, letting the caller handle cursor placement. This is correct becauseinsertBefore/insertAfterreturns the inserted node, and thetruereturn signals the command was handled. -
Behavioral safety: The functions return
truewhich stops command propagation. The parent context (layout, collapsible, etc.) that calls these utils is responsible for cursor placement. Removing.selectEnd()is safe because the callers already handle selection after the escape — the double-creation was the bug. -
Test coverage:
- New e2e test in
CodeBlock.spec.mjs(116 lines): Creates a layout with 2 columns, inserts a code block in column 1, presses ArrowRight/ArrowDown at end, asserts a single paragraph is created inside the layout item (not outside). Tests both arrow keys. Properly skips Firefox (known layout interaction issues) and collab mode. - Existing test in
ColumnLayoutBackspaceAtEnd.spec.mjsupdated: adds Firefox skip (matching the new test), minor title text fix (keys→key).
- New e2e test in
-
Edge cases considered:
- ArrowRight at end of code = escape down ✓
- ArrowDown at last line of code = escape down ✓
- Both directions (up/down) fixed symmetrically ✓
- Layout items with multiple children won't be affected (guard checks
firstDescendant/lastDescendant)
-
www compat:
$onEscapeUp/$onEscapeDownare exported from@lexical/utils. The behavioral change (no longer auto-selecting the new paragraph) could theoretically affect www consumers that relied on the selection side-effect. However, since the function returnstrueand callers are expected to handle post-escape behavior themselves, this is a bug fix, not a breaking change. Any www consumer that was seeing double paragraphs will benefit. -
CI: ALL GREEN — unit 22.x ✅, unit 24.x ✅, browser ✅, integrity ✅, e2e canary chromium ✅, CLA ✅, Vercel ✅.
Ready to approve.
etrepum
left a comment
There was a problem hiding this comment.
The change looks fine but I don't see why we should be skipping firefox in the tests
In these tests, Firefox Playwright behaves differently than the real Firefox #8393 (comment) Unfortunately, I don't understand why this is, so I added selectEnd() in the original PR. Now, if I remove selectEnd and don't skip Firefox, the tests will fail |
…browsers The previous fix removed selectEnd() and relied on the browser's native arrow-key caret movement to land in the inserted paragraph. That movement is not portable: Chromium moves into the freshly inserted paragraph while Firefox leaves the caret inside the container, which is why the e2e tests were skipped on Firefox. Restore selectEnd() so the selection is placed explicitly, and preventDefault() the triggering keyboard event so the browser does not additionally move the selection (which otherwise overshoots into the adjacent layout column). The escape handlers now position the caret identically on Chromium, Firefox and WebKit. Thread the keyboard event through the code-block and layout arrow-key handlers and un-skip Firefox in the affected tests.
Description
Follow-up #8393
When inserting new paragraphs, calls to
selectEndwere added to the$onEscapeDownand$onEscapeUphandlers as a workaround to pass the tests in Firefox #8393 (comment). This resulted in an undesirable effect where, in a nested setup, the selection moved past the inserted paragraph and out of its container (which also has$onEscapeUp/$onEscapeDownhandlers).The root cause is that the handlers inserted a paragraph and then relied on the browser's native arrow-key caret movement to land in it, but that movement is not portable:
selectEnd, Chromium moves into the freshly inserted paragraph while Firefox leaves the caret inside the container (hence the Firefox skips);selectEndbut nopreventDefault, the native movement fires on top ofselectEndand overshoots into the adjacent container/column.Instead of skipping Firefox, this makes the handlers fully own the behavior: when a paragraph is inserted we set the selection explicitly with
selectEnd()and callevent.preventDefault()so the browser does not additionally move the caret. The keyboard event is now threaded through the code-block and layout arrow-key handlers for this purpose. The caret now lands identically on Chromium, Firefox and WebKit, so the Firefox skips are removed.Test plan
Added e2e test for a nested code block inside LayoutContainer. The new test and the existing
ColumnLayoutBackspaceAtEndarrow-key tests now pass on Chromium, Firefox and WebKit (no Firefox skip).Before
Screen.Recording.2026-06-12.at.03.21.25.mov
After
Screen.Recording.2026-06-12.at.03.20.28.mov