ink
Version:
React for CLI
96 lines • 3.97 kB
JavaScript
import { useEffect } from 'react';
import parseKeypress, { nonAlphanumericKeys } from '../parse-keypress.js';
import reconciler from '../reconciler.js';
import useStdin from './use-stdin.js';
/**
* 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, options = {}) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { stdin, setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdin();
useEffect(() => {
if (options.isActive === false) {
return;
}
setRawMode(true);
return () => {
setRawMode(false);
};
}, [options.isActive, setRawMode]);
useEffect(() => {
if (options.isActive === false) {
return;
}
const handleData = (data) => {
const keypress = parseKeypress(data);
const key = {
upArrow: keypress.name === 'up',
downArrow: keypress.name === 'down',
leftArrow: keypress.name === 'left',
rightArrow: keypress.name === 'right',
pageDown: keypress.name === 'pagedown',
pageUp: keypress.name === 'pageup',
return: keypress.name === 'return',
escape: keypress.name === 'escape',
ctrl: keypress.ctrl,
shift: keypress.shift,
tab: keypress.name === 'tab',
backspace: keypress.name === 'backspace',
delete: keypress.name === 'delete',
// `parseKeypress` parses \u001B\u001B[A (meta + up arrow) as meta = false
// but with option = true, so we need to take this into account here
// to avoid breaking changes in Ink.
// TODO(vadimdemedes): consider removing this in the next major version.
meta: keypress.meta || keypress.name === 'escape' || keypress.option,
};
let input = keypress.ctrl ? keypress.name : keypress.sequence;
if (nonAlphanumericKeys.includes(keypress.name)) {
input = '';
}
// Strip meta if it's still remaining after `parseKeypress`
// TODO(vadimdemedes): remove this in the next major version.
if (input.startsWith('\u001B')) {
input = input.slice(1);
}
if (input.length === 1 &&
typeof input[0] === 'string' &&
/[A-Z]/.test(input[0])) {
key.shift = true;
}
// If app is not supposed to exit on Ctrl+C, then let input listener handle it
if (!(input === 'c' && key.ctrl) || !internal_exitOnCtrlC) {
// @ts-expect-error TypeScript types for `batchedUpdates` require an argument, but React's codebase doesn't provide it and it works without it as expected.
reconciler.batchedUpdates(() => {
inputHandler(input, key);
});
}
};
internal_eventEmitter?.on('input', handleData);
return () => {
internal_eventEmitter?.removeListener('input', handleData);
};
}, [options.isActive, stdin, internal_exitOnCtrlC, inputHandler]);
};
export default useInput;
//# sourceMappingURL=use-input.js.map