@onekeyfe/react-native-native-list 3.0.177 → 3.0.179-alpha.20261015

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +3 -0
  2. package/docs/SPEC.md +19 -0
  3. package/ios/HybridNativeList.swift +18 -0
  4. package/ios/RNCNativeListView.swift +87 -0
  5. package/lib/module/NativeList.js +24 -5
  6. package/lib/module/NativeList.web.js +2 -0
  7. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +18 -0
  8. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  9. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  10. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +12 -0
  11. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +12 -0
  12. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  13. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  14. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +22 -0
  15. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  16. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  17. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  18. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  19. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  20. package/lib/typescript/src/NativeList.nitro.d.ts +2 -0
  21. package/lib/typescript/src/NativeList.types.d.ts +4 -0
  22. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +18 -0
  23. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  24. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  25. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +12 -0
  26. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +12 -0
  27. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  28. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  29. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +22 -0
  30. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  31. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  32. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  33. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  34. package/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  35. package/package.json +3 -3
  36. package/src/NativeList.nitro.ts +2 -0
  37. package/src/NativeList.tsx +25 -1
  38. package/src/NativeList.types.ts +4 -0
  39. package/src/NativeList.web.tsx +2 -0
@@ -31,6 +31,9 @@ class HybridNativeList(context: ThemedReactContext) : HybridNativeListSpec() {
31
31
  dispatchToUi { hostView.setContainerSlotHeightsJson(value) }
32
32
  }
33
33
 
34
+ override var topScrollEdgeHard: Boolean = false
35
+ override var topOverlayHeight: Double = 0.0
36
+
34
37
  override var snapshotJson: String = ""
35
38
  get() = field
36
39
  set(value) {
package/docs/SPEC.md CHANGED
@@ -26,6 +26,25 @@ prevention; there is no automatic fitting or shrinking pass.
26
26
 
27
27
  ## Public API and defaults
28
28
 
29
+ ### iOS 26 fixed top scroll-edge overlay
30
+
31
+ Standalone `NativeList` accepts optional `topScrollEdgeEffect="hard"` and
32
+ `topOverlay` React content. Both are disabled by default. On iOS 26 or newer,
33
+ the fixed overlay is a fourth native container slot, outside the row stream and
34
+ outside the existing scrolling `listHeader`/`listEmpty`/`listFooter` slots.
35
+ Fabric measures its height; the list owns an equal top inset on its collection
36
+ view and binds UIKit's `UIScrollEdgeElementContainerInteraction` to that view,
37
+ using the hard top edge style. The list restores its original inset and effect
38
+ on removal, disposal and reuse. The UI thread owns all view and UIKit state;
39
+ there is no persistence, background-runtime state or per-frame JS callback.
40
+ An invisible, noninteractive native label fills the overlay host so UIKit
41
+ recognizes React-drawn content as a scroll-edge element.
42
+ Inside a CollapsiblePagerView, the pager owns this effect and inset, and child
43
+ lists do not opt in separately. iOS before 26, Android and Web retain their
44
+ existing layout and scrolling. Missing overlay or style changes nothing.
45
+ Acceptance requires actual iOS 26 initial layout, long/short/empty scrolling,
46
+ refresh and overlay replacement/removal, including dark mode.
47
+
29
48
  [README.md](../README.md) documents the snapshot, patch, selection, events and
30
49
  imperative API; [STYLE_SPEC.md](STYLE_SPEC.md) defines normalization, omitted
31
50
  style defaults and platform differences. Height precedence is
@@ -36,6 +36,24 @@ final class HybridNativeList: HybridNativeListSpec {
36
36
  }
37
37
  }
38
38
 
39
+ var topScrollEdgeHard = false {
40
+ didSet {
41
+ runOnMain { [weak self] in
42
+ self?.hostView?.setTopScrollEdge(hard: self?.topScrollEdgeHard ?? false,
43
+ overlayHeight: CGFloat(self?.topOverlayHeight ?? 0))
44
+ }
45
+ }
46
+ }
47
+
48
+ var topOverlayHeight: Double = 0 {
49
+ didSet {
50
+ runOnMain { [weak self] in
51
+ self?.hostView?.setTopScrollEdge(hard: self?.topScrollEdgeHard ?? false,
52
+ overlayHeight: CGFloat(self?.topOverlayHeight ?? 0))
53
+ }
54
+ }
55
+ }
56
+
39
57
  var snapshotJson: String = "" {
40
58
  didSet {
41
59
  guard snapshotJson != oldValue else { return }
@@ -11,11 +11,27 @@ final class NativeListView: UIView {
11
11
  // Each root therefore lives in a list-owned host whose origin the list alone controls, so a
12
12
  // React layout update can never move a slot out of its native position, even for a frame.
13
13
  private var reactContainerSlots: [(content: UIView, host: UIView)] = []
14
+ private let topOverlayHost = UIView()
15
+ private weak var topOverlayContent: UIView?
16
+ private var topEdgeInteraction: UIInteraction?
17
+ private var topEdgeMarker: UILabel?
18
+ private var originalTopEdgeStyle: AnyObject?
19
+ private var topScrollEdgeHard = false
20
+ private var requestedTopOverlayHeight: CGFloat = 0
21
+ private var appliedTopOverlayInset: CGFloat = 0
22
+
14
23
  private var lastLaidOutContentHeight: CGFloat?
15
24
  private var needsContentShrinkCorrection = false
16
25
  private var contentShrinkCorrectionScheduled = false
17
26
 
18
27
  @objc func mountContainerSlot(_ view: UIView, atIndex index: Int) {
28
+ if index == 3 {
29
+ topOverlayContent?.removeFromSuperview()
30
+ topOverlayContent = view
31
+ topOverlayHost.addSubview(view)
32
+ updateTopScrollEdge()
33
+ return
34
+ }
19
35
  let host = UIView()
20
36
  // Clipping to the reported height keeps new content from overlapping rows until the list
21
37
  // has reserved space for it.
@@ -28,6 +44,12 @@ final class NativeListView: UIView {
28
44
  }
29
45
 
30
46
  @objc func unmountContainerSlot(_ view: UIView) {
47
+ if topOverlayContent === view {
48
+ view.removeFromSuperview()
49
+ topOverlayContent = nil
50
+ updateTopScrollEdge()
51
+ return
52
+ }
31
53
  reactContainerSlots.removeAll { slot in
32
54
  guard slot.content === view else { return false }
33
55
  view.removeFromSuperview()
@@ -36,6 +58,66 @@ final class NativeListView: UIView {
36
58
  }
37
59
  }
38
60
 
61
+ func setTopScrollEdge(hard: Bool, overlayHeight: CGFloat) {
62
+ topScrollEdgeHard = hard
63
+ requestedTopOverlayHeight = overlayHeight.isFinite ? max(0, overlayHeight) : 0
64
+ updateTopScrollEdge()
65
+ setNeedsLayout()
66
+ }
67
+
68
+ private func updateTopScrollEdge() {
69
+ if #available(iOS 26.0, *) {
70
+ let hasOverlay = topScrollEdgeHard && topOverlayContent != nil
71
+ let enabled = hasOverlay && window != nil
72
+ let nextInset = hasOverlay ? requestedTopOverlayHeight : 0
73
+ if nextInset != appliedTopOverlayInset {
74
+ // Do not interrupt UIKit's rebound after a refresh has ended.
75
+ let wasReturningFromPull =
76
+ collectionView.contentOffset.y < -collectionView.adjustedContentInset.top - 0.5
77
+ let logicalOffset = collectionView.contentOffset.y + collectionView.contentInset.top
78
+ var inset = collectionView.contentInset
79
+ inset.top += nextInset - appliedTopOverlayInset
80
+ collectionView.contentInset = inset
81
+ appliedTopOverlayInset = nextInset
82
+ if !wasReturningFromPull {
83
+ collectionView.setContentOffset(
84
+ CGPoint(x: collectionView.contentOffset.x, y: logicalOffset - inset.top),
85
+ animated: false)
86
+ }
87
+ }
88
+ if enabled && topEdgeInteraction == nil {
89
+ originalTopEdgeStyle = collectionView.topEdgeEffect.style
90
+ // UIKit needs a native element to shape the edge effect around React-drawn content.
91
+ let marker = UILabel(frame: topOverlayHost.bounds)
92
+ marker.autoresizingMask = [.flexibleWidth, .flexibleHeight]
93
+ marker.text = " "
94
+ marker.textColor = .clear
95
+ marker.backgroundColor = .clear
96
+ marker.isUserInteractionEnabled = false
97
+ marker.isAccessibilityElement = false
98
+ marker.accessibilityElementsHidden = true
99
+ topOverlayHost.addSubview(marker)
100
+ topEdgeMarker = marker
101
+ let interaction = UIScrollEdgeElementContainerInteraction()
102
+ interaction.edge = .top
103
+ interaction.scrollView = collectionView
104
+ topOverlayHost.addInteraction(interaction)
105
+ topEdgeInteraction = interaction
106
+ } else if !enabled, let interaction = topEdgeInteraction {
107
+ topOverlayHost.removeInteraction(interaction)
108
+ (interaction as? UIScrollEdgeElementContainerInteraction)?.scrollView = nil
109
+ topEdgeInteraction = nil
110
+ topEdgeMarker?.removeFromSuperview()
111
+ topEdgeMarker = nil
112
+ if let originalTopEdgeStyle = originalTopEdgeStyle as? UIScrollEdgeEffect.Style {
113
+ collectionView.topEdgeEffect.style = originalTopEdgeStyle
114
+ }
115
+ originalTopEdgeStyle = nil
116
+ }
117
+ if enabled { collectionView.topEdgeEffect.style = .hard }
118
+ }
119
+ }
120
+
39
121
  func setContainerSlotHeightsJson(_ json: String) {
40
122
  guard let data = json.data(using: .utf8),
41
123
  let heights = try? JSONSerialization.jsonObject(with: data) as? [Double],
@@ -271,6 +353,7 @@ final class NativeListView: UIView {
271
353
  addSubview(footerContainer)
272
354
  addSubview(sectionIndexView)
273
355
  addSubview(sectionIndexPreview)
356
+ addSubview(topOverlayHost)
274
357
  footerContainer.addSubview(footerCell)
275
358
  collectionView.translatesAutoresizingMaskIntoConstraints = false
276
359
  footerContainer.translatesAutoresizingMaskIntoConstraints = false
@@ -422,11 +505,14 @@ final class NativeListView: UIView {
422
505
 
423
506
  override func didMoveToWindow() {
424
507
  super.didMoveToWindow()
508
+ updateTopScrollEdge()
425
509
  updateSectionIndexAttachment()
426
510
  }
427
511
 
428
512
  override func layoutSubviews() {
429
513
  super.layoutSubviews()
514
+ topOverlayHost.frame = CGRect(x: 0, y: 0, width: collectionView.bounds.width,
515
+ height: requestedTopOverlayHeight)
430
516
  updateSectionIndexAttachment()
431
517
  let direction = effectiveUserInterfaceLayoutDirection
432
518
  if let lastLayoutSize,
@@ -1998,6 +2084,7 @@ final class NativeListView: UIView {
1998
2084
 
1999
2085
  func dispose() {
2000
2086
  guard !disposed else { return }
2087
+ setTopScrollEdge(hard: false, overlayHeight: 0)
2001
2088
  disposed = true
2002
2089
 
2003
2090
  disposeActionAnchor()
@@ -3,12 +3,12 @@
3
3
  import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
4
4
  import { OneKeyImageCache, OneKeyImageCachePolicy } from '@onekeyfe/react-native-image';
5
5
  import { NativeAvatarPrefetchModel, NativeAvatarPrefetchQueue } from "./avatarPrefetch.js";
6
- import { View } from 'react-native';
6
+ import { Platform, View } from 'react-native';
7
7
  import { callback, getHostComponent } from 'react-native-nitro-modules';
8
8
  import { isSelectableRow } from "./selection.js";
9
9
  import { normalizeIndexScroll, normalizeKeyScroll, normalizePositionScroll, resolveLocationIndex, scrollFailure, validateOffset } from "./scrolling.js";
10
10
  import { applyRowPatches, serializePatches, serializeSnapshot } from "./validation.js";
11
- import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  const NativeListConfig = require('../nitrogen/generated/shared/json/NativeListConfig.json');
13
13
  const NativeListHost = getHostComponent('NativeList', () => NativeListConfig);
14
14
  export function preloadNativeListAvatarImages(sources) {
@@ -30,6 +30,8 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
30
30
  listHeader,
31
31
  listFooter,
32
32
  listEmpty,
33
+ topOverlay,
34
+ topScrollEdgeEffect,
33
35
  scrollPositionThresholds,
34
36
  webVirtualizationEnabled: _webVirtualizationEnabled,
35
37
  webSectionIndexContainerRef: _webSectionIndexContainerRef,
@@ -51,6 +53,8 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
51
53
  ...viewProps
52
54
  }, forwardedRef) {
53
55
  const [slotHeights, setSlotHeights] = useState([0, 0, 0]);
56
+ const [topOverlayHeight, setTopOverlayHeight] = useState(0);
57
+ const hasTopOverlay = Platform?.OS === 'ios' && Number(Platform.Version) >= 26 && topScrollEdgeEffect === 'hard' && topOverlay != null;
54
58
  const containerSlotHeightsJson = JSON.stringify(slotHeights);
55
59
  const slotInset = snapshot.layout.contentPaddingHorizontal ?? snapshot.layout.contentPadding ?? 0;
56
60
  if (snapshot.layout.orientation === 'horizontal' && (listHeader || listEmpty || listFooter)) {
@@ -313,15 +317,17 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
313
317
  callbacksRef.current.onVisibleRangeChanged?.(payload);
314
318
  })
315
319
  }), []);
316
- return /*#__PURE__*/_jsx(NativeListHost, {
320
+ return /*#__PURE__*/_jsxs(NativeListHost, {
317
321
  ...viewProps,
318
322
  ...nativeCallbacks,
319
323
  snapshotJson: snapshotJson,
320
324
  containerSlotHeightsJson: containerSlotHeightsJson,
325
+ topScrollEdgeHard: hasTopOverlay,
326
+ topOverlayHeight: hasTopOverlay ? topOverlayHeight : 0,
321
327
  scrollPositionThresholdsJson: scrollPositionThresholdsJson,
322
328
  keyboardDismissMode: keyboardDismissMode,
323
329
  keyboardShouldPersistTaps: normalizedKeyboardShouldPersistTaps,
324
- children: slotContents.map((content, index) => /*#__PURE__*/_jsx(View, {
330
+ children: [slotContents.map((content, index) => /*#__PURE__*/_jsx(View, {
325
331
  collapsable: false,
326
332
  style: {
327
333
  position: 'absolute',
@@ -334,7 +340,20 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
334
340
  setSlotHeights(previous => previous[index] === height ? previous : previous.map((value, slot) => slot === index ? height : value));
335
341
  },
336
342
  children: content
337
- }, index))
343
+ }, index)), hasTopOverlay ? /*#__PURE__*/_jsx(View, {
344
+ collapsable: false,
345
+ style: {
346
+ position: 'absolute',
347
+ top: 0,
348
+ left: 0,
349
+ right: 0
350
+ },
351
+ onLayout: event => {
352
+ const height = Math.ceil(event.nativeEvent.layout.height);
353
+ setTopOverlayHeight(previous => previous === height ? previous : height);
354
+ },
355
+ children: topOverlay
356
+ }) : null]
338
357
  });
339
358
  });
340
359
  //# sourceMappingURL=NativeList.js.map
@@ -25,6 +25,8 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
25
25
  listHeader: _listHeader,
26
26
  listEmpty: _listEmpty,
27
27
  listFooter: _listFooter,
28
+ topOverlay: _topOverlay,
29
+ topScrollEdgeEffect: _topScrollEdgeEffect,
28
30
  scrollPositionThresholds: _scrollPositionThresholds,
29
31
  onScrollPositionThresholdChange: _onScrollPositionThresholdChange,
30
32
  webVirtualizationEnabled = true,
@@ -75,6 +75,24 @@ namespace margelo::nitro::nativelist {
75
75
  static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* containerSlotHeightsJson */)>("setContainerSlotHeightsJson");
76
76
  method(_javaPart, jni::make_jstring(containerSlotHeightsJson));
77
77
  }
78
+ bool JHybridNativeListSpec::getTopScrollEdgeHard() {
79
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("getTopScrollEdgeHard");
80
+ auto __result = method(_javaPart);
81
+ return static_cast<bool>(__result);
82
+ }
83
+ void JHybridNativeListSpec::setTopScrollEdgeHard(bool topScrollEdgeHard) {
84
+ static const auto method = _javaPart->javaClassStatic()->getMethod<void(jboolean /* topScrollEdgeHard */)>("setTopScrollEdgeHard");
85
+ method(_javaPart, topScrollEdgeHard);
86
+ }
87
+ double JHybridNativeListSpec::getTopOverlayHeight() {
88
+ static const auto method = _javaPart->javaClassStatic()->getMethod<double()>("getTopOverlayHeight");
89
+ auto __result = method(_javaPart);
90
+ return __result;
91
+ }
92
+ void JHybridNativeListSpec::setTopOverlayHeight(double topOverlayHeight) {
93
+ static const auto method = _javaPart->javaClassStatic()->getMethod<void(double /* topOverlayHeight */)>("setTopOverlayHeight");
94
+ method(_javaPart, topOverlayHeight);
95
+ }
78
96
  std::string JHybridNativeListSpec::getScrollPositionThresholdsJson() {
79
97
  static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getScrollPositionThresholdsJson");
80
98
  auto __result = method(_javaPart);
@@ -54,6 +54,10 @@ namespace margelo::nitro::nativelist {
54
54
  void setSnapshotJson(const std::string& snapshotJson) override;
55
55
  std::string getContainerSlotHeightsJson() override;
56
56
  void setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) override;
57
+ bool getTopScrollEdgeHard() override;
58
+ void setTopScrollEdgeHard(bool topScrollEdgeHard) override;
59
+ double getTopOverlayHeight() override;
60
+ void setTopOverlayHeight(double topOverlayHeight) override;
57
61
  std::string getScrollPositionThresholdsJson() override;
58
62
  void setScrollPositionThresholdsJson(const std::string& scrollPositionThresholdsJson) override;
59
63
  std::optional<std::function<void(bool /* isBeyondThreshold */)>> getOnScrollPositionThresholdChange() override;
@@ -63,6 +63,16 @@ void JHybridNativeListStateUpdater::updateViewProps(jni::alias_ref<jni::JClass>
63
63
  : !newProps->containerSlotHeightsJson.hasSameValue(oldProps->containerSlotHeightsJson)) {
64
64
  hybridView->setContainerSlotHeightsJson(newProps->containerSlotHeightsJson.get());
65
65
  }
66
+ if (oldProps == nullptr
67
+ ? newProps->topScrollEdgeHard.isProvided()
68
+ : !newProps->topScrollEdgeHard.hasSameValue(oldProps->topScrollEdgeHard)) {
69
+ hybridView->setTopScrollEdgeHard(newProps->topScrollEdgeHard.get());
70
+ }
71
+ if (oldProps == nullptr
72
+ ? newProps->topOverlayHeight.isProvided()
73
+ : !newProps->topOverlayHeight.hasSameValue(oldProps->topOverlayHeight)) {
74
+ hybridView->setTopOverlayHeight(newProps->topOverlayHeight.get());
75
+ }
66
76
  if (oldProps == nullptr
67
77
  ? newProps->scrollPositionThresholdsJson.isProvided()
68
78
  : !newProps->scrollPositionThresholdsJson.hasSameValue(oldProps->scrollPositionThresholdsJson)) {
@@ -39,6 +39,18 @@ abstract class HybridNativeListSpec: HybridView() {
39
39
  @set:Keep
40
40
  abstract var containerSlotHeightsJson: String
41
41
 
42
+ @get:DoNotStrip
43
+ @get:Keep
44
+ @set:DoNotStrip
45
+ @set:Keep
46
+ abstract var topScrollEdgeHard: Boolean
47
+
48
+ @get:DoNotStrip
49
+ @get:Keep
50
+ @set:DoNotStrip
51
+ @set:Keep
52
+ abstract var topOverlayHeight: Double
53
+
42
54
  @get:DoNotStrip
43
55
  @get:Keep
44
56
  @set:DoNotStrip
@@ -86,6 +86,18 @@ namespace margelo::nitro::nativelist {
86
86
  inline void setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) noexcept override {
87
87
  _swiftPart.setContainerSlotHeightsJson(containerSlotHeightsJson);
88
88
  }
89
+ inline bool getTopScrollEdgeHard() noexcept override {
90
+ return _swiftPart.getTopScrollEdgeHard();
91
+ }
92
+ inline void setTopScrollEdgeHard(bool topScrollEdgeHard) noexcept override {
93
+ _swiftPart.setTopScrollEdgeHard(std::forward<decltype(topScrollEdgeHard)>(topScrollEdgeHard));
94
+ }
95
+ inline double getTopOverlayHeight() noexcept override {
96
+ return _swiftPart.getTopOverlayHeight();
97
+ }
98
+ inline void setTopOverlayHeight(double topOverlayHeight) noexcept override {
99
+ _swiftPart.setTopOverlayHeight(std::forward<decltype(topOverlayHeight)>(topOverlayHeight));
100
+ }
89
101
  inline std::string getScrollPositionThresholdsJson() noexcept override {
90
102
  auto __result = _swiftPart.getScrollPositionThresholdsJson();
91
103
  return __result;
@@ -124,6 +124,18 @@ using namespace margelo::nitro::nativelist::views;
124
124
  : !newViewProps.containerSlotHeightsJson.hasSameValue(oldViewProps->containerSlotHeightsJson)) {
125
125
  swiftPart.setContainerSlotHeightsJson(newViewProps.containerSlotHeightsJson.get());
126
126
  }
127
+ // topScrollEdgeHard: boolean
128
+ if (oldViewProps == nullptr
129
+ ? newViewProps.topScrollEdgeHard.isProvided()
130
+ : !newViewProps.topScrollEdgeHard.hasSameValue(oldViewProps->topScrollEdgeHard)) {
131
+ swiftPart.setTopScrollEdgeHard(newViewProps.topScrollEdgeHard.get());
132
+ }
133
+ // topOverlayHeight: number
134
+ if (oldViewProps == nullptr
135
+ ? newViewProps.topOverlayHeight.isProvided()
136
+ : !newViewProps.topOverlayHeight.hasSameValue(oldViewProps->topOverlayHeight)) {
137
+ swiftPart.setTopOverlayHeight(newViewProps.topOverlayHeight.get());
138
+ }
127
139
  // scrollPositionThresholdsJson: string
128
140
  if (oldViewProps == nullptr
129
141
  ? newViewProps.scrollPositionThresholdsJson.isProvided()
@@ -12,6 +12,8 @@ public protocol HybridNativeListSpec_protocol: HybridObject, HybridView {
12
12
  // Properties
13
13
  var snapshotJson: String { get set }
14
14
  var containerSlotHeightsJson: String { get set }
15
+ var topScrollEdgeHard: Bool { get set }
16
+ var topOverlayHeight: Double { get set }
15
17
  var scrollPositionThresholdsJson: String { get set }
16
18
  var onScrollPositionThresholdChange: ((_ isBeyondThreshold: Bool) -> Void)? { get set }
17
19
  var keyboardDismissMode: NativeListKeyboardDismissMode { get set }
@@ -143,6 +143,28 @@ open class HybridNativeListSpec_cxx {
143
143
  }
144
144
  }
145
145
 
146
+ public final var topScrollEdgeHard: Bool {
147
+ @inline(__always)
148
+ get {
149
+ return self.__implementation.topScrollEdgeHard
150
+ }
151
+ @inline(__always)
152
+ set {
153
+ self.__implementation.topScrollEdgeHard = newValue
154
+ }
155
+ }
156
+
157
+ public final var topOverlayHeight: Double {
158
+ @inline(__always)
159
+ get {
160
+ return self.__implementation.topOverlayHeight
161
+ }
162
+ @inline(__always)
163
+ set {
164
+ self.__implementation.topOverlayHeight = newValue
165
+ }
166
+ }
167
+
146
168
  public final var scrollPositionThresholdsJson: std.string {
147
169
  @inline(__always)
148
170
  get {
@@ -18,6 +18,10 @@ namespace margelo::nitro::nativelist {
18
18
  prototype.registerHybridSetter("snapshotJson", &HybridNativeListSpec::setSnapshotJson);
19
19
  prototype.registerHybridGetter("containerSlotHeightsJson", &HybridNativeListSpec::getContainerSlotHeightsJson);
20
20
  prototype.registerHybridSetter("containerSlotHeightsJson", &HybridNativeListSpec::setContainerSlotHeightsJson);
21
+ prototype.registerHybridGetter("topScrollEdgeHard", &HybridNativeListSpec::getTopScrollEdgeHard);
22
+ prototype.registerHybridSetter("topScrollEdgeHard", &HybridNativeListSpec::setTopScrollEdgeHard);
23
+ prototype.registerHybridGetter("topOverlayHeight", &HybridNativeListSpec::getTopOverlayHeight);
24
+ prototype.registerHybridSetter("topOverlayHeight", &HybridNativeListSpec::setTopOverlayHeight);
21
25
  prototype.registerHybridGetter("scrollPositionThresholdsJson", &HybridNativeListSpec::getScrollPositionThresholdsJson);
22
26
  prototype.registerHybridSetter("scrollPositionThresholdsJson", &HybridNativeListSpec::setScrollPositionThresholdsJson);
23
27
  prototype.registerHybridGetter("onScrollPositionThresholdChange", &HybridNativeListSpec::getOnScrollPositionThresholdChange);
@@ -58,6 +58,10 @@ namespace margelo::nitro::nativelist {
58
58
  virtual void setSnapshotJson(const std::string& snapshotJson) = 0;
59
59
  virtual std::string getContainerSlotHeightsJson() = 0;
60
60
  virtual void setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) = 0;
61
+ virtual bool getTopScrollEdgeHard() = 0;
62
+ virtual void setTopScrollEdgeHard(bool topScrollEdgeHard) = 0;
63
+ virtual double getTopOverlayHeight() = 0;
64
+ virtual void setTopOverlayHeight(double topOverlayHeight) = 0;
61
65
  virtual std::string getScrollPositionThresholdsJson() = 0;
62
66
  virtual void setScrollPositionThresholdsJson(const std::string& scrollPositionThresholdsJson) = 0;
63
67
  virtual std::optional<std::function<void(bool /* isBeyondThreshold */)>> getOnScrollPositionThresholdChange() = 0;
@@ -22,6 +22,8 @@ namespace margelo::nitro::nativelist::views {
22
22
  react::ViewProps(context, sourceProps, rawProps, filterObjectKeys),
23
23
  snapshotJson(nitro::ReactProp<std::string>::fromRawValue("NativeList", "snapshotJson", rawProps, sourceProps.snapshotJson)),
24
24
  containerSlotHeightsJson(nitro::ReactProp<std::string>::fromRawValue("NativeList", "containerSlotHeightsJson", rawProps, sourceProps.containerSlotHeightsJson)),
25
+ topScrollEdgeHard(nitro::ReactProp<bool>::fromRawValue("NativeList", "topScrollEdgeHard", rawProps, sourceProps.topScrollEdgeHard)),
26
+ topOverlayHeight(nitro::ReactProp<double>::fromRawValue("NativeList", "topOverlayHeight", rawProps, sourceProps.topOverlayHeight)),
25
27
  scrollPositionThresholdsJson(nitro::ReactProp<std::string>::fromRawValue("NativeList", "scrollPositionThresholdsJson", rawProps, sourceProps.scrollPositionThresholdsJson)),
26
28
  onScrollPositionThresholdChange(nitro::ReactProp<std::optional<std::function<void(bool /* isBeyondThreshold */)>>>::fromRawValue("NativeList", "onScrollPositionThresholdChange", rawProps, sourceProps.onScrollPositionThresholdChange)),
27
29
  keyboardDismissMode(nitro::ReactProp<NativeListKeyboardDismissMode>::fromRawValue("NativeList", "keyboardDismissMode", rawProps, sourceProps.keyboardDismissMode)),
@@ -38,6 +40,8 @@ namespace margelo::nitro::nativelist::views {
38
40
  switch (hashString(propName)) {
39
41
  case hashString("snapshotJson"): return true;
40
42
  case hashString("containerSlotHeightsJson"): return true;
43
+ case hashString("topScrollEdgeHard"): return true;
44
+ case hashString("topOverlayHeight"): return true;
41
45
  case hashString("scrollPositionThresholdsJson"): return true;
42
46
  case hashString("onScrollPositionThresholdChange"): return true;
43
47
  case hashString("keyboardDismissMode"): return true;
@@ -47,6 +47,8 @@ namespace margelo::nitro::nativelist::views {
47
47
  public:
48
48
  nitro::ReactProp<std::string> snapshotJson;
49
49
  nitro::ReactProp<std::string> containerSlotHeightsJson;
50
+ nitro::ReactProp<bool> topScrollEdgeHard;
51
+ nitro::ReactProp<double> topOverlayHeight;
50
52
  nitro::ReactProp<std::string> scrollPositionThresholdsJson;
51
53
  nitro::ReactProp<std::optional<std::function<void(bool /* isBeyondThreshold */)>>> onScrollPositionThresholdChange;
52
54
  nitro::ReactProp<NativeListKeyboardDismissMode> keyboardDismissMode;
@@ -63,6 +65,8 @@ namespace margelo::nitro::nativelist::views {
63
65
  bool hasSameProps(const HybridNativeListProps& other) const noexcept {
64
66
  return snapshotJson.hasSameValue(other.snapshotJson) &&
65
67
  containerSlotHeightsJson.hasSameValue(other.containerSlotHeightsJson) &&
68
+ topScrollEdgeHard.hasSameValue(other.topScrollEdgeHard) &&
69
+ topOverlayHeight.hasSameValue(other.topOverlayHeight) &&
66
70
  scrollPositionThresholdsJson.hasSameValue(other.scrollPositionThresholdsJson) &&
67
71
  onScrollPositionThresholdChange.hasSameValue(other.onScrollPositionThresholdChange) &&
68
72
  keyboardDismissMode.hasSameValue(other.keyboardDismissMode) &&
@@ -80,6 +84,8 @@ namespace margelo::nitro::nativelist::views {
80
84
  bool hasAnyProvidedProps() const noexcept {
81
85
  return snapshotJson.isProvided() ||
82
86
  containerSlotHeightsJson.isProvided() ||
87
+ topScrollEdgeHard.isProvided() ||
88
+ topOverlayHeight.isProvided() ||
83
89
  scrollPositionThresholdsJson.isProvided() ||
84
90
  onScrollPositionThresholdChange.isProvided() ||
85
91
  keyboardDismissMode.isProvided() ||
@@ -6,6 +6,8 @@
6
6
  "validAttributes": {
7
7
  "snapshotJson": true,
8
8
  "containerSlotHeightsJson": true,
9
+ "topScrollEdgeHard": true,
10
+ "topOverlayHeight": true,
9
11
  "scrollPositionThresholdsJson": true,
10
12
  "onScrollPositionThresholdChange": true,
11
13
  "keyboardDismissMode": true,
@@ -7,6 +7,8 @@ import type { HybridView, HybridViewMethods, HybridViewProps } from 'react-nativ
7
7
  export interface NativeListNativeProps extends HybridViewProps {
8
8
  snapshotJson: string;
9
9
  containerSlotHeightsJson: string;
10
+ topScrollEdgeHard: boolean;
11
+ topOverlayHeight: number;
10
12
  scrollPositionThresholdsJson: string;
11
13
  onScrollPositionThresholdChange?: (isBeyondThreshold: boolean) => void;
12
14
  keyboardDismissMode: NativeListKeyboardDismissMode;
@@ -79,6 +79,10 @@ export type NativeListProps = Omit<ViewProps, 'children'> & Readonly<{
79
79
  listHeader?: ReactNode;
80
80
  listFooter?: ReactNode;
81
81
  listEmpty?: ReactNode;
82
+ /** Fixed overlay above scrolling rows on iOS 26 and newer. */
83
+ topOverlay?: ReactNode;
84
+ /** Enables UIKit's hard top scroll-edge effect on iOS 26 and newer. */
85
+ topScrollEdgeEffect?: 'hard';
82
86
  /** Web only. Defaults to true and is ignored by the native host. */
83
87
  webVirtualizationEnabled?: boolean;
84
88
  /** Web only. Hosts the section index inside this container. */
@@ -75,6 +75,24 @@ namespace margelo::nitro::nativelist {
75
75
  static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* containerSlotHeightsJson */)>("setContainerSlotHeightsJson");
76
76
  method(_javaPart, jni::make_jstring(containerSlotHeightsJson));
77
77
  }
78
+ bool JHybridNativeListSpec::getTopScrollEdgeHard() {
79
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("getTopScrollEdgeHard");
80
+ auto __result = method(_javaPart);
81
+ return static_cast<bool>(__result);
82
+ }
83
+ void JHybridNativeListSpec::setTopScrollEdgeHard(bool topScrollEdgeHard) {
84
+ static const auto method = _javaPart->javaClassStatic()->getMethod<void(jboolean /* topScrollEdgeHard */)>("setTopScrollEdgeHard");
85
+ method(_javaPart, topScrollEdgeHard);
86
+ }
87
+ double JHybridNativeListSpec::getTopOverlayHeight() {
88
+ static const auto method = _javaPart->javaClassStatic()->getMethod<double()>("getTopOverlayHeight");
89
+ auto __result = method(_javaPart);
90
+ return __result;
91
+ }
92
+ void JHybridNativeListSpec::setTopOverlayHeight(double topOverlayHeight) {
93
+ static const auto method = _javaPart->javaClassStatic()->getMethod<void(double /* topOverlayHeight */)>("setTopOverlayHeight");
94
+ method(_javaPart, topOverlayHeight);
95
+ }
78
96
  std::string JHybridNativeListSpec::getScrollPositionThresholdsJson() {
79
97
  static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getScrollPositionThresholdsJson");
80
98
  auto __result = method(_javaPart);
@@ -54,6 +54,10 @@ namespace margelo::nitro::nativelist {
54
54
  void setSnapshotJson(const std::string& snapshotJson) override;
55
55
  std::string getContainerSlotHeightsJson() override;
56
56
  void setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) override;
57
+ bool getTopScrollEdgeHard() override;
58
+ void setTopScrollEdgeHard(bool topScrollEdgeHard) override;
59
+ double getTopOverlayHeight() override;
60
+ void setTopOverlayHeight(double topOverlayHeight) override;
57
61
  std::string getScrollPositionThresholdsJson() override;
58
62
  void setScrollPositionThresholdsJson(const std::string& scrollPositionThresholdsJson) override;
59
63
  std::optional<std::function<void(bool /* isBeyondThreshold */)>> getOnScrollPositionThresholdChange() override;
@@ -63,6 +63,16 @@ void JHybridNativeListStateUpdater::updateViewProps(jni::alias_ref<jni::JClass>
63
63
  : !newProps->containerSlotHeightsJson.hasSameValue(oldProps->containerSlotHeightsJson)) {
64
64
  hybridView->setContainerSlotHeightsJson(newProps->containerSlotHeightsJson.get());
65
65
  }
66
+ if (oldProps == nullptr
67
+ ? newProps->topScrollEdgeHard.isProvided()
68
+ : !newProps->topScrollEdgeHard.hasSameValue(oldProps->topScrollEdgeHard)) {
69
+ hybridView->setTopScrollEdgeHard(newProps->topScrollEdgeHard.get());
70
+ }
71
+ if (oldProps == nullptr
72
+ ? newProps->topOverlayHeight.isProvided()
73
+ : !newProps->topOverlayHeight.hasSameValue(oldProps->topOverlayHeight)) {
74
+ hybridView->setTopOverlayHeight(newProps->topOverlayHeight.get());
75
+ }
66
76
  if (oldProps == nullptr
67
77
  ? newProps->scrollPositionThresholdsJson.isProvided()
68
78
  : !newProps->scrollPositionThresholdsJson.hasSameValue(oldProps->scrollPositionThresholdsJson)) {
@@ -39,6 +39,18 @@ abstract class HybridNativeListSpec: HybridView() {
39
39
  @set:Keep
40
40
  abstract var containerSlotHeightsJson: String
41
41
 
42
+ @get:DoNotStrip
43
+ @get:Keep
44
+ @set:DoNotStrip
45
+ @set:Keep
46
+ abstract var topScrollEdgeHard: Boolean
47
+
48
+ @get:DoNotStrip
49
+ @get:Keep
50
+ @set:DoNotStrip
51
+ @set:Keep
52
+ abstract var topOverlayHeight: Double
53
+
42
54
  @get:DoNotStrip
43
55
  @get:Keep
44
56
  @set:DoNotStrip
@@ -86,6 +86,18 @@ namespace margelo::nitro::nativelist {
86
86
  inline void setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) noexcept override {
87
87
  _swiftPart.setContainerSlotHeightsJson(containerSlotHeightsJson);
88
88
  }
89
+ inline bool getTopScrollEdgeHard() noexcept override {
90
+ return _swiftPart.getTopScrollEdgeHard();
91
+ }
92
+ inline void setTopScrollEdgeHard(bool topScrollEdgeHard) noexcept override {
93
+ _swiftPart.setTopScrollEdgeHard(std::forward<decltype(topScrollEdgeHard)>(topScrollEdgeHard));
94
+ }
95
+ inline double getTopOverlayHeight() noexcept override {
96
+ return _swiftPart.getTopOverlayHeight();
97
+ }
98
+ inline void setTopOverlayHeight(double topOverlayHeight) noexcept override {
99
+ _swiftPart.setTopOverlayHeight(std::forward<decltype(topOverlayHeight)>(topOverlayHeight));
100
+ }
89
101
  inline std::string getScrollPositionThresholdsJson() noexcept override {
90
102
  auto __result = _swiftPart.getScrollPositionThresholdsJson();
91
103
  return __result;
@@ -124,6 +124,18 @@ using namespace margelo::nitro::nativelist::views;
124
124
  : !newViewProps.containerSlotHeightsJson.hasSameValue(oldViewProps->containerSlotHeightsJson)) {
125
125
  swiftPart.setContainerSlotHeightsJson(newViewProps.containerSlotHeightsJson.get());
126
126
  }
127
+ // topScrollEdgeHard: boolean
128
+ if (oldViewProps == nullptr
129
+ ? newViewProps.topScrollEdgeHard.isProvided()
130
+ : !newViewProps.topScrollEdgeHard.hasSameValue(oldViewProps->topScrollEdgeHard)) {
131
+ swiftPart.setTopScrollEdgeHard(newViewProps.topScrollEdgeHard.get());
132
+ }
133
+ // topOverlayHeight: number
134
+ if (oldViewProps == nullptr
135
+ ? newViewProps.topOverlayHeight.isProvided()
136
+ : !newViewProps.topOverlayHeight.hasSameValue(oldViewProps->topOverlayHeight)) {
137
+ swiftPart.setTopOverlayHeight(newViewProps.topOverlayHeight.get());
138
+ }
127
139
  // scrollPositionThresholdsJson: string
128
140
  if (oldViewProps == nullptr
129
141
  ? newViewProps.scrollPositionThresholdsJson.isProvided()
@@ -12,6 +12,8 @@ public protocol HybridNativeListSpec_protocol: HybridObject, HybridView {
12
12
  // Properties
13
13
  var snapshotJson: String { get set }
14
14
  var containerSlotHeightsJson: String { get set }
15
+ var topScrollEdgeHard: Bool { get set }
16
+ var topOverlayHeight: Double { get set }
15
17
  var scrollPositionThresholdsJson: String { get set }
16
18
  var onScrollPositionThresholdChange: ((_ isBeyondThreshold: Bool) -> Void)? { get set }
17
19
  var keyboardDismissMode: NativeListKeyboardDismissMode { get set }
@@ -143,6 +143,28 @@ open class HybridNativeListSpec_cxx {
143
143
  }
144
144
  }
145
145
 
146
+ public final var topScrollEdgeHard: Bool {
147
+ @inline(__always)
148
+ get {
149
+ return self.__implementation.topScrollEdgeHard
150
+ }
151
+ @inline(__always)
152
+ set {
153
+ self.__implementation.topScrollEdgeHard = newValue
154
+ }
155
+ }
156
+
157
+ public final var topOverlayHeight: Double {
158
+ @inline(__always)
159
+ get {
160
+ return self.__implementation.topOverlayHeight
161
+ }
162
+ @inline(__always)
163
+ set {
164
+ self.__implementation.topOverlayHeight = newValue
165
+ }
166
+ }
167
+
146
168
  public final var scrollPositionThresholdsJson: std.string {
147
169
  @inline(__always)
148
170
  get {
@@ -18,6 +18,10 @@ namespace margelo::nitro::nativelist {
18
18
  prototype.registerHybridSetter("snapshotJson", &HybridNativeListSpec::setSnapshotJson);
19
19
  prototype.registerHybridGetter("containerSlotHeightsJson", &HybridNativeListSpec::getContainerSlotHeightsJson);
20
20
  prototype.registerHybridSetter("containerSlotHeightsJson", &HybridNativeListSpec::setContainerSlotHeightsJson);
21
+ prototype.registerHybridGetter("topScrollEdgeHard", &HybridNativeListSpec::getTopScrollEdgeHard);
22
+ prototype.registerHybridSetter("topScrollEdgeHard", &HybridNativeListSpec::setTopScrollEdgeHard);
23
+ prototype.registerHybridGetter("topOverlayHeight", &HybridNativeListSpec::getTopOverlayHeight);
24
+ prototype.registerHybridSetter("topOverlayHeight", &HybridNativeListSpec::setTopOverlayHeight);
21
25
  prototype.registerHybridGetter("scrollPositionThresholdsJson", &HybridNativeListSpec::getScrollPositionThresholdsJson);
22
26
  prototype.registerHybridSetter("scrollPositionThresholdsJson", &HybridNativeListSpec::setScrollPositionThresholdsJson);
23
27
  prototype.registerHybridGetter("onScrollPositionThresholdChange", &HybridNativeListSpec::getOnScrollPositionThresholdChange);
@@ -58,6 +58,10 @@ namespace margelo::nitro::nativelist {
58
58
  virtual void setSnapshotJson(const std::string& snapshotJson) = 0;
59
59
  virtual std::string getContainerSlotHeightsJson() = 0;
60
60
  virtual void setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) = 0;
61
+ virtual bool getTopScrollEdgeHard() = 0;
62
+ virtual void setTopScrollEdgeHard(bool topScrollEdgeHard) = 0;
63
+ virtual double getTopOverlayHeight() = 0;
64
+ virtual void setTopOverlayHeight(double topOverlayHeight) = 0;
61
65
  virtual std::string getScrollPositionThresholdsJson() = 0;
62
66
  virtual void setScrollPositionThresholdsJson(const std::string& scrollPositionThresholdsJson) = 0;
63
67
  virtual std::optional<std::function<void(bool /* isBeyondThreshold */)>> getOnScrollPositionThresholdChange() = 0;
@@ -22,6 +22,8 @@ namespace margelo::nitro::nativelist::views {
22
22
  react::ViewProps(context, sourceProps, rawProps, filterObjectKeys),
23
23
  snapshotJson(nitro::ReactProp<std::string>::fromRawValue("NativeList", "snapshotJson", rawProps, sourceProps.snapshotJson)),
24
24
  containerSlotHeightsJson(nitro::ReactProp<std::string>::fromRawValue("NativeList", "containerSlotHeightsJson", rawProps, sourceProps.containerSlotHeightsJson)),
25
+ topScrollEdgeHard(nitro::ReactProp<bool>::fromRawValue("NativeList", "topScrollEdgeHard", rawProps, sourceProps.topScrollEdgeHard)),
26
+ topOverlayHeight(nitro::ReactProp<double>::fromRawValue("NativeList", "topOverlayHeight", rawProps, sourceProps.topOverlayHeight)),
25
27
  scrollPositionThresholdsJson(nitro::ReactProp<std::string>::fromRawValue("NativeList", "scrollPositionThresholdsJson", rawProps, sourceProps.scrollPositionThresholdsJson)),
26
28
  onScrollPositionThresholdChange(nitro::ReactProp<std::optional<std::function<void(bool /* isBeyondThreshold */)>>>::fromRawValue("NativeList", "onScrollPositionThresholdChange", rawProps, sourceProps.onScrollPositionThresholdChange)),
27
29
  keyboardDismissMode(nitro::ReactProp<NativeListKeyboardDismissMode>::fromRawValue("NativeList", "keyboardDismissMode", rawProps, sourceProps.keyboardDismissMode)),
@@ -38,6 +40,8 @@ namespace margelo::nitro::nativelist::views {
38
40
  switch (hashString(propName)) {
39
41
  case hashString("snapshotJson"): return true;
40
42
  case hashString("containerSlotHeightsJson"): return true;
43
+ case hashString("topScrollEdgeHard"): return true;
44
+ case hashString("topOverlayHeight"): return true;
41
45
  case hashString("scrollPositionThresholdsJson"): return true;
42
46
  case hashString("onScrollPositionThresholdChange"): return true;
43
47
  case hashString("keyboardDismissMode"): return true;
@@ -47,6 +47,8 @@ namespace margelo::nitro::nativelist::views {
47
47
  public:
48
48
  nitro::ReactProp<std::string> snapshotJson;
49
49
  nitro::ReactProp<std::string> containerSlotHeightsJson;
50
+ nitro::ReactProp<bool> topScrollEdgeHard;
51
+ nitro::ReactProp<double> topOverlayHeight;
50
52
  nitro::ReactProp<std::string> scrollPositionThresholdsJson;
51
53
  nitro::ReactProp<std::optional<std::function<void(bool /* isBeyondThreshold */)>>> onScrollPositionThresholdChange;
52
54
  nitro::ReactProp<NativeListKeyboardDismissMode> keyboardDismissMode;
@@ -63,6 +65,8 @@ namespace margelo::nitro::nativelist::views {
63
65
  bool hasSameProps(const HybridNativeListProps& other) const noexcept {
64
66
  return snapshotJson.hasSameValue(other.snapshotJson) &&
65
67
  containerSlotHeightsJson.hasSameValue(other.containerSlotHeightsJson) &&
68
+ topScrollEdgeHard.hasSameValue(other.topScrollEdgeHard) &&
69
+ topOverlayHeight.hasSameValue(other.topOverlayHeight) &&
66
70
  scrollPositionThresholdsJson.hasSameValue(other.scrollPositionThresholdsJson) &&
67
71
  onScrollPositionThresholdChange.hasSameValue(other.onScrollPositionThresholdChange) &&
68
72
  keyboardDismissMode.hasSameValue(other.keyboardDismissMode) &&
@@ -80,6 +84,8 @@ namespace margelo::nitro::nativelist::views {
80
84
  bool hasAnyProvidedProps() const noexcept {
81
85
  return snapshotJson.isProvided() ||
82
86
  containerSlotHeightsJson.isProvided() ||
87
+ topScrollEdgeHard.isProvided() ||
88
+ topOverlayHeight.isProvided() ||
83
89
  scrollPositionThresholdsJson.isProvided() ||
84
90
  onScrollPositionThresholdChange.isProvided() ||
85
91
  keyboardDismissMode.isProvided() ||
@@ -6,6 +6,8 @@
6
6
  "validAttributes": {
7
7
  "snapshotJson": true,
8
8
  "containerSlotHeightsJson": true,
9
+ "topScrollEdgeHard": true,
10
+ "topOverlayHeight": true,
9
11
  "scrollPositionThresholdsJson": true,
10
12
  "onScrollPositionThresholdChange": true,
11
13
  "keyboardDismissMode": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-native-list",
3
- "version": "3.0.177",
3
+ "version": "3.0.179-alpha.20261015",
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",
@@ -87,8 +87,8 @@
87
87
  "typescript": "^5.9.2"
88
88
  },
89
89
  "peerDependencies": {
90
- "@onekeyfe/react-native-image": "3.0.177",
91
- "@onekeyfe/react-native-native-logger": "3.0.177",
90
+ "@onekeyfe/react-native-image": "3.0.179-alpha.20261015",
91
+ "@onekeyfe/react-native-native-logger": "3.0.179-alpha.20261015",
92
92
  "react": "*",
93
93
  "react-native": "*",
94
94
  "react-native-nitro-modules": "0.37.0"
@@ -12,6 +12,8 @@ import type {
12
12
  export interface NativeListNativeProps extends HybridViewProps {
13
13
  snapshotJson: string;
14
14
  containerSlotHeightsJson: string;
15
+ topScrollEdgeHard: boolean;
16
+ topOverlayHeight: number;
15
17
  scrollPositionThresholdsJson: string;
16
18
  onScrollPositionThresholdChange?: (isBeyondThreshold: boolean) => void;
17
19
  keyboardDismissMode: NativeListKeyboardDismissMode;
@@ -14,7 +14,7 @@ import {
14
14
  NativeAvatarPrefetchModel,
15
15
  NativeAvatarPrefetchQueue,
16
16
  } from './avatarPrefetch';
17
- import { View } from 'react-native';
17
+ import { Platform, View } from 'react-native';
18
18
  import { callback, getHostComponent } from 'react-native-nitro-modules';
19
19
  import type {
20
20
  NativeListMethods,
@@ -101,6 +101,8 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
101
101
  listHeader,
102
102
  listFooter,
103
103
  listEmpty,
104
+ topOverlay,
105
+ topScrollEdgeEffect,
104
106
  scrollPositionThresholds,
105
107
  webVirtualizationEnabled: _webVirtualizationEnabled,
106
108
  webSectionIndexContainerRef: _webSectionIndexContainerRef,
@@ -124,6 +126,12 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
124
126
  forwardedRef
125
127
  ) {
126
128
  const [slotHeights, setSlotHeights] = useState([0, 0, 0]);
129
+ const [topOverlayHeight, setTopOverlayHeight] = useState(0);
130
+ const hasTopOverlay =
131
+ Platform?.OS === 'ios' &&
132
+ Number(Platform.Version) >= 26 &&
133
+ topScrollEdgeEffect === 'hard' &&
134
+ topOverlay != null;
127
135
  const containerSlotHeightsJson = JSON.stringify(slotHeights);
128
136
  const slotInset =
129
137
  snapshot.layout.contentPaddingHorizontal ??
@@ -506,6 +514,8 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
506
514
  {...nativeCallbacks}
507
515
  snapshotJson={snapshotJson}
508
516
  containerSlotHeightsJson={containerSlotHeightsJson}
517
+ topScrollEdgeHard={hasTopOverlay}
518
+ topOverlayHeight={hasTopOverlay ? topOverlayHeight : 0}
509
519
  scrollPositionThresholdsJson={scrollPositionThresholdsJson}
510
520
  keyboardDismissMode={keyboardDismissMode}
511
521
  keyboardShouldPersistTaps={normalizedKeyboardShouldPersistTaps}
@@ -534,6 +544,20 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
534
544
  {content}
535
545
  </View>
536
546
  ))}
547
+ {hasTopOverlay ? (
548
+ <View
549
+ collapsable={false}
550
+ style={{ position: 'absolute', top: 0, left: 0, right: 0 }}
551
+ onLayout={(event) => {
552
+ const height = Math.ceil(event.nativeEvent.layout.height);
553
+ setTopOverlayHeight((previous) =>
554
+ previous === height ? previous : height
555
+ );
556
+ }}
557
+ >
558
+ {topOverlay}
559
+ </View>
560
+ ) : null}
537
561
  </NativeListHost>
538
562
  );
539
563
  }
@@ -123,6 +123,10 @@ export type NativeListProps = Omit<ViewProps, 'children'> &
123
123
  listHeader?: ReactNode;
124
124
  listFooter?: ReactNode;
125
125
  listEmpty?: ReactNode;
126
+ /** Fixed overlay above scrolling rows on iOS 26 and newer. */
127
+ topOverlay?: ReactNode;
128
+ /** Enables UIKit's hard top scroll-edge effect on iOS 26 and newer. */
129
+ topScrollEdgeEffect?: 'hard';
126
130
  /** Web only. Defaults to true and is ignored by the native host. */
127
131
  webVirtualizationEnabled?: boolean;
128
132
  /** Web only. Hosts the section index inside this container. */
@@ -85,6 +85,8 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
85
85
  listHeader: _listHeader,
86
86
  listEmpty: _listEmpty,
87
87
  listFooter: _listFooter,
88
+ topOverlay: _topOverlay,
89
+ topScrollEdgeEffect: _topScrollEdgeEffect,
88
90
  scrollPositionThresholds: _scrollPositionThresholds,
89
91
  onScrollPositionThresholdChange: _onScrollPositionThresholdChange,
90
92
  webVirtualizationEnabled = true,