@@ -24,6 +24,26 @@ function fixEmojiLength(value: string, maxLength: number) {
2424 return [ ...( value || '' ) ] . slice ( 0 , maxLength ) . join ( '' ) ;
2525}
2626
27+ function setTriggerValue (
28+ isCursorInEnd : boolean ,
29+ preValue : string ,
30+ triggerValue : string ,
31+ maxLength : number ,
32+ ) {
33+ let newTriggerValue = triggerValue ;
34+ if ( isCursorInEnd ) {
35+ // 光标在尾部,直接截断
36+ newTriggerValue = fixEmojiLength ( triggerValue , maxLength ! ) ;
37+ } else if (
38+ [ ...( preValue || '' ) ] . length < triggerValue . length &&
39+ [ ...( triggerValue || '' ) ] . length > maxLength !
40+ ) {
41+ // 光标在中间,如果最后的值超过最大值,则采用原先的值
42+ newTriggerValue = preValue ;
43+ }
44+ return newTriggerValue ;
45+ }
46+
2747export default defineComponent ( {
2848 name : 'ATextarea' ,
2949 inheritAttrs : false ,
@@ -40,6 +60,40 @@ export default defineComponent({
4060 // Max length value
4161 const hasMaxLength = computed ( ( ) => Number ( props . maxlength ) > 0 ) ;
4262 const compositing = ref ( false ) ;
63+
64+ const oldCompositionValueRef = ref < string > ( ) ;
65+ const oldSelectionStartRef = ref < number > ( 0 ) ;
66+ const onInternalCompositionStart = ( e : CompositionEvent ) => {
67+ compositing . value = true ;
68+ // 拼音输入前保存一份旧值
69+ oldCompositionValueRef . value = mergedValue . value as string ;
70+ // 保存旧的光标位置
71+ oldSelectionStartRef . value = ( e . currentTarget as any ) . selectionStart ;
72+ emit ( 'compositionstart' , e ) ;
73+ } ;
74+
75+ const onInternalCompositionEnd = ( e : CompositionEvent ) => {
76+ compositing . value = false ;
77+ let triggerValue = ( e . currentTarget as any ) . value ;
78+ if ( hasMaxLength . value ) {
79+ const isCursorInEnd =
80+ oldSelectionStartRef . value >= props . maxlength + 1 ||
81+ oldSelectionStartRef . value === oldCompositionValueRef . value ?. length ;
82+ triggerValue = setTriggerValue (
83+ isCursorInEnd ,
84+ oldCompositionValueRef . value as string ,
85+ triggerValue ,
86+ props . maxlength ,
87+ ) ;
88+ }
89+ // Patch composition onChange when value changed
90+ if ( triggerValue !== mergedValue . value ) {
91+ setValue ( triggerValue ) ;
92+ resolveOnChange ( e . currentTarget as any , e , triggerChange , triggerValue ) ;
93+ }
94+
95+ emit ( 'compositionend' , e ) ;
96+ } ;
4397 const instance = getCurrentInstance ( ) ;
4498 watch (
4599 ( ) => props . value ,
@@ -103,12 +157,24 @@ export default defineComponent({
103157 } ;
104158
105159 const handleChange = ( e : Event ) => {
106- const { value, composing } = e . target as any ;
107- compositing . value = ( e as any ) . isComposing || composing ;
108- if ( ( compositing . value && props . lazy ) || stateValue . value === value ) return ;
109- let triggerValue = ( e . currentTarget as any ) . value ;
160+ const { composing } = e . target as any ;
161+ let triggerValue = ( e . target as any ) . value ;
162+ compositing . value = ! ! ( ( e as any ) . isComposing || composing ) ;
163+ if ( ( compositing . value && props . lazy ) || stateValue . value === triggerValue ) return ;
164+
110165 if ( hasMaxLength . value ) {
111- triggerValue = fixEmojiLength ( triggerValue , props . maxlength ! ) ;
166+ // 1. 复制粘贴超过maxlength的情况 2.未超过maxlength的情况
167+ const target = e . target as any ;
168+ const isCursorInEnd =
169+ target . selectionStart >= props . maxlength ! + 1 ||
170+ target . selectionStart === triggerValue . length ||
171+ ! target . selectionStart ;
172+ triggerValue = setTriggerValue (
173+ isCursorInEnd ,
174+ mergedValue . value as string ,
175+ triggerValue ,
176+ props . maxlength ! ,
177+ ) ;
112178 }
113179 resolveOnChange ( e . currentTarget as any , e , triggerChange , triggerValue ) ;
114180 setValue ( triggerValue ) ;
@@ -132,6 +198,8 @@ export default defineComponent({
132198 onChange : handleChange ,
133199 onBlur,
134200 onKeydown : handleKeyDown ,
201+ onCompositionstart : onInternalCompositionStart ,
202+ onCompositionend : onInternalCompositionEnd ,
135203 } ;
136204 if ( props . valueModifiers ?. lazy ) {
137205 delete resizeProps . onInput ;
@@ -172,7 +240,7 @@ export default defineComponent({
172240 mergedValue . value = val ;
173241 } ) ;
174242 return ( ) => {
175- const { maxlength, bordered = true } = props ;
243+ const { maxlength, bordered = true , hidden } = props ;
176244 const { style, class : customClass } = attrs ;
177245
178246 const inputProps : any = {
@@ -204,6 +272,7 @@ export default defineComponent({
204272 }
205273 textareaNode = (
206274 < div
275+ hidden = { hidden }
207276 class = { classNames (
208277 `${ prefixCls . value } -textarea` ,
209278 {
0 commit comments