Files
paseo/patches/react-native-unistyles+3.2.4.patch
T
Mohamed Boudra 82d5f6c580 fix(app): keep layout sized to the window after rotation (#8377)
A style update racing a window resize could recommit old root constraints. Rebuild the style update from the current root on each commit attempt.
2026-11-01 10:34:06 +07:00

764 lines
35 KiB
Diff

diff --git a/node_modules/react-native-unistyles/lib/commonjs/web/registry.js b/node_modules/react-native-unistyles/lib/commonjs/web/registry.js
index 120ff60..d1ff102 100644
--- a/node_modules/react-native-unistyles/lib/commonjs/web/registry.js
+++ b/node_modules/react-native-unistyles/lib/commonjs/web/registry.js
@@ -10,6 +10,17 @@ class UnistylesRegistry {
stylesheets = new Map();
stylesCache = new Set();
stylesCounter = new Map();
+ cleanupGeneration = 0;
+ finalizerTokens = new WeakMap();
+ finalizationRegistry = typeof FinalizationRegistry === 'undefined' ? undefined : new FinalizationRegistry(({
+ hash,
+ generation
+ }) => {
+ if (generation !== this.cleanupGeneration) {
+ return;
+ }
+ this.decrementAndMaybeCleanup(hash);
+ });
disposeListenersMap = new Map();
dependenciesMap = new Map();
constructor(services) {
@@ -50,26 +61,48 @@ class UnistylesRegistry {
this.disposeListenersMap.set(stylesheet, dispose);
};
connect = (ref, hash) => {
- const stylesCounter = this.stylesCounter.get(hash) ?? new Set();
- stylesCounter.add(ref);
- this.stylesCounter.set(hash, stylesCounter);
+ this.stylesCounter.set(hash, (this.stylesCounter.get(hash) ?? 0) + 1);
+ if (!this.finalizationRegistry) {
+ return;
+ }
+ const token = {};
+ const tokensByHash = this.finalizerTokens.get(ref) ?? new Map();
+ const tokens = tokensByHash.get(hash) ?? [];
+ tokens.push(token);
+ tokensByHash.set(hash, tokens);
+ this.finalizerTokens.set(ref, tokensByHash);
+ this.finalizationRegistry.register(ref, {
+ hash,
+ generation: this.cleanupGeneration
+ }, token);
};
remove = (ref, hash) => {
- const stylesCounter = this.stylesCounter.get(hash) ?? new Set();
- stylesCounter.delete(ref);
- if (stylesCounter.size === 0) {
- // Move this to the end of the event loop so the element is removed from the DOM
- return Promise.resolve().then(() => {
- // Check if element is still in the DOM
- if (document.querySelector(`.${hash}`)) {
- return false;
- }
- this.css.remove(hash);
- this.stylesCache.delete(hash);
- return true;
- });
+ const token = this.finalizerTokens.get(ref)?.get(hash)?.pop();
+ if (token) {
+ this.finalizationRegistry?.unregister(token);
+ }
+ return this.decrementAndMaybeCleanup(hash);
+ };
+ decrementAndMaybeCleanup = hash => {
+ const next = (this.stylesCounter.get(hash) ?? 0) - 1;
+ if (next > 0) {
+ this.stylesCounter.set(hash, next);
+ return Promise.resolve(false);
}
- return Promise.resolve(false);
+ this.stylesCounter.delete(hash);
+ // Move this to the end of the event loop so the element is removed from the DOM
+ return Promise.resolve().then(() => {
+ // Check if element is still in the DOM
+ if ((this.stylesCounter.get(hash) ?? 0) > 0) {
+ return false;
+ }
+ if (document.querySelector(`.${hash}`)) {
+ return false;
+ }
+ this.css.remove(hash);
+ this.stylesCache.delete(hash);
+ return true;
+ });
};
add = (value, forChild) => {
const generatedHash = (0, _utils.generateHash)(value);
@@ -91,7 +124,10 @@ class UnistylesRegistry {
this.css.add(hash, value);
};
reset = () => {
+ this.cleanupGeneration += 1;
+ this.disposeListenersMap.forEach(dispose => dispose());
this.css.reset();
+ this.stylesheets.clear();
this.stylesCache.clear();
this.dependenciesMap.clear();
this.disposeListenersMap.clear();
diff --git a/node_modules/react-native-unistyles/lib/module/web/registry.js b/node_modules/react-native-unistyles/lib/module/web/registry.js
index 6362f43..8e769f8 100644
--- a/node_modules/react-native-unistyles/lib/module/web/registry.js
+++ b/node_modules/react-native-unistyles/lib/module/web/registry.js
@@ -6,6 +6,17 @@ export class UnistylesRegistry {
stylesheets = new Map();
stylesCache = new Set();
stylesCounter = new Map();
+ cleanupGeneration = 0;
+ finalizerTokens = new WeakMap();
+ finalizationRegistry = typeof FinalizationRegistry === 'undefined' ? undefined : new FinalizationRegistry(({
+ hash,
+ generation
+ }) => {
+ if (generation !== this.cleanupGeneration) {
+ return;
+ }
+ this.decrementAndMaybeCleanup(hash);
+ });
disposeListenersMap = new Map();
dependenciesMap = new Map();
constructor(services) {
@@ -46,26 +57,48 @@ export class UnistylesRegistry {
this.disposeListenersMap.set(stylesheet, dispose);
};
connect = (ref, hash) => {
- const stylesCounter = this.stylesCounter.get(hash) ?? new Set();
- stylesCounter.add(ref);
- this.stylesCounter.set(hash, stylesCounter);
+ this.stylesCounter.set(hash, (this.stylesCounter.get(hash) ?? 0) + 1);
+ if (!this.finalizationRegistry) {
+ return;
+ }
+ const token = {};
+ const tokensByHash = this.finalizerTokens.get(ref) ?? new Map();
+ const tokens = tokensByHash.get(hash) ?? [];
+ tokens.push(token);
+ tokensByHash.set(hash, tokens);
+ this.finalizerTokens.set(ref, tokensByHash);
+ this.finalizationRegistry.register(ref, {
+ hash,
+ generation: this.cleanupGeneration
+ }, token);
};
remove = (ref, hash) => {
- const stylesCounter = this.stylesCounter.get(hash) ?? new Set();
- stylesCounter.delete(ref);
- if (stylesCounter.size === 0) {
- // Move this to the end of the event loop so the element is removed from the DOM
- return Promise.resolve().then(() => {
- // Check if element is still in the DOM
- if (document.querySelector(`.${hash}`)) {
- return false;
- }
- this.css.remove(hash);
- this.stylesCache.delete(hash);
- return true;
- });
+ const token = this.finalizerTokens.get(ref)?.get(hash)?.pop();
+ if (token) {
+ this.finalizationRegistry?.unregister(token);
+ }
+ return this.decrementAndMaybeCleanup(hash);
+ };
+ decrementAndMaybeCleanup = hash => {
+ const next = (this.stylesCounter.get(hash) ?? 0) - 1;
+ if (next > 0) {
+ this.stylesCounter.set(hash, next);
+ return Promise.resolve(false);
}
- return Promise.resolve(false);
+ this.stylesCounter.delete(hash);
+ // Move this to the end of the event loop so the element is removed from the DOM
+ return Promise.resolve().then(() => {
+ // Check if element is still in the DOM
+ if ((this.stylesCounter.get(hash) ?? 0) > 0) {
+ return false;
+ }
+ if (document.querySelector(`.${hash}`)) {
+ return false;
+ }
+ this.css.remove(hash);
+ this.stylesCache.delete(hash);
+ return true;
+ });
};
add = (value, forChild) => {
const generatedHash = generateHash(value);
@@ -87,7 +120,10 @@ export class UnistylesRegistry {
this.css.add(hash, value);
};
reset = () => {
+ this.cleanupGeneration += 1;
+ this.disposeListenersMap.forEach(dispose => dispose());
this.css.reset();
+ this.stylesheets.clear();
this.stylesCache.clear();
this.dependenciesMap.clear();
this.disposeListenersMap.clear();
diff --git a/node_modules/react-native-unistyles/src/web/registry.ts b/node_modules/react-native-unistyles/src/web/registry.ts
index 39e0d9d..a21ba95 100644
--- a/node_modules/react-native-unistyles/src/web/registry.ts
+++ b/node_modules/react-native-unistyles/src/web/registry.ts
@@ -9,7 +9,24 @@ import { error, extractUnistyleDependencies, generateHash } from './utils'
export class UnistylesRegistry {
private readonly stylesheets = new Map<StyleSheetWithSuperPowers<StyleSheet>, StyleSheet>()
private readonly stylesCache = new Set<string>()
- private readonly stylesCounter = new Map<string, Set<HTMLElement>>()
+ // Use counter instead of Set<HTMLElement> to avoid retaining detached DOM nodes
+ // FR is a fallback when remove() is missed
+ private readonly stylesCounter = new Map<string, number>()
+ // Bumped on reset() so stale FR callbacks are ignored
+ private cleanupGeneration = 0
+ // Per-registration unregister token, so remove() cancels only its own registration
+ private readonly finalizerTokens = new WeakMap<HTMLElement, Map<string, Array<object>>>()
+ // FR isn't available on every runtime - fall back to counter-only cleanup
+ private readonly finalizationRegistry =
+ typeof FinalizationRegistry === 'undefined'
+ ? undefined
+ : new FinalizationRegistry<{ hash: string; generation: number }>(({ hash, generation }) => {
+ if (generation !== this.cleanupGeneration) {
+ return
+ }
+
+ this.decrementAndMaybeCleanup(hash)
+ })
private readonly disposeListenersMap = new Map<object, VoidFunction>()
private readonly dependenciesMap = new Map<StyleSheetWithSuperPowers<StyleSheet>, Set<UnistyleDependency>>()
readonly css: CSSState
@@ -78,33 +95,61 @@ export class UnistylesRegistry {
}
connect = (ref: HTMLElement, hash: string) => {
- const stylesCounter = this.stylesCounter.get(hash) ?? new Set()
+ this.stylesCounter.set(hash, (this.stylesCounter.get(hash) ?? 0) + 1)
- stylesCounter.add(ref)
- this.stylesCounter.set(hash, stylesCounter)
+ if (!this.finalizationRegistry) {
+ return
+ }
+
+ const token = {}
+ const tokensByHash = this.finalizerTokens.get(ref) ?? new Map<string, Array<object>>()
+ const tokens = tokensByHash.get(hash) ?? []
+
+ tokens.push(token)
+ tokensByHash.set(hash, tokens)
+ this.finalizerTokens.set(ref, tokensByHash)
+ this.finalizationRegistry.register(ref, { hash, generation: this.cleanupGeneration }, token)
}
remove = (ref: HTMLElement, hash: string) => {
- const stylesCounter = this.stylesCounter.get(hash) ?? new Set()
+ const token = this.finalizerTokens.get(ref)?.get(hash)?.pop()
- stylesCounter.delete(ref)
+ if (token) {
+ this.finalizationRegistry?.unregister(token)
+ }
- if (stylesCounter.size === 0) {
- // Move this to the end of the event loop so the element is removed from the DOM
- return Promise.resolve().then(() => {
- // Check if element is still in the DOM
- if (document.querySelector(`.${hash}`)) {
- return false
- }
+ return this.decrementAndMaybeCleanup(hash)
+ }
+
+ private decrementAndMaybeCleanup = (hash: string): Promise<boolean> => {
+ const next = (this.stylesCounter.get(hash) ?? 0) - 1
- this.css.remove(hash)
- this.stylesCache.delete(hash)
+ if (next > 0) {
+ this.stylesCounter.set(hash, next)
- return true
- })
+ return Promise.resolve(false)
}
- return Promise.resolve(false)
+ // Drop the empty bucket from the Map
+ this.stylesCounter.delete(hash)
+
+ // Move this to the end of the event loop so the element is removed from the DOM
+ return Promise.resolve().then(() => {
+ // New connect raced in during the microtask
+ if ((this.stylesCounter.get(hash) ?? 0) > 0) {
+ return false
+ }
+
+ // Check if element is still in the DOM
+ if (document.querySelector(`.${hash}`)) {
+ return false
+ }
+
+ this.css.remove(hash)
+ this.stylesCache.delete(hash)
+
+ return true
+ })
}
add = (value: UnistylesValues, forChild?: boolean) => {
@@ -126,7 +171,11 @@ export class UnistylesRegistry {
}
reset = () => {
+ // Invalidate pending FR callbacks from this generation
+ this.cleanupGeneration += 1
+ this.disposeListenersMap.forEach((dispose) => dispose())
this.css.reset()
+ this.stylesheets.clear()
this.stylesCache.clear()
this.dependenciesMap.clear()
this.disposeListenersMap.clear()
diff --git a/node_modules/react-native-unistyles/android/build.gradle b/node_modules/react-native-unistyles/android/build.gradle
index 195954b..c4ac28c 100644
--- a/node_modules/react-native-unistyles/android/build.gradle
+++ b/node_modules/react-native-unistyles/android/build.gradle
@@ -38,6 +38,7 @@ if (isNewArchitectureEnabled()) {
android {
compileSdkVersion safeExtGet('compileSdkVersion', 35)
+ ndkVersion safeExtGet('ndkVersion', '27.1.12297006')
namespace "com.unistyles"
defaultConfig {
diff --git a/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.tsx b/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.tsx
--- a/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.tsx
+++ b/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.tsx
@@ -28,13 +28,14 @@
return forwardRef<GenericComponentRef<TComponent>, PropsWithUnistyles>((props, ref) => {
const narrowedProps = props as PropsWithUnistyles & UnistyleStyles
+ const { uniProps, ...componentProps } = narrowedProps
const styleClassNames = getClassName(narrowedProps.style, true)
const contentContainerStyleClassNames = getClassName(narrowedProps.contentContainerStyle)
const { proxifiedRuntime, proxifiedTheme } = useProxifiedUnistyles()
const { key: mappingsKey, ...mappingsProps } = mappings ? mappings(proxifiedTheme, proxifiedRuntime) : {}
- const { key: uniPropsKey, ...unistyleProps } = narrowedProps.uniProps
- ? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime)
+ const { key: uniPropsKey, ...unistyleProps } = uniProps
+ ? uniProps(proxifiedTheme, proxifiedRuntime)
: {}
const emptyStyles = narrowedProps.style
@@ -46,7 +47,7 @@
: undefined
const combinedProps = {
- ...deepMergeObjects(mappingsProps, unistyleProps, props),
+ ...deepMergeObjects(mappingsProps, unistyleProps, componentProps),
...(narrowedProps.style
? {
// Override default component styles with undefined values to reset them
diff --git a/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.native.tsx b/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.native.tsx
--- a/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.native.tsx
+++ b/node_modules/react-native-unistyles/src/core/withUnistyles/withUnistyles.native.tsx
@@ -63,6 +63,7 @@
return forwardRef<GenericComponentRef<TComponent>, PropsWithUnistyles>((props, ref) => {
const narrowedProps = props as PropsWithUnistyles & UnistyleStyles
+ const { uniProps, ...componentProps } = narrowedProps
const NativeComponent = Component as ComponentType
// @ts-ignore we don't know the type of the component
@@ -89,15 +90,15 @@
}, [narrowedProps.style, narrowedProps.contentContainerStyle])
const { key: mappingsKey, ...mappingsProps } = mappings ? mappings(proxifiedTheme, proxifiedRuntime) : {}
- const { key: uniPropsKey, ...unistyleProps } = narrowedProps.uniProps
- ? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime)
+ const { key: uniPropsKey, ...unistyleProps } = uniProps
+ ? uniProps(proxifiedTheme, proxifiedRuntime)
: {}
const styleSecrets = getSecrets(narrowedProps.style)
const contentContainerStyleSecrets = getSecrets(narrowedProps.contentContainerStyle)
const finalProps = {
- ...deepMergeObjects(mappingsProps, unistyleProps, props),
+ ...deepMergeObjects(mappingsProps, unistyleProps, componentProps),
...(narrowedProps.style
? {
style: styleSecrets.styles,
diff --git a/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.js b/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.js
--- a/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.js
+++ b/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.js
@@ -13,6 +13,7 @@
export const withUnistyles = (Component, mappings) => {
return /*#__PURE__*/forwardRef((props, ref) => {
const narrowedProps = props;
+ const { uniProps, ...componentProps } = narrowedProps;
const styleClassNames = getClassName(narrowedProps.style, true);
const contentContainerStyleClassNames = getClassName(narrowedProps.contentContainerStyle);
const {
@@ -26,10 +27,10 @@
const {
key: uniPropsKey,
...unistyleProps
- } = narrowedProps.uniProps ? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime) : {};
+ } = uniProps ? uniProps(proxifiedTheme, proxifiedRuntime) : {};
const emptyStyles = narrowedProps.style ? Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(narrowedProps.style)).filter(([key]) => !key.startsWith('unistyles') && !key.startsWith('_')).map(([key]) => [key, undefined])) : undefined;
const combinedProps = {
- ...deepMergeObjects(mappingsProps, unistyleProps, props),
+ ...deepMergeObjects(mappingsProps, unistyleProps, componentProps),
...(narrowedProps.style ? {
// Override default component styles with undefined values to reset them
style: emptyStyles
diff --git a/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.native.js b/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.native.js
--- a/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.native.js
+++ b/node_modules/react-native-unistyles/lib/module/core/withUnistyles/withUnistyles.native.js
@@ -27,6 +27,7 @@
};
return /*#__PURE__*/forwardRef((props, ref) => {
const narrowedProps = props;
+ const { uniProps, ...componentProps } = narrowedProps;
const NativeComponent = Component;
// @ts-ignore we don't know the type of the component
@@ -57,11 +58,11 @@
const {
key: uniPropsKey,
...unistyleProps
- } = narrowedProps.uniProps ? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime) : {};
+ } = uniProps ? uniProps(proxifiedTheme, proxifiedRuntime) : {};
const styleSecrets = getSecrets(narrowedProps.style);
const contentContainerStyleSecrets = getSecrets(narrowedProps.contentContainerStyle);
const finalProps = {
- ...deepMergeObjects(mappingsProps, unistyleProps, props),
+ ...deepMergeObjects(mappingsProps, unistyleProps, componentProps),
...(narrowedProps.style ? {
style: styleSecrets.styles
} : {}),
diff --git a/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.js b/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.js
--- a/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.js
+++ b/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.js
@@ -18,6 +18,7 @@
const withUnistyles = (Component, mappings) => {
return /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
const narrowedProps = props;
+ const { uniProps, ...componentProps } = narrowedProps;
const styleClassNames = (0, _getClassname.getClassName)(narrowedProps.style, true);
const contentContainerStyleClassNames = (0, _getClassname.getClassName)(narrowedProps.contentContainerStyle);
const {
@@ -31,10 +32,10 @@
const {
key: uniPropsKey,
...unistyleProps
- } = narrowedProps.uniProps ? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime) : {};
+ } = uniProps ? uniProps(proxifiedTheme, proxifiedRuntime) : {};
const emptyStyles = narrowedProps.style ? Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(narrowedProps.style)).filter(([key]) => !key.startsWith('unistyles') && !key.startsWith('_')).map(([key]) => [key, undefined])) : undefined;
const combinedProps = {
- ...(0, _utils.deepMergeObjects)(mappingsProps, unistyleProps, props),
+ ...(0, _utils.deepMergeObjects)(mappingsProps, unistyleProps, componentProps),
...(narrowedProps.style ? {
// Override default component styles with undefined values to reset them
style: emptyStyles
diff --git a/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.native.js b/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.native.js
--- a/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.native.js
+++ b/node_modules/react-native-unistyles/lib/commonjs/core/withUnistyles/withUnistyles.native.js
@@ -32,6 +32,7 @@
};
return /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
const narrowedProps = props;
+ const { uniProps, ...componentProps } = narrowedProps;
const NativeComponent = Component;
// @ts-ignore we don't know the type of the component
@@ -62,11 +63,11 @@
const {
key: uniPropsKey,
...unistyleProps
- } = narrowedProps.uniProps ? narrowedProps.uniProps(proxifiedTheme, proxifiedRuntime) : {};
+ } = uniProps ? uniProps(proxifiedTheme, proxifiedRuntime) : {};
const styleSecrets = getSecrets(narrowedProps.style);
const contentContainerStyleSecrets = getSecrets(narrowedProps.contentContainerStyle);
const finalProps = {
- ...(0, _utils.deepMergeObjects)(mappingsProps, unistyleProps, props),
+ ...(0, _utils.deepMergeObjects)(mappingsProps, unistyleProps, componentProps),
...(narrowedProps.style ? {
style: styleSecrets.styles
} : {}),
diff --git a/node_modules/react-native-unistyles/lib/commonjs/web/runtime.js b/node_modules/react-native-unistyles/lib/commonjs/web/runtime.js
index 305fa49..5957aa5 100644
--- a/node_modules/react-native-unistyles/lib/commonjs/web/runtime.js
+++ b/node_modules/react-native-unistyles/lib/commonjs/web/runtime.js
@@ -35,10 +35,17 @@ class UnistylesRuntime {
return this.darkMedia;
}
get colorScheme() {
+ // Read the scheme through fresh lists, never through lightMedia/darkMedia. Chromium defers
+ // MediaQueryList change events while the document is hidden and drops the pending event
+ // of any list whose `matches` was read before the document became visible again, which
+ // would silence the adaptive-theme listeners registered on those two lists.
+ if ((0, _utils.isServer)()) {
+ return _types.ColorScheme.Unspecified;
+ }
switch (true) {
- case this.getLightMedia()?.matches:
+ case window.matchMedia('(prefers-color-scheme: light)').matches:
return _types.ColorScheme.Light;
- case this.getDarkMedia()?.matches:
+ case window.matchMedia('(prefers-color-scheme: dark)').matches:
return _types.ColorScheme.Dark;
default:
return _types.ColorScheme.Unspecified;
diff --git a/node_modules/react-native-unistyles/lib/module/web/runtime.js b/node_modules/react-native-unistyles/lib/module/web/runtime.js
index 7f13a06..c487bee 100644
--- a/node_modules/react-native-unistyles/lib/module/web/runtime.js
+++ b/node_modules/react-native-unistyles/lib/module/web/runtime.js
@@ -31,10 +31,17 @@ export class UnistylesRuntime {
return this.darkMedia;
}
get colorScheme() {
+ // Read the scheme through fresh lists, never through lightMedia/darkMedia. Chromium defers
+ // MediaQueryList change events while the document is hidden and drops the pending event
+ // of any list whose `matches` was read before the document became visible again, which
+ // would silence the adaptive-theme listeners registered on those two lists.
+ if (isServer()) {
+ return ColorScheme.Unspecified;
+ }
switch (true) {
- case this.getLightMedia()?.matches:
+ case window.matchMedia('(prefers-color-scheme: light)').matches:
return ColorScheme.Light;
- case this.getDarkMedia()?.matches:
+ case window.matchMedia('(prefers-color-scheme: dark)').matches:
return ColorScheme.Dark;
default:
return ColorScheme.Unspecified;
diff --git a/node_modules/react-native-unistyles/src/web/runtime.ts b/node_modules/react-native-unistyles/src/web/runtime.ts
index a16dac0..6519858 100644
--- a/node_modules/react-native-unistyles/src/web/runtime.ts
+++ b/node_modules/react-native-unistyles/src/web/runtime.ts
@@ -41,8 +41,16 @@ export class UnistylesRuntime {
get colorScheme() {
+ // Read the scheme through fresh lists, never through lightMedia/darkMedia. Chromium defers
+ // MediaQueryList change events while the document is hidden and drops the pending event
+ // of any list whose `matches` was read before the document became visible again, which
+ // would silence the adaptive-theme listeners registered on those two lists.
+ if (isServer()) {
+ return ColorScheme.Unspecified
+ }
+
switch (true) {
- case this.getLightMedia()?.matches:
+ case window.matchMedia('(prefers-color-scheme: light)').matches:
return ColorScheme.Light
- case this.getDarkMedia()?.matches:
+ case window.matchMedia('(prefers-color-scheme: dark)').matches:
return ColorScheme.Dark
default:
return ColorScheme.Unspecified
diff --git a/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+android.kt b/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+android.kt
index 0f8d1f0..154a12a 100644
--- a/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+android.kt
+++ b/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+android.kt
@@ -3,11 +3,16 @@ package com.unistyles
import android.os.Build
import android.content.Context
import android.content.res.Configuration
+import android.content.res.Resources
+import android.os.Handler
+import android.os.Looper
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
import androidx.annotation.Keep
+import androidx.core.content.OnConfigurationChangedProvider
import androidx.core.text.TextUtilsCompat
+import androidx.core.util.Consumer
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@@ -22,6 +27,7 @@ import com.margelo.nitro.unistyles.Insets
import com.margelo.nitro.unistyles.Orientation
import com.margelo.nitro.unistyles.UnistyleDependency
import com.margelo.nitro.unistyles.UnistylesNativeMiniRuntime
+import java.lang.ref.WeakReference
import java.util.Locale
@Keep
@@ -30,6 +36,14 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
private val _insets = NativePlatformInsets(reactContext, this::getMiniRuntime) { this.onConfigChange() }
private var _miniRuntime: UnistylesNativeMiniRuntime = buildMiniRuntime()
private val _listener = NativePlatformListener(reactContext, this::getMiniRuntime) { this.diffMiniRuntime() }
+ private var _configurationProvider: WeakReference<OnConfigurationChangedProvider>? = null
+ private val _activityConfigurationListener = Consumer<Configuration> {
+ Handler(Looper.getMainLooper()).post { this.onConfigChange() }
+ }
+
+ // Application resources can lag behind configuration changes
+ private val configurationResources: Resources
+ get() = reactContext.currentActivity?.resources ?: reactContext.resources
init {
checkEdgeToEdge()
@@ -37,12 +51,16 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
}
fun onDestroy() {
+ stopObservingActivityConfiguration()
_listener.onDestroy()
_insets.onDestroy()
reactContext.removeLifecycleEventListener(this)
}
override fun onHostResume() {
+ observeActivityConfiguration()
+ // catch configuration changes missed while no Activity was attached
+ onConfigChange()
_insets.getInitialInsets(false)
_insets.startInsetsListener()
}
@@ -51,7 +69,26 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
_insets.stopInsetsListener()
}
- override fun onHostDestroy() {}
+ override fun onHostDestroy() {
+ stopObservingActivityConfiguration()
+ }
+
+ private fun observeActivityConfiguration() {
+ val provider = reactContext.currentActivity as? OnConfigurationChangedProvider
+
+ if (_configurationProvider?.get() === provider) {
+ return
+ }
+
+ stopObservingActivityConfiguration()
+ provider?.addOnConfigurationChangedListener(_activityConfigurationListener)
+ _configurationProvider = provider?.let { WeakReference(it) }
+ }
+
+ private fun stopObservingActivityConfiguration() {
+ _configurationProvider?.get()?.removeOnConfigurationChangedListener(_activityConfigurationListener)
+ _configurationProvider = null
+ }
override val memorySize: Long
get() = 0
@@ -61,7 +98,7 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
}
override fun getColorScheme(): ColorScheme {
- val uiMode = reactContext.resources.configuration.uiMode
+ val uiMode = configurationResources.configuration.uiMode
val colorScheme = when (uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
Configuration.UI_MODE_NIGHT_YES -> ColorScheme.DARK
@@ -73,15 +110,15 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
}
override fun getFontScale(): Double {
- return reactContext.resources.configuration.fontScale.toDouble()
+ return configurationResources.configuration.fontScale.toDouble()
}
override fun getPixelRatio(): Double {
- return reactContext.resources.displayMetrics.density.toDouble()
+ return configurationResources.displayMetrics.density.toDouble()
}
override fun getOrientation(): Orientation {
- val orientation = when (reactContext.resources.configuration.orientation) {
+ val orientation = when (configurationResources.configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> Orientation.PORTRAIT
Configuration.ORIENTATION_LANDSCAPE -> Orientation.LANDSCAPE
else -> Orientation.PORTRAIT
@@ -122,7 +159,7 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
return Dimensions(screenWidth, screenHeight)
}
else -> {
- val displayMetrics = reactContext.resources.displayMetrics
+ val displayMetrics = configurationResources.displayMetrics
reactContext.currentActivity?.windowManager?.currentWindowMetrics?.bounds?.let {
val boundsWidth = (it.width() / displayMetrics.density).toDouble()
@@ -157,7 +194,7 @@ class NativePlatformAndroid(private val reactContext: ReactApplicationContext):
"com.facebook.react.modules.i18nmanager.I18nUtil",
Context.MODE_PRIVATE
)
- val resourcesLocale = reactContext.resources.configuration.locales[0]
+ val resourcesLocale = configurationResources.configuration.locales[0]
val hasForcedRtl = sharedPrefs.getBoolean("RCTI18nUtil_forceRTL", false)
// user preferences
val isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
diff --git a/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+insets.kt b/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+insets.kt
index a9c1804..f5a3c20 100644
--- a/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+insets.kt
+++ b/node_modules/react-native-unistyles/android/src/main/java/com/unistyles/NativePlatform+insets.kt
@@ -38,7 +38,7 @@ class NativePlatformInsets(
}
fun getInsets(): Insets {
- val density = reactContext.resources.displayMetrics.density
+ val density = (reactContext.currentActivity?.resources ?: reactContext.resources).displayMetrics.density
return Insets(
this._insets.top / density,
diff --git a/node_modules/react-native-unistyles/cxx/shadowTree/ShadowTreeManager.cpp b/node_modules/react-native-unistyles/cxx/shadowTree/ShadowTreeManager.cpp
index 01026dc..3786ec4 100644
--- a/node_modules/react-native-unistyles/cxx/shadowTree/ShadowTreeManager.cpp
+++ b/node_modules/react-native-unistyles/cxx/shadowTree/ShadowTreeManager.cpp
@@ -17,13 +17,16 @@ void shadow::ShadowTreeManager::updateShadowTree(jsi::Runtime& rt) {
}
#if REACT_NATIVE_VERSION_MINOR >= 81
- std::unordered_map<Tag, folly::dynamic> tagToProps;
+ // Paseo backport of https://github.com/jpudysz/react-native-unistyles/pull/1357.
+ // UIManager::updateShadowTree commits a root cloned before the commit, so when a
+ // window-size commit (rotation, keyboard) lands in between, the retried Unistyles
+ // commit puts the previous layout constraints back and the app stays laid out for
+ // the old size. Commit here instead and clone from the root the commit hands us.
+ std::unordered_set<SurfaceId> affectedSurfaces;
for (const auto& [family, props] : updates) {
auto safeProps = props.isObject() ? props : folly::dynamic::object();
- tagToProps.insert({family->getTag(), safeProps});
-
auto* mutableFamily = const_cast<ShadowNodeFamily*>(family);
if (mutableFamily->nativeProps_DEPRECATED && mutableFamily->nativeProps_DEPRECATED->isObject()) {
@@ -31,9 +34,30 @@ void shadow::ShadowTreeManager::updateShadowTree(jsi::Runtime& rt) {
} else {
mutableFamily->nativeProps_DEPRECATED = std::make_unique<folly::dynamic>(safeProps);
}
+
+ affectedSurfaces.insert(family->getSurfaceId());
}
- UIManagerBinding::getBinding(rt)->getUIManager().updateShadowTree(std::move(tagToProps));
+ const auto& shadowTreeRegistry = UIManagerBinding::getBinding(rt)->getUIManager().getShadowTreeRegistry();
+
+ shadowTreeRegistry.enumerate([&updates, &affectedSurfaces](const ShadowTree& shadowTree, bool&){
+ if (!affectedSurfaces.contains(shadowTree.getSurfaceId())) {
+ return;
+ }
+
+ auto transaction = [&updates](const RootShadowNode& oldRootShadowNode) -> RootShadowNode::Unshared {
+ auto affectedNodes = shadow::ShadowTreeManager::findAffectedNodes(oldRootShadowNode, updates);
+
+ return std::static_pointer_cast<RootShadowNode>(shadow::ShadowTreeManager::cloneShadowTree(
+ oldRootShadowNode,
+ updates,
+ affectedNodes
+ ));
+ };
+
+ // mountSynchronously: this is an update from C++, not React
+ shadowTree.commit(transaction, {false, true});
+ });
#else
const auto& shadowTreeRegistry = UIManagerBinding::getBinding(rt)->getUIManager().getShadowTreeRegistry();