@bherila/react-native-otp-inputs
Version:
One-time password inputs built in pure JS for React-Native
236 lines (215 loc) • 7.37 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import Clipboard from '@react-native-clipboard/clipboard';
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useReducer, useRef } from 'react';
import { Keyboard, Platform, StyleSheet, View } from 'react-native';
import OtpInput from './OtpInput';
import { OtpInputsRef } from './types';
import { fillOtpCode } from './helpers';
import reducer from './reducer';
const supportAutofillFromClipboard = Platform.OS === 'android' || parseInt(Platform.Version, 10) < 14;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
}
});
const OtpInputs = /*#__PURE__*/forwardRef(({
autofillFromClipboard = supportAutofillFromClipboard,
autofillListenerIntervalMS = 1000,
autoFocus,
autoCapitalize = 'none',
clearTextOnFocus = false,
defaultValue,
focusStyles,
handleChange = console.log,
inputContainerStyles,
inputStyles,
isRTL = false,
keyboardType = 'phone-pad',
numberOfInputs = 4,
placeholder = '',
secureTextEntry = false,
selectTextOnFocus = true,
style,
testIDPrefix = 'otpInput',
...restProps
}, ref) => {
const previousCopiedText = useRef('');
const inputs = useRef([]);
const [{
otpCode,
hasKeySupport
}, dispatch] = useReducer(reducer, {}, () => ({
otpCode: fillOtpCode(numberOfInputs, defaultValue),
handleChange,
hasKeySupport: Platform.OS === 'ios'
}));
useEffect(() => {
dispatch({
type: 'setOtpCode',
payload: {
numberOfInputs,
code: defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''
}
});
}, [defaultValue, numberOfInputs]);
useEffect(() => {
dispatch({
type: 'setHandleChange',
payload: handleChange
});
}, [handleChange]);
useImperativeHandle(ref, () => ({
reset: () => {
dispatch({
type: 'clearOtp',
payload: numberOfInputs
});
inputs.current.forEach(input => {
var _input$current;
return input === null || input === void 0 ? void 0 : (_input$current = input.current) === null || _input$current === void 0 ? void 0 : _input$current.clear();
});
previousCopiedText.current = '';
Clipboard.setString('');
},
focus: () => {
var _firstInput$current;
const firstInput = inputs.current[0];
firstInput === null || firstInput === void 0 ? void 0 : (_firstInput$current = firstInput.current) === null || _firstInput$current === void 0 ? void 0 : _firstInput$current.focus();
}
}), [numberOfInputs]);
const handleInputTextChange = (text, index) => {
if (!text.length) {
handleClearInput(index);
}
if (text.length > 1) {
handleClearInput(index);
Keyboard.dismiss();
return fillInputs(text);
}
if (text) {
dispatch({
type: 'setOtpTextForIndex',
payload: {
text,
index
}
});
focusInput(index + 1);
}
if (index === numberOfInputs - 1 && text) {
Keyboard.dismiss();
}
};
const handleTextChange = (text, index) => {
if (Platform.OS === 'android' && !hasKeySupport || Platform.OS === 'ios' && text.length > 1) {
handleInputTextChange(text, index);
}
};
const handleKeyPress = ({
nativeEvent: {
key
}
}, index) => {
handleInputTextChange(key === 'Backspace' ? '' : key, index);
if (Platform.OS === 'android' && !hasKeySupport && !isNaN(parseInt(key))) dispatch({
type: 'setHasKeySupport',
payload: true
});
};
const focusInput = useCallback(index => {
if (index >= 0 && index < numberOfInputs) {
var _input$current2;
const input = inputs.current[index];
input === null || input === void 0 ? void 0 : (_input$current2 = input.current) === null || _input$current2 === void 0 ? void 0 : _input$current2.focus();
}
}, [numberOfInputs]);
const handleClearInput = useCallback(inputIndex => {
var _input$current3;
const input = inputs.current[inputIndex];
input === null || input === void 0 ? void 0 : (_input$current3 = input.current) === null || _input$current3 === void 0 ? void 0 : _input$current3.clear();
dispatch({
type: 'setOtpTextForIndex',
payload: {
index: inputIndex,
text: ''
}
});
focusInput(inputIndex - 1);
}, [focusInput]);
const fillInputs = useCallback(code => {
dispatch({
type: 'setOtpCode',
payload: {
numberOfInputs,
code
}
});
}, [numberOfInputs]);
const listenOnCopiedText = useCallback(async () => {
const copiedText = await Clipboard.getString();
const otpCodeValue = Object.values(otpCode).join('');
if (copiedText && copiedText.length === numberOfInputs && copiedText !== otpCodeValue && copiedText !== previousCopiedText.current) {
previousCopiedText.current = copiedText;
fillInputs(copiedText);
}
}, [fillInputs, numberOfInputs, otpCode]);
useEffect(() => {
let interval;
if (autofillFromClipboard) {
interval = setInterval(() => {
listenOnCopiedText();
}, autofillListenerIntervalMS);
}
return () => {
clearInterval(interval);
};
}, [autofillFromClipboard, autofillListenerIntervalMS, listenOnCopiedText, numberOfInputs]);
const renderInputs = () => {
const iterationArray = Array(numberOfInputs).fill(0);
return iterationArray.map((_, index) => {
let inputIndex = index;
if (isRTL) {
inputIndex = numberOfInputs - 1 - index;
}
const inputValue = otpCode[`${inputIndex}`];
if (!inputs.current[inputIndex]) {
inputs.current[inputIndex] = /*#__PURE__*/React.createRef();
}
return /*#__PURE__*/React.createElement(OtpInput, _extends({
autoFocus: autoFocus && index === 0,
autoCapitalize: autoCapitalize,
clearTextOnFocus: clearTextOnFocus,
firstInput: index === 0,
focusStyles: focusStyles,
handleKeyPress: keyPressEvent => handleKeyPress(keyPressEvent, inputIndex),
handleTextChange: text => handleTextChange(text, inputIndex),
inputContainerStyles: inputContainerStyles,
inputStyles: inputStyles,
inputValue: inputValue,
key: inputIndex,
keyboardType: keyboardType,
maxLength: Platform.select({
android: 1,
ios: index === 0 ? numberOfInputs : 1
}),
numberOfInputs: numberOfInputs,
placeholder: placeholder,
ref: inputs.current[inputIndex],
secureTextEntry: secureTextEntry,
selectTextOnFocus: selectTextOnFocus,
accessible: true,
testID: `${testIDPrefix}-${inputIndex}`,
accessibilityLabel: `${testIDPrefix}-${inputIndex}`
}, restProps));
});
};
return /*#__PURE__*/React.createElement(View, {
style: style || styles.container
}, renderInputs());
});
export { OtpInputsRef };
export default OtpInputs;
//# sourceMappingURL=index.js.map