Skip to content

[lexical-utils][lexical-playground] Bug Fix: Double paragraph creation when exiting a nested code block#8684

Merged
etrepum merged 2 commits into
facebook:mainfrom
levensta:remove-redudant-selectend
Jun 12, 2026
Merged

[lexical-utils][lexical-playground] Bug Fix: Double paragraph creation when exiting a nested code block#8684
etrepum merged 2 commits into
facebook:mainfrom
levensta:remove-redudant-selectend

Conversation

@levensta

@levensta levensta commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up #8393

When inserting new paragraphs, calls to selectEnd were added to the $onEscapeDown and $onEscapeUp handlers 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/$onEscapeDown handlers).

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:

  • without selectEnd, Chromium moves into the freshly inserted paragraph while Firefox leaves the caret inside the container (hence the Firefox skips);
  • with selectEnd but no preventDefault, the native movement fires on top of selectEnd and 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 call event.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 ColumnLayoutBackspaceAtEnd arrow-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
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 12, 2026
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 12, 2026 4:56pm
lexical-playground Ready Ready Preview, Comment Jun 12, 2026 4:56pm

Request Review

@levensta levensta changed the title fix redudant selectEnd Jun 12, 2026
@levensta levensta changed the title [lexical-utils][lexical-playground] Bug Fix: Fix redundant paragraph creation when exiting a nested code block Jun 12, 2026
@levensta levensta changed the title [lexical-utils][lexical-playground] Bug Fix: Redundant paragraph creation when exiting a nested code block Jun 12, 2026

@potatowagon potatowagon 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.

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:

  1. Core fix (packages/lexical-utils/src/index.ts): Removes .selectEnd() from both $onEscapeUp and $onEscapeDown helpers. 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 returns true (event consumed) without moving selection, letting the caller handle cursor placement. This is correct because insertBefore/insertAfter returns the inserted node, and the true return signals the command was handled.

  2. Behavioral safety: The functions return true which 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.

  3. 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.mjs updated: adds Firefox skip (matching the new test), minor title text fix (keyskey).
  4. 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)
  5. www compat: $onEscapeUp/$onEscapeDown are 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 returns true and 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.

  6. CI: ALL GREEN — unit 22.x ✅, unit 24.x ✅, browser ✅, integrity ✅, e2e canary chromium ✅, CLA ✅, Vercel ✅.

Ready to approve.

@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Jun 12, 2026

@etrepum etrepum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The change looks fine but I don't see why we should be skipping firefox in the tests

Comment thread packages/lexical-playground/__tests__/e2e/ColumnLayoutBackspaceAtEnd.spec.mjs Outdated
@levensta

Copy link
Copy Markdown
Contributor Author

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.
@etrepum etrepum added this pull request to the merge queue Jun 12, 2026
Merged via the queue into facebook:main with commit acf957a Jun 12, 2026
46 checks passed
@etrepum etrepum mentioned this pull request Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

4 participants