@momo-kits/foundation 0.168.1-beta.4 → 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 +60 -29
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -32,9 +32,26 @@ import {
|
|
|
32
32
|
} from './common';
|
|
33
33
|
import { InputMoneyProps } from './index';
|
|
34
34
|
import styles from './styles';
|
|
35
|
-
import { formatNumberToMoney } from './utils';
|
|
36
35
|
import SystemTextInput from './SystemTextInput';
|
|
37
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
|
+
|
|
38
55
|
const InputMoney = forwardRef(
|
|
39
56
|
(
|
|
40
57
|
{
|
|
@@ -76,6 +93,11 @@ const InputMoney = forwardRef(
|
|
|
76
93
|
const [lastDisplayValue, setLastDisplayValue] = useState('');
|
|
77
94
|
const inputRef = useRef<TextInput>(null);
|
|
78
95
|
const caretDigitsAfter = useRef<number | undefined>(undefined);
|
|
96
|
+
const lastRawText = useRef('');
|
|
97
|
+
const [caretSelection, setCaretSelection] = useState<{
|
|
98
|
+
start: number;
|
|
99
|
+
end: number;
|
|
100
|
+
}>();
|
|
79
101
|
const componentName = 'InputMoney';
|
|
80
102
|
|
|
81
103
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -100,30 +122,40 @@ const InputMoney = forwardRef(
|
|
|
100
122
|
}
|
|
101
123
|
}, [value]);
|
|
102
124
|
|
|
103
|
-
// Android moves the caret proportionally when the text is replaced, so it
|
|
104
|
-
// lands before the typed digit once a separator is added. Put it back after
|
|
105
|
-
// the same digit.
|
|
106
125
|
useEffect(() => {
|
|
107
126
|
setLastDisplayValue(displayValue);
|
|
127
|
+
}, [displayValue]);
|
|
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
|
+
|
|
139
|
+
const updateDisplayValue = (numeric: string) => {
|
|
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.
|
|
108
149
|
const digitsAfter = caretDigitsAfter.current;
|
|
109
150
|
caretDigitsAfter.current = undefined;
|
|
110
151
|
if (Platform.OS !== 'android' || digitsAfter === undefined) return;
|
|
111
|
-
let before = parseInputText(
|
|
152
|
+
let before = parseInputText(next).length - digitsAfter;
|
|
112
153
|
let caret = 0;
|
|
113
|
-
while (before > 0 && caret <
|
|
114
|
-
if (/\d/.test(
|
|
154
|
+
while (before > 0 && caret < next.length) {
|
|
155
|
+
if (/\d/.test(next[caret])) before -= 1;
|
|
115
156
|
caret += 1;
|
|
116
157
|
}
|
|
117
|
-
|
|
118
|
-
}, [displayValue]);
|
|
119
|
-
|
|
120
|
-
const updateDisplayValue = (numeric: string) => {
|
|
121
|
-
if (!numeric) {
|
|
122
|
-
setDisplayValue('');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const formatted = formatNumberToMoney(Number(numeric));
|
|
126
|
-
setDisplayValue(formatted ? `${formatted}${currency}` : '');
|
|
158
|
+
setCaretSelection({ start: caret, end: caret });
|
|
127
159
|
};
|
|
128
160
|
|
|
129
161
|
const parseInputText = (text: string): string => {
|
|
@@ -153,23 +185,21 @@ const InputMoney = forwardRef(
|
|
|
153
185
|
|
|
154
186
|
const _onChangeText = (text: string) => {
|
|
155
187
|
if (!text) {
|
|
188
|
+
lastRawText.current = '';
|
|
156
189
|
setDisplayValue('');
|
|
157
190
|
setNumericValue('');
|
|
158
191
|
onChangeText?.('');
|
|
159
192
|
return;
|
|
160
193
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
) {
|
|
171
|
-
suffix += 1;
|
|
172
|
-
}
|
|
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;
|
|
173
203
|
caretDigitsAfter.current = text
|
|
174
204
|
.slice(text.length - suffix)
|
|
175
205
|
.replace(/\D/g, '').length;
|
|
@@ -276,6 +306,7 @@ const InputMoney = forwardRef(
|
|
|
276
306
|
allowFontScaling={false}
|
|
277
307
|
textBreakStrategy="highQuality"
|
|
278
308
|
value={displayValue}
|
|
309
|
+
selection={caretSelection ?? props.selection}
|
|
279
310
|
onChangeText={_onChangeText}
|
|
280
311
|
onFocus={_onFocus}
|
|
281
312
|
onBlur={_onBlur}
|