Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into LexicalNode-afterClon…
Browse files Browse the repository at this point in the history
…eFrom
  • Loading branch information
etrepum committed Aug 12, 2024
2 parents 78eba94 + db4c743 commit 7b8cc9c
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 67 deletions.
31 changes: 10 additions & 21 deletions packages/lexical/src/LexicalNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,27 +351,16 @@ export class LexicalNode {

const parentNode = this.getParent();
if ($isDecoratorNode(this) && this.isInline() && parentNode) {
const {anchor, focus} = targetSelection;

if (anchor.isBefore(focus)) {
const anchorNode = anchor.getNode() as ElementNode;
const isAnchorPointToLast =
anchor.offset === anchorNode.getChildrenSize();
const isAnchorNodeIsParent = anchorNode.is(parentNode);
const isLastChild = anchorNode.getLastChildOrThrow().is(this);

if (isAnchorPointToLast && isAnchorNodeIsParent && isLastChild) {
return false;
}
} else {
const focusNode = focus.getNode() as ElementNode;
const isFocusPointToLast =
focus.offset === focusNode.getChildrenSize();
const isFocusNodeIsParent = focusNode.is(parentNode);
const isLastChild = focusNode.getLastChildOrThrow().is(this);
if (isFocusPointToLast && isFocusNodeIsParent && isLastChild) {
return false;
}
const firstPoint = targetSelection.isBackward()
? targetSelection.focus
: targetSelection.anchor;
const firstElement = firstPoint.getNode() as ElementNode;
if (
firstPoint.offset === firstElement.getChildrenSize() &&
firstElement.is(parentNode) &&
firstElement.getLastChildOrThrow().is(this)
) {
return false;
}
}
}
Expand Down
30 changes: 28 additions & 2 deletions packages/lexical/src/LexicalReconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,18 @@ function reconcileElementTerminatingLineBreak(
const element = dom.__lexicalLineBreak;

if (element != null) {
dom.removeChild(element);
try {
dom.removeChild(element);
} catch (error) {
if (typeof error === 'object' && error != null) {
const msg = `${error.toString()} Parent: ${dom.tagName}, child: ${
element.tagName
}.`;
throw new Error(msg);
} else {
throw error;
}
}
}

// @ts-expect-error: internal field
Expand Down Expand Up @@ -501,7 +512,22 @@ function $reconcileChildren(
} else {
const lastDOM = getPrevElementByKeyOrThrow(prevFirstChildKey);
const replacementDOM = $createNode(nextFrstChildKey, null, null);
dom.replaceChild(replacementDOM, lastDOM);
try {
dom.replaceChild(replacementDOM, lastDOM);
} catch (error) {
if (typeof error === 'object' && error != null) {
const msg = `${error.toString()} Parent: ${
dom.tagName
}, new child: {tag: ${
replacementDOM.tagName
} key: ${nextFrstChildKey}}, old child: {tag: ${
lastDOM.tagName
}, key: ${prevFirstChildKey}}.`;
throw new Error(msg);
} else {
throw error;
}
}
destroyNode(prevFirstChildKey, null);
}
const nextChildNode = activeNextNodeMap.get(nextFrstChildKey);
Expand Down
203 changes: 159 additions & 44 deletions packages/lexical/src/__tests__/unit/LexicalNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import {
$isDecoratorNode,
$isElementNode,
$isRangeSelection,
$setSelection,
createEditor,
DecoratorNode,
ElementNode,
LexicalEditor,
NodeKey,
ParagraphNode,
RangeSelection,
SerializedTextNode,
TextNode,
} from 'lexical';
Expand Down Expand Up @@ -375,54 +379,165 @@ describe('LexicalNode tests', () => {
await Promise.resolve().then();
});

test('LexicalNode.isSelected(): with inline decorator node', async () => {
const {editor} = testEnv;
describe('LexicalNode.isSelected(): with inline decorator node', () => {
let editor: LexicalEditor;
let paragraphNode1: ParagraphNode;
let paragraphNode2: ParagraphNode;
let paragraphNode3: ParagraphNode;
let inlineDecoratorNode: InlineDecoratorNode;

editor.update(() => {
paragraphNode1 = $createParagraphNode();
paragraphNode2 = $createParagraphNode();
inlineDecoratorNode = new InlineDecoratorNode();
paragraphNode1.append(inlineDecoratorNode);
$getRoot().append(paragraphNode1, paragraphNode2);
paragraphNode1.selectEnd();
const selection = $getSelection();

if ($isRangeSelection(selection)) {
expect(selection.anchor.getNode().is(paragraphNode1)).toBe(true);
}
});

editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
expect(selection.anchor.key).toBe(paragraphNode1.getKey());

selection.focus.set(paragraphNode2.getKey(), 1, 'element');
}
});

await Promise.resolve().then();

editor.getEditorState().read(() => {
expect(inlineDecoratorNode.isSelected()).toBe(false);
});

editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
selection.anchor.set(paragraphNode2.getKey(), 0, 'element');
selection.focus.set(paragraphNode1.getKey(), 1, 'element');
}
});

await Promise.resolve().then();

editor.getEditorState().read(() => {
expect(inlineDecoratorNode.isSelected()).toBe(false);
let names: Record<NodeKey, string>;
beforeEach(() => {
editor = testEnv.editor;
editor.update(() => {
inlineDecoratorNode = new InlineDecoratorNode();
paragraphNode1 = $createParagraphNode();
paragraphNode2 = $createParagraphNode().append(inlineDecoratorNode);
paragraphNode3 = $createParagraphNode();
names = {
[inlineDecoratorNode.getKey()]: 'd',
[paragraphNode1.getKey()]: 'p1',
[paragraphNode2.getKey()]: 'p2',
[paragraphNode3.getKey()]: 'p3',
};
$getRoot()
.clear()
.append(paragraphNode1, paragraphNode2, paragraphNode3);
});
});
const cases: {
label: string;
isSelected: boolean;
update: () => void;
}[] = [
{
isSelected: true,
label: 'whole editor',
update() {
$getRoot().select(0);
},
},
{
isSelected: true,
label: 'containing paragraph',
update() {
paragraphNode2.select(0);
},
},
{
isSelected: true,
label: 'before and containing',
update() {
paragraphNode2
.select(0)
.anchor.set(paragraphNode1.getKey(), 0, 'element');
},
},
{
isSelected: true,
label: 'containing and after',
update() {
paragraphNode2
.select(0)
.focus.set(paragraphNode3.getKey(), 0, 'element');
},
},
{
isSelected: true,
label: 'before and after',
update() {
paragraphNode1
.select(0)
.focus.set(paragraphNode3.getKey(), 0, 'element');
},
},
{
isSelected: false,
label: 'collapsed before',
update() {
paragraphNode2.select(0, 0);
},
},
{
isSelected: false,
label: 'in another element',
update() {
paragraphNode1.select(0);
},
},
{
isSelected: false,
label: 'before',
update() {
paragraphNode1
.select(0)
.focus.set(paragraphNode2.getKey(), 0, 'element');
},
},
{
isSelected: false,
label: 'collapsed after',
update() {
paragraphNode2.selectEnd();
},
},
{
isSelected: false,
label: 'after',
update() {
paragraphNode3
.select(0)
.anchor.set(
paragraphNode2.getKey(),
paragraphNode2.getChildrenSize(),
'element',
);
},
},
];
for (const {label, isSelected, update} of cases) {
test(`${isSelected ? 'is' : "isn't"} selected ${label}`, () => {
editor.update(update);
const $verify = () => {
const selection = $getSelection() as RangeSelection;
expect($isRangeSelection(selection)).toBe(true);
const dbg = [selection.anchor, selection.focus]
.map(
(point) =>
`(${names[point.key] || point.key}:${point.offset})`,
)
.join(' ');
const nodes = `[${selection
.getNodes()
.map((k) => names[k.__key] || k.__key)
.join(',')}]`;
expect([dbg, nodes, inlineDecoratorNode.isSelected()]).toEqual([
dbg,
nodes,
isSelected,
]);
};
editor.read($verify);
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const backwards = $createRangeSelection();
backwards.anchor.set(
selection.focus.key,
selection.focus.offset,
selection.focus.type,
);
backwards.focus.set(
selection.anchor.key,
selection.anchor.offset,
selection.anchor.type,
);
$setSelection(backwards);
}
expect($isRangeSelection(selection)).toBe(true);
});
editor.read($verify);
});
}
});

test('LexicalNode.getKey()', async () => {
Expand Down

0 comments on commit 7b8cc9c

Please sign in to comment.