Search code examples
javascripttypescriptvuejs3cleave

Vue3 Cleave js not working if not using v-model.lazy


Currently i try to use cleavejs to formatting thousand separator my input number, it's show strange behavior when i input 1000.123 it show 1,000.12 which is correct format, but my v-model value is 1000.123 not 1000.12. other people suggesting using v-model.lazy but it only updated when i leave my focus from input text.

Is there any other way to solve this issue without using v-model.lazy ?

This is my current component

<script setup lang="ts">
import { ref, watch } from 'vue'
const props = withDefaults(defineProps<Props>(), {
  type: 'text',
  required: false,
  readonly: false,
  disabled: false
})
const inputValue = ref(props.modelValue)
const emit = defineEmits<{
  (e: 'update:modelValue', value: string | number): void
}>()

watch(
  () => props.modelValue,
  () => {
    inputValue.value = numeric.format(props.modelValue)
  },
  {
    immediate: true
  }
)

watch(
  inputValue,
  () => {
    emit('update:modelValue', parseFloat(inputValue.value.toString().replace(/(\d+),(?=\d+(\D|$))/g, '$1')))
  },
  {
    immediate: true
  }
)
</script>

<template>
 <input
        class="form-input"
        v-model.lazy="inputValue"
        v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand' }"
        :placeholder="props.placeholder"
        :required="props.required"
        :readonly="props.readonly"
        :disabled="props.disabled"
      />
</template>

I expect v-model return the same value as cleavejs did without using .lazy


Solution

  • First, you don't need use watch at all, and we won't emit on this setter because we wait Cleavejs event

    const inputValue = computed({
      set: (text: string) => {},
      get: () => new Intl.NumberFormat('en-US').format(props.modelValue)
    })
    

    Second, emit raw value data from Cleavejs onValueChanged event

    const emit = defineEmits<{
      (e: 'update:modelValue', value: number): void
    }>()
    
    const onValueChanged = (e: any) => {
      emit('update:modelValue', Number(e.target.rawValue))
    }
    

    Last, Update your input directive

    v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand', onValueChanged: onValueChanged }"
    

    Full Component example

    <script setup lang="ts">
    import { computed } from 'vue'
    
    export interface Props {
      modelValue: number
    }
    
    const props = withDefaults(defineProps<Props>(), {})
    
    const inputValue = computed({
      set: (text: string) => {},
      get: () => new Intl.NumberFormat('en-US').format(props.modelValue)
    })
    
    const emit = defineEmits<{
      (e: 'update:modelValue', value: number): void
    }>()
    
    const onValueChanged = (e: any) => {
      emit('update:modelValue', Number(e.target.rawValue))
    }
    </script>
    
    <template>
      <input
        v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand', onValueChanged: onValueChanged }"
        v-model="inputValue"
      />
    </template>