(inputHandler: Handler, options: Options = {})
| 40 | * ``` |
| 41 | */ |
| 42 | const useInput = (inputHandler: Handler, options: Options = {}) => { |
| 43 | const { setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdin() |
| 44 | |
| 45 | // useLayoutEffect (not useEffect) so that raw mode is enabled synchronously |
| 46 | // during React's commit phase, before render() returns. With useEffect, raw |
| 47 | // mode setup is deferred to the next event loop tick via React's scheduler, |
| 48 | // leaving the terminal in cooked mode — keystrokes echo and the cursor is |
| 49 | // visible until the effect fires. |
| 50 | useLayoutEffect(() => { |
| 51 | if (options.isActive === false) { |
| 52 | return |
| 53 | } |
| 54 | |
| 55 | setRawMode(true) |
| 56 | |
| 57 | return () => { |
| 58 | setRawMode(false) |
| 59 | } |
| 60 | }, [options.isActive, setRawMode]) |
| 61 | |
| 62 | // Register the listener once on mount so its slot in the EventEmitter's |
| 63 | // listener array is stable. If isActive were in the effect's deps, the |
| 64 | // listener would re-append on false→true, moving it behind listeners |
| 65 | // that registered while it was inactive — breaking |
| 66 | // stopImmediatePropagation() ordering. useEventCallback keeps the |
| 67 | // reference stable while reading latest isActive/inputHandler from |
| 68 | // closure (it syncs via useLayoutEffect, so it's compiler-safe). |
| 69 | const handleData = useEventCallback((event: InputEvent) => { |
| 70 | if (options.isActive === false) { |
| 71 | return |
| 72 | } |
| 73 | const { input, key } = event |
| 74 | |
| 75 | // If app is not supposed to exit on Ctrl+C, then let input listener handle it |
| 76 | // Note: discreteUpdates is called at the App level when emitting events, |
| 77 | // so all listeners are already within a high-priority update context. |
| 78 | if (!(input === 'c' && key.ctrl) || !internal_exitOnCtrlC) { |
| 79 | inputHandler(input, key, event) |
| 80 | } |
| 81 | }) |
| 82 | |
| 83 | useEffect(() => { |
| 84 | internal_eventEmitter?.on('input', handleData) |
| 85 | |
| 86 | return () => { |
| 87 | internal_eventEmitter?.removeListener('input', handleData) |
| 88 | } |
| 89 | }, [internal_eventEmitter, handleData]) |
| 90 | } |
| 91 | |
| 92 | export default useInput |
no test coverage detected