[lexical-code][lexical-playground] Bug Fix: Prevent formatting on TabNode inside code blocks#8752
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
potatowagon
left a comment
There was a problem hiding this comment.
Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.
Assessment: LGTM ✅ — Clean, targeted fix with good test coverage.
What this PR does
Prevents text formatting (bold, italic, etc.) from being applied to TabNode elements inside code blocks, and fixes the floating text format toolbar to correctly suppress itself when the selection is inside a code block.
What I checked
-
Logic correctness:
- The
registerNodeTransform(TabNode, ...)approach is correct — it reactively strips formatting (setFormat(0)) whenever a TabNode inside a CodeNode has non-zero format. This covers all mutation paths (user applying format, paste, collaboration sync). - The parent check
$isCodeNode(node.getParent())is the right level — TabNodes are direct children of CodeNode, not nested deeper. - The toolbar fix changes from
$isCodeHighlightNode(selection.anchor.getNode())to$isCodeNode(selection.anchor.getNode().getParent()). This is more robust — the old check only caught CodeHighlightNode but missed TabNode (which is also a direct child of CodeNode). The new check catches ANY node whose parent is CodeNode.
- The
-
Edge cases:
node.getParent()could returnnullif the node is detached, but$isCodeNode(null)safely returnsfalse— no crash risk.- The transform only fires when
node.getFormat() !== 0, avoiding unnecessary writes/re-triggers. mergeRegistercorrectly composes the existing KEY_ENTER_COMMAND handler with the new node transform — cleanup is properly handled.
-
Test coverage:
- New unit test
should strip format from TabNode inside CodeNodeproperly exercises the transform: creates a tab inside code, applies format, then verifies it gets stripped back to 0. Uses{discrete: true}for synchronous verification.
- New unit test
-
No regressions:
- The toolbar change is playground-only (not library code), and the logic is strictly more correct (suppresses toolbar for all code-block children, not just CodeHighlightNode).
- Unit tests pass on Node 22.x and 24.x. Browser tests pass. CLA signed. Vercel deploys pass.
-
www compat:
- No removed/renamed exports.
$isCodeNodewas already exported.mergeRegisterandTabNodealready exported from lexical. No breaking changes to public API.
- No removed/renamed exports.
CI status: Core unit + browser tests all green. Integrity check and one e2e canary still pending — recommend waiting for those to finish before merging, but no red flags.
Ready to approve once remaining CI checks complete.
…Node inside code blocks TabNode inside a code block accepted text formatting (bold, highlight, etc.) because it inherits TextNode.setFormat() — unlike CodeHighlightNode which overrides setFormat() as a no-op. The floating toolbar also appeared when selecting a TabNode inside a code block. Register a TabNode transform in CodeExtension that strips format when the tab is inside a CodeNode. Also add a $isCodeNode(parent) guard to the floating toolbar so it hides for any node inside a code block, not just CodeHighlightNode. Closes facebook#8749
2b4d827 to
5d9ae0f
Compare
Description
The floating text format toolbar shows when selecting a
TabNodeinside a code block, and formatting (bold, highlight, etc.) sticks.CodeHighlightNodealready blocks this via a no-opsetFormat(), butTabNodeinherits fromTextNodeand has no such guard.Fix
CodeExtensionnow registers aregisterNodeTransform(TabNode, ...)that strips format back to 0 when the parent is aCodeNode. A globalTabNode.setFormat()override was not used becauseTabNodelegitimately inherits format in normal paragraphs (the existingINSERT_TAB_COMMANDtest verifies this).The
FloatingTextFormatToolbarPluginguard is simplified from checking both$isCodeHighlightNode(anchor)and$isCodeNode(anchor.getParent())to just$isCodeNode(anchor.getParent())— the latter is a superset sinceCodeHighlightNodealways has aCodeNodeparent.Closes #8749
Test plan
pnpm vitest run --project unit -t "CodeExtension"— 5 pass (including new "should strip format from TabNode inside CodeNode").pnpm vitest run --project unit -t "TabNode"— 23 pass, no regression.pnpm tsc --noEmitclean.