expo-modules-core 3.0.0 → 3.0.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,10 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 3.0.1 — 2025-08-15
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
13
17
  ## 3.0.0 — 2025-08-13
14
18
 
15
19
  ### 🎉 New features
@@ -22,6 +26,7 @@
22
26
  - [Android] Add VRUtilities for VR-related values and utils ([#37744](https://github.com/expo/expo/pull/37744) by [@behenate](https://github.com/behenate))
23
27
  - [Andorid] Start using experimental converters in properties. ([#38597](https://github.com/expo/expo/pull/38597) by [@lukmccall](https://github.com/lukmccall))
24
28
  - [Android] Rethrow pending Jni exceptions. ([#38625](https://github.com/expo/expo/pull/38625) by [@jakex7](https://github.com/jakex7))
29
+ - [iOS] Add new type - `ValueOrUndefined`. ([#38620](https://github.com/expo/expo/pull/38620) by [@lukmccall](https://github.com/lukmccall))
25
30
 
26
31
  ### 🐛 Bug fixes
27
32
 
@@ -48,6 +53,7 @@
48
53
  - Add missing peer dependencies for `react` and `react-native` ([#38528](https://github.com/expo/expo/pull/38528) by [@kitten](https://github.com/kitten))
49
54
  - Deprecate `Constants` in favor of `Constant` and `Property`. ([#38748](https://github.com/expo/expo/pull/38748) by [@jakex7](https://github.com/jakex7))
50
55
  - Refactor typings to replace `ExpoGlobal` with namespace and to preserve type+value overlay on re-exported core globals ([#38649](https://github.com/expo/expo/pull/38649) by [@kitten](https://github.com/kitten))
56
+ - Fixed internal `JavaScriptValue` and added `props.globalEventDispatcher` for expo-ui. ([#38542](https://github.com/expo/expo/pull/38542) by [@kudo](https://github.com/kudo))
51
57
 
52
58
  ## 2.5.0 - 2025-07-17
53
59
 
@@ -0,0 +1,132 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesTestCore
4
+
5
+ @testable import ExpoModulesCore
6
+
7
+ final class ValueOrUndefinedSpec: ExpoSpec {
8
+ override class func spec() {
9
+ describe("operators") {
10
+ it("==") {
11
+ expect(
12
+ ValueOrUndefined<Int>.undefined == ValueOrUndefined<Int>.undefined
13
+ ).to(beTrue())
14
+ expect(
15
+ ValueOrUndefined<Int>.value(unwrapped: 10) == ValueOrUndefined<Int>.value(unwrapped: 10)
16
+ ).to(beTrue())
17
+ expect(
18
+ ValueOrUndefined<Int>.value(unwrapped: 10) == ValueOrUndefined<Int>.undefined
19
+ ).to(beFalse())
20
+ expect(
21
+ ValueOrUndefined<Int>.undefined == ValueOrUndefined<Int>.value(unwrapped: 10)
22
+ ).to(beFalse())
23
+ }
24
+
25
+ it("<") {
26
+ expect(
27
+ ValueOrUndefined<Int>.undefined < ValueOrUndefined<Int>.undefined
28
+ ).to(beFalse())
29
+ expect(
30
+ ValueOrUndefined<Int>.value(unwrapped: 10) < ValueOrUndefined<Int>.value(unwrapped: 10)
31
+ ).to(beFalse())
32
+ expect(
33
+ ValueOrUndefined<Int>.value(unwrapped: 10) < ValueOrUndefined<Int>.value(unwrapped: 20)
34
+ ).to(beTrue())
35
+ expect(
36
+ ValueOrUndefined<Int>.value(unwrapped: 10) < ValueOrUndefined<Int>.undefined
37
+ ).to(beFalse())
38
+ expect(
39
+ ValueOrUndefined<Int>.undefined == ValueOrUndefined<Int>.value(unwrapped: 10)
40
+ ).to(beFalse())
41
+ }
42
+ }
43
+ describe("module") {
44
+ let appContext = AppContext.create()
45
+ let runtime = try! appContext.runtime
46
+
47
+ beforeSuite {
48
+ appContext.moduleRegistry.register(moduleType: UndefinedSpecModule.self)
49
+ }
50
+
51
+ it("converts from undefined to ValueOrUndefinedSpec<Int>") {
52
+ let wasUndefinded = try runtime
53
+ .eval("expo.modules.ValueOrUndefinedModule.undefined_of_int(undefined)")
54
+ .asBool()
55
+
56
+ expect(wasUndefinded).to(beTrue())
57
+ }
58
+
59
+ it("converts from int to ValueOrUndefinedSpec<Int>") {
60
+ let wasUndefinded = try runtime
61
+ .eval("expo.modules.ValueOrUndefinedModule.undefined_of_int(10)")
62
+ .asBool()
63
+
64
+ expect(wasUndefinded).to(beFalse())
65
+ }
66
+
67
+ it("converts from undefined to ValueOrUndefinedSpec<Int?>") {
68
+ let wasUndefinded = try runtime
69
+ .eval("expo.modules.ValueOrUndefinedModule.undefined_of_optional_int(undefined, null)")
70
+ .asBool()
71
+
72
+ expect(wasUndefinded).to(beTrue())
73
+ }
74
+
75
+ it("converts from int to ValueOrUndefinedSpec<Int?>") {
76
+ let wasUndefinded = try runtime
77
+ .eval("expo.modules.ValueOrUndefinedModule.undefined_of_optional_int(10, 10)")
78
+ .asBool()
79
+
80
+ expect(wasUndefinded).to(beFalse())
81
+ }
82
+
83
+ it("converts from null to ValueOrUndefinedSpec<Int?>") {
84
+ let wasUndefinded = try runtime
85
+ .eval("expo.modules.ValueOrUndefinedModule.undefined_of_optional_int(null, null)")
86
+ .asBool()
87
+
88
+ expect(wasUndefinded).to(beFalse())
89
+ }
90
+
91
+ it("converts from array to [ValueOrUndefinedSpec<Int>]") {
92
+ let wereUndefinded = try runtime
93
+ .eval("expo.modules.ValueOrUndefinedModule.array_of_undefined_of_int([1, undefined, 2, undefined, 3])")
94
+ .asArray().map { try $0!.asBool() }
95
+
96
+ expect(wereUndefinded).to(equal([false, true, false, true, false]))
97
+ }
98
+
99
+ it("converts from array to [ValueOrUndefinedSpec<Int?>]") {
100
+ let wereUndefinded = try runtime
101
+ .eval("expo.modules.ValueOrUndefinedModule.array_of_undefined_of_optional_int([1, undefined, null, 2, undefined, null], [1, null, null, 2, null, null])")
102
+ .asArray().map { try $0!.asBool() }
103
+
104
+ expect(wereUndefinded).to(equal([false, true, false, false, true, false]))
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ fileprivate final class UndefinedSpecModule: Module {
111
+ func definition() -> ModuleDefinition {
112
+ Name("ValueOrUndefinedModule")
113
+
114
+ Function("undefined_of_int") { (value: ValueOrUndefined<Int>) in
115
+ return value.isUndefinded
116
+ }
117
+
118
+ Function("undefined_of_optional_int") { (value: ValueOrUndefined<Int?>, expectedValue: Int?) in
119
+ expect(value.optional).to(expectedValue == nil ? beNil() : equal(expectedValue))
120
+ return value.isUndefinded
121
+ }
122
+
123
+ Function("array_of_undefined_of_int") { (values: [ValueOrUndefined<Int>]) in
124
+ return values.map { $0.isUndefinded }
125
+ }
126
+
127
+ Function("array_of_undefined_of_optional_int") { (values: [ValueOrUndefined<Int?>], expectedValues: [Int?]) in
128
+ expect(values.map{ $0.optional} ).to(equal(expectedValues))
129
+ return values.map { $0.isUndefinded }
130
+ }
131
+ }
132
+ }
@@ -25,7 +25,7 @@ if (shouldIncludeCompose) {
25
25
  }
26
26
 
27
27
  group = 'host.exp.exponent'
28
- version = '3.0.0'
28
+ version = '3.0.1'
29
29
 
30
30
  def isExpoModulesCoreTests = {
31
31
  Gradle gradle = getGradle()
@@ -75,7 +75,7 @@ android {
75
75
  defaultConfig {
76
76
  consumerProguardFiles 'proguard-rules.pro'
77
77
  versionCode 1
78
- versionName "3.0.0"
78
+ versionName "3.0.1"
79
79
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
80
80
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
81
81
 
@@ -0,0 +1,62 @@
1
+ // Copyright 2021-present 650 Industries. All rights reserved.
2
+
3
+ protocol AnyValueOrUndefined: AnyArgument {}
4
+
5
+ public enum ValueOrUndefined<InnerType: AnyArgument>: AnyValueOrUndefined {
6
+ public static func getDynamicType() -> any AnyDynamicType {
7
+ return DynamicValueOrUndefinedType<InnerType>()
8
+ }
9
+
10
+ case undefined
11
+ case value(unwrapped: InnerType)
12
+
13
+ var optional: InnerType? {
14
+ switch self {
15
+ // We want to produce Optional(nil) instead of nil - that's what DynamicOptionalType does
16
+ case .undefined: return Any??.some(nil) as Any?? as! InnerType?
17
+ case .value(let value): return value
18
+ }
19
+ }
20
+
21
+ var isUndefinded: Bool {
22
+ if case .undefined = self {
23
+ return true
24
+ }
25
+
26
+ return false
27
+ }
28
+ }
29
+
30
+ extension ValueOrUndefined: Equatable where InnerType: Equatable {
31
+ public static func == (lhs: ValueOrUndefined, rhs: ValueOrUndefined) -> Bool {
32
+ switch (lhs, rhs) {
33
+ case (.undefined, .undefined):
34
+ return true
35
+ case (.value(let lhsValue), .value(let rhsValue)):
36
+ return lhsValue == rhsValue
37
+ default:
38
+ return false
39
+ }
40
+ }
41
+ }
42
+
43
+ extension ValueOrUndefined: Comparable where InnerType: Comparable {
44
+ public static func < (lhs: ValueOrUndefined, rhs: ValueOrUndefined) -> Bool {
45
+ switch (lhs, rhs) {
46
+ case (.undefined, .undefined):
47
+ return false // undefined is considered equal to another undefined
48
+ case (.undefined, _):
49
+ return false
50
+ case (_, .undefined):
51
+ return false
52
+ case (.value(let lhsValue), .value(let rhsValue)):
53
+ return lhsValue < rhsValue
54
+ }
55
+ }
56
+ }
57
+
58
+ extension ValueOrUndefined {
59
+ public static func ?? <T>(valueOrUndefined: consuming ValueOrUndefined<T>, defaultValue: @autoclosure () -> T) -> T {
60
+ return valueOrUndefined.optional ?? defaultValue()
61
+ }
62
+ }
@@ -123,7 +123,7 @@ internal protocol ClassAssociatedObject {}
123
123
 
124
124
  // Basically we only need these two
125
125
  extension JavaScriptObject: ClassAssociatedObject, AnyArgument, AnyJavaScriptValue {
126
- internal static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
126
+ public static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
127
127
  guard value.kind == .object else {
128
128
  throw Conversions.ConvertingException<JavaScriptObject>(value)
129
129
  }
@@ -53,6 +53,9 @@ private func DynamicType<T>(_ type: T.Type) -> AnyDynamicType {
53
53
  if let AnyEitherType = T.self as? AnyEither.Type {
54
54
  return AnyEitherType.getDynamicType()
55
55
  }
56
+ if let AnyValueOrUndefinedType = T.self as? AnyValueOrUndefined.Type {
57
+ return AnyValueOrUndefinedType.getDynamicType()
58
+ }
56
59
  return DynamicRawType(innerType: T.self)
57
60
  }
58
61
 
@@ -0,0 +1,34 @@
1
+ // Copyright 2021-present 650 Industries. All rights reserved.
2
+
3
+ internal struct DynamicValueOrUndefinedType<InnerType: AnyArgument>: AnyDynamicType {
4
+ let innerType: InnerType.Type = InnerType.self
5
+ let dynamicInnerType: AnyDynamicType = InnerType.getDynamicType()
6
+
7
+ func wraps<InnerType>(_ type: InnerType.Type) -> Bool {
8
+ return innerType == type
9
+ }
10
+
11
+ func equals(_ type: any AnyDynamicType) -> Bool {
12
+ return type is Self
13
+ }
14
+
15
+ func cast(jsValue: JavaScriptValue, appContext: AppContext) throws -> Any {
16
+ if jsValue.isUndefined() {
17
+ return ValueOrUndefined<InnerType>.undefined
18
+ }
19
+
20
+ return try dynamicInnerType.cast(jsValue: jsValue, appContext: appContext)
21
+ }
22
+
23
+ func cast<ValueType>(_ value: ValueType, appContext: AppContext) throws -> Any {
24
+ if value is ValueOrUndefined<InnerType> {
25
+ return value
26
+ }
27
+
28
+ return ValueOrUndefined<InnerType>.value(unwrapped: try dynamicInnerType.cast(value, appContext: appContext) as! InnerType)
29
+ }
30
+
31
+ var description: String {
32
+ return "ValueOrUndefined<\(dynamicInnerType)>"
33
+ }
34
+ }
@@ -53,7 +53,7 @@ public final class JavaScriptFunction<ReturnType>: AnyArgument, AnyJavaScriptVal
53
53
 
54
54
  // MARK: - AnyJavaScriptValue
55
55
 
56
- internal static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
56
+ public static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
57
57
  guard value.kind == .function else {
58
58
  throw Conversions.ConvertingException<JavaScriptFunction<ReturnType>>(value)
59
59
  }
@@ -117,12 +117,14 @@ extension ExpoSwiftUI {
117
117
  }
118
118
 
119
119
  public override func getSupportedEventNames() -> [String] {
120
- return dummyPropsMirror.children.compactMap { (label: String?, value: Any) in
120
+ let builtInEventNames = [GLOBAL_EVENT_NAME]
121
+ let propEventNames: [String] = dummyPropsMirror.children.compactMap { (label: String?, value: Any) in
121
122
  guard let event = value as? EventDispatcher else {
122
123
  return nil
123
124
  }
124
125
  return event.customName ?? convertLabelToKey(label)
125
126
  }
127
+ return builtInEventNames + propEventNames
126
128
  }
127
129
  }
128
130
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  import SwiftUI
4
4
 
5
+ internal let GLOBAL_EVENT_NAME = "onGlobalEvent"
6
+
5
7
  extension ExpoSwiftUI {
6
8
  /**
7
9
  Base implementation of the view props object for SwiftUI views.
@@ -15,6 +17,11 @@ extension ExpoSwiftUI {
15
17
  */
16
18
  @Field public var children: [any AnyChild]?
17
19
 
20
+ /**
21
+ A global event dispatcher that allows views to call `view.dispatchEvent(_:payload)` directly
22
+ */
23
+ public let globalEventDispatcher = EventDispatcher(GLOBAL_EVENT_NAME)
24
+
18
25
  internal func updateRawProps(_ rawProps: [String: Any], appContext: AppContext) throws {
19
26
  // Update the props just like the records
20
27
  try update(withDict: rawProps, appContext: appContext)
@@ -24,6 +31,10 @@ extension ExpoSwiftUI {
24
31
  }
25
32
 
26
33
  internal func setUpEvents(_ dispatcher: @escaping (_ eventName: String, _ payload: Any) -> Void) {
34
+ globalEventDispatcher.handler = { payload in
35
+ dispatcher(GLOBAL_EVENT_NAME, payload)
36
+ }
37
+
27
38
  Mirror(reflecting: self).children.forEach { (label: String?, value: Any) in
28
39
  guard let event = value as? EventDispatcher else {
29
40
  return
@@ -18,7 +18,7 @@ public enum JavaScriptValueKind: String {
18
18
  /**
19
19
  A protocol that JavaScript values, objects and functions can conform to.
20
20
  */
21
- protocol AnyJavaScriptValue: AnyArgument {
21
+ public protocol AnyJavaScriptValue: AnyArgument {
22
22
  /**
23
23
  Tries to convert a raw JavaScript value to the conforming type.
24
24
  */
@@ -118,7 +118,7 @@ extension JavaScriptValue: AnyJavaScriptValue, AnyArgument {
118
118
 
119
119
  // MARK: - AnyJavaScriptValue
120
120
 
121
- internal static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
121
+ public static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
122
122
  // It's already a `JavaScriptValue` so it should always pass through.
123
123
  if let value = value as? Self {
124
124
  return value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -46,8 +46,8 @@
46
46
  "react-native": "*"
47
47
  },
48
48
  "devDependencies": {
49
- "@testing-library/react-native": "^13.1.0",
50
- "expo-module-scripts": "^5.0.0"
49
+ "@testing-library/react-native": "^13.2.0",
50
+ "expo-module-scripts": "^5.0.1"
51
51
  },
52
- "gitHead": "cb7062e2c17d1fb09522834aaaac0e19b766df62"
52
+ "gitHead": "2f7f90d0736af48cb542ccbc8addb836e330693a"
53
53
  }