import * as React from 'react'; import { KeyboardTypeOptions, Platform, StyleSheet, Text, TextStyle, View, ViewStyle, } from 'react-native'; import base from '../../_styles/themes/default.basic'; import { inputItem } from '../../_styles/themes/default.components'; import { formatValueByGapRule, formatValueByGapStep, trimValue, } from '../../_utils/format'; import MDTextInput, { IMDtextInputProps } from '../text-input'; import MDMaterialTextInput, { MDMaterialTextInputStyles, } from './material-text-input'; type MDInputItemType = | 'text' | 'bank-card' | 'password' | 'phone' | 'money' | 'digit'; type MDInputItemAlign = 'left' | 'center' | 'right'; type MDInputItemSize = 'large' | 'normal'; export interface IMDInputItemProps { styles?: IMDInputItemStyles; name?: string; // 表单名称 区分表单组件 title?: string; // 表单左侧标题 可用left代替 brief?: React.ReactNode; error?: React.ReactNode; left?: React.ReactNode; right?: React.ReactNode; value?: string; placeholder?: string; maxLength?: number; type?: MDInputItemType | KeyboardTypeOptions; size?: MDInputItemSize; align?: MDInputItemAlign; amount?: boolean; // 是否使用金融字体 readonly?: boolean; disabled?: boolean; highlight?: boolean; material?: boolean; clearable?: boolean; formative?: boolean; // 根据type自动格式化 numberKeyboard?: boolean; // 是否使用金融数字键盘 keyboardShuffle?: boolean; // 金融键盘乱序 keyboardOKText?: string; // 金融数字键盘确认键文案 onChangeText?: (name: string | undefined, text: string) => void; onFocus?: (name?: string) => void; onBlur?: (name?: string) => void; onConfirm?: (name: string | undefined, value: string) => void; } interface IMDInputItemState { value: string; oldValue: string; currentValue: string; highlight: boolean; } interface IMDInputItemStyles { container?: ViewStyle; wrapper?: ViewStyle; inputText?: TextStyle; titleText?: TextStyle; errorText?: TextStyle; briefText?: TextStyle; } export const MDInputItemStyles: IMDInputItemStyles = { container: { display: 'flex', flexDirection: 'row', alignItems: 'center', borderBottomColor: base.colors.borderBase, borderBottomWidth: 1, height: inputItem.height, }, wrapper: {}, inputText: { flex: 1, color: inputItem.color, fontSize: inputItem.fontSize, }, titleText: { color: inputItem.color, fontSize: inputItem.fontSize, }, errorText: { color: inputItem.colorError, fontSize: inputItem.fontSizeError, marginVertical: 10, }, briefText: { color: inputItem.colorBrief, fontSize: inputItem.fontSizeBrief, marginVertical: 10, }, }; const styles = StyleSheet.create(MDInputItemStyles); const materialInputStyles = Object.assign({}, MDMaterialTextInputStyles, { wrapper: { ...MDMaterialTextInputStyles.wrapper, width: '100%', paddingLeft: 0, }, input: { ...MDMaterialTextInputStyles.input, color: inputItem.color, fontSize: inputItem.fontSize, textAlign: 'left', } }); export default class MDInputItem extends React.Component< IMDInputItemProps, IMDInputItemState > { public static defaultProps = { styles, type: 'text', size: 'normal', align: 'left', amount: false, readonly: false, disabled: false, highlight: false, material: false, clearable: false, numberKeyboard: false, }; constructor (props: IMDInputItemProps) { super(props); const { value } = this.props; this.state = { currentValue: value || '', oldValue: value || '', value: value || '', highlight: false, }; } private inputRef: MDTextInput | MDMaterialTextInput | null = null; public render () { const { title, highlight, material, error, left, right, size, styles: _styles, } = this.props; const highlighted = highlight && this.state.highlight; const borderBottomColor = error ? inputItem.colorError : highlighted ? base.colors.textBase : base.colors.borderBase; const brief = this.renderBrief(); const content = this.renderContent(); return ( {title ? ( {title} ) : null} {left} {content} {right} {brief} ); } public focus () { this.inputRef && this.inputRef.focus(); } public blur () { this.inputRef && this.inputRef.blur(); } public getValue () { return this.state.value; } private renderBrief () { const { styles: _styles, error, brief } = this.props; const errorNode = typeof error === 'string' ? ( error ) : ( error ); const briefNode = typeof brief === 'string' ? ( brief ) : ( brief ); return error || brief ? ( {error ? errorNode : briefNode} ) : null; } private renderContent () { const { styles: _styles, title, disabled, readonly, value } = this.props; return title && (disabled || readonly) ? ( {value} ) : ( this.renderInput() ); } private renderInput () { const { material, name, type, title, align, left, right, placeholder, clearable, size, onFocus, onBlur, onConfirm, amount, numberKeyboard, keyboardShuffle, keyboardOKText, } = this.props; const inputStyle: TextStyle = { paddingLeft: title ? 20 : 0, textAlign: align, marginLeft: left ? 10 : 0, marginRight: right ? 10 : 0, // fontFamily: amount ? 'FD+_Number' : undefined, // fontSize: size === 'large' ? inputItem.fontSizeLarge : undefined, }; if(!!amount){ inputStyle.fontFamily = 'FD+_Number'; } if(size === 'large') { inputStyle.fontSize = inputItem.fontSizeLarge; } const keyboardType = this.keyboardTypeByType(type!); let maxLength = this.props.maxLength; if (type === 'phone') { maxLength = 13; } const inputProps: IMDtextInputProps = { secureTextEntry: type === 'password', maxLength, placeholder, underlineColorAndroid: 'transparent', clearButtonMode: clearable ? 'while-editing' : 'never', type: numberKeyboard ? 'professional' : 'system', okText: keyboardOKText, shuffle: keyboardShuffle, keyboardType, placeholderTextColor: inputItem.placeholder, onFocus: () => { this.setState({ highlight: true }); onFocus && onFocus(name); }, onBlur: () => { this.setState({ highlight: false }); onBlur && onBlur(name); }, onChangeText: (text) => { this.handleChangeText(name, text); }, onSubmitEditing: () => { onConfirm && onConfirm(name, this.state.value); }, value: this.state.value, }; return material ? this.renderMaterialInput(inputProps, inputStyle) : this.renderNormalInput(inputProps, inputStyle); } private renderMaterialInput ( inputProps: IMDtextInputProps, inputStyle: TextStyle ) { return ( (this.inputRef = ref)} /> ); } private renderNormalInput ( inputProps: IMDtextInputProps, inputStyle: TextStyle ) { const _styles = this.props.styles; const sty = Object.assign({}, _styles!.inputText, { ...inputStyle }); return ( (this.inputRef = ref)} /> ); } private keyboardTypeByType ( type: MDInputItemType | KeyboardTypeOptions ): KeyboardTypeOptions { switch (type) { case 'bank-card': case 'phone': case 'digit': case 'money': case 'text': case 'password': return 'default'; default: return type; } } private handleChangeText (name: string | undefined, text: string) { const { onChangeText, formative, type } = this.props; const oldValue = this.state.currentValue; // 未设置formative的情况下, type为bankCard,phone, money默认为true const needFormat = formative || (formative === undefined && (type === 'bank-card' || type === 'phone' || type === 'money')); const formatValue = needFormat ? this.formatValue(text, text.length) : { value: text, range: text.length }; this.setState({ value: formatValue.value, currentValue: formatValue.value, oldValue, }); const original = trimValue(trimValue(text, ' '), ','); onChangeText && onChangeText(name, original); } private inputMaxLength () { if (this.props.type === 'phone') { return 11; } else { return this.props.maxLength; } } private subValue (value: string) { const length = this.inputMaxLength(); if (length && value.length > length) { return value.substring(0, length); } return value; } private formatValue (value: string, position = 0) { const { type } = this.props; let formatValue = { value, range: position }; if (value === '') { return formatValue; } let gap = ' '; let curValue = value; const oldValue = this.state.oldValue; const isAdd = oldValue.length > curValue.length ? -1 : 1; switch (type) { case 'bank-card': curValue = this.subValue(trimValue(curValue.replace(/\D/g, ''))); formatValue = { value: curValue.replace(/(\d{4})(?=\d)/g, '$1 '), range: position, }; break; case 'phone': curValue = this.subValue(trimValue(curValue.replace(/\D/g, ''))); formatValue = { value: curValue.replace(/(^\d{3}|\d{4}\B)/g, '$1 '), range: position, }; break; case 'money': gap = ','; curValue = this.subValue(trimValue(curValue.replace(/[^\d.]/g, ''))); // curValue = curValue.replace(/\D/g, '') const dotPos = curValue.indexOf('.'); // format if no dot or new add dot or insert befor dot const moneyCurValue = curValue.split('.')[0]; const moneyCurDecimal = ~dotPos ? `.${curValue.split('.')[1]}` : ''; formatValue = formatValueByGapStep( 3, trimValue(moneyCurValue, gap), gap, 'right', position, isAdd, oldValue.split('.')[0] ); formatValue.value += moneyCurDecimal; break; case 'digit': curValue = this.subValue(trimValue(curValue.replace(/\D/g, ''))); formatValue.value = curValue; break; default: curValue = this.subValue(curValue); formatValue.value = curValue; break; } return formatValue; } }