From 6393d7a2adb2653353e09ecf2fd15050f6c3d408 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 24 Dec 2024 18:22:35 +0530 Subject: [PATCH 1/4] [lexical-list] Bug Fix: Prevent error when calling formatList when selection is at root --- packages/lexical-list/src/formatList.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 46694253ebb..18902c90542 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -9,11 +9,13 @@ import {$getNearestNodeOfType} from '@lexical/utils'; import { $createParagraphNode, + $getRoot, $getSelection, $isElementNode, $isLeafNode, $isRangeSelection, $isRootOrShadowRoot, + $setSelection, ElementNode, LexicalEditor, LexicalNode, @@ -77,6 +79,20 @@ export function insertList(editor: LexicalEditor, listType: ListType): void { const anchorNode = anchor.getNode(); const anchorNodeParent = anchorNode.getParent(); + if (anchor.key === 'root') { + const root = $getRoot(); + const firstChild = root.getFirstChild(); + if (firstChild) { + $setSelection(firstChild.selectStart()); + } else { + const paragraph = $createParagraphNode(); + root.append(paragraph); + $setSelection(paragraph.select()); + } + insertList(editor, listType); + return; + } + if ($isSelectingEmptyListItem(anchorNode, nodes)) { const list = $createListNode(listType); From 4c7e080ed880707dbed0ce151b5024719c52f11a Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 27 Dec 2024 01:41:05 +0530 Subject: [PATCH 2/4] Use rootOrShadowRoot guard, remove recursion and add test --- .../src/__tests__/unit/formatList.test.ts | 72 +++++++++++++++++++ packages/lexical-list/src/formatList.ts | 17 ++--- 2 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 packages/lexical-list/src/__tests__/unit/formatList.test.ts diff --git a/packages/lexical-list/src/__tests__/unit/formatList.test.ts b/packages/lexical-list/src/__tests__/unit/formatList.test.ts new file mode 100644 index 00000000000..2c04ec68ee3 --- /dev/null +++ b/packages/lexical-list/src/__tests__/unit/formatList.test.ts @@ -0,0 +1,72 @@ +/** + * 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 { + $createTableCellNode, + $createTableNode, + $createTableRowNode, + TableCellNode, + TableNode, +} from '@lexical/table'; +import {$getRoot} from 'lexical'; +import {initializeUnitTest} from 'lexical/src/__tests__/utils'; + +import {TableRowNode} from '../../../../lexical-table/LexicalTable'; +import {insertList} from '../../formatList'; +import {$isListNode} from '../../LexicalListNode'; + +describe('insertList', () => { + initializeUnitTest((testEnv) => { + test('inserting with empty root selection', async () => { + const {editor} = testEnv; + + await editor.update(() => { + $getRoot().select(); + }); + + insertList(editor, 'number'); + + editor.read(() => { + const root = $getRoot(); + + expect(root.getChildrenSize()).toBe(1); + + const firstChild = root.getFirstChildOrThrow(); + + expect($isListNode(firstChild)).toBe(true); + }); + }); + + test('inserting with empty shadow root selection', async () => { + const {editor} = testEnv; + + await editor.update(() => { + const table = $createTableNode(); + const row = $createTableRowNode(); + const cell = $createTableCellNode(); + $getRoot().append(table.append(row.append(cell))); + cell.select(); + }); + + insertList(editor, 'number'); + + editor.read(() => { + const cell = $getRoot() + .getFirstChildOrThrow() + .getFirstChildOrThrow() + .getFirstChildOrThrow(); + + expect(cell.getChildrenSize()).toBe(1); + + const firstChild = cell.getFirstChildOrThrow(); + + expect($isListNode(firstChild)).toBe(true); + }); + }); + }); +}); diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 18902c90542..f6af85e7e7c 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -9,13 +9,11 @@ import {$getNearestNodeOfType} from '@lexical/utils'; import { $createParagraphNode, - $getRoot, $getSelection, $isElementNode, $isLeafNode, $isRangeSelection, $isRootOrShadowRoot, - $setSelection, ElementNode, LexicalEditor, LexicalNode, @@ -68,7 +66,7 @@ export function insertList(editor: LexicalEditor, listType: ListType): void { const selection = $getSelection(); if (selection !== null) { - const nodes = selection.getNodes(); + let nodes = selection.getNodes(); if ($isRangeSelection(selection)) { const anchorAndFocus = selection.getStartEndPoints(); invariant( @@ -79,18 +77,15 @@ export function insertList(editor: LexicalEditor, listType: ListType): void { const anchorNode = anchor.getNode(); const anchorNodeParent = anchorNode.getParent(); - if (anchor.key === 'root') { - const root = $getRoot(); - const firstChild = root.getFirstChild(); + if ($isRootOrShadowRoot(anchorNode)) { + const firstChild = anchorNode.getFirstChild(); if (firstChild) { - $setSelection(firstChild.selectStart()); + nodes = firstChild.selectStart().getNodes(); } else { const paragraph = $createParagraphNode(); - root.append(paragraph); - $setSelection(paragraph.select()); + anchorNode.append(paragraph); + nodes = paragraph.select().getNodes(); } - insertList(editor, listType); - return; } if ($isSelectingEmptyListItem(anchorNode, nodes)) { From ce1a1596cbc68418402d06539dbfdf1ce5ea1cd5 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 27 Dec 2024 01:43:52 +0530 Subject: [PATCH 3/4] fix import --- packages/lexical-list/src/__tests__/unit/formatList.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lexical-list/src/__tests__/unit/formatList.test.ts b/packages/lexical-list/src/__tests__/unit/formatList.test.ts index 2c04ec68ee3..4f5915d98ab 100644 --- a/packages/lexical-list/src/__tests__/unit/formatList.test.ts +++ b/packages/lexical-list/src/__tests__/unit/formatList.test.ts @@ -12,11 +12,11 @@ import { $createTableRowNode, TableCellNode, TableNode, + TableRowNode, } from '@lexical/table'; import {$getRoot} from 'lexical'; import {initializeUnitTest} from 'lexical/src/__tests__/utils'; -import {TableRowNode} from '../../../../lexical-table/LexicalTable'; import {insertList} from '../../formatList'; import {$isListNode} from '../../LexicalListNode'; From ad3abba44c132d32f602d33f35312bc7033a1939 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 27 Dec 2024 11:15:34 +0530 Subject: [PATCH 4/4] make condition mutually exclusive --- .../src/__tests__/unit/formatList.test.ts | 25 ++++++++++++++++++- packages/lexical-list/src/formatList.ts | 4 +-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/lexical-list/src/__tests__/unit/formatList.test.ts b/packages/lexical-list/src/__tests__/unit/formatList.test.ts index 4f5915d98ab..37753354521 100644 --- a/packages/lexical-list/src/__tests__/unit/formatList.test.ts +++ b/packages/lexical-list/src/__tests__/unit/formatList.test.ts @@ -14,7 +14,7 @@ import { TableNode, TableRowNode, } from '@lexical/table'; -import {$getRoot} from 'lexical'; +import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical'; import {initializeUnitTest} from 'lexical/src/__tests__/utils'; import {insertList} from '../../formatList'; @@ -42,6 +42,29 @@ describe('insertList', () => { }); }); + test('inserting in root selection with existing child', async () => { + const {editor} = testEnv; + + await editor.update(() => { + $getRoot().select(); + $getRoot().append( + $createParagraphNode().append($createTextNode('hello')), + ); + }); + + insertList(editor, 'number'); + + editor.read(() => { + const root = $getRoot(); + + expect(root.getChildrenSize()).toBe(1); + + const firstChild = root.getFirstChildOrThrow(); + + expect($isListNode(firstChild)).toBe(true); + }); + }); + test('inserting with empty shadow root selection', async () => { const {editor} = testEnv; diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index f6af85e7e7c..54e150619a0 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -86,9 +86,7 @@ export function insertList(editor: LexicalEditor, listType: ListType): void { anchorNode.append(paragraph); nodes = paragraph.select().getNodes(); } - } - - if ($isSelectingEmptyListItem(anchorNode, nodes)) { + } else if ($isSelectingEmptyListItem(anchorNode, nodes)) { const list = $createListNode(listType); if ($isRootOrShadowRoot(anchorNodeParent)) {