@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.
- package/dist/cli.js +789 -200
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +156 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
- 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/Helpers/EventThrottle.kt +57 -0
- 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 +80 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +244 -112
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import Foundation
|
|
3
|
+
import QuartzCore
|
|
4
|
+
|
|
5
|
+
/// Throttles high-frequency event handlers to avoid flooding the JS bridge.
|
|
6
|
+
///
|
|
7
|
+
/// When a high-frequency event (scroll, slider drag, text input) fires many
|
|
8
|
+
/// times per frame, each invocation becomes a bridge round-trip. This utility
|
|
9
|
+
/// ensures at most one call per `interval` seconds, with a trailing call
|
|
10
|
+
/// to deliver the latest value.
|
|
11
|
+
///
|
|
12
|
+
/// Default interval: 16ms (~60 FPS). Callers can customize via `interval`.
|
|
13
|
+
final class EventThrottle {
|
|
14
|
+
|
|
15
|
+
/// Minimum time between handler invocations (seconds).
|
|
16
|
+
let interval: TimeInterval
|
|
17
|
+
|
|
18
|
+
/// The throttled handler to call.
|
|
19
|
+
private let handler: (Any?) -> Void
|
|
20
|
+
|
|
21
|
+
/// Timestamp of the last invocation.
|
|
22
|
+
private var lastFireTime: CFTimeInterval = 0
|
|
23
|
+
|
|
24
|
+
/// Whether a trailing call is pending.
|
|
25
|
+
private var pendingTrailing = false
|
|
26
|
+
|
|
27
|
+
/// The most recent payload, used for the trailing call.
|
|
28
|
+
private var latestPayload: Any?
|
|
29
|
+
|
|
30
|
+
/// Timer for delivering the trailing call.
|
|
31
|
+
private var trailingTimer: DispatchSourceTimer?
|
|
32
|
+
|
|
33
|
+
/// Create a throttled event handler.
|
|
34
|
+
/// - Parameters:
|
|
35
|
+
/// - interval: Minimum seconds between invocations. Default 0.016 (~60fps).
|
|
36
|
+
/// - handler: The closure to invoke with the event payload.
|
|
37
|
+
init(interval: TimeInterval = 0.016, handler: @escaping (Any?) -> Void) {
|
|
38
|
+
self.interval = interval
|
|
39
|
+
self.handler = handler
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
deinit {
|
|
43
|
+
trailingTimer?.cancel()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Call this from the native event callback instead of the original handler.
|
|
47
|
+
/// Fires immediately if enough time has elapsed, otherwise schedules a trailing call.
|
|
48
|
+
func fire(_ payload: Any?) {
|
|
49
|
+
let now = CACurrentMediaTime()
|
|
50
|
+
let elapsed = now - lastFireTime
|
|
51
|
+
|
|
52
|
+
latestPayload = payload
|
|
53
|
+
|
|
54
|
+
if elapsed >= interval {
|
|
55
|
+
// Enough time has passed — fire immediately
|
|
56
|
+
lastFireTime = now
|
|
57
|
+
pendingTrailing = false
|
|
58
|
+
trailingTimer?.cancel()
|
|
59
|
+
trailingTimer = nil
|
|
60
|
+
handler(payload)
|
|
61
|
+
} else if !pendingTrailing {
|
|
62
|
+
// Schedule a trailing call for the remaining time
|
|
63
|
+
pendingTrailing = true
|
|
64
|
+
let remaining = interval - elapsed
|
|
65
|
+
let timer = DispatchSource.makeTimerSource(queue: .main)
|
|
66
|
+
timer.schedule(deadline: .now() + remaining)
|
|
67
|
+
timer.setEventHandler { [weak self] in
|
|
68
|
+
guard let self = self else { return }
|
|
69
|
+
self.lastFireTime = CACurrentMediaTime()
|
|
70
|
+
self.pendingTrailing = false
|
|
71
|
+
self.trailingTimer = nil
|
|
72
|
+
self.handler(self.latestPayload)
|
|
73
|
+
}
|
|
74
|
+
trailingTimer = timer
|
|
75
|
+
timer.resume()
|
|
76
|
+
}
|
|
77
|
+
// If a trailing call is already pending, just update latestPayload (done above)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
#endif
|
|
@@ -61,6 +61,23 @@ public final class NativeBridge {
|
|
|
61
61
|
/// Reference to the JS runtime.
|
|
62
62
|
private let runtime = JSRuntime.shared
|
|
63
63
|
|
|
64
|
+
// MARK: - Teleport Support
|
|
65
|
+
|
|
66
|
+
/// Maps teleport marker IDs (start, end) for cleanup
|
|
67
|
+
private var teleportMarkers: [Int: (start: Int, end: Int)] = [:]
|
|
68
|
+
|
|
69
|
+
/// Maps parent node IDs to their teleport containers
|
|
70
|
+
private var teleportContainers: [Int: UIView] = [:]
|
|
71
|
+
|
|
72
|
+
/// Modal container for teleporting modals
|
|
73
|
+
private lazy var modalContainer: UIView = {
|
|
74
|
+
let container = UIView()
|
|
75
|
+
container.backgroundColor = .clear
|
|
76
|
+
container.isUserInteractionEnabled = true
|
|
77
|
+
container.translatesAutoresizingMaskIntoConstraints = false
|
|
78
|
+
return container
|
|
79
|
+
}()
|
|
80
|
+
|
|
64
81
|
// MARK: - Initialization
|
|
65
82
|
|
|
66
83
|
private init() {}
|
|
@@ -69,6 +86,59 @@ public final class NativeBridge {
|
|
|
69
86
|
|
|
70
87
|
private static var traitObserverKey: UInt8 = 99
|
|
71
88
|
|
|
89
|
+
// MARK: - Bridge Function Registration
|
|
90
|
+
|
|
91
|
+
/// Register `__VN_flushOperations`, `__VN_teardown`, `__VN_log`, and `__VN_handleError`
|
|
92
|
+
/// on the given JSContext. Called from both `initialize()` and `reloadWithBundle()`.
|
|
93
|
+
private func registerBridgeFunctions(on context: JSContext) {
|
|
94
|
+
let flushOps: @convention(block) (JSValue) -> Void = { [weak self] opsValue in
|
|
95
|
+
guard let self = self else { return }
|
|
96
|
+
guard let jsonString = opsValue.toString(), !jsonString.isEmpty else {
|
|
97
|
+
NSLog("[VueNative Bridge] Warning: Empty operations batch")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
guard let data = jsonString.data(using: .utf8),
|
|
102
|
+
let operations = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
103
|
+
NSLog("[VueNative Bridge] Error: Failed to parse operations JSON")
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
DispatchQueue.main.async { [weak self] in
|
|
108
|
+
self?.processOperations(operations)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
context.setObject(flushOps, forKeyedSubscript: "__VN_flushOperations" as NSString)
|
|
112
|
+
|
|
113
|
+
let teardown: @convention(block) () -> Void = { [weak self] in
|
|
114
|
+
NSLog("[VueNative] Teardown called from JS")
|
|
115
|
+
_ = self
|
|
116
|
+
}
|
|
117
|
+
context.setObject(teardown, forKeyedSubscript: "__VN_teardown" as NSString)
|
|
118
|
+
|
|
119
|
+
let log: @convention(block) (JSValue) -> Void = { message in
|
|
120
|
+
NSLog("[VueNative JS] \(message.toString() ?? "")")
|
|
121
|
+
}
|
|
122
|
+
context.setObject(log, forKeyedSubscript: "__VN_log" as NSString)
|
|
123
|
+
|
|
124
|
+
let handleError: @convention(block) (JSValue) -> Void = { errorInfoValue in
|
|
125
|
+
let jsonString = errorInfoValue.toString() ?? "{}"
|
|
126
|
+
NSLog("[VueNative Error] %@", jsonString)
|
|
127
|
+
|
|
128
|
+
if let data = jsonString.data(using: .utf8),
|
|
129
|
+
let info = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
130
|
+
let message = info["message"] as? String ?? "Unknown error"
|
|
131
|
+
let stack = info["stack"] as? String ?? ""
|
|
132
|
+
let componentName = info["componentName"] as? String ?? "unknown"
|
|
133
|
+
NSLog("[VueNative Error] Component: %@, Message: %@", componentName, message)
|
|
134
|
+
if !stack.isEmpty {
|
|
135
|
+
NSLog("[VueNative Error] Stack: %@", stack)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
context.setObject(handleError, forKeyedSubscript: "__VN_handleError" as NSString)
|
|
140
|
+
}
|
|
141
|
+
|
|
72
142
|
// MARK: - Setup
|
|
73
143
|
|
|
74
144
|
/// Initialize the bridge. Must be called after JSRuntime.initialize().
|
|
@@ -83,61 +153,7 @@ public final class NativeBridge {
|
|
|
83
153
|
runtime.jsQueue.async { [weak self] in
|
|
84
154
|
guard let self = self, let context = runtime.context else { return }
|
|
85
155
|
|
|
86
|
-
|
|
87
|
-
guard let self = self else { return }
|
|
88
|
-
guard let jsonString = opsValue.toString(), !jsonString.isEmpty else {
|
|
89
|
-
NSLog("[VueNative Bridge] Warning: Empty operations batch")
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Parse JSON on the JS queue (avoid main thread work)
|
|
94
|
-
guard let data = jsonString.data(using: .utf8),
|
|
95
|
-
let operations = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
96
|
-
NSLog("[VueNative Bridge] Error: Failed to parse operations JSON")
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Process operations on the main thread
|
|
101
|
-
DispatchQueue.main.async { [weak self] in
|
|
102
|
-
self?.processOperations(operations)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
context.setObject(flushOps, forKeyedSubscript: "__VN_flushOperations" as NSString)
|
|
106
|
-
|
|
107
|
-
// Register __VN_teardown for graceful JS-side shutdown on hot reload
|
|
108
|
-
let teardown: @convention(block) () -> Void = { [weak self] in
|
|
109
|
-
// Graceful shutdown: bridge will be re-initialized on reload
|
|
110
|
-
NSLog("[VueNative] Teardown called from JS")
|
|
111
|
-
_ = self
|
|
112
|
-
}
|
|
113
|
-
context.setObject(teardown, forKeyedSubscript: "__VN_teardown" as NSString)
|
|
114
|
-
|
|
115
|
-
// Register __VN_log for debug logging from JS
|
|
116
|
-
let log: @convention(block) (JSValue) -> Void = { message in
|
|
117
|
-
NSLog("[VueNative JS] \(message.toString() ?? "")")
|
|
118
|
-
}
|
|
119
|
-
context.setObject(log, forKeyedSubscript: "__VN_log" as NSString)
|
|
120
|
-
|
|
121
|
-
// Register __VN_handleError for JS error reporting
|
|
122
|
-
// Called from JS with a JSON-encoded string containing error info
|
|
123
|
-
// (message, stack, componentName).
|
|
124
|
-
let handleError: @convention(block) (JSValue) -> Void = { errorInfoValue in
|
|
125
|
-
let jsonString = errorInfoValue.toString() ?? "{}"
|
|
126
|
-
NSLog("[VueNative Error] %@", jsonString)
|
|
127
|
-
|
|
128
|
-
// Try to extract structured fields for clearer logging
|
|
129
|
-
if let data = jsonString.data(using: .utf8),
|
|
130
|
-
let info = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
131
|
-
let message = info["message"] as? String ?? "Unknown error"
|
|
132
|
-
let stack = info["stack"] as? String ?? ""
|
|
133
|
-
let componentName = info["componentName"] as? String ?? "unknown"
|
|
134
|
-
NSLog("[VueNative Error] Component: %@, Message: %@", componentName, message)
|
|
135
|
-
if !stack.isEmpty {
|
|
136
|
-
NSLog("[VueNative Error] Stack: %@", stack)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
context.setObject(handleError, forKeyedSubscript: "__VN_handleError" as NSString)
|
|
156
|
+
self.registerBridgeFunctions(on: context)
|
|
141
157
|
}
|
|
142
158
|
|
|
143
159
|
// Register all native modules synchronously so they are available
|
|
@@ -153,19 +169,44 @@ public final class NativeBridge {
|
|
|
153
169
|
"setRootView", "setText", "setElementText"
|
|
154
170
|
]
|
|
155
171
|
|
|
172
|
+
/// Style properties that affect Yoga layout and require a layout pass when changed.
|
|
173
|
+
/// When a batch contains only updateStyle ops for non-layout properties (e.g.
|
|
174
|
+
/// backgroundColor, opacity), we skip the expensive layout recalculation.
|
|
175
|
+
private static let layoutAffectingStyles: Set<String> = [
|
|
176
|
+
// Dimensions
|
|
177
|
+
"width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight",
|
|
178
|
+
// Flex
|
|
179
|
+
"flex", "flexGrow", "flexShrink", "flexBasis", "flexDirection",
|
|
180
|
+
"flexWrap", "alignItems", "alignSelf", "alignContent", "justifyContent",
|
|
181
|
+
// Spacing
|
|
182
|
+
"padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft",
|
|
183
|
+
"paddingHorizontal", "paddingVertical", "paddingStart", "paddingEnd",
|
|
184
|
+
"margin", "marginTop", "marginRight", "marginBottom", "marginLeft",
|
|
185
|
+
"marginHorizontal", "marginVertical", "marginStart", "marginEnd",
|
|
186
|
+
// Gap
|
|
187
|
+
"gap", "rowGap", "columnGap",
|
|
188
|
+
// Position
|
|
189
|
+
"position", "top", "right", "bottom", "left", "start", "end",
|
|
190
|
+
// Other layout
|
|
191
|
+
"aspectRatio", "display", "overflow", "direction",
|
|
192
|
+
]
|
|
193
|
+
|
|
156
194
|
/// Process a batch of operations on the main thread.
|
|
157
195
|
/// Each operation has an "op" key and "args" array.
|
|
158
|
-
///
|
|
159
|
-
/// (create, appendChild, insertBefore, removeChild, etc.)
|
|
160
|
-
///
|
|
196
|
+
/// Triggers a Yoga layout pass when the batch contains tree mutations
|
|
197
|
+
/// (create, appendChild, insertBefore, removeChild, etc.) OR style changes
|
|
198
|
+
/// that affect layout (width, height, padding, margin, flex, etc.).
|
|
199
|
+
/// Batches that only update visual styles/events skip the expensive layout.
|
|
161
200
|
///
|
|
162
201
|
/// Access: internal (not private) so that `@testable import` can exercise
|
|
163
202
|
/// operation handling without going through JSContext.
|
|
164
203
|
func processOperations(_ operations: [[String: Any]]) {
|
|
165
204
|
dispatchPrecondition(condition: .onQueue(.main))
|
|
205
|
+
#if DEBUG
|
|
166
206
|
NSLog("[VueNative Bridge] Processing %d operations", operations.count)
|
|
207
|
+
#endif
|
|
167
208
|
|
|
168
|
-
var
|
|
209
|
+
var needsLayout = false
|
|
169
210
|
|
|
170
211
|
for operation in operations {
|
|
171
212
|
guard let op = operation["op"] as? String,
|
|
@@ -174,8 +215,20 @@ public final class NativeBridge {
|
|
|
174
215
|
continue
|
|
175
216
|
}
|
|
176
217
|
|
|
177
|
-
if !
|
|
178
|
-
|
|
218
|
+
if !needsLayout && NativeBridge.treeMutationOps.contains(op) {
|
|
219
|
+
needsLayout = true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check if updateStyle changes any layout-affecting property
|
|
223
|
+
if !needsLayout && op == "updateStyle",
|
|
224
|
+
args.count >= 2,
|
|
225
|
+
let styles = args[1] as? [String: Any] {
|
|
226
|
+
for key in styles.keys {
|
|
227
|
+
if NativeBridge.layoutAffectingStyles.contains(key) {
|
|
228
|
+
needsLayout = true
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
}
|
|
179
232
|
}
|
|
180
233
|
|
|
181
234
|
switch op {
|
|
@@ -203,6 +256,12 @@ public final class NativeBridge {
|
|
|
203
256
|
handleRemoveEventListener(args: args)
|
|
204
257
|
case "setRootView":
|
|
205
258
|
handleSetRootView(args: args)
|
|
259
|
+
case "createTeleport":
|
|
260
|
+
handleCreateTeleport(args: args)
|
|
261
|
+
case "removeTeleport":
|
|
262
|
+
handleRemoveTeleport(args: args)
|
|
263
|
+
case "teleportTo":
|
|
264
|
+
handleTeleportTo(args: args)
|
|
206
265
|
case "invokeNativeModule":
|
|
207
266
|
handleInvokeNativeModule(args: args)
|
|
208
267
|
case "invokeNativeModuleSync":
|
|
@@ -212,8 +271,8 @@ public final class NativeBridge {
|
|
|
212
271
|
}
|
|
213
272
|
}
|
|
214
273
|
|
|
215
|
-
//
|
|
216
|
-
if
|
|
274
|
+
// Trigger layout when tree was mutated or layout-affecting styles changed
|
|
275
|
+
if needsLayout {
|
|
217
276
|
triggerLayout()
|
|
218
277
|
}
|
|
219
278
|
}
|
|
@@ -388,8 +447,9 @@ public final class NativeBridge {
|
|
|
388
447
|
if let factory = ComponentRegistry.factory(for: parentView) {
|
|
389
448
|
factory.insertChild(childView, into: container, before: beforeView)
|
|
390
449
|
} else if let index = container.subviews.firstIndex(of: beforeView) {
|
|
391
|
-
container.flex.addItem(childView)
|
|
392
450
|
container.insertSubview(childView, at: index)
|
|
451
|
+
container.flex.markDirty()
|
|
452
|
+
container.setNeedsLayout()
|
|
393
453
|
} else {
|
|
394
454
|
container.flex.addItem(childView)
|
|
395
455
|
}
|
|
@@ -559,6 +619,124 @@ public final class NativeBridge {
|
|
|
559
619
|
vc.view.addSubview(traitObserver)
|
|
560
620
|
// Retain the observer for the lifetime of the root view controller's view
|
|
561
621
|
objc_setAssociatedObject(vc.view as UIView, &NativeBridge.traitObserverKey, traitObserver as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
622
|
+
|
|
623
|
+
// Add modal container to root view for teleport
|
|
624
|
+
if rootView != nil {
|
|
625
|
+
rootView?.addSubview(modalContainer)
|
|
626
|
+
modalContainer.topAnchor.constraint(equalTo: rootView!.topAnchor).isActive = true
|
|
627
|
+
modalContainer.leadingAnchor.constraint(equalTo: rootView!.leadingAnchor).isActive = true
|
|
628
|
+
modalContainer.trailingAnchor.constraint(equalTo: rootView!.trailingAnchor).isActive = true
|
|
629
|
+
modalContainer.bottomAnchor.constraint(equalTo: rootView!.bottomAnchor).isActive = true
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// MARK: - Teleport Handlers
|
|
634
|
+
|
|
635
|
+
/// createTeleport: [parentId: Int, startId: Int, endId: Int]
|
|
636
|
+
private func handleCreateTeleport(args: [Any]) {
|
|
637
|
+
guard args.count >= 3,
|
|
638
|
+
let parentId = asInt(args[0]),
|
|
639
|
+
let startId = asInt(args[1]),
|
|
640
|
+
let endId = asInt(args[2]) else {
|
|
641
|
+
NSLog("[VueNative Bridge] Error: Invalid createTeleport args: \(args)")
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
guard let parentView = viewRegistry[parentId] else {
|
|
646
|
+
NSLog("[VueNative Bridge] Error: Parent view not found for teleport (id: \(parentId))")
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Store teleport marker IDs
|
|
651
|
+
teleportMarkers[parentId] = (start: startId, end: endId)
|
|
652
|
+
|
|
653
|
+
// Create container for teleported content
|
|
654
|
+
let container = UIView()
|
|
655
|
+
container.tag = -parentId // Negative tag to identify as teleport container
|
|
656
|
+
container.backgroundColor = .clear
|
|
657
|
+
container.isUserInteractionEnabled = true
|
|
658
|
+
container.translatesAutoresizingMaskIntoConstraints = false
|
|
659
|
+
|
|
660
|
+
parentView.addSubview(container)
|
|
661
|
+
teleportContainers[parentId] = container
|
|
662
|
+
|
|
663
|
+
#if DEBUG
|
|
664
|
+
NSLog("[VueNative Bridge] Created teleport container for parent \(parentId)")
|
|
665
|
+
#endif
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/// removeTeleport: [parentId: Int, startId: Int, endId: Int]
|
|
669
|
+
private func handleRemoveTeleport(args: [Any]) {
|
|
670
|
+
guard args.count >= 1,
|
|
671
|
+
let parentId = asInt(args[0]) else {
|
|
672
|
+
NSLog("[VueNative Bridge] Error: Invalid removeTeleport args: \(args)")
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Remove teleport container
|
|
677
|
+
if let container = teleportContainers.removeValue(forKey: parentId) {
|
|
678
|
+
container.removeFromSuperview()
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Clean up markers
|
|
682
|
+
teleportMarkers.removeValue(forKey: parentId)
|
|
683
|
+
|
|
684
|
+
#if DEBUG
|
|
685
|
+
NSLog("[VueNative Bridge] Removed teleport container for parent \(parentId)")
|
|
686
|
+
#endif
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/// teleportTo: [target: String, nodeId: Int]
|
|
690
|
+
private func handleTeleportTo(args: [Any]) {
|
|
691
|
+
guard args.count >= 2,
|
|
692
|
+
let target = args[0] as? String,
|
|
693
|
+
let nodeId = asInt(args[1]) else {
|
|
694
|
+
NSLog("[VueNative Bridge] Error: Invalid teleportTo args: \(args)")
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
guard let targetView = getTeleportTarget(target) else {
|
|
699
|
+
NSLog("[VueNative Bridge] Warning: Teleport target '\(target)' not found")
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
guard let childView = viewRegistry[nodeId] else {
|
|
704
|
+
NSLog("[VueNative Bridge] Warning: Node view not found for teleport (id: \(nodeId))")
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Move view to teleport target
|
|
709
|
+
childView.removeFromSuperview()
|
|
710
|
+
targetView.addSubview(childView)
|
|
711
|
+
|
|
712
|
+
// Set up full-size constraints
|
|
713
|
+
childView.translatesAutoresizingMaskIntoConstraints = false
|
|
714
|
+
NSLayoutConstraint.activate([
|
|
715
|
+
childView.topAnchor.constraint(equalTo: targetView.topAnchor),
|
|
716
|
+
childView.leadingAnchor.constraint(equalTo: targetView.leadingAnchor),
|
|
717
|
+
childView.trailingAnchor.constraint(equalTo: targetView.trailingAnchor),
|
|
718
|
+
childView.bottomAnchor.constraint(equalTo: targetView.bottomAnchor),
|
|
719
|
+
])
|
|
720
|
+
|
|
721
|
+
#if DEBUG
|
|
722
|
+
NSLog("[VueNative Bridge] Teleported node \(nodeId) to target '\(target)'")
|
|
723
|
+
#endif
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/// Get teleport target view by name
|
|
727
|
+
private func getTeleportTarget(_ target: String) -> UIView? {
|
|
728
|
+
switch target {
|
|
729
|
+
case "root":
|
|
730
|
+
return rootView
|
|
731
|
+
case "modal":
|
|
732
|
+
// Ensure modal container is added to root if not already
|
|
733
|
+
if modalContainer.superview == nil && rootView != nil {
|
|
734
|
+
rootView?.addSubview(modalContainer)
|
|
735
|
+
}
|
|
736
|
+
return modalContainer
|
|
737
|
+
default:
|
|
738
|
+
return nil
|
|
739
|
+
}
|
|
562
740
|
}
|
|
563
741
|
|
|
564
742
|
// MARK: - Native Module Handlers
|
|
@@ -781,55 +959,9 @@ public final class NativeBridge {
|
|
|
781
959
|
return
|
|
782
960
|
}
|
|
783
961
|
|
|
784
|
-
// Re-register
|
|
962
|
+
// Re-register bridge functions on new context
|
|
785
963
|
guard let context = self.runtime.context else { return }
|
|
786
|
-
|
|
787
|
-
let flushOps: @convention(block) (JSValue) -> Void = { [weak self] opsValue in
|
|
788
|
-
guard let self = self else { return }
|
|
789
|
-
guard let jsonString = opsValue.toString(), !jsonString.isEmpty else {
|
|
790
|
-
NSLog("[VueNative Bridge] Warning: Empty operations batch")
|
|
791
|
-
return
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
guard let data = jsonString.data(using: .utf8),
|
|
795
|
-
let operations = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
796
|
-
NSLog("[VueNative Bridge] Error: Failed to parse operations JSON")
|
|
797
|
-
return
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
DispatchQueue.main.async { [weak self] in
|
|
801
|
-
self?.processOperations(operations)
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
context.setObject(flushOps, forKeyedSubscript: "__VN_flushOperations" as NSString)
|
|
805
|
-
|
|
806
|
-
let teardown: @convention(block) () -> Void = { [weak self] in
|
|
807
|
-
NSLog("[VueNative] Teardown called from JS")
|
|
808
|
-
_ = self
|
|
809
|
-
}
|
|
810
|
-
context.setObject(teardown, forKeyedSubscript: "__VN_teardown" as NSString)
|
|
811
|
-
|
|
812
|
-
let log: @convention(block) (JSValue) -> Void = { message in
|
|
813
|
-
NSLog("[VueNative JS] \(message.toString() ?? "")")
|
|
814
|
-
}
|
|
815
|
-
context.setObject(log, forKeyedSubscript: "__VN_log" as NSString)
|
|
816
|
-
|
|
817
|
-
let handleError: @convention(block) (JSValue) -> Void = { errorInfoValue in
|
|
818
|
-
let jsonString = errorInfoValue.toString() ?? "{}"
|
|
819
|
-
NSLog("[VueNative Error] %@", jsonString)
|
|
820
|
-
|
|
821
|
-
if let data = jsonString.data(using: .utf8),
|
|
822
|
-
let info = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
823
|
-
let message = info["message"] as? String ?? "Unknown error"
|
|
824
|
-
let stack = info["stack"] as? String ?? ""
|
|
825
|
-
let componentName = info["componentName"] as? String ?? "unknown"
|
|
826
|
-
NSLog("[VueNative Error] Component: %@, Message: %@", componentName, message)
|
|
827
|
-
if !stack.isEmpty {
|
|
828
|
-
NSLog("[VueNative Error] Stack: %@", stack)
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
context.setObject(handleError, forKeyedSubscript: "__VN_handleError" as NSString)
|
|
964
|
+
self.registerBridgeFunctions(on: context)
|
|
833
965
|
|
|
834
966
|
NSLog("[VueNative Bridge] reloadWithBundle: bridge re-registered on new context")
|
|
835
967
|
}
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift
CHANGED
|
@@ -37,6 +37,7 @@ final class VListFactory: NativeComponentFactory {
|
|
|
37
37
|
switch event {
|
|
38
38
|
case "scroll":
|
|
39
39
|
container.onScroll = handler
|
|
40
|
+
container.scrollThrottle = EventThrottle(interval: 0.016, handler: handler)
|
|
40
41
|
case "endReached":
|
|
41
42
|
container.onEndReached = handler
|
|
42
43
|
default:
|
|
@@ -47,7 +48,9 @@ final class VListFactory: NativeComponentFactory {
|
|
|
47
48
|
func removeEventListener(view: UIView, event: String) {
|
|
48
49
|
guard let container = view as? VListContainerView else { return }
|
|
49
50
|
switch event {
|
|
50
|
-
case "scroll":
|
|
51
|
+
case "scroll":
|
|
52
|
+
container.onScroll = nil
|
|
53
|
+
container.scrollThrottle = nil
|
|
51
54
|
case "endReached": container.onEndReached = nil
|
|
52
55
|
default: break
|
|
53
56
|
}
|
|
@@ -106,6 +109,7 @@ final class VListContainerView: UIView {
|
|
|
106
109
|
var itemViews: [UIView] = []
|
|
107
110
|
var estimatedItemHeight: CGFloat = 44
|
|
108
111
|
var onScroll: ((Any?) -> Void)?
|
|
112
|
+
var scrollThrottle: EventThrottle?
|
|
109
113
|
var onEndReached: ((Any?) -> Void)?
|
|
110
114
|
fileprivate var firedEndReached = false
|
|
111
115
|
private lazy var internalDelegate = VListInternalDelegate(container: self)
|
|
@@ -195,7 +199,20 @@ private final class VListInternalDelegate: NSObject,
|
|
|
195
199
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
196
200
|
guard let container = container else { return }
|
|
197
201
|
let offset = scrollView.contentOffset
|
|
198
|
-
|
|
202
|
+
let payload: [String: Any] = [
|
|
203
|
+
"x": Double(offset.x),
|
|
204
|
+
"y": Double(offset.y),
|
|
205
|
+
"contentWidth": Double(scrollView.contentSize.width),
|
|
206
|
+
"contentHeight": Double(scrollView.contentSize.height),
|
|
207
|
+
"layoutWidth": Double(scrollView.frame.width),
|
|
208
|
+
"layoutHeight": Double(scrollView.frame.height),
|
|
209
|
+
]
|
|
210
|
+
// Use throttle if available, otherwise fire directly
|
|
211
|
+
if let throttle = container.scrollThrottle {
|
|
212
|
+
throttle.fire(payload)
|
|
213
|
+
} else {
|
|
214
|
+
container.onScroll?(payload)
|
|
215
|
+
}
|
|
199
216
|
|
|
200
217
|
// endReached detection (threshold = 20% from bottom)
|
|
201
218
|
let contentH = scrollView.contentSize.height
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift
CHANGED
|
@@ -221,20 +221,25 @@ private final class RefreshTarget: NSObject {
|
|
|
221
221
|
// MARK: - ScrollDelegateProxy
|
|
222
222
|
|
|
223
223
|
/// UIScrollViewDelegate proxy that forwards scroll events to a JS handler.
|
|
224
|
+
/// Uses EventThrottle to limit bridge round-trips to ~60/s during fast scrolling.
|
|
224
225
|
private final class ScrollDelegateProxy: NSObject, UIScrollViewDelegate {
|
|
225
|
-
private let
|
|
226
|
+
private let throttle: EventThrottle
|
|
226
227
|
|
|
227
228
|
init(handler: @escaping (Any?) -> Void) {
|
|
228
|
-
self.
|
|
229
|
+
self.throttle = EventThrottle(interval: 0.016, handler: handler)
|
|
229
230
|
super.init()
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
233
234
|
let payload: [String: Any] = [
|
|
234
235
|
"x": scrollView.contentOffset.x,
|
|
235
|
-
"y": scrollView.contentOffset.y
|
|
236
|
+
"y": scrollView.contentOffset.y,
|
|
237
|
+
"contentWidth": scrollView.contentSize.width,
|
|
238
|
+
"contentHeight": scrollView.contentSize.height,
|
|
239
|
+
"layoutWidth": scrollView.frame.width,
|
|
240
|
+
"layoutHeight": scrollView.frame.height,
|
|
236
241
|
]
|
|
237
|
-
|
|
242
|
+
throttle.fire(payload)
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
245
|
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift
CHANGED
|
@@ -53,11 +53,16 @@ final class VSliderFactory: NativeComponentFactory {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/// Throttled target for UISlider .valueChanged events.
|
|
57
|
+
/// Limits bridge round-trips to ~60/s during continuous slider dragging.
|
|
56
58
|
private final class SliderTarget: NSObject {
|
|
57
|
-
let
|
|
58
|
-
init(handler: @escaping (Any?) -> Void) {
|
|
59
|
+
private let throttle: EventThrottle
|
|
60
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
61
|
+
self.throttle = EventThrottle(interval: 0.016, handler: handler)
|
|
62
|
+
super.init()
|
|
63
|
+
}
|
|
59
64
|
@objc func handleValueChanged(_ slider: UISlider) {
|
|
60
|
-
|
|
65
|
+
throttle.fire(["value": Double(slider.value)])
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift
CHANGED
|
@@ -40,6 +40,7 @@ final class VTextFactory: NativeComponentFactory {
|
|
|
40
40
|
private static var fontSizeKey: UInt8 = 0
|
|
41
41
|
private static var fontWeightKey: UInt8 = 0
|
|
42
42
|
private static var fontFamilyKey: UInt8 = 0
|
|
43
|
+
private static var textChildrenKey: UInt8 = 0
|
|
43
44
|
|
|
44
45
|
// MARK: - NativeComponentFactory
|
|
45
46
|
|
|
@@ -57,6 +58,7 @@ final class VTextFactory: NativeComponentFactory {
|
|
|
57
58
|
|
|
58
59
|
switch key {
|
|
59
60
|
case "text":
|
|
61
|
+
storeTextChildren([], on: label)
|
|
60
62
|
if let text = value as? String {
|
|
61
63
|
label.text = text
|
|
62
64
|
} else {
|
|
@@ -235,6 +237,31 @@ final class VTextFactory: NativeComponentFactory {
|
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
func insertChild(_ child: UIView, into parent: UIView, before anchor: UIView?) {
|
|
241
|
+
guard let label = parent as? UILabel else {
|
|
242
|
+
child.removeFromSuperview()
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
var children = storedTextChildren(on: label)
|
|
247
|
+
if let anchor = anchor, let index = children.firstIndex(where: { $0 === anchor }) {
|
|
248
|
+
children.insert(child, at: index)
|
|
249
|
+
} else {
|
|
250
|
+
children.append(child)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
storeTextChildren(children, on: label)
|
|
254
|
+
rebuildText(from: children, on: label)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
func removeChild(_ child: UIView, from parent: UIView) {
|
|
258
|
+
guard let label = parent as? UILabel else { return }
|
|
259
|
+
var children = storedTextChildren(on: label)
|
|
260
|
+
children.removeAll { $0 === child }
|
|
261
|
+
storeTextChildren(children, on: label)
|
|
262
|
+
rebuildText(from: children, on: label)
|
|
263
|
+
}
|
|
264
|
+
|
|
238
265
|
// MARK: - Font rebuilding
|
|
239
266
|
|
|
240
267
|
/// Rebuild the UIFont from stored fontSize, fontWeight, and fontFamily.
|
|
@@ -286,5 +313,21 @@ final class VTextFactory: NativeComponentFactory {
|
|
|
286
313
|
private func storedFontFamily(on view: UIView) -> String? {
|
|
287
314
|
return objc_getAssociatedObject(view, &VTextFactory.fontFamilyKey) as? String
|
|
288
315
|
}
|
|
316
|
+
|
|
317
|
+
private func storeTextChildren(_ children: [UIView], on view: UIView) {
|
|
318
|
+
objc_setAssociatedObject(view, &VTextFactory.textChildrenKey, children, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private func storedTextChildren(on view: UIView) -> [UIView] {
|
|
322
|
+
return objc_getAssociatedObject(view, &VTextFactory.textChildrenKey) as? [UIView] ?? []
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private func rebuildText(from children: [UIView], on label: UILabel) {
|
|
326
|
+
let text = children.compactMap { child -> String? in
|
|
327
|
+
(child as? UILabel)?.text
|
|
328
|
+
}.joined()
|
|
329
|
+
label.text = text.isEmpty ? nil : text
|
|
330
|
+
label.flex.markDirty()
|
|
331
|
+
}
|
|
289
332
|
}
|
|
290
333
|
#endif
|