@expo/ui 56.0.7 → 56.0.9

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.
Files changed (208) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +44 -3
  4. package/android/src/main/java/expo/modules/ui/HostView.kt +2 -0
  5. package/android/src/main/java/expo/modules/ui/LoadingView.kt +80 -0
  6. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +50 -4
  7. package/android/src/main/java/expo/modules/ui/RNHostView.kt +8 -3
  8. package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +28 -0
  9. package/android/src/main/java/expo/modules/ui/SnackbarView.kt +126 -0
  10. package/android/src/main/java/expo/modules/ui/state/ObservableState.kt +10 -0
  11. package/assets/keyboard_arrow_down.xml +10 -0
  12. package/build/State/useNativeState.d.ts +32 -3
  13. package/build/State/useNativeState.d.ts.map +1 -1
  14. package/build/community/bottom-sheet/BottomSheet.ios.d.ts.map +1 -1
  15. package/build/community/menu/MenuView.android.d.ts +16 -0
  16. package/build/community/menu/MenuView.android.d.ts.map +1 -0
  17. package/build/community/menu/MenuView.d.ts +19 -0
  18. package/build/community/menu/MenuView.d.ts.map +1 -0
  19. package/build/community/menu/MenuView.ios.d.ts +10 -0
  20. package/build/community/menu/MenuView.ios.d.ts.map +1 -0
  21. package/build/community/menu/index.d.ts +5 -0
  22. package/build/community/menu/index.d.ts.map +1 -0
  23. package/build/community/menu/types.d.ts +166 -0
  24. package/build/community/menu/types.d.ts.map +1 -0
  25. package/build/jetpack-compose/LoadingIndicator/index.d.ts +41 -0
  26. package/build/jetpack-compose/LoadingIndicator/index.d.ts.map +1 -0
  27. package/build/jetpack-compose/Snackbar/index.d.ts +94 -0
  28. package/build/jetpack-compose/Snackbar/index.d.ts.map +1 -0
  29. package/build/jetpack-compose/index.d.ts +2 -0
  30. package/build/jetpack-compose/index.d.ts.map +1 -1
  31. package/build/jetpack-compose/modifiers/index.d.ts +21 -2
  32. package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
  33. package/build/swift-ui/Alert/index.d.ts +42 -0
  34. package/build/swift-ui/Alert/index.d.ts.map +1 -0
  35. package/build/swift-ui/BottomSheet/index.d.ts +5 -1
  36. package/build/swift-ui/BottomSheet/index.d.ts.map +1 -1
  37. package/build/swift-ui/SlotView.d.ts +5 -2
  38. package/build/swift-ui/SlotView.d.ts.map +1 -1
  39. package/build/swift-ui/SwipeActions/index.d.ts +38 -0
  40. package/build/swift-ui/SwipeActions/index.d.ts.map +1 -0
  41. package/build/swift-ui/index.d.ts +3 -0
  42. package/build/swift-ui/index.d.ts.map +1 -1
  43. package/build/swift-ui/modifiers/index.d.ts +3 -1
  44. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  45. package/build/swift-ui/modifiers/symbolEffect.d.ts +103 -0
  46. package/build/swift-ui/modifiers/symbolEffect.d.ts.map +1 -0
  47. package/build/swift-ui/withAnimation.d.ts +26 -0
  48. package/build/swift-ui/withAnimation.d.ts.map +1 -0
  49. package/build/universal/BottomSheet/index.android.d.ts +1 -1
  50. package/build/universal/BottomSheet/index.android.d.ts.map +1 -1
  51. package/build/universal/BottomSheet/index.d.ts +1 -1
  52. package/build/universal/BottomSheet/index.d.ts.map +1 -1
  53. package/build/universal/BottomSheet/index.ios.d.ts +1 -1
  54. package/build/universal/BottomSheet/index.ios.d.ts.map +1 -1
  55. package/build/universal/BottomSheet/types.d.ts +27 -0
  56. package/build/universal/BottomSheet/types.d.ts.map +1 -1
  57. package/build/universal/Collapsible/index.android.d.ts +8 -0
  58. package/build/universal/Collapsible/index.android.d.ts.map +1 -0
  59. package/build/universal/Collapsible/index.d.ts +8 -0
  60. package/build/universal/Collapsible/index.d.ts.map +1 -0
  61. package/build/universal/Collapsible/index.ios.d.ts +7 -0
  62. package/build/universal/Collapsible/index.ios.d.ts.map +1 -0
  63. package/build/universal/Collapsible/types.d.ts +23 -0
  64. package/build/universal/Collapsible/types.d.ts.map +1 -0
  65. package/build/universal/Column/index.d.ts.map +1 -1
  66. package/build/universal/Host/index.d.ts +5 -7
  67. package/build/universal/Host/index.d.ts.map +1 -1
  68. package/build/universal/Host/types.d.ts +72 -0
  69. package/build/universal/Host/types.d.ts.map +1 -0
  70. package/build/universal/List/index.android.d.ts +9 -0
  71. package/build/universal/List/index.android.d.ts.map +1 -0
  72. package/build/universal/List/index.d.ts +8 -0
  73. package/build/universal/List/index.d.ts.map +1 -0
  74. package/build/universal/List/index.ios.d.ts +8 -0
  75. package/build/universal/List/index.ios.d.ts.map +1 -0
  76. package/build/universal/List/types.d.ts +26 -0
  77. package/build/universal/List/types.d.ts.map +1 -0
  78. package/build/universal/ListItem/ListItem.android.d.ts +8 -0
  79. package/build/universal/ListItem/ListItem.android.d.ts.map +1 -0
  80. package/build/universal/ListItem/ListItem.d.ts +9 -0
  81. package/build/universal/ListItem/ListItem.d.ts.map +1 -0
  82. package/build/universal/ListItem/ListItem.ios.d.ts +8 -0
  83. package/build/universal/ListItem/ListItem.ios.d.ts.map +1 -0
  84. package/build/universal/ListItem/ListItemSlots.d.ts +21 -0
  85. package/build/universal/ListItem/ListItemSlots.d.ts.map +1 -0
  86. package/build/universal/ListItem/index.d.ts +10 -0
  87. package/build/universal/ListItem/index.d.ts.map +1 -0
  88. package/build/universal/ListItem/types.d.ts +59 -0
  89. package/build/universal/ListItem/types.d.ts.map +1 -0
  90. package/build/universal/Picker/Picker.android.d.ts +9 -0
  91. package/build/universal/Picker/Picker.android.d.ts.map +1 -0
  92. package/build/universal/Picker/Picker.d.ts +8 -0
  93. package/build/universal/Picker/Picker.d.ts.map +1 -0
  94. package/build/universal/Picker/Picker.ios.d.ts +9 -0
  95. package/build/universal/Picker/Picker.ios.d.ts.map +1 -0
  96. package/build/universal/Picker/PickerItem.d.ts +9 -0
  97. package/build/universal/Picker/PickerItem.d.ts.map +1 -0
  98. package/build/universal/Picker/index.d.ts +8 -0
  99. package/build/universal/Picker/index.d.ts.map +1 -0
  100. package/build/universal/Picker/types.d.ts +69 -0
  101. package/build/universal/Picker/types.d.ts.map +1 -0
  102. package/build/universal/index.d.ts +4 -0
  103. package/build/universal/index.d.ts.map +1 -1
  104. package/expo-module.config.json +1 -1
  105. package/ios/Alert/Alert.swift +56 -0
  106. package/ios/Alert/AlertProps.swift +8 -0
  107. package/ios/BottomSheetView.swift +4 -1
  108. package/ios/ExpoUIModule.swift +43 -1
  109. package/ios/ExpoUITouchHandlerHelper.h +4 -1
  110. package/ios/ExpoUITouchHandlerHelper.mm +1 -0
  111. package/ios/Modifiers/AnimationConfig.swift +109 -0
  112. package/ios/Modifiers/SwipeActionsModifier.swift +97 -0
  113. package/ios/Modifiers/SymbolEffectModifier.swift +452 -0
  114. package/ios/Modifiers/ViewModifierRegistry.swift +5 -112
  115. package/ios/SlotView.swift +5 -0
  116. package/ios/State/ObservableState.swift +12 -1
  117. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7-sources.jar → 56.0.9/expo.modules.ui-56.0.9-sources.jar} +0 -0
  118. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.md5 +1 -0
  119. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha1 +1 -0
  120. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha256 +1 -0
  121. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha512 +1 -0
  122. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar +0 -0
  123. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.md5 +1 -0
  124. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha1 +1 -0
  125. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha256 +1 -0
  126. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha512 +1 -0
  127. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7.module → 56.0.9/expo.modules.ui-56.0.9.module} +22 -22
  128. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.md5 +1 -0
  129. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha1 +1 -0
  130. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha256 +1 -0
  131. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha512 +1 -0
  132. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7.pom → 56.0.9/expo.modules.ui-56.0.9.pom} +1 -1
  133. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.md5 +1 -0
  134. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha1 +1 -0
  135. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha256 +1 -0
  136. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha512 +1 -0
  137. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  138. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  139. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  140. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  141. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  142. package/package.json +7 -3
  143. package/src/State/useNativeState.ts +70 -10
  144. package/src/community/bottom-sheet/BottomSheet.ios.tsx +0 -17
  145. package/src/community/menu/MenuView.android.tsx +224 -0
  146. package/src/community/menu/MenuView.ios.tsx +149 -0
  147. package/src/community/menu/MenuView.tsx +36 -0
  148. package/src/community/menu/index.tsx +14 -0
  149. package/src/community/menu/types.tsx +171 -0
  150. package/src/jetpack-compose/LoadingIndicator/index.tsx +92 -0
  151. package/src/jetpack-compose/Snackbar/index.tsx +135 -0
  152. package/src/jetpack-compose/index.ts +2 -0
  153. package/src/jetpack-compose/modifiers/index.ts +30 -2
  154. package/src/swift-ui/Alert/index.tsx +87 -0
  155. package/src/swift-ui/BottomSheet/index.tsx +32 -15
  156. package/src/swift-ui/SlotView.tsx +17 -4
  157. package/src/swift-ui/SwipeActions/index.tsx +73 -0
  158. package/src/swift-ui/index.tsx +3 -0
  159. package/src/swift-ui/modifiers/index.ts +3 -0
  160. package/src/swift-ui/modifiers/symbolEffect.ts +181 -0
  161. package/src/swift-ui/withAnimation.ts +71 -0
  162. package/src/ts-declarations/react-native-web.d.ts +27 -0
  163. package/src/universal/BottomSheet/index.android.tsx +27 -3
  164. package/src/universal/BottomSheet/index.ios.tsx +30 -12
  165. package/src/universal/BottomSheet/index.tsx +46 -4
  166. package/src/universal/BottomSheet/types.ts +25 -0
  167. package/src/universal/Collapsible/index.android.tsx +72 -0
  168. package/src/universal/Collapsible/index.ios.tsx +16 -0
  169. package/src/universal/Collapsible/index.tsx +58 -0
  170. package/src/universal/Collapsible/types.ts +25 -0
  171. package/src/universal/Column/index.tsx +3 -1
  172. package/src/universal/Host/index.tsx +69 -5
  173. package/src/universal/Host/types.ts +70 -0
  174. package/src/universal/List/index.android.tsx +44 -0
  175. package/src/universal/List/index.ios.tsx +19 -0
  176. package/src/universal/List/index.tsx +26 -0
  177. package/src/universal/List/types.ts +28 -0
  178. package/src/universal/ListItem/ListItem.android.tsx +52 -0
  179. package/src/universal/ListItem/ListItem.ios.tsx +58 -0
  180. package/src/universal/ListItem/ListItem.tsx +72 -0
  181. package/src/universal/ListItem/ListItemSlots.tsx +66 -0
  182. package/src/universal/ListItem/index.ts +15 -0
  183. package/src/universal/ListItem/types.ts +67 -0
  184. package/src/universal/Picker/Picker.android.tsx +69 -0
  185. package/src/universal/Picker/Picker.ios.tsx +45 -0
  186. package/src/universal/Picker/Picker.tsx +52 -0
  187. package/src/universal/Picker/PickerItem.tsx +27 -0
  188. package/src/universal/Picker/index.ts +11 -0
  189. package/src/universal/Picker/types.ts +79 -0
  190. package/src/universal/index.ts +4 -0
  191. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.md5 +0 -1
  192. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha1 +0 -1
  193. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha256 +0 -1
  194. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha512 +0 -1
  195. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar +0 -0
  196. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.md5 +0 -1
  197. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha1 +0 -1
  198. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha256 +0 -1
  199. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha512 +0 -1
  200. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.md5 +0 -1
  201. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha1 +0 -1
  202. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha256 +0 -1
  203. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha512 +0 -1
  204. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.md5 +0 -1
  205. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha1 +0 -1
  206. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha256 +0 -1
  207. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha512 +0 -1
  208. package/src/community/bottom-sheet/CLAUDE.md +0 -55
@@ -0,0 +1,87 @@
1
+ import { requireNativeView } from 'expo';
2
+ import type { NativeSyntheticEvent } from 'react-native';
3
+
4
+ import { Slot } from '../SlotView';
5
+ import { createViewModifierEventListener } from '../modifiers/utils';
6
+ import { type CommonViewModifierProps } from '../types';
7
+
8
+ /**
9
+ * Props of the `Alert` component.
10
+ */
11
+ export type AlertProps = {
12
+ /**
13
+ * The contents of the alert.
14
+ * Should include `Alert.Trigger`, `Alert.Actions`, and optionally `Alert.Message`.
15
+ */
16
+ children: React.ReactNode;
17
+ /**
18
+ * The title of the alert.
19
+ */
20
+ title: string;
21
+ /**
22
+ * Whether the alert is presented.
23
+ */
24
+ isPresented?: boolean;
25
+ /**
26
+ * A callback that is called when the `isPresented` state changes.
27
+ */
28
+ onIsPresentedChange?: (isPresented: boolean) => void;
29
+ } & CommonViewModifierProps;
30
+
31
+ type NativeAlertProps = Omit<AlertProps, 'onIsPresentedChange'> & {
32
+ onIsPresentedChange?: (event: NativeSyntheticEvent<{ isPresented: boolean }>) => void;
33
+ };
34
+
35
+ const AlertNativeView: React.ComponentType<NativeAlertProps> = requireNativeView(
36
+ 'ExpoUI',
37
+ 'AlertView'
38
+ );
39
+
40
+ /**
41
+ * The component visible all the time that triggers the alert presentation.
42
+ */
43
+ function Trigger(props: { children: React.ReactNode }) {
44
+ return <Slot name="trigger">{props.children}</Slot>;
45
+ }
46
+
47
+ /**
48
+ * The action buttons displayed in the alert. Use `Button` components from `@expo/ui/swift-ui` as children.
49
+ */
50
+ function Actions(props: { children: React.ReactNode }) {
51
+ return <Slot name="actions">{props.children}</Slot>;
52
+ }
53
+
54
+ /**
55
+ * An optional message displayed below the title in the alert.
56
+ */
57
+ function Message(props: { children: React.ReactNode }) {
58
+ return <Slot name="message">{props.children}</Slot>;
59
+ }
60
+
61
+ /**
62
+ * `Alert` presents a SwiftUI alert with a title, optional message, and action buttons.
63
+ *
64
+ * @see Official [SwiftUI documentation](https://developer.apple.com/documentation/swiftui/view/alert(_:ispresented:actions:message:)).
65
+ */
66
+ function Alert(props: AlertProps) {
67
+ const { onIsPresentedChange, modifiers, children, ...restProps } = props;
68
+
69
+ const handleIsPresentedChange = (event: NativeSyntheticEvent<{ isPresented: boolean }>) => {
70
+ onIsPresentedChange?.(event.nativeEvent.isPresented);
71
+ };
72
+
73
+ return (
74
+ <AlertNativeView
75
+ {...(modifiers ? createViewModifierEventListener(modifiers) : undefined)}
76
+ {...restProps}
77
+ onIsPresentedChange={handleIsPresentedChange}>
78
+ {children}
79
+ </AlertNativeView>
80
+ );
81
+ }
82
+
83
+ Alert.Trigger = Trigger;
84
+ Alert.Actions = Actions;
85
+ Alert.Message = Message;
86
+
87
+ export { Alert };
@@ -1,5 +1,5 @@
1
1
  import { requireNativeView } from 'expo';
2
- import type { ComponentType } from 'react';
2
+ import { useState, type ComponentType } from 'react';
3
3
  import type { NativeSyntheticEvent } from 'react-native';
4
4
 
5
5
  import { createViewModifierEventListener } from '../modifiers/utils';
@@ -21,6 +21,10 @@ export type BottomSheetProps = {
21
21
  * Callback function that is called when the `BottomSheet` presented state changes.
22
22
  */
23
23
  onIsPresentedChange: (isPresented: boolean) => void;
24
+ /**
25
+ * Callback function that is called after the `BottomSheet` has been fully dismissed.
26
+ */
27
+ onDismiss?: () => void;
24
28
  /**
25
29
  * When `true`, the sheet will automatically size itself to fit its content.
26
30
  * This sets the presentation detent to match the height of the children.
@@ -29,8 +33,9 @@ export type BottomSheetProps = {
29
33
  fitToContents?: boolean;
30
34
  } & CommonViewModifierProps;
31
35
 
32
- type NativeBottomSheetProps = Omit<BottomSheetProps, 'onIsPresentedChange'> & {
36
+ type NativeBottomSheetProps = Omit<BottomSheetProps, 'onIsPresentedChange' | 'onDismiss'> & {
33
37
  onIsPresentedChange: (event: NativeSyntheticEvent<{ isPresented: boolean }>) => void;
38
+ onDismiss: (event: NativeSyntheticEvent<object>) => void;
34
39
  };
35
40
 
36
41
  const BottomSheetNativeView: ComponentType<NativeBottomSheetProps> = requireNativeView(
@@ -38,23 +43,35 @@ const BottomSheetNativeView: ComponentType<NativeBottomSheetProps> = requireNati
38
43
  'BottomSheetView'
39
44
  );
40
45
 
41
- function transformBottomSheetProps(props: BottomSheetProps): NativeBottomSheetProps {
42
- const { modifiers, ...restProps } = props;
43
- return {
44
- modifiers,
45
- ...(modifiers ? createViewModifierEventListener(modifiers) : undefined),
46
- ...restProps,
47
- onIsPresentedChange: ({ nativeEvent: { isPresented } }) => {
48
- props?.onIsPresentedChange?.(isPresented);
49
- },
50
- };
51
- }
52
-
53
46
  /**
54
47
  * `BottomSheet` presents content from the bottom of the screen.
55
48
  */
56
49
  function BottomSheet(props: BottomSheetProps) {
57
- return <BottomSheetNativeView {...transformBottomSheetProps(props)} />;
50
+ const { modifiers, onIsPresentedChange, onDismiss, ...restProps } = props;
51
+ const [isMounted, setIsMounted] = useState(props.isPresented);
52
+
53
+ if (props.isPresented && !isMounted) {
54
+ setIsMounted(true);
55
+ }
56
+
57
+ if (!isMounted) {
58
+ return null;
59
+ }
60
+
61
+ return (
62
+ <BottomSheetNativeView
63
+ modifiers={modifiers}
64
+ {...(modifiers ? createViewModifierEventListener(modifiers) : undefined)}
65
+ {...restProps}
66
+ onIsPresentedChange={({ nativeEvent: { isPresented } }) => {
67
+ onIsPresentedChange?.(isPresented);
68
+ }}
69
+ onDismiss={() => {
70
+ setIsMounted(false);
71
+ onDismiss?.();
72
+ }}
73
+ />
74
+ );
58
75
  }
59
76
 
60
77
  export { BottomSheet };
@@ -1,8 +1,21 @@
1
1
  import { requireNativeView } from 'expo';
2
2
 
3
- const SlotNativeView: React.ComponentType<{ name: string; children?: React.ReactNode }> =
4
- requireNativeView('ExpoUI', 'SlotView');
3
+ type SlotProps<ExtraProps extends Record<string, unknown> = Record<string, unknown>> = {
4
+ name: string;
5
+ extraProps?: ExtraProps;
6
+ children?: React.ReactNode;
7
+ };
5
8
 
6
- export function Slot({ name, children }: { name: string; children?: React.ReactNode }) {
7
- return <SlotNativeView name={name}>{children}</SlotNativeView>;
9
+ const SlotNativeView: React.ComponentType<SlotProps> = requireNativeView('ExpoUI', 'SlotView');
10
+
11
+ export function Slot<ExtraProps extends Record<string, unknown> = Record<string, unknown>>({
12
+ name,
13
+ extraProps,
14
+ children,
15
+ }: SlotProps<ExtraProps>) {
16
+ return (
17
+ <SlotNativeView name={name} extraProps={extraProps}>
18
+ {children}
19
+ </SlotNativeView>
20
+ );
8
21
  }
@@ -0,0 +1,73 @@
1
+ import { requireNativeView } from 'expo';
2
+
3
+ import { Slot } from '../SlotView';
4
+ import { createViewModifierEventListener } from '../modifiers/utils';
5
+ import { type CommonViewModifierProps } from '../types';
6
+
7
+ export type SwipeActionsEdge = 'leading' | 'trailing';
8
+
9
+ export type SwipeActionsProps = {
10
+ /**
11
+ * The regular content and `SwipeActions.Actions` action groups.
12
+ */
13
+ children: React.ReactNode;
14
+ } & CommonViewModifierProps;
15
+
16
+ export type SwipeActionsGroupProps = {
17
+ /**
18
+ * The edge where these swipe actions are revealed.
19
+ * @default 'trailing'
20
+ */
21
+ edge?: SwipeActionsEdge;
22
+ /**
23
+ * Whether a full swipe automatically performs the first action in this group.
24
+ * @default true
25
+ */
26
+ allowsFullSwipe?: boolean;
27
+ /**
28
+ * The buttons revealed when the user swipes from this edge.
29
+ */
30
+ children: React.ReactNode;
31
+ };
32
+
33
+ type SwipeActionsNativeProps = SwipeActionsProps;
34
+
35
+ const SwipeActionsNativeView: React.ComponentType<SwipeActionsNativeProps> = requireNativeView(
36
+ 'ExpoUI',
37
+ 'SwipeActionsView'
38
+ );
39
+
40
+ /**
41
+ * The buttons revealed when the user swipes the regular content from an edge.
42
+ */
43
+ export function Actions({
44
+ edge = 'trailing',
45
+ allowsFullSwipe = true,
46
+ children,
47
+ }: SwipeActionsGroupProps) {
48
+ return (
49
+ <Slot name="actions" extraProps={{ edge, allowsFullSwipe }}>
50
+ {children}
51
+ </Slot>
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Applies native SwiftUI swipe actions to its non-slot children.
57
+ * @platform ios
58
+ */
59
+ function SwipeActionsComponent(props: SwipeActionsProps) {
60
+ const { modifiers, ...restProps } = props;
61
+
62
+ return (
63
+ <SwipeActionsNativeView
64
+ modifiers={modifiers}
65
+ {...(modifiers ? createViewModifierEventListener(modifiers) : undefined)}
66
+ {...restProps}
67
+ />
68
+ );
69
+ }
70
+
71
+ const SwipeActions = Object.assign(SwipeActionsComponent, { Actions });
72
+
73
+ export { SwipeActions };
@@ -1,6 +1,7 @@
1
1
  import '../State/index.fx';
2
2
 
3
3
  export * from './AccessoryWidgetBackground';
4
+ export * from './Alert';
4
5
  export * from './BottomSheet';
5
6
  export * from './Button';
6
7
  export * from './Chart';
@@ -33,8 +34,10 @@ export * from './ShareLink';
33
34
  export * from './Slider';
34
35
  export * from './Spacer';
35
36
  export * from './Stepper';
37
+ export * from './SwipeActions';
36
38
  export * from './Text';
37
39
  export { useNativeState } from '../State/useNativeState';
40
+ export { withAnimation, type WithAnimationCompletionCriteria } from './withAnimation';
38
41
  export * from './SyncToggle';
39
42
  export * from './TabView';
40
43
  export * from './Toggle';
@@ -20,6 +20,7 @@ import { environment } from './environment';
20
20
  import { gaugeStyle } from './gaugeStyle';
21
21
  import { progressViewStyle } from './progressViewStyle';
22
22
  import { id, scrollPosition } from './scrollPosition';
23
+ import { symbolEffect } from './symbolEffect';
23
24
  import type { Color } from './types';
24
25
  import { widgetAccentedRenderingMode, widgetURL } from './widgets';
25
26
 
@@ -1376,6 +1377,7 @@ export type BuiltInModifier =
1376
1377
  | ReturnType<typeof listStyle>
1377
1378
  | ReturnType<typeof contentTransition>
1378
1379
  | ReturnType<typeof resizable>
1380
+ | ReturnType<typeof symbolEffect>
1379
1381
  | ReturnType<typeof widgetAccentedRenderingMode>
1380
1382
  | ReturnType<typeof widgetURL>
1381
1383
  | ReturnType<typeof containerBackground>;
@@ -1426,6 +1428,7 @@ export * from './gaugeStyle';
1426
1428
  export * from './presentationModifiers';
1427
1429
  export * from './environment';
1428
1430
  export * from './scrollPosition';
1431
+ export * from './symbolEffect';
1429
1432
  export * from './widgets';
1430
1433
  export type {
1431
1434
  TimingAnimationParams,
@@ -0,0 +1,181 @@
1
+ import { createModifier } from './createModifier';
2
+ import { type ObservableState } from '../../State/useNativeState';
3
+ import { getStateId } from '../../State/utils';
4
+
5
+ // https://developer.apple.com/documentation/symbols/appearsymboleffect
6
+ type AppearSymbolEffect = {
7
+ effect: 'appear';
8
+ scale?: 'down' | 'up';
9
+ scope?: 'byLayer' | 'wholeSymbol';
10
+ };
11
+
12
+ // https://developer.apple.com/documentation/symbols/bouncesymboleffect
13
+ type BounceSymbolEffect = {
14
+ effect: 'bounce';
15
+ direction?: 'down' | 'up';
16
+ scope?: 'byLayer' | 'wholeSymbol';
17
+ };
18
+
19
+ // https://developer.apple.com/documentation/symbols/breathesymboleffect
20
+ type BreatheSymbolEffect = {
21
+ effect: 'breathe';
22
+ style?: 'plain' | 'pulse';
23
+ scope?: 'byLayer' | 'wholeSymbol';
24
+ };
25
+
26
+ // https://developer.apple.com/documentation/symbols/disappearsymboleffect
27
+ type DisappearSymbolEffect = {
28
+ effect: 'disappear';
29
+ scale?: 'down' | 'up';
30
+ scope?: 'byLayer' | 'wholeSymbol';
31
+ };
32
+
33
+ // https://developer.apple.com/documentation/symbols/drawoffsymboleffect
34
+ type DrawOffSymbolEffect = {
35
+ effect: 'drawOff';
36
+ playbackStyle?: 'nonReversed' | 'reversed';
37
+ scope?: 'byLayer' | 'individually' | 'wholeSymbol';
38
+ };
39
+
40
+ // https://developer.apple.com/documentation/symbols/drawonsymboleffect
41
+ type DrawOnSymbolEffect = {
42
+ effect: 'drawOn';
43
+ scope?: 'byLayer' | 'individually' | 'wholeSymbol';
44
+ };
45
+
46
+ // https://developer.apple.com/documentation/symbols/pulsesymboleffect
47
+ type PulseSymbolEffect = {
48
+ effect: 'pulse';
49
+ scope?: 'byLayer' | 'wholeSymbol';
50
+ };
51
+
52
+ // https://developer.apple.com/documentation/symbols/rotatesymboleffect
53
+ type RotateSymbolEffect = {
54
+ effect: 'rotate';
55
+ direction?: 'clockwise' | 'counterClockwise';
56
+ scope?: 'byLayer' | 'wholeSymbol';
57
+ };
58
+
59
+ // https://developer.apple.com/documentation/symbols/scalesymboleffect
60
+ type ScaleSymbolEffect = {
61
+ effect: 'scale';
62
+ scale?: 'down' | 'up';
63
+ scope?: 'byLayer' | 'wholeSymbol';
64
+ };
65
+
66
+ // https://developer.apple.com/documentation/symbols/variablecolorsymboleffect
67
+ type VariableColorSymbolEffect = {
68
+ effect: 'variableColor';
69
+ fillStyle?: 'cumulative' | 'iterative';
70
+ playbackStyle?: 'nonReversing' | 'reversing';
71
+ inactiveLayers?: 'dim' | 'hide';
72
+ };
73
+
74
+ // https://developer.apple.com/documentation/symbols/wigglesymboleffect
75
+ type WiggleSymbolEffect = {
76
+ effect: 'wiggle';
77
+ direction?:
78
+ | 'backward'
79
+ | 'clockwise'
80
+ | 'counterClockwise'
81
+ | 'down'
82
+ | 'forward'
83
+ | 'left'
84
+ | 'right'
85
+ | 'up';
86
+ // Custom wiggle angle in degrees. Takes precedence over `direction` when set.
87
+ customAngle?: number;
88
+ scope?: 'byLayer' | 'wholeSymbol';
89
+ };
90
+
91
+ export type SymbolEffect =
92
+ | AppearSymbolEffect
93
+ | BounceSymbolEffect
94
+ | BreatheSymbolEffect
95
+ | DisappearSymbolEffect
96
+ | DrawOffSymbolEffect
97
+ | DrawOnSymbolEffect
98
+ | PulseSymbolEffect
99
+ | RotateSymbolEffect
100
+ | ScaleSymbolEffect
101
+ | VariableColorSymbolEffect
102
+ | WiggleSymbolEffect;
103
+
104
+ /**
105
+ * Animation options for a symbol effect.
106
+ *
107
+ * @see Official [Apple documentation](https://developer.apple.com/documentation/symbols/symboleffectoptions).
108
+ */
109
+ export type SymbolEffectOptions = {
110
+ /**
111
+ * How the effect repeats. Omit for the effect's natural cadence.
112
+ * - `'nonRepeating'` — play exactly once.
113
+ * - `'continuous'` — smooth, indefinite repetition (iOS 18+).
114
+ * - `{ count?, delay? }` — periodic repetition with optional count and delay in seconds (iOS 18+).
115
+ */
116
+ repeat?: 'continuous' | 'nonRepeating' | { count?: number; delay?: number };
117
+ /** Animation speed multiplier (1.0 = default). */
118
+ speed?: number;
119
+ };
120
+
121
+ /** Equatable primitive accepted as a discrete effect trigger. */
122
+ export type DiscreteSymbolEffectValue = number | string | boolean;
123
+
124
+ /**
125
+ * Applies an SF Symbol effect to a view.
126
+ *
127
+ * @platform ios 17.0+
128
+ * @platform tvos 17.0+
129
+ * @see Official [SwiftUI documentation](https://developer.apple.com/documentation/SwiftUI/View/symbolEffect(_:options:value:)).
130
+ *
131
+ * @example
132
+ * ```tsx
133
+ * const trigger = useNativeState(0);
134
+ * <Image
135
+ * systemName="bell.fill"
136
+ * modifiers={[symbolEffect({ effect: 'bounce', direction: 'up' }, { value: trigger })]}
137
+ * />
138
+ * ```
139
+ */
140
+ export const symbolEffect = (
141
+ effect: SymbolEffect,
142
+ args: {
143
+ options?: SymbolEffectOptions;
144
+ /** Indefinite effects: runs while `state.value === true`. Default active when omitted. */
145
+ isActive?: ObservableState<boolean>;
146
+ /** Discrete effects: the effect fires once each time this value changes. */
147
+ value?: ObservableState<DiscreteSymbolEffectValue>;
148
+ } = {}
149
+ ) => {
150
+ const { options, isActive, value } = args;
151
+ return createModifier('symbolEffect', {
152
+ effect,
153
+ options: flattenOptions(options),
154
+ isActive: isActive ? getStateId(isActive) : undefined,
155
+ value: value ? getStateId(value) : undefined,
156
+ });
157
+ };
158
+
159
+ function flattenOptions(options?: SymbolEffectOptions) {
160
+ if (!options) return undefined;
161
+ const { speed } = options;
162
+ const repeatField = options.repeat;
163
+ if (repeatField === undefined) {
164
+ return { speed };
165
+ }
166
+ if (repeatField === 'nonRepeating') {
167
+ return { repeatKind: 'nonRepeating' as const, speed };
168
+ }
169
+ if (repeatField === 'continuous') {
170
+ return { repeatKind: 'continuous' as const, speed };
171
+ }
172
+ return {
173
+ repeatKind: 'periodic' as const,
174
+ repeatCount: repeatField.count,
175
+ repeatDelay: repeatField.delay,
176
+ speed,
177
+ };
178
+ }
179
+
180
+ // exported for docs api data
181
+ export { type ObservableState };
@@ -0,0 +1,71 @@
1
+ import { requireNativeModule } from 'expo';
2
+
3
+ import { worklets } from '../State/optionalWorklets';
4
+ import { VALUE_SYMBOL } from './modifiers/animation/constants';
5
+ import type { AnimationObject, ChainableAnimationType } from './modifiers/animation/types';
6
+
7
+ const ExpoUI = requireNativeModule('ExpoUI');
8
+
9
+ /**
10
+ * @see Official [SwiftUI documentation](https://developer.apple.com/documentation/swiftui/animationcompletioncriteria).
11
+ */
12
+ export type WithAnimationCompletionCriteria = 'logicallyComplete' | 'removed';
13
+
14
+ /**
15
+ * Mirrors SwiftUI's [`withAnimation(_:_:)`](https://developer.apple.com/documentation/swiftui/withanimation(_:_:)).
16
+ * The body must be a worklet so the mutations run synchronously on the
17
+ * UI thread inside the animation transaction.
18
+ *
19
+ * Performs `body` inside a SwiftUI animation transaction. Any
20
+ * `useNativeState` values mutated by the worklet animate to their new value
21
+ * using `animation`.
22
+ *
23
+ * @param animation Animation to apply, built with the `Animation` factory
24
+ * from `@expo/ui/swift-ui/modifiers`. Pass `null` to run `body` without an
25
+ * animation.
26
+ * @param body Worklet that mutates one or more `useNativeState` values.
27
+ * @param completion Optional worklet invoked on the main thread when the
28
+ * animation finishes. Requires iOS 17 / tvOS 17; on earlier versions the
29
+ * animation still runs but the callback is silently skipped.
30
+ * @param completionCriteria Controls when `completion` fires. Defaults to
31
+ * `'logicallyComplete'`.
32
+ */
33
+ export function withAnimation(
34
+ animation: ChainableAnimationType | null,
35
+ body: () => void,
36
+ completion?: () => void,
37
+ completionCriteria?: WithAnimationCompletionCriteria
38
+ ): void {
39
+ if (!worklets) {
40
+ throw new Error(
41
+ "withAnimation needs the 'react-native-worklets' package, which couldn't be loaded. " +
42
+ 'Install react-native-worklets and rebuild the native app, then call withAnimation again.'
43
+ );
44
+ }
45
+
46
+ if (!worklets.isWorkletFunction(body)) {
47
+ throw new Error(
48
+ 'withAnimation body must be a worklet. Worklets run synchronously on the UI thread ' +
49
+ "so state changes are captured by SwiftUI's animation transaction. " +
50
+ "Add the 'worklet' directive as the first statement: " +
51
+ "() => { 'worklet'; state.value = next; }."
52
+ );
53
+ }
54
+
55
+ let completionCallback: object | null = null;
56
+ if (completion) {
57
+ if (!worklets.isWorkletFunction(completion)) {
58
+ throw new Error(
59
+ "withAnimation completion must be a worklet. Add the 'worklet' directive as the " +
60
+ 'first statement in your completion callback.'
61
+ );
62
+ }
63
+ completionCallback = new ExpoUI.WorkletCallback(worklets.createSerializable(completion));
64
+ }
65
+
66
+ const animationObject: AnimationObject | null =
67
+ animation == null ? null : animation[VALUE_SYMBOL]();
68
+
69
+ const bodyCallback = new ExpoUI.WorkletCallback(worklets.createSerializable(body));
70
+ ExpoUI.withAnimation(animationObject, bodyCallback, completionCallback, completionCriteria);
71
+ }
@@ -7,6 +7,8 @@ declare module 'react-native' {
7
7
  ) => React.ReactElement<P>;
8
8
 
9
9
  type DisplayValue = ReactNative.FlexStyle['display'] | 'inline-flex';
10
+ type OverflowValue = 'auto' | 'hidden' | 'scroll' | 'visible';
11
+ type WebDimensionValue = ReactNative.DimensionValue | string;
10
12
 
11
13
  type WebRole =
12
14
  | ReactNative.Role
@@ -95,18 +97,43 @@ declare module 'react-native' {
95
97
  }
96
98
 
97
99
  export interface ViewProps extends WebAccessibilityProps {
100
+ dir?: string;
98
101
  role?: WebRole;
99
102
  }
100
103
 
101
104
  export interface ImageStyle {
102
105
  display?: DisplayValue;
106
+ height?: WebDimensionValue;
107
+ width?: WebDimensionValue;
108
+ overflowX?: OverflowValue;
109
+ overflowY?: OverflowValue;
110
+ paddingLeft?: WebDimensionValue;
111
+ paddingRight?: WebDimensionValue;
112
+ paddingTop?: WebDimensionValue;
113
+ paddingBottom?: WebDimensionValue;
103
114
  }
104
115
 
105
116
  export interface TextStyle {
106
117
  display?: DisplayValue;
118
+ height?: WebDimensionValue;
119
+ width?: WebDimensionValue;
120
+ overflowX?: OverflowValue;
121
+ overflowY?: OverflowValue;
122
+ paddingLeft?: WebDimensionValue;
123
+ paddingRight?: WebDimensionValue;
124
+ paddingTop?: WebDimensionValue;
125
+ paddingBottom?: WebDimensionValue;
107
126
  }
108
127
 
109
128
  export interface ViewStyle {
110
129
  display?: DisplayValue;
130
+ height?: WebDimensionValue;
131
+ width?: WebDimensionValue;
132
+ overflowX?: OverflowValue;
133
+ overflowY?: OverflowValue;
134
+ paddingLeft?: WebDimensionValue;
135
+ paddingRight?: WebDimensionValue;
136
+ paddingTop?: WebDimensionValue;
137
+ paddingBottom?: WebDimensionValue;
111
138
  }
112
139
  }
@@ -1,32 +1,56 @@
1
1
  import { Column, ModalBottomSheet } from '@expo/ui/jetpack-compose';
2
2
  import {
3
+ fillMaxHeight,
3
4
  padding,
4
5
  testID as testIDModifier,
5
6
  type ModifierConfig,
6
7
  } from '@expo/ui/jetpack-compose/modifiers';
7
8
 
8
- import type { BottomSheetProps } from './types';
9
+ import type { BottomSheetProps, SnapPoint } from './types';
10
+
11
+ // M3 `ModalBottomSheet` only has partial/expanded states.
12
+ // Only allow the partial state when the consumer requested a partial-friendly snap point.
13
+ function shouldSkipPartiallyExpanded(snapPoints: SnapPoint[] | undefined): boolean {
14
+ if (!snapPoints || snapPoints.length === 0) return false;
15
+ return !snapPoints.some(
16
+ (sp) =>
17
+ sp === 'half' ||
18
+ (typeof sp === 'object' && 'fraction' in sp && sp.fraction < 1) ||
19
+ (typeof sp === 'object' && 'height' in sp)
20
+ );
21
+ }
22
+
23
+ // M3 sizes content to intrinsic height.
24
+ // Apply `fillMaxHeight` so `'full'` actually fills the viewport instead of stopping at content height.
25
+ function shouldFillMaxHeight(snapPoints: SnapPoint[] | undefined): boolean {
26
+ if (!snapPoints || snapPoints.length === 0) return false;
27
+ return snapPoints.some(
28
+ (sp) => sp === 'full' || (typeof sp === 'object' && 'fraction' in sp && sp.fraction >= 1)
29
+ );
30
+ }
9
31
 
10
32
  export function BottomSheet({
11
33
  children,
12
34
  isPresented,
13
35
  onDismiss,
14
36
  showDragIndicator = true,
37
+ snapPoints,
15
38
  testID,
16
39
  modifiers,
17
40
  }: BottomSheetProps) {
18
41
  if (!isPresented) return null;
19
42
 
20
43
  const contentModifiers: ModifierConfig[] = [padding(16, showDragIndicator ? 0 : 16, 16, 0)];
44
+ if (shouldFillMaxHeight(snapPoints)) contentModifiers.push(fillMaxHeight());
21
45
  if (testID) contentModifiers.push(testIDModifier(testID));
22
46
 
23
47
  return (
24
48
  <ModalBottomSheet
25
49
  onDismissRequest={onDismiss}
26
50
  showDragHandle={showDragIndicator}
51
+ skipPartiallyExpanded={shouldSkipPartiallyExpanded(snapPoints)}
27
52
  modifiers={modifiers}>
28
- {/* When the drag handle is hidden, add top padding so content doesn't
29
- crop against the top edge of the sheet. */}
53
+ {/* When the drag handle is hidden, add top padding so content doesn't crop against the top edge of the sheet. */}
30
54
  <Column modifiers={contentModifiers}>{children}</Column>
31
55
  </ModalBottomSheet>
32
56
  );