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

View File

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

View File

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

View File

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