import React, { forwardRef, useRef, useState, useMemo } from 'react'; import { KeyboardTypeOptions, TextInput, TextInputIOSProps, View, Pressable, StyleSheet, TextStyle, } from 'react-native'; import { Clear } from '@pingtou/rn-vant-icons'; import toString from 'lodash-es/toString'; import isFunction from 'lodash-es/isFunction'; import isObject from 'lodash-es/isObject'; import { useControllableValue, useMemoizedFn } from '../hooks'; import { formatNumber } from '../utils/number'; import { cloneReactNode } from '../utils/cloneReactNode'; import { useThemeFactory } from '../Theme'; import { createInputStyle } from './style'; import type { InputProps, TextAreaProps, InputInstance, InputFousEvent, InputChangeEvent, InputFormatTrigger, ContentSizeChangeEvent, KeyPressEvent, } from './type'; interface BaseInputProps extends InputProps, TextAreaProps {} const BaseInput = forwardRef((props, ref) => { const { type = 'text', formatter, clearIcon = , clearTrigger = 'focus', formatTrigger = 'onChange', rows = 1, autoSize, } = props; const { styles, theme } = useThemeFactory(createInputStyle); const [value, setValue] = useControllableValue(props); const [inputFocus, setInputFocus] = useState(false); // 单行的高度 const singleRowHeight = theme.cell_line_height ?? 24; // input 高度 const [inputHeight, setInputHeight] = useState(); const inputRef = useRef(null); const contentChanged = React.useRef(false); React.useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus(), blur: () => inputRef.current?.blur(), clear: () => inputRef.current?.clear(), })); // 是否显示清楚按钮 const showClear = useMemo(() => { if (props.clearable && !props?.readOnly) { const hasValue = value !== '' && value !== undefined; const trigger = clearTrigger === 'always' || (clearTrigger === 'focus' && inputFocus); return hasValue && trigger; } return false; }, [value, clearTrigger, inputFocus]); const formatValue = useMemoizedFn((inputValue: string | number, trigger = 'onChange') => { if (isFunction(formatter) && trigger === formatTrigger) { return formatter(inputValue); } return inputValue; }); const handleFocus = useMemoizedFn((e: InputFousEvent) => { setInputFocus(true); props.onFocus?.(e); }); const handleBulr = useMemoizedFn((e: InputFousEvent) => { setInputFocus(false); handleChange(toString(value), 'onBlur'); props.onBlur?.(e); }); const handleKeyPress = useMemoizedFn((e: KeyPressEvent) => { props.onKeyPress?.(e); }); const handleChange = useMemoizedFn((text: string, trigger?: InputFormatTrigger) => { contentChanged.current = true; let finalValue: string | number = text; if (type === 'number' || type === 'digit') { const isNumber = type === 'number'; finalValue = formatNumber(finalValue, isNumber, isNumber); } finalValue = formatValue(finalValue, trigger); setValue(finalValue); }); const onChange = useMemoizedFn(({ nativeEvent }: InputChangeEvent) => { handleChange(nativeEvent.text, 'onChange'); }); const handleContentSizeChange = useMemoizedFn((event: ContentSizeChangeEvent) => { if (!contentChanged.current) return; let _height: number; if (autoSize) { const { minHeight = singleRowHeight, maxHeight = event.nativeEvent.contentSize.height } = isObject(autoSize) ? autoSize : {}; _height = Math.max(minHeight, maxHeight); } else if (rows > 1) { _height = rows * singleRowHeight; } else { _height = singleRowHeight; } setInputHeight(_height); }); const getHeight = useMemoizedFn(() => { if (inputHeight) return inputHeight; return rows * singleRowHeight; }); const getTextContentType = (): TextInputIOSProps['textContentType'] => { if (type === 'tel') return 'telephoneNumber'; if (type === 'password') return 'password'; return undefined; }; const getKeyboardType = (): KeyboardTypeOptions | undefined => { if (type === 'digit' || type === 'number') return 'numeric'; if (type === 'tel') return 'phone-pad'; return undefined; }; const renderInput = useMemoizedFn(() => { const inputStyles = StyleSheet.flatten([ styles.control, !!props.disabled && styles.disabledControl, !!props.error && styles.errorControl, ]); const placeholderTextColor = props.error ? inputStyles.color : theme.field_placeholder_text_color; return ( 1} textAlign={props.align} textContentType={getTextContentType()} keyboardType={getKeyboardType()} style={[inputStyles, { height: getHeight() }]} onBlur={handleBulr} onFocus={handleFocus} onKeyPress={handleKeyPress} onContentSizeChange={handleContentSizeChange} secureTextEntry={type === 'password'} /> ); }); const renderClearIcon = () => { if (showClear) { return ( inputRef.current?.clear()} style={{ marginLeft: theme.padding_xs }} > {cloneReactNode(clearIcon, { size: theme.field_clear_icon_size, color: theme.field_clear_icon_color, })} ); } return null; }; return ( {renderInput()} {renderClearIcon()} ); }); export default BaseInput;