@thelacanians/vue-native-cli 0.4.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VRadio — a radio button group for mutually exclusive selection.
|
|
5
|
+
/// Maps to an NSStackView containing multiple NSButton radio items.
|
|
6
|
+
final class VRadioFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
private static var changeHandlerKey: UInt8 = 0
|
|
9
|
+
nonisolated(unsafe) static var optionsKey: UInt8 = 0
|
|
10
|
+
nonisolated(unsafe) static var selectedValueKey: UInt8 = 0
|
|
11
|
+
|
|
12
|
+
// MARK: - NativeComponentFactory
|
|
13
|
+
|
|
14
|
+
func createView() -> NSView {
|
|
15
|
+
let stack = NSStackView()
|
|
16
|
+
stack.orientation = .vertical
|
|
17
|
+
stack.alignment = .leading
|
|
18
|
+
stack.spacing = 4
|
|
19
|
+
stack.wantsLayer = true
|
|
20
|
+
stack.ensureLayoutNode()
|
|
21
|
+
return stack
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
25
|
+
guard let stack = view as? NSStackView else {
|
|
26
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch key {
|
|
31
|
+
case "options":
|
|
32
|
+
guard let options = value as? [[String: Any]] else { return }
|
|
33
|
+
rebuildRadioButtons(in: stack, options: options)
|
|
34
|
+
objc_setAssociatedObject(stack, &VRadioFactory.optionsKey, options, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
35
|
+
|
|
36
|
+
case "selectedValue":
|
|
37
|
+
if let val = value as? String {
|
|
38
|
+
objc_setAssociatedObject(stack, &VRadioFactory.selectedValueKey, val, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
39
|
+
selectValue(val, in: stack)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case "disabled":
|
|
43
|
+
let disabled: Bool
|
|
44
|
+
if let val = value as? Bool {
|
|
45
|
+
disabled = val
|
|
46
|
+
} else if let val = value as? Int {
|
|
47
|
+
disabled = val != 0
|
|
48
|
+
} else {
|
|
49
|
+
disabled = false
|
|
50
|
+
}
|
|
51
|
+
for case let button as NSButton in stack.arrangedSubviews {
|
|
52
|
+
button.isEnabled = !disabled
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case "tintColor":
|
|
56
|
+
if let colorStr = value as? String {
|
|
57
|
+
let color = NSColor.fromHex(colorStr)
|
|
58
|
+
for case let button as NSButton in stack.arrangedSubviews {
|
|
59
|
+
button.contentTintColor = color
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
default:
|
|
64
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
69
|
+
guard event == "change" else { return }
|
|
70
|
+
|
|
71
|
+
let proxy = RadioGroupActionProxy(stack: view as? NSStackView, handler: handler)
|
|
72
|
+
objc_setAssociatedObject(
|
|
73
|
+
view,
|
|
74
|
+
&VRadioFactory.changeHandlerKey,
|
|
75
|
+
proxy,
|
|
76
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Wire existing buttons to this proxy
|
|
80
|
+
if let stack = view as? NSStackView {
|
|
81
|
+
wireButtons(in: stack, to: proxy)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func removeEventListener(view: NSView, event: String) {
|
|
86
|
+
guard event == "change" else { return }
|
|
87
|
+
|
|
88
|
+
if let stack = view as? NSStackView {
|
|
89
|
+
for case let button as NSButton in stack.arrangedSubviews {
|
|
90
|
+
button.target = nil
|
|
91
|
+
button.action = nil
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
objc_setAssociatedObject(view, &VRadioFactory.changeHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Override insertChild/removeChild: radio group manages its own children
|
|
98
|
+
func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?) {
|
|
99
|
+
// No-op: radio buttons are internally managed
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func removeChild(_ child: NSView, from parent: NSView) {
|
|
103
|
+
// No-op: radio buttons are internally managed
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// MARK: - Private
|
|
107
|
+
|
|
108
|
+
private func rebuildRadioButtons(in stack: NSStackView, options: [[String: Any]]) {
|
|
109
|
+
// Remove existing buttons
|
|
110
|
+
for view in stack.arrangedSubviews {
|
|
111
|
+
stack.removeArrangedSubview(view)
|
|
112
|
+
view.removeFromSuperview()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let selectedValue = objc_getAssociatedObject(stack, &VRadioFactory.selectedValueKey) as? String
|
|
116
|
+
let proxy = objc_getAssociatedObject(stack, &VRadioFactory.changeHandlerKey) as? RadioGroupActionProxy
|
|
117
|
+
|
|
118
|
+
for (index, option) in options.enumerated() {
|
|
119
|
+
let label = option["label"] as? String ?? option["value"] as? String ?? ""
|
|
120
|
+
let value = option["value"] as? String ?? label
|
|
121
|
+
|
|
122
|
+
let button = NSButton()
|
|
123
|
+
button.setButtonType(.radio)
|
|
124
|
+
button.title = label
|
|
125
|
+
button.tag = index
|
|
126
|
+
button.wantsLayer = true
|
|
127
|
+
|
|
128
|
+
if value == selectedValue {
|
|
129
|
+
button.state = .on
|
|
130
|
+
} else {
|
|
131
|
+
button.state = .off
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if let proxy = proxy {
|
|
135
|
+
button.target = proxy
|
|
136
|
+
button.action = #selector(RadioGroupActionProxy.radioSelected(_:))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
stack.addArrangedSubview(button)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private func selectValue(_ value: String, in stack: NSStackView) {
|
|
144
|
+
let options = objc_getAssociatedObject(stack, &VRadioFactory.optionsKey) as? [[String: Any]] ?? []
|
|
145
|
+
for (index, option) in options.enumerated() {
|
|
146
|
+
let optionValue = option["value"] as? String ?? option["label"] as? String ?? ""
|
|
147
|
+
if let button = stack.arrangedSubviews[safe: index] as? NSButton {
|
|
148
|
+
button.state = optionValue == value ? .on : .off
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func wireButtons(in stack: NSStackView, to proxy: RadioGroupActionProxy) {
|
|
154
|
+
for case let button as NSButton in stack.arrangedSubviews {
|
|
155
|
+
button.target = proxy
|
|
156
|
+
button.action = #selector(RadioGroupActionProxy.radioSelected(_:))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// MARK: - RadioGroupActionProxy
|
|
162
|
+
|
|
163
|
+
/// Target-action proxy that routes radio button selections to a closure handler.
|
|
164
|
+
final class RadioGroupActionProxy: NSObject {
|
|
165
|
+
|
|
166
|
+
private weak var stack: NSStackView?
|
|
167
|
+
private let handler: (Any?) -> Void
|
|
168
|
+
|
|
169
|
+
init(stack: NSStackView?, handler: @escaping (Any?) -> Void) {
|
|
170
|
+
self.stack = stack
|
|
171
|
+
self.handler = handler
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@objc func radioSelected(_ sender: NSButton) {
|
|
175
|
+
guard let stack = stack else { return }
|
|
176
|
+
|
|
177
|
+
// Deselect all other buttons (enforce mutual exclusivity)
|
|
178
|
+
for case let button as NSButton in stack.arrangedSubviews where button !== sender {
|
|
179
|
+
button.state = .off
|
|
180
|
+
}
|
|
181
|
+
sender.state = .on
|
|
182
|
+
|
|
183
|
+
let options = objc_getAssociatedObject(stack, &VRadioFactory.optionsKey) as? [[String: Any]] ?? []
|
|
184
|
+
let index = sender.tag
|
|
185
|
+
let option = options[safe: index]
|
|
186
|
+
let value = option?["value"] as? String ?? sender.title
|
|
187
|
+
|
|
188
|
+
// Update stored selected value
|
|
189
|
+
objc_setAssociatedObject(stack, &VRadioFactory.selectedValueKey, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
190
|
+
|
|
191
|
+
handler(["value": value])
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
/// Factory for VRefreshControl — stub on macOS.
|
|
4
|
+
/// Pull-to-refresh is a mobile pattern that does not exist on desktop.
|
|
5
|
+
/// This factory exists for API compatibility so that cross-platform code
|
|
6
|
+
/// using VRefreshControl does not break on macOS.
|
|
7
|
+
final class VRefreshControlFactory: NativeComponentFactory {
|
|
8
|
+
|
|
9
|
+
func createView() -> NSView {
|
|
10
|
+
let view = FlippedView()
|
|
11
|
+
view.isHidden = true
|
|
12
|
+
let node = view.ensureLayoutNode()
|
|
13
|
+
node.width = .points(0)
|
|
14
|
+
node.height = .points(0)
|
|
15
|
+
return view
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
19
|
+
// No-op — pull-to-refresh doesn't exist on macOS
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
23
|
+
// No-op
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VSafeArea — pass-through container on macOS.
|
|
5
|
+
/// macOS has no notches or system bars that require safe area insets,
|
|
6
|
+
/// so this component acts as a plain FlippedView container with gesture support.
|
|
7
|
+
final class VSafeAreaFactory: NativeComponentFactory {
|
|
8
|
+
|
|
9
|
+
func createView() -> NSView {
|
|
10
|
+
let view = FlippedView()
|
|
11
|
+
view.ensureLayoutNode()
|
|
12
|
+
return view
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
16
|
+
// Delegate all props to StyleEngine — acts as a plain container
|
|
17
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
21
|
+
switch event {
|
|
22
|
+
case "press":
|
|
23
|
+
let wrapper = ClickGestureWrapper(handler: handler)
|
|
24
|
+
let click = NSClickGestureRecognizer(
|
|
25
|
+
target: wrapper,
|
|
26
|
+
action: #selector(ClickGestureWrapper.handleGesture(_:))
|
|
27
|
+
)
|
|
28
|
+
view.addGestureRecognizer(click)
|
|
29
|
+
GestureStorage.store(wrapper, for: view, event: event)
|
|
30
|
+
|
|
31
|
+
default:
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func removeEventListener(view: NSView, event: String) {
|
|
37
|
+
GestureStorage.remove(for: view, event: event)
|
|
38
|
+
if event == "press" {
|
|
39
|
+
view.gestureRecognizers.forEach { recognizer in
|
|
40
|
+
if let click = recognizer as? NSClickGestureRecognizer, click.buttonMask == 0x1 {
|
|
41
|
+
view.removeGestureRecognizer(recognizer)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VScrollView — scrollable container component.
|
|
5
|
+
/// Maps to NSScrollView with a FlippedView document view.
|
|
6
|
+
/// NSScrollView hierarchy: NSScrollView → NSClipView → documentView (FlippedView).
|
|
7
|
+
/// Children are added to the document view, not the scroll view itself.
|
|
8
|
+
final class VScrollViewFactory: NativeComponentFactory {
|
|
9
|
+
|
|
10
|
+
// MARK: - Associated object keys
|
|
11
|
+
|
|
12
|
+
private static var scrollThrottleKey: UInt8 = 0
|
|
13
|
+
private static var scrollObserverKey: UInt8 = 0
|
|
14
|
+
private static var scrollHandlerKey: UInt8 = 0
|
|
15
|
+
|
|
16
|
+
// MARK: - NativeComponentFactory
|
|
17
|
+
|
|
18
|
+
func createView() -> NSView {
|
|
19
|
+
let scrollView = NSScrollView()
|
|
20
|
+
scrollView.wantsLayer = true
|
|
21
|
+
scrollView.hasVerticalScroller = true
|
|
22
|
+
scrollView.hasHorizontalScroller = false
|
|
23
|
+
scrollView.autohidesScrollers = true
|
|
24
|
+
scrollView.drawsBackground = false
|
|
25
|
+
|
|
26
|
+
// Create document view (children go here)
|
|
27
|
+
let documentView = FlippedView()
|
|
28
|
+
documentView.ensureLayoutNode()
|
|
29
|
+
scrollView.documentView = documentView
|
|
30
|
+
|
|
31
|
+
// Enable bounds change notifications on the clip view for scroll events
|
|
32
|
+
scrollView.contentView.postsBoundsChangedNotifications = true
|
|
33
|
+
|
|
34
|
+
scrollView.ensureLayoutNode()
|
|
35
|
+
return scrollView
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
39
|
+
guard let scrollView = view as? NSScrollView else {
|
|
40
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
switch key {
|
|
45
|
+
case "horizontal":
|
|
46
|
+
let horizontal = (value as? Bool) ?? false
|
|
47
|
+
scrollView.hasHorizontalScroller = horizontal
|
|
48
|
+
scrollView.hasVerticalScroller = !horizontal
|
|
49
|
+
|
|
50
|
+
case "showsVerticalScrollIndicator":
|
|
51
|
+
let show = (value as? Bool) ?? true
|
|
52
|
+
if show {
|
|
53
|
+
scrollView.hasVerticalScroller = true
|
|
54
|
+
} else {
|
|
55
|
+
scrollView.hasVerticalScroller = false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case "showsHorizontalScrollIndicator":
|
|
59
|
+
let show = (value as? Bool) ?? false
|
|
60
|
+
if show {
|
|
61
|
+
scrollView.hasHorizontalScroller = true
|
|
62
|
+
} else {
|
|
63
|
+
scrollView.hasHorizontalScroller = false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case "bounces":
|
|
67
|
+
let bounces = (value as? Bool) ?? true
|
|
68
|
+
scrollView.verticalScrollElasticity = bounces ? .allowed : .none
|
|
69
|
+
scrollView.horizontalScrollElasticity = bounces ? .allowed : .none
|
|
70
|
+
|
|
71
|
+
case "scrollEnabled":
|
|
72
|
+
let enabled = (value as? Bool) ?? true
|
|
73
|
+
// Disabling scroll by removing scrollers and preventing scroll
|
|
74
|
+
if enabled {
|
|
75
|
+
scrollView.hasVerticalScroller = true
|
|
76
|
+
scrollView.verticalScrollElasticity = .allowed
|
|
77
|
+
} else {
|
|
78
|
+
scrollView.hasVerticalScroller = false
|
|
79
|
+
scrollView.hasHorizontalScroller = false
|
|
80
|
+
scrollView.verticalScrollElasticity = .none
|
|
81
|
+
scrollView.horizontalScrollElasticity = .none
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
case "contentContainerStyle":
|
|
85
|
+
// Apply styles to the document view
|
|
86
|
+
if let styles = value as? [String: Any], let docView = scrollView.documentView {
|
|
87
|
+
StyleEngine.applyStyles(styles, to: docView)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case "pagingEnabled":
|
|
91
|
+
// NSScrollView doesn't have native paging. Store as internal prop.
|
|
92
|
+
StyleEngine.setInternalPropDirect("__pagingEnabled", value: value, on: view)
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
100
|
+
guard let scrollView = view as? NSScrollView else { return }
|
|
101
|
+
|
|
102
|
+
switch event {
|
|
103
|
+
case "scroll":
|
|
104
|
+
let throttle = EventThrottle(interval: 0.016) { payload in
|
|
105
|
+
handler(payload)
|
|
106
|
+
}
|
|
107
|
+
objc_setAssociatedObject(
|
|
108
|
+
view, &VScrollViewFactory.scrollThrottleKey,
|
|
109
|
+
throttle, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
let observer = NotificationCenter.default.addObserver(
|
|
113
|
+
forName: NSView.boundsDidChangeNotification,
|
|
114
|
+
object: scrollView.contentView,
|
|
115
|
+
queue: .main
|
|
116
|
+
) { [weak scrollView] _ in
|
|
117
|
+
guard let sv = scrollView else { return }
|
|
118
|
+
let clipBounds = sv.contentView.bounds
|
|
119
|
+
let docSize = sv.documentView?.frame.size ?? .zero
|
|
120
|
+
let visibleSize = sv.contentView.bounds.size
|
|
121
|
+
|
|
122
|
+
let payload: [String: Any] = [
|
|
123
|
+
"x": clipBounds.origin.x,
|
|
124
|
+
"y": clipBounds.origin.y,
|
|
125
|
+
"contentWidth": docSize.width,
|
|
126
|
+
"contentHeight": docSize.height,
|
|
127
|
+
"layoutWidth": visibleSize.width,
|
|
128
|
+
"layoutHeight": visibleSize.height,
|
|
129
|
+
]
|
|
130
|
+
throttle.fire(payload)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
objc_setAssociatedObject(
|
|
134
|
+
view, &VScrollViewFactory.scrollObserverKey,
|
|
135
|
+
observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
break
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func removeEventListener(view: NSView, event: String) {
|
|
144
|
+
switch event {
|
|
145
|
+
case "scroll":
|
|
146
|
+
if let observer = objc_getAssociatedObject(view, &VScrollViewFactory.scrollObserverKey) {
|
|
147
|
+
NotificationCenter.default.removeObserver(observer)
|
|
148
|
+
objc_setAssociatedObject(
|
|
149
|
+
view, &VScrollViewFactory.scrollObserverKey,
|
|
150
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
objc_setAssociatedObject(
|
|
154
|
+
view, &VScrollViewFactory.scrollThrottleKey,
|
|
155
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// MARK: - Child management (redirect to document view)
|
|
164
|
+
|
|
165
|
+
func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?) {
|
|
166
|
+
guard let scrollView = parent as? NSScrollView,
|
|
167
|
+
let documentView = scrollView.documentView else {
|
|
168
|
+
// Fallback: add directly
|
|
169
|
+
if let anchor = anchor, let idx = parent.subviews.firstIndex(of: anchor) {
|
|
170
|
+
parent.addSubview(child, positioned: .below, relativeTo: anchor)
|
|
171
|
+
} else {
|
|
172
|
+
parent.addSubview(child)
|
|
173
|
+
}
|
|
174
|
+
child.ensureLayoutNode()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if let anchor = anchor, let idx = documentView.subviews.firstIndex(of: anchor) {
|
|
179
|
+
documentView.addSubview(child, positioned: .below, relativeTo: anchor)
|
|
180
|
+
} else {
|
|
181
|
+
documentView.addSubview(child)
|
|
182
|
+
}
|
|
183
|
+
child.ensureLayoutNode()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func removeChild(_ child: NSView, from parent: NSView) {
|
|
187
|
+
// Child is in the document view, just remove from superview
|
|
188
|
+
child.removeFromSuperview()
|
|
189
|
+
}
|
|
190
|
+
}
|