diff --git a/.changeset/odd-eyes-fry.md b/.changeset/odd-eyes-fry.md new file mode 100644 index 0000000000..260a16a490 --- /dev/null +++ b/.changeset/odd-eyes-fry.md @@ -0,0 +1,5 @@ +--- +'@td-design/react-native': patch +--- + +修复Toast和Passcode组件的bug diff --git a/packages/react-native-picker/src/picker/components/Normal/index.tsx b/packages/react-native-picker/src/picker/components/Normal/index.tsx index f90522f479..e2dd6f0e0f 100644 --- a/packages/react-native-picker/src/picker/components/Normal/index.tsx +++ b/packages/react-native-picker/src/picker/components/Normal/index.tsx @@ -13,6 +13,7 @@ function NormalPicker(props: ImperativeModalChildrenProps(props: ImperativeModalChildrenProps 0 ? data[0].value : undefined; - - const { selectedValue, handleOk, handleChange, handleClose } = useNormalPicker({ + const { selectedValue, handleOk, handleChange, handleClose, options } = useNormalPicker({ + data, + request, value, - initialValue, onChange, closeModal, }); - if (data.length === 0) return null; + if (options.length === 0) return null; return ( <> @@ -61,7 +59,7 @@ function NormalPicker(props: ImperativeModalChildrenProps - + ); diff --git a/packages/react-native-picker/src/picker/components/Normal/useNormalPicker.ts b/packages/react-native-picker/src/picker/components/Normal/useNormalPicker.ts index dee169a61c..746e1226e4 100644 --- a/packages/react-native-picker/src/picker/components/Normal/useNormalPicker.ts +++ b/packages/react-native-picker/src/picker/components/Normal/useNormalPicker.ts @@ -6,10 +6,11 @@ import { NormalPickerProps } from '../../type'; export default function useNormalPicker({ value, - initialValue, onChange, closeModal, -}: ImperativeModalChildrenProps, 'data'> & { initialValue?: T }>) { +}: ImperativeModalChildrenProps, 'data'>>) { + const initialValue = data.length > 0 ? data[0].value : undefined; + const [selectedValue, selectValue] = useSafeState(value || initialValue); const handleChange = (val: PickerData) => { diff --git a/packages/react-native-picker/src/picker/type.ts b/packages/react-native-picker/src/picker/type.ts index c301d9d2b7..85785a50ae 100644 --- a/packages/react-native-picker/src/picker/type.ts +++ b/packages/react-native-picker/src/picker/type.ts @@ -2,13 +2,21 @@ import { CascadePickerItemProps, WheelPickerPropsBase } from '../components/Whee export interface PickerProps extends WheelPickerPropsBase { /** 选择项列表 */ - data: CascadePickerItemProps[]; + data?: CascadePickerItemProps[]; + /** 请求数据 */ + request?: () => Promise[]>; /** 是否级联 */ cascade?: boolean; /** 展示几列 */ cols?: number; value?: T[] | T; onChange?: (value?: T extends (infer U)[] ? U[] : T) => void; + /** 字段名 */ + fieldNames?: { + label?: string; + value?: string; + children?: string; + }; } /** 弹窗Picker的属性 */ diff --git a/packages/react-native/src/passcode/PasscodeItem.tsx b/packages/react-native/src/passcode/PasscodeItem.tsx index 7380963248..1bbe8547c6 100644 --- a/packages/react-native/src/passcode/PasscodeItem.tsx +++ b/packages/react-native/src/passcode/PasscodeItem.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, memo, RefObject, useEffect } from 'react'; +import React, { forwardRef, memo } from 'react'; import { Platform, StyleSheet, TextInput } from 'react-native'; import { useTheme } from '@shopify/restyle'; @@ -18,9 +18,9 @@ const PasscodeItem = forwardRef( inputContainerStyle, focusStyle, inputStyle, - handleTextChange, inputValue, handleKeyPress, + handleChangeText, selectTextOnFocus, ...rest }, @@ -29,13 +29,6 @@ const PasscodeItem = forwardRef( const theme = useTheme(); const [focused, { setTrue, setFalse }] = useBoolean(false); - useEffect(() => { - (ref as RefObject)?.current?.setNativeProps({ - value: inputValue, - text: inputValue, - }); - }, [inputValue]); - const styles = StyleSheet.create({ input: { textAlign: 'center', @@ -61,8 +54,8 @@ const PasscodeItem = forwardRef( autoComplete="off" onBlur={setFalse} onFocus={setTrue} - onChangeText={handleTextChange} - onKeyPress={handleKeyPress} + onKeyPress={handleKeyPress} // 键盘按下事件, 只用来处理键盘按下删除键的场景 + onChangeText={handleChangeText} // 输入框内容改变事件, 用来处理输入框内容改变的场景 selectionColor={theme.colors.primary200} style={[styles.input, inputStyle]} textContentType={isOTPSupported ? 'oneTimeCode' : 'none'} @@ -73,6 +66,7 @@ const PasscodeItem = forwardRef( ios: selectTextOnFocus, android: true, })} + value={inputValue} {...rest} /> diff --git a/packages/react-native/src/passcode/index.tsx b/packages/react-native/src/passcode/index.tsx index fedf14a9ee..e70963b506 100644 --- a/packages/react-native/src/passcode/index.tsx +++ b/packages/react-native/src/passcode/index.tsx @@ -28,7 +28,7 @@ const Passcode = forwardRef( }, ref ) => { - const { otpCode, inputs, handleKeyPress, handleTextChange } = usePasscode({ + const { otpCode, inputs, handleKeyPress, handleChangeText } = usePasscode({ onChange, value, count, @@ -56,7 +56,7 @@ const Passcode = forwardRef( clearTextOnFocus={clearTextOnFocus} focusStyle={focusStyle} handleKeyPress={handleKeyPress(index)} - handleTextChange={handleTextChange(index)} + handleChangeText={handleChangeText(index)} inputContainerStyle={inputContainerStyle} inputStyle={inputStyle} inputValue={inputValue} diff --git a/packages/react-native/src/passcode/reducer.ts b/packages/react-native/src/passcode/reducer.ts index 1d51bce164..9790d4d86b 100644 --- a/packages/react-native/src/passcode/reducer.ts +++ b/packages/react-native/src/passcode/reducer.ts @@ -52,6 +52,7 @@ export default (state: ReducerState, { type, payload }: Actions) => { return { ...state, otpCode, + index: (payload as SetOtpTextForIndexPayload).index, }; } diff --git a/packages/react-native/src/passcode/type.ts b/packages/react-native/src/passcode/type.ts index f74c1388e4..fe353364e1 100644 --- a/packages/react-native/src/passcode/type.ts +++ b/packages/react-native/src/passcode/type.ts @@ -26,7 +26,7 @@ export interface PasscodeProps extends Omit void; + onFinish?: (value: string) => void; } export interface PasscodeRef { @@ -45,10 +45,10 @@ export interface PasscodeItemProps extends TextInputProps { focusStyle?: StyleProp; /** TextInput的样式 */ inputStyle?: StyleProp; - /** 文字改变时的回调事件 */ - handleTextChange: (text: string) => void; /** 输入的值 */ inputValue: string; /** 按键按下的回调事件 */ handleKeyPress: (keyPressEvent: NativeSyntheticEvent) => void; + /** 输入框内容改变时的回调事件 */ + handleChangeText: (text: string) => void; } diff --git a/packages/react-native/src/passcode/usePasscode.ts b/packages/react-native/src/passcode/usePasscode.ts index c788213cb4..5734a3c2db 100644 --- a/packages/react-native/src/passcode/usePasscode.ts +++ b/packages/react-native/src/passcode/usePasscode.ts @@ -1,7 +1,7 @@ import { ForwardedRef, RefObject, useEffect, useImperativeHandle, useReducer, useRef } from 'react'; import { Keyboard, NativeSyntheticEvent, Platform, TextInput, TextInputKeyPressEventData } from 'react-native'; -import { useMemoizedFn, useSafeState } from '@td-design/rn-hooks'; +import { useMemoizedFn } from '@td-design/rn-hooks'; import { fillOtpCode } from './helpers'; import reducer from './reducer'; @@ -19,7 +19,6 @@ export default function usePasscode({ }) { const previousCopiedText = useRef(''); const inputs = useRef>>([]); - const [index, setIndex] = useSafeState(0); const [{ otpCode, hasKeySupport }, dispatch] = useReducer(reducer, { otpCode: fillOtpCode(count, value), @@ -36,13 +35,6 @@ export default function usePasscode({ } }, [value, count]); - const fillInputs = useMemoizedFn((code: string) => { - dispatch({ - type: 'setOtpCode', - payload: { count, code }, - }); - }); - useImperativeHandle(ref, () => ({ reset: () => { dispatch({ type: 'clearOtp', payload: count }); @@ -75,43 +67,33 @@ export default function usePasscode({ text: '', }, }); - focusInput(inputIndex - 1); + }; + + const handleChangeText = (index: number) => (text: string) => { + handleInputTextChange(text, index); }; const handleKeyPress = (index: number) => ({ nativeEvent: { key } }: NativeSyntheticEvent) => { - const text = key === 'Backspace' || key.length > 1 ? '' : key; - handleInputTextChange(text, index); - - if (Platform.OS === 'android' && !hasKeySupport && !isNaN(parseInt(key))) + if (Platform.OS === 'android' && !hasKeySupport && !isNaN(parseInt(key))) { dispatch({ type: 'setHasKeySupport', payload: true }); + } + + if (key === 'Backspace') { + // 当前输入框的值 + const value = otpCode[`${index}`]; + // 清除当前输入框的值 + handleClearInput(index); + // 如果当前输入框的值为空,则聚焦上一个输入框 + if (!value) { + focusInput(index - 1); + } + } }; - const handleTextChange = (index: number) => (text: string) => { - if ( - (Platform.OS === 'android' && !hasKeySupport) || - // Pasted from input accessory - (Platform.OS === 'ios' && text.length > 1) - ) { - handleInputTextChange(text, index); - } - }; - const handleInputTextChange = (text: string, index: number): void => { - setIndex(index); - - if (!text.length) { - handleClearInput(index); - } - - if (text.length > 1) { - handleClearInput(index); - Keyboard.dismiss(); - return fillInputs(text); - } - - if (text) { + if ([1, 2, 3, 4, 5, 6, 7, 8, 9, 0].includes(parseInt(text))) { dispatch({ type: 'setOtpTextForIndex', payload: { @@ -119,21 +101,24 @@ export default function usePasscode({ index, }, }); + focusInput(index + 1); + } else { + handleClearInput(index); } - focusInput(index + 1); }; useEffect(() => { - if (index === count - 1) { - onFinish?.(); + const value = Object.values(otpCode).join(''); + if (value.length === count) { + onFinish?.(value); Keyboard.dismiss(); } - }, [index, count]); + }, [count, otpCode]); return { otpCode, inputs, handleKeyPress: useMemoizedFn(handleKeyPress), - handleTextChange: useMemoizedFn(handleTextChange), + handleChangeText: useMemoizedFn(handleChangeText), }; } diff --git a/packages/react-native/src/toast/useToast.ts b/packages/react-native/src/toast/useToast.ts index 558638d17b..74a219c340 100644 --- a/packages/react-native/src/toast/useToast.ts +++ b/packages/react-native/src/toast/useToast.ts @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import { BackHandler } from 'react-native'; import { useMemoizedFn } from '@td-design/rn-hooks'; @@ -18,8 +19,11 @@ export default function useToast() { clearTimeout(timer.current); }; + /** 每次显示前,先隐藏之前的Toast */ const show = (params: ToastProps) => { - if (visible) return; + if (visible) { + hide(); + } setOptions(params); setVisible(true); @@ -39,6 +43,15 @@ export default function useToast() { return () => clearTimeout(timer.current); }, [visible, options]); + /** 当Toast显示的时候,不允许安卓物理返回键可用 */ + useEffect(() => { + BackHandler.addEventListener('hardwareBackPress', () => visible); + + return () => { + BackHandler.removeEventListener('hardwareBackPress', () => false); + }; + }, [visible]); + return { visible, options,