react-native-unistyles 3.6.0 → 3.7.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 (142) hide show
  1. package/README.md +4 -9
  2. package/android/build.gradle +42 -0
  3. package/android/src/legacy/java/com/unistyles/NativePlatform+extraWindows.kt +16 -0
  4. package/android/src/main/cxx/NativeUnistylesModule.cpp +5 -11
  5. package/android/src/main/cxx/NativeUnistylesModule.h +0 -4
  6. package/android/src/main/java/com/unistyles/NativePlatform+android.kt +82 -39
  7. package/android/src/main/java/com/unistyles/NativePlatform+insets.kt +73 -0
  8. package/android/src/main/java/com/unistyles/UnistylesModule.kt +2 -13
  9. package/android/src/rn86/java/com/unistyles/NativePlatform+extraWindows.kt +28 -0
  10. package/cxx/common/PerformanceTracing.h +85 -0
  11. package/cxx/converters/BackgroundLayerConverter.cpp +523 -0
  12. package/cxx/converters/BackgroundLayerConverter.h +14 -0
  13. package/cxx/converters/FontVariationSettingsConverter.cpp +104 -0
  14. package/cxx/converters/FontVariationSettingsConverter.h +17 -0
  15. package/cxx/core/UnistyleWrapper.h +16 -1
  16. package/cxx/hybridObjects/HybridShadowRegistry.cpp +8 -0
  17. package/cxx/hybridObjects/HybridStyleSheet.cpp +11 -0
  18. package/cxx/hybridObjects/HybridStyleSheet.h +1 -0
  19. package/cxx/hybridObjects/HybridUnistylesRuntime.cpp +10 -30
  20. package/cxx/hybridObjects/HybridUnistylesRuntime.h +2 -2
  21. package/cxx/parser/Parser.cpp +75 -10
  22. package/cxx/shadowTree/ShadowTreeDiagnostics.cpp +74 -0
  23. package/cxx/shadowTree/ShadowTreeManager.cpp +3 -0
  24. package/lib/commonjs/components/NamedTheme.js +4 -4
  25. package/lib/commonjs/components/NamedTheme.js.map +1 -1
  26. package/lib/commonjs/components/ScopedThemeContext.js +3 -1
  27. package/lib/commonjs/components/ScopedThemeContext.js.map +1 -1
  28. package/lib/commonjs/core/createUnistylesImageBackground.js +6 -2
  29. package/lib/commonjs/core/createUnistylesImageBackground.js.map +1 -1
  30. package/lib/commonjs/core/withUnistyles/withUnistyles.js +10 -7
  31. package/lib/commonjs/core/withUnistyles/withUnistyles.js.map +1 -1
  32. package/lib/commonjs/core/withUnistyles/withUnistyles.native.js +22 -3
  33. package/lib/commonjs/core/withUnistyles/withUnistyles.native.js.map +1 -1
  34. package/lib/commonjs/mocks.js +27 -3
  35. package/lib/commonjs/mocks.js.map +1 -1
  36. package/lib/commonjs/specs/ShadowRegistry/index.js +13 -3
  37. package/lib/commonjs/specs/ShadowRegistry/index.js.map +1 -1
  38. package/lib/commonjs/specs/ShadowRegistry/tracing.js +26 -0
  39. package/lib/commonjs/specs/ShadowRegistry/tracing.js.map +1 -0
  40. package/lib/commonjs/utils.js +52 -1
  41. package/lib/commonjs/utils.js.map +1 -1
  42. package/lib/commonjs/web/convert/index.js +19 -1
  43. package/lib/commonjs/web/convert/index.js.map +1 -1
  44. package/lib/commonjs/web/convert/module.d.ts +3 -0
  45. package/lib/commonjs/web/convert/object/backgroundLayers.js +30 -0
  46. package/lib/commonjs/web/convert/object/backgroundLayers.js.map +1 -0
  47. package/lib/commonjs/web/convert/object/fontVariationSettings.js +12 -0
  48. package/lib/commonjs/web/convert/object/fontVariationSettings.js.map +1 -0
  49. package/lib/commonjs/web/convert/object/index.js +22 -0
  50. package/lib/commonjs/web/convert/object/index.js.map +1 -1
  51. package/lib/commonjs/web/convert/utils.js +7 -1
  52. package/lib/commonjs/web/convert/utils.js.map +1 -1
  53. package/lib/commonjs/web/registry.js +6 -4
  54. package/lib/commonjs/web/registry.js.map +1 -1
  55. package/lib/commonjs/web/runtime.js +9 -6
  56. package/lib/commonjs/web/runtime.js.map +1 -1
  57. package/lib/commonjs/web/utils/unistyle.js +1 -3
  58. package/lib/commonjs/web/utils/unistyle.js.map +1 -1
  59. package/lib/module/components/NamedTheme.js +4 -4
  60. package/lib/module/components/NamedTheme.js.map +1 -1
  61. package/lib/module/components/ScopedThemeContext.js +3 -1
  62. package/lib/module/components/ScopedThemeContext.js.map +1 -1
  63. package/lib/module/core/createUnistylesImageBackground.js +6 -2
  64. package/lib/module/core/createUnistylesImageBackground.js.map +1 -1
  65. package/lib/module/core/withUnistyles/withUnistyles.js +9 -6
  66. package/lib/module/core/withUnistyles/withUnistyles.js.map +1 -1
  67. package/lib/module/core/withUnistyles/withUnistyles.native.js +24 -5
  68. package/lib/module/core/withUnistyles/withUnistyles.native.js.map +1 -1
  69. package/lib/module/mocks.js +27 -3
  70. package/lib/module/mocks.js.map +1 -1
  71. package/lib/module/specs/ShadowRegistry/index.js +13 -3
  72. package/lib/module/specs/ShadowRegistry/index.js.map +1 -1
  73. package/lib/module/specs/ShadowRegistry/tracing.js +21 -0
  74. package/lib/module/specs/ShadowRegistry/tracing.js.map +1 -0
  75. package/lib/module/utils.js +50 -0
  76. package/lib/module/utils.js.map +1 -1
  77. package/lib/module/web/convert/index.js +22 -3
  78. package/lib/module/web/convert/index.js.map +1 -1
  79. package/lib/module/web/convert/module.d.ts +3 -0
  80. package/lib/module/web/convert/object/backgroundLayers.js +25 -0
  81. package/lib/module/web/convert/object/backgroundLayers.js.map +1 -0
  82. package/lib/module/web/convert/object/fontVariationSettings.js +7 -0
  83. package/lib/module/web/convert/object/fontVariationSettings.js.map +1 -0
  84. package/lib/module/web/convert/object/index.js +2 -0
  85. package/lib/module/web/convert/object/index.js.map +1 -1
  86. package/lib/module/web/convert/utils.js +4 -0
  87. package/lib/module/web/convert/utils.js.map +1 -1
  88. package/lib/module/web/registry.js +6 -4
  89. package/lib/module/web/registry.js.map +1 -1
  90. package/lib/module/web/runtime.js +9 -6
  91. package/lib/module/web/runtime.js.map +1 -1
  92. package/lib/module/web/utils/unistyle.js +2 -4
  93. package/lib/module/web/utils/unistyle.js.map +1 -1
  94. package/lib/typescript/src/components/ScopedThemeContext.d.ts +2 -1
  95. package/lib/typescript/src/components/ScopedThemeContext.d.ts.map +1 -1
  96. package/lib/typescript/src/core/createUnistylesImageBackground.d.ts.map +1 -1
  97. package/lib/typescript/src/core/withUnistyles/withUnistyles.d.ts +6 -3
  98. package/lib/typescript/src/core/withUnistyles/withUnistyles.d.ts.map +1 -1
  99. package/lib/typescript/src/core/withUnistyles/withUnistyles.native.d.ts +7 -4
  100. package/lib/typescript/src/core/withUnistyles/withUnistyles.native.d.ts.map +1 -1
  101. package/lib/typescript/src/specs/ShadowRegistry/index.d.ts.map +1 -1
  102. package/lib/typescript/src/specs/ShadowRegistry/tracing.d.ts +2 -0
  103. package/lib/typescript/src/specs/ShadowRegistry/tracing.d.ts.map +1 -0
  104. package/lib/typescript/src/utils.d.ts +1 -0
  105. package/lib/typescript/src/utils.d.ts.map +1 -1
  106. package/lib/typescript/src/web/convert/index.d.ts.map +1 -1
  107. package/lib/typescript/src/web/convert/object/backgroundLayers.d.ts +4 -0
  108. package/lib/typescript/src/web/convert/object/backgroundLayers.d.ts.map +1 -0
  109. package/lib/typescript/src/web/convert/object/fontVariationSettings.d.ts +4 -0
  110. package/lib/typescript/src/web/convert/object/fontVariationSettings.d.ts.map +1 -0
  111. package/lib/typescript/src/web/convert/object/index.d.ts +2 -0
  112. package/lib/typescript/src/web/convert/object/index.d.ts.map +1 -1
  113. package/lib/typescript/src/web/convert/utils.d.ts +2 -0
  114. package/lib/typescript/src/web/convert/utils.d.ts.map +1 -1
  115. package/lib/typescript/src/web/registry.d.ts.map +1 -1
  116. package/lib/typescript/src/web/runtime.d.ts +1 -0
  117. package/lib/typescript/src/web/runtime.d.ts.map +1 -1
  118. package/lib/typescript/src/web/utils/unistyle.d.ts.map +1 -1
  119. package/metro/babel-transformer.js +79 -17
  120. package/package.json +26 -26
  121. package/plugin/index.js +79 -17
  122. package/src/components/NamedTheme.tsx +5 -5
  123. package/src/components/ScopedThemeContext.ts +6 -2
  124. package/src/core/createUnistylesImageBackground.tsx +6 -2
  125. package/src/core/withUnistyles/withUnistyles.native.tsx +28 -6
  126. package/src/core/withUnistyles/withUnistyles.tsx +6 -6
  127. package/src/mocks.ts +14 -5
  128. package/src/specs/ShadowRegistry/index.ts +14 -3
  129. package/src/specs/ShadowRegistry/tracing.ts +23 -0
  130. package/src/utils.ts +51 -0
  131. package/src/web/convert/index.ts +39 -3
  132. package/src/web/convert/object/backgroundLayers.ts +33 -0
  133. package/src/web/convert/object/fontVariationSettings.ts +7 -0
  134. package/src/web/convert/object/index.ts +2 -0
  135. package/src/web/convert/utils.ts +11 -0
  136. package/src/web/registry.ts +7 -8
  137. package/src/web/runtime.ts +7 -7
  138. package/src/web/utils/unistyle.ts +2 -4
  139. package/lib/commonjs/web/convert/module.d.js +0 -2
  140. package/lib/commonjs/web/convert/module.d.js.map +0 -1
  141. package/lib/module/web/convert/module.d.js +0 -2
  142. package/lib/module/web/convert/module.d.js.map +0 -1
package/README.md CHANGED
@@ -34,7 +34,7 @@ yarn add react-native-unistyles react-native-nitro-modules
34
34
  | >= 3.3.0 | >= 0.36.1 |
35
35
  | >= 3.4.0 | >= 0.37.1 |
36
36
 
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`.
37
+ > **Note:** You don't need `react-native-edge-to-edge` (optional since v3.1.0, now deprecated). We strongly recommend setting `edgeToEdgeEnabled=true` in your `android/gradle.properties` instead — it enforces translucent system bars on modals, disables legacy StatusBar hacks, and enables additional React Native core fixes. **Expo SDK 54+** enables this automatically.
38
38
 
39
39
  Then follow [installation guides](https://unistyl.es/v3/start/getting-started) for your platform.
40
40
 
@@ -80,9 +80,6 @@ npx skills add jpudysz/react-native-unistyles
80
80
  <a href="https://github.com/jordmccord">
81
81
  <img src="https://avatars.githubusercontent.com/u/7591840?v=4" height="70px" width="70px" alt="jordmccord" />
82
82
  </a>
83
- <a href="https://github.com/kerwanp">
84
- <img src="https://avatars.githubusercontent.com/u/36955373?v=4" height="70px" width="70px" alt="kerwanp" />
85
- </a>
86
83
  <a href="https://github.com/andkindness">
87
84
  <img src="https://avatars.githubusercontent.com/u/143941782?v=4" height="70px" width="70px" alt="andkindness" />
88
85
  </a>
@@ -171,6 +168,9 @@ npx skills add jpudysz/react-native-unistyles
171
168
  <a href="https://galaxies.dev">
172
169
  <img src="https://avatars.githubusercontent.com/u/118431096?s=200&v=4" height="60px" width="60px" alt="galaxies-dev" />
173
170
  </a>
171
+ <a href="https://github.com/kerwanp">
172
+ <img src="https://avatars.githubusercontent.com/u/36955373?v=4" height="60px" width="60px" alt="kerwanp" />
173
+ </a>
174
174
 
175
175
  ## Sponsor my work
176
176
 
@@ -185,11 +185,6 @@ Ko-fi: https://ko-fi.com/jpudysz
185
185
  Your support is greatly appreciated and helps me dedicate more time and resources to creating quality libraries. Thank you for all the support!
186
186
 
187
187
 
188
- ## Discord
189
- Looking for help or you want to chat with me?
190
-
191
- [Join Discord](https://discord.gg/akGHf27P4C)
192
-
193
188
  ## Contributing
194
189
  The repository uses a git submodule ([unifiber](https://github.com/uni-stack/unifiber)). Clone it with `git clone --recurse-submodules`, or run `git submodule update --init` in an existing clone.
195
190
 
@@ -32,6 +32,48 @@ apply from: '../nitrogen/generated/android/unistyles+autolinking.gradle'
32
32
  // doesn't compile. It only compiles Kotlin directories from the kotlin source set.
33
33
  android.sourceSets.main.kotlin.srcDirs += ["${project.projectDir}/../nitrogen/generated/android/kotlin"]
34
34
 
35
+ def resolveReactNativeMinorVersion() {
36
+ def reactNativeDir = safeExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
37
+
38
+ if (reactNativeDir == null) {
39
+ try {
40
+ def reactNativePackageJson = providers.exec {
41
+ workingDir(rootDir)
42
+ commandLine("node", "--print", "require.resolve('react-native/package.json')")
43
+ }.standardOutput.asText.get().trim()
44
+
45
+ reactNativeDir = file(reactNativePackageJson).parentFile
46
+ } catch (Exception ignored) {
47
+ reactNativeDir = file("${rootDir}/../node_modules/react-native")
48
+ }
49
+ }
50
+
51
+ def reactNativePackageJson = file("${reactNativeDir}/package.json")
52
+
53
+ if (!reactNativePackageJson.exists()) {
54
+ logger.warn("🦄 Unistyles: React Native not found. Set REACT_NATIVE_NODE_MODULES_DIR in your root build.gradle to enable features that require React Native 0.86+.")
55
+
56
+ return 0
57
+ }
58
+
59
+ String version = new groovy.json.JsonSlurper().parse(reactNativePackageJson).version
60
+
61
+ // nightlies
62
+ if (version.startsWith("0.0.0")) {
63
+ return Integer.MAX_VALUE
64
+ }
65
+
66
+ def (major, minor) = version.tokenize('.')
67
+
68
+ return major.toInteger() > 0 ? Integer.MAX_VALUE : minor.toInteger()
69
+ }
70
+
71
+ // Kotlin sources that use APIs available only in newer React Native versions
72
+ def reactNativeSourceDir = resolveReactNativeMinorVersion() >= 86 ? "src/rn86/java" : "src/legacy/java"
73
+
74
+ android.sourceSets.main.java.srcDirs += [reactNativeSourceDir]
75
+ android.sourceSets.main.kotlin.srcDirs += [reactNativeSourceDir]
76
+
35
77
  def resolveBuildType() {
36
78
  Gradle gradle = getGradle()
37
79
  String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString()
@@ -0,0 +1,16 @@
1
+ package com.unistyles
2
+
3
+ import android.view.Window
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+
6
+ // React Native below 0.86 doesn't report windows created on top of the Activity (e.g. Modal's Dialog)
7
+ @Suppress("UNUSED_PARAMETER")
8
+ class NativePlatformExtraWindows(
9
+ reactContext: ReactApplicationContext,
10
+ onWindowCreate: (window: Window) -> Unit,
11
+ onWindowDestroy: (window: Window) -> Unit
12
+ ) {
13
+ fun startObserving() {}
14
+
15
+ fun stopObserving() {}
16
+ }
@@ -9,17 +9,14 @@ using namespace facebook::react;
9
9
 
10
10
  UnistylesModule::UnistylesModule(
11
11
  jni::alias_ref<UnistylesModule::jhybridobject> jThis,
12
- jni::alias_ref<react::JRuntimeExecutor::javaobject> runtimeExecutorHolder,
13
12
  jni::alias_ref<JHybridNativePlatformSpec::JavaPart> nativePlatform
14
- ): _runtimeExecutor(runtimeExecutorHolder->cthis()->get()),
15
- _nativePlatform(nativePlatform->getJHybridNativePlatformSpec()) {}
13
+ ): _nativePlatform(nativePlatform->getJHybridNativePlatformSpec()) {}
16
14
 
17
15
  jni::local_ref<UnistylesModule::jhybriddata> UnistylesModule::initHybrid(
18
16
  jni::alias_ref<UnistylesModule::jhybridobject> jThis,
19
- jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutorHolder,
20
17
  jni::alias_ref<JHybridNativePlatformSpec::JavaPart> nativePlatform
21
18
  ) {
22
- return makeCxxInstance(jThis, runtimeExecutorHolder, nativePlatform);
19
+ return makeCxxInstance(jThis, nativePlatform);
23
20
  }
24
21
 
25
22
  void UnistylesModule::registerNatives() {
@@ -32,10 +29,9 @@ void UnistylesModule::registerNatives() {
32
29
 
33
30
  jni::local_ref<BindingsInstallerHolder::javaobject> UnistylesModule::getBindingsInstaller(jni::alias_ref<UnistylesModule::javaobject> jobj) {
34
31
  auto* self = jobj->cthis();
35
- auto& runtimeExecutor = self->_runtimeExecutor;
36
32
  auto& nativePlatform = self->_nativePlatform;
37
33
 
38
- return BindingsInstallerHolder::newObjectCxxArgs([self, &runtimeExecutor, &nativePlatform](jsi::Runtime& rt) {
34
+ return BindingsInstallerHolder::newObjectCxxArgs([self, &nativePlatform](jsi::Runtime& rt, const std::shared_ptr<CallInvoker>& callInvoker) {
39
35
  // function is called on: first init and every live reload
40
36
  // newest runtime owns the registry, previous runtime's state is kept until it releases ownership
41
37
  self->_generation = core::UnistylesRegistry::get().takeOwnership();
@@ -49,10 +45,8 @@ jni::local_ref<BindingsInstallerHolder::javaobject> UnistylesModule::getBindings
49
45
  HybridObjectRegistry::unregisterHybridObjectConstructor("UnistylesShadowRegistry");
50
46
  }
51
47
 
52
- auto runOnJSThread = [&runtimeExecutor](std::function<void(jsi::Runtime&)>&& callback) {
53
- runtimeExecutor([callback = std::move(callback)](jsi::Runtime &rt) {
54
- callback(rt);
55
- });
48
+ auto runOnJSThread = [callInvoker](std::function<void(jsi::Runtime&)>&& callback) {
49
+ callInvoker->invokeAsync(std::move(callback));
56
50
  };
57
51
 
58
52
  // init hybrids
@@ -1,7 +1,6 @@
1
1
  #pragma once
2
2
 
3
3
  #include <ReactCommon/BindingsInstallerHolder.h>
4
- #include <react/jni/JRuntimeExecutor.h>
5
4
  #include <react/renderer/scheduler/Scheduler.h>
6
5
  #include "UnistylesRegistry.h"
7
6
  #include <fbjni/fbjni.h>
@@ -18,14 +17,12 @@ struct UnistylesModule : public jni::HybridClass<UnistylesModule> {
18
17
 
19
18
  explicit UnistylesModule(
20
19
  jni::alias_ref<jhybridobject> jThis,
21
- jni::alias_ref<react::JRuntimeExecutor::javaobject> runtimeExecutorHolder,
22
20
  jni::alias_ref<JHybridNativePlatformSpec::JavaPart> nativePlatform
23
21
  );
24
22
 
25
23
  static void registerNatives();
26
24
  static jni::local_ref<jhybriddata> initHybrid(
27
25
  jni::alias_ref<jhybridobject> jThis,
28
- jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutorHolder,
29
26
  jni::alias_ref<JHybridNativePlatformSpec::JavaPart> nativePlatform
30
27
  );
31
28
  static void invalidateNative(jni::alias_ref<jhybridobject> jThis) {
@@ -38,7 +35,6 @@ struct UnistylesModule : public jni::HybridClass<UnistylesModule> {
38
35
  static jni::local_ref<BindingsInstallerHolder::javaobject> getBindingsInstaller(jni::alias_ref<UnistylesModule::javaobject> jThis);
39
36
 
40
37
  private:
41
- RuntimeExecutor _runtimeExecutor;
42
38
  std::shared_ptr<HybridNativePlatformSpec> _nativePlatform;
43
39
  uint64_t _generation = 0;
44
40
  };
@@ -8,6 +8,7 @@ import android.os.Handler
8
8
  import android.os.Looper
9
9
  import android.util.DisplayMetrics
10
10
  import android.view.View
11
+ import android.view.Window
11
12
  import android.view.WindowManager
12
13
  import androidx.annotation.Keep
13
14
  import androidx.core.content.OnConfigurationChangedProvider
@@ -33,13 +34,17 @@ import java.util.Locale
33
34
  @Keep
34
35
  @DoNotStrip
35
36
  class NativePlatformAndroid(private val reactContext: ReactApplicationContext): HybridNativePlatformSpec(), LifecycleEventListener {
36
- private val _insets = NativePlatformInsets(reactContext, this::getMiniRuntime) { this.onConfigChange() }
37
+ private val _insets = NativePlatformInsets(reactContext, this::getMiniRuntime, this::onImeChange) { this.onConfigChange() }
38
+ @Volatile
37
39
  private var _miniRuntime: UnistylesNativeMiniRuntime = buildMiniRuntime()
38
40
  private val _listener = NativePlatformListener(reactContext, this::getMiniRuntime) { this.diffMiniRuntime() }
39
41
  private var _configurationProvider: WeakReference<OnConfigurationChangedProvider>? = null
40
42
  private val _activityConfigurationListener = Consumer<Configuration> {
41
43
  Handler(Looper.getMainLooper()).post { this.onConfigChange() }
42
44
  }
45
+ // windows created on top of the Activity, e.g. Modal's Dialog (React Native 0.86+)
46
+ private val _extraWindows: MutableList<Window> = mutableListOf()
47
+ private val _extraWindowsObserver = NativePlatformExtraWindows(reactContext, this::onExtraWindowCreate, this::onExtraWindowDestroy)
43
48
 
44
49
  // Application resources can lag behind configuration changes
45
50
  private val configurationResources: Resources
@@ -48,9 +53,12 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
48
53
  init {
49
54
  checkEdgeToEdge()
50
55
  reactContext.addLifecycleEventListener(this)
56
+ _extraWindowsObserver.startObserving()
51
57
  }
52
58
 
53
59
  fun onDestroy() {
60
+ _extraWindowsObserver.stopObserving()
61
+ _extraWindows.clear()
54
62
  stopObservingActivityConfiguration()
55
63
  _listener.onDestroy()
56
64
  _insets.onDestroy()
@@ -90,6 +98,16 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
90
98
  _configurationProvider = null
91
99
  }
92
100
 
101
+ private fun onExtraWindowCreate(window: Window) {
102
+ _extraWindows.add(window)
103
+ _insets.attachToExtraWindow(window)
104
+ }
105
+
106
+ private fun onExtraWindowDestroy(window: Window) {
107
+ _extraWindows.remove(window)
108
+ _insets.detachFromExtraWindow(window)
109
+ }
110
+
93
111
  override val memorySize: Long
94
112
  get() = 0
95
113
 
@@ -216,58 +234,78 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
216
234
 
217
235
  override fun setNavigationBarHidden(isHidden: Boolean) {
218
236
  reactContext.currentActivity?.let { activity ->
219
- WindowInsetsControllerCompat(activity.window, activity.window.decorView).apply {
220
- activity.window?.decorView?.let { decorView ->
221
- @Suppress("DEPRECATION")
222
- activity.runOnUiThread {
223
- if (isHidden) {
224
- // below Android 11, we need to use window flags to hide the navigation bar
225
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
226
- decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
227
- or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
228
- } else {
229
- hide(WindowInsetsCompat.Type.navigationBars())
230
- systemBarsBehavior =
231
- WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
232
- }
233
-
234
- // dispatch new insets to invoke the insets listener
235
- val newInsets = WindowInsetsCompat.Builder()
236
- .setInsets(WindowInsetsCompat.Type.navigationBars(), androidx.core.graphics.Insets.of(0, 0, 0, 0))
237
- .build()
238
-
239
- ViewCompat.dispatchApplyWindowInsets(activity.findViewById(android.R.id.content), newInsets)
240
- } else {
241
- show(WindowInsetsCompat.Type.navigationBars())
242
- }
237
+ activity.window?.let { window ->
238
+ activity.runOnUiThread {
239
+ setNavigationBarHidden(window, isHidden)
240
+
241
+ if (isHidden) {
242
+ // dispatch new insets to invoke the insets listener
243
+ val newInsets = WindowInsetsCompat.Builder()
244
+ .setInsets(WindowInsetsCompat.Type.navigationBars(), androidx.core.graphics.Insets.of(0, 0, 0, 0))
245
+ .build()
246
+
247
+ ViewCompat.dispatchApplyWindowInsets(activity.findViewById(android.R.id.content), newInsets)
248
+ }
249
+
250
+ // otherwise opened Modals would keep the previous state
251
+ _extraWindows.forEach { extraWindow ->
252
+ setNavigationBarHidden(extraWindow, isHidden)
243
253
  }
244
254
  }
245
255
  }
246
256
  }
247
257
  }
248
258
 
259
+ @Suppress("DEPRECATION")
260
+ private fun setNavigationBarHidden(window: Window, isHidden: Boolean) {
261
+ WindowInsetsControllerCompat(window, window.decorView).apply {
262
+ if (isHidden) {
263
+ // below Android 11, we need to use window flags to hide the navigation bar
264
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
265
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
266
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
267
+ } else {
268
+ hide(WindowInsetsCompat.Type.navigationBars())
269
+ systemBarsBehavior =
270
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
271
+ }
272
+ } else {
273
+ show(WindowInsetsCompat.Type.navigationBars())
274
+ }
275
+ }
276
+ }
277
+
249
278
  override fun setStatusBarHidden(isHidden: Boolean) {
250
279
  reactContext.currentActivity?.let { activity ->
251
- WindowInsetsControllerCompat(activity.window, activity.window.decorView).apply {
252
- activity.window?.let { window ->
253
- @Suppress("DEPRECATION")
254
- activity.runOnUiThread {
255
- if (isHidden) {
256
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
257
- window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
258
- window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
259
- } else {
260
- hide(WindowInsetsCompat.Type.statusBars())
261
- }
262
- } else {
263
- show(WindowInsetsCompat.Type.statusBars())
264
- }
280
+ activity.window?.let { window ->
281
+ activity.runOnUiThread {
282
+ setStatusBarHidden(window, isHidden)
283
+
284
+ // otherwise opened Modals would keep the previous state
285
+ _extraWindows.forEach { extraWindow ->
286
+ setStatusBarHidden(extraWindow, isHidden)
265
287
  }
266
288
  }
267
289
  }
268
290
  }
269
291
  }
270
292
 
293
+ @Suppress("DEPRECATION")
294
+ private fun setStatusBarHidden(window: Window, isHidden: Boolean) {
295
+ WindowInsetsControllerCompat(window, window.decorView).apply {
296
+ if (isHidden) {
297
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
298
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
299
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
300
+ } else {
301
+ hide(WindowInsetsCompat.Type.statusBars())
302
+ }
303
+ } else {
304
+ show(WindowInsetsCompat.Type.statusBars())
305
+ }
306
+ }
307
+ }
308
+
271
309
  override fun setImmersiveMode(isEnabled: Boolean) {
272
310
  this.setStatusBarHidden(isEnabled)
273
311
  this.setNavigationBarHidden(isEnabled)
@@ -310,6 +348,11 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
310
348
  this._listener.onConfigChange()
311
349
  }
312
350
 
351
+ // only the IME, other insets are diffed by onConfigChange
352
+ private fun onImeChange(ime: Double) {
353
+ this._miniRuntime = this._miniRuntime.copy(insets = this._miniRuntime.insets.copy(ime = ime))
354
+ }
355
+
313
356
  override fun registerPlatformListener(callback: (dependencies: Array<UnistyleDependency>, miniRuntime: UnistylesNativeMiniRuntime) -> Unit) {
314
357
  this._listener.addPlatformListener(callback)
315
358
  }
@@ -21,12 +21,16 @@ typealias CxxImeListener = (miniRuntime: UnistylesNativeMiniRuntime) -> Unit
21
21
  class NativePlatformInsets(
22
22
  private val reactContext: ReactApplicationContext,
23
23
  private val getMiniRuntime: () -> UnistylesNativeMiniRuntime,
24
+ private val onImeChange: (ime: Double) -> Unit,
24
25
  private val onConfigChange: () -> Unit
25
26
  ) {
26
27
  private var _didGetInsets = false
27
28
  private var _shouldListenToImeEvents = false
28
29
  private val _imeListeners: MutableList<CxxImeListener> = mutableListOf()
29
30
  private var _insets: Insets = Insets(0.0, 0.0, 0.0, 0.0, 0.0)
31
+ private val _extraWindows: MutableList<Window> = mutableListOf()
32
+ // the extra window that reported the keyboard last
33
+ private var _imeWindow: Window? = null
30
34
 
31
35
  init {
32
36
  // for SDK below 35, it's possible to get it synchronously
@@ -35,6 +39,9 @@ class NativePlatformInsets(
35
39
 
36
40
  fun onDestroy() {
37
41
  this.removeImeListeners()
42
+ _extraWindows.toList().forEach { window ->
43
+ detachFromExtraWindow(window)
44
+ }
38
45
  }
39
46
 
40
47
  fun getInsets(): Insets {
@@ -195,7 +202,73 @@ class NativePlatformInsets(
195
202
  }
196
203
  }
197
204
 
205
+ // From Android 11 IME insets are dispatched only to the window with the focused input (e.g. Modal's Dialog),
206
+ // so we need to listen to them there, as the Activity won't receive them. Below Android 11 the Activity's listener
207
+ // still gets them
208
+ fun attachToExtraWindow(window: Window) {
209
+ if (Build.VERSION.SDK_INT < 30) {
210
+ return
211
+ }
212
+
213
+ val mainView = window.decorView.findViewById<View>(android.R.id.content) ?: return
214
+
215
+ _extraWindows.add(window)
216
+
217
+ ViewCompat.setWindowInsetsAnimationCallback(
218
+ mainView,
219
+ object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
220
+ override fun onProgress(
221
+ insets: WindowInsetsCompat,
222
+ runningAnimations: List<WindowInsetsAnimationCompat>
223
+ ): WindowInsetsCompat {
224
+ if (!_shouldListenToImeEvents) {
225
+ return insets
226
+ }
227
+
228
+ runningAnimations.firstOrNull { animation ->
229
+ animation.typeMask and WindowInsetsCompat.Type.ime() != 0
230
+ }?.let {
231
+ val bottomInset = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom.toDouble() - this@NativePlatformInsets._insets.bottom
232
+
233
+ this@NativePlatformInsets._imeWindow = window
234
+ this@NativePlatformInsets.setImeInsets(maxOf(bottomInset, 0.0))
235
+ }
236
+
237
+ return insets
238
+ }
239
+ }
240
+ )
241
+ }
242
+
243
+ fun detachFromExtraWindow(window: Window) {
244
+ if (!_extraWindows.remove(window)) {
245
+ return
246
+ }
247
+
248
+ window.decorView.findViewById<View>(android.R.id.content)?.let { mainView ->
249
+ ViewCompat.setWindowInsetsAnimationCallback(mainView, null)
250
+ }
251
+
252
+ // keyboard is gone together with the window, closing another Modal keeps it
253
+ if (_imeWindow === window) {
254
+ _imeWindow = null
255
+ setImeInsets(0.0)
256
+ }
257
+ }
258
+
259
+ private fun setImeInsets(imeInsets: Double) {
260
+ if (this._insets.ime == imeInsets) {
261
+ return
262
+ }
263
+
264
+ this._insets = this._insets.copy(ime = imeInsets)
265
+ this.emitImeEvent(this.getMiniRuntime().copy(insets = this.getInsets()))
266
+ }
267
+
198
268
  fun emitImeEvent(miniRuntime: UnistylesNativeMiniRuntime) {
269
+ // like on iOS, the cached mini runtime follows the keyboard, useUnistyles reads it
270
+ onImeChange(miniRuntime.insets.ime)
271
+
199
272
  _imeListeners.forEach { listener ->
200
273
  listener(miniRuntime)
201
274
  }
@@ -4,7 +4,6 @@ import com.facebook.fbreact.specs.NativeTurboUnistylesSpec
4
4
  import com.facebook.jni.HybridData
5
5
  import com.facebook.proguard.annotations.DoNotStrip
6
6
  import com.facebook.react.bridge.ReactApplicationContext
7
- import com.facebook.react.bridge.RuntimeExecutor
8
7
  import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
9
8
  import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
10
9
  import com.margelo.nitro.unistyles.HybridNativePlatformSpec
@@ -30,24 +29,14 @@ class UnistylesModule(reactContext: ReactApplicationContext): NativeTurboUnistyl
30
29
  }
31
30
 
32
31
  init {
33
- mHybridData = initializeHybridData(reactContext)
34
- }
35
-
36
- private fun initializeHybridData(reactContext: ReactApplicationContext): HybridData {
37
- val runtimeExecutor = reactContext.catalystInstance?.runtimeExecutor
38
- ?: throw IllegalStateException("Unistyles: React Native runtime executor is not available. Please follow installation guides.")
39
-
40
- return initHybrid(runtimeExecutor, _nativePlatform)
32
+ mHybridData = initHybrid(_nativePlatform)
41
33
  }
42
34
 
43
35
  @DoNotStrip
44
36
  external override fun getBindingsInstaller(): BindingsInstallerHolder
45
37
 
46
38
  @DoNotStrip
47
- private external fun initHybrid(
48
- runtimeExecutor: RuntimeExecutor,
49
- nativePlatform: HybridNativePlatformSpec
50
- ): HybridData
39
+ private external fun initHybrid(nativePlatform: HybridNativePlatformSpec): HybridData
51
40
 
52
41
  @DoNotStrip
53
42
  private external fun invalidateNative()
@@ -0,0 +1,28 @@
1
+ package com.unistyles
2
+
3
+ import android.view.Window
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.interfaces.ExtraWindowEventListener
6
+
7
+ // React Native 0.86+ reports windows created on top of the Activity (e.g. Modal's Dialog)
8
+ class NativePlatformExtraWindows(
9
+ private val reactContext: ReactApplicationContext,
10
+ private val onWindowCreate: (window: Window) -> Unit,
11
+ private val onWindowDestroy: (window: Window) -> Unit
12
+ ) : ExtraWindowEventListener {
13
+ fun startObserving() {
14
+ reactContext.addExtraWindowEventListener(this)
15
+ }
16
+
17
+ fun stopObserving() {
18
+ reactContext.removeExtraWindowEventListener(this)
19
+ }
20
+
21
+ override fun onExtraWindowCreate(window: Window) {
22
+ onWindowCreate(window)
23
+ }
24
+
25
+ override fun onExtraWindowDestroy(window: Window) {
26
+ onWindowDestroy(window)
27
+ }
28
+ }
@@ -0,0 +1,85 @@
1
+ #pragma once
2
+
3
+ #include <cxxreact/ReactNativeVersion.h>
4
+ #include <string>
5
+ #include <vector>
6
+ #include "UnistyleDependency.hpp"
7
+
8
+ // Scoped slice on the "Unistyles" track of the React Native DevTools Performance panel. Extra args are
9
+ // [name, value] property pairs, evaluated even when not tracing, so keep them cheap (counts, literals)
10
+ #if REACT_NATIVE_VERSION_MAJOR > 0 || REACT_NATIVE_VERSION_MINOR >= 86
11
+ #include <jsinspector-modern/tracing/PerformanceTracer.h>
12
+ #include <jsinspector-modern/tracing/PerformanceTracerSection.h>
13
+
14
+ #define UNISTYLES_TRACE_CONCAT_INNER(a, b) a##b
15
+ #define UNISTYLES_TRACE_CONCAT(a, b) UNISTYLES_TRACE_CONCAT_INNER(a, b)
16
+ #define UNISTYLES_TRACE_SECTION(name, ...) \
17
+ facebook::react::jsinspector_modern::tracing::PerformanceTracerSection UNISTYLES_TRACE_CONCAT( \
18
+ unistylesTrace, __LINE__)(name, "Unistyles", nullptr, nullptr __VA_OPT__(, ) __VA_ARGS__)
19
+ #define UNISTYLES_IS_TRACING() facebook::react::jsinspector_modern::tracing::PerformanceTracer::getInstance().isTracing()
20
+ #else
21
+ #define UNISTYLES_TRACE_SECTION(name, ...)
22
+ #define UNISTYLES_IS_TRACING() false
23
+ #endif
24
+
25
+ namespace margelo::nitro::unistyles::helpers {
26
+
27
+ inline const char* dependencyToString(UnistyleDependency dependency) {
28
+ switch (dependency) {
29
+ case UnistyleDependency::THEME:
30
+ return "theme";
31
+ case UnistyleDependency::THEMENAME:
32
+ return "themeName";
33
+ case UnistyleDependency::ADAPTIVETHEMES:
34
+ return "adaptiveThemes";
35
+ case UnistyleDependency::BREAKPOINTS:
36
+ return "breakpoints";
37
+ case UnistyleDependency::VARIANTS:
38
+ return "variants";
39
+ case UnistyleDependency::COLORSCHEME:
40
+ return "colorScheme";
41
+ case UnistyleDependency::DIMENSIONS:
42
+ return "dimensions";
43
+ case UnistyleDependency::ORIENTATION:
44
+ return "orientation";
45
+ case UnistyleDependency::CONTENTSIZECATEGORY:
46
+ return "contentSizeCategory";
47
+ case UnistyleDependency::INSETS:
48
+ return "insets";
49
+ case UnistyleDependency::PIXELRATIO:
50
+ return "pixelRatio";
51
+ case UnistyleDependency::FONTSCALE:
52
+ return "fontScale";
53
+ case UnistyleDependency::STATUSBAR:
54
+ return "statusBar";
55
+ case UnistyleDependency::NAVIGATIONBAR:
56
+ return "navigationBar";
57
+ case UnistyleDependency::IME:
58
+ return "ime";
59
+ case UnistyleDependency::RTL:
60
+ return "rtl";
61
+ }
62
+
63
+ return "unknown";
64
+ }
65
+
66
+ // built only while DevTools records, it allocates
67
+ inline std::string dependenciesToString(const std::vector<UnistyleDependency>& dependencies) {
68
+ if (!UNISTYLES_IS_TRACING()) {
69
+ return {};
70
+ }
71
+
72
+ std::string result;
73
+
74
+ for (auto dependency : dependencies) {
75
+ if (!result.empty()) {
76
+ result += ", ";
77
+ }
78
+
79
+ result += dependencyToString(dependency);
80
+ }
81
+
82
+ return result;
83
+ }
84
+
85
+ }