vue.jsvuetify.jsmaska

Maska number format loses fraction digits when masking decimals


I am trying to add a formatted field to my vue app by using maska. Until now it works fine if my value do not have faction digits (for instance 26767).

But if I get a value with faction like 26717.01 i get the output 2.671.701 (i expect 26.717,01)

Can someone point me to the right direction?

<script setup lang="ts">
import { vMaska } from "maska/vue";


const emit = defineEmits([ 'update:modelValue']);

let props = defineProps({
  amount: {
    type: Number
  },
})

const options = {
  number:{
    fraction: 2,
    locale: 'de-DE'
  },
};

</script>
<script lang="ts">
import {defineComponent} from "vue";

export default defineComponent({
  name: 'AmountField',
});

</script>

<template>
  <v-text-field
    variant="outlined"
    :clearable=false
    density="compact"
    auto-grow
    rows="1"
    :model-value="props.amount"
    v-maska="options"
    v-bind="$attrs"
    @maska="(event) => {console.log(event.detail.unmasked); emit('update:modelValue',Number(event?.detail?.unmasked))}"
  />
</template>

<style scoped>

</style>

my testcases looks like:

import AmountField from "@/components/elements/AmountField.vue";

describe('xxxx', (): void => {

  it('no fraction', () => {

    cy.mount(AmountField, {
      propsData: {
        amount: 26717,
        editable: true,
        id: "amountField"
      }
    } as any).then(({wrapper, component}) =>
    {
      cy.get("#amountField").find('input').should('have.value', '26.717').should('not.be.disabled')
      cy.get("#amountField").find('input').type('{selectAll}1234,56').then(() => {
        expect((wrapper).emitted('update:modelValue')).to.have.length;
        expect((wrapper).emitted('update:modelValue')[7][0]).to.equal(1234.56)
      })

    });
  })

  it('with fraction', () => {

    cy.mount(AmountField, {
      propsData: {
        betrag: 26717.01,
        editable: true,
        disabled: true,
        id: "amountField"
      }
    } as any);
    cy.get("#amountField").find('input').should('have.value', '26.717,01')
  })

})

Solution

  • This is an issue in Maska (v3.0) with numeric input and a format that uses a dot as grouping separator (like the german 1.000.000,99). Even when input is not masked, Maska starts by removing the grouping separator. So when input is a float and grouping separator is a dot, the decimal point will be removed. 26717.01 becomes 2671701, which is formatted as 2.671.701.

    I assume this is not intended behavior. As a workaround, you can start with an already formatted value:

    <template>
    
      <v-text-field
        v-model="maskedValue"
        v-maska:amount.unmasked="options"
      />
    
    </template>
    <script setup>
    
    const amount = ref(26717.01)
    const options = {...}
    const preformattedValue = new Intl.NumberFormat(options.number.locale).format(amount.value)
    const maskedValue = ref(preformattedValue)
    
    </script>
    

    If amount can be changed from outside, you probably have to use a watcher to update the formatted value.

    const { createApp, ref } = Vue;
    const { createVuetify } = Vuetify
    const vuetify = createVuetify()
    const app = {
      directives: {maska: window.Maska.vMaska},
      template: `
          <v-app>
            <v-main>
              <v-container>
    
                <v-text-field
                  v-model="maskedValue"
                  v-maska:amount.unmasked="options"
                />
                
                <pre>amount: {{amount}}</pre>
              
              </v-container>
            </v-main>
          </v-app>
      `,
      setup(){
        const options = {
            number: {
              fraction: 2,
              locale: 'de',
            },
            reversed: true,
          }
        const formatter = new Intl.NumberFormat(options.number.locale)
        const amount = ref(26717.01)
    
        return {
          amount,
          maskedValue: ref(formatter.format(amount.value)),
          options,
        }
      }
    
    }
    const a = createApp(app).use(vuetify).mount('#app')
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.css" />
    <link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet">
    <div id="app"></div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.js"></script>
    <script src="https://unpkg.com/maska@3.0.0/dist/cdn/vue.js"></script>