@onekeyfe/react-native-pager-view 3.0.157-alpha.256 → 3.0.157-alpha.258
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 +51 -4
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +254 -8
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +24 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +573 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +130 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
- package/android/src/main/jni/CMakeLists.txt +19 -0
- package/android/src/main/jni/pagerview.h +11 -0
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +1 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +38 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h +8 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp +15 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h +23 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerState.h +33 -0
- package/docs/SPEC.md +268 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +187 -1
- package/ios/RNCNativeScrollerComponentView.h +17 -0
- package/ios/RNCNativeScrollerComponentView.mm +479 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +346 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
- package/ios/tests/RNCNativeScrollerTests.mm +252 -0
- package/lib/module/CollapsiblePagerView.js +1 -1
- package/lib/module/NativeScroller.js +171 -11
- package/lib/module/NativeScrollerNativeComponent.ts +101 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +2 -3
- package/lib/typescript/src/NativeScroller.d.ts +15 -7
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
- package/package.json +6 -3
- package/react-native-pager-view.podspec +2 -1
- package/react-native.config.js +15 -0
- package/src/CollapsiblePagerView.tsx +4 -4
- package/src/NativeScroller.tsx +291 -14
- package/src/NativeScrollerNativeComponent.ts +101 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
package com.reactnativepagerview
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import android.view.View
|
|
5
|
+
import com.facebook.react.bridge.ReadableArray
|
|
6
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
7
|
+
import com.facebook.react.uimanager.ReactStylesDiffMap
|
|
8
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
11
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
12
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
13
|
+
import com.facebook.react.viewmanagers.RNCNativeScrollerManagerDelegate
|
|
14
|
+
import com.facebook.react.viewmanagers.RNCNativeScrollerManagerInterface
|
|
15
|
+
|
|
16
|
+
@ReactModule(name = NativeScrollerViewManager.NAME)
|
|
17
|
+
class NativeScrollerViewManager : ViewGroupManager<NativeScrollerView>(),
|
|
18
|
+
RNCNativeScrollerManagerInterface<NativeScrollerView> {
|
|
19
|
+
private val delegate: ViewManagerDelegate<NativeScrollerView> = RNCNativeScrollerManagerDelegate(this)
|
|
20
|
+
override fun getDelegate() = delegate
|
|
21
|
+
override fun getName() = NAME
|
|
22
|
+
override fun createViewInstance(context: ThemedReactContext) = NativeScrollerView(context)
|
|
23
|
+
override fun needsCustomLayoutForChildren() = false
|
|
24
|
+
override fun addView(parent: NativeScrollerView, child: View, index: Int) = parent.addReactChild(child, index)
|
|
25
|
+
override fun getChildCount(parent: NativeScrollerView) = parent.reactChildCount()
|
|
26
|
+
override fun getChildAt(parent: NativeScrollerView, index: Int) = parent.reactChildAt(index)
|
|
27
|
+
override fun removeViewAt(parent: NativeScrollerView, index: Int) {
|
|
28
|
+
require(index == 0)
|
|
29
|
+
parent.removeReactChild()
|
|
30
|
+
}
|
|
31
|
+
override fun removeAllViews(parent: NativeScrollerView) = parent.removeReactChild()
|
|
32
|
+
override fun onDropViewInstance(view: NativeScrollerView) {
|
|
33
|
+
view.viewport.stopMotion()
|
|
34
|
+
view.setFabricState(null)
|
|
35
|
+
super.onDropViewInstance(view)
|
|
36
|
+
}
|
|
37
|
+
override fun updateState(view: NativeScrollerView, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? {
|
|
38
|
+
view.setFabricState(stateWrapper)
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
override fun onAfterUpdateTransaction(view: NativeScrollerView) {
|
|
42
|
+
super.onAfterUpdateTransaction(view)
|
|
43
|
+
view.scheduleLayout()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@ReactProp(name = "contentWidth")
|
|
47
|
+
override fun setContentWidth(view: NativeScrollerView, value: Double) = view.setContentSize(width = view.dip(value))
|
|
48
|
+
@ReactProp(name = "contentHeight")
|
|
49
|
+
override fun setContentHeight(view: NativeScrollerView, value: Double) = view.setContentSize(height = view.dip(value))
|
|
50
|
+
@ReactProp(name = "scrollEnabled", defaultBoolean = true)
|
|
51
|
+
override fun setScrollEnabled(view: NativeScrollerView, value: Boolean) = view.setScrollEnabled(value)
|
|
52
|
+
@ReactProp(name = "nestedScrollEnabled", defaultBoolean = true)
|
|
53
|
+
override fun setNestedScrollEnabled(view: NativeScrollerView, value: Boolean) {
|
|
54
|
+
if (!value) view.viewport.stopMotion()
|
|
55
|
+
view.viewport.isNestedScrollingEnabled = value
|
|
56
|
+
}
|
|
57
|
+
@ReactProp(name = "showsVerticalScrollIndicator", defaultBoolean = true)
|
|
58
|
+
override fun setShowsVerticalScrollIndicator(view: NativeScrollerView, value: Boolean) {
|
|
59
|
+
view.viewport.isVerticalScrollBarEnabled = value
|
|
60
|
+
}
|
|
61
|
+
@ReactProp(name = "showsHorizontalScrollIndicator")
|
|
62
|
+
override fun setShowsHorizontalScrollIndicator(view: NativeScrollerView, value: Boolean) {
|
|
63
|
+
view.viewport.isHorizontalScrollBarEnabled = value
|
|
64
|
+
}
|
|
65
|
+
// UIKit-only properties: Android keeps the platform NestedScrollView fling/edge policy.
|
|
66
|
+
@ReactProp(name = "bounces", defaultBoolean = true)
|
|
67
|
+
override fun setBounces(view: NativeScrollerView, value: Boolean) = Unit
|
|
68
|
+
@ReactProp(name = "alwaysBounceVertical", defaultBoolean = true)
|
|
69
|
+
override fun setAlwaysBounceVertical(view: NativeScrollerView, value: Boolean) = Unit
|
|
70
|
+
@ReactProp(name = "decelerationRate", defaultDouble = 0.998)
|
|
71
|
+
override fun setDecelerationRate(view: NativeScrollerView, value: Double) = Unit
|
|
72
|
+
@ReactProp(name = "keyboardDismissMode")
|
|
73
|
+
override fun setKeyboardDismissMode(view: NativeScrollerView, value: String?) {
|
|
74
|
+
view.keyboardDismissMode = value ?: "none"
|
|
75
|
+
}
|
|
76
|
+
@ReactProp(name = "keyboardShouldPersistTaps")
|
|
77
|
+
override fun setKeyboardShouldPersistTaps(view: NativeScrollerView, value: String?) {
|
|
78
|
+
// The JS responder wrapper owns never/handled/always and TextInput exceptions.
|
|
79
|
+
view.keyboardShouldPersistTaps = value ?: "never"
|
|
80
|
+
}
|
|
81
|
+
@ReactProp(name = "scrollEventThrottle")
|
|
82
|
+
override fun setScrollEventThrottle(view: NativeScrollerView, value: Double) {
|
|
83
|
+
view.scrollEventThrottle = value.coerceAtLeast(0.0)
|
|
84
|
+
}
|
|
85
|
+
@ReactProp(name = "contentInsetTop")
|
|
86
|
+
override fun setContentInsetTop(view: NativeScrollerView, value: Double) { view.insetTop = view.dip(value).coerceAtLeast(0) }
|
|
87
|
+
@ReactProp(name = "contentInsetRight")
|
|
88
|
+
override fun setContentInsetRight(view: NativeScrollerView, value: Double) { view.insetRight = view.dip(value).coerceAtLeast(0) }
|
|
89
|
+
@ReactProp(name = "contentInsetBottom")
|
|
90
|
+
override fun setContentInsetBottom(view: NativeScrollerView, value: Double) { view.insetBottom = view.dip(value).coerceAtLeast(0) }
|
|
91
|
+
@ReactProp(name = "contentInsetLeft")
|
|
92
|
+
override fun setContentInsetLeft(view: NativeScrollerView, value: Double) { view.insetLeft = view.dip(value).coerceAtLeast(0) }
|
|
93
|
+
@ReactProp(name = "refreshEnabled")
|
|
94
|
+
override fun setRefreshEnabled(view: NativeScrollerView, value: Boolean) { view.refreshEnabled = value }
|
|
95
|
+
@ReactProp(name = "refreshing")
|
|
96
|
+
override fun setRefreshing(view: NativeScrollerView, value: Boolean) = view.setRefreshing(value)
|
|
97
|
+
@ReactProp(name = "refreshTintColor", customType = "Color")
|
|
98
|
+
override fun setRefreshTintColor(view: NativeScrollerView, value: Int?) {
|
|
99
|
+
// Android's refreshColors determines the native arc; tintColor is iOS-only.
|
|
100
|
+
}
|
|
101
|
+
@ReactProp(name = "refreshColors", customType = "ColorArray")
|
|
102
|
+
override fun setRefreshColors(view: NativeScrollerView, value: ReadableArray?) {
|
|
103
|
+
view.setRefreshColors(if (value == null) intArrayOf(Color.GRAY) else IntArray(value.size()) { value.getInt(it) })
|
|
104
|
+
}
|
|
105
|
+
@ReactProp(name = "refreshProgressBackgroundColor", customType = "Color")
|
|
106
|
+
override fun setRefreshProgressBackgroundColor(view: NativeScrollerView, value: Int?) =
|
|
107
|
+
view.setRefreshBackground(value ?: Color.WHITE)
|
|
108
|
+
@ReactProp(name = "refreshProgressViewOffset")
|
|
109
|
+
override fun setRefreshProgressViewOffset(view: NativeScrollerView, value: Double) = view.setRefreshOffset(view.dip(value))
|
|
110
|
+
@ReactProp(name = "refreshTitle")
|
|
111
|
+
override fun setRefreshTitle(view: NativeScrollerView, value: String?) = Unit
|
|
112
|
+
@ReactProp(name = "refreshTitleColor", customType = "Color")
|
|
113
|
+
override fun setRefreshTitleColor(view: NativeScrollerView, value: Int?) = Unit
|
|
114
|
+
@ReactProp(name = "refreshSize")
|
|
115
|
+
override fun setRefreshSize(view: NativeScrollerView, value: String?) = view.setRefreshSize(value)
|
|
116
|
+
|
|
117
|
+
override fun receiveCommand(view: NativeScrollerView, commandId: String, args: ReadableArray?) =
|
|
118
|
+
delegate.receiveCommand(view, commandId, args)
|
|
119
|
+
override fun setRefreshState(view: NativeScrollerView, refreshing: Boolean) = view.setRefreshing(refreshing)
|
|
120
|
+
override fun scrollTo(view: NativeScrollerView, x: Double, y: Double, animated: Boolean) =
|
|
121
|
+
view.scrollToPosition(view.dip(x), view.dip(y), animated)
|
|
122
|
+
override fun scrollToEnd(view: NativeScrollerView, animated: Boolean) = view.scrollToEnd(animated)
|
|
123
|
+
override fun flashScrollIndicators(view: NativeScrollerView) { view.viewport.flashIndicators() }
|
|
124
|
+
|
|
125
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Map<String, String>> =
|
|
126
|
+
listOf("Scroll", "ScrollBeginDrag", "ScrollEndDrag", "MomentumScrollBegin", "MomentumScrollEnd", "Refresh", "ContentViewportChange")
|
|
127
|
+
.associate { "top$it" to mapOf("registrationName" to "on$it") }.toMutableMap()
|
|
128
|
+
|
|
129
|
+
companion object { const val NAME = "RNCNativeScroller" }
|
|
130
|
+
}
|
|
@@ -12,6 +12,6 @@ class PagerViewPackage : ReactPackage {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
15
|
-
return listOf(PagerViewViewManager(), CollapsiblePagerViewManager())
|
|
15
|
+
return listOf(PagerViewViewManager(), CollapsiblePagerViewManager(), NativeScrollerViewManager())
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.13)
|
|
2
|
+
|
|
3
|
+
set(PAGER_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../..)
|
|
4
|
+
set(PAGER_GENERATED ${PAGER_ROOT}/android/build/generated/source/codegen/jni)
|
|
5
|
+
set(PAGER_COMMON ${PAGER_ROOT}/common/cpp)
|
|
6
|
+
file(GLOB PAGER_GENERATED_SOURCES CONFIGURE_DEPENDS
|
|
7
|
+
${PAGER_GENERATED}/*.cpp
|
|
8
|
+
${PAGER_GENERATED}/react/renderer/components/pagerview/*.cpp)
|
|
9
|
+
|
|
10
|
+
add_library(react_codegen_pagerview OBJECT
|
|
11
|
+
${PAGER_GENERATED_SOURCES}
|
|
12
|
+
${PAGER_COMMON}/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp)
|
|
13
|
+
target_include_directories(react_codegen_pagerview PUBLIC
|
|
14
|
+
${CMAKE_CURRENT_SOURCE_DIR}
|
|
15
|
+
${PAGER_COMMON}
|
|
16
|
+
${PAGER_GENERATED}
|
|
17
|
+
${PAGER_GENERATED}/react/renderer/components/pagerview)
|
|
18
|
+
target_link_libraries(react_codegen_pagerview fbjni jsi reactnative)
|
|
19
|
+
target_compile_reactnative_options(react_codegen_pagerview PRIVATE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <ReactCommon/JavaTurboModule.h>
|
|
4
|
+
#include <ReactCommon/TurboModule.h>
|
|
5
|
+
#include <jsi/jsi.h>
|
|
6
|
+
#include <react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h>
|
|
7
|
+
|
|
8
|
+
namespace facebook::react {
|
|
9
|
+
JSI_EXPORT std::shared_ptr<TurboModule> pagerview_ModuleProvider(
|
|
10
|
+
const std::string &moduleName, const JavaTurboModule::InitParams ¶ms);
|
|
11
|
+
} // namespace facebook::react
|
package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt
CHANGED
|
@@ -34,4 +34,42 @@ fun main() {
|
|
|
34
34
|
// Initial discovery must capture caller values before this pager owns an inset.
|
|
35
35
|
check(!nativeScrollerPaddingWasResetByScrollAway(0, 0, 0, 0, 230, 230, 0))
|
|
36
36
|
println("NativeScroller ScrollAway ownership: initial, repeat, measured resize, nested detach, Fabric reset, RN padding replay, and caller updates passed")
|
|
37
|
+
|
|
38
|
+
val firstOwner = Any()
|
|
39
|
+
val secondOwner = Any()
|
|
40
|
+
val alpha = NativeRefreshForegroundAlpha(0.75f)
|
|
41
|
+
check(alpha.isEmpty)
|
|
42
|
+
var current = alpha.update(0.75f, firstOwner, acquire = true)
|
|
43
|
+
check(current == 0f && alpha.drawingAlpha == 0.75f && !alpha.isEmpty)
|
|
44
|
+
current = alpha.update(current, firstOwner, acquire = true)
|
|
45
|
+
check(current == 0f && alpha.drawingAlpha == 0.75f)
|
|
46
|
+
current = alpha.update(current, secondOwner, acquire = true)
|
|
47
|
+
current = alpha.update(current, firstOwner, acquire = false)
|
|
48
|
+
check(current == 0f && !alpha.isEmpty)
|
|
49
|
+
current = alpha.update(current, secondOwner, acquire = false)
|
|
50
|
+
check(current == 0.75f && alpha.isEmpty)
|
|
51
|
+
|
|
52
|
+
current = alpha.update(current, firstOwner, acquire = true)
|
|
53
|
+
current = alpha.update(current, secondOwner, acquire = true)
|
|
54
|
+
current = alpha.update(current, secondOwner, acquire = false)
|
|
55
|
+
check(current == 0f)
|
|
56
|
+
current = alpha.update(current, firstOwner, acquire = false)
|
|
57
|
+
check(current == 0.75f && alpha.isEmpty)
|
|
58
|
+
|
|
59
|
+
current = alpha.update(current, firstOwner, acquire = true)
|
|
60
|
+
current = alpha.update(0.4f, firstOwner, acquire = true)
|
|
61
|
+
check(current == 0f && alpha.drawingAlpha == 0.4f)
|
|
62
|
+
current = alpha.update(current, firstOwner, acquire = false)
|
|
63
|
+
check(current == 0.4f && alpha.isEmpty)
|
|
64
|
+
|
|
65
|
+
val transparent = NativeRefreshForegroundAlpha(0f)
|
|
66
|
+
check(transparent.update(0f, firstOwner, acquire = true) == 0f)
|
|
67
|
+
check(!transparent.isEmpty && transparent.drawingAlpha == 0f)
|
|
68
|
+
check(transparent.update(0f, firstOwner, acquire = false) == 0f && transparent.isEmpty)
|
|
69
|
+
// RN caller offset is already in top; an absent coordinator inset is zero.
|
|
70
|
+
check(nativeRefreshForegroundTop(37, 0f) == 37f)
|
|
71
|
+
// A list publishes the sum of both actual ancestor insets; subtract it once.
|
|
72
|
+
check(nativeRefreshForegroundTop(37 + 230 + 64, 294f) == 37f)
|
|
73
|
+
check(nativeRefreshForegroundTop(-40 + 294, 294f) == -40f)
|
|
74
|
+
println("Refresh foreground policy: repeat, owner transfer/release, external alpha, transparent baseline, caller offset, and combined nested inset passed")
|
|
37
75
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h>
|
|
4
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
5
|
+
|
|
6
|
+
namespace facebook::react {
|
|
7
|
+
using RNCNativeScrollerComponentDescriptor = ConcreteComponentDescriptor<RNCNativeScrollerShadowNode>;
|
|
8
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#include <react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h>
|
|
2
|
+
|
|
3
|
+
namespace facebook::react {
|
|
4
|
+
|
|
5
|
+
const char RNCNativeScrollerComponentName[] = "RNCNativeScroller";
|
|
6
|
+
|
|
7
|
+
Point RNCNativeScrollerShadowNode::getContentOriginOffset(bool includeTransform) const {
|
|
8
|
+
const auto state = getStateData();
|
|
9
|
+
const auto origin = state.contentOrigin;
|
|
10
|
+
const auto transform = includeTransform ? getTransform() : Transform::Identity();
|
|
11
|
+
const auto result = transform * Vector{origin.x, origin.y, 0.0f, 1.0f};
|
|
12
|
+
return {result.x + state.ancestorOffset.x, result.y + state.ancestorOffset.y};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
#include <react/renderer/components/pagerview/EventEmitters.h>
|
|
5
|
+
#include <react/renderer/components/pagerview/Props.h>
|
|
6
|
+
#include <react/renderer/components/pagerview/RNCNativeScrollerState.h>
|
|
7
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
JSI_EXPORT extern const char RNCNativeScrollerComponentName[];
|
|
12
|
+
|
|
13
|
+
class JSI_EXPORT RNCNativeScrollerShadowNode final
|
|
14
|
+
: public ConcreteViewShadowNode<RNCNativeScrollerComponentName,
|
|
15
|
+
RNCNativeScrollerProps,
|
|
16
|
+
RNCNativeScrollerEventEmitter,
|
|
17
|
+
RNCNativeScrollerState> {
|
|
18
|
+
public:
|
|
19
|
+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
20
|
+
Point getContentOriginOffset(bool includeTransform) const override;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/graphics/Point.h>
|
|
4
|
+
#ifdef RN_SERIALIZABLE_STATE
|
|
5
|
+
#include <folly/dynamic.h>
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
namespace facebook::react {
|
|
9
|
+
|
|
10
|
+
// Local content displacement and ancestor translation have different transform spaces.
|
|
11
|
+
class RNCNativeScrollerState final {
|
|
12
|
+
public:
|
|
13
|
+
Point contentOrigin{};
|
|
14
|
+
Point ancestorOffset{};
|
|
15
|
+
RNCNativeScrollerState() = default;
|
|
16
|
+
explicit RNCNativeScrollerState(Point origin) : contentOrigin(origin) {}
|
|
17
|
+
|
|
18
|
+
#ifdef RN_SERIALIZABLE_STATE
|
|
19
|
+
RNCNativeScrollerState(const RNCNativeScrollerState &, folly::dynamic data)
|
|
20
|
+
: contentOrigin({static_cast<Float>(data["contentOriginX"].getDouble()),
|
|
21
|
+
static_cast<Float>(data["contentOriginY"].getDouble())}),
|
|
22
|
+
ancestorOffset({static_cast<Float>(data.getDefault("ancestorOffsetX", 0.0).getDouble()),
|
|
23
|
+
static_cast<Float>(data.getDefault("ancestorOffsetY", 0.0).getDouble())}) {}
|
|
24
|
+
|
|
25
|
+
folly::dynamic getDynamic() const {
|
|
26
|
+
return folly::dynamic::object("contentOriginX", contentOrigin.x)
|
|
27
|
+
("contentOriginY", contentOrigin.y)
|
|
28
|
+
("ancestorOffsetX", ancestorOffset.x)("ancestorOffsetY", ancestorOffset.y);
|
|
29
|
+
}
|
|
30
|
+
#endif
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
} // namespace facebook::react
|
package/docs/SPEC.md
CHANGED
|
@@ -3,9 +3,276 @@
|
|
|
3
3
|
Existing PagerView and CollapsiblePagerView APIs are documented in README.md.
|
|
4
4
|
The sections below define native coordination boundaries without new React props.
|
|
5
5
|
|
|
6
|
+
## Independent NativeScroller migration
|
|
7
|
+
|
|
8
|
+
Status: implemented in source. Both native builds pass. Android focused emulator
|
|
9
|
+
cases below and the iOS same-source UIKit harness pass; the full acceptance
|
|
10
|
+
matrix remains incomplete. Device evidence is scoped below.
|
|
11
|
+
NativeScroller keeps React children and their Fabric/Yoga layout, but owns its
|
|
12
|
+
vertical native scrolling implementation: AndroidX NestedScrollView on Android
|
|
13
|
+
and UIScrollView on iOS. It MUST NOT inherit ReactScrollView/RCTScrollView or
|
|
14
|
+
require rebuilding ReactAndroid. Web retains its existing implementation.
|
|
15
|
+
|
|
16
|
+
The initial compatibility contract covers existing Home callers: viewport style,
|
|
17
|
+
contentContainerStyle including padding and flexGrow, dynamically mounted React
|
|
18
|
+
children, vertical scroll enablement/indicator, stable pagerScrollKey, scroll
|
|
19
|
+
throttling, onScroll/onScrollBeginDrag/onScrollEndDrag and momentum callbacks,
|
|
20
|
+
content-size notification, and scrollTo/scrollToEnd commands. Standard RN
|
|
21
|
+
RefreshControl JSX remains a supported input; its props and onRefresh callback
|
|
22
|
+
are adapted to owned native refresh controls rather than mounting an RN-private
|
|
23
|
+
scroll-view implementation. Supported properties and intentional platform limits
|
|
24
|
+
MUST be explicit in the exported types. Unknown RN ScrollView features MUST NOT
|
|
25
|
+
be silently advertised as supported. No business data or Home-specific geometry
|
|
26
|
+
belongs in the native component.
|
|
27
|
+
|
|
28
|
+
This is a vertical, non-virtualized subset, not a drop-in implementation of every
|
|
29
|
+
ScrollView prop. Horizontal scrolling, sticky rows, snapping, zoom and automatic
|
|
30
|
+
keyboard insets are outside this contract. iOS owns bounces and decelerationRate;
|
|
31
|
+
Android retains AndroidX fling physics without a public friction override.
|
|
32
|
+
Keyboard persist-tap behavior is coordinated by the JS responder wrapper using
|
|
33
|
+
RN 0.86's TextInput registry, while native owns drag-to-dismiss. The RN-private
|
|
34
|
+
registry dependency and keyboard behavior need validation on RN upgrades.
|
|
35
|
+
Arbitrary custom refresh components are not supported.
|
|
36
|
+
|
|
37
|
+
Native views, gestures, insets and refresh controllers belong to the UI thread.
|
|
38
|
+
React content layout remains Fabric-owned. Layout-size updates may cross the
|
|
39
|
+
bridge when dimensions change; native scrolling and header coordination MUST NOT
|
|
40
|
+
depend on per-frame JS updates. This is a main/UI component, with no background
|
|
41
|
+
runtime, persistent storage or cross-runtime heap state.
|
|
42
|
+
|
|
43
|
+
The wrapper mounts one non-collapsible React content View. Native MUST preserve
|
|
44
|
+
its Fabric-assigned frame and transform. A shared Fabric ShadowNode reads native
|
|
45
|
+
state describing local content displacement and separate ancestor displacement;
|
|
46
|
+
it MUST apply the ancestor displacement outside the content transform. UIKit
|
|
47
|
+
updates this state from contentOffset; Android updates it from content origin,
|
|
48
|
+
scroll position and pager consumption. Equal values are deduplicated, and a
|
|
49
|
+
replacement state wrapper is synchronized with the current native position.
|
|
50
|
+
These native state updates do not require per-frame JavaScript callbacks.
|
|
51
|
+
Fabric hit testing and descendant measureInWindow MUST include the displacement,
|
|
52
|
+
so measurement follows visible content after scroll, inset changes and page
|
|
53
|
+
return. Geometry acceptance MUST compare a measured React child with its visible
|
|
54
|
+
position, including the last child after scrollToEnd.
|
|
55
|
+
|
|
56
|
+
The automatic flexGrow minimum MUST use the native usable content viewport,
|
|
57
|
+
not the raw host height. An internal onContentViewportChange({height}) event
|
|
58
|
+
reports logical units only when static geometry changes; it is not a public
|
|
59
|
+
NativeScroller prop. Android reports viewport height minus caller/pager top
|
|
60
|
+
reservation and caller bottom inset. Its pager viewport already includes the
|
|
61
|
+
extra collapsible-header height. iOS reports bounds height minus caller top and
|
|
62
|
+
bottom insets and the sum of pager-owned sticky reservations. Both describe the
|
|
63
|
+
React area available with the shared header collapsed. The height MUST NOT
|
|
64
|
+
depend on contentSize, current header offset, refresh temporary insets or iOS
|
|
65
|
+
synthetic bottom fill, which would create a layout feedback loop. Owners MUST
|
|
66
|
+
remove their reservation on release. The current value MUST be delivered when
|
|
67
|
+
the view first becomes able to dispatch its event and after reattachment. An
|
|
68
|
+
event queued before its initial emitter is ready MUST be delivered when that
|
|
69
|
+
emitter becomes available. Replacing the emitter on the same attached view does
|
|
70
|
+
not require another event when the usable height is unchanged.
|
|
71
|
+
|
|
72
|
+
Before the first native viewport event, the wrapper contributes no positive
|
|
73
|
+
automatic minimum. It combines valid native height with an explicit numeric
|
|
74
|
+
caller minHeight using the greater value; nonnumeric explicit minima remain
|
|
75
|
+
untouched. Content padding remains part of Yoga layout. Long-to-short replacement
|
|
76
|
+
MUST clamp the local offset without leaving a pager-inset-sized artificial scroll
|
|
77
|
+
range or hiding the first React child. Tests and device acceptance MUST cover
|
|
78
|
+
long-to-empty/restore, page return, header/sticky resizing, refresh completion
|
|
79
|
+
without minimum-height changes, and an intentionally larger caller minimum.
|
|
80
|
+
|
|
81
|
+
Android MUST dispatch touch and non-touch nested scrolling continuously across
|
|
82
|
+
the content/header boundary. Content returns to its start before downward travel
|
|
83
|
+
expands the header; upward travel collapses the header before scrolling content.
|
|
84
|
+
Consumed and remaining distance MUST be accounted for once. Only touch pull can
|
|
85
|
+
trigger refresh; inertial movement MUST NOT trigger refresh. iOS MUST preserve a
|
|
86
|
+
single UIScrollView trajectory through the inset/header range. Existing Pager
|
|
87
|
+
refresh foreground placement and shared-header press cancellation remain in
|
|
88
|
+
force. NativeList and unrelated RN ScrollViews retain their implementations.
|
|
89
|
+
|
|
90
|
+
New touch, page deactivation, replacement of the mounted React content root and
|
|
91
|
+
detach MUST stop stale inertia. Every started drag/momentum sequence has one
|
|
92
|
+
terminal notification. An animated scrollTo/scrollToEnd command with a different
|
|
93
|
+
clamped target emits one momentum begin/end pair, including AndroidX's immediate
|
|
94
|
+
execution branch for closely spaced commands. A target equal to the current
|
|
95
|
+
position emits no new pair. A new command ends any previous active sequence
|
|
96
|
+
before starting its own, and non-animated movement does not start momentum;
|
|
97
|
+
recycled views MUST NOT emit into an old owner. Content shrink clamps unreachable
|
|
98
|
+
offsets without a blank tail; resize/inset changes MUST NOT accumulate offsets.
|
|
99
|
+
Refresh is controlled by the caller and emits once per qualifying gesture.
|
|
100
|
+
|
|
101
|
+
Acceptance requires both native builds, focused contract checks and iOS/Android
|
|
102
|
+
recordings: expanded/collapsed Home Spot/Perps/DeFi, short/empty and dynamically
|
|
103
|
+
resized content, fast return through the header boundary, new-touch interruption,
|
|
104
|
+
adjacent/distant page changes, horizontal gestures versus row presses, pull from
|
|
105
|
+
header/content, refresh completion, retained-page return, imperative
|
|
106
|
+
animation/no-op/interruption, and React child measureInWindow versus visible
|
|
107
|
+
geometry. History/NFT and Market/Discover are regression controls. Source/type/build success alone is not
|
|
108
|
+
device acceptance. Implementation findings and remaining limits are recorded
|
|
109
|
+
after validation, before this section is promoted to runtime verified.
|
|
110
|
+
|
|
111
|
+
Android acceptance checkpoint (2026-09-28): the official local-shell/local-vendor
|
|
112
|
+
DevSession build passed, with custom Fabric descriptor registration verified.
|
|
113
|
+
On the dedicated emulator, native field inspection confirmed NativeScrollerView
|
|
114
|
+
and its AndroidX viewport. Home Spot/Perps/DeFi return gestures crossed the
|
|
115
|
+
content/header boundary. In the final short-fill binary, Perps content offset
|
|
116
|
+
1389 reached zero and the same non-touch momentum continued header offset
|
|
117
|
+
755 -> 648 -> 441 -> 217 -> 56 before natural exhaustion. This proves continuity,
|
|
118
|
+
not that every gesture must fully expand the header.
|
|
119
|
+
|
|
120
|
+
The first valid binary exposed an artificial 284 px short-content range. After the
|
|
121
|
+
usable-viewport correction, Gallery long-to-empty replacement had local offset 0
|
|
122
|
+
with short text visible; restoring rows and scrollToEnd reached the final child.
|
|
123
|
+
measureInWindow y746.7/h72.0 matched native visible bounds y1960/h189px at
|
|
124
|
+
pixel density2.625. Short-content refresh advanced the fixture counter once and
|
|
125
|
+
controlled completion removed the indicator. Home three-page refresh visibility,
|
|
126
|
+
History/NFT vertical scrolling and Market/Browser horizontal controls passed on
|
|
127
|
+
the preceding valid binary; these control implementations were unchanged by the
|
|
128
|
+
short-fill correction. Evidence is recorded in the app task's ignored
|
|
129
|
+
`ignore/independent-native-scroller/android-acceptance.md`, alongside screenshots,
|
|
130
|
+
recording chunks and native field samples.
|
|
131
|
+
|
|
132
|
+
This checkpoint does not cover a physical handset, keyboard/multi-touch/accessory
|
|
133
|
+
interactions, exhaustive event counts and command interruption, explicit larger
|
|
134
|
+
caller minHeight, header/sticky resizing or the full refresh matrix. Existing
|
|
135
|
+
development warnings were observed. Android evidence does not establish iOS
|
|
136
|
+
acceptance, and source/build success is not a substitute for its runtime cases.
|
|
137
|
+
|
|
138
|
+
iOS acceptance checkpoint (2026-09-28): the final official local-shell/local-vendor
|
|
139
|
+
build passed and launched on the dedicated external-image simulator. Nine
|
|
140
|
+
same-source UIKit harness methods and 36 assertions pass, including negative-pull
|
|
141
|
+
preservation during shrink, caller inset ownership, static usable height and
|
|
142
|
+
refresh/recycle interruption. This is UIKit execution, not a formal XCTest run.
|
|
143
|
+
|
|
144
|
+
On the final binary, Home Spot/Perps/DeFi horizontal transitions and top-positioned
|
|
145
|
+
refresh feedback passed. Gallery scrollToEnd reached the last React row;
|
|
146
|
+
measureInWindow y786/h72 matched its native frame. Long-to-empty replacement,
|
|
147
|
+
restoring rows, controlled refresh counter 0 -> 1 with indicator dismissal,
|
|
148
|
+
normal row press and retained-page return passed. History/NFT scrolling and
|
|
149
|
+
media, Discover Browser -> DeFi and Market vertical scrolling plus Trending ->
|
|
150
|
+
Stocks horizontal transition passed without unintended detail navigation.
|
|
151
|
+
The final device is left on expanded Home Spot. Evidence is in the app task's
|
|
152
|
+
`ignore/independent-native-scroller/ios-acceptance.md` and associated recordings.
|
|
153
|
+
|
|
154
|
+
These iOS gestures use public XCTest coordinate drags. Earlier private synthesized
|
|
155
|
+
pan attempts had inconsistent timing and are excluded; their cause is not proven.
|
|
156
|
+
Gallery's floating application tab bar overlaps part of the last row, so matching
|
|
157
|
+
coordinates do not establish an unobscured bottom viewport. Existing development
|
|
158
|
+
warnings were observed. Keyboard, multi-touch, physical-device performance and
|
|
159
|
+
the full lifecycle/interaction matrix remain unverified. These are local source
|
|
160
|
+
builds; the currently installed npm version does not yet contain this migration.
|
|
161
|
+
|
|
162
|
+
## Native tab-press animation default
|
|
163
|
+
|
|
164
|
+
On iOS and Android, an omitted `nativeTabPressAnimationEnabled` MUST animate a
|
|
165
|
+
press to an adjacent page and jump directly to a non-adjacent page. Adjacency is
|
|
166
|
+
the absolute difference between the target index and the currently selected
|
|
167
|
+
index, not the last requested target. Explicit `true` continues to animate all
|
|
168
|
+
tab presses, and explicit `false` continues to jump for all tab presses.
|
|
169
|
+
Page retention, selection callbacks, swipe gestures, imperative `setPage` /
|
|
170
|
+
`setPageWithoutAnimation` commands and Web behavior retain their contracts.
|
|
171
|
+
Callers rendering their own JS tab bar choose the matching imperative command.
|
|
172
|
+
|
|
173
|
+
Status: implemented; focused native-wrapper tests cover both directions and
|
|
174
|
+
explicit overrides. Home applies this policy to its JS tab bar, with iOS and
|
|
175
|
+
Android simulator recordings verifying distant jumps and adjacent transitions.
|
|
176
|
+
|
|
177
|
+
## Android refresh foreground coordination
|
|
178
|
+
|
|
179
|
+
Status: implemented; Android Home simulator placement verified on 2026-09-28.
|
|
180
|
+
The previous below-tab placement is not accepted: the Home header can place the
|
|
181
|
+
indicator in the middle of asset rows. In smooth-header mode the outermost
|
|
182
|
+
coordinator MUST draw the active primary scroller's native refresh indicator
|
|
183
|
+
above the shared headers at the coordinator's top. SwipeRefreshLayout retains
|
|
184
|
+
trigger, animation, native visibility and refresh-state ownership. The indicator
|
|
185
|
+
MUST NOT be reparented, and the coordinator MUST NOT reset progress offsets.
|
|
186
|
+
|
|
187
|
+
NativeScroller and NativeList use the same foreground drawing boundary. A native
|
|
188
|
+
View tag, `onekey_native_scroll_coordinator_refresh_inset`, reports only the
|
|
189
|
+
refresh controller's actual coordinator-applied inset. An absent tag means zero.
|
|
190
|
+
The outer drawer subtracts that inset once, preserving caller geometry and the
|
|
191
|
+
native indicator matrix. This is native drawing metadata, with no JS API or row
|
|
192
|
+
semantics. Nested owners MUST NOT draw duplicate indicators.
|
|
193
|
+
|
|
194
|
+
The coordinator suppresses only the indicator's original drawing and restores
|
|
195
|
+
its prior alpha when ownership ends, the target changes, or the pager detaches.
|
|
196
|
+
An inactive page MUST NOT appear in the foreground. The foreground must clear
|
|
197
|
+
when the indicator disappears. Native animation transformations MUST NOT be
|
|
198
|
+
advanced a second time during drawing. Focused policy checks and device evidence
|
|
199
|
+
must cover alpha restoration, nested ownership, page changes and dismissal.
|
|
200
|
+
All five Home tabs require top-positioned feedback and stable header position
|
|
201
|
+
through pull, release and refresh completion. Source/compile success is not
|
|
202
|
+
runtime acceptance. Existing iOS/Web behavior is unchanged.
|
|
203
|
+
|
|
204
|
+
The five Home tabs were recorded from both header and content origins: the
|
|
205
|
+
foreground arc appears at the coordinator top and dismisses normally. The fast
|
|
206
|
+
release reversal that previously changed header offset from 0 to 767 px now
|
|
207
|
+
consumes the pending pull fling and keeps the header expanded. These recordings
|
|
208
|
+
verify visual feedback, not downstream network completion or new callback counts.
|
|
209
|
+
Production-helper tests cover alpha restoration and combined native insets;
|
|
210
|
+
real nested-owner replacement, dynamic caller geometry and detach remain separate
|
|
211
|
+
acceptance work. Direct foreground View drawing preserves the native circle and
|
|
212
|
+
arc but does not reproduce the original RenderNode elevation shadow.
|
|
213
|
+
|
|
214
|
+
## Android refresh release settlement
|
|
215
|
+
|
|
216
|
+
Status: implemented; the captured release reversal passes a device A/B check.
|
|
217
|
+
A downward touch gesture that leaves unconsumed travel for the active
|
|
218
|
+
SwipeRefreshLayout is a pending native refresh pull. While that same gesture's
|
|
219
|
+
indicator is visible, the shared header is expanded, and primary content remains
|
|
220
|
+
at the top, the coordinator MUST consume nested pre-fling on release. A small
|
|
221
|
+
upward reversal before lifting must settle the pull instead of starting a
|
|
222
|
+
content fling and abruptly collapsing the header. SwipeRefreshLayout still owns
|
|
223
|
+
whether the release triggers or cancels refresh.
|
|
224
|
+
|
|
225
|
+
The pending target is weak, scoped to the current touch nested-scroll gesture,
|
|
226
|
+
and cleared at its stop or replacement. An old indicator's exit animation alone
|
|
227
|
+
is not a pending pull. Ordinary upward swipes, content scrolled away from the
|
|
228
|
+
top, inactive pages and non-smooth mode retain existing fling behavior. A pull
|
|
229
|
+
whose indicator is already hidden no longer qualifies. AndroidX can leave the
|
|
230
|
+
indicator visible when reverse travel exactly exhausts the pull; that same
|
|
231
|
+
gesture may still be settled instead of flung. Required regression cases include immediate reversal release,
|
|
232
|
+
a stationary release, below-threshold cancellation, ordinary upward fling and
|
|
233
|
+
header expansion before refresh. No JS timeout or data-refresh suppression is
|
|
234
|
+
introduced.
|
|
235
|
+
|
|
236
|
+
## Android downward scroll ownership
|
|
237
|
+
|
|
238
|
+
Status: implemented; Android Home History runtime verified on 2026-09-28.
|
|
239
|
+
Downward scrolling MUST move the active content back to its top before expanding
|
|
240
|
+
the shared header. A refresh wrapper forwarding nested scrolling MUST report its
|
|
241
|
+
child's ability to scroll upward; the wrapper's own scroll range is not evidence
|
|
242
|
+
that the content is at the top. SwipeRefreshLayout's public canChildScrollUp()
|
|
243
|
+
contract, including caller overrides, supplies this check. Other nested-scroll
|
|
244
|
+
targets retain their own canScrollVertically(-1) check.
|
|
245
|
+
|
|
246
|
+
The correction changes only the downward pre-scroll eligibility check. Existing
|
|
247
|
+
delta consumption, content-observer alignment, refresh ownership and release
|
|
248
|
+
settlement remain unchanged. Runtime comparison captured premature expansion
|
|
249
|
+
with content offset 1960 px before the fix. Afterward the header stayed collapsed
|
|
250
|
+
while content returned to zero, and expanded only at the top, without an observer
|
|
251
|
+
forced collapse. Debugger state traces prove ownership, not frame timing;
|
|
252
|
+
recordings with the debugger detached cover History rapid direction reversal
|
|
253
|
+
and top refresh.
|
|
254
|
+
Spot's scroll-then-refresh release-reversal sequence still jumps in both the
|
|
255
|
+
baseline and corrected builds; that existing NativeScroller case remains open.
|
|
256
|
+
|
|
257
|
+
## Superseded Android indicator placement
|
|
258
|
+
|
|
259
|
+
The alpha.257 approach translated the RN indicator below the shared tabs.
|
|
260
|
+
Its ten Home checks established visibility and event dispatch only. Subsequent
|
|
261
|
+
2026-09-28 feedback showed the indicator over asset rows; those checks do not
|
|
262
|
+
establish correct placement. The foreground coordination contract above replaces
|
|
263
|
+
that translation policy. NativeList keeps its controller offset internally and
|
|
264
|
+
publishes only its actual native inset for foreground drawing.
|
|
265
|
+
|
|
6
266
|
## iOS refresh indicator placement
|
|
7
267
|
|
|
8
|
-
Status:
|
|
268
|
+
Status: implemented; iOS Home pull-gesture visibility verified on 2026-09-27.
|
|
269
|
+
Spot, Perps and DeFi (RN NativeScroller), plus NFT and History (NativeList),
|
|
270
|
+
each pass header-origin and content-origin pulls: ten recordings show a visible
|
|
271
|
+
indicator above the shared header, one refresh event and normal disappearance.
|
|
272
|
+
This verifies UI feedback and event dispatch, not downstream request completion.
|
|
273
|
+
The Pager UIKit suite passes 14 tests, including six refresh-control cases
|
|
274
|
+
(native-tests run 36318243869). Rapid nested gestures and iPad geometry remain
|
|
275
|
+
separate acceptance work.
|
|
9
276
|
In smooth-header mode, the pager shifts the refresh control's sublayers upward
|
|
10
277
|
by its header-plus-sticky height. It does not change the refresh control frame,
|
|
11
278
|
bounds, scroll offset, refresh state or JavaScript callback. RN's caller-owned
|