@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.
@@ -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(displayValue).length - digitsAfter;
152
+ let before = parseInputText(next).length - digitsAfter;
112
153
  let caret = 0;
113
- while (before > 0 && caret < displayValue.length) {
114
- if (/\d/.test(displayValue[caret])) before -= 1;
154
+ while (before > 0 && caret < next.length) {
155
+ if (/\d/.test(next[caret])) before -= 1;
115
156
  caret += 1;
116
157
  }
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}` : '');
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
- 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
- }
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}
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.5",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},