Skip to content

[lexical-playground] Bug Fix: Normalize collapsible content children#8539

Merged
etrepum merged 6 commits into
facebook:mainfrom
rohan-patnaik:fix-collapsible-content-inline-children
May 25, 2026
Merged

[lexical-playground] Bug Fix: Normalize collapsible content children#8539
etrepum merged 6 commits into
facebook:mainfrom
rohan-patnaik:fix-collapsible-content-inline-children

Conversation

@rohan-patnaik

@rohan-patnaik rohan-patnaik commented May 22, 2026

Copy link
Copy Markdown
Contributor

Description

This PR fixes the collapsible playground crash when invalid inline or empty content is placed directly under CollapsibleContentNode.

CollapsibleContentNode is a shadow root, so its children need to be block elements. The content transform now wraps direct inline children in paragraphs, leaves existing block children unchanged, and normalizes empty content with a paragraph so the repro state stays editable.

Closes #8530

Test plan

Before

The issue repro could load a collapsible-content node with no block child. After dirtying that content node, later editor operations could throw because the content node did not have a valid block structure.

After

  • corepack pnpm exec vitest --project unit --run packages/lexical-playground/__tests__/unit/CollapsibleContainerNode.test.ts
  • corepack pnpm exec eslint packages/lexical-playground/src/plugins/CollapsibleExtension/index.ts packages/lexical-playground/__tests__/unit/CollapsibleContainerNode.test.ts
  • corepack pnpm exec prettier --check packages/lexical-playground/src/plugins/CollapsibleExtension/index.ts packages/lexical-playground/__tests__/unit/CollapsibleContainerNode.test.ts

@meta-cla

meta-cla Bot commented May 22, 2026

Copy link
Copy Markdown

Hi @rohan-patnaik!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at [email protected]. Thanks!

@vercel

vercel Bot commented May 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment May 25, 2026 4:44am
lexical-playground Ready Ready Preview, Comment May 25, 2026 4:44am

Request Review

@mayrang

mayrang commented May 22, 2026

Copy link
Copy Markdown
Contributor

The issue's repro link starts with a collapsible-content that has no children at all (children: []) inside a normal container. Neither path in this PR's transform touches an empty content, so it stays empty after normalization — and Shift+Enter still throws Expected node CollapsibleContentNode of type collapsible-content to have a block ElementNode ancestor. Reproduced on main, and confirmed the throw goes away once the same transform also handles the empty-content case. A regression test loading the repro state would catch this directly.

@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 May 22, 2026
@rohan-patnaik

rohan-patnaik commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

@mayrang thanks for catching the empty-content repro case. I addressed it in d896b1c by normalizing empty collapsible-content to contain a paragraph, and added a regression test that loads the serialized repro shape before dirtying the content node.

@etrepum

etrepum commented May 22, 2026

Copy link
Copy Markdown
Collaborator

@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label May 22, 2026
@rohan-patnaik rohan-patnaik changed the title Fix collapsible content inline child normalization [lexical-playground] Bug Fix: Normalize collapsible content children May 22, 2026
@rohan-patnaik

Copy link
Copy Markdown
Contributor Author

@etrepum thanks for pointing that out. I updated the title and PR body to follow the template format. Could you please take another look and let me know if this reads correctly now?

@etrepum

etrepum commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Title and description look good, no need to update the branch unless there's a conflict. As a first time contributor this will require a maintainer to approve the CI checks to run again which will slow down your review.

Comment on lines +110 to +119
const $wrapInlineContentChildren = (node: CollapsibleContentNode) => {
if (node.isEmpty()) {
node.append($createParagraphNode());
return;
}

let paragraph: ReturnType<typeof $createParagraphNode> | null = null;

for (const child of node.getChildren()) {
if ($isBlockElementNode(child)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$isBlockElementNode checks only ElementNode, although there are block decorators too

I also think it makes sense to export $wrapInlineNodes to lexical/utils and reuse it
https://github.com/facebook/lexical/blob/main/packages/lexical/src/LexicalSelection.ts#L3328-L3366

@rohan-patnaik rohan-patnaik May 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that makes sense. I updated this in ec9e073: moved the inline wrapping helper into LexicalUtils, re-exported it through lexical / @lexical/utils, and reused it in the collapsible transform.

I also added a reg test covering the case you called out: block decorator children stay unwrapped, while inline content is still wrapped into paragraphs.

Checked locally too:

  • vitest --project unit --run packages/lexical-playground/__tests__/unit/CollapsibleContainerNode.test.ts
  • tsc --noEmit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the $wrapInlineContentChildren wrapper is not needed, it checks exactly the same thing as $wrapInlineNodes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I guess I agree that the pre-check duplicates what $wrapInlineNodes already does.

The only extra behavior in $wrapInlineContentChildren is the empty content case, where we add a paragraph. $wrapInlineNodes([]) would return no children, so removing that handling would regress the empty-content repro.

Would you prefer that I keep a very small wrapper for the empty case and always call $wrapInlineNodes, or inline that logic directly in the transform?

Comment thread packages/lexical/src/LexicalUtils.ts Outdated
);
}

export function $wrapInlineNodes(nodes: LexicalNode[]) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new export should have documentation, an explicit return type, and ideally some tests. I know this is code just copied from elsewhere but the LineBreakNode special case seems a bit suspicious. Not sure that's really the correct behavior in all cases. I'm not sure that it's correct for [LineBreakNode, TextNode] to be indistinguishable from [TextNode] after wrapping.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I removed the new public export in e53545c and kept this PR scoped to the collapsible fix instead.

The wrapping logic is now local to the playground collapsible transform, and the net PR diff is back to the collapsible extension plus its regression tests. This keeps the empty-content fix and the block-decorator coverage without adding a new utility API.

return;
}

let paragraph: ReturnType<typeof $createParagraphNode> | null = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let paragraph: ReturnType<typeof $createParagraphNode> | null = null;
let paragraph: ElementNode | null = null;

}

if (paragraph === null) {
paragraph = $createParagraphNode();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
paragraph = $createParagraphNode();
paragraph = child.createParentNode();

@rohan-patnaik

rohan-patnaik commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

@etrepum thanks, I pushed 90cdb1a with the wrapping cleanup.

Used ElementNode | null for the local wrapper type and switched the non-empty wrapping path to child.createParentElementNode().
Used the existing createParentElementNode() API since createParentNode() is not available on these nodes.

Checked locally:

  • vitest --project unit --run packages/lexical-playground/__tests__/unit/CollapsibleContainerNode.test.ts
  • tsc --noEmit

@etrepum
etrepum added this pull request to the merge queue May 25, 2026
Merged via the queue into facebook:main with commit eeefefc May 25, 2026
42 checks passed
@etrepum etrepum mentioned this pull request 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. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: CollapsibleContainer crashes when you press Shift + Enter

4 participants