react-native-nitro-modal 0.2.0 → 0.3.0-rc
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 +48 -4
- package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +335 -34
- package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +63 -22
- package/android/src/main/java/com/margelo/nitro/nitromodal/ModalConfig.kt +31 -1
- package/android/src/main/java/com/margelo/nitro/nitromodal/ModalContentRoot.kt +48 -5
- package/android/src/main/java/com/margelo/nitro/nitromodal/ModalPresenter.kt +63 -4
- package/android/src/main/java/com/margelo/nitro/nitromodal/NitroModalHostView.kt +68 -8
- package/ios/HybridNitroModal.swift +111 -20
- package/ios/ModalConfig.swift +44 -2
- package/ios/NitroModalHostView.swift +73 -8
- package/ios/SheetView.swift +575 -0
- package/ios/SheetViewController.swift +19 -450
- package/lib/module/NitroModal.js +38 -5
- package/lib/module/NitroModal.js.map +1 -1
- package/lib/module/NitroModalView.web.js +55 -25
- package/lib/module/NitroModalView.web.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/web/AnimatedValue.js +3 -0
- package/lib/module/web/AnimatedValue.js.map +1 -1
- package/lib/module/web/ModalConfig.js +8 -4
- package/lib/module/web/ModalConfig.js.map +1 -1
- package/lib/module/web/ModalController.js +29 -2
- package/lib/module/web/ModalController.js.map +1 -1
- package/lib/module/web/ModalPresenter.js +47 -12
- package/lib/module/web/ModalPresenter.js.map +1 -1
- package/lib/module/web/SheetPresenter.js +54 -29
- package/lib/module/web/SheetPresenter.js.map +1 -1
- package/lib/module/web/dom.js +13 -0
- package/lib/module/web/dom.js.map +1 -1
- package/lib/typescript/src/NitroModal.d.ts +33 -2
- package/lib/typescript/src/NitroModal.d.ts.map +1 -1
- package/lib/typescript/src/NitroModal.nitro.d.ts +29 -2
- package/lib/typescript/src/NitroModal.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroModalView.web.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/web/AnimatedValue.d.ts +1 -0
- package/lib/typescript/src/web/AnimatedValue.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalConfig.d.ts +6 -0
- package/lib/typescript/src/web/ModalConfig.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalController.d.ts +5 -0
- package/lib/typescript/src/web/ModalController.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalPresenter.d.ts +9 -0
- package/lib/typescript/src/web/ModalPresenter.d.ts.map +1 -1
- package/lib/typescript/src/web/SheetPresenter.d.ts +1 -1
- package/lib/typescript/src/web/SheetPresenter.d.ts.map +1 -1
- package/lib/typescript/src/web/dom.d.ts +2 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroModalSpec.cpp +60 -6
- package/nitrogen/generated/android/c++/JHybridNitroModalSpec.hpp +10 -2
- package/nitrogen/generated/android/c++/JNamedSheetDetent.hpp +64 -0
- package/nitrogen/generated/android/c++/JSheetDetent.cpp +26 -0
- package/nitrogen/generated/android/c++/JSheetDetent.hpp +42 -37
- package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.cpp +20 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/HybridNitroModalSpec.kt +40 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/NamedSheetDetent.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/SheetDetent.kt +47 -10
- package/nitrogen/generated/android/nitromodal+autolinking.cmake +1 -0
- package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Bridge.hpp +53 -8
- package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Umbrella.hpp +4 -3
- package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.hpp +32 -5
- package/nitrogen/generated/ios/c++/views/HybridNitroModalComponent.mm +24 -0
- package/nitrogen/generated/ios/swift/HybridNitroModalSpec.swift +4 -0
- package/nitrogen/generated/ios/swift/HybridNitroModalSpec_cxx.swift +110 -5
- package/nitrogen/generated/ios/swift/NamedSheetDetent.swift +48 -0
- package/nitrogen/generated/ios/swift/SheetDetent.swift +15 -33
- package/nitrogen/generated/shared/c++/HybridNitroModalSpec.cpp +8 -0
- package/nitrogen/generated/shared/c++/HybridNitroModalSpec.hpp +14 -5
- package/nitrogen/generated/shared/c++/{SheetDetent.hpp → NamedSheetDetent.hpp} +17 -17
- package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.cpp +9 -1
- package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.hpp +15 -2
- package/nitrogen/generated/shared/json/NitroModalConfig.json +4 -0
- package/package.json +1 -1
- package/src/NitroModal.nitro.ts +30 -2
- package/src/NitroModal.tsx +68 -4
- package/src/NitroModalView.web.tsx +54 -12
- package/src/index.tsx +2 -0
- package/src/web/AnimatedValue.ts +4 -0
- package/src/web/ModalConfig.ts +13 -3
- package/src/web/ModalController.ts +35 -2
- package/src/web/ModalPresenter.ts +46 -11
- package/src/web/SheetPresenter.ts +98 -33
- package/src/web/dom.ts +17 -0
|
@@ -5,67 +5,29 @@
|
|
|
5
5
|
|
|
6
6
|
import UIKit
|
|
7
7
|
|
|
8
|
-
/// `bottomSheet` mode: an edge-attached
|
|
8
|
+
/// Modal `bottomSheet` mode: an edge-attached `SheetView` presented
|
|
9
|
+
/// `.overFullScreen`.
|
|
9
10
|
///
|
|
10
11
|
/// `UISheetPresentationController` is not used because iOS 26 always floats
|
|
11
12
|
/// sheets inset from the screen edges at partial detents, with no API to opt
|
|
12
13
|
/// out. Detents, dragging, swipe-to-dismiss and the hand-off to scroll views
|
|
13
|
-
/// inside the content are implemented
|
|
14
|
-
final class SheetViewController: UIViewController, ModalPresenting, UIViewControllerTransitioningDelegate
|
|
15
|
-
weak var presentationDelegate: ModalPresentationDelegate?
|
|
16
|
-
|
|
17
|
-
/// Gap kept above the sheet at its tallest, like a system page sheet.
|
|
18
|
-
private static let topGap: CGFloat = 10
|
|
19
|
-
/// Extra sheet height below the screen so it stays attached to the bottom
|
|
20
|
-
/// edge while rubber-banding upward.
|
|
21
|
-
private static let overscroll: CGFloat = 400
|
|
22
|
-
private static let defaultCornerRadius: CGFloat = 16
|
|
23
|
-
private static let regularMaxWidth: CGFloat = 704
|
|
24
|
-
private static let grabberSize = CGSize(width: 36, height: 5)
|
|
25
|
-
|
|
26
|
-
private let contentView: NitroModalContentView
|
|
27
|
-
private let backdrop = BackdropView()
|
|
28
|
-
private let sheetView = UIView()
|
|
29
|
-
private let grabber = UIView()
|
|
30
|
-
private var config: ModalConfig
|
|
31
|
-
private var selectedIndex: Int
|
|
32
|
-
private var keyboardHeight: CGFloat = 0
|
|
33
|
-
|
|
34
|
-
/// The sheet sits below the screen (before presenting / while dismissing).
|
|
35
|
-
private var isOffscreen = true
|
|
36
|
-
/// Top of the sheet while the user drags it; `nil` rests on `selectedIndex`.
|
|
37
|
-
private var dragTop: CGFloat?
|
|
38
|
-
private var drag: DragState?
|
|
39
|
-
/// Vertical velocity of the swipe that requested the dismissal.
|
|
40
|
-
private var releaseVelocity: CGFloat = 0
|
|
41
|
-
|
|
42
|
-
private var lockedScrollView: UIScrollView?
|
|
43
|
-
private var lockedOffsetY: CGFloat = 0
|
|
44
|
-
private var scrollLock: NSKeyValueObservation?
|
|
45
|
-
|
|
46
|
-
private struct DragState {
|
|
47
|
-
/// Unconstrained sheet top, before rubber-banding.
|
|
48
|
-
var rawTop: CGFloat
|
|
49
|
-
var lastTranslation: CGFloat = 0
|
|
50
|
-
var scrollView: UIScrollView?
|
|
51
|
-
/// Whether the last movement moved the sheet rather than the scroll view.
|
|
52
|
-
var sheetDrove = false
|
|
14
|
+
/// inside the content are implemented by `SheetView` with a pan gesture.
|
|
15
|
+
final class SheetViewController: UIViewController, ModalPresenting, UIViewControllerTransitioningDelegate {
|
|
16
|
+
weak var presentationDelegate: ModalPresentationDelegate? {
|
|
17
|
+
didSet { sheet.delegate = presentationDelegate }
|
|
53
18
|
}
|
|
54
19
|
|
|
20
|
+
private let sheet: SheetView
|
|
21
|
+
|
|
55
22
|
init(contentView: NitroModalContentView, config: ModalConfig) {
|
|
56
|
-
|
|
57
|
-
self.config = config
|
|
58
|
-
selectedIndex = config.clampedInitialDetentIndex
|
|
23
|
+
sheet = SheetView(contentView: contentView, config: config, isInline: false)
|
|
59
24
|
super.init(nibName: nil, bundle: nil)
|
|
60
25
|
modalPresentationStyle = .overFullScreen
|
|
61
26
|
transitioningDelegate = self
|
|
62
|
-
|
|
63
|
-
self
|
|
27
|
+
sheet.isTransitioning = { [weak self] in
|
|
28
|
+
guard let self else { return false }
|
|
29
|
+
return self.isBeingPresented || self.isBeingDismissed
|
|
64
30
|
}
|
|
65
|
-
|
|
66
|
-
let center = NotificationCenter.default
|
|
67
|
-
center.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
|
68
|
-
center.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
|
|
69
31
|
}
|
|
70
32
|
|
|
71
33
|
@available(*, unavailable)
|
|
@@ -74,67 +36,30 @@ final class SheetViewController: UIViewController, ModalPresenting, UIViewContro
|
|
|
74
36
|
}
|
|
75
37
|
|
|
76
38
|
override func loadView() {
|
|
77
|
-
|
|
78
|
-
root.backgroundColor = .clear
|
|
79
|
-
root.addSubview(backdrop)
|
|
80
|
-
|
|
81
|
-
sheetView.clipsToBounds = true
|
|
82
|
-
sheetView.layer.cornerCurve = .continuous
|
|
83
|
-
sheetView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
|
84
|
-
sheetView.accessibilityViewIsModal = true
|
|
85
|
-
sheetView.addSubview(contentView)
|
|
86
|
-
|
|
87
|
-
grabber.isUserInteractionEnabled = false
|
|
88
|
-
grabber.backgroundColor = .tertiaryLabel
|
|
89
|
-
grabber.layer.cornerRadius = Self.grabberSize.height / 2
|
|
90
|
-
sheetView.addSubview(grabber)
|
|
91
|
-
root.addSubview(sheetView)
|
|
92
|
-
|
|
93
|
-
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
|
|
94
|
-
pan.delegate = self
|
|
95
|
-
sheetView.addGestureRecognizer(pan)
|
|
96
|
-
|
|
97
|
-
view = root
|
|
98
|
-
applyAppearance()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
override func viewDidLayoutSubviews() {
|
|
102
|
-
super.viewDidLayoutSubviews()
|
|
103
|
-
layoutSheet()
|
|
104
|
-
reportContentArea()
|
|
39
|
+
view = sheet
|
|
105
40
|
}
|
|
106
41
|
|
|
107
42
|
override func viewDidDisappear(_ animated: Bool) {
|
|
108
43
|
super.viewDidDisappear(animated)
|
|
109
|
-
unlockScroll()
|
|
44
|
+
sheet.unlockScroll()
|
|
110
45
|
}
|
|
111
46
|
|
|
112
47
|
override func accessibilityPerformEscape() -> Bool {
|
|
113
|
-
|
|
114
|
-
presentationDelegate?.modalPresentationDidRequestSwipeDismiss()
|
|
115
|
-
return true
|
|
48
|
+
sheet.performEscape()
|
|
116
49
|
}
|
|
117
50
|
|
|
118
51
|
// MARK: ModalPresenting
|
|
119
52
|
|
|
120
53
|
func apply(_ newConfig: ModalConfig) {
|
|
121
|
-
|
|
122
|
-
selectedIndex = min(selectedIndex, max(detents.count - 1, 0))
|
|
123
|
-
guard isViewLoaded else { return }
|
|
124
|
-
applyAppearance()
|
|
125
|
-
animateToRest()
|
|
54
|
+
sheet.apply(newConfig)
|
|
126
55
|
}
|
|
127
56
|
|
|
128
57
|
func contentSizeDidChange() {
|
|
129
|
-
|
|
130
|
-
animateToRest()
|
|
58
|
+
sheet.contentSizeDidChange()
|
|
131
59
|
}
|
|
132
60
|
|
|
133
61
|
func snapToDetent(at index: Int) {
|
|
134
|
-
|
|
135
|
-
selectedIndex = index
|
|
136
|
-
animateToRest()
|
|
137
|
-
presentationDelegate?.modalPresentationDidChangeDetent(index)
|
|
62
|
+
sheet.snapToDetent(at: index)
|
|
138
63
|
}
|
|
139
64
|
|
|
140
65
|
// MARK: UIViewControllerTransitioningDelegate
|
|
@@ -151,349 +76,8 @@ final class SheetViewController: UIViewController, ModalPresenting, UIViewContro
|
|
|
151
76
|
SheetTransition(presenting: false)
|
|
152
77
|
}
|
|
153
78
|
|
|
154
|
-
/// Slides the sheet in or out; a swipe dismissal keeps its release velocity.
|
|
155
79
|
fileprivate func animateTransition(toOffscreen offscreen: Bool, animated: Bool, duration: TimeInterval, completion: @escaping () -> Void) {
|
|
156
|
-
|
|
157
|
-
let velocity = offscreen ? releaseVelocity : 0
|
|
158
|
-
releaseVelocity = 0
|
|
159
|
-
isOffscreen = offscreen
|
|
160
|
-
dragTop = nil
|
|
161
|
-
guard animated else {
|
|
162
|
-
layoutSheet()
|
|
163
|
-
completion()
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
animate(
|
|
167
|
-
distance: currentTop - from,
|
|
168
|
-
velocity: velocity,
|
|
169
|
-
damping: offscreen ? 1 : 0.88,
|
|
170
|
-
duration: duration,
|
|
171
|
-
animations: { self.layoutSheet() },
|
|
172
|
-
completion: completion
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// MARK: Layout
|
|
177
|
-
|
|
178
|
-
private var detents: [SheetDetent] {
|
|
179
|
-
config.detents.isEmpty ? [.fitcontent] : config.detents
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/// Tallest height the content may occupy (excludes the bottom safe area).
|
|
183
|
-
private var maximumDetentValue: CGFloat {
|
|
184
|
-
let insets = view.safeAreaInsets
|
|
185
|
-
return max(0, view.bounds.height - insets.top - insets.bottom - Self.topGap)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private var keyboardLift: CGFloat {
|
|
189
|
-
config.keyboardBehavior == .none ? 0 : max(0, keyboardHeight - view.safeAreaInsets.bottom)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private func height(of detent: SheetDetent) -> CGFloat {
|
|
193
|
-
let maximum = maximumDetentValue
|
|
194
|
-
switch detent {
|
|
195
|
-
case .small: return maximum * 0.25
|
|
196
|
-
case .medium: return maximum * 0.5
|
|
197
|
-
case .large: return maximum
|
|
198
|
-
case .fitcontent: return min(max(contentView.contentSize.height, 1), maximum)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/// Sheet top when resting on `detents[index]`. The bottom safe area is
|
|
203
|
-
/// added below the content.
|
|
204
|
-
private func restingTop(for index: Int) -> CGFloat {
|
|
205
|
-
let detent = detents[min(max(index, 0), detents.count - 1)]
|
|
206
|
-
let top = view.bounds.height - view.safeAreaInsets.bottom - keyboardLift - height(of: detent)
|
|
207
|
-
return max(top, view.safeAreaInsets.top + Self.topGap)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private var detentTops: [CGFloat] {
|
|
211
|
-
detents.indices.map(restingTop(for:))
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private var currentTop: CGFloat {
|
|
215
|
-
if isOffscreen { return view.bounds.height }
|
|
216
|
-
return dragTop ?? restingTop(for: selectedIndex)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private func layoutSheet() {
|
|
220
|
-
let bounds = view.bounds
|
|
221
|
-
let width = Self.width(for: bounds.width, sizeClass: traitCollection.horizontalSizeClass)
|
|
222
|
-
let top = currentTop
|
|
223
|
-
backdrop.frame = bounds
|
|
224
|
-
sheetView.frame = CGRect(x: (bounds.width - width) / 2, y: top, width: width, height: bounds.height + Self.overscroll)
|
|
225
|
-
contentView.frame = CGRect(x: 0, y: 0, width: width, height: maximumDetentValue)
|
|
226
|
-
grabber.frame = CGRect(
|
|
227
|
-
origin: CGPoint(x: (width - Self.grabberSize.width) / 2, y: 5),
|
|
228
|
-
size: Self.grabberSize
|
|
229
|
-
)
|
|
230
|
-
backdrop.alpha = backdropAlpha(forTop: top)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/// Fully dimmed at every detent; fades out below the lowest one.
|
|
234
|
-
private func backdropAlpha(forTop top: CGFloat) -> CGFloat {
|
|
235
|
-
let bottom = view.bounds.height
|
|
236
|
-
guard let lowest = detentTops.max(), bottom > lowest else { return top >= bottom ? 0 : 1 }
|
|
237
|
-
return min(max((bottom - top) / (bottom - lowest), 0), 1)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
private func applyAppearance() {
|
|
241
|
-
backdrop.configure(color: config.backdropColor, opacity: config.backdropOpacity, blurRadius: config.backdropBlurRadius)
|
|
242
|
-
sheetView.backgroundColor = config.contentBackgroundColor
|
|
243
|
-
sheetView.layer.cornerRadius = config.cornerRadius ?? Self.defaultCornerRadius
|
|
244
|
-
grabber.isHidden = !config.grabberVisible
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
private func animateToRest() {
|
|
248
|
-
guard isViewLoaded, drag == nil, !isOffscreen else { return }
|
|
249
|
-
dragTop = nil
|
|
250
|
-
UIView.animate(withDuration: 0.3, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction]) {
|
|
251
|
-
self.view.setNeedsLayout()
|
|
252
|
-
self.view.layoutIfNeeded()
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private func animate(
|
|
257
|
-
distance: CGFloat,
|
|
258
|
-
velocity: CGFloat,
|
|
259
|
-
damping: CGFloat = 0.9,
|
|
260
|
-
duration: TimeInterval = 0.45,
|
|
261
|
-
animations: @escaping () -> Void,
|
|
262
|
-
completion: (() -> Void)? = nil
|
|
263
|
-
) {
|
|
264
|
-
// UIKit expects the initial velocity relative to the distance travelled.
|
|
265
|
-
let relativeVelocity = abs(distance) > 1 ? min(max(velocity / distance, -30), 30) : 0
|
|
266
|
-
UIView.animate(
|
|
267
|
-
withDuration: duration,
|
|
268
|
-
delay: 0,
|
|
269
|
-
usingSpringWithDamping: damping,
|
|
270
|
-
initialSpringVelocity: relativeVelocity,
|
|
271
|
-
options: [.allowUserInteraction, .beginFromCurrentState],
|
|
272
|
-
animations: animations,
|
|
273
|
-
completion: { _ in completion?() }
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// MARK: Content area
|
|
278
|
-
|
|
279
|
-
private func reportContentArea() {
|
|
280
|
-
guard isViewLoaded, view.bounds.width > 0 else { return }
|
|
281
|
-
var height = Self.contentHeight(for: config.detents, maximum: maximumDetentValue)
|
|
282
|
-
if config.keyboardBehavior == .resize {
|
|
283
|
-
height -= keyboardLift
|
|
284
|
-
}
|
|
285
|
-
let width = Self.width(for: view.bounds.width, sizeClass: traitCollection.horizontalSizeClass)
|
|
286
|
-
presentationDelegate?.modalPresentationDidChangeContentArea(CGSize(width: width, height: max(height, 0)))
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/// Height the content may occupy at the largest of `detents`.
|
|
290
|
-
static func contentHeight(for detents: [SheetDetent], maximum: CGFloat) -> CGFloat {
|
|
291
|
-
if detents.isEmpty || detents.contains(.large) || detents.contains(.fitcontent) {
|
|
292
|
-
return maximum
|
|
293
|
-
}
|
|
294
|
-
return detents.contains(.medium) ? maximum * 0.5 : maximum * 0.25
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/// Best guess of `maximumDetentValue` before the sheet is on screen.
|
|
298
|
-
static func estimatedMaximumDetentValue(in window: UIWindow) -> CGFloat {
|
|
299
|
-
let insets = window.safeAreaInsets
|
|
300
|
-
return window.bounds.height - insets.top - insets.bottom - topGap
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/// Best guess of the sheet width before it is on screen.
|
|
304
|
-
static func estimatedWidth(in window: UIWindow) -> CGFloat {
|
|
305
|
-
width(for: window.bounds.width, sizeClass: window.traitCollection.horizontalSizeClass)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private static func width(for available: CGFloat, sizeClass: UIUserInterfaceSizeClass) -> CGFloat {
|
|
309
|
-
sizeClass == .regular ? min(available, regularMaxWidth) : available
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// MARK: Dragging
|
|
313
|
-
|
|
314
|
-
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
315
|
-
guard let pan = gestureRecognizer as? UIPanGestureRecognizer,
|
|
316
|
-
!isBeingPresented, !isBeingDismissed, !isOffscreen else { return false }
|
|
317
|
-
let velocity = pan.velocity(in: view)
|
|
318
|
-
return abs(velocity.y) > abs(velocity.x)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/// Scroll views inside the content keep scrolling; `handlePan` decides
|
|
322
|
-
/// frame by frame whether the sheet or the scroll view moves.
|
|
323
|
-
func gestureRecognizer(
|
|
324
|
-
_ gestureRecognizer: UIGestureRecognizer,
|
|
325
|
-
shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer
|
|
326
|
-
) -> Bool {
|
|
327
|
-
guard let scrollView = other.view as? UIScrollView else { return false }
|
|
328
|
-
return other === scrollView.panGestureRecognizer && scrollView.isDescendant(of: sheetView)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
@objc private func handlePan(_ pan: UIPanGestureRecognizer) {
|
|
332
|
-
switch pan.state {
|
|
333
|
-
case .began:
|
|
334
|
-
// Grab the sheet where it is, even mid-animation.
|
|
335
|
-
let top = sheetView.layer.presentation()?.frame.minY ?? sheetView.frame.minY
|
|
336
|
-
sheetView.layer.removeAllAnimations()
|
|
337
|
-
backdrop.layer.removeAllAnimations()
|
|
338
|
-
let translation = pan.translation(in: sheetView)
|
|
339
|
-
let location = pan.location(in: sheetView)
|
|
340
|
-
let start = CGPoint(x: location.x - translation.x, y: location.y - translation.y)
|
|
341
|
-
var state = DragState(rawTop: top)
|
|
342
|
-
state.lastTranslation = pan.translation(in: view).y
|
|
343
|
-
state.scrollView = verticalScrollView(at: start)
|
|
344
|
-
if let scrollView = state.scrollView {
|
|
345
|
-
lockedOffsetY = scrollView.contentOffset.y
|
|
346
|
-
}
|
|
347
|
-
drag = state
|
|
348
|
-
dragTop = top
|
|
349
|
-
layoutSheet()
|
|
350
|
-
|
|
351
|
-
case .changed:
|
|
352
|
-
guard var state = drag, let minTop = detentTops.min() else { return }
|
|
353
|
-
let translation = pan.translation(in: view).y
|
|
354
|
-
let delta = translation - state.lastTranslation
|
|
355
|
-
state.lastTranslation = translation
|
|
356
|
-
|
|
357
|
-
if let scrollView = state.scrollView {
|
|
358
|
-
// Up: expand the sheet before scrolling. Down: scroll back to the top
|
|
359
|
-
// before collapsing the sheet.
|
|
360
|
-
let drive = delta < 0 ? state.rawTop > minTop + 0.5 : scrollView.isScrolledToTop
|
|
361
|
-
if drive {
|
|
362
|
-
state.rawTop = max(state.rawTop + delta, minTop)
|
|
363
|
-
lockScroll(scrollView, at: delta < 0 ? lockedOffsetY : scrollView.topOffsetY)
|
|
364
|
-
} else {
|
|
365
|
-
unlockScroll()
|
|
366
|
-
lockedOffsetY = scrollView.contentOffset.y
|
|
367
|
-
}
|
|
368
|
-
state.sheetDrove = drive
|
|
369
|
-
} else {
|
|
370
|
-
state.rawTop += delta
|
|
371
|
-
state.sheetDrove = true
|
|
372
|
-
}
|
|
373
|
-
drag = state
|
|
374
|
-
dragTop = constrained(state.rawTop)
|
|
375
|
-
layoutSheet()
|
|
376
|
-
|
|
377
|
-
case .ended, .cancelled, .failed:
|
|
378
|
-
guard let state = drag else { return }
|
|
379
|
-
drag = nil
|
|
380
|
-
let velocity = state.sheetDrove ? pan.velocity(in: view).y : 0
|
|
381
|
-
endDrag(at: dragTop ?? currentTop, velocity: velocity, holdingScroll: state.sheetDrove)
|
|
382
|
-
|
|
383
|
-
default:
|
|
384
|
-
break
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private func endDrag(at top: CGFloat, velocity: CGFloat, holdingScroll: Bool) {
|
|
389
|
-
if !holdingScroll {
|
|
390
|
-
unlockScroll()
|
|
391
|
-
}
|
|
392
|
-
let tops = detentTops
|
|
393
|
-
guard let lowest = tops.max() else { return }
|
|
394
|
-
// Where a fling of this velocity would come to rest.
|
|
395
|
-
let projected = top + velocity * 0.2
|
|
396
|
-
|
|
397
|
-
if config.dismissOnSwipe, velocity >= 0, projected > lowest + (view.bounds.height - lowest) / 2 {
|
|
398
|
-
releaseVelocity = velocity
|
|
399
|
-
presentationDelegate?.modalPresentationDidRequestSwipeDismiss()
|
|
400
|
-
if isBeingDismissed {
|
|
401
|
-
return
|
|
402
|
-
}
|
|
403
|
-
releaseVelocity = 0
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
let index = tops.indices.min { abs(tops[$0] - projected) < abs(tops[$1] - projected) } ?? selectedIndex
|
|
407
|
-
if index != selectedIndex {
|
|
408
|
-
selectedIndex = index
|
|
409
|
-
presentationDelegate?.modalPresentationDidChangeDetent(index)
|
|
410
|
-
}
|
|
411
|
-
dragTop = nil
|
|
412
|
-
animate(distance: restingTop(for: index) - top, velocity: velocity, animations: { self.layoutSheet() }) { [weak self] in
|
|
413
|
-
guard let self, self.drag == nil else { return }
|
|
414
|
-
self.unlockScroll()
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/// Rubber-bands above the tallest detent, and below the lowest one when
|
|
419
|
-
/// swiping cannot dismiss.
|
|
420
|
-
private func constrained(_ top: CGFloat) -> CGFloat {
|
|
421
|
-
let tops = detentTops
|
|
422
|
-
guard let minTop = tops.min(), let maxTop = tops.max() else { return top }
|
|
423
|
-
let dimension = max(view.bounds.height, 1)
|
|
424
|
-
if top < minTop {
|
|
425
|
-
return minTop - Self.rubberBand(minTop - top, dimension: dimension)
|
|
426
|
-
}
|
|
427
|
-
if !config.dismissOnSwipe, top > maxTop {
|
|
428
|
-
return maxTop + Self.rubberBand(top - maxTop, dimension: dimension)
|
|
429
|
-
}
|
|
430
|
-
return top
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
private static func rubberBand(_ offset: CGFloat, dimension: CGFloat) -> CGFloat {
|
|
434
|
-
(1 - 1 / (offset * 0.55 / dimension + 1)) * dimension
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/// The innermost vertically scrollable view under `point` (in `sheetView`).
|
|
438
|
-
private func verticalScrollView(at point: CGPoint) -> UIScrollView? {
|
|
439
|
-
var candidate = sheetView.hitTest(point, with: nil)
|
|
440
|
-
while let current = candidate, current !== sheetView {
|
|
441
|
-
if let scrollView = current as? UIScrollView, scrollView.isScrollEnabled, scrollView.canScrollVertically {
|
|
442
|
-
return scrollView
|
|
443
|
-
}
|
|
444
|
-
candidate = current.superview
|
|
445
|
-
}
|
|
446
|
-
return nil
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/// Pins the scroll view's offset while the sheet moves, so content does not
|
|
450
|
-
/// scroll (or bounce) at the same time.
|
|
451
|
-
private func lockScroll(_ scrollView: UIScrollView, at offsetY: CGFloat) {
|
|
452
|
-
if lockedScrollView !== scrollView {
|
|
453
|
-
unlockScroll()
|
|
454
|
-
lockedScrollView = scrollView
|
|
455
|
-
}
|
|
456
|
-
lockedOffsetY = offsetY
|
|
457
|
-
if scrollView.contentOffset.y != offsetY {
|
|
458
|
-
scrollView.contentOffset.y = offsetY
|
|
459
|
-
}
|
|
460
|
-
guard scrollLock == nil else { return }
|
|
461
|
-
scrollLock = scrollView.observe(\.contentOffset) { [weak self] scrollView, _ in
|
|
462
|
-
guard let self, scrollView.contentOffset.y != self.lockedOffsetY else { return }
|
|
463
|
-
scrollView.contentOffset.y = self.lockedOffsetY
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
private func unlockScroll() {
|
|
468
|
-
guard let scrollView = lockedScrollView else { return }
|
|
469
|
-
scrollLock?.invalidate()
|
|
470
|
-
scrollLock = nil
|
|
471
|
-
lockedScrollView = nil
|
|
472
|
-
// Stops any deceleration left over from a fling that moved the sheet.
|
|
473
|
-
scrollView.setContentOffset(scrollView.contentOffset, animated: false)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// MARK: Keyboard
|
|
477
|
-
|
|
478
|
-
@objc private func keyboardWillChangeFrame(_ notification: Notification) {
|
|
479
|
-
guard isViewLoaded, let window = view.window,
|
|
480
|
-
let frame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
|
|
481
|
-
let keyboard = window.convert(frame, from: window.screen.coordinateSpace)
|
|
482
|
-
updateKeyboardHeight(max(0, window.bounds.maxY - keyboard.minY), notification: notification)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
@objc private func keyboardWillHide(_ notification: Notification) {
|
|
486
|
-
updateKeyboardHeight(0, notification: notification)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
private func updateKeyboardHeight(_ height: CGFloat, notification: Notification) {
|
|
490
|
-
guard isViewLoaded, height != keyboardHeight else { return }
|
|
491
|
-
keyboardHeight = height
|
|
492
|
-
guard drag == nil else { return }
|
|
493
|
-
notification.animateAlongsideKeyboard {
|
|
494
|
-
self.view.setNeedsLayout()
|
|
495
|
-
self.view.layoutIfNeeded()
|
|
496
|
-
}
|
|
80
|
+
sheet.animateTransition(toOffscreen: offscreen, animated: animated, duration: duration, completion: completion)
|
|
497
81
|
}
|
|
498
82
|
}
|
|
499
83
|
|
|
@@ -531,18 +115,3 @@ private final class SheetTransition: NSObject, UIViewControllerAnimatedTransitio
|
|
|
531
115
|
}
|
|
532
116
|
}
|
|
533
117
|
}
|
|
534
|
-
|
|
535
|
-
private extension UIScrollView {
|
|
536
|
-
var topOffsetY: CGFloat {
|
|
537
|
-
-adjustedContentInset.top
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
var isScrolledToTop: Bool {
|
|
541
|
-
contentOffset.y <= topOffsetY + 0.5
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
var canScrollVertically: Bool {
|
|
545
|
-
alwaysBounceVertical
|
|
546
|
-
|| contentSize.height + adjustedContentInset.top + adjustedContentInset.bottom > bounds.height + 0.5
|
|
547
|
-
}
|
|
548
|
-
}
|
package/lib/module/NitroModal.js
CHANGED
|
@@ -3,9 +3,21 @@
|
|
|
3
3
|
import { useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { processColor, StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
5
5
|
import { callback, NitroModalView } from './NitroModalView';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where a `bottomSheet` lives.
|
|
9
|
+
* - `modal`: presented over the whole app, with a backdrop.
|
|
10
|
+
* - `inline`: inside the component's own frame (`style`), as part of your
|
|
11
|
+
* screen. No backdrop, the views behind it stay interactive, and the user
|
|
12
|
+
* can't dismiss it.
|
|
13
|
+
*/
|
|
6
14
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
15
|
const DEFAULT_DETENTS = ['fitContent'];
|
|
8
16
|
const BLACK = 0xff000000;
|
|
17
|
+
function parseDetent(value) {
|
|
18
|
+
const points = Number(value);
|
|
19
|
+
return value !== '' && Number.isFinite(points) ? points : value;
|
|
20
|
+
}
|
|
9
21
|
function toNativeColor(color) {
|
|
10
22
|
if (color == null) return undefined;
|
|
11
23
|
const processed = processColor(color);
|
|
@@ -15,6 +27,8 @@ function toNativeColor(color) {
|
|
|
15
27
|
export function NitroModal({
|
|
16
28
|
isOpen,
|
|
17
29
|
mode = 'bottomSheet',
|
|
30
|
+
presentation = 'modal',
|
|
31
|
+
style,
|
|
18
32
|
detents = DEFAULT_DETENTS,
|
|
19
33
|
initialDetentIndex = 0,
|
|
20
34
|
backdropColor = 'black',
|
|
@@ -34,11 +48,13 @@ export function NitroModal({
|
|
|
34
48
|
onDetentChange,
|
|
35
49
|
onBackdropPress,
|
|
36
50
|
onBackButtonPress,
|
|
51
|
+
onPullToRefresh,
|
|
37
52
|
children,
|
|
38
53
|
testID,
|
|
39
54
|
ref
|
|
40
55
|
}) {
|
|
41
56
|
const isControlled = isOpen !== undefined;
|
|
57
|
+
const isInline = presentation === 'inline' && mode === 'bottomSheet';
|
|
42
58
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
43
59
|
const open = isControlled ? isOpen : internalOpen;
|
|
44
60
|
|
|
@@ -52,6 +68,10 @@ export function NitroModal({
|
|
|
52
68
|
}
|
|
53
69
|
const renderContent = open || sessionActive;
|
|
54
70
|
const [area, setArea] = useState(null);
|
|
71
|
+
// Inline (iOS/Android): where the sheet's top rests in the host. The content
|
|
72
|
+
// is laid out there so `measure()`, which presses rely on, matches the
|
|
73
|
+
// screen; native cancels the offset out on screen.
|
|
74
|
+
const [restingTop, setRestingTop] = useState(0);
|
|
55
75
|
const hybridRef = useRef(null);
|
|
56
76
|
|
|
57
77
|
// Native callbacks are created once (stable props, no native updates on
|
|
@@ -62,7 +82,8 @@ export function NitroModal({
|
|
|
62
82
|
onDismiss,
|
|
63
83
|
onDetentChange,
|
|
64
84
|
onBackdropPress,
|
|
65
|
-
onBackButtonPress
|
|
85
|
+
onBackButtonPress,
|
|
86
|
+
onPullToRefresh
|
|
66
87
|
});
|
|
67
88
|
useLayoutEffect(() => {
|
|
68
89
|
latest.current = {
|
|
@@ -71,7 +92,8 @@ export function NitroModal({
|
|
|
71
92
|
onDismiss,
|
|
72
93
|
onDetentChange,
|
|
73
94
|
onBackdropPress,
|
|
74
|
-
onBackButtonPress
|
|
95
|
+
onBackButtonPress,
|
|
96
|
+
onPullToRefresh
|
|
75
97
|
};
|
|
76
98
|
});
|
|
77
99
|
const nativeCallbacks = useMemo(() => ({
|
|
@@ -96,6 +118,12 @@ export function NitroModal({
|
|
|
96
118
|
onBackButtonPress: callback(() => {
|
|
97
119
|
latest.current.onBackButtonPress?.();
|
|
98
120
|
}),
|
|
121
|
+
onPullToRefresh: callback(() => {
|
|
122
|
+
latest.current.onPullToRefresh?.();
|
|
123
|
+
}),
|
|
124
|
+
onRestingTopChange: callback(top => {
|
|
125
|
+
setRestingTop(top);
|
|
126
|
+
}),
|
|
99
127
|
onContentAreaChange: callback(next => {
|
|
100
128
|
setArea(current => current?.width === next.width && current.height === next.height ? current : next);
|
|
101
129
|
})
|
|
@@ -122,7 +150,7 @@ export function NitroModal({
|
|
|
122
150
|
|
|
123
151
|
// Arrays are diffed by identity, so keep one instance per distinct value.
|
|
124
152
|
const detentsKey = detents.join(',');
|
|
125
|
-
const nativeDetents = useMemo(() => [...new Set(detentsKey.split(','))], [detentsKey]);
|
|
153
|
+
const nativeDetents = useMemo(() => [...new Set(detentsKey.split(','))].map(parseDetent), [detentsKey]);
|
|
126
154
|
|
|
127
155
|
// Native reports the exact area; this only covers the first frame.
|
|
128
156
|
const windowSize = useWindowDimensions();
|
|
@@ -141,9 +169,11 @@ export function NitroModal({
|
|
|
141
169
|
height: contentArea.height
|
|
142
170
|
};
|
|
143
171
|
return /*#__PURE__*/_jsx(NitroModalView, {
|
|
144
|
-
style: styles.host,
|
|
172
|
+
style: isInline ? [StyleSheet.absoluteFill, style] : styles.host,
|
|
173
|
+
pointerEvents: isInline ? 'box-none' : undefined,
|
|
145
174
|
isOpen: open,
|
|
146
175
|
mode: mode,
|
|
176
|
+
isInline: isInline,
|
|
147
177
|
detents: nativeDetents,
|
|
148
178
|
initialDetentIndex: initialDetentIndex,
|
|
149
179
|
backdropColor: toNativeColor(backdropColor) ?? BLACK,
|
|
@@ -157,11 +187,14 @@ export function NitroModal({
|
|
|
157
187
|
contentBackgroundColor: toNativeColor(backgroundColor),
|
|
158
188
|
keyboardBehavior: keyboardBehavior,
|
|
159
189
|
popupAnimation: popupAnimation,
|
|
190
|
+
pullToRefreshEnabled: onPullToRefresh != null,
|
|
160
191
|
...nativeCallbacks,
|
|
161
192
|
children: renderContent ? /*#__PURE__*/_jsx(View, {
|
|
162
193
|
collapsable: false,
|
|
163
194
|
testID: testID,
|
|
164
|
-
style: [styles.container, containerStyle, contentContainerStyle
|
|
195
|
+
style: [styles.container, containerStyle, contentContainerStyle, isInline && restingTop > 0 ? {
|
|
196
|
+
top: restingTop
|
|
197
|
+
} : null],
|
|
165
198
|
children: children
|
|
166
199
|
}) : null
|
|
167
200
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useImperativeHandle","useLayoutEffect","useMemo","useRef","useState","processColor","StyleSheet","useWindowDimensions","View","callback","NitroModalView","jsx","_jsx","DEFAULT_DETENTS","BLACK","toNativeColor","color","undefined","processed","NitroModal","isOpen","mode","detents","initialDetentIndex","backdropColor","backdropOpacity","backdropBlur","dismissOnBackdropPress","dismissOnSwipe","dismissOnBackButton","showGrabber","cornerRadius","backgroundColor","keyboardBehavior","popupAnimation","contentContainerStyle","onPresent","onDismiss","onDetentChange","onBackdropPress","onBackButtonPress","children","testID","ref","isControlled","internalOpen","setInternalOpen","open","sessionActive","setSessionActive","prevOpen","setPrevOpen","renderContent","area","setArea","hybridRef","latest","current","nativeCallbacks","instance","reason","index","onContentAreaChange","next","width","height","present","console","warn","dismiss","snapToDetent","detentsKey","join","nativeDetents","Set","split","windowSize","contentArea","containerStyle","maxWidth","maxHeight","includes","
|
|
1
|
+
{"version":3,"names":["useImperativeHandle","useLayoutEffect","useMemo","useRef","useState","processColor","StyleSheet","useWindowDimensions","View","callback","NitroModalView","jsx","_jsx","DEFAULT_DETENTS","BLACK","parseDetent","value","points","Number","isFinite","toNativeColor","color","undefined","processed","NitroModal","isOpen","mode","presentation","style","detents","initialDetentIndex","backdropColor","backdropOpacity","backdropBlur","dismissOnBackdropPress","dismissOnSwipe","dismissOnBackButton","showGrabber","cornerRadius","backgroundColor","keyboardBehavior","popupAnimation","contentContainerStyle","onPresent","onDismiss","onDetentChange","onBackdropPress","onBackButtonPress","onPullToRefresh","children","testID","ref","isControlled","isInline","internalOpen","setInternalOpen","open","sessionActive","setSessionActive","prevOpen","setPrevOpen","renderContent","area","setArea","restingTop","setRestingTop","hybridRef","latest","current","nativeCallbacks","instance","reason","index","onRestingTopChange","top","onContentAreaChange","next","width","height","present","console","warn","dismiss","snapToDetent","detentsKey","join","nativeDetents","Set","split","map","windowSize","contentArea","containerStyle","maxWidth","maxHeight","includes","absoluteFill","styles","host","pointerEvents","backdropBlurRadius","grabberVisible","contentBackgroundColor","pullToRefreshEnabled","collapsable","container","create","position","left"],"sourceRoot":"../../src","sources":["NitroModal.tsx"],"mappings":"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","ignoreList":[]}
|