@expo/ui 1.0.0-canary-20250305-0af9ad2 → 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 +6 -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 +25 -8
- package/build/components/Button/index.d.ts.map +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 +62 -19
- package/build/components/ContextMenu/index.d.ts.map +1 -1
- package/build/components/DatePicker/index.d.ts +8 -9
- package/build/components/DatePicker/index.d.ts.map +1 -1
- package/build/components/Picker/index.d.ts +30 -19
- 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 +18 -10
- 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 +25 -2
- package/build/components/TextInput/index.d.ts.map +1 -1
- package/build/src/types.d.ts +13 -0
- package/build/src/types.d.ts.map +1 -0
- package/components/BottomSheet/index.ios.tsx +34 -0
- package/components/BottomSheet/index.tsx +12 -0
- package/components/Button/index.tsx +26 -8
- package/components/ContextMenu/index.android.tsx +72 -0
- package/components/ContextMenu/index.tsx +73 -30
- package/components/DatePicker/index.tsx +7 -8
- package/components/Picker/index.tsx +32 -19
- package/components/Progress/index.tsx +1 -1
- package/components/Section/index.tsx +6 -1
- package/components/Slider/index.tsx +19 -10
- package/components/Switch/index.tsx +37 -54
- package/components/TextInput/index.tsx +31 -4
- 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 -6
- package/src/types.ts +16 -0
- package/tsconfig.json +1 -1
- package/build/src/index.d.ts +0 -13
- package/build/src/index.d.ts.map +0 -1
- package/src/index.ts +0 -22
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
import { Platform } from 'expo-modules-core';
|
|
3
|
-
import React from 'react';
|
|
4
3
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
5
4
|
|
|
6
|
-
import { ViewEvent } from '../../src';
|
|
5
|
+
import { ViewEvent } from '../../src/types';
|
|
7
6
|
|
|
8
7
|
type AndroidVariant = 'picker' | 'input';
|
|
9
8
|
|
|
@@ -16,12 +15,12 @@ type DisplayedComponents = 'date' | 'hourAndMinute' | 'dateAndTime';
|
|
|
16
15
|
*/
|
|
17
16
|
export type DatePickerProps = {
|
|
18
17
|
/**
|
|
19
|
-
* The
|
|
18
|
+
* The initial date to display on the picker.
|
|
20
19
|
*/
|
|
21
20
|
initialDate?: string | null;
|
|
22
21
|
/**
|
|
23
22
|
* A title displayed on the picker on iOS.
|
|
24
|
-
* @platform
|
|
23
|
+
* @platform ios
|
|
25
24
|
*/
|
|
26
25
|
title?: string;
|
|
27
26
|
/**
|
|
@@ -30,7 +29,7 @@ export type DatePickerProps = {
|
|
|
30
29
|
onDateSelected?: (date: Date) => void;
|
|
31
30
|
/**
|
|
32
31
|
* The variant of the picker, which determines its appearance and behavior.
|
|
33
|
-
* @platform
|
|
32
|
+
* @platform ios
|
|
34
33
|
* @default 'automatic'
|
|
35
34
|
*/
|
|
36
35
|
iosVariant?: IOSVariant;
|
|
@@ -43,15 +42,15 @@ export type DatePickerProps = {
|
|
|
43
42
|
/**
|
|
44
43
|
* Show to button to toggle between variants on Android.
|
|
45
44
|
* @platform android
|
|
46
|
-
* @default
|
|
45
|
+
* @default true
|
|
47
46
|
*/
|
|
48
47
|
showVariantToggle?: boolean;
|
|
49
48
|
/**
|
|
50
49
|
* The components that the picker should display.
|
|
51
|
-
* On iOS, you can have a picker that selects both date and time.
|
|
52
50
|
* On Android, you can have a picker that selects just the date or just the time.
|
|
53
51
|
* `dateAndTime` is only available on iOS and will result in a date picker on Android.
|
|
54
|
-
*
|
|
52
|
+
* On iOS, you can have a picker that selects both date and time.
|
|
53
|
+
* @default 'date'
|
|
55
54
|
*/
|
|
56
55
|
displayedComponents?: DisplayedComponents;
|
|
57
56
|
/**
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Colors for picker's core elements.
|
|
6
|
+
* @platform android
|
|
7
|
+
*/
|
|
8
|
+
export type PickerElementColors = {
|
|
9
|
+
activeBorderColor?: string;
|
|
10
|
+
activeContentColor?: string;
|
|
11
|
+
inactiveBorderColor?: string;
|
|
12
|
+
inactiveContentColor?: string;
|
|
13
|
+
disabledActiveBorderColor?: string;
|
|
14
|
+
disabledActiveContentColor?: string;
|
|
15
|
+
disabledInactiveBorderColor?: string;
|
|
16
|
+
disabledInactiveContentColor?: string;
|
|
17
|
+
activeContainerColor?: string;
|
|
18
|
+
inactiveContainerColor?: string;
|
|
19
|
+
disabledActiveContainerColor?: string;
|
|
20
|
+
disabledInactiveContainerColor?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
4
23
|
/**
|
|
5
24
|
* Props for the Picker component.
|
|
6
25
|
*/
|
|
@@ -14,8 +33,8 @@ export type PickerProps = {
|
|
|
14
33
|
*/
|
|
15
34
|
selectedIndex: number | null;
|
|
16
35
|
/**
|
|
17
|
-
* A label displayed on the picker when in `menu` variant inside a form section on iOS.
|
|
18
|
-
* @platform
|
|
36
|
+
* A label displayed on the picker when in `'menu'` variant inside a form section on iOS.
|
|
37
|
+
* @platform ios
|
|
19
38
|
*/
|
|
20
39
|
label?: string;
|
|
21
40
|
/**
|
|
@@ -24,34 +43,22 @@ export type PickerProps = {
|
|
|
24
43
|
onOptionSelected?: (event: { nativeEvent: { index: number; label: string } }) => void;
|
|
25
44
|
/**
|
|
26
45
|
* The variant of the picker, which determines its appearance and behavior.
|
|
27
|
-
* The 'wheel' and 'menu' variants are iOS only, the 'radio' variant is Android only.
|
|
46
|
+
* The `'wheel'`, `'inline'`, `'palette'` and `'menu'` variants are iOS only, the `'radio'` variant is Android only. The `'inline'` variant can only be used inside sections or lists. The `'palette'` variant displays differently inside menus.
|
|
28
47
|
* @default 'segmented'
|
|
29
48
|
*/
|
|
30
|
-
variant?: 'wheel' | 'segmented' | 'menu' | 'radio';
|
|
49
|
+
variant?: 'wheel' | 'segmented' | 'menu' | 'radio' | 'inline' | 'palette';
|
|
31
50
|
/**
|
|
32
51
|
* Optional style to apply to the picker component.
|
|
33
52
|
*/
|
|
34
53
|
style?: StyleProp<ViewStyle>;
|
|
54
|
+
|
|
35
55
|
/**
|
|
36
56
|
* Colors for picker's core elements.
|
|
37
57
|
* @platform android
|
|
38
58
|
*/
|
|
39
|
-
elementColors?:
|
|
40
|
-
activeBorderColor?: string;
|
|
41
|
-
activeContentColor?: string;
|
|
42
|
-
inactiveBorderColor?: string;
|
|
43
|
-
inactiveContentColor?: string;
|
|
44
|
-
disabledActiveBorderColor?: string;
|
|
45
|
-
disabledActiveContentColor?: string;
|
|
46
|
-
disabledInactiveBorderColor?: string;
|
|
47
|
-
disabledInactiveContentColor?: string;
|
|
48
|
-
activeContainerColor?: string;
|
|
49
|
-
inactiveContainerColor?: string;
|
|
50
|
-
disabledActiveContainerColor?: string;
|
|
51
|
-
disabledInactiveContainerColor?: string;
|
|
52
|
-
};
|
|
59
|
+
elementColors?: PickerElementColors;
|
|
53
60
|
/**
|
|
54
|
-
* Picker color. On iOS it only applies to the `menu` variant.
|
|
61
|
+
* Picker color. On iOS it only applies to the `'menu'` variant.
|
|
55
62
|
*/
|
|
56
63
|
color?: string;
|
|
57
64
|
};
|
|
@@ -63,6 +70,9 @@ const PickerNativeView: React.ComponentType<PickerProps> = requireNativeView(
|
|
|
63
70
|
|
|
64
71
|
type NativePickerProps = PickerProps;
|
|
65
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @hidden
|
|
75
|
+
*/
|
|
66
76
|
export function transformPickerProps(props: PickerProps): NativePickerProps {
|
|
67
77
|
return {
|
|
68
78
|
...props,
|
|
@@ -78,6 +88,9 @@ export function transformPickerProps(props: PickerProps): NativePickerProps {
|
|
|
78
88
|
};
|
|
79
89
|
}
|
|
80
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Displays a native picker component. Depending on the variant it can be a segmented button, an inline picker, a list of choices or a radio button.
|
|
93
|
+
*/
|
|
81
94
|
export function Picker(props: PickerProps) {
|
|
82
95
|
return <PickerNativeView {...transformPickerProps(props)} />;
|
|
83
96
|
}
|
|
@@ -16,7 +16,7 @@ export type ProgressProps = {
|
|
|
16
16
|
*/
|
|
17
17
|
style?: StyleProp<ViewStyle>;
|
|
18
18
|
/**
|
|
19
|
-
* The current progress value of the slider. This is a number between 0 and 1
|
|
19
|
+
* The current progress value of the slider. This is a number between `0` and `1`.
|
|
20
20
|
*/
|
|
21
21
|
progress?: number | null;
|
|
22
22
|
/**
|
|
@@ -2,10 +2,15 @@ import { StyleProp, ViewStyle } from 'react-native';
|
|
|
2
2
|
|
|
3
3
|
export type SectionProps = {
|
|
4
4
|
style?: StyleProp<ViewStyle>;
|
|
5
|
-
title
|
|
5
|
+
title?: string;
|
|
6
6
|
children: any;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Section component uses the native [Section](https://developer.apple.com/documentation/swiftui/section) component.
|
|
11
|
+
* It has no intrinsic dimensions, so it needs explicit height or flex set to display content (like ScrollView).
|
|
12
|
+
* @platform ios
|
|
13
|
+
*/
|
|
9
14
|
export function Section({ children }: SectionProps) {
|
|
10
15
|
return children;
|
|
11
16
|
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import { ViewEvent } from '../../src';
|
|
4
|
+
import { ViewEvent } from '../../src/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Colors for slider's core elements.
|
|
8
|
+
* @platform android
|
|
9
|
+
*/
|
|
10
|
+
export type SliderElementColors = {
|
|
11
|
+
thumbColor?: string;
|
|
12
|
+
activeTrackColor?: string;
|
|
13
|
+
inactiveTrackColor?: string;
|
|
14
|
+
activeTickColor?: string;
|
|
15
|
+
inactiveTickColor?: string;
|
|
16
|
+
};
|
|
5
17
|
|
|
6
18
|
export type SliderProps = {
|
|
7
19
|
/**
|
|
@@ -14,12 +26,12 @@ export type SliderProps = {
|
|
|
14
26
|
*/
|
|
15
27
|
value?: number;
|
|
16
28
|
/**
|
|
17
|
-
* The number of steps between the minimum and maximum values
|
|
29
|
+
* The number of steps between the minimum and maximum values, `0` signifies infinite steps.
|
|
18
30
|
* @default 0
|
|
19
31
|
*/
|
|
20
32
|
steps?: number;
|
|
21
33
|
/**
|
|
22
|
-
* The
|
|
34
|
+
* The minimum value of the slider. Updating this value does not trigger callbacks if the current value is below `min`.
|
|
23
35
|
* @default 0
|
|
24
36
|
*/
|
|
25
37
|
min?: number;
|
|
@@ -32,13 +44,7 @@ export type SliderProps = {
|
|
|
32
44
|
* Colors for slider's core elements.
|
|
33
45
|
* @platform android
|
|
34
46
|
*/
|
|
35
|
-
elementColors?:
|
|
36
|
-
thumbColor?: string;
|
|
37
|
-
activeTrackColor?: string;
|
|
38
|
-
inactiveTrackColor?: string;
|
|
39
|
-
activeTickColor?: string;
|
|
40
|
-
inactiveTickColor?: string;
|
|
41
|
-
};
|
|
47
|
+
elementColors?: SliderElementColors;
|
|
42
48
|
/**
|
|
43
49
|
* Slider color.
|
|
44
50
|
*/
|
|
@@ -57,6 +63,9 @@ const SliderNativeView: React.ComponentType<NativeSliderProps> = requireNativeVi
|
|
|
57
63
|
'SliderView'
|
|
58
64
|
);
|
|
59
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @hidden
|
|
68
|
+
*/
|
|
60
69
|
export function transformSliderProps(props: SliderProps): NativeSliderProps {
|
|
61
70
|
return {
|
|
62
71
|
...props,
|
|
@@ -1,49 +1,27 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
import { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
+
// @docsMissing
|
|
5
|
+
/**
|
|
6
|
+
* Only for switch.
|
|
7
|
+
*/
|
|
4
8
|
type SwitchElementColors = {
|
|
5
|
-
/**
|
|
6
|
-
* Only for switch.
|
|
7
|
-
*/
|
|
8
9
|
checkedThumbColor?: string;
|
|
9
|
-
/**
|
|
10
|
-
* Only for switch.
|
|
11
|
-
*/
|
|
12
10
|
checkedTrackColor?: string;
|
|
13
|
-
/**
|
|
14
|
-
* Only for switch.
|
|
15
|
-
*/
|
|
16
11
|
uncheckedThumbColor?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Only for switch.
|
|
19
|
-
*/
|
|
20
12
|
uncheckedTrackColor?: string;
|
|
21
13
|
};
|
|
22
14
|
|
|
15
|
+
// @docsMissing
|
|
16
|
+
/**
|
|
17
|
+
* Only for checkbox.
|
|
18
|
+
*/
|
|
23
19
|
type CheckboxElementColors = {
|
|
24
|
-
/**
|
|
25
|
-
* Only for checkbox.
|
|
26
|
-
*/
|
|
27
20
|
checkedColor?: string;
|
|
28
|
-
/**
|
|
29
|
-
* Only for checkbox.
|
|
30
|
-
*/
|
|
31
21
|
disabledCheckedColor?: string;
|
|
32
|
-
/**
|
|
33
|
-
* Only for checkbox.
|
|
34
|
-
*/
|
|
35
22
|
uncheckedColor?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Only for checkbox.
|
|
38
|
-
*/
|
|
39
23
|
disabledUncheckedColor?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Only for checkbox.
|
|
42
|
-
*/
|
|
43
24
|
checkmarkColor?: string;
|
|
44
|
-
/**
|
|
45
|
-
* Only for checkbox.
|
|
46
|
-
*/
|
|
47
25
|
disabledIndeterminateColor?: string;
|
|
48
26
|
};
|
|
49
27
|
|
|
@@ -61,7 +39,7 @@ export type SwitchProps = {
|
|
|
61
39
|
label?: string;
|
|
62
40
|
|
|
63
41
|
/**
|
|
64
|
-
* Type of the switch component. Can be 'checkbox'
|
|
42
|
+
* Type of the switch component. Can be `'checkbox'`, `'switch'`, or `'button'`. The `'button'` style is iOS only.
|
|
65
43
|
* @default 'switch'
|
|
66
44
|
*/
|
|
67
45
|
variant?: 'checkbox' | 'switch' | 'button';
|
|
@@ -74,31 +52,33 @@ export type SwitchProps = {
|
|
|
74
52
|
*/
|
|
75
53
|
style?: StyleProp<ViewStyle>;
|
|
76
54
|
/**
|
|
77
|
-
* Picker color. On iOS it only applies to the `menu` variant.
|
|
55
|
+
* Picker color. On iOS, it only applies to the `menu` variant.
|
|
78
56
|
*/
|
|
79
57
|
color?: string;
|
|
80
|
-
} & (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
58
|
+
} & (SwitchSwitchVariantProps | SwitchCheckboxVariantProps | SwitchButtonVariantProps);
|
|
59
|
+
|
|
60
|
+
export type SwitchSwitchVariantProps = {
|
|
61
|
+
variant?: 'switch';
|
|
62
|
+
/**
|
|
63
|
+
* Colors for switch's core elements.
|
|
64
|
+
* @platform android
|
|
65
|
+
*/
|
|
66
|
+
elementColors?: SwitchElementColors;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type SwitchCheckboxVariantProps = {
|
|
70
|
+
variant: 'checkbox';
|
|
71
|
+
/**
|
|
72
|
+
* Colors for checkbox core elements.
|
|
73
|
+
* @platform android
|
|
74
|
+
*/
|
|
75
|
+
elementColors?: CheckboxElementColors;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type SwitchButtonVariantProps = {
|
|
79
|
+
variant: 'button';
|
|
80
|
+
elementColors?: undefined;
|
|
81
|
+
};
|
|
102
82
|
|
|
103
83
|
type NativeSwitchProps = Omit<SwitchProps, 'onValueChange'> & {
|
|
104
84
|
onValueChange: (event: NativeSyntheticEvent<{ value: boolean }>) => void;
|
|
@@ -127,6 +107,9 @@ function getElementColors(props: SwitchProps) {
|
|
|
127
107
|
return props.elementColors;
|
|
128
108
|
}
|
|
129
109
|
|
|
110
|
+
/**
|
|
111
|
+
* @hidden
|
|
112
|
+
*/
|
|
130
113
|
export function transformSwitchProps(props: SwitchProps): NativeSwitchProps {
|
|
131
114
|
return {
|
|
132
115
|
...props,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { requireNativeView } from 'expo';
|
|
2
2
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import { ViewEvent } from '../../src';
|
|
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
|
}
|