@@ -19,7 +19,9 @@ interface Symbols {
1919 group ?: string ,
2020 literals : RegExp ,
2121 numeral : RegExp ,
22- index : ( v : string ) => string
22+ numerals : string [ ] ,
23+ index : ( v : string ) => string ,
24+ noNumeralUnits : Array < { unit : string , value : number } >
2325}
2426
2527const CURRENCY_SIGN_REGEX = new RegExp ( '^.*\\(.*\\).*$' ) ;
@@ -130,13 +132,17 @@ class NumberParserImpl {
130132 }
131133
132134 parse ( value : string ) {
135+ let isGroupSymbolAllowed = this . formatter . resolvedOptions ( ) . useGrouping ;
133136 // to parse the number, we need to remove anything that isn't actually part of the number, for example we want '-10.40' not '-10.40 USD'
134137 let fullySanitizedValue = this . sanitize ( value ) ;
135138
136- if ( this . symbols . group ) {
137- // Remove group characters, and replace decimal points and numerals with ASCII values.
138- fullySanitizedValue = replaceAll ( fullySanitizedValue , this . symbols . group , '' ) ;
139+ // Return NaN if there is a group symbol but useGrouping is false
140+ if ( ! isGroupSymbolAllowed && this . symbols . group && fullySanitizedValue . includes ( this . symbols . group ) ) {
141+ return NaN ;
142+ } else if ( this . symbols . group ) {
143+ fullySanitizedValue = fullySanitizedValue . replaceAll ( this . symbols . group ! , '' ) ;
139144 }
145+
140146 if ( this . symbols . decimal ) {
141147 fullySanitizedValue = fullySanitizedValue . replace ( this . symbols . decimal ! , '.' ) ;
142148 }
@@ -189,12 +195,17 @@ class NumberParserImpl {
189195 if ( this . options . currencySign === 'accounting' && CURRENCY_SIGN_REGEX . test ( value ) ) {
190196 newValue = - 1 * newValue ;
191197 }
192-
193198 return newValue ;
194199 }
195200
196201 sanitize ( value : string ) {
197- // Remove literals and whitespace, which are allowed anywhere in the string
202+ let isGroupSymbolAllowed = this . formatter . resolvedOptions ( ) . useGrouping ;
203+ // If the value is only a unit and it matches one of the formatted numbers where the value is part of the unit and doesn't have any numerals, then
204+ // return the known value for that case.
205+ if ( this . symbols . noNumeralUnits . length > 0 && this . symbols . noNumeralUnits . find ( obj => obj . unit === value ) ) {
206+ return this . symbols . noNumeralUnits . find ( obj => obj . unit === value ) ! . value . toString ( ) ;
207+ }
208+
198209 value = value . replace ( this . symbols . literals , '' ) ;
199210
200211 // Replace the ASCII minus sign with the minus sign used in the current locale
@@ -207,23 +218,23 @@ class NumberParserImpl {
207218 // instead they use the , (44) character or apparently the (1548) character.
208219 if ( this . options . numberingSystem === 'arab' ) {
209220 if ( this . symbols . decimal ) {
210- value = value . replace ( ',' , this . symbols . decimal ) ;
211- value = value . replace ( String . fromCharCode ( 1548 ) , this . symbols . decimal ) ;
221+ value = replaceAll ( value , ',' , this . symbols . decimal ) ;
222+ value = replaceAll ( value , String . fromCharCode ( 1548 ) , this . symbols . decimal ) ;
212223 }
213- if ( this . symbols . group ) {
224+ if ( this . symbols . group && isGroupSymbolAllowed ) {
214225 value = replaceAll ( value , '.' , this . symbols . group ) ;
215226 }
216227 }
217228
218229 // In some locale styles, such as swiss currency, the group character can be a special single quote
219230 // that keyboards don't typically have. This expands the character to include the easier to type single quote.
220- if ( this . symbols . group === '’' && value . includes ( "'" ) ) {
231+ if ( this . symbols . group === '’' && value . includes ( "'" ) && isGroupSymbolAllowed ) {
221232 value = replaceAll ( value , "'" , this . symbols . group ) ;
222233 }
223234
224235 // fr-FR group character is narrow non-breaking space, char code 8239 (U+202F), but that's not a key on the french keyboard,
225236 // so allow space and non-breaking space as a group char as well
226- if ( this . options . locale === 'fr-FR' && this . symbols . group ) {
237+ if ( this . options . locale === 'fr-FR' && this . symbols . group && isGroupSymbolAllowed ) {
227238 value = replaceAll ( value , ' ' , this . symbols . group ) ;
228239 value = replaceAll ( value , / \u00A0 / g, this . symbols . group ) ;
229240 }
@@ -232,6 +243,7 @@ class NumberParserImpl {
232243 }
233244
234245 isValidPartialNumber ( value : string , minValue : number = - Infinity , maxValue : number = Infinity ) : boolean {
246+ let isGroupSymbolAllowed = this . formatter . resolvedOptions ( ) . useGrouping ;
235247 value = this . sanitize ( value ) ;
236248
237249 // Remove minus or plus sign, which must be at the start of the string.
@@ -241,18 +253,13 @@ class NumberParserImpl {
241253 value = value . slice ( this . symbols . plusSign . length ) ;
242254 }
243255
244- // Numbers cannot start with a group separator
245- if ( this . symbols . group && value . startsWith ( this . symbols . group ) ) {
246- return false ;
247- }
248-
249256 // Numbers that can't have any decimal values fail if a decimal character is typed
250257 if ( this . symbols . decimal && value . indexOf ( this . symbols . decimal ) > - 1 && this . options . maximumFractionDigits === 0 ) {
251258 return false ;
252259 }
253260
254261 // Remove numerals, groups, and decimals
255- if ( this . symbols . group ) {
262+ if ( this . symbols . group && isGroupSymbolAllowed ) {
256263 value = replaceAll ( value , this . symbols . group , '' ) ;
257264 }
258265 value = value . replace ( this . symbols . numeral , '' ) ;
@@ -282,12 +289,21 @@ function getSymbols(locale: string, formatter: Intl.NumberFormat, intlOptions: I
282289 maximumSignificantDigits : 21 ,
283290 roundingIncrement : 1 ,
284291 roundingPriority : 'auto' ,
285- roundingMode : 'halfExpand'
292+ roundingMode : 'halfExpand' ,
293+ useGrouping : true
286294 } ) ;
287295 // Note: some locale's don't add a group symbol until there is a ten thousands place
288296 let allParts = symbolFormatter . formatToParts ( - 10000.111 ) ;
289297 let posAllParts = symbolFormatter . formatToParts ( 10000.111 ) ;
290298 let pluralParts = pluralNumbers . map ( n => symbolFormatter . formatToParts ( n ) ) ;
299+ // if the plural parts include a unit but no integer or fraction, then we need to add the unit to the special set
300+ let noNumeralUnits = pluralParts . map ( ( p , i ) => {
301+ let unit = p . find ( p => p . type === 'unit' ) ;
302+ if ( unit && ! p . some ( p => p . type === 'integer' || p . type === 'fraction' ) ) {
303+ return { unit : unit . value , value : pluralNumbers [ i ] } ;
304+ }
305+ return null ;
306+ } ) . filter ( p => ! ! p ) ;
291307
292308 let minusSign = allParts . find ( p => p . type === 'minusSign' ) ?. value ?? '-' ;
293309 let plusSign = posAllParts . find ( p => p . type === 'plusSign' ) ?. value ;
@@ -311,17 +327,18 @@ function getSymbols(locale: string, formatter: Intl.NumberFormat, intlOptions: I
311327 let pluralPartsLiterals = pluralParts . flatMap ( p => p . filter ( p => ! nonLiteralParts . has ( p . type ) ) . map ( p => escapeRegex ( p . value ) ) ) ;
312328 let sortedLiterals = [ ...new Set ( [ ...allPartsLiterals , ...pluralPartsLiterals ] ) ] . sort ( ( a , b ) => b . length - a . length ) ;
313329
330+ // Match both whitespace and formatting characters
314331 let literals = sortedLiterals . length === 0 ?
315- new RegExp ( '[ \\p{White_Space}] ' , 'gu' ) :
316- new RegExp ( `${ sortedLiterals . join ( '|' ) } |[ \\p{White_Space}] ` , 'gu' ) ;
332+ new RegExp ( '\\p{White_Space}|\\p{Cf} ' , 'gu' ) :
333+ new RegExp ( `${ sortedLiterals . join ( '|' ) } |\\p{White_Space}|\\p{Cf} ` , 'gu' ) ;
317334
318335 // These are for replacing non-latn characters with the latn equivalent
319336 let numerals = [ ...new Intl . NumberFormat ( intlOptions . locale , { useGrouping : false } ) . format ( 9876543210 ) ] . reverse ( ) ;
320337 let indexes = new Map ( numerals . map ( ( d , i ) => [ d , i ] ) ) ;
321338 let numeral = new RegExp ( `[${ numerals . join ( '' ) } ]` , 'g' ) ;
322339 let index = d => String ( indexes . get ( d ) ) ;
323340
324- return { minusSign, plusSign, decimal, group, literals, numeral, index} ;
341+ return { minusSign, plusSign, decimal, group, literals, numeral, numerals , index, noNumeralUnits } ;
325342}
326343
327344function replaceAll ( str : string , find : string | RegExp , replace : string ) {
0 commit comments