@thelacanians/vue-native-cli 0.4.15 → 0.6.2

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.
Files changed (116) hide show
  1. package/dist/cli.js +329 -15
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  6. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  7. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  8. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  9. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  13. package/native/android/gradle.properties +1 -0
  14. package/native/android/gradlew +1 -1
  15. package/native/ios/VueNativeCore/Package.swift +1 -1
  16. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
  17. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
  18. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  19. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  23. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  24. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  25. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  26. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  27. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  28. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  29. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  30. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  31. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  32. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  33. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  34. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  35. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  36. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  94. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  95. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  96. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  97. package/native/shared/VueNativeShared/Package.swift +14 -0
  98. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  99. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  100. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  101. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  102. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  103. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  104. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  115. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  116. package/package.json +8 -2
@@ -0,0 +1,354 @@
1
+ import AppKit
2
+ import VueNativeShared
3
+
4
+ /// Native module for NSView animations.
5
+ /// Exposes timing, spring, keyframe, sequence, and parallel animations to JS composables.
6
+ final class AnimationModule: NativeModule {
7
+
8
+ let moduleName = "Animation"
9
+ private let viewLookup: (Int) -> NSView?
10
+
11
+ init(viewLookup: @escaping (Int) -> NSView?) {
12
+ self.viewLookup = viewLookup
13
+ }
14
+
15
+ func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
16
+ switch method {
17
+ case "timing":
18
+ handleTiming(args: args, callback: callback)
19
+ case "spring":
20
+ handleSpring(args: args, callback: callback)
21
+ case "keyframe":
22
+ guard args.count >= 3,
23
+ let viewId = coerceInt(args[0]),
24
+ let keyframesData = args[1] as? [[String: Any]],
25
+ let options = args[2] as? [String: Any] else {
26
+ callback(nil, "Invalid args"); return
27
+ }
28
+ let duration = (options["duration"] as? Double ?? 300) / 1000.0
29
+ animateKeyframes(viewId: viewId, keyframes: keyframesData, duration: duration, callback: callback)
30
+ case "sequence":
31
+ guard let animationsData = args.first as? [[String: Any]] else {
32
+ callback(nil, "Invalid args"); return
33
+ }
34
+ runSequence(animationsData: animationsData, index: 0, callback: callback)
35
+ case "parallel":
36
+ guard let animationsData = args.first as? [[String: Any]] else {
37
+ callback(nil, "Invalid args"); return
38
+ }
39
+ runParallel(animationsData: animationsData, callback: callback)
40
+ default:
41
+ callback(nil, "AnimationModule: unknown method '\(method)'")
42
+ }
43
+ }
44
+
45
+ func invokeSync(method: String, args: [Any]) -> Any? {
46
+ return nil
47
+ }
48
+
49
+ // MARK: - timing(viewId, styles, options)
50
+
51
+ private func handleTiming(args: [Any], callback: @escaping (Any?, String?) -> Void) {
52
+ guard let viewId = args.first.flatMap({ coerceInt($0) }) else {
53
+ callback(nil, "timing: invalid arguments")
54
+ return
55
+ }
56
+
57
+ let styles = args.count > 1 ? (args[1] as? [String: Any] ?? [:]) : [:]
58
+ let options = args.count > 2 ? (args[2] as? [String: Any] ?? [:]) : [:]
59
+ let duration = (options["duration"] as? Double ?? 300) / 1000.0
60
+ let easing = options["easing"] as? String ?? "ease"
61
+
62
+ let timingFunction: CAMediaTimingFunction
63
+ switch easing {
64
+ case "linear": timingFunction = CAMediaTimingFunction(name: .linear)
65
+ case "ease-in", "easeIn": timingFunction = CAMediaTimingFunction(name: .easeIn)
66
+ case "ease-out", "easeOut": timingFunction = CAMediaTimingFunction(name: .easeOut)
67
+ default: timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
68
+ }
69
+
70
+ DispatchQueue.main.async { [weak self] in
71
+ guard let view = self?.viewLookup(viewId) else {
72
+ callback(nil, "timing: view \(viewId) not found")
73
+ return
74
+ }
75
+
76
+ NSAnimationContext.runAnimationGroup({ ctx in
77
+ ctx.duration = duration
78
+ ctx.timingFunction = timingFunction
79
+ ctx.allowsImplicitAnimation = true
80
+
81
+ for (key, value) in styles {
82
+ self?.applyAnimatableStyle(key: key, value: value, to: view)
83
+ }
84
+ view.layoutSubtreeIfNeeded()
85
+ }, completionHandler: {
86
+ callback(nil, nil)
87
+ })
88
+ }
89
+ }
90
+
91
+ // MARK: - spring(viewId, styles, options)
92
+
93
+ private func handleSpring(args: [Any], callback: @escaping (Any?, String?) -> Void) {
94
+ guard let viewId = args.first.flatMap({ coerceInt($0) }) else {
95
+ callback(nil, "spring: invalid arguments")
96
+ return
97
+ }
98
+
99
+ let styles = args.count > 1 ? (args[1] as? [String: Any] ?? [:]) : [:]
100
+ let options = args.count > 2 ? (args[2] as? [String: Any] ?? [:]) : [:]
101
+ let duration = (options["duration"] as? Double ?? 500) / 1000.0
102
+ let damping = CGFloat(options["damping"] as? Double ?? 0.7)
103
+
104
+ DispatchQueue.main.async { [weak self] in
105
+ guard let view = self?.viewLookup(viewId) else {
106
+ callback(nil, "spring: view \(viewId) not found")
107
+ return
108
+ }
109
+
110
+ // Use CASpringAnimation for true spring physics
111
+ let springAnim = CASpringAnimation(keyPath: "transform")
112
+ springAnim.damping = damping * 20 // scale to CA range
113
+ springAnim.duration = duration
114
+ springAnim.isRemovedOnCompletion = false
115
+ springAnim.fillMode = .forwards
116
+
117
+ // Fall back to NSAnimationContext for property changes
118
+ NSAnimationContext.runAnimationGroup({ ctx in
119
+ ctx.duration = duration
120
+ ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
121
+ ctx.allowsImplicitAnimation = true
122
+
123
+ for (key, value) in styles {
124
+ self?.applyAnimatableStyle(key: key, value: value, to: view)
125
+ }
126
+ view.layoutSubtreeIfNeeded()
127
+ }, completionHandler: {
128
+ callback(nil, nil)
129
+ })
130
+ }
131
+ }
132
+
133
+ // MARK: - keyframe(viewId, keyframes, options)
134
+
135
+ private func animateKeyframes(viewId: Int, keyframes: [[String: Any]], duration: TimeInterval, callback: @escaping (Any?, String?) -> Void) {
136
+ DispatchQueue.main.async { [weak self] in
137
+ guard let view = self?.viewLookup(viewId) else {
138
+ callback(nil, "View not found for id \(viewId)"); return
139
+ }
140
+
141
+ guard let layer = view.layer else {
142
+ callback(nil, "View has no layer"); return
143
+ }
144
+
145
+ let group = CAAnimationGroup()
146
+ group.duration = duration
147
+ group.fillMode = .forwards
148
+ group.isRemovedOnCompletion = false
149
+
150
+ var animations: [CAAnimation] = []
151
+
152
+ // Collect all property names from keyframes
153
+ var propertyKeys = Set<String>()
154
+ for kf in keyframes { propertyKeys.formUnion(kf.keys.filter { $0 != "offset" }) }
155
+
156
+ for propKey in propertyKeys {
157
+ let keyPath: String
158
+ var values: [Any] = []
159
+ var keyTimes: [NSNumber] = []
160
+
161
+ switch propKey {
162
+ case "opacity":
163
+ keyPath = "opacity"
164
+ for kf in keyframes {
165
+ if let v = kf[propKey] as? Double {
166
+ values.append(v)
167
+ keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
168
+ }
169
+ }
170
+ case "translateX":
171
+ keyPath = "transform.translation.x"
172
+ for kf in keyframes {
173
+ if let v = kf[propKey] as? Double {
174
+ values.append(v)
175
+ keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
176
+ }
177
+ }
178
+ case "translateY":
179
+ keyPath = "transform.translation.y"
180
+ for kf in keyframes {
181
+ if let v = kf[propKey] as? Double {
182
+ values.append(v)
183
+ keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
184
+ }
185
+ }
186
+ case "scale", "scaleX":
187
+ keyPath = "transform.scale.x"
188
+ for kf in keyframes {
189
+ if let v = kf[propKey] as? Double {
190
+ values.append(v)
191
+ keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
192
+ }
193
+ }
194
+ case "scaleY":
195
+ keyPath = "transform.scale.y"
196
+ for kf in keyframes {
197
+ if let v = kf[propKey] as? Double {
198
+ values.append(v)
199
+ keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
200
+ }
201
+ }
202
+ default:
203
+ continue
204
+ }
205
+
206
+ if !values.isEmpty {
207
+ let anim = CAKeyframeAnimation(keyPath: keyPath)
208
+ anim.values = values
209
+ anim.keyTimes = keyTimes
210
+ anim.duration = duration
211
+ animations.append(anim)
212
+ }
213
+ }
214
+
215
+ if animations.isEmpty { callback(nil, nil); return }
216
+ group.animations = animations
217
+
218
+ CATransaction.begin()
219
+ CATransaction.setCompletionBlock { callback(nil, nil) }
220
+ layer.add(group, forKey: "keyframeAnimation")
221
+ CATransaction.commit()
222
+ }
223
+ }
224
+
225
+ // MARK: - sequence([animData])
226
+
227
+ private func runSequence(animationsData: [[String: Any]], index: Int, callback: @escaping (Any?, String?) -> Void) {
228
+ guard index < animationsData.count else { callback(nil, nil); return }
229
+ let animData = animationsData[index]
230
+ let method = animData["type"] as? String ?? "timing"
231
+ let viewId = animData["viewId"] as? Int ?? 0
232
+ let toStyles = animData["toStyles"] as? [String: Any] ?? [:]
233
+ let options = animData["options"] as? [String: Any] ?? [:]
234
+
235
+ runSingleAnimation(method: method, viewId: viewId, toStyles: toStyles, options: options) { [weak self] _, error in
236
+ if let error = error { callback(nil, error); return }
237
+ self?.runSequence(animationsData: animationsData, index: index + 1, callback: callback)
238
+ }
239
+ }
240
+
241
+ private func runSingleAnimation(method: String, viewId: Int, toStyles: [String: Any], options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
242
+ switch method {
243
+ case "timing":
244
+ invoke(method: "timing", args: [viewId, toStyles, options], callback: callback)
245
+ case "spring":
246
+ invoke(method: "spring", args: [viewId, toStyles, options], callback: callback)
247
+ default:
248
+ callback(nil, "Unknown animation type: \(method)")
249
+ }
250
+ }
251
+
252
+ // MARK: - parallel([animData])
253
+
254
+ private func runParallel(animationsData: [[String: Any]], callback: @escaping (Any?, String?) -> Void) {
255
+ guard !animationsData.isEmpty else { callback(nil, nil); return }
256
+ let total = animationsData.count
257
+ var completed = 0
258
+ let lock = NSLock()
259
+
260
+ for animData in animationsData {
261
+ let method = animData["type"] as? String ?? "timing"
262
+ let viewId = animData["viewId"] as? Int ?? 0
263
+ let toStyles = animData["toStyles"] as? [String: Any] ?? [:]
264
+ let options = animData["options"] as? [String: Any] ?? [:]
265
+
266
+ runSingleAnimation(method: method, viewId: viewId, toStyles: toStyles, options: options) { _, _ in
267
+ lock.lock()
268
+ completed += 1
269
+ let allDone = completed == total
270
+ lock.unlock()
271
+ if allDone { callback(nil, nil) }
272
+ }
273
+ }
274
+ }
275
+
276
+ // MARK: - Helpers
277
+
278
+ private func coerceInt(_ value: Any) -> Int? {
279
+ if let i = value as? Int { return i }
280
+ if let d = value as? Double { return Int(d) }
281
+ return nil
282
+ }
283
+
284
+ /// Apply an animatable style property to an NSView.
285
+ /// For full style application, the bridge's StyleEngine should be used;
286
+ /// this handles the common animatable subset.
287
+ private func applyAnimatableStyle(key: String, value: Any?, to view: NSView) {
288
+ guard let layer = view.layer else { return }
289
+ switch key {
290
+ case "opacity":
291
+ if let v = value as? Double {
292
+ view.alphaValue = CGFloat(v)
293
+ }
294
+ case "backgroundColor":
295
+ if let hex = value as? String {
296
+ layer.backgroundColor = NSColor.fromHex(hex)?.cgColor
297
+ }
298
+ case "translateX":
299
+ if let v = value as? Double {
300
+ var transform = view.layer?.affineTransform() ?? .identity
301
+ transform.tx = CGFloat(v)
302
+ view.layer?.setAffineTransform(transform)
303
+ }
304
+ case "translateY":
305
+ if let v = value as? Double {
306
+ var transform = view.layer?.affineTransform() ?? .identity
307
+ transform.ty = CGFloat(v)
308
+ view.layer?.setAffineTransform(transform)
309
+ }
310
+ case "scale":
311
+ if let v = value as? Double {
312
+ let cv = CGFloat(v)
313
+ view.layer?.setAffineTransform(CGAffineTransform(scaleX: cv, y: cv))
314
+ }
315
+ case "borderRadius":
316
+ if let v = value as? Double {
317
+ layer.cornerRadius = CGFloat(v)
318
+ }
319
+ default:
320
+ break
321
+ }
322
+ }
323
+ }
324
+
325
+ // MARK: - NSColor hex helper
326
+
327
+ private extension NSColor {
328
+ static func fromHex(_ hex: String) -> NSColor? {
329
+ var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines)
330
+ if hexString.hasPrefix("#") { hexString.removeFirst() }
331
+
332
+ var rgb: UInt64 = 0
333
+ guard Scanner(string: hexString).scanHexInt64(&rgb) else { return nil }
334
+
335
+ switch hexString.count {
336
+ case 6:
337
+ return NSColor(
338
+ red: CGFloat((rgb >> 16) & 0xFF) / 255.0,
339
+ green: CGFloat((rgb >> 8) & 0xFF) / 255.0,
340
+ blue: CGFloat(rgb & 0xFF) / 255.0,
341
+ alpha: 1.0
342
+ )
343
+ case 8:
344
+ return NSColor(
345
+ red: CGFloat((rgb >> 24) & 0xFF) / 255.0,
346
+ green: CGFloat((rgb >> 16) & 0xFF) / 255.0,
347
+ blue: CGFloat((rgb >> 8) & 0xFF) / 255.0,
348
+ alpha: CGFloat(rgb & 0xFF) / 255.0
349
+ )
350
+ default:
351
+ return nil
352
+ }
353
+ }
354
+ }
@@ -0,0 +1,62 @@
1
+ import AppKit
2
+ import VueNativeShared
3
+
4
+ /// Native module providing application lifecycle state notifications.
5
+ ///
6
+ /// Events dispatched:
7
+ /// - appState:change { state: "active"|"inactive"|"background" }
8
+ ///
9
+ /// Methods:
10
+ /// - getState() -> String
11
+ final class AppStateModule: NSObject, NativeModule {
12
+ var moduleName: String { "AppState" }
13
+ private weak var dispatcher: NativeEventDispatcher?
14
+ private var observers: [NSObjectProtocol] = []
15
+
16
+ init(dispatcher: NativeEventDispatcher) {
17
+ self.dispatcher = dispatcher
18
+ super.init()
19
+ setupObservers()
20
+ }
21
+
22
+ private func setupObservers() {
23
+ observers.append(NotificationCenter.default.addObserver(
24
+ forName: NSApplication.didBecomeActiveNotification, object: nil, queue: .main
25
+ ) { [weak self] _ in
26
+ self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "active"])
27
+ })
28
+ observers.append(NotificationCenter.default.addObserver(
29
+ forName: NSApplication.didResignActiveNotification, object: nil, queue: .main
30
+ ) { [weak self] _ in
31
+ self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "inactive"])
32
+ })
33
+ observers.append(NotificationCenter.default.addObserver(
34
+ forName: NSApplication.didHideNotification, object: nil, queue: .main
35
+ ) { [weak self] _ in
36
+ self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "background"])
37
+ })
38
+ observers.append(NotificationCenter.default.addObserver(
39
+ forName: NSApplication.didUnhideNotification, object: nil, queue: .main
40
+ ) { [weak self] _ in
41
+ self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "active"])
42
+ })
43
+ }
44
+
45
+ deinit {
46
+ observers.forEach { NotificationCenter.default.removeObserver($0) }
47
+ }
48
+
49
+ func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
50
+ switch method {
51
+ case "getState":
52
+ DispatchQueue.main.async {
53
+ let state = NSApp.isActive ? "active" : "inactive"
54
+ callback(state, nil)
55
+ }
56
+ default:
57
+ callback(nil, "AppStateModule: Unknown method '\(method)'")
58
+ }
59
+ }
60
+
61
+ func invokeSync(method: String, args: [Any]) -> Any? { nil }
62
+ }
@@ -0,0 +1,60 @@
1
+ import LocalAuthentication
2
+ import VueNativeShared
3
+
4
+ /// Native module providing biometric authentication (Touch ID) on macOS.
5
+ ///
6
+ /// Methods:
7
+ /// - isAvailable() -> { available: Bool, biometryType: "touchId"/"none" }
8
+ /// - authenticate(reason: String) -> { success: Bool }
9
+ final class BiometryModule: NativeModule {
10
+ let moduleName = "Biometry"
11
+
12
+ func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
13
+ switch method {
14
+ case "isAvailable":
15
+ let context = LAContext()
16
+ var error: NSError?
17
+ let canEvaluate = context.canEvaluatePolicy(
18
+ .deviceOwnerAuthenticationWithBiometrics,
19
+ error: &error
20
+ )
21
+
22
+ let biometryType: String
23
+ if canEvaluate {
24
+ switch context.biometryType {
25
+ case .touchID:
26
+ biometryType = "touchId"
27
+ default:
28
+ biometryType = "none"
29
+ }
30
+ } else {
31
+ biometryType = "none"
32
+ }
33
+
34
+ let result: [String: Any] = [
35
+ "available": canEvaluate,
36
+ "biometryType": biometryType
37
+ ]
38
+ callback(result, nil)
39
+
40
+ case "authenticate":
41
+ let reason = args.first as? String ?? "Authenticate"
42
+ let context = LAContext()
43
+
44
+ context.evaluatePolicy(
45
+ .deviceOwnerAuthenticationWithBiometrics,
46
+ localizedReason: reason
47
+ ) { success, error in
48
+ if success {
49
+ callback(["success": true], nil)
50
+ } else {
51
+ let message = error?.localizedDescription ?? "Authentication failed"
52
+ callback(nil, message)
53
+ }
54
+ }
55
+
56
+ default:
57
+ callback(nil, "BiometryModule: Unknown method '\(method)'")
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,167 @@
1
+ import AVFoundation
2
+ import AppKit
3
+ import VueNativeShared
4
+
5
+ /// Native module providing camera access on macOS.
6
+ ///
7
+ /// Methods:
8
+ /// - checkPermission() -> "granted"/"denied"/"undetermined"
9
+ /// - requestPermission() -> Bool
10
+ /// - takePicture() -> { uri, width, height }
11
+ /// - getAvailableCameras() -> [{ id, name, position }]
12
+ final class CameraModule: NativeModule {
13
+ let moduleName = "Camera"
14
+
15
+ func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
16
+ switch method {
17
+ case "checkPermission":
18
+ let status = AVCaptureDevice.authorizationStatus(for: .video)
19
+ let result: String
20
+ switch status {
21
+ case .authorized:
22
+ result = "granted"
23
+ case .denied, .restricted:
24
+ result = "denied"
25
+ case .notDetermined:
26
+ result = "undetermined"
27
+ @unknown default:
28
+ result = "undetermined"
29
+ }
30
+ callback(result, nil)
31
+
32
+ case "requestPermission":
33
+ AVCaptureDevice.requestAccess(for: .video) { granted in
34
+ callback(granted, nil)
35
+ }
36
+
37
+ case "takePicture":
38
+ takePicture(callback: callback)
39
+
40
+ case "getAvailableCameras":
41
+ let discoverySession = AVCaptureDevice.DiscoverySession(
42
+ deviceTypes: [.builtInWideAngleCamera, .externalUnknown],
43
+ mediaType: .video,
44
+ position: .unspecified
45
+ )
46
+ let cameras: [[String: Any]] = discoverySession.devices.map { device in
47
+ let position: String
48
+ switch device.position {
49
+ case .front: position = "front"
50
+ case .back: position = "back"
51
+ default: position = "unspecified"
52
+ }
53
+ return [
54
+ "id": device.uniqueID,
55
+ "name": device.localizedName,
56
+ "position": position
57
+ ]
58
+ }
59
+ callback(cameras, nil)
60
+
61
+ default:
62
+ callback(nil, "CameraModule: Unknown method '\(method)'")
63
+ }
64
+ }
65
+
66
+ // MARK: - Photo capture
67
+
68
+ private func takePicture(callback: @escaping (Any?, String?) -> Void) {
69
+ guard let device = AVCaptureDevice.default(for: .video) else {
70
+ callback(nil, "No camera available")
71
+ return
72
+ }
73
+
74
+ let session = AVCaptureSession()
75
+ session.sessionPreset = .photo
76
+
77
+ do {
78
+ let input = try AVCaptureDeviceInput(device: device)
79
+ guard session.canAddInput(input) else {
80
+ callback(nil, "Cannot add camera input")
81
+ return
82
+ }
83
+ session.addInput(input)
84
+ } catch {
85
+ callback(nil, "Camera input error: \(error.localizedDescription)")
86
+ return
87
+ }
88
+
89
+ let photoOutput = AVCapturePhotoOutput()
90
+ guard session.canAddOutput(photoOutput) else {
91
+ callback(nil, "Cannot add photo output")
92
+ return
93
+ }
94
+ session.addOutput(photoOutput)
95
+
96
+ let delegate = PhotoCaptureDelegate(session: session, callback: callback)
97
+ // Prevent delegate from being deallocated before capture completes
98
+ PhotoCaptureDelegate.activeDelegates.insert(delegate)
99
+
100
+ session.startRunning()
101
+
102
+ let settings = AVCapturePhotoSettings()
103
+ photoOutput.capturePhoto(with: settings, delegate: delegate)
104
+ }
105
+ }
106
+
107
+ // MARK: - PhotoCaptureDelegate
108
+
109
+ private final class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
110
+
111
+ /// Strong references to active delegates to prevent premature deallocation.
112
+ static var activeDelegates = Set<PhotoCaptureDelegate>()
113
+
114
+ private let session: AVCaptureSession
115
+ private let callback: (Any?, String?) -> Void
116
+
117
+ init(session: AVCaptureSession, callback: @escaping (Any?, String?) -> Void) {
118
+ self.session = session
119
+ self.callback = callback
120
+ super.init()
121
+ }
122
+
123
+ func photoOutput(
124
+ _ output: AVCapturePhotoOutput,
125
+ didFinishProcessingPhoto photo: AVCapturePhoto,
126
+ error: Error?
127
+ ) {
128
+ defer {
129
+ session.stopRunning()
130
+ PhotoCaptureDelegate.activeDelegates.remove(self)
131
+ }
132
+
133
+ if let error = error {
134
+ callback(nil, "Photo capture error: \(error.localizedDescription)")
135
+ return
136
+ }
137
+
138
+ guard let imageData = photo.fileDataRepresentation() else {
139
+ callback(nil, "Failed to get photo data")
140
+ return
141
+ }
142
+
143
+ guard let image = NSImage(data: imageData) else {
144
+ callback(nil, "Failed to create image from data")
145
+ return
146
+ }
147
+
148
+ // Write to temp file
149
+ let tempDir = NSTemporaryDirectory()
150
+ let fileName = "vue_native_photo_\(UUID().uuidString).jpg"
151
+ let filePath = (tempDir as NSString).appendingPathComponent(fileName)
152
+
153
+ do {
154
+ try imageData.write(to: URL(fileURLWithPath: filePath))
155
+ } catch {
156
+ callback(nil, "Failed to save photo: \(error.localizedDescription)")
157
+ return
158
+ }
159
+
160
+ let result: [String: Any] = [
161
+ "uri": filePath,
162
+ "width": image.size.width,
163
+ "height": image.size.height
164
+ ]
165
+ callback(result, nil)
166
+ }
167
+ }
@@ -0,0 +1,34 @@
1
+ import AppKit
2
+ import VueNativeShared
3
+
4
+ /// Native module providing clipboard access via NSPasteboard.
5
+ ///
6
+ /// Methods:
7
+ /// - copy(text: String)
8
+ /// - paste() -> String?
9
+ final class ClipboardModule: NativeModule {
10
+ let moduleName = "Clipboard"
11
+
12
+ func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
13
+ DispatchQueue.main.async {
14
+ switch method {
15
+ case "copy":
16
+ guard let text = args.first as? String else {
17
+ callback(nil, "copy: missing text")
18
+ return
19
+ }
20
+ let pasteboard = NSPasteboard.general
21
+ pasteboard.clearContents()
22
+ pasteboard.setString(text, forType: .string)
23
+ callback(nil, nil)
24
+
25
+ case "paste":
26
+ let text = NSPasteboard.general.string(forType: .string)
27
+ callback(text, nil)
28
+
29
+ default:
30
+ callback(nil, "ClipboardModule: Unknown method '\(method)'")
31
+ }
32
+ }
33
+ }
34
+ }