@onekeyfe/react-native-native-list 3.0.112 → 3.0.113

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.
@@ -1,4 +1,4 @@
1
- import type { ActionAnchorInvalidatedEvent, NativeListSnapshot, NativeListActionAnchor, NativeListActionSource, ReorderEvent, RowActionEvent, RowModel, RowPatch, SelectionDeltaEvent, VisibleRangeChangedEvent } from '../models';
1
+ import type { ActionAnchorInvalidatedEvent, MarketRow, NativeListSnapshot, NativeListActionAnchor, NativeListActionSource, ReorderEvent, RowActionEvent, RowModel, RowPatch, SelectionDeltaEvent, VisibleRangeChangedEvent } from '../models';
2
2
  import type { ActionAnchorState, ScrollToIndexFailedInfo, ScrollToLocationParams } from '../NativeList.types';
3
3
  import { type NormalizedPositionScroll } from '../scrolling';
4
4
  export declare const WEB_REORDER_ANIMATION: {
@@ -7,6 +7,7 @@ export declare const WEB_REORDER_ANIMATION: {
7
7
  readonly dropDurationMs: 80;
8
8
  readonly dropTimingFunction: "ease";
9
9
  };
10
+ export declare const WEB_MARKET_VERIFIED_PATH = "M9.483 11.458v3.5h-1v-3.5z M10.467 2.698a2.03 2.03 0 0 1 3.065 0l1.358 1.564a.03.03 0 0 0 .028.01l2.046-.325a2.03 2.03 0 0 1 2.347 1.971l.037 2.07q0 .016.014.026l1.776 1.066a2.03 2.03 0 0 1 .532 3.019l-1.304 1.609a.03.03 0 0 0-.005.03l.675 1.956a2.03 2.03 0 0 1-1.533 2.656l-2.033.394a.03.03 0 0 0-.023.019l-.741 1.933a2.03 2.03 0 0 1-2.88 1.05l-1.811-1.006a.03.03 0 0 0-.03 0l-1.811 1.005a2.03 2.03 0 0 1-2.88-1.049l-.742-1.933a.03.03 0 0 0-.023-.019l-2.033-.394a2.03 2.03 0 0 1-1.532-2.656l.675-1.957a.03.03 0 0 0-.005-.029l-1.304-1.61a2.03 2.03 0 0 1 .532-3.018l1.776-1.066a.03.03 0 0 0 .014-.026l.035-2.07a2.03 2.03 0 0 1 2.349-1.97l2.045.324a.03.03 0 0 0 .028-.01zm1.516 3.76a.5.5 0 0 0-.447.276l-1.861 3.724H8.483a1 1 0 0 0-1 1v3.5a1 1 0 0 0 1 1h6.692a2 2 0 0 0 1.981-1.73l.341-2.5a2 2 0 0 0-1.982-2.27h-1.939l.197-1.269a1.5 1.5 0 0 0-1.481-1.731z";
10
11
  export type WebLayoutItem = Readonly<{
11
12
  index: number;
12
13
  key: string;
@@ -21,6 +22,21 @@ export type WebListLayout = Readonly<{
21
22
  contentHeight: number;
22
23
  horizontal: boolean;
23
24
  }>;
25
+ export type WebMarketLayoutStyle = Readonly<{
26
+ horizontalPadding: number;
27
+ verticalPadding: number;
28
+ leadingGap: number;
29
+ titleBadgeGap: number;
30
+ trailingGap: number;
31
+ imageWidth: number;
32
+ imageHeight: number;
33
+ imageCornerRadius: number;
34
+ changeWidth: number;
35
+ changeHeight: number;
36
+ changeCornerRadius: number;
37
+ }>;
38
+ /** Resolves every reusable Market geometry field, including legacy defaults. */
39
+ export declare function resolveWebMarketLayoutStyle(row: MarketRow): WebMarketLayoutStyle;
24
40
  export type NativeListWebCallbacks = Readonly<{
25
41
  onRowAction?: (event: RowActionEvent) => void;
26
42
  onActionAnchorInvalidated?: (event: ActionAnchorInvalidatedEvent) => void;
@@ -52,6 +68,9 @@ export declare function webWalletGroupReorderBadge(row: RowModel): string | unde
52
68
  export declare function visibleWebLayoutItems(layout: WebListLayout, offset: number, viewportLength: number, overscan?: number): readonly WebLayoutItem[];
53
69
  export declare function webLayoutItemsForMount(layout: WebListLayout, offset: number, viewportLength: number, virtualizationEnabled: boolean, overscan?: number): readonly WebLayoutItem[];
54
70
  export declare function webRowRenderSignature(row: RowModel): string;
71
+ export declare function isWebMarketQuotePatch(patch: RowPatch): boolean;
72
+ /** Number of Market image-binding passes caused by one Web patch transaction. */
73
+ export declare function webMarketImageBindDeltaForPatches(patches: readonly RowPatch[]): 0 | 1;
55
74
  export declare const WEB_LIST_CSS: string;
56
75
  export declare class NativeListWebEngine {
57
76
  private readonly document;
@@ -101,6 +120,8 @@ export declare class NativeListWebEngine {
101
120
  private readonly actionAnchorInstanceId;
102
121
  private actionAnchorCounter;
103
122
  private bindingEpochCounter;
123
+ private marketPointer;
124
+ private marketLongPressFired;
104
125
  private lastViewportWidth;
105
126
  private lastViewportHeight;
106
127
  private destroyed;
@@ -152,6 +173,10 @@ export declare class NativeListWebEngine {
152
173
  private invalidateActionAnchor;
153
174
  private emitActionAnchorInvalidated;
154
175
  private handleRowPress;
176
+ private cancelMarketPointer;
177
+ private handleMarketPointerDown;
178
+ private handleMarketPointerMove;
179
+ private handleMarketPointerEnd;
155
180
  private handleTitlePointerOver;
156
181
  private handleTitlePointerOut;
157
182
  private handleClick;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-native-list",
3
- "version": "3.0.112",
3
+ "version": "3.0.113",
4
4
  "description": "Template-driven native RecyclerView and UICollectionView for React Native",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",
@@ -82,7 +82,7 @@
82
82
  "typescript": "^5.9.2"
83
83
  },
84
84
  "peerDependencies": {
85
- "@onekeyfe/react-native-image": "3.0.112",
85
+ "@onekeyfe/react-native-image": "3.0.113",
86
86
  "react": "*",
87
87
  "react-native": "*",
88
88
  "react-native-nitro-modules": "0.37.0"
package/src/models.ts CHANGED
@@ -33,6 +33,62 @@ export type BadgeModel = Readonly<{
33
33
  tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
34
34
  }>;
35
35
 
36
+ export type MarketTextStyle = Readonly<{
37
+ fontSize?: number;
38
+ fontWeight?: 'regular' | 'medium' | 'semibold' | 'bold';
39
+ color?: string;
40
+ lineHeight?: number;
41
+ lines?: 1 | 2;
42
+ alignment?: 'start' | 'center' | 'end';
43
+ }>;
44
+
45
+ export type MarketImageStyle = Readonly<{
46
+ width?: number;
47
+ height?: number;
48
+ shape?: 'circle' | 'rounded' | 'square';
49
+ cornerRadius?: number;
50
+ contentFit?: ImageContentFit;
51
+ }>;
52
+
53
+ export type MarketRowStyle = Readonly<{
54
+ horizontalPadding?: number;
55
+ verticalPadding?: number;
56
+ leadingGap?: number;
57
+ /** Space between title and subtitle; 0 by default, bounded to 0..16. */
58
+ lineGap?: number;
59
+ titleBadgeGap?: number;
60
+ trailingGap?: number;
61
+ image?: MarketImageStyle;
62
+ title?: MarketTextStyle;
63
+ subtitle?: MarketTextStyle;
64
+ price?: MarketTextStyle;
65
+ change?: MarketTextStyle;
66
+ changeWidth?: number;
67
+ changeHeight?: number;
68
+ changeCornerRadius?: number;
69
+ }>;
70
+
71
+ export type MarketBadgeModel = Readonly<{
72
+ key: string;
73
+ text?: string;
74
+ /** Finite native glyph set; currently only the community verified mark. */
75
+ iconName?: 'verified';
76
+ icon?: ImageSource;
77
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
78
+ textColor?: string;
79
+ backgroundColor?: string;
80
+ actionKey?: string;
81
+ accessibilityLabel?: string;
82
+ }>;
83
+
84
+ export type MarketChangeModel = Readonly<{
85
+ text: string;
86
+ textSegments?: readonly ValueTextSegment[];
87
+ tone: 'positive' | 'negative' | 'neutral';
88
+ textColor?: string;
89
+ backgroundColor?: string;
90
+ }>;
91
+
36
92
  // OneKey patch: keep selector decoration and text data serializable.
37
93
  export type SelectorTextSegment = Readonly<{
38
94
  text: string;
@@ -272,6 +328,26 @@ export type DataRow = RowBase &
272
328
  favoriteActive?: boolean;
273
329
  }>;
274
330
 
331
+ /** Native Market quote row. All values are already localized/formatted strings. */
332
+ export type MarketRow = RowBase &
333
+ Readonly<{
334
+ type: 'market';
335
+ variant: 'token' | 'stock' | 'perp';
336
+ leading: LeadingVisual;
337
+ title: string;
338
+ subtitle?: string;
339
+ subtitleSegments?: readonly ValueTextSegment[];
340
+ price: string;
341
+ priceSegments?: readonly ValueTextSegment[];
342
+ change: MarketChangeModel;
343
+ badges?: readonly MarketBadgeModel[];
344
+ pressActionKey?: string;
345
+ pressInActionKey?: string;
346
+ longPressActionKey?: string;
347
+ diagnostics?: Readonly<{ imageBindActionKey?: string }>;
348
+ style?: MarketRowStyle;
349
+ }>;
350
+
275
351
  export type MediaTileRow = RowBase &
276
352
  Readonly<{
277
353
  type: 'mediaTile';
@@ -345,10 +421,17 @@ export type ActionRow = RowBase &
345
421
 
346
422
  export type SystemRow = RowBase &
347
423
  (
348
- | Readonly<{ type: 'system'; variant: 'loading'; message?: string }>
424
+ | Readonly<{
425
+ type: 'system';
426
+ variant: 'loading';
427
+ presentation?: 'market';
428
+ loadingStyle?: 'skeleton' | 'spinner';
429
+ message?: string;
430
+ }>
349
431
  | Readonly<{
350
432
  type: 'system';
351
433
  variant: 'retry';
434
+ presentation?: 'market';
352
435
  message: string;
353
436
  actionKey: string;
354
437
  }>
@@ -359,8 +442,18 @@ export type SystemRow = RowBase &
359
442
  message: string;
360
443
  borderColor?: string;
361
444
  }>
362
- | Readonly<{ type: 'system'; variant: 'noMatch'; message: string }>
363
- | Readonly<{ type: 'system'; variant: 'end'; message?: string }>
445
+ | Readonly<{
446
+ type: 'system';
447
+ variant: 'noMatch';
448
+ presentation?: 'market';
449
+ message: string;
450
+ }>
451
+ | Readonly<{
452
+ type: 'system';
453
+ variant: 'end';
454
+ presentation?: 'market';
455
+ message?: string;
456
+ }>
364
457
  | Readonly<{ type: 'system'; variant: 'spacer'; height: number }>
365
458
  );
366
459
 
@@ -371,6 +464,7 @@ export type RowModel =
371
464
  | ActivityRow
372
465
  | MessageRow
373
466
  | DataRow
467
+ | MarketRow
374
468
  | MediaTileRow
375
469
  | MetricCardRow
376
470
  | SectionHeaderRow
@@ -545,6 +639,29 @@ export type RowPatch =
545
639
  >
546
640
  >;
547
641
  }>
642
+ | Readonly<{
643
+ type: 'market';
644
+ key: string;
645
+ changes: Partial<
646
+ Pick<
647
+ MarketRow,
648
+ | CommonPatchFields
649
+ | 'leading'
650
+ | 'title'
651
+ | 'subtitle'
652
+ | 'subtitleSegments'
653
+ | 'price'
654
+ | 'priceSegments'
655
+ | 'change'
656
+ | 'badges'
657
+ | 'pressActionKey'
658
+ | 'pressInActionKey'
659
+ | 'longPressActionKey'
660
+ | 'diagnostics'
661
+ | 'style'
662
+ >
663
+ >;
664
+ }>
548
665
  | Readonly<{
549
666
  type: 'mediaTile';
550
667
  key: string;
@@ -629,6 +746,7 @@ export type RowPatch =
629
746
 
630
747
  export type NativeListActionSource =
631
748
  | 'row'
749
+ | 'marketBadge'
632
750
  | 'leadingAction'
633
751
  | 'trailingAccessory'
634
752
  | 'footerAction'
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,173 @@ 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
+ ] as const) {
166
+ assertBoundedStyleNumber(style[field], `${path}.${field}`, 0, 64);
167
+ }
168
+ assertBoundedStyleNumber(style.lineGap, `${path}.lineGap`, 0, 16);
169
+ assertBoundedStyleNumber(style.changeWidth, `${path}.changeWidth`, 1, 160);
170
+ assertBoundedStyleNumber(style.changeHeight, `${path}.changeHeight`, 1, 160);
171
+ assertBoundedStyleNumber(
172
+ style.changeCornerRadius,
173
+ `${path}.changeCornerRadius`,
174
+ 0,
175
+ 80
176
+ );
177
+ if (style.image) {
178
+ assertBoundedStyleNumber(style.image.width, `${path}.image.width`, 1, 160);
179
+ assertBoundedStyleNumber(
180
+ style.image.height,
181
+ `${path}.image.height`,
182
+ 1,
183
+ 160
184
+ );
185
+ assertBoundedStyleNumber(
186
+ style.image.cornerRadius,
187
+ `${path}.image.cornerRadius`,
188
+ 0,
189
+ 80
190
+ );
191
+ assertVisualShape(style.image.shape, `${path}.image.shape`);
192
+ if (
193
+ style.image.contentFit !== undefined &&
194
+ !['cover', 'contain', 'fill', 'center'].includes(style.image.contentFit)
195
+ ) {
196
+ fail(
197
+ `${path}.image.contentFit`,
198
+ 'must be cover, contain, fill, or center'
199
+ );
200
+ }
201
+ }
202
+ assertMarketTextStyle(style.title, `${path}.title`);
203
+ assertMarketTextStyle(style.subtitle, `${path}.subtitle`);
204
+ assertMarketTextStyle(style.price, `${path}.price`);
205
+ assertMarketTextStyle(style.change, `${path}.change`);
206
+ }
207
+
208
+ function assertMarketRow(row: MarketRow, path: string): void {
209
+ if (!['token', 'stock', 'perp'].includes(row.variant)) {
210
+ fail(`${path}.variant`, 'must be token, stock, or perp');
211
+ }
212
+ assertText(row.title, `${path}.title`);
213
+ assertText(row.subtitle, `${path}.subtitle`);
214
+ assertText(row.price, `${path}.price`);
215
+ assertText(row.change.text, `${path}.change.text`);
216
+ assertLeadingVisual(row.leading, `${path}.leading`);
217
+ const assertSegments = (
218
+ segments: MarketRow['priceSegments'],
219
+ segmentPath: string
220
+ ) => {
221
+ segments?.forEach((segment, index) => {
222
+ assertText(segment.text, `${segmentPath}[${index}].text`);
223
+ if (segment.style !== undefined && segment.style !== 'subscript') {
224
+ fail(
225
+ `${segmentPath}[${index}].style`,
226
+ 'must be subscript when provided'
227
+ );
228
+ }
229
+ });
230
+ };
231
+ assertSegments(row.subtitleSegments, `${path}.subtitleSegments`);
232
+ assertSegments(row.priceSegments, `${path}.priceSegments`);
233
+ assertSegments(row.change.textSegments, `${path}.change.textSegments`);
234
+ if (!['positive', 'negative', 'neutral'].includes(row.change.tone)) {
235
+ fail(`${path}.change.tone`, 'must be positive, negative, or neutral');
236
+ }
237
+ if ((row.badges?.length ?? 0) > MAX_MARKET_BADGES) {
238
+ fail(`${path}.badges`, `supports at most ${MAX_MARKET_BADGES} badges`);
239
+ }
240
+ const badgeKeys = new Set<string>();
241
+ row.badges?.forEach((badge, index) => {
242
+ const badgePath = `${path}.badges[${index}]`;
243
+ assertKey(badge.key, `${badgePath}.key`);
244
+ if (badgeKeys.has(badge.key)) {
245
+ fail(`${path}.badges`, `duplicate badge key "${badge.key}"`);
246
+ }
247
+ badgeKeys.add(badge.key);
248
+ assertText(badge.text, `${badgePath}.text`);
249
+ if (badge.iconName !== undefined && badge.iconName !== 'verified') {
250
+ fail(`${badgePath}.iconName`, 'must be verified when provided');
251
+ }
252
+ if (badge.iconName !== undefined && badge.icon !== undefined) {
253
+ fail(`${badgePath}.icon`, 'cannot be combined with iconName');
254
+ }
255
+ assertImage(badge.icon, `${badgePath}.icon`);
256
+ if (
257
+ badge.tone !== undefined &&
258
+ !['neutral', 'info', 'success', 'warning', 'danger'].includes(badge.tone)
259
+ ) {
260
+ fail(
261
+ `${badgePath}.tone`,
262
+ 'must be neutral, info, success, warning, or danger'
263
+ );
264
+ }
265
+ if (!badge.text && !badge.icon && !badge.iconName) {
266
+ fail(badgePath, 'requires text, icon, or iconName');
267
+ }
268
+ if (badge.actionKey !== undefined) {
269
+ assertKey(badge.actionKey, `${badgePath}.actionKey`);
270
+ }
271
+ });
272
+ for (const [key, value] of [
273
+ ['pressActionKey', row.pressActionKey],
274
+ ['pressInActionKey', row.pressInActionKey],
275
+ ['longPressActionKey', row.longPressActionKey],
276
+ ['diagnostics.imageBindActionKey', row.diagnostics?.imageBindActionKey],
277
+ ] as const) {
278
+ if (value !== undefined) assertKey(value, `${path}.${key}`);
279
+ }
280
+ assertMarketStyle(row.style, `${path}.style`);
281
+ }
282
+
112
283
  function assertSectionHeaderVariant(
113
284
  variant: string | undefined,
114
285
  path: string
@@ -295,6 +466,8 @@ function assertVisual(row: RowModel, path: string): void {
295
466
  ? [row.leading]
296
467
  : row.type === 'dataRow'
297
468
  ? [row.leading]
469
+ : row.type === 'market'
470
+ ? [row.leading]
298
471
  : row.type === 'metricCard'
299
472
  ? [row.visual]
300
473
  : [];
@@ -488,6 +661,9 @@ function assertRow(
488
661
  fail(`${path}.badges`, `supports at most ${MAX_BADGES} badges`);
489
662
  }
490
663
  break;
664
+ case 'market':
665
+ assertMarketRow(row, path);
666
+ break;
491
667
  case 'mediaTile':
492
668
  assertImage(row.image, `${path}.image`);
493
669
  if (
@@ -583,6 +759,10 @@ function assertRow(
583
759
  assertTrailingAccessories(row.trailing, `${path}.trailing`);
584
760
  break;
585
761
  case 'system':
762
+ const presentation = 'presentation' in row ? row.presentation : undefined;
763
+ if (presentation !== undefined && presentation !== 'market') {
764
+ fail(`${path}.presentation`, 'must be market when provided');
765
+ }
586
766
  if (
587
767
  // OneKey patch: deprecated-wallet warnings retain the original scrolling semantics.
588
768
  !['loading', 'retry', 'noMatch', 'end', 'spacer', 'warning'].includes(
@@ -595,6 +775,17 @@ function assertRow(
595
775
  );
596
776
  }
597
777
  if (row.variant === 'warning') assertText(row.title, `${path}.title`);
778
+ if (
779
+ row.variant === 'loading' &&
780
+ row.loadingStyle !== undefined &&
781
+ row.loadingStyle !== 'skeleton' &&
782
+ row.loadingStyle !== 'spinner'
783
+ ) {
784
+ fail(
785
+ `${path}.loadingStyle`,
786
+ 'must be skeleton or spinner when provided'
787
+ );
788
+ }
598
789
  if (row.variant !== 'spacer') {
599
790
  assertText(row.message, `${path}.message`);
600
791
  }
@@ -879,6 +1070,21 @@ function assertPatchChanges(patch: RowPatch, index: number): void {
879
1070
  fail(`${path}.badges`, `supports at most ${MAX_BADGES} badges`);
880
1071
  }
881
1072
  break;
1073
+ case 'market':
1074
+ assertMarketRow(
1075
+ {
1076
+ type: 'market',
1077
+ key: patch.key,
1078
+ variant: 'token',
1079
+ leading: { kind: 'icon', name: 'placeholder' },
1080
+ title: '',
1081
+ price: '',
1082
+ change: { text: '', tone: 'neutral' },
1083
+ ...patch.changes,
1084
+ } as MarketRow,
1085
+ path
1086
+ );
1087
+ break;
882
1088
  case 'mediaTile':
883
1089
  assertImage(patch.changes.image, `${path}.image`);
884
1090
  if (