@automattic/number-formatters 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,701 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ require("@wordpress/date");
25
+ let debug = require("debug");
26
+ debug = __toESM(debug, 1);
27
+ //#region src/get-cached-formatter.ts
28
+ const debug$2 = (0, debug.default)("number-formatters:get-cached-formatter");
29
+ const formatterCache = /* @__PURE__ */ new Map();
30
+ /**
31
+ * Get a cached formatter for a given locale and options.
32
+ * @param params - The parameters for the formatter.
33
+ * @param params.locale - The locale to format the number in.
34
+ * @param params.options - Intl.NumberFormatOptions to pass to the formatter.
35
+ * @param params.fallbackLocale - The locale to fallback to if the locale is not supported.
36
+ * @param params.retries - The number of retries to attempt if the formatter is not created.
37
+ * @return {Intl.NumberFormat} A cached formatter for the given locale and options.
38
+ */
39
+ function getCachedFormatter({ locale, fallbackLocale = "en", options, retries = 1 }) {
40
+ const cacheKey = JSON.stringify([locale, options]);
41
+ try {
42
+ return formatterCache.get(cacheKey) ?? formatterCache.set(cacheKey, new Intl.NumberFormat(locale, options)).get(cacheKey);
43
+ } catch (error) {
44
+ debug$2(`Intl.NumberFormat was called with a non-existent locale "${locale}"; falling back to ${fallbackLocale}`);
45
+ if (retries) return getCachedFormatter({
46
+ locale: fallbackLocale,
47
+ options,
48
+ retries: retries - 1
49
+ });
50
+ throw error;
51
+ }
52
+ }
53
+ //#endregion
54
+ //#region src/number-format-currency/currencies.ts
55
+ const defaultCurrencyOverrides = {
56
+ AED: { symbol: "د.إ.‏" },
57
+ AFN: { symbol: "؋" },
58
+ ALL: { symbol: "Lek" },
59
+ AMD: { symbol: "֏" },
60
+ ANG: { symbol: "ƒ" },
61
+ AOA: { symbol: "Kz" },
62
+ ARS: { symbol: "$" },
63
+ AUD: { symbol: "A$" },
64
+ AWG: { symbol: "ƒ" },
65
+ AZN: { symbol: "₼" },
66
+ BAM: { symbol: "КМ" },
67
+ BBD: { symbol: "Bds$" },
68
+ BDT: { symbol: "৳" },
69
+ BGN: { symbol: "лв." },
70
+ BHD: { symbol: "د.ب.‏" },
71
+ BIF: { symbol: "FBu" },
72
+ BMD: { symbol: "$" },
73
+ BND: { symbol: "$" },
74
+ BOB: { symbol: "Bs" },
75
+ BRL: { symbol: "R$" },
76
+ BSD: { symbol: "$" },
77
+ BTC: { symbol: "Ƀ" },
78
+ BTN: { symbol: "Nu." },
79
+ BWP: { symbol: "P" },
80
+ BYR: { symbol: "р." },
81
+ BZD: { symbol: "BZ$" },
82
+ CAD: { symbol: "C$" },
83
+ CDF: { symbol: "FC" },
84
+ CHF: { symbol: "CHF" },
85
+ CLP: { symbol: "$" },
86
+ CNY: { symbol: "¥" },
87
+ COP: { symbol: "$" },
88
+ CRC: { symbol: "₡" },
89
+ CUC: { symbol: "CUC" },
90
+ CUP: { symbol: "$MN" },
91
+ CVE: { symbol: "$" },
92
+ CZK: { symbol: "Kč" },
93
+ DJF: { symbol: "Fdj" },
94
+ DKK: { symbol: "kr." },
95
+ DOP: { symbol: "RD$" },
96
+ DZD: { symbol: "د.ج.‏" },
97
+ EGP: { symbol: "ج.م.‏" },
98
+ ERN: { symbol: "Nfk" },
99
+ ETB: { symbol: "ETB" },
100
+ EUR: { symbol: "€" },
101
+ FJD: { symbol: "FJ$" },
102
+ FKP: { symbol: "£" },
103
+ GBP: { symbol: "£" },
104
+ GEL: { symbol: "Lari" },
105
+ GHS: { symbol: "₵" },
106
+ GIP: { symbol: "£" },
107
+ GMD: { symbol: "D" },
108
+ GNF: { symbol: "FG" },
109
+ GTQ: { symbol: "Q" },
110
+ GYD: { symbol: "G$" },
111
+ HKD: { symbol: "HK$" },
112
+ HNL: { symbol: "L." },
113
+ HRK: { symbol: "kn" },
114
+ HTG: { symbol: "G" },
115
+ HUF: { symbol: "Ft" },
116
+ IDR: { symbol: "Rp" },
117
+ ILS: { symbol: "₪" },
118
+ INR: { symbol: "₹" },
119
+ IQD: { symbol: "د.ع.‏" },
120
+ IRR: { symbol: "﷼" },
121
+ ISK: { symbol: "kr." },
122
+ JMD: { symbol: "J$" },
123
+ JOD: { symbol: "د.ا.‏" },
124
+ JPY: { symbol: "¥" },
125
+ KES: { symbol: "S" },
126
+ KGS: { symbol: "сом" },
127
+ KHR: { symbol: "៛" },
128
+ KMF: { symbol: "CF" },
129
+ KPW: { symbol: "₩" },
130
+ KRW: { symbol: "₩" },
131
+ KWD: { symbol: "د.ك.‏" },
132
+ KYD: { symbol: "$" },
133
+ KZT: { symbol: "₸" },
134
+ LAK: { symbol: "₭" },
135
+ LBP: { symbol: "ل.ل.‏" },
136
+ LKR: { symbol: "₨" },
137
+ LRD: { symbol: "L$" },
138
+ LSL: { symbol: "M" },
139
+ LYD: { symbol: "د.ل.‏" },
140
+ MAD: { symbol: "د.م.‏" },
141
+ MDL: { symbol: "lei" },
142
+ MGA: { symbol: "Ar" },
143
+ MKD: { symbol: "ден." },
144
+ MMK: { symbol: "K" },
145
+ MNT: { symbol: "₮" },
146
+ MOP: { symbol: "MOP$" },
147
+ MRO: { symbol: "UM" },
148
+ MTL: { symbol: "₤" },
149
+ MUR: { symbol: "₨" },
150
+ MVR: { symbol: "MVR" },
151
+ MWK: { symbol: "MK" },
152
+ MXN: { symbol: "MX$" },
153
+ MYR: { symbol: "RM" },
154
+ MZN: { symbol: "MT" },
155
+ NAD: { symbol: "N$" },
156
+ NGN: { symbol: "₦" },
157
+ NIO: { symbol: "C$" },
158
+ NOK: { symbol: "kr" },
159
+ NPR: { symbol: "₨" },
160
+ NZD: { symbol: "NZ$" },
161
+ OMR: { symbol: "﷼" },
162
+ PAB: { symbol: "B/." },
163
+ PEN: { symbol: "S/." },
164
+ PGK: { symbol: "K" },
165
+ PHP: { symbol: "₱" },
166
+ PKR: { symbol: "₨" },
167
+ PLN: { symbol: "zł" },
168
+ PYG: { symbol: "₲" },
169
+ QAR: { symbol: "﷼" },
170
+ RON: { symbol: "lei" },
171
+ RSD: { symbol: "Дин." },
172
+ RUB: { symbol: "₽" },
173
+ RWF: { symbol: "RWF" },
174
+ SAR: { symbol: "﷼" },
175
+ SBD: { symbol: "S$" },
176
+ SCR: { symbol: "₨" },
177
+ SDD: { symbol: "LSd" },
178
+ SDG: { symbol: "£‏" },
179
+ SEK: { symbol: "kr" },
180
+ SGD: { symbol: "S$" },
181
+ SHP: { symbol: "£" },
182
+ SLL: { symbol: "Le" },
183
+ SOS: { symbol: "S" },
184
+ SRD: { symbol: "$" },
185
+ STD: { symbol: "Db" },
186
+ SVC: { symbol: "₡" },
187
+ SYP: { symbol: "£" },
188
+ SZL: { symbol: "E" },
189
+ THB: { symbol: "฿" },
190
+ TJS: { symbol: "TJS" },
191
+ TMT: { symbol: "m" },
192
+ TND: { symbol: "د.ت.‏" },
193
+ TOP: { symbol: "T$" },
194
+ TRY: { symbol: "TL" },
195
+ TTD: { symbol: "TT$" },
196
+ TVD: { symbol: "$T" },
197
+ TWD: { symbol: "NT$" },
198
+ TZS: { symbol: "TSh" },
199
+ UAH: { symbol: "₴" },
200
+ UGX: { symbol: "USh" },
201
+ USD: {},
202
+ UYU: { symbol: "$U" },
203
+ UZS: { symbol: "сўм" },
204
+ VEB: { symbol: "Bs." },
205
+ VEF: { symbol: "Bs. F." },
206
+ VND: { symbol: "₫" },
207
+ VUV: { symbol: "VT" },
208
+ WST: { symbol: "WS$" },
209
+ XAF: { symbol: "F" },
210
+ XCD: { symbol: "$" },
211
+ XOF: { symbol: "F" },
212
+ XPF: { symbol: "F" },
213
+ YER: { symbol: "﷼" },
214
+ ZAR: { symbol: "R" },
215
+ ZMW: { symbol: "ZK" },
216
+ WON: { symbol: "₩" }
217
+ };
218
+ //#endregion
219
+ //#region src/number-format-currency/index.ts
220
+ const debug$1 = (0, debug.default)("number-formatters:number-format-currency");
221
+ /**
222
+ * Retrieves the currency override for a given currency.
223
+ *
224
+ * If the currency is USD and the user is not in the US, it will return `US$`.
225
+ *
226
+ * Per-field merge order is: dynamic overrides (from `currencyOverrides`) → hard-coded defaults.
227
+ * This means a caller can supply a partial map (eg: only `decimal`) without losing the
228
+ * default `symbol`.
229
+ * @param currency - The currency to get the override for.
230
+ * @param geoLocation - The geo location of the user.
231
+ * @param currencyOverrides - Dynamic per-currency overrides supplied by the host application.
232
+ * @return {CurrencyOverride | undefined} The currency override.
233
+ */
234
+ function getCurrencyOverride(currency, geoLocation, currencyOverrides) {
235
+ if (currency === "USD" && geoLocation && geoLocation !== "" && geoLocation !== "US") return {
236
+ symbol: "US$",
237
+ ...currencyOverrides?.USD
238
+ };
239
+ const defaultOverride = defaultCurrencyOverrides[currency];
240
+ const dynamicOverride = currencyOverrides?.[currency];
241
+ if (!defaultOverride && !dynamicOverride) return;
242
+ return {
243
+ ...defaultOverride,
244
+ ...dynamicOverride
245
+ };
246
+ }
247
+ /**
248
+ * Returns a valid currency code based on a shortlist of currency codes.
249
+ * Only currencies from the shortlist are allowed. Everything else will fall back to `FALLBACK_CURRENCY`.
250
+ * @param currency - The currency to get the valid currency for.
251
+ * @param geoLocation - The geo location of the user.
252
+ * @param currencyOverrides - Dynamic per-currency overrides supplied by the host application.
253
+ * @return {string} The valid currency.
254
+ */
255
+ function getValidCurrency(currency, geoLocation, currencyOverrides) {
256
+ if (!getCurrencyOverride(currency, geoLocation, currencyOverrides)) {
257
+ debug$1(`getValidCurrency was called with a non-existent currency "${currency}"; falling back to USD`);
258
+ return "USD";
259
+ }
260
+ return currency;
261
+ }
262
+ /**
263
+ * Returns a currency formatter for a given currency.
264
+ * @param params - The parameters for the currency formatter.
265
+ * @param params.number - The number to format.
266
+ * @param params.currency - The currency to format.
267
+ * @param params.browserSafeLocale - The browser safe locale.
268
+ * @param params.forceLatin - Whether to force the latin locale.
269
+ * @param params.stripZeros - Whether to strip zeros.
270
+ * @param params.signForPositive - Whether to show the sign for positive numbers.
271
+ * @return {Intl.NumberFormat} The currency formatter.
272
+ */
273
+ function getCurrencyFormatter({ number, currency, browserSafeLocale, forceLatin = true, stripZeros, signForPositive }) {
274
+ return getCachedFormatter({
275
+ locale: `${browserSafeLocale}${forceLatin ? "-u-nu-latn" : ""}`,
276
+ options: {
277
+ style: "currency",
278
+ currency,
279
+ ...stripZeros && Number.isInteger(number) && {
280
+ /**
281
+ * There's an option called `trailingZeroDisplay` but it does not yet work
282
+ * in FF so we have to strip zeros manually.
283
+ */
284
+ maximumFractionDigits: 0,
285
+ minimumFractionDigits: 0
286
+ },
287
+ ...signForPositive && { signDisplay: "exceptZero" }
288
+ }
289
+ });
290
+ }
291
+ /**
292
+ * Hard-coded smallest-unit exponent overrides for currencies where browser ICU's
293
+ * `maximumFractionDigits` disagrees with the API's smallest-unit encoding.
294
+ *
295
+ * This list exists as a safety net for callers that have not yet wired up the
296
+ * dynamic `currencyOverrides` path (eg: the WPCOM currencies endpoint). Once a
297
+ * host application provides overrides via `setCurrencyOverrides`, those take
298
+ * precedence on a per-currency basis.
299
+ *
300
+ * Keep this list minimal — the backend is the source of truth for the API's
301
+ * smallest-unit encoding, so adding speculative entries here risks silent
302
+ * drift. Only add a currency once we've verified that browsers report a
303
+ * value the API does not use.
304
+ *
305
+ * - IDR: modern Chrome / Node 24+ ICU reports 0; the API encodes with exponent 2.
306
+ * - HUF: same browser/API divergence as IDR.
307
+ */
308
+ const SMALLEST_UNIT_EXPONENT_OVERRIDES = {
309
+ IDR: 2,
310
+ HUF: 2
311
+ };
312
+ /**
313
+ * Returns the smallest unit exponent for a currency.
314
+ *
315
+ * Lookup order:
316
+ * 1. The dynamic `currencyOverrides[currency].decimal` if a host application has supplied one (typically via `setCurrencyOverrides`).
317
+ * 2. The hard-coded `SMALLEST_UNIT_EXPONENT_OVERRIDES` map.
318
+ * 3. The browser-derived display precision (`fallback`).
319
+ * @param currency - The currency code (ISO 4217)
320
+ * @param fallback - The browser-derived precision to use when no override applies
321
+ * @param currencyOverrides - Dynamic per-currency overrides supplied by the host application
322
+ * @return number - The smallest unit exponent
323
+ */
324
+ function getSmallestUnitExponent(currency, fallback, currencyOverrides) {
325
+ const dynamicDecimal = currencyOverrides?.[currency]?.decimal;
326
+ if (typeof dynamicDecimal === "number") return dynamicDecimal;
327
+ return SMALLEST_UNIT_EXPONENT_OVERRIDES[currency] ?? fallback;
328
+ }
329
+ /**
330
+ * Returns the precision for a given locale and currency.
331
+ * @param browserSafeLocale - The browser safe locale.
332
+ * @param currency - The currency to get the precision for.
333
+ * @param forceLatin - Whether to force the latin locale.
334
+ * @return {number | undefined} The precision.
335
+ */
336
+ function getPrecisionForLocaleAndCurrency(browserSafeLocale, currency, forceLatin) {
337
+ /**
338
+ * For regular numbers, the default is 3 if neither `minimumFractionDigits` or `maximumFractionDigits` are set,
339
+ * otherwise the greatest betweem `minimumFractionDigits` and 3.
340
+ *
341
+ * For currencies, the default is dependent on the currency.
342
+ *
343
+ * This may also result in undefined, for several reasons:
344
+ * see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#significantdigitsfractiondigits_default_values
345
+ */
346
+ return getCurrencyFormatter({
347
+ number: 0,
348
+ currency,
349
+ browserSafeLocale,
350
+ forceLatin
351
+ }).resolvedOptions().maximumFractionDigits;
352
+ }
353
+ /**
354
+ * Scales a number to a specified precision and rounds it to that precision.
355
+ * It ensures that all currency values are consistently rounded to the desired precision,
356
+ * avoiding issues with floating-point arithmetic.
357
+ * @param number - The number to scale.
358
+ * @param currencyPrecision - The precision to scale the number to.
359
+ * @return {number} The scaled number.
360
+ */
361
+ function scaleNumberForPrecision(number, currencyPrecision) {
362
+ const scale = Math.pow(10, currencyPrecision);
363
+ return Math.round(number * scale) / scale;
364
+ }
365
+ /**
366
+ * Prepares a number for formatting.
367
+ * @param number - The number to prepare.
368
+ * @param currencyPrecision - The display precision (from the browser) to round the result to.
369
+ * @param currency - The currency code, used to look up any smallest-unit exponent override.
370
+ * @param isSmallestUnit - Whether the number is the smallest unit of a currency.
371
+ * @param currencyOverrides - Dynamic per-currency overrides supplied by the host application.
372
+ * @return {number} The prepared number.
373
+ */
374
+ function prepareNumberForFormatting(number, currencyPrecision, currency, isSmallestUnit, currencyOverrides) {
375
+ if (isNaN(number)) {
376
+ debug$1("formatCurrency was called with NaN");
377
+ return 0;
378
+ }
379
+ if (isSmallestUnit) {
380
+ if (!Number.isInteger(number)) debug$1("formatCurrency was called with isSmallestUnit and a float which will be rounded", number);
381
+ const smallestUnitDivisor = 10 ** getSmallestUnitExponent(currency, currencyPrecision, currencyOverrides);
382
+ return scaleNumberForPrecision(Math.round(number) / smallestUnitDivisor, currencyPrecision);
383
+ }
384
+ return scaleNumberForPrecision(number, currencyPrecision);
385
+ }
386
+ /**
387
+ * Formats money with a given currency code.
388
+ *
389
+ * The currency will define the properties to use for this formatting, but
390
+ * those properties can be overridden using the options. Be careful when doing
391
+ * this.
392
+ *
393
+ * For currencies that include decimals, this will always return the amount
394
+ * with decimals included, even if those decimals are zeros. To exclude the
395
+ * zeros, use the `stripZeros` option. For example, the function will normally
396
+ * format `10.00` in `USD` as `$10.00` but when this option is true, it will
397
+ * return `$10` instead.
398
+ *
399
+ * Since rounding errors are common in floating point math, sometimes a price
400
+ * is provided as an integer in the smallest unit of a currency (eg: cents in
401
+ * USD or yen in JPY). Set the `isSmallestUnit` to change the function to
402
+ * operate on integer numbers instead. If this option is not set or false, the
403
+ * function will format the amount `1025` in `USD` as `$1,025.00`, but when the
404
+ * option is true, it will return `$10.25` instead.
405
+ *
406
+ * If the number is NaN, it will be treated as 0.
407
+ *
408
+ * If the currency code is not known, this will assume a default currency
409
+ * similar to USD.
410
+ *
411
+ * If `isSmallestUnit` is set and the number is not an integer, it will be
412
+ * rounded to an integer.
413
+ *
414
+ * @param params - The parameters for the currency formatter.
415
+ * @param params.number - The number to format.
416
+ * @param params.browserSafeLocale - The browser safe locale.
417
+ * @param params.currency - The currency to format.
418
+ * @param params.stripZeros - Whether to strip zeros.
419
+ * @param params.isSmallestUnit - Whether the number is the smallest unit of a currency.
420
+ * @param params.signForPositive - Whether to show the sign for positive numbers.
421
+ * @param params.geoLocation - The geo location of the user.
422
+ * @param params.forceLatin - Whether to force the latin locale.
423
+ * @param params.currencyOverrides - Dynamic per-currency overrides supplied by the host application.
424
+ * @return {string} A formatted string.
425
+ */
426
+ const numberFormatCurrency = ({ number, browserSafeLocale, currency, stripZeros, isSmallestUnit, signForPositive, geoLocation, forceLatin, currencyOverrides }) => {
427
+ const validCurrency = getValidCurrency(currency, geoLocation, currencyOverrides);
428
+ const currencyOverride = getCurrencyOverride(validCurrency, geoLocation, currencyOverrides);
429
+ const currencyPrecision = getPrecisionForLocaleAndCurrency(browserSafeLocale, validCurrency, forceLatin);
430
+ if (isSmallestUnit && typeof currencyPrecision === "undefined") throw new Error(`Could not determine currency precision for ${validCurrency} in ${browserSafeLocale}`);
431
+ const numberAsFloat = prepareNumberForFormatting(number, currencyPrecision ?? 0, validCurrency, isSmallestUnit, currencyOverrides);
432
+ return getCurrencyFormatter({
433
+ number: numberAsFloat,
434
+ currency: validCurrency,
435
+ browserSafeLocale,
436
+ forceLatin,
437
+ stripZeros,
438
+ signForPositive
439
+ }).formatToParts(numberAsFloat).reduce((formatted, part) => {
440
+ switch (part.type) {
441
+ case "currency":
442
+ if (currencyOverride?.symbol) return formatted + currencyOverride.symbol;
443
+ return formatted + part.value;
444
+ default: return formatted + part.value;
445
+ }
446
+ }, "");
447
+ };
448
+ /**
449
+ * Returns a formatted price object which can be used to manually render a
450
+ * formatted currency (eg: if you wanted to render the currency symbol in a
451
+ * different font size).
452
+ *
453
+ * The currency will define the properties to use for this formatting, but
454
+ * those properties can be overridden using the options. Be careful when doing
455
+ * this.
456
+ *
457
+ * For currencies that include decimals, this will always return the amount
458
+ * with decimals included, even if those decimals are zeros. To exclude the
459
+ * zeros, use the `stripZeros` option. For example, the function will normally
460
+ * format `10.00` in `USD` as `$10.00` but when this option is true, it will
461
+ * return `$10` instead.
462
+ *
463
+ * Since rounding errors are common in floating point math, sometimes a price
464
+ * is provided as an integer in the smallest unit of a currency (eg: cents in
465
+ * USD or yen in JPY). Set the `isSmallestUnit` to change the function to
466
+ * operate on integer numbers instead. If this option is not set or false, the
467
+ * function will format the amount `1025` in `USD` as `$1,025.00`, but when the
468
+ * option is true, it will return `$10.25` instead.
469
+ *
470
+ * Note that the `integer` return value of this function is not a number, but a
471
+ * locale-formatted string which may include symbols like spaces, commas, or
472
+ * periods as group separators. Similarly, the `fraction` property is a string
473
+ * that contains the decimal separator.
474
+ *
475
+ * If the number is NaN, it will be treated as 0.
476
+ *
477
+ * If the currency code is not known, this will assume a default currency
478
+ * similar to USD.
479
+ *
480
+ * If `isSmallestUnit` is set and the number is not an integer, it will be
481
+ * rounded to an integer.
482
+ *
483
+ * @param params - The parameters for the currency formatter.
484
+ * @param params.number - The number to format.
485
+ * @param params.browserSafeLocale - The browser safe locale.
486
+ * @param params.currency - The currency to format.
487
+ * @param params.stripZeros - Whether to strip zeros.
488
+ * @param params.isSmallestUnit - Whether the number is the smallest unit of a currency.
489
+ * @param params.signForPositive - Whether to show the sign for positive numbers.
490
+ * @param params.geoLocation - The geo location of the user.
491
+ * @param params.forceLatin - Whether to force the latin locale.
492
+ * @param params.currencyOverrides - Dynamic per-currency overrides supplied by the host application.
493
+ * @return {CurrencyObject} A formatted string e.g. { symbol:'$', integer: '$99', fraction: '.99', sign: '-' }
494
+ */
495
+ const getCurrencyObject$1 = ({ number, browserSafeLocale, currency, stripZeros, isSmallestUnit, signForPositive, geoLocation, forceLatin, currencyOverrides }) => {
496
+ const validCurrency = getValidCurrency(currency, geoLocation, currencyOverrides);
497
+ const currencyOverride = getCurrencyOverride(validCurrency, geoLocation, currencyOverrides);
498
+ const numberAsFloat = prepareNumberForFormatting(number, getPrecisionForLocaleAndCurrency(browserSafeLocale, validCurrency, forceLatin) ?? 0, validCurrency, isSmallestUnit, currencyOverrides);
499
+ const parts = getCurrencyFormatter({
500
+ number: numberAsFloat,
501
+ currency: validCurrency,
502
+ browserSafeLocale,
503
+ forceLatin,
504
+ stripZeros,
505
+ signForPositive
506
+ }).formatToParts(numberAsFloat);
507
+ let sign = "";
508
+ let symbol = "$";
509
+ let symbolPosition = "before";
510
+ let hasAmountBeenSet = false;
511
+ let hasDecimalBeenSet = false;
512
+ let integer = "";
513
+ let fraction = "";
514
+ parts.forEach((part) => {
515
+ switch (part.type) {
516
+ case "currency":
517
+ symbol = currencyOverride?.symbol ?? part.value;
518
+ if (hasAmountBeenSet) symbolPosition = "after";
519
+ return;
520
+ case "group":
521
+ integer += part.value;
522
+ hasAmountBeenSet = true;
523
+ return;
524
+ case "decimal":
525
+ fraction += part.value;
526
+ hasAmountBeenSet = true;
527
+ hasDecimalBeenSet = true;
528
+ return;
529
+ case "integer":
530
+ integer += part.value;
531
+ hasAmountBeenSet = true;
532
+ return;
533
+ case "fraction":
534
+ fraction += part.value;
535
+ hasAmountBeenSet = true;
536
+ hasDecimalBeenSet = true;
537
+ return;
538
+ case "minusSign":
539
+ sign = "-";
540
+ return;
541
+ case "plusSign": sign = "+";
542
+ }
543
+ });
544
+ return {
545
+ sign,
546
+ symbol,
547
+ symbolPosition,
548
+ integer,
549
+ fraction,
550
+ hasNonZeroFraction: !Number.isInteger(numberAsFloat) && hasDecimalBeenSet,
551
+ floatValue: numberAsFloat
552
+ };
553
+ };
554
+ //#endregion
555
+ //#region src/number-format.ts
556
+ /**
557
+ * Formats numbers using locale settings and/or passed options.
558
+ * @param params - The parameters for the number formatter.
559
+ * @param params.browserSafeLocale - The browser safe locale.
560
+ * @param params.decimals - The number of decimal places to use.
561
+ * @param params.forceLatin - Whether to force the latin locale.
562
+ * @param params.numberFormatOptions - The options for the number formatter.
563
+ * @return {Intl.NumberFormat} The number formatter.
564
+ */
565
+ const numberFormat = ({ browserSafeLocale, decimals = 0, forceLatin = true, numberFormatOptions = {} }) => {
566
+ return getCachedFormatter({
567
+ locale: `${browserSafeLocale}${forceLatin ? "-u-nu-latn" : ""}`,
568
+ options: {
569
+ minimumFractionDigits: decimals,
570
+ maximumFractionDigits: decimals,
571
+ ...numberFormatOptions
572
+ }
573
+ });
574
+ };
575
+ /**
576
+ * Convenience method for formatting numbers in a compact notation e.g. 1K, 1M, etc.
577
+ * Basically sets `notation: 'compact'` and `maximumFractionDigits: 1` in the options.
578
+ * Everything is overridable by passing the `numberFormatOptions` option.
579
+ * If you want more digits, pass `maximumFractionDigits: 2`.
580
+ * @param params - The parameters for the number formatter.
581
+ * @param params.numberFormatOptions - The options for the number formatter.
582
+ * @return {Intl.NumberFormat} The number formatter.
583
+ */
584
+ const numberFormatCompact = ({ numberFormatOptions = {}, ...params }) => numberFormat({
585
+ ...params,
586
+ numberFormatOptions: {
587
+ notation: "compact",
588
+ maximumFractionDigits: 1,
589
+ ...numberFormatOptions
590
+ }
591
+ });
592
+ //#endregion
593
+ //#region src/create-number-formatters.ts
594
+ /**
595
+ * Creates a NumberFormatters instance that provides number and currency formatting functionality with locale awareness
596
+ * @return {NumberFormatters} A NumberFormatters instance
597
+ */
598
+ function createNumberFormatters() {
599
+ let localeState;
600
+ let geoLocationState;
601
+ let currencyOverridesState;
602
+ const setLocale = (locale) => {
603
+ /**
604
+ * The `Intl.NumberFormat` constructor fails only when there is a variant, divided by `_`.
605
+ * These suffixes should be removed. Values like `de-at` or `es-mx`
606
+ * should all be valid inputs for the constructor.
607
+ */
608
+ localeState = locale;
609
+ };
610
+ const setCurrencyOverrides = (overrides) => {
611
+ currencyOverridesState = overrides;
612
+ };
613
+ /**
614
+ * Returns the locale defined on the module instance (through `setLocale`)
615
+ * or the "fallback locale" if no locale has been set.
616
+ *
617
+ * The "fallback locale" is defined as:
618
+ * - the current WP user locale, if available through `@wordpress/date` settings (assuming this runs in a WordPress context)
619
+ * - or the browser locale, if available through `window.navigator.language`
620
+ * - or the fallback locale constant (`FALLBACK_LOCALE`)
621
+ *
622
+ * @return {string} The locale to use for formatting.
623
+ */
624
+ const getBrowserSafeLocale = () => {
625
+ const localeFromUserSettings = typeof window !== "undefined" ? window.wp?.date?.getSettings?.()?.l10n?.locale : void 0;
626
+ const localeFromNavigator = typeof window !== "undefined" ? window?.navigator?.language : void 0;
627
+ return (localeState ?? (localeFromUserSettings || localeFromNavigator) ?? "en").split("_")[0];
628
+ };
629
+ const setGeoLocation = (geoLocation) => {
630
+ geoLocationState = geoLocation;
631
+ };
632
+ const formatNumber = (number, { decimals = 0, forceLatin = true, numberFormatOptions = {} } = {}) => {
633
+ try {
634
+ return numberFormat({
635
+ browserSafeLocale: getBrowserSafeLocale(),
636
+ decimals,
637
+ forceLatin,
638
+ numberFormatOptions
639
+ }).format(number);
640
+ } catch {
641
+ return String(number);
642
+ }
643
+ };
644
+ const formatNumberCompact = (number, { decimals = 0, forceLatin = true, numberFormatOptions = {} } = {}) => {
645
+ try {
646
+ return numberFormatCompact({
647
+ browserSafeLocale: getBrowserSafeLocale(),
648
+ decimals,
649
+ forceLatin,
650
+ numberFormatOptions
651
+ }).format(number);
652
+ } catch {
653
+ return String(number);
654
+ }
655
+ };
656
+ const formatCurrency = (number, currency, { stripZeros = false, isSmallestUnit = false, signForPositive = false, forceLatin = true } = {}) => {
657
+ return numberFormatCurrency({
658
+ number,
659
+ currency,
660
+ browserSafeLocale: getBrowserSafeLocale(),
661
+ stripZeros,
662
+ isSmallestUnit,
663
+ signForPositive,
664
+ geoLocation: geoLocationState,
665
+ forceLatin,
666
+ currencyOverrides: currencyOverridesState
667
+ });
668
+ };
669
+ const getCurrencyObject = (number, currency, { stripZeros = false, isSmallestUnit = false, signForPositive = false, forceLatin = true } = {}) => {
670
+ return getCurrencyObject$1({
671
+ number,
672
+ currency,
673
+ browserSafeLocale: getBrowserSafeLocale(),
674
+ stripZeros,
675
+ isSmallestUnit,
676
+ signForPositive,
677
+ geoLocation: geoLocationState,
678
+ forceLatin,
679
+ currencyOverrides: currencyOverridesState
680
+ });
681
+ };
682
+ return {
683
+ setLocale,
684
+ setGeoLocation,
685
+ setCurrencyOverrides,
686
+ formatNumber,
687
+ formatNumberCompact,
688
+ formatCurrency,
689
+ getCurrencyObject
690
+ };
691
+ }
692
+ const { setLocale, setGeoLocation, setCurrencyOverrides, formatNumber, formatNumberCompact, formatCurrency, getCurrencyObject } = createNumberFormatters();
693
+ //#endregion
694
+ exports.createNumberFormatters = createNumberFormatters;
695
+ exports.formatCurrency = formatCurrency;
696
+ exports.formatNumber = formatNumber;
697
+ exports.formatNumberCompact = formatNumberCompact;
698
+ exports.getCurrencyObject = getCurrencyObject;
699
+ exports.setCurrencyOverrides = setCurrencyOverrides;
700
+ exports.setGeoLocation = setGeoLocation;
701
+ exports.setLocale = setLocale;