@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,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
+ }