@twotalltotems/react-native-otp-input
Version:
is a tiny JS library for one time passcode (OTP). Supports smart input suggestion on iOS and code autofill on Android (it will be filled when you press the copy button on the SMS notification bar)
200 lines • 8.88 kB
JavaScript
import React, { Component } from 'react';
import { View, TextInput, TouchableWithoutFeedback, Keyboard, Platform, I18nManager, } from 'react-native';
import Clipboard from '@react-native-community/clipboard';
import styles from './styles';
import { isAutoFillSupported } from './helpers/device';
import { codeToArray } from './helpers/codeToArray';
export default class OTPInputView extends Component {
constructor(props) {
super(props);
this.fields = [];
this.copyCodeFromClipBoardOnAndroid = () => {
if (Platform.OS === "android") {
this.checkPinCodeFromClipBoard();
this.timer = setInterval(this.checkPinCodeFromClipBoard, 400);
}
};
this.bringUpKeyBoardIfNeeded = () => {
const { autoFocusOnLoad, pinCount } = this.props;
const digits = this.getDigits();
const focusIndex = digits.length ? digits.length - 1 : 0;
if (focusIndex < pinCount && autoFocusOnLoad) {
this.focusField(focusIndex);
}
};
this.getDigits = () => {
const { digits: innerDigits } = this.state;
const { code } = this.props;
return code === undefined ? innerDigits : code.split("");
};
this.handleKeyboardDidHide = () => {
this.blurAllFields();
};
this.notifyCodeChanged = () => {
const { digits } = this.state;
const code = digits.join("");
const { onCodeChanged } = this.props;
if (onCodeChanged) {
onCodeChanged(code);
}
};
this.checkPinCodeFromClipBoard = () => {
const { pinCount, onCodeFilled } = this.props;
const regexp = new RegExp(`^\\d{${pinCount}}$`);
Clipboard.getString().then(code => {
if (this.hasCheckedClipBoard && regexp.test(code) && (this.clipBoardCode !== code)) {
this.setState({
digits: code.split(""),
}, () => {
this.blurAllFields();
this.notifyCodeChanged();
onCodeFilled && onCodeFilled(code);
});
}
this.clipBoardCode = code;
this.hasCheckedClipBoard = true;
}).catch(() => {
});
};
this.handleChangeText = (index, text) => {
const { onCodeFilled, pinCount } = this.props;
const digits = this.getDigits();
let newdigits = digits.slice();
const oldTextLength = newdigits[index] ? newdigits[index].length : 0;
const newTextLength = text.length;
if (newTextLength - oldTextLength === pinCount) { // user pasted text in.
newdigits = text.split("").slice(oldTextLength, newTextLength);
this.setState({ digits: newdigits }, this.notifyCodeChanged);
}
else {
if (text.length === 0) {
if (newdigits.length > 0) {
newdigits = newdigits.slice(0, newdigits.length - 1);
}
}
else {
text.split("").forEach((value) => {
if (index < pinCount) {
newdigits[index] = value;
index += 1;
}
});
index -= 1;
}
this.setState({ digits: newdigits }, this.notifyCodeChanged);
}
let result = newdigits.join("");
if (result.length >= pinCount) {
onCodeFilled && onCodeFilled(result);
this.focusField(pinCount - 1);
this.blurAllFields();
}
else {
if (text.length > 0 && index < pinCount - 1) {
this.focusField(index + 1);
}
}
};
this.handleKeyPressTextInput = (index, key) => {
const digits = this.getDigits();
if (key === 'Backspace') {
if (!digits[index] && index > 0) {
this.handleChangeText(index - 1, '');
this.focusField(index - 1);
}
}
};
this.focusField = (index) => {
if (index < this.fields.length) {
this.fields[index].focus();
this.setState({
selectedIndex: index
});
}
};
this.blurAllFields = () => {
this.fields.forEach((field) => field.blur());
this.setState({
selectedIndex: -1,
});
};
this.clearAllFields = () => {
const { clearInputs, code } = this.props;
if (clearInputs && code === "") {
this.setState({ digits: [], selectedIndex: 0 });
}
};
this.renderOneInputField = (_, index) => {
const { codeInputFieldStyle, codeInputHighlightStyle, secureTextEntry, editable, keyboardType, selectionColor, keyboardAppearance } = this.props;
const { defaultTextFieldStyle } = styles;
const { selectedIndex, digits } = this.state;
const { clearInputs, placeholderCharacter, placeholderTextColor } = this.props;
const { color: defaultPlaceholderTextColor } = { ...defaultTextFieldStyle, ...codeInputFieldStyle };
return (<View pointerEvents="none" key={index + "view"} testID="inputSlotView">
<TextInput testID="textInput" underlineColorAndroid='rgba(0,0,0,0)' style={selectedIndex === index ? [defaultTextFieldStyle, codeInputFieldStyle, codeInputHighlightStyle] : [defaultTextFieldStyle, codeInputFieldStyle]} ref={ref => { this.fields[index] = ref; }} onChangeText={text => {
this.handleChangeText(index, text);
}} onKeyPress={({ nativeEvent: { key } }) => { this.handleKeyPressTextInput(index, key); }} value={!clearInputs ? digits[index] : ""} keyboardAppearance={keyboardAppearance} keyboardType={keyboardType} textContentType={isAutoFillSupported ? "oneTimeCode" : "none"} key={index} selectionColor={selectionColor} secureTextEntry={secureTextEntry} editable={editable} placeholder={placeholderCharacter} placeholderTextColor={placeholderTextColor || defaultPlaceholderTextColor}/>
</View>);
};
this.renderTextFields = () => {
const { pinCount } = this.props;
const array = new Array(pinCount).fill(0);
return array.map(this.renderOneInputField);
};
const { code } = props;
this.state = {
digits: codeToArray(code),
selectedIndex: props.autoFocusOnLoad ? 0 : -1,
};
}
UNSAFE_componentWillReceiveProps(nextProps) {
const { code } = this.props;
if (nextProps.code !== code) {
this.setState({ digits: codeToArray(nextProps.code) });
}
}
componentDidMount() {
this.copyCodeFromClipBoardOnAndroid();
this.bringUpKeyBoardIfNeeded();
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.handleKeyboardDidHide);
}
componentWillUnmount() {
var _a;
if (this.timer) {
clearInterval(this.timer);
}
(_a = this.keyboardDidHideListener) === null || _a === void 0 ? void 0 : _a.remove();
}
render() {
const { pinCount, style, clearInputs } = this.props;
const digits = this.getDigits();
return (<View testID="OTPInputView" style={style}>
<TouchableWithoutFeedback style={{ width: '100%', height: '100%' }} onPress={() => {
if (!clearInputs) {
let filledPinCount = digits.filter((digit) => { return (digit !== null && digit !== undefined); }).length;
this.focusField(Math.min(filledPinCount, pinCount - 1));
}
else {
this.clearAllFields();
this.focusField(0);
}
}}>
<View style={{ flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row', justifyContent: 'space-between', alignItems: 'center', width: '100%', height: '100%' }}>
{this.renderTextFields()}
</View>
</TouchableWithoutFeedback>
</View>);
}
}
OTPInputView.defaultProps = {
pinCount: 6,
autoFocusOnLoad: true,
secureTextEntry: false,
editable: true,
keyboardAppearance: "default",
keyboardType: "number-pad",
clearInputs: false,
placeholderCharacter: "",
selectionColor: '#000',
};
//# sourceMappingURL=index.js.map