diff --git a/.npmignore b/.npmignore index 32239cd..206811a 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,4 @@ .idea .npmignore -example \ No newline at end of file +example +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md index 01352fb..d637430 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ A react-native confirmation code input for both IOS and Android ## Installation ```sh -npm install react-native-confirmation-code-input --save +yarn add @andreferi/react-native-confirmation-code-input --save ``` ## Usage ### Basic Import this module: ```javascript -import CodeInput from 'react-native-confirmation-code-input'; +import CodeInput from '@andreferi/react-native-confirmation-code-input'; ``` Use as a component and style it: ```javascript diff --git a/components/ConfirmationCodeInput.js b/components/ConfirmationCodeInput.js index 1d9f9b2..11a55e7 100644 --- a/components/ConfirmationCodeInput.js +++ b/components/ConfirmationCodeInput.js @@ -1,6 +1,6 @@ -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 { View, TextInput, StyleSheet, ViewPropTypes, Clipboard } from 'react-native'; import _ from 'lodash'; // if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44) @@ -22,9 +22,8 @@ export default class ConfirmationCodeInput extends Component { codeInputStyle: TextInput.propTypes.style, containerStyle: viewPropTypes.style, onFulfill: PropTypes.func, - onCodeChange: PropTypes.func, }; - + static defaultProps = { codeLength: 5, inputPosition: 'center', @@ -36,31 +35,32 @@ export default class ConfirmationCodeInput extends Component { inactiveColor: 'rgba(255, 255, 255, 0.2)', space: 8, compareWithCode: '', - ignoreCase: false, + ignoreCase: false }; - + constructor(props) { super(props); - + this.state = { codeArr: new Array(this.props.codeLength).fill(''), - currentIndex: 0 + currentIndex: 0, + isPasted: false }; - + 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"); } - + 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(''), @@ -68,15 +68,15 @@ export default class ConfirmationCodeInput extends Component { }); 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); @@ -88,20 +88,20 @@ export default class ConfirmationCodeInput extends Component { newCodeArr[i] = ''; } } - + this.setState({ codeArr: newCodeArr, currentIndex: index }) } - + _isMatchingCode(code, compareWithCode, ignoreCase = false) { if (ignoreCase) { return code.toLowerCase() == compareWithCode.toLowerCase(); } return code == compareWithCode; } - + _getContainerStyle(size, position) { switch (position) { case 'left': @@ -126,7 +126,7 @@ export default class ConfirmationCodeInput extends Component { } } } - + _getInputSpaceStyle(space) { const { inputPosition } = this.props; switch (inputPosition) { @@ -136,8 +136,8 @@ export default class ConfirmationCodeInput extends Component { }; case 'center': return { - marginRight: space/2, - marginLeft: space/2 + marginRight: space / 2, + marginLeft: space / 2 }; case 'right': return { @@ -150,14 +150,14 @@ export default class ConfirmationCodeInput extends Component { }; } } - + _getClassStyle(className, active) { const { cellBorderWidth, activeColor, inactiveColor, space } = this.props; let classStyle = { ...this._getInputSpaceStyle(space), color: activeColor }; - + switch (className) { case 'clear': return _.merge(classStyle, { borderWidth: 0 }); @@ -193,50 +193,73 @@ export default class ConfirmationCodeInput extends Component { return className; } } - + _onKeyPress(e) { if (e.nativeEvent.key === 'Backspace') { const { currentIndex } = this.state; - let newCodeArr = _.clone(this.state.codeArr); const nextIndex = currentIndex > 0 ? currentIndex - 1 : 0; - for (const i in newCodeArr) { - if (i >= nextIndex) { - newCodeArr[i] = ''; - } - } - this.props.onCodeChange(newCodeArr.join('')) this._setFocus(nextIndex); } } - - _onInputCode(character, index) { - const { codeLength, onFulfill, compareWithCode, ignoreCase, onCodeChange } = this.props; + + async _onInputCode(character, index) { + var copiedContent = await Clipboard.getString(); + let pastedArr = copiedContent.split('') let newCodeArr = _.clone(this.state.codeArr); - newCodeArr[index] = character; - - if (index == codeLength - 1) { - const code = newCodeArr.join(''); - - if (compareWithCode) { - const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase); - onFulfill(isMatching, code); - !isMatching && this.clear(); + let isPasted = newCodeArr.includes(pastedArr[0]) + const { codeLength, onFulfill, compareWithCode, ignoreCase } = this.props; + if (!isPasted && index == 0 && character.length > 1) { + for (let i in pastedArr) { + newCodeArr[i] = pastedArr[i] + } + + if (copiedContent.length == codeLength) { + const code = newCodeArr.join(''); + + if (compareWithCode) { + const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase); + onFulfill(isMatching, code); + !isMatching && this.clear(); + } else { + onFulfill(code); + } + this._blur(this.state.currentIndex); } else { - onFulfill(code); + this._setFocus(pastedArr.length) } - this._blur(this.state.currentIndex); + + this.setState({ + codeArr: newCodeArr, + currentIndex: pastedArr.length, + isPasted: true + }) } else { - this._setFocus(this.state.currentIndex + 1); - } - - this.setState(prevState => { - return { + const i = this.state.currentIndex + newCodeArr[i] = character + + if (i == codeLength - 1) { + const code = newCodeArr.join(''); + + if (compareWithCode) { + const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase); + onFulfill(isMatching, code); + !isMatching && this.clear(); + } else { + onFulfill(code); + } + this._blur(i); + } else { + this._setFocus(i + 1) + } + + this.setState(prevState => ({ codeArr: newCodeArr, - currentIndex: prevState.currentIndex + 1 - }; - }, () => { onCodeChange(newCodeArr.join('')) }); + currentIndex: prevState.currentIndex + 1, + isPasted: true + })) + } } - + render() { const { codeLength, @@ -245,15 +268,13 @@ export default class ConfirmationCodeInput extends Component { inputPosition, autoFocus, className, - size, - activeColor - } = this.props; - + size } = this.props; + const initialCodeInputStyle = { width: size, height: size }; - + let codeInputs = []; for (let i = 0; i < codeLength; i++) { const id = i; @@ -262,26 +283,28 @@ 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, + (className == 'border-circle' && this.state.codeArr[id]) && { + backgroundColor: '#e5e5e5' + } ]} underlineColorAndroid="transparent" - selectionColor={activeColor} + selectionColor={'white'} keyboardType={'name-phone-pad'} returnKeyType={'done'} {...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] && className !== 'border-circle' ? this.state.codeArr[id].toString() : ''} onChangeText={text => this._onInputCode(text, id)} onKeyPress={(e) => this._onKeyPress(e)} - maxLength={1} /> ) } - + return ( {codeInputs} diff --git a/index.d.ts b/index.d.ts index 3473a5d..fe44263 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,7 @@ import * as React from "react"; import * as ReactNative from "react-native"; -declare module "react-native-confirmation-code-input" { +declare module "@andreferi/react-native-confirmation-code-input" { type InputPositions = 'left' | 'right' | 'center' | 'full-width'; type ClassNames = 'border-box' | 'border-circle' | 'border-b' | 'border-b-t' | 'border-l-r'; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b893226 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,46 @@ +{ + "name": "@andre/react-native-confirmation-code-input", + "version": "1.1.0", + "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": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "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..5fe09f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-native-confirmation-code-input", - "version": "1.0.1", + "name": "@andreferi/react-native-confirmation-code-input", + "version": "1.1.0", "description": "A react-native component to input confirmation code for both Android and IOS", "main": "index.js", "scripts": { @@ -30,8 +30,11 @@ }, "repository": { "type": "git", - "url": "https://github.com/ttdung11t2/react-native-confirmation-code-input.git" + "url": "git+https://github.com/ttdung11t2/react-native-confirmation-code-input.git" }, "author": "Dung Tran ", - "license": "MIT" + "license": "MIT", + "directories": { + "example": "example" + } }