app/tui/useMouse.ts
zzmq.dev@gmail.com/zz / app / tui / useMouse.ts
@ 55e9576b
Download
Ladder Summary
- file The module parses SGR mouse escape sequences from `process.stdin` and forwards them to user‑provided callbacks. It exports the `MouseHandlers` interface (with optional `onWheelUp`, `onWheelDown`, and `onClick` callbacks) and the React hook `useMouse(handlers)` which registers a stdin “data” listener, extracts wheel‑up/down and left‑click events (including column and row), and invokes the corresponding handlers while cleaning up the listener on unmount.
// Parses SGR mouse escape sequences directly off stdin (ink's own input
// handling is keyboard-only; it has no concept of mouse events) --
// wheel up/down (wired to scrolling) and a plain left-click press
// (wired to clicking a tab), with its terminal column/row so the
// caller can decide what, if anything, was clicked. Requires
// fullscreen.ts's mouse reporting to be enabled. SGR sequences look
// like "\x1b[<Cb;Cx;CyM" (press) or "...m" (release) -- Cb 64/65 are
// wheel up/down, Cb 0 is the left button.
import { useEffect, useRef } from "react";
const MOUSE_RE = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
export interface MouseHandlers {
onWheelUp?: () => void;
onWheelDown?: () => void;
onClick?: (col: number, row: number) => void;
}
export function useMouse(handlers: MouseHandlers): void {
const handlersRef = useRef(handlers);
handlersRef.current = handlers;
useEffect(() => {
const stdin = process.stdin;
const onData = (chunk: Buffer | string) => {
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
if (!text.includes("\x1b[<")) return;
for (const match of text.matchAll(MOUSE_RE)) {
const cb = Number(match[1]);
const col = Number(match[2]);
const row = Number(match[3]);
const isPress = match[4] === "M";
if (cb === 64) handlersRef.current.onWheelUp?.();
else if (cb === 65) handlersRef.current.onWheelDown?.();
else if (cb === 0 && isPress) handlersRef.current.onClick?.(col, row);
}
};
stdin.on("data", onData);
return () => {
stdin.off("data", onData);
};
}, []);
}