@thelacanians/vue-native-cli 0.4.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift
ADDED
|
@@ -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
|
+
}
|
package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift
ADDED
|
@@ -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
|
+
}
|