@expo/ui 56.0.7 → 56.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -1
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +44 -3
- package/android/src/main/java/expo/modules/ui/HostView.kt +2 -0
- package/android/src/main/java/expo/modules/ui/LoadingView.kt +80 -0
- package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +50 -4
- package/android/src/main/java/expo/modules/ui/RNHostView.kt +8 -3
- package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +28 -0
- package/android/src/main/java/expo/modules/ui/SnackbarView.kt +126 -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/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/menu/MenuView.android.d.ts +16 -0
- package/build/community/menu/MenuView.android.d.ts.map +1 -0
- package/build/community/menu/MenuView.d.ts +19 -0
- package/build/community/menu/MenuView.d.ts.map +1 -0
- package/build/community/menu/MenuView.ios.d.ts +10 -0
- package/build/community/menu/MenuView.ios.d.ts.map +1 -0
- package/build/community/menu/index.d.ts +5 -0
- package/build/community/menu/index.d.ts.map +1 -0
- package/build/community/menu/types.d.ts +166 -0
- package/build/community/menu/types.d.ts.map +1 -0
- 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/index.d.ts +2 -0
- package/build/jetpack-compose/index.d.ts.map +1 -1
- package/build/jetpack-compose/modifiers/index.d.ts +21 -2
- package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/Alert/index.d.ts +42 -0
- package/build/swift-ui/Alert/index.d.ts.map +1 -0
- 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/SlotView.d.ts +5 -2
- package/build/swift-ui/SlotView.d.ts.map +1 -1
- package/build/swift-ui/SwipeActions/index.d.ts +38 -0
- package/build/swift-ui/SwipeActions/index.d.ts.map +1 -0
- package/build/swift-ui/index.d.ts +3 -0
- package/build/swift-ui/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +3 -1
- package/build/swift-ui/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/symbolEffect.d.ts +103 -0
- package/build/swift-ui/modifiers/symbolEffect.d.ts.map +1 -0
- 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/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 -7
- 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/index.d.ts +4 -0
- package/build/universal/index.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/Alert/Alert.swift +56 -0
- package/ios/Alert/AlertProps.swift +8 -0
- package/ios/BottomSheetView.swift +4 -1
- package/ios/ExpoUIModule.swift +43 -1
- package/ios/ExpoUITouchHandlerHelper.h +4 -1
- package/ios/ExpoUITouchHandlerHelper.mm +1 -0
- package/ios/Modifiers/AnimationConfig.swift +109 -0
- package/ios/Modifiers/SwipeActionsModifier.swift +97 -0
- package/ios/Modifiers/SymbolEffectModifier.swift +452 -0
- package/ios/Modifiers/ViewModifierRegistry.swift +5 -112
- package/ios/SlotView.swift +5 -0
- package/ios/State/ObservableState.swift +12 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7-sources.jar → 56.0.9/expo.modules.ui-56.0.9-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7.module → 56.0.9/expo.modules.ui-56.0.9.module} +22 -22
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7.pom → 56.0.9/expo.modules.ui-56.0.9.pom} +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.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/useNativeState.ts +70 -10
- package/src/community/bottom-sheet/BottomSheet.ios.tsx +0 -17
- package/src/community/menu/MenuView.android.tsx +224 -0
- package/src/community/menu/MenuView.ios.tsx +149 -0
- package/src/community/menu/MenuView.tsx +36 -0
- package/src/community/menu/index.tsx +14 -0
- package/src/community/menu/types.tsx +171 -0
- package/src/jetpack-compose/LoadingIndicator/index.tsx +92 -0
- package/src/jetpack-compose/Snackbar/index.tsx +135 -0
- package/src/jetpack-compose/index.ts +2 -0
- package/src/jetpack-compose/modifiers/index.ts +30 -2
- package/src/swift-ui/Alert/index.tsx +87 -0
- package/src/swift-ui/BottomSheet/index.tsx +32 -15
- package/src/swift-ui/SlotView.tsx +17 -4
- package/src/swift-ui/SwipeActions/index.tsx +73 -0
- package/src/swift-ui/index.tsx +3 -0
- package/src/swift-ui/modifiers/index.ts +3 -0
- package/src/swift-ui/modifiers/symbolEffect.ts +181 -0
- package/src/swift-ui/withAnimation.ts +71 -0
- package/src/ts-declarations/react-native-web.d.ts +27 -0
- package/src/universal/BottomSheet/index.android.tsx +27 -3
- package/src/universal/BottomSheet/index.ios.tsx +30 -12
- package/src/universal/BottomSheet/index.tsx +46 -4
- package/src/universal/BottomSheet/types.ts +25 -0
- package/src/universal/Collapsible/index.android.tsx +72 -0
- package/src/universal/Collapsible/index.ios.tsx +16 -0
- package/src/universal/Collapsible/index.tsx +58 -0
- package/src/universal/Collapsible/types.ts +25 -0
- package/src/universal/Column/index.tsx +3 -1
- package/src/universal/Host/index.tsx +69 -5
- 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 +72 -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/index.ts +4 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha512 +0 -1
- package/src/community/bottom-sheet/CLAUDE.md +0 -55
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,36 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.9 — 2026-05-19
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- [iOS][android] Added `onChange` listener to `useNativeState`. ([#45961](https://github.com/expo/expo/pull/45961) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
18
|
+
- Allow writing to native state from the JS thread. ([#45901](https://github.com/expo/expo/pull/45901) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
19
|
+
- [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))
|
|
20
|
+
- [jetpack-compose] Added `Snackbar` component. ([#45667](https://github.com/expo/expo/pull/45667) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
21
|
+
- [android] Added `LoadingIndicator` and `ContainedLoadingIndicator` components. ([#41169](https://github.com/expo/expo/pull/41169) by [@suveshmoza](https://github.com/suveshmoza))
|
|
22
|
+
|
|
23
|
+
### 🐛 Bug fixes
|
|
24
|
+
|
|
25
|
+
- [iOS] Unmount `BottomSheet` when it is dismissed. ([#45846](https://github.com/expo/expo/pull/45846) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
26
|
+
|
|
27
|
+
### 💡 Others
|
|
28
|
+
|
|
29
|
+
- [universal] Add base styling to universal Picker on web ([#45932](https://github.com/expo/expo/pull/45932) by [@zoontek](https://github.com/zoontek))
|
|
30
|
+
|
|
31
|
+
## 56.0.8 — 2026-05-15
|
|
32
|
+
|
|
33
|
+
### 🎉 New features
|
|
34
|
+
|
|
35
|
+
- [universal] Added `matchContents`, `layoutDirection`, `onLayoutContent`, `useViewportSizeMeasurement`, and `ignoreSafeArea` support to the universal `Host`. ([#45776](https://github.com/expo/expo/pull/45776) by [@zoontek](https://github.com/zoontek))
|
|
36
|
+
- [iOS] Added `symbolEffect` modifier. ([#45727](https://github.com/expo/expo/pull/45727) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
37
|
+
- [iOS] Added `Alert` component wrapping SwiftUI's `.alert(_:isPresented:actions:message:)` modifier, with `Alert.Trigger`, `Alert.Actions`, and optional `Alert.Message` slots. Mirrors the existing `ConfirmationDialog` shape so it composes the same way with `Button` actions and `isPresented` bindings. ([#45700](https://github.com/expo/expo/pull/45700) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
38
|
+
|
|
39
|
+
### 💡 Others
|
|
40
|
+
|
|
41
|
+
- Fixed precompile xcframework warning. ([#45762](https://github.com/expo/expo/pull/45762) by [@kudo](https://github.com/kudo))
|
|
42
|
+
|
|
13
43
|
## 56.0.7 — 2026-05-13
|
|
14
44
|
|
|
15
45
|
_This version does not introduce any user-facing changes._
|
|
@@ -20,10 +50,16 @@ _This version does not introduce any user-facing changes._
|
|
|
20
50
|
|
|
21
51
|
- Added `@expo/ui/community/slider`, a drop-in replacement for `@react-native-community/slider`. ([#45623](https://github.com/expo/expo/pull/45623) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
22
52
|
- Make `ChartView` public. ([#45674](https://github.com/expo/expo/pull/45674) by [@jakex7](https://github.com/jakex7))
|
|
53
|
+
- 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))
|
|
54
|
+
- [android] Added Compose `combinedClickable` modifier. ([#45670](https://github.com/expo/expo/pull/45670) by [@vonovak](https://github.com/vonovak))
|
|
55
|
+
- [universal] Added `Collapsible`, `List`, `ListItem`, and `Picker` components. ([#45754](https://github.com/expo/expo/pull/45754) by [@kudo](https://github.com/kudo))
|
|
56
|
+
- [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))
|
|
57
|
+
- [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))
|
|
23
58
|
|
|
24
59
|
### 🐛 Bug fixes
|
|
25
60
|
|
|
26
61
|
- Fix `useNativeState` recreating the `ObservableState` when initial value changes; the seed is now captured once via `useRef`. ([#45623](https://github.com/expo/expo/pull/45623) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
62
|
+
- [Android] Fix layout shift in `Host` with `matchContents`. ([#45775](https://github.com/expo/expo/pull/45775) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
27
63
|
|
|
28
64
|
## 56.0.5 — 2026-05-11
|
|
29
65
|
|
|
@@ -87,6 +123,7 @@ _This version does not introduce any user-facing changes._
|
|
|
87
123
|
- [iOS] Add `WorkletCallback` shared object for synchronous UI thread callbacks. ([#44216](https://github.com/expo/expo/pull/44216) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
88
124
|
- [iOS] Added `scrollPosition` and `id` modifiers for tracking and scrolling to view-aligned targets in `ScrollView` and other scrollable containers (iOS 17+). ([#44652](https://github.com/expo/expo/pull/44652) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
89
125
|
- Added `@expo/ui/datetimepicker` — an Android and iOS `DateTimePicker` drop-in replacement for `@react-native-community/datetimepicker`. ([#44014](https://github.com/expo/expo/pull/44014) by [@vonovak](https://github.com/vonovak))
|
|
126
|
+
- [iOS] Added `SwipeActions` component. ([#44689](https://github.com/expo/expo/pull/44689) by [@yousofabouhalawa](https://github.com/yousofabouhalawa))
|
|
90
127
|
- [swift-ui] Added `LazyHStack` and `LazyVStack`. ([#44612](https://github.com/expo/expo/pull/44612) by [@kudo](https://github.com/kudo))
|
|
91
128
|
- [jetpack-compose] Added `LazyRow` component and `onVisibilityChanged` modifier. ([#44615](https://github.com/expo/expo/pull/44615) by [@kudo](https://github.com/kudo))
|
|
92
129
|
- Added universal components. ([#44601](https://github.com/expo/expo/pull/44601) by [@kudo](https://github.com/kudo))
|
|
@@ -115,6 +152,7 @@ _This version does not introduce any user-facing changes._
|
|
|
115
152
|
### 💡 Others
|
|
116
153
|
|
|
117
154
|
- Moved `DateTimePicker` to `@expo/ui/community/datetime-picker`. The old `@expo/ui/datetimepicker` export still works but logs a deprecation warning in development. ([@vonovak](https://github.com/vonovak)) ([#45211](https://github.com/expo/expo/pull/45211) by [@vonovak](https://github.com/vonovak))
|
|
155
|
+
- [iOS] Added `extraProps` to `SlotView` for passing metadata to SwiftUI slot consumers. ([#44689](https://github.com/expo/expo/pull/45287) by [@yousofabouhalawa](https://github.com/yousofabouhalawa))
|
|
118
156
|
- [jetpack-compose] Use view hash code as key for `Children`. ([#44521](https://github.com/expo/expo/pull/44521) by [@kudo](https://github.com/kudo))
|
|
119
157
|
- Refactored `ComposableScope` and allow extensibility. ([#44698](https://github.com/expo/expo/pull/44698) by [@kudo](https://github.com/kudo))
|
|
120
158
|
- [jetpack-compose] Reuse `HorizontalAlignment` converter in `LazyColumn`. ([#44755](https://github.com/expo/expo/pull/44755) by [@kudo](https://github.com/kudo))
|
|
@@ -316,7 +354,7 @@ _This version does not introduce any user-facing changes._
|
|
|
316
354
|
|
|
317
355
|
- [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))
|
|
318
356
|
- Improved Jetpack Compose integration for Expo UI. ([#42450](https://github.com/expo/expo/pull/42450) by [@kudo](https://github.com/kudo))
|
|
319
|
-
- [iOS] Added `contentShape` modifier for SwiftUI ([#42813](https://github.com/expo/expo
|
|
357
|
+
- [iOS] Added `contentShape` modifier for SwiftUI ([#42813](https://github.com/expo/expo/pull/42813) by [@sam-shubham](https://github.com/sam-shubham))
|
|
320
358
|
|
|
321
359
|
## 55.0.0-beta.3 — 2026-01-27
|
|
322
360
|
|
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.9'
|
|
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.9"
|
|
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,14 +81,28 @@ 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
|
|
|
86
101
|
//region Views use expo-modules-core DSL for uncommon features
|
|
87
102
|
|
|
88
103
|
View(HostView::class) {
|
|
89
|
-
|
|
104
|
+
// See ShadowNodeSyncFlush.kt for why onExpoUISyncFlush is needed.
|
|
105
|
+
Events("onLayoutContent", "onExpoUISyncFlush")
|
|
90
106
|
|
|
91
107
|
OnViewDidUpdateProps { view ->
|
|
92
108
|
view.onViewDidUpdateProps()
|
|
@@ -123,7 +139,10 @@ class ExpoUIModule : Module() {
|
|
|
123
139
|
colorScheme.toTokenMap()
|
|
124
140
|
}
|
|
125
141
|
|
|
126
|
-
View(RNHostView::class)
|
|
142
|
+
View(RNHostView::class) {
|
|
143
|
+
// See ShadowNodeSyncFlush.kt for why this internal phantom event is needed.
|
|
144
|
+
Events("onExpoUISyncFlush")
|
|
145
|
+
}
|
|
127
146
|
|
|
128
147
|
View(SlotView::class) {
|
|
129
148
|
Events("onSlotEvent")
|
|
@@ -135,6 +154,8 @@ class ExpoUIModule : Module() {
|
|
|
135
154
|
// Class-based views so TooltipBoxView can detect them by type via findChildOfType
|
|
136
155
|
View(PlainTooltipView::class)
|
|
137
156
|
View(RichTooltipView::class)
|
|
157
|
+
// Class-based view so SnackbarHostView can read its styling via findChildOfType
|
|
158
|
+
View(SnackbarView::class)
|
|
138
159
|
|
|
139
160
|
//endregion Views use expo-modules-core DSL for uncommon features
|
|
140
161
|
|
|
@@ -336,6 +357,18 @@ class ExpoUIModule : Module() {
|
|
|
336
357
|
}
|
|
337
358
|
}
|
|
338
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
|
+
|
|
339
372
|
ExpoUIView<LinearProgressIndicatorProps>("LinearProgressIndicatorView") {
|
|
340
373
|
Content { props ->
|
|
341
374
|
LinearProgressIndicatorContent(props)
|
|
@@ -592,6 +625,14 @@ class ExpoUIModule : Module() {
|
|
|
592
625
|
}
|
|
593
626
|
}
|
|
594
627
|
|
|
628
|
+
ExpoUIView<SnackbarHostProps>("SnackbarHostView") {
|
|
629
|
+
val showSnackbar by AsyncFunction<SnackbarShowOptions>()
|
|
630
|
+
|
|
631
|
+
Content { props ->
|
|
632
|
+
SnackbarHostContent(props, showSnackbar)
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
595
636
|
ExpoUIView<TooltipBoxViewProps>("TooltipBoxView") {
|
|
596
637
|
val show by AsyncFunction()
|
|
597
638
|
val dismiss by AsyncFunction()
|
|
@@ -193,6 +193,7 @@ internal class HostView(context: Context, appContext: AppContext) :
|
|
|
193
193
|
if (constraints.maxWidth == 0) widthDp else Double.NaN,
|
|
194
194
|
if (constraints.maxHeight == 0) heightDp else Double.NaN
|
|
195
195
|
)
|
|
196
|
+
flushPendingStateUpdates()
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
|
|
@@ -221,6 +222,7 @@ internal class HostView(context: Context, appContext: AppContext) :
|
|
|
221
222
|
val styleWidth = if (matchContentsHorizontal == true && width > 0) width else null
|
|
222
223
|
val styleHeight = if (matchContentsVertical == true && height > 0) height else null
|
|
223
224
|
shadowNodeProxy.setStyleSize(styleWidth?.toDouble(), styleHeight?.toDouble())
|
|
225
|
+
flushPendingStateUpdates()
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
onLayoutContent(LayoutContentEvent(width.toDouble(), height.toDouble()))
|
|
@@ -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
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
|
1
|
+
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
|
2
2
|
|
|
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
|
|
14
|
+
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
10
15
|
import androidx.compose.foundation.background
|
|
11
16
|
import androidx.compose.foundation.border
|
|
12
17
|
import androidx.compose.foundation.clickable
|
|
18
|
+
import androidx.compose.foundation.combinedClickable
|
|
13
19
|
import androidx.compose.foundation.horizontalScroll
|
|
14
20
|
import androidx.compose.foundation.rememberScrollState
|
|
15
21
|
import androidx.compose.foundation.verticalScroll
|
|
@@ -36,6 +42,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|
|
36
42
|
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
|
37
43
|
import androidx.compose.material3.toShape
|
|
38
44
|
import androidx.compose.runtime.Composable
|
|
45
|
+
import androidx.compose.runtime.getValue
|
|
39
46
|
import androidx.compose.ui.Modifier
|
|
40
47
|
import androidx.compose.ui.draw.alpha
|
|
41
48
|
import androidx.compose.ui.draw.blur
|
|
@@ -145,6 +152,24 @@ internal data class BackgroundParams(
|
|
|
145
152
|
@Field val color: Color? = null
|
|
146
153
|
) : Record
|
|
147
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
|
+
|
|
148
173
|
@OptimizedRecord
|
|
149
174
|
internal data class BorderParams(
|
|
150
175
|
@Field val borderWidth: Int = 1,
|
|
@@ -455,9 +480,14 @@ object ModifierRegistry {
|
|
|
455
480
|
// Appearance modifiers
|
|
456
481
|
register("background") { map, _, _, _ ->
|
|
457
482
|
val params = recordFromMap<BackgroundParams>(map)
|
|
458
|
-
params.color?.
|
|
459
|
-
|
|
460
|
-
|
|
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
|
+
}
|
|
461
491
|
}
|
|
462
492
|
|
|
463
493
|
register("border") { map, _, _, _ ->
|
|
@@ -633,6 +663,22 @@ object ModifierRegistry {
|
|
|
633
663
|
}
|
|
634
664
|
}
|
|
635
665
|
|
|
666
|
+
register("combinedClickable") { map, _, _, eventDispatcher ->
|
|
667
|
+
val params = recordFromMap<ClickableParams>(map)
|
|
668
|
+
val onClick = { eventDispatcher("combinedClickable", mapOf("event" to "click")) }
|
|
669
|
+
val onLongClick = { eventDispatcher("combinedClickable", mapOf("event" to "longClick")) }
|
|
670
|
+
if (params.indication) {
|
|
671
|
+
Modifier.combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
|
672
|
+
} else {
|
|
673
|
+
Modifier.combinedClickable(
|
|
674
|
+
interactionSource = null,
|
|
675
|
+
indication = null,
|
|
676
|
+
onClick = onClick,
|
|
677
|
+
onLongClick = onLongClick
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
636
682
|
register("selectable") { map, _, _, eventDispatcher ->
|
|
637
683
|
val params = recordFromMap<SelectableParams>(map)
|
|
638
684
|
Modifier.selectable(
|
|
@@ -36,7 +36,8 @@ import expo.modules.kotlin.views.OptimizedComposeProps
|
|
|
36
36
|
|
|
37
37
|
@OptimizedComposeProps
|
|
38
38
|
internal data class RNHostViewProps(
|
|
39
|
-
val matchContents: MutableState<Boolean?> = mutableStateOf(null)
|
|
39
|
+
val matchContents: MutableState<Boolean?> = mutableStateOf(null),
|
|
40
|
+
val modifiers: ModifierList = emptyList()
|
|
40
41
|
) : ComposeProps
|
|
41
42
|
|
|
42
43
|
@SuppressLint("ViewConstructor")
|
|
@@ -86,23 +87,26 @@ internal class RNHostView(context: Context, appContext: AppContext) :
|
|
|
86
87
|
@Composable
|
|
87
88
|
override fun ComposableScope.Content() {
|
|
88
89
|
val matchContents = props.matchContents.value ?: false
|
|
90
|
+
val scope: ComposableScope = this
|
|
89
91
|
|
|
90
92
|
wrapperState.value?.let { wrapper ->
|
|
91
93
|
val childView = childViewState.value ?: return@let
|
|
92
|
-
val
|
|
94
|
+
val sizingModifier = if (matchContents) {
|
|
93
95
|
applySizeFromYogaNodeModifier(childView)
|
|
94
96
|
} else {
|
|
95
97
|
Modifier
|
|
96
98
|
.fillMaxSize()
|
|
97
99
|
.then(reportSizeToYogaNodeModifier())
|
|
98
100
|
}
|
|
101
|
+
val modifiers = sizingModifier
|
|
102
|
+
.then(ModifierRegistry.applyModifiers(props.modifiers, appContext, scope, globalEventDispatcher))
|
|
99
103
|
|
|
100
104
|
AndroidView(
|
|
101
105
|
factory = {
|
|
102
106
|
(wrapper.parent as? ViewGroup)?.removeView(wrapper)
|
|
103
107
|
wrapper
|
|
104
108
|
},
|
|
105
|
-
modifier =
|
|
109
|
+
modifier = modifiers
|
|
106
110
|
)
|
|
107
111
|
}
|
|
108
112
|
}
|
|
@@ -154,6 +158,7 @@ internal class RNHostView(context: Context, appContext: AppContext) :
|
|
|
154
158
|
size.width.toDp().value.toDouble(),
|
|
155
159
|
size.height.toDp().value.toDouble()
|
|
156
160
|
)
|
|
161
|
+
flushPendingStateUpdates()
|
|
157
162
|
}
|
|
158
163
|
}
|
|
159
164
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package expo.modules.ui
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
5
|
+
import com.facebook.react.bridge.ReactContext
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
8
|
+
import com.facebook.react.uimanager.events.Event
|
|
9
|
+
|
|
10
|
+
// Workaround helper that triggers a synchronous event to flush a pending
|
|
11
|
+
// shadow-node state update in the current event beat. Mirrors iOS's
|
|
12
|
+
// `EventQueue::UpdateMode::unstable_Immediate`, which Android does not expose
|
|
13
|
+
// to Java/Kotlin as of now.
|
|
14
|
+
// TODO: Remove when a synchronous state update API is exposed on Android.
|
|
15
|
+
// https://github.com/facebook/react-native/pull/56311
|
|
16
|
+
internal fun View.flushPendingStateUpdates() {
|
|
17
|
+
val reactContext = context as? ReactContext ?: return
|
|
18
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
19
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
|
|
20
|
+
?.dispatchEvent(SyncFlushEvent(surfaceId, id))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private class SyncFlushEvent(surfaceId: Int, viewTag: Int) : Event<SyncFlushEvent>(surfaceId, viewTag) {
|
|
24
|
+
override fun getEventName(): String = "topExpoUISyncFlush"
|
|
25
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
26
|
+
override fun canCoalesce(): Boolean = true
|
|
27
|
+
override fun experimental_isSynchronous(): Boolean = true
|
|
28
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
android:width="24dp"
|
|
3
|
+
android:height="24dp"
|
|
4
|
+
android:viewportWidth="960"
|
|
5
|
+
android:viewportHeight="960"
|
|
6
|
+
android:tint="?attr/colorControlNormal">
|
|
7
|
+
<path
|
|
8
|
+
android:fillColor="@android:color/white"
|
|
9
|
+
android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
|
|
10
|
+
</vector>
|
|
@@ -5,11 +5,40 @@ import { type SharedObject } from 'expo-modules-core';
|
|
|
5
5
|
*/
|
|
6
6
|
export type ObservableState<T> = SharedObject & {
|
|
7
7
|
/**
|
|
8
|
-
* The current value.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The current value.
|
|
9
|
+
*
|
|
10
|
+
* Writes from a UI worklet are synchronous and immediately readable. Writes
|
|
11
|
+
* from the JS thread are scheduled to the UI thread asynchronously, the new value is not readable until the update has been
|
|
12
|
+
* applied. Prefer writing from a worklet when you need synchronous updates
|
|
11
13
|
*/
|
|
12
14
|
value: T;
|
|
15
|
+
/**
|
|
16
|
+
* A single listener invoked on the native UI runtime whenever the value changes
|
|
17
|
+
* (after iOS `didSet` and Android's setter). Assigning replaces the previous
|
|
18
|
+
* listener; assign `null` to clear. The initial value does not fire `onChange`.
|
|
19
|
+
*
|
|
20
|
+
* The callback must be a worklet so it can run synchronously on the UI thread.
|
|
21
|
+
* Attach it inside `useEffect` and clear it in the cleanup so the listener
|
|
22
|
+
* lifecycle matches the component lifecycle.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const state = useNativeState(0);
|
|
27
|
+
*
|
|
28
|
+
* useEffect(() => {
|
|
29
|
+
* state.onChange = (value) => {
|
|
30
|
+
* 'worklet';
|
|
31
|
+
* console.log('changed to', value);
|
|
32
|
+
* };
|
|
33
|
+
* return () => {
|
|
34
|
+
* state.onChange = null;
|
|
35
|
+
* };
|
|
36
|
+
* }, []);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
onChange: {
|
|
40
|
+
listener(value: T): void;
|
|
41
|
+
}['listener'] | null;
|
|
13
42
|
};
|
|
14
43
|
/**
|
|
15
44
|
* Creates an observable native state that is automatically cleaned up when the
|