From ed2fd081c30022e881864f6c17dee0e7deb5664b Mon Sep 17 00:00:00 2001 From: Sergey Gorbachev Date: Mon, 16 Mar 2026 18:26:55 +0300 Subject: [PATCH 1/4] [lexical-list] Fix: create copies ListNode/ListItemNode in split-like operations (#8213) --- .../lexical-list/src/LexicalListItemNode.ts | 4 +- packages/lexical-list/src/LexicalListNode.ts | 10 +- .../__tests__/unit/LexicalListNode.test.ts | 52 ++++-- .../src/__tests__/unit/formatList.test.ts | 160 +++++++++++++++++- packages/lexical-list/src/formatList.ts | 24 ++- 5 files changed, 218 insertions(+), 32 deletions(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index 6ee0284ccee..f3be1852e0e 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -229,7 +229,7 @@ export class ListItemNode extends ElementNode { list.insertAfter(replaceWithNode); } else { // Split the list - const newList = $createListNode(list.getListType()); + const newList = $copyNode(list); let nextSibling = this.getNextSibling(); while (nextSibling) { const nodeToAppend = nextSibling; @@ -275,7 +275,7 @@ export class ListItemNode extends ElementNode { listNode.insertAfter(node, restoreSelection); if (siblings.length !== 0) { - const newListNode = $createListNode(listNode.getListType()); + const newListNode = $copyNode(listNode); siblings.forEach((sibling) => newListNode.append(sibling)); diff --git a/packages/lexical-list/src/LexicalListNode.ts b/packages/lexical-list/src/LexicalListNode.ts index 990d4d068f2..1a70de13028 100644 --- a/packages/lexical-list/src/LexicalListNode.ts +++ b/packages/lexical-list/src/LexicalListNode.ts @@ -13,6 +13,7 @@ import { } from '@lexical/utils'; import { $applyNodeReplacement, + $copyNode, $createTextNode, $isElementNode, buildImportMap, @@ -197,6 +198,13 @@ export class ListNode extends ElementNode { deleteCount: number, nodesToInsert: LexicalNode[], ): this { + const exampleListItem = + nodesToInsert.find($isListItemNode) ?? + this.getChildren().find($isListItemNode); + const $newListItem = exampleListItem + ? () => $copyNode(exampleListItem) + : $createListItemNode; + let listItemNodesToInsert = nodesToInsert; for (let i = 0; i < nodesToInsert.length; i++) { const node = nodesToInsert[i]; @@ -204,7 +212,7 @@ export class ListNode extends ElementNode { if (listItemNodesToInsert === nodesToInsert) { listItemNodesToInsert = [...nodesToInsert]; } - listItemNodesToInsert[i] = $createListItemNode().append( + listItemNodesToInsert[i] = $newListItem().append( $isElementNode(node) && !($isListNode(node) || node.isInline()) ? $createTextNode(node.getTextContent()) : node, diff --git a/packages/lexical-list/src/__tests__/unit/LexicalListNode.test.ts b/packages/lexical-list/src/__tests__/unit/LexicalListNode.test.ts index eaf9648139a..b8b76e29942 100644 --- a/packages/lexical-list/src/__tests__/unit/LexicalListNode.test.ts +++ b/packages/lexical-list/src/__tests__/unit/LexicalListNode.test.ts @@ -6,15 +6,6 @@ * */ import {$createLinkNode, $isLinkNode, LinkNode} from '@lexical/link'; -import {$createTextNode, $getRoot, ParagraphNode, TextNode} from 'lexical'; -import { - expectHtmlToBeEqual, - html, - initializeUnitTest, -} from 'lexical/src/__tests__/utils'; -import {waitForReact} from 'packages/lexical-react/src/__tests__/unit/utils'; -import {describe, expect, test} from 'vitest'; - import { $createListItemNode, $createListNode, @@ -22,7 +13,15 @@ import { $isListNode, ListItemNode, ListNode, -} from '../..'; +} from '@lexical/list'; +import {$createTextNode, $getRoot, ParagraphNode, TextNode} from 'lexical'; +import { + expectHtmlToBeEqual, + html, + initializeUnitTest, +} from 'lexical/src/__tests__/utils'; +import {waitForReact} from 'packages/lexical-react/src/__tests__/unit/utils'; +import {assert, describe, expect, test} from 'vitest'; const editorConfig = Object.freeze({ namespace: '', @@ -306,6 +305,39 @@ describe('LexicalListNode tests', () => { }); }); + test('ListNode.splice() should wrap multiple non-ListItem nodes in individual ListItem nodes', async () => { + const {editor} = testEnv; + + await editor.update(() => { + const list = $createListNode('bullet').append( + $createListItemNode().append($createTextNode('A')), + $createListItemNode().append($createTextNode('D')), + ); + const root = $getRoot(); + root.append(list); + + const textA = $createTextNode('B'); + const textB = $createTextNode('C'); + + list.splice(1, 0, [textA, textB]); + }); + + await editor.read(() => { + const list = $getRoot().getFirstChild(); + assert($isListNode(list), 'First child must be a ListNode'); + + const children = list.getChildren(); + expect(children).toHaveLength(4); + + // Each child must be its own ListItemNode, not the same instance + expect($isListItemNode(children[1])).toBe(true); + expect($isListItemNode(children[2])).toBe(true); + expect(children[1]).not.toBe(children[2]); + expect(children[1].getTextContent()).toBe('B'); + expect(children[2].getTextContent()).toBe('C'); + }); + }); + test('Should update list children when switching from checklist to bullet', async () => { const {editor} = testEnv; diff --git a/packages/lexical-list/src/__tests__/unit/formatList.test.ts b/packages/lexical-list/src/__tests__/unit/formatList.test.ts index 66ca5eab4a1..ee35026db49 100644 --- a/packages/lexical-list/src/__tests__/unit/formatList.test.ts +++ b/packages/lexical-list/src/__tests__/unit/formatList.test.ts @@ -5,6 +5,17 @@ * LICENSE file in the root directory of this source tree. * */ +import { + $createListItemNode, + $createListNode, + $insertList, + $isListItemNode, + $isListNode, + ListItemNode, + ListNode, + ListType, + registerList, +} from '@lexical/list'; import {registerRichText} from '@lexical/rich-text'; import { $createTableCellNode, @@ -26,6 +37,7 @@ import { $nodesOfType, $selectAll, INSERT_PARAGRAPH_COMMAND, + LexicalNode, } from 'lexical'; import { $createTestDecoratorNode, @@ -33,10 +45,39 @@ import { } from 'lexical/src/__tests__/utils'; import {describe, expect, test} from 'vitest'; -import {registerList} from '../../'; -import {$insertList} from '../../formatList'; -import {$createListItemNode, $isListItemNode} from '../../LexicalListItemNode'; -import {$createListNode, $isListNode, ListNode} from '../../LexicalListNode'; +import {$handleIndent, $handleOutdent} from '../../formatList'; + +class ExtendedTestListNode extends ListNode { + $config() { + return this.config('extended-test-list', {extends: ListNode}); + } +} + +function $createExtendedTestListNode(listType: ListType): ExtendedTestListNode { + return new ExtendedTestListNode(listType); +} + +function $isExtendedTestListNode(node?: LexicalNode | null) { + return node instanceof ExtendedTestListNode; +} + +class ExtendedTestListItemNode extends ListItemNode { + $config() { + return this.config('extended-test-list-item', {extends: ListItemNode}); + } +} + +function $createExtendedTestListItemNode(): ExtendedTestListItemNode { + return new ExtendedTestListItemNode(); +} + +function $isExtendedTestListItemNode(node?: LexicalNode | null) { + return node instanceof ExtendedTestListItemNode; +} + +const initOptions = { + nodes: [ExtendedTestListNode, ExtendedTestListItemNode], +}; describe('insertList', () => { initializeUnitTest((testEnv) => { @@ -298,5 +339,114 @@ describe('$handleListInsertParagraph', () => { expect((children[0] as ListNode).getChildrenSize()).toBe(3); }); }); - }); + + test('splits list when the empty element is not the last one', async () => { + const {editor} = testEnv; + registerList(editor); + + let emptyListItemKey: string; + await editor.update(() => { + const firstListItemWithContent = $createExtendedTestListItemNode(); + const secondListItemWithContent = $createExtendedTestListItemNode(); + const listItemEmpty = $createExtendedTestListItemNode(); + emptyListItemKey = listItemEmpty.getKey(); + const listNode = $createExtendedTestListNode('bullet'); + firstListItemWithContent.append($createTextNode('item1')); + secondListItemWithContent.append($createTextNode('item2')); + listNode.append( + firstListItemWithContent, + listItemEmpty, + secondListItemWithContent, + ); + $getRoot().append(listNode); + listItemEmpty.select(); + editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined); + }); + + editor.read(() => { + const children = $getRoot().getChildren(); + const firstList = children[0] as ExtendedTestListNode; + const secondList = children[2] as ExtendedTestListNode; + + expect(children.length).toBe(3); + expect($isListNode(children[0])).toBe(true); + expect($isParagraphNode(children[1])).toBe(true); + expect($isListNode(children[2])).toBe(true); + expect(firstList.getChildrenSize()).toBe(1); + expect(secondList.getChildrenSize()).toBe(1); + expect($getNodeByKey(emptyListItemKey)).toBeNull(); + // check that the new list is of the same type + expect(secondList).toBeInstanceOf(firstList.constructor); + expect(firstList.getListType()).toBe(secondList.getListType()); + }); + }); + }, initOptions); +}); + +describe('$handleIndent', () => { + initializeUnitTest( + (testEnv) => { + test('creates a new nested sublist', async () => { + const {editor} = testEnv; + + await editor.update(() => { + const root = $getRoot(); + const listNode = $createExtendedTestListNode('bullet'); + const listItem1 = $createExtendedTestListItemNode(); + const listItem2 = $createExtendedTestListItemNode(); + + listNode.append(listItem1, listItem2); + root.append(listNode); + + $handleIndent(listItem2); + + // new item keeps the same type + const newListItem2 = + listNode.getChildren()[1] as ExtendedTestListItemNode; + expect($isExtendedTestListItemNode(newListItem2)).toBe(true); + expect(newListItem2.getChildren().length).toBe(1); + + // nested list contains the original list item + const nestedList = + newListItem2.getChildren()[0] as ExtendedTestListNode; + expect($isExtendedTestListNode(nestedList)).toBe(true); + expect(nestedList.getChildren().length).toBe(1); + expect(nestedList.getChildren()[0].is(listItem2)).toBe(true); + }); + }); + }, + {nodes: [ExtendedTestListNode, ExtendedTestListItemNode]}, + ); +}); + +describe('$handleOutdent', () => { + initializeUnitTest((testEnv) => { + test('removes the nested list and replaces list item', async () => { + const {editor} = testEnv; + + await editor.update(() => { + const root = $getRoot(); + const listNode = $createExtendedTestListNode('bullet'); + const listItem1 = $createExtendedTestListItemNode(); + const listItem2 = $createExtendedTestListItemNode(); + const indentedListItem = $createExtendedTestListItemNode(); + + listNode.append( + listItem1, + listItem2.append( + $createExtendedTestListNode('bullet').append(indentedListItem), + ), + ); + root.append(listNode); + + $handleOutdent(indentedListItem); + + const children = listNode.getChildren(); + // item is outdented and doesn't have nested list + expect(children.length).toBe(2); + expect(children[1].is(indentedListItem)).toBe(true); + expect(indentedListItem.getChildren().length).toBe(0); + }); + }); + }, initOptions); }); diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 65a13605c9b..5802e0a8028 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -8,6 +8,7 @@ import {$getNearestNodeOfType} from '@lexical/utils'; import { + $copyNode, $createParagraphNode, $getChildCaret, $getSelection, @@ -415,12 +416,8 @@ export function $handleIndent(listItemNode: ListItemNode): void { // otherwise, we need to create a new nested ListNode if ($isListNode(parent)) { - const newListItem = $createListItemNode() - .setTextFormat(listItemNode.getTextFormat()) - .setTextStyle(listItemNode.getTextStyle()); - const newList = $createListNode(parent.getListType()) - .setTextFormat(parent.getTextFormat()) - .setTextStyle(parent.getTextStyle()); + const newListItem = $copyNode(listItemNode); + const newList = $copyNode(parent); newListItem.append(newList); newList.append(listItemNode); @@ -480,15 +477,14 @@ export function $handleOutdent(listItemNode: ListItemNode): void { } } else { // otherwise, we need to split the siblings into two new nested lists - const listType = parentList.getListType(); - const previousSiblingsListItem = $createListItemNode(); - const previousSiblingsList = $createListNode(listType); + const previousSiblingsListItem = $copyNode(listItemNode); + const previousSiblingsList = $copyNode(parentList); previousSiblingsListItem.append(previousSiblingsList); listItemNode .getPreviousSiblings() .forEach((sibling) => previousSiblingsList.append(sibling)); - const nextSiblingsListItem = $createListItemNode(); - const nextSiblingsList = $createListNode(listType); + const nextSiblingsListItem = $copyNode(listItemNode); + const nextSiblingsList = $copyNode(parentList); nextSiblingsListItem.append(nextSiblingsList); append(nextSiblingsList, listItemNode.getNextSiblings()); // put the sibling nested lists on either side of the grandparent list item in the great grandparent. @@ -560,7 +556,7 @@ export function $handleListInsertParagraph( replacementNode = $createParagraphNode(); topListNode.insertAfter(replacementNode); } else if ($isListItemNode(grandparent)) { - replacementNode = $createListItemNode(); + replacementNode = $copyNode(grandparent); grandparent.insertAfter(replacementNode); } else { return false; @@ -574,10 +570,10 @@ export function $handleListInsertParagraph( if (nextSiblings.length > 0) { const newStart = restoreNumbering ? $getNewListStart(parent, listItem) : 1; - const newList = $createListNode(parent.getListType(), newStart); + const newList = $copyNode(parent).setStart(newStart); if ($isListItemNode(replacementNode)) { - const newListItem = $createListItemNode(); + const newListItem = $copyNode(replacementNode); newListItem.append(newList); replacementNode.insertAfter(newListItem); } else { From 3dde93739b073a2365db2798d586b272e940a51e Mon Sep 17 00:00:00 2001 From: Sergey Gorbachev Date: Mon, 16 Mar 2026 18:28:26 +0300 Subject: [PATCH 2/4] [lexical-link]: Refactor: add `afterCloneFrom` method to LinkNode/AutoLinkNode (#8226) --- packages/lexical-link/src/LexicalLinkNode.ts | 34 ++++++++++++------- .../__tests__/unit/LexicalLinkNode.test.ts | 21 ++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/packages/lexical-link/src/LexicalLinkNode.ts b/packages/lexical-link/src/LexicalLinkNode.ts index 40fd5aee4dc..8239cf15fc0 100644 --- a/packages/lexical-link/src/LexicalLinkNode.ts +++ b/packages/lexical-link/src/LexicalLinkNode.ts @@ -111,6 +111,14 @@ export class LinkNode extends ElementNode { this.__title = title; } + afterCloneFrom(prevNode: this): void { + super.afterCloneFrom(prevNode); + this.__url = prevNode.__url; + this.__rel = prevNode.__rel; + this.__target = prevNode.__target; + this.__title = prevNode.__target; + } + createDOM(config: EditorConfig): LinkHTMLElementType { const element = document.createElement('a'); this.updateLinkDOM(null, element, config); @@ -240,11 +248,7 @@ export class LinkNode extends ElementNode { _: RangeSelection, restoreSelection = true, ): null | ElementNode { - const linkNode = $createLinkNode(this.__url, { - rel: this.__rel, - target: this.__target, - title: this.__title, - }); + const linkNode = $copyNode(this); this.insertAfter(linkNode, restoreSelection); return linkNode; } @@ -257,7 +261,7 @@ export class LinkNode extends ElementNode { return false; } - canBeEmpty(): false { + canBeEmpty(): boolean { return false; } @@ -326,6 +330,7 @@ export function $linkNodeTransform(link: LinkNode): void { focusPair = $saveCaretPair(selection.focus); } + let transformed = false; for (const caret of $getChildCaret(link, 'next')) { const node = caret.origin; if ($isElementNode(node) && !node.isInline()) { @@ -334,13 +339,17 @@ export function $linkNodeTransform(link: LinkNode): void { const innerLink = $copyNode(link); innerLink.append(...blockChildren); node.append(innerLink); + transformed = true; } $insertNodeToNearestRootAtCaret(node, $rewindSiblingCaret(caret), { $shouldSplit: () => false, }); } } - if (link.isEmpty()) { + if (!transformed) { + return; + } + if (!link.canBeEmpty() && link.isEmpty()) { const parent = link.getParent(); link.remove(); if (parent && parent.isEmpty()) { @@ -420,6 +429,11 @@ export class AutoLinkNode extends LinkNode { : false; } + afterCloneFrom(prevNode: this): void { + super.afterCloneFrom(prevNode); + this.__isUnlinked = prevNode.__isUnlinked; + } + static getType(): string { return 'autolink'; } @@ -647,11 +661,7 @@ function $splitLinkAtSelection( const trailingChildren = allChildren.slice(lastExtractedIndex + 1); if (trailingChildren.length > 0) { - const newLink = $createLinkNode(parentLink.getURL(), { - rel: parentLink.getRel(), - target: parentLink.getTarget(), - title: parentLink.getTitle(), - }); + const newLink = $copyNode(parentLink); extractedChildren[extractedChildren.length - 1].insertAfter(newLink); trailingChildren.forEach((child) => newLink.append(child)); diff --git a/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts b/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts index c8872af3371..fb3ac439dcb 100644 --- a/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts +++ b/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts @@ -1266,4 +1266,25 @@ describe('LinkNode transform (Regression #8083)', () => { expect(selection.anchor.offset).toBe(7); }); }); + + test('an empty link is not deleted if the transformation did not occur', () => { + const editor = buildEditorFromExtensions(transformExtension); + let linkKey: string; + editor.update( + () => { + const root = $getRoot(); + const link = $createLinkNode('https://lexical.dev'); + linkKey = link.getKey(); + const paragraph = $createParagraphNode(); + paragraph.append(link); + root.clear().append(paragraph); + link.select(); + }, + {discrete: true}, + ); + editor.read(() => { + const linkNode = $getNodeByKey(linkKey); + expect(linkNode).not.toBe(null); + }); + }); }); From d7de6f0a2810ed14fe21369cca7813f2353b02a6 Mon Sep 17 00:00:00 2001 From: Ajinkya Nikam Date: Mon, 16 Mar 2026 22:38:57 +0530 Subject: [PATCH 3/4] [lexical] Fix: Fixing cursor position after inline equation, fix block equations (#8228) Co-authored-by: Bob Ippolito --- .../__tests__/e2e/EquationNode.spec.mjs | 107 ++++++++++++++++++ packages/lexical-playground/src/index.css | 4 + .../src/nodes/EquationNode.tsx | 21 +++- .../src/plugins/EquationsPlugin/index.tsx | 12 +- .../src/ui/KatexEquationAlterer.tsx | 13 ++- 5 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 packages/lexical-playground/__tests__/e2e/EquationNode.spec.mjs diff --git a/packages/lexical-playground/__tests__/e2e/EquationNode.spec.mjs b/packages/lexical-playground/__tests__/e2e/EquationNode.spec.mjs new file mode 100644 index 00000000000..5cecd1c901b --- /dev/null +++ b/packages/lexical-playground/__tests__/e2e/EquationNode.spec.mjs @@ -0,0 +1,107 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + assertHTML, + click, + focus, + focusEditor, + html, + initialize, + selectFromInsertDropdown, + test, + waitForSelector, +} from '../utils/index.mjs'; + +export async function insertBlockEquation(page, equation) { + await selectFromInsertDropdown(page, '.equation'); + await click(page, 'input[data-test-id="equation-inline-checkbox"]'); + await focus(page, 'textarea[data-test-id="equation-input"]'); + await page.keyboard.type(equation); + await click(page, 'button[data-test-id="equation-submit-btn"]'); +} + +function equationHtml(inline = true) { + const tag = inline ? 'span' : 'div'; + return `<${tag} + class="editor-equation" + contenteditable="false" + data-lexical-decorator="true"> + + + ${inline ? '' : ``} + + + + ${inline ? '' : ``} + + + `; +} + +test.describe('EquationNode', () => { + test.beforeEach(({isCollab, isPlainText, page}) => { + test.skip(isPlainText); + return initialize({ + isCollab, + page, + }); + }); + test('inline EquationNode is wrapped in a paragraph', async ({ + page, + isCollab, + }) => { + await focusEditor(page); + await page.keyboard.type('$1$'); + await waitForSelector(page, '.editor-equation'); + + await assertHTML( + page, + html` +

+ ${equationHtml(true)} +
+

+ `, + ); + }); + test('block EquationNode is a child of the root', async ({ + page, + isCollab, + }) => { + await focusEditor(page); + await insertBlockEquation(page, '1'); + await waitForSelector(page, '.editor-equation'); + + await assertHTML( + page, + html` +

+
+

+ ${equationHtml(false)} +

+
+

+ `, + ); + }); +}); diff --git a/packages/lexical-playground/src/index.css b/packages/lexical-playground/src/index.css index 9e18413d342..23a57f94e93 100644 --- a/packages/lexical-playground/src/index.css +++ b/packages/lexical-playground/src/index.css @@ -1699,6 +1699,10 @@ button.toolbar-item.active i { user-select: none; } +span.editor-equation { + display: inline-block; +} + .editor-equation.focused { outline: 2px solid rgb(60, 132, 244); } diff --git a/packages/lexical-playground/src/nodes/EquationNode.tsx b/packages/lexical-playground/src/nodes/EquationNode.tsx index c32aa5bea3c..5db9e63482d 100644 --- a/packages/lexical-playground/src/nodes/EquationNode.tsx +++ b/packages/lexical-playground/src/nodes/EquationNode.tsx @@ -58,12 +58,18 @@ export class EquationNode extends DecoratorNode { return new EquationNode(node.__equation, node.__inline, node.__key); } - constructor(equation: string, inline?: boolean, key?: NodeKey) { + constructor(equation: string = '', inline?: boolean, key?: NodeKey) { super(key); this.__equation = equation; this.__inline = inline ?? false; } + afterCloneFrom(prevNode: this): void { + super.afterCloneFrom(prevNode); + this.__equation = prevNode.__equation; + this.__inline = prevNode.__inline; + } + static importJSON(serializedNode: SerializedEquationNode): EquationNode { return $createEquationNode( serializedNode.equation, @@ -75,7 +81,7 @@ export class EquationNode extends DecoratorNode { return { ...super.exportJSON(), equation: this.getEquation(), - inline: this.__inline, + inline: this.isInline(), }; } @@ -132,16 +138,21 @@ export class EquationNode extends DecoratorNode { } getTextContent(): string { - return this.__equation; + return this.getEquation(); + } + + isInline(): boolean { + return this.getLatest().__inline; } getEquation(): string { - return this.__equation; + return this.getLatest().__equation; } - setEquation(equation: string): void { + setEquation(equation: string): this { const writable = this.getWritable(); writable.__equation = equation; + return this; } decorate(): JSX.Element { diff --git a/packages/lexical-playground/src/plugins/EquationsPlugin/index.tsx b/packages/lexical-playground/src/plugins/EquationsPlugin/index.tsx index 46f1bf7ae1e..16b4ebaa3b7 100644 --- a/packages/lexical-playground/src/plugins/EquationsPlugin/index.tsx +++ b/packages/lexical-playground/src/plugins/EquationsPlugin/index.tsx @@ -11,7 +11,7 @@ import type {JSX} from 'react'; import 'katex/dist/katex.css'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; -import {$wrapNodeInElement} from '@lexical/utils'; +import {$insertNodeToNearestRoot, $wrapNodeInElement} from '@lexical/utils'; import { $createParagraphNode, $insertNodes, @@ -69,9 +69,13 @@ export default function EquationsPlugin(): JSX.Element | null { const {equation, inline} = payload; const equationNode = $createEquationNode(equation, inline); - $insertNodes([equationNode]); - if ($isRootOrShadowRoot(equationNode.getParentOrThrow())) { - $wrapNodeInElement(equationNode, $createParagraphNode).selectEnd(); + if (inline) { + $insertNodes([equationNode]); + if ($isRootOrShadowRoot(equationNode.getParentOrThrow())) { + $wrapNodeInElement(equationNode, $createParagraphNode).selectEnd(); + } + } else { + $insertNodeToNearestRoot(equationNode); } return true; diff --git a/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx b/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx index ab3dd38abbc..abe9ead8b92 100644 --- a/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx +++ b/packages/lexical-playground/src/ui/KatexEquationAlterer.tsx @@ -43,7 +43,12 @@ export default function KatexEquationAlterer({ <>
Inline - +
Equation
@@ -54,6 +59,7 @@ export default function KatexEquationAlterer({ }} value={equation} className="KatexEquationAlterer_textArea" + data-test-id="equation-input" /> ) : (