Skip to content

Commit f86672a

Browse files
author
Michael Jordan
committed
Refactor logic into NumberParser's sanitize method.
1 parent 4cc26f4 commit f86672a

File tree

3 files changed

+29
-180
lines changed

3 files changed

+29
-180
lines changed

packages/@internationalized/number/src/NumberParser.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ interface Symbols {
2424

2525
const CURRENCY_SIGN_REGEX = new RegExp('^.*\\(.*\\).*$');
2626
const NUMBERING_SYSTEMS = ['latn', 'arab', 'hanidec'];
27+
const GROUPING_SYMBOLS_REGEX = /[,٬ .\u202f\u00a0]/g;
28+
const DECIMAL_SYMBOLS = '[.\u066b,]';
2729

2830
/**
2931
* A NumberParser can be used to perform locale-aware parsing of numbers from Unicode strings,
@@ -180,6 +182,31 @@ class NumberParserImpl {
180182
}
181183

182184
sanitize(value: string) {
185+
// Replace group and decimal symbols with the current locale's symbols
186+
let groupSymbolMatch = value.match(GROUPING_SYMBOLS_REGEX);
187+
let groupSymbol = groupSymbolMatch?.[0];
188+
let decimalSymbol = value.match(new RegExp(groupSymbol ? DECIMAL_SYMBOLS.replace(groupSymbol, '') : DECIMAL_SYMBOLS, 'g'))?.[0];
189+
if (
190+
// If we have both a group and decimal symbol,
191+
groupSymbol && decimalSymbol &&
192+
// and they're not the same as the current locale's symbols, then we need to replace them.
193+
(groupSymbol !== this.symbols.group || decimalSymbol !== this.symbols.decimal)
194+
&&
195+
// However, the decimal or group symbol can sometimes appear within a literal,
196+
// for example in "bg-BG" the currency symbol is "щ.д.",
197+
// so we need to check if it's actually a decimal or group.
198+
!(
199+
this.symbols.literals.test(value) &&
200+
(
201+
this.symbols.literals.toString().includes(decimalSymbol) ||
202+
this.symbols.literals.toString().includes(groupSymbol)
203+
)
204+
)
205+
) {
206+
value = replaceAll(value, groupSymbol, '');
207+
value = replaceAll(value, decimalSymbol, this.symbols.decimal);
208+
}
209+
183210
// Remove literals and whitespace, which are allowed anywhere in the string
184211
value = value.replace(this.symbols.literals, '');
185212

packages/@react-stately/numberfield/src/useNumberFieldState.ts

Lines changed: 1 addition & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -16,150 +16,6 @@ import {NumberFieldProps} from '@react-types/numberfield';
1616
import {NumberFormatter, NumberParser} from '@internationalized/number';
1717
import {useCallback, useMemo, useState} from 'react';
1818

19-
20-
let supportedLocales = new Map<string, {groupSeparator: string, decimalSeparator: string}>([
21-
[
22-
'ar-AE', // Arabic (United Arab Emirates)
23-
{groupSeparator: ',', decimalSeparator: '.'}
24-
],
25-
[
26-
'bg-BG', // Bulgarian (Bulgaria)
27-
{groupSeparator: '', decimalSeparator: ','}
28-
],
29-
[
30-
'zh-CN', // Chinese (Simplified)
31-
{groupSeparator: ',', decimalSeparator: '.'}
32-
],
33-
[
34-
'zh-TW', // Chinese (Traditional)
35-
{groupSeparator: ',', decimalSeparator: '.'}
36-
],
37-
[
38-
'hr-HR', // Croatian (Croatia)
39-
{groupSeparator: '.', decimalSeparator: ','}
40-
],
41-
[
42-
'cs-CZ', // Czech (Czech Republic)
43-
{groupSeparator: ' ', decimalSeparator: ','}
44-
],
45-
[
46-
'da-DK', // Danish (Denmark)
47-
{groupSeparator: '.', decimalSeparator: ','}
48-
],
49-
[
50-
'nl-NL', // Dutch (Netherlands)
51-
{groupSeparator: '.', decimalSeparator: ','}
52-
],
53-
[
54-
'en-GB', // English (Great Britain)
55-
{groupSeparator: ',', decimalSeparator: '.'}
56-
],
57-
[
58-
'en-US', // English (United States)
59-
{groupSeparator: ',', decimalSeparator: '.'}
60-
],
61-
[
62-
'et-EE', // Estonian (Estonia)
63-
{groupSeparator: '', decimalSeparator: ','}
64-
],
65-
[
66-
'fi-FI', // Finnish (Finland)
67-
{groupSeparator: ' ', decimalSeparator: ','}
68-
],
69-
[
70-
'fr-CA', // French (Canada)
71-
{groupSeparator: ' ', decimalSeparator: ','}
72-
],
73-
[
74-
'fr-FR', // French (France)
75-
{groupSeparator: ' ', decimalSeparator: ','}
76-
],
77-
[
78-
'de-DE', // German (Germany)
79-
{groupSeparator: '.', decimalSeparator: ','}
80-
],
81-
[
82-
'el-GR', // Greek (Greece)
83-
{groupSeparator: '.', decimalSeparator: ','}
84-
],
85-
[
86-
'he-IL', // Hebrew (Israel)
87-
{groupSeparator: ',', decimalSeparator: '.'}
88-
],
89-
[
90-
'hu-HU', // Hungarian (Hungary)
91-
{groupSeparator: ' ', decimalSeparator: ','}
92-
],
93-
[
94-
'it-IT', // Italian (Italy)
95-
{groupSeparator: '.', decimalSeparator: ','}
96-
],
97-
[
98-
'ja-JP', // Japanese (Japan)
99-
{groupSeparator: ',', decimalSeparator: '.'}
100-
],
101-
[
102-
'ko-KR', // Korean (Korea)
103-
{groupSeparator: ',', decimalSeparator: '.'}
104-
],
105-
[
106-
'lv-LV', // Latvian (Latvia)
107-
{groupSeparator: '', decimalSeparator: ','}
108-
],
109-
[
110-
'lt-LT', // Lithuanian (Lithuania)
111-
{groupSeparator: ' ', decimalSeparator: ','}
112-
],
113-
[
114-
'no-NO', // Norwegian (Norway)
115-
{groupSeparator: ' ', decimalSeparator: ','}
116-
],
117-
[
118-
'pl-PL', // Polish (Poland)
119-
{groupSeparator: '', decimalSeparator: ','}
120-
],
121-
[
122-
'pt-BR', // Portuguese (Brazil)
123-
{groupSeparator: '.', decimalSeparator: ','}
124-
],
125-
[
126-
'ro-RO', // Romanian (Romania)
127-
{groupSeparator: '.', decimalSeparator: ','}
128-
],
129-
[
130-
'ru-RU', // Russian (Russia)
131-
{groupSeparator: ' ', decimalSeparator: ','}
132-
],
133-
[
134-
'sr-RS', // Serbian (Serbia)
135-
{groupSeparator: '.', decimalSeparator: ','}
136-
],
137-
[
138-
'sk-SK', // Slovakian (Slovakia)
139-
{groupSeparator: ' ', decimalSeparator: ','}
140-
],
141-
[
142-
'sl-SI', // Slovenian (Slovenia)
143-
{groupSeparator: '.', decimalSeparator: ','}
144-
],
145-
[
146-
'es-ES', // Spanish (Spain)
147-
{groupSeparator: '', decimalSeparator: ','}
148-
],
149-
[
150-
'sv-SE', // Swedish (Sweden)
151-
{groupSeparator: ' ', decimalSeparator: ','}
152-
],
153-
[
154-
'tr-TR', // Turkish (Turkey)
155-
{groupSeparator: '.', decimalSeparator: ','}
156-
],
157-
[
158-
'uk-UA', // Ukrainian (Ukraine)
159-
{groupSeparator: ' ', decimalSeparator: ','}
160-
]
161-
]);
162-
16319
export interface NumberFieldState extends FormValidationState {
16420
/**
16521
* The current text value of the input. Updated as the user types,
@@ -410,41 +266,7 @@ export function useNumberFieldState(
410266

411267
let validate = (value: string) => numberParser.isValidPartialNumber(value, minValue, maxValue);
412268

413-
let parseValueInAnySupportedLocale = (value: string) => {
414-
let currentLocaleCode = [...supportedLocales].find(([localeCode]) => localeCode === locale) || ['en-US', {groupSeparator: ',', decimalSeparator: '.'}];
415-
let localesWithDifferentSeparators = [...supportedLocales].filter(([, separators]) => separators.groupSeparator !== currentLocaleCode?.[1].groupSeparator && separators.decimalSeparator !== currentLocaleCode?.[1].decimalSeparator);
416-
let locales = new Map(
417-
[
418-
currentLocaleCode,
419-
...localesWithDifferentSeparators
420-
]
421-
);
422-
423-
let _parsedValue = NaN;
424-
for (let [localeCode, separators] of locales) {
425-
let _numberParser = new NumberParser(localeCode, formatOptions);
426-
if (
427-
(
428-
value.includes(separators.groupSeparator) ||
429-
value.includes(separators.decimalSeparator)
430-
) &&
431-
value.lastIndexOf(separators.groupSeparator) > value.lastIndexOf(separators.decimalSeparator)
432-
) {
433-
if (value.lastIndexOf(separators.decimalSeparator) === -1) {
434-
let pv = _numberParser.parse(value.replaceAll(separators.groupSeparator, ''));
435-
if (!isNaN(pv) && parseFloat(value.replaceAll(separators.groupSeparator, '')) === pv) {
436-
return pv;
437-
}
438-
}
439-
continue;
440-
}
441-
_parsedValue = _numberParser.parse(value.replaceAll(separators.groupSeparator, ''));
442-
if (!isNaN(_parsedValue)) {
443-
return _parsedValue;
444-
}
445-
}
446-
return _parsedValue;
447-
};
269+
let parseValueInAnySupportedLocale = (value: string) => numberParser.parse(value);
448270

449271
return {
450272
...validation,

packages/react-aria-components/test/NumberField.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ describe('NumberField', () => {
183183
expect(numberfield).not.toHaveAttribute('data-invalid');
184184
});
185185

186-
it('supports pasting value in another format', async () => {
186+
it('supports pasting value in another numbering system', async () => {
187187
let {getByRole, rerender} = render(<TestNumberField />);
188188
let input = getByRole('textbox');
189189
act(() => {

0 commit comments

Comments
 (0)