Skip to content

Commit b8ee858

Browse files
committed
Update ConfirmationCodeInput.js
✨🐛Add support for pasting and fix onFullfill call on backspace on last element ttdung11t2#30 from A-Tokyo (ttdung11t2#30)
1 parent 71491db commit b8ee858

File tree

1 file changed

+125
-80
lines changed

1 file changed

+125
-80
lines changed
Lines changed: 125 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
import React, {Component} from 'react';
1+
import React, { Component } from 'react';
22
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';
410
import _ from 'lodash';
511

612
// 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 {
2228
codeInputStyle: TextInput.propTypes.style,
2329
containerStyle: viewPropTypes.style,
2430
onFulfill: PropTypes.func,
25-
onCodeChange: PropTypes.func,
2631
};
27-
32+
2833
static defaultProps = {
2934
codeLength: 5,
3035
inputPosition: 'center',
@@ -38,45 +43,51 @@ export default class ConfirmationCodeInput extends Component {
3843
compareWithCode: '',
3944
ignoreCase: false,
4045
};
41-
46+
4247
constructor(props) {
4348
super(props);
44-
49+
4550
this.state = {
4651
codeArr: new Array(this.props.codeLength).fill(''),
47-
currentIndex: 0
52+
currentIndex: 0,
4853
};
49-
54+
5055
this.codeInputRefs = [];
5156
}
52-
57+
5358
componentDidMount() {
5459
const { compareWithCode, codeLength, inputPosition } = this.props;
5560
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+
);
5764
}
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+
);
6172
}
6273
}
63-
74+
6475
clear() {
6576
this.setState({
6677
codeArr: new Array(this.props.codeLength).fill(''),
67-
currentIndex: 0
78+
currentIndex: 0,
6879
});
6980
this._setFocus(0);
7081
}
71-
82+
7283
_setFocus(index) {
7384
this.codeInputRefs[index].focus();
7485
}
75-
86+
7687
_blur(index) {
7788
this.codeInputRefs[index].blur();
7889
}
79-
90+
8091
_onFocus(index) {
8192
let newCodeArr = _.clone(this.state.codeArr);
8293
const currentEmptyIndex = _.findIndex(newCodeArr, c => !c);
@@ -88,155 +99,182 @@ export default class ConfirmationCodeInput extends Component {
8899
newCodeArr[i] = '';
89100
}
90101
}
91-
102+
92103
this.setState({
93104
codeArr: newCodeArr,
94-
currentIndex: index
95-
})
105+
currentIndex: index,
106+
});
96107
}
97-
108+
98109
_isMatchingCode(code, compareWithCode, ignoreCase = false) {
99110
if (ignoreCase) {
100111
return code.toLowerCase() == compareWithCode.toLowerCase();
101112
}
102113
return code == compareWithCode;
103114
}
104-
115+
105116
_getContainerStyle(size, position) {
106117
switch (position) {
107118
case 'left':
108119
return {
109120
justifyContent: 'flex-start',
110-
height: size
121+
height: size,
111122
};
112123
case 'center':
113124
return {
114125
justifyContent: 'center',
115-
height: size
126+
height: size,
116127
};
117128
case 'right':
118129
return {
119130
justifyContent: 'flex-end',
120-
height: size
131+
height: size,
121132
};
122133
default:
123134
return {
124135
justifyContent: 'space-between',
125-
height: size
126-
}
136+
height: size,
137+
};
127138
}
128139
}
129-
140+
130141
_getInputSpaceStyle(space) {
131142
const { inputPosition } = this.props;
132143
switch (inputPosition) {
133144
case 'left':
134145
return {
135-
marginRight: space
146+
marginRight: space,
136147
};
137148
case 'center':
138149
return {
139-
marginRight: space/2,
140-
marginLeft: space/2
150+
marginRight: space / 2,
151+
marginLeft: space / 2,
141152
};
142153
case 'right':
143154
return {
144-
marginLeft: space
155+
marginLeft: space,
145156
};
146157
default:
147158
return {
148159
marginRight: 0,
149-
marginLeft: 0
160+
marginLeft: 0,
150161
};
151162
}
152163
}
153-
164+
154165
_getClassStyle(className, active) {
155166
const { cellBorderWidth, activeColor, inactiveColor, space } = this.props;
156167
let classStyle = {
157168
...this._getInputSpaceStyle(space),
158-
color: activeColor
169+
color: activeColor,
159170
};
160-
171+
161172
switch (className) {
162173
case 'clear':
163174
return _.merge(classStyle, { borderWidth: 0 });
164175
case 'border-box':
165176
return _.merge(classStyle, {
166177
borderWidth: cellBorderWidth,
167-
borderColor: (active ? activeColor : inactiveColor)
178+
borderColor: active ? activeColor : inactiveColor,
168179
});
169180
case 'border-circle':
170181
return _.merge(classStyle, {
171182
borderWidth: cellBorderWidth,
172183
borderRadius: 50,
173-
borderColor: (active ? activeColor : inactiveColor)
184+
borderColor: active ? activeColor : inactiveColor,
174185
});
175186
case 'border-b':
176187
return _.merge(classStyle, {
177188
borderBottomWidth: cellBorderWidth,
178-
borderColor: (active ? activeColor : inactiveColor),
189+
borderColor: active ? activeColor : inactiveColor,
179190
});
180191
case 'border-b-t':
181192
return _.merge(classStyle, {
182193
borderTopWidth: cellBorderWidth,
183194
borderBottomWidth: cellBorderWidth,
184-
borderColor: (active ? activeColor : inactiveColor)
195+
borderColor: active ? activeColor : inactiveColor,
185196
});
186197
case 'border-l-r':
187198
return _.merge(classStyle, {
188199
borderLeftWidth: cellBorderWidth,
189200
borderRightWidth: cellBorderWidth,
190-
borderColor: (active ? activeColor : inactiveColor)
201+
borderColor: active ? activeColor : inactiveColor,
191202
});
192203
default:
193204
return className;
194205
}
195206
}
196-
207+
197208
_onKeyPress(e) {
198209
if (e.nativeEvent.key === 'Backspace') {
199210
const { currentIndex } = this.state;
200-
let newCodeArr = _.clone(this.state.codeArr);
201211
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(''))
208212
this._setFocus(nextIndex);
209213
}
210214
}
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+
214239
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) {
220254
if (compareWithCode) {
221-
const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase);
255+
const isMatching = this._isMatchingCode(
256+
code,
257+
compareWithCode,
258+
ignoreCase
259+
);
222260
onFulfill(isMatching, code);
223261
!isMatching && this.clear();
224262
} else {
225263
onFulfill(code);
226264
}
227265
this._blur(this.state.currentIndex);
228266
} else {
229-
this._setFocus(this.state.currentIndex + 1);
267+
this._setFocus(index + 1);
230268
}
231-
269+
232270
this.setState(prevState => {
233271
return {
234272
codeArr: newCodeArr,
235-
currentIndex: prevState.currentIndex + 1
273+
currentIndex: index + 1,
236274
};
237-
}, () => { onCodeChange(newCodeArr.join('')) });
275+
});
238276
}
239-
277+
240278
render() {
241279
const {
242280
codeLength,
@@ -246,14 +284,14 @@ export default class ConfirmationCodeInput extends Component {
246284
autoFocus,
247285
className,
248286
size,
249-
activeColor
287+
activeColor,
250288
} = this.props;
251-
289+
252290
const initialCodeInputStyle = {
253291
width: size,
254-
height: size
292+
height: size,
255293
};
256-
294+
257295
let codeInputs = [];
258296
for (let i = 0; i < codeLength; i++) {
259297
const id = i;
@@ -262,10 +300,10 @@ export default class ConfirmationCodeInput extends Component {
262300
key={id}
263301
ref={ref => (this.codeInputRefs[id] = ref)}
264302
style={[
265-
styles.codeInput,
266-
initialCodeInputStyle,
303+
styles.codeInput,
304+
initialCodeInputStyle,
267305
this._getClassStyle(className, this.state.currentIndex == id),
268-
codeInputStyle
306+
codeInputStyle,
269307
]}
270308
underlineColorAndroid="transparent"
271309
selectionColor={activeColor}
@@ -274,16 +312,23 @@ export default class ConfirmationCodeInput extends Component {
274312
{...this.props}
275313
autoFocus={autoFocus && id == 0}
276314
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+
}
278318
onChangeText={text => this._onInputCode(text, id)}
279-
onKeyPress={(e) => this._onKeyPress(e)}
280-
maxLength={1}
319+
onKeyPress={e => this._onKeyPress(e)}
281320
/>
282-
)
321+
);
283322
}
284-
323+
285324
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+
>
287332
{codeInputs}
288333
</View>
289334
);
@@ -294,11 +339,11 @@ const styles = StyleSheet.create({
294339
container: {
295340
flex: 1,
296341
flexDirection: 'row',
297-
marginTop: 20
342+
marginTop: 20,
298343
},
299344
codeInput: {
300345
backgroundColor: 'transparent',
301346
textAlign: 'center',
302-
padding: 0
303-
}
347+
padding: 0,
348+
},
304349
});

0 commit comments

Comments
 (0)