expo-modules-core 2.0.6 → 2.1.1

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 CHANGED
@@ -10,6 +10,25 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 2.1.1 — 2024-12-02
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 2.1.0 — 2024-11-29
18
+
19
+ ### 🎉 New features
20
+
21
+ - Added support for rendering children components in SwiftUI views. ([#33271](https://github.com/expo/expo/pull/33271) by [@tsapeta](https://github.com/tsapeta))
22
+
23
+ ### 🐛 Bug fixes
24
+
25
+ - Fixed backwards compatibility in the `EventEmitter` constructor. ([#33294](https://github.com/expo/expo/pull/33294) by [@tsapeta](https://github.com/tsapeta))
26
+
27
+ ### 💡 Others
28
+
29
+ - Fixed compatibility for React Native 0.77. ([#33277](https://github.com/expo/expo/pull/33277) by [@kudo](https://github.com/kudo))
30
+ - [Android] Bump KSP version for Kotlin 2.0.21. ([#33306](https://github.com/expo/expo/pull/33306) by [@kudo](https://github.com/kudo))
31
+
13
32
  ## 2.0.6 — 2024-11-22
14
33
 
15
34
  ### 🐛 Bug fixes
@@ -27,7 +27,7 @@ class KotlinExpoModulesCorePlugin implements Plugin<Project> {
27
27
  "1.8.22": "1.8.22-1.0.11",
28
28
  "1.9.23": "1.9.23-1.0.20",
29
29
  "1.9.24": "1.9.24-1.0.20",
30
- "2.0.21": "2.0.21-1.0.27"
30
+ "2.0.21": "2.0.21-1.0.28"
31
31
  ]
32
32
 
33
33
  project.rootProject.ext.has("kspVersion")
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3
3
  apply plugin: 'com.android.library'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '2.0.6'
6
+ version = '2.1.1'
7
7
 
8
8
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
9
9
  apply from: expoModulesCorePlugin
@@ -67,7 +67,7 @@ android {
67
67
  defaultConfig {
68
68
  consumerProguardFiles 'proguard-rules.pro'
69
69
  versionCode 1
70
- versionName "2.0.6"
70
+ versionName "2.1.1"
71
71
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
72
72
 
73
73
  testInstrumentationRunner "expo.modules.TestRunner"
@@ -15,6 +15,7 @@ import com.facebook.react.uimanager.style.BoxShadow
15
15
  import com.facebook.react.uimanager.style.LogicalEdge
16
16
  import expo.modules.kotlin.types.enforceType
17
17
  import expo.modules.kotlin.views.ViewDefinitionBuilder
18
+ import expo.modules.rncompatibility.parseBoxShadow
18
19
 
19
20
  inline fun <reified T : View> ViewDefinitionBuilder<T>.UseBorderColorProps(crossinline body: (view: T, edge: LogicalEdge, color: Int?) -> Unit) {
20
21
  PropGroup(
@@ -143,7 +144,7 @@ inline fun <reified T : View> ViewDefinitionBuilder<T>.UseBoxShadowProp(crossinl
143
144
 
144
145
  val shadowStyle = mutableListOf<BoxShadow>()
145
146
  for (i in 0..<shadows.size()) {
146
- val shadow = BoxShadow.parse(shadows.getMap(i)) ?: continue
147
+ val shadow = parseBoxShadow(shadows.getMap(i), view.context) ?: continue
147
148
  shadowStyle.add((shadow))
148
149
  }
149
150
  body(view, shadowStyle)
@@ -0,0 +1,14 @@
1
+ package expo.modules.rncompatibility
2
+
3
+ import android.content.Context
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.uimanager.style.BoxShadow
6
+
7
+ /**
8
+ * A compatible wrapper of [BoxShadow.parse] for React Native compatibility
9
+ * TODO(kudo,20241127): Remove this when we drop react-native 0.76 support
10
+ */
11
+ @Suppress("UNUSED_PARAMETER")
12
+ fun parseBoxShadow(boxShadow: ReadableMap, context: Context): BoxShadow? {
13
+ return BoxShadow.parse(boxShadow)
14
+ }
@@ -0,0 +1,13 @@
1
+ package expo.modules.rncompatibility
2
+
3
+ import android.content.Context
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.uimanager.style.BoxShadow
6
+
7
+ /**
8
+ * A compatible wrapper of [BoxShadow.parse] for React Native compatibility
9
+ * TODO(kudo,20241127): Remove this when we drop react-native 0.76 support
10
+ */
11
+ fun parseBoxShadow(boxShadow: ReadableMap?, context: Context): BoxShadow? {
12
+ return BoxShadow.parse(boxShadow, context)
13
+ }
@@ -32,6 +32,12 @@ export declare class EventEmitter<TEventsMap extends EventsMap = Record<never, n
32
32
  * Creates a new event emitter instance.
33
33
  */
34
34
  constructor();
35
+ /**
36
+ * @deprecated As of Expo SDK 52 the given object is already an EventEmitter.
37
+ * Creating a new one is not necessary.
38
+ * @hidden
39
+ */
40
+ constructor(object: EventEmitter);
35
41
  /**
36
42
  * Adds a listener for the given event name.
37
43
  */
@@ -1 +1 @@
1
- {"version":3,"file":"EventEmitter.d.ts","sourceRoot":"","sources":["../../src/ts-declarations/EventEmitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,YAAY,CAAC,UAAU,SAAS,SAAS,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC;IACnF;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,UAAU,CAAC;IAErC;;OAEG;;IAGH;;OAEG;IACH,WAAW,CAAC,SAAS,SAAS,MAAM,UAAU,EAC5C,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,GAC9B,iBAAiB;IAEpB;;OAEG;IACH,cAAc,CAAC,SAAS,SAAS,MAAM,UAAU,EAC/C,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,GAC9B,IAAI;IAEP;;OAEG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,UAAU,GAAG,IAAI;IAErD;;;OAGG;IACH,IAAI,CAAC,SAAS,SAAS,MAAM,UAAU,EACrC,SAAS,EAAE,SAAS,EACpB,GAAG,IAAI,EAAE,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,GACzC,IAAI;IAEP;;OAEG;IACH,aAAa,CAAC,SAAS,SAAS,MAAM,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM;IAE/E;;;OAGG;IACH,cAAc,CAAC,CAAC,SAAS,SAAS,MAAM,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;IAE/E;;;OAGG;IACH,aAAa,CAAC,CAAC,SAAS,SAAS,MAAM,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;CAC/E"}
1
+ {"version":3,"file":"EventEmitter.d.ts","sourceRoot":"","sources":["../../src/ts-declarations/EventEmitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,YAAY,CAAC,UAAU,SAAS,SAAS,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC;IACnF;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,UAAU,CAAC;IAErC;;OAEG;;IAGH;;;;OAIG;gBACS,MAAM,EAAE,YAAY;IAEhC;;OAEG;IACH,WAAW,CAAC,SAAS,SAAS,MAAM,UAAU,EAC5C,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,GAC9B,iBAAiB;IAEpB;;OAEG;IACH,cAAc,CAAC,SAAS,SAAS,MAAM,UAAU,EAC/C,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,GAC9B,IAAI;IAEP;;OAEG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,UAAU,GAAG,IAAI;IAErD;;;OAGG;IACH,IAAI,CAAC,SAAS,SAAS,MAAM,UAAU,EACrC,SAAS,EAAE,SAAS,EACpB,GAAG,IAAI,EAAE,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,GACzC,IAAI;IAEP;;OAEG;IACH,aAAa,CAAC,SAAS,SAAS,MAAM,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM;IAE/E;;;OAGG;IACH,cAAc,CAAC,CAAC,SAAS,SAAS,MAAM,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;IAE/E;;;OAGG;IACH,aAAa,CAAC,CAAC,SAAS,SAAS,MAAM,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;CAC/E"}
@@ -211,8 +211,13 @@ void installClass(jsi::Runtime &runtime) {
211
211
  // To provide backwards compatibility with the old EventEmitter where the native module object was passed as an argument.
212
212
  // We're checking if the argument is already an instance of the new emitter and if so, just return it without unnecessarily wrapping it.
213
213
  if (count > 0) {
214
- jsi::Object firstArg = args[0].asObject(runtime);
215
- jsi::Function constructor = thisValue.asObject(runtime).getPropertyAsFunction(runtime, "constructor");
214
+ // We need the tmp object to correctly unwrap the lazy object.
215
+ // For some reason, if we inline the retrieval of the first argument, the instanceOf check fails on Android.
216
+ // This is probably because the object is copied somewhere in the process.
217
+ const jsi::Object &tmp = args[0].asObject(runtime);
218
+ const jsi::Object &firstArg = LazyObject::unwrapObjectIfNecessary(runtime, tmp);
219
+
220
+ jsi::Function constructor = thisValue.getObject(runtime).getPropertyAsFunction(runtime, "constructor");
216
221
 
217
222
  if (firstArg.instanceOf(runtime, constructor)) {
218
223
  return jsi::Value(runtime, args[0]);
@@ -0,0 +1,45 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ import SwiftUI
4
+
5
+ extension ExpoSwiftUI {
6
+ /**
7
+ View that renders an UIKit-based React child view and manages its layout by synchronizing `UIView.frame` with the SwiftUI system.
8
+
9
+ React Native sets `center` and `bounds` properties during the layout, but as per Apple docs, this is not recommended and may result
10
+ in undefined behavior (read more in ``UIViewRepresentable`` docs). To fix it, we're observing for changes in `bounds` and then
11
+ pass the new frame to SwiftUI so it can update its layout metrics and then set UIView's origin to zero (so it doesn't affect SwiftUI's layout in any way).
12
+ */
13
+ public struct Child: SwiftUI.View, Identifiable {
14
+ public let id: Int
15
+ public let view: UIView
16
+
17
+ @ObservedObject
18
+ private var layoutMetrics: LayoutMetrics
19
+
20
+ private let viewFrameObserver: UIViewFrameObserver
21
+
22
+ init(view: UIView) {
23
+ self.id = ObjectIdentifier(view).hashValue
24
+ self.view = view
25
+ self.layoutMetrics = LayoutMetrics(frame: view.frame)
26
+ self.viewFrameObserver = UIViewFrameObserver(view: view)
27
+
28
+ // Observe for layout changes made by React.
29
+ viewFrameObserver.observe { [weak view, layoutMetrics] frame in
30
+ // Update layout metrics for the SwiftUI view. This will trigger a re-render as it changes the observed object.
31
+ layoutMetrics.frame = frame
32
+
33
+ // Reset UIKit's origin to zero so it's fully controlled by `.offset(x:y:)` in SwiftUI.
34
+ // SwiftUI may reset it anyway, but we want this to be explicit.
35
+ view?.frame = CGRect(origin: .zero, size: frame.size)
36
+ }
37
+ }
38
+
39
+ public var body: some SwiftUI.View {
40
+ return UIViewHost(view: view)
41
+ .frame(width: layoutMetrics.width, height: layoutMetrics.height, alignment: .topLeading)
42
+ .offset(x: layoutMetrics.x, y: layoutMetrics.y)
43
+ }
44
+ }
45
+ }
@@ -78,6 +78,32 @@ extension ExpoSwiftUI {
78
78
  }
79
79
 
80
80
  #if os(iOS) || os(tvOS)
81
+ #if RCT_NEW_ARCH_ENABLED
82
+ /**
83
+ Fabric calls this function when mounting (attaching) a child component view.
84
+ */
85
+ public override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
86
+ var children = props.children ?? []
87
+ let child = Child(view: childComponentView)
88
+
89
+ children.insert(child, at: index)
90
+
91
+ props.children = children
92
+ }
93
+
94
+ /**
95
+ Fabric calls this function when unmounting (detaching) a child component view.
96
+ */
97
+ public override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
98
+ // Make sure the view has no superview, React Native asserts against this.
99
+ childComponentView.removeFromSuperview()
100
+
101
+ if let children = props.children {
102
+ props.children = children.filter({ $0.view != childComponentView })
103
+ }
104
+ }
105
+ #endif // RCT_NEW_ARCH_ENABLED
106
+
81
107
  /**
82
108
  Setups layout constraints of the hosting controller view to match the layout set by React.
83
109
  */
@@ -0,0 +1,31 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ extension ExpoSwiftUI {
4
+ /**
5
+ Represents layout metrics to apply on SwiftUI view, i.e. its size and offset.
6
+ */
7
+ class LayoutMetrics: ObservableObject {
8
+ @Published
9
+ var frame: CGRect = .zero
10
+
11
+ init(frame: CGRect) {
12
+ self.frame = frame
13
+ }
14
+
15
+ var width: CGFloat {
16
+ return frame.width
17
+ }
18
+
19
+ var height: CGFloat {
20
+ return frame.height
21
+ }
22
+
23
+ var x: CGFloat {
24
+ return frame.minX
25
+ }
26
+
27
+ var y: CGFloat {
28
+ return frame.minY
29
+ }
30
+ }
31
+ }
@@ -14,6 +14,17 @@ public protocol ExpoSwiftUIView<Props>: SwiftUI.View {
14
14
  init()
15
15
  }
16
16
 
17
+ public extension ExpoSwiftUIView {
18
+ /**
19
+ Returns React's children as SwiftUI views.
20
+ */
21
+ func Children() -> some View { // swiftlint:disable:this identifier_name
22
+ ZStack(alignment: .topLeading) {
23
+ ForEach(props.children ?? []) { $0 }
24
+ }
25
+ }
26
+ }
27
+
17
28
  extension ExpoSwiftUI {
18
29
  public typealias View = ExpoSwiftUIView
19
30
 
@@ -0,0 +1,32 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ extension ExpoSwiftUI {
4
+ /**
5
+ Observes for view frame changes, usually applied by React Native.
6
+ */
7
+ internal class UIViewFrameObserver {
8
+ private let view: UIView
9
+ private var observer: NSKeyValueObservation?
10
+
11
+ init(view: UIView) {
12
+ self.view = view
13
+ }
14
+
15
+ func observe(_ callback: @escaping (CGRect) -> Void) {
16
+ // When React Native lays out views, it sets `center` and `bounds` properties to control the `frame`.
17
+ // You can find this implementation in `updateLayoutMetrics:oldLayoutMetrics:` in `UIView+ComponentViewProtocol.mm`.
18
+ // We observe changes on `bounds` because:
19
+ // - It's being changed after `center`, so the origin is already up-to-date.
20
+ // - `frame` changes much more often, it seems that SwiftUI does it as well.
21
+ observer = view.observe(\.bounds, options: [.old, .new]) { view, change in
22
+ guard let newValue = change.newValue else {
23
+ return
24
+ }
25
+
26
+ // Update layout metrics of the `Child` view. The `origin` needs to be frame's origin,
27
+ // as `bounds` refers to coordinates relative to view's own space (instead of the parent's space).
28
+ callback(CGRect(origin: view.frame.origin, size: newValue.size))
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ // Copyright 2024-present 650 Industries. All rights reserved.
2
+
3
+ import SwiftUI
4
+
5
+ extension ExpoSwiftUI {
6
+ /**
7
+ SwiftUI view that embeds an UIKit-based view.
8
+ */
9
+ struct UIViewHost: UIViewRepresentable {
10
+ let view: UIView
11
+
12
+ func makeUIView(context: Context) -> UIView {
13
+ return view
14
+ }
15
+
16
+ func updateUIView(_ uiView: UIView, context: Context) {
17
+ // Nothing to do here
18
+ }
19
+ }
20
+ }
@@ -10,6 +10,11 @@ extension ExpoSwiftUI {
10
10
  open class ViewProps: ObservableObject, Record {
11
11
  public required init() {}
12
12
 
13
+ /**
14
+ An array of views passed by React as children.
15
+ */
16
+ @Field public var children: [Child]?
17
+
13
18
  internal func updateRawProps(_ rawProps: [String: Any], appContext: AppContext) throws {
14
19
  // Update the props just like the records
15
20
  try update(withDict: rawProps, appContext: appContext)
@@ -1,10 +1,9 @@
1
1
  // Copyright 2022-present 650 Industries. All rights reserved.
2
2
 
3
+ #import <ExpoModulesCore/Platform.h>
4
+
3
5
  #ifdef RCT_NEW_ARCH_ENABLED
4
6
 
5
- #if !TARGET_OS_OSX
6
- #import <UIKit/UIKit.h>
7
- #endif // !TARGET_OS_OSX
8
7
  #import <React/React-Core-umbrella.h>
9
8
 
10
9
  #ifdef __cplusplus
@@ -41,6 +40,18 @@
41
40
 
42
41
  - (BOOL)supportsPropWithName:(nonnull NSString *)name;
43
42
 
43
+ // MARK: - Derived from RCTComponentViewProtocol
44
+
44
45
  - (void)prepareForRecycle;
45
46
 
47
+ /*
48
+ * Called for mounting (attaching) a child component view inside `self` component view.
49
+ */
50
+ - (void)mountChildComponentView:(nonnull UIView *)childComponentView index:(NSInteger)index;
51
+
52
+ /*
53
+ * Called for unmounting (detaching) a child component view from `self` component view.
54
+ */
55
+ - (void)unmountChildComponentView:(nonnull UIView *)childComponentView index:(NSInteger)index;
56
+
46
57
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "2.0.6",
3
+ "version": "2.1.1",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -44,5 +44,5 @@
44
44
  "@testing-library/react-native": "^12.5.2",
45
45
  "expo-module-scripts": "^4.0.0"
46
46
  },
47
- "gitHead": "65ae6fda78837f77eea5a2107066fc545a211804"
47
+ "gitHead": "bc1fea6bcab47889e2922d543920397691b200f3"
48
48
  }
@@ -36,6 +36,13 @@ export declare class EventEmitter<TEventsMap extends EventsMap = Record<never, n
36
36
  */
37
37
  constructor();
38
38
 
39
+ /**
40
+ * @deprecated As of Expo SDK 52 the given object is already an EventEmitter.
41
+ * Creating a new one is not necessary.
42
+ * @hidden
43
+ */
44
+ constructor(object: EventEmitter);
45
+
39
46
  /**
40
47
  * Adds a listener for the given event name.
41
48
  */