From 54e5fdf1be68a14c4d386bff19958ae799ed12af Mon Sep 17 00:00:00 2001 From: ZQDesigned <2990918167@qq.com> Date: Thu, 11 Jun 2026 01:49:35 +0800 Subject: [PATCH 1/2] fix: use committed count value during IME composition --- src/Input.tsx | 15 ++++++++++++++- src/TextArea.tsx | 15 ++++++++++++++- tests/TextArea.count.test.tsx | 22 ++++++++++++++++++++++ tests/count.test.tsx | 22 ++++++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/Input.tsx b/src/Input.tsx index 1e5fa14..4aab869 100644 --- a/src/Input.tsx +++ b/src/Input.tsx @@ -60,11 +60,14 @@ const Input = forwardRef((props, ref) => { props.defaultValue, props.value, ); + // Count-related state should reflect the committed value during IME composition + // instead of the intermediate phonetic text. + const [countValue, setCountValue] = useState(formatValue); const countConfig = useCount(count, showCount); const { isOutOfRange, dataCount } = useCountDisplay({ countConfig, - value: formatValue, + value: countValue, maxLength, }); const getExceedValue = useCountExceed({ @@ -99,6 +102,12 @@ const Input = forwardRef((props, ref) => { setFocused((prev) => (prev && disabled ? false : prev)); }, [disabled]); + useEffect(() => { + if (!compositionRef.current) { + setCountValue(formatValue); + } + }, [formatValue]); + const triggerChange = ( e: | React.ChangeEvent @@ -114,6 +123,9 @@ const Input = forwardRef((props, ref) => { return; } setValue(cutValue); + if (!compositionRef.current) { + setCountValue(cutValue); + } if (inputRef.current) { resolveOnChange(inputRef.current, e, onChange, cutValue); @@ -227,6 +239,7 @@ const Input = forwardRef((props, ref) => { type={type} onCompositionStart={(e) => { compositionRef.current = true; + setCountValue(formatValue); onCompositionStart?.(e); }} onCompositionEnd={onInternalCompositionEnd} diff --git a/src/TextArea.tsx b/src/TextArea.tsx index 8529814..10f4cdb 100644 --- a/src/TextArea.tsx +++ b/src/TextArea.tsx @@ -60,10 +60,13 @@ const TextArea = React.forwardRef( const getTextArea = () => resizableTextAreaRef.current?.textArea || null; const { setValue, formatValue } = useMergedValue(defaultValue, customValue); + // Count-related state should reflect the committed value during IME composition + // instead of the intermediate phonetic text. + const [countValue, setCountValue] = React.useState(formatValue); const countConfig = useCount(count, showCount); const { isOutOfRange, dataCount } = useCountDisplay({ countConfig, - value: formatValue, + value: countValue, maxLength, }); const getExceedValue = useCountExceed({ @@ -88,6 +91,12 @@ const TextArea = React.forwardRef( setFocused((prev) => !disabled && prev); }, [disabled]); + useEffect(() => { + if (!compositionRef.current) { + setCountValue(formatValue); + } + }, [formatValue]); + // ============================== Count =============================== // ============================== Change ============================== const triggerChange = ( @@ -98,6 +107,9 @@ const TextArea = React.forwardRef( ) => { const cutValue = getExceedValue(currentValue, compositionRef.current); setValue(cutValue); + if (!compositionRef.current) { + setCountValue(cutValue); + } resolveOnChange(e.currentTarget, e, onChange, cutValue); }; @@ -107,6 +119,7 @@ const TextArea = React.forwardRef( HTMLTextAreaElement > = (e) => { compositionRef.current = true; + setCountValue(formatValue); onCompositionStart?.(e); }; diff --git a/tests/TextArea.count.test.tsx b/tests/TextArea.count.test.tsx index f58def5..20cae9a 100644 --- a/tests/TextArea.count.test.tsx +++ b/tests/TextArea.count.test.tsx @@ -129,6 +129,28 @@ describe('TextArea.Count', () => { expect(container.querySelector('.rc-textarea-out-of-range')).toBeTruthy(); }); + it('should base showCount on committed value during IME composition', () => { + const { container } = render( +