vxe-pc-ui
Version:
A vue based PC component library
1,097 lines (1,009 loc) • 37.7 kB
text/typescript
import { h, ref, Ref, computed, reactive, inject, nextTick, watch, onMounted, onBeforeUnmount, PropType } from 'vue'
import { defineVxeComponent } from '../../ui/src/comp'
import XEUtils from 'xe-utils'
import { getConfig, getIcon, getI18n, globalEvents, GLOBAL_EVENT_KEYS, createEvent, useSize, renderEmptyElement } from '../../ui'
import { getFuncText, eqEmptyValue, isEnableConf } from '../../ui/src/utils'
import { hasClass, getEventTargetNode, hasControlKey } from '../../ui/src/dom'
import { getSlotVNs } from '../../ui/src/vn'
import { handleNumber, toFloatValueFixed } from './util'
import type { VxeNumberInputConstructor, NumberInputInternalData, VxeNumberInputEmits, VxeNumberInputPrivateComputed, NumberInputReactData, NumberInputMethods, VxeNumberInputPropTypes, InputPrivateRef, VxeFormConstructor, VxeFormPrivateMethods, VxeFormDefines, ValueOf } from '../../../types'
export default defineVxeComponent({
name: 'VxeNumberInput',
props: {
modelValue: [String, Number] as PropType<VxeNumberInputPropTypes.ModelValue>,
immediate: {
type: Boolean as PropType<VxeNumberInputPropTypes.Immediate>,
default: true
},
name: String as PropType<VxeNumberInputPropTypes.Name>,
type: {
type: String as PropType<VxeNumberInputPropTypes.Type>,
default: 'number'
},
clearable: {
type: Boolean as PropType<VxeNumberInputPropTypes.Clearable>,
default: () => getConfig().numberInput.clearable
},
readonly: {
type: Boolean as PropType<VxeNumberInputPropTypes.Readonly>,
default: null
},
disabled: {
type: Boolean as PropType<VxeNumberInputPropTypes.Disabled>,
default: null
},
placeholder: String as PropType<VxeNumberInputPropTypes.Placeholder>,
maxLength: {
type: [String, Number] as PropType<VxeNumberInputPropTypes.MaxLength>,
default: () => getConfig().numberInput.maxLength
},
autoComplete: {
type: String as PropType<VxeNumberInputPropTypes.AutoComplete>,
default: 'off'
},
align: String as PropType<VxeNumberInputPropTypes.Align>,
form: String as PropType<VxeNumberInputPropTypes.Form>,
className: String as PropType<VxeNumberInputPropTypes.ClassName>,
size: {
type: String as PropType<VxeNumberInputPropTypes.Size>,
default: () => getConfig().numberInput.size || getConfig().size
},
// number、integer、float
min: {
type: [String, Number] as PropType<VxeNumberInputPropTypes.Min>,
default: null
},
max: {
type: [String, Number] as PropType<VxeNumberInputPropTypes.Max>,
default: null
},
step: [String, Number] as PropType<VxeNumberInputPropTypes.Step>,
exponential: {
type: Boolean as PropType<VxeNumberInputPropTypes.Exponential>,
default: () => getConfig().numberInput.exponential
},
showCurrency: {
type: Boolean as PropType<VxeNumberInputPropTypes.ShowCurrency>,
default: () => getConfig().numberInput.showCurrency
},
currencySymbol: {
type: String as PropType<VxeNumberInputPropTypes.CurrencySymbol>,
default: () => getConfig().numberInput.currencySymbol
},
controlConfig: Object as PropType<VxeNumberInputPropTypes.ControlConfig>,
// float
digits: {
type: [String, Number] as PropType<VxeNumberInputPropTypes.Digits>,
default: null
},
autoFill: {
type: Boolean as PropType<VxeNumberInputPropTypes.AutoFill>,
default: () => getConfig().numberInput.autoFill
},
editable: {
type: Boolean as PropType<VxeNumberInputPropTypes.Editable>,
default: true
},
plusIcon: String as PropType<VxeNumberInputPropTypes.PlusIcon>,
minusIcon: String as PropType<VxeNumberInputPropTypes.MinusIcon>,
prefixIcon: String as PropType<VxeNumberInputPropTypes.PrefixIcon>,
prefixConfig: Object as PropType<VxeNumberInputPropTypes.PrefixConfig>,
suffixIcon: String as PropType<VxeNumberInputPropTypes.SuffixIcon>,
suffixConfig: Object as PropType<VxeNumberInputPropTypes.SuffixConfig>,
// 已废弃
controls: {
type: Boolean as PropType<VxeNumberInputPropTypes.Controls>,
default: null
},
// 已废弃
maxlength: [String, Number] as PropType<VxeNumberInputPropTypes.Maxlength>,
// 已废弃
autocomplete: String as PropType<VxeNumberInputPropTypes.Autocomplete>
},
emits: [
'update:modelValue',
'input',
'change',
'keydown',
'keyup',
'wheel',
'click',
'focus',
'blur',
'clear',
'lazy-change',
'plus-number',
'minus-number',
'prefix-click',
'suffix-click',
// 已废弃
'prev-number',
'next-number'
] as VxeNumberInputEmits,
setup (props, context) {
const { slots, emit } = context
const $xeForm = inject<VxeFormConstructor & VxeFormPrivateMethods | null>('$xeForm', null)
const formItemInfo = inject<VxeFormDefines.ProvideItemInfo | null>('xeFormItemInfo', null)
const xID = XEUtils.uniqueId()
const { computeSize } = useSize(props)
const reactData = reactive<NumberInputReactData>({
isFocus: false,
isActivated: false,
inputValue: ''
})
const internalData: NumberInputInternalData = {
// dnTimeout: undefined,
// ainTimeout: undefined,
// isMouseDown: undefined,
// isUM: undefined
}
const refElem = ref() as Ref<HTMLDivElement>
const refInputTarget = ref() as Ref<HTMLInputElement>
const refInputPanel = ref() as Ref<HTMLDivElement>
const computeFormReadonly = computed(() => {
const { readonly } = props
if (readonly === null) {
if ($xeForm) {
return $xeForm.props.readonly
}
return false
}
return readonly
})
const computeIsDisabled = computed(() => {
const { disabled } = props
if (disabled === null) {
if ($xeForm) {
return $xeForm.props.disabled
}
return false
}
return disabled
})
const computeDigitsValue = computed(() => {
const { type, digits } = props
let defDigits: any = digits
if (defDigits === null) {
defDigits = getConfig().numberInput.digits
if (defDigits === null) {
if (type === 'amount') {
defDigits = 2
}
}
}
return XEUtils.toInteger(defDigits) || 1
})
const computeControlOpts = computed(() => {
return Object.assign({}, getConfig().numberInput.controlConfig, props.controlConfig)
})
const computePrefixOpts = computed(() => {
return Object.assign({}, getConfig().numberInput.prefixConfig, props.prefixConfig)
})
const computeSuffixOpts = computed(() => {
return Object.assign({}, getConfig().numberInput.suffixConfig, props.suffixConfig)
})
const computeDecimalsType = computed(() => {
const { type } = props
return type === 'float' || type === 'amount'
})
const computeStepValue = computed(() => {
const { type } = props
const digitsValue = computeDigitsValue.value
const decimalsType = computeDecimalsType.value
const step = props.step
if (type === 'integer') {
return XEUtils.toInteger(step) || 1
} else if (decimalsType) {
return XEUtils.toNumber(step) || (1 / Math.pow(10, digitsValue))
}
return XEUtils.toNumber(step) || 1
})
const computeIsClearable = computed(() => {
return props.clearable
})
const computeInputReadonly = computed(() => {
const { editable } = props
const formReadonly = computeFormReadonly.value
return formReadonly || !editable
})
const computeInpPlaceholder = computed(() => {
const { placeholder } = props
if (placeholder) {
return getFuncText(placeholder)
}
const globalPlaceholder = getConfig().numberInput.placeholder
if (globalPlaceholder) {
return getFuncText(globalPlaceholder)
}
return getI18n('vxe.base.pleaseInput')
})
const computeInpMaxLength = computed(() => {
const { type, maxLength, maxlength, min, max } = props
const digitsValue = computeDigitsValue.value
// 数值最大长度默认限制 16 位,包含小数
const maxDefLen = 16
if (!eqEmptyValue(min) && !eqEmptyValue(max)) {
return `${max}`.length + (type === 'integer' ? 0 : (digitsValue + 1)) + (XEUtils.toNumber(min) >= 0 ? 0 : 1)
}
if (maxLength || maxlength) {
return XEUtils.toNumber(maxLength || maxlength)
}
return maxDefLen
})
const computeInpImmediate = computed(() => {
const { immediate } = props
return immediate
})
const computeNumValue = computed(() => {
const { type } = props
const { inputValue } = reactData
return type === 'integer' ? XEUtils.toInteger(handleNumber(inputValue)) : XEUtils.toNumber(handleNumber(inputValue))
})
const computeNumLabel = computed(() => {
const { type, showCurrency, currencySymbol, autoFill } = props
const { inputValue } = reactData
const digitsValue = computeDigitsValue.value
if (type === 'amount') {
const num = XEUtils.toNumber(inputValue)
let amountLabel = XEUtils.commafy(num, { digits: digitsValue })
if (!autoFill) {
const [iStr, dStr] = amountLabel.split('.')
if (dStr) {
const dRest = dStr.replace(/0+$/, '')
amountLabel = dRest ? [iStr, '.', dRest].join('') : iStr
}
}
if (showCurrency) {
return `${currencySymbol || getI18n('vxe.numberInput.currencySymbol') || ''}${amountLabel}`
}
return amountLabel
}
return XEUtils.toString(inputValue)
})
const computeIsDisabledSubtractNumber = computed(() => {
const { min } = props
const { inputValue } = reactData
const numValue = computeNumValue.value
// 当有值时再进行判断
if ((inputValue || inputValue === 0) && min !== null) {
return numValue <= XEUtils.toNumber(min)
}
return false
})
const computeIsDisabledAddNumber = computed(() => {
const { max } = props
const { inputValue } = reactData
const numValue = computeNumValue.value
// 当有值时再进行判断
if ((inputValue || inputValue === 0) && max !== null) {
return numValue >= XEUtils.toNumber(max)
}
return false
})
const refMaps: InputPrivateRef = {
refElem,
refInput: refInputTarget
}
const computeMaps: VxeNumberInputPrivateComputed = {
computeControlOpts
}
const $xeNumberInput = {
xID,
props,
context,
reactData,
internalData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps
} as unknown as VxeNumberInputConstructor
let numberInputMethods = {} as NumberInputMethods
const handleNumberString = (val: any) => {
if (XEUtils.eqNull(val)) {
return ''
}
return `${val}`
}
const getNumberValue = (val: any) => {
const { exponential, autoFill } = props
const inpMaxLength = computeInpMaxLength.value
const digitsValue = computeDigitsValue.value
const decimalsType = computeDecimalsType.value
let restVal = ''
if (decimalsType) {
restVal = toFloatValueFixed(val, digitsValue)
if (!autoFill) {
restVal = handleNumberString(XEUtils.toNumber(restVal))
}
} else {
restVal = handleNumberString(val)
}
if (exponential && (val === restVal || handleNumberString(val).toLowerCase() === XEUtils.toNumber(restVal).toExponential())) {
return val
}
return restVal.slice(0, inpMaxLength)
}
const triggerEvent = (evnt: Event & { type: 'input' | 'change' | 'keydown' | 'keyup' | 'wheel' | 'click' | 'focus' | 'blur' }) => {
const { inputValue } = reactData
numberInputMethods.dispatchEvent(evnt.type, { value: inputValue }, evnt)
}
const handleChange = (val: number | null, inputValue: string, evnt: Event | { type: string }) => {
const value = eqEmptyValue(val) ? null : Number(val)
const isChange = value !== props.modelValue
if (isChange) {
internalData.isUM = true
emit('update:modelValue', value)
}
if (reactData.inputValue !== inputValue) {
nextTick(() => {
reactData.inputValue = inputValue || ''
})
}
numberInputMethods.dispatchEvent('input', { value }, evnt as Event)
if (isChange) {
numberInputMethods.dispatchEvent('change', { value }, evnt as Event)
// 自动更新校验状态
if ($xeForm && formItemInfo) {
$xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, value)
}
}
}
const emitInputEvent = (inputValue: any, evnt: Event) => {
const inpImmediate = computeInpImmediate.value
const value = eqEmptyValue(inputValue) ? null : XEUtils.toNumber(inputValue)
reactData.inputValue = inputValue
if (inpImmediate) {
handleChange(value, inputValue, evnt)
} else {
numberInputMethods.dispatchEvent('input', { value }, evnt)
}
}
const inputEvent = (evnt: Event & { type: 'input' }) => {
const inputElem = evnt.target as HTMLInputElement
const value = inputElem.value
emitInputEvent(value, evnt)
}
const changeEvent = (evnt: Event & { type: 'change' }) => {
const inpImmediate = computeInpImmediate.value
if (!inpImmediate) {
triggerEvent(evnt)
}
$xeNumberInput.dispatchEvent('lazy-change', { value: reactData.inputValue }, evnt)
}
const focusEvent = (evnt: Event & { type: 'focus' }) => {
const inputReadonly = computeInputReadonly.value
if (!inputReadonly) {
const { inputValue } = reactData
reactData.inputValue = eqEmptyValue(inputValue) ? '' : `${XEUtils.toNumber(inputValue)}`
reactData.isFocus = true
reactData.isActivated = true
triggerEvent(evnt)
}
}
const clickPrefixEvent = (evnt: Event) => {
const isDisabled = computeIsDisabled.value
if (!isDisabled) {
const { inputValue } = reactData
numberInputMethods.dispatchEvent('prefix-click', { value: inputValue }, evnt)
}
}
const clearValueEvent = (evnt: Event, value: VxeNumberInputPropTypes.ModelValue) => {
focus()
handleChange(null, '', evnt)
numberInputMethods.dispatchEvent('clear', { value }, evnt)
$xeNumberInput.dispatchEvent('lazy-change', { value }, evnt)
}
const clickSuffixEvent = (evnt: Event) => {
const isDisabled = computeIsDisabled.value
if (!isDisabled) {
const { inputValue } = reactData
numberInputMethods.dispatchEvent('suffix-click', { value: inputValue }, evnt)
}
}
const updateModel = (val: any) => {
const { autoFill } = props
const { inputValue } = reactData
const digitsValue = computeDigitsValue.value
const decimalsType = computeDecimalsType.value
if (eqEmptyValue(val)) {
reactData.inputValue = ''
} else {
let textValue = `${val}`
if (decimalsType) {
textValue = toFloatValueFixed(val, digitsValue)
if (!autoFill) {
textValue = `${XEUtils.toNumber(textValue)}`
}
}
if (textValue !== inputValue) {
reactData.inputValue = textValue
}
}
}
/**
* 检查初始值
*/
const initValue = () => {
const { autoFill } = props
const { inputValue } = reactData
const digitsValue = computeDigitsValue.value
const decimalsType = computeDecimalsType.value
if (decimalsType) {
if (inputValue) {
let textValue = ''
let validValue: number | null = null
if (inputValue) {
textValue = toFloatValueFixed(inputValue, digitsValue)
validValue = XEUtils.toNumber(textValue)
if (!autoFill) {
textValue = `${validValue}`
}
}
if (inputValue !== validValue) {
handleChange(validValue, textValue, { type: 'init' })
} else {
reactData.inputValue = textValue
}
}
}
}
const validMaxNum = (num: number | string) => {
return props.max === null || props.max === '' || XEUtils.toNumber(num) <= XEUtils.toNumber(props.max)
}
const validMinNum = (num: number | string) => {
return props.min === null || props.min === '' || XEUtils.toNumber(num) >= XEUtils.toNumber(props.min)
}
const afterCheckValue = () => {
const { type, min, max, exponential } = props
const { inputValue } = reactData
const inputReadonly = computeInputReadonly.value
if (!inputReadonly) {
if (eqEmptyValue(inputValue)) {
let inpNumVal = null
let inpValue = inputValue
if (min || min === 0) {
inpNumVal = XEUtils.toNumber(min)
inpValue = `${inpNumVal}`
}
handleChange(inpNumVal, `${inpValue || ''}`, { type: 'check' })
return
}
if (inputValue || (min || max)) {
let inpNumVal: number | string = type === 'integer' ? XEUtils.toInteger(handleNumber(inputValue)) : XEUtils.toNumber(handleNumber(inputValue))
if (!validMinNum(inpNumVal)) {
inpNumVal = min as number
} else if (!validMaxNum(inpNumVal)) {
inpNumVal = max as number
}
if (exponential) {
const inpStringVal = handleNumberString(inputValue).toLowerCase()
if (inpStringVal === XEUtils.toNumber(inpNumVal).toExponential()) {
inpNumVal = inpStringVal
}
}
const inpValue = getNumberValue(inpNumVal)
handleChange(eqEmptyValue(inpValue) ? null : Number(inpValue), inpValue, { type: 'check' })
}
}
}
const blurEvent = (evnt: Event & { type: 'blur' }) => {
const { inputValue } = reactData
const inpImmediate = computeInpImmediate.value
const value = inputValue ? Number(inputValue) : null
if (!inpImmediate) {
handleChange(value, handleNumberString(inputValue), evnt)
}
afterCheckValue()
reactData.isFocus = false
reactData.isActivated = false
numberInputMethods.dispatchEvent('blur', { value }, evnt)
// 自动更新校验状态
if ($xeForm && formItemInfo) {
$xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, value)
}
}
// 数值
const numberChange = (isPlus: boolean, evnt: Event) => {
const { min, max, type } = props
const { inputValue } = reactData
const stepValue = computeStepValue.value
const numValue = type === 'integer' ? XEUtils.toInteger(handleNumber(inputValue)) : XEUtils.toNumber(handleNumber(inputValue))
const newValue = isPlus ? XEUtils.add(numValue, stepValue) : XEUtils.subtract(numValue, stepValue)
let restNum: number | string
if (!validMinNum(newValue)) {
restNum = min as number
} else if (!validMaxNum(newValue)) {
restNum = max as number
} else {
restNum = newValue
}
emitInputEvent(getNumberValue(restNum), evnt as (Event & { type: 'input' }))
}
const numberPlusEvent = (evnt: Event) => {
const isDisabled = computeIsDisabled.value
const formReadonly = computeFormReadonly.value
const isDisabledAddNumber = computeIsDisabledAddNumber.value
if (!isDisabled && !formReadonly && !isDisabledAddNumber) {
numberChange(true, evnt)
}
reactData.isActivated = true
numberInputMethods.dispatchEvent('plus-number', { value: reactData.inputValue }, evnt)
$xeNumberInput.dispatchEvent('lazy-change', { value: reactData.inputValue }, evnt)
// 已废弃
numberInputMethods.dispatchEvent('next-number', { value: reactData.inputValue }, evnt)
}
const numberMinusEvent = (evnt: Event) => {
const isDisabled = computeIsDisabled.value
const formReadonly = computeFormReadonly.value
const isDisabledSubtractNumber = computeIsDisabledSubtractNumber.value
if (!isDisabled && !formReadonly && !isDisabledSubtractNumber) {
numberChange(false, evnt)
}
reactData.isActivated = true
numberInputMethods.dispatchEvent('minus-number', { value: reactData.inputValue }, evnt)
$xeNumberInput.dispatchEvent('lazy-change', { value: reactData.inputValue }, evnt)
// 已废弃
numberInputMethods.dispatchEvent('prev-number', { value: reactData.inputValue }, evnt)
}
const numberKeydownEvent = (evnt: KeyboardEvent) => {
const isUpArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)
const isDwArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)
if (isUpArrow || isDwArrow) {
evnt.preventDefault()
if (isUpArrow) {
numberPlusEvent(evnt)
} else {
numberMinusEvent(evnt)
}
}
}
const keydownEvent = (evnt: KeyboardEvent & { type: 'keydown' }) => {
const { type, exponential, controls } = props
const controlOpts = computeControlOpts.value
const { isArrow } = controlOpts
const inputReadonly = computeInputReadonly.value
const isControlKey = hasControlKey(evnt)
const isShiftKey = evnt.shiftKey
const isAltKey = evnt.altKey
const keyCode = evnt.keyCode
const isEsc = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ESCAPE)
const isUpArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)
const isDwArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)
if (!isControlKey && !isShiftKey && !isAltKey) {
if (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.SPACEBAR) || (type === 'integer' && keyCode === 110) || ((!exponential || keyCode !== 69) && (keyCode >= 65 && keyCode <= 90)) || (keyCode >= 186 && keyCode <= 188) || keyCode >= 191) {
evnt.preventDefault()
}
}
if (isEsc) {
afterCheckValue()
} else if (isUpArrow || isDwArrow) {
if (isEnableConf(controlOpts) && (controls === false ? controls : isArrow) && !inputReadonly) {
numberKeydownEvent(evnt)
}
}
triggerEvent(evnt)
}
const keyupEvent = (evnt: KeyboardEvent & { type: 'keyup' }) => {
triggerEvent(evnt)
}
// 数值
const stopDown = () => {
const { dnTimeout } = internalData
if (dnTimeout) {
clearTimeout(dnTimeout)
internalData.dnTimeout = undefined
}
}
const stopAutoIncrement = () => {
const { ainTimeout } = internalData
if (ainTimeout) {
clearTimeout(ainTimeout)
internalData.ainTimeout = undefined
}
}
const numberDownMinusEvent = (evnt: Event) => {
numberStopAll()
internalData.ainTimeout = setTimeout(() => {
numberMinusEvent(evnt)
numberDownMinusEvent(evnt)
}, 60)
}
const numberDownPlusEvent = (evnt: Event) => {
numberStopAll()
internalData.ainTimeout = setTimeout(() => {
numberPlusEvent(evnt)
numberDownPlusEvent(evnt)
}, 60)
}
const numberStopAll = () => {
stopDown()
stopAutoIncrement()
}
const numberClickEvent = (evnt: MouseEvent) => {
if (internalData.isMouseDown) {
internalData.isMouseDown = false
} else {
numberStopAll()
const isAddNumber = hasClass(evnt.currentTarget, 'is--plus')
if (isAddNumber) {
numberPlusEvent(evnt)
} else {
numberMinusEvent(evnt)
}
}
}
const numberMousedownEvent = (evnt: MouseEvent) => {
numberStopAll()
internalData.isMouseDown = true
if (evnt.button === 0) {
const isAddNumber = hasClass(evnt.currentTarget, 'is--plus')
if (isAddNumber) {
numberPlusEvent(evnt)
} else {
numberMinusEvent(evnt)
}
internalData.dnTimeout = setTimeout(() => {
if (isAddNumber) {
numberDownPlusEvent(evnt)
} else {
numberDownMinusEvent(evnt)
}
}, 500)
}
}
const wheelEvent = (evnt: WheelEvent) => {
const { controls } = props
const controlOpts = computeControlOpts.value
const { isWheel } = controlOpts
const inputReadonly = computeInputReadonly.value
if (isEnableConf(controlOpts) && (controls === false ? controls : isWheel) && !inputReadonly) {
if (reactData.isActivated) {
evnt.stopPropagation()
evnt.preventDefault()
const delta = evnt.deltaY
if (delta > 0) {
// 向下
numberMinusEvent(evnt)
} else if (delta < 0) {
// 向上
numberPlusEvent(evnt)
}
}
}
triggerEvent(evnt as WheelEvent & { type: 'wheel' })
}
const clickEvent = (evnt: Event & { type: 'click' }) => {
triggerEvent(evnt)
}
// 全局事件
const handleGlobalMousedownEvent = (evnt: Event) => {
const { isActivated } = reactData
const el = refElem.value
const panelElem = refInputPanel.value
const isDisabled = computeIsDisabled.value
const inputReadonly = computeInputReadonly.value
const inpImmediate = computeInpImmediate.value
if (!isDisabled && !inputReadonly && isActivated) {
reactData.isActivated = getEventTargetNode(evnt, el).flag || getEventTargetNode(evnt, panelElem).flag
if (!reactData.isActivated) {
if (!inpImmediate) {
const { inputValue } = reactData
const value = inputValue ? Number(inputValue) : null
handleChange(value, handleNumberString(inputValue), evnt)
}
afterCheckValue()
}
}
}
const handleGlobalKeydownEvent = (evnt: KeyboardEvent) => {
const { clearable } = props
const isDisabled = computeIsDisabled.value
const inputReadonly = computeInputReadonly.value
if (!isDisabled && !inputReadonly) {
const isTab = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.TAB)
const isDel = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.DELETE)
let isActivated = reactData.isActivated
if (isTab) {
if (isActivated) {
afterCheckValue()
}
isActivated = false
reactData.isActivated = isActivated
}
if (isDel && clearable) {
if (isActivated) {
clearValueEvent(evnt, null)
}
}
}
}
const handleGlobalBlurEvent = () => {
const { isActivated } = reactData
if (isActivated) {
afterCheckValue()
}
}
const dispatchEvent = (type: ValueOf<VxeNumberInputEmits>, params: Record<string, any>, evnt: Event | null) => {
emit(type, createEvent(evnt, { $numberInput: $xeNumberInput }, params))
}
numberInputMethods = {
dispatchEvent,
focus () {
const inputReadonly = computeInputReadonly.value
if (!inputReadonly) {
const inputElem = refInputTarget.value
reactData.isActivated = true
inputElem.focus()
}
return nextTick()
},
blur () {
const inputElem = refInputTarget.value
inputElem.blur()
reactData.isActivated = false
return nextTick()
},
select () {
const inputElem = refInputTarget.value
inputElem.select()
reactData.isActivated = false
return nextTick()
}
}
Object.assign($xeNumberInput, numberInputMethods)
const renderPrefixIcon = () => {
const { prefixIcon } = props
const prefixOpts = computePrefixOpts.value
const prefixSlot = slots.prefix
const preIcon = prefixIcon || prefixOpts.icon
const sufContent = prefixOpts.content
return prefixSlot || preIcon || sufContent
? h('div', {
class: 'vxe-number-input--prefix',
onClick: clickPrefixEvent
}, [
h('div', {
class: 'vxe-number-input--prefix-icon'
}, prefixSlot
? getSlotVNs(prefixSlot({}))
: [
preIcon
? h('i', {
class: preIcon
})
: renderEmptyElement($xeNumberInput),
sufContent
? h('span', {
class: 'vxe-prefix-input--suffix-text'
}, `${sufContent}`)
: renderEmptyElement($xeNumberInput)
])
])
: renderEmptyElement($xeNumberInput)
}
const renderSuffixIcon = () => {
const { suffixIcon } = props
const { inputValue } = reactData
const suffixSlot = slots.suffix
const suffixOpts = computeSuffixOpts.value
const isDisabled = computeIsDisabled.value
const isClearable = computeIsClearable.value
const sufIcon = suffixIcon || suffixOpts.icon
const sufContent = suffixOpts.content
return h('div', {
class: ['vxe-number-input--suffix', {
'is--clear': isClearable && !isDisabled && !(inputValue === '' || XEUtils.eqNull(inputValue))
}]
}, [
isClearable
? h('div', {
class: 'vxe-number-input--clear-icon',
onClick: clearValueEvent
}, [
h('i', {
class: getIcon().INPUT_CLEAR
})
])
: renderEmptyElement($xeNumberInput),
suffixSlot || sufIcon || sufContent
? h('div', {
class: 'vxe-number-input--suffix-icon',
onClick: clickSuffixEvent
}, suffixSlot
? getSlotVNs(suffixSlot({}))
: [
sufIcon
? h('i', {
class: sufIcon
})
: renderEmptyElement($xeNumberInput),
sufContent
? h('span', {
class: 'vxe-number-input--suffix-text'
}, `${sufContent}`)
: renderEmptyElement($xeNumberInput)
])
: renderEmptyElement($xeNumberInput)
])
}
const renderInput = () => {
const { type, name, autocomplete, autoComplete } = props
const { inputValue, isFocus } = reactData
const isDisabled = computeIsDisabled.value
const numLabel = computeNumLabel.value
const inputReadonly = computeInputReadonly.value
const inpMaxLength = computeInpMaxLength.value
const inpPlaceholder = computeInpPlaceholder.value
return h('div', {
key: 'ni',
class: 'vxe-number-input--input-wrapper'
}, [
renderPrefixIcon(),
h('div', {
class: 'vxe-number-input--input-inner'
}, [
h('input', {
ref: refInputTarget,
class: 'vxe-number-input--input',
value: !isFocus && type === 'amount' ? numLabel : inputValue,
name,
type: 'text',
placeholder: inpPlaceholder,
maxlength: inpMaxLength,
readonly: inputReadonly,
disabled: isDisabled,
autocomplete: autoComplete || autocomplete,
onKeydown: keydownEvent,
onKeyup: keyupEvent,
onClick: clickEvent,
onInput: inputEvent,
onChange: changeEvent,
onFocus: focusEvent,
onBlur: blurEvent
})
]),
renderSuffixIcon()
])
}
const renderMinusBtn = () => {
const { minusIcon } = props
const isDisabledSubtractNumber = computeIsDisabledSubtractNumber.value
return h('button', {
key: 'prev',
class: ['vxe-number-input--minus-btn is--minus', {
'is--disabled': isDisabledSubtractNumber
}],
type: 'button',
onClick: numberClickEvent,
onMousedown: numberMousedownEvent,
onMouseup: numberStopAll,
onMouseleave: numberStopAll
}, [
h('i', {
class: minusIcon || getIcon().NUMBER_INPUT_MINUS_NUM
})
])
}
const renderPlusBtn = () => {
const { plusIcon } = props
const isDisabledAddNumber = computeIsDisabledAddNumber.value
return h('button', {
key: 'next',
class: ['vxe-number-input--plus-btn is--plus', {
'is--disabled': isDisabledAddNumber
}],
type: 'button',
onClick: numberClickEvent,
onMousedown: numberMousedownEvent,
onMouseup: numberStopAll,
onMouseleave: numberStopAll
}, [
h('i', {
class: plusIcon || getIcon().NUMBER_INPUT_PLUS_NUM
})
])
}
const renderSideControl = () => {
return h('div', {
key: 'cplr',
class: 'vxe-number-input--side-control'
}, [
renderPlusBtn(),
renderMinusBtn()
])
}
const renderVN = () => {
const { className, controls, type, align, prefixIcon, suffixIcon } = props
const { inputValue, isActivated } = reactData
const vSize = computeSize.value
const controlOpts = computeControlOpts.value
const { layout, showButton } = controlOpts
const isDisabled = computeIsDisabled.value
const formReadonly = computeFormReadonly.value
const numLabel = computeNumLabel.value
const prefixSlot = slots.prefix
const suffixSlot = slots.suffix
if (formReadonly) {
return h('div', {
ref: refElem,
class: ['vxe-number-input--readonly', `type--${type}`, className]
}, numLabel)
}
const inputReadonly = computeInputReadonly.value
const isClearable = computeIsClearable.value
const isControls = isEnableConf(controlOpts) && (controls === false ? controls : showButton)
return h('div', {
ref: refElem,
class: ['vxe-number-input', `type--${type}`, `ctl--${layout === 'right' || layout === 'left' ? layout : 'default'}`, className, {
[`size--${vSize}`]: vSize,
[`is--${align}`]: align,
'is--controls': isControls && !inputReadonly,
'is--prefix': !!prefixSlot || prefixIcon,
'is--suffix': !!suffixSlot || suffixIcon,
'is--disabled': isDisabled,
'is--active': isActivated,
'show--clear': isClearable && !isDisabled && !(inputValue === '' || XEUtils.eqNull(inputValue))
}],
spellcheck: false
}, isControls
? (layout === 'right'
? [
renderInput(),
renderSideControl()
]
: (layout === 'left'
? [
renderSideControl(),
renderInput()
]
: [
renderMinusBtn(),
renderInput(),
renderPlusBtn()
]))
: [
renderInput()
])
}
$xeNumberInput.renderVN = renderVN
watch(() => props.modelValue, (val) => {
if (!internalData.isUM) {
updateModel(val)
}
internalData.isUM = false
})
watch(() => props.type, () => {
// 切换类型是重置内置变量
Object.assign(reactData, {
inputValue: props.modelValue
})
initValue()
})
onMounted(() => {
updateModel(props.modelValue)
const targetElem = refInputTarget.value
if (targetElem) {
targetElem.addEventListener('wheel', wheelEvent, { passive: false })
}
globalEvents.on($xeNumberInput, 'mousedown', handleGlobalMousedownEvent)
globalEvents.on($xeNumberInput, 'keydown', handleGlobalKeydownEvent)
globalEvents.on($xeNumberInput, 'blur', handleGlobalBlurEvent)
})
onBeforeUnmount(() => {
reactData.isFocus = false
numberStopAll()
afterCheckValue()
const targetElem = refInputTarget.value
if (targetElem) {
targetElem.removeEventListener('wheel', wheelEvent)
}
globalEvents.off($xeNumberInput, 'mousedown')
globalEvents.off($xeNumberInput, 'keydown')
globalEvents.off($xeNumberInput, 'blur')
})
initValue()
return $xeNumberInput
},
render () {
return this.renderVN()
}
})