@onekeyfe/react-native-native-list 3.0.157-alpha.249 → 3.0.157-alpha.250

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 (130) hide show
  1. package/README.md +21 -5
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
  5. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
  6. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
  16. package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
  18. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
  19. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
  20. package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
  21. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
  22. package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
  23. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
  24. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
  25. package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
  26. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
  27. package/docs/DESIGN.md +518 -9
  28. package/docs/MIGRATION_BASELINE.md +117 -0
  29. package/docs/ROW_TEMPLATES.md +359 -0
  30. package/docs/SPEC.md +358 -0
  31. package/docs/STYLE_SPEC.md +978 -0
  32. package/docs/images/list-capabilities.svg +39 -0
  33. package/docs/images/row-action.svg +17 -0
  34. package/docs/images/row-activity.svg +24 -0
  35. package/docs/images/row-data.svg +24 -0
  36. package/docs/images/row-identity.svg +25 -0
  37. package/docs/images/row-market.svg +23 -0
  38. package/docs/images/row-media-tile.svg +26 -0
  39. package/docs/images/row-message.svg +20 -0
  40. package/docs/images/row-metric-card.svg +33 -0
  41. package/docs/images/row-rail.svg +18 -0
  42. package/docs/images/row-section-header.svg +19 -0
  43. package/docs/images/row-style-regions.svg +30 -0
  44. package/docs/images/row-system.svg +30 -0
  45. package/docs/images/row-wallet-group.svg +26 -0
  46. package/ios/NativeListAccessoryStack.swift +680 -0
  47. package/ios/NativeListActionRenderer.swift +108 -0
  48. package/ios/NativeListActivityRenderer.swift +179 -0
  49. package/ios/NativeListDataRowRenderer.swift +444 -0
  50. package/ios/NativeListIdentityRenderer.swift +685 -0
  51. package/ios/NativeListImageSlot.swift +140 -0
  52. package/ios/NativeListLeadingVisual.swift +372 -0
  53. package/ios/NativeListMarketRenderer.swift +698 -0
  54. package/ios/NativeListMediaTileRenderer.swift +184 -0
  55. package/ios/NativeListMessageRenderer.swift +168 -0
  56. package/ios/NativeListMetricCardRenderer.swift +420 -0
  57. package/ios/NativeListModels.swift +32 -0
  58. package/ios/NativeListRailRenderer.swift +152 -0
  59. package/ios/NativeListRendererCell.swift +311 -0
  60. package/ios/NativeListRendererRegistry.swift +50 -0
  61. package/ios/NativeListResolvedText.swift +122 -0
  62. package/ios/NativeListRowHost.swift +63 -0
  63. package/ios/NativeListSectionHeaderRenderer.swift +708 -0
  64. package/ios/NativeListSystemRenderer.swift +568 -0
  65. package/ios/NativeListTextLabel.swift +42 -0
  66. package/ios/NativeListTextStyles.swift +236 -0
  67. package/ios/NativeListWalletGroupRenderer.swift +255 -0
  68. package/ios/RNCNativeListView.swift +111 -180
  69. package/lib/module/models.js +54 -0
  70. package/lib/module/validation.js +324 -26
  71. package/lib/module/web/NativeListWebEngine.js +148 -936
  72. package/lib/module/web/templates/ActionRowRenderer.js +97 -0
  73. package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
  74. package/lib/module/web/templates/DataRowRenderer.js +145 -0
  75. package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
  76. package/lib/module/web/templates/MarketRowRenderer.js +310 -0
  77. package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
  78. package/lib/module/web/templates/MessageRowRenderer.js +165 -0
  79. package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
  80. package/lib/module/web/templates/RailRowRenderer.js +98 -0
  81. package/lib/module/web/templates/RowContainerStyle.js +100 -0
  82. package/lib/module/web/templates/RowElements.js +93 -0
  83. package/lib/module/web/templates/RowRendererRegistry.js +70 -0
  84. package/lib/module/web/templates/RowText.js +121 -0
  85. package/lib/module/web/templates/RowVisual.js +43 -0
  86. package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
  87. package/lib/module/web/templates/SystemRowRenderer.js +161 -0
  88. package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
  89. package/lib/typescript/src/models.d.ts +166 -13
  90. package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
  91. package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
  92. package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
  93. package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
  94. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
  95. package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
  96. package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
  97. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
  98. package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
  99. package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
  100. package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
  101. package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
  102. package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
  103. package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
  104. package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
  105. package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
  106. package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
  107. package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
  108. package/package.json +3 -3
  109. package/src/models.ts +281 -19
  110. package/src/validation.ts +501 -46
  111. package/src/web/NativeListWebEngine.ts +361 -1932
  112. package/src/web/templates/ActionRowRenderer.ts +123 -0
  113. package/src/web/templates/ActivityRowRenderer.ts +159 -0
  114. package/src/web/templates/DataRowRenderer.ts +212 -0
  115. package/src/web/templates/IdentityRowRenderer.ts +527 -0
  116. package/src/web/templates/MarketRowRenderer.ts +467 -0
  117. package/src/web/templates/MediaTileRowRenderer.ts +182 -0
  118. package/src/web/templates/MessageRowRenderer.ts +306 -0
  119. package/src/web/templates/MetricCardRowRenderer.ts +337 -0
  120. package/src/web/templates/RailRowRenderer.ts +136 -0
  121. package/src/web/templates/RowContainerStyle.ts +131 -0
  122. package/src/web/templates/RowElements.ts +160 -0
  123. package/src/web/templates/RowRendererRegistry.ts +111 -0
  124. package/src/web/templates/RowText.ts +181 -0
  125. package/src/web/templates/RowVisual.ts +115 -0
  126. package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
  127. package/src/web/templates/SystemRowRenderer.ts +282 -0
  128. package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
  129. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
  130. package/ios/NativeListCell.swift +0 -4300
@@ -1,10 +1,26 @@
1
+ import {
2
+ applyRowContainerStyle,
3
+ resetRowContainerStyle,
4
+ } from './templates/RowContainerStyle';
5
+ import { hasExplicitRowHeight, rowSizeModifier } from './templates/RowElements';
6
+ import { applyTextStyleToSlot, applyValueSegments } from './templates/RowText';
7
+ import { setMarketQuoteContent } from './templates/MarketRowRenderer';
8
+ export {
9
+ resolveWebMarketLayoutStyle,
10
+ type WebMarketLayoutStyle,
11
+ } from './templates/MarketRowRenderer';
12
+ import { measureRailWidth } from './templates/RailRowRenderer';
13
+ import {
14
+ rowRenderer,
15
+ rowRendererKey,
16
+ recycleRowBody,
17
+ type RowRendererKey,
18
+ } from './templates/RowRendererRegistry';
1
19
  import type {
2
20
  ActionAnchorInvalidatedEvent,
3
21
  CheckboxState,
4
22
  ImageSource,
5
23
  LeadingVisual,
6
- MarketRow,
7
- MarketTextStyle,
8
24
  NativeListSnapshot,
9
25
  NativeListActionAnchor,
10
26
  NativeListActionSource,
@@ -189,49 +205,7 @@ export function webSectionIndexActiveKey(
189
205
  return activeKey;
190
206
  }
191
207
 
192
- export type WebMarketLayoutStyle = Readonly<{
193
- horizontalPadding: number;
194
- verticalPadding: number;
195
- leadingGap: number;
196
- titleBadgeGap: number;
197
- trailingGap: number;
198
- imageWidth: number;
199
- imageHeight: number;
200
- imageCornerRadius: number;
201
- changeWidth: number;
202
- changeHeight: number;
203
- changeCornerRadius: number;
204
- }>;
205
-
206
208
  /** Resolves every reusable Market geometry field, including legacy defaults. */
207
- export function resolveWebMarketLayoutStyle(
208
- row: MarketRow
209
- ): WebMarketLayoutStyle {
210
- const style = row.style;
211
- const imageWidth = style?.image?.width ?? (row.variant === 'stock' ? 40 : 32);
212
- const imageHeight =
213
- style?.image?.height ?? (row.variant === 'stock' ? 40 : 32);
214
- return {
215
- horizontalPadding:
216
- style?.horizontalPadding ?? (row.variant === 'perp' ? 16 : 20),
217
- verticalPadding: style?.verticalPadding ?? 12,
218
- leadingGap: style?.leadingGap ?? (row.variant === 'perp' ? 8 : 14),
219
- titleBadgeGap: style?.titleBadgeGap ?? 4,
220
- trailingGap: style?.trailingGap ?? 8,
221
- imageWidth,
222
- imageHeight,
223
- imageCornerRadius:
224
- style?.image?.cornerRadius ??
225
- (style?.image?.shape === 'square'
226
- ? 0
227
- : style?.image?.shape === 'rounded'
228
- ? 8
229
- : Math.min(imageWidth, imageHeight) / 2),
230
- changeWidth: style?.changeWidth ?? 80,
231
- changeHeight: style?.changeHeight ?? 32,
232
- changeCornerRadius: style?.changeCornerRadius ?? 8,
233
- };
234
- }
235
209
 
236
210
  export type NativeListWebCallbacks = Readonly<{
237
211
  onRowAction?: (event: RowActionEvent) => void;
@@ -460,28 +434,7 @@ function sizeModifier(row: RowModel): number {
460
434
  (row.variant === 'summary' || row.variant === 'gallery')
461
435
  )
462
436
  return 0;
463
- if (row.size === 'small') return -8;
464
- if (row.size === 'large') return 12;
465
- return 0;
466
- }
467
-
468
- function approximateMessageHeight(row: RowModel, availableWidth: number) {
469
- if (row.type !== 'message') return 0;
470
- const leadingWidth = row.leading ? 52 : 0;
471
- const thumbnailWidth = row.thumbnail ? 88 : 0;
472
- const charactersPerLine = Math.max(
473
- 18,
474
- Math.floor((availableWidth - leadingWidth - thumbnailWidth - 40) / 7)
475
- );
476
- const titleLines = Math.min(
477
- 2,
478
- Math.max(1, Math.ceil(row.title.length / charactersPerLine))
479
- );
480
- const bodyLines = Math.min(
481
- row.bodyLines ?? 3,
482
- Math.max(1, Math.ceil(row.body.length / charactersPerLine))
483
- );
484
- return 32 + titleLines * 20 + bodyLines * 20 + 22;
437
+ return rowSizeModifier(row.size);
485
438
  }
486
439
 
487
440
  export function estimateWebRowHeight(
@@ -489,145 +442,21 @@ export function estimateWebRowHeight(
489
442
  snapshot: NativeListSnapshot,
490
443
  availableWidth: number
491
444
  ): number {
492
- // OneKey patch: explicit selector height takes precedence over presets.
493
- // if (row.type === 'system' && row.variant === 'spacer') return row.height;
494
- if (row.height !== undefined) return row.height;
495
- if (row.type === 'system' && row.variant === 'warning') {
496
- const width = Math.max(1, availableWidth - 24);
497
- const lines = (text: string) =>
498
- Math.max(
499
- 1,
500
- Math.ceil(
501
- Array.from(text).reduce(
502
- (length, char) => length + (char.charCodeAt(0) > 255 ? 14 : 7),
503
- 0
504
- ) / width
505
- )
506
- );
507
- return 32 + 20 * (lines(row.title) + lines(row.message));
508
- }
509
- if (row.type === 'walletGroup')
510
- // OneKey patch: wallet badges participate in the outer group height.
511
- // return (row.children.length + 1) * 68 + row.children.length * 12;
512
- return (
513
- [row.parent, ...row.children].reduce(
514
- (height, member) =>
515
- height + estimateWebRowHeight(member, snapshot, availableWidth),
516
- 0
517
- ) +
518
- row.children.length * 12 +
519
- (row.parent.height !== undefined ? 2 : 0)
520
- );
521
- // OneKey patch: reserve the source badge line below wallet names.
522
- // if (row.type === 'identity' && row.presentation === 'walletSidebar')
523
- // return 68;
524
- if (row.type === 'identity' && row.presentation === 'walletSidebar')
525
- return 68 + (row.badges?.length ? 24 : 0);
526
- if (row.type === 'identity' && row.presentation === 'networkSelector')
527
- return 47;
528
- if (row.type === 'identity' && row.presentation === 'accountSelector')
529
- return 58;
530
-
531
- let base: number;
532
- switch (row.type) {
533
- case 'rail':
534
- base = 40;
535
- break;
536
- case 'activity':
537
- base = row.footerActions?.length ? 100 : 60;
538
- break;
539
- case 'message':
540
- base = approximateMessageHeight(row, availableWidth);
541
- break;
542
- case 'mediaTile':
543
- base = 244;
544
- break;
545
- case 'metricCard':
546
- base =
547
- row.variant === 'activity'
548
- ? 161
549
- : row.variant === 'performance'
550
- ? 178
551
- : 132;
552
- break;
553
- case 'sectionHeader': {
554
- const isHistory =
555
- row.variant === 'history' ||
556
- row.key.startsWith('history-') ||
557
- row.sectionKey.startsWith('history-');
558
- base =
559
- snapshot.layout.kind === 'table'
560
- ? 28
561
- : row.presentation === 'networkSelector'
562
- ? 47
563
- : isHistory
564
- ? 16
565
- : row.variant === 'summary'
566
- ? 68
567
- : row.variant === 'gallery'
568
- ? 32
569
- : row.checkbox
570
- ? 56
571
- : snapshot.layout.kind === 'linear'
572
- ? 30
573
- : 36;
574
- break;
575
- }
576
- case 'system':
577
- base =
578
- row.variant === 'loading' && row.loadingStyle === 'skeleton'
579
- ? 56
580
- : row.variant === 'loading' && row.loadingStyle === 'spinner'
581
- ? 52
582
- : 'presentation' in row && row.presentation === 'market'
583
- ? row.variant === 'loading'
584
- ? 68
585
- : 44
586
- : row.variant === 'noMatch' || row.variant === 'end'
587
- ? 36
588
- : row.variant === 'retry'
589
- ? 44
590
- : 56;
591
- break;
592
- case 'action':
593
- base = row.presentation === 'accountSelector' ? 48 : row.icon ? 60 : 44;
594
- break;
595
- case 'dataRow':
596
- base = row.columns.some((column) => column.secondaryText) ? 60 : 56;
597
- break;
598
- case 'market': {
599
- const marketStyle = resolveWebMarketLayoutStyle(row);
600
- base = Math.max(
601
- row.variant === 'stock' ? 72 : 68,
602
- marketStyle.imageHeight + marketStyle.verticalPadding * 2
603
- );
604
- break;
605
- }
606
- case 'identity':
607
- base = row.tertiary ? 72 : row.subtitle ? 60 : 56;
608
- break;
609
- default:
610
- base = 56;
611
- }
612
- const tableAdjustment =
613
- snapshot.layout.kind === 'table' &&
614
- row.type === 'dataRow' &&
615
- !row.columns.some((column) => column.secondaryText)
616
- ? -8
617
- : 0;
618
- return Math.max(0, base + sizeModifier(row) + tableAdjustment);
445
+ // Explicit container/model height takes precedence over template presets.
446
+ const explicitHeight = row.style?.container?.height ?? row.height;
447
+ if (explicitHeight !== undefined) return explicitHeight;
448
+ const registered = rowRenderer(row);
449
+ return Math.max(
450
+ 0,
451
+ registered.measure(availableWidth, snapshot.layout.kind) +
452
+ (registered.appliesSizePreset ? sizeModifier(row) : 0)
453
+ );
619
454
  }
620
455
 
621
456
  function estimateHorizontalWidth(row: RowModel): number {
622
457
  if (row.type === 'mediaTile') return 200;
623
458
  if (row.type !== 'rail') return 280;
624
- const badgeLength = row.badge?.text.length ?? 0;
625
- const statusLength =
626
- row.status && row.status !== 'none' ? row.status.length : 0;
627
- return Math.min(
628
- 288,
629
- Math.max(72, 50 + (row.title.length + badgeLength + statusLength) * 7)
630
- );
459
+ return measureRailWidth(row);
631
460
  }
632
461
 
633
462
  function sectionIndexEnabled(snapshot: NativeListSnapshot): boolean {
@@ -680,9 +509,14 @@ export function computeWebListLayout(
680
509
  y: padding.top,
681
510
  width: itemWidth,
682
511
  height:
683
- row.type === 'rail'
512
+ (row.type === 'walletGroup' && row.key === compactRowKey
513
+ ? WALLET_REORDER_COMPACT_HEIGHT
514
+ : undefined) ??
515
+ row.style?.container?.height ??
516
+ row.height ??
517
+ (row.type === 'rail'
684
518
  ? Math.min(rowHeight, availableHeight)
685
- : availableHeight,
519
+ : availableHeight),
686
520
  });
687
521
  x += itemWidth + spacing;
688
522
  });
@@ -760,9 +594,11 @@ export function computeWebListLayout(
760
594
  return;
761
595
  }
762
596
  const itemHeight =
763
- row.type === 'mediaTile'
597
+ row.style?.container?.height ??
598
+ row.height ??
599
+ (row.type === 'mediaTile'
764
600
  ? itemWidth + 48
765
- : estimateWebRowHeight(row, snapshot, itemWidth);
601
+ : estimateWebRowHeight(row, snapshot, itemWidth));
766
602
  items.push({
767
603
  index,
768
604
  key: row.key,
@@ -806,6 +642,8 @@ export function canStartWebWalletGroupReorder(
806
642
  const member = [row.parent, ...row.children].find(
807
643
  (candidate) => candidate.key === memberKey
808
644
  );
645
+ // Legacy: only `draggable: false` excludes a member; disabled members may
646
+ // still start the atomic group drag.
809
647
  return member?.draggable !== false;
810
648
  }
811
649
 
@@ -951,15 +789,15 @@ export function webMarketImageBindDeltaForPatches(
951
789
  // OneKey patch: selector names must shrink before the sidebar clips their contents.
952
790
  export const WEB_LIST_CSS = `
953
791
  [data-native-list-selector="walletSidebar"] .ok-native-list-title{max-width:100%;min-width:0}
954
- .ok-native-list-root{--nl-bg:#f7f7f7;--nl-row:#fff;--nl-selected:#eaf2ff;--nl-pressed:#e8e8e8;--nl-subdued:#f9f9f9;--nl-strong:#0000000f;--nl-primary:#111;--nl-secondary:#6b7280;--nl-disabled:#8d8d8d;--nl-icon:#111;--nl-icon-subdued:#8d8d8d;--nl-separator:#e5e7eb;--nl-accent:#2f6bff;--nl-positive:#15803d;--nl-negative:#dc2626;--nl-critical:#feecec;--nl-inverse:#202020;--nl-inverse-text:#fcfcfc;--nl-info:#0d74ce;position:absolute;inset:0;display:flex;min-width:0;min-height:0;overflow:hidden;background:var(--nl-bg);color:var(--nl-primary);font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-synthesis:none}
792
+ .ok-native-list-root{--nl-bg:#f7f7f7;--nl-row:#fff;--nl-selected:#eaf2ff;--nl-pressed:#e8e8e8;--nl-subdued:#f9f9f9;--nl-strong:#0000000f;--nl-primary:#111;--nl-secondary:#6b7280;--nl-disabled:#8d8d8d;--nl-icon:#111;--nl-icon-subdued:#8d8d8d;--nl-separator:#e5e7eb;--nl-accent:#2f6bff;--nl-positive:#15803d;--nl-negative:#dc2626;--nl-critical:#feecec;--nl-inverse:#202020;--nl-inverse-text:#fcfcfc;--nl-info:#0d74ce;position:absolute;inset:0;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:var(--nl-bg);color:var(--nl-primary);font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-synthesis:none}
955
793
  .ok-native-list-viewport-frame{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
956
794
  .ok-native-list-viewport{position:absolute;inset:0;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;scrollbar-gutter:stable}
957
795
  .ok-native-list-content{position:relative;min-width:100%;min-height:100%}
958
- .ok-native-list-item{position:absolute;box-sizing:border-box;contain:layout paint style;outline:none}
796
+ .ok-native-list-item{position:absolute;left:0;top:0;box-sizing:border-box;contain:layout paint style;outline:none}
959
797
  .ok-native-list-row{width:100%;height:100%;box-sizing:border-box;display:flex;align-items:center;gap:12px;overflow:hidden;background:var(--nl-row);color:var(--nl-primary);cursor:default;user-select:none;-webkit-user-select:none}
960
798
  .ok-native-list-item[data-table-alternate="true"]>.ok-native-list-row{background:var(--nl-bg)}
961
799
  .ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
962
- .ok-native-list-item[data-native-list-disabled="true"]>.ok-native-list-row{opacity:.5;cursor:default}
800
+ .ok-native-list-item[data-native-list-disabled="true"]>.ok-native-list-row{cursor:default}
963
801
  .ok-native-list-item[data-native-list-row-press-enabled="true"]>.ok-native-list-row{cursor:pointer}
964
802
  .ok-native-list-item[data-native-list-row-press-enabled="true"]:hover>.ok-native-list-row{background:var(--nl-pressed)}
965
803
  .ok-native-list-item[data-native-list-row-press-enabled="true"]:active>.ok-native-list-row{background:var(--nl-pressed)}
@@ -984,10 +822,13 @@ export const WEB_LIST_CSS = `
984
822
  .ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-pressed);cursor:grabbing}
985
823
  .ok-native-list-reorder-preview[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
986
824
  .ok-native-list-reorder-count{position:absolute;right:4px;bottom:4px;display:flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:24px;height:24px;padding:0 6px;border:1px solid var(--nl-row);border-radius:12px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;line-height:22px;font-weight:600}
987
- .ok-native-list-item[data-separator="true"]>.ok-native-list-row{border-bottom:1px solid var(--nl-separator)}
988
- .ok-native-list-item[data-group-position="first"]>.ok-native-list-row{border-radius:12px 12px 0 0}
989
- .ok-native-list-item[data-group-position="last"]>.ok-native-list-row{border-radius:0 0 12px 12px}
990
- .ok-native-list-item[data-group-position="single"]>.ok-native-list-row{border-radius:12px}
825
+ .ok-native-list-item[data-separator="true"]>.ok-native-list-row{border-bottom:1px solid var(--nl-separator-color,var(--nl-separator))}
826
+ /* An inset separator keeps the transparent border so the row's height is unchanged, and paints the visible line with a logical-inset overlay so it follows RTL. */
827
+ .ok-native-list-root[data-separator-inset="true"] .ok-native-list-item[data-separator="true"]>.ok-native-list-row{position:relative;border-bottom-color:transparent}
828
+ .ok-native-list-root[data-separator-inset="true"] .ok-native-list-item[data-separator="true"]>.ok-native-list-row::after{content:"";position:absolute;inset-inline-start:var(--nl-separator-inset,0);inset-inline-end:0;bottom:-1px;height:1px;background:var(--nl-separator-color,var(--nl-separator))}
829
+ .ok-native-list-item[data-group-position="first"]>.ok-native-list-row{border-radius:var(--nl-group-radius,12px) var(--nl-group-radius,12px) 0 0}
830
+ .ok-native-list-item[data-group-position="last"]>.ok-native-list-row{border-radius:0 0 var(--nl-group-radius,12px) var(--nl-group-radius,12px)}
831
+ .ok-native-list-item[data-group-position="single"]>.ok-native-list-row{border-radius:var(--nl-group-radius,12px)}
991
832
  .ok-native-list-standard{padding:8px 12px}.ok-native-list-network-row{padding:0 12px}.ok-native-list-wallet-row{padding:4px 8px;flex-direction:column;justify-content:center;gap:4px}.ok-native-list-account-row{padding:4px 12px;gap:8px}
992
833
  .ok-native-list-account-row .ok-native-list-visual,.ok-native-list-account-action-row .ok-native-list-visual{width:32px;height:32px;flex-basis:32px}.ok-native-list-account-row .ok-native-list-visual>img,.ok-native-list-account-row .ok-native-list-visual-main,.ok-native-list-account-action-row .ok-native-list-visual>img,.ok-native-list-account-action-row .ok-native-list-visual-main{width:32px;height:32px}.ok-native-list-account-row .ok-native-list-visual>.ok-native-list-visual-corner{width:20px;height:20px;padding:2px}.ok-native-list-account-action-row .ok-native-list-visual{border-radius:8px!important}.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-secondary);font-size:12px;line-height:16px;font-weight:400}.ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-primary)}.ok-native-list-account-row .ok-native-list-title{font-size:16px;line-height:20px;font-weight:400}.ok-native-list-account-row .ok-native-list-secondary{font-size:14px;line-height:20px;font-weight:400}
993
834
  .ok-native-list-flex{display:flex;flex:1;min-width:0;flex-direction:column;justify-content:center}.ok-native-list-title{font-size:15px;line-height:20px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-secondary{font-size:13px;line-height:18px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-tertiary{color:var(--nl-secondary)}.ok-native-list-info{color:var(--nl-info)}
@@ -1007,18 +848,13 @@ export const WEB_LIST_CSS = `
1007
848
  .ok-native-list-data{padding:6px 12px}.ok-native-list-index{flex:0 0 28px;color:var(--nl-secondary);font-size:13px}.ok-native-list-favorite{flex:0 0 24px;color:var(--nl-icon-subdued);font-size:22px}.ok-native-list-favorite[data-active="true"]{color:var(--nl-accent)}.ok-native-list-data-cell{display:flex;flex-direction:column;min-width:0}.ok-native-list-data-cell[data-align="center"]{align-items:center}.ok-native-list-data-cell[data-align="end"]{align-items:flex-end}.ok-native-list-data-primary{display:flex;align-items:center;gap:5px;max-width:100%;font-size:16px;font-weight:500;white-space:nowrap}.ok-native-list-unread{width:7px;height:7px;flex:0 0 7px;border-radius:50%;background:var(--nl-accent)}.ok-native-list-thumbnail{width:64px;height:64px;border-radius:10px;object-fit:cover}
1008
849
  .ok-native-list-market{padding:12px 20px;gap:14px}.ok-native-list-market>.ok-native-list-visual{width:32px;height:32px;flex-basis:32px}.ok-native-list-market[data-variant="stock"]>.ok-native-list-visual{width:40px;height:40px;flex-basis:40px}.ok-native-list-market>.ok-native-list-visual>.ok-native-list-visual-main{width:100%;height:100%}.ok-native-list-market-main{display:flex;flex:1;min-width:0;flex-direction:column;justify-content:center}.ok-native-list-market-title-line{display:flex;align-items:center;min-width:0;gap:4px}.ok-native-list-market-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nl-primary);font-size:16px;line-height:24px;font-weight:500}.ok-native-list-market-subtitle{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nl-secondary);font-size:14px;line-height:20px;font-weight:400}.ok-native-list-market-badge{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;box-sizing:border-box;max-width:72px;height:18px;padding:0 5px;border:0;border-radius:4px;background:var(--nl-strong);color:var(--nl-secondary);font:500 11px/18px inherit;overflow:hidden;white-space:nowrap}.ok-native-list-market-badge img{width:14px;height:14px;object-fit:contain}.ok-native-list-market-trailing{display:flex;flex:0 0 auto;align-items:center;gap:8px}.ok-native-list-market-price{max-width:112px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;color:var(--nl-primary);font-size:16px;line-height:24px;font-weight:500;font-variant-numeric:tabular-nums}.ok-native-list-market-change{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:80px;height:32px;border-radius:8px;color:#fff;background:#8d8d8d;font-size:14px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap}.ok-native-list-market-change[data-tone="positive"]{background:var(--nl-positive)}.ok-native-list-market-change[data-tone="negative"]{background:var(--nl-negative)}
1009
850
  .ok-native-list-market-badge>svg,.ok-native-list-market-badge>.ok-native-list-market-badge-icon>svg{display:block;width:16px;height:16px;flex:0 0 16px}.ok-native-list-system[data-native-list-presentation="market"]{box-sizing:border-box;padding:12px 20px}.ok-native-list-system[data-native-list-presentation="market"]>.ok-native-list-spinner{width:32px;height:32px}
1010
- .ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:6px;display:flex;width:20px;height:16px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:8px;background:transparent;color:var(--nl-secondary);font:600 10px/1 inherit;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-accent);color:var(--nl-inverse-text)}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-accent);outline-offset:1px}.ok-native-list-index-preview{position:absolute;z-index:8;right:40px;top:50%;display:flex;width:48px;height:48px;align-items:center;justify-content:center;transform:translateY(-50%) scale(.92);border-radius:14px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:22px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease}.ok-native-list-index-preview[data-visible="true"]{opacity:1;transform:translateY(-50%) scale(1)}
1011
- .ok-native-list-refresh{position:absolute;z-index:7;left:50%;top:8px;display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;opacity:0;transform:translate(-50%,-16px);transition:opacity .15s ease,transform .15s ease;pointer-events:none}.ok-native-list-refresh[data-visible="true"]{opacity:1;transform:translate(-50%,0)}
1012
- .ok-native-list-warning{height:auto;display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:14px 12px;border-top:1px solid;border-bottom:1px solid;box-sizing:border-box;cursor:default}.ok-native-list-warning-title,.ok-native-list-warning-message{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}.ok-native-list-warning-title{font-weight:500;color:var(--nl-primary)}.ok-native-list-warning-message{font-weight:400;color:var(--nl-secondary)}
1013
- .ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
851
+ .ok-native-list-index-preview{position:absolute;z-index:8;right:40px;top:50%;display:flex;width:48px;height:48px;align-items:center;justify-content:center;transform:translateY(-50%) scale(.92);border-radius:14px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:22px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease}.ok-native-list-index-preview[data-visible="true"]{opacity:1;transform:translateY(-50%) scale(1)}
1014
852
  .ok-native-list-row.ok-native-list-market-skeleton{padding:12px 20px;gap:0}.ok-native-list-skeleton-left{display:flex;align-items:center;gap:12px;flex:1}.ok-native-list-skeleton-text{display:flex;flex-direction:column;gap:4px}.ok-native-list-skeleton-right{display:flex;align-items:center;gap:8px}.ok-native-list-skeleton-mark{display:block;flex-shrink:0;border-radius:8px;animation:ok-native-list-skeleton 1.5s linear infinite alternate}@keyframes ok-native-list-skeleton{from{background-color:var(--nl-skeleton-base)}to{background-color:var(--nl-skeleton-highlight)}}.ok-native-list-market-spinner{display:block;width:20px;height:20px;flex-shrink:0;color:var(--nl-icon);animation:ok-native-list-spin .75s linear infinite}
1015
853
  @media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner,.ok-native-list-market-spinner{animation:none}.ok-native-list-skeleton-mark{animation:none;background:var(--nl-skeleton-base)}}
1016
854
  .ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport,.ok-native-list-viewport-frame[data-section-index-visible="true"]>.ok-native-list-viewport{scrollbar-width:none}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport::-webkit-scrollbar,.ok-native-list-viewport-frame[data-section-index-visible="true"]>.ok-native-list-viewport::-webkit-scrollbar{display:none}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer;font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:15px;display:flex;width:14px;height:14px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:7px;background:transparent;color:var(--nl-disabled);font-family:inherit;font-size:10px;font-weight:400;line-height:1;cursor:pointer;opacity:.5;transition:opacity .15s ease}.ok-native-list-index-rail:hover .ok-native-list-index-button,.ok-native-list-index-rail:focus-within .ok-native-list-index-button,.ok-native-list-index-button[data-active="true"]{opacity:1}.ok-native-list-index-button[data-active="true"]{background:var(--nl-positive);color:var(--nl-inverse-text);font-weight:500}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-positive);outline-offset:1px}
1017
855
  .ok-native-list-refresh{position:absolute;z-index:7;left:50%;top:8px;display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;opacity:0;transform:translate(-50%,-16px);transition:opacity .15s ease,transform .15s ease;pointer-events:none}.ok-native-list-refresh[data-visible="true"]{opacity:1;transform:translate(-50%,0)}
1018
856
  .ok-native-list-warning{height:auto;display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:14px 12px;border-top:1px solid;border-bottom:1px solid;box-sizing:border-box;cursor:default}.ok-native-list-warning-title,.ok-native-list-warning-message{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}.ok-native-list-warning-title{font-weight:500;color:var(--nl-primary)}.ok-native-list-warning-message{font-weight:400;color:var(--nl-secondary)}
1019
- .ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
1020
- @media (prefers-reduced-motion:reduce){.ok-native-list-refresh{transition:none}.ok-native-list-spinner{animation:none}}
1021
- /* OneKey patch: selector controls follow their original semantic colors and geometry. */
857
+ .ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}/* OneKey patch: selector controls follow their original semantic colors and geometry. */
1022
858
  .ok-native-list-checkbox[data-selector="networkSelector"]{padding:0;border-radius:4px;border-color:var(--nl-checkbox-border,var(--nl-separator));background:var(--nl-checkbox-icon,var(--nl-inverse-text))}
1023
859
  .ok-native-list-checkbox[data-selector="networkSelector"]::after{display:none}
1024
860
  .ok-native-list-checkbox[data-selector="networkSelector"]>svg{display:none;width:16px;height:16px;color:var(--nl-checkbox-icon,var(--nl-inverse-text));flex-shrink:0}
@@ -1044,6 +880,11 @@ export const WEB_LIST_CSS = `
1044
880
  .ok-native-list-root .ok-native-list-item>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-wallet-member>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-action-row,.ok-native-list-wallet-member{cursor:default}
1045
881
  /* OneKey patch: Add account hover and pressed backgrounds keep the account row corner radius. */
1046
882
  .ok-native-list-account-action-row{border-radius:12px}
883
+
884
+ .ok-native-list-root .ok-native-list-item>[data-nl-container-background="true"],.ok-native-list-root .ok-native-list-wallet-member>[data-nl-container-background="true"]{background:var(--nl-container-background)}
885
+ .ok-native-list-root [data-nl-container-border="true"]{position:relative}
886
+ .ok-native-list-root [data-nl-container-border="true"]::before{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 var(--nl-container-border-width) var(--nl-container-border-color);z-index:2;pointer-events:none}
887
+ .ok-native-list-root .ok-native-list-item[data-native-list-row-press-enabled="true"]:hover>[data-nl-container-background="true"],.ok-native-list-root .ok-native-list-item[data-native-list-row-press-enabled="true"]:active>[data-nl-container-background="true"],.ok-native-list-root .ok-native-list-wallet-member:hover>[data-nl-container-background="true"],.ok-native-list-root .ok-native-list-wallet-member:active>[data-nl-container-background="true"]{background:var(--nl-pressed)}
1047
888
  `;
1048
889
 
1049
890
  function createElement(
@@ -1604,17 +1445,6 @@ function iconGlyph(name: string): string {
1604
1445
  return name.slice(0, 1).toLocaleUpperCase();
1605
1446
  }
1606
1447
 
1607
- function visualFromRow(row: RowModel): LeadingVisual | undefined {
1608
- if (row.type === 'identity') return row.leading;
1609
- if (row.type === 'rail') return row.visual;
1610
- if (row.type === 'activity') return row.leading;
1611
- if (row.type === 'message') return row.leading;
1612
- if (row.type === 'dataRow') return row.leading;
1613
- if (row.type === 'market') return row.leading;
1614
- if (row.type === 'metricCard') return row.visual;
1615
- return undefined;
1616
- }
1617
-
1618
1448
  function createVisual(
1619
1449
  context: RenderContext,
1620
1450
  visual: LeadingVisual | undefined,
@@ -1828,20 +1658,6 @@ function toneColor(
1828
1658
  return fallback === 'secondary' ? 'var(--nl-secondary)' : 'var(--nl-primary)';
1829
1659
  }
1830
1660
 
1831
- function createBadge(
1832
- context: RenderContext,
1833
- badge: Readonly<{ text: string; tone?: string }>
1834
- ): HTMLElement {
1835
- const element = createElement(
1836
- context.document,
1837
- 'span',
1838
- 'ok-native-list-badge',
1839
- badge.text
1840
- );
1841
- setData(element, 'tone', badge.tone);
1842
- return element;
1843
- }
1844
-
1845
1661
  function selectionKeysForTarget(
1846
1662
  target: SelectionTarget | undefined,
1847
1663
  rowKey: string,
@@ -1909,1625 +1725,207 @@ function createCheckbox(
1909
1725
  context.document,
1910
1726
  'button',
1911
1727
  'ok-native-list-checkbox'
1912
- );
1913
- element.setAttribute('type', 'button');
1914
- element.setAttribute('role', 'checkbox');
1915
- element.setAttribute(
1916
- 'aria-checked',
1917
- state === 'indeterminate' ? 'mixed' : String(state === 'checked')
1918
- );
1919
- element.setAttribute('aria-label', 'Select');
1920
- element.toggleAttribute('disabled', Boolean(accessory.disabled));
1921
- setData(element, 'state', state);
1922
- setData(element, 'checkboxFallback', accessory.state);
1923
- setData(element, 'nativeListAction', accessory.actionKey ?? 'selection');
1924
- setData(element, 'selectionScope', accessory.target?.scope ?? 'row');
1925
- const row = context.snapshot.rows[context.itemIndex];
1926
- if (row && 'presentation' in row && row.presentation === 'networkSelector') {
1927
- setData(element, 'selector', 'networkSelector');
1928
- for (const [state, name] of [
1929
- ['checked', 'CheckboxCheckedCustom'],
1930
- ['indeterminate', 'CheckboxIndeterminateCustom'],
1931
- ]) {
1932
- const holder = createElement(context.document, 'span');
1933
- applySelectorIcon(holder, name);
1934
- const svg = holder.firstElementChild;
1935
- if (svg) {
1936
- svg.setAttribute('data-state', state);
1937
- element.appendChild(svg);
1938
- }
1939
- }
1940
- }
1941
- if (accessory.target?.scope === 'section')
1942
- setData(element, 'selectionKey', accessory.target.sectionKey);
1943
- else if (accessory.target?.scope === 'row')
1944
- setData(element, 'selectionKey', rowKey);
1945
- return element;
1946
- }
1947
-
1948
- function createIconAction(
1949
- context: RenderContext,
1950
- name: string,
1951
- actionKey: string | undefined,
1952
- disabled?: boolean,
1953
- tintColor?: string
1954
- ): HTMLElement {
1955
- const element = createElement(
1956
- context.document,
1957
- actionKey ? 'button' : 'span',
1958
- 'ok-native-list-icon-button',
1959
- iconGlyph(name)
1960
- );
1961
- if (element.tagName === 'BUTTON') {
1962
- element.setAttribute('type', 'button');
1963
- element.toggleAttribute('disabled', Boolean(disabled));
1964
- }
1965
- applySelectorIcon(element, name);
1966
- if (actionKey) setData(element, 'nativeListAction', actionKey);
1967
- if (tintColor) element.style.color = tintColor;
1968
- return element;
1969
- }
1970
-
1971
- function markActionAnchorSource(
1972
- element: HTMLElement,
1973
- source: NativeListActionSource,
1974
- slot?: number
1975
- ) {
1976
- setData(element, 'nativeListAnchorSource', source);
1977
- if (slot !== undefined) setData(element, 'nativeListAnchorSlot', slot);
1978
- }
1979
-
1980
- // OneKey patch: the compact zero-count digits share the amount baseline.
1981
- function applyValueSegments(
1982
- element: HTMLElement,
1983
- segments:
1984
- | readonly Readonly<{ text: string; style?: 'subscript' }>[]
1985
- | undefined,
1986
- fontSize = 16,
1987
- lineHeight = 24,
1988
- weight = 500
1989
- ) {
1990
- if (!segments?.length) return;
1991
- element.textContent = '';
1992
- element.style.fontSize = String(fontSize) + 'px';
1993
- element.style.lineHeight = String(lineHeight) + 'px';
1994
- element.style.fontWeight = String(weight);
1995
- segments.forEach((segment) => {
1996
- const span = createElement(
1997
- element.ownerDocument,
1998
- 'span',
1999
- undefined,
2000
- segment.text
2001
- );
2002
- if (segment.style === 'subscript') {
2003
- span.style.fontSize = String(Math.ceil(fontSize * 0.6)) + 'px';
2004
- span.style.lineHeight = String(fontSize) + 'px';
2005
- }
2006
- element.appendChild(span);
2007
- });
2008
- }
2009
-
2010
- function createAccessory(
2011
- context: RenderContext,
2012
- rowKey: string,
2013
- accessory: TrailingAccessory,
2014
- slot: number
2015
- ): HTMLElement {
2016
- if (accessory.kind === 'checkbox') {
2017
- const element = createCheckbox(context, rowKey, accessory);
2018
- markActionAnchorSource(element, 'trailingAccessory', slot);
2019
- return element;
2020
- }
2021
- if (accessory.kind === 'icon') {
2022
- const element = createIconAction(
2023
- context,
2024
- accessory.name,
2025
- accessory.actionKey,
2026
- accessory.disabled,
2027
- accessory.tintColor
2028
- );
2029
- markActionAnchorSource(element, 'trailingAccessory', slot);
2030
- setData(element, 'testid', accessory.testID);
2031
- if (accessory.hoverActionKey)
2032
- setData(element, 'nativeListHoverAction', accessory.hoverActionKey);
2033
- if (accessory.accessibilityLabel)
2034
- element.setAttribute('aria-label', accessory.accessibilityLabel);
2035
- const row = context.snapshot.rows[context.itemIndex];
2036
- if (
2037
- row &&
2038
- 'presentation' in row &&
2039
- row.presentation === 'accountSelector'
2040
- ) {
2041
- setData(element, 'nativeListAnchorInset', 7);
2042
- // OneKey patch: the create-address button omits IconButton's one-point border.
2043
- if (row.height !== undefined && accessory.name === 'PlusSmallOutline') {
2044
- setData(element, 'nativeListAccountControl', 'createAddress');
2045
- if (!accessory.tintColor)
2046
- element.style.color = 'var(--nl-icon-subdued)';
2047
- }
2048
- }
2049
- return element;
2050
- }
2051
- if (accessory.kind === 'spinner') {
2052
- return createElement(context.document, 'span', 'ok-native-list-spinner');
2053
- }
2054
- const actionKey = 'actionKey' in accessory ? accessory.actionKey : undefined;
2055
- const element = createElement(
2056
- context.document,
2057
- actionKey ? 'button' : 'span',
2058
- 'ok-native-list-accessory'
2059
- );
2060
- if (element.tagName === 'BUTTON') {
2061
- element.setAttribute('type', 'button');
2062
- element.classList.add('ok-native-list-icon-button');
2063
- setData(element, 'nativeListAction', actionKey);
2064
- }
2065
- switch (accessory.kind) {
2066
- case 'value':
2067
- element.textContent = accessory.text;
2068
- applyValueSegments(element, accessory.textSegments);
2069
- if (accessory.secondary)
2070
- element.classList.add('ok-native-list-accessory-secondary');
2071
- break;
2072
- case 'valuePair': {
2073
- const primary = createElement(
2074
- context.document,
2075
- 'span',
2076
- undefined,
2077
- accessory.primary
2078
- );
2079
- primary.style.color = toneColor(accessory.primaryTone, 'primary');
2080
- const secondary = createElement(
2081
- context.document,
2082
- 'span',
2083
- 'ok-native-list-secondary',
2084
- accessory.secondary
2085
- );
2086
- secondary.style.color = toneColor(accessory.secondaryTone, 'secondary');
2087
- element.replaceChildren(primary, secondary);
2088
- element.classList.add('ok-native-list-amounts');
2089
- break;
2090
- }
2091
- case 'radio':
2092
- element.textContent = accessory.checked ? '●' : '○';
2093
- break;
2094
- case 'switch':
2095
- element.textContent = accessory.value ? 'ON' : 'OFF';
2096
- setData(element, 'nativeListAction', accessory.actionKey);
2097
- break;
2098
- case 'chevron':
2099
- element.textContent = '›';
2100
- break;
2101
- case 'menu':
2102
- element.textContent = '⋮';
2103
- break;
2104
- case 'drag':
2105
- element.textContent = '≡';
2106
- break;
2107
- case 'progress':
2108
- element.textContent = String(Math.round(accessory.value * 100)) + '%';
2109
- break;
2110
- }
2111
- if (actionKey) markActionAnchorSource(element, 'trailingAccessory', slot);
2112
- return element;
2113
- }
2114
-
2115
- function appendAccessories(
2116
- parent: HTMLElement,
2117
- context: RenderContext,
2118
- rowKey: string,
2119
- accessories: readonly TrailingAccessory[] | undefined
2120
- ) {
2121
- if (!accessories?.length) return;
2122
- const container = createElement(
2123
- context.document,
2124
- 'span',
2125
- 'ok-native-list-accessories'
2126
- );
2127
- const row = context.snapshot.rows[context.itemIndex];
2128
- if (
2129
- row &&
2130
- row.height !== undefined &&
2131
- 'presentation' in row &&
2132
- row.presentation === 'networkSelector'
2133
- ) {
2134
- container.style.gap = accessories.some(
2135
- (accessory) => accessory.kind === 'checkbox'
2136
- )
2137
- ? '12px'
2138
- : '20px';
2139
- }
2140
- if (
2141
- row &&
2142
- row.height !== undefined &&
2143
- 'presentation' in row &&
2144
- row.presentation === 'accountSelector' &&
2145
- accessories.length === 1 &&
2146
- accessories[0]?.kind === 'icon' &&
2147
- accessories[0].name === 'PlusSmallOutline'
2148
- ) {
2149
- setData(container, 'nativeListAccountControl', 'createAddress');
2150
- }
2151
- accessories.forEach((accessory, slot) =>
2152
- container.appendChild(createAccessory(context, rowKey, accessory, slot))
2153
- );
2154
- parent.appendChild(container);
2155
- }
2156
-
2157
- function createTextColumn(
2158
- context: RenderContext,
2159
- title: string,
2160
- subtitle?: string,
2161
- tertiary?: string,
2162
- tertiaryTone?: 'secondary' | 'info',
2163
- badges?: readonly Readonly<{ text: string; tone?: string }>[]
2164
- ): HTMLElement {
2165
- const column = createElement(context.document, 'span', 'ok-native-list-flex');
2166
- const titleLine = createElement(
2167
- context.document,
2168
- 'span',
2169
- 'ok-native-list-title',
2170
- title
2171
- );
2172
- if (badges?.length) {
2173
- const badgeLine = createElement(
2174
- context.document,
2175
- 'span',
2176
- 'ok-native-list-badges'
2177
- );
2178
- badges.forEach((badge) =>
2179
- badgeLine.appendChild(createBadge(context, badge))
2180
- );
2181
- titleLine.appendChild(badgeLine);
2182
- }
2183
- column.appendChild(titleLine);
2184
- if (subtitle)
2185
- column.appendChild(
2186
- createElement(
2187
- context.document,
2188
- 'span',
2189
- 'ok-native-list-secondary',
2190
- subtitle
2191
- )
2192
- );
2193
- if (tertiary) {
2194
- const element = createElement(
2195
- context.document,
2196
- 'span',
2197
- tertiaryTone === 'info'
2198
- ? 'ok-native-list-secondary ok-native-list-info'
2199
- : 'ok-native-list-secondary ok-native-list-tertiary',
2200
- tertiary
2201
- );
2202
- column.appendChild(element);
2203
- }
2204
- return column;
2205
- }
2206
-
2207
- function createSectionHeader(
2208
- context: RenderContext,
2209
- row: Extract<RowModel, { type: 'sectionHeader' }>
2210
- ): HTMLElement {
2211
- const body = createElement(
2212
- context.document,
2213
- 'div',
2214
- 'ok-native-list-row ok-native-list-section'
2215
- );
2216
- setData(body, 'variant', row.variant);
2217
- if (row.titleIcon) {
2218
- const titleIcon = createIconAction(
2219
- context,
2220
- row.titleIcon.name,
2221
- row.titleIcon.actionKey,
2222
- row.titleIcon.disabled,
2223
- row.titleIcon.tintColor
2224
- );
2225
- markActionAnchorSource(titleIcon, 'leadingAction');
2226
- body.appendChild(titleIcon);
2227
- }
2228
- // OneKey patch: section title help has its own measurable action target.
2229
- // body.appendChild(createTextColumn(context, row.title, row.subtitle));
2230
- const column = createTextColumn(context, row.title, row.subtitle);
2231
- const title = column.firstElementChild as HTMLElement;
2232
- title.classList.add('ok-native-list-section-title');
2233
- if (row.titleActionKey) {
2234
- setData(title, 'nativeListAction', row.titleActionKey);
2235
- markActionAnchorSource(title, 'leadingAction');
2236
- title.setAttribute('role', 'button');
2237
- title.tabIndex = 0;
2238
- title.style.alignSelf = 'flex-start';
2239
- title.style.maxWidth = '100%';
2240
- // OneKey patch: explicit network headers reserve a separate 3-point underline area.
2241
- if (row.presentation !== 'networkSelector' || row.height === undefined) {
2242
- title.style.textDecoration = 'underline dotted';
2243
- title.style.textUnderlineOffset = '6px';
2244
- }
2245
- if (row.titleActionOnHover) setData(title, 'nativeListHoverAction', true);
2246
- }
2247
- if (row.presentation === 'networkSelector' && row.height !== undefined) {
2248
- body.style.padding =
2249
- row.variant === 'summary' ? '24px 12px 20px' : '0 12px';
2250
- body.style.backgroundColor = 'var(--nl-bg)';
2251
- body.style.gap = row.checkbox ? '12px' : '8px';
2252
- title.style.fontSize = row.variant === 'summary' ? '16px' : '14px';
2253
- title.style.lineHeight = row.variant === 'summary' ? '24px' : '20px';
2254
- title.style.fontWeight =
2255
- row.variant === 'summary' || (row.titleActionKey && !row.checkbox)
2256
- ? '500'
2257
- : '600';
2258
- if (row.titleActionKey) {
2259
- const text = createElement(
2260
- context.document,
2261
- 'span',
2262
- 'ok-native-list-section-title-text',
2263
- row.title
2264
- );
2265
- text.style.overflow = 'hidden';
2266
- text.style.textOverflow = 'ellipsis';
2267
- text.style.maxWidth = '100%';
2268
- const dotted = context.document.createElementNS(
2269
- 'http://www.w3.org/2000/svg',
2270
- 'svg'
2271
- );
2272
- dotted.setAttribute('height', '2');
2273
- dotted.style.cssText =
2274
- 'display:block;position:absolute;left:0;bottom:0;width:100%;height:2px;color:var(--nl-secondary)';
2275
- const line = context.document.createElementNS(
2276
- 'http://www.w3.org/2000/svg',
2277
- 'line'
2278
- );
2279
- for (const [key, value] of Object.entries({
2280
- x1: '1',
2281
- y1: '1',
2282
- x2: '100%',
2283
- y2: '1',
2284
- stroke: 'currentColor',
2285
- 'stroke-width': '1.5',
2286
- 'stroke-dasharray': '0,4',
2287
- 'stroke-linecap': 'round',
2288
- }))
2289
- line.setAttribute(key, value);
2290
- // OneKey patch: keep both round caps inside the original full-width viewport.
2291
- const lineViewport = context.document.createElementNS(
2292
- 'http://www.w3.org/2000/svg',
2293
- 'svg'
2294
- );
2295
- lineViewport.setAttribute('width', 'calc(100% - 1px)');
2296
- lineViewport.setAttribute('height', '2');
2297
- lineViewport.setAttribute('overflow', 'visible');
2298
- lineViewport.appendChild(line);
2299
- dotted.appendChild(lineViewport);
2300
- // OneKey patch: SVG intrinsic width must not expand the title action beyond its text.
2301
- title.style.display = 'block';
2302
- title.style.position = 'relative';
2303
- title.style.width = 'fit-content';
2304
- title.style.paddingBottom = '3px';
2305
- text.style.display = 'block';
2306
- title.replaceChildren(text, dotted);
2307
- }
2308
- }
2309
- body.appendChild(column);
2310
- if (row.value) {
2311
- const value = createElement(
2312
- context.document,
2313
- row.valueActionKey ? 'button' : 'span',
2314
- row.valueActionKey
2315
- ? 'ok-native-list-action-button ok-native-list-section-value'
2316
- : 'ok-native-list-value ok-native-list-section-value',
2317
- row.value
2318
- );
2319
- applyValueSegments(value, row.valueSegments);
2320
- if (row.presentation === 'networkSelector' && row.height !== undefined) {
2321
- value.style.fontFamily = 'inherit';
2322
- value.style.fontSize = '16px';
2323
- value.style.lineHeight = '24px';
2324
- value.style.fontWeight = '500';
2325
- if (row.valueActionKey) {
2326
- value.style.color = 'var(--nl-secondary)';
2327
- value.style.padding = '0';
2328
- value.style.flexShrink = '0';
2329
- }
2330
- }
2331
- if (row.valueActionTestID) setData(value, 'testid', row.valueActionTestID);
2332
- if (row.valueActionKey) {
2333
- value.setAttribute('type', 'button');
2334
- setData(value, 'nativeListAction', row.valueActionKey);
2335
- markActionAnchorSource(value, 'trailingAccessory', 0);
2336
- }
2337
- body.appendChild(value);
2338
- }
2339
- if (row.valueIcon) {
2340
- const valueIcon = createIconAction(
2341
- context,
2342
- row.valueIcon.name,
2343
- row.valueIcon.actionKey,
2344
- row.valueIcon.disabled,
2345
- row.valueIcon.tintColor
2346
- );
2347
- markActionAnchorSource(valueIcon, 'trailingAccessory', 1);
2348
- body.appendChild(valueIcon);
2349
- }
2350
- if (row.checkbox)
2351
- body.appendChild(createCheckbox(context, row.key, row.checkbox));
2352
- return body;
2353
- }
2354
-
2355
- function createActionRow(
2356
- context: RenderContext,
2357
- row: Extract<RowModel, { type: 'action' }>
2358
- ): HTMLElement {
2359
- const body = createElement(
2360
- context.document,
2361
- 'div',
2362
- [
2363
- 'ok-native-list-row',
2364
- 'ok-native-list-action-row',
2365
- row.presentation === 'accountSelector'
2366
- ? 'ok-native-list-account-action-row'
2367
- : '',
2368
- ]
2369
- .filter(Boolean)
2370
- .join(' ')
2371
- );
2372
- if (row.icon) body.appendChild(createVisual(context, row.icon)!);
2373
- const title = createElement(
2374
- context.document,
2375
- 'span',
2376
- 'ok-native-list-action-title',
2377
- row.title
2378
- );
2379
- setData(title, 'tone', row.tone);
2380
- if (row.presentation === 'accountSelector' && row.icon)
2381
- title.style.fontWeight = '500';
2382
- body.appendChild(title);
2383
- if (row.checkbox)
2384
- body.appendChild(createCheckbox(context, row.key, row.checkbox));
2385
- appendAccessories(body, context, row.key, row.trailing);
2386
- return body;
2387
- }
2388
-
2389
- function createSystemRow(
2390
- context: RenderContext,
2391
- row: Extract<RowModel, { type: 'system' }>
2392
- ): HTMLElement {
2393
- const body = createElement(
2394
- context.document,
2395
- 'div',
2396
- 'ok-native-list-row ok-native-list-system'
2397
- );
2398
- setData(body, 'variant', row.variant);
2399
- if ('presentation' in row)
2400
- setData(body, 'nativeListPresentation', row.presentation);
2401
- if (row.variant === 'loading' && row.loadingStyle === 'skeleton') {
2402
- body.classList.add('ok-native-list-market-skeleton');
2403
- const background = resolvedTheme(context.snapshot).background;
2404
- const rgb = Number.parseInt(background.slice(1, 7), 16);
2405
- const dark =
2406
- ((rgb >> 16) & 255) * 0.299 +
2407
- ((rgb >> 8) & 255) * 0.587 +
2408
- (rgb & 255) * 0.114 <
2409
- 128;
2410
- body.style.setProperty('--nl-skeleton-base', dark ? '#111111' : '#fafafa');
2411
- body.style.setProperty(
2412
- '--nl-skeleton-highlight',
2413
- dark ? '#333333' : '#cdcdcd'
2414
- );
2415
- const left = createElement(
2416
- context.document,
2417
- 'div',
2418
- 'ok-native-list-skeleton-left'
2419
- );
2420
- const mark = (width: number, height: number, circle = false) => {
2421
- const element = createElement(
2422
- context.document,
2423
- 'span',
2424
- 'ok-native-list-skeleton-mark'
2425
- );
2426
- element.style.width = `${width}px`;
2427
- element.style.height = `${height}px`;
2428
- if (circle) element.style.borderRadius = '50%';
2429
- return element;
2430
- };
2431
- left.appendChild(mark(32, 32, true));
2432
- const text = createElement(
2433
- context.document,
2434
- 'div',
2435
- 'ok-native-list-skeleton-text'
2436
- );
2437
- text.appendChild(mark(80, 16));
2438
- text.appendChild(mark(60, 12));
2439
- left.appendChild(text);
2440
- body.appendChild(left);
2441
- const right = createElement(
2442
- context.document,
2443
- 'div',
2444
- 'ok-native-list-skeleton-right'
2445
- );
2446
- right.appendChild(mark(80, 18));
2447
- right.appendChild(mark(80, 18));
2448
- body.appendChild(right);
2449
- return body;
2450
- }
2451
- if (row.variant === 'loading' && row.loadingStyle === 'spinner') {
2452
- body.style.justifyContent = 'center';
2453
- body.style.padding = '16px';
2454
- const spinner = createElement(
2455
- context.document,
2456
- 'span',
2457
- 'ok-native-list-market-spinner'
2458
- );
2459
- spinner.setAttribute('role', 'progressbar');
2460
- // Same 20px SVG and 750ms rotation as react-native-web ActivityIndicator.
2461
- spinner.innerHTML =
2462
- '<svg viewBox="0 0 32 32" width="20" height="20"><circle cx="16" cy="16" r="14" fill="none" stroke="currentColor" stroke-width="4" opacity="0.2"/><circle cx="16" cy="16" r="14" fill="none" stroke="currentColor" stroke-width="4" stroke-dasharray="80" stroke-dashoffset="60"/></svg>';
2463
- body.appendChild(spinner);
2464
- return body;
2465
- }
2466
- if ('presentation' in row && row.presentation === 'market') {
2467
- if (row.variant === 'noMatch') {
2468
- body.style.justifyContent = 'center';
2469
- body.style.padding = '32px';
2470
- const message = createElement(context.document, 'span', '', row.message);
2471
- message.style.fontSize = '16px';
2472
- message.style.lineHeight = '24px';
2473
- body.appendChild(message);
2474
- return body;
2475
- }
2476
- if (row.variant === 'end') {
2477
- body.style.justifyContent = 'center';
2478
- body.style.padding = '16px';
2479
- body.style.gap = '8px';
2480
- for (const width of [80, 4, 80]) {
2481
- const mark = createElement(context.document, 'span', '');
2482
- mark.style.width = String(width) + 'px';
2483
- mark.style.height = width === 4 ? '4px' : '1px';
2484
- mark.style.borderRadius = width === 4 ? '2px' : '0';
2485
- mark.style.background = 'var(--nl-separator)';
2486
- body.appendChild(mark);
2487
- }
2488
- return body;
2489
- }
2490
- }
2491
- if (row.variant === 'warning') {
2492
- body.classList.add('ok-native-list-warning');
2493
- body.style.borderColor = row.borderColor ?? 'var(--nl-separator)';
2494
- body.appendChild(
2495
- createElement(
2496
- context.document,
2497
- 'span',
2498
- 'ok-native-list-warning-title',
2499
- row.title
2500
- )
2501
- );
2502
- body.appendChild(
2503
- createElement(
2504
- context.document,
2505
- 'span',
2506
- 'ok-native-list-warning-message',
2507
- row.message
2508
- )
2509
- );
2510
- return body;
2511
- }
2512
- if (row.variant === 'loading')
2513
- body.appendChild(
2514
- createElement(context.document, 'span', 'ok-native-list-spinner')
2515
- );
2516
- const message =
2517
- row.variant === 'spacer'
2518
- ? ''
2519
- : row.message ?? (row.variant === 'end' ? 'End' : '');
2520
- if (message)
2521
- body.appendChild(
2522
- createElement(
2523
- context.document,
2524
- 'span',
2525
- 'ok-native-list-secondary',
2526
- message
2527
- )
2528
- );
2529
- return body;
2530
- }
2531
-
2532
- function createRailRow(
2533
- context: RenderContext,
2534
- row: Extract<RowModel, { type: 'rail' }>
2535
- ): HTMLElement {
2536
- const body = createElement(
2537
- context.document,
2538
- 'div',
2539
- 'ok-native-list-row ok-native-list-rail'
2540
- );
2541
- const visual = createVisual(context, row.visual);
2542
- if (visual) body.appendChild(visual);
2543
- body.appendChild(
2544
- createElement(
2545
- context.document,
2546
- 'span',
2547
- 'ok-native-list-rail-title',
2548
- row.title
2549
- )
2550
- );
2551
- if (row.status && row.status !== 'none')
2552
- body.appendChild(
2553
- createElement(
2554
- context.document,
2555
- 'span',
2556
- 'ok-native-list-secondary',
2557
- row.status
2558
- )
2559
- );
2560
- if (row.badge) body.appendChild(createBadge(context, row.badge));
2561
- return body;
2562
- }
2563
-
2564
- function createMediaRow(
2565
- context: RenderContext,
2566
- row: Extract<RowModel, { type: 'mediaTile' }>
2567
- ): HTMLElement {
2568
- const body = createElement(
2569
- context.document,
2570
- 'div',
2571
- 'ok-native-list-row ok-native-list-media'
2572
- );
2573
- if (row.image && !row.imageState) {
2574
- const image = createImage(context, row.image, 'ok-native-list-media-image');
2575
- if (image) body.appendChild(image);
2576
- } else {
2577
- const placeholder = createElement(
2578
- context.document,
2579
- 'span',
2580
- 'ok-native-list-media-image',
2581
- row.imageState === 'error' ? '▧' : ''
2582
- );
2583
- setData(placeholder, 'state', row.imageState ?? 'empty');
2584
- body.appendChild(placeholder);
2585
- }
2586
- const metadata = createElement(
2587
- context.document,
2588
- 'div',
2589
- 'ok-native-list-media-meta'
2590
- );
2591
- const subtitleLine = createElement(
2592
- context.document,
2593
- 'div',
2594
- 'ok-native-list-media-subtitle-row'
2595
- );
2596
- subtitleLine.appendChild(
2597
- createElement(
2598
- context.document,
2599
- 'span',
2600
- 'ok-native-list-media-subtitle',
2601
- row.subtitle || '-'
2602
- )
2603
- );
2604
- if (row.networkImage) {
2605
- const network = createImage(
2606
- context,
2607
- row.networkImage,
2608
- 'ok-native-list-media-network'
2609
- );
2610
- if (network) subtitleLine.appendChild(network);
2611
- }
2612
- metadata.appendChild(subtitleLine);
2613
- metadata.appendChild(
2614
- createElement(
2615
- context.document,
2616
- 'div',
2617
- 'ok-native-list-media-title',
2618
- row.title
2619
- )
2620
- );
2621
- if (row.badge) metadata.appendChild(createBadge(context, row.badge));
2622
- body.appendChild(metadata);
2623
- if (row.closeActionKey) {
2624
- const close = createElement(
2625
- context.document,
2626
- 'button',
2627
- 'ok-native-list-media-close',
2628
- '×'
2629
- );
2630
- close.setAttribute('type', 'button');
2631
- close.setAttribute('aria-label', 'Close');
2632
- setData(close, 'nativeListAction', row.closeActionKey);
2633
- markActionAnchorSource(close, 'mediaClose');
2634
- body.appendChild(close);
2635
- }
2636
- return body;
2637
- }
2638
-
2639
- function createMetricCell(
2640
- context: RenderContext,
2641
- metric: NonNullable<
2642
- Extract<RowModel, { type: 'metricCard' }>['metrics']
2643
- >[number],
2644
- shaded: boolean
2645
- ): HTMLElement {
2646
- const cell = createElement(
2647
- context.document,
2648
- 'div',
2649
- 'ok-native-list-composite-cell'
2650
- );
2651
- setData(cell, 'shaded', shaded);
2652
- cell.appendChild(
2653
- createElement(
2654
- context.document,
2655
- 'div',
2656
- 'ok-native-list-secondary',
2657
- metric.label
2658
- )
2659
- );
2660
- const valueLine = createElement(context.document, 'div');
2661
- valueLine.style.display = 'flex';
2662
- valueLine.style.alignItems = 'center';
2663
- valueLine.style.gap = '6px';
2664
- if (metric.visual) {
2665
- const visual = createVisual(context, metric.visual);
2666
- if (visual) {
2667
- visual.style.width = '16px';
2668
- visual.style.height = '16px';
2669
- visual.style.flexBasis = '16px';
2670
- const image = visual.querySelector('img');
2671
- if (image) {
2672
- image.style.width = '16px';
2673
- image.style.height = '16px';
2674
- }
2675
- valueLine.appendChild(visual);
2676
- }
2677
- }
2678
- const value = createElement(
2679
- context.document,
2680
- 'span',
2681
- 'ok-native-list-composite-value',
2682
- metric.value
2683
- );
2684
- value.style.color = toneColor(metric.tone, 'primary');
2685
- valueLine.appendChild(value);
2686
- cell.appendChild(valueLine);
2687
- return cell;
2688
- }
2689
-
2690
- function createMetricRow(
2691
- context: RenderContext,
2692
- row: Extract<RowModel, { type: 'metricCard' }>
2693
- ): HTMLElement {
2694
- if (row.variant === 'activity' || row.variant === 'performance') {
2695
- const body = createElement(
2696
- context.document,
2697
- 'div',
2698
- 'ok-native-list-row ok-native-list-composite'
2699
- );
2700
- body.appendChild(
2701
- createElement(
2702
- context.document,
2703
- 'div',
2704
- 'ok-native-list-composite-heading',
2705
- row.title
2706
- )
2707
- );
2708
- const metrics = row.metrics ?? [];
2709
- const firstLine = createElement(
2710
- context.document,
2711
- 'div',
2712
- 'ok-native-list-composite-row'
2713
- );
2714
- metrics
2715
- .slice(0, 2)
2716
- .forEach((metric) =>
2717
- firstLine.appendChild(createMetricCell(context, metric, false))
2718
- );
2719
- body.appendChild(firstLine);
2720
- if (row.variant === 'activity') {
2721
- body.appendChild(
2722
- createElement(context.document, 'div', 'ok-native-list-divider')
2723
- );
2724
- } else {
2725
- const progress = createElement(
2726
- context.document,
2727
- 'div',
2728
- 'ok-native-list-progress'
2729
- );
2730
- const fill = createElement(context.document, 'span');
2731
- fill.style.width = String(Math.round((row.progress ?? 0) * 100)) + '%';
2732
- progress.appendChild(fill);
2733
- body.appendChild(progress);
2734
- }
2735
- const secondLine = createElement(
2736
- context.document,
2737
- 'div',
2738
- 'ok-native-list-composite-row'
2739
- );
2740
- metrics
2741
- .slice(2)
2742
- .forEach((metric) =>
2743
- secondLine.appendChild(
2744
- createMetricCell(context, metric, row.variant === 'performance')
2745
- )
2746
- );
2747
- body.appendChild(secondLine);
2748
- return body;
2749
- }
2750
-
2751
- const body = createElement(
2752
- context.document,
2753
- 'div',
2754
- 'ok-native-list-row ok-native-list-metric'
2755
- );
2756
- if (row.visual) {
2757
- const visual = createVisual(context, row.visual);
2758
- if (visual) body.appendChild(visual);
2759
- }
2760
- body.appendChild(
2761
- createElement(
2762
- context.document,
2763
- 'div',
2764
- 'ok-native-list-secondary',
2765
- row.title
2766
- )
2767
- );
2768
- body.appendChild(
2769
- createElement(
2770
- context.document,
2771
- 'div',
2772
- 'ok-native-list-metric-value',
2773
- row.value
2774
- )
2775
- );
2776
- if (row.trend) {
2777
- const trend = createElement(
2778
- context.document,
2779
- 'div',
2780
- 'ok-native-list-secondary',
2781
- row.trend
2782
- );
2783
- trend.style.color =
2784
- row.trendTone === 'positive'
2785
- ? 'var(--nl-positive)'
2786
- : row.trendTone === 'negative'
2787
- ? 'var(--nl-negative)'
2788
- : 'var(--nl-secondary)';
2789
- body.appendChild(trend);
2790
- }
2791
- if (row.subtitle)
2792
- body.appendChild(
2793
- createElement(
2794
- context.document,
2795
- 'div',
2796
- 'ok-native-list-secondary',
2797
- row.subtitle
2798
- )
2799
- );
2800
- if (row.badge) body.appendChild(createBadge(context, row.badge));
2801
- return body;
2802
- }
2803
-
2804
- function createDataRow(
2805
- context: RenderContext,
2806
- row: Extract<RowModel, { type: 'dataRow' }>
2807
- ): HTMLElement {
2808
- const body = createElement(
2809
- context.document,
2810
- 'div',
2811
- 'ok-native-list-row ok-native-list-data'
2812
- );
2813
- if (row.checkbox)
2814
- body.appendChild(createCheckbox(context, row.key, row.checkbox));
2815
- if (row.index !== undefined)
2816
- body.appendChild(
2817
- createElement(
2818
- context.document,
2819
- 'span',
2820
- 'ok-native-list-index',
2821
- String(row.index)
2822
- )
2823
- );
2824
- if (row.favorite) {
2825
- const favorite = createElement(
2826
- context.document,
2827
- 'span',
2828
- 'ok-native-list-favorite',
2829
- row.favoriteActive ? '★' : '☆'
2830
- );
2831
- setData(favorite, 'active', row.favoriteActive);
2832
- body.appendChild(favorite);
2833
- }
2834
- if (row.leading) {
2835
- const visual = createVisual(context, row.leading);
2836
- if (visual) body.appendChild(visual);
2837
- }
2838
- row.columns.forEach((column) => {
2839
- const cell = createElement(
2840
- context.document,
2841
- 'span',
2842
- 'ok-native-list-data-cell'
2843
- );
2844
- cell.style.flex = String(column.weight ?? 1);
2845
- setData(cell, 'align', column.alignment ?? 'start');
2846
- const primary = createElement(
2847
- context.document,
2848
- 'span',
2849
- 'ok-native-list-data-primary'
2850
- );
2851
- primary.style.color = toneColor(column.tone, 'primary');
2852
- if (column.secondaryLeadingText)
2853
- primary.appendChild(
2854
- createElement(
2855
- context.document,
2856
- 'span',
2857
- 'ok-native-list-secondary',
2858
- column.secondaryLeadingText
2859
- )
2860
- );
2861
- primary.appendChild(
2862
- createElement(context.document, 'span', undefined, column.text)
2863
- );
2864
- if (column.key === 'asset' && row.badges?.length) {
2865
- row.badges.forEach((badge) =>
2866
- primary.appendChild(createBadge(context, badge))
2867
- );
2868
- }
2869
- cell.appendChild(primary);
2870
- if (column.secondaryText) {
2871
- const secondary = createElement(
2872
- context.document,
2873
- 'span',
2874
- 'ok-native-list-secondary',
2875
- column.secondaryText
2876
- );
2877
- secondary.style.color = toneColor(column.secondaryTone, 'secondary');
2878
- cell.appendChild(secondary);
2879
- }
2880
- body.appendChild(cell);
2881
- });
2882
- return body;
2883
- }
2884
-
2885
- function createIdentityActivityOrMessageRow(
2886
- context: RenderContext,
2887
- row: Extract<RowModel, { type: 'identity' | 'activity' | 'message' }>
2888
- ): HTMLElement {
2889
- const presentation = row.type === 'identity' ? row.presentation : undefined;
2890
- const body = createElement(
2891
- context.document,
2892
- 'div',
2893
- [
2894
- 'ok-native-list-row',
2895
- 'ok-native-list-standard',
2896
- row.type === 'identity' && !presentation
2897
- ? 'ok-native-list-identity-row'
2898
- : '',
2899
- presentation === 'networkSelector' ? 'ok-native-list-network-row' : '',
2900
- presentation === 'walletSidebar' ? 'ok-native-list-wallet-row' : '',
2901
- presentation === 'accountSelector' ? 'ok-native-list-account-row' : '',
2902
- ]
2903
- .filter(Boolean)
2904
- .join(' ')
2905
- );
2906
- setData(
2907
- body,
2908
- 'nativeListSelector',
2909
- row.height !== undefined ? presentation : undefined
2910
- );
2911
- if (row.type === 'identity' && row.titleActionKey && row.titleActionOnHover) {
2912
- setData(body, 'nativeListHoverAction', row.titleActionKey);
2913
- markActionAnchorSource(body, 'leadingAction');
2914
- }
2915
- if (row.type === 'identity' && row.leadingAction) {
2916
- const action = createIconAction(
2917
- context,
2918
- row.leadingAction.name,
2919
- row.leadingAction.actionKey,
2920
- row.leadingAction.disabled,
2921
- row.leadingAction.tintColor
2922
- );
2923
- markActionAnchorSource(action, 'leadingAction');
2924
- body.appendChild(action);
2925
- }
2926
- const visual = createVisual(
2927
- context,
2928
- visualFromRow(row),
2929
- row.height !== undefined ? presentation : undefined
2930
- );
2931
- if (
2932
- visual &&
2933
- row.type === 'identity' &&
2934
- row.height !== undefined &&
2935
- row.presentation === 'walletSidebar' &&
2936
- 'fallbackIcon' in row.leading &&
2937
- row.leading.fallbackIcon?.name === 'LockSolid'
2938
- ) {
2939
- // OneKey patch: hidden-wallet locks use WalletAvatar's full 40-point icon.
2940
- const icon = visual.querySelector<SVGElement>(
2941
- '.ok-native-list-visual-fallback svg'
2942
- );
2943
- if (icon) {
2944
- icon.style.width = '40px';
2945
- icon.style.height = '40px';
2946
- }
2947
- const fallback = visual.querySelector<HTMLElement>(
2948
- '.ok-native-list-visual-fallback'
2949
- );
2950
- if (fallback) {
2951
- fallback.style.borderRadius = '0';
2952
- fallback.style.overflow = 'visible';
2953
- }
2954
- }
2955
- if (
2956
- visual &&
2957
- row.type === 'identity' &&
2958
- row.height !== undefined &&
2959
- row.presentation === 'walletSidebar' &&
2960
- 'borderStyle' in row.leading &&
2961
- row.leading.borderStyle === 'dashed'
2962
- )
2963
- visual.style.borderWidth = '1px';
2964
- if (visual) body.appendChild(visual);
2965
- if (row.type === 'activity' && row.secondaryLeading) {
2966
- const secondVisual = createVisual(context, row.secondaryLeading);
2967
- if (secondVisual) body.appendChild(secondVisual);
2968
- }
2969
- if (row.type === 'message' && row.unread)
2970
- body.appendChild(
2971
- createElement(context.document, 'span', 'ok-native-list-unread')
2972
- );
2973
- const title = row.title;
2974
- const subtitle =
2975
- row.type === 'identity'
2976
- ? row.subtitle
2977
- : row.type === 'activity'
2978
- ? row.description
2979
- : row.body;
2980
- const column = createTextColumn(
2981
- context,
2982
- title,
2983
- subtitle,
2984
- row.type === 'identity' ? row.tertiary : undefined,
2985
- row.type === 'identity' ? row.tertiaryTone : undefined,
2986
- row.type === 'identity' && presentation !== 'walletSidebar'
2987
- ? row.badges
2988
- : undefined
2989
- );
2990
- // OneKey patch: match existing search, subtitle fragments, and sidebar badges.
2991
- if (row.type === 'identity') {
2992
- const titleElement = column.firstElementChild as HTMLElement;
2993
- if (row.titleMatch?.length) {
2994
- const firstText = titleElement.firstChild;
2995
- if (firstText) firstText.remove();
2996
- const fragment = context.document.createDocumentFragment();
2997
- let offset = 0;
2998
- row.titleMatch.forEach(({ start, end }) => {
2999
- fragment.appendChild(
3000
- context.document.createTextNode(row.title.slice(offset, start))
3001
- );
3002
- const match = createElement(
3003
- context.document,
3004
- 'span',
3005
- 'ok-native-list-info',
3006
- row.title.slice(start, end)
3007
- );
3008
- fragment.appendChild(match);
3009
- offset = end;
3010
- });
3011
- fragment.appendChild(
3012
- context.document.createTextNode(row.title.slice(offset))
3013
- );
3014
- titleElement.prepend(fragment);
3015
- }
3016
- if (row.subtitleSegments?.length) {
3017
- column.querySelector('.ok-native-list-secondary')?.remove();
3018
- const segments = createElement(
3019
- context.document,
3020
- 'span',
3021
- 'ok-native-list-subtitle-segments'
3022
- );
3023
- row.subtitleSegments.forEach((segment) => {
3024
- if (segment.separatorBefore)
3025
- segments.appendChild(
3026
- createElement(
3027
- context.document,
3028
- 'span',
3029
- 'ok-native-list-subtitle-dot'
3030
- )
3031
- );
3032
- const text = createElement(
3033
- context.document,
3034
- 'span',
3035
- 'ok-native-list-secondary',
3036
- segment.text
3037
- );
3038
- applyValueSegments(text, segment.textSegments, 14, 20, 400);
3039
- setData(text, 'tone', segment.tone);
3040
- text.style.color =
3041
- segment.tone === 'disabled'
3042
- ? 'var(--nl-disabled)'
3043
- : segment.tone === 'caution'
3044
- ? 'var(--nl-caution)'
3045
- : toneColor(segment.tone, 'secondary');
3046
- segments.appendChild(text);
3047
- });
3048
- column.insertBefore(segments, titleElement.nextSibling);
3049
- }
3050
- if (presentation === 'walletSidebar' && row.badges?.length) {
3051
- const badges = createElement(
3052
- context.document,
3053
- 'span',
3054
- 'ok-native-list-wallet-badges'
3055
- );
3056
- row.badges.forEach((badge) =>
3057
- badges.appendChild(createBadge(context, badge))
3058
- );
3059
- column.appendChild(badges);
3060
- }
3061
- }
3062
- if (row.type === 'activity' && row.status)
3063
- column.appendChild(
3064
- createElement(
3065
- context.document,
3066
- 'span',
3067
- 'ok-native-list-secondary',
3068
- row.status
3069
- )
3070
- );
3071
- if (row.type === 'activity' && row.footerActions?.length) {
3072
- const actions = createElement(
3073
- context.document,
3074
- 'span',
3075
- 'ok-native-list-actions'
3076
- );
3077
- row.footerActions.forEach((action, slot) => {
3078
- const button = createElement(
3079
- context.document,
3080
- 'button',
3081
- 'ok-native-list-action-button',
3082
- action.label
3083
- );
3084
- button.setAttribute('type', 'button');
3085
- button.toggleAttribute('disabled', Boolean(action.disabled));
3086
- setData(button, 'tone', action.tone);
3087
- setData(button, 'nativeListAction', action.key);
3088
- markActionAnchorSource(button, 'footerAction', slot);
3089
- actions.appendChild(button);
3090
- });
3091
- column.appendChild(actions);
3092
- }
3093
- body.appendChild(column);
3094
- if (row.type === 'activity') {
3095
- const amounts = createElement(
3096
- context.document,
3097
- 'span',
3098
- 'ok-native-list-amounts'
3099
- );
3100
- if (row.primaryAmount)
3101
- amounts.appendChild(
3102
- createElement(
3103
- context.document,
3104
- 'span',
3105
- 'ok-native-list-value',
3106
- row.primaryAmount
3107
- )
3108
- );
3109
- if (row.secondaryAmount)
3110
- amounts.appendChild(
3111
- createElement(
3112
- context.document,
3113
- 'span',
3114
- 'ok-native-list-secondary',
3115
- row.secondaryAmount
3116
- )
3117
- );
3118
- body.appendChild(amounts);
3119
- } else if (row.type === 'message') {
3120
- body.appendChild(
3121
- createElement(context.document, 'span', 'ok-native-list-time', row.time)
3122
- );
3123
- if (row.thumbnail) {
3124
- const thumbnail = createImage(
3125
- context,
3126
- row.thumbnail,
3127
- 'ok-native-list-thumbnail'
3128
- );
3129
- if (thumbnail) body.appendChild(thumbnail);
1728
+ );
1729
+ element.setAttribute('type', 'button');
1730
+ element.setAttribute('role', 'checkbox');
1731
+ element.setAttribute(
1732
+ 'aria-checked',
1733
+ state === 'indeterminate' ? 'mixed' : String(state === 'checked')
1734
+ );
1735
+ element.setAttribute('aria-label', 'Select');
1736
+ element.toggleAttribute('disabled', Boolean(accessory.disabled));
1737
+ setData(element, 'state', state);
1738
+ setData(element, 'checkboxFallback', accessory.state);
1739
+ setData(element, 'nativeListAction', accessory.actionKey ?? 'selection');
1740
+ setData(element, 'selectionScope', accessory.target?.scope ?? 'row');
1741
+ const row = context.snapshot.rows[context.itemIndex];
1742
+ if (row && 'presentation' in row && row.presentation === 'networkSelector') {
1743
+ setData(element, 'selector', 'networkSelector');
1744
+ for (const [state, name] of [
1745
+ ['checked', 'CheckboxCheckedCustom'],
1746
+ ['indeterminate', 'CheckboxIndeterminateCustom'],
1747
+ ]) {
1748
+ const holder = createElement(context.document, 'span');
1749
+ applySelectorIcon(holder, name);
1750
+ const svg = holder.firstElementChild;
1751
+ if (svg) {
1752
+ svg.setAttribute('data-state', state);
1753
+ element.appendChild(svg);
1754
+ }
3130
1755
  }
3131
- } else {
3132
- appendAccessories(body, context, row.key, row.trailing);
3133
1756
  }
3134
- return body;
3135
- }
3136
-
3137
- // OneKey patch: SizableText enables tabular digits without replacing its font family.
3138
- function applySelectorTabularNumbers(body: HTMLElement, row: RowModel) {
3139
- if (
3140
- !('presentation' in row) ||
3141
- !['accountSelector', 'networkSelector', 'walletSidebar'].includes(
3142
- row.presentation ?? ''
3143
- )
3144
- )
3145
- return;
3146
- body.style.fontVariantNumeric = 'tabular-nums';
3147
- body.querySelectorAll<HTMLElement>('span,button').forEach((text) => {
3148
- text.style.fontVariantNumeric = 'tabular-nums';
3149
- });
1757
+ if (accessory.target?.scope === 'section')
1758
+ setData(element, 'selectionKey', accessory.target.sectionKey);
1759
+ else if (accessory.target?.scope === 'row')
1760
+ setData(element, 'selectionKey', rowKey);
1761
+ return element;
3150
1762
  }
3151
1763
 
3152
- function createWalletGroupRow(
1764
+ function createIconAction(
3153
1765
  context: RenderContext,
3154
- row: Extract<RowModel, { type: 'walletGroup' }>
1766
+ name: string,
1767
+ actionKey: string | undefined,
1768
+ disabled?: boolean,
1769
+ tintColor?: string
3155
1770
  ): HTMLElement {
3156
- const body = createElement(
1771
+ const element = createElement(
3157
1772
  context.document,
3158
- 'div',
3159
- 'ok-native-list-wallet-group'
1773
+ actionKey ? 'button' : 'span',
1774
+ 'ok-native-list-icon-button',
1775
+ iconGlyph(name)
3160
1776
  );
3161
- [row.parent, ...row.children].forEach((member, memberIndex) => {
3162
- const memberElement = createElement(
3163
- context.document,
3164
- 'div',
3165
- 'ok-native-list-wallet-member'
3166
- );
3167
- setData(memberElement, 'nativeListGroupMemberKey', member.key);
3168
- setData(memberElement, 'testid', member.testID);
3169
- setData(memberElement, 'nativeListGroupParent', memberIndex === 0);
3170
- setData(memberElement, 'nativeListSelected', member.selected);
3171
- // OneKey patch: grouped members have the same selector typography as standalone wallets.
3172
- // memberElement.appendChild(createIdentityActivityOrMessageRow(context, member));
3173
- const memberBody = createIdentityActivityOrMessageRow(context, member);
3174
- applySelectorTabularNumbers(memberBody, member);
3175
- memberElement.appendChild(memberBody);
3176
- // OneKey patch: group children use their own measured badge height.
3177
- memberElement.style.flexBasis =
3178
- String(member.height ?? 68 + (member.badges?.length ? 24 : 0)) + 'px';
3179
- memberElement.style.height = memberElement.style.flexBasis;
3180
- memberElement.style.opacity = String(member.opacity ?? 1);
3181
- body.appendChild(memberElement);
3182
- });
3183
- return body;
3184
- }
3185
-
3186
- function marketFontWeight(
3187
- value: MarketTextStyle['fontWeight'] | undefined,
3188
- fallback: number
3189
- ): number {
3190
- if (value === 'bold') return 700;
3191
- if (value === 'semibold') return 600;
3192
- if (value === 'medium') return 500;
3193
- if (value === 'regular') return 400;
3194
- return fallback;
1777
+ if (element.tagName === 'BUTTON') {
1778
+ element.setAttribute('type', 'button');
1779
+ element.toggleAttribute('disabled', Boolean(disabled));
1780
+ }
1781
+ applySelectorIcon(element, name);
1782
+ if (actionKey) setData(element, 'nativeListAction', actionKey);
1783
+ if (tintColor) element.style.color = tintColor;
1784
+ return element;
3195
1785
  }
3196
1786
 
3197
- function applyMarketTextStyle(
1787
+ function markActionAnchorSource(
3198
1788
  element: HTMLElement,
3199
- style: MarketTextStyle | undefined,
3200
- defaults: Readonly<{
3201
- fontSize: number;
3202
- lineHeight: number;
3203
- weight: number;
3204
- alignment: 'start' | 'center' | 'end';
3205
- }>
1789
+ source: NativeListActionSource,
1790
+ slot?: number
3206
1791
  ) {
3207
- element.style.fontSize = String(style?.fontSize ?? defaults.fontSize) + 'px';
3208
- element.style.lineHeight =
3209
- String(style?.lineHeight ?? defaults.lineHeight) + 'px';
3210
- element.style.fontWeight = String(
3211
- marketFontWeight(style?.fontWeight, defaults.weight)
3212
- );
3213
- element.style.color = style?.color ?? '';
3214
- element.style.textAlign = style?.alignment ?? defaults.alignment;
3215
- element.style.whiteSpace = style?.lines === 2 ? 'normal' : 'nowrap';
3216
- element.style.display = '';
3217
- element.style.removeProperty('-webkit-line-clamp');
3218
- element.style.removeProperty('-webkit-box-orient');
3219
- if (style?.lines === 2) {
3220
- element.style.display = '-webkit-box';
3221
- element.style.setProperty('-webkit-line-clamp', '2');
3222
- element.style.setProperty('-webkit-box-orient', 'vertical');
3223
- }
1792
+ setData(element, 'nativeListAnchorSource', source);
1793
+ if (slot !== undefined) setData(element, 'nativeListAnchorSlot', slot);
3224
1794
  }
3225
1795
 
3226
- function setMarketQuoteContent(element: HTMLElement, row: MarketRow) {
3227
- const style = row.style;
3228
- const layoutStyle = resolveWebMarketLayoutStyle(row);
3229
- const price = element.querySelector<HTMLElement>(
3230
- '.ok-native-list-market-price'
3231
- );
3232
- if (price) {
3233
- price.textContent = row.price;
3234
- applyMarketTextStyle(price, style?.price, {
3235
- fontSize: 16,
3236
- lineHeight: 24,
3237
- weight: 500,
3238
- alignment: 'end',
3239
- });
3240
- applyValueSegments(
3241
- price,
3242
- row.priceSegments,
3243
- style?.price?.fontSize ?? 16,
3244
- style?.price?.lineHeight ?? 24,
3245
- marketFontWeight(style?.price?.fontWeight, 500)
3246
- );
3247
- }
3248
- const change = element.querySelector<HTMLElement>(
3249
- '.ok-native-list-market-change'
3250
- );
3251
- if (change) {
3252
- change.textContent = row.change.text;
3253
- setData(change, 'tone', row.change.tone);
3254
- applyMarketTextStyle(change, style?.change, {
3255
- fontSize: 14,
3256
- lineHeight: 20,
3257
- weight: 500,
3258
- alignment: 'center',
3259
- });
3260
- applyValueSegments(
3261
- change,
3262
- row.change.textSegments,
3263
- style?.change?.fontSize ?? 14,
3264
- style?.change?.lineHeight ?? 20,
3265
- marketFontWeight(style?.change?.fontWeight, 500)
3266
- );
3267
- change.style.width = String(layoutStyle.changeWidth) + 'px';
3268
- change.style.height = String(layoutStyle.changeHeight) + 'px';
3269
- change.style.borderRadius = String(layoutStyle.changeCornerRadius) + 'px';
3270
- change.style.color = row.change.textColor ?? style?.change?.color ?? '';
3271
- change.style.background = row.change.backgroundColor ?? '';
3272
- }
3273
- element.setAttribute(
3274
- 'aria-label',
3275
- row.accessibilityLabel ??
3276
- [row.title, row.subtitle, row.price, row.change.text]
3277
- .filter(Boolean)
3278
- .join(', ')
3279
- );
3280
- }
1796
+ // OneKey patch: the compact zero-count digits share the amount baseline.
3281
1797
 
3282
- function createMarketRow(context: RenderContext, row: MarketRow): HTMLElement {
3283
- const body = createElement(
3284
- context.document,
3285
- 'div',
3286
- 'ok-native-list-row ok-native-list-market'
3287
- );
3288
- setData(body, 'variant', row.variant);
3289
- const style = row.style;
3290
- const layoutStyle = resolveWebMarketLayoutStyle(row);
3291
- body.style.padding =
3292
- String(layoutStyle.verticalPadding) +
3293
- 'px ' +
3294
- String(layoutStyle.horizontalPadding) +
3295
- 'px';
3296
- body.style.gap = '0px';
3297
- if (row.leadingAction) {
3298
- const action = createIconAction(
1798
+ function createAccessory(
1799
+ context: RenderContext,
1800
+ rowKey: string,
1801
+ accessory: TrailingAccessory,
1802
+ slot: number
1803
+ ): HTMLElement {
1804
+ if (accessory.kind === 'checkbox') {
1805
+ const element = createCheckbox(context, rowKey, accessory);
1806
+ markActionAnchorSource(element, 'trailingAccessory', slot);
1807
+ return element;
1808
+ }
1809
+ if (accessory.kind === 'icon') {
1810
+ const element = createIconAction(
3299
1811
  context,
3300
- row.leadingAction.name,
3301
- row.leadingAction.actionKey,
3302
- row.leadingAction.disabled,
3303
- row.leadingAction.tintColor
1812
+ accessory.name,
1813
+ accessory.actionKey,
1814
+ accessory.disabled,
1815
+ accessory.tintColor
3304
1816
  );
3305
- action.style.flex = '0 0 36px';
3306
- action.style.width = '36px';
3307
- action.style.height = '36px';
3308
- action.style.marginRight = '5px';
3309
- setData(action, 'testid', row.leadingAction.testID);
3310
- if (row.leadingAction.accessibilityLabel)
3311
- action.setAttribute('aria-label', row.leadingAction.accessibilityLabel);
3312
- markActionAnchorSource(action, 'leadingAction');
3313
- body.appendChild(action);
3314
- }
3315
- const visual = createVisual(context, row.leading);
3316
- if (visual) {
3317
- const width = layoutStyle.imageWidth;
3318
- const height = layoutStyle.imageHeight;
3319
- visual.style.width = String(width) + 'px';
3320
- visual.style.height = String(height) + 'px';
3321
- visual.style.flexBasis = String(width) + 'px';
3322
- visual.style.borderRadius = String(layoutStyle.imageCornerRadius) + 'px';
3323
- const borderColor =
3324
- 'borderColor' in row.leading ? row.leading.borderColor : undefined;
3325
- if (borderColor) {
3326
- visual.style.border = '1px solid ' + borderColor;
3327
- visual.style.boxSizing = 'border-box';
3328
- }
3329
- visual.querySelectorAll<HTMLElement>('img').forEach((image) => {
3330
- if (!image.classList.contains('ok-native-list-visual-corner')) {
3331
- image.style.width = '100%';
3332
- image.style.height = '100%';
3333
- if (borderColor) {
3334
- image.style.borderRadius = '0';
3335
- image.style.clipPath =
3336
- 'inset(-1px round ' + String(layoutStyle.imageCornerRadius) + 'px)';
3337
- }
3338
- image.style.objectFit =
3339
- style?.image?.contentFit ?? image.style.objectFit;
3340
- } else {
3341
- image.style.width = '20px';
3342
- image.style.height = '20px';
3343
- if (borderColor) {
3344
- image.style.right = '-5px';
3345
- image.style.bottom = '-5px';
3346
- }
1817
+ markActionAnchorSource(element, 'trailingAccessory', slot);
1818
+ setData(element, 'testid', accessory.testID);
1819
+ if (accessory.hoverActionKey)
1820
+ setData(element, 'nativeListHoverAction', accessory.hoverActionKey);
1821
+ if (accessory.accessibilityLabel)
1822
+ element.setAttribute('aria-label', accessory.accessibilityLabel);
1823
+ const row = context.snapshot.rows[context.itemIndex];
1824
+ if (
1825
+ row &&
1826
+ 'presentation' in row &&
1827
+ row.presentation === 'accountSelector'
1828
+ ) {
1829
+ setData(element, 'nativeListAnchorInset', 7);
1830
+ // OneKey patch: the create-address button omits IconButton's one-point border.
1831
+ if (hasExplicitRowHeight(row) && accessory.name === 'PlusSmallOutline') {
1832
+ setData(element, 'nativeListAccountControl', 'createAddress');
1833
+ if (!accessory.tintColor)
1834
+ element.style.color = 'var(--nl-icon-subdued)';
3347
1835
  }
3348
- });
3349
- visual.style.marginRight = String(layoutStyle.leadingGap) + 'px';
3350
- body.appendChild(visual);
1836
+ }
1837
+ return element;
3351
1838
  }
3352
- const main = createElement(
3353
- context.document,
3354
- 'span',
3355
- 'ok-native-list-market-main'
3356
- );
3357
- main.style.gap = String(style?.lineGap ?? 0) + 'px';
3358
- const titleLine = createElement(
3359
- context.document,
3360
- 'span',
3361
- 'ok-native-list-market-title-line'
3362
- );
3363
- titleLine.style.gap = String(layoutStyle.titleBadgeGap) + 'px';
3364
- const title = createElement(
1839
+ if (accessory.kind === 'spinner') {
1840
+ return createElement(context.document, 'span', 'ok-native-list-spinner');
1841
+ }
1842
+ const actionKey = 'actionKey' in accessory ? accessory.actionKey : undefined;
1843
+ const element = createElement(
3365
1844
  context.document,
3366
- 'span',
3367
- 'ok-native-list-market-title',
3368
- row.title
1845
+ actionKey ? 'button' : 'span',
1846
+ 'ok-native-list-accessory'
3369
1847
  );
3370
- applyMarketTextStyle(title, style?.title, {
3371
- fontSize: 16,
3372
- lineHeight: 24,
3373
- weight: 500,
3374
- alignment: 'start',
3375
- });
3376
- titleLine.appendChild(title);
3377
- row.badges?.forEach((badge, slot) => {
3378
- const element = createElement(
3379
- context.document,
3380
- badge.actionKey ? 'button' : 'span',
3381
- 'ok-native-list-market-badge',
3382
- badge.text
3383
- );
3384
- if (badge.actionKey) {
3385
- element.setAttribute('type', 'button');
3386
- setData(element, 'nativeListAction', badge.actionKey);
3387
- markActionAnchorSource(element, 'marketBadge', slot);
3388
- }
3389
- setData(element, 'tone', badge.tone);
3390
- element.style.color =
3391
- badge.textColor ??
3392
- (badge.tone === 'success'
3393
- ? 'var(--nl-positive)'
3394
- : badge.tone === 'danger'
3395
- ? 'var(--nl-negative)'
3396
- : badge.tone === 'info'
3397
- ? 'var(--nl-info)'
3398
- : badge.tone === 'warning'
3399
- ? 'var(--nl-primary)'
3400
- : '');
3401
- element.style.background =
3402
- badge.backgroundColor ??
3403
- ((badge.icon || badge.iconName) && !badge.text ? 'transparent' : '');
3404
- if ((badge.icon || badge.iconName) && !badge.text) {
3405
- element.style.padding = '0';
3406
- }
3407
- if (badge.accessibilityLabel)
3408
- element.setAttribute('aria-label', badge.accessibilityLabel);
3409
- if (badge.icon) {
3410
- const icon = createImage(context, badge.icon);
3411
- if (icon) {
3412
- icon.style.borderRadius = '50%';
3413
- element.prepend(icon);
3414
- }
3415
- }
3416
- if (badge.iconName === 'verified') {
3417
- const icon = createElement(
3418
- context.document,
3419
- 'span',
3420
- 'ok-native-list-market-badge-icon'
3421
- );
3422
- applySelectorIcon(icon, 'BadgeVerifiedSolid');
3423
- element.prepend(icon);
3424
- }
3425
- titleLine.appendChild(element);
3426
- });
3427
- main.appendChild(titleLine);
3428
- if (row.subtitlePrefix || row.subtitle || row.subtitleSegments?.length) {
3429
- const subtitleLine = createElement(
3430
- context.document,
3431
- 'span',
3432
- 'ok-native-list-market-subtitle-line'
3433
- );
3434
- subtitleLine.style.display = 'flex';
3435
- subtitleLine.style.alignItems = 'center';
3436
- subtitleLine.style.minWidth = '0';
3437
- subtitleLine.style.overflow = 'hidden';
3438
- subtitleLine.style.gap =
3439
- String(row.subtitlePrefix ? row.subtitlePrefix.gap ?? 4 : 0) + 'px';
3440
- if (row.subtitlePrefix) {
3441
- const prefix = createElement(
1848
+ if (element.tagName === 'BUTTON') {
1849
+ element.setAttribute('type', 'button');
1850
+ element.classList.add('ok-native-list-icon-button');
1851
+ setData(element, 'nativeListAction', actionKey);
1852
+ }
1853
+ switch (accessory.kind) {
1854
+ case 'value':
1855
+ element.textContent = accessory.text;
1856
+ applyValueSegments(element, accessory.textSegments);
1857
+ if (accessory.secondary)
1858
+ element.classList.add('ok-native-list-accessory-secondary');
1859
+ break;
1860
+ case 'valuePair': {
1861
+ const primary = createElement(
3442
1862
  context.document,
3443
1863
  'span',
3444
- 'ok-native-list-market-subtitle-prefix',
3445
- row.subtitlePrefix.text
1864
+ undefined,
1865
+ accessory.primary
3446
1866
  );
3447
- applyMarketTextStyle(prefix, row.subtitlePrefix.style, {
3448
- fontSize: 12,
3449
- lineHeight: 16,
3450
- weight: 400,
3451
- alignment: 'start',
3452
- });
3453
- prefix.style.display = 'block';
3454
- prefix.style.flex = '1 1 auto';
3455
- prefix.style.minWidth = '0';
3456
- prefix.style.overflow = 'hidden';
3457
- prefix.style.textOverflow = 'ellipsis';
3458
- prefix.style.color =
3459
- row.subtitlePrefix.style?.color ?? 'var(--nl-secondary)';
3460
- if (row.subtitlePrefix.maxWidth !== undefined) {
3461
- prefix.style.maxWidth = String(row.subtitlePrefix.maxWidth) + 'px';
3462
- }
3463
- subtitleLine.appendChild(prefix);
3464
- }
3465
- if (row.subtitle || row.subtitleSegments?.length) {
3466
- const subtitle = createElement(
1867
+ primary.style.color = toneColor(accessory.primaryTone, 'primary');
1868
+ const secondary = createElement(
3467
1869
  context.document,
3468
1870
  'span',
3469
- 'ok-native-list-market-subtitle',
3470
- row.subtitle
3471
- );
3472
- applyMarketTextStyle(subtitle, style?.subtitle, {
3473
- fontSize: 14,
3474
- lineHeight: 20,
3475
- weight: 400,
3476
- alignment: 'start',
3477
- });
3478
- applyValueSegments(
3479
- subtitle,
3480
- row.subtitleSegments,
3481
- style?.subtitle?.fontSize ?? 14,
3482
- style?.subtitle?.lineHeight ?? 20,
3483
- marketFontWeight(style?.subtitle?.fontWeight, 400)
1871
+ 'ok-native-list-secondary',
1872
+ accessory.secondary
3484
1873
  );
3485
- subtitle.style.flex = row.subtitlePrefix ? '0 0 auto' : '1 1 auto';
3486
- subtitleLine.appendChild(subtitle);
1874
+ secondary.style.color = toneColor(accessory.secondaryTone, 'secondary');
1875
+ element.replaceChildren(primary, '\n', secondary);
1876
+ element.classList.add('ok-native-list-amounts');
1877
+ break;
3487
1878
  }
3488
- main.appendChild(subtitleLine);
1879
+ case 'radio':
1880
+ element.textContent = accessory.checked ? '●' : '○';
1881
+ break;
1882
+ case 'switch':
1883
+ element.textContent = accessory.value ? 'ON' : 'OFF';
1884
+ setData(element, 'nativeListAction', accessory.actionKey);
1885
+ break;
1886
+ case 'chevron':
1887
+ element.textContent = '›';
1888
+ break;
1889
+ case 'menu':
1890
+ element.textContent = '⋮';
1891
+ break;
1892
+ case 'drag':
1893
+ element.textContent = '≡';
1894
+ break;
1895
+ case 'progress':
1896
+ element.textContent = String(Math.round(accessory.value * 100)) + '%';
1897
+ break;
3489
1898
  }
3490
- body.appendChild(main);
3491
- const trailing = createElement(
3492
- context.document,
3493
- 'span',
3494
- 'ok-native-list-market-trailing'
3495
- );
3496
- trailing.style.gap = String(layoutStyle.trailingGap) + 'px';
3497
- trailing.append(
3498
- createElement(context.document, 'span', 'ok-native-list-market-price'),
3499
- createElement(context.document, 'span', 'ok-native-list-market-change')
3500
- );
3501
- body.appendChild(trailing);
3502
- setMarketQuoteContent(body, row);
3503
- return body;
1899
+ if (actionKey) markActionAnchorSource(element, 'trailingAccessory', slot);
1900
+ return element;
3504
1901
  }
3505
1902
 
3506
- function createRowBody(context: RenderContext, row: RowModel): HTMLElement {
3507
- switch (row.type) {
3508
- case 'walletGroup':
3509
- return createWalletGroupRow(context, row);
3510
- case 'sectionHeader':
3511
- return createSectionHeader(context, row);
3512
- case 'action':
3513
- return createActionRow(context, row);
3514
- case 'system':
3515
- return createSystemRow(context, row);
3516
- case 'rail':
3517
- return createRailRow(context, row);
3518
- case 'mediaTile':
3519
- return createMediaRow(context, row);
3520
- case 'metricCard':
3521
- return createMetricRow(context, row);
3522
- case 'dataRow':
3523
- return createDataRow(context, row);
3524
- case 'market':
3525
- return createMarketRow(context, row);
3526
- case 'identity':
3527
- case 'activity':
3528
- case 'message':
3529
- return createIdentityActivityOrMessageRow(context, row);
3530
- }
1903
+ function rendererPrimitives(context: RenderContext) {
1904
+ return {
1905
+ background: resolvedTheme(context.snapshot).background,
1906
+ accessory: (key: string, descriptor: TrailingAccessory, slot: number) =>
1907
+ createAccessory(context, key, descriptor, slot),
1908
+ visual: (source: LeadingVisual | undefined, presentation?: string) =>
1909
+ createVisual(context, source, presentation),
1910
+ thumbnail: (source: ImageSource) =>
1911
+ createImage(context, source, 'ok-native-list-thumbnail'),
1912
+ textStyle: applyTextStyleToSlot,
1913
+ icon: applySelectorIcon,
1914
+ iconAction: (
1915
+ name: string,
1916
+ actionKey: string | undefined,
1917
+ disabled?: boolean,
1918
+ tintColor?: string
1919
+ ) => createIconAction(context, name, actionKey, disabled, tintColor),
1920
+ dispose: disposeWebImageRetries,
1921
+ };
1922
+ }
1923
+
1924
+ export function createRowBody(
1925
+ context: RenderContext,
1926
+ row: RowModel
1927
+ ): HTMLElement {
1928
+ return rowRenderer(row).create(context.document, rendererPrimitives(context));
3531
1929
  }
3532
1930
 
3533
1931
  export class NativeListWebEngine {
@@ -3554,7 +1952,21 @@ export class NativeListWebEngine {
3554
1952
  horizontal: false,
3555
1953
  };
3556
1954
  private readonly mounted = new Map<number, HTMLElement>();
3557
- private readonly pool: HTMLElement[] = [];
1955
+ private readonly pool: Record<RowRendererKey, HTMLElement[]> = {
1956
+ walletGroup: [],
1957
+ message: [],
1958
+ rail: [],
1959
+ mediaTile: [],
1960
+ action: [],
1961
+ system: [],
1962
+ activity: [],
1963
+ dataRow: [],
1964
+ metricCard: [],
1965
+ market: [],
1966
+ identity: [],
1967
+ sectionHeader: [],
1968
+ };
1969
+ private readonly rendererKeys = new WeakMap<HTMLElement, RowRendererKey>();
3558
1970
  private frameHandle: number | undefined;
3559
1971
  private resizeObserver: ResizeObserver | undefined;
3560
1972
  private sectionIndexTransformObserver: MutationObserver | undefined;
@@ -3607,8 +2019,11 @@ export class NativeListWebEngine {
3607
2019
  private lastViewportHeight = -1;
3608
2020
  private destroyed = false;
3609
2021
  private sectionIndexAlignStart = false;
3610
- // OneKey patch: warning banners are measured after normal browser text wrapping.
3611
- private measuredWarningHeights = new Map<string, number>();
2022
+ // Renderers report intrinsic height after browser text wrapping.
2023
+ private measuredRendererHeights = new Map<
2024
+ string,
2025
+ { signature: string; height: number }
2026
+ >();
3612
2027
 
3613
2028
  constructor(
3614
2029
  host: HTMLElement,
@@ -3996,14 +2411,18 @@ export class NativeListWebEngine {
3996
2411
  this.viewport.removeEventListener('pointercancel', this.handlePullEnd);
3997
2412
  const host = this.root.parentElement;
3998
2413
  disposeWebImageRetries(this.root);
3999
- this.pool.forEach(disposeWebImageRetries);
2414
+ Object.values(this.pool).forEach((elements) =>
2415
+ elements.forEach(disposeWebImageRetries)
2416
+ );
4000
2417
  this.root.remove();
4001
2418
  this.indexRail.remove();
4002
2419
  this.hideReorderPreview();
4003
2420
  this.reorderPreview.remove();
4004
2421
  if (host) host.style.position = this.previousHostPosition;
4005
2422
  this.mounted.clear();
4006
- this.pool.length = 0;
2423
+ Object.values(this.pool).forEach((elements) => {
2424
+ elements.length = 0;
2425
+ });
4007
2426
  this.avatarLeases.forEach((release) => release());
4008
2427
  this.avatarLeases.clear();
4009
2428
  }
@@ -4013,7 +2432,7 @@ export class NativeListWebEngine {
4013
2432
  selectedKeys?: ReadonlySet<string>
4014
2433
  ) {
4015
2434
  this.cancelMarketPointer();
4016
- this.measuredWarningHeights.clear();
2435
+ this.measuredRendererHeights.clear();
4017
2436
  this.snapshot = snapshot;
4018
2437
  this.rows = effectiveRows(snapshot);
4019
2438
  this.selectedKeys =
@@ -4060,6 +2479,31 @@ export class NativeListWebEngine {
4060
2479
  this.indexRail.style.setProperty(name, value);
4061
2480
  this.reorderPreview.style.setProperty(name, value);
4062
2481
  });
2482
+ this.applyListStyle();
2483
+ }
2484
+
2485
+ /**
2486
+ * List chrome from `snapshot.listStyle`. Every value is absent by default and
2487
+ * the stylesheet carries today's number as the fallback, so an untouched list
2488
+ * renders exactly as before. See docs/STYLE_SPEC.md §5.
2489
+ */
2490
+ private applyListStyle() {
2491
+ const listStyle = this.snapshot.listStyle;
2492
+ const inset = listStyle?.separator?.inset;
2493
+ setData(this.root, 'separatorInset', inset !== undefined && inset > 0);
2494
+ const chrome: Readonly<Record<string, string | undefined>> = {
2495
+ '--nl-separator-inset':
2496
+ inset === undefined ? undefined : String(inset) + 'px',
2497
+ '--nl-separator-color': listStyle?.separator?.color,
2498
+ '--nl-group-radius':
2499
+ listStyle?.groupCornerRadius === undefined
2500
+ ? undefined
2501
+ : String(listStyle.groupCornerRadius) + 'px',
2502
+ };
2503
+ Object.entries(chrome).forEach(([name, value]) => {
2504
+ if (value === undefined) this.root.style.removeProperty(name);
2505
+ else this.root.style.setProperty(name, value);
2506
+ });
4063
2507
  }
4064
2508
 
4065
2509
  private recomputeLayout = () => {
@@ -4086,7 +2530,7 @@ export class NativeListWebEngine {
4086
2530
  viewportHeight !== this.lastViewportHeight)
4087
2531
  ) {
4088
2532
  this.invalidateActionAnchor('layout');
4089
- this.measuredWarningHeights.clear();
2533
+ this.measuredRendererHeights.clear();
4090
2534
  }
4091
2535
  this.lastViewportWidth = viewportWidth;
4092
2536
  this.lastViewportHeight = viewportHeight;
@@ -4094,11 +2538,14 @@ export class NativeListWebEngine {
4094
2538
  const measuredSnapshot: NativeListSnapshot = {
4095
2539
  ...this.snapshot,
4096
2540
  rows: this.snapshot.rows.map((row) =>
4097
- row.type === 'system' &&
4098
- row.variant === 'warning' &&
2541
+ row.style?.container?.height === undefined &&
4099
2542
  row.height === undefined &&
4100
- this.measuredWarningHeights.has(row.key)
4101
- ? { ...row, height: this.measuredWarningHeights.get(row.key) }
2543
+ this.measuredRendererHeights.get(row.key)?.signature ===
2544
+ webRowRenderSignature(row)
2545
+ ? {
2546
+ ...row,
2547
+ height: this.measuredRendererHeights.get(row.key)!.height,
2548
+ }
4102
2549
  : row
4103
2550
  ),
4104
2551
  };
@@ -4190,13 +2637,22 @@ export class NativeListWebEngine {
4190
2637
  );
4191
2638
  const desired = new Set(visible.map((item) => item.index));
4192
2639
  this.mounted.forEach((element, index) => {
4193
- if (!desired.has(index)) {
2640
+ const nextRow = this.rows[index];
2641
+ const rendererKey = this.rendererKeys.get(element);
2642
+ if (
2643
+ !desired.has(index) ||
2644
+ !nextRow ||
2645
+ rendererKey !== rowRendererKey(nextRow)
2646
+ ) {
4194
2647
  this.invalidateActionAnchorForElement(element);
4195
2648
  this.mounted.delete(index);
4196
- if (disposeWebImageRetries(element))
2649
+ const recycled = element.firstElementChild
2650
+ ? recycleRowBody(element.firstElementChild as HTMLElement)
2651
+ : false;
2652
+ if (disposeWebImageRetries(element) || recycled)
4197
2653
  element.removeAttribute('data-render-signature');
4198
2654
  element.remove();
4199
- this.pool.push(element);
2655
+ if (rendererKey) this.pool[rendererKey].push(element);
4200
2656
  }
4201
2657
  });
4202
2658
 
@@ -4205,9 +2661,11 @@ export class NativeListWebEngine {
4205
2661
  if (!row) return;
4206
2662
  let element = this.mounted.get(layoutItem.index);
4207
2663
  if (!element) {
2664
+ const rendererKey = rowRendererKey(row);
4208
2665
  element =
4209
- this.pool.pop() ??
2666
+ this.pool[rendererKey].pop() ??
4210
2667
  createElement(this.document, 'div', 'ok-native-list-item');
2668
+ this.rendererKeys.set(element, rendererKey);
4211
2669
  setData(element, 'nativeListAnimateReorder', false);
4212
2670
  this.mounted.set(layoutItem.index, element);
4213
2671
  this.content.appendChild(element);
@@ -4222,24 +2680,30 @@ export class NativeListWebEngine {
4222
2680
  element.dataset.renderSignature = signature;
4223
2681
  }
4224
2682
  });
4225
- let measuredWarningChanged = false;
2683
+ let measuredHeightChanged = false;
4226
2684
  this.mounted.forEach((element, index) => {
4227
2685
  const row = this.rows[index];
4228
- if (
4229
- row?.type !== 'system' ||
4230
- row.variant !== 'warning' ||
4231
- row.height !== undefined
4232
- )
2686
+ const registered = row && rowRenderer(row);
2687
+ if (registered && element.firstElementChild) {
2688
+ const height = registered.measureRendered(
2689
+ element.firstElementChild as HTMLElement
2690
+ );
2691
+ if (
2692
+ height !== undefined &&
2693
+ (height !== this.measuredRendererHeights.get(row.key)?.height ||
2694
+ webRowRenderSignature(row) !==
2695
+ this.measuredRendererHeights.get(row.key)?.signature)
2696
+ ) {
2697
+ this.measuredRendererHeights.set(row.key, {
2698
+ signature: webRowRenderSignature(row),
2699
+ height,
2700
+ });
2701
+ measuredHeightChanged = true;
2702
+ }
4233
2703
  return;
4234
- const height =
4235
- element.querySelector<HTMLElement>('.ok-native-list-warning')
4236
- ?.offsetHeight ?? 0;
4237
- if (height > 0 && height !== this.measuredWarningHeights.get(row.key)) {
4238
- this.measuredWarningHeights.set(row.key, height);
4239
- measuredWarningChanged = true;
4240
2704
  }
4241
2705
  });
4242
- if (measuredWarningChanged) {
2706
+ if (measuredHeightChanged) {
4243
2707
  this.recomputeLayout();
4244
2708
  return;
4245
2709
  }
@@ -4261,7 +2725,13 @@ export class NativeListWebEngine {
4261
2725
  overlay = false
4262
2726
  ) {
4263
2727
  this.invalidateActionAnchorForElement(element);
4264
- disposeWebImageRetries(element);
2728
+ const registered = rowRenderer(row);
2729
+ const existingBody = element.firstElementChild as HTMLElement | null;
2730
+ const reusableBody =
2731
+ existingBody?.dataset.nlRenderer === registered.key
2732
+ ? existingBody
2733
+ : undefined;
2734
+ if (!reusableBody) disposeWebImageRetries(element);
4265
2735
  const bindingEpoch = String(++this.bindingEpochCounter);
4266
2736
  element.className = overlay
4267
2737
  ? 'ok-native-list-item ok-native-list-sticky'
@@ -4273,7 +2743,10 @@ export class NativeListWebEngine {
4273
2743
  setData(element, 'nativeListRowPressEnabled', isRowPressEnabled(row));
4274
2744
  setData(element, 'testid', row.testID);
4275
2745
  // OneKey patch: deprecation dims a row without disabling its actions.
4276
- element.style.opacity = String(row.opacity ?? 1);
2746
+ element.style.opacity = String(
2747
+ (row.style?.container?.opacity ?? row.opacity ?? 1) *
2748
+ (row.disabled ? 0.5 : 1)
2749
+ );
4277
2750
  setData(element, 'nativeListReorderable', this.isReorderable(row));
4278
2751
  setData(
4279
2752
  element,
@@ -4306,84 +2779,32 @@ export class NativeListWebEngine {
4306
2779
  selectedKeys: this.selectedKeys,
4307
2780
  itemIndex: index,
4308
2781
  };
4309
- const body = createRowBody(context, row);
4310
- applySelectorTabularNumbers(body, row);
2782
+ const body = reusableBody ?? createRowBody(context, row);
2783
+ if (reusableBody) {
2784
+ resetRowContainerStyle(body);
2785
+ registered.bind(body, rendererPrimitives(context));
2786
+ }
4311
2787
  // OneKey patch: explicit selector fields preserve original page geometry.
4312
2788
  element.style.contain = row.backgroundFullWidth ? 'layout style' : '';
4313
2789
  if (row.backgroundColor) body.style.backgroundColor = row.backgroundColor;
4314
- if (row.backgroundFullWidth && row.backgroundColor) {
2790
+ const restingBackground =
2791
+ row.style?.container?.backgroundColor ?? row.backgroundColor;
2792
+ if (row.backgroundFullWidth && restingBackground) {
4315
2793
  const bleed = paddingValues(this.snapshot).horizontal;
4316
2794
  body.style.position = 'relative';
4317
2795
  body.style.overflow = 'visible';
4318
2796
  body.style.boxShadow =
4319
2797
  String(-bleed) +
4320
2798
  'px 0 ' +
4321
- row.backgroundColor +
2799
+ restingBackground +
4322
2800
  ',' +
4323
2801
  String(bleed) +
4324
2802
  'px 0 ' +
4325
- row.backgroundColor;
4326
- }
4327
- if (row.type === 'identity' && row.height !== undefined) {
4328
- const title = body.querySelector<HTMLElement>('.ok-native-list-title');
4329
- if (row.presentation === 'accountSelector') {
4330
- body.style.gap = '12px';
4331
- body.style.borderRadius = '12px';
4332
- if ('shape' in row.leading && row.leading.shape === 'rounded') {
4333
- const visual = body.querySelector<HTMLElement>(
4334
- '.ok-native-list-visual'
4335
- );
4336
- if (visual) visual.style.borderRadius = '8px';
4337
- }
4338
- if (title) title.style.lineHeight = '24px';
4339
- }
4340
- if (row.presentation === 'networkSelector') {
4341
- body.style.borderRadius = '12px';
4342
- const visual = body.querySelector<HTMLElement>(
4343
- '.ok-native-list-visual'
4344
- );
4345
- if (visual) {
4346
- visual.style.width = '32px';
4347
- visual.style.height = '32px';
4348
- visual.style.flexBasis = '32px';
4349
- }
4350
- if (
4351
- row.leading.kind === 'network' &&
4352
- !row.leading.image &&
4353
- !row.leading.fallbackIcon &&
4354
- row.leading.fallbackText
4355
- ) {
4356
- const fallback = visual?.querySelector<HTMLElement>(
4357
- '.ok-native-list-visual-fallback'
4358
- );
4359
- if (fallback) {
4360
- fallback.style.fontSize = '19px';
4361
- fallback.style.lineHeight = '27px';
4362
- fallback.style.fontWeight = '600';
4363
- fallback.style.color = 'var(--nl-inverse-text)';
4364
- }
4365
- }
4366
- visual
4367
- ?.querySelectorAll<HTMLElement>('.ok-native-list-visual-main')
4368
- .forEach((image) => {
4369
- image.style.width = '32px';
4370
- image.style.height = '32px';
4371
- });
4372
- if (title) {
4373
- title.style.fontSize = '16px';
4374
- title.style.lineHeight = '24px';
4375
- title.style.fontWeight = '500';
4376
- }
4377
- body
4378
- .querySelectorAll<HTMLElement>('.ok-native-list-accessory')
4379
- .forEach((value) => {
4380
- value.style.fontSize = '16px';
4381
- value.style.lineHeight = '24px';
4382
- value.style.fontWeight = '500';
4383
- });
4384
- }
2803
+ restingBackground;
4385
2804
  }
4386
- element.replaceChildren(body);
2805
+ // Template and presentation defaults must precede caller overrides.
2806
+ applyRowStyle(body, row);
2807
+ if (body.parentElement !== element) element.replaceChildren(body);
4387
2808
  if (
4388
2809
  row.type === 'market' &&
4389
2810
  row.diagnostics?.imageBindActionKey &&
@@ -5254,6 +3675,8 @@ export class NativeListWebEngine {
5254
3675
  action.dataset.nativeListHoverAction === 'true'
5255
3676
  ? action.dataset.nativeListAction
5256
3677
  : action.dataset.nativeListHoverAction;
3678
+ // Legacy Web: hover checks only the resolved source (the member for a
3679
+ // WalletGroup), not the enclosing row's `disabled`.
5257
3680
  if (sourceRow && !sourceRow.disabled && actionKey)
5258
3681
  this.emitRowAction(sourceRow, actionKey, action, rowElement ?? undefined);
5259
3682
  };
@@ -5298,6 +3721,9 @@ export class NativeListWebEngine {
5298
3721
  (member) => member.key === memberKey
5299
3722
  ) ?? row
5300
3723
  : row;
3724
+ // Legacy Web: a disabled row (or whole WalletGroup) blocks everything
3725
+ // above. A disabled WalletGroup member still emits its own actions, but its
3726
+ // member press is blocked by `isRowPressEnabled` in `handleRowPress`.
5301
3727
  const action = target.closest<HTMLElement>('[data-native-list-action]');
5302
3728
  if (action) {
5303
3729
  const scope = action.dataset.selectionScope;
@@ -5781,11 +4207,11 @@ export class NativeListWebEngine {
5781
4207
  const row = source?.firstElementChild;
5782
4208
  if (!source || !row) return;
5783
4209
  const rect = source.getBoundingClientRect();
5784
- const groupParent = row.querySelector<HTMLElement>(
5785
- '[data-native-list-group-parent="true"]>.ok-native-list-wallet-row'
5786
- );
5787
- const previewRow = groupParent ?? row;
5788
- const previewHeight = groupParent ? 68 : rect.height;
4210
+ const sourceRow = state.workingRows[state.currentIndex];
4211
+ const preview =
4212
+ sourceRow && rowRenderer(sourceRow).reorderPreview(row as HTMLElement);
4213
+ const previewRow = preview?.element ?? row;
4214
+ const previewHeight = preview?.height ?? rect.height;
5789
4215
  state.previewOffsetX = state.startX - rect.left;
5790
4216
  state.previewOffsetY = Math.min(
5791
4217
  previewHeight,
@@ -5809,7 +4235,6 @@ export class NativeListWebEngine {
5809
4235
  }
5810
4236
  configureWebAvatar(image, avatar.source, avatar.uri);
5811
4237
  });
5812
- const sourceRow = state.workingRows[state.currentIndex];
5813
4238
  const badgeText = sourceRow
5814
4239
  ? webWalletGroupReorderBadge(sourceRow)
5815
4240
  : undefined;
@@ -6217,3 +4642,7 @@ export class NativeListWebEngine {
6217
4642
  });
6218
4643
  }
6219
4644
  }
4645
+
4646
+ export function applyRowStyle(body: HTMLElement, row: RowModel): void {
4647
+ applyRowContainerStyle(body, row);
4648
+ }