expo-modules-core 56.0.11 → 56.0.13
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.
- package/CHANGELOG.md +16 -0
- package/android/build.gradle +2 -2
- package/android/src/compose/expo/modules/kotlin/views/ComposeProps.kt +35 -0
- package/android/src/compose/expo/modules/kotlin/views/ComposeViewProp.kt +20 -11
- package/android/src/main/cpp/types/FrontendConverter.cpp +1 -1
- package/build/NativeViewManagerAdapter.native.d.ts.map +1 -1
- package/package.json +2 -2
- package/prebuilds/output/debug/xcframeworks/ExpoModulesCore.tar.gz +0 -0
- package/prebuilds/output/debug/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoModulesCore.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
- package/src/NativeViewManagerAdapter.native.tsx +37 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,22 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.13 — 2026-05-26
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- [Android] Create Compose props without View. ([#46256](https://github.com/expo/expo/pull/46256) by [@jakex7](https://github.com/jakex7))
|
|
18
|
+
|
|
19
|
+
### 💡 Others
|
|
20
|
+
|
|
21
|
+
- Native view config attributes now carry a `process` function that unwraps shared objects to their registry id, so callers can pass shared objects directly as view props instead of unwrapping them manually. ([#46212](https://github.com/expo/expo/pull/46212) by [@tsapeta](https://github.com/tsapeta))
|
|
22
|
+
|
|
23
|
+
## 56.0.12 — 2026-05-21
|
|
24
|
+
|
|
25
|
+
### 🐛 Bug fixes
|
|
26
|
+
|
|
27
|
+
- [Android] Suppress `-Wunused-result` compiler warning in `FrontendConverter.cpp`. ([#46073](https://github.com/expo/expo/pull/46073) by [@tomekzaw](https://github.com/tomekzaw))
|
|
28
|
+
|
|
13
29
|
## 56.0.11 — 2026-05-20
|
|
14
30
|
|
|
15
31
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -27,7 +27,7 @@ if (shouldIncludeCompose) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
group = 'host.exp.exponent'
|
|
30
|
-
version = '56.0.
|
|
30
|
+
version = '56.0.13'
|
|
31
31
|
|
|
32
32
|
def isExpoModulesCoreTests = {
|
|
33
33
|
Gradle gradle = getGradle()
|
|
@@ -94,7 +94,7 @@ android {
|
|
|
94
94
|
defaultConfig {
|
|
95
95
|
consumerProguardFiles 'proguard-rules.pro'
|
|
96
96
|
versionCode 1
|
|
97
|
-
versionName "56.0.
|
|
97
|
+
versionName "56.0.13"
|
|
98
98
|
buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
|
|
99
99
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
|
|
100
100
|
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
package expo.modules.kotlin.views
|
|
2
2
|
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import expo.modules.kotlin.AppContext
|
|
5
|
+
import expo.modules.kotlin.recycle
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* A marker interface for props classes that are used to pass data to Compose views.
|
|
5
9
|
* Needed for the R8 to not remove needed signatures that are used to receive prop types.
|
|
6
10
|
*/
|
|
7
11
|
interface ComposeProps
|
|
12
|
+
|
|
13
|
+
inline fun <reified Props : ComposeProps> createComposeProps(
|
|
14
|
+
propsMap: ReadableMap?,
|
|
15
|
+
appContext: AppContext? = null
|
|
16
|
+
): Props {
|
|
17
|
+
val propsParsingStrategy = toPropsParsingStrategy<Props>()
|
|
18
|
+
val propsInstance = propsParsingStrategy.createNewInstance()
|
|
19
|
+
|
|
20
|
+
if (propsMap == null) {
|
|
21
|
+
return propsInstance
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
val props = propsParsingStrategy.props()
|
|
25
|
+
val iterator = propsMap.keySetIterator()
|
|
26
|
+
|
|
27
|
+
while (iterator.hasNextKey()) {
|
|
28
|
+
val name = iterator.nextKey()
|
|
29
|
+
val prop = props[name] ?: continue
|
|
30
|
+
|
|
31
|
+
propsMap.getDynamic(name).recycle {
|
|
32
|
+
@Suppress("UNCHECKED_CAST")
|
|
33
|
+
prop.setPropDirectly(
|
|
34
|
+
prop = this,
|
|
35
|
+
currentProps = propsInstance,
|
|
36
|
+
appContext = appContext
|
|
37
|
+
) as Props
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return propsInstance
|
|
42
|
+
}
|
|
@@ -20,11 +20,16 @@ class ComposeViewProp(
|
|
|
20
20
|
|
|
21
21
|
@Suppress("UNCHECKED_CAST")
|
|
22
22
|
override fun set(prop: Dynamic, onView: View, appContext: AppContext?) {
|
|
23
|
-
setPropDirectly(prop, onView, appContext)
|
|
23
|
+
setPropDirectly(prop = prop, onView = onView, appContext = appContext)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
override fun set(prop: Any?, onView: View, appContext: AppContext?) {
|
|
27
|
-
setPropDirectly(prop, onView, appContext)
|
|
27
|
+
setPropDirectly(prop = prop, onView = onView, appContext = appContext)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@PublishedApi
|
|
31
|
+
internal fun setPropDirectly(prop: Dynamic, currentProps: Any, appContext: AppContext?): Any {
|
|
32
|
+
return copyPropsWithNewValue(prop, currentProps, appContext) ?: currentProps
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
@Suppress("UNCHECKED_CAST")
|
|
@@ -37,15 +42,7 @@ class ComposeViewProp(
|
|
|
37
42
|
if (onView is ComposeFunctionHolder<*>) {
|
|
38
43
|
// Use current props state, not the initial props instance
|
|
39
44
|
val currentProps = onView.propsMutableState.value
|
|
40
|
-
|
|
41
|
-
val copy = currentProps::class.memberFunctions.firstOrNull { it.name == "copy" }
|
|
42
|
-
if (copy == null) {
|
|
43
|
-
logger.warn("⚠️ Props are not a data class with default values for all properties, cannot set prop $name dynamically.")
|
|
44
|
-
return@exceptionDecorator
|
|
45
|
-
}
|
|
46
|
-
val instanceParam = copy.instanceParameter!!
|
|
47
|
-
val newPropParam = copy.parameters.firstOrNull { it.name == name } ?: return@exceptionDecorator
|
|
48
|
-
val result = copy.callBy(mapOf(instanceParam to currentProps, newPropParam to type.convert(prop, appContext)))
|
|
45
|
+
val result = copyPropsWithNewValue(prop, currentProps, appContext) ?: return@exceptionDecorator
|
|
49
46
|
// Set the new props instance back to the onView
|
|
50
47
|
(onView.propsMutableState as MutableState<Any?>).value = result
|
|
51
48
|
return@exceptionDecorator
|
|
@@ -60,6 +57,18 @@ class ComposeViewProp(
|
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
|
|
60
|
+
private fun copyPropsWithNewValue(prop: Any?, currentProps: Any, appContext: AppContext?): Any? {
|
|
61
|
+
// TODO(@lukmccall): We should remove the copy call
|
|
62
|
+
val copy = currentProps::class.memberFunctions.firstOrNull { it.name == "copy" }
|
|
63
|
+
if (copy == null) {
|
|
64
|
+
logger.warn("⚠️ Props are not a data class with default values for all properties, cannot set prop $name dynamically.")
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
val instanceParam = copy.instanceParameter!!
|
|
68
|
+
val newPropParam = copy.parameters.firstOrNull { it.name == name } ?: return null
|
|
69
|
+
return copy.callBy(mapOf(instanceParam to currentProps, newPropParam to type.convert(prop, appContext)))
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
fun asStateProp(): ComposeViewProp {
|
|
64
73
|
_isStateProp = true
|
|
65
74
|
return this
|
|
@@ -775,7 +775,7 @@ jobject SynchronizableFrontendConverter::convert(
|
|
|
775
775
|
bool SynchronizableFrontendConverter::canConvert(jsi::Runtime &rt, const jsi::Value &value) const {
|
|
776
776
|
try {
|
|
777
777
|
// TODO(@lukmccall): find a better way to check this without throwing exception
|
|
778
|
-
worklets::extractSerializableOrThrow(rt, value);
|
|
778
|
+
(void)worklets::extractSerializableOrThrow(rt, value);
|
|
779
779
|
return true;
|
|
780
780
|
} catch (...) {
|
|
781
781
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeViewManagerAdapter.native.d.ts","sourceRoot":"","sources":["../src/NativeViewManagerAdapter.native.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAkB,KAAK,aAAa,EAA4B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeViewManagerAdapter.native.d.ts","sourceRoot":"","sources":["../src/NativeViewManagerAdapter.native.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAkB,KAAK,aAAa,EAA4B,MAAM,OAAO,CAAC;AA4HrF;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,aAAa,CAAC,CAAC,CAAC,CAsClB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-core",
|
|
3
|
-
"version": "56.0.
|
|
3
|
+
"version": "56.0.13",
|
|
4
4
|
"description": "The core of Expo Modules architecture",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@types/invariant": "^2.2.33",
|
|
67
67
|
"expo-module-scripts": "56.0.2"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "f67a101bcbe56114e982184834b93da7bbed00af",
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "expo-module build",
|
|
72
72
|
"clean": "expo-module clean",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -8,6 +8,7 @@ import { type Component, type ComponentType, createRef, PureComponent } from 're
|
|
|
8
8
|
import { type ReactNativeElement, findNodeHandle, type HostComponent } from 'react-native';
|
|
9
9
|
import { get as componentRegistryGet } from 'react-native/Libraries/NativeComponent/NativeComponentRegistry';
|
|
10
10
|
|
|
11
|
+
import { SharedObject } from './SharedObject';
|
|
11
12
|
import { requireNativeModule } from './requireNativeModule';
|
|
12
13
|
|
|
13
14
|
// To make the transition from React Native's `requireNativeComponent` to Expo's
|
|
@@ -63,15 +64,50 @@ function requireNativeComponent<Props>(
|
|
|
63
64
|
viewName ?? 'default view',
|
|
64
65
|
moduleName
|
|
65
66
|
);
|
|
67
|
+
return { uiViewClassName: nativeViewName };
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
return {
|
|
69
71
|
uiViewClassName: nativeViewName,
|
|
70
|
-
|
|
72
|
+
directEventTypes: expoViewConfig.directEventTypes,
|
|
73
|
+
validAttributes: addAttributeProcessing(expoViewConfig.validAttributes),
|
|
71
74
|
};
|
|
72
75
|
});
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Unwraps a shared object to its id so that native receives the registry id
|
|
80
|
+
* instead of the JS wrapper. Other values are passed through unchanged.
|
|
81
|
+
*/
|
|
82
|
+
function processPropValue(value: unknown): unknown {
|
|
83
|
+
if (value != null && typeof value === 'object' && value instanceof SharedObject) {
|
|
84
|
+
// `__expo_shared_object_id__` is a hidden property installed by native and planned for
|
|
85
|
+
// removal; the `typeof` check guards against returning `undefined` once native stops
|
|
86
|
+
// setting the property.
|
|
87
|
+
// @ts-expect-error
|
|
88
|
+
const sharedObjectId = value.__expo_shared_object_id__;
|
|
89
|
+
if (typeof sharedObjectId === 'number') {
|
|
90
|
+
return sharedObjectId;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wraps each attribute in the descriptor shape React Native expects and attaches `process`
|
|
98
|
+
* to unwrap shared objects to their registry id. `diff` is intentionally left unset so
|
|
99
|
+
* React Native falls back to its `deepDiffer` default, which does structural comparison
|
|
100
|
+
* for object/array props.
|
|
101
|
+
*/
|
|
102
|
+
function addAttributeProcessing(validAttributes: Record<string, any>): Record<string, any> {
|
|
103
|
+
const descriptor = { process: processPropValue };
|
|
104
|
+
const attributes: Record<string, any> = {};
|
|
105
|
+
for (const key of Object.keys(validAttributes)) {
|
|
106
|
+
attributes[key] = descriptor;
|
|
107
|
+
}
|
|
108
|
+
return attributes;
|
|
109
|
+
}
|
|
110
|
+
|
|
75
111
|
/**
|
|
76
112
|
* Requires a React Native component from cache if possible. This prevents
|
|
77
113
|
* "Tried to register two views with the same name" errors on fast refresh, but
|