@rn-tools/sheets 0.1.4 → 3.0.2
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/CHANGELOG.md +16 -0
- package/README.md +107 -67
- package/android/src/main/java/expo/modules/sheets/RNToolsSheetsModule.kt +8 -4
- package/android/src/main/java/expo/modules/sheets/RNToolsSheetsView.kt +110 -76
- package/android/src/main/java/expo/modules/sheets/SheetProps.kt +2 -1
- package/ios/RNToolsSheets.podspec +3 -3
- package/ios/RNToolsSheetsModule.swift +27 -10
- package/ios/RNToolsSheetsView.swift +269 -224
- package/ios/Sources/RNToolsTouchHandlerHelper.h +15 -0
- package/ios/Sources/RNToolsTouchHandlerHelper.mm +31 -0
- package/mocks/expo-modules-core.mock.ts +9 -0
- package/package.json +10 -14
- package/src/index.ts +4 -1
- package/src/native-sheets-view.tsx +126 -42
- package/src/sheet-slot.tsx +70 -0
- package/src/sheets-client.test.tsx +239 -0
- package/src/sheets-client.tsx +233 -0
- package/src/sheets-provider.tsx +20 -0
- package/vitest.config.mts +25 -0
- package/ios/Sources/RNTSurfaceTouchHandlerWrapper.h +0 -11
- package/ios/Sources/RNTSurfaceTouchHandlerWrapper.mm +0 -43
|
@@ -1,83 +1,133 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
2
|
import React
|
|
3
|
-
import
|
|
3
|
+
import UIKit
|
|
4
4
|
|
|
5
|
-
public class SheetProps
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
public class SheetProps {
|
|
6
|
+
var isOpen: Bool = false
|
|
7
|
+
var initialIndex: Int = 0
|
|
8
|
+
var snapPoints: [CGFloat] = []
|
|
9
|
+
var canDismiss: Bool = true
|
|
10
10
|
|
|
11
11
|
// Appearance props
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
var grabberVisible: Bool = true
|
|
13
|
+
var backgroundColor: String? = nil
|
|
14
|
+
var cornerRadius: Float? = nil
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@Field
|
|
22
|
-
var backgroundColor: String?
|
|
23
|
-
|
|
24
|
-
@Field
|
|
25
|
-
var cornerRadius: Float?
|
|
17
|
+
protocol RNToolsSheetsViewDelegate: AnyObject {
|
|
18
|
+
func handleSheetStateChange(index: Int)
|
|
19
|
+
func handleSheetDismissed()
|
|
20
|
+
func handleSheetCanDismiss() -> Bool
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
public class RNToolsSheetsView: ExpoView {
|
|
23
|
+
public class RNToolsSheetsView: ExpoView, RNToolsSheetsViewDelegate {
|
|
29
24
|
public var props = SheetProps()
|
|
25
|
+
|
|
30
26
|
var onDismiss = EventDispatcher()
|
|
31
27
|
var onStateChange = EventDispatcher()
|
|
28
|
+
var onDismissPrevented = EventDispatcher()
|
|
32
29
|
|
|
33
|
-
var
|
|
34
|
-
|
|
35
|
-
private lazy var sheetVC = SheetInternalViewController()
|
|
36
|
-
|
|
37
|
-
lazy var hostingController = UIHostingController(
|
|
38
|
-
rootView: ContentView(
|
|
39
|
-
props: props, sheetVC: sheetVC, onDismiss: onDismiss,
|
|
40
|
-
onStateChange: onStateChange))
|
|
30
|
+
private lazy var sheetVC = SheetViewController()
|
|
41
31
|
|
|
42
32
|
required init(appContext: AppContext? = nil) {
|
|
43
33
|
super.init(appContext: appContext)
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
sheetVC.appContext = appContext
|
|
36
|
+
sheetVC.delegate = self
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
deinit {
|
|
40
|
+
sheetVC.cleanup()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func updateSnapPoints(_ snapPoints: [CGFloat]) {
|
|
44
|
+
props.snapPoints = snapPoints
|
|
45
|
+
if props.isOpen {
|
|
46
|
+
sheetVC.updateSheetConfiguration(
|
|
47
|
+
openTo: props.initialIndex,
|
|
48
|
+
snapPoints: props.snapPoints,
|
|
49
|
+
grabberVisible: props.grabberVisible,
|
|
50
|
+
backgroundColor: props.backgroundColor,
|
|
51
|
+
cornerRadius: props.cornerRadius
|
|
52
|
+
)
|
|
47
53
|
}
|
|
54
|
+
}
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
func updateIsOpen(_ isOpen: Bool) {
|
|
57
|
+
props.isOpen = isOpen
|
|
58
|
+
if isOpen {
|
|
59
|
+
sheetVC.presentSheet(
|
|
60
|
+
openTo: props.initialIndex,
|
|
61
|
+
snapPoints: props.snapPoints,
|
|
62
|
+
grabberVisible: props.grabberVisible,
|
|
63
|
+
backgroundColor: props.backgroundColor,
|
|
64
|
+
cornerRadius: props.cornerRadius
|
|
65
|
+
)
|
|
66
|
+
} else {
|
|
67
|
+
sheetVC.dismissSheet()
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
func updateInitialIndex(_ initialIndex: Int) {
|
|
72
|
+
props.initialIndex = initialIndex
|
|
73
|
+
if props.isOpen {
|
|
74
|
+
sheetVC.updateSheetConfiguration(
|
|
75
|
+
openTo: props.initialIndex,
|
|
76
|
+
snapPoints: props.snapPoints,
|
|
77
|
+
grabberVisible: props.grabberVisible,
|
|
78
|
+
backgroundColor: props.backgroundColor,
|
|
79
|
+
cornerRadius: props.cornerRadius
|
|
80
|
+
)
|
|
81
|
+
}
|
|
58
82
|
}
|
|
59
83
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
func updateCanDismiss(_ canDismiss: Bool) {
|
|
85
|
+
props.canDismiss = canDismiss
|
|
86
|
+
}
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
func updateAppearance(
|
|
89
|
+
grabberVisible: Bool,
|
|
90
|
+
backgroundColor: String?,
|
|
91
|
+
cornerRadius: Float?
|
|
92
|
+
) {
|
|
93
|
+
props.grabberVisible = grabberVisible
|
|
94
|
+
props.backgroundColor = backgroundColor
|
|
95
|
+
props.cornerRadius = cornerRadius
|
|
96
|
+
if props.isOpen {
|
|
97
|
+
sheetVC.updateSheetConfiguration(
|
|
98
|
+
openTo: props.initialIndex,
|
|
99
|
+
snapPoints: props.snapPoints,
|
|
100
|
+
grabberVisible: props.grabberVisible,
|
|
101
|
+
backgroundColor: props.backgroundColor,
|
|
102
|
+
cornerRadius: props.cornerRadius
|
|
103
|
+
)
|
|
66
104
|
}
|
|
105
|
+
}
|
|
67
106
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
107
|
+
func handleSheetDismissed() {
|
|
108
|
+
onDismiss([:])
|
|
109
|
+
onStateChange(["type": "HIDDEN"])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func handleSheetStateChange(index: Int) {
|
|
113
|
+
onStateChange([
|
|
114
|
+
"type": "OPEN",
|
|
115
|
+
"payload": ["index": index],
|
|
116
|
+
])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func handleSheetCanDismiss() -> Bool {
|
|
120
|
+
if !props.canDismiss {
|
|
121
|
+
onDismissPrevented([:])
|
|
73
122
|
}
|
|
123
|
+
return props.canDismiss
|
|
74
124
|
}
|
|
75
125
|
|
|
76
126
|
public override func reactSubviews() -> [UIView]! {
|
|
77
127
|
return []
|
|
78
128
|
}
|
|
79
129
|
|
|
80
|
-
|
|
130
|
+
|
|
81
131
|
public override func mountChildComponentView(
|
|
82
132
|
_ childComponentView: UIView,
|
|
83
133
|
index: Int
|
|
@@ -91,164 +141,136 @@ public class RNToolsSheetsView: ExpoView {
|
|
|
91
141
|
) {
|
|
92
142
|
childComponentView.removeFromSuperview()
|
|
93
143
|
}
|
|
144
|
+
// public override func insertReactSubview(
|
|
145
|
+
// _ subview: UIView!, at atIndex: Int
|
|
146
|
+
// ) {
|
|
147
|
+
// sheetVC.insertChild(subview, at: atIndex)
|
|
148
|
+
// }
|
|
149
|
+
//
|
|
150
|
+
// public override func removeReactSubview(_ subview: UIView!) {
|
|
151
|
+
// sheetVC.removeChild(subview)
|
|
152
|
+
// }
|
|
94
153
|
|
|
95
|
-
|
|
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
|
-
}
|
|
154
|
+
}
|
|
105
155
|
|
|
106
|
-
|
|
156
|
+
final class SheetViewController: UIViewController,
|
|
157
|
+
UISheetPresentationControllerDelegate
|
|
158
|
+
{
|
|
159
|
+
weak var delegate: RNToolsSheetsViewDelegate?
|
|
107
160
|
|
|
108
|
-
|
|
161
|
+
var appContext: AppContext? {
|
|
162
|
+
didSet {
|
|
163
|
+
_ = view
|
|
164
|
+
}
|
|
165
|
+
}
|
|
109
166
|
|
|
110
|
-
|
|
111
|
-
@ObservedObject var props: SheetProps
|
|
112
|
-
var sheetVC: SheetInternalViewController
|
|
113
|
-
var onDismiss: EventDispatcher
|
|
114
|
-
var onStateChange: EventDispatcher
|
|
167
|
+
@available(*, unavailable) required init?(coder: NSCoder) { fatalError() }
|
|
115
168
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
169
|
+
init() {
|
|
170
|
+
super.init(nibName: nil, bundle: nil)
|
|
171
|
+
view.backgroundColor = .white
|
|
172
|
+
}
|
|
120
173
|
|
|
121
|
-
|
|
122
|
-
|
|
174
|
+
override func loadView() {
|
|
175
|
+
self.view = UIView()
|
|
176
|
+
RNToolsTouchHandlerHelper.createAndAttachTouchHandler(for: self.view)
|
|
123
177
|
}
|
|
178
|
+
|
|
124
179
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
let i = index,
|
|
128
|
-
detents.indices.contains(i)
|
|
129
|
-
else { return detents.first! }
|
|
130
|
-
return detents[i]
|
|
180
|
+
deinit {
|
|
181
|
+
overlayWindow = nil
|
|
131
182
|
}
|
|
132
183
|
|
|
133
|
-
private
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
184
|
+
private let detentTag = UUID().uuidString
|
|
185
|
+
|
|
186
|
+
private var overlayWindow: UIWindow?
|
|
187
|
+
|
|
188
|
+
func presentSheet(
|
|
189
|
+
openTo index: Int = 0,
|
|
190
|
+
snapPoints: [CGFloat],
|
|
191
|
+
grabberVisible: Bool,
|
|
192
|
+
backgroundColor: String?,
|
|
193
|
+
cornerRadius: Float?
|
|
194
|
+
) {
|
|
195
|
+
guard overlayWindow == nil else {
|
|
196
|
+
updateSheetConfiguration(
|
|
197
|
+
openTo: index,
|
|
198
|
+
snapPoints: snapPoints,
|
|
199
|
+
grabberVisible: grabberVisible,
|
|
200
|
+
backgroundColor: backgroundColor,
|
|
201
|
+
cornerRadius: cornerRadius
|
|
202
|
+
)
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
modalPresentationStyle = .pageSheet
|
|
207
|
+
|
|
208
|
+
updateSheetConfiguration(
|
|
209
|
+
openTo: index,
|
|
210
|
+
snapPoints: snapPoints,
|
|
211
|
+
grabberVisible: grabberVisible,
|
|
212
|
+
backgroundColor: backgroundColor,
|
|
213
|
+
cornerRadius: cornerRadius
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
let w = UIWindow(frame: UIScreen.main.bounds)
|
|
217
|
+
w.windowLevel = .statusBar + 2
|
|
218
|
+
w.rootViewController = UIViewController()
|
|
219
|
+
w.makeKeyAndVisible()
|
|
138
220
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
221
|
+
overlayWindow = w
|
|
222
|
+
|
|
223
|
+
let host = UIViewController()
|
|
224
|
+
host.modalPresentationStyle = .overFullScreen
|
|
225
|
+
host.view.backgroundColor = .clear
|
|
226
|
+
|
|
227
|
+
w.rootViewController?.present(host, animated: false) {
|
|
228
|
+
host.present(self, animated: true) { [weak self] in
|
|
229
|
+
self?.emitInitialOpenState(requestedIndex: index)
|
|
230
|
+
}
|
|
142
231
|
}
|
|
143
|
-
return sorted.count - 1
|
|
144
232
|
}
|
|
145
233
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
}
|
|
167
|
-
|
|
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
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
lastHeight = newHeight
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
)
|
|
195
|
-
.presentationBackground16_4(
|
|
196
|
-
props.backgroundColor != nil
|
|
197
|
-
? Color(hex: props.backgroundColor!) : Color.white
|
|
198
|
-
)
|
|
199
|
-
.presentationCornerRadius16_4(
|
|
200
|
-
props.cornerRadius.map { CGFloat($0) }
|
|
201
|
-
)
|
|
202
|
-
.presentationDragIndicator(
|
|
203
|
-
props.grabberVisible ? .visible : .hidden
|
|
204
|
-
)
|
|
205
|
-
.presentationDetents(
|
|
206
|
-
Set(detents),
|
|
207
|
-
selection: $selectedDetent
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
.onAppear {
|
|
211
|
-
selectedDetent = detent(for: props.openToIndex)
|
|
212
|
-
}
|
|
234
|
+
func updateSheetConfiguration(
|
|
235
|
+
openTo index: Int = 0,
|
|
236
|
+
snapPoints: [CGFloat],
|
|
237
|
+
grabberVisible: Bool,
|
|
238
|
+
backgroundColor: String?,
|
|
239
|
+
cornerRadius: Float?
|
|
240
|
+
) {
|
|
241
|
+
if let sheet = sheetPresentationController {
|
|
242
|
+
sheet.delegate = self
|
|
243
|
+
sheet.prefersGrabberVisible = grabberVisible
|
|
244
|
+
sheet.detents = makeDetents(from: snapPoints)
|
|
245
|
+
|
|
246
|
+
if let radius = cornerRadius {
|
|
247
|
+
sheet.preferredCornerRadius = CGFloat(radius)
|
|
213
248
|
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
249
|
|
|
217
|
-
|
|
218
|
-
|
|
250
|
+
let detents = sheet.detents
|
|
251
|
+
if detents.indices.contains(index) {
|
|
252
|
+
sheet.selectedDetentIdentifier = detents[index].identifier
|
|
253
|
+
} else {
|
|
254
|
+
sheet.selectedDetentIdentifier = detents.first?.identifier
|
|
255
|
+
}
|
|
256
|
+
}
|
|
219
257
|
|
|
220
|
-
|
|
221
|
-
controller
|
|
258
|
+
view.backgroundColor = UIColor(hex: backgroundColor) ?? .white
|
|
222
259
|
}
|
|
223
|
-
func updateUIViewController(
|
|
224
|
-
_ uiViewController: SheetInternalViewController,
|
|
225
|
-
context: Context
|
|
226
|
-
) {}
|
|
227
|
-
}
|
|
228
260
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
self
|
|
261
|
+
func dismissSheet() {
|
|
262
|
+
dismiss(animated: true) { [weak self] in
|
|
263
|
+
self?.delegate?.handleSheetDismissed()
|
|
264
|
+
self?.overlayWindow?.isHidden = true
|
|
265
|
+
self?.overlayWindow = nil
|
|
234
266
|
}
|
|
235
267
|
}
|
|
236
|
-
var surfaceTouchHandler = RNTSurfaceTouchHandlerWrapper()
|
|
237
|
-
var touchHandler: RCTTouchHandler?
|
|
238
|
-
|
|
239
|
-
init() {
|
|
240
|
-
super.init(nibName: nil, bundle: nil)
|
|
241
|
-
view.backgroundColor = .clear
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
override func loadView() {
|
|
245
|
-
self.view = UIView()
|
|
246
268
|
|
|
247
|
-
|
|
269
|
+
func cleanup() {
|
|
270
|
+
overlayWindow?.isHidden = true
|
|
271
|
+
overlayWindow = nil
|
|
248
272
|
}
|
|
249
273
|
|
|
250
|
-
@available(*, unavailable) required init?(coder: NSCoder) { fatalError() }
|
|
251
|
-
|
|
252
274
|
func insertChild(_ child: UIView, at index: Int) {
|
|
253
275
|
view.insertSubview(child, at: index)
|
|
254
276
|
|
|
@@ -257,64 +279,87 @@ final class SheetInternalViewController: UIViewController {
|
|
|
257
279
|
func removeChild(_ child: UIView) {
|
|
258
280
|
child.removeFromSuperview()
|
|
259
281
|
}
|
|
260
|
-
}
|
|
261
282
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
283
|
+
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(
|
|
284
|
+
_ sheetPresentationController: UISheetPresentationController
|
|
285
|
+
) {
|
|
286
|
+
guard
|
|
287
|
+
let selectedID = sheetPresentationController
|
|
288
|
+
.selectedDetentIdentifier,
|
|
289
|
+
let index = sheetPresentationController.detents
|
|
290
|
+
.firstIndex(where: { $0.identifier == selectedID })
|
|
291
|
+
else { return }
|
|
292
|
+
|
|
293
|
+
delegate?.handleSheetStateChange(index: index)
|
|
267
294
|
}
|
|
268
|
-
}
|
|
269
295
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
while nextResponder != nil && nextResponder as? UIViewController == nil
|
|
276
|
-
{
|
|
277
|
-
nextResponder = nextResponder?.next
|
|
296
|
+
func presentationControllerShouldDismiss(
|
|
297
|
+
_ presentationController: UIPresentationController
|
|
298
|
+
) -> Bool {
|
|
299
|
+
if let d = delegate {
|
|
300
|
+
return d.handleSheetCanDismiss()
|
|
278
301
|
}
|
|
279
302
|
|
|
280
|
-
return
|
|
303
|
+
return true
|
|
281
304
|
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
extension Color {
|
|
285
|
-
init(hex: String) {
|
|
286
|
-
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
287
|
-
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
|
288
|
-
|
|
289
|
-
var rgb: UInt64 = 0
|
|
290
|
-
Scanner(string: hexSanitized).scanHexInt64(&rgb)
|
|
291
305
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
306
|
+
func presentationControllerDidDismiss(
|
|
307
|
+
_ presentationController: UIPresentationController
|
|
308
|
+
) {
|
|
309
|
+
delegate?.handleSheetDismissed()
|
|
310
|
+
cleanup()
|
|
297
311
|
}
|
|
298
|
-
}
|
|
299
312
|
|
|
313
|
+
private func emitInitialOpenState(requestedIndex: Int) {
|
|
314
|
+
guard let sheet = sheetPresentationController else { return }
|
|
300
315
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
316
|
+
if
|
|
317
|
+
let selectedID = sheet.selectedDetentIdentifier,
|
|
318
|
+
let index = sheet.detents.firstIndex(where: { $0.identifier == selectedID })
|
|
319
|
+
{
|
|
320
|
+
delegate?.handleSheetStateChange(index: index)
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let fallbackIndex: Int
|
|
325
|
+
if sheet.detents.indices.contains(requestedIndex) {
|
|
326
|
+
fallbackIndex = requestedIndex
|
|
306
327
|
} else {
|
|
307
|
-
|
|
328
|
+
fallbackIndex = 0
|
|
308
329
|
}
|
|
330
|
+
delegate?.handleSheetStateChange(index: fallbackIndex)
|
|
309
331
|
}
|
|
310
332
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
333
|
+
private func makeDetents(from points: [CGFloat])
|
|
334
|
+
-> [UISheetPresentationController.Detent]
|
|
335
|
+
{
|
|
336
|
+
guard !points.isEmpty else { return [.large()] }
|
|
337
|
+
|
|
338
|
+
return points.enumerated().map { idx, raw in
|
|
339
|
+
.custom(identifier: .init("\(detentTag)_\(idx)")) { _ in
|
|
340
|
+
return raw
|
|
341
|
+
}
|
|
317
342
|
}
|
|
318
343
|
}
|
|
344
|
+
|
|
319
345
|
}
|
|
320
346
|
|
|
347
|
+
extension UIColor {
|
|
348
|
+
convenience init?(hex: String?) {
|
|
349
|
+
guard let hex, !hex.isEmpty else { return nil }
|
|
350
|
+
|
|
351
|
+
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
352
|
+
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
|
353
|
+
|
|
354
|
+
guard hexSanitized.count == 6 else { return nil }
|
|
355
|
+
|
|
356
|
+
var rgb: UInt64 = 0
|
|
357
|
+
guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
|
|
358
|
+
|
|
359
|
+
let red = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
|
|
360
|
+
let green = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
|
|
361
|
+
let blue = CGFloat(rgb & 0x0000FF) / 255.0
|
|
362
|
+
|
|
363
|
+
self.init(red: red, green: green, blue: blue, alpha: 1.0)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// RNToolsTouchHandlerHelper.h
|
|
2
|
+
|
|
3
|
+
#import <Foundation/Foundation.h>
|
|
4
|
+
#import <UIKit/UIKit.h>
|
|
5
|
+
|
|
6
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
7
|
+
|
|
8
|
+
@interface RNToolsTouchHandlerHelper : NSObject
|
|
9
|
+
|
|
10
|
+
+ (nullable UIGestureRecognizer *)createAndAttachTouchHandlerForView:(UIView *)view;
|
|
11
|
+
+ (void)detachTouchHandler:(nullable UIGestureRecognizer *)handler fromView:(UIView *)view;
|
|
12
|
+
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// RNToolsTouchHandlerHelper.mm
|
|
2
|
+
|
|
3
|
+
#import "RNToolsTouchHandlerHelper.h"
|
|
4
|
+
#import <React/RCTSurfaceTouchHandler.h>
|
|
5
|
+
|
|
6
|
+
@implementation RNToolsTouchHandlerHelper
|
|
7
|
+
|
|
8
|
+
+ (nullable UIGestureRecognizer *)createAndAttachTouchHandlerForView:(UIView *)view {
|
|
9
|
+
for (UIGestureRecognizer *recognizer in [view.gestureRecognizers copy]) {
|
|
10
|
+
if ([recognizer isKindOfClass:[RCTSurfaceTouchHandler class]]) {
|
|
11
|
+
return nil;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
RCTSurfaceTouchHandler *touchHandler = [[RCTSurfaceTouchHandler alloc] init];
|
|
16
|
+
[touchHandler attachToView:view];
|
|
17
|
+
return touchHandler;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
+ (void)detachTouchHandler:(nullable UIGestureRecognizer *)handler fromView:(UIView *)view {
|
|
21
|
+
if (!handler) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if ([handler isKindOfClass:[RCTSurfaceTouchHandler class]]) {
|
|
26
|
+
RCTSurfaceTouchHandler *touchHandler = (RCTSurfaceTouchHandler *)handler;
|
|
27
|
+
[touchHandler detachFromView:view];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-tools/sheets",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
|
+
"description": "A React Native library for creating and managing native sheets in Expo applications.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "expo-module build",
|
|
8
|
-
"clean": "expo-module clean",
|
|
9
|
-
"lint": "expo-module lint",
|
|
10
|
-
"test": "expo-module test",
|
|
11
|
-
"prepare": "expo-module prepare",
|
|
12
|
-
"prepublishOnly": "expo-module prepublishOnly",
|
|
13
|
-
"expo-module": "expo-module",
|
|
14
7
|
"open:ios": "xed example/ios",
|
|
15
|
-
"open:android": "open -a \"Android Studio\" example/android"
|
|
8
|
+
"open:android": "open -a \"Android Studio\" example/android",
|
|
9
|
+
"test": "NODE_OPTIONS='--no-experimental-detect-module' vitest"
|
|
16
10
|
},
|
|
17
11
|
"keywords": [
|
|
18
12
|
"react-native",
|
|
@@ -28,10 +22,12 @@
|
|
|
28
22
|
"license": "MIT",
|
|
29
23
|
"homepage": "https://github.com/ajsmth/rn-tools#readme",
|
|
30
24
|
"devDependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
25
|
+
"@testing-library/react": "^14.2.1",
|
|
26
|
+
"@types/react": "18.3.12",
|
|
27
|
+
"vitest": "^1.6.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@rn-tools/core": "3.0.2"
|
|
35
31
|
},
|
|
36
32
|
"peerDependencies": {
|
|
37
33
|
"expo": "*",
|