react-native-unistyles 3.4.0 → 3.5.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 (185) 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 +1 -0
  7. package/cxx/converters/TransformOriginConverter.cpp +106 -0
  8. package/cxx/core/StyleSheetRegistry.cpp +9 -9
  9. package/cxx/core/StyleSheetRegistry.h +3 -3
  10. package/cxx/core/UnistyleWrapper.h +13 -1
  11. package/cxx/core/UnistylesRegistry.cpp +102 -18
  12. package/cxx/core/UnistylesRegistry.h +42 -5
  13. package/cxx/hybridObjects/HybridShadowRegistry.cpp +124 -4
  14. package/cxx/hybridObjects/HybridShadowRegistry.h +20 -0
  15. package/cxx/hybridObjects/HybridStyleSheet.cpp +20 -12
  16. package/cxx/hybridObjects/HybridUnistylesRuntime.cpp +11 -1
  17. package/cxx/hybridObjects/HybridUnistylesRuntime.h +4 -2
  18. package/cxx/parser/Parser.cpp +29 -1
  19. package/cxx/parser/Parser.h +1 -0
  20. package/cxx/shadowTree/NativeProps.h +46 -2
  21. package/cxx/shadowTree/ShadowTrafficController.h +4 -0
  22. package/cxx/shadowTree/ShadowTreeDiagnostics.cpp +339 -0
  23. package/cxx/shadowTree/ShadowTreeDiagnostics.h +18 -0
  24. package/cxx/shadowTree/ShadowTreeManager.cpp +7 -1
  25. package/cxx/shadowTree/ShadowTreeManager.h +1 -1
  26. package/diagnostics/package.json +6 -0
  27. package/ios/UnistylesModuleOnLoad.mm +9 -3
  28. package/lib/commonjs/components/native/ActivityIndicator.native.js +10 -0
  29. package/lib/commonjs/components/native/ActivityIndicator.native.js.map +1 -0
  30. package/lib/commonjs/components/native/NativeText.js +6 -0
  31. package/lib/commonjs/components/native/NativeText.js.map +1 -1
  32. package/lib/commonjs/components/native/NativeText.native.js +17 -1
  33. package/lib/commonjs/components/native/NativeText.native.js.map +1 -1
  34. package/lib/commonjs/components/native/NativeView.js +6 -0
  35. package/lib/commonjs/components/native/NativeView.js.map +1 -1
  36. package/lib/commonjs/components/native/NativeView.native.js +22 -3
  37. package/lib/commonjs/components/native/NativeView.native.js.map +1 -1
  38. package/lib/commonjs/components/native/Pressable.js +7 -3
  39. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  40. package/lib/commonjs/components/native/TouchableHighlight.native.js +10 -0
  41. package/lib/commonjs/components/native/TouchableHighlight.native.js.map +1 -0
  42. package/lib/commonjs/core/createUnistylesActivityIndicator.js +41 -0
  43. package/lib/commonjs/core/createUnistylesActivityIndicator.js.map +1 -0
  44. package/lib/commonjs/core/createUnistylesElement.js +59 -14
  45. package/lib/commonjs/core/createUnistylesElement.js.map +1 -1
  46. package/lib/commonjs/core/createUnistylesTouchableHighlight.js +58 -0
  47. package/lib/commonjs/core/createUnistylesTouchableHighlight.js.map +1 -0
  48. package/lib/commonjs/core/index.js +14 -0
  49. package/lib/commonjs/core/index.js.map +1 -1
  50. package/lib/commonjs/diagnostics/index.js +13 -0
  51. package/lib/commonjs/diagnostics/index.js.map +1 -0
  52. package/lib/commonjs/diagnostics/types.js +2 -0
  53. package/lib/commonjs/diagnostics/types.js.map +1 -0
  54. package/lib/commonjs/diagnostics/verify.js +19 -0
  55. package/lib/commonjs/diagnostics/verify.js.map +1 -0
  56. package/lib/commonjs/diagnostics/verify.native.js +12 -0
  57. package/lib/commonjs/diagnostics/verify.native.js.map +1 -0
  58. package/lib/commonjs/index.js +38 -0
  59. package/lib/commonjs/index.js.map +1 -1
  60. package/lib/commonjs/mocks.js +41 -0
  61. package/lib/commonjs/mocks.js.map +1 -1
  62. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js +8 -0
  63. package/lib/commonjs/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  64. package/lib/commonjs/specs/ShadowRegistry/index.js +97 -1
  65. package/lib/commonjs/specs/ShadowRegistry/index.js.map +1 -1
  66. package/lib/commonjs/specs/StyleSheet/index.js +5 -1
  67. package/lib/commonjs/specs/StyleSheet/index.js.map +1 -1
  68. package/lib/commonjs/specs/UnistylesRuntime/index.js.map +1 -1
  69. package/lib/commonjs/web/registry.js +3 -0
  70. package/lib/commonjs/web/registry.js.map +1 -1
  71. package/lib/module/components/native/ActivityIndicator.native.js +6 -0
  72. package/lib/module/components/native/ActivityIndicator.native.js.map +1 -0
  73. package/lib/module/components/native/NativeText.js +1 -1
  74. package/lib/module/components/native/NativeText.js.map +1 -1
  75. package/lib/module/components/native/NativeText.native.js +15 -1
  76. package/lib/module/components/native/NativeText.native.js.map +1 -1
  77. package/lib/module/components/native/NativeView.js +1 -0
  78. package/lib/module/components/native/NativeView.js.map +1 -1
  79. package/lib/module/components/native/NativeView.native.js +19 -2
  80. package/lib/module/components/native/NativeView.native.js.map +1 -1
  81. package/lib/module/components/native/Pressable.js +7 -3
  82. package/lib/module/components/native/Pressable.js.map +1 -1
  83. package/lib/module/components/native/TouchableHighlight.native.js +6 -0
  84. package/lib/module/components/native/TouchableHighlight.native.js.map +1 -0
  85. package/lib/module/core/createUnistylesActivityIndicator.js +35 -0
  86. package/lib/module/core/createUnistylesActivityIndicator.js.map +1 -0
  87. package/lib/module/core/createUnistylesElement.js +58 -13
  88. package/lib/module/core/createUnistylesElement.js.map +1 -1
  89. package/lib/module/core/createUnistylesTouchableHighlight.js +52 -0
  90. package/lib/module/core/createUnistylesTouchableHighlight.js.map +1 -0
  91. package/lib/module/core/index.js +2 -0
  92. package/lib/module/core/index.js.map +1 -1
  93. package/lib/module/diagnostics/index.js +5 -0
  94. package/lib/module/diagnostics/index.js.map +1 -0
  95. package/lib/module/diagnostics/types.js +2 -0
  96. package/lib/module/diagnostics/types.js.map +1 -0
  97. package/lib/module/diagnostics/verify.js +14 -0
  98. package/lib/module/diagnostics/verify.js.map +1 -0
  99. package/lib/module/diagnostics/verify.native.js +8 -0
  100. package/lib/module/diagnostics/verify.native.js.map +1 -0
  101. package/lib/module/index.js +2 -0
  102. package/lib/module/index.js.map +1 -1
  103. package/lib/module/mocks.js +41 -0
  104. package/lib/module/mocks.js.map +1 -1
  105. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js +9 -1
  106. package/lib/module/reanimated/variant/useUpdateVariantColor.native.js.map +1 -1
  107. package/lib/module/specs/ShadowRegistry/index.js +97 -1
  108. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  109. package/lib/module/specs/StyleSheet/index.js +6 -1
  110. package/lib/module/specs/StyleSheet/index.js.map +1 -1
  111. package/lib/module/specs/UnistylesRuntime/index.js.map +1 -1
  112. package/lib/module/web/registry.js +3 -0
  113. package/lib/module/web/registry.js.map +1 -1
  114. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts +2 -0
  115. package/lib/typescript/src/components/native/ActivityIndicator.native.d.ts.map +1 -0
  116. package/lib/typescript/src/components/native/NativeText.d.ts +1 -1
  117. package/lib/typescript/src/components/native/NativeText.d.ts.map +1 -1
  118. package/lib/typescript/src/components/native/NativeText.native.d.ts.map +1 -1
  119. package/lib/typescript/src/components/native/NativeView.d.ts +1 -0
  120. package/lib/typescript/src/components/native/NativeView.d.ts.map +1 -1
  121. package/lib/typescript/src/components/native/NativeView.native.d.ts +2 -2
  122. package/lib/typescript/src/components/native/NativeView.native.d.ts.map +1 -1
  123. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  124. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts +2 -0
  125. package/lib/typescript/src/components/native/TouchableHighlight.native.d.ts.map +1 -0
  126. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts +3 -0
  127. package/lib/typescript/src/core/createUnistylesActivityIndicator.d.ts.map +1 -0
  128. package/lib/typescript/src/core/createUnistylesElement.d.ts.map +1 -1
  129. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts +3 -0
  130. package/lib/typescript/src/core/createUnistylesTouchableHighlight.d.ts.map +1 -0
  131. package/lib/typescript/src/core/index.d.ts +2 -0
  132. package/lib/typescript/src/core/index.d.ts.map +1 -1
  133. package/lib/typescript/src/core/useUnistyles.d.ts +1 -1
  134. package/lib/typescript/src/diagnostics/index.d.ts +3 -0
  135. package/lib/typescript/src/diagnostics/index.d.ts.map +1 -0
  136. package/lib/typescript/src/diagnostics/types.d.ts +19 -0
  137. package/lib/typescript/src/diagnostics/types.d.ts.map +1 -0
  138. package/lib/typescript/src/diagnostics/verify.d.ts +3 -0
  139. package/lib/typescript/src/diagnostics/verify.d.ts.map +1 -0
  140. package/lib/typescript/src/diagnostics/verify.native.d.ts +3 -0
  141. package/lib/typescript/src/diagnostics/verify.native.d.ts.map +1 -0
  142. package/lib/typescript/src/index.d.ts +4 -1
  143. package/lib/typescript/src/index.d.ts.map +1 -1
  144. package/lib/typescript/src/reanimated/variant/useUpdateVariantColor.native.d.ts.map +1 -1
  145. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts +5 -1
  146. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  147. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts +3 -0
  148. package/lib/typescript/src/specs/ShadowRegistry/types.d.ts.map +1 -1
  149. package/lib/typescript/src/specs/StyleSheet/index.d.ts +4 -1
  150. package/lib/typescript/src/specs/StyleSheet/index.d.ts.map +1 -1
  151. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts +2 -2
  152. package/lib/typescript/src/specs/UnistylesRuntime/index.d.ts.map +1 -1
  153. package/lib/typescript/src/types/breakpoints.d.ts +1 -1
  154. package/lib/typescript/src/types/breakpoints.d.ts.map +1 -1
  155. package/lib/typescript/src/types/stylesheet.d.ts +3 -3
  156. package/lib/typescript/src/types/stylesheet.d.ts.map +1 -1
  157. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  158. package/package.json +9 -1
  159. package/plugin/index.d.ts +4 -4
  160. package/plugin/index.js +21 -5
  161. package/src/components/native/ActivityIndicator.native.tsx +5 -0
  162. package/src/components/native/NativeText.native.tsx +18 -1
  163. package/src/components/native/NativeText.tsx +1 -1
  164. package/src/components/native/NativeView.native.tsx +22 -2
  165. package/src/components/native/NativeView.tsx +2 -0
  166. package/src/components/native/Pressable.tsx +8 -3
  167. package/src/components/native/TouchableHighlight.native.tsx +5 -0
  168. package/src/core/createUnistylesActivityIndicator.tsx +47 -0
  169. package/src/core/createUnistylesElement.tsx +45 -14
  170. package/src/core/createUnistylesTouchableHighlight.tsx +69 -0
  171. package/src/core/index.ts +2 -0
  172. package/src/diagnostics/index.ts +3 -0
  173. package/src/diagnostics/types.ts +25 -0
  174. package/src/diagnostics/verify.native.ts +7 -0
  175. package/src/diagnostics/verify.ts +13 -0
  176. package/src/index.ts +4 -1
  177. package/src/mocks.ts +41 -0
  178. package/src/reanimated/variant/useUpdateVariantColor.native.ts +7 -1
  179. package/src/specs/ShadowRegistry/index.ts +128 -1
  180. package/src/specs/ShadowRegistry/types.ts +3 -0
  181. package/src/specs/StyleSheet/index.ts +10 -2
  182. package/src/specs/UnistylesRuntime/index.ts +7 -2
  183. package/src/types/breakpoints.ts +5 -3
  184. package/src/types/stylesheet.ts +3 -3
  185. package/src/web/registry.ts +3 -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,6 @@ 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)";
15
16
 
16
17
  }
@@ -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
@@ -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
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "set"
4
4
  #include <atomic>
5
+ #include <mutex>
5
6
  #include <jsi/jsi.h>
6
7
  #include <folly/dynamic.h>
7
8
  #include <react/renderer/uimanager/UIManager.h>
@@ -29,6 +30,13 @@ struct LinkedFamily {
29
30
  bool isSuspended = false;
30
31
  };
31
32
 
33
+ struct LinkedFamilySnapshot {
34
+ std::shared_ptr<const ShadowNodeFamily> family;
35
+ std::vector<std::shared_ptr<UnistyleData>> unistyles;
36
+ bool isSuspended = false;
37
+ bool isOwnedOnlyByUnistyles = false;
38
+ };
39
+
32
40
  struct UnistylesRegistry: public StyleSheetRegistry {
33
41
  static UnistylesRegistry& get();
34
42
 
@@ -37,20 +45,20 @@ struct UnistylesRegistry: public StyleSheetRegistry {
37
45
 
38
46
  bool shouldUsePointsForBreakpoints = false;
39
47
 
40
- void registerTheme(jsi::Runtime& rt, std::string name, jsi::Value& theme);
48
+ void registerTheme(jsi::Runtime& rt, uint64_t generation, std::string name, jsi::Value& theme);
41
49
  void registerBreakpoints(std::vector<std::pair<std::string, double>>& sortedBreakpoints);
42
50
  void setPrefersAdaptiveThemes(bool prefersAdaptiveThemes);
43
51
  void setInitialThemeName(std::string themeName);
44
- void updateTheme(jsi::Runtime& rt, std::string& themeName, jsi::Function&& callback);
52
+ void updateTheme(jsi::Runtime& rt, uint64_t generation, std::string& themeName, jsi::Function&& callback);
45
53
 
46
54
  UnistylesState& getState();
47
55
  void createState();
48
56
  std::vector<std::shared_ptr<core::StyleSheet>> getStyleSheetsToRefresh(std::vector<UnistyleDependency>& unistylesDependencies);
49
- void linkShadowNodeWithUnistyle(jsi::Runtime& rt, const std::shared_ptr<const ShadowNodeFamily>& shadowNodeFamily, std::vector<std::shared_ptr<UnistyleData>>& unistylesData, std::optional<folly::dynamic> initialScopedUpdate = std::nullopt);
57
+ void linkShadowNodeWithUnistyle(jsi::Runtime& rt, uint64_t generation, const std::shared_ptr<const ShadowNodeFamily>& shadowNodeFamily, std::vector<std::shared_ptr<UnistyleData>>& unistylesData, std::optional<folly::dynamic> initialScopedUpdate = std::nullopt);
50
58
  void unlinkShadowNodeWithUnistyles(const ShadowNodeFamily*);
51
59
  void suspendShadowNode(const ShadowNodeFamily*);
52
60
  bool isSuspended(const ShadowNodeFamily*) const noexcept;
53
- std::shared_ptr<core::StyleSheet> addStyleSheet(jsi::Runtime& rt, core::StyleSheetType type, jsi::Object&& rawValue);
61
+ std::shared_ptr<core::StyleSheet> addStyleSheet(jsi::Runtime& rt, uint64_t generation, core::StyleSheetType type, jsi::Object&& rawValue);
54
62
  DependencyMap buildDependencyMap(std::vector<UnistyleDependency>& deps);
55
63
  void shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Unistyle::Shared unistyle, jsi::Value& maybePressableId);
56
64
  // call it only within trafficController.withLock!
@@ -60,13 +68,36 @@ struct UnistylesRegistry: public StyleSheetRegistry {
60
68
  void removeDuplicatedUnistyles(const ShadowNodeFamily* shadowNodeFamily, std::vector<core::Unistyle::Shared>& unistyles);
61
69
  void setScopedTheme(std::optional<std::string> themeName);
62
70
  core::Unistyle::Shared getUnistyleById(std::string unistyleID);
63
- void destroy();
71
+ std::vector<LinkedFamilySnapshot> getLinkedFamiliesSnapshot();
72
+ size_t getPendingUpdatesCount();
73
+ void setCommittedTags(std::vector<Tag>&& tags);
74
+ std::vector<Tag> takeCommittedTags();
75
+
76
+ // newest installed runtime owns the registry, generation 0 means no owner
77
+ // previous runtime's state is kept aside until it releases ownership, as it may still run JS
78
+ uint64_t takeOwnership();
79
+ void releaseOwnership(uint64_t generation);
80
+
81
+ inline bool isCurrent(uint64_t generation) const noexcept {
82
+ return generation != 0 && generation == this->_generation.load();
83
+ }
64
84
 
65
85
  private:
66
86
  using ReleasedFamilies = std::vector<std::shared_ptr<const ShadowNodeFamily>>;
67
87
 
88
+ struct OwnedState {
89
+ std::unordered_map<const ShadowNodeFamily*, LinkedFamily> shadowRegistry;
90
+ shadow::PinnedShadowLeafUpdates updates;
91
+ std::unordered_map<int, std::shared_ptr<core::StyleSheet>> styleSheets;
92
+ std::unique_ptr<UnistylesState> state;
93
+ };
94
+
68
95
  UnistylesRegistry() = default;
69
96
 
97
+ // call it only within _ownershipMutex!
98
+ // destructs nothing, so it can run on the next runtime's thread
99
+ OwnedState takeOwnedState();
100
+
70
101
  // call it only within trafficController.withLock!
71
102
  // pins are moved to releasedFamilies, so they can be released after unlocking
72
103
  void forgetFamilyUnsafe(const ShadowNodeFamily* shadowNodeFamily, ReleasedFamilies& releasedFamilies);
@@ -77,12 +108,18 @@ private:
77
108
 
78
109
  static constexpr size_t MIN_SWEEP_THRESHOLD = 64;
79
110
 
111
+ // lock order is _ownershipMutex -> trafficController, never call JS within
112
+ std::mutex _ownershipMutex;
113
+ std::atomic<uint64_t> _generation{0};
114
+ uint64_t _lastGeneration = 0;
115
+ std::unordered_map<uint64_t, OwnedState> _replacedStates{};
80
116
  static std::atomic<int> _nextStyleSheetTag;
81
117
  std::optional<std::string> _scopedTheme{};
82
118
  std::unique_ptr<UnistylesState> _state{};
83
119
  std::unordered_map<int, std::shared_ptr<core::StyleSheet>> _styleSheetRegistry{};
84
120
  std::unordered_map<const ShadowNodeFamily*, LinkedFamily> _shadowRegistry{};
85
121
  size_t _sweepThreshold = MIN_SWEEP_THRESHOLD;
122
+ std::vector<Tag> _committedTags{};
86
123
  };
87
124
 
88
125
  inline UnistylesRegistry& UnistylesRegistry::get() {