Skip to content

[lexical] Bug Fix: Prevent deleteLine from removing adjacent block decorators#8744

Open
mayrang wants to merge 2 commits into
facebook:mainfrom
mayrang:fix/8722-delete-line-decorator
Open

[lexical] Bug Fix: Prevent deleteLine from removing adjacent block decorators#8744
mayrang wants to merge 2 commits into
facebook:mainfrom
mayrang:fix/8722-delete-line-decorator

Conversation

@mayrang

@mayrang mayrang commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

deleteLine (Cmd+Backspace / Cmd+Delete) from an empty block removes an adjacent block-level decorator (HorizontalRule, PageBreak) when it should only delete the empty block. The modify('extend', isBackward, 'lineboundary') call extends the selection past block boundaries into the adjacent decorator, and the subsequent removeText() deletes the decorator along with the empty block.

After the lineboundary extend, check whether the resulting selection includes any non-inline DecoratorNode. If so, collapse back to the anchor and delegate to deleteCharacter(), which already handles decorator boundaries correctly — it removes the empty anchor block and selects the decorator via NodeSelection.

Closes #8722

Test plan

  • pnpm vitest run --project unit -t "DELETE_LINE_COMMAND" — 6 tests pass:
    • deleteCharacter backward from empty ListItem preserves preceding decorator
    • deleteLine backward/forward from empty ListItem and empty paragraph preserves adjacent decorator (3 cases via test.for)
    • deleteLine backward from empty ListItem preserves preceding empty paragraph (regression guard)
    • deleteLine backward with preceding non-empty paragraph merges normally
  • pnpm vitest run --project unit — full suite passes (3337 tests).
  • pnpm tsc --noEmit, prettier / eslint clean.
  • Manual playground on macOS (Chrome, Safari, Firefox):
    • Insert horizontal rule → empty bullet list below → Cmd+Backspace: rule survives.
    • Empty paragraph after horizontal rule → Cmd+Backspace: rule survives.
    • Empty bullet list above horizontal rule → Cmd+Delete: rule survives.
    • Empty paragraph before empty bullet list → Cmd+Backspace: paragraph preserved (regression check).
    • Text before empty bullet list → Cmd+Backspace: normal merge.
    • Non-empty list item after horizontal rule → Cmd+Backspace: text deleted, rule untouched.
    • Empty editor → Cmd+Backspace: no change.

mayrang added 2 commits June 25, 2026 02:12
…corators

When deleteLine calls modify('extend', …, 'lineboundary') from an empty
block, the lineboundary extension can overreach past a block-level
decorator (e.g. HorizontalRuleNode, PageBreakNode). The subsequent
removeText() then removes the decorator along with the empty block.

After the lineboundary extend, check whether the resulting selection
includes any non-inline decorator. If so, collapse back to the anchor
and delegate to deleteCharacter, which already handles decorator
boundaries correctly — it removes the empty anchor block and selects
the decorator via NodeSelection.

Fixes facebook#8722
@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 24, 2026
@vercel

vercel Bot commented Jun 24, 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 Jun 24, 2026 5:23pm
lexical-playground Ready Ready Preview Jun 24, 2026 5:23pm

Request Review

@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 ✅ — Ready to approve

This is a well-crafted bug fix for #8722. The approach is sound and minimal.

What I checked:

  1. Logic correctness: The fix correctly identifies when deleteLine overreaches past block-level decorators after the lineboundary extend. By collapsing the selection back to the anchor and delegating to deleteCharacter, it leverages the existing decorator-boundary logic rather than reimplementing it. This is the right abstraction layer to handle this.

  2. Edge cases covered by tests:

    • Backward delete from empty ListItem with preceding decorator ✅
    • Backward delete from empty paragraph with preceding decorator ✅
    • Forward delete from empty ListItem with following decorator ✅
    • Backward delete should not remove preceding empty paragraph ✅
    • Backward delete from list item preceded by non-empty paragraph (normal merge behavior preserved) ✅
  3. No regressions: The condition (this.getNodes().some(node => $isDecoratorNode(node) && !node.isInline())) is narrow — it only triggers when the extended selection actually contains a block-level decorator. Normal text deletion, inline decorator handling, and paragraph merging are unaffected.

  4. Placement in control flow: The new branch sits between the "anchor === focus after extend" early return (which delegates to deleteCharacter) and the removeText() fallback. This is the correct insertion point — it catches the case where the extend DID move focus but overshot into a decorator.

  5. www compat: No API changes, no export modifications, no changed function signatures. Purely behavioral fix in internal selection logic. Safe for internal consumers (MLCComposer, XDSRichText, EPS).

  6. CI: All checks green — core-tests (unit Node 22/24, browser, integrity), e2e canary, CLA, Vercel deploys all pass.

One minor observation: The .some() call iterates all selected nodes on every deleteLine. For typical selections this is negligible, but in theory a very large selection could be slightly slower. Not a concern for real-world usage since deleteLine operates on single lines.

Ship it. 🚀

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: DELETE_LINE_COMMAND on an empty ListItem removes the previous decorator

2 participants