@transferwise/components 46.112.1 → 46.113.0

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.
Files changed (120) hide show
  1. package/build/expressiveMoneyInput/ExpressiveMoneyInput.js +113 -0
  2. package/build/expressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -0
  3. package/build/expressiveMoneyInput/ExpressiveMoneyInput.messages.js +17 -0
  4. package/build/expressiveMoneyInput/ExpressiveMoneyInput.messages.js.map +1 -0
  5. package/build/expressiveMoneyInput/ExpressiveMoneyInput.messages.mjs +13 -0
  6. package/build/expressiveMoneyInput/ExpressiveMoneyInput.messages.mjs.map +1 -0
  7. package/build/expressiveMoneyInput/ExpressiveMoneyInput.mjs +109 -0
  8. package/build/expressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -0
  9. package/build/expressiveMoneyInput/amountInput/AmountInput.js +281 -0
  10. package/build/expressiveMoneyInput/amountInput/AmountInput.js.map +1 -0
  11. package/build/expressiveMoneyInput/amountInput/AmountInput.mjs +279 -0
  12. package/build/expressiveMoneyInput/amountInput/AmountInput.mjs.map +1 -0
  13. package/build/expressiveMoneyInput/amountInput/utils.js +87 -0
  14. package/build/expressiveMoneyInput/amountInput/utils.js.map +1 -0
  15. package/build/expressiveMoneyInput/amountInput/utils.mjs +78 -0
  16. package/build/expressiveMoneyInput/amountInput/utils.mjs.map +1 -0
  17. package/build/expressiveMoneyInput/animatedNumber/AnimatedNumber.js +50 -0
  18. package/build/expressiveMoneyInput/animatedNumber/AnimatedNumber.js.map +1 -0
  19. package/build/expressiveMoneyInput/animatedNumber/AnimatedNumber.mjs +48 -0
  20. package/build/expressiveMoneyInput/animatedNumber/AnimatedNumber.mjs.map +1 -0
  21. package/build/expressiveMoneyInput/chevron/Chevron.js +31 -0
  22. package/build/expressiveMoneyInput/chevron/Chevron.js.map +1 -0
  23. package/build/expressiveMoneyInput/chevron/Chevron.mjs +29 -0
  24. package/build/expressiveMoneyInput/chevron/Chevron.mjs.map +1 -0
  25. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js +160 -0
  26. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js.map +1 -0
  27. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs +157 -0
  28. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs.map +1 -0
  29. package/build/expressiveMoneyInput/hooks/useFocus.js +37 -0
  30. package/build/expressiveMoneyInput/hooks/useFocus.js.map +1 -0
  31. package/build/expressiveMoneyInput/hooks/useFocus.mjs +35 -0
  32. package/build/expressiveMoneyInput/hooks/useFocus.mjs.map +1 -0
  33. package/build/expressiveMoneyInput/hooks/useInputStyle.js +71 -0
  34. package/build/expressiveMoneyInput/hooks/useInputStyle.js.map +1 -0
  35. package/build/expressiveMoneyInput/hooks/useInputStyle.mjs +69 -0
  36. package/build/expressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -0
  37. package/build/i18n/en.json +2 -0
  38. package/build/i18n/en.json.js +2 -0
  39. package/build/i18n/en.json.js.map +1 -1
  40. package/build/i18n/en.json.mjs +2 -0
  41. package/build/i18n/en.json.mjs.map +1 -1
  42. package/build/index.js +2 -0
  43. package/build/index.js.map +1 -1
  44. package/build/index.mjs +1 -0
  45. package/build/index.mjs.map +1 -1
  46. package/build/main.css +73 -7
  47. package/build/prompt/InlinePrompt/InlinePrompt.js +7 -0
  48. package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -1
  49. package/build/prompt/InlinePrompt/InlinePrompt.mjs +8 -1
  50. package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -1
  51. package/build/styles/expressiveMoneyInput/ExpressiveMoneyInput.css +58 -0
  52. package/build/styles/expressiveMoneyInput/amountInput/AmountInput.css +32 -0
  53. package/build/styles/expressiveMoneyInput/chevron/Chevron.css +12 -0
  54. package/build/styles/expressiveMoneyInput/currencySelector/CurrencySelector.css +6 -0
  55. package/build/styles/main.css +73 -7
  56. package/build/styles/moneyInput/MoneyInput.css +8 -0
  57. package/build/styles/prompt/InlinePrompt/InlinePrompt.css +7 -7
  58. package/build/types/expressiveMoneyInput/ExpressiveMoneyInput.d.ts +59 -0
  59. package/build/types/expressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -0
  60. package/build/types/expressiveMoneyInput/ExpressiveMoneyInput.messages.d.ts +12 -0
  61. package/build/types/expressiveMoneyInput/ExpressiveMoneyInput.messages.d.ts.map +1 -0
  62. package/build/types/expressiveMoneyInput/amountInput/AmountInput.d.ts +13 -0
  63. package/build/types/expressiveMoneyInput/amountInput/AmountInput.d.ts.map +1 -0
  64. package/build/types/expressiveMoneyInput/amountInput/utils.d.ts +22 -0
  65. package/build/types/expressiveMoneyInput/amountInput/utils.d.ts.map +1 -0
  66. package/build/types/expressiveMoneyInput/animatedNumber/AnimatedNumber.d.ts +9 -0
  67. package/build/types/expressiveMoneyInput/animatedNumber/AnimatedNumber.d.ts.map +1 -0
  68. package/build/types/expressiveMoneyInput/chevron/Chevron.d.ts +6 -0
  69. package/build/types/expressiveMoneyInput/chevron/Chevron.d.ts.map +1 -0
  70. package/build/types/expressiveMoneyInput/currencySelector/CurrencySelector.d.ts +30 -0
  71. package/build/types/expressiveMoneyInput/currencySelector/CurrencySelector.d.ts.map +1 -0
  72. package/build/types/expressiveMoneyInput/hooks/useFocus.d.ts +7 -0
  73. package/build/types/expressiveMoneyInput/hooks/useFocus.d.ts.map +1 -0
  74. package/build/types/expressiveMoneyInput/hooks/useInputStyle.d.ts +10 -0
  75. package/build/types/expressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -0
  76. package/build/types/expressiveMoneyInput/hooks/useSelectionRange.d.ts +10 -0
  77. package/build/types/expressiveMoneyInput/hooks/useSelectionRange.d.ts.map +1 -0
  78. package/build/types/expressiveMoneyInput/index.d.ts +3 -0
  79. package/build/types/expressiveMoneyInput/index.d.ts.map +1 -0
  80. package/build/types/index.d.ts +2 -0
  81. package/build/types/index.d.ts.map +1 -1
  82. package/build/types/prompt/InlinePrompt/InlinePrompt.d.ts +3 -2
  83. package/build/types/prompt/InlinePrompt/InlinePrompt.d.ts.map +1 -1
  84. package/build/types/test-utils/index.d.ts +4 -0
  85. package/build/types/test-utils/index.d.ts.map +1 -1
  86. package/package.json +3 -3
  87. package/src/expressiveMoneyInput/ExpressiveMoneyInput.autofocus.docs.mdx +12 -0
  88. package/src/expressiveMoneyInput/ExpressiveMoneyInput.css +58 -0
  89. package/src/expressiveMoneyInput/ExpressiveMoneyInput.less +13 -0
  90. package/src/expressiveMoneyInput/ExpressiveMoneyInput.messages.ts +13 -0
  91. package/src/expressiveMoneyInput/ExpressiveMoneyInput.story.tsx +232 -0
  92. package/src/expressiveMoneyInput/ExpressiveMoneyInput.tsx +156 -0
  93. package/src/expressiveMoneyInput/amountInput/AmountInput.css +32 -0
  94. package/src/expressiveMoneyInput/amountInput/AmountInput.less +43 -0
  95. package/src/expressiveMoneyInput/amountInput/AmountInput.tsx +353 -0
  96. package/src/expressiveMoneyInput/amountInput/utils.spec.ts +114 -0
  97. package/src/expressiveMoneyInput/amountInput/utils.ts +116 -0
  98. package/src/expressiveMoneyInput/animatedNumber/AnimatedNumber.tsx +40 -0
  99. package/src/expressiveMoneyInput/chevron/Chevron.css +12 -0
  100. package/src/expressiveMoneyInput/chevron/Chevron.less +13 -0
  101. package/src/expressiveMoneyInput/chevron/Chevron.tsx +35 -0
  102. package/src/expressiveMoneyInput/currencySelector/CurrencySelector.css +6 -0
  103. package/src/expressiveMoneyInput/currencySelector/CurrencySelector.less +7 -0
  104. package/src/expressiveMoneyInput/currencySelector/CurrencySelector.tsx +220 -0
  105. package/src/expressiveMoneyInput/hooks/useFocus.ts +35 -0
  106. package/src/expressiveMoneyInput/hooks/useInputStyle.ts +85 -0
  107. package/src/expressiveMoneyInput/hooks/useSelectionRange.ts +23 -0
  108. package/src/expressiveMoneyInput/index.ts +2 -0
  109. package/src/i18n/en.json +2 -0
  110. package/src/index.ts +2 -0
  111. package/src/main.css +73 -7
  112. package/src/main.less +1 -0
  113. package/src/moneyInput/MoneyInput.css +8 -0
  114. package/src/moneyInput/MoneyInput.less +5 -0
  115. package/src/prompt/InlinePrompt/InlinePrompt.css +7 -7
  116. package/src/prompt/InlinePrompt/InlinePrompt.less +7 -7
  117. package/src/prompt/InlinePrompt/InlinePrompt.spec.tsx +6 -0
  118. package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +39 -0
  119. package/src/prompt/InlinePrompt/InlinePrompt.tsx +12 -2
  120. package/src/ssr.spec.tsx +1 -0
@@ -0,0 +1,279 @@
1
+ import { formatAmount } from '@transferwise/formatting';
2
+ import { clsx } from 'clsx';
3
+ import { AnimatePresence } from 'framer-motion';
4
+ import { useState, useMemo, useRef, useEffect } from 'react';
5
+ import { useIntl } from 'react-intl';
6
+ import { AnimatedNumber } from '../animatedNumber/AnimatedNumber.mjs';
7
+ import { useFocus } from '../hooks/useFocus.mjs';
8
+ import { useInputStyle } from '../hooks/useInputStyle.mjs';
9
+ import { getFormattedString, getUnformattedNumber, getGroupSeparator, getDecimalSeparator, getDecimalCount, isInputPossiblyOverflowing, isAllowedInputKey, getEnteredDecimalsCount } from './utils.mjs';
10
+ import { jsx, jsxs } from 'react/jsx-runtime';
11
+
12
+ const AmountInput = ({
13
+ id,
14
+ describedById,
15
+ amount,
16
+ currency,
17
+ autoFocus,
18
+ onChange,
19
+ onFocusChange,
20
+ loading
21
+ }) => {
22
+ const intl = useIntl();
23
+ const {
24
+ focus,
25
+ setFocus,
26
+ visualFocus,
27
+ setVisualFocus
28
+ } = useFocus();
29
+ const [value, setValue] = useState(amount ? getFormattedString({
30
+ value: amount,
31
+ currency,
32
+ locale: intl.locale
33
+ }) : '');
34
+ const numericValue = useMemo(() => {
35
+ return getUnformattedNumber({
36
+ value,
37
+ currency,
38
+ locale: intl.locale
39
+ });
40
+ }, [value, currency, intl.locale]);
41
+ const valueWithFullDecimals = useMemo(() => {
42
+ return getFormattedString({
43
+ value: numericValue ?? 0,
44
+ currency,
45
+ locale: intl.locale,
46
+ alwaysShowDecimals: true
47
+ });
48
+ }, [numericValue, currency, intl.locale]);
49
+ const ref = useRef(null);
50
+ useEffect(() => {
51
+ if (autoFocus) {
52
+ ref.current?.focus();
53
+ }
54
+ }, []);
55
+ const placeholder = getPlaceholder(currency, intl.locale);
56
+ const groupSeparator = getGroupSeparator(currency, intl.locale);
57
+ const decimalSeparator = getDecimalSeparator(currency, intl.locale);
58
+ const maxDecimalCount = getDecimalCount(currency, intl.locale);
59
+ const decimalPart = getDecimalPart(value, decimalSeparator);
60
+ const decimalMode = decimalSeparator && value.includes(decimalSeparator);
61
+ useEffect(() => {
62
+ if (!focus) {
63
+ setValue(amount ? getFormattedString({
64
+ value: amount,
65
+ currency,
66
+ locale: intl.locale
67
+ }) : '');
68
+ }
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps
70
+ }, [amount]);
71
+ useEffect(() => {
72
+ onFocusChange?.(visualFocus);
73
+ }, [visualFocus]);
74
+ const shouldReformatAfterUserInput = newValue => {
75
+ // don't reformat if formatting would wipe out user's input
76
+ if (reformatValue(newValue) === '') {
77
+ return false;
78
+ }
79
+ const endsWithDecimalSeparator = decimalSeparator && newValue.endsWith(decimalSeparator);
80
+ const endsWithGroupSeparator = groupSeparator && newValue.endsWith(groupSeparator);
81
+ // if the user has entered a seperator to the end, formatting would delete it
82
+ if (endsWithDecimalSeparator || endsWithGroupSeparator) {
83
+ return false;
84
+ }
85
+ const containsDecimalSeparator = decimalSeparator && newValue.includes(decimalSeparator);
86
+ if (containsDecimalSeparator) {
87
+ const enteredDecimalsCount = getEnteredDecimalsCount(newValue, decimalSeparator);
88
+ // don't reformat until user has entered all the allowed decimals
89
+ // for example, we don't want 1.1 to be reformatted to 1.10 immediately
90
+ if (enteredDecimalsCount < maxDecimalCount) {
91
+ return false;
92
+ }
93
+ }
94
+ return true;
95
+ };
96
+ const reformatValue = newValue => {
97
+ const unformattedValue = getUnformattedNumber({
98
+ value: newValue,
99
+ currency,
100
+ locale: intl.locale
101
+ });
102
+ const formattedValue = unformattedValue ? getFormattedString({
103
+ value: unformattedValue,
104
+ currency,
105
+ locale: intl.locale
106
+ }) : '';
107
+ return formattedValue;
108
+ };
109
+ const handleChange = newValue => {
110
+ const oldCursorPosition = ref.current?.selectionStart ?? 0;
111
+ const newFormattedString = shouldReformatAfterUserInput(newValue) ? reformatValue(newValue) : newValue;
112
+ setValue(newFormattedString);
113
+ const newNumber = getUnformattedNumber({
114
+ value: newFormattedString,
115
+ currency,
116
+ locale: intl.locale
117
+ });
118
+ if (newNumber !== numericValue) {
119
+ if (numericValue || newNumber) {
120
+ onChange(newNumber);
121
+ }
122
+ }
123
+ const newCursorPosition = oldCursorPosition + (newFormattedString.length - newValue.length);
124
+ requestAnimationFrame(() => {
125
+ ref?.current?.setSelectionRange(newCursorPosition, newCursorPosition);
126
+ });
127
+ };
128
+ const handlePaste = e => {
129
+ e.preventDefault();
130
+ const clipboardData = e.clipboardData?.getData('text/plain');
131
+ if (!clipboardData) {
132
+ return;
133
+ }
134
+ // need to sanitise the pasted value otherwise other validation logic will ignore the input entirely
135
+ const sanitisedValue = reformatValue(clipboardData);
136
+ handleChange(sanitisedValue);
137
+ };
138
+ const handleBlur = () => {
139
+ setFocus(false);
140
+ setValue(reformatValue(value));
141
+ };
142
+ const handleBackspace = e => {
143
+ const input = e.target;
144
+ // using the updated selection range after the backspace key has been processed, instead of the current selection range in state
145
+ const {
146
+ value: currentValue,
147
+ selectionStart,
148
+ selectionEnd
149
+ } = input;
150
+ if (selectionStart === selectionEnd && selectionStart && selectionStart > 0) {
151
+ const charBeforeCursor = currentValue[selectionStart - 1];
152
+ // if the user deletes a thousands separator, remove the digit before it as well
153
+ if (charBeforeCursor === groupSeparator) {
154
+ e.preventDefault();
155
+ const beforeCursor = currentValue.slice(0, selectionStart - 2);
156
+ const afterCursor = currentValue.slice(selectionStart);
157
+ const newValue = `${beforeCursor}${afterCursor}`;
158
+ input.setSelectionRange(beforeCursor.length, beforeCursor.length);
159
+ handleChange(newValue);
160
+ }
161
+ }
162
+ };
163
+ const handleKeyDown = e => {
164
+ setFocus(true);
165
+ if (!isAllowedInputKey(e)) {
166
+ e.preventDefault();
167
+ }
168
+ if (e.key === 'Backspace') {
169
+ handleBackspace(e);
170
+ }
171
+ };
172
+ const isAllowedInput = e => {
173
+ const hasMultipleDecimalSeparators = decimalSeparator && e.target.value.split(decimalSeparator).length > 2;
174
+ if (hasMultipleDecimalSeparators) {
175
+ return false;
176
+ }
177
+ const newNumericValue = getUnformattedNumber({
178
+ value: e.target.value,
179
+ currency,
180
+ locale: intl.locale
181
+ });
182
+ const maxLength = Number.MAX_SAFE_INTEGER.toString().length;
183
+ if (String(newNumericValue).length > maxLength) {
184
+ return false;
185
+ }
186
+ return true;
187
+ };
188
+ const addonContent = useMemo(() => {
189
+ // because we're using a separate "addon" element for the placeholder decimals, there is a possibility that the input itself will become scrollable
190
+ // and the decimals will appear on top of the input. Safest thing to do is to just hide the addon if there is not enough room
191
+ if (isInputPossiblyOverflowing({
192
+ ref,
193
+ value
194
+ })) {
195
+ return null;
196
+ }
197
+ if (!decimalSeparator || !value) {
198
+ return null;
199
+ }
200
+ // if the user has typed a decimal separator, show the full decimal part as a placeholder
201
+ // this returns a string even if there is no content, typing should replace the placeholder immediately without animation
202
+ // otherwise there is an ugly animation when going from 1.23 to 1.2 due to AnimatePresence
203
+ if (focus && decimalMode) {
204
+ // reuse getDecimalPart
205
+ const fullDecimalPart = getDecimalPart(valueWithFullDecimals, decimalSeparator);
206
+ // show only the characters that are not already displayed by the input
207
+ return fullDecimalPart?.slice(decimalPart?.length);
208
+ }
209
+ // in unfocused state, always show the full decimal part unless the user has already entered decimals
210
+ if (!focus && !decimalMode) {
211
+ const [_, decimalPlaceholder] = placeholder.split(decimalSeparator);
212
+ return decimalSeparator + decimalPlaceholder;
213
+ }
214
+ return null;
215
+ }, [decimalMode, decimalPart?.length, decimalSeparator, focus, placeholder, value, valueWithFullDecimals]);
216
+ const style = useInputStyle({
217
+ // whenever decimals are shown, we need to account for the full decimal part for the font size calculation
218
+ value: addonContent ? valueWithFullDecimals : value,
219
+ focus: visualFocus,
220
+ inputElement: ref.current,
221
+ loading
222
+ });
223
+ return /*#__PURE__*/jsx("div", {
224
+ className: "wds-amount-input-container",
225
+ children: /*#__PURE__*/jsxs("div", {
226
+ className: clsx('wds-amount-input-input-container', 'np-text-display-large'),
227
+ style: style,
228
+ children: [/*#__PURE__*/jsx("input", {
229
+ ref: ref,
230
+ className: "wds-amount-input-input",
231
+ id: id,
232
+ autoComplete: "off",
233
+ inputMode: "decimal",
234
+ value: value,
235
+ type: "text",
236
+ placeholder: placeholder,
237
+ "aria-describedby": describedById
238
+ /* without this, the input tries to keep an aspect ratio and doesn't respect CSS width rules */,
239
+ size: 1,
240
+ onChange: e => {
241
+ if (isAllowedInput(e)) {
242
+ handleChange(e.target.value);
243
+ }
244
+ },
245
+ onBlurCapture: () => handleBlur(),
246
+ onPaste: e => handlePaste(e),
247
+ onFocus: () => {
248
+ setFocus(true);
249
+ },
250
+ onBlur: () => {
251
+ setTimeout(() => setVisualFocus(false), 30);
252
+ },
253
+ onKeyDown: e => handleKeyDown(e)
254
+ }), /*#__PURE__*/jsx(AnimatePresence, {
255
+ initial: false,
256
+ children: addonContent !== null && /*#__PURE__*/jsx(AnimatedNumber, {
257
+ className: clsx('wds-amount-input-placeholder', visualFocus && 'wds-amount-input-placeholder-focus'),
258
+ onClick: () => ref.current?.focus(),
259
+ children: addonContent
260
+ })
261
+ })]
262
+ })
263
+ });
264
+ };
265
+ const getPlaceholder = (currency, locale) => {
266
+ return formatAmount(0, currency, locale, {
267
+ alwaysShowDecimals: true
268
+ });
269
+ };
270
+ const getDecimalPart = (value, decimalSeparator) => {
271
+ if (!value || !decimalSeparator) {
272
+ return undefined;
273
+ }
274
+ const [_, decimalPart] = value.split(decimalSeparator);
275
+ return decimalPart ?? undefined;
276
+ };
277
+
278
+ export { AmountInput };
279
+ //# sourceMappingURL=AmountInput.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AmountInput.mjs","sources":["../../../src/expressiveMoneyInput/amountInput/AmountInput.tsx"],"sourcesContent":["import { formatAmount } from '@transferwise/formatting';\nimport { clsx } from 'clsx';\nimport { AnimatePresence } from 'framer-motion';\nimport { type ChangeEvent, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';\nimport { useIntl } from 'react-intl';\n\nimport { Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';\nimport { AnimatedNumber } from '../animatedNumber/AnimatedNumber';\nimport { useFocus } from '../hooks/useFocus';\nimport { useInputStyle } from '../hooks/useInputStyle';\nimport {\n getDecimalCount,\n getDecimalSeparator,\n getEnteredDecimalsCount,\n getFormattedString,\n getGroupSeparator,\n getUnformattedNumber,\n isAllowedInputKey,\n isInputPossiblyOverflowing,\n} from './utils';\n\ntype Props = {\n id: string;\n describedById?: string;\n amount?: number | null;\n currency: string;\n autoFocus?: boolean;\n onChange: (amount: number | null) => void;\n onFocusChange?: (focused: boolean) => void;\n} & Pick<ExpressiveMoneyInputProps, 'loading'>;\n\nexport const AmountInput = ({\n id,\n describedById,\n amount,\n currency,\n autoFocus,\n onChange,\n onFocusChange,\n loading,\n}: Props) => {\n const intl = useIntl();\n const { focus, setFocus, visualFocus, setVisualFocus } = useFocus();\n\n const [value, setValue] = useState<string>(\n amount\n ? getFormattedString({\n value: amount,\n currency,\n locale: intl.locale,\n })\n : '',\n );\n const numericValue = useMemo(() => {\n return getUnformattedNumber({\n value,\n currency,\n locale: intl.locale,\n });\n }, [value, currency, intl.locale]);\n\n const valueWithFullDecimals = useMemo(() => {\n return getFormattedString({\n value: numericValue ?? 0,\n currency,\n locale: intl.locale,\n alwaysShowDecimals: true,\n });\n }, [numericValue, currency, intl.locale]);\n\n const ref = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (autoFocus) {\n ref.current?.focus();\n }\n }, []);\n\n const placeholder = getPlaceholder(currency, intl.locale);\n const groupSeparator = getGroupSeparator(currency, intl.locale);\n const decimalSeparator = getDecimalSeparator(currency, intl.locale);\n const maxDecimalCount = getDecimalCount(currency, intl.locale);\n\n const decimalPart = getDecimalPart(value, decimalSeparator);\n const decimalMode = decimalSeparator && value.includes(decimalSeparator);\n\n useEffect(() => {\n if (!focus) {\n setValue(\n amount\n ? getFormattedString({\n value: amount,\n currency,\n locale: intl.locale,\n })\n : '',\n );\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amount]);\n\n useEffect(() => {\n onFocusChange?.(visualFocus);\n }, [visualFocus]);\n\n const shouldReformatAfterUserInput = (newValue: string) => {\n // don't reformat if formatting would wipe out user's input\n if (reformatValue(newValue) === '') {\n return false;\n }\n\n const endsWithDecimalSeparator = decimalSeparator && newValue.endsWith(decimalSeparator);\n const endsWithGroupSeparator = groupSeparator && newValue.endsWith(groupSeparator);\n\n // if the user has entered a seperator to the end, formatting would delete it\n if (endsWithDecimalSeparator || endsWithGroupSeparator) {\n return false;\n }\n\n const containsDecimalSeparator = decimalSeparator && newValue.includes(decimalSeparator);\n\n if (containsDecimalSeparator) {\n const enteredDecimalsCount = getEnteredDecimalsCount(newValue, decimalSeparator);\n // don't reformat until user has entered all the allowed decimals\n // for example, we don't want 1.1 to be reformatted to 1.10 immediately\n if (enteredDecimalsCount < maxDecimalCount) {\n return false;\n }\n }\n\n return true;\n };\n\n const reformatValue = (newValue: string) => {\n const unformattedValue = getUnformattedNumber({\n value: newValue,\n currency,\n locale: intl.locale,\n });\n const formattedValue = unformattedValue\n ? getFormattedString({\n value: unformattedValue,\n currency,\n locale: intl.locale,\n })\n : '';\n return formattedValue;\n };\n\n const handleChange = (newValue: string) => {\n const oldCursorPosition = ref.current?.selectionStart ?? 0;\n\n const newFormattedString = shouldReformatAfterUserInput(newValue)\n ? reformatValue(newValue)\n : newValue;\n setValue(newFormattedString);\n\n const newNumber = getUnformattedNumber({\n value: newFormattedString,\n currency,\n locale: intl.locale,\n });\n\n if (newNumber !== numericValue) {\n if (numericValue || newNumber) {\n onChange(newNumber);\n }\n }\n\n const newCursorPosition = oldCursorPosition + (newFormattedString.length - newValue.length);\n requestAnimationFrame(() => {\n ref?.current?.setSelectionRange(newCursorPosition, newCursorPosition);\n });\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n\n const clipboardData = e.clipboardData?.getData('text/plain');\n if (!clipboardData) {\n return;\n }\n\n // need to sanitise the pasted value otherwise other validation logic will ignore the input entirely\n const sanitisedValue = reformatValue(clipboardData);\n\n handleChange(sanitisedValue);\n };\n\n const handleBlur = () => {\n setFocus(false);\n setValue(reformatValue(value));\n };\n\n const handleBackspace = (e: KeyboardEvent<HTMLInputElement>) => {\n const input = e.target as HTMLInputElement;\n // using the updated selection range after the backspace key has been processed, instead of the current selection range in state\n const { value: currentValue, selectionStart, selectionEnd } = input;\n\n if (selectionStart === selectionEnd && selectionStart && selectionStart > 0) {\n const charBeforeCursor = currentValue[selectionStart - 1];\n\n // if the user deletes a thousands separator, remove the digit before it as well\n if (charBeforeCursor === groupSeparator) {\n e.preventDefault();\n const beforeCursor = currentValue.slice(0, selectionStart - 2);\n const afterCursor = currentValue.slice(selectionStart);\n const newValue = `${beforeCursor}${afterCursor}`;\n input.setSelectionRange(beforeCursor.length, beforeCursor.length);\n handleChange(newValue);\n }\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n setFocus(true);\n if (!isAllowedInputKey(e)) {\n e.preventDefault();\n }\n\n if (e.key === 'Backspace') {\n handleBackspace(e);\n }\n };\n\n const isAllowedInput = (e: ChangeEvent<HTMLInputElement>) => {\n const hasMultipleDecimalSeparators =\n decimalSeparator && e.target.value.split(decimalSeparator).length > 2;\n if (hasMultipleDecimalSeparators) {\n return false;\n }\n\n const newNumericValue = getUnformattedNumber({\n value: e.target.value,\n currency,\n locale: intl.locale,\n });\n const maxLength = Number.MAX_SAFE_INTEGER.toString().length;\n if (String(newNumericValue).length > maxLength) {\n return false;\n }\n\n return true;\n };\n\n const addonContent = useMemo((): string | null | undefined => {\n // because we're using a separate \"addon\" element for the placeholder decimals, there is a possibility that the input itself will become scrollable\n // and the decimals will appear on top of the input. Safest thing to do is to just hide the addon if there is not enough room\n if (isInputPossiblyOverflowing({ ref, value })) {\n return null;\n }\n if (!decimalSeparator || !value) {\n return null;\n }\n\n // if the user has typed a decimal separator, show the full decimal part as a placeholder\n // this returns a string even if there is no content, typing should replace the placeholder immediately without animation\n // otherwise there is an ugly animation when going from 1.23 to 1.2 due to AnimatePresence\n if (focus && decimalMode) {\n // reuse getDecimalPart\n const fullDecimalPart = getDecimalPart(valueWithFullDecimals, decimalSeparator);\n // show only the characters that are not already displayed by the input\n return fullDecimalPart?.slice(decimalPart?.length);\n }\n\n // in unfocused state, always show the full decimal part unless the user has already entered decimals\n if (!focus && !decimalMode) {\n const [_, decimalPlaceholder] = placeholder.split(decimalSeparator);\n return decimalSeparator + decimalPlaceholder;\n }\n\n return null;\n }, [\n decimalMode,\n decimalPart?.length,\n decimalSeparator,\n focus,\n placeholder,\n value,\n valueWithFullDecimals,\n ]);\n\n const style = useInputStyle({\n // whenever decimals are shown, we need to account for the full decimal part for the font size calculation\n value: addonContent ? valueWithFullDecimals : value,\n focus: visualFocus,\n inputElement: ref.current,\n loading,\n });\n\n return (\n <div className=\"wds-amount-input-container\">\n <div\n className={clsx('wds-amount-input-input-container', 'np-text-display-large')}\n style={style}\n >\n <input\n ref={ref}\n className=\"wds-amount-input-input\"\n id={id}\n autoComplete=\"off\"\n inputMode=\"decimal\"\n value={value}\n type=\"text\"\n placeholder={placeholder}\n aria-describedby={describedById}\n /* without this, the input tries to keep an aspect ratio and doesn't respect CSS width rules */\n size={1}\n onChange={(e) => {\n if (isAllowedInput(e)) {\n handleChange(e.target.value);\n }\n }}\n onBlurCapture={() => handleBlur()}\n onPaste={(e) => handlePaste(e)}\n onFocus={() => {\n setFocus(true);\n }}\n onBlur={() => {\n setTimeout(() => setVisualFocus(false), 30);\n }}\n onKeyDown={(e) => handleKeyDown(e)}\n />\n <AnimatePresence initial={false}>\n {addonContent !== null && (\n <AnimatedNumber\n className={clsx(\n 'wds-amount-input-placeholder',\n visualFocus && 'wds-amount-input-placeholder-focus',\n )}\n onClick={() => ref.current?.focus()}\n >\n {addonContent}\n </AnimatedNumber>\n )}\n </AnimatePresence>\n </div>\n </div>\n );\n};\n\nconst getPlaceholder = (currency: string, locale: string) => {\n return formatAmount(0, currency, locale, { alwaysShowDecimals: true });\n};\n\nconst getDecimalPart = (value: string, decimalSeparator: string | null) => {\n if (!value || !decimalSeparator) {\n return undefined;\n }\n\n const [_, decimalPart] = value.split(decimalSeparator);\n return decimalPart ?? undefined;\n};\n"],"names":["AmountInput","id","describedById","amount","currency","autoFocus","onChange","onFocusChange","loading","intl","useIntl","focus","setFocus","visualFocus","setVisualFocus","useFocus","value","setValue","useState","getFormattedString","locale","numericValue","useMemo","getUnformattedNumber","valueWithFullDecimals","alwaysShowDecimals","ref","useRef","useEffect","current","placeholder","getPlaceholder","groupSeparator","getGroupSeparator","decimalSeparator","getDecimalSeparator","maxDecimalCount","getDecimalCount","decimalPart","getDecimalPart","decimalMode","includes","shouldReformatAfterUserInput","newValue","reformatValue","endsWithDecimalSeparator","endsWith","endsWithGroupSeparator","containsDecimalSeparator","enteredDecimalsCount","getEnteredDecimalsCount","unformattedValue","formattedValue","handleChange","oldCursorPosition","selectionStart","newFormattedString","newNumber","newCursorPosition","length","requestAnimationFrame","setSelectionRange","handlePaste","e","preventDefault","clipboardData","getData","sanitisedValue","handleBlur","handleBackspace","input","target","currentValue","selectionEnd","charBeforeCursor","beforeCursor","slice","afterCursor","handleKeyDown","isAllowedInputKey","key","isAllowedInput","hasMultipleDecimalSeparators","split","newNumericValue","maxLength","Number","MAX_SAFE_INTEGER","toString","String","addonContent","isInputPossiblyOverflowing","fullDecimalPart","_","decimalPlaceholder","style","useInputStyle","inputElement","_jsx","className","children","_jsxs","clsx","autoComplete","inputMode","type","size","onBlurCapture","onPaste","onFocus","onBlur","setTimeout","onKeyDown","AnimatePresence","initial","AnimatedNumber","onClick","formatAmount","undefined"],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ var formatting = require('@transferwise/formatting');
4
+
5
+ const getDecimalSeparator = (currency, locale) => {
6
+ return formatting.formatAmount(1.1, currency, locale).replace(/\p{Number}/gu, '');
7
+ };
8
+ const getGroupSeparator = (currency, locale) => {
9
+ return formatting.formatAmount(10000000, currency, locale).replace(/\p{Number}/gu, '')[0];
10
+ };
11
+ const getDecimalCount = (currency, locale) => {
12
+ const decimalSeparator = getDecimalSeparator(currency, locale);
13
+ if (!decimalSeparator) {
14
+ return 0;
15
+ }
16
+ const parts = formatting.formatAmount(1.1, currency, locale).split(decimalSeparator);
17
+ return parts.length === 2 ? parts[1].length : 0;
18
+ };
19
+ const getEnteredDecimalsCount = (value, decimalSeparator) => {
20
+ return value.split(decimalSeparator)[1]?.length ?? 0;
21
+ };
22
+ const getUnformattedNumber = ({
23
+ value: formattedValue,
24
+ currency,
25
+ locale
26
+ }) => {
27
+ const groupSeparator = getGroupSeparator(currency, locale);
28
+ const decimalSeparator = getDecimalSeparator(currency, locale);
29
+ if (!formattedValue) {
30
+ return null;
31
+ }
32
+ // parseFloat can't handle thousands separators
33
+ const withoutGroupSeparator = groupSeparator ? formattedValue.replace(/ /gu, '').replace(new RegExp(`\\${groupSeparator}`, 'g'), '') : formattedValue;
34
+ // parseFloat can only handle . as decimal separator
35
+ const withNormalisedDecimalSeparator = decimalSeparator ? withoutGroupSeparator.replace(decimalSeparator, '.') : withoutGroupSeparator;
36
+ const parsedValue = Number.parseFloat(withNormalisedDecimalSeparator);
37
+ return parsedValue;
38
+ };
39
+ const getFormattedString = ({
40
+ value: unformattedValue,
41
+ currency,
42
+ locale,
43
+ alwaysShowDecimals = false
44
+ }) => {
45
+ const decimalSeparator = getDecimalSeparator(currency, locale);
46
+ // formatAmount rounds extra decimals, so 1.999 will become 2. Instead we will manually strip extra decimals so that it becomes 1.99 after formatting
47
+ const decimalCount = getDecimalCount(currency, locale);
48
+ const unformattedString = unformattedValue.toString();
49
+ const [integerPart, decimalPart] = decimalSeparator ? unformattedString.split(decimalSeparator) : [unformattedString, undefined];
50
+ const formattedDecimalPart = decimalPart ? decimalPart.slice(0, decimalCount) : '';
51
+ const sanitisedUnformattedValue = Number.parseFloat(`${integerPart}.${formattedDecimalPart}`);
52
+ return formatting.formatAmount(sanitisedUnformattedValue, currency, locale, {
53
+ alwaysShowDecimals
54
+ });
55
+ };
56
+ const isInputPossiblyOverflowing = ({
57
+ ref,
58
+ value
59
+ }) => {
60
+ const textLength = value.length;
61
+ const inputWidth = ref.current?.clientWidth;
62
+ if (!inputWidth || !textLength) {
63
+ return;
64
+ }
65
+ const maxCharactersWithoutOverflow = Math.floor(inputWidth / 19);
66
+ return textLength > maxCharactersWithoutOverflow;
67
+ };
68
+ const allowedInputKeys = new Set(['Backspace', 'Delete', ',', '.', 'ArrowLeft', 'ArrowRight', 'Enter', 'Tab']);
69
+ const isAllowedInputKey = e => {
70
+ const {
71
+ metaKey,
72
+ key,
73
+ ctrlKey
74
+ } = e;
75
+ const isNumberKey = !Number.isNaN(Number.parseInt(key, 10));
76
+ return isNumberKey || metaKey || ctrlKey || allowedInputKeys.has(key);
77
+ };
78
+
79
+ exports.getDecimalCount = getDecimalCount;
80
+ exports.getDecimalSeparator = getDecimalSeparator;
81
+ exports.getEnteredDecimalsCount = getEnteredDecimalsCount;
82
+ exports.getFormattedString = getFormattedString;
83
+ exports.getGroupSeparator = getGroupSeparator;
84
+ exports.getUnformattedNumber = getUnformattedNumber;
85
+ exports.isAllowedInputKey = isAllowedInputKey;
86
+ exports.isInputPossiblyOverflowing = isInputPossiblyOverflowing;
87
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../../src/expressiveMoneyInput/amountInput/utils.ts"],"sourcesContent":["import { formatAmount } from '@transferwise/formatting';\nimport type { KeyboardEvent } from 'react';\n\nexport const getDecimalSeparator = (currency: string, locale: string): string | null => {\n return formatAmount(1.1, currency, locale).replace(/\\p{Number}/gu, '');\n};\n\nexport const getGroupSeparator = (currency: string, locale: string): string | null => {\n return formatAmount(10000000, currency, locale).replace(/\\p{Number}/gu, '')[0];\n};\n\nexport const getDecimalCount = (currency: string, locale: string): number => {\n const decimalSeparator = getDecimalSeparator(currency, locale);\n if (!decimalSeparator) {\n return 0;\n }\n const parts = formatAmount(1.1, currency, locale).split(decimalSeparator);\n return parts.length === 2 ? parts[1].length : 0;\n};\n\nexport const getEnteredDecimalsCount = (value: string, decimalSeparator: string) => {\n return value.split(decimalSeparator)[1]?.length ?? 0;\n};\n\nexport const getUnformattedNumber = ({\n value: formattedValue,\n currency,\n locale,\n}: {\n value: string;\n currency: string;\n locale: string;\n}): number | null => {\n const groupSeparator = getGroupSeparator(currency, locale);\n const decimalSeparator = getDecimalSeparator(currency, locale);\n if (!formattedValue) {\n return null;\n }\n\n // parseFloat can't handle thousands separators\n const withoutGroupSeparator = groupSeparator\n ? formattedValue.replace(/ /gu, '').replace(new RegExp(`\\\\${groupSeparator}`, 'g'), '')\n : formattedValue;\n\n // parseFloat can only handle . as decimal separator\n const withNormalisedDecimalSeparator = decimalSeparator\n ? withoutGroupSeparator.replace(decimalSeparator, '.')\n : withoutGroupSeparator;\n\n const parsedValue = Number.parseFloat(withNormalisedDecimalSeparator);\n return parsedValue;\n};\n\nexport const getFormattedString = ({\n value: unformattedValue,\n currency,\n locale,\n alwaysShowDecimals = false,\n}: {\n value: number;\n currency: string;\n locale: string;\n alwaysShowDecimals?: boolean;\n}): string => {\n const decimalSeparator = getDecimalSeparator(currency, locale);\n // formatAmount rounds extra decimals, so 1.999 will become 2. Instead we will manually strip extra decimals so that it becomes 1.99 after formatting\n const decimalCount = getDecimalCount(currency, locale);\n const unformattedString = unformattedValue.toString();\n\n const [integerPart, decimalPart] = decimalSeparator\n ? unformattedString.split(decimalSeparator)\n : [unformattedString, undefined];\n\n const formattedDecimalPart = decimalPart ? decimalPart.slice(0, decimalCount) : '';\n\n const sanitisedUnformattedValue = Number.parseFloat(`${integerPart}.${formattedDecimalPart}`);\n\n return formatAmount(sanitisedUnformattedValue, currency, locale, {\n alwaysShowDecimals,\n });\n};\n\nexport const isInputPossiblyOverflowing = ({\n ref,\n value,\n}: {\n ref: React.RefObject<HTMLInputElement>;\n value: string;\n}) => {\n const textLength = value.length;\n const inputWidth = ref.current?.clientWidth;\n if (!inputWidth || !textLength) {\n return;\n }\n\n const maxCharactersWithoutOverflow = Math.floor(inputWidth / 19);\n return textLength > maxCharactersWithoutOverflow;\n};\n\nconst allowedInputKeys = new Set([\n 'Backspace',\n 'Delete',\n ',',\n '.',\n 'ArrowLeft',\n 'ArrowRight',\n 'Enter',\n 'Tab',\n]);\n\nexport const isAllowedInputKey = (e: KeyboardEvent<HTMLInputElement>) => {\n const { metaKey, key, ctrlKey } = e;\n const isNumberKey = !Number.isNaN(Number.parseInt(key, 10));\n\n return isNumberKey || metaKey || ctrlKey || allowedInputKeys.has(key);\n};\n"],"names":["getDecimalSeparator","currency","locale","formatAmount","replace","getGroupSeparator","getDecimalCount","decimalSeparator","parts","split","length","getEnteredDecimalsCount","value","getUnformattedNumber","formattedValue","groupSeparator","withoutGroupSeparator","RegExp","withNormalisedDecimalSeparator","parsedValue","Number","parseFloat","getFormattedString","unformattedValue","alwaysShowDecimals","decimalCount","unformattedString","toString","integerPart","decimalPart","undefined","formattedDecimalPart","slice","sanitisedUnformattedValue","isInputPossiblyOverflowing","ref","textLength","inputWidth","current","clientWidth","maxCharactersWithoutOverflow","Math","floor","allowedInputKeys","Set","isAllowedInputKey","e","metaKey","key","ctrlKey","isNumberKey","isNaN","parseInt","has"],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { formatAmount } from '@transferwise/formatting';
2
+
3
+ const getDecimalSeparator = (currency, locale) => {
4
+ return formatAmount(1.1, currency, locale).replace(/\p{Number}/gu, '');
5
+ };
6
+ const getGroupSeparator = (currency, locale) => {
7
+ return formatAmount(10000000, currency, locale).replace(/\p{Number}/gu, '')[0];
8
+ };
9
+ const getDecimalCount = (currency, locale) => {
10
+ const decimalSeparator = getDecimalSeparator(currency, locale);
11
+ if (!decimalSeparator) {
12
+ return 0;
13
+ }
14
+ const parts = formatAmount(1.1, currency, locale).split(decimalSeparator);
15
+ return parts.length === 2 ? parts[1].length : 0;
16
+ };
17
+ const getEnteredDecimalsCount = (value, decimalSeparator) => {
18
+ return value.split(decimalSeparator)[1]?.length ?? 0;
19
+ };
20
+ const getUnformattedNumber = ({
21
+ value: formattedValue,
22
+ currency,
23
+ locale
24
+ }) => {
25
+ const groupSeparator = getGroupSeparator(currency, locale);
26
+ const decimalSeparator = getDecimalSeparator(currency, locale);
27
+ if (!formattedValue) {
28
+ return null;
29
+ }
30
+ // parseFloat can't handle thousands separators
31
+ const withoutGroupSeparator = groupSeparator ? formattedValue.replace(/ /gu, '').replace(new RegExp(`\\${groupSeparator}`, 'g'), '') : formattedValue;
32
+ // parseFloat can only handle . as decimal separator
33
+ const withNormalisedDecimalSeparator = decimalSeparator ? withoutGroupSeparator.replace(decimalSeparator, '.') : withoutGroupSeparator;
34
+ const parsedValue = Number.parseFloat(withNormalisedDecimalSeparator);
35
+ return parsedValue;
36
+ };
37
+ const getFormattedString = ({
38
+ value: unformattedValue,
39
+ currency,
40
+ locale,
41
+ alwaysShowDecimals = false
42
+ }) => {
43
+ const decimalSeparator = getDecimalSeparator(currency, locale);
44
+ // formatAmount rounds extra decimals, so 1.999 will become 2. Instead we will manually strip extra decimals so that it becomes 1.99 after formatting
45
+ const decimalCount = getDecimalCount(currency, locale);
46
+ const unformattedString = unformattedValue.toString();
47
+ const [integerPart, decimalPart] = decimalSeparator ? unformattedString.split(decimalSeparator) : [unformattedString, undefined];
48
+ const formattedDecimalPart = decimalPart ? decimalPart.slice(0, decimalCount) : '';
49
+ const sanitisedUnformattedValue = Number.parseFloat(`${integerPart}.${formattedDecimalPart}`);
50
+ return formatAmount(sanitisedUnformattedValue, currency, locale, {
51
+ alwaysShowDecimals
52
+ });
53
+ };
54
+ const isInputPossiblyOverflowing = ({
55
+ ref,
56
+ value
57
+ }) => {
58
+ const textLength = value.length;
59
+ const inputWidth = ref.current?.clientWidth;
60
+ if (!inputWidth || !textLength) {
61
+ return;
62
+ }
63
+ const maxCharactersWithoutOverflow = Math.floor(inputWidth / 19);
64
+ return textLength > maxCharactersWithoutOverflow;
65
+ };
66
+ const allowedInputKeys = new Set(['Backspace', 'Delete', ',', '.', 'ArrowLeft', 'ArrowRight', 'Enter', 'Tab']);
67
+ const isAllowedInputKey = e => {
68
+ const {
69
+ metaKey,
70
+ key,
71
+ ctrlKey
72
+ } = e;
73
+ const isNumberKey = !Number.isNaN(Number.parseInt(key, 10));
74
+ return isNumberKey || metaKey || ctrlKey || allowedInputKeys.has(key);
75
+ };
76
+
77
+ export { getDecimalCount, getDecimalSeparator, getEnteredDecimalsCount, getFormattedString, getGroupSeparator, getUnformattedNumber, isAllowedInputKey, isInputPossiblyOverflowing };
78
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.mjs","sources":["../../../src/expressiveMoneyInput/amountInput/utils.ts"],"sourcesContent":["import { formatAmount } from '@transferwise/formatting';\nimport type { KeyboardEvent } from 'react';\n\nexport const getDecimalSeparator = (currency: string, locale: string): string | null => {\n return formatAmount(1.1, currency, locale).replace(/\\p{Number}/gu, '');\n};\n\nexport const getGroupSeparator = (currency: string, locale: string): string | null => {\n return formatAmount(10000000, currency, locale).replace(/\\p{Number}/gu, '')[0];\n};\n\nexport const getDecimalCount = (currency: string, locale: string): number => {\n const decimalSeparator = getDecimalSeparator(currency, locale);\n if (!decimalSeparator) {\n return 0;\n }\n const parts = formatAmount(1.1, currency, locale).split(decimalSeparator);\n return parts.length === 2 ? parts[1].length : 0;\n};\n\nexport const getEnteredDecimalsCount = (value: string, decimalSeparator: string) => {\n return value.split(decimalSeparator)[1]?.length ?? 0;\n};\n\nexport const getUnformattedNumber = ({\n value: formattedValue,\n currency,\n locale,\n}: {\n value: string;\n currency: string;\n locale: string;\n}): number | null => {\n const groupSeparator = getGroupSeparator(currency, locale);\n const decimalSeparator = getDecimalSeparator(currency, locale);\n if (!formattedValue) {\n return null;\n }\n\n // parseFloat can't handle thousands separators\n const withoutGroupSeparator = groupSeparator\n ? formattedValue.replace(/ /gu, '').replace(new RegExp(`\\\\${groupSeparator}`, 'g'), '')\n : formattedValue;\n\n // parseFloat can only handle . as decimal separator\n const withNormalisedDecimalSeparator = decimalSeparator\n ? withoutGroupSeparator.replace(decimalSeparator, '.')\n : withoutGroupSeparator;\n\n const parsedValue = Number.parseFloat(withNormalisedDecimalSeparator);\n return parsedValue;\n};\n\nexport const getFormattedString = ({\n value: unformattedValue,\n currency,\n locale,\n alwaysShowDecimals = false,\n}: {\n value: number;\n currency: string;\n locale: string;\n alwaysShowDecimals?: boolean;\n}): string => {\n const decimalSeparator = getDecimalSeparator(currency, locale);\n // formatAmount rounds extra decimals, so 1.999 will become 2. Instead we will manually strip extra decimals so that it becomes 1.99 after formatting\n const decimalCount = getDecimalCount(currency, locale);\n const unformattedString = unformattedValue.toString();\n\n const [integerPart, decimalPart] = decimalSeparator\n ? unformattedString.split(decimalSeparator)\n : [unformattedString, undefined];\n\n const formattedDecimalPart = decimalPart ? decimalPart.slice(0, decimalCount) : '';\n\n const sanitisedUnformattedValue = Number.parseFloat(`${integerPart}.${formattedDecimalPart}`);\n\n return formatAmount(sanitisedUnformattedValue, currency, locale, {\n alwaysShowDecimals,\n });\n};\n\nexport const isInputPossiblyOverflowing = ({\n ref,\n value,\n}: {\n ref: React.RefObject<HTMLInputElement>;\n value: string;\n}) => {\n const textLength = value.length;\n const inputWidth = ref.current?.clientWidth;\n if (!inputWidth || !textLength) {\n return;\n }\n\n const maxCharactersWithoutOverflow = Math.floor(inputWidth / 19);\n return textLength > maxCharactersWithoutOverflow;\n};\n\nconst allowedInputKeys = new Set([\n 'Backspace',\n 'Delete',\n ',',\n '.',\n 'ArrowLeft',\n 'ArrowRight',\n 'Enter',\n 'Tab',\n]);\n\nexport const isAllowedInputKey = (e: KeyboardEvent<HTMLInputElement>) => {\n const { metaKey, key, ctrlKey } = e;\n const isNumberKey = !Number.isNaN(Number.parseInt(key, 10));\n\n return isNumberKey || metaKey || ctrlKey || allowedInputKeys.has(key);\n};\n"],"names":["getDecimalSeparator","currency","locale","formatAmount","replace","getGroupSeparator","getDecimalCount","decimalSeparator","parts","split","length","getEnteredDecimalsCount","value","getUnformattedNumber","formattedValue","groupSeparator","withoutGroupSeparator","RegExp","withNormalisedDecimalSeparator","parsedValue","Number","parseFloat","getFormattedString","unformattedValue","alwaysShowDecimals","decimalCount","unformattedString","toString","integerPart","decimalPart","undefined","formattedDecimalPart","slice","sanitisedUnformattedValue","isInputPossiblyOverflowing","ref","textLength","inputWidth","current","clientWidth","maxCharactersWithoutOverflow","Math","floor","allowedInputKeys","Set","isAllowedInputKey","e","metaKey","key","ctrlKey","isNumberKey","isNaN","parseInt","has"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ var framerMotion = require('framer-motion');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ const AnimatedNumber = ({
7
+ children,
8
+ onClick,
9
+ className
10
+ }) => {
11
+ const reducedMotion = framerMotion.useReducedMotion();
12
+ return /*#__PURE__*/jsxRuntime.jsx(framerMotion.motion.span, {
13
+ className: className,
14
+ "aria-hidden": true,
15
+ initial: {
16
+ zoom: 0.01
17
+ },
18
+ animate: {
19
+ zoom: 1
20
+ },
21
+ exit: {
22
+ zoom: 0.01
23
+ },
24
+ transition: {
25
+ duration: reducedMotion ? 0 : 0.3,
26
+ type: 'tween',
27
+ ease: [0.3, 0, 0.1, 1]
28
+ },
29
+ onClick: () => onClick?.(),
30
+ children: /*#__PURE__*/jsxRuntime.jsx(framerMotion.motion.span, {
31
+ initial: {
32
+ opacity: 0
33
+ },
34
+ animate: {
35
+ opacity: [0, 0, 1]
36
+ },
37
+ exit: {
38
+ opacity: [1, 0, 0]
39
+ },
40
+ transition: {
41
+ duration: reducedMotion ? 0 : 0.3,
42
+ times: [0, 0.5, 1]
43
+ },
44
+ children: children
45
+ })
46
+ });
47
+ };
48
+
49
+ exports.AnimatedNumber = AnimatedNumber;
50
+ //# sourceMappingURL=AnimatedNumber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedNumber.js","sources":["../../../src/expressiveMoneyInput/animatedNumber/AnimatedNumber.tsx"],"sourcesContent":["import { motion, useReducedMotion } from 'framer-motion';\nimport type { ReactNode } from 'react';\n\ninterface Props {\n children: ReactNode;\n onClick?: () => void;\n className?: string;\n}\n\nexport const AnimatedNumber = ({ children, onClick, className }: Props) => {\n const reducedMotion = useReducedMotion();\n\n return (\n <motion.span\n className={className}\n aria-hidden\n initial={{ zoom: 0.01 }}\n animate={{ zoom: 1 }}\n exit={{ zoom: 0.01 }}\n transition={{\n duration: reducedMotion ? 0 : 0.3,\n type: 'tween',\n ease: [0.3, 0, 0.1, 1],\n }}\n onClick={() => onClick?.()}\n >\n <motion.span\n initial={{ opacity: 0 }}\n animate={{ opacity: [0, 0, 1] }}\n exit={{ opacity: [1, 0, 0] }}\n transition={{\n duration: reducedMotion ? 0 : 0.3,\n times: [0, 0.5, 1],\n }}\n >\n {children}\n </motion.span>\n </motion.span>\n );\n};\n"],"names":["AnimatedNumber","children","onClick","className","reducedMotion","useReducedMotion","_jsx","motion","span","initial","zoom","animate","exit","transition","duration","type","ease","opacity","times"],"mappings":";;;;;AASO,MAAMA,cAAc,GAAGA,CAAC;EAAEC,QAAQ;EAAEC,OAAO;AAAEC,EAAAA;AAAS,CAAS,KAAI;AACxE,EAAA,MAAMC,aAAa,GAAGC,6BAAgB,EAAE;AAExC,EAAA,oBACEC,cAAA,CAACC,mBAAM,CAACC,IAAI,EAAA;AACVL,IAAAA,SAAS,EAAEA,SAAU;IACrB,aAAA,EAAA,IAAW;AACXM,IAAAA,OAAO,EAAE;AAAEC,MAAAA,IAAI,EAAE;KAAO;AACxBC,IAAAA,OAAO,EAAE;AAAED,MAAAA,IAAI,EAAE;KAAI;AACrBE,IAAAA,IAAI,EAAE;AAAEF,MAAAA,IAAI,EAAE;KAAO;AACrBG,IAAAA,UAAU,EAAE;AACVC,MAAAA,QAAQ,EAAEV,aAAa,GAAG,CAAC,GAAG,GAAG;AACjCW,MAAAA,IAAI,EAAE,OAAO;MACbC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;KACrB;AACFd,IAAAA,OAAO,EAAEA,MAAMA,OAAO,IAAK;AAAAD,IAAAA,QAAA,eAE3BK,cAAA,CAACC,mBAAM,CAACC,IAAI,EAAA;AACVC,MAAAA,OAAO,EAAE;AAAEQ,QAAAA,OAAO,EAAE;OAAI;AACxBN,MAAAA,OAAO,EAAE;AAAEM,QAAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;OAAI;AAChCL,MAAAA,IAAI,EAAE;AAAEK,QAAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;OAAI;AAC7BJ,MAAAA,UAAU,EAAE;AACVC,QAAAA,QAAQ,EAAEV,aAAa,GAAG,CAAC,GAAG,GAAG;AACjCc,QAAAA,KAAK,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;OACjB;AAAAjB,MAAAA,QAAA,EAEDA;KACU;AACf,GAAa,CAAC;AAElB;;;;"}
@@ -0,0 +1,48 @@
1
+ import { useReducedMotion, motion } from 'framer-motion';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ const AnimatedNumber = ({
5
+ children,
6
+ onClick,
7
+ className
8
+ }) => {
9
+ const reducedMotion = useReducedMotion();
10
+ return /*#__PURE__*/jsx(motion.span, {
11
+ className: className,
12
+ "aria-hidden": true,
13
+ initial: {
14
+ zoom: 0.01
15
+ },
16
+ animate: {
17
+ zoom: 1
18
+ },
19
+ exit: {
20
+ zoom: 0.01
21
+ },
22
+ transition: {
23
+ duration: reducedMotion ? 0 : 0.3,
24
+ type: 'tween',
25
+ ease: [0.3, 0, 0.1, 1]
26
+ },
27
+ onClick: () => onClick?.(),
28
+ children: /*#__PURE__*/jsx(motion.span, {
29
+ initial: {
30
+ opacity: 0
31
+ },
32
+ animate: {
33
+ opacity: [0, 0, 1]
34
+ },
35
+ exit: {
36
+ opacity: [1, 0, 0]
37
+ },
38
+ transition: {
39
+ duration: reducedMotion ? 0 : 0.3,
40
+ times: [0, 0.5, 1]
41
+ },
42
+ children: children
43
+ })
44
+ });
45
+ };
46
+
47
+ export { AnimatedNumber };
48
+ //# sourceMappingURL=AnimatedNumber.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedNumber.mjs","sources":["../../../src/expressiveMoneyInput/animatedNumber/AnimatedNumber.tsx"],"sourcesContent":["import { motion, useReducedMotion } from 'framer-motion';\nimport type { ReactNode } from 'react';\n\ninterface Props {\n children: ReactNode;\n onClick?: () => void;\n className?: string;\n}\n\nexport const AnimatedNumber = ({ children, onClick, className }: Props) => {\n const reducedMotion = useReducedMotion();\n\n return (\n <motion.span\n className={className}\n aria-hidden\n initial={{ zoom: 0.01 }}\n animate={{ zoom: 1 }}\n exit={{ zoom: 0.01 }}\n transition={{\n duration: reducedMotion ? 0 : 0.3,\n type: 'tween',\n ease: [0.3, 0, 0.1, 1],\n }}\n onClick={() => onClick?.()}\n >\n <motion.span\n initial={{ opacity: 0 }}\n animate={{ opacity: [0, 0, 1] }}\n exit={{ opacity: [1, 0, 0] }}\n transition={{\n duration: reducedMotion ? 0 : 0.3,\n times: [0, 0.5, 1],\n }}\n >\n {children}\n </motion.span>\n </motion.span>\n );\n};\n"],"names":["AnimatedNumber","children","onClick","className","reducedMotion","useReducedMotion","_jsx","motion","span","initial","zoom","animate","exit","transition","duration","type","ease","opacity","times"],"mappings":";;;AASO,MAAMA,cAAc,GAAGA,CAAC;EAAEC,QAAQ;EAAEC,OAAO;AAAEC,EAAAA;AAAS,CAAS,KAAI;AACxE,EAAA,MAAMC,aAAa,GAAGC,gBAAgB,EAAE;AAExC,EAAA,oBACEC,GAAA,CAACC,MAAM,CAACC,IAAI,EAAA;AACVL,IAAAA,SAAS,EAAEA,SAAU;IACrB,aAAA,EAAA,IAAW;AACXM,IAAAA,OAAO,EAAE;AAAEC,MAAAA,IAAI,EAAE;KAAO;AACxBC,IAAAA,OAAO,EAAE;AAAED,MAAAA,IAAI,EAAE;KAAI;AACrBE,IAAAA,IAAI,EAAE;AAAEF,MAAAA,IAAI,EAAE;KAAO;AACrBG,IAAAA,UAAU,EAAE;AACVC,MAAAA,QAAQ,EAAEV,aAAa,GAAG,CAAC,GAAG,GAAG;AACjCW,MAAAA,IAAI,EAAE,OAAO;MACbC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;KACrB;AACFd,IAAAA,OAAO,EAAEA,MAAMA,OAAO,IAAK;AAAAD,IAAAA,QAAA,eAE3BK,GAAA,CAACC,MAAM,CAACC,IAAI,EAAA;AACVC,MAAAA,OAAO,EAAE;AAAEQ,QAAAA,OAAO,EAAE;OAAI;AACxBN,MAAAA,OAAO,EAAE;AAAEM,QAAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;OAAI;AAChCL,MAAAA,IAAI,EAAE;AAAEK,QAAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;OAAI;AAC7BJ,MAAAA,UAAU,EAAE;AACVC,QAAAA,QAAQ,EAAEV,aAAa,GAAG,CAAC,GAAG,GAAG;AACjCc,QAAAA,KAAK,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;OACjB;AAAAjB,MAAAA,QAAA,EAEDA;KACU;AACf,GAAa,CAAC;AAElB;;;;"}