@thelacanians/vue-native-cli 0.4.14 → 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.
Files changed (123) hide show
  1. package/dist/cli.js +789 -200
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +156 -5
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
  6. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  7. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
  8. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  9. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  13. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  14. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  15. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  16. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  17. package/native/android/gradle.properties +1 -0
  18. package/native/android/gradlew +1 -1
  19. package/native/ios/VueNativeCore/Package.swift +1 -1
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +80 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +244 -112
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
  23. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
  24. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
  25. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  26. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  27. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  28. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  29. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  30. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  31. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  32. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  33. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  34. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  35. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  36. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  94. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  95. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  96. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  97. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  98. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  99. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  100. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  101. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  102. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  103. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  104. package/native/shared/VueNativeShared/Package.swift +14 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  115. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  116. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  117. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  118. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  119. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  120. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  121. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  122. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  123. 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
+ }
@@ -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
+ }