@onekeyfe/react-native-native-list 3.0.105

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 (181) hide show
  1. package/README.md +342 -0
  2. package/android/CMakeLists.txt +20 -0
  3. package/android/build.gradle +105 -0
  4. package/android/gradle.properties +4 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  7. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +125 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +165 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +147 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListPackage.kt +25 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +2939 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1794 -0
  13. package/common/fonts/Roobert-Bold.ttf +0 -0
  14. package/common/fonts/Roobert-Medium.ttf +0 -0
  15. package/common/fonts/Roobert-Regular.ttf +0 -0
  16. package/common/fonts/Roobert-SemiBold.ttf +0 -0
  17. package/docs/DESIGN.md +129 -0
  18. package/ios/HybridNativeList.swift +131 -0
  19. package/ios/NativeListCell.swift +2858 -0
  20. package/ios/NativeListDesignAssets.swift +110 -0
  21. package/ios/NativeListModels.swift +215 -0
  22. package/ios/RNCNativeListView.swift +1737 -0
  23. package/ios/Resources/NativeListIcons.xcassets/Contents.json +1 -0
  24. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/Contents.json +1 -0
  25. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/icon.svg +1 -0
  26. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/Contents.json +1 -0
  27. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/icon.svg +1 -0
  28. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/Contents.json +1 -0
  29. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/icon.svg +1 -0
  30. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/Contents.json +1 -0
  31. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/icon.svg +1 -0
  32. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/Contents.json +1 -0
  33. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/icon.svg +1 -0
  34. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/Contents.json +1 -0
  35. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/icon.svg +1 -0
  36. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/Contents.json +1 -0
  37. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/icon.svg +1 -0
  38. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/Contents.json +1 -0
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/icon.svg +1 -0
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/Contents.json +1 -0
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/icon.svg +1 -0
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/Contents.json +1 -0
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/icon.svg +1 -0
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/Contents.json +1 -0
  45. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/icon.svg +1 -0
  46. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/Contents.json +1 -0
  47. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/icon.svg +1 -0
  48. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/Contents.json +1 -0
  49. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/icon.svg +1 -0
  50. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/Contents.json +1 -0
  51. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/icon.svg +1 -0
  52. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/Contents.json +1 -0
  53. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/icon.svg +1 -0
  54. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/Contents.json +1 -0
  55. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/icon.svg +1 -0
  56. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/Contents.json +1 -0
  57. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/icon.svg +1 -0
  58. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/Contents.json +1 -0
  59. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/icon.svg +1 -0
  60. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/Contents.json +1 -0
  61. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/icon.svg +1 -0
  62. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/Contents.json +1 -0
  63. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/icon.svg +1 -0
  64. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/Contents.json +1 -0
  65. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/icon.svg +1 -0
  66. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/Contents.json +1 -0
  67. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/icon.svg +1 -0
  68. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/Contents.json +1 -0
  69. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/icon.svg +1 -0
  70. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/Contents.json +1 -0
  71. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/icon.svg +1 -0
  72. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/Contents.json +1 -0
  73. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/icon.svg +1 -0
  74. package/lib/module/NativeList.js +183 -0
  75. package/lib/module/NativeList.nitro.js +4 -0
  76. package/lib/module/NativeList.types.js +4 -0
  77. package/lib/module/NativeList.web.js +167 -0
  78. package/lib/module/index.js +6 -0
  79. package/lib/module/models.js +2 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/scrolling.js +121 -0
  82. package/lib/module/selection.js +85 -0
  83. package/lib/module/validation.js +562 -0
  84. package/lib/module/web/NativeListWebEngine.js +2238 -0
  85. package/lib/nitrogen/generated/.gitattributes +1 -0
  86. package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  87. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
  88. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
  89. package/lib/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
  90. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
  91. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
  92. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
  93. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
  94. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
  95. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
  96. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
  97. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
  98. package/lib/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
  99. package/lib/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
  100. package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
  101. package/lib/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
  102. package/lib/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
  103. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
  104. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
  105. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
  106. package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
  107. package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
  108. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
  109. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
  110. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
  111. package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  112. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
  113. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
  114. package/lib/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
  115. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
  116. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
  117. package/lib/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
  118. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
  119. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
  120. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
  121. package/lib/typescript/package.json +1 -0
  122. package/lib/typescript/src/NativeList.d.ts +18 -0
  123. package/lib/typescript/src/NativeList.nitro.d.ts +29 -0
  124. package/lib/typescript/src/NativeList.types.d.ts +87 -0
  125. package/lib/typescript/src/NativeList.web.d.ts +19 -0
  126. package/lib/typescript/src/index.d.ts +7 -0
  127. package/lib/typescript/src/models.d.ts +456 -0
  128. package/lib/typescript/src/scrolling.d.ts +32 -0
  129. package/lib/typescript/src/selection.d.ts +27 -0
  130. package/lib/typescript/src/validation.d.ts +7 -0
  131. package/lib/typescript/src/web/NativeListWebEngine.d.ts +203 -0
  132. package/nitro.json +23 -0
  133. package/nitrogen/generated/.gitattributes +1 -0
  134. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  135. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
  136. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
  137. package/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
  138. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
  139. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
  146. package/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
  147. package/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
  148. package/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
  149. package/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
  150. package/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
  151. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
  152. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
  153. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
  154. package/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
  155. package/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
  156. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
  157. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
  158. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
  159. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  160. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
  161. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
  162. package/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
  163. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
  164. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
  165. package/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
  166. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
  167. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
  168. package/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
  169. package/package.json +126 -0
  170. package/react-native-native-list.podspec +38 -0
  171. package/react-native.config.js +19 -0
  172. package/src/NativeList.nitro.ts +48 -0
  173. package/src/NativeList.tsx +298 -0
  174. package/src/NativeList.types.ts +125 -0
  175. package/src/NativeList.web.tsx +258 -0
  176. package/src/index.ts +35 -0
  177. package/src/models.ts +604 -0
  178. package/src/scrolling.ts +189 -0
  179. package/src/selection.ts +159 -0
  180. package/src/validation.ts +877 -0
  181. package/src/web/NativeListWebEngine.ts +3767 -0
@@ -0,0 +1,2238 @@
1
+ "use strict";
2
+
3
+ import { checkboxStateForKeys, checkboxStateForSection, isSelectableRow, reduceSelection, selectionStateFromSnapshot } from "../selection.js";
4
+ import { calculateAlignedScrollOffset, resolveLocationIndex, scrollFailure, validateOffset } from "../scrolling.js";
5
+ import { applyRowPatches, validateSnapshot } from "../validation.js";
6
+ const SECTION_INDEX_GUTTER = 44;
7
+ const DEFAULT_VIEWPORT_WIDTH = 320;
8
+ const DEFAULT_VIEWPORT_HEIGHT = 640;
9
+ const OVERSCAN_VIEWPORTS = 1;
10
+ export const WEB_REORDER_ANIMATION = {
11
+ outOfWayDurationMs: 200,
12
+ outOfWayTimingFunction: 'cubic-bezier(0.2, 0, 0, 1)',
13
+ dropDurationMs: 80,
14
+ dropTimingFunction: 'ease'
15
+ };
16
+ const REORDER_TOUCH_LONG_PRESS_MS = 120;
17
+ const REORDER_MOUSE_MOVE_THRESHOLD_PX = 5;
18
+ const REORDER_EDGE_RATIO = 0.25;
19
+ const REORDER_MAX_SPEED_ZONE_RATIO = 0.05;
20
+ const REORDER_MAX_SCROLL_PX = 28;
21
+ const REORDER_SCROLL_ACCELERATE_AT_MS = 360;
22
+ const REORDER_SCROLL_DAMPENING_MS = 1_200;
23
+ const REORDER_DROP_TRANSITION_MS = WEB_REORDER_ANIMATION.dropDurationMs;
24
+ const WALLET_REORDER_COMPACT_HEIGHT = 68;
25
+ let webNativeListInstanceCounter = 0;
26
+ const defaultTheme = {
27
+ background: '#F7F7F7',
28
+ rowBackground: '#FFFFFF',
29
+ rowSelectedBackground: '#EAF2FF',
30
+ rowPressedBackground: '#E8E8E8',
31
+ subduedBackground: '#F9F9F9',
32
+ strongBackground: '#0000000F',
33
+ primaryText: '#111111',
34
+ secondaryText: '#6B7280',
35
+ disabledText: '#8D8D8D',
36
+ icon: '#111111',
37
+ iconSubdued: '#8D8D8D',
38
+ separator: '#E5E7EB',
39
+ accent: '#2F6BFF',
40
+ positive: '#15803D',
41
+ negative: '#DC2626',
42
+ criticalBackground: '#FEECEC',
43
+ inverseBackground: '#202020',
44
+ inverseText: '#FCFCFC',
45
+ info: '#0D74CE'
46
+ };
47
+ export function webActionAnchorPayload(token, rect, source, generation, layoutDirection, slot) {
48
+ return {
49
+ token,
50
+ windowRect: {
51
+ x: rect.left,
52
+ y: rect.top,
53
+ width: rect.width,
54
+ height: rect.height
55
+ },
56
+ source,
57
+ slot,
58
+ generation,
59
+ layoutDirection
60
+ };
61
+ }
62
+ export function isWebRowReorderable(snapshot, row) {
63
+ if (!snapshot.capabilities?.reorderable || row.disabled) return false;
64
+ if (row.type === 'rail' && row.draggable) return true;
65
+ if (row.type === 'identity' && row.draggable) return true;
66
+ if (row.type === 'walletGroup' && row.draggable) return true;
67
+ return (row.type === 'identity' || row.type === 'action') && Boolean(row.trailing?.some(accessory => accessory.kind === 'drag'));
68
+ }
69
+ export function moveWebReorderRow(rows, fromIndex, toIndex) {
70
+ if (fromIndex === toIndex || fromIndex < 0 || fromIndex >= rows.length || toIndex < 0 || toIndex >= rows.length) {
71
+ return rows;
72
+ }
73
+ const next = [...rows];
74
+ const [moved] = next.splice(fromIndex, 1);
75
+ if (!moved) return rows;
76
+ next.splice(toIndex, 0, moved);
77
+ return next;
78
+ }
79
+ export function cancelWebReorderRows(originalRows) {
80
+ return originalRows;
81
+ }
82
+ export function webReorderEventForRows(originalRows, finalRows, key) {
83
+ const fromIndex = originalRows.findIndex(row => row.key === key);
84
+ const toIndex = finalRows.findIndex(row => row.key === key);
85
+ if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return undefined;
86
+ return {
87
+ key,
88
+ fromIndex,
89
+ toIndex,
90
+ beforeKey: finalRows[toIndex - 1]?.key,
91
+ afterKey: finalRows[toIndex + 1]?.key
92
+ };
93
+ }
94
+ export function webReorderAutoScrollVelocity(point, start, end, elapsedMs) {
95
+ const viewportLength = Math.max(1, end - start);
96
+ const edge = viewportLength * REORDER_EDGE_RATIO;
97
+ const maxZone = viewportLength * REORDER_MAX_SPEED_ZONE_RATIO;
98
+ const speedForDistance = distance => {
99
+ if (distance > edge) return 0;
100
+ const proposed = distance <= maxZone ? REORDER_MAX_SCROLL_PX : distance === edge ? 1 : Math.ceil(REORDER_MAX_SCROLL_PX * Math.pow((edge - distance) / Math.max(1, edge - maxZone), 2));
101
+ if (elapsedMs < REORDER_SCROLL_ACCELERATE_AT_MS) return 1;
102
+ if (elapsedMs >= REORDER_SCROLL_DAMPENING_MS) return proposed;
103
+ const timeProgress = (elapsedMs - REORDER_SCROLL_ACCELERATE_AT_MS) / (REORDER_SCROLL_DAMPENING_MS - REORDER_SCROLL_ACCELERATE_AT_MS);
104
+ return Math.ceil(proposed * timeProgress * timeProgress);
105
+ };
106
+ if (point <= start + edge) return -speedForDistance(point - start);
107
+ if (point >= end - edge) return speedForDistance(end - point);
108
+ return 0;
109
+ }
110
+ export function hasExceededWebReorderMouseThreshold(deltaX, deltaY) {
111
+ return Math.abs(deltaX) >= REORDER_MOUSE_MOVE_THRESHOLD_PX || Math.abs(deltaY) >= REORDER_MOUSE_MOVE_THRESHOLD_PX;
112
+ }
113
+ function effectiveRows(snapshot) {
114
+ if (snapshot.rows.length > 0) return snapshot.rows;
115
+ return snapshot.emptyState ? [snapshot.emptyState] : [];
116
+ }
117
+ function resolvedTheme(snapshot) {
118
+ return {
119
+ ...defaultTheme,
120
+ ...snapshot.theme
121
+ };
122
+ }
123
+ function paddingValues(snapshot) {
124
+ const fallback = snapshot.layout.contentPadding ?? 0;
125
+ return {
126
+ horizontal: snapshot.layout.contentPaddingHorizontal ?? fallback,
127
+ top: snapshot.layout.contentPaddingTop ?? fallback,
128
+ bottom: snapshot.layout.contentPaddingBottom ?? fallback
129
+ };
130
+ }
131
+ function isStructuralRow(row) {
132
+ return row.type === 'sectionHeader' || row.type === 'system' || row.type === 'action';
133
+ }
134
+ function sizeModifier(row) {
135
+ if (row.type === 'sectionHeader' && (row.variant === 'summary' || row.variant === 'gallery')) return 0;
136
+ if (row.size === 'small') return -8;
137
+ if (row.size === 'large') return 12;
138
+ return 0;
139
+ }
140
+ function approximateMessageHeight(row, availableWidth) {
141
+ if (row.type !== 'message') return 0;
142
+ const leadingWidth = row.leading ? 52 : 0;
143
+ const thumbnailWidth = row.thumbnail ? 88 : 0;
144
+ const charactersPerLine = Math.max(18, Math.floor((availableWidth - leadingWidth - thumbnailWidth - 40) / 7));
145
+ const titleLines = Math.min(2, Math.max(1, Math.ceil(row.title.length / charactersPerLine)));
146
+ const bodyLines = Math.min(row.bodyLines ?? 3, Math.max(1, Math.ceil(row.body.length / charactersPerLine)));
147
+ return 32 + titleLines * 20 + bodyLines * 20 + 22;
148
+ }
149
+ export function estimateWebRowHeight(row, snapshot, availableWidth) {
150
+ if (row.type === 'system' && row.variant === 'spacer') return row.height;
151
+ if (row.type === 'walletGroup') return (row.children.length + 1) * 68 + row.children.length * 12;
152
+ if (row.type === 'identity' && row.presentation === 'walletSidebar') return 68;
153
+ if (row.type === 'identity' && row.presentation === 'networkSelector') return 47;
154
+ if (row.type === 'identity' && row.presentation === 'accountSelector') return 58;
155
+ let base;
156
+ switch (row.type) {
157
+ case 'rail':
158
+ base = 40;
159
+ break;
160
+ case 'activity':
161
+ base = row.footerActions?.length ? 100 : 60;
162
+ break;
163
+ case 'message':
164
+ base = approximateMessageHeight(row, availableWidth);
165
+ break;
166
+ case 'mediaTile':
167
+ base = 244;
168
+ break;
169
+ case 'metricCard':
170
+ base = row.variant === 'activity' ? 161 : row.variant === 'performance' ? 178 : 132;
171
+ break;
172
+ case 'sectionHeader':
173
+ {
174
+ const isHistory = row.variant === 'history' || row.key.startsWith('history-') || row.sectionKey.startsWith('history-');
175
+ base = snapshot.layout.kind === 'table' ? 28 : row.presentation === 'networkSelector' ? 47 : isHistory ? 16 : row.variant === 'summary' ? 68 : row.variant === 'gallery' ? 32 : row.checkbox ? 56 : snapshot.layout.kind === 'linear' ? 30 : 36;
176
+ break;
177
+ }
178
+ case 'system':
179
+ base = row.variant === 'noMatch' || row.variant === 'end' ? 36 : row.variant === 'retry' ? 44 : 56;
180
+ break;
181
+ case 'action':
182
+ base = row.presentation === 'accountSelector' ? 48 : row.icon ? 60 : 44;
183
+ break;
184
+ case 'dataRow':
185
+ base = row.columns.some(column => column.secondaryText) ? 60 : 56;
186
+ break;
187
+ case 'identity':
188
+ base = row.tertiary ? 72 : row.subtitle ? 60 : 56;
189
+ break;
190
+ default:
191
+ base = 56;
192
+ }
193
+ const tableAdjustment = snapshot.layout.kind === 'table' && row.type === 'dataRow' && !row.columns.some(column => column.secondaryText) ? -8 : 0;
194
+ return Math.max(0, base + sizeModifier(row) + tableAdjustment);
195
+ }
196
+ function estimateHorizontalWidth(row) {
197
+ if (row.type === 'mediaTile') return 200;
198
+ if (row.type !== 'rail') return 280;
199
+ const badgeLength = row.badge?.text.length ?? 0;
200
+ const statusLength = row.status && row.status !== 'none' ? row.status.length : 0;
201
+ return Math.min(288, Math.max(72, 50 + (row.title.length + badgeLength + statusLength) * 7));
202
+ }
203
+ function sectionIndexEnabled(snapshot) {
204
+ return Boolean(snapshot.capabilities?.sectionIndex?.enabled && snapshot.layout.kind === 'sectioned' && snapshot.layout.orientation !== 'horizontal' && snapshot.rows.some(row => row.type === 'sectionHeader' && row.indexTitle));
205
+ }
206
+ export function computeWebListLayout(snapshot, viewportWidth, viewportHeight, compactRowKey) {
207
+ const rows = effectiveRows(snapshot);
208
+ const horizontal = snapshot.layout.orientation === 'horizontal';
209
+ const spacing = snapshot.layout.itemSpacing ?? 0;
210
+ const padding = paddingValues(snapshot);
211
+ const indexGutter = sectionIndexEnabled(snapshot) ? SECTION_INDEX_GUTTER : 0;
212
+ const width = Math.max(1, viewportWidth || DEFAULT_VIEWPORT_WIDTH);
213
+ const height = Math.max(1, viewportHeight || DEFAULT_VIEWPORT_HEIGHT);
214
+ const availableWidth = Math.max(1, width - padding.horizontal * 2 - indexGutter);
215
+ const availableHeight = Math.max(1, height - padding.top - padding.bottom);
216
+ const items = [];
217
+ if (horizontal) {
218
+ let x = padding.horizontal;
219
+ rows.forEach((row, index) => {
220
+ const itemWidth = estimateHorizontalWidth(row);
221
+ const rowHeight = row.type === 'walletGroup' && row.key === compactRowKey ? WALLET_REORDER_COMPACT_HEIGHT : estimateWebRowHeight(row, snapshot, availableWidth);
222
+ items.push({
223
+ index,
224
+ key: row.key,
225
+ x,
226
+ y: padding.top,
227
+ width: itemWidth,
228
+ height: row.type === 'rail' ? Math.min(rowHeight, availableHeight) : availableHeight
229
+ });
230
+ x += itemWidth + spacing;
231
+ });
232
+ const contentWidth = rows.length > 0 ? x - spacing + padding.horizontal : padding.horizontal * 2;
233
+ return {
234
+ items,
235
+ horizontal: true,
236
+ contentWidth: Math.max(width, contentWidth),
237
+ contentHeight: height
238
+ };
239
+ }
240
+ if (snapshot.layout.kind !== 'grid') {
241
+ let y = padding.top;
242
+ rows.forEach((row, index) => {
243
+ const itemHeight = row.type === 'walletGroup' && row.key === compactRowKey ? WALLET_REORDER_COMPACT_HEIGHT : estimateWebRowHeight(row, snapshot, availableWidth);
244
+ items.push({
245
+ index,
246
+ key: row.key,
247
+ x: padding.horizontal,
248
+ y,
249
+ width: availableWidth,
250
+ height: itemHeight
251
+ });
252
+ y += itemHeight + spacing;
253
+ });
254
+ const contentHeight = rows.length > 0 ? y - spacing + padding.bottom : padding.top + padding.bottom;
255
+ return {
256
+ items,
257
+ horizontal: false,
258
+ contentWidth: width,
259
+ contentHeight: Math.max(height, contentHeight)
260
+ };
261
+ }
262
+ const columns = snapshot.layout.gridColumns ?? 2;
263
+ const itemWidth = Math.floor((availableWidth - spacing * (columns - 1)) / columns);
264
+ let y = padding.top;
265
+ let column = 0;
266
+ let bandStart = y;
267
+ let bandHeight = 0;
268
+ const flushBand = () => {
269
+ if (column === 0) return;
270
+ y = bandStart + bandHeight + spacing;
271
+ column = 0;
272
+ bandHeight = 0;
273
+ bandStart = y;
274
+ };
275
+ rows.forEach((row, index) => {
276
+ if (isStructuralRow(row)) {
277
+ flushBand();
278
+ const itemHeight = estimateWebRowHeight(row, snapshot, availableWidth);
279
+ items.push({
280
+ index,
281
+ key: row.key,
282
+ x: padding.horizontal,
283
+ y,
284
+ width: availableWidth,
285
+ height: itemHeight
286
+ });
287
+ y += itemHeight + spacing;
288
+ bandStart = y;
289
+ return;
290
+ }
291
+ const itemHeight = row.type === 'mediaTile' ? itemWidth + 48 : estimateWebRowHeight(row, snapshot, itemWidth);
292
+ items.push({
293
+ index,
294
+ key: row.key,
295
+ x: padding.horizontal + column * (itemWidth + spacing),
296
+ y: bandStart,
297
+ width: itemWidth,
298
+ height: itemHeight
299
+ });
300
+ bandHeight = Math.max(bandHeight, itemHeight);
301
+ column += 1;
302
+ if (column === columns) flushBand();
303
+ });
304
+ flushBand();
305
+ const contentHeight = rows.length > 0 ? Math.max(padding.top, y - spacing) + padding.bottom : padding.top + padding.bottom;
306
+ return {
307
+ items,
308
+ horizontal: false,
309
+ contentWidth: width,
310
+ contentHeight: Math.max(height, contentHeight)
311
+ };
312
+ }
313
+ export function webWalletGroupReorderBadge(row) {
314
+ return row.type === 'walletGroup' && row.children.length > 0 ? '+' + String(row.children.length) : undefined;
315
+ }
316
+ function itemStart(item, horizontal) {
317
+ return horizontal ? item.x : item.y;
318
+ }
319
+ function itemEnd(item, horizontal) {
320
+ return itemStart(item, horizontal) + (horizontal ? item.width : item.height);
321
+ }
322
+ export function visibleWebLayoutItems(layout, offset, viewportLength, overscan = 0) {
323
+ if (layout.items.length === 0) return [];
324
+ const start = Math.max(0, offset - overscan);
325
+ const end = offset + viewportLength + overscan;
326
+ let low = 0;
327
+ let high = layout.items.length;
328
+ while (low < high) {
329
+ const middle = Math.floor((low + high) / 2);
330
+ if (itemStart(layout.items[middle], layout.horizontal) < start) low = middle + 1;else high = middle;
331
+ }
332
+ // Grid items in a band share a start coordinate but can have different
333
+ // heights. Include the preceding band when any of its items overlaps the
334
+ // viewport; every earlier band is guaranteed to have ended before it.
335
+ if (low > 0) {
336
+ const previousStart = itemStart(layout.items[low - 1], layout.horizontal);
337
+ let bandStart = low - 1;
338
+ while (bandStart > 0 && itemStart(layout.items[bandStart - 1], layout.horizontal) === previousStart) bandStart -= 1;
339
+ if (layout.items.slice(bandStart, low).some(item => itemEnd(item, layout.horizontal) >= start)) low = bandStart;
340
+ }
341
+ const visible = [];
342
+ for (let index = low; index < layout.items.length; index += 1) {
343
+ const item = layout.items[index];
344
+ if (itemStart(item, layout.horizontal) > end) break;
345
+ if (itemEnd(item, layout.horizontal) >= start) visible.push(item);
346
+ }
347
+ return visible;
348
+ }
349
+ export function webLayoutItemsForMount(layout, offset, viewportLength, virtualizationEnabled, overscan = 0) {
350
+ return virtualizationEnabled ? visibleWebLayoutItems(layout, offset, viewportLength, overscan) : layout.items;
351
+ }
352
+ function rowWithoutSelectionState(row) {
353
+ const copy = {
354
+ ...row
355
+ };
356
+ delete copy.selected;
357
+ if (row.type === 'sectionHeader' || row.type === 'action' || row.type === 'dataRow') {
358
+ if (row.checkbox) copy.checkbox = {
359
+ ...row.checkbox,
360
+ state: undefined
361
+ };
362
+ }
363
+ if (row.type === 'identity' || row.type === 'action') {
364
+ copy.trailing = row.trailing?.map(accessory => accessory.kind === 'checkbox' ? {
365
+ ...accessory,
366
+ state: undefined
367
+ } : accessory);
368
+ }
369
+ return copy;
370
+ }
371
+ export function webRowRenderSignature(row) {
372
+ return JSON.stringify(rowWithoutSelectionState(row));
373
+ }
374
+ export const WEB_LIST_CSS = `
375
+ .ok-native-list-root{--nl-bg:#f7f7f7;--nl-row:#fff;--nl-selected:#eaf2ff;--nl-pressed:#e8e8e8;--nl-subdued:#f9f9f9;--nl-strong:#0000000f;--nl-primary:#111;--nl-secondary:#6b7280;--nl-disabled:#8d8d8d;--nl-icon:#111;--nl-icon-subdued:#8d8d8d;--nl-separator:#e5e7eb;--nl-accent:#2f6bff;--nl-positive:#15803d;--nl-negative:#dc2626;--nl-critical:#feecec;--nl-inverse:#202020;--nl-inverse-text:#fcfcfc;--nl-info:#0d74ce;position:absolute;inset:0;display:flex;min-width:0;min-height:0;overflow:hidden;background:var(--nl-bg);color:var(--nl-primary);font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-synthesis:none}
376
+ .ok-native-list-viewport-frame{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
377
+ .ok-native-list-viewport{position:absolute;inset:0;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;scrollbar-gutter:stable}
378
+ .ok-native-list-content{position:relative;min-width:100%;min-height:100%}
379
+ .ok-native-list-item{position:absolute;box-sizing:border-box;contain:layout paint style;outline:none}
380
+ .ok-native-list-row{width:100%;height:100%;box-sizing:border-box;display:flex;align-items:center;gap:12px;overflow:hidden;background:var(--nl-row);color:var(--nl-primary);cursor:pointer;user-select:none;-webkit-user-select:none}
381
+ .ok-native-list-item[data-table-alternate="true"]>.ok-native-list-row{background:var(--nl-bg)}
382
+ .ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
383
+ .ok-native-list-item[data-native-list-disabled="true"]>.ok-native-list-row{opacity:.5;cursor:default}
384
+ .ok-native-list-item:not([data-native-list-disabled="true"]):hover>.ok-native-list-row{background:var(--nl-pressed)}
385
+ .ok-native-list-item:not([data-native-list-disabled="true"]):not([data-native-list-selected="true"]):hover>.ok-native-list-wallet-row{background:var(--nl-strong)}
386
+ .ok-native-list-item:not([data-native-list-disabled="true"]):not([data-native-list-selected="true"]):active>.ok-native-list-wallet-row{background:var(--nl-pressed)}
387
+ .ok-native-list-item[data-native-list-selected="true"]:hover>.ok-native-list-wallet-row{background:var(--nl-selected)}
388
+ .ok-native-list-wallet-group{display:flex;width:100%;height:100%;box-sizing:border-box;flex-direction:column;gap:12px;overflow:hidden;border:1px solid var(--nl-separator);border-radius:12px;background:var(--nl-subdued);user-select:none;-webkit-user-select:none}
389
+ .ok-native-list-wallet-member{flex:0 0 68px;height:68px;overflow:hidden;border-radius:12px;background:transparent;cursor:pointer}
390
+ .ok-native-list-wallet-member>.ok-native-list-wallet-row{height:100%;background:transparent}
391
+ .ok-native-list-wallet-member[data-native-list-selected="true"]>.ok-native-list-wallet-row{background:var(--nl-selected)}
392
+ .ok-native-list-wallet-member:not([data-native-list-selected="true"]):hover>.ok-native-list-wallet-row{background:var(--nl-strong)}
393
+ .ok-native-list-wallet-member:not([data-native-list-selected="true"]):active>.ok-native-list-wallet-row{background:var(--nl-pressed)}
394
+ .ok-native-list-item:focus-visible>.ok-native-list-row{outline:2px solid var(--nl-accent);outline-offset:-2px}
395
+ .ok-native-list-item[data-native-list-reorderable="true"]>.ok-native-list-row{cursor:grab}
396
+ .ok-native-list-root[data-native-list-dragging="true"] .ok-native-list-row{cursor:grabbing}
397
+ .ok-native-list-item[data-native-list-animate-reorder="true"]{transition:transform ${WEB_REORDER_ANIMATION.outOfWayDurationMs}ms ${WEB_REORDER_ANIMATION.outOfWayTimingFunction},height ${WEB_REORDER_ANIMATION.outOfWayDurationMs}ms ${WEB_REORDER_ANIMATION.outOfWayTimingFunction}}
398
+ .ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row{overflow:hidden;border-radius:12px;background:var(--nl-row)}
399
+ .ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row>*{visibility:hidden}
400
+ .ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group{border-color:transparent;background:var(--nl-row)}
401
+ .ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group>*{visibility:hidden}
402
+ .ok-native-list-reorder-preview{position:fixed;left:0;top:0;z-index:100001;pointer-events:none;overflow:hidden;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.12);transform-origin:center;will-change:transform;font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
403
+ .ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-row);cursor:grabbing}
404
+ .ok-native-list-reorder-preview[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
405
+ .ok-native-list-reorder-count{position:absolute;right:4px;bottom:4px;display:flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:24px;height:24px;padding:0 6px;border:1px solid var(--nl-row);border-radius:12px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;line-height:22px;font-weight:600}
406
+ .ok-native-list-item[data-separator="true"]>.ok-native-list-row{border-bottom:1px solid var(--nl-separator)}
407
+ .ok-native-list-item[data-group-position="first"]>.ok-native-list-row{border-radius:12px 12px 0 0}
408
+ .ok-native-list-item[data-group-position="last"]>.ok-native-list-row{border-radius:0 0 12px 12px}
409
+ .ok-native-list-item[data-group-position="single"]>.ok-native-list-row{border-radius:12px}
410
+ .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}
411
+ .ok-native-list-account-row .ok-native-list-visual,.ok-native-list-account-action-row .ok-native-list-visual{width:32px;height:32px;flex-basis:32px}.ok-native-list-account-row .ok-native-list-visual>img,.ok-native-list-account-row .ok-native-list-visual-main,.ok-native-list-account-action-row .ok-native-list-visual>img,.ok-native-list-account-action-row .ok-native-list-visual-main{width:32px;height:32px}.ok-native-list-account-row .ok-native-list-visual>.ok-native-list-visual-corner{width:20px;height:20px;padding:2px}.ok-native-list-account-action-row .ok-native-list-visual{border-radius:8px!important}.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-secondary);font-size:12px;line-height:16px;font-weight:400}.ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-primary)}.ok-native-list-account-row .ok-native-list-title{font-size:16px;line-height:20px;font-weight:400}.ok-native-list-account-row .ok-native-list-secondary{font-size:14px;line-height:20px;font-weight:400}
412
+ .ok-native-list-flex{display:flex;flex:1;min-width:0;flex-direction:column;justify-content:center}.ok-native-list-title{font-size:15px;line-height:20px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-secondary{font-size:13px;line-height:18px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-tertiary{color:var(--nl-secondary)}.ok-native-list-info{color:var(--nl-info)}
413
+ .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}
414
+ .ok-native-list-value{font-size:14px;line-height:20px;font-weight:500;white-space:nowrap}.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}
415
+ .ok-native-list-action-button{appearance:none;border:0;background:transparent;padding:2px;color:var(--nl-accent);font:600 12px/16px inherit;cursor:pointer}.ok-native-list-action-button[data-tone="danger"]{color:var(--nl-negative)}.ok-native-list-action-button:disabled{opacity:.5;cursor:default}
416
+ .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}.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}
417
+ .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;font-family:inherit;cursor:pointer}.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)}}
418
+ .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}
419
+ .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}.ok-native-list-section[data-variant="summary"] .ok-native-list-section-title{font-size:16px;font-weight:500;color:var(--nl-primary)}.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}.ok-native-list-section-value{margin-left:auto}
420
+ .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)}.ok-native-list-action-title[data-tone="danger"]{color:var(--nl-negative)}
421
+ .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}
422
+ .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}
423
+ .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}
424
+ .ok-native-list-media{display:block;padding:0 5px;background:transparent;border-radius:16px}.ok-native-list-media-image{display:block;width:100%;aspect-ratio:1;border-radius:10px;background:var(--nl-strong);object-fit:cover}.ok-native-list-media-image[data-state="empty"]{background:transparent}.ok-native-list-media-image[data-state="error"]{display:flex;align-items:center;justify-content:center;color:var(--nl-icon-subdued);font-size:24px}.ok-native-list-media-meta{padding-top:7px}.ok-native-list-media-subtitle-row{display:flex;align-items:center;gap:6px}.ok-native-list-media-subtitle{flex:1;min-width:0;font-size:12px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-media-network{width:14px;height:14px;border-radius:50%}.ok-native-list-media-title{font-size:16px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-media-close{position:absolute;right:9px;top:4px;border:0;background:color-mix(in srgb,var(--nl-inverse) 72%,transparent);color:var(--nl-inverse-text);width:24px;height:24px;border-radius:50%;font:18px/20px inherit;cursor:pointer}
425
+ .ok-native-list-metric{display:flex;flex-direction:column;align-items:flex-start;padding:12px;border-radius:12px;gap:5px;background:var(--nl-row)}.ok-native-list-metric-value{font-size:22px;line-height:28px;font-weight:700}.ok-native-list-composite{display:flex;flex-direction:column;align-items:stretch;padding:14px;border-radius:12px;gap:12px;background:var(--nl-subdued)}.ok-native-list-composite-heading{font-size:14px;letter-spacing:1px;color:var(--nl-secondary)}.ok-native-list-composite-row{display:flex;gap:12px}.ok-native-list-composite-cell{flex:1;min-width:0}.ok-native-list-composite-cell[data-shaded="true"]{padding:10px;border-radius:10px;background:color-mix(in srgb,var(--nl-primary) 5%,transparent)}.ok-native-list-composite-value{font-size:18px;font-weight:600}.ok-native-list-divider{height:1px;background:var(--nl-separator)}.ok-native-list-progress{height:4px;border-radius:2px;overflow:hidden;background:var(--nl-negative)}.ok-native-list-progress>span{display:block;height:100%;border-radius:2px;background:var(--nl-positive)}
426
+ .ok-native-list-data{padding:6px 12px}.ok-native-list-index{flex:0 0 28px;color:var(--nl-secondary);font-size:13px}.ok-native-list-favorite{flex:0 0 24px;color:var(--nl-icon-subdued);font-size:22px}.ok-native-list-favorite[data-active="true"]{color:var(--nl-accent)}.ok-native-list-data-cell{display:flex;flex-direction:column;min-width:0}.ok-native-list-data-cell[data-align="center"]{align-items:center}.ok-native-list-data-cell[data-align="end"]{align-items:flex-end}.ok-native-list-data-primary{display:flex;align-items:center;gap:5px;max-width:100%;font-size:16px;font-weight:500;white-space:nowrap}.ok-native-list-unread{width:7px;height:7px;flex:0 0 7px;border-radius:50%;background:var(--nl-accent)}.ok-native-list-thumbnail{width:64px;height:64px;border-radius:10px;object-fit:cover}
427
+ .ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:44px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;touch-action:none}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;border:0;background:transparent;display:flex;flex:1;max-height:22px;min-height:12px;width:100%;align-items:center;justify-content:center;padding:0;color:var(--nl-secondary);font:600 11px/1 inherit;cursor:pointer}.ok-native-list-index-button[data-active="true"]{color:var(--nl-accent)}.ok-native-list-index-preview{position:absolute;z-index:8;left:50%;top:50%;display:flex;width:72px;height:72px;align-items:center;justify-content:center;transform:translate(-50%,-50%) scale(.92);border-radius:16px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:28px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease}.ok-native-list-index-preview[data-visible="true"]{opacity:1;transform:translate(-50%,-50%) scale(1)}
428
+ .ok-native-list-refresh{position:absolute;z-index:7;left:50%;top:8px;display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;opacity:0;transform:translate(-50%,-16px);transition:opacity .15s ease,transform .15s ease;pointer-events:none}.ok-native-list-refresh[data-visible="true"]{opacity:1;transform:translate(-50%,0)}
429
+ @media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner{animation:none}}
430
+ `;
431
+ function createElement(document, tag, className, text) {
432
+ const element = document.createElement(tag);
433
+ if (className) element.className = className;
434
+ if (text !== undefined) element.textContent = text;
435
+ return element;
436
+ }
437
+ function setData(element, name, value) {
438
+ if (value === undefined) delete element.dataset[name];else element.dataset[name] = String(value);
439
+ }
440
+ function safeImageUri(uri) {
441
+ const trimmed = uri.trim();
442
+ if (/^(https?:|data:image\/|blob:|file:)/i.test(trimmed) || trimmed.startsWith('/')) return trimmed;
443
+ return undefined;
444
+ }
445
+ function createImage(context, source, className) {
446
+ const uri = safeImageUri(source.uri);
447
+ if (!uri) return undefined;
448
+ const image = context.document.createElement('img');
449
+ if (className) image.className = className;
450
+ image.src = uri;
451
+ image.alt = '';
452
+ image.draggable = false;
453
+ image.loading = 'lazy';
454
+ image.decoding = 'async';
455
+ image.style.objectFit = source.contentFit === 'fill' ? 'fill' : source.contentFit ?? 'cover';
456
+ return image;
457
+ }
458
+ function iconGlyph(name) {
459
+ const normalized = name.toLocaleLowerCase();
460
+ if (normalized.includes('chevron')) {
461
+ if (normalized.includes('top')) return '⌃';
462
+ if (normalized.includes('bottom')) return '⌄';
463
+ return '›';
464
+ }
465
+ if (normalized.includes('plus') || normalized.includes('add')) return '+';
466
+ if (normalized.includes('minus')) return '−';
467
+ if (normalized.includes('star')) return '☆';
468
+ if (normalized.includes('drag') || normalized.includes('grabber')) return '≡';
469
+ if (normalized.includes('dot') || normalized.includes('more')) return '•••';
470
+ if (normalized.includes('pencil') || normalized.includes('edit')) return '✎';
471
+ if (normalized.includes('error') || normalized.includes('exclamation')) return '!';
472
+ if (normalized.includes('info')) return 'i';
473
+ if (normalized.includes('question')) return '?';
474
+ if (normalized.includes('swap')) return '⇄';
475
+ if (normalized.includes('trend') || normalized.includes('arrow')) return '↗';
476
+ return name.slice(0, 1).toLocaleUpperCase();
477
+ }
478
+ function visualFromRow(row) {
479
+ if (row.type === 'identity') return row.leading;
480
+ if (row.type === 'rail') return row.visual;
481
+ if (row.type === 'activity') return row.leading;
482
+ if (row.type === 'message') return row.leading;
483
+ if (row.type === 'dataRow') return row.leading;
484
+ if (row.type === 'metricCard') return row.visual;
485
+ return undefined;
486
+ }
487
+ function createVisual(context, visual) {
488
+ if (!visual) return undefined;
489
+ if (visual.kind === 'stackedImages') {
490
+ const stack = createElement(context.document, 'span', 'ok-native-list-stacked');
491
+ visual.images.forEach(source => {
492
+ const image = createImage(context, source);
493
+ if (image) stack.appendChild(image);
494
+ });
495
+ return stack;
496
+ }
497
+ const frame = createElement(context.document, 'span', 'ok-native-list-visual');
498
+ const shape = visual.kind === 'icon' ? 'circle' : visual.shape ?? 'circle';
499
+ frame.style.borderRadius = shape === 'square' ? '0' : shape === 'rounded' ? '10px' : '50%';
500
+ if (visual.kind === 'icon') {
501
+ frame.style.background = visual.backgroundColor ?? 'var(--nl-strong)';
502
+ const fallback = createElement(context.document, 'span', 'ok-native-list-visual-fallback', iconGlyph(visual.name));
503
+ if (visual.tintColor) fallback.style.color = visual.tintColor;
504
+ frame.appendChild(fallback);
505
+ return frame;
506
+ }
507
+ if ('backgroundColor' in visual && visual.backgroundColor) frame.style.background = visual.backgroundColor;
508
+ const source = visual.kind === 'image' ? visual.image : visual.image;
509
+ const image = source ? createImage(context, source) : undefined;
510
+ if (image) {
511
+ image.className = 'ok-native-list-visual-main';
512
+ frame.appendChild(image);
513
+ } else {
514
+ frame.appendChild(createElement(context.document, 'span', 'ok-native-list-visual-fallback', 'fallbackText' in visual ? visual.fallbackText ?? '' : ''));
515
+ }
516
+ if (visual.kind === 'token' && visual.networkImage) {
517
+ const corner = createImage(context, visual.networkImage, 'ok-native-list-visual-corner');
518
+ if (corner) frame.appendChild(corner);
519
+ } else if ('cornerIcon' in visual && visual.cornerIcon) {
520
+ const corner = createElement(context.document, 'span', 'ok-native-list-visual-corner ok-native-list-visual-fallback', iconGlyph(visual.cornerIcon.name));
521
+ if (visual.cornerIcon.tintColor) corner.style.color = visual.cornerIcon.tintColor;
522
+ if (visual.cornerIcon.backgroundColor) corner.style.background = visual.cornerIcon.backgroundColor;
523
+ frame.appendChild(corner);
524
+ }
525
+ return frame;
526
+ }
527
+ function toneColor(tone, fallback) {
528
+ if (tone === 'positive') return 'var(--nl-positive)';
529
+ if (tone === 'negative') return 'var(--nl-negative)';
530
+ if (tone === 'secondary') return 'var(--nl-secondary)';
531
+ return fallback === 'secondary' ? 'var(--nl-secondary)' : 'var(--nl-primary)';
532
+ }
533
+ function createBadge(context, badge) {
534
+ const element = createElement(context.document, 'span', 'ok-native-list-badge', badge.text);
535
+ setData(element, 'tone', badge.tone);
536
+ return element;
537
+ }
538
+ function selectionKeysForTarget(target, rowKey, snapshot) {
539
+ if (!target || target.scope === 'row') {
540
+ return snapshot.rows.some(row => row.key === rowKey && isSelectableRow(row)) ? [rowKey] : [];
541
+ }
542
+ if (target.scope === 'section') {
543
+ return snapshot.rows.filter(row => row.sectionKey === target.sectionKey && isSelectableRow(row)).map(row => row.key);
544
+ }
545
+ return snapshot.rows.filter(isSelectableRow).map(row => row.key);
546
+ }
547
+ function checkboxState(target, fallback, rowKey, snapshot, selectedKeys) {
548
+ if (target?.scope === 'section') {
549
+ return checkboxStateForSection(target.sectionKey, snapshot.rows, selectedKeys);
550
+ }
551
+ return checkboxStateForKeys(selectionKeysForTarget(target, rowKey, snapshot), selectedKeys);
552
+ }
553
+ function createCheckbox(context, rowKey, accessory) {
554
+ if (accessory.loading) {
555
+ const spinner = createElement(context.document, 'span', 'ok-native-list-spinner');
556
+ spinner.setAttribute('aria-label', 'Loading');
557
+ return spinner;
558
+ }
559
+ const state = checkboxState(accessory.target, accessory.state, rowKey, context.snapshot, context.selectedKeys);
560
+ const element = createElement(context.document, 'button', 'ok-native-list-checkbox');
561
+ element.setAttribute('type', 'button');
562
+ element.setAttribute('role', 'checkbox');
563
+ element.setAttribute('aria-checked', state === 'indeterminate' ? 'mixed' : String(state === 'checked'));
564
+ element.setAttribute('aria-label', 'Select');
565
+ element.toggleAttribute('disabled', Boolean(accessory.disabled));
566
+ setData(element, 'state', state);
567
+ setData(element, 'checkboxFallback', accessory.state);
568
+ setData(element, 'nativeListAction', accessory.actionKey ?? 'selection');
569
+ setData(element, 'selectionScope', accessory.target?.scope ?? 'row');
570
+ if (accessory.target?.scope === 'section') setData(element, 'selectionKey', accessory.target.sectionKey);else if (accessory.target?.scope === 'row') setData(element, 'selectionKey', rowKey);
571
+ return element;
572
+ }
573
+ function createIconAction(context, name, actionKey, disabled, tintColor) {
574
+ const element = createElement(context.document, actionKey ? 'button' : 'span', 'ok-native-list-icon-button', iconGlyph(name));
575
+ if (element.tagName === 'BUTTON') {
576
+ element.setAttribute('type', 'button');
577
+ element.toggleAttribute('disabled', Boolean(disabled));
578
+ }
579
+ if (actionKey) setData(element, 'nativeListAction', actionKey);
580
+ if (tintColor) element.style.color = tintColor;
581
+ return element;
582
+ }
583
+ function markActionAnchorSource(element, source, slot) {
584
+ setData(element, 'nativeListAnchorSource', source);
585
+ if (slot !== undefined) setData(element, 'nativeListAnchorSlot', slot);
586
+ }
587
+ function createAccessory(context, rowKey, accessory, slot) {
588
+ if (accessory.kind === 'checkbox') {
589
+ const element = createCheckbox(context, rowKey, accessory);
590
+ markActionAnchorSource(element, 'trailingAccessory', slot);
591
+ return element;
592
+ }
593
+ if (accessory.kind === 'icon') {
594
+ const element = createIconAction(context, accessory.name, accessory.actionKey, accessory.disabled, accessory.tintColor);
595
+ markActionAnchorSource(element, 'trailingAccessory', slot);
596
+ return element;
597
+ }
598
+ if (accessory.kind === 'spinner') {
599
+ return createElement(context.document, 'span', 'ok-native-list-spinner');
600
+ }
601
+ const actionKey = 'actionKey' in accessory ? accessory.actionKey : undefined;
602
+ const element = createElement(context.document, actionKey ? 'button' : 'span', 'ok-native-list-accessory');
603
+ if (element.tagName === 'BUTTON') {
604
+ element.setAttribute('type', 'button');
605
+ element.classList.add('ok-native-list-icon-button');
606
+ setData(element, 'nativeListAction', actionKey);
607
+ }
608
+ switch (accessory.kind) {
609
+ case 'value':
610
+ element.textContent = accessory.text;
611
+ if (accessory.secondary) element.classList.add('ok-native-list-accessory-secondary');
612
+ break;
613
+ case 'valuePair':
614
+ {
615
+ const primary = createElement(context.document, 'span', undefined, accessory.primary);
616
+ primary.style.color = toneColor(accessory.primaryTone, 'primary');
617
+ const secondary = createElement(context.document, 'span', 'ok-native-list-secondary', accessory.secondary);
618
+ secondary.style.color = toneColor(accessory.secondaryTone, 'secondary');
619
+ element.replaceChildren(primary, secondary);
620
+ element.classList.add('ok-native-list-amounts');
621
+ break;
622
+ }
623
+ case 'radio':
624
+ element.textContent = accessory.checked ? '●' : '○';
625
+ break;
626
+ case 'switch':
627
+ element.textContent = accessory.value ? 'ON' : 'OFF';
628
+ setData(element, 'nativeListAction', accessory.actionKey);
629
+ break;
630
+ case 'chevron':
631
+ element.textContent = '›';
632
+ break;
633
+ case 'menu':
634
+ element.textContent = '⋮';
635
+ break;
636
+ case 'drag':
637
+ element.textContent = '≡';
638
+ break;
639
+ case 'progress':
640
+ element.textContent = String(Math.round(accessory.value * 100)) + '%';
641
+ break;
642
+ }
643
+ if (actionKey) markActionAnchorSource(element, 'trailingAccessory', slot);
644
+ return element;
645
+ }
646
+ function appendAccessories(parent, context, rowKey, accessories) {
647
+ if (!accessories?.length) return;
648
+ const container = createElement(context.document, 'span', 'ok-native-list-accessories');
649
+ accessories.forEach((accessory, slot) => container.appendChild(createAccessory(context, rowKey, accessory, slot)));
650
+ parent.appendChild(container);
651
+ }
652
+ function createTextColumn(context, title, subtitle, tertiary, tertiaryTone, badges) {
653
+ const column = createElement(context.document, 'span', 'ok-native-list-flex');
654
+ const titleLine = createElement(context.document, 'span', 'ok-native-list-title', title);
655
+ if (badges?.length) {
656
+ const badgeLine = createElement(context.document, 'span', 'ok-native-list-badges');
657
+ badges.forEach(badge => badgeLine.appendChild(createBadge(context, badge)));
658
+ titleLine.appendChild(badgeLine);
659
+ }
660
+ column.appendChild(titleLine);
661
+ if (subtitle) column.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', subtitle));
662
+ if (tertiary) {
663
+ const element = createElement(context.document, 'span', tertiaryTone === 'info' ? 'ok-native-list-secondary ok-native-list-info' : 'ok-native-list-secondary ok-native-list-tertiary', tertiary);
664
+ column.appendChild(element);
665
+ }
666
+ return column;
667
+ }
668
+ function createSectionHeader(context, row) {
669
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-section');
670
+ setData(body, 'variant', row.variant);
671
+ if (row.titleIcon) {
672
+ const titleIcon = createIconAction(context, row.titleIcon.name, row.titleIcon.actionKey, row.titleIcon.disabled, row.titleIcon.tintColor);
673
+ markActionAnchorSource(titleIcon, 'leadingAction');
674
+ body.appendChild(titleIcon);
675
+ }
676
+ body.appendChild(createTextColumn(context, row.title, row.subtitle));
677
+ if (row.value) {
678
+ const value = createElement(context.document, row.valueActionKey ? 'button' : 'span', row.valueActionKey ? 'ok-native-list-action-button ok-native-list-section-value' : 'ok-native-list-value ok-native-list-section-value', row.value);
679
+ if (row.valueActionKey) {
680
+ value.setAttribute('type', 'button');
681
+ setData(value, 'nativeListAction', row.valueActionKey);
682
+ markActionAnchorSource(value, 'trailingAccessory', 0);
683
+ }
684
+ body.appendChild(value);
685
+ }
686
+ if (row.valueIcon) {
687
+ const valueIcon = createIconAction(context, row.valueIcon.name, row.valueIcon.actionKey, row.valueIcon.disabled, row.valueIcon.tintColor);
688
+ markActionAnchorSource(valueIcon, 'trailingAccessory', 1);
689
+ body.appendChild(valueIcon);
690
+ }
691
+ if (row.checkbox) body.appendChild(createCheckbox(context, row.key, row.checkbox));
692
+ return body;
693
+ }
694
+ function createActionRow(context, row) {
695
+ const body = createElement(context.document, 'div', ['ok-native-list-row', 'ok-native-list-action-row', row.presentation === 'accountSelector' ? 'ok-native-list-account-action-row' : ''].filter(Boolean).join(' '));
696
+ if (row.icon) body.appendChild(createVisual(context, row.icon));
697
+ const title = createElement(context.document, 'span', 'ok-native-list-action-title', row.title);
698
+ setData(title, 'tone', row.tone);
699
+ body.appendChild(title);
700
+ if (row.checkbox) body.appendChild(createCheckbox(context, row.key, row.checkbox));
701
+ appendAccessories(body, context, row.key, row.trailing);
702
+ return body;
703
+ }
704
+ function createSystemRow(context, row) {
705
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-system');
706
+ setData(body, 'variant', row.variant);
707
+ if (row.variant === 'loading') body.appendChild(createElement(context.document, 'span', 'ok-native-list-spinner'));
708
+ const message = row.variant === 'spacer' ? '' : row.message ?? (row.variant === 'end' ? 'End' : '');
709
+ if (message) body.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', message));
710
+ return body;
711
+ }
712
+ function createRailRow(context, row) {
713
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-rail');
714
+ const visual = createVisual(context, row.visual);
715
+ if (visual) body.appendChild(visual);
716
+ body.appendChild(createElement(context.document, 'span', 'ok-native-list-rail-title', row.title));
717
+ if (row.status && row.status !== 'none') body.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', row.status));
718
+ if (row.badge) body.appendChild(createBadge(context, row.badge));
719
+ return body;
720
+ }
721
+ function createMediaRow(context, row) {
722
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-media');
723
+ if (row.image && !row.imageState) {
724
+ const image = createImage(context, row.image, 'ok-native-list-media-image');
725
+ if (image) body.appendChild(image);
726
+ } else {
727
+ const placeholder = createElement(context.document, 'span', 'ok-native-list-media-image', row.imageState === 'error' ? '▧' : '');
728
+ setData(placeholder, 'state', row.imageState ?? 'empty');
729
+ body.appendChild(placeholder);
730
+ }
731
+ const metadata = createElement(context.document, 'div', 'ok-native-list-media-meta');
732
+ const subtitleLine = createElement(context.document, 'div', 'ok-native-list-media-subtitle-row');
733
+ subtitleLine.appendChild(createElement(context.document, 'span', 'ok-native-list-media-subtitle', row.subtitle || '-'));
734
+ if (row.networkImage) {
735
+ const network = createImage(context, row.networkImage, 'ok-native-list-media-network');
736
+ if (network) subtitleLine.appendChild(network);
737
+ }
738
+ metadata.appendChild(subtitleLine);
739
+ metadata.appendChild(createElement(context.document, 'div', 'ok-native-list-media-title', row.title));
740
+ if (row.badge) metadata.appendChild(createBadge(context, row.badge));
741
+ body.appendChild(metadata);
742
+ if (row.closeActionKey) {
743
+ const close = createElement(context.document, 'button', 'ok-native-list-media-close', '×');
744
+ close.setAttribute('type', 'button');
745
+ close.setAttribute('aria-label', 'Close');
746
+ setData(close, 'nativeListAction', row.closeActionKey);
747
+ markActionAnchorSource(close, 'mediaClose');
748
+ body.appendChild(close);
749
+ }
750
+ return body;
751
+ }
752
+ function createMetricCell(context, metric, shaded) {
753
+ const cell = createElement(context.document, 'div', 'ok-native-list-composite-cell');
754
+ setData(cell, 'shaded', shaded);
755
+ cell.appendChild(createElement(context.document, 'div', 'ok-native-list-secondary', metric.label));
756
+ const valueLine = createElement(context.document, 'div');
757
+ valueLine.style.display = 'flex';
758
+ valueLine.style.alignItems = 'center';
759
+ valueLine.style.gap = '6px';
760
+ if (metric.visual) {
761
+ const visual = createVisual(context, metric.visual);
762
+ if (visual) {
763
+ visual.style.width = '16px';
764
+ visual.style.height = '16px';
765
+ visual.style.flexBasis = '16px';
766
+ const image = visual.querySelector('img');
767
+ if (image) {
768
+ image.style.width = '16px';
769
+ image.style.height = '16px';
770
+ }
771
+ valueLine.appendChild(visual);
772
+ }
773
+ }
774
+ const value = createElement(context.document, 'span', 'ok-native-list-composite-value', metric.value);
775
+ value.style.color = toneColor(metric.tone, 'primary');
776
+ valueLine.appendChild(value);
777
+ cell.appendChild(valueLine);
778
+ return cell;
779
+ }
780
+ function createMetricRow(context, row) {
781
+ if (row.variant === 'activity' || row.variant === 'performance') {
782
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-composite');
783
+ body.appendChild(createElement(context.document, 'div', 'ok-native-list-composite-heading', row.title));
784
+ const metrics = row.metrics ?? [];
785
+ const firstLine = createElement(context.document, 'div', 'ok-native-list-composite-row');
786
+ metrics.slice(0, 2).forEach(metric => firstLine.appendChild(createMetricCell(context, metric, false)));
787
+ body.appendChild(firstLine);
788
+ if (row.variant === 'activity') {
789
+ body.appendChild(createElement(context.document, 'div', 'ok-native-list-divider'));
790
+ } else {
791
+ const progress = createElement(context.document, 'div', 'ok-native-list-progress');
792
+ const fill = createElement(context.document, 'span');
793
+ fill.style.width = String(Math.round((row.progress ?? 0) * 100)) + '%';
794
+ progress.appendChild(fill);
795
+ body.appendChild(progress);
796
+ }
797
+ const secondLine = createElement(context.document, 'div', 'ok-native-list-composite-row');
798
+ metrics.slice(2).forEach(metric => secondLine.appendChild(createMetricCell(context, metric, row.variant === 'performance')));
799
+ body.appendChild(secondLine);
800
+ return body;
801
+ }
802
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-metric');
803
+ if (row.visual) {
804
+ const visual = createVisual(context, row.visual);
805
+ if (visual) body.appendChild(visual);
806
+ }
807
+ body.appendChild(createElement(context.document, 'div', 'ok-native-list-secondary', row.title));
808
+ body.appendChild(createElement(context.document, 'div', 'ok-native-list-metric-value', row.value));
809
+ if (row.trend) {
810
+ const trend = createElement(context.document, 'div', 'ok-native-list-secondary', row.trend);
811
+ trend.style.color = row.trendTone === 'positive' ? 'var(--nl-positive)' : row.trendTone === 'negative' ? 'var(--nl-negative)' : 'var(--nl-secondary)';
812
+ body.appendChild(trend);
813
+ }
814
+ if (row.subtitle) body.appendChild(createElement(context.document, 'div', 'ok-native-list-secondary', row.subtitle));
815
+ if (row.badge) body.appendChild(createBadge(context, row.badge));
816
+ return body;
817
+ }
818
+ function createDataRow(context, row) {
819
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-data');
820
+ if (row.checkbox) body.appendChild(createCheckbox(context, row.key, row.checkbox));
821
+ if (row.index !== undefined) body.appendChild(createElement(context.document, 'span', 'ok-native-list-index', String(row.index)));
822
+ if (row.favorite) {
823
+ const favorite = createElement(context.document, 'span', 'ok-native-list-favorite', row.favoriteActive ? '★' : '☆');
824
+ setData(favorite, 'active', row.favoriteActive);
825
+ body.appendChild(favorite);
826
+ }
827
+ if (row.leading) {
828
+ const visual = createVisual(context, row.leading);
829
+ if (visual) body.appendChild(visual);
830
+ }
831
+ row.columns.forEach(column => {
832
+ const cell = createElement(context.document, 'span', 'ok-native-list-data-cell');
833
+ cell.style.flex = String(column.weight ?? 1);
834
+ setData(cell, 'align', column.alignment ?? 'start');
835
+ const primary = createElement(context.document, 'span', 'ok-native-list-data-primary');
836
+ primary.style.color = toneColor(column.tone, 'primary');
837
+ if (column.secondaryLeadingText) primary.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', column.secondaryLeadingText));
838
+ primary.appendChild(createElement(context.document, 'span', undefined, column.text));
839
+ if (column.key === 'asset' && row.badges?.length) {
840
+ row.badges.forEach(badge => primary.appendChild(createBadge(context, badge)));
841
+ }
842
+ cell.appendChild(primary);
843
+ if (column.secondaryText) {
844
+ const secondary = createElement(context.document, 'span', 'ok-native-list-secondary', column.secondaryText);
845
+ secondary.style.color = toneColor(column.secondaryTone, 'secondary');
846
+ cell.appendChild(secondary);
847
+ }
848
+ body.appendChild(cell);
849
+ });
850
+ return body;
851
+ }
852
+ function createIdentityActivityOrMessageRow(context, row) {
853
+ const presentation = row.type === 'identity' ? row.presentation : undefined;
854
+ const body = createElement(context.document, 'div', ['ok-native-list-row', 'ok-native-list-standard', row.type === 'identity' && !presentation ? 'ok-native-list-identity-row' : '', presentation === 'networkSelector' ? 'ok-native-list-network-row' : '', presentation === 'walletSidebar' ? 'ok-native-list-wallet-row' : '', presentation === 'accountSelector' ? 'ok-native-list-account-row' : ''].filter(Boolean).join(' '));
855
+ if (row.type === 'identity' && row.leadingAction) {
856
+ const action = createIconAction(context, row.leadingAction.name, row.leadingAction.actionKey, row.leadingAction.disabled, row.leadingAction.tintColor);
857
+ markActionAnchorSource(action, 'leadingAction');
858
+ body.appendChild(action);
859
+ }
860
+ const visual = createVisual(context, visualFromRow(row));
861
+ if (visual) body.appendChild(visual);
862
+ if (row.type === 'activity' && row.secondaryLeading) {
863
+ const secondVisual = createVisual(context, row.secondaryLeading);
864
+ if (secondVisual) body.appendChild(secondVisual);
865
+ }
866
+ if (row.type === 'message' && row.unread) body.appendChild(createElement(context.document, 'span', 'ok-native-list-unread'));
867
+ const title = row.title;
868
+ const subtitle = row.type === 'identity' ? row.subtitle : row.type === 'activity' ? row.description : row.body;
869
+ const column = createTextColumn(context, title, subtitle, row.type === 'identity' ? row.tertiary : undefined, row.type === 'identity' ? row.tertiaryTone : undefined, row.type === 'identity' ? row.badges : undefined);
870
+ if (row.type === 'activity' && row.status) column.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', row.status));
871
+ if (row.type === 'activity' && row.footerActions?.length) {
872
+ const actions = createElement(context.document, 'span', 'ok-native-list-actions');
873
+ row.footerActions.forEach((action, slot) => {
874
+ const button = createElement(context.document, 'button', 'ok-native-list-action-button', action.label);
875
+ button.setAttribute('type', 'button');
876
+ button.toggleAttribute('disabled', Boolean(action.disabled));
877
+ setData(button, 'tone', action.tone);
878
+ setData(button, 'nativeListAction', action.key);
879
+ markActionAnchorSource(button, 'footerAction', slot);
880
+ actions.appendChild(button);
881
+ });
882
+ column.appendChild(actions);
883
+ }
884
+ body.appendChild(column);
885
+ if (row.type === 'activity') {
886
+ const amounts = createElement(context.document, 'span', 'ok-native-list-amounts');
887
+ if (row.primaryAmount) amounts.appendChild(createElement(context.document, 'span', 'ok-native-list-value', row.primaryAmount));
888
+ if (row.secondaryAmount) amounts.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', row.secondaryAmount));
889
+ body.appendChild(amounts);
890
+ } else if (row.type === 'message') {
891
+ body.appendChild(createElement(context.document, 'span', 'ok-native-list-time', row.time));
892
+ if (row.thumbnail) {
893
+ const thumbnail = createImage(context, row.thumbnail, 'ok-native-list-thumbnail');
894
+ if (thumbnail) body.appendChild(thumbnail);
895
+ }
896
+ } else {
897
+ appendAccessories(body, context, row.key, row.trailing);
898
+ }
899
+ return body;
900
+ }
901
+ function createWalletGroupRow(context, row) {
902
+ const body = createElement(context.document, 'div', 'ok-native-list-wallet-group');
903
+ [row.parent, ...row.children].forEach((member, memberIndex) => {
904
+ const memberElement = createElement(context.document, 'div', 'ok-native-list-wallet-member');
905
+ setData(memberElement, 'nativeListGroupMemberKey', member.key);
906
+ setData(memberElement, 'nativeListGroupParent', memberIndex === 0);
907
+ setData(memberElement, 'nativeListSelected', member.selected);
908
+ memberElement.appendChild(createIdentityActivityOrMessageRow(context, member));
909
+ body.appendChild(memberElement);
910
+ });
911
+ return body;
912
+ }
913
+ function createRowBody(context, row) {
914
+ switch (row.type) {
915
+ case 'walletGroup':
916
+ return createWalletGroupRow(context, row);
917
+ case 'sectionHeader':
918
+ return createSectionHeader(context, row);
919
+ case 'action':
920
+ return createActionRow(context, row);
921
+ case 'system':
922
+ return createSystemRow(context, row);
923
+ case 'rail':
924
+ return createRailRow(context, row);
925
+ case 'mediaTile':
926
+ return createMediaRow(context, row);
927
+ case 'metricCard':
928
+ return createMetricRow(context, row);
929
+ case 'dataRow':
930
+ return createDataRow(context, row);
931
+ case 'identity':
932
+ case 'activity':
933
+ case 'message':
934
+ return createIdentityActivityOrMessageRow(context, row);
935
+ }
936
+ }
937
+ export class NativeListWebEngine {
938
+ rows = [];
939
+ selectedKeys = new Set();
940
+ layout = {
941
+ items: [],
942
+ contentWidth: 0,
943
+ contentHeight: 0,
944
+ horizontal: false
945
+ };
946
+ mounted = new Map();
947
+ pool = [];
948
+ suppressClickUntil = 0;
949
+ pullDistance = 0;
950
+ actionAnchorInstanceId = String(++webNativeListInstanceCounter);
951
+ actionAnchorCounter = 0;
952
+ bindingEpochCounter = 0;
953
+ lastViewportWidth = -1;
954
+ lastViewportHeight = -1;
955
+ destroyed = false;
956
+ constructor(host, snapshot, callbacks, virtualizationEnabled = true) {
957
+ this.document = host.ownerDocument;
958
+ this.snapshot = validateSnapshot(snapshot);
959
+ this.callbacks = callbacks;
960
+ this.virtualizationEnabled = virtualizationEnabled;
961
+ this.previousHostPosition = host.style.position;
962
+ if (!host.style.position) host.style.position = 'relative';
963
+ this.root = createElement(this.document, 'div', 'ok-native-list-root');
964
+ const style = this.document.createElement('style');
965
+ style.textContent = WEB_LIST_CSS;
966
+ this.viewportFrame = createElement(this.document, 'div', 'ok-native-list-viewport-frame');
967
+ this.viewport = createElement(this.document, 'div', 'ok-native-list-viewport');
968
+ this.viewport.setAttribute('role', 'list');
969
+ this.content = createElement(this.document, 'div', 'ok-native-list-content');
970
+ this.footer = createElement(this.document, 'div', 'ok-native-list-footer');
971
+ this.sticky = createElement(this.document, 'div', 'ok-native-list-item ok-native-list-sticky');
972
+ this.sticky.setAttribute('aria-hidden', 'true');
973
+ this.sticky.hidden = true;
974
+ this.indexRail = createElement(this.document, 'div', 'ok-native-list-index-rail');
975
+ this.indexRail.setAttribute('role', 'navigation');
976
+ this.indexRail.setAttribute('aria-label', 'Section index');
977
+ this.indexRail.hidden = true;
978
+ this.indexPreview = createElement(this.document, 'div', 'ok-native-list-index-preview');
979
+ this.indexPreview.setAttribute('aria-live', 'polite');
980
+ this.refreshIndicator = createElement(this.document, 'div', 'ok-native-list-refresh', 'Refreshing');
981
+ this.refreshIndicator.setAttribute('role', 'status');
982
+ this.reorderPreview = createElement(this.document, 'div', 'ok-native-list-reorder-preview');
983
+ this.reorderPreview.hidden = true;
984
+ this.reorderPreview.setAttribute('aria-hidden', 'true');
985
+ this.document.body.appendChild(this.reorderPreview);
986
+ this.viewport.append(this.content);
987
+ this.viewportFrame.append(this.viewport, this.sticky, this.indexRail, this.indexPreview, this.refreshIndicator);
988
+ this.root.append(style, this.viewportFrame, this.footer);
989
+ host.appendChild(this.root);
990
+ this.viewport.addEventListener('scroll', this.handleScroll, {
991
+ passive: true
992
+ });
993
+ this.root.addEventListener('click', this.handleClick);
994
+ this.root.addEventListener('keydown', this.handleKeyDown);
995
+ this.viewport.addEventListener('pointerdown', this.handleReorderPointerDown, {
996
+ passive: false
997
+ });
998
+ this.viewport.addEventListener('pointermove', this.handleReorderPointerMove, {
999
+ passive: false
1000
+ });
1001
+ this.viewport.addEventListener('pointerup', this.handleReorderPointerEnd);
1002
+ this.viewport.addEventListener('pointercancel', this.handleReorderPointerCancel);
1003
+ this.viewport.addEventListener('touchmove', this.handleReorderTouchMove, {
1004
+ passive: false
1005
+ });
1006
+ this.viewport.addEventListener('touchend', this.handleReorderTouchEnd, {
1007
+ passive: false
1008
+ });
1009
+ this.viewport.addEventListener('touchcancel', this.handleReorderTouchCancel);
1010
+ this.indexRail.addEventListener('pointerdown', this.handleIndexPointer);
1011
+ this.indexRail.addEventListener('pointermove', this.handleIndexPointer);
1012
+ this.indexRail.addEventListener('click', this.handleIndexClick);
1013
+ this.viewport.addEventListener('pointerdown', this.handlePullStart, {
1014
+ passive: true
1015
+ });
1016
+ this.viewport.addEventListener('pointermove', this.handlePullMove, {
1017
+ passive: false
1018
+ });
1019
+ this.viewport.addEventListener('pointerup', this.handlePullEnd);
1020
+ this.viewport.addEventListener('pointercancel', this.handlePullEnd);
1021
+ const ResizeObserverConstructor = this.document.defaultView?.ResizeObserver;
1022
+ if (ResizeObserverConstructor) {
1023
+ this.resizeObserver = new ResizeObserverConstructor(() => this.recomputeLayout());
1024
+ this.resizeObserver.observe(this.viewport);
1025
+ } else {
1026
+ this.document.defaultView?.addEventListener('resize', this.handleWindowResize);
1027
+ }
1028
+ this.applySnapshot(this.snapshot);
1029
+ }
1030
+ updateCallbacks(callbacks) {
1031
+ this.callbacks = callbacks;
1032
+ }
1033
+ setVirtualizationEnabled(enabled) {
1034
+ if (this.virtualizationEnabled === enabled) return;
1035
+ this.virtualizationEnabled = enabled;
1036
+ this.renderWindow();
1037
+ }
1038
+ applySnapshot(snapshot) {
1039
+ const validated = validateSnapshot(snapshot);
1040
+ this.invalidateActionAnchor('snapshot');
1041
+ this.setSnapshot(validated);
1042
+ }
1043
+ applyPatches(patches) {
1044
+ if (patches.length === 0) return;
1045
+ const next = applyRowPatches(this.snapshot, patches);
1046
+ this.invalidateActionAnchor('snapshot');
1047
+ const selection = new Set(this.selectedKeys);
1048
+ patches.forEach(patch => {
1049
+ const selected = 'selected' in patch.changes ? patch.changes.selected : undefined;
1050
+ if (selected === true) selection.add(patch.key);else if (selected === false) selection.delete(patch.key);
1051
+ });
1052
+ this.setSnapshot(next, selection);
1053
+ }
1054
+ reconcileSelection(keys) {
1055
+ const selectable = new Set(this.snapshot.rows.filter(isSelectableRow).map(row => row.key));
1056
+ if (keys.some(key => !selectable.has(key))) return;
1057
+ if (this.snapshot.selection?.mode === 'single' && keys.length > 1) return;
1058
+ this.selectedKeys = new Set(keys);
1059
+ this.updateVisibleSelection();
1060
+ this.renderFooter();
1061
+ }
1062
+ scrollToIndex(index, scroll) {
1063
+ if (index < 0 || index >= this.rows.length) {
1064
+ this.emitScrollFailure(index, 'index-out-of-range');
1065
+ return;
1066
+ }
1067
+ if (!this.canScroll()) {
1068
+ this.pendingScroll = {
1069
+ kind: 'index',
1070
+ index,
1071
+ scroll
1072
+ };
1073
+ return;
1074
+ }
1075
+ const item = this.layout.items[index];
1076
+ if (!item) {
1077
+ this.pendingScroll = {
1078
+ kind: 'index',
1079
+ index,
1080
+ scroll
1081
+ };
1082
+ return;
1083
+ }
1084
+ const viewportLength = this.viewportLength();
1085
+ const contentLength = this.contentLength();
1086
+ const currentOffset = this.currentOffset();
1087
+ const offset = calculateAlignedScrollOffset({
1088
+ itemOffset: itemStart(item, this.layout.horizontal),
1089
+ itemLength: this.layout.horizontal ? item.width : item.height,
1090
+ viewportLength,
1091
+ contentLength,
1092
+ currentOffset,
1093
+ alignment: scroll.alignment,
1094
+ viewPosition: scroll.viewPosition,
1095
+ viewOffset: scroll.viewOffset
1096
+ });
1097
+ this.scrollToAbsoluteOffset(offset, scroll.animated);
1098
+ }
1099
+ scrollToKey(key, scroll) {
1100
+ const index = this.rows.findIndex(row => row.key === key);
1101
+ if (index >= 0) this.scrollToIndex(index, scroll);
1102
+ }
1103
+ scrollToOffset(offset, animated) {
1104
+ validateOffset(offset);
1105
+ if (!this.canScroll()) {
1106
+ this.pendingScroll = {
1107
+ kind: 'offset',
1108
+ offset,
1109
+ animated
1110
+ };
1111
+ return;
1112
+ }
1113
+ this.scrollToAbsoluteOffset(offset, animated);
1114
+ }
1115
+ scrollToEnd(animated) {
1116
+ if (!this.canScroll()) {
1117
+ this.pendingScroll = {
1118
+ kind: 'end',
1119
+ animated
1120
+ };
1121
+ return;
1122
+ }
1123
+ this.scrollToAbsoluteOffset(Math.max(0, this.contentLength() - this.viewportLength()), animated);
1124
+ }
1125
+ scrollToLocation(params, scroll) {
1126
+ const index = resolveLocationIndex(this.snapshot.rows, params);
1127
+ if (index === undefined) {
1128
+ const sectionCount = this.snapshot.rows.filter(row => row.type === 'sectionHeader' && row.variant !== 'summary').length;
1129
+ this.emitScrollFailure(params.itemIndex, params.sectionIndex >= sectionCount ? 'section-out-of-range' : 'item-out-of-range');
1130
+ return;
1131
+ }
1132
+ this.scrollToIndex(index, scroll);
1133
+ }
1134
+ setRefreshing(refreshing) {
1135
+ this.snapshot = {
1136
+ ...this.snapshot,
1137
+ capabilities: {
1138
+ ...this.snapshot.capabilities,
1139
+ refreshing
1140
+ }
1141
+ };
1142
+ this.updateRefreshIndicator();
1143
+ }
1144
+ setActionAnchorState(state) {
1145
+ const anchor = this.actionAnchor;
1146
+ if (!anchor || anchor.token !== state.token) return;
1147
+ if (!state.open) {
1148
+ if (state.restoreFocus && this.isActionAnchorValid(anchor)) {
1149
+ anchor.actionElement.focus({
1150
+ preventScroll: true
1151
+ });
1152
+ }
1153
+ anchor.actionElement.removeAttribute('aria-expanded');
1154
+ this.actionAnchor = undefined;
1155
+ return;
1156
+ }
1157
+ if (anchor.invalidatedReason || !this.isActionAnchorValid(anchor)) {
1158
+ this.emitActionAnchorInvalidated(anchor, anchor.invalidatedReason ?? 'rebind');
1159
+ return;
1160
+ }
1161
+ anchor.open = true;
1162
+ anchor.actionElement.setAttribute('aria-expanded', 'true');
1163
+ }
1164
+ destroy() {
1165
+ if (this.destroyed) return;
1166
+ this.invalidateActionAnchor('destroy');
1167
+ this.destroyed = true;
1168
+ if (this.frameHandle !== undefined) this.cancelFrame(this.frameHandle);
1169
+ if (this.previewTimer !== undefined) this.document.defaultView?.clearTimeout(this.previewTimer);
1170
+ if (this.reorderMovementTimer !== undefined) this.document.defaultView?.clearTimeout(this.reorderMovementTimer);
1171
+ this.resizeObserver?.disconnect();
1172
+ this.document.defaultView?.removeEventListener('resize', this.handleWindowResize);
1173
+ this.viewport.removeEventListener('scroll', this.handleScroll);
1174
+ this.root.removeEventListener('click', this.handleClick);
1175
+ this.root.removeEventListener('keydown', this.handleKeyDown);
1176
+ this.cancelPointerReorder(true);
1177
+ this.viewport.removeEventListener('pointerdown', this.handleReorderPointerDown);
1178
+ this.viewport.removeEventListener('pointermove', this.handleReorderPointerMove);
1179
+ this.viewport.removeEventListener('pointerup', this.handleReorderPointerEnd);
1180
+ this.viewport.removeEventListener('pointercancel', this.handleReorderPointerCancel);
1181
+ this.viewport.removeEventListener('touchmove', this.handleReorderTouchMove);
1182
+ this.viewport.removeEventListener('touchend', this.handleReorderTouchEnd);
1183
+ this.viewport.removeEventListener('touchcancel', this.handleReorderTouchCancel);
1184
+ this.indexRail.removeEventListener('pointerdown', this.handleIndexPointer);
1185
+ this.indexRail.removeEventListener('pointermove', this.handleIndexPointer);
1186
+ this.indexRail.removeEventListener('click', this.handleIndexClick);
1187
+ this.viewport.removeEventListener('pointerdown', this.handlePullStart);
1188
+ this.viewport.removeEventListener('pointermove', this.handlePullMove);
1189
+ this.viewport.removeEventListener('pointerup', this.handlePullEnd);
1190
+ this.viewport.removeEventListener('pointercancel', this.handlePullEnd);
1191
+ const host = this.root.parentElement;
1192
+ this.root.remove();
1193
+ this.hideReorderPreview();
1194
+ this.reorderPreview.remove();
1195
+ if (host) host.style.position = this.previousHostPosition;
1196
+ this.mounted.clear();
1197
+ this.pool.length = 0;
1198
+ }
1199
+ setSnapshot(snapshot, selectedKeys) {
1200
+ this.snapshot = snapshot;
1201
+ this.rows = effectiveRows(snapshot);
1202
+ this.selectedKeys = selectedKeys ?? selectionStateFromSnapshot(snapshot).selectedKeys;
1203
+ if (this.reachedGeneration !== snapshot.generation) this.reachedGeneration = undefined;
1204
+ this.renderSectionIndex();
1205
+ this.applyTheme();
1206
+ this.recomputeLayout();
1207
+ this.renderFooter();
1208
+ this.updateRefreshIndicator();
1209
+ }
1210
+ applyTheme() {
1211
+ const theme = resolvedTheme(this.snapshot);
1212
+ const values = {
1213
+ '--nl-bg': theme.background,
1214
+ '--nl-row': theme.rowBackground,
1215
+ '--nl-selected': theme.rowSelectedBackground,
1216
+ '--nl-pressed': theme.rowPressedBackground,
1217
+ '--nl-subdued': theme.subduedBackground,
1218
+ '--nl-strong': theme.strongBackground,
1219
+ '--nl-primary': theme.primaryText,
1220
+ '--nl-secondary': theme.secondaryText,
1221
+ '--nl-disabled': theme.disabledText,
1222
+ '--nl-icon': theme.icon,
1223
+ '--nl-icon-subdued': theme.iconSubdued,
1224
+ '--nl-separator': theme.separator,
1225
+ '--nl-accent': theme.accent,
1226
+ '--nl-positive': theme.positive,
1227
+ '--nl-negative': theme.negative,
1228
+ '--nl-critical': theme.criticalBackground,
1229
+ '--nl-inverse': theme.inverseBackground,
1230
+ '--nl-inverse-text': theme.inverseText,
1231
+ '--nl-info': theme.info
1232
+ };
1233
+ Object.entries(values).forEach(([name, value]) => {
1234
+ if (!value) return;
1235
+ this.root.style.setProperty(name, value);
1236
+ this.reorderPreview.style.setProperty(name, value);
1237
+ });
1238
+ }
1239
+ recomputeLayout = () => {
1240
+ if (this.destroyed) return;
1241
+ const viewportWidth = this.viewport.clientWidth;
1242
+ const viewportHeight = this.viewport.clientHeight;
1243
+ if (this.lastViewportWidth >= 0 && (viewportWidth !== this.lastViewportWidth || viewportHeight !== this.lastViewportHeight)) {
1244
+ this.invalidateActionAnchor('layout');
1245
+ }
1246
+ this.lastViewportWidth = viewportWidth;
1247
+ this.lastViewportHeight = viewportHeight;
1248
+ const previousHorizontal = this.layout.horizontal;
1249
+ this.layout = computeWebListLayout(this.snapshot, viewportWidth, viewportHeight, this.reorderCompactKey);
1250
+ this.content.style.width = String(this.layout.contentWidth) + 'px';
1251
+ this.content.style.height = String(this.layout.contentHeight) + 'px';
1252
+ if (previousHorizontal !== this.layout.horizontal) {
1253
+ this.viewport.scrollLeft = 0;
1254
+ this.viewport.scrollTop = 0;
1255
+ }
1256
+ this.renderWindow();
1257
+ this.performPendingScroll();
1258
+ };
1259
+ renderWindow() {
1260
+ const viewportLength = this.viewportLength();
1261
+ const visible = webLayoutItemsForMount(this.layout, this.currentOffset(), viewportLength, this.virtualizationEnabled, viewportLength * OVERSCAN_VIEWPORTS);
1262
+ const desired = new Set(visible.map(item => item.index));
1263
+ this.mounted.forEach((element, index) => {
1264
+ if (!desired.has(index)) {
1265
+ this.invalidateActionAnchorForElement(element);
1266
+ this.mounted.delete(index);
1267
+ element.remove();
1268
+ this.pool.push(element);
1269
+ }
1270
+ });
1271
+ visible.forEach(layoutItem => {
1272
+ const row = this.rows[layoutItem.index];
1273
+ if (!row) return;
1274
+ let element = this.mounted.get(layoutItem.index);
1275
+ if (!element) {
1276
+ element = this.pool.pop() ?? createElement(this.document, 'div', 'ok-native-list-item');
1277
+ setData(element, 'nativeListAnimateReorder', false);
1278
+ this.mounted.set(layoutItem.index, element);
1279
+ this.content.appendChild(element);
1280
+ }
1281
+ this.positionElement(element, layoutItem);
1282
+ const signature = webRowRenderSignature(row);
1283
+ if (element.dataset.nativeListRowKey !== row.key || element.dataset.renderSignature !== signature) {
1284
+ this.renderElement(element, layoutItem.index, row);
1285
+ element.dataset.renderSignature = signature;
1286
+ }
1287
+ });
1288
+ this.updateVisibleSelection();
1289
+ this.updateVisibleState();
1290
+ }
1291
+ positionElement(element, item) {
1292
+ element.style.transform = 'translate3d(' + String(item.x) + 'px,' + String(item.y) + 'px,0)';
1293
+ element.style.width = String(item.width) + 'px';
1294
+ element.style.height = String(item.height) + 'px';
1295
+ }
1296
+ renderElement(element, index, row, overlay = false) {
1297
+ this.invalidateActionAnchorForElement(element);
1298
+ const bindingEpoch = String(++this.bindingEpochCounter);
1299
+ element.className = overlay ? 'ok-native-list-item ok-native-list-sticky' : 'ok-native-list-item';
1300
+ setData(element, 'nativeListRowKey', row.key);
1301
+ setData(element, 'nativeListBindingEpoch', bindingEpoch);
1302
+ setData(element, 'nativeListRowIndex', index);
1303
+ setData(element, 'nativeListDisabled', Boolean(row.disabled));
1304
+ setData(element, 'nativeListReorderable', this.isReorderable(row));
1305
+ setData(element, 'nativeListDragging', this.pointerReorder?.active && this.pointerReorder.sourceKey === row.key);
1306
+ setData(element, 'separator', row.separator);
1307
+ setData(element, 'groupPosition', row.groupPosition);
1308
+ setData(element, 'tableAlternate', this.snapshot.layout.kind === 'table' && row.type === 'dataRow' && (row.index ?? index) % 2 === 0);
1309
+ element.setAttribute('role', 'listitem');
1310
+ element.setAttribute('aria-label', row.accessibilityLabel ?? this.rowLabel(row));
1311
+ if (!row.disabled && !(row.type === 'system' && row.variant === 'spacer')) {
1312
+ element.tabIndex = 0;
1313
+ } else {
1314
+ element.removeAttribute('tabindex');
1315
+ }
1316
+ element.draggable = false;
1317
+ const context = {
1318
+ document: this.document,
1319
+ snapshot: this.snapshot,
1320
+ selectedKeys: this.selectedKeys,
1321
+ itemIndex: index
1322
+ };
1323
+ element.replaceChildren(createRowBody(context, row));
1324
+ }
1325
+ renderFooter() {
1326
+ const row = this.snapshot.fixedFooter;
1327
+ this.invalidateActionAnchorForElement(this.footer);
1328
+ this.footer.replaceChildren();
1329
+ if (!row) return;
1330
+ const element = createElement(this.document, 'div', 'ok-native-list-item');
1331
+ this.renderElement(element, -1, row);
1332
+ element.style.position = 'relative';
1333
+ element.style.transform = 'none';
1334
+ element.style.width = '100%';
1335
+ element.style.height = String(estimateWebRowHeight(row, this.snapshot, this.viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH)) + 'px';
1336
+ this.footer.appendChild(element);
1337
+ }
1338
+ renderSectionIndex() {
1339
+ this.indexRail.replaceChildren();
1340
+ if (!sectionIndexEnabled(this.snapshot)) {
1341
+ this.indexRail.hidden = true;
1342
+ return;
1343
+ }
1344
+ const fragment = this.document.createDocumentFragment();
1345
+ this.snapshot.rows.forEach((row, index) => {
1346
+ if (row.type !== 'sectionHeader' || !row.indexTitle) return;
1347
+ const button = createElement(this.document, 'button', 'ok-native-list-index-button', row.indexTitle);
1348
+ button.setAttribute('type', 'button');
1349
+ button.setAttribute('aria-label', 'Jump to ' + row.indexTitle);
1350
+ setData(button, 'sectionPosition', index);
1351
+ setData(button, 'sectionKey', row.key);
1352
+ fragment.appendChild(button);
1353
+ });
1354
+ this.indexRail.appendChild(fragment);
1355
+ this.indexRail.hidden = this.indexRail.childElementCount === 0;
1356
+ }
1357
+ updateVisibleSelection() {
1358
+ const update = (element, row) => {
1359
+ if (!row) return;
1360
+ const selected = this.selectedKeys.has(row.key);
1361
+ setData(element, 'nativeListSelected', selected);
1362
+ element.setAttribute('aria-selected', String(selected));
1363
+ element.querySelectorAll('.ok-native-list-checkbox').forEach(checkbox => {
1364
+ const scope = checkbox.dataset.selectionScope;
1365
+ const key = checkbox.dataset.selectionKey;
1366
+ const target = scope === 'section' && key ? {
1367
+ scope: 'section',
1368
+ sectionKey: key
1369
+ } : scope === 'list' ? {
1370
+ scope: 'list'
1371
+ } : {
1372
+ scope: 'row'
1373
+ };
1374
+ const state = checkboxState(target, checkbox.dataset.checkboxFallback ?? 'unchecked', row.key, this.snapshot, this.selectedKeys);
1375
+ setData(checkbox, 'state', state);
1376
+ checkbox.setAttribute('aria-checked', state === 'indeterminate' ? 'mixed' : String(state === 'checked'));
1377
+ });
1378
+ };
1379
+ this.mounted.forEach((element, index) => update(element, this.rows[index]));
1380
+ const stickyIndex = Number(this.sticky.dataset.nativeListRowIndex);
1381
+ if (!Number.isNaN(stickyIndex)) update(this.sticky, this.rows[stickyIndex]);
1382
+ }
1383
+ updateVisibleState() {
1384
+ const visible = visibleWebLayoutItems(this.layout, this.currentOffset(), this.viewportLength());
1385
+ const first = visible[0];
1386
+ const last = visible.at(-1);
1387
+ const signature = String(first?.index ?? -1) + ':' + String(last?.index ?? -1) + ':' + (first?.key ?? '') + ':' + (last?.key ?? '');
1388
+ if (signature !== this.lastVisibleSignature) {
1389
+ this.lastVisibleSignature = signature;
1390
+ this.callbacks.onVisibleRangeChanged?.({
1391
+ firstKey: first?.key,
1392
+ lastKey: last?.key,
1393
+ firstIndex: first?.index ?? -1,
1394
+ lastIndex: last?.index ?? -1
1395
+ });
1396
+ }
1397
+ this.checkEndReached(last?.index ?? -1);
1398
+ this.updateStickyHeader(first?.index ?? -1);
1399
+ this.updateSectionIndex(first?.index ?? -1);
1400
+ }
1401
+ updateStickyHeader(firstVisibleIndex) {
1402
+ if (!this.snapshot.layout.stickyHeaders || this.layout.horizontal || firstVisibleIndex < 0) {
1403
+ this.sticky.hidden = true;
1404
+ this.stickyKey = undefined;
1405
+ return;
1406
+ }
1407
+ let index = -1;
1408
+ for (let cursor = firstVisibleIndex; cursor >= 0; cursor -= 1) {
1409
+ const row = this.rows[cursor];
1410
+ if (row?.type === 'sectionHeader' && row.variant !== 'summary') {
1411
+ index = cursor;
1412
+ break;
1413
+ }
1414
+ }
1415
+ const row = this.rows[index];
1416
+ const item = this.layout.items[index];
1417
+ if (!row || row.type !== 'sectionHeader' || !item) {
1418
+ this.sticky.hidden = true;
1419
+ this.stickyKey = undefined;
1420
+ return;
1421
+ }
1422
+ const signature = webRowRenderSignature(row);
1423
+ if (this.stickyKey !== row.key || this.sticky.dataset.renderSignature !== signature) {
1424
+ this.renderElement(this.sticky, index, row, true);
1425
+ this.sticky.dataset.renderSignature = signature;
1426
+ this.stickyKey = row.key;
1427
+ }
1428
+ this.sticky.hidden = false;
1429
+ this.sticky.style.left = String(item.x) + 'px';
1430
+ this.sticky.style.right = 'auto';
1431
+ this.sticky.style.width = String(item.width) + 'px';
1432
+ this.sticky.style.height = String(item.height) + 'px';
1433
+ let nextIndex = -1;
1434
+ for (let cursor = index + 1; cursor < this.rows.length; cursor += 1) {
1435
+ const candidate = this.rows[cursor];
1436
+ if (candidate?.type === 'sectionHeader' && candidate.variant !== 'summary') {
1437
+ nextIndex = cursor;
1438
+ break;
1439
+ }
1440
+ }
1441
+ const next = this.layout.items[nextIndex];
1442
+ const translate = next ? Math.min(0, next.y - this.viewport.scrollTop - item.height) : 0;
1443
+ this.sticky.style.transform = 'translate3d(0,' + String(translate) + 'px,0)';
1444
+ this.updateVisibleSelection();
1445
+ }
1446
+ updateSectionIndex(firstVisibleIndex) {
1447
+ let activeKey;
1448
+ this.snapshot.rows.forEach((row, index) => {
1449
+ if (index <= firstVisibleIndex && row.type === 'sectionHeader' && row.indexTitle) activeKey = row.key;
1450
+ });
1451
+ this.indexRail.querySelectorAll('[data-section-key]').forEach(button => setData(button, 'active', button.dataset.sectionKey === activeKey));
1452
+ }
1453
+ checkEndReached(lastVisibleIndex) {
1454
+ if (!this.snapshot.capabilities?.loadMore || this.reachedGeneration === this.snapshot.generation || this.rows.length === 0) return;
1455
+ const threshold = Math.max(1, Math.ceil(this.rows.length * (this.snapshot.capabilities.endReachedThreshold ?? 0.2)));
1456
+ if (lastVisibleIndex < this.rows.length - threshold) return;
1457
+ this.reachedGeneration = this.snapshot.generation;
1458
+ this.callbacks.onEndReached?.({
1459
+ generation: this.snapshot.generation,
1460
+ lastKey: this.rows.at(-1)?.key
1461
+ });
1462
+ }
1463
+ emitScrollFailure(index, reason) {
1464
+ const average = this.layout.items.length === 0 ? 0 : this.layout.items.reduce((sum, item) => sum + (this.layout.horizontal ? item.width : item.height), 0) / this.layout.items.length;
1465
+ this.callbacks.onScrollToIndexFailed?.(scrollFailure(this.rows, index, reason, average));
1466
+ }
1467
+ canScroll() {
1468
+ const viewportLength = this.layout.horizontal ? this.viewport.clientWidth : this.viewport.clientHeight;
1469
+ return viewportLength > 0 && this.layout.items.length > 0;
1470
+ }
1471
+ viewportLength() {
1472
+ return this.layout.horizontal ? this.viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH : this.viewport.clientHeight || DEFAULT_VIEWPORT_HEIGHT;
1473
+ }
1474
+ contentLength() {
1475
+ return this.layout.horizontal ? this.layout.contentWidth : this.layout.contentHeight;
1476
+ }
1477
+ currentOffset() {
1478
+ return this.layout.horizontal ? this.viewport.scrollLeft : this.viewport.scrollTop;
1479
+ }
1480
+ scrollToAbsoluteOffset(offset, animated) {
1481
+ const resolved = Math.min(Math.max(0, offset), Math.max(0, this.contentLength() - this.viewportLength()));
1482
+ this.viewport.scrollTo(this.layout.horizontal ? {
1483
+ left: resolved,
1484
+ top: 0,
1485
+ behavior: animated ? 'smooth' : 'auto'
1486
+ } : {
1487
+ left: 0,
1488
+ top: resolved,
1489
+ behavior: animated ? 'smooth' : 'auto'
1490
+ });
1491
+ this.scheduleFrame();
1492
+ }
1493
+ performPendingScroll() {
1494
+ const pending = this.pendingScroll;
1495
+ if (!pending || !this.canScroll()) return;
1496
+ this.pendingScroll = undefined;
1497
+ if (pending.kind === 'index') this.scrollToIndex(pending.index, pending.scroll);else if (pending.kind === 'offset') this.scrollToOffset(pending.offset, pending.animated);else this.scrollToEnd(pending.animated);
1498
+ }
1499
+ rowAtElement(element) {
1500
+ if (!element) return undefined;
1501
+ const key = element.dataset.nativeListRowKey;
1502
+ return this.rows.find(row => row.key === key) ?? (this.snapshot.fixedFooter?.key === key ? this.snapshot.fixedFooter : undefined);
1503
+ }
1504
+ rowLabel(row) {
1505
+ if (row.type === 'walletGroup') return row.parent.title;
1506
+ if ('title' in row) return row.title;
1507
+ if ('message' in row && row.message) return row.message;
1508
+ return row.type;
1509
+ }
1510
+ isReorderable(row) {
1511
+ return isWebRowReorderable(this.snapshot, row);
1512
+ }
1513
+ activateSelection(target, sourceRow) {
1514
+ const action = target.scope === 'row' ? {
1515
+ scope: 'row',
1516
+ key: sourceRow.key
1517
+ } : target;
1518
+ const result = reduceSelection({
1519
+ mode: this.snapshot.selection?.mode ?? 'none',
1520
+ selectedKeys: this.selectedKeys
1521
+ }, action, this.snapshot.rows);
1522
+ if (result.delta.addedKeys.length === 0 && result.delta.removedKeys.length === 0) return;
1523
+ this.selectedKeys = result.state.selectedKeys;
1524
+ this.updateVisibleSelection();
1525
+ this.renderFooter();
1526
+ this.callbacks.onSelectionDelta?.(result.delta);
1527
+ }
1528
+ emitRowAction(row, actionKey, actionElement, rowElement) {
1529
+ const anchor = actionElement && rowElement ? this.createActionAnchor(actionElement, rowElement) : undefined;
1530
+ this.callbacks.onRowAction?.({
1531
+ rowKey: row.key,
1532
+ actionKey,
1533
+ sectionKey: row.sectionKey,
1534
+ anchor
1535
+ });
1536
+ }
1537
+ createActionAnchor(actionElement, rowElement, sourceOverride) {
1538
+ const source = sourceOverride ?? actionElement.dataset.nativeListAnchorSource;
1539
+ const bindingEpoch = rowElement.dataset.nativeListBindingEpoch;
1540
+ if (!source || !bindingEpoch || !rowElement.contains(actionElement)) return undefined;
1541
+ this.invalidateActionAnchor('rebind');
1542
+ const rect = actionElement.getBoundingClientRect();
1543
+ const token = [this.actionAnchorInstanceId, this.snapshot.generation, ++this.actionAnchorCounter, bindingEpoch].join(':');
1544
+ const slotValue = actionElement.dataset.nativeListAnchorSlot;
1545
+ const direction = this.document.defaultView?.getComputedStyle(actionElement).direction === 'rtl' || actionElement.closest('[dir="rtl"]') ? 'rtl' : 'ltr';
1546
+ this.actionAnchor = {
1547
+ token,
1548
+ actionElement,
1549
+ rowElement,
1550
+ bindingEpoch,
1551
+ open: false
1552
+ };
1553
+ return webActionAnchorPayload(token, rect, source, this.snapshot.generation, direction, slotValue === undefined ? undefined : Number(slotValue));
1554
+ }
1555
+ isActionAnchorValid(anchor) {
1556
+ return anchor.actionElement.isConnected && anchor.rowElement.isConnected && anchor.rowElement.contains(anchor.actionElement) && anchor.rowElement.dataset.nativeListBindingEpoch === anchor.bindingEpoch;
1557
+ }
1558
+ invalidateActionAnchorForElement(element) {
1559
+ const anchor = this.actionAnchor;
1560
+ if (anchor && (element === anchor.rowElement || element.contains(anchor.actionElement))) {
1561
+ this.invalidateActionAnchor('rebind');
1562
+ }
1563
+ }
1564
+ invalidateActionAnchor(reason) {
1565
+ const anchor = this.actionAnchor;
1566
+ if (!anchor || anchor.invalidatedReason) return;
1567
+ anchor.invalidatedReason = reason;
1568
+ anchor.actionElement.removeAttribute('aria-expanded');
1569
+ if (anchor.open) this.emitActionAnchorInvalidated(anchor, reason);
1570
+ }
1571
+ emitActionAnchorInvalidated(anchor, reason) {
1572
+ if (this.actionAnchor !== anchor) return;
1573
+ this.actionAnchor = undefined;
1574
+ this.callbacks.onActionAnchorInvalidated?.({
1575
+ token: anchor.token,
1576
+ reason
1577
+ });
1578
+ }
1579
+ handleRowPress(row, rowElement, sourceElement = rowElement) {
1580
+ if (row.disabled) return;
1581
+ if (this.snapshot.selection?.rowPressToggles && this.snapshot.selection.mode !== 'none' && isSelectableRow(row)) {
1582
+ this.activateSelection({
1583
+ scope: 'row'
1584
+ }, row);
1585
+ return;
1586
+ }
1587
+ const actionKey = row.type === 'action' ? row.actionKey : row.type === 'system' && row.variant === 'retry' ? row.actionKey : 'press';
1588
+ if (rowElement && sourceElement) {
1589
+ const anchor = this.createActionAnchor(sourceElement, rowElement, 'row');
1590
+ this.callbacks.onRowAction?.({
1591
+ rowKey: row.key,
1592
+ actionKey,
1593
+ sectionKey: row.sectionKey,
1594
+ anchor
1595
+ });
1596
+ } else {
1597
+ this.emitRowAction(row, actionKey);
1598
+ }
1599
+ }
1600
+ handleClick = event => {
1601
+ if (Date.now() < this.suppressClickUntil) {
1602
+ event.preventDefault();
1603
+ event.stopPropagation();
1604
+ return;
1605
+ }
1606
+ const target = event.target;
1607
+ if (!(target instanceof Element)) return;
1608
+ const rowElement = target.closest('[data-native-list-row-key]');
1609
+ const row = this.rowAtElement(rowElement);
1610
+ if (!row || row.disabled) return;
1611
+ const memberElement = target.closest('[data-native-list-group-member-key]');
1612
+ const memberKey = memberElement?.dataset.nativeListGroupMemberKey;
1613
+ const sourceRow = row.type === 'walletGroup' && memberKey ? [row.parent, ...row.children].find(member => member.key === memberKey) ?? row : row;
1614
+ const action = target.closest('[data-native-list-action]');
1615
+ if (action) {
1616
+ const scope = action.dataset.selectionScope;
1617
+ if (scope === 'row') this.activateSelection({
1618
+ scope: 'row'
1619
+ }, row);else if (scope === 'section' && action.dataset.selectionKey) this.activateSelection({
1620
+ scope: 'section',
1621
+ sectionKey: action.dataset.selectionKey
1622
+ }, row);else if (scope === 'list') this.activateSelection({
1623
+ scope: 'list'
1624
+ }, row);else if (action.dataset.nativeListAction) this.emitRowAction(sourceRow, action.dataset.nativeListAction, action, rowElement ?? undefined);
1625
+ return;
1626
+ }
1627
+ this.handleRowPress(sourceRow, rowElement ?? undefined, memberElement ?? rowElement ?? undefined);
1628
+ };
1629
+ handleKeyDown = event => {
1630
+ if (event.key === 'Escape') {
1631
+ if (this.pointerReorder?.active) {
1632
+ event.preventDefault();
1633
+ this.cancelPointerReorder(true);
1634
+ return;
1635
+ }
1636
+ if (this.keyboardReorder) {
1637
+ event.preventDefault();
1638
+ this.cancelKeyboardReorder();
1639
+ return;
1640
+ }
1641
+ }
1642
+ if (this.keyboardReorder && (event.key === 'ArrowUp' || event.key === 'ArrowLeft')) {
1643
+ event.preventDefault();
1644
+ this.moveKeyboardReorder(-1);
1645
+ return;
1646
+ }
1647
+ if (this.keyboardReorder && (event.key === 'ArrowDown' || event.key === 'ArrowRight')) {
1648
+ event.preventDefault();
1649
+ this.moveKeyboardReorder(1);
1650
+ return;
1651
+ }
1652
+ if (event.key !== 'Enter' && event.key !== ' ') return;
1653
+ const target = event.target;
1654
+ if (!(target instanceof HTMLElement)) return;
1655
+ if (target.matches('button')) return;
1656
+ const row = this.rowAtElement(target.closest('[data-native-list-row-key]'));
1657
+ if (!row) return;
1658
+ event.preventDefault();
1659
+ if (event.key === ' ' && this.isReorderable(row)) {
1660
+ if (this.keyboardReorder) this.finishKeyboardReorder();else this.startKeyboardReorder(row);
1661
+ return;
1662
+ }
1663
+ this.handleRowPress(row, target.closest('[data-native-list-row-key]') ?? undefined);
1664
+ };
1665
+ startKeyboardReorder(row) {
1666
+ this.keyboardReorder = {
1667
+ sourceKey: row.key,
1668
+ originalRows: this.snapshot.rows
1669
+ };
1670
+ this.updateReorderVisualState();
1671
+ }
1672
+ moveKeyboardReorder(delta) {
1673
+ const state = this.keyboardReorder;
1674
+ if (!state) return;
1675
+ const fromIndex = this.snapshot.rows.findIndex(row => row.key === state.sourceKey);
1676
+ const toIndex = fromIndex + delta;
1677
+ const from = this.snapshot.rows[fromIndex];
1678
+ const to = this.snapshot.rows[toIndex];
1679
+ if (!from || !to || !this.isReorderable(to) || from.sectionKey !== to.sectionKey) return;
1680
+ const rows = moveWebReorderRow(this.snapshot.rows, fromIndex, toIndex);
1681
+ this.remapMountedRows(rows);
1682
+ this.setSnapshot({
1683
+ ...this.snapshot,
1684
+ rows
1685
+ }, this.selectedKeys);
1686
+ this.updateReorderVisualState();
1687
+ this.scrollToIndex(toIndex, {
1688
+ animated: false,
1689
+ alignment: 'nearest',
1690
+ viewPosition: 0,
1691
+ viewOffset: 0
1692
+ });
1693
+ [...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey)?.focus({
1694
+ preventScroll: true
1695
+ });
1696
+ }
1697
+ finishKeyboardReorder() {
1698
+ const state = this.keyboardReorder;
1699
+ if (!state) return;
1700
+ const reorderEvent = webReorderEventForRows(state.originalRows, this.snapshot.rows, state.sourceKey);
1701
+ this.keyboardReorder = undefined;
1702
+ this.updateReorderVisualState();
1703
+ if (reorderEvent) this.callbacks.onReorder?.(reorderEvent);
1704
+ }
1705
+ cancelKeyboardReorder() {
1706
+ const state = this.keyboardReorder;
1707
+ if (!state) return;
1708
+ this.keyboardReorder = undefined;
1709
+ this.remapMountedRows(state.originalRows);
1710
+ this.setSnapshot({
1711
+ ...this.snapshot,
1712
+ rows: cancelWebReorderRows(state.originalRows)
1713
+ }, this.selectedKeys);
1714
+ this.updateReorderVisualState();
1715
+ [...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey)?.focus({
1716
+ preventScroll: true
1717
+ });
1718
+ }
1719
+ handleScroll = () => {
1720
+ this.invalidateActionAnchor('scroll');
1721
+ this.scheduleFrame();
1722
+ };
1723
+ handleWindowResize = () => {
1724
+ const state = this.pointerReorder;
1725
+ if (state?.active) state.viewportRect = this.reorderViewportRect();
1726
+ this.recomputeLayout();
1727
+ };
1728
+ scheduleFrame() {
1729
+ if (this.frameHandle !== undefined || this.destroyed) return;
1730
+ this.frameHandle = this.requestFrame(() => {
1731
+ this.frameHandle = undefined;
1732
+ if (this.virtualizationEnabled) this.renderWindow();else this.updateVisibleState();
1733
+ });
1734
+ }
1735
+ requestFrame(callback) {
1736
+ const view = this.document.defaultView;
1737
+ return view?.requestAnimationFrame ? view.requestAnimationFrame(callback) : view?.setTimeout(() => callback(Date.now()), 16) ?? 0;
1738
+ }
1739
+ cancelFrame(handle) {
1740
+ const view = this.document.defaultView;
1741
+ if (view?.cancelAnimationFrame) view.cancelAnimationFrame(handle);else view?.clearTimeout(handle);
1742
+ }
1743
+ selectIndexPosition(position, title) {
1744
+ this.scrollToIndex(position, {
1745
+ animated: false,
1746
+ alignment: 'start',
1747
+ viewPosition: 0,
1748
+ viewOffset: 0
1749
+ });
1750
+ this.indexPreview.textContent = title;
1751
+ setData(this.indexPreview, 'visible', true);
1752
+ if (this.previewTimer !== undefined) this.document.defaultView?.clearTimeout(this.previewTimer);
1753
+ this.previewTimer = this.document.defaultView?.setTimeout(() => {
1754
+ setData(this.indexPreview, 'visible', false);
1755
+ }, 180);
1756
+ }
1757
+ indexButtonAtEvent(event) {
1758
+ const direct = event.target?.closest('[data-section-position]');
1759
+ if (direct) return direct;
1760
+ return this.document.elementFromPoint(event.clientX, event.clientY)?.closest('[data-section-position]') ?? undefined;
1761
+ }
1762
+ handleIndexPointer = event => {
1763
+ if (event.type === 'pointermove' && event.buttons === 0) return;
1764
+ const button = this.indexButtonAtEvent(event);
1765
+ if (!button) return;
1766
+ event.preventDefault();
1767
+ this.selectIndexPosition(Number(button.dataset.sectionPosition), button.textContent ?? '');
1768
+ };
1769
+ handleIndexClick = event => {
1770
+ const target = event.target;
1771
+ if (!(target instanceof Element)) return;
1772
+ const button = target.closest('[data-section-position]');
1773
+ if (!button) return;
1774
+ this.selectIndexPosition(Number(button.dataset.sectionPosition), button.textContent ?? '');
1775
+ };
1776
+ handlePullStart = event => {
1777
+ if (this.pointerReorder?.pointerId === event.pointerId || !this.snapshot.capabilities?.pullToRefresh || this.viewport.scrollTop > 0 || event.pointerType !== 'touch' && event.pointerType !== 'pen') return;
1778
+ this.pullStartY = event.clientY;
1779
+ this.pullDistance = 0;
1780
+ };
1781
+ handlePullMove = event => {
1782
+ if (this.pullStartY === undefined) return;
1783
+ this.pullDistance = Math.max(0, Math.min(96, event.clientY - this.pullStartY));
1784
+ if (this.pullDistance <= 0) return;
1785
+ event.preventDefault();
1786
+ this.refreshIndicator.textContent = this.pullDistance >= 64 ? 'Release to refresh' : 'Pull to refresh';
1787
+ setData(this.refreshIndicator, 'visible', true);
1788
+ this.refreshIndicator.style.transform = 'translate(-50%,' + String(Math.min(32, this.pullDistance / 2)) + 'px)';
1789
+ };
1790
+ handlePullEnd = () => {
1791
+ if (this.pullStartY === undefined) return;
1792
+ const shouldRefresh = this.pullDistance >= 64;
1793
+ this.pullStartY = undefined;
1794
+ this.pullDistance = 0;
1795
+ this.refreshIndicator.style.removeProperty('transform');
1796
+ this.updateRefreshIndicator();
1797
+ if (!shouldRefresh) return;
1798
+ this.callbacks.onRefresh?.();
1799
+ this.callbacks.onRowAction?.({
1800
+ actionKey: 'nativeList.refresh'
1801
+ });
1802
+ };
1803
+ updateRefreshIndicator() {
1804
+ const refreshing = Boolean(this.snapshot.capabilities?.refreshing);
1805
+ this.refreshIndicator.textContent = refreshing ? 'Refreshing' : '';
1806
+ setData(this.refreshIndicator, 'visible', refreshing);
1807
+ }
1808
+ handleReorderPointerDown = event => {
1809
+ if (this.pointerReorder || this.keyboardReorder || event.isPrimary === false || event.pointerType === 'mouse' && event.button !== 0) {
1810
+ return;
1811
+ }
1812
+ const target = event.target;
1813
+ if (!(target instanceof Element)) return;
1814
+ if (target.closest('button,input,textarea,select,a')) return;
1815
+ const rowElement = target.closest('[data-native-list-row-index]');
1816
+ const index = Number(rowElement?.dataset.nativeListRowIndex);
1817
+ const row = this.rows[index];
1818
+ if (!row || !this.isReorderable(row)) return;
1819
+ if (row.type === 'walletGroup' && target.closest('[data-native-list-group-parent]')?.dataset.nativeListGroupParent !== 'true') return;
1820
+ const view = this.document.defaultView;
1821
+ const state = {
1822
+ pointerId: event.pointerId,
1823
+ pointerType: event.pointerType || 'mouse',
1824
+ sourceKey: row.key,
1825
+ currentIndex: index,
1826
+ startX: event.clientX,
1827
+ startY: event.clientY,
1828
+ clientX: event.clientX,
1829
+ clientY: event.clientY,
1830
+ originalRows: this.snapshot.rows,
1831
+ workingRows: [...this.snapshot.rows],
1832
+ active: false
1833
+ };
1834
+ this.pointerReorder = state;
1835
+ if (state.pointerType === 'mouse') {
1836
+ this.captureReorderPointer(state);
1837
+ } else {
1838
+ state.longPressTimer = view?.setTimeout(() => this.activatePointerReorder(state), REORDER_TOUCH_LONG_PRESS_MS);
1839
+ }
1840
+ };
1841
+ activatePointerReorder(state) {
1842
+ if (this.pointerReorder !== state) return;
1843
+ this.clearReorderLongPress(state);
1844
+ state.active = true;
1845
+ state.activatedAt = Date.now();
1846
+ state.viewportRect = this.reorderViewportRect();
1847
+ this.captureReorderPointer(state);
1848
+ this.showReorderPreview(state);
1849
+ if (state.workingRows[state.currentIndex]?.type === 'walletGroup' && this.reorderCompactKey !== state.sourceKey) {
1850
+ this.reorderCompactKey = state.sourceKey;
1851
+ this.mounted.forEach(element => setData(element, 'nativeListAnimateReorder', true));
1852
+ this.recomputeLayout();
1853
+ }
1854
+ this.updateReorderVisualState();
1855
+ this.scheduleReorderAutoScroll();
1856
+ }
1857
+ captureReorderPointer(state) {
1858
+ try {
1859
+ this.viewport.setPointerCapture?.(state.pointerId);
1860
+ } catch {
1861
+ // A canceled pointer can disappear before the long-press timer fires.
1862
+ }
1863
+ }
1864
+ releaseReorderPointer(state) {
1865
+ try {
1866
+ if (this.viewport.hasPointerCapture?.(state.pointerId)) this.viewport.releasePointerCapture?.(state.pointerId);
1867
+ } catch {
1868
+ // The browser may have released capture while dispatching pointercancel.
1869
+ }
1870
+ }
1871
+ clearReorderLongPress(state) {
1872
+ if (state.longPressTimer === undefined) return;
1873
+ this.document.defaultView?.clearTimeout(state.longPressTimer);
1874
+ state.longPressTimer = undefined;
1875
+ }
1876
+ handleReorderPointerMove = event => {
1877
+ const state = this.pointerReorder;
1878
+ if (!state || state.pointerId !== event.pointerId) return;
1879
+ state.clientX = event.clientX;
1880
+ state.clientY = event.clientY;
1881
+ if (!state.active) {
1882
+ if (state.pointerType !== 'mouse') {
1883
+ this.cancelPointerReorder(false);
1884
+ return;
1885
+ }
1886
+ if (!hasExceededWebReorderMouseThreshold(event.clientX - state.startX, event.clientY - state.startY)) return;
1887
+ this.activatePointerReorder(state);
1888
+ if (!state.active) return;
1889
+ event.preventDefault();
1890
+ this.scheduleReorderMove();
1891
+ this.scheduleReorderAutoScroll();
1892
+ return;
1893
+ }
1894
+ event.preventDefault();
1895
+ this.scheduleReorderMove();
1896
+ this.scheduleReorderAutoScroll();
1897
+ };
1898
+ handleReorderTouchMove = event => {
1899
+ const state = this.pointerReorder;
1900
+ if (!state?.active || state.pointerType === 'mouse') return;
1901
+ const touch = event.touches[0];
1902
+ if (!touch) return;
1903
+ event.preventDefault();
1904
+ state.clientX = touch.clientX;
1905
+ state.clientY = touch.clientY;
1906
+ this.scheduleReorderMove();
1907
+ this.scheduleReorderAutoScroll();
1908
+ };
1909
+ handleReorderTouchEnd = event => {
1910
+ const state = this.pointerReorder;
1911
+ if (!state?.active || state.pointerType === 'mouse') return;
1912
+ event.preventDefault();
1913
+ this.completePointerReorder(state);
1914
+ };
1915
+ handleReorderTouchCancel = () => {
1916
+ const state = this.pointerReorder;
1917
+ if (!state?.active || state.pointerType === 'mouse') return;
1918
+ this.cancelPointerReorder(true);
1919
+ };
1920
+ setCurrentOffset(offset) {
1921
+ const maximum = Math.max(0, this.contentLength() - this.viewportLength());
1922
+ const next = Math.max(0, Math.min(maximum, offset));
1923
+ if (this.layout.horizontal) this.viewport.scrollLeft = next;else this.viewport.scrollTop = next;
1924
+ this.scheduleFrame();
1925
+ }
1926
+ showReorderPreview(state) {
1927
+ this.hideReorderPreview();
1928
+ const source = [...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey);
1929
+ const row = source?.firstElementChild;
1930
+ if (!source || !row) return;
1931
+ const rect = source.getBoundingClientRect();
1932
+ const groupParent = row.querySelector('[data-native-list-group-parent="true"]>.ok-native-list-wallet-row');
1933
+ const previewRow = groupParent ?? row;
1934
+ const previewHeight = groupParent ? 68 : rect.height;
1935
+ state.previewOffsetX = state.startX - rect.left;
1936
+ state.previewOffsetY = Math.min(previewHeight, Math.max(0, state.startY - rect.top));
1937
+ this.reorderPreview.replaceChildren(previewRow.cloneNode(true));
1938
+ const sourceRow = state.workingRows[state.currentIndex];
1939
+ const badgeText = sourceRow ? webWalletGroupReorderBadge(sourceRow) : undefined;
1940
+ if (badgeText) {
1941
+ this.reorderPreview.appendChild(createElement(this.document, 'span', 'ok-native-list-reorder-count', badgeText));
1942
+ }
1943
+ setData(this.reorderPreview, 'nativeListSelected', source.dataset.nativeListSelected === 'true');
1944
+ this.reorderPreview.style.width = String(rect.width) + 'px';
1945
+ this.reorderPreview.style.height = String(previewHeight) + 'px';
1946
+ this.reorderPreview.style.transition = 'none';
1947
+ this.reorderPreview.hidden = false;
1948
+ this.updateReorderPreview(state);
1949
+ }
1950
+ animateReorderPreviewToCurrent(state) {
1951
+ const destination = [...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey);
1952
+ const destinationIndex = this.rows.findIndex(row => row.key === state.sourceKey);
1953
+ const destinationLayout = this.layout.items.find(item => item.index === destinationIndex);
1954
+ if (this.reorderPreview.hidden || !destination || !destinationLayout) {
1955
+ this.hideReorderPreview();
1956
+ return;
1957
+ }
1958
+ this.reorderSettlingKey = state.sourceKey;
1959
+ const viewportRect = this.viewport.getBoundingClientRect();
1960
+ const left = viewportRect.left + destinationLayout.x - this.viewport.scrollLeft;
1961
+ const top = viewportRect.top + destinationLayout.y - this.viewport.scrollTop;
1962
+ this.reorderPreview.getBoundingClientRect();
1963
+ this.reorderPreview.style.transition = 'transform ' + String(REORDER_DROP_TRANSITION_MS) + 'ms ' + WEB_REORDER_ANIMATION.dropTimingFunction + ', box-shadow ' + String(REORDER_DROP_TRANSITION_MS) + 'ms ' + WEB_REORDER_ANIMATION.dropTimingFunction;
1964
+ this.reorderPreview.style.boxShadow = 'none';
1965
+ this.reorderPreview.style.transform = 'translate3d(' + String(left) + 'px,' + String(top) + 'px,0) scale(1)';
1966
+ this.reorderDropTimer = this.document.defaultView?.setTimeout(() => {
1967
+ this.reorderDropTimer = undefined;
1968
+ this.finishReorderDrop();
1969
+ }, REORDER_DROP_TRANSITION_MS);
1970
+ }
1971
+ clearReorderPreviewVisual() {
1972
+ this.reorderPreview.hidden = true;
1973
+ this.reorderPreview.replaceChildren();
1974
+ this.reorderPreview.style.removeProperty('transform');
1975
+ this.reorderPreview.style.removeProperty('transition');
1976
+ this.reorderPreview.style.removeProperty('box-shadow');
1977
+ this.reorderPreview.removeAttribute('data-native-list-selected');
1978
+ }
1979
+ finishReorderDrop() {
1980
+ this.clearReorderPreviewVisual();
1981
+ this.reorderSettlingKey = undefined;
1982
+ if (this.reorderCompactKey) {
1983
+ this.reorderExpandingKey = this.reorderCompactKey;
1984
+ this.reorderCompactKey = undefined;
1985
+ this.mounted.forEach(element => setData(element, 'nativeListAnimateReorder', true));
1986
+ this.recomputeLayout();
1987
+ }
1988
+ this.updateReorderVisualState();
1989
+ }
1990
+ hideReorderPreview() {
1991
+ if (this.reorderDropTimer !== undefined) {
1992
+ this.document.defaultView?.clearTimeout(this.reorderDropTimer);
1993
+ this.reorderDropTimer = undefined;
1994
+ }
1995
+ this.clearReorderPreviewVisual();
1996
+ this.reorderSettlingKey = undefined;
1997
+ this.reorderExpandingKey = undefined;
1998
+ if (this.reorderCompactKey) {
1999
+ this.reorderCompactKey = undefined;
2000
+ this.recomputeLayout();
2001
+ }
2002
+ }
2003
+ updateReorderPreview(state) {
2004
+ if (this.reorderPreview.hidden || state.previewOffsetX === undefined || state.previewOffsetY === undefined) return;
2005
+ const x = state.clientX - state.previewOffsetX;
2006
+ const y = state.clientY - state.previewOffsetY;
2007
+ this.reorderPreview.style.transform = 'translate3d(' + String(x) + 'px,' + String(y) + 'px,0)';
2008
+ }
2009
+ reorderViewportRect() {
2010
+ const {
2011
+ left,
2012
+ top,
2013
+ right,
2014
+ bottom,
2015
+ width,
2016
+ height
2017
+ } = this.viewport.getBoundingClientRect();
2018
+ return {
2019
+ left,
2020
+ top,
2021
+ right,
2022
+ bottom,
2023
+ width,
2024
+ height
2025
+ };
2026
+ }
2027
+ scheduleReorderMove() {
2028
+ if (this.reorderMoveFrame !== undefined || !this.pointerReorder?.active) return;
2029
+ this.reorderMoveFrame = this.requestFrame(this.runReorderMove);
2030
+ }
2031
+ runReorderMove = () => {
2032
+ this.reorderMoveFrame = undefined;
2033
+ const state = this.pointerReorder;
2034
+ if (!state?.active) return;
2035
+ this.updateReorderPreview(state);
2036
+ this.movePointerReorderTo(state.clientX, state.clientY);
2037
+ };
2038
+ flushReorderMove(state) {
2039
+ if (this.reorderMoveFrame !== undefined) {
2040
+ this.cancelFrame(this.reorderMoveFrame);
2041
+ this.reorderMoveFrame = undefined;
2042
+ }
2043
+ if (this.pointerReorder !== state || !state.active) return;
2044
+ this.updateReorderPreview(state);
2045
+ this.movePointerReorderTo(state.clientX, state.clientY);
2046
+ }
2047
+ stopReorderMove() {
2048
+ if (this.reorderMoveFrame === undefined) return;
2049
+ this.cancelFrame(this.reorderMoveFrame);
2050
+ this.reorderMoveFrame = undefined;
2051
+ }
2052
+ reorderIndexAtPointer(state, clientX, clientY) {
2053
+ const rect = state.viewportRect ?? this.reorderViewportRect();
2054
+ const local = this.layout.horizontal ? Math.max(0, Math.min(Math.max(0, rect.width - 1), clientX - rect.left)) : Math.max(0, Math.min(Math.max(0, rect.height - 1), clientY - rect.top));
2055
+ const coordinate = this.currentOffset() + local;
2056
+ const centerAt = index => {
2057
+ const item = this.layout.items[index];
2058
+ return item ? (itemStart(item, this.layout.horizontal) + itemEnd(item, this.layout.horizontal)) / 2 : Number.POSITIVE_INFINITY;
2059
+ };
2060
+ let low = 0;
2061
+ let high = this.layout.items.length;
2062
+ while (low < high) {
2063
+ const middle = Math.floor((low + high) / 2);
2064
+ if (centerAt(middle) < coordinate) low = middle + 1;else high = middle;
2065
+ }
2066
+ const previous = Math.max(0, low - 1);
2067
+ const next = Math.min(this.layout.items.length - 1, low);
2068
+ const nearest = Math.abs(centerAt(previous) - coordinate) <= Math.abs(centerAt(next) - coordinate) ? previous : next;
2069
+ return this.layout.items[nearest]?.index ?? -1;
2070
+ }
2071
+ movePointerReorderTo(clientX, clientY) {
2072
+ const state = this.pointerReorder;
2073
+ if (!state?.active) return;
2074
+ const fromIndex = state.currentIndex;
2075
+ const toIndex = this.reorderIndexAtPointer(state, clientX, clientY);
2076
+ const from = state.workingRows[fromIndex];
2077
+ const to = state.workingRows[toIndex];
2078
+ if (!from || !to || fromIndex === toIndex || !this.isReorderable(from) || !this.isReorderable(to) || from.sectionKey !== to.sectionKey) return;
2079
+ const [moved] = state.workingRows.splice(fromIndex, 1);
2080
+ if (!moved) return;
2081
+ state.workingRows.splice(toIndex, 0, moved);
2082
+ state.currentIndex = toIndex;
2083
+ const canReuseLayout = this.canReuseReorderLayout(fromIndex, toIndex);
2084
+ this.snapshot = {
2085
+ ...this.snapshot,
2086
+ rows: state.workingRows
2087
+ };
2088
+ this.rows = state.workingRows;
2089
+ this.remapMountedRowsForMove(fromIndex, toIndex);
2090
+ if (canReuseLayout) this.renderWindow();else this.recomputeLayout();
2091
+ this.updateReorderVisualState();
2092
+ }
2093
+ canReuseReorderLayout(fromIndex, toIndex) {
2094
+ const start = Math.min(fromIndex, toIndex);
2095
+ const end = Math.max(fromIndex, toIndex);
2096
+ const first = this.layout.items[start];
2097
+ if (!first || this.layout.items.length !== this.rows.length) return false;
2098
+ for (let index = start + 1; index <= end; index += 1) {
2099
+ const item = this.layout.items[index];
2100
+ if (!item || item.width !== first.width || item.height !== first.height) return false;
2101
+ }
2102
+ return true;
2103
+ }
2104
+ remapMountedRowsForMove(fromIndex, toIndex) {
2105
+ const remapped = [];
2106
+ this.mounted.forEach((element, index) => {
2107
+ let nextIndex = index;
2108
+ if (index === fromIndex) nextIndex = toIndex;else if (fromIndex < toIndex && index > fromIndex && index <= toIndex) nextIndex = index - 1;else if (fromIndex > toIndex && index >= toIndex && index < fromIndex) nextIndex = index + 1;
2109
+ setData(element, 'nativeListAnimateReorder', true);
2110
+ setData(element, 'nativeListRowIndex', nextIndex);
2111
+ remapped.push([nextIndex, element]);
2112
+ });
2113
+ this.mounted.clear();
2114
+ remapped.forEach(([index, element]) => this.mounted.set(index, element));
2115
+ }
2116
+ reorderAutoScrollVelocity() {
2117
+ const state = this.pointerReorder;
2118
+ if (!state?.active) return 0;
2119
+ const rect = state.viewportRect ?? this.reorderViewportRect();
2120
+ const point = this.layout.horizontal ? state.clientX : state.clientY;
2121
+ const start = this.layout.horizontal ? rect.left : rect.top;
2122
+ const end = this.layout.horizontal ? rect.right : rect.bottom;
2123
+ return webReorderAutoScrollVelocity(point, start, end, Date.now() - (state.activatedAt ?? 0));
2124
+ }
2125
+ scheduleReorderAutoScroll() {
2126
+ if (this.reorderAutoScrollFrame !== undefined || !this.pointerReorder?.active) return;
2127
+ this.reorderAutoScrollFrame = this.requestFrame(this.runReorderAutoScroll);
2128
+ }
2129
+ runReorderAutoScroll = () => {
2130
+ this.reorderAutoScrollFrame = undefined;
2131
+ const state = this.pointerReorder;
2132
+ if (!state?.active) return;
2133
+ const velocity = this.reorderAutoScrollVelocity();
2134
+ if (velocity === 0) return;
2135
+ const before = this.currentOffset();
2136
+ this.setCurrentOffset(before + velocity);
2137
+ if (this.currentOffset() !== before) {
2138
+ this.movePointerReorderTo(state.clientX, state.clientY);
2139
+ this.scheduleReorderAutoScroll();
2140
+ }
2141
+ };
2142
+ handleReorderPointerEnd = event => {
2143
+ const state = this.pointerReorder;
2144
+ if (!state || state.pointerId !== event.pointerId) return;
2145
+ this.clearReorderLongPress(state);
2146
+ this.releaseReorderPointer(state);
2147
+ if (!state.active) {
2148
+ this.pointerReorder = undefined;
2149
+ return;
2150
+ }
2151
+ event.preventDefault();
2152
+ state.clientX = event.clientX;
2153
+ state.clientY = event.clientY;
2154
+ this.completePointerReorder(state);
2155
+ };
2156
+ completePointerReorder(state) {
2157
+ this.flushReorderMove(state);
2158
+ const finalRows = this.snapshot.rows;
2159
+ const reorderEvent = webReorderEventForRows(state.originalRows, finalRows, state.sourceKey);
2160
+ this.animateReorderPreviewToCurrent(state);
2161
+ this.pointerReorder = undefined;
2162
+ this.stopReorderMove();
2163
+ this.stopReorderAutoScroll();
2164
+ this.suppressClickUntil = Date.now() + 300;
2165
+ this.updateReorderVisualState();
2166
+ if (reorderEvent) this.callbacks.onReorder?.(reorderEvent);
2167
+ }
2168
+ handleReorderPointerCancel = event => {
2169
+ if (this.pointerReorder?.pointerId !== event.pointerId) return;
2170
+ if (this.pointerReorder.active && this.pointerReorder.pointerType !== 'mouse') return;
2171
+ this.cancelPointerReorder(true);
2172
+ };
2173
+ stopReorderAutoScroll() {
2174
+ if (this.reorderAutoScrollFrame === undefined) return;
2175
+ this.cancelFrame(this.reorderAutoScrollFrame);
2176
+ this.reorderAutoScrollFrame = undefined;
2177
+ }
2178
+ cancelPointerReorder(restore) {
2179
+ const state = this.pointerReorder;
2180
+ if (!state) return;
2181
+ this.clearReorderLongPress(state);
2182
+ this.releaseReorderPointer(state);
2183
+ this.pointerReorder = undefined;
2184
+ this.stopReorderMove();
2185
+ this.stopReorderAutoScroll();
2186
+ if (state.active && restore) {
2187
+ this.remapMountedRows(state.originalRows);
2188
+ this.setSnapshot({
2189
+ ...this.snapshot,
2190
+ rows: cancelWebReorderRows(state.originalRows)
2191
+ }, this.selectedKeys);
2192
+ this.suppressClickUntil = Date.now() + 300;
2193
+ }
2194
+ if (state.active && restore) this.animateReorderPreviewToCurrent(state);else this.hideReorderPreview();
2195
+ this.updateReorderVisualState();
2196
+ }
2197
+ remapMountedRows(rows) {
2198
+ const elementByKey = new Map();
2199
+ this.mounted.forEach(element => {
2200
+ const key = element.dataset.nativeListRowKey;
2201
+ if (key) elementByKey.set(key, element);
2202
+ setData(element, 'nativeListAnimateReorder', true);
2203
+ });
2204
+ this.mounted.clear();
2205
+ rows.forEach((row, index) => {
2206
+ const element = elementByKey.get(row.key);
2207
+ if (element) {
2208
+ setData(element, 'nativeListRowIndex', index);
2209
+ this.mounted.set(index, element);
2210
+ }
2211
+ });
2212
+ }
2213
+ updateReorderVisualState() {
2214
+ const sourceKey = this.pointerReorder?.active ? this.pointerReorder.sourceKey : this.keyboardReorder?.sourceKey ?? this.reorderSettlingKey;
2215
+ setData(this.root, 'nativeListDragging', Boolean(sourceKey));
2216
+ if (this.reorderMovementTimer !== undefined) {
2217
+ this.document.defaultView?.clearTimeout(this.reorderMovementTimer);
2218
+ this.reorderMovementTimer = undefined;
2219
+ }
2220
+ if (!sourceKey) {
2221
+ this.reorderMovementTimer = this.document.defaultView?.setTimeout(() => {
2222
+ this.reorderMovementTimer = undefined;
2223
+ this.reorderExpandingKey = undefined;
2224
+ this.mounted.forEach(element => {
2225
+ setData(element, 'nativeListAnimateReorder', false);
2226
+ setData(element, 'nativeListReorderExpanding', false);
2227
+ });
2228
+ }, WEB_REORDER_ANIMATION.outOfWayDurationMs);
2229
+ }
2230
+ this.mounted.forEach(element => {
2231
+ const dragging = element.dataset.nativeListRowKey === sourceKey;
2232
+ setData(element, 'nativeListReorderExpanding', element.dataset.nativeListRowKey === this.reorderExpandingKey);
2233
+ setData(element, 'nativeListDragging', dragging);
2234
+ if (dragging) element.setAttribute('aria-grabbed', 'true');else element.removeAttribute('aria-grabbed');
2235
+ });
2236
+ }
2237
+ }
2238
+ //# sourceMappingURL=NativeListWebEngine.js.map