Skip to content

Commit

Permalink
fix: improve detection of decimal/grouping separator
Browse files Browse the repository at this point in the history
  • Loading branch information
dm4t2 committed Mar 6, 2022
1 parent 16f191f commit 291d841
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 15 deletions.
25 changes: 14 additions & 11 deletions src/numberFormat.ts
@@ -1,4 +1,4 @@
import { count, escapeRegExp, substringBefore } from './utils'
import { escapeRegExp, substringBefore } from './utils'
import { CurrencyDisplay, NumberFormatStyle, NumberInputOptions, UnitDisplay } from './api'
import NumberFormatOptions = Intl.NumberFormatOptions

Expand All @@ -14,7 +14,7 @@ export class NumberFormat {
unitDisplay?: UnitDisplay
digits: string[]
decimalSymbol: string | undefined
groupingSymbol: string
groupingSymbol: string | undefined
minusSymbol: string
minimumFractionDigits: number
maximumFractionDigits: number
Expand All @@ -32,7 +32,7 @@ export class NumberFormat {
style,
minimumFractionDigits: style !== NumberFormatStyle.Currency ? 1 : undefined
})
const ps = numberFormat.format(style === NumberFormatStyle.Percent ? 1234.56 : 123456)
const formatParts = numberFormat.formatToParts(style === NumberFormatStyle.Percent ? 1234.56 : 123456)

this.locale = locale
this.style = style
Expand All @@ -41,26 +41,29 @@ export class NumberFormat {
this.unit = unit
this.unitDisplay = unitDisplay
this.digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => i.toLocaleString(locale))
this.decimalSymbol = count(ps, this.digits[0]) ? ps.substr(ps.indexOf(this.digits[6]) + 1, 1) : undefined
this.groupingSymbol = ps.substr(ps.indexOf(this.digits[3]) + 1, 1)
this.decimalSymbol = formatParts.find(({ type }) => type === 'decimal')?.value
this.groupingSymbol = formatParts.find(({ type }) => type === 'group')?.value
this.minusSymbol = substringBefore(Number(-1).toLocaleString(locale), this.digits[1])

if (this.decimalSymbol === undefined) {
this.minimumFractionDigits = this.maximumFractionDigits = 0
} else if (typeof precision === 'number') {
this.minimumFractionDigits = this.maximumFractionDigits = precision
} else if (typeof precision === 'object') {
this.minimumFractionDigits = precision.min || 0
this.maximumFractionDigits = precision.max !== undefined ? precision.max : 15
this.minimumFractionDigits = precision.min ?? 0
this.maximumFractionDigits = precision.max ?? 15
} else {
const { maximumFractionDigits, minimumFractionDigits } = new Intl.NumberFormat(locale, { currency, unit, style }).resolvedOptions()
this.minimumFractionDigits = minimumFractionDigits
this.maximumFractionDigits = maximumFractionDigits
}

this.prefix = substringBefore(ps, this.digits[1])
this.negativePrefix = substringBefore(numberFormat.format(-1), this.digits[1])
this.suffix = ps.substring(ps.lastIndexOf(this.decimalSymbol ? this.digits[0] : this.digits[6]) + 1)
const getPrefix = (str: string) => substringBefore(str, this.digits[1])
const getSuffix = (str: string) => str.substring(str.lastIndexOf(this.decimalSymbol ? this.digits[0] : this.digits[1]) + 1)

this.prefix = getPrefix(numberFormat.format(1))
this.suffix = getSuffix(numberFormat.format(1))
this.negativePrefix = getPrefix(numberFormat.format(-1))
}

parse(str: string | null): number | null {
Expand Down Expand Up @@ -137,7 +140,7 @@ export class NumberFormat {
}

stripGroupingSeparator(str: string): string {
return str.replace(new RegExp(escapeRegExp(this.groupingSymbol), 'g'), '')
return this.groupingSymbol !== undefined ? str.replace(new RegExp(escapeRegExp(this.groupingSymbol), 'g'), '') : str
}

stripMinusSymbol(str: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/numberInput.ts
Expand Up @@ -253,7 +253,7 @@ export class NumberInput {
if (this.options.hidePrefixOrSuffixOnFocus) {
result -= prefix.length
}
if (this.options.hideGroupingSeparatorOnFocus) {
if (this.options.hideGroupingSeparatorOnFocus && groupingSymbol !== undefined) {
result -= count(value.substring(0, selectionStart), groupingSymbol)
}
return result
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/__snapshots__/numberFormat.spec.ts.snap
Expand Up @@ -31,6 +31,37 @@ NumberFormat {
}
`;

exports[`NumberFormat constructing number formats currency should work for the respective locale and currency: bg-BG_BGN 1`] = `
NumberFormat {
"currency": "BGN",
"currencyDisplay": undefined,
"decimalSymbol": ",",
"digits": Array [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
],
"groupingSymbol": undefined,
"locale": "bg-BG",
"maximumFractionDigits": 2,
"minimumFractionDigits": 2,
"minusSymbol": "-",
"negativePrefix": "-",
"prefix": "",
"style": "currency",
"suffix": " лв.",
"unit": undefined,
"unitDisplay": undefined,
}
`;

exports[`NumberFormat constructing number formats currency should work for the respective locale and currency: de-CH_EUR 1`] = `
NumberFormat {
"currency": "EUR",
Expand Down
18 changes: 15 additions & 3 deletions tests/unit/numberFormat.spec.ts
Expand Up @@ -116,6 +116,14 @@ describe('NumberFormat', () => {
currency: 'IRR'
})
).toMatchSnapshot('fa-IR_IRR')

expect(
new NumberFormat({
formatStyle: NumberFormatStyle.Currency,
locale: 'bg-BG',
currency: 'BGN'
})
).toMatchSnapshot('bg-BG_BGN')
})

describe('custom precision', () => {
Expand All @@ -124,9 +132,13 @@ describe('NumberFormat', () => {
expect.objectContaining({ minimumFractionDigits: 0, maximumFractionDigits: 0 })
)

expect(new NumberFormat({ formatStyle: NumberFormatStyle.Currency, currency: 'EUR', precision: { min: 2, max: 5 } })).toEqual(
expect.objectContaining({ minimumFractionDigits: 2, maximumFractionDigits: 5 })
)
expect(
new NumberFormat({
formatStyle: NumberFormatStyle.Currency,
currency: 'EUR',
precision: { min: 2, max: 5 }
})
).toEqual(expect.objectContaining({ minimumFractionDigits: 2, maximumFractionDigits: 5 }))
})

it('should ignore the custom precision if the locale does not support decimal digits', () => {
Expand Down

0 comments on commit 291d841

Please sign in to comment.