///
import type {
InputProps,
OTPInputViewState,
} from 'react-native-otp-input-reborn';
import React, { Component } from 'react';
import {
View,
TextInput,
TouchableWithoutFeedback,
Keyboard,
Platform,
I18nManager,
EmitterSubscription,
} from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import styles from './styles';
import { isAutoFillSupported } from './helpers/device';
import { codeToArray } from './helpers/codeToArray';
export default class OTPInputView extends Component<
InputProps,
OTPInputViewState
> {
static defaultProps: InputProps = {
pinCount: 6,
autoFocusOnLoad: true,
secureTextEntry: false,
editable: true,
keyboardAppearance: 'default',
keyboardType: 'number-pad',
clearInputs: false,
placeholderCharacter: '',
selectionColor: '#000',
maxLength: 1,
autoFillCode: true,
};
private fields: TextInput[] | null[] = [];
private keyboardDidHideListener?: EmitterSubscription;
private timer?: NodeJS.Timeout;
private hasCheckedClipBoard?: boolean;
private clipBoardCode?: string;
constructor(props: InputProps) {
super(props);
const { code } = props;
this.state = {
digits: codeToArray(code),
selectedIndex: props.autoFocusOnLoad ? 0 : -1,
};
}
UNSAFE_componentWillReceiveProps(nextProps: InputProps) {
const { code } = this.props;
if (nextProps.code !== code) {
this.setState({ digits: codeToArray(nextProps.code) });
}
}
componentDidMount() {
this.copyCodeFromClipBoardOnAndroid();
setTimeout(() => this.bringUpKeyBoardIfNeeded(), 100);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
this.handleKeyboardDidHide
);
}
componentWillUnmount() {
if (this.timer) {
clearInterval(this.timer);
}
this.keyboardDidHideListener?.remove();
}
private copyCodeFromClipBoardOnAndroid = () => {
if (Platform.OS === 'android') {
this.checkPinCodeFromClipBoard();
this.timer = setInterval(this.checkPinCodeFromClipBoard, 400);
}
};
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);
}
};
getDigits = () => {
const { digits: innerDigits } = this.state;
const { code } = this.props;
return code === undefined ? innerDigits : code.split('');
};
private handleKeyboardDidHide = () => {
this.blurAllFields();
};
private notifyCodeChanged = () => {
const { digits } = this.state;
const code = digits.join('');
const { onCodeChanged } = this.props;
if (onCodeChanged) {
onCodeChanged(code);
}
};
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(() => {});
};
private handleChangeText = (index: number, text: string) => {
const { onCodeFilled, pinCount } = this.props;
const digits = this.getDigits();
let newdigits = digits.slice();
const oldTextLength = 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);
}
}
};
private handleKeyPressTextInput = (index: number, key: string) => {
const digits = this.getDigits();
if (key === 'Backspace') {
if (!digits[index] && index > 0) {
this.handleChangeText(index - 1, '');
this.focusField(index - 1);
}
}
};
focusField = (index: number) => {
if (index < this.fields.length && this.fields[index]) {
(this.fields[index] as TextInput).focus();
this.setState({
selectedIndex: index,
});
}
};
blurAllFields = () => {
this.fields.forEach((field: TextInput | null) =>
(field as TextInput).blur()
);
this.setState({
selectedIndex: -1,
});
};
clearAllFields = () => {
const { clearInputs, code } = this.props;
if (clearInputs && !code) {
this.setState({ digits: [], selectedIndex: 0 });
}
};
renderOneInputField = (_: TextInput, index: number) => {
const {
codeInputFieldStyle,
codeInputHighlightStyle,
secureTextEntry,
editable,
keyboardType,
selectionColor,
keyboardAppearance,
autoFillCode,
clearInputs,
} = this.props;
const { defaultTextFieldStyle } = styles;
const { selectedIndex, digits } = this.state;
const { placeholderCharacter, placeholderTextColor, maxLength } =
this.props;
const { color: defaultPlaceholderTextColor } = {
...defaultTextFieldStyle,
...codeInputFieldStyle,
};
return (
{
this.fields[index] = ref;
}}
onChangeText={(text) => {
this.handleChangeText(index, text);
}}
maxLength={maxLength}
onKeyPress={({ nativeEvent: { key } }) => {
this.handleKeyPressTextInput(index, key);
}}
value={!clearInputs ? digits[index] : ''}
keyboardAppearance={keyboardAppearance}
keyboardType={keyboardType}
textContentType={
autoFillCode && isAutoFillSupported ? 'oneTimeCode' : 'none'
}
key={index}
selectionColor={selectionColor}
secureTextEntry={secureTextEntry}
editable={editable}
placeholder={placeholderCharacter}
placeholderTextColor={
placeholderTextColor || defaultPlaceholderTextColor
}
/>
);
};
renderTextFields = () => {
const { pinCount } = this.props;
const array = new Array(pinCount).fill(0);
return array.map(this.renderOneInputField);
};
render() {
const { pinCount, style, clearInputs } = this.props;
const digits = this.getDigits();
return (
{
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);
}
}}
>
{this.renderTextFields()}
);
}
}