ink
Version:
React for CLI
95 lines (91 loc) • 4.01 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 for `data` events. The callback you pass to `useInput` is called for each character when the user enters any input. However, if the 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',
home: keypress.name === 'home',
end: keypress.name === 'end',
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