Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[lexical-text] Bug Fix: a text entity keeps the style and detail of the text it replaces - #9130

Merged
etrepum merged 1 commit into
facebook:mainfrom
Om-singhaI:fix/text-entity-preserves-style-detail
Sep 9, 2026
Merged

[lexical-text] Bug Fix: a text entity keeps the style and detail of the text it replaces#9130
etrepum merged 1 commit into
facebook:mainfrom
Om-singhaI:fix/text-entity-preserves-style-detail

Conversation

@Om-singhaI

Copy link
Copy Markdown
Contributor

Description

registerLexicalTextEntity carries only format from the TextNode it promotes onto the node createNode returns. style and detail are dropped, so a hashtag or a keyword typed inside colored text comes out with the color stripped.

Reproduction, using the test entity node already in the package's unit test:

paragraph.append(
  $createTextNode('#lexical')
    .setFormat('bold')
    .setStyle('color: red')
    .setDetail('directionless'),
);

After the transform runs, the entity node is bold, but getStyle() is '' and isDirectionless() is false.

The inconsistency is inside this same function. $replaceWithSimpleText already chains .setFormat().setStyle().setDetail() when it turns an entity back into plain text. $createAutoLinkNode_ in @lexical/link copies all three as well, off an almost identical splitText sequence. The forward path is the only one that stops at format.

That asymmetry makes the loss permanent, since the reverse path can only give back what the entity node holds. I logged both ends of a full trip on main:

AFTER PROMOTE:     style='' directionless=false bold=true
AFTER BREAK MATCH: text='lexical' style='' directionless=false bold=true

Bold survives typing a hashtag and then editing it back to plain text. The color and the directionless flag don't.

The fix chains setStyle and setDetail onto the setFormat that's already there.

One thing worth calling out: the call is unconditional, so if a caller's createNode sets a style itself, an unstyled source now overwrites it with ''. I kept it unconditional on purpose. The setFormat line has always done exactly that to format, and KeywordNode in the playground already documents that behavior as the contract it relies on. Making style conditional would strand a caller's style on the entity node, and the reverse transform would then copy that style onto the plain text the user is left with, which is worse. Neither caller in this repo sets style or detail in createNode.

Test plan

New unit test in packages/lexical-text/src/__tests__/unit/registerLexicalTextEntity.test.ts, next to the existing test that covers the reverse direction.

Before

$ node_modules/.bin/vitest --project unit --no-watch run packages/lexical-text/src/__tests__/unit/registerLexicalTextEntity.test.ts

 FAIL  |unit| packages/lexical-text/src/__tests__/unit/registerLexicalTextEntity.test.ts > registerLexicalTextEntity > preserves style and detail when promoting simple text to an entity
AssertionError: expected '' to be 'color: red' // Object.is equality

 ❯ packages/lexical-text/src/__tests__/unit/registerLexicalTextEntity.test.ts:72:35
     72|           expect(node.getStyle()).toBe('color: red');

 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)

Dropping the style assertion so the next one runs shows detail is gone too:

AssertionError: expected false to be true // Object.is equality

 ❯ packages/lexical-text/src/__tests__/unit/registerLexicalTextEntity.test.ts:72:42
     72|           expect(node.isDirectionless()).toBe(true);

After

$ node_modules/.bin/vitest --project unit --no-watch run packages/lexical-text/src/__tests__/unit/registerLexicalTextEntity.test.ts

 Test Files  1 passed (1)
      Tests  2 passed (2)

The packages around it, all green:

$ node_modules/.bin/vitest --project unit --no-watch run packages/lexical-text packages/lexical-hashtag packages/lexical-link packages/lexical-mark packages/lexical-playground/__tests__/unit/KeywordNode.test.ts

 Test Files  23 passed (23)
      Tests  844 passed (844)

I read the e2e specs instead of running them. Keywords.spec.mjs asserts style="cursor: default;" on the keyword span, and that comes from KeywordNode.createDOM, not from the node's style. Carrying an empty style over changes nothing there: createDOM skips the write when the style is '', updateDOM skips it when the style hasn't changed, and setDOMStyleFromCSS sets individual properties rather than replacing cssText, so cursor is never clobbered either way. Hashtags.spec.mjs and the hashtag regression specs assert no style attribute at all, and none of these specs applies a text color before typing. I don't expect any of those snapshots to move.

…he text it replaces

The forward transform only carried format onto the node createNode returns, so
style and detail were dropped when text was promoted to an entity.
$replaceWithSimpleText already carries all three the other way, so anything
lost here can never be restored once the match breaks.
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
lexical Ready Ready Preview Sep 8, 2026 12:46am UTC
lexical-playground Ready Ready Preview Sep 8, 2026 12:46am UTC

Request Review

@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 Sep 8, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Sep 8, 2026
@etrepum
etrepum added this pull request to the merge queue Sep 9, 2026
Merged via the queue into facebook:main with commit 5fa43cc Sep 9, 2026
82 of 83 checks passed
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants