1- import type { CompositionEvent , FocusEvent , FormEvent , TouchEvent } from 'react' ;
2- import React , { forwardRef , useEffect , useImperativeHandle , useRef , useState } from 'react' ;
1+ import type { ChangeEvent , CompositionEvent , CSSProperties , FocusEvent , FormEvent , TouchEvent } from 'react' ;
2+ import React , { forwardRef , useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
33import classNames from 'classnames' ;
44import { isFunction } from 'lodash-es' ;
55import { BrowseIcon , BrowseOffIcon , CloseCircleFilledIcon } from 'tdesign-icons-react' ;
@@ -46,6 +46,8 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
4646 tips,
4747 type,
4848 readonly,
49+ extra,
50+ cursorColor,
4951 onBlur,
5052 onClear,
5153 onFocus,
@@ -54,11 +56,12 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
5456 onChange,
5557 } = useDefaultProps ( props , inputDefaultProps ) ;
5658
57- const [ showClear , setShowClear ] = useState < boolean > ( false ) ;
5859 const [ innerValue , setInnerValue ] = useDefault ( value , defaultValue , onChange ) ;
60+ const [ composingValue , setComposingValue ] = useState < string > ( '' ) ;
5961 const [ renderType , setRenderType ] = useState ( type ) ;
6062 const inputRef = useRef < HTMLInputElement > ( null ) ;
61- const focused = useRef < boolean > ( false ) ;
63+ const composingRef = useRef < boolean > ( false ) ;
64+ const [ focused , setFocused ] = useState < boolean > ( false ) ;
6265 const status = props . status || 'default' ;
6366 const { classPrefix } = useConfig ( ) ;
6467 const rootClassName = `${ classPrefix } -input` ;
@@ -78,18 +81,15 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
7881 blur,
7982 } ) ) ;
8083
81- useEffect ( ( ) => {
82- const computeShowClear = ( ) => {
83- if ( disabled || readonly ) {
84- return false ;
85- }
86- if ( clearable ) {
87- return clearTrigger === 'always' || ( clearTrigger === 'focus' && focused . current ) ;
88- }
84+ const showClear = useMemo < boolean > ( ( ) => {
85+ if ( disabled || readonly ) {
8986 return false ;
90- } ;
91- setShowClear ( computeShowClear ( ) ) ;
92- } , [ clearTrigger , clearable , disabled , readonly ] ) ;
87+ }
88+ if ( clearable ) {
89+ return clearTrigger === 'always' || ( clearTrigger === 'focus' && focused ) ;
90+ }
91+ return false ;
92+ } , [ clearTrigger , clearable , disabled , focused , readonly ] ) ;
9393
9494 useEffect ( ( ) => {
9595 if ( autofocus ) {
@@ -102,63 +102,84 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
102102 } , [ type ] ) ;
103103
104104 function focus ( ) {
105- focused . current = true ;
105+ setFocused ( true ) ;
106106 inputRef . current ?. focus ( ) ;
107107 }
108108
109109 function blur ( ) {
110- focused . current = false ;
110+ setFocused ( false ) ;
111111 inputRef . current ?. blur ( ) ;
112112 }
113113
114- const inputValueChangeHandle = ( e : FormEvent < HTMLInputElement > ) => {
114+ const handleInputValue = ( e : FormEvent < HTMLInputElement > ) => {
115115 const { value } = e . target as HTMLInputElement ;
116116 const { allowInputOverMax, maxcharacter } = props ;
117- if ( ! allowInputOverMax && maxcharacter && ! Number . isNaN ( maxcharacter ) ) {
117+
118+ // 如果允许超出最大输入限制,直接更新值并返回
119+ if ( allowInputOverMax ) {
120+ return value ;
121+ }
122+
123+ // 根据不同的限制条件处理输入值
124+ let finalValue = value ;
125+
126+ // 处理maxcharacter限制(优先级高于maxlength)
127+ if ( maxcharacter && ! Number . isNaN ( maxcharacter ) ) {
118128 const { characters } = getCharacterLength ( value , maxcharacter ) as {
119129 length : number ;
120130 characters : string ;
121131 } ;
122- setInnerValue ( characters ) ;
123- } else {
124- setInnerValue ( value ) ;
132+ finalValue = characters ;
125133 }
134+ // 处理maxlength限制
135+ else if ( resultMaxLength > 0 && value . length > resultMaxLength ) {
136+ finalValue = value . slice ( 0 , resultMaxLength ) ;
137+ }
138+
139+ return finalValue ;
126140 } ;
127141
128- const handleInput = ( e : FormEvent < HTMLInputElement > ) => {
129- // 中文输入的时候inputType是insertCompositionText所以中文输入的时候禁止触发。
130- if ( e instanceof InputEvent ) {
131- const checkInputType = e . inputType && e . inputType === 'insertCompositionText' ;
132- if ( e . isComposing || checkInputType ) return ;
142+ const handleInput = ( e : ChangeEvent < HTMLInputElement > ) => {
143+ const finalValue = handleInputValue ( e ) ;
144+ // react 中为合成事件,需要通过 nativeEvent 获取原始的事件
145+ const nativeEvent = e . nativeEvent as InputEvent ;
146+ // 中文输入的时候 inputType 是 insertCompositionText 所以中文输入的时候禁止触发。
147+ if ( nativeEvent . isComposing || nativeEvent . inputType === 'insertCompositionText' ) {
148+ composingRef . current = true ;
149+ setComposingValue ( finalValue ) ;
150+ return ;
133151 }
134- inputValueChangeHandle ( e ) ;
152+
153+ setInnerValue ( finalValue , { e, trigger : 'input' } ) ;
135154 } ;
136155
137156 const handleClear = ( e : TouchEvent < HTMLInputElement > ) => {
138157 e . preventDefault ( ) ;
139- setInnerValue ( '' ) ;
158+ setInnerValue ( '' , { e , trigger : 'clear' } ) ;
140159 focus ( ) ;
141160 onClear ?.( { e } ) ;
142161 } ;
143162
144163 const handleFocus = ( e : FocusEvent < HTMLInputElement > ) => {
145- focused . current = true ;
164+ setFocused ( true ) ;
146165 onFocus ?.( innerValue , { e } ) ;
147166 } ;
148167
149168 const handleBlur = ( e : FocusEvent < HTMLInputElement > ) => {
150- focused . current = false ;
151-
169+ setFocused ( false ) ;
152170 // 失焦时处理 format
153171 if ( isFunction ( format ) ) {
154- setInnerValue ( format ( innerValue ) ) ;
172+ setInnerValue ( format ( innerValue ) , { e , trigger : 'blur' } ) ;
155173 }
156174
157175 onBlur ?.( innerValue , { e } ) ;
158176 } ;
159177
160178 const handleCompositionend = ( e : CompositionEvent < HTMLInputElement > ) => {
161- inputValueChangeHandle ( e ) ;
179+ const finalValue = handleInputValue ( e ) ;
180+ composingRef . current = false ;
181+ // 更新输入值
182+ setInnerValue ( finalValue , { e, trigger : 'input' } ) ;
162183 } ;
163184
164185 const handlePwdIconClick = ( ) => {
@@ -175,6 +196,8 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
175196 </ div >
176197 ) ;
177198
199+ const renderExtra = ( ) => parseTNode ( extra ) ;
200+
178201 const renderClearable = ( ) =>
179202 showClear ? (
180203 < div className = { `${ rootClassName } __wrap--clearable-icon` } onTouchEnd = { handleClear } >
@@ -200,6 +223,12 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
200223 const renderTips = ( ) =>
201224 tips ? < div className = { `${ rootClassName } __tips ${ rootClassName } --${ align } ` } > { parseTNode ( tips ) } </ div > : null ;
202225
226+ const style : CSSProperties = {
227+ '--td-input-cursor-color' : cursorColor ,
228+ } as any ;
229+
230+ const finalValue = composingRef . current ? composingValue : ( innerValue ?? '' ) ;
231+
203232 return withNativeProps (
204233 props ,
205234 < div className = { rootClasses } >
@@ -209,28 +238,29 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
209238 < input
210239 ref = { inputRef }
211240 autoFocus = { autofocus }
212- value = { innerValue }
241+ value = { finalValue }
213242 name = { name }
214243 className = { inputClasses }
215244 type = { renderType }
216245 disabled = { disabled }
217246 autoComplete = { autocomplete }
218247 placeholder = { placeholder }
219248 readOnly = { readonly }
220- maxLength = { resultMaxLength || - 1 }
221249 enterKeyHint = { enterkeyhint }
222250 spellCheck = { spellcheck }
223251 onFocus = { handleFocus }
224252 onBlur = { handleBlur }
225253 onInput = { handleInput }
226254 onCompositionEnd = { handleCompositionend }
255+ style = { style }
227256 />
228257 { renderClearable ( ) }
229258 { renderSuffix ( ) }
230259 { renderSuffixIcon ( ) }
231260 </ div >
232261 { renderTips ( ) }
233262 </ div >
263+ { renderExtra ( ) }
234264 </ div > ,
235265 ) ;
236266} ) ;
0 commit comments