|
@@ -0,0 +1,108 @@
|
|
|
|
|
+<!--
|
|
|
|
|
+An input for numeric values
|
|
|
|
|
+-->
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <v-text-field
|
|
|
|
|
+ ref="input"
|
|
|
|
|
+ :modelValue.number="modelValue"
|
|
|
|
|
+ hide-details
|
|
|
|
|
+ single-line
|
|
|
|
|
+ :density="density"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ @update:modelValue="modelValue = keepInRange(cast($event)); emitUpdate()"
|
|
|
|
|
+ />
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+
|
|
|
|
|
+import {PropType} from "@vue/runtime-core";
|
|
|
|
|
+
|
|
|
|
|
+type Density = null | 'default' | 'comfortable' | 'compact';
|
|
|
|
|
+
|
|
|
|
|
+const props = defineProps({
|
|
|
|
|
+ modelValue: {
|
|
|
|
|
+ type: Number,
|
|
|
|
|
+ required: true
|
|
|
|
|
+ },
|
|
|
|
|
+ default: {
|
|
|
|
|
+ type: Number,
|
|
|
|
|
+ required: false,
|
|
|
|
|
+ default: 0
|
|
|
|
|
+ },
|
|
|
|
|
+ min: {
|
|
|
|
|
+ type: Number,
|
|
|
|
|
+ required: false,
|
|
|
|
|
+ default: null
|
|
|
|
|
+ },
|
|
|
|
|
+ max: {
|
|
|
|
|
+ type: Number,
|
|
|
|
|
+ required: false,
|
|
|
|
|
+ default: null
|
|
|
|
|
+ },
|
|
|
|
|
+ density: {
|
|
|
|
|
+ type: String as PropType<Density>,
|
|
|
|
|
+ required: false,
|
|
|
|
|
+ default: 'default'
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Reference to the v-text-field
|
|
|
|
|
+ */
|
|
|
|
|
+const input: Ref<any> = ref(null)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Cast the value to a number, or fallback on default value
|
|
|
|
|
+ * @param val
|
|
|
|
|
+ */
|
|
|
|
|
+const cast = (val: number | string): number => {
|
|
|
|
|
+ val = Number(val)
|
|
|
|
|
+ if (isNaN(val)) {
|
|
|
|
|
+ return props.default
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return val
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Ensure the value is between min and max values
|
|
|
|
|
+ * @param val
|
|
|
|
|
+ */
|
|
|
|
|
+const keepInRange = (val: number) => {
|
|
|
|
|
+ if (props.min !== null && props.max !== null && props.min >= props.max) {
|
|
|
|
|
+ console.warn('Number input: minimum value is greater than maximum value')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (props.min !== null && val < props.min) {
|
|
|
|
|
+ val = props.min
|
|
|
|
|
+ }
|
|
|
|
|
+ if (props.max !== null && val > props.max) {
|
|
|
|
|
+ val = props.max
|
|
|
|
|
+ }
|
|
|
|
|
+ return val
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const emit = defineEmits(['update:modelValue'])
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Emit the update event
|
|
|
|
|
+ */
|
|
|
|
|
+const emitUpdate = () => {
|
|
|
|
|
+ emit('update:modelValue', props.modelValue)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Setup min and max values at the input level
|
|
|
|
|
+ */
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ console.log(input.value)
|
|
|
|
|
+ const inputElement = input.value.$el.querySelector('input')
|
|
|
|
|
+ if (props.min !== null) {
|
|
|
|
|
+ inputElement.min = props.min
|
|
|
|
|
+ }
|
|
|
|
|
+ if (props.max !== null) {
|
|
|
|
|
+ inputElement.max = props.max
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|