Skip to content

Commit

Permalink
[lexical-selection] Bug Fix / Fixes text formatting with segmented an…
Browse files Browse the repository at this point in the history
…d token nodes #6059 (#6062)
  • Loading branch information
lacroixdavid1 authored May 22, 2024
1 parent 53f6f1d commit 2dac7f5
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,112 @@ describe('LexicalSelection tests', () => {
],
name: 'Format selection that starts on element and ends on text and retain selection',
},

{
expectedHTML:
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true">' +
'<p class="editor-paragraph"><br></p>' +
'<p class="editor-paragraph" dir="ltr">' +
'<strong class="editor-text-bold" data-lexical-text="true">Hello</strong><strong class="editor-text-bold" data-lexical-text="true"> world</strong>' +
'</p>' +
'<p class="editor-paragraph"><br></p>' +
'</div>',
expectedSelection: {
anchorOffset: 2,
anchorPath: [1, 0, 0],
focusOffset: 0,
focusPath: [2],
},
inputs: [
insertParagraph(),
insertTokenNode('Hello'),
insertText(' world'),
insertParagraph(),
moveNativeSelection([1, 0, 0], 2, [2], 0),
formatBold(),
],
name: 'Format selection that starts on middle of token node should format complete node',
},

{
expectedHTML:
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true">' +
'<p class="editor-paragraph"><br></p>' +
'<p class="editor-paragraph" dir="ltr">' +
'<strong class="editor-text-bold" data-lexical-text="true">Hello </strong><strong class="editor-text-bold" data-lexical-text="true">world</strong>' +
'</p>' +
'<p class="editor-paragraph"><br></p>' +
'</div>',
expectedSelection: {
anchorOffset: 0,
anchorPath: [0],
focusOffset: 2,
focusPath: [1, 1, 0],
},
inputs: [
insertParagraph(),
insertText('Hello '),
insertTokenNode('world'),
insertParagraph(),
moveNativeSelection([0], 0, [1, 1, 0], 2),
formatBold(),
],
name: 'Format selection that ends on middle of token node should format complete node',
},

{
expectedHTML:
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true">' +
'<p class="editor-paragraph"><br></p>' +
'<p class="editor-paragraph" dir="ltr">' +
'<strong class="editor-text-bold" data-lexical-text="true">Hello</strong><span data-lexical-text="true"> world</span>' +
'</p>' +
'<p class="editor-paragraph"><br></p>' +
'</div>',
expectedSelection: {
anchorOffset: 2,
anchorPath: [1, 0, 0],
focusOffset: 3,
focusPath: [1, 0, 0],
},
inputs: [
insertParagraph(),
insertTokenNode('Hello'),
insertText(' world'),
insertParagraph(),
moveNativeSelection([1, 0, 0], 2, [1, 0, 0], 3),
formatBold(),
],
name: 'Format token node if it is the single one selected',
},

{
expectedHTML:
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true">' +
'<p class="editor-paragraph"><br></p>' +
'<p class="editor-paragraph" dir="ltr">' +
'<strong class="editor-text-bold" data-lexical-text="true">Hello </strong><strong class="editor-text-bold" data-lexical-text="true">beautiful</strong><strong class="editor-text-bold" data-lexical-text="true"> world</strong>' +
'</p>' +
'<p class="editor-paragraph"><br></p>' +
'</div>',
expectedSelection: {
anchorOffset: 0,
anchorPath: [0],
focusOffset: 0,
focusPath: [2],
},
inputs: [
insertParagraph(),
insertText('Hello '),
insertTokenNode('beautiful'),
insertText(' world'),
insertParagraph(),
moveNativeSelection([0], 0, [2], 0),
formatBold(),
],
name: 'Format selection that contains a token node in the middle should format the token node',
},

// Tests need fixing:
// ...GRAPHEME_SCENARIOS.flatMap(({description, grapheme}) => [
// {
Expand Down
20 changes: 12 additions & 8 deletions packages/lexical/src/LexicalSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1132,8 +1132,11 @@ export class RangeSelection implements BaseSelection {
if (startOffset === endOffset) {
return;
}
// The entire node is selected, so just format it
if (startOffset === 0 && endOffset === firstNode.getTextContentSize()) {
// The entire node is selected or it is token, so just format it
if (
$isTokenOrSegmented(firstNode) ||
(startOffset === 0 && endOffset === firstNode.getTextContentSize())
) {
firstNode.setFormat(firstNextFormat);
} else {
// Node is partially selected, so split it into two nodes
Expand All @@ -1157,7 +1160,7 @@ export class RangeSelection implements BaseSelection {
}
// Multiple nodes selected
// The entire first node isn't selected, so split it
if (startOffset !== 0) {
if (startOffset !== 0 && !$isTokenOrSegmented(firstNode)) {
[, firstNode as TextNode] = firstNode.splitText(startOffset);
startOffset = 0;
}
Expand All @@ -1167,7 +1170,10 @@ export class RangeSelection implements BaseSelection {
// If the offset is 0, it means no actual characters are selected,
// so we skip formatting the last node altogether.
if (endOffset > 0) {
if (endOffset !== lastNode.getTextContentSize()) {
if (
endOffset !== lastNode.getTextContentSize() &&
!$isTokenOrSegmented(lastNode)
) {
[lastNode as TextNode] = lastNode.splitText(endOffset);
}
lastNode.setFormat(lastNextFormat);
Expand All @@ -1176,10 +1182,8 @@ export class RangeSelection implements BaseSelection {
// Process all text nodes in between
for (let i = firstIndex + 1; i < lastIndex; i++) {
const textNode = selectedTextNodes[i];
if (!textNode.isToken()) {
const nextFormat = textNode.getFormatFlags(formatType, lastNextFormat);
textNode.setFormat(nextFormat);
}
const nextFormat = textNode.getFormatFlags(formatType, lastNextFormat);
textNode.setFormat(nextFormat);
}

// Update selection only if starts/ends on text node
Expand Down

0 comments on commit 2dac7f5

Please sign in to comment.