1
- import React , { Component } from 'react' ;
1
+ import * as R from 'ramda' ;
2
+ import React , { PureComponent } from 'react' ;
2
3
import PropTypes from 'prop-types' ;
3
- import { omit , isEmpty } from 'ramda' ;
4
+ import isNumeric from 'fast-isnumeric' ;
5
+ import './css/input.css' ;
6
+
7
+ // eslint-disable-next-line no-implicit-coercion
8
+ const convert = val => ( isNumeric ( val ) ? + val : NaN ) ;
9
+
10
+ const isEquivalent = ( v1 , v2 ) => v1 === v2 || ( isNaN ( v1 ) && isNaN ( v2 ) ) ;
4
11
5
12
/**
6
13
* A basic HTML input control for entering text, numbers, or passwords.
@@ -9,76 +16,60 @@ import {omit, isEmpty} from 'ramda';
9
16
* the Checklist and RadioItems component. Dates, times, and file uploads
10
17
* are also supported through separate components.
11
18
*/
12
- export default class Input extends Component {
19
+ export default class Input extends PureComponent {
13
20
constructor ( props ) {
14
21
super ( props ) ;
15
- this . propsToState = this . propsToState . bind ( this ) ;
22
+
23
+ this . input = React . createRef ( ) ;
24
+
25
+ this . onBlur = this . onBlur . bind ( this ) ;
26
+ this . onChange = this . onChange . bind ( this ) ;
27
+ this . onEvent = this . onEvent . bind ( this ) ;
28
+ this . onKeyPress = this . onKeyPress . bind ( this ) ;
29
+ this . setInputValue = this . setInputValue . bind ( this ) ;
30
+ this . setPropValue = this . setPropValue . bind ( this ) ;
16
31
}
17
32
18
- propsToState ( newProps ) {
19
- this . setState ( { value : newProps . value } ) ;
33
+ componentWillReceiveProps ( nextProps ) {
34
+ const { value, valueAsNumber} = this . input . current ;
35
+ this . setInputValue (
36
+ R . isNil ( valueAsNumber ) ? value : valueAsNumber ,
37
+ nextProps . value
38
+ ) ;
39
+ if ( this . props . type !== 'number' ) {
40
+ this . setState ( { value : nextProps . value } ) ;
41
+ }
20
42
}
21
43
22
- componentWillReceiveProps ( newProps ) {
23
- this . propsToState ( newProps ) ;
44
+ componentDidMount ( ) {
45
+ const { value, valueAsNumber} = this . input . current ;
46
+ this . setInputValue (
47
+ R . isNil ( valueAsNumber ) ? value : valueAsNumber ,
48
+ this . props . value
49
+ ) ;
24
50
}
25
51
26
52
componentWillMount ( ) {
27
- this . propsToState ( this . props ) ;
53
+ if ( this . props . type !== 'number' ) {
54
+ this . setState ( { value : this . props . value } ) ;
55
+ }
28
56
}
29
57
30
58
render ( ) {
31
- const { setProps, type, min, max, debounce, loading_state} = this . props ;
32
- const value = this . state . value ;
59
+ const valprops =
60
+ this . props . type === 'number' ? { } : { value : this . state . value } ;
61
+ const { loading_state} = this . props ;
33
62
return (
34
63
< input
35
64
data-dash-is-loading = {
36
65
( loading_state && loading_state . is_loading ) || undefined
37
66
}
38
- onChange = { e => {
39
- const newValue = e . target . value ;
40
- if (
41
- ( ! isEmpty ( min ) && Number ( newValue ) < min ) ||
42
- ( ! isEmpty ( max ) && Number ( newValue ) > max )
43
- ) {
44
- return ;
45
- }
46
- if ( ! debounce ) {
47
- const castValue =
48
- type === 'number' ? Number ( newValue ) : newValue ;
49
- setProps ( {
50
- value : castValue ,
51
- } ) ;
52
- } else {
53
- this . setState ( { value : newValue } ) ;
54
- }
55
- } }
56
- onBlur = { ( ) => {
57
- const payload = {
58
- n_blur : this . props . n_blur + 1 ,
59
- n_blur_timestamp : Date . now ( ) ,
60
- } ;
61
- if ( debounce ) {
62
- payload . value =
63
- type === 'number' ? Number ( value ) : value ;
64
- }
65
- setProps ( payload ) ;
66
- } }
67
- onKeyPress = { e => {
68
- if ( e . key === 'Enter' ) {
69
- const payload = {
70
- n_submit : this . props . n_submit + 1 ,
71
- n_submit_timestamp : Date . now ( ) ,
72
- } ;
73
- if ( debounce ) {
74
- payload . value =
75
- type === 'number' ? Number ( value ) : value ;
76
- }
77
- setProps ( payload ) ;
78
- }
79
- } }
80
- value = { value }
81
- { ...omit (
67
+ ref = { this . input }
68
+ onBlur = { this . onBlur }
69
+ onChange = { this . onChange }
70
+ onKeyPress = { this . onKeyPress }
71
+ { ...valprops }
72
+ { ...R . omit (
82
73
[
83
74
'debounce' ,
84
75
'value' ,
@@ -97,6 +88,65 @@ export default class Input extends Component {
97
88
/>
98
89
) ;
99
90
}
91
+
92
+ setInputValue ( base , value ) {
93
+ const __value = value ;
94
+ base = this . input . current . checkValidity ( ) ? convert ( base ) : NaN ;
95
+ value = convert ( value ) ;
96
+
97
+ if ( ! isEquivalent ( base , value ) ) {
98
+ this . input . current . value = isNumeric ( value ) ? value : __value ;
99
+ }
100
+ }
101
+
102
+ setPropValue ( base , value ) {
103
+ base = convert ( base ) ;
104
+ value = this . input . current . checkValidity ( ) ? convert ( value ) : NaN ;
105
+
106
+ if ( ! isEquivalent ( base , value ) ) {
107
+ this . props . setProps ( { value} ) ;
108
+ }
109
+ }
110
+
111
+ onEvent ( ) {
112
+ const { value, valueAsNumber} = this . input . current ;
113
+ if ( this . props . type === 'number' ) {
114
+ this . setPropValue (
115
+ this . props . value ,
116
+ R . isNil ( valueAsNumber ) ? value : valueAsNumber
117
+ ) ;
118
+ } else {
119
+ this . props . setProps ( { value} ) ;
120
+ }
121
+ }
122
+
123
+ onBlur ( ) {
124
+ this . props . setProps ( {
125
+ n_blur : this . props . n_blur + 1 ,
126
+ n_blur_timestamp : Date . now ( ) ,
127
+ } ) ;
128
+ this . input . current . checkValidity ( ) ;
129
+ return this . props . debounce && this . onEvent ( ) ;
130
+ }
131
+
132
+ onKeyPress ( e ) {
133
+ if ( e . key === 'Enter' ) {
134
+ this . props . setProps ( {
135
+ n_submit : this . props . n_submit + 1 ,
136
+ n_submit_timestamp : Date . now ( ) ,
137
+ } ) ;
138
+ this . input . current . checkValidity ( ) ;
139
+ }
140
+ return this . props . debounce && e . key === 'Enter' && this . onEvent ( ) ;
141
+ }
142
+
143
+ onChange ( ) {
144
+ if ( ! this . props . debounce ) {
145
+ this . onEvent ( ) ;
146
+ } else if ( this . props . type !== 'number' ) {
147
+ this . setState ( { value : this . input . current . value } ) ;
148
+ }
149
+ }
100
150
}
101
151
102
152
Input . defaultProps = {
@@ -106,6 +156,7 @@ Input.defaultProps = {
106
156
n_submit : 0 ,
107
157
n_submit_timestamp : - 1 ,
108
158
debounce : false ,
159
+ step : 'any' ,
109
160
} ;
110
161
111
162
Input . propTypes = {
0 commit comments