@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.
Files changed (116) hide show
  1. package/dist/cli.js +329 -15
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  6. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  7. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  8. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  9. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  13. package/native/android/gradle.properties +1 -0
  14. package/native/android/gradlew +1 -1
  15. package/native/ios/VueNativeCore/Package.swift +1 -1
  16. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
  17. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
  18. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  19. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  23. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  24. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  25. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  26. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  27. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  28. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  29. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  30. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  31. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  32. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  33. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  34. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  35. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  36. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  94. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  95. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  96. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  97. package/native/shared/VueNativeShared/Package.swift +14 -0
  98. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  99. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  100. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  101. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  102. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  103. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  104. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  115. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  116. package/package.json +8 -2
@@ -0,0 +1,324 @@
1
+ import AppKit
2
+ import ObjectiveC
3
+
4
+ /// Factory for VList — virtualized list component backed by NSTableView.
5
+ /// Uses a single-column, view-based NSTableView inside a VListContainerView.
6
+ /// JS sends child views that are placed into table view cells.
7
+ final class VListFactory: NativeComponentFactory {
8
+
9
+ // MARK: - Associated object keys
10
+
11
+ nonisolated(unsafe) private static var scrollThrottleKey: UInt8 = 0
12
+ nonisolated(unsafe) private static var scrollObserverKey: UInt8 = 0
13
+ nonisolated(unsafe) private static var endReachedHandlerKey: UInt8 = 0
14
+ nonisolated(unsafe) private static var scrollHandlerKey: UInt8 = 0
15
+
16
+ // MARK: - NativeComponentFactory
17
+
18
+ func createView() -> NSView {
19
+ let container = VListContainerView()
20
+ container.ensureLayoutNode()
21
+ return container
22
+ }
23
+
24
+ func updateProp(view: NSView, key: String, value: Any?) {
25
+ guard let container = view as? VListContainerView else {
26
+ StyleEngine.apply(key: key, value: value, to: view)
27
+ return
28
+ }
29
+
30
+ switch key {
31
+ case "data":
32
+ if let items = value as? [Any] {
33
+ container.itemCount = items.count
34
+ } else if let count = value as? Int {
35
+ container.itemCount = count
36
+ }
37
+ container.tableView.reloadData()
38
+
39
+ case "estimatedItemHeight":
40
+ container.estimatedItemHeight = Self.cgFloat(from: value) ?? 44
41
+
42
+ case "scrollEnabled":
43
+ let enabled = (value as? Bool) ?? true
44
+ container.scrollView.verticalScrollElasticity = enabled ? .allowed : .none
45
+ container.scrollView.horizontalScrollElasticity = enabled ? .allowed : .none
46
+ if container.isHorizontal {
47
+ container.scrollView.hasHorizontalScroller = enabled && container.showsScrollIndicator
48
+ } else {
49
+ container.scrollView.hasVerticalScroller = enabled && container.showsScrollIndicator
50
+ }
51
+
52
+ case "showsScrollIndicator":
53
+ let show = (value as? Bool) ?? true
54
+ container.showsScrollIndicator = show
55
+ if container.isHorizontal {
56
+ container.scrollView.hasHorizontalScroller = show
57
+ container.scrollView.hasVerticalScroller = false
58
+ } else {
59
+ container.scrollView.hasVerticalScroller = show
60
+ container.scrollView.hasHorizontalScroller = false
61
+ }
62
+
63
+ case "bounces":
64
+ let bounces = (value as? Bool) ?? true
65
+ let elasticity: NSScrollView.Elasticity = bounces ? .allowed : .none
66
+ container.scrollView.verticalScrollElasticity = elasticity
67
+ container.scrollView.horizontalScrollElasticity = elasticity
68
+
69
+ case "horizontal":
70
+ let horizontal = (value as? Bool) ?? false
71
+ container.isHorizontal = horizontal
72
+ container.scrollView.hasHorizontalScroller = horizontal && container.showsScrollIndicator
73
+ container.scrollView.hasVerticalScroller = !horizontal && container.showsScrollIndicator
74
+
75
+ default:
76
+ StyleEngine.apply(key: key, value: value, to: view)
77
+ }
78
+ }
79
+
80
+ func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
81
+ guard let container = view as? VListContainerView else { return }
82
+
83
+ switch event {
84
+ case "scroll":
85
+ let throttle = EventThrottle(interval: 0.016) { payload in
86
+ handler(payload)
87
+ }
88
+ objc_setAssociatedObject(
89
+ view, &VListFactory.scrollThrottleKey,
90
+ throttle, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
91
+ )
92
+
93
+ container.scrollView.contentView.postsBoundsChangedNotifications = true
94
+ let observer = NotificationCenter.default.addObserver(
95
+ forName: NSView.boundsDidChangeNotification,
96
+ object: container.scrollView.contentView,
97
+ queue: .main
98
+ ) { [weak container] _ in
99
+ guard let c = container else { return }
100
+ let clipBounds = c.scrollView.contentView.bounds
101
+ let docSize = c.scrollView.documentView?.frame.size ?? .zero
102
+ let visibleSize = c.scrollView.contentView.bounds.size
103
+
104
+ let payload: [String: Any] = [
105
+ "x": clipBounds.origin.x,
106
+ "y": clipBounds.origin.y,
107
+ "contentWidth": docSize.width,
108
+ "contentHeight": docSize.height,
109
+ "layoutWidth": visibleSize.width,
110
+ "layoutHeight": visibleSize.height,
111
+ ]
112
+ throttle.fire(payload)
113
+
114
+ // Check for endReached
115
+ let endThreshold: CGFloat = 100
116
+ let scrolledToBottom = clipBounds.origin.y + visibleSize.height >= docSize.height - endThreshold
117
+ if scrolledToBottom, let endHandler = objc_getAssociatedObject(
118
+ c, &VListFactory.endReachedHandlerKey
119
+ ) as? HandlerWrapper {
120
+ endHandler.handler(nil)
121
+ }
122
+ }
123
+
124
+ objc_setAssociatedObject(
125
+ view, &VListFactory.scrollObserverKey,
126
+ observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
127
+ )
128
+
129
+ case "endReached":
130
+ let wrapper = HandlerWrapper(handler: handler)
131
+ objc_setAssociatedObject(
132
+ view, &VListFactory.endReachedHandlerKey,
133
+ wrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
134
+ )
135
+
136
+ default:
137
+ break
138
+ }
139
+ }
140
+
141
+ func removeEventListener(view: NSView, event: String) {
142
+ switch event {
143
+ case "scroll":
144
+ if let observer = objc_getAssociatedObject(view, &VListFactory.scrollObserverKey) {
145
+ NotificationCenter.default.removeObserver(observer)
146
+ objc_setAssociatedObject(
147
+ view, &VListFactory.scrollObserverKey,
148
+ nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
149
+ )
150
+ }
151
+ objc_setAssociatedObject(
152
+ view, &VListFactory.scrollThrottleKey,
153
+ nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
154
+ )
155
+
156
+ case "endReached":
157
+ objc_setAssociatedObject(
158
+ view, &VListFactory.endReachedHandlerKey,
159
+ nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
160
+ )
161
+
162
+ default:
163
+ break
164
+ }
165
+ }
166
+
167
+ // MARK: - Child management
168
+
169
+ func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?) {
170
+ guard let container = parent as? VListContainerView else { return }
171
+ child.ensureLayoutNode()
172
+
173
+ if let anchor = anchor, let idx = container.childViews.firstIndex(where: { $0 === anchor }) {
174
+ container.childViews.insert(child, at: idx)
175
+ } else {
176
+ container.childViews.append(child)
177
+ }
178
+ container.itemCount = container.childViews.count
179
+ container.tableView.reloadData()
180
+ }
181
+
182
+ func removeChild(_ child: NSView, from parent: NSView) {
183
+ guard let container = parent as? VListContainerView else {
184
+ child.removeFromSuperview()
185
+ return
186
+ }
187
+ container.childViews.removeAll { $0 === child }
188
+ child.removeFromSuperview()
189
+ container.itemCount = container.childViews.count
190
+ container.tableView.reloadData()
191
+ }
192
+ }
193
+
194
+ // MARK: - HandlerWrapper
195
+
196
+ /// NSObject wrapper for storing closure-based handlers as associated objects.
197
+ private final class HandlerWrapper: NSObject {
198
+ let handler: (Any?) -> Void
199
+
200
+ init(handler: @escaping (Any?) -> Void) {
201
+ self.handler = handler
202
+ super.init()
203
+ }
204
+ }
205
+
206
+ // MARK: - VListContainerView
207
+
208
+ /// Container view that hosts an NSScrollView + NSTableView for virtualized list rendering.
209
+ /// JS-rendered child views are stored and served as cell contents via the data source.
210
+ final class VListContainerView: FlippedView, NSTableViewDataSource, NSTableViewDelegate {
211
+
212
+ // MARK: - Subviews
213
+
214
+ let scrollView: NSScrollView
215
+ let tableView: NSTableView
216
+
217
+ // MARK: - State
218
+
219
+ var childViews: [NSView] = []
220
+ var itemCount: Int = 0
221
+ var estimatedItemHeight: CGFloat = 44
222
+ var showsScrollIndicator = true
223
+ var isHorizontal = false
224
+
225
+ private static let cellIdentifier = NSUserInterfaceItemIdentifier("VListCell")
226
+
227
+ // MARK: - Init
228
+
229
+ override init(frame: NSRect) {
230
+ scrollView = NSScrollView()
231
+ tableView = NSTableView()
232
+
233
+ super.init(frame: frame)
234
+
235
+ // Configure table view
236
+ let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("main"))
237
+ column.isEditable = false
238
+ tableView.addTableColumn(column)
239
+ tableView.headerView = nil
240
+ tableView.style = .plain
241
+ tableView.selectionHighlightStyle = .none
242
+ tableView.intercellSpacing = NSSize(width: 0, height: 0)
243
+ tableView.dataSource = self
244
+ tableView.delegate = self
245
+
246
+ // Configure scroll view
247
+ scrollView.documentView = tableView
248
+ scrollView.hasVerticalScroller = true
249
+ scrollView.autohidesScrollers = true
250
+ scrollView.drawsBackground = false
251
+
252
+ addSubview(scrollView)
253
+ }
254
+
255
+ required init?(coder: NSCoder) {
256
+ fatalError("init(coder:) has not been implemented")
257
+ }
258
+
259
+ override func layout() {
260
+ super.layout()
261
+ scrollView.frame = bounds
262
+ }
263
+
264
+ // MARK: - NSTableViewDataSource
265
+
266
+ func numberOfRows(in tableView: NSTableView) -> Int {
267
+ return itemCount
268
+ }
269
+
270
+ // MARK: - NSTableViewDelegate
271
+
272
+ func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
273
+ // Wrap the child view in a container cell view
274
+ guard row < childViews.count else { return nil }
275
+
276
+ let cellView: NSTableCellView
277
+ if let reused = tableView.makeView(withIdentifier: VListContainerView.cellIdentifier, owner: nil) as? NSTableCellView {
278
+ // Remove old content
279
+ reused.subviews.forEach { $0.removeFromSuperview() }
280
+ cellView = reused
281
+ } else {
282
+ cellView = NSTableCellView()
283
+ cellView.identifier = VListContainerView.cellIdentifier
284
+ }
285
+
286
+ let child = childViews[row]
287
+ child.removeFromSuperview()
288
+ cellView.addSubview(child)
289
+ child.frame = cellView.bounds
290
+ child.autoresizingMask = [.width, .height]
291
+
292
+ return cellView
293
+ }
294
+
295
+ func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
296
+ guard row < childViews.count else { return estimatedItemHeight }
297
+
298
+ let child = childViews[row]
299
+ if let node = child.layoutNode {
300
+ // Use layout node computed height if available
301
+ if node.computedFrame.height > 0 {
302
+ return node.computedFrame.height
303
+ }
304
+ // Try resolving the height
305
+ if let h = node.height.resolve(relativeTo: bounds.height), h > 0 {
306
+ return h
307
+ }
308
+ }
309
+ // Default row height
310
+ return estimatedItemHeight
311
+ }
312
+ }
313
+
314
+ private extension VListFactory {
315
+ static func cgFloat(from value: Any?) -> CGFloat? {
316
+ if let value = value as? CGFloat { return value }
317
+ if let value = value as? Double { return CGFloat(value) }
318
+ if let value = value as? Int { return CGFloat(value) }
319
+ if let value = value as? String, let parsed = Double(value) {
320
+ return CGFloat(parsed)
321
+ }
322
+ return nil
323
+ }
324
+ }
@@ -0,0 +1,231 @@
1
+ import AppKit
2
+ import ObjectiveC
3
+
4
+ /// Factory for VModal — presents content in a modal panel or sheet.
5
+ /// The view itself is a zero-size hidden placeholder in the native tree.
6
+ /// Its children are moved into an NSPanel when visible=true.
7
+ final class VModalFactory: NativeComponentFactory {
8
+
9
+ // MARK: - Associated object keys
10
+
11
+ private static var panelKey: UInt8 = 0
12
+ private static var visibleKey: UInt8 = 1
13
+ private static var animationTypeKey: UInt8 = 2
14
+ private static var transparentKey: UInt8 = 3
15
+ private static var presentationStyleKey: UInt8 = 4
16
+ private static var onDismissKey: UInt8 = 5
17
+ private static var onShowKey: UInt8 = 6
18
+ private static var overlayKey: UInt8 = 7
19
+
20
+ // MARK: - NativeComponentFactory
21
+
22
+ func createView() -> NSView {
23
+ let placeholder = FlippedView()
24
+ placeholder.isHidden = true
25
+ let node = placeholder.ensureLayoutNode()
26
+ node.width = .points(0)
27
+ node.height = .points(0)
28
+ return placeholder
29
+ }
30
+
31
+ func updateProp(view: NSView, key: String, value: Any?) {
32
+ switch key {
33
+ case "visible":
34
+ let visible = (value as? Bool) ?? ((value as? Int) != nil && (value as! Int) != 0)
35
+ let wasVisible = objc_getAssociatedObject(view, &VModalFactory.visibleKey) as? Bool ?? false
36
+ objc_setAssociatedObject(view, &VModalFactory.visibleKey, visible, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
37
+ if visible && !wasVisible {
38
+ showModal(for: view)
39
+ } else if !visible && wasVisible {
40
+ hideModal(for: view)
41
+ }
42
+
43
+ case "animationType":
44
+ objc_setAssociatedObject(
45
+ view, &VModalFactory.animationTypeKey,
46
+ value as? String ?? "none",
47
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
48
+ )
49
+
50
+ case "transparent":
51
+ let transparent = (value as? Bool) ?? false
52
+ objc_setAssociatedObject(
53
+ view, &VModalFactory.transparentKey,
54
+ transparent,
55
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
56
+ )
57
+
58
+ case "presentationStyle":
59
+ objc_setAssociatedObject(
60
+ view, &VModalFactory.presentationStyleKey,
61
+ value as? String ?? "modal",
62
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
63
+ )
64
+
65
+ default:
66
+ break
67
+ }
68
+ }
69
+
70
+ func addEventListener(view: NSView, event: String, handler: @escaping (Any?) -> Void) {
71
+ switch event {
72
+ case "dismiss":
73
+ objc_setAssociatedObject(
74
+ view, &VModalFactory.onDismissKey,
75
+ handler as AnyObject,
76
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
77
+ )
78
+ case "show":
79
+ objc_setAssociatedObject(
80
+ view, &VModalFactory.onShowKey,
81
+ handler as AnyObject,
82
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC
83
+ )
84
+ default:
85
+ break
86
+ }
87
+ }
88
+
89
+ func removeEventListener(view: NSView, event: String) {
90
+ switch event {
91
+ case "dismiss":
92
+ objc_setAssociatedObject(view, &VModalFactory.onDismissKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
93
+ case "show":
94
+ objc_setAssociatedObject(view, &VModalFactory.onShowKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
95
+ default:
96
+ break
97
+ }
98
+ }
99
+
100
+ // Custom child management: route children to the overlay container
101
+ func insertChild(_ child: NSView, into parent: NSView, before anchor: NSView?) {
102
+ let overlay = getOrCreateOverlay(for: parent)
103
+ if let anchor = anchor, let idx = overlay.subviews.firstIndex(of: anchor) {
104
+ overlay.addSubview(child, positioned: .below, relativeTo: anchor)
105
+ } else {
106
+ overlay.addSubview(child)
107
+ }
108
+ child.ensureLayoutNode()
109
+ }
110
+
111
+ func removeChild(_ child: NSView, from parent: NSView) {
112
+ child.removeFromSuperview()
113
+ }
114
+
115
+ // MARK: - Private helpers
116
+
117
+ private func getOrCreateOverlay(for placeholder: NSView) -> FlippedView {
118
+ if let existing = objc_getAssociatedObject(placeholder, &VModalFactory.overlayKey) as? FlippedView {
119
+ return existing
120
+ }
121
+ let overlay = FlippedView()
122
+ overlay.wantsLayer = true
123
+ let node = overlay.ensureLayoutNode()
124
+ node.flexGrow = 1
125
+ objc_setAssociatedObject(
126
+ placeholder, &VModalFactory.overlayKey,
127
+ overlay, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
128
+ )
129
+ return overlay
130
+ }
131
+
132
+ private func showModal(for placeholder: NSView) {
133
+ let style = objc_getAssociatedObject(placeholder, &VModalFactory.presentationStyleKey) as? String ?? "modal"
134
+ let transparent = objc_getAssociatedObject(placeholder, &VModalFactory.transparentKey) as? Bool ?? false
135
+ let animationType = objc_getAssociatedObject(placeholder, &VModalFactory.animationTypeKey) as? String ?? "none"
136
+
137
+ let overlay = getOrCreateOverlay(for: placeholder)
138
+
139
+ if style == "sheet" {
140
+ // Present as sheet on the key window
141
+ guard let parentWindow = NSApplication.shared.keyWindow else { return }
142
+
143
+ let sheetWindow = NSWindow(
144
+ contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
145
+ styleMask: [.titled, .closable, .resizable],
146
+ backing: .buffered,
147
+ defer: false
148
+ )
149
+ sheetWindow.contentView = overlay
150
+ objc_setAssociatedObject(
151
+ placeholder, &VModalFactory.panelKey,
152
+ sheetWindow, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
153
+ )
154
+
155
+ parentWindow.beginSheet(sheetWindow) { [weak placeholder] _ in
156
+ guard let placeholder = placeholder else { return }
157
+ self.fireEvent(for: placeholder, key: &VModalFactory.onDismissKey)
158
+ }
159
+ } else {
160
+ // Present as floating panel
161
+ let panel = NSPanel(
162
+ contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
163
+ styleMask: [.titled, .closable, .resizable, .utilityWindow],
164
+ backing: .buffered,
165
+ defer: false
166
+ )
167
+ panel.isFloatingPanel = true
168
+
169
+ if transparent {
170
+ panel.isOpaque = false
171
+ panel.backgroundColor = .clear
172
+ } else {
173
+ panel.backgroundColor = NSColor(white: 0.95, alpha: 1.0)
174
+ }
175
+
176
+ panel.contentView = overlay
177
+ objc_setAssociatedObject(
178
+ placeholder, &VModalFactory.panelKey,
179
+ panel, .OBJC_ASSOCIATION_RETAIN_NONATOMIC
180
+ )
181
+
182
+ if animationType == "fade" {
183
+ panel.alphaValue = 0
184
+ panel.makeKeyAndOrderFront(nil)
185
+ panel.center()
186
+ NSAnimationContext.runAnimationGroup { context in
187
+ context.duration = 0.25
188
+ panel.animator().alphaValue = 1
189
+ }
190
+ } else {
191
+ panel.makeKeyAndOrderFront(nil)
192
+ panel.center()
193
+ }
194
+ }
195
+
196
+ fireEvent(for: placeholder, key: &VModalFactory.onShowKey)
197
+ }
198
+
199
+ private func hideModal(for placeholder: NSView) {
200
+ if let panel = objc_getAssociatedObject(placeholder, &VModalFactory.panelKey) as? NSWindow {
201
+ if let parentWindow = panel.sheetParent {
202
+ parentWindow.endSheet(panel)
203
+ } else {
204
+ let animationType = objc_getAssociatedObject(placeholder, &VModalFactory.animationTypeKey) as? String ?? "none"
205
+ if animationType == "fade" {
206
+ NSAnimationContext.runAnimationGroup({ context in
207
+ context.duration = 0.25
208
+ panel.animator().alphaValue = 0
209
+ }, completionHandler: {
210
+ panel.orderOut(nil)
211
+ })
212
+ } else {
213
+ panel.orderOut(nil)
214
+ }
215
+ }
216
+
217
+ // Move overlay back to placeholder ownership (detach from panel)
218
+ if let overlay = objc_getAssociatedObject(placeholder, &VModalFactory.overlayKey) as? FlippedView {
219
+ overlay.removeFromSuperview()
220
+ }
221
+
222
+ fireEvent(for: placeholder, key: &VModalFactory.onDismissKey)
223
+ }
224
+ }
225
+
226
+ private func fireEvent(for view: NSView, key: inout UInt8) {
227
+ if let handler = objc_getAssociatedObject(view, &key) as? ((Any?) -> Void) {
228
+ handler(nil)
229
+ }
230
+ }
231
+ }