Skip to content

Commit 467419c

Browse files
authored
Fix Input cursor jump (#707)
1 parent 406eee3 commit 467419c

File tree

1 file changed

+95
-163
lines changed

1 file changed

+95
-163
lines changed

src/components/input/Input.js

Lines changed: 95 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,105 @@ 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+
size,
30+
html_size,
31+
setProps,
32+
debounce,
33+
loading_state,
34+
className,
35+
class_name,
36+
autoComplete,
37+
autocomplete,
38+
autoFocus,
39+
autofocus,
40+
inputMode,
41+
inputmode,
42+
maxLength,
43+
maxlength,
44+
minLength,
45+
minlength,
46+
tabIndex,
47+
tabindex,
2148
...otherProps
2249
} = props;
50+
const inputRef = useRef(null);
51+
52+
const formControlClass = plaintext
53+
? 'form-control-plaintext'
54+
: 'form-control';
55+
56+
const classes = classNames(
57+
class_name || className,
58+
invalid && 'is-invalid',
59+
valid && 'is-valid',
60+
size ? `form-control-${size}` : false,
61+
formControlClass
62+
);
63+
64+
const onChange = () => {
65+
if (!debounce) {
66+
onEvent();
67+
}
68+
};
69+
70+
useEffect(() => {
71+
if (type === 'number') {
72+
const inputValue = inputRef.current.value;
73+
const inputValueAsNumber = inputRef.current.checkValidity()
74+
? convert(inputValue)
75+
: NaN;
76+
const valueAsNumber = convert(value);
77+
78+
if (!isEquivalent(valueAsNumber, inputValueAsNumber)) {
79+
inputRef.current.value = isNil(valueAsNumber) ? valueAsNumber : value;
80+
}
81+
} else {
82+
const inputValue = inputRef.current.value;
83+
84+
if (value !== inputValue) {
85+
inputRef.current.value =
86+
value !== null && value !== undefined ? value : '';
87+
}
88+
}
89+
}, [value]);
90+
91+
const onEvent = (payload = {}) => {
92+
if (type === 'number') {
93+
const inputValue = inputRef.current.value;
94+
const inputValueAsNumber = inputRef.current.checkValidity()
95+
? convert(inputValue)
96+
: NaN;
97+
const valueAsNumber = convert(value);
98+
99+
if (!isEquivalent(valueAsNumber, inputValueAsNumber)) {
100+
setProps({...payload, value: inputValueAsNumber});
101+
} else if (Object.keys(payload).length) {
102+
setProps(payload);
103+
}
104+
} else {
105+
payload.value = inputRef.current.value;
106+
setProps(payload);
107+
}
108+
};
23109

24110
const onBlur = () => {
25111
if (setProps) {
@@ -51,7 +137,9 @@ const BaseInput = forwardRef((props, ref) => {
51137

52138
return (
53139
<input
54-
ref={ref}
140+
ref={inputRef}
141+
type={type}
142+
className={classes}
55143
onChange={onChange}
56144
onBlur={onBlur}
57145
onKeyPress={onKeyPress}
@@ -70,162 +158,6 @@ const BaseInput = forwardRef((props, ref) => {
70158
data-dash-is-loading={
71159
(loading_state && loading_state.is_loading) || undefined
72160
}
73-
/>
74-
);
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 {
174-
plaintext,
175-
className,
176-
class_name,
177-
autoComplete,
178-
autocomplete,
179-
autoFocus,
180-
autofocus,
181-
inputMode,
182-
inputmode,
183-
maxLength,
184-
maxlength,
185-
minLength,
186-
minlength,
187-
tabIndex,
188-
tabindex,
189-
size,
190-
html_size,
191-
...otherProps
192-
} = props;
193-
const inputRef = useRef(null);
194-
195-
const formControlClass = plaintext
196-
? 'form-control-plaintext'
197-
: 'form-control';
198-
199-
const classes = classNames(
200-
class_name || className,
201-
props.invalid && 'is-invalid',
202-
props.valid && 'is-valid',
203-
size ? `form-control-${size}` : false,
204-
formControlClass
205-
);
206-
207-
if (props.type === 'number') {
208-
return (
209-
<NumberInput
210-
ref={inputRef}
211-
{...otherProps}
212-
className={classes}
213-
autoComplete={autocomplete || autoComplete}
214-
autoFocus={autofocus || autoFocus}
215-
inputMode={inputmode || inputMode}
216-
maxLength={maxlength || maxLength}
217-
minLength={minlength || minLength}
218-
tabIndex={tabindex || tabIndex}
219-
size={html_size}
220-
/>
221-
);
222-
}
223-
224-
return (
225-
<NonNumberInput
226-
ref={inputRef}
227-
{...otherProps}
228-
className={classes}
229161
autoComplete={autocomplete || autoComplete}
230162
autoFocus={autofocus || autoFocus}
231163
inputMode={inputmode || inputMode}

0 commit comments

Comments
 (0)