Fix Input.TextArea cut text logic when maxLength configured.

pull/5361/head
tangjinzhou 2022-03-17 10:19:18 +08:00
parent ab26180635
commit d929217752
4 changed files with 86 additions and 8 deletions

View File

@ -33,6 +33,7 @@ export default defineComponent({
focused: PropTypes.looseBool,
bordered: PropTypes.looseBool.def(true),
triggerFocus: { type: Function as PropType<() => void> },
hidden: Boolean,
},
setup(props, { slots, attrs }) {
const containerRef = ref();
@ -91,6 +92,7 @@ export default defineComponent({
direction,
readonly,
bordered,
hidden,
addonAfter = slots.addonAfter,
addonBefore = slots.addonBefore,
} = props;
@ -121,6 +123,7 @@ export default defineComponent({
class={affixWrapperCls}
style={attrs.style}
onMouseup={onInputMouseUp}
hidden={hidden}
>
{prefixNode}
{cloneElement(element, {
@ -139,6 +142,7 @@ export default defineComponent({
addonAfter = slots.addonAfter?.(),
size,
direction,
hidden,
} = props;
// Not wrap when there is not addons
if (!hasAddon({ addonBefore, addonAfter })) {
@ -169,7 +173,7 @@ export default defineComponent({
// Need another wrapper for changing display:table to display:inline-block
// and put style prop in wrapper
return (
<span class={mergedGroupClassName} style={attrs.style}>
<span class={mergedGroupClassName} style={attrs.style} hidden={hidden}>
<span class={mergedWrapperClassName}>
{addonBeforeNode}
{cloneElement(labeledElement, { style: null })}
@ -185,6 +189,7 @@ export default defineComponent({
allowClear,
direction,
bordered,
hidden,
addonAfter = slots.addonAfter,
addonBefore = slots.addonBefore,
} = props;
@ -204,7 +209,7 @@ export default defineComponent({
},
);
return (
<span class={affixWrapperCls} style={attrs.style}>
<span class={affixWrapperCls} style={attrs.style} hidden={hidden}>
{cloneElement(element, {
style: null,
value,

View File

@ -24,6 +24,26 @@ function fixEmojiLength(value: string, maxLength: number) {
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 default defineComponent({
name: 'ATextarea',
inheritAttrs: false,
@ -40,6 +60,40 @@ export default defineComponent({
// Max length value
const hasMaxLength = computed(() => Number(props.maxlength) > 0);
const compositing = ref(false);
const oldCompositionValueRef = ref<string>();
const oldSelectionStartRef = ref<number>(0);
const onInternalCompositionStart = (e: CompositionEvent) => {
compositing.value = true;
//
oldCompositionValueRef.value = mergedValue.value as string;
//
oldSelectionStartRef.value = (e.currentTarget as any).selectionStart;
emit('compositionstart', e);
};
const onInternalCompositionEnd = (e: CompositionEvent) => {
compositing.value = false;
let triggerValue = (e.currentTarget as any).value;
if (hasMaxLength.value) {
const isCursorInEnd =
oldSelectionStartRef.value >= props.maxlength + 1 ||
oldSelectionStartRef.value === oldCompositionValueRef.value?.length;
triggerValue = setTriggerValue(
isCursorInEnd,
oldCompositionValueRef.value as string,
triggerValue,
props.maxlength,
);
}
// Patch composition onChange when value changed
if (triggerValue !== mergedValue.value) {
setValue(triggerValue);
resolveOnChange(e.currentTarget as any, e, triggerChange, triggerValue);
}
emit('compositionend', e);
};
const instance = getCurrentInstance();
watch(
() => props.value,
@ -103,12 +157,24 @@ export default defineComponent({
};
const handleChange = (e: Event) => {
const { value, composing } = e.target as any;
compositing.value = (e as any).isComposing || composing;
if ((compositing.value && props.lazy) || stateValue.value === value) return;
let triggerValue = (e.currentTarget as any).value;
const { composing } = e.target as any;
let triggerValue = (e.target as any).value;
compositing.value = !!((e as any).isComposing || composing);
if ((compositing.value && props.lazy) || stateValue.value === triggerValue) return;
if (hasMaxLength.value) {
triggerValue = fixEmojiLength(triggerValue, props.maxlength!);
// 1. maxlength 2.maxlength
const target = e.target as any;
const isCursorInEnd =
target.selectionStart >= props.maxlength! + 1 ||
target.selectionStart === triggerValue.length ||
!target.selectionStart;
triggerValue = setTriggerValue(
isCursorInEnd,
mergedValue.value as string,
triggerValue,
props.maxlength!,
);
}
resolveOnChange(e.currentTarget as any, e, triggerChange, triggerValue);
setValue(triggerValue);
@ -132,6 +198,8 @@ export default defineComponent({
onChange: handleChange,
onBlur,
onKeydown: handleKeyDown,
onCompositionstart: onInternalCompositionStart,
onCompositionend: onInternalCompositionEnd,
};
if (props.valueModifiers?.lazy) {
delete resizeProps.onInput;
@ -172,7 +240,7 @@ export default defineComponent({
mergedValue.value = val;
});
return () => {
const { maxlength, bordered = true } = props;
const { maxlength, bordered = true, hidden } = props;
const { style, class: customClass } = attrs;
const inputProps: any = {
@ -204,6 +272,7 @@ export default defineComponent({
}
textareaNode = (
<div
hidden={hidden}
class={classNames(
`${prefixCls.value}-textarea`,
{

View File

@ -17,6 +17,9 @@ For multi-line input.
</docs>
<template>
<a-textarea v-model:value="value" placeholder="Basic usage" :rows="4" />
<br />
<br />
<a-textarea :rows="4" placeholder="maxLength is 6" :maxlength="6" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

View File

@ -72,6 +72,7 @@ const inputProps = {
onInput: PropTypes.func,
'onUpdate:value': PropTypes.func,
valueModifiers: Object,
hidden: Boolean,
};
export default inputProps;
export type InputProps = Partial<ExtractPropTypes<typeof inputProps>>;