[lexical][lexical-rich-text][lexical-code-core] Bug Fix: Cursor stuck before leading inline DecoratorNode#8558
Merged
etrepum merged 1 commit intoMay 26, 2026
Conversation
… before leading inline DecoratorNode
mayrang
requested review from
acywatson,
etrepum,
fantactuka,
ivailop7,
potatowagon and
zurfyx
as code owners
May 25, 2026 07:55
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
etrepum
approved these changes
May 25, 2026
potatowagon
added a commit
that referenced
this pull request
May 28, 2026
PR #8558 broadened isMoveToStart/isMoveToEnd matchers to shiftKey:'any', causing Shift+Cmd+Arrow to dispatch MOVE_TO_START/MOVE_TO_END. The handlers call selectEnd()/selectStart() unconditionally, which throws when the element contains only an inline DecoratorNode with no selectable text content. This change: 1. Tightens isMoveToStart/isMoveToEnd back to Cmd+Arrow only (no shiftKey) 2. Adds defensive checks in MOVE_TO_END/MOVE_TO_START handlers to bail out (return false) when the element has no selectable text descendants The original PR #8558 behavior for mixed-content (decorator + text) is preserved.
potatowagon
added a commit
that referenced
this pull request
May 28, 2026
PR #8558 broadened isMoveToStart/isMoveToEnd matchers to shiftKey:'any', causing Shift+Cmd+Arrow to dispatch MOVE_TO_START/MOVE_TO_END. The handlers call selectEnd()/selectStart() unconditionally, which throws when the element contains only an inline DecoratorNode with no selectable text content. This change: 1. Tightens isMoveToStart/isMoveToEnd back to Cmd+Arrow only (no shiftKey) 2. Adds defensive checks in MOVE_TO_END/MOVE_TO_START handlers to bail out (return false) when the element has no selectable text descendants The original PR #8558 behavior for mixed-content (decorator + text) is preserved.
Merged
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When a paragraph (or heading) starts with an inline
DecoratorNodeand the caret is at element offset 0, pressing Cmd/Ctrl+ArrowRight leaves the caret stuck in front of the decorator. The Shift variant is also stuck — selection never extends past the decorator boundary. The symmetric backwards case is also broken in Chromium: with the caret inside the trailing text, Cmd/Ctrl+ArrowLeft stops at the decorator's right edge instead of reaching element offset 0. Safari handles the backwards case correctly.Root cause: the decorator renders as
contenteditable=false, so the native caret can't move past its boundary. And the Shift variants don't even reach the relevant lexical commands becauseisMoveToEnd/isMoveToStartmatching masks omitshiftKey: 'any'.What changed
packages/lexical/src/LexicalUtils.ts—isMoveToEnd/isMoveToStartmasks now includeshiftKey: 'any', aligning them withisMoveForward/isMoveBackward. Shift variants now dispatchMOVE_TO_END/MOVE_TO_STARTinstead of being dropped at the matcher.packages/lexical-rich-text/src/index.ts— registers twoCOMMAND_PRIORITY_EDITORlisteners.MOVE_TO_ENDfires when anchor sits at element offset 0 and the block's first child is an inlineDecoratorNode; it moves selection to the element end, with the Shift variant pinning anchor at element offset 0.MOVE_TO_STARTfires when the block's first child is an inlineDecoratorNodeand selection endpoints are within the same block; it moves focus to element offset 0, with the Shift variant preserving anchor. Cross-block selections fall through to native handling.packages/lexical-code-core/src/CodeIndentation.ts—$handleMoveTonow resolves the target via the existing framework helpers (selectNext/selectStart/setTextNodeRange/node.select), then restores the original anchor whenevent.shiftKeyis set. Without this, the mask change would regress Shift+Cmd+Home/End inside code blocks to a collapsed caret.Backwards compatibility
isMoveToEnd/isMoveToStartare exported. Any external listener registered onMOVE_TO_END/MOVE_TO_STARTwill now also receive Shift events; collapse-only handlers may need to branch onevent.shiftKey. Matches the pre-existing shape ofisMoveForward/isMoveBackward, so the omission looks unintentional, not deliberate.Known limitation
For a leading-inline-
DecoratorNodeparagraph that soft-wraps to a second visual line, theMOVE_TO_STARThandler unconditionally targets element offset 0 (block start) rather than the visual line start. Lexical state doesn't track visual lines, so the handler can't tell them apart. Out of scope for #8555 (single-line caret case); leaving as-is unless it surfaces in practice.Open question
The non-Shift
MOVE_TO_STARTcaret lands as an element-type point (block, offset 0), because no text node exists before the inline decorator to anchor on.MOVE_TO_ENDnon-Shift caret stays text-type viaelement.selectEnd()since the last descendant is a text node. The asymmetry is intentional — each shape is the natural representation of its target. If one type is preferred,MOVE_TO_ENDcan be flipped to element-type to match.Closes #8555
Test plan
pnpm test-unit -- packages/lexical packages/lexical-rich-text packages/lexical-code packages/lexical-code-core— all green (3231 passed, +10 new tests covering Shift / no-op / heading / mask matching / code-block regression).pnpm tscclean.<inline equation><text>paragraph, caret at element offset 0 → Cmd+ArrowRight moves caret to text end; Cmd+Shift+ArrowRight selects equation + text.