@momo-kits/foundation 0.165.2-beta.2 → 0.167.1-beta.1

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 (2) hide show
  1. package/Input/InputMoney.tsx +53 -160
  2. package/package.json +1 -1
@@ -9,10 +9,8 @@ import React, {
9
9
  } from 'react';
10
10
  import {
11
11
  NativeSyntheticEvent,
12
- Platform,
13
12
  TextInput,
14
13
  TextInputFocusEventData,
15
- TextInputSelectionChangeEventData,
16
14
  TouchableOpacity,
17
15
  View,
18
16
  } from 'react-native';
@@ -34,124 +32,9 @@ import {
34
32
  } from './common';
35
33
  import { InputMoneyProps } from './index';
36
34
  import styles from './styles';
35
+ import { formatNumberToMoney } from './utils';
37
36
  import SystemTextInput from './SystemTextInput';
38
37
 
39
- type Selection = { start: number; end: number };
40
- type Edit = { start: number; end: number; inserted: string };
41
-
42
- const countDigits = (text: string) => text.replace(/\D/g, '').length;
43
-
44
- const groupDigits = (digits: string) =>
45
- digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
46
-
47
- // Caret position right after the `digitIndex`-th digit of `display`.
48
- const caretFromDigitIndex = (display: string, digitIndex: number) => {
49
- let count = 0;
50
- for (let i = 0; i < display.length && count < digitIndex; i++) {
51
- if (/\d/.test(display[i])) {
52
- count += 1;
53
- if (count === digitIndex) return i + 1;
54
- }
55
- }
56
- return 0;
57
- };
58
-
59
- // Finds which range of `old` was replaced to produce `next`. `selection` is
60
- // the pre-edit selection on iOS and already the post-edit caret on Android,
61
- // where onSelectionChange fires before onChangeText.
62
- const detectEdit = (old: string, next: string, selection: Selection): Edit => {
63
- const { start, end } = selection;
64
- const removed = old.length - next.length;
65
- const preEdit: Edit[] = [
66
- {
67
- start,
68
- end,
69
- inserted: next.slice(start, next.length - (old.length - end)),
70
- },
71
- { start: start - removed, end: start, inserted: '' },
72
- { start, end: start + removed, inserted: '' },
73
- ];
74
- const postEdit: Edit[] = [];
75
- const tail = next.length - start;
76
- if (start === end && tail <= old.length) {
77
- let prefix = 0;
78
- while (
79
- prefix < Math.min(start, old.length - tail) &&
80
- old[prefix] === next[prefix]
81
- ) {
82
- prefix += 1;
83
- }
84
- postEdit.push({
85
- start: prefix,
86
- end: old.length - tail,
87
- inserted: next.slice(prefix, start),
88
- });
89
- }
90
- const candidates =
91
- Platform.OS === 'android'
92
- ? [...postEdit, ...preEdit]
93
- : [...preEdit, ...postEdit];
94
- const match = candidates.find(
95
- e =>
96
- e.start >= 0 &&
97
- e.end >= e.start &&
98
- e.end <= old.length &&
99
- old.slice(0, e.start) + e.inserted + old.slice(e.end) === next,
100
- );
101
- if (match) return match;
102
-
103
- let prefix = 0;
104
- while (
105
- prefix < Math.min(old.length, next.length) &&
106
- old[prefix] === next[prefix]
107
- ) {
108
- prefix += 1;
109
- }
110
- let suffix = 0;
111
- while (
112
- suffix < Math.min(old.length, next.length) - prefix &&
113
- old[old.length - 1 - suffix] === next[next.length - 1 - suffix]
114
- ) {
115
- suffix += 1;
116
- }
117
- return {
118
- start: prefix,
119
- end: old.length - suffix,
120
- inserted: next.slice(prefix, next.length - suffix),
121
- };
122
- };
123
-
124
- // Applies `edit` to the digits of `display`. Separators and currency are never
125
- // deleted: removing a separator deletes the digit before it instead, removing
126
- // only the currency leaves the digits untouched.
127
- const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
128
- const digits = display.replace(/\D/g, '');
129
- let left = countDigits(display.slice(0, edit.start));
130
- let removed = countDigits(display.slice(edit.start, edit.end));
131
- const inserted = edit.inserted.replace(/\D/g, '');
132
- if (
133
- !removed &&
134
- !inserted &&
135
- left > 0 &&
136
- display.slice(edit.start, edit.end).includes('.')
137
- ) {
138
- left -= 1;
139
- removed = 1;
140
- }
141
- const next = digits.slice(0, left) + inserted + digits.slice(left + removed);
142
- const trimmed = next.replace(/^0+(?=\d)/, '');
143
- if ((!removed && !inserted) || trimmed.length > digitLimit) {
144
- return { digits, caretDigit: left };
145
- }
146
- return {
147
- digits: trimmed,
148
- caretDigit: Math.max(
149
- 0,
150
- left + inserted.length - (next.length - trimmed.length),
151
- ),
152
- };
153
- };
154
-
155
38
  const InputMoney = forwardRef(
156
39
  (
157
40
  {
@@ -190,8 +73,7 @@ const InputMoney = forwardRef(
190
73
  const [focused, setFocused] = useState(false);
191
74
  const [displayValue, setDisplayValue] = useState('');
192
75
  const [numericValue, setNumericValue] = useState('');
193
- const [selection, setSelection] = useState<Selection>();
194
- const selectionRef = useRef<Selection>({ start: 0, end: 0 });
76
+ const [lastDisplayValue, setLastDisplayValue] = useState('');
195
77
  const inputRef = useRef<TextInput>(null);
196
78
  const componentName = 'InputMoney';
197
79
 
@@ -209,21 +91,25 @@ const InputMoney = forwardRef(
209
91
 
210
92
  useEffect(() => {
211
93
  if (value !== undefined && typeof value === 'string') {
212
- const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
94
+ const cleanValue = value.replace(/\D/g, '');
213
95
  if (cleanValue !== numericValue) {
214
- commit(cleanValue, cleanValue.length);
96
+ setNumericValue(cleanValue);
97
+ updateDisplayValue(cleanValue);
215
98
  }
216
99
  }
217
100
  }, [value]);
218
101
 
219
- const commit = (digits: string, caretDigit: number) => {
220
- const display = digits ? `${groupDigits(digits)}${currency}` : '';
221
- const caret = caretFromDigitIndex(display, caretDigit);
222
- selectionRef.current = { start: caret, end: caret };
223
- setSelection({ start: caret, end: caret });
224
- setDisplayValue(display);
225
- setNumericValue(digits);
226
- return digits !== numericValue;
102
+ useEffect(() => {
103
+ setLastDisplayValue(displayValue);
104
+ }, [displayValue]);
105
+
106
+ const updateDisplayValue = (numeric: string) => {
107
+ if (!numeric) {
108
+ setDisplayValue('');
109
+ return;
110
+ }
111
+ const formatted = formatNumberToMoney(Number(numeric));
112
+ setDisplayValue(formatted ? `${formatted}${currency}` : '');
227
113
  };
228
114
 
229
115
  const parseInputText = (text: string): string => {
@@ -233,35 +119,47 @@ const InputMoney = forwardRef(
233
119
  return textWithoutCurrency.replace(/[^\d]/g, '');
234
120
  };
235
121
 
122
+ const updateNumericValue = (val: string) => {
123
+ let next = val.replace(/[^\d]/g, '');
124
+ if (next.length > digitLimit) next = next.slice(0, digitLimit);
125
+ if (/^\d*$/.test(next)) {
126
+ setNumericValue(next);
127
+ updateDisplayValue(next);
128
+ onChangeText?.(next);
129
+ }
130
+ };
131
+
236
132
  const onClearText = () => {
237
133
  inputRef?.current?.clear();
238
134
  inputRef.current?.setNativeProps({ text: '' });
239
- commit('', 0);
135
+ setDisplayValue('');
136
+ setNumericValue('');
240
137
  onChangeText?.('');
241
138
  };
242
139
 
243
140
  const _onChangeText = (text: string) => {
244
- const { digits, caretDigit } = text
245
- ? applyEdit(
246
- displayValue,
247
- detectEdit(displayValue, text, selectionRef.current),
248
- digitLimit,
249
- )
250
- : { digits: '', caretDigit: 0 };
251
- if (commit(digits, caretDigit)) {
252
- onChangeText?.(digits);
141
+ if (!text) {
142
+ setDisplayValue('');
143
+ setNumericValue('');
144
+ onChangeText?.('');
145
+ return;
253
146
  }
254
- };
255
-
256
- // Keeps the caret out of the currency suffix.
257
- const _onSelectionChange = (
258
- e: NativeSyntheticEvent<TextInputSelectionChangeEventData>,
259
- ) => {
260
- const { start, end } = e.nativeEvent.selection;
261
- selectionRef.current = { start, end };
262
- const max = displayValue ? displayValue.length - currency.length : 0;
263
- setSelection({ start: Math.min(start, max), end: Math.min(end, max) });
264
- props.onSelectionChange?.(e);
147
+ if (text.length < lastDisplayValue.length) {
148
+ const lastChar = lastDisplayValue.charAt(lastDisplayValue.length - 1);
149
+ const isRemovingCurrency =
150
+ lastDisplayValue.endsWith(currency) || lastChar === ' ';
151
+ const isRemovingDot = lastChar === '.';
152
+ if (isRemovingCurrency || isRemovingDot) {
153
+ const currentNumericValue = numericValue;
154
+ if (currentNumericValue.length > 0) {
155
+ const newNumericValue = currentNumericValue.slice(0, -1);
156
+ updateNumericValue(newNumericValue);
157
+ return;
158
+ }
159
+ }
160
+ }
161
+ const newNumericValue = parseInputText(text);
162
+ updateNumericValue(newNumericValue);
265
163
  };
266
164
 
267
165
  const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
@@ -276,16 +174,13 @@ const InputMoney = forwardRef(
276
174
 
277
175
  const _setText = (text: string) => {
278
176
  if (!text) {
279
- commit('', 0);
177
+ setDisplayValue('');
178
+ setNumericValue('');
280
179
  onChangeText?.('');
281
180
  return;
282
181
  }
283
- const digits = parseInputText(text)
284
- .replace(/^0+(?=\d)/, '')
285
- .slice(0, digitLimit);
286
- if (commit(digits, digits.length)) {
287
- onChangeText?.(digits);
288
- }
182
+ const newNumericValue = parseInputText(text);
183
+ updateNumericValue(newNumericValue);
289
184
  };
290
185
 
291
186
  useImperativeHandle(ref, () => {
@@ -352,8 +247,6 @@ const InputMoney = forwardRef(
352
247
  textBreakStrategy="highQuality"
353
248
  value={displayValue}
354
249
  onChangeText={_onChangeText}
355
- selection={selection}
356
- onSelectionChange={_onSelectionChange}
357
250
  onFocus={_onFocus}
358
251
  onBlur={_onBlur}
359
252
  placeholder={placeholder}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.165.2-beta.2",
3
+ "version": "0.167.1-beta.1",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},