@thelacanians/vue-native-cli 0.4.14 → 0.6.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/dist/cli.js +789 -200
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +156 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +80 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +244 -112
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VAlertDialog — presents an NSAlert when `visible=true`.
|
|
5
|
+
/// The view itself is a zero-size, hidden placeholder in the native tree.
|
|
6
|
+
final class VAlertDialogFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
// MARK: - Associated-object keys
|
|
9
|
+
|
|
10
|
+
private static var titleKey: UInt8 = 0
|
|
11
|
+
private static var messageKey: UInt8 = 1
|
|
12
|
+
private static var buttonsKey: UInt8 = 2
|
|
13
|
+
private static var onActionKey: UInt8 = 3
|
|
14
|
+
|
|
15
|
+
// MARK: - NativeComponentFactory
|
|
16
|
+
|
|
17
|
+
func createView() -> NSView {
|
|
18
|
+
let view = FlippedView()
|
|
19
|
+
view.isHidden = true
|
|
20
|
+
let node = view.ensureLayoutNode()
|
|
21
|
+
node.width = .points(0)
|
|
22
|
+
node.height = .points(0)
|
|
23
|
+
return view
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
27
|
+
switch key {
|
|
28
|
+
case "visible":
|
|
29
|
+
let visible = (value as? Bool) ?? ((value as? Int).map { $0 != 0 } ?? false)
|
|
30
|
+
if visible {
|
|
31
|
+
presentAlert(for: view)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case "title":
|
|
35
|
+
objc_setAssociatedObject(
|
|
36
|
+
view, &VAlertDialogFactory.titleKey,
|
|
37
|
+
value as? String,
|
|
38
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
case "message":
|
|
42
|
+
objc_setAssociatedObject(
|
|
43
|
+
view, &VAlertDialogFactory.messageKey,
|
|
44
|
+
value as? String,
|
|
45
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
case "buttons":
|
|
49
|
+
objc_setAssociatedObject(
|
|
50
|
+
view, &VAlertDialogFactory.buttonsKey,
|
|
51
|
+
value,
|
|
52
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
default:
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
61
|
+
switch event {
|
|
62
|
+
case "action":
|
|
63
|
+
objc_setAssociatedObject(
|
|
64
|
+
view, &VAlertDialogFactory.onActionKey,
|
|
65
|
+
handler as AnyObject,
|
|
66
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
67
|
+
)
|
|
68
|
+
default:
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func removeEventListener(view: NSView, event: String) {
|
|
74
|
+
switch event {
|
|
75
|
+
case "action":
|
|
76
|
+
objc_setAssociatedObject(view, &VAlertDialogFactory.onActionKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
77
|
+
default:
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// MARK: - Alert Presentation
|
|
83
|
+
|
|
84
|
+
private func presentAlert(for view: NSView) {
|
|
85
|
+
let title = objc_getAssociatedObject(view, &VAlertDialogFactory.titleKey) as? String
|
|
86
|
+
let message = objc_getAssociatedObject(view, &VAlertDialogFactory.messageKey) as? String
|
|
87
|
+
|
|
88
|
+
let alert = NSAlert()
|
|
89
|
+
alert.messageText = title ?? ""
|
|
90
|
+
alert.informativeText = message ?? ""
|
|
91
|
+
alert.alertStyle = .informational
|
|
92
|
+
|
|
93
|
+
// Parse buttons — supports both [String] and [[String: Any]] formats
|
|
94
|
+
var buttonLabels: [String] = []
|
|
95
|
+
if let buttonsArray = objc_getAssociatedObject(view, &VAlertDialogFactory.buttonsKey) as? [String] {
|
|
96
|
+
buttonLabels = buttonsArray
|
|
97
|
+
} else if let buttonsArray = objc_getAssociatedObject(view, &VAlertDialogFactory.buttonsKey) as? [[String: Any]] {
|
|
98
|
+
buttonLabels = buttonsArray.compactMap { $0["label"] as? String }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add buttons (first is default, last is cancel style if > 1)
|
|
102
|
+
if buttonLabels.isEmpty {
|
|
103
|
+
alert.addButton(withTitle: "OK")
|
|
104
|
+
buttonLabels = ["OK"]
|
|
105
|
+
} else {
|
|
106
|
+
for label in buttonLabels {
|
|
107
|
+
alert.addButton(withTitle: label)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Present as sheet if we have a key window, otherwise run modal
|
|
112
|
+
if let window = NSApplication.shared.keyWindow {
|
|
113
|
+
alert.beginSheetModal(for: window) { [weak view] response in
|
|
114
|
+
guard let view = view else { return }
|
|
115
|
+
let index = response.rawValue - NSApplication.ModalResponse.alertFirstButtonReturn.rawValue
|
|
116
|
+
let label = index < buttonLabels.count ? buttonLabels[index] : ""
|
|
117
|
+
self.fireAction(for: view, buttonIndex: index, buttonLabel: label)
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
let response = alert.runModal()
|
|
121
|
+
let index = response.rawValue - NSApplication.ModalResponse.alertFirstButtonReturn.rawValue
|
|
122
|
+
let label = index < buttonLabels.count ? buttonLabels[index] : ""
|
|
123
|
+
fireAction(for: view, buttonIndex: index, buttonLabel: label)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private func fireAction(for view: NSView, buttonIndex: Int, buttonLabel: String) {
|
|
128
|
+
if let handler = objc_getAssociatedObject(view, &VAlertDialogFactory.onActionKey) as? ((Any?) -> Void) {
|
|
129
|
+
handler(["buttonIndex": buttonIndex, "buttonLabel": buttonLabel])
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VButton — the clickable/pressable component.
|
|
5
|
+
/// Maps to a ClickableView (custom NSView subclass) with a LayoutNode.
|
|
6
|
+
/// Supports press and long press events with configurable active opacity.
|
|
7
|
+
final class VButtonFactory: NativeComponentFactory {
|
|
8
|
+
|
|
9
|
+
// MARK: - NativeComponentFactory
|
|
10
|
+
|
|
11
|
+
func createView() -> NSView {
|
|
12
|
+
let clickable = ClickableView()
|
|
13
|
+
// Ensure layout node is attached
|
|
14
|
+
clickable.ensureLayoutNode()
|
|
15
|
+
return clickable
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
19
|
+
guard let clickable = view as? ClickableView else {
|
|
20
|
+
// Fallback to StyleEngine for non-ClickableView
|
|
21
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
switch key {
|
|
26
|
+
case "disabled":
|
|
27
|
+
if let disabled = value as? Bool {
|
|
28
|
+
clickable.isDisabled = disabled
|
|
29
|
+
} else if let disabled = value as? Int {
|
|
30
|
+
clickable.isDisabled = disabled != 0
|
|
31
|
+
} else {
|
|
32
|
+
clickable.isDisabled = false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case "activeOpacity":
|
|
36
|
+
if let opacity = value as? Double {
|
|
37
|
+
clickable.activeOpacity = CGFloat(opacity)
|
|
38
|
+
} else if let opacity = value as? Int {
|
|
39
|
+
clickable.activeOpacity = CGFloat(opacity)
|
|
40
|
+
} else {
|
|
41
|
+
clickable.activeOpacity = 0.7
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
default:
|
|
45
|
+
// Delegate to StyleEngine for layout/visual styling
|
|
46
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
51
|
+
guard let clickable = view as? ClickableView else { return }
|
|
52
|
+
|
|
53
|
+
switch event {
|
|
54
|
+
case "press":
|
|
55
|
+
clickable.onPress = {
|
|
56
|
+
handler(nil)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case "longpress":
|
|
60
|
+
clickable.onLongPress = {
|
|
61
|
+
handler(nil)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
default:
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func removeEventListener(view: NSView, event: String) {
|
|
70
|
+
guard let clickable = view as? ClickableView else { return }
|
|
71
|
+
|
|
72
|
+
switch event {
|
|
73
|
+
case "press":
|
|
74
|
+
clickable.onPress = nil
|
|
75
|
+
|
|
76
|
+
case "longpress":
|
|
77
|
+
clickable.onLongPress = nil
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VCheckbox — a checkbox toggle control.
|
|
5
|
+
/// Maps to NSButton with .checkbox style.
|
|
6
|
+
final class VCheckboxFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
private static var changeHandlerKey: UInt8 = 0
|
|
9
|
+
|
|
10
|
+
// MARK: - NativeComponentFactory
|
|
11
|
+
|
|
12
|
+
func createView() -> NSView {
|
|
13
|
+
let button = NSButton()
|
|
14
|
+
button.setButtonType(.switch) // .switch is the checkbox type in AppKit
|
|
15
|
+
button.title = ""
|
|
16
|
+
button.state = .off
|
|
17
|
+
button.wantsLayer = true
|
|
18
|
+
button.ensureLayoutNode()
|
|
19
|
+
return button
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
23
|
+
guard let button = view as? NSButton else {
|
|
24
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
switch key {
|
|
29
|
+
case "checked", "value":
|
|
30
|
+
let checked: Bool
|
|
31
|
+
if let val = value as? Bool {
|
|
32
|
+
checked = val
|
|
33
|
+
} else if let val = value as? Int {
|
|
34
|
+
checked = val != 0
|
|
35
|
+
} else {
|
|
36
|
+
checked = false
|
|
37
|
+
}
|
|
38
|
+
let newState: NSControl.StateValue = checked ? .on : .off
|
|
39
|
+
if button.state != newState {
|
|
40
|
+
button.state = newState
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case "label", "title":
|
|
44
|
+
if let text = value as? String {
|
|
45
|
+
button.title = text
|
|
46
|
+
} else {
|
|
47
|
+
button.title = ""
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
case "disabled":
|
|
51
|
+
if let disabled = value as? Bool {
|
|
52
|
+
button.isEnabled = !disabled
|
|
53
|
+
} else if let disabled = value as? Int {
|
|
54
|
+
button.isEnabled = disabled == 0
|
|
55
|
+
} else {
|
|
56
|
+
button.isEnabled = true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case "tintColor":
|
|
60
|
+
if let colorStr = value as? String {
|
|
61
|
+
button.contentTintColor = NSColor.fromHex(colorStr)
|
|
62
|
+
} else {
|
|
63
|
+
button.contentTintColor = nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
default:
|
|
67
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
72
|
+
guard let button = view as? NSButton, event == "change" else { return }
|
|
73
|
+
|
|
74
|
+
let proxy = CheckboxActionProxy(handler: handler)
|
|
75
|
+
button.target = proxy
|
|
76
|
+
button.action = #selector(CheckboxActionProxy.toggled(_:))
|
|
77
|
+
objc_setAssociatedObject(
|
|
78
|
+
button,
|
|
79
|
+
&VCheckboxFactory.changeHandlerKey,
|
|
80
|
+
proxy,
|
|
81
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func removeEventListener(view: NSView, event: String) {
|
|
86
|
+
guard let button = view as? NSButton, event == "change" else { return }
|
|
87
|
+
|
|
88
|
+
button.target = nil
|
|
89
|
+
button.action = nil
|
|
90
|
+
objc_setAssociatedObject(button, &VCheckboxFactory.changeHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MARK: - CheckboxActionProxy
|
|
95
|
+
|
|
96
|
+
/// Target-action proxy that routes NSButton checkbox state changes to a closure handler.
|
|
97
|
+
final class CheckboxActionProxy: NSObject {
|
|
98
|
+
|
|
99
|
+
let handler: (Any?) -> Void
|
|
100
|
+
|
|
101
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
102
|
+
self.handler = handler
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc func toggled(_ sender: NSButton) {
|
|
106
|
+
handler(["checked": sender.state == .on])
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VDropdown — a dropdown selection control.
|
|
5
|
+
/// Maps to NSPopUpButton configured for dropdown selection.
|
|
6
|
+
final class VDropdownFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
private static var changeHandlerKey: UInt8 = 0
|
|
9
|
+
nonisolated(unsafe) static var optionsKey: UInt8 = 0
|
|
10
|
+
nonisolated(unsafe) static var placeholderKey: UInt8 = 0
|
|
11
|
+
|
|
12
|
+
// MARK: - NativeComponentFactory
|
|
13
|
+
|
|
14
|
+
func createView() -> NSView {
|
|
15
|
+
let popup = NSPopUpButton()
|
|
16
|
+
popup.pullsDown = false
|
|
17
|
+
popup.wantsLayer = true
|
|
18
|
+
popup.ensureLayoutNode()
|
|
19
|
+
return popup
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
23
|
+
guard let popup = view as? NSPopUpButton else {
|
|
24
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
switch key {
|
|
29
|
+
case "options":
|
|
30
|
+
popup.removeAllItems()
|
|
31
|
+
var storedOptions: [[String: Any]] = []
|
|
32
|
+
|
|
33
|
+
// Add placeholder as first disabled item if set
|
|
34
|
+
let placeholder = objc_getAssociatedObject(popup, &VDropdownFactory.placeholderKey) as? String
|
|
35
|
+
|
|
36
|
+
if let options = value as? [[String: Any]] {
|
|
37
|
+
if let placeholder = placeholder {
|
|
38
|
+
popup.addItem(withTitle: placeholder)
|
|
39
|
+
popup.item(at: 0)?.isEnabled = false
|
|
40
|
+
}
|
|
41
|
+
for option in options {
|
|
42
|
+
let label = option["label"] as? String ?? option["value"] as? String ?? ""
|
|
43
|
+
popup.addItem(withTitle: label)
|
|
44
|
+
storedOptions.append(option)
|
|
45
|
+
}
|
|
46
|
+
} else if let strings = value as? [String] {
|
|
47
|
+
if let placeholder = placeholder {
|
|
48
|
+
popup.addItem(withTitle: placeholder)
|
|
49
|
+
popup.item(at: 0)?.isEnabled = false
|
|
50
|
+
}
|
|
51
|
+
for str in strings {
|
|
52
|
+
popup.addItem(withTitle: str)
|
|
53
|
+
storedOptions.append(["label": str, "value": str])
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
objc_setAssociatedObject(popup, &VDropdownFactory.optionsKey, storedOptions, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
57
|
+
|
|
58
|
+
case "selectedValue":
|
|
59
|
+
if let val = value as? String,
|
|
60
|
+
let options = objc_getAssociatedObject(popup, &VDropdownFactory.optionsKey) as? [[String: Any]] {
|
|
61
|
+
let offset = hasPlaceholder(popup) ? 1 : 0
|
|
62
|
+
if let idx = options.firstIndex(where: { ($0["value"] as? String) == val }) {
|
|
63
|
+
popup.selectItem(at: idx + offset)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case "placeholder":
|
|
68
|
+
objc_setAssociatedObject(popup, &VDropdownFactory.placeholderKey, value as? String, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
69
|
+
// Rebuild items if options are already set
|
|
70
|
+
if let options = objc_getAssociatedObject(popup, &VDropdownFactory.optionsKey) as? [[String: Any]], !options.isEmpty {
|
|
71
|
+
updateProp(view: view, key: "options", value: options)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case "disabled":
|
|
75
|
+
if let disabled = value as? Bool {
|
|
76
|
+
popup.isEnabled = !disabled
|
|
77
|
+
} else if let disabled = value as? Int {
|
|
78
|
+
popup.isEnabled = disabled == 0
|
|
79
|
+
} else {
|
|
80
|
+
popup.isEnabled = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case "tintColor":
|
|
84
|
+
if let colorStr = value as? String {
|
|
85
|
+
popup.contentTintColor = NSColor.fromHex(colorStr)
|
|
86
|
+
} else {
|
|
87
|
+
popup.contentTintColor = nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
96
|
+
guard let popup = view as? NSPopUpButton, event == "change" else { return }
|
|
97
|
+
|
|
98
|
+
let proxy = DropdownActionProxy(popup: popup, handler: handler)
|
|
99
|
+
popup.target = proxy
|
|
100
|
+
popup.action = #selector(DropdownActionProxy.selectionChanged(_:))
|
|
101
|
+
objc_setAssociatedObject(
|
|
102
|
+
popup,
|
|
103
|
+
&VDropdownFactory.changeHandlerKey,
|
|
104
|
+
proxy,
|
|
105
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func removeEventListener(view: NSView, event: String) {
|
|
110
|
+
guard let popup = view as? NSPopUpButton, event == "change" else { return }
|
|
111
|
+
|
|
112
|
+
popup.target = nil
|
|
113
|
+
popup.action = nil
|
|
114
|
+
objc_setAssociatedObject(popup, &VDropdownFactory.changeHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - Private
|
|
118
|
+
|
|
119
|
+
private func hasPlaceholder(_ popup: NSPopUpButton) -> Bool {
|
|
120
|
+
return objc_getAssociatedObject(popup, &VDropdownFactory.placeholderKey) as? String != nil
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// MARK: - DropdownActionProxy
|
|
125
|
+
|
|
126
|
+
/// Target-action proxy that routes NSPopUpButton selection changes to a closure handler.
|
|
127
|
+
final class DropdownActionProxy: NSObject {
|
|
128
|
+
|
|
129
|
+
private weak var popup: NSPopUpButton?
|
|
130
|
+
private let handler: (Any?) -> Void
|
|
131
|
+
|
|
132
|
+
init(popup: NSPopUpButton, handler: @escaping (Any?) -> Void) {
|
|
133
|
+
self.popup = popup
|
|
134
|
+
self.handler = handler
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc func selectionChanged(_ sender: NSPopUpButton) {
|
|
138
|
+
let options = objc_getAssociatedObject(sender, &VDropdownFactory.optionsKey) as? [[String: Any]] ?? []
|
|
139
|
+
let hasPlaceholder = objc_getAssociatedObject(sender, &VDropdownFactory.placeholderKey) as? String != nil
|
|
140
|
+
let rawIndex = sender.indexOfSelectedItem
|
|
141
|
+
let offset = hasPlaceholder ? 1 : 0
|
|
142
|
+
let optionIndex = rawIndex - offset
|
|
143
|
+
|
|
144
|
+
guard optionIndex >= 0 && optionIndex < options.count else { return }
|
|
145
|
+
|
|
146
|
+
let option = options[optionIndex]
|
|
147
|
+
let label = option["label"] as? String ?? sender.titleOfSelectedItem ?? ""
|
|
148
|
+
let value = option["value"] as? String ?? label
|
|
149
|
+
|
|
150
|
+
handler([
|
|
151
|
+
"value": value,
|
|
152
|
+
"label": label
|
|
153
|
+
])
|
|
154
|
+
}
|
|
155
|
+
}
|