From c2e445612b38841197fc9341992248e69b9329c4 Mon Sep 17 00:00:00 2001 From: Brian Birtles Date: Tue, 18 Oct 2022 12:13:20 +0900 Subject: [PATCH] Fix $patchStyleText when the selection is anchored to the end of a text node (#3116) Specifically when the next sibling is not a text node or there is no next sibling. Without the code changes in this patch, the two test cases will throw errors when trying to use the updated `firstNode`. --- .../unit/LexicalSelectionHelpers.test.ts | 105 +++++++++++++++++- .../lexical-selection/src/lexical-node.ts | 5 +- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts b/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts index bc42f0bf792..29452235d85 100644 --- a/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts +++ b/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts @@ -8,7 +8,7 @@ import {$createLinkNode} from '@lexical/link'; import {$createHeadingNode} from '@lexical/rich-text'; -import {$cloneContents} from '@lexical/selection'; +import {$cloneContents, $patchStyleText} from '@lexical/selection'; import { $createParagraphNode, $createTextNode, @@ -17,6 +17,7 @@ import { $getSelection, $isNodeSelection, $isRangeSelection, + RangeSelection, TextNode, } from 'lexical'; import { @@ -2830,3 +2831,105 @@ describe('insertNodes', () => { ); }); }); + +describe('$patchStyleText', () => { + test('can patch a selection anchored to the end of a TextNode before an inline element', async () => { + const editor = createTestEditor(); + const element = document.createElement('div'); + editor.setRootElement(element); + + await editor.update(() => { + const root = $getRoot(); + + const paragraph = createParagraphWithNodes(editor, [ + { + key: 'a', + mergeable: false, + text: 'a', + }, + { + key: 'b', + mergeable: false, + text: 'b', + }, + ]); + + root.append(paragraph); + + const link = $createLinkNode('https://'); + link.append($createTextNode('link')); + + const a = $getNodeByKey('a'); + a.insertAfter(link); + + setAnchorPoint({ + key: 'a', + offset: 1, + type: 'text', + }); + setFocusPoint({ + key: 'b', + offset: 1, + type: 'text', + }); + + const selection = $getSelection() as RangeSelection; + $patchStyleText(selection, {'text-emphasis': 'filled'}); + }); + + expect(element.innerHTML).toBe( + '

a' + + '' + + 'link' + + '' + + 'b

', + ); + }); + + test('can patch a selection anchored to the end of a TextNode at the end of a paragraph', async () => { + const editor = createTestEditor(); + const element = document.createElement('div'); + editor.setRootElement(element); + + await editor.update(() => { + const root = $getRoot(); + + const paragraph1 = createParagraphWithNodes(editor, [ + { + key: 'a', + mergeable: false, + text: 'a', + }, + ]); + const paragraph2 = createParagraphWithNodes(editor, [ + { + key: 'b', + mergeable: false, + text: 'b', + }, + ]); + + root.append(paragraph1); + root.append(paragraph2); + + setAnchorPoint({ + key: 'a', + offset: 1, + type: 'text', + }); + setFocusPoint({ + key: 'b', + offset: 1, + type: 'text', + }); + + const selection = $getSelection() as RangeSelection; + $patchStyleText(selection, {'text-emphasis': 'filled'}); + }); + + expect(element.innerHTML).toBe( + '

a

' + + '

b

', + ); + }); +}); diff --git a/packages/lexical-selection/src/lexical-node.ts b/packages/lexical-selection/src/lexical-node.ts index b80227a8c61..c5aa3e50792 100644 --- a/packages/lexical-selection/src/lexical-node.ts +++ b/packages/lexical-selection/src/lexical-node.ts @@ -373,7 +373,10 @@ export function $patchStyleText( } } // multiple nodes selected. } else { - if ($isTextNode(firstNode)) { + if ( + $isTextNode(firstNode) && + startOffset < firstNode.getTextContentSize() + ) { if (startOffset !== 0) { // the entire first node isn't selected, so split it firstNode = firstNode.splitText(startOffset)[1];