@rn-tools/sheets 0.1.1 → 0.1.3
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 +18 -2
- package/android/src/main/java/expo/modules/sheets/RNToolsSheetsView.kt +2 -2
- package/ios/RNToolsSheets.podspec +4 -0
- package/ios/RNToolsSheetsView.swift +165 -86
- package/ios/Sources/RNTSurfaceTouchHandlerWrapper.h +11 -0
- package/ios/Sources/RNTSurfaceTouchHandlerWrapper.mm +21 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,8 +12,24 @@ Uses SwiftUI's sheet API and Android's BottomSheetDialog component to render Rea
|
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
-
`yarn add @rntools/sheets`
|
|
15
|
+
`yarn add @rntools/sheets expo-build-properties`
|
|
16
|
+
|
|
17
|
+
Update your minimum iOS deployment target to 16 in `app.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"plugins": [
|
|
22
|
+
[
|
|
23
|
+
"expo-build-properties",
|
|
24
|
+
{
|
|
25
|
+
"ios": {
|
|
26
|
+
"deploymentTarget": "16.4"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
16
31
|
|
|
32
|
+
```
|
|
17
33
|
|
|
18
34
|
As with most non-core expo modules this requires a new native build
|
|
19
35
|
|
|
@@ -28,7 +44,7 @@ export default function App() {
|
|
|
28
44
|
|
|
29
45
|
return (
|
|
30
46
|
<View className="flex-1">
|
|
31
|
-
<Button title="Show sheet" onPress={() => setIsOpen(
|
|
47
|
+
<Button title="Show sheet" onPress={() => setIsOpen(true)} />
|
|
32
48
|
|
|
33
49
|
<BottomSheet
|
|
34
50
|
isOpen={isOpen}
|
|
@@ -89,9 +89,9 @@ class RNToolsSheetsView(context: Context, appContext: AppContext) : ExpoView(con
|
|
|
89
89
|
try {
|
|
90
90
|
Color.parseColor(it) // Convert hex string to Color
|
|
91
91
|
} catch (e: IllegalArgumentException) {
|
|
92
|
-
Color.
|
|
92
|
+
Color.WHITE
|
|
93
93
|
}
|
|
94
|
-
} ?: Color.
|
|
94
|
+
} ?: Color.WHITE
|
|
95
95
|
|
|
96
96
|
val drawable = GradientDrawable().apply {
|
|
97
97
|
setColor(backgroundColor)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
Pod::Spec.new do |s|
|
|
6
8
|
s.name = 'RNToolsSheets'
|
|
@@ -19,10 +21,12 @@ Pod::Spec.new do |s|
|
|
|
19
21
|
s.static_framework = true
|
|
20
22
|
|
|
21
23
|
s.dependency 'ExpoModulesCore'
|
|
24
|
+
s.public_header_files = 'Sources/RNTSurfaceTouchHandlerWrapper.h'
|
|
22
25
|
|
|
23
26
|
# Swift/Objective-C compatibility
|
|
24
27
|
s.pod_target_xcconfig = {
|
|
25
28
|
'DEFINES_MODULE' => 'YES',
|
|
29
|
+
'OTHER_SWIFT_FLAGS' => new_arch_enabled ? '-DRCT_NEW_ARCH_ENABLED' : ''
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
|
+
import React
|
|
2
3
|
import SwiftUI
|
|
3
4
|
|
|
4
5
|
public class SheetProps: ObservableObject {
|
|
@@ -11,7 +12,6 @@ public class SheetProps: ObservableObject {
|
|
|
11
12
|
@Published var grabberVisible: Bool = true
|
|
12
13
|
@Published var backgroundColor: String? = nil
|
|
13
14
|
@Published var cornerRadius: Float? = nil
|
|
14
|
-
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
struct SheetAppearance: Record {
|
|
@@ -20,7 +20,7 @@ struct SheetAppearance: Record {
|
|
|
20
20
|
|
|
21
21
|
@Field
|
|
22
22
|
var backgroundColor: String?
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
@Field
|
|
25
25
|
var cornerRadius: Float?
|
|
26
26
|
}
|
|
@@ -32,50 +32,84 @@ public class RNToolsSheetsView: ExpoView {
|
|
|
32
32
|
|
|
33
33
|
var touchHandler: RCTTouchHandler?
|
|
34
34
|
|
|
35
|
+
private lazy var sheetVC = SheetInternalViewController()
|
|
36
|
+
|
|
35
37
|
lazy var hostingController = UIHostingController(
|
|
36
38
|
rootView: ContentView(
|
|
37
|
-
props: props,
|
|
39
|
+
props: props, sheetVC: sheetVC, onDismiss: onDismiss,
|
|
40
|
+
onStateChange: onStateChange))
|
|
38
41
|
|
|
39
42
|
required init(appContext: AppContext? = nil) {
|
|
40
43
|
super.init(appContext: appContext)
|
|
41
44
|
|
|
42
45
|
if let bridge = appContext?.reactBridge {
|
|
43
|
-
|
|
46
|
+
sheetVC.bridge = bridge
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
hostingController.view.autoresizingMask = [
|
|
47
50
|
.flexibleWidth, .flexibleHeight,
|
|
48
51
|
]
|
|
49
52
|
hostingController.view.backgroundColor = UIColor.clear
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public override func layoutSubviews() {
|
|
56
|
+
super.layoutSubviews()
|
|
57
|
+
hostingController.view.frame = bounds
|
|
58
|
+
}
|
|
52
59
|
|
|
60
|
+
public override func didMoveToSuperview() {
|
|
61
|
+
super.didMoveToSuperview()
|
|
62
|
+
let parentViewController = findParentViewControllerOrNil()
|
|
63
|
+
|
|
64
|
+
parentViewController.apply {
|
|
65
|
+
$0.addChild(hostingController)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
hostingController.view.layer.removeAllAnimations()
|
|
69
|
+
hostingController.view.frame = bounds
|
|
53
70
|
addSubview(hostingController.view)
|
|
71
|
+
parentViewController.apply {
|
|
72
|
+
hostingController.didMove(toParent: $0)
|
|
73
|
+
}
|
|
54
74
|
}
|
|
55
75
|
|
|
56
76
|
public override func reactSubviews() -> [UIView]! {
|
|
57
77
|
return []
|
|
58
78
|
}
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
81
|
+
public override func mountChildComponentView(
|
|
82
|
+
_ childComponentView: UIView,
|
|
83
|
+
index: Int
|
|
84
|
+
) {
|
|
85
|
+
sheetVC.insertChild(childComponentView, at: index)
|
|
66
86
|
}
|
|
67
|
-
}
|
|
68
87
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
public override func unmountChildComponentView(
|
|
89
|
+
_ childComponentView: UIView,
|
|
90
|
+
index: Int
|
|
91
|
+
) {
|
|
92
|
+
childComponentView.removeFromSuperview()
|
|
73
93
|
}
|
|
74
|
-
|
|
94
|
+
|
|
95
|
+
#else
|
|
96
|
+
public override func insertReactSubview(
|
|
97
|
+
_ subview: UIView!, at atIndex: Int
|
|
98
|
+
) {
|
|
99
|
+
sheetVC.insertChild(subview, at: atIndex)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public override func removeReactSubview(_ subview: UIView!) {
|
|
103
|
+
sheetVC.removeChild(subview)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#endif
|
|
107
|
+
|
|
75
108
|
}
|
|
76
109
|
|
|
77
110
|
struct ContentView: View {
|
|
78
111
|
@ObservedObject var props: SheetProps
|
|
112
|
+
var sheetVC: SheetInternalViewController
|
|
79
113
|
var onDismiss: EventDispatcher
|
|
80
114
|
var onStateChange: EventDispatcher
|
|
81
115
|
|
|
@@ -119,101 +153,146 @@ struct ContentView: View {
|
|
|
119
153
|
onStateChange(["type": "HIDDEN"])
|
|
120
154
|
}
|
|
121
155
|
) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if !isDragging {
|
|
134
|
-
isDragging = true
|
|
135
|
-
onStateChange(["type": "DRAGGING"])
|
|
136
|
-
}
|
|
156
|
+
SheetInternalVCRepresentable(controller: sheetVC)
|
|
157
|
+
.background(
|
|
158
|
+
GeometryReader { geometry in
|
|
159
|
+
Color.clear
|
|
160
|
+
.onChange(of: geometry.size.height) {
|
|
161
|
+
newHeight in
|
|
162
|
+
if abs(newHeight - lastHeight) > 2 {
|
|
163
|
+
if !isDragging {
|
|
164
|
+
isDragging = true
|
|
165
|
+
onStateChange(["type": "DRAGGING"])
|
|
166
|
+
}
|
|
137
167
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
168
|
+
settleTimer?.invalidate()
|
|
169
|
+
settleTimer = Timer.scheduledTimer(
|
|
170
|
+
withTimeInterval: 0.15,
|
|
171
|
+
repeats: false
|
|
172
|
+
) { _ in
|
|
173
|
+
isDragging = false
|
|
174
|
+
onStateChange(["type": "SETTLING"])
|
|
175
|
+
|
|
176
|
+
DispatchQueue.main.asyncAfter(
|
|
177
|
+
deadline: .now() + 0.15
|
|
178
|
+
) {
|
|
179
|
+
let idx = upperSnapIndex(
|
|
180
|
+
for: newHeight,
|
|
181
|
+
snapPoints: props.snapPoints
|
|
182
|
+
)
|
|
183
|
+
onStateChange([
|
|
184
|
+
"type": "OPEN",
|
|
185
|
+
"payload": ["index": idx],
|
|
186
|
+
])
|
|
187
|
+
}
|
|
156
188
|
}
|
|
157
189
|
}
|
|
158
|
-
}
|
|
159
190
|
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
lastHeight = newHeight
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
.presentationBackground(
|
|
196
|
+
props.backgroundColor != nil
|
|
197
|
+
? Color(hex: props.backgroundColor!) : Color.white
|
|
198
|
+
)
|
|
199
|
+
.presentationDragIndicator(
|
|
200
|
+
props.grabberVisible ? .visible : .hidden
|
|
201
|
+
)
|
|
202
|
+
.presentationDetents(
|
|
203
|
+
Set(detents),
|
|
204
|
+
selection: $selectedDetent
|
|
205
|
+
)
|
|
206
|
+
.presentationCornerRadius(
|
|
207
|
+
props.cornerRadius.map { CGFloat($0) }
|
|
208
|
+
)
|
|
209
|
+
.onAppear {
|
|
210
|
+
selectedDetent = detent(for: props.openToIndex)
|
|
162
211
|
}
|
|
163
|
-
)
|
|
164
|
-
.presentationBackground(props.backgroundColor != nil ? Color(hex: props.backgroundColor!) : Color.white)
|
|
165
|
-
.presentationDragIndicator(
|
|
166
|
-
props.grabberVisible ? .visible : .hidden
|
|
167
|
-
)
|
|
168
|
-
.presentationDetents(
|
|
169
|
-
Set(detents),
|
|
170
|
-
selection: $selectedDetent
|
|
171
|
-
)
|
|
172
|
-
.presentationCornerRadius(props.cornerRadius.map { CGFloat($0) })
|
|
173
|
-
.onAppear {
|
|
174
|
-
selectedDetent = detent(for: props.openToIndex)
|
|
175
|
-
}
|
|
176
212
|
}
|
|
177
213
|
}
|
|
178
214
|
}
|
|
179
215
|
|
|
180
|
-
struct
|
|
181
|
-
|
|
216
|
+
struct SheetInternalVCRepresentable: UIViewControllerRepresentable {
|
|
217
|
+
let controller: SheetInternalViewController
|
|
182
218
|
|
|
183
|
-
func
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
func makeUIViewController(context: Context) -> SheetInternalViewController {
|
|
220
|
+
controller
|
|
221
|
+
}
|
|
222
|
+
func updateUIViewController(
|
|
223
|
+
_ uiViewController: SheetInternalViewController,
|
|
224
|
+
context: Context
|
|
225
|
+
) {}
|
|
226
|
+
}
|
|
186
227
|
|
|
187
|
-
|
|
188
|
-
|
|
228
|
+
final class SheetInternalViewController: UIViewController {
|
|
229
|
+
var bridge: RCTBridge? {
|
|
230
|
+
didSet {
|
|
231
|
+
touchHandler = RCTTouchHandler(bridge: bridge)
|
|
232
|
+
self.touchHandler?.attach(to: self.view)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
var surfaceTouchHandler = RNTSurfaceTouchHandlerWrapper()
|
|
236
|
+
var touchHandler: RCTTouchHandler?
|
|
189
237
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
238
|
+
init() {
|
|
239
|
+
super.init(nibName: nil, bundle: nil)
|
|
240
|
+
view.backgroundColor = .clear
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
override func loadView() {
|
|
244
|
+
self.view = UIView()
|
|
245
|
+
|
|
246
|
+
self.surfaceTouchHandler.attach(to: self.view)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@available(*, unavailable) required init?(coder: NSCoder) { fatalError() }
|
|
250
|
+
|
|
251
|
+
func insertChild(_ child: UIView, at index: Int) {
|
|
252
|
+
view.insertSubview(child, at: index)
|
|
197
253
|
|
|
198
|
-
return containerView
|
|
199
254
|
}
|
|
200
255
|
|
|
201
|
-
func
|
|
256
|
+
func removeChild(_ child: UIView) {
|
|
257
|
+
child.removeFromSuperview()
|
|
258
|
+
}
|
|
202
259
|
}
|
|
203
260
|
|
|
261
|
+
extension Optional {
|
|
262
|
+
func apply(_ fn: (Wrapped) -> Void) {
|
|
263
|
+
if case let .some(val) = self {
|
|
264
|
+
fn(val)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
extension UIView {
|
|
270
|
+
// Walks the responder chain to find the parent UIViewController
|
|
271
|
+
// or null if not in a heirarchy yet
|
|
272
|
+
func findParentViewControllerOrNil() -> UIViewController? {
|
|
273
|
+
var nextResponder: UIResponder? = next
|
|
274
|
+
while nextResponder != nil && nextResponder as? UIViewController == nil
|
|
275
|
+
{
|
|
276
|
+
nextResponder = nextResponder?.next
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return nextResponder as? UIViewController
|
|
280
|
+
}
|
|
281
|
+
}
|
|
204
282
|
|
|
205
283
|
extension Color {
|
|
206
284
|
init(hex: String) {
|
|
207
285
|
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
208
286
|
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
|
209
|
-
|
|
287
|
+
|
|
210
288
|
var rgb: UInt64 = 0
|
|
211
289
|
Scanner(string: hexSanitized).scanHexInt64(&rgb)
|
|
212
|
-
|
|
290
|
+
|
|
213
291
|
let red = Double((rgb & 0xFF0000) >> 16) / 255.0
|
|
214
292
|
let green = Double((rgb & 0x00FF00) >> 8) / 255.0
|
|
215
293
|
let blue = Double(rgb & 0x0000FF) / 255.0
|
|
216
|
-
|
|
294
|
+
|
|
217
295
|
self.init(red: red, green: green, blue: blue)
|
|
218
296
|
}
|
|
219
297
|
}
|
|
298
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// RNTSurfaceTouchHandlerWrapper.mm (Objective‑C++, .mm extension!)
|
|
2
|
+
#import "RNTSurfaceTouchHandlerWrapper.h"
|
|
3
|
+
#import <React/RCTSurfaceTouchHandler.h>
|
|
4
|
+
|
|
5
|
+
@implementation RNTSurfaceTouchHandlerWrapper {
|
|
6
|
+
RCTSurfaceTouchHandler *_handler;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
- (instancetype)init {
|
|
10
|
+
if (self = [super init]) {
|
|
11
|
+
_handler = [[RCTSurfaceTouchHandler alloc] init];
|
|
12
|
+
}
|
|
13
|
+
return self;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (void)attachToView:(UIView *)view {
|
|
17
|
+
[_handler attachToView:view];
|
|
18
|
+
}
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-tools/sheets",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "My new module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"sheets",
|
|
21
21
|
"RNToolsSheets"
|
|
22
22
|
],
|
|
23
|
-
"repository": "https://github.com/ajsmth/rn-tools",
|
|
23
|
+
"repository": "https://github.com/ajsmth/rn-tools/tree/main/packages/sheets",
|
|
24
24
|
"bugs": {
|
|
25
25
|
"url": "https://github.com/ajsmth/rn-tools/issues"
|
|
26
26
|
},
|