@lodev09/react-native-true-sheet 0.1.0

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.
Files changed (47) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +53 -0
  3. package/TrueSheet.podspec +42 -0
  4. package/android/build.gradle +94 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +17 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +20 -0
  10. package/ios/Extensions/UIView+pinTo.swift +25 -0
  11. package/ios/Extensions/UIViewController+detentForSize.swift +112 -0
  12. package/ios/TrueSheet-Bridging-Header.h +9 -0
  13. package/ios/TrueSheetView.swift +249 -0
  14. package/ios/TrueSheetViewController.swift +98 -0
  15. package/ios/TrueSheetViewManager.m +38 -0
  16. package/ios/TrueSheetViewManager.swift +46 -0
  17. package/ios/Utils/Logger.swift +43 -0
  18. package/ios/Utils/Promise.swift +25 -0
  19. package/lib/commonjs/TrueSheet.js +104 -0
  20. package/lib/commonjs/TrueSheet.js.map +1 -0
  21. package/lib/commonjs/TrueSheetModule.js +17 -0
  22. package/lib/commonjs/TrueSheetModule.js.map +1 -0
  23. package/lib/commonjs/index.js +28 -0
  24. package/lib/commonjs/index.js.map +1 -0
  25. package/lib/commonjs/types.js +6 -0
  26. package/lib/commonjs/types.js.map +1 -0
  27. package/lib/module/TrueSheet.js +95 -0
  28. package/lib/module/TrueSheet.js.map +1 -0
  29. package/lib/module/TrueSheetModule.js +11 -0
  30. package/lib/module/TrueSheetModule.js.map +1 -0
  31. package/lib/module/index.js +3 -0
  32. package/lib/module/index.js.map +1 -0
  33. package/lib/module/types.js +2 -0
  34. package/lib/module/types.js.map +1 -0
  35. package/lib/typescript/src/TrueSheet.d.ts +31 -0
  36. package/lib/typescript/src/TrueSheet.d.ts.map +1 -0
  37. package/lib/typescript/src/TrueSheetModule.d.ts +2 -0
  38. package/lib/typescript/src/TrueSheetModule.d.ts.map +1 -0
  39. package/lib/typescript/src/index.d.ts +3 -0
  40. package/lib/typescript/src/index.d.ts.map +1 -0
  41. package/lib/typescript/src/types.d.ts +94 -0
  42. package/lib/typescript/src/types.d.ts.map +1 -0
  43. package/package.json +183 -0
  44. package/src/TrueSheet.tsx +162 -0
  45. package/src/TrueSheetModule.ts +18 -0
  46. package/src/index.ts +2 -0
  47. package/src/types.ts +107 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 lodev09
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # React Native True Sheet
2
+
3
+ The real native bottom sheet.
4
+
5
+ ## Features
6
+ - ✅ Implemented on the native realm.
7
+ - ✅ **_NOT_** your pure JS, (re)animated View.
8
+ - ✅ Clean, fast and lightweight.
9
+ - ✅ Handles your Sscrolling needs, easy.
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ yarn add @lodev09/react-native-true-sheet
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import { TrueSheet } from "@lodev09/react-native-true-sheet";
21
+
22
+ // ...
23
+
24
+ const sheet = useRef<TrueSheet>(null)
25
+
26
+ const openSheet = () => {
27
+ sheet.current?.present()
28
+ }
29
+
30
+ return (
31
+ <View>
32
+ <Button onPress={openSheet} title="Open Sheet" />
33
+ <TrueSheet ref={sheet}>
34
+ // ...
35
+ </TrueSheet>
36
+ </View>
37
+ )
38
+ ```
39
+
40
+ ## Options
41
+ > TODO - laters
42
+
43
+ ## Contributing
44
+
45
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
46
+
47
+ ## License
48
+
49
+ MIT
50
+
51
+ ---
52
+
53
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,42 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5
+
6
+ Pod::Spec.new do |s|
7
+ s.name = "TrueSheet"
8
+ s.version = package["version"]
9
+ s.summary = package["description"]
10
+ s.homepage = package["homepage"]
11
+ s.license = package["license"]
12
+ s.authors = package["author"]
13
+
14
+ s.platforms = { :ios => min_ios_version_supported }
15
+ s.source = { :git => "https://github.com/lodev09/react-native-true-sheet.git", :tag => "#{s.version}" }
16
+
17
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
18
+
19
+ # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
20
+ # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
21
+ if respond_to?(:install_modules_dependencies, true)
22
+ install_modules_dependencies(s)
23
+ else
24
+ s.dependency "React-Core"
25
+
26
+ # Don't install the dependencies when we run `pod install` in the old architecture.
27
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
28
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
29
+ s.pod_target_xcconfig = {
30
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
31
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
32
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
33
+ }
34
+ s.dependency "React-RCTFabric"
35
+ s.dependency "React-Codegen"
36
+ s.dependency "RCT-Folly"
37
+ s.dependency "RCTRequired"
38
+ s.dependency "RCTTypeSafety"
39
+ s.dependency "ReactCommon/turbomodule/core"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,94 @@
1
+ buildscript {
2
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["TrueSheet_kotlinVersion"]
4
+
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+
10
+ dependencies {
11
+ classpath "com.android.tools.build:gradle:7.2.1"
12
+ // noinspection DifferentKotlinGradleVersion
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
+ }
15
+ }
16
+
17
+ def isNewArchitectureEnabled() {
18
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
+ }
20
+
21
+ apply plugin: "com.android.library"
22
+ apply plugin: "kotlin-android"
23
+
24
+ if (isNewArchitectureEnabled()) {
25
+ apply plugin: "com.facebook.react"
26
+ }
27
+
28
+ def getExtOrDefault(name) {
29
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["TrueSheet_" + name]
30
+ }
31
+
32
+ def getExtOrIntegerDefault(name) {
33
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["TrueSheet_" + name]).toInteger()
34
+ }
35
+
36
+ def supportsNamespace() {
37
+ def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
38
+ def major = parsed[0].toInteger()
39
+ def minor = parsed[1].toInteger()
40
+
41
+ // Namespace support was added in 7.3.0
42
+ return (major == 7 && minor >= 3) || major >= 8
43
+ }
44
+
45
+ android {
46
+ if (supportsNamespace()) {
47
+ namespace "com.lodev09.truesheet"
48
+
49
+ sourceSets {
50
+ main {
51
+ manifest.srcFile "src/main/AndroidManifestNew.xml"
52
+ }
53
+ }
54
+ }
55
+
56
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
57
+
58
+ defaultConfig {
59
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
60
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
61
+
62
+ }
63
+
64
+ buildTypes {
65
+ release {
66
+ minifyEnabled false
67
+ }
68
+ }
69
+
70
+ lintOptions {
71
+ disable "GradleCompatible"
72
+ }
73
+
74
+ compileOptions {
75
+ sourceCompatibility JavaVersion.VERSION_1_8
76
+ targetCompatibility JavaVersion.VERSION_1_8
77
+ }
78
+ }
79
+
80
+ repositories {
81
+ mavenCentral()
82
+ google()
83
+ }
84
+
85
+ def kotlin_version = getExtOrDefault("kotlinVersion")
86
+
87
+ dependencies {
88
+ // For < 0.71, this will be from the local maven repo
89
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
90
+ //noinspection GradleDynamicVersion
91
+ implementation "com.facebook.react:react-native:+"
92
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
93
+ }
94
+
@@ -0,0 +1,5 @@
1
+ TrueSheet_kotlinVersion=1.7.0
2
+ TrueSheet_minSdkVersion=21
3
+ TrueSheet_targetSdkVersion=31
4
+ TrueSheet_compileSdkVersion=31
5
+ TrueSheet_ndkversion=21.4.7075529
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.lodev09.truesheet">
3
+ </manifest>
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,17 @@
1
+ package com.lodev09.truesheet
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+
9
+ class TrueSheetPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return emptyList()
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return listOf(TrueSheetViewManager())
16
+ }
17
+ }
@@ -0,0 +1,20 @@
1
+ package com.lodev09.truesheet
2
+
3
+ import android.graphics.Color
4
+ import android.view.View
5
+ import com.facebook.react.uimanager.SimpleViewManager
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+
9
+ class TrueSheetViewManager : SimpleViewManager<View>() {
10
+ override fun getName() = "TrueSheetView"
11
+
12
+ override fun createViewInstance(reactContext: ThemedReactContext): View {
13
+ return View(reactContext)
14
+ }
15
+
16
+ @ReactProp(name = "color")
17
+ fun setColor(view: View, color: String) {
18
+ view.setBackgroundColor(Color.parseColor(color))
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ extension UIView {
10
+ func pinTo(view: UIView, from edges: UIRectEdge = .all, with height: CGFloat? = nil) {
11
+ var constraints: [NSLayoutConstraint] = []
12
+
13
+ if edges.contains(.top) { constraints.append(topAnchor.constraint(equalTo: view.topAnchor)) }
14
+ if edges.contains(.bottom) { constraints.append(bottomAnchor.constraint(equalTo: view.bottomAnchor)) }
15
+ if edges.contains(.left) { constraints.append(leadingAnchor.constraint(equalTo: view.leadingAnchor)) }
16
+ if edges.contains(.right) { constraints.append(trailingAnchor.constraint(equalTo: view.trailingAnchor)) }
17
+
18
+ if let height { constraints.append(heightAnchor.constraint(equalToConstant: height)) }
19
+
20
+ if !constraints.isEmpty {
21
+ translatesAutoresizingMaskIntoConstraints = false
22
+ NSLayoutConstraint.activate(constraints)
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,112 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ @available(iOS 15.0, *)
10
+ extension UISheetPresentationController.Detent.Identifier {
11
+ static let small = UISheetPresentationController.Detent.Identifier("small")
12
+ }
13
+
14
+ @available(iOS 15.0, *)
15
+ extension UIViewController {
16
+ private func detentFor(identifier: UISheetPresentationController.Detent.Identifier,
17
+ _ resolution: @escaping (CGFloat) -> Void) -> UISheetPresentationController.Detent {
18
+ switch identifier {
19
+ case .large:
20
+ if #available(iOS 16.0, *) {
21
+ return .custom(identifier: .large) { context in
22
+ let value = UISheetPresentationController.Detent.large().resolvedValue(in: context)
23
+ resolution(value ?? 0)
24
+ return value
25
+ }
26
+ } else {
27
+ resolution(view.frame.height)
28
+ return .large()
29
+ }
30
+ case .medium:
31
+ if #available(iOS 16.0, *) {
32
+ return .custom(identifier: .medium) { context in
33
+ let value = UISheetPresentationController.Detent.medium().resolvedValue(in: context)
34
+ resolution(value ?? 0)
35
+ return value
36
+ }
37
+ }
38
+ case .small:
39
+ if #available(iOS 16.0, *) {
40
+ return .custom { context in
41
+ let value = 0.25 * context.maximumDetentValue
42
+ resolution(value)
43
+ return value
44
+ }
45
+ }
46
+ default:
47
+ break
48
+ }
49
+
50
+ resolution(view.frame.height / 2)
51
+ return .medium()
52
+ }
53
+
54
+ /// Get the custom detent based on the given size and view frame size
55
+ func detentFor(_ anySize: Any, with height: CGFloat?, _ resolution: @escaping (String, CGFloat) -> Void) -> UISheetPresentationController.Detent {
56
+ let id = "custom-\(anySize)"
57
+
58
+ if let floatSize = anySize as? CGFloat {
59
+ if #available(iOS 16.0, *) {
60
+ return .custom(identifier: identifier(from: id)) { context in
61
+ let value = min(floatSize, context.maximumDetentValue)
62
+ resolution(id, value)
63
+ return value
64
+ }
65
+ }
66
+ }
67
+
68
+ if var stringSize = anySize as? String {
69
+ switch stringSize {
70
+ case "small":
71
+ return detentFor(identifier: .small) { value in
72
+ resolution(UISheetPresentationController.Detent.Identifier.small.rawValue, value)
73
+ }
74
+ case "medium":
75
+ return detentFor(identifier: .medium) { value in
76
+ resolution(UISheetPresentationController.Detent.Identifier.medium.rawValue, value)
77
+ }
78
+ case "large":
79
+ return detentFor(identifier: .large) { value in
80
+ resolution(UISheetPresentationController.Detent.Identifier.large.rawValue, value)
81
+ }
82
+ case "auto":
83
+ if #available(iOS 16.0, *), let height {
84
+ return .custom(identifier: identifier(from: id)) { context in
85
+ let value = min(height, context.maximumDetentValue)
86
+ resolution(id, value)
87
+ return value
88
+ }
89
+ }
90
+ default:
91
+ if #available(iOS 16.0, *) {
92
+ // Percent
93
+ stringSize.removeAll(where: { $0 == "%" })
94
+ let floatSize = CGFloat((stringSize as NSString).floatValue)
95
+ if floatSize > 0.0 {
96
+ return .custom(identifier: identifier(from: id)) { context in
97
+ let value = min((floatSize / 100) * context.maximumDetentValue, context.maximumDetentValue)
98
+ resolution(id, value)
99
+ return value
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ return .medium()
107
+ }
108
+
109
+ func identifier(from id: String) -> UISheetPresentationController.Detent.Identifier {
110
+ return UISheetPresentationController.Detent.Identifier(id)
111
+ }
112
+ }
@@ -0,0 +1,9 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #import <React/RCTViewManager.h>
@@ -0,0 +1,249 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ @objc(TrueSheetView)
10
+ class TrueSheetView: UIView, RCTInvalidating, TrueSheetViewControllerDelegate {
11
+ // MARK: - React properties
12
+
13
+ @objc var sizes: [Any] = []
14
+
15
+ // Events
16
+ @objc var onDismiss: RCTDirectEventBlock?
17
+ @objc var onPresent: RCTDirectEventBlock?
18
+ @objc var onSizeChange: RCTDirectEventBlock?
19
+
20
+ // MARK: - Private properties
21
+
22
+ private var isPresented = false
23
+ private var activeIndex: Int?
24
+ private var bridge: RCTBridge
25
+ private var touchHandler: RCTTouchHandler
26
+ private var viewController: TrueSheetViewController
27
+
28
+ // MARK: - Content properties
29
+
30
+ private var containerView: UIView?
31
+
32
+ private var contentView: UIView?
33
+ private var footerView: UIView?
34
+ private var rctScrollView: RCTScrollView?
35
+
36
+ private var isContentMounted: Bool {
37
+ return contentView != nil
38
+ }
39
+
40
+ // Content height minus the footer height for `auto` layout
41
+ private var contentHeight: CGFloat {
42
+ guard let contentView else { return 0 }
43
+
44
+ // Exclude bottom safe area for consistency with a Scrollable content
45
+ let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
46
+ let bottomInset = window?.safeAreaInsets.bottom ?? 0
47
+
48
+ return contentView.frame.height - bottomInset
49
+ }
50
+
51
+ // MARK: - Setup
52
+
53
+ init(with bridge: RCTBridge) {
54
+ self.bridge = bridge
55
+
56
+ viewController = TrueSheetViewController()
57
+ touchHandler = RCTTouchHandler(bridge: bridge)
58
+
59
+ super.init(frame: .zero)
60
+
61
+ viewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
62
+ viewController.delegate = self
63
+ }
64
+
65
+ @available(*, unavailable)
66
+ required init?(coder _: NSCoder) {
67
+ fatalError("init(coder:) has not been implemented")
68
+ }
69
+
70
+ func viewDidChangeWidth(_ width: CGFloat) {
71
+ guard let containerView else { return }
72
+
73
+ let size = CGSize(width: width, height: containerView.bounds.height)
74
+ bridge.uiManager.setSize(size, for: containerView)
75
+
76
+ if let footerView {
77
+ bridge.uiManager.setSize(size, for: footerView)
78
+ }
79
+ }
80
+
81
+ func didDismiss() {
82
+ isPresented = false
83
+ activeIndex = nil
84
+
85
+ onDismiss?(nil)
86
+ }
87
+
88
+ func didChangeSize(_ value: CGFloat, at index: Int) {
89
+ if index != activeIndex {
90
+ activeIndex = index
91
+ onSizeChange?(["index": index, "value": value])
92
+ }
93
+ }
94
+
95
+ override func insertReactSubview(_ subview: UIView!, at index: Int) {
96
+ super.insertReactSubview(subview, at: index)
97
+
98
+ guard containerView == nil else {
99
+ Logger.error("Sheet can only have one content view.")
100
+ return
101
+ }
102
+
103
+ viewController.view.insertSubview(subview, at: 0)
104
+ viewController.view.backgroundColor = backgroundColor ?? .white
105
+ backgroundColor = .clear
106
+
107
+ containerView = subview
108
+ touchHandler.attach(to: subview)
109
+ }
110
+
111
+ override func removeReactSubview(_ subview: UIView!) {
112
+ guard subview == containerView else {
113
+ Logger.error("Cannot remove view other than sheet view")
114
+ return
115
+ }
116
+
117
+ super.removeReactSubview(subview)
118
+
119
+ touchHandler.detach(from: subview)
120
+
121
+ containerView = nil
122
+ contentView = nil
123
+ footerView = nil
124
+ }
125
+
126
+ override func didUpdateReactSubviews() {
127
+ // Do nothing, as subviews are managed by `insertReactSubview`
128
+ }
129
+
130
+ override func layoutSubviews() {
131
+ super.layoutSubviews()
132
+
133
+ if let containerView, contentView == nil {
134
+ contentView = containerView.subviews.first
135
+ setupContentIfNeeded()
136
+ }
137
+ }
138
+
139
+ // MARK: - Prop setters
140
+
141
+ @objc
142
+ func setScrollableHandle(_ tag: NSNumber?) {
143
+ let view = bridge.uiManager.view(forReactTag: tag) as? RCTScrollView
144
+ rctScrollView = view
145
+ setupContentIfNeeded()
146
+ }
147
+
148
+ @objc
149
+ func setFooterHandle(_ tag: NSNumber?) {
150
+ let view = bridge.uiManager.view(forReactTag: tag)
151
+ footerView = view
152
+ setupContentIfNeeded()
153
+
154
+ if #available(iOS 16.0, *) {
155
+ viewController.sheet?.invalidateDetents()
156
+ }
157
+ }
158
+
159
+ func invalidate() {
160
+ viewController.dismiss(animated: true)
161
+ }
162
+
163
+ // MARK: - Methods
164
+
165
+ func setupContentIfNeeded() {
166
+ guard isContentMounted, let containerView else { return }
167
+
168
+ containerView.pinTo(view: viewController.view)
169
+
170
+ // Add constraints to fix weirdness and support ScrollView
171
+ if let contentView, let rctScrollView {
172
+ contentView.pinTo(view: containerView)
173
+ rctScrollView.pinTo(view: contentView)
174
+ }
175
+
176
+ // Pin footer at the bottom
177
+ if let footerView {
178
+ containerView.bringSubviewToFront(footerView)
179
+ footerView.pinTo(
180
+ view: viewController.view,
181
+ from: [.bottom, .left, .right],
182
+ with: footerView.frame.height
183
+ )
184
+ }
185
+ }
186
+
187
+ func dismiss(promise: Promise) {
188
+ if isPresented {
189
+ viewController.dismiss(animated: true) {
190
+ promise.resolve(true)
191
+ }
192
+ }
193
+ }
194
+
195
+ func present(at index: Int, promise: Promise) {
196
+ let rvc = reactViewController()
197
+
198
+ guard let rvc else {
199
+ promise.reject(message: "No react view controller present.")
200
+ return
201
+ }
202
+
203
+ if #available(iOS 15.0, *), let sheet = viewController.sheet {
204
+ viewController.configureSheet(for: sizes, with: contentHeight)
205
+
206
+ guard sizes.indices.contains(index) else {
207
+ promise.reject(message: "Size at \(index) is not configured.")
208
+ return
209
+ }
210
+
211
+ var identifier: UISheetPresentationController.Detent.Identifier = .medium
212
+
213
+ if sheet.detents.indices.contains(index) {
214
+ let detent = sheet.detents[index]
215
+ if #available(iOS 16.0, *) {
216
+ identifier = detent.identifier
217
+ } else if detent == .large() {
218
+ identifier = .large
219
+ }
220
+ }
221
+
222
+ if isPresented {
223
+ sheet.animateChanges {
224
+ sheet.selectedDetentIdentifier = identifier
225
+
226
+ // Notify when size is changed programatically
227
+ let info = viewController.detentValues.first(where: { $0.value.index == index })
228
+ if let sizeValue = info?.value.value {
229
+ self.didChangeSize(sizeValue, at: index)
230
+ }
231
+ }
232
+ } else {
233
+ sheet.selectedDetentIdentifier = identifier
234
+ }
235
+ }
236
+
237
+ if !isPresented {
238
+ // Keep track of the active index
239
+ activeIndex = index
240
+
241
+ rvc.present(viewController, animated: true) {
242
+ self.isPresented = true
243
+ self.onPresent?(nil)
244
+
245
+ promise.resolve(true)
246
+ }
247
+ }
248
+ }
249
+ }