Skip to content

Commit 42d39f9

Browse files
[lexical][lexical-clipboard][lexical-playground][lexical-react][lexical-selection][lexical-table][lexical-utils] Simplify word, line and symbol deletion in Shadow DOM
1 parent 9ec8637 commit 42d39f9

File tree

1 file changed

+61
-120
lines changed

1 file changed

+61
-120
lines changed

packages/lexical/src/LexicalSelection.ts

Lines changed: 61 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,48 +1747,24 @@ export class RangeSelection implements BaseSelection {
17471747
return;
17481748
}
17491749

1750-
// Only handle if we're in a Shadow DOM context
1750+
// Special handling for Shadow DOM where browser selection might not work
17511751
const editor = getActiveEditor();
17521752
const rootElement = editor.getRootElement();
17531753
if (rootElement && isShadowRoot(getShadowRootOrDocument(rootElement))) {
1754-
// Only handle collapsed selections for character deletion
1755-
if (this.isCollapsed()) {
1756-
// Only handle text nodes
1757-
if ($isTextNode(anchorNode)) {
1758-
const textContent = anchorNode.getTextContent();
1759-
const offset = anchor.offset;
1760-
1761-
// Simple deletion logic
1762-
if (isBackward) {
1763-
// Backspace: delete character before cursor
1764-
if (offset > 0) {
1765-
const newText =
1766-
textContent.slice(0, offset - 1) + textContent.slice(offset);
1767-
anchorNode.setTextContent(newText);
1768-
1769-
// Update selection position
1770-
const newOffset = offset - 1;
1771-
anchor.set(anchor.key, newOffset, anchor.type);
1772-
this.focus.set(this.focus.key, newOffset, this.focus.type);
1773-
this.dirty = true;
1774-
1775-
return;
1776-
}
1777-
} else {
1778-
// Delete: delete character after cursor
1779-
if (offset < textContent.length) {
1780-
const newText =
1781-
textContent.slice(0, offset) + textContent.slice(offset + 1);
1782-
anchorNode.setTextContent(newText);
1783-
1784-
// Keep cursor at same position
1785-
anchor.set(anchor.key, offset, anchor.type);
1786-
this.focus.set(this.focus.key, offset, this.focus.type);
1787-
this.dirty = true;
1788-
1789-
return;
1790-
}
1791-
}
1754+
if ($isTextNode(anchorNode) && anchor.type === 'text') {
1755+
const textContent = anchorNode.getTextContent();
1756+
const offset = anchor.offset;
1757+
1758+
if (isBackward && offset > 0) {
1759+
// Select the character before cursor and remove it
1760+
this.anchor.set(anchor.key, offset - 1, 'text');
1761+
this.removeText();
1762+
return;
1763+
} else if (!isBackward && offset < textContent.length) {
1764+
// Select the character after cursor and remove it
1765+
this.focus.set(this.focus.key, offset + 1, 'text');
1766+
this.removeText();
1767+
return;
17921768
}
17931769
}
17941770
}
@@ -1972,39 +1948,26 @@ export class RangeSelection implements BaseSelection {
19721948
}
19731949

19741950
const anchor = this.anchor;
1975-
const focus = this.focus;
19761951
const anchorNode = anchor.getNode();
19771952

1978-
if ($isTextNode(anchorNode)) {
1953+
if ($isTextNode(anchorNode) && anchor.type === 'text') {
19791954
const textContent = anchorNode.getTextContent();
19801955
const offset = anchor.offset;
19811956

19821957
if (isBackward) {
19831958
// Cmd+Backspace: delete from beginning of line to cursor
19841959
if (offset > 0) {
1985-
const newText = textContent.slice(offset);
1986-
anchorNode.setTextContent(newText);
1987-
1988-
// Move cursor to beginning of line (position 0)
1989-
anchor.set(anchor.key, 0, anchor.type);
1990-
focus.set(focus.key, 0, focus.type);
1991-
1992-
// Mark selection as dirty to force reconciliation
1993-
this.dirty = true;
1960+
// Select from beginning to current position
1961+
this.anchor.set(anchor.key, 0, 'text');
1962+
this.removeText();
19941963
return;
19951964
}
19961965
} else {
19971966
// Cmd+Delete: delete from cursor to end of line
19981967
if (offset < textContent.length) {
1999-
const newText = textContent.slice(0, offset);
2000-
anchorNode.setTextContent(newText);
2001-
2002-
// Keep cursor at same position
2003-
anchor.set(anchor.key, offset, anchor.type);
2004-
focus.set(focus.key, offset, focus.type);
2005-
2006-
// Mark selection as dirty to force reconciliation
2007-
this.dirty = true;
1968+
// Select from current position to end
1969+
this.focus.set(this.focus.key, textContent.length, 'text');
1970+
this.removeText();
20081971
return;
20091972
}
20101973
}
@@ -2037,80 +2000,58 @@ export class RangeSelection implements BaseSelection {
20372000
return;
20382001
}
20392002

2040-
// Try Shadow DOM direct deletion first for better reliability
2003+
// Special handling for Shadow DOM where browser selection might not work
20412004
const editor = getActiveEditor();
20422005
const rootElement = editor.getRootElement();
20432006
if (rootElement && isShadowRoot(getShadowRootOrDocument(rootElement))) {
2044-
// Use simple word boundary detection for Shadow DOM
2045-
2046-
if (!this.isCollapsed()) {
2047-
// If there's already a selection, just remove it
2048-
this.removeText();
2049-
return;
2050-
}
2051-
2052-
const focus = this.focus;
2053-
2054-
if ($isTextNode(anchorNode)) {
2007+
if ($isTextNode(anchorNode) && anchor.type === 'text') {
20552008
const textContent = anchorNode.getTextContent();
20562009
const offset = anchor.offset;
20572010

2058-
if (isBackward) {
2059-
// Option+Backspace: delete word before cursor
2060-
if (offset > 0) {
2061-
// Find word boundary
2062-
let wordStart = offset;
2063-
2064-
// Skip trailing spaces
2065-
while (wordStart > 0 && /\s/.test(textContent[wordStart - 1])) {
2066-
wordStart--;
2067-
}
2011+
if (isBackward && offset > 0) {
2012+
// Find word boundary before cursor
2013+
let wordStart = offset;
20682014

2069-
// Find start of word
2070-
while (wordStart > 0 && !/\s/.test(textContent[wordStart - 1])) {
2071-
wordStart--;
2072-
}
2015+
// Skip trailing spaces
2016+
while (wordStart > 0 && /\s/.test(textContent[wordStart - 1])) {
2017+
wordStart--;
2018+
}
20732019

2074-
const newText =
2075-
textContent.slice(0, wordStart) + textContent.slice(offset);
2076-
anchorNode.setTextContent(newText);
2020+
// Find start of word
2021+
while (wordStart > 0 && !/\s/.test(textContent[wordStart - 1])) {
2022+
wordStart--;
2023+
}
20772024

2078-
// Move cursor to word start
2079-
anchor.set(anchor.key, wordStart, anchor.type);
2080-
focus.set(focus.key, wordStart, focus.type);
2081-
this.dirty = true;
2025+
if (wordStart < offset) {
2026+
// Select from word start to current position
2027+
this.anchor.set(anchor.key, wordStart, 'text');
2028+
this.removeText();
20822029
return;
20832030
}
2084-
} else {
2085-
// Option+Delete: delete word after cursor
2086-
if (offset < textContent.length) {
2087-
// Find word boundary
2088-
let wordEnd = offset;
2089-
2090-
// Skip leading spaces
2091-
while (
2092-
wordEnd < textContent.length &&
2093-
/\s/.test(textContent[wordEnd])
2094-
) {
2095-
wordEnd++;
2096-
}
2097-
2098-
// Find end of word
2099-
while (
2100-
wordEnd < textContent.length &&
2101-
!/\s/.test(textContent[wordEnd])
2102-
) {
2103-
wordEnd++;
2104-
}
2031+
} else if (!isBackward && offset < textContent.length) {
2032+
// Find word boundary after cursor
2033+
let wordEnd = offset;
2034+
2035+
// Skip leading spaces
2036+
while (
2037+
wordEnd < textContent.length &&
2038+
/\s/.test(textContent[wordEnd])
2039+
) {
2040+
wordEnd++;
2041+
}
21052042

2106-
const newText =
2107-
textContent.slice(0, offset) + textContent.slice(wordEnd);
2108-
anchorNode.setTextContent(newText);
2043+
// Find end of word
2044+
while (
2045+
wordEnd < textContent.length &&
2046+
!/\s/.test(textContent[wordEnd])
2047+
) {
2048+
wordEnd++;
2049+
}
21092050

2110-
// Keep cursor at same position
2111-
anchor.set(anchor.key, offset, anchor.type);
2112-
focus.set(focus.key, offset, focus.type);
2113-
this.dirty = true;
2051+
if (wordEnd > offset) {
2052+
// Select from current position to word end
2053+
this.focus.set(this.focus.key, wordEnd, 'text');
2054+
this.removeText();
21142055
return;
21152056
}
21162057
}

0 commit comments

Comments
 (0)