@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
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VSectionList — sectioned list component backed by NSTableView with group rows.
|
|
5
|
+
/// Children marked with `__sectionHeader` are treated as section headers.
|
|
6
|
+
final class VSectionListFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
// MARK: - Associated object keys
|
|
9
|
+
|
|
10
|
+
nonisolated(unsafe) private static var scrollThrottleKey: UInt8 = 0
|
|
11
|
+
nonisolated(unsafe) private static var scrollObserverKey: UInt8 = 0
|
|
12
|
+
nonisolated(unsafe) private static var endReachedHandlerKey: UInt8 = 0
|
|
13
|
+
|
|
14
|
+
// MARK: - NativeComponentFactory
|
|
15
|
+
|
|
16
|
+
func createView() -> NSView {
|
|
17
|
+
let container = VSectionListContainerView()
|
|
18
|
+
container.ensureLayoutNode()
|
|
19
|
+
return container
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
23
|
+
guard let container = view as? VSectionListContainerView else {
|
|
24
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
switch key {
|
|
29
|
+
case "estimatedItemHeight":
|
|
30
|
+
container.estimatedItemHeight = Self.cgFloat(from: value) ?? 44
|
|
31
|
+
|
|
32
|
+
case "stickySectionHeaders":
|
|
33
|
+
let sticky = (value as? Bool) ?? true
|
|
34
|
+
container.stickySectionHeaders = sticky
|
|
35
|
+
container.tableView.floatsGroupRows = sticky
|
|
36
|
+
|
|
37
|
+
case "showsScrollIndicator":
|
|
38
|
+
container.scrollView.hasVerticalScroller = (value as? Bool) ?? true
|
|
39
|
+
|
|
40
|
+
case "bounces":
|
|
41
|
+
let elasticity: NSScrollView.Elasticity = ((value as? Bool) ?? true) ? .allowed : .none
|
|
42
|
+
container.scrollView.verticalScrollElasticity = elasticity
|
|
43
|
+
container.scrollView.horizontalScrollElasticity = elasticity
|
|
44
|
+
|
|
45
|
+
default:
|
|
46
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
51
|
+
guard let container = view as? VSectionListContainerView else { return }
|
|
52
|
+
|
|
53
|
+
switch event {
|
|
54
|
+
case "scroll":
|
|
55
|
+
let throttle = EventThrottle(interval: 0.016) { payload in
|
|
56
|
+
handler(payload)
|
|
57
|
+
}
|
|
58
|
+
objc_setAssociatedObject(
|
|
59
|
+
view, &VSectionListFactory.scrollThrottleKey,
|
|
60
|
+
throttle, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
container.scrollView.contentView.postsBoundsChangedNotifications = true
|
|
64
|
+
let observer = NotificationCenter.default.addObserver(
|
|
65
|
+
forName: NSView.boundsDidChangeNotification,
|
|
66
|
+
object: container.scrollView.contentView,
|
|
67
|
+
queue: .main
|
|
68
|
+
) { [weak container] _ in
|
|
69
|
+
guard let c = container else { return }
|
|
70
|
+
let clipBounds = c.scrollView.contentView.bounds
|
|
71
|
+
let docSize = c.scrollView.documentView?.frame.size ?? .zero
|
|
72
|
+
let visibleSize = c.scrollView.contentView.bounds.size
|
|
73
|
+
|
|
74
|
+
let payload: [String: Any] = [
|
|
75
|
+
"x": clipBounds.origin.x,
|
|
76
|
+
"y": clipBounds.origin.y,
|
|
77
|
+
"contentWidth": docSize.width,
|
|
78
|
+
"contentHeight": docSize.height,
|
|
79
|
+
"layoutWidth": visibleSize.width,
|
|
80
|
+
"layoutHeight": visibleSize.height,
|
|
81
|
+
]
|
|
82
|
+
throttle.fire(payload)
|
|
83
|
+
|
|
84
|
+
let endThreshold: CGFloat = 100
|
|
85
|
+
let scrolledToBottom = clipBounds.origin.y + visibleSize.height >= docSize.height - endThreshold
|
|
86
|
+
if scrolledToBottom, let wrapper = objc_getAssociatedObject(
|
|
87
|
+
c, &VSectionListFactory.endReachedHandlerKey
|
|
88
|
+
) as? SectionHandlerWrapper {
|
|
89
|
+
wrapper.handler(nil)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
objc_setAssociatedObject(
|
|
94
|
+
view, &VSectionListFactory.scrollObserverKey,
|
|
95
|
+
observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
case "endReached":
|
|
99
|
+
let wrapper = SectionHandlerWrapper(handler: handler)
|
|
100
|
+
objc_setAssociatedObject(
|
|
101
|
+
view, &VSectionListFactory.endReachedHandlerKey,
|
|
102
|
+
wrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func removeEventListener(view: NSView, event: String) {
|
|
111
|
+
switch event {
|
|
112
|
+
case "scroll":
|
|
113
|
+
if let observer = objc_getAssociatedObject(view, &VSectionListFactory.scrollObserverKey) {
|
|
114
|
+
NotificationCenter.default.removeObserver(observer)
|
|
115
|
+
objc_setAssociatedObject(
|
|
116
|
+
view, &VSectionListFactory.scrollObserverKey,
|
|
117
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
objc_setAssociatedObject(
|
|
121
|
+
view, &VSectionListFactory.scrollThrottleKey,
|
|
122
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
case "endReached":
|
|
126
|
+
objc_setAssociatedObject(
|
|
127
|
+
view, &VSectionListFactory.endReachedHandlerKey,
|
|
128
|
+
nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
default:
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// MARK: - Child management
|
|
137
|
+
|
|
138
|
+
func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?) {
|
|
139
|
+
guard let container = parent as? VSectionListContainerView else { return }
|
|
140
|
+
child.ensureLayoutNode()
|
|
141
|
+
|
|
142
|
+
if let anchor = anchor, let idx = container.allChildren.firstIndex(where: { $0 === anchor }) {
|
|
143
|
+
container.allChildren.insert(child, at: idx)
|
|
144
|
+
} else {
|
|
145
|
+
container.allChildren.append(child)
|
|
146
|
+
}
|
|
147
|
+
container.rebuildSections()
|
|
148
|
+
container.tableView.reloadData()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func removeChild(_ child: NSView, from parent: NSView) {
|
|
152
|
+
guard let container = parent as? VSectionListContainerView else {
|
|
153
|
+
child.removeFromSuperview()
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
container.allChildren.removeAll { $0 === child }
|
|
158
|
+
child.removeFromSuperview()
|
|
159
|
+
container.rebuildSections()
|
|
160
|
+
container.tableView.reloadData()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MARK: - SectionHandlerWrapper
|
|
165
|
+
|
|
166
|
+
private final class SectionHandlerWrapper: NSObject {
|
|
167
|
+
let handler: (Any?) -> Void
|
|
168
|
+
|
|
169
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
170
|
+
self.handler = handler
|
|
171
|
+
super.init()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// MARK: - SectionData
|
|
176
|
+
|
|
177
|
+
private struct SectionData {
|
|
178
|
+
var headerView: NSView?
|
|
179
|
+
var itemViews: [NSView]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// MARK: - FlatRow
|
|
183
|
+
|
|
184
|
+
private enum FlatRow {
|
|
185
|
+
case sectionHeader(sectionIndex: Int)
|
|
186
|
+
case item(sectionIndex: Int, itemIndex: Int)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// MARK: - VSectionListContainerView
|
|
190
|
+
|
|
191
|
+
/// Container view hosting NSScrollView + NSTableView with section support.
|
|
192
|
+
final class VSectionListContainerView: FlippedView, NSTableViewDataSource, NSTableViewDelegate {
|
|
193
|
+
|
|
194
|
+
// MARK: - Subviews
|
|
195
|
+
|
|
196
|
+
let scrollView: NSScrollView
|
|
197
|
+
let tableView: NSTableView
|
|
198
|
+
|
|
199
|
+
// MARK: - State
|
|
200
|
+
|
|
201
|
+
var allChildren: [NSView] = []
|
|
202
|
+
var estimatedItemHeight: CGFloat = 44
|
|
203
|
+
var stickySectionHeaders = true
|
|
204
|
+
fileprivate var sections: [SectionData] = []
|
|
205
|
+
fileprivate var flatRows: [FlatRow] = []
|
|
206
|
+
|
|
207
|
+
private static let cellIdentifier = NSUserInterfaceItemIdentifier("VSectionListCell")
|
|
208
|
+
|
|
209
|
+
// MARK: - Init
|
|
210
|
+
|
|
211
|
+
override init(frame: NSRect) {
|
|
212
|
+
scrollView = NSScrollView()
|
|
213
|
+
tableView = NSTableView()
|
|
214
|
+
|
|
215
|
+
super.init(frame: frame)
|
|
216
|
+
|
|
217
|
+
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("main"))
|
|
218
|
+
column.isEditable = false
|
|
219
|
+
tableView.addTableColumn(column)
|
|
220
|
+
tableView.headerView = nil
|
|
221
|
+
tableView.style = .plain
|
|
222
|
+
tableView.selectionHighlightStyle = .none
|
|
223
|
+
tableView.intercellSpacing = NSSize(width: 0, height: 0)
|
|
224
|
+
tableView.dataSource = self
|
|
225
|
+
tableView.delegate = self
|
|
226
|
+
tableView.floatsGroupRows = true
|
|
227
|
+
|
|
228
|
+
scrollView.documentView = tableView
|
|
229
|
+
scrollView.hasVerticalScroller = true
|
|
230
|
+
scrollView.autohidesScrollers = true
|
|
231
|
+
scrollView.drawsBackground = false
|
|
232
|
+
|
|
233
|
+
addSubview(scrollView)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
required init?(coder: NSCoder) {
|
|
237
|
+
fatalError("init(coder:) has not been implemented")
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
override func layout() {
|
|
241
|
+
super.layout()
|
|
242
|
+
scrollView.frame = bounds
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
func rebuildSections() {
|
|
246
|
+
sections.removeAll()
|
|
247
|
+
var currentSection = SectionData(headerView: nil, itemViews: [])
|
|
248
|
+
|
|
249
|
+
for child in allChildren {
|
|
250
|
+
let isSectionHeader = StyleEngine.getInternalProp("__sectionHeader", from: child) as? Bool ?? false
|
|
251
|
+
if isSectionHeader {
|
|
252
|
+
if currentSection.headerView != nil || !currentSection.itemViews.isEmpty {
|
|
253
|
+
sections.append(currentSection)
|
|
254
|
+
}
|
|
255
|
+
currentSection = SectionData(headerView: child, itemViews: [])
|
|
256
|
+
} else {
|
|
257
|
+
currentSection.itemViews.append(child)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if currentSection.headerView != nil || !currentSection.itemViews.isEmpty {
|
|
262
|
+
sections.append(currentSection)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
rebuildFlatRows()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private func rebuildFlatRows() {
|
|
269
|
+
flatRows.removeAll()
|
|
270
|
+
|
|
271
|
+
for (sectionIndex, section) in sections.enumerated() {
|
|
272
|
+
if section.headerView != nil {
|
|
273
|
+
flatRows.append(.sectionHeader(sectionIndex: sectionIndex))
|
|
274
|
+
}
|
|
275
|
+
for itemIndex in section.itemViews.indices {
|
|
276
|
+
flatRows.append(.item(sectionIndex: sectionIndex, itemIndex: itemIndex))
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// MARK: - NSTableViewDataSource
|
|
282
|
+
|
|
283
|
+
func numberOfRows(in tableView: NSTableView) -> Int {
|
|
284
|
+
flatRows.count
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// MARK: - NSTableViewDelegate
|
|
288
|
+
|
|
289
|
+
func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool {
|
|
290
|
+
guard row < flatRows.count else { return false }
|
|
291
|
+
if case .sectionHeader = flatRows[row] {
|
|
292
|
+
return true
|
|
293
|
+
}
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
|
298
|
+
guard row < flatRows.count else { return nil }
|
|
299
|
+
|
|
300
|
+
switch flatRows[row] {
|
|
301
|
+
case .sectionHeader(let sectionIndex):
|
|
302
|
+
guard sectionIndex < sections.count,
|
|
303
|
+
let headerView = sections[sectionIndex].headerView else { return nil }
|
|
304
|
+
headerView.removeFromSuperview()
|
|
305
|
+
return headerView
|
|
306
|
+
|
|
307
|
+
case .item(let sectionIndex, let itemIndex):
|
|
308
|
+
guard sectionIndex < sections.count,
|
|
309
|
+
itemIndex < sections[sectionIndex].itemViews.count else { return nil }
|
|
310
|
+
|
|
311
|
+
let cellView: NSTableCellView
|
|
312
|
+
if let reused = tableView.makeView(withIdentifier: VSectionListContainerView.cellIdentifier, owner: nil) as? NSTableCellView {
|
|
313
|
+
reused.subviews.forEach { $0.removeFromSuperview() }
|
|
314
|
+
cellView = reused
|
|
315
|
+
} else {
|
|
316
|
+
cellView = NSTableCellView()
|
|
317
|
+
cellView.identifier = VSectionListContainerView.cellIdentifier
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let child = sections[sectionIndex].itemViews[itemIndex]
|
|
321
|
+
child.removeFromSuperview()
|
|
322
|
+
cellView.addSubview(child)
|
|
323
|
+
child.frame = cellView.bounds
|
|
324
|
+
child.autoresizingMask = [.width, .height]
|
|
325
|
+
return cellView
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
|
|
330
|
+
guard row < flatRows.count else { return estimatedItemHeight }
|
|
331
|
+
|
|
332
|
+
switch flatRows[row] {
|
|
333
|
+
case .sectionHeader(let sectionIndex):
|
|
334
|
+
guard sectionIndex < sections.count,
|
|
335
|
+
let headerView = sections[sectionIndex].headerView else { return estimatedItemHeight }
|
|
336
|
+
return resolvedHeight(for: headerView)
|
|
337
|
+
|
|
338
|
+
case .item(let sectionIndex, let itemIndex):
|
|
339
|
+
guard sectionIndex < sections.count,
|
|
340
|
+
itemIndex < sections[sectionIndex].itemViews.count else { return estimatedItemHeight }
|
|
341
|
+
return resolvedHeight(for: sections[sectionIndex].itemViews[itemIndex])
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private func resolvedHeight(for view: NSView) -> CGFloat {
|
|
346
|
+
let explicitHeight = view.frame.size.height
|
|
347
|
+
if explicitHeight > 1 {
|
|
348
|
+
return explicitHeight
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if let node = view.layoutNode {
|
|
352
|
+
if node.computedFrame.height > 0 {
|
|
353
|
+
return node.computedFrame.height
|
|
354
|
+
}
|
|
355
|
+
if let resolved = node.height.resolve(relativeTo: bounds.height), resolved > 0 {
|
|
356
|
+
return resolved
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return estimatedItemHeight
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private extension VSectionListFactory {
|
|
365
|
+
static func cgFloat(from value: Any?) -> CGFloat? {
|
|
366
|
+
if let value = value as? CGFloat { return value }
|
|
367
|
+
if let value = value as? Double { return CGFloat(value) }
|
|
368
|
+
if let value = value as? Int { return CGFloat(value) }
|
|
369
|
+
if let value = value as? String, let parsed = Double(value) {
|
|
370
|
+
return CGFloat(parsed)
|
|
371
|
+
}
|
|
372
|
+
return nil
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VSegmentedControl — a segmented control for mutually exclusive choices.
|
|
5
|
+
/// Maps to NSSegmentedControl.
|
|
6
|
+
final class VSegmentedControlFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
private static var changeHandlerKey: UInt8 = 0
|
|
9
|
+
nonisolated(unsafe) static var valuesKey: UInt8 = 0
|
|
10
|
+
|
|
11
|
+
// MARK: - NativeComponentFactory
|
|
12
|
+
|
|
13
|
+
func createView() -> NSView {
|
|
14
|
+
let control = NSSegmentedControl()
|
|
15
|
+
control.segmentStyle = .rounded
|
|
16
|
+
control.trackingMode = .selectOne
|
|
17
|
+
control.wantsLayer = true
|
|
18
|
+
control.ensureLayoutNode()
|
|
19
|
+
return control
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
23
|
+
guard let control = view as? NSSegmentedControl else {
|
|
24
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
switch key {
|
|
29
|
+
case "values":
|
|
30
|
+
if let labels = value as? [String] {
|
|
31
|
+
control.segmentCount = labels.count
|
|
32
|
+
for (i, label) in labels.enumerated() {
|
|
33
|
+
control.setLabel(label, forSegment: i)
|
|
34
|
+
control.setWidth(0, forSegment: i) // Auto-size
|
|
35
|
+
}
|
|
36
|
+
objc_setAssociatedObject(control, &VSegmentedControlFactory.valuesKey, labels, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "selectedIndex":
|
|
40
|
+
if let idx = value as? Int {
|
|
41
|
+
if idx >= 0 && idx < control.segmentCount {
|
|
42
|
+
control.selectedSegment = idx
|
|
43
|
+
}
|
|
44
|
+
} else if let idx = value as? Double {
|
|
45
|
+
let i = Int(idx)
|
|
46
|
+
if i >= 0 && i < control.segmentCount {
|
|
47
|
+
control.selectedSegment = i
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "tintColor":
|
|
52
|
+
if let colorStr = value as? String {
|
|
53
|
+
if #available(macOS 10.14, *) {
|
|
54
|
+
control.selectedSegmentBezelColor = NSColor.fromHex(colorStr)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case "enabled":
|
|
59
|
+
if let enabled = value as? Bool {
|
|
60
|
+
control.isEnabled = enabled
|
|
61
|
+
} else {
|
|
62
|
+
control.isEnabled = true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case "disabled":
|
|
66
|
+
if let disabled = value as? Bool {
|
|
67
|
+
control.isEnabled = !disabled
|
|
68
|
+
} else if let disabled = value as? Int {
|
|
69
|
+
control.isEnabled = disabled == 0
|
|
70
|
+
} else {
|
|
71
|
+
control.isEnabled = true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
80
|
+
guard let control = view as? NSSegmentedControl, event == "change" else { return }
|
|
81
|
+
|
|
82
|
+
let proxy = SegmentedActionProxy(control: control, handler: handler)
|
|
83
|
+
control.target = proxy
|
|
84
|
+
control.action = #selector(SegmentedActionProxy.selectionChanged(_:))
|
|
85
|
+
objc_setAssociatedObject(
|
|
86
|
+
control,
|
|
87
|
+
&VSegmentedControlFactory.changeHandlerKey,
|
|
88
|
+
proxy,
|
|
89
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func removeEventListener(view: NSView, event: String) {
|
|
94
|
+
guard let control = view as? NSSegmentedControl, event == "change" else { return }
|
|
95
|
+
|
|
96
|
+
control.target = nil
|
|
97
|
+
control.action = nil
|
|
98
|
+
objc_setAssociatedObject(control, &VSegmentedControlFactory.changeHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// MARK: - SegmentedActionProxy
|
|
103
|
+
|
|
104
|
+
/// Target-action proxy that routes NSSegmentedControl selection changes to a closure handler.
|
|
105
|
+
final class SegmentedActionProxy: NSObject {
|
|
106
|
+
|
|
107
|
+
private weak var control: NSSegmentedControl?
|
|
108
|
+
private let handler: (Any?) -> Void
|
|
109
|
+
|
|
110
|
+
init(control: NSSegmentedControl, handler: @escaping (Any?) -> Void) {
|
|
111
|
+
self.control = control
|
|
112
|
+
self.handler = handler
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@objc func selectionChanged(_ sender: NSSegmentedControl) {
|
|
116
|
+
let index = sender.selectedSegment
|
|
117
|
+
let labels = objc_getAssociatedObject(sender, &VSegmentedControlFactory.valuesKey) as? [String]
|
|
118
|
+
let label = labels?[safe: index] ?? ""
|
|
119
|
+
|
|
120
|
+
handler([
|
|
121
|
+
"selectedIndex": index,
|
|
122
|
+
"value": label
|
|
123
|
+
])
|
|
124
|
+
}
|
|
125
|
+
}
|
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
|
|
4
|
+
/// Factory for VSlider — a continuous slider control.
|
|
5
|
+
/// Maps to NSSlider with throttled change events (~60fps).
|
|
6
|
+
final class VSliderFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
private static var changeHandlerKey: UInt8 = 0
|
|
9
|
+
|
|
10
|
+
// MARK: - NativeComponentFactory
|
|
11
|
+
|
|
12
|
+
func createView() -> NSView {
|
|
13
|
+
let slider = NSSlider()
|
|
14
|
+
slider.isContinuous = true
|
|
15
|
+
slider.minValue = 0
|
|
16
|
+
slider.maxValue = 1
|
|
17
|
+
slider.doubleValue = 0
|
|
18
|
+
slider.wantsLayer = true
|
|
19
|
+
slider.ensureLayoutNode()
|
|
20
|
+
return slider
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func updateProp(view: NSView, key: String, value: Any?) {
|
|
24
|
+
guard let slider = view as? NSSlider else {
|
|
25
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch key {
|
|
30
|
+
case "value":
|
|
31
|
+
if let val = value as? Double {
|
|
32
|
+
if slider.doubleValue != val {
|
|
33
|
+
slider.doubleValue = val
|
|
34
|
+
}
|
|
35
|
+
} else if let val = value as? Int {
|
|
36
|
+
slider.doubleValue = Double(val)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "minimumValue", "min":
|
|
40
|
+
if let val = value as? Double {
|
|
41
|
+
slider.minValue = val
|
|
42
|
+
} else if let val = value as? Int {
|
|
43
|
+
slider.minValue = Double(val)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case "maximumValue", "max":
|
|
47
|
+
if let val = value as? Double {
|
|
48
|
+
slider.maxValue = val
|
|
49
|
+
} else if let val = value as? Int {
|
|
50
|
+
slider.maxValue = Double(val)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case "minimumTrackTintColor":
|
|
54
|
+
if #available(macOS 14.0, *) {
|
|
55
|
+
if let colorStr = value as? String {
|
|
56
|
+
slider.trackFillColor = NSColor.fromHex(colorStr)
|
|
57
|
+
} else {
|
|
58
|
+
slider.trackFillColor = nil
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case "maximumTrackTintColor":
|
|
63
|
+
// No direct NSSlider API for max track color
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
case "disabled":
|
|
67
|
+
if let disabled = value as? Bool {
|
|
68
|
+
slider.isEnabled = !disabled
|
|
69
|
+
} else if let disabled = value as? Int {
|
|
70
|
+
slider.isEnabled = disabled == 0
|
|
71
|
+
} else {
|
|
72
|
+
slider.isEnabled = true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case "enabled":
|
|
76
|
+
if let enabled = value as? Bool {
|
|
77
|
+
slider.isEnabled = enabled
|
|
78
|
+
} else if let enabled = value as? Int {
|
|
79
|
+
slider.isEnabled = enabled != 0
|
|
80
|
+
} else {
|
|
81
|
+
slider.isEnabled = true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
|
|
90
|
+
guard let slider = view as? NSSlider, event == "change" else { return }
|
|
91
|
+
|
|
92
|
+
let throttle = EventThrottle { payload in
|
|
93
|
+
handler(payload)
|
|
94
|
+
}
|
|
95
|
+
let proxy = SliderActionProxy(slider: slider, throttle: throttle)
|
|
96
|
+
slider.target = proxy
|
|
97
|
+
slider.action = #selector(SliderActionProxy.valueChanged(_:))
|
|
98
|
+
objc_setAssociatedObject(
|
|
99
|
+
slider,
|
|
100
|
+
&VSliderFactory.changeHandlerKey,
|
|
101
|
+
proxy,
|
|
102
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func removeEventListener(view: NSView, event: String) {
|
|
107
|
+
guard let slider = view as? NSSlider, event == "change" else { return }
|
|
108
|
+
|
|
109
|
+
slider.target = nil
|
|
110
|
+
slider.action = nil
|
|
111
|
+
objc_setAssociatedObject(slider, &VSliderFactory.changeHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// MARK: - SliderActionProxy
|
|
116
|
+
|
|
117
|
+
/// Target-action proxy that routes NSSlider value changes through an EventThrottle.
|
|
118
|
+
final class SliderActionProxy: NSObject {
|
|
119
|
+
|
|
120
|
+
private weak var slider: NSSlider?
|
|
121
|
+
private let throttle: EventThrottle
|
|
122
|
+
|
|
123
|
+
init(slider: NSSlider, throttle: EventThrottle) {
|
|
124
|
+
self.slider = slider
|
|
125
|
+
self.throttle = throttle
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc func valueChanged(_ sender: NSSlider) {
|
|
129
|
+
throttle.fire(["value": sender.doubleValue])
|
|
130
|
+
}
|
|
131
|
+
}
|