Skip to content

fix(Teaxarea): fix value calc error on iOS devices #601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 22 additions & 19 deletions src/textarea/Textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,29 @@ const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originProps, r
label,
indicator,
readonly,
onCompositionstart,
onCompositionend,
...otherProps
} = props;

const textareaClass = usePrefixClass('textarea');

const [value, setValue] = useDefault(props.value, defaultValue, props.onChange);
const [textareaStyle, setTextareaStyle] = useState({});
const [composingValue, setComposingValue] = useState<string>('');
const composingRef = useRef(false);
const textareaRef: React.RefObject<HTMLTextAreaElement> = useRef();
const wrapperRef: React.RefObject<HTMLDivElement> = useRef();

const textareaLength = useMemo(() => {
const realValue = composingRef.current ? composingValue : (value ?? '');
const realValue = value ?? '';
if (typeof maxcharacter !== 'undefined') {
const { length = 0 } = getCharacterLength(String(realValue), maxcharacter) as {
length: number;
};
return length;
}
return String(realValue).length || 0;
}, [value, maxcharacter, composingRef, composingValue]);
}, [value, maxcharacter]);

const textareaPropsNames = Object.keys(otherProps).filter((key) => !/^on[A-Z]/.test(key));
const textareaProps = textareaPropsNames.reduce(
Expand Down Expand Up @@ -112,32 +113,34 @@ const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originProps, r

if (value === newStr) return; // 避免在Firefox中重复触发

if (composingRef.current) {
setComposingValue(newStr);
} else {
if (!allowInputOverMax) {
newStr = limitUnicodeMaxLength(newStr, maxlength);
if (maxcharacter && maxcharacter >= 0) {
const stringInfo = getCharacterLength(newStr, maxcharacter);
newStr = typeof stringInfo === 'object' && stringInfo.characters;
}
if (!allowInputOverMax && !composingRef.current) {
newStr = limitUnicodeMaxLength(newStr, maxlength);
if (maxcharacter && maxcharacter >= 0) {
const stringInfo = getCharacterLength(newStr, maxcharacter);
newStr = typeof stringInfo === 'object' && stringInfo.characters;
}

// 中文输入结束,同步 composingValue
setComposingValue(newStr);
setValue(newStr, { e });
}

setValue(newStr, { e });
};

const handleCompositionStart = () => {
const handleCompositionStart = (e: React.CompositionEvent<HTMLTextAreaElement>) => {
composingRef.current = true;
const {
currentTarget: { value },
} = e;
onCompositionstart?.(value, { e });
};

const handleCompositionEnd = (e) => {
const handleCompositionEnd = (e: React.CompositionEvent<HTMLTextAreaElement>) => {
if (composingRef.current) {
composingRef.current = false;
inputValueChangeHandle(e);
}
const {
currentTarget: { value },
} = e;
onCompositionend?.(value, { e });
};

useEffect(() => {
Expand Down Expand Up @@ -168,7 +171,7 @@ const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originProps, r
{...eventProps}
className={textareaInnerClasses}
style={textareaStyle}
value={composingRef.current ? composingValue : value}
value={value}
readOnly={readonly}
autoFocus={autofocus}
disabled={disabled}
Expand Down
2 changes: 1 addition & 1 deletion src/textarea/defaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export const textareaDefaultProps: TdTextareaProps = {
indicator: false,
layout: 'horizontal',
placeholder: undefined,
readonly: false,
readonly: undefined,
};
4 changes: 3 additions & 1 deletion src/textarea/textarea.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ maxcharacter | Number | - | \- | N
maxlength | Number | - | \- | N
name | String | - | \- | N
placeholder | String | undefined | \- | N
readonly | Boolean | false | \- | N
readonly | Boolean | undefined | \- | N
value | String / Number | - | Typescript:`TextareaValue` `type TextareaValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/textarea/type.ts) | N
defaultValue | String / Number | - | uncontrolled property。Typescript:`TextareaValue` `type TextareaValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/textarea/type.ts) | N
onBlur | Function | | Typescript:`(value: TextareaValue, context: { e: FocusEvent }) => void`<br/> | N
onChange | Function | | Typescript:`(value: TextareaValue, context?: { e?: InputEvent }) => void`<br/> | N
onCompositionend | Function | | Typescript:`(value: string, context: { e: CompositionEvent }) => void`<br/>trigger on compositionend | N
onCompositionstart | Function | | Typescript:`(value: string, context: { e: CompositionEvent }) => void`<br/>trigger on compositionstart | N
onFocus | Function | | Typescript:`(value: TextareaValue, context : { e: FocusEvent }) => void`<br/> | N
4 changes: 3 additions & 1 deletion src/textarea/textarea.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ maxcharacter | Number | - | 用户最多可以输入的字符个数,一个中
maxlength | Number | - | 用户最多可以输入的字符个数 | N
name | String | - | 名称,HTML 元素原生属性 | N
placeholder | String | undefined | 占位符 | N
readonly | Boolean | false | 只读状态 | N
readonly | Boolean | undefined | 只读状态 | N
value | String / Number | - | 文本框值。TS 类型:`TextareaValue` `type TextareaValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/textarea/type.ts) | N
defaultValue | String / Number | - | 文本框值。非受控属性。TS 类型:`TextareaValue` `type TextareaValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/textarea/type.ts) | N
onBlur | Function | | TS 类型:`(value: TextareaValue, context: { e: FocusEvent }) => void`<br/>失去焦点时触发 | N
onChange | Function | | TS 类型:`(value: TextareaValue, context?: { e?: InputEvent }) => void`<br/>输入内容变化时触发 | N
onCompositionend | Function | | TS 类型:`(value: string, context: { e: CompositionEvent }) => void`<br/>中文输入结束时触发 | N
onCompositionstart | Function | | TS 类型:`(value: string, context: { e: CompositionEvent }) => void`<br/>中文输入开始时触发 | N
onFocus | Function | | TS 类型:`(value: TextareaValue, context : { e: FocusEvent }) => void`<br/>获得焦点时触发 | N
11 changes: 9 additions & 2 deletions src/textarea/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* */

import { TNode } from '../common';
import { FocusEvent, FormEvent } from 'react';
import { FocusEvent, FormEvent, CompositionEvent } from 'react';

export interface TdTextareaProps {
/**
Expand Down Expand Up @@ -65,7 +65,6 @@ export interface TdTextareaProps {
placeholder?: string;
/**
* 只读状态
* @default false
*/
readonly?: boolean;
/**
Expand All @@ -84,6 +83,14 @@ export interface TdTextareaProps {
* 输入内容变化时触发
*/
onChange?: (value: TextareaValue, context?: { e?: FormEvent<HTMLTextAreaElement> }) => void;
/**
* 中文输入结束时触发
*/
onCompositionend?: (value: string, context: { e: CompositionEvent<HTMLTextAreaElement> }) => void;
/**
* 中文输入开始时触发
*/
onCompositionstart?: (value: string, context: { e: CompositionEvent<HTMLTextAreaElement> }) => void;
/**
* 获得焦点时触发
*/
Expand Down