mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-11 03:22:59 +08:00
fix textarea maxlength issue (#33910)
* fix: textarea maxlength * refactor: delete some useless code * refactor: fix ci * refactor and fix test case fall * update snapshot * add testcase * refactor type defined
This commit is contained in:
parent
9eed463be3
commit
38b4a03c56
@ -17,6 +17,26 @@ function fixEmojiLength(value: string, maxLength: number) {
|
|||||||
return [...(value || '')].slice(0, maxLength).join('');
|
return [...(value || '')].slice(0, maxLength).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setTriggerValue(
|
||||||
|
isCursorInEnd: boolean,
|
||||||
|
preValue: string,
|
||||||
|
triggerValue: string,
|
||||||
|
maxLength: number,
|
||||||
|
) {
|
||||||
|
let newTriggerValue = triggerValue;
|
||||||
|
if (isCursorInEnd) {
|
||||||
|
// 光标在尾部,直接截断
|
||||||
|
newTriggerValue = fixEmojiLength(triggerValue, maxLength!);
|
||||||
|
} else if (
|
||||||
|
[...(preValue || '')].length < triggerValue.length &&
|
||||||
|
[...(triggerValue || '')].length > maxLength!
|
||||||
|
) {
|
||||||
|
// 光标在中间,如果最后的值超过最大值,则采用原先的值
|
||||||
|
newTriggerValue = preValue;
|
||||||
|
}
|
||||||
|
return newTriggerValue;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TextAreaProps extends RcTextAreaProps {
|
export interface TextAreaProps extends RcTextAreaProps {
|
||||||
allowClear?: boolean;
|
allowClear?: boolean;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
@ -54,6 +74,8 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
|
|||||||
const clearableInputRef = React.useRef<ClearableLabeledInput>(null);
|
const clearableInputRef = React.useRef<ClearableLabeledInput>(null);
|
||||||
|
|
||||||
const [compositing, setCompositing] = React.useState(false);
|
const [compositing, setCompositing] = React.useState(false);
|
||||||
|
const oldCompositionValueRef = React.useRef<string>();
|
||||||
|
const oldSelectionStartRef = React.useRef<number>(0);
|
||||||
|
|
||||||
const [value, setValue] = useMergedState(props.defaultValue, {
|
const [value, setValue] = useMergedState(props.defaultValue, {
|
||||||
value: props.value,
|
value: props.value,
|
||||||
@ -73,6 +95,10 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
|
|||||||
|
|
||||||
const onInternalCompositionStart: React.CompositionEventHandler<HTMLTextAreaElement> = e => {
|
const onInternalCompositionStart: React.CompositionEventHandler<HTMLTextAreaElement> = e => {
|
||||||
setCompositing(true);
|
setCompositing(true);
|
||||||
|
// 拼音输入前保存一份旧值
|
||||||
|
oldCompositionValueRef.current = value as string;
|
||||||
|
// 保存旧的光标位置
|
||||||
|
oldSelectionStartRef.current = e.currentTarget.selectionStart;
|
||||||
onCompositionStart?.(e);
|
onCompositionStart?.(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,9 +107,16 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
|
|||||||
|
|
||||||
let triggerValue = e.currentTarget.value;
|
let triggerValue = e.currentTarget.value;
|
||||||
if (hasMaxLength) {
|
if (hasMaxLength) {
|
||||||
triggerValue = fixEmojiLength(triggerValue, maxLength!);
|
const isCursorInEnd =
|
||||||
|
oldSelectionStartRef.current >= maxLength! + 1 ||
|
||||||
|
oldSelectionStartRef.current === oldCompositionValueRef.current?.length;
|
||||||
|
triggerValue = setTriggerValue(
|
||||||
|
isCursorInEnd,
|
||||||
|
oldCompositionValueRef.current as string,
|
||||||
|
triggerValue,
|
||||||
|
maxLength!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch composition onChange when value changed
|
// Patch composition onChange when value changed
|
||||||
if (triggerValue !== value) {
|
if (triggerValue !== value) {
|
||||||
handleSetValue(triggerValue);
|
handleSetValue(triggerValue);
|
||||||
@ -96,9 +129,13 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
|
|||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
let triggerValue = e.target.value;
|
let triggerValue = e.target.value;
|
||||||
if (!compositing && hasMaxLength) {
|
if (!compositing && hasMaxLength) {
|
||||||
triggerValue = fixEmojiLength(triggerValue, maxLength!);
|
// 1. 复制粘贴超过maxlength的情况 2.未超过maxlength的情况
|
||||||
|
const isCursorInEnd =
|
||||||
|
e.target.selectionStart >= maxLength! + 1 ||
|
||||||
|
e.target.selectionStart === triggerValue.length ||
|
||||||
|
!e.target.selectionStart;
|
||||||
|
triggerValue = setTriggerValue(isCursorInEnd, value as string, triggerValue, maxLength!);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSetValue(triggerValue);
|
handleSetValue(triggerValue);
|
||||||
resolveOnChange(e.currentTarget, e, onChange, triggerValue);
|
resolveOnChange(e.currentTarget, e, onChange, triggerValue);
|
||||||
};
|
};
|
||||||
|
@ -9530,10 +9530,19 @@ Array [
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/input/demo/textarea.md extend context correctly 1`] = `
|
exports[`renders ./components/input/demo/textarea.md extend context correctly 1`] = `
|
||||||
|
Array [
|
||||||
<textarea
|
<textarea
|
||||||
class="ant-input"
|
class="ant-input"
|
||||||
rows="4"
|
rows="4"
|
||||||
/>
|
/>,
|
||||||
|
<br />,
|
||||||
|
<br />,
|
||||||
|
<textarea
|
||||||
|
class="ant-input"
|
||||||
|
placeholder="maxLength is 6"
|
||||||
|
rows="4"
|
||||||
|
/>,
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/input/demo/textarea-resize.md extend context correctly 1`] = `
|
exports[`renders ./components/input/demo/textarea-resize.md extend context correctly 1`] = `
|
||||||
|
@ -3304,10 +3304,19 @@ Array [
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/input/demo/textarea.md correctly 1`] = `
|
exports[`renders ./components/input/demo/textarea.md correctly 1`] = `
|
||||||
|
Array [
|
||||||
<textarea
|
<textarea
|
||||||
class="ant-input"
|
class="ant-input"
|
||||||
rows="4"
|
rows="4"
|
||||||
/>
|
/>,
|
||||||
|
<br />,
|
||||||
|
<br />,
|
||||||
|
<textarea
|
||||||
|
class="ant-input"
|
||||||
|
placeholder="maxLength is 6"
|
||||||
|
rows="4"
|
||||||
|
/>,
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/input/demo/textarea-resize.md correctly 1`] = `
|
exports[`renders ./components/input/demo/textarea-resize.md correctly 1`] = `
|
||||||
|
@ -110,6 +110,64 @@ describe('TextArea', () => {
|
|||||||
expect.objectContaining({ target: expect.objectContaining({ value: '竹' }) }),
|
expect.objectContaining({ target: expect.objectContaining({ value: '竹' }) }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 字符输入
|
||||||
|
it('should not cut off string when cursor position is not at the end', () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const wrapper = mount(<TextArea maxLength={6} defaultValue="123456" onChange={onChange} />);
|
||||||
|
wrapper
|
||||||
|
.find('textarea')
|
||||||
|
.simulate('change', { target: { selectionStart: 1, value: 'w123456' } });
|
||||||
|
wrapper
|
||||||
|
.find('textarea')
|
||||||
|
.simulate('change', { target: { selectionStart: 3, value: '123w456' } });
|
||||||
|
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('123456');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拼音输入
|
||||||
|
// 1. 光标位于最后,且当前字符数未达到6个,若选中的字符 + 原字符的长度超过6个,则将最终的字符按照maxlength截断
|
||||||
|
it('when the input method is pinyin and the cursor is at the end, should use maxLength to crop', () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const wrapper = mount(<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />);
|
||||||
|
wrapper.find('textarea').instance().value = '1234'; // enzyme not support change `currentTarget`
|
||||||
|
wrapper.find('textarea').instance().selectionStart = 4;
|
||||||
|
wrapper.find('textarea').simulate('compositionStart');
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('textarea')
|
||||||
|
.simulate('change', { target: { selectionStart: 9, value: '1234z z z' } });
|
||||||
|
wrapper
|
||||||
|
.find('textarea')
|
||||||
|
.simulate('change', { target: { selectionStart: 7, value: '1234组织者' } });
|
||||||
|
|
||||||
|
wrapper.find('textarea').instance().value = '1234组织者';
|
||||||
|
wrapper.find('textarea').instance().selectionStart = 7;
|
||||||
|
wrapper.find('textarea').simulate('compositionEnd');
|
||||||
|
|
||||||
|
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('1234组织');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 光标位于中间或开头,且当前字符数未达到6个,若选中的字符 + 原字符的长度超过6个,则显示原有字符
|
||||||
|
it('when the input method is Pinyin and the cursor is in the middle, should display the original string', () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const wrapper = mount(<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />);
|
||||||
|
wrapper.find('textarea').instance().value = '1234'; // enzyme not support change `currentTarget`
|
||||||
|
wrapper.find('textarea').instance().selectionStart = 2;
|
||||||
|
wrapper.find('textarea').simulate('compositionStart');
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('textarea')
|
||||||
|
.simulate('change', { target: { selectionStart: 2, value: '12z z z34' } });
|
||||||
|
wrapper
|
||||||
|
.find('textarea')
|
||||||
|
.simulate('change', { target: { selectionStart: 5, value: '12组织者34' } });
|
||||||
|
|
||||||
|
wrapper.find('textarea').instance().value = '12组织者34';
|
||||||
|
wrapper.find('textarea').instance().selectionStart = 5;
|
||||||
|
wrapper.find('textarea').simulate('compositionEnd');
|
||||||
|
|
||||||
|
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('1234');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when prop value not in this.props, resizeTextarea should be called', async () => {
|
it('when prop value not in this.props, resizeTextarea should be called', async () => {
|
||||||
|
@ -18,5 +18,13 @@ import { Input } from 'antd';
|
|||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
ReactDOM.render(<TextArea rows={4} />, mountNode);
|
ReactDOM.render(
|
||||||
|
<>
|
||||||
|
<TextArea rows={4} />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<TextArea rows={4} placeholder="maxLength is 6" maxLength={6} />
|
||||||
|
</>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user