pythonnative 0.36.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
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.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.36.0.dist-info/RECORD +0 -155
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// `Text`: a `UILabel` with rich spans, text transforms, shadows, and the
|
|
4
|
+
/// full font prop set.
|
|
5
|
+
public final class PNTextManager: PNComponentManager {
|
|
6
|
+
static let textShadowKeys = ["text_shadow_color", "text_shadow_offset", "text_shadow_radius"]
|
|
7
|
+
static let attributedKeys = ["letter_spacing", "line_height", "text_decoration"] + textShadowKeys
|
|
8
|
+
static let fontKeys = ["font_size", "font_weight", "font_family", "italic", "bold", "font_style"]
|
|
9
|
+
static let spanRebuildKeys = ["spans", "text", "text_transform", "color"] + fontKeys + attributedKeys
|
|
10
|
+
|
|
11
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
12
|
+
let label = UILabel(frame: .zero)
|
|
13
|
+
label.numberOfLines = 0
|
|
14
|
+
label.translatesAutoresizingMaskIntoConstraints = true
|
|
15
|
+
return label
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
19
|
+
guard let label = view as? UILabel else { return }
|
|
20
|
+
let merged = mergedProps(label)
|
|
21
|
+
let hasSpans = !((PNProps.value(merged, "spans") as? [Any]) ?? []).isEmpty
|
|
22
|
+
let textChanged = PNProps.has(props, "text") || PNProps.has(props, "text_transform")
|
|
23
|
+
if textChanged, !hasSpans {
|
|
24
|
+
label.text = PNTextManager.transform(PNProps.string(PNProps.value(merged, "text")), mode: PNProps.string(PNProps.value(merged, "text_transform")))
|
|
25
|
+
}
|
|
26
|
+
if PNTextManager.fontKeys.contains(where: { PNProps.has(props, $0) }) {
|
|
27
|
+
label.font = PNTextManager.font(from: merged, base: label.font)
|
|
28
|
+
}
|
|
29
|
+
if let color = PNColor.parse(PNProps.value(props, "color")) {
|
|
30
|
+
label.textColor = color
|
|
31
|
+
}
|
|
32
|
+
if let color = PNColor.parse(PNProps.value(props, "background_color")) {
|
|
33
|
+
label.backgroundColor = color
|
|
34
|
+
}
|
|
35
|
+
if PNProps.has(props, "max_lines") || PNProps.has(props, "number_of_lines") {
|
|
36
|
+
let lines = PNProps.int(PNProps.value(props, "max_lines")) ?? PNProps.int(PNProps.value(props, "number_of_lines")) ?? 0
|
|
37
|
+
label.numberOfLines = max(0, lines)
|
|
38
|
+
}
|
|
39
|
+
if PNProps.has(props, "text_align") {
|
|
40
|
+
label.textAlignment = PNTextManager.alignment(PNProps.string(PNProps.value(props, "text_align")))
|
|
41
|
+
}
|
|
42
|
+
if PNProps.has(props, "selectable") {
|
|
43
|
+
label.isUserInteractionEnabled = PNProps.bool(PNProps.value(props, "selectable")) ?? false
|
|
44
|
+
}
|
|
45
|
+
if hasSpans {
|
|
46
|
+
if PNTextManager.spanRebuildKeys.contains(where: { PNProps.has(props, $0) }) {
|
|
47
|
+
applySpans(label, merged)
|
|
48
|
+
}
|
|
49
|
+
} else if PNProps.has(props, "spans") {
|
|
50
|
+
label.text = PNTextManager.transform(PNProps.string(PNProps.value(merged, "text")), mode: PNProps.string(PNProps.value(merged, "text_transform")))
|
|
51
|
+
} else if PNTextManager.attributedKeys.contains(where: { PNProps.has(props, $0) })
|
|
52
|
+
|| (textChanged && PNTextManager.attributedKeys.contains { PNProps.value(merged, $0) != nil })
|
|
53
|
+
{
|
|
54
|
+
applyAttributed(label, merged)
|
|
55
|
+
}
|
|
56
|
+
PNViewStyler.applyDecoration(label, props)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// MARK: - Fonts
|
|
60
|
+
|
|
61
|
+
/// Resolve a `UIFont` from the element's font props.
|
|
62
|
+
static func font(from props: [String: Any], base: UIFont?) -> UIFont {
|
|
63
|
+
let currentSize = base?.pointSize ?? 17
|
|
64
|
+
let size = CGFloat(PNProps.double(PNProps.value(props, "font_size")) ?? Double(currentSize))
|
|
65
|
+
var weight: Any? = PNProps.value(props, "font_weight")
|
|
66
|
+
if weight == nil, PNProps.bool(PNProps.value(props, "bold")) == true { weight = "bold" }
|
|
67
|
+
let italic = PNProps.bool(PNProps.value(props, "italic")) == true
|
|
68
|
+
|| PNProps.string(PNProps.value(props, "font_style")) == "italic"
|
|
69
|
+
return font(size: size, weight: weight, family: PNProps.string(PNProps.value(props, "font_family")), italic: italic)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static func font(size: CGFloat, weight: Any?, family: String?, italic: Bool) -> UIFont {
|
|
73
|
+
if let family = family, !family.isEmpty, let named = UIFont(name: family, size: size) {
|
|
74
|
+
return italic ? italicized(named, size: size) : named
|
|
75
|
+
}
|
|
76
|
+
let font = UIFont.systemFont(ofSize: size, weight: fontWeight(weight))
|
|
77
|
+
return italic ? italicized(font, size: size) : font
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static func italicized(_ font: UIFont, size: CGFloat) -> UIFont {
|
|
81
|
+
var traits = font.fontDescriptor.symbolicTraits
|
|
82
|
+
traits.insert(.traitItalic)
|
|
83
|
+
guard let descriptor = font.fontDescriptor.withSymbolicTraits(traits) else { return font }
|
|
84
|
+
return UIFont(descriptor: descriptor, size: size)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static func fontWeight(_ value: Any?) -> UIFont.Weight {
|
|
88
|
+
if let name = value as? String {
|
|
89
|
+
switch name.lowercased() {
|
|
90
|
+
case "ultralight": return .ultraLight
|
|
91
|
+
case "thin": return .thin
|
|
92
|
+
case "light": return .light
|
|
93
|
+
case "medium": return .medium
|
|
94
|
+
case "semibold": return .semibold
|
|
95
|
+
case "bold": return .bold
|
|
96
|
+
case "heavy": return .heavy
|
|
97
|
+
case "black": return .black
|
|
98
|
+
default:
|
|
99
|
+
if let numeric = Double(name) { return fontWeight(numeric) }
|
|
100
|
+
return .regular
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
guard let numeric = PNProps.double(value) else { return .regular }
|
|
104
|
+
let n = max(100, min(900, numeric))
|
|
105
|
+
switch n {
|
|
106
|
+
case ...100: return .ultraLight
|
|
107
|
+
case ...200: return .thin
|
|
108
|
+
case ...300: return .light
|
|
109
|
+
case ...400: return .regular
|
|
110
|
+
case ...500: return .medium
|
|
111
|
+
case ...600: return .semibold
|
|
112
|
+
case ...700: return .bold
|
|
113
|
+
case ...800: return .heavy
|
|
114
|
+
default: return .black
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static func alignment(_ value: String?) -> NSTextAlignment {
|
|
119
|
+
switch value {
|
|
120
|
+
case "center": return .center
|
|
121
|
+
case "right": return .right
|
|
122
|
+
case "justify": return .justified
|
|
123
|
+
case "natural": return .natural
|
|
124
|
+
default: return .left
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// MARK: - Transforms
|
|
129
|
+
|
|
130
|
+
/// Apply a `text_transform` mode (`uppercase`, `lowercase`, `capitalize`).
|
|
131
|
+
public static func transform(_ text: String?, mode: String?) -> String {
|
|
132
|
+
let s = text ?? ""
|
|
133
|
+
switch mode {
|
|
134
|
+
case "uppercase": return s.uppercased()
|
|
135
|
+
case "lowercase": return s.lowercased()
|
|
136
|
+
case "capitalize":
|
|
137
|
+
var out = ""
|
|
138
|
+
var atWordStart = true
|
|
139
|
+
for ch in s {
|
|
140
|
+
if ch.isWhitespace {
|
|
141
|
+
atWordStart = true
|
|
142
|
+
out.append(ch)
|
|
143
|
+
} else if atWordStart {
|
|
144
|
+
out.append(contentsOf: String(ch).uppercased())
|
|
145
|
+
atWordStart = false
|
|
146
|
+
} else {
|
|
147
|
+
out.append(ch)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
151
|
+
default: return s
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// MARK: - Attributed rendering
|
|
156
|
+
|
|
157
|
+
static func baseAttributes(_ props: [String: Any], font: UIFont?) -> [NSAttributedString.Key: Any] {
|
|
158
|
+
var attrs: [NSAttributedString.Key: Any] = [:]
|
|
159
|
+
if let font = font { attrs[.font] = font }
|
|
160
|
+
if let kern = PNProps.double(PNProps.value(props, "letter_spacing")) { attrs[.kern] = kern }
|
|
161
|
+
if let lineHeight = PNProps.double(PNProps.value(props, "line_height")) {
|
|
162
|
+
let style = NSMutableParagraphStyle()
|
|
163
|
+
style.minimumLineHeight = CGFloat(lineHeight)
|
|
164
|
+
style.maximumLineHeight = CGFloat(lineHeight)
|
|
165
|
+
attrs[.paragraphStyle] = style
|
|
166
|
+
}
|
|
167
|
+
switch PNProps.string(PNProps.value(props, "text_decoration")) {
|
|
168
|
+
case "underline": attrs[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
|
169
|
+
case "line_through": attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
|
|
170
|
+
default: break
|
|
171
|
+
}
|
|
172
|
+
if let shadow = textShadow(props) { attrs[.shadow] = shadow }
|
|
173
|
+
return attrs
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static func textShadow(_ props: [String: Any]) -> NSShadow? {
|
|
177
|
+
guard textShadowKeys.contains(where: { PNProps.value(props, $0) != nil }) else { return nil }
|
|
178
|
+
let shadow = NSShadow()
|
|
179
|
+
shadow.shadowColor = PNColor.parse(PNProps.value(props, "text_shadow_color")) ?? UIColor.black
|
|
180
|
+
let (dx, dy) = PNViewStyler.shadowOffset(PNProps.value(props, "text_shadow_offset"))
|
|
181
|
+
shadow.shadowOffset = CGSize(width: dx, height: dy)
|
|
182
|
+
shadow.shadowBlurRadius = CGFloat(PNProps.double(PNProps.value(props, "text_shadow_radius")) ?? 0)
|
|
183
|
+
return shadow
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func applyAttributed(_ label: UILabel, _ props: [String: Any]) {
|
|
187
|
+
guard let text = label.text, !text.isEmpty else { return }
|
|
188
|
+
label.attributedText = NSAttributedString(string: text, attributes: PNTextManager.baseAttributes(props, font: label.font))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func applySpans(_ label: UILabel, _ merged: [String: Any]) {
|
|
192
|
+
let mode = PNProps.string(PNProps.value(merged, "text_transform"))
|
|
193
|
+
let spans = ((PNProps.value(merged, "spans") as? [Any]) ?? []).compactMap { $0 as? [String: Any] }
|
|
194
|
+
let texts = spans.map { PNTextManager.transform(PNProps.string($0["text"]), mode: mode) }
|
|
195
|
+
let full = NSMutableAttributedString(string: texts.joined(), attributes: PNTextManager.baseAttributes(merged, font: label.font))
|
|
196
|
+
let baseSize = label.font?.pointSize ?? 17
|
|
197
|
+
var location = 0
|
|
198
|
+
for (span, text) in zip(spans, texts) {
|
|
199
|
+
let length = (text as NSString).length
|
|
200
|
+
let range = NSRange(location: location, length: length)
|
|
201
|
+
location += length
|
|
202
|
+
if length == 0 { continue }
|
|
203
|
+
if PNTextManager.fontKeys.contains(where: { PNProps.value(span, $0) != nil }) {
|
|
204
|
+
let size = CGFloat(PNProps.double(PNProps.value(span, "font_size")) ?? Double(baseSize))
|
|
205
|
+
var weight: Any? = PNProps.value(span, "font_weight")
|
|
206
|
+
if weight == nil, PNProps.bool(PNProps.value(span, "bold")) == true { weight = "bold" }
|
|
207
|
+
let font = PNTextManager.font(
|
|
208
|
+
size: size, weight: weight, family: PNProps.string(PNProps.value(span, "font_family")),
|
|
209
|
+
italic: PNProps.bool(PNProps.value(span, "italic")) == true
|
|
210
|
+
)
|
|
211
|
+
full.addAttribute(.font, value: font, range: range)
|
|
212
|
+
}
|
|
213
|
+
if let color = PNColor.parse(PNProps.value(span, "color")) {
|
|
214
|
+
full.addAttribute(.foregroundColor, value: color, range: range)
|
|
215
|
+
}
|
|
216
|
+
if let color = PNColor.parse(PNProps.value(span, "background_color")) {
|
|
217
|
+
full.addAttribute(.backgroundColor, value: color, range: range)
|
|
218
|
+
}
|
|
219
|
+
if let kern = PNProps.double(PNProps.value(span, "letter_spacing")) {
|
|
220
|
+
full.addAttribute(.kern, value: kern, range: range)
|
|
221
|
+
}
|
|
222
|
+
switch PNProps.string(PNProps.value(span, "text_decoration")) {
|
|
223
|
+
case "underline": full.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
|
|
224
|
+
case "line_through": full.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: range)
|
|
225
|
+
default: break
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
label.attributedText = full
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Builds `CGAffineTransform`s from the `transform` style prop.
|
|
4
|
+
///
|
|
5
|
+
/// The prop is a list of single-key dicts: `rotate` (degrees, or a
|
|
6
|
+
/// `"45deg"` / `"0.5rad"` string), `scale`, `scale_x`, `scale_y`,
|
|
7
|
+
/// `translate_x`, `translate_y`, `skew_x`, `skew_y` (degrees).
|
|
8
|
+
public enum PNTransform {
|
|
9
|
+
/// Build a transform from a spec list (or single dict). `nil` is identity.
|
|
10
|
+
public static func make(_ spec: Any?) -> CGAffineTransform {
|
|
11
|
+
var transform = CGAffineTransform.identity
|
|
12
|
+
guard let spec = spec, !(spec is NSNull) else { return transform }
|
|
13
|
+
let entries: [Any] = (spec as? [Any]) ?? [spec]
|
|
14
|
+
for entry in entries {
|
|
15
|
+
guard let dict = entry as? [String: Any] else { continue }
|
|
16
|
+
if let rotate = dict["rotate"] {
|
|
17
|
+
transform = transform.rotated(by: angle(rotate))
|
|
18
|
+
}
|
|
19
|
+
if let scale = PNProps.double(dict["scale"]) {
|
|
20
|
+
transform = transform.scaledBy(x: CGFloat(scale), y: CGFloat(scale))
|
|
21
|
+
}
|
|
22
|
+
if let sx = PNProps.double(dict["scale_x"]) {
|
|
23
|
+
transform = transform.scaledBy(x: CGFloat(sx), y: 1)
|
|
24
|
+
}
|
|
25
|
+
if let sy = PNProps.double(dict["scale_y"]) {
|
|
26
|
+
transform = transform.scaledBy(x: 1, y: CGFloat(sy))
|
|
27
|
+
}
|
|
28
|
+
if dict["translate_x"] != nil || dict["translate_y"] != nil {
|
|
29
|
+
let tx = PNProps.double(dict["translate_x"]) ?? 0
|
|
30
|
+
let ty = PNProps.double(dict["translate_y"]) ?? 0
|
|
31
|
+
transform = transform.translatedBy(x: CGFloat(tx), y: CGFloat(ty))
|
|
32
|
+
}
|
|
33
|
+
if dict["skew_x"] != nil || dict["skew_y"] != nil {
|
|
34
|
+
let skew = CGAffineTransform(
|
|
35
|
+
a: 1, b: tan(angle(dict["skew_y"] ?? 0)),
|
|
36
|
+
c: tan(angle(dict["skew_x"] ?? 0)), d: 1,
|
|
37
|
+
tx: 0, ty: 0
|
|
38
|
+
)
|
|
39
|
+
transform = skew.concatenating(transform)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return transform
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Whether every component of `t` is finite.
|
|
46
|
+
public static func isFinite(_ t: CGAffineTransform) -> Bool {
|
|
47
|
+
t.a.isFinite && t.b.isFinite && t.c.isFinite && t.d.isFinite && t.tx.isFinite && t.ty.isFinite
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Apply the `transform` prop to `view` (identity when `null`).
|
|
51
|
+
public static func apply(_ view: UIView, spec: Any?) {
|
|
52
|
+
let transform = make(spec)
|
|
53
|
+
guard isFinite(transform) else {
|
|
54
|
+
PNLog.rateLimited(PNLog.components, key: "set_transform:nan", "[set_transform:nan] spec=\(String(describing: spec))")
|
|
55
|
+
view.transform = .identity
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
view.transform = transform
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Parse a rotation value into radians (`"90deg"`, `"1.5rad"`, or numeric degrees).
|
|
62
|
+
public static func angle(_ value: Any) -> CGFloat {
|
|
63
|
+
if let text = value as? String {
|
|
64
|
+
let lower = text.lowercased()
|
|
65
|
+
if lower.hasSuffix("deg"), let degrees = Double(lower.dropLast(3)) {
|
|
66
|
+
return CGFloat(degrees * .pi / 180)
|
|
67
|
+
}
|
|
68
|
+
if lower.hasSuffix("rad"), let radians = Double(lower.dropLast(3)) {
|
|
69
|
+
return CGFloat(radians)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return CGFloat((PNProps.double(value) ?? 0) * .pi / 180)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Decompose an affine transform into the animatable channels.
|
|
76
|
+
public static func decompose(_ t: CGAffineTransform) -> (translateX: CGFloat, translateY: CGFloat, scaleX: CGFloat, scaleY: CGFloat, rotateDegrees: CGFloat) {
|
|
77
|
+
let scaleX = sqrt(t.a * t.a + t.b * t.b)
|
|
78
|
+
let scaleY = sqrt(t.c * t.c + t.d * t.d)
|
|
79
|
+
let rotation = atan2(t.b, t.a)
|
|
80
|
+
return (t.tx, t.ty, scaleX, scaleY, rotation * 180 / .pi)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// `View`, `Row`, and `Column`: a hit-test-aware flex container. All
|
|
4
|
+
/// layout semantics live in the Python layout engine; the manager only
|
|
5
|
+
/// applies visual props and frames.
|
|
6
|
+
public final class PNViewManager: PNComponentManager {
|
|
7
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
8
|
+
PNContainerView(frame: .zero)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// `Spacer`: an empty layout placeholder.
|
|
13
|
+
public final class PNSpacerManager: PNComponentManager {
|
|
14
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
15
|
+
UIView(frame: .zero)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
19
|
+
// Sizing is entirely the layout engine's; nothing visual to apply.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// `SafeAreaView`: a plain container; insets are applied by the layout engine.
|
|
24
|
+
public final class PNSafeAreaViewManager: PNComponentManager {
|
|
25
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
26
|
+
PNContainerView(frame: .zero)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// `KeyboardAvoidingView`: a container that keeps the keyboard observer
|
|
31
|
+
/// installed so Python receives keyboard height updates.
|
|
32
|
+
public final class PNKeyboardAvoidingViewManager: PNComponentManager {
|
|
33
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
34
|
+
PNKeyboardObserver.shared.start()
|
|
35
|
+
return PNContainerView(frame: .zero)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Publishes the keyboard height through
|
|
40
|
+
/// `callback("module", 0, "Host", {"event": "keyboard", "payload": {"height": h}})`.
|
|
41
|
+
public final class PNKeyboardObserver {
|
|
42
|
+
public static let shared = PNKeyboardObserver()
|
|
43
|
+
|
|
44
|
+
private var started = false
|
|
45
|
+
private(set) public var height: CGFloat = 0
|
|
46
|
+
|
|
47
|
+
private init() {}
|
|
48
|
+
|
|
49
|
+
/// Begin observing keyboard notifications (idempotent).
|
|
50
|
+
public func start() {
|
|
51
|
+
if started { return }
|
|
52
|
+
started = true
|
|
53
|
+
let center = NotificationCenter.default
|
|
54
|
+
center.addObserver(self, selector: #selector(willShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
|
|
55
|
+
center.addObserver(self, selector: #selector(willChange(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
|
56
|
+
center.addObserver(self, selector: #selector(willHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@objc private func willShow(_ note: Notification) {
|
|
60
|
+
publish(Self.keyboardHeight(from: note))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@objc private func willChange(_ note: Notification) {
|
|
64
|
+
publish(Self.keyboardHeight(from: note))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@objc private func willHide(_ note: Notification) {
|
|
68
|
+
publish(0)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private func publish(_ value: CGFloat) {
|
|
72
|
+
let clamped = max(0, value)
|
|
73
|
+
if clamped == height { return }
|
|
74
|
+
height = clamped
|
|
75
|
+
PNModuleEvents.emit(module: "Host", event: "keyboard", payload: ["height": Double(clamped)])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static func keyboardHeight(from note: Notification) -> CGFloat {
|
|
79
|
+
guard let frame = (note.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
|
|
80
|
+
return 0
|
|
81
|
+
}
|
|
82
|
+
let screen = PNWindow.screenBounds()
|
|
83
|
+
if screen.height > 0, frame.origin.y >= screen.height { return 0 }
|
|
84
|
+
return frame.height
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Per-view state attached to every bridge-created `UIView`.
|
|
4
|
+
///
|
|
5
|
+
/// Managers are shared per element type, so anything a manager needs
|
|
6
|
+
/// to remember about one view (its merged props, style bookkeeping,
|
|
7
|
+
/// recognizers, suppress flags) lives here. The state is stored as an
|
|
8
|
+
/// associated object so it can be reached from any `UIView` reference.
|
|
9
|
+
public final class PNViewState {
|
|
10
|
+
/// The reconciler tag of the view.
|
|
11
|
+
public let tag: Int64
|
|
12
|
+
/// Element type name.
|
|
13
|
+
public let typeName: String
|
|
14
|
+
/// Every prop the view has received so far (updates merge; `null` removes).
|
|
15
|
+
public var props: [String: Any] = [:]
|
|
16
|
+
/// Free-form storage for manager-specific values (suppress flags, delegates, ...).
|
|
17
|
+
public var extras: [String: Any] = [:]
|
|
18
|
+
/// Strong references the view's manager needs to keep alive (delegates, targets).
|
|
19
|
+
public var retained: [AnyObject] = []
|
|
20
|
+
/// Recognizers installed from the `gestures` prop.
|
|
21
|
+
public var gestureRecognizers: [UIGestureRecognizer] = []
|
|
22
|
+
|
|
23
|
+
// Style bookkeeping used by `PNViewStyler` at frame time.
|
|
24
|
+
var requestedCornerRadius: CGFloat?
|
|
25
|
+
var cornerRadii: [CGFloat]?
|
|
26
|
+
var sideBorderWidths: [CGFloat]?
|
|
27
|
+
var sideBorderColors: [UIColor]?
|
|
28
|
+
var sideBorderLayers: [CALayer?] = [nil, nil, nil, nil]
|
|
29
|
+
|
|
30
|
+
init(tag: Int64, typeName: String) {
|
|
31
|
+
self.tag = tag
|
|
32
|
+
self.typeName = typeName
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Names of the events wired on the element this render (`_pn_events`).
|
|
36
|
+
public var eventNames: Set<String> {
|
|
37
|
+
guard let list = props["_pn_events"] as? [Any] else { return [] }
|
|
38
|
+
return Set(list.compactMap { $0 as? String })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Whether the element wired a callback named `name`.
|
|
42
|
+
public func hasEvent(_ name: String) -> Bool {
|
|
43
|
+
eventNames.contains(name)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Typed access to `extras`.
|
|
47
|
+
public func flag(_ key: String) -> Bool {
|
|
48
|
+
(extras[key] as? Bool) ?? false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static var key: UInt8 = 0
|
|
52
|
+
|
|
53
|
+
/// Attach a fresh state object to `view`.
|
|
54
|
+
@discardableResult
|
|
55
|
+
static func attach(_ view: UIView, tag: Int64, typeName: String) -> PNViewState {
|
|
56
|
+
let state = PNViewState(tag: tag, typeName: typeName)
|
|
57
|
+
objc_setAssociatedObject(view, &key, state, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
58
|
+
return state
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// The state attached to `view`, if any.
|
|
62
|
+
public static func existing(for view: UIView) -> PNViewState? {
|
|
63
|
+
objc_getAssociatedObject(view, &key) as? PNViewState
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static func detach(_ view: UIView) {
|
|
67
|
+
objc_setAssociatedObject(view, &key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Event emission keyed by view.
|
|
72
|
+
public enum PNEvents {
|
|
73
|
+
/// Emit `name` for `view` with positional `args`. Returns Python's
|
|
74
|
+
/// JSON reply for request-style events, else `nil`.
|
|
75
|
+
@discardableResult
|
|
76
|
+
public static func emit(_ view: UIView, _ name: String, _ args: [Any?] = []) -> String? {
|
|
77
|
+
guard let state = PNViewState.existing(for: view) else { return nil }
|
|
78
|
+
return PNBridge.shared.emitEvent(tag: state.tag, name: name, args: args)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Emit only when the element wired a handler for `name`.
|
|
82
|
+
@discardableResult
|
|
83
|
+
public static func emitIfWired(_ view: UIView, _ name: String, _ args: [Any?] = []) -> String? {
|
|
84
|
+
guard let state = PNViewState.existing(for: view), state.hasEvent(name) else { return nil }
|
|
85
|
+
return PNBridge.shared.emitEvent(tag: state.tag, name: name, args: args)
|
|
86
|
+
}
|
|
87
|
+
}
|