init claude-code
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { useEffect, useLayoutEffect } from 'react'
|
||||
import { useEventCallback } from 'usehooks-ts'
|
||||
import type { InputEvent, Key } from '../events/input-event.js'
|
||||
import useStdin from './use-stdin.js'
|
||||
|
||||
type Handler = (input: string, key: Key, event: InputEvent) => void
|
||||
|
||||
type Options = {
|
||||
/**
|
||||
* Enable or disable capturing of user input.
|
||||
* Useful when there are multiple useInput hooks used at once to avoid handling the same input several times.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is used for handling user input.
|
||||
* It's a more convenient alternative to using `StdinContext` and listening to `data` events.
|
||||
* The callback you pass to `useInput` is called for each character when user enters any input.
|
||||
* However, if user pastes text and it's more than one character, the callback will be called only once and the whole string will be passed as `input`.
|
||||
*
|
||||
* ```
|
||||
* import {useInput} from 'ink';
|
||||
*
|
||||
* const UserInput = () => {
|
||||
* useInput((input, key) => {
|
||||
* if (input === 'q') {
|
||||
* // Exit program
|
||||
* }
|
||||
*
|
||||
* if (key.leftArrow) {
|
||||
* // Left arrow key pressed
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* return …
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
const useInput = (inputHandler: Handler, options: Options = {}) => {
|
||||
const { setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdin()
|
||||
|
||||
// useLayoutEffect (not useEffect) so that raw mode is enabled synchronously
|
||||
// during React's commit phase, before render() returns. With useEffect, raw
|
||||
// mode setup is deferred to the next event loop tick via React's scheduler,
|
||||
// leaving the terminal in cooked mode — keystrokes echo and the cursor is
|
||||
// visible until the effect fires.
|
||||
useLayoutEffect(() => {
|
||||
if (options.isActive === false) {
|
||||
return
|
||||
}
|
||||
|
||||
setRawMode(true)
|
||||
|
||||
return () => {
|
||||
setRawMode(false)
|
||||
}
|
||||
}, [options.isActive, setRawMode])
|
||||
|
||||
// Register the listener once on mount so its slot in the EventEmitter's
|
||||
// listener array is stable. If isActive were in the effect's deps, the
|
||||
// listener would re-append on false→true, moving it behind listeners
|
||||
// that registered while it was inactive — breaking
|
||||
// stopImmediatePropagation() ordering. useEventCallback keeps the
|
||||
// reference stable while reading latest isActive/inputHandler from
|
||||
// closure (it syncs via useLayoutEffect, so it's compiler-safe).
|
||||
const handleData = useEventCallback((event: InputEvent) => {
|
||||
if (options.isActive === false) {
|
||||
return
|
||||
}
|
||||
const { input, key } = event
|
||||
|
||||
// If app is not supposed to exit on Ctrl+C, then let input listener handle it
|
||||
// Note: discreteUpdates is called at the App level when emitting events,
|
||||
// so all listeners are already within a high-priority update context.
|
||||
if (!(input === 'c' && key.ctrl) || !internal_exitOnCtrlC) {
|
||||
inputHandler(input, key, event)
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
internal_eventEmitter?.on('input', handleData)
|
||||
|
||||
return () => {
|
||||
internal_eventEmitter?.removeListener('input', handleData)
|
||||
}
|
||||
}, [internal_eventEmitter, handleData])
|
||||
}
|
||||
|
||||
export default useInput
|
||||
Reference in New Issue
Block a user