@onekeyfe/react-native-native-list 3.0.114 → 3.0.116

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/src/validation.ts CHANGED
@@ -3,6 +3,9 @@ import type {
3
3
  IdentityRow,
4
4
  ImageSource,
5
5
  LeadingVisual,
6
+ MarketRow,
7
+ MarketRowStyle,
8
+ MarketTextStyle,
6
9
  NativeListSnapshot,
7
10
  RowModel,
8
11
  RowPatch,
@@ -19,6 +22,7 @@ const MAX_IMAGE_HEADERS = 32;
19
22
  const MAX_HEADER_LENGTH = 4096;
20
23
  const MAX_SPACER_HEIGHT = 512;
21
24
  const MAX_SECTION_INDEX_TITLE_LENGTH = 8;
25
+ const MAX_MARKET_BADGES = 3;
22
26
 
23
27
  function fail(path: string, message: string): never {
24
28
  throw new Error(`NativeList ${path}: ${message}`);
@@ -109,6 +113,216 @@ function assertTextTone(tone: string | undefined, path: string): void {
109
113
  }
110
114
  }
111
115
 
116
+ function assertBoundedStyleNumber(
117
+ value: number | undefined,
118
+ path: string,
119
+ min: number,
120
+ max: number
121
+ ): void {
122
+ if (
123
+ value !== undefined &&
124
+ (!Number.isFinite(value) || value < min || value > max)
125
+ ) {
126
+ fail(path, `must be within ${min}...${max}`);
127
+ }
128
+ }
129
+
130
+ function assertMarketTextStyle(
131
+ style: MarketTextStyle | undefined,
132
+ path: string
133
+ ): void {
134
+ if (!style) return;
135
+ assertBoundedStyleNumber(style.fontSize, `${path}.fontSize`, 8, 48);
136
+ assertBoundedStyleNumber(style.lineHeight, `${path}.lineHeight`, 8, 64);
137
+ if (
138
+ style.fontWeight !== undefined &&
139
+ !['regular', 'medium', 'semibold', 'bold'].includes(style.fontWeight)
140
+ ) {
141
+ fail(`${path}.fontWeight`, 'must be regular, medium, semibold, or bold');
142
+ }
143
+ if (
144
+ style.alignment !== undefined &&
145
+ !['start', 'center', 'end'].includes(style.alignment)
146
+ ) {
147
+ fail(`${path}.alignment`, 'must be start, center, or end');
148
+ }
149
+ if (style.lines !== undefined && ![1, 2].includes(style.lines)) {
150
+ fail(`${path}.lines`, 'must be 1 or 2');
151
+ }
152
+ }
153
+
154
+ function assertMarketStyle(
155
+ style: MarketRowStyle | undefined,
156
+ path: string
157
+ ): void {
158
+ if (!style) return;
159
+ for (const field of [
160
+ 'horizontalPadding',
161
+ 'verticalPadding',
162
+ 'leadingGap',
163
+ 'titleBadgeGap',
164
+ 'trailingGap',
165
+ 'contentTrailingGap',
166
+ 'subtitleTrailingPadding',
167
+ ] as const) {
168
+ assertBoundedStyleNumber(style[field], `${path}.${field}`, 0, 64);
169
+ }
170
+ // OneKey patch: validate the opt-in Market badge layout.
171
+ if (
172
+ style.titleBadgeLayout !== undefined &&
173
+ style.titleBadgeLayout !== 'inline'
174
+ ) {
175
+ fail(`${path}.titleBadgeLayout`, 'must be inline when provided');
176
+ }
177
+ assertBoundedStyleNumber(style.lineGap, `${path}.lineGap`, 0, 16);
178
+ assertBoundedStyleNumber(style.changeWidth, `${path}.changeWidth`, 1, 160);
179
+ assertBoundedStyleNumber(style.changeHeight, `${path}.changeHeight`, 1, 160);
180
+ assertBoundedStyleNumber(
181
+ style.changeCornerRadius,
182
+ `${path}.changeCornerRadius`,
183
+ 0,
184
+ 80
185
+ );
186
+ if (style.image) {
187
+ assertBoundedStyleNumber(style.image.width, `${path}.image.width`, 1, 160);
188
+ assertBoundedStyleNumber(
189
+ style.image.height,
190
+ `${path}.image.height`,
191
+ 1,
192
+ 160
193
+ );
194
+ assertBoundedStyleNumber(
195
+ style.image.cornerRadius,
196
+ `${path}.image.cornerRadius`,
197
+ 0,
198
+ 80
199
+ );
200
+ assertVisualShape(style.image.shape, `${path}.image.shape`);
201
+ if (
202
+ style.image.contentFit !== undefined &&
203
+ !['cover', 'contain', 'fill', 'center'].includes(style.image.contentFit)
204
+ ) {
205
+ fail(
206
+ `${path}.image.contentFit`,
207
+ 'must be cover, contain, fill, or center'
208
+ );
209
+ }
210
+ }
211
+ assertMarketTextStyle(style.title, `${path}.title`);
212
+ assertMarketTextStyle(style.subtitle, `${path}.subtitle`);
213
+ assertMarketTextStyle(style.price, `${path}.price`);
214
+ assertMarketTextStyle(style.change, `${path}.change`);
215
+ }
216
+
217
+ function assertMarketRow(row: MarketRow, path: string): void {
218
+ if (!['token', 'stock', 'perp'].includes(row.variant)) {
219
+ fail(`${path}.variant`, 'must be token, stock, or perp');
220
+ }
221
+ assertText(row.title, `${path}.title`);
222
+ assertText(row.subtitle, `${path}.subtitle`);
223
+ // OneKey patch: validate the independently laid out Market name.
224
+ if (row.subtitlePrefix) {
225
+ assertText(row.subtitlePrefix.text, `${path}.subtitlePrefix.text`);
226
+ assertBoundedStyleNumber(
227
+ row.subtitlePrefix.gap,
228
+ `${path}.subtitlePrefix.gap`,
229
+ 0,
230
+ 64
231
+ );
232
+ assertBoundedStyleNumber(
233
+ row.subtitlePrefix.maxWidth,
234
+ `${path}.subtitlePrefix.maxWidth`,
235
+ 1,
236
+ 320
237
+ );
238
+ assertMarketTextStyle(
239
+ row.subtitlePrefix.style,
240
+ `${path}.subtitlePrefix.style`
241
+ );
242
+ }
243
+ assertText(row.price, `${path}.price`);
244
+ assertText(row.change.text, `${path}.change.text`);
245
+ assertLeadingVisual(row.leading, `${path}.leading`);
246
+ const assertSegments = (
247
+ segments: MarketRow['priceSegments'],
248
+ segmentPath: string
249
+ ) => {
250
+ segments?.forEach((segment, index) => {
251
+ assertText(segment.text, `${segmentPath}[${index}].text`);
252
+ if (segment.style !== undefined && segment.style !== 'subscript') {
253
+ fail(
254
+ `${segmentPath}[${index}].style`,
255
+ 'must be subscript when provided'
256
+ );
257
+ }
258
+ });
259
+ };
260
+ assertSegments(row.subtitleSegments, `${path}.subtitleSegments`);
261
+ assertSegments(row.priceSegments, `${path}.priceSegments`);
262
+ assertSegments(row.change.textSegments, `${path}.change.textSegments`);
263
+ if (!['positive', 'negative', 'neutral'].includes(row.change.tone)) {
264
+ fail(`${path}.change.tone`, 'must be positive, negative, or neutral');
265
+ }
266
+ if ((row.badges?.length ?? 0) > MAX_MARKET_BADGES) {
267
+ fail(`${path}.badges`, `supports at most ${MAX_MARKET_BADGES} badges`);
268
+ }
269
+ const badgeKeys = new Set<string>();
270
+ row.badges?.forEach((badge, index) => {
271
+ const badgePath = `${path}.badges[${index}]`;
272
+ assertKey(badge.key, `${badgePath}.key`);
273
+ if (badgeKeys.has(badge.key)) {
274
+ fail(`${path}.badges`, `duplicate badge key "${badge.key}"`);
275
+ }
276
+ badgeKeys.add(badge.key);
277
+ assertText(badge.text, `${badgePath}.text`);
278
+ // OneKey patch: share typography bounds with Market text styles.
279
+ assertMarketTextStyle(badge.style, `${badgePath}.style`);
280
+ assertBoundedStyleNumber(
281
+ badge.style?.height,
282
+ `${badgePath}.style.height`,
283
+ 1,
284
+ 64
285
+ );
286
+ assertBoundedStyleNumber(
287
+ badge.style?.horizontalPadding,
288
+ `${badgePath}.style.horizontalPadding`,
289
+ 0,
290
+ 32
291
+ );
292
+ if (badge.iconName !== undefined && badge.iconName !== 'verified') {
293
+ fail(`${badgePath}.iconName`, 'must be verified when provided');
294
+ }
295
+ if (badge.iconName !== undefined && badge.icon !== undefined) {
296
+ fail(`${badgePath}.icon`, 'cannot be combined with iconName');
297
+ }
298
+ assertImage(badge.icon, `${badgePath}.icon`);
299
+ if (
300
+ badge.tone !== undefined &&
301
+ !['neutral', 'info', 'success', 'warning', 'danger'].includes(badge.tone)
302
+ ) {
303
+ fail(
304
+ `${badgePath}.tone`,
305
+ 'must be neutral, info, success, warning, or danger'
306
+ );
307
+ }
308
+ if (!badge.text && !badge.icon && !badge.iconName) {
309
+ fail(badgePath, 'requires text, icon, or iconName');
310
+ }
311
+ if (badge.actionKey !== undefined) {
312
+ assertKey(badge.actionKey, `${badgePath}.actionKey`);
313
+ }
314
+ });
315
+ for (const [key, value] of [
316
+ ['pressActionKey', row.pressActionKey],
317
+ ['pressInActionKey', row.pressInActionKey],
318
+ ['longPressActionKey', row.longPressActionKey],
319
+ ['diagnostics.imageBindActionKey', row.diagnostics?.imageBindActionKey],
320
+ ] as const) {
321
+ if (value !== undefined) assertKey(value, `${path}.${key}`);
322
+ }
323
+ assertMarketStyle(row.style, `${path}.style`);
324
+ }
325
+
112
326
  function assertSectionHeaderVariant(
113
327
  variant: string | undefined,
114
328
  path: string
@@ -295,6 +509,8 @@ function assertVisual(row: RowModel, path: string): void {
295
509
  ? [row.leading]
296
510
  : row.type === 'dataRow'
297
511
  ? [row.leading]
512
+ : row.type === 'market'
513
+ ? [row.leading]
298
514
  : row.type === 'metricCard'
299
515
  ? [row.visual]
300
516
  : [];
@@ -488,6 +704,9 @@ function assertRow(
488
704
  fail(`${path}.badges`, `supports at most ${MAX_BADGES} badges`);
489
705
  }
490
706
  break;
707
+ case 'market':
708
+ assertMarketRow(row, path);
709
+ break;
491
710
  case 'mediaTile':
492
711
  assertImage(row.image, `${path}.image`);
493
712
  if (
@@ -583,6 +802,10 @@ function assertRow(
583
802
  assertTrailingAccessories(row.trailing, `${path}.trailing`);
584
803
  break;
585
804
  case 'system':
805
+ const presentation = 'presentation' in row ? row.presentation : undefined;
806
+ if (presentation !== undefined && presentation !== 'market') {
807
+ fail(`${path}.presentation`, 'must be market when provided');
808
+ }
586
809
  if (
587
810
  // OneKey patch: deprecated-wallet warnings retain the original scrolling semantics.
588
811
  !['loading', 'retry', 'noMatch', 'end', 'spacer', 'warning'].includes(
@@ -595,6 +818,17 @@ function assertRow(
595
818
  );
596
819
  }
597
820
  if (row.variant === 'warning') assertText(row.title, `${path}.title`);
821
+ if (
822
+ row.variant === 'loading' &&
823
+ row.loadingStyle !== undefined &&
824
+ row.loadingStyle !== 'skeleton' &&
825
+ row.loadingStyle !== 'spinner'
826
+ ) {
827
+ fail(
828
+ `${path}.loadingStyle`,
829
+ 'must be skeleton or spinner when provided'
830
+ );
831
+ }
598
832
  if (row.variant !== 'spacer') {
599
833
  assertText(row.message, `${path}.message`);
600
834
  }
@@ -606,6 +840,7 @@ function assertRow(
606
840
  }
607
841
  if (row.variant === 'retry') {
608
842
  assertKey(row.actionKey, `${path}.actionKey`);
843
+ assertText(row.actionText, `${path}.actionText`);
609
844
  }
610
845
  break;
611
846
  }
@@ -879,6 +1114,21 @@ function assertPatchChanges(patch: RowPatch, index: number): void {
879
1114
  fail(`${path}.badges`, `supports at most ${MAX_BADGES} badges`);
880
1115
  }
881
1116
  break;
1117
+ case 'market':
1118
+ assertMarketRow(
1119
+ {
1120
+ type: 'market',
1121
+ key: patch.key,
1122
+ variant: 'token',
1123
+ leading: { kind: 'icon', name: 'placeholder' },
1124
+ title: '',
1125
+ price: '',
1126
+ change: { text: '', tone: 'neutral' },
1127
+ ...patch.changes,
1128
+ } as MarketRow,
1129
+ path
1130
+ );
1131
+ break;
882
1132
  case 'mediaTile':
883
1133
  assertImage(patch.changes.image, `${path}.image`);
884
1134
  if (