Skip to content

[lexical][lexical-rich-text] Bug Fix: Restore Shift+Cmd+Arrow selection extension on leading inline DecoratorNode#8583

Closed
mayrang wants to merge 1 commit into
facebook:mainfrom
mayrang:fix/restore-shift-move-to-end
Closed

[lexical][lexical-rich-text] Bug Fix: Restore Shift+Cmd+Arrow selection extension on leading inline DecoratorNode#8583
mayrang wants to merge 1 commit into
facebook:mainfrom
mayrang:fix/restore-shift-move-to-end

Conversation

@mayrang
Copy link
Copy Markdown
Contributor

@mayrang mayrang commented May 28, 2026

Description

PR #8577 removed shiftKey: 'any' from isMoveToStart / isMoveToEnd to avoid a reported crash on decorator-only paragraphs. That also disabled the Shift+Cmd+Arrow selection-extension functionality #8558 added. Per the #8577 discussion, the matcher's Shift behavior was intentional — the right fix is to address the selectEnd() recursion on decorator-only elements, not to disable the matcher.

What changed

  • isMoveToStart / isMoveToEnd accept Shift again (shiftKey: 'any' restored).
  • MOVE_TO_END handler calls node.select() directly, branching on lastDescendant: text → text.select(), otherwise → element.select(childrenSize, childrenSize). The decorator-only path is now explicit instead of routed through element.selectEnd()decorator.selectEnd()parent.select(...).
  • MOVE_TO_START handler drops the lastDescendant === decorator guard. That handler never called selectStart(), so the guard was symmetry-only.

Behavior on decorator-only paragraphs

Cmd+ArrowLeft / Right now move the caret to the position before / after the decorator. Shift variants extend the selection over it. Previously these fell through to native, which left the caret stuck.

Out of scope

text [decorator] long-wrapping-text still uses native Cmd+ArrowRight (= visual line end) while [decorator] long-wrapping-text jumps to paragraph end via this handler. That inconsistency was introduced by #8558 and isn't touched here.

Test plan

  • Unit tests in RichTextInlineDecoratorMoveToEnd.test.ts + RichTextInlineDecoratorMoveToStart.test.tsdecorator-only cases moved out of no-op into cursor-move / decorator-selected expectations; mixed-content cases preserved.
  • Full unit suite (pnpm vitest run --project unit) — 140 files / 2764 pass / 1 skip / 0 fail.
  • pnpm tsc --noEmit -p tsconfig.json, pnpm flow, prettier, eslint clean.
  • Manual playground (Chrome + Safari):
    • Decorator-only paragraph ([equation]):
      • [eq]| + Cmd+ArrowLeft → caret moves to element offset 0.
      • [eq]| + Shift+Cmd+ArrowLeft → selection (anchor=element 1, focus=element 0).
      • |[eq] + Cmd+ArrowRight → caret moves to element offset 1.
      • |[eq] + Shift+Cmd+ArrowRight → selection (anchor=element 0, focus=element 1).
    • Mixed content ([equation]hello):
      • |[eq]hello + Cmd+ArrowRight → caret at text "hello" end.
      • |[eq]hello + Shift+Cmd+ArrowRight → selection (anchor=element 0, focus=text end).
      • [eq]hello| + Cmd+ArrowLeft → caret at element offset 0.
      • [eq]hello| + Shift+Cmd+ArrowLeft → selection (anchor=text end, focus=element 0).
    • Wrapping text ([lexical][lexical-rich-text][lexical-code-core] Bug Fix: Cursor stuck before leading inline DecoratorNode #8558 behavior preserved):
      • [equation]long-wrapping-text — Cmd+ArrowRight / Shift+Cmd+ArrowRight reach paragraph end (handler fires).
      • text [equation] long-wrapping-text — handler doesn't fire (first child is text); native Cmd+ArrowRight reaches visual line end only.

Refs #8558, #8577.

…on extension on leading inline DecoratorNode

PR facebook#8577 removed shiftKey: 'any' from isMoveToStart / isMoveToEnd to avoid a reported crash on decorator-only paragraphs. This also disabled the selection-extension functionality facebook#8558 added (per etrepum: the shiftKey behavior was intentional to catch an intentional selection expansion).

Root cause: avoid the selectEnd() recursion chain on decorator-only elements instead of disabling the matcher. Restore shiftKey: 'any' on the matchers and rewrite MOVE_TO_END to call node.select() directly, branching on lastDescendant — text → text.select(), otherwise → element.select(childrenSize, childrenSize) for the decorator-only case. Drop MOVE_TO_START's lastDescendant guard: that handler never called selectStart(), so the guard was symmetry-only.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment May 28, 2026 4:57pm
lexical-playground Ready Ready Preview, Comment May 28, 2026 4:57pm

Request Review

@mayrang
Copy link
Copy Markdown
Contributor Author

mayrang commented May 28, 2026

Duplicate of #8581@potatowagon already opened it with the matcher restore. Closing.

@mayrang mayrang closed this May 28, 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant