11import React , { forwardRef , useContext , useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
2- import type { FocusEvent , ReactNode } from 'react' ;
32import clsx from 'clsx' ;
43import { useMergeProps , useMergeState } from '@sqi-ui/hooks' ;
54import { isFunction , isNumber , isObject , isString , isUndefined } from '@sqi-ui/utils' ;
@@ -58,14 +57,20 @@ const Input = forwardRef<InputRef, InputProps>((baseProps, ref) => {
5857 visibilityToggle,
5958 maxLength,
6059 tips,
60+ composing,
61+ onKeyDown,
6162 onFocus,
6263 onBlur,
6364 onChange,
65+ onEnter,
66+ onCompositionStart,
67+ onCompositionEnd,
6468 ...restProps
6569 } = useMergeProps ( baseProps , defaultProps , componentConfig ?. Input ) ;
6670
6771 const wrapperRef = useRef < HTMLDivElement > ( null ) ;
6872 const inputRef = useRef < HTMLInputElement > ( null ) ;
73+ const composingRef = useRef ( false ) ;
6974
7075 useImperativeHandle ( ref , ( ) => ( {
7176 currentElement : wrapperRef . current ,
@@ -75,21 +80,39 @@ const Input = forwardRef<InputRef, InputProps>((baseProps, ref) => {
7580 select : ( ) => inputRef . current ?. select ( ) ,
7681 } ) ) ;
7782
78- // =========== Input Focus ============
83+ // =========== Input Events Handler ============
7984 const [ isFocused , toggleIsFocused ] = useState ( false ) ;
8085
81- const internalFocus = ( e : FocusEvent < HTMLInputElement , Element > ) => {
86+ const internalFocus = ( e : React . FocusEvent < HTMLInputElement , Element > ) => {
8287 if ( disabled || readOnly ) return ;
8388 toggleIsFocused ( true ) ;
8489 onFocus ?.( e ) ;
8590 } ;
8691
87- const internalBlur = ( e : FocusEvent < HTMLInputElement , Element > ) => {
92+ const internalBlur = ( e : React . FocusEvent < HTMLInputElement , Element > ) => {
8893 if ( disabled || readOnly ) return ;
8994 toggleIsFocused ( false ) ;
9095 onBlur ?.( e ) ;
9196 } ;
9297
98+ const internalKeyDown = ( e : React . KeyboardEvent < HTMLInputElement > ) => {
99+ e . key === 'Enter' && onEnter ?.( e ) ;
100+ onKeyDown ?.( e ) ;
101+ } ;
102+
103+ const internalCompositionStart = ( e : React . CompositionEvent < HTMLInputElement > ) => {
104+ if ( composing ) composingRef . current = true ;
105+ onCompositionStart ?.( e ) ;
106+ } ;
107+
108+ const internalCompositionEnd = ( e : React . CompositionEvent < HTMLInputElement > ) => {
109+ if ( composingRef . current ) {
110+ composingRef . current = false ;
111+ handleChange ( e ) ;
112+ }
113+ onCompositionEnd ?.( e ) ;
114+ } ;
115+
93116 // =========== Input State ============
94117 const mergedMaxLength = isNumber ( maxLength ) ? maxLength : maxLength ?. length ;
95118 const mergedErrorOnly = isNumber ( maxLength ) ? false : maxLength ?. errorOnly ;
@@ -100,10 +123,10 @@ const Input = forwardRef<InputRef, InputProps>((baseProps, ref) => {
100123 const formatValue = formatValueToString ( innerValue , mergedMaxLength , mergedErrorOnly ) ;
101124 const isErrorLength = isNumber ( mergedMaxLength ) ? formatValue . length > mergedMaxLength : false ;
102125
103- const handleChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
104- const { value } = e . target ;
126+ const handleChange = ( e : React . ChangeEvent < HTMLInputElement > | React . CompositionEvent < HTMLInputElement > ) => {
127+ const value = e . currentTarget . value ;
105128 setInnerValue ( value ) ;
106- onChange ?.( value , e ) ;
129+ ! composingRef . current && onChange ?.( value , e ) ;
107130 } ;
108131
109132 const handleClickInputWrapper = ( ) => {
@@ -177,7 +200,7 @@ const Input = forwardRef<InputRef, InputProps>((baseProps, ref) => {
177200 // =========== Input Suffix ============
178201 const isPassword = type === 'password' ;
179202
180- const suffixBaseElement : ReactNode = useMemo ( ( ) => {
203+ const suffixBaseElement : React . ReactNode = useMemo ( ( ) => {
181204 if ( ! isPassword ) return suffix ;
182205 if ( isObject ( visibilityToggle ) && isFunction ( visibilityToggle . renderIcon ) ) {
183206 return visibilityToggle . renderIcon ( renderType === 'text' ) ;
@@ -191,7 +214,7 @@ const Input = forwardRef<InputRef, InputProps>((baseProps, ref) => {
191214
192215 // fix: rerender 会重新渲染 `InputGroupWrapper`,导致无法正常聚焦失焦
193216 const InputGroupWrapper = useMemo ( ( ) => {
194- return function GroupWrapper ( { children } : { children : ReactNode } ) {
217+ return function GroupWrapper ( { children } : { children : React . ReactNode } ) {
195218 const hasCoreWrapper = addonBefore || addonAfter ;
196219
197220 let content = children ;
@@ -267,6 +290,9 @@ const Input = forwardRef<InputRef, InputProps>((baseProps, ref) => {
267290 onChange = { handleChange }
268291 onFocus = { internalFocus }
269292 onBlur = { internalBlur }
293+ onKeyDown = { internalKeyDown }
294+ onCompositionStart = { internalCompositionStart }
295+ onCompositionEnd = { internalCompositionEnd }
270296 />
271297 { clearElement }
272298 { suffixElement }
0 commit comments