@pakenfit/react-native-pin-input
Version:
Phone Pin Input for React Native
154 lines • 5.52 kB
JavaScript
function _extends() { _extends = Object.assign ? Object.assign.bind() : 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 React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
import { StyleSheet } from 'react-native';
import { View, Keyboard } from 'react-native';
import { Input } from './Input';
import * as Clipboard from 'expo-clipboard';
import { IS_IOS } from '../constants';
export const PinInput = /*#__PURE__*/forwardRef((_ref, ref) => {
let {
length = 4,
inputProps,
inputStyle,
containerProps,
containerStyle,
onFillEnded,
autoFocus = true
} = _ref;
const pins = Array.from({
length
}).map((_, i) => i);
const inputRefs = useRef([]);
const pinsValues = useRef([]);
const iosOTP = useRef({
key: '',
index: null
});
const [keyPressed, setKeyPressed] = useState(false);
const handleOTP = useCallback(otp => {
const regexp = new RegExp(`[0-9]{${length}}`);
const otps = otp.match(regexp);
if (otps !== null && otps !== void 0 && otps.length) {
const otpSplits = otp.split('');
otpSplits.forEach((otpSplit, i) => {
var _inputRefs$current$i;
return inputRefs === null || inputRefs === void 0 || (_inputRefs$current$i = inputRefs.current[i]) === null || _inputRefs$current$i === void 0 ? void 0 : _inputRefs$current$i.setNativeProps({
text: otpSplit
});
});
onFillEnded === null || onFillEnded === void 0 || onFillEnded(otp);
iosOTP.current = {
key: '',
index: null
};
Keyboard.dismiss();
return true;
}
return false;
}, [length, onFillEnded]);
const handleChangeText = useCallback(async (text, index) => {
const copiedText = await Clipboard.getStringAsync();
if (copiedText.includes(text) && !keyPressed) {
const otpHandled = handleOTP(copiedText);
if (otpHandled) {
return;
}
}
pinsValues.current[index] = text;
if (index + 1 <= pins.length - 1) {
var _inputRefs$current;
inputRefs === null || inputRefs === void 0 || (_inputRefs$current = inputRefs.current[index + 1]) === null || _inputRefs$current === void 0 || _inputRefs$current.focus();
} else {
onFillEnded === null || onFillEnded === void 0 || onFillEnded(pinsValues.current.join(''));
setKeyPressed(false);
Keyboard.dismiss();
}
}, [handleOTP, keyPressed, onFillEnded, pins.length]);
const onKeyPress = useCallback((event, index) => {
event.persist();
setKeyPressed(true);
if (IS_IOS && Number.isInteger(Number(event.nativeEvent.key))) {
if (iosOTP.current.index === null) {
iosOTP.current = {
key: event.nativeEvent.key,
index
};
} else {
if (iosOTP.current.index === index) {
iosOTP.current = {
key: `${iosOTP.current.key}${event.nativeEvent.key}`,
index
};
} else {
iosOTP.current = {
key: '',
index: null
};
}
}
if (iosOTP.current.key.length === length) {
handleOTP(iosOTP.current.key);
return;
}
}
if (event.nativeEvent.key === 'Backspace') {
// Clear only the current digit if it has value
if (pinsValues.current[index]) {
pinsValues.current[index] = '';
// Don't move focus - stay on current field
// We only reset partial state, not the entire PIN
} else {
// Only move to previous field when current field is empty
if (index - 1 >= 0) {
var _inputRefs$current2;
inputRefs === null || inputRefs === void 0 || (_inputRefs$current2 = inputRefs.current[index - 1]) === null || _inputRefs$current2 === void 0 || _inputRefs$current2.focus();
}
}
setKeyPressed(false);
iosOTP.current = {
key: '',
index: null
};
}
}, [handleOTP, length]);
const clear = useCallback(() => {
var _inputRefs$current$;
pinsValues.current = [];
inputRefs.current.forEach(input => {
input === null || input === void 0 || input.setNativeProps({
text: '',
placeholder: '0'
});
});
(_inputRefs$current$ = inputRefs.current[0]) === null || _inputRefs$current$ === void 0 || _inputRefs$current$.focus();
}, []);
useImperativeHandle(ref, () => ({
clear
}), [clear]);
return /*#__PURE__*/React.createElement(View, _extends({
style: [styles.container, containerStyle]
}, containerProps), pins.map(pin => {
return /*#__PURE__*/React.createElement(Input, _extends({}, inputProps, {
autoFocus: autoFocus && pin === 0,
ref: input => inputRefs === null || inputRefs === void 0 ? void 0 : inputRefs.current.push(input),
key: pin,
style: inputStyle,
onChangeText: text => handleChangeText(text, pin),
onKeyPress: event => onKeyPress(event, pin),
autoComplete: "sms-otp",
textContentType: "oneTimeCode",
keyboardType: "numeric"
}));
}));
});
PinInput.displayName = 'PinInput';
const styles = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
gap: 5,
alignItems: 'center',
justifyContent: 'center'
}
});
//# sourceMappingURL=PinInput.js.map