react-native-tinyui 0.5.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +351 -22
  2. package/ios/ConcentricView/TinyuiConcentricView.h +12 -0
  3. package/ios/ConcentricView/TinyuiConcentricView.mm +60 -0
  4. package/ios/Core/Tinyui.mm +54 -0
  5. package/ios/Core/TinyuiComponentClasses.mm +44 -0
  6. package/ios/Menu/TinyuiMenuBuilder.swift +238 -0
  7. package/ios/Menu/TinyuiMenuProvider.swift +51 -85
  8. package/ios/Menu/TinyuiMenuView.mm +12 -35
  9. package/ios/Popover/TinyuiPopoverProvider.swift +198 -44
  10. package/ios/Popover/TinyuiPopoverView.mm +26 -1
  11. package/ios/SFSymbol/TinyuiSFSymbolEffects.h +9 -0
  12. package/ios/SFSymbol/TinyuiSFSymbolEffects.mm +180 -0
  13. package/ios/SFSymbol/TinyuiSFSymbolImage.h +8 -0
  14. package/ios/SFSymbol/TinyuiSFSymbolImage.mm +141 -0
  15. package/ios/SFSymbol/TinyuiSFSymbolShadowNode.h +31 -0
  16. package/ios/SFSymbol/TinyuiSFSymbolShadowNode.mm +30 -0
  17. package/ios/SFSymbol/TinyuiSFSymbolView.h +4 -0
  18. package/ios/SFSymbol/TinyuiSFSymbolView.mm +173 -0
  19. package/ios/Stepper/TinyuiStepperShadowNode.h +32 -0
  20. package/ios/Stepper/TinyuiStepperShadowNode.mm +28 -0
  21. package/ios/Stepper/TinyuiStepperView.h +12 -0
  22. package/ios/Stepper/TinyuiStepperView.mm +128 -0
  23. package/ios/TipKit/TinyuiTip.swift +31 -0
  24. package/ios/TipKit/TinyuiTipPopoverProvider.swift +276 -0
  25. package/ios/TipKit/TinyuiTipPopoverView.h +8 -0
  26. package/ios/TipKit/TinyuiTipPopoverView.mm +114 -0
  27. package/ios/TipKit/TinyuiTipsRuntime.swift +148 -0
  28. package/lib/module/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
  29. package/lib/module/ConcentricView/index.js +29 -0
  30. package/lib/module/ConcentricView/index.js.map +1 -0
  31. package/lib/module/ConcentricView/types.js +4 -0
  32. package/lib/module/ConcentricView/types.js.map +1 -0
  33. package/lib/module/Menu/MenuNativeComponent.ts +1 -3
  34. package/lib/module/Menu/index.js +4 -9
  35. package/lib/module/Menu/index.js.map +1 -1
  36. package/lib/module/Menu/options.js +4 -12
  37. package/lib/module/Menu/options.js.map +1 -1
  38. package/lib/module/NativeTinyui.js.map +1 -1
  39. package/lib/module/Popover/index.js +4 -2
  40. package/lib/module/Popover/index.js.map +1 -1
  41. package/lib/module/SFSymbol/SFSymbolNativeComponent.ts +20 -0
  42. package/lib/module/SFSymbol/index.js +73 -0
  43. package/lib/module/SFSymbol/index.js.map +1 -0
  44. package/lib/module/SFSymbol/options.js +79 -0
  45. package/lib/module/SFSymbol/options.js.map +1 -0
  46. package/lib/module/SFSymbol/types.js +4 -0
  47. package/lib/module/SFSymbol/types.js.map +1 -0
  48. package/lib/module/Stepper/StepperNativeComponent.ts +29 -0
  49. package/lib/module/Stepper/index.js +75 -0
  50. package/lib/module/Stepper/index.js.map +1 -0
  51. package/lib/module/Stepper/types.js +4 -0
  52. package/lib/module/Stepper/types.js.map +1 -0
  53. package/lib/module/TipKit/Popover.js +89 -0
  54. package/lib/module/TipKit/Popover.js.map +1 -0
  55. package/lib/module/TipKit/TipPopoverNativeComponent.ts +37 -0
  56. package/lib/module/TipKit/index.js +28 -0
  57. package/lib/module/TipKit/index.js.map +1 -0
  58. package/lib/module/TipKit/types.js +4 -0
  59. package/lib/module/TipKit/types.js.map +1 -0
  60. package/lib/module/TipKit/validation.js +24 -0
  61. package/lib/module/TipKit/validation.js.map +1 -0
  62. package/lib/module/index.js +4 -0
  63. package/lib/module/index.js.map +1 -1
  64. package/lib/module/utils/getNativeTinyui.js.map +1 -1
  65. package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts +7 -0
  66. package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts.map +1 -0
  67. package/lib/typescript/src/ConcentricView/index.d.ts +4 -0
  68. package/lib/typescript/src/ConcentricView/index.d.ts.map +1 -0
  69. package/lib/typescript/src/ConcentricView/types.d.ts +6 -0
  70. package/lib/typescript/src/ConcentricView/types.d.ts.map +1 -0
  71. package/lib/typescript/src/Menu/MenuNativeComponent.d.ts +2 -3
  72. package/lib/typescript/src/Menu/MenuNativeComponent.d.ts.map +1 -1
  73. package/lib/typescript/src/Menu/index.d.ts +1 -1
  74. package/lib/typescript/src/Menu/index.d.ts.map +1 -1
  75. package/lib/typescript/src/Menu/options.d.ts +1 -5
  76. package/lib/typescript/src/Menu/options.d.ts.map +1 -1
  77. package/lib/typescript/src/Menu/types.d.ts +16 -4
  78. package/lib/typescript/src/Menu/types.d.ts.map +1 -1
  79. package/lib/typescript/src/NativeTinyui.d.ts +2 -0
  80. package/lib/typescript/src/NativeTinyui.d.ts.map +1 -1
  81. package/lib/typescript/src/Popover/index.d.ts.map +1 -1
  82. package/lib/typescript/src/Popover/types.d.ts +2 -0
  83. package/lib/typescript/src/Popover/types.d.ts.map +1 -1
  84. package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts +11 -0
  85. package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts.map +1 -0
  86. package/lib/typescript/src/SFSymbol/index.d.ts +4 -0
  87. package/lib/typescript/src/SFSymbol/index.d.ts.map +1 -0
  88. package/lib/typescript/src/SFSymbol/options.d.ts +86 -0
  89. package/lib/typescript/src/SFSymbol/options.d.ts.map +1 -0
  90. package/lib/typescript/src/SFSymbol/types.d.ts +113 -0
  91. package/lib/typescript/src/SFSymbol/types.d.ts.map +1 -0
  92. package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts +21 -0
  93. package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts.map +1 -0
  94. package/lib/typescript/src/Stepper/index.d.ts +4 -0
  95. package/lib/typescript/src/Stepper/index.d.ts.map +1 -0
  96. package/lib/typescript/src/Stepper/types.d.ts +24 -0
  97. package/lib/typescript/src/Stepper/types.d.ts.map +1 -0
  98. package/lib/typescript/src/TipKit/Popover.d.ts +3 -0
  99. package/lib/typescript/src/TipKit/Popover.d.ts.map +1 -0
  100. package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts +38 -0
  101. package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts.map +1 -0
  102. package/lib/typescript/src/TipKit/index.d.ts +10 -0
  103. package/lib/typescript/src/TipKit/index.d.ts.map +1 -0
  104. package/lib/typescript/src/TipKit/types.d.ts +46 -0
  105. package/lib/typescript/src/TipKit/types.d.ts.map +1 -0
  106. package/lib/typescript/src/TipKit/validation.d.ts +4 -0
  107. package/lib/typescript/src/TipKit/validation.d.ts.map +1 -0
  108. package/lib/typescript/src/index.d.ts +9 -1
  109. package/lib/typescript/src/index.d.ts.map +1 -1
  110. package/lib/typescript/src/utils/getNativeTinyui.d.ts +1 -1
  111. package/lib/typescript/src/utils/getNativeTinyui.d.ts.map +1 -1
  112. package/package.json +37 -2
  113. package/{Tinyui.podspec → react-native-tinyui.podspec} +23 -2
  114. package/src/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
  115. package/src/ConcentricView/index.tsx +34 -0
  116. package/src/ConcentricView/types.ts +6 -0
  117. package/src/Menu/MenuNativeComponent.ts +1 -3
  118. package/src/Menu/index.tsx +6 -6
  119. package/src/Menu/options.ts +2 -20
  120. package/src/Menu/types.ts +18 -4
  121. package/src/NativeTinyui.ts +2 -0
  122. package/src/Popover/index.tsx +8 -3
  123. package/src/Popover/types.ts +2 -0
  124. package/src/SFSymbol/SFSymbolNativeComponent.ts +20 -0
  125. package/src/SFSymbol/index.tsx +89 -0
  126. package/src/SFSymbol/options.ts +116 -0
  127. package/src/SFSymbol/types.ts +158 -0
  128. package/src/Stepper/StepperNativeComponent.ts +29 -0
  129. package/src/Stepper/index.tsx +91 -0
  130. package/src/Stepper/types.ts +24 -0
  131. package/src/TipKit/Popover.tsx +87 -0
  132. package/src/TipKit/TipPopoverNativeComponent.ts +37 -0
  133. package/src/TipKit/index.ts +47 -0
  134. package/src/TipKit/types.ts +57 -0
  135. package/src/TipKit/validation.ts +39 -0
  136. package/src/index.tsx +30 -0
  137. package/src/utils/getNativeTinyui.ts +8 -1
  138. package/ios/Core/TinyuiHostingHelpers.swift +0 -74
  139. package/ios/Menu/TinyuiMenuViewSwift.swift +0 -326
  140. package/ios/Popover/TinyuiPopoverViewSwift.swift +0 -88
@@ -0,0 +1,238 @@
1
+ import React
2
+ import UIKit
3
+
4
+ @objc public protocol TinyuiMenuViewDelegate: AnyObject {
5
+ func onActionPress(id: String, title: String)
6
+ }
7
+
8
+ /** Builds UIKit menu elements from the configuration generated in JavaScript. */
9
+ final class TinyuiMenuBuilder {
10
+ private weak var delegate: TinyuiMenuViewDelegate?
11
+
12
+ init(delegate: TinyuiMenuViewDelegate?) {
13
+ self.delegate = delegate
14
+ }
15
+
16
+ func buildMenu(title: String, config: [String: Any]) -> UIMenu {
17
+ print("[TinyUI][Menu] config:", config)
18
+
19
+ return UIMenu(
20
+ title: title,
21
+ children: buildChildren(from: dictionaries(in: config, key: "items"))
22
+ )
23
+ }
24
+
25
+ private func buildChildren(
26
+ from dictionaries: [[String: Any]],
27
+ inheritedDestructive: Bool = false,
28
+ inheritedDisabled: Bool = false
29
+ ) -> [UIMenuElement] {
30
+ // UIKit represents separators by placing adjacent groups in inline menus.
31
+ // Partitioning here also makes consecutive/leading/trailing dividers no-ops.
32
+ let groups = dictionaries.split {
33
+ ($0["type"] as? String) == "separator"
34
+ }
35
+
36
+ if groups.count <= 1 {
37
+ return dictionaries.compactMap {
38
+ buildElement(
39
+ from: $0,
40
+ inheritedDestructive: inheritedDestructive,
41
+ inheritedDisabled: inheritedDisabled
42
+ )
43
+ }
44
+ }
45
+
46
+ return groups.compactMap { group in
47
+ let children = group.compactMap {
48
+ buildElement(
49
+ from: $0,
50
+ inheritedDestructive: inheritedDestructive,
51
+ inheritedDisabled: inheritedDisabled
52
+ )
53
+ }
54
+ guard !children.isEmpty else {
55
+ return nil
56
+ }
57
+ return UIMenu(title: "", options: .displayInline, children: children)
58
+ }
59
+ }
60
+
61
+ private func buildElement(
62
+ from dictionary: [String: Any],
63
+ inheritedDestructive: Bool,
64
+ inheritedDisabled: Bool
65
+ ) -> UIMenuElement? {
66
+ guard !(dictionary["hidden"] as? Bool ?? false) else {
67
+ return nil
68
+ }
69
+
70
+ switch dictionary["type"] as? String {
71
+ case "action":
72
+ return buildAction(
73
+ from: dictionary,
74
+ inheritedDestructive: inheritedDestructive,
75
+ inheritedDisabled: inheritedDisabled
76
+ )
77
+ case "submenu":
78
+ return buildSubmenu(
79
+ from: dictionary,
80
+ inheritedDestructive: inheritedDestructive,
81
+ inheritedDisabled: inheritedDisabled
82
+ )
83
+ case "section":
84
+ let children = buildChildren(
85
+ from: dictionaries(in: dictionary, key: "children"),
86
+ inheritedDestructive: inheritedDestructive,
87
+ inheritedDisabled: inheritedDisabled
88
+ )
89
+ guard !children.isEmpty else {
90
+ return nil
91
+ }
92
+ return UIMenu(
93
+ title: dictionary["title"] as? String ?? "",
94
+ options: .displayInline,
95
+ children: children
96
+ )
97
+ default:
98
+ return nil
99
+ }
100
+ }
101
+
102
+ private func buildAction(
103
+ from dictionary: [String: Any],
104
+ inheritedDestructive: Bool,
105
+ inheritedDisabled: Bool
106
+ ) -> UIAction {
107
+ let identifier = dictionary["id"] as? String ?? ""
108
+ let title = dictionary["title"] as? String ?? ""
109
+ var attributes: UIMenuElement.Attributes = []
110
+
111
+ if inheritedDisabled || (dictionary["disabled"] as? Bool ?? false) {
112
+ attributes.insert(.disabled)
113
+ }
114
+ if inheritedDestructive || (dictionary["destructive"] as? Bool ?? false) {
115
+ attributes.insert(.destructive)
116
+ }
117
+ if dictionary["hidden"] as? Bool ?? false {
118
+ attributes.insert(.hidden)
119
+ }
120
+ if dictionary["keepOpen"] as? Bool ?? false {
121
+ attributes.insert(.keepsMenuPresented)
122
+ }
123
+
124
+ return UIAction(
125
+ title: title,
126
+ subtitle: nonEmptyString(dictionary["subtitle"]),
127
+ image: image(from: dictionary),
128
+ identifier: nil,
129
+ discoverabilityTitle: nil,
130
+ attributes: attributes,
131
+ state: state(from: dictionary)
132
+ ) { [weak delegate] _ in
133
+ delegate?.onActionPress(id: identifier, title: title)
134
+ }
135
+ }
136
+
137
+ private func buildSubmenu(
138
+ from dictionary: [String: Any],
139
+ inheritedDestructive: Bool,
140
+ inheritedDisabled: Bool
141
+ ) -> UIMenuElement? {
142
+ let isDestructive =
143
+ inheritedDestructive || (dictionary["destructive"] as? Bool ?? false)
144
+ let isDisabled =
145
+ inheritedDisabled || (dictionary["disabled"] as? Bool ?? false)
146
+ let isInline = dictionary["displayInline"] as? Bool ?? false
147
+
148
+ // UIKit doesn't expose disabled/hidden attributes on UIMenu. A disabled
149
+ // hierarchical submenu is therefore represented by a disabled UIAction;
150
+ // inline submenu children can be disabled individually.
151
+ if isDisabled && !isInline {
152
+ var attributes: UIMenuElement.Attributes = [.disabled]
153
+ if isDestructive {
154
+ attributes.insert(.destructive)
155
+ }
156
+ return UIAction(
157
+ title: dictionary["title"] as? String ?? "",
158
+ subtitle: nonEmptyString(dictionary["subtitle"]),
159
+ image: image(from: dictionary),
160
+ identifier: nil,
161
+ discoverabilityTitle: nil,
162
+ attributes: attributes,
163
+ state: .off
164
+ ) { _ in }
165
+ }
166
+
167
+ let children = buildChildren(
168
+ from: dictionaries(in: dictionary, key: "children"),
169
+ inheritedDestructive: isDestructive,
170
+ inheritedDisabled: isDisabled
171
+ )
172
+ guard !children.isEmpty else {
173
+ return nil
174
+ }
175
+
176
+ var options: UIMenu.Options = []
177
+ if isInline {
178
+ options.insert(.displayInline)
179
+ }
180
+ if isDestructive {
181
+ options.insert(.destructive)
182
+ }
183
+
184
+ return UIMenu(
185
+ title: dictionary["title"] as? String ?? "",
186
+ subtitle: nonEmptyString(dictionary["subtitle"]),
187
+ image: image(from: dictionary),
188
+ identifier: nil,
189
+ options: options,
190
+ children: children
191
+ )
192
+ }
193
+
194
+ private func image(from dictionary: [String: Any]) -> UIImage? {
195
+ let image: UIImage?
196
+ if let assetName = nonEmptyString(dictionary["icon"]),
197
+ let assetImage = UIImage(named: assetName) {
198
+ image = assetImage
199
+ } else if let systemName = nonEmptyString(dictionary["systemImage"]) {
200
+ image = UIImage(systemName: systemName)
201
+ } else {
202
+ image = nil
203
+ }
204
+
205
+ guard let image else {
206
+ return nil
207
+ }
208
+ guard let tintColor = dictionary["iconColor"].flatMap(RCTConvert.uiColor) else {
209
+ return image.withRenderingMode(.alwaysTemplate)
210
+ }
211
+ return image.withTintColor(tintColor, renderingMode: .alwaysOriginal)
212
+ }
213
+
214
+ private func state(from dictionary: [String: Any]) -> UIMenuElement.State {
215
+ switch dictionary["state"] as? String {
216
+ case "on":
217
+ return .on
218
+ case "mixed":
219
+ return .mixed
220
+ default:
221
+ return .off
222
+ }
223
+ }
224
+
225
+ private func dictionaries(
226
+ in dictionary: [String: Any],
227
+ key: String
228
+ ) -> [[String: Any]] {
229
+ dictionary[key] as? [[String: Any]] ?? []
230
+ }
231
+
232
+ private func nonEmptyString(_ value: Any?) -> String? {
233
+ guard let value = value as? String, !value.isEmpty else {
234
+ return nil
235
+ }
236
+ return value
237
+ }
238
+ }
@@ -1,116 +1,82 @@
1
- import SwiftUI
2
1
  import UIKit
3
2
 
4
3
  /**
5
- * UIKit bridge between the Fabric `TinyuiMenuView` component and the SwiftUI
6
- * `TinyuiMenuSwiftUIView`. Hosts the SwiftUI view through a
7
- * `UIHostingController`, mirroring react-native-pager-view's `PagerViewProvider`.
8
- *
9
- * The presenting view controller is supplied by the Objective-C++ component
10
- * view so this file does not depend on React.
4
+ * UIButton-backed UIKit menu trigger used as the Fabric component's content
5
+ * view. UIButton owns the UIMenu presentation and its long-press interaction.
11
6
  */
12
- @objc public class TinyuiMenuProvider: UIView {
7
+ @objc public final class TinyuiMenuProvider: UIButton {
13
8
  private weak var delegate: TinyuiMenuViewDelegate?
14
- private var hostingController: UIHostingController<TinyuiMenuSwiftUIView>?
15
- private var props = TinyuiMenuProps()
16
9
 
17
- /// Presenting view controller, resolved by the component view from its
18
- /// React view controller hierarchy.
19
- @objc public weak var parentViewController: UIViewController? {
20
- didSet {
21
- if window != nil {
22
- setupView()
23
- }
24
- }
25
- }
26
-
27
- /// The React Native trigger view rendered as the menu's label.
28
10
  @objc public var triggerView: UIView? {
29
11
  didSet {
30
- props.triggerView = triggerView
31
- }
32
- }
33
-
34
- /// Menu configuration passed to the SwiftUI view.
35
- @objc public var menuConfig: [String: Any] = [:] {
36
- didSet {
37
- props.menuConfig = menuConfig
38
- }
39
- }
40
-
41
- @objc public var title: String = "" {
42
- didSet {
43
- props.title = title
44
- }
45
- }
46
-
47
- @objc public var disabled: Bool = false {
48
- didSet {
49
- props.disabled = disabled
50
- }
51
- }
52
-
53
- @objc public var hasPrimaryAction: Bool = false {
54
- didSet {
55
- props.hasPrimaryAction = hasPrimaryAction
12
+ guard oldValue !== triggerView else {
13
+ return
14
+ }
15
+ oldValue?.removeFromSuperview()
16
+ if let triggerView {
17
+ addSubview(triggerView)
18
+ }
19
+ setNeedsLayout()
56
20
  }
57
21
  }
58
22
 
59
23
  @objc public convenience init(delegate: TinyuiMenuViewDelegate) {
60
- self.init()
24
+ self.init(frame: .zero)
61
25
  self.delegate = delegate
62
26
  }
63
27
 
64
- override public func didMoveToWindow() {
65
- super.didMoveToWindow()
66
- if window != nil {
67
- setupView()
68
- }
28
+ override public init(frame: CGRect) {
29
+ super.init(frame: frame)
30
+ configure()
31
+ }
32
+
33
+ required init?(coder: NSCoder) {
34
+ super.init(coder: coder)
35
+ configure()
69
36
  }
70
37
 
71
38
  override public func layoutSubviews() {
72
39
  super.layoutSubviews()
73
- if window != nil {
74
- setupView()
40
+ triggerView?.frame = bounds
41
+ if let triggerView {
42
+ bringSubviewToFront(triggerView)
75
43
  }
76
44
  }
77
45
 
78
- private func setupView() {
79
- if hostingController != nil {
80
- syncParentViewController()
81
- return
82
- }
83
-
84
- guard let parentViewController else {
85
- return
46
+ override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
47
+ guard !isHidden, alpha > 0.01, isEnabled, isUserInteractionEnabled,
48
+ self.point(inside: point, with: event) else {
49
+ return nil
86
50
  }
51
+ return self
52
+ }
87
53
 
88
- let hostingController = UIHostingController(
89
- rootView: TinyuiMenuSwiftUIView(props: props, delegate: delegate)
90
- )
91
- self.hostingController = hostingController
92
-
93
- parentViewController.addChild(hostingController)
94
- hostingController.view.backgroundColor = .clear
95
- addSubview(hostingController.view)
96
- hostingController.view.translatesAutoresizingMaskIntoConstraints = false
97
- hostingController.view.pinEdges(to: self)
98
- hostingController.didMove(toParent: parentViewController)
54
+ private func configure() {
55
+ backgroundColor = .clear
56
+ showsMenuAsPrimaryAction = true
99
57
  }
100
58
 
101
- /// Keeps the hosting controller attached to the current view controller.
102
- private func syncParentViewController(to parentViewController: UIViewController? = nil) {
103
- guard
104
- let hostingController,
105
- let parentViewController = parentViewController ?? self.parentViewController,
106
- hostingController.parent !== parentViewController
107
- else {
59
+ /** Applies one complete Fabric props snapshot before rebuilding the menu. */
60
+ @objc public func update(
61
+ menuConfig: [String: Any],
62
+ title: String,
63
+ disabled: Bool
64
+ ) {
65
+ isEnabled = !disabled
66
+ isUserInteractionEnabled = !disabled
67
+
68
+ // UIButton enables its context menu interaction whenever a menu is assigned,
69
+ // independently of the control's enabled state. Remove it while disabled
70
+ // to block both primary-action and long-press presentation.
71
+ if disabled {
72
+ contextMenuInteraction?.dismissMenu()
73
+ menu = nil
108
74
  return
109
75
  }
110
76
 
111
- hostingController.willMove(toParent: nil)
112
- hostingController.removeFromParent()
113
- parentViewController.addChild(hostingController)
114
- hostingController.didMove(toParent: parentViewController)
77
+ menu = TinyuiMenuBuilder(delegate: delegate).buildMenu(
78
+ title: title,
79
+ config: menuConfig
80
+ )
115
81
  }
116
82
  }
@@ -3,7 +3,6 @@
3
3
  #import "TinyuiMenuView.h"
4
4
 
5
5
  #import <React/RCTConversions.h>
6
- #import <React/UIView+React.h>
7
6
  #import <react/utils/FollyConvert.h>
8
7
 
9
8
  #import <react/renderer/components/TinyuiSpec/ComponentDescriptors.h>
@@ -44,24 +43,15 @@ using namespace facebook::react;
44
43
  return self;
45
44
  }
46
45
 
47
- - (void)didMoveToWindow
48
- {
49
- [super didMoveToWindow];
50
- if (self.window != nil) {
51
- _menuProvider.parentViewController = [self reactViewController];
52
- }
53
- }
54
-
55
46
  #pragma mark - React lifecycle
56
47
 
57
48
  - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
58
49
  index:(NSInteger)index
59
50
  {
60
- // TinyuiMenuView renders its trigger as a child; we hand it to the
61
- // provider, which uses it as the SwiftUI `Menu` label.
51
+ // TinyuiMenuView renders its trigger as a child; the UIButton-backed
52
+ // provider uses it as the menu's visual content.
62
53
  _triggerView = childComponentView;
63
54
  _menuProvider.triggerView = childComponentView;
64
- [_menuProvider addSubview:childComponentView];
65
55
  }
66
56
 
67
57
  - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
@@ -82,17 +72,12 @@ using namespace facebook::react;
82
72
  const auto &oldMenuProps = *std::static_pointer_cast<TinyuiMenuViewProps const>(_props);
83
73
  const auto &newMenuProps = *std::static_pointer_cast<TinyuiMenuViewProps const>(props);
84
74
 
85
- if (oldMenuProps.menuConfig != newMenuProps.menuConfig) {
86
- _menuProvider.menuConfig = convertFollyDynamicToId(newMenuProps.menuConfig);
87
- }
88
- if (oldMenuProps.title != newMenuProps.title) {
89
- _menuProvider.title = RCTNSStringFromString(newMenuProps.title);
90
- }
91
- if (oldMenuProps.hasPrimaryAction != newMenuProps.hasPrimaryAction) {
92
- _menuProvider.hasPrimaryAction = newMenuProps.hasPrimaryAction;
93
- }
94
- if (oldMenuProps.disabled != newMenuProps.disabled) {
95
- _menuProvider.disabled = newMenuProps.disabled;
75
+ if (oldMenuProps.menuConfig != newMenuProps.menuConfig ||
76
+ oldMenuProps.title != newMenuProps.title ||
77
+ oldMenuProps.disabled != newMenuProps.disabled) {
78
+ [_menuProvider updateWithMenuConfig:convertFollyDynamicToId(newMenuProps.menuConfig)
79
+ title:RCTNSStringFromString(newMenuProps.title)
80
+ disabled:newMenuProps.disabled];
96
81
  }
97
82
 
98
83
  [super updateProps:props oldProps:oldProps];
@@ -100,23 +85,15 @@ using namespace facebook::react;
100
85
 
101
86
  #pragma mark - TinyuiMenuViewDelegate
102
87
 
103
- - (void)onItemPressWithId:(NSString *)identifier
104
- {
105
- if (_eventEmitter == nullptr) {
106
- return;
107
- }
108
- std::static_pointer_cast<const TinyuiMenuViewEventEmitter>(_eventEmitter)
109
- ->onItemPress(TinyuiMenuViewEventEmitter::OnItemPress{
110
- .id = std::string(identifier.UTF8String ?: "")});
111
- }
112
-
113
- - (void)onPrimaryAction
88
+ - (void)onActionPressWithId:(NSString *)identifier title:(NSString *)title
114
89
  {
115
90
  if (_eventEmitter == nullptr) {
116
91
  return;
117
92
  }
118
93
  std::static_pointer_cast<const TinyuiMenuViewEventEmitter>(_eventEmitter)
119
- ->onPrimaryAction(TinyuiMenuViewEventEmitter::OnPrimaryAction{});
94
+ ->onActionPress(TinyuiMenuViewEventEmitter::OnActionPress{
95
+ .id = std::string(identifier.UTF8String ?: ""),
96
+ .title = std::string(title.UTF8String ?: "")});
120
97
  }
121
98
 
122
99
  @end