@soundtrackio/react-native-bottom-tabs 1.4.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.
- package/README.md +32 -0
- package/android/build.gradle +132 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/rcttabview/ImageSource.kt +86 -0
- package/android/src/main/java/com/rcttabview/LatestRequestTracker.kt +19 -0
- package/android/src/main/java/com/rcttabview/RCTTabView.kt +689 -0
- package/android/src/main/java/com/rcttabview/RCTTabViewManager.kt +222 -0
- package/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt +19 -0
- package/android/src/main/java/com/rcttabview/RCTTabViewPrewarm.kt +40 -0
- package/android/src/main/java/com/rcttabview/RebuildBatch.kt +31 -0
- package/android/src/main/java/com/rcttabview/TabBarVisibility.kt +14 -0
- package/android/src/main/java/com/rcttabview/Utils.kt +31 -0
- package/android/src/main/java/com/rcttabview/events/OnNativeLayoutEvent.kt +25 -0
- package/android/src/main/java/com/rcttabview/events/OnTabBarMeasuredEvent.kt +24 -0
- package/android/src/main/java/com/rcttabview/events/PageSelectedEvent.kt +32 -0
- package/android/src/main/java/com/rcttabview/events/TabLongPressedEvent.kt +24 -0
- package/android/src/test/java/com/rcttabview/LatestRequestTrackerTest.kt +37 -0
- package/android/src/test/java/com/rcttabview/RebuildBatchTest.kt +44 -0
- package/android/src/test/java/com/rcttabview/TabBarVisibilityTest.kt +58 -0
- package/app.plugin.js +1 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h +33 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +19 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +35 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp +15 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h +25 -0
- package/ios/BottomAccessoryProvider.swift +27 -0
- package/ios/Extensions.swift +101 -0
- package/ios/PlatformAliases.swift +43 -0
- package/ios/RCTBottomAccessoryComponentView.h +17 -0
- package/ios/RCTBottomAccessoryComponentView.mm +76 -0
- package/ios/RCTTabViewComponentView.h +16 -0
- package/ios/RCTTabViewComponentView.mm +285 -0
- package/ios/RepresentableView.swift +55 -0
- package/ios/SVG/CoreSVG.h +19 -0
- package/ios/SVG/CoreSVG.mm +177 -0
- package/ios/SVG/SvgDecoder.h +10 -0
- package/ios/SVG/SvgDecoder.mm +38 -0
- package/ios/TabAppearModifier.swift +38 -0
- package/ios/TabBarFontSize.swift +88 -0
- package/ios/TabItem.swift +35 -0
- package/ios/TabItemEventModifier.swift +163 -0
- package/ios/TabView/AnyTabView.swift +7 -0
- package/ios/TabView/LegacyTabView.swift +70 -0
- package/ios/TabView/NewTabView.swift +138 -0
- package/ios/TabViewImpl.swift +566 -0
- package/ios/TabViewProps.swift +109 -0
- package/ios/TabViewProvider.swift +368 -0
- package/ios/react-native-bottom-tabs-Bridging-Header.h +4 -0
- package/lib/module/BottomAccessoryView.js +47 -0
- package/lib/module/BottomAccessoryView.js.map +1 -0
- package/lib/module/BottomAccessoryViewNativeComponent.ts +27 -0
- package/lib/module/DelayedFreeze.js +31 -0
- package/lib/module/DelayedFreeze.js.map +1 -0
- package/lib/module/NativeSVGDecoder.js +5 -0
- package/lib/module/NativeSVGDecoder.js.map +1 -0
- package/lib/module/SceneMap.js +28 -0
- package/lib/module/SceneMap.js.map +1 -0
- package/lib/module/TabView.js +294 -0
- package/lib/module/TabView.js.map +1 -0
- package/lib/module/TabViewNativeComponent.ts +73 -0
- package/lib/module/expo.js +35 -0
- package/lib/module/expo.js.map +1 -0
- package/lib/module/index.js +20 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/BottomTabBarHeightContext.js +5 -0
- package/lib/module/utils/BottomTabBarHeightContext.js.map +1 -0
- package/lib/module/utils/useBottomTabBarHeight.js +12 -0
- package/lib/module/utils/useBottomTabBarHeight.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/BottomAccessoryView.d.ts +7 -0
- package/lib/typescript/src/BottomAccessoryView.d.ts.map +1 -0
- package/lib/typescript/src/BottomAccessoryView.js +24 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts +15 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.js +4 -0
- package/lib/typescript/src/DelayedFreeze.d.ts +7 -0
- package/lib/typescript/src/DelayedFreeze.d.ts.map +1 -0
- package/lib/typescript/src/DelayedFreeze.js +21 -0
- package/lib/typescript/src/NativeSVGDecoder.d.ts +5 -0
- package/lib/typescript/src/NativeSVGDecoder.d.ts.map +1 -0
- package/lib/typescript/src/NativeSVGDecoder.js +2 -0
- package/lib/typescript/src/SceneMap.d.ts +9 -0
- package/lib/typescript/src/SceneMap.d.ts.map +1 -0
- package/lib/typescript/src/SceneMap.js +15 -0
- package/lib/typescript/src/TabView.d.ts +219 -0
- package/lib/typescript/src/TabView.d.ts.map +1 -0
- package/lib/typescript/src/TabView.js +157 -0
- package/lib/typescript/src/TabViewNativeComponent.d.ts +59 -0
- package/lib/typescript/src/TabViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/TabViewNativeComponent.js +4 -0
- package/lib/typescript/src/expo.d.ts +6 -0
- package/lib/typescript/src/expo.d.ts.map +1 -0
- package/lib/typescript/src/expo.js +34 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/index.js +11 -0
- package/lib/typescript/src/types.d.ts +31 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/types.js +1 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts +2 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts.map +1 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.js +2 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts +1 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts.map +1 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.js +9 -0
- package/lib/typescript/tsconfig.tsbuildinfo +1 -0
- package/package.json +135 -0
- package/react-native-bottom-tabs.podspec +43 -0
- package/src/BottomAccessoryView.tsx +59 -0
- package/src/BottomAccessoryViewNativeComponent.ts +27 -0
- package/src/DelayedFreeze.tsx +31 -0
- package/src/NativeSVGDecoder.ts +5 -0
- package/src/SceneMap.tsx +34 -0
- package/src/TabView.tsx +558 -0
- package/src/TabViewNativeComponent.ts +73 -0
- package/src/expo.ts +58 -0
- package/src/index.tsx +23 -0
- package/src/types.ts +36 -0
- package/src/utils/BottomTabBarHeightContext.ts +5 -0
- package/src/utils/useBottomTabBarHeight.ts +15 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
internal enum MinimizeBehavior: String {
|
|
5
|
+
case automatic
|
|
6
|
+
case never
|
|
7
|
+
case onScrollUp
|
|
8
|
+
case onScrollDown
|
|
9
|
+
|
|
10
|
+
#if compiler(>=6.2)
|
|
11
|
+
@available(iOS 26.0, macOS 26.0, tvOS 26.0, *)
|
|
12
|
+
func convert() -> TabBarMinimizeBehavior {
|
|
13
|
+
#if os(macOS) || os(tvOS)
|
|
14
|
+
return .automatic
|
|
15
|
+
#else
|
|
16
|
+
switch self {
|
|
17
|
+
case .automatic:
|
|
18
|
+
return .automatic
|
|
19
|
+
case .never:
|
|
20
|
+
return .never
|
|
21
|
+
case .onScrollUp:
|
|
22
|
+
return .onScrollUp
|
|
23
|
+
case .onScrollDown:
|
|
24
|
+
return .onScrollDown
|
|
25
|
+
}
|
|
26
|
+
#endif
|
|
27
|
+
}
|
|
28
|
+
#endif
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public enum TabBarRole: String {
|
|
32
|
+
case search
|
|
33
|
+
|
|
34
|
+
@available(iOS 18, macOS 15, visionOS 2, tvOS 18, *)
|
|
35
|
+
func convert() -> TabRole {
|
|
36
|
+
switch self {
|
|
37
|
+
case .search:
|
|
38
|
+
return .search
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
struct IdentifiablePlatformView: Identifiable, Equatable {
|
|
44
|
+
let id = UUID()
|
|
45
|
+
let view: PlatformView
|
|
46
|
+
|
|
47
|
+
init(_ view: PlatformView) {
|
|
48
|
+
self.view = view
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Props that component accepts. SwiftUI view gets re-rendered when ObservableObject changes.
|
|
53
|
+
class TabViewProps: ObservableObject {
|
|
54
|
+
@Published var children: [IdentifiablePlatformView] = []
|
|
55
|
+
@Published var items: [TabInfo] = []
|
|
56
|
+
@Published var selectedPage: String?
|
|
57
|
+
@Published var icons: [Int: PlatformImage] = [:]
|
|
58
|
+
@Published var focusedIcons: [Int: PlatformImage] = [:]
|
|
59
|
+
@Published var iconsRevision: Int = 0
|
|
60
|
+
@Published var sidebarAdaptable: Bool?
|
|
61
|
+
@Published var labeled: Bool = false
|
|
62
|
+
@Published var minimizeBehavior: MinimizeBehavior?
|
|
63
|
+
@Published var scrollEdgeAppearance: String?
|
|
64
|
+
@Published var barTintColor: PlatformColor?
|
|
65
|
+
@Published var activeTintColor: PlatformColor?
|
|
66
|
+
@Published var inactiveTintColor: PlatformColor?
|
|
67
|
+
@Published var experimentalBakedTintColors: Bool = false
|
|
68
|
+
@Published var translucent: Bool = true
|
|
69
|
+
@Published var disablePageAnimations: Bool = false
|
|
70
|
+
@Published var hapticFeedbackEnabled: Bool = false
|
|
71
|
+
@Published var layoutDirection: String?
|
|
72
|
+
@Published var fontSize: Int?
|
|
73
|
+
@Published var fontFamily: String?
|
|
74
|
+
@Published var fontWeight: String?
|
|
75
|
+
@Published var tabBarHidden: Bool = false
|
|
76
|
+
|
|
77
|
+
var selectedActiveTintColor: PlatformColor? {
|
|
78
|
+
if let selectedPage,
|
|
79
|
+
let tabData = items.findByKey(selectedPage),
|
|
80
|
+
let activeTintColor = tabData.activeTintColor
|
|
81
|
+
{
|
|
82
|
+
return activeTintColor
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return activeTintColor
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var hasCustomTintColors: Bool {
|
|
89
|
+
activeTintColor != nil
|
|
90
|
+
|| inactiveTintColor != nil
|
|
91
|
+
|| items.contains(where: { $0.activeTintColor != nil })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var effectiveInactiveTintColor: PlatformColor? {
|
|
95
|
+
#if os(iOS)
|
|
96
|
+
if ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 26 {
|
|
97
|
+
return nil
|
|
98
|
+
}
|
|
99
|
+
#endif
|
|
100
|
+
|
|
101
|
+
return inactiveTintColor
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var filteredItems: [TabInfo] {
|
|
105
|
+
items.filter {
|
|
106
|
+
!$0.hidden || $0.key == selectedPage
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import SwiftUI
|
|
4
|
+
|
|
5
|
+
@objcMembers
|
|
6
|
+
public final class TabInfo: NSObject {
|
|
7
|
+
public let key: String
|
|
8
|
+
public let title: String
|
|
9
|
+
public let badge: String?
|
|
10
|
+
public let sfSymbol: String
|
|
11
|
+
public let focusedSfSymbol: String?
|
|
12
|
+
public let activeTintColor: PlatformColor?
|
|
13
|
+
public let iconRenderingMode: String?
|
|
14
|
+
public let hidden: Bool
|
|
15
|
+
public let testID: String?
|
|
16
|
+
public let role: TabBarRole?
|
|
17
|
+
public let preventsDefault: Bool
|
|
18
|
+
|
|
19
|
+
public init(
|
|
20
|
+
key: String,
|
|
21
|
+
title: String,
|
|
22
|
+
badge: String?,
|
|
23
|
+
sfSymbol: String,
|
|
24
|
+
focusedSfSymbol: String?,
|
|
25
|
+
activeTintColor: PlatformColor?,
|
|
26
|
+
iconRenderingMode: String?,
|
|
27
|
+
hidden: Bool,
|
|
28
|
+
testID: String?,
|
|
29
|
+
role: String?,
|
|
30
|
+
preventsDefault: Bool = false
|
|
31
|
+
) {
|
|
32
|
+
self.key = key
|
|
33
|
+
self.title = title
|
|
34
|
+
self.badge = badge
|
|
35
|
+
self.sfSymbol = sfSymbol
|
|
36
|
+
self.focusedSfSymbol = focusedSfSymbol
|
|
37
|
+
self.activeTintColor = activeTintColor
|
|
38
|
+
self.iconRenderingMode = iconRenderingMode
|
|
39
|
+
self.hidden = hidden
|
|
40
|
+
self.testID = testID
|
|
41
|
+
self.role = TabBarRole(rawValue: role ?? "")
|
|
42
|
+
self.preventsDefault = preventsDefault
|
|
43
|
+
super.init()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@objc public protocol TabViewProviderDelegate {
|
|
48
|
+
func onPageSelected(key: String, reactTag: NSNumber?)
|
|
49
|
+
func onLongPress(key: String, reactTag: NSNumber?)
|
|
50
|
+
func onTabBarMeasured(height: Int, reactTag: NSNumber?)
|
|
51
|
+
func onLayout(size: CGSize, reactTag: NSNumber?)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@objc public class TabViewProvider: PlatformView {
|
|
55
|
+
private var imageLoader: RCTImageLoaderProtocol?
|
|
56
|
+
private weak var delegate: TabViewProviderDelegate?
|
|
57
|
+
private var props = TabViewProps()
|
|
58
|
+
private var hostingController: TabViewHostingController?
|
|
59
|
+
private var appearanceFixScheduled = false
|
|
60
|
+
private var coalescingKey: UInt16 = 0
|
|
61
|
+
private var iconSize = CGSize(width: 27, height: 27)
|
|
62
|
+
|
|
63
|
+
@objc var onPageSelected: RCTDirectEventBlock?
|
|
64
|
+
|
|
65
|
+
@objc var onTabLongPress: RCTDirectEventBlock?
|
|
66
|
+
@objc var onTabBarMeasured: RCTDirectEventBlock?
|
|
67
|
+
@objc var onNativeLayout: RCTDirectEventBlock?
|
|
68
|
+
|
|
69
|
+
@objc public var icons: NSArray? {
|
|
70
|
+
didSet {
|
|
71
|
+
loadIcons(icons, focused: false)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@objc public var focusedIcons: NSArray? {
|
|
76
|
+
didSet {
|
|
77
|
+
loadIcons(focusedIcons, focused: true)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@objc public var sidebarAdaptable: Bool = false {
|
|
82
|
+
didSet {
|
|
83
|
+
props.sidebarAdaptable = sidebarAdaptable
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@objc public var disablePageAnimations: Bool = false {
|
|
88
|
+
didSet {
|
|
89
|
+
props.disablePageAnimations = disablePageAnimations
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@objc public var labeled: Bool = false {
|
|
94
|
+
didSet {
|
|
95
|
+
props.labeled = labeled
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@objc public var selectedPage: NSString? {
|
|
100
|
+
didSet {
|
|
101
|
+
props.selectedPage = selectedPage as? String
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc public var hapticFeedbackEnabled: Bool = false {
|
|
106
|
+
didSet {
|
|
107
|
+
props.hapticFeedbackEnabled = hapticFeedbackEnabled
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@objc public var layoutDirection: NSString? {
|
|
112
|
+
didSet {
|
|
113
|
+
props.layoutDirection = layoutDirection as? String
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
@objc public var scrollEdgeAppearance: NSString? {
|
|
117
|
+
didSet {
|
|
118
|
+
props.scrollEdgeAppearance = scrollEdgeAppearance as? String
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@objc public var minimizeBehavior: NSString? {
|
|
123
|
+
didSet {
|
|
124
|
+
props.minimizeBehavior = MinimizeBehavior(rawValue: minimizeBehavior as? String ?? "")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc public var translucent: Bool = true {
|
|
129
|
+
didSet {
|
|
130
|
+
props.translucent = translucent
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@objc public var barTintColor: PlatformColor? {
|
|
135
|
+
didSet {
|
|
136
|
+
props.barTintColor = barTintColor
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc public var activeTintColor: PlatformColor? {
|
|
141
|
+
didSet {
|
|
142
|
+
props.activeTintColor = activeTintColor
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc public var inactiveTintColor: PlatformColor? {
|
|
147
|
+
didSet {
|
|
148
|
+
props.inactiveTintColor = inactiveTintColor
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@objc public var experimentalBakedTintColors: Bool = false {
|
|
153
|
+
didSet {
|
|
154
|
+
props.experimentalBakedTintColors = experimentalBakedTintColors
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@objc public var fontFamily: NSString? {
|
|
159
|
+
didSet {
|
|
160
|
+
props.fontFamily = fontFamily as? String
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@objc public var fontWeight: NSString? {
|
|
165
|
+
didSet {
|
|
166
|
+
props.fontWeight = fontWeight as? String
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@objc public var fontSize: NSNumber? {
|
|
171
|
+
didSet {
|
|
172
|
+
props.fontSize = fontSize as? Int
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@objc public var tabBarHidden: Bool = false {
|
|
177
|
+
didSet {
|
|
178
|
+
props.tabBarHidden = tabBarHidden
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@objc public var itemsData: [TabInfo] = [] {
|
|
183
|
+
didSet {
|
|
184
|
+
props.items = itemsData
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@objc public convenience init(delegate: TabViewProviderDelegate) {
|
|
189
|
+
self.init()
|
|
190
|
+
self.delegate = delegate
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@objc public func setImageLoader(_ imageLoader: RCTImageLoader) {
|
|
194
|
+
self.imageLoader = imageLoader
|
|
195
|
+
loadIcons(icons, focused: false)
|
|
196
|
+
loadIcons(focusedIcons, focused: true)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
override public func didUpdateReactSubviews() {
|
|
200
|
+
props.children = reactSubviews().map(IdentifiablePlatformView.init)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#if os(macOS)
|
|
204
|
+
override public func layout() {
|
|
205
|
+
super.layout()
|
|
206
|
+
setupView()
|
|
207
|
+
}
|
|
208
|
+
#else
|
|
209
|
+
override public func layoutSubviews() {
|
|
210
|
+
super.layoutSubviews()
|
|
211
|
+
setupView()
|
|
212
|
+
ensureHostingControllerAppeared()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
UIKit forwards appearance callbacks to a child controller added while its
|
|
217
|
+
parent is on screen, but not to one added while the parent is still
|
|
218
|
+
transitioning in, e.g. when the tab view mounts during a native-stack
|
|
219
|
+
replace animation. The host then stays "disappeared" and its view keeps
|
|
220
|
+
zero safe-area insets, so the tab bar and the tabs' navigation bars ignore
|
|
221
|
+
the notch and home indicator. Runs the missing transition once the parent
|
|
222
|
+
has settled.
|
|
223
|
+
*/
|
|
224
|
+
private func ensureHostingControllerAppeared() {
|
|
225
|
+
guard let host = hostingController, !host.hasAppeared, !appearanceFixScheduled, window != nil else {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
appearanceFixScheduled = true
|
|
229
|
+
if let coordinator = host.parent?.transitionCoordinator {
|
|
230
|
+
coordinator.animate(alongsideTransition: nil) { [weak self] _ in
|
|
231
|
+
self?.appearanceFixScheduled = false
|
|
232
|
+
self?.ensureHostingControllerAppeared()
|
|
233
|
+
}
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
// Deferred a run-loop turn so an appearance transition UIKit is about to
|
|
237
|
+
// forward itself takes precedence over the manual one.
|
|
238
|
+
DispatchQueue.main.async { [weak self] in
|
|
239
|
+
guard let self, let host = self.hostingController else { return }
|
|
240
|
+
self.appearanceFixScheduled = false
|
|
241
|
+
guard !host.hasAppeared, self.window != nil, let parent = host.parent else { return }
|
|
242
|
+
if parent.transitionCoordinator != nil {
|
|
243
|
+
self.ensureHostingControllerAppeared()
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
host.beginAppearanceTransition(true, animated: false)
|
|
247
|
+
host.endAppearanceTransition()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
#endif
|
|
251
|
+
|
|
252
|
+
private func setupView() {
|
|
253
|
+
if self.hostingController != nil {
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
self.hostingController = TabViewHostingController(rootView: TabViewImpl(props: props) { key in
|
|
258
|
+
self.delegate?.onPageSelected(key: key, reactTag: self.reactTag)
|
|
259
|
+
} onLongPress: { key in
|
|
260
|
+
self.delegate?.onLongPress(key: key, reactTag: self.reactTag)
|
|
261
|
+
} onLayout: { size in
|
|
262
|
+
self.delegate?.onLayout(size: size, reactTag: self.reactTag)
|
|
263
|
+
} onTabBarMeasured: { height in
|
|
264
|
+
self.delegate?.onTabBarMeasured(height: height, reactTag: self.reactTag)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
if let hostingController = self.hostingController, let parentViewController = reactViewController() {
|
|
268
|
+
parentViewController.addChild(hostingController)
|
|
269
|
+
#if !os(macOS)
|
|
270
|
+
hostingController.view.backgroundColor = .clear
|
|
271
|
+
#endif
|
|
272
|
+
addSubview(hostingController.view)
|
|
273
|
+
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
274
|
+
hostingController.view.pinEdges(to: self)
|
|
275
|
+
#if !os(macOS)
|
|
276
|
+
hostingController.didMove(toParent: parentViewController)
|
|
277
|
+
#endif
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@objc(insertChild:atIndex:)
|
|
282
|
+
public func insertChild(_ child: PlatformView, at index: Int) {
|
|
283
|
+
guard index >= 0 && index <= props.children.count else {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
props.children.insert(IdentifiablePlatformView(child), at: index)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
@objc(removeChildAtIndex:)
|
|
290
|
+
public func removeChild(at index: Int) {
|
|
291
|
+
guard index >= 0 && index < props.children.count else {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
props.children.remove(at: index)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private func loadIcons(_ icons: NSArray?, focused: Bool) {
|
|
298
|
+
guard let imageLoader else { return }
|
|
299
|
+
|
|
300
|
+
// TODO: Diff the arrays and update only changed items.
|
|
301
|
+
// Now if the user passes `unfocusedIcon` we update every item.
|
|
302
|
+
if let imageSources = icons as? [RCTImageSource?] {
|
|
303
|
+
for (index, imageSource) in imageSources.enumerated() {
|
|
304
|
+
guard let imageSource else { continue }
|
|
305
|
+
// Images in the app bundle, including asset catalog images referenced by
|
|
306
|
+
// name (`{ uri: 'tab_home' }`), are loaded right here so the first frame
|
|
307
|
+
// shows them. The image loader only handles what has to be fetched or
|
|
308
|
+
// decoded asynchronously.
|
|
309
|
+
if let url = imageSource.request.url, url.isFileURL, let image = RCTImageFromLocalAssetURL(url) {
|
|
310
|
+
setIcon(image, at: index, focused: focused)
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
imageLoader.loadImage(
|
|
314
|
+
with: imageSource.request,
|
|
315
|
+
size: imageSource.size,
|
|
316
|
+
scale: imageSource.scale,
|
|
317
|
+
clipped: true,
|
|
318
|
+
resizeMode: RCTResizeMode.contain,
|
|
319
|
+
progressBlock: { _, _ in },
|
|
320
|
+
partialLoad: { _ in },
|
|
321
|
+
completionBlock: { error, image in
|
|
322
|
+
if error != nil {
|
|
323
|
+
print("[TabView] Error loading image: \(error!.localizedDescription)")
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
guard let image else { return }
|
|
327
|
+
DispatchQueue.main.async { [weak self] in
|
|
328
|
+
self?.setIcon(image, at: index, focused: focused)
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private func setIcon(_ image: PlatformImage, at index: Int, focused: Bool) {
|
|
336
|
+
var icon = image.resizeImageTo(size: iconSize)
|
|
337
|
+
#if os(iOS)
|
|
338
|
+
if props.experimentalBakedTintColors {
|
|
339
|
+
icon = icon?.withRenderingMode(.alwaysTemplate)
|
|
340
|
+
}
|
|
341
|
+
#endif
|
|
342
|
+
if focused {
|
|
343
|
+
props.focusedIcons[index] = icon
|
|
344
|
+
} else {
|
|
345
|
+
props.icons[index] = icon
|
|
346
|
+
}
|
|
347
|
+
props.iconsRevision += 1
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#if os(macOS)
|
|
352
|
+
typealias TabViewHostingController = NSHostingController<TabViewImpl>
|
|
353
|
+
#else
|
|
354
|
+
/// Tracks appearance so `TabViewProvider` can detect a host UIKit never transitioned in.
|
|
355
|
+
final class TabViewHostingController: UIHostingController<TabViewImpl> {
|
|
356
|
+
private(set) var hasAppeared = false
|
|
357
|
+
|
|
358
|
+
override func viewDidAppear(_ animated: Bool) {
|
|
359
|
+
super.viewDidAppear(animated)
|
|
360
|
+
hasAppeared = true
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
override func viewDidDisappear(_ animated: Bool) {
|
|
364
|
+
super.viewDidDisappear(animated)
|
|
365
|
+
hasAppeared = false
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
#endif
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import BottomAccessoryViewNativeComponent from './BottomAccessoryViewNativeComponent';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
const defaultStyle = {
|
|
7
|
+
position: 'absolute',
|
|
8
|
+
top: 0,
|
|
9
|
+
left: 0
|
|
10
|
+
};
|
|
11
|
+
export const BottomAccessoryView = props => {
|
|
12
|
+
const {
|
|
13
|
+
renderBottomAccessoryView
|
|
14
|
+
} = props;
|
|
15
|
+
const [bottomAccessoryDimensions, setBottomAccessoryDimensions] = React.useState({
|
|
16
|
+
width: '100%',
|
|
17
|
+
height: '100%'
|
|
18
|
+
});
|
|
19
|
+
const [placement, setPlacement] = React.useState('none');
|
|
20
|
+
const handleNativeLayout = React.useCallback(({
|
|
21
|
+
nativeEvent: {
|
|
22
|
+
width,
|
|
23
|
+
height
|
|
24
|
+
}
|
|
25
|
+
}) => {
|
|
26
|
+
setBottomAccessoryDimensions({
|
|
27
|
+
width,
|
|
28
|
+
height
|
|
29
|
+
});
|
|
30
|
+
}, [setBottomAccessoryDimensions]);
|
|
31
|
+
const handlePlacementChanged = React.useCallback(({
|
|
32
|
+
nativeEvent
|
|
33
|
+
}) => {
|
|
34
|
+
if (nativeEvent.placement === 'inline' || nativeEvent.placement === 'expanded' || nativeEvent.placement === 'none') {
|
|
35
|
+
setPlacement(nativeEvent.placement);
|
|
36
|
+
}
|
|
37
|
+
}, [setPlacement]);
|
|
38
|
+
return /*#__PURE__*/_jsx(BottomAccessoryViewNativeComponent, {
|
|
39
|
+
style: [defaultStyle, bottomAccessoryDimensions],
|
|
40
|
+
onNativeLayout: handleNativeLayout,
|
|
41
|
+
onPlacementChanged: handlePlacementChanged,
|
|
42
|
+
children: renderBottomAccessoryView({
|
|
43
|
+
placement
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=BottomAccessoryView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","BottomAccessoryViewNativeComponent","jsx","_jsx","defaultStyle","position","top","left","BottomAccessoryView","props","renderBottomAccessoryView","bottomAccessoryDimensions","setBottomAccessoryDimensions","useState","width","height","placement","setPlacement","handleNativeLayout","useCallback","nativeEvent","handlePlacementChanged","style","onNativeLayout","onPlacementChanged","children"],"sourceRoot":"../../src","sources":["BottomAccessoryView.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AAEzB,OAAOC,kCAAkC,MAGlC,sCAAsC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE9C,MAAMC,YAAuB,GAAG;EAC9BC,QAAQ,EAAE,UAAU;EACpBC,GAAG,EAAE,CAAC;EACNC,IAAI,EAAE;AACR,CAAC;AAQD,OAAO,MAAMC,mBAAmB,GAAIC,KAA+B,IAAK;EACtE,MAAM;IAAEC;EAA0B,CAAC,GAAGD,KAAK;EAC3C,MAAM,CAACE,yBAAyB,EAAEC,4BAA4B,CAAC,GAC7DZ,KAAK,CAACa,QAAQ,CAEZ;IAAEC,KAAK,EAAE,MAAM;IAAEC,MAAM,EAAE;EAAO,CAAC,CAAC;EACtC,MAAM,CAACC,SAAS,EAAEC,YAAY,CAAC,GAAGjB,KAAK,CAACa,QAAQ,CAE9C,MAAM,CAAC;EAET,MAAMK,kBAAkB,GAAGlB,KAAK,CAACmB,WAAW,CAC1C,CAAC;IAAEC,WAAW,EAAE;MAAEN,KAAK;MAAEC;IAAO;EAAmC,CAAC,KAAK;IACvEH,4BAA4B,CAAC;MAAEE,KAAK;MAAEC;IAAO,CAAC,CAAC;EACjD,CAAC,EACD,CAACH,4BAA4B,CAC/B,CAAC;EAED,MAAMS,sBAAsB,GAAGrB,KAAK,CAACmB,WAAW,CAC9C,CAAC;IAAEC;EAAiD,CAAC,KAAK;IACxD,IACEA,WAAW,CAACJ,SAAS,KAAK,QAAQ,IAClCI,WAAW,CAACJ,SAAS,KAAK,UAAU,IACpCI,WAAW,CAACJ,SAAS,KAAK,MAAM,EAChC;MACAC,YAAY,CAACG,WAAW,CAACJ,SAAS,CAAC;IACrC;EACF,CAAC,EACD,CAACC,YAAY,CACf,CAAC;EAED,oBACEd,IAAA,CAACF,kCAAkC;IACjCqB,KAAK,EAAE,CAAClB,YAAY,EAAEO,yBAAyB,CAAE;IACjDY,cAAc,EAAEL,kBAAmB;IACnCM,kBAAkB,EAAEH,sBAAuB;IAAAI,QAAA,EAE1Cf,yBAAyB,CAAC;MAAEM;IAAU,CAAC;EAAC,CACP,CAAC;AAEzC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { codegenNativeComponent } from 'react-native';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
import type {
|
|
4
|
+
DirectEventHandler,
|
|
5
|
+
Double,
|
|
6
|
+
} from 'react-native/Libraries/Types/CodegenTypes';
|
|
7
|
+
|
|
8
|
+
export type OnNativeLayout = Readonly<{
|
|
9
|
+
width: Double;
|
|
10
|
+
height: Double;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export type OnPlacementChanged = Readonly<{
|
|
14
|
+
placement: string;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export interface BottomAccessoryViewNativeProps extends ViewProps {
|
|
18
|
+
onNativeLayout?: DirectEventHandler<OnNativeLayout>;
|
|
19
|
+
onPlacementChanged?: DirectEventHandler<OnPlacementChanged>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default codegenNativeComponent<BottomAccessoryViewNativeProps>(
|
|
23
|
+
'BottomAccessoryView',
|
|
24
|
+
{
|
|
25
|
+
excludedPlatforms: ['android'],
|
|
26
|
+
}
|
|
27
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Freeze } from 'react-freeze';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
// Animation delay for freeze effect.
|
|
7
|
+
const ANIMATION_DELAY = 200;
|
|
8
|
+
|
|
9
|
+
// This component delays the freeze effect by animation delay.
|
|
10
|
+
// So that the screen is not frozen immediately causing background flash.
|
|
11
|
+
function DelayedFreeze({
|
|
12
|
+
freeze,
|
|
13
|
+
children
|
|
14
|
+
}) {
|
|
15
|
+
// flag used for determining whether freeze should be enabled
|
|
16
|
+
const [freezeState, setFreezeState] = React.useState(false);
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const id = setTimeout(() => {
|
|
19
|
+
setFreezeState(freeze);
|
|
20
|
+
}, ANIMATION_DELAY);
|
|
21
|
+
return () => {
|
|
22
|
+
clearTimeout(id);
|
|
23
|
+
};
|
|
24
|
+
}, [freeze]);
|
|
25
|
+
return /*#__PURE__*/_jsx(Freeze, {
|
|
26
|
+
freeze: freeze ? freezeState : false,
|
|
27
|
+
children: children
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export default DelayedFreeze;
|
|
31
|
+
//# sourceMappingURL=DelayedFreeze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","Freeze","jsx","_jsx","ANIMATION_DELAY","DelayedFreeze","freeze","children","freezeState","setFreezeState","useState","useEffect","id","setTimeout","clearTimeout"],"sourceRoot":"../../src","sources":["DelayedFreeze.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,MAAM,QAAQ,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOtC;AACA,MAAMC,eAAe,GAAG,GAAG;;AAE3B;AACA;AACA,SAASC,aAAaA,CAAC;EAAEC,MAAM;EAAEC;AAA6B,CAAC,EAAE;EAC/D;EACA,MAAM,CAACC,WAAW,EAAEC,cAAc,CAAC,GAAGT,KAAK,CAACU,QAAQ,CAAC,KAAK,CAAC;EAE3DV,KAAK,CAACW,SAAS,CAAC,MAAM;IACpB,MAAMC,EAAE,GAAGC,UAAU,CAAC,MAAM;MAC1BJ,cAAc,CAACH,MAAM,CAAC;IACxB,CAAC,EAAEF,eAAe,CAAC;IAEnB,OAAO,MAAM;MACXU,YAAY,CAACF,EAAE,CAAC;IAClB,CAAC;EACH,CAAC,EAAE,CAACN,MAAM,CAAC,CAAC;EAEZ,oBAAOH,IAAA,CAACF,MAAM;IAACK,MAAM,EAAEA,MAAM,GAAGE,WAAW,GAAG,KAAM;IAAAD,QAAA,EAAEA;EAAQ,CAAS,CAAC;AAC1E;AAEA,eAAeF,aAAa","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSVGDecoder.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAIpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,YAAY,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
const SceneComponent = /*#__PURE__*/React.memo(({
|
|
6
|
+
component,
|
|
7
|
+
...rest
|
|
8
|
+
}) => {
|
|
9
|
+
return /*#__PURE__*/React.createElement(component, rest);
|
|
10
|
+
});
|
|
11
|
+
SceneComponent.displayName = 'SceneComponent';
|
|
12
|
+
export function SceneMap(scenes) {
|
|
13
|
+
return ({
|
|
14
|
+
route,
|
|
15
|
+
jumpTo
|
|
16
|
+
}) => {
|
|
17
|
+
const component = scenes[route.key];
|
|
18
|
+
if (!component) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return /*#__PURE__*/_jsx(SceneComponent, {
|
|
22
|
+
jumpTo: jumpTo,
|
|
23
|
+
component: component,
|
|
24
|
+
route: route
|
|
25
|
+
}, route.key);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=SceneMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","jsx","_jsx","SceneComponent","memo","component","rest","createElement","displayName","SceneMap","scenes","route","jumpTo","key"],"sourceRoot":"../../src","sources":["SceneMap.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAO/B,MAAMC,cAAc,gBAAGH,KAAK,CAACI,IAAI,CAC/B,CAAiE;EAC/DC,SAAS;EACT,GAAGC;AACF,CAAC,KAAK;EACP,oBAAON,KAAK,CAACO,aAAa,CAACF,SAAS,EAAEC,IAAI,CAAC;AAC7C,CACF,CAAC;AAEDH,cAAc,CAACK,WAAW,GAAG,gBAAgB;AAE7C,OAAO,SAASC,QAAQA,CAAIC,MAAiD,EAAE;EAC7E,OAAO,CAAC;IAAEC,KAAK;IAAEC;EAAmB,CAAC,KAAK;IACxC,MAAMP,SAAS,GAAGK,MAAM,CAACC,KAAK,CAACE,GAAG,CAAC;IACnC,IAAI,CAACR,SAAS,EAAE;MACd,OAAO,IAAI;IACb;IACA,oBACEH,IAAA,CAACC,cAAc;MAEbS,MAAM,EAAEA,MAAO;MACfP,SAAS,EAAEA,SAAU;MACrBM,KAAK,EAAEA;IAAM,GAHRA,KAAK,CAACE,GAIZ,CAAC;EAEN,CAAC;AACH","ignoreList":[]}
|