react-native-platform-components 0.6.1 → 0.8.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 +259 -44
- package/android/src/main/java/com/platformcomponents/PCLiquidGlassView.kt +84 -0
- package/android/src/main/java/com/platformcomponents/PCLiquidGlassViewManager.kt +52 -0
- package/android/src/main/java/com/platformcomponents/PCSegmentedControlView.kt +241 -0
- package/android/src/main/java/com/platformcomponents/PCSegmentedControlViewManager.kt +105 -0
- package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +2 -0
- package/ios/PCDatePickerView.swift +16 -13
- package/ios/PCLiquidGlass.h +10 -0
- package/ios/PCLiquidGlass.mm +140 -0
- package/ios/PCLiquidGlass.swift +354 -0
- package/ios/PCSegmentedControl.h +10 -0
- package/ios/PCSegmentedControl.mm +194 -0
- package/ios/PCSegmentedControl.swift +200 -0
- package/ios/PCSelectionMenu.swift +1 -1
- package/lib/commonjs/LiquidGlass.js +72 -0
- package/lib/commonjs/LiquidGlass.js.map +1 -0
- package/lib/commonjs/LiquidGlassNativeComponent.ts +110 -0
- package/lib/commonjs/SegmentedControl.js +93 -0
- package/lib/commonjs/SegmentedControl.js.map +1 -0
- package/lib/commonjs/SegmentedControlNativeComponent.ts +79 -0
- package/lib/commonjs/index.js +22 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/LiquidGlass.js +64 -0
- package/lib/module/LiquidGlass.js.map +1 -0
- package/lib/module/LiquidGlassNativeComponent.ts +110 -0
- package/lib/module/SegmentedControl.js +87 -0
- package/lib/module/SegmentedControl.js.map +1 -0
- package/lib/module/SegmentedControlNativeComponent.ts +79 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/LiquidGlass.d.ts +96 -0
- package/lib/typescript/commonjs/src/LiquidGlass.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts +93 -0
- package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SegmentedControl.d.ts +62 -0
- package/lib/typescript/commonjs/src/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts +63 -0
- package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/LiquidGlass.d.ts +96 -0
- package/lib/typescript/module/src/LiquidGlass.d.ts.map +1 -0
- package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts +93 -0
- package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/SegmentedControl.d.ts +62 -0
- package/lib/typescript/module/src/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts +63 -0
- package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +2 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +13 -4
- package/react-native.config.js +1 -0
- package/shared/PCSegmentedControlComponentDescriptors-custom.h +22 -0
- package/shared/PCSegmentedControlShadowNode-custom.cpp +54 -0
- package/shared/PCSegmentedControlShadowNode-custom.h +56 -0
- package/shared/PCSegmentedControlState-custom.h +62 -0
- package/shared/react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h +1 -0
- package/src/LiquidGlass.tsx +169 -0
- package/src/LiquidGlassNativeComponent.ts +110 -0
- package/src/SegmentedControl.tsx +178 -0
- package/src/SegmentedControlNativeComponent.ts +79 -0
- package/src/index.tsx +2 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Glass effect style enum for bridging to ObjC++
|
|
4
|
+
@objc public enum PCLiquidGlassEffectStyle: Int {
|
|
5
|
+
case regular
|
|
6
|
+
case clear
|
|
7
|
+
case none
|
|
8
|
+
|
|
9
|
+
#if compiler(>=6.2)
|
|
10
|
+
@available(iOS 26.0, *)
|
|
11
|
+
var glassStyle: UIGlassEffect.Style? {
|
|
12
|
+
switch self {
|
|
13
|
+
case .regular:
|
|
14
|
+
return .regular
|
|
15
|
+
case .clear:
|
|
16
|
+
return .clear
|
|
17
|
+
case .none:
|
|
18
|
+
return nil
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
#endif
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#if compiler(>=6.2)
|
|
25
|
+
|
|
26
|
+
/// The actual Liquid Glass view implementation for iOS 26+
|
|
27
|
+
/// Extends UIVisualEffectView to properly support UIGlassEffect
|
|
28
|
+
@available(iOS 26.0, *)
|
|
29
|
+
@objcMembers
|
|
30
|
+
public final class PCLiquidGlassView: UIVisualEffectView {
|
|
31
|
+
|
|
32
|
+
// MARK: - Static
|
|
33
|
+
|
|
34
|
+
@objc public static var isSupported: Bool {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - Props (set from ObjC++)
|
|
39
|
+
|
|
40
|
+
private var isFirstMount: Bool = true
|
|
41
|
+
|
|
42
|
+
/// Effect style: "clear", "regular", "none"
|
|
43
|
+
public var effectStyle: String = "regular" {
|
|
44
|
+
didSet { applyGlassEffect() }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Corner radius for the glass effect
|
|
48
|
+
public var glassCornerRadius: CGFloat = 0 {
|
|
49
|
+
didSet { applyCornerRadius() }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Enable touch interaction feedback
|
|
53
|
+
public var interactive: Bool = false
|
|
54
|
+
|
|
55
|
+
/// Tint color for the glass effect (hex string)
|
|
56
|
+
public var glassTintColor: String? {
|
|
57
|
+
didSet { applyGlassEffect() }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Color scheme: "light", "dark", "system"
|
|
61
|
+
public var colorScheme: String = "system" {
|
|
62
|
+
didSet { applyColorScheme() }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Shadow radius for glow effect
|
|
66
|
+
public var glassShadowRadius: CGFloat = 20 {
|
|
67
|
+
didSet { applyShadow() }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Manual highlight state control (no-op on iOS 26+, use `interactive` instead)
|
|
71
|
+
/// The native UIGlassEffect.isInteractive handles touch-based highlighting automatically
|
|
72
|
+
public var isHighlighted: Bool = false
|
|
73
|
+
|
|
74
|
+
/// Callback for press events with touch coordinates
|
|
75
|
+
public var onPressCallback: ((CGFloat, CGFloat) -> Void)?
|
|
76
|
+
|
|
77
|
+
// MARK: - Init
|
|
78
|
+
|
|
79
|
+
public override init(effect: UIVisualEffect?) {
|
|
80
|
+
super.init(effect: effect)
|
|
81
|
+
setup()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public required init?(coder: NSCoder) {
|
|
85
|
+
super.init(coder: coder)
|
|
86
|
+
setup()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private func setup() {
|
|
90
|
+
clipsToBounds = false
|
|
91
|
+
setupTapGesture()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private func setupTapGesture() {
|
|
95
|
+
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
|
|
96
|
+
addGestureRecognizer(tap)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
|
100
|
+
let location = gesture.location(in: self)
|
|
101
|
+
onPressCallback?(location.x, location.y)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MARK: - Layout
|
|
105
|
+
|
|
106
|
+
public override func layoutSubviews() {
|
|
107
|
+
super.layoutSubviews()
|
|
108
|
+
|
|
109
|
+
// Apply glass effect on first layout when we have bounds
|
|
110
|
+
if effect == nil && bounds.size != .zero {
|
|
111
|
+
applyGlassEffect()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// MARK: - Public Setup (called from ObjC++ after props are set)
|
|
116
|
+
|
|
117
|
+
/// Call this after setting props to apply/re-apply the glass effect
|
|
118
|
+
@objc public func setupView() {
|
|
119
|
+
applyGlassEffect()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// MARK: - Glass Effect
|
|
123
|
+
|
|
124
|
+
private func applyGlassEffect() {
|
|
125
|
+
guard bounds.size != .zero else { return }
|
|
126
|
+
|
|
127
|
+
// Parse effect style
|
|
128
|
+
let style: PCLiquidGlassEffectStyle
|
|
129
|
+
switch effectStyle {
|
|
130
|
+
case "clear":
|
|
131
|
+
style = .clear
|
|
132
|
+
case "none":
|
|
133
|
+
style = .none
|
|
134
|
+
default:
|
|
135
|
+
style = .regular
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Handle "none" style
|
|
139
|
+
guard let glassStyle = style.glassStyle else {
|
|
140
|
+
UIView.animate(withDuration: 0.2) {
|
|
141
|
+
self.effect = nil
|
|
142
|
+
}
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Create the glass effect
|
|
147
|
+
let glassEffect = UIGlassEffect(style: glassStyle)
|
|
148
|
+
glassEffect.isInteractive = interactive
|
|
149
|
+
|
|
150
|
+
// Apply tint color
|
|
151
|
+
if let tintHex = glassTintColor, !tintHex.isEmpty,
|
|
152
|
+
let color = UIColor(pcHex: tintHex) {
|
|
153
|
+
glassEffect.tintColor = color
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Apply the effect
|
|
157
|
+
if isFirstMount {
|
|
158
|
+
self.effect = glassEffect
|
|
159
|
+
isFirstMount = false
|
|
160
|
+
} else {
|
|
161
|
+
UIView.animate(withDuration: 0.2) {
|
|
162
|
+
self.effect = glassEffect
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
applyCornerRadius()
|
|
167
|
+
applyShadow()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func applyCornerRadius() {
|
|
171
|
+
layer.cornerRadius = glassCornerRadius
|
|
172
|
+
layer.cornerCurve = .continuous
|
|
173
|
+
// Don't clip to bounds - allow shadow to show
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private func applyShadow() {
|
|
177
|
+
if glassShadowRadius > 0 {
|
|
178
|
+
layer.shadowColor = UIColor.white.cgColor
|
|
179
|
+
layer.shadowOpacity = 0.2
|
|
180
|
+
layer.shadowRadius = glassShadowRadius
|
|
181
|
+
layer.shadowOffset = .zero
|
|
182
|
+
layer.masksToBounds = false
|
|
183
|
+
} else {
|
|
184
|
+
layer.shadowOpacity = 0
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func applyColorScheme() {
|
|
189
|
+
switch colorScheme {
|
|
190
|
+
case "light":
|
|
191
|
+
overrideUserInterfaceStyle = .light
|
|
192
|
+
case "dark":
|
|
193
|
+
overrideUserInterfaceStyle = .dark
|
|
194
|
+
default:
|
|
195
|
+
overrideUserInterfaceStyle = .unspecified
|
|
196
|
+
}
|
|
197
|
+
applyGlassEffect()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// MARK: - Sizing
|
|
201
|
+
|
|
202
|
+
public override func sizeThatFits(_ size: CGSize) -> CGSize {
|
|
203
|
+
return size
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public override var intrinsicContentSize: CGSize {
|
|
207
|
+
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#else
|
|
212
|
+
|
|
213
|
+
/// Fallback for older Swift compilers (pre-iOS 26 SDK)
|
|
214
|
+
/// Uses UIBlurEffect as a fallback
|
|
215
|
+
@objcMembers
|
|
216
|
+
public final class PCLiquidGlassView: UIVisualEffectView {
|
|
217
|
+
|
|
218
|
+
@objc public static var isSupported: Bool {
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public var effectStyle: String = "regular" {
|
|
223
|
+
didSet { applyFallbackEffect() }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public var glassCornerRadius: CGFloat = 0 {
|
|
227
|
+
didSet { applyCornerRadius() }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public var interactive: Bool = false
|
|
231
|
+
public var glassTintColor: String?
|
|
232
|
+
public var colorScheme: String = "system" {
|
|
233
|
+
didSet { applyColorScheme() }
|
|
234
|
+
}
|
|
235
|
+
public var glassShadowRadius: CGFloat = 20
|
|
236
|
+
public var isHighlighted: Bool = false
|
|
237
|
+
public var onPressCallback: ((CGFloat, CGFloat) -> Void)?
|
|
238
|
+
|
|
239
|
+
public override init(effect: UIVisualEffect?) {
|
|
240
|
+
super.init(effect: effect)
|
|
241
|
+
setup()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public required init?(coder: NSCoder) {
|
|
245
|
+
super.init(coder: coder)
|
|
246
|
+
setup()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private func setup() {
|
|
250
|
+
clipsToBounds = false
|
|
251
|
+
setupTapGesture()
|
|
252
|
+
applyFallbackEffect()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private func setupTapGesture() {
|
|
256
|
+
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
|
|
257
|
+
addGestureRecognizer(tap)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
|
261
|
+
let location = gesture.location(in: self)
|
|
262
|
+
onPressCallback?(location.x, location.y)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public override func layoutSubviews() {
|
|
266
|
+
super.layoutSubviews()
|
|
267
|
+
if effect == nil && bounds.size != .zero {
|
|
268
|
+
applyFallbackEffect()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// Call this after setting props to apply/re-apply the effect
|
|
273
|
+
@objc public func setupView() {
|
|
274
|
+
applyFallbackEffect()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private func applyFallbackEffect() {
|
|
278
|
+
guard effectStyle != "none" else {
|
|
279
|
+
self.effect = nil
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Use thin blur materials as fallback
|
|
284
|
+
let blurStyle: UIBlurEffect.Style
|
|
285
|
+
switch (effectStyle, colorScheme) {
|
|
286
|
+
case ("clear", "dark"):
|
|
287
|
+
blurStyle = .systemUltraThinMaterialDark
|
|
288
|
+
case ("clear", "light"):
|
|
289
|
+
blurStyle = .systemUltraThinMaterialLight
|
|
290
|
+
case ("clear", _):
|
|
291
|
+
blurStyle = .systemUltraThinMaterial
|
|
292
|
+
case (_, "dark"):
|
|
293
|
+
blurStyle = .systemThinMaterialDark
|
|
294
|
+
case (_, "light"):
|
|
295
|
+
blurStyle = .systemThinMaterialLight
|
|
296
|
+
default:
|
|
297
|
+
blurStyle = .systemThinMaterial
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
self.effect = UIBlurEffect(style: blurStyle)
|
|
301
|
+
applyCornerRadius()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private func applyCornerRadius() {
|
|
305
|
+
layer.cornerRadius = glassCornerRadius
|
|
306
|
+
layer.cornerCurve = .continuous
|
|
307
|
+
clipsToBounds = glassCornerRadius > 0
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private func applyColorScheme() {
|
|
311
|
+
switch colorScheme {
|
|
312
|
+
case "light":
|
|
313
|
+
overrideUserInterfaceStyle = .light
|
|
314
|
+
case "dark":
|
|
315
|
+
overrideUserInterfaceStyle = .dark
|
|
316
|
+
default:
|
|
317
|
+
overrideUserInterfaceStyle = .unspecified
|
|
318
|
+
}
|
|
319
|
+
applyFallbackEffect()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
#endif
|
|
324
|
+
|
|
325
|
+
// MARK: - UIColor hex extension
|
|
326
|
+
|
|
327
|
+
private extension UIColor {
|
|
328
|
+
convenience init?(pcHex: String) {
|
|
329
|
+
var hexSanitized = pcHex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
330
|
+
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
|
331
|
+
|
|
332
|
+
var rgb: UInt64 = 0
|
|
333
|
+
guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
|
|
334
|
+
|
|
335
|
+
let length = hexSanitized.count
|
|
336
|
+
if length == 6 {
|
|
337
|
+
self.init(
|
|
338
|
+
red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
|
|
339
|
+
green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
|
|
340
|
+
blue: CGFloat(rgb & 0x0000FF) / 255.0,
|
|
341
|
+
alpha: 1.0
|
|
342
|
+
)
|
|
343
|
+
} else if length == 8 {
|
|
344
|
+
self.init(
|
|
345
|
+
red: CGFloat((rgb & 0xFF000000) >> 24) / 255.0,
|
|
346
|
+
green: CGFloat((rgb & 0x00FF0000) >> 16) / 255.0,
|
|
347
|
+
blue: CGFloat((rgb & 0x0000FF00) >> 8) / 255.0,
|
|
348
|
+
alpha: CGFloat(rgb & 0x000000FF) / 255.0
|
|
349
|
+
)
|
|
350
|
+
} else {
|
|
351
|
+
return nil
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// PCSegmentedControl.mm
|
|
2
|
+
|
|
3
|
+
#import "PCSegmentedControl.h"
|
|
4
|
+
|
|
5
|
+
#import <React/RCTComponentViewFactory.h>
|
|
6
|
+
#import <React/RCTConversions.h>
|
|
7
|
+
#import <React/RCTFabricComponentsPlugins.h>
|
|
8
|
+
|
|
9
|
+
#import <react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h>
|
|
10
|
+
#import <react/renderer/components/PlatformComponentsViewSpec/EventEmitters.h>
|
|
11
|
+
#import <react/renderer/components/PlatformComponentsViewSpec/Props.h>
|
|
12
|
+
#import <react/renderer/core/LayoutPrimitives.h>
|
|
13
|
+
|
|
14
|
+
#if __has_include(<PlatformComponents/PlatformComponents-Swift.h>)
|
|
15
|
+
#import <PlatformComponents/PlatformComponents-Swift.h>
|
|
16
|
+
#else
|
|
17
|
+
#import "PlatformComponents-Swift.h"
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
#import "PCSegmentedControlComponentDescriptors-custom.h"
|
|
21
|
+
#import "PCSegmentedControlShadowNode-custom.h"
|
|
22
|
+
#import "PCSegmentedControlState-custom.h"
|
|
23
|
+
|
|
24
|
+
using namespace facebook::react;
|
|
25
|
+
|
|
26
|
+
namespace {
|
|
27
|
+
static inline bool SegmentsEqual(
|
|
28
|
+
const std::vector<facebook::react::PCSegmentedControlSegmentsStruct> &a,
|
|
29
|
+
const std::vector<facebook::react::PCSegmentedControlSegmentsStruct> &b) {
|
|
30
|
+
if (a.size() != b.size()) return false;
|
|
31
|
+
for (size_t i = 0; i < a.size(); i++) {
|
|
32
|
+
if (a[i].label != b[i].label) return false;
|
|
33
|
+
if (a[i].value != b[i].value) return false;
|
|
34
|
+
if (a[i].disabled != b[i].disabled) return false;
|
|
35
|
+
if (a[i].icon != b[i].icon) return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
} // namespace
|
|
40
|
+
|
|
41
|
+
@interface PCSegmentedControl ()
|
|
42
|
+
|
|
43
|
+
- (void)updateMeasurements;
|
|
44
|
+
|
|
45
|
+
@end
|
|
46
|
+
|
|
47
|
+
@implementation PCSegmentedControl {
|
|
48
|
+
PCSegmentedControlView *_view;
|
|
49
|
+
MeasuringPCSegmentedControlShadowNode::ConcreteState::Shared _state;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
53
|
+
return concreteComponentDescriptorProvider<
|
|
54
|
+
MeasuringPCSegmentedControlComponentDescriptor>();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
58
|
+
if (self = [super initWithFrame:frame]) {
|
|
59
|
+
_view = [PCSegmentedControlView new];
|
|
60
|
+
self.contentView = _view;
|
|
61
|
+
|
|
62
|
+
__weak __typeof(self) weakSelf = self;
|
|
63
|
+
|
|
64
|
+
_view.onSelect = ^(NSInteger index, NSString *value) {
|
|
65
|
+
__typeof(self) strongSelf = weakSelf;
|
|
66
|
+
if (!strongSelf) return;
|
|
67
|
+
|
|
68
|
+
auto eventEmitter =
|
|
69
|
+
std::static_pointer_cast<const PCSegmentedControlEventEmitter>(
|
|
70
|
+
strongSelf->_eventEmitter);
|
|
71
|
+
if (!eventEmitter) return;
|
|
72
|
+
|
|
73
|
+
PCSegmentedControlEventEmitter::OnSelect payload = {
|
|
74
|
+
.index = (int)index,
|
|
75
|
+
.value = value.UTF8String,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
eventEmitter->onSelect(payload);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return self;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
- (void)updateProps:(Props::Shared const &)props
|
|
85
|
+
oldProps:(Props::Shared const &)oldProps {
|
|
86
|
+
const auto &newProps =
|
|
87
|
+
*std::static_pointer_cast<const PCSegmentedControlProps>(props);
|
|
88
|
+
const auto prevProps =
|
|
89
|
+
std::static_pointer_cast<const PCSegmentedControlProps>(oldProps);
|
|
90
|
+
|
|
91
|
+
// segments: [{label, value, disabled, icon}]
|
|
92
|
+
if (!prevProps || !SegmentsEqual(newProps.segments, prevProps->segments)) {
|
|
93
|
+
NSMutableArray *arr = [NSMutableArray new];
|
|
94
|
+
for (const auto &seg : newProps.segments) {
|
|
95
|
+
NSString *label = seg.label.empty()
|
|
96
|
+
? @""
|
|
97
|
+
: [NSString stringWithUTF8String:seg.label.c_str()];
|
|
98
|
+
NSString *value = seg.value.empty()
|
|
99
|
+
? @""
|
|
100
|
+
: [NSString stringWithUTF8String:seg.value.c_str()];
|
|
101
|
+
NSString *disabled = seg.disabled.empty()
|
|
102
|
+
? @"enabled"
|
|
103
|
+
: [NSString stringWithUTF8String:seg.disabled.c_str()];
|
|
104
|
+
NSString *icon = seg.icon.empty()
|
|
105
|
+
? @""
|
|
106
|
+
: [NSString stringWithUTF8String:seg.icon.c_str()];
|
|
107
|
+
[arr addObject:@{
|
|
108
|
+
@"label": label,
|
|
109
|
+
@"value": value,
|
|
110
|
+
@"disabled": disabled,
|
|
111
|
+
@"icon": icon
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
_view.segments = arr;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// selectedValue (default "")
|
|
118
|
+
if (!prevProps || newProps.selectedValue != prevProps->selectedValue) {
|
|
119
|
+
if (!newProps.selectedValue.empty()) {
|
|
120
|
+
_view.selectedValue =
|
|
121
|
+
[NSString stringWithUTF8String:newProps.selectedValue.c_str()];
|
|
122
|
+
} else {
|
|
123
|
+
_view.selectedValue = @""; // sentinel for no selection
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// interactivity: "enabled" | "disabled"
|
|
128
|
+
if (!prevProps || newProps.interactivity != prevProps->interactivity) {
|
|
129
|
+
if (!newProps.interactivity.empty()) {
|
|
130
|
+
_view.interactivity =
|
|
131
|
+
[NSString stringWithUTF8String:newProps.interactivity.c_str()];
|
|
132
|
+
} else {
|
|
133
|
+
_view.interactivity = @"enabled";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// iOS-specific props
|
|
138
|
+
const auto &newIos = newProps.ios;
|
|
139
|
+
const auto &oldIos =
|
|
140
|
+
prevProps ? prevProps->ios : PCSegmentedControlIosStruct{};
|
|
141
|
+
|
|
142
|
+
if (!prevProps || newIos.momentary != oldIos.momentary) {
|
|
143
|
+
_view.momentary = (newIos.momentary == "true");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!prevProps || newIos.apportionsSegmentWidthsByContent != oldIos.apportionsSegmentWidthsByContent) {
|
|
147
|
+
_view.apportionsSegmentWidthsByContent = (newIos.apportionsSegmentWidthsByContent == "true");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!prevProps || newIos.selectedSegmentTintColor != oldIos.selectedSegmentTintColor) {
|
|
151
|
+
if (!newIos.selectedSegmentTintColor.empty()) {
|
|
152
|
+
_view.selectedSegmentTintColor =
|
|
153
|
+
[NSString stringWithUTF8String:newIos.selectedSegmentTintColor.c_str()];
|
|
154
|
+
} else {
|
|
155
|
+
_view.selectedSegmentTintColor = nil;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
[super updateProps:props oldProps:oldProps];
|
|
160
|
+
|
|
161
|
+
// Update measurements when props change that affect layout
|
|
162
|
+
[self updateMeasurements];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#pragma mark - State (Measuring)
|
|
166
|
+
|
|
167
|
+
- (void)updateState:(const State::Shared &)state
|
|
168
|
+
oldState:(const State::Shared &)oldState {
|
|
169
|
+
_state = std::static_pointer_cast<
|
|
170
|
+
const MeasuringPCSegmentedControlShadowNode::ConcreteState>(state);
|
|
171
|
+
|
|
172
|
+
if (oldState == nullptr) {
|
|
173
|
+
// First time: compute initial size.
|
|
174
|
+
[self updateMeasurements];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
[super updateState:state oldState:oldState];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
- (void)updateMeasurements {
|
|
181
|
+
if (_state == nullptr)
|
|
182
|
+
return;
|
|
183
|
+
|
|
184
|
+
// Use the real width Yoga gave us
|
|
185
|
+
const CGFloat w = self.bounds.size.width > 1 ? self.bounds.size.width : 320;
|
|
186
|
+
|
|
187
|
+
CGSize size = [_view sizeForLayoutWithConstrainedTo:CGSizeMake(w, 0)];
|
|
188
|
+
|
|
189
|
+
PCSegmentedControlStateFrameSize next;
|
|
190
|
+
next.frameSize = {(Float)size.width, (Float)size.height};
|
|
191
|
+
_state->updateState(std::move(next));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@end
|