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 +6 -0
- package/ValueOrUndefinedSpec.swift +132 -0
- package/android/build.gradle +2 -2
- package/ios/Core/Arguments/ValueOrUndefined.swift +62 -0
- package/ios/Core/Classes/ClassDefinition.swift +1 -1
- package/ios/Core/DynamicTypes/DynamicType.swift +3 -0
- package/ios/Core/DynamicTypes/DynamicValueOrUndefinedType.swift +34 -0
- package/ios/Core/JavaScriptFunction.swift +1 -1
- package/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift +3 -1
- package/ios/Core/Views/SwiftUI/SwiftUIViewProps.swift +11 -0
- package/ios/JSI/JavaScriptValue.swift +2 -2
- package/package.json +4 -4
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
|
+
}
|
package/android/build.gradle
CHANGED
|
@@ -25,7 +25,7 @@ if (shouldIncludeCompose) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
group = 'host.exp.exponent'
|
|
28
|
-
version = '3.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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
50
|
-
"expo-module-scripts": "^5.0.
|
|
49
|
+
"@testing-library/react-native": "^13.2.0",
|
|
50
|
+
"expo-module-scripts": "^5.0.1"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "2f7f90d0736af48cb542ccbc8addb836e330693a"
|
|
53
53
|
}
|