@lodev09/react-native-true-sheet 3.11.4 → 3.11.6
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/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +69 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekView.kt +72 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekViewManager.kt +33 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +5 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +5 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +5 -3
- package/ios/TrueSheetContainerView.h +13 -0
- package/ios/TrueSheetContainerView.mm +72 -1
- package/ios/TrueSheetPeekView.h +32 -0
- package/ios/TrueSheetPeekView.mm +99 -0
- package/ios/TrueSheetView.mm +12 -0
- package/ios/TrueSheetViewController.h +1 -0
- package/ios/TrueSheetViewController.mm +28 -15
- package/ios/core/TrueSheetDetentCalculator.h +3 -2
- package/ios/core/TrueSheetDetentCalculator.mm +2 -1
- package/ios/tvos/TrueSheetStubs.mm +11 -0
- package/lib/module/TrueSheet.web.js +85 -70
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetPeek.js +15 -0
- package/lib/module/TrueSheetPeek.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +46 -0
- package/lib/module/TrueSheetPeek.web.js.map +1 -0
- package/lib/module/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +10 -0
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +3 -1
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetPeek.d.ts +9 -0
- package/lib/typescript/src/TrueSheetPeek.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +19 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +6 -2
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/TrueSheet.types.ts +3 -1
- package/src/TrueSheet.web.tsx +83 -69
- package/src/TrueSheetPeek.tsx +11 -0
- package/src/TrueSheetPeek.web.tsx +48 -0
- package/src/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +8 -1
|
@@ -2,8 +2,10 @@ package com.lodev09.truesheet
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.view.View
|
|
5
|
+
import android.view.ViewGroup
|
|
5
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
7
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
8
|
+
import com.facebook.react.util.RNLog
|
|
7
9
|
import com.facebook.react.views.view.ReactViewGroup
|
|
8
10
|
|
|
9
11
|
interface TrueSheetContainerViewDelegate {
|
|
@@ -13,6 +15,7 @@ interface TrueSheetContainerViewDelegate {
|
|
|
13
15
|
fun containerViewScrollViewDidChange()
|
|
14
16
|
fun containerViewHeaderDidChangeSize(width: Int, height: Int)
|
|
15
17
|
fun containerViewFooterDidChangeSize(width: Int, height: Int)
|
|
18
|
+
fun containerViewPeekDidChangeSize(width: Int, height: Int)
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -24,18 +27,40 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
24
27
|
ReactViewGroup(reactContext),
|
|
25
28
|
TrueSheetContentViewDelegate,
|
|
26
29
|
TrueSheetHeaderViewDelegate,
|
|
27
|
-
TrueSheetFooterViewDelegate
|
|
30
|
+
TrueSheetFooterViewDelegate,
|
|
31
|
+
TrueSheetPeekViewDelegate {
|
|
28
32
|
|
|
29
33
|
var delegate: TrueSheetContainerViewDelegate? = null
|
|
30
34
|
|
|
31
35
|
var contentView: TrueSheetContentView? = null
|
|
32
36
|
var headerView: TrueSheetHeaderView? = null
|
|
33
37
|
var footerView: TrueSheetFooterView? = null
|
|
38
|
+
var peekView: TrueSheetPeekView? = null
|
|
34
39
|
|
|
35
40
|
var contentHeight: Int = 0
|
|
36
41
|
var headerHeight: Int = 0
|
|
37
42
|
var footerHeight: Int = 0
|
|
38
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Distance from the top of the content view to the bottom of the peek view.
|
|
46
|
+
* Includes the peek view's offset within the content (padding, views above it)
|
|
47
|
+
* so the peek detent reveals everything down to the peek content's bottom edge.
|
|
48
|
+
*/
|
|
49
|
+
val peekContentHeight: Int
|
|
50
|
+
get() {
|
|
51
|
+
val peek = peekView ?: return 0
|
|
52
|
+
val content = contentView ?: return peek.height
|
|
53
|
+
|
|
54
|
+
var bottom = peek.height
|
|
55
|
+
var view: View = peek
|
|
56
|
+
while (view !== content) {
|
|
57
|
+
bottom += view.top
|
|
58
|
+
view = view.parent as? View ?: return peek.height
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return bottom
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
var insetAdjustment: TrueSheetInsetAdjustment = TrueSheetInsetAdjustment.AUTOMATIC
|
|
40
65
|
var scrollViewBottomInset: Int = 0
|
|
41
66
|
var scrollableEnabled: Boolean = false
|
|
@@ -75,6 +100,10 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
75
100
|
child.delegate = this
|
|
76
101
|
child.scrollableOptions = scrollableOptions
|
|
77
102
|
contentView = child
|
|
103
|
+
|
|
104
|
+
// Children mount bottom-up, so the content subtree is complete here.
|
|
105
|
+
// Late-mounted peek views attach themselves instead (see TrueSheetPeekView).
|
|
106
|
+
findPeekView(child)?.let { attachPeekView(it) }
|
|
78
107
|
}
|
|
79
108
|
|
|
80
109
|
is TrueSheetHeaderView -> {
|
|
@@ -113,6 +142,41 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
113
142
|
super.removeViewAt(index)
|
|
114
143
|
}
|
|
115
144
|
|
|
145
|
+
// ==================== Peek View ====================
|
|
146
|
+
|
|
147
|
+
private fun findPeekView(view: View): TrueSheetPeekView? {
|
|
148
|
+
if (view is TrueSheetPeekView) return view
|
|
149
|
+
|
|
150
|
+
if (view is ViewGroup) {
|
|
151
|
+
for (i in 0 until view.childCount) {
|
|
152
|
+
findPeekView(view.getChildAt(i))?.let { return it }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fun attachPeekView(view: TrueSheetPeekView) {
|
|
160
|
+
if (peekView === view) return
|
|
161
|
+
|
|
162
|
+
if (peekView != null) {
|
|
163
|
+
RNLog.w(context as ThemedReactContext, "TrueSheet: Sheet can only have one peek component.")
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
peekView = view
|
|
168
|
+
view.delegate = this
|
|
169
|
+
peekViewDidChangeSize(view.width, view.height)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fun detachPeekView(view: TrueSheetPeekView) {
|
|
173
|
+
if (peekView !== view) return
|
|
174
|
+
|
|
175
|
+
view.delegate = null
|
|
176
|
+
peekView = null
|
|
177
|
+
peekViewDidChangeSize(0, 0)
|
|
178
|
+
}
|
|
179
|
+
|
|
116
180
|
// ==================== Delegate Implementations ====================
|
|
117
181
|
|
|
118
182
|
override fun contentViewDidChangeSize(width: Int, height: Int) {
|
|
@@ -138,6 +202,10 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
138
202
|
delegate?.containerViewFooterDidChangeSize(width, height)
|
|
139
203
|
}
|
|
140
204
|
|
|
205
|
+
override fun peekViewDidChangeSize(width: Int, height: Int) {
|
|
206
|
+
delegate?.containerViewPeekDidChangeSize(width, height)
|
|
207
|
+
}
|
|
208
|
+
|
|
141
209
|
companion object {
|
|
142
210
|
const val TAG_NAME = "TrueSheet"
|
|
143
211
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package com.lodev09.truesheet
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.view.ViewParent
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Delegate interface for peek view size changes
|
|
10
|
+
*/
|
|
11
|
+
interface TrueSheetPeekViewDelegate {
|
|
12
|
+
fun peekViewDidChangeSize(width: Int, height: Int)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Peek view that marks part of the content as the sheet's peek content.
|
|
17
|
+
* The peek detent covers the content through this view's bottom edge.
|
|
18
|
+
*/
|
|
19
|
+
@SuppressLint("ViewConstructor")
|
|
20
|
+
class TrueSheetPeekView(context: ThemedReactContext) : ReactViewGroup(context) {
|
|
21
|
+
var delegate: TrueSheetPeekViewDelegate? = null
|
|
22
|
+
|
|
23
|
+
override fun onLayout(
|
|
24
|
+
changed: Boolean,
|
|
25
|
+
left: Int,
|
|
26
|
+
top: Int,
|
|
27
|
+
right: Int,
|
|
28
|
+
bottom: Int
|
|
29
|
+
) {
|
|
30
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
31
|
+
|
|
32
|
+
attachToContainerView()
|
|
33
|
+
|
|
34
|
+
// Position changes matter too — the peek's offset within the content
|
|
35
|
+
// affects the peek detent.
|
|
36
|
+
if (changed) {
|
|
37
|
+
delegate?.peekViewDidChangeSize(width, height)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override fun onAttachedToWindow() {
|
|
42
|
+
super.onAttachedToWindow()
|
|
43
|
+
attachToContainerView()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Peek can be nested anywhere within the content, so it attaches itself to the
|
|
48
|
+
* nearest container instead of being mounted by it. The container also searches
|
|
49
|
+
* for it when the content is added to cover the initial (bottom-up) mount order.
|
|
50
|
+
*/
|
|
51
|
+
private fun attachToContainerView() {
|
|
52
|
+
if (delegate != null) return
|
|
53
|
+
|
|
54
|
+
var parent: ViewParent? = parent
|
|
55
|
+
while (parent != null && parent !is TrueSheetContainerView) {
|
|
56
|
+
parent = parent.parent
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
(parent as? TrueSheetContainerView)?.attachPeekView(this)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Called by the ViewManager when the view is dropped (Fabric unmount)
|
|
64
|
+
*/
|
|
65
|
+
fun detachFromContainerView() {
|
|
66
|
+
(delegate as? TrueSheetContainerView)?.detachPeekView(this)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
companion object {
|
|
70
|
+
const val TAG_NAME = "TrueSheet"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.lodev09.truesheet
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ViewManager for TrueSheetPeekView
|
|
11
|
+
* Marks part of the content as the sheet's peek content
|
|
12
|
+
*/
|
|
13
|
+
@ReactModule(name = TrueSheetPeekViewManager.REACT_CLASS)
|
|
14
|
+
class TrueSheetPeekViewManager : ViewGroupManager<TrueSheetPeekView>() {
|
|
15
|
+
|
|
16
|
+
override fun getName(): String = REACT_CLASS
|
|
17
|
+
|
|
18
|
+
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetPeekView = TrueSheetPeekView(reactContext)
|
|
19
|
+
|
|
20
|
+
override fun onDropViewInstance(view: TrueSheetPeekView) {
|
|
21
|
+
super.onDropViewInstance(view)
|
|
22
|
+
view.detachFromContainerView()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@ReactProp(name = "pointerEvents")
|
|
26
|
+
fun setPointerEvents(view: TrueSheetPeekView, pointerEventsStr: String?) {
|
|
27
|
+
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
companion object {
|
|
31
|
+
const val REACT_CLASS = "TrueSheetPeekView"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -620,6 +620,11 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
620
620
|
viewController.positionFooter()
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
+
override fun containerViewPeekDidChangeSize(width: Int, height: Int) {
|
|
624
|
+
// Peek content height affects peek detents
|
|
625
|
+
updateSheetIfNeeded()
|
|
626
|
+
}
|
|
627
|
+
|
|
623
628
|
// ==================== RNScreensEventObserverDelegate ====================
|
|
624
629
|
|
|
625
630
|
override fun presenterScreenWillDisappear() {
|
|
@@ -276,6 +276,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
276
276
|
private var cachedContentHeight: Int = 0
|
|
277
277
|
private var cachedHeaderHeight: Int = 0
|
|
278
278
|
private var cachedFooterHeight: Int = 0
|
|
279
|
+
private var cachedPeekContentHeight: Int = 0
|
|
279
280
|
|
|
280
281
|
override val contentHeight: Int
|
|
281
282
|
get() = containerView?.contentHeight ?: cachedContentHeight
|
|
@@ -286,6 +287,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
286
287
|
override val footerHeight: Int
|
|
287
288
|
get() = containerView?.footerHeight ?: cachedFooterHeight
|
|
288
289
|
|
|
290
|
+
override val peekContentHeight: Int
|
|
291
|
+
get() = containerView?.peekContentHeight ?: cachedPeekContentHeight
|
|
292
|
+
|
|
289
293
|
// Insets
|
|
290
294
|
// Target keyboard height used for detent calculations
|
|
291
295
|
override val keyboardInset: Int
|
|
@@ -841,6 +845,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
841
845
|
cachedContentHeight = it.contentHeight
|
|
842
846
|
cachedHeaderHeight = it.headerHeight
|
|
843
847
|
cachedFooterHeight = it.footerHeight
|
|
848
|
+
cachedPeekContentHeight = it.peekContentHeight
|
|
844
849
|
}
|
|
845
850
|
|
|
846
851
|
interactionState = InteractionState.Reconfiguring
|
|
@@ -16,6 +16,7 @@ interface TrueSheetDetentCalculatorDelegate {
|
|
|
16
16
|
val contentHeight: Int
|
|
17
17
|
val headerHeight: Int
|
|
18
18
|
val footerHeight: Int
|
|
19
|
+
val peekContentHeight: Int
|
|
19
20
|
val contentBottomInset: Int
|
|
20
21
|
val maxContentHeight: Int?
|
|
21
22
|
val keyboardInset: Int
|
|
@@ -34,17 +35,18 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
34
35
|
private val contentHeight: Int get() = delegate?.contentHeight ?: 0
|
|
35
36
|
private val headerHeight: Int get() = delegate?.headerHeight ?: 0
|
|
36
37
|
private val footerHeight: Int get() = delegate?.footerHeight ?: 0
|
|
38
|
+
private val peekContentHeight: Int get() = delegate?.peekContentHeight ?: 0
|
|
37
39
|
private val contentBottomInset: Int get() = delegate?.contentBottomInset ?: 0
|
|
38
40
|
private val maxContentHeight: Int? get() = delegate?.maxContentHeight
|
|
39
41
|
private val keyboardInset: Int get() = delegate?.keyboardInset ?: 0
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
* Height for peek (-2.0) detents: header + footer height.
|
|
43
|
-
* Falls back to 150dp when
|
|
44
|
+
* Height for peek (-2.0) detents: header + footer + peek content height.
|
|
45
|
+
* Falls back to 150dp when none is present.
|
|
44
46
|
*/
|
|
45
47
|
private val peekDetentHeight: Int
|
|
46
48
|
get() {
|
|
47
|
-
val height = headerHeight + footerHeight
|
|
49
|
+
val height = headerHeight + footerHeight + peekContentHeight
|
|
48
50
|
return if (height > 0) height else DEFAULT_PEEK_HEIGHT.dpToPx().toInt()
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
23
23
|
|
|
24
24
|
@end
|
|
25
25
|
|
|
26
|
+
@class TrueSheetPeekView;
|
|
27
|
+
|
|
26
28
|
@protocol TrueSheetContainerViewDelegate <NSObject>
|
|
27
29
|
|
|
28
30
|
- (void)containerViewContentDidChangeSize:(CGSize)newSize;
|
|
@@ -32,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
32
34
|
|
|
33
35
|
- (void)containerViewHeaderDidChangeSize:(CGSize)newSize;
|
|
34
36
|
- (void)containerViewFooterDidChangeSize:(CGSize)newSize;
|
|
37
|
+
- (void)containerViewPeekDidChangeSize:(CGSize)newSize;
|
|
35
38
|
|
|
36
39
|
@end
|
|
37
40
|
|
|
@@ -72,6 +75,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
72
75
|
*/
|
|
73
76
|
- (CGFloat)footerHeight;
|
|
74
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Returns the current height of the peek view within the content
|
|
80
|
+
*/
|
|
81
|
+
- (CGFloat)peekContentHeight;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Registers a peek view found within the content subtree
|
|
85
|
+
*/
|
|
86
|
+
- (void)attachPeekView:(TrueSheetPeekView *)peekView;
|
|
87
|
+
|
|
75
88
|
/**
|
|
76
89
|
* Updates footer layout constraints if needed
|
|
77
90
|
*/
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#import "TrueSheetContentView.h"
|
|
14
14
|
#import "TrueSheetFooterView.h"
|
|
15
15
|
#import "TrueSheetHeaderView.h"
|
|
16
|
+
#import "TrueSheetPeekView.h"
|
|
16
17
|
#import "TrueSheetViewController.h"
|
|
17
18
|
#import "core/TrueSheetKeyboardObserver.h"
|
|
18
19
|
#import "utils/UIView+ScrollEdgeInteraction.h"
|
|
@@ -45,13 +46,15 @@ using namespace facebook::react;
|
|
|
45
46
|
|
|
46
47
|
@interface TrueSheetContainerView () <TrueSheetContentViewDelegate,
|
|
47
48
|
TrueSheetHeaderViewDelegate,
|
|
48
|
-
TrueSheetFooterViewDelegate
|
|
49
|
+
TrueSheetFooterViewDelegate,
|
|
50
|
+
TrueSheetPeekViewDelegate>
|
|
49
51
|
@end
|
|
50
52
|
|
|
51
53
|
@implementation TrueSheetContainerView {
|
|
52
54
|
TrueSheetContentView *_contentView;
|
|
53
55
|
TrueSheetHeaderView *_headerView;
|
|
54
56
|
TrueSheetFooterView *_footerView;
|
|
57
|
+
TrueSheetPeekView *__weak _peekView;
|
|
55
58
|
TrueSheetKeyboardObserver *_keyboardObserver;
|
|
56
59
|
BOOL _scrollableSet;
|
|
57
60
|
}
|
|
@@ -123,6 +126,21 @@ using namespace facebook::react;
|
|
|
123
126
|
return _footerView ? _footerView.frame.size.height : 0;
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
// Distance from the top of the content view to the bottom of the peek view.
|
|
130
|
+
// Includes the peek view's offset within the content (padding, views above it)
|
|
131
|
+
// so the peek detent reveals everything down to the peek content's bottom edge.
|
|
132
|
+
- (CGFloat)peekContentHeight {
|
|
133
|
+
if (!_peekView) {
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!_contentView || ![_peekView isDescendantOfView:_contentView]) {
|
|
138
|
+
return _peekView.frame.size.height;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return CGRectGetMaxY([_peekView convertRect:_peekView.bounds toView:_contentView]);
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
- (void)layoutFooter {
|
|
127
145
|
if (_footerView) {
|
|
128
146
|
CGFloat height = _footerView.frame.size.height;
|
|
@@ -196,6 +214,13 @@ using namespace facebook::react;
|
|
|
196
214
|
}
|
|
197
215
|
_contentView = (TrueSheetContentView *)childComponentView;
|
|
198
216
|
_contentView.delegate = self;
|
|
217
|
+
|
|
218
|
+
// Children mount bottom-up, so the content subtree is complete here.
|
|
219
|
+
// Late-mounted peek views attach themselves instead (see TrueSheetPeekView).
|
|
220
|
+
TrueSheetPeekView *peekView = [self findPeekViewIn:_contentView];
|
|
221
|
+
if (peekView) {
|
|
222
|
+
[self attachPeekView:peekView];
|
|
223
|
+
}
|
|
199
224
|
}
|
|
200
225
|
|
|
201
226
|
if ([childComponentView isKindOfClass:[TrueSheetHeaderView class]]) {
|
|
@@ -278,6 +303,52 @@ using namespace facebook::react;
|
|
|
278
303
|
}
|
|
279
304
|
}
|
|
280
305
|
|
|
306
|
+
#pragma mark - TrueSheetPeekViewDelegate
|
|
307
|
+
|
|
308
|
+
- (nullable TrueSheetPeekView *)findPeekViewIn:(UIView *)view {
|
|
309
|
+
if ([view isKindOfClass:[TrueSheetPeekView class]]) {
|
|
310
|
+
return (TrueSheetPeekView *)view;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (UIView *subview in view.subviews) {
|
|
314
|
+
TrueSheetPeekView *found = [self findPeekViewIn:subview];
|
|
315
|
+
if (found) {
|
|
316
|
+
return found;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return nil;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
- (void)attachPeekView:(TrueSheetPeekView *)peekView {
|
|
324
|
+
if (_peekView == peekView) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (_peekView != nil) {
|
|
329
|
+
RCTLogWarn(@"TrueSheet: Sheet can only have one peek component.");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
_peekView = peekView;
|
|
334
|
+
peekView.delegate = self;
|
|
335
|
+
[self peekViewDidChangeSize:peekView.frame.size];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
- (void)peekViewDidChangeSize:(CGSize)newSize {
|
|
339
|
+
[self.delegate containerViewPeekDidChangeSize:newSize];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
- (void)peekViewWillDetach:(TrueSheetPeekView *)peekView {
|
|
343
|
+
if (_peekView != peekView) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
_peekView.delegate = nil;
|
|
348
|
+
_peekView = nil;
|
|
349
|
+
[self peekViewDidChangeSize:CGSizeZero];
|
|
350
|
+
}
|
|
351
|
+
|
|
281
352
|
#pragma mark - Keyboard Observer
|
|
282
353
|
|
|
283
354
|
- (void)setupKeyboardObserverWithViewController:(UIViewController *)viewController {
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
@class TrueSheetPeekView;
|
|
17
|
+
|
|
18
|
+
@protocol TrueSheetPeekViewDelegate <NSObject>
|
|
19
|
+
@optional
|
|
20
|
+
- (void)peekViewDidChangeSize:(CGSize)size;
|
|
21
|
+
- (void)peekViewWillDetach:(TrueSheetPeekView *)peekView;
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
@interface TrueSheetPeekView : RCTViewComponentView
|
|
25
|
+
|
|
26
|
+
@property (nonatomic, weak, nullable) id<TrueSheetPeekViewDelegate> delegate;
|
|
27
|
+
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
NS_ASSUME_NONNULL_END
|
|
31
|
+
|
|
32
|
+
#endif
|
|
@@ -0,0 +1,99 @@
|
|
|
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 "TrueSheetPeekView.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 "TrueSheetContainerView.h"
|
|
17
|
+
|
|
18
|
+
using namespace facebook::react;
|
|
19
|
+
|
|
20
|
+
@implementation TrueSheetPeekView {
|
|
21
|
+
CGRect _lastFrame;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
25
|
+
return concreteComponentDescriptorProvider<TrueSheetPeekViewComponentDescriptor>();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
29
|
+
if (self = [super initWithFrame:frame]) {
|
|
30
|
+
static const auto defaultProps = std::make_shared<const TrueSheetPeekViewProps>();
|
|
31
|
+
_props = defaultProps;
|
|
32
|
+
|
|
33
|
+
_lastFrame = CGRectZero;
|
|
34
|
+
}
|
|
35
|
+
return self;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Peek can be nested anywhere within the content, so it attaches itself to the
|
|
40
|
+
* nearest container instead of being mounted by it. The container also searches
|
|
41
|
+
* for it when the content mounts to cover the initial (bottom-up) mount order.
|
|
42
|
+
*/
|
|
43
|
+
- (void)attachToContainerView {
|
|
44
|
+
if (self.delegate) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
UIView *view = self.superview;
|
|
49
|
+
while (view && ![view isKindOfClass:[TrueSheetContainerView class]]) {
|
|
50
|
+
view = view.superview;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (view) {
|
|
54
|
+
[(TrueSheetContainerView *)view attachPeekView:self];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)didMoveToSuperview {
|
|
59
|
+
[super didMoveToSuperview];
|
|
60
|
+
if (self.superview) {
|
|
61
|
+
[self attachToContainerView];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
- (void)didMoveToWindow {
|
|
66
|
+
[super didMoveToWindow];
|
|
67
|
+
if (self.window) {
|
|
68
|
+
[self attachToContainerView];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
|
|
73
|
+
oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics {
|
|
74
|
+
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
75
|
+
|
|
76
|
+
// Compare the whole frame — the peek's offset within the content affects
|
|
77
|
+
// the peek detent, so position changes matter too.
|
|
78
|
+
CGRect newFrame = CGRectMake(layoutMetrics.frame.origin.x, layoutMetrics.frame.origin.y,
|
|
79
|
+
layoutMetrics.frame.size.width, layoutMetrics.frame.size.height);
|
|
80
|
+
|
|
81
|
+
if (!CGRectEqualToRect(newFrame, _lastFrame)) {
|
|
82
|
+
_lastFrame = newFrame;
|
|
83
|
+
[self.delegate peekViewDidChangeSize:newFrame.size];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)prepareForRecycle {
|
|
88
|
+
[super prepareForRecycle];
|
|
89
|
+
_lastFrame = CGRectZero;
|
|
90
|
+
[self.delegate peekViewWillDetach:self];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@end
|
|
94
|
+
|
|
95
|
+
Class<RCTComponentViewProtocol> TrueSheetPeekViewCls(void) {
|
|
96
|
+
return TrueSheetPeekView.class;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#endif
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -398,6 +398,11 @@ using namespace facebook::react;
|
|
|
398
398
|
_controller.footerHeight = @(footerHeight);
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
+
CGFloat peekContentHeight = [_containerView peekContentHeight];
|
|
402
|
+
if (peekContentHeight > 0) {
|
|
403
|
+
_controller.peekContentHeight = @(peekContentHeight);
|
|
404
|
+
}
|
|
405
|
+
|
|
401
406
|
if (_eventEmitter) {
|
|
402
407
|
[TrueSheetLifecycleEvents emitMount:_eventEmitter];
|
|
403
408
|
} else {
|
|
@@ -570,6 +575,9 @@ using namespace facebook::react;
|
|
|
570
575
|
if (!self->_containerView)
|
|
571
576
|
return;
|
|
572
577
|
|
|
578
|
+
// Refresh here (not just on peek size events) since the peek's offset
|
|
579
|
+
// within the content can change without its own size changing.
|
|
580
|
+
self->_controller.peekContentHeight = @([self->_containerView peekContentHeight]);
|
|
573
581
|
[self->_controller setupSheetDetentsForSizeChange];
|
|
574
582
|
});
|
|
575
583
|
}
|
|
@@ -590,6 +598,10 @@ using namespace facebook::react;
|
|
|
590
598
|
[_controller setupAccessibilityContainer];
|
|
591
599
|
}
|
|
592
600
|
|
|
601
|
+
- (void)containerViewPeekDidChangeSize:(CGSize)newSize {
|
|
602
|
+
[self setupSheetDetentsForSizeChange];
|
|
603
|
+
}
|
|
604
|
+
|
|
593
605
|
// When the ScrollView changes (e.g. conditional remount), re-pin the new ScrollView.
|
|
594
606
|
- (void)containerViewScrollViewDidChange {
|
|
595
607
|
[self setupScrollable];
|
|
@@ -58,6 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
58
58
|
@property (nonatomic, strong, nullable) NSNumber *contentHeight;
|
|
59
59
|
@property (nonatomic, strong, nullable) NSNumber *headerHeight;
|
|
60
60
|
@property (nonatomic, strong, nullable) NSNumber *footerHeight;
|
|
61
|
+
@property (nonatomic, strong, nullable) NSNumber *peekContentHeight;
|
|
61
62
|
@property (nonatomic, strong, nullable) UIColor *backgroundColor;
|
|
62
63
|
@property (nonatomic, strong, nullable) NSNumber *cornerRadius;
|
|
63
64
|
@property (nonatomic, assign) BOOL grabber;
|