107 lines
3.9 KiB
JavaScript
107 lines
3.9 KiB
JavaScript
import { useEffect, useRef } from 'react';
|
|
import parseKeypress, { nonAlphanumericKeys } from '../parse-keypress.js';
|
|
import reconciler from '../reconciler.js';
|
|
import useStdin from './use-stdin.js';
|
|
|
|
// Patched for bracketed paste: propagate "isPasted" and avoid leaking ESC sequences
|
|
// Also patched to use ref for inputHandler to avoid effect churn with inline handlers
|
|
const useInput = (inputHandler, options = {}) => {
|
|
const { stdin, setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdin();
|
|
|
|
// Store handler in ref to avoid re-subscribing when handler identity changes
|
|
const handlerRef = useRef(inputHandler);
|
|
handlerRef.current = inputHandler;
|
|
|
|
useEffect(() => {
|
|
if (options.isActive === false) {
|
|
return;
|
|
}
|
|
setRawMode(true);
|
|
return () => {
|
|
setRawMode(false);
|
|
};
|
|
}, [options.isActive, setRawMode]);
|
|
|
|
useEffect(() => {
|
|
if (options.isActive === false) {
|
|
return;
|
|
}
|
|
|
|
const handleData = (data) => {
|
|
// Handle bracketed paste events emitted by Ink stdin manager
|
|
if (data && typeof data === 'object' && data.isPasted) {
|
|
const key = {
|
|
upArrow: false,
|
|
downArrow: false,
|
|
leftArrow: false,
|
|
rightArrow: false,
|
|
pageDown: false,
|
|
pageUp: false,
|
|
return: false,
|
|
escape: false,
|
|
ctrl: false,
|
|
shift: false,
|
|
tab: false,
|
|
backspace: false,
|
|
delete: false,
|
|
meta: false,
|
|
isPasted: true
|
|
};
|
|
reconciler.batchedUpdates(() => {
|
|
handlerRef.current(data.sequence || data.raw || '', key);
|
|
});
|
|
return;
|
|
}
|
|
|
|
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',
|
|
meta: keypress.meta || keypress.name === 'escape' || keypress.option,
|
|
isPasted: false
|
|
};
|
|
|
|
let input = keypress.ctrl ? keypress.name : keypress.sequence;
|
|
const seq = typeof keypress.sequence === 'string' ? keypress.sequence : '';
|
|
// Filter xterm focus in/out sequences (ESC[I / ESC[O)
|
|
if (seq === '\u001B[I' || seq === '\u001B[O' || input === '[I' || input === '[O' || /^(?:\[I|\[O)+$/.test(input || '')) {
|
|
return;
|
|
}
|
|
|
|
if (nonAlphanumericKeys.includes(keypress.name)) {
|
|
input = '';
|
|
}
|
|
|
|
if (input.length === 1 && typeof input[0] === 'string' && /[A-Z]/.test(input[0])) {
|
|
key.shift = true;
|
|
}
|
|
|
|
if (!(input === 'c' && key.ctrl) || !internal_exitOnCtrlC) {
|
|
reconciler.batchedUpdates(() => {
|
|
handlerRef.current(input, key);
|
|
});
|
|
}
|
|
};
|
|
|
|
internal_eventEmitter?.on('input', handleData);
|
|
return () => {
|
|
internal_eventEmitter?.removeListener('input', handleData);
|
|
};
|
|
}, [options.isActive, stdin, internal_exitOnCtrlC]);
|
|
};
|
|
|
|
export default useInput;
|
|
|
|
|