Skip to content

Commit 6d0aed8

Browse files
authored
Fix Input cursor jump (#712)
1 parent 5c2fe24 commit 6d0aed8

File tree

1 file changed

+81
-125
lines changed

1 file changed

+81
-125
lines changed

src/components/input/Input.js

Lines changed: 81 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,91 @@ import classNames from 'classnames';
77
const convert = val => (isNumeric(val) ? +val : NaN);
88
const isEquivalent = (v1, v2) => v1 === v2 || (isNaN(v1) && isNaN(v2));
99

10-
const BaseInput = forwardRef((props, ref) => {
10+
/**
11+
* A basic HTML input control for entering text, numbers, or passwords, with
12+
* Bootstrap styles automatically applied. This component is much like its
13+
* counterpart in dash_core_components, but with a few additions such as the
14+
* `valid` and `invalid` props for providing user feedback.
15+
*
16+
* Note that checkbox and radio types are supported through
17+
* the Checklist and RadioItems component. Dates, times, and file uploads
18+
* are supported through separate components in other libraries.
19+
*/
20+
const Input = props => {
1121
const {
12-
debounce,
22+
type,
23+
value,
1324
n_blur,
1425
n_submit,
15-
loading_state,
16-
setProps,
17-
onEvent,
18-
onChange,
1926
valid,
2027
invalid,
28+
plaintext,
29+
bs_size,
30+
setProps,
31+
debounce,
32+
loading_state,
33+
className,
2134
...otherProps
2235
} = props;
36+
const inputRef = useRef(null);
37+
38+
const formControlClass = plaintext
39+
? 'form-control-plaintext'
40+
: 'form-control';
41+
42+
const classes = classNames(
43+
className,
44+
invalid && 'is-invalid',
45+
valid && 'is-valid',
46+
bs_size ? `form-control-${bs_size}` : false,
47+
formControlClass
48+
);
49+
50+
const onChange = () => {
51+
if (!debounce) {
52+
onEvent();
53+
}
54+
};
55+
56+
useEffect(() => {
57+
if (type === 'number') {
58+
const inputValue = inputRef.current.value;
59+
const inputValueAsNumber = inputRef.current.checkValidity()
60+
? convert(inputValue)
61+
: NaN;
62+
const valueAsNumber = convert(value);
63+
64+
if (!isEquivalent(valueAsNumber, inputValueAsNumber)) {
65+
inputRef.current.value = isNil(valueAsNumber) ? valueAsNumber : value;
66+
}
67+
} else {
68+
const inputValue = inputRef.current.value;
69+
70+
if (value !== inputValue) {
71+
inputRef.current.value =
72+
value !== null && value !== undefined ? value : '';
73+
}
74+
}
75+
}, [value]);
76+
77+
const onEvent = (payload = {}) => {
78+
if (type === 'number') {
79+
const inputValue = inputRef.current.value;
80+
const inputValueAsNumber = inputRef.current.checkValidity()
81+
? convert(inputValue)
82+
: NaN;
83+
const valueAsNumber = convert(value);
84+
85+
if (!isEquivalent(valueAsNumber, inputValueAsNumber)) {
86+
setProps({...payload, value: inputValueAsNumber});
87+
} else if (Object.keys(payload).length) {
88+
setProps(payload);
89+
}
90+
} else {
91+
payload.value = inputRef.current.value;
92+
setProps(payload);
93+
}
94+
};
2395

2496
const onBlur = () => {
2597
if (setProps) {
@@ -51,7 +123,9 @@ const BaseInput = forwardRef((props, ref) => {
51123

52124
return (
53125
<input
54-
ref={ref}
126+
ref={inputRef}
127+
type={type}
128+
className={classes}
55129
onChange={onChange}
56130
onBlur={onBlur}
57131
onKeyPress={onKeyPress}
@@ -72,124 +146,6 @@ const BaseInput = forwardRef((props, ref) => {
72146
}
73147
/>
74148
);
75-
});
76-
77-
const NumberInput = forwardRef((props, inputRef) => {
78-
const {setProps, debounce, value, ...otherProps} = props;
79-
80-
const onChange = () => {
81-
if (!debounce) {
82-
onEvent();
83-
}
84-
};
85-
86-
useEffect(() => {
87-
const inputValue = inputRef.current.value;
88-
const inputValueAsNumber = inputRef.current.checkValidity()
89-
? convert(inputValue)
90-
: NaN;
91-
const valueAsNumber = convert(value);
92-
93-
if (!isEquivalent(valueAsNumber, inputValueAsNumber)) {
94-
inputRef.current.value = isNil(valueAsNumber) ? valueAsNumber : value;
95-
}
96-
}, [value]);
97-
98-
const onEvent = (payload = {}) => {
99-
const inputValue = inputRef.current.value;
100-
const inputValueAsNumber = inputRef.current.checkValidity()
101-
? convert(inputValue)
102-
: NaN;
103-
const valueAsNumber = convert(value);
104-
105-
if (!isEquivalent(valueAsNumber, inputValueAsNumber)) {
106-
setProps({...payload, value: inputValueAsNumber});
107-
} else if (Object.keys(payload).length) {
108-
setProps(payload);
109-
}
110-
};
111-
112-
return (
113-
<BaseInput
114-
ref={inputRef}
115-
debounce={debounce}
116-
onEvent={onEvent}
117-
onChange={onChange}
118-
setProps={setProps}
119-
{...otherProps}
120-
/>
121-
);
122-
});
123-
124-
const NonNumberInput = forwardRef((props, inputRef) => {
125-
const {value, debounce, setProps, ...otherProps} = props;
126-
const [valueState, setValueState] = useState(value || '');
127-
128-
const onChange = () => {
129-
if (!debounce) {
130-
onEvent();
131-
} else {
132-
setValueState(inputRef.current.value);
133-
}
134-
};
135-
136-
useEffect(() => {
137-
if (value !== null && value !== undefined) {
138-
setValueState(value);
139-
} else {
140-
setValueState('');
141-
}
142-
}, [value]);
143-
144-
const onEvent = (payload = {}) => {
145-
payload.value = inputRef.current.value;
146-
setProps(payload);
147-
};
148-
149-
return (
150-
<BaseInput
151-
ref={inputRef}
152-
value={valueState}
153-
debounce={debounce}
154-
onEvent={onEvent}
155-
onChange={onChange}
156-
setProps={setProps}
157-
{...otherProps}
158-
/>
159-
);
160-
});
161-
162-
/**
163-
* A basic HTML input control for entering text, numbers, or passwords, with
164-
* Bootstrap styles automatically applied. This component is much like its
165-
* counterpart in dash_core_components, but with a few additions such as the
166-
* `valid` and `invalid` props for providing user feedback.
167-
*
168-
* Note that checkbox and radio types are supported through
169-
* the Checklist and RadioItems component. Dates, times, and file uploads
170-
* are supported through separate components in other libraries.
171-
*/
172-
const Input = props => {
173-
const {plaintext, className, bs_size, ...otherProps} = props;
174-
const inputRef = useRef(null);
175-
176-
const formControlClass = plaintext
177-
? 'form-control-plaintext'
178-
: 'form-control';
179-
180-
const classes = classNames(
181-
className,
182-
props.invalid && 'is-invalid',
183-
props.valid && 'is-valid',
184-
bs_size ? `form-control-${bs_size}` : false,
185-
formControlClass
186-
);
187-
188-
if (props.type === 'number') {
189-
return <NumberInput ref={inputRef} {...otherProps} className={classes} />;
190-
}
191-
192-
return <NonNumberInput ref={inputRef} {...otherProps} className={classes} />;
193149
};
194150

195151
Input.propTypes = {

0 commit comments

Comments
 (0)