@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.
- package/.turbo/turbo-build.log +2 -0
- package/android/build.gradle +32 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/dev/vxrn/native/VxrnNativeModule.kt +117 -0
- package/android/src/main/java/dev/vxrn/native/VxrnNativePackage.kt +16 -0
- package/dist/cjs/color/android.dynamic.types.cjs +16 -0
- package/dist/cjs/color/android.dynamic.types.native.js +19 -0
- package/dist/cjs/color/android.dynamic.types.native.js.map +1 -0
- package/dist/cjs/color/android.material.types.cjs +16 -0
- package/dist/cjs/color/android.material.types.native.js +19 -0
- package/dist/cjs/color/android.material.types.native.js.map +1 -0
- package/dist/cjs/color/index.cjs +66 -0
- package/dist/cjs/color/index.native.js +69 -0
- package/dist/cjs/color/index.native.js.map +1 -0
- package/dist/cjs/color/ios.types.cjs +16 -0
- package/dist/cjs/color/ios.types.native.js +19 -0
- package/dist/cjs/color/ios.types.native.js.map +1 -0
- package/dist/cjs/color/materialColor.cjs +32 -0
- package/dist/cjs/color/materialColor.native.js +43 -0
- package/dist/cjs/color/materialColor.native.js.map +1 -0
- package/dist/cjs/index.cjs +37 -0
- package/dist/cjs/index.native.js +40 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/menu/index.cjs +28 -0
- package/dist/cjs/menu/index.native.js +47 -0
- package/dist/cjs/menu/index.native.js.map +1 -0
- package/dist/cjs/menu/types.cjs +16 -0
- package/dist/cjs/menu/types.native.js +19 -0
- package/dist/cjs/menu/types.native.js.map +1 -0
- package/dist/cjs/split-view/index.cjs +26 -0
- package/dist/cjs/split-view/index.native.js +29 -0
- package/dist/cjs/split-view/index.native.js.map +1 -0
- package/dist/cjs/split-view/split-view.cjs +80 -0
- package/dist/cjs/split-view/split-view.native.js +88 -0
- package/dist/cjs/split-view/split-view.native.js.map +1 -0
- package/dist/cjs/toolbar/index.cjs +32 -0
- package/dist/cjs/toolbar/index.native.js +60 -0
- package/dist/cjs/toolbar/index.native.js.map +1 -0
- package/dist/cjs/toolbar/types.cjs +16 -0
- package/dist/cjs/toolbar/types.native.js +19 -0
- package/dist/cjs/toolbar/types.native.js.map +1 -0
- package/dist/cjs/zoom/index.cjs +36 -0
- package/dist/cjs/zoom/index.native.js +65 -0
- package/dist/cjs/zoom/index.native.js.map +1 -0
- package/dist/cjs/zoom/types.cjs +16 -0
- package/dist/cjs/zoom/types.native.js +19 -0
- package/dist/cjs/zoom/types.native.js.map +1 -0
- package/dist/esm/color/android.dynamic.types.mjs +2 -0
- package/dist/esm/color/android.dynamic.types.mjs.map +1 -0
- package/dist/esm/color/android.dynamic.types.native.js +2 -0
- package/dist/esm/color/android.dynamic.types.native.js.map +1 -0
- package/dist/esm/color/android.material.types.mjs +2 -0
- package/dist/esm/color/android.material.types.mjs.map +1 -0
- package/dist/esm/color/android.material.types.native.js +2 -0
- package/dist/esm/color/android.material.types.native.js.map +1 -0
- package/dist/esm/color/index.mjs +42 -0
- package/dist/esm/color/index.mjs.map +1 -0
- package/dist/esm/color/index.native.js +42 -0
- package/dist/esm/color/index.native.js.map +1 -0
- package/dist/esm/color/ios.types.mjs +2 -0
- package/dist/esm/color/ios.types.mjs.map +1 -0
- package/dist/esm/color/ios.types.native.js +2 -0
- package/dist/esm/color/ios.types.native.js.map +1 -0
- package/dist/esm/color/materialColor.mjs +8 -0
- package/dist/esm/color/materialColor.mjs.map +1 -0
- package/dist/esm/color/materialColor.native.js +16 -0
- package/dist/esm/color/materialColor.native.js.map +1 -0
- package/dist/esm/index.mjs +7 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +7 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/menu/index.mjs +5 -0
- package/dist/esm/menu/index.mjs.map +1 -0
- package/dist/esm/menu/index.native.js +21 -0
- package/dist/esm/menu/index.native.js.map +1 -0
- package/dist/esm/menu/types.mjs +2 -0
- package/dist/esm/menu/types.mjs.map +1 -0
- package/dist/esm/menu/types.native.js +2 -0
- package/dist/esm/menu/types.native.js.map +1 -0
- package/dist/esm/split-view/index.mjs +3 -0
- package/dist/esm/split-view/index.mjs.map +1 -0
- package/dist/esm/split-view/index.native.js +3 -0
- package/dist/esm/split-view/index.native.js.map +1 -0
- package/dist/esm/split-view/split-view.mjs +46 -0
- package/dist/esm/split-view/split-view.mjs.map +1 -0
- package/dist/esm/split-view/split-view.native.js +51 -0
- package/dist/esm/split-view/split-view.native.js.map +1 -0
- package/dist/esm/toolbar/index.mjs +8 -0
- package/dist/esm/toolbar/index.mjs.map +1 -0
- package/dist/esm/toolbar/index.native.js +33 -0
- package/dist/esm/toolbar/index.native.js.map +1 -0
- package/dist/esm/toolbar/types.mjs +2 -0
- package/dist/esm/toolbar/types.mjs.map +1 -0
- package/dist/esm/toolbar/types.native.js +2 -0
- package/dist/esm/toolbar/types.native.js.map +1 -0
- package/dist/esm/zoom/index.mjs +11 -0
- package/dist/esm/zoom/index.mjs.map +1 -0
- package/dist/esm/zoom/index.native.js +37 -0
- package/dist/esm/zoom/index.native.js.map +1 -0
- package/dist/esm/zoom/types.mjs +2 -0
- package/dist/esm/zoom/types.mjs.map +1 -0
- package/dist/esm/zoom/types.native.js +2 -0
- package/dist/esm/zoom/types.native.js.map +1 -0
- package/ios/Menu/MenuActionView.swift +339 -0
- package/ios/Menu/VxrnMenuActionManager.m +47 -0
- package/ios/Toolbar/FontUtils.swift +46 -0
- package/ios/Toolbar/ToolbarHostView.swift +138 -0
- package/ios/Toolbar/ToolbarItemView.swift +334 -0
- package/ios/Toolbar/VxrnToolbarHostManager.m +20 -0
- package/ios/Toolbar/VxrnToolbarItemManager.m +41 -0
- package/ios/VxrnNative-Bridging-Header.h +7 -0
- package/ios/VxrnNative.podspec +31 -0
- package/ios/ZoomTransition/VxrnZoomAlignmentManager.m +22 -0
- package/ios/ZoomTransition/VxrnZoomEnablerManager.m +23 -0
- package/ios/ZoomTransition/VxrnZoomSourceManager.m +24 -0
- package/ios/ZoomTransition/ZoomTransition.swift +384 -0
- package/package.json +82 -0
- package/react-native.config.js +14 -0
- package/src/color/android.dynamic.types.ts +384 -0
- package/src/color/android.material.types.ts +291 -0
- package/src/color/index.native.ts +75 -0
- package/src/color/index.ts +75 -0
- package/src/color/ios.types.ts +156 -0
- package/src/color/materialColor.native.ts +15 -0
- package/src/color/materialColor.ts +7 -0
- package/src/index.ts +20 -0
- package/src/menu/index.native.tsx +28 -0
- package/src/menu/index.ts +7 -0
- package/src/menu/types.ts +32 -0
- package/src/split-view/index.ts +2 -0
- package/src/split-view/split-view.tsx +80 -0
- package/src/toolbar/index.native.tsx +39 -0
- package/src/toolbar/index.ts +11 -0
- package/src/toolbar/types.ts +39 -0
- package/src/zoom/index.native.tsx +56 -0
- package/src/zoom/index.ts +28 -0
- package/src/zoom/types.ts +30 -0
- package/tsconfig.json +7 -0
- package/types/color/android.dynamic.types.d.ts +384 -0
- package/types/color/android.dynamic.types.d.ts.map +1 -0
- package/types/color/android.material.types.d.ts +291 -0
- package/types/color/android.material.types.d.ts.map +1 -0
- package/types/color/index.d.ts +25 -0
- package/types/color/index.d.ts.map +1 -0
- package/types/color/index.native.d.ts +25 -0
- package/types/color/index.native.d.ts.map +1 -0
- package/types/color/ios.types.d.ts +156 -0
- package/types/color/ios.types.d.ts.map +1 -0
- package/types/color/materialColor.d.ts +3 -0
- package/types/color/materialColor.d.ts.map +1 -0
- package/types/color/materialColor.native.d.ts +3 -0
- package/types/color/materialColor.native.d.ts.map +1 -0
- package/types/index.d.ts +10 -0
- package/types/index.d.ts.map +1 -0
- package/types/menu/index.d.ts +4 -0
- package/types/menu/index.d.ts.map +1 -0
- package/types/menu/index.native.d.ts +4 -0
- package/types/menu/index.native.d.ts.map +1 -0
- package/types/menu/types.d.ts +32 -0
- package/types/menu/types.d.ts.map +1 -0
- package/types/split-view/index.d.ts +3 -0
- package/types/split-view/index.d.ts.map +1 -0
- package/types/split-view/split-view.d.ts +19 -0
- package/types/split-view/split-view.d.ts.map +1 -0
- package/types/toolbar/index.d.ts +5 -0
- package/types/toolbar/index.d.ts.map +1 -0
- package/types/toolbar/index.native.d.ts +5 -0
- package/types/toolbar/index.native.d.ts.map +1 -0
- package/types/toolbar/types.d.ts +37 -0
- package/types/toolbar/types.d.ts.map +1 -0
- package/types/zoom/index.d.ts +6 -0
- package/types/zoom/index.d.ts.map +1 -0
- package/types/zoom/index.native.d.ts +6 -0
- package/types/zoom/index.native.d.ts.map +1 -0
- package/types/zoom/types.d.ts +26 -0
- 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
|
+
}
|