1
- import React , { Component } from 'react' ;
1
+ import React , { Component } from 'react' ;
2
2
import PropTypes from 'prop-types' ;
3
- import { View , TextInput , StyleSheet , Dimensions , ViewPropTypes } from 'react-native' ;
3
+ import {
4
+ View ,
5
+ TextInput ,
6
+ StyleSheet ,
7
+ Dimensions ,
8
+ ViewPropTypes ,
9
+ } from 'react-native' ;
4
10
import _ from 'lodash' ;
5
11
6
12
// if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44)
@@ -22,9 +28,8 @@ export default class ConfirmationCodeInput extends Component {
22
28
codeInputStyle : TextInput . propTypes . style ,
23
29
containerStyle : viewPropTypes . style ,
24
30
onFulfill : PropTypes . func ,
25
- onCodeChange : PropTypes . func ,
26
31
} ;
27
-
32
+
28
33
static defaultProps = {
29
34
codeLength : 5 ,
30
35
inputPosition : 'center' ,
@@ -38,45 +43,51 @@ export default class ConfirmationCodeInput extends Component {
38
43
compareWithCode : '' ,
39
44
ignoreCase : false ,
40
45
} ;
41
-
46
+
42
47
constructor ( props ) {
43
48
super ( props ) ;
44
-
49
+
45
50
this . state = {
46
51
codeArr : new Array ( this . props . codeLength ) . fill ( '' ) ,
47
- currentIndex : 0
52
+ currentIndex : 0 ,
48
53
} ;
49
-
54
+
50
55
this . codeInputRefs = [ ] ;
51
56
}
52
-
57
+
53
58
componentDidMount ( ) {
54
59
const { compareWithCode, codeLength, inputPosition } = this . props ;
55
60
if ( compareWithCode && compareWithCode . length !== codeLength ) {
56
- console . error ( "Invalid props: compareWith length is not equal to codeLength" ) ;
61
+ console . error (
62
+ 'Invalid props: compareWith length is not equal to codeLength'
63
+ ) ;
57
64
}
58
-
59
- if ( _ . indexOf ( [ 'center' , 'left' , 'right' , 'full-width' ] , inputPosition ) === - 1 ) {
60
- console . error ( 'Invalid input position. Must be in: center, left, right, full' ) ;
65
+
66
+ if (
67
+ _ . indexOf ( [ 'center' , 'left' , 'right' , 'full-width' ] , inputPosition ) === - 1
68
+ ) {
69
+ console . error (
70
+ 'Invalid input position. Must be in: center, left, right, full'
71
+ ) ;
61
72
}
62
73
}
63
-
74
+
64
75
clear ( ) {
65
76
this . setState ( {
66
77
codeArr : new Array ( this . props . codeLength ) . fill ( '' ) ,
67
- currentIndex : 0
78
+ currentIndex : 0 ,
68
79
} ) ;
69
80
this . _setFocus ( 0 ) ;
70
81
}
71
-
82
+
72
83
_setFocus ( index ) {
73
84
this . codeInputRefs [ index ] . focus ( ) ;
74
85
}
75
-
86
+
76
87
_blur ( index ) {
77
88
this . codeInputRefs [ index ] . blur ( ) ;
78
89
}
79
-
90
+
80
91
_onFocus ( index ) {
81
92
let newCodeArr = _ . clone ( this . state . codeArr ) ;
82
93
const currentEmptyIndex = _ . findIndex ( newCodeArr , c => ! c ) ;
@@ -88,155 +99,182 @@ export default class ConfirmationCodeInput extends Component {
88
99
newCodeArr [ i ] = '' ;
89
100
}
90
101
}
91
-
102
+
92
103
this . setState ( {
93
104
codeArr : newCodeArr ,
94
- currentIndex : index
95
- } )
105
+ currentIndex : index ,
106
+ } ) ;
96
107
}
97
-
108
+
98
109
_isMatchingCode ( code , compareWithCode , ignoreCase = false ) {
99
110
if ( ignoreCase ) {
100
111
return code . toLowerCase ( ) == compareWithCode . toLowerCase ( ) ;
101
112
}
102
113
return code == compareWithCode ;
103
114
}
104
-
115
+
105
116
_getContainerStyle ( size , position ) {
106
117
switch ( position ) {
107
118
case 'left' :
108
119
return {
109
120
justifyContent : 'flex-start' ,
110
- height : size
121
+ height : size ,
111
122
} ;
112
123
case 'center' :
113
124
return {
114
125
justifyContent : 'center' ,
115
- height : size
126
+ height : size ,
116
127
} ;
117
128
case 'right' :
118
129
return {
119
130
justifyContent : 'flex-end' ,
120
- height : size
131
+ height : size ,
121
132
} ;
122
133
default :
123
134
return {
124
135
justifyContent : 'space-between' ,
125
- height : size
126
- }
136
+ height : size ,
137
+ } ;
127
138
}
128
139
}
129
-
140
+
130
141
_getInputSpaceStyle ( space ) {
131
142
const { inputPosition } = this . props ;
132
143
switch ( inputPosition ) {
133
144
case 'left' :
134
145
return {
135
- marginRight : space
146
+ marginRight : space ,
136
147
} ;
137
148
case 'center' :
138
149
return {
139
- marginRight : space / 2 ,
140
- marginLeft : space / 2
150
+ marginRight : space / 2 ,
151
+ marginLeft : space / 2 ,
141
152
} ;
142
153
case 'right' :
143
154
return {
144
- marginLeft : space
155
+ marginLeft : space ,
145
156
} ;
146
157
default :
147
158
return {
148
159
marginRight : 0 ,
149
- marginLeft : 0
160
+ marginLeft : 0 ,
150
161
} ;
151
162
}
152
163
}
153
-
164
+
154
165
_getClassStyle ( className , active ) {
155
166
const { cellBorderWidth, activeColor, inactiveColor, space } = this . props ;
156
167
let classStyle = {
157
168
...this . _getInputSpaceStyle ( space ) ,
158
- color : activeColor
169
+ color : activeColor ,
159
170
} ;
160
-
171
+
161
172
switch ( className ) {
162
173
case 'clear' :
163
174
return _ . merge ( classStyle , { borderWidth : 0 } ) ;
164
175
case 'border-box' :
165
176
return _ . merge ( classStyle , {
166
177
borderWidth : cellBorderWidth ,
167
- borderColor : ( active ? activeColor : inactiveColor )
178
+ borderColor : active ? activeColor : inactiveColor ,
168
179
} ) ;
169
180
case 'border-circle' :
170
181
return _ . merge ( classStyle , {
171
182
borderWidth : cellBorderWidth ,
172
183
borderRadius : 50 ,
173
- borderColor : ( active ? activeColor : inactiveColor )
184
+ borderColor : active ? activeColor : inactiveColor ,
174
185
} ) ;
175
186
case 'border-b' :
176
187
return _ . merge ( classStyle , {
177
188
borderBottomWidth : cellBorderWidth ,
178
- borderColor : ( active ? activeColor : inactiveColor ) ,
189
+ borderColor : active ? activeColor : inactiveColor ,
179
190
} ) ;
180
191
case 'border-b-t' :
181
192
return _ . merge ( classStyle , {
182
193
borderTopWidth : cellBorderWidth ,
183
194
borderBottomWidth : cellBorderWidth ,
184
- borderColor : ( active ? activeColor : inactiveColor )
195
+ borderColor : active ? activeColor : inactiveColor ,
185
196
} ) ;
186
197
case 'border-l-r' :
187
198
return _ . merge ( classStyle , {
188
199
borderLeftWidth : cellBorderWidth ,
189
200
borderRightWidth : cellBorderWidth ,
190
- borderColor : ( active ? activeColor : inactiveColor )
201
+ borderColor : active ? activeColor : inactiveColor ,
191
202
} ) ;
192
203
default :
193
204
return className ;
194
205
}
195
206
}
196
-
207
+
197
208
_onKeyPress ( e ) {
198
209
if ( e . nativeEvent . key === 'Backspace' ) {
199
210
const { currentIndex } = this . state ;
200
- let newCodeArr = _ . clone ( this . state . codeArr ) ;
201
211
const nextIndex = currentIndex > 0 ? currentIndex - 1 : 0 ;
202
- for ( const i in newCodeArr ) {
203
- if ( i >= nextIndex ) {
204
- newCodeArr [ i ] = '' ;
205
- }
206
- }
207
- this . props . onCodeChange ( newCodeArr . join ( '' ) )
208
212
this . _setFocus ( nextIndex ) ;
209
213
}
210
214
}
211
-
212
- _onInputCode ( character , index ) {
213
- const { codeLength, onFulfill, compareWithCode, ignoreCase, onCodeChange } = this . props ;
215
+
216
+ /** synthesizes the input characters based on the keyboard type removing invalid characters */
217
+ _synthesizeInput = characters => {
218
+ const { keyboardType } = this . props ;
219
+ if ( keyboardType === 'numeric' ) {
220
+ return characters . replace ( / \D / g, '' ) ;
221
+ }
222
+ return characters ;
223
+ } ;
224
+
225
+ _onInputCode ( baseCharacters , baseIndex ) {
226
+ const {
227
+ codeLength,
228
+ onFulfill,
229
+ compareWithCode,
230
+ ignoreCase,
231
+ keyboardType,
232
+ } = this . props ;
233
+
234
+ const characters = this . _synthesizeInput ( baseCharacters ) . substring (
235
+ 0 ,
236
+ codeLength - baseIndex
237
+ ) ;
238
+
214
239
let newCodeArr = _ . clone ( this . state . codeArr ) ;
215
- newCodeArr [ index ] = character ;
216
-
217
- if ( index == codeLength - 1 ) {
218
- const code = newCodeArr . join ( '' ) ;
219
-
240
+ for (
241
+ let i = baseIndex , j = 0 ;
242
+ i < codeLength && j < characters . length ;
243
+ i ++ , j ++
244
+ ) {
245
+ newCodeArr [ i ] = characters [ j ] ;
246
+ }
247
+
248
+ /** caret position */
249
+ let index = baseIndex + characters . length - 1 ;
250
+
251
+ /** constructed plain code */
252
+ const code = newCodeArr . join ( '' ) ;
253
+ if ( index === codeLength - 1 && code . length === codeLength ) {
220
254
if ( compareWithCode ) {
221
- const isMatching = this . _isMatchingCode ( code , compareWithCode , ignoreCase ) ;
255
+ const isMatching = this . _isMatchingCode (
256
+ code ,
257
+ compareWithCode ,
258
+ ignoreCase
259
+ ) ;
222
260
onFulfill ( isMatching , code ) ;
223
261
! isMatching && this . clear ( ) ;
224
262
} else {
225
263
onFulfill ( code ) ;
226
264
}
227
265
this . _blur ( this . state . currentIndex ) ;
228
266
} else {
229
- this . _setFocus ( this . state . currentIndex + 1 ) ;
267
+ this . _setFocus ( index + 1 ) ;
230
268
}
231
-
269
+
232
270
this . setState ( prevState => {
233
271
return {
234
272
codeArr : newCodeArr ,
235
- currentIndex : prevState . currentIndex + 1
273
+ currentIndex : index + 1 ,
236
274
} ;
237
- } , ( ) => { onCodeChange ( newCodeArr . join ( '' ) ) } ) ;
275
+ } ) ;
238
276
}
239
-
277
+
240
278
render ( ) {
241
279
const {
242
280
codeLength,
@@ -246,14 +284,14 @@ export default class ConfirmationCodeInput extends Component {
246
284
autoFocus,
247
285
className,
248
286
size,
249
- activeColor
287
+ activeColor,
250
288
} = this . props ;
251
-
289
+
252
290
const initialCodeInputStyle = {
253
291
width : size ,
254
- height : size
292
+ height : size ,
255
293
} ;
256
-
294
+
257
295
let codeInputs = [ ] ;
258
296
for ( let i = 0 ; i < codeLength ; i ++ ) {
259
297
const id = i ;
@@ -262,10 +300,10 @@ export default class ConfirmationCodeInput extends Component {
262
300
key = { id }
263
301
ref = { ref => ( this . codeInputRefs [ id ] = ref ) }
264
302
style = { [
265
- styles . codeInput ,
266
- initialCodeInputStyle ,
303
+ styles . codeInput ,
304
+ initialCodeInputStyle ,
267
305
this . _getClassStyle ( className , this . state . currentIndex == id ) ,
268
- codeInputStyle
306
+ codeInputStyle ,
269
307
] }
270
308
underlineColorAndroid = "transparent"
271
309
selectionColor = { activeColor }
@@ -274,16 +312,23 @@ export default class ConfirmationCodeInput extends Component {
274
312
{ ...this . props }
275
313
autoFocus = { autoFocus && id == 0 }
276
314
onFocus = { ( ) => this . _onFocus ( id ) }
277
- value = { this . state . codeArr [ id ] ? this . state . codeArr [ id ] . toString ( ) : '' }
315
+ value = {
316
+ this . state . codeArr [ id ] ? this . state . codeArr [ id ] . toString ( ) : ''
317
+ }
278
318
onChangeText = { text => this . _onInputCode ( text , id ) }
279
- onKeyPress = { ( e ) => this . _onKeyPress ( e ) }
280
- maxLength = { 1 }
319
+ onKeyPress = { e => this . _onKeyPress ( e ) }
281
320
/>
282
- )
321
+ ) ;
283
322
}
284
-
323
+
285
324
return (
286
- < View style = { [ styles . container , this . _getContainerStyle ( size , inputPosition ) , containerStyle ] } >
325
+ < View
326
+ style = { [
327
+ styles . container ,
328
+ this . _getContainerStyle ( size , inputPosition ) ,
329
+ containerStyle ,
330
+ ] }
331
+ >
287
332
{ codeInputs }
288
333
</ View >
289
334
) ;
@@ -294,11 +339,11 @@ const styles = StyleSheet.create({
294
339
container : {
295
340
flex : 1 ,
296
341
flexDirection : 'row' ,
297
- marginTop : 20
342
+ marginTop : 20 ,
298
343
} ,
299
344
codeInput : {
300
345
backgroundColor : 'transparent' ,
301
346
textAlign : 'center' ,
302
- padding : 0
303
- }
347
+ padding : 0 ,
348
+ } ,
304
349
} ) ;
0 commit comments