Skip to content

Commit

Permalink
[lexical] Bug Fix: Fix getNodes over-selection (#7006)
Browse files Browse the repository at this point in the history
  • Loading branch information
etrepum authored Jan 1, 2025
1 parent 803391d commit aaa9009
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* 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 {
deleteBackward,
moveToLineBeginning,
} from '../keyboardShortcuts/index.mjs';
import {
assertHTML,
focusEditor,
html,
initialize,
test,
} from '../utils/index.mjs';

test.describe('Regression tests for #6974', () => {
test.beforeEach(({isPlainText, isCollab, page}) =>
initialize({isCollab, isPlainText, page}),
);

test(`deleteCharacter merges children from adjacent blocks even if the previous leaf is an inline decorator`, async ({
page,
isCollab,
isPlainText,
}) => {
test.skip(isCollab || isPlainText);
await focusEditor(page);
const testEquation = '$x$';
const testString = 'test';
await page.keyboard.type(testEquation);
await page.keyboard.press('Enter');
await page.keyboard.type(testString);
const beforeHtml = html`
<p>
<span contenteditable="false" data-lexical-decorator="true">
<img alt="" src="#" />
<span role="button" tabindex="-1">
<span>
<span aria-hidden="true">
<span>
<span></span>
<span>x</span>
</span>
</span>
</span>
</span>
<img alt="" src="#" />
</span>
<br />
</p>
<p dir="ltr"><span data-lexical-text="true">test</span></p>
`;
await assertHTML(page, beforeHtml, beforeHtml, {
ignoreClasses: true,
ignoreInlineStyles: true,
});
await moveToLineBeginning(page);
await deleteBackward(page);
const afterHtml = html`
<p dir="ltr">
<span contenteditable="false" data-lexical-decorator="true">
<img alt="" src="#" />
<span role="button" tabindex="-1">
<span>
<span aria-hidden="true">
<span>
<span></span>
<span>x</span>
</span>
</span>
</span>
</span>
<img alt="" src="#" />
</span>
<span data-lexical-text="true">test</span>
</p>
`;
await assertHTML(page, afterHtml, afterHtml, {
ignoreClasses: true,
ignoreInlineStyles: true,
});
});
});
23 changes: 21 additions & 2 deletions packages/lexical/src/LexicalSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ export class Point {
_selection: BaseSelection | null;

constructor(key: NodeKey, offset: number, type: 'text' | 'element') {
if (__DEV__) {
// This prevents a circular reference error when serialized as JSON,
// which happens on unit test failures
Object.defineProperty(this, '_selection', {
enumerable: false,
writable: true,
});
}
this._selection = null;
this.key = key;
this.offset = offset;
Expand Down Expand Up @@ -473,6 +481,10 @@ export class RangeSelection implements BaseSelection {
const lastPoint = isBefore ? focus : anchor;
let firstNode = firstPoint.getNode();
let lastNode = lastPoint.getNode();
const overselectedFirstNode =
$isElementNode(firstNode) &&
firstPoint.offset > 0 &&
firstPoint.offset >= firstNode.getChildrenSize();
const startOffset = firstPoint.offset;
const endOffset = lastPoint.offset;

Expand Down Expand Up @@ -506,6 +518,13 @@ export class RangeSelection implements BaseSelection {
}
} else {
nodes = firstNode.getNodesBetween(lastNode);
// Prevent over-selection due to the edge case of getDescendantByIndex always returning something #6974
if (overselectedFirstNode) {
const deleteCount = nodes.findIndex(
(node) => !node.is(firstNode) && !node.isBefore(firstNode),
);
nodes.splice(0, deleteCount);
}
}
if (!isCurrentlyReadOnlyMode()) {
this._cachedNodes = nodes;
Expand Down Expand Up @@ -1129,7 +1148,7 @@ export class RangeSelection implements BaseSelection {
lastPoint.offset = lastNode.getTextContentSize();
}

selectedNodes.forEach((node) => {
for (const node of selectedNodes) {
if (
!$hasAncestor(firstNode, node) &&
!$hasAncestor(lastNode, node) &&
Expand All @@ -1138,7 +1157,7 @@ export class RangeSelection implements BaseSelection {
) {
node.remove();
}
});
}

const fixText = (node: TextNode, del: number) => {
if (node.getTextContent() === '') {
Expand Down

0 comments on commit aaa9009

Please sign in to comment.