@onekeyfe/react-native-native-list 3.0.157-alpha.254 → 3.0.157-alpha.255

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.
Files changed (87) hide show
  1. package/android/build.gradle +7 -0
  2. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +20 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +179 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +23 -2
  5. package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +53 -0
  6. package/android/src/main/java/com/onekey/nativelist/NativeListEndAlignment.kt +5 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListMediaPreviewSlot.kt +215 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +15 -6
  9. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +3 -1
  10. package/android/src/main/java/com/onekey/nativelist/NativeListScrollPositionTracker.kt +29 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +16 -1
  12. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +163 -26
  13. package/android/src/main/res/values/native_scroll_coordinator_ids.xml +4 -0
  14. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListEndAlignmentTest.kt +34 -0
  15. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListScrollPositionTrackerTest.kt +28 -0
  16. package/docs/DESIGN.md +4 -2
  17. package/docs/ROW_TEMPLATES.md +45 -0
  18. package/docs/SPEC.md +227 -1
  19. package/docs/STYLE_SPEC.md +8 -0
  20. package/ios/HybridNativeList.swift +30 -0
  21. package/ios/NativeListActivityDetailsView.swift +229 -0
  22. package/ios/NativeListActivityRenderer.swift +24 -2
  23. package/ios/NativeListMediaPreviewSlot.swift +199 -0
  24. package/ios/NativeListMediaTileRenderer.swift +10 -4
  25. package/ios/NativeListModels.swift +4 -1
  26. package/ios/NativeListScrollPositionTracker.swift +30 -0
  27. package/ios/NativeListSectionHeaderRenderer.swift +27 -1
  28. package/ios/RNCNativeListView.swift +159 -4
  29. package/ios/tests/NativeListActivityPresentationTests.swift +43 -0
  30. package/ios/tests/NativeListLifecycleTests.swift +123 -0
  31. package/ios/tests/NativeListMediaVisibilityTests.swift +42 -0
  32. package/ios/tests/NativeListScrollPositionTrackerTests.swift +25 -0
  33. package/lib/module/NativeList.js +56 -3
  34. package/lib/module/NativeList.web.js +5 -0
  35. package/lib/module/validation.js +64 -4
  36. package/lib/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  37. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +39 -3
  38. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +6 -0
  39. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +15 -0
  40. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_bool.kt +78 -0
  41. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +26 -0
  42. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +24 -9
  43. package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +2 -0
  44. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +8 -0
  45. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +37 -0
  46. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +23 -2
  47. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +32 -0
  48. package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  49. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +3 -0
  50. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +54 -0
  51. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +6 -0
  52. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +8 -2
  53. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +6 -0
  54. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +11 -2
  55. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +3 -0
  56. package/lib/typescript/src/NativeList.nitro.d.ts +3 -0
  57. package/lib/typescript/src/NativeList.types.d.ts +14 -1
  58. package/lib/typescript/src/models.d.ts +33 -5
  59. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  60. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +39 -3
  61. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +6 -0
  62. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +15 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_bool.kt +78 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +26 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +24 -9
  66. package/nitrogen/generated/android/nativelistOnLoad.cpp +2 -0
  67. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +8 -0
  68. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +37 -0
  69. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +23 -2
  70. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +32 -0
  71. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  72. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +3 -0
  73. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +54 -0
  74. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +6 -0
  75. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +8 -2
  76. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +6 -0
  77. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +11 -2
  78. package/nitrogen/generated/shared/json/NativeListConfig.json +3 -0
  79. package/package.json +6 -5
  80. package/scripts/enable-container-slots.mjs +58 -0
  81. package/scripts/enable-container-slots.test.mjs +58 -0
  82. package/src/NativeList.nitro.ts +3 -0
  83. package/src/NativeList.tsx +84 -2
  84. package/src/NativeList.types.ts +14 -1
  85. package/src/NativeList.web.tsx +5 -0
  86. package/src/models.ts +38 -1
  87. package/src/validation.ts +146 -4
package/src/models.ts CHANGED
@@ -517,6 +517,16 @@ export type RailRow = RowBase &
517
517
  style?: RailRowStyle;
518
518
  }>;
519
519
 
520
+ export type ActivityAmount = Readonly<{
521
+ key: string;
522
+ text: string;
523
+ textSegments?: readonly ValueTextSegment[];
524
+ leading?: LeadingVisual;
525
+ tone?: TextTone;
526
+ secondaryText?: string;
527
+ secondaryTextSegments?: readonly ValueTextSegment[];
528
+ }>;
529
+
520
530
  export type ActivityRow = RowBase &
521
531
  Readonly<{
522
532
  type: 'activity';
@@ -527,6 +537,18 @@ export type ActivityRow = RowBase &
527
537
  status?: string;
528
538
  primaryAmount?: string;
529
539
  secondaryAmount?: string;
540
+ amounts?: readonly ActivityAmount[];
541
+ badges?: readonly BadgeModel[];
542
+ presentation?: 'stacked' | 'table';
543
+ fee?: Readonly<{
544
+ label?: string;
545
+ primary: string;
546
+ primaryTextSegments?: readonly ValueTextSegment[];
547
+ secondary?: string;
548
+ secondaryTextSegments?: readonly ValueTextSegment[];
549
+ hidden?: boolean;
550
+ }>;
551
+ descriptionActionKey?: string;
530
552
  footerActions?: readonly FooterAction[];
531
553
  style?: ActivityRowStyle;
532
554
  }>;
@@ -601,6 +623,11 @@ export type MediaTileRow = RowBase &
601
623
  Readonly<{
602
624
  type: 'mediaTile';
603
625
  variant: 'gallery' | 'browserPreview';
626
+ /** Native preview; video candidates display a silent first frame, never autoplay. */
627
+ media?: Readonly<{
628
+ source: ImageSource;
629
+ probeOrder: readonly ('image' | 'video')[];
630
+ }>;
604
631
  image?: ImageSource;
605
632
  imageState?: 'empty' | 'error';
606
633
  networkImage?: ImageSource;
@@ -650,6 +677,7 @@ export type SectionHeaderRow = RowBase &
650
677
  indexTitle?: string;
651
678
  variant?: 'summary' | 'gallery' | 'history';
652
679
  title: string;
680
+ titleLoading?: boolean;
653
681
  subtitle?: string;
654
682
  value?: string;
655
683
  valueActionKey?: string;
@@ -795,7 +823,7 @@ export type NativeListSnapshot = Readonly<{
795
823
  layout: Readonly<{
796
824
  kind: NativeListLayout;
797
825
  orientation?: NativeListOrientation;
798
- gridColumns?: 2 | 3 | 4;
826
+ gridColumns?: 2 | 3 | 4 | 5 | 6 | 7;
799
827
  stickyHeaders?: boolean;
800
828
  contentPadding?: number;
801
829
  contentPaddingHorizontal?: number;
@@ -813,6 +841,7 @@ export type NativeListSnapshot = Readonly<{
813
841
  reorderable?: boolean;
814
842
  pullToRefresh?: boolean;
815
843
  refreshing?: boolean;
844
+ refreshTriggerDistance?: number;
816
845
  loadMore?: boolean;
817
846
  endReachedThreshold?: number;
818
847
  sectionIndex?: SectionIndexConfig;
@@ -882,6 +911,11 @@ export type RowPatch =
882
911
  | 'title'
883
912
  | 'description'
884
913
  | 'status'
914
+ | 'amounts'
915
+ | 'badges'
916
+ | 'presentation'
917
+ | 'fee'
918
+ | 'descriptionActionKey'
885
919
  | 'primaryAmount'
886
920
  | 'secondaryAmount'
887
921
  | 'leading'
@@ -957,6 +991,7 @@ export type RowPatch =
957
991
  | CommonPatchFields
958
992
  | 'image'
959
993
  | 'imageState'
994
+ | 'media'
960
995
  | 'networkImage'
961
996
  | 'title'
962
997
  | 'subtitle'
@@ -993,6 +1028,7 @@ export type RowPatch =
993
1028
  SectionHeaderRow,
994
1029
  | CommonPatchFields
995
1030
  | 'variant'
1031
+ | 'titleLoading'
996
1032
  | 'title'
997
1033
  | 'subtitle'
998
1034
  | 'value'
@@ -1036,6 +1072,7 @@ export type NativeListActionSource =
1036
1072
  | 'leadingAction'
1037
1073
  | 'trailingAccessory'
1038
1074
  | 'footerAction'
1075
+ | 'description'
1039
1076
  | 'mediaClose';
1040
1077
 
1041
1078
  export type NativeListWindowRect = Readonly<{
package/src/validation.ts CHANGED
@@ -175,6 +175,11 @@ const TEXT_STYLE_KEYS_BY_ROW_TYPE: Readonly<
175
175
  'title',
176
176
  'description',
177
177
  'status',
178
+ 'amounts',
179
+ 'badges',
180
+ 'presentation',
181
+ 'fee',
182
+ 'descriptionActionKey',
178
183
  'primaryAmount',
179
184
  'secondaryAmount',
180
185
  ],
@@ -183,7 +188,7 @@ const TEXT_STYLE_KEYS_BY_ROW_TYPE: Readonly<
183
188
  market: ['title', 'subtitle', 'price', 'change'],
184
189
  mediaTile: ['title', 'subtitle', 'badge'],
185
190
  metricCard: ['title', 'value', 'subtitle', 'trend'],
186
- sectionHeader: ['title', 'subtitle', 'value'],
191
+ sectionHeader: ['titleLoading', 'title', 'subtitle', 'value'],
187
192
  action: ['title', 'value'],
188
193
  system: ['title', 'message', 'actionText'],
189
194
  };
@@ -917,7 +922,84 @@ function assertVisual(row: RowModel, path: string): void {
917
922
  });
918
923
  }
919
924
 
920
- function assertActivity(row: ActivityRow, path: string): void {
925
+ function assertActivity(row: Partial<ActivityRow>, path: string): void {
926
+ assertText(row.title, `${path}.title`);
927
+ assertText(row.description, `${path}.description`);
928
+ if (
929
+ row.presentation !== undefined &&
930
+ !['stacked', 'table'].includes(row.presentation)
931
+ )
932
+ fail(`${path}.presentation`, 'must be stacked or table');
933
+ if (row.descriptionActionKey !== undefined)
934
+ assertKey(row.descriptionActionKey, `${path}.descriptionActionKey`);
935
+ if ((row.amounts?.length ?? 0) > 32)
936
+ fail(`${path}.amounts`, 'supports at most 32 amount lines');
937
+ const amountKeys = new Set<string>();
938
+ row.amounts?.forEach((amount, index) => {
939
+ const amountPath = `${path}.amounts[${index}]`;
940
+ assertKey(amount.key, `${amountPath}.key`);
941
+ if (amountKeys.has(amount.key)) fail(amountPath, 'duplicate amount key');
942
+ amountKeys.add(amount.key);
943
+ assertText(amount.text, `${amountPath}.text`);
944
+ assertLeadingVisual(amount.leading, `${amountPath}.leading`);
945
+ assertText(amount.secondaryText, `${amountPath}.secondaryText`);
946
+ if (
947
+ amount.tone !== undefined &&
948
+ !['primary', 'secondary', 'positive', 'negative'].includes(amount.tone)
949
+ )
950
+ fail(`${amountPath}.tone`, 'invalid text tone');
951
+ amount.textSegments?.forEach((segment, segmentIndex) => {
952
+ assertText(
953
+ segment.text,
954
+ `${amountPath}.textSegments[${segmentIndex}].text`
955
+ );
956
+ if (segment.style !== undefined && segment.style !== 'subscript')
957
+ fail(amountPath, 'invalid text segment style');
958
+ });
959
+ });
960
+ const assertActivitySegments = (
961
+ segments: readonly { text: string; style?: string }[] | undefined,
962
+ segmentPath: string
963
+ ) =>
964
+ segments?.forEach((segment, index) => {
965
+ assertText(segment.text, `${segmentPath}[${index}].text`);
966
+ if (segment.style !== undefined && segment.style !== 'subscript')
967
+ fail(segmentPath, 'invalid text segment style');
968
+ });
969
+ row.amounts?.forEach((amount, index) =>
970
+ assertActivitySegments(
971
+ amount.secondaryTextSegments,
972
+ `${path}.amounts[${index}].secondaryTextSegments`
973
+ )
974
+ );
975
+ assertActivitySegments(
976
+ row.fee?.primaryTextSegments,
977
+ `${path}.fee.primaryTextSegments`
978
+ );
979
+ assertActivitySegments(
980
+ row.fee?.secondaryTextSegments,
981
+ `${path}.fee.secondaryTextSegments`
982
+ );
983
+ assertText(row.fee?.label, `${path}.fee.label`);
984
+ assertText(row.fee?.primary, `${path}.fee.primary`);
985
+ assertText(row.fee?.secondary, `${path}.fee.secondary`);
986
+ if ((row.badges?.length ?? 0) > 4)
987
+ fail(`${path}.badges`, 'supports at most 4 activity badges');
988
+ const badgeKeys = new Set<string>();
989
+ row.badges?.forEach((badge, index) => {
990
+ assertKey(badge.key, `${path}.badges[${index}].key`);
991
+ if (badgeKeys.has(badge.key)) fail(`${path}.badges`, 'duplicate badge key');
992
+ badgeKeys.add(badge.key);
993
+ assertText(badge.text, `${path}.badges[${index}].text`);
994
+ if (
995
+ badge.tone !== undefined &&
996
+ !['neutral', 'info', 'success', 'warning', 'danger'].includes(badge.tone)
997
+ )
998
+ fail(`${path}.badges`, 'invalid badge tone');
999
+ });
1000
+ if (row.fee?.hidden !== undefined && typeof row.fee.hidden !== 'boolean')
1001
+ fail(`${path}.fee.hidden`, 'must be boolean');
1002
+
921
1003
  if ((row.footerActions?.length ?? 0) > MAX_FOOTER_ACTIONS) {
922
1004
  fail(
923
1005
  `${path}.footerActions`,
@@ -1119,6 +1201,7 @@ function assertRow(
1119
1201
  assertMarketRow(row, path);
1120
1202
  break;
1121
1203
  case 'mediaTile':
1204
+ assertMediaPreview(row.media, `${path}.media`);
1122
1205
  assertImage(row.image, `${path}.image`);
1123
1206
  if (
1124
1207
  row.imageState !== undefined &&
@@ -1126,10 +1209,14 @@ function assertRow(
1126
1209
  ) {
1127
1210
  fail(`${path}.imageState`, 'must be empty or error when provided');
1128
1211
  }
1129
- if (row.imageState === undefined && row.image === undefined) {
1212
+ if (
1213
+ row.imageState === undefined &&
1214
+ row.image === undefined &&
1215
+ row.media === undefined
1216
+ ) {
1130
1217
  fail(
1131
1218
  `${path}.image`,
1132
- 'is required unless imageState is empty or error'
1219
+ 'is required unless media is provided or imageState is empty or error'
1133
1220
  );
1134
1221
  }
1135
1222
  assertImage(row.networkImage, `${path}.networkImage`);
@@ -1168,6 +1255,11 @@ function assertRow(
1168
1255
  }
1169
1256
  break;
1170
1257
  case 'sectionHeader':
1258
+ if (
1259
+ row.titleLoading !== undefined &&
1260
+ typeof row.titleLoading !== 'boolean'
1261
+ )
1262
+ fail(`${path}.titleLoading`, 'must be boolean');
1171
1263
  assertKey(row.sectionKey, `${path}.sectionKey`);
1172
1264
  if (
1173
1265
  row.presentation !== undefined &&
@@ -1292,14 +1384,57 @@ function assertGroups(rows: readonly RowModel[]): void {
1292
1384
  }
1293
1385
  }
1294
1386
 
1387
+ function assertMediaPreview(value: unknown, path: string) {
1388
+ if (value === undefined) return;
1389
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1390
+ fail(path, 'must be a media descriptor');
1391
+ const media = value as {
1392
+ source?: import('./models').ImageSource;
1393
+ probeOrder?: unknown;
1394
+ };
1395
+ assertImage(media.source, `${path}.source`);
1396
+ if (!media.source) fail(`${path}.source`, 'is required');
1397
+ const order = media.probeOrder;
1398
+ if (
1399
+ !Array.isArray(order) ||
1400
+ order.length < 1 ||
1401
+ order.length > 2 ||
1402
+ order.some((kind) => kind !== 'image' && kind !== 'video') ||
1403
+ new Set(order).size !== order.length
1404
+ ) {
1405
+ fail(
1406
+ `${path}.probeOrder`,
1407
+ 'must contain one or two distinct image/video candidates'
1408
+ );
1409
+ }
1410
+ }
1411
+
1295
1412
  export function validateSnapshot(
1296
1413
  snapshot: NativeListSnapshot
1297
1414
  ): NativeListSnapshot {
1415
+ if (
1416
+ snapshot.capabilities?.refreshTriggerDistance !== undefined &&
1417
+ (!Number.isFinite(snapshot.capabilities.refreshTriggerDistance) ||
1418
+ snapshot.capabilities.refreshTriggerDistance <= 0)
1419
+ )
1420
+ fail(
1421
+ 'capabilities.refreshTriggerDistance',
1422
+ 'must be a positive finite number'
1423
+ );
1424
+
1298
1425
  assertPlainSerializable(snapshot, 'snapshot', new Set<object>());
1299
1426
  if (snapshot.schemaVersion !== 1) fail('snapshot.schemaVersion', 'must be 1');
1300
1427
  if (!Number.isSafeInteger(snapshot.generation) || snapshot.generation < 0) {
1301
1428
  fail('snapshot.generation', 'must be a non-negative safe integer');
1302
1429
  }
1430
+ if (
1431
+ snapshot.layout.gridColumns !== undefined &&
1432
+ (!Number.isInteger(snapshot.layout.gridColumns) ||
1433
+ snapshot.layout.gridColumns < 2 ||
1434
+ snapshot.layout.gridColumns > 7)
1435
+ ) {
1436
+ fail('snapshot.layout.gridColumns', 'must be an integer from 2 to 7');
1437
+ }
1303
1438
  if (snapshot.layout.kind === 'grid' && !snapshot.layout.gridColumns) {
1304
1439
  fail('snapshot.layout.gridColumns', 'is required for grid layout');
1305
1440
  }
@@ -1508,6 +1643,7 @@ function assertPatchChanges(patch: RowPatch, index: number): void {
1508
1643
  assertLeadingVisual(patch.changes.visual, `${path}.visual`);
1509
1644
  break;
1510
1645
  case 'activity':
1646
+ assertActivity(patch.changes, path);
1511
1647
  assertText(patch.changes.title, `${path}.title`);
1512
1648
  assertText(patch.changes.description, `${path}.description`);
1513
1649
  assertText(patch.changes.status, `${path}.status`);
@@ -1576,6 +1712,7 @@ function assertPatchChanges(patch: RowPatch, index: number): void {
1576
1712
  );
1577
1713
  break;
1578
1714
  case 'mediaTile':
1715
+ assertMediaPreview(patch.changes.media, `${path}.media`);
1579
1716
  assertImage(patch.changes.image, `${path}.image`);
1580
1717
  if (
1581
1718
  patch.changes.imageState !== undefined &&
@@ -1623,6 +1760,11 @@ function assertPatchChanges(patch: RowPatch, index: number): void {
1623
1760
  }
1624
1761
  break;
1625
1762
  case 'sectionHeader':
1763
+ if (
1764
+ patch.changes.titleLoading !== undefined &&
1765
+ typeof patch.changes.titleLoading !== 'boolean'
1766
+ )
1767
+ fail(`${path}.titleLoading`, 'must be boolean');
1626
1768
  assertSectionHeaderVariant(patch.changes.variant, `${path}.variant`);
1627
1769
  assertText(patch.changes.title, `${path}.title`);
1628
1770
  assertText(patch.changes.subtitle, `${path}.subtitle`);