@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.
@@ -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(displayValue).length - digitsAfter;
151
+ let before = parseInputText(next).length - digitsAfter;
112
152
  let caret = 0;
113
- while (before > 0 && caret < displayValue.length) {
114
- if (/\d/.test(displayValue[caret])) before -= 1;
153
+ while (before > 0 && caret < next.length) {
154
+ if (/\d/.test(next[caret])) before -= 1;
115
155
  caret += 1;
116
156
  }
117
- inputRef.current?.setSelection(caret, caret);
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
- const old = lastDisplayValue;
162
- let start = 0;
163
- while (start < text.length && old[start] === text[start]) {
164
- start += 1;
165
- }
166
- let suffix = 0;
167
- while (
168
- suffix < Math.min(text.length, old.length) - start &&
169
- old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
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}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.168.1-beta.4",
3
+ "version": "0.168.1-beta.6",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},