DecimalInput.vue (1988B)
1 <template> 2 <input ref="inputElement" type="number" step="any" required @blur="onblur" v-model="stringValue"> 3 </template> 4 5 <script setup lang="ts"> 6 import { ref, watch } from 'vue'; 7 import { formatNumber } from '@/core/units'; 8 9 /** 10 * The component value 11 */ 12 const model = defineModel({ 13 type: Number, 14 default: 0, 15 }); 16 17 const props = defineProps({ 18 /** 19 * The number of digits to show before the decimal point 20 */ 21 padding: { 22 type: Number, 23 default: 0, 24 validator(value: number) { 25 return value >= 0; 26 }, 27 }, 28 29 /** 30 * The number of digits to show after the decimal point 31 */ 32 digits: { 33 type: Number, 34 default: 1, 35 validator(value: number) { 36 return value > 0; 37 }, 38 }, 39 }); 40 41 /** 42 * The internal float value 43 */ 44 const internalValue = ref(model.value); 45 46 /** 47 * The raw string value (empty if input is currently invalid) 48 */ 49 const stringValue = ref(format(model.value)); 50 51 /** 52 * The input element 53 */ 54 const inputElement = ref(); 55 56 /* 57 * Update the internal value when the component value changes 58 */ 59 watch(model, (newValue) => { 60 if (Math.abs(newValue - internalValue.value) > 0.00001) { 61 internalValue.value = newValue; 62 stringValue.value = format(internalValue.value); 63 } 64 }); 65 66 /** 67 * Update the internal value when the raw string value changes 68 */ 69 watch(stringValue, (newValue) => { 70 if (inputElement.value.validity.valid) { 71 internalValue.value = Number(newValue); 72 model.value = internalValue.value; 73 } 74 }); 75 76 /** 77 * Reformat display value if not invalid 78 */ 79 function onblur() { 80 if (inputElement.value.validity.valid) { 81 stringValue.value = format(internalValue.value); 82 } 83 } 84 85 /** 86 * Format a decimal as a string 87 * @param {number} value The decimal 88 * @returns {string} The formated string 89 */ 90 function format(value: number): string { 91 return formatNumber(value, props.padding, props.digits, true); 92 } 93 </script> 94 95 <style scoped> 96 input { 97 width: 5em; /* can fit 999.99 comfortably */ 98 text-align: center; 99 } 100 </style>