@momo-kits/foundation 0.169.6 → 0.169.7-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/BottomSheet.hooks.ts +8 -8
- package/Application/BottomSheet.parts.tsx +34 -44
- package/Application/BottomSheet.tsx +37 -40
- package/Application/BottomTab/ActiveIndicator.tsx +14 -15
- package/Application/BottomTab/BottomTabBar.tsx +62 -49
- package/Application/BottomTab/BottomTabInset.tsx +4 -8
- package/Application/BottomTab/CurvedContainer.tsx +12 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
- package/Application/BottomTab/DuoBarCompression.tsx +19 -13
- package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
- package/Application/BottomTab/DuoTabBar.tsx +10 -8
- package/Application/BottomTab/TabBarIcon.tsx +29 -15
- package/Application/BottomTab/TabScreen.tsx +5 -1
- package/Application/BottomTab/index.tsx +30 -33
- package/Application/BottomTab/metrics.ts +23 -23
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +0 -4
- package/Application/Components/DuoGlassBackground.tsx +4 -5
- package/Application/Components/HeaderAnimated.tsx +0 -4
- package/Application/Components/HeaderBackground.tsx +0 -4
- package/Application/Components/HeaderExtendHeader.tsx +10 -11
- package/Application/Components/HeaderLeft.tsx +1 -2
- package/Application/Components/HeaderRight.tsx +1 -2
- package/Application/Components/HeaderTitle.shared.ts +9 -11
- package/Application/Components/HeaderTitle.tsx +3 -4
- package/Application/Components/HeaderToolkitAction.tsx +20 -17
- package/Application/Components/NavigationButton.tsx +1 -2
- package/Application/Components/SearchHeader.tsx +10 -17
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +32 -17
- package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
- package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
- package/Application/KitAdoption/AdoptionStore.ts +80 -0
- package/Application/KitAdoption/Authorship/utils.ts +137 -0
- package/Application/KitAdoption/Slots/utils.ts +68 -0
- package/Application/KitAdoption/constants.ts +120 -0
- package/Application/KitAdoption/index.ts +12 -0
- package/Application/KitAdoption/styles.ts +55 -0
- package/Application/KitAdoption/types.ts +61 -0
- package/Application/KitAdoption/utils.ts +376 -0
- package/Application/Localize.ts +0 -7
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +16 -14
- package/Application/NavigationContainer.tsx +18 -19
- package/Application/Navigator.ts +48 -20
- package/Application/ScaleSizeProvider.tsx +6 -13
- package/Application/StackScreen.tracking.ts +33 -11
- package/Application/StackScreen.tsx +54 -10
- package/Application/TooltipPortal.tsx +5 -12
- package/Application/WidgetContainer.tsx +4 -10
- package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
- package/Application/__tests__/KitAdoption.test.ts +283 -0
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
- package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
- package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/modalLayout.ts +14 -13
- package/Application/navigationOptions.tsx +17 -21
- package/Badge/Badge.tsx +0 -5
- package/Badge/BadgeDot.tsx +0 -1
- package/Badge/BadgeDotAnimation.tsx +7 -15
- package/Badge/BadgeRibbon.tsx +38 -46
- package/Badge/Shape.tsx +0 -2
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +16 -5
- package/Button/utils.ts +15 -9
- package/CheckBox/index.tsx +0 -4
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +1 -5
- package/Divider/DashDivider.tsx +0 -1
- package/Divider/index.tsx +0 -4
- package/FoundationList/index.tsx +0 -4
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +12 -4
- package/Image/index.tsx +7 -9
- package/Input/Input.tsx +9 -8
- package/Input/InputDropDown.tsx +12 -10
- package/Input/InputMoney.tsx +8 -12
- package/Input/InputMoneyView.tsx +7 -14
- package/Input/InputOTP.tsx +33 -35
- package/Input/InputPhoneNumber.tsx +9 -8
- package/Input/InputSearch.tsx +6 -4
- package/Input/InputTextArea.tsx +0 -4
- package/Input/SearchTextTyping.tsx +0 -4
- package/Input/SystemTextInput.tsx +2 -7
- package/Input/TextTyping.tsx +0 -4
- package/Input/common.tsx +12 -26
- package/Input/index.tsx +20 -17
- package/Input/moneyUtils.ts +4 -10
- package/Input/styles.ts +5 -0
- package/Input/utils.ts +12 -14
- package/Layout/Card.tsx +13 -16
- package/Layout/FloatingButton.tsx +0 -4
- package/Layout/Item.tsx +2 -6
- package/Layout/ItemList.tsx +7 -9
- package/Layout/ItemSectionList.tsx +1 -2
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +58 -56
- package/Layout/Section.tsx +13 -17
- package/Layout/TrackingScope.tsx +0 -1
- package/Layout/constants.ts +19 -0
- package/Layout/screenContent.tsx +2 -4
- package/Layout/screenFooter.tsx +18 -28
- package/Layout/screenHeader.tsx +31 -34
- package/Layout/screenQuickReturn.tsx +20 -17
- package/Layout/screenScroll.ts +7 -4
- package/Layout/screenTypes.ts +2 -3
- package/Layout/types.ts +41 -3
- package/Layout/useGridSystem.ts +49 -0
- package/Layout/utils.ts +37 -38
- package/Loader/DotLoader.tsx +0 -4
- package/Loader/Loader.tsx +0 -4
- package/Loader/ProgressBar.tsx +0 -1
- package/Loader/Spinner.tsx +0 -4
- package/Loader/utils.ts +7 -11
- package/Pagination/Dot.tsx +0 -4
- package/Pagination/PaginationDot.tsx +0 -1
- package/Pagination/PaginationNumber.tsx +3 -5
- package/Pagination/PaginationScroll.tsx +0 -5
- package/Pagination/PaginationWhiteDot.tsx +0 -1
- package/Pagination/index.tsx +0 -4
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +48 -44
- package/Popup/PopupPromotion.tsx +7 -4
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +0 -4
- package/Radio/index.tsx +0 -4
- package/Skeleton/index.tsx +0 -8
- package/Switch/index.tsx +0 -1
- package/Tag/index.tsx +1 -5
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +3 -14
- package/Text/styles.ts +1 -1
- package/Text/types.ts +6 -1
- package/Text/utils.ts +4 -20
- package/Title/index.tsx +32 -41
- package/ViewBoundary/index.tsx +2 -4
- package/index.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COUNTED_FIBER_TAGS,
|
|
3
|
+
FIBER_TAG,
|
|
4
|
+
FOUNDATION_REEXPORT_NAMES,
|
|
5
|
+
HOST_RN_PREFIXES,
|
|
6
|
+
LIB_UNIT_NAMES,
|
|
7
|
+
MAX_FIBERS,
|
|
8
|
+
REACT_FORWARD_REF_TYPE,
|
|
9
|
+
REACT_MEMO_TYPE,
|
|
10
|
+
RN_PRIMITIVE_NAMES,
|
|
11
|
+
TOP_TRANSPARENT_LIMIT,
|
|
12
|
+
} from './constants';
|
|
13
|
+
import {
|
|
14
|
+
getAuthorFiber,
|
|
15
|
+
isAppAuthored,
|
|
16
|
+
isHostDefined,
|
|
17
|
+
} from './Authorship/utils';
|
|
18
|
+
import { collectCellSlots, collectSlotProps, isListCell } from './Slots/utils';
|
|
19
|
+
import type {
|
|
20
|
+
AdoptionBucket,
|
|
21
|
+
AdoptionTrackingParams,
|
|
22
|
+
FiberLike,
|
|
23
|
+
KitRegistries,
|
|
24
|
+
SnapshotStatus,
|
|
25
|
+
WalkResult,
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
28
|
+
const ANONYMOUS_NAME = 'Anonymous';
|
|
29
|
+
const UNKNOWN_APP_ID = 'unknown';
|
|
30
|
+
|
|
31
|
+
type LooseObject = Record<string, unknown>;
|
|
32
|
+
type Classification = AdoptionBucket | 'unit' | 'transparent';
|
|
33
|
+
type WalkEntry = { fiber: FiberLike; unitSlots: Set<unknown> | null };
|
|
34
|
+
type Tally = {
|
|
35
|
+
counts: Record<AdoptionBucket, number>;
|
|
36
|
+
names: Record<AdoptionBucket, Record<string, number>>;
|
|
37
|
+
transparent: Record<string, number>;
|
|
38
|
+
appAuthoredSeen: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* True for non-null objects and functions, the only values React fibers and
|
|
43
|
+
* component types can hold.
|
|
44
|
+
*/
|
|
45
|
+
const isObjectLike = (value: unknown): value is LooseObject =>
|
|
46
|
+
(typeof value === 'object' && value !== null) || typeof value === 'function';
|
|
47
|
+
|
|
48
|
+
/** Non-empty string name, or undefined. */
|
|
49
|
+
const readName = (value: unknown): string | undefined =>
|
|
50
|
+
typeof value === 'string' && value !== '' ? value : undefined;
|
|
51
|
+
|
|
52
|
+
const readNameOf = (value: unknown): string | undefined =>
|
|
53
|
+
isObjectLike(value) ? readName(value.name) : undefined;
|
|
54
|
+
|
|
55
|
+
/** Runs a read that may throw (RN exports use throwing getters); returns undefined on failure. */
|
|
56
|
+
const readSafely = (read: () => unknown): unknown => {
|
|
57
|
+
try {
|
|
58
|
+
return read();
|
|
59
|
+
} catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Functions and React exotic objects ($$typeof) are component candidates. */
|
|
65
|
+
const isComponentLike = (value: unknown): value is object =>
|
|
66
|
+
typeof value === 'function' ||
|
|
67
|
+
(isObjectLike(value) && value.$$typeof !== undefined);
|
|
68
|
+
|
|
69
|
+
const isInSet = (set: WeakSet<object>, value: unknown): boolean =>
|
|
70
|
+
isObjectLike(value) && set.has(value);
|
|
71
|
+
|
|
72
|
+
/** Adds a non-null object-typed count, keeping prototype keys (e.g. "constructor") out of the lookup. */
|
|
73
|
+
const incrementName = (record: Record<string, number>, name: string): void => {
|
|
74
|
+
const current = Object.prototype.hasOwnProperty.call(record, name)
|
|
75
|
+
? record[name]
|
|
76
|
+
: 0;
|
|
77
|
+
record[name] = current + 1;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns the live copy of a fiber: the one under the committed HostRoot,
|
|
82
|
+
* otherwise its alternate. Walking a stale copy would count the old tree.
|
|
83
|
+
*/
|
|
84
|
+
const getCurrentFiber = (fiber: FiberLike): FiberLike => {
|
|
85
|
+
let hostRoot: FiberLike | null | undefined = fiber;
|
|
86
|
+
while (hostRoot && hostRoot.tag !== FIBER_TAG.HostRoot) {
|
|
87
|
+
hostRoot = hostRoot.return;
|
|
88
|
+
}
|
|
89
|
+
if (!hostRoot) return fiber;
|
|
90
|
+
const root = hostRoot.stateNode as { current?: unknown } | null | undefined;
|
|
91
|
+
return root?.current === hostRoot ? fiber : fiber.alternate ?? fiber;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Inner component of a React.memo or forwardRef wrapper, or undefined for other types. */
|
|
95
|
+
const getWrappedType = (type: LooseObject): unknown => {
|
|
96
|
+
if (type.$$typeof === REACT_MEMO_TYPE) return type.type;
|
|
97
|
+
if (type.$$typeof === REACT_FORWARD_REF_TYPE) return type.render;
|
|
98
|
+
return undefined;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Strips memo and forwardRef wrappers (recursively) to reach the underlying
|
|
103
|
+
* component, since a fiber's type is often the wrapper.
|
|
104
|
+
*/
|
|
105
|
+
const unwrapType = (type: unknown): unknown => {
|
|
106
|
+
if (!isObjectLike(type)) return type;
|
|
107
|
+
const inner = getWrappedType(type);
|
|
108
|
+
return inner === undefined ? type : unwrapType(inner);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Display name for a fiber type, following wrappers when the type has no name
|
|
113
|
+
* of its own. Used for lib-unit matching and the report.
|
|
114
|
+
*/
|
|
115
|
+
const getTypeName = (type: unknown): string => {
|
|
116
|
+
if (typeof type === 'string') return type;
|
|
117
|
+
if (!isObjectLike(type)) return ANONYMOUS_NAME;
|
|
118
|
+
return (
|
|
119
|
+
readName(type.displayName) ??
|
|
120
|
+
readName(type.name) ??
|
|
121
|
+
readNameOf(type.render) ??
|
|
122
|
+
readNameOf(type.type) ??
|
|
123
|
+
ANONYMOUS_NAME
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** Adds a component and its unwrapped form to the set. */
|
|
128
|
+
const addRegistryEntry = (set: WeakSet<object>, value: unknown): void => {
|
|
129
|
+
if (!isComponentLike(value)) return;
|
|
130
|
+
set.add(value);
|
|
131
|
+
const inner = unwrapType(value);
|
|
132
|
+
if (isObjectLike(inner)) set.add(inner);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Builds the identity sets used to classify fibers. RN is read by name only,
|
|
137
|
+
* because enumerating react-native exports hits throwing getters.
|
|
138
|
+
*/
|
|
139
|
+
const createRegistries = (
|
|
140
|
+
kitModules: Record<string, unknown>[],
|
|
141
|
+
rnModule: Record<string, unknown>,
|
|
142
|
+
): KitRegistries => {
|
|
143
|
+
const kit = new WeakSet<object>();
|
|
144
|
+
const rn = new WeakSet<object>();
|
|
145
|
+
kitModules.forEach(mod => {
|
|
146
|
+
Object.keys(mod).forEach(name => {
|
|
147
|
+
if (FOUNDATION_REEXPORT_NAMES.includes(name)) return;
|
|
148
|
+
addRegistryEntry(
|
|
149
|
+
kit,
|
|
150
|
+
readSafely(() => mod[name]),
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
RN_PRIMITIVE_NAMES.forEach(name => {
|
|
155
|
+
addRegistryEntry(
|
|
156
|
+
rn,
|
|
157
|
+
readSafely(() => rnModule[name]),
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
return { kit, rn };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** App id from the mini-app context, or a placeholder when it is missing. */
|
|
164
|
+
const getAppId = (context: unknown): string => {
|
|
165
|
+
if (!isObjectLike(context) || context.appId == null) return UNKNOWN_APP_ID;
|
|
166
|
+
return String(context.appId);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** True for host string types that come from react-native's native components. */
|
|
170
|
+
const isRnHostType = (type: unknown): boolean =>
|
|
171
|
+
typeof type === 'string' &&
|
|
172
|
+
HOST_RN_PREFIXES.some(prefix => type.startsWith(prefix));
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Buckets a fiber: K/R/O for kit, RN and other components, 'unit' for lib
|
|
176
|
+
* units (counted once), or 'transparent' for app wrappers that are not counted.
|
|
177
|
+
*/
|
|
178
|
+
const classifyFiber = (
|
|
179
|
+
fiber: FiberLike,
|
|
180
|
+
registries: KitRegistries,
|
|
181
|
+
): Classification => {
|
|
182
|
+
if (fiber.tag === FIBER_TAG.HostComponent) {
|
|
183
|
+
return isRnHostType(fiber.type) ? 'R' : 'O';
|
|
184
|
+
}
|
|
185
|
+
const inner = unwrapType(fiber.type);
|
|
186
|
+
if (isInSet(registries.kit, fiber.type) || isInSet(registries.kit, inner)) {
|
|
187
|
+
return 'K';
|
|
188
|
+
}
|
|
189
|
+
if (isInSet(registries.rn, fiber.type) || isInSet(registries.rn, inner)) {
|
|
190
|
+
return 'R';
|
|
191
|
+
}
|
|
192
|
+
if (LIB_UNIT_NAMES.includes(getTypeName(fiber.type))) return 'unit';
|
|
193
|
+
return isHostDefined(fiber) ? 'O' : 'transparent';
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const createTally = (): Tally => ({
|
|
197
|
+
counts: { K: 0, R: 0, O: 0 },
|
|
198
|
+
names: { K: {}, R: {}, O: {} },
|
|
199
|
+
transparent: {},
|
|
200
|
+
appAuthoredSeen: false,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const countComponent = (
|
|
204
|
+
tally: Tally,
|
|
205
|
+
bucket: AdoptionBucket,
|
|
206
|
+
name: string,
|
|
207
|
+
): void => {
|
|
208
|
+
tally.counts[bucket] += 1;
|
|
209
|
+
incrementName(tally.names[bucket], name);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/** Top transparent wrapper names by count, most frequent first. */
|
|
213
|
+
const topEntries = (
|
|
214
|
+
record: Record<string, number>,
|
|
215
|
+
): { name: string; count: number }[] =>
|
|
216
|
+
Object.keys(record)
|
|
217
|
+
.map(name => ({ name, count: record[name] }))
|
|
218
|
+
.sort((a, b) => b.count - a.count)
|
|
219
|
+
.slice(0, TOP_TRANSPARENT_LIMIT);
|
|
220
|
+
|
|
221
|
+
/** Status 'dev_bundle' means no fiber was authored by a bundle other than the host's. */
|
|
222
|
+
const getStatus = (
|
|
223
|
+
truncated: boolean,
|
|
224
|
+
fibers: number,
|
|
225
|
+
appAuthoredSeen: boolean,
|
|
226
|
+
): SnapshotStatus => {
|
|
227
|
+
if (truncated) return 'truncated';
|
|
228
|
+
if (fibers > 0 && !appAuthoredSeen) return 'dev_bundle';
|
|
229
|
+
return 'ok';
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const buildResult = (
|
|
233
|
+
tally: Tally,
|
|
234
|
+
fibers: number,
|
|
235
|
+
truncated: boolean,
|
|
236
|
+
): WalkResult => ({
|
|
237
|
+
counts: { ...tally.counts },
|
|
238
|
+
kit: tally.names.K,
|
|
239
|
+
rn: tally.names.R,
|
|
240
|
+
other: tally.names.O,
|
|
241
|
+
top_transparent: topEntries(tally.transparent),
|
|
242
|
+
fibers,
|
|
243
|
+
truncated,
|
|
244
|
+
status: getStatus(truncated, fibers, tally.appAuthoredSeen),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Queues the children of a fiber (and their siblings) with the given slot set.
|
|
249
|
+
* Slot sets only pass through lib-unit internals and walk-through fibers.
|
|
250
|
+
*/
|
|
251
|
+
const pushChildren = (
|
|
252
|
+
fiber: FiberLike,
|
|
253
|
+
unitSlots: Set<unknown> | null,
|
|
254
|
+
stack: WalkEntry[],
|
|
255
|
+
): void => {
|
|
256
|
+
for (let child = fiber.child; child; child = child.sibling) {
|
|
257
|
+
stack.push({ fiber: child, unitSlots });
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Counts one app-scope fiber. A lib unit counts as K once and, in structural
|
|
263
|
+
* mode, so does every K/R/O fiber; only the app-passed slot descendants are walked on.
|
|
264
|
+
*/
|
|
265
|
+
const countFiber = (
|
|
266
|
+
fiber: FiberLike,
|
|
267
|
+
registries: KitRegistries,
|
|
268
|
+
tally: Tally,
|
|
269
|
+
stack: WalkEntry[],
|
|
270
|
+
isStructural: boolean,
|
|
271
|
+
): void => {
|
|
272
|
+
const name = getTypeName(fiber.type);
|
|
273
|
+
const kind = classifyFiber(fiber, registries);
|
|
274
|
+
if (kind === 'transparent') {
|
|
275
|
+
incrementName(tally.transparent, name);
|
|
276
|
+
pushChildren(fiber, null, stack);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
countComponent(tally, kind === 'unit' ? 'K' : kind, name);
|
|
280
|
+
if (kind !== 'unit' && !isStructural) {
|
|
281
|
+
pushChildren(fiber, null, stack);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const slots = new Set<unknown>();
|
|
285
|
+
collectSlotProps(fiber.memoizedProps, slots, 0);
|
|
286
|
+
pushChildren(fiber, slots, stack);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Handles one popped fiber: walk-through tags and lib-unit internals are
|
|
291
|
+
* only traversed, host-defined fibers are skipped, and app-scope fibers are counted.
|
|
292
|
+
*/
|
|
293
|
+
const visitEntry = (
|
|
294
|
+
entry: WalkEntry,
|
|
295
|
+
registries: KitRegistries,
|
|
296
|
+
tally: Tally,
|
|
297
|
+
stack: WalkEntry[],
|
|
298
|
+
isStructural: boolean,
|
|
299
|
+
): void => {
|
|
300
|
+
const { fiber, unitSlots } = entry;
|
|
301
|
+
if (!COUNTED_FIBER_TAGS.includes(fiber.tag)) {
|
|
302
|
+
pushChildren(fiber, unitSlots, stack);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (unitSlots && !unitSlots.has(fiber.memoizedProps)) {
|
|
306
|
+
if (isStructural && isListCell(fiber)) collectCellSlots(fiber, unitSlots);
|
|
307
|
+
pushChildren(fiber, unitSlots, stack);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!isAppAuthored(getAuthorFiber(fiber))) {
|
|
311
|
+
pushChildren(fiber, null, stack);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
tally.appAuthoredSeen = true;
|
|
315
|
+
countFiber(fiber, registries, tally, stack, isStructural);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Walks a screen's fiber tree from rootFiber (not its siblings) and tallies
|
|
320
|
+
* app-authored kit, RN and other components. Stops at maxFibers and marks truncated.
|
|
321
|
+
* Structural mode (release) treats every counted fiber as a unit and walks only app-passed slots.
|
|
322
|
+
*/
|
|
323
|
+
const walkScreen = (
|
|
324
|
+
rootFiber: FiberLike,
|
|
325
|
+
registries: KitRegistries,
|
|
326
|
+
stopAt?: (fiber: FiberLike) => boolean,
|
|
327
|
+
maxFibers = MAX_FIBERS,
|
|
328
|
+
isStructural = !__DEV__,
|
|
329
|
+
): WalkResult => {
|
|
330
|
+
const tally = createTally();
|
|
331
|
+
const stack: WalkEntry[] = [{ fiber: rootFiber, unitSlots: null }];
|
|
332
|
+
let fibers = 0;
|
|
333
|
+
let truncated = false;
|
|
334
|
+
for (let entry = stack.pop(); entry; entry = stack.pop()) {
|
|
335
|
+
if (fibers === maxFibers) {
|
|
336
|
+
truncated = true;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
fibers += 1;
|
|
340
|
+
if (entry.fiber !== rootFiber && stopAt?.(entry.fiber)) continue;
|
|
341
|
+
visitEntry(entry, registries, tally, stack, isStructural);
|
|
342
|
+
}
|
|
343
|
+
return buildResult(tally, fibers, truncated);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/** True only when the host marks this app as whitelisted for adoption tracking. */
|
|
347
|
+
const isAdoptionTrackingEnabled = (context: unknown): boolean => {
|
|
348
|
+
if (!isObjectLike(context)) return false;
|
|
349
|
+
const config = context.designSystemConfig;
|
|
350
|
+
return isObjectLike(config) && config.isWhiteList === true;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/** Counts as the analytics params of a screen-load event, or undefined unless the walk is ok. */
|
|
354
|
+
const getAdoptionTrackingParams = (
|
|
355
|
+
result: WalkResult | undefined,
|
|
356
|
+
): AdoptionTrackingParams | undefined => {
|
|
357
|
+
if (result?.status !== 'ok') return undefined;
|
|
358
|
+
const { K, R, O } = result.counts;
|
|
359
|
+
return { num_design_system: K, num_rn: R, num_other: O };
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export {
|
|
363
|
+
classifyFiber,
|
|
364
|
+
collectSlotProps,
|
|
365
|
+
createRegistries,
|
|
366
|
+
getAdoptionTrackingParams,
|
|
367
|
+
getAppId,
|
|
368
|
+
getAuthorFiber,
|
|
369
|
+
getCurrentFiber,
|
|
370
|
+
getTypeName,
|
|
371
|
+
isAdoptionTrackingEnabled,
|
|
372
|
+
isAppAuthored,
|
|
373
|
+
isHostDefined,
|
|
374
|
+
unwrapType,
|
|
375
|
+
walkScreen,
|
|
376
|
+
};
|
package/Application/Localize.ts
CHANGED
|
@@ -2,10 +2,6 @@ import { NativeModules } from 'react-native';
|
|
|
2
2
|
import { LocalizationObject } from './types';
|
|
3
3
|
import defaultLanguage from '../Assets/language.json';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* String table for kit and feature copy. The language is read once from the
|
|
7
|
-
* native `RNResource` module at construction; Vietnamese is the default.
|
|
8
|
-
*/
|
|
9
5
|
class Localize {
|
|
10
6
|
private assets: LocalizationObject;
|
|
11
7
|
private readonly currentLanguage: 'en' | 'vi';
|
|
@@ -29,17 +25,14 @@ class Localize {
|
|
|
29
25
|
return this.assets;
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
/** Text for `key` in the current language, or the key itself if missing. */
|
|
33
28
|
translate(key: string) {
|
|
34
29
|
return this.assets[this.currentLanguage]?.[key] || key;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
/** Picks the current-language text of a `{ vi, en }` pair, else its JSON. */
|
|
38
32
|
translateData(data: { vi: string; en: string }) {
|
|
39
33
|
return data[this.currentLanguage] ?? JSON.stringify(data);
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
/** Merges extra translations; keys that already exist keep their text. */
|
|
43
36
|
addTranslations(translations: LocalizationObject) {
|
|
44
37
|
this.assets = {
|
|
45
38
|
vi: { ...translations?.vi, ...this.assets.vi },
|
|
@@ -24,13 +24,10 @@ import Navigation from './Navigation';
|
|
|
24
24
|
import { ModalParams } from './types';
|
|
25
25
|
import BottomSheet from './BottomSheet';
|
|
26
26
|
import { isIPhoneDuo } from './utils';
|
|
27
|
+
import { AdoptionProbe } from './KitAdoption';
|
|
27
28
|
import { useModalLayout } from './modalLayout';
|
|
28
29
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
-
* Screen of the `Modal` route: a bottom sheet when pushed by
|
|
32
|
-
* `showBottomSheet`, otherwise a centered popup.
|
|
33
|
-
*/
|
|
34
31
|
const ModalScreen: React.FC<any> = props => {
|
|
35
32
|
if (props.route?.params?.isBottomSheet) {
|
|
36
33
|
return <BottomSheet {...props} />;
|
|
@@ -38,10 +35,6 @@ const ModalScreen: React.FC<any> = props => {
|
|
|
38
35
|
return <Modal {...props} />;
|
|
39
36
|
};
|
|
40
37
|
|
|
41
|
-
/**
|
|
42
|
-
* Centered popup that fades and scales in over a scrim. On foldables it is
|
|
43
|
-
* centered in the modal placement area, at most a popup wide.
|
|
44
|
-
*/
|
|
45
38
|
const Modal: React.FC<ModalParams> = props => {
|
|
46
39
|
const { navigator } = useContext(ApplicationContext);
|
|
47
40
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -58,7 +51,9 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
58
51
|
const isDuo = isIPhoneDuo();
|
|
59
52
|
const { isFoldable, placement, popup, scrimOpacity } = useModalLayout();
|
|
60
53
|
|
|
61
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Fire auto_popup_displayed tracking event on mount
|
|
56
|
+
*/
|
|
62
57
|
useEffect(() => {
|
|
63
58
|
navigator?.maxApi?.getDataObserver?.('current_screen', (curScreen: any) => {
|
|
64
59
|
context.autoTracking({
|
|
@@ -188,7 +183,12 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
188
183
|
modalStyle,
|
|
189
184
|
]}
|
|
190
185
|
>
|
|
191
|
-
<
|
|
186
|
+
<AdoptionProbe
|
|
187
|
+
screenName={Component?.name || Component?.type?.name || 'Invalid'}
|
|
188
|
+
navigation={props.navigation}
|
|
189
|
+
>
|
|
190
|
+
<Component {...params} onRequestClose={onDismiss} />
|
|
191
|
+
</AdoptionProbe>
|
|
192
192
|
</Animated.View>
|
|
193
193
|
</KeyboardAvoidingView>
|
|
194
194
|
</Container>
|
|
@@ -11,10 +11,6 @@ export const WHITE_LIST = [
|
|
|
11
11
|
'vn.momo.ekyc',
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* Screen-level navigation handle given to features; `setOptions` enforces the
|
|
16
|
-
* design system's header rules before applying React Navigation options.
|
|
17
|
-
*/
|
|
18
14
|
class Navigation {
|
|
19
15
|
instance: NavigationProp<any>;
|
|
20
16
|
context: any;
|
|
@@ -25,33 +21,39 @@ class Navigation {
|
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* navigation options filter by design system rule
|
|
25
|
+
* @param params
|
|
30
26
|
*/
|
|
31
27
|
private filterParams = (params: NavigationOptions) => {
|
|
32
|
-
const isWhiteListed = WHITE_LIST.includes(this.context?.appId);
|
|
33
|
-
const rightType = typeof params.headerRight;
|
|
34
|
-
const titleType = typeof params.headerTitle;
|
|
35
|
-
const isTitleRenderer = titleType !== 'string' && titleType !== 'object';
|
|
36
|
-
|
|
37
28
|
if (params.headerLeft) {
|
|
38
29
|
console.warn('headerLeft not allow override render by design system!');
|
|
39
30
|
delete params.headerLeft;
|
|
40
31
|
}
|
|
41
32
|
|
|
42
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
params.headerRight &&
|
|
35
|
+
typeof params.headerRight !== 'object' &&
|
|
36
|
+
!WHITE_LIST.includes(this.context?.appId)
|
|
37
|
+
) {
|
|
43
38
|
console.warn('headerRight not allow type func render by design system!');
|
|
44
39
|
delete params.headerRight;
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
params.headerTitle &&
|
|
44
|
+
!['string', 'object'].includes(typeof params.headerTitle) &&
|
|
45
|
+
!WHITE_LIST.includes(this.context?.appId)
|
|
46
|
+
) {
|
|
48
47
|
console.warn('headerTitle not allow type func render by design system!');
|
|
49
48
|
delete params.headerTitle;
|
|
50
49
|
}
|
|
51
50
|
return params;
|
|
52
51
|
};
|
|
53
52
|
|
|
54
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* navigation setOptions support props NavigationOptions base on react-navigation options
|
|
55
|
+
* @param params
|
|
56
|
+
*/
|
|
55
57
|
setOptions = (params: NavigationOptions) => {
|
|
56
58
|
const options = getOptions(this.filterParams(params));
|
|
57
59
|
this.instance.setOptions(options as StackNavigationOptions);
|
|
@@ -30,13 +30,10 @@ import { HeaderBackground } from './Components/HeaderBackground';
|
|
|
30
30
|
import { useScreenLayout } from '../Layout';
|
|
31
31
|
import { getHeaderRowOptions } from '../Layout/utils';
|
|
32
32
|
import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
|
|
33
|
+
import { AdoptionBadge } from './KitAdoption';
|
|
33
34
|
|
|
34
35
|
const Stack = createStackNavigator();
|
|
35
36
|
|
|
36
|
-
/**
|
|
37
|
-
* Root of a mini app: safe area, reserved regions and the merged mini app
|
|
38
|
-
* context around the kit's navigator.
|
|
39
|
-
*/
|
|
40
37
|
const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
41
38
|
screen,
|
|
42
39
|
theme = defaultTheme,
|
|
@@ -90,16 +87,13 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
90
87
|
initialParams={initialParams}
|
|
91
88
|
localize={localize}
|
|
92
89
|
/>
|
|
90
|
+
<AdoptionBadge />
|
|
93
91
|
</MiniAppContext.Provider>
|
|
94
92
|
</ReservedRegionsProvider>
|
|
95
93
|
</SafeAreaProvider>
|
|
96
94
|
);
|
|
97
95
|
};
|
|
98
96
|
|
|
99
|
-
/**
|
|
100
|
-
* Stack navigator with its `Stack`, `Dialog` and `Modal` routes, under an
|
|
101
|
-
* `ApplicationContext` carrying the navigator, themed fonts and `translate`.
|
|
102
|
-
*/
|
|
103
97
|
const Navigation: React.FC<any> = ({
|
|
104
98
|
screen,
|
|
105
99
|
theme = defaultTheme,
|
|
@@ -117,10 +111,9 @@ const Navigation: React.FC<any> = ({
|
|
|
117
111
|
const syncCanGoBack = useNativeCanGoBack();
|
|
118
112
|
const isWhiteList = isWhiteListDesignSystem(context);
|
|
119
113
|
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
114
|
+
const navigationFontFamily = isWhiteList
|
|
115
|
+
? 'MomoTrustSans-Regular'
|
|
116
|
+
: 'SFProText';
|
|
124
117
|
let headerBackground = context?.designSystemConfig?.config?.headerBar;
|
|
125
118
|
let headerGradient = theme.colors?.gradient;
|
|
126
119
|
|
|
@@ -131,9 +124,15 @@ const Navigation: React.FC<any> = ({
|
|
|
131
124
|
headerGradient = context?.designSystemConfig?.config?.headerGradient;
|
|
132
125
|
}
|
|
133
126
|
|
|
127
|
+
/**
|
|
128
|
+
* inject data for navigator
|
|
129
|
+
*/
|
|
134
130
|
navigator.current.maxApi = maxApi;
|
|
135
131
|
|
|
136
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* translate context method
|
|
134
|
+
* @param data
|
|
135
|
+
*/
|
|
137
136
|
const translate = (data?: string | { vi: string; en: string }) => {
|
|
138
137
|
if (data && typeof data !== 'string') {
|
|
139
138
|
return localize?.translateData(data);
|
|
@@ -231,12 +230,12 @@ const Navigation: React.FC<any> = ({
|
|
|
231
230
|
backgroundColor: theme.colors.background.default,
|
|
232
231
|
},
|
|
233
232
|
headerLeft: (props: any) => <HeaderLeft {...props} floating />,
|
|
234
|
-
headerBackground: (props: any) =>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
headerRight: (props: any) =>
|
|
238
|
-
|
|
239
|
-
|
|
233
|
+
headerBackground: (props: any) => (
|
|
234
|
+
<HeaderBackground {...props} />
|
|
235
|
+
),
|
|
236
|
+
headerRight: (props: any) => (
|
|
237
|
+
<HeaderRight {...props} floating />
|
|
238
|
+
),
|
|
240
239
|
headerTintColor: Colors.black_17,
|
|
241
240
|
...TransitionPresets.ModalTransition,
|
|
242
241
|
}}
|