@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3
3
import invariant from 'tiny-invariant' ;
4
4
import has from 'lodash.has' ;
5
5
import Select from 'react-select' ;
6
+ import { injectIntl } from 'react-intl' ;
6
7
import ClearIndicator from '../../internals/clear-indicator' ;
7
8
import DropdownIndicator from '../../internals/dropdown-indicator' ;
8
9
import isNumberish from '../../../utils/is-numberish' ;
@@ -117,10 +118,29 @@ const createCurrencySelectStyles = ({
117
118
// }
118
119
// which equals 0.0123456 €
119
120
120
- // Parses the value returned from <input type="number" />'s onChange function
121
- // The input will only call us with parseable values, or an empty string
122
- // The function will return NaN for the empty string.
123
- export const parseAmount = amount => parseFloat ( amount , 10 ) ;
121
+ // Parsing
122
+ // Since most users are not careful about how they enter values, we will parse
123
+ // both `.` and `,` as decimal separators.
124
+ // When a value is `1.000,00` we parse it as `1000`.
125
+ // When a value is `1,000.00` we also parse it as `1000`.
126
+ //
127
+ // This means the highest amount always wins. We do this by comparing the last
128
+ // position of `.` and `,`. Whatever occurs later is used as the decimal
129
+ // separator.
130
+ export const parseRawAmountToNumber = rawAmount => {
131
+ const lastDot = String ( rawAmount ) . lastIndexOf ( '.' ) ;
132
+ const lastComma = String ( rawAmount ) . lastIndexOf ( ',' ) ;
133
+
134
+ const separator = lastComma > lastDot ? ',' : '.' ;
135
+ const throwaway = separator === '.' ? ',' : '\\.' ;
136
+
137
+ // The raw amount with only one sparator
138
+ const normalizedAmount = String ( rawAmount )
139
+ . replace ( new RegExp ( `${ throwaway } ` , 'g' ) , '' )
140
+ . replace ( separator , '.' ) ;
141
+
142
+ return parseFloat ( normalizedAmount , 10 ) ;
143
+ } ;
124
144
125
145
// Turns the user input into a value the MoneyInput can pass up through onChange
126
146
// In case the number of fraction digits contained in "amount" exceeds the
@@ -133,15 +153,15 @@ export const parseAmount = amount => parseFloat(amount, 10);
133
153
// - no currency was selected
134
154
//
135
155
// This function expects the "amount" to be a trimmed value.
136
- export const createMoneyValue = ( currencyCode , amount ) => {
156
+ export const createMoneyValue = ( currencyCode , rawAmount ) => {
137
157
if ( ! currencyCode ) return null ;
138
158
139
159
const currency = currencies [ currencyCode ] ;
140
160
if ( ! currency ) return null ;
141
161
142
- if ( amount . length === 0 || ! isNumberish ( amount ) ) return null ;
162
+ if ( rawAmount . length === 0 || ! isNumberish ( rawAmount ) ) return null ;
143
163
144
- const amountAsNumber = parseAmount ( amount ) ;
164
+ const amountAsNumber = parseRawAmountToNumber ( rawAmount ) ;
145
165
if ( isNaN ( amountAsNumber ) ) return null ;
146
166
147
167
const centAmount = amountAsNumber * 10 ** currency . fractionDigits ;
@@ -175,27 +195,30 @@ export const createMoneyValue = (currencyCode, amount) => {
175
195
} ;
176
196
} ;
177
197
178
- const formatAmount = ( amount , currencyCode ) => {
198
+ const getAmountAsNumberFromMoneyValue = moneyValue =>
199
+ moneyValue . type === 'highPrecision'
200
+ ? moneyValue . preciseAmount / 10 ** moneyValue . fractionDigits
201
+ : moneyValue . centAmount /
202
+ 10 ** currencies [ moneyValue . currencyCode ] . fractionDigits ;
203
+
204
+ // gets called with a string and should return a formatted string
205
+ const formatAmount = ( rawAmount , currencyCode , locale ) => {
179
206
// fallback in case the user didn't enter an amount yet (or it's invalid)
180
- const moneyValue = createMoneyValue ( currencyCode , amount ) || {
207
+ const moneyValue = createMoneyValue ( currencyCode , rawAmount ) || {
181
208
currencyCode,
182
209
centAmount : NaN ,
183
210
} ;
184
- // format highPrecision so that . gets replaced by, and vice versa.
185
- if ( moneyValue . type === 'highPrecision' ) {
186
- return ( moneyValue . preciseAmount / 10 ** moneyValue . fractionDigits ) . toFixed (
187
- moneyValue . fractionDigits
188
- ) ;
189
- }
190
- return ( moneyValue . centAmount / 10 ** moneyValue . fractionDigits ) . toFixed (
191
- moneyValue . fractionDigits
192
- ) ;
193
- } ;
194
211
195
- const getAmountAsNumberFromMoneyValue = money =>
196
- money . type === 'highPrecision'
197
- ? money . preciseAmount / 10 ** money . fractionDigits
198
- : money . centAmount / 10 ** currencies [ money . currencyCode ] . fractionDigits ;
212
+ const amount = getAmountAsNumberFromMoneyValue ( moneyValue ) ;
213
+
214
+ return isNaN ( amount )
215
+ ? ''
216
+ : amount . toLocaleString ( locale , {
217
+ minimumFractionDigits : moneyValue . preciseAmount
218
+ ? moneyValue . fractionDigits
219
+ : currencies [ moneyValue . currencyCode ] . fractionDigits ,
220
+ } ) ;
221
+ } ;
199
222
200
223
const getAmountStyles = props => {
201
224
if ( props . isDisabled ) return styles [ 'amount-disabled' ] ;
@@ -210,7 +233,7 @@ const getAmountInputName = name => (name ? `${name}.amount` : undefined);
210
233
const getCurrencyDropdownName = name =>
211
234
name ? `${ name } .currencyCode` : undefined ;
212
235
213
- export default class MoneyInput extends React . Component {
236
+ class MoneyInput extends React . Component {
214
237
static displayName = 'MoneyInput' ;
215
238
216
239
static getAmountInputId = getAmountInputName ;
@@ -223,9 +246,14 @@ export default class MoneyInput extends React.Component {
223
246
typeof value . amount === 'string' ? value . amount . trim ( ) : ''
224
247
) ;
225
248
226
- static parseMoneyValue = moneyValue => {
249
+ static parseMoneyValue = ( moneyValue , locale ) => {
227
250
if ( ! moneyValue ) return { currencyCode : '' , amount : '' } ;
228
251
252
+ invariant (
253
+ typeof locale === 'string' ,
254
+ 'MoneyInput.parseMoneyValue: A locale must be passed as the second argument'
255
+ ) ;
256
+
229
257
invariant (
230
258
typeof moneyValue === 'object' ,
231
259
'MoneyInput.parseMoneyValue: Value must be passed as an object or be undefined'
@@ -251,7 +279,8 @@ export default class MoneyInput extends React.Component {
251
279
252
280
const amount = formatAmount (
253
281
String ( getAmountAsNumberFromMoneyValue ( moneyValue ) ) ,
254
- moneyValue . currencyCode
282
+ moneyValue . currencyCode ,
283
+ locale
255
284
) ;
256
285
257
286
return { amount, currencyCode : moneyValue . currencyCode } ;
@@ -288,6 +317,9 @@ export default class MoneyInput extends React.Component {
288
317
onChange : PropTypes . func . isRequired ,
289
318
hasError : PropTypes . bool ,
290
319
hasWarning : PropTypes . bool ,
320
+ intl : PropTypes . shape ( {
321
+ locale : PropTypes . string . isRequired ,
322
+ } ) . isRequired ,
291
323
292
324
horizontalConstraint : PropTypes . oneOf ( [ 's' , 'm' , 'l' , 'xl' , 'scale' ] ) ,
293
325
} ;
@@ -313,7 +345,8 @@ export default class MoneyInput extends React.Component {
313
345
// be lost
314
346
const formattedAmount = formatAmount (
315
347
this . props . value . amount . trim ( ) ,
316
- currencyCode
348
+ currencyCode ,
349
+ this . props . intl . locale
317
350
) ;
318
351
// The user could be changing the currency before entering any amount,
319
352
// or while the amount is invalid. In these cases, we don't attempt to
@@ -339,9 +372,7 @@ export default class MoneyInput extends React.Component {
339
372
persist : ( ) => { } ,
340
373
target : {
341
374
name : getAmountInputName ( this . props . name ) ,
342
- value : isNaN ( formattedAmount )
343
- ? this . props . value . amount
344
- : formattedAmount ,
375
+ value : nextAmount ,
345
376
} ,
346
377
} ;
347
378
@@ -366,7 +397,8 @@ export default class MoneyInput extends React.Component {
366
397
if ( amount . length > 0 && currencies [ this . props . value . currencyCode ] ) {
367
398
const formattedAmount = formatAmount (
368
399
amount ,
369
- this . props . value . currencyCode
400
+ this . props . value . currencyCode ,
401
+ this . props . intl . locale
370
402
) ;
371
403
372
404
// When the user entered a value with centPrecision, we can format
@@ -464,3 +496,5 @@ export default class MoneyInput extends React.Component {
464
496
) ;
465
497
}
466
498
}
499
+
500
+ export default injectIntl ( MoneyInput ) ;
0 commit comments