diff --git a/.gitignore b/.gitignore index 91048aa..b93bf30 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,3 @@ typings/ # OSX .DS_Store - diff --git a/components/ConfirmationCodeInput.js b/components/ConfirmationCodeInput.js index 1d9f9b2..3872987 100644 --- a/components/ConfirmationCodeInput.js +++ b/components/ConfirmationCodeInput.js @@ -1,7 +1,16 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { View, TextInput, StyleSheet, Dimensions, ViewPropTypes } from 'react-native'; -import _ from 'lodash'; +import { + View, + TextInput, + StyleSheet, + Dimensions, + ViewPropTypes, +} from 'react-native'; +import clone from 'lodash.clone'; +import findIndex from 'lodash.findindex'; +import indexOf from 'lodash.indexof'; +import merge from 'lodash.merge'; // if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44) const viewPropTypes = ViewPropTypes || View.propTypes; @@ -24,7 +33,7 @@ export default class ConfirmationCodeInput extends Component { onFulfill: PropTypes.func, onCodeChange: PropTypes.func, }; - + static defaultProps = { codeLength: 5, inputPosition: 'center', @@ -38,48 +47,54 @@ export default class ConfirmationCodeInput extends Component { compareWithCode: '', ignoreCase: false, }; - + constructor(props) { super(props); - + this.state = { codeArr: new Array(this.props.codeLength).fill(''), - currentIndex: 0 + currentIndex: 0, }; - + this.codeInputRefs = []; } - + componentDidMount() { const { compareWithCode, codeLength, inputPosition } = this.props; if (compareWithCode && compareWithCode.length !== codeLength) { - console.error("Invalid props: compareWith length is not equal to codeLength"); + console.error( + 'Invalid props: compareWith length is not equal to codeLength' + ); } - - if (_.indexOf(['center', 'left', 'right', 'full-width'], inputPosition) === -1) { - console.error('Invalid input position. Must be in: center, left, right, full'); + + if ( + indexOf(['center', 'left', 'right', 'full-width'], inputPosition) === -1 + ) { + console.error( + 'Invalid input position. Must be in: center, left, right, full' + ); } } - + clear() { this.setState({ codeArr: new Array(this.props.codeLength).fill(''), - currentIndex: 0 + currentIndex: 0, }); this._setFocus(0); } - + _setFocus(index) { this.codeInputRefs[index].focus(); } - + _blur(index) { this.codeInputRefs[index].blur(); } - + _onFocus(index) { - let newCodeArr = _.clone(this.state.codeArr); - const currentEmptyIndex = _.findIndex(newCodeArr, c => !c); + let newCodeArr = clone(this.state.codeArr); + const currentEmptyIndex = findIndex(newCodeArr, c => !c); if (currentEmptyIndex !== -1 && currentEmptyIndex < index) { return this._setFocus(currentEmptyIndex); } @@ -88,112 +103,112 @@ export default class ConfirmationCodeInput extends Component { newCodeArr[i] = ''; } } - + this.setState({ codeArr: newCodeArr, - currentIndex: index - }) + currentIndex: index, + }); } - + _isMatchingCode(code, compareWithCode, ignoreCase = false) { if (ignoreCase) { return code.toLowerCase() == compareWithCode.toLowerCase(); } return code == compareWithCode; } - + _getContainerStyle(size, position) { switch (position) { case 'left': return { justifyContent: 'flex-start', - height: size + height: size, }; case 'center': return { justifyContent: 'center', - height: size + height: size, }; case 'right': return { justifyContent: 'flex-end', - height: size + height: size, }; default: return { justifyContent: 'space-between', - height: size - } + height: size, + }; } } - + _getInputSpaceStyle(space) { const { inputPosition } = this.props; switch (inputPosition) { case 'left': return { - marginRight: space + marginRight: space, }; case 'center': return { - marginRight: space/2, - marginLeft: space/2 + marginRight: space / 2, + marginLeft: space / 2, }; case 'right': return { - marginLeft: space + marginLeft: space, }; default: return { marginRight: 0, - marginLeft: 0 + marginLeft: 0, }; } } - + _getClassStyle(className, active) { const { cellBorderWidth, activeColor, inactiveColor, space } = this.props; let classStyle = { ...this._getInputSpaceStyle(space), - color: activeColor + color: activeColor, }; - + switch (className) { case 'clear': - return _.merge(classStyle, { borderWidth: 0 }); + return merge(classStyle, { borderWidth: 0 }); case 'border-box': - return _.merge(classStyle, { + return merge(classStyle, { borderWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor) + borderColor: active ? activeColor : inactiveColor, }); case 'border-circle': - return _.merge(classStyle, { + return merge(classStyle, { borderWidth: cellBorderWidth, borderRadius: 50, - borderColor: (active ? activeColor : inactiveColor) + borderColor: active ? activeColor : inactiveColor, }); case 'border-b': - return _.merge(classStyle, { + return merge(classStyle, { borderBottomWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor), + borderColor: active ? activeColor : inactiveColor, }); case 'border-b-t': - return _.merge(classStyle, { + return merge(classStyle, { borderTopWidth: cellBorderWidth, borderBottomWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor) + borderColor: active ? activeColor : inactiveColor, }); case 'border-l-r': - return _.merge(classStyle, { + return merge(classStyle, { borderLeftWidth: cellBorderWidth, borderRightWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor) + borderColor: active ? activeColor : inactiveColor, }); default: return className; } } - + _onKeyPress(e) { if (e.nativeEvent.key === 'Backspace') { const { currentIndex } = this.state; @@ -208,17 +223,52 @@ export default class ConfirmationCodeInput extends Component { this._setFocus(nextIndex); } } - - _onInputCode(character, index) { - const { codeLength, onFulfill, compareWithCode, ignoreCase, onCodeChange } = this.props; - let newCodeArr = _.clone(this.state.codeArr); - newCodeArr[index] = character; - - if (index == codeLength - 1) { - const code = newCodeArr.join(''); - + + /** synthesizes the input characters based on the keyboard type removing invalid characters */ + _synthesizeInput = characters => { + const { keyboardType } = this.props; + if (keyboardType === 'numeric') { + return characters.replace(/\D/g, ''); + } + return characters; + }; + + _onInputCode(baseCharacters, baseIndex) { + const { + codeLength, + onFulfill, + compareWithCode, + ignoreCase, + keyboardType, + onCodeChange, + } = this.props; + + const characters = this._synthesizeInput(baseCharacters).substring( + 0, + codeLength - baseIndex + ); + + let newCodeArr = clone(this.state.codeArr); + for ( + let i = baseIndex, j = 0; + i < codeLength && j < characters.length; + i++ , j++ + ) { + newCodeArr[i] = characters[j]; + } + + /** caret position */ + let index = baseIndex + characters.length - 1; + + /** constructed plain code */ + const code = newCodeArr.join(''); + if (index === codeLength - 1 && code.length === codeLength) { if (compareWithCode) { - const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase); + const isMatching = this._isMatchingCode( + code, + compareWithCode, + ignoreCase + ); onFulfill(isMatching, code); !isMatching && this.clear(); } else { @@ -226,17 +276,21 @@ export default class ConfirmationCodeInput extends Component { } this._blur(this.state.currentIndex); } else { - this._setFocus(this.state.currentIndex + 1); + this._setFocus(index + 1); } - + this.setState(prevState => { return { codeArr: newCodeArr, - currentIndex: prevState.currentIndex + 1 + currentIndex: index + 1, }; - }, () => { onCodeChange(newCodeArr.join('')) }); + }, () => { + if (onCodeChange) { + onCodeChange(newCodeArr.join('')) + } + }); } - + render() { const { codeLength, @@ -246,14 +300,14 @@ export default class ConfirmationCodeInput extends Component { autoFocus, className, size, - activeColor + activeColor, } = this.props; - + const initialCodeInputStyle = { width: size, - height: size + height: size, }; - + let codeInputs = []; for (let i = 0; i < codeLength; i++) { const id = i; @@ -262,10 +316,10 @@ export default class ConfirmationCodeInput extends Component { key={id} ref={ref => (this.codeInputRefs[id] = ref)} style={[ - styles.codeInput, - initialCodeInputStyle, + styles.codeInput, + initialCodeInputStyle, this._getClassStyle(className, this.state.currentIndex == id), - codeInputStyle + codeInputStyle, ]} underlineColorAndroid="transparent" selectionColor={activeColor} @@ -274,16 +328,23 @@ export default class ConfirmationCodeInput extends Component { {...this.props} autoFocus={autoFocus && id == 0} onFocus={() => this._onFocus(id)} - value={this.state.codeArr[id] ? this.state.codeArr[id].toString() : ''} + value={ + this.state.codeArr[id] ? this.state.codeArr[id].toString() : '' + } onChangeText={text => this._onInputCode(text, id)} - onKeyPress={(e) => this._onKeyPress(e)} - maxLength={1} + onKeyPress={e => this._onKeyPress(e)} /> - ) + ); } - + return ( - + {codeInputs} ); @@ -294,11 +355,11 @@ const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', - marginTop: 20 + marginTop: 20, }, codeInput: { backgroundColor: 'transparent', textAlign: 'center', - padding: 0 - } + padding: 0, + }, }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..77cbd02 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,61 @@ +{ + "name": "react-native-confirmation-code-input", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "lodash.findindex": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", + "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=" + }, + "lodash.indexof": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz", + "integrity": "sha1-U3FK3Czd1u2HY4+JOqm2wk4x7zw=" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } +} diff --git a/package.json b/package.json index d7b2aa8..6730fbf 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,11 @@ "pin-code-input" ], "dependencies": { - "lodash": "^4.17.4", - "prop-types": "^15.5.10" + "lodash.clone": "^4.5.0", + "lodash.findindex": "^4.6.0", + "lodash.indexof": "^4.0.5", + "lodash.merge": "^4.6.2", + "prop-types": "^15.7.2" }, "homepage": "https://github.com/ttdung11t2/react-native-confirmation-code-input.git", "bugs": { @@ -33,5 +36,17 @@ "url": "https://github.com/ttdung11t2/react-native-confirmation-code-input.git" }, "author": "Dung Tran ", + "contributors": [ + { + "name": "Dung Tran", + "email": "ttdung001@gmail.com", + "url": "https://github.com/ttdung11t2" + }, + { + "name": "Ahmed Tarek", + "email": "ahmed.tokyo1@gmail.com", + "url": "https://github.com/A-Tokyo" + } + ], "license": "MIT" }