diff --git a/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx b/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx index 992bf4ca..a11fe0b1 100644 --- a/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx +++ b/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx @@ -2110,6 +2110,33 @@ describe("SchemaForm enlarge keyboard access (#2138)", () => { expect(noteField().value).toBe(""); }); + // WebKit is reported to fire `compositionend` before the committing keydown, + // so `isComposing` is already false there and the guard above cannot see it. + // 229 is the older sentinel for "this key went to the IME", which that event + // still carries (#2139 review). + it("ignores a committing IME keydown that only reports keyCode 229", () => { + renderWithMantine(); + + fireEvent.keyDown(noteField(), { + key: "Enter", + isComposing: false, + keyCode: 229, + }); + + expect(noteField().tagName).toBe("INPUT"); + expect(noteField().value).toBe(""); + }); + + // The control for the sentinel: a real Enter reports 13 and must still work, + // so the guard cannot be swallowing ordinary keystrokes. + it("still enlarges on an Enter reporting keyCode 13", () => { + renderWithMantine(); + + fireEvent.keyDown(noteField(), { key: "Enter", keyCode: 13 }); + + expect(noteField().tagName).toBe("TEXTAREA"); + }); + // Same event without the flag, to prove the guard above is what turned it // away rather than fireEvent simply not reaching the handler. it("still enlarges on an Enter that is not composing", () => { diff --git a/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx b/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx index 1fdd748b..76603591 100644 --- a/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx +++ b/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx @@ -119,6 +119,12 @@ const TWO_ACTION_WIDTH = rem( ENLARGE_WIDTH + ACTION_GAP + CLEAR_WIDTH + SECTION_INSET, ); +// `keyCode` reported for a keydown the IME consumed — the pre-`isComposing` +// sentinel every browser still sets during composition. Only needed for the +// browsers whose composition events land too late for `isComposing` to help +// (#2138 review); a real Enter reports 13, so this cannot swallow one. +const IME_KEY_CODE = 229; + function serializeJson(value: unknown): string { return JSON.stringify(value, null, 2); } @@ -791,6 +797,13 @@ export function SchemaForm({ // keystroke means "accept this word", not "new line". Acting on it // would enlarge the field and insert a stray newline every time such a // user finished a word, making the field unusable for them. + // - `keyCode === 229` is the same guard again, for WebKit. It is reported + // to fire `compositionend` *before* the committing keydown, which + // leaves `isComposing` already false by the time this runs; 229 is the + // pre-`isComposing` sentinel for "this key went to the IME" and is + // still set there. Kept despite `keyCode` being deprecated because + // nothing non-deprecated distinguishes that event, and it cannot misfire + // on a real Enter, which reports 13. // - The Ctrl/Cmd/Alt chords are deliberately left alone: those read as // "submit" in a form, and a consumer binding one (run the tool) must // not be overridden into enlarging a field instead. @@ -798,6 +811,7 @@ export function SchemaForm({ if ( event.key !== "Enter" || event.nativeEvent.isComposing || + event.keyCode === IME_KEY_CODE || event.ctrlKey || event.metaKey || event.altKey