@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.
@@ -333,7 +333,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
333
333
  onRequestClose={() => {
334
334
  onDismiss();
335
335
  }}
336
- style={StyleSheet.absoluteFillObject}
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.absoluteFillObject}
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.absoluteFillObject}
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.absoluteFillObject}
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, InteractionManager } from 'react-native';
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
- const runAfterInteractions = InteractionManager.runAfterInteractions;
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}
@@ -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 digitLimit = props.maxLength ?? Number.POSITIVE_INFINITY;
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 !== digitsRef.current) {
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 || !inputRef.current?.isFocused()) return;
204
+ if (caret === null) return;
195
205
  pendingCaretRef.current = null;
196
- inputRef.current.setSelection(caret, caret);
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 !== digitsRef.current;
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
- const digits = text
278
- .replace(/\D/g, '')
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-beta.2",
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-native-fast-image": "git+https://oauth2:TGi6oBj-LdzsKpLXQSoH@gitlab.mservice.com.vn/momo-platform/public/react-native-fast-image.git#v8.11.0",
12
- "@react-navigation/bottom-tabs": "7.4.2",
13
- "@react-navigation/core": "7.12.1",
14
- "@react-navigation/elements": "2.5.2",
15
- "@react-navigation/native": "7.1.14",
16
- "@react-navigation/routers": "7.4.1",
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.2.4"
26
- },
27
- "peerDependencies": {
28
- "react-native": "*",
29
- "react-native-worklets": "0.6.1"
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
- "publishConfig": {
35
- "registry": "https://registry.npmjs.org/"
36
- },
37
- "license": "MoMo"
35
+ "peerDependencies": {
36
+ "react-native": "*",
37
+ "react-native-reanimated": ">=4.7.0",
38
+ "react-native-worklets": ">=0.13.0"
39
+ }
38
40
  }