@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,314 @@
1
+ import AppKit
2
+ import ObjectiveC
3
+
4
+ /// Factory for VView — the basic container component.
5
+ /// Maps to a FlippedView (NSView subclass) with a LayoutNode.
6
+ /// Supports all style props via StyleEngine and gesture events.
7
+ final class VViewFactory: NativeComponentFactory {
8
+
9
+ func createView() -> NSView {
10
+ let view = FlippedView()
11
+ // Ensure layout node is attached
12
+ view.ensureLayoutNode()
13
+ return view
14
+ }
15
+
16
+ func updateProp(view: NSView, key: String, value: Any?) {
17
+ // All VView props are style-related — delegate to StyleEngine
18
+ StyleEngine.apply(key: key, value: value, to: view)
19
+ }
20
+
21
+ func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
22
+ switch event {
23
+
24
+ // MARK: Click / Press
25
+ case "press":
26
+ let wrapper = ClickGestureWrapper(handler: handler)
27
+ let click = NSClickGestureRecognizer(
28
+ target: wrapper,
29
+ action: #selector(ClickGestureWrapper.handleGesture(_:))
30
+ )
31
+ view.addGestureRecognizer(click)
32
+ GestureStorage.store(wrapper, for: view, event: event)
33
+
34
+ // MARK: Right Click
35
+ case "rightPress":
36
+ let wrapper = ClickGestureWrapper(handler: handler)
37
+ let click = NSClickGestureRecognizer(
38
+ target: wrapper,
39
+ action: #selector(ClickGestureWrapper.handleGesture(_:))
40
+ )
41
+ click.buttonMask = 0x2 // Right mouse button
42
+ view.addGestureRecognizer(click)
43
+ GestureStorage.store(wrapper, for: view, event: event)
44
+
45
+ // MARK: Pan
46
+ case "pan":
47
+ let panWrapper = PanGestureWrapper(handler: handler)
48
+ let pan = NSPanGestureRecognizer(
49
+ target: panWrapper,
50
+ action: #selector(PanGestureWrapper.handle(_:))
51
+ )
52
+ view.addGestureRecognizer(pan)
53
+ GestureStorage.storeObject(panWrapper, for: view, event: event)
54
+
55
+ // MARK: Magnify (macOS equivalent of pinch)
56
+ case "pinch", "magnify":
57
+ let magWrapper = MagnificationGestureWrapper(handler: handler)
58
+ let mag = NSMagnificationGestureRecognizer(
59
+ target: magWrapper,
60
+ action: #selector(MagnificationGestureWrapper.handle(_:))
61
+ )
62
+ view.addGestureRecognizer(mag)
63
+ GestureStorage.storeObject(magWrapper, for: view, event: event)
64
+
65
+ // MARK: Rotation
66
+ case "rotate":
67
+ let rotationWrapper = RotationGestureWrapper(handler: handler)
68
+ let rotation = NSRotationGestureRecognizer(
69
+ target: rotationWrapper,
70
+ action: #selector(RotationGestureWrapper.handle(_:))
71
+ )
72
+ view.addGestureRecognizer(rotation)
73
+ GestureStorage.storeObject(rotationWrapper, for: view, event: event)
74
+
75
+ // MARK: Double Click / Double Tap
76
+ case "doubleTap":
77
+ let wrapper = DoubleClickWrapper(handler: handler)
78
+ let click = NSClickGestureRecognizer(
79
+ target: wrapper,
80
+ action: #selector(DoubleClickWrapper.handleGesture(_:))
81
+ )
82
+ click.numberOfClicksRequired = 2
83
+ view.addGestureRecognizer(click)
84
+ GestureStorage.storeObject(wrapper, for: view, event: event)
85
+
86
+ // MARK: Hover
87
+ case "hover":
88
+ let hoverWrapper = HoverWrapper(handler: handler)
89
+ GestureStorage.storeObject(hoverWrapper, for: view, event: event)
90
+ attachHoverHandler(to: view, wrapper: hoverWrapper)
91
+
92
+ // MARK: Force Touch / Pressure
93
+ case "forceTouch":
94
+ let pressureWrapper = PressureWrapper(handler: handler)
95
+ GestureStorage.storeObject(pressureWrapper, for: view, event: event)
96
+ attachPressureHandler(to: view, wrapper: pressureWrapper)
97
+
98
+ default:
99
+ break
100
+ }
101
+ }
102
+
103
+ func removeEventListener(view: NSView, event: String) {
104
+ GestureStorage.remove(for: view, event: event)
105
+ // Remove matching gesture recognizers
106
+ view.gestureRecognizers.forEach { recognizer in
107
+ switch event {
108
+ case "press" where recognizer is NSClickGestureRecognizer:
109
+ let click = recognizer as! NSClickGestureRecognizer
110
+ if click.buttonMask == 0x1 {
111
+ view.removeGestureRecognizer(recognizer)
112
+ }
113
+ case "rightPress" where recognizer is NSClickGestureRecognizer:
114
+ let click = recognizer as! NSClickGestureRecognizer
115
+ if click.buttonMask == 0x2 {
116
+ view.removeGestureRecognizer(recognizer)
117
+ }
118
+ case "pan" where recognizer is NSPanGestureRecognizer:
119
+ view.removeGestureRecognizer(recognizer)
120
+ case "pinch", "magnify":
121
+ if recognizer is NSMagnificationGestureRecognizer {
122
+ view.removeGestureRecognizer(recognizer)
123
+ }
124
+ case "rotate":
125
+ if recognizer is NSRotationGestureRecognizer {
126
+ view.removeGestureRecognizer(recognizer)
127
+ }
128
+ case "doubleTap" where recognizer is NSClickGestureRecognizer:
129
+ let click = recognizer as! NSClickGestureRecognizer
130
+ if click.numberOfClicksRequired == 2 {
131
+ view.removeGestureRecognizer(recognizer)
132
+ }
133
+ default:
134
+ break
135
+ }
136
+ }
137
+ }
138
+
139
+ // MARK: - Hover Helper
140
+
141
+ private func attachHoverHandler(to view: NSView, wrapper: HoverWrapper) {
142
+ let trackingView = HoverTrackingView(wrapper: wrapper)
143
+ objc_setAssociatedObject(view, &hoverTrackingKey, trackingView, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
144
+ trackingView.attach(to: view)
145
+ }
146
+
147
+ // MARK: - Pressure Helper
148
+
149
+ private func attachPressureHandler(to view: NSView, wrapper: PressureWrapper) {
150
+ let pressureView = PressureTrackingView(wrapper: wrapper)
151
+ objc_setAssociatedObject(view, &pressureTrackingKey, pressureView, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
152
+ pressureView.attach(to: view)
153
+ }
154
+ }
155
+
156
+ // MARK: - HoverTrackingView
157
+
158
+ /// Custom NSView that handles hover events via tracking area
159
+ private class HoverTrackingView: NSView {
160
+ private let wrapper: HoverWrapper
161
+ private weak var targetView: NSView?
162
+ private var trackingArea: NSTrackingArea?
163
+
164
+ init(wrapper: HoverWrapper) {
165
+ self.wrapper = wrapper
166
+ super.init(frame: .zero)
167
+ }
168
+
169
+ required init?(coder: NSCoder) {
170
+ fatalError("init(coder:) has not been implemented")
171
+ }
172
+
173
+ func attach(to view: NSView) {
174
+ targetView = view
175
+ updateTrackingAreas()
176
+ }
177
+
178
+ override func updateTrackingAreas() {
179
+ super.updateTrackingAreas()
180
+
181
+ if let existing = trackingArea {
182
+ removeTrackingArea(existing)
183
+ }
184
+
185
+ if let target = targetView {
186
+ let area = NSTrackingArea(
187
+ rect: target.bounds,
188
+ options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways, .inVisibleRect],
189
+ owner: self,
190
+ userInfo: nil
191
+ )
192
+ addTrackingArea(area)
193
+ trackingArea = area
194
+ }
195
+ }
196
+
197
+ override func mouseEntered(with event: NSEvent) {
198
+ guard let target = targetView else { return }
199
+ let location = convert(event.locationInWindow, from: nil)
200
+ wrapper.handleHover(location: location, isEntering: true)
201
+ }
202
+
203
+ override func mouseExited(with event: NSEvent) {
204
+ guard let target = targetView else { return }
205
+ let location = convert(event.locationInWindow, from: nil)
206
+ wrapper.handleHover(location: location, isEntering: false)
207
+ }
208
+
209
+ override func mouseMoved(with event: NSEvent) {
210
+ guard let target = targetView else { return }
211
+ let location = convert(event.locationInWindow, from: nil)
212
+ wrapper.handleHover(location: location, isEntering: true)
213
+ }
214
+ }
215
+
216
+ // MARK: - PressureTrackingView
217
+
218
+ /// Custom NSView that handles Force Touch / pressure events
219
+ private class PressureTrackingView: NSView {
220
+ private let wrapper: PressureWrapper
221
+ private weak var targetView: NSView?
222
+
223
+ init(wrapper: PressureWrapper) {
224
+ self.wrapper = wrapper
225
+ super.init(frame: .zero)
226
+ }
227
+
228
+ required init?(coder: NSCoder) {
229
+ fatalError("init(coder:) has not been implemented")
230
+ }
231
+
232
+ func attach(to view: NSView) {
233
+ targetView = view
234
+ }
235
+
236
+ override func touchesBegan(with event: NSEvent) {
237
+ super.touchesBegan(with: event)
238
+ handlePressure(event: event)
239
+ }
240
+
241
+ override func touchesMoved(with event: NSEvent) {
242
+ super.touchesMoved(with: event)
243
+ handlePressure(event: event)
244
+ }
245
+
246
+ override func touchesEnded(with event: NSEvent) {
247
+ super.touchesEnded(with: event)
248
+ handlePressure(event: event)
249
+ }
250
+
251
+ private func handlePressure(event: NSEvent) {
252
+ guard let target = targetView else { return }
253
+
254
+ // Get pressure information (macOS 10.10.3+)
255
+ let pressure = CGFloat(event.pressure)
256
+ let location = event.locationInWindow
257
+ let locationInView = target.convert(location, from: nil)
258
+
259
+ // Pressure stages: 0 = no touch, 1-2 = light touch, 3+ = force touch
260
+ let stage: Int
261
+ if event.stage == 0 && pressure == 0 {
262
+ stage = 0
263
+ } else if event.stage == 1 {
264
+ stage = 1
265
+ } else if event.stage == 2 {
266
+ stage = 2
267
+ } else {
268
+ stage = Int(event.stage)
269
+ }
270
+
271
+ wrapper.handlePressure(pressure: pressure, location: locationInView, stage: stage)
272
+ }
273
+ }
274
+
275
+ private var hoverTrackingKey: UInt8 = 0
276
+ private var pressureTrackingKey: UInt8 = 0
277
+
278
+ // MARK: - GestureStorage
279
+
280
+ /// Stores gesture wrapper references as associated objects on views to prevent deallocation.
281
+ /// Uses a dictionary keyed by event name to support multiple gesture types per view.
282
+ enum GestureStorage {
283
+ private static var storageKey: UInt8 = 0
284
+
285
+ // MARK: Legacy — typed store for ClickGestureWrapper
286
+ static func store(_ wrapper: ClickGestureWrapper, for view: NSView, event: String) {
287
+ storeObject(wrapper, for: view, event: event)
288
+ }
289
+
290
+ // MARK: Generic store for any NSObject-derived wrapper
291
+ static func storeObject(_ wrapper: NSObject, for view: NSView, event: String) {
292
+ var storage = getStorage(for: view)
293
+ storage[event] = wrapper
294
+ setStorage(storage, for: view)
295
+ }
296
+
297
+ static func remove(for view: NSView, event: String) {
298
+ var storage = getStorage(for: view)
299
+ storage.removeValue(forKey: event)
300
+ setStorage(storage, for: view)
301
+ }
302
+
303
+ static func get(for view: NSView, event: String) -> ClickGestureWrapper? {
304
+ return getStorage(for: view)[event] as? ClickGestureWrapper
305
+ }
306
+
307
+ private static func getStorage(for view: NSView) -> [String: NSObject] {
308
+ return objc_getAssociatedObject(view, &storageKey) as? [String: NSObject] ?? [:]
309
+ }
310
+
311
+ private static func setStorage(_ storage: [String: NSObject], for view: NSView) {
312
+ objc_setAssociatedObject(view, &storageKey, storage, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
313
+ }
314
+ }
@@ -0,0 +1,162 @@
1
+ import AppKit
2
+ import WebKit
3
+ import ObjectiveC
4
+
5
+ /// Factory for VWebView — wraps WKWebView for macOS.
6
+ /// Supports loading URLs and inline HTML, plus load/error/loadStart events.
7
+ final class VWebViewFactory: NativeComponentFactory {
8
+
9
+ // nonisolated(unsafe) for keys accessed from WKNavigationDelegate callbacks
10
+ nonisolated(unsafe) fileprivate static var onLoadKey: UInt8 = 0
11
+ nonisolated(unsafe) fileprivate static var onErrorKey: UInt8 = 1
12
+ nonisolated(unsafe) fileprivate static var onLoadStartKey: UInt8 = 2
13
+ fileprivate static var delegateKey: UInt8 = 3
14
+
15
+ // MARK: - NativeComponentFactory
16
+
17
+ func createView() -> NSView {
18
+ let config = WKWebViewConfiguration()
19
+ let webView = WKWebView(frame: .zero, configuration: config)
20
+ webView.wantsLayer = true
21
+ webView.ensureLayoutNode()
22
+ return webView
23
+ }
24
+
25
+ func updateProp(view: NSView, key: String, value: Any?) {
26
+ guard let webView = view as? WKWebView else {
27
+ StyleEngine.apply(key: key, value: value, to: view)
28
+ return
29
+ }
30
+
31
+ switch key {
32
+ case "source", "uri":
33
+ if let dict = value as? [String: Any] {
34
+ if let uri = dict["uri"] as? String, let url = URL(string: uri) {
35
+ webView.load(URLRequest(url: url))
36
+ } else if let html = dict["html"] as? String {
37
+ webView.loadHTMLString(html, baseURL: nil)
38
+ }
39
+ } else if let urlString = value as? String, let url = URL(string: urlString) {
40
+ webView.load(URLRequest(url: url))
41
+ }
42
+
43
+ case "html":
44
+ if let html = value as? String {
45
+ webView.loadHTMLString(html, baseURL: nil)
46
+ }
47
+
48
+ case "javaScriptEnabled":
49
+ // WKWebView has JS enabled by default; disabling requires WKPreferences on the
50
+ // configuration object, which cannot be changed after init. Silently ignore.
51
+ break
52
+
53
+ case "scrollEnabled":
54
+ // On macOS, WKWebView uses an enclosing scroll view
55
+ if let enabled = value as? Bool {
56
+ webView.enclosingScrollView?.hasVerticalScroller = enabled
57
+ webView.enclosingScrollView?.hasHorizontalScroller = enabled
58
+ }
59
+
60
+ case "userAgent":
61
+ if let ua = value as? String {
62
+ webView.customUserAgent = ua
63
+ }
64
+
65
+ default:
66
+ StyleEngine.apply(key: key, value: value, to: view)
67
+ }
68
+ }
69
+
70
+ func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
71
+ guard let webView = view as? WKWebView else { return }
72
+
73
+ switch event {
74
+ case "load":
75
+ objc_setAssociatedObject(
76
+ view, &VWebViewFactory.onLoadKey,
77
+ handler as AnyObject,
78
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
79
+ )
80
+ ensureDelegate(for: webView)
81
+
82
+ case "error":
83
+ objc_setAssociatedObject(
84
+ view, &VWebViewFactory.onErrorKey,
85
+ handler as AnyObject,
86
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
87
+ )
88
+ ensureDelegate(for: webView)
89
+
90
+ case "loadStart":
91
+ objc_setAssociatedObject(
92
+ view, &VWebViewFactory.onLoadStartKey,
93
+ handler as AnyObject,
94
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
95
+ )
96
+ ensureDelegate(for: webView)
97
+
98
+ default:
99
+ break
100
+ }
101
+ }
102
+
103
+ func removeEventListener(view: NSView, event: String) {
104
+ switch event {
105
+ case "load":
106
+ objc_setAssociatedObject(view, &VWebViewFactory.onLoadKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
107
+ case "error":
108
+ objc_setAssociatedObject(view, &VWebViewFactory.onErrorKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
109
+ case "loadStart":
110
+ objc_setAssociatedObject(view, &VWebViewFactory.onLoadStartKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
111
+ default:
112
+ break
113
+ }
114
+ }
115
+
116
+ // MARK: - Helpers
117
+
118
+ private func ensureDelegate(for webView: WKWebView) {
119
+ guard !(webView.navigationDelegate is MacWebViewDelegate) else { return }
120
+ let delegate = MacWebViewDelegate(view: webView)
121
+ objc_setAssociatedObject(
122
+ webView, &VWebViewFactory.delegateKey,
123
+ delegate,
124
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
125
+ )
126
+ webView.navigationDelegate = delegate
127
+ }
128
+ }
129
+
130
+ // MARK: - MacWebViewDelegate
131
+
132
+ private final class MacWebViewDelegate: NSObject, WKNavigationDelegate {
133
+ private weak var view: NSView?
134
+
135
+ init(view: NSView) { self.view = view }
136
+
137
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
138
+ guard let view = view else { return }
139
+ let handler = objc_getAssociatedObject(view, &VWebViewFactory.onLoadKey) as? ((Any?) -> Void)
140
+ handler?(["url": webView.url?.absoluteString ?? ""])
141
+ }
142
+
143
+ func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
144
+ guard let view = view else { return }
145
+ let handler = objc_getAssociatedObject(view, &VWebViewFactory.onLoadStartKey) as? ((Any?) -> Void)
146
+ handler?(["url": webView.url?.absoluteString ?? ""])
147
+ }
148
+
149
+ func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
150
+ guard let view = view else { return }
151
+ let handler = objc_getAssociatedObject(view, &VWebViewFactory.onErrorKey) as? ((Any?) -> Void)
152
+ handler?(["message": error.localizedDescription])
153
+ }
154
+
155
+ func webView(_ webView: WKWebView,
156
+ didFailProvisionalNavigation navigation: WKNavigation!,
157
+ withError error: Error) {
158
+ guard let view = view else { return }
159
+ let handler = objc_getAssociatedObject(view, &VWebViewFactory.onErrorKey) as? ((Any?) -> Void)
160
+ handler?(["message": error.localizedDescription])
161
+ }
162
+ }
@@ -0,0 +1,54 @@
1
+ import AppKit
2
+
3
+ /// Protocol that all native component factories must implement.
4
+ /// Each factory knows how to create an NSView, update its properties,
5
+ /// and wire up event listeners for a specific component type.
6
+ @MainActor
7
+ protocol NativeComponentFactory {
8
+
9
+ /// Create a new NSView instance for this component type.
10
+ /// The view should be configured with sensible defaults and a LayoutNode.
11
+ func createView() -> NSView
12
+
13
+ /// Update a property on the view. The key is the property name from JS,
14
+ /// and value is the property value (nil means the prop was removed).
15
+ func updateProp(view: NSView, key: String, value: Any?)
16
+
17
+ /// Add an event listener to the view. The handler closure will dispatch
18
+ /// the event payload back to the JS thread.
19
+ func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void)
20
+
21
+ /// Remove an event listener from the view for the given event name.
22
+ /// Default implementation is a no-op.
23
+ func removeEventListener(view: NSView, event: String)
24
+
25
+ /// Insert a child view into the parent. Called by the bridge instead of addSubview.
26
+ /// Default implementation calls parent.addSubview(child) with optional anchor positioning.
27
+ func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?)
28
+
29
+ /// Remove a child view from the parent. Called by the bridge instead of removeFromSuperview.
30
+ /// Default implementation calls child.removeFromSuperview().
31
+ func removeChild(_ child: NSView, from parent: NSView)
32
+ }
33
+
34
+ // Default implementation for optional methods
35
+ extension NativeComponentFactory {
36
+ func removeEventListener(view: NSView, event: String) {
37
+ // Default no-op. Factories can override to clean up specific listeners.
38
+ }
39
+
40
+ func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?) {
41
+ if let anchor = anchor, let idx = parent.subviews.firstIndex(of: anchor) {
42
+ // NSView uses addSubview(_:positioned:relativeTo:) for ordering.
43
+ // .below places the child just before the anchor in the subview array.
44
+ parent.addSubview(child, positioned: .below, relativeTo: anchor)
45
+ } else {
46
+ parent.addSubview(child)
47
+ }
48
+ child.ensureLayoutNode()
49
+ }
50
+
51
+ func removeChild(_ child: NSView, from parent: NSView) {
52
+ child.removeFromSuperview()
53
+ }
54
+ }
@@ -0,0 +1,100 @@
1
+ import AppKit
2
+
3
+ /// Custom NSView subclass that provides button-like behavior with mouse events.
4
+ /// macOS equivalent of iOS TouchableView.
5
+ /// Supports press and long press events with configurable active opacity.
6
+ class ClickableView: FlippedView {
7
+
8
+ // MARK: - Public properties
9
+
10
+ /// The opacity to apply when the user is pressing the view.
11
+ var activeOpacity: CGFloat = 0.7
12
+
13
+ /// Called when a click completes within the view bounds.
14
+ var onPress: (() -> Void)?
15
+
16
+ /// Called when a long press gesture is recognized.
17
+ var onLongPress: (() -> Void)?
18
+
19
+ /// Whether mouse interactions are disabled.
20
+ var isDisabled: Bool = false {
21
+ didSet {
22
+ alphaValue = isDisabled ? 0.4 : 1.0
23
+ }
24
+ }
25
+
26
+ // MARK: - Private properties
27
+
28
+ private var isPressed = false
29
+ private var longPressTimer: Timer?
30
+
31
+ // MARK: - Mouse handling
32
+
33
+ override func mouseDown(with event: NSEvent) {
34
+ guard !isDisabled else { return }
35
+ isPressed = true
36
+
37
+ NSAnimationContext.runAnimationGroup { ctx in
38
+ ctx.duration = 0.1
39
+ self.animator().alphaValue = activeOpacity
40
+ }
41
+
42
+ // Start long press timer
43
+ longPressTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
44
+ self?.onLongPress?()
45
+ }
46
+ }
47
+
48
+ override func mouseUp(with event: NSEvent) {
49
+ guard !isDisabled, isPressed else { return }
50
+ isPressed = false
51
+
52
+ longPressTimer?.invalidate()
53
+ longPressTimer = nil
54
+
55
+ NSAnimationContext.runAnimationGroup { ctx in
56
+ ctx.duration = 0.1
57
+ self.animator().alphaValue = 1.0
58
+ }
59
+
60
+ // Check if mouse is still inside bounds
61
+ let location = convert(event.locationInWindow, from: nil)
62
+ if bounds.contains(location) {
63
+ onPress?()
64
+ }
65
+ }
66
+
67
+ override func mouseExited(with event: NSEvent) {
68
+ guard isPressed else { return }
69
+ NSAnimationContext.runAnimationGroup { ctx in
70
+ ctx.duration = 0.1
71
+ self.animator().alphaValue = 1.0
72
+ }
73
+ }
74
+
75
+ override func mouseEntered(with event: NSEvent) {
76
+ guard isPressed else { return }
77
+ NSAnimationContext.runAnimationGroup { ctx in
78
+ ctx.duration = 0.1
79
+ self.animator().alphaValue = activeOpacity
80
+ }
81
+ }
82
+
83
+ // MARK: - Tracking areas
84
+
85
+ override func updateTrackingAreas() {
86
+ super.updateTrackingAreas()
87
+ // Remove old tracking areas
88
+ for area in trackingAreas {
89
+ removeTrackingArea(area)
90
+ }
91
+ // Add new tracking area for mouse enter/exit events
92
+ let area = NSTrackingArea(
93
+ rect: bounds,
94
+ options: [.mouseEnteredAndExited, .activeInActiveApp],
95
+ owner: self,
96
+ userInfo: nil
97
+ )
98
+ addTrackingArea(area)
99
+ }
100
+ }
@@ -0,0 +1,23 @@
1
+ import AppKit
2
+
3
+ // MARK: - Safe array subscript
4
+
5
+ extension Array {
6
+ subscript(safe index: Int) -> Element? {
7
+ indices.contains(index) ? self[index] : nil
8
+ }
9
+ }
10
+
11
+ // MARK: - Main window helper
12
+
13
+ extension NSApplication {
14
+ /// The main window, or the first visible window.
15
+ static var vn_mainWindow: NSWindow? {
16
+ NSApp.mainWindow ?? NSApp.windows.first { $0.isVisible }
17
+ }
18
+
19
+ /// Returns the main window's content view controller.
20
+ static var vn_contentViewController: NSViewController? {
21
+ vn_mainWindow?.contentViewController
22
+ }
23
+ }