@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.
- 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/VViewFactory.swift
ADDED
|
@@ -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
|
+
}
|
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift
ADDED
|
@@ -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
|
+
}
|