@momo-kits/foundation 0.168.1-beta.2 → 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 +80 -172
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
1
2
|
import React, {
|
|
2
3
|
forwardRef,
|
|
3
4
|
useContext,
|
|
4
5
|
useEffect,
|
|
5
6
|
useImperativeHandle,
|
|
6
|
-
useLayoutEffect,
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import {
|
|
11
11
|
TextInput,
|
|
12
12
|
TextInputFocusEvent,
|
|
13
|
-
TextInputSelectionChangeEvent,
|
|
14
13
|
TouchableOpacity,
|
|
15
14
|
View,
|
|
16
15
|
} from 'react-native';
|
|
@@ -32,97 +31,9 @@ import {
|
|
|
32
31
|
} from './common';
|
|
33
32
|
import { InputMoneyProps } from './index';
|
|
34
33
|
import styles from './styles';
|
|
34
|
+
import { formatNumberToMoney } from './utils';
|
|
35
35
|
import SystemTextInput from './SystemTextInput';
|
|
36
36
|
|
|
37
|
-
type Selection = { start: number; end: number };
|
|
38
|
-
type Edit = { start: number; end: number; inserted: string };
|
|
39
|
-
|
|
40
|
-
const countDigits = (text: string) => text.replace(/\D/g, '').length;
|
|
41
|
-
|
|
42
|
-
const groupDigits = (digits: string) =>
|
|
43
|
-
digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
44
|
-
|
|
45
|
-
const caretFromDigitIndex = (display: string, digitIndex: number) => {
|
|
46
|
-
let count = 0;
|
|
47
|
-
for (let i = 0; i < display.length && count < digitIndex; i++) {
|
|
48
|
-
if (/\d/.test(display[i])) {
|
|
49
|
-
count += 1;
|
|
50
|
-
if (count === digitIndex) return i + 1;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return 0;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const detectEdit = (old: string, next: string, selection: Selection): Edit => {
|
|
57
|
-
const { start, end } = selection;
|
|
58
|
-
const removed = old.length - next.length;
|
|
59
|
-
const candidates: Edit[] = [
|
|
60
|
-
{
|
|
61
|
-
start,
|
|
62
|
-
end,
|
|
63
|
-
inserted: next.slice(start, next.length - (old.length - end)),
|
|
64
|
-
},
|
|
65
|
-
{ start: start - removed, end: start, inserted: '' },
|
|
66
|
-
{ start, end: start + removed, inserted: '' },
|
|
67
|
-
];
|
|
68
|
-
const match = candidates.find(
|
|
69
|
-
e =>
|
|
70
|
-
e.start >= 0 &&
|
|
71
|
-
e.end >= e.start &&
|
|
72
|
-
e.end <= old.length &&
|
|
73
|
-
old.slice(0, e.start) + e.inserted + old.slice(e.end) === next,
|
|
74
|
-
);
|
|
75
|
-
if (match) return match;
|
|
76
|
-
|
|
77
|
-
let prefix = 0;
|
|
78
|
-
while (
|
|
79
|
-
prefix < Math.min(old.length, next.length) &&
|
|
80
|
-
old[prefix] === next[prefix]
|
|
81
|
-
) {
|
|
82
|
-
prefix += 1;
|
|
83
|
-
}
|
|
84
|
-
let suffix = 0;
|
|
85
|
-
while (
|
|
86
|
-
suffix < Math.min(old.length, next.length) - prefix &&
|
|
87
|
-
old[old.length - 1 - suffix] === next[next.length - 1 - suffix]
|
|
88
|
-
) {
|
|
89
|
-
suffix += 1;
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
start: prefix,
|
|
93
|
-
end: old.length - suffix,
|
|
94
|
-
inserted: next.slice(prefix, next.length - suffix),
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
|
|
99
|
-
const digits = display.replace(/\D/g, '');
|
|
100
|
-
let left = countDigits(display.slice(0, edit.start));
|
|
101
|
-
let removed = countDigits(display.slice(edit.start, edit.end));
|
|
102
|
-
const inserted = edit.inserted.replace(/\D/g, '');
|
|
103
|
-
if (
|
|
104
|
-
!removed &&
|
|
105
|
-
!inserted &&
|
|
106
|
-
left > 0 &&
|
|
107
|
-
display.slice(edit.start, edit.end).includes('.')
|
|
108
|
-
) {
|
|
109
|
-
left -= 1;
|
|
110
|
-
removed = 1;
|
|
111
|
-
}
|
|
112
|
-
const next = digits.slice(0, left) + inserted + digits.slice(left + removed);
|
|
113
|
-
const trimmed = next.replace(/^0+(?=\d)/, '');
|
|
114
|
-
if ((!removed && !inserted) || trimmed.length > digitLimit) {
|
|
115
|
-
return { digits, caretDigit: left };
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
digits: trimmed,
|
|
119
|
-
caretDigit: Math.max(
|
|
120
|
-
0,
|
|
121
|
-
left + inserted.length - (next.length - trimmed.length),
|
|
122
|
-
),
|
|
123
|
-
};
|
|
124
|
-
};
|
|
125
|
-
|
|
126
37
|
const InputMoney = forwardRef(
|
|
127
38
|
(
|
|
128
39
|
{
|
|
@@ -160,13 +71,8 @@ const InputMoney = forwardRef(
|
|
|
160
71
|
const inputFontFamily = exportFontFamily('bold');
|
|
161
72
|
const [focused, setFocused] = useState(false);
|
|
162
73
|
const [displayValue, setDisplayValue] = useState('');
|
|
163
|
-
const [
|
|
164
|
-
const
|
|
165
|
-
const digitsRef = useRef('');
|
|
166
|
-
const selectionRef = useRef<Selection>({ start: 0, end: 0 });
|
|
167
|
-
const pendingCaretRef = useRef<number | null>(null);
|
|
168
|
-
const pendingSelectionRef = useRef<Selection | null>(null);
|
|
169
|
-
const editingRef = useRef(false);
|
|
74
|
+
const [numericValue, setNumericValue] = useState('');
|
|
75
|
+
const [lastDisplayValue, setLastDisplayValue] = useState('');
|
|
170
76
|
const inputRef = useRef<TextInput>(null);
|
|
171
77
|
const componentName = 'InputMoney';
|
|
172
78
|
|
|
@@ -177,94 +83,97 @@ const InputMoney = forwardRef(
|
|
|
177
83
|
props.accessibilityLabel,
|
|
178
84
|
);
|
|
179
85
|
|
|
180
|
-
const
|
|
86
|
+
const maxLength = (props as any)?.maxLength;
|
|
87
|
+
const hasMaxLength = typeof maxLength === 'number';
|
|
88
|
+
const digitLimit = hasMaxLength ? maxLength : Number.POSITIVE_INFINITY;
|
|
181
89
|
|
|
182
90
|
useEffect(() => {
|
|
183
91
|
if (value !== undefined && typeof value === 'string') {
|
|
184
92
|
const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
|
|
185
|
-
if (cleanValue !==
|
|
186
|
-
|
|
93
|
+
if (cleanValue !== numericValue) {
|
|
94
|
+
setNumericValue(cleanValue);
|
|
95
|
+
updateDisplayValue(cleanValue);
|
|
187
96
|
}
|
|
188
97
|
}
|
|
189
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
190
98
|
}, [value]);
|
|
191
99
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
pendingCaretRef.current = null;
|
|
196
|
-
inputRef.current.setSelection(caret, caret);
|
|
197
|
-
}, [commitCount]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
setLastDisplayValue(displayValue);
|
|
102
|
+
}, [displayValue]);
|
|
198
103
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return
|
|
104
|
+
const updateDisplayValue = (numeric: string) => {
|
|
105
|
+
if (!numeric) {
|
|
106
|
+
setDisplayValue('');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const formatted = formatNumberToMoney(Number(numeric));
|
|
110
|
+
setDisplayValue(formatted ? `${formatted}${currency}` : '');
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const parseInputText = (text: string): string => {
|
|
114
|
+
if (!text) return '';
|
|
115
|
+
const currencyRegex = new RegExp(` ?${currency}$`, 'g');
|
|
116
|
+
const textWithoutCurrency = text.replace(currencyRegex, '').trim();
|
|
117
|
+
return textWithoutCurrency.replace(/[^\d]/g, '');
|
|
118
|
+
};
|
|
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
|
+
}
|
|
210
128
|
};
|
|
211
129
|
|
|
212
130
|
const onClearText = () => {
|
|
213
|
-
|
|
131
|
+
inputRef?.current?.clear();
|
|
132
|
+
inputRef.current?.setNativeProps({ text: '' });
|
|
133
|
+
setDisplayValue('');
|
|
134
|
+
setNumericValue('');
|
|
214
135
|
onChangeText?.('');
|
|
215
136
|
};
|
|
216
137
|
|
|
217
138
|
const _onChangeText = (text: string) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
editingRef.current = false;
|
|
224
|
-
});
|
|
225
|
-
if (text) {
|
|
226
|
-
const edit = detectEdit(displayRef.current, text, selectionRef.current);
|
|
227
|
-
({ digits, caretDigit } = applyEdit(
|
|
228
|
-
displayRef.current,
|
|
229
|
-
edit,
|
|
230
|
-
digitLimit,
|
|
231
|
-
));
|
|
232
|
-
}
|
|
233
|
-
if (commit(digits, caretDigit)) {
|
|
234
|
-
onChangeText?.(digits);
|
|
139
|
+
if (!text) {
|
|
140
|
+
setDisplayValue('');
|
|
141
|
+
setNumericValue('');
|
|
142
|
+
onChangeText?.('');
|
|
143
|
+
return;
|
|
235
144
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
157
|
+
}
|
|
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;
|
|
256
169
|
}
|
|
257
|
-
}
|
|
170
|
+
}
|
|
171
|
+
const newNumericValue = parseInputText(text);
|
|
172
|
+
updateNumericValue(newNumericValue);
|
|
258
173
|
};
|
|
259
174
|
|
|
260
175
|
const _onFocus = (e: TextInputFocusEvent) => {
|
|
261
176
|
setFocused(true);
|
|
262
|
-
const caret = pendingCaretRef.current;
|
|
263
|
-
pendingCaretRef.current = null;
|
|
264
|
-
if (caret !== null && !props.selectTextOnFocus) {
|
|
265
|
-
selectionRef.current = { start: caret, end: caret };
|
|
266
|
-
inputRef.current?.setSelection(caret, caret);
|
|
267
|
-
}
|
|
268
177
|
onFocus?.(e);
|
|
269
178
|
};
|
|
270
179
|
|
|
@@ -274,13 +183,14 @@ const InputMoney = forwardRef(
|
|
|
274
183
|
};
|
|
275
184
|
|
|
276
185
|
const _setText = (text: string) => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
onChangeText?.(digits);
|
|
186
|
+
if (!text) {
|
|
187
|
+
setDisplayValue('');
|
|
188
|
+
setNumericValue('');
|
|
189
|
+
onChangeText?.('');
|
|
190
|
+
return;
|
|
283
191
|
}
|
|
192
|
+
const newNumericValue = parseInputText(text);
|
|
193
|
+
updateNumericValue(newNumericValue);
|
|
284
194
|
};
|
|
285
195
|
|
|
286
196
|
useImperativeHandle(ref, () => {
|
|
@@ -326,7 +236,6 @@ const InputMoney = forwardRef(
|
|
|
326
236
|
accessibilityLabel={`${componentId}`}
|
|
327
237
|
textAlignVertical="center"
|
|
328
238
|
ref={inputRef}
|
|
329
|
-
editable={props.editable !== false && !disabled}
|
|
330
239
|
accessibilityState={{
|
|
331
240
|
disabled: disabled,
|
|
332
241
|
...props.accessibilityState,
|
|
@@ -348,7 +257,6 @@ const InputMoney = forwardRef(
|
|
|
348
257
|
textBreakStrategy="highQuality"
|
|
349
258
|
value={displayValue}
|
|
350
259
|
onChangeText={_onChangeText}
|
|
351
|
-
onSelectionChange={_onSelectionChange}
|
|
352
260
|
onFocus={_onFocus}
|
|
353
261
|
onBlur={_onBlur}
|
|
354
262
|
placeholder={placeholder}
|