@momo-kits/foundation 0.167.1-beta.1 → 0.167.1-beta.10
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/Components/HeaderTitle.tsx +24 -10
- package/Application/NavigationContainer.tsx +14 -7
- package/Application/WidgetContainer.tsx +9 -7
- package/Application/utils.tsx +4 -0
- package/Consts/index.ts +0 -1
- package/Consts/theme.ts +2 -2
- package/Input/InputMoney.tsx +192 -50
- package/Text/index.tsx +2 -11
- package/package.json +1 -1
- package/Consts/designSystemFont.ts +0 -38
|
@@ -16,7 +16,16 @@ import {
|
|
|
16
16
|
import { Image } from '../../Image';
|
|
17
17
|
import { Icon } from '../../Icon';
|
|
18
18
|
import { Skeleton } from '../../Skeleton';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
AnimatedCompatValue,
|
|
21
|
+
isWhiteListDesignSystem,
|
|
22
|
+
useNormalizedSharedValue,
|
|
23
|
+
} from '../utils';
|
|
24
|
+
|
|
25
|
+
const useHeaderFontFamily = (): string => {
|
|
26
|
+
const context = useContext<any>(MiniAppContext);
|
|
27
|
+
return isWhiteListDesignSystem(context) ? 'MoMoTrustDisplay' : 'SFProText';
|
|
28
|
+
};
|
|
20
29
|
|
|
21
30
|
type HeaderTitleInterpolate = {
|
|
22
31
|
inputRange: number[];
|
|
@@ -37,6 +46,7 @@ const HeaderTitle: React.FC<
|
|
|
37
46
|
HeaderTitleExtraProps & { [key: string]: any }
|
|
38
47
|
> = props => {
|
|
39
48
|
const context = useContext<any>(MiniAppContext);
|
|
49
|
+
const headerFontFamily = useHeaderFontFamily();
|
|
40
50
|
|
|
41
51
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
42
52
|
|
|
@@ -85,7 +95,7 @@ const HeaderTitle: React.FC<
|
|
|
85
95
|
fontFamily: exportFontFamily(
|
|
86
96
|
'bold',
|
|
87
97
|
'action_xs_bold',
|
|
88
|
-
|
|
98
|
+
headerFontFamily,
|
|
89
99
|
),
|
|
90
100
|
color: props.tintColor,
|
|
91
101
|
},
|
|
@@ -130,6 +140,7 @@ const TitleUser: React.FC<TitleUserProps> = ({
|
|
|
130
140
|
isLoading,
|
|
131
141
|
}) => {
|
|
132
142
|
const { theme } = useContext(ApplicationContext);
|
|
143
|
+
const headerFontFamily = useHeaderFontFamily();
|
|
133
144
|
const maxWidth = Dimensions.get('window').width - 172;
|
|
134
145
|
const getShortName = (name: string) => {
|
|
135
146
|
const words = name.split(' ');
|
|
@@ -378,7 +389,7 @@ const TitleUser: React.FC<TitleUserProps> = ({
|
|
|
378
389
|
<View style={Styles.row}>
|
|
379
390
|
<Text
|
|
380
391
|
typography="action_xs_bold"
|
|
381
|
-
fontFamily=
|
|
392
|
+
fontFamily={headerFontFamily}
|
|
382
393
|
color={tintColor}
|
|
383
394
|
numberOfLines={1}
|
|
384
395
|
>
|
|
@@ -412,6 +423,7 @@ const TitleLocation: React.FC<TitleLocationProps> = ({
|
|
|
412
423
|
onPress,
|
|
413
424
|
isLoading,
|
|
414
425
|
}) => {
|
|
426
|
+
const headerFontFamily = useHeaderFontFamily();
|
|
415
427
|
const maxWidth = Dimensions.get('window').width - 172;
|
|
416
428
|
const renderShimmer = () => {
|
|
417
429
|
return (
|
|
@@ -448,9 +460,12 @@ const TitleLocation: React.FC<TitleLocationProps> = ({
|
|
|
448
460
|
</Text>
|
|
449
461
|
)}
|
|
450
462
|
<View style={Styles.row}>
|
|
451
|
-
<Text
|
|
452
|
-
|
|
453
|
-
|
|
463
|
+
<Text
|
|
464
|
+
typography="action_xs_bold"
|
|
465
|
+
fontFamily={headerFontFamily}
|
|
466
|
+
color={tintColor}
|
|
467
|
+
numberOfLines={1}
|
|
468
|
+
>
|
|
454
469
|
{location}
|
|
455
470
|
</Text>
|
|
456
471
|
<Icon
|
|
@@ -477,6 +492,7 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
|
|
|
477
492
|
onPress,
|
|
478
493
|
isLoading,
|
|
479
494
|
}) => {
|
|
495
|
+
const headerFontFamily = useHeaderFontFamily();
|
|
480
496
|
const { width } = Dimensions.get('screen');
|
|
481
497
|
const maxWidth = Dimensions.get('window').width - 172;
|
|
482
498
|
const startWidth = width * 0.25;
|
|
@@ -512,8 +528,7 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
|
|
|
512
528
|
style={{ maxWidth: startWidth }}
|
|
513
529
|
color={tintColor}
|
|
514
530
|
numberOfLines={1}
|
|
515
|
-
|
|
516
|
-
|
|
531
|
+
fontFamily={headerFontFamily}
|
|
517
532
|
>
|
|
518
533
|
{start}
|
|
519
534
|
</Text>
|
|
@@ -529,8 +544,7 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
|
|
|
529
544
|
typography="action_xs_bold"
|
|
530
545
|
color={tintColor}
|
|
531
546
|
numberOfLines={1}
|
|
532
|
-
|
|
533
|
-
|
|
547
|
+
fontFamily={headerFontFamily}
|
|
534
548
|
>
|
|
535
549
|
{end}
|
|
536
550
|
</Text>
|
|
@@ -17,10 +17,14 @@ import { DeviceEventEmitter } from 'react-native';
|
|
|
17
17
|
import StackScreen from './StackScreen';
|
|
18
18
|
import ModalScreen from './ModalScreen';
|
|
19
19
|
import Navigator from './Navigator';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
getModalOptions,
|
|
22
|
+
getStackOptions,
|
|
23
|
+
isWhiteListDesignSystem,
|
|
24
|
+
useNativeCanGoBack,
|
|
25
|
+
} from './utils';
|
|
21
26
|
import { NavigationContainerProps } from './types';
|
|
22
27
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
23
|
-
import { useDefaultRegularFontFamily } from '../Consts/designSystemFont';
|
|
24
28
|
import Localize from './Localize';
|
|
25
29
|
import { Colors, defaultTheme } from '../Consts';
|
|
26
30
|
import { HeaderLeft } from './Components/HeaderLeft';
|
|
@@ -103,8 +107,10 @@ const Navigation: React.FC<any> = ({
|
|
|
103
107
|
const navigator = useRef(new Navigator(navigationRef, isReady));
|
|
104
108
|
const [showGrid, setShowGrid] = useState(false);
|
|
105
109
|
const insets = useSafeAreaInsets();
|
|
106
|
-
const defaultRegularFontFamily = useDefaultRegularFontFamily();
|
|
107
110
|
const syncCanGoBack = useNativeCanGoBack();
|
|
111
|
+
const isWhiteList = isWhiteListDesignSystem(context);
|
|
112
|
+
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
113
|
+
const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
|
|
108
114
|
let headerBackground = context?.designSystemConfig?.config?.headerBar;
|
|
109
115
|
let headerGradient = theme.colors?.gradient;
|
|
110
116
|
|
|
@@ -156,6 +162,7 @@ const Navigation: React.FC<any> = ({
|
|
|
156
162
|
navigator: navigator.current,
|
|
157
163
|
theme: {
|
|
158
164
|
...theme,
|
|
165
|
+
font: fontFamily,
|
|
159
166
|
colors: {
|
|
160
167
|
...theme.colors,
|
|
161
168
|
gradient: headerGradient,
|
|
@@ -183,19 +190,19 @@ const Navigation: React.FC<any> = ({
|
|
|
183
190
|
},
|
|
184
191
|
fonts: {
|
|
185
192
|
regular: {
|
|
186
|
-
fontFamily:
|
|
193
|
+
fontFamily: navigationFontFamily,
|
|
187
194
|
fontWeight: '400',
|
|
188
195
|
},
|
|
189
196
|
medium: {
|
|
190
|
-
fontFamily:
|
|
197
|
+
fontFamily: navigationFontFamily,
|
|
191
198
|
fontWeight: '400',
|
|
192
199
|
},
|
|
193
200
|
bold: {
|
|
194
|
-
fontFamily:
|
|
201
|
+
fontFamily: navigationFontFamily,
|
|
195
202
|
fontWeight: '400',
|
|
196
203
|
},
|
|
197
204
|
heavy: {
|
|
198
|
-
fontFamily:
|
|
205
|
+
fontFamily: navigationFontFamily,
|
|
199
206
|
fontWeight: '400',
|
|
200
207
|
},
|
|
201
208
|
},
|
|
@@ -3,7 +3,6 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
|
3
3
|
import Navigator from './Navigator';
|
|
4
4
|
import { WidgetContainerProps } from './types';
|
|
5
5
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
6
|
-
import { useDefaultRegularFontFamily } from '../Consts/designSystemFont';
|
|
7
6
|
import Localize from './Localize';
|
|
8
7
|
import { defaultTheme } from '../Consts';
|
|
9
8
|
import {
|
|
@@ -11,7 +10,7 @@ import {
|
|
|
11
10
|
NavigationContainerRef,
|
|
12
11
|
NavigationIndependentTree,
|
|
13
12
|
} from '@react-navigation/native';
|
|
14
|
-
import { getModalOptions } from './utils';
|
|
13
|
+
import { getModalOptions, isWhiteListDesignSystem } from './utils';
|
|
15
14
|
import { createStackNavigator } from '@react-navigation/stack';
|
|
16
15
|
import ModalScreen from './ModalScreen';
|
|
17
16
|
import { View } from 'react-native';
|
|
@@ -43,7 +42,6 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
43
42
|
const isReady = useRef(false);
|
|
44
43
|
const navigator = useRef(new Navigator(navigationRef, isReady, true));
|
|
45
44
|
const [currentContext, setCurrentContext] = useState({});
|
|
46
|
-
const defaultRegularFontFamily = useDefaultRegularFontFamily();
|
|
47
45
|
|
|
48
46
|
useEffect(() => {
|
|
49
47
|
const sub = maxApi?.observer?.('font_scale_config', (data: any) => {
|
|
@@ -80,6 +78,9 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
80
78
|
...context,
|
|
81
79
|
...currentContext,
|
|
82
80
|
};
|
|
81
|
+
const isWhiteList = isWhiteListDesignSystem(mergedContext);
|
|
82
|
+
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
83
|
+
const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
|
|
83
84
|
|
|
84
85
|
return (
|
|
85
86
|
<View style={{ height: height ?? '100%', minHeight: 1 }}>
|
|
@@ -90,6 +91,7 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
90
91
|
navigator: navigator.current,
|
|
91
92
|
theme: {
|
|
92
93
|
...theme,
|
|
94
|
+
font: fontFamily,
|
|
93
95
|
colors: {
|
|
94
96
|
...theme.colors,
|
|
95
97
|
gradient: headerGradient,
|
|
@@ -116,19 +118,19 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
116
118
|
},
|
|
117
119
|
fonts: {
|
|
118
120
|
regular: {
|
|
119
|
-
fontFamily:
|
|
121
|
+
fontFamily: navigationFontFamily,
|
|
120
122
|
fontWeight: '400',
|
|
121
123
|
},
|
|
122
124
|
medium: {
|
|
123
|
-
fontFamily:
|
|
125
|
+
fontFamily: navigationFontFamily,
|
|
124
126
|
fontWeight: '400',
|
|
125
127
|
},
|
|
126
128
|
bold: {
|
|
127
|
-
fontFamily:
|
|
129
|
+
fontFamily: navigationFontFamily,
|
|
128
130
|
fontWeight: '400',
|
|
129
131
|
},
|
|
130
132
|
heavy: {
|
|
131
|
-
fontFamily:
|
|
133
|
+
fontFamily: navigationFontFamily,
|
|
132
134
|
fontWeight: '400',
|
|
133
135
|
},
|
|
134
136
|
},
|
package/Application/utils.tsx
CHANGED
|
@@ -475,6 +475,9 @@ const SwipeBackHaptic = () => {
|
|
|
475
475
|
return null;
|
|
476
476
|
};
|
|
477
477
|
|
|
478
|
+
const isWhiteListDesignSystem = (context: any): boolean =>
|
|
479
|
+
context?.designSystemConfig?.isWhiteListDesignSystem === true;
|
|
480
|
+
|
|
478
481
|
export {
|
|
479
482
|
getStackOptions,
|
|
480
483
|
getModalOptions,
|
|
@@ -487,4 +490,5 @@ export {
|
|
|
487
490
|
useNativeCanGoBack,
|
|
488
491
|
useNormalizedSharedValue,
|
|
489
492
|
SwipeBackHaptic,
|
|
493
|
+
isWhiteListDesignSystem,
|
|
490
494
|
};
|
package/Consts/index.ts
CHANGED
package/Consts/theme.ts
CHANGED
|
@@ -51,7 +51,7 @@ const defaultTheme: Theme = {
|
|
|
51
51
|
container: Colors.blue_08,
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
|
-
font: '
|
|
54
|
+
font: 'SFProText',
|
|
55
55
|
assets: {},
|
|
56
56
|
};
|
|
57
57
|
|
|
@@ -105,7 +105,7 @@ const defaultDarkTheme: Theme = {
|
|
|
105
105
|
container: Colors.blue_08,
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
|
-
font: '
|
|
108
|
+
font: 'SFProText',
|
|
109
109
|
assets: {},
|
|
110
110
|
};
|
|
111
111
|
|
package/Input/InputMoney.tsx
CHANGED
|
@@ -4,13 +4,16 @@ import React, {
|
|
|
4
4
|
useContext,
|
|
5
5
|
useEffect,
|
|
6
6
|
useImperativeHandle,
|
|
7
|
+
useLayoutEffect,
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
9
10
|
} from 'react';
|
|
10
11
|
import {
|
|
11
12
|
NativeSyntheticEvent,
|
|
13
|
+
Platform,
|
|
12
14
|
TextInput,
|
|
13
15
|
TextInputFocusEventData,
|
|
16
|
+
TextInputSelectionChangeEventData,
|
|
14
17
|
TouchableOpacity,
|
|
15
18
|
View,
|
|
16
19
|
} from 'react-native';
|
|
@@ -32,9 +35,124 @@ import {
|
|
|
32
35
|
} from './common';
|
|
33
36
|
import { InputMoneyProps } from './index';
|
|
34
37
|
import styles from './styles';
|
|
35
|
-
import { formatNumberToMoney } from './utils';
|
|
36
38
|
import SystemTextInput from './SystemTextInput';
|
|
37
39
|
|
|
40
|
+
type Selection = { start: number; end: number };
|
|
41
|
+
type Edit = { start: number; end: number; inserted: string };
|
|
42
|
+
|
|
43
|
+
const countDigits = (text: string) => text.replace(/\D/g, '').length;
|
|
44
|
+
|
|
45
|
+
const groupDigits = (digits: string) =>
|
|
46
|
+
digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
47
|
+
|
|
48
|
+
// Caret position right after the `digitIndex`-th digit of `display`.
|
|
49
|
+
const caretFromDigitIndex = (display: string, digitIndex: number) => {
|
|
50
|
+
let count = 0;
|
|
51
|
+
for (let i = 0; i < display.length && count < digitIndex; i++) {
|
|
52
|
+
if (/\d/.test(display[i])) {
|
|
53
|
+
count += 1;
|
|
54
|
+
if (count === digitIndex) return i + 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Finds which range of `old` was replaced to produce `next`. `selection` is
|
|
61
|
+
// the pre-edit selection on iOS and already the post-edit caret on Android,
|
|
62
|
+
// where onSelectionChange fires before onChangeText.
|
|
63
|
+
const detectEdit = (old: string, next: string, selection: Selection): Edit => {
|
|
64
|
+
const { start, end } = selection;
|
|
65
|
+
const removed = old.length - next.length;
|
|
66
|
+
const preEdit: Edit[] = [
|
|
67
|
+
{
|
|
68
|
+
start,
|
|
69
|
+
end,
|
|
70
|
+
inserted: next.slice(start, next.length - (old.length - end)),
|
|
71
|
+
},
|
|
72
|
+
{ start: start - removed, end: start, inserted: '' },
|
|
73
|
+
{ start, end: start + removed, inserted: '' },
|
|
74
|
+
];
|
|
75
|
+
const postEdit: Edit[] = [];
|
|
76
|
+
const tail = next.length - start;
|
|
77
|
+
if (start === end && tail <= old.length) {
|
|
78
|
+
let prefix = 0;
|
|
79
|
+
while (
|
|
80
|
+
prefix < Math.min(start, old.length - tail) &&
|
|
81
|
+
old[prefix] === next[prefix]
|
|
82
|
+
) {
|
|
83
|
+
prefix += 1;
|
|
84
|
+
}
|
|
85
|
+
postEdit.push({
|
|
86
|
+
start: prefix,
|
|
87
|
+
end: old.length - tail,
|
|
88
|
+
inserted: next.slice(prefix, start),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const candidates =
|
|
92
|
+
Platform.OS === 'android'
|
|
93
|
+
? [...postEdit, ...preEdit]
|
|
94
|
+
: [...preEdit, ...postEdit];
|
|
95
|
+
const match = candidates.find(
|
|
96
|
+
e =>
|
|
97
|
+
e.start >= 0 &&
|
|
98
|
+
e.end >= e.start &&
|
|
99
|
+
e.end <= old.length &&
|
|
100
|
+
old.slice(0, e.start) + e.inserted + old.slice(e.end) === next,
|
|
101
|
+
);
|
|
102
|
+
if (match) return match;
|
|
103
|
+
|
|
104
|
+
let prefix = 0;
|
|
105
|
+
while (
|
|
106
|
+
prefix < Math.min(old.length, next.length) &&
|
|
107
|
+
old[prefix] === next[prefix]
|
|
108
|
+
) {
|
|
109
|
+
prefix += 1;
|
|
110
|
+
}
|
|
111
|
+
let suffix = 0;
|
|
112
|
+
while (
|
|
113
|
+
suffix < Math.min(old.length, next.length) - prefix &&
|
|
114
|
+
old[old.length - 1 - suffix] === next[next.length - 1 - suffix]
|
|
115
|
+
) {
|
|
116
|
+
suffix += 1;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
start: prefix,
|
|
120
|
+
end: old.length - suffix,
|
|
121
|
+
inserted: next.slice(prefix, next.length - suffix),
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Applies `edit` to the digits of `display`. Separators and currency are never
|
|
126
|
+
// deleted: removing a separator deletes the digit before it instead, removing
|
|
127
|
+
// only the currency leaves the digits untouched.
|
|
128
|
+
const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
|
|
129
|
+
const digits = display.replace(/\D/g, '');
|
|
130
|
+
let left = countDigits(display.slice(0, edit.start));
|
|
131
|
+
let removed = countDigits(display.slice(edit.start, edit.end));
|
|
132
|
+
const inserted = edit.inserted.replace(/\D/g, '');
|
|
133
|
+
if (
|
|
134
|
+
!removed &&
|
|
135
|
+
!inserted &&
|
|
136
|
+
left > 0 &&
|
|
137
|
+
display.slice(edit.start, edit.end).includes('.')
|
|
138
|
+
) {
|
|
139
|
+
left -= 1;
|
|
140
|
+
removed = 1;
|
|
141
|
+
}
|
|
142
|
+
const next = digits.slice(0, left) + inserted + digits.slice(left + removed);
|
|
143
|
+
const trimmed = next.replace(/^0+(?=\d)/, '');
|
|
144
|
+
if ((!removed && !inserted) || trimmed.length > digitLimit) {
|
|
145
|
+
return { digits, caretDigit: left };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
digits: trimmed,
|
|
149
|
+
caretDigit: Math.max(
|
|
150
|
+
0,
|
|
151
|
+
left + inserted.length - (next.length - trimmed.length),
|
|
152
|
+
),
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
|
|
38
156
|
const InputMoney = forwardRef(
|
|
39
157
|
(
|
|
40
158
|
{
|
|
@@ -73,7 +191,14 @@ const InputMoney = forwardRef(
|
|
|
73
191
|
const [focused, setFocused] = useState(false);
|
|
74
192
|
const [displayValue, setDisplayValue] = useState('');
|
|
75
193
|
const [numericValue, setNumericValue] = useState('');
|
|
76
|
-
const [
|
|
194
|
+
const [commitCount, setCommitCount] = useState(0);
|
|
195
|
+
const displayRef = useRef('');
|
|
196
|
+
const selectionRef = useRef<Selection>({ start: 0, end: 0 });
|
|
197
|
+
// Caret to push to native once the committed text is rendered.
|
|
198
|
+
const pendingCaretRef = useRef<number | null>(null);
|
|
199
|
+
// Caret the native input reports for the raw, unformatted text. On iOS that
|
|
200
|
+
// event lands after onChangeText and must not override the formatted caret.
|
|
201
|
+
const rawCaretRef = useRef<number | null>(null);
|
|
77
202
|
const inputRef = useRef<TextInput>(null);
|
|
78
203
|
const componentName = 'InputMoney';
|
|
79
204
|
|
|
@@ -91,25 +216,36 @@ const InputMoney = forwardRef(
|
|
|
91
216
|
|
|
92
217
|
useEffect(() => {
|
|
93
218
|
if (value !== undefined && typeof value === 'string') {
|
|
94
|
-
const cleanValue = value.replace(/\D/g, '');
|
|
219
|
+
const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
|
|
95
220
|
if (cleanValue !== numericValue) {
|
|
96
|
-
|
|
97
|
-
updateDisplayValue(cleanValue);
|
|
221
|
+
commit(cleanValue, cleanValue.length);
|
|
98
222
|
}
|
|
99
223
|
}
|
|
100
224
|
}, [value]);
|
|
101
225
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
226
|
+
// Always set the caret imperatively: a controlled `selection` prop is only
|
|
227
|
+
// sent to native when it differs from the last native selection, so it is
|
|
228
|
+
// skipped whenever a stale selection event already reported the same value.
|
|
229
|
+
useLayoutEffect(() => {
|
|
230
|
+
const caret = pendingCaretRef.current;
|
|
231
|
+
if (caret === null) return;
|
|
232
|
+
pendingCaretRef.current = null;
|
|
233
|
+
if (inputRef.current?.isFocused()) {
|
|
234
|
+
inputRef.current.setSelection(caret, caret);
|
|
110
235
|
}
|
|
111
|
-
|
|
112
|
-
|
|
236
|
+
}, [commitCount]);
|
|
237
|
+
|
|
238
|
+
const commit = (digits: string, caretDigit: number) => {
|
|
239
|
+
const display = digits ? `${groupDigits(digits)}${currency}` : '';
|
|
240
|
+
const caret = caretFromDigitIndex(display, caretDigit);
|
|
241
|
+
const changed = digits !== numericValue;
|
|
242
|
+
displayRef.current = display;
|
|
243
|
+
selectionRef.current = { start: caret, end: caret };
|
|
244
|
+
pendingCaretRef.current = caret;
|
|
245
|
+
setCommitCount(count => count + 1);
|
|
246
|
+
setDisplayValue(display);
|
|
247
|
+
setNumericValue(digits);
|
|
248
|
+
return changed;
|
|
113
249
|
};
|
|
114
250
|
|
|
115
251
|
const parseInputText = (text: string): string => {
|
|
@@ -119,47 +255,49 @@ const InputMoney = forwardRef(
|
|
|
119
255
|
return textWithoutCurrency.replace(/[^\d]/g, '');
|
|
120
256
|
};
|
|
121
257
|
|
|
122
|
-
const updateNumericValue = (val: string) => {
|
|
123
|
-
let next = val.replace(/[^\d]/g, '');
|
|
124
|
-
if (next.length > digitLimit) next = next.slice(0, digitLimit);
|
|
125
|
-
if (/^\d*$/.test(next)) {
|
|
126
|
-
setNumericValue(next);
|
|
127
|
-
updateDisplayValue(next);
|
|
128
|
-
onChangeText?.(next);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
258
|
const onClearText = () => {
|
|
133
259
|
inputRef?.current?.clear();
|
|
134
260
|
inputRef.current?.setNativeProps({ text: '' });
|
|
135
|
-
|
|
136
|
-
setNumericValue('');
|
|
261
|
+
commit('', 0);
|
|
137
262
|
onChangeText?.('');
|
|
138
263
|
};
|
|
139
264
|
|
|
140
265
|
const _onChangeText = (text: string) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
266
|
+
let digits = '';
|
|
267
|
+
let caretDigit = 0;
|
|
268
|
+
rawCaretRef.current = 0;
|
|
269
|
+
if (text) {
|
|
270
|
+
const edit = detectEdit(displayRef.current, text, selectionRef.current);
|
|
271
|
+
rawCaretRef.current = edit.start + edit.inserted.length;
|
|
272
|
+
({ digits, caretDigit } = applyEdit(
|
|
273
|
+
displayRef.current,
|
|
274
|
+
edit,
|
|
275
|
+
digitLimit,
|
|
276
|
+
));
|
|
277
|
+
}
|
|
278
|
+
if (commit(digits, caretDigit)) {
|
|
279
|
+
onChangeText?.(digits);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Keeps the caret out of the currency suffix.
|
|
284
|
+
const _onSelectionChange = (
|
|
285
|
+
e: NativeSyntheticEvent<TextInputSelectionChangeEventData>,
|
|
286
|
+
) => {
|
|
287
|
+
props.onSelectionChange?.(e);
|
|
288
|
+
const { start, end } = e.nativeEvent.selection;
|
|
289
|
+
const rawCaret = rawCaretRef.current;
|
|
290
|
+
rawCaretRef.current = null;
|
|
291
|
+
if (start === end && start === rawCaret) {
|
|
145
292
|
return;
|
|
146
293
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const currentNumericValue = numericValue;
|
|
154
|
-
if (currentNumericValue.length > 0) {
|
|
155
|
-
const newNumericValue = currentNumericValue.slice(0, -1);
|
|
156
|
-
updateNumericValue(newNumericValue);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
294
|
+
const display = displayRef.current;
|
|
295
|
+
const max = display ? display.length - currency.length : 0;
|
|
296
|
+
const clamped = { start: Math.min(start, max), end: Math.min(end, max) };
|
|
297
|
+
selectionRef.current = clamped;
|
|
298
|
+
if (clamped.start !== start || clamped.end !== end) {
|
|
299
|
+
inputRef.current?.setSelection(clamped.start, clamped.end);
|
|
160
300
|
}
|
|
161
|
-
const newNumericValue = parseInputText(text);
|
|
162
|
-
updateNumericValue(newNumericValue);
|
|
163
301
|
};
|
|
164
302
|
|
|
165
303
|
const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
@@ -174,13 +312,16 @@ const InputMoney = forwardRef(
|
|
|
174
312
|
|
|
175
313
|
const _setText = (text: string) => {
|
|
176
314
|
if (!text) {
|
|
177
|
-
|
|
178
|
-
setNumericValue('');
|
|
315
|
+
commit('', 0);
|
|
179
316
|
onChangeText?.('');
|
|
180
317
|
return;
|
|
181
318
|
}
|
|
182
|
-
const
|
|
183
|
-
|
|
319
|
+
const digits = parseInputText(text)
|
|
320
|
+
.replace(/^0+(?=\d)/, '')
|
|
321
|
+
.slice(0, digitLimit);
|
|
322
|
+
if (commit(digits, digits.length)) {
|
|
323
|
+
onChangeText?.(digits);
|
|
324
|
+
}
|
|
184
325
|
};
|
|
185
326
|
|
|
186
327
|
useImperativeHandle(ref, () => {
|
|
@@ -247,6 +388,7 @@ const InputMoney = forwardRef(
|
|
|
247
388
|
textBreakStrategy="highQuality"
|
|
248
389
|
value={displayValue}
|
|
249
390
|
onChangeText={_onChangeText}
|
|
391
|
+
onSelectionChange={_onSelectionChange}
|
|
250
392
|
onFocus={_onFocus}
|
|
251
393
|
onBlur={_onBlur}
|
|
252
394
|
placeholder={placeholder}
|
package/Text/index.tsx
CHANGED
|
@@ -7,10 +7,6 @@ import {
|
|
|
7
7
|
MiniAppContext,
|
|
8
8
|
SkeletonContext,
|
|
9
9
|
} from '../Context';
|
|
10
|
-
import {
|
|
11
|
-
NEW_DEFAULT_FONT_FAMILY,
|
|
12
|
-
useResolvedFontFamily,
|
|
13
|
-
} from '../Consts/designSystemFont';
|
|
14
10
|
import { scaleSize, useScaleSize } from './utils';
|
|
15
11
|
import { Skeleton } from '../Skeleton';
|
|
16
12
|
|
|
@@ -118,8 +114,7 @@ const TypoStyles = (typo: Typography, newFontFamily?: string) => {
|
|
|
118
114
|
const typoStyle = styleSheet[typo] ?? styleSheet.paragraph_default;
|
|
119
115
|
const { fontWeight, fontSize, fontStyle, lineHeight } = typoStyle;
|
|
120
116
|
const style = FontStyle[fontStyle ?? 'normal'];
|
|
121
|
-
const font =
|
|
122
|
-
const resolvedDefaultFamily = useResolvedFontFamily(NEW_DEFAULT_FONT_FAMILY);
|
|
117
|
+
const font = newFontFamily ?? theme.font;
|
|
123
118
|
let fontFamily: string;
|
|
124
119
|
|
|
125
120
|
switch (font) {
|
|
@@ -148,11 +143,7 @@ const TypoStyles = (typo: Typography, newFontFamily?: string) => {
|
|
|
148
143
|
break;
|
|
149
144
|
}
|
|
150
145
|
default: {
|
|
151
|
-
fontFamily = `${
|
|
152
|
-
resolvedDefaultFamily === NEW_DEFAULT_FONT_FAMILY
|
|
153
|
-
? MomoTrustSans[fontWeight]
|
|
154
|
-
: SFProText[fontWeight]
|
|
155
|
-
}`;
|
|
146
|
+
fontFamily = `${font}-${SFProText[fontWeight]}${style}`;
|
|
156
147
|
}
|
|
157
148
|
}
|
|
158
149
|
|
package/package.json
CHANGED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { useContext } from 'react';
|
|
2
|
-
import { MiniAppContext } from '../Context';
|
|
3
|
-
|
|
4
|
-
export const NEW_DEFAULT_FONT_FAMILY = 'MomoTrustSans';
|
|
5
|
-
export const LEGACY_DEFAULT_FONT_FAMILY = 'SFProText';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Font thuoc bo design system moi. Ngoai whitelist thi ha het ve font cu:
|
|
9
|
-
* MoMoTrustDisplay chi duoc dung tu commit header title 5bfde6251 (DS-791),
|
|
10
|
-
* truoc do header lay font theo theme.
|
|
11
|
-
*/
|
|
12
|
-
const NEW_FONT_FAMILIES = [NEW_DEFAULT_FONT_FAMILY, 'MoMoTrustDisplay'];
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Cờ whitelist design system, host đẩy xuống qua `decorateProps`
|
|
16
|
-
* (`designSystemConfig.isWhiteListDesignSystem`). Ngoài whitelist thì kit giữ
|
|
17
|
-
* nguyên font cũ.
|
|
18
|
-
*/
|
|
19
|
-
export const useIsDesignSystemWhiteList = (): boolean => {
|
|
20
|
-
const context = useContext<any>(MiniAppContext);
|
|
21
|
-
return context?.designSystemConfig?.isWhiteListDesignSystem === true;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Hạ font mặc định mới về font cũ khi user không nằm trong whitelist.
|
|
26
|
-
* Font do caller chỉ định tường minh (MoMoSignature, AlegreyaSans...) giữ nguyên.
|
|
27
|
-
*/
|
|
28
|
-
export const useResolvedFontFamily = (font: string): string => {
|
|
29
|
-
const isWhiteList = useIsDesignSystemWhiteList();
|
|
30
|
-
if (NEW_FONT_FAMILIES.includes(font) && !isWhiteList) {
|
|
31
|
-
return LEGACY_DEFAULT_FONT_FAMILY;
|
|
32
|
-
}
|
|
33
|
-
return font;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/** Tên file font Regular tương ứng với nhánh whitelist đang áp dụng. */
|
|
37
|
-
export const useDefaultRegularFontFamily = (): string =>
|
|
38
|
-
`${useResolvedFontFamily(NEW_DEFAULT_FONT_FAMILY)}-Regular`;
|