@expo/ui 1.0.0-canary-20250306-d9d3e02 → 1.0.0-canary-20250320-7a205d3
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 +5 -0
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +4 -0
- package/android/src/main/java/expo/modules/ui/PickerView.kt +3 -0
- package/android/src/main/java/expo/modules/ui/TextInputView.kt +75 -0
- package/android/src/main/java/expo/modules/ui/menu/ContextMenu.kt +7 -3
- package/build/components/BottomSheet/index.d.ts +9 -0
- package/build/components/BottomSheet/index.d.ts.map +1 -0
- package/build/components/BottomSheet/index.ios.d.ts +11 -0
- package/build/components/BottomSheet/index.ios.d.ts.map +1 -0
- package/build/components/Button/index.d.ts +1 -1
- package/build/components/ContextMenu/index.android.d.ts +23 -0
- package/build/components/ContextMenu/index.android.d.ts.map +1 -0
- package/build/components/ContextMenu/index.d.ts +54 -20
- package/build/components/ContextMenu/index.d.ts.map +1 -1
- package/build/components/DatePicker/index.d.ts +6 -6
- package/build/components/Picker/index.d.ts +8 -5
- package/build/components/Picker/index.d.ts.map +1 -1
- package/build/components/Progress/index.d.ts +1 -1
- package/build/components/Section/index.d.ts +6 -1
- package/build/components/Section/index.d.ts.map +1 -1
- package/build/components/Slider/index.d.ts +5 -2
- package/build/components/Slider/index.d.ts.map +1 -1
- package/build/components/Switch/index.d.ts +18 -36
- package/build/components/Switch/index.d.ts.map +1 -1
- package/build/components/TextInput/index.d.ts +24 -1
- package/build/components/TextInput/index.d.ts.map +1 -1
- package/build/src/types.d.ts +2 -2
- package/build/src/types.d.ts.map +1 -1
- package/components/BottomSheet/index.ios.tsx +34 -0
- package/components/BottomSheet/index.tsx +12 -0
- package/components/Button/index.tsx +1 -1
- package/components/ContextMenu/index.android.tsx +72 -0
- package/components/ContextMenu/index.tsx +67 -33
- package/components/DatePicker/index.tsx +6 -6
- package/components/Picker/index.tsx +8 -5
- package/components/Progress/index.tsx +1 -1
- package/components/Section/index.tsx +6 -1
- package/components/Slider/index.tsx +5 -2
- package/components/Switch/index.tsx +37 -54
- package/components/TextInput/index.tsx +30 -3
- package/ios/BottomSheetView.swift +82 -0
- package/ios/ContextMenu/ContextMenu.swift +65 -2
- package/ios/ContextMenu/ContextMenuRecords.swift +6 -0
- package/ios/ExpoUIModule.swift +3 -0
- package/ios/PickerView.swift +32 -27
- package/package.json +3 -4
- package/src/types.ts +2 -2
|
@@ -3,6 +3,9 @@ import { StyleProp, ViewStyle } from 'react-native';
|
|
|
3
3
|
|
|
4
4
|
import { ViewEvent } from '../../src/types';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @hidden Not used anywhere yet.
|
|
8
|
+
*/
|
|
6
9
|
export type TextInputRole = 'default' | 'cancel' | 'destructive';
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -34,6 +37,27 @@ export type TextInputProps = {
|
|
|
34
37
|
numberOfLines?: number;
|
|
35
38
|
/**
|
|
36
39
|
* Determines which keyboard to open, e.g., numeric.
|
|
40
|
+
*
|
|
41
|
+
* Types that work on both platforms:
|
|
42
|
+
* - default
|
|
43
|
+
* - numeric
|
|
44
|
+
* - email-address
|
|
45
|
+
* - phone-pad
|
|
46
|
+
* - decimal-pad
|
|
47
|
+
* - ascii-capable
|
|
48
|
+
* - url
|
|
49
|
+
*
|
|
50
|
+
* Types that only work on Android:
|
|
51
|
+
* - password
|
|
52
|
+
* - password-numeric
|
|
53
|
+
*
|
|
54
|
+
* Types that only work on iOS:
|
|
55
|
+
* - numbers-and-punctuation
|
|
56
|
+
* - name-phone-pad
|
|
57
|
+
* - twitter
|
|
58
|
+
* - web-search
|
|
59
|
+
* - ascii-capable-number-pad
|
|
60
|
+
*
|
|
37
61
|
* @default default
|
|
38
62
|
*/
|
|
39
63
|
keyboardType?:
|
|
@@ -58,7 +82,7 @@ export type TextInputProps = {
|
|
|
58
82
|
|
|
59
83
|
export type NativeTextInputProps = Omit<TextInputProps, 'onChangeText'> & {} & ViewEvent<
|
|
60
84
|
'onValueChanged',
|
|
61
|
-
{ value: string
|
|
85
|
+
{ value: string }
|
|
62
86
|
>;
|
|
63
87
|
|
|
64
88
|
// We have to work around the `role` and `onPress` props being reserved by React Native.
|
|
@@ -67,11 +91,14 @@ const TextInputNativeView: React.ComponentType<NativeTextInputProps> = requireNa
|
|
|
67
91
|
'TextInputView'
|
|
68
92
|
);
|
|
69
93
|
|
|
94
|
+
/**
|
|
95
|
+
* @hidden
|
|
96
|
+
*/
|
|
70
97
|
function transformTextInputProps(props: TextInputProps): NativeTextInputProps {
|
|
71
98
|
return {
|
|
72
99
|
...props,
|
|
73
|
-
onValueChanged: (
|
|
74
|
-
props.onChangeText?.(
|
|
100
|
+
onValueChanged: (event) => {
|
|
101
|
+
props.onChangeText?.(event.nativeEvent.value);
|
|
75
102
|
},
|
|
76
103
|
};
|
|
77
104
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Copyright 2025-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import SwiftUI
|
|
4
|
+
import ExpoModulesCore
|
|
5
|
+
|
|
6
|
+
class BottomSheetProps: ExpoSwiftUI.ViewProps {
|
|
7
|
+
@Field var isOpened: Bool = false
|
|
8
|
+
var onIsOpenedChange = EventDispatcher()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct HeightPreferenceKey: PreferenceKey {
|
|
12
|
+
static var defaultValue: CGFloat?
|
|
13
|
+
|
|
14
|
+
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
|
|
15
|
+
guard let nextValue = nextValue() else {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
value = nextValue
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private struct ReadHeightModifier: ViewModifier {
|
|
23
|
+
private var sizeView: some View {
|
|
24
|
+
GeometryReader { geometry in
|
|
25
|
+
Color.clear.preference(key: HeightPreferenceKey.self, value: geometry.size.height)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func body(content: Content) -> some View {
|
|
30
|
+
content.background(sizeView)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
struct BottomSheetView: ExpoSwiftUI.View {
|
|
35
|
+
@EnvironmentObject var props: BottomSheetProps
|
|
36
|
+
|
|
37
|
+
@State private var isOpened = true
|
|
38
|
+
@State var height: CGFloat = 0
|
|
39
|
+
|
|
40
|
+
var body: some View {
|
|
41
|
+
if #available(iOS 16.0, tvOS 16.0, *) {
|
|
42
|
+
Rectangle().hidden()
|
|
43
|
+
.sheet(isPresented: $isOpened) {
|
|
44
|
+
Children()
|
|
45
|
+
.modifier(ReadHeightModifier())
|
|
46
|
+
.onPreferenceChange(HeightPreferenceKey.self) { height in
|
|
47
|
+
if let height {
|
|
48
|
+
self.height = height
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
.presentationDetents([.height(self.height)])
|
|
52
|
+
}
|
|
53
|
+
.onChange(of: isOpened, perform: { newIsOpened in
|
|
54
|
+
if props.isOpened == newIsOpened {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
props.onIsOpenedChange([
|
|
58
|
+
"isOpened": newIsOpened
|
|
59
|
+
])
|
|
60
|
+
})
|
|
61
|
+
.onReceive(props.objectWillChange, perform: {
|
|
62
|
+
isOpened = props.isOpened
|
|
63
|
+
})
|
|
64
|
+
} else {
|
|
65
|
+
Rectangle().hidden()
|
|
66
|
+
.sheet(isPresented: $isOpened) {
|
|
67
|
+
Children()
|
|
68
|
+
}
|
|
69
|
+
.onChange(of: isOpened, perform: { newIsOpened in
|
|
70
|
+
if props.isOpened == newIsOpened {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
props.onIsOpenedChange([
|
|
74
|
+
"isOpened": newIsOpened
|
|
75
|
+
])
|
|
76
|
+
})
|
|
77
|
+
.onReceive(props.objectWillChange, perform: {
|
|
78
|
+
isOpened = props.isOpened
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -87,14 +87,77 @@ struct LongPressContextMenu<ActivationElement: View>: View {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
struct LongPressContextMenuWithPreview<ActivationElement: View, Preview: View>: View {
|
|
91
|
+
let elements: [ContextMenuElement]?
|
|
92
|
+
let activationElement: ActivationElement
|
|
93
|
+
let preview: Preview
|
|
94
|
+
let props: ContextMenuProps?
|
|
95
|
+
|
|
96
|
+
var body: some View {
|
|
97
|
+
if #available(iOS 16.0, tvOS 16.0, *) {
|
|
98
|
+
activationElement.contextMenu(menuItems: {
|
|
99
|
+
MenuItems(fromElements: elements, props: props)
|
|
100
|
+
}, preview: {
|
|
101
|
+
preview
|
|
102
|
+
})
|
|
103
|
+
} else {
|
|
104
|
+
activationElement.contextMenu(menuItems: {
|
|
105
|
+
MenuItems(fromElements: elements, props: props)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
struct ContextMenuPreview: ExpoSwiftUI.View {
|
|
112
|
+
@EnvironmentObject var props: ContextMenuPreviewProps
|
|
113
|
+
|
|
114
|
+
var body: some View {
|
|
115
|
+
Children()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
struct ContextMenuActivationElement: ExpoSwiftUI.View {
|
|
120
|
+
@EnvironmentObject var props: ContextMenuActivationElementProps
|
|
121
|
+
|
|
122
|
+
var body: some View {
|
|
123
|
+
Children()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
90
127
|
struct ContextMenu: ExpoSwiftUI.View {
|
|
91
128
|
@EnvironmentObject var props: ContextMenuProps
|
|
92
129
|
|
|
93
130
|
var body: some View {
|
|
94
131
|
if props.activationMethod == .singlePress {
|
|
95
|
-
|
|
132
|
+
let activationElement = props.children?.filter({
|
|
133
|
+
$0.view is ExpoSwiftUI.HostingView<ContextMenuActivationElementProps, ContextMenuActivationElement>
|
|
134
|
+
})
|
|
135
|
+
SinglePressContextMenu(
|
|
136
|
+
elements: props.elements,
|
|
137
|
+
activationElement: UnwrappedChildren(children: activationElement),
|
|
138
|
+
props: props
|
|
139
|
+
)
|
|
96
140
|
} else {
|
|
97
|
-
|
|
141
|
+
let preview = props.children?.filter({
|
|
142
|
+
$0.view is ExpoSwiftUI.HostingView<ContextMenuPreviewProps, ContextMenuPreview>
|
|
143
|
+
})
|
|
144
|
+
let activationElement = props.children?.filter({
|
|
145
|
+
$0.view is ExpoSwiftUI.HostingView<ContextMenuActivationElementProps, ContextMenuActivationElement>
|
|
146
|
+
})
|
|
147
|
+
if preview?.count ?? 0 > 0 {
|
|
148
|
+
LongPressContextMenuWithPreview(
|
|
149
|
+
elements: props.elements,
|
|
150
|
+
activationElement: UnwrappedChildren(children: activationElement),
|
|
151
|
+
preview: UnwrappedChildren(children: preview),
|
|
152
|
+
props: props
|
|
153
|
+
)
|
|
154
|
+
} else {
|
|
155
|
+
LongPressContextMenu(
|
|
156
|
+
elements: props.elements,
|
|
157
|
+
activationElement: UnwrappedChildren(children: activationElement),
|
|
158
|
+
props: props
|
|
159
|
+
)
|
|
160
|
+
}
|
|
98
161
|
}
|
|
99
162
|
}
|
|
100
163
|
}
|
|
@@ -27,3 +27,9 @@ internal class ContextMenuProps: ExpoSwiftUI.ViewProps {
|
|
|
27
27
|
var onContextMenuSwitchCheckedChanged = EventDispatcher()
|
|
28
28
|
@Field var activationMethod: ActivationMethod? = .singlePress
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
internal class ContextMenuPreviewProps: ExpoSwiftUI.ViewProps {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
internal class ContextMenuActivationElementProps: ExpoSwiftUI.ViewProps {
|
|
35
|
+
}
|
package/ios/ExpoUIModule.swift
CHANGED
|
@@ -10,8 +10,11 @@ public class ExpoUIModule: Module {
|
|
|
10
10
|
View(PickerView.self)
|
|
11
11
|
View(SwitchView.self)
|
|
12
12
|
View(SectionView.self)
|
|
13
|
+
View(BottomSheetView.self)
|
|
13
14
|
View(SliderView.self)
|
|
14
15
|
View(ExpoUI.ContextMenu.self)
|
|
16
|
+
View(ExpoUI.ContextMenuActivationElement.self)
|
|
17
|
+
View(ExpoUI.ContextMenuPreview.self)
|
|
15
18
|
View(ColorPickerView.self)
|
|
16
19
|
View(DateTimePickerView.self)
|
|
17
20
|
View(TextInputView.self)
|
package/ios/PickerView.swift
CHANGED
|
@@ -13,40 +13,45 @@ class PickerProps: ExpoSwiftUI.ViewProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
struct PickerView: ExpoSwiftUI.View {
|
|
16
|
-
@State var selection: Int
|
|
16
|
+
@State var selection: Int = 0
|
|
17
17
|
@State var prevSelectedIndex: Int?
|
|
18
18
|
@EnvironmentObject var props: PickerProps
|
|
19
|
+
@EnvironmentObject var shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy
|
|
19
20
|
|
|
20
21
|
var body: some View {
|
|
21
22
|
if #available(iOS 17.0, tvOS 17.0, *) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
ExpoSwiftUI.AutoSizingStack(shadowNodeProxy: shadowNodeProxy) {
|
|
24
|
+
Picker(props.label ?? "", selection: $selection) {
|
|
25
|
+
ForEach(Array(props.options.enumerated()), id: \.0) { index, option in
|
|
26
|
+
Text(option).tag(index)
|
|
27
|
+
}
|
|
25
28
|
}
|
|
29
|
+
.tint(props.color)
|
|
30
|
+
#if !os(tvOS)
|
|
31
|
+
.if(props.variant == "wheel", { $0.pickerStyle(.wheel) })
|
|
32
|
+
.if(props.variant == "palette", { $0.pickerStyle(.palette) })
|
|
33
|
+
#endif
|
|
34
|
+
.if(props.variant == "segmented", { $0.pickerStyle(.segmented) })
|
|
35
|
+
.if(props.variant == "inline", { $0.pickerStyle(.inline) })
|
|
36
|
+
.if(props.variant == "menu", { $0.pickerStyle(.menu) })
|
|
37
|
+
.onChange(of: selection, perform: { newValue in
|
|
38
|
+
if props.selectedIndex == newValue {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
let payload = [
|
|
42
|
+
"index": newValue,
|
|
43
|
+
"label": props.options[newValue]
|
|
44
|
+
]
|
|
45
|
+
props.onOptionSelected(payload)
|
|
46
|
+
})
|
|
47
|
+
.onReceive(props.selectedIndex.publisher, perform: { newValue in
|
|
48
|
+
if prevSelectedIndex == newValue {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
selection = newValue
|
|
52
|
+
prevSelectedIndex = newValue
|
|
53
|
+
})
|
|
26
54
|
}
|
|
27
|
-
.tint(props.color)
|
|
28
|
-
#if !os(tvOS)
|
|
29
|
-
.if(props.variant == "wheel", { $0.pickerStyle(.wheel) })
|
|
30
|
-
#endif
|
|
31
|
-
.if(props.variant == "segmented", { $0.pickerStyle(.segmented) })
|
|
32
|
-
.if(props.variant == "menu", { $0.pickerStyle(.menu) })
|
|
33
|
-
.onChange(of: selection, perform: { newValue in
|
|
34
|
-
if props.selectedIndex == newValue {
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
let payload = [
|
|
38
|
-
"index": newValue ?? 0,
|
|
39
|
-
"label": props.options[newValue ?? 0]
|
|
40
|
-
]
|
|
41
|
-
props.onOptionSelected(payload)
|
|
42
|
-
})
|
|
43
|
-
.onReceive(props.selectedIndex.publisher, perform: { newValue in
|
|
44
|
-
if prevSelectedIndex == newValue {
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
selection = newValue
|
|
48
|
-
prevSelectedIndex = newValue
|
|
49
|
-
})
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/ui",
|
|
3
|
-
"version": "1.0.0-canary-
|
|
3
|
+
"version": "1.0.0-canary-20250320-7a205d3",
|
|
4
4
|
"description": "A collection of UI components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "expo-module build",
|
|
@@ -27,12 +27,11 @@
|
|
|
27
27
|
"dependencies": {},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/react": "~19.0.10",
|
|
30
|
-
"expo-module-scripts": "4.0.5-canary-
|
|
30
|
+
"expo-module-scripts": "4.0.5-canary-20250320-7a205d3"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"expo": "*",
|
|
34
34
|
"react": "*",
|
|
35
35
|
"react-native": "*"
|
|
36
|
-
}
|
|
37
|
-
"gitHead": "d9d3e024d8742099c307754673f17117a20c1dea"
|
|
36
|
+
}
|
|
38
37
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export type * from '../components/Switch';
|
|
2
|
-
export type * from '../components/Picker';
|
|
3
1
|
export type * from '../components/Button';
|
|
4
2
|
export type * from '../components/ContextMenu';
|
|
3
|
+
export type * from '../components/Picker';
|
|
5
4
|
export type * from '../components/Section';
|
|
6
5
|
export type * from '../components/Slider';
|
|
6
|
+
export type * from '../components/Switch';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @hidden
|