fix: update wysiwyg color on theme change (#10618)

This commit is contained in:
David Luzar
2026-01-07 12:55:33 +01:00
committed by GitHub
parent 87faa5d3da
commit cc6c29c0b9
2 changed files with 72 additions and 0 deletions
@@ -8,7 +8,10 @@ import {
KEYS, KEYS,
FONT_FAMILY, FONT_FAMILY,
TEXT_ALIGN, TEXT_ALIGN,
THEME,
VERTICAL_ALIGN, VERTICAL_ALIGN,
applyDarkModeFilter,
tinycolor,
} from "@excalidraw/common"; } from "@excalidraw/common";
import type { import type {
@@ -22,6 +25,7 @@ import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { getTextEditor, updateTextEditor } from "../tests/queries/dom"; import { getTextEditor, updateTextEditor } from "../tests/queries/dom";
import { import {
GlobalTestState, GlobalTestState,
act,
render, render,
screen, screen,
unmountComponent, unmountComponent,
@@ -1662,4 +1666,60 @@ describe("textWysiwyg", () => {
expect(h.elements[1].angle).toBe(30); expect(h.elements[1].angle).toBe(30);
}); });
}); });
describe("Test theme change", () => {
const { h } = window;
// Helper to compare colors (browser may return rgb format)
const colorsAreEqual = (color1: string, color2: string) => {
return tinycolor(color1).toHex() === tinycolor(color2).toHex();
};
beforeEach(async () => {
await render(
<Excalidraw
handleKeyboardGlobally={true}
initialData={{
appState: {
theme: THEME.LIGHT,
},
}}
/>,
);
API.setElements([]);
});
it("should update textarea color when theme changes to dark mode and back", async () => {
const originalColor = "#ff0000";
const textElement = API.createElement({
type: "text",
text: "test",
strokeColor: originalColor,
});
API.setElements([textElement]);
mouse.doubleClickOn(textElement as ExcalidrawTextElement);
const editor = await getTextEditor({ waitForEditor: true });
expect(colorsAreEqual(editor.style.color, originalColor)).toBe(true);
act(() => {
h.setState({ theme: THEME.DARK });
// Trigger element mutation to fire onChange callback
h.app.scene.mutateElement(textElement, {});
});
expect(
colorsAreEqual(editor.style.color, applyDarkModeFilter(originalColor)),
).toBe(true);
act(() => {
h.setState({ theme: THEME.LIGHT });
h.app.scene.mutateElement(textElement, {});
});
expect(colorsAreEqual(editor.style.color, originalColor)).toBe(true);
});
});
}); });
@@ -132,7 +132,11 @@ export const textWysiwyg = ({
return false; return false;
}; };
let LAST_THEME = app.state.theme;
const updateWysiwygStyle = () => { const updateWysiwygStyle = () => {
LAST_THEME = app.state.theme;
const appState = app.state; const appState = app.state;
const updatedTextElement = app.scene.getElement<ExcalidrawTextElement>(id); const updatedTextElement = app.scene.getElement<ExcalidrawTextElement>(id);
@@ -598,6 +602,7 @@ export const textWysiwyg = ({
window.removeEventListener("blur", handleSubmit); window.removeEventListener("blur", handleSubmit);
window.removeEventListener("beforeunload", handleSubmit); window.removeEventListener("beforeunload", handleSubmit);
unbindUpdate(); unbindUpdate();
unsubOnChange();
unbindOnScroll(); unbindOnScroll();
editable.remove(); editable.remove();
@@ -696,6 +701,13 @@ export const textWysiwyg = ({
} }
}; };
// FIXME after we start emitting updates from Store for appState.theme
const unsubOnChange = app.onChangeEmitter.on((elements) => {
if (app.state.theme !== LAST_THEME) {
updateWysiwygStyle();
}
});
// handle updates of textElement properties of editing element // handle updates of textElement properties of editing element
const unbindUpdate = app.scene.onUpdate(() => { const unbindUpdate = app.scene.onUpdate(() => {
updateWysiwygStyle(); updateWysiwygStyle();