@onekeyfe/react-native-native-list 6.7.0-alpha.378 → 6.7.0-alpha.383

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 (116) hide show
  1. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +14 -1
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +240 -11
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +7 -4
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +6 -3
  5. package/android/src/main/java/com/onekey/nativelist/NativeListButtonGroupRenderer.kt +102 -0
  6. package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +12 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +87 -7
  8. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +177 -1
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +43 -14
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +25 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +33 -2
  12. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +22 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +2 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +2 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +3 -2
  16. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +69 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +55 -1
  18. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1912 -107
  19. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListPressPolicyTest.kt +1 -0
  20. package/docs/ROW_TEMPLATES.md +60 -9
  21. package/docs/SPEC.md +450 -5
  22. package/docs/STYLE_SPEC.md +6 -0
  23. package/ios/HybridNativeList.swift +21 -2
  24. package/ios/NativeListAccessoryStack.swift +227 -39
  25. package/ios/NativeListActivityDetailsView.swift +18 -8
  26. package/ios/NativeListActivityRenderer.swift +8 -2
  27. package/ios/NativeListButtonGroupRenderer.swift +89 -0
  28. package/ios/NativeListDataRowRenderer.swift +155 -27
  29. package/ios/NativeListDesignAssets.swift +3 -0
  30. package/ios/NativeListIdentityRenderer.swift +166 -30
  31. package/ios/NativeListLeadingVisual.swift +49 -14
  32. package/ios/NativeListMessageRenderer.swift +34 -1
  33. package/ios/NativeListModels.swift +42 -2
  34. package/ios/NativeListRendererRegistry.swift +1 -0
  35. package/ios/NativeListResolvedText.swift +2 -1
  36. package/ios/NativeListRowHost.swift +14 -0
  37. package/ios/NativeListSectionHeaderRenderer.swift +88 -0
  38. package/ios/RNCNativeListView.swift +1013 -55
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +15 -0
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +4 -0
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +15 -0
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +3 -0
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +15 -0
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +3 -0
  45. package/ios/tests/NativeListActivityPresentationTests.swift +56 -0
  46. package/ios/tests/NativeListLifecycleTests.swift +2720 -3
  47. package/lib/module/NativeList.js +175 -28
  48. package/lib/module/NativeList.web.js +129 -30
  49. package/lib/module/index.js +1 -1
  50. package/lib/module/models.js +1 -0
  51. package/lib/module/selection.js +1 -1
  52. package/lib/module/validation.js +421 -8
  53. package/lib/module/web/NativeListWebEngine.js +507 -19
  54. package/lib/module/web/templates/ActivityRowRenderer.js +8 -7
  55. package/lib/module/web/templates/ButtonGroupRowRenderer.js +47 -0
  56. package/lib/module/web/templates/DataRowRenderer.js +40 -5
  57. package/lib/module/web/templates/IdentityRowRenderer.js +90 -4
  58. package/lib/module/web/templates/MessageRowRenderer.js +18 -2
  59. package/lib/module/web/templates/RowContainerStyle.js +1 -0
  60. package/lib/module/web/templates/RowRendererRegistry.js +3 -0
  61. package/lib/module/web/templates/SectionHeaderRowRenderer.js +36 -2
  62. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
  63. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  64. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  65. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
  66. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
  67. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  68. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  69. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
  70. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  71. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  72. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  73. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  74. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  75. package/lib/typescript/src/NativeList.d.ts +1 -1
  76. package/lib/typescript/src/NativeList.nitro.d.ts +2 -0
  77. package/lib/typescript/src/NativeList.types.d.ts +18 -0
  78. package/lib/typescript/src/index.d.ts +2 -2
  79. package/lib/typescript/src/models.d.ts +105 -9
  80. package/lib/typescript/src/validation.d.ts +12 -0
  81. package/lib/typescript/src/web/NativeListWebEngine.d.ts +32 -0
  82. package/lib/typescript/src/web/templates/ButtonGroupRowRenderer.d.ts +14 -0
  83. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +1 -1
  84. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +9 -3
  85. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
  86. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  87. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
  89. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
  90. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  91. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  92. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
  93. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  94. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  95. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  96. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  97. package/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  98. package/package.json +3 -3
  99. package/react-native-native-list.podspec +1 -0
  100. package/src/NativeList.nitro.ts +2 -0
  101. package/src/NativeList.tsx +313 -44
  102. package/src/NativeList.types.ts +22 -0
  103. package/src/NativeList.web.tsx +204 -48
  104. package/src/index.ts +2 -0
  105. package/src/models.ts +117 -3
  106. package/src/selection.ts +1 -0
  107. package/src/validation.ts +852 -6
  108. package/src/web/NativeListWebEngine.ts +803 -20
  109. package/src/web/templates/ActivityRowRenderer.ts +14 -6
  110. package/src/web/templates/ButtonGroupRowRenderer.ts +58 -0
  111. package/src/web/templates/DataRowRenderer.ts +93 -20
  112. package/src/web/templates/IdentityRowRenderer.ts +150 -5
  113. package/src/web/templates/MessageRowRenderer.ts +23 -7
  114. package/src/web/templates/RowContainerStyle.ts +2 -0
  115. package/src/web/templates/RowRendererRegistry.ts +11 -1
  116. package/src/web/templates/SectionHeaderRowRenderer.ts +47 -4
@@ -421,7 +421,7 @@ export const WEB_LIST_CSS = `
421
421
  .ok-native-list-table-secondary{display:block;min-width:0;overflow:clip}.ok-native-list-table-secondary-track{display:flex;flex-direction:column;transition:transform 100ms ease}.ok-native-list-market-table:focus-within .ok-native-list-table-secondary[data-has-hover="true"]>.ok-native-list-table-secondary-track{transform:translateY(calc(-1 * var(--nl-table-secondary-height)))}@media(hover:hover){.ok-native-list-market-table:hover .ok-native-list-table-secondary[data-has-hover="true"]>.ok-native-list-table-secondary-track{transform:translateY(calc(-1 * var(--nl-table-secondary-height)))}}.ok-native-list-market-table button:focus-visible{outline:2px solid var(--nl-accent);outline-offset:1px}.ok-native-list-market-table button:hover{opacity:.75}.ok-native-list-market-table button:active{opacity:.5}
422
422
 
423
423
  [data-native-list-selector="walletSidebar"] .ok-native-list-title{max-width:100%;min-width:0}
424
- .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-synthesis:none;font-family:var(--nl-font-regular)}
424
+ .ok-native-list-root{--nl-bg:#f7f7f7;--nl-row:#fff;--nl-selected:#eaf2ff;--nl-pressed:#e8e8e8;--nl-hover:#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-synthesis:none;font-family:var(--nl-font-regular)}
425
425
  .ok-native-list-viewport-frame{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
426
426
  .ok-native-list-viewport{position:absolute;inset:0;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;scrollbar-gutter:stable}
427
427
  .ok-native-list-content{position:relative;min-width:100%;min-height:100%}
@@ -432,7 +432,7 @@ export const WEB_LIST_CSS = `
432
432
  .ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
433
433
  .ok-native-list-item[data-native-list-disabled="true"]>.ok-native-list-row{cursor:default}
434
434
  .ok-native-list-item[data-native-list-row-press-enabled="true"]>.ok-native-list-row{cursor:pointer}
435
- .ok-native-list-item[data-native-list-row-press-enabled="true"]:hover>.ok-native-list-row{background:var(--nl-pressed)}
435
+ .ok-native-list-item[data-native-list-row-press-enabled="true"]:hover>.ok-native-list-row{background:var(--nl-hover)}
436
436
  .ok-native-list-item[data-native-list-row-press-enabled="true"]:active>.ok-native-list-row{background:var(--nl-pressed)}
437
437
  .ok-native-list-item[data-native-list-row-press-enabled="true"]:not([data-native-list-selected="true"]):hover>.ok-native-list-wallet-row{background:var(--nl-strong)}
438
438
  .ok-native-list-item[data-native-list-row-press-enabled="true"]:not([data-native-list-selected="true"]):active>.ok-native-list-wallet-row{background:var(--nl-pressed)}
@@ -463,16 +463,23 @@ export const WEB_LIST_CSS = `
463
463
  .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)}
464
464
  .ok-native-list-item[data-group-position="single"]>.ok-native-list-row{border-radius:var(--nl-group-radius,12px)}
465
465
  .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}
466
+ .ok-native-list-trailing-button{appearance:none;box-sizing:border-box;flex:0 0 auto;padding:4px 10px;border:1px solid transparent;border-radius:9999px;background:var(--nl-button-bg,var(--nl-strong));color:var(--nl-button-text,var(--nl-primary));font:500 14px/20px var(--nl-font-medium);white-space:nowrap;cursor:pointer}.ok-native-list-trailing-button:hover:not(:disabled){background:var(--nl-button-hover,var(--nl-strong))}.ok-native-list-trailing-button:active:not(:disabled){background:var(--nl-button-pressed,var(--nl-pressed))}.ok-native-list-trailing-button:focus-visible{outline:2px solid var(--nl-button-focus,var(--nl-accent))}.ok-native-list-trailing-button:disabled{opacity:.4;cursor:default}
467
+ .ok-native-list-icon-button.ok-native-list-tertiary-icon{box-sizing:border-box;flex:0 0 38px;width:38px;height:38px;margin:-7px;padding:6px;border:1px solid transparent;border-radius:9999px;color:var(--nl-icon-subdued);background:transparent}.ok-native-list-icon-button.ok-native-list-tertiary-icon:hover:not(:disabled){background:var(--nl-hover)}.ok-native-list-icon-button.ok-native-list-tertiary-icon:active:not(:disabled){background:var(--nl-pressed)}.ok-native-list-icon-button.ok-native-list-tertiary-icon:disabled{opacity:.4;cursor:default}
468
+ .ok-native-list-icon-button.ok-native-list-tertiary-icon:focus-visible:not(:disabled){outline:2px solid var(--nl-accent)}
469
+ .ok-native-list-identity-title-icon{flex:0 0 20px;width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;color:var(--nl-icon);font-size:20px}.ok-native-list-identity-title-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
466
470
  .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;font-family:var(--nl-font-regular)}.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;font-family:var(--nl-font-regular)}.ok-native-list-account-row .ok-native-list-secondary{font-size:14px;line-height:20px;font-weight:400;font-family:var(--nl-font-regular)}
467
471
  .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;font-family:var(--nl-font-semibold)}.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)}
468
472
  .ok-native-list-identity-row .ok-native-list-title,.ok-native-list-identity-row .ok-native-list-amounts>span:first-child{font-size:16px;line-height:20px}.ok-native-list-identity-row .ok-native-list-secondary{font-size:14px;line-height:20px}
469
473
  .ok-native-list-value{font-size:14px;line-height:20px;font-weight:500;white-space:nowrap;font-family:var(--nl-font-medium)}.ok-native-list-time{font-size:11px;line-height:16px;color:var(--nl-secondary);align-self:flex-start}.ok-native-list-amounts{display:flex;flex-direction:column;align-items:flex-end;min-width:0}.ok-native-list-actions{display:flex;gap:16px;margin-top:6px}
470
474
  .ok-native-list-action-button{appearance:none;border:0;background:transparent;padding:2px;color:var(--nl-accent);cursor:pointer;font:600 12px/16px var(--nl-font-semibold)}.ok-native-list-action-button[data-tone="danger"]{color:var(--nl-negative)}.ok-native-list-action-button:disabled{opacity:.5;cursor:default}
471
- .ok-native-list-visual{position:relative;flex:0 0 40px;width:40px;height:40px;border-radius:20px;overflow:visible;background:var(--nl-strong)}.ok-native-list-visual>img,.ok-native-list-visual-main{display:block;width:40px;height:40px;border-radius:inherit;object-fit:cover}.ok-native-list-visual-fallback{display:flex;width:100%;height:100%;align-items:center;justify-content:center;border-radius:inherit;color:var(--nl-primary);font-size:15px;font-weight:600;overflow:hidden;font-family:var(--nl-font-semibold)}.ok-native-list-visual>.ok-native-list-visual-corner{position:absolute;right:-4px;bottom:-4px;width:18px;height:18px;padding:2px;border-radius:50%;background:var(--nl-row);object-fit:cover}.ok-native-list-stacked{display:flex;align-items:center;width:48px;flex-basis:48px}.ok-native-list-stacked img{width:28px;height:28px;border:2px solid var(--nl-row);border-radius:50%;object-fit:cover;margin-left:-10px}.ok-native-list-stacked img:first-child{margin-left:0}
475
+ .ok-native-list-visual{position:relative;flex:0 0 40px;width:40px;height:40px;border-radius:20px;overflow:visible;background:var(--nl-strong)}.ok-native-list-visual>img,.ok-native-list-visual-main{display:block;width:40px;height:40px;border-radius:inherit;object-fit:cover}.ok-native-list-visual-fallback{display:flex;width:100%;height:100%;align-items:center;justify-content:center;border-radius:inherit;color:var(--nl-primary);font-size:15px;font-weight:600;overflow:hidden;font-family:var(--nl-font-semibold)}.ok-native-list-image-skeleton{position:absolute;inset:0;border-radius:inherit;pointer-events:none;animation:ok-native-list-skeleton-light 1.5s linear infinite alternate}.ok-native-list-image-skeleton[data-dark="true"]{animation-name:ok-native-list-skeleton-dark}@keyframes ok-native-list-skeleton-light{0%{background-color:#fafafa}100%{background-color:#cdcdcd}}@keyframes ok-native-list-skeleton-dark{0%{background-color:#111111}100%{background-color:#333333}}@media(prefers-reduced-motion:reduce){.ok-native-list-image-skeleton{animation:none;background-color:var(--nl-strong)}}.ok-native-list-visual>.ok-native-list-visual-corner{position:absolute;right:-4px;bottom:-4px;width:18px;height:18px;padding:2px;border-radius:50%;background:var(--nl-row);object-fit:cover}.ok-native-list-stacked{display:flex;align-items:center;width:48px;flex-basis:48px}.ok-native-list-stacked img{width:28px;height:28px;border:2px solid var(--nl-row);border-radius:50%;object-fit:cover;margin-left:-10px}.ok-native-list-stacked img:first-child{margin-left:0}
472
476
  .ok-native-list-accessories{display:flex;align-items:center;gap:10px;min-width:0}.ok-native-list-accessory{font-size:14px;color:var(--nl-primary);white-space:nowrap}.ok-native-list-accessory-secondary{color:var(--nl-secondary)}.ok-native-list-icon-button{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;border:0;background:transparent;color:var(--nl-icon);font-size:20px;line-height:1;font-weight:500;cursor:pointer;font-family:var(--nl-font-medium)}.ok-native-list-checkbox{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:20px;height:20px;border:2px solid color-mix(in srgb,var(--nl-primary) 19%,transparent);border-radius:5px;background:var(--nl-row);cursor:pointer}.ok-native-list-checkbox[data-state="checked"],.ok-native-list-checkbox[data-state="indeterminate"]{border-color:transparent;background:var(--nl-primary)}.ok-native-list-checkbox[data-state="checked"]::after{content:"";width:6px;height:10px;margin-top:-2px;border-right:2px solid var(--nl-row);border-bottom:2px solid var(--nl-row);transform:rotate(45deg)}.ok-native-list-checkbox[data-state="indeterminate"]::after{content:"";width:8px;height:2px;border-radius:1px;background:var(--nl-row)}.ok-native-list-spinner{width:16px;height:16px;border:2px solid var(--nl-separator);border-top-color:var(--nl-primary);border-radius:50%;animation:ok-native-list-spin .8s linear infinite}@keyframes ok-native-list-spin{to{transform:rotate(360deg)}}
473
- .ok-native-list-badge{display:inline-flex;align-items:center;max-width:100%;height:18px;padding:0 5px;border-radius:5px;background:color-mix(in srgb,var(--nl-accent) 12%,transparent);color:var(--nl-accent);font-size:11px;line-height:18px;white-space:nowrap}.ok-native-list-badge[data-tone="danger"]{background:var(--nl-critical);color:var(--nl-negative)}.ok-native-list-badges{display:inline-flex;gap:4px;margin-left:6px;vertical-align:middle}
477
+ .ok-native-list-icon-button.ok-native-list-decorative-refresh{min-width:16px;width:16px;height:16px}
478
+ .ok-native-list-badge{display:inline-flex;align-items:center;max-width:100%;height:18px;padding:0 5px;border-radius:5px;background:color-mix(in srgb,var(--nl-accent) 12%,transparent);color:var(--nl-accent);font-size:11px;line-height:18px;white-space:nowrap}.ok-native-list-badge[data-tone="danger"]{background:var(--nl-critical);color:var(--nl-negative)}.ok-native-list-badge[data-tone="success"]{background:var(--nl-success-background,color-mix(in srgb,var(--nl-positive) 12%,transparent));color:var(--nl-positive)}.ok-native-list-section>.ok-native-list-badge[data-tone="success"]{box-sizing:border-box;height:20px;padding:2px 8px;border-radius:4px;font:500 12px/16px var(--nl-font-medium)}.ok-native-list-badges{display:inline-flex;gap:4px;margin-left:6px;vertical-align:middle}
479
+ .ok-native-list-identity-row .ok-native-list-badge[data-tone="neutral"]{height:20px;box-sizing:border-box;padding:2px 8px;border-radius:4px;background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;font-weight:500;font-family:var(--nl-font-medium)}
474
480
  .ok-native-list-section{padding:0 8px;background:var(--nl-bg);gap:8px}.ok-native-list-section[data-variant="summary"]{padding:0 20px}.ok-native-list-section[data-variant="gallery"]{padding:0 12px}.ok-native-list-section[data-variant="history"]{padding:0 8px}.ok-native-list-section-title{font-size:13px;line-height:18px;font-weight:700;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--nl-font-bold)}.ok-native-list-section[data-variant="summary"] .ok-native-list-section-title{font-size:16px;font-weight:500;color:var(--nl-primary);font-family:var(--nl-font-medium)}.ok-native-list-section[data-variant="gallery"] .ok-native-list-section-title{font-size:16px;color:var(--nl-primary)}.ok-native-list-section[data-variant="history"] .ok-native-list-section-title{font-size:12px;line-height:16px;letter-spacing:0.8px}.ok-native-list-section-value{margin-left:auto}
475
481
  .ok-native-list-action-row{justify-content:space-between;padding:0 16px}.ok-native-list-action-title{font-size:15px;font-weight:600;color:var(--nl-accent);font-family:var(--nl-font-semibold)}.ok-native-list-action-title[data-tone="danger"]{color:var(--nl-negative)}
482
+ .ok-native-list-button-group{padding:0;gap:10px;overflow:visible}.ok-native-list-block-button{position:relative;display:flex;flex:1 1 0;min-width:0;min-height:48px;align-items:center;justify-content:center;gap:8px;padding:12px 8px;border:1px solid color-mix(in srgb,var(--nl-info) 35%,transparent);border-radius:12px;background:transparent;color:var(--nl-info);font:500 14px/20px var(--nl-font-medium);cursor:pointer}.ok-native-list-block-button>span:not(.ok-native-list-spinner){min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ok-native-list-block-button>.ok-native-list-spinner{position:static;flex:0 0 20px;width:20px;height:20px;border-top-color:var(--nl-info)}.ok-native-list-block-button:hover:not(:disabled){background:color-mix(in srgb,var(--nl-info) 10%,transparent);border-color:var(--nl-info)}.ok-native-list-block-button:active:not(:disabled){background:color-mix(in srgb,var(--nl-info) 17%,transparent)}.ok-native-list-block-button:focus-visible{outline:2px solid var(--nl-info);outline-offset:1px}.ok-native-list-block-button:disabled{opacity:.4;cursor:default}
476
483
  .ok-native-list-account-action-row{justify-content:flex-start;gap:12px;padding:0 12px}.ok-native-list-account-action-row .ok-native-list-action-title{color:var(--nl-secondary);font-size:16px;line-height:24px;font-weight:400;font-family:var(--nl-font-regular)}
477
484
  .ok-native-list-system{justify-content:center;padding:8px 12px;background:var(--nl-bg);color:var(--nl-secondary);cursor:default}.ok-native-list-system[data-variant="retry"],.ok-native-list-system[data-variant="noMatch"],.ok-native-list-system[data-variant="end"]{justify-content:flex-start}.ok-native-list-system[data-variant="retry"]{cursor:pointer}
478
485
  .ok-native-list-rail{padding:4px;gap:6px;border-radius:8px}.ok-native-list-rail .ok-native-list-visual{width:20px;height:20px;flex-basis:20px}.ok-native-list-rail .ok-native-list-visual>img,.ok-native-list-rail .ok-native-list-visual-main{width:20px;height:20px}.ok-native-list-rail-title{font-size:12px;font-weight:500;white-space:nowrap;font-family:var(--nl-font-medium)}
@@ -521,6 +528,7 @@ export const WEB_LIST_CSS = `
521
528
  .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)}
522
529
  .ok-native-list-root [data-nl-container-border="true"]{position:relative}
523
530
  .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}
531
+ .ok-native-list-root [data-nl-inline-warning="true"][data-nl-container-border="true"]::before{box-shadow:none;border-block:var(--nl-container-border-width) solid var(--nl-container-border-color);border-inline:0;border-radius:0}
524
532
  .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)}
525
533
  `;
526
534
  function createElement(document, tag, className, text) {
@@ -718,6 +726,38 @@ function paintSelectorImageBackground(image, frame, inset = 0) {
718
726
 
719
727
  // OneKey patch: use source SVG paths for selector actions and wallet provider marks.
720
728
  const selectorIcons = {
729
+ RefreshCcwOutline: {
730
+ viewBox: '0 0 24 24',
731
+ paths: [{
732
+ d: 'M5 12a7 7 0 0 0 7 7 7.15 7.15 0 0 0 4.955-2h-2.943v-2h6v6h-2v-2.257A9.148 9.148 0 0 1 12 21a9 9 0 0 1-9-9v-1h2v1Zm1-6.754A9.148 9.148 0 0 1 12 3a9 9 0 0 1 9 9v1h-2v-1a7 7 0 0 0-7-7 7.15 7.15 0 0 0-4.955 2H10v2H4V3h2v2.246Z',
733
+ fill: 'currentColor',
734
+ fillRule: 'nonzero',
735
+ opacity: 1
736
+ }]
737
+ },
738
+ TicketOutline: {
739
+ viewBox: '0 0 24 24',
740
+ paths: [{
741
+ d: 'M16 17.01h-2V15h2v2.01Zm0-4h-2V11h2v2.01Zm0-4h-2V7h2v2.01Z',
742
+ fill: 'currentColor',
743
+ fillRule: 'nonzero',
744
+ opacity: 1
745
+ }, {
746
+ d: 'm22 9.927-.629.252c-1.661.665-1.661 2.977 0 3.642l.629.252V20H2v-5.927l.629-.252c1.661-.665 1.661-2.977 0-3.642L2 9.927V4h20v5.927ZM4 8.636c2.5 1.52 2.499 5.207 0 6.727V18h16v-2.637c-2.499-1.52-2.5-5.208 0-6.727V6H4v2.636Z',
747
+ fill: 'currentColor',
748
+ fillRule: 'evenodd',
749
+ opacity: 1
750
+ }]
751
+ },
752
+ GasSolid: {
753
+ viewBox: '0 0 24 24',
754
+ paths: [{
755
+ d: 'M15 9h4v7h1V8.414L17.586 6 19 4.586l3 3V18h-5v-7h-2v8h1v2H2v-2h1V3h12zM6 9v2h6V9z',
756
+ fill: 'currentColor',
757
+ fillRule: 'evenodd',
758
+ opacity: 1
759
+ }]
760
+ },
721
761
  CryptoCoinOutline: {
722
762
  viewBox: '0 0 24 24',
723
763
  paths: [{
@@ -962,6 +1002,7 @@ function applySelectorIcon(element, name) {
962
1002
  }
963
1003
  function iconGlyph(name) {
964
1004
  const normalized = name.toLocaleLowerCase();
1005
+ if (normalized === 'dotversolid') return '⋮';
965
1006
  if (normalized.includes('chevron')) {
966
1007
  if (normalized.includes('top')) return '⌃';
967
1008
  if (normalized.includes('bottom')) return '⌄';
@@ -1009,6 +1050,8 @@ function createVisual(context, visual, selectorPresentation) {
1009
1050
  const showsSourcePlaceholder = !!source && source.loadingStrategy !== undefined && source.loadingStrategy !== 'none';
1010
1051
  frame.style.background = showsSourcePlaceholder ? 'var(--nl-strong)' : visualBackground;
1011
1052
  const fallbackIcon = 'fallbackIcon' in visual ? visual.fallbackIcon : undefined;
1053
+ const fallbackBackground = fallbackIcon?.backgroundColor ?? 'var(--nl-strong)';
1054
+ if (!source && fallbackIcon?.backgroundColor) frame.style.background = fallbackBackground;
1012
1055
  const handlesSourceFallback = showsSourcePlaceholder && (fallbackIcon !== undefined || 'fallbackText' in visual);
1013
1056
  const image = source ? createImage(context, source) : undefined;
1014
1057
  if (image) {
@@ -1022,6 +1065,24 @@ function createVisual(context, visual, selectorPresentation) {
1022
1065
  if (image.complete && image.naturalWidth > 0) restoreBackground();
1023
1066
  }
1024
1067
  if (selectorPresentation) paintSelectorImageBackground(image, frame);
1068
+ if (source?.loadingStrategy === 'skeleton' && !(image.complete && image.naturalWidth > 0)) {
1069
+ const skeleton = createElement(context.document, 'span', 'ok-native-list-image-skeleton');
1070
+ const background = resolvedTheme(context.snapshot).background;
1071
+ const rgb = /^#([0-9a-f]{6})/i.exec(background)?.[1];
1072
+ if (rgb) {
1073
+ const red = Number.parseInt(rgb.slice(0, 2), 16);
1074
+ const green = Number.parseInt(rgb.slice(2, 4), 16);
1075
+ const blue = Number.parseInt(rgb.slice(4, 6), 16);
1076
+ if (red * 299 + green * 587 + blue * 114 < 128_000) skeleton.dataset.dark = 'true';
1077
+ }
1078
+ frame.appendChild(skeleton);
1079
+ image.addEventListener('load', () => skeleton.remove(), {
1080
+ once: true
1081
+ });
1082
+ image.addEventListener('error', () => skeleton.remove(), {
1083
+ once: true
1084
+ });
1085
+ }
1025
1086
  } else if (!source || showsSourcePlaceholder) {
1026
1087
  frame.appendChild(createElement(context.document, 'span', 'ok-native-list-visual-fallback', 'fallbackText' in visual ? visual.fallbackText ?? '' : ''));
1027
1088
  }
@@ -1045,11 +1106,19 @@ function createVisual(context, visual, selectorPresentation) {
1045
1106
  }
1046
1107
  frame.querySelector('.ok-native-list-visual-fallback:not(.ok-native-list-visual-corner)')?.remove();
1047
1108
  const fallback = createElement(context.document, 'span', 'ok-native-list-visual-fallback', fallbackIcon ? undefined : 'fallbackText' in visual ? visual.fallbackText ?? '' : '');
1109
+ if ('fallbackTextColor' in visual && visual.fallbackTextColor && !fallbackIcon) fallback.style.color = visual.fallbackTextColor;
1048
1110
  if (fallbackIcon) {
1049
1111
  applySelectorIcon(fallback, fallbackIcon.name);
1050
1112
  if (fallbackIcon.tintColor) fallback.style.color = fallbackIcon.tintColor;
1113
+ if (fallbackIcon.size) {
1114
+ const svg = fallback.querySelector('svg');
1115
+ if (svg) {
1116
+ svg.style.width = `${fallbackIcon.size}px`;
1117
+ svg.style.height = `${fallbackIcon.size}px`;
1118
+ }
1119
+ }
1051
1120
  }
1052
- fallback.style.backgroundColor = 'var(--nl-image-fallback-background, transparent)';
1121
+ if (fallbackIcon?.backgroundColor) frame.style.background = fallbackBackground;else fallback.style.backgroundColor = 'var(--nl-image-fallback-background, transparent)';
1053
1122
  frame.prepend(fallback);
1054
1123
  };
1055
1124
  if (image) image.addEventListener('error', showFallback, {
@@ -1059,6 +1128,9 @@ function createVisual(context, visual, selectorPresentation) {
1059
1128
  if ('borderStyle' in visual && visual.borderStyle === 'dashed') {
1060
1129
  frame.style.border = '2px dashed ' + (visual.borderColor ?? 'var(--nl-disabled)');
1061
1130
  frame.style.boxSizing = 'border-box';
1131
+ } else if ('borderColor' in visual && visual.borderColor) {
1132
+ frame.style.border = '1px solid ' + visual.borderColor;
1133
+ frame.style.boxSizing = 'border-box';
1062
1134
  }
1063
1135
  if ('overlays' in visual) visual.overlays?.forEach(overlay => {
1064
1136
  const corner = createElement(context.document, 'span', 'ok-native-list-visual-overlay', overlay.text);
@@ -1072,6 +1144,12 @@ function createVisual(context, visual, selectorPresentation) {
1072
1144
  corner.style.lineHeight = '16px';
1073
1145
  applyTextWeight(corner, '400');
1074
1146
  }
1147
+ if (overlay.fontWeight) applyTextWeight(corner, {
1148
+ regular: 400,
1149
+ medium: 500,
1150
+ semibold: 600,
1151
+ bold: 700
1152
+ }[overlay.fontWeight]);
1075
1153
  if (isWalletText || overlay.width !== undefined || overlay.height !== undefined) corner.style.borderRadius = '9999px';
1076
1154
  const offsetX = String(-(overlay.offsetX ?? overlay.offset ?? 2)) + 'px';
1077
1155
  const offsetY = String(-(overlay.offsetY ?? overlay.offset ?? 2)) + 'px';
@@ -1087,7 +1165,20 @@ function createVisual(context, visual, selectorPresentation) {
1087
1165
  if (overlay.image) {
1088
1166
  const overlayImage = createImage(context, overlay.image);
1089
1167
  if (overlayImage) {
1168
+ if ((overlay.padding ?? 0) > 0 && (overlay.width ?? size) === (overlay.height ?? size)) overlayImage.style.borderRadius = '50%';
1090
1169
  corner.appendChild(overlayImage);
1170
+ if (overlay.fallbackIcon) {
1171
+ overlayImage.addEventListener('error', () => {
1172
+ disposeWebImageRetries(overlayImage);
1173
+ overlayImage.remove();
1174
+ const glyph = createElement(context.document, 'span', 'ok-native-list-overlay-fallback');
1175
+ applySelectorIcon(glyph, overlay.fallbackIcon.name);
1176
+ if (overlay.fallbackIcon?.tintColor) glyph.style.color = overlay.fallbackIcon.tintColor;
1177
+ corner.appendChild(glyph);
1178
+ }, {
1179
+ once: true
1180
+ });
1181
+ }
1091
1182
  if (selectorPresentation) paintSelectorImageBackground(overlayImage, corner, overlay.padding ?? 0);
1092
1183
  }
1093
1184
  } else if (overlay.name) applySelectorIcon(corner, overlay.name);
@@ -1167,7 +1258,23 @@ function markActionAnchorSource(element, source, slot) {
1167
1258
 
1168
1259
  // OneKey patch: the compact zero-count digits share the amount baseline.
1169
1260
 
1170
- function createAccessory(context, rowKey, accessory, slot) {
1261
+ function createAccessory(context, rowKey, accessory, slot, rowDisabled) {
1262
+ if (accessory.kind === 'button') {
1263
+ const element = createElement(context.document, 'button', 'ok-native-list-trailing-button', accessory.label);
1264
+ element.type = 'button';
1265
+ element.disabled = Boolean(rowDisabled || accessory.disabled);
1266
+ element.setAttribute('aria-disabled', String(element.disabled));
1267
+ element.setAttribute('aria-label', accessory.accessibilityLabel ?? accessory.label);
1268
+ setData(element, 'testid', accessory.testID);
1269
+ setData(element, 'nativeListAction', accessory.actionKey);
1270
+ markActionAnchorSource(element, 'trailingAccessory', slot);
1271
+ if (accessory.textColor) element.style.setProperty('--nl-button-text', accessory.textColor);
1272
+ if (accessory.backgroundColor) element.style.setProperty('--nl-button-bg', accessory.backgroundColor);
1273
+ if (accessory.pressedBackgroundColor) element.style.setProperty('--nl-button-pressed', accessory.pressedBackgroundColor);
1274
+ if (accessory.hoverBackgroundColor) element.style.setProperty('--nl-button-hover', accessory.hoverBackgroundColor);
1275
+ if (accessory.focusRingColor) element.style.setProperty('--nl-button-focus', accessory.focusRingColor);
1276
+ return element;
1277
+ }
1171
1278
  if (accessory.kind === 'checkbox') {
1172
1279
  const element = createCheckbox(context, rowKey, accessory);
1173
1280
  markActionAnchorSource(element, 'trailingAccessory', slot);
@@ -1176,8 +1283,19 @@ function createAccessory(context, rowKey, accessory, slot) {
1176
1283
  if (accessory.kind === 'icon') {
1177
1284
  const element = createIconAction(context, accessory.name, accessory.actionKey, accessory.disabled, accessory.tintColor);
1178
1285
  markActionAnchorSource(element, 'trailingAccessory', slot);
1286
+ if (accessory.name === 'RefreshCcwOutline' && !accessory.actionKey) {
1287
+ element.classList.add('ok-native-list-decorative-refresh');
1288
+ const glyph = element.querySelector('svg');
1289
+ glyph?.setAttribute('width', '16');
1290
+ glyph?.setAttribute('height', '16');
1291
+ }
1292
+ if (accessory.buttonStyle === 'tertiary') {
1293
+ element.classList.add('ok-native-list-tertiary-icon');
1294
+ setData(element, 'nativeListAnchorInset', 7);
1295
+ }
1179
1296
  setData(element, 'testid', accessory.testID);
1180
1297
  if (accessory.hoverActionKey) setData(element, 'nativeListHoverAction', accessory.hoverActionKey);
1298
+ if (accessory.hoverActionKey && accessory.buttonStyle === 'tertiary') setData(element, 'nativeListIconHelp', true);
1181
1299
  if (accessory.accessibilityLabel) element.setAttribute('aria-label', accessory.accessibilityLabel);
1182
1300
  const row = context.snapshot.rows[context.itemIndex];
1183
1301
  if (row && 'presentation' in row && row.presentation === 'accountSelector') {
@@ -1210,8 +1328,27 @@ function createAccessory(context, rowKey, accessory, slot) {
1210
1328
  {
1211
1329
  const primary = createElement(context.document, 'span', undefined, accessory.primary);
1212
1330
  primary.style.color = toneColor(accessory.primaryTone, 'primary');
1331
+ applyValueSegments(primary, accessory.primaryTextSegments, 16, 20, 500);
1332
+ if (accessory.primaryFontWeight === 'medium') applyTextWeight(primary, 500);
1333
+ if (accessory.primaryHelp) {
1334
+ const help = accessory.primaryHelp;
1335
+ primary.tabIndex = 0;
1336
+ primary.setAttribute('role', 'button');
1337
+ if (help.presentation === 'tooltip') {
1338
+ setData(primary, 'nativeListHoverAction', help.actionKey);
1339
+ setData(primary, 'nativeListIconHelp', true);
1340
+ } else {
1341
+ setData(primary, 'nativeListAction', help.actionKey);
1342
+ }
1343
+ if (help.underline === 'dashed') {
1344
+ primary.style.textDecoration = 'underline dashed';
1345
+ primary.style.textUnderlineOffset = '4px';
1346
+ }
1347
+ markActionAnchorSource(primary, 'trailingAccessory', slot);
1348
+ }
1213
1349
  const secondary = createElement(context.document, 'span', 'ok-native-list-secondary', accessory.secondary);
1214
1350
  secondary.style.color = toneColor(accessory.secondaryTone, 'secondary');
1351
+ applyValueSegments(secondary, accessory.secondaryTextSegments, 14, 20, 400);
1215
1352
  element.replaceChildren(primary, '\n', secondary);
1216
1353
  element.classList.add('ok-native-list-amounts');
1217
1354
  break;
@@ -1239,10 +1376,10 @@ function createAccessory(context, rowKey, accessory, slot) {
1239
1376
  if (actionKey) markActionAnchorSource(element, 'trailingAccessory', slot);
1240
1377
  return element;
1241
1378
  }
1242
- function rendererPrimitives(context) {
1379
+ function rendererPrimitives(context, rowDisabled) {
1243
1380
  return {
1244
1381
  background: resolvedTheme(context.snapshot).background,
1245
- accessory: (key, descriptor, slot) => createAccessory(context, key, descriptor, slot),
1382
+ accessory: (key, descriptor, slot) => createAccessory(context, key, descriptor, slot, rowDisabled),
1246
1383
  visual: (source, presentation) => createVisual(context, source, presentation),
1247
1384
  thumbnail: source => createImage(context, source, 'ok-native-list-thumbnail'),
1248
1385
  textStyle: applyTextStyleToSlot,
@@ -1252,7 +1389,7 @@ function rendererPrimitives(context) {
1252
1389
  };
1253
1390
  }
1254
1391
  export function createRowBody(context, row) {
1255
- return rowRenderer(row).create(context.document, rendererPrimitives(context));
1392
+ return rowRenderer(row).create(context.document, rendererPrimitives(context, Boolean(row.disabled)));
1256
1393
  }
1257
1394
  export class NativeListWebEngine {
1258
1395
  rows = [];
@@ -1270,6 +1407,7 @@ export class NativeListWebEngine {
1270
1407
  rail: [],
1271
1408
  mediaTile: [],
1272
1409
  action: [],
1410
+ buttonGroup: [],
1273
1411
  system: [],
1274
1412
  activity: [],
1275
1413
  dataRow: [],
@@ -1296,6 +1434,16 @@ export class NativeListWebEngine {
1296
1434
  lastViewportWidth = -1;
1297
1435
  lastViewportHeight = -1;
1298
1436
  destroyed = false;
1437
+ hasAppliedSnapshot = false;
1438
+ structuralRevision = 0;
1439
+ structuralAnimations = [];
1440
+ structuralScrollInterrupted = false;
1441
+ reactFooterHeight = 0;
1442
+ structuralFooterCompensation = 0;
1443
+ structuralFooterExpansion = 0;
1444
+ structuralFooterFromHeight = 0;
1445
+ structuralProgress = 0;
1446
+ structuralPrefixTransition = false;
1299
1447
  sectionIndexAlignStart = false;
1300
1448
  // Renderers report intrinsic height after browser text wrapping.
1301
1449
  measuredRendererHeights = new Map();
@@ -1384,6 +1532,9 @@ export class NativeListWebEngine {
1384
1532
  });
1385
1533
  }
1386
1534
  (this.externalScrollHost ?? this.viewport).addEventListener('ok-collapsible-pager-cancel-scroll-command', this.handlePagerScrollCommandCancel);
1535
+ for (const event of ['wheel', 'pointerdown', 'touchstart']) (this.externalScrollHost ?? this.viewport).addEventListener(event, this.handleStructuralUserScroll, {
1536
+ passive: true
1537
+ });
1387
1538
  this.viewport.addEventListener('scroll', this.handleScroll, {
1388
1539
  passive: true
1389
1540
  });
@@ -1399,6 +1550,7 @@ export class NativeListWebEngine {
1399
1550
  this.root.addEventListener('pointercancel', this.handleMarketPointerEnd);
1400
1551
  // OneKey patch: preserve web tooltip hover for section titles.
1401
1552
  this.root.addEventListener('pointerover', this.handleTitlePointerOver);
1553
+ this.root.addEventListener('pointerdown', this.handleIconHelpPointerDown);
1402
1554
  this.root.addEventListener('focusin', this.handleTitlePointerOver);
1403
1555
  this.root.addEventListener('focusout', this.handleTitlePointerOut);
1404
1556
  this.root.addEventListener('pointerout', this.handleTitlePointerOut);
@@ -1452,7 +1604,7 @@ export class NativeListWebEngine {
1452
1604
  this.document.fonts?.addEventListener('loadingdone', this.handleWindowResize);
1453
1605
  this.observeSectionIndexTransforms();
1454
1606
  this.applyFontFamilies(fontFamiliesJson);
1455
- this.applySnapshot(this.snapshot);
1607
+ this.applyValidatedSnapshot(this.snapshot);
1456
1608
  }
1457
1609
  validateStickyInset(value = 0) {
1458
1610
  if (!Number.isFinite(value) || value < 0) throw new Error('webStickyInset must be finite and non-negative');
@@ -1522,8 +1674,285 @@ export class NativeListWebEngine {
1522
1674
  return next;
1523
1675
  });
1524
1676
  if (!validated) return;
1677
+ this.applyValidatedSnapshot(validated);
1678
+ }
1679
+ applyValidatedSnapshot(validated) {
1680
+ this.pressedHelpElement = undefined;
1525
1681
  this.invalidateActionAnchor('snapshot');
1526
- this.setSnapshot(validated);
1682
+ const transition = validated.structuralTransition;
1683
+ const isNewTransition = transition !== undefined && transition.id !== this.lastStructuralTransitionId;
1684
+ if (transition) this.lastStructuralTransitionId = transition.id;
1685
+ const nextRows = effectiveRows(validated);
1686
+ const changedKeys = this.rows.length !== nextRows.length || this.rows.some((row, index) => row.key !== nextRows[index]?.key);
1687
+ const incompatibleActivePrefix = this.structuralPrefixTransition && (validated.layout.kind !== this.snapshot.layout.kind || validated.layout.orientation !== this.snapshot.layout.orientation || nextRows.some((row, index) => row.height !== this.rows[index]?.height || row.style?.container?.height !== this.rows[index]?.style?.container?.height));
1688
+ const shouldAnimate = this.hasAppliedSnapshot && isNewTransition && changedKeys && (this.snapshot.layout.kind === 'linear' || this.snapshot.layout.kind === 'table') && this.snapshot.layout.orientation !== 'horizontal' && (validated.layout.kind === 'linear' || validated.layout.kind === 'table') && validated.layout.orientation !== 'horizontal' && (transition?.durationMs ?? 220) > 0 && !this.document.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
1689
+ if (shouldAnimate && transition) this.applyStructuralSnapshot(validated, transition);else {
1690
+ if (isNewTransition || changedKeys || incompatibleActivePrefix) this.cancelStructuralAnimation();
1691
+ this.setSnapshot(validated);
1692
+ }
1693
+ this.hasAppliedSnapshot = true;
1694
+ }
1695
+ cancelStructuralAnimation() {
1696
+ this.structuralRevision += 1;
1697
+ if (this.structuralFrame !== undefined) this.document.defaultView?.cancelAnimationFrame(this.structuralFrame);
1698
+ if (this.structuralFinish !== undefined) this.document.defaultView?.clearTimeout(this.structuralFinish);
1699
+ this.structuralFrame = undefined;
1700
+ this.structuralFinish = undefined;
1701
+ this.structuralAnimations.forEach(animation => animation.cancel());
1702
+ this.structuralAnimations = [];
1703
+ if (this.structuralTail) {
1704
+ disposeWebImageRetries(this.structuralTail.layer);
1705
+ this.structuralTail.layer.remove();
1706
+ this.structuralTail = undefined;
1707
+ }
1708
+ if (this.structuralContentOverflow !== undefined) {
1709
+ this.content.style.overflow = this.structuralContentOverflow;
1710
+ this.structuralContentOverflow = undefined;
1711
+ }
1712
+ this.structuralPrefixTransition = false;
1713
+ if (this.structuralFooterElement && this.structuralFooterStyles) {
1714
+ this.structuralFooterElement.style.marginBottom = this.structuralFooterStyles.marginBottom;
1715
+ this.structuralFooterElement.style.transform = this.structuralFooterStyles.transform;
1716
+ this.structuralFooterElement.style.clipPath = this.structuralFooterStyles.clipPath;
1717
+ }
1718
+ this.structuralFooterElement = undefined;
1719
+ this.structuralFooterStyles = undefined;
1720
+ this.structuralScrollInterrupted = false;
1721
+ this.structuralRenderedHeight = undefined;
1722
+ this.structuralTargetHeight = undefined;
1723
+ this.structuralFooterCompensation = 0;
1724
+ this.structuralFooterExpansion = 0;
1725
+ this.structuralProgress = 0;
1726
+ const owner = this.externalScrollHost ?? this.viewport;
1727
+ if (this.structuralAnchorStyles) {
1728
+ owner.style.overflowAnchor = this.structuralAnchorStyles.owner;
1729
+ this.content.style.overflowAnchor = this.structuralAnchorStyles.content;
1730
+ this.structuralAnchorStyles = undefined;
1731
+ }
1732
+ }
1733
+ handleStructuralUserScroll = event => {
1734
+ if (event.type === 'pointerdown' && event.target !== (this.externalScrollHost ?? this.viewport) && event.pointerType !== 'touch') return;
1735
+ this.structuralScrollInterrupted = true;
1736
+ if (this.structuralPrefixTransition) {
1737
+ this.cancelStructuralAnimation();
1738
+ this.recomputeLayout();
1739
+ }
1740
+ };
1741
+ updateStructuralTailWindow() {
1742
+ const tail = this.structuralTail;
1743
+ if (!tail) return;
1744
+ const visible = visibleWebLayoutItems(tail.layout, this.currentOffset(), this.viewportLength(), this.viewportLength() * OVERSCAN_VIEWPORTS).filter(item => item.index >= tail.firstIndex);
1745
+ const desired = new Set(visible.map(item => item.index));
1746
+ tail.mounted.forEach((element, index) => {
1747
+ if (desired.has(index)) return;
1748
+ disposeWebImageRetries(element);
1749
+ element.remove();
1750
+ tail.mounted.delete(index);
1751
+ });
1752
+ visible.forEach(item => {
1753
+ if (tail.mounted.has(item.index)) return;
1754
+ const row = tail.rows[item.index];
1755
+ if (!row) return;
1756
+ const element = createElement(this.document, 'div', 'ok-native-list-item');
1757
+ element.setAttribute('aria-hidden', 'true');
1758
+ element.style.pointerEvents = 'none';
1759
+ element.style.opacity = String((row.style?.container?.opacity ?? row.opacity ?? 1) * (row.disabled ? 0.5 : 1));
1760
+ const body = createRowBody({
1761
+ document: this.document,
1762
+ snapshot: tail.snapshot,
1763
+ selectedKeys: selectionStateFromSnapshot(tail.snapshot).selectedKeys,
1764
+ itemIndex: item.index
1765
+ }, row);
1766
+ applyRowStyle(body, row);
1767
+ element.append(body);
1768
+ this.positionElement(element, item);
1769
+ tail.layer.append(element);
1770
+ tail.mounted.set(item.index, element);
1771
+ });
1772
+ }
1773
+ writeStructuralHeight(height) {
1774
+ if (this.externalScrollHost) this.writeExternalHeight(height);else this.content.style.height = `${height}px`;
1775
+ }
1776
+ reactFooterElement() {
1777
+ return this.contentBounds?.querySelector('[data-testid="native-list-footer-slot"]');
1778
+ }
1779
+ readReactFooterHeight() {
1780
+ return this.reactFooterElement()?.getBoundingClientRect().height ?? 0;
1781
+ }
1782
+ applyStructuralFooterVisual(progress) {
1783
+ const footer = this.structuralFooterElement;
1784
+ if (!footer || this.structuralFooterExpansion <= 0) return;
1785
+ const hidden = this.structuralFooterExpansion * (1 - progress);
1786
+ footer.style.marginBottom = `${-hidden}px`;
1787
+ footer.style.transform = `translateY(${-hidden}px)`;
1788
+ footer.style.clipPath = `inset(${hidden}px 0 0 0)`;
1789
+ }
1790
+ applyStructuralSnapshot(next, transition) {
1791
+ const owner = this.externalScrollHost ?? this.viewport;
1792
+ const oldOffset = owner.scrollTop;
1793
+ const oldScrollHeight = owner.scrollHeight;
1794
+ const oldHeight = this.structuralRenderedHeight ?? (this.content.getBoundingClientRect().height || this.layout.contentHeight);
1795
+ const nextFooterHeight = this.readReactFooterHeight();
1796
+ const oldFooterHeight = this.structuralRenderedHeight === undefined ? this.reactFooterHeight : this.structuralFooterFromHeight + (this.reactFooterHeight - this.structuralFooterFromHeight) * this.structuralProgress;
1797
+ const footerDifference = oldFooterHeight - nextFooterHeight;
1798
+ const startHeight = oldHeight + Math.max(0, footerDifference);
1799
+ const oldSnapshot = this.snapshot;
1800
+ const oldRows = this.rows;
1801
+ const oldLayout = this.layout;
1802
+ const oldItems = oldLayout.items;
1803
+ const nextRows = effectiveRows(next);
1804
+ const prefixCount = Math.min(oldRows.length, nextRows.length);
1805
+ const prefixTailChange = oldSnapshot.layout.kind === next.layout.kind && oldSnapshot.layout.orientation === next.layout.orientation && oldRows.length !== nextRows.length && oldRows.slice(0, prefixCount).every((row, index) => row.key === nextRows[index]?.key);
1806
+ const oldPositions = new Map();
1807
+ this.mounted.forEach((element, index) => {
1808
+ const row = this.rows[index];
1809
+ if (row) oldPositions.set(row.key, element.getBoundingClientRect().top);
1810
+ });
1811
+ const nextKeys = new Set(effectiveRows(next).map(row => row.key));
1812
+ const visibleOffset = this.currentOffset();
1813
+ const visibleSurvivor = oldItems.find(item => item.y + item.height > visibleOffset && nextKeys.has(item.key));
1814
+ const anchor = visibleSurvivor;
1815
+ this.cancelStructuralAnimation();
1816
+ this.structuralScrollInterrupted = false;
1817
+ this.structuralAnchorStyles = {
1818
+ owner: owner.style.overflowAnchor,
1819
+ content: this.content.style.overflowAnchor
1820
+ };
1821
+ owner.style.overflowAnchor = 'none';
1822
+ this.content.style.overflowAnchor = 'none';
1823
+ this.structuralContentOverflow = this.content.style.overflow;
1824
+ if (prefixTailChange) this.content.style.overflow = 'clip';
1825
+ this.structuralRenderedHeight = startHeight;
1826
+ this.structuralFooterFromHeight = oldFooterHeight;
1827
+ this.structuralFooterCompensation = Math.max(0, footerDifference);
1828
+ this.structuralFooterExpansion = Math.max(0, -footerDifference);
1829
+ this.structuralProgress = 0;
1830
+ this.reactFooterHeight = nextFooterHeight;
1831
+ this.structuralFooterElement = this.reactFooterElement() ?? undefined;
1832
+ if (this.structuralFooterElement) {
1833
+ const style = this.structuralFooterElement.style;
1834
+ this.structuralFooterStyles = {
1835
+ marginBottom: style.marginBottom,
1836
+ transform: style.transform,
1837
+ clipPath: style.clipPath
1838
+ };
1839
+ this.applyStructuralFooterVisual(0);
1840
+ }
1841
+ this.setSnapshot(next);
1842
+ const newHeight = this.layout.contentHeight;
1843
+ this.structuralTargetHeight = newHeight;
1844
+ const stablePrefixGeometry = prefixTailChange && oldItems.length === oldRows.length && this.layout.items.length === nextRows.length && oldItems.slice(0, prefixCount).every((item, index) => {
1845
+ const nextItem = this.layout.items[index];
1846
+ return nextItem?.y === item.y && nextItem?.height === item.height && nextItem?.width === item.width;
1847
+ });
1848
+ if (!stablePrefixGeometry) {
1849
+ this.content.style.overflow = this.structuralContentOverflow;
1850
+ this.structuralContentOverflow = undefined;
1851
+ } else {
1852
+ this.structuralPrefixTransition = true;
1853
+ }
1854
+ if (stablePrefixGeometry && nextRows.length < oldRows.length) {
1855
+ const layer = createElement(this.document, 'div', 'ok-native-list-structural-tail');
1856
+ layer.setAttribute('aria-hidden', 'true');
1857
+ layer.setAttribute('inert', '');
1858
+ Object.assign(layer.style, {
1859
+ position: 'absolute',
1860
+ inset: '0',
1861
+ pointerEvents: 'none'
1862
+ });
1863
+ this.content.append(layer);
1864
+ this.structuralTail = {
1865
+ snapshot: oldSnapshot,
1866
+ rows: oldRows,
1867
+ layout: oldLayout,
1868
+ firstIndex: prefixCount,
1869
+ layer,
1870
+ mounted: new Map()
1871
+ };
1872
+ this.updateStructuralTailWindow();
1873
+ }
1874
+ const nextAnchor = anchor && this.layout.items.find(item => item.key === anchor.key);
1875
+ const delta = nextAnchor && anchor ? nextAnchor.y - anchor.y : 0;
1876
+ let finalScrollHeight = oldScrollHeight + newHeight - oldHeight;
1877
+ if (stablePrefixGeometry && this.externalScrollHost) {
1878
+ const beforeMeasure = owner.scrollTop;
1879
+ const tailLayer = this.structuralTail?.layer;
1880
+ if (tailLayer) tailLayer.style.display = 'none';
1881
+ this.writeStructuralHeight(newHeight);
1882
+ this.applyStructuralFooterVisual(1);
1883
+ finalScrollHeight = owner.scrollHeight;
1884
+ this.writeStructuralHeight(startHeight);
1885
+ this.applyStructuralFooterVisual(0);
1886
+ if (tailLayer) tailLayer.style.display = '';
1887
+ owner.scrollTop = beforeMeasure;
1888
+ }
1889
+ const finalMaxOffset = Math.max(0, finalScrollHeight - owner.clientHeight);
1890
+ let targetOffset = Math.max(0, Math.min(oldOffset + delta, finalMaxOffset));
1891
+ if (!visibleSurvivor && transition.anchorKey && !(stablePrefixGeometry && nextRows.length > oldRows.length)) {
1892
+ const fallback = this.layout.items.find(item => item.key === transition.anchorKey);
1893
+ if (fallback) {
1894
+ const pagerStickyInset = Number.parseFloat(this.externalScrollHost?.style.getPropertyValue('--ok-collapsible-pager-sticky-inset') ?? '');
1895
+ const effectiveStickyInset = Math.max(this.externalStickyInset, Number.isFinite(pagerStickyInset) ? pagerStickyInset : 0);
1896
+ const fallbackTop = this.externalScrollHost ? this.externalGeometry().origin + fallback.y - effectiveStickyInset : resolveWebCollapsiblePagerRawOffset(Math.max(0, fallback.y - this.collapsiblePagerInsets().sticky), this.collapsiblePagerInsets().header);
1897
+ if (stablePrefixGeometry && this.externalScrollHost && this.reactFooterHeight > 0) {
1898
+ const footerTop = this.externalGeometry().origin + newHeight;
1899
+ const footerStart = Math.max(0, footerTop + this.reactFooterHeight - owner.clientHeight);
1900
+ const footerEnd = Math.max(0, footerTop - effectiveStickyInset);
1901
+ const nearestVisibleOffset = footerStart <= footerEnd ? Math.max(footerStart, Math.min(oldOffset, footerEnd)) : footerEnd;
1902
+ targetOffset = Math.max(0, Math.min(nearestVisibleOffset, finalMaxOffset));
1903
+ } else {
1904
+ targetOffset = Math.min(targetOffset, Math.max(0, fallbackTop));
1905
+ }
1906
+ }
1907
+ }
1908
+ const duration = transition.durationMs ?? 220;
1909
+ const easing = 'cubic-bezier(0.42, 0, 0.58, 1)';
1910
+ this.mounted.forEach((element, index) => {
1911
+ const row = this.rows[index];
1912
+ const previousTop = row && oldPositions.get(row.key);
1913
+ if (previousTop === undefined || typeof element.animate !== 'function') return;
1914
+ const offset = previousTop - element.getBoundingClientRect().top;
1915
+ if (Math.abs(offset) < 0.5) return;
1916
+ this.structuralAnimations.push(element.animate([{
1917
+ transform: `translateY(${offset}px)`
1918
+ }, {
1919
+ transform: 'translateY(0)'
1920
+ }], {
1921
+ duration,
1922
+ easing
1923
+ }));
1924
+ });
1925
+ const startedAt = this.document.defaultView?.performance.now() ?? Date.now();
1926
+ const revision = this.structuralRevision;
1927
+ const finish = () => {
1928
+ if (this.destroyed || revision !== this.structuralRevision) return;
1929
+ const ownsScroll = !this.structuralScrollInterrupted;
1930
+ this.cancelStructuralAnimation();
1931
+ this.recomputeLayout();
1932
+ if (stablePrefixGeometry && ownsScroll) {
1933
+ owner.scrollTop = Math.max(0, Math.min(targetOffset, owner.scrollHeight - owner.clientHeight));
1934
+ }
1935
+ };
1936
+ const step = time => {
1937
+ if (this.destroyed || revision !== this.structuralRevision) return;
1938
+ if (this.structuralPrefixTransition && this.document.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
1939
+ finish();
1940
+ return;
1941
+ }
1942
+ const progress = Math.min(1, (time - startedAt) / duration);
1943
+ const eased = progress * progress * (3 - 2 * progress);
1944
+ this.structuralProgress = eased;
1945
+ const renderedHeight = oldHeight + ((this.structuralTargetHeight ?? newHeight) - oldHeight) * eased + this.structuralFooterCompensation * (1 - eased);
1946
+ this.structuralRenderedHeight = renderedHeight;
1947
+ this.writeStructuralHeight(renderedHeight);
1948
+ this.applyStructuralFooterVisual(eased);
1949
+ if (!this.structuralScrollInterrupted) owner.scrollTop = oldOffset + (targetOffset - oldOffset) * eased;
1950
+ this.externalGeometryCache = undefined;
1951
+ this.updateStructuralTailWindow();
1952
+ if (progress < 1) this.structuralFrame = this.document.defaultView?.requestAnimationFrame(step);
1953
+ };
1954
+ this.structuralFrame = this.document.defaultView?.requestAnimationFrame(step);
1955
+ this.structuralFinish = this.document.defaultView?.setTimeout(finish, duration + 40);
1527
1956
  }
1528
1957
  currentContentHeight() {
1529
1958
  return this.layout.contentHeight;
@@ -1696,6 +2125,8 @@ export class NativeListWebEngine {
1696
2125
  }
1697
2126
  destroy() {
1698
2127
  if (this.destroyed) return;
2128
+ this.cancelStructuralAnimation();
2129
+ this.pressedHelpElement = undefined;
1699
2130
  this.invalidateActionAnchor('destroy');
1700
2131
  this.destroyed = true;
1701
2132
  if (this.frameHandle !== undefined) this.cancelFrame(this.frameHandle);
@@ -1708,6 +2139,7 @@ export class NativeListWebEngine {
1708
2139
  this.document.defaultView?.removeEventListener('resize', this.handleWindowResize);
1709
2140
  this.pendingScroll = undefined;
1710
2141
  (this.externalScrollHost ?? this.viewport).removeEventListener('ok-collapsible-pager-cancel-scroll-command', this.handlePagerScrollCommandCancel);
2142
+ for (const event of ['wheel', 'pointerdown', 'touchstart']) (this.externalScrollHost ?? this.viewport).removeEventListener(event, this.handleStructuralUserScroll);
1711
2143
  this.viewport.removeEventListener('scroll', this.handleScroll);
1712
2144
  (this.externalScrollHost ?? this.viewport).removeEventListener('ok-collapsible-pager-insets-changed', this.handleCollapsiblePagerInsetsChanged);
1713
2145
  this.root.removeEventListener('click', this.handleClick);
@@ -1719,6 +2151,7 @@ export class NativeListWebEngine {
1719
2151
  this.root.removeEventListener('pointerup', this.handleMarketPointerEnd);
1720
2152
  this.root.removeEventListener('pointercancel', this.handleMarketPointerEnd);
1721
2153
  this.root.removeEventListener('pointerover', this.handleTitlePointerOver);
2154
+ this.root.removeEventListener('pointerdown', this.handleIconHelpPointerDown);
1722
2155
  this.root.removeEventListener('focusin', this.handleTitlePointerOver);
1723
2156
  this.root.removeEventListener('focusout', this.handleTitlePointerOut);
1724
2157
  this.root.removeEventListener('pointerout', this.handleTitlePointerOut);
@@ -1757,6 +2190,7 @@ export class NativeListWebEngine {
1757
2190
  }
1758
2191
  setSnapshot(snapshot, selectedKeys) {
1759
2192
  this.cancelMarketPointer();
2193
+ this.pressedHelpElement = undefined;
1760
2194
  this.measuredRendererHeights.clear();
1761
2195
  this.snapshot = snapshot;
1762
2196
  this.rows = effectiveRows(snapshot);
@@ -1774,6 +2208,7 @@ export class NativeListWebEngine {
1774
2208
  '--nl-row': theme.rowBackground,
1775
2209
  '--nl-selected': theme.rowSelectedBackground,
1776
2210
  '--nl-pressed': theme.rowPressedBackground,
2211
+ '--nl-hover': theme.rowHoverBackground ?? theme.rowPressedBackground,
1777
2212
  '--nl-subdued': theme.subduedBackground,
1778
2213
  '--nl-strong': theme.strongBackground,
1779
2214
  '--nl-image-fallback-background': theme.imageFallbackBackground ?? 'transparent',
@@ -1790,6 +2225,7 @@ export class NativeListWebEngine {
1790
2225
  '--nl-separator': theme.separator,
1791
2226
  '--nl-accent': theme.accent,
1792
2227
  '--nl-positive': theme.positive,
2228
+ '--nl-success-background': theme.successBackground,
1793
2229
  '--nl-negative': theme.negative,
1794
2230
  '--nl-critical': theme.criticalBackground,
1795
2231
  '--nl-inverse': theme.inverseBackground,
@@ -1850,10 +2286,24 @@ export class NativeListWebEngine {
1850
2286
  }
1851
2287
  recomputeLayout = () => {
1852
2288
  if (this.destroyed) return;
2289
+ if (this.structuralPrefixTransition && this.lastViewportWidth > 0 && this.viewport.clientWidth > 0 && this.viewport.clientWidth !== this.lastViewportWidth) this.cancelStructuralAnimation();
2290
+ if (this.structuralRenderedHeight === undefined) this.reactFooterHeight = this.readReactFooterHeight();else {
2291
+ const nextFooterHeight = this.readReactFooterHeight();
2292
+ const footerDelta = this.reactFooterHeight - nextFooterHeight;
2293
+ if (footerDelta !== 0) {
2294
+ this.reactFooterHeight = nextFooterHeight;
2295
+ const priorCompensation = this.structuralFooterCompensation;
2296
+ const difference = this.structuralFooterFromHeight - nextFooterHeight;
2297
+ this.structuralFooterCompensation = Math.max(0, difference);
2298
+ this.structuralFooterExpansion = Math.max(0, -difference);
2299
+ this.structuralRenderedHeight += (this.structuralFooterCompensation - priorCompensation) * (1 - this.structuralProgress);
2300
+ this.applyStructuralFooterVisual(this.structuralProgress);
2301
+ }
2302
+ }
1853
2303
  const cachedWidth = this.lastViewportWidth;
1854
2304
  const cachedHeight = this.lastViewportHeight;
1855
2305
  const precomputedLayout = this.externalScrollHost && cachedWidth > 0 && cachedHeight > 0 ? this.computeLayout(cachedWidth, cachedHeight) : undefined;
1856
- if (precomputedLayout) {
2306
+ if (precomputedLayout && this.structuralRenderedHeight === undefined) {
1857
2307
  // Commit appended row extent before measuring React-owned slot changes.
1858
2308
  this.writeExternalHeight(Math.max(this.layout.contentHeight, precomputedLayout.contentHeight));
1859
2309
  }
@@ -1883,11 +2333,13 @@ export class NativeListWebEngine {
1883
2333
  const previousHorizontal = this.layout.horizontal;
1884
2334
  this.layout = precomputedLayout && viewportWidth === cachedWidth && viewportHeight === cachedHeight ? precomputedLayout : this.computeLayout(viewportWidth, viewportHeight);
1885
2335
  this.content.style.width = String(this.layout.contentWidth) + 'px';
1886
- this.content.style.height = String(this.layout.contentHeight) + 'px';
2336
+ const renderedHeight = this.structuralRenderedHeight ?? this.layout.contentHeight;
2337
+ this.content.style.height = String(renderedHeight) + 'px';
1887
2338
  if (this.externalScrollHost) {
1888
- this.writeExternalHeight(this.layout.contentHeight);
2339
+ this.writeExternalHeight(renderedHeight);
1889
2340
  this.externalGeometryCache = undefined;
1890
2341
  }
2342
+ if (this.structuralRenderedHeight !== undefined) this.structuralTargetHeight = this.layout.contentHeight;
1891
2343
  this.renderSectionIndex(this.viewport.clientHeight <= 0 ? DEFAULT_VIEWPORT_HEIGHT : Math.max(1, viewportHeight), stickyInset);
1892
2344
  if (previousHorizontal !== this.layout.horizontal) {
1893
2345
  this.viewport.scrollLeft = 0;
@@ -1995,6 +2447,7 @@ export class NativeListWebEngine {
1995
2447
  }
1996
2448
  this.updateVisibleSelection();
1997
2449
  this.updateVisibleState();
2450
+ this.updateStructuralTailWindow();
1998
2451
  }
1999
2452
  positionElement(element, item) {
2000
2453
  element.style.transform = 'translate3d(' + String(item.x) + 'px,' + String(item.y) + 'px,0)';
@@ -2040,7 +2493,7 @@ export class NativeListWebEngine {
2040
2493
  const body = reusableBody ?? createRowBody(context, row);
2041
2494
  if (reusableBody) {
2042
2495
  resetRowContainerStyle(body);
2043
- registered.bind(body, rendererPrimitives(context));
2496
+ registered.bind(body, rendererPrimitives(context, Boolean(row.disabled)));
2044
2497
  }
2045
2498
  // OneKey patch: explicit selector fields preserve original page geometry.
2046
2499
  element.style.contain = row.backgroundFullWidth ? 'layout style' : '';
@@ -2522,6 +2975,7 @@ export class NativeListWebEngine {
2522
2975
  return anchor.actionElement.isConnected && anchor.rowElement.isConnected && anchor.rowElement.contains(anchor.actionElement) && anchor.rowElement.dataset.nativeListBindingEpoch === anchor.bindingEpoch;
2523
2976
  }
2524
2977
  invalidateActionAnchorForElement(element) {
2978
+ if (this.pressedHelpElement && element.contains(this.pressedHelpElement)) this.pressedHelpElement = undefined;
2525
2979
  const anchor = this.actionAnchor;
2526
2980
  if (anchor && (element === anchor.rowElement || element.contains(anchor.actionElement))) {
2527
2981
  this.invalidateActionAnchor('rebind');
@@ -2594,9 +3048,9 @@ export class NativeListWebEngine {
2594
3048
  if (event.target.closest('[data-native-list-action]')) return;
2595
3049
  const rowElement = event.target.closest('[data-native-list-row-key]');
2596
3050
  const row = this.rowAtElement(rowElement);
2597
- if (row?.type !== 'market' || !isRowPressEnabled(row)) return;
3051
+ if (row?.type !== 'market' && row?.type !== 'identity' || !isRowPressEnabled(row)) return;
2598
3052
  this.cancelMarketPointer();
2599
- if (row.pressInActionKey) this.emitRowAction(row, row.pressInActionKey, rowElement ?? undefined, rowElement ?? undefined);
3053
+ if (row.type === 'market' && row.pressInActionKey) this.emitRowAction(row, row.pressInActionKey, rowElement ?? undefined, rowElement ?? undefined);
2600
3054
  if (!row.longPressActionKey || !rowElement) return;
2601
3055
  markActionAnchorSource(rowElement, 'row');
2602
3056
  const bindingEpoch = rowElement.dataset.nativeListBindingEpoch;
@@ -2605,7 +3059,7 @@ export class NativeListWebEngine {
2605
3059
  if (!current || current.pointerId !== event.pointerId) return;
2606
3060
  const currentRow = this.rowAtElement(rowElement);
2607
3061
  this.marketPointer = undefined;
2608
- if (!rowElement.isConnected || rowElement.dataset.nativeListBindingEpoch !== bindingEpoch || currentRow?.key !== row.key || currentRow.type !== 'market' || !isRowPressEnabled(currentRow) || !currentRow.longPressActionKey) return;
3062
+ if (!rowElement.isConnected || rowElement.dataset.nativeListBindingEpoch !== bindingEpoch || currentRow?.key !== row.key || currentRow.type !== 'market' && currentRow.type !== 'identity' || !isRowPressEnabled(currentRow) || !currentRow.longPressActionKey) return;
2609
3063
  this.marketLongPressFired = true;
2610
3064
  this.emitRowAction(currentRow, currentRow.longPressActionKey, rowElement, rowElement, {
2611
3065
  x: event.clientX,
@@ -2616,7 +3070,7 @@ export class NativeListWebEngine {
2616
3070
  this.marketPointer = {
2617
3071
  pointerId: event.pointerId,
2618
3072
  pointerType: event.pointerType,
2619
- compact: !row.webTable,
3073
+ compact: row.type === 'identity' || !row.webTable,
2620
3074
  rowKey: row.key,
2621
3075
  startX: event.clientX,
2622
3076
  startY: event.clientY,
@@ -2652,7 +3106,14 @@ export class NativeListWebEngine {
2652
3106
  if (!(target instanceof Element)) return;
2653
3107
  const action = target.closest('[data-native-list-hover-action]');
2654
3108
  if (!action || event.relatedTarget instanceof Node && action.contains(event.relatedTarget)) return;
3109
+ if (action.dataset.nativeListIconHelp === 'true') {
3110
+ if (event.type === 'pointerover' && event.pointerType === 'touch') return;
3111
+ if (this.pressedHelpElement === action) return;
3112
+ }
2655
3113
  if (action.classList.contains('ok-native-list-market-subtitle-prefix') && action.scrollWidth <= action.clientWidth + 1) return;
3114
+ this.emitHoverAction(action);
3115
+ };
3116
+ emitHoverAction(action) {
2656
3117
  const rowElement = action.closest('[data-native-list-row-key]');
2657
3118
  const row = this.rowAtElement(rowElement);
2658
3119
  const memberKey = action.closest('[data-native-list-group-member-key]')?.dataset.nativeListGroupMemberKey;
@@ -2661,12 +3122,26 @@ export class NativeListWebEngine {
2661
3122
  // Legacy Web: hover checks only the resolved source (the member for a
2662
3123
  // WalletGroup), not the enclosing row's `disabled`.
2663
3124
  if (sourceRow && !sourceRow.disabled && actionKey) this.emitRowAction(sourceRow, actionKey, action, rowElement ?? undefined);
3125
+ }
3126
+ handleIconHelpPointerDown = event => {
3127
+ const target = event.target;
3128
+ if (!(target instanceof Element)) return;
3129
+ const action = target.closest('[data-native-list-icon-help="true"]');
3130
+ if (!action) return;
3131
+ if (event.pointerType === 'touch') {
3132
+ if (this.actionAnchor?.actionElement === action && this.actionAnchor.open) this.invalidateActionAnchor('pointerLeave');else this.emitHoverAction(action);
3133
+ } else {
3134
+ this.pressedHelpElement = action;
3135
+ if (this.actionAnchor?.actionElement === action) this.invalidateActionAnchor('pointerLeave');
3136
+ }
2664
3137
  };
2665
3138
  handleTitlePointerOut = event => {
2666
3139
  const target = event.target;
2667
3140
  if (!(target instanceof Element)) return;
2668
3141
  const action = target.closest('[data-native-list-hover-action]');
2669
3142
  if (!action || event.relatedTarget instanceof Node && action.contains(event.relatedTarget)) return;
3143
+ if (action.dataset.nativeListIconHelp === 'true' && event.type === 'pointerout' && event.pointerType === 'touch') return;
3144
+ if (event.type === 'pointerout' && this.pressedHelpElement === action) this.pressedHelpElement = undefined;
2670
3145
  if (this.actionAnchor?.actionElement === action) this.invalidateActionAnchor('pointerLeave');
2671
3146
  };
2672
3147
  handleClick = event => {
@@ -2678,6 +3153,11 @@ export class NativeListWebEngine {
2678
3153
  }
2679
3154
  const target = event.target;
2680
3155
  if (!(target instanceof Element)) return;
3156
+ if (target.closest('[data-native-list-icon-help="true"]')) {
3157
+ event.preventDefault();
3158
+ event.stopPropagation();
3159
+ return;
3160
+ }
2681
3161
  const rowElement = target.closest('[data-native-list-row-key]');
2682
3162
  const row = this.rowAtElement(rowElement);
2683
3163
  if (!row || row.disabled) return;
@@ -2689,6 +3169,8 @@ export class NativeListWebEngine {
2689
3169
  // member press is blocked by `isRowPressEnabled` in `handleRowPress`.
2690
3170
  const action = target.closest('[data-native-list-action]');
2691
3171
  if (action) {
3172
+ if (action.tagName === 'BUTTON' && action.hasAttribute('disabled')) return;
3173
+ if (action.classList.contains('ok-native-list-trailing-button')) event.stopPropagation();
2692
3174
  const scope = action.dataset.selectionScope;
2693
3175
  if (scope === 'row') this.activateSelection({
2694
3176
  scope: 'row'
@@ -2703,6 +3185,11 @@ export class NativeListWebEngine {
2703
3185
  this.handleRowPress(sourceRow, rowElement ?? undefined, memberElement ?? rowElement ?? undefined);
2704
3186
  };
2705
3187
  handleKeyDown = event => {
3188
+ if ((event.key === 'Enter' || event.key === ' ') && event.target instanceof HTMLElement && event.target.matches('[role="button"][data-native-list-hover-action]')) {
3189
+ event.preventDefault();
3190
+ if (this.actionAnchor?.actionElement !== event.target || !this.actionAnchor.open) this.emitHoverAction(event.target);
3191
+ return;
3192
+ }
2706
3193
  // OneKey patch: non-button title help supports keyboard activation.
2707
3194
  if ((event.key === 'Enter' || event.key === ' ') && event.target instanceof HTMLElement && event.target.matches('[role="button"][data-native-list-action]')) {
2708
3195
  event.preventDefault();
@@ -2804,6 +3291,7 @@ export class NativeListWebEngine {
2804
3291
  };
2805
3292
  handleScroll = () => {
2806
3293
  this.externalGeometryCache = undefined;
3294
+ this.pressedHelpElement = undefined;
2807
3295
  this.cancelMarketPointer();
2808
3296
  this.invalidateActionAnchor('scroll');
2809
3297
  this.scheduleFrame();