basalt-core 0.0.0-rc5

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/basalt.d.ts +3 -0
  3. package/dist/cli/basalt.d.ts.map +1 -0
  4. package/dist/cli/basalt.js +47 -0
  5. package/dist/cli/basalt.js.map +1 -0
  6. package/dist/cli/bundle-cli.d.ts +12 -0
  7. package/dist/cli/bundle-cli.d.ts.map +1 -0
  8. package/dist/cli/bundle-cli.js +124 -0
  9. package/dist/cli/bundle-cli.js.map +1 -0
  10. package/dist/cli/bundleWithAssets.d.ts +79 -0
  11. package/dist/cli/bundleWithAssets.d.ts.map +1 -0
  12. package/dist/cli/bundleWithAssets.js +198 -0
  13. package/dist/cli/bundleWithAssets.js.map +1 -0
  14. package/dist/cli/desktop.d.ts +339 -0
  15. package/dist/cli/desktop.d.ts.map +1 -0
  16. package/dist/cli/desktop.js +1029 -0
  17. package/dist/cli/desktop.js.map +1 -0
  18. package/dist/cli/doctor.d.ts +31 -0
  19. package/dist/cli/doctor.d.ts.map +1 -0
  20. package/dist/cli/doctor.js +283 -0
  21. package/dist/cli/doctor.js.map +1 -0
  22. package/dist/cli/init.d.ts +77 -0
  23. package/dist/cli/init.d.ts.map +1 -0
  24. package/dist/cli/init.js +442 -0
  25. package/dist/cli/init.js.map +1 -0
  26. package/dist/cli/metro.d.ts +49 -0
  27. package/dist/cli/metro.d.ts.map +1 -0
  28. package/dist/cli/metro.js +148 -0
  29. package/dist/cli/metro.js.map +1 -0
  30. package/dist/cli/packageApp.d.ts +102 -0
  31. package/dist/cli/packageApp.d.ts.map +1 -0
  32. package/dist/cli/packageApp.js +319 -0
  33. package/dist/cli/packageApp.js.map +1 -0
  34. package/dist/cli/steps.d.ts +34 -0
  35. package/dist/cli/steps.d.ts.map +1 -0
  36. package/dist/cli/steps.js +53 -0
  37. package/dist/cli/steps.js.map +1 -0
  38. package/dist/metro-config.d.ts +168 -0
  39. package/dist/metro-config.d.ts.map +1 -0
  40. package/dist/metro-config.js +671 -0
  41. package/dist/metro-config.js.map +1 -0
  42. package/dist/react-native.config.d.ts +7 -0
  43. package/dist/react-native.config.d.ts.map +1 -0
  44. package/dist/react-native.config.js +80 -0
  45. package/dist/react-native.config.js.map +1 -0
  46. package/dist/src/DragSource.d.ts +52 -0
  47. package/dist/src/DragSource.d.ts.map +1 -0
  48. package/dist/src/DragSource.js +97 -0
  49. package/dist/src/DragSource.js.map +1 -0
  50. package/dist/src/DropTarget.d.ts +58 -0
  51. package/dist/src/DropTarget.d.ts.map +1 -0
  52. package/dist/src/DropTarget.js +131 -0
  53. package/dist/src/DropTarget.js.map +1 -0
  54. package/dist/src/KeyHandler.d.ts +57 -0
  55. package/dist/src/KeyHandler.d.ts.map +1 -0
  56. package/dist/src/KeyHandler.js +48 -0
  57. package/dist/src/KeyHandler.js.map +1 -0
  58. package/dist/src/Menu.d.ts +72 -0
  59. package/dist/src/Menu.d.ts.map +1 -0
  60. package/dist/src/Menu.js +179 -0
  61. package/dist/src/Menu.js.map +1 -0
  62. package/dist/src/TitleBar.d.ts +75 -0
  63. package/dist/src/TitleBar.d.ts.map +1 -0
  64. package/dist/src/TitleBar.js +236 -0
  65. package/dist/src/TitleBar.js.map +1 -0
  66. package/dist/src/Window.d.ts +87 -0
  67. package/dist/src/Window.d.ts.map +1 -0
  68. package/dist/src/Window.js +248 -0
  69. package/dist/src/Window.js.map +1 -0
  70. package/dist/src/closeRequest.d.ts +55 -0
  71. package/dist/src/closeRequest.d.ts.map +1 -0
  72. package/dist/src/closeRequest.js +134 -0
  73. package/dist/src/closeRequest.js.map +1 -0
  74. package/dist/src/index.d.ts +45 -0
  75. package/dist/src/index.d.ts.map +1 -0
  76. package/dist/src/index.js +92 -0
  77. package/dist/src/index.js.map +1 -0
  78. package/dist/src/overrides/Alert.d.ts +69 -0
  79. package/dist/src/overrides/Alert.d.ts.map +1 -0
  80. package/dist/src/overrides/Alert.js +112 -0
  81. package/dist/src/overrides/Alert.js.map +1 -0
  82. package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
  83. package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
  84. package/dist/src/overrides/BaseViewConfig.js +72 -0
  85. package/dist/src/overrides/BaseViewConfig.js.map +1 -0
  86. package/dist/src/overrides/Platform.linux.d.ts +10 -0
  87. package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
  88. package/dist/src/overrides/Platform.linux.js +15 -0
  89. package/dist/src/overrides/Platform.linux.js.map +1 -0
  90. package/dist/src/overrides/Platform.macos.d.ts +10 -0
  91. package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
  92. package/dist/src/overrides/Platform.macos.js +15 -0
  93. package/dist/src/overrides/Platform.macos.js.map +1 -0
  94. package/dist/src/overrides/Platform.windows.d.ts +10 -0
  95. package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
  96. package/dist/src/overrides/Platform.windows.js +15 -0
  97. package/dist/src/overrides/Platform.windows.js.map +1 -0
  98. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
  99. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
  100. package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
  101. package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
  102. package/dist/src/overrides/RefreshControl.d.ts +52 -0
  103. package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
  104. package/dist/src/overrides/RefreshControl.js +92 -0
  105. package/dist/src/overrides/RefreshControl.js.map +1 -0
  106. package/dist/src/overrides/ScrollView.d.ts +39 -0
  107. package/dist/src/overrides/ScrollView.d.ts.map +1 -0
  108. package/dist/src/overrides/ScrollView.js +59 -0
  109. package/dist/src/overrides/ScrollView.js.map +1 -0
  110. package/dist/src/overrides/Share.d.ts +96 -0
  111. package/dist/src/overrides/Share.d.ts.map +1 -0
  112. package/dist/src/overrides/Share.js +97 -0
  113. package/dist/src/overrides/Share.js.map +1 -0
  114. package/dist/src/overrides/TextInput.d.ts +81 -0
  115. package/dist/src/overrides/TextInput.d.ts.map +1 -0
  116. package/dist/src/overrides/TextInput.js +213 -0
  117. package/dist/src/overrides/TextInput.js.map +1 -0
  118. package/dist/src/overrides/View.d.ts +36 -0
  119. package/dist/src/overrides/View.d.ts.map +1 -0
  120. package/dist/src/overrides/View.js +99 -0
  121. package/dist/src/overrides/View.js.map +1 -0
  122. package/dist/src/overrides/createPlatform.d.ts +32 -0
  123. package/dist/src/overrides/createPlatform.d.ts.map +1 -0
  124. package/dist/src/overrides/createPlatform.js +84 -0
  125. package/dist/src/overrides/createPlatform.js.map +1 -0
  126. package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
  127. package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
  128. package/dist/src/overrides/setUpDeveloperTools.js +84 -0
  129. package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
  130. package/dist/src/overrides/setUpXHR.d.ts +46 -0
  131. package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
  132. package/dist/src/overrides/setUpXHR.js +236 -0
  133. package/dist/src/overrides/setUpXHR.js.map +1 -0
  134. package/dist/src/quitRequest.d.ts +55 -0
  135. package/dist/src/quitRequest.d.ts.map +1 -0
  136. package/dist/src/quitRequest.js +121 -0
  137. package/dist/src/quitRequest.js.map +1 -0
  138. package/dist/src/resourcePath.d.ts +18 -0
  139. package/dist/src/resourcePath.d.ts.map +1 -0
  140. package/dist/src/resourcePath.js +31 -0
  141. package/dist/src/resourcePath.js.map +1 -0
  142. package/dist/src/responseBody.d.ts +76 -0
  143. package/dist/src/responseBody.d.ts.map +1 -0
  144. package/dist/src/responseBody.js +94 -0
  145. package/dist/src/responseBody.js.map +1 -0
  146. package/dist/src/responseHeaders.d.ts +40 -0
  147. package/dist/src/responseHeaders.d.ts.map +1 -0
  148. package/dist/src/responseHeaders.js +75 -0
  149. package/dist/src/responseHeaders.js.map +1 -0
  150. package/dist/src/titleBarState.d.ts +43 -0
  151. package/dist/src/titleBarState.d.ts.map +1 -0
  152. package/dist/src/titleBarState.js +77 -0
  153. package/dist/src/titleBarState.js.map +1 -0
  154. package/dist/src/useContextMenu.d.ts +113 -0
  155. package/dist/src/useContextMenu.d.ts.map +1 -0
  156. package/dist/src/useContextMenu.js +109 -0
  157. package/dist/src/useContextMenu.js.map +1 -0
  158. package/dist/src/useDialog.d.ts +98 -0
  159. package/dist/src/useDialog.d.ts.map +1 -0
  160. package/dist/src/useDialog.js +112 -0
  161. package/dist/src/useDialog.js.map +1 -0
  162. package/dist/src/useDisplays.d.ts +70 -0
  163. package/dist/src/useDisplays.d.ts.map +1 -0
  164. package/dist/src/useDisplays.js +118 -0
  165. package/dist/src/useDisplays.js.map +1 -0
  166. package/dist/src/useHandledKeys.d.ts +38 -0
  167. package/dist/src/useHandledKeys.d.ts.map +1 -0
  168. package/dist/src/useHandledKeys.js +70 -0
  169. package/dist/src/useHandledKeys.js.map +1 -0
  170. package/dist/src/useWindow.d.ts +140 -0
  171. package/dist/src/useWindow.d.ts.map +1 -0
  172. package/dist/src/useWindow.js +233 -0
  173. package/dist/src/useWindow.js.map +1 -0
  174. package/dist/tsconfig.tsbuildinfo +1 -0
  175. package/native/CMakeLists.txt +355 -0
  176. package/native/bootstrap.sh +613 -0
  177. package/native/cmake/CompilerFlags.cmake +49 -0
  178. package/native/cmake/Expo.cmake +125 -0
  179. package/native/cmake/ReactNativeCore.cmake +612 -0
  180. package/native/cmake/Reanimated.cmake +108 -0
  181. package/native/cmake/Skia.cmake +379 -0
  182. package/native/cmake/ThirdParty.cmake +290 -0
  183. package/native/cmake/Worklets.cmake +67 -0
  184. package/native/cmake/codegen/CMakeLists.txt +32 -0
  185. package/native/core/AppIdentity.cpp +85 -0
  186. package/native/core/AppIdentity.h +59 -0
  187. package/native/core/AppPaths.cpp +96 -0
  188. package/native/core/AppPaths.h +53 -0
  189. package/native/core/AppearanceModule.cpp +60 -0
  190. package/native/core/AppearanceModule.h +40 -0
  191. package/native/core/Backface.h +59 -0
  192. package/native/core/BlobModule.cpp +283 -0
  193. package/native/core/BlobModule.h +76 -0
  194. package/native/core/BlobRegistry.cpp +75 -0
  195. package/native/core/BlobRegistry.h +44 -0
  196. package/native/core/ColorScheme.cpp +105 -0
  197. package/native/core/ColorScheme.h +59 -0
  198. package/native/core/ComponentRegistry.h +30 -0
  199. package/native/core/ControlMetrics.h +37 -0
  200. package/native/core/CoreModules.cpp +592 -0
  201. package/native/core/CoreModules.h +308 -0
  202. package/native/core/DebuggingOverlay.cpp +106 -0
  203. package/native/core/DebuggingOverlay.h +65 -0
  204. package/native/core/DesktopControls.cpp +232 -0
  205. package/native/core/DesktopControls.h +170 -0
  206. package/native/core/DevBundle.cpp +108 -0
  207. package/native/core/DevBundle.h +69 -0
  208. package/native/core/DevMenu.cpp +99 -0
  209. package/native/core/DevMenu.h +75 -0
  210. package/native/core/DialogModule.cpp +150 -0
  211. package/native/core/DialogModule.h +51 -0
  212. package/native/core/DragAndDrop.cpp +45 -0
  213. package/native/core/DragAndDrop.h +225 -0
  214. package/native/core/ExpoImageComponent.cpp +79 -0
  215. package/native/core/ExpoImageComponent.h +80 -0
  216. package/native/core/ExpoModules.cpp +415 -0
  217. package/native/core/ExpoModules.h +115 -0
  218. package/native/core/ExpoRuntime.cpp +366 -0
  219. package/native/core/ExpoRuntime.h +42 -0
  220. package/native/core/FocusRing.h +36 -0
  221. package/native/core/FontRegistry.h +51 -0
  222. package/native/core/GestureHandlerModule.cpp +136 -0
  223. package/native/core/GestureHandlerModule.h +71 -0
  224. package/native/core/Gestures.cpp +533 -0
  225. package/native/core/Gestures.h +145 -0
  226. package/native/core/HoverTracker.cpp +69 -0
  227. package/native/core/HoverTracker.h +109 -0
  228. package/native/core/HttpClient.cpp +282 -0
  229. package/native/core/ImageBytes.cpp +205 -0
  230. package/native/core/ImageBytes.h +29 -0
  231. package/native/core/ImageCache.cpp +51 -0
  232. package/native/core/ImageCache.h +89 -0
  233. package/native/core/JsRuntimeAccess.cpp +70 -0
  234. package/native/core/JsRuntimeAccess.h +60 -0
  235. package/native/core/JsiPromise.h +51 -0
  236. package/native/core/KeyEvents.cpp +116 -0
  237. package/native/core/KeyEvents.h +144 -0
  238. package/native/core/LogBoxSurface.cpp +46 -0
  239. package/native/core/LogBoxSurface.h +68 -0
  240. package/native/core/MenuModel.h +117 -0
  241. package/native/core/MenuModule.cpp +209 -0
  242. package/native/core/MenuModule.h +66 -0
  243. package/native/core/MountingWalk.h +524 -0
  244. package/native/core/PackageModules.cpp.in +25 -0
  245. package/native/core/PackageModules.h +50 -0
  246. package/native/core/PlatformConstantsModule.cpp +63 -0
  247. package/native/core/PlatformConstantsModule.h +64 -0
  248. package/native/core/PlatformServices.h +213 -0
  249. package/native/core/PointerButtons.h +63 -0
  250. package/native/core/PullToRefresh.cpp +57 -0
  251. package/native/core/PullToRefresh.h +73 -0
  252. package/native/core/ReanimatedCompat.h +47 -0
  253. package/native/core/ReanimatedModule.cpp +203 -0
  254. package/native/core/ReanimatedModule.h +55 -0
  255. package/native/core/ScrollAnimation.cpp +59 -0
  256. package/native/core/ScrollAnimation.h +70 -0
  257. package/native/core/ScrollBounds.cpp +26 -0
  258. package/native/core/ScrollBounds.h +68 -0
  259. package/native/core/ScrollIndicator.cpp +50 -0
  260. package/native/core/ScrollIndicator.h +69 -0
  261. package/native/core/ScrollMomentum.cpp +71 -0
  262. package/native/core/ScrollMomentum.h +73 -0
  263. package/native/core/ScrollSnap.cpp +123 -0
  264. package/native/core/ScrollSnap.h +80 -0
  265. package/native/core/ShareFallback.cpp +101 -0
  266. package/native/core/ShareFallback.h +45 -0
  267. package/native/core/SkiaPictureViewComponent.cpp +11 -0
  268. package/native/core/SkiaPictureViewComponent.h +47 -0
  269. package/native/core/SourceCodeModule.cpp +55 -0
  270. package/native/core/SourceCodeModule.h +53 -0
  271. package/native/core/StatusBarModule.cpp +23 -0
  272. package/native/core/StatusBarModule.h +42 -0
  273. package/native/core/TestDialog.cpp +148 -0
  274. package/native/core/TestDialog.h +96 -0
  275. package/native/core/TestKeys.cpp +78 -0
  276. package/native/core/TestKeys.h +50 -0
  277. package/native/core/TestQuitFile.cpp +33 -0
  278. package/native/core/TestQuitFile.h +60 -0
  279. package/native/core/TestSettle.h +192 -0
  280. package/native/core/TitleBarRegions.h +31 -0
  281. package/native/core/TurboModuleProxy.cpp +51 -0
  282. package/native/core/TurboModuleProxy.h +46 -0
  283. package/native/core/UIManagerAccess.cpp +43 -0
  284. package/native/core/UIManagerAccess.h +43 -0
  285. package/native/core/WindowBoundsCache.cpp +319 -0
  286. package/native/core/WindowControl.h +235 -0
  287. package/native/core/WindowHost.h +158 -0
  288. package/native/core/WindowMethods.cpp +167 -0
  289. package/native/core/WindowMethods.h +84 -0
  290. package/native/core/WindowsModule.cpp +448 -0
  291. package/native/core/WindowsModule.h +133 -0
  292. package/native/core/WorkletsModule.cpp +199 -0
  293. package/native/core/WorkletsModule.h +57 -0
  294. package/native/core/portability_probe.cpp +210 -0
  295. package/native/tests/EventRecorder.h +124 -0
  296. package/native/tests/TestHarness.cpp +109 -0
  297. package/native/tests/TestHarness.h +89 -0
  298. package/native/tests/TreeDump.h +49 -0
  299. package/native/tests/test_app_identity.cpp +114 -0
  300. package/native/tests/test_app_paths.cpp +48 -0
  301. package/native/tests/test_appearance.cpp +107 -0
  302. package/native/tests/test_backface.cpp +79 -0
  303. package/native/tests/test_blobs.cpp +157 -0
  304. package/native/tests/test_controls.cpp +185 -0
  305. package/native/tests/test_devbundle.cpp +103 -0
  306. package/native/tests/test_drag_and_drop.cpp +143 -0
  307. package/native/tests/test_gestures.cpp +331 -0
  308. package/native/tests/test_hover.cpp +92 -0
  309. package/native/tests/test_image_cache.cpp +109 -0
  310. package/native/tests/test_js_runtime_access.cpp +137 -0
  311. package/native/tests/test_key_events.cpp +235 -0
  312. package/native/tests/test_momentum.cpp +138 -0
  313. package/native/tests/test_quit_file.cpp +104 -0
  314. package/native/tests/test_scroll_animation.cpp +120 -0
  315. package/native/tests/test_scroll_bounds.cpp +66 -0
  316. package/native/tests/test_scroll_indicator.cpp +149 -0
  317. package/native/tests/test_scroll_snap.cpp +112 -0
  318. package/native/tests/test_settle.cpp +115 -0
  319. package/native/tests/test_share.cpp +75 -0
  320. package/native/tests/test_sourcecode.cpp +62 -0
  321. package/native/tests/test_window_close.cpp +119 -0
  322. package/native/tests/test_window_limits.cpp +123 -0
  323. package/package.json +43 -0
  324. package/react-native.config.js +86 -0
  325. package/supported-versions.json +66 -0
@@ -0,0 +1,524 @@
1
+ // The mutation walk, with no toolkit in it.
2
+ //
3
+ // `IMountingManager` has two pure-virtual methods, and one of them --
4
+ // `executeMount` -- is where a platform is most likely to be wrong on its own.
5
+ // Fabric's mutation stream has rules that are not obvious from the interface:
6
+ // a Create does not attach, a Remove does not destroy, an Insert's index counts
7
+ // positions in the parent's *final* child list, virtual views have no peer at
8
+ // all, and a mutation naming a tag that no longer exists has to be survivable
9
+ // rather than fatal.
10
+ //
11
+ // None of that is about GTK or AppKit. Writing it twice means two chances to get
12
+ // it subtly different, and a difference here shows up as a layout that is wrong
13
+ // on one desktop and right on another -- which is the exact failure this project
14
+ // exists to avoid. So it is written once, here, and each platform supplies the
15
+ // half-dozen operations that do touch a view.
16
+ //
17
+ // CRTP rather than virtual dispatch: the view type differs per platform
18
+ // (`RnView *` under GObject, `RnAppKitView *` under ARC) and a template keeps it
19
+ // concrete in each translation unit, with no type erasure and no indirection on
20
+ // a path that runs for every mutation of every frame.
21
+ //
22
+ // A platform provides:
23
+ //
24
+ // using ViewRef = ...; // a pointer, nullable
25
+ // ViewRef createView(const ShadowView &); // owned; props not yet applied
26
+ // ViewRef createRootView(Tag); // owned; a surface's root
27
+ // void destroyView(ViewRef); // release the owned reference
28
+ // void insertChild(ViewRef parent, ViewRef child, int index);
29
+ // void removeChild(ViewRef parent, ViewRef child);
30
+ // void updateView(ViewRef, const ShadowView &); // props, layout, state
31
+ // void forgetTag(Tag); // per-tag side tables, if any
32
+ // void applyControlPeer(ViewRef, const ControlState &); // spinner/switch
33
+ // void setHighlights(ViewRef, const std::vector<Highlight> &); // DevTools
34
+ //
35
+ // and must call `releaseAllViews()` from its own destructor: this base cannot,
36
+ // because by the time a base destructor runs the platform half is already gone.
37
+
38
+ #pragma once
39
+
40
+ #include "DebuggingOverlay.h"
41
+ #include "DesktopControls.h"
42
+ #include "HoverTracker.h"
43
+ #include "TestSettle.h"
44
+ #include "PlatformServices.h"
45
+ #include "PullToRefresh.h"
46
+
47
+ #include <react/renderer/components/view/ViewProps.h>
48
+ #include <react/renderer/core/EventEmitter.h>
49
+ #include <react/renderer/mounting/ShadowViewMutation.h>
50
+
51
+ #include <glog/logging.h>
52
+
53
+ #include <algorithm>
54
+ #include <cassert>
55
+ #include <cstdint>
56
+ #include <memory>
57
+ #include <string_view>
58
+ #include <thread>
59
+ #include <unordered_map>
60
+ #include <unordered_set>
61
+ #include <vector>
62
+
63
+ namespace basalt {
64
+
65
+ template <typename Platform, typename ViewRefT>
66
+ class MountingWalk {
67
+ public:
68
+ using ViewRef = ViewRefT;
69
+ using Tag = facebook::react::Tag;
70
+ using SurfaceId = facebook::react::SurfaceId;
71
+ using ShadowView = facebook::react::ShadowView;
72
+
73
+ MountingWalk() : mainThreadId_(std::this_thread::get_id()) {}
74
+
75
+ // The mounted view for a tag, or null. Not `viewForTag` on purpose: platform
76
+ // code reads better saying what kind of thing it wants back.
77
+ ViewRef viewForTag(Tag tag) const {
78
+ const auto it = registry_.find(tag);
79
+ return it == registry_.end() ? ViewRef{} : it->second;
80
+ }
81
+
82
+ // The event emitter for a mounted view, or null.
83
+ //
84
+ // Kept per tag rather than on the view because a view holds no React Native
85
+ // types, and because a view can sit detached between a Remove and its Delete
86
+ // while still needing to deliver a cancel.
87
+ facebook::react::EventEmitter::Shared eventEmitterForTag(Tag tag) const {
88
+ const auto it = eventEmitters_.find(tag);
89
+ return it == eventEmitters_.end() ? nullptr : it->second;
90
+ }
91
+
92
+ // Which hover events this view listens for, as a HoverListener mask.
93
+ //
94
+ // Recorded here, next to the emitter, for the same reason: it comes off the
95
+ // props on every Create and Update, and a touch dispatcher asking per motion
96
+ // event must not have to reach into the shadow tree to find it. Views that
97
+ // listen for none -- nearly all of them -- are not stored at all.
98
+ // --- Controls -------------------------------------------------------------
99
+ //
100
+ // <Switch>, <ActivityIndicator>, <RefreshControl> and <Modal> are the four
101
+ // components that are more than a box, and every part of them that is not
102
+ // pixels is here rather than in three view layers: which views are controls,
103
+ // which scroll view a refresh control belongs to, which modals are open, and
104
+ // what size a modal thinks the window is. Only `applyControlPeer` -- making a
105
+ // GtkSwitch or drawing one -- is left to the platform.
106
+
107
+ // The pull past the top of a list. The platform's scroll view feeds it and
108
+ // calls `fireRefresh` when it says so; see core/PullToRefresh.h.
109
+ PullToRefreshTracker &pullToRefresh() {
110
+ return pullToRefresh_;
111
+ }
112
+
113
+ // Tells the <RefreshControl> belonging to `scrollTag` that it was pulled.
114
+ // Safe on a scroll view that has none.
115
+ void fireRefresh(Tag scrollTag) {
116
+ const Tag control = pullToRefresh_.controlFor(scrollTag);
117
+ if (control != 0) {
118
+ emitRefresh(eventEmitterForTag(control));
119
+ }
120
+ }
121
+
122
+ // React DevTools' overlay commands, which every host answers the same way.
123
+ //
124
+ // Returns false for anything else, so a platform's `applyCommand` can try
125
+ // this first and carry on. The rectangles are parsed in
126
+ // core/DebuggingOverlay.h; what a platform supplies is the drawing.
127
+ bool applyOverlayCommand(Tag tag, const std::string &name, const folly::dynamic &args) {
128
+ ViewRef view = viewForTag(tag);
129
+ if (view == ViewRef{}) {
130
+ return false;
131
+ }
132
+
133
+ if (name == "clearElementsHighlights") {
134
+ platform().setHighlights(view, {});
135
+ return true;
136
+ }
137
+ if (name == "highlightElements") {
138
+ platform().setHighlights(view, parseElementHighlights(args));
139
+ return true;
140
+ }
141
+ if (name != "highlightTraceUpdates") {
142
+ return false;
143
+ }
144
+
145
+ platform().setHighlights(view, parseTraceUpdates(args));
146
+
147
+ // A trace update flashes: React Native's own overlay clears them rather
148
+ // than waiting to be told, because a box left behind after a component
149
+ // stopped re-rendering says the opposite of what it means.
150
+ //
151
+ // The generation is what keeps a stale timer from clearing a *newer* set:
152
+ // these arrive many times a second while the DevTools option is on, so
153
+ // there is almost always more than one in flight.
154
+ const std::uint64_t generation = ++overlayGeneration_;
155
+ postDelayed(kTraceUpdateLifetimeMs, [this, alive = alive_, tag, generation] {
156
+ if (alive.use_count() == 1 || overlayGeneration_ != generation) {
157
+ return;
158
+ }
159
+ ViewRef target = viewForTag(tag);
160
+ if (target != ViewRef{}) {
161
+ platform().setHighlights(target, {});
162
+ }
163
+ });
164
+ return true;
165
+ }
166
+
167
+ // The user asked to close the topmost <Modal> -- Escape, on all three
168
+ // desktops. Returns false when no modal is open, so the host can let the key
169
+ // do whatever it did before.
170
+ bool requestCloseTopModal() {
171
+ if (modalStack_.empty()) {
172
+ return false;
173
+ }
174
+ emitModalRequestClose(eventEmitterForTag(modalStack_.back()));
175
+ return true;
176
+ }
177
+
178
+ bool hasOpenModal() const {
179
+ return !modalStack_.empty();
180
+ }
181
+
182
+ // The size of the window a surface is in, which a <Modal> lays out against.
183
+ //
184
+ // React Native's own cxx platform answers `ModalHostViewScreenSize()` with
185
+ // zero, so a modal that is never told otherwise lays out 0x0 and the app
186
+ // renders nothing at all -- no error, no warning, an empty window. The host
187
+ // calls this wherever it already updates its layout constraints.
188
+ void setSurfaceSize(float width, float height) {
189
+ if (width == surfaceWidth_ && height == surfaceHeight_) {
190
+ return;
191
+ }
192
+ surfaceWidth_ = width;
193
+ surfaceHeight_ = height;
194
+ for (const auto &[tag, shadowView] : modalViews_) {
195
+ (void)tag;
196
+ if (modalScreenSizeNeedsUpdate(shadowView, surfaceWidth_, surfaceHeight_)) {
197
+ updateModalScreenSize(shadowView, surfaceWidth_, surfaceHeight_);
198
+ }
199
+ }
200
+ }
201
+
202
+ std::uint16_t hoverListenersForTag(Tag tag) const {
203
+ const auto it = hoverListeners_.find(tag);
204
+ return it == hoverListeners_.end() ? HoverListenerNone : it->second;
205
+ }
206
+
207
+ // Fabric emits no Create for a surface's root: the root shadow node is the
208
+ // base of every diff, so it must already exist when the first transaction
209
+ // arrives. The host makes one before `ReactHost::startSurface` and parents it
210
+ // into a window. In Fabric a SurfaceId *is* the root node's tag, which is what
211
+ // lets the root take part in the registry like any other view.
212
+ ViewRef createSurfaceRoot(SurfaceId surfaceId) {
213
+ const Tag rootTag = static_cast<Tag>(surfaceId);
214
+ if (const auto it = registry_.find(rootTag); it != registry_.end()) {
215
+ return it->second;
216
+ }
217
+ ViewRef root = platform().createRootView(rootTag);
218
+ registry_[rootTag] = root;
219
+ return root;
220
+ }
221
+
222
+ void destroySurfaceRoot(SurfaceId surfaceId) {
223
+ const Tag rootTag = static_cast<Tag>(surfaceId);
224
+ if (const auto it = registry_.find(rootTag); it != registry_.end()) {
225
+ platform().destroyView(it->second);
226
+ registry_.erase(it);
227
+ }
228
+ }
229
+
230
+ ViewRef getSurfaceRoot(SurfaceId surfaceId) const {
231
+ return viewForTag(static_cast<Tag>(surfaceId));
232
+ }
233
+
234
+ // Applies one transaction's mutations, in order, on the platform's main
235
+ // thread. Read off `StubViewTree::mutate`, React Native's own reference walk.
236
+ void applyMutations(const facebook::react::ShadowViewMutationList &mutations) {
237
+ assert(std::this_thread::get_id() == mainThreadId_ &&
238
+ "mutations must be applied on the platform's main thread");
239
+
240
+ // That React has produced a tree and a host has put it on screen. The one
241
+ // place all three mount through, which is why BASALT_QUIT_WHEN_SETTLED needs
242
+ // this in one file rather than in three. Only the first is kept; see
243
+ // core/TestSettle.h.
244
+ noteMountApplied();
245
+
246
+ for (const auto &mutation : mutations) {
247
+ switch (mutation.type) {
248
+ case facebook::react::ShadowViewMutation::Create:
249
+ create(mutation.newChildShadowView);
250
+ break;
251
+ case facebook::react::ShadowViewMutation::Delete:
252
+ destroy(mutation.oldChildShadowView.tag);
253
+ break;
254
+ case facebook::react::ShadowViewMutation::Insert:
255
+ insert(mutation);
256
+ break;
257
+ case facebook::react::ShadowViewMutation::Remove:
258
+ remove(mutation);
259
+ break;
260
+ case facebook::react::ShadowViewMutation::Update:
261
+ update(mutation.newChildShadowView);
262
+ break;
263
+ }
264
+ }
265
+
266
+ // After the whole transaction rather than at each Insert.
267
+ //
268
+ // Fabric inserts a subtree from the bottom up: a <RefreshControl> is put
269
+ // inside the scroll view's content view before that content view is put
270
+ // inside the scroll view, so at the moment the control arrives its
271
+ // grandparent is not known yet and walking up finds nothing. The tree is
272
+ // whole here.
273
+ resolveRefreshControls();
274
+ }
275
+
276
+ protected:
277
+ // Not virtual and not public: this is a mixin, never a base pointer.
278
+ ~MountingWalk() = default;
279
+
280
+ // Platforms call this from their own destructor. See the note at the top.
281
+ void releaseAllViews() {
282
+ for (auto &[tag, view] : registry_) {
283
+ (void)tag;
284
+ platform().destroyView(view);
285
+ }
286
+ registry_.clear();
287
+ eventEmitters_.clear();
288
+ componentNames_.clear();
289
+ hoverListeners_.clear();
290
+ parentOf_.clear();
291
+ scrollTags_.clear();
292
+ modalViews_.clear();
293
+ modalStack_.clear();
294
+ }
295
+
296
+ void rememberEventEmitter(const ShadowView &shadowView) {
297
+ if (shadowView.eventEmitter != nullptr) {
298
+ eventEmitters_[shadowView.tag] = shadowView.eventEmitter;
299
+ }
300
+ if (shadowView.componentName != nullptr) {
301
+ componentNames_[shadowView.tag] = shadowView.componentName;
302
+ }
303
+ rememberHoverListeners(shadowView);
304
+ }
305
+
306
+ // An Update can take listeners away as well as add them, so a view that stops
307
+ // listening is erased rather than left with its old mask.
308
+ void rememberHoverListeners(const ShadowView &shadowView) {
309
+ const auto *viewProps = dynamic_cast<const facebook::react::ViewProps *>(shadowView.props.get());
310
+ const std::uint16_t mask =
311
+ viewProps == nullptr ? HoverListenerNone : hoverListenersFrom(viewProps->events);
312
+ if (mask == HoverListenerNone) {
313
+ hoverListeners_.erase(shadowView.tag);
314
+ } else {
315
+ hoverListeners_[shadowView.tag] = mask;
316
+ }
317
+ }
318
+
319
+ bool onMainThread() const {
320
+ return std::this_thread::get_id() == mainThreadId_;
321
+ }
322
+
323
+ // The portable half of mounting a control, called from the platform's
324
+ // `updateView`. Reads the props once, hands the platform the result, and
325
+ // keeps the bookkeeping the events need.
326
+ //
327
+ // Called from `updateView` rather than from the walk so that a platform
328
+ // applies it in its own order -- after layout on every host, because a
329
+ // spinner is centred in a frame that has to exist first.
330
+ void applyControls(ViewRef view, const ShadowView &shadowView) {
331
+ const ControlState state = controlStateOf(shadowView);
332
+ if (state.kind != ControlKind::None) {
333
+ platform().applyControlPeer(view, state);
334
+ return;
335
+ }
336
+
337
+ if (shadowView.componentName == nullptr) {
338
+ return;
339
+ }
340
+ const std::string_view name(shadowView.componentName);
341
+ if (name == "ScrollView") {
342
+ scrollTags_.insert(shadowView.tag);
343
+ return;
344
+ }
345
+ if (name != "ModalHostView") {
346
+ return;
347
+ }
348
+
349
+ // A modal, which is where the screen size has to be corrected. The first
350
+ // sighting is also where `onShow` belongs: React Native fires it once, when
351
+ // the modal appears, and a modal appears by being mounted -- `visible` is
352
+ // false by not rendering the component at all.
353
+ const bool firstSighting = modalViews_.find(shadowView.tag) == modalViews_.end();
354
+ modalViews_[shadowView.tag] = shadowView;
355
+ if (firstSighting) {
356
+ modalStack_.push_back(shadowView.tag);
357
+ emitModalShow(shadowView.eventEmitter);
358
+ }
359
+ if (modalScreenSizeNeedsUpdate(shadowView, surfaceWidth_, surfaceHeight_)) {
360
+ updateModalScreenSize(shadowView, surfaceWidth_, surfaceHeight_);
361
+ }
362
+ }
363
+
364
+ private:
365
+ Platform &platform() {
366
+ return static_cast<Platform &>(*this);
367
+ }
368
+
369
+ // Create allocates a view but does not attach it; an Insert follows, possibly
370
+ // in a later transaction.
371
+ void create(const ShadowView &shadowView) {
372
+ ViewRef view = platform().createView(shadowView);
373
+ platform().updateView(view, shadowView);
374
+ registry_[shadowView.tag] = view;
375
+ rememberEventEmitter(shadowView);
376
+ }
377
+
378
+ void destroy(Tag tag) {
379
+ const auto it = registry_.find(tag);
380
+ if (it == registry_.end()) {
381
+ LOG(WARNING) << "Delete for unknown tag " << tag;
382
+ return;
383
+ }
384
+ platform().destroyView(it->second);
385
+ registry_.erase(it);
386
+ eventEmitters_.erase(tag);
387
+ componentNames_.erase(tag);
388
+ hoverListeners_.erase(tag);
389
+ parentOf_.erase(tag);
390
+ scrollTags_.erase(tag);
391
+ pullToRefresh_.forget(tag);
392
+ modalViews_.erase(tag);
393
+ modalStack_.erase(std::remove(modalStack_.begin(), modalStack_.end(), tag), modalStack_.end());
394
+ platform().forgetTag(tag);
395
+ }
396
+
397
+ void insert(const facebook::react::ShadowViewMutation &mutation) {
398
+ // Virtual views exist in the shadow tree only, to keep an EventEmitter
399
+ // alive. They have no peer to parent.
400
+ if (mutation.mutatedViewIsVirtual()) {
401
+ return;
402
+ }
403
+ ViewRef parent = viewForTag(mutation.parentTag);
404
+ ViewRef child = viewForTag(mutation.newChildShadowView.tag);
405
+ if (parent == ViewRef{} || child == ViewRef{}) {
406
+ // Survivable rather than fatal: this is what a bug upstream, or a
407
+ // transaction racing a surface teardown, looks like from here.
408
+ LOG(WARNING) << "Insert with unknown tag (parent " << mutation.parentTag << ", child "
409
+ << mutation.newChildShadowView.tag << ")";
410
+ return;
411
+ }
412
+ // Props can change in the same transaction that inserts the view.
413
+ platform().updateView(child, mutation.newChildShadowView);
414
+ platform().insertChild(parent, child, static_cast<int>(mutation.index));
415
+ parentOf_[mutation.newChildShadowView.tag] = mutation.parentTag;
416
+ if (controlKindFor(mutation.newChildShadowView.componentName) == ControlKind::PullToRefresh) {
417
+ unresolvedRefreshControls_.push_back(mutation.newChildShadowView.tag);
418
+ }
419
+ }
420
+
421
+ void remove(const facebook::react::ShadowViewMutation &mutation) {
422
+ if (mutation.mutatedViewIsVirtual()) {
423
+ return;
424
+ }
425
+ ViewRef parent = viewForTag(mutation.parentTag);
426
+ ViewRef child = viewForTag(mutation.oldChildShadowView.tag);
427
+ if (parent == ViewRef{} || child == ViewRef{}) {
428
+ LOG(WARNING) << "Remove with unknown tag (parent " << mutation.parentTag << ", child "
429
+ << mutation.oldChildShadowView.tag << ")";
430
+ return;
431
+ }
432
+ // Detached, not destroyed: the Delete is a separate mutation and may never
433
+ // come -- a reparent is a Remove and an Insert with no Delete between them.
434
+ platform().removeChild(parent, child);
435
+ }
436
+
437
+ void update(const ShadowView &shadowView) {
438
+ ViewRef view = viewForTag(shadowView.tag);
439
+ if (view == ViewRef{}) {
440
+ LOG(WARNING) << "Update for unknown tag " << shadowView.tag;
441
+ return;
442
+ }
443
+ platform().updateView(view, shadowView);
444
+ // A clone carries a new emitter instance; keeping the old one would deliver
445
+ // touches to a stale target.
446
+ rememberEventEmitter(shadowView);
447
+ }
448
+
449
+ // Ties each <RefreshControl> mounted in this transaction to the scroll view
450
+ // it refreshes.
451
+ //
452
+ // Not its parent: React Native's ScrollView puts the control inside the
453
+ // content container, so the scroll view is an ancestor. Walking up is also
454
+ // what makes this survive somebody wrapping the control in a <View>.
455
+ //
456
+ // A control whose scroll view is still not found is dropped rather than
457
+ // carried: it is a <RefreshControl> outside any list, which is an app bug
458
+ // and not something to keep retrying on every frame.
459
+ void resolveRefreshControls() {
460
+ for (const Tag tag : unresolvedRefreshControls_) {
461
+ for (Tag walk = tag; walk != 0;) {
462
+ const auto parent = parentOf_.find(walk);
463
+ if (parent == parentOf_.end()) {
464
+ break;
465
+ }
466
+ walk = parent->second;
467
+ if (scrollTags_.count(walk) != 0) {
468
+ pullToRefresh_.attach(walk, tag);
469
+ break;
470
+ }
471
+ }
472
+ }
473
+ unresolvedRefreshControls_.clear();
474
+ }
475
+
476
+ // Views are held with a strong reference from Create until Delete. Between a
477
+ // Remove and its Delete a view has no parent, so this is the only thing
478
+ // keeping it alive.
479
+ std::unordered_map<Tag, ViewRef> registry_;
480
+
481
+ // Parallel to registry_, and torn down with it on Delete.
482
+ std::unordered_map<Tag, facebook::react::EventEmitter::Shared> eventEmitters_;
483
+
484
+ // Every view's component name, as the static string Fabric holds. Needed
485
+ // because a Remove and an Insert carry a ShadowView and the walk's own
486
+ // bookkeeping does not.
487
+ std::unordered_map<Tag, const char *> componentNames_;
488
+
489
+ // Sparse, unlike the two above: only views that listen for a hover event
490
+ // appear, so an app that uses none carries an empty map.
491
+ std::unordered_map<Tag, std::uint16_t> hoverListeners_;
492
+
493
+ // --- Controls ---------------------------------------------------------------
494
+
495
+ // Who each view's parent is, so a <RefreshControl> can find the scroll view
496
+ // it is nested inside. Fabric's mutations carry it and nothing else here
497
+ // needed it until now.
498
+ std::unordered_map<Tag, Tag> parentOf_;
499
+ std::unordered_set<Tag> scrollTags_;
500
+ PullToRefreshTracker pullToRefresh_;
501
+ // Controls mounted in the transaction being applied, resolved at the end of
502
+ // it; see resolveRefreshControls.
503
+ std::vector<Tag> unresolvedRefreshControls_;
504
+
505
+ // Modals, and the order they opened in. A vector rather than a set: Escape
506
+ // closes the topmost one, which needs an order.
507
+ std::unordered_map<Tag, ShadowView> modalViews_;
508
+ std::vector<Tag> modalStack_;
509
+
510
+ float surfaceWidth_{0.0F};
511
+ float surfaceHeight_{0.0F};
512
+
513
+ // DevTools' trace updates, and the token that tells a timer whether this
514
+ // manager is still here. The scroll views keep the same pair for the same
515
+ // reason: a delayed callback outlives whatever scheduled it.
516
+ std::uint64_t overlayGeneration_{0};
517
+ std::shared_ptr<bool> alive_{std::make_shared<bool>(true)};
518
+
519
+ // The main thread, recorded at construction. `executeMount` arrives on the JS
520
+ // thread and marshals here; `applyMutations` asserts it got there.
521
+ std::thread::id mainThreadId_;
522
+ };
523
+
524
+ } // namespace basalt
@@ -0,0 +1,25 @@
1
+ // Generated by CMake. Do not edit; edit core/PackageModules.cpp.in.
2
+ //
3
+ // One function calling each discovered capability package's installer. Empty
4
+ // when nothing was discovered, which is why core can call it unconditionally.
5
+
6
+ #include "PackageModules.h"
7
+
8
+ namespace basalt {
9
+
10
+ @basalt_package_declarations@
11
+ void installPackageExpoModules(facebook::jsi::Runtime &runtime, facebook::jsi::Object &modules) {
12
+ (void)runtime;
13
+ (void)modules;
14
+ @basalt_package_calls@}
15
+
16
+ @basalt_package_turbo_declarations@
17
+ std::shared_ptr<facebook::react::TurboModule> packageTurboModule(
18
+ const std::string &name,
19
+ const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker) {
20
+ (void)name;
21
+ (void)jsInvoker;
22
+ @basalt_package_turbo_calls@ return nullptr;
23
+ }
24
+
25
+ } // namespace basalt
@@ -0,0 +1,50 @@
1
+ // How a capability that does not live in core reaches the runtime.
2
+ //
3
+ // The host is one binary. There is no dynamic native module loading here, so a
4
+ // capability shipped as its own package is still *compiled in* -- which means
5
+ // the question is not how it loads but how core learns it exists without
6
+ // naming it.
7
+ //
8
+ // It does not. CMake writes the answer. When the build discovers a package that
9
+ // declares native code, it generates a translation unit defining the function
10
+ // below to call that package's installer; when it discovers none, it generates
11
+ // one with an empty body. Core calls it either way and never mentions a
12
+ // package.
13
+ //
14
+ // Generated rather than registered at static initialisation, which is the
15
+ // obvious alternative and the wrong one here: a registrar in a static library
16
+ // is only linked in if something already references its translation unit, so
17
+ // the capability would vanish from a release build and stay in a debug one.
18
+ // That failure is silent and platform-specific, which is the worst shape a
19
+ // build problem can have.
20
+
21
+ #pragma once
22
+
23
+ #include <ReactCommon/CallInvoker.h>
24
+ #include <ReactCommon/TurboModule.h>
25
+
26
+ #include <jsi/jsi.h>
27
+
28
+ #include <memory>
29
+ #include <string>
30
+
31
+ namespace basalt {
32
+
33
+ // Sets each discovered package's Expo modules on `modules`, which is
34
+ // `globalThis.expo.modules`. Defined by the generated translation unit; see
35
+ // cmake/BasaltPackages.cmake.
36
+ void installPackageExpoModules(facebook::jsi::Runtime &runtime, facebook::jsi::Object &modules);
37
+
38
+ // A discovered package's TurboModule, or nullptr when no package supplies one by
39
+ // that name. Defined by the same generated translation unit.
40
+ //
41
+ // The signature is React Native's own `TurboModuleProvider`, so a host adds this
42
+ // to its list of providers directly rather than wrapping it -- which is the
43
+ // whole of the wiring, and why a package contributing a TurboModule costs a host
44
+ // nothing. A package that wants one names a factory of this shape in its
45
+ // CMakeLists; see basalt-subprocess.
46
+ std::shared_ptr<facebook::react::TurboModule> packageTurboModule(
47
+ const std::string &name,
48
+ const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker);
49
+
50
+ } // namespace basalt
@@ -0,0 +1,63 @@
1
+ #include "PlatformConstantsModule.h"
2
+
3
+ #include <cstdlib>
4
+
5
+ #ifndef BASALT_REACT_NATIVE_VERSION
6
+ // Only reachable if the build did not pass one, which CMake always does.
7
+ #define BASALT_REACT_NATIVE_VERSION "0.0.0"
8
+ #endif
9
+
10
+ namespace basalt {
11
+
12
+ using facebook::react::PlatformConstantsAndroid;
13
+
14
+ DesktopPlatformConstantsModule::Version DesktopPlatformConstantsModule::parseVersion(
15
+ const std::string &text) {
16
+ Version version;
17
+
18
+ // A prerelease is everything after the first '-', and it must not be parsed
19
+ // as part of the patch number: "0.88.0-rc.2" is patch 0, prerelease "rc.2".
20
+ const auto dash = text.find('-');
21
+ const std::string numbers = text.substr(0, dash);
22
+ if (dash != std::string::npos && dash + 1 < text.size()) {
23
+ version.prerelease = text.substr(dash + 1);
24
+ }
25
+
26
+ int *fields[] = {&version.major, &version.minor, &version.patch};
27
+ std::size_t start = 0;
28
+ for (int index = 0; index < 3 && start <= numbers.size(); ++index) {
29
+ const auto dot = numbers.find('.', start);
30
+ const std::string part =
31
+ numbers.substr(start, dot == std::string::npos ? std::string::npos : dot - start);
32
+ *fields[index] = std::atoi(part.c_str());
33
+ if (dot == std::string::npos) {
34
+ break;
35
+ }
36
+ start = dot + 1;
37
+ }
38
+
39
+ return version;
40
+ }
41
+
42
+ PlatformConstantsAndroid DesktopPlatformConstantsModule::getConstants(facebook::jsi::Runtime &rt) {
43
+ // Everything but the version is what ReactCxxPlatform reports, kept in step
44
+ // with it deliberately: this class exists to correct one field, not to invent
45
+ // a different set of constants.
46
+ facebook::react::PlatformConstantsModule base{jsInvoker_};
47
+ PlatformConstantsAndroid constants = base.getConstants(rt);
48
+
49
+ const Version version = parseVersion(BASALT_REACT_NATIVE_VERSION);
50
+ constants.reactNativeVersion.major = version.major;
51
+ constants.reactNativeVersion.minor = version.minor;
52
+ constants.reactNativeVersion.patch = version.patch;
53
+ // Left absent deliberately. The Android spec this platform answers types
54
+ // `prerelease` as an optional *int*, so a tag like "rc.2" has nowhere to go;
55
+ // reporting nothing is at least not a lie, and a stable release has none
56
+ // anyway. The tag is still parsed off, above, so that it cannot be read as
57
+ // part of the patch number.
58
+ constants.reactNativeVersion.prerelease = std::nullopt;
59
+
60
+ return constants;
61
+ }
62
+
63
+ } // namespace basalt