🐛 Fix wrong typography font size in sidebar when selecting text in Firefox (editor v2)

This commit is contained in:
Alejandro Alonso
2026-03-25 10:01:26 +01:00
parent 6268a8aaf1
commit 334039668d
4 changed files with 62 additions and 1 deletions

View File

@@ -291,6 +291,13 @@ export class TextNodeIterator {
break;
}
}
// The loop exits when currentNode === endNode without yielding endNode.
// Callers (e.g. selection style merge) must visit every text/BR node in the
// range, including the last one, or the final span is omitted (e.g. empty
// paragraph with only <br>) and the sidebar shows "mixed" incorrectly.
if (this.#currentNode === endNode) {
yield this.#currentNode;
}
}
}

View File

@@ -70,4 +70,29 @@ describe("TextNodeIterator", () => {
textNodeIterator.nextNode();
expect(textNodeIterator.currentNode.nodeValue).toBe("Whatever");
});
test("collectFrom includes the end node (iterateFrom must yield end inclusive)", () => {
const rootNode = createRoot([
createParagraph([createTextSpan(new Text("Hello"))]),
createParagraph([createTextSpan(createLineBreak())]),
]);
const firstText = rootNode.firstChild.firstChild.firstChild;
const br = rootNode.lastChild.firstChild.firstChild;
const textNodeIterator = new TextNodeIterator(rootNode);
const nodes = textNodeIterator.collectFrom(firstText, br);
expect(nodes.length).toBe(2);
expect(nodes[0]).toBe(firstText);
expect(nodes[1]).toBe(br);
});
test("collectFrom with identical start and end returns one node", () => {
const rootNode = createRoot([
createParagraph([createTextSpan(new Text("Hi"))]),
]);
const text = rootNode.firstChild.firstChild.firstChild;
const textNodeIterator = new TextNodeIterator(rootNode);
const nodes = textNodeIterator.collectFrom(text, text);
expect(nodes.length).toBe(1);
expect(nodes[0]).toBe(text);
});
});

View File

@@ -403,7 +403,12 @@ export class SelectionController extends EventTarget {
this.#updateCurrentStyle(textSpan);
} else {
// SELECTION.
this.#updateCurrentStyleFrom(this.#anchorNode, this.#focusNode);
// Use range boundaries normalized to text nodes, not anchor/focus.
// Firefox may set anchorNode on the paragraph element and focusNode on a
// text node for word selection; passing those to #updateCurrentStyleFrom
// breaks TextNodeIterator and yields wrong styles (e.g. default 14px).
const { startNode, endNode } = this.getRanges();
this.#updateCurrentStyleFrom(startNode, endNode);
}
this.dispatchEvent(
new CustomEvent("stylechange", {

View File

@@ -1666,6 +1666,30 @@ describe("SelectionController", () => {
expect(selectionController.focusAtEnd).toBeTruthy();
})
test("`currentStyle` uses text span font-size when anchor is paragraph (Firefox-style word selection)", () => {
const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([
createParagraph([
createTextSpan(new Text("Hello World"), { "font-size": "36" }),
]),
]);
const root = textEditorMock.root;
const paragraph = root.firstChild;
const textNode = paragraph.firstChild.firstChild;
const selection = document.getSelection();
const selectionController = new SelectionController(
textEditorMock,
selection,
);
textEditorMock.element.focus();
// Anchor on the paragraph (child offset 0) and focus in the text node — matches
// Firefox when double-click selects a word; anchor/focus are not both text nodes.
selection.setBaseAndExtent(paragraph, 0, textNode, 5);
document.dispatchEvent(new Event("selectionchange"));
expect(selectionController.currentStyle.getPropertyValue("font-size")).toBe(
"36px",
);
});
test("`dispose` should release every held reference", () => {
const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([
createParagraphWith(["Hello, "], {