@expo/ui 58.0.21 → 58.0.22

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 (113) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +86 -2
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/java/expo/modules/ui/CompositionLocalProviderView.kt +26 -0
  5. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +12 -0
  6. package/android/src/main/java/expo/modules/ui/HostView.kt +4 -1
  7. package/android/src/main/java/expo/modules/ui/MaskView.kt +3 -2
  8. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +3 -1
  9. package/android/src/main/java/expo/modules/ui/RNHostView.kt +11 -7
  10. package/android/src/main/java/expo/modules/ui/SwitchView.kt +6 -1
  11. package/android/src/main/java/expo/modules/ui/TopAppBarView.kt +62 -0
  12. package/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt +61 -0
  13. package/build/community/bottom-sheet/BottomSheet.ios.js +8 -3
  14. package/build/community/bottom-sheet/BottomSheet.ios.js.map +1 -1
  15. package/build/community/pager-view/PagerView.ios.js +25 -8
  16. package/build/community/pager-view/PagerView.ios.js.map +1 -1
  17. package/build/community/segmented-control/SegmentedControl.ios.js +11 -2
  18. package/build/community/segmented-control/SegmentedControl.ios.js.map +1 -1
  19. package/build/community/segmented-control/types.d.ts +5 -1
  20. package/build/community/segmented-control/types.js.map +1 -1
  21. package/build/jetpack-compose/CompositionLocalProvider/index.d.ts +28 -0
  22. package/build/jetpack-compose/CompositionLocalProvider/index.js +15 -0
  23. package/build/jetpack-compose/CompositionLocalProvider/index.js.map +1 -0
  24. package/build/jetpack-compose/Host/index.d.ts +8 -0
  25. package/build/jetpack-compose/Host/index.js +38 -5
  26. package/build/jetpack-compose/Host/index.js.map +1 -1
  27. package/build/jetpack-compose/Switch/index.d.ts +2 -0
  28. package/build/jetpack-compose/Switch/index.js +1 -0
  29. package/build/jetpack-compose/Switch/index.js.map +1 -1
  30. package/build/jetpack-compose/TopAppBar/index.d.ts +72 -0
  31. package/build/jetpack-compose/TopAppBar/index.js +45 -0
  32. package/build/jetpack-compose/TopAppBar/index.js.map +1 -0
  33. package/build/jetpack-compose/index.d.ts +2 -0
  34. package/build/jetpack-compose/index.js +2 -0
  35. package/build/jetpack-compose/index.js.map +1 -1
  36. package/build/jetpack-compose/modifiers/index.d.ts +5 -1
  37. package/build/jetpack-compose/modifiers/index.js +6 -2
  38. package/build/jetpack-compose/modifiers/index.js.map +1 -1
  39. package/build/swift-ui/BottomSheet/index.d.ts +6 -0
  40. package/build/swift-ui/BottomSheet/index.js +9 -3
  41. package/build/swift-ui/BottomSheet/index.js.map +1 -1
  42. package/build/universal/Checkbox/index.android.js +16 -4
  43. package/build/universal/Checkbox/index.android.js.map +1 -1
  44. package/build/universal/ScrollView/index.ios.js +12 -6
  45. package/build/universal/ScrollView/index.ios.js.map +1 -1
  46. package/build/universal/Switch/index.android.js +17 -4
  47. package/build/universal/Switch/index.android.js.map +1 -1
  48. package/build/universal/Switch/index.js +1 -0
  49. package/build/universal/Switch/index.js.map +1 -1
  50. package/build/universal/Text/index.js +2 -2
  51. package/build/universal/Text/index.js.map +1 -1
  52. package/build/universal/webUtils.js +4 -0
  53. package/build/universal/webUtils.js.map +1 -1
  54. package/ios/BottomSheetView.swift +11 -2
  55. package/ios/DatePickerView.swift +134 -9
  56. package/ios/Modifiers/SelectedSegmentTintColorModifier.swift +86 -0
  57. package/ios/Modifiers/ViewModifierRegistry.swift +4 -0
  58. package/ios/RNHostView.swift +34 -16
  59. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.21/expo.modules.ui-58.0.21-sources.jar → 58.0.22/expo.modules.ui-58.0.22-sources.jar} +0 -0
  60. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22-sources.jar.md5 +1 -0
  61. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22-sources.jar.sha1 +1 -0
  62. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22-sources.jar.sha256 +1 -0
  63. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22-sources.jar.sha512 +1 -0
  64. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.aar +0 -0
  65. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.aar.md5 +1 -0
  66. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.aar.sha1 +1 -0
  67. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.aar.sha256 +1 -0
  68. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.aar.sha512 +1 -0
  69. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.21/expo.modules.ui-58.0.21.module → 58.0.22/expo.modules.ui-58.0.22.module} +22 -22
  70. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.module.md5 +1 -0
  71. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.module.sha1 +1 -0
  72. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.module.sha256 +1 -0
  73. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.module.sha512 +1 -0
  74. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.21/expo.modules.ui-58.0.21.pom → 58.0.22/expo.modules.ui-58.0.22.pom} +1 -1
  75. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.pom.md5 +1 -0
  76. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.pom.sha1 +1 -0
  77. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.pom.sha256 +1 -0
  78. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.22/expo.modules.ui-58.0.22.pom.sha512 +1 -0
  79. package/package.json +2 -2
  80. package/src/community/bottom-sheet/BottomSheet.ios.tsx +7 -3
  81. package/src/community/pager-view/PagerView.ios.tsx +25 -8
  82. package/src/community/segmented-control/SegmentedControl.ios.tsx +13 -1
  83. package/src/community/segmented-control/types.tsx +5 -1
  84. package/src/jetpack-compose/CompositionLocalProvider/index.tsx +43 -0
  85. package/src/jetpack-compose/Host/index.tsx +50 -4
  86. package/src/jetpack-compose/Switch/index.tsx +4 -0
  87. package/src/jetpack-compose/TopAppBar/index.tsx +113 -0
  88. package/src/jetpack-compose/index.ts +2 -0
  89. package/src/jetpack-compose/modifiers/index.ts +11 -3
  90. package/src/swift-ui/BottomSheet/index.tsx +24 -3
  91. package/src/universal/Checkbox/index.android.tsx +24 -5
  92. package/src/universal/ScrollView/index.ios.tsx +14 -3
  93. package/src/universal/Switch/index.android.tsx +23 -4
  94. package/src/universal/Switch/index.tsx +1 -0
  95. package/src/universal/Text/index.tsx +2 -2
  96. package/src/universal/webUtils.ts +4 -0
  97. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21-sources.jar.md5 +0 -1
  98. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21-sources.jar.sha1 +0 -1
  99. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21-sources.jar.sha256 +0 -1
  100. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21-sources.jar.sha512 +0 -1
  101. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.aar +0 -0
  102. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.aar.md5 +0 -1
  103. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.aar.sha1 +0 -1
  104. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.aar.sha256 +0 -1
  105. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.aar.sha512 +0 -1
  106. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.module.md5 +0 -1
  107. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.module.sha1 +0 -1
  108. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.module.sha256 +0 -1
  109. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.module.sha512 +0 -1
  110. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.pom.md5 +0 -1
  111. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.pom.sha1 +0 -1
  112. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.pom.sha256 +0 -1
  113. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.21/expo.modules.ui-58.0.21.pom.sha512 +0 -1
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "expo.modules.ui",
5
5
  "module": "expo.modules.ui",
6
- "version": "58.0.21",
6
+ "version": "58.0.22",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.ui-58.0.21.aar",
28
- "url": "expo.modules.ui-58.0.21.aar",
29
- "size": 1861060,
30
- "sha512": "61d884b5f7528ac4d2791cebbdc45eefa1bfaf4eca17faf37b322a58a5670b042b1096a860bba051adb4a9d95334f227a9ea8f69473289d7a7725ea2600d7c48",
31
- "sha256": "83f788f94510400d5695ee901721e3cef284309da751b00c9df7eeb8e4fdd5ca",
32
- "sha1": "0a7945dfaec70218a5a6ed678049dd831dde7c23",
33
- "md5": "485ed53072102e73937c25dae52b054c"
27
+ "name": "expo.modules.ui-58.0.22.aar",
28
+ "url": "expo.modules.ui-58.0.22.aar",
29
+ "size": 1882334,
30
+ "sha512": "0a89be2233ee1dd16b1cb671e0694d6d9a428f7f199e3e3efcdd621090c541d435478b39402a6da271373a70cbed690f989e938ebcbbcd35b740e1bf240dfb64",
31
+ "sha256": "59cc7013ee6674f030b4f81641fa80fb1829815d6ab5a7903f0e7479e6adb96e",
32
+ "sha1": "7acd625f3cd50ad228fac699424fcff3c77942bb",
33
+ "md5": "6b7272e2ff6f2f155c76a4e7a3341af6"
34
34
  }
35
35
  ]
36
36
  },
@@ -141,13 +141,13 @@
141
141
  ],
142
142
  "files": [
143
143
  {
144
- "name": "expo.modules.ui-58.0.21.aar",
145
- "url": "expo.modules.ui-58.0.21.aar",
146
- "size": 1861060,
147
- "sha512": "61d884b5f7528ac4d2791cebbdc45eefa1bfaf4eca17faf37b322a58a5670b042b1096a860bba051adb4a9d95334f227a9ea8f69473289d7a7725ea2600d7c48",
148
- "sha256": "83f788f94510400d5695ee901721e3cef284309da751b00c9df7eeb8e4fdd5ca",
149
- "sha1": "0a7945dfaec70218a5a6ed678049dd831dde7c23",
150
- "md5": "485ed53072102e73937c25dae52b054c"
144
+ "name": "expo.modules.ui-58.0.22.aar",
145
+ "url": "expo.modules.ui-58.0.22.aar",
146
+ "size": 1882334,
147
+ "sha512": "0a89be2233ee1dd16b1cb671e0694d6d9a428f7f199e3e3efcdd621090c541d435478b39402a6da271373a70cbed690f989e938ebcbbcd35b740e1bf240dfb64",
148
+ "sha256": "59cc7013ee6674f030b4f81641fa80fb1829815d6ab5a7903f0e7479e6adb96e",
149
+ "sha1": "7acd625f3cd50ad228fac699424fcff3c77942bb",
150
+ "md5": "6b7272e2ff6f2f155c76a4e7a3341af6"
151
151
  }
152
152
  ]
153
153
  },
@@ -161,13 +161,13 @@
161
161
  },
162
162
  "files": [
163
163
  {
164
- "name": "expo.modules.ui-58.0.21-sources.jar",
165
- "url": "expo.modules.ui-58.0.21-sources.jar",
166
- "size": 113113,
167
- "sha512": "10ed672c04082cf5f06e6ca6dca0f28229f11f3d503d46e5b61dbc7d3ad50bfdc1399838dd38dc5b8c1643ec375c4bf1672c95606ce28f7ed36384f66b829b68",
168
- "sha256": "f9a24faad34f9f66bebcdde7d05a6cf72a8b4b9030819b80049de5084a694ef0",
169
- "sha1": "5d7c4fd251da7578f23af5102d539533aefb3e64",
170
- "md5": "83ac1d8d00a142a3ffa834c4a6c1db65"
164
+ "name": "expo.modules.ui-58.0.22-sources.jar",
165
+ "url": "expo.modules.ui-58.0.22-sources.jar",
166
+ "size": 115385,
167
+ "sha512": "6c228c1dcbe9ee23cca52ba20eb724d39d18533e6c31ad738ab5738e5dfc64432b7c79363f7ff772f019f8bda955d0d38ccb3f4331d3236e4beddd70e6e426f4",
168
+ "sha256": "2487d657c68bf866e677ef379bc97b7de37edb41eb173b6d8d938b81bfcb2293",
169
+ "sha1": "a1628408f238e018286c0c9887ebb3cf9912577d",
170
+ "md5": "3367880bd9dfec581496b8b5087f9ae0"
171
171
  }
172
172
  ]
173
173
  }
@@ -0,0 +1 @@
1
+ 0ca2cbc2529c5570d31a5cbd827f2b9c58602e25
@@ -0,0 +1 @@
1
+ 113bc88759a3cbbb991dc12571a1b53722855970232edf6508469dc52c822423
@@ -0,0 +1 @@
1
+ ef4e3b199c2d69deab60d857ef81dc8c786f2bdc5a4321e9397111507d9f235c8575cd91289d8c612666b901cadd7aeca414aece1769dca15ec2088f5b8c0cb4
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>expo.modules.ui</groupId>
11
11
  <artifactId>expo.modules.ui</artifactId>
12
- <version>58.0.21</version>
12
+ <version>58.0.22</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.ui</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ 2a13240cd35aa73ed4d9787b4a70cbe0453431dd
@@ -0,0 +1 @@
1
+ c15bb9d564da8ea75a501e89d3c855df5294966438d81168675388dab731e23e
@@ -0,0 +1 @@
1
+ a1a75142fcd486afa2e7bd2f27cdc21e1997cb309fb762963b61e65c88fd6e5a62af5234f448b485b0a2f3081f74eb1c8664e9d08a1f1a445ca2c62639e7f3f1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/ui",
3
- "version": "58.0.21",
3
+ "version": "58.0.22",
4
4
  "description": "A collection of UI components",
5
5
  "keywords": [
6
6
  "Jetpack Compose",
@@ -141,7 +141,7 @@
141
141
  },
142
142
  "devDependencies": {
143
143
  "@babel/core": "^7.26.0",
144
- "@expo/internal-scripts": "58.0.3",
144
+ "@expo/internal-scripts": "58.0.4",
145
145
  "@playwright/test": "^1.59.0",
146
146
  "@testing-library/react": "^16.3.0",
147
147
  "@testing-library/react-native": "^14.0.1",
@@ -135,10 +135,14 @@ export function BottomSheet(props: BottomSheetProps) {
135
135
  } else if (indexProp >= 0) {
136
136
  closedRef.current = false;
137
137
  setIsPresented(true);
138
- const clampedIndex = Math.min(indexProp, detents.length - 1);
139
- setCurrentIndex(clampedIndex);
140
- currentIndexRef.current = clampedIndex;
141
138
  }
139
+ }, [indexProp]);
140
+
141
+ useEffect(() => {
142
+ const requestedIndex = indexProp >= 0 ? indexProp : currentIndexRef.current;
143
+ const clampedIndex = Math.min(requestedIndex, detents.length - 1);
144
+ setCurrentIndex(clampedIndex);
145
+ currentIndexRef.current = clampedIndex;
142
146
  }, [indexProp, detents.length]);
143
147
 
144
148
  const handlePresentedChange = useCallback((presented: boolean) => {
@@ -9,7 +9,7 @@ import {
9
9
  useState,
10
10
  type ReactElement,
11
11
  } from 'react';
12
- import { Platform } from 'react-native';
12
+ import { Platform, StyleSheet, View } from 'react-native';
13
13
 
14
14
  import { useNativeState, worklets } from '../../State';
15
15
  import { Group } from '../../swift-ui/Group';
@@ -89,6 +89,12 @@ export function PagerView(props: PagerViewProps) {
89
89
  const pageCountRef = useRef(0);
90
90
  pageCountRef.current = pageCount;
91
91
 
92
+ const isScrollingRef = useRef(false);
93
+ // The SwiftUI scroll doesn't cancel React Native touches on its pages. Like
94
+ // React Native's `ScrollView`, take the touch from the page content while
95
+ // the pager moves, so a swipe or a tap that stops it doesn't press anything.
96
+ const shouldCaptureTouch = () => isScrollingRef.current;
97
+
92
98
  // Clamp on first render — out-of-range initials produce an id that matches
93
99
  // no `Group`, leaving `.scrollPosition(id:)` silently stuck at 0.
94
100
  const [clampedInitialPage] = useState(() => {
@@ -188,17 +194,24 @@ export function PagerView(props: PagerViewProps) {
188
194
  );
189
195
  const geometryModifier = useScrollGeometryChange(handleScrollGeometry);
190
196
 
191
- const phaseModifier = onPageScrollStateChanged
192
- ? onScrollPhaseChange((phase) =>
193
- onPageScrollStateChanged(wrapNativeEvent({ pageScrollState: phaseToPageState(phase) }))
194
- )
195
- : null;
197
+ const phaseModifier = onScrollPhaseChange((phase) => {
198
+ const pageScrollState = phaseToPageState(phase);
199
+ isScrollingRef.current = pageScrollState !== 'idle';
200
+ onPageScrollStateChanged?.(wrapNativeEvent({ pageScrollState }));
201
+ });
196
202
 
197
203
  const pages = validChildren.map((child, index) => (
198
204
  <Group
199
205
  key={child.key ?? String(index)}
200
206
  modifiers={[containerRelativeFrame({ axes: 'horizontal' }), id(String(index))]}>
201
- <RNHostView>{child}</RNHostView>
207
+ <RNHostView>
208
+ <View
209
+ style={styles.page}
210
+ onStartShouldSetResponderCapture={shouldCaptureTouch}
211
+ onMoveShouldSetResponderCapture={shouldCaptureTouch}>
212
+ {child}
213
+ </View>
214
+ </RNHostView>
202
215
  </Group>
203
216
  ));
204
217
 
@@ -209,7 +222,7 @@ export function PagerView(props: PagerViewProps) {
209
222
  scrollDisabled(!scrollEnabledState),
210
223
  scrollPosition(activePageState, { onChange: handleScrolledIDChange }),
211
224
  ...(geometryModifier ? [geometryModifier] : []),
212
- ...(phaseModifier ? [phaseModifier] : []),
225
+ phaseModifier,
213
226
  ];
214
227
 
215
228
  return (
@@ -271,3 +284,7 @@ function warnIfPreIOS18ScrollCallbacksDropped(
271
284
  );
272
285
  }
273
286
  }
287
+
288
+ const styles = StyleSheet.create({
289
+ page: { flex: 1 },
290
+ });
@@ -1,7 +1,11 @@
1
1
  import { Host } from '../../swift-ui/Host';
2
2
  import { Picker } from '../../swift-ui/Picker';
3
3
  import { Text } from '../../swift-ui/Text';
4
- import { disabled as disabledModifier } from '../../swift-ui/modifiers';
4
+ import {
5
+ accessibilityLabel as accessibilityLabelModifier,
6
+ disabled as disabledModifier,
7
+ } from '../../swift-ui/modifiers';
8
+ import { createModifier } from '../../swift-ui/modifiers/createModifier';
5
9
  import { environment } from '../../swift-ui/modifiers/environment';
6
10
  import { pickerStyle } from '../../swift-ui/modifiers/pickerStyle';
7
11
  import { tag } from '../../swift-ui/modifiers/tag';
@@ -15,15 +19,23 @@ export function SegmentedControl(props: SegmentedControlProps) {
15
19
  enabled = true,
16
20
  onChange,
17
21
  onValueChange,
22
+ tintColor,
18
23
  appearance,
19
24
  style,
20
25
  testID,
26
+ accessibilityLabel,
21
27
  } = props;
22
28
 
23
29
  const modifiers: ModifierConfig[] = [pickerStyle('segmented')];
24
30
  if (!enabled) {
25
31
  modifiers.push(disabledModifier());
26
32
  }
33
+ if (accessibilityLabel) {
34
+ modifiers.push(accessibilityLabelModifier(accessibilityLabel));
35
+ }
36
+ if (tintColor) {
37
+ modifiers.push(createModifier('selectedSegmentTintColor', { color: tintColor }));
38
+ }
27
39
  if (appearance) {
28
40
  modifiers.push(environment('colorScheme', appearance));
29
41
  }
@@ -43,7 +43,6 @@ export interface SegmentedControlProps {
43
43
  onValueChange?: (value: string) => void;
44
44
  /**
45
45
  * Accent color of the control.
46
- * @platform android, web
47
46
  */
48
47
  tintColor?: string;
49
48
  /**
@@ -52,6 +51,11 @@ export interface SegmentedControlProps {
52
51
  appearance?: 'dark' | 'light';
53
52
  style?: StyleProp<ViewStyle>;
54
53
  testID?: string;
54
+ /**
55
+ * The name of the control that screen readers announce.
56
+ * @platform ios
57
+ */
58
+ accessibilityLabel?: string;
55
59
  }
56
60
 
57
61
  /**
@@ -0,0 +1,43 @@
1
+ import { requireNativeView } from 'expo';
2
+ import type { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Composition local values that `CompositionLocalProvider` can provide.
6
+ */
7
+ export type CompositionLocalValues = {
8
+ /**
9
+ * The minimum size, in dp, that Material components such as `Button`, `IconButton`, `Checkbox`, and
10
+ * `Switch` reserve for their touch target. Maps to `LocalMinimumInteractiveComponentSize`.
11
+ * Set it to `0` to let these components be smaller than Material's default of 48dp.
12
+ */
13
+ minimumInteractiveComponentSize?: number;
14
+ };
15
+
16
+ export type CompositionLocalProviderProps = {
17
+ /**
18
+ * The values to provide to the children.
19
+ */
20
+ values: CompositionLocalValues;
21
+ /**
22
+ * The content that receives the values.
23
+ */
24
+ children?: ReactNode;
25
+ };
26
+
27
+ type NativeCompositionLocalProviderProps = CompositionLocalValues & {
28
+ children?: ReactNode;
29
+ };
30
+
31
+ const CompositionLocalProviderNativeView: React.ComponentType<NativeCompositionLocalProviderProps> =
32
+ requireNativeView('ExpoUI', 'CompositionLocalProviderView');
33
+
34
+ /**
35
+ * Provides composition local values to its children, like Jetpack Compose's `CompositionLocalProvider`.
36
+ * It adds no layout of its own, so children keep modifiers such as `weight` from the parent `Row`
37
+ * or `Column`.
38
+ */
39
+ export function CompositionLocalProvider({ values, children }: CompositionLocalProviderProps) {
40
+ return (
41
+ <CompositionLocalProviderNativeView {...values}>{children}</CompositionLocalProviderNativeView>
42
+ );
43
+ }
@@ -6,13 +6,19 @@ import {
6
6
  type StyleProp,
7
7
  type ViewStyle,
8
8
  I18nManager,
9
+ processColor,
9
10
  useColorScheme as useRNColorScheme,
10
11
  } from 'react-native';
11
12
 
12
13
  import { NativeTreeContext, useWarnIfNestedHost } from '../../NativeTreeContext';
13
14
  import { TextInputHostProvider, useTextInputHostRef } from '../../keyboard';
14
15
  import { useMergeRefs } from '../../utils/useMergeRefs';
15
- import { getMaterialColors, HostPaletteContext } from '../colors';
16
+ import {
17
+ getMaterialColors,
18
+ HostPaletteContext,
19
+ type MaterialColors,
20
+ type RgbaHex,
21
+ } from '../colors';
16
22
  import { type PrimitiveBaseProps } from '../layout';
17
23
 
18
24
  export interface HostProps extends PrimitiveBaseProps {
@@ -52,6 +58,14 @@ export interface HostProps extends PrimitiveBaseProps {
52
58
  */
53
59
  seedColor?: ColorValue;
54
60
 
61
+ /**
62
+ * Material 3 color roles that replace the matching roles of the palette from
63
+ * `colorScheme` and `seedColor`. Roles you omit keep their palette value.
64
+ * The overrides theme Compose children and are returned by `useMaterialColors()`
65
+ * in the same `#RRGGBBAA` form as the other roles.
66
+ */
67
+ colorOverrides?: Partial<MaterialColors>;
68
+
55
69
  /**
56
70
  * The layout direction for the content.
57
71
  * Defaults to the current locale direction from I18nManager.
@@ -93,6 +107,7 @@ export function Host(props: HostProps) {
93
107
  layoutDirection,
94
108
  colorScheme,
95
109
  seedColor,
110
+ colorOverrides,
96
111
  ref,
97
112
  children,
98
113
  ...restProps
@@ -101,10 +116,19 @@ export function Host(props: HostProps) {
101
116
 
102
117
  const systemScheme = useRNColorScheme();
103
118
  const resolvedScheme = schemeString ?? (systemScheme === 'dark' ? 'dark' : 'light');
104
- const palette = useMemo(
105
- () => getMaterialColors({ scheme: resolvedScheme, seedColor }),
106
- [resolvedScheme, seedColor]
119
+ // Key on the normalized values, not the object, so an inline `colorOverrides` object keeps
120
+ // the palette from `useMaterialColors()` reference-stable across re-renders.
121
+ const overridesKey = colorOverrides
122
+ ? JSON.stringify(normalizeColorOverrides(colorOverrides))
123
+ : null;
124
+ const normalizedOverrides = useMemo<Partial<MaterialColors> | undefined>(
125
+ () => (overridesKey ? JSON.parse(overridesKey) : undefined),
126
+ [overridesKey]
107
127
  );
128
+ const palette = useMemo(() => {
129
+ const basePalette = getMaterialColors({ scheme: resolvedScheme, seedColor });
130
+ return normalizedOverrides ? { ...basePalette, ...normalizedOverrides } : basePalette;
131
+ }, [resolvedScheme, seedColor, normalizedOverrides]);
108
132
  const hostRef = useTextInputHostRef();
109
133
  const mergedRef = useMergeRefs(ref, hostRef);
110
134
  useWarnIfNestedHost('Jetpack Compose');
@@ -123,6 +147,7 @@ export function Host(props: HostProps) {
123
147
  }
124
148
  colorScheme={schemeString}
125
149
  seedColor={seedColor}
150
+ colorOverrides={normalizedOverrides}
126
151
  onLayoutContent={onLayoutContent}
127
152
  layoutDirection={
128
153
  layoutDirection ?? (I18nManager.getConstants().isRTL ? 'rightToLeft' : 'leftToRight')
@@ -134,3 +159,24 @@ export function Host(props: HostProps) {
134
159
  </HostPaletteContext.Provider>
135
160
  );
136
161
  }
162
+
163
+ function normalizeColorOverrides(overrides: Partial<MaterialColors>): Partial<MaterialColors> {
164
+ const normalized: Partial<MaterialColors> = {};
165
+ for (const [role, color] of Object.entries(overrides) as [keyof MaterialColors, RgbaHex][]) {
166
+ const hex = color != null ? toRgbaHex(color) : null;
167
+ if (hex) {
168
+ normalized[role] = hex;
169
+ }
170
+ }
171
+ return normalized;
172
+ }
173
+
174
+ // `processColor` returns `0xAARRGGBB`; the palette uses `#RRGGBBAA` like native `toTokenMap()`.
175
+ function toRgbaHex(color: RgbaHex): RgbaHex | null {
176
+ const argb = processColor(color);
177
+ if (typeof argb !== 'number') {
178
+ return null;
179
+ }
180
+ const rgba = ((argb << 8) | (argb >>> 24)) >>> 0;
181
+ return `#${rgba.toString(16).padStart(8, '0').toUpperCase()}`;
182
+ }
@@ -39,6 +39,8 @@ export interface SwitchProps {
39
39
  enabled?: boolean;
40
40
  /**
41
41
  * Callback function that is called when the checked state changes.
42
+ * When omitted, the switch is not interactive on its own, so a parent with the
43
+ * `toggleable` modifier can handle input and accessibility for it.
42
44
  */
43
45
  onCheckedChange?: (value: boolean) => void;
44
46
  /**
@@ -58,6 +60,7 @@ export interface SwitchProps {
58
60
 
59
61
  type NativeSwitchProps = Omit<SwitchProps, 'onCheckedChange' | 'children'> & {
60
62
  children?: React.ReactNode;
63
+ nativeClickable: boolean;
61
64
  onCheckedChange: (event: NativeSyntheticEvent<{ value: boolean }>) => void;
62
65
  };
63
66
 
@@ -94,6 +97,7 @@ function transformSwitchProps(props: SwitchProps): Omit<NativeSwitchProps, 'chil
94
97
  modifiers,
95
98
  ...(modifiers ? createViewModifierEventListener(modifiers) : undefined),
96
99
  ...restProps,
100
+ nativeClickable: onCheckedChange != null,
97
101
  onCheckedChange: ({ nativeEvent: { value } }) => {
98
102
  onCheckedChange?.(value);
99
103
  },
@@ -0,0 +1,113 @@
1
+ import { requireNativeView } from 'expo';
2
+ import type { ColorValue } from 'react-native';
3
+
4
+ import type { ExpoModifier } from '../../types';
5
+ import type { PaddingValuesRecord } from '../Carousel';
6
+ import { createViewModifierEventListener } from '../modifiers/utils';
7
+
8
+ export type { PaddingValuesRecord };
9
+
10
+ /**
11
+ * Colors for the top app bar.
12
+ */
13
+ export type TopAppBarColors = {
14
+ /**
15
+ * Color of the container (background).
16
+ */
17
+ containerColor?: ColorValue;
18
+ /**
19
+ * Color of the navigation icon.
20
+ */
21
+ navigationIconContentColor?: ColorValue;
22
+ /**
23
+ * Color of the title.
24
+ */
25
+ titleContentColor?: ColorValue;
26
+ /**
27
+ * Color of the action icons.
28
+ */
29
+ actionIconContentColor?: ColorValue;
30
+ };
31
+
32
+ export interface TopAppBarProps {
33
+ /**
34
+ * Colors for the app bar. Unset colors use `TopAppBarDefaults.topAppBarColors()`.
35
+ */
36
+ colors?: TopAppBarColors;
37
+ /**
38
+ * Insets in dp that the app bar adds around its content. Missing sides are `0`.
39
+ * When omitted, the app bar uses `TopAppBarDefaults.windowInsets`, which assumes
40
+ * the bar is at the top edge of the window, even inside a nested `Host`.
41
+ * Pass `{}` when a parent handles the insets. This removes both the top status
42
+ * bar inset and the horizontal system bar and display cutout insets.
43
+ */
44
+ windowInsets?: PaddingValuesRecord;
45
+ /**
46
+ * Modifiers for the component.
47
+ */
48
+ modifiers?: ExpoModifier[];
49
+ /**
50
+ * `TopAppBar.Title`, `TopAppBar.NavigationIcon`, and `TopAppBar.Actions` slots.
51
+ */
52
+ children: React.ReactNode;
53
+ }
54
+
55
+ type SlotProps = {
56
+ children: React.ReactNode;
57
+ };
58
+
59
+ type NativeSlotViewProps = {
60
+ slotName: string;
61
+ children: React.ReactNode;
62
+ };
63
+
64
+ const TopAppBarNativeView: React.ComponentType<TopAppBarProps> = requireNativeView(
65
+ 'ExpoUI',
66
+ 'TopAppBarView'
67
+ );
68
+
69
+ const SlotNativeView: React.ComponentType<NativeSlotViewProps> = requireNativeView(
70
+ 'ExpoUI',
71
+ 'SlotView'
72
+ );
73
+
74
+ /**
75
+ * Title slot for `TopAppBar`.
76
+ */
77
+ function TopAppBarTitle(props: SlotProps) {
78
+ return <SlotNativeView slotName="title">{props.children}</SlotNativeView>;
79
+ }
80
+
81
+ /**
82
+ * Navigation icon slot for `TopAppBar`. It appears at the start of the app bar.
83
+ */
84
+ function TopAppBarNavigationIcon(props: SlotProps) {
85
+ return <SlotNativeView slotName="navigationIcon">{props.children}</SlotNativeView>;
86
+ }
87
+
88
+ /**
89
+ * Actions slot for `TopAppBar`. Its children appear in a row at the end of the app bar.
90
+ */
91
+ function TopAppBarActions(props: SlotProps) {
92
+ return <SlotNativeView slotName="actions">{props.children}</SlotNativeView>;
93
+ }
94
+
95
+ /**
96
+ * A Material 3 small top app bar that shows a title, a navigation icon, and actions.
97
+ */
98
+ function TopAppBar(props: TopAppBarProps) {
99
+ const { modifiers, ...restProps } = props;
100
+ return (
101
+ <TopAppBarNativeView
102
+ modifiers={modifiers}
103
+ {...(modifiers ? createViewModifierEventListener(modifiers) : undefined)}
104
+ {...restProps}
105
+ />
106
+ );
107
+ }
108
+
109
+ TopAppBar.Title = TopAppBarTitle;
110
+ TopAppBar.NavigationIcon = TopAppBarNavigationIcon;
111
+ TopAppBar.Actions = TopAppBarActions;
112
+
113
+ export { TopAppBar };
@@ -59,6 +59,7 @@ export * from './SearchBar';
59
59
  export * from './Snackbar';
60
60
  export * from './DockedSearchBar';
61
61
  export * from './HorizontalFloatingToolbar';
62
+ export * from './TopAppBar';
62
63
  export * from './FloatingActionButton';
63
64
  export * from './PullToRefreshBox';
64
65
  export * from './RadioButton';
@@ -71,6 +72,7 @@ export * from './AnimatedVisibility';
71
72
  export * from './Box';
72
73
  export * from './Row';
73
74
  export * from './Column';
75
+ export * from './CompositionLocalProvider';
74
76
  export * from './FlowRow';
75
77
  export { useNativeState, useWorkletCallback, type WorkletCallback } from '../State';
76
78
  export type { ViewEvent } from '../types';
@@ -403,13 +403,21 @@ export const selectableGroup = () => createModifier('selectableGroup');
403
403
  * @param value - The current toggle state.
404
404
  * @param handler - Function to call when toggled.
405
405
  * @param options - Optional configuration.
406
- * @param options.role - The semantic role for accessibility: `'checkbox'`, `'radioButton'`, `'switch'`, or `'tab'`.
406
+ * @param options.role - The semantic role for accessibility: `'checkbox'`, `'radioButton'`,
407
+ * `'switch'`, or `'tab'`.
408
+ * @param options.enabled - Whether the view responds to input and is reported as enabled to
409
+ * accessibility services. Defaults to `true`.
407
410
  */
408
411
  export const toggleable = (
409
412
  value: boolean,
410
413
  handler: () => void,
411
- options?: { role?: 'checkbox' | 'radioButton' | 'switch' | 'tab' }
412
- ) => createModifierWithEventListener('toggleable', handler, { value, role: options?.role });
414
+ options?: { role?: 'checkbox' | 'radioButton' | 'switch' | 'tab'; enabled?: boolean }
415
+ ) =>
416
+ createModifierWithEventListener('toggleable', handler, {
417
+ value,
418
+ role: options?.role,
419
+ enabled: options?.enabled,
420
+ });
413
421
 
414
422
  // =============================================================================
415
423
  // Lifecycle Modifiers
@@ -18,6 +18,12 @@ export interface BottomSheetProps extends CommonViewModifierProps {
18
18
  * kept mounted, so presenting the sheet doesn't shift surrounding layout. Optional.
19
19
  */
20
20
  anchor?: ReactNode;
21
+ /**
22
+ * A view that fills the whole sheet behind its content, for example a `Rectangle` filled with a
23
+ * material. Unlike the `presentationBackground` modifier, a material in a view stays translucent
24
+ * on iOS 26 and later.
25
+ */
26
+ presentationBackground?: ReactNode;
21
27
  /**
22
28
  * Whether the `BottomSheet` is presented.
23
29
  */
@@ -40,7 +46,7 @@ export interface BottomSheetProps extends CommonViewModifierProps {
40
46
 
41
47
  type NativeBottomSheetProps = Omit<
42
48
  BottomSheetProps,
43
- 'onIsPresentedChange' | 'onDismiss' | 'anchor'
49
+ 'onIsPresentedChange' | 'onDismiss' | 'anchor' | 'presentationBackground'
44
50
  > & {
45
51
  onIsPresentedChange: (event: NativeSyntheticEvent<{ isPresented: boolean }>) => void;
46
52
  onDismiss: (event: NativeSyntheticEvent<object>) => void;
@@ -55,7 +61,15 @@ const BottomSheetNativeView: ComponentType<NativeBottomSheetProps> = requireNati
55
61
  * `BottomSheet` presents content from the bottom of the screen.
56
62
  */
57
63
  function BottomSheet(props: BottomSheetProps) {
58
- const { modifiers, onIsPresentedChange, onDismiss, anchor, children, ...restProps } = props;
64
+ const {
65
+ modifiers,
66
+ onIsPresentedChange,
67
+ onDismiss,
68
+ anchor,
69
+ presentationBackground,
70
+ children,
71
+ ...restProps
72
+ } = props;
59
73
  const [isMounted, setIsMounted] = useState(props.isPresented);
60
74
 
61
75
  if (props.isPresented && !isMounted) {
@@ -79,7 +93,14 @@ function BottomSheet(props: BottomSheetProps) {
79
93
  {anchor != null ? <Slot name="anchor">{anchor}</Slot> : null}
80
94
  {/* Only the sheet's content is presented in its own view controller. The anchor renders in
81
95
  place, in the React Native surface, so it stays outside `PresentedContent`. */}
82
- {isMounted ? <PresentedContent>{children}</PresentedContent> : null}
96
+ {isMounted ? (
97
+ <PresentedContent>
98
+ {presentationBackground != null ? (
99
+ <Slot name="presentationBackground">{presentationBackground}</Slot>
100
+ ) : null}
101
+ {children}
102
+ </PresentedContent>
103
+ ) : null}
83
104
  </BottomSheetNativeView>
84
105
  );
85
106
  }