@momo-kits/foundation 0.168.1-beta.3 → 0.168.1-beta.5
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 +70 -19
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import React, {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import {
|
|
11
|
+
Platform,
|
|
11
12
|
TextInput,
|
|
12
13
|
TextInputFocusEvent,
|
|
13
14
|
TouchableOpacity,
|
|
@@ -31,9 +32,26 @@ import {
|
|
|
31
32
|
} from './common';
|
|
32
33
|
import { InputMoneyProps } from './index';
|
|
33
34
|
import styles from './styles';
|
|
34
|
-
import { formatNumberToMoney } from './utils';
|
|
35
35
|
import SystemTextInput from './SystemTextInput';
|
|
36
36
|
|
|
37
|
+
// Finds the common prefix and suffix of `old` and `text`; `size` counts the
|
|
38
|
+
// characters removed plus inserted between them.
|
|
39
|
+
const findEdit = (old: string, text: string) => {
|
|
40
|
+
let start = 0;
|
|
41
|
+
while (start < text.length && old[start] === text[start]) {
|
|
42
|
+
start += 1;
|
|
43
|
+
}
|
|
44
|
+
let suffix = 0;
|
|
45
|
+
while (
|
|
46
|
+
suffix < Math.min(text.length, old.length) - start &&
|
|
47
|
+
old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
|
|
48
|
+
) {
|
|
49
|
+
suffix += 1;
|
|
50
|
+
}
|
|
51
|
+
const size = old.length + text.length - 2 * (start + suffix);
|
|
52
|
+
return { old, start, suffix, size };
|
|
53
|
+
};
|
|
54
|
+
|
|
37
55
|
const InputMoney = forwardRef(
|
|
38
56
|
(
|
|
39
57
|
{
|
|
@@ -74,6 +92,12 @@ const InputMoney = forwardRef(
|
|
|
74
92
|
const [numericValue, setNumericValue] = useState('');
|
|
75
93
|
const [lastDisplayValue, setLastDisplayValue] = useState('');
|
|
76
94
|
const inputRef = useRef<TextInput>(null);
|
|
95
|
+
const caretDigitsAfter = useRef<number | undefined>(undefined);
|
|
96
|
+
const lastRawText = useRef('');
|
|
97
|
+
const [caretSelection, setCaretSelection] = useState<{
|
|
98
|
+
start: number;
|
|
99
|
+
end: number;
|
|
100
|
+
}>();
|
|
77
101
|
const componentName = 'InputMoney';
|
|
78
102
|
|
|
79
103
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -91,6 +115,7 @@ const InputMoney = forwardRef(
|
|
|
91
115
|
if (value !== undefined && typeof value === 'string') {
|
|
92
116
|
const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
|
|
93
117
|
if (cleanValue !== numericValue) {
|
|
118
|
+
caretDigitsAfter.current = undefined;
|
|
94
119
|
setNumericValue(cleanValue);
|
|
95
120
|
updateDisplayValue(cleanValue);
|
|
96
121
|
}
|
|
@@ -101,13 +126,36 @@ const InputMoney = forwardRef(
|
|
|
101
126
|
setLastDisplayValue(displayValue);
|
|
102
127
|
}, [displayValue]);
|
|
103
128
|
|
|
129
|
+
// The selection only rides along with the render that reformats the text,
|
|
130
|
+
// then it is released so the user can move the caret freely.
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!caretSelection) return;
|
|
133
|
+
// A newer keystroke may already have queued its own selection; keep it.
|
|
134
|
+
setCaretSelection(current =>
|
|
135
|
+
current === caretSelection ? undefined : current,
|
|
136
|
+
);
|
|
137
|
+
}, [caretSelection]);
|
|
138
|
+
|
|
104
139
|
const updateDisplayValue = (numeric: string) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
// Group the digit string directly: Number() loses precision past 15
|
|
141
|
+
// digits and switches to "1e+21" notation past 20.
|
|
142
|
+
const formatted = numeric.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
143
|
+
const next = formatted ? `${formatted}${currency}` : '';
|
|
144
|
+
setDisplayValue(next);
|
|
145
|
+
|
|
146
|
+
// Android moves the caret proportionally when the text is replaced, so
|
|
147
|
+
// it lands before the typed digit once a separator is added. Send the
|
|
148
|
+
// caret together with the text so a fast keystroke can't land in between.
|
|
149
|
+
const digitsAfter = caretDigitsAfter.current;
|
|
150
|
+
caretDigitsAfter.current = undefined;
|
|
151
|
+
if (Platform.OS !== 'android' || digitsAfter === undefined) return;
|
|
152
|
+
let before = parseInputText(next).length - digitsAfter;
|
|
153
|
+
let caret = 0;
|
|
154
|
+
while (before > 0 && caret < next.length) {
|
|
155
|
+
if (/\d/.test(next[caret])) before -= 1;
|
|
156
|
+
caret += 1;
|
|
108
157
|
}
|
|
109
|
-
|
|
110
|
-
setDisplayValue(formatted ? `${formatted}${currency}` : '');
|
|
158
|
+
setCaretSelection({ start: caret, end: caret });
|
|
111
159
|
};
|
|
112
160
|
|
|
113
161
|
const parseInputText = (text: string): string => {
|
|
@@ -137,24 +185,25 @@ const InputMoney = forwardRef(
|
|
|
137
185
|
|
|
138
186
|
const _onChangeText = (text: string) => {
|
|
139
187
|
if (!text) {
|
|
188
|
+
lastRawText.current = '';
|
|
140
189
|
setDisplayValue('');
|
|
141
190
|
setNumericValue('');
|
|
142
191
|
onChangeText?.('');
|
|
143
192
|
return;
|
|
144
193
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
194
|
+
// When typing outpaces JS (slow Android devices) native rejects the stale
|
|
195
|
+
// formatted text and keeps the raw one, so the edit may apply to either.
|
|
196
|
+
// Pick the one that differs from the new text by a single edit.
|
|
197
|
+
const raw = lastRawText.current;
|
|
198
|
+
lastRawText.current = text;
|
|
199
|
+
const formattedEdit = findEdit(lastDisplayValue, text);
|
|
200
|
+
const rawEdit = findEdit(raw, text);
|
|
201
|
+
const { old, start, suffix } =
|
|
202
|
+
raw && rawEdit.size < formattedEdit.size ? rawEdit : formattedEdit;
|
|
203
|
+
caretDigitsAfter.current = text
|
|
204
|
+
.slice(text.length - suffix)
|
|
205
|
+
.replace(/\D/g, '').length;
|
|
206
|
+
if (text.length < old.length) {
|
|
158
207
|
const removed = old.slice(start, old.length - suffix);
|
|
159
208
|
if (!/\d/.test(removed)) {
|
|
160
209
|
const index = removed.includes('.')
|
|
@@ -183,6 +232,7 @@ const InputMoney = forwardRef(
|
|
|
183
232
|
};
|
|
184
233
|
|
|
185
234
|
const _setText = (text: string) => {
|
|
235
|
+
caretDigitsAfter.current = undefined;
|
|
186
236
|
if (!text) {
|
|
187
237
|
setDisplayValue('');
|
|
188
238
|
setNumericValue('');
|
|
@@ -256,6 +306,7 @@ const InputMoney = forwardRef(
|
|
|
256
306
|
allowFontScaling={false}
|
|
257
307
|
textBreakStrategy="highQuality"
|
|
258
308
|
value={displayValue}
|
|
309
|
+
selection={caretSelection ?? props.selection}
|
|
259
310
|
onChangeText={_onChangeText}
|
|
260
311
|
onFocus={_onFocus}
|
|
261
312
|
onBlur={_onBlur}
|