expo-modules-core 57.0.18 → 57.0.19
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 +10 -0
- package/android/build.gradle +2 -2
- package/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt +4 -4
- package/android/src/compose/expo/modules/kotlin/views/HostingViewSaveableState.kt +23 -0
- package/android/src/main/java/expo/modules/kotlin/jni/worklets/WorkletNativeRuntime.kt +2 -0
- package/common/cpp/fabric/ExpoViewShadowNode.h +8 -4
- package/ios/Core/Views/SwiftUI/SwiftUIShadowNodeProxy.swift +5 -0
- package/ios/Core/Views/SwiftUI/SwiftUIViewProps.swift +3 -0
- package/ios/Core/Views/SwiftUI/SystemMenuTouchGate.swift +5 -11
- package/package.json +3 -3
- 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/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 57.0.19 — 2026-09-24
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Fixed SwiftUI view mounting blocking the main thread: `ViewProps` and `ShadowNodeProxy` now store their `objectWillChange` publisher instead of resolving it through Combine's global observable-object table on every access. ([#49528](https://github.com/expo/expo/pull/49528) by [@wielski](https://github.com/wielski))
|
|
18
|
+
- Fixed a crash while laying out an `RNHostView` with `matchContents` whose content is laid out more than once, for example a nested host or a Reanimated node. The hosted shadow node could be released before `replaceChild` finished reading it. ([#50456](https://github.com/expo/expo/pull/50456) by [@expo-bot](https://github.com/expo-bot))
|
|
19
|
+
- [Android] Fixed worklet callbacks crashing with `NoSuchFieldError` in minified release builds. ([#50502](https://github.com/expo/expo/issues/50502) by [@dhalenok](https://github.com/dhalenok), [#50510](https://github.com/expo/expo/pull/50510) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
|
|
20
|
+
- [Android] Replaced the hosting `ComposeView`'s view id with a tag for its `rememberSaveable` namespace, fixing a crash on `react-native-screens` tab switch and touches inside a `Host` reaching the wrong React Native view. ([#49964](https://github.com/expo/expo/issues/49964) by [@b2x-software](https://github.com/b2x-software), [#50299](https://github.com/expo/expo/pull/50299) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
|
|
21
|
+
- [iOS] Fixed the tap that closes a SwiftUI menu still reaching the React Native view underneath it in apps with no accessibility client attached. The menu gate no longer requires `accessibilityViewIsModal`, which UIKit only implements for the menu container while its accessibility bundle is loaded. ([#48765](https://github.com/expo/expo/issues/48765) by [@ayyoub-coder](https://github.com/ayyoub-coder), [#49916](https://github.com/expo/expo/pull/49916) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
|
|
22
|
+
|
|
13
23
|
## 57.0.18 — 2026-09-11
|
|
14
24
|
|
|
15
25
|
### 🐛 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 = '57.0.
|
|
30
|
+
version = '57.0.19'
|
|
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 "57.0.
|
|
97
|
+
versionName "57.0.19"
|
|
98
98
|
buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
|
|
99
99
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
|
|
100
100
|
|
|
@@ -221,10 +221,10 @@ abstract class ExpoComposeView<T : ComposeProps>(
|
|
|
221
221
|
|
|
222
222
|
private fun addComposeView() {
|
|
223
223
|
val composeView = ComposeView(context).also {
|
|
224
|
-
// Give each Host a unique
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
224
|
+
// Give each Host a unique rememberSaveable namespace. All Hosts share the Activity's
|
|
225
|
+
// SavedStateRegistry (set below), so without one they'd collide on a single key and only
|
|
226
|
+
// the first could save/restore state.
|
|
227
|
+
HostingViewSaveableState.assignNamespace(it)
|
|
228
228
|
it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
229
229
|
// Pin the composition to the Activity lifecycle so it survives
|
|
230
230
|
// react-native-screens detaching inactive screens on every switch.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package expo.modules.kotlin.views
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import androidx.compose.ui.R as ComposeUiR
|
|
5
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Gives each Compose hosting view a unique `rememberSaveable` namespace.
|
|
9
|
+
*
|
|
10
|
+
* Previous solutions tried to give view an id but it caused some other issues
|
|
11
|
+
* See https://github.com/expo/expo/issues/49964 and
|
|
12
|
+
* https://github.com/expo/expo/issues/49964#issuecomment-5714493768.
|
|
13
|
+
*
|
|
14
|
+
* So using a tag instead of an id is a better solution.
|
|
15
|
+
* Compose reads this tag before falling back to `View.id`, so we don't call `generateViewId()`.
|
|
16
|
+
*/
|
|
17
|
+
internal object HostingViewSaveableState {
|
|
18
|
+
private val nextNamespace = AtomicInteger()
|
|
19
|
+
|
|
20
|
+
fun assignNamespace(view: View) {
|
|
21
|
+
view.setTag(ComposeUiR.id.compose_view_saveable_id_tag, "Host:${nextNamespace.getAndIncrement()}")
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
package expo.modules.kotlin.jni.worklets
|
|
2
2
|
|
|
3
3
|
import com.facebook.jni.HybridData
|
|
4
|
+
import expo.modules.core.interfaces.DoNotStrip
|
|
4
5
|
|
|
5
6
|
class WorkletNativeRuntime(
|
|
6
7
|
jsRuntimePointer: Long
|
|
7
8
|
) {
|
|
9
|
+
@DoNotStrip
|
|
8
10
|
private val mHybridData = initHybrid(jsRuntimePointer)
|
|
9
11
|
|
|
10
12
|
external fun initHybrid(jsRuntimePointer: Long): HybridData
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#include <react/renderer/core/LayoutableShadowNode.h>
|
|
11
11
|
|
|
12
12
|
#include <algorithm>
|
|
13
|
+
#include <memory>
|
|
13
14
|
|
|
14
15
|
#include "ContentOriginRegistry.h"
|
|
15
16
|
#include "ExpoViewEventEmitter.h"
|
|
@@ -104,7 +105,7 @@ public:
|
|
|
104
105
|
return ConcreteViewShadowNode::measureContent(layoutContext, layoutConstraints);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
auto const
|
|
108
|
+
auto const content = hostedContent();
|
|
108
109
|
|
|
109
110
|
if (content == nullptr) {
|
|
110
111
|
return {};
|
|
@@ -122,7 +123,7 @@ public:
|
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
auto const
|
|
126
|
+
auto const content = hostedContent();
|
|
126
127
|
|
|
127
128
|
if (content == nullptr) {
|
|
128
129
|
return;
|
|
@@ -144,12 +145,15 @@ public:
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
private:
|
|
147
|
-
|
|
148
|
+
// Owning on purpose: `layout()` calls `replaceChild()`, which overwrites this node's only strong
|
|
149
|
+
// reference to the content — `RNHostView` is a leaf Yoga node, so Yoga keeps no second one, and
|
|
150
|
+
// `YogaLayoutableShadowNode::replaceChild()` reads `oldChild` right after.
|
|
151
|
+
std::shared_ptr<const react::LayoutableShadowNode> hostedContent() const {
|
|
148
152
|
auto const &children = this->getChildren();
|
|
149
153
|
|
|
150
154
|
return children.empty()
|
|
151
155
|
? nullptr
|
|
152
|
-
:
|
|
156
|
+
: std::dynamic_pointer_cast<const react::LayoutableShadowNode>(children.front());
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
react::LayoutDirection resolvedLayoutDirection() const {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// Copyright 2025-present 650 Industries. All rights reserved.
|
|
2
2
|
|
|
3
|
+
import Combine
|
|
4
|
+
|
|
3
5
|
extension ExpoSwiftUI {
|
|
4
6
|
open class ShadowNodeProxy: ObservableObject, Record, @unchecked Sendable {
|
|
7
|
+
// Stored so `objectWillChange` is a plain property read instead of Combine's O(live objects) side-table lookup.
|
|
8
|
+
public let objectWillChange = ObservableObjectPublisher()
|
|
9
|
+
|
|
5
10
|
public required init() {}
|
|
6
11
|
|
|
7
12
|
// We use Double.nan to mark a dimension as unset. This value is passed to https://github.com/facebook/yoga/blob/49ee855f99fb67079c24d507a4ea1b6d80fa2ebf/yoga/style/StyleLength.h#L33
|
|
@@ -23,6 +23,9 @@ extension ExpoSwiftUI {
|
|
|
23
23
|
It's a record that can be observed by SwiftUI to re-render on its changes.
|
|
24
24
|
*/
|
|
25
25
|
open class ViewProps: ObservableObject, Record {
|
|
26
|
+
// Stored so `objectWillChange` is a plain property read instead of Combine's O(live objects) side-table lookup.
|
|
27
|
+
public let objectWillChange = ObservableObjectPublisher()
|
|
28
|
+
|
|
26
29
|
public required init() {}
|
|
27
30
|
|
|
28
31
|
public required init(rawProps: [String: Any], context: AppContext) throws {
|
|
@@ -31,24 +31,18 @@ internal final class SystemMenuTouchGate: UIGestureRecognizer, UIGestureRecogniz
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
Consider Menu to be open
|
|
34
|
+
Consider Menu to be open while the container is present and still accepts interaction.
|
|
35
35
|
UIKit turns `isUserInteractionEnabled` off the moment dismissal commits, so taps made during
|
|
36
|
-
the dismiss animation pass through to the app again.
|
|
37
|
-
marks the container as blocking the content behind it — a semantic signal beside the class name.
|
|
36
|
+
the dismiss animation pass through to the app again.
|
|
38
37
|
*/
|
|
39
|
-
static func isOpenContextMenuContainer(
|
|
40
|
-
className
|
|
41
|
-
isUserInteractionEnabled: Bool,
|
|
42
|
-
accessibilityViewIsModal: Bool
|
|
43
|
-
) -> Bool {
|
|
44
|
-
return isUserInteractionEnabled && accessibilityViewIsModal && isContextMenuContainerClassName(className)
|
|
38
|
+
static func isOpenContextMenuContainer(className: String, isUserInteractionEnabled: Bool) -> Bool {
|
|
39
|
+
return isUserInteractionEnabled && isContextMenuContainerClassName(className)
|
|
45
40
|
}
|
|
46
41
|
|
|
47
42
|
static func isContextMenuContainer(_ view: UIView) -> Bool {
|
|
48
43
|
return isOpenContextMenuContainer(
|
|
49
44
|
className: NSStringFromClass(type(of: view)),
|
|
50
|
-
isUserInteractionEnabled: view.isUserInteractionEnabled
|
|
51
|
-
accessibilityViewIsModal: view.accessibilityViewIsModal
|
|
45
|
+
isUserInteractionEnabled: view.isUserInteractionEnabled
|
|
52
46
|
)
|
|
53
47
|
}
|
|
54
48
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-core",
|
|
3
|
-
"version": "57.0.
|
|
3
|
+
"version": "57.0.19",
|
|
4
4
|
"description": "The core of Expo Modules architecture",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@expo/expo-modules-macros-plugin": "0.6.1",
|
|
50
|
-
"expo-modules-jsi": "~57.1.
|
|
50
|
+
"expo-modules-jsi": "~57.1.1",
|
|
51
51
|
"invariant": "^2.2.4"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@types/invariant": "^2.2.33",
|
|
67
67
|
"expo-module-scripts": "56.0.3"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "9349d1457178dd9cccb8bdd3aed47473c49134e1",
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "expo-module build",
|
|
72
72
|
"clean": "expo-module clean",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|