Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/lexical/src/__tests__/unit/LexicalSelection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,53 @@ function mapLatest<T extends LexicalNode>(nodes: T[]): T[] {

describe('LexicalSelection tests', () => {
initializeUnitTest(testEnv => {
test('programmatic selection movement updates format and style to match target node', async () => {
const {editor} = testEnv;

// 1. Setup two differently formatted nodes
await editor.update(() => {
const root = $getRoot();
const paragraph = $createParagraphNode();

// We will make text1 bold via the selection below
const text1 = $createTextNode('Hello');
const text2 = $createTextNode(' World')
.toggleFormat('italic')
.setStyle('color: red;');

paragraph.append(text1, text2);
root.append(paragraph);

// Select the first node and format the selection to bold
text1.select(0, 5);
const selection = $getSelection();
if ($isRangeSelection(selection)) {
selection.formatText('bold');
}
});

// 2. Verify initial selection is bold (This will pass now!)
await editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
expect(selection.hasFormat('bold')).toBe(true);
expect(selection.hasFormat('italic')).toBe(false);
}

// Programmatically jump to the end of the document (which lands exactly on the italic/red text2)
$getRoot().selectEnd();
});

// 3. Verification: format and style must exactly match text2 (italic, red, NOT bold)
await editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
expect(selection.hasFormat('bold')).toBe(false);
expect(selection.hasFormat('italic')).toBe(true);
expect(selection.style).toBe('color: red;');
}
});
});
describe('Inserting text either side of inline elements', () => {
const setup = async (
mode: 'start-of-paragraph' | 'mid-paragraph' | 'end-of-paragraph',
Expand Down
8 changes: 8 additions & 0 deletions packages/lexical/src/nodes/LexicalElementNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,17 @@ export class ElementNode
'element',
);
} else {
const isDifferentNode =
selection.anchor.key !== key || selection.focus.key !== key;

selection.anchor.set(key, anchorOffset, 'element');
selection.focus.set(key, focusOffset, 'element');
selection.dirty = true;

if (isDifferentNode) {
selection.format = this.getTextFormat();
selection.style = this.getTextStyle();
}
}
return selection;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/lexical/src/nodes/LexicalTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,9 @@ export class TextNode extends LexicalNode implements InlineFormattableNode {
'text',
);
} else {
const isDifferentNode =
selection.anchor.key !== key || selection.focus.key !== key;

const compositionKey = $getCompositionKey();
if (
compositionKey === selection.anchor.key ||
Expand All @@ -905,6 +908,11 @@ export class TextNode extends LexicalNode implements InlineFormattableNode {
$setCompositionKey(key);
}
selection.setTextNodeRange(this, anchorOffset, this, focusOffset);

if (isDifferentNode) {
selection.format = this.getFormat();
selection.style = this.getStyle();
}
}
return selection;
}
Expand Down
Loading