@thelacanians/vue-native-cli 0.4.2 → 0.4.4
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/dist/cli.js +43 -23
- package/native/android/README.md +205 -0
- package/native/android/VueNativeCore/build.gradle.kts +100 -0
- package/native/android/VueNativeCore/consumer-rules.pro +12 -0
- package/native/android/VueNativeCore/proguard-rules.pro +33 -0
- package/native/android/VueNativeCore/src/main/AndroidManifest.xml +17 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/ErrorOverlayView.kt +94 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/HotReloadManager.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +652 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +207 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +417 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +76 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +78 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +46 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +84 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VCheckboxFactory.kt +93 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VDropdownFactory.kt +125 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +210 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +31 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +183 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +57 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +109 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VProgressBarFactory.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRadioFactory.kt +103 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRefreshControlFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSafeAreaFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +144 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +77 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +74 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +52 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +62 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VTextFactory.kt +53 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VVideoFactory.kt +191 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +90 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +40 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/VTextNodeView.kt +23 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +16 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/TouchableView.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +292 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AppStateModule.kt +41 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +59 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AudioModule.kt +331 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +166 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +56 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +198 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CameraModule.kt +64 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +288 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DatabaseModule.kt +229 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +193 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +68 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +61 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +111 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +26 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModule.kt +27 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +92 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +181 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +255 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +147 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +126 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +51 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +134 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +160 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +155 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +802 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +169 -0
- package/native/android/VueNativeCore/src/main/res/values/ids.xml +8 -0
- package/native/android/app/build.gradle.kts +45 -0
- package/native/android/app/proguard-rules.pro +5 -0
- package/native/android/app/src/main/AndroidManifest.xml +25 -0
- package/native/android/app/src/main/assets/.gitkeep +0 -0
- package/native/android/app/src/main/kotlin/com/vuenative/example/counter/MainActivity.kt +14 -0
- package/native/android/app/src/main/res/layout/activity_main.xml +6 -0
- package/native/android/app/src/main/res/values/strings.xml +3 -0
- package/native/android/app/src/main/res/values/themes.xml +9 -0
- package/native/android/app/src/main/res/xml/network_security_config.xml +8 -0
- package/native/android/build.gradle.kts +6 -0
- package/native/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/native/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/native/android/gradle.properties +4 -0
- package/native/android/gradlew +87 -0
- package/native/android/gradlew.bat +48 -0
- package/native/android/settings.gradle.kts +20 -0
- package/native/ios/VueNativeCore/Package.resolved +23 -0
- package/native/ios/VueNativeCore/Package.swift +32 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/CertificatePinning.swift +132 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/ErrorOverlayView.swift +92 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/HotReloadManager.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSPolyfills.swift +711 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSRuntime.swift +421 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +891 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/VueNativeViewController.swift +88 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/ComponentRegistry.swift +193 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActionSheetFactory.swift +91 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActivityIndicatorFactory.swift +74 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VAlertDialogFactory.swift +150 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VButtonFactory.swift +93 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VCheckboxFactory.swift +114 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VDropdownFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VImageFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VInputFactory.swift +357 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VKeyboardAvoidingFactory.swift +99 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +250 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPickerFactory.swift +96 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPressableFactory.swift +168 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VProgressBarFactory.swift +39 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRadioFactory.swift +167 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRefreshControlFactory.swift +153 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSafeAreaFactory.swift +56 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSectionListFactory.swift +248 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSegmentedControlFactory.swift +73 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +63 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VStatusBarFactory.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSwitchFactory.swift +108 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +290 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VVideoFactory.swift +246 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +157 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/NativeComponentFactory.swift +53 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +107 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/TouchableView.swift +136 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/UIColor+Hex.swift +80 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AnimationModule.swift +291 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AppStateModule.swift +65 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AsyncStorageModule.swift +68 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AudioModule.swift +366 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BackgroundTaskModule.swift +135 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BiometryModule.swift +61 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BluetoothModule.swift +387 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CalendarModule.swift +161 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CameraModule.swift +318 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ClipboardModule.swift +33 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ContactsModule.swift +173 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DatabaseModule.swift +259 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DeviceInfoModule.swift +34 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/FileSystemModule.swift +233 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeolocationModule.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/HapticsModule.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/IAPModule.swift +194 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/KeyboardModule.swift +31 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/LinkingModule.swift +42 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModule.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +78 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NetworkModule.swift +62 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NotificationsModule.swift +215 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/OTAModule.swift +281 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PerformanceModule.swift +138 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PermissionsModule.swift +190 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SecureStorageModule.swift +118 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SensorsModule.swift +103 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ShareModule.swift +49 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SocialAuthModule.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/WebSocketModule.swift +213 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Resources/vue-native-placeholder.js +8 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Styling/StyleEngine.swift +885 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSRuntimeTests.swift +362 -0
- package/package.json +3 -2
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
import ObjectiveC
|
|
5
|
+
|
|
6
|
+
/// Static class that applies style properties to UIViews via FlexLayout (Yoga).
|
|
7
|
+
/// Handles both Yoga layout properties (flex, padding, margin, etc.) and
|
|
8
|
+
/// UIView visual properties (backgroundColor, borderRadius, etc.).
|
|
9
|
+
///
|
|
10
|
+
/// Supports point values, percentage values, and auto for dimensions.
|
|
11
|
+
@MainActor
|
|
12
|
+
enum StyleEngine {
|
|
13
|
+
|
|
14
|
+
// MARK: - Public API
|
|
15
|
+
|
|
16
|
+
/// Apply a batch of style properties to a view.
|
|
17
|
+
static func applyStyles(_ styles: [String: Any], to view: UIView) {
|
|
18
|
+
for (key, value) in styles {
|
|
19
|
+
apply(key: key, value: value, to: view)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Apply a single style property to a view.
|
|
24
|
+
/// Routes to the appropriate handler based on the property key.
|
|
25
|
+
static func apply(key: String, value: Any?, to view: UIView) {
|
|
26
|
+
// Store internal props (prefixed with "__") as associated objects
|
|
27
|
+
// so parent factories can inspect them (e.g. VSectionListFactory).
|
|
28
|
+
if key.hasPrefix("__") {
|
|
29
|
+
setInternalProp(key, value: value, on: view)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// First try layout properties (FlexLayout / Yoga)
|
|
34
|
+
if applyLayoutProp(key: key, value: value, to: view) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Then try visual properties (UIView)
|
|
39
|
+
if applyVisualProp(key: key, value: value, to: view) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Text properties are handled by VTextFactory directly,
|
|
44
|
+
// but we handle them here as a fallback for convenience
|
|
45
|
+
if applyTextProp(key: key, value: value, to: view) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: - Internal Props
|
|
51
|
+
|
|
52
|
+
private static var internalPropsKey: UInt8 = 0
|
|
53
|
+
|
|
54
|
+
/// Store an internal prop (prefixed with "__") on a view as an associated object.
|
|
55
|
+
private static func setInternalProp(_ key: String, value: Any?, on view: UIView) {
|
|
56
|
+
var props = objc_getAssociatedObject(view, &internalPropsKey) as? [String: Any] ?? [:]
|
|
57
|
+
if let value = value {
|
|
58
|
+
props[key] = value
|
|
59
|
+
} else {
|
|
60
|
+
props.removeValue(forKey: key)
|
|
61
|
+
}
|
|
62
|
+
objc_setAssociatedObject(view, &internalPropsKey, props, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Retrieve an internal prop from a view.
|
|
66
|
+
static func getInternalProp(_ key: String, from view: UIView) -> Any? {
|
|
67
|
+
let props = objc_getAssociatedObject(view, &internalPropsKey) as? [String: Any]
|
|
68
|
+
return props?[key]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// MARK: - Yoga Value Helpers
|
|
72
|
+
|
|
73
|
+
/// Convert a value to CGFloat points. Supports Double and Int.
|
|
74
|
+
/// Returns nil for non-numeric values (strings like "50%", "auto").
|
|
75
|
+
static func yogaValue(_ value: Any?) -> CGFloat? {
|
|
76
|
+
if let num = value as? Double { return CGFloat(num) }
|
|
77
|
+
if let num = value as? Int { return CGFloat(num) }
|
|
78
|
+
if let num = value as? CGFloat { return num }
|
|
79
|
+
if let str = value as? String, let num = Double(str) { return CGFloat(num) }
|
|
80
|
+
return nil
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Check if a value represents "auto" (for dimensions that support it).
|
|
84
|
+
static func isAuto(_ value: Any?) -> Bool {
|
|
85
|
+
if let str = value as? String, str.lowercased() == "auto" {
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Extract percentage value from strings like "50%". Returns 50.0 for "50%".
|
|
92
|
+
static func asPercent(_ value: Any?) -> CGFloat? {
|
|
93
|
+
guard let str = value as? String, str.hasSuffix("%"),
|
|
94
|
+
let num = Double(str.dropLast()) else { return nil }
|
|
95
|
+
return CGFloat(num)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// MARK: - Layout Properties (FlexLayout / Yoga)
|
|
99
|
+
|
|
100
|
+
/// Apply a layout property via FlexLayout. Returns true if the key was recognized.
|
|
101
|
+
@discardableResult
|
|
102
|
+
private static func applyLayoutProp(key: String, value: Any?, to view: UIView) -> Bool {
|
|
103
|
+
let flex = view.flex
|
|
104
|
+
|
|
105
|
+
switch key {
|
|
106
|
+
|
|
107
|
+
// MARK: Flex container properties
|
|
108
|
+
|
|
109
|
+
case "flexDirection":
|
|
110
|
+
if let str = value as? String {
|
|
111
|
+
switch str {
|
|
112
|
+
case "row": flex.direction(.row)
|
|
113
|
+
case "row-reverse", "rowReverse": flex.direction(.rowReverse)
|
|
114
|
+
case "column-reverse", "columnReverse": flex.direction(.columnReverse)
|
|
115
|
+
default: flex.direction(.column)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true
|
|
119
|
+
|
|
120
|
+
case "justifyContent":
|
|
121
|
+
if let str = value as? String {
|
|
122
|
+
switch str {
|
|
123
|
+
case "flex-start", "flexStart", "start": flex.justifyContent(.start)
|
|
124
|
+
case "flex-end", "flexEnd", "end": flex.justifyContent(.end)
|
|
125
|
+
case "center": flex.justifyContent(.center)
|
|
126
|
+
case "space-between", "spaceBetween": flex.justifyContent(.spaceBetween)
|
|
127
|
+
case "space-around", "spaceAround": flex.justifyContent(.spaceAround)
|
|
128
|
+
case "space-evenly", "spaceEvenly": flex.justifyContent(.spaceEvenly)
|
|
129
|
+
default: flex.justifyContent(.start)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true
|
|
133
|
+
|
|
134
|
+
case "alignItems":
|
|
135
|
+
if let str = value as? String {
|
|
136
|
+
switch str {
|
|
137
|
+
case "flex-start", "flexStart", "start": flex.alignItems(.start)
|
|
138
|
+
case "flex-end", "flexEnd", "end": flex.alignItems(.end)
|
|
139
|
+
case "center": flex.alignItems(.center)
|
|
140
|
+
case "stretch": flex.alignItems(.stretch)
|
|
141
|
+
case "baseline": flex.alignItems(.baseline)
|
|
142
|
+
default: flex.alignItems(.stretch)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return true
|
|
146
|
+
|
|
147
|
+
case "alignSelf":
|
|
148
|
+
if let str = value as? String {
|
|
149
|
+
switch str {
|
|
150
|
+
case "auto": flex.alignSelf(.auto)
|
|
151
|
+
case "flex-start", "flexStart", "start": flex.alignSelf(.start)
|
|
152
|
+
case "flex-end", "flexEnd", "end": flex.alignSelf(.end)
|
|
153
|
+
case "center": flex.alignSelf(.center)
|
|
154
|
+
case "stretch": flex.alignSelf(.stretch)
|
|
155
|
+
case "baseline": flex.alignSelf(.baseline)
|
|
156
|
+
default: flex.alignSelf(.auto)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true
|
|
160
|
+
|
|
161
|
+
case "alignContent":
|
|
162
|
+
if let str = value as? String {
|
|
163
|
+
switch str {
|
|
164
|
+
case "flex-start", "flexStart", "start": flex.alignContent(.start)
|
|
165
|
+
case "flex-end", "flexEnd", "end": flex.alignContent(.end)
|
|
166
|
+
case "center": flex.alignContent(.center)
|
|
167
|
+
case "stretch": flex.alignContent(.stretch)
|
|
168
|
+
case "space-between", "spaceBetween": flex.alignContent(.spaceBetween)
|
|
169
|
+
case "space-around", "spaceAround": flex.alignContent(.spaceAround)
|
|
170
|
+
default: flex.alignContent(.stretch)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return true
|
|
174
|
+
|
|
175
|
+
case "flexWrap":
|
|
176
|
+
if let str = value as? String {
|
|
177
|
+
switch str {
|
|
178
|
+
case "wrap": flex.wrap(.wrap)
|
|
179
|
+
case "wrap-reverse", "wrapReverse": flex.wrap(.wrapReverse)
|
|
180
|
+
default: flex.wrap(.noWrap)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return true
|
|
184
|
+
|
|
185
|
+
// MARK: Flex item properties
|
|
186
|
+
|
|
187
|
+
case "flex":
|
|
188
|
+
if let num = yogaValue(value) {
|
|
189
|
+
// CSS "flex" shorthand: when a single number, it sets flexGrow.
|
|
190
|
+
// flex: 1 => grow(1), shrink(1), basis(0)
|
|
191
|
+
flex.grow(num)
|
|
192
|
+
if num > 0 {
|
|
193
|
+
flex.shrink(1)
|
|
194
|
+
flex.basis(0)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return true
|
|
198
|
+
|
|
199
|
+
case "flexGrow":
|
|
200
|
+
if let num = yogaValue(value) {
|
|
201
|
+
flex.grow(num)
|
|
202
|
+
}
|
|
203
|
+
return true
|
|
204
|
+
|
|
205
|
+
case "flexShrink":
|
|
206
|
+
if let num = yogaValue(value) {
|
|
207
|
+
flex.shrink(num)
|
|
208
|
+
}
|
|
209
|
+
return true
|
|
210
|
+
|
|
211
|
+
case "flexBasis":
|
|
212
|
+
if isAuto(value) {
|
|
213
|
+
flex.basis(nil) // nil means auto in FlexLayout
|
|
214
|
+
} else if let num = yogaValue(value) {
|
|
215
|
+
flex.basis(num)
|
|
216
|
+
}
|
|
217
|
+
return true
|
|
218
|
+
|
|
219
|
+
// MARK: Dimensions
|
|
220
|
+
|
|
221
|
+
case "width":
|
|
222
|
+
if isAuto(value) {
|
|
223
|
+
flex.width(nil)
|
|
224
|
+
} else if let pct = asPercent(value) {
|
|
225
|
+
flex.width(pct%)
|
|
226
|
+
} else if let num = yogaValue(value) {
|
|
227
|
+
flex.width(num)
|
|
228
|
+
}
|
|
229
|
+
return true
|
|
230
|
+
|
|
231
|
+
case "height":
|
|
232
|
+
if isAuto(value) {
|
|
233
|
+
flex.height(nil)
|
|
234
|
+
} else if let pct = asPercent(value) {
|
|
235
|
+
flex.height(pct%)
|
|
236
|
+
} else if let num = yogaValue(value) {
|
|
237
|
+
flex.height(num)
|
|
238
|
+
}
|
|
239
|
+
return true
|
|
240
|
+
|
|
241
|
+
case "minWidth":
|
|
242
|
+
if let pct = asPercent(value) {
|
|
243
|
+
flex.minWidth(pct%)
|
|
244
|
+
} else if let num = yogaValue(value) {
|
|
245
|
+
flex.minWidth(num)
|
|
246
|
+
}
|
|
247
|
+
return true
|
|
248
|
+
|
|
249
|
+
case "minHeight":
|
|
250
|
+
if let pct = asPercent(value) {
|
|
251
|
+
flex.minHeight(pct%)
|
|
252
|
+
} else if let num = yogaValue(value) {
|
|
253
|
+
flex.minHeight(num)
|
|
254
|
+
}
|
|
255
|
+
return true
|
|
256
|
+
|
|
257
|
+
case "maxWidth":
|
|
258
|
+
if let pct = asPercent(value) {
|
|
259
|
+
flex.maxWidth(pct%)
|
|
260
|
+
} else if let num = yogaValue(value) {
|
|
261
|
+
flex.maxWidth(num)
|
|
262
|
+
}
|
|
263
|
+
return true
|
|
264
|
+
|
|
265
|
+
case "maxHeight":
|
|
266
|
+
if let pct = asPercent(value) {
|
|
267
|
+
flex.maxHeight(pct%)
|
|
268
|
+
} else if let num = yogaValue(value) {
|
|
269
|
+
flex.maxHeight(num)
|
|
270
|
+
}
|
|
271
|
+
return true
|
|
272
|
+
|
|
273
|
+
case "aspectRatio":
|
|
274
|
+
if let num = yogaValue(value) {
|
|
275
|
+
flex.aspectRatio(num)
|
|
276
|
+
}
|
|
277
|
+
return true
|
|
278
|
+
|
|
279
|
+
// MARK: Padding
|
|
280
|
+
|
|
281
|
+
case "padding":
|
|
282
|
+
if let num = yogaValue(value) {
|
|
283
|
+
flex.padding(num)
|
|
284
|
+
}
|
|
285
|
+
return true
|
|
286
|
+
|
|
287
|
+
case "paddingTop":
|
|
288
|
+
if let num = yogaValue(value) {
|
|
289
|
+
flex.paddingTop(num)
|
|
290
|
+
}
|
|
291
|
+
return true
|
|
292
|
+
|
|
293
|
+
case "paddingRight":
|
|
294
|
+
if let num = yogaValue(value) {
|
|
295
|
+
flex.paddingRight(num)
|
|
296
|
+
}
|
|
297
|
+
return true
|
|
298
|
+
|
|
299
|
+
case "paddingBottom":
|
|
300
|
+
if let num = yogaValue(value) {
|
|
301
|
+
flex.paddingBottom(num)
|
|
302
|
+
}
|
|
303
|
+
return true
|
|
304
|
+
|
|
305
|
+
case "paddingLeft":
|
|
306
|
+
if let num = yogaValue(value) {
|
|
307
|
+
flex.paddingLeft(num)
|
|
308
|
+
}
|
|
309
|
+
return true
|
|
310
|
+
|
|
311
|
+
case "paddingHorizontal":
|
|
312
|
+
if let num = yogaValue(value) {
|
|
313
|
+
flex.paddingHorizontal(num)
|
|
314
|
+
}
|
|
315
|
+
return true
|
|
316
|
+
|
|
317
|
+
case "paddingVertical":
|
|
318
|
+
if let num = yogaValue(value) {
|
|
319
|
+
flex.paddingVertical(num)
|
|
320
|
+
}
|
|
321
|
+
return true
|
|
322
|
+
|
|
323
|
+
case "paddingStart":
|
|
324
|
+
if let num = yogaValue(value) {
|
|
325
|
+
flex.paddingStart(num)
|
|
326
|
+
}
|
|
327
|
+
return true
|
|
328
|
+
|
|
329
|
+
case "paddingEnd":
|
|
330
|
+
if let num = yogaValue(value) {
|
|
331
|
+
flex.paddingEnd(num)
|
|
332
|
+
}
|
|
333
|
+
return true
|
|
334
|
+
|
|
335
|
+
// MARK: Margin
|
|
336
|
+
|
|
337
|
+
case "margin":
|
|
338
|
+
// Note: FlexLayout does not expose auto margins via its Swift API.
|
|
339
|
+
// Point values are supported; auto margins are not.
|
|
340
|
+
if isAuto(value) {
|
|
341
|
+
// Auto margins not supported by FlexLayout — skip gracefully
|
|
342
|
+
} else if let num = yogaValue(value) {
|
|
343
|
+
flex.margin(num)
|
|
344
|
+
}
|
|
345
|
+
return true
|
|
346
|
+
|
|
347
|
+
case "marginTop":
|
|
348
|
+
if let num = yogaValue(value) {
|
|
349
|
+
flex.marginTop(num)
|
|
350
|
+
}
|
|
351
|
+
return true
|
|
352
|
+
|
|
353
|
+
case "marginRight":
|
|
354
|
+
if let num = yogaValue(value) {
|
|
355
|
+
flex.marginRight(num)
|
|
356
|
+
}
|
|
357
|
+
return true
|
|
358
|
+
|
|
359
|
+
case "marginBottom":
|
|
360
|
+
if let num = yogaValue(value) {
|
|
361
|
+
flex.marginBottom(num)
|
|
362
|
+
}
|
|
363
|
+
return true
|
|
364
|
+
|
|
365
|
+
case "marginLeft":
|
|
366
|
+
if let num = yogaValue(value) {
|
|
367
|
+
flex.marginLeft(num)
|
|
368
|
+
}
|
|
369
|
+
return true
|
|
370
|
+
|
|
371
|
+
case "marginHorizontal":
|
|
372
|
+
if let num = yogaValue(value) {
|
|
373
|
+
flex.marginHorizontal(num)
|
|
374
|
+
}
|
|
375
|
+
return true
|
|
376
|
+
|
|
377
|
+
case "marginVertical":
|
|
378
|
+
if let num = yogaValue(value) {
|
|
379
|
+
flex.marginVertical(num)
|
|
380
|
+
}
|
|
381
|
+
return true
|
|
382
|
+
|
|
383
|
+
case "marginStart":
|
|
384
|
+
if let num = yogaValue(value) {
|
|
385
|
+
flex.marginStart(num)
|
|
386
|
+
}
|
|
387
|
+
return true
|
|
388
|
+
|
|
389
|
+
case "marginEnd":
|
|
390
|
+
if let num = yogaValue(value) {
|
|
391
|
+
flex.marginEnd(num)
|
|
392
|
+
}
|
|
393
|
+
return true
|
|
394
|
+
|
|
395
|
+
// MARK: Gap
|
|
396
|
+
|
|
397
|
+
case "gap":
|
|
398
|
+
if let num = yogaValue(value) {
|
|
399
|
+
flex.gap(num)
|
|
400
|
+
}
|
|
401
|
+
return true
|
|
402
|
+
|
|
403
|
+
case "rowGap":
|
|
404
|
+
if let num = yogaValue(value) {
|
|
405
|
+
flex.rowGap(num)
|
|
406
|
+
}
|
|
407
|
+
return true
|
|
408
|
+
|
|
409
|
+
case "columnGap":
|
|
410
|
+
if let num = yogaValue(value) {
|
|
411
|
+
flex.columnGap(num)
|
|
412
|
+
}
|
|
413
|
+
return true
|
|
414
|
+
|
|
415
|
+
// MARK: Position
|
|
416
|
+
|
|
417
|
+
case "position":
|
|
418
|
+
if let str = value as? String {
|
|
419
|
+
switch str {
|
|
420
|
+
case "absolute": flex.position(.absolute)
|
|
421
|
+
case "relative": flex.position(.relative)
|
|
422
|
+
default: flex.position(.relative)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return true
|
|
426
|
+
|
|
427
|
+
case "top":
|
|
428
|
+
if let num = yogaValue(value) {
|
|
429
|
+
flex.top(num)
|
|
430
|
+
}
|
|
431
|
+
return true
|
|
432
|
+
|
|
433
|
+
case "right":
|
|
434
|
+
if let num = yogaValue(value) {
|
|
435
|
+
flex.right(num)
|
|
436
|
+
}
|
|
437
|
+
return true
|
|
438
|
+
|
|
439
|
+
case "bottom":
|
|
440
|
+
if let num = yogaValue(value) {
|
|
441
|
+
flex.bottom(num)
|
|
442
|
+
}
|
|
443
|
+
return true
|
|
444
|
+
|
|
445
|
+
case "left":
|
|
446
|
+
if let num = yogaValue(value) {
|
|
447
|
+
flex.left(num)
|
|
448
|
+
}
|
|
449
|
+
return true
|
|
450
|
+
|
|
451
|
+
case "start":
|
|
452
|
+
if let num = yogaValue(value) {
|
|
453
|
+
flex.start(num)
|
|
454
|
+
}
|
|
455
|
+
return true
|
|
456
|
+
|
|
457
|
+
case "end":
|
|
458
|
+
if let num = yogaValue(value) {
|
|
459
|
+
flex.end(num)
|
|
460
|
+
}
|
|
461
|
+
return true
|
|
462
|
+
|
|
463
|
+
// MARK: Overflow
|
|
464
|
+
|
|
465
|
+
case "overflow":
|
|
466
|
+
// Note: FlexLayout's Flex.overflow() is not exposed in the public API.
|
|
467
|
+
// We handle overflow purely via UIView.clipsToBounds.
|
|
468
|
+
if let str = value as? String {
|
|
469
|
+
switch str {
|
|
470
|
+
case "hidden":
|
|
471
|
+
view.clipsToBounds = true
|
|
472
|
+
default:
|
|
473
|
+
view.clipsToBounds = false
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return true
|
|
477
|
+
|
|
478
|
+
// MARK: Display
|
|
479
|
+
|
|
480
|
+
case "display":
|
|
481
|
+
if let str = value as? String {
|
|
482
|
+
switch str {
|
|
483
|
+
case "none":
|
|
484
|
+
flex.display(.none)
|
|
485
|
+
view.isHidden = true
|
|
486
|
+
default:
|
|
487
|
+
flex.display(.flex)
|
|
488
|
+
view.isHidden = false
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return true
|
|
492
|
+
|
|
493
|
+
// MARK: Direction (RTL/LTR)
|
|
494
|
+
|
|
495
|
+
case "direction":
|
|
496
|
+
if let str = value as? String {
|
|
497
|
+
switch str {
|
|
498
|
+
case "ltr": flex.direction(.LTR)
|
|
499
|
+
case "rtl": flex.direction(.RTL)
|
|
500
|
+
case "inherit": flex.direction(.inherit)
|
|
501
|
+
default: break
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return true
|
|
505
|
+
|
|
506
|
+
default:
|
|
507
|
+
return false
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// MARK: - Visual Properties (UIView)
|
|
512
|
+
|
|
513
|
+
/// Apply a visual property directly on the UIView. Returns true if recognized.
|
|
514
|
+
@discardableResult
|
|
515
|
+
private static func applyVisualProp(key: String, value: Any?, to view: UIView) -> Bool {
|
|
516
|
+
switch key {
|
|
517
|
+
|
|
518
|
+
case "backgroundColor":
|
|
519
|
+
if let colorStr = value as? String {
|
|
520
|
+
view.backgroundColor = UIColor.fromHex(colorStr)
|
|
521
|
+
} else {
|
|
522
|
+
view.backgroundColor = nil
|
|
523
|
+
}
|
|
524
|
+
return true
|
|
525
|
+
|
|
526
|
+
case "opacity":
|
|
527
|
+
if let num = yogaValue(value) {
|
|
528
|
+
view.alpha = num
|
|
529
|
+
} else {
|
|
530
|
+
view.alpha = 1.0
|
|
531
|
+
}
|
|
532
|
+
return true
|
|
533
|
+
|
|
534
|
+
case "borderRadius":
|
|
535
|
+
if let num = yogaValue(value) {
|
|
536
|
+
view.layer.cornerRadius = num
|
|
537
|
+
// Automatically enable clipping when border radius is set
|
|
538
|
+
if num > 0 {
|
|
539
|
+
view.clipsToBounds = true
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
view.layer.cornerRadius = 0
|
|
543
|
+
}
|
|
544
|
+
return true
|
|
545
|
+
|
|
546
|
+
case "borderTopLeftRadius":
|
|
547
|
+
if let num = yogaValue(value) {
|
|
548
|
+
applyCornerRadius(view: view, corner: .layerMinXMinYCorner, radius: num)
|
|
549
|
+
}
|
|
550
|
+
return true
|
|
551
|
+
|
|
552
|
+
case "borderTopRightRadius":
|
|
553
|
+
if let num = yogaValue(value) {
|
|
554
|
+
applyCornerRadius(view: view, corner: .layerMaxXMinYCorner, radius: num)
|
|
555
|
+
}
|
|
556
|
+
return true
|
|
557
|
+
|
|
558
|
+
case "borderBottomLeftRadius":
|
|
559
|
+
if let num = yogaValue(value) {
|
|
560
|
+
applyCornerRadius(view: view, corner: .layerMinXMaxYCorner, radius: num)
|
|
561
|
+
}
|
|
562
|
+
return true
|
|
563
|
+
|
|
564
|
+
case "borderBottomRightRadius":
|
|
565
|
+
if let num = yogaValue(value) {
|
|
566
|
+
applyCornerRadius(view: view, corner: .layerMaxXMaxYCorner, radius: num)
|
|
567
|
+
}
|
|
568
|
+
return true
|
|
569
|
+
|
|
570
|
+
case "borderWidth":
|
|
571
|
+
if let num = yogaValue(value) {
|
|
572
|
+
view.layer.borderWidth = num
|
|
573
|
+
} else {
|
|
574
|
+
view.layer.borderWidth = 0
|
|
575
|
+
}
|
|
576
|
+
return true
|
|
577
|
+
|
|
578
|
+
case "borderColor":
|
|
579
|
+
if let colorStr = value as? String {
|
|
580
|
+
view.layer.borderColor = UIColor.fromHex(colorStr).cgColor
|
|
581
|
+
} else {
|
|
582
|
+
view.layer.borderColor = nil
|
|
583
|
+
}
|
|
584
|
+
return true
|
|
585
|
+
|
|
586
|
+
case "shadowColor":
|
|
587
|
+
if let colorStr = value as? String {
|
|
588
|
+
view.layer.shadowColor = UIColor.fromHex(colorStr).cgColor
|
|
589
|
+
}
|
|
590
|
+
return true
|
|
591
|
+
|
|
592
|
+
case "shadowOpacity":
|
|
593
|
+
if let num = yogaValue(value) {
|
|
594
|
+
view.layer.shadowOpacity = Float(num)
|
|
595
|
+
}
|
|
596
|
+
return true
|
|
597
|
+
|
|
598
|
+
case "shadowRadius":
|
|
599
|
+
if let num = yogaValue(value) {
|
|
600
|
+
view.layer.shadowRadius = num
|
|
601
|
+
}
|
|
602
|
+
return true
|
|
603
|
+
|
|
604
|
+
case "shadowOffsetX":
|
|
605
|
+
if let num = yogaValue(value) {
|
|
606
|
+
view.layer.shadowOffset = CGSize(
|
|
607
|
+
width: num,
|
|
608
|
+
height: view.layer.shadowOffset.height
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
return true
|
|
612
|
+
|
|
613
|
+
case "shadowOffsetY":
|
|
614
|
+
if let num = yogaValue(value) {
|
|
615
|
+
view.layer.shadowOffset = CGSize(
|
|
616
|
+
width: view.layer.shadowOffset.width,
|
|
617
|
+
height: num
|
|
618
|
+
)
|
|
619
|
+
}
|
|
620
|
+
return true
|
|
621
|
+
|
|
622
|
+
case "shadowOffset":
|
|
623
|
+
if let dict = value as? [String: Any] {
|
|
624
|
+
let w = (dict["width"] as? Double).map { CGFloat($0) } ?? view.layer.shadowOffset.width
|
|
625
|
+
let h = (dict["height"] as? Double).map { CGFloat($0) } ?? view.layer.shadowOffset.height
|
|
626
|
+
view.layer.shadowOffset = CGSize(width: w, height: h)
|
|
627
|
+
}
|
|
628
|
+
return true
|
|
629
|
+
|
|
630
|
+
case "transform":
|
|
631
|
+
if let transforms = value as? [[String: Any]] {
|
|
632
|
+
var result = CGAffineTransform.identity
|
|
633
|
+
for dict in transforms {
|
|
634
|
+
if let rotateStr = dict["rotate"] as? String {
|
|
635
|
+
let angle = parseAngle(rotateStr)
|
|
636
|
+
result = result.rotated(by: angle)
|
|
637
|
+
}
|
|
638
|
+
if let scale = dict["scale"] as? Double {
|
|
639
|
+
result = result.scaledBy(x: CGFloat(scale), y: CGFloat(scale))
|
|
640
|
+
}
|
|
641
|
+
if let scaleX = dict["scaleX"] as? Double {
|
|
642
|
+
result = result.scaledBy(x: CGFloat(scaleX), y: 1)
|
|
643
|
+
}
|
|
644
|
+
if let scaleY = dict["scaleY"] as? Double {
|
|
645
|
+
result = result.scaledBy(x: 1, y: CGFloat(scaleY))
|
|
646
|
+
}
|
|
647
|
+
if let tx = dict["translateX"] as? Double {
|
|
648
|
+
result = result.translatedBy(x: CGFloat(tx), y: 0)
|
|
649
|
+
}
|
|
650
|
+
if let ty = dict["translateY"] as? Double {
|
|
651
|
+
result = result.translatedBy(x: 0, y: CGFloat(ty))
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
view.transform = result
|
|
655
|
+
} else {
|
|
656
|
+
view.transform = .identity
|
|
657
|
+
}
|
|
658
|
+
return true
|
|
659
|
+
|
|
660
|
+
case "hidden":
|
|
661
|
+
view.isHidden = (value as? Bool) ?? false
|
|
662
|
+
return true
|
|
663
|
+
|
|
664
|
+
case "isHidden":
|
|
665
|
+
if let hidden = value as? Bool {
|
|
666
|
+
view.isHidden = hidden
|
|
667
|
+
} else if let hidden = value as? Int {
|
|
668
|
+
view.isHidden = hidden != 0
|
|
669
|
+
}
|
|
670
|
+
return true
|
|
671
|
+
|
|
672
|
+
case "zIndex":
|
|
673
|
+
if let num = yogaValue(value) {
|
|
674
|
+
view.layer.zPosition = num
|
|
675
|
+
}
|
|
676
|
+
return true
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
case "accessibilityLabel":
|
|
680
|
+
view.accessibilityLabel = value as? String
|
|
681
|
+
view.isAccessibilityElement = true
|
|
682
|
+
return true
|
|
683
|
+
|
|
684
|
+
case "accessibilityHint":
|
|
685
|
+
view.accessibilityHint = value as? String
|
|
686
|
+
view.isAccessibilityElement = true
|
|
687
|
+
return true
|
|
688
|
+
|
|
689
|
+
case "accessibilityValue":
|
|
690
|
+
view.accessibilityValue = value as? String
|
|
691
|
+
view.isAccessibilityElement = true
|
|
692
|
+
return true
|
|
693
|
+
|
|
694
|
+
case "accessibilityRole":
|
|
695
|
+
if let role = value as? String {
|
|
696
|
+
switch role {
|
|
697
|
+
case "button": view.accessibilityTraits = .button
|
|
698
|
+
case "link": view.accessibilityTraits = .link
|
|
699
|
+
case "header": view.accessibilityTraits = .header
|
|
700
|
+
case "image": view.accessibilityTraits = .image
|
|
701
|
+
case "selected": view.accessibilityTraits = .selected
|
|
702
|
+
case "text": view.accessibilityTraits = .staticText
|
|
703
|
+
case "adjustable": view.accessibilityTraits = .adjustable
|
|
704
|
+
case "search": view.accessibilityTraits = .searchField
|
|
705
|
+
case "tab": view.accessibilityTraits = .tabBar
|
|
706
|
+
case "none": view.accessibilityTraits = .none
|
|
707
|
+
default: break
|
|
708
|
+
}
|
|
709
|
+
view.isAccessibilityElement = true
|
|
710
|
+
}
|
|
711
|
+
return true
|
|
712
|
+
|
|
713
|
+
case "accessibilityState":
|
|
714
|
+
if let state = value as? [String: Any] {
|
|
715
|
+
var traits = view.accessibilityTraits
|
|
716
|
+
if let disabled = state["disabled"] as? Bool, disabled { traits.insert(.notEnabled) }
|
|
717
|
+
if let selected = state["selected"] as? Bool, selected { traits.insert(.selected) }
|
|
718
|
+
if let checked = state["checked"] as? Bool, checked { traits.insert(.selected) }
|
|
719
|
+
view.accessibilityTraits = traits
|
|
720
|
+
view.isAccessibilityElement = true
|
|
721
|
+
}
|
|
722
|
+
return true
|
|
723
|
+
|
|
724
|
+
case "accessible":
|
|
725
|
+
let acc = (value as? Bool) ?? (value as? NSNumber)?.boolValue ?? false
|
|
726
|
+
view.isAccessibilityElement = acc
|
|
727
|
+
return true
|
|
728
|
+
|
|
729
|
+
case "importantForAccessibility":
|
|
730
|
+
if let val = value as? String {
|
|
731
|
+
view.accessibilityElementsHidden = (val == "no-hide-descendants")
|
|
732
|
+
}
|
|
733
|
+
return true
|
|
734
|
+
|
|
735
|
+
default:
|
|
736
|
+
return false
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// MARK: - Text Properties
|
|
741
|
+
|
|
742
|
+
/// Apply text-specific properties. Returns true if recognized.
|
|
743
|
+
/// This is a fallback — VTextFactory handles these directly when it can.
|
|
744
|
+
@discardableResult
|
|
745
|
+
private static func applyTextProp(key: String, value: Any?, to view: UIView) -> Bool {
|
|
746
|
+
guard let label = view as? UILabel else { return false }
|
|
747
|
+
|
|
748
|
+
switch key {
|
|
749
|
+
case "fontSize":
|
|
750
|
+
if let num = yogaValue(value) {
|
|
751
|
+
label.font = label.font.withSize(num)
|
|
752
|
+
label.flex.markDirty()
|
|
753
|
+
}
|
|
754
|
+
return true
|
|
755
|
+
|
|
756
|
+
case "fontWeight":
|
|
757
|
+
if let str = value as? String {
|
|
758
|
+
let weight = VTextFactory.fontWeightMap[str] ?? .regular
|
|
759
|
+
label.font = UIFont.systemFont(ofSize: label.font.pointSize, weight: weight)
|
|
760
|
+
label.flex.markDirty()
|
|
761
|
+
}
|
|
762
|
+
return true
|
|
763
|
+
|
|
764
|
+
case "color":
|
|
765
|
+
if let colorStr = value as? String {
|
|
766
|
+
label.textColor = UIColor.fromHex(colorStr)
|
|
767
|
+
}
|
|
768
|
+
return true
|
|
769
|
+
|
|
770
|
+
case "textAlign":
|
|
771
|
+
if let alignStr = value as? String {
|
|
772
|
+
label.textAlignment = VTextFactory.textAlignMap[alignStr] ?? .natural
|
|
773
|
+
}
|
|
774
|
+
return true
|
|
775
|
+
|
|
776
|
+
case "fontStyle":
|
|
777
|
+
if let str = value as? String, str == "italic" {
|
|
778
|
+
let descriptor = label.font.fontDescriptor.withSymbolicTraits(.traitItalic) ?? label.font.fontDescriptor
|
|
779
|
+
label.font = UIFont(descriptor: descriptor, size: label.font.pointSize)
|
|
780
|
+
label.flex.markDirty()
|
|
781
|
+
} else {
|
|
782
|
+
// Remove italic if "normal"
|
|
783
|
+
var traits = label.font.fontDescriptor.symbolicTraits
|
|
784
|
+
traits.remove(.traitItalic)
|
|
785
|
+
if let descriptor = label.font.fontDescriptor.withSymbolicTraits(traits) {
|
|
786
|
+
label.font = UIFont(descriptor: descriptor, size: label.font.pointSize)
|
|
787
|
+
label.flex.markDirty()
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return true
|
|
791
|
+
|
|
792
|
+
case "lineHeight":
|
|
793
|
+
if let num = yogaValue(value) {
|
|
794
|
+
let paragraphStyle = NSMutableParagraphStyle()
|
|
795
|
+
paragraphStyle.minimumLineHeight = num
|
|
796
|
+
paragraphStyle.maximumLineHeight = num
|
|
797
|
+
paragraphStyle.alignment = label.textAlignment
|
|
798
|
+
let text = label.text ?? ""
|
|
799
|
+
let attrs: [NSAttributedString.Key: Any] = [
|
|
800
|
+
.paragraphStyle: paragraphStyle,
|
|
801
|
+
.font: label.font as Any
|
|
802
|
+
]
|
|
803
|
+
label.attributedText = NSAttributedString(string: text, attributes: attrs)
|
|
804
|
+
label.flex.markDirty()
|
|
805
|
+
}
|
|
806
|
+
return true
|
|
807
|
+
|
|
808
|
+
case "letterSpacing":
|
|
809
|
+
if let num = yogaValue(value) {
|
|
810
|
+
let text = label.text ?? ""
|
|
811
|
+
let attrs: [NSAttributedString.Key: Any] = [
|
|
812
|
+
.kern: num,
|
|
813
|
+
.font: label.font as Any
|
|
814
|
+
]
|
|
815
|
+
label.attributedText = NSAttributedString(string: text, attributes: attrs)
|
|
816
|
+
label.flex.markDirty()
|
|
817
|
+
}
|
|
818
|
+
return true
|
|
819
|
+
|
|
820
|
+
case "textDecorationLine":
|
|
821
|
+
if let str = value as? String {
|
|
822
|
+
let text = label.text ?? ""
|
|
823
|
+
var attrs: [NSAttributedString.Key: Any] = [.font: label.font as Any]
|
|
824
|
+
switch str {
|
|
825
|
+
case "underline":
|
|
826
|
+
attrs[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
|
827
|
+
case "line-through", "lineThrough":
|
|
828
|
+
attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
|
|
829
|
+
case "underline line-through":
|
|
830
|
+
attrs[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
|
831
|
+
attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
|
|
832
|
+
default:
|
|
833
|
+
break
|
|
834
|
+
}
|
|
835
|
+
label.attributedText = NSAttributedString(string: text, attributes: attrs)
|
|
836
|
+
label.flex.markDirty()
|
|
837
|
+
}
|
|
838
|
+
return true
|
|
839
|
+
|
|
840
|
+
case "textTransform":
|
|
841
|
+
if let str = value as? String, let text = label.text {
|
|
842
|
+
switch str {
|
|
843
|
+
case "uppercase": label.text = text.uppercased()
|
|
844
|
+
case "lowercase": label.text = text.lowercased()
|
|
845
|
+
case "capitalize": label.text = text.capitalized
|
|
846
|
+
default: break
|
|
847
|
+
}
|
|
848
|
+
label.flex.markDirty()
|
|
849
|
+
}
|
|
850
|
+
return true
|
|
851
|
+
|
|
852
|
+
default:
|
|
853
|
+
return false
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// MARK: - Corner radius helpers
|
|
858
|
+
|
|
859
|
+
/// Apply a corner radius to a specific corner of the view.
|
|
860
|
+
private static func applyCornerRadius(view: UIView, corner: CACornerMask, radius: CGFloat) {
|
|
861
|
+
view.clipsToBounds = true
|
|
862
|
+
view.layer.maskedCorners.insert(corner)
|
|
863
|
+
view.layer.cornerRadius = max(view.layer.cornerRadius, radius)
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// MARK: - Helpers
|
|
867
|
+
|
|
868
|
+
/// Parse an angle string into radians.
|
|
869
|
+
/// Supports "45deg" (degrees) and "1.5rad" (radians).
|
|
870
|
+
private static func parseAngle(_ str: String) -> CGFloat {
|
|
871
|
+
let s = str.trimmingCharacters(in: .whitespaces).lowercased()
|
|
872
|
+
if s.hasSuffix("deg"), let num = Double(s.dropLast(3)) {
|
|
873
|
+
return CGFloat(num * .pi / 180)
|
|
874
|
+
}
|
|
875
|
+
if s.hasSuffix("rad"), let num = Double(s.dropLast(3)) {
|
|
876
|
+
return CGFloat(num)
|
|
877
|
+
}
|
|
878
|
+
// Fallback: try to parse as raw number (radians)
|
|
879
|
+
if let num = Double(s) {
|
|
880
|
+
return CGFloat(num)
|
|
881
|
+
}
|
|
882
|
+
return 0
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
#endif
|