@vxrn/native 1.12.8-1774390675831

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 (176) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/android/build.gradle +32 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/java/dev/vxrn/native/VxrnNativeModule.kt +117 -0
  5. package/android/src/main/java/dev/vxrn/native/VxrnNativePackage.kt +16 -0
  6. package/dist/cjs/color/android.dynamic.types.cjs +16 -0
  7. package/dist/cjs/color/android.dynamic.types.native.js +19 -0
  8. package/dist/cjs/color/android.dynamic.types.native.js.map +1 -0
  9. package/dist/cjs/color/android.material.types.cjs +16 -0
  10. package/dist/cjs/color/android.material.types.native.js +19 -0
  11. package/dist/cjs/color/android.material.types.native.js.map +1 -0
  12. package/dist/cjs/color/index.cjs +66 -0
  13. package/dist/cjs/color/index.native.js +69 -0
  14. package/dist/cjs/color/index.native.js.map +1 -0
  15. package/dist/cjs/color/ios.types.cjs +16 -0
  16. package/dist/cjs/color/ios.types.native.js +19 -0
  17. package/dist/cjs/color/ios.types.native.js.map +1 -0
  18. package/dist/cjs/color/materialColor.cjs +32 -0
  19. package/dist/cjs/color/materialColor.native.js +43 -0
  20. package/dist/cjs/color/materialColor.native.js.map +1 -0
  21. package/dist/cjs/index.cjs +37 -0
  22. package/dist/cjs/index.native.js +40 -0
  23. package/dist/cjs/index.native.js.map +1 -0
  24. package/dist/cjs/menu/index.cjs +28 -0
  25. package/dist/cjs/menu/index.native.js +47 -0
  26. package/dist/cjs/menu/index.native.js.map +1 -0
  27. package/dist/cjs/menu/types.cjs +16 -0
  28. package/dist/cjs/menu/types.native.js +19 -0
  29. package/dist/cjs/menu/types.native.js.map +1 -0
  30. package/dist/cjs/split-view/index.cjs +26 -0
  31. package/dist/cjs/split-view/index.native.js +29 -0
  32. package/dist/cjs/split-view/index.native.js.map +1 -0
  33. package/dist/cjs/split-view/split-view.cjs +80 -0
  34. package/dist/cjs/split-view/split-view.native.js +88 -0
  35. package/dist/cjs/split-view/split-view.native.js.map +1 -0
  36. package/dist/cjs/toolbar/index.cjs +32 -0
  37. package/dist/cjs/toolbar/index.native.js +60 -0
  38. package/dist/cjs/toolbar/index.native.js.map +1 -0
  39. package/dist/cjs/toolbar/types.cjs +16 -0
  40. package/dist/cjs/toolbar/types.native.js +19 -0
  41. package/dist/cjs/toolbar/types.native.js.map +1 -0
  42. package/dist/cjs/zoom/index.cjs +36 -0
  43. package/dist/cjs/zoom/index.native.js +65 -0
  44. package/dist/cjs/zoom/index.native.js.map +1 -0
  45. package/dist/cjs/zoom/types.cjs +16 -0
  46. package/dist/cjs/zoom/types.native.js +19 -0
  47. package/dist/cjs/zoom/types.native.js.map +1 -0
  48. package/dist/esm/color/android.dynamic.types.mjs +2 -0
  49. package/dist/esm/color/android.dynamic.types.mjs.map +1 -0
  50. package/dist/esm/color/android.dynamic.types.native.js +2 -0
  51. package/dist/esm/color/android.dynamic.types.native.js.map +1 -0
  52. package/dist/esm/color/android.material.types.mjs +2 -0
  53. package/dist/esm/color/android.material.types.mjs.map +1 -0
  54. package/dist/esm/color/android.material.types.native.js +2 -0
  55. package/dist/esm/color/android.material.types.native.js.map +1 -0
  56. package/dist/esm/color/index.mjs +42 -0
  57. package/dist/esm/color/index.mjs.map +1 -0
  58. package/dist/esm/color/index.native.js +42 -0
  59. package/dist/esm/color/index.native.js.map +1 -0
  60. package/dist/esm/color/ios.types.mjs +2 -0
  61. package/dist/esm/color/ios.types.mjs.map +1 -0
  62. package/dist/esm/color/ios.types.native.js +2 -0
  63. package/dist/esm/color/ios.types.native.js.map +1 -0
  64. package/dist/esm/color/materialColor.mjs +8 -0
  65. package/dist/esm/color/materialColor.mjs.map +1 -0
  66. package/dist/esm/color/materialColor.native.js +16 -0
  67. package/dist/esm/color/materialColor.native.js.map +1 -0
  68. package/dist/esm/index.mjs +7 -0
  69. package/dist/esm/index.mjs.map +1 -0
  70. package/dist/esm/index.native.js +7 -0
  71. package/dist/esm/index.native.js.map +1 -0
  72. package/dist/esm/menu/index.mjs +5 -0
  73. package/dist/esm/menu/index.mjs.map +1 -0
  74. package/dist/esm/menu/index.native.js +21 -0
  75. package/dist/esm/menu/index.native.js.map +1 -0
  76. package/dist/esm/menu/types.mjs +2 -0
  77. package/dist/esm/menu/types.mjs.map +1 -0
  78. package/dist/esm/menu/types.native.js +2 -0
  79. package/dist/esm/menu/types.native.js.map +1 -0
  80. package/dist/esm/split-view/index.mjs +3 -0
  81. package/dist/esm/split-view/index.mjs.map +1 -0
  82. package/dist/esm/split-view/index.native.js +3 -0
  83. package/dist/esm/split-view/index.native.js.map +1 -0
  84. package/dist/esm/split-view/split-view.mjs +46 -0
  85. package/dist/esm/split-view/split-view.mjs.map +1 -0
  86. package/dist/esm/split-view/split-view.native.js +51 -0
  87. package/dist/esm/split-view/split-view.native.js.map +1 -0
  88. package/dist/esm/toolbar/index.mjs +8 -0
  89. package/dist/esm/toolbar/index.mjs.map +1 -0
  90. package/dist/esm/toolbar/index.native.js +33 -0
  91. package/dist/esm/toolbar/index.native.js.map +1 -0
  92. package/dist/esm/toolbar/types.mjs +2 -0
  93. package/dist/esm/toolbar/types.mjs.map +1 -0
  94. package/dist/esm/toolbar/types.native.js +2 -0
  95. package/dist/esm/toolbar/types.native.js.map +1 -0
  96. package/dist/esm/zoom/index.mjs +11 -0
  97. package/dist/esm/zoom/index.mjs.map +1 -0
  98. package/dist/esm/zoom/index.native.js +37 -0
  99. package/dist/esm/zoom/index.native.js.map +1 -0
  100. package/dist/esm/zoom/types.mjs +2 -0
  101. package/dist/esm/zoom/types.mjs.map +1 -0
  102. package/dist/esm/zoom/types.native.js +2 -0
  103. package/dist/esm/zoom/types.native.js.map +1 -0
  104. package/ios/Menu/MenuActionView.swift +339 -0
  105. package/ios/Menu/VxrnMenuActionManager.m +47 -0
  106. package/ios/Toolbar/FontUtils.swift +46 -0
  107. package/ios/Toolbar/ToolbarHostView.swift +138 -0
  108. package/ios/Toolbar/ToolbarItemView.swift +334 -0
  109. package/ios/Toolbar/VxrnToolbarHostManager.m +20 -0
  110. package/ios/Toolbar/VxrnToolbarItemManager.m +41 -0
  111. package/ios/VxrnNative-Bridging-Header.h +7 -0
  112. package/ios/VxrnNative.podspec +31 -0
  113. package/ios/ZoomTransition/VxrnZoomAlignmentManager.m +22 -0
  114. package/ios/ZoomTransition/VxrnZoomEnablerManager.m +23 -0
  115. package/ios/ZoomTransition/VxrnZoomSourceManager.m +24 -0
  116. package/ios/ZoomTransition/ZoomTransition.swift +384 -0
  117. package/package.json +82 -0
  118. package/react-native.config.js +14 -0
  119. package/src/color/android.dynamic.types.ts +384 -0
  120. package/src/color/android.material.types.ts +291 -0
  121. package/src/color/index.native.ts +75 -0
  122. package/src/color/index.ts +75 -0
  123. package/src/color/ios.types.ts +156 -0
  124. package/src/color/materialColor.native.ts +15 -0
  125. package/src/color/materialColor.ts +7 -0
  126. package/src/index.ts +20 -0
  127. package/src/menu/index.native.tsx +28 -0
  128. package/src/menu/index.ts +7 -0
  129. package/src/menu/types.ts +32 -0
  130. package/src/split-view/index.ts +2 -0
  131. package/src/split-view/split-view.tsx +80 -0
  132. package/src/toolbar/index.native.tsx +39 -0
  133. package/src/toolbar/index.ts +11 -0
  134. package/src/toolbar/types.ts +39 -0
  135. package/src/zoom/index.native.tsx +56 -0
  136. package/src/zoom/index.ts +28 -0
  137. package/src/zoom/types.ts +30 -0
  138. package/tsconfig.json +7 -0
  139. package/types/color/android.dynamic.types.d.ts +384 -0
  140. package/types/color/android.dynamic.types.d.ts.map +1 -0
  141. package/types/color/android.material.types.d.ts +291 -0
  142. package/types/color/android.material.types.d.ts.map +1 -0
  143. package/types/color/index.d.ts +25 -0
  144. package/types/color/index.d.ts.map +1 -0
  145. package/types/color/index.native.d.ts +25 -0
  146. package/types/color/index.native.d.ts.map +1 -0
  147. package/types/color/ios.types.d.ts +156 -0
  148. package/types/color/ios.types.d.ts.map +1 -0
  149. package/types/color/materialColor.d.ts +3 -0
  150. package/types/color/materialColor.d.ts.map +1 -0
  151. package/types/color/materialColor.native.d.ts +3 -0
  152. package/types/color/materialColor.native.d.ts.map +1 -0
  153. package/types/index.d.ts +10 -0
  154. package/types/index.d.ts.map +1 -0
  155. package/types/menu/index.d.ts +4 -0
  156. package/types/menu/index.d.ts.map +1 -0
  157. package/types/menu/index.native.d.ts +4 -0
  158. package/types/menu/index.native.d.ts.map +1 -0
  159. package/types/menu/types.d.ts +32 -0
  160. package/types/menu/types.d.ts.map +1 -0
  161. package/types/split-view/index.d.ts +3 -0
  162. package/types/split-view/index.d.ts.map +1 -0
  163. package/types/split-view/split-view.d.ts +19 -0
  164. package/types/split-view/split-view.d.ts.map +1 -0
  165. package/types/toolbar/index.d.ts +5 -0
  166. package/types/toolbar/index.d.ts.map +1 -0
  167. package/types/toolbar/index.native.d.ts +5 -0
  168. package/types/toolbar/index.native.d.ts.map +1 -0
  169. package/types/toolbar/types.d.ts +37 -0
  170. package/types/toolbar/types.d.ts.map +1 -0
  171. package/types/zoom/index.d.ts +6 -0
  172. package/types/zoom/index.d.ts.map +1 -0
  173. package/types/zoom/index.native.d.ts +6 -0
  174. package/types/zoom/index.native.d.ts.map +1 -0
  175. package/types/zoom/types.d.ts +26 -0
  176. package/types/zoom/types.d.ts.map +1 -0
@@ -0,0 +1,339 @@
1
+ // adapted from expo-router (MIT license) - https://github.com/expo/expo
2
+ import React
3
+
4
+ import UIKit
5
+
6
+ protocol MenuUpdatable: AnyObject {
7
+ func updateMenu()
8
+ }
9
+
10
+ enum MenuElementSize: String {
11
+ case small
12
+ case medium
13
+ case large
14
+ case auto
15
+
16
+ @available(iOS 16.0, *)
17
+ func toUIMenuElementSize() -> UIMenu.ElementSize {
18
+ switch self {
19
+ case .small: return .small
20
+ case .medium: return .medium
21
+ case .large: return .large
22
+ case .auto:
23
+ if #available(iOS 17.0, *) { return .automatic }
24
+ else { return .medium }
25
+ }
26
+ }
27
+ }
28
+
29
+ @objc(MenuActionView) public
30
+ class MenuActionView: RCTView, MenuUpdatable {
31
+ // MARK: - internal identifiers (avoid UIView property conflicts)
32
+ var menuIdentifier: String = ""
33
+ var menuLabel: String?
34
+ var menuDisabled: Bool = false
35
+ var menuTitleStyle: TitleStyle?
36
+ var menuSharesBackground: Bool?
37
+ var menuHidesSharedBackground: Bool?
38
+ var menuCustomTintColor: UIColor?
39
+ var menuBarButtonItemStyle: UIBarButtonItem.Style?
40
+ var menuAccessibilityLabel: String?
41
+ var menuAccessibilityHint: String?
42
+
43
+ @objc var onSelected: RCTDirectEventBlock?
44
+ weak var parentMenuUpdatable: MenuUpdatable?
45
+
46
+ private var baseUiAction: UIAction
47
+ private var menuAction: UIMenu
48
+ private var subActions: [MenuActionView] = []
49
+
50
+ var isMenuAction: Bool { return !subActions.isEmpty }
51
+ var uiAction: UIMenuElement { isMenuAction ? menuAction : baseUiAction }
52
+
53
+ // MARK: - resolved image
54
+
55
+ private var resolvedImage: UIImage?
56
+
57
+ var image: UIImage? {
58
+ if let resolvedImage = resolvedImage {
59
+ let renderingMode: UIImage.RenderingMode = _imageRenderingMode == .template ? .alwaysTemplate : .alwaysOriginal
60
+ return resolvedImage.withRenderingMode(renderingMode)
61
+ }
62
+ if let xcassetName = _xcassetName {
63
+ let renderingMode: UIImage.RenderingMode = _imageRenderingMode == .template ? .alwaysTemplate : .alwaysOriginal
64
+ return UIImage(named: xcassetName)?.withRenderingMode(renderingMode)
65
+ }
66
+ if let icon = _icon { return UIImage(systemName: icon) }
67
+ return nil
68
+ }
69
+
70
+ // MARK: - @objc props
71
+
72
+ @objc var identifier: NSString = "" {
73
+ didSet { menuIdentifier = identifier as String }
74
+ }
75
+
76
+ private var _title: String = ""
77
+ @objc var title: NSString = "" {
78
+ didSet {
79
+ _title = title as String
80
+ updateUiAction()
81
+ updateMenu()
82
+ }
83
+ }
84
+
85
+ @objc var label: NSString? {
86
+ didSet {
87
+ menuLabel = label as String?
88
+ updateMenu()
89
+ }
90
+ }
91
+
92
+ private var _icon: String?
93
+ @objc var icon: NSString? {
94
+ didSet {
95
+ _icon = icon as String?
96
+ updateUiAction()
97
+ updateMenu()
98
+ }
99
+ }
100
+
101
+ private var _xcassetName: String?
102
+ @objc var xcassetName: NSString? {
103
+ didSet {
104
+ _xcassetName = xcassetName as String?
105
+ updateUiAction()
106
+ updateMenu()
107
+ }
108
+ }
109
+
110
+ @objc var imageSource: NSDictionary? {
111
+ didSet { resolveImageFromSource() }
112
+ }
113
+
114
+ private func resolveImageFromSource() {
115
+ guard let source = imageSource, let uriObj = source["uri"] else {
116
+ resolvedImage = nil
117
+ updateUiAction()
118
+ updateMenu()
119
+ return
120
+ }
121
+ guard let uri = uriObj as? String, !uri.isEmpty, let url = URL(string: uri) else {
122
+ resolvedImage = nil
123
+ updateUiAction()
124
+ updateMenu()
125
+ return
126
+ }
127
+ URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
128
+ guard let data = data, let image = UIImage(data: data) else { return }
129
+ DispatchQueue.main.async {
130
+ self?.resolvedImage = image
131
+ self?.updateUiAction()
132
+ self?.updateMenu()
133
+ }
134
+ }.resume()
135
+ }
136
+
137
+ private var _imageRenderingMode: ImageRenderingMode?
138
+ @objc var imageRenderingMode: NSString? {
139
+ didSet {
140
+ if let m = imageRenderingMode as String? { _imageRenderingMode = ImageRenderingMode(rawValue: m) } else { _imageRenderingMode = nil }
141
+ updateUiAction()
142
+ updateMenu()
143
+ }
144
+ }
145
+
146
+ private var _destructive: Bool?
147
+ @objc var destructive: NSNumber? {
148
+ didSet {
149
+ _destructive = destructive?.boolValue
150
+ updateUiAction()
151
+ updateMenu()
152
+ }
153
+ }
154
+
155
+ @objc var disabled: Bool = false {
156
+ didSet {
157
+ menuDisabled = disabled
158
+ updateUiAction()
159
+ updateMenu()
160
+ }
161
+ }
162
+
163
+ private var _isOn: Bool?
164
+ @objc var isOn: NSNumber? {
165
+ didSet {
166
+ _isOn = isOn?.boolValue
167
+ updateUiAction()
168
+ }
169
+ }
170
+
171
+ private var _keepPresented: Bool?
172
+ @objc var keepPresented: NSNumber? {
173
+ didSet {
174
+ _keepPresented = keepPresented?.boolValue
175
+ updateUiAction()
176
+ }
177
+ }
178
+
179
+ private var _discoverabilityLabel: String?
180
+ @objc var discoverabilityLabel: NSString? {
181
+ didSet {
182
+ _discoverabilityLabel = discoverabilityLabel as String?
183
+ updateUiAction()
184
+ }
185
+ }
186
+
187
+ private var _subtitle: String?
188
+ @objc var subtitle: NSString? {
189
+ didSet {
190
+ _subtitle = subtitle as String?
191
+ updateUiAction()
192
+ updateMenu()
193
+ }
194
+ }
195
+
196
+ @objc var singleSelection: Bool = false {
197
+ didSet { updateMenu() }
198
+ }
199
+
200
+ @objc var displayAsPalette: Bool = false {
201
+ didSet { updateMenu() }
202
+ }
203
+
204
+ @objc var displayInline: Bool = false {
205
+ didSet { updateMenu() }
206
+ }
207
+
208
+ private var _preferredElementSize: MenuElementSize?
209
+ @objc var preferredElementSize: NSString? {
210
+ didSet {
211
+ if let s = preferredElementSize as String? { _preferredElementSize = MenuElementSize(rawValue: s) } else { _preferredElementSize = nil }
212
+ updateMenu()
213
+ }
214
+ }
215
+
216
+ @objc var routerHidden: Bool = false {
217
+ didSet {
218
+ updateUiAction()
219
+ updateMenu()
220
+ }
221
+ }
222
+
223
+ @objc var titleStyleDict: NSDictionary? {
224
+ didSet {
225
+ menuTitleStyle = parseTitleStyle(titleStyleDict)
226
+ updateMenu()
227
+ }
228
+ }
229
+
230
+ @objc var sharesBackground: NSNumber? {
231
+ didSet {
232
+ menuSharesBackground = sharesBackground?.boolValue
233
+ updateMenu()
234
+ }
235
+ }
236
+
237
+ @objc var hidesSharedBackground: NSNumber? {
238
+ didSet {
239
+ menuHidesSharedBackground = hidesSharedBackground?.boolValue
240
+ updateMenu()
241
+ }
242
+ }
243
+
244
+ @objc var customTintColor: UIColor? {
245
+ didSet {
246
+ menuCustomTintColor = customTintColor
247
+ updateUiAction()
248
+ updateMenu()
249
+ }
250
+ }
251
+
252
+ @objc var barButtonItemStyleProp: NSString? {
253
+ didSet {
254
+ if let s = barButtonItemStyleProp as String? {
255
+ menuBarButtonItemStyle = BarItemStyle(rawValue: s)?.toUIBarButtonItemStyle()
256
+ } else {
257
+ menuBarButtonItemStyle = nil
258
+ }
259
+ updateMenu()
260
+ }
261
+ }
262
+
263
+ @objc var accessibilityLabelForMenu: NSString? {
264
+ didSet {
265
+ menuAccessibilityLabel = accessibilityLabelForMenu as String?
266
+ updateMenu()
267
+ }
268
+ }
269
+
270
+ @objc var accessibilityHintForMenu: NSString? {
271
+ didSet {
272
+ menuAccessibilityHint = accessibilityHintForMenu as String?
273
+ updateMenu()
274
+ }
275
+ }
276
+
277
+ // MARK: - init
278
+
279
+ override init(frame: CGRect) {
280
+ baseUiAction = UIAction(title: "", handler: { _ in })
281
+ menuAction = UIMenu(title: "", image: nil, options: [], children: [])
282
+ super.init(frame: frame)
283
+ clipsToBounds = true
284
+ baseUiAction = UIAction(title: "", handler: { [weak self] _ in self?.onSelected?([:]) })
285
+ }
286
+
287
+ required init?(coder: NSCoder) {
288
+ fatalError("init(coder:) has not been implemented")
289
+ }
290
+
291
+ // MARK: - menu building
292
+
293
+ func updateMenu() {
294
+ let childActions = subActions.map { $0.uiAction }
295
+ var options: UIMenu.Options = []
296
+ if #available(iOS 17.0, *) { if displayAsPalette { options.insert(.displayAsPalette) } }
297
+ if singleSelection { options.insert(.singleSelection) }
298
+ if displayInline { options.insert(.displayInline) }
299
+ if _destructive == true { options.insert(.destructive) }
300
+ menuAction = UIMenu(title: _title, image: image, options: options, children: childActions)
301
+ if let subtitle = _subtitle { menuAction.subtitle = subtitle }
302
+ if #available(iOS 16.0, *) {
303
+ if let preferredElementSize = _preferredElementSize {
304
+ menuAction.preferredElementSize = preferredElementSize.toUIMenuElementSize()
305
+ }
306
+ }
307
+ parentMenuUpdatable?.updateMenu()
308
+ }
309
+
310
+ func updateUiAction() {
311
+ var attributes: UIMenuElement.Attributes = []
312
+ if _destructive == true { attributes.insert(.destructive) }
313
+ if disabled { attributes.insert(.disabled) }
314
+ if routerHidden { attributes.insert(.hidden) }
315
+ if #available(iOS 16.0, *) { if _keepPresented == true { attributes.insert(.keepsMenuPresented) } }
316
+ baseUiAction.title = _title
317
+ baseUiAction.image = image
318
+ baseUiAction.attributes = attributes
319
+ baseUiAction.state = _isOn == true ? .on : .off
320
+ if let subtitle = _subtitle { baseUiAction.subtitle = subtitle }
321
+ if let label = _discoverabilityLabel { baseUiAction.discoverabilityTitle = label }
322
+ parentMenuUpdatable?.updateMenu()
323
+ }
324
+
325
+ // MARK: - child management
326
+
327
+ public override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
328
+ if let childActionView = subview as? MenuActionView {
329
+ subActions.insert(childActionView, at: atIndex)
330
+ childActionView.parentMenuUpdatable = self
331
+ }
332
+ }
333
+
334
+ public override func removeReactSubview(_ subview: UIView!) {
335
+ if let childActionView = subview as? MenuActionView {
336
+ subActions.removeAll(where: { $0 === childActionView })
337
+ }
338
+ }
339
+ }
@@ -0,0 +1,47 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ #if __has_include("VxrnNative-Swift.h")
4
+ #import "VxrnNative-Swift.h"
5
+ #else
6
+ #import <VxrnNative/VxrnNative-Swift.h>
7
+ #endif
8
+
9
+ @interface VxrnMenuActionManager : RCTViewManager
10
+ @end
11
+
12
+ @implementation VxrnMenuActionManager
13
+
14
+ RCT_EXPORT_MODULE(VxrnMenuAction)
15
+
16
+ - (UIView *)view {
17
+ return [MenuActionView new];
18
+ }
19
+
20
+ RCT_EXPORT_VIEW_PROPERTY(identifier, NSString)
21
+ RCT_EXPORT_VIEW_PROPERTY(title, NSString)
22
+ RCT_EXPORT_VIEW_PROPERTY(label, NSString)
23
+ RCT_EXPORT_VIEW_PROPERTY(icon, NSString)
24
+ RCT_EXPORT_VIEW_PROPERTY(xcassetName, NSString)
25
+ RCT_EXPORT_VIEW_PROPERTY(imageSource, NSDictionary)
26
+ RCT_EXPORT_VIEW_PROPERTY(imageRenderingMode, NSString)
27
+ RCT_EXPORT_VIEW_PROPERTY(destructive, NSNumber)
28
+ RCT_EXPORT_VIEW_PROPERTY(disabled, BOOL)
29
+ RCT_EXPORT_VIEW_PROPERTY(isOn, NSNumber)
30
+ RCT_EXPORT_VIEW_PROPERTY(keepPresented, NSNumber)
31
+ RCT_EXPORT_VIEW_PROPERTY(discoverabilityLabel, NSString)
32
+ RCT_EXPORT_VIEW_PROPERTY(subtitle, NSString)
33
+ RCT_EXPORT_VIEW_PROPERTY(singleSelection, BOOL)
34
+ RCT_EXPORT_VIEW_PROPERTY(displayAsPalette, BOOL)
35
+ RCT_EXPORT_VIEW_PROPERTY(displayInline, BOOL)
36
+ RCT_EXPORT_VIEW_PROPERTY(preferredElementSize, NSString)
37
+ RCT_REMAP_VIEW_PROPERTY(hidden, routerHidden, BOOL)
38
+ RCT_REMAP_VIEW_PROPERTY(titleStyle, titleStyleDict, NSDictionary)
39
+ RCT_EXPORT_VIEW_PROPERTY(sharesBackground, NSNumber)
40
+ RCT_EXPORT_VIEW_PROPERTY(hidesSharedBackground, NSNumber)
41
+ RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor)
42
+ RCT_REMAP_VIEW_PROPERTY(barButtonItemStyle, barButtonItemStyleProp, NSString)
43
+ RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, accessibilityLabelForMenu, NSString)
44
+ RCT_REMAP_VIEW_PROPERTY(accessibilityHint, accessibilityHintForMenu, NSString)
45
+ RCT_EXPORT_VIEW_PROPERTY(onSelected, RCTDirectEventBlock)
46
+
47
+ @end
@@ -0,0 +1,46 @@
1
+ // adapted from expo-router (MIT license) - https://github.com/expo/expo
2
+ import React
3
+
4
+ import UIKit
5
+
6
+ struct FontUtils {
7
+ static func convertTitleStyleToFont(_ titleStyle: TitleStyle) -> UIFont {
8
+ let fontFamily = titleStyle.fontFamily
9
+ let fontWeight = titleStyle.fontWeight
10
+ let resolvedFontSize = resolveFontSize(titleStyle.fontSize)
11
+ if fontFamily != nil || fontWeight != nil {
12
+ return RCTFont.update(
13
+ nil, withFamily: fontFamily, size: NSNumber(value: Float(resolvedFontSize)),
14
+ weight: fontWeight, style: nil, variant: nil, scaleMultiplier: 1.0)
15
+ }
16
+ return UIFont.systemFont(ofSize: resolvedFontSize)
17
+ }
18
+
19
+ static func setTitleStyle(fromConfig titleStyle: TitleStyle, for item: UIBarButtonItem) {
20
+ var attrs: [NSAttributedString.Key: Any] = [:]
21
+ attrs[.font] = convertTitleStyleToFont(titleStyle)
22
+ if let color = titleStyle.color { attrs[.foregroundColor] = color }
23
+ item.setTitleTextAttributes(attrs, for: .normal)
24
+ item.setTitleTextAttributes(attrs, for: .highlighted)
25
+ item.setTitleTextAttributes(attrs, for: .disabled)
26
+ item.setTitleTextAttributes(attrs, for: .selected)
27
+ item.setTitleTextAttributes(attrs, for: .focused)
28
+ }
29
+
30
+ static func clearTitleStyle(for item: UIBarButtonItem) {
31
+ item.setTitleTextAttributes(nil, for: .normal)
32
+ item.setTitleTextAttributes(nil, for: .highlighted)
33
+ item.setTitleTextAttributes(nil, for: .disabled)
34
+ item.setTitleTextAttributes(nil, for: .selected)
35
+ item.setTitleTextAttributes(nil, for: .focused)
36
+ }
37
+
38
+ private static func resolveFontSize(_ fontSize: Double?) -> CGFloat {
39
+ if let fontSize = fontSize { return CGFloat(fontSize) }
40
+ #if os(tvOS)
41
+ return 17.0
42
+ #else
43
+ return UIFont.labelFontSize
44
+ #endif
45
+ }
46
+ }
@@ -0,0 +1,138 @@
1
+ // adapted from expo-router (MIT license) - https://github.com/expo/expo
2
+ import React
3
+
4
+ // RNSScreen accessed via responder chain without direct import
5
+ import UIKit
6
+
7
+ @objc(ToolbarHostView) public
8
+ class ToolbarHostView: RCTView, MenuUpdatable {
9
+ private weak var cachedController: UIViewController?
10
+ var toolbarItemsArray: [String] = []
11
+ var toolbarItemsMap: [String: ToolbarItemView] = [:]
12
+ var menuItemsMap: [String: MenuActionView] = [:]
13
+ private var hasPendingToolbarUpdate = false
14
+
15
+ private func addToolbarItemAtIndex(_ item: ToolbarItemView, index: Int) {
16
+ let identifier = item.itemIdentifier
17
+ toolbarItemsArray.insert(identifier, at: index)
18
+ toolbarItemsMap[identifier] = item
19
+ item.host = self
20
+ }
21
+
22
+ private func addMenuItemAtIndex(_ item: MenuActionView, index: Int) {
23
+ let identifier = item.menuIdentifier
24
+ toolbarItemsArray.insert(identifier, at: index)
25
+ menuItemsMap[identifier] = item
26
+ }
27
+
28
+ private func removeItemWithId(_ id: String) {
29
+ if let index = toolbarItemsArray.firstIndex(of: id) {
30
+ toolbarItemsArray.remove(at: index)
31
+ toolbarItemsMap.removeValue(forKey: id)
32
+ menuItemsMap.removeValue(forKey: id)
33
+ }
34
+ }
35
+
36
+ func updateToolbarItems() {
37
+ if hasPendingToolbarUpdate { return }
38
+ hasPendingToolbarUpdate = true
39
+ DispatchQueue.main.async { [weak self] in
40
+ guard let self = self else { return }
41
+ self.hasPendingToolbarUpdate = false
42
+ self.performToolbarUpdate()
43
+ }
44
+ }
45
+
46
+ private func performToolbarUpdate() {
47
+ if let controller = self.findViewController() {
48
+ if #available(iOS 18.0, *) {
49
+ let items = toolbarItemsArray.compactMap { identifier -> UIBarButtonItem? in
50
+ if let item = toolbarItemsMap[identifier] {
51
+ if item.routerHidden { return nil }
52
+ return item.barButtonItem
53
+ }
54
+ if let menu = menuItemsMap[identifier] {
55
+ if menu.routerHidden { return nil }
56
+ let item = UIBarButtonItem(
57
+ title: menu.menuLabel, image: menu.image, primaryAction: nil, menu: menu.uiAction as? UIMenu)
58
+ item.preferredMenuElementOrder = .fixed
59
+ if #available(iOS 26.0, *) {
60
+ if let hidesSharedBackground = menu.menuHidesSharedBackground { item.hidesSharedBackground = hidesSharedBackground }
61
+ if let sharesBackground = menu.menuSharesBackground { item.sharesBackground = sharesBackground }
62
+ }
63
+ if let titleStyle = menu.menuTitleStyle {
64
+ FontUtils.setTitleStyle(fromConfig: titleStyle, for: item)
65
+ }
66
+ item.isEnabled = !menu.menuDisabled
67
+ if let accessibilityLabel = menu.menuAccessibilityLabel { item.accessibilityLabel = accessibilityLabel }
68
+ else if let label = menu.menuLabel { item.accessibilityLabel = label }
69
+ if let accessibilityHint = menu.menuAccessibilityHint { item.accessibilityHint = accessibilityHint }
70
+ item.tintColor = menu.menuCustomTintColor
71
+ if let style = menu.menuBarButtonItemStyle { item.style = style }
72
+ return item
73
+ }
74
+ return nil
75
+ }
76
+ controller.setToolbarItems(items, animated: true)
77
+ controller.navigationController?.setToolbarHidden(false, animated: true)
78
+ }
79
+ }
80
+ }
81
+
82
+ public override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
83
+ if let toolbarItem = subview as? ToolbarItemView {
84
+ if toolbarItem.itemIdentifier.isEmpty { return }
85
+ addToolbarItemAtIndex(toolbarItem, index: atIndex)
86
+ } else if let menu = subview as? MenuActionView {
87
+ menu.parentMenuUpdatable = self
88
+ addMenuItemAtIndex(menu, index: atIndex)
89
+ }
90
+ updateToolbarItems()
91
+ }
92
+
93
+ public override func removeReactSubview(_ subview: UIView!) {
94
+ if let toolbarItem = subview as? ToolbarItemView {
95
+ if toolbarItem.itemIdentifier.isEmpty { return }
96
+ removeItemWithId(toolbarItem.itemIdentifier)
97
+ } else if let menu = subview as? MenuActionView {
98
+ if menu.menuIdentifier.isEmpty { return }
99
+ removeItemWithId(menu.menuIdentifier)
100
+ }
101
+ updateToolbarItems()
102
+ }
103
+
104
+ public override func didMoveToWindow() {
105
+ super.didMoveToWindow()
106
+ if window == nil {
107
+ if let controller = cachedController { controller.setToolbarItems(nil, animated: true) }
108
+ cachedController = nil
109
+ } else {
110
+ updateToolbarItems()
111
+ }
112
+ }
113
+
114
+ func updateMenu() {
115
+ updateToolbarItems()
116
+ }
117
+
118
+ func findViewController() -> UIViewController? {
119
+ if let cached = cachedController { return cached }
120
+ let rnsScreenClass: AnyClass? = NSClassFromString("RNSScreen")
121
+ var responder: UIResponder? = self
122
+ while let r = responder {
123
+ if let vc = r as? UIViewController {
124
+ if let rnsClass = rnsScreenClass, vc.isKind(of: rnsClass) {
125
+ cachedController = vc; return vc
126
+ }
127
+ }
128
+ responder = r.next
129
+ }
130
+ // fallback
131
+ responder = self
132
+ while let r = responder {
133
+ if let vc = r as? UIViewController { cachedController = vc; return vc }
134
+ responder = r.next
135
+ }
136
+ return nil
137
+ }
138
+ }