import * as React from 'react'; import { TextInput, StyleSheet, Pressable, Platform } from 'react-native'; import type { OTPInputProps, OTPInputRef } from './types'; import { useInput } from './use-input'; /** * Default paste transformer that removes all non-numeric characters. */ const defaultPasteTransformer = (maxLength: number) => (pasted: string) => { // match exactly maxLength digits, not preceded or followed by another digit const otpRegex = new RegExp(`(?( ( { style, onChange, maxLength, pattern, placeholder, inputMode = 'numeric', containerStyle, onComplete, render, clearTextOnFocus = true, ...props }, ref ) => { const { inputRef, contextValue, value, handlers, actions } = useInput({ onChange, maxLength, pattern, placeholder, defaultValue: props.defaultValue, pasteTransformer: props.pasteTransformer ?? defaultPasteTransformer(maxLength), onComplete, }); React.useImperativeHandle(ref, () => ({ setValue: (newValue: string) => { handlers.onChangeText(newValue); }, focus: () => { actions.focus(); // for test only we need to call onFocus handlers.onFocus(); }, blur: () => inputRef.current?.blur(), clear: actions.clear, focusSlot: (index: number) => { actions.focusSlot(index); handlers.onFocus(); }, })); const renderedChildren = React.useMemo(() => { if (render) { return render(contextValue); } return null; }, [contextValue, render]); const onPress = React.useCallback(() => { actions.focus(); if (clearTextOnFocus) actions.clear(); }, [actions, clearTextOnFocus]); return ( {renderedChildren} ); } ); OTPInput.displayName = 'OTPInput'; const styles = StyleSheet.create({ container: { position: 'relative', }, input: { ...StyleSheet.absoluteFillObject, /** * On iOS if the input has an opacity of 0, we can't paste text into it. * This is a workaround to allow pasting text into the input. */ ...Platform.select({ ios: { opacity: 0.02, color: 'transparent', }, android: { opacity: 0, }, }), }, });