@thelacanians/vue-native-cli 0.4.14 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/cli.js +789 -200
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +156 -5
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
  6. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  7. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
  8. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  9. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  13. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  14. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  15. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  16. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  17. package/native/android/gradle.properties +1 -0
  18. package/native/android/gradlew +1 -1
  19. package/native/ios/VueNativeCore/Package.swift +1 -1
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +80 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +244 -112
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
  23. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
  24. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
  25. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  26. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  27. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  28. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  29. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  30. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  31. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  32. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  33. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  34. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  35. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  36. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  94. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  95. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  96. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  97. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  98. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  99. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  100. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  101. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  102. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  103. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  104. package/native/shared/VueNativeShared/Package.swift +14 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  115. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  116. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  117. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  118. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  119. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  120. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  121. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  122. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  123. package/package.json +8 -2
@@ -6,6 +6,7 @@ import android.os.Looper
6
6
  import android.util.Log
7
7
  import android.view.View
8
8
  import android.view.ViewGroup
9
+ import android.widget.FrameLayout
9
10
  import org.json.JSONArray
10
11
  import org.json.JSONObject
11
12
 
@@ -23,6 +24,20 @@ class NativeBridge(private val context: Context) {
23
24
  "create", "createText", "appendChild", "insertBefore", "removeChild",
24
25
  "setRootView", "setText", "setElementText"
25
26
  )
27
+
28
+ /** Style properties that affect layout and require a layout pass when changed. */
29
+ private val layoutAffectingStyles = setOf(
30
+ "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight",
31
+ "flex", "flexGrow", "flexShrink", "flexBasis", "flexDirection",
32
+ "flexWrap", "alignItems", "alignSelf", "alignContent", "justifyContent",
33
+ "padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft",
34
+ "paddingHorizontal", "paddingVertical", "paddingStart", "paddingEnd",
35
+ "margin", "marginTop", "marginRight", "marginBottom", "marginLeft",
36
+ "marginHorizontal", "marginVertical", "marginStart", "marginEnd",
37
+ "gap", "rowGap", "columnGap",
38
+ "position", "top", "right", "bottom", "left", "start", "end",
39
+ "aspectRatio", "display", "overflow", "direction",
40
+ )
26
41
  }
27
42
 
28
43
  private val mainHandler = Handler(Looper.getMainLooper())
@@ -53,6 +68,26 @@ class NativeBridge(private val context: Context) {
53
68
  /** The container view from the host Activity — where the root view is attached. */
54
69
  var hostContainer: ViewGroup? = null
55
70
 
71
+ // -- Teleport support --
72
+ /** Maps teleport marker IDs for cleanup */
73
+ private val teleportMarkers = mutableMapOf<Int, Pair<Int, Int>>()
74
+
75
+ /** Maps parent node IDs to their teleport containers */
76
+ private val teleportContainers = mutableMapOf<Int, ViewGroup>()
77
+
78
+ /** Modal container for teleporting modals */
79
+ private val modalContainer: FrameLayout by lazy {
80
+ FrameLayout(context).apply {
81
+ layoutParams = FrameLayout.LayoutParams(
82
+ FrameLayout.LayoutParams.MATCH_PARENT,
83
+ FrameLayout.LayoutParams.MATCH_PARENT
84
+ )
85
+ setBackgroundColor(android.graphics.Color.TRANSPARENT)
86
+ isClickable = true
87
+ isFocusable = false
88
+ }
89
+ }
90
+
56
91
  /** Called when native fires an event to JS (nodeId, eventName, payloadJson). */
57
92
  var onFireEvent: ((nodeId: Int, eventName: String, payloadJson: String) -> Unit)? = null
58
93
 
@@ -81,20 +116,34 @@ class NativeBridge(private val context: Context) {
81
116
  }
82
117
 
83
118
  mainHandler.post {
84
- var hasMutations = false
119
+ var needsLayout = false
85
120
  for (op in ops) {
86
121
  try {
87
122
  val opName = op.optString("op")
88
- if (!hasMutations && opName in treeMutationOps) {
89
- hasMutations = true
123
+ if (!needsLayout && opName in treeMutationOps) {
124
+ needsLayout = true
125
+ }
126
+ // Check if updateStyle changes any layout-affecting property
127
+ if (!needsLayout && opName == "updateStyle") {
128
+ val args = op.optJSONArray("args")
129
+ val styleObj = args?.optJSONObject(1)
130
+ if (styleObj != null) {
131
+ val keys = styleObj.keys()
132
+ while (keys.hasNext()) {
133
+ if (keys.next() in layoutAffectingStyles) {
134
+ needsLayout = true
135
+ break
136
+ }
137
+ }
138
+ }
90
139
  }
91
140
  handleOperation(op)
92
141
  } catch (e: Exception) {
93
142
  Log.e(TAG, "Error handling op '${op.optString("op")}': ${e.message}")
94
143
  }
95
144
  }
96
- // Only trigger layout when the batch mutated the view tree
97
- if (hasMutations) {
145
+ // Trigger layout when tree was mutated or layout-affecting styles changed
146
+ if (needsLayout) {
98
147
  triggerLayout()
99
148
  }
100
149
  }
@@ -115,6 +164,9 @@ class NativeBridge(private val context: Context) {
115
164
  "insertBefore" -> handleInsertBefore(args)
116
165
  "removeChild" -> handleRemoveChild(args)
117
166
  "setRootView" -> handleSetRootView(args)
167
+ "createTeleport" -> handleCreateTeleport(args)
168
+ "removeTeleport" -> handleRemoveTeleport(args)
169
+ "teleportTo" -> handleTeleportTo(args)
118
170
  "addEventListener" -> handleAddEventListener(args)
119
171
  "removeEventListener" -> handleRemoveEventListener(args)
120
172
  "invokeNativeModule" -> handleInvokeNativeModule(args)
@@ -283,6 +335,100 @@ class NativeBridge(private val context: Context) {
283
335
  ViewGroup.LayoutParams.MATCH_PARENT
284
336
  )
285
337
  )
338
+
339
+ // Add modal container for teleport
340
+ container.addView(
341
+ modalContainer,
342
+ ViewGroup.LayoutParams(
343
+ ViewGroup.LayoutParams.MATCH_PARENT,
344
+ ViewGroup.LayoutParams.MATCH_PARENT
345
+ )
346
+ )
347
+ }
348
+
349
+ // -- Teleport handlers --
350
+
351
+ private fun handleCreateTeleport(args: JSONArray) {
352
+ val parentId = args.getInt(0)
353
+ val startId = args.getInt(1)
354
+ val endId = args.getInt(2)
355
+
356
+ val parentView = nodeViews[parentId] ?: run {
357
+ Log.w(TAG, "Parent view not found for teleport (id: $parentId)")
358
+ return
359
+ }
360
+
361
+ // Store teleport marker IDs
362
+ teleportMarkers[parentId] = Pair(startId, endId)
363
+
364
+ // Create container for teleported content
365
+ val container = FrameLayout(context).apply {
366
+ id = View.generateViewId()
367
+ setBackgroundColor(android.graphics.Color.TRANSPARENT)
368
+ isClickable = true
369
+ isFocusable = false
370
+ }
371
+
372
+ if (parentView is ViewGroup) {
373
+ parentView.addView(container)
374
+ teleportContainers[parentId] = container
375
+ }
376
+
377
+ Log.d(TAG, "Created teleport container for parent $parentId")
378
+ }
379
+
380
+ private fun handleRemoveTeleport(args: JSONArray) {
381
+ val parentId = args.getInt(0)
382
+
383
+ // Remove teleport container
384
+ teleportContainers.remove(parentId)?.let { container ->
385
+ mainHandler.post {
386
+ (container.parent as? ViewGroup)?.removeView(container)
387
+ }
388
+ }
389
+
390
+ // Clean up markers
391
+ teleportMarkers.remove(parentId)
392
+
393
+ Log.d(TAG, "Removed teleport container for parent $parentId")
394
+ }
395
+
396
+ private fun handleTeleportTo(args: JSONArray) {
397
+ val target = args.getString(0)
398
+ val nodeId = args.getInt(1)
399
+
400
+ val targetView = getTeleportTarget(target) ?: run {
401
+ Log.w(TAG, "Teleport target '$target' not found")
402
+ return
403
+ }
404
+
405
+ val childView = nodeViews[nodeId] ?: run {
406
+ Log.w(TAG, "Node view not found for teleport (id: $nodeId)")
407
+ return
408
+ }
409
+
410
+ // Move view to teleport target
411
+ mainHandler.post {
412
+ (childView.parent as? ViewGroup)?.removeView(childView)
413
+ targetView.addView(childView)
414
+ childView.requestLayout()
415
+ }
416
+
417
+ Log.d(TAG, "Teleported node $nodeId to target '$target'")
418
+ }
419
+
420
+ private fun getTeleportTarget(target: String): ViewGroup? {
421
+ return when (target) {
422
+ "root" -> rootView as? ViewGroup
423
+ "modal" -> {
424
+ // Ensure modal container is added to root if not already
425
+ if (modalContainer.parent == null && rootView != null) {
426
+ (rootView as? ViewGroup)?.addView(modalContainer)
427
+ }
428
+ modalContainer
429
+ }
430
+ else -> null
431
+ }
286
432
  }
287
433
 
288
434
  private fun handleAddEventListener(args: JSONArray) {
@@ -380,6 +526,11 @@ class NativeBridge(private val context: Context) {
380
526
 
381
527
  /** Clear all registries. Called during hot reload after JS teardown. Must run on main thread. */
382
528
  fun clearAllRegistries() {
529
+ // Destroy all views via their factories before clearing maps
530
+ for ((_, view) in nodeViews) {
531
+ val factory = componentRegistry.factoryForView(view)
532
+ factory?.destroyView(view)
533
+ }
383
534
  nodeViews.clear()
384
535
  nodeTypes.clear()
385
536
  eventHandlers.clear()
@@ -98,9 +98,16 @@ class VListFactory : NativeComponentFactory {
98
98
  cumulativeX += dx
99
99
  cumulativeY += dy
100
100
 
101
- // Dispatch scroll event with cumulative position
101
+ // Dispatch scroll event with cumulative position and dimensions
102
102
  scrollHandlers[recyclerView]?.invoke(
103
- mapOf("x" to cumulativeX, "y" to cumulativeY)
103
+ mapOf(
104
+ "x" to cumulativeX,
105
+ "y" to cumulativeY,
106
+ "contentWidth" to (recyclerView.computeHorizontalScrollRange()),
107
+ "contentHeight" to (recyclerView.computeVerticalScrollRange()),
108
+ "layoutWidth" to recyclerView.width,
109
+ "layoutHeight" to recyclerView.height,
110
+ )
104
111
  )
105
112
 
106
113
  // endReached detection (threshold = 20% from bottom)
@@ -167,20 +174,33 @@ class VListFactory : NativeComponentFactory {
167
174
  }
168
175
 
169
176
  class VListAdapter(private val items: List<View>) : RecyclerView.Adapter<VListAdapter.VH>() {
170
- class VH(val view: View) : RecyclerView.ViewHolder(view)
177
+ class VH(itemView: View) : RecyclerView.ViewHolder(itemView)
171
178
 
172
179
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
173
- val v = items.getOrNull(viewType) ?: android.widget.FrameLayout(parent.context)
174
- // Remove from any existing parent
175
- (v.parent as? ViewGroup)?.removeView(v)
176
- v.layoutParams = RecyclerView.LayoutParams(
177
- RecyclerView.LayoutParams.MATCH_PARENT,
178
- RecyclerView.LayoutParams.WRAP_CONTENT
179
- )
180
- return VH(v)
180
+ val container = android.widget.FrameLayout(parent.context).apply {
181
+ layoutParams = RecyclerView.LayoutParams(
182
+ RecyclerView.LayoutParams.MATCH_PARENT,
183
+ RecyclerView.LayoutParams.WRAP_CONTENT
184
+ )
185
+ }
186
+ return VH(container)
187
+ }
188
+
189
+ override fun onBindViewHolder(holder: VH, position: Int) {
190
+ val container = holder.itemView as android.widget.FrameLayout
191
+ // Remove the previous item view from this recycled container
192
+ container.removeAllViews()
193
+ val itemView = items.getOrNull(position) ?: return
194
+ // Remove from any existing parent (previous container or direct parent)
195
+ (itemView.parent as? ViewGroup)?.removeView(itemView)
196
+ container.addView(itemView, ViewGroup.LayoutParams(
197
+ ViewGroup.LayoutParams.MATCH_PARENT,
198
+ ViewGroup.LayoutParams.WRAP_CONTENT
199
+ ))
181
200
  }
182
201
 
183
- override fun onBindViewHolder(holder: VH, position: Int) {}
184
202
  override fun getItemCount(): Int = items.size
185
- override fun getItemViewType(position: Int): Int = position
203
+
204
+ // Use a single view type so RecyclerView can reuse all ViewHolders
205
+ override fun getItemViewType(position: Int): Int = 0
186
206
  }
@@ -64,6 +64,9 @@ class VScrollViewFactory : NativeComponentFactory {
64
64
  }
65
65
  }
66
66
 
67
+ private val scrollThrottles = mutableMapOf<SwipeRefreshLayout, EventThrottle>()
68
+ private val scrollListeners = mutableMapOf<SwipeRefreshLayout, android.view.ViewTreeObserver.OnScrollChangedListener>()
69
+
67
70
  override fun addEventListener(view: View, event: String, handler: (Any?) -> Unit) {
68
71
  val srf = view as? SwipeRefreshLayout ?: return
69
72
  val scroll = states[srf]?.scrollView
@@ -73,20 +76,38 @@ class VScrollViewFactory : NativeComponentFactory {
73
76
  srf.setOnRefreshListener { handler(null) }
74
77
  }
75
78
  "scroll" -> {
76
- scroll?.viewTreeObserver?.addOnScrollChangedListener {
77
- handler(mapOf(
78
- "contentOffset" to mapOf("x" to scroll.scrollX, "y" to scroll.scrollY)
79
+ val throttle = EventThrottle(intervalMs = 16L, handler = handler)
80
+ scrollThrottles[srf] = throttle
81
+ val listener = android.view.ViewTreeObserver.OnScrollChangedListener {
82
+ val child = if (scroll != null && scroll.childCount > 0) scroll.getChildAt(0) else null
83
+ throttle.fire(mapOf(
84
+ "x" to (scroll?.scrollX ?: 0),
85
+ "y" to (scroll?.scrollY ?: 0),
86
+ "contentWidth" to (child?.width ?: scroll?.width ?: 0),
87
+ "contentHeight" to (child?.height ?: scroll?.height ?: 0),
88
+ "layoutWidth" to (scroll?.width ?: 0),
89
+ "layoutHeight" to (scroll?.height ?: 0),
79
90
  ))
80
91
  }
92
+ scrollListeners[srf] = listener
93
+ scroll?.viewTreeObserver?.addOnScrollChangedListener(listener)
81
94
  }
82
95
  }
83
96
  }
84
97
 
85
98
  override fun removeEventListener(view: View, event: String) {
86
99
  val srf = view as? SwipeRefreshLayout ?: return
87
- if (event == "refresh") {
88
- srf.setOnRefreshListener(null)
89
- srf.isEnabled = false
100
+ when (event) {
101
+ "refresh" -> {
102
+ srf.setOnRefreshListener(null)
103
+ srf.isEnabled = false
104
+ }
105
+ "scroll" -> {
106
+ scrollListeners.remove(srf)?.let { listener ->
107
+ states[srf]?.scrollView?.viewTreeObserver?.removeOnScrollChangedListener(listener)
108
+ }
109
+ scrollThrottles.remove(srf)?.cancel()
110
+ }
90
111
  }
91
112
  }
92
113
 
@@ -48,18 +48,24 @@ class VSliderFactory : NativeComponentFactory {
48
48
  }
49
49
  }
50
50
 
51
+ private val throttles = mutableMapOf<SeekBar, EventThrottle>()
52
+
51
53
  override fun addEventListener(view: View, event: String, handler: (Any?) -> Unit) {
52
54
  val sb = view as? SeekBar ?: return
53
55
  when (event) {
54
56
  "change", "valueChange" -> {
55
57
  changeHandlers[sb] = handler
58
+ val throttle = EventThrottle(intervalMs = 16L, handler = handler)
59
+ throttles[sb] = throttle
56
60
  sb.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
57
61
  override fun onProgressChanged(s: SeekBar, progress: Int, fromUser: Boolean) {
58
- if (fromUser) changeHandlers[s]?.invoke(mapOf("value" to progress.toFloat()))
62
+ if (fromUser) throttle.fire(mapOf("value" to progress.toFloat() / s.max.toFloat()))
59
63
  }
60
64
  override fun onStartTrackingTouch(s: SeekBar) {}
61
65
  override fun onStopTrackingTouch(s: SeekBar) {
62
- changeHandlers[s]?.invoke(mapOf("value" to s.progress.toFloat()))
66
+ // Always deliver the final value immediately on release
67
+ throttle.cancel()
68
+ changeHandlers[s]?.invoke(mapOf("value" to s.progress.toFloat() / s.max.toFloat()))
63
69
  }
64
70
  })
65
71
  }
@@ -69,6 +75,7 @@ class VSliderFactory : NativeComponentFactory {
69
75
  override fun removeEventListener(view: View, event: String) {
70
76
  val sb = view as? SeekBar ?: return
71
77
  changeHandlers.remove(sb)
78
+ throttles.remove(sb)?.cancel()
72
79
  sb.setOnSeekBarChangeListener(null)
73
80
  }
74
81
  }
@@ -1,10 +1,16 @@
1
1
  package com.vuenative.core
2
2
 
3
3
  import android.content.Context
4
+ import android.view.GestureDetector
5
+ import android.view.MotionEvent
6
+ import android.view.ScaleGestureDetector
4
7
  import android.view.View
5
8
  import android.view.ViewGroup
6
9
  import com.google.android.flexbox.FlexDirection
7
10
  import com.google.android.flexbox.FlexboxLayout
11
+ import kotlin.math.abs
12
+ import kotlin.math.atan2
13
+ import kotlin.math.sqrt
8
14
 
9
15
  class VViewFactory : NativeComponentFactory {
10
16
  override fun createView(context: Context): View {
@@ -28,6 +34,125 @@ class VViewFactory : NativeComponentFactory {
28
34
  handler(null)
29
35
  true
30
36
  }
37
+ "pan", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "pinch", "rotate" -> {
38
+ setupGestureListener(view, event, handler)
39
+ }
40
+ }
41
+ }
42
+
43
+ private fun setupGestureListener(view: View, event: String, handler: (Any?) -> Unit) {
44
+ val context = view.context
45
+
46
+ when (event) {
47
+ "pan" -> {
48
+ val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
49
+ override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
50
+ val payload = mapOf(
51
+ "translationX" to -distanceX,
52
+ "translationY" to -distanceY,
53
+ "velocityX" to 0f,
54
+ "velocityY" to 0f,
55
+ "state" to "changed"
56
+ )
57
+ handler(payload)
58
+ return true
59
+ }
60
+ })
61
+ view.setOnTouchListener { _, motionEvent ->
62
+ gestureDetector.onTouchEvent(motionEvent)
63
+ false
64
+ }
65
+ }
66
+ "swipeLeft" -> {
67
+ val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
68
+ override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
69
+ if (abs(velocityX) > abs(velocityY) && velocityX < 0) {
70
+ val payload = mapOf("direction" to "left")
71
+ handler(payload)
72
+ }
73
+ return true
74
+ }
75
+ })
76
+ view.setOnTouchListener { _, motionEvent ->
77
+ gestureDetector.onTouchEvent(motionEvent)
78
+ false
79
+ }
80
+ }
81
+ "swipeRight" -> {
82
+ val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
83
+ override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
84
+ if (abs(velocityX) > abs(velocityY) && velocityX > 0) {
85
+ val payload = mapOf("direction" to "right")
86
+ handler(payload)
87
+ }
88
+ return true
89
+ }
90
+ })
91
+ view.setOnTouchListener { _, motionEvent ->
92
+ gestureDetector.onTouchEvent(motionEvent)
93
+ false
94
+ }
95
+ }
96
+ "swipeUp" -> {
97
+ val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
98
+ override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
99
+ if (abs(velocityY) > abs(velocityX) && velocityY < 0) {
100
+ val payload = mapOf("direction" to "up")
101
+ handler(payload)
102
+ }
103
+ return true
104
+ }
105
+ })
106
+ view.setOnTouchListener { _, motionEvent ->
107
+ gestureDetector.onTouchEvent(motionEvent)
108
+ false
109
+ }
110
+ }
111
+ "swipeDown" -> {
112
+ val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
113
+ override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
114
+ if (abs(velocityY) > abs(velocityX) && velocityY > 0) {
115
+ val payload = mapOf("direction" to "down")
116
+ handler(payload)
117
+ }
118
+ return true
119
+ }
120
+ })
121
+ view.setOnTouchListener { _, motionEvent ->
122
+ gestureDetector.onTouchEvent(motionEvent)
123
+ false
124
+ }
125
+ }
126
+ "pinch" -> {
127
+ val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
128
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
129
+ val payload = mapOf(
130
+ "scale" to detector.scaleFactor,
131
+ "velocity" to detector.currentSpan,
132
+ "state" to "changed"
133
+ )
134
+ handler(payload)
135
+ return true
136
+ }
137
+ })
138
+ view.setOnTouchListener { _, motionEvent ->
139
+ scaleGestureDetector.onTouchEvent(motionEvent)
140
+ false
141
+ }
142
+ }
143
+ "rotate" -> {
144
+ val rotationDetector = RotationGestureDetector { rotation ->
145
+ val payload = mapOf(
146
+ "rotation" to rotation,
147
+ "state" to "changed"
148
+ )
149
+ handler(payload)
150
+ }
151
+ view.setOnTouchListener { _, motionEvent ->
152
+ rotationDetector.onTouchEvent(motionEvent)
153
+ false
154
+ }
155
+ }
31
156
  }
32
157
  }
33
158
 
@@ -35,6 +160,7 @@ class VViewFactory : NativeComponentFactory {
35
160
  when (event) {
36
161
  "press" -> view.setOnClickListener(null)
37
162
  "longPress" -> view.setOnLongClickListener(null)
163
+ else -> view.setOnTouchListener(null)
38
164
  }
39
165
  }
40
166
 
@@ -51,4 +177,55 @@ class VViewFactory : NativeComponentFactory {
51
177
  override fun removeChild(parent: View, child: View) {
52
178
  (parent as? ViewGroup)?.removeView(child)
53
179
  }
54
- }
180
+
181
+ /**
182
+ * RotationGestureDetector detectsstwo-finger rotation gestures.
183
+ * Calculates rotation angle between two touch points.
184
+ */
185
+ private class RotationGestureDetector(
186
+ private val onRotation: (Float) -> Unit
187
+ ) {
188
+ private var previousAngle: Float = 0f
189
+ private var isTracking = false
190
+
191
+ fun onTouchEvent(event: MotionEvent): Boolean {
192
+ when (event.actionMasked) {
193
+ MotionEvent.ACTION_POINTER_DOWN -> {
194
+ if (event.pointerCount == 2) {
195
+ previousAngle = calculateAngle(event)
196
+ isTracking = true
197
+ }
198
+ }
199
+ MotionEvent.ACTION_MOVE -> {
200
+ if (isTracking && event.pointerCount == 2) {
201
+ val currentAngle = calculateAngle(event)
202
+ val deltaAngle = currentAngle - previousAngle
203
+
204
+ // Normalize angle to -PI to PI range
205
+ val normalizedDelta = when {
206
+ deltaAngle > Math.PI -> deltaAngle - (2 * Math.PI).toFloat()
207
+ deltaAngle < -Math.PI -> deltaAngle + (2 * Math.PI).toFloat()
208
+ else -> deltaAngle
209
+ }
210
+
211
+ onRotation(normalizedDelta)
212
+ previousAngle = currentAngle
213
+ }
214
+ }
215
+ MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
216
+ isTracking = false
217
+ }
218
+ }
219
+ return true
220
+ }
221
+
222
+ private fun calculateAngle(event: MotionEvent): Float {
223
+ if (event.pointerCount < 2) return 0f
224
+
225
+ val dx = event.getX(1) - event.getX(0)
226
+ val dy = event.getY(1) - event.getY(0)
227
+
228
+ return atan2(dy, dx)
229
+ }
230
+ }
231
+ }
@@ -0,0 +1,57 @@
1
+ package com.vuenative.core
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.os.SystemClock
6
+
7
+ /**
8
+ * Throttles high-frequency event handlers to avoid flooding the JS bridge.
9
+ *
10
+ * When a high-frequency event (scroll, slider drag) fires many times per frame,
11
+ * each invocation becomes a bridge round-trip. This utility ensures at most one
12
+ * call per [intervalMs] milliseconds, with a trailing call to deliver the latest value.
13
+ *
14
+ * Default interval: 16ms (~60 FPS).
15
+ */
16
+ class EventThrottle(
17
+ private val intervalMs: Long = 16L,
18
+ private val handler: (Any?) -> Unit
19
+ ) {
20
+ private var lastFireTime: Long = 0
21
+ private var pendingTrailing = false
22
+ private var latestPayload: Any? = null
23
+ private val mainHandler = Handler(Looper.getMainLooper())
24
+
25
+ private val trailingRunnable = Runnable {
26
+ lastFireTime = SystemClock.uptimeMillis()
27
+ pendingTrailing = false
28
+ handler(latestPayload)
29
+ }
30
+
31
+ /**
32
+ * Call this from the native event callback instead of the original handler.
33
+ * Fires immediately if enough time has elapsed, otherwise schedules a trailing call.
34
+ */
35
+ fun fire(payload: Any?) {
36
+ val now = SystemClock.uptimeMillis()
37
+ val elapsed = now - lastFireTime
38
+ latestPayload = payload
39
+
40
+ if (elapsed >= intervalMs) {
41
+ lastFireTime = now
42
+ pendingTrailing = false
43
+ mainHandler.removeCallbacks(trailingRunnable)
44
+ handler(payload)
45
+ } else if (!pendingTrailing) {
46
+ pendingTrailing = true
47
+ mainHandler.postDelayed(trailingRunnable, intervalMs - elapsed)
48
+ }
49
+ // If trailing is already pending, latestPayload is updated above
50
+ }
51
+
52
+ /** Cancel any pending trailing call. */
53
+ fun cancel() {
54
+ mainHandler.removeCallbacks(trailingRunnable)
55
+ pendingTrailing = false
56
+ }
57
+ }
@@ -0,0 +1,28 @@
1
+ // ────────────────────────────────────────────────────────────────────────────────
2
+ // Auto-Generated Module Registration
3
+ // ────────────────────────────────────────────────────────────────────────────────
4
+ //
5
+ // ⚠️ WARNING: This file is auto-generated. DO NOT EDIT MANUALLY.
6
+ // Changes will be overwritten by the vue-native-codegen tool.
7
+ //
8
+ // Generated: {{GENERATED_DATE}}
9
+ //
10
+ // This file registers all native modules generated from <native> blocks
11
+ // in Vue SFC files.
12
+ //
13
+ // ────────────────────────────────────────────────────────────────────────────────
14
+
15
+ package com.vuenative.core
16
+
17
+ /**
18
+ * Register all generated native modules from <native> blocks
19
+ * This function is called automatically after registerDefaults()
20
+ */
21
+ fun NativeModuleRegistry.registerGeneratedModules() {
22
+ // Generated modules will be registered here
23
+ // Example:
24
+ // register(HapticsModule())
25
+ // register(CameraModule())
26
+
27
+ // This function is called automatically after registerDefaults()
28
+ }
@@ -58,6 +58,9 @@ class NativeModuleRegistry private constructor(private val context: Context) {
58
58
  register(m)
59
59
  m.initialize(ctx, bridge)
60
60
  }
61
+
62
+ // Register generated modules from <native> blocks
63
+ registerGeneratedModules()
61
64
  }
62
65
 
63
66
  fun getModule(name: String): NativeModule? = modules[name]