refactor(components): [input-number] switch to script-setup syntax (#7888)

This commit is contained in:
zz 2022-05-29 03:55:04 +08:00 committed by GitHub
parent ca658915dd
commit ffd83fda93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 239 additions and 265 deletions

View File

@ -4,7 +4,6 @@ import { describe, expect, it, test } from 'vitest'
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
import { ElFormItem } from '@element-plus/components/form'
import InputNumber from '../src/input-number.vue'
import type { InputNumberInstance } from '../src/input-number'
const mouseup = new Event('mouseup')
@ -305,36 +304,50 @@ describe('InputNumber.vue', () => {
test('check increase and decrease button when modelValue not in [min, max]', async () => {
const num1 = ref(-5)
const num2 = ref(15)
const inputNumber1 = ref<InputNumberInstance>()
const inputNumber2 = ref<InputNumberInstance>()
const wrapper = mount({
setup() {
return () => (
<>
<InputNumber
ref="inputNumber1"
v-model={num1.value}
min={1}
max={10}
/>
<InputNumber
ref="inputNumber2"
v-model={num2.value}
min={1}
max={10}
/>
</>
)
},
})
mount(() => (
<>
<InputNumber ref={inputNumber1} v-model={num1.value} min={1} max={10} />
<InputNumber ref={inputNumber2} v-model={num2.value} min={1} max={10} />
</>
))
const inputNumber1 = wrapper.findComponent({ ref: 'inputNumber1' }).vm
const inputNumber2 = wrapper.findComponent({ ref: 'inputNumber2' }).vm
expect(num1.value).toBe(1)
expect(num2.value).toBe(10)
inputNumber1.value!.decrease()
inputNumber1.decrease()
await nextTick()
expect(num1.value).toBe(1)
inputNumber1.value!.increase()
inputNumber1.increase()
await nextTick()
expect(num1.value).toBe(2)
inputNumber1.value!.increase()
inputNumber1.increase()
await nextTick()
expect(num1.value).toBe(3)
inputNumber2.value!.increase()
inputNumber2.increase()
await nextTick()
expect(num2.value).toBe(10)
inputNumber2.value!.decrease()
inputNumber2.decrease()
await nextTick()
expect(num2.value).toBe(9)
inputNumber2.value!.decrease()
inputNumber2.decrease()
await nextTick()
expect(num2.value).toBe(8)
})

View File

@ -58,20 +58,12 @@
/>
</div>
</template>
<script lang="ts">
import {
computed,
defineComponent,
onMounted,
onUpdated,
reactive,
ref,
watch,
} from 'vue'
<script lang="ts" setup>
import { computed, onMounted, onUpdated, reactive, ref, watch } from 'vue'
import { isNil } from 'lodash-unified'
import { ElInput } from '@element-plus/components/input'
import { ElIcon } from '@element-plus/components/icon'
import { RepeatClick } from '@element-plus/directives'
import { RepeatClick as vRepeatClick } from '@element-plus/directives'
import {
useDisabled,
useFormItem,
@ -79,257 +71,226 @@ import {
useNamespace,
useSize,
} from '@element-plus/hooks'
import ElInput from '@element-plus/components/input'
import { debugWarn, isNumber, isString, isUndefined } from '@element-plus/utils'
import { ArrowDown, ArrowUp, Minus, Plus } from '@element-plus/icons-vue'
import { inputNumberEmits, inputNumberProps } from './input-number'
import type { InputInstance } from '@element-plus/components/input'
import type { ComponentPublicInstance } from 'vue'
defineOptions({
name: 'ElInputNumber',
})
interface IData {
const props = defineProps(inputNumberProps)
const emit = defineEmits(inputNumberEmits)
const { t } = useLocale()
const ns = useNamespace('input-number')
const input = ref<InputInstance>()
interface Data {
currentValue: number | null | undefined
userInput: null | number | string
}
const data = reactive<Data>({
currentValue: props.modelValue,
userInput: null,
})
export default defineComponent({
name: 'ElInputNumber',
components: {
ElInput,
ElIcon,
ArrowUp,
ArrowDown,
Plus,
Minus,
},
directives: {
RepeatClick,
},
props: inputNumberProps,
emits: inputNumberEmits,
setup(props, { emit }) {
const input = ref<ComponentPublicInstance<typeof ElInput>>()
const data = reactive<IData>({
currentValue: props.modelValue,
userInput: null,
})
const { t } = useLocale()
const { formItem } = useFormItem()
const ns = useNamespace('input-number')
const { formItem } = useFormItem()
const minDisabled = computed(
() =>
isNumber(props.modelValue) &&
ensurePrecision(props.modelValue, -1) < props.min
)
const maxDisabled = computed(
() =>
isNumber(props.modelValue) &&
ensurePrecision(props.modelValue) > props.max
)
const minDisabled = computed(
() =>
isNumber(props.modelValue) &&
ensurePrecision(props.modelValue, -1)! < props.min
)
const maxDisabled = computed(
() =>
isNumber(props.modelValue) && ensurePrecision(props.modelValue)! > props.max
)
const numPrecision = computed(() => {
const stepPrecision = getPrecision(props.step)
if (!isUndefined(props.precision)) {
if (stepPrecision > props.precision) {
debugWarn(
'InputNumber',
'precision should not be less than the decimal places of step'
)
}
return props.precision
} else {
return Math.max(getPrecision(props.modelValue), stepPrecision)
}
})
const controlsAtRight = computed(() => {
return props.controls && props.controlsPosition === 'right'
})
const inputNumberSize = useSize()
const inputNumberDisabled = useDisabled()
const displayValue = computed(() => {
if (data.userInput !== null) {
return data.userInput
}
let currentValue: number | string | undefined | null = data.currentValue
if (isNil(currentValue)) return ''
if (isNumber(currentValue)) {
if (Number.isNaN(currentValue)) return ''
if (!isUndefined(props.precision)) {
currentValue = currentValue.toFixed(props.precision)
}
}
return currentValue
})
const toPrecision = (num: number, pre?: number) => {
if (isUndefined(pre)) pre = numPrecision.value
if (pre === 0) return Math.round(num)
let snum = String(num)
const pointPos = snum.indexOf('.')
if (pointPos === -1) return num
const nums = snum.replace('.', '').split('')
const datum = nums[pointPos + pre]
if (!datum) return num
const length = snum.length
if (snum.charAt(length - 1) === '5') {
snum = `${snum.slice(0, Math.max(0, length - 1))}6`
}
return Number.parseFloat(Number(snum).toFixed(pre))
}
const getPrecision = (value: number | null | undefined) => {
if (isNil(value)) return 0
const valueString = value.toString()
const dotPosition = valueString.indexOf('.')
let precision = 0
if (dotPosition !== -1) {
precision = valueString.length - dotPosition - 1
}
return precision
}
const ensurePrecision = (val: number, coefficient: 1 | -1 = 1) => {
if (!isNumber(val)) return data.currentValue
// Solve the accuracy problem of JS decimal calculation by converting the value to integer.
return toPrecision(val + props.step * coefficient)
}
const increase = () => {
if (inputNumberDisabled.value || maxDisabled.value) return
const value = props.modelValue || 0
const newVal = ensurePrecision(value)
setCurrentValue(newVal)
}
const decrease = () => {
if (inputNumberDisabled.value || minDisabled.value) return
const value = props.modelValue || 0
const newVal = ensurePrecision(value, -1)
setCurrentValue(newVal)
}
const verifyValue = (
value: number | string | null | undefined,
update?: boolean
): number | null | undefined => {
const { max, min, step, precision, stepStrictly, valueOnClear } = props
let newVal = Number(value)
if (isNil(value) || Number.isNaN(newVal)) {
return null
}
if (value === '') {
if (valueOnClear === null) {
return null
}
newVal = isString(valueOnClear)
? { min, max }[valueOnClear]
: valueOnClear
}
if (stepStrictly) {
newVal = Math.round(newVal / step) * step
}
if (!isUndefined(precision)) {
newVal = toPrecision(newVal, precision)
}
if (newVal > max || newVal < min) {
newVal = newVal > max ? max : min
update && emit('update:modelValue', newVal)
}
return newVal
}
const setCurrentValue = (value: number | string | null | undefined) => {
const oldVal = data.currentValue
const newVal = verifyValue(value)
if (oldVal === newVal) return
data.userInput = null
emit('update:modelValue', newVal)
emit('input', newVal)
emit('change', newVal, oldVal)
formItem?.validate?.('change').catch((err) => debugWarn(err))
data.currentValue = newVal
}
const handleInput = (value: string) => {
return (data.userInput = value)
}
const handleInputChange = (value: string) => {
const newVal = value !== '' ? Number(value) : ''
if ((isNumber(newVal) && !Number.isNaN(newVal)) || value === '') {
setCurrentValue(newVal)
}
data.userInput = null
}
const focus = () => {
input.value?.focus?.()
}
const blur = () => {
input.value?.blur?.()
}
const handleFocus = (event: MouseEvent | FocusEvent) => {
emit('focus', event)
}
const handleBlur = (event: MouseEvent | FocusEvent) => {
emit('blur', event)
formItem?.validate?.('blur').catch((err) => debugWarn(err))
}
watch(
() => props.modelValue,
(value) => {
data.currentValue = verifyValue(value, true)
data.userInput = null
},
{ immediate: true }
)
onMounted(() => {
const { min, max, modelValue } = props
const innerInput = input.value?.input as HTMLInputElement
innerInput.setAttribute('role', 'spinbutton')
if (Number.isFinite(max)) {
innerInput.setAttribute('aria-valuemax', String(max))
} else {
innerInput.removeAttribute('aria-valuemax')
}
if (Number.isFinite(min)) {
innerInput.setAttribute('aria-valuemin', String(min))
} else {
innerInput.removeAttribute('aria-valuemin')
}
innerInput.setAttribute('aria-valuenow', String(data.currentValue))
innerInput.setAttribute(
'aria-disabled',
String(inputNumberDisabled.value)
const numPrecision = computed(() => {
const stepPrecision = getPrecision(props.step)
if (!isUndefined(props.precision)) {
if (stepPrecision > props.precision) {
debugWarn(
'InputNumber',
'precision should not be less than the decimal places of step'
)
if (!isNumber(modelValue) && modelValue != null) {
let val: number | null = Number(modelValue)
if (Number.isNaN(val)) {
val = null
}
emit('update:modelValue', val)
}
})
onUpdated(() => {
const innerInput = input.value?.input
innerInput?.setAttribute('aria-valuenow', data.currentValue)
})
return {
t,
input,
displayValue,
handleInput,
handleInputChange,
controlsAtRight,
decrease,
increase,
inputNumberSize,
inputNumberDisabled,
maxDisabled,
minDisabled,
focus,
blur,
handleFocus,
handleBlur,
ns,
}
return props.precision
} else {
return Math.max(getPrecision(props.modelValue), stepPrecision)
}
})
const controlsAtRight = computed(() => {
return props.controls && props.controlsPosition === 'right'
})
const inputNumberSize = useSize()
const inputNumberDisabled = useDisabled()
const displayValue = computed(() => {
if (data.userInput !== null) {
return data.userInput
}
let currentValue: number | string | undefined | null = data.currentValue
if (isNil(currentValue)) return ''
if (isNumber(currentValue)) {
if (Number.isNaN(currentValue)) return ''
if (!isUndefined(props.precision)) {
currentValue = currentValue.toFixed(props.precision)
}
}
return currentValue
})
const toPrecision = (num: number, pre?: number) => {
if (isUndefined(pre)) pre = numPrecision.value
if (pre === 0) return Math.round(num)
let snum = String(num)
const pointPos = snum.indexOf('.')
if (pointPos === -1) return num
const nums = snum.replace('.', '').split('')
const datum = nums[pointPos + pre]
if (!datum) return num
const length = snum.length
if (snum.charAt(length - 1) === '5') {
snum = `${snum.slice(0, Math.max(0, length - 1))}6`
}
return Number.parseFloat(Number(snum).toFixed(pre))
}
const getPrecision = (value: number | null | undefined) => {
if (isNil(value)) return 0
const valueString = value.toString()
const dotPosition = valueString.indexOf('.')
let precision = 0
if (dotPosition !== -1) {
precision = valueString.length - dotPosition - 1
}
return precision
}
const ensurePrecision = (val: number, coefficient: 1 | -1 = 1) => {
if (!isNumber(val)) return data.currentValue
// Solve the accuracy problem of JS decimal calculation by converting the value to integer.
return toPrecision(val + props.step * coefficient)
}
const increase = () => {
if (inputNumberDisabled.value || maxDisabled.value) return
const value = props.modelValue || 0
const newVal = ensurePrecision(value)
setCurrentValue(newVal)
}
const decrease = () => {
if (inputNumberDisabled.value || minDisabled.value) return
const value = props.modelValue || 0
const newVal = ensurePrecision(value, -1)
setCurrentValue(newVal)
}
const verifyValue = (
value: number | string | null | undefined,
update?: boolean
): number | null | undefined => {
const { max, min, step, precision, stepStrictly, valueOnClear } = props
let newVal = Number(value)
if (isNil(value) || Number.isNaN(newVal)) {
return null
}
if (value === '') {
if (valueOnClear === null) {
return null
}
newVal = isString(valueOnClear) ? { min, max }[valueOnClear] : valueOnClear
}
if (stepStrictly) {
newVal = Math.round(newVal / step) * step
}
if (!isUndefined(precision)) {
newVal = toPrecision(newVal, precision)
}
if (newVal > max || newVal < min) {
newVal = newVal > max ? max : min
update && emit('update:modelValue', newVal)
}
return newVal
}
const setCurrentValue = (value: number | string | null | undefined) => {
const oldVal = data.currentValue
const newVal = verifyValue(value)
if (oldVal === newVal) return
data.userInput = null
emit('update:modelValue', newVal!)
emit('input', newVal)
emit('change', newVal!, oldVal!)
formItem?.validate?.('change').catch((err) => debugWarn(err))
data.currentValue = newVal
}
const handleInput = (value: string) => {
return (data.userInput = value)
}
const handleInputChange = (value: string) => {
const newVal = value !== '' ? Number(value) : ''
if ((isNumber(newVal) && !Number.isNaN(newVal)) || value === '') {
setCurrentValue(newVal)
}
data.userInput = null
}
const focus = () => {
input.value?.focus?.()
}
const blur = () => {
input.value?.blur?.()
}
const handleFocus = (event: MouseEvent | FocusEvent) => {
emit('focus', event)
}
const handleBlur = (event: MouseEvent | FocusEvent) => {
emit('blur', event)
formItem?.validate?.('blur').catch((err) => debugWarn(err))
}
watch(
() => props.modelValue,
(value) => {
data.currentValue = verifyValue(value, true)
data.userInput = null
},
{ immediate: true }
)
onMounted(() => {
const { min, max, modelValue } = props
const innerInput = input.value?.input as HTMLInputElement
innerInput.setAttribute('role', 'spinbutton')
if (Number.isFinite(max)) {
innerInput.setAttribute('aria-valuemax', String(max))
} else {
innerInput.removeAttribute('aria-valuemax')
}
if (Number.isFinite(min)) {
innerInput.setAttribute('aria-valuemin', String(min))
} else {
innerInput.removeAttribute('aria-valuemin')
}
innerInput.setAttribute('aria-valuenow', String(data.currentValue))
innerInput.setAttribute('aria-disabled', String(inputNumberDisabled.value))
if (!isNumber(modelValue) && modelValue != null) {
let val: number | null = Number(modelValue)
if (Number.isNaN(val)) {
val = null
}
emit('update:modelValue', val!)
}
})
onUpdated(() => {
const innerInput = input.value?.input
innerInput?.setAttribute('aria-valuenow', `${data.currentValue}`)
})
defineExpose({
/** @description get focus the input component */
focus,
/** @description remove focus the input component */
blur,
})
</script>