Skip to content

Commit 08c5077

Browse files
jimgqyuclaude
andcommitted
fix: filter SGR mouse sequences in useInput compat wrapper
SGR mouse escape sequences (CSI < btn ; x ; y M/m) arrive through the same stdin stream as keyboard input when mouse tracking is enabled. Ink v7's useInput is multicast — ALL handlers receive ALL input — so SGR sequences leaked into TextInput and appeared as garbled text. Fix: silently drop SGR sequences in the compat wrapper. MouseProvider and ScrollBox import useInput directly from 'ink' and are unaffected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 370a690 commit 08c5077

1 file changed

Lines changed: 23 additions & 2 deletions

File tree

packages/tui/src/compat/use-input.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* useInput compat wrapper — Phase 1
2+
* useInput compat wrapper — Phase 2
33
*
44
* Wraps ink v7's useInput (which provides 2 args: input, key) to provide
55
* a 3-argument handler (input, key, event) expected by CA's CLI code.
@@ -8,24 +8,45 @@
88
* + isPasted flag) that textInput.tsx and other CA components depend on.
99
* Without this wrapper, `event` is `undefined` and any access to
1010
* `event.keypress` throws TypeError, crashing the React tree.
11+
*
12+
* ## SGR Mouse Filtering
13+
* SGR extended mouse escape sequences (CSI < btn ; x ; y M/m) arrive
14+
* through the same stdin stream as keyboard input when mouse tracking is
15+
* enabled. The MouseProvider (which imports useInput directly from ink)
16+
* parses these into typed mouse events. However, ink v7's useInput is
17+
* multicast — ALL handlers receive ALL input. Without filtering, SGR
18+
* sequences leak into TextInput and appear as garbled text.
19+
*
20+
* We filter SGR sequences here so they never reach CA component handlers.
21+
* MouseProvider is unaffected because it imports useInput from ink directly.
1122
*/
1223
import { useInput as inkUseInput } from 'ink';
1324
import type { Key as InkKey } from 'ink';
1425
import { InputEvent, type Key } from './types.js';
1526

1627
type InputHandler = (input: string, key: Key, event: InputEvent) => void;
1728

29+
/** SGR extended mouse escape sequence: CSI < btn ; x ; y M/m */
30+
const SGR_MOUSE_RE = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/;
31+
1832
/**
1933
* Wraps ink v7's 2-arg useInput to provide CA's 3-arg handler signature.
2034
*
2135
* For each input event, constructs an InputEvent with:
2236
* - keypress.raw = input (the raw stdin string)
2337
* - keypress.isPasted = false (bracketed paste detection deferred)
2438
*
25-
* The Key type is cast to our extended type with wheelUp/wheelDown stubs.
39+
* SGR mouse escape sequences are silently dropped — they are consumed by
40+
* the MouseProvider (which uses ink's useInput directly) and should not
41+
* reach CA component handlers such as TextInput.
2642
*/
2743
export function useInput(inputHandler: InputHandler, options?: { isActive?: boolean }): void {
2844
inkUseInput((input: string, key: InkKey) => {
45+
// Drop SGR mouse escape sequences — they are handled by MouseProvider
46+
// which imports useInput directly from 'ink'. Letting them through causes
47+
// garbled text in TextInput (e.g. "[<64;60;19M" on touch scroll).
48+
if (SGR_MOUSE_RE.test(input)) return;
49+
2950
const event = new InputEvent(input, key as Key);
3051
inputHandler(input, key as Key, event);
3152
}, options as any);

0 commit comments

Comments
 (0)