@thelacanians/vue-native-cli 0.4.15 → 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 +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- 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/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 +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- 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
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VImage — async image loading component.
|
|
5
|
+
/// Maps to NSImageView with URL-based async loading via URLSession,
|
|
6
|
+
/// NSCache for in-memory caching, and various resize modes.
|
|
7
|
+
final class VImageFactory: NativeComponentFactory {
|
|
8
|
+
|
|
9
|
+
// MARK: - Associated object keys
|
|
10
|
+
|
|
11
|
+
private static var loadTaskKey: UInt8 = 0
|
|
12
|
+
private static var loadHandlerKey: UInt8 = 0
|
|
13
|
+
private static var errorHandlerKey: UInt8 = 0
|
|
14
|
+
|
|
15
|
+
// MARK: - Shared image cache
|
|
16
|
+
|
|
17
|
+
private static let imageCache = NSCache<NSString, NSImage>()
|
|
18
|
+
|
|
19
|
+
// MARK: - NativeComponentFactory
|
|
20
|
+
|
|
21
|
+
func createView() -> NSView {
|
|
22
|
+
let imageView = NSImageView()
|
|
23
|
+
imageView.imageScaling = .scaleProportionallyUpOrDown
|
|
24
|
+
imageView.wantsLayer = true
|
|
25
|
+
imageView.ensureLayoutNode()
|
|
26
|
+
return imageView
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
30
|
+
guard let imageView = view as? NSImageView else {
|
|
31
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch key {
|
|
36
|
+
case "source", "src":
|
|
37
|
+
// Cancel any in-flight load
|
|
38
|
+
cancelLoad(on: view)
|
|
39
|
+
|
|
40
|
+
guard let source = value as? String, !source.isEmpty else {
|
|
41
|
+
imageView.image = nil
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Try loading as URL
|
|
46
|
+
if let url = URL(string: source), url.scheme == "https" || url.scheme == "http" {
|
|
47
|
+
loadImageFromURL(url, into: imageView)
|
|
48
|
+
} else {
|
|
49
|
+
// Try loading as local resource name
|
|
50
|
+
if let image = NSImage(named: source) {
|
|
51
|
+
imageView.image = image
|
|
52
|
+
fireLoadEvent(for: imageView, image: image)
|
|
53
|
+
} else if let image = loadFromBundle(source) {
|
|
54
|
+
imageView.image = image
|
|
55
|
+
fireLoadEvent(for: imageView, image: image)
|
|
56
|
+
} else {
|
|
57
|
+
fireErrorEvent(for: imageView, message: "Image not found: \(source)")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
case "resizeMode":
|
|
62
|
+
guard let mode = value as? String else { return }
|
|
63
|
+
switch mode {
|
|
64
|
+
case "cover":
|
|
65
|
+
imageView.imageScaling = .scaleProportionallyUpOrDown
|
|
66
|
+
imageView.layer?.contentsGravity = .resizeAspectFill
|
|
67
|
+
imageView.layer?.masksToBounds = true
|
|
68
|
+
case "contain":
|
|
69
|
+
imageView.imageScaling = .scaleProportionallyDown
|
|
70
|
+
imageView.layer?.contentsGravity = .resizeAspect
|
|
71
|
+
case "stretch":
|
|
72
|
+
imageView.imageScaling = .scaleAxesIndependently
|
|
73
|
+
imageView.layer?.contentsGravity = .resize
|
|
74
|
+
case "center":
|
|
75
|
+
imageView.imageScaling = .scaleNone
|
|
76
|
+
imageView.layer?.contentsGravity = .center
|
|
77
|
+
default:
|
|
78
|
+
imageView.imageScaling = .scaleProportionallyUpOrDown
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case "tintColor":
|
|
82
|
+
if let colorStr = value as? String {
|
|
83
|
+
imageView.contentTintColor = NSColor.fromHex(colorStr)
|
|
84
|
+
// Set image as template so tint applies
|
|
85
|
+
imageView.image?.isTemplate = true
|
|
86
|
+
} else {
|
|
87
|
+
imageView.contentTintColor = nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case "blurRadius":
|
|
91
|
+
if let radius = value as? Double, radius > 0 {
|
|
92
|
+
applyBlur(to: imageView, radius: radius)
|
|
93
|
+
} else {
|
|
94
|
+
// Remove blur
|
|
95
|
+
imageView.layer?.filters = nil
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case "defaultSource":
|
|
99
|
+
// Set placeholder image
|
|
100
|
+
if let source = value as? String {
|
|
101
|
+
if let image = NSImage(named: source) {
|
|
102
|
+
// Only set if no image is currently loaded
|
|
103
|
+
if imageView.image == nil {
|
|
104
|
+
imageView.image = image
|
|
105
|
+
}
|
|
106
|
+
} else if let image = loadFromBundle(source) {
|
|
107
|
+
if imageView.image == nil {
|
|
108
|
+
imageView.image = image
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
default:
|
|
114
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
119
|
+
switch event {
|
|
120
|
+
case "load":
|
|
121
|
+
objc_setAssociatedObject(
|
|
122
|
+
view, &VImageFactory.loadHandlerKey,
|
|
123
|
+
handler as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
case "error":
|
|
127
|
+
objc_setAssociatedObject(
|
|
128
|
+
view, &VImageFactory.errorHandlerKey,
|
|
129
|
+
handler as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
default:
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func removeEventListener(view: NSView, event: String) {
|
|
138
|
+
switch event {
|
|
139
|
+
case "load":
|
|
140
|
+
objc_setAssociatedObject(
|
|
141
|
+
view, &VImageFactory.loadHandlerKey,
|
|
142
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
case "error":
|
|
146
|
+
objc_setAssociatedObject(
|
|
147
|
+
view, &VImageFactory.errorHandlerKey,
|
|
148
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MARK: - Async image loading
|
|
157
|
+
|
|
158
|
+
private func loadImageFromURL(_ url: URL, into imageView: NSImageView) {
|
|
159
|
+
let cacheKey = url.absoluteString as NSString
|
|
160
|
+
|
|
161
|
+
// Check cache first
|
|
162
|
+
if let cached = VImageFactory.imageCache.object(forKey: cacheKey) {
|
|
163
|
+
imageView.image = cached
|
|
164
|
+
fireLoadEvent(for: imageView, image: cached)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let task = URLSession.shared.dataTask(with: url) { [weak imageView] data, response, error in
|
|
169
|
+
DispatchQueue.main.async {
|
|
170
|
+
guard let imageView = imageView else { return }
|
|
171
|
+
|
|
172
|
+
if let error = error {
|
|
173
|
+
self.fireErrorEvent(for: imageView, message: error.localizedDescription)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
guard let data = data, let image = NSImage(data: data) else {
|
|
178
|
+
self.fireErrorEvent(for: imageView, message: "Failed to decode image data")
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Cache the image
|
|
183
|
+
VImageFactory.imageCache.setObject(image, forKey: cacheKey)
|
|
184
|
+
|
|
185
|
+
imageView.image = image
|
|
186
|
+
self.fireLoadEvent(for: imageView, image: image)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
task.resume()
|
|
190
|
+
|
|
191
|
+
// Store the task so we can cancel it if source changes
|
|
192
|
+
objc_setAssociatedObject(
|
|
193
|
+
imageView, &VImageFactory.loadTaskKey,
|
|
194
|
+
task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private func cancelLoad(on view: NSView) {
|
|
199
|
+
if let task = objc_getAssociatedObject(view, &VImageFactory.loadTaskKey) as? URLSessionDataTask {
|
|
200
|
+
task.cancel()
|
|
201
|
+
objc_setAssociatedObject(
|
|
202
|
+
view, &VImageFactory.loadTaskKey,
|
|
203
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private func loadFromBundle(_ name: String) -> NSImage? {
|
|
209
|
+
// Try loading from main bundle by path
|
|
210
|
+
if let path = Bundle.main.path(forResource: name, ofType: nil) {
|
|
211
|
+
return NSImage(contentsOfFile: path)
|
|
212
|
+
}
|
|
213
|
+
// Try without extension
|
|
214
|
+
let components = name.split(separator: ".")
|
|
215
|
+
if components.count >= 2 {
|
|
216
|
+
let baseName = String(components.dropLast().joined(separator: "."))
|
|
217
|
+
let ext = String(components.last!)
|
|
218
|
+
if let path = Bundle.main.path(forResource: baseName, ofType: ext) {
|
|
219
|
+
return NSImage(contentsOfFile: path)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return nil
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// MARK: - Event helpers
|
|
226
|
+
|
|
227
|
+
private func fireLoadEvent(for imageView: NSImageView, image: NSImage) {
|
|
228
|
+
guard let handler = objc_getAssociatedObject(
|
|
229
|
+
imageView, &VImageFactory.loadHandlerKey
|
|
230
|
+
) as? (Any?) -> Void else { return }
|
|
231
|
+
|
|
232
|
+
let payload: [String: Any] = [
|
|
233
|
+
"width": image.size.width,
|
|
234
|
+
"height": image.size.height
|
|
235
|
+
]
|
|
236
|
+
handler(payload)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private func fireErrorEvent(for imageView: NSImageView, message: String) {
|
|
240
|
+
guard let handler = objc_getAssociatedObject(
|
|
241
|
+
imageView, &VImageFactory.errorHandlerKey
|
|
242
|
+
) as? (Any?) -> Void else { return }
|
|
243
|
+
|
|
244
|
+
let payload: [String: Any] = [
|
|
245
|
+
"error": message
|
|
246
|
+
]
|
|
247
|
+
handler(payload)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// MARK: - Blur effect
|
|
251
|
+
|
|
252
|
+
private func applyBlur(to imageView: NSImageView, radius: Double) {
|
|
253
|
+
guard let image = imageView.image,
|
|
254
|
+
let tiffData = image.tiffRepresentation,
|
|
255
|
+
let ciImage = CIImage(data: tiffData) else { return }
|
|
256
|
+
|
|
257
|
+
let filter = CIFilter(name: "CIGaussianBlur")
|
|
258
|
+
filter?.setValue(ciImage, forKey: kCIInputImageKey)
|
|
259
|
+
filter?.setValue(radius, forKey: kCIInputRadiusKey)
|
|
260
|
+
|
|
261
|
+
guard let outputImage = filter?.outputImage else { return }
|
|
262
|
+
|
|
263
|
+
let context = CIContext()
|
|
264
|
+
let extent = ciImage.extent
|
|
265
|
+
guard let cgImage = context.createCGImage(outputImage, from: extent) else { return }
|
|
266
|
+
|
|
267
|
+
let blurredImage = NSImage(cgImage: cgImage, size: image.size)
|
|
268
|
+
imageView.image = blurredImage
|
|
269
|
+
}
|
|
270
|
+
}
|
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VInput — the text input component.
|
|
5
|
+
/// Maps to an NSTextField (editable) with a LayoutNode.
|
|
6
|
+
/// Supports v-model via text prop and changetext event.
|
|
7
|
+
/// For secure text entry, swaps to an NSSecureTextField.
|
|
8
|
+
final class VInputFactory: NativeComponentFactory {
|
|
9
|
+
|
|
10
|
+
// MARK: - Associated object keys
|
|
11
|
+
|
|
12
|
+
private static var delegateKey: UInt8 = 0
|
|
13
|
+
private static var changeTextHandlerKey: UInt8 = 0
|
|
14
|
+
private static var focusHandlerKey: UInt8 = 0
|
|
15
|
+
private static var blurHandlerKey: UInt8 = 0
|
|
16
|
+
private static var submitHandlerKey: UInt8 = 0
|
|
17
|
+
private static var maxLengthKey: UInt8 = 0
|
|
18
|
+
|
|
19
|
+
// MARK: - NativeComponentFactory
|
|
20
|
+
|
|
21
|
+
func createView() -> NSView {
|
|
22
|
+
let textField = NSTextField()
|
|
23
|
+
textField.isBordered = true
|
|
24
|
+
textField.isEditable = true
|
|
25
|
+
textField.isSelectable = true
|
|
26
|
+
textField.wantsLayer = true
|
|
27
|
+
// Set a sensible default height so the text field is not collapsed
|
|
28
|
+
let node = textField.ensureLayoutNode()
|
|
29
|
+
node.height = .points(28)
|
|
30
|
+
return textField
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
34
|
+
guard let textField = view as? NSTextField else {
|
|
35
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch key {
|
|
40
|
+
case "text", "value":
|
|
41
|
+
if let text = value as? String {
|
|
42
|
+
// Only update if different to avoid cursor jump
|
|
43
|
+
if textField.stringValue != text {
|
|
44
|
+
textField.stringValue = text
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
textField.stringValue = ""
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
case "placeholder":
|
|
51
|
+
if let placeholder = value as? String {
|
|
52
|
+
textField.placeholderString = placeholder
|
|
53
|
+
} else {
|
|
54
|
+
textField.placeholderString = nil
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
case "placeholderColor", "placeholderTextColor":
|
|
58
|
+
if let colorStr = value as? String, let placeholder = textField.placeholderString {
|
|
59
|
+
textField.placeholderAttributedString = NSAttributedString(
|
|
60
|
+
string: placeholder,
|
|
61
|
+
attributes: [.foregroundColor: NSColor.fromHex(colorStr)]
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case "secureTextEntry":
|
|
66
|
+
// Note: NSSecureTextField is a subclass, not a property toggle.
|
|
67
|
+
// Full secure text entry requires view replacement at the bridge level.
|
|
68
|
+
// Here we just store the prop for reference.
|
|
69
|
+
let secure: Bool
|
|
70
|
+
if let val = value as? Bool {
|
|
71
|
+
secure = val
|
|
72
|
+
} else if let val = value as? Int {
|
|
73
|
+
secure = val != 0
|
|
74
|
+
} else {
|
|
75
|
+
secure = false
|
|
76
|
+
}
|
|
77
|
+
StyleEngine.setInternalPropDirect("__secureTextEntry", value: secure, on: view)
|
|
78
|
+
|
|
79
|
+
case "editable":
|
|
80
|
+
if let editable = value as? Bool {
|
|
81
|
+
textField.isEditable = editable
|
|
82
|
+
} else {
|
|
83
|
+
textField.isEditable = true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case "maxLength":
|
|
87
|
+
if let maxLen = value as? Int {
|
|
88
|
+
storeMaxLength(maxLen, on: textField)
|
|
89
|
+
} else if let maxLen = value as? Double {
|
|
90
|
+
storeMaxLength(Int(maxLen), on: textField)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case "color":
|
|
94
|
+
if let colorStr = value as? String {
|
|
95
|
+
textField.textColor = NSColor.fromHex(colorStr)
|
|
96
|
+
} else {
|
|
97
|
+
textField.textColor = .labelColor
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case "fontSize":
|
|
101
|
+
if let size = value as? Double {
|
|
102
|
+
textField.font = NSFont.systemFont(ofSize: CGFloat(size))
|
|
103
|
+
} else if let size = value as? Int {
|
|
104
|
+
textField.font = NSFont.systemFont(ofSize: CGFloat(size))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case "textAlign":
|
|
108
|
+
if let alignStr = value as? String {
|
|
109
|
+
switch alignStr {
|
|
110
|
+
case "left": textField.alignment = .left
|
|
111
|
+
case "center": textField.alignment = .center
|
|
112
|
+
case "right": textField.alignment = .right
|
|
113
|
+
default: textField.alignment = .natural
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
default:
|
|
118
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
123
|
+
guard let textField = view as? NSTextField else { return }
|
|
124
|
+
|
|
125
|
+
// Ensure we have a delegate proxy set up
|
|
126
|
+
let delegate = ensureDelegate(for: textField)
|
|
127
|
+
|
|
128
|
+
switch event {
|
|
129
|
+
case "changetext":
|
|
130
|
+
delegate.onChangeText = handler
|
|
131
|
+
objc_setAssociatedObject(
|
|
132
|
+
view,
|
|
133
|
+
&VInputFactory.changeTextHandlerKey,
|
|
134
|
+
handler as AnyObject,
|
|
135
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
case "focus":
|
|
139
|
+
delegate.onFocus = handler
|
|
140
|
+
objc_setAssociatedObject(
|
|
141
|
+
view,
|
|
142
|
+
&VInputFactory.focusHandlerKey,
|
|
143
|
+
handler as AnyObject,
|
|
144
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
case "blur":
|
|
148
|
+
delegate.onBlur = handler
|
|
149
|
+
objc_setAssociatedObject(
|
|
150
|
+
view,
|
|
151
|
+
&VInputFactory.blurHandlerKey,
|
|
152
|
+
handler as AnyObject,
|
|
153
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
case "submit":
|
|
157
|
+
delegate.onSubmit = handler
|
|
158
|
+
objc_setAssociatedObject(
|
|
159
|
+
view,
|
|
160
|
+
&VInputFactory.submitHandlerKey,
|
|
161
|
+
handler as AnyObject,
|
|
162
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
default:
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func removeEventListener(view: NSView, event: String) {
|
|
171
|
+
guard let textField = view as? NSTextField else { return }
|
|
172
|
+
guard let delegate = objc_getAssociatedObject(textField, &VInputFactory.delegateKey) as? InputDelegateProxy else { return }
|
|
173
|
+
|
|
174
|
+
switch event {
|
|
175
|
+
case "changetext":
|
|
176
|
+
delegate.onChangeText = nil
|
|
177
|
+
case "focus":
|
|
178
|
+
delegate.onFocus = nil
|
|
179
|
+
case "blur":
|
|
180
|
+
delegate.onBlur = nil
|
|
181
|
+
case "submit":
|
|
182
|
+
delegate.onSubmit = nil
|
|
183
|
+
default:
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// MARK: - Private helpers
|
|
189
|
+
|
|
190
|
+
private func ensureDelegate(for textField: NSTextField) -> InputDelegateProxy {
|
|
191
|
+
if let existing = objc_getAssociatedObject(textField, &VInputFactory.delegateKey) as? InputDelegateProxy {
|
|
192
|
+
return existing
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let delegate = InputDelegateProxy()
|
|
196
|
+
delegate.maxLengthProvider = { [weak textField] in
|
|
197
|
+
guard let tf = textField else { return nil }
|
|
198
|
+
return self.storedMaxLength(on: tf)
|
|
199
|
+
}
|
|
200
|
+
textField.delegate = delegate
|
|
201
|
+
objc_setAssociatedObject(
|
|
202
|
+
textField,
|
|
203
|
+
&VInputFactory.delegateKey,
|
|
204
|
+
delegate,
|
|
205
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
206
|
+
)
|
|
207
|
+
return delegate
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// MARK: - Max length storage
|
|
211
|
+
|
|
212
|
+
private func storeMaxLength(_ length: Int, on view: NSView) {
|
|
213
|
+
objc_setAssociatedObject(view, &VInputFactory.maxLengthKey, length, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private func storedMaxLength(on view: NSView) -> Int? {
|
|
217
|
+
return objc_getAssociatedObject(view, &VInputFactory.maxLengthKey) as? Int
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - InputDelegateProxy
|
|
222
|
+
|
|
223
|
+
/// NSTextFieldDelegate proxy that routes text field events to closure-based handlers.
|
|
224
|
+
/// Stored as an associated object on the NSTextField.
|
|
225
|
+
final class InputDelegateProxy: NSObject, NSTextFieldDelegate {
|
|
226
|
+
|
|
227
|
+
var onChangeText: ((Any?) -> Void)?
|
|
228
|
+
var onFocus: ((Any?) -> Void)?
|
|
229
|
+
var onBlur: ((Any?) -> Void)?
|
|
230
|
+
var onSubmit: ((Any?) -> Void)?
|
|
231
|
+
var maxLengthProvider: (() -> Int?)?
|
|
232
|
+
|
|
233
|
+
// MARK: - NSTextFieldDelegate
|
|
234
|
+
|
|
235
|
+
func controlTextDidChange(_ obj: Notification) {
|
|
236
|
+
guard let textField = obj.object as? NSTextField else { return }
|
|
237
|
+
// Enforce max length
|
|
238
|
+
if let maxLength = maxLengthProvider?(), textField.stringValue.count > maxLength {
|
|
239
|
+
textField.stringValue = String(textField.stringValue.prefix(maxLength))
|
|
240
|
+
}
|
|
241
|
+
onChangeText?(textField.stringValue)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func controlTextDidBeginEditing(_ obj: Notification) {
|
|
245
|
+
onFocus?(nil)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func controlTextDidEndEditing(_ obj: Notification) {
|
|
249
|
+
onBlur?(nil)
|
|
250
|
+
// Check if editing ended due to Return key
|
|
251
|
+
if let textField = obj.object as? NSTextField,
|
|
252
|
+
let movement = obj.userInfo?["NSTextMovement"] as? Int,
|
|
253
|
+
movement == NSReturnTextMovement {
|
|
254
|
+
onSubmit?(textField.stringValue)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
/// Factory for VKeyboardAvoiding — pass-through container on macOS.
|
|
4
|
+
/// macOS keyboards do not cover content (unlike iOS on-screen keyboards),
|
|
5
|
+
/// so this component acts as a plain FlippedView container for API compatibility.
|
|
6
|
+
final class VKeyboardAvoidingFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
func createView() -> NSView {
|
|
9
|
+
let view = FlippedView()
|
|
10
|
+
view.ensureLayoutNode()
|
|
11
|
+
return view
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
15
|
+
// Delegate all props to StyleEngine — acts as a plain container
|
|
16
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
20
|
+
// No-op — no keyboard avoidance on macOS
|
|
21
|
+
}
|
|
22
|
+
}
|