@@ -592,8 +592,8 @@ export const getInlineStartProperty = (
592592} ;
593593
594594/**
595- * Parses a decimal input string, accepting both comma and dot as the decimal separator .
596- * Given the locale, it discovers the locale's separators and integers using a reference value .
595+ * Parses a decimal input string using the given formatter's locale separators .
596+ * Grouping is removed only when formatToParts reports a group separator .
597597 *
598598 * @param {string } value - The input string to parse
599599 * @param {Intl.NumberFormat } formatter - The Intl.NumberFormat instance to use for formatting
@@ -605,7 +605,9 @@ export const parseLocalizedDecimal = (value: string, formatter: Intl.NumberForma
605605 }
606606
607607 const parts = formatter . formatToParts ( 12345.6 ) ;
608- const groupSymbol = parts . find ( ( p ) => p . type === 'group' ) ?. value || ',' ;
608+ // Only strip grouping when the formatter actually reports a group separator.
609+ // Never fall back to ',' — that can match comma-decimal locales and eat the decimal.
610+ const groupSymbol = parts . find ( ( p ) => p . type === 'group' ) ?. value ;
609611 const decimalSymbol = parts . find ( ( p ) => p . type === 'decimal' ) ?. value || '.' ;
610612
611613 // in case of non-Arabic numerals
@@ -614,22 +616,59 @@ export const parseLocalizedDecimal = (value: string, formatter: Intl.NumberForma
614616 . filter ( ( p ) => p . type === 'integer' )
615617 . map ( ( p ) => p . value )
616618 . join ( '' ) ;
617- let normalizedString = value ;
619+ let normalizedString = value . trim ( ) ;
618620 if ( localDigits . length === 10 && localDigits !== '1234567890' ) {
619621 const standardDigits = [ '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '0' ] ;
620622 const digitMap = new Map ( ) ;
621623 [ ...localDigits ] . forEach ( ( char , idx ) => digitMap . set ( char , standardDigits [ idx ] ) ) ;
622- normalizedString = [ ...value ] . map ( ( char ) => digitMap . get ( char ) || char ) . join ( '' ) ;
624+ normalizedString = [ ...normalizedString ] . map ( ( char ) => digitMap . get ( char ) || char ) . join ( '' ) ;
623625 }
624626
625- const escapeRegex = ( str : string ) => str . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
627+ // Reject '.' / ',' when they are neither this locale's decimal nor its reported group separator
628+ // (e.g. mixed-separator de-DE input like "1,234.56").
629+ for ( const sep of [ '.' , ',' ] as const ) {
630+ if ( sep !== decimalSymbol && sep !== groupSymbol && normalizedString . includes ( sep ) ) {
631+ return NaN ;
632+ }
633+ }
634+
635+ const decimalIndex = normalizedString . indexOf ( decimalSymbol ) ;
636+ if ( decimalIndex !== - 1 && normalizedString . indexOf ( decimalSymbol , decimalIndex + decimalSymbol . length ) !== - 1 ) {
637+ return NaN ;
638+ }
626639
627- const cleaned = normalizedString
628- . replace ( new RegExp ( escapeRegex ( groupSymbol ) , 'g' ) , '' )
629- . replace ( new RegExp ( escapeRegex ( decimalSymbol ) , 'g' ) , '.' )
630- . replace ( / [ ^ 0 - 9 . - ] / g, '' ) ;
640+ let intPart = decimalIndex === - 1 ? normalizedString : normalizedString . slice ( 0 , decimalIndex ) ;
641+ const fracPart = decimalIndex === - 1 ? undefined : normalizedString . slice ( decimalIndex + decimalSymbol . length ) ;
642+
643+ let sign = '' ;
644+ if ( intPart . startsWith ( '-' ) || intPart . startsWith ( '+' ) ) {
645+ sign = intPart [ 0 ] === '-' ? '-' : '' ;
646+ intPart = intPart . slice ( 1 ) ;
647+ }
648+
649+ if ( fracPart !== undefined && groupSymbol && fracPart . includes ( groupSymbol ) ) {
650+ return NaN ;
651+ }
652+
653+ // If grouping appears, require valid thousands groups so values like en-US "50,2" are invalid
654+ // instead of silently merging into 502.
655+ if ( groupSymbol && intPart . includes ( groupSymbol ) ) {
656+ const groups = intPart . split ( groupSymbol ) ;
657+ if ( groups . length < 2 || ! / ^ \d { 1 , 3 } $ / . test ( groups [ 0 ] ) || groups . slice ( 1 ) . some ( ( group ) => ! / ^ \d { 3 } $ / . test ( group ) ) ) {
658+ return NaN ;
659+ }
660+ intPart = groups . join ( '' ) ;
661+ }
662+
663+ if ( ! / ^ \d * $ / . test ( intPart ) || ( fracPart !== undefined && ! / ^ \d * $ / . test ( fracPart ) ) ) {
664+ return NaN ;
665+ }
666+
667+ if ( intPart === '' && ( fracPart === undefined || fracPart === '' ) ) {
668+ return NaN ;
669+ }
631670
632- return parseFloat ( cleaned ) ;
671+ return parseFloat ( ` ${ sign } ${ intPart || '0' } ${ fracPart !== undefined ? `. ${ fracPart } ` : '' } ` ) ;
633672} ;
634673
635674/**
0 commit comments