@transferwise/components 46.4.0 → 46.6.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 (69) hide show
  1. package/build/i18n/th.json +3 -3
  2. package/build/index.esm.js +177 -239
  3. package/build/index.esm.js.map +1 -1
  4. package/build/index.js +178 -240
  5. package/build/index.js.map +1 -1
  6. package/build/types/common/textFormat/formatWithPattern/formatWithPattern.d.ts +1 -1
  7. package/build/types/common/textFormat/formatWithPattern/formatWithPattern.d.ts.map +1 -1
  8. package/build/types/common/textFormat/getCursorPositionAfterKeystroke/getCursorPositionAfterKeystroke.d.ts +2 -2
  9. package/build/types/common/textFormat/getCursorPositionAfterKeystroke/getCursorPositionAfterKeystroke.d.ts.map +1 -1
  10. package/build/types/common/textFormat/getSymbolsInPatternWithPosition/getSymbolsInPatternWithPosition.d.ts +5 -1
  11. package/build/types/common/textFormat/getSymbolsInPatternWithPosition/getSymbolsInPatternWithPosition.d.ts.map +1 -1
  12. package/build/types/common/textFormat/unformatWithPattern/unformatWithPattern.d.ts +1 -1
  13. package/build/types/common/textFormat/unformatWithPattern/unformatWithPattern.d.ts.map +1 -1
  14. package/build/types/index.d.ts +3 -0
  15. package/build/types/index.d.ts.map +1 -1
  16. package/build/types/inputWithDisplayFormat/InputWithDisplayFormat.d.ts +7 -11
  17. package/build/types/inputWithDisplayFormat/InputWithDisplayFormat.d.ts.map +1 -1
  18. package/build/types/inputWithDisplayFormat/index.d.ts +2 -1
  19. package/build/types/inputWithDisplayFormat/index.d.ts.map +1 -1
  20. package/build/types/inputs/SelectInput.d.ts +2 -2
  21. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  22. package/build/types/moneyInput/MoneyInput.d.ts +45 -31
  23. package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
  24. package/build/types/moneyInput/MoneyInput.messages.d.ts +6 -6
  25. package/build/types/moneyInput/MoneyInput.messages.d.ts.map +1 -1
  26. package/build/types/moneyInput/currencyFormatting.d.ts +2 -2
  27. package/build/types/moneyInput/currencyFormatting.d.ts.map +1 -1
  28. package/build/types/moneyInput/index.d.ts +2 -1
  29. package/build/types/moneyInput/index.d.ts.map +1 -1
  30. package/build/types/textareaWithDisplayFormat/TextareaWithDisplayFormat.d.ts +7 -11
  31. package/build/types/textareaWithDisplayFormat/TextareaWithDisplayFormat.d.ts.map +1 -1
  32. package/build/types/textareaWithDisplayFormat/index.d.ts +2 -1
  33. package/build/types/textareaWithDisplayFormat/index.d.ts.map +1 -1
  34. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts +55 -83
  35. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  36. package/build/types/withDisplayFormat/index.d.ts +2 -1
  37. package/build/types/withDisplayFormat/index.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/common/textFormat/formatWithPattern/{formatWithPattern.js → formatWithPattern.ts} +8 -4
  40. package/src/common/textFormat/getCursorPositionAfterKeystroke/{getCursorPositionAfterKeystroke.js → getCursorPositionAfterKeystroke.ts} +8 -8
  41. package/src/common/textFormat/getSymbolsInPatternWithPosition/{getSymbolsInPatternWithPosition.js → getSymbolsInPatternWithPosition.ts} +7 -2
  42. package/src/common/textFormat/unformatWithPattern/{unformatWithPattern.js → unformatWithPattern.ts} +3 -2
  43. package/src/flowNavigation/FlowNavigation.story.js +1 -1
  44. package/src/i18n/th.json +3 -3
  45. package/src/index.ts +8 -0
  46. package/src/inputWithDisplayFormat/InputWithDisplayFormat.tsx +10 -0
  47. package/src/inputWithDisplayFormat/index.ts +2 -0
  48. package/src/inputs/SelectInput.tsx +2 -2
  49. package/src/moneyInput/{MoneyInput.rtl.spec.js → MoneyInput.rtl.spec.tsx} +4 -4
  50. package/src/moneyInput/MoneyInput.spec.js +109 -49
  51. package/src/moneyInput/MoneyInput.story.tsx +6 -14
  52. package/src/moneyInput/{MoneyInput.js → MoneyInput.tsx} +189 -173
  53. package/src/moneyInput/{currencyFormatting.spec.js → currencyFormatting.spec.ts} +2 -2
  54. package/src/moneyInput/{currencyFormatting.js → currencyFormatting.ts} +7 -10
  55. package/src/moneyInput/index.ts +7 -0
  56. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.spec.js +3 -1
  57. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +32 -0
  58. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.tsx +13 -0
  59. package/src/textareaWithDisplayFormat/index.ts +2 -0
  60. package/src/withDisplayFormat/WithDisplayFormat.spec.js +1 -1
  61. package/src/withDisplayFormat/{WithDisplayFormat.js → WithDisplayFormat.tsx} +127 -107
  62. package/src/withDisplayFormat/index.ts +2 -0
  63. package/src/inputWithDisplayFormat/InputWithDisplayFormat.js +0 -14
  64. package/src/inputWithDisplayFormat/index.js +0 -1
  65. package/src/moneyInput/index.js +0 -1
  66. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.js +0 -14
  67. package/src/textareaWithDisplayFormat/index.js +0 -1
  68. package/src/withDisplayFormat/index.js +0 -1
  69. /package/src/moneyInput/{MoneyInput.messages.js → MoneyInput.messages.ts} +0 -0
@@ -145,7 +145,7 @@ describe('InputWithTextFormat', () => {
145
145
  });
146
146
 
147
147
  describe('set cursor position', () => {
148
- const triggerEventA = { ...triggerEvent, target: { setSelectionRange: () => {} } };
148
+ const triggerEventA = { ...triggerEvent, currentTarget: { setSelectionRange: () => {} } };
149
149
  beforeEach(() => {
150
150
  component.setState({
151
151
  triggerEvent: triggerEventA,
@@ -1,4 +1,3 @@
1
- import PropTypes from 'prop-types';
2
1
  import { Component } from 'react';
3
2
 
4
3
  import { HistoryNavigator } from '../common';
@@ -10,27 +9,93 @@ import {
10
9
  getDistanceToPreviousSymbol,
11
10
  getDistanceToNextSymbol,
12
11
  } from '../common/textFormat';
12
+ import { InputProps } from '../inputs/Input';
13
+ import { TextAreaProps } from '../inputs/TextArea';
14
+
15
+ type HTMLTextElement = HTMLInputElement | HTMLTextAreaElement;
16
+ type TextElementProps = InputProps | TextAreaProps;
17
+
18
+ export type EventType =
19
+ | 'KeyDown'
20
+ | 'Paste'
21
+ | 'Cut'
22
+ | 'Undo'
23
+ | 'Redo'
24
+ | 'Backspace'
25
+ | 'Delete'
26
+ | 'Initial';
27
+
28
+ interface WithDisplayFormatState {
29
+ value: string;
30
+ historyNavigator: HistoryNavigator;
31
+ prevDisplayPattern: string;
32
+ triggerType: EventType;
33
+ triggerEvent: React.KeyboardEvent<HTMLTextElement> | null;
34
+ pastedLength: number;
35
+ selectionStart: number;
36
+ selectionEnd: number;
37
+ }
38
+
39
+ export interface WithDisplayFormatProps<T extends TextElementProps = TextElementProps>
40
+ extends Pick<
41
+ TextElementProps,
42
+ | 'className'
43
+ | 'disabled'
44
+ | 'id'
45
+ | 'maxLength'
46
+ | 'minLength'
47
+ | 'name'
48
+ | 'placeholder'
49
+ | 'readOnly'
50
+ | 'required'
51
+ | 'inputMode'
52
+ > {
53
+ value?: string;
54
+ displayPattern: string;
55
+ /**
56
+ * autocomplete hides our form help so we need to disable it when help text
57
+ * is present. Chrome ignores autocomplete=off, the only way to disable it is
58
+ * to provide an 'invalid' value, for which 'disabled' serves.
59
+ */
60
+ autoComplete?: TextElementProps['autoComplete'] | 'disabled';
61
+ onChange?: (value: string) => void;
62
+ onBlur?: (value: string) => void;
63
+ onFocus?: (value: string) => void;
64
+ render: (renderProps: T) => JSX.Element;
65
+ }
13
66
 
14
- class WithDisplayFormat extends Component {
15
- constructor(props) {
67
+ class WithDisplayFormat<T extends TextElementProps> extends Component<
68
+ WithDisplayFormatProps<T>,
69
+ WithDisplayFormatState
70
+ > {
71
+ declare props: WithDisplayFormatProps<T> &
72
+ Required<Pick<WithDisplayFormatProps<T>, keyof typeof WithDisplayFormat.defaultProps>>;
73
+ static defaultProps = {
74
+ autoComplete: 'off',
75
+ displayPattern: '',
76
+ value: '',
77
+ };
78
+
79
+ constructor(props: WithDisplayFormatProps) {
16
80
  super(props);
17
- const { value, displayPattern } = props;
18
- const unformattedValue = unformatWithPattern(value, displayPattern);
81
+ const unformattedValue = unformatWithPattern(props.value ?? '', props.displayPattern);
19
82
  this.state = {
20
- value: formatWithPattern(unformattedValue, displayPattern),
83
+ value: formatWithPattern(unformattedValue, props.displayPattern),
21
84
  historyNavigator: new HistoryNavigator(),
22
85
  prevDisplayPattern: props.displayPattern,
23
- triggerType: null,
86
+ triggerType: 'Initial',
24
87
  triggerEvent: null,
88
+ selectionStart: 0,
89
+ selectionEnd: 0,
90
+ pastedLength: 0,
25
91
  };
26
92
  }
27
93
 
28
- static getDerivedStateFromProps(nextProps, previousState) {
29
- const { displayPattern } = nextProps;
30
- const { prevDisplayPattern } = previousState;
31
- if (previousState.prevDisplayPattern !== displayPattern) {
32
- const { value, historyNavigator } = previousState;
33
-
94
+ static getDerivedStateFromProps(
95
+ { displayPattern }: WithDisplayFormatProps,
96
+ { prevDisplayPattern = displayPattern, value, historyNavigator }: WithDisplayFormatState,
97
+ ) {
98
+ if (prevDisplayPattern !== displayPattern) {
34
99
  const unFormattedValue = unformatWithPattern(value, prevDisplayPattern);
35
100
  historyNavigator.reset();
36
101
 
@@ -45,43 +110,48 @@ class WithDisplayFormat extends Component {
45
110
  return null;
46
111
  }
47
112
 
48
- getUserAction = (unformattedValue) => {
113
+ getUserAction = (unformattedValue: string): EventType | string => {
49
114
  const { triggerEvent, triggerType, value } = this.state;
50
115
  const { displayPattern } = this.props;
51
116
 
52
- const charCode = String.fromCharCode(triggerEvent.which).toLowerCase();
117
+ if (triggerEvent) {
118
+ const charCode = String.fromCharCode(triggerEvent.which).toLowerCase();
53
119
 
54
- if (triggerType === 'Paste' || triggerType === 'Cut') {
55
- return triggerType;
56
- }
120
+ if (triggerType === 'Paste' || triggerType === 'Cut') {
121
+ return triggerType;
122
+ }
57
123
 
58
- if ((triggerEvent.ctrlKey || triggerEvent.metaKey) && charCode === 'z') {
59
- return triggerEvent.shiftKey ? 'Redo' : 'Undo';
60
- }
61
- // Detect mouse event redo
62
- if (triggerEvent.ctrlKey && charCode === 'd') {
63
- return 'Delete';
64
- }
124
+ if ((triggerEvent.ctrlKey || triggerEvent.metaKey) && charCode === 'z') {
125
+ return triggerEvent.shiftKey ? 'Redo' : 'Undo';
126
+ }
127
+ // Detect mouse event redo
128
+ if (triggerEvent.ctrlKey && charCode === 'd') {
129
+ return 'Delete';
130
+ }
65
131
 
66
- // Android Fix.
67
- if (typeof triggerEvent.key === 'undefined') {
68
- if (unformattedValue.length <= unformatWithPattern(value, displayPattern).length) {
132
+ // Android Fix.
133
+ if (
134
+ typeof triggerEvent.key === 'undefined' &&
135
+ unformattedValue.length <= unformatWithPattern(value, displayPattern).length
136
+ ) {
69
137
  return 'Backspace';
70
138
  }
139
+ return triggerEvent.key;
140
+ } else {
141
+ // triggerEvent can be null only in case of "autofilling" (via password manager extension or browser build-in one) events
142
+ return 'Paste';
71
143
  }
72
-
73
- return triggerEvent.key;
74
144
  };
75
145
 
76
146
  resetEvent = () => {
77
147
  this.setState({
78
- triggerType: null,
148
+ triggerType: 'Initial',
79
149
  triggerEvent: null,
80
150
  pastedLength: 0,
81
151
  });
82
152
  };
83
153
 
84
- detectUndoRedo = (event) => {
154
+ detectUndoRedo = (event: React.KeyboardEvent<HTMLTextElement>) => {
85
155
  const charCode = String.fromCharCode(event.which).toLowerCase();
86
156
  if ((event.ctrlKey || event.metaKey) && charCode === 'z') {
87
157
  return event.shiftKey ? 'Redo' : 'Undo';
@@ -89,9 +159,9 @@ class WithDisplayFormat extends Component {
89
159
  return null;
90
160
  };
91
161
 
92
- handleOnKeyDown = (event) => {
162
+ handleOnKeyDown: React.KeyboardEventHandler<HTMLTextElement> = (event) => {
93
163
  event.persist();
94
- const { selectionStart, selectionEnd } = event.target;
164
+ const { selectionStart, selectionEnd } = event.currentTarget;
95
165
  const { historyNavigator } = this.state;
96
166
  const { displayPattern } = this.props;
97
167
 
@@ -108,13 +178,13 @@ class WithDisplayFormat extends Component {
108
178
  this.setState({
109
179
  triggerEvent: event,
110
180
  triggerType: 'KeyDown',
111
- selectionStart,
112
- selectionEnd,
181
+ selectionStart: selectionStart ?? 0,
182
+ selectionEnd: selectionEnd ?? 0,
113
183
  });
114
184
  }
115
185
  };
116
186
 
117
- handleOnPaste = (event) => {
187
+ handleOnPaste: React.ClipboardEventHandler<HTMLTextElement> = (event) => {
118
188
  const { displayPattern } = this.props;
119
189
  const pastedLength = unformatWithPattern(
120
190
  event.clipboardData.getData('Text'),
@@ -124,27 +194,23 @@ class WithDisplayFormat extends Component {
124
194
  this.setState({ triggerType: 'Paste', pastedLength });
125
195
  };
126
196
 
127
- handleOnCut = () => {
197
+ handleOnCut: React.ClipboardEventHandler<HTMLTextElement> = () => {
128
198
  this.setState({ triggerType: 'Cut' });
129
199
  };
130
200
 
131
- isKeyAllowed = (action) => {
201
+ isKeyAllowed = (action: EventType | string) => {
132
202
  const { displayPattern } = this.props;
133
203
  const symbolsInPattern = displayPattern.split('').filter((character) => character !== '*');
134
204
 
135
205
  return !symbolsInPattern.includes(action);
136
206
  };
137
207
 
138
- handleOnChange = (event) => {
139
- const { historyNavigator, triggerEvent, triggerType } = this.state;
208
+ handleOnChange: React.ChangeEventHandler<HTMLTextElement> = (event) => {
209
+ const { historyNavigator, triggerType } = this.state;
140
210
  const { displayPattern, onChange } = this.props;
141
211
  const { value } = event.target;
142
212
  let unformattedValue = unformatWithPattern(value, displayPattern);
143
- const action =
144
- triggerEvent === null
145
- ? // triggerEvent can be null only in case of "autofilling" (via password manager extension or browser build-in one) events
146
- 'Paste'
147
- : this.getUserAction(unformattedValue);
213
+ const action = this.getUserAction(unformattedValue);
148
214
  if (!this.isKeyAllowed(action) || triggerType === 'Undo' || triggerType === 'Redo') {
149
215
  return;
150
216
  }
@@ -158,19 +224,20 @@ class WithDisplayFormat extends Component {
158
224
 
159
225
  this.handleCursorPositioning(action);
160
226
 
161
- const broadcastValue = unformatWithPattern(newFormattedValue, displayPattern);
162
-
163
- this.setState({ value: newFormattedValue }, this.resetEvent(), onChange(broadcastValue));
227
+ this.setState({ value: newFormattedValue }, () => {
228
+ this.resetEvent();
229
+ if (onChange) {
230
+ const broadcastValue = unformatWithPattern(newFormattedValue, displayPattern);
231
+ onChange(broadcastValue);
232
+ }
233
+ });
164
234
  };
165
235
 
166
- handleOnBlur = (event) => {
167
- const { displayPattern, onBlur } = this.props;
168
- if (onBlur) {
169
- onBlur(unformatWithPattern(event.target.value, displayPattern));
170
- }
236
+ handleOnBlur: React.FocusEventHandler<HTMLTextElement> = (event) => {
237
+ this.props.onBlur?.(unformatWithPattern(event.target.value, this.props.displayPattern));
171
238
  };
172
239
 
173
- handleOnFocus = (event) => {
240
+ handleOnFocus: React.FocusEventHandler<HTMLTextElement> = (event) => {
174
241
  const { displayPattern, onFocus } = this.props;
175
242
  if (onFocus) {
176
243
  this.handleOnChange(event);
@@ -178,7 +245,7 @@ class WithDisplayFormat extends Component {
178
245
  }
179
246
  };
180
247
 
181
- handleDelete = (unformattedValue, action) => {
248
+ handleDelete = (unformattedValue: string, action: EventType) => {
182
249
  const { displayPattern } = this.props;
183
250
  const { selectionStart, selectionEnd } = this.state;
184
251
  const newStack = [...unformattedValue];
@@ -203,7 +270,7 @@ class WithDisplayFormat extends Component {
203
270
  return newStack.join('');
204
271
  };
205
272
 
206
- handleCursorPositioning = (action) => {
273
+ handleCursorPositioning = (action: string) => {
207
274
  const { displayPattern } = this.props;
208
275
  const { triggerEvent, selectionStart, selectionEnd, pastedLength } = this.state;
209
276
 
@@ -217,7 +284,7 @@ class WithDisplayFormat extends Component {
217
284
 
218
285
  setTimeout(() => {
219
286
  if (triggerEvent) {
220
- triggerEvent.target.setSelectionRange(cursorPosition, cursorPosition);
287
+ triggerEvent.currentTarget.setSelectionRange(cursorPosition, cursorPosition);
221
288
  }
222
289
  this.setState({ selectionStart: cursorPosition, selectionEnd: cursorPosition });
223
290
  }, 0);
@@ -225,7 +292,6 @@ class WithDisplayFormat extends Component {
225
292
 
226
293
  render() {
227
294
  const {
228
- type,
229
295
  inputMode,
230
296
  className,
231
297
  id,
@@ -239,8 +305,7 @@ class WithDisplayFormat extends Component {
239
305
  autoComplete,
240
306
  } = this.props;
241
307
  const { value } = this.state;
242
- const renderProps = {
243
- type,
308
+ const renderProps: TextElementProps = {
244
309
  inputMode,
245
310
  className,
246
311
  id,
@@ -260,53 +325,8 @@ class WithDisplayFormat extends Component {
260
325
  onChange: this.handleOnChange,
261
326
  onCut: this.handleOnCut,
262
327
  };
263
- return this.props.render(renderProps);
328
+ return this.props.render(renderProps as T);
264
329
  }
265
330
  }
266
331
 
267
- WithDisplayFormat.propTypes = {
268
- /**
269
- * autocomplete hides our form help so we need to disable it when help text
270
- * is present. Chrome ignores autocomplete=off, the only way to disable it is
271
- * to provide an 'invalid' value, for which 'disabled' serves.
272
- */
273
- autoComplete: PropTypes.oneOf(['on', 'off', 'disabled']),
274
- className: PropTypes.string,
275
- disabled: PropTypes.bool,
276
- id: PropTypes.string,
277
- maxLength: PropTypes.number,
278
- minLength: PropTypes.number,
279
- name: PropTypes.string,
280
- onFocus: PropTypes.func,
281
- onBlur: PropTypes.func,
282
- onChange: PropTypes.func.isRequired,
283
- placeholder: PropTypes.string,
284
- readOnly: PropTypes.bool,
285
- render: PropTypes.func.isRequired,
286
- required: PropTypes.bool,
287
- displayPattern: PropTypes.string,
288
- type: PropTypes.string,
289
- inputMode: PropTypes.string,
290
- value: PropTypes.string,
291
- };
292
-
293
- WithDisplayFormat.defaultProps = {
294
- autoComplete: 'off',
295
- className: null,
296
- disabled: false,
297
- id: null,
298
- maxLength: null,
299
- minLength: null,
300
- name: null,
301
- placeholder: null,
302
- readOnly: false,
303
- required: false,
304
- displayPattern: '',
305
- type: 'text',
306
- inputMode: null,
307
- value: '',
308
- onFocus: null,
309
- onBlur: null,
310
- };
311
-
312
332
  export default WithDisplayFormat;
@@ -0,0 +1,2 @@
1
+ export { default } from './WithDisplayFormat';
2
+ export type { WithDisplayFormatProps } from './WithDisplayFormat';
@@ -1,14 +0,0 @@
1
- import PropTypes from 'prop-types';
2
-
3
- import WithDisplayFormat from '../withDisplayFormat';
4
-
5
- const InputWithDisplayFormat = (props) => (
6
- <WithDisplayFormat {...props} render={(renderProps) => <input {...renderProps} />} />
7
- );
8
-
9
- InputWithDisplayFormat.propTypes = {
10
- displayPattern: PropTypes.string.isRequired,
11
- onChange: PropTypes.func.isRequired,
12
- };
13
-
14
- export default InputWithDisplayFormat;
@@ -1 +0,0 @@
1
- export { default } from './InputWithDisplayFormat';
@@ -1 +0,0 @@
1
- export { default } from './MoneyInput';
@@ -1,14 +0,0 @@
1
- import PropTypes from 'prop-types';
2
-
3
- import WithDisplayFormat from '../withDisplayFormat';
4
-
5
- const TextareaWithDisplayFormat = (props) => (
6
- <WithDisplayFormat {...props} render={(renderProps) => <textarea {...renderProps} />} />
7
- );
8
-
9
- TextareaWithDisplayFormat.propTypes = {
10
- displayPattern: PropTypes.string.isRequired,
11
- onChange: PropTypes.func.isRequired,
12
- };
13
-
14
- export default TextareaWithDisplayFormat;
@@ -1 +0,0 @@
1
- export { default } from './TextareaWithDisplayFormat';
@@ -1 +0,0 @@
1
- export { default } from './WithDisplayFormat';