@onekeyfe/react-native-native-list 6.7.0-alpha.375 → 6.7.0-alpha.382

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
@@ -801,7 +801,7 @@ export const WEB_LIST_CSS = `
801
801
  .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}
802
802
 
803
803
  [data-native-list-selector="walletSidebar"] .ok-native-list-title{max-width:100%;min-width:0}
804
- .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)}
804
+ .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)}
805
805
  .ok-native-list-viewport-frame{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
806
806
  .ok-native-list-viewport{position:absolute;inset:0;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;scrollbar-gutter:stable}
807
807
  .ok-native-list-content{position:relative;min-width:100%;min-height:100%}
@@ -812,7 +812,7 @@ export const WEB_LIST_CSS = `
812
812
  .ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
813
813
  .ok-native-list-item[data-native-list-disabled="true"]>.ok-native-list-row{cursor:default}
814
814
  .ok-native-list-item[data-native-list-row-press-enabled="true"]>.ok-native-list-row{cursor:pointer}
815
- .ok-native-list-item[data-native-list-row-press-enabled="true"]:hover>.ok-native-list-row{background:var(--nl-pressed)}
815
+ .ok-native-list-item[data-native-list-row-press-enabled="true"]:hover>.ok-native-list-row{background:var(--nl-hover)}
816
816
  .ok-native-list-item[data-native-list-row-press-enabled="true"]:active>.ok-native-list-row{background:var(--nl-pressed)}
817
817
  .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)}
818
818
  .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)}
@@ -843,16 +843,23 @@ export const WEB_LIST_CSS = `
843
843
  .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)}
844
844
  .ok-native-list-item[data-group-position="single"]>.ok-native-list-row{border-radius:var(--nl-group-radius,12px)}
845
845
  .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}
846
+ .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}
847
+ .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}
848
+ .ok-native-list-icon-button.ok-native-list-tertiary-icon:focus-visible:not(:disabled){outline:2px solid var(--nl-accent)}
849
+ .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}
846
850
  .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)}
847
851
  .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)}
848
852
  .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}
849
853
  .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}
850
854
  .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}
851
- .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}
855
+ .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}
852
856
  .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)}}
853
- .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}
857
+ .ok-native-list-icon-button.ok-native-list-decorative-refresh{min-width:16px;width:16px;height:16px}
858
+ .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}
859
+ .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)}
854
860
  .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}
855
861
  .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)}
862
+ .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}
856
863
  .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)}
857
864
  .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}
858
865
  .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)}
@@ -901,6 +908,7 @@ export const WEB_LIST_CSS = `
901
908
  .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)}
902
909
  .ok-native-list-root [data-nl-container-border="true"]{position:relative}
903
910
  .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}
911
+ .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}
904
912
  .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)}
905
913
  `;
906
914
 
@@ -1187,6 +1195,45 @@ const selectorIcons: Readonly<
1187
1195
  }>
1188
1196
  >
1189
1197
  > = {
1198
+ RefreshCcwOutline: {
1199
+ viewBox: '0 0 24 24',
1200
+ paths: [
1201
+ {
1202
+ 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',
1203
+ fill: 'currentColor',
1204
+ fillRule: 'nonzero',
1205
+ opacity: 1,
1206
+ },
1207
+ ],
1208
+ },
1209
+ TicketOutline: {
1210
+ viewBox: '0 0 24 24',
1211
+ paths: [
1212
+ {
1213
+ d: 'M16 17.01h-2V15h2v2.01Zm0-4h-2V11h2v2.01Zm0-4h-2V7h2v2.01Z',
1214
+ fill: 'currentColor',
1215
+ fillRule: 'nonzero',
1216
+ opacity: 1,
1217
+ },
1218
+ {
1219
+ 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',
1220
+ fill: 'currentColor',
1221
+ fillRule: 'evenodd',
1222
+ opacity: 1,
1223
+ },
1224
+ ],
1225
+ },
1226
+ GasSolid: {
1227
+ viewBox: '0 0 24 24',
1228
+ paths: [
1229
+ {
1230
+ d: 'M15 9h4v7h1V8.414L17.586 6 19 4.586l3 3V18h-5v-7h-2v8h1v2H2v-2h1V3h12zM6 9v2h6V9z',
1231
+ fill: 'currentColor',
1232
+ fillRule: 'evenodd',
1233
+ opacity: 1,
1234
+ },
1235
+ ],
1236
+ },
1190
1237
  CryptoCoinOutline: {
1191
1238
  viewBox: '0 0 24 24',
1192
1239
  paths: [
@@ -1486,6 +1533,7 @@ function applySelectorIcon(element: HTMLElement, name: string) {
1486
1533
 
1487
1534
  function iconGlyph(name: string): string {
1488
1535
  const normalized = name.toLocaleLowerCase();
1536
+ if (normalized === 'dotversolid') return '⋮';
1489
1537
  if (normalized.includes('chevron')) {
1490
1538
  if (normalized.includes('top')) return '⌃';
1491
1539
  if (normalized.includes('bottom')) return '⌄';
@@ -1563,6 +1611,10 @@ function createVisual(
1563
1611
  : visualBackground;
1564
1612
  const fallbackIcon =
1565
1613
  'fallbackIcon' in visual ? visual.fallbackIcon : undefined;
1614
+ const fallbackBackground =
1615
+ fallbackIcon?.backgroundColor ?? 'var(--nl-strong)';
1616
+ if (!source && fallbackIcon?.backgroundColor)
1617
+ frame.style.background = fallbackBackground;
1566
1618
  const handlesSourceFallback =
1567
1619
  showsSourcePlaceholder &&
1568
1620
  (fallbackIcon !== undefined || 'fallbackText' in visual);
@@ -1578,6 +1630,28 @@ function createVisual(
1578
1630
  if (image.complete && image.naturalWidth > 0) restoreBackground();
1579
1631
  }
1580
1632
  if (selectorPresentation) paintSelectorImageBackground(image, frame);
1633
+ if (
1634
+ source?.loadingStrategy === 'skeleton' &&
1635
+ !(image.complete && image.naturalWidth > 0)
1636
+ ) {
1637
+ const skeleton = createElement(
1638
+ context.document,
1639
+ 'span',
1640
+ 'ok-native-list-image-skeleton'
1641
+ );
1642
+ const background = resolvedTheme(context.snapshot).background;
1643
+ const rgb = /^#([0-9a-f]{6})/i.exec(background)?.[1];
1644
+ if (rgb) {
1645
+ const red = Number.parseInt(rgb.slice(0, 2), 16);
1646
+ const green = Number.parseInt(rgb.slice(2, 4), 16);
1647
+ const blue = Number.parseInt(rgb.slice(4, 6), 16);
1648
+ if (red * 299 + green * 587 + blue * 114 < 128_000)
1649
+ skeleton.dataset.dark = 'true';
1650
+ }
1651
+ frame.appendChild(skeleton);
1652
+ image.addEventListener('load', () => skeleton.remove(), { once: true });
1653
+ image.addEventListener('error', () => skeleton.remove(), { once: true });
1654
+ }
1581
1655
  } else if (!source || showsSourcePlaceholder) {
1582
1656
  frame.appendChild(
1583
1657
  createElement(
@@ -1632,13 +1706,29 @@ function createVisual(
1632
1706
  ? visual.fallbackText ?? ''
1633
1707
  : ''
1634
1708
  );
1709
+ if (
1710
+ 'fallbackTextColor' in visual &&
1711
+ visual.fallbackTextColor &&
1712
+ !fallbackIcon
1713
+ )
1714
+ fallback.style.color = visual.fallbackTextColor;
1635
1715
  if (fallbackIcon) {
1636
1716
  applySelectorIcon(fallback, fallbackIcon.name);
1637
1717
  if (fallbackIcon.tintColor)
1638
1718
  fallback.style.color = fallbackIcon.tintColor;
1719
+ if (fallbackIcon.size) {
1720
+ const svg = fallback.querySelector<SVGElement>('svg');
1721
+ if (svg) {
1722
+ svg.style.width = `${fallbackIcon.size}px`;
1723
+ svg.style.height = `${fallbackIcon.size}px`;
1724
+ }
1725
+ }
1639
1726
  }
1640
- fallback.style.backgroundColor =
1641
- 'var(--nl-image-fallback-background, transparent)';
1727
+ if (fallbackIcon?.backgroundColor)
1728
+ frame.style.background = fallbackBackground;
1729
+ else
1730
+ fallback.style.backgroundColor =
1731
+ 'var(--nl-image-fallback-background, transparent)';
1642
1732
  frame.prepend(fallback);
1643
1733
  };
1644
1734
  if (image) image.addEventListener('error', showFallback, { once: true });
@@ -1648,6 +1738,9 @@ function createVisual(
1648
1738
  frame.style.border =
1649
1739
  '2px dashed ' + (visual.borderColor ?? 'var(--nl-disabled)');
1650
1740
  frame.style.boxSizing = 'border-box';
1741
+ } else if ('borderColor' in visual && visual.borderColor) {
1742
+ frame.style.border = '1px solid ' + visual.borderColor;
1743
+ frame.style.boxSizing = 'border-box';
1651
1744
  }
1652
1745
  if ('overlays' in visual)
1653
1746
  visual.overlays?.forEach((overlay) => {
@@ -1677,6 +1770,16 @@ function createVisual(
1677
1770
  corner.style.lineHeight = '16px';
1678
1771
  applyTextWeight(corner, '400');
1679
1772
  }
1773
+ if (overlay.fontWeight)
1774
+ applyTextWeight(
1775
+ corner,
1776
+ {
1777
+ regular: 400,
1778
+ medium: 500,
1779
+ semibold: 600,
1780
+ bold: 700,
1781
+ }[overlay.fontWeight]
1782
+ );
1680
1783
  if (
1681
1784
  isWalletText ||
1682
1785
  overlay.width !== undefined ||
@@ -1697,7 +1800,31 @@ function createVisual(
1697
1800
  if (overlay.image) {
1698
1801
  const overlayImage = createImage(context, overlay.image);
1699
1802
  if (overlayImage) {
1803
+ if (
1804
+ (overlay.padding ?? 0) > 0 &&
1805
+ (overlay.width ?? size) === (overlay.height ?? size)
1806
+ )
1807
+ overlayImage.style.borderRadius = '50%';
1700
1808
  corner.appendChild(overlayImage);
1809
+ if (overlay.fallbackIcon) {
1810
+ overlayImage.addEventListener(
1811
+ 'error',
1812
+ () => {
1813
+ disposeWebImageRetries(overlayImage);
1814
+ overlayImage.remove();
1815
+ const glyph = createElement(
1816
+ context.document,
1817
+ 'span',
1818
+ 'ok-native-list-overlay-fallback'
1819
+ );
1820
+ applySelectorIcon(glyph, overlay.fallbackIcon!.name);
1821
+ if (overlay.fallbackIcon?.tintColor)
1822
+ glyph.style.color = overlay.fallbackIcon.tintColor;
1823
+ corner.appendChild(glyph);
1824
+ },
1825
+ { once: true }
1826
+ );
1827
+ }
1701
1828
  if (selectorPresentation)
1702
1829
  paintSelectorImageBackground(
1703
1830
  overlayImage,
@@ -1862,8 +1989,44 @@ function createAccessory(
1862
1989
  context: RenderContext,
1863
1990
  rowKey: string,
1864
1991
  accessory: TrailingAccessory,
1865
- slot: number
1992
+ slot: number,
1993
+ rowDisabled: boolean
1866
1994
  ): HTMLElement {
1995
+ if (accessory.kind === 'button') {
1996
+ const element = createElement(
1997
+ context.document,
1998
+ 'button',
1999
+ 'ok-native-list-trailing-button',
2000
+ accessory.label
2001
+ ) as HTMLButtonElement;
2002
+ element.type = 'button';
2003
+ element.disabled = Boolean(rowDisabled || accessory.disabled);
2004
+ element.setAttribute('aria-disabled', String(element.disabled));
2005
+ element.setAttribute(
2006
+ 'aria-label',
2007
+ accessory.accessibilityLabel ?? accessory.label
2008
+ );
2009
+ setData(element, 'testid', accessory.testID);
2010
+ setData(element, 'nativeListAction', accessory.actionKey);
2011
+ markActionAnchorSource(element, 'trailingAccessory', slot);
2012
+ if (accessory.textColor)
2013
+ element.style.setProperty('--nl-button-text', accessory.textColor);
2014
+ if (accessory.backgroundColor)
2015
+ element.style.setProperty('--nl-button-bg', accessory.backgroundColor);
2016
+ if (accessory.pressedBackgroundColor)
2017
+ element.style.setProperty(
2018
+ '--nl-button-pressed',
2019
+ accessory.pressedBackgroundColor
2020
+ );
2021
+ if (accessory.hoverBackgroundColor)
2022
+ element.style.setProperty(
2023
+ '--nl-button-hover',
2024
+ accessory.hoverBackgroundColor
2025
+ );
2026
+ if (accessory.focusRingColor)
2027
+ element.style.setProperty('--nl-button-focus', accessory.focusRingColor);
2028
+ return element;
2029
+ }
1867
2030
  if (accessory.kind === 'checkbox') {
1868
2031
  const element = createCheckbox(context, rowKey, accessory);
1869
2032
  markActionAnchorSource(element, 'trailingAccessory', slot);
@@ -1878,9 +2041,21 @@ function createAccessory(
1878
2041
  accessory.tintColor
1879
2042
  );
1880
2043
  markActionAnchorSource(element, 'trailingAccessory', slot);
2044
+ if (accessory.name === 'RefreshCcwOutline' && !accessory.actionKey) {
2045
+ element.classList.add('ok-native-list-decorative-refresh');
2046
+ const glyph = element.querySelector('svg');
2047
+ glyph?.setAttribute('width', '16');
2048
+ glyph?.setAttribute('height', '16');
2049
+ }
2050
+ if (accessory.buttonStyle === 'tertiary') {
2051
+ element.classList.add('ok-native-list-tertiary-icon');
2052
+ setData(element, 'nativeListAnchorInset', 7);
2053
+ }
1881
2054
  setData(element, 'testid', accessory.testID);
1882
2055
  if (accessory.hoverActionKey)
1883
2056
  setData(element, 'nativeListHoverAction', accessory.hoverActionKey);
2057
+ if (accessory.hoverActionKey && accessory.buttonStyle === 'tertiary')
2058
+ setData(element, 'nativeListIconHelp', true);
1884
2059
  if (accessory.accessibilityLabel)
1885
2060
  element.setAttribute('aria-label', accessory.accessibilityLabel);
1886
2061
  const row = context.snapshot.rows[context.itemIndex];
@@ -1928,6 +2103,25 @@ function createAccessory(
1928
2103
  accessory.primary
1929
2104
  );
1930
2105
  primary.style.color = toneColor(accessory.primaryTone, 'primary');
2106
+ applyValueSegments(primary, accessory.primaryTextSegments, 16, 20, 500);
2107
+ if (accessory.primaryFontWeight === 'medium')
2108
+ applyTextWeight(primary, 500);
2109
+ if (accessory.primaryHelp) {
2110
+ const help = accessory.primaryHelp;
2111
+ primary.tabIndex = 0;
2112
+ primary.setAttribute('role', 'button');
2113
+ if (help.presentation === 'tooltip') {
2114
+ setData(primary, 'nativeListHoverAction', help.actionKey);
2115
+ setData(primary, 'nativeListIconHelp', true);
2116
+ } else {
2117
+ setData(primary, 'nativeListAction', help.actionKey);
2118
+ }
2119
+ if (help.underline === 'dashed') {
2120
+ primary.style.textDecoration = 'underline dashed';
2121
+ primary.style.textUnderlineOffset = '4px';
2122
+ }
2123
+ markActionAnchorSource(primary, 'trailingAccessory', slot);
2124
+ }
1931
2125
  const secondary = createElement(
1932
2126
  context.document,
1933
2127
  'span',
@@ -1935,6 +2129,13 @@ function createAccessory(
1935
2129
  accessory.secondary
1936
2130
  );
1937
2131
  secondary.style.color = toneColor(accessory.secondaryTone, 'secondary');
2132
+ applyValueSegments(
2133
+ secondary,
2134
+ accessory.secondaryTextSegments,
2135
+ 14,
2136
+ 20,
2137
+ 400
2138
+ );
1938
2139
  element.replaceChildren(primary, '\n', secondary);
1939
2140
  element.classList.add('ok-native-list-amounts');
1940
2141
  break;
@@ -1963,11 +2164,11 @@ function createAccessory(
1963
2164
  return element;
1964
2165
  }
1965
2166
 
1966
- function rendererPrimitives(context: RenderContext) {
2167
+ function rendererPrimitives(context: RenderContext, rowDisabled: boolean) {
1967
2168
  return {
1968
2169
  background: resolvedTheme(context.snapshot).background,
1969
2170
  accessory: (key: string, descriptor: TrailingAccessory, slot: number) =>
1970
- createAccessory(context, key, descriptor, slot),
2171
+ createAccessory(context, key, descriptor, slot, rowDisabled),
1971
2172
  visual: (source: LeadingVisual | undefined, presentation?: string) =>
1972
2173
  createVisual(context, source, presentation),
1973
2174
  thumbnail: (source: ImageSource) =>
@@ -1988,7 +2189,10 @@ export function createRowBody(
1988
2189
  context: RenderContext,
1989
2190
  row: RowModel
1990
2191
  ): HTMLElement {
1991
- return rowRenderer(row).create(context.document, rendererPrimitives(context));
2192
+ return rowRenderer(row).create(
2193
+ context.document,
2194
+ rendererPrimitives(context, Boolean(row.disabled))
2195
+ );
1992
2196
  }
1993
2197
 
1994
2198
  export type NativeListWebScrollOptions = Readonly<{
@@ -2037,6 +2241,7 @@ export class NativeListWebEngine {
2037
2241
  rail: [],
2038
2242
  mediaTile: [],
2039
2243
  action: [],
2244
+ buttonGroup: [],
2040
2245
  system: [],
2041
2246
  activity: [],
2042
2247
  dataRow: [],
@@ -2080,6 +2285,7 @@ export class NativeListWebEngine {
2080
2285
  private pullDistance = 0;
2081
2286
  private virtualizationEnabled: boolean;
2082
2287
  private actionAnchor: WebActionAnchorRecord | undefined;
2288
+ private pressedHelpElement: HTMLElement | undefined;
2083
2289
  private readonly actionAnchorInstanceId = String(
2084
2290
  ++webNativeListInstanceCounter
2085
2291
  );
@@ -2100,6 +2306,39 @@ export class NativeListWebEngine {
2100
2306
  private lastViewportWidth = -1;
2101
2307
  private lastViewportHeight = -1;
2102
2308
  private destroyed = false;
2309
+ private hasAppliedSnapshot = false;
2310
+ private lastStructuralTransitionId: string | undefined;
2311
+ private structuralFrame: number | undefined;
2312
+ private structuralFinish: number | undefined;
2313
+ private structuralRevision = 0;
2314
+ private structuralAnimations: Animation[] = [];
2315
+ private structuralScrollInterrupted = false;
2316
+ private structuralRenderedHeight: number | undefined;
2317
+ private structuralTargetHeight: number | undefined;
2318
+ private reactFooterHeight = 0;
2319
+ private structuralFooterCompensation = 0;
2320
+ private structuralFooterExpansion = 0;
2321
+ private structuralFooterFromHeight = 0;
2322
+ private structuralFooterElement: HTMLElement | undefined;
2323
+ private structuralFooterStyles:
2324
+ | { marginBottom: string; transform: string; clipPath: string }
2325
+ | undefined;
2326
+ private structuralProgress = 0;
2327
+ private structuralAnchorStyles:
2328
+ | { owner: string; content: string }
2329
+ | undefined;
2330
+ private structuralContentOverflow: string | undefined;
2331
+ private structuralPrefixTransition = false;
2332
+ private structuralTail:
2333
+ | {
2334
+ snapshot: NativeListSnapshot;
2335
+ rows: readonly RowModel[];
2336
+ layout: WebListLayout;
2337
+ firstIndex: number;
2338
+ layer: HTMLElement;
2339
+ mounted: Map<number, HTMLElement>;
2340
+ }
2341
+ | undefined;
2103
2342
  private sectionIndexAlignStart = false;
2104
2343
  // Renderers report intrinsic height after browser text wrapping.
2105
2344
  private measuredRendererHeights = new Map<
@@ -2271,6 +2510,12 @@ export class NativeListWebEngine {
2271
2510
  'ok-collapsible-pager-cancel-scroll-command',
2272
2511
  this.handlePagerScrollCommandCancel
2273
2512
  );
2513
+ for (const event of ['wheel', 'pointerdown', 'touchstart'])
2514
+ (this.externalScrollHost ?? this.viewport).addEventListener(
2515
+ event,
2516
+ this.handleStructuralUserScroll,
2517
+ { passive: true }
2518
+ );
2274
2519
  this.viewport.addEventListener('scroll', this.handleScroll, {
2275
2520
  passive: true,
2276
2521
  });
@@ -2289,6 +2534,7 @@ export class NativeListWebEngine {
2289
2534
  this.root.addEventListener('pointercancel', this.handleMarketPointerEnd);
2290
2535
  // OneKey patch: preserve web tooltip hover for section titles.
2291
2536
  this.root.addEventListener('pointerover', this.handleTitlePointerOver);
2537
+ this.root.addEventListener('pointerdown', this.handleIconHelpPointerDown);
2292
2538
  this.root.addEventListener('focusin', this.handleTitlePointerOver);
2293
2539
  this.root.addEventListener('focusout', this.handleTitlePointerOut);
2294
2540
  this.root.addEventListener('pointerout', this.handleTitlePointerOut);
@@ -2365,7 +2611,7 @@ export class NativeListWebEngine {
2365
2611
  );
2366
2612
  this.observeSectionIndexTransforms();
2367
2613
  this.applyFontFamilies(fontFamiliesJson);
2368
- this.applySnapshot(this.snapshot);
2614
+ this.applyValidatedSnapshot(this.snapshot);
2369
2615
  }
2370
2616
 
2371
2617
  private validateStickyInset(value = 0): number {
@@ -2457,8 +2703,435 @@ export class NativeListWebEngine {
2457
2703
  return next;
2458
2704
  });
2459
2705
  if (!validated) return;
2706
+ this.applyValidatedSnapshot(validated);
2707
+ }
2708
+
2709
+ private applyValidatedSnapshot(validated: NativeListSnapshot) {
2710
+ this.pressedHelpElement = undefined;
2460
2711
  this.invalidateActionAnchor('snapshot');
2461
- this.setSnapshot(validated);
2712
+ const transition = validated.structuralTransition;
2713
+ const isNewTransition =
2714
+ transition !== undefined &&
2715
+ transition.id !== this.lastStructuralTransitionId;
2716
+ if (transition) this.lastStructuralTransitionId = transition.id;
2717
+ const nextRows = effectiveRows(validated);
2718
+ const changedKeys =
2719
+ this.rows.length !== nextRows.length ||
2720
+ this.rows.some((row, index) => row.key !== nextRows[index]?.key);
2721
+ const incompatibleActivePrefix =
2722
+ this.structuralPrefixTransition &&
2723
+ (validated.layout.kind !== this.snapshot.layout.kind ||
2724
+ validated.layout.orientation !== this.snapshot.layout.orientation ||
2725
+ nextRows.some(
2726
+ (row, index) =>
2727
+ row.height !== this.rows[index]?.height ||
2728
+ row.style?.container?.height !==
2729
+ this.rows[index]?.style?.container?.height
2730
+ ));
2731
+ const shouldAnimate =
2732
+ this.hasAppliedSnapshot &&
2733
+ isNewTransition &&
2734
+ changedKeys &&
2735
+ (this.snapshot.layout.kind === 'linear' ||
2736
+ this.snapshot.layout.kind === 'table') &&
2737
+ this.snapshot.layout.orientation !== 'horizontal' &&
2738
+ (validated.layout.kind === 'linear' ||
2739
+ validated.layout.kind === 'table') &&
2740
+ validated.layout.orientation !== 'horizontal' &&
2741
+ (transition?.durationMs ?? 220) > 0 &&
2742
+ !this.document.defaultView?.matchMedia?.(
2743
+ '(prefers-reduced-motion: reduce)'
2744
+ ).matches;
2745
+ if (shouldAnimate && transition)
2746
+ this.applyStructuralSnapshot(validated, transition);
2747
+ else {
2748
+ if (isNewTransition || changedKeys || incompatibleActivePrefix)
2749
+ this.cancelStructuralAnimation();
2750
+ this.setSnapshot(validated);
2751
+ }
2752
+ this.hasAppliedSnapshot = true;
2753
+ }
2754
+
2755
+ private cancelStructuralAnimation() {
2756
+ this.structuralRevision += 1;
2757
+ if (this.structuralFrame !== undefined)
2758
+ this.document.defaultView?.cancelAnimationFrame(this.structuralFrame);
2759
+ if (this.structuralFinish !== undefined)
2760
+ this.document.defaultView?.clearTimeout(this.structuralFinish);
2761
+ this.structuralFrame = undefined;
2762
+ this.structuralFinish = undefined;
2763
+ this.structuralAnimations.forEach((animation) => animation.cancel());
2764
+ this.structuralAnimations = [];
2765
+ if (this.structuralTail) {
2766
+ disposeWebImageRetries(this.structuralTail.layer);
2767
+ this.structuralTail.layer.remove();
2768
+ this.structuralTail = undefined;
2769
+ }
2770
+ if (this.structuralContentOverflow !== undefined) {
2771
+ this.content.style.overflow = this.structuralContentOverflow;
2772
+ this.structuralContentOverflow = undefined;
2773
+ }
2774
+ this.structuralPrefixTransition = false;
2775
+ if (this.structuralFooterElement && this.structuralFooterStyles) {
2776
+ this.structuralFooterElement.style.marginBottom =
2777
+ this.structuralFooterStyles.marginBottom;
2778
+ this.structuralFooterElement.style.transform =
2779
+ this.structuralFooterStyles.transform;
2780
+ this.structuralFooterElement.style.clipPath =
2781
+ this.structuralFooterStyles.clipPath;
2782
+ }
2783
+ this.structuralFooterElement = undefined;
2784
+ this.structuralFooterStyles = undefined;
2785
+ this.structuralScrollInterrupted = false;
2786
+ this.structuralRenderedHeight = undefined;
2787
+ this.structuralTargetHeight = undefined;
2788
+ this.structuralFooterCompensation = 0;
2789
+ this.structuralFooterExpansion = 0;
2790
+ this.structuralProgress = 0;
2791
+ const owner = this.externalScrollHost ?? this.viewport;
2792
+ if (this.structuralAnchorStyles) {
2793
+ owner.style.overflowAnchor = this.structuralAnchorStyles.owner;
2794
+ this.content.style.overflowAnchor = this.structuralAnchorStyles.content;
2795
+ this.structuralAnchorStyles = undefined;
2796
+ }
2797
+ }
2798
+
2799
+ private handleStructuralUserScroll = (event: Event) => {
2800
+ if (
2801
+ event.type === 'pointerdown' &&
2802
+ event.target !== (this.externalScrollHost ?? this.viewport) &&
2803
+ (event as PointerEvent).pointerType !== 'touch'
2804
+ )
2805
+ return;
2806
+ this.structuralScrollInterrupted = true;
2807
+ if (this.structuralPrefixTransition) {
2808
+ this.cancelStructuralAnimation();
2809
+ this.recomputeLayout();
2810
+ }
2811
+ };
2812
+
2813
+ private updateStructuralTailWindow() {
2814
+ const tail = this.structuralTail;
2815
+ if (!tail) return;
2816
+ const visible = visibleWebLayoutItems(
2817
+ tail.layout,
2818
+ this.currentOffset(),
2819
+ this.viewportLength(),
2820
+ this.viewportLength() * OVERSCAN_VIEWPORTS
2821
+ ).filter((item) => item.index >= tail.firstIndex);
2822
+ const desired = new Set(visible.map((item) => item.index));
2823
+ tail.mounted.forEach((element, index) => {
2824
+ if (desired.has(index)) return;
2825
+ disposeWebImageRetries(element);
2826
+ element.remove();
2827
+ tail.mounted.delete(index);
2828
+ });
2829
+ visible.forEach((item) => {
2830
+ if (tail.mounted.has(item.index)) return;
2831
+ const row = tail.rows[item.index];
2832
+ if (!row) return;
2833
+ const element = createElement(
2834
+ this.document,
2835
+ 'div',
2836
+ 'ok-native-list-item'
2837
+ );
2838
+ element.setAttribute('aria-hidden', 'true');
2839
+ element.style.pointerEvents = 'none';
2840
+ element.style.opacity = String(
2841
+ (row.style?.container?.opacity ?? row.opacity ?? 1) *
2842
+ (row.disabled ? 0.5 : 1)
2843
+ );
2844
+ const body = createRowBody(
2845
+ {
2846
+ document: this.document,
2847
+ snapshot: tail.snapshot,
2848
+ selectedKeys: selectionStateFromSnapshot(tail.snapshot).selectedKeys,
2849
+ itemIndex: item.index,
2850
+ },
2851
+ row
2852
+ );
2853
+ applyRowStyle(body, row);
2854
+ element.append(body);
2855
+ this.positionElement(element, item);
2856
+ tail.layer.append(element);
2857
+ tail.mounted.set(item.index, element);
2858
+ });
2859
+ }
2860
+
2861
+ private writeStructuralHeight(height: number) {
2862
+ if (this.externalScrollHost) this.writeExternalHeight(height);
2863
+ else this.content.style.height = `${height}px`;
2864
+ }
2865
+
2866
+ private reactFooterElement() {
2867
+ return this.contentBounds?.querySelector<HTMLElement>(
2868
+ '[data-testid="native-list-footer-slot"]'
2869
+ );
2870
+ }
2871
+
2872
+ private readReactFooterHeight() {
2873
+ return this.reactFooterElement()?.getBoundingClientRect().height ?? 0;
2874
+ }
2875
+
2876
+ private applyStructuralFooterVisual(progress: number) {
2877
+ const footer = this.structuralFooterElement;
2878
+ if (!footer || this.structuralFooterExpansion <= 0) return;
2879
+ const hidden = this.structuralFooterExpansion * (1 - progress);
2880
+ footer.style.marginBottom = `${-hidden}px`;
2881
+ footer.style.transform = `translateY(${-hidden}px)`;
2882
+ footer.style.clipPath = `inset(${hidden}px 0 0 0)`;
2883
+ }
2884
+
2885
+ private applyStructuralSnapshot(
2886
+ next: NativeListSnapshot,
2887
+ transition: NonNullable<NativeListSnapshot['structuralTransition']>
2888
+ ) {
2889
+ const owner = this.externalScrollHost ?? this.viewport;
2890
+ const oldOffset = owner.scrollTop;
2891
+ const oldScrollHeight = owner.scrollHeight;
2892
+ const oldHeight =
2893
+ this.structuralRenderedHeight ??
2894
+ (this.content.getBoundingClientRect().height ||
2895
+ this.layout.contentHeight);
2896
+ const nextFooterHeight = this.readReactFooterHeight();
2897
+ const oldFooterHeight =
2898
+ this.structuralRenderedHeight === undefined
2899
+ ? this.reactFooterHeight
2900
+ : this.structuralFooterFromHeight +
2901
+ (this.reactFooterHeight - this.structuralFooterFromHeight) *
2902
+ this.structuralProgress;
2903
+ const footerDifference = oldFooterHeight - nextFooterHeight;
2904
+ const startHeight = oldHeight + Math.max(0, footerDifference);
2905
+ const oldSnapshot = this.snapshot;
2906
+ const oldRows = this.rows;
2907
+ const oldLayout = this.layout;
2908
+ const oldItems = oldLayout.items;
2909
+ const nextRows = effectiveRows(next);
2910
+ const prefixCount = Math.min(oldRows.length, nextRows.length);
2911
+ const prefixTailChange =
2912
+ oldSnapshot.layout.kind === next.layout.kind &&
2913
+ oldSnapshot.layout.orientation === next.layout.orientation &&
2914
+ oldRows.length !== nextRows.length &&
2915
+ oldRows
2916
+ .slice(0, prefixCount)
2917
+ .every((row, index) => row.key === nextRows[index]?.key);
2918
+ const oldPositions = new Map<string, number>();
2919
+ this.mounted.forEach((element, index) => {
2920
+ const row = this.rows[index];
2921
+ if (row) oldPositions.set(row.key, element.getBoundingClientRect().top);
2922
+ });
2923
+ const nextKeys = new Set(effectiveRows(next).map((row) => row.key));
2924
+ const visibleOffset = this.currentOffset();
2925
+ const visibleSurvivor = oldItems.find(
2926
+ (item) => item.y + item.height > visibleOffset && nextKeys.has(item.key)
2927
+ );
2928
+ const anchor = visibleSurvivor;
2929
+ this.cancelStructuralAnimation();
2930
+ this.structuralScrollInterrupted = false;
2931
+ this.structuralAnchorStyles = {
2932
+ owner: owner.style.overflowAnchor,
2933
+ content: this.content.style.overflowAnchor,
2934
+ };
2935
+ owner.style.overflowAnchor = 'none';
2936
+ this.content.style.overflowAnchor = 'none';
2937
+ this.structuralContentOverflow = this.content.style.overflow;
2938
+ if (prefixTailChange) this.content.style.overflow = 'clip';
2939
+ this.structuralRenderedHeight = startHeight;
2940
+ this.structuralFooterFromHeight = oldFooterHeight;
2941
+ this.structuralFooterCompensation = Math.max(0, footerDifference);
2942
+ this.structuralFooterExpansion = Math.max(0, -footerDifference);
2943
+ this.structuralProgress = 0;
2944
+ this.reactFooterHeight = nextFooterHeight;
2945
+ this.structuralFooterElement = this.reactFooterElement() ?? undefined;
2946
+ if (this.structuralFooterElement) {
2947
+ const style = this.structuralFooterElement.style;
2948
+ this.structuralFooterStyles = {
2949
+ marginBottom: style.marginBottom,
2950
+ transform: style.transform,
2951
+ clipPath: style.clipPath,
2952
+ };
2953
+ this.applyStructuralFooterVisual(0);
2954
+ }
2955
+ this.setSnapshot(next);
2956
+ const newHeight = this.layout.contentHeight;
2957
+ this.structuralTargetHeight = newHeight;
2958
+ const stablePrefixGeometry =
2959
+ prefixTailChange &&
2960
+ oldItems.length === oldRows.length &&
2961
+ this.layout.items.length === nextRows.length &&
2962
+ oldItems.slice(0, prefixCount).every((item, index) => {
2963
+ const nextItem = this.layout.items[index];
2964
+ return (
2965
+ nextItem?.y === item.y &&
2966
+ nextItem?.height === item.height &&
2967
+ nextItem?.width === item.width
2968
+ );
2969
+ });
2970
+ if (!stablePrefixGeometry) {
2971
+ this.content.style.overflow = this.structuralContentOverflow;
2972
+ this.structuralContentOverflow = undefined;
2973
+ } else {
2974
+ this.structuralPrefixTransition = true;
2975
+ }
2976
+ if (stablePrefixGeometry && nextRows.length < oldRows.length) {
2977
+ const layer = createElement(
2978
+ this.document,
2979
+ 'div',
2980
+ 'ok-native-list-structural-tail'
2981
+ );
2982
+ layer.setAttribute('aria-hidden', 'true');
2983
+ layer.setAttribute('inert', '');
2984
+ Object.assign(layer.style, {
2985
+ position: 'absolute',
2986
+ inset: '0',
2987
+ pointerEvents: 'none',
2988
+ });
2989
+ this.content.append(layer);
2990
+ this.structuralTail = {
2991
+ snapshot: oldSnapshot,
2992
+ rows: oldRows,
2993
+ layout: oldLayout,
2994
+ firstIndex: prefixCount,
2995
+ layer,
2996
+ mounted: new Map(),
2997
+ };
2998
+ this.updateStructuralTailWindow();
2999
+ }
3000
+ const nextAnchor =
3001
+ anchor && this.layout.items.find((item) => item.key === anchor.key);
3002
+ const delta = nextAnchor && anchor ? nextAnchor.y - anchor.y : 0;
3003
+ let finalScrollHeight = oldScrollHeight + newHeight - oldHeight;
3004
+ if (stablePrefixGeometry && this.externalScrollHost) {
3005
+ const beforeMeasure = owner.scrollTop;
3006
+ const tailLayer = this.structuralTail?.layer;
3007
+ if (tailLayer) tailLayer.style.display = 'none';
3008
+ this.writeStructuralHeight(newHeight);
3009
+ this.applyStructuralFooterVisual(1);
3010
+ finalScrollHeight = owner.scrollHeight;
3011
+ this.writeStructuralHeight(startHeight);
3012
+ this.applyStructuralFooterVisual(0);
3013
+ if (tailLayer) tailLayer.style.display = '';
3014
+ owner.scrollTop = beforeMeasure;
3015
+ }
3016
+ const finalMaxOffset = Math.max(0, finalScrollHeight - owner.clientHeight);
3017
+ let targetOffset = Math.max(0, Math.min(oldOffset + delta, finalMaxOffset));
3018
+ if (
3019
+ !visibleSurvivor &&
3020
+ transition.anchorKey &&
3021
+ !(stablePrefixGeometry && nextRows.length > oldRows.length)
3022
+ ) {
3023
+ const fallback = this.layout.items.find(
3024
+ (item) => item.key === transition.anchorKey
3025
+ );
3026
+ if (fallback) {
3027
+ const pagerStickyInset = Number.parseFloat(
3028
+ this.externalScrollHost?.style.getPropertyValue(
3029
+ '--ok-collapsible-pager-sticky-inset'
3030
+ ) ?? ''
3031
+ );
3032
+ const effectiveStickyInset = Math.max(
3033
+ this.externalStickyInset,
3034
+ Number.isFinite(pagerStickyInset) ? pagerStickyInset : 0
3035
+ );
3036
+ const fallbackTop = this.externalScrollHost
3037
+ ? this.externalGeometry().origin + fallback.y - effectiveStickyInset
3038
+ : resolveWebCollapsiblePagerRawOffset(
3039
+ Math.max(0, fallback.y - this.collapsiblePagerInsets().sticky),
3040
+ this.collapsiblePagerInsets().header
3041
+ );
3042
+ if (
3043
+ stablePrefixGeometry &&
3044
+ this.externalScrollHost &&
3045
+ this.reactFooterHeight > 0
3046
+ ) {
3047
+ const footerTop = this.externalGeometry().origin + newHeight;
3048
+ const footerStart = Math.max(
3049
+ 0,
3050
+ footerTop + this.reactFooterHeight - owner.clientHeight
3051
+ );
3052
+ const footerEnd = Math.max(0, footerTop - effectiveStickyInset);
3053
+ const nearestVisibleOffset =
3054
+ footerStart <= footerEnd
3055
+ ? Math.max(footerStart, Math.min(oldOffset, footerEnd))
3056
+ : footerEnd;
3057
+ targetOffset = Math.max(
3058
+ 0,
3059
+ Math.min(nearestVisibleOffset, finalMaxOffset)
3060
+ );
3061
+ } else {
3062
+ targetOffset = Math.min(targetOffset, Math.max(0, fallbackTop));
3063
+ }
3064
+ }
3065
+ }
3066
+ const duration = transition.durationMs ?? 220;
3067
+ const easing = 'cubic-bezier(0.42, 0, 0.58, 1)';
3068
+ this.mounted.forEach((element, index) => {
3069
+ const row = this.rows[index];
3070
+ const previousTop = row && oldPositions.get(row.key);
3071
+ if (previousTop === undefined || typeof element.animate !== 'function')
3072
+ return;
3073
+ const offset = previousTop - element.getBoundingClientRect().top;
3074
+ if (Math.abs(offset) < 0.5) return;
3075
+ this.structuralAnimations.push(
3076
+ element.animate(
3077
+ [
3078
+ { transform: `translateY(${offset}px)` },
3079
+ { transform: 'translateY(0)' },
3080
+ ],
3081
+ { duration, easing }
3082
+ )
3083
+ );
3084
+ });
3085
+ const startedAt =
3086
+ this.document.defaultView?.performance.now() ?? Date.now();
3087
+ const revision = this.structuralRevision;
3088
+ const finish = () => {
3089
+ if (this.destroyed || revision !== this.structuralRevision) return;
3090
+ const ownsScroll = !this.structuralScrollInterrupted;
3091
+ this.cancelStructuralAnimation();
3092
+ this.recomputeLayout();
3093
+ if (stablePrefixGeometry && ownsScroll) {
3094
+ owner.scrollTop = Math.max(
3095
+ 0,
3096
+ Math.min(targetOffset, owner.scrollHeight - owner.clientHeight)
3097
+ );
3098
+ }
3099
+ };
3100
+ const step = (time: number) => {
3101
+ if (this.destroyed || revision !== this.structuralRevision) return;
3102
+ if (
3103
+ this.structuralPrefixTransition &&
3104
+ this.document.defaultView?.matchMedia?.(
3105
+ '(prefers-reduced-motion: reduce)'
3106
+ ).matches
3107
+ ) {
3108
+ finish();
3109
+ return;
3110
+ }
3111
+ const progress = Math.min(1, (time - startedAt) / duration);
3112
+ const eased = progress * progress * (3 - 2 * progress);
3113
+ this.structuralProgress = eased;
3114
+ const renderedHeight =
3115
+ oldHeight +
3116
+ ((this.structuralTargetHeight ?? newHeight) - oldHeight) * eased +
3117
+ this.structuralFooterCompensation * (1 - eased);
3118
+ this.structuralRenderedHeight = renderedHeight;
3119
+ this.writeStructuralHeight(renderedHeight);
3120
+ this.applyStructuralFooterVisual(eased);
3121
+ if (!this.structuralScrollInterrupted)
3122
+ owner.scrollTop = oldOffset + (targetOffset - oldOffset) * eased;
3123
+ this.externalGeometryCache = undefined;
3124
+ this.updateStructuralTailWindow();
3125
+ if (progress < 1)
3126
+ this.structuralFrame =
3127
+ this.document.defaultView?.requestAnimationFrame(step);
3128
+ };
3129
+ this.structuralFrame =
3130
+ this.document.defaultView?.requestAnimationFrame(step);
3131
+ this.structuralFinish = this.document.defaultView?.setTimeout(
3132
+ finish,
3133
+ duration + 40
3134
+ );
2462
3135
  }
2463
3136
 
2464
3137
  currentContentHeight(): number {
@@ -2663,6 +3336,8 @@ export class NativeListWebEngine {
2663
3336
 
2664
3337
  destroy() {
2665
3338
  if (this.destroyed) return;
3339
+ this.cancelStructuralAnimation();
3340
+ this.pressedHelpElement = undefined;
2666
3341
  this.invalidateActionAnchor('destroy');
2667
3342
  this.destroyed = true;
2668
3343
  if (this.frameHandle !== undefined) this.cancelFrame(this.frameHandle);
@@ -2690,6 +3365,11 @@ export class NativeListWebEngine {
2690
3365
  'ok-collapsible-pager-cancel-scroll-command',
2691
3366
  this.handlePagerScrollCommandCancel
2692
3367
  );
3368
+ for (const event of ['wheel', 'pointerdown', 'touchstart'])
3369
+ (this.externalScrollHost ?? this.viewport).removeEventListener(
3370
+ event,
3371
+ this.handleStructuralUserScroll
3372
+ );
2693
3373
  this.viewport.removeEventListener('scroll', this.handleScroll);
2694
3374
  (this.externalScrollHost ?? this.viewport).removeEventListener(
2695
3375
  'ok-collapsible-pager-insets-changed',
@@ -2704,6 +3384,10 @@ export class NativeListWebEngine {
2704
3384
  this.root.removeEventListener('pointerup', this.handleMarketPointerEnd);
2705
3385
  this.root.removeEventListener('pointercancel', this.handleMarketPointerEnd);
2706
3386
  this.root.removeEventListener('pointerover', this.handleTitlePointerOver);
3387
+ this.root.removeEventListener(
3388
+ 'pointerdown',
3389
+ this.handleIconHelpPointerDown
3390
+ );
2707
3391
  this.root.removeEventListener('focusin', this.handleTitlePointerOver);
2708
3392
  this.root.removeEventListener('focusout', this.handleTitlePointerOut);
2709
3393
  this.root.removeEventListener('pointerout', this.handleTitlePointerOut);
@@ -2766,6 +3450,7 @@ export class NativeListWebEngine {
2766
3450
  selectedKeys?: ReadonlySet<string>
2767
3451
  ) {
2768
3452
  this.cancelMarketPointer();
3453
+ this.pressedHelpElement = undefined;
2769
3454
  this.measuredRendererHeights.clear();
2770
3455
  this.snapshot = snapshot;
2771
3456
  this.rows = effectiveRows(snapshot);
@@ -2786,6 +3471,7 @@ export class NativeListWebEngine {
2786
3471
  '--nl-row': theme.rowBackground,
2787
3472
  '--nl-selected': theme.rowSelectedBackground,
2788
3473
  '--nl-pressed': theme.rowPressedBackground,
3474
+ '--nl-hover': theme.rowHoverBackground ?? theme.rowPressedBackground,
2789
3475
  '--nl-subdued': theme.subduedBackground,
2790
3476
  '--nl-strong': theme.strongBackground,
2791
3477
  '--nl-image-fallback-background':
@@ -2803,6 +3489,7 @@ export class NativeListWebEngine {
2803
3489
  '--nl-separator': theme.separator,
2804
3490
  '--nl-accent': theme.accent,
2805
3491
  '--nl-positive': theme.positive,
3492
+ '--nl-success-background': theme.successBackground,
2806
3493
  '--nl-negative': theme.negative,
2807
3494
  '--nl-critical': theme.criticalBackground,
2808
3495
  '--nl-inverse': theme.inverseBackground,
@@ -2890,13 +3577,37 @@ export class NativeListWebEngine {
2890
3577
 
2891
3578
  private recomputeLayout = () => {
2892
3579
  if (this.destroyed) return;
3580
+ if (
3581
+ this.structuralPrefixTransition &&
3582
+ this.lastViewportWidth > 0 &&
3583
+ this.viewport.clientWidth > 0 &&
3584
+ this.viewport.clientWidth !== this.lastViewportWidth
3585
+ )
3586
+ this.cancelStructuralAnimation();
3587
+ if (this.structuralRenderedHeight === undefined)
3588
+ this.reactFooterHeight = this.readReactFooterHeight();
3589
+ else {
3590
+ const nextFooterHeight = this.readReactFooterHeight();
3591
+ const footerDelta = this.reactFooterHeight - nextFooterHeight;
3592
+ if (footerDelta !== 0) {
3593
+ this.reactFooterHeight = nextFooterHeight;
3594
+ const priorCompensation = this.structuralFooterCompensation;
3595
+ const difference = this.structuralFooterFromHeight - nextFooterHeight;
3596
+ this.structuralFooterCompensation = Math.max(0, difference);
3597
+ this.structuralFooterExpansion = Math.max(0, -difference);
3598
+ this.structuralRenderedHeight +=
3599
+ (this.structuralFooterCompensation - priorCompensation) *
3600
+ (1 - this.structuralProgress);
3601
+ this.applyStructuralFooterVisual(this.structuralProgress);
3602
+ }
3603
+ }
2893
3604
  const cachedWidth = this.lastViewportWidth;
2894
3605
  const cachedHeight = this.lastViewportHeight;
2895
3606
  const precomputedLayout =
2896
3607
  this.externalScrollHost && cachedWidth > 0 && cachedHeight > 0
2897
3608
  ? this.computeLayout(cachedWidth, cachedHeight)
2898
3609
  : undefined;
2899
- if (precomputedLayout) {
3610
+ if (precomputedLayout && this.structuralRenderedHeight === undefined) {
2900
3611
  // Commit appended row extent before measuring React-owned slot changes.
2901
3612
  this.writeExternalHeight(
2902
3613
  Math.max(this.layout.contentHeight, precomputedLayout.contentHeight)
@@ -2949,11 +3660,15 @@ export class NativeListWebEngine {
2949
3660
  ? precomputedLayout
2950
3661
  : this.computeLayout(viewportWidth, viewportHeight);
2951
3662
  this.content.style.width = String(this.layout.contentWidth) + 'px';
2952
- this.content.style.height = String(this.layout.contentHeight) + 'px';
3663
+ const renderedHeight =
3664
+ this.structuralRenderedHeight ?? this.layout.contentHeight;
3665
+ this.content.style.height = String(renderedHeight) + 'px';
2953
3666
  if (this.externalScrollHost) {
2954
- this.writeExternalHeight(this.layout.contentHeight);
3667
+ this.writeExternalHeight(renderedHeight);
2955
3668
  this.externalGeometryCache = undefined;
2956
3669
  }
3670
+ if (this.structuralRenderedHeight !== undefined)
3671
+ this.structuralTargetHeight = this.layout.contentHeight;
2957
3672
  this.renderSectionIndex(
2958
3673
  this.viewport.clientHeight <= 0
2959
3674
  ? DEFAULT_VIEWPORT_HEIGHT
@@ -3114,6 +3829,7 @@ export class NativeListWebEngine {
3114
3829
  }
3115
3830
  this.updateVisibleSelection();
3116
3831
  this.updateVisibleState();
3832
+ this.updateStructuralTailWindow();
3117
3833
  }
3118
3834
 
3119
3835
  private positionElement(element: HTMLElement, item: WebLayoutItem) {
@@ -3192,7 +3908,7 @@ export class NativeListWebEngine {
3192
3908
  const body = reusableBody ?? createRowBody(context, row);
3193
3909
  if (reusableBody) {
3194
3910
  resetRowContainerStyle(body);
3195
- registered.bind(body, rendererPrimitives(context));
3911
+ registered.bind(body, rendererPrimitives(context, Boolean(row.disabled)));
3196
3912
  }
3197
3913
  // OneKey patch: explicit selector fields preserve original page geometry.
3198
3914
  element.style.contain = row.backgroundFullWidth ? 'layout style' : '';
@@ -3984,6 +4700,8 @@ export class NativeListWebEngine {
3984
4700
  }
3985
4701
 
3986
4702
  private invalidateActionAnchorForElement(element: HTMLElement) {
4703
+ if (this.pressedHelpElement && element.contains(this.pressedHelpElement))
4704
+ this.pressedHelpElement = undefined;
3987
4705
  const anchor = this.actionAnchor;
3988
4706
  if (
3989
4707
  anchor &&
@@ -4096,9 +4814,13 @@ export class NativeListWebEngine {
4096
4814
  '[data-native-list-row-key]'
4097
4815
  );
4098
4816
  const row = this.rowAtElement(rowElement);
4099
- if (row?.type !== 'market' || !isRowPressEnabled(row)) return;
4817
+ if (
4818
+ (row?.type !== 'market' && row?.type !== 'identity') ||
4819
+ !isRowPressEnabled(row)
4820
+ )
4821
+ return;
4100
4822
  this.cancelMarketPointer();
4101
- if (row.pressInActionKey)
4823
+ if (row.type === 'market' && row.pressInActionKey)
4102
4824
  this.emitRowAction(
4103
4825
  row,
4104
4826
  row.pressInActionKey,
@@ -4117,7 +4839,7 @@ export class NativeListWebEngine {
4117
4839
  !rowElement.isConnected ||
4118
4840
  rowElement.dataset.nativeListBindingEpoch !== bindingEpoch ||
4119
4841
  currentRow?.key !== row.key ||
4120
- currentRow.type !== 'market' ||
4842
+ (currentRow.type !== 'market' && currentRow.type !== 'identity') ||
4121
4843
  !isRowPressEnabled(currentRow) ||
4122
4844
  !currentRow.longPressActionKey
4123
4845
  )
@@ -4135,7 +4857,7 @@ export class NativeListWebEngine {
4135
4857
  this.marketPointer = {
4136
4858
  pointerId: event.pointerId,
4137
4859
  pointerType: event.pointerType,
4138
- compact: !row.webTable,
4860
+ compact: row.type === 'identity' || !row.webTable,
4139
4861
  rowKey: row.key,
4140
4862
  startX: event.clientX,
4141
4863
  startY: event.clientY,
@@ -4203,11 +4925,23 @@ export class NativeListWebEngine {
4203
4925
  action.contains(event.relatedTarget))
4204
4926
  )
4205
4927
  return;
4928
+ if (action.dataset.nativeListIconHelp === 'true') {
4929
+ if (
4930
+ event.type === 'pointerover' &&
4931
+ (event as PointerEvent).pointerType === 'touch'
4932
+ )
4933
+ return;
4934
+ if (this.pressedHelpElement === action) return;
4935
+ }
4206
4936
  if (
4207
4937
  action.classList.contains('ok-native-list-market-subtitle-prefix') &&
4208
4938
  action.scrollWidth <= action.clientWidth + 1
4209
4939
  )
4210
4940
  return;
4941
+ this.emitHoverAction(action);
4942
+ };
4943
+
4944
+ private emitHoverAction(action: HTMLElement) {
4211
4945
  const rowElement = action.closest<HTMLElement>(
4212
4946
  '[data-native-list-row-key]'
4213
4947
  );
@@ -4229,6 +4963,24 @@ export class NativeListWebEngine {
4229
4963
  // WalletGroup), not the enclosing row's `disabled`.
4230
4964
  if (sourceRow && !sourceRow.disabled && actionKey)
4231
4965
  this.emitRowAction(sourceRow, actionKey, action, rowElement ?? undefined);
4966
+ }
4967
+
4968
+ private handleIconHelpPointerDown = (event: PointerEvent) => {
4969
+ const target = event.target;
4970
+ if (!(target instanceof Element)) return;
4971
+ const action = target.closest<HTMLElement>(
4972
+ '[data-native-list-icon-help="true"]'
4973
+ );
4974
+ if (!action) return;
4975
+ if (event.pointerType === 'touch') {
4976
+ if (this.actionAnchor?.actionElement === action && this.actionAnchor.open)
4977
+ this.invalidateActionAnchor('pointerLeave');
4978
+ else this.emitHoverAction(action);
4979
+ } else {
4980
+ this.pressedHelpElement = action;
4981
+ if (this.actionAnchor?.actionElement === action)
4982
+ this.invalidateActionAnchor('pointerLeave');
4983
+ }
4232
4984
  };
4233
4985
 
4234
4986
  private handleTitlePointerOut = (event: PointerEvent | FocusEvent) => {
@@ -4243,6 +4995,14 @@ export class NativeListWebEngine {
4243
4995
  action.contains(event.relatedTarget))
4244
4996
  )
4245
4997
  return;
4998
+ if (
4999
+ action.dataset.nativeListIconHelp === 'true' &&
5000
+ event.type === 'pointerout' &&
5001
+ (event as PointerEvent).pointerType === 'touch'
5002
+ )
5003
+ return;
5004
+ if (event.type === 'pointerout' && this.pressedHelpElement === action)
5005
+ this.pressedHelpElement = undefined;
4246
5006
  if (this.actionAnchor?.actionElement === action)
4247
5007
  this.invalidateActionAnchor('pointerLeave');
4248
5008
  };
@@ -4256,6 +5016,11 @@ export class NativeListWebEngine {
4256
5016
  }
4257
5017
  const target = event.target;
4258
5018
  if (!(target instanceof Element)) return;
5019
+ if (target.closest('[data-native-list-icon-help="true"]')) {
5020
+ event.preventDefault();
5021
+ event.stopPropagation();
5022
+ return;
5023
+ }
4259
5024
  const rowElement = target.closest<HTMLElement>(
4260
5025
  '[data-native-list-row-key]'
4261
5026
  );
@@ -4276,6 +5041,10 @@ export class NativeListWebEngine {
4276
5041
  // member press is blocked by `isRowPressEnabled` in `handleRowPress`.
4277
5042
  const action = target.closest<HTMLElement>('[data-native-list-action]');
4278
5043
  if (action) {
5044
+ if (action.tagName === 'BUTTON' && action.hasAttribute('disabled'))
5045
+ return;
5046
+ if (action.classList.contains('ok-native-list-trailing-button'))
5047
+ event.stopPropagation();
4279
5048
  const scope = action.dataset.selectionScope;
4280
5049
  if (scope === 'row') this.activateSelection({ scope: 'row' }, row);
4281
5050
  else if (scope === 'section' && action.dataset.selectionKey)
@@ -4301,6 +5070,19 @@ export class NativeListWebEngine {
4301
5070
  };
4302
5071
 
4303
5072
  private handleKeyDown = (event: KeyboardEvent) => {
5073
+ if (
5074
+ (event.key === 'Enter' || event.key === ' ') &&
5075
+ event.target instanceof HTMLElement &&
5076
+ event.target.matches('[role="button"][data-native-list-hover-action]')
5077
+ ) {
5078
+ event.preventDefault();
5079
+ if (
5080
+ this.actionAnchor?.actionElement !== event.target ||
5081
+ !this.actionAnchor.open
5082
+ )
5083
+ this.emitHoverAction(event.target);
5084
+ return;
5085
+ }
4304
5086
  // OneKey patch: non-button title help supports keyboard activation.
4305
5087
  if (
4306
5088
  (event.key === 'Enter' || event.key === ' ') &&
@@ -4438,6 +5220,7 @@ export class NativeListWebEngine {
4438
5220
 
4439
5221
  private handleScroll = () => {
4440
5222
  this.externalGeometryCache = undefined;
5223
+ this.pressedHelpElement = undefined;
4441
5224
  this.cancelMarketPointer();
4442
5225
  this.invalidateActionAnchor('scroll');
4443
5226
  this.scheduleFrame();