Skip to content

Commit 09fe15b

Browse files
committed
fix(editor): prevent IME selection flicker
1 parent 6ed8495 commit 09fe15b

3 files changed

Lines changed: 60 additions & 1 deletion

File tree

packages/editor/src/codemirror/live-markdown.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { forceParsing } from "@codemirror/language";
22
import { EditorSelection, EditorState, type Extension } from "@codemirror/state";
3-
import { EditorView } from "@codemirror/view";
3+
import { drawSelection, EditorView } from "@codemirror/view";
44
import { afterEach, describe, expect, it, vi } from "vitest";
55
import { liveMarkdown } from "./index.ts";
66

@@ -378,6 +378,44 @@ describe("liveMarkdown", () => {
378378
});
379379
});
380380

381+
it("hides CodeMirror's drawn selection while an IME composition is active", () => {
382+
const view = createView({
383+
doc: "Compose",
384+
selection: EditorSelection.range(0, 3),
385+
extensions: [drawSelection(), liveMarkdown()],
386+
});
387+
const selectionLayer = view.dom.querySelector<HTMLElement>(
388+
".cm-selectionLayer",
389+
);
390+
if (!selectionLayer) throw new Error("Expected CodeMirror's selection layer");
391+
392+
const selectionBackground = document.createElement("div");
393+
selectionBackground.className = "cm-selectionBackground";
394+
selectionLayer.append(selectionBackground);
395+
396+
expect(getComputedStyle(selectionBackground).backgroundColor).not.toBe(
397+
"rgba(0, 0, 0, 0)",
398+
);
399+
400+
view.contentDOM.dispatchEvent(
401+
new CompositionEvent("compositionstart", { bubbles: true }),
402+
);
403+
404+
expect(view.dom.dataset.markraComposing).toBe("true");
405+
expect(getComputedStyle(selectionBackground).backgroundColor).toBe(
406+
"rgba(0, 0, 0, 0)",
407+
);
408+
409+
view.contentDOM.dispatchEvent(
410+
new CompositionEvent("compositionend", { bubbles: true }),
411+
);
412+
413+
expect(view.dom.dataset.markraComposing).toBeUndefined();
414+
expect(getComputedStyle(selectionBackground).backgroundColor).not.toBe(
415+
"rgba(0, 0, 0, 0)",
416+
);
417+
});
418+
381419
it("renders task markers as checkboxes that update the Markdown source", () => {
382420
const doc = "- [ ] Ship mock release\n\nEdit";
383421
const view = createView({ doc });

packages/editor/src/codemirror/preview.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,14 @@ function previewPlugin(config: LivePreviewConfig): Extension {
596596
const reveal = config.reveal ?? revealActiveLine;
597597
const taskCheckboxes = config.taskCheckboxes ?? true;
598598

599+
function syncCompositionUi(view: EditorView, composing: boolean) {
600+
if (composing) {
601+
view.dom.dataset.markraComposing = "true";
602+
} else {
603+
delete view.dom.dataset.markraComposing;
604+
}
605+
}
606+
599607
return ViewPlugin.fromClass(
600608
class {
601609
decorations: DecorationSet;
@@ -609,6 +617,7 @@ function previewPlugin(config: LivePreviewConfig): Extension {
609617
update(update: ViewUpdate) {
610618
const compositionEnded = this.composing && !update.view.composing;
611619
this.composing = update.view.composing;
620+
syncCompositionUi(update.view, this.composing);
612621

613622
if (this.composing) {
614623
// Mapping preserves the current DOM while the platform owns the
@@ -645,11 +654,18 @@ function previewPlugin(config: LivePreviewConfig): Extension {
645654
{
646655
decorations: (plugin) => plugin.decorations,
647656
eventHandlers: {
657+
compositionstart(_event, view) {
658+
syncCompositionUi(view, true);
659+
},
648660
compositionend(_event, view) {
661+
syncCompositionUi(view, false);
649662
// CodeMirror updates its composition state before plugin handlers.
650663
// An empty transaction gives the plugin an immediate rebuild point.
651664
view.dispatch({});
652665
},
666+
blur(_event, view) {
667+
syncCompositionUi(view, false);
668+
},
653669
},
654670
},
655671
);

packages/editor/src/codemirror/theme.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { EditorView } from "@codemirror/view";
22

33
export const markraTheme = EditorView.baseTheme({
4+
'&[data-markra-composing="true"] .cm-selectionBackground': {
5+
// IME pre-edit updates temporarily expose their range as a CodeMirror
6+
// selection. Hiding only that drawn layer prevents it from flashing.
7+
backgroundColor: "transparent !important",
8+
},
49
".cm-markra-h1": {
510
fontSize: "1.8em",
611
fontWeight: "700",

0 commit comments

Comments
 (0)