Skip to content

Commit f505ffe

Browse files
authored
[Breaking Change][lexical][lexical-link] Bug Fix: Collapse through inline elements in deleteCharacter (#7180)
1 parent ad2511c commit f505ffe

File tree

3 files changed

+128
-9
lines changed

3 files changed

+128
-9
lines changed

packages/lexical-playground/__tests__/e2e/List.spec.mjs

+93
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
moveRight,
1515
moveToEditorBeginning,
1616
moveToEditorEnd,
17+
moveToLineBeginning,
1718
moveToParagraphEnd,
19+
pressBackspace,
1820
redo,
1921
selectAll,
2022
selectCharacters,
@@ -1946,4 +1948,95 @@ test.describe.parallel('Nested List', () => {
19461948
`,
19471949
);
19481950
});
1951+
test('collapseAtStart for trivial bullet list', async ({page}) => {
1952+
await focusEditor(page);
1953+
await toggleBulletList(page);
1954+
await assertHTML(
1955+
page,
1956+
html`
1957+
<ul class="PlaygroundEditorTheme__ul">
1958+
<li class="PlaygroundEditorTheme__listItem" value="1">
1959+
<br />
1960+
</li>
1961+
</ul>
1962+
`,
1963+
);
1964+
await pressBackspace(page);
1965+
await assertHTML(
1966+
page,
1967+
html`
1968+
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
1969+
`,
1970+
);
1971+
});
1972+
test('collapseAtStart for bullet list with text', async ({page}) => {
1973+
await focusEditor(page);
1974+
await toggleBulletList(page);
1975+
await page.keyboard.type('Hello World');
1976+
await moveToLineBeginning(page);
1977+
await assertHTML(
1978+
page,
1979+
html`
1980+
<ul class="PlaygroundEditorTheme__ul">
1981+
<li
1982+
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
1983+
dir="ltr"
1984+
value="1">
1985+
<span data-lexical-text="true">Hello World</span>
1986+
</li>
1987+
</ul>
1988+
`,
1989+
);
1990+
await pressBackspace(page);
1991+
await assertHTML(
1992+
page,
1993+
html`
1994+
<p
1995+
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
1996+
dir="ltr">
1997+
<span data-lexical-text="true">Hello World</span>
1998+
</p>
1999+
`,
2000+
);
2001+
});
2002+
test('collapseAtStart for bullet list with text inside autolink', async ({
2003+
page,
2004+
}) => {
2005+
await focusEditor(page);
2006+
await toggleBulletList(page);
2007+
await page.keyboard.type('www.example.com');
2008+
await moveToLineBeginning(page);
2009+
await assertHTML(
2010+
page,
2011+
html`
2012+
<ul class="PlaygroundEditorTheme__ul">
2013+
<li
2014+
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
2015+
dir="ltr"
2016+
value="1">
2017+
<a
2018+
class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr"
2019+
dir="ltr"
2020+
href="https://www.example.com">
2021+
<span data-lexical-text="true">www.example.com</span>
2022+
</a>
2023+
</li>
2024+
</ul>
2025+
`,
2026+
);
2027+
await pressBackspace(page);
2028+
await assertHTML(
2029+
page,
2030+
html`
2031+
<p class="PlaygroundEditorTheme__paragraph">
2032+
<a
2033+
class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr"
2034+
dir="ltr"
2035+
href="https://www.example.com">
2036+
<span data-lexical-text="true">www.example.com</span>
2037+
</a>
2038+
</p>
2039+
`,
2040+
);
2041+
});
19492042
});

packages/lexical/src/LexicalSelection.ts

+27-7
Original file line numberDiff line numberDiff line change
@@ -1842,11 +1842,7 @@ export class RangeSelection implements BaseSelection {
18421842
$updateCaretSelectionForUnicodeCharacter(this, isBackward);
18431843
} else if (isBackward && anchor.offset === 0) {
18441844
// Special handling around rich text nodes
1845-
const element =
1846-
anchor.type === 'element'
1847-
? anchor.getNode()
1848-
: anchor.getNode().getParentOrThrow();
1849-
if (element.collapseAtStart(this)) {
1845+
if ($collapseAtStart(this, anchor.getNode())) {
18501846
return;
18511847
}
18521848
}
@@ -1863,9 +1859,9 @@ export class RangeSelection implements BaseSelection {
18631859
if (
18641860
anchorNode.isEmpty() &&
18651861
$isRootNode(anchorNode.getParent()) &&
1866-
anchorNode.getIndexWithinParent() === 0
1862+
anchorNode.getPreviousSibling() === null
18671863
) {
1868-
anchorNode.collapseAtStart(this);
1864+
$collapseAtStart(this, anchorNode);
18691865
}
18701866
}
18711867
}
@@ -1972,6 +1968,30 @@ export function $getCharacterOffsets(
19721968
return [getCharacterOffset(anchor), getCharacterOffset(focus)];
19731969
}
19741970

1971+
function $collapseAtStart(
1972+
selection: RangeSelection,
1973+
startNode: LexicalNode,
1974+
): boolean {
1975+
for (
1976+
let node: null | LexicalNode = startNode;
1977+
node;
1978+
node = node.getParent()
1979+
) {
1980+
if ($isElementNode(node)) {
1981+
if (node.collapseAtStart(selection)) {
1982+
return true;
1983+
}
1984+
if (!node.isInline()) {
1985+
break;
1986+
}
1987+
}
1988+
if (node.getPreviousSibling()) {
1989+
break;
1990+
}
1991+
}
1992+
return false;
1993+
}
1994+
19751995
function $swapPoints(selection: RangeSelection): void {
19761996
const focus = selection.focus;
19771997
const anchor = selection.anchor;

packages/lexical/src/nodes/LexicalElementNode.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -880,9 +880,15 @@ export class ElementNode extends LexicalNode {
880880
return true;
881881
}
882882
/*
883-
* This method controls the behavior of a the node during backwards
883+
* This method controls the behavior of the node during backwards
884884
* deletion (i.e., backspace) when selection is at the beginning of
885-
* the node (offset 0)
885+
* the node (offset 0). You may use this to have the node replace
886+
* itself, change its state, or do nothing. When you do make such
887+
* a change, you should return true.
888+
*
889+
* When true is returned, the collapse phase will stop.
890+
* When false is returned, and isInline() is true, and getPreviousSibling() is null,
891+
* then this function will be called on its parent.
886892
*/
887893
collapseAtStart(selection: RangeSelection): boolean {
888894
return false;

0 commit comments

Comments
 (0)