primefaces / primevue

Next Generation Vue UI Component Library
https://primevue.org
MIT License
10.28k stars 1.21k forks source link

Component Name: InputNumber #6530

Open CasperJohansen opened 2 weeks ago

CasperJohansen commented 2 weeks ago

Describe the bug

When using locale "da-DK" (danish) and mode "currency", the suffix "Kr." is bugged. When pressing backspace in the input, standing at the end, the "k" gets duplicated, and number cannot be edited with more than one number.

Reproduction:

<script setup>
   import InputNumber from 'primevue/inputnumber';
   import { ref } from 'vue';
   const number = ref(1500)
</script>

<template>
            <InputNumber v-model="number" inputId="currency-germany" mode="currency" currency="dkk" locale="da-dk" /> <!-- not working -->
            <InputNumber v-model="number" inputId="currency-germany" mode="currency" currency="dkk" locale="de-de" /> <!-- working, but wrong suffix -->
</template>

video: https://github.com/user-attachments/assets/8dbdf3ab-2d5f-449a-899a-a48aabd27d22

Reproducer

https://stackblitz.com/edit/primevue-4-vite-issue-template-ksrojy?file=src%2FApp.vue

PrimeVue version

4.0.7

Vue version

4.x

Language

ES6

Build / Runtime

Vite

Browser(s)

No response

Steps to reproduce the behavior

Edit the number in both input fields.

Expected behavior

Number being editable

KumJungMin commented 2 weeks ago

~~default value of the currencyDisplay is symbol. if you change currencyDisplay to code, it will work :)~~

https://github.com/user-attachments/assets/67e5fb34-0a5a-40b8-b4c7-94f315ec3b6f

<InputNumber 
  v-model="value1" 
  inputId="currency-germany" 
  mode="currency" 
  currency-display="code"   <!-- this! -->
  currency="dkk" 
  locale="da-DK" 
/>
CasperJohansen commented 2 weeks ago

default value of the currencyDisplay is symbol. if you change currencyDisplay to code, it will work :)

2024-10-08.8.35.48.mov

<InputNumber 
  v-model="value1" 
  inputId="currency-germany" 
  mode="currency" 
  currency-display="code"   <!-- this! -->
  currency="dkk" 
  locale="da-DK" 
/>

Is that a temporary solution or the solution? The correct suffix is Kr. and currency-display="code" does not provide that.

KumJungMin commented 2 weeks ago

default value of the currencyDisplay is symbol. if you change currencyDisplay to code, it will work :) 2024-10-08.8.35.48.mov

<InputNumber 
  v-model="value1" 
  inputId="currency-germany" 
  mode="currency" 
  currency-display="code"   <!-- this! -->
  currency="dkk" 
  locale="da-DK" 
/>

Is that a temporary solution or the solution? The correct suffix is Kr. and currency-display="code" does not provide that.


The correct format is Kr.! I was mistaken. Thank you for pointing that out.

CCodam commented 1 week ago

[!IMPORTANT]

  • For Denmark (da-DK), the issue lies with part of the currency, in this case the period in "kr.", colliding with the grouping (thousands separator), which is also a period.
  • The issue is much worse for Sweden (sv-SE) and Norway (no-NO/nb-NO), who has a space as the grouping (thousands separator), which means all currencies are broken, as the currencies are prefixed/suffixed with a space.
  • There's probably an elegant way to fix this, but the best I've come up with, is replacing the period with '\u2024' and the space with '\u2005' for the currency.

[!TIP] Workaround - by supplying your own currency with prefix/suffix

Instead of defining mode and currency, we'll define prefix and suffix a long with minFractionDigits If you know the locale and currency beforehand, you can just go ahead and use the prefix/suffix

<InputNumber
  v-model="ndadkk"
  :minFractionDigits="2"
  locale="da-dk"
  :suffix="&#x2005;kr&#x2024;"
/>

If you have a handful of known locale and currencies, you could hardcode them within a function. If you need to be able to use any locale/currency combination, well let's take a look .. https://stackblitz.com/edit/primevue-4-vite-issue-template-yhdehd?file=src%2FApp.vue

First we need a function to tell us if the currency is a prefix or suffic.

function getCurrencyPrefixOrSuffix(locale, currency) {
  const formatter = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: currency,
  });
  const groups = formatter.format(1).split('1');
  const prefix = groups[0].replace('00', '').replace(',', '').replace('.', '');
  const suffix = groups[1].replace('00', '').replace(',', '').replace('.', '');
  if (prefix.length > suffix.length) {
    return 'prefix';
  }
  if (suffix.length > prefix.length) {
    return 'suffix';
  }
  return 'unknown';
}

Then we need a function to isolate the currency, and replace bad characters, like the period.

function getCurrencySymbol(locale, currency) {
  const numerals = [...new Intl.NumberFormat(locale, { useGrouping: false }).format(9876543210),].reverse();
  const regexNumeral = new RegExp(`[${numerals.join('')}]`, 'g');

  const fmGroup = new Intl.NumberFormat(locale, {
    useGrouping: true,
  });
  const groupChar = fmGroup.format(1000000).trim().replace(regexNumeral, '').charAt(0);
  const regexGroup = new RegExp(`[${groupChar}]`, 'g');

  const fmSymbol = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: currency,
    minimumFractionDigits: 0,
    maximumFractionDigits: 0,
  });

  return fmSymbol.format(1)
    .replace(/\s/g, '')
    .replace(regexNumeral, '')
    .replace('.', '\u2024') // Period cannot be a period, use Unicode 2024 (da-DK fix)
    .replace(regexGroup, '')
}

Then depending on it being a prefix/suffix, we need to be able to either return an empty string or the currency with a prefixed/suffixed space .. well not exactly a space, but the Unicode replacement of it.

function getCurrencySymbolPrefix(locale, currency) {
  // Space cannot be a space, use Unicode 2005 (sv-SE fix)
  return getCurrencyPrefixOrSuffix(locale, currency) === 'prefix'
    ? getCurrencySymbol(locale, currency) + '\u2005'
    : ''
}

function getCurrencySymbolSuffix(locale, currency) {
  // Space cannot be a space, use Unicode 2005 (sv-SE fix)
  return getCurrencyPrefixOrSuffix(locale, currency) === 'suffix'
    ? '\u2005' + getCurrencySymbol(locale, currency)
    : ''
}

Now let's use those functions with our InputNumber component

<InputNumber
  v-model="ndadkk"
  :minFractionDigits="2"
  locale="da-dk"
  :suffix="getCurrencySymbolSuffix('da-DK', 'DKK')"
  :prefix="getCurrencySymbolPrefix('da-DK', 'DKK')"
/>

That said, I'm partial to using the InputGroup instead, placing the currencies within the InputGroupAddon. I find that the UX is better, not having to deal with currencies within the input, but that is of course subjective. https://stackblitz.com/edit/primevue-4-vite-issue-template-esmqhv?file=src%2FApp.vue

CasperJohansen commented 1 week ago

Thank you for the thorough reply, @CCodam!