@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.
- package/CHANGELOG.md +35 -1
- package/CLAUDE.md +1 -1
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +49 -6
- package/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt +97 -16
- package/android/src/main/java/expo/modules/ui/LoadingView.kt +80 -0
- package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +31 -3
- package/android/src/main/java/expo/modules/ui/SnackbarView.kt +126 -0
- package/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt +2 -0
- package/android/src/main/java/expo/modules/ui/state/ObservableState.kt +10 -0
- package/assets/keyboard_arrow_down.xml +10 -0
- package/build/State/index.d.ts +6 -0
- package/build/State/index.d.ts.map +1 -0
- package/build/State/useNativeState.d.ts +32 -3
- package/build/State/useNativeState.d.ts.map +1 -1
- package/build/community/bottom-sheet/BottomSheet.ios.d.ts.map +1 -1
- package/build/community/pager-view/PagerView.android.d.ts +7 -0
- package/build/community/pager-view/PagerView.android.d.ts.map +1 -0
- package/build/community/pager-view/PagerView.d.ts +8 -0
- package/build/community/pager-view/PagerView.d.ts.map +1 -0
- package/build/community/pager-view/PagerView.ios.d.ts +15 -0
- package/build/community/pager-view/PagerView.ios.d.ts.map +1 -0
- package/build/community/pager-view/index.d.ts +3 -0
- package/build/community/pager-view/index.d.ts.map +1 -0
- package/build/community/pager-view/types.d.ts +128 -0
- package/build/community/pager-view/types.d.ts.map +1 -0
- package/build/community/segmented-control/vendor/SegmentsSeparators.d.ts.map +1 -1
- package/build/jetpack-compose/HorizontalPager/index.d.ts +27 -0
- package/build/jetpack-compose/HorizontalPager/index.d.ts.map +1 -1
- package/build/jetpack-compose/Host/index.d.ts +1 -0
- package/build/jetpack-compose/Host/index.d.ts.map +1 -1
- package/build/jetpack-compose/LoadingIndicator/index.d.ts +41 -0
- package/build/jetpack-compose/LoadingIndicator/index.d.ts.map +1 -0
- package/build/jetpack-compose/Snackbar/index.d.ts +94 -0
- package/build/jetpack-compose/Snackbar/index.d.ts.map +1 -0
- package/build/jetpack-compose/SyncSwitch/index.d.ts +1 -1
- package/build/jetpack-compose/SyncSwitch/index.d.ts.map +1 -1
- package/build/jetpack-compose/TextField/index.d.ts +1 -1
- package/build/jetpack-compose/TextField/index.d.ts.map +1 -1
- package/build/jetpack-compose/index.d.ts +3 -2
- package/build/jetpack-compose/index.d.ts.map +1 -1
- package/build/jetpack-compose/modifiers/index.d.ts +6 -2
- package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/BottomSheet/index.d.ts +5 -1
- package/build/swift-ui/BottomSheet/index.d.ts.map +1 -1
- package/build/swift-ui/Host/index.d.ts +1 -0
- package/build/swift-ui/Host/index.d.ts.map +1 -1
- package/build/swift-ui/ScrollView/index.d.ts +30 -0
- package/build/swift-ui/ScrollView/index.d.ts.map +1 -1
- package/build/swift-ui/SecureField/index.d.ts +1 -1
- package/build/swift-ui/SecureField/index.d.ts.map +1 -1
- package/build/swift-ui/SyncToggle/index.d.ts +1 -1
- package/build/swift-ui/SyncToggle/index.d.ts.map +1 -1
- package/build/swift-ui/TextField/index.d.ts +1 -1
- package/build/swift-ui/TextField/index.d.ts.map +1 -1
- package/build/swift-ui/index.d.ts +2 -2
- package/build/swift-ui/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +25 -15
- package/build/swift-ui/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/scrollObservation.d.ts +52 -0
- package/build/swift-ui/modifiers/scrollObservation.d.ts.map +1 -0
- package/build/swift-ui/modifiers/scrollPosition.d.ts +1 -1
- package/build/swift-ui/modifiers/scrollPosition.d.ts.map +1 -1
- package/build/swift-ui/modifiers/symbolEffect.d.ts +1 -1
- package/build/swift-ui/modifiers/symbolEffect.d.ts.map +1 -1
- package/build/swift-ui/withAnimation.d.ts +26 -0
- package/build/swift-ui/withAnimation.d.ts.map +1 -0
- package/build/universal/BottomSheet/index.android.d.ts +1 -1
- package/build/universal/BottomSheet/index.android.d.ts.map +1 -1
- package/build/universal/BottomSheet/index.d.ts +1 -1
- package/build/universal/BottomSheet/index.d.ts.map +1 -1
- package/build/universal/BottomSheet/index.ios.d.ts +1 -1
- package/build/universal/BottomSheet/index.ios.d.ts.map +1 -1
- package/build/universal/BottomSheet/types.d.ts +27 -0
- package/build/universal/BottomSheet/types.d.ts.map +1 -1
- package/build/universal/Checkbox/index.d.ts.map +1 -1
- package/build/universal/Collapsible/index.android.d.ts +8 -0
- package/build/universal/Collapsible/index.android.d.ts.map +1 -0
- package/build/universal/Collapsible/index.d.ts +8 -0
- package/build/universal/Collapsible/index.d.ts.map +1 -0
- package/build/universal/Collapsible/index.ios.d.ts +7 -0
- package/build/universal/Collapsible/index.ios.d.ts.map +1 -0
- package/build/universal/Collapsible/types.d.ts +23 -0
- package/build/universal/Collapsible/types.d.ts.map +1 -0
- package/build/universal/Column/index.d.ts.map +1 -1
- package/build/universal/Host/index.d.ts +5 -18
- package/build/universal/Host/index.d.ts.map +1 -1
- package/build/universal/Host/types.d.ts +72 -0
- package/build/universal/Host/types.d.ts.map +1 -0
- package/build/universal/List/index.android.d.ts +9 -0
- package/build/universal/List/index.android.d.ts.map +1 -0
- package/build/universal/List/index.d.ts +8 -0
- package/build/universal/List/index.d.ts.map +1 -0
- package/build/universal/List/index.ios.d.ts +8 -0
- package/build/universal/List/index.ios.d.ts.map +1 -0
- package/build/universal/List/types.d.ts +26 -0
- package/build/universal/List/types.d.ts.map +1 -0
- package/build/universal/ListItem/ListItem.android.d.ts +8 -0
- package/build/universal/ListItem/ListItem.android.d.ts.map +1 -0
- package/build/universal/ListItem/ListItem.d.ts +9 -0
- package/build/universal/ListItem/ListItem.d.ts.map +1 -0
- package/build/universal/ListItem/ListItem.ios.d.ts +8 -0
- package/build/universal/ListItem/ListItem.ios.d.ts.map +1 -0
- package/build/universal/ListItem/ListItemSlots.d.ts +21 -0
- package/build/universal/ListItem/ListItemSlots.d.ts.map +1 -0
- package/build/universal/ListItem/index.d.ts +10 -0
- package/build/universal/ListItem/index.d.ts.map +1 -0
- package/build/universal/ListItem/types.d.ts +59 -0
- package/build/universal/ListItem/types.d.ts.map +1 -0
- package/build/universal/Picker/Picker.android.d.ts +9 -0
- package/build/universal/Picker/Picker.android.d.ts.map +1 -0
- package/build/universal/Picker/Picker.d.ts +8 -0
- package/build/universal/Picker/Picker.d.ts.map +1 -0
- package/build/universal/Picker/Picker.ios.d.ts +9 -0
- package/build/universal/Picker/Picker.ios.d.ts.map +1 -0
- package/build/universal/Picker/PickerItem.d.ts +9 -0
- package/build/universal/Picker/PickerItem.d.ts.map +1 -0
- package/build/universal/Picker/index.d.ts +8 -0
- package/build/universal/Picker/index.d.ts.map +1 -0
- package/build/universal/Picker/types.d.ts +69 -0
- package/build/universal/Picker/types.d.ts.map +1 -0
- package/build/universal/RNHostView/index.android.d.ts +7 -0
- package/build/universal/RNHostView/index.android.d.ts.map +1 -0
- package/build/universal/RNHostView/index.d.ts +7 -0
- package/build/universal/RNHostView/index.d.ts.map +1 -0
- package/build/universal/RNHostView/index.ios.d.ts +7 -0
- package/build/universal/RNHostView/index.ios.d.ts.map +1 -0
- package/build/universal/RNHostView/types.d.ts +23 -0
- package/build/universal/RNHostView/types.d.ts.map +1 -0
- package/build/universal/Switch/index.d.ts.map +1 -1
- package/build/universal/TextInput/index.d.ts.map +1 -1
- package/build/universal/index.d.ts +5 -0
- package/build/universal/index.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/BottomSheetView.swift +4 -1
- package/ios/ExpoUIModule.swift +47 -4
- package/ios/HostView.swift +21 -18
- package/ios/Modifiers/AnimationConfig.swift +109 -0
- package/ios/Modifiers/FontModifier.swift +72 -20
- package/ios/Modifiers/ScrollObservationModifiers.swift +107 -0
- package/ios/Modifiers/ViewModifierRegistry.swift +9 -112
- package/ios/State/ObservableState.swift +12 -1
- 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
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.aar.sha512 +1 -0
- 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
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.module.sha512 +1 -0
- 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
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.10/expo.modules.ui-56.0.10.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
- package/package.json +7 -3
- package/src/State/index.ts +10 -0
- package/src/State/useNativeState.ts +71 -3
- package/src/community/bottom-sheet/BottomSheet.ios.tsx +0 -17
- package/src/community/pager-view/PagerView.android.tsx +223 -0
- package/src/community/pager-view/PagerView.ios.tsx +267 -0
- package/src/community/pager-view/PagerView.tsx +14 -0
- package/src/community/pager-view/index.tsx +13 -0
- package/src/community/pager-view/types.tsx +137 -0
- package/src/community/picker/Picker.android.tsx +1 -1
- package/src/community/segmented-control/vendor/SegmentsSeparators.tsx +3 -6
- package/src/jetpack-compose/HorizontalPager/index.tsx +89 -18
- package/src/jetpack-compose/Host/index.tsx +1 -0
- package/src/jetpack-compose/LoadingIndicator/index.tsx +91 -0
- package/src/jetpack-compose/Snackbar/index.tsx +135 -0
- package/src/jetpack-compose/SyncSwitch/index.tsx +1 -3
- package/src/jetpack-compose/TextField/index.tsx +1 -4
- package/src/jetpack-compose/index.ts +3 -2
- package/src/jetpack-compose/modifiers/index.ts +5 -2
- package/src/swift-ui/BottomSheet/index.tsx +32 -15
- package/src/swift-ui/Host/index.tsx +1 -0
- package/src/swift-ui/ScrollView/index.tsx +33 -0
- package/src/swift-ui/SecureField/index.tsx +1 -4
- package/src/swift-ui/SyncToggle/index.tsx +1 -3
- package/src/swift-ui/TextField/index.tsx +1 -4
- package/src/swift-ui/index.tsx +2 -3
- package/src/swift-ui/modifiers/index.ts +37 -14
- package/src/swift-ui/modifiers/scrollObservation.ts +80 -0
- package/src/swift-ui/modifiers/scrollPosition.ts +1 -2
- package/src/swift-ui/modifiers/symbolEffect.ts +1 -2
- package/src/swift-ui/withAnimation.ts +71 -0
- package/src/ts-declarations/react-native-web.d.ts +7 -0
- package/src/universal/BottomSheet/index.android.tsx +58 -11
- package/src/universal/BottomSheet/index.ios.tsx +40 -20
- package/src/universal/BottomSheet/index.tsx +49 -4
- package/src/universal/BottomSheet/types.ts +25 -0
- package/src/universal/Checkbox/index.tsx +14 -2
- package/src/universal/Collapsible/index.android.tsx +72 -0
- package/src/universal/Collapsible/index.ios.tsx +16 -0
- package/src/universal/Collapsible/index.tsx +71 -0
- package/src/universal/Collapsible/types.ts +25 -0
- package/src/universal/Column/index.tsx +3 -1
- package/src/universal/Host/index.tsx +9 -10
- package/src/universal/Host/types.ts +70 -0
- package/src/universal/List/index.android.tsx +44 -0
- package/src/universal/List/index.ios.tsx +19 -0
- package/src/universal/List/index.tsx +26 -0
- package/src/universal/List/types.ts +28 -0
- package/src/universal/ListItem/ListItem.android.tsx +52 -0
- package/src/universal/ListItem/ListItem.ios.tsx +58 -0
- package/src/universal/ListItem/ListItem.tsx +77 -0
- package/src/universal/ListItem/ListItemSlots.tsx +66 -0
- package/src/universal/ListItem/index.ts +15 -0
- package/src/universal/ListItem/types.ts +67 -0
- package/src/universal/Picker/Picker.android.tsx +69 -0
- package/src/universal/Picker/Picker.ios.tsx +45 -0
- package/src/universal/Picker/Picker.tsx +52 -0
- package/src/universal/Picker/PickerItem.tsx +27 -0
- package/src/universal/Picker/index.ts +11 -0
- package/src/universal/Picker/types.ts +79 -0
- package/src/universal/RNHostView/index.android.tsx +33 -0
- package/src/universal/RNHostView/index.ios.tsx +12 -0
- package/src/universal/RNHostView/index.tsx +39 -0
- package/src/universal/RNHostView/types.ts +25 -0
- package/src/universal/Switch/index.tsx +7 -2
- package/src/universal/TextInput/index.tsx +12 -2
- package/src/universal/index.ts +5 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.8/expo.modules.ui-56.0.8.pom.sha512 +0 -1
- package/src/community/bottom-sheet/CLAUDE.md +0 -55
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,37 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.10 — 2026-05-20
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Added `@expo/ui/community/pager-view` — a drop-in replacement for `react-native-pager-view`. ([#45499](https://github.com/expo/expo/pull/45499) by [@vonovak](https://github.com/vonovak))
|
|
18
|
+
- [iOS] Added `textStyle` option to `font` modifier in `@expo/ui/swift-ui` for iOS Dynamic Type scaling. ([#46007](https://github.com/expo/expo/pull/46007) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
19
|
+
|
|
20
|
+
### 🐛 Bug fixes
|
|
21
|
+
|
|
22
|
+
- [universal] Fix universal components dark theme ([#45969](https://github.com/expo/expo/pull/45969) by [@zoontek](https://github.com/zoontek))
|
|
23
|
+
- [universal] Fix `BottomSheet` behavior by making `Host` optional, and fix Android exit animation. ([#46031](https://github.com/expo/expo/pull/46031) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
24
|
+
|
|
25
|
+
## 56.0.9 — 2026-05-19
|
|
26
|
+
|
|
27
|
+
### 🎉 New features
|
|
28
|
+
|
|
29
|
+
- [iOS][android] Added `onChange` listener to `useNativeState`. ([#45961](https://github.com/expo/expo/pull/45961) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
30
|
+
- Allow writing to native state from the JS thread. ([#45901](https://github.com/expo/expo/pull/45901) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
31
|
+
- [iOS] Added `withAnimation(animation, body)` in `@expo/ui/swift-ui`, mirroring SwiftUI's [`withAnimation(_:_:)`](<https://developer.apple.com/documentation/swiftui/withanimation(_:_:)>). ([#45893](https://github.com/expo/expo/pull/45893) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
32
|
+
- [jetpack-compose] Added `Snackbar` component. ([#45667](https://github.com/expo/expo/pull/45667) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
33
|
+
- [android] Added `LoadingIndicator` and `ContainedLoadingIndicator` components. ([#41169](https://github.com/expo/expo/pull/41169) by [@suveshmoza](https://github.com/suveshmoza))
|
|
34
|
+
|
|
35
|
+
### 🐛 Bug fixes
|
|
36
|
+
|
|
37
|
+
- [iOS] Unmount `BottomSheet` when it is dismissed. ([#45846](https://github.com/expo/expo/pull/45846) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
38
|
+
- [iOS] Apply `modifiers` prop on `Host` instead of silently dropping it. ([#45872](https://github.com/expo/expo/pull/45872) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
39
|
+
|
|
40
|
+
### 💡 Others
|
|
41
|
+
|
|
42
|
+
- [universal] Add base styling to universal Picker on web ([#45932](https://github.com/expo/expo/pull/45932) by [@zoontek](https://github.com/zoontek))
|
|
43
|
+
|
|
13
44
|
## 56.0.8 — 2026-05-15
|
|
14
45
|
|
|
15
46
|
### 🎉 New features
|
|
@@ -34,6 +65,9 @@ _This version does not introduce any user-facing changes._
|
|
|
34
65
|
- Make `ChartView` public. ([#45674](https://github.com/expo/expo/pull/45674) by [@jakex7](https://github.com/jakex7))
|
|
35
66
|
- Added `@expo/ui/community/menu`, a drop-in replacement for `@react-native-menu/menu`. ([#45670](https://github.com/expo/expo/pull/45670) by [@vonovak](https://github.com/vonovak))
|
|
36
67
|
- [android] Added Compose `combinedClickable` modifier. ([#45670](https://github.com/expo/expo/pull/45670) by [@vonovak](https://github.com/vonovak))
|
|
68
|
+
- [universal] Added `Collapsible`, `List`, `ListItem`, and `Picker` components. ([#45754](https://github.com/expo/expo/pull/45754) by [@kudo](https://github.com/kudo))
|
|
69
|
+
- [universal] Added `snapPoints` prop on `BottomSheet` and `colorScheme` / `layoutDirection` props on `Host`. ([#45754](https://github.com/expo/expo/pull/45754) by [@kudo](https://github.com/kudo))
|
|
70
|
+
- [jetpack-compose] `background(color, { animationSpec })` accepts an optional `animationSpec` and wraps the color in `animateColorAsState` so changes between renders animate smoothly. ([#45754](https://github.com/expo/expo/pull/45754) by [@kudo](https://github.com/kudo))
|
|
37
71
|
|
|
38
72
|
### 🐛 Bug fixes
|
|
39
73
|
|
|
@@ -333,7 +367,7 @@ _This version does not introduce any user-facing changes._
|
|
|
333
367
|
|
|
334
368
|
- [iOS] Remove leftover `Switch` TypeScript exports from swift-ui package. Use `Toggle` instead. ([#42571](https://github.com/expo/expo/pull/42571) by [@shubh73](https://github.com/shubh73))
|
|
335
369
|
- Improved Jetpack Compose integration for Expo UI. ([#42450](https://github.com/expo/expo/pull/42450) by [@kudo](https://github.com/kudo))
|
|
336
|
-
- [iOS] Added `contentShape` modifier for SwiftUI ([#42813](https://github.com/expo/expo
|
|
370
|
+
- [iOS] Added `contentShape` modifier for SwiftUI ([#42813](https://github.com/expo/expo/pull/42813) by [@sam-shubham](https://github.com/sam-shubham))
|
|
337
371
|
|
|
338
372
|
## 55.0.0-beta.3 — 2026-01-27
|
|
339
373
|
|
package/CLAUDE.md
CHANGED
|
@@ -6,7 +6,7 @@ expo-ui is a library of native UI components for React Native, bridging SwiftUI
|
|
|
6
6
|
|
|
7
7
|
Bridge native components to JavaScript with as little abstraction as possible. Native views should be thin wrappers — no added logic, state management, or behavior beyond what the platform component provides. Everything that can be set or controlled from JavaScript should be controlled from JavaScript.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Mirror the native API shape. If the underlying SwiftUI / Compose component exposes imperative methods (e.g. SwiftUI's `ScrollViewProxy.scrollTo`, Compose's `PagerState.animateScrollToPage`), expose them as imperative methods in JS — don't paper them over with a controlled prop + sync layer just for the sake of a "React-y" API.
|
|
10
10
|
|
|
11
11
|
## Naming
|
|
12
12
|
|
package/android/build.gradle
CHANGED
|
@@ -12,13 +12,13 @@ apply plugin: 'expo-module-gradle-plugin'
|
|
|
12
12
|
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
|
|
13
13
|
|
|
14
14
|
group = 'expo.modules.ui'
|
|
15
|
-
version = '56.0.
|
|
15
|
+
version = '56.0.10'
|
|
16
16
|
|
|
17
17
|
android {
|
|
18
18
|
namespace "expo.modules.ui"
|
|
19
19
|
defaultConfig {
|
|
20
20
|
versionCode 1
|
|
21
|
-
versionName "56.0.
|
|
21
|
+
versionName "56.0.10"
|
|
22
22
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
23
23
|
}
|
|
24
24
|
buildFeatures {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
package expo.modules.ui
|
|
4
4
|
|
|
5
|
+
import android.os.Looper
|
|
5
6
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|
6
7
|
import androidx.compose.material3.SwitchDefaults
|
|
7
8
|
import androidx.compose.material3.ToggleButtonDefaults
|
|
@@ -39,6 +40,7 @@ import expo.modules.ui.menu.ExposedDropdownMenuBoxContent
|
|
|
39
40
|
import expo.modules.ui.menu.ExposedDropdownMenuBoxProps
|
|
40
41
|
import expo.modules.ui.menu.ExposedDropdownMenuContent
|
|
41
42
|
import expo.modules.ui.menu.ExposedDropdownMenuProps
|
|
43
|
+
import kotlinx.coroutines.launch
|
|
42
44
|
import okhttp3.OkHttpClient
|
|
43
45
|
|
|
44
46
|
class ExpoUIModule : Module() {
|
|
@@ -79,7 +81,20 @@ class ExpoUIModule : Module() {
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
Function("setValue") { state: ObservableState, wrapper: Map<String, Any?> ->
|
|
82
|
-
|
|
84
|
+
val newValue = wrapper["value"]
|
|
85
|
+
val mainLooper = Looper.getMainLooper()
|
|
86
|
+
// Update state on the UI thread
|
|
87
|
+
if (mainLooper.isCurrentThread) {
|
|
88
|
+
state.value = newValue
|
|
89
|
+
} else {
|
|
90
|
+
appContext.mainQueue.launch {
|
|
91
|
+
state.value = newValue
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Function("setOnChange") { state: ObservableState, callback: WorkletCallback? ->
|
|
97
|
+
state.onChange = callback
|
|
83
98
|
}
|
|
84
99
|
}
|
|
85
100
|
|
|
@@ -139,6 +154,8 @@ class ExpoUIModule : Module() {
|
|
|
139
154
|
// Class-based views so TooltipBoxView can detect them by type via findChildOfType
|
|
140
155
|
View(PlainTooltipView::class)
|
|
141
156
|
View(RichTooltipView::class)
|
|
157
|
+
// Class-based view so SnackbarHostView can read its styling via findChildOfType
|
|
158
|
+
View(SnackbarView::class)
|
|
142
159
|
|
|
143
160
|
//endregion Views use expo-modules-core DSL for uncommon features
|
|
144
161
|
|
|
@@ -340,6 +357,18 @@ class ExpoUIModule : Module() {
|
|
|
340
357
|
}
|
|
341
358
|
}
|
|
342
359
|
|
|
360
|
+
ExpoUIView<LoadingIndicatorProps>("LoadingIndicatorView") {
|
|
361
|
+
Content { props ->
|
|
362
|
+
LoadingIndicatorContent(props)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
ExpoUIView<ContainedLoadingIndicatorProps>("ContainedLoadingIndicatorView") {
|
|
367
|
+
Content { props ->
|
|
368
|
+
ContainedLoadingIndicatorContent(props)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
343
372
|
ExpoUIView<LinearProgressIndicatorProps>("LinearProgressIndicatorView") {
|
|
344
373
|
Content { props ->
|
|
345
374
|
LinearProgressIndicatorContent(props)
|
|
@@ -429,14 +458,20 @@ class ExpoUIModule : Module() {
|
|
|
429
458
|
val scrollToPage by AsyncFunction<Int>()
|
|
430
459
|
val onCurrentPageChange by Event<HorizontalPagerCurrentPageChangeEvent>()
|
|
431
460
|
val onSettledPageChange by Event<HorizontalPagerSettledPageChangeEvent>()
|
|
461
|
+
val onPageScroll by Event<HorizontalPagerPageScrollEvent>()
|
|
462
|
+
val onScrollInProgressChange by Event<HorizontalPagerScrollInProgressChangeEvent>()
|
|
463
|
+
val onDragInteraction by Event<HorizontalPagerDragInteractionEvent>()
|
|
432
464
|
|
|
433
465
|
Content { props ->
|
|
434
466
|
HorizontalPagerContent(
|
|
435
|
-
props,
|
|
436
|
-
animateScrollToPage,
|
|
437
|
-
scrollToPage,
|
|
438
|
-
{ onCurrentPageChange(it) },
|
|
439
|
-
{ onSettledPageChange(it) }
|
|
467
|
+
props = props,
|
|
468
|
+
animateScrollToPage = animateScrollToPage,
|
|
469
|
+
scrollToPage = scrollToPage,
|
|
470
|
+
onCurrentPageChange = { onCurrentPageChange(it) },
|
|
471
|
+
onSettledPageChange = { onSettledPageChange(it) },
|
|
472
|
+
onPageScroll = { onPageScroll(it) },
|
|
473
|
+
onScrollInProgressChange = { onScrollInProgressChange(it) },
|
|
474
|
+
onDragInteraction = { onDragInteraction(it) }
|
|
440
475
|
)
|
|
441
476
|
}
|
|
442
477
|
}
|
|
@@ -596,6 +631,14 @@ class ExpoUIModule : Module() {
|
|
|
596
631
|
}
|
|
597
632
|
}
|
|
598
633
|
|
|
634
|
+
ExpoUIView<SnackbarHostProps>("SnackbarHostView") {
|
|
635
|
+
val showSnackbar by AsyncFunction<SnackbarShowOptions>()
|
|
636
|
+
|
|
637
|
+
Content { props ->
|
|
638
|
+
SnackbarHostContent(props, showSnackbar)
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
599
642
|
ExpoUIView<TooltipBoxViewProps>("TooltipBoxView") {
|
|
600
643
|
val show by AsyncFunction()
|
|
601
644
|
val dismiss by AsyncFunction()
|
|
@@ -2,6 +2,7 @@ package expo.modules.ui
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import android.view.ViewGroup
|
|
5
|
+
import androidx.compose.foundation.interaction.DragInteraction
|
|
5
6
|
import androidx.compose.foundation.pager.HorizontalPager
|
|
6
7
|
import androidx.compose.foundation.pager.rememberPagerState
|
|
7
8
|
import androidx.compose.runtime.Composable
|
|
@@ -23,6 +24,7 @@ import expo.modules.kotlin.views.FunctionalComposableScope
|
|
|
23
24
|
import expo.modules.kotlin.types.Either
|
|
24
25
|
import expo.modules.kotlin.types.OptimizedRecord
|
|
25
26
|
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
27
|
+
import expo.modules.ui.state.WorkletCallback
|
|
26
28
|
|
|
27
29
|
@OptimizedRecord
|
|
28
30
|
data class HorizontalPagerCurrentPageChangeEvent(
|
|
@@ -34,6 +36,22 @@ data class HorizontalPagerSettledPageChangeEvent(
|
|
|
34
36
|
@Field val position: Int = 0
|
|
35
37
|
) : Record
|
|
36
38
|
|
|
39
|
+
@OptimizedRecord
|
|
40
|
+
data class HorizontalPagerPageScrollEvent(
|
|
41
|
+
@Field val currentPage: Int = 0,
|
|
42
|
+
@Field val currentPageOffsetFraction: Float = 0f
|
|
43
|
+
) : Record
|
|
44
|
+
|
|
45
|
+
@OptimizedRecord
|
|
46
|
+
data class HorizontalPagerScrollInProgressChangeEvent(
|
|
47
|
+
@Field val isScrollInProgress: Boolean = false
|
|
48
|
+
) : Record
|
|
49
|
+
|
|
50
|
+
@OptimizedRecord
|
|
51
|
+
data class HorizontalPagerDragInteractionEvent(
|
|
52
|
+
@Field val kind: String = "start"
|
|
53
|
+
) : Record
|
|
54
|
+
|
|
37
55
|
@OptimizedComposeProps
|
|
38
56
|
data class HorizontalPagerProps(
|
|
39
57
|
val initialPage: Int = 0,
|
|
@@ -42,6 +60,7 @@ data class HorizontalPagerProps(
|
|
|
42
60
|
val userScrollEnabled: Boolean = true,
|
|
43
61
|
val reverseLayout: Boolean = false,
|
|
44
62
|
val beyondViewportPageCount: Int = 0,
|
|
63
|
+
val onPageScrollSync: WorkletCallback? = null,
|
|
45
64
|
val modifiers: ModifierList = emptyList()
|
|
46
65
|
) : ComposeProps
|
|
47
66
|
|
|
@@ -51,7 +70,10 @@ fun FunctionalComposableScope.HorizontalPagerContent(
|
|
|
51
70
|
animateScrollToPage: AsyncFunctionHandle<Int>,
|
|
52
71
|
scrollToPage: AsyncFunctionHandle<Int>,
|
|
53
72
|
onCurrentPageChange: (HorizontalPagerCurrentPageChangeEvent) -> Unit,
|
|
54
|
-
onSettledPageChange: (HorizontalPagerSettledPageChangeEvent) -> Unit
|
|
73
|
+
onSettledPageChange: (HorizontalPagerSettledPageChangeEvent) -> Unit,
|
|
74
|
+
onPageScroll: (HorizontalPagerPageScrollEvent) -> Unit,
|
|
75
|
+
onScrollInProgressChange: (HorizontalPagerScrollInProgressChangeEvent) -> Unit,
|
|
76
|
+
onDragInteraction: (HorizontalPagerDragInteractionEvent) -> Unit
|
|
55
77
|
) {
|
|
56
78
|
// Mirror view.size into snapshot state so the outer scope recomposes when
|
|
57
79
|
// children are added/removed. Without this, Compose's pager caches its
|
|
@@ -59,6 +81,9 @@ fun FunctionalComposableScope.HorizontalPagerContent(
|
|
|
59
81
|
// and crashes on scroll past the last index it knew about, because reading
|
|
60
82
|
// view.size — a plain Java property — registers no snapshot dependency.
|
|
61
83
|
val pageCountState = remember { mutableIntStateOf(view.size) }
|
|
84
|
+
// Assumes sole ownership of `view`'s OnHierarchyChangeListener — there is
|
|
85
|
+
// only one slot per ViewGroup, and `onDispose` resets it to null. Safe
|
|
86
|
+
// because `view` is the expo Host's private container.
|
|
62
87
|
DisposableEffect(view) {
|
|
63
88
|
view.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
|
|
64
89
|
override fun onChildViewAdded(parent: View?, child: View?) {
|
|
@@ -72,35 +97,91 @@ fun FunctionalComposableScope.HorizontalPagerContent(
|
|
|
72
97
|
onDispose { view.setOnHierarchyChangeListener(null) }
|
|
73
98
|
}
|
|
74
99
|
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
// Register the imperative handles before the empty-children early return, so
|
|
101
|
+
// in case there ever are JS calls dispatched while `view.size` is still 0, they bind to a handler.
|
|
102
|
+
// `coerceAtLeast(0)` guards against `coerceIn(0, -1)` throwing in that gap.
|
|
77
103
|
val pagerState = rememberPagerState(
|
|
78
|
-
initialPage = props.initialPage.
|
|
104
|
+
initialPage = props.initialPage.coerceAtLeast(0)
|
|
79
105
|
) { pageCountState.intValue }
|
|
80
106
|
val scope = rememberCoroutineScope()
|
|
81
107
|
|
|
82
108
|
// Dispatch into the composition's scope so the scroll runs with Compose's
|
|
83
109
|
// MonotonicFrameClock; .join() lets the JS-side promise await completion.
|
|
110
|
+
// Clamp page indices here because Compose throws on out-of-range values.
|
|
84
111
|
animateScrollToPage.handle { page ->
|
|
85
|
-
|
|
112
|
+
val count = pageCountState.intValue
|
|
113
|
+
if (count > 0) {
|
|
114
|
+
val clamped = page.coerceIn(0, count - 1)
|
|
115
|
+
scope.launch { pagerState.animateScrollToPage(clamped) }.join()
|
|
116
|
+
}
|
|
86
117
|
}
|
|
87
118
|
|
|
88
119
|
scrollToPage.handle { page ->
|
|
89
|
-
|
|
120
|
+
val count = pageCountState.intValue
|
|
121
|
+
if (count > 0) {
|
|
122
|
+
val clamped = page.coerceIn(0, count - 1)
|
|
123
|
+
scope.launch { pagerState.scrollToPage(clamped) }.join()
|
|
124
|
+
}
|
|
90
125
|
}
|
|
91
126
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
LaunchedEffect(pagerState) {
|
|
95
|
-
snapshotFlow { pagerState.currentPage }
|
|
96
|
-
.drop(1)
|
|
97
|
-
.collect { onCurrentPageChange(HorizontalPagerCurrentPageChangeEvent(it)) }
|
|
98
|
-
}
|
|
127
|
+
val pageCount = pageCountState.intValue
|
|
128
|
+
if (pageCount == 0) return
|
|
99
129
|
|
|
130
|
+
// Mirror Compose's PagerState observable fields to JS callbacks. Each
|
|
131
|
+
// state-backed snapshotFlow drops its first emission so we don't echo the
|
|
132
|
+
// initial value back on mount; the interactionSource flow doesn't drop
|
|
133
|
+
// because its emissions are discrete events, not state values.
|
|
100
134
|
LaunchedEffect(pagerState) {
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
135
|
+
launch {
|
|
136
|
+
snapshotFlow { pagerState.currentPage }
|
|
137
|
+
.drop(1)
|
|
138
|
+
.collect { onCurrentPageChange(HorizontalPagerCurrentPageChangeEvent(it)) }
|
|
139
|
+
}
|
|
140
|
+
launch {
|
|
141
|
+
snapshotFlow { pagerState.settledPage }
|
|
142
|
+
.drop(1)
|
|
143
|
+
.collect { onSettledPageChange(HorizontalPagerSettledPageChangeEvent(it)) }
|
|
144
|
+
}
|
|
145
|
+
launch {
|
|
146
|
+
snapshotFlow {
|
|
147
|
+
pagerState.currentPage to pagerState.currentPageOffsetFraction
|
|
148
|
+
}
|
|
149
|
+
.drop(1)
|
|
150
|
+
.collect { (currentPage, fraction) ->
|
|
151
|
+
// Mutually exclusive: the JS wrapper only wires one path at a time.
|
|
152
|
+
// Skipping the regular event when a worklet is attached avoids the
|
|
153
|
+
// per-frame Record allocation + async JS-thread event dispatch.
|
|
154
|
+
val sync = props.onPageScrollSync
|
|
155
|
+
if (sync != null) {
|
|
156
|
+
sync.invoke(currentPage, fraction)
|
|
157
|
+
} else {
|
|
158
|
+
onPageScroll(
|
|
159
|
+
HorizontalPagerPageScrollEvent(
|
|
160
|
+
currentPage = currentPage,
|
|
161
|
+
currentPageOffsetFraction = fraction
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
launch {
|
|
168
|
+
snapshotFlow { pagerState.isScrollInProgress }
|
|
169
|
+
.drop(1)
|
|
170
|
+
.collect {
|
|
171
|
+
onScrollInProgressChange(HorizontalPagerScrollInProgressChangeEvent(isScrollInProgress = it))
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
launch {
|
|
175
|
+
pagerState.interactionSource.interactions.collect { interaction ->
|
|
176
|
+
val kind = when (interaction) {
|
|
177
|
+
is DragInteraction.Start -> "start"
|
|
178
|
+
is DragInteraction.Stop -> "stop"
|
|
179
|
+
is DragInteraction.Cancel -> "cancel"
|
|
180
|
+
else -> return@collect
|
|
181
|
+
}
|
|
182
|
+
onDragInteraction(HorizontalPagerDragInteractionEvent(kind = kind))
|
|
183
|
+
}
|
|
184
|
+
}
|
|
104
185
|
}
|
|
105
186
|
|
|
106
187
|
val contentPadding = props.contentPadding.toPaddingValues()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
|
2
|
+
|
|
3
|
+
package expo.modules.ui
|
|
4
|
+
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import androidx.compose.material3.ContainedLoadingIndicator
|
|
7
|
+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|
8
|
+
import androidx.compose.material3.LoadingIndicator
|
|
9
|
+
import androidx.compose.material3.LoadingIndicatorDefaults
|
|
10
|
+
import androidx.compose.runtime.Composable
|
|
11
|
+
import expo.modules.kotlin.views.ComposeProps
|
|
12
|
+
import expo.modules.kotlin.views.FunctionalComposableScope
|
|
13
|
+
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
14
|
+
import expo.modules.ui.state.ObservableState
|
|
15
|
+
|
|
16
|
+
// region LoadingIndicator
|
|
17
|
+
|
|
18
|
+
@OptimizedComposeProps
|
|
19
|
+
data class LoadingIndicatorProps(
|
|
20
|
+
val progress: ObservableState? = null,
|
|
21
|
+
val color: Color? = null,
|
|
22
|
+
val modifiers: ModifierList = emptyList()
|
|
23
|
+
) : ComposeProps
|
|
24
|
+
|
|
25
|
+
@Composable
|
|
26
|
+
fun FunctionalComposableScope.LoadingIndicatorContent(props: LoadingIndicatorProps) {
|
|
27
|
+
val modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
|
|
28
|
+
val indicatorColor = props.color.composeOrNull ?: LoadingIndicatorDefaults.indicatorColor
|
|
29
|
+
|
|
30
|
+
val progressState = props.progress
|
|
31
|
+
if (progressState != null) {
|
|
32
|
+
LoadingIndicator(
|
|
33
|
+
progress = { (progressState.value as? Number)?.toFloat() ?: 0f },
|
|
34
|
+
modifier = modifier,
|
|
35
|
+
color = indicatorColor
|
|
36
|
+
)
|
|
37
|
+
} else {
|
|
38
|
+
LoadingIndicator(
|
|
39
|
+
modifier = modifier,
|
|
40
|
+
color = indicatorColor
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// endregion
|
|
46
|
+
|
|
47
|
+
// region ContainedLoadingIndicator
|
|
48
|
+
|
|
49
|
+
@OptimizedComposeProps
|
|
50
|
+
data class ContainedLoadingIndicatorProps(
|
|
51
|
+
val progress: ObservableState? = null,
|
|
52
|
+
val color: Color? = null,
|
|
53
|
+
val containerColor: Color? = null,
|
|
54
|
+
val modifiers: ModifierList = emptyList()
|
|
55
|
+
) : ComposeProps
|
|
56
|
+
|
|
57
|
+
@Composable
|
|
58
|
+
fun FunctionalComposableScope.ContainedLoadingIndicatorContent(props: ContainedLoadingIndicatorProps) {
|
|
59
|
+
val modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
|
|
60
|
+
val indicatorColor = props.color.composeOrNull ?: LoadingIndicatorDefaults.containedIndicatorColor
|
|
61
|
+
val containerColor = props.containerColor.composeOrNull ?: LoadingIndicatorDefaults.containedContainerColor
|
|
62
|
+
|
|
63
|
+
val progressState = props.progress
|
|
64
|
+
if (progressState != null) {
|
|
65
|
+
ContainedLoadingIndicator(
|
|
66
|
+
progress = { (progressState.value as? Number)?.toFloat() ?: 0f },
|
|
67
|
+
modifier = modifier,
|
|
68
|
+
indicatorColor = indicatorColor,
|
|
69
|
+
containerColor = containerColor
|
|
70
|
+
)
|
|
71
|
+
} else {
|
|
72
|
+
ContainedLoadingIndicator(
|
|
73
|
+
modifier = modifier,
|
|
74
|
+
indicatorColor = indicatorColor,
|
|
75
|
+
containerColor = containerColor
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// endregion
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
package expo.modules.ui
|
|
4
4
|
|
|
5
5
|
import android.graphics.Color
|
|
6
|
+
import androidx.compose.animation.animateColorAsState
|
|
6
7
|
import androidx.compose.animation.animateContentSize
|
|
8
|
+
import androidx.compose.animation.core.AnimationSpec
|
|
7
9
|
import androidx.compose.animation.core.Spring
|
|
10
|
+
import androidx.compose.animation.core.snap
|
|
8
11
|
import androidx.compose.animation.core.spring
|
|
12
|
+
import androidx.compose.animation.core.tween
|
|
9
13
|
import androidx.compose.foundation.BorderStroke
|
|
10
14
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
11
15
|
import androidx.compose.foundation.background
|
|
@@ -38,6 +42,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|
|
38
42
|
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
|
39
43
|
import androidx.compose.material3.toShape
|
|
40
44
|
import androidx.compose.runtime.Composable
|
|
45
|
+
import androidx.compose.runtime.getValue
|
|
41
46
|
import androidx.compose.ui.Modifier
|
|
42
47
|
import androidx.compose.ui.draw.alpha
|
|
43
48
|
import androidx.compose.ui.draw.blur
|
|
@@ -147,6 +152,24 @@ internal data class BackgroundParams(
|
|
|
147
152
|
@Field val color: Color? = null
|
|
148
153
|
) : Record
|
|
149
154
|
|
|
155
|
+
// Color animation specs reuse the JS `$type` shape from `@expo/ui/jetpack-compose/modifiers/animation` (spring / tween / snap).
|
|
156
|
+
// Keyframes are float-only and aren't supported for colors.
|
|
157
|
+
private fun parseColorAnimationSpec(raw: Any?): AnimationSpec<androidx.compose.ui.graphics.Color>? {
|
|
158
|
+
if (raw !is Map<*, *>) return null
|
|
159
|
+
return when (raw["\$type"]) {
|
|
160
|
+
"spring" -> spring(
|
|
161
|
+
dampingRatio = (raw["dampingRatio"] as? Number)?.toFloat() ?: Spring.DampingRatioNoBouncy,
|
|
162
|
+
stiffness = (raw["stiffness"] as? Number)?.toFloat() ?: Spring.StiffnessMedium
|
|
163
|
+
)
|
|
164
|
+
"tween" -> tween(
|
|
165
|
+
durationMillis = (raw["durationMillis"] as? Number)?.toInt() ?: 300,
|
|
166
|
+
delayMillis = (raw["delayMillis"] as? Number)?.toInt() ?: 0
|
|
167
|
+
)
|
|
168
|
+
"snap" -> snap(delayMillis = (raw["delayMillis"] as? Number)?.toInt() ?: 0)
|
|
169
|
+
else -> null
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
150
173
|
@OptimizedRecord
|
|
151
174
|
internal data class BorderParams(
|
|
152
175
|
@Field val borderWidth: Int = 1,
|
|
@@ -457,9 +480,14 @@ object ModifierRegistry {
|
|
|
457
480
|
// Appearance modifiers
|
|
458
481
|
register("background") { map, _, _, _ ->
|
|
459
482
|
val params = recordFromMap<BackgroundParams>(map)
|
|
460
|
-
params.color?.
|
|
461
|
-
|
|
462
|
-
|
|
483
|
+
val color = params.color?.compose ?: return@register Modifier
|
|
484
|
+
val spec = parseColorAnimationSpec(map["animationSpec"])
|
|
485
|
+
if (spec != null) {
|
|
486
|
+
val animated by animateColorAsState(color, spec, label = "background-color")
|
|
487
|
+
Modifier.background(animated)
|
|
488
|
+
} else {
|
|
489
|
+
Modifier.background(color)
|
|
490
|
+
}
|
|
463
491
|
}
|
|
464
492
|
|
|
465
493
|
register("border") { map, _, _, _ ->
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
@file:OptIn(ExperimentalMaterial3Api::class)
|
|
2
|
+
|
|
3
|
+
package expo.modules.ui
|
|
4
|
+
|
|
5
|
+
import android.annotation.SuppressLint
|
|
6
|
+
import android.content.Context
|
|
7
|
+
import android.graphics.Color
|
|
8
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
9
|
+
import androidx.compose.material3.Snackbar
|
|
10
|
+
import androidx.compose.material3.SnackbarDefaults
|
|
11
|
+
import androidx.compose.material3.SnackbarDuration
|
|
12
|
+
import androidx.compose.material3.SnackbarHost
|
|
13
|
+
import androidx.compose.material3.SnackbarHostState
|
|
14
|
+
import androidx.compose.material3.SnackbarResult
|
|
15
|
+
import androidx.compose.runtime.Composable
|
|
16
|
+
import androidx.compose.runtime.MutableState
|
|
17
|
+
import androidx.compose.runtime.mutableStateOf
|
|
18
|
+
import androidx.compose.runtime.remember
|
|
19
|
+
import androidx.compose.runtime.rememberCoroutineScope
|
|
20
|
+
import expo.modules.kotlin.AppContext
|
|
21
|
+
import expo.modules.kotlin.records.Field
|
|
22
|
+
import expo.modules.kotlin.records.Record
|
|
23
|
+
import expo.modules.kotlin.types.OptimizedRecord
|
|
24
|
+
import expo.modules.kotlin.views.AsyncFunctionHandle
|
|
25
|
+
import expo.modules.kotlin.views.ComposableScope
|
|
26
|
+
import expo.modules.kotlin.views.ComposeProps
|
|
27
|
+
import expo.modules.kotlin.views.ExpoComposeView
|
|
28
|
+
import expo.modules.kotlin.views.FunctionalComposableScope
|
|
29
|
+
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
30
|
+
import kotlinx.coroutines.withContext
|
|
31
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
32
|
+
|
|
33
|
+
// Holds styling props that `SnackbarHost` reads via `findChildOfType`.
|
|
34
|
+
|
|
35
|
+
@OptimizedComposeProps
|
|
36
|
+
data class SnackbarViewProps(
|
|
37
|
+
val containerColor: MutableState<Color?> = mutableStateOf(null),
|
|
38
|
+
val contentColor: MutableState<Color?> = mutableStateOf(null),
|
|
39
|
+
val actionContentColor: MutableState<Color?> = mutableStateOf(null),
|
|
40
|
+
val dismissActionContentColor: MutableState<Color?> = mutableStateOf(null),
|
|
41
|
+
val actionOnNewLine: MutableState<Boolean> = mutableStateOf(false),
|
|
42
|
+
val modifiers: MutableState<ModifierList> = mutableStateOf(emptyList())
|
|
43
|
+
) : ComposeProps
|
|
44
|
+
|
|
45
|
+
@SuppressLint("ViewConstructor")
|
|
46
|
+
class SnackbarView(context: Context, appContext: AppContext) :
|
|
47
|
+
ExpoComposeView<SnackbarViewProps>(context, appContext) {
|
|
48
|
+
override val props = SnackbarViewProps()
|
|
49
|
+
|
|
50
|
+
@Composable
|
|
51
|
+
override fun ComposableScope.Content() {
|
|
52
|
+
// Empty by design: `SnackbarHost` renders the snackbar using the props above.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- SnackbarHostView ---
|
|
57
|
+
|
|
58
|
+
@OptimizedRecord
|
|
59
|
+
data class SnackbarShowOptions(
|
|
60
|
+
@Field val message: String = "",
|
|
61
|
+
@Field val actionLabel: String? = null,
|
|
62
|
+
@Field val withDismissAction: Boolean = false,
|
|
63
|
+
@Field val duration: String? = null
|
|
64
|
+
) : Record
|
|
65
|
+
|
|
66
|
+
@OptimizedComposeProps
|
|
67
|
+
data class SnackbarHostProps(
|
|
68
|
+
val modifiers: ModifierList = emptyList()
|
|
69
|
+
) : ComposeProps
|
|
70
|
+
|
|
71
|
+
@Composable
|
|
72
|
+
fun FunctionalComposableScope.SnackbarHostContent(
|
|
73
|
+
props: SnackbarHostProps,
|
|
74
|
+
showSnackbar: AsyncFunctionHandle<SnackbarShowOptions>
|
|
75
|
+
) {
|
|
76
|
+
val hostState = remember { SnackbarHostState() }
|
|
77
|
+
val scope = rememberCoroutineScope()
|
|
78
|
+
val snackbarConfig = findChildOfType<SnackbarView>(view)
|
|
79
|
+
|
|
80
|
+
showSnackbar.handle { options ->
|
|
81
|
+
val duration = when (options.duration) {
|
|
82
|
+
"short" -> SnackbarDuration.Short
|
|
83
|
+
"long" -> SnackbarDuration.Long
|
|
84
|
+
"indefinite" -> SnackbarDuration.Indefinite
|
|
85
|
+
// M3 default: indefinite when there's an action label, else short.
|
|
86
|
+
else -> if (options.actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite
|
|
87
|
+
}
|
|
88
|
+
val result = try {
|
|
89
|
+
withContext(scope.coroutineContext) {
|
|
90
|
+
hostState.showSnackbar(
|
|
91
|
+
message = options.message,
|
|
92
|
+
actionLabel = options.actionLabel,
|
|
93
|
+
withDismissAction = options.withDismissAction,
|
|
94
|
+
duration = duration
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
} catch (_: CancellationException) {
|
|
98
|
+
// The compose scope can be cancelled if the view is disposed before user dismisses the snackbar or performs the action.
|
|
99
|
+
// In that case, we treat it as if the snackbar was dismissed.
|
|
100
|
+
SnackbarResult.Dismissed
|
|
101
|
+
}
|
|
102
|
+
when (result) {
|
|
103
|
+
SnackbarResult.ActionPerformed -> "actionPerformed"
|
|
104
|
+
SnackbarResult.Dismissed -> "dismissed"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
SnackbarHost(
|
|
109
|
+
hostState = hostState,
|
|
110
|
+
modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
|
|
111
|
+
) { data ->
|
|
112
|
+
Snackbar(
|
|
113
|
+
snackbarData = data,
|
|
114
|
+
modifier = snackbarConfig?.let {
|
|
115
|
+
ModifierRegistry.applyModifiers(it.props.modifiers.value, appContext, composableScope, globalEventDispatcher)
|
|
116
|
+
} ?: androidx.compose.ui.Modifier,
|
|
117
|
+
actionOnNewLine = snackbarConfig?.props?.actionOnNewLine?.value ?: false,
|
|
118
|
+
containerColor = snackbarConfig?.props?.containerColor?.value.composeOrNull ?: SnackbarDefaults.color,
|
|
119
|
+
contentColor = snackbarConfig?.props?.contentColor?.value.composeOrNull ?: SnackbarDefaults.contentColor,
|
|
120
|
+
actionContentColor = snackbarConfig?.props?.actionContentColor?.value.composeOrNull
|
|
121
|
+
?: SnackbarDefaults.actionContentColor,
|
|
122
|
+
dismissActionContentColor = snackbarConfig?.props?.dismissActionContentColor?.value.composeOrNull
|
|
123
|
+
?: SnackbarDefaults.dismissActionContentColor
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -13,6 +13,7 @@ import com.google.android.material.color.utilities.MaterialDynamicColors
|
|
|
13
13
|
import com.google.android.material.color.utilities.SchemeTonalSpot
|
|
14
14
|
import expo.modules.kotlin.records.Field
|
|
15
15
|
import expo.modules.kotlin.records.Record
|
|
16
|
+
import expo.modules.kotlin.types.OptimizedRecord
|
|
16
17
|
import expo.modules.ui.ExpoColorScheme
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -21,6 +22,7 @@ import expo.modules.ui.ExpoColorScheme
|
|
|
21
22
|
*/
|
|
22
23
|
internal val isDynamicColorSupported: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
|
23
24
|
|
|
25
|
+
@OptimizedRecord
|
|
24
26
|
internal class MaterialColorsOptions : Record {
|
|
25
27
|
@Field val scheme: ExpoColorScheme? = null
|
|
26
28
|
|
|
@@ -11,11 +11,21 @@ import expo.modules.kotlin.sharedobjects.SharedObject
|
|
|
11
11
|
*/
|
|
12
12
|
class ObservableState(initialValue: Any? = null) : SharedObject() {
|
|
13
13
|
private val _state: MutableState<Any?> = mutableStateOf(initialValue)
|
|
14
|
+
internal var onChange: WorkletCallback? = null
|
|
15
|
+
private var isNotifying = false
|
|
14
16
|
|
|
15
17
|
var value: Any?
|
|
16
18
|
get() = _state.value
|
|
17
19
|
set(v) {
|
|
18
20
|
_state.value = v
|
|
21
|
+
// Skip re-invoking onChange if state.value was written from inside onChange.
|
|
22
|
+
if (isNotifying) return
|
|
23
|
+
isNotifying = true
|
|
24
|
+
try {
|
|
25
|
+
onChange?.invoke(v)
|
|
26
|
+
} finally {
|
|
27
|
+
isNotifying = false
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
@Suppress("UNCHECKED_CAST")
|