@lodev09/react-native-true-sheet 3.0.0-beta.11 → 3.0.0-beta.13
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 +1 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +3 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +33 -45
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +151 -22
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +93 -0
- package/android/src/main/java/com/lodev09/truesheet/events/BlurEvent.kt +20 -0
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/events/FocusEvent.kt +20 -0
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +5 -3
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +2 -1
- package/android/src/main/res/anim/true_sheet_slide_in.xml +13 -0
- package/android/src/main/res/anim/true_sheet_slide_out.xml +13 -0
- package/android/src/main/res/values/styles.xml +13 -1
- package/ios/TrueSheetContentView.mm +37 -30
- package/ios/TrueSheetView.mm +26 -8
- package/ios/TrueSheetViewController.h +7 -1
- package/ios/TrueSheetViewController.mm +110 -40
- package/ios/events/OnDetentChangeEvent.h +2 -1
- package/ios/events/OnDetentChangeEvent.mm +3 -1
- package/ios/events/OnDidBlurEvent.h +26 -0
- package/ios/events/OnDidBlurEvent.mm +25 -0
- package/ios/events/OnDidFocusEvent.h +26 -0
- package/ios/events/OnDidFocusEvent.mm +25 -0
- package/ios/events/OnDidPresentEvent.h +2 -1
- package/ios/events/OnDidPresentEvent.mm +3 -1
- package/ios/events/OnDragBeginEvent.h +2 -1
- package/ios/events/OnDragBeginEvent.mm +3 -1
- package/ios/events/OnDragChangeEvent.h +2 -1
- package/ios/events/OnDragChangeEvent.mm +3 -1
- package/ios/events/OnDragEndEvent.h +2 -1
- package/ios/events/OnDragEndEvent.mm +3 -1
- package/ios/events/OnPositionChangeEvent.h +2 -1
- package/ios/events/OnPositionChangeEvent.mm +4 -2
- package/ios/events/OnWillPresentEvent.h +2 -1
- package/ios/events/OnWillPresentEvent.mm +3 -1
- package/lib/module/TrueSheet.js +10 -0
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.js +9 -4
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +2 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +14 -0
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +5 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +14 -0
- package/src/TrueSheet.types.ts +16 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +5 -1
- package/src/reanimated/ReanimatedTrueSheet.tsx +9 -4
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +11 -3
|
@@ -8,7 +8,7 @@ import com.facebook.react.uimanager.events.Event
|
|
|
8
8
|
* Fired continuously while user is dragging the sheet
|
|
9
9
|
* Payload: { index: number, position: number }
|
|
10
10
|
*/
|
|
11
|
-
class DragChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float) :
|
|
11
|
+
class DragChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
12
12
|
Event<DragChangeEvent>(surfaceId, viewId) {
|
|
13
13
|
|
|
14
14
|
override fun getEventName(): String = EVENT_NAME
|
|
@@ -17,6 +17,7 @@ class DragChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, priva
|
|
|
17
17
|
Arguments.createMap().apply {
|
|
18
18
|
putInt("index", index)
|
|
19
19
|
putDouble("position", position.toDouble())
|
|
20
|
+
putDouble("detent", detent.toDouble())
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
companion object {
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.uimanager.events.Event
|
|
|
8
8
|
* Fired when user stops dragging the sheet
|
|
9
9
|
* Payload: { index: number, position: number }
|
|
10
10
|
*/
|
|
11
|
-
class DragEndEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float) :
|
|
11
|
+
class DragEndEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
12
12
|
Event<DragEndEvent>(surfaceId, viewId) {
|
|
13
13
|
|
|
14
14
|
override fun getEventName(): String = EVENT_NAME
|
|
@@ -17,6 +17,7 @@ class DragEndEvent(surfaceId: Int, viewId: Int, private val index: Int, private
|
|
|
17
17
|
Arguments.createMap().apply {
|
|
18
18
|
putInt("index", index)
|
|
19
19
|
putDouble("position", position.toDouble())
|
|
20
|
+
putDouble("detent", detent.toDouble())
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
companion object {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.lodev09.truesheet.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fired when the sheet regains focus after a sheet on top of it is dismissed
|
|
9
|
+
*/
|
|
10
|
+
class FocusEvent(surfaceId: Int, viewId: Int) : Event<FocusEvent>(surfaceId, viewId) {
|
|
11
|
+
|
|
12
|
+
override fun getEventName(): String = EVENT_NAME
|
|
13
|
+
|
|
14
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
15
|
+
|
|
16
|
+
companion object {
|
|
17
|
+
const val EVENT_NAME = "topDidFocus"
|
|
18
|
+
const val REGISTRATION_NAME = "onDidFocus"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -6,13 +6,14 @@ import com.facebook.react.uimanager.events.Event
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Fired continuously for position updates during drag and animation
|
|
9
|
-
* Payload: { index: number, position: number, transitioning: boolean }
|
|
9
|
+
* Payload: { index: number, position: number, detent: number, transitioning: boolean }
|
|
10
10
|
*/
|
|
11
11
|
class PositionChangeEvent(
|
|
12
12
|
surfaceId: Int,
|
|
13
13
|
viewId: Int,
|
|
14
|
-
private val index:
|
|
14
|
+
private val index: Float,
|
|
15
15
|
private val position: Float,
|
|
16
|
+
private val detent: Float,
|
|
16
17
|
private val transitioning: Boolean = false
|
|
17
18
|
) : Event<PositionChangeEvent>(surfaceId, viewId) {
|
|
18
19
|
|
|
@@ -20,8 +21,9 @@ class PositionChangeEvent(
|
|
|
20
21
|
|
|
21
22
|
override fun getEventData(): WritableMap =
|
|
22
23
|
Arguments.createMap().apply {
|
|
23
|
-
|
|
24
|
+
putDouble("index", index.toDouble())
|
|
24
25
|
putDouble("position", position.toDouble())
|
|
26
|
+
putDouble("detent", detent.toDouble())
|
|
25
27
|
putBoolean("transitioning", transitioning)
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.uimanager.events.Event
|
|
|
8
8
|
* Fired before the sheet is presented
|
|
9
9
|
* Payload: { index: number, position: number }
|
|
10
10
|
*/
|
|
11
|
-
class WillPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float) :
|
|
11
|
+
class WillPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
12
12
|
Event<WillPresentEvent>(surfaceId, viewId) {
|
|
13
13
|
|
|
14
14
|
override fun getEventName(): String = EVENT_NAME
|
|
@@ -17,6 +17,7 @@ class WillPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, priv
|
|
|
17
17
|
Arguments.createMap().apply {
|
|
18
18
|
putInt("index", index)
|
|
19
19
|
putDouble("position", position.toDouble())
|
|
20
|
+
putDouble("detent", detent.toDouble())
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
companion object {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<translate
|
|
4
|
+
android:duration="200"
|
|
5
|
+
android:fromYDelta="20%p"
|
|
6
|
+
android:toYDelta="0"
|
|
7
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
8
|
+
<alpha
|
|
9
|
+
android:duration="200"
|
|
10
|
+
android:fromAlpha="0.0"
|
|
11
|
+
android:toAlpha="1.0"
|
|
12
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
13
|
+
</set>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<translate
|
|
4
|
+
android:duration="150"
|
|
5
|
+
android:fromYDelta="0"
|
|
6
|
+
android:toYDelta="20%p"
|
|
7
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
8
|
+
<alpha
|
|
9
|
+
android:duration="150"
|
|
10
|
+
android:fromAlpha="1.0"
|
|
11
|
+
android:toAlpha="0.0"
|
|
12
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
13
|
+
</set>
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<resources>
|
|
3
|
-
<!--
|
|
3
|
+
<!-- Smooth slide animation for BottomSheetDialog -->
|
|
4
|
+
<style name="TrueSheetAnimation" parent="Animation.AppCompat.Dialog">
|
|
5
|
+
<item name="android:windowEnterAnimation">@anim/true_sheet_slide_in</item>
|
|
6
|
+
<item name="android:windowExitAnimation">@anim/true_sheet_slide_out</item>
|
|
7
|
+
</style>
|
|
8
|
+
|
|
9
|
+
<!-- Default BottomSheetDialog style with smooth animations -->
|
|
10
|
+
<style name="TrueSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
|
11
|
+
<item name="android:windowAnimationStyle">@style/TrueSheetAnimation</item>
|
|
12
|
+
</style>
|
|
13
|
+
|
|
14
|
+
<!-- BottomSheetDialog style with edge-to-edge and smooth animations -->
|
|
4
15
|
<style name="TrueSheetEdgeToEdgeEnabledDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
|
5
16
|
<item name="android:windowIsFloating">false</item>
|
|
6
17
|
<item name="enableEdgeToEdge">true</item>
|
|
18
|
+
<item name="android:windowAnimationStyle">@style/TrueSheetAnimation</item>
|
|
7
19
|
</style>
|
|
8
20
|
</resources>
|
|
@@ -126,46 +126,53 @@ using namespace facebook::react;
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
- (RCTScrollViewComponentView *)findScrollViewInSubviews:(NSArray<UIView *> *)subviews {
|
|
130
|
+
for (UIView *subview in subviews) {
|
|
131
|
+
if ([subview isKindOfClass:RCTScrollViewComponentView.class]) {
|
|
132
|
+
return (RCTScrollViewComponentView *)subview;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return nil;
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
- (RCTScrollViewComponentView *)findScrollView:(UIView **)outTopSibling {
|
|
130
139
|
if (self.subviews.count == 0) {
|
|
131
140
|
return nil;
|
|
132
141
|
}
|
|
133
142
|
|
|
134
|
-
RCTScrollViewComponentView *scrollView = nil;
|
|
135
143
|
UIView *topSibling = nil;
|
|
136
144
|
|
|
137
145
|
// Check first-level children for scroll views (ScrollView or FlatList)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
for (UIView *sibling in self.subviews) {
|
|
148
|
-
// Skip the ScrollView itself
|
|
149
|
-
if (sibling == scrollView) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
CGFloat siblingBottom = CGRectGetMaxY(sibling.frame);
|
|
154
|
-
|
|
155
|
-
// Check if this sibling is positioned above the ScrollView
|
|
156
|
-
if (siblingBottom <= scrollViewTop) {
|
|
157
|
-
CGFloat distance = scrollViewTop - siblingBottom;
|
|
158
|
-
|
|
159
|
-
// Find the closest view above (smallest distance)
|
|
160
|
-
if (distance < closestDistance) {
|
|
161
|
-
closestDistance = distance;
|
|
162
|
-
topSibling = sibling;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
146
|
+
RCTScrollViewComponentView *scrollView = [self findScrollViewInSubviews:self.subviews];
|
|
147
|
+
|
|
148
|
+
// If not found, check second level (grandchildren)
|
|
149
|
+
if (!scrollView) {
|
|
150
|
+
for (UIView *subview in self.subviews) {
|
|
151
|
+
scrollView = [self findScrollViewInSubviews:subview.subviews];
|
|
152
|
+
if (scrollView) {
|
|
153
|
+
break;
|
|
166
154
|
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
167
157
|
|
|
168
|
-
|
|
158
|
+
// Find the view positioned directly above the ScrollView (only for first-level)
|
|
159
|
+
if (scrollView && scrollView.superview == self && self.subviews.count > 1) {
|
|
160
|
+
CGFloat scrollViewTop = CGRectGetMinY(scrollView.frame);
|
|
161
|
+
CGFloat closestDistance = CGFLOAT_MAX;
|
|
162
|
+
|
|
163
|
+
for (UIView *sibling in self.subviews) {
|
|
164
|
+
if (sibling == scrollView) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
CGFloat siblingBottom = CGRectGetMaxY(sibling.frame);
|
|
169
|
+
if (siblingBottom <= scrollViewTop) {
|
|
170
|
+
CGFloat distance = scrollViewTop - siblingBottom;
|
|
171
|
+
if (distance < closestDistance) {
|
|
172
|
+
closestDistance = distance;
|
|
173
|
+
topSibling = sibling;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
#import "TrueSheetModule.h"
|
|
16
16
|
#import "TrueSheetViewController.h"
|
|
17
17
|
#import "events/OnDetentChangeEvent.h"
|
|
18
|
+
#import "events/OnDidBlurEvent.h"
|
|
18
19
|
#import "events/OnDidDismissEvent.h"
|
|
20
|
+
#import "events/OnDidFocusEvent.h"
|
|
19
21
|
#import "events/OnDidPresentEvent.h"
|
|
20
22
|
#import "events/OnDragBeginEvent.h"
|
|
21
23
|
#import "events/OnDragChangeEvent.h"
|
|
@@ -369,24 +371,28 @@ using namespace facebook::react;
|
|
|
369
371
|
- (void)viewControllerWillPresent {
|
|
370
372
|
NSInteger index = [_controller currentDetentIndex];
|
|
371
373
|
_controller.activeDetentIndex = index;
|
|
372
|
-
[
|
|
374
|
+
CGFloat detent = [_controller detentValueForIndex:index];
|
|
375
|
+
[OnWillPresentEvent emit:_eventEmitter index:index position:_controller.currentPosition detent:detent];
|
|
373
376
|
}
|
|
374
377
|
|
|
375
378
|
- (void)viewControllerDidPresent {
|
|
376
|
-
|
|
379
|
+
NSInteger index = [_controller currentDetentIndex];
|
|
380
|
+
CGFloat detent = [_controller detentValueForIndex:index];
|
|
381
|
+
[OnDidPresentEvent emit:_eventEmitter index:index position:_controller.currentPosition detent:detent];
|
|
377
382
|
}
|
|
378
383
|
|
|
379
384
|
- (void)viewControllerDidDrag:(UIGestureRecognizerState)state index:(NSInteger)index position:(CGFloat)position {
|
|
385
|
+
CGFloat detent = [_controller detentValueForIndex:index];
|
|
380
386
|
switch (state) {
|
|
381
387
|
case UIGestureRecognizerStateBegan:
|
|
382
|
-
[OnDragBeginEvent emit:_eventEmitter index:index position:position];
|
|
388
|
+
[OnDragBeginEvent emit:_eventEmitter index:index position:position detent:detent];
|
|
383
389
|
break;
|
|
384
390
|
case UIGestureRecognizerStateChanged:
|
|
385
|
-
[OnDragChangeEvent emit:_eventEmitter index:index position:position];
|
|
391
|
+
[OnDragChangeEvent emit:_eventEmitter index:index position:position detent:detent];
|
|
386
392
|
break;
|
|
387
393
|
case UIGestureRecognizerStateEnded:
|
|
388
394
|
case UIGestureRecognizerStateCancelled:
|
|
389
|
-
[OnDragEndEvent emit:_eventEmitter index:index position:position];
|
|
395
|
+
[OnDragEndEvent emit:_eventEmitter index:index position:position detent:detent];
|
|
390
396
|
break;
|
|
391
397
|
default:
|
|
392
398
|
break;
|
|
@@ -406,17 +412,29 @@ using namespace facebook::react;
|
|
|
406
412
|
if (_controller.activeDetentIndex != index) {
|
|
407
413
|
_controller.activeDetentIndex = index;
|
|
408
414
|
}
|
|
409
|
-
[
|
|
415
|
+
CGFloat detent = [_controller detentValueForIndex:index];
|
|
416
|
+
[OnDetentChangeEvent emit:_eventEmitter index:index position:position detent:detent];
|
|
410
417
|
}
|
|
411
418
|
|
|
412
|
-
- (void)viewControllerDidChangePosition:(
|
|
413
|
-
|
|
419
|
+
- (void)viewControllerDidChangePosition:(CGFloat)index
|
|
420
|
+
position:(CGFloat)position
|
|
421
|
+
detent:(CGFloat)detent
|
|
422
|
+
transitioning:(BOOL)transitioning {
|
|
423
|
+
[OnPositionChangeEvent emit:_eventEmitter index:index position:position detent:detent transitioning:transitioning];
|
|
414
424
|
}
|
|
415
425
|
|
|
416
426
|
- (void)viewControllerDidChangeSize:(CGSize)size {
|
|
417
427
|
[self updateStateWithSize:size];
|
|
418
428
|
}
|
|
419
429
|
|
|
430
|
+
- (void)viewControllerDidFocus {
|
|
431
|
+
[OnDidFocusEvent emit:_eventEmitter];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
- (void)viewControllerDidBlur {
|
|
435
|
+
[OnDidBlurEvent emit:_eventEmitter];
|
|
436
|
+
}
|
|
437
|
+
|
|
420
438
|
#pragma mark - Private Helpers
|
|
421
439
|
|
|
422
440
|
- (UIViewController *)findPresentingViewController {
|
|
@@ -25,8 +25,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
25
25
|
- (void)viewControllerDidDismiss;
|
|
26
26
|
- (void)viewControllerDidChangeDetent:(NSInteger)index position:(CGFloat)position;
|
|
27
27
|
- (void)viewControllerDidDrag:(UIGestureRecognizerState)state index:(NSInteger)index position:(CGFloat)position;
|
|
28
|
-
- (void)viewControllerDidChangePosition:(
|
|
28
|
+
- (void)viewControllerDidChangePosition:(CGFloat)index
|
|
29
|
+
position:(CGFloat)position
|
|
30
|
+
detent:(CGFloat)detent
|
|
31
|
+
transitioning:(BOOL)transitioning;
|
|
29
32
|
- (void)viewControllerDidChangeSize:(CGSize)size;
|
|
33
|
+
- (void)viewControllerDidFocus;
|
|
34
|
+
- (void)viewControllerDidBlur;
|
|
30
35
|
|
|
31
36
|
@end
|
|
32
37
|
|
|
@@ -60,6 +65,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
60
65
|
- (NSInteger)currentDetentIndex;
|
|
61
66
|
- (CGFloat)currentPosition;
|
|
62
67
|
- (CGFloat)bottomInset;
|
|
68
|
+
- (CGFloat)detentValueForIndex:(NSInteger)index;
|
|
63
69
|
|
|
64
70
|
@end
|
|
65
71
|
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
// Hidden view used to track position during native transition animations
|
|
30
30
|
UIView *_fakeTransitionView;
|
|
31
31
|
CADisplayLink *_displayLink;
|
|
32
|
+
|
|
33
|
+
// Reference to parent TrueSheetViewController (if presented from another sheet)
|
|
34
|
+
__weak TrueSheetViewController *_parentSheetController;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
#pragma mark - Initialization
|
|
@@ -97,13 +100,8 @@
|
|
|
97
100
|
return window ? window.safeAreaInsets.bottom : 0;
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
- (CGFloat)
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
- (CGFloat)containerHeight {
|
|
105
|
-
UIView *sheetContainerView = self.sheetPresentationController.containerView;
|
|
106
|
-
return sheetContainerView ? sheetContainerView.frame.size.height : 0.0;
|
|
103
|
+
- (CGFloat)screenHeight {
|
|
104
|
+
return UIScreen.mainScreen.bounds.size.height;
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
- (NSInteger)currentDetentIndex {
|
|
@@ -146,6 +144,16 @@
|
|
|
146
144
|
|
|
147
145
|
// Only trigger on initial presentation, not repositioning
|
|
148
146
|
if (!_isPresented) {
|
|
147
|
+
// Capture parent sheet reference if presented from another TrueSheet
|
|
148
|
+
UIViewController *presenter = self.presentingViewController;
|
|
149
|
+
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
150
|
+
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
151
|
+
// Notify parent that it lost focus
|
|
152
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
153
|
+
[_parentSheetController.delegate viewControllerDidBlur];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
149
157
|
if ([self.delegate respondsToSelector:@selector(viewControllerWillPresent)]) {
|
|
150
158
|
[self.delegate viewControllerWillPresent];
|
|
151
159
|
}
|
|
@@ -182,8 +190,18 @@
|
|
|
182
190
|
// Only dispatch didDismiss when actually dismissing (not when another modal is presented on top)
|
|
183
191
|
BOOL isActuallyDismissing = self.presentingViewController == nil || self.isBeingDismissed;
|
|
184
192
|
|
|
185
|
-
if (isActuallyDismissing
|
|
186
|
-
|
|
193
|
+
if (isActuallyDismissing) {
|
|
194
|
+
// Notify the parent sheet (if any) that it regained focus
|
|
195
|
+
if (_parentSheetController) {
|
|
196
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
197
|
+
[_parentSheetController.delegate viewControllerDidFocus];
|
|
198
|
+
}
|
|
199
|
+
_parentSheetController = nil;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidDismiss)]) {
|
|
203
|
+
[self.delegate viewControllerDidDismiss];
|
|
204
|
+
}
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
_isTrackingPositionFromLayout = NO;
|
|
@@ -283,13 +301,67 @@
|
|
|
283
301
|
if (_lastPosition != position) {
|
|
284
302
|
_lastPosition = position;
|
|
285
303
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
304
|
+
CGFloat index = [self interpolatedIndexForPosition:position];
|
|
305
|
+
NSInteger discreteIndex = (NSInteger)round(index);
|
|
306
|
+
CGFloat detent = [self detentValueForIndex:discreteIndex];
|
|
307
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangePosition:position:detent:transitioning:)]) {
|
|
308
|
+
[self.delegate viewControllerDidChangePosition:index position:position detent:detent transitioning:transitioning];
|
|
289
309
|
}
|
|
290
310
|
}
|
|
291
311
|
}
|
|
292
312
|
|
|
313
|
+
- (CGFloat)interpolatedIndexForPosition:(CGFloat)position {
|
|
314
|
+
NSInteger count = _detents.count;
|
|
315
|
+
if (count == 0)
|
|
316
|
+
return -1;
|
|
317
|
+
if (count == 1)
|
|
318
|
+
return 0;
|
|
319
|
+
|
|
320
|
+
// Convert position to detent fraction: detent = (screenHeight - position) / screenHeight
|
|
321
|
+
CGFloat currentDetent = (self.screenHeight - position) / self.screenHeight;
|
|
322
|
+
|
|
323
|
+
// Handle below first detent (interpolate from -1 to 0)
|
|
324
|
+
CGFloat firstDetentValue = [self detentValueForIndex:0];
|
|
325
|
+
if (currentDetent < firstDetentValue) {
|
|
326
|
+
// Interpolate: 0 at firstDetentValue, -1 at 0
|
|
327
|
+
if (firstDetentValue <= 0)
|
|
328
|
+
return 0;
|
|
329
|
+
CGFloat progress = currentDetent / firstDetentValue;
|
|
330
|
+
return progress - 1;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Find which segment the current detent falls into and interpolate
|
|
334
|
+
for (NSInteger i = 0; i < count - 1; i++) {
|
|
335
|
+
CGFloat detentValue = [self detentValueForIndex:i];
|
|
336
|
+
CGFloat nextDetentValue = [self detentValueForIndex:i + 1];
|
|
337
|
+
|
|
338
|
+
if (currentDetent <= nextDetentValue) {
|
|
339
|
+
// Interpolate between index i and i+1
|
|
340
|
+
CGFloat range = nextDetentValue - detentValue;
|
|
341
|
+
if (range <= 0)
|
|
342
|
+
return i;
|
|
343
|
+
CGFloat progress = (currentDetent - detentValue) / range;
|
|
344
|
+
return i + fmax(0, fmin(1, progress));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return count - 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
- (CGFloat)detentValueForIndex:(NSInteger)index {
|
|
352
|
+
if (index >= 0 && index < (NSInteger)_detents.count) {
|
|
353
|
+
CGFloat value = [_detents[index] floatValue];
|
|
354
|
+
// For auto (-1), calculate actual fraction from content + header height
|
|
355
|
+
// Subtract bottomInset to match the actual sheet height (consistent with detent resolver)
|
|
356
|
+
if (value == -1) {
|
|
357
|
+
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue] - self.bottomInset;
|
|
358
|
+
return autoHeight / self.screenHeight;
|
|
359
|
+
}
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
return 0;
|
|
363
|
+
}
|
|
364
|
+
|
|
293
365
|
/**
|
|
294
366
|
* Sets up position tracking during view controller transitions using a fake view.
|
|
295
367
|
*
|
|
@@ -313,7 +385,7 @@
|
|
|
313
385
|
BOOL isPresenting = self.isBeingPresented;
|
|
314
386
|
|
|
315
387
|
// Set initial position: presenting starts from bottom, dismissing from current
|
|
316
|
-
frame.origin.y = isPresenting ? self.
|
|
388
|
+
frame.origin.y = isPresenting ? self.screenHeight : presentedView.frame.origin.y;
|
|
317
389
|
_fakeTransitionView.frame = frame;
|
|
318
390
|
|
|
319
391
|
auto animation = ^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
@@ -375,12 +447,12 @@
|
|
|
375
447
|
NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray array];
|
|
376
448
|
|
|
377
449
|
// Subtract bottomInset to prevent iOS from adding extra bottom insets
|
|
378
|
-
CGFloat
|
|
450
|
+
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue] - self.bottomInset;
|
|
379
451
|
|
|
380
452
|
for (NSInteger index = 0; index < self.detents.count; index++) {
|
|
381
453
|
id detent = self.detents[index];
|
|
382
454
|
UISheetPresentationControllerDetent *sheetDetent = [self detentForValue:detent
|
|
383
|
-
|
|
455
|
+
withAutoHeight:autoHeight
|
|
384
456
|
atIndex:index];
|
|
385
457
|
[detents addObject:sheetDetent];
|
|
386
458
|
}
|
|
@@ -408,7 +480,9 @@
|
|
|
408
480
|
}
|
|
409
481
|
}
|
|
410
482
|
|
|
411
|
-
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
483
|
+
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
484
|
+
withAutoHeight:(CGFloat)autoHeight
|
|
485
|
+
atIndex:(NSInteger)index {
|
|
412
486
|
if (![detent isKindOfClass:[NSNumber class]]) {
|
|
413
487
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
414
488
|
}
|
|
@@ -418,15 +492,9 @@
|
|
|
418
492
|
// -1 represents "auto" (fit content height)
|
|
419
493
|
if (value == -1) {
|
|
420
494
|
if (@available(iOS 16.0, *)) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
425
|
-
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
426
|
-
CGFloat maxValue =
|
|
427
|
-
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
428
|
-
return fmin(height, maxValue);
|
|
429
|
-
}];
|
|
495
|
+
// Subtract bottomInset so position matches screenHeight - sheetHeight
|
|
496
|
+
// iOS adds bottom safe area to sheet height internally
|
|
497
|
+
return [self customDetentWithIdentifier:@"custom-auto" height:autoHeight - self.bottomInset];
|
|
430
498
|
} else {
|
|
431
499
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
432
500
|
}
|
|
@@ -438,21 +506,11 @@
|
|
|
438
506
|
}
|
|
439
507
|
|
|
440
508
|
if (@available(iOS 16.0, *)) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
447
|
-
return [UISheetPresentationControllerDetent
|
|
448
|
-
customDetentWithIdentifier:detentId
|
|
449
|
-
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
450
|
-
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
451
|
-
CGFloat maxValue =
|
|
452
|
-
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
453
|
-
return fmin(value * maxDetentValue, maxValue);
|
|
454
|
-
}];
|
|
455
|
-
}
|
|
509
|
+
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
510
|
+
// Subtract bottomInset so position matches screenHeight - (detent * screenHeight)
|
|
511
|
+
// iOS adds bottom safe area to sheet height internally
|
|
512
|
+
CGFloat sheetHeight = value * self.screenHeight - self.bottomInset;
|
|
513
|
+
return [self customDetentWithIdentifier:detentId height:sheetHeight];
|
|
456
514
|
} else if (value >= 0.5) {
|
|
457
515
|
return [UISheetPresentationControllerDetent largeDetent];
|
|
458
516
|
} else {
|
|
@@ -460,6 +518,18 @@
|
|
|
460
518
|
}
|
|
461
519
|
}
|
|
462
520
|
|
|
521
|
+
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
522
|
+
height:(CGFloat)height API_AVAILABLE(ios(16.0)) {
|
|
523
|
+
return [UISheetPresentationControllerDetent
|
|
524
|
+
customDetentWithIdentifier:identifier
|
|
525
|
+
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
526
|
+
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
527
|
+
CGFloat maxValue =
|
|
528
|
+
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
529
|
+
return fmin(height, maxValue);
|
|
530
|
+
}];
|
|
531
|
+
}
|
|
532
|
+
|
|
463
533
|
- (UISheetPresentationControllerDetentIdentifier)detentIdentifierForIndex:(NSInteger)index {
|
|
464
534
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
465
535
|
if (!sheet)
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
+ (void)emit:(std::shared_ptr<const facebook::react::EventEmitter>)eventEmitter
|
|
16
16
|
index:(NSInteger)index
|
|
17
|
-
position:(CGFloat)position
|
|
17
|
+
position:(CGFloat)position
|
|
18
|
+
detent:(CGFloat)detent {
|
|
18
19
|
if (!eventEmitter)
|
|
19
20
|
return;
|
|
20
21
|
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
TrueSheetViewEventEmitter::OnDetentChange event;
|
|
23
24
|
event.index = static_cast<int>(index);
|
|
24
25
|
event.position = static_cast<double>(position);
|
|
26
|
+
event.detent = static_cast<double>(detent);
|
|
25
27
|
emitter->onDetentChange(event);
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -0,0 +1,26 @@
|
|
|
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 <Foundation/Foundation.h>
|
|
12
|
+
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
|
|
13
|
+
|
|
14
|
+
using namespace facebook::react;
|
|
15
|
+
|
|
16
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
17
|
+
|
|
18
|
+
@interface OnDidBlurEvent : NSObject
|
|
19
|
+
|
|
20
|
+
+ (void)emit:(std::shared_ptr<const facebook::react::EventEmitter>)eventEmitter;
|
|
21
|
+
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
NS_ASSUME_NONNULL_END
|
|
25
|
+
|
|
26
|
+
#endif // RCT_NEW_ARCH_ENABLED
|
|
@@ -0,0 +1,25 @@
|
|
|
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 "OnDidBlurEvent.h"
|
|
12
|
+
|
|
13
|
+
@implementation OnDidBlurEvent
|
|
14
|
+
|
|
15
|
+
+ (void)emit:(std::shared_ptr<const facebook::react::EventEmitter>)eventEmitter {
|
|
16
|
+
if (!eventEmitter)
|
|
17
|
+
return;
|
|
18
|
+
|
|
19
|
+
auto emitter = std::static_pointer_cast<TrueSheetViewEventEmitter const>(eventEmitter);
|
|
20
|
+
emitter->onDidBlur({});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@end
|
|
24
|
+
|
|
25
|
+
#endif // RCT_NEW_ARCH_ENABLED
|