react-native-unistyles 3.4.0 → 3.5.1

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 (204) hide show
  1. package/README.md +23 -7
  2. package/android/build.gradle +12 -1
  3. package/android/src/main/cxx/NativeUnistylesModule.cpp +8 -4
  4. package/android/src/main/cxx/NativeUnistylesModule.h +5 -1
  5. package/components/native/ActivityIndicator/package.json +1 -1
  6. package/cxx/common/UnistylesConstants.h +4 -0
  7. package/cxx/converters/TransformOriginConverter.cpp +106 -0
  8. package/cxx/core/HostUnistyle.cpp +13 -1
  9. package/cxx/core/HostUnistyle.h +6 -2
  10. package/cxx/core/RNStyle.h +1 -1
  11. package/cxx/core/StyleSheetRegistry.cpp +9 -9
  12. package/cxx/core/StyleSheetRegistry.h +3 -3
  13. package/cxx/core/UnistyleWrapper.h +13 -1
  14. package/cxx/core/UnistylesRegistry.cpp +102 -18
  15. package/cxx/core/UnistylesRegistry.h +42 -5
  16. package/cxx/hybridObjects/HybridShadowRegistry.cpp +124 -4
  17. package/cxx/hybridObjects/HybridShadowRegistry.h +20 -0
  18. package/cxx/hybridObjects/HybridStyleSheet.cpp +20 -12
  19. package/cxx/hybridObjects/HybridUnistylesRuntime.cpp +11 -1
  20. package/cxx/hybridObjects/HybridUnistylesRuntime.h +4 -2
  21. package/cxx/parser/Parser.cpp +29 -1
  22. package/cxx/parser/Parser.h +1 -0
  23. package/cxx/shadowTree/NativeProps.h +46 -2
  24. package/cxx/shadowTree/ShadowTrafficController.h +4 -0
  25. package/cxx/shadowTree/ShadowTreeDiagnostics.cpp +344 -0
  26. package/cxx/shadowTree/ShadowTreeDiagnostics.h +18 -0
  27. package/cxx/shadowTree/ShadowTreeManager.cpp +7 -1
  28. package/cxx/shadowTree/ShadowTreeManager.h +1 -1
  29. package/diagnostics/package.json +6 -0
  30. package/ios/UnistylesModuleOnLoad.mm +9 -3
  31. package/lib/commonjs/components/native/ActivityIndicator.native.js +10 -0
  32. package/lib/commonjs/components/native/ActivityIndicator.native.js.map +1 -0
  33. package/lib/commonjs/components/native/NativeText.js +6 -0
  34. package/lib/commonjs/components/native/NativeText.js.map +1 -1
  35. package/lib/commonjs/components/native/NativeText.native.js +17 -1
  36. package/lib/commonjs/components/native/NativeText.native.js.map +1 -1
  37. package/lib/commonjs/components/native/NativeView.js +6 -0
  38. package/lib/commonjs/components/native/NativeView.js.map +1 -1
  39. package/lib/commonjs/components/native/NativeView.native.js +22 -3
  40. package/lib/commonjs/components/native/NativeView.native.js.map +1 -1
  41. package/lib/commonjs/components/native/Pressable.js +7 -3
  42. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  43. package/lib/commonjs/components/native/TouchableHighlight.native.js +10 -0
  44. package/lib/commonjs/components/native/TouchableHighlight.native.js.map +1 -0
  45. package/lib/commonjs/core/createUnistylesActivityIndicator.js +41 -0
  46. package/lib/commonjs/core/createUnistylesActivityIndicator.js.map +1 -0
  47. package/lib/commonjs/core/createUnistylesElement.js +60 -15
  48. package/lib/commonjs/core/createUnistylesElement.js.map +1 -1
  49. package/lib/commonjs/core/createUnistylesTouchableHighlight.js +58 -0
  50. package/lib/commonjs/core/createUnistylesTouchableHighlight.js.map +1 -0
  51. package/lib/commonjs/core/index.js +14 -0
  52. package/lib/commonjs/core/index.js.map +1 -1
  53. package/lib/commonjs/core/passForwardRef.js +15 -3
  54. package/lib/commonjs/core/passForwardRef.js.map +1 -1
  55. package/lib/commonjs/diagnostics/index.js +13 -0
  56. package/lib/commonjs/diagnostics/index.js.map +1 -0
  57. package/lib/commonjs/diagnostics/types.js +2 -0
  58. package/lib/commonjs/diagnostics/types.js.map +1 -0
  59. package/lib/commonjs/diagnostics/verify.js +19 -0
  60. package/lib/commonjs/diagnostics/verify.js.map +1 -0
  61. package/lib/commonjs/diagnostics/verify.native.js +12 -0
  62. package/lib/commonjs/diagnostics/verify.native.js.map +1 -0
  63. package/lib/commonjs/index.js +38 -0
  64. package/lib/commonjs/index.js.map +1 -1
  65. package/lib/commonjs/mocks.js +42 -1
  66. package/lib/commonjs/mocks.js.map +1 -1
  67. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js +8 -0
  68. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  69. package/lib/commonjs/specs/ShadowRegistry/index.js +97 -1
  70. package/lib/commonjs/specs/ShadowRegistry/index.js.map +1 -1
  71. package/lib/commonjs/specs/StyleSheet/index.js +5 -1
  72. package/lib/commonjs/specs/StyleSheet/index.js.map +1 -1
  73. package/lib/commonjs/specs/UnistylesRuntime/index.js.map +1 -1
  74. package/lib/commonjs/web/registry.js +3 -0
  75. package/lib/commonjs/web/registry.js.map +1 -1
  76. package/lib/commonjs/web/runtime.js +6 -0
  77. package/lib/commonjs/web/runtime.js.map +1 -1
  78. package/lib/module/components/native/ActivityIndicator.native.js +6 -0
  79. package/lib/module/components/native/ActivityIndicator.native.js.map +1 -0
  80. package/lib/module/components/native/NativeText.js +1 -1
  81. package/lib/module/components/native/NativeText.js.map +1 -1
  82. package/lib/module/components/native/NativeText.native.js +15 -1
  83. package/lib/module/components/native/NativeText.native.js.map +1 -1
  84. package/lib/module/components/native/NativeView.js +1 -0
  85. package/lib/module/components/native/NativeView.js.map +1 -1
  86. package/lib/module/components/native/NativeView.native.js +19 -2
  87. package/lib/module/components/native/NativeView.native.js.map +1 -1
  88. package/lib/module/components/native/Pressable.js +7 -3
  89. package/lib/module/components/native/Pressable.js.map +1 -1
  90. package/lib/module/components/native/TouchableHighlight.native.js +6 -0
  91. package/lib/module/components/native/TouchableHighlight.native.js.map +1 -0
  92. package/lib/module/core/createUnistylesActivityIndicator.js +35 -0
  93. package/lib/module/core/createUnistylesActivityIndicator.js.map +1 -0
  94. package/lib/module/core/createUnistylesElement.js +59 -14
  95. package/lib/module/core/createUnistylesElement.js.map +1 -1
  96. package/lib/module/core/createUnistylesTouchableHighlight.js +52 -0
  97. package/lib/module/core/createUnistylesTouchableHighlight.js.map +1 -0
  98. package/lib/module/core/index.js +2 -0
  99. package/lib/module/core/index.js.map +1 -1
  100. package/lib/module/core/passForwardRef.js +15 -3
  101. package/lib/module/core/passForwardRef.js.map +1 -1
  102. package/lib/module/diagnostics/index.js +5 -0
  103. package/lib/module/diagnostics/index.js.map +1 -0
  104. package/lib/module/diagnostics/types.js +2 -0
  105. package/lib/module/diagnostics/types.js.map +1 -0
  106. package/lib/module/diagnostics/verify.js +14 -0
  107. package/lib/module/diagnostics/verify.js.map +1 -0
  108. package/lib/module/diagnostics/verify.native.js +8 -0
  109. package/lib/module/diagnostics/verify.native.js.map +1 -0
  110. package/lib/module/index.js +2 -0
  111. package/lib/module/index.js.map +1 -1
  112. package/lib/module/mocks.js +42 -1
  113. package/lib/module/mocks.js.map +1 -1
  114. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js +9 -1
  115. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  116. package/lib/module/specs/ShadowRegistry/index.js +97 -1
  117. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  118. package/lib/module/specs/StyleSheet/index.js +6 -1
  119. package/lib/module/specs/StyleSheet/index.js.map +1 -1
  120. package/lib/module/specs/UnistylesRuntime/index.js.map +1 -1
  121. package/lib/module/web/registry.js +3 -0
  122. package/lib/module/web/registry.js.map +1 -1
  123. package/lib/module/web/runtime.js +6 -0
  124. package/lib/module/web/runtime.js.map +1 -1
  125. package/lib/typescript/src/__tests__/passForwardRef.spec.d.ts +2 -0
  126. package/lib/typescript/src/__tests__/passForwardRef.spec.d.ts.map +1 -0
  127. package/lib/typescript/src/__tests__/web-runtime.spec.d.ts +2 -0
  128. package/lib/typescript/src/__tests__/web-runtime.spec.d.ts.map +1 -0
  129. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts +2 -0
  130. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts.map +1 -0
  131. package/lib/typescript/src/components/native/NativeText.d.ts +1 -1
  132. package/lib/typescript/src/components/native/NativeText.d.ts.map +1 -1
  133. package/lib/typescript/src/components/native/NativeText.native.d.ts.map +1 -1
  134. package/lib/typescript/src/components/native/NativeView.d.ts +1 -0
  135. package/lib/typescript/src/components/native/NativeView.d.ts.map +1 -1
  136. package/lib/typescript/src/components/native/NativeView.native.d.ts +2 -2
  137. package/lib/typescript/src/components/native/NativeView.native.d.ts.map +1 -1
  138. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  139. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts +2 -0
  140. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts.map +1 -0
  141. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts +3 -0
  142. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts.map +1 -0
  143. package/lib/typescript/src/core/createUnistylesElement.d.ts.map +1 -1
  144. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts +3 -0
  145. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts.map +1 -0
  146. package/lib/typescript/src/core/index.d.ts +2 -0
  147. package/lib/typescript/src/core/index.d.ts.map +1 -1
  148. package/lib/typescript/src/core/passForwardRef.d.ts.map +1 -1
  149. package/lib/typescript/src/core/useUnistyles.d.ts +1 -1
  150. package/lib/typescript/src/diagnostics/index.d.ts +3 -0
  151. package/lib/typescript/src/diagnostics/index.d.ts.map +1 -0
  152. package/lib/typescript/src/diagnostics/types.d.ts +19 -0
  153. package/lib/typescript/src/diagnostics/types.d.ts.map +1 -0
  154. package/lib/typescript/src/diagnostics/verify.d.ts +3 -0
  155. package/lib/typescript/src/diagnostics/verify.d.ts.map +1 -0
  156. package/lib/typescript/src/diagnostics/verify.native.d.ts +3 -0
  157. package/lib/typescript/src/diagnostics/verify.native.d.ts.map +1 -0
  158. package/lib/typescript/src/index.d.ts +4 -1
  159. package/lib/typescript/src/index.d.ts.map +1 -1
  160. package/lib/typescript/src/reanimated/variant/useUpdateVariantColor.native.d.ts.map +1 -1
  161. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts +5 -1
  162. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  163. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts +3 -0
  164. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts.map +1 -1
  165. package/lib/typescript/src/specs/StyleSheet/index.d.ts +4 -1
  166. package/lib/typescript/src/specs/StyleSheet/index.d.ts.map +1 -1
  167. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts +2 -2
  168. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts.map +1 -1
  169. package/lib/typescript/src/types/breakpoints.d.ts +1 -1
  170. package/lib/typescript/src/types/breakpoints.d.ts.map +1 -1
  171. package/lib/typescript/src/types/stylesheet.d.ts +3 -3
  172. package/lib/typescript/src/types/stylesheet.d.ts.map +1 -1
  173. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  174. package/lib/typescript/src/web/runtime.d.ts.map +1 -1
  175. package/package.json +9 -1
  176. package/plugin/index.d.ts +4 -4
  177. package/plugin/index.js +21 -5
  178. package/src/components/native/ActivityIndicator.native.tsx +5 -0
  179. package/src/components/native/NativeText.native.tsx +18 -1
  180. package/src/components/native/NativeText.tsx +1 -1
  181. package/src/components/native/NativeView.native.tsx +22 -2
  182. package/src/components/native/NativeView.tsx +2 -0
  183. package/src/components/native/Pressable.tsx +8 -3
  184. package/src/components/native/TouchableHighlight.native.tsx +5 -0
  185. package/src/core/createUnistylesActivityIndicator.tsx +47 -0
  186. package/src/core/createUnistylesElement.tsx +51 -15
  187. package/src/core/createUnistylesTouchableHighlight.tsx +69 -0
  188. package/src/core/index.ts +2 -0
  189. package/src/core/passForwardRef.ts +16 -4
  190. package/src/diagnostics/index.ts +3 -0
  191. package/src/diagnostics/types.ts +25 -0
  192. package/src/diagnostics/verify.native.ts +7 -0
  193. package/src/diagnostics/verify.ts +13 -0
  194. package/src/index.ts +4 -1
  195. package/src/mocks.ts +47 -2
  196. package/src/reanimated/variant/useUpdateVariantColor.native.ts +7 -1
  197. package/src/specs/ShadowRegistry/index.ts +128 -1
  198. package/src/specs/ShadowRegistry/types.ts +3 -0
  199. package/src/specs/StyleSheet/index.ts +10 -2
  200. package/src/specs/UnistylesRuntime/index.ts +7 -2
  201. package/src/types/breakpoints.ts +5 -3
  202. package/src/types/stylesheet.ts +3 -3
  203. package/src/web/registry.ts +3 -0
  204. package/src/web/runtime.ts +6 -0
package/README.md CHANGED
@@ -12,16 +12,18 @@
12
12
  [![platform - ssr](https://img.shields.io/badge/SSR-black?style=for-the-badge&logo=next.js&logoColor=white)](https://nextjs.org/)
13
13
 
14
14
 
15
- ## Installation
15
+ ## Requirements
16
16
 
17
- ```shell
18
- yarn add react-native-unistyles
19
- ```
17
+ - React Native 0.81+ with the New Architecture
18
+ - React 19+
19
+ - Expo SDK 54+ (if you use Expo, development builds only, Expo Go is not supported)
20
20
 
21
- Install dependencies:
21
+ Need support for older React Native versions or the Old Architecture? Use [Unistyles 2.0](https://v2.unistyl.es).
22
+
23
+ ## Installation
22
24
 
23
25
  ```shell
24
- yarn add react-native-nitro-modules
26
+ yarn add react-native-unistyles react-native-nitro-modules
25
27
  ```
26
28
 
27
29
  | react-native-unistyles | Minimum react-native-nitro-modules |
@@ -29,6 +31,8 @@ yarn add react-native-nitro-modules
29
31
  | >= 3.0.0 | >= 0.33.9 |
30
32
  | >= 3.1.0 | >= 0.35.0 |
31
33
  | >= 3.2.0 | >= 0.35.2 |
34
+ | >= 3.3.0 | >= 0.36.1 |
35
+ | >= 3.4.0 | >= 0.37.1 |
32
36
 
33
37
  > **Note:** Since v3.1.0, `react-native-edge-to-edge` is an **optional** dependency. We strongly recommend setting `edgeToEdgeEnabled=true` in your `android/gradle.properties` — it enforces translucent system bars on modals, disables legacy StatusBar hacks, and enables additional React Native core fixes. **Expo SDK 54+** enables this automatically. You can still install `react-native-edge-to-edge` for ecosystem compatibility with libraries like `react-native-bootsplash` or `react-native-permissions`.
34
38
 
@@ -39,7 +43,19 @@ Then follow [installation guides](https://unistyl.es/v3/start/getting-started) f
39
43
  - [Migration from Unistyles 2.0](https://unistyl.es/v3/start/migration-guide)
40
44
  - [Learn how Unistyles 3.0 works](https://unistyl.es/v3/start/how-unistyles-works)
41
45
  - [API](https://unistyl.es/v3/references/stylesheet)
42
- - [Examples](https://unistyl.es/v3/examples/examples)
46
+ - [Tutorial](https://unistyl.es/v3/tutorial/intro)
47
+ - [LLMs](https://unistyl.es/v3/llms/info)
48
+
49
+ ## AI agent skills
50
+
51
+ This repository ships skills for AI coding agents (Claude Code, Codex, Cursor and others):
52
+
53
+ - `react-native-unistyles-v3` - setup, theming, responsive styles, variants, web and 3rd party integrations
54
+ - `unistyles-v2-to-v3-migration` - step by step migration from Unistyles 2.0
55
+
56
+ ```shell
57
+ npx skills add jpudysz/react-native-unistyles
58
+ ```
43
59
 
44
60
  ## Features
45
61
  - 🚀 Shared core with C++ and JSI bindings
@@ -18,9 +18,20 @@ def reactNativeArchitectures() {
18
18
  }
19
19
 
20
20
  apply plugin: 'com.android.library'
21
- apply plugin: 'org.jetbrains.kotlin.android'
21
+ // AGP 9 ships built-in Kotlin support and registers the `kotlin` extension
22
+ // itself. Applying the Kotlin plugin on top of it fails configuration with
23
+ // "Cannot add extension with name 'kotlin'". Only apply it when nothing has
24
+ // registered that extension yet.
25
+ if (project.extensions.findByName('kotlin') == null) {
26
+ apply plugin: 'org.jetbrains.kotlin.android'
27
+ }
28
+
22
29
  apply from: '../nitrogen/generated/android/unistyles+autolinking.gradle'
23
30
 
31
+ // Nitrogen adds its generated Kotlin files to java.srcDirs, which AGP 9 built-in Kotlin
32
+ // doesn't compile. It only compiles Kotlin directories from the kotlin source set.
33
+ android.sourceSets.main.kotlin.srcDirs += ["${project.projectDir}/../nitrogen/generated/android/kotlin"]
34
+
24
35
  def resolveBuildType() {
25
36
  Gradle gradle = getGradle()
26
37
  String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString()
@@ -31,11 +31,15 @@ void UnistylesModule::registerNatives() {
31
31
  }
32
32
 
33
33
  jni::local_ref<BindingsInstallerHolder::javaobject> UnistylesModule::getBindingsInstaller(jni::alias_ref<UnistylesModule::javaobject> jobj) {
34
- auto& runtimeExecutor = jobj->cthis()->_runtimeExecutor;
35
- auto& nativePlatform = jobj->cthis()->_nativePlatform;
34
+ auto* self = jobj->cthis();
35
+ auto& runtimeExecutor = self->_runtimeExecutor;
36
+ auto& nativePlatform = self->_nativePlatform;
36
37
 
37
- return BindingsInstallerHolder::newObjectCxxArgs([&runtimeExecutor, &nativePlatform](jsi::Runtime& rt) {
38
+ return BindingsInstallerHolder::newObjectCxxArgs([self, &runtimeExecutor, &nativePlatform](jsi::Runtime& rt) {
38
39
  // function is called on: first init and every live reload
40
+ // newest runtime owns the registry, previous runtime's state is kept until it releases ownership
41
+ self->_generation = core::UnistylesRegistry::get().takeOwnership();
42
+
39
43
  // check if this is live reload, if so let's replace UnistylesRuntime with new runtime
40
44
  auto hasUnistylesRuntime = HybridObjectRegistry::hasHybridObject("UnistylesRuntime");
41
45
 
@@ -52,7 +56,7 @@ jni::local_ref<BindingsInstallerHolder::javaobject> UnistylesModule::getBindings
52
56
  };
53
57
 
54
58
  // init hybrids
55
- auto unistylesRuntime = std::make_shared<HybridUnistylesRuntime>(nativePlatform, runOnJSThread);
59
+ auto unistylesRuntime = std::make_shared<HybridUnistylesRuntime>(nativePlatform, runOnJSThread, self->_generation);
56
60
  auto styleSheet = std::make_shared<HybridStyleSheet>(unistylesRuntime);
57
61
 
58
62
  HybridObjectRegistry::registerHybridObjectConstructor("UnistylesRuntime", [unistylesRuntime]() -> std::shared_ptr<HybridObject>{
@@ -29,7 +29,10 @@ struct UnistylesModule : public jni::HybridClass<UnistylesModule> {
29
29
  jni::alias_ref<JHybridNativePlatformSpec::JavaPart> nativePlatform
30
30
  );
31
31
  static void invalidateNative(jni::alias_ref<jhybridobject> jThis) {
32
- core::UnistylesRegistry::get().destroy();
32
+ auto* self = jThis->cthis();
33
+
34
+ // releases this runtime's state, also when a newer runtime already replaced it
35
+ core::UnistylesRegistry::get().releaseOwnership(self->_generation);
33
36
  }
34
37
 
35
38
  static jni::local_ref<BindingsInstallerHolder::javaobject> getBindingsInstaller(jni::alias_ref<UnistylesModule::javaobject> jThis);
@@ -37,6 +40,7 @@ struct UnistylesModule : public jni::HybridClass<UnistylesModule> {
37
40
  private:
38
41
  RuntimeExecutor _runtimeExecutor;
39
42
  std::shared_ptr<HybridNativePlatformSpec> _nativePlatform;
43
+ uint64_t _generation = 0;
40
44
  };
41
45
 
42
46
  }
@@ -2,5 +2,5 @@
2
2
  "main": "../../../lib/commonjs/components/native/ActivityIndicator.js",
3
3
  "module": "../../../lib/module/components/native/ActivityIndicator.js",
4
4
  "browser": "../../../lib/module/components/native/ActivityIndicator.js",
5
- "react-native": "../../../src/components/native/ActivityIndicator.tsx"
5
+ "react-native": "../../../src/components/native/ActivityIndicator.native.tsx"
6
6
  }
@@ -12,5 +12,9 @@ static const std::string EXOTIC_STYLE_KEY = "_exotic";
12
12
  static const std::string ARGUMENTS = "__uni__args";
13
13
  static const std::string GET_STYLES = "uni__getStyles";
14
14
  static const std::string UNDEFINED_MARKER = "__unistyles_undefined__";
15
+ static const std::string RUNTIME_REPLACED_ERROR = "Unistyles: this runtime was replaced (reload in progress)";
16
+ static const std::string WORKLET_ACCESS_ERROR = R"(Unistyles: styles can't be read inside a worklet (eg. useAnimatedStyle)!
17
+
18
+ Pass them next to the animated style instead: style={[styles.box, animatedStyle]}. Use useAnimatedTheme to read the theme in a worklet.)";
15
19
 
16
20
  }
@@ -1,13 +1,117 @@
1
1
  #include "TransformOriginConverter.h"
2
2
 
3
+ #include <cxxreact/ReactNativeVersion.h>
4
+
5
+ // facebook::react::parseUnprocessedTransformOriginString was introduced in
6
+ // React Native 0.85, while conversions.h is available in every supported
7
+ // version, so the include alone is not enough to detect the parser.
3
8
  #if defined(RN_SERIALIZABLE_STATE) && \
9
+ (REACT_NATIVE_VERSION_MAJOR > 0 || REACT_NATIVE_VERSION_MINOR >= 85) && \
4
10
  __has_include(<react/renderer/components/view/conversions.h>)
5
11
  #include <react/renderer/components/view/conversions.h>
6
12
  #define UNISTYLES_HAS_RN_TRANSFORM_ORIGIN_PARSER 1
13
+ #elif defined(RN_SERIALIZABLE_STATE)
14
+ #include <algorithm>
15
+ #include <cctype>
16
+ #include <cstdlib>
17
+ #include <regex>
18
+ #define UNISTYLES_HAS_TRANSFORM_ORIGIN_FALLBACK 1
7
19
  #endif
8
20
 
9
21
  namespace margelo::nitro::unistyles::converters {
10
22
 
23
+ #ifdef UNISTYLES_HAS_TRANSFORM_ORIGIN_FALLBACK
24
+ namespace {
25
+
26
+ // Port of React Native's processTransformOrigin.js. Android forwards raw props
27
+ // to the view manager, which expects the processed [x, y, z] array, so a string
28
+ // must not reach it. Returns null (default origin) for an invalid value.
29
+ folly::dynamic processTransformOriginString(const std::string &value) {
30
+ static const std::regex tokenRegex(
31
+ "(top|bottom|left|right|center|\\d+(?:%|px)|0)", std::regex::icase);
32
+ constexpr size_t INDEX_X = 0;
33
+ constexpr size_t INDEX_Y = 1;
34
+ constexpr size_t INDEX_Z = 2;
35
+
36
+ auto toLower = [](std::string token) {
37
+ std::transform(token.begin(), token.end(), token.begin(),
38
+ [](unsigned char c) { return std::tolower(c); });
39
+
40
+ return token;
41
+ };
42
+
43
+ folly::dynamic result = folly::dynamic::array("50%", "50%", 0);
44
+ size_t index = INDEX_X;
45
+ auto it = std::sregex_iterator(value.begin(), value.end(), tokenRegex);
46
+ const auto end = std::sregex_iterator();
47
+
48
+ for (; it != end; ++it) {
49
+ if (index > INDEX_Z) {
50
+ return nullptr;
51
+ }
52
+
53
+ auto token = toLower(it->str());
54
+ auto nextIndex = index + 1;
55
+
56
+ if (token == "left" || token == "right") {
57
+ if (index != INDEX_X) {
58
+ return nullptr;
59
+ }
60
+
61
+ result[INDEX_X] = token == "left" ? folly::dynamic(0) : folly::dynamic("100%");
62
+ } else if (token == "top" || token == "bottom") {
63
+ if (index == INDEX_Z) {
64
+ return nullptr;
65
+ }
66
+
67
+ result[INDEX_Y] = token == "top" ? folly::dynamic(0) : folly::dynamic("100%");
68
+
69
+ // [ center | left | right ] after a vertical keyword in the first place
70
+ if (index == INDEX_X) {
71
+ if (++it == end) {
72
+ break;
73
+ }
74
+
75
+ auto horizontal = toLower(it->str());
76
+
77
+ if (horizontal == "left") {
78
+ result[INDEX_X] = 0;
79
+ } else if (horizontal == "right") {
80
+ result[INDEX_X] = "100%";
81
+ } else if (horizontal == "center") {
82
+ result[INDEX_X] = "50%";
83
+ } else {
84
+ return nullptr;
85
+ }
86
+
87
+ nextIndex = INDEX_Z;
88
+ }
89
+ } else if (token == "center") {
90
+ if (index == INDEX_Z) {
91
+ return nullptr;
92
+ }
93
+
94
+ result[index] = "50%";
95
+ } else if (token.back() == '%') {
96
+ if (index == INDEX_Z) {
97
+ return nullptr;
98
+ }
99
+
100
+ result[index] = token;
101
+ } else {
102
+ // 0 or <digits>px
103
+ result[index] = std::strtod(token.c_str(), nullptr);
104
+ }
105
+
106
+ index = nextIndex;
107
+ }
108
+
109
+ return result;
110
+ }
111
+
112
+ } // namespace
113
+ #endif
114
+
11
115
  bool isTransformOriginPropName(const std::string &propertyName) {
12
116
  return propertyName == "transformOrigin";
13
117
  }
@@ -25,6 +129,8 @@ parseTransformOriginString(const std::string &transformOriginString) {
25
129
  }
26
130
 
27
131
  return static_cast<folly::dynamic>(transformOrigin);
132
+ #elif defined(UNISTYLES_HAS_TRANSFORM_ORIGIN_FALLBACK)
133
+ return processTransformOriginString(transformOriginString);
28
134
  #else
29
135
  return std::nullopt;
30
136
  #endif
@@ -23,7 +23,19 @@ HostUnistyle::~HostUnistyle() {
23
23
  }
24
24
  }
25
25
 
26
+ bool HostUnistyle::isWorkletRuntime(jsi::Runtime& rt) {
27
+ auto isWorklet = rt.global().getProperty(rt, "_WORKLET");
28
+
29
+ return isWorklet.isBool() && isWorklet.getBool();
30
+ }
31
+
26
32
  jsi::Value HostUnistyle::get(jsi::Runtime& rt, const jsi::PropNameID& propNameId) {
33
+ // worklets share host objects with other runtimes, eg. when useAnimatedStyle captures `styles`
34
+ // cached styles and unistyles hold values of our runtime, reading them from another one corrupts both heaps
35
+ if (&rt != this->_runtime && this->isWorkletRuntime(rt)) {
36
+ throw jsi::JSError(rt, helpers::WORKLET_ACCESS_ERROR);
37
+ }
38
+
27
39
  auto propertyName = propNameId.utf8(rt);
28
40
 
29
41
  if (propertyName == helpers::STYLESHEET_ID.c_str()) {
@@ -114,7 +126,7 @@ jsi::Function HostUnistyle::createAddVariantsProxyFunction(jsi::Runtime& rt) {
114
126
  }
115
127
  });
116
128
 
117
- auto style = std::make_shared<core::HostUnistyle>(stylesheetCopy, this->_unistylesRuntime, variants, true);
129
+ auto style = std::make_shared<core::HostUnistyle>(rt, stylesheetCopy, this->_unistylesRuntime, variants, true);
118
130
  auto styleHostObject = jsi::Object::createFromHostObject(rt, style);
119
131
 
120
132
  return styleHostObject;
@@ -9,8 +9,8 @@ namespace margelo::nitro::unistyles::core {
9
9
  using Variants = std::vector<std::pair<std::string, std::string>>;
10
10
 
11
11
  struct JSI_EXPORT HostUnistyle : public jsi::HostObject {
12
- HostUnistyle(std::shared_ptr<StyleSheet> stylesheet, std::shared_ptr<HybridUnistylesRuntime> unistylesRuntime, Variants& variants, bool ownsStyleSheet = false)
13
- : _stylesheet(stylesheet), _unistylesRuntime{unistylesRuntime}, _variants{std::move(variants)}, _ownsStyleSheet(ownsStyleSheet) {};
12
+ HostUnistyle(jsi::Runtime& rt, std::shared_ptr<StyleSheet> stylesheet, std::shared_ptr<HybridUnistylesRuntime> unistylesRuntime, Variants& variants, bool ownsStyleSheet = false)
13
+ : _runtime(&rt), _stylesheet(stylesheet), _unistylesRuntime{unistylesRuntime}, _variants{std::move(variants)}, _ownsStyleSheet(ownsStyleSheet) {};
14
14
  ~HostUnistyle();
15
15
 
16
16
  std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt);
@@ -20,6 +20,10 @@ struct JSI_EXPORT HostUnistyle : public jsi::HostObject {
20
20
  jsi::Function createAddVariantsProxyFunction(jsi::Runtime& rt);
21
21
 
22
22
  private:
23
+ bool isWorkletRuntime(jsi::Runtime& rt);
24
+
25
+ // the runtime that created this stylesheet, cached styles and unistyles hold its values
26
+ jsi::Runtime* _runtime;
23
27
  bool _ownsStyleSheet;
24
28
  Variants _variants;
25
29
  std::shared_ptr<StyleSheet> _stylesheet;
@@ -9,7 +9,7 @@
9
9
  namespace margelo::nitro::unistyles::core {
10
10
 
11
11
  inline jsi::Object toRNStyle(jsi::Runtime& rt, std::shared_ptr<StyleSheet> stylesheet, std::shared_ptr<HybridUnistylesRuntime> unistylesRuntime, Variants&& variants) {
12
- auto style = std::make_shared<core::HostUnistyle>(stylesheet, unistylesRuntime, variants);
12
+ auto style = std::make_shared<core::HostUnistyle>(rt, stylesheet, unistylesRuntime, variants);
13
13
  auto styleHostObject = jsi::Object::createFromHostObject(rt, style);
14
14
 
15
15
  return styleHostObject;
@@ -4,15 +4,15 @@
4
4
  using namespace margelo::nitro::unistyles::core;
5
5
  using namespace facebook;
6
6
 
7
- std::shared_ptr<StyleSheet> StyleSheetRegistry::addStyleSheetFromValue(jsi::Runtime& rt, jsi::Object rawStyleSheet) {
7
+ std::shared_ptr<StyleSheet> StyleSheetRegistry::addStyleSheetFromValue(jsi::Runtime& rt, uint64_t generation, jsi::Object rawStyleSheet) {
8
8
  if (rawStyleSheet.isFunction(rt)) {
9
- return this->addFromFunction(rt, rawStyleSheet.asFunction(rt));
9
+ return this->addFromFunction(rt, generation, rawStyleSheet.asFunction(rt));
10
10
  }
11
11
 
12
- return this->addFromObject(rt, std::move(rawStyleSheet));
12
+ return this->addFromObject(rt, generation, std::move(rawStyleSheet));
13
13
  }
14
14
 
15
- std::shared_ptr<StyleSheet> StyleSheetRegistry::addFromFunction(jsi::Runtime& rt, jsi::Function styleSheetFn) {
15
+ std::shared_ptr<StyleSheet> StyleSheetRegistry::addFromFunction(jsi::Runtime& rt, uint64_t generation, jsi::Function styleSheetFn) {
16
16
  auto numberOfArgs = styleSheetFn.getProperty(rt, "length").getNumber();
17
17
 
18
18
  helpers::assertThat(rt, numberOfArgs <= 2, "StyleSheet.create expected up to 2 arguments.");
@@ -23,20 +23,20 @@ std::shared_ptr<StyleSheet> StyleSheetRegistry::addFromFunction(jsi::Runtime& rt
23
23
  if (numberOfArgs == 0) {
24
24
  auto staticStyleSheet = styleSheetFn.call(rt).asObject(rt);
25
25
 
26
- return registry.addStyleSheet(rt, core::StyleSheetType::Static, std::move(staticStyleSheet));
26
+ return registry.addStyleSheet(rt, generation, core::StyleSheetType::Static, std::move(staticStyleSheet));
27
27
  }
28
28
 
29
29
  // stylesheet depends only on theme
30
30
  if (numberOfArgs == 1) {
31
- return registry.addStyleSheet(rt, core::StyleSheetType::Themable, std::move(styleSheetFn));
31
+ return registry.addStyleSheet(rt, generation, core::StyleSheetType::Themable, std::move(styleSheetFn));
32
32
  }
33
33
 
34
34
  // stylesheet depends on theme and mini runtime
35
- return registry.addStyleSheet(rt, core::StyleSheetType::ThemableWithMiniRuntime, std::move(styleSheetFn));
35
+ return registry.addStyleSheet(rt, generation, core::StyleSheetType::ThemableWithMiniRuntime, std::move(styleSheetFn));
36
36
  }
37
37
 
38
- std::shared_ptr<StyleSheet> StyleSheetRegistry::addFromObject(jsi::Runtime& rt, jsi::Object rawStyleSheet) {
38
+ std::shared_ptr<StyleSheet> StyleSheetRegistry::addFromObject(jsi::Runtime& rt, uint64_t generation, jsi::Object rawStyleSheet) {
39
39
  auto& registry = UnistylesRegistry::get();
40
40
 
41
- return registry.addStyleSheet(rt, core::StyleSheetType::Static, std::move(rawStyleSheet));
41
+ return registry.addStyleSheet(rt, generation, core::StyleSheetType::Static, std::move(rawStyleSheet));
42
42
  }
@@ -16,11 +16,11 @@ struct StyleSheetRegistry {
16
16
  StyleSheetRegistry(const StyleSheetRegistry&) = delete;
17
17
  StyleSheetRegistry(StyleSheetRegistry&&) = delete;
18
18
 
19
- virtual std::shared_ptr<StyleSheet> addStyleSheetFromValue(jsi::Runtime& rt, jsi::Object rawStyleSheet);
19
+ virtual std::shared_ptr<StyleSheet> addStyleSheetFromValue(jsi::Runtime& rt, uint64_t generation, jsi::Object rawStyleSheet);
20
20
 
21
21
  private:
22
- virtual std::shared_ptr<StyleSheet> addFromFunction(jsi::Runtime& rt, jsi::Function styleSheetFn);
23
- virtual std::shared_ptr<StyleSheet> addFromObject(jsi::Runtime& rt, jsi::Object rawStyleSheet);
22
+ virtual std::shared_ptr<StyleSheet> addFromFunction(jsi::Runtime& rt, uint64_t generation, jsi::Function styleSheetFn);
23
+ virtual std::shared_ptr<StyleSheet> addFromObject(jsi::Runtime& rt, uint64_t generation, jsi::Object rawStyleSheet);
24
24
  };
25
25
 
26
26
  }
@@ -157,7 +157,19 @@ inline static jsi::Value objectFromUnistyle(jsi::Runtime& rt, std::shared_ptr<Hy
157
157
  [unistyleID = unistyle->unid, unistylesRuntime, variants, parsedArguments](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count
158
158
  ) {
159
159
  auto& registry = UnistylesRegistry::get();
160
- auto unistyle = registry.getUnistyleById(unistyleID);
160
+ // ids repeat across runtimes, so a replaced runtime can't look up its unistyle anymore
161
+ auto unistyle = unistylesRuntime->ownsRegistry()
162
+ ? registry.getUnistyleById(unistyleID)
163
+ : nullptr;
164
+
165
+ // the unistyle may have been unregistered while this call was in flight,
166
+ // eg. during a reload while the outgoing runtime still executes queued work
167
+ if (unistyle == nullptr) {
168
+ throw jsi::JSError(rt, R"(Unistyles: Style is no longer registered!
169
+
170
+ You likely accessed a style while the runtime was being torn down (eg. during a reload).)");
171
+ }
172
+
161
173
  auto scopedTheme = registry.getScopedTheme();
162
174
  parser::Parser parser(unistylesRuntime);
163
175
 
@@ -9,7 +9,13 @@ using namespace facebook::react;
9
9
 
10
10
  std::atomic<int> core::UnistylesRegistry::_nextStyleSheetTag{0};
11
11
 
12
- void core::UnistylesRegistry::registerTheme(jsi::Runtime& rt, std::string name, jsi::Value& theme) {
12
+ void core::UnistylesRegistry::registerTheme(jsi::Runtime& rt, uint64_t generation, std::string name, jsi::Value& theme) {
13
+ std::lock_guard<std::mutex> lock(this->_ownershipMutex);
14
+
15
+ if (!this->isCurrent(generation)) {
16
+ throw std::runtime_error(helpers::RUNTIME_REPLACED_ERROR);
17
+ }
18
+
13
19
  auto& state = this->getState();
14
20
 
15
21
  state._jsThemes.emplace(name, std::move(theme));
@@ -46,7 +52,7 @@ void core::UnistylesRegistry::createState() {
46
52
  this->_state = std::make_unique<UnistylesState>();
47
53
  }
48
54
 
49
- void core::UnistylesRegistry::updateTheme(jsi::Runtime& rt, std::string& themeName, jsi::Function&& callback) {
55
+ void core::UnistylesRegistry::updateTheme(jsi::Runtime& rt, uint64_t generation, std::string& themeName, jsi::Function&& callback) {
50
56
  auto& state = this->getState();
51
57
  auto it = state._jsThemes.find(themeName);
52
58
 
@@ -56,25 +62,38 @@ void core::UnistylesRegistry::updateTheme(jsi::Runtime& rt, std::string& themeNa
56
62
 
57
63
  helpers::assertThat(rt, result.isObject(), "Unistyles: Returned theme is not an object. Please check your updateTheme function.");
58
64
 
59
- it->second = result.asObject(rt);
65
+ auto theme = result.asObject(rt);
66
+ std::lock_guard<std::mutex> lock(this->_ownershipMutex);
67
+
68
+ if (!this->isCurrent(generation)) {
69
+ throw std::runtime_error(helpers::RUNTIME_REPLACED_ERROR);
70
+ }
71
+
72
+ it->second = std::move(theme);
60
73
  }
61
74
 
62
75
  void core::UnistylesRegistry::linkShadowNodeWithUnistyle(
63
76
  jsi::Runtime& rt,
77
+ uint64_t generation,
64
78
  const std::shared_ptr<const ShadowNodeFamily>& shadowNodeFamily,
65
79
  std::vector<std::shared_ptr<UnistyleData>>& unistylesData,
66
80
  std::optional<folly::dynamic> initialScopedUpdate
67
81
  ) {
68
82
  // released after unlocking, so a family destructor never runs within the lock
69
83
  ReleasedFamilies releasedFamilies;
84
+ std::lock_guard<std::mutex> lock(this->_ownershipMutex);
85
+
86
+ if (!this->isCurrent(generation)) {
87
+ return;
88
+ }
70
89
 
71
90
  this->trafficController.withLock([this, &unistylesData, &shadowNodeFamily, &initialScopedUpdate, &releasedFamilies](){
72
91
  auto family = shadowNodeFamily.get();
73
92
  auto it = this->_shadowRegistry.find(family);
74
93
 
75
94
  // Clear suspension state if this family was previously suspended
95
+ // native props stay, they hold values from theme changes that React doesn't know about
76
96
  if (it != this->_shadowRegistry.end() && it->second.isSuspended) {
77
- shadow::resetNativeProps(family);
78
97
  // Clear old registry entries to prevent stale UnistyleData accumulation
79
98
  // and any stale traffic controller entry (e.g. from a theme change during suspension)
80
99
  this->forgetFamilyUnsafe(family, releasedFamilies);
@@ -183,7 +202,13 @@ bool core::UnistylesRegistry::isSuspended(const ShadowNodeFamily* family) const
183
202
  return it != this->_shadowRegistry.end() && it->second.isSuspended;
184
203
  }
185
204
 
186
- std::shared_ptr<core::StyleSheet> core::UnistylesRegistry::addStyleSheet(jsi::Runtime& rt, core::StyleSheetType type, jsi::Object&& rawValue) {
205
+ std::shared_ptr<core::StyleSheet> core::UnistylesRegistry::addStyleSheet(jsi::Runtime& rt, uint64_t generation, core::StyleSheetType type, jsi::Object&& rawValue) {
206
+ std::lock_guard<std::mutex> lock(this->_ownershipMutex);
207
+
208
+ if (!this->isCurrent(generation)) {
209
+ throw std::runtime_error(helpers::RUNTIME_REPLACED_ERROR);
210
+ }
211
+
187
212
  int tag = _nextStyleSheetTag.fetch_add(1);
188
213
 
189
214
  auto sheet = std::make_shared<core::StyleSheet>(tag, type, std::move(rawValue));
@@ -335,26 +360,85 @@ void core::UnistylesRegistry::setScopedTheme(std::optional<std::string> themeNam
335
360
  this->_scopedTheme = std::move(themeName);
336
361
  }
337
362
 
338
- void core::UnistylesRegistry::destroy() {
339
- // called when module is invalidated, before React Native destroys the runtime
340
- // pins must be released now, as families hold JS handles that can't outlive the runtime
341
- ReleasedFamilies releasedFamilies;
363
+ uint64_t core::UnistylesRegistry::takeOwnership() {
364
+ std::lock_guard<std::mutex> lock(this->_ownershipMutex);
365
+ auto previousGeneration = this->_generation.load();
342
366
 
343
- this->trafficController.withLock([this, &releasedFamilies](){
344
- for (auto& [_, linkedFamily] : this->_shadowRegistry) {
345
- releasedFamilies.emplace_back(std::move(linkedFamily.family));
346
- }
367
+ if (previousGeneration != 0) {
368
+ this->_replacedStates[previousGeneration] = this->takeOwnedState();
369
+ }
347
370
 
348
- for (auto& [_, pendingUpdate] : this->trafficController.takeUpdates()) {
349
- releasedFamilies.emplace_back(std::move(pendingUpdate.family));
371
+ this->_generation.store(++this->_lastGeneration);
372
+
373
+ return this->_lastGeneration;
374
+ }
375
+
376
+ void core::UnistylesRegistry::releaseOwnership(uint64_t generation) {
377
+ // called when module is invalidated, before React Native destroys the runtime
378
+ // declared before the lock, so jsi values and pins are released after unlocking
379
+ OwnedState releasedState;
380
+
381
+ {
382
+ std::lock_guard<std::mutex> lock(this->_ownershipMutex);
383
+
384
+ if (this->isCurrent(generation)) {
385
+ releasedState = this->takeOwnedState();
386
+ this->_generation.store(0);
387
+ } else if (auto it = this->_replacedStates.find(generation); it != this->_replacedStates.end()) {
388
+ releasedState = std::move(it->second);
389
+ this->_replacedStates.erase(it);
350
390
  }
391
+ }
351
392
 
352
- this->_shadowRegistry.clear();
393
+ for (auto& [_, styleSheet] : releasedState.styleSheets) {
394
+ styleSheet->unistyles.clear();
395
+ }
396
+ }
397
+
398
+ core::UnistylesRegistry::OwnedState core::UnistylesRegistry::takeOwnedState() {
399
+ OwnedState ownedState;
400
+
401
+ this->trafficController.withLock([this, &ownedState](){
402
+ ownedState.shadowRegistry = std::exchange(this->_shadowRegistry, {});
403
+ ownedState.updates = this->trafficController.takeUpdates();
353
404
  this->_sweepThreshold = MIN_SWEEP_THRESHOLD;
354
405
  });
355
406
 
356
- this->_state.reset();
357
- this->_styleSheetRegistry.clear();
407
+ ownedState.styleSheets = std::exchange(this->_styleSheetRegistry, {});
408
+ ownedState.state = std::move(this->_state);
358
409
  this->_scopedTheme = std::nullopt;
410
+ this->_committedTags.clear();
359
411
  _nextStyleSheetTag.store(0);
412
+
413
+ return ownedState;
414
+ }
415
+
416
+ std::vector<core::LinkedFamilySnapshot> core::UnistylesRegistry::getLinkedFamiliesSnapshot() {
417
+ std::vector<LinkedFamilySnapshot> snapshot;
418
+
419
+ this->trafficController.withLock([this, &snapshot](){
420
+ snapshot.reserve(this->_shadowRegistry.size());
421
+
422
+ for (const auto& [_, linkedFamily] : this->_shadowRegistry) {
423
+ const bool isOwnedOnlyByUnistyles = this->isOwnedOnlyByUnistyles(linkedFamily);
424
+
425
+ snapshot.push_back({linkedFamily.family, linkedFamily.unistyles, linkedFamily.isSuspended, isOwnedOnlyByUnistyles});
426
+ }
427
+ });
428
+
429
+ return snapshot;
430
+ }
431
+
432
+ size_t core::UnistylesRegistry::getPendingUpdatesCount() {
433
+ return this->trafficController.withLock([this](){
434
+ return this->trafficController.getUpdatesCount();
435
+ });
436
+ }
437
+
438
+ void core::UnistylesRegistry::setCommittedTags(std::vector<Tag>&& tags) {
439
+ this->_committedTags = std::move(tags);
440
+ }
441
+
442
+ std::vector<Tag> core::UnistylesRegistry::takeCommittedTags() {
443
+ return std::exchange(this->_committedTags, {});
360
444
  }