@expo/ui 56.0.8 → 56.0.10

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 (252) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/CLAUDE.md +1 -1
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +49 -6
  5. package/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt +97 -16
  6. package/android/src/main/java/expo/modules/ui/LoadingView.kt +80 -0
  7. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +31 -3
  8. package/android/src/main/java/expo/modules/ui/SnackbarView.kt +126 -0
  9. package/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt +2 -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/index.d.ts +6 -0
  13. package/build/State/index.d.ts.map +1 -0
  14. package/build/State/useNativeState.d.ts +32 -3
  15. package/build/State/useNativeState.d.ts.map +1 -1
  16. package/build/community/bottom-sheet/BottomSheet.ios.d.ts.map +1 -1
  17. package/build/community/pager-view/PagerView.android.d.ts +7 -0
  18. package/build/community/pager-view/PagerView.android.d.ts.map +1 -0
  19. package/build/community/pager-view/PagerView.d.ts +8 -0
  20. package/build/community/pager-view/PagerView.d.ts.map +1 -0
  21. package/build/community/pager-view/PagerView.ios.d.ts +15 -0
  22. package/build/community/pager-view/PagerView.ios.d.ts.map +1 -0
  23. package/build/community/pager-view/index.d.ts +3 -0
  24. package/build/community/pager-view/index.d.ts.map +1 -0
  25. package/build/community/pager-view/types.d.ts +128 -0
  26. package/build/community/pager-view/types.d.ts.map +1 -0
  27. package/build/community/segmented-control/vendor/SegmentsSeparators.d.ts.map +1 -1
  28. package/build/jetpack-compose/HorizontalPager/index.d.ts +27 -0
  29. package/build/jetpack-compose/HorizontalPager/index.d.ts.map +1 -1
  30. package/build/jetpack-compose/Host/index.d.ts +1 -0
  31. package/build/jetpack-compose/Host/index.d.ts.map +1 -1
  32. package/build/jetpack-compose/LoadingIndicator/index.d.ts +41 -0
  33. package/build/jetpack-compose/LoadingIndicator/index.d.ts.map +1 -0
  34. package/build/jetpack-compose/Snackbar/index.d.ts +94 -0
  35. package/build/jetpack-compose/Snackbar/index.d.ts.map +1 -0
  36. package/build/jetpack-compose/SyncSwitch/index.d.ts +1 -1
  37. package/build/jetpack-compose/SyncSwitch/index.d.ts.map +1 -1
  38. package/build/jetpack-compose/TextField/index.d.ts +1 -1
  39. package/build/jetpack-compose/TextField/index.d.ts.map +1 -1
  40. package/build/jetpack-compose/index.d.ts +3 -2
  41. package/build/jetpack-compose/index.d.ts.map +1 -1
  42. package/build/jetpack-compose/modifiers/index.d.ts +6 -2
  43. package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
  44. package/build/swift-ui/BottomSheet/index.d.ts +5 -1
  45. package/build/swift-ui/BottomSheet/index.d.ts.map +1 -1
  46. package/build/swift-ui/Host/index.d.ts +1 -0
  47. package/build/swift-ui/Host/index.d.ts.map +1 -1
  48. package/build/swift-ui/ScrollView/index.d.ts +30 -0
  49. package/build/swift-ui/ScrollView/index.d.ts.map +1 -1
  50. package/build/swift-ui/SecureField/index.d.ts +1 -1
  51. package/build/swift-ui/SecureField/index.d.ts.map +1 -1
  52. package/build/swift-ui/SyncToggle/index.d.ts +1 -1
  53. package/build/swift-ui/SyncToggle/index.d.ts.map +1 -1
  54. package/build/swift-ui/TextField/index.d.ts +1 -1
  55. package/build/swift-ui/TextField/index.d.ts.map +1 -1
  56. package/build/swift-ui/index.d.ts +2 -2
  57. package/build/swift-ui/index.d.ts.map +1 -1
  58. package/build/swift-ui/modifiers/index.d.ts +25 -15
  59. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  60. package/build/swift-ui/modifiers/scrollObservation.d.ts +52 -0
  61. package/build/swift-ui/modifiers/scrollObservation.d.ts.map +1 -0
  62. package/build/swift-ui/modifiers/scrollPosition.d.ts +1 -1
  63. package/build/swift-ui/modifiers/scrollPosition.d.ts.map +1 -1
  64. package/build/swift-ui/modifiers/symbolEffect.d.ts +1 -1
  65. package/build/swift-ui/modifiers/symbolEffect.d.ts.map +1 -1
  66. package/build/swift-ui/withAnimation.d.ts +26 -0
  67. package/build/swift-ui/withAnimation.d.ts.map +1 -0
  68. package/build/universal/BottomSheet/index.android.d.ts +1 -1
  69. package/build/universal/BottomSheet/index.android.d.ts.map +1 -1
  70. package/build/universal/BottomSheet/index.d.ts +1 -1
  71. package/build/universal/BottomSheet/index.d.ts.map +1 -1
  72. package/build/universal/BottomSheet/index.ios.d.ts +1 -1
  73. package/build/universal/BottomSheet/index.ios.d.ts.map +1 -1
  74. package/build/universal/BottomSheet/types.d.ts +27 -0
  75. package/build/universal/BottomSheet/types.d.ts.map +1 -1
  76. package/build/universal/Checkbox/index.d.ts.map +1 -1
  77. package/build/universal/Collapsible/index.android.d.ts +8 -0
  78. package/build/universal/Collapsible/index.android.d.ts.map +1 -0
  79. package/build/universal/Collapsible/index.d.ts +8 -0
  80. package/build/universal/Collapsible/index.d.ts.map +1 -0
  81. package/build/universal/Collapsible/index.ios.d.ts +7 -0
  82. package/build/universal/Collapsible/index.ios.d.ts.map +1 -0
  83. package/build/universal/Collapsible/types.d.ts +23 -0
  84. package/build/universal/Collapsible/types.d.ts.map +1 -0
  85. package/build/universal/Column/index.d.ts.map +1 -1
  86. package/build/universal/Host/index.d.ts +5 -18
  87. package/build/universal/Host/index.d.ts.map +1 -1
  88. package/build/universal/Host/types.d.ts +72 -0
  89. package/build/universal/Host/types.d.ts.map +1 -0
  90. package/build/universal/List/index.android.d.ts +9 -0
  91. package/build/universal/List/index.android.d.ts.map +1 -0
  92. package/build/universal/List/index.d.ts +8 -0
  93. package/build/universal/List/index.d.ts.map +1 -0
  94. package/build/universal/List/index.ios.d.ts +8 -0
  95. package/build/universal/List/index.ios.d.ts.map +1 -0
  96. package/build/universal/List/types.d.ts +26 -0
  97. package/build/universal/List/types.d.ts.map +1 -0
  98. package/build/universal/ListItem/ListItem.android.d.ts +8 -0
  99. package/build/universal/ListItem/ListItem.android.d.ts.map +1 -0
  100. package/build/universal/ListItem/ListItem.d.ts +9 -0
  101. package/build/universal/ListItem/ListItem.d.ts.map +1 -0
  102. package/build/universal/ListItem/ListItem.ios.d.ts +8 -0
  103. package/build/universal/ListItem/ListItem.ios.d.ts.map +1 -0
  104. package/build/universal/ListItem/ListItemSlots.d.ts +21 -0
  105. package/build/universal/ListItem/ListItemSlots.d.ts.map +1 -0
  106. package/build/universal/ListItem/index.d.ts +10 -0
  107. package/build/universal/ListItem/index.d.ts.map +1 -0
  108. package/build/universal/ListItem/types.d.ts +59 -0
  109. package/build/universal/ListItem/types.d.ts.map +1 -0
  110. package/build/universal/Picker/Picker.android.d.ts +9 -0
  111. package/build/universal/Picker/Picker.android.d.ts.map +1 -0
  112. package/build/universal/Picker/Picker.d.ts +8 -0
  113. package/build/universal/Picker/Picker.d.ts.map +1 -0
  114. package/build/universal/Picker/Picker.ios.d.ts +9 -0
  115. package/build/universal/Picker/Picker.ios.d.ts.map +1 -0
  116. package/build/universal/Picker/PickerItem.d.ts +9 -0
  117. package/build/universal/Picker/PickerItem.d.ts.map +1 -0
  118. package/build/universal/Picker/index.d.ts +8 -0
  119. package/build/universal/Picker/index.d.ts.map +1 -0
  120. package/build/universal/Picker/types.d.ts +69 -0
  121. package/build/universal/Picker/types.d.ts.map +1 -0
  122. package/build/universal/RNHostView/index.android.d.ts +7 -0
  123. package/build/universal/RNHostView/index.android.d.ts.map +1 -0
  124. package/build/universal/RNHostView/index.d.ts +7 -0
  125. package/build/universal/RNHostView/index.d.ts.map +1 -0
  126. package/build/universal/RNHostView/index.ios.d.ts +7 -0
  127. package/build/universal/RNHostView/index.ios.d.ts.map +1 -0
  128. package/build/universal/RNHostView/types.d.ts +23 -0
  129. package/build/universal/RNHostView/types.d.ts.map +1 -0
  130. package/build/universal/Switch/index.d.ts.map +1 -1
  131. package/build/universal/TextInput/index.d.ts.map +1 -1
  132. package/build/universal/index.d.ts +5 -0
  133. package/build/universal/index.d.ts.map +1 -1
  134. package/expo-module.config.json +1 -1
  135. package/ios/BottomSheetView.swift +4 -1
  136. package/ios/ExpoUIModule.swift +47 -4
  137. package/ios/HostView.swift +21 -18
  138. package/ios/Modifiers/AnimationConfig.swift +109 -0
  139. package/ios/Modifiers/FontModifier.swift +72 -20
  140. package/ios/Modifiers/ScrollObservationModifiers.swift +107 -0
  141. package/ios/Modifiers/ViewModifierRegistry.swift +9 -112
  142. package/ios/State/ObservableState.swift +12 -1
  143. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.8/expo.modules.ui-56.0.8-sources.jar → 56.0.10/expo.modules.ui-56.0.10-sources.jar} +0 -0
  144. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.md5 +1 -0
  145. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.sha1 +1 -0
  146. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.sha256 +1 -0
  147. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.sha512 +1 -0
  148. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar +0 -0
  149. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.md5 +1 -0
  150. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.sha1 +1 -0
  151. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.sha256 +1 -0
  152. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.sha512 +1 -0
  153. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.8/expo.modules.ui-56.0.8.module → 56.0.10/expo.modules.ui-56.0.10.module} +22 -22
  154. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.md5 +1 -0
  155. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.sha1 +1 -0
  156. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.sha256 +1 -0
  157. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.sha512 +1 -0
  158. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.8/expo.modules.ui-56.0.8.pom → 56.0.10/expo.modules.ui-56.0.10.pom} +1 -1
  159. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.md5 +1 -0
  160. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.sha1 +1 -0
  161. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.sha256 +1 -0
  162. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.sha512 +1 -0
  163. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  164. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  165. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  166. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  167. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  168. package/package.json +7 -3
  169. package/src/State/index.ts +10 -0
  170. package/src/State/useNativeState.ts +71 -3
  171. package/src/community/bottom-sheet/BottomSheet.ios.tsx +0 -17
  172. package/src/community/pager-view/PagerView.android.tsx +223 -0
  173. package/src/community/pager-view/PagerView.ios.tsx +267 -0
  174. package/src/community/pager-view/PagerView.tsx +14 -0
  175. package/src/community/pager-view/index.tsx +13 -0
  176. package/src/community/pager-view/types.tsx +137 -0
  177. package/src/community/picker/Picker.android.tsx +1 -1
  178. package/src/community/segmented-control/vendor/SegmentsSeparators.tsx +3 -6
  179. package/src/jetpack-compose/HorizontalPager/index.tsx +89 -18
  180. package/src/jetpack-compose/Host/index.tsx +1 -0
  181. package/src/jetpack-compose/LoadingIndicator/index.tsx +91 -0
  182. package/src/jetpack-compose/Snackbar/index.tsx +135 -0
  183. package/src/jetpack-compose/SyncSwitch/index.tsx +1 -3
  184. package/src/jetpack-compose/TextField/index.tsx +1 -4
  185. package/src/jetpack-compose/index.ts +3 -2
  186. package/src/jetpack-compose/modifiers/index.ts +5 -2
  187. package/src/swift-ui/BottomSheet/index.tsx +32 -15
  188. package/src/swift-ui/Host/index.tsx +1 -0
  189. package/src/swift-ui/ScrollView/index.tsx +33 -0
  190. package/src/swift-ui/SecureField/index.tsx +1 -4
  191. package/src/swift-ui/SyncToggle/index.tsx +1 -3
  192. package/src/swift-ui/TextField/index.tsx +1 -4
  193. package/src/swift-ui/index.tsx +2 -3
  194. package/src/swift-ui/modifiers/index.ts +37 -14
  195. package/src/swift-ui/modifiers/scrollObservation.ts +80 -0
  196. package/src/swift-ui/modifiers/scrollPosition.ts +1 -2
  197. package/src/swift-ui/modifiers/symbolEffect.ts +1 -2
  198. package/src/swift-ui/withAnimation.ts +71 -0
  199. package/src/ts-declarations/react-native-web.d.ts +7 -0
  200. package/src/universal/BottomSheet/index.android.tsx +58 -11
  201. package/src/universal/BottomSheet/index.ios.tsx +40 -20
  202. package/src/universal/BottomSheet/index.tsx +49 -4
  203. package/src/universal/BottomSheet/types.ts +25 -0
  204. package/src/universal/Checkbox/index.tsx +14 -2
  205. package/src/universal/Collapsible/index.android.tsx +72 -0
  206. package/src/universal/Collapsible/index.ios.tsx +16 -0
  207. package/src/universal/Collapsible/index.tsx +71 -0
  208. package/src/universal/Collapsible/types.ts +25 -0
  209. package/src/universal/Column/index.tsx +3 -1
  210. package/src/universal/Host/index.tsx +9 -10
  211. package/src/universal/Host/types.ts +70 -0
  212. package/src/universal/List/index.android.tsx +44 -0
  213. package/src/universal/List/index.ios.tsx +19 -0
  214. package/src/universal/List/index.tsx +26 -0
  215. package/src/universal/List/types.ts +28 -0
  216. package/src/universal/ListItem/ListItem.android.tsx +52 -0
  217. package/src/universal/ListItem/ListItem.ios.tsx +58 -0
  218. package/src/universal/ListItem/ListItem.tsx +77 -0
  219. package/src/universal/ListItem/ListItemSlots.tsx +66 -0
  220. package/src/universal/ListItem/index.ts +15 -0
  221. package/src/universal/ListItem/types.ts +67 -0
  222. package/src/universal/Picker/Picker.android.tsx +69 -0
  223. package/src/universal/Picker/Picker.ios.tsx +45 -0
  224. package/src/universal/Picker/Picker.tsx +52 -0
  225. package/src/universal/Picker/PickerItem.tsx +27 -0
  226. package/src/universal/Picker/index.ts +11 -0
  227. package/src/universal/Picker/types.ts +79 -0
  228. package/src/universal/RNHostView/index.android.tsx +33 -0
  229. package/src/universal/RNHostView/index.ios.tsx +12 -0
  230. package/src/universal/RNHostView/index.tsx +39 -0
  231. package/src/universal/RNHostView/types.ts +25 -0
  232. package/src/universal/Switch/index.tsx +7 -2
  233. package/src/universal/TextInput/index.tsx +12 -2
  234. package/src/universal/index.ts +5 -0
  235. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.md5 +0 -1
  236. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.sha1 +0 -1
  237. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.sha256 +0 -1
  238. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.sha512 +0 -1
  239. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar +0 -0
  240. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.md5 +0 -1
  241. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.sha1 +0 -1
  242. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.sha256 +0 -1
  243. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.sha512 +0 -1
  244. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.md5 +0 -1
  245. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.sha1 +0 -1
  246. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.sha256 +0 -1
  247. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.sha512 +0 -1
  248. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.md5 +0 -1
  249. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.sha1 +0 -1
  250. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.sha256 +0 -1
  251. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.sha512 +0 -1
  252. package/src/community/bottom-sheet/CLAUDE.md +0 -55
@@ -0,0 +1,267 @@
1
+ import {
2
+ Children,
3
+ isValidElement,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type ReactElement,
11
+ } from 'react';
12
+ import { Platform } from 'react-native';
13
+
14
+ import { wrapNativeEvent, type PagerViewProps } from './types';
15
+ import { useNativeState, worklets } from '../../State';
16
+ import { Group } from '../../swift-ui/Group';
17
+ import { Host } from '../../swift-ui/Host';
18
+ import { LazyHStack } from '../../swift-ui/LazyHStack';
19
+ import { RNHostView } from '../../swift-ui/RNHostView';
20
+ import { ScrollView, type ScrollGeometry, type ScrollPhase } from '../../swift-ui/ScrollView';
21
+ import {
22
+ containerRelativeFrame,
23
+ id,
24
+ onScrollPhaseChange,
25
+ scrollDisabled,
26
+ scrollPosition,
27
+ scrollTargetBehavior,
28
+ scrollTargetLayout,
29
+ useScrollGeometryChange,
30
+ } from '../../swift-ui/modifiers';
31
+ import { Animation } from '../../swift-ui/modifiers/animation';
32
+ import { withAnimation } from '../../swift-ui/withAnimation';
33
+
34
+ function phaseToPageState(phase: ScrollPhase): 'idle' | 'dragging' | 'settling' {
35
+ switch (phase) {
36
+ case 'idle':
37
+ return 'idle';
38
+ case 'tracking':
39
+ case 'interacting':
40
+ return 'dragging';
41
+ case 'animating':
42
+ case 'decelerating':
43
+ return 'settling';
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Drop-in replacement for `react-native-pager-view` on iOS.
49
+ *
50
+ * Renders a SwiftUI `ScrollView` with paging behavior. Scroll position is the
51
+ * single source of truth: an `ObservableState` bound through the
52
+ * `scrollPosition` modifier drives initial placement, user-swipe writeback,
53
+ * and imperative `setPage` / `setPageWithoutAnimation`. The animated path
54
+ * routes the write through `withAnimation`; if `react-native-worklets` isn't
55
+ * installed, `setPage` falls back to a non-animated jump. Requires iOS 17+.
56
+ * Continuous progress (`onPageScroll`) and scroll state
57
+ * (`onPageScrollStateChanged`) events require iOS 18+.
58
+ */
59
+ export function PagerView(props: PagerViewProps) {
60
+ const {
61
+ ref,
62
+ initialPage = 0,
63
+ scrollEnabled = true,
64
+ onPageScroll,
65
+ onPageScrollStateChanged,
66
+ onPageSelected,
67
+ children,
68
+ style,
69
+ } = props;
70
+
71
+ warnIfPreIOS18ScrollCallbacksDropped(onPageScroll, onPageScrollStateChanged);
72
+
73
+ const [scrollEnabledState, setScrollEnabledState] = useState(scrollEnabled);
74
+ useEffect(() => {
75
+ setScrollEnabledState(scrollEnabled);
76
+ }, [scrollEnabled]);
77
+
78
+ const validChildren = useMemo(
79
+ () => Children.toArray(children).filter((c): c is ReactElement => isValidElement(c)),
80
+ [children]
81
+ );
82
+ const pageCount = validChildren.length;
83
+ const pageCountRef = useRef(0);
84
+ pageCountRef.current = pageCount;
85
+
86
+ // Clamp on first render — out-of-range initials produce an id that matches
87
+ // no `Group`, leaving `.scrollPosition(id:)` silently stuck at 0.
88
+ const [clampedInitialPage] = useState(() => {
89
+ if (pageCount === 0) return 0;
90
+ return Math.max(0, Math.min(pageCount - 1, initialPage));
91
+ });
92
+
93
+ const activePageState = useNativeState<string | null>(
94
+ clampedInitialPage > 0 ? String(clampedInitialPage) : null
95
+ );
96
+
97
+ // The SwiftUI writeback fires for both user swipes and our own imperative
98
+ // writes; dedup against this ref so `onPageSelected` fires once per change.
99
+ const lastSelectedPageRef = useRef(clampedInitialPage);
100
+
101
+ // Read the latest `onPageSelected` through a ref so the shrink-clamp effect
102
+ // doesn't need it in deps (and won't re-run / double-fire on callback
103
+ // identity changes).
104
+ const onPageSelectedRef = useRef(onPageSelected);
105
+ onPageSelectedRef.current = onPageSelected;
106
+
107
+ const handleScrolledIDChange = (newId: string | null) => {
108
+ if (newId == null) return;
109
+ const page = parseInt(newId, 10);
110
+ if (!Number.isFinite(page)) return;
111
+ if (page !== lastSelectedPageRef.current) {
112
+ lastSelectedPageRef.current = page;
113
+ onPageSelectedRef.current?.(wrapNativeEvent({ position: page }));
114
+ }
115
+ };
116
+
117
+ // Without clamping, an out-of-range id would silently no-op on
118
+ // `.scrollPosition(id:)` instead of jumping to the nearest page.
119
+ const clampPage = (page: number): number | null => {
120
+ const count = pageCountRef.current;
121
+ if (count === 0 || !Number.isFinite(page)) return null;
122
+ return Math.max(0, Math.min(count - 1, page));
123
+ };
124
+
125
+ // Bypasses the public `value` setter on JS-thread writes — its dev warning
126
+ // is aimed at user code; our own imperative API is an intentional JS-thread
127
+ // writer. Inside a worklet we use `activePageState.value = …` directly,
128
+ // which routes through the SharedObject prototype installed by `index.fx`.
129
+ const writePageFromJS = (id: string) => {
130
+ (activePageState as unknown as { setValue(v: { value: string }): void }).setValue({
131
+ value: id,
132
+ });
133
+ };
134
+
135
+ // Re-anchor when the page count drops past the current selection. Matches
136
+ // Android, where Compose's `PagerState` clamps `currentPage` to the new
137
+ // max and `settledPage` fires the corresponding event. Without this on
138
+ // iOS, `.scrollPosition(id:)` silently no-ops on the missing id and the
139
+ // pager visually drifts without firing `onPageSelected`.
140
+ useLayoutEffect(() => {
141
+ if (pageCount === 0) return;
142
+ if (lastSelectedPageRef.current < pageCount) return;
143
+ const clamped = pageCount - 1;
144
+ lastSelectedPageRef.current = clamped;
145
+ writePageFromJS(String(clamped));
146
+ onPageSelectedRef.current?.(wrapNativeEvent({ position: clamped }));
147
+ }, [pageCount]);
148
+
149
+ useImperativeHandle(
150
+ ref,
151
+ () => ({
152
+ setPage: (page: number) => {
153
+ const clamped = clampPage(page);
154
+ if (clamped == null) return;
155
+ const nextId = String(clamped);
156
+ // `withAnimation` requires `react-native-worklets`; fall back to an
157
+ // instant jump if it isn't installed.
158
+ if (worklets) {
159
+ // `null` would disable animation; `Animation.default` opts into
160
+ // SwiftUI's default paging animation.
161
+ withAnimation(Animation.default, () => {
162
+ 'worklet';
163
+ activePageState.value = nextId;
164
+ });
165
+ } else {
166
+ writePageFromJS(nextId);
167
+ }
168
+ },
169
+ setPageWithoutAnimation: (page: number) => {
170
+ const clamped = clampPage(page);
171
+ if (clamped == null) return;
172
+ writePageFromJS(String(clamped));
173
+ },
174
+ setScrollEnabled: setScrollEnabledState,
175
+ }),
176
+ [activePageState]
177
+ );
178
+
179
+ const handleScrollGeometry = useMemo(
180
+ () => (onPageScroll ? buildHandleScrollGeometry(onPageScroll) : undefined),
181
+ [onPageScroll]
182
+ );
183
+ const geometryModifier = useScrollGeometryChange(handleScrollGeometry);
184
+
185
+ const phaseModifier = onPageScrollStateChanged
186
+ ? onScrollPhaseChange((phase) =>
187
+ onPageScrollStateChanged(wrapNativeEvent({ pageScrollState: phaseToPageState(phase) }))
188
+ )
189
+ : null;
190
+
191
+ const pages = validChildren.map((child, index) => (
192
+ <Group
193
+ key={child.key ?? String(index)}
194
+ modifiers={[containerRelativeFrame({ axes: 'horizontal' }), id(String(index))]}>
195
+ <RNHostView>{child}</RNHostView>
196
+ </Group>
197
+ ));
198
+
199
+ // Toggle the flag rather than splicing the modifier in/out — SwiftUI diffs
200
+ // modifiers by position, so a shifting array resets the ScrollView's content.
201
+ const modifiers = [
202
+ scrollTargetBehavior('paging'),
203
+ scrollDisabled(!scrollEnabledState),
204
+ scrollPosition(activePageState, { onChange: handleScrolledIDChange }),
205
+ ...(geometryModifier ? [geometryModifier] : []),
206
+ ...(phaseModifier ? [phaseModifier] : []),
207
+ ];
208
+
209
+ return (
210
+ <Host style={style ?? { flex: 1 }}>
211
+ <ScrollView axes="horizontal" showsIndicators={false} modifiers={modifiers}>
212
+ <LazyHStack spacing={0} modifiers={[scrollTargetLayout()]}>
213
+ {pages}
214
+ </LazyHStack>
215
+ </ScrollView>
216
+ </Host>
217
+ );
218
+ }
219
+
220
+ function geometryToPageScroll(geometry: ScrollGeometry): { position: number; offset: number } {
221
+ 'worklet';
222
+ // Clamp so rubber-band overscroll doesn't emit `position` outside
223
+ // `[0, pageCount - 1]`. `Math.round` tolerates sub-pixel content sizing.
224
+ const pageCount = Math.max(1, Math.round(geometry.contentWidth / geometry.containerWidth));
225
+ const positionFloat = Math.max(
226
+ 0,
227
+ Math.min(pageCount - 1, geometry.contentOffsetX / geometry.containerWidth)
228
+ );
229
+ const position = Math.floor(positionFloat);
230
+ return { position, offset: positionFloat - position };
231
+ }
232
+
233
+ // Mirrors the worklet-ness of the user's callback so the per-frame mapping
234
+ // stays on the UI runtime when the user is also on it.
235
+ function buildHandleScrollGeometry(
236
+ userOnPageScroll: NonNullable<PagerViewProps['onPageScroll']>
237
+ ): (geometry: ScrollGeometry) => void {
238
+ if (worklets?.isWorkletFunction?.(userOnPageScroll)) {
239
+ return (geometry) => {
240
+ 'worklet';
241
+ if (geometry.containerWidth <= 0) return;
242
+ userOnPageScroll(wrapNativeEvent(geometryToPageScroll(geometry)));
243
+ };
244
+ }
245
+ return (geometry) => {
246
+ if (geometry.containerWidth <= 0) return;
247
+ userOnPageScroll(wrapNativeEvent(geometryToPageScroll(geometry)));
248
+ };
249
+ }
250
+
251
+ let didWarnPreIOS18 = false;
252
+ function warnIfPreIOS18ScrollCallbacksDropped(
253
+ onPageScroll: PagerViewProps['onPageScroll'],
254
+ onPageScrollStateChanged: PagerViewProps['onPageScrollStateChanged']
255
+ ) {
256
+ if (!__DEV__ || didWarnPreIOS18) return;
257
+ if (!onPageScroll && !onPageScrollStateChanged) return;
258
+ const major = parseInt(String(Platform.Version), 10);
259
+ if (Number.isFinite(major) && major < 18) {
260
+ didWarnPreIOS18 = true;
261
+ console.warn(
262
+ `[expo-ui PagerView] onPageScroll and onPageScrollStateChanged require iOS 18+ ` +
263
+ `and will not fire on iOS ${Platform.Version}. Guard with Platform.Version or ` +
264
+ `provide a fallback for older iOS targets.`
265
+ );
266
+ }
267
+ }
@@ -0,0 +1,14 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ import type { PagerViewProps } from './types';
4
+
5
+ /**
6
+ * A drop-in replacement for `react-native-pager-view`. Renders a horizontally
7
+ * paged view backed by Jetpack Compose's `HorizontalPager` on Android and
8
+ * SwiftUI on iOS. Each child is treated as a separate page.
9
+ */
10
+ export function PagerView(_props: PagerViewProps): never {
11
+ throw new Error(
12
+ `@expo/ui/community/pager-view is not implemented on ${Platform.OS}. Supported platforms: android, ios.`
13
+ );
14
+ }
@@ -0,0 +1,13 @@
1
+ export type {
2
+ PagerViewProps,
3
+ PagerViewRef,
4
+ PagerViewOnPageScrollEventData,
5
+ PagerViewOnPageSelectedEventData,
6
+ PageScrollStateChangedEventData,
7
+ PagerViewOnPageScrollEvent,
8
+ PagerViewOnPageSelectedEvent,
9
+ PageScrollStateChangedEvent,
10
+ } from './types';
11
+
12
+ // named export is for the docs generator; default for normal consumption
13
+ export { PagerView, PagerView as default } from './PagerView';
@@ -0,0 +1,137 @@
1
+ import type { ReactNode, Ref } from 'react';
2
+ import type { NativeSyntheticEvent, ViewProps } from 'react-native';
3
+
4
+ /**
5
+ * Event payload for `onPageScroll`. Mirrors the upstream
6
+ * `react-native-pager-view` shape.
7
+ */
8
+ export type PagerViewOnPageScrollEventData = Readonly<{
9
+ position: number;
10
+ offset: number;
11
+ }>;
12
+
13
+ /**
14
+ * Event payload for `onPageSelected`. Mirrors the upstream
15
+ * `react-native-pager-view` shape.
16
+ */
17
+ export type PagerViewOnPageSelectedEventData = Readonly<{
18
+ position: number;
19
+ }>;
20
+
21
+ /**
22
+ * Event payload for `onPageScrollStateChanged`. Mirrors the upstream
23
+ * `react-native-pager-view` shape.
24
+ */
25
+ export type PageScrollStateChangedEventData = Readonly<{
26
+ pageScrollState: 'idle' | 'dragging' | 'settling';
27
+ }>;
28
+
29
+ export type PagerViewOnPageScrollEvent = NativeSyntheticEvent<PagerViewOnPageScrollEventData>;
30
+ export type PagerViewOnPageSelectedEvent = NativeSyntheticEvent<PagerViewOnPageSelectedEventData>;
31
+ export type PageScrollStateChangedEvent = NativeSyntheticEvent<PageScrollStateChangedEventData>;
32
+
33
+ /**
34
+ * Wraps a payload as `NativeSyntheticEvent`. We only populate `nativeEvent`
35
+ * since our consumers (mirroring upstream `react-native-pager-view`) read
36
+ * only `event.nativeEvent.X`; the unset SyntheticEvent fields would never
37
+ * be observed in practice.
38
+ */
39
+ export const wrapNativeEvent = <T,>(nativeEvent: T): NativeSyntheticEvent<T> => {
40
+ 'worklet';
41
+ return { nativeEvent } as NativeSyntheticEvent<T>;
42
+ };
43
+
44
+ /**
45
+ * Props for the `PagerView` component.
46
+ * Compatible with `react-native-pager-view`.
47
+ */
48
+ export type PagerViewProps = ViewProps & {
49
+ /**
50
+ * Ref handle exposing imperative `setPage`, `setPageWithoutAnimation`,
51
+ * and `setScrollEnabled` methods.
52
+ */
53
+ ref?: Ref<PagerViewRef>;
54
+ /**
55
+ * Index of the page that is initially selected. Read **once** on mount;
56
+ * later changes are ignored. To navigate after mount, call
57
+ * `ref.setPage()` or `ref.setPageWithoutAnimation()`.
58
+ * @default 0
59
+ */
60
+ initialPage?: number;
61
+ /**
62
+ * Whether the user can swipe between pages.
63
+ * @default true
64
+ */
65
+ scrollEnabled?: boolean;
66
+ /**
67
+ * Layout direction for paging.
68
+ * @default 'ltr'
69
+ * @platform android
70
+ */
71
+ layoutDirection?: 'ltr' | 'rtl';
72
+ /**
73
+ * Number of pages kept off-screen on each side of the visible page.
74
+ * @platform android
75
+ */
76
+ offscreenPageLimit?: number;
77
+ /**
78
+ * Pixels of padding between pages.
79
+ * @platform android
80
+ */
81
+ pageMargin?: number;
82
+ /**
83
+ * Fires continuously while a swipe is in progress. The event's `position`
84
+ * is the index of the leading visible page; `offset` is the fractional
85
+ * progress toward the next page in the `[0, 1)` range.
86
+ *
87
+ * **iOS 18+ only.**
88
+ */
89
+ onPageScroll?: (event: PagerViewOnPageScrollEvent) => void;
90
+ /**
91
+ * Fires when a page is fully selected. The event's `position` is the
92
+ * index of the new page.
93
+ */
94
+ onPageSelected?: (event: PagerViewOnPageSelectedEvent) => void;
95
+ /**
96
+ * Fires when the scroll state changes between `idle`, `dragging`,
97
+ * and `settling`.
98
+ *
99
+ * **iOS 18+ only.**
100
+ */
101
+ onPageScrollStateChanged?: (event: PageScrollStateChangedEvent) => void;
102
+ /**
103
+ * Pages of the pager. Each child is treated as a separate page and
104
+ * stretched to fill the pager. Each child should have a stable `key`.
105
+ */
106
+ children?: ReactNode;
107
+ };
108
+
109
+ /**
110
+ * Ref handle for the `PagerView` component.
111
+ * Compatible with `react-native-pager-view`.
112
+ */
113
+ export type PagerViewRef = {
114
+ /**
115
+ * Animate the pager to the given page index. Out-of-range indices are
116
+ * silently ignored. On iOS the animation requires `react-native-worklets`;
117
+ * without it, `setPage` falls back to a non-animated jump.
118
+ */
119
+ setPage: (selectedPage: number) => void;
120
+ /**
121
+ * Jump to the given page index without an animation.
122
+ */
123
+ setPageWithoutAnimation: (selectedPage: number) => void;
124
+ /**
125
+ * Imperatively enable or disable user scrolling — convenient when
126
+ * toggling from a non-React context like a ref-based gesture handler.
127
+ *
128
+ * > **Note:** If the `scrollEnabled` prop is also provided, subsequent
129
+ * > prop changes win and reset the value set imperatively. To use the
130
+ * > imperative path exclusively, omit the prop.
131
+ *
132
+ * > Unlike upstream `react-native-pager-view` (which calls UIManager
133
+ * > directly), this triggers a re-render of `PagerView` so the new
134
+ * > flag flows through to the native view via props.
135
+ */
136
+ setScrollEnabled: (scrollEnabled: boolean) => void;
137
+ };
@@ -7,7 +7,7 @@ import {
7
7
  type PickerItemValue,
8
8
  type PickerProps,
9
9
  } from './types';
10
- import { useNativeState } from '../../State/useNativeState';
10
+ import { useNativeState } from '../../State';
11
11
  import { DropdownMenuItem } from '../../jetpack-compose/DropdownMenu/DropdownMenuItem';
12
12
  import {
13
13
  ExposedDropdownMenuBox,
@@ -12,7 +12,8 @@ export function SegmentsSeparators({
12
12
  values: number;
13
13
  selectedIndex?: number;
14
14
  }) {
15
- const colorScheme = useColorScheme();
15
+ const isDark = useColorScheme() === 'dark';
16
+
16
17
  const hide = (val: number) => {
17
18
  return selectedIndex === val || selectedIndex === val + 1;
18
19
  };
@@ -22,11 +23,7 @@ export function SegmentsSeparators({
22
23
  {[...Array(values - 1).keys()].map((val) => (
23
24
  <View
24
25
  key={val}
25
- style={[
26
- styles.separator,
27
- colorScheme === 'dark' && styles.darkSeparator,
28
- hide(val) && styles.hide,
29
- ]}
26
+ style={[styles.separator, isDark && styles.darkSeparator, hide(val) && styles.hide]}
30
27
  />
31
28
  ))}
32
29
  </View>
@@ -1,6 +1,7 @@
1
1
  import { requireNativeView } from 'expo';
2
2
  import type { Ref } from 'react';
3
3
 
4
+ import { getStateId, useWorkletProp, worklets } from '../../State';
4
5
  import { type ModifierConfig, type ViewEvent } from '../../types';
5
6
  import { type PaddingValuesRecord } from '../Carousel';
6
7
  import { createViewModifierEventListener } from '../modifiers/utils';
@@ -19,6 +20,12 @@ export type HorizontalPagerHandle = {
19
20
  scrollToPage: (page: number) => Promise<void>;
20
21
  };
21
22
 
23
+ /**
24
+ * Kind of drag interaction reported by `onDragInteraction`. Mirrors Compose's
25
+ * `DragInteraction.Start` / `DragInteraction.Stop` / `DragInteraction.Cancel`.
26
+ */
27
+ export type HorizontalPagerDragInteraction = 'start' | 'stop' | 'cancel';
28
+
22
29
  export type HorizontalPagerProps = {
23
30
  /**
24
31
  * Imperative handle for programmatic navigation. Mirrors the methods on
@@ -42,6 +49,28 @@ export type HorizontalPagerProps = {
42
49
  * swipe or programmatic scroll has fully settled.
43
50
  */
44
51
  onSettledPageChange?: (page: number) => void;
52
+ /**
53
+ * Fires continuously while a swipe is in progress. Mirrors Compose's
54
+ * `PagerState.currentPage` and `currentPageOffsetFraction` — the latter is
55
+ * the signed fractional offset from `currentPage`, in the `[-0.5, 0.5]` range.
56
+ *
57
+ * If the callback is marked with the `'worklet'` directive, it runs
58
+ * synchronously on the UI thread; otherwise it is delivered asynchronously
59
+ * as a regular JS event.
60
+ */
61
+ onPageScroll?: (currentPage: number, currentPageOffsetFraction: number) => void;
62
+ /**
63
+ * Fires when Compose's `PagerState.isScrollInProgress` toggles — true while
64
+ * the pager is being dragged or animating to a snap target, false once it
65
+ * has settled.
66
+ */
67
+ onScrollInProgressChange?: (isScrollInProgress: boolean) => void;
68
+ /**
69
+ * Fires for each drag interaction emitted by `PagerState.interactionSource`.
70
+ * Combine with `onScrollInProgressChange` to distinguish user dragging from
71
+ * fling/snap-settling.
72
+ */
73
+ onDragInteraction?: (kind: HorizontalPagerDragInteraction) => void;
45
74
  /**
46
75
  * Spacing between pages in dp.
47
76
  * @default 0
@@ -79,35 +108,77 @@ export type HorizontalPagerProps = {
79
108
 
80
109
  type NativeHorizontalPagerProps = Omit<
81
110
  HorizontalPagerProps,
82
- 'onCurrentPageChange' | 'onSettledPageChange'
111
+ | 'onCurrentPageChange'
112
+ | 'onSettledPageChange'
113
+ | 'onPageScroll'
114
+ | 'onScrollInProgressChange'
115
+ | 'onDragInteraction'
83
116
  > &
84
117
  ViewEvent<'onCurrentPageChange', { position: number }> &
85
- ViewEvent<'onSettledPageChange', { position: number }>;
118
+ ViewEvent<'onSettledPageChange', { position: number }> &
119
+ ViewEvent<'onPageScroll', { currentPage: number; currentPageOffsetFraction: number }> &
120
+ ViewEvent<'onScrollInProgressChange', { isScrollInProgress: boolean }> &
121
+ ViewEvent<'onDragInteraction', { kind: HorizontalPagerDragInteraction }> & {
122
+ onPageScrollSync?: number | null;
123
+ };
86
124
 
87
125
  const NativeView: React.ComponentType<NativeHorizontalPagerProps> = requireNativeView(
88
126
  'ExpoUI',
89
127
  'HorizontalPagerView'
90
128
  );
91
129
 
92
- function transformProps(props: HorizontalPagerProps): NativeHorizontalPagerProps {
93
- const { modifiers, onCurrentPageChange, onSettledPageChange, ...restProps } = props;
94
- return {
95
- modifiers,
96
- ...(modifiers ? createViewModifierEventListener(modifiers) : undefined),
97
- ...restProps,
98
- onCurrentPageChange: onCurrentPageChange
99
- ? ({ nativeEvent: { position } }) => onCurrentPageChange(position)
100
- : undefined,
101
- onSettledPageChange: onSettledPageChange
102
- ? ({ nativeEvent: { position } }) => onSettledPageChange(position)
103
- : undefined,
104
- };
105
- }
106
-
107
130
  /**
108
131
  * A horizontally scrolling pager that snaps to individual pages,
109
132
  * matching Compose's `HorizontalPager`.
110
133
  */
111
134
  export function HorizontalPager(props: HorizontalPagerProps) {
112
- return <NativeView {...transformProps(props)} />;
135
+ const {
136
+ modifiers,
137
+ onCurrentPageChange,
138
+ onSettledPageChange,
139
+ onPageScroll,
140
+ onScrollInProgressChange,
141
+ onDragInteraction,
142
+ ...restProps
143
+ } = props;
144
+
145
+ const isPageScrollWorklet = !!onPageScroll && !!worklets?.isWorkletFunction?.(onPageScroll);
146
+ const pageScrollWorkletCallback = useWorkletProp(
147
+ isPageScrollWorklet ? onPageScroll : undefined,
148
+ 'onPageScroll'
149
+ );
150
+
151
+ return (
152
+ <NativeView
153
+ {...restProps}
154
+ modifiers={modifiers}
155
+ {...(modifiers ? createViewModifierEventListener(modifiers) : undefined)}
156
+ onCurrentPageChange={
157
+ onCurrentPageChange
158
+ ? ({ nativeEvent: { position } }) => onCurrentPageChange(position)
159
+ : undefined
160
+ }
161
+ onSettledPageChange={
162
+ onSettledPageChange
163
+ ? ({ nativeEvent: { position } }) => onSettledPageChange(position)
164
+ : undefined
165
+ }
166
+ onPageScroll={
167
+ !isPageScrollWorklet && onPageScroll
168
+ ? ({ nativeEvent: { currentPage, currentPageOffsetFraction } }) =>
169
+ onPageScroll(currentPage, currentPageOffsetFraction)
170
+ : undefined
171
+ }
172
+ onPageScrollSync={getStateId(pageScrollWorkletCallback)}
173
+ onScrollInProgressChange={
174
+ onScrollInProgressChange
175
+ ? ({ nativeEvent: { isScrollInProgress } }) =>
176
+ onScrollInProgressChange(isScrollInProgress)
177
+ : undefined
178
+ }
179
+ onDragInteraction={
180
+ onDragInteraction ? ({ nativeEvent: { kind } }) => onDragInteraction(kind) : undefined
181
+ }
182
+ />
183
+ );
113
184
  }
@@ -64,6 +64,7 @@ export type HostProps = {
64
64
 
65
65
  children: React.ReactNode;
66
66
  style?: StyleProp<ViewStyle>;
67
+ pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto';
67
68
  } & PrimitiveBaseProps;
68
69
 
69
70
  type NativeHostProps = Omit<HostProps, 'colorScheme'> & {