Skip to content
Permalink
Browse files

Highlight pasted input (#24)

* added cursorWidth (test not working)

* working test

* minor fix

* add highlightPasted prop

* renamed highlightPasted to highlightPastedInput and update readme

* Update readme.md
  • Loading branch information...
ivanross authored and vadimdemedes committed Mar 23, 2019
1 parent e4af0a5 commit 85afe6adf45f02edeffd566e77e5783d315aef5e
Showing with 37 additions and 9 deletions.
  1. +7 −0 readme.md
  2. +18 −7 src/index.js
  3. +12 −2 test.js
@@ -75,6 +75,13 @@ Default: `false`

Whether to show cursor and allow navigation inside text input with arrow keys.

### highlightPastedText

Type: `boolean`<br>
Default: `false`

Highlight pasted text.

### mask

Type: `string`
@@ -22,38 +22,44 @@ class TextInput extends PureComponent {
stdin: PropTypes.object.isRequired,
setRawMode: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func
onSubmit: PropTypes.func,
highlightPastedText: PropTypes.bool
}

static defaultProps = {
placeholder: '',
showCursor: true,
focus: true,
mask: undefined,
onSubmit: undefined
onSubmit: undefined,
highlightPastedText: false
};

state = {
cursorOffset: (this.props.value || '').length
cursorOffset: (this.props.value || '').length,
cursorWidth: 0
}

render() {
const {value, placeholder, showCursor, focus, mask} = this.props;
const {cursorOffset} = this.state;
const {value, placeholder, showCursor, focus, mask, highlightPastedText} = this.props;
const {cursorOffset, cursorWidth} = this.state;
const hasValue = value.length > 0;
let renderedValue = value;
const cursorActualWidth = highlightPastedText ? cursorWidth : 0;

// Fake mouse cursor, because it's too inconvenient to deal with actual cursor and ansi escapes
if (showCursor && !mask && focus) {
renderedValue = value.length > 0 ? '' : chalk.inverse(' ');

let i = 0;
for (const char of value) {
if (i++ === cursorOffset) {
if (i >= cursorOffset - cursorActualWidth && i <= cursorOffset) {
renderedValue += chalk.inverse(char);
} else {
renderedValue += char;
}

i++;
}

if (value.length > 0 && cursorOffset === value.length) {
@@ -110,6 +116,7 @@ class TextInput extends PureComponent {

let cursorOffset = originalCursorOffset;
let value = originalValue;
let cursorWidth = 0;

if (s === ARROW_LEFT) {
if (showCursor && !mask) {
@@ -125,6 +132,10 @@ class TextInput extends PureComponent {
} else {
value = value.substr(0, cursorOffset) + s + value.substr(cursorOffset, value.length);
cursorOffset += s.length;

if (s.length > 1) {
cursorWidth = s.length;
}
}

if (cursorOffset < 0) {
@@ -135,7 +146,7 @@ class TextInput extends PureComponent {
cursorOffset = value.length;
}

this.setState({cursorOffset});
this.setState({cursorOffset, cursorWidth});

if (value !== originalValue) {
onChange(value);
14 test.js
@@ -10,6 +10,7 @@ const noop = () => {};
const CURSOR = chalk.inverse(' ');
const ENTER = '\r';
const ARROW_LEFT = '\u001B[D';
const ARROW_RIGHT = '\u001B[C';

test('default state', t => {
const {lastFrame} = render(<TextInput value="" onChange={noop}/>);
@@ -100,15 +101,24 @@ test('paste and move cursor', t => {
const StatefulTextInput = () => {
const [value, setValue] = useState('');

return <TextInput value={value} onChange={setValue}/>;
return <TextInput highlightPastedText value={value} onChange={setValue}/>;
};

const {stdin, lastFrame} = render(<StatefulTextInput/>);

// Need this to invert each char separately
const inverse = str => str.split('').map(c => chalk.inverse(c)).join('');

stdin.write('A');
stdin.write('B');
t.is(lastFrame(), `AB${CURSOR}`);

stdin.write(ARROW_LEFT);
t.is(lastFrame(), `A${chalk.inverse('B')}`);

stdin.write('Hello World');
t.is(lastFrame(), `A${inverse('Hello WorldB')}`);

t.is(lastFrame(), `AHello World${chalk.inverse('B')}`);
stdin.write(ARROW_RIGHT);
t.is(lastFrame(), `AHello WorldB${CURSOR}`);
});

0 comments on commit 85afe6a

Please sign in to comment.
You can’t perform that action at this time.