@momo-kits/foundation 0.168.1-beta.2 → 0.168.1-rn.88.0
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/Application/BottomSheet.tsx +2 -2
- package/Application/ModalScreen.tsx +2 -2
- package/Application/StackScreen.tsx +7 -2
- package/Divider/index.tsx +3 -3
- package/Image/index.tsx +46 -1
- package/Input/InputMoney.tsx +36 -17
- package/package.json +28 -26
|
@@ -333,7 +333,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
333
333
|
onRequestClose={() => {
|
|
334
334
|
onDismiss();
|
|
335
335
|
}}
|
|
336
|
-
style={StyleSheet.
|
|
336
|
+
style={StyleSheet.absoluteFill}
|
|
337
337
|
isModalKit={true}
|
|
338
338
|
>
|
|
339
339
|
<KeyboardAvoidingView
|
|
@@ -347,7 +347,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
347
347
|
style={styles.container}
|
|
348
348
|
>
|
|
349
349
|
<Pressable
|
|
350
|
-
style={StyleSheet.
|
|
350
|
+
style={StyleSheet.absoluteFill}
|
|
351
351
|
onPress={() => {
|
|
352
352
|
action.current = 'touch';
|
|
353
353
|
onDismiss();
|
|
@@ -133,7 +133,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
133
133
|
transparent
|
|
134
134
|
visible={true}
|
|
135
135
|
onRequestClose={onDismiss}
|
|
136
|
-
style={StyleSheet.
|
|
136
|
+
style={StyleSheet.absoluteFill}
|
|
137
137
|
isModalKit={true}
|
|
138
138
|
>
|
|
139
139
|
<KeyboardAvoidingView
|
|
@@ -141,7 +141,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
141
141
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
142
142
|
>
|
|
143
143
|
<Pressable
|
|
144
|
-
style={StyleSheet.
|
|
144
|
+
style={StyleSheet.absoluteFill}
|
|
145
145
|
onPress={() => onDismiss(undefined, barrierDismissible)}
|
|
146
146
|
>
|
|
147
147
|
<Animated.View style={[styles.overlayContent, { opacity }]} />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
|
-
import { Alert
|
|
2
|
+
import { Alert } from 'react-native';
|
|
3
3
|
import { useHeaderHeight } from '@react-navigation/elements';
|
|
4
4
|
import { ScreenTrackingParams } from './types';
|
|
5
5
|
import Navigation from './Navigation';
|
|
@@ -8,7 +8,12 @@ import { version } from '../package.json';
|
|
|
8
8
|
import { SwipeBackHaptic, useAppState } from './utils';
|
|
9
9
|
import { TooltipPortalHost, TooltipPortalProvider } from './TooltipPortal';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// RN 0.87+: InteractionManager đã bị xoá khỏi react-native → thay bằng requestIdleCallback.
|
|
12
|
+
// Giữ nguyên shape trả về { cancel } để code bên dưới (timeoutInteraction?.cancel?.()) không đổi.
|
|
13
|
+
const runAfterInteractions = (task: () => void) => {
|
|
14
|
+
const handle = requestIdleCallback(() => task());
|
|
15
|
+
return { cancel: () => cancelIdleCallback(handle) };
|
|
16
|
+
};
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* container for stack screen
|
package/Divider/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
-
import { View, ViewStyle } from 'react-native';
|
|
2
|
+
import { StyleProp, View, ViewStyle } from 'react-native';
|
|
3
3
|
import { ApplicationContext } from '../Context';
|
|
4
4
|
import { Spacing } from '../Consts';
|
|
5
5
|
import { DashDivider } from './DashDivider';
|
|
@@ -8,7 +8,7 @@ export interface DividerProps {
|
|
|
8
8
|
/**
|
|
9
9
|
* Custom styles for divider
|
|
10
10
|
*/
|
|
11
|
-
style?: ViewStyle
|
|
11
|
+
style?: StyleProp<ViewStyle>;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Enable margin vertical 4px
|
|
@@ -30,13 +30,13 @@ const Divider: React.FC<DividerProps> = ({
|
|
|
30
30
|
return (
|
|
31
31
|
<View
|
|
32
32
|
style={[
|
|
33
|
-
style,
|
|
34
33
|
{
|
|
35
34
|
height: 1,
|
|
36
35
|
width: '100%',
|
|
37
36
|
backgroundColor: theme.colors.border.default,
|
|
38
37
|
marginVertical: useMargin ? Spacing.XS : 0,
|
|
39
38
|
},
|
|
39
|
+
style,
|
|
40
40
|
]}
|
|
41
41
|
/>
|
|
42
42
|
);
|
package/Image/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext, useRef, useState } from 'react';
|
|
2
|
-
import { StyleSheet, View } from 'react-native';
|
|
2
|
+
import { Image as RNImage, StyleSheet, View } from 'react-native';
|
|
3
3
|
import FastImage from 'react-native-fast-image';
|
|
4
4
|
import styles from './styles';
|
|
5
5
|
import {
|
|
@@ -23,6 +23,10 @@ export const getImageName = (source: any): string => {
|
|
|
23
23
|
return '';
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const isDataUriSource = (source: any): boolean => {
|
|
27
|
+
return typeof source?.uri === 'string' && source.uri.startsWith('data:');
|
|
28
|
+
};
|
|
29
|
+
|
|
26
30
|
const Image: React.FC<ImageProps> = ({
|
|
27
31
|
style,
|
|
28
32
|
source,
|
|
@@ -43,6 +47,7 @@ const Image: React.FC<ImageProps> = ({
|
|
|
43
47
|
const [status, setStatus] = useState<Status>('success');
|
|
44
48
|
|
|
45
49
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
50
|
+
const useFastImage = !isDataUriSource(source);
|
|
46
51
|
|
|
47
52
|
let accessibilityLabel: any = `img|${getImageName(source)}`;
|
|
48
53
|
if (component?.componentId) {
|
|
@@ -79,6 +84,46 @@ const Image: React.FC<ImageProps> = ({
|
|
|
79
84
|
return children;
|
|
80
85
|
};
|
|
81
86
|
|
|
87
|
+
if (!useFastImage) {
|
|
88
|
+
return (
|
|
89
|
+
<RNImage
|
|
90
|
+
{...rest}
|
|
91
|
+
{...{
|
|
92
|
+
accessibilityLabel: rest.accessibilityLabel ?? accessibilityLabel,
|
|
93
|
+
}}
|
|
94
|
+
source={source}
|
|
95
|
+
style={[
|
|
96
|
+
styles.container,
|
|
97
|
+
style,
|
|
98
|
+
showBaseLineDebug && styles.debugBaseLine,
|
|
99
|
+
]}
|
|
100
|
+
onLoadStart={() => {
|
|
101
|
+
error.current = false;
|
|
102
|
+
if (status !== 'loading' && loading) {
|
|
103
|
+
setStatus('loading');
|
|
104
|
+
}
|
|
105
|
+
}}
|
|
106
|
+
onLoad={() => {
|
|
107
|
+
error.current = false;
|
|
108
|
+
}}
|
|
109
|
+
onLoadEnd={() => {
|
|
110
|
+
let current: Status = 'success';
|
|
111
|
+
if (error.current) {
|
|
112
|
+
current = 'error';
|
|
113
|
+
}
|
|
114
|
+
setStatus(prevState =>
|
|
115
|
+
prevState !== current ? current : prevState,
|
|
116
|
+
);
|
|
117
|
+
}}
|
|
118
|
+
onError={() => {
|
|
119
|
+
error.current = true;
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{renderContent()}
|
|
123
|
+
</RNImage>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
82
127
|
return (
|
|
83
128
|
<FastImage
|
|
84
129
|
{...rest}
|
package/Input/InputMoney.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
1
2
|
import React, {
|
|
2
3
|
forwardRef,
|
|
3
4
|
useContext,
|
|
@@ -42,6 +43,7 @@ const countDigits = (text: string) => text.replace(/\D/g, '').length;
|
|
|
42
43
|
const groupDigits = (digits: string) =>
|
|
43
44
|
digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
44
45
|
|
|
46
|
+
// Caret position right after the `digitIndex`-th digit of `display`.
|
|
45
47
|
const caretFromDigitIndex = (display: string, digitIndex: number) => {
|
|
46
48
|
let count = 0;
|
|
47
49
|
for (let i = 0; i < display.length && count < digitIndex; i++) {
|
|
@@ -95,6 +97,9 @@ const detectEdit = (old: string, next: string, selection: Selection): Edit => {
|
|
|
95
97
|
};
|
|
96
98
|
};
|
|
97
99
|
|
|
100
|
+
// Applies `edit` to the digits of `display`. Separators and currency are never
|
|
101
|
+
// deleted: removing a separator deletes the digit before it instead, removing
|
|
102
|
+
// only the currency leaves the digits untouched.
|
|
98
103
|
const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
|
|
99
104
|
const digits = display.replace(/\D/g, '');
|
|
100
105
|
let left = countDigits(display.slice(0, edit.start));
|
|
@@ -160,10 +165,11 @@ const InputMoney = forwardRef(
|
|
|
160
165
|
const inputFontFamily = exportFontFamily('bold');
|
|
161
166
|
const [focused, setFocused] = useState(false);
|
|
162
167
|
const [displayValue, setDisplayValue] = useState('');
|
|
168
|
+
const [numericValue, setNumericValue] = useState('');
|
|
163
169
|
const [commitCount, setCommitCount] = useState(0);
|
|
164
170
|
const displayRef = useRef('');
|
|
165
|
-
const digitsRef = useRef('');
|
|
166
171
|
const selectionRef = useRef<Selection>({ start: 0, end: 0 });
|
|
172
|
+
// Caret to push to native once the committed text is rendered.
|
|
167
173
|
const pendingCaretRef = useRef<number | null>(null);
|
|
168
174
|
const pendingSelectionRef = useRef<Selection | null>(null);
|
|
169
175
|
const editingRef = useRef(false);
|
|
@@ -177,39 +183,54 @@ const InputMoney = forwardRef(
|
|
|
177
183
|
props.accessibilityLabel,
|
|
178
184
|
);
|
|
179
185
|
|
|
180
|
-
const
|
|
186
|
+
const maxLength = (props as any)?.maxLength;
|
|
187
|
+
const hasMaxLength = typeof maxLength === 'number';
|
|
188
|
+
const digitLimit = hasMaxLength ? maxLength : Number.POSITIVE_INFINITY;
|
|
181
189
|
|
|
182
190
|
useEffect(() => {
|
|
183
191
|
if (value !== undefined && typeof value === 'string') {
|
|
184
192
|
const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
|
|
185
|
-
if (cleanValue !==
|
|
193
|
+
if (cleanValue !== numericValue) {
|
|
186
194
|
commit(cleanValue, cleanValue.length);
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
190
197
|
}, [value]);
|
|
191
198
|
|
|
199
|
+
// Always set the caret imperatively: a controlled `selection` prop is only
|
|
200
|
+
// sent to native when it differs from the last native selection, so it is
|
|
201
|
+
// skipped whenever a stale selection event already reported the same value.
|
|
192
202
|
useLayoutEffect(() => {
|
|
193
203
|
const caret = pendingCaretRef.current;
|
|
194
|
-
if (caret === null
|
|
204
|
+
if (caret === null) return;
|
|
195
205
|
pendingCaretRef.current = null;
|
|
196
|
-
inputRef.current
|
|
206
|
+
if (inputRef.current?.isFocused()) {
|
|
207
|
+
inputRef.current.setSelection(caret, caret);
|
|
208
|
+
}
|
|
197
209
|
}, [commitCount]);
|
|
198
210
|
|
|
199
211
|
const commit = (digits: string, caretDigit: number) => {
|
|
200
212
|
const display = digits ? `${groupDigits(digits)}${currency}` : '';
|
|
201
213
|
const caret = caretFromDigitIndex(display, caretDigit);
|
|
202
|
-
const changed = digits !==
|
|
214
|
+
const changed = digits !== numericValue;
|
|
203
215
|
displayRef.current = display;
|
|
204
|
-
digitsRef.current = digits;
|
|
205
216
|
selectionRef.current = { start: caret, end: caret };
|
|
206
217
|
pendingCaretRef.current = caret;
|
|
207
218
|
setCommitCount(count => count + 1);
|
|
208
219
|
setDisplayValue(display);
|
|
220
|
+
setNumericValue(digits);
|
|
209
221
|
return changed;
|
|
210
222
|
};
|
|
211
223
|
|
|
224
|
+
const parseInputText = (text: string): string => {
|
|
225
|
+
if (!text) return '';
|
|
226
|
+
const currencyRegex = new RegExp(` ?${currency}$`, 'g');
|
|
227
|
+
const textWithoutCurrency = text.replace(currencyRegex, '').trim();
|
|
228
|
+
return textWithoutCurrency.replace(/[^\d]/g, '');
|
|
229
|
+
};
|
|
230
|
+
|
|
212
231
|
const onClearText = () => {
|
|
232
|
+
inputRef?.current?.clear();
|
|
233
|
+
inputRef.current?.setNativeProps({ text: '' });
|
|
213
234
|
commit('', 0);
|
|
214
235
|
onChangeText?.('');
|
|
215
236
|
};
|
|
@@ -235,6 +256,7 @@ const InputMoney = forwardRef(
|
|
|
235
256
|
}
|
|
236
257
|
};
|
|
237
258
|
|
|
259
|
+
// Keeps the caret out of the currency suffix.
|
|
238
260
|
const _onSelectionChange = (e: TextInputSelectionChangeEvent) => {
|
|
239
261
|
props.onSelectionChange?.(e);
|
|
240
262
|
if (editingRef.current) return;
|
|
@@ -259,12 +281,6 @@ const InputMoney = forwardRef(
|
|
|
259
281
|
|
|
260
282
|
const _onFocus = (e: TextInputFocusEvent) => {
|
|
261
283
|
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
284
|
onFocus?.(e);
|
|
269
285
|
};
|
|
270
286
|
|
|
@@ -274,8 +290,12 @@ const InputMoney = forwardRef(
|
|
|
274
290
|
};
|
|
275
291
|
|
|
276
292
|
const _setText = (text: string) => {
|
|
277
|
-
|
|
278
|
-
|
|
293
|
+
if (!text) {
|
|
294
|
+
commit('', 0);
|
|
295
|
+
onChangeText?.('');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const digits = parseInputText(text)
|
|
279
299
|
.replace(/^0+(?=\d)/, '')
|
|
280
300
|
.slice(0, digitLimit);
|
|
281
301
|
if (commit(digits, digits.length)) {
|
|
@@ -326,7 +346,6 @@ const InputMoney = forwardRef(
|
|
|
326
346
|
accessibilityLabel={`${componentId}`}
|
|
327
347
|
textAlignVertical="center"
|
|
328
348
|
ref={inputRef}
|
|
329
|
-
editable={props.editable !== false && !disabled}
|
|
330
349
|
accessibilityState={{
|
|
331
350
|
disabled: disabled,
|
|
332
351
|
...props.accessibilityState,
|
package/package.json
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momo-kits/foundation",
|
|
3
|
-
"version": "0.168.1-
|
|
3
|
+
"version": "0.168.1-rn.88.0",
|
|
4
4
|
"description": "React Native Component Kits",
|
|
5
|
-
"main": "index.ts",
|
|
6
|
-
"scripts": {},
|
|
7
5
|
"keywords": [
|
|
8
6
|
"@momo-kits/foundation"
|
|
9
7
|
],
|
|
8
|
+
"license": "MoMo",
|
|
9
|
+
"main": "index.ts",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {},
|
|
10
14
|
"dependencies": {
|
|
11
|
-
"react-
|
|
12
|
-
"@react-navigation/
|
|
13
|
-
"@react-navigation/
|
|
14
|
-
"@react-navigation/
|
|
15
|
-
"@react-navigation/
|
|
16
|
-
"@react-navigation/
|
|
17
|
-
"@react-navigation/stack": "7.4.2",
|
|
18
|
-
"react-native-gesture-handler": "2.27.1",
|
|
19
|
-
"react-native-linear-gradient": "git+https://oauth2:TGi6oBj-LdzsKpLXQSoH@gitlab.mservice.com.vn/momo-platform/public/react-native-linear-gradient#v3.0.0",
|
|
20
|
-
"react-native-reanimated": "4.2.2",
|
|
21
|
-
"react-native-worklets": "0.6.1",
|
|
22
|
-
"react-native-safe-area-context": "5.5.2",
|
|
23
|
-
"react-native-reserved-regions": "0.2.0",
|
|
15
|
+
"@react-navigation/bottom-tabs": "7.21.0",
|
|
16
|
+
"@react-navigation/core": "7.24.0",
|
|
17
|
+
"@react-navigation/elements": "2.9.45",
|
|
18
|
+
"@react-navigation/native": "7.5.1",
|
|
19
|
+
"@react-navigation/routers": "7.6.5",
|
|
20
|
+
"@react-navigation/stack": "7.12.1",
|
|
24
21
|
"@shopify/flash-list": "2.1.0",
|
|
25
|
-
"lottie-react-native": "7.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"react-native": "
|
|
29
|
-
"react-native-
|
|
22
|
+
"lottie-react-native": "7.3.6",
|
|
23
|
+
"react-native-fast-image": "git+https://oauth2:k_r5y_gV6sX9-TqQxgcd@gitlab.mservice.com.vn/momo-platform/public/react-native-fast-image.git#v8.11.0",
|
|
24
|
+
"react-native-gesture-handler": "3.4.0",
|
|
25
|
+
"react-native-linear-gradient": "git+https://oauth2:k_r5y_gV6sX9-TqQxgcd@gitlab.mservice.com.vn/momo-platform/public/react-native-linear-gradient#v3.0.0",
|
|
26
|
+
"react-native-reanimated": "4.7.1",
|
|
27
|
+
"react-native-safe-area-context": "5.7.0",
|
|
28
|
+
"react-native-reserved-regions": "0.2.0"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
|
-
"@types/color": "3.0.6"
|
|
31
|
+
"@types/color": "3.0.6",
|
|
32
|
+
"react-native-reanimated": "4.7.1",
|
|
33
|
+
"react-native-worklets": "0.13.0"
|
|
33
34
|
},
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react-native": "*",
|
|
37
|
+
"react-native-reanimated": ">=4.7.0",
|
|
38
|
+
"react-native-worklets": ">=0.13.0"
|
|
39
|
+
}
|
|
38
40
|
}
|