🐛 Fix text selection

This commit is contained in:
Elena Torro
2025-10-28 10:59:49 +01:00
parent e18aef1d39
commit 6edc29dce2
2 changed files with 67 additions and 8 deletions

View File

@@ -9,6 +9,7 @@
"coverage": "vitest run --coverage",
"wasm:update": "cp ../resources/public/js/render_wasm.wasm ./src/wasm/render_wasm.wasm && cp ../resources/public/js/render_wasm.js ./src/wasm/render_wasm.js",
"test": "vitest --run",
"fmt:js": "yarn run prettier -c src/**/*.js",
"test:watch": "vitest",
"test:watch:ui": "vitest --ui",
"test:watch:e2e": "vitest --browser"

View File

@@ -1263,6 +1263,7 @@ export class SelectionController extends EventTarget {
replaceText(newText) {
const startOffset = Math.min(this.anchorOffset, this.focusOffset);
const endOffset = Math.max(this.anchorOffset, this.focusOffset);
if (this.isTextFocus) {
this.focusNode.nodeValue = replaceWith(
this.focusNode.nodeValue,
@@ -1279,7 +1280,11 @@ export class SelectionController extends EventTarget {
this.focusNode.replaceChildren(newParagraph);
return this.collapse(newTextNode, newText.length + 1);
} else {
throw new Error("Unknown node type");
const newTextNode = new Text(newText);
const newTextSpan = createTextSpan(newTextNode, this.#currentStyle);
const newParagraph = createParagraph([newTextSpan], this.#currentStyle);
this.#textEditor.root.replaceChildren(newParagraph);
return this.collapse(newTextNode, newText.length + 1);
}
this.#mutations.update(this.focusTextSpan);
return this.collapse(this.focusNode, startOffset + newText.length);
@@ -1538,15 +1543,15 @@ export class SelectionController extends EventTarget {
const affectedTextSpans = new Set();
const affectedParagraphs = new Set();
const startNode = getClosestTextNode(this.#range.startContainer);
const endNode = getClosestTextNode(this.#range.endContainer);
const startOffset = this.#range.startOffset;
const endOffset = this.#range.endOffset;
let previousNode = null;
let nextNode = null;
let { startNode, endNode, startOffset, endOffset } = this.getRanges();
if (this.shouldHandleCompleteDeletion(startNode, endNode)) {
return this.handleCompleteContentDeletion();
}
// This is the simplest case, when the startNode and
// the endNode are the same and they're textNodes.
if (startNode === endNode) {
@@ -1612,7 +1617,8 @@ export class SelectionController extends EventTarget {
} else if (currentNode === endNode) {
if (
isLineBreak(endNode) ||
(isTextNode(endNode) && endOffset === (endNode.nodeValue?.length || 0))
(isTextNode(endNode) &&
endOffset === (endNode.nodeValue?.length || 0))
) {
// We should remove this node completely.
shouldRemoveNodeCompletely = true;
@@ -1694,6 +1700,58 @@ export class SelectionController extends EventTarget {
return this.collapse(startNode, startOffset);
}
getRanges() {
let startNode = getClosestTextNode(this.#range.startContainer);
let endNode = getClosestTextNode(this.#range.endContainer);
let startOffset = this.#range.startOffset;
let endOffset = this.#range.startOffset + this.#range.toString().length;
return { startNode, endNode, startOffset, endOffset };
}
shouldHandleCompleteDeletion(startNode, endNode) {
const root = this.#textEditor.root;
return (
(startNode &&
endNode &&
this.#range.toString() === (root.textContent || "") &&
root.textContent.length > 0) ||
!startNode ||
!endNode
);
}
/**
* Handles complete content deletion when all content is selected.
* @returns {SelectionController}
*/
handleCompleteContentDeletion() {
const root = this.#textEditor.root;
const firstParagraph = root.firstElementChild;
const newTextSpan = createEmptyTextSpan(this.#currentStyle);
if (!newTextSpan.firstChild) {
newTextSpan.appendChild(new Text(""));
}
if (firstParagraph) {
firstParagraph.replaceChildren(newTextSpan);
let currentParagraph = firstParagraph.nextElementSibling;
while (currentParagraph) {
const nextParagraph = currentParagraph.nextElementSibling;
currentParagraph.remove();
currentParagraph = nextParagraph;
}
} else {
const newParagraph = createEmptyParagraph();
newParagraph.appendChild(newTextSpan);
root.replaceChildren(newParagraph);
}
return this.collapse(newTextSpan.firstChild, 0);
}
/**
* Applies styles from the startNode to the endNode.
*