@lodev09/react-native-true-sheet 3.0.0-beta.7 → 3.0.0-beta.9

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 (35) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +51 -49
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +10 -18
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +76 -20
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderView.kt +38 -0
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderViewManager.kt +21 -0
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +1 -0
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +81 -147
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +309 -415
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +2 -4
  10. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +116 -0
  11. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +33 -5
  12. package/ios/TrueSheetContainerView.h +20 -2
  13. package/ios/TrueSheetContainerView.mm +61 -14
  14. package/ios/TrueSheetContentView.h +4 -2
  15. package/ios/TrueSheetContentView.mm +29 -72
  16. package/ios/TrueSheetFooterView.mm +2 -2
  17. package/ios/TrueSheetHeaderView.h +29 -0
  18. package/ios/TrueSheetHeaderView.mm +60 -0
  19. package/ios/TrueSheetView.mm +178 -232
  20. package/ios/TrueSheetViewController.h +1 -2
  21. package/ios/TrueSheetViewController.mm +126 -236
  22. package/ios/utils/LayoutUtil.h +2 -1
  23. package/ios/utils/LayoutUtil.mm +14 -1
  24. package/lib/module/TrueSheet.js +10 -2
  25. package/lib/module/TrueSheet.js.map +1 -1
  26. package/lib/module/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
  27. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  28. package/lib/typescript/src/TrueSheet.types.d.ts +12 -9
  29. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  30. package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts +6 -0
  31. package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts.map +1 -0
  32. package/package.json +4 -1
  33. package/src/TrueSheet.tsx +10 -0
  34. package/src/TrueSheet.types.ts +13 -11
  35. package/src/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
@@ -37,7 +37,7 @@ class TrueSheetViewManager :
37
37
 
38
38
  override fun onAfterUpdateTransaction(view: TrueSheetView) {
39
39
  super.onAfterUpdateTransaction(view)
40
- view.showOrUpdate()
40
+ view.finalizeUpdates()
41
41
  }
42
42
 
43
43
  override fun addEventEmitters(reactContext: ThemedReactContext, view: TrueSheetView) {
@@ -97,9 +97,7 @@ class TrueSheetViewManager :
97
97
 
98
98
  @ReactProp(name = "cornerRadius", defaultDouble = -1.0)
99
99
  override fun setCornerRadius(view: TrueSheetView, radius: Double) {
100
- if (radius >= 0) {
101
- view.setCornerRadius(radius.dpToPx())
102
- }
100
+ view.setCornerRadius(radius.dpToPx())
103
101
  }
104
102
 
105
103
  @ReactProp(name = "grabber", defaultBoolean = true)
@@ -0,0 +1,116 @@
1
+ package com.lodev09.truesheet.core
2
+
3
+ import androidx.appcompat.app.AppCompatActivity
4
+ import androidx.fragment.app.Fragment
5
+ import androidx.fragment.app.FragmentManager
6
+ import com.facebook.react.bridge.ReactContext
7
+
8
+ private const val RN_SCREENS_PACKAGE = "com.swmansion.rnscreens"
9
+
10
+ /**
11
+ * Observes fragment lifecycle to detect react-native-screens modal presentation.
12
+ * Automatically notifies when modals are presented/dismissed.
13
+ */
14
+ class RNScreensFragmentObserver(
15
+ private val reactContext: ReactContext,
16
+ private val onModalPresented: () -> Unit,
17
+ private val onModalDismissed: () -> Unit
18
+ ) {
19
+ private var fragmentLifecycleCallback: FragmentManager.FragmentLifecycleCallbacks? = null
20
+ private val activeModalFragments: MutableSet<Fragment> = mutableSetOf()
21
+
22
+ /**
23
+ * Check if there are active modal fragments being tracked.
24
+ */
25
+ fun hasActiveModals(): Boolean = activeModalFragments.isNotEmpty()
26
+
27
+ /**
28
+ * Start observing fragment lifecycle events.
29
+ */
30
+ fun start() {
31
+ val activity = reactContext.currentActivity as? AppCompatActivity ?: return
32
+ val fragmentManager = activity.supportFragmentManager
33
+
34
+ fragmentLifecycleCallback = object : FragmentManager.FragmentLifecycleCallbacks() {
35
+ override fun onFragmentAttached(fm: FragmentManager, fragment: Fragment, context: android.content.Context) {
36
+ super.onFragmentAttached(fm, fragment, context)
37
+
38
+ if (isModalFragment(fragment)) {
39
+ activeModalFragments.add(fragment)
40
+
41
+ // Notify when the first modal is attached
42
+ if (activeModalFragments.size == 1) {
43
+ onModalPresented()
44
+ }
45
+ }
46
+ }
47
+
48
+ override fun onFragmentStopped(fm: FragmentManager, fragment: Fragment) {
49
+ super.onFragmentStopped(fm, fragment)
50
+
51
+ if (activeModalFragments.contains(fragment)) {
52
+ activeModalFragments.remove(fragment)
53
+
54
+ // Notify when all modals are dismissed
55
+ if (activeModalFragments.isEmpty()) {
56
+ onModalDismissed()
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallback!!, true)
63
+ }
64
+
65
+ /**
66
+ * Stop observing and cleanup.
67
+ */
68
+ fun stop() {
69
+ fragmentLifecycleCallback?.let { callback ->
70
+ val activity = reactContext.currentActivity as? AppCompatActivity
71
+ activity?.supportFragmentManager?.unregisterFragmentLifecycleCallbacks(callback)
72
+ }
73
+ fragmentLifecycleCallback = null
74
+ activeModalFragments.clear()
75
+ }
76
+
77
+ companion object {
78
+ /**
79
+ * Check if fragment is from react-native-screens.
80
+ */
81
+ private fun isScreensFragment(fragment: Fragment): Boolean = fragment.javaClass.name.startsWith(RN_SCREENS_PACKAGE)
82
+
83
+ /**
84
+ * Check if fragment is a react-native-screens modal (fullScreenModal, transparentModal, or formSheet).
85
+ * Uses reflection to check the fragment's screen.stackPresentation property.
86
+ */
87
+ private fun isModalFragment(fragment: Fragment): Boolean {
88
+ val className = fragment.javaClass.name
89
+
90
+ if (!isScreensFragment(fragment)) {
91
+ return false
92
+ }
93
+
94
+ // ScreenModalFragment is always a modal (used for formSheet with BottomSheetDialog)
95
+ if (className.contains("ScreenModalFragment")) {
96
+ return true
97
+ }
98
+
99
+ // For ScreenStackFragment, check the screen's stackPresentation via reflection
100
+ try {
101
+ val getScreenMethod = fragment.javaClass.getMethod("getScreen")
102
+ val screen = getScreenMethod.invoke(fragment) ?: return false
103
+
104
+ val getStackPresentationMethod = screen.javaClass.getMethod("getStackPresentation")
105
+ val stackPresentation = getStackPresentationMethod.invoke(screen) ?: return false
106
+
107
+ val presentationName = stackPresentation.toString()
108
+ return presentationName == "MODAL" ||
109
+ presentationName == "TRANSPARENT_MODAL" ||
110
+ presentationName == "FORM_SHEET"
111
+ } catch (e: Exception) {
112
+ return false
113
+ }
114
+ }
115
+ }
116
+ }
@@ -89,14 +89,42 @@ object ScreenUtils {
89
89
  }
90
90
 
91
91
  /**
92
- * Get the Y coordinate of a view in screen coordinates
92
+ * Get the screen width
93
93
  *
94
- * @param view The view to get screen Y coordinate for
95
- * @return Y coordinate in screen space
94
+ * @param context React context
95
+ * @return Screen width in pixels
96
96
  */
97
- fun getScreenY(view: View): Int {
97
+ fun getScreenWidth(context: ReactContext): Int {
98
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
99
+
100
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
101
+ val windowMetrics = windowManager.currentWindowMetrics
102
+ windowMetrics.bounds.width()
103
+ } else {
104
+ val displayMetrics = DisplayMetrics()
105
+ @Suppress("DEPRECATION")
106
+ windowManager.defaultDisplay.getMetrics(displayMetrics)
107
+ displayMetrics.widthPixels
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Get the location of a view in screen coordinates
113
+ *
114
+ * @param view The view to get screen location for
115
+ * @return IntArray with [x, y] coordinates in screen space
116
+ */
117
+ fun getScreenLocation(view: View): IntArray {
98
118
  val location = IntArray(2)
99
119
  view.getLocationOnScreen(location)
100
- return location[1]
120
+ return location
101
121
  }
122
+
123
+ /**
124
+ * Get the Y coordinate of a view in screen coordinates
125
+ *
126
+ * @param view The view to get screen Y coordinate for
127
+ * @return Y coordinate in screen space
128
+ */
129
+ fun getScreenY(view: View): Int = getScreenLocation(view)[1]
102
130
  }
@@ -21,6 +21,14 @@ NS_ASSUME_NONNULL_BEGIN
21
21
  */
22
22
  - (void)containerViewContentDidChangeSize:(CGSize)newSize;
23
23
 
24
+ @optional
25
+
26
+ /**
27
+ * Called when the header size changes
28
+ * @param newSize The new size of the header
29
+ */
30
+ - (void)containerViewHeaderDidChangeSize:(CGSize)newSize;
31
+
24
32
  @end
25
33
 
26
34
  @interface TrueSheetContainerView : RCTViewComponentView
@@ -30,20 +38,30 @@ NS_ASSUME_NONNULL_BEGIN
30
38
  */
31
39
  @property (nonatomic, weak, nullable) id<TrueSheetContainerViewDelegate> delegate;
32
40
 
41
+ /**
42
+ * Enable ScrollView pinning
43
+ */
44
+ @property (nonatomic, assign) BOOL scrollViewPinningEnabled;
45
+
33
46
  /**
34
47
  * Returns the current content height
35
48
  */
36
49
  - (CGFloat)contentHeight;
37
50
 
51
+ /**
52
+ * Returns the current header height
53
+ */
54
+ - (CGFloat)headerHeight;
55
+
38
56
  /**
39
57
  * Updates footer layout constraints if needed
40
58
  */
41
59
  - (void)layoutFooter;
42
60
 
43
61
  /**
44
- * setup ScrollView pinning
62
+ * Setup ScrollView pinning
45
63
  */
46
- - (void)setupContentScrollViewPinning:(BOOL)pinned;
64
+ - (void)setupContentScrollViewPinning;
47
65
 
48
66
  @end
49
67
 
@@ -9,26 +9,32 @@
9
9
  #ifdef RCT_NEW_ARCH_ENABLED
10
10
 
11
11
  #import "TrueSheetContainerView.h"
12
+ #import "TrueSheetContentView.h"
13
+ #import "TrueSheetFooterView.h"
14
+ #import "TrueSheetHeaderView.h"
15
+
12
16
  #import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
13
17
  #import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
14
18
  #import <react/renderer/components/TrueSheetSpec/Props.h>
15
19
  #import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
16
- #import "TrueSheetContentView.h"
17
- #import "TrueSheetFooterView.h"
18
20
 
19
21
  #import <React/RCTConversions.h>
20
22
  #import <React/RCTLog.h>
21
23
 
22
24
  using namespace facebook::react;
23
25
 
24
- @interface TrueSheetContainerView () <TrueSheetContentViewDelegate>
26
+ @interface TrueSheetContainerView () <TrueSheetContentViewDelegate, TrueSheetHeaderViewDelegate>
25
27
  @end
26
28
 
27
29
  @implementation TrueSheetContainerView {
28
30
  TrueSheetContentView *_contentView;
31
+ TrueSheetHeaderView *_headerView;
29
32
  TrueSheetFooterView *_footerView;
33
+ BOOL _scrollViewPinningSet;
30
34
  }
31
35
 
36
+ #pragma mark - Initialization
37
+
32
38
  + (ComponentDescriptorProvider)componentDescriptorProvider {
33
39
  return concreteComponentDescriptorProvider<TrueSheetContainerViewComponentDescriptor>();
34
40
  }
@@ -39,17 +45,20 @@ using namespace facebook::react;
39
45
  _props = defaultProps;
40
46
 
41
47
  self.backgroundColor = [UIColor clearColor];
42
-
43
48
  _contentView = nil;
49
+ _headerView = nil;
44
50
  _footerView = nil;
51
+ _scrollViewPinningSet = NO;
45
52
  }
46
53
  return self;
47
54
  }
48
55
 
56
+ #pragma mark - Layout
57
+
49
58
  - (void)layoutSubviews {
50
59
  [super layoutSubviews];
51
60
 
52
- // Override Yoga layout - fill the entire parent (controller's view)
61
+ // Override Yoga layout to fill parent (controller's view)
53
62
  if (self.superview) {
54
63
  CGRect parentBounds = self.superview.bounds;
55
64
  if (!CGRectEqualToRect(self.frame, parentBounds)) {
@@ -62,9 +71,12 @@ using namespace facebook::react;
62
71
  return _contentView ? _contentView.frame.size.height : 0;
63
72
  }
64
73
 
74
+ - (CGFloat)headerHeight {
75
+ return _headerView ? _headerView.frame.size.height : 0;
76
+ }
77
+
65
78
  - (void)layoutFooter {
66
79
  if (_footerView) {
67
- // Force footer to reapply constraints on size change
68
80
  CGFloat height = _footerView.frame.size.height;
69
81
  if (height > 0) {
70
82
  [_footerView setupConstraintsWithHeight:height];
@@ -72,42 +84,70 @@ using namespace facebook::react;
72
84
  }
73
85
  }
74
86
 
75
- - (void)setupContentScrollViewPinning:(BOOL)pinned {
76
- if (_contentView) {
77
- [_contentView setupScrollViewPinning:pinned];
87
+ - (void)setScrollViewPinningEnabled:(BOOL)scrollViewPinningEnabled {
88
+ _scrollViewPinningEnabled = scrollViewPinningEnabled;
89
+ _scrollViewPinningSet = YES;
90
+ }
91
+
92
+ - (void)setupContentScrollViewPinning {
93
+ if (_scrollViewPinningSet && _contentView) {
94
+ [_contentView setupScrollViewPinning:_scrollViewPinningEnabled withHeaderView:_headerView];
78
95
  }
79
96
  }
80
97
 
98
+ #pragma mark - Child Component Mounting
99
+
81
100
  - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
82
101
  [super mountChildComponentView:childComponentView index:index];
83
102
 
84
- // Handle content view mounting
85
103
  if ([childComponentView isKindOfClass:[TrueSheetContentView class]]) {
86
104
  if (_contentView != nil) {
87
105
  RCTLogWarn(@"TrueSheet: Container can only have one content component.");
88
106
  return;
89
107
  }
90
-
91
108
  _contentView = (TrueSheetContentView *)childComponentView;
92
109
  _contentView.delegate = self;
93
110
  }
94
111
 
95
- // Handle footer view mounting
112
+ if ([childComponentView isKindOfClass:[TrueSheetHeaderView class]]) {
113
+ if (_headerView != nil) {
114
+ RCTLogWarn(@"TrueSheet: Container can only have one header component.");
115
+ return;
116
+ }
117
+ _headerView = (TrueSheetHeaderView *)childComponentView;
118
+ _headerView.delegate = self;
119
+
120
+ if (_contentView) {
121
+ [self setupContentScrollViewPinning];
122
+ }
123
+ [self headerViewDidChangeSize:_headerView.frame.size];
124
+ }
125
+
96
126
  if ([childComponentView isKindOfClass:[TrueSheetFooterView class]]) {
97
127
  if (_footerView != nil) {
98
128
  RCTLogWarn(@"TrueSheet: Container can only have one footer component.");
99
129
  return;
100
130
  }
101
-
102
131
  _footerView = (TrueSheetFooterView *)childComponentView;
103
132
  }
104
133
  }
105
134
 
106
135
  - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
107
136
  if ([childComponentView isKindOfClass:[TrueSheetContentView class]]) {
137
+ _contentView.delegate = nil;
108
138
  _contentView = nil;
109
139
  }
110
140
 
141
+ if ([childComponentView isKindOfClass:[TrueSheetHeaderView class]]) {
142
+ _headerView.delegate = nil;
143
+ _headerView = nil;
144
+
145
+ if (_contentView) {
146
+ [self setupContentScrollViewPinning];
147
+ }
148
+ [self headerViewDidChangeSize:CGSizeZero];
149
+ }
150
+
111
151
  if ([childComponentView isKindOfClass:[TrueSheetFooterView class]]) {
112
152
  _footerView = nil;
113
153
  }
@@ -122,12 +162,19 @@ using namespace facebook::react;
122
162
  #pragma mark - TrueSheetContentViewDelegate
123
163
 
124
164
  - (void)contentViewDidChangeSize:(CGSize)newSize {
125
- // Forward content size changes to host view for sheet resizing
126
165
  if ([self.delegate respondsToSelector:@selector(containerViewContentDidChangeSize:)]) {
127
166
  [self.delegate containerViewContentDidChangeSize:newSize];
128
167
  }
129
168
  }
130
169
 
170
+ #pragma mark - TrueSheetHeaderViewDelegate
171
+
172
+ - (void)headerViewDidChangeSize:(CGSize)newSize {
173
+ if ([self.delegate respondsToSelector:@selector(containerViewHeaderDidChangeSize:)]) {
174
+ [self.delegate containerViewHeaderDidChangeSize:newSize];
175
+ }
176
+ }
177
+
131
178
  @end
132
179
 
133
180
  #endif
@@ -31,9 +31,11 @@ NS_ASSUME_NONNULL_BEGIN
31
31
  - (RCTScrollViewComponentView *_Nullable)findScrollView;
32
32
 
33
33
  /**
34
- * setup ScrollView pinning
34
+ * Setup ScrollView pinning
35
+ * @param pinned Whether to pin the scroll view
36
+ * @param headerView Optional header view to pin below (can be nil)
35
37
  */
36
- - (void)setupScrollViewPinning:(BOOL)pinned;
38
+ - (void)setupScrollViewPinning:(BOOL)pinned withHeaderView:(nullable UIView *)headerView;
37
39
 
38
40
  @end
39
41
 
@@ -53,98 +53,55 @@ using namespace facebook::react;
53
53
  }
54
54
  }
55
55
 
56
- - (void)unpinScrollView {
56
+ - (void)unpinScrollViewFromParentView:(UIView *)parentView {
57
57
  // Unpin previous scroll view if exists
58
58
  if (_pinnedScrollView) {
59
- [LayoutUtil unpinView:_pinnedScrollView];
59
+ [LayoutUtil unpinView:_pinnedScrollView fromParentView:parentView];
60
60
  }
61
61
  }
62
62
 
63
- - (void)setupScrollViewPinning:(BOOL)pinned {
63
+ - (void)setupScrollViewPinning:(BOOL)pinned withHeaderView:(UIView *)headerView {
64
+ // Pin to container view (parent of content view)
65
+ UIView *containerView = self.superview;
66
+
64
67
  if (!pinned) {
65
- [self unpinScrollView];
68
+ [self unpinScrollViewFromParentView:containerView];
69
+ _pinnedScrollView = nil;
66
70
  return;
67
71
  }
68
72
 
69
73
  // Auto-detect and pin scroll views for proper sheet scrolling behavior
70
- // Pinning ensures ScrollView fills the sheet area and scrolls correctly with the sheet
71
- UIView *topSibling = nil;
72
- RCTScrollViewComponentView *scrollView = [self findScrollView:&topSibling];
73
-
74
- if (scrollView && scrollView != _pinnedScrollView) {
75
- [self unpinScrollView];
76
-
77
- // Pin to container view to enable proper scrolling within the sheet
78
- UIView *containerView = self.superview;
79
- if (containerView) {
80
- if (topSibling) {
81
- // Pin ScrollView below the top sibling
82
- [LayoutUtil pinView:scrollView
83
- toParentView:containerView
84
- withTopView:topSibling
85
- edges:UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom];
86
- } else {
87
- // No top sibling, pin to all edges of container (original behavior)
88
- [LayoutUtil pinView:scrollView toParentView:containerView edges:UIRectEdgeAll];
89
- }
90
-
91
- _pinnedScrollView = scrollView;
74
+ // Pinning ensures ScrollView fills the available area and scrolls correctly with the sheet
75
+ RCTScrollViewComponentView *scrollView = [self findScrollView];
76
+
77
+ if (scrollView && containerView) {
78
+ // Always unpin first to remove old constraints
79
+ [self unpinScrollViewFromParentView:containerView];
80
+
81
+ if (headerView) {
82
+ // Pin ScrollView below the header view
83
+ [LayoutUtil pinView:scrollView
84
+ toParentView:containerView
85
+ withTopView:headerView
86
+ edges:UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom];
87
+ } else {
88
+ // No header, pin to all edges of container
89
+ [LayoutUtil pinView:scrollView toParentView:containerView edges:UIRectEdgeAll];
92
90
  }
91
+
92
+ _pinnedScrollView = scrollView;
93
93
  }
94
94
  }
95
95
 
96
96
  - (RCTScrollViewComponentView *)findScrollView {
97
- return [self findScrollView:nil];
98
- }
99
-
100
- - (RCTScrollViewComponentView *)findScrollView:(UIView **)outTopSibling {
101
- if (self.subviews.count == 0) {
102
- return nil;
103
- }
104
-
105
- RCTScrollViewComponentView *scrollView = nil;
106
- UIView *topSibling = nil;
107
-
108
97
  // Check first-level children for scroll views (ScrollView or FlatList)
109
98
  for (UIView *subview in self.subviews) {
110
99
  if ([subview isKindOfClass:RCTScrollViewComponentView.class]) {
111
- scrollView = static_cast<RCTScrollViewComponentView *>(subview);
112
-
113
- // Find the view positioned directly above this ScrollView by frame position
114
- if (self.subviews.count > 1) {
115
- CGFloat scrollViewTop = CGRectGetMinY(scrollView.frame);
116
- CGFloat closestDistance = CGFLOAT_MAX;
117
-
118
- for (UIView *sibling in self.subviews) {
119
- // Skip the ScrollView itself
120
- if (sibling == scrollView) {
121
- continue;
122
- }
123
-
124
- CGFloat siblingBottom = CGRectGetMaxY(sibling.frame);
125
-
126
- // Check if this sibling is positioned above the ScrollView
127
- if (siblingBottom <= scrollViewTop) {
128
- CGFloat distance = scrollViewTop - siblingBottom;
129
-
130
- // Find the closest view above (smallest distance)
131
- if (distance < closestDistance) {
132
- closestDistance = distance;
133
- topSibling = sibling;
134
- }
135
- }
136
- }
137
- }
138
-
139
- break; // Found ScrollView, no need to continue
100
+ return (RCTScrollViewComponentView *)subview;
140
101
  }
141
102
  }
142
103
 
143
- if (outTopSibling) {
144
- *outTopSibling = topSibling;
145
- }
146
-
147
- return scrollView;
104
+ return nil;
148
105
  }
149
106
 
150
107
  - (void)prepareForRecycle {
@@ -152,7 +109,7 @@ using namespace facebook::react;
152
109
 
153
110
  // Remove scroll view constraints
154
111
  if (_pinnedScrollView) {
155
- [LayoutUtil unpinView:_pinnedScrollView];
112
+ [LayoutUtil unpinView:_pinnedScrollView fromParentView:self.superview];
156
113
  _pinnedScrollView = nil;
157
114
  }
158
115
  }
@@ -48,7 +48,7 @@ using namespace facebook::react;
48
48
  }
49
49
 
50
50
  // Remove existing constraints before applying new ones
51
- [LayoutUtil unpinView:self];
51
+ [LayoutUtil unpinView:self fromParentView:parentView];
52
52
 
53
53
  // Pin footer to bottom and sides of container with specific height
54
54
  [LayoutUtil pinView:self
@@ -90,7 +90,7 @@ using namespace facebook::react;
90
90
  [super prepareForRecycle];
91
91
 
92
92
  // Remove footer constraints
93
- [LayoutUtil unpinView:self];
93
+ [LayoutUtil unpinView:self fromParentView:self.superview];
94
94
 
95
95
  _lastHeight = 0;
96
96
  _didInitialLayout = NO;
@@ -0,0 +1,29 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import <React/RCTViewComponentView.h>
12
+ #import <UIKit/UIKit.h>
13
+
14
+ NS_ASSUME_NONNULL_BEGIN
15
+
16
+ @protocol TrueSheetHeaderViewDelegate <NSObject>
17
+ @optional
18
+ - (void)headerViewDidChangeSize:(CGSize)size;
19
+ @end
20
+
21
+ @interface TrueSheetHeaderView : RCTViewComponentView
22
+
23
+ @property (nonatomic, weak, nullable) id<TrueSheetHeaderViewDelegate> delegate;
24
+
25
+ @end
26
+
27
+ NS_ASSUME_NONNULL_END
28
+
29
+ #endif
@@ -0,0 +1,60 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import "TrueSheetHeaderView.h"
12
+ #import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
13
+ #import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
14
+ #import <react/renderer/components/TrueSheetSpec/Props.h>
15
+ #import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
16
+ #import "utils/LayoutUtil.h"
17
+
18
+ using namespace facebook::react;
19
+
20
+ @implementation TrueSheetHeaderView {
21
+ CGSize _lastSize;
22
+ }
23
+
24
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
25
+ return concreteComponentDescriptorProvider<TrueSheetHeaderViewComponentDescriptor>();
26
+ }
27
+
28
+ - (instancetype)initWithFrame:(CGRect)frame {
29
+ if (self = [super initWithFrame:frame]) {
30
+ static const auto defaultProps = std::make_shared<const TrueSheetHeaderViewProps>();
31
+ _props = defaultProps;
32
+
33
+ _lastSize = CGSizeZero;
34
+ }
35
+ return self;
36
+ }
37
+
38
+ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
39
+ oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics {
40
+ [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
41
+
42
+ CGSize newSize = CGSizeMake(layoutMetrics.frame.size.width, layoutMetrics.frame.size.height);
43
+
44
+ // Notify delegate when header size changes
45
+ if (!CGSizeEqualToSize(newSize, _lastSize)) {
46
+ _lastSize = newSize;
47
+ if ([self.delegate respondsToSelector:@selector(headerViewDidChangeSize:)]) {
48
+ [self.delegate headerViewDidChangeSize:newSize];
49
+ }
50
+ }
51
+ }
52
+
53
+ - (void)prepareForRecycle {
54
+ [super prepareForRecycle];
55
+ _lastSize = CGSizeZero;
56
+ }
57
+
58
+ @end
59
+
60
+ #endif