zz_

app/tui/useMouse.ts

zzmq.dev@gmail.com/zz / app / tui / useMouse.ts @ 69f3dd4b 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);
+ };
+ }, []);
+}
+