@onekeyfe/react-native-native-list 3.0.122 → 3.0.124
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.
- package/README.md +2 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +150 -30
- package/ios/NativeListCell.swift +23 -0
- package/ios/RNCNativeListView.swift +79 -20
- package/lib/module/web/NativeListWebEngine.js +57 -29
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +1 -0
- package/package.json +4 -4
- package/src/web/NativeListWebEngine.ts +87 -33
package/README.md
CHANGED
|
@@ -155,6 +155,8 @@ order, skips headers without `indexTitle`, and scrolls to the header's existing
|
|
|
155
155
|
row `key`. Titles must be unique after Unicode NFC normalization, contain no
|
|
156
156
|
leading/trailing whitespace, and be at most eight Unicode code points. Set
|
|
157
157
|
`hapticsEnabled: false` to follow an application-level reduced-haptics setting.
|
|
158
|
+
Set `centeredInWindow: true` to render the interactive index rail against the
|
|
159
|
+
full window and center it on the window's geometric midpoint.
|
|
158
160
|
The web implementation renders the same explicit index entries as a DOM
|
|
159
161
|
overlay, with click and pointer-drag navigation.
|
|
160
162
|
|
|
@@ -18,14 +18,13 @@ import android.view.Gravity
|
|
|
18
18
|
import android.view.MotionEvent
|
|
19
19
|
import android.view.View
|
|
20
20
|
import android.view.ViewConfiguration
|
|
21
|
+
import android.view.ViewGroup
|
|
21
22
|
import android.view.accessibility.AccessibilityEvent
|
|
22
23
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
23
24
|
import android.widget.FrameLayout
|
|
24
25
|
import android.widget.LinearLayout
|
|
25
26
|
import android.widget.SeekBar
|
|
26
27
|
import android.widget.TextView
|
|
27
|
-
import androidx.core.view.ViewCompat
|
|
28
|
-
import androidx.core.view.WindowInsetsCompat
|
|
29
28
|
import androidx.recyclerview.widget.GridLayoutManager
|
|
30
29
|
import androidx.recyclerview.widget.ItemTouchHelper
|
|
31
30
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
@@ -51,6 +50,11 @@ import kotlin.math.sqrt
|
|
|
51
50
|
class NativeListView(
|
|
52
51
|
private val reactContext: ThemedReactContext,
|
|
53
52
|
) : LinearLayout(reactContext) {
|
|
53
|
+
private data class VisibleMarketAnchor(
|
|
54
|
+
val key: String,
|
|
55
|
+
val offset: Int,
|
|
56
|
+
)
|
|
57
|
+
|
|
54
58
|
private class ActionAnchorRecord(
|
|
55
59
|
val token: String,
|
|
56
60
|
origin: NativeListActionOrigin,
|
|
@@ -163,6 +167,8 @@ class NativeListView(
|
|
|
163
167
|
private var sectionIndexScrubbing = false
|
|
164
168
|
private var sectionIndexProgrammaticScroll = false
|
|
165
169
|
private var sectionIndexHapticsEnabled = true
|
|
170
|
+
private val sectionIndexLocationOnScreen = IntArray(2)
|
|
171
|
+
private val sectionIndexHostLocationOnScreen = IntArray(2)
|
|
166
172
|
private var pendingScrollRequest: ScrollRequest? = null
|
|
167
173
|
private val actionAnchorInstanceId = UUID.randomUUID().toString()
|
|
168
174
|
private var actionAnchorCounter = 0L
|
|
@@ -170,6 +176,7 @@ class NativeListView(
|
|
|
170
176
|
private var lastLayoutWidth = -1
|
|
171
177
|
private var lastLayoutHeight = -1
|
|
172
178
|
private var lastLayoutDirection = layoutDirection
|
|
179
|
+
private var lastMarketPaginationAnchorLogAtMs = 0L
|
|
173
180
|
private var disposed = false
|
|
174
181
|
|
|
175
182
|
init {
|
|
@@ -298,6 +305,7 @@ class NativeListView(
|
|
|
298
305
|
lastLayoutWidth = nextWidth
|
|
299
306
|
lastLayoutHeight = nextHeight
|
|
300
307
|
lastLayoutDirection = layoutDirection
|
|
308
|
+
updateSectionIndexAttachment()
|
|
301
309
|
performPendingScrollIfNeeded()
|
|
302
310
|
}
|
|
303
311
|
|
|
@@ -312,6 +320,7 @@ class NativeListView(
|
|
|
312
320
|
|
|
313
321
|
override fun onAttachedToWindow() {
|
|
314
322
|
super.onAttachedToWindow()
|
|
323
|
+
updateSectionIndexAttachment()
|
|
315
324
|
val first = config?.items?.firstOrNull()
|
|
316
325
|
if (recyclerView.isLayoutRequested && (first?.type == "market" || first?.json?.optString("presentation") == "market")) {
|
|
317
326
|
relayoutContents()
|
|
@@ -319,6 +328,7 @@ class NativeListView(
|
|
|
319
328
|
}
|
|
320
329
|
|
|
321
330
|
override fun onDetachedFromWindow() {
|
|
331
|
+
attachSectionIndexToList()
|
|
322
332
|
updateRefreshIndicatorOffset(0)
|
|
323
333
|
super.onDetachedFromWindow()
|
|
324
334
|
}
|
|
@@ -363,6 +373,9 @@ class NativeListView(
|
|
|
363
373
|
}
|
|
364
374
|
return
|
|
365
375
|
}
|
|
376
|
+
val preserveMarketPaginationAnchor = previous?.let {
|
|
377
|
+
isMarketPaginationUpdate(it, next)
|
|
378
|
+
} ?: false
|
|
366
379
|
// Keep the first Market row at its current pixel offset when it is
|
|
367
380
|
// reordered. RecyclerView otherwise follows the previous first key.
|
|
368
381
|
val marketStartOffset = if (
|
|
@@ -390,7 +403,7 @@ class NativeListView(
|
|
|
390
403
|
if (marketStartOffset != null) {
|
|
391
404
|
layoutManager.scrollToPositionWithOffset(0, marketStartOffset)
|
|
392
405
|
}
|
|
393
|
-
relayoutContents()
|
|
406
|
+
relayoutContents(preserveMarketPaginationAnchor)
|
|
394
407
|
performPendingScrollIfNeeded()
|
|
395
408
|
syncSectionIndexToVisibleRows()
|
|
396
409
|
scheduleVisibleEvent()
|
|
@@ -402,6 +415,40 @@ class NativeListView(
|
|
|
402
415
|
updateReordering(next)
|
|
403
416
|
}
|
|
404
417
|
|
|
418
|
+
private fun captureVisibleMarketAnchor(): VisibleMarketAnchor? {
|
|
419
|
+
val position = layoutManager.findFirstVisibleItemPosition()
|
|
420
|
+
if (position == RecyclerView.NO_POSITION) return null
|
|
421
|
+
val item = adapter.itemAt(position) ?: return null
|
|
422
|
+
val view = layoutManager.findViewByPosition(position) ?: return null
|
|
423
|
+
return VisibleMarketAnchor(
|
|
424
|
+
key = item.key,
|
|
425
|
+
offset = layoutManager.getDecoratedTop(view) - recyclerView.paddingTop,
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private fun isMarketPaginationUpdate(
|
|
430
|
+
previous: NativeListConfig,
|
|
431
|
+
next: NativeListConfig,
|
|
432
|
+
): Boolean {
|
|
433
|
+
if (!previous.loadMore && !next.loadMore) return false
|
|
434
|
+
val previousRows = previous.items.dropLastWhile {
|
|
435
|
+
it.key in MARKET_PAGINATION_KEYS
|
|
436
|
+
}
|
|
437
|
+
val nextRows = next.items.dropLastWhile {
|
|
438
|
+
it.key in MARKET_PAGINATION_KEYS
|
|
439
|
+
}
|
|
440
|
+
if (previousRows.isEmpty() || previousRows.size > nextRows.size) return false
|
|
441
|
+
if (
|
|
442
|
+
previousRows.firstOrNull()?.type != "market" ||
|
|
443
|
+
nextRows.firstOrNull()?.type != "market"
|
|
444
|
+
) {
|
|
445
|
+
return false
|
|
446
|
+
}
|
|
447
|
+
return previousRows.indices.all { index ->
|
|
448
|
+
previousRows[index].key == nextRows[index].key
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
405
452
|
/**
|
|
406
453
|
* A controlled selection update sends the snapshot back after the native
|
|
407
454
|
* selection delta. Its structure is unchanged; only selectedKeys and the
|
|
@@ -907,6 +954,7 @@ class NativeListView(
|
|
|
907
954
|
|
|
908
955
|
fun dispose() {
|
|
909
956
|
if (disposed) return
|
|
957
|
+
attachSectionIndexToList()
|
|
910
958
|
stopReorderRelayoutLoop()
|
|
911
959
|
invalidateActionAnchor("destroy")
|
|
912
960
|
actionAnchor = null
|
|
@@ -981,9 +1029,9 @@ class NativeListView(
|
|
|
981
1029
|
themeColor(next.theme, "disabledText", "#8D8D8D"),
|
|
982
1030
|
themeColor(next.theme, "positive", "#218358"),
|
|
983
1031
|
themeColor(next.theme, "inverseText", "#FCFCFC"),
|
|
984
|
-
next.sectionIndexCenteredInWindow,
|
|
985
1032
|
)
|
|
986
1033
|
sectionIndexView.visibility = if (sectionIndexEntries.isEmpty()) GONE else VISIBLE
|
|
1034
|
+
updateSectionIndexAttachment()
|
|
987
1035
|
sectionIndexPreview.setTextColor(Color.WHITE)
|
|
988
1036
|
sectionIndexPreview.background = sectionIndexPreviewBackground()
|
|
989
1037
|
sectionIndexView.setActiveIndex(
|
|
@@ -1035,11 +1083,75 @@ class NativeListView(
|
|
|
1035
1083
|
private fun positionSectionIndexPreview(index: Int) {
|
|
1036
1084
|
val halfHeight = sectionIndexDp(SECTION_INDEX_PREVIEW_HEIGHT_DP) / 2f
|
|
1037
1085
|
val maximumY = (contentContainer.height - halfHeight).coerceAtLeast(halfHeight)
|
|
1038
|
-
|
|
1086
|
+
sectionIndexView.getLocationOnScreen(sectionIndexLocationOnScreen)
|
|
1087
|
+
contentContainer.getLocationOnScreen(sectionIndexHostLocationOnScreen)
|
|
1088
|
+
val targetY = sectionIndexLocationOnScreen[1] - sectionIndexHostLocationOnScreen[1] +
|
|
1089
|
+
sectionIndexView.centerYForIndex(index)
|
|
1039
1090
|
val clampedY = targetY.coerceIn(halfHeight, maximumY)
|
|
1040
1091
|
sectionIndexPreview.translationY = clampedY - contentContainer.height / 2f
|
|
1041
1092
|
}
|
|
1042
1093
|
|
|
1094
|
+
private fun updateSectionIndexAttachment() {
|
|
1095
|
+
val windowHost = rootView as? FrameLayout
|
|
1096
|
+
val useWindowHost = config?.sectionIndexCenteredInWindow == true &&
|
|
1097
|
+
sectionIndexEntries.isNotEmpty() &&
|
|
1098
|
+
isAttachedToWindow &&
|
|
1099
|
+
isShown &&
|
|
1100
|
+
width > 0 &&
|
|
1101
|
+
height > 0 &&
|
|
1102
|
+
windowHost != null &&
|
|
1103
|
+
windowHost.width > 0 &&
|
|
1104
|
+
windowHost.height > 0 &&
|
|
1105
|
+
windowHost !== contentContainer
|
|
1106
|
+
if (useWindowHost) {
|
|
1107
|
+
attachSectionIndexToWindow(windowHost)
|
|
1108
|
+
} else {
|
|
1109
|
+
attachSectionIndexToList()
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
private fun attachSectionIndexToList() {
|
|
1114
|
+
if (sectionIndexView.parent === contentContainer) return
|
|
1115
|
+
(sectionIndexView.parent as? ViewGroup)?.removeView(sectionIndexView)
|
|
1116
|
+
contentContainer.addView(
|
|
1117
|
+
sectionIndexView,
|
|
1118
|
+
FrameLayout.LayoutParams(
|
|
1119
|
+
sectionIndexDp(SECTION_INDEX_RAIL_WIDTH_DP),
|
|
1120
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
1121
|
+
Gravity.END,
|
|
1122
|
+
),
|
|
1123
|
+
)
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
private fun attachSectionIndexToWindow(windowHost: FrameLayout) {
|
|
1127
|
+
val railWidth = sectionIndexDp(SECTION_INDEX_RAIL_WIDTH_DP)
|
|
1128
|
+
val railHeight = sectionIndexView.preferredHeight(windowHost.height)
|
|
1129
|
+
windowHost.getLocationOnScreen(sectionIndexHostLocationOnScreen)
|
|
1130
|
+
getLocationOnScreen(sectionIndexLocationOnScreen)
|
|
1131
|
+
val listLeft = sectionIndexLocationOnScreen[0] - sectionIndexHostLocationOnScreen[0]
|
|
1132
|
+
val maximumEndMargin = (windowHost.width - railWidth).coerceAtLeast(0)
|
|
1133
|
+
val endMargin = if (layoutDirection == LAYOUT_DIRECTION_RTL) {
|
|
1134
|
+
listLeft
|
|
1135
|
+
} else {
|
|
1136
|
+
windowHost.width - listLeft - width
|
|
1137
|
+
}.coerceIn(0, maximumEndMargin)
|
|
1138
|
+
val layoutParams = FrameLayout.LayoutParams(
|
|
1139
|
+
railWidth,
|
|
1140
|
+
railHeight,
|
|
1141
|
+
Gravity.TOP or Gravity.END,
|
|
1142
|
+
).apply {
|
|
1143
|
+
marginEnd = endMargin
|
|
1144
|
+
topMargin = (windowHost.height - railHeight) / 2
|
|
1145
|
+
}
|
|
1146
|
+
if (sectionIndexView.parent !== windowHost) {
|
|
1147
|
+
(sectionIndexView.parent as? ViewGroup)?.removeView(sectionIndexView)
|
|
1148
|
+
windowHost.addView(sectionIndexView, layoutParams)
|
|
1149
|
+
sectionIndexView.bringToFront()
|
|
1150
|
+
} else {
|
|
1151
|
+
sectionIndexView.layoutParams = layoutParams
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1043
1155
|
private fun sectionIndexPreviewBackground(): ShapeDrawable {
|
|
1044
1156
|
val path = Path().apply {
|
|
1045
1157
|
moveTo(25f, 0f)
|
|
@@ -1839,15 +1951,36 @@ class NativeListView(
|
|
|
1839
1951
|
* already-sized host once after a committed data update so the native rows
|
|
1840
1952
|
* are measured and rebound without rebuilding the adapter.
|
|
1841
1953
|
*/
|
|
1842
|
-
private fun relayoutContents() {
|
|
1954
|
+
private fun relayoutContents(preserveMarketPaginationAnchor: Boolean = false) {
|
|
1843
1955
|
post {
|
|
1844
1956
|
if (disposed || width <= 0 || height <= 0) return@post
|
|
1957
|
+
val anchor = if (preserveMarketPaginationAnchor) {
|
|
1958
|
+
captureVisibleMarketAnchor()
|
|
1959
|
+
} else {
|
|
1960
|
+
null
|
|
1961
|
+
}
|
|
1962
|
+
val anchorIndex = anchor?.let { currentAnchor ->
|
|
1963
|
+
adapter.currentList.indexOfFirst { it.key == currentAnchor.key }
|
|
1964
|
+
} ?: RecyclerView.NO_POSITION
|
|
1965
|
+
if (anchor != null && anchorIndex >= 0) {
|
|
1966
|
+
layoutManager.scrollToPositionWithOffset(anchorIndex, anchor.offset)
|
|
1967
|
+
}
|
|
1845
1968
|
forceLayout()
|
|
1846
1969
|
measure(
|
|
1847
1970
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
1848
1971
|
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
|
1849
1972
|
)
|
|
1850
1973
|
layout(left, top, right, bottom)
|
|
1974
|
+
if (anchorIndex >= 0) {
|
|
1975
|
+
val now = SystemClock.uptimeMillis()
|
|
1976
|
+
if (now - lastMarketPaginationAnchorLogAtMs >= 1_000L) {
|
|
1977
|
+
lastMarketPaginationAnchorLogAtMs = now
|
|
1978
|
+
OneKeyLog.debug(
|
|
1979
|
+
"NativeList",
|
|
1980
|
+
"market-pagination-anchor-restored generation=${config?.generation} index=$anchorIndex",
|
|
1981
|
+
)
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1851
1984
|
}
|
|
1852
1985
|
}
|
|
1853
1986
|
|
|
@@ -1937,6 +2070,11 @@ class NativeListView(
|
|
|
1937
2070
|
private fun sectionIndexDp(value: Int): Int = (value * density).roundToInt()
|
|
1938
2071
|
|
|
1939
2072
|
companion object {
|
|
2073
|
+
private val MARKET_PAGINATION_KEYS = setOf(
|
|
2074
|
+
"market-loading-more",
|
|
2075
|
+
"market-load-more-retry",
|
|
2076
|
+
"market-end",
|
|
2077
|
+
)
|
|
1940
2078
|
private val MARKET_QUOTE_FIELDS = setOf(
|
|
1941
2079
|
"revision",
|
|
1942
2080
|
"price",
|
|
@@ -2039,10 +2177,7 @@ private class NativeListSectionIndexView(
|
|
|
2039
2177
|
private var normalColor = Color.GRAY
|
|
2040
2178
|
private var activeColor = Color.BLACK
|
|
2041
2179
|
private var activeTextColor = Color.WHITE
|
|
2042
|
-
private var centeredInWindow = false
|
|
2043
2180
|
private var lastTouchIndex: Int? = null
|
|
2044
|
-
private val rootLocationOnScreen = IntArray(2)
|
|
2045
|
-
private val locationOnScreen = IntArray(2)
|
|
2046
2181
|
private val activeBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
2047
2182
|
private val normalPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
2048
2183
|
textAlign = Paint.Align.CENTER
|
|
@@ -2065,13 +2200,11 @@ private class NativeListSectionIndexView(
|
|
|
2065
2200
|
normalColor: Int,
|
|
2066
2201
|
activeColor: Int,
|
|
2067
2202
|
activeTextColor: Int,
|
|
2068
|
-
centeredInWindow: Boolean,
|
|
2069
2203
|
) {
|
|
2070
2204
|
this.titles = titles
|
|
2071
2205
|
this.normalColor = normalColor
|
|
2072
2206
|
this.activeColor = activeColor
|
|
2073
2207
|
this.activeTextColor = activeTextColor
|
|
2074
|
-
this.centeredInWindow = centeredInWindow
|
|
2075
2208
|
activeIndex = null
|
|
2076
2209
|
updateContentDescription()
|
|
2077
2210
|
invalidate()
|
|
@@ -2202,6 +2335,11 @@ private class NativeListSectionIndexView(
|
|
|
2202
2335
|
|
|
2203
2336
|
fun centerYForIndex(index: Int): Float = entryCenterY(index, indexMetrics())
|
|
2204
2337
|
|
|
2338
|
+
fun preferredHeight(maximumHeight: Int): Int = minOf(
|
|
2339
|
+
maximumHeight,
|
|
2340
|
+
(dp(8f) * 2f + dp(16f) * titles.size).roundToInt(),
|
|
2341
|
+
)
|
|
2342
|
+
|
|
2205
2343
|
private data class Metrics(val originY: Float, val trackHeight: Float)
|
|
2206
2344
|
|
|
2207
2345
|
private fun indexMetrics(): Metrics {
|
|
@@ -2210,25 +2348,7 @@ private class NativeListSectionIndexView(
|
|
|
2210
2348
|
val labelSpacing = dp(16f)
|
|
2211
2349
|
val availableHeight = (height - edgePadding * 2f).coerceAtLeast(0f)
|
|
2212
2350
|
val trackHeight = minOf(availableHeight, labelSpacing * titles.size)
|
|
2213
|
-
|
|
2214
|
-
if (!centeredInWindow || !isAttachedToWindow) {
|
|
2215
|
-
return Metrics(centeredOriginY, trackHeight)
|
|
2216
|
-
}
|
|
2217
|
-
val systemBarInsets = ViewCompat.getRootWindowInsets(rootView)
|
|
2218
|
-
?.getInsetsIgnoringVisibility(
|
|
2219
|
-
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
|
|
2220
|
-
) ?: return Metrics(centeredOriginY, trackHeight)
|
|
2221
|
-
rootView.getLocationOnScreen(rootLocationOnScreen)
|
|
2222
|
-
getLocationOnScreen(locationOnScreen)
|
|
2223
|
-
val safeTop = rootLocationOnScreen[1] + systemBarInsets.top
|
|
2224
|
-
val safeBottom = rootLocationOnScreen[1] + rootView.height - systemBarInsets.bottom
|
|
2225
|
-
if (safeBottom <= safeTop) return Metrics(centeredOriginY, trackHeight)
|
|
2226
|
-
val localCenterY = (safeTop + safeBottom) / 2f - locationOnScreen[1]
|
|
2227
|
-
val maximumOrigin = (height - edgePadding - trackHeight).coerceAtLeast(edgePadding)
|
|
2228
|
-
return Metrics(
|
|
2229
|
-
(localCenterY - trackHeight / 2f).coerceIn(edgePadding, maximumOrigin),
|
|
2230
|
-
trackHeight,
|
|
2231
|
-
)
|
|
2351
|
+
return Metrics((height - trackHeight) / 2f, trackHeight)
|
|
2232
2352
|
}
|
|
2233
2353
|
|
|
2234
2354
|
private fun entryCenterY(index: Int, metrics: Metrics): Float {
|
package/ios/NativeListCell.swift
CHANGED
|
@@ -100,6 +100,15 @@ final class NativeListActionOrigin {
|
|
|
100
100
|
|
|
101
101
|
// OneKey patch: explicit summary actions use the source text's physical-pixel line box.
|
|
102
102
|
private final class NativeListAccessoryButton: UIButton {
|
|
103
|
+
var pressedBackgroundColor: UIColor?
|
|
104
|
+
|
|
105
|
+
override var isHighlighted: Bool {
|
|
106
|
+
didSet {
|
|
107
|
+
guard let pressedBackgroundColor else { return }
|
|
108
|
+
backgroundColor = isHighlighted ? pressedBackgroundColor : .clear
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
var selectorSummaryLineHeight: CGFloat? {
|
|
104
113
|
didSet { invalidateIntrinsicContentSize(); setNeedsLayout() }
|
|
105
114
|
}
|
|
@@ -1373,6 +1382,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1373
1382
|
button.setImage(nil, for: .disabled)
|
|
1374
1383
|
button.isEnabled = true
|
|
1375
1384
|
button.alpha = 1
|
|
1385
|
+
button.pressedBackgroundColor = nil
|
|
1376
1386
|
button.titleLabel?.numberOfLines = 1
|
|
1377
1387
|
button.contentHorizontalAlignment = .center
|
|
1378
1388
|
button.backgroundColor = .clear
|
|
@@ -1924,6 +1934,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1924
1934
|
trailingStack.spacing = 10
|
|
1925
1935
|
}
|
|
1926
1936
|
bindAccessories(item, accessories, theme, checkboxState)
|
|
1937
|
+
if item.data.string("presentation") == "accountSelector",
|
|
1938
|
+
accessories.count == 1,
|
|
1939
|
+
let accessory = accessoryButtons.first(where: { !$0.isHidden }) {
|
|
1940
|
+
// The vertical trailing stack has no stable intrinsic width, so keep its
|
|
1941
|
+
// flexible space in the account title and subtitle column.
|
|
1942
|
+
let width = trailingStack.widthAnchor.constraint(equalTo: accessory.widthAnchor)
|
|
1943
|
+
width.isActive = true
|
|
1944
|
+
selectorConstraints.append(width)
|
|
1945
|
+
}
|
|
1927
1946
|
}
|
|
1928
1947
|
|
|
1929
1948
|
private func bindRail(_ item: NativeListItem, theme: [String: Any]?) {
|
|
@@ -3965,6 +3984,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3965
3984
|
let isAccountIcon = currentItem?.data.string("presentation") == "accountSelector" && !isDrillIn
|
|
3966
3985
|
let size: CGFloat = isDrillIn ? 24 : isAccountIcon && !isAccountCreate ? 38 : 36
|
|
3967
3986
|
if isAccountCreate { button.layer.cornerRadius = 8 }
|
|
3987
|
+
if isAccountIcon && !isAccountCreate && !data.string("actionKey").isEmpty {
|
|
3988
|
+
button.pressedBackgroundColor = nativeListColor(currentTheme, "rowPressedBackground", "#E8E8E8")
|
|
3989
|
+
button.layer.cornerRadius = size / 2
|
|
3990
|
+
}
|
|
3968
3991
|
if isAccountIcon { rootStack.setCustomSpacing(5, after: mainStack) }
|
|
3969
3992
|
button.accessibilityIdentifier = data["testID"] as? String
|
|
3970
3993
|
button.accessibilityLabel = data["accessibilityLabel"] as? String
|
|
@@ -65,6 +65,8 @@ final class NativeListView: UIView {
|
|
|
65
65
|
private let footerCell = NativeListCell(frame: .zero)
|
|
66
66
|
private let sectionIndexView = NativeListSectionIndexView()
|
|
67
67
|
private let sectionIndexPreview = NativeListSectionIndexPreviewView()
|
|
68
|
+
private var sectionIndexLayoutConstraints: [NSLayoutConstraint] = []
|
|
69
|
+
private var sectionIndexWindowHeightConstraint: NSLayoutConstraint?
|
|
68
70
|
private var footerHeightConstraint: NSLayoutConstraint!
|
|
69
71
|
private var dataSource: UICollectionViewDiffableDataSource<Int, String>!
|
|
70
72
|
private var config: NativeListConfig?
|
|
@@ -161,10 +163,6 @@ final class NativeListView: UIView {
|
|
|
161
163
|
footerContainer.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
162
164
|
footerContainer.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
163
165
|
footerHeightConstraint,
|
|
164
|
-
sectionIndexView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
|
|
165
|
-
sectionIndexView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
|
|
166
|
-
sectionIndexView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
|
|
167
|
-
sectionIndexView.widthAnchor.constraint(equalToConstant: Self.sectionIndexRailWidth),
|
|
168
166
|
sectionIndexPreview.trailingAnchor.constraint(
|
|
169
167
|
equalTo: safeAreaLayoutGuide.trailingAnchor,
|
|
170
168
|
constant: -Self.sectionIndexPreviewEndMargin
|
|
@@ -173,6 +171,7 @@ final class NativeListView: UIView {
|
|
|
173
171
|
sectionIndexPreview.widthAnchor.constraint(equalToConstant: Self.sectionIndexPreviewWidth),
|
|
174
172
|
sectionIndexPreview.heightAnchor.constraint(equalToConstant: Self.sectionIndexPreviewHeight),
|
|
175
173
|
])
|
|
174
|
+
attachSectionIndexToList()
|
|
176
175
|
|
|
177
176
|
sectionIndexView.isHidden = true
|
|
178
177
|
sectionIndexView.onSelect = { [weak self] index, interacting in
|
|
@@ -233,8 +232,19 @@ final class NativeListView: UIView {
|
|
|
233
232
|
fatalError("init(coder:) has not been implemented")
|
|
234
233
|
}
|
|
235
234
|
|
|
235
|
+
override func willMove(toWindow newWindow: UIWindow?) {
|
|
236
|
+
if newWindow == nil { attachSectionIndexToList() }
|
|
237
|
+
super.willMove(toWindow: newWindow)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
override func didMoveToWindow() {
|
|
241
|
+
super.didMoveToWindow()
|
|
242
|
+
updateSectionIndexAttachment()
|
|
243
|
+
}
|
|
244
|
+
|
|
236
245
|
override func layoutSubviews() {
|
|
237
246
|
super.layoutSubviews()
|
|
247
|
+
updateSectionIndexAttachment()
|
|
238
248
|
let direction = effectiveUserInterfaceLayoutDirection
|
|
239
249
|
if let lastLayoutSize,
|
|
240
250
|
lastLayoutSize != bounds.size || lastLayoutDirection != nil && lastLayoutDirection != direction {
|
|
@@ -1027,10 +1037,10 @@ final class NativeListView: UIView {
|
|
|
1027
1037
|
titles: sectionIndexEntries.map(\.title),
|
|
1028
1038
|
textColor: nativeListColor(config.theme, "disabledText", "#8D8D8D"),
|
|
1029
1039
|
activeColor: nativeListColor(config.theme, "positive", "#218358"),
|
|
1030
|
-
activeTextColor: nativeListColor(config.theme, "inverseText", "#FCFCFC")
|
|
1031
|
-
centeredInWindow: config.sectionIndexCenteredInWindow
|
|
1040
|
+
activeTextColor: nativeListColor(config.theme, "inverseText", "#FCFCFC")
|
|
1032
1041
|
)
|
|
1033
1042
|
sectionIndexView.isHidden = sectionIndexEntries.isEmpty
|
|
1043
|
+
updateSectionIndexAttachment()
|
|
1034
1044
|
sectionIndexPreview.configure(
|
|
1035
1045
|
fillColor: UIColor(
|
|
1036
1046
|
red: 202.0 / 255.0,
|
|
@@ -1049,6 +1059,63 @@ final class NativeListView: UIView {
|
|
|
1049
1059
|
if sectionIndexEntries.isEmpty { finishSectionIndexInteraction(immediately: true) }
|
|
1050
1060
|
}
|
|
1051
1061
|
|
|
1062
|
+
private func updateSectionIndexAttachment() {
|
|
1063
|
+
guard config?.sectionIndexCenteredInWindow == true,
|
|
1064
|
+
!sectionIndexEntries.isEmpty,
|
|
1065
|
+
bounds.width > 0,
|
|
1066
|
+
bounds.height > 0,
|
|
1067
|
+
isVisibleInHierarchy,
|
|
1068
|
+
let window else {
|
|
1069
|
+
attachSectionIndexToList()
|
|
1070
|
+
return
|
|
1071
|
+
}
|
|
1072
|
+
attachSectionIndex(to: window)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
private var isVisibleInHierarchy: Bool {
|
|
1076
|
+
var current: UIView? = self
|
|
1077
|
+
while let view = current {
|
|
1078
|
+
if view.isHidden || view.alpha <= 0.01 { return false }
|
|
1079
|
+
current = view.superview
|
|
1080
|
+
}
|
|
1081
|
+
return true
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
private func attachSectionIndexToList() {
|
|
1085
|
+
guard sectionIndexView.superview !== self || sectionIndexLayoutConstraints.isEmpty else { return }
|
|
1086
|
+
NSLayoutConstraint.deactivate(sectionIndexLayoutConstraints)
|
|
1087
|
+
sectionIndexWindowHeightConstraint = nil
|
|
1088
|
+
sectionIndexView.removeFromSuperview()
|
|
1089
|
+
addSubview(sectionIndexView)
|
|
1090
|
+
sectionIndexLayoutConstraints = [
|
|
1091
|
+
sectionIndexView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
|
|
1092
|
+
sectionIndexView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
|
|
1093
|
+
sectionIndexView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
|
|
1094
|
+
sectionIndexView.widthAnchor.constraint(equalToConstant: Self.sectionIndexRailWidth),
|
|
1095
|
+
]
|
|
1096
|
+
NSLayoutConstraint.activate(sectionIndexLayoutConstraints)
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
private func attachSectionIndex(to window: UIWindow) {
|
|
1100
|
+
let railHeight = sectionIndexView.preferredHeight(constrainedTo: window.bounds.height)
|
|
1101
|
+
if sectionIndexView.superview === window {
|
|
1102
|
+
sectionIndexWindowHeightConstraint?.constant = railHeight
|
|
1103
|
+
return
|
|
1104
|
+
}
|
|
1105
|
+
NSLayoutConstraint.deactivate(sectionIndexLayoutConstraints)
|
|
1106
|
+
sectionIndexView.removeFromSuperview()
|
|
1107
|
+
window.addSubview(sectionIndexView)
|
|
1108
|
+
let heightConstraint = sectionIndexView.heightAnchor.constraint(equalToConstant: railHeight)
|
|
1109
|
+
sectionIndexWindowHeightConstraint = heightConstraint
|
|
1110
|
+
sectionIndexLayoutConstraints = [
|
|
1111
|
+
sectionIndexView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
|
|
1112
|
+
sectionIndexView.centerYAnchor.constraint(equalTo: window.centerYAnchor),
|
|
1113
|
+
sectionIndexView.widthAnchor.constraint(equalToConstant: Self.sectionIndexRailWidth),
|
|
1114
|
+
heightConstraint,
|
|
1115
|
+
]
|
|
1116
|
+
NSLayoutConstraint.activate(sectionIndexLayoutConstraints)
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1052
1119
|
private func selectSectionIndex(_ index: Int, interacting: Bool) {
|
|
1053
1120
|
guard let entry = sectionIndexEntries[safe: index] else { return }
|
|
1054
1121
|
let changed = sectionIndexView.activeIndex != index
|
|
@@ -2156,7 +2223,6 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
2156
2223
|
private var textColor: UIColor = .secondaryLabel
|
|
2157
2224
|
private var activeColor: UIColor = .tintColor
|
|
2158
2225
|
private var activeTextColor: UIColor = .white
|
|
2159
|
-
private var centeredInWindow = false
|
|
2160
2226
|
private var lastTouchIndex: Int?
|
|
2161
2227
|
private(set) var activeIndex: Int?
|
|
2162
2228
|
|
|
@@ -2197,14 +2263,12 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
2197
2263
|
titles: [String],
|
|
2198
2264
|
textColor: UIColor,
|
|
2199
2265
|
activeColor: UIColor,
|
|
2200
|
-
activeTextColor: UIColor
|
|
2201
|
-
centeredInWindow: Bool
|
|
2266
|
+
activeTextColor: UIColor
|
|
2202
2267
|
) {
|
|
2203
2268
|
self.titles = titles
|
|
2204
2269
|
self.textColor = textColor
|
|
2205
2270
|
self.activeColor = activeColor
|
|
2206
2271
|
self.activeTextColor = activeTextColor
|
|
2207
|
-
self.centeredInWindow = centeredInWindow
|
|
2208
2272
|
labels.forEach { $0.removeFromSuperview() }
|
|
2209
2273
|
labels = titles.map { title in
|
|
2210
2274
|
let label = UILabel()
|
|
@@ -2313,20 +2377,15 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
2313
2377
|
entryCenterY(index: index, metrics: indexMetrics(count: titles.count))
|
|
2314
2378
|
}
|
|
2315
2379
|
|
|
2380
|
+
func preferredHeight(constrainedTo maximumHeight: CGFloat) -> CGFloat {
|
|
2381
|
+
min(maximumHeight, Self.edgePadding * 2 + Self.labelSpacing * CGFloat(titles.count))
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2316
2384
|
private func indexMetrics(count: Int) -> (originY: CGFloat, trackHeight: CGFloat) {
|
|
2317
2385
|
guard count > 0 else { return (bounds.midY, 0) }
|
|
2318
2386
|
let availableHeight = max(0, bounds.height - Self.edgePadding * 2)
|
|
2319
2387
|
let trackHeight = min(availableHeight, Self.labelSpacing * CGFloat(count))
|
|
2320
|
-
|
|
2321
|
-
guard centeredInWindow, let window else { return (centeredOriginY, trackHeight) }
|
|
2322
|
-
let windowCenterY = window.safeAreaLayoutGuide.layoutFrame.midY
|
|
2323
|
-
let localCenterY = convert(CGPoint(x: 0, y: windowCenterY), from: window).y
|
|
2324
|
-
return (
|
|
2325
|
-
(localCenterY - trackHeight / 2).clamped(
|
|
2326
|
-
to: Self.edgePadding...max(Self.edgePadding, bounds.height - Self.edgePadding - trackHeight)
|
|
2327
|
-
),
|
|
2328
|
-
trackHeight
|
|
2329
|
-
)
|
|
2388
|
+
return ((bounds.height - trackHeight) / 2, trackHeight)
|
|
2330
2389
|
}
|
|
2331
2390
|
|
|
2332
2391
|
private func entryCenterY(
|
|
@@ -10,6 +10,7 @@ const SECTION_INDEX_RAIL_WIDTH = 32;
|
|
|
10
10
|
const SECTION_INDEX_EDGE_PADDING = 8;
|
|
11
11
|
const SECTION_INDEX_LABEL_SPACING = 16;
|
|
12
12
|
const SECTION_INDEX_MIN_HEIGHT = 120;
|
|
13
|
+
const SECTION_INDEX_WINDOW_Z_INDEX = 100_000;
|
|
13
14
|
const DEFAULT_VIEWPORT_WIDTH = 320;
|
|
14
15
|
const DEFAULT_VIEWPORT_HEIGHT = 640;
|
|
15
16
|
const OVERSCAN_VIEWPORTS = 1;
|
|
@@ -467,12 +468,12 @@ export const WEB_LIST_CSS = `
|
|
|
467
468
|
.ok-native-list-item[data-native-list-reorderable="true"]>.ok-native-list-row{cursor:grab}
|
|
468
469
|
.ok-native-list-root[data-native-list-dragging="true"] .ok-native-list-row{cursor:grabbing}
|
|
469
470
|
.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}}
|
|
470
|
-
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row{overflow:hidden;border-radius:12px;background:var(--nl-
|
|
471
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row{overflow:hidden;border-radius:12px;background:var(--nl-pressed)}
|
|
471
472
|
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row>*{visibility:hidden}
|
|
472
|
-
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group{border-color:transparent;background:var(--nl-
|
|
473
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group{border-color:transparent;background:var(--nl-pressed)}
|
|
473
474
|
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group>*{visibility:hidden}
|
|
474
475
|
.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}
|
|
475
|
-
.ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-
|
|
476
|
+
.ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-pressed);cursor:grabbing}
|
|
476
477
|
.ok-native-list-reorder-preview[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
|
|
477
478
|
.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}
|
|
478
479
|
.ok-native-list-item[data-separator="true"]>.ok-native-list-row{border-bottom:1px solid var(--nl-separator)}
|
|
@@ -504,7 +505,7 @@ export const WEB_LIST_CSS = `
|
|
|
504
505
|
.ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
|
|
505
506
|
.ok-native-list-row.ok-native-list-market-skeleton{padding:12px 20px;gap:0}.ok-native-list-skeleton-left{display:flex;align-items:center;gap:12px;flex:1}.ok-native-list-skeleton-text{display:flex;flex-direction:column;gap:4px}.ok-native-list-skeleton-right{display:flex;align-items:center;gap:8px}.ok-native-list-skeleton-mark{display:block;flex-shrink:0;border-radius:8px;animation:ok-native-list-skeleton 1.5s linear infinite alternate}@keyframes ok-native-list-skeleton{from{background-color:var(--nl-skeleton-base)}to{background-color:var(--nl-skeleton-highlight)}}.ok-native-list-market-spinner{display:block;width:20px;height:20px;flex-shrink:0;color:var(--nl-icon);animation:ok-native-list-spin .75s linear infinite}
|
|
506
507
|
@media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner,.ok-native-list-market-spinner{animation:none}.ok-native-list-skeleton-mark{animation:none;background:var(--nl-skeleton-base)}}
|
|
507
|
-
.ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport{scrollbar-width:none}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport::-webkit-scrollbar{display:none}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:15px;display:flex;width:14px;height:14px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:7px;background:transparent;color:var(--nl-disabled);font-family:inherit;font-size:10px;font-weight:400;line-height:1;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-positive);color:var(--nl-inverse-text);font-weight:500}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-positive);outline-offset:1px}
|
|
508
|
+
.ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport,.ok-native-list-viewport-frame[data-section-index-visible="true"]>.ok-native-list-viewport{scrollbar-width:none}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport::-webkit-scrollbar,.ok-native-list-viewport-frame[data-section-index-visible="true"]>.ok-native-list-viewport::-webkit-scrollbar{display:none}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer;font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:15px;display:flex;width:14px;height:14px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:7px;background:transparent;color:var(--nl-disabled);font-family:inherit;font-size:10px;font-weight:400;line-height:1;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-positive);color:var(--nl-inverse-text);font-weight:500}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-positive);outline-offset:1px}
|
|
508
509
|
.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)}
|
|
509
510
|
.ok-native-list-warning{height:auto;display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:14px 12px;border-top:1px solid;border-bottom:1px solid;box-sizing:border-box;cursor:default}.ok-native-list-warning-title,.ok-native-list-warning-message{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}.ok-native-list-warning-title{font-weight:500;color:var(--nl-primary)}.ok-native-list-warning-message{font-weight:400;color:var(--nl-secondary)}
|
|
510
511
|
.ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
|
|
@@ -2257,6 +2258,7 @@ export class NativeListWebEngine {
|
|
|
2257
2258
|
disposeWebImageRetries(this.root);
|
|
2258
2259
|
this.pool.forEach(disposeWebImageRetries);
|
|
2259
2260
|
this.root.remove();
|
|
2261
|
+
this.indexRail.remove();
|
|
2260
2262
|
this.hideReorderPreview();
|
|
2261
2263
|
this.reorderPreview.remove();
|
|
2262
2264
|
if (host) host.style.position = this.previousHostPosition;
|
|
@@ -2308,6 +2310,7 @@ export class NativeListWebEngine {
|
|
|
2308
2310
|
Object.entries(values).forEach(([name, value]) => {
|
|
2309
2311
|
if (!value) return;
|
|
2310
2312
|
this.root.style.setProperty(name, value);
|
|
2313
|
+
this.indexRail.style.setProperty(name, value);
|
|
2311
2314
|
this.reorderPreview.style.setProperty(name, value);
|
|
2312
2315
|
});
|
|
2313
2316
|
}
|
|
@@ -2315,14 +2318,12 @@ export class NativeListWebEngine {
|
|
|
2315
2318
|
if (this.destroyed) return;
|
|
2316
2319
|
const viewportWidth = this.viewport.clientWidth;
|
|
2317
2320
|
const viewportHeight = this.snapshot.layout.orientation === 'horizontal' ? this.viewport.clientHeight : this.verticalScrollMetrics().viewportLength;
|
|
2321
|
+
const stickyInset = this.snapshot.layout.orientation === 'horizontal' ? 0 : this.collapsiblePagerInsets().sticky;
|
|
2318
2322
|
if (this.snapshot.layout.orientation !== 'horizontal') {
|
|
2319
|
-
const stickyInset = this.collapsiblePagerInsets().sticky;
|
|
2320
2323
|
this.sticky.style.top = String(stickyInset) + 'px';
|
|
2321
|
-
this.indexRail.style.top = String(stickyInset) + 'px';
|
|
2322
2324
|
this.refreshIndicator.style.top = String(stickyInset + 8) + 'px';
|
|
2323
2325
|
} else {
|
|
2324
2326
|
this.sticky.style.top = '0px';
|
|
2325
|
-
this.indexRail.style.top = '0px';
|
|
2326
2327
|
this.refreshIndicator.style.top = '8px';
|
|
2327
2328
|
}
|
|
2328
2329
|
if (this.lastViewportWidth >= 0 && (viewportWidth !== this.lastViewportWidth || viewportHeight !== this.lastViewportHeight)) {
|
|
@@ -2342,7 +2343,7 @@ export class NativeListWebEngine {
|
|
|
2342
2343
|
this.layout = computeWebListLayout(measuredSnapshot, viewportWidth, viewportHeight, this.reorderCompactKey);
|
|
2343
2344
|
this.content.style.width = String(this.layout.contentWidth) + 'px';
|
|
2344
2345
|
this.content.style.height = String(this.layout.contentHeight) + 'px';
|
|
2345
|
-
this.renderSectionIndex(this.viewport.clientHeight <= 0 ? DEFAULT_VIEWPORT_HEIGHT : Math.max(1, viewportHeight));
|
|
2346
|
+
this.renderSectionIndex(this.viewport.clientHeight <= 0 ? DEFAULT_VIEWPORT_HEIGHT : Math.max(1, viewportHeight), stickyInset);
|
|
2346
2347
|
if (previousHorizontal !== this.layout.horizontal) {
|
|
2347
2348
|
this.viewport.scrollLeft = 0;
|
|
2348
2349
|
this.viewport.scrollTop = 0;
|
|
@@ -2570,32 +2571,54 @@ export class NativeListWebEngine {
|
|
|
2570
2571
|
sectionIndexMetrics(viewportHeight) {
|
|
2571
2572
|
const availableHeight = Math.max(0, viewportHeight - SECTION_INDEX_EDGE_PADDING * 2);
|
|
2572
2573
|
const trackHeight = Math.min(availableHeight, SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length);
|
|
2573
|
-
const centeredOriginY = (viewportHeight - trackHeight) / 2;
|
|
2574
|
-
if (!this.snapshot.capabilities?.sectionIndex?.centeredInWindow) {
|
|
2575
|
-
return {
|
|
2576
|
-
originY: centeredOriginY,
|
|
2577
|
-
trackHeight
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
const view = this.document.defaultView;
|
|
2581
|
-
if (!view) return {
|
|
2582
|
-
originY: centeredOriginY,
|
|
2583
|
-
trackHeight
|
|
2584
|
-
};
|
|
2585
|
-
const visualViewport = view.visualViewport;
|
|
2586
|
-
const windowCenterY = visualViewport ? visualViewport.offsetTop + visualViewport.height / 2 : view.innerHeight / 2;
|
|
2587
|
-
const railTop = this.viewportFrame.getBoundingClientRect().top + (Number.parseFloat(this.indexRail.style.top) || 0);
|
|
2588
|
-
const minOriginY = SECTION_INDEX_EDGE_PADDING;
|
|
2589
|
-
const maxOriginY = Math.max(minOriginY, viewportHeight - SECTION_INDEX_EDGE_PADDING - trackHeight);
|
|
2590
2574
|
return {
|
|
2591
|
-
originY:
|
|
2575
|
+
originY: (viewportHeight - trackHeight) / 2,
|
|
2592
2576
|
trackHeight
|
|
2593
2577
|
};
|
|
2594
2578
|
}
|
|
2595
|
-
|
|
2579
|
+
configureSectionIndexRail(viewportHeight, stickyInset, windowCentered) {
|
|
2580
|
+
const view = this.document.defaultView;
|
|
2581
|
+
const direction = view?.getComputedStyle(this.viewportFrame).direction === 'rtl' ? 'rtl' : 'ltr';
|
|
2582
|
+
this.indexRail.style.direction = direction;
|
|
2583
|
+
if (!windowCentered) {
|
|
2584
|
+
if (this.indexRail.parentElement !== this.viewportFrame) {
|
|
2585
|
+
this.viewportFrame.appendChild(this.indexRail);
|
|
2586
|
+
}
|
|
2587
|
+
this.indexRail.style.removeProperty('position');
|
|
2588
|
+
this.indexRail.style.removeProperty('left');
|
|
2589
|
+
this.indexRail.style.removeProperty('right');
|
|
2590
|
+
this.indexRail.style.removeProperty('height');
|
|
2591
|
+
this.indexRail.style.removeProperty('z-index');
|
|
2592
|
+
this.indexRail.style.insetInlineEnd = '0px';
|
|
2593
|
+
this.indexRail.style.top = String(stickyInset) + 'px';
|
|
2594
|
+
this.indexRail.style.bottom = '0px';
|
|
2595
|
+
return viewportHeight;
|
|
2596
|
+
}
|
|
2597
|
+
if (!view) return viewportHeight;
|
|
2598
|
+
const visualViewport = view.visualViewport;
|
|
2599
|
+
const windowTop = visualViewport?.offsetTop ?? 0;
|
|
2600
|
+
const windowHeight = visualViewport?.height ?? view.innerHeight;
|
|
2601
|
+
const railHeight = Math.min(windowHeight, SECTION_INDEX_EDGE_PADDING * 2 + SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length);
|
|
2602
|
+
const frame = this.viewportFrame.getBoundingClientRect();
|
|
2603
|
+
if (this.indexRail.parentElement !== this.document.body) {
|
|
2604
|
+
this.document.body.appendChild(this.indexRail);
|
|
2605
|
+
}
|
|
2606
|
+
this.indexRail.style.position = 'fixed';
|
|
2607
|
+
this.indexRail.style.removeProperty('left');
|
|
2608
|
+
this.indexRail.style.removeProperty('right');
|
|
2609
|
+
this.indexRail.style.insetInlineEnd = String(Math.max(0, direction === 'rtl' ? frame.left : view.innerWidth - frame.right)) + 'px';
|
|
2610
|
+
this.indexRail.style.top = String(windowTop + (windowHeight - railHeight) / 2) + 'px';
|
|
2611
|
+
this.indexRail.style.bottom = 'auto';
|
|
2612
|
+
this.indexRail.style.height = String(railHeight) + 'px';
|
|
2613
|
+
this.indexRail.style.zIndex = String(SECTION_INDEX_WINDOW_Z_INDEX);
|
|
2614
|
+
return railHeight;
|
|
2615
|
+
}
|
|
2616
|
+
renderSectionIndex(viewportHeight, stickyInset) {
|
|
2596
2617
|
this.indexRail.replaceChildren();
|
|
2597
2618
|
if (!sectionIndexEnabled(this.snapshot)) {
|
|
2598
2619
|
this.sectionIndexEntries = [];
|
|
2620
|
+
this.configureSectionIndexRail(viewportHeight, stickyInset, false);
|
|
2621
|
+
setData(this.viewportFrame, 'sectionIndexVisible', false);
|
|
2599
2622
|
this.indexRail.hidden = true;
|
|
2600
2623
|
return;
|
|
2601
2624
|
}
|
|
@@ -2605,12 +2628,16 @@ export class NativeListWebEngine {
|
|
|
2605
2628
|
position
|
|
2606
2629
|
}] : []);
|
|
2607
2630
|
if (this.sectionIndexEntries.length === 0 || viewportHeight < SECTION_INDEX_MIN_HEIGHT) {
|
|
2631
|
+
this.configureSectionIndexRail(viewportHeight, stickyInset, false);
|
|
2632
|
+
setData(this.viewportFrame, 'sectionIndexVisible', false);
|
|
2608
2633
|
this.indexRail.hidden = true;
|
|
2609
2634
|
return;
|
|
2610
2635
|
}
|
|
2611
|
-
const
|
|
2636
|
+
const windowCentered = this.snapshot.capabilities?.sectionIndex?.centeredInWindow === true && this.viewport.clientHeight > 0;
|
|
2637
|
+
const indexLayoutHeight = this.configureSectionIndexRail(viewportHeight, stickyInset, windowCentered);
|
|
2638
|
+
const visibleEntryIndices = this.sectionIndexVisibleEntryIndices(indexLayoutHeight);
|
|
2612
2639
|
setData(this.indexRail, 'compact', visibleEntryIndices.length < this.sectionIndexEntries.length);
|
|
2613
|
-
const metrics = this.sectionIndexMetrics(
|
|
2640
|
+
const metrics = this.sectionIndexMetrics(indexLayoutHeight);
|
|
2614
2641
|
const visibleTrackHeight = Math.min(metrics.trackHeight, SECTION_INDEX_LABEL_SPACING * visibleEntryIndices.length);
|
|
2615
2642
|
const visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2;
|
|
2616
2643
|
const fragment = this.document.createDocumentFragment();
|
|
@@ -2628,6 +2655,7 @@ export class NativeListWebEngine {
|
|
|
2628
2655
|
});
|
|
2629
2656
|
this.indexRail.appendChild(fragment);
|
|
2630
2657
|
this.indexRail.hidden = this.indexRail.childElementCount === 0;
|
|
2658
|
+
setData(this.viewportFrame, 'sectionIndexVisible', !this.indexRail.hidden);
|
|
2631
2659
|
}
|
|
2632
2660
|
updateVisibleSelection() {
|
|
2633
2661
|
const update = (element, row) => {
|
|
@@ -154,6 +154,7 @@ export declare class NativeListWebEngine {
|
|
|
154
154
|
private renderFooter;
|
|
155
155
|
private sectionIndexVisibleEntryIndices;
|
|
156
156
|
private sectionIndexMetrics;
|
|
157
|
+
private configureSectionIndexRail;
|
|
157
158
|
private renderSectionIndex;
|
|
158
159
|
private updateVisibleSelection;
|
|
159
160
|
private updateVisibleState;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-native-list",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.124",
|
|
4
4
|
"description": "Template-driven native RecyclerView and UICollectionView for React Native",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"nitrogen": "nitrogen",
|
|
39
39
|
"typecheck": "tsc -b",
|
|
40
40
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
41
|
-
"test": "jest --runInBand",
|
|
41
|
+
"test": "jest --runInBand && node --test src/__tests__/selector-parity.cjs",
|
|
42
42
|
"release": "yarn prepare && npm whoami && npm publish --access public"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"typescript": "^5.9.2"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
-
"@onekeyfe/react-native-image": "3.0.
|
|
87
|
-
"@onekeyfe/react-native-native-logger": "3.0.
|
|
86
|
+
"@onekeyfe/react-native-image": "3.0.124",
|
|
87
|
+
"@onekeyfe/react-native-native-logger": "3.0.124",
|
|
88
88
|
"react": "*",
|
|
89
89
|
"react-native": "*",
|
|
90
90
|
"react-native-nitro-modules": "0.37.0"
|
|
@@ -51,6 +51,7 @@ const SECTION_INDEX_RAIL_WIDTH = 32;
|
|
|
51
51
|
const SECTION_INDEX_EDGE_PADDING = 8;
|
|
52
52
|
const SECTION_INDEX_LABEL_SPACING = 16;
|
|
53
53
|
const SECTION_INDEX_MIN_HEIGHT = 120;
|
|
54
|
+
const SECTION_INDEX_WINDOW_Z_INDEX = 100_000;
|
|
54
55
|
const DEFAULT_VIEWPORT_WIDTH = 320;
|
|
55
56
|
const DEFAULT_VIEWPORT_HEIGHT = 640;
|
|
56
57
|
const OVERSCAN_VIEWPORTS = 1;
|
|
@@ -885,12 +886,12 @@ export const WEB_LIST_CSS = `
|
|
|
885
886
|
.ok-native-list-item[data-native-list-reorderable="true"]>.ok-native-list-row{cursor:grab}
|
|
886
887
|
.ok-native-list-root[data-native-list-dragging="true"] .ok-native-list-row{cursor:grabbing}
|
|
887
888
|
.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}}
|
|
888
|
-
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row{overflow:hidden;border-radius:12px;background:var(--nl-
|
|
889
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row{overflow:hidden;border-radius:12px;background:var(--nl-pressed)}
|
|
889
890
|
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row>*{visibility:hidden}
|
|
890
|
-
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group{border-color:transparent;background:var(--nl-
|
|
891
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group{border-color:transparent;background:var(--nl-pressed)}
|
|
891
892
|
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group>*{visibility:hidden}
|
|
892
893
|
.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}
|
|
893
|
-
.ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-
|
|
894
|
+
.ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-pressed);cursor:grabbing}
|
|
894
895
|
.ok-native-list-reorder-preview[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
|
|
895
896
|
.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}
|
|
896
897
|
.ok-native-list-item[data-separator="true"]>.ok-native-list-row{border-bottom:1px solid var(--nl-separator)}
|
|
@@ -922,7 +923,7 @@ export const WEB_LIST_CSS = `
|
|
|
922
923
|
.ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
|
|
923
924
|
.ok-native-list-row.ok-native-list-market-skeleton{padding:12px 20px;gap:0}.ok-native-list-skeleton-left{display:flex;align-items:center;gap:12px;flex:1}.ok-native-list-skeleton-text{display:flex;flex-direction:column;gap:4px}.ok-native-list-skeleton-right{display:flex;align-items:center;gap:8px}.ok-native-list-skeleton-mark{display:block;flex-shrink:0;border-radius:8px;animation:ok-native-list-skeleton 1.5s linear infinite alternate}@keyframes ok-native-list-skeleton{from{background-color:var(--nl-skeleton-base)}to{background-color:var(--nl-skeleton-highlight)}}.ok-native-list-market-spinner{display:block;width:20px;height:20px;flex-shrink:0;color:var(--nl-icon);animation:ok-native-list-spin .75s linear infinite}
|
|
924
925
|
@media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner,.ok-native-list-market-spinner{animation:none}.ok-native-list-skeleton-mark{animation:none;background:var(--nl-skeleton-base)}}
|
|
925
|
-
.ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport{scrollbar-width:none}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport::-webkit-scrollbar{display:none}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:15px;display:flex;width:14px;height:14px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:7px;background:transparent;color:var(--nl-disabled);font-family:inherit;font-size:10px;font-weight:400;line-height:1;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-positive);color:var(--nl-inverse-text);font-weight:500}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-positive);outline-offset:1px}
|
|
926
|
+
.ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport,.ok-native-list-viewport-frame[data-section-index-visible="true"]>.ok-native-list-viewport{scrollbar-width:none}.ok-native-list-viewport-frame:has(>.ok-native-list-index-rail:not([hidden]))>.ok-native-list-viewport::-webkit-scrollbar,.ok-native-list-viewport-frame[data-section-index-visible="true"]>.ok-native-list-viewport::-webkit-scrollbar{display:none}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer;font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:15px;display:flex;width:14px;height:14px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:7px;background:transparent;color:var(--nl-disabled);font-family:inherit;font-size:10px;font-weight:400;line-height:1;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-positive);color:var(--nl-inverse-text);font-weight:500}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-positive);outline-offset:1px}
|
|
926
927
|
.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)}
|
|
927
928
|
.ok-native-list-warning{height:auto;display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:14px 12px;border-top:1px solid;border-bottom:1px solid;box-sizing:border-box;cursor:default}.ok-native-list-warning-title,.ok-native-list-warning-message{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}.ok-native-list-warning-title{font-weight:500;color:var(--nl-primary)}.ok-native-list-warning-message{font-weight:400;color:var(--nl-secondary)}
|
|
928
929
|
.ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
|
|
@@ -3887,6 +3888,7 @@ export class NativeListWebEngine {
|
|
|
3887
3888
|
disposeWebImageRetries(this.root);
|
|
3888
3889
|
this.pool.forEach(disposeWebImageRetries);
|
|
3889
3890
|
this.root.remove();
|
|
3891
|
+
this.indexRail.remove();
|
|
3890
3892
|
this.hideReorderPreview();
|
|
3891
3893
|
this.reorderPreview.remove();
|
|
3892
3894
|
if (host) host.style.position = this.previousHostPosition;
|
|
@@ -3945,6 +3947,7 @@ export class NativeListWebEngine {
|
|
|
3945
3947
|
Object.entries(values).forEach(([name, value]) => {
|
|
3946
3948
|
if (!value) return;
|
|
3947
3949
|
this.root.style.setProperty(name, value);
|
|
3950
|
+
this.indexRail.style.setProperty(name, value);
|
|
3948
3951
|
this.reorderPreview.style.setProperty(name, value);
|
|
3949
3952
|
});
|
|
3950
3953
|
}
|
|
@@ -3956,14 +3959,15 @@ export class NativeListWebEngine {
|
|
|
3956
3959
|
this.snapshot.layout.orientation === 'horizontal'
|
|
3957
3960
|
? this.viewport.clientHeight
|
|
3958
3961
|
: this.verticalScrollMetrics().viewportLength;
|
|
3962
|
+
const stickyInset =
|
|
3963
|
+
this.snapshot.layout.orientation === 'horizontal'
|
|
3964
|
+
? 0
|
|
3965
|
+
: this.collapsiblePagerInsets().sticky;
|
|
3959
3966
|
if (this.snapshot.layout.orientation !== 'horizontal') {
|
|
3960
|
-
const stickyInset = this.collapsiblePagerInsets().sticky;
|
|
3961
3967
|
this.sticky.style.top = String(stickyInset) + 'px';
|
|
3962
|
-
this.indexRail.style.top = String(stickyInset) + 'px';
|
|
3963
3968
|
this.refreshIndicator.style.top = String(stickyInset + 8) + 'px';
|
|
3964
3969
|
} else {
|
|
3965
3970
|
this.sticky.style.top = '0px';
|
|
3966
|
-
this.indexRail.style.top = '0px';
|
|
3967
3971
|
this.refreshIndicator.style.top = '8px';
|
|
3968
3972
|
}
|
|
3969
3973
|
if (
|
|
@@ -3999,7 +4003,8 @@ export class NativeListWebEngine {
|
|
|
3999
4003
|
this.renderSectionIndex(
|
|
4000
4004
|
this.viewport.clientHeight <= 0
|
|
4001
4005
|
? DEFAULT_VIEWPORT_HEIGHT
|
|
4002
|
-
: Math.max(1, viewportHeight)
|
|
4006
|
+
: Math.max(1, viewportHeight),
|
|
4007
|
+
stickyInset
|
|
4003
4008
|
);
|
|
4004
4009
|
if (previousHorizontal !== this.layout.horizontal) {
|
|
4005
4010
|
this.viewport.scrollLeft = 0;
|
|
@@ -4333,37 +4338,75 @@ export class NativeListWebEngine {
|
|
|
4333
4338
|
availableHeight,
|
|
4334
4339
|
SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length
|
|
4335
4340
|
);
|
|
4336
|
-
const centeredOriginY = (viewportHeight - trackHeight) / 2;
|
|
4337
|
-
if (!this.snapshot.capabilities?.sectionIndex?.centeredInWindow) {
|
|
4338
|
-
return { originY: centeredOriginY, trackHeight };
|
|
4339
|
-
}
|
|
4340
|
-
const view = this.document.defaultView;
|
|
4341
|
-
if (!view) return { originY: centeredOriginY, trackHeight };
|
|
4342
|
-
const visualViewport = view.visualViewport;
|
|
4343
|
-
const windowCenterY = visualViewport
|
|
4344
|
-
? visualViewport.offsetTop + visualViewport.height / 2
|
|
4345
|
-
: view.innerHeight / 2;
|
|
4346
|
-
const railTop =
|
|
4347
|
-
this.viewportFrame.getBoundingClientRect().top +
|
|
4348
|
-
(Number.parseFloat(this.indexRail.style.top) || 0);
|
|
4349
|
-
const minOriginY = SECTION_INDEX_EDGE_PADDING;
|
|
4350
|
-
const maxOriginY = Math.max(
|
|
4351
|
-
minOriginY,
|
|
4352
|
-
viewportHeight - SECTION_INDEX_EDGE_PADDING - trackHeight
|
|
4353
|
-
);
|
|
4354
4341
|
return {
|
|
4355
|
-
originY:
|
|
4356
|
-
maxOriginY,
|
|
4357
|
-
Math.max(minOriginY, windowCenterY - railTop - trackHeight / 2)
|
|
4358
|
-
),
|
|
4342
|
+
originY: (viewportHeight - trackHeight) / 2,
|
|
4359
4343
|
trackHeight,
|
|
4360
4344
|
};
|
|
4361
4345
|
}
|
|
4362
4346
|
|
|
4363
|
-
private
|
|
4347
|
+
private configureSectionIndexRail(
|
|
4348
|
+
viewportHeight: number,
|
|
4349
|
+
stickyInset: number,
|
|
4350
|
+
windowCentered: boolean
|
|
4351
|
+
): number {
|
|
4352
|
+
const view = this.document.defaultView;
|
|
4353
|
+
const direction =
|
|
4354
|
+
view?.getComputedStyle(this.viewportFrame).direction === 'rtl'
|
|
4355
|
+
? 'rtl'
|
|
4356
|
+
: 'ltr';
|
|
4357
|
+
this.indexRail.style.direction = direction;
|
|
4358
|
+
if (!windowCentered) {
|
|
4359
|
+
if (this.indexRail.parentElement !== this.viewportFrame) {
|
|
4360
|
+
this.viewportFrame.appendChild(this.indexRail);
|
|
4361
|
+
}
|
|
4362
|
+
this.indexRail.style.removeProperty('position');
|
|
4363
|
+
this.indexRail.style.removeProperty('left');
|
|
4364
|
+
this.indexRail.style.removeProperty('right');
|
|
4365
|
+
this.indexRail.style.removeProperty('height');
|
|
4366
|
+
this.indexRail.style.removeProperty('z-index');
|
|
4367
|
+
this.indexRail.style.insetInlineEnd = '0px';
|
|
4368
|
+
this.indexRail.style.top = String(stickyInset) + 'px';
|
|
4369
|
+
this.indexRail.style.bottom = '0px';
|
|
4370
|
+
return viewportHeight;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
if (!view) return viewportHeight;
|
|
4374
|
+
const visualViewport = view.visualViewport;
|
|
4375
|
+
const windowTop = visualViewport?.offsetTop ?? 0;
|
|
4376
|
+
const windowHeight = visualViewport?.height ?? view.innerHeight;
|
|
4377
|
+
const railHeight = Math.min(
|
|
4378
|
+
windowHeight,
|
|
4379
|
+
SECTION_INDEX_EDGE_PADDING * 2 +
|
|
4380
|
+
SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length
|
|
4381
|
+
);
|
|
4382
|
+
const frame = this.viewportFrame.getBoundingClientRect();
|
|
4383
|
+
if (this.indexRail.parentElement !== this.document.body) {
|
|
4384
|
+
this.document.body.appendChild(this.indexRail);
|
|
4385
|
+
}
|
|
4386
|
+
this.indexRail.style.position = 'fixed';
|
|
4387
|
+
this.indexRail.style.removeProperty('left');
|
|
4388
|
+
this.indexRail.style.removeProperty('right');
|
|
4389
|
+
this.indexRail.style.insetInlineEnd =
|
|
4390
|
+
String(
|
|
4391
|
+
Math.max(
|
|
4392
|
+
0,
|
|
4393
|
+
direction === 'rtl' ? frame.left : view.innerWidth - frame.right
|
|
4394
|
+
)
|
|
4395
|
+
) + 'px';
|
|
4396
|
+
this.indexRail.style.top =
|
|
4397
|
+
String(windowTop + (windowHeight - railHeight) / 2) + 'px';
|
|
4398
|
+
this.indexRail.style.bottom = 'auto';
|
|
4399
|
+
this.indexRail.style.height = String(railHeight) + 'px';
|
|
4400
|
+
this.indexRail.style.zIndex = String(SECTION_INDEX_WINDOW_Z_INDEX);
|
|
4401
|
+
return railHeight;
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
private renderSectionIndex(viewportHeight: number, stickyInset: number) {
|
|
4364
4405
|
this.indexRail.replaceChildren();
|
|
4365
4406
|
if (!sectionIndexEnabled(this.snapshot)) {
|
|
4366
4407
|
this.sectionIndexEntries = [];
|
|
4408
|
+
this.configureSectionIndexRail(viewportHeight, stickyInset, false);
|
|
4409
|
+
setData(this.viewportFrame, 'sectionIndexVisible', false);
|
|
4367
4410
|
this.indexRail.hidden = true;
|
|
4368
4411
|
return;
|
|
4369
4412
|
}
|
|
@@ -4376,17 +4419,27 @@ export class NativeListWebEngine {
|
|
|
4376
4419
|
this.sectionIndexEntries.length === 0 ||
|
|
4377
4420
|
viewportHeight < SECTION_INDEX_MIN_HEIGHT
|
|
4378
4421
|
) {
|
|
4422
|
+
this.configureSectionIndexRail(viewportHeight, stickyInset, false);
|
|
4423
|
+
setData(this.viewportFrame, 'sectionIndexVisible', false);
|
|
4379
4424
|
this.indexRail.hidden = true;
|
|
4380
4425
|
return;
|
|
4381
4426
|
}
|
|
4427
|
+
const windowCentered =
|
|
4428
|
+
this.snapshot.capabilities?.sectionIndex?.centeredInWindow === true &&
|
|
4429
|
+
this.viewport.clientHeight > 0;
|
|
4430
|
+
const indexLayoutHeight = this.configureSectionIndexRail(
|
|
4431
|
+
viewportHeight,
|
|
4432
|
+
stickyInset,
|
|
4433
|
+
windowCentered
|
|
4434
|
+
);
|
|
4382
4435
|
const visibleEntryIndices =
|
|
4383
|
-
this.sectionIndexVisibleEntryIndices(
|
|
4436
|
+
this.sectionIndexVisibleEntryIndices(indexLayoutHeight);
|
|
4384
4437
|
setData(
|
|
4385
4438
|
this.indexRail,
|
|
4386
4439
|
'compact',
|
|
4387
4440
|
visibleEntryIndices.length < this.sectionIndexEntries.length
|
|
4388
4441
|
);
|
|
4389
|
-
const metrics = this.sectionIndexMetrics(
|
|
4442
|
+
const metrics = this.sectionIndexMetrics(indexLayoutHeight);
|
|
4390
4443
|
const visibleTrackHeight = Math.min(
|
|
4391
4444
|
metrics.trackHeight,
|
|
4392
4445
|
SECTION_INDEX_LABEL_SPACING * visibleEntryIndices.length
|
|
@@ -4418,6 +4471,7 @@ export class NativeListWebEngine {
|
|
|
4418
4471
|
});
|
|
4419
4472
|
this.indexRail.appendChild(fragment);
|
|
4420
4473
|
this.indexRail.hidden = this.indexRail.childElementCount === 0;
|
|
4474
|
+
setData(this.viewportFrame, 'sectionIndexVisible', !this.indexRail.hidden);
|
|
4421
4475
|
}
|
|
4422
4476
|
|
|
4423
4477
|
private updateVisibleSelection() {
|