@onekeyfe/react-native-native-overlay 3.0.162-alpha.268
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/LICENSE +21 -0
- package/NativeOverlay.podspec +30 -0
- package/README.md +35 -0
- package/android/build.gradle +82 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAnimation.kt +176 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayEntryRootView.kt +415 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayEvents.kt +38 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayHost.kt +174 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPackage.kt +16 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostView.kt +122 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostViewManager.kt +40 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayView.kt +465 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayViewManager.kt +156 -0
- package/android/src/main/jni/CMakeLists.txt +44 -0
- package/android/src/main/jni/RNCNativeOverlay.h +19 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayComponentDescriptor.h +12 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayShadowNode.cpp +12 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayShadowNode.h +41 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayState.h +40 -0
- package/docs/SPEC.md +693 -0
- package/ios/NativeOverlayAnimation.swift +160 -0
- package/ios/NativeOverlayContainerView.swift +688 -0
- package/ios/NativeOverlayKeyboard.swift +88 -0
- package/ios/NativeOverlayPageHostView.swift +98 -0
- package/ios/NativeOverlaySheetController.swift +391 -0
- package/ios/NativeOverlayWindowManager.swift +194 -0
- package/ios/RCTNativeOverlayComponentView.h +11 -0
- package/ios/RCTNativeOverlayComponentView.mm +193 -0
- package/ios/RCTNativeOverlayPageHostComponentView.h +11 -0
- package/ios/RCTNativeOverlayPageHostComponentView.mm +64 -0
- package/lib/module/NativeOverlayNativeComponent.ts +61 -0
- package/lib/module/NativeOverlayPageHostNativeComponent.ts +13 -0
- package/lib/module/OverlayErrors.js +6 -0
- package/lib/module/OverlayErrors.js.map +1 -0
- package/lib/module/OverlayLevelContext.js +27 -0
- package/lib/module/OverlayLevelContext.js.map +1 -0
- package/lib/module/OverlayLevels.js +33 -0
- package/lib/module/OverlayLevels.js.map +1 -0
- package/lib/module/OverlayPageHost.js +24 -0
- package/lib/module/OverlayPageHost.js.map +1 -0
- package/lib/module/OverlayPageHost.web.js +49 -0
- package/lib/module/OverlayPageHost.web.js.map +1 -0
- package/lib/module/OverlayPageScope.js +41 -0
- package/lib/module/OverlayPageScope.js.map +1 -0
- package/lib/module/OverlayStore.js +305 -0
- package/lib/module/OverlayStore.js.map +1 -0
- package/lib/module/OverlayView.js +141 -0
- package/lib/module/OverlayView.js.map +1 -0
- package/lib/module/OverlayView.web.js +290 -0
- package/lib/module/OverlayView.web.js.map +1 -0
- package/lib/module/OverlayViewTypes.js +4 -0
- package/lib/module/OverlayViewTypes.js.map +1 -0
- package/lib/module/animation/motionPresets.js +57 -0
- package/lib/module/animation/motionPresets.js.map +1 -0
- package/lib/module/animation/platformMotionPresets.js +4 -0
- package/lib/module/animation/platformMotionPresets.js.map +1 -0
- package/lib/module/animation/platformMotionPresets.web.js +4 -0
- package/lib/module/animation/platformMotionPresets.web.js.map +1 -0
- package/lib/module/animation/resolveAnimation.js +80 -0
- package/lib/module/animation/resolveAnimation.js.map +1 -0
- package/lib/module/animation/spring.js +69 -0
- package/lib/module/animation/spring.js.map +1 -0
- package/lib/module/animation/types.js +2 -0
- package/lib/module/animation/types.js.map +1 -0
- package/lib/module/index.js +16 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/useOverlayController.js +127 -0
- package/lib/module/useOverlayController.js.map +1 -0
- package/lib/module/useSuspendedPageOwners.js +16 -0
- package/lib/module/useSuspendedPageOwners.js.map +1 -0
- package/lib/module/web/animateTransition.js +115 -0
- package/lib/module/web/animateTransition.js.map +1 -0
- package/lib/module/web/overlayLayers.js +147 -0
- package/lib/module/web/overlayLayers.js.map +1 -0
- package/lib/module/web/useKeyboardShift.js +110 -0
- package/lib/module/web/useKeyboardShift.js.map +1 -0
- package/lib/module/web/useSheetDrag.js +126 -0
- package/lib/module/web/useSheetDrag.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeOverlayNativeComponent.d.ts +41 -0
- package/lib/typescript/src/NativeOverlayNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/NativeOverlayPageHostNativeComponent.d.ts +9 -0
- package/lib/typescript/src/NativeOverlayPageHostNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/OverlayErrors.d.ts +4 -0
- package/lib/typescript/src/OverlayErrors.d.ts.map +1 -0
- package/lib/typescript/src/OverlayLevelContext.d.ts +12 -0
- package/lib/typescript/src/OverlayLevelContext.d.ts.map +1 -0
- package/lib/typescript/src/OverlayLevels.d.ts +13 -0
- package/lib/typescript/src/OverlayLevels.d.ts.map +1 -0
- package/lib/typescript/src/OverlayPageHost.d.ts +9 -0
- package/lib/typescript/src/OverlayPageHost.d.ts.map +1 -0
- package/lib/typescript/src/OverlayPageHost.web.d.ts +11 -0
- package/lib/typescript/src/OverlayPageHost.web.d.ts.map +1 -0
- package/lib/typescript/src/OverlayPageScope.d.ts +17 -0
- package/lib/typescript/src/OverlayPageScope.d.ts.map +1 -0
- package/lib/typescript/src/OverlayStore.d.ts +72 -0
- package/lib/typescript/src/OverlayStore.d.ts.map +1 -0
- package/lib/typescript/src/OverlayView.d.ts +3 -0
- package/lib/typescript/src/OverlayView.d.ts.map +1 -0
- package/lib/typescript/src/OverlayView.web.d.ts +3 -0
- package/lib/typescript/src/OverlayView.web.d.ts.map +1 -0
- package/lib/typescript/src/OverlayViewTypes.d.ts +68 -0
- package/lib/typescript/src/OverlayViewTypes.d.ts.map +1 -0
- package/lib/typescript/src/animation/motionPresets.d.ts +13 -0
- package/lib/typescript/src/animation/motionPresets.d.ts.map +1 -0
- package/lib/typescript/src/animation/platformMotionPresets.d.ts +2 -0
- package/lib/typescript/src/animation/platformMotionPresets.d.ts.map +1 -0
- package/lib/typescript/src/animation/platformMotionPresets.web.d.ts +2 -0
- package/lib/typescript/src/animation/platformMotionPresets.web.d.ts.map +1 -0
- package/lib/typescript/src/animation/resolveAnimation.d.ts +23 -0
- package/lib/typescript/src/animation/resolveAnimation.d.ts.map +1 -0
- package/lib/typescript/src/animation/spring.d.ts +24 -0
- package/lib/typescript/src/animation/spring.d.ts.map +1 -0
- package/lib/typescript/src/animation/types.d.ts +56 -0
- package/lib/typescript/src/animation/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +18 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +59 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/useOverlayController.d.ts +29 -0
- package/lib/typescript/src/useOverlayController.d.ts.map +1 -0
- package/lib/typescript/src/useSuspendedPageOwners.d.ts +7 -0
- package/lib/typescript/src/useSuspendedPageOwners.d.ts.map +1 -0
- package/lib/typescript/src/web/animateTransition.d.ts +18 -0
- package/lib/typescript/src/web/animateTransition.d.ts.map +1 -0
- package/lib/typescript/src/web/overlayLayers.d.ts +10 -0
- package/lib/typescript/src/web/overlayLayers.d.ts.map +1 -0
- package/lib/typescript/src/web/useKeyboardShift.d.ts +29 -0
- package/lib/typescript/src/web/useKeyboardShift.d.ts.map +1 -0
- package/lib/typescript/src/web/useSheetDrag.d.ts +13 -0
- package/lib/typescript/src/web/useSheetDrag.d.ts.map +1 -0
- package/package.json +188 -0
- package/react-native.config.js +16 -0
- package/src/NativeOverlayNativeComponent.ts +61 -0
- package/src/NativeOverlayPageHostNativeComponent.ts +13 -0
- package/src/OverlayErrors.ts +3 -0
- package/src/OverlayLevelContext.tsx +32 -0
- package/src/OverlayLevels.ts +40 -0
- package/src/OverlayPageHost.tsx +21 -0
- package/src/OverlayPageHost.web.tsx +53 -0
- package/src/OverlayPageScope.tsx +42 -0
- package/src/OverlayStore.ts +401 -0
- package/src/OverlayView.tsx +165 -0
- package/src/OverlayView.web.tsx +356 -0
- package/src/OverlayViewTypes.ts +81 -0
- package/src/animation/motionPresets.ts +52 -0
- package/src/animation/platformMotionPresets.ts +1 -0
- package/src/animation/platformMotionPresets.web.ts +1 -0
- package/src/animation/resolveAnimation.ts +103 -0
- package/src/animation/spring.ts +80 -0
- package/src/animation/types.ts +71 -0
- package/src/index.ts +55 -0
- package/src/types.ts +79 -0
- package/src/useOverlayController.ts +199 -0
- package/src/useSuspendedPageOwners.ts +26 -0
- package/src/web/animateTransition.ts +137 -0
- package/src/web/overlayLayers.ts +182 -0
- package/src/web/useKeyboardShift.ts +130 -0
- package/src/web/useSheetDrag.ts +143 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import React
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// One overlay inside a level window: backdrop plus the reparented content.
|
|
5
|
+
final class NativeOverlayEntryView: UIView {
|
|
6
|
+
let backdropView = UIView()
|
|
7
|
+
/// Carries the keyboard avoidance lift, so it composes with the enter /
|
|
8
|
+
/// exit transforms animated on `contentView`.
|
|
9
|
+
let keyboardLayer = UIView()
|
|
10
|
+
let contentView = UIView()
|
|
11
|
+
var stackOrder = 0
|
|
12
|
+
var levelOrder = 0
|
|
13
|
+
/// Page scope: the owning page, used by the page host to hide the entry.
|
|
14
|
+
var ownerKey = ""
|
|
15
|
+
var blocking = true
|
|
16
|
+
var dismissOnBackPress = true
|
|
17
|
+
var onEscape: (() -> Void)?
|
|
18
|
+
var onBackdropTap: (() -> Void)?
|
|
19
|
+
/// Page sheet: the drag closed it past the threshold.
|
|
20
|
+
var onPanDismiss: (() -> Void)?
|
|
21
|
+
|
|
22
|
+
/// Page scope cannot use UIKit sheets (they always cover the window), so a
|
|
23
|
+
/// page sheet is this entry's content view pinned to the bottom.
|
|
24
|
+
private(set) var pageSheetHeight: CGFloat?
|
|
25
|
+
private var pageSheetPan: UIPanGestureRecognizer?
|
|
26
|
+
|
|
27
|
+
override init(frame: CGRect) {
|
|
28
|
+
super.init(frame: frame)
|
|
29
|
+
backgroundColor = .clear
|
|
30
|
+
backdropView.frame = bounds
|
|
31
|
+
backdropView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
32
|
+
backdropView.alpha = 0
|
|
33
|
+
keyboardLayer.frame = bounds
|
|
34
|
+
keyboardLayer.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
35
|
+
keyboardLayer.backgroundColor = .clear
|
|
36
|
+
contentView.frame = bounds
|
|
37
|
+
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
38
|
+
contentView.backgroundColor = .clear
|
|
39
|
+
addSubview(backdropView)
|
|
40
|
+
addSubview(keyboardLayer)
|
|
41
|
+
keyboardLayer.addSubview(contentView)
|
|
42
|
+
let tap = UITapGestureRecognizer(target: self, action: #selector(handleBackdropTap))
|
|
43
|
+
tap.cancelsTouchesInView = false
|
|
44
|
+
backdropView.addGestureRecognizer(tap)
|
|
45
|
+
NotificationCenter.default.addObserver(
|
|
46
|
+
self,
|
|
47
|
+
selector: #selector(keyboardDidChange),
|
|
48
|
+
name: NativeOverlayKeyboard.didChange,
|
|
49
|
+
object: nil
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@available(*, unavailable)
|
|
54
|
+
required init?(coder: NSCoder) {
|
|
55
|
+
fatalError("init(coder:) has not been implemented")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Touches that miss the React content either hit the backdrop (blocking)
|
|
59
|
+
/// or fall through to the windows below (toast, debug).
|
|
60
|
+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
61
|
+
guard let hit = super.hitTest(point, with: event) else { return nil }
|
|
62
|
+
// The React root is `pointerEvents="box-none"`, so Fabric returns nil for
|
|
63
|
+
// it and the plain content view reports itself when nothing was hit.
|
|
64
|
+
if hit === self || hit === keyboardLayer || hit === contentView || hit === backdropView {
|
|
65
|
+
return blocking ? backdropView : nil
|
|
66
|
+
}
|
|
67
|
+
return hit
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override func accessibilityPerformEscape() -> Bool {
|
|
71
|
+
guard blocking else { return false }
|
|
72
|
+
if dismissOnBackPress {
|
|
73
|
+
onEscape?()
|
|
74
|
+
}
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@objc private func handleBackdropTap() {
|
|
79
|
+
onBackdropTap?()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func configurePageSheet(
|
|
83
|
+
height: CGFloat,
|
|
84
|
+
backgroundColor: UIColor,
|
|
85
|
+
cornerRadius: CGFloat,
|
|
86
|
+
draggable: Bool
|
|
87
|
+
) {
|
|
88
|
+
pageSheetHeight = height
|
|
89
|
+
contentView.backgroundColor = backgroundColor
|
|
90
|
+
contentView.layer.cornerRadius = cornerRadius
|
|
91
|
+
contentView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
|
92
|
+
contentView.layer.cornerCurve = .continuous
|
|
93
|
+
contentView.clipsToBounds = true
|
|
94
|
+
contentView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
|
|
95
|
+
layoutPageSheet()
|
|
96
|
+
if draggable, pageSheetPan == nil {
|
|
97
|
+
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePageSheetPan(_:)))
|
|
98
|
+
contentView.addGestureRecognizer(pan)
|
|
99
|
+
pageSheetPan = pan
|
|
100
|
+
}
|
|
101
|
+
pageSheetPan?.isEnabled = draggable
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Layout changed (rotation, page sheet height); the owner re-reports
|
|
105
|
+
/// where its content is.
|
|
106
|
+
var onLayout: (() -> Void)?
|
|
107
|
+
|
|
108
|
+
override func layoutSubviews() {
|
|
109
|
+
super.layoutSubviews()
|
|
110
|
+
layoutPageSheet()
|
|
111
|
+
updateKeyboardShift(animated: false)
|
|
112
|
+
onLayout?()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override func didMoveToWindow() {
|
|
116
|
+
super.didMoveToWindow()
|
|
117
|
+
updateKeyboardShift(animated: false)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// MARK: - Keyboard
|
|
121
|
+
|
|
122
|
+
private var keyboardShift: CGFloat = 0
|
|
123
|
+
|
|
124
|
+
@objc private func keyboardDidChange() {
|
|
125
|
+
updateKeyboardShift(animated: true)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Lifts the content above the software keyboard (see
|
|
129
|
+
/// `NativeOverlayKeyboardShift`). A full-window content never moves.
|
|
130
|
+
func updateKeyboardShift(animated: Bool) {
|
|
131
|
+
let keyboard = NativeOverlayKeyboard.shared
|
|
132
|
+
let isSheet = pageSheetHeight != nil
|
|
133
|
+
// The resting frame, independent of the running enter / exit transform.
|
|
134
|
+
let contentFrame: CGRect
|
|
135
|
+
if isSheet {
|
|
136
|
+
let height = min(pageSheetHeight ?? 0, bounds.height)
|
|
137
|
+
contentFrame = CGRect(x: 0, y: bounds.height - height, width: bounds.width, height: height)
|
|
138
|
+
} else {
|
|
139
|
+
contentFrame = contentExtent()
|
|
140
|
+
}
|
|
141
|
+
let shift = NativeOverlayKeyboardShift.compute(
|
|
142
|
+
keyboardTop: window == nil ? nil : keyboard.top(in: self),
|
|
143
|
+
contentFrame: contentFrame,
|
|
144
|
+
contentBottom: contentFrame.maxY,
|
|
145
|
+
isSheet: isSheet,
|
|
146
|
+
safeTop: safeAreaInsets.top
|
|
147
|
+
)
|
|
148
|
+
guard abs(shift - keyboardShift) >= 0.5 else { return }
|
|
149
|
+
keyboardShift = shift
|
|
150
|
+
let apply = { self.keyboardLayer.transform = CGAffineTransform(translationX: 0, y: -shift) }
|
|
151
|
+
guard animated else {
|
|
152
|
+
apply()
|
|
153
|
+
onLayout?()
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
let animator = UIViewPropertyAnimator(duration: keyboard.duration, curve: keyboard.curve, animations: apply)
|
|
157
|
+
animator.addCompletion { [weak self] _ in self?.onLayout?() }
|
|
158
|
+
animator.startAnimation()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func layoutPageSheet() {
|
|
162
|
+
guard let height = pageSheetHeight else { return }
|
|
163
|
+
let sheetHeight = min(height, bounds.height)
|
|
164
|
+
// Keep the transform while animating; only the resting frame changes.
|
|
165
|
+
let transform = contentView.transform
|
|
166
|
+
contentView.transform = .identity
|
|
167
|
+
contentView.frame = CGRect(x: 0, y: bounds.height - sheetHeight, width: bounds.width, height: sheetHeight)
|
|
168
|
+
contentView.transform = transform
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc private func handlePageSheetPan(_ pan: UIPanGestureRecognizer) {
|
|
172
|
+
let travel = max(pan.translation(in: self).y, 0)
|
|
173
|
+
let height = max(contentView.bounds.height, 1)
|
|
174
|
+
switch pan.state {
|
|
175
|
+
case .changed:
|
|
176
|
+
contentView.transform = CGAffineTransform(translationX: 0, y: travel)
|
|
177
|
+
backdropView.alpha = 1 - min(travel / height, 1)
|
|
178
|
+
case .ended, .cancelled:
|
|
179
|
+
let velocity = pan.velocity(in: self).y
|
|
180
|
+
if pan.state == .ended, travel > height * 0.4 || velocity > 1000 {
|
|
181
|
+
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) {
|
|
182
|
+
self.contentView.transform = CGAffineTransform(translationX: 0, y: height)
|
|
183
|
+
self.backdropView.alpha = 0
|
|
184
|
+
} completion: { _ in
|
|
185
|
+
self.onPanDismiss?()
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0) {
|
|
189
|
+
self.contentView.transform = .identity
|
|
190
|
+
self.backdropView.alpha = 1
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
default:
|
|
194
|
+
break
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Union of the rendered React children; the root React view fills the window.
|
|
199
|
+
func contentExtent() -> CGRect {
|
|
200
|
+
let roots = contentView.subviews
|
|
201
|
+
let rects = roots.flatMap { root in
|
|
202
|
+
root.subviews.map { root.convert($0.frame, to: contentView) }
|
|
203
|
+
}
|
|
204
|
+
guard var union = rects.first else { return bounds }
|
|
205
|
+
rects.dropFirst().forEach { union = union.union($0) }
|
|
206
|
+
return union
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@objc public final class NativeOverlayContainerView: UIView {
|
|
211
|
+
@objc public var visible = false
|
|
212
|
+
@objc public var level = "modal"
|
|
213
|
+
@objc public var scope = "global"
|
|
214
|
+
@objc public var hostKey = ""
|
|
215
|
+
@objc public var ownerKey = ""
|
|
216
|
+
@objc public var presentation = "center"
|
|
217
|
+
@objc public var stackOrder = 0
|
|
218
|
+
@objc public var blocking = true
|
|
219
|
+
@objc public var dismissOnBackPress = true
|
|
220
|
+
@objc public var dismissOnBackdropPress = false
|
|
221
|
+
@objc public var backdropColor: UIColor?
|
|
222
|
+
@objc public var sheetHeight: CGFloat = 0
|
|
223
|
+
@objc public var sheetCornerRadius: CGFloat = 24
|
|
224
|
+
@objc public var showHandle = false
|
|
225
|
+
@objc public var sheetBackgroundColor: UIColor?
|
|
226
|
+
@objc public var dismissOnPanDown = true
|
|
227
|
+
@objc public var animationConfig = ""
|
|
228
|
+
@objc public var touchHandler: UIGestureRecognizer?
|
|
229
|
+
@objc public var onPresented: RCTDirectEventBlock?
|
|
230
|
+
@objc public var onDismissed: RCTDirectEventBlock?
|
|
231
|
+
@objc public var onRequestDismiss: RCTDirectEventBlock?
|
|
232
|
+
/// Window origin (points) of the view the content lives in; the shadow
|
|
233
|
+
/// node offsets `measure` / `measureInWindow` by it.
|
|
234
|
+
@objc public var onContentOffsetChanged: ((CGPoint) -> Void)?
|
|
235
|
+
private var reportedContentOffset: CGPoint?
|
|
236
|
+
|
|
237
|
+
private enum Phase {
|
|
238
|
+
case hidden
|
|
239
|
+
case entering
|
|
240
|
+
case shown
|
|
241
|
+
case exiting
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private var phase: Phase = .hidden
|
|
245
|
+
private weak var contentChild: UIView?
|
|
246
|
+
private var entryView: NativeOverlayEntryView?
|
|
247
|
+
private var sheetController: NativeOverlaySheetController?
|
|
248
|
+
private weak var hostWindow: UIWindow?
|
|
249
|
+
private var isSheet: Bool { presentation == "sheet" }
|
|
250
|
+
private var isPageScope: Bool { scope == "page" }
|
|
251
|
+
/// UIKit sheets only for global scope; page sheets are drawn by the entry.
|
|
252
|
+
private var usesSystemSheet: Bool { isSheet && !isPageScope }
|
|
253
|
+
private var animator: UIViewPropertyAnimator?
|
|
254
|
+
private var backdropAnimator: UIViewPropertyAnimator?
|
|
255
|
+
private var parsedConfigSource: String?
|
|
256
|
+
private var config = NativeOverlayAnimationConfig()
|
|
257
|
+
/// True between a visible=true commit and the matching onDismissed.
|
|
258
|
+
private var cycleOpen = false
|
|
259
|
+
|
|
260
|
+
override public func didMoveToWindow() {
|
|
261
|
+
super.didMoveToWindow()
|
|
262
|
+
if window != nil {
|
|
263
|
+
commitConfiguration()
|
|
264
|
+
} else if phase != .hidden && !isPageScope {
|
|
265
|
+
// A page overlay's owner screen leaves the window when another screen
|
|
266
|
+
// is pushed over it; the page host hides the entry, it is not closed.
|
|
267
|
+
finishDismiss(reason: "system")
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
override public init(frame: CGRect) {
|
|
272
|
+
super.init(frame: frame)
|
|
273
|
+
// The staged child lives in this zero-size view while hidden.
|
|
274
|
+
clipsToBounds = true
|
|
275
|
+
// Start tracking the keyboard with the first overlay host (the toaster
|
|
276
|
+
// host mounts at launch), so later entries know a keyboard already up.
|
|
277
|
+
_ = NativeOverlayKeyboard.shared
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@available(*, unavailable)
|
|
281
|
+
required init?(coder: NSCoder) {
|
|
282
|
+
fatalError("init(coder:) has not been implemented")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@objc public func insertChild(_ child: UIView, atIndex index: Int) {
|
|
286
|
+
contentChild = child
|
|
287
|
+
child.removeFromSuperview()
|
|
288
|
+
if let sheetController, sheetController.isViewLoaded {
|
|
289
|
+
sheetController.view.addSubview(child)
|
|
290
|
+
} else if let entryView {
|
|
291
|
+
entryView.contentView.addSubview(child)
|
|
292
|
+
} else {
|
|
293
|
+
stageContent()
|
|
294
|
+
}
|
|
295
|
+
reportContentOffset()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Keeps the React child attached (inside this clipped, zero-size view)
|
|
299
|
+
/// while hidden, so Fabric keeps reporting its layout; fitted sheets size
|
|
300
|
+
/// themselves from that measurement before presenting.
|
|
301
|
+
private func stageContent() {
|
|
302
|
+
guard let child = contentChild, child.superview !== self else { return }
|
|
303
|
+
child.removeFromSuperview()
|
|
304
|
+
addSubview(child)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@objc public func removeChild(_ child: UIView) {
|
|
308
|
+
guard contentChild === child else { return }
|
|
309
|
+
child.removeFromSuperview()
|
|
310
|
+
contentChild = nil
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@objc public func commitConfiguration() {
|
|
314
|
+
if parsedConfigSource != animationConfig {
|
|
315
|
+
parsedConfigSource = animationConfig
|
|
316
|
+
config = NativeOverlayAnimationConfig(jsonString: animationConfig)
|
|
317
|
+
}
|
|
318
|
+
if let entryView {
|
|
319
|
+
applyEntryConfiguration(entryView)
|
|
320
|
+
}
|
|
321
|
+
if let sheetController, phase == .entering || phase == .shown {
|
|
322
|
+
sheetController.update(style: sheetStyle(), animated: phase == .shown)
|
|
323
|
+
}
|
|
324
|
+
if visible {
|
|
325
|
+
cycleOpen = true
|
|
326
|
+
// A sheet that is still animating out is re-presented after it finishes.
|
|
327
|
+
if phase == .hidden || (phase == .exiting && !usesSystemSheet) {
|
|
328
|
+
present()
|
|
329
|
+
}
|
|
330
|
+
} else if cycleOpen {
|
|
331
|
+
if phase == .entering || phase == .shown {
|
|
332
|
+
dismiss()
|
|
333
|
+
} else if phase == .hidden {
|
|
334
|
+
cycleOpen = false
|
|
335
|
+
onDismissed?(["reason": "programmatic"])
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@objc public func invalidate() {
|
|
341
|
+
if phase != .hidden {
|
|
342
|
+
finishDismiss(reason: nil)
|
|
343
|
+
}
|
|
344
|
+
onPresented = nil
|
|
345
|
+
onDismissed = nil
|
|
346
|
+
onRequestDismiss = nil
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// MARK: - Presentation
|
|
350
|
+
|
|
351
|
+
private func present() {
|
|
352
|
+
guard let overlayLevel = NativeOverlayLevel(rawValue: level) else { return }
|
|
353
|
+
let entry: NativeOverlayEntryView
|
|
354
|
+
if isPageScope {
|
|
355
|
+
guard let pageHost = NativeOverlayPageHostView.host(for: hostKey) else { return }
|
|
356
|
+
entry = entryView ?? makeEntryView(in: pageHost)
|
|
357
|
+
entry.levelOrder = overlayLevel.order
|
|
358
|
+
entry.ownerKey = ownerKey
|
|
359
|
+
applyEntryConfiguration(entry)
|
|
360
|
+
pageHost.insert(entry)
|
|
361
|
+
} else {
|
|
362
|
+
guard let appWindow = window,
|
|
363
|
+
let host = NativeOverlayWindowManager.shared.window(for: overlayLevel, appWindow: appWindow)
|
|
364
|
+
else { return }
|
|
365
|
+
hostWindow = host
|
|
366
|
+
if isSheet {
|
|
367
|
+
presentSheet(in: host)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
entry = entryView ?? makeEntryView(in: host)
|
|
371
|
+
applyEntryConfiguration(entry)
|
|
372
|
+
insert(entry, into: host)
|
|
373
|
+
}
|
|
374
|
+
if let child = contentChild, child.superview !== entry.contentView {
|
|
375
|
+
entry.contentView.addSubview(child)
|
|
376
|
+
}
|
|
377
|
+
entry.layoutIfNeeded()
|
|
378
|
+
|
|
379
|
+
let transition = config.enter.accessibilityAdjusted
|
|
380
|
+
if phase == .hidden {
|
|
381
|
+
entry.contentView.alpha = transition.hidesOpacity ? 0 : 1
|
|
382
|
+
entry.contentView.transform = transition.hiddenTransform(
|
|
383
|
+
contentExtent: entry.contentExtent(),
|
|
384
|
+
bounds: entry.bounds
|
|
385
|
+
)
|
|
386
|
+
entry.backdropView.alpha = 0
|
|
387
|
+
}
|
|
388
|
+
stopAnimators()
|
|
389
|
+
phase = .entering
|
|
390
|
+
if blocking {
|
|
391
|
+
UIAccessibility.post(notification: .screenChanged, argument: entry)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if transition.kind == .none {
|
|
395
|
+
entry.contentView.alpha = 1
|
|
396
|
+
entry.contentView.transform = .identity
|
|
397
|
+
entry.backdropView.alpha = 1
|
|
398
|
+
phase = .shown
|
|
399
|
+
didPresent()
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
let contentAnimator = transition.motion.makeAnimator()
|
|
403
|
+
contentAnimator.addAnimations {
|
|
404
|
+
entry.contentView.alpha = 1
|
|
405
|
+
entry.contentView.transform = .identity
|
|
406
|
+
}
|
|
407
|
+
contentAnimator.addCompletion { [weak self] position in
|
|
408
|
+
guard let self, position == .end, self.phase == .entering else { return }
|
|
409
|
+
self.phase = .shown
|
|
410
|
+
self.didPresent()
|
|
411
|
+
}
|
|
412
|
+
let backdrop = config.backdropMotion.makeAnimator()
|
|
413
|
+
backdrop.addAnimations { entry.backdropView.alpha = 1 }
|
|
414
|
+
animator = contentAnimator
|
|
415
|
+
backdropAnimator = backdrop
|
|
416
|
+
contentAnimator.startAnimation()
|
|
417
|
+
backdrop.startAnimation()
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private func dismiss() {
|
|
421
|
+
if usesSystemSheet || sheetController != nil {
|
|
422
|
+
dismissSheet()
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
guard let entry = entryView else {
|
|
426
|
+
finishDismiss(reason: "programmatic")
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
stopAnimators()
|
|
430
|
+
phase = .exiting
|
|
431
|
+
let transition = config.exit.accessibilityAdjusted
|
|
432
|
+
guard transition.kind != .none else {
|
|
433
|
+
finishDismiss(reason: "programmatic")
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
let hiddenTransform = transition.hiddenTransform(
|
|
437
|
+
contentExtent: entry.contentExtent(),
|
|
438
|
+
bounds: entry.bounds
|
|
439
|
+
)
|
|
440
|
+
let contentAnimator = transition.motion.makeAnimator()
|
|
441
|
+
contentAnimator.addAnimations {
|
|
442
|
+
if transition.hidesOpacity {
|
|
443
|
+
entry.contentView.alpha = 0
|
|
444
|
+
}
|
|
445
|
+
entry.contentView.transform = hiddenTransform
|
|
446
|
+
}
|
|
447
|
+
contentAnimator.addCompletion { [weak self] position in
|
|
448
|
+
guard let self, position == .end, self.phase == .exiting else { return }
|
|
449
|
+
self.finishDismiss(reason: "programmatic")
|
|
450
|
+
}
|
|
451
|
+
let backdrop = config.backdropMotion.makeAnimator()
|
|
452
|
+
backdrop.addAnimations { entry.backdropView.alpha = 0 }
|
|
453
|
+
animator = contentAnimator
|
|
454
|
+
backdropAnimator = backdrop
|
|
455
|
+
contentAnimator.startAnimation()
|
|
456
|
+
backdrop.startAnimation()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/// `reason == nil` tears down without notifying JS (view invalidated).
|
|
460
|
+
private func finishDismiss(reason: String?) {
|
|
461
|
+
stopAnimators()
|
|
462
|
+
phase = .hidden
|
|
463
|
+
teardownSheet()
|
|
464
|
+
if let entry = entryView {
|
|
465
|
+
if let touchHandler, touchHandler.view === entry {
|
|
466
|
+
touchHandler.perform(NSSelectorFromString("detachFromView:"), with: entry)
|
|
467
|
+
}
|
|
468
|
+
stageContent()
|
|
469
|
+
entry.removeFromSuperview()
|
|
470
|
+
}
|
|
471
|
+
entryView = nil
|
|
472
|
+
stageContent()
|
|
473
|
+
reportContentOffset()
|
|
474
|
+
NativeOverlayWindowManager.shared.entryRemoved(from: hostWindow)
|
|
475
|
+
guard let reason, cycleOpen else { return }
|
|
476
|
+
cycleOpen = visible
|
|
477
|
+
onDismissed?(["reason": reason])
|
|
478
|
+
// Reopened while the exit animation ran; UIKit-initiated dismissals
|
|
479
|
+
// ("system") are left for JS to settle instead.
|
|
480
|
+
if reason == "programmatic", visible, window != nil {
|
|
481
|
+
present()
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private func didPresent() {
|
|
486
|
+
reportContentOffset()
|
|
487
|
+
onPresented?(["stackOrder": stackOrder])
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/// Reports the window origin of the content's current superview (the
|
|
491
|
+
/// level window, sheet, page host, or this host while staged). Only at
|
|
492
|
+
/// rest: the enter / exit transforms would skew it, while UIKit's own
|
|
493
|
+
/// sheet container transform must be included.
|
|
494
|
+
func reportContentOffset() {
|
|
495
|
+
guard phase == .shown || phase == .hidden,
|
|
496
|
+
let container = contentChild?.superview,
|
|
497
|
+
let containerWindow = container.window
|
|
498
|
+
else { return }
|
|
499
|
+
var offset = container.convert(CGPoint.zero, to: nil)
|
|
500
|
+
// Overlay level windows and the app window normally share the screen origin.
|
|
501
|
+
if let referenceWindow = window, referenceWindow !== containerWindow {
|
|
502
|
+
offset.x += containerWindow.frame.origin.x - referenceWindow.frame.origin.x
|
|
503
|
+
offset.y += containerWindow.frame.origin.y - referenceWindow.frame.origin.y
|
|
504
|
+
}
|
|
505
|
+
if let reported = reportedContentOffset,
|
|
506
|
+
abs(reported.x - offset.x) < 0.5, abs(reported.y - offset.y) < 0.5 {
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
reportedContentOffset = offset
|
|
510
|
+
onContentOffsetChanged?(offset)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private func stopAnimators() {
|
|
514
|
+
// Freeze at the current presentation values so the next run retargets
|
|
515
|
+
// from where the previous one left off.
|
|
516
|
+
[animator, backdropAnimator].forEach { running in
|
|
517
|
+
guard let running, running.state == .active else { return }
|
|
518
|
+
running.stopAnimation(false)
|
|
519
|
+
running.finishAnimation(at: .current)
|
|
520
|
+
}
|
|
521
|
+
animator = nil
|
|
522
|
+
backdropAnimator = nil
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// MARK: - Sheet
|
|
526
|
+
|
|
527
|
+
private func sheetStyle() -> NativeOverlaySheetStyle {
|
|
528
|
+
NativeOverlaySheetStyle(
|
|
529
|
+
height: max(sheetHeight, 1),
|
|
530
|
+
backgroundColor: sheetBackgroundColor ?? .systemBackground,
|
|
531
|
+
cornerRadius: max(sheetCornerRadius, 0),
|
|
532
|
+
showHandle: showHandle,
|
|
533
|
+
backdropColor: backdropColor,
|
|
534
|
+
dismissOnBackdropPress: dismissOnBackdropPress,
|
|
535
|
+
enterMotion: config.enter.motion,
|
|
536
|
+
exitMotion: config.exit.motion
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private func presentSheet(in host: UIWindow) {
|
|
541
|
+
guard sheetController == nil,
|
|
542
|
+
let presenter = NativeOverlayWindowManager.shared.presenter(in: host) else { return }
|
|
543
|
+
// UIKit refuses to present while another transition runs in this window.
|
|
544
|
+
if presenter.isBeingPresented || presenter.transitionCoordinator != nil {
|
|
545
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
|
|
546
|
+
guard let self, self.visible, self.phase == .hidden else { return }
|
|
547
|
+
self.present()
|
|
548
|
+
}
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
let controller = NativeOverlaySheetController(host: self, style: sheetStyle())
|
|
552
|
+
sheetController = controller
|
|
553
|
+
phase = .entering
|
|
554
|
+
presenter.present(controller, animated: true) { [weak self, weak controller] in
|
|
555
|
+
guard let self, let controller, self.sheetController === controller else { return }
|
|
556
|
+
if self.phase == .entering {
|
|
557
|
+
self.phase = .shown
|
|
558
|
+
self.didPresent()
|
|
559
|
+
}
|
|
560
|
+
if !self.visible {
|
|
561
|
+
self.dismissSheet()
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private func dismissSheet() {
|
|
567
|
+
guard let controller = sheetController else {
|
|
568
|
+
finishDismiss(reason: "programmatic")
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
// Closing mid-presentation is handled by the present completion.
|
|
572
|
+
guard !controller.isBeingPresented, phase != .exiting else { return }
|
|
573
|
+
phase = .exiting
|
|
574
|
+
controller.dismissRequestedByHost = true
|
|
575
|
+
controller.dismiss(animated: true) { [weak self, weak controller] in
|
|
576
|
+
guard let self, self.sheetController === controller else { return }
|
|
577
|
+
self.finishDismiss(reason: "programmatic")
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private func teardownSheet() {
|
|
582
|
+
guard let controller = sheetController else { return }
|
|
583
|
+
if let touchHandler, controller.isViewLoaded, touchHandler.view === controller.view {
|
|
584
|
+
touchHandler.perform(NSSelectorFromString("detachFromView:"), with: controller.view)
|
|
585
|
+
}
|
|
586
|
+
stageContent()
|
|
587
|
+
if controller.presentingViewController != nil, !controller.isBeingDismissed {
|
|
588
|
+
controller.dismissRequestedByHost = true
|
|
589
|
+
controller.dismiss(animated: false)
|
|
590
|
+
}
|
|
591
|
+
sheetController = nil
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// MARK: - Entry view
|
|
595
|
+
|
|
596
|
+
private func makeEntryView(in host: UIView) -> NativeOverlayEntryView {
|
|
597
|
+
let entry = NativeOverlayEntryView(frame: host.bounds)
|
|
598
|
+
entry.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
599
|
+
entry.onEscape = { [weak self] in
|
|
600
|
+
self?.onRequestDismiss?(["reason": "back"])
|
|
601
|
+
}
|
|
602
|
+
entry.onBackdropTap = { [weak self] in
|
|
603
|
+
guard let self, self.dismissOnBackdropPress, self.phase != .exiting else { return }
|
|
604
|
+
self.onRequestDismiss?(["reason": "backdrop"])
|
|
605
|
+
}
|
|
606
|
+
entry.onLayout = { [weak self] in
|
|
607
|
+
self?.reportContentOffset()
|
|
608
|
+
}
|
|
609
|
+
entry.onPanDismiss = { [weak self] in
|
|
610
|
+
guard let self, self.phase == .shown || self.phase == .entering else { return }
|
|
611
|
+
self.finishDismiss(reason: nil)
|
|
612
|
+
self.onRequestDismiss?(["reason": "pan"])
|
|
613
|
+
}
|
|
614
|
+
// Page hosts live inside the React surface, whose touch handler already
|
|
615
|
+
// reaches the moved content; a second handler would double-dispatch.
|
|
616
|
+
if !isPageScope, let touchHandler, touchHandler.view == nil {
|
|
617
|
+
touchHandler.perform(NSSelectorFromString("attachToView:"), with: entry)
|
|
618
|
+
}
|
|
619
|
+
entryView = entry
|
|
620
|
+
return entry
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private func applyEntryConfiguration(_ entry: NativeOverlayEntryView) {
|
|
624
|
+
entry.stackOrder = stackOrder
|
|
625
|
+
entry.blocking = blocking
|
|
626
|
+
entry.dismissOnBackPress = dismissOnBackPress
|
|
627
|
+
entry.accessibilityViewIsModal = blocking
|
|
628
|
+
entry.backdropView.backgroundColor = backdropColor ?? .clear
|
|
629
|
+
if isPageScope && isSheet {
|
|
630
|
+
entry.configurePageSheet(
|
|
631
|
+
height: max(sheetHeight, 1),
|
|
632
|
+
backgroundColor: sheetBackgroundColor ?? .systemBackground,
|
|
633
|
+
cornerRadius: max(sheetCornerRadius, 0),
|
|
634
|
+
draggable: dismissOnPanDown
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/// Keeps entries of one level ordered by `stackOrder`.
|
|
640
|
+
private func insert(_ entry: NativeOverlayEntryView, into host: UIView) {
|
|
641
|
+
if entry.superview === host { return }
|
|
642
|
+
entry.frame = host.bounds
|
|
643
|
+
let above = host.subviews
|
|
644
|
+
.compactMap { $0 as? NativeOverlayEntryView }
|
|
645
|
+
.first { $0.stackOrder > entry.stackOrder }
|
|
646
|
+
if let above {
|
|
647
|
+
host.insertSubview(entry, belowSubview: above)
|
|
648
|
+
} else {
|
|
649
|
+
host.addSubview(entry)
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
extension NativeOverlayContainerView: NativeOverlaySheetHost {
|
|
655
|
+
func sheetDidLayout(_ controller: NativeOverlaySheetController) {
|
|
656
|
+
guard sheetController === controller else { return }
|
|
657
|
+
reportContentOffset()
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
func sheetDidLoad(_ controller: NativeOverlaySheetController) {
|
|
661
|
+
if let touchHandler, touchHandler.view == nil {
|
|
662
|
+
touchHandler.perform(NSSelectorFromString("attachToView:"), with: controller.view)
|
|
663
|
+
}
|
|
664
|
+
if let child = contentChild {
|
|
665
|
+
child.removeFromSuperview()
|
|
666
|
+
controller.view.addSubview(child)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
func sheetDidDismissInteractively(_ controller: NativeOverlaySheetController) {
|
|
671
|
+
guard sheetController === controller else { return }
|
|
672
|
+
// Already off screen: tear down, then let JS close the entry; the next
|
|
673
|
+
// visible=false commit reports onDismissed.
|
|
674
|
+
finishDismiss(reason: nil)
|
|
675
|
+
onRequestDismiss?(["reason": "pan"])
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
func sheetDidDisappearUnexpectedly(_ controller: NativeOverlaySheetController) {
|
|
679
|
+
guard sheetController === controller else { return }
|
|
680
|
+
finishDismiss(reason: "system")
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
func sheetRequestedDismiss(_ controller: NativeOverlaySheetController, reason: String) {
|
|
684
|
+
guard sheetController === controller, phase != .exiting else { return }
|
|
685
|
+
if reason == "back", !dismissOnBackPress { return }
|
|
686
|
+
onRequestDismiss?(["reason": reason])
|
|
687
|
+
}
|
|
688
|
+
}
|