From 123ab8fecc95cdb7b1d0bcc0cc482ba5a3a0dbed Mon Sep 17 00:00:00 2001 From: Eric Han Date: Tue, 12 May 2026 20:27:01 +0900 Subject: [PATCH] refactor(hooks): migrate use-mouse to ink 7 public API ink 7 removed internal_eventEmitter from useStdin's public type; attach mouse SGR listener directly to stdin and manage raw mode through ink's setRawMode counter. Closes #62 Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- pnpm-lock.yaml | 80 ++++++++++++++++++++++++------------------ src/hooks/use-mouse.ts | 13 ++++--- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 3db3005..4d2c1e0 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "check": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm test" }, "dependencies": { - "ink": "^6.8.0", + "ink": "^7.0.2", "ink-select-input": "^6.2.0", "ink-text-input": "^6.0.0", "react": "^19.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5385904..f89d4c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: ink: - specifier: ^6.8.0 - version: 6.8.0(@types/react@19.2.14)(react@19.2.6) + specifier: ^7.0.2 + version: 7.0.2(@types/react@19.2.14)(react@19.2.6) ink-select-input: specifier: ^6.2.0 - version: 6.2.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + version: 6.2.0(ink@7.0.2(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) ink-text-input: specifier: ^6.0.0 - version: 6.0.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + version: 6.0.0(ink@7.0.2(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react: specifier: ^19.0.0 version: 19.2.6 @@ -84,8 +84,8 @@ packages: '@actions/io@3.0.2': resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} - '@alcalzone/ansi-tokenize@0.2.5': - resolution: {integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==} + '@alcalzone/ansi-tokenize@0.3.0': + resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} '@ampproject/remapping@2.3.0': @@ -917,9 +917,9 @@ packages: resolution: {integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==} engines: {node: '>=14.16'} - cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} + cli-boxes@4.0.1: + resolution: {integrity: sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==} + engines: {node: '>=18.20 <19 || >=20.10'} cli-cursor@4.0.0: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} @@ -934,9 +934,9 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} - engines: {node: '>=20'} + cli-truncate@6.0.0: + resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==} + engines: {node: '>=22'} cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -1425,12 +1425,12 @@ packages: ink: '>=5' react: '>=18' - ink@6.8.0: - resolution: {integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==} - engines: {node: '>=20'} + ink@7.0.2: + resolution: {integrity: sha512-cnkE2SsDC/gieJ+BD8+gWpXrZPMInv7agBYN5gcKVlQZYp+IKa/FKM5bp1OIuJFp3ZIuRK7ZNxY4MZR3tUzyfQ==} + engines: {node: '>=22'} peerDependencies: - '@types/react': '>=19.0.0' - react: '>=19.0.0' + '@types/react': '>=19.2.0' + react: '>=19.2.0' react-devtools-core: '>=6.1.2' peerDependenciesMeta: '@types/react': @@ -2098,9 +2098,9 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} - slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} + slice-ansi@9.0.0: + resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} + engines: {node: '>=22'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -2481,6 +2481,10 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2570,7 +2574,7 @@ snapshots: '@actions/io@3.0.2': {} - '@alcalzone/ansi-tokenize@0.2.5': + '@alcalzone/ansi-tokenize@0.3.0': dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 @@ -3392,7 +3396,7 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 - cli-boxes@3.0.0: {} + cli-boxes@4.0.1: {} cli-cursor@4.0.0: dependencies: @@ -3413,9 +3417,9 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - cli-truncate@5.2.0: + cli-truncate@6.0.0: dependencies: - slice-ansi: 8.0.0 + slice-ansi: 9.0.0 string-width: 8.2.0 cliui@7.0.4: @@ -3910,30 +3914,30 @@ snapshots: ini@1.3.8: {} - ink-select-input@6.2.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): + ink-select-input@6.2.0(ink@7.0.2(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: figures: 6.1.0 - ink: 6.8.0(@types/react@19.2.14)(react@19.2.6) + ink: 7.0.2(@types/react@19.2.14)(react@19.2.6) react: 19.2.6 to-rotated: 1.0.0 - ink-text-input@6.0.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): + ink-text-input@6.0.0(ink@7.0.2(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: chalk: 5.6.2 - ink: 6.8.0(@types/react@19.2.14)(react@19.2.6) + ink: 7.0.2(@types/react@19.2.14)(react@19.2.6) react: 19.2.6 type-fest: 4.41.0 - ink@6.8.0(@types/react@19.2.14)(react@19.2.6): + ink@7.0.2(@types/react@19.2.14)(react@19.2.6): dependencies: - '@alcalzone/ansi-tokenize': 0.2.5 + '@alcalzone/ansi-tokenize': 0.3.0 ansi-escapes: 7.3.0 ansi-styles: 6.2.3 auto-bind: 5.0.1 chalk: 5.6.2 - cli-boxes: 3.0.0 + cli-boxes: 4.0.1 cli-cursor: 4.0.0 - cli-truncate: 5.2.0 + cli-truncate: 6.0.0 code-excerpt: 4.0.0 es-toolkit: 1.45.1 indent-string: 5.0.0 @@ -3943,13 +3947,13 @@ snapshots: react-reconciler: 0.33.0(react@19.2.6) scheduler: 0.27.0 signal-exit: 3.0.7 - slice-ansi: 8.0.0 + slice-ansi: 9.0.0 stack-utils: 2.0.6 string-width: 8.2.0 terminal-size: 4.0.1 type-fest: 5.5.0 widest-line: 6.0.0 - wrap-ansi: 9.0.2 + wrap-ansi: 10.0.0 ws: 8.20.0 yoga-layout: 3.2.1 optionalDependencies: @@ -4536,7 +4540,7 @@ snapshots: dependencies: unicode-emoji-modifier-base: 1.0.0 - slice-ansi@8.0.0: + slice-ansi@9.0.0: dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 @@ -4886,6 +4890,12 @@ snapshots: wordwrap@1.0.0: {} + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.0 + strip-ansi: 7.2.0 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/src/hooks/use-mouse.ts b/src/hooks/use-mouse.ts index 110ec69..e5c9ac4 100644 --- a/src/hooks/use-mouse.ts +++ b/src/hooks/use-mouse.ts @@ -31,7 +31,7 @@ function parseSgrEvent( export function useMouse(handler: (event: MouseEvent) => void) { const { stdout } = useStdout(); - const { internal_eventEmitter } = useStdin(); + const { stdin, setRawMode, isRawModeSupported } = useStdin(); const handlerRef = useRef(handler); const bufferRef = useRef(""); @@ -40,9 +40,11 @@ export function useMouse(handler: (event: MouseEvent) => void) { }, [handler]); useEffect(() => { + if (!isRawModeSupported) return; + setRawMode(true); stdout.write(ENABLE_MOUSE); - const onInput = (data: Buffer | string) => { + const onData = (data: Buffer | string) => { const chunk = typeof data === "string" ? data : data.toString("utf8"); bufferRef.current += chunk; @@ -72,10 +74,11 @@ export function useMouse(handler: (event: MouseEvent) => void) { } }; - internal_eventEmitter.on("input", onInput); + stdin.on("data", onData); return () => { - internal_eventEmitter.removeListener("input", onInput); + stdin.off("data", onData); stdout.write(DISABLE_MOUSE); + setRawMode(false); }; - }, [stdout, internal_eventEmitter]); + }, [stdin, setRawMode, isRawModeSupported, stdout]); }