@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/Bridge/VueNativeWindowController.swift
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
/// Base window controller for Vue Native macOS apps.
|
|
4
|
+
/// Subclass and override `bundleName` and optionally `devServerURL`.
|
|
5
|
+
///
|
|
6
|
+
/// Usage:
|
|
7
|
+
/// ```swift
|
|
8
|
+
/// class MainWindowController: VueNativeWindowController {
|
|
9
|
+
/// override var bundleName: String { "vue-native-bundle" }
|
|
10
|
+
/// override var devServerURL: URL? {
|
|
11
|
+
/// #if DEBUG
|
|
12
|
+
/// URL(string: "ws://localhost:8174")
|
|
13
|
+
/// #else
|
|
14
|
+
/// nil
|
|
15
|
+
/// #endif
|
|
16
|
+
/// }
|
|
17
|
+
/// }
|
|
18
|
+
/// ```
|
|
19
|
+
open class VueNativeWindowController: NSWindowController {
|
|
20
|
+
|
|
21
|
+
// MARK: - Overridable API
|
|
22
|
+
|
|
23
|
+
/// Name of the JS bundle resource (without extension) bundled in your app target.
|
|
24
|
+
open var bundleName: String { "vue-native-bundle" }
|
|
25
|
+
|
|
26
|
+
/// WebSocket URL of the Vite dev server for hot reload.
|
|
27
|
+
/// Return `nil` (the default) to disable hot reload and load only from the bundle.
|
|
28
|
+
open var devServerURL: URL? { nil }
|
|
29
|
+
|
|
30
|
+
// MARK: - Private state
|
|
31
|
+
|
|
32
|
+
private let runtime = JSRuntime.shared
|
|
33
|
+
private let bridge = NativeBridge.shared
|
|
34
|
+
|
|
35
|
+
// MARK: - Convenience initializer
|
|
36
|
+
|
|
37
|
+
public convenience init() {
|
|
38
|
+
let window = NSWindow(
|
|
39
|
+
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
|
|
40
|
+
styleMask: [.titled, .closable, .resizable, .miniaturizable],
|
|
41
|
+
backing: .buffered,
|
|
42
|
+
defer: false
|
|
43
|
+
)
|
|
44
|
+
window.center()
|
|
45
|
+
window.title = "Vue Native"
|
|
46
|
+
|
|
47
|
+
// Use a FlippedView as the content view so all coordinates are top-left origin.
|
|
48
|
+
let flippedContent = FlippedView(frame: window.contentView!.bounds)
|
|
49
|
+
flippedContent.autoresizingMask = [.width, .height]
|
|
50
|
+
window.contentView = flippedContent
|
|
51
|
+
|
|
52
|
+
self.init(window: window)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MARK: - Lifecycle
|
|
56
|
+
|
|
57
|
+
override open func windowDidLoad() {
|
|
58
|
+
super.windowDidLoad()
|
|
59
|
+
|
|
60
|
+
guard let contentView = window?.contentView else { return }
|
|
61
|
+
contentView.wantsLayer = true
|
|
62
|
+
contentView.layer?.backgroundColor = NSColor.black.cgColor
|
|
63
|
+
|
|
64
|
+
// Initialize JS engine first, then bridge.
|
|
65
|
+
runtime.initialize { [weak self] in
|
|
66
|
+
guard let self = self else { return }
|
|
67
|
+
self.bridge.initialize(contentView: contentView)
|
|
68
|
+
DispatchQueue.main.async {
|
|
69
|
+
self.loadBundle()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// MARK: - Bundle loading
|
|
75
|
+
|
|
76
|
+
private func loadBundle() {
|
|
77
|
+
#if DEBUG
|
|
78
|
+
if let wsURL = devServerURL {
|
|
79
|
+
HotReloadManager.shared.connect(to: wsURL)
|
|
80
|
+
}
|
|
81
|
+
#endif
|
|
82
|
+
loadEmbeddedBundle()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private func loadEmbeddedBundle() {
|
|
86
|
+
runtime.loadBundle(source: .embedded(name: bundleName)) { success in
|
|
87
|
+
if !success {
|
|
88
|
+
NSLog("[VueNative macOS] ERROR: Failed to load bundle '%@'", self.bundleName)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MARK: - VueNativeAppDelegate
|
|
95
|
+
|
|
96
|
+
/// Convenience NSApplicationDelegate for single-window Vue Native apps.
|
|
97
|
+
/// Subclass and override `createWindowController()` to provide your custom window controller.
|
|
98
|
+
///
|
|
99
|
+
/// Usage:
|
|
100
|
+
/// ```swift
|
|
101
|
+
/// @main
|
|
102
|
+
/// class AppDelegate: VueNativeAppDelegate {
|
|
103
|
+
/// override func createWindowController() -> VueNativeWindowController {
|
|
104
|
+
/// return MainWindowController()
|
|
105
|
+
/// }
|
|
106
|
+
/// }
|
|
107
|
+
/// ```
|
|
108
|
+
open class VueNativeAppDelegate: NSObject, NSApplicationDelegate {
|
|
109
|
+
public var windowController: VueNativeWindowController?
|
|
110
|
+
|
|
111
|
+
open func applicationDidFinishLaunching(_ notification: Notification) {
|
|
112
|
+
let controller = createWindowController()
|
|
113
|
+
controller.showWindow(nil)
|
|
114
|
+
windowController = controller
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Override this to provide your custom VueNativeWindowController subclass.
|
|
118
|
+
open func createWindowController() -> VueNativeWindowController {
|
|
119
|
+
return VueNativeWindowController()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
open func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
// MARK: - Associated object key for factory reference
|
|
5
|
+
|
|
6
|
+
private var factoryAssociatedKey: UInt8 = 0
|
|
7
|
+
|
|
8
|
+
// MARK: - ComponentRegistry
|
|
9
|
+
|
|
10
|
+
/// Singleton registry that maps component type strings (e.g., "VView", "VText")
|
|
11
|
+
/// to their corresponding NativeComponentFactory instances.
|
|
12
|
+
/// When a view is created by a factory, the factory reference is stored on the
|
|
13
|
+
/// view via objc_setAssociatedObject for later prop updates and event wiring.
|
|
14
|
+
@MainActor
|
|
15
|
+
final class ComponentRegistry {
|
|
16
|
+
|
|
17
|
+
// MARK: - Singleton
|
|
18
|
+
|
|
19
|
+
static let shared = ComponentRegistry()
|
|
20
|
+
|
|
21
|
+
// MARK: - Properties
|
|
22
|
+
|
|
23
|
+
/// Mapping from component type strings to factory instances.
|
|
24
|
+
private var factories: [String: NativeComponentFactory] = [:]
|
|
25
|
+
|
|
26
|
+
// MARK: - Initialization
|
|
27
|
+
|
|
28
|
+
private init() {
|
|
29
|
+
registerDefaults()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Register all built-in component factories.
|
|
33
|
+
private func registerDefaults() {
|
|
34
|
+
// Core components (Phase 1)
|
|
35
|
+
register("VView", factory: VViewFactory())
|
|
36
|
+
register("VText", factory: VTextFactory())
|
|
37
|
+
register("VButton", factory: VButtonFactory())
|
|
38
|
+
register("VInput", factory: VInputFactory())
|
|
39
|
+
register("VSwitch", factory: VSwitchFactory())
|
|
40
|
+
register("__ROOT__", factory: VRootFactory())
|
|
41
|
+
|
|
42
|
+
// Simple controls (Phase 2)
|
|
43
|
+
register("VSlider", factory: VSliderFactory())
|
|
44
|
+
register("VProgressBar", factory: VProgressBarFactory())
|
|
45
|
+
register("VActivityIndicator", factory: VActivityIndicatorFactory())
|
|
46
|
+
register("VPicker", factory: VPickerFactory())
|
|
47
|
+
register("VSegmentedControl", factory: VSegmentedControlFactory())
|
|
48
|
+
register("VCheckbox", factory: VCheckboxFactory())
|
|
49
|
+
register("VRadio", factory: VRadioFactory())
|
|
50
|
+
register("VDropdown", factory: VDropdownFactory())
|
|
51
|
+
|
|
52
|
+
// Complex components (Phase 2)
|
|
53
|
+
register("VScrollView", factory: VScrollViewFactory())
|
|
54
|
+
register("VList", factory: VListFactory())
|
|
55
|
+
register("VSectionList", factory: VSectionListFactory())
|
|
56
|
+
register("VImage", factory: VImageFactory())
|
|
57
|
+
register("VPressable", factory: VPressableFactory())
|
|
58
|
+
|
|
59
|
+
// Dialogs and media (Phase 2)
|
|
60
|
+
register("VModal", factory: VModalFactory())
|
|
61
|
+
register("VAlertDialog", factory: VAlertDialogFactory())
|
|
62
|
+
register("VActionSheet", factory: VActionSheetFactory())
|
|
63
|
+
register("VWebView", factory: VWebViewFactory())
|
|
64
|
+
register("VVideo", factory: VVideoFactory())
|
|
65
|
+
|
|
66
|
+
// Platform stubs (no-op on macOS)
|
|
67
|
+
register("VStatusBar", factory: VStatusBarFactory())
|
|
68
|
+
register("VKeyboardAvoiding", factory: VKeyboardAvoidingFactory())
|
|
69
|
+
register("VSafeArea", factory: VSafeAreaFactory())
|
|
70
|
+
register("VRefreshControl", factory: VRefreshControlFactory())
|
|
71
|
+
|
|
72
|
+
// macOS-specific components (Phase 3)
|
|
73
|
+
register("VToolbar", factory: VToolbarFactory())
|
|
74
|
+
register("VSplitView", factory: VSplitViewFactory())
|
|
75
|
+
register("VOutlineView", factory: VOutlineViewFactory())
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: - Registration
|
|
79
|
+
|
|
80
|
+
/// Register a factory for a given component type string.
|
|
81
|
+
/// Replaces any existing factory for that type.
|
|
82
|
+
func register(_ type: String, factory: NativeComponentFactory) {
|
|
83
|
+
factories[type] = factory
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Unregister a factory for a given component type string.
|
|
87
|
+
func unregister(_ type: String) {
|
|
88
|
+
factories.removeValue(forKey: type)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MARK: - View Creation
|
|
92
|
+
|
|
93
|
+
/// Create a new NSView for the given component type.
|
|
94
|
+
/// Returns nil if no factory is registered for the type.
|
|
95
|
+
/// The factory is stored as an associated object on the created view
|
|
96
|
+
/// so it can be retrieved later for prop updates and event handling.
|
|
97
|
+
func createView(type: String) -> NSView? {
|
|
98
|
+
guard let factory = factories[type] else {
|
|
99
|
+
NSLog("[VueNative] Warning: No factory registered for component type '%@'", type)
|
|
100
|
+
return nil
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let view = factory.createView()
|
|
104
|
+
|
|
105
|
+
// Store factory reference on the view for later lookups
|
|
106
|
+
objc_setAssociatedObject(
|
|
107
|
+
view,
|
|
108
|
+
&factoryAssociatedKey,
|
|
109
|
+
FactoryBox(factory: factory),
|
|
110
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return view
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// MARK: - Factory Retrieval
|
|
117
|
+
|
|
118
|
+
/// Retrieve the factory for the given component type string.
|
|
119
|
+
func factory(for type: String) -> NativeComponentFactory? {
|
|
120
|
+
return factories[type]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Retrieve the factory that was used to create the given view.
|
|
124
|
+
/// Uses the associated object stored during createView().
|
|
125
|
+
static func factory(for view: NSView) -> NativeComponentFactory? {
|
|
126
|
+
guard let box = objc_getAssociatedObject(view, &factoryAssociatedKey) as? FactoryBox else {
|
|
127
|
+
return nil
|
|
128
|
+
}
|
|
129
|
+
return box.factory
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MARK: - Prop Updates
|
|
133
|
+
|
|
134
|
+
/// Update a property on a view using its associated factory.
|
|
135
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
136
|
+
guard let factory = ComponentRegistry.factory(for: view) else {
|
|
137
|
+
NSLog("[VueNative] Warning: No factory found for view %@", String(describing: Swift.type(of: view)))
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
factory.updateProp(view: view, key: key, value: value)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// MARK: - Event Listeners
|
|
144
|
+
|
|
145
|
+
/// Add an event listener to a view using its associated factory.
|
|
146
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
147
|
+
guard let factory = ComponentRegistry.factory(for: view) else {
|
|
148
|
+
NSLog("[VueNative] Warning: No factory found for view %@", String(describing: Swift.type(of: view)))
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
factory.addEventListener(view: view, event: event, handler: handler)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Remove an event listener from a view using its associated factory.
|
|
155
|
+
func removeEventListener(view: NSView, event: String) {
|
|
156
|
+
guard let factory = ComponentRegistry.factory(for: view) else { return }
|
|
157
|
+
factory.removeEventListener(view: view, event: event)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// MARK: - FactoryBox
|
|
162
|
+
|
|
163
|
+
/// Box wrapper for NativeComponentFactory to store as an associated object.
|
|
164
|
+
/// objc_setAssociatedObject requires an AnyObject, so we wrap the protocol.
|
|
165
|
+
private final class FactoryBox {
|
|
166
|
+
let factory: NativeComponentFactory
|
|
167
|
+
|
|
168
|
+
init(factory: NativeComponentFactory) {
|
|
169
|
+
self.factory = factory
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// MARK: - VRootFactory
|
|
174
|
+
|
|
175
|
+
/// Factory for the __ROOT__ component type.
|
|
176
|
+
/// Creates a FlippedView with a LayoutNode that serves as the root container.
|
|
177
|
+
final class VRootFactory: NativeComponentFactory {
|
|
178
|
+
|
|
179
|
+
func createView() -> NSView {
|
|
180
|
+
let view = FlippedView()
|
|
181
|
+
// Configure the root layout node to fill its container and stack children
|
|
182
|
+
// vertically (column direction matches the default CSS flex behaviour).
|
|
183
|
+
let node = view.ensureLayoutNode()
|
|
184
|
+
node.flexDirection = .column
|
|
185
|
+
node.flexGrow = 1
|
|
186
|
+
node.flexShrink = 1
|
|
187
|
+
node.width = .percent(100)
|
|
188
|
+
node.height = .percent(100)
|
|
189
|
+
return view
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
193
|
+
// Root view generally doesn't have custom props.
|
|
194
|
+
// Delegate to StyleEngine for any style-related props.
|
|
195
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
199
|
+
if event == "press" {
|
|
200
|
+
let wrapper = ClickGestureWrapper(handler: handler)
|
|
201
|
+
let click = NSClickGestureRecognizer(
|
|
202
|
+
target: wrapper,
|
|
203
|
+
action: #selector(ClickGestureWrapper.handleGesture(_:))
|
|
204
|
+
)
|
|
205
|
+
view.addGestureRecognizer(click)
|
|
206
|
+
GestureStorage.store(wrapper, for: view, event: event)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VActionSheet — maps to NSMenu (context menu) on macOS.
|
|
5
|
+
/// macOS has no action sheet UI; an NSMenu popup is the closest equivalent.
|
|
6
|
+
/// The view itself is a zero-size hidden placeholder in the native tree.
|
|
7
|
+
final class VActionSheetFactory: NativeComponentFactory {
|
|
8
|
+
|
|
9
|
+
// MARK: - Associated object keys
|
|
10
|
+
|
|
11
|
+
private static var actionsKey: UInt8 = 0
|
|
12
|
+
private static var cancelLabelKey: UInt8 = 1
|
|
13
|
+
nonisolated(unsafe) static var onActionKey: UInt8 = 2
|
|
14
|
+
nonisolated(unsafe) static var onCancelKey: UInt8 = 3
|
|
15
|
+
private static var menuDelegateKey: UInt8 = 4
|
|
16
|
+
|
|
17
|
+
// MARK: - NativeComponentFactory
|
|
18
|
+
|
|
19
|
+
func createView() -> NSView {
|
|
20
|
+
let view = FlippedView()
|
|
21
|
+
view.isHidden = true
|
|
22
|
+
let node = view.ensureLayoutNode()
|
|
23
|
+
node.width = .points(0)
|
|
24
|
+
node.height = .points(0)
|
|
25
|
+
return view
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
29
|
+
switch key {
|
|
30
|
+
case "visible":
|
|
31
|
+
let visible = (value as? Bool) ?? ((value as? Int).map { $0 != 0 } ?? false)
|
|
32
|
+
if visible {
|
|
33
|
+
showMenu(for: view)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case "title":
|
|
37
|
+
// Ignored on macOS — NSMenu doesn't display a title in popup mode
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
case "actions":
|
|
41
|
+
objc_setAssociatedObject(
|
|
42
|
+
view, &VActionSheetFactory.actionsKey,
|
|
43
|
+
value,
|
|
44
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
case "cancelLabel":
|
|
48
|
+
objc_setAssociatedObject(
|
|
49
|
+
view, &VActionSheetFactory.cancelLabelKey,
|
|
50
|
+
value as? String,
|
|
51
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
60
|
+
switch event {
|
|
61
|
+
case "action":
|
|
62
|
+
objc_setAssociatedObject(
|
|
63
|
+
view, &VActionSheetFactory.onActionKey,
|
|
64
|
+
handler as AnyObject,
|
|
65
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
66
|
+
)
|
|
67
|
+
case "cancel":
|
|
68
|
+
objc_setAssociatedObject(
|
|
69
|
+
view, &VActionSheetFactory.onCancelKey,
|
|
70
|
+
handler as AnyObject,
|
|
71
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
72
|
+
)
|
|
73
|
+
default:
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func removeEventListener(view: NSView, event: String) {
|
|
79
|
+
switch event {
|
|
80
|
+
case "action":
|
|
81
|
+
objc_setAssociatedObject(view, &VActionSheetFactory.onActionKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
82
|
+
case "cancel":
|
|
83
|
+
objc_setAssociatedObject(view, &VActionSheetFactory.onCancelKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
84
|
+
default:
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Menu presentation
|
|
90
|
+
|
|
91
|
+
private func showMenu(for view: NSView) {
|
|
92
|
+
let menu = NSMenu()
|
|
93
|
+
|
|
94
|
+
// Parse actions — supports [String] and [[String: Any]] with {label, destructive?}
|
|
95
|
+
var actionLabels: [String] = []
|
|
96
|
+
if let actions = objc_getAssociatedObject(view, &VActionSheetFactory.actionsKey) as? [String] {
|
|
97
|
+
actionLabels = actions
|
|
98
|
+
} else if let actions = objc_getAssociatedObject(view, &VActionSheetFactory.actionsKey) as? [[String: Any]] {
|
|
99
|
+
actionLabels = actions.compactMap { $0["label"] as? String }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Create proxy to handle menu item selection
|
|
103
|
+
let proxy = ActionSheetProxy(view: view)
|
|
104
|
+
objc_setAssociatedObject(
|
|
105
|
+
view, &VActionSheetFactory.menuDelegateKey,
|
|
106
|
+
proxy, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
for (index, label) in actionLabels.enumerated() {
|
|
110
|
+
let item = NSMenuItem(title: label, action: #selector(ActionSheetProxy.itemSelected(_:)), keyEquivalent: "")
|
|
111
|
+
item.target = proxy
|
|
112
|
+
item.tag = index
|
|
113
|
+
menu.addItem(item)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add separator + cancel item if cancelLabel is set
|
|
117
|
+
if let cancelLabel = objc_getAssociatedObject(view, &VActionSheetFactory.cancelLabelKey) as? String {
|
|
118
|
+
menu.addItem(.separator())
|
|
119
|
+
let cancelItem = NSMenuItem(title: cancelLabel, action: #selector(ActionSheetProxy.cancelSelected(_:)), keyEquivalent: "")
|
|
120
|
+
cancelItem.target = proxy
|
|
121
|
+
cancelItem.tag = -1
|
|
122
|
+
menu.addItem(cancelItem)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Pop up the menu at the view's location (use superview if placeholder is hidden)
|
|
126
|
+
let targetView = view.superview ?? view
|
|
127
|
+
let location = NSPoint(x: 0, y: 0)
|
|
128
|
+
menu.popUp(positioning: nil, at: location, in: targetView)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MARK: - ActionSheetProxy
|
|
133
|
+
|
|
134
|
+
/// Target-action proxy for NSMenu item selection.
|
|
135
|
+
private final class ActionSheetProxy: NSObject {
|
|
136
|
+
private weak var view: NSView?
|
|
137
|
+
|
|
138
|
+
init(view: NSView) {
|
|
139
|
+
self.view = view
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@objc func itemSelected(_ sender: NSMenuItem) {
|
|
143
|
+
guard let view = view else { return }
|
|
144
|
+
if let handler = objc_getAssociatedObject(view, &VActionSheetFactory.onActionKey) as? ((Any?) -> Void) {
|
|
145
|
+
handler(["label": sender.title, "index": sender.tag])
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@objc func cancelSelected(_ sender: NSMenuItem) {
|
|
150
|
+
guard let view = view else { return }
|
|
151
|
+
if let handler = objc_getAssociatedObject(view, &VActionSheetFactory.onCancelKey) as? ((Any?) -> Void) {
|
|
152
|
+
handler(nil)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
/// Factory for VActivityIndicator — a spinning progress indicator.
|
|
4
|
+
/// Maps to NSProgressIndicator with spinning style.
|
|
5
|
+
final class VActivityIndicatorFactory: NativeComponentFactory {
|
|
6
|
+
|
|
7
|
+
// MARK: - NativeComponentFactory
|
|
8
|
+
|
|
9
|
+
func createView() -> NSView {
|
|
10
|
+
let indicator = NSProgressIndicator()
|
|
11
|
+
indicator.style = .spinning
|
|
12
|
+
indicator.isIndeterminate = true
|
|
13
|
+
indicator.isDisplayedWhenStopped = false
|
|
14
|
+
indicator.wantsLayer = true
|
|
15
|
+
indicator.ensureLayoutNode()
|
|
16
|
+
indicator.startAnimation(nil)
|
|
17
|
+
return indicator
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
21
|
+
guard let indicator = view as? NSProgressIndicator else {
|
|
22
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
switch key {
|
|
27
|
+
case "animating":
|
|
28
|
+
let animating: Bool
|
|
29
|
+
if let val = value as? Bool {
|
|
30
|
+
animating = val
|
|
31
|
+
} else if let val = value as? Int {
|
|
32
|
+
animating = val != 0
|
|
33
|
+
} else {
|
|
34
|
+
animating = true
|
|
35
|
+
}
|
|
36
|
+
if animating {
|
|
37
|
+
indicator.startAnimation(nil)
|
|
38
|
+
} else {
|
|
39
|
+
indicator.stopAnimation(nil)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case "color":
|
|
43
|
+
if let colorStr = value as? String {
|
|
44
|
+
indicator.appearance = nil // Reset to allow tinting
|
|
45
|
+
indicator.contentFilters = [CIFilter(name: "CIFalseColor",
|
|
46
|
+
parameters: [
|
|
47
|
+
"inputColor0": CIColor(color: NSColor.fromHex(colorStr))!,
|
|
48
|
+
"inputColor1": CIColor(color: NSColor.fromHex(colorStr))!
|
|
49
|
+
])!]
|
|
50
|
+
} else {
|
|
51
|
+
indicator.contentFilters = []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case "hidesWhenStopped":
|
|
55
|
+
let hides: Bool
|
|
56
|
+
if let val = value as? Bool {
|
|
57
|
+
hides = val
|
|
58
|
+
} else if let val = value as? Int {
|
|
59
|
+
hides = val != 0
|
|
60
|
+
} else {
|
|
61
|
+
hides = true
|
|
62
|
+
}
|
|
63
|
+
indicator.isDisplayedWhenStopped = !hides
|
|
64
|
+
|
|
65
|
+
case "size":
|
|
66
|
+
if let sizeStr = value as? String {
|
|
67
|
+
switch sizeStr {
|
|
68
|
+
case "small":
|
|
69
|
+
indicator.controlSize = .small
|
|
70
|
+
case "large":
|
|
71
|
+
indicator.controlSize = .large
|
|
72
|
+
default:
|
|
73
|
+
indicator.controlSize = .regular
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
default:
|
|
78
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
83
|
+
// No events for activity indicator
|
|
84
|
+
}
|
|
85
|
+
}
|