@momo-kits/foundation 0.168.1-beta.2 → 0.168.1-beta.3

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.
Files changed (2) hide show
  1. package/Input/InputMoney.tsx +80 -172
  2. package/package.json +1 -1
@@ -1,16 +1,15 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
1
2
  import React, {
2
3
  forwardRef,
3
4
  useContext,
4
5
  useEffect,
5
6
  useImperativeHandle,
6
- useLayoutEffect,
7
7
  useRef,
8
8
  useState,
9
9
  } from 'react';
10
10
  import {
11
11
  TextInput,
12
12
  TextInputFocusEvent,
13
- TextInputSelectionChangeEvent,
14
13
  TouchableOpacity,
15
14
  View,
16
15
  } from 'react-native';
@@ -32,97 +31,9 @@ import {
32
31
  } from './common';
33
32
  import { InputMoneyProps } from './index';
34
33
  import styles from './styles';
34
+ import { formatNumberToMoney } from './utils';
35
35
  import SystemTextInput from './SystemTextInput';
36
36
 
37
- type Selection = { start: number; end: number };
38
- type Edit = { start: number; end: number; inserted: string };
39
-
40
- const countDigits = (text: string) => text.replace(/\D/g, '').length;
41
-
42
- const groupDigits = (digits: string) =>
43
- digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
44
-
45
- const caretFromDigitIndex = (display: string, digitIndex: number) => {
46
- let count = 0;
47
- for (let i = 0; i < display.length && count < digitIndex; i++) {
48
- if (/\d/.test(display[i])) {
49
- count += 1;
50
- if (count === digitIndex) return i + 1;
51
- }
52
- }
53
- return 0;
54
- };
55
-
56
- const detectEdit = (old: string, next: string, selection: Selection): Edit => {
57
- const { start, end } = selection;
58
- const removed = old.length - next.length;
59
- const candidates: Edit[] = [
60
- {
61
- start,
62
- end,
63
- inserted: next.slice(start, next.length - (old.length - end)),
64
- },
65
- { start: start - removed, end: start, inserted: '' },
66
- { start, end: start + removed, inserted: '' },
67
- ];
68
- const match = candidates.find(
69
- e =>
70
- e.start >= 0 &&
71
- e.end >= e.start &&
72
- e.end <= old.length &&
73
- old.slice(0, e.start) + e.inserted + old.slice(e.end) === next,
74
- );
75
- if (match) return match;
76
-
77
- let prefix = 0;
78
- while (
79
- prefix < Math.min(old.length, next.length) &&
80
- old[prefix] === next[prefix]
81
- ) {
82
- prefix += 1;
83
- }
84
- let suffix = 0;
85
- while (
86
- suffix < Math.min(old.length, next.length) - prefix &&
87
- old[old.length - 1 - suffix] === next[next.length - 1 - suffix]
88
- ) {
89
- suffix += 1;
90
- }
91
- return {
92
- start: prefix,
93
- end: old.length - suffix,
94
- inserted: next.slice(prefix, next.length - suffix),
95
- };
96
- };
97
-
98
- const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
99
- const digits = display.replace(/\D/g, '');
100
- let left = countDigits(display.slice(0, edit.start));
101
- let removed = countDigits(display.slice(edit.start, edit.end));
102
- const inserted = edit.inserted.replace(/\D/g, '');
103
- if (
104
- !removed &&
105
- !inserted &&
106
- left > 0 &&
107
- display.slice(edit.start, edit.end).includes('.')
108
- ) {
109
- left -= 1;
110
- removed = 1;
111
- }
112
- const next = digits.slice(0, left) + inserted + digits.slice(left + removed);
113
- const trimmed = next.replace(/^0+(?=\d)/, '');
114
- if ((!removed && !inserted) || trimmed.length > digitLimit) {
115
- return { digits, caretDigit: left };
116
- }
117
- return {
118
- digits: trimmed,
119
- caretDigit: Math.max(
120
- 0,
121
- left + inserted.length - (next.length - trimmed.length),
122
- ),
123
- };
124
- };
125
-
126
37
  const InputMoney = forwardRef(
127
38
  (
128
39
  {
@@ -160,13 +71,8 @@ const InputMoney = forwardRef(
160
71
  const inputFontFamily = exportFontFamily('bold');
161
72
  const [focused, setFocused] = useState(false);
162
73
  const [displayValue, setDisplayValue] = useState('');
163
- const [commitCount, setCommitCount] = useState(0);
164
- const displayRef = useRef('');
165
- const digitsRef = useRef('');
166
- const selectionRef = useRef<Selection>({ start: 0, end: 0 });
167
- const pendingCaretRef = useRef<number | null>(null);
168
- const pendingSelectionRef = useRef<Selection | null>(null);
169
- const editingRef = useRef(false);
74
+ const [numericValue, setNumericValue] = useState('');
75
+ const [lastDisplayValue, setLastDisplayValue] = useState('');
170
76
  const inputRef = useRef<TextInput>(null);
171
77
  const componentName = 'InputMoney';
172
78
 
@@ -177,94 +83,97 @@ const InputMoney = forwardRef(
177
83
  props.accessibilityLabel,
178
84
  );
179
85
 
180
- const digitLimit = props.maxLength ?? Number.POSITIVE_INFINITY;
86
+ const maxLength = (props as any)?.maxLength;
87
+ const hasMaxLength = typeof maxLength === 'number';
88
+ const digitLimit = hasMaxLength ? maxLength : Number.POSITIVE_INFINITY;
181
89
 
182
90
  useEffect(() => {
183
91
  if (value !== undefined && typeof value === 'string') {
184
92
  const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
185
- if (cleanValue !== digitsRef.current) {
186
- commit(cleanValue, cleanValue.length);
93
+ if (cleanValue !== numericValue) {
94
+ setNumericValue(cleanValue);
95
+ updateDisplayValue(cleanValue);
187
96
  }
188
97
  }
189
- // eslint-disable-next-line react-hooks/exhaustive-deps
190
98
  }, [value]);
191
99
 
192
- useLayoutEffect(() => {
193
- const caret = pendingCaretRef.current;
194
- if (caret === null || !inputRef.current?.isFocused()) return;
195
- pendingCaretRef.current = null;
196
- inputRef.current.setSelection(caret, caret);
197
- }, [commitCount]);
100
+ useEffect(() => {
101
+ setLastDisplayValue(displayValue);
102
+ }, [displayValue]);
198
103
 
199
- const commit = (digits: string, caretDigit: number) => {
200
- const display = digits ? `${groupDigits(digits)}${currency}` : '';
201
- const caret = caretFromDigitIndex(display, caretDigit);
202
- const changed = digits !== digitsRef.current;
203
- displayRef.current = display;
204
- digitsRef.current = digits;
205
- selectionRef.current = { start: caret, end: caret };
206
- pendingCaretRef.current = caret;
207
- setCommitCount(count => count + 1);
208
- setDisplayValue(display);
209
- return changed;
104
+ const updateDisplayValue = (numeric: string) => {
105
+ if (!numeric) {
106
+ setDisplayValue('');
107
+ return;
108
+ }
109
+ const formatted = formatNumberToMoney(Number(numeric));
110
+ setDisplayValue(formatted ? `${formatted}${currency}` : '');
111
+ };
112
+
113
+ const parseInputText = (text: string): string => {
114
+ if (!text) return '';
115
+ const currencyRegex = new RegExp(` ?${currency}$`, 'g');
116
+ const textWithoutCurrency = text.replace(currencyRegex, '').trim();
117
+ return textWithoutCurrency.replace(/[^\d]/g, '');
118
+ };
119
+
120
+ const updateNumericValue = (val: string) => {
121
+ let next = val.replace(/[^\d]/g, '').replace(/^0+(?=\d)/, '');
122
+ if (next.length > digitLimit) next = next.slice(0, digitLimit);
123
+ if (/^\d*$/.test(next)) {
124
+ setNumericValue(next);
125
+ updateDisplayValue(next);
126
+ onChangeText?.(next);
127
+ }
210
128
  };
211
129
 
212
130
  const onClearText = () => {
213
- commit('', 0);
131
+ inputRef?.current?.clear();
132
+ inputRef.current?.setNativeProps({ text: '' });
133
+ setDisplayValue('');
134
+ setNumericValue('');
214
135
  onChangeText?.('');
215
136
  };
216
137
 
217
138
  const _onChangeText = (text: string) => {
218
- let digits = '';
219
- let caretDigit = 0;
220
- pendingSelectionRef.current = null;
221
- editingRef.current = true;
222
- setTimeout(() => {
223
- editingRef.current = false;
224
- });
225
- if (text) {
226
- const edit = detectEdit(displayRef.current, text, selectionRef.current);
227
- ({ digits, caretDigit } = applyEdit(
228
- displayRef.current,
229
- edit,
230
- digitLimit,
231
- ));
232
- }
233
- if (commit(digits, caretDigit)) {
234
- onChangeText?.(digits);
139
+ if (!text) {
140
+ setDisplayValue('');
141
+ setNumericValue('');
142
+ onChangeText?.('');
143
+ return;
235
144
  }
236
- };
237
-
238
- const _onSelectionChange = (e: TextInputSelectionChangeEvent) => {
239
- props.onSelectionChange?.(e);
240
- if (editingRef.current) return;
241
- const selection = e.nativeEvent.selection;
242
- pendingSelectionRef.current = selection;
243
- setTimeout(() => {
244
- if (pendingSelectionRef.current !== selection) return;
245
- pendingSelectionRef.current = null;
246
- const { start, end } = selection;
247
- const display = displayRef.current;
248
- const max = display ? display.length - currency.length : 0;
249
- const clamped = {
250
- start: Math.min(start, max),
251
- end: Math.min(end, max),
252
- };
253
- selectionRef.current = clamped;
254
- if (clamped.start !== start || clamped.end !== end) {
255
- inputRef.current?.setSelection(clamped.start, clamped.end);
145
+ if (text.length < lastDisplayValue.length) {
146
+ const old = lastDisplayValue;
147
+ let start = 0;
148
+ while (start < text.length && old[start] === text[start]) {
149
+ start += 1;
150
+ }
151
+ let suffix = 0;
152
+ while (
153
+ suffix < text.length - start &&
154
+ old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
155
+ ) {
156
+ suffix += 1;
157
+ }
158
+ const removed = old.slice(start, old.length - suffix);
159
+ if (!/\d/.test(removed)) {
160
+ const index = removed.includes('.')
161
+ ? parseInputText(old.slice(0, start)).length - 1
162
+ : numericValue.length - 1;
163
+ if (index >= 0) {
164
+ updateNumericValue(
165
+ numericValue.slice(0, index) + numericValue.slice(index + 1),
166
+ );
167
+ }
168
+ return;
256
169
  }
257
- });
170
+ }
171
+ const newNumericValue = parseInputText(text);
172
+ updateNumericValue(newNumericValue);
258
173
  };
259
174
 
260
175
  const _onFocus = (e: TextInputFocusEvent) => {
261
176
  setFocused(true);
262
- const caret = pendingCaretRef.current;
263
- pendingCaretRef.current = null;
264
- if (caret !== null && !props.selectTextOnFocus) {
265
- selectionRef.current = { start: caret, end: caret };
266
- inputRef.current?.setSelection(caret, caret);
267
- }
268
177
  onFocus?.(e);
269
178
  };
270
179
 
@@ -274,13 +183,14 @@ const InputMoney = forwardRef(
274
183
  };
275
184
 
276
185
  const _setText = (text: string) => {
277
- const digits = text
278
- .replace(/\D/g, '')
279
- .replace(/^0+(?=\d)/, '')
280
- .slice(0, digitLimit);
281
- if (commit(digits, digits.length)) {
282
- onChangeText?.(digits);
186
+ if (!text) {
187
+ setDisplayValue('');
188
+ setNumericValue('');
189
+ onChangeText?.('');
190
+ return;
283
191
  }
192
+ const newNumericValue = parseInputText(text);
193
+ updateNumericValue(newNumericValue);
284
194
  };
285
195
 
286
196
  useImperativeHandle(ref, () => {
@@ -326,7 +236,6 @@ const InputMoney = forwardRef(
326
236
  accessibilityLabel={`${componentId}`}
327
237
  textAlignVertical="center"
328
238
  ref={inputRef}
329
- editable={props.editable !== false && !disabled}
330
239
  accessibilityState={{
331
240
  disabled: disabled,
332
241
  ...props.accessibilityState,
@@ -348,7 +257,6 @@ const InputMoney = forwardRef(
348
257
  textBreakStrategy="highQuality"
349
258
  value={displayValue}
350
259
  onChangeText={_onChangeText}
351
- onSelectionChange={_onSelectionChange}
352
260
  onFocus={_onFocus}
353
261
  onBlur={_onBlur}
354
262
  placeholder={placeholder}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.168.1-beta.2",
3
+ "version": "0.168.1-beta.3",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},