import { computed , defineComponent , inject , PropType , ref , watch } from 'vue' ; import NP from 'number-precision' ; import { getPrefixCls } from '../_utils/global-config' ; import { isNumber , isUndefined } from '../_utils/is' ; import IconUp from '../icon/icon-up' ; import IconDown from '../icon/icon-down' ; import IconPlus from '../icon/icon-plus' ; import IconMinus from '../icon/icon-minus' ; import ArcoButton from '../button' ; import ArcoInput from '../input' ; import { EmitType } from '../_utils/types' ; import { Size } from '../_utils/constant' ; import { configProviderInjectionKey } from '../config-provider/context' ; type StepMethods = 'minus' | 'plus' ; const MODES = [ 'embed' , 'button' ] as const ; const SPEED = 150 ; NP . enableBoundaryChecking ( false ) ; export default defineComponent ( { name : 'InputNumber' , props : { zh en modelValue : Number , zh en defaultValue : Number , zh en values mode : { type : String as PropType < typeof MODES [ number ] > , default : 'embed' , validator : ( value ) => MODES . includes ( value ) } , zh en precision : Number , zh en step : { type : Number , default : 1 } , zh en disabled : { type : Boolean , default : false } , zh en max : { type : Number , default : Infinity } , zh en min : { type : Number , default : - Infinity } , zh en formatter : { type : Function } , zh en parser : { type : Function } , zh en placeholder : String , zh en hideButton : { type : Boolean , default : false } , zh en values defaultValue size : { type : String as PropType < Size > , default : ( ) => inject ( configProviderInjectionKey , undefined ) ?. size ?? 'medium' } , onChange : { type : [ Function , Array ] as PropType < EmitType < ( value : number | undefined ) => void > > } , onFocus : { type : [ Function , Array ] as PropType < EmitType < ( ev : FocusEvent ) => void > > } , onBlur : { type : [ Function , Array ] as PropType < EmitType < ( ev : FocusEvent ) => void > > } } , emits : [ 'update:modelValue' , 'change' , 'focus' , 'blur' , ] , setup ( props , { emit , slots } ) { const prefixCls = getPrefixCls ( 'input-number' ) ; const inputRef = ref < HTMLInputElement > ( ) ; const getStringValue = ( number ) => { return isUndefined ( number ) ? '' : props . formatter ?. ( String ( number ) ) ?? String ( number ) ; } ; const _value = ref ( getStringValue ( props . modelValue ?? props . defaultValue ) ) ; const valueNumber = computed ( ( ) => { if ( ! _value . value ) { return undefined ; } const number = Number ( props . parser ?. ( _value . value ) ?? _value . value ) ; return Number . isNaN ( number ) ? undefined : number ; } ) ; const mergedPrecision = computed ( ( ) => { if ( isNumber ( props . precision ) ) { const decimal = `${ props . step }` . split ( '.' ) [ 1 ] ; const stepPrecision = ( decimal && decimal . length ) || 0 ; return Math . max ( stepPrecision , props . precision ) ; } return undefined ; } ) ; const isMin = ref ( isNumber ( valueNumber . value ) && valueNumber . value <= props . min ) ; const isMax = ref ( isNumber ( valueNumber . value ) && valueNumber . value >= props . max ) ; let repeatTimer = 0 ; const clearRepeatTimer = ( ) => { if ( repeatTimer ) { window . clearTimeout ( repeatTimer ) ; repeatTimer = 0 ; } } ; const getLegalValue = ( value ) : number | undefined => { if ( isUndefined ( value ) ) { return undefined ; } if ( isNumber ( props . min ) && value < props . min ) { value = props . min ; } if ( isNumber ( props . max ) && value > props . max ) { value = props . max ; } return isNumber ( mergedPrecision . value ) ? NP . round ( value , mergedPrecision . value ) : value ; } ; const updateNumberStatus = ( number ) => { let _isMin = false ; let _isMax = false ; if ( isNumber ( number ) ) { if ( number <= props . min ) { _isMin = true ; } if ( number >= props . max ) { _isMax = true ; } } if ( isMax . value !== _isMax ) { isMax . value = _isMax ; } if ( isMin . value !== _isMin ) { isMin . value = _isMin ; } } ; const handleStepButton = ( event , method , needRepeat = false ) => { event . preventDefault ( ) ; inputRef . value ?. focus ( ) ; if ( props . disabled || ( method === 'plus' && isMax . value ) || ( method === 'minus' && isMin . value ) ) { return ; } let nextValue ; if ( isNumber ( valueNumber . value ) ) { nextValue = getLegalValue ( NP [ method ] ( valueNumber . value , props . step ) ) ; } else { nextValue = props . min === - Infinity ? 0 : props . min ; } _value . value = getStringValue ( nextValue ) ; updateNumberStatus ( nextValue ) ; emit ( 'update:modelValue' , nextValue ) ; emit ( 'change' , nextValue , event ) ; if ( needRepeat ) { repeatTimer = window . setTimeout ( ( ) => ( event . target as HTMLElement ) . dispatchEvent ( event ) , SPEED ) ; } } ; const handleInput = ( value , ev ) => { value = value . trim ( ) . replace ( /。/g , '.' ) ; value = props . parser ?. ( value ) ?? value ; if ( isNumber ( Number ( value ) ) || /^(\.|-)$/ . test ( value ) ) { _value . value = props . formatter ?. ( value ) ?? value ; updateNumberStatus ( valueNumber . value ) ; } } ; const handleFocus = ( ev ) => { emit ( 'focus' , ev ) ; } ; const handleChange = ( value , ev ) => { const finalValue = getLegalValue ( valueNumber . value ) ; const stringValue = getStringValue ( finalValue ) ; if ( finalValue !== valueNumber . value || _value . value !== stringValue ) { _value . value = stringValue ; updateNumberStatus ( finalValue ) ; } emit ( 'update:modelValue' , finalValue ) ; emit ( 'change' , finalValue , ev ) ; } ; const handleBlur = ( ev ) => { emit ( 'blur' , ev ) ; } ; const handleClear = ( ) => { _value . value = '' ; emit ( 'update:modelValue' , undefined ) ; emit ( 'change' , undefined ) ; } ; watch ( ( ) => props . modelValue , ( value ) => { if ( value !== valueNumber . value ) { _value . value = getStringValue ( value ) ; updateNumberStatus ( value ) ; } } ) ; const renderSuffix = ( ) => ( < > { slots . suffix ?. ( ) } < div class = { `${ prefixCls }-step` } > < button class = { [ `${ prefixCls }-step-button` , { [ `${ prefixCls }-step-button-disabled` ] : props . disabled || isMax . value } , ] } type = "button" disabled = { props . disabled || isMax . value } onMousedown = { ( e ) => handleStepButton ( e , 'plus' , true ) } onMouseup = { clearRepeatTimer } onMouseleave = { clearRepeatTimer } > < IconUp /> < /button> < button class = { [ `${ prefixCls }-step-button` , { [ `${ prefixCls }-step-button-disabled` ] : props . disabled || isMin . value } , ] } type = "button" disabled = { props . disabled || isMin . value } onMousedown = { ( e ) => handleStepButton ( e , 'minus' , true ) } onMouseup = { clearRepeatTimer } onMouseleave = { clearRepeatTimer } > < IconDown /> < /button> < /div> < />); const cls = computed ( ( ) => [ prefixCls , `${ prefixCls }-mode-${ props . mode }` , `${ prefixCls }-size-${ props . size }` , ] ) ; const renderPrependButton = ( ) => { return ( < ArcoButton size = { props . size } v - slots = { { icon : ( ) => < IconMinus /> }} class={`${prefixCls}-step-button`} disabled={props.disabled || isMin.value} onMousedown={(e) => handleStepButton(e, 'minus', true)} onMouseup={clearRepeatTimer} onMouseleave={clearRepeatTimer}/ > ) ; } ; const renderAppendButton = ( ) => { return ( < ArcoButton size = { props . size } v - slots = { { icon : ( ) => < IconPlus /> }} class={`${prefixCls}-step-button`} disabled={props.disabled || isMax.value} onMousedown={(e) => handleStepButton(e, 'plus', true)} onMouseup={clearRepeatTimer} onMouseleave={clearRepeatTimer}/ > ) ; } ; const render = ( ) => ( < ArcoInput v - slots = { { prepend : props . mode === 'button' ? renderPrependButton : slots . prepend , prefix : slots . prefix , suffix : props . mode === 'embed' && ! props . hideButton ? renderSuffix : slots . suffix , append : props . mode === 'button' ? renderAppendButton : slots . append } } ref = { inputRef } class = { cls . value } type = "text" size = { props . size } modelValue = { _value . value } placeholder = { props . placeholder } disabled = { props . disabled } onInput = { handleInput } onFocus = { handleFocus } onBlur = { handleBlur } onClear = { handleClear } onChange = { handleChange } / > ) ; 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 ( ) ; } } ) ;