react-native-unistyles 3.2.5 → 3.4.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 (100) hide show
  1. package/README.md +3 -3
  2. package/android/CMakeLists.txt +2 -1
  3. package/cxx/converters/BackgroundImageConverter.cpp +55 -0
  4. package/cxx/converters/BackgroundImageConverter.h +14 -0
  5. package/cxx/converters/TransformOriginConverter.cpp +33 -0
  6. package/cxx/converters/TransformOriginConverter.h +13 -0
  7. package/cxx/core/UnistylesRegistry.cpp +135 -47
  8. package/cxx/core/UnistylesRegistry.h +23 -3
  9. package/cxx/hybridObjects/HybridShadowRegistry.cpp +11 -9
  10. package/cxx/parser/Parser.cpp +47 -2
  11. package/cxx/shadowTree/NativeProps.h +44 -0
  12. package/cxx/shadowTree/ShadowLeafUpdate.h +8 -0
  13. package/cxx/shadowTree/ShadowTrafficController.h +16 -37
  14. package/cxx/shadowTree/ShadowTreeManager.cpp +8 -148
  15. package/cxx/shadowTree/ShadowTreeManager.h +1 -5
  16. package/lib/commonjs/components/native/Animated.js +2 -0
  17. package/lib/commonjs/components/native/Animated.js.map +1 -1
  18. package/lib/commonjs/components/native/Pressable.js +2 -0
  19. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  20. package/lib/commonjs/components/native/Pressable.native.js +2 -0
  21. package/lib/commonjs/components/native/Pressable.native.js.map +1 -1
  22. package/lib/commonjs/index.js +0 -12
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/commonjs/server/resetServerUnistyles.js +2 -0
  25. package/lib/commonjs/server/resetServerUnistyles.js.map +1 -1
  26. package/lib/commonjs/specs/ShadowRegistry/index.js +2 -1
  27. package/lib/commonjs/specs/ShadowRegistry/index.js.map +1 -1
  28. package/lib/commonjs/web/convert/object/boxShadow.js +2 -2
  29. package/lib/commonjs/web/convert/object/boxShadow.js.map +1 -1
  30. package/lib/commonjs/web/index.js +0 -17
  31. package/lib/commonjs/web/index.js.map +1 -1
  32. package/lib/commonjs/web/listener.js +5 -0
  33. package/lib/commonjs/web/listener.js.map +1 -1
  34. package/lib/commonjs/web/registry.js +2 -0
  35. package/lib/commonjs/web/registry.js.map +1 -1
  36. package/lib/commonjs/web/shadowRegistry.js +4 -0
  37. package/lib/commonjs/web/shadowRegistry.js.map +1 -1
  38. package/lib/module/components/native/Animated.js +3 -0
  39. package/lib/module/components/native/Animated.js.map +1 -1
  40. package/lib/module/components/native/Pressable.js +2 -0
  41. package/lib/module/components/native/Pressable.js.map +1 -1
  42. package/lib/module/components/native/Pressable.native.js +2 -0
  43. package/lib/module/components/native/Pressable.native.js.map +1 -1
  44. package/lib/module/index.js +1 -1
  45. package/lib/module/index.js.map +1 -1
  46. package/lib/module/server/resetServerUnistyles.js +2 -0
  47. package/lib/module/server/resetServerUnistyles.js.map +1 -1
  48. package/lib/module/specs/ShadowRegistry/index.js +2 -1
  49. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  50. package/lib/module/web/convert/object/boxShadow.js +2 -2
  51. package/lib/module/web/convert/object/boxShadow.js.map +1 -1
  52. package/lib/module/web/index.js +0 -1
  53. package/lib/module/web/index.js.map +1 -1
  54. package/lib/module/web/listener.js +5 -0
  55. package/lib/module/web/listener.js.map +1 -1
  56. package/lib/module/web/registry.js +2 -0
  57. package/lib/module/web/registry.js.map +1 -1
  58. package/lib/module/web/shadowRegistry.js +4 -0
  59. package/lib/module/web/shadowRegistry.js.map +1 -1
  60. package/lib/typescript/src/components/native/Animated.d.ts +16 -36
  61. package/lib/typescript/src/components/native/Animated.d.ts.map +1 -1
  62. package/lib/typescript/src/components/native/Pressable.d.ts +4 -2
  63. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  64. package/lib/typescript/src/components/native/Pressable.native.d.ts +5 -2
  65. package/lib/typescript/src/components/native/Pressable.native.d.ts.map +1 -1
  66. package/lib/typescript/src/index.d.ts +1 -1
  67. package/lib/typescript/src/index.d.ts.map +1 -1
  68. package/lib/typescript/src/server/resetServerUnistyles.d.ts.map +1 -1
  69. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  70. package/lib/typescript/src/types/core.d.ts +7 -2
  71. package/lib/typescript/src/types/core.d.ts.map +1 -1
  72. package/lib/typescript/src/web/index.d.ts +0 -1
  73. package/lib/typescript/src/web/index.d.ts.map +1 -1
  74. package/lib/typescript/src/web/listener.d.ts +1 -0
  75. package/lib/typescript/src/web/listener.d.ts.map +1 -1
  76. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  77. package/lib/typescript/src/web/shadowRegistry.d.ts +1 -0
  78. package/lib/typescript/src/web/shadowRegistry.d.ts.map +1 -1
  79. package/nitrogen/generated/android/c++/JColorScheme.hpp +1 -1
  80. package/nitrogen/generated/android/c++/JDimensions.hpp +1 -1
  81. package/nitrogen/generated/android/c++/JInsets.hpp +1 -1
  82. package/nitrogen/generated/android/c++/JOrientation.hpp +1 -1
  83. package/nitrogen/generated/android/c++/JUnistyleDependency.hpp +1 -1
  84. package/nitrogen/generated/android/c++/JUnistylesNativeMiniRuntime.hpp +1 -1
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/Func_void_UnistylesNativeMiniRuntime.kt +0 -2
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/Func_void_std__vector_UnistyleDependency__UnistylesNativeMiniRuntime.kt +0 -2
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/HybridNativePlatformSpec.kt +2 -0
  88. package/package.json +28 -26
  89. package/src/components/native/Animated.tsx +18 -1
  90. package/src/components/native/Pressable.native.tsx +10 -2
  91. package/src/components/native/Pressable.tsx +7 -1
  92. package/src/index.ts +1 -1
  93. package/src/server/resetServerUnistyles.ts +2 -0
  94. package/src/specs/ShadowRegistry/index.ts +2 -1
  95. package/src/types/core.ts +7 -28
  96. package/src/web/convert/object/boxShadow.ts +2 -2
  97. package/src/web/index.ts +2 -3
  98. package/src/web/listener.ts +6 -0
  99. package/src/web/registry.ts +2 -0
  100. package/src/web/shadowRegistry.ts +5 -0
package/README.md CHANGED
@@ -58,9 +58,6 @@ Then follow [installation guides](https://unistyl.es/v3/start/getting-started) f
58
58
  <a href="https://codemask.com">
59
59
  <img src="https://avatars.githubusercontent.com/u/51229884?s=200&v=4" height="70px" width="70px" alt="codemask" />
60
60
  </a>
61
- <a href="https://galaxies.dev">
62
- <img src="https://avatars.githubusercontent.com/u/118431096?s=200&v=4" height="70px" width="70px" alt="galaxies-dev" />
63
- </a>
64
61
  <a href="https://github.com/ryanlanciaux">
65
62
  <img src="https://avatars.githubusercontent.com/u/85041?v=4" height="70px" width="70px" alt="ryanlanciaux" />
66
63
  </a>
@@ -155,6 +152,9 @@ Then follow [installation guides](https://unistyl.es/v3/start/getting-started) f
155
152
  <a href="https://github.com/AdiRishi">
156
153
  <img src="https://avatars.githubusercontent.com/u/8351234?v=4" height="60px" width="60px" alt="AdiRishi" />
157
154
  </a>
155
+ <a href="https://galaxies.dev">
156
+ <img src="https://avatars.githubusercontent.com/u/118431096?s=200&v=4" height="60px" width="60px" alt="galaxies-dev" />
157
+ </a>
158
158
 
159
159
  ## Sponsor my work
160
160
 
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.9.0)
2
2
 
3
3
  project(unistyles)
4
4
 
5
- file(GLOB_RECURSE CORE_SRC RELATIVE ${CMAKE_SOURCE_DIR} "../cxx/**/*.cpp")
5
+ file(GLOB_RECURSE CORE_SRC CONFIGURE_DEPENDS RELATIVE ${CMAKE_SOURCE_DIR} "../cxx/*.cpp")
6
6
  file(GLOB_RECURSE PLATFORM_SRC RELATIVE ${CMAKE_SOURCE_DIR} "./src/main/cxx/*.cpp")
7
7
 
8
8
  if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
@@ -21,6 +21,7 @@ include_directories(
21
21
  ./src/main/cxx
22
22
  ../cxx
23
23
  ../cxx/common
24
+ ../cxx/converters
24
25
  ../cxx/core
25
26
  ../cxx/hybridObjects
26
27
  ../cxx/parser
@@ -0,0 +1,55 @@
1
+ #include "BackgroundImageConverter.h"
2
+
3
+ #include <algorithm>
4
+
5
+ #if defined(RN_SERIALIZABLE_STATE) && \
6
+ __has_include( \
7
+ <react/renderer/components/view/BackgroundImagePropsConversions.h>)
8
+ #include <react/renderer/components/view/BackgroundImagePropsConversions.h>
9
+ #include <react/renderer/core/propsConversions.h>
10
+ #define UNISTYLES_HAS_RN_BACKGROUND_IMAGE_PARSER 1
11
+ #endif
12
+
13
+ namespace margelo::nitro::unistyles::converters {
14
+
15
+ bool isBackgroundImagePropName(const std::string &propertyName) {
16
+ return propertyName == "backgroundImage" ||
17
+ propertyName == "experimental_backgroundImage";
18
+ }
19
+
20
+ bool hasSafeBackgroundImageColorStops(const folly::dynamic &backgroundImages) {
21
+ if (!backgroundImages.isArray()) {
22
+ return false;
23
+ }
24
+
25
+ return std::all_of(
26
+ backgroundImages.begin(), backgroundImages.end(), [](const auto &image) {
27
+ if (!image.isObject() || image.count("colorStops") == 0) {
28
+ return false;
29
+ }
30
+
31
+ const auto &colorStops = image["colorStops"];
32
+
33
+ return colorStops.isArray() && colorStops.size() >= 2;
34
+ });
35
+ }
36
+
37
+ std::optional<folly::dynamic>
38
+ parseBackgroundImageString(const std::string &backgroundImageString) {
39
+ #ifdef UNISTYLES_HAS_RN_BACKGROUND_IMAGE_PARSER
40
+ std::vector<facebook::react::BackgroundImage> backgroundImages;
41
+
42
+ facebook::react::parseUnprocessedBackgroundImageString(backgroundImageString,
43
+ backgroundImages);
44
+
45
+ auto parsedBackgroundImages = facebook::react::toDynamic(backgroundImages);
46
+
47
+ return hasSafeBackgroundImageColorStops(parsedBackgroundImages)
48
+ ? std::move(parsedBackgroundImages)
49
+ : folly::dynamic::array();
50
+ #else
51
+ return std::nullopt;
52
+ #endif
53
+ }
54
+
55
+ } // namespace margelo::nitro::unistyles::converters
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ #include <folly/dynamic.h>
4
+ #include <optional>
5
+ #include <string>
6
+
7
+ namespace margelo::nitro::unistyles::converters {
8
+
9
+ bool isBackgroundImagePropName(const std::string &propertyName);
10
+ bool hasSafeBackgroundImageColorStops(const folly::dynamic &backgroundImages);
11
+ std::optional<folly::dynamic>
12
+ parseBackgroundImageString(const std::string &backgroundImageString);
13
+
14
+ } // namespace margelo::nitro::unistyles::converters
@@ -0,0 +1,33 @@
1
+ #include "TransformOriginConverter.h"
2
+
3
+ #if defined(RN_SERIALIZABLE_STATE) && \
4
+ __has_include(<react/renderer/components/view/conversions.h>)
5
+ #include <react/renderer/components/view/conversions.h>
6
+ #define UNISTYLES_HAS_RN_TRANSFORM_ORIGIN_PARSER 1
7
+ #endif
8
+
9
+ namespace margelo::nitro::unistyles::converters {
10
+
11
+ bool isTransformOriginPropName(const std::string &propertyName) {
12
+ return propertyName == "transformOrigin";
13
+ }
14
+
15
+ std::optional<folly::dynamic>
16
+ parseTransformOriginString(const std::string &transformOriginString) {
17
+ #ifdef UNISTYLES_HAS_RN_TRANSFORM_ORIGIN_PARSER
18
+ facebook::react::TransformOrigin transformOrigin;
19
+
20
+ facebook::react::parseUnprocessedTransformOriginString(transformOriginString,
21
+ transformOrigin);
22
+
23
+ if (!transformOrigin.isSet()) {
24
+ return folly::dynamic(nullptr);
25
+ }
26
+
27
+ return static_cast<folly::dynamic>(transformOrigin);
28
+ #else
29
+ return std::nullopt;
30
+ #endif
31
+ }
32
+
33
+ } // namespace margelo::nitro::unistyles::converters
@@ -0,0 +1,13 @@
1
+ #pragma once
2
+
3
+ #include <folly/dynamic.h>
4
+ #include <optional>
5
+ #include <string>
6
+
7
+ namespace margelo::nitro::unistyles::converters {
8
+
9
+ bool isTransformOriginPropName(const std::string &propertyName);
10
+ std::optional<folly::dynamic>
11
+ parseTransformOriginString(const std::string &transformOriginString);
12
+
13
+ } // namespace margelo::nitro::unistyles::converters
@@ -1,6 +1,7 @@
1
1
  #include "UnistylesRegistry.h"
2
2
  #include "UnistylesState.h"
3
3
  #include "Parser.h"
4
+ #include "NativeProps.h"
4
5
 
5
6
  using namespace margelo::nitro::unistyles;
6
7
  using namespace facebook;
@@ -60,37 +61,85 @@ void core::UnistylesRegistry::updateTheme(jsi::Runtime& rt, std::string& themeNa
60
61
 
61
62
  void core::UnistylesRegistry::linkShadowNodeWithUnistyle(
62
63
  jsi::Runtime& rt,
63
- const ShadowNodeFamily* shadowNodeFamily,
64
+ const std::shared_ptr<const ShadowNodeFamily>& shadowNodeFamily,
64
65
  std::vector<std::shared_ptr<UnistyleData>>& unistylesData,
65
66
  std::optional<folly::dynamic> initialScopedUpdate
66
67
  ) {
67
- this->trafficController.withLock([this, &rt, &unistylesData, shadowNodeFamily, &initialScopedUpdate](){
68
+ // released after unlocking, so a family destructor never runs within the lock
69
+ ReleasedFamilies releasedFamilies;
70
+
71
+ this->trafficController.withLock([this, &unistylesData, &shadowNodeFamily, &initialScopedUpdate, &releasedFamilies](){
72
+ auto family = shadowNodeFamily.get();
73
+ auto it = this->_shadowRegistry.find(family);
74
+
68
75
  // Clear suspension state if this family was previously suspended
69
- if (_suspendedFamilies.erase(shadowNodeFamily) > 0) {
70
- auto* mutableFamily = const_cast<ShadowNodeFamily*>(shadowNodeFamily);
71
- mutableFamily->nativeProps_DEPRECATED.reset();
76
+ if (it != this->_shadowRegistry.end() && it->second.isSuspended) {
77
+ shadow::resetNativeProps(family);
72
78
  // Clear old registry entries to prevent stale UnistyleData accumulation
73
- this->_shadowRegistry.erase(shadowNodeFamily);
74
- // Remove any stale traffic controller entry (e.g. from a theme change during suspension)
75
- this->trafficController.removeShadowNode(shadowNodeFamily);
79
+ // and any stale traffic controller entry (e.g. from a theme change during suspension)
80
+ this->forgetFamilyUnsafe(family, releasedFamilies);
76
81
  }
77
82
 
78
- std::for_each(unistylesData.begin(), unistylesData.end(), [this, shadowNodeFamily](std::shared_ptr<UnistyleData> unistyleData){
79
- this->_shadowRegistry[shadowNodeFamily].emplace_back(unistyleData);
80
- });
83
+ auto& linkedFamily = this->_shadowRegistry[family];
84
+
85
+ linkedFamily.family = shadowNodeFamily;
86
+ linkedFamily.unistyles.insert(linkedFamily.unistyles.end(), unistylesData.begin(), unistylesData.end());
81
87
 
82
88
  // Required for scoped themes to apply on initial mount
83
89
  if (initialScopedUpdate.has_value()) {
84
- shadow::ShadowLeafUpdates updates;
90
+ this->trafficController.setUpdate(shadowNodeFamily, std::move(*initialScopedUpdate));
91
+ }
85
92
 
86
- updates.emplace(shadowNodeFamily, std::move(*initialScopedUpdate));
87
- this->trafficController.setUpdates(updates);
93
+ // families unmounted without unlink (eg. while frozen) are pinned by us only
94
+ // sweep them once the registry doubles, so it's amortized O(1) per link
95
+ if (this->_shadowRegistry.size() >= this->_sweepThreshold) {
96
+ this->sweepUnownedFamiliesUnsafe(releasedFamilies);
97
+ this->_sweepThreshold = std::max(MIN_SWEEP_THRESHOLD, this->_shadowRegistry.size() * 2);
88
98
  }
89
99
  });
90
100
  }
91
101
 
102
+ bool core::UnistylesRegistry::isOwnedOnlyByUnistyles(const LinkedFamily& linkedFamily) noexcept {
103
+ // shadow nodes own their family, so if nothing but our pin owns it, the family is not part of any
104
+ // shadow tree anymore. React Native can't get it back, as it only keeps weak references beside shadow nodes
105
+ return linkedFamily.family.use_count() == 1;
106
+ }
107
+
108
+ void core::UnistylesRegistry::forgetFamilyUnsafe(const ShadowNodeFamily* shadowNodeFamily, ReleasedFamilies& releasedFamilies) {
109
+ auto it = this->_shadowRegistry.find(shadowNodeFamily);
110
+
111
+ if (it != this->_shadowRegistry.end()) {
112
+ releasedFamilies.emplace_back(std::move(it->second.family));
113
+ this->_shadowRegistry.erase(it);
114
+ }
115
+
116
+ if (auto pendingFamily = this->trafficController.removeShadowNode(shadowNodeFamily)) {
117
+ releasedFamilies.emplace_back(std::move(pendingFamily));
118
+ }
119
+ }
120
+
121
+ void core::UnistylesRegistry::sweepUnownedFamiliesUnsafe(ReleasedFamilies& releasedFamilies) {
122
+ std::vector<const ShadowNodeFamily*> unownedFamilies;
123
+
124
+ for (const auto& [family, linkedFamily] : this->_shadowRegistry) {
125
+ if (isOwnedOnlyByUnistyles(linkedFamily)) {
126
+ unownedFamilies.emplace_back(family);
127
+ }
128
+ }
129
+
130
+ for (const auto* family : unownedFamilies) {
131
+ this->forgetFamilyUnsafe(family, releasedFamilies);
132
+ }
133
+ }
134
+
92
135
  void core::UnistylesRegistry::removeDuplicatedUnistyles(const ShadowNodeFamily *shadowNodeFamily, std::vector<core::Unistyle::Shared>& unistyles) {
93
- auto targetFamilyUnistyles = this->_shadowRegistry[shadowNodeFamily];
136
+ auto it = this->_shadowRegistry.find(shadowNodeFamily);
137
+
138
+ if (it == this->_shadowRegistry.end()) {
139
+ return;
140
+ }
141
+
142
+ auto& targetFamilyUnistyles = it->second.unistyles;
94
143
 
95
144
  unistyles.erase(
96
145
  std::remove_if(
@@ -111,23 +160,27 @@ void core::UnistylesRegistry::removeDuplicatedUnistyles(const ShadowNodeFamily *
111
160
  }
112
161
 
113
162
  void core::UnistylesRegistry::unlinkShadowNodeWithUnistyles(const ShadowNodeFamily* shadowNodeFamily) {
114
- this->trafficController.withLock([this, shadowNodeFamily](){
115
- this->_shadowRegistry.erase(shadowNodeFamily);
116
- this->_suspendedFamilies.erase(shadowNodeFamily);
117
- this->trafficController.removeShadowNode(shadowNodeFamily);
163
+ ReleasedFamilies releasedFamilies;
164
+
165
+ this->trafficController.withLock([this, shadowNodeFamily, &releasedFamilies](){
166
+ this->forgetFamilyUnsafe(shadowNodeFamily, releasedFamilies);
118
167
  });
119
168
  }
120
169
 
121
170
  void core::UnistylesRegistry::suspendShadowNode(const ShadowNodeFamily* shadowNodeFamily) {
122
171
  this->trafficController.withLock([this, shadowNodeFamily](){
123
- if (this->_shadowRegistry.contains(shadowNodeFamily)) {
124
- this->_suspendedFamilies.insert(shadowNodeFamily);
172
+ auto it = this->_shadowRegistry.find(shadowNodeFamily);
173
+
174
+ if (it != this->_shadowRegistry.end()) {
175
+ it->second.isSuspended = true;
125
176
  }
126
177
  });
127
178
  }
128
179
 
129
180
  bool core::UnistylesRegistry::isSuspended(const ShadowNodeFamily* family) const noexcept {
130
- return _suspendedFamilies.count(family) > 0;
181
+ auto it = this->_shadowRegistry.find(family);
182
+
183
+ return it != this->_shadowRegistry.end() && it->second.isSuspended;
131
184
  }
132
185
 
133
186
  std::shared_ptr<core::StyleSheet> core::UnistylesRegistry::addStyleSheet(jsi::Runtime& rt, core::StyleSheetType type, jsi::Object&& rawValue) {
@@ -143,36 +196,56 @@ core::DependencyMap core::UnistylesRegistry::buildDependencyMap(std::vector<Unis
143
196
  core::DependencyMap dependencyMap;
144
197
 
145
198
  std::unordered_set<UnistyleDependency> uniqueDependencies(deps.begin(), deps.end());
199
+ ReleasedFamilies releasedFamilies;
200
+
201
+ // families stay pinned by the registry until the shadow tree update is committed
202
+ this->trafficController.withLock([this, &dependencyMap, &uniqueDependencies, &releasedFamilies](){
203
+ // we iterate whole registry anyway, so it's a free moment to drop families unmounted without unlink
204
+ this->sweepUnownedFamiliesUnsafe(releasedFamilies);
205
+
206
+ for (const auto& [family, linkedFamily] : this->_shadowRegistry) {
207
+ bool hasAnyOfDependencies = false;
208
+
209
+ // Check if any dependency matches
210
+ for (const auto& unistyleData : linkedFamily.unistyles) {
211
+ for (const auto& dep : unistyleData->unistyle->dependencies) {
212
+ if (uniqueDependencies.count(dep)) {
213
+ hasAnyOfDependencies = true;
214
+ break;
215
+ }
216
+ }
146
217
 
147
- for (const auto& [family, unistyles] : this->_shadowRegistry) {
148
- bool hasAnyOfDependencies = false;
149
-
150
- // Check if any dependency matches
151
- for (const auto& unistyleData : unistyles) {
152
- for (const auto& dep : unistyleData->unistyle->dependencies) {
153
- if (uniqueDependencies.count(dep)) {
154
- hasAnyOfDependencies = true;
218
+ if (hasAnyOfDependencies) {
155
219
  break;
156
- }
220
+ };
157
221
  }
158
222
 
159
- if (hasAnyOfDependencies) {
160
- break;
161
- };
223
+ if (!hasAnyOfDependencies) {
224
+ continue;
225
+ }
226
+
227
+ dependencyMap[family].insert(
228
+ dependencyMap[family].end(),
229
+ linkedFamily.unistyles.begin(),
230
+ linkedFamily.unistyles.end()
231
+ );
162
232
  }
233
+ });
234
+
235
+ return dependencyMap;
236
+ }
163
237
 
164
- if (!hasAnyOfDependencies) {
238
+ void core::UnistylesRegistry::queueShadowLeafUpdatesUnsafe(shadow::ShadowLeafUpdates& updates) {
239
+ for (auto& [family, props] : updates) {
240
+ auto it = this->_shadowRegistry.find(family);
241
+
242
+ // family was unlinked in the meantime, there is nothing to update
243
+ if (it == this->_shadowRegistry.end()) {
165
244
  continue;
166
245
  }
167
246
 
168
- dependencyMap[family].insert(
169
- dependencyMap[family].end(),
170
- unistyles.begin(),
171
- unistyles.end()
172
- );
247
+ this->trafficController.setUpdate(it->second.family, std::move(props));
173
248
  }
174
-
175
- return dependencyMap;
176
249
  }
177
250
 
178
251
  // called from proxied function only, we don't know host
@@ -185,15 +258,15 @@ void core::UnistylesRegistry::shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Uni
185
258
  ? std::make_optional(maybePressableId.asString(rt).utf8(rt))
186
259
  : std::nullopt;
187
260
 
188
- for (const auto& [family, unistyles] : this->_shadowRegistry) {
189
- for (const auto& unistyleData : unistyles) {
261
+ for (const auto& [family, linkedFamily] : this->_shadowRegistry) {
262
+ for (const auto& unistyleData : linkedFamily.unistyles) {
190
263
  if (unistyleData->unistyle == unistyle) {
191
264
  updates[family] = parser.parseStylesToShadowTreeStyles(rt, { unistyleData });
192
265
  }
193
266
  }
194
267
  }
195
268
 
196
- this->trafficController.setUpdates(updates);
269
+ this->queueShadowLeafUpdatesUnsafe(updates);
197
270
  });
198
271
  }
199
272
 
@@ -263,10 +336,25 @@ void core::UnistylesRegistry::setScopedTheme(std::optional<std::string> themeNam
263
336
  }
264
337
 
265
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;
342
+
343
+ this->trafficController.withLock([this, &releasedFamilies](){
344
+ for (auto& [_, linkedFamily] : this->_shadowRegistry) {
345
+ releasedFamilies.emplace_back(std::move(linkedFamily.family));
346
+ }
347
+
348
+ for (auto& [_, pendingUpdate] : this->trafficController.takeUpdates()) {
349
+ releasedFamilies.emplace_back(std::move(pendingUpdate.family));
350
+ }
351
+
352
+ this->_shadowRegistry.clear();
353
+ this->_sweepThreshold = MIN_SWEEP_THRESHOLD;
354
+ });
355
+
266
356
  this->_state.reset();
267
357
  this->_styleSheetRegistry.clear();
268
- this->_shadowRegistry.clear();
269
- this->_suspendedFamilies.clear();
270
358
  this->_scopedTheme = std::nullopt;
271
359
  _nextStyleSheetTag.store(0);
272
360
  }
@@ -23,6 +23,12 @@ using namespace facebook::react;
23
23
 
24
24
  using DependencyMap = std::unordered_map<const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>>;
25
25
 
26
+ struct LinkedFamily {
27
+ std::shared_ptr<const ShadowNodeFamily> family;
28
+ std::vector<std::shared_ptr<UnistyleData>> unistyles;
29
+ bool isSuspended = false;
30
+ };
31
+
26
32
  struct UnistylesRegistry: public StyleSheetRegistry {
27
33
  static UnistylesRegistry& get();
28
34
 
@@ -40,13 +46,15 @@ struct UnistylesRegistry: public StyleSheetRegistry {
40
46
  UnistylesState& getState();
41
47
  void createState();
42
48
  std::vector<std::shared_ptr<core::StyleSheet>> getStyleSheetsToRefresh(std::vector<UnistyleDependency>& unistylesDependencies);
43
- void linkShadowNodeWithUnistyle(jsi::Runtime& rt, const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>& unistylesData, std::optional<folly::dynamic> initialScopedUpdate = std::nullopt);
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);
44
50
  void unlinkShadowNodeWithUnistyles(const ShadowNodeFamily*);
45
51
  void suspendShadowNode(const ShadowNodeFamily*);
46
52
  bool isSuspended(const ShadowNodeFamily*) const noexcept;
47
53
  std::shared_ptr<core::StyleSheet> addStyleSheet(jsi::Runtime& rt, core::StyleSheetType type, jsi::Object&& rawValue);
48
54
  DependencyMap buildDependencyMap(std::vector<UnistyleDependency>& deps);
49
55
  void shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Unistyle::Shared unistyle, jsi::Value& maybePressableId);
56
+ // call it only within trafficController.withLock!
57
+ void queueShadowLeafUpdatesUnsafe(shadow::ShadowLeafUpdates& updates);
50
58
  shadow::ShadowTrafficController trafficController{};
51
59
  const std::optional<std::string> getScopedTheme();
52
60
  void removeDuplicatedUnistyles(const ShadowNodeFamily* shadowNodeFamily, std::vector<core::Unistyle::Shared>& unistyles);
@@ -55,14 +63,26 @@ struct UnistylesRegistry: public StyleSheetRegistry {
55
63
  void destroy();
56
64
 
57
65
  private:
66
+ using ReleasedFamilies = std::vector<std::shared_ptr<const ShadowNodeFamily>>;
67
+
58
68
  UnistylesRegistry() = default;
59
69
 
70
+ // call it only within trafficController.withLock!
71
+ // pins are moved to releasedFamilies, so they can be released after unlocking
72
+ void forgetFamilyUnsafe(const ShadowNodeFamily* shadowNodeFamily, ReleasedFamilies& releasedFamilies);
73
+ void sweepUnownedFamiliesUnsafe(ReleasedFamilies& releasedFamilies);
74
+
75
+ // React Native doesn't own the family anymore, it was unmounted without unlink
76
+ static bool isOwnedOnlyByUnistyles(const LinkedFamily& linkedFamily) noexcept;
77
+
78
+ static constexpr size_t MIN_SWEEP_THRESHOLD = 64;
79
+
60
80
  static std::atomic<int> _nextStyleSheetTag;
61
81
  std::optional<std::string> _scopedTheme{};
62
82
  std::unique_ptr<UnistylesState> _state{};
63
83
  std::unordered_map<int, std::shared_ptr<core::StyleSheet>> _styleSheetRegistry{};
64
- std::unordered_map<const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>> _shadowRegistry{};
65
- std::unordered_set<const ShadowNodeFamily*> _suspendedFamilies{};
84
+ std::unordered_map<const ShadowNodeFamily*, LinkedFamily> _shadowRegistry{};
85
+ size_t _sweepThreshold = MIN_SWEEP_THRESHOLD;
66
86
  };
67
87
 
68
88
  inline UnistylesRegistry& UnistylesRegistry::get() {
@@ -11,10 +11,11 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa
11
11
  std::vector<core::Unistyle::Shared> unistyleWrappers = core::unistyleFromValue(rt, args[1]);
12
12
  std::vector<std::vector<folly::dynamic>> arguments;
13
13
  auto& registry = core::UnistylesRegistry::get();
14
+ const bool wasSuspended = registry.isSuspended(&shadowNodeWrapper->getFamily());
14
15
 
15
16
  // this is special case for Animated, and prevents appending same unistyles to node
16
17
  // skip for suspended families - they need a full re-link with fresh UnistyleData
17
- if (!registry.isSuspended(&shadowNodeWrapper->getFamily())) {
18
+ if (!wasSuspended) {
18
19
  registry.removeDuplicatedUnistyles(&shadowNodeWrapper->getFamily(), unistyleWrappers);
19
20
 
20
21
  if (unistyleWrappers.empty()) {
@@ -86,9 +87,10 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa
86
87
  parser.rebuildUnistyleWithScopedTheme(rt, parsedStyleSheet, unistyleData);
87
88
  }
88
89
 
89
- // Initialize parsedStyle from the unistyle's current computed style from withUnistyles
90
+ // The shared cache can belong to another view by the time React attaches refs.
91
+ // Restore this view's arguments and variants before saving its native styles.
90
92
  if (!unistyleData->parsedStyle.has_value() && unistyle->parsedStyle.has_value()) {
91
- unistyleData->parsedStyle = jsi::Value(rt, unistyle->parsedStyle.value()).asObject(rt);
93
+ parser.rebuildUnistyleWithVariants(rt, unistyleData);
92
94
  }
93
95
 
94
96
  unistylesData.emplace_back(unistyleData);
@@ -96,17 +98,21 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa
96
98
 
97
99
  std::optional<folly::dynamic> initialScopedUpdate;
98
100
 
99
- if (scopedTheme.has_value()) {
101
+ if (scopedTheme.has_value() || wasSuspended) {
100
102
  initialScopedUpdate = parser.parseStylesToShadowTreeStyles(rt, unistylesData);
101
103
  }
102
104
 
103
105
  registry.linkShadowNodeWithUnistyle(
104
106
  rt,
105
- &shadowNodeWrapper->getFamily(),
107
+ shadowNodeWrapper->getFamilyShared(),
106
108
  unistylesData,
107
109
  std::move(initialScopedUpdate)
108
110
  );
109
111
 
112
+ if (wasSuspended) {
113
+ shadow::ShadowTreeManager::updateShadowTree(rt);
114
+ }
115
+
110
116
  return jsi::Value::undefined();
111
117
  }
112
118
 
@@ -165,9 +171,5 @@ jsi::Value HybridShadowRegistry::getScopedTheme(jsi::Runtime &rt, const jsi::Val
165
171
  }
166
172
 
167
173
  std::shared_ptr<const core::ShadowNode> HybridShadowRegistry::getShadowNodeFromRef(jsi::Runtime& rt, const jsi::Value& maybeRef) {
168
- #if REACT_NATIVE_VERSION_MINOR >= 81
169
174
  return Bridging<std::shared_ptr<const ShadowNode>>::fromJs(rt, maybeRef);
170
- #else
171
- return shadowNodeFromValue(rt, maybeRef);
172
- #endif
173
175
  }
@@ -1,5 +1,7 @@
1
1
  #include "Parser.h"
2
2
  #include "UnistyleWrapper.h"
3
+ #include "BackgroundImageConverter.h"
4
+ #include "TransformOriginConverter.h"
3
5
  #include <iomanip>
4
6
  #include <sstream>
5
7
  #include <react/renderer/css/CSSFilter.h>
@@ -442,8 +444,7 @@ void parser::Parser::rebuildShadowLeafUpdates(jsi::Runtime& rt, core::Dependency
442
444
  updates.emplace(shadowNode, std::move(rawProps));
443
445
  }
444
446
 
445
- registry.trafficController.setUpdates(updates);
446
- registry.trafficController.resumeUnistylesTraffic();
447
+ registry.queueShadowLeafUpdatesUnsafe(updates);
447
448
  });
448
449
  }
449
450
 
@@ -1072,6 +1073,50 @@ folly::dynamic parser::Parser::parseStylesToShadowTreeStyles(jsi::Runtime& rt, c
1072
1073
  rt,
1073
1074
  unistyleData->parsedStyle.value(),
1074
1075
  [this, &rt, &state, &convertedStyles](const std::string& propertyName, jsi::Value& propertyValue) {
1076
+ if (converters::isBackgroundImagePropName(propertyName) && propertyValue.isString()) {
1077
+ auto maybeBackgroundImage = converters::parseBackgroundImageString(propertyValue.asString(rt).utf8(rt));
1078
+
1079
+ if (maybeBackgroundImage.has_value()) {
1080
+ convertedStyles.setProperty(
1081
+ rt,
1082
+ propertyName.c_str(),
1083
+ jsi::valueFromDynamic(rt, maybeBackgroundImage.value())
1084
+ );
1085
+
1086
+ return;
1087
+ }
1088
+
1089
+ convertedStyles.setProperty(
1090
+ rt,
1091
+ propertyName.c_str(),
1092
+ propertyValue
1093
+ );
1094
+
1095
+ return;
1096
+ }
1097
+
1098
+ if (converters::isTransformOriginPropName(propertyName) && propertyValue.isString()) {
1099
+ auto maybeTransformOrigin = converters::parseTransformOriginString(propertyValue.asString(rt).utf8(rt));
1100
+
1101
+ if (maybeTransformOrigin.has_value()) {
1102
+ convertedStyles.setProperty(
1103
+ rt,
1104
+ propertyName.c_str(),
1105
+ jsi::valueFromDynamic(rt, maybeTransformOrigin.value())
1106
+ );
1107
+
1108
+ return;
1109
+ }
1110
+
1111
+ convertedStyles.setProperty(
1112
+ rt,
1113
+ propertyName.c_str(),
1114
+ propertyValue
1115
+ );
1116
+
1117
+ return;
1118
+ }
1119
+
1075
1120
  if (this->isColor(propertyName)) {
1076
1121
  if (propertyValue.isString()) {
1077
1122
  convertedStyles.setProperty(
@@ -0,0 +1,44 @@
1
+ #pragma once
2
+
3
+ #include <folly/dynamic.h>
4
+ #include <react/renderer/core/ShadowNodeFamily.h>
5
+ #include <cxxreact/ReactNativeVersion.h>
6
+ #include <memory>
7
+ #include <mutex>
8
+
9
+ // React Native reads ShadowNodeFamily::nativeProps_DEPRECATED from any thread
10
+ // (eg. ShadowNode::clone during main thread layout) and since 0.86.1 guards every access with nativePropsMutex
11
+ // we need to hold the same lock, otherwise mutating / replacing the folly::dynamic races with RN copying it
12
+ #if REACT_NATIVE_VERSION_MINOR > 86 || (REACT_NATIVE_VERSION_MINOR == 86 && REACT_NATIVE_VERSION_PATCH >= 1)
13
+ #define UNISTYLES_HAS_NATIVE_PROPS_MUTEX 1
14
+ #else
15
+ #define UNISTYLES_HAS_NATIVE_PROPS_MUTEX 0
16
+ #endif
17
+
18
+ namespace margelo::nitro::unistyles::shadow {
19
+
20
+ using namespace facebook::react;
21
+
22
+ inline void mergeNativeProps(const ShadowNodeFamily* family, const folly::dynamic& props) {
23
+ #if UNISTYLES_HAS_NATIVE_PROPS_MUTEX
24
+ std::lock_guard<std::mutex> lock(family->nativePropsMutex);
25
+ #endif
26
+
27
+ if (family->nativeProps_DEPRECATED && family->nativeProps_DEPRECATED->isObject()) {
28
+ family->nativeProps_DEPRECATED->update(props);
29
+
30
+ return;
31
+ }
32
+
33
+ family->nativeProps_DEPRECATED = std::make_unique<folly::dynamic>(props);
34
+ }
35
+
36
+ inline void resetNativeProps(const ShadowNodeFamily* family) {
37
+ #if UNISTYLES_HAS_NATIVE_PROPS_MUTEX
38
+ std::lock_guard<std::mutex> lock(family->nativePropsMutex);
39
+ #endif
40
+
41
+ family->nativeProps_DEPRECATED.reset();
42
+ }
43
+
44
+ }