diff --git a/packages/ui/src/components/tree/TreeItem.tsx b/packages/ui/src/components/tree/TreeItem.tsx index 6898d114..3f14ffd7 100644 --- a/packages/ui/src/components/tree/TreeItem.tsx +++ b/packages/ui/src/components/tree/TreeItem.tsx @@ -20,6 +20,7 @@ import { } from "./context"; import type { TreeNode } from "./common"; import { getNodeKey } from "./common"; +import { isImeCompositionEvent } from "./keyboard"; import type { TreeProps } from "./Tree"; import { TreeIndentGuide } from "./TreeIndentGuide"; @@ -170,6 +171,8 @@ function TreeItem_({ const handleEditKeyDown = useCallback( async (e: ReactKeyboardEvent) => { e.stopPropagation(); // Don't trigger other tree keys (like arrows) + if (isImeCompositionEvent(e.nativeEvent)) return; + switch (e.key) { case "Enter": if (editing) { diff --git a/packages/ui/src/components/tree/keyboard.test.ts b/packages/ui/src/components/tree/keyboard.test.ts new file mode 100644 index 00000000..de7c0fe0 --- /dev/null +++ b/packages/ui/src/components/tree/keyboard.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from "vite-plus/test"; +import { isImeCompositionEvent } from "./keyboard"; + +describe("isImeCompositionEvent", () => { + test("detects an active standards-based composition", () => { + expect(isImeCompositionEvent({ isComposing: true, keyCode: 13 })).toBe(true); + }); + + test("detects the Safari/WebKit key code fallback", () => { + expect(isImeCompositionEvent({ isComposing: false, keyCode: 229 })).toBe(true); + }); + + test("does not classify an ordinary Enter keydown as composition", () => { + expect(isImeCompositionEvent({ isComposing: false, keyCode: 13 })).toBe(false); + }); + + test("does not classify an ordinary Escape keydown as composition", () => { + expect(isImeCompositionEvent({ isComposing: false, keyCode: 27 })).toBe(false); + }); +}); diff --git a/packages/ui/src/components/tree/keyboard.ts b/packages/ui/src/components/tree/keyboard.ts new file mode 100644 index 00000000..beecf099 --- /dev/null +++ b/packages/ui/src/components/tree/keyboard.ts @@ -0,0 +1,7 @@ +export type ImeKeyboardEvent = Pick; + +export function isImeCompositionEvent(event: ImeKeyboardEvent): boolean { + // Safari can clear `isComposing` on the keydown that finishes composition. + // `229` is retained as the compatibility signal that an IME is processing it. + return event.isComposing || event.keyCode === 229; +}