Repository navigation
[lexical-plain-text][lexical-selection] Bug Fix: Move the caret beside a node-selected decorator on an arrow key - #9710
Conversation
…e a node-selected decorator on an arrow key
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
|
Heads up, and apologies for the noise: after opening this I noticed #9286 by @rinarakaki, opened on Sept 30, which fixes the same issue (#9282, and #4214). I missed it when I checked for existing PRs. Both move Please take whichever you prefer. I am happy to close this one in favor of #9286, just say so. |
There was a problem hiding this comment.
This looks good — thanks for tracking down #9282 and sharing rich text's handlers rather than copying them. Plain text now answers arrow keys on a node-selected decorator the same way rich text does, for each arrow with and without Shift, and the two helpers move to @lexical/selection with unchanged bodies, so rich text's behavior is untouched.
A few optional, non-blocking suggestions:
- Add a browser-tier test next to
packages/lexical-plain-text/src/__tests__/unit/NodeSelectionArrow.test.ts. AGENTS.md (Testing Strategy) puts behavior that depends on a real layout or selection engine underpackages/**/__tests__/browser/**, and the bug in #9282 is about where the browser's caret goes.packages/lexical-rich-text/src/__tests__/browser/RichTextNodeSelectionArrow.test.tsis a ready template; a plain-text twin would also cover Shift+ArrowLeft/Right, which jsdom cannot exercise because$moveCharacterrelies onSelection.modify. - Document the new public helpers in
packages/lexical-selection/src/node-selection.ts:48-53. Now that$convertContiguousNodeSelectionis exported, its JSDoc is its contract: please mention that it commits the current update synchronously ($flushSyncAfterUpdate()at line 87) and that it returnsfalseand leaves the selection alone when the nodes are not contiguous. The phrase "the arrow handlers" points at rich text's internals, which API readers won't see. On your question about keeping the helpers internal: exporting them is additive and consistent with AGENTS.md's backwards-compatibility rules, so that's a maintainer preference either way. - Nit: the Shift+ArrowDown test (line 97) only asserts
isCollapsed() === false. Asserting the converted anchor and focus (therootoffsets on either side of the rule) would pin the conversion itself rather than any non-collapsed selection.
The changesets look right per CONTRIBUTING.md: [fix] with a Verify: paragraph for @lexical/plain-text, and [feature] at patch for @lexical/selection.
lexical
@lexical/a11y
@lexical/clipboard
@lexical/code
@lexical/code-core
@lexical/code-prism
@lexical/code-shiki
@lexical/compiler
@lexical/devtools-core
@lexical/dragon
@lexical/eslint-plugin
@lexical/extension
@lexical/fast-check
@lexical/file
@lexical/hashtag
@lexical/headless
@lexical/history
@lexical/html
@lexical/internal
@lexical/link
@lexical/list
@lexical/mark
@lexical/markdown
@lexical/mdast
@lexical/offset
@lexical/overflow
@lexical/plain-text
@lexical/react
@lexical/rich-text
@lexical/selection
@lexical/table
@lexical/tailwind
@lexical/text
@lexical/utils
@lexical/y
@lexical/yjs
commit: |
…ction helpers' contract and pin the Shift+Arrow range
There was a problem hiding this comment.
This looks good — thanks for picking up #9282, and for turning the earlier JSDoc and Shift-range suggestions around in 4ac060e. Plain text now answers arrow keys on a node-selected decorator the way rich text does: each arrow leaves the caret beside the decorator and the next character is typed there, and with Shift a contiguous node selection becomes the range the key extends. The two helpers move to @lexical/selection unchanged, so rich text's behavior is untouched.
A few optional, non-blocking suggestions:
- Make the Shift test's event cancelable (
packages/lexical-plain-text/src/__tests__/unit/NodeSelectionArrow.test.ts:99-102). Withoutcancelable: true,preventDefault()does nothing on that event, soexpect(event.defaultPrevented).toBe(false)on line 111 passes even if the handler cancels the key, which would stop the browser from extending the range. The non-Shift cases already passcancelable: true(line 76). - Consider a browser-tier test. AGENTS.md (Testing Strategy) puts behavior that depends on a real layout or selection engine under
packages/**/__tests__/browser/**, and #9282 is about where the browser's caret goes.packages/lexical-rich-text/src/__tests__/browser/RichTextNodeSelectionArrow.test.tsis a ready template; a plain-text version would also cover Shift+ArrowLeft/Right, which jsdom can't exercise because$moveCharacterrelies onSelection.modify. - Name
@lexical/rich-textin a changeset..changeset/README.mdasks the frontmatter to list every published package a change affects, and the Changesets check warns thatpackages/lexical-rich-text/src/index.tschanged without one. Adding'@lexical/rich-text': patchto.changeset/selection-node-selection-exit-helpers.mdcovers it.
On the two open questions in the description and the comments: exporting the helpers is additive and fits AGENTS.md's backwards-compatibility rules, and #9286 exposes the same two names from the same package, so either pull request leaves the same public API. Which of the two to merge is the maintainers' call.
The changesets otherwise follow CONTRIBUTING.md: [fix] with a Verify: paragraph for @lexical/plain-text, and [feature] at patch for @lexical/selection.
…est event cancelable and list rich-text in the changeset
There was a problem hiding this comment.
This looks good — thanks for picking up #9282, and for making the Shift test's event cancelable and adding @lexical/rich-text to the changeset in 6956b05. Plain text now answers arrow keys on a node-selected decorator the way rich text does: each arrow leaves the caret beside the decorator and the next character is typed there, and with Shift a contiguous node selection becomes the range the key extends. The two helpers move to @lexical/selection unchanged, so rich text's behavior is untouched.
One optional, non-blocking suggestion:
- Consider a browser-tier test. AGENTS.md (Testing Strategy) puts behavior that depends on a real layout or selection engine under
packages/**/__tests__/browser/**, and #9282 is about where the browser's caret goes once Lexical holds a node selection.packages/lexical-rich-text/src/__tests__/browser/RichTextNodeSelectionArrow.test.tsis a ready template; a plain-text version would also cover Shift+ArrowLeft/Right, which jsdom can't exercise because$moveCharacterrelies onSelection.modify. The new unit test inpackages/lexical-plain-text/src/__tests__/unit/NodeSelectionArrow.test.tsalready tells the fix apart frommain, so this is extra coverage rather than a blocker.
On the open questions in the description and the comments: exporting the helpers is additive and fits AGENTS.md's backwards-compatibility rules, and #9286 exposes the same two names from the same package, so either pull request leaves the same public API. Which of the two to merge is the maintainers' call.
The changesets follow CONTRIBUTING.md: [fix] with a Verify: paragraph for @lexical/plain-text, and [feature] at patch naming @lexical/selection and @lexical/rich-text.
Description
Current behavior: in a plain-text editor, a click on a decorator such as the horizontal rule gives a
NodeSelection. Plain text answers the left and right arrows only for aRangeSelectionand has no up or down listener, so the browser's default runs. The browser's range was removed for the node selection, so the caret starts from the top of the document while Lexical still holds the node selection, and what is typed next goes to the top of the document (#4214 is the same report).This PR gives plain text's four arrow listeners the node-selection branch rich text already has: the caret moves to the decorator's side, and with Shift a contiguous node selection becomes the range it covers for the key to extend. Rich text's
$exitNodeSelectionTowardand$convertContiguousNodeSelectionmove unchanged to@lexical/selection(the issue's suggestion; plain text already depends on that package) and rich text imports them from there. That adds two exports to@lexical/selectionand its Flow types; if you would rather keep them internal, say so and I will change it.The diagnosis and the plain-text diff are from the issue.
Closes #9282
Test plan
New file:
packages/lexical-plain-text/src/__tests__/unit/NodeSelectionArrow.test.ts(each of the four arrows on a node-selected horizontal rule, and Shift+ArrowDown).Before
pnpm exec vitest run --project unit packages/lexical-plain-textonmain'spackages/lexical-plain-text/src/index.ts:After
pnpm run tsc,pnpm run flow,pnpm run check-changesets, eslint and prettier are clean.Only the unit tests (jsdom, macOS) were run. They dispatch the arrow commands directly, so real key presses in a browser, and the e2e suite, were not exercised.