@momo-kits/foundation 0.168.1-beta.4 → 0.168.1-beta.6
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 +58 -28
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -35,6 +35,24 @@ import styles from './styles';
|
|
|
35
35
|
import { formatNumberToMoney } from './utils';
|
|
36
36
|
import SystemTextInput from './SystemTextInput';
|
|
37
37
|
|
|
38
|
+
// Finds the common prefix and suffix of `old` and `text`; `size` counts the
|
|
39
|
+
// characters removed plus inserted between them.
|
|
40
|
+
const findEdit = (old: string, text: string) => {
|
|
41
|
+
let start = 0;
|
|
42
|
+
while (start < text.length && old[start] === text[start]) {
|
|
43
|
+
start += 1;
|
|
44
|
+
}
|
|
45
|
+
let suffix = 0;
|
|
46
|
+
while (
|
|
47
|
+
suffix < Math.min(text.length, old.length) - start &&
|
|
48
|
+
old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
|
|
49
|
+
) {
|
|
50
|
+
suffix += 1;
|
|
51
|
+
}
|
|
52
|
+
const size = old.length + text.length - 2 * (start + suffix);
|
|
53
|
+
return { old, start, suffix, size };
|
|
54
|
+
};
|
|
55
|
+
|
|
38
56
|
const InputMoney = forwardRef(
|
|
39
57
|
(
|
|
40
58
|
{
|
|
@@ -76,6 +94,11 @@ const InputMoney = forwardRef(
|
|
|
76
94
|
const [lastDisplayValue, setLastDisplayValue] = useState('');
|
|
77
95
|
const inputRef = useRef<TextInput>(null);
|
|
78
96
|
const caretDigitsAfter = useRef<number | undefined>(undefined);
|
|
97
|
+
const lastRawText = useRef('');
|
|
98
|
+
const [caretSelection, setCaretSelection] = useState<{
|
|
99
|
+
start: number;
|
|
100
|
+
end: number;
|
|
101
|
+
}>();
|
|
79
102
|
const componentName = 'InputMoney';
|
|
80
103
|
|
|
81
104
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -100,30 +123,38 @@ const InputMoney = forwardRef(
|
|
|
100
123
|
}
|
|
101
124
|
}, [value]);
|
|
102
125
|
|
|
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
126
|
useEffect(() => {
|
|
107
127
|
setLastDisplayValue(displayValue);
|
|
128
|
+
}, [displayValue]);
|
|
129
|
+
|
|
130
|
+
// The selection only rides along with the render that reformats the text,
|
|
131
|
+
// then it is released so the user can move the caret freely.
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!caretSelection) return;
|
|
134
|
+
// A newer keystroke may already have queued its own selection; keep it.
|
|
135
|
+
setCaretSelection(current =>
|
|
136
|
+
current === caretSelection ? undefined : current,
|
|
137
|
+
);
|
|
138
|
+
}, [caretSelection]);
|
|
139
|
+
|
|
140
|
+
const updateDisplayValue = (numeric: string) => {
|
|
141
|
+
const formatted = numeric ? formatNumberToMoney(Number(numeric)) : '';
|
|
142
|
+
const next = formatted ? `${formatted}${currency}` : '';
|
|
143
|
+
setDisplayValue(next);
|
|
144
|
+
|
|
145
|
+
// Android moves the caret proportionally when the text is replaced, so
|
|
146
|
+
// it lands before the typed digit once a separator is added. Send the
|
|
147
|
+
// caret together with the text so a fast keystroke can't land in between.
|
|
108
148
|
const digitsAfter = caretDigitsAfter.current;
|
|
109
149
|
caretDigitsAfter.current = undefined;
|
|
110
150
|
if (Platform.OS !== 'android' || digitsAfter === undefined) return;
|
|
111
|
-
let before = parseInputText(
|
|
151
|
+
let before = parseInputText(next).length - digitsAfter;
|
|
112
152
|
let caret = 0;
|
|
113
|
-
while (before > 0 && caret <
|
|
114
|
-
if (/\d/.test(
|
|
153
|
+
while (before > 0 && caret < next.length) {
|
|
154
|
+
if (/\d/.test(next[caret])) before -= 1;
|
|
115
155
|
caret += 1;
|
|
116
156
|
}
|
|
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}` : '');
|
|
157
|
+
setCaretSelection({ start: caret, end: caret });
|
|
127
158
|
};
|
|
128
159
|
|
|
129
160
|
const parseInputText = (text: string): string => {
|
|
@@ -153,23 +184,21 @@ const InputMoney = forwardRef(
|
|
|
153
184
|
|
|
154
185
|
const _onChangeText = (text: string) => {
|
|
155
186
|
if (!text) {
|
|
187
|
+
lastRawText.current = '';
|
|
156
188
|
setDisplayValue('');
|
|
157
189
|
setNumericValue('');
|
|
158
190
|
onChangeText?.('');
|
|
159
191
|
return;
|
|
160
192
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
) {
|
|
171
|
-
suffix += 1;
|
|
172
|
-
}
|
|
193
|
+
// When typing outpaces JS (slow Android devices) native rejects the stale
|
|
194
|
+
// formatted text and keeps the raw one, so the edit may apply to either.
|
|
195
|
+
// Pick the one that differs from the new text by a single edit.
|
|
196
|
+
const raw = lastRawText.current;
|
|
197
|
+
lastRawText.current = text;
|
|
198
|
+
const formattedEdit = findEdit(lastDisplayValue, text);
|
|
199
|
+
const rawEdit = findEdit(raw, text);
|
|
200
|
+
const { old, start, suffix } =
|
|
201
|
+
raw && rawEdit.size < formattedEdit.size ? rawEdit : formattedEdit;
|
|
173
202
|
caretDigitsAfter.current = text
|
|
174
203
|
.slice(text.length - suffix)
|
|
175
204
|
.replace(/\D/g, '').length;
|
|
@@ -276,6 +305,7 @@ const InputMoney = forwardRef(
|
|
|
276
305
|
allowFontScaling={false}
|
|
277
306
|
textBreakStrategy="highQuality"
|
|
278
307
|
value={displayValue}
|
|
308
|
+
selection={caretSelection ?? props.selection}
|
|
279
309
|
onChangeText={_onChangeText}
|
|
280
310
|
onFocus={_onFocus}
|
|
281
311
|
onBlur={_onBlur}
|