@momo-kits/foundation 0.168.1-beta.1 → 0.168.1-beta.3
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/Input/InputMoney.tsx +62 -173
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -4,14 +4,12 @@ import React, {
|
|
|
4
4
|
useContext,
|
|
5
5
|
useEffect,
|
|
6
6
|
useImperativeHandle,
|
|
7
|
-
useLayoutEffect,
|
|
8
7
|
useRef,
|
|
9
8
|
useState,
|
|
10
9
|
} from 'react';
|
|
11
10
|
import {
|
|
12
11
|
TextInput,
|
|
13
12
|
TextInputFocusEvent,
|
|
14
|
-
TextInputSelectionChangeEvent,
|
|
15
13
|
TouchableOpacity,
|
|
16
14
|
View,
|
|
17
15
|
} from 'react-native';
|
|
@@ -33,101 +31,9 @@ import {
|
|
|
33
31
|
} from './common';
|
|
34
32
|
import { InputMoneyProps } from './index';
|
|
35
33
|
import styles from './styles';
|
|
34
|
+
import { formatNumberToMoney } from './utils';
|
|
36
35
|
import SystemTextInput from './SystemTextInput';
|
|
37
36
|
|
|
38
|
-
type Selection = { start: number; end: number };
|
|
39
|
-
type Edit = { start: number; end: number; inserted: string };
|
|
40
|
-
|
|
41
|
-
const countDigits = (text: string) => text.replace(/\D/g, '').length;
|
|
42
|
-
|
|
43
|
-
const groupDigits = (digits: string) =>
|
|
44
|
-
digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
45
|
-
|
|
46
|
-
// Caret position right after the `digitIndex`-th digit of `display`.
|
|
47
|
-
const caretFromDigitIndex = (display: string, digitIndex: number) => {
|
|
48
|
-
let count = 0;
|
|
49
|
-
for (let i = 0; i < display.length && count < digitIndex; i++) {
|
|
50
|
-
if (/\d/.test(display[i])) {
|
|
51
|
-
count += 1;
|
|
52
|
-
if (count === digitIndex) return i + 1;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return 0;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const detectEdit = (old: string, next: string, selection: Selection): Edit => {
|
|
59
|
-
const { start, end } = selection;
|
|
60
|
-
const removed = old.length - next.length;
|
|
61
|
-
const candidates: Edit[] = [
|
|
62
|
-
{
|
|
63
|
-
start,
|
|
64
|
-
end,
|
|
65
|
-
inserted: next.slice(start, next.length - (old.length - end)),
|
|
66
|
-
},
|
|
67
|
-
{ start: start - removed, end: start, inserted: '' },
|
|
68
|
-
{ start, end: start + removed, inserted: '' },
|
|
69
|
-
];
|
|
70
|
-
const match = candidates.find(
|
|
71
|
-
e =>
|
|
72
|
-
e.start >= 0 &&
|
|
73
|
-
e.end >= e.start &&
|
|
74
|
-
e.end <= old.length &&
|
|
75
|
-
old.slice(0, e.start) + e.inserted + old.slice(e.end) === next,
|
|
76
|
-
);
|
|
77
|
-
if (match) return match;
|
|
78
|
-
|
|
79
|
-
let prefix = 0;
|
|
80
|
-
while (
|
|
81
|
-
prefix < Math.min(old.length, next.length) &&
|
|
82
|
-
old[prefix] === next[prefix]
|
|
83
|
-
) {
|
|
84
|
-
prefix += 1;
|
|
85
|
-
}
|
|
86
|
-
let suffix = 0;
|
|
87
|
-
while (
|
|
88
|
-
suffix < Math.min(old.length, next.length) - prefix &&
|
|
89
|
-
old[old.length - 1 - suffix] === next[next.length - 1 - suffix]
|
|
90
|
-
) {
|
|
91
|
-
suffix += 1;
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
start: prefix,
|
|
95
|
-
end: old.length - suffix,
|
|
96
|
-
inserted: next.slice(prefix, next.length - suffix),
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Applies `edit` to the digits of `display`. Separators and currency are never
|
|
101
|
-
// deleted: removing a separator deletes the digit before it instead, removing
|
|
102
|
-
// only the currency leaves the digits untouched.
|
|
103
|
-
const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
|
|
104
|
-
const digits = display.replace(/\D/g, '');
|
|
105
|
-
let left = countDigits(display.slice(0, edit.start));
|
|
106
|
-
let removed = countDigits(display.slice(edit.start, edit.end));
|
|
107
|
-
const inserted = edit.inserted.replace(/\D/g, '');
|
|
108
|
-
if (
|
|
109
|
-
!removed &&
|
|
110
|
-
!inserted &&
|
|
111
|
-
left > 0 &&
|
|
112
|
-
display.slice(edit.start, edit.end).includes('.')
|
|
113
|
-
) {
|
|
114
|
-
left -= 1;
|
|
115
|
-
removed = 1;
|
|
116
|
-
}
|
|
117
|
-
const next = digits.slice(0, left) + inserted + digits.slice(left + removed);
|
|
118
|
-
const trimmed = next.replace(/^0+(?=\d)/, '');
|
|
119
|
-
if ((!removed && !inserted) || trimmed.length > digitLimit) {
|
|
120
|
-
return { digits, caretDigit: left };
|
|
121
|
-
}
|
|
122
|
-
return {
|
|
123
|
-
digits: trimmed,
|
|
124
|
-
caretDigit: Math.max(
|
|
125
|
-
0,
|
|
126
|
-
left + inserted.length - (next.length - trimmed.length),
|
|
127
|
-
),
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
37
|
const InputMoney = forwardRef(
|
|
132
38
|
(
|
|
133
39
|
{
|
|
@@ -166,13 +72,7 @@ const InputMoney = forwardRef(
|
|
|
166
72
|
const [focused, setFocused] = useState(false);
|
|
167
73
|
const [displayValue, setDisplayValue] = useState('');
|
|
168
74
|
const [numericValue, setNumericValue] = useState('');
|
|
169
|
-
const [
|
|
170
|
-
const displayRef = useRef('');
|
|
171
|
-
const selectionRef = useRef<Selection>({ start: 0, end: 0 });
|
|
172
|
-
// Caret to push to native once the committed text is rendered.
|
|
173
|
-
const pendingCaretRef = useRef<number | null>(null);
|
|
174
|
-
const pendingSelectionRef = useRef<Selection | null>(null);
|
|
175
|
-
const editingRef = useRef(false);
|
|
75
|
+
const [lastDisplayValue, setLastDisplayValue] = useState('');
|
|
176
76
|
const inputRef = useRef<TextInput>(null);
|
|
177
77
|
const componentName = 'InputMoney';
|
|
178
78
|
|
|
@@ -191,34 +91,23 @@ const InputMoney = forwardRef(
|
|
|
191
91
|
if (value !== undefined && typeof value === 'string') {
|
|
192
92
|
const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
|
|
193
93
|
if (cleanValue !== numericValue) {
|
|
194
|
-
|
|
94
|
+
setNumericValue(cleanValue);
|
|
95
|
+
updateDisplayValue(cleanValue);
|
|
195
96
|
}
|
|
196
97
|
}
|
|
197
98
|
}, [value]);
|
|
198
99
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
useLayoutEffect(() => {
|
|
203
|
-
const caret = pendingCaretRef.current;
|
|
204
|
-
if (caret === null) return;
|
|
205
|
-
pendingCaretRef.current = null;
|
|
206
|
-
if (inputRef.current?.isFocused()) {
|
|
207
|
-
inputRef.current.setSelection(caret, caret);
|
|
208
|
-
}
|
|
209
|
-
}, [commitCount]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
setLastDisplayValue(displayValue);
|
|
102
|
+
}, [displayValue]);
|
|
210
103
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
setCommitCount(count => count + 1);
|
|
219
|
-
setDisplayValue(display);
|
|
220
|
-
setNumericValue(digits);
|
|
221
|
-
return changed;
|
|
104
|
+
const updateDisplayValue = (numeric: string) => {
|
|
105
|
+
if (!numeric) {
|
|
106
|
+
setDisplayValue('');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const formatted = formatNumberToMoney(Number(numeric));
|
|
110
|
+
setDisplayValue(formatted ? `${formatted}${currency}` : '');
|
|
222
111
|
};
|
|
223
112
|
|
|
224
113
|
const parseInputText = (text: string): string => {
|
|
@@ -228,55 +117,59 @@ const InputMoney = forwardRef(
|
|
|
228
117
|
return textWithoutCurrency.replace(/[^\d]/g, '');
|
|
229
118
|
};
|
|
230
119
|
|
|
120
|
+
const updateNumericValue = (val: string) => {
|
|
121
|
+
let next = val.replace(/[^\d]/g, '').replace(/^0+(?=\d)/, '');
|
|
122
|
+
if (next.length > digitLimit) next = next.slice(0, digitLimit);
|
|
123
|
+
if (/^\d*$/.test(next)) {
|
|
124
|
+
setNumericValue(next);
|
|
125
|
+
updateDisplayValue(next);
|
|
126
|
+
onChangeText?.(next);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
231
130
|
const onClearText = () => {
|
|
232
131
|
inputRef?.current?.clear();
|
|
233
132
|
inputRef.current?.setNativeProps({ text: '' });
|
|
234
|
-
|
|
133
|
+
setDisplayValue('');
|
|
134
|
+
setNumericValue('');
|
|
235
135
|
onChangeText?.('');
|
|
236
136
|
};
|
|
237
137
|
|
|
238
138
|
const _onChangeText = (text: string) => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
editingRef.current = false;
|
|
245
|
-
});
|
|
246
|
-
if (text) {
|
|
247
|
-
const edit = detectEdit(displayRef.current, text, selectionRef.current);
|
|
248
|
-
({ digits, caretDigit } = applyEdit(
|
|
249
|
-
displayRef.current,
|
|
250
|
-
edit,
|
|
251
|
-
digitLimit,
|
|
252
|
-
));
|
|
253
|
-
}
|
|
254
|
-
if (commit(digits, caretDigit)) {
|
|
255
|
-
onChangeText?.(digits);
|
|
139
|
+
if (!text) {
|
|
140
|
+
setDisplayValue('');
|
|
141
|
+
setNumericValue('');
|
|
142
|
+
onChangeText?.('');
|
|
143
|
+
return;
|
|
256
144
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const display = displayRef.current;
|
|
270
|
-
const max = display ? display.length - currency.length : 0;
|
|
271
|
-
const clamped = {
|
|
272
|
-
start: Math.min(start, max),
|
|
273
|
-
end: Math.min(end, max),
|
|
274
|
-
};
|
|
275
|
-
selectionRef.current = clamped;
|
|
276
|
-
if (clamped.start !== start || clamped.end !== end) {
|
|
277
|
-
inputRef.current?.setSelection(clamped.start, clamped.end);
|
|
145
|
+
if (text.length < lastDisplayValue.length) {
|
|
146
|
+
const old = lastDisplayValue;
|
|
147
|
+
let start = 0;
|
|
148
|
+
while (start < text.length && old[start] === text[start]) {
|
|
149
|
+
start += 1;
|
|
150
|
+
}
|
|
151
|
+
let suffix = 0;
|
|
152
|
+
while (
|
|
153
|
+
suffix < text.length - start &&
|
|
154
|
+
old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
|
|
155
|
+
) {
|
|
156
|
+
suffix += 1;
|
|
278
157
|
}
|
|
279
|
-
|
|
158
|
+
const removed = old.slice(start, old.length - suffix);
|
|
159
|
+
if (!/\d/.test(removed)) {
|
|
160
|
+
const index = removed.includes('.')
|
|
161
|
+
? parseInputText(old.slice(0, start)).length - 1
|
|
162
|
+
: numericValue.length - 1;
|
|
163
|
+
if (index >= 0) {
|
|
164
|
+
updateNumericValue(
|
|
165
|
+
numericValue.slice(0, index) + numericValue.slice(index + 1),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const newNumericValue = parseInputText(text);
|
|
172
|
+
updateNumericValue(newNumericValue);
|
|
280
173
|
};
|
|
281
174
|
|
|
282
175
|
const _onFocus = (e: TextInputFocusEvent) => {
|
|
@@ -291,16 +184,13 @@ const InputMoney = forwardRef(
|
|
|
291
184
|
|
|
292
185
|
const _setText = (text: string) => {
|
|
293
186
|
if (!text) {
|
|
294
|
-
|
|
187
|
+
setDisplayValue('');
|
|
188
|
+
setNumericValue('');
|
|
295
189
|
onChangeText?.('');
|
|
296
190
|
return;
|
|
297
191
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
.slice(0, digitLimit);
|
|
301
|
-
if (commit(digits, digits.length)) {
|
|
302
|
-
onChangeText?.(digits);
|
|
303
|
-
}
|
|
192
|
+
const newNumericValue = parseInputText(text);
|
|
193
|
+
updateNumericValue(newNumericValue);
|
|
304
194
|
};
|
|
305
195
|
|
|
306
196
|
useImperativeHandle(ref, () => {
|
|
@@ -367,7 +257,6 @@ const InputMoney = forwardRef(
|
|
|
367
257
|
textBreakStrategy="highQuality"
|
|
368
258
|
value={displayValue}
|
|
369
259
|
onChangeText={_onChangeText}
|
|
370
|
-
onSelectionChange={_onSelectionChange}
|
|
371
260
|
onFocus={_onFocus}
|
|
372
261
|
onBlur={_onBlur}
|
|
373
262
|
placeholder={placeholder}
|