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