import type { PropType } from 'vue' ; import { computed , defineComponent , ref , nextTick , inject } from 'vue' ; import { getPrefixCls } from '../_utils/global-config' ; import { INPUT_EVENTS , Size , SIZES } from '../_utils/constant' ; import { Enter } from '../_utils/keycode' ; import IconHover from '../_components/icon-hover.vue' ; import IconClose from '../icon/icon-close' ; import { omit } from '../_utils/omit' ; import pick from '../_utils/pick' ; import { isFunction , isObject } from '../_utils/is' ; import { EmitType } from '../_utils/types' ; import { configProviderInjectionKey } from '../config-provider/context' ; const INPUT_TYPES = [ 'text' , 'password' ] as const ; type InputType = typeof INPUT_TYPES [ number ] ; export default defineComponent ( { name : 'Input' , inheritAttrs : false , props : { zh en modelValue : String , zh en defaultValue : { type : String , default : '' } , zh en values defaultValue size : { type : String as PropType < Size > , default : ( ) => inject ( configProviderInjectionKey , undefined ) ?. size ?? 'medium' } , zh en allowClear : { type : Boolean , default : false } , zh en disabled : { type : Boolean , default : false } , zh en readonly : { type : Boolean , default : false } , zh en error : { type : Boolean , default : false } , zh en placeholder : String , zh en maxLength : { type : [ Number , Object ] as PropType < number | { length : number ; errorOnly ? : boolean ; } > , default : 0 } , zh en showWordLimit : { type : Boolean , default : false } , zh en wordLength : { type : Function as PropType < ( value : string ) => number > } , zh en version wordSlice : { type : Function as PropType < ( value : string , maxLength : number ) => string > } , type : { type : String as PropType < InputType > , default : 'text' } , onInput : { type : [ Function , Array ] as PropType < EmitType < ( value : string , ev : Event ) => void > > } , onChange : { type : [ Function , Array ] as PropType < EmitType < ( value : string , ev : Event ) => void > > } , onPressEnter : { type : [ Function , Array ] as PropType < EmitType < ( ev : KeyboardEvent ) => void > > } , onClear : { type : [ Function , Array ] as PropType < EmitType < ( ev : MouseEvent ) => void > > } , onFocus : { type : [ Function , Array ] as PropType < EmitType < ( ev : FocusEvent ) => void > > } , onBlur : { type : [ Function , Array ] as PropType < EmitType < ( ev : FocusEvent ) => void > > } } , emits : [ 'update:modelValue' , 'input' , 'change' , 'pressEnter' , 'clear' , 'focus' , 'blur' , ] , zh en slot zh en slot zh en slot zh en slot setup ( props , { emit , slots , attrs } ) { const prefixCls = getPrefixCls ( 'input' ) ; const inputRef = ref < HTMLInputElement > ( ) ; const _value = ref ( props . defaultValue ) ; const computedValue = computed ( ( ) => props . modelValue ?? _value . value ) ; let preValue = computedValue . value ; const focused = ref ( false ) ; const showClearBtn = computed ( ( ) => props . allowClear && ! props . disabled && Boolean ( computedValue . value ) ) ; const isComposition = ref ( false ) ; const compositionValue = ref ( '' ) ; const getValueLength = ( value ) => { if ( isFunction ( props . wordLength ) ) { return props . wordLength ( value ) ; } return value . length ?? 0 ; } ; const valueLength = computed ( ( ) => getValueLength ( computedValue . value ) ) ; const mergedError = computed ( ( ) => { if ( props . error ) { return props . error ; } return Boolean ( isObject ( props . maxLength ) && props . maxLength . errorOnly && valueLength . value > maxLength . value ) ; } ) ; const maxLengthErrorOnly = computed ( ( ) => isObject ( props . maxLength ) && Boolean ( props . maxLength . errorOnly ) ) ; const maxLength = computed ( ( ) => { if ( isObject ( props . maxLength ) ) { return props . maxLength . length ; } return props . maxLength ; } ) ; const updateValue = ( value , inner = true ) => { if ( maxLength . value && ! maxLengthErrorOnly . value && getValueLength ( value ) > maxLength . value ) { value = props . wordSlice ?. ( value , maxLength . value ) ?? value . slice ( 0 , maxLength . value ) ; } _value . value = value ; if ( inner ) { emit ( 'update:modelValue' , value ) ; } } ; const handleMousedown = ( e ) => { if ( inputRef . value && e . target !== inputRef . value ) { e . preventDefault ( ) ; inputRef . value . focus ( ) ; } } ; const emitChange = ( value , ev ) => { if ( value !== preValue ) { preValue = value ; emit ( 'change' , value , ev ) ; } } ; const handleFocus = ( e ) => { focused . value = true ; preValue = computedValue . value ; emit ( 'focus' , e ) ; } ; const handleBlur = ( e ) => { focused . value = false ; emitChange ( computedValue . value , e ) ; emit ( 'blur' , e ) ; } ; const handleComposition = ( e ) => { const { value } = e . target as HTMLInputElement ; if ( e . type === 'compositionend' ) { isComposition . value = false ; compositionValue . value = '' ; updateValue ( value ) ; emit ( 'input' , value , e ) ; nextTick ( ( ) => { if ( inputRef . value && computedValue . value !== inputRef . value . value ) { inputRef . value . value = computedValue . value ; } } ) ; } else { isComposition . value = true ; compositionValue . value = computedValue . value + ( e . data ?? '' ) ; } } ; const handleInput = ( e ) => { const { value } = e . target as HTMLInputElement ; if ( ! isComposition . value ) { updateValue ( value ) ; emit ( 'input' , value , e ) ; nextTick ( ( ) => { if ( inputRef . value && computedValue . value !== inputRef . value . value ) { inputRef . value . value = computedValue . value ; } } ) ; } } ; const handleClear = ( ev ) => { updateValue ( '' ) ; emit ( 'clear' , ev ) ; } ; const handleKeyDown = ( e ) => { const keyCode = e . key || e . code ; if ( ! isComposition . value && keyCode === Enter . key ) { preValue = computedValue . value ; emit ( 'change' , computedValue . value , e ) ; emit ( 'pressEnter' , e ) ; } } ; const outerCls = computed ( ( ) => [ `${ prefixCls }-outer` , `${ prefixCls }-outer-size-${ props . size }` , { [ `${ prefixCls }-outer-has-suffix` ] : Boolean ( slots . suffix ) , [ `${ prefixCls }-outer-disabled` ] : props . disabled } , ] ) ; const wrapperCls = computed ( ( ) => [ `${ prefixCls }-wrapper` , { [ `${ prefixCls }-error` ] : mergedError . value , [ `${ prefixCls }-disabled` ] : props . disabled , [ `${ prefixCls }-focus` ] : focused . value } , ] ) ; const cls = computed ( ( ) => [ prefixCls , `${ prefixCls }-size-${ props . size }` ] ) ; const wrapperAttrs = computed ( ( ) => omit ( attrs , INPUT_EVENTS ) ) ; const inputAttrs = computed ( ( ) => pick ( attrs , INPUT_EVENTS ) ) ; const renderInput = ( hasOuter ? ) => ( < span class = { wrapperCls . value } onMousedown = { handleMousedown } { ... ( ! hasOuter ? wrapperAttrs . value : undefined ) } > { slots . prefix && ( < span class = { `${ prefixCls }-prefix` } > { slots . prefix ( ) } < /span>)} < input { ... inputAttrs . value } ref = { inputRef } class = { cls . value } value = { computedValue . value } type = { props . type } placeholder = { props . placeholder } readonly = { props . readonly } disabled = { props . disabled } onInput = { handleInput } onKeydown = { handleKeyDown } onFocus = { handleFocus } onBlur = { handleBlur } onCompositionstart = { handleComposition } onCompositionupdate = { handleComposition } onCompositionend = { handleComposition } / > { showClearBtn . value && ( < IconHover prefix = { prefixCls } class = { `${ prefixCls }-clear-btn` } onClick = { handleClear } > < IconClose /> < /IconHover>)} { ( slots . suffix || ( Boolean ( props . maxLength ) && props . showWordLimit ) ) && ( < span class = { `${ prefixCls }-suffix` } > { Boolean ( props . maxLength ) && props . showWordLimit && ( < span class = { `${ prefixCls }-word-limit` } > { valueLength . value } / { maxLength . value } < /span>)} { slots . suffix ?. ( ) } < /span>)} < /span>); const render = ( ) => { if ( slots . prepend || slots . append ) { return ( < span class = { outerCls . value } { ... wrapperAttrs . value } > { slots . prepend && ( < span class = { `${ prefixCls }-prepend` } > { slots . prepend ( ) } < /span>)} { renderInput ( true ) } { slots . append && ( < span class = { `${ prefixCls }-append` } > { slots . append ( ) } < /span>)} < /span>); } return renderInput ( ) ; } ; return { inputRef , render } ; } , methods : { zh en public focus ( ) { ( this . inputRef as HTMLInputElement ) ?. focus ( ) ; } , zh en public blur ( ) { ( this . inputRef as HTMLInputElement ) ?. blur ( ) ; } } , render ( ) { return this . render ( ) ; } } ) ;