@expo/ui 56.0.16 → 56.0.18
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 +33 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +40 -6
- package/android/src/main/java/expo/modules/ui/HostView.kt +0 -2
- package/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt +14 -0
- package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +20 -0
- package/android/src/main/java/expo/modules/ui/RNHostView.kt +182 -6
- package/android/src/main/java/expo/modules/ui/textfield/BasicTextField.kt +203 -0
- package/android/src/main/java/expo/modules/ui/{TextFieldView.kt → textfield/TextField.kt} +34 -248
- package/android/src/main/java/expo/modules/ui/textfield/TextFieldShared.kt +299 -0
- package/build/State/useNativeState.d.ts +8 -3
- package/build/State/useNativeState.d.ts.map +1 -1
- package/build/community/bottom-sheet/BottomSheet.android.d.ts.map +1 -1
- package/build/community/pager-view/PagerView.android.d.ts.map +1 -1
- package/build/jetpack-compose/ModalBottomSheet/index.d.ts +6 -0
- package/build/jetpack-compose/ModalBottomSheet/index.d.ts.map +1 -1
- package/build/jetpack-compose/RNHostView/index.d.ts +8 -0
- package/build/jetpack-compose/RNHostView/index.d.ts.map +1 -1
- package/build/jetpack-compose/TextField/BasicTextField.d.ts +36 -0
- package/build/jetpack-compose/TextField/BasicTextField.d.ts.map +1 -0
- package/build/jetpack-compose/TextField/TextField.d.ts +131 -0
- package/build/jetpack-compose/TextField/TextField.d.ts.map +1 -0
- package/build/jetpack-compose/TextField/index.d.ts +3 -244
- package/build/jetpack-compose/TextField/index.d.ts.map +1 -1
- package/build/jetpack-compose/TextField/shared.d.ts +171 -0
- package/build/jetpack-compose/TextField/shared.d.ts.map +1 -0
- package/build/jetpack-compose/index.d.ts +1 -1
- package/build/jetpack-compose/index.d.ts.map +1 -1
- package/build/jetpack-compose/modifiers/index.d.ts +11 -0
- package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/Image/index.d.ts +3 -1
- package/build/swift-ui/Image/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +34 -5
- package/build/swift-ui/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/widgets.d.ts +7 -0
- package/build/swift-ui/modifiers/widgets.d.ts.map +1 -1
- package/build/universal/Button/index.ios.d.ts.map +1 -1
- package/build/universal/FieldGroup/FieldSectionSlots.d.ts.map +1 -1
- package/build/universal/FieldGroup/groupChildren.d.ts.map +1 -1
- package/build/universal/Text/index.ios.d.ts.map +1 -1
- package/build/universal/TextInput/index.android.d.ts.map +1 -1
- package/build/universal/TextInput/types.d.ts +5 -1
- package/build/universal/TextInput/types.d.ts.map +1 -1
- package/build/universal/modifierUtils.d.ts +16 -0
- package/build/universal/modifierUtils.d.ts.map +1 -0
- package/build/universal/transformStyle.android.d.ts +3 -0
- package/build/universal/transformStyle.android.d.ts.map +1 -1
- package/build/universal/transformStyle.ios.d.ts +3 -0
- package/build/universal/transformStyle.ios.d.ts.map +1 -1
- package/build/universal/types.d.ts +2 -0
- package/build/universal/types.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/ImageView.swift +1 -5
- package/ios/Modifiers/ImageScaleModifier.swift +29 -0
- package/ios/Modifiers/OnGeometryChangeModifier.swift +8 -16
- package/ios/Modifiers/ViewModifierRegistry.swift +36 -0
- package/ios/Modifiers/WidgetModifiers.swift +12 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.16/expo.modules.ui-56.0.16-sources.jar → 56.0.18/expo.modules.ui-56.0.18-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.16/expo.modules.ui-56.0.16.module → 56.0.18/expo.modules.ui-56.0.18.module} +22 -22
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.16/expo.modules.ui-56.0.16.pom → 56.0.18/expo.modules.ui-56.0.18.pom} +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.18/expo.modules.ui-56.0.18.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 +3 -3
- package/src/State/index.fx.ts +4 -1
- package/src/State/useNativeState.ts +24 -5
- package/src/community/bottom-sheet/BottomSheet.android.tsx +2 -13
- package/src/community/bottom-sheet/BottomSheet.ios.tsx +1 -1
- package/src/community/datetime-picker/DateTimePicker.tsx +1 -1
- package/src/community/menu/MenuView.ios.tsx +1 -1
- package/src/community/pager-view/PagerView.android.tsx +21 -3
- package/src/community/pager-view/PagerView.ios.tsx +1 -1
- package/src/community/picker/Picker.ios.tsx +1 -1
- package/src/community/slider/Slider.ios.tsx +1 -1
- package/src/jetpack-compose/ModalBottomSheet/index.tsx +7 -0
- package/src/jetpack-compose/RNHostView/index.tsx +8 -0
- package/src/jetpack-compose/TextField/BasicTextField.tsx +118 -0
- package/src/jetpack-compose/TextField/TextField.tsx +198 -0
- package/src/jetpack-compose/TextField/index.ts +19 -0
- package/src/jetpack-compose/TextField/{index.tsx → shared.ts} +71 -203
- package/src/jetpack-compose/index.ts +6 -0
- package/src/jetpack-compose/modifiers/index.ts +13 -0
- package/src/swift-ui/BottomSheet/index.tsx +1 -1
- package/src/swift-ui/Image/index.tsx +12 -3
- package/src/swift-ui/modifiers/index.ts +44 -6
- package/src/swift-ui/modifiers/widgets.ts +9 -0
- package/src/universal/Button/index.ios.tsx +6 -1
- package/src/universal/FieldGroup/FieldSectionSlots.tsx +3 -0
- package/src/universal/FieldGroup/groupChildren.tsx +3 -0
- package/src/universal/Text/index.ios.tsx +3 -1
- package/src/universal/TextInput/index.android.tsx +26 -60
- package/src/universal/TextInput/types.ts +5 -1
- package/src/universal/modifierUtils.ts +23 -0
- package/src/universal/transformStyle.android.ts +9 -1
- package/src/universal/transformStyle.ios.ts +9 -1
- package/src/universal/types.ts +2 -0
- package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +0 -28
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.16/expo.modules.ui-56.0.16.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.18 — 2026-06-15
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- [iOS] Added the SwiftUI `activityBackgroundTint` modifier for setting Live Activity background. ([#46756](https://github.com/expo/expo/pull/46756) by [@jakex7](https://github.com/jakex7))
|
|
18
|
+
|
|
19
|
+
## 56.0.17 — 2026-06-10
|
|
20
|
+
|
|
21
|
+
### 🛠 Breaking changes
|
|
22
|
+
|
|
23
|
+
- [universal][android] Use `BasicTextField` component instead of Filled Material TextField. ([#46442](https://github.com/expo/expo/pull/46442) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
24
|
+
|
|
25
|
+
### 🎉 New features
|
|
26
|
+
|
|
27
|
+
- [iOS] Added the SwiftUI `imageScale` modifier to scale SF Symbols within a view relative to the surrounding text (`small`, `medium`, `large`). ([#46774](https://github.com/expo/expo/pull/46774) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
28
|
+
- [jetpack-compose] Added `onGloballyPositioned` modifier, which reports a composable's window position and size. ([#46744](https://github.com/expo/expo/pull/46744) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
29
|
+
- [iOS] Extended the SwiftUI `onGeometryChange` modifier to also report the view's global position (`x`/`y`) alongside its size. ([#46744](https://github.com/expo/expo/pull/46744) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
30
|
+
- [iOS] Added the SwiftUI `minimumScaleFactor` modifier to let text shrink down to a given fraction of its size before truncating. ([#46740](https://github.com/expo/expo/pull/46740) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
31
|
+
- [iOS][android] Added React Compiler-friendly `get()` / `set()` accessors to `useNativeState`, as an alternative to reading and writing `.value`. ([#46690](https://github.com/expo/expo/pull/46692) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
32
|
+
- [jetpack-compose] Added `BasicTextField` component. ([#46442](https://github.com/expo/expo/pull/46442) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
33
|
+
- [iOS] Added the SwiftUI `accessibilityInputLabels` modifier to set alternative spoken phrases Voice Control uses to refer to a view (for example "Hang up" for an "End" button). ([#46661](https://github.com/expo/expo/pull/46661) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
34
|
+
|
|
13
35
|
## 56.0.16 — 2026-06-05
|
|
14
36
|
|
|
15
37
|
### 🎉 New features
|
|
@@ -28,7 +50,18 @@
|
|
|
28
50
|
|
|
29
51
|
### 🐛 Bug fixes
|
|
30
52
|
|
|
53
|
+
- [android] Fix `FieldGroup` rendering an empty row/section when a conditional child (e.g. `{condition && <FieldGroup.Section>…</FieldGroup.Section>}`) evaluates to `false`. ([##46874](https://github.com/expo/expo/pull/46874) by [@dileepapeiris](https://github.com/dileepapeiris))
|
|
54
|
+
- [android] Fix React Native `Pressable`/`Button` taps not registering on `community/pager-view` pages after the first on physical devices. ([#46851](https://github.com/expo/expo/pull/46851) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
55
|
+
- [universal] Fix user-supplied `modifiers` having no effect when the component derives a modifier of the same type from its props, e.g. `buttonStyle` on `Button` always losing to the `variant` prop. ([#46815](https://github.com/expo/expo/pull/46815) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
56
|
+
- [android] Fix race between JS imperative `expand()` and Compose handler registration on `ModalBottomSheet`. Adds `initialFullyExpanded` prop to drive initial snap state natively. ([#46367](https://github.com/expo/expo/pull/46367) by [@duyanhv](https://github.com/duyanhv))
|
|
57
|
+
- [iOS][android] Fix `community/bottom-sheet` blocking touches behind the sheet after it's dismissed. ([#46805](https://github.com/expo/expo/pull/46805) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
58
|
+
- [android] Fix React Native touchables (e.g. `Pressable`) on `community/pager-view` pages not responding, or triggering the wrong page's handler, after navigating between pages. ([#46778](https://github.com/expo/expo/pull/46778) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
59
|
+
- [iOS] Fix `font`, `dynamicTypeSize`, and `resizable` modifiers not applying to the SwiftUI `Image`. SF Symbols scale with Dynamic Type when a `font` modifier sets a `textStyle`. ([#46714](https://github.com/expo/expo/pull/46714) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
60
|
+
- [android] Fix React Native `ScrollView` nested scrolling inside `BottomSheet`. ([#46544](https://github.com/expo/expo/pull/46544) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
61
|
+
- [jetpack-compose] Fix layout shift when `Host` with `matchContents` is used inside React Native Screens. ([#46604](https://github.com/expo/expo/pull/46604) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
62
|
+
- [iOS] Fix `PagerView` offsetting `ScrollView` by safe area insets. ([#46637](https://github.com/expo/expo/pull/46637) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
31
63
|
- [iOS] Fix `SegmentedControl` being overlapped by sibling components inside a `ScrollView`, by disabling the `Host` safe area insets. ([#46575](https://github.com/expo/expo/pull/46575) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
64
|
+
- [iOS] Fix the community `DateTimePicker`, `MenuView`, `Picker`, and `Slider` applying safe area insets (including keyboard avoidance) twice, by disabling the `Host` safe area insets (`ignoreSafeArea="all"`). ([#46721](https://github.com/expo/expo/pull/46721) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
32
65
|
- [jetpack-compose] Fix `TextField` jiggling the surrounding content while its label animates on focus (a Material 3 expressive motion spring overshoot). ([#46568](https://github.com/expo/expo/pull/46568) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
33
66
|
- [iOS] Fix `BottomSheet` animating open from the bottom-left corner in `fitToContents` mode. ([#46546](https://github.com/expo/expo/pull/46546) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
34
67
|
- [iOS] Fix `TextField` and `SecureField` worklet `onTextChange` firing more than once per keystroke (when a change triggers reformatting) and on programmatic text updates. ([#46483](https://github.com/expo/expo/pull/46483) by [@nishan](https://github.com/intergalacticspacehighway))
|
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.18'
|
|
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.18"
|
|
22
22
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
23
23
|
}
|
|
24
24
|
buildFeatures {
|
|
@@ -36,6 +36,15 @@ import expo.modules.ui.menu.DropdownMenuItemProps
|
|
|
36
36
|
import expo.modules.kotlin.jni.worklets.Worklet
|
|
37
37
|
import expo.modules.ui.state.ObservableState
|
|
38
38
|
import expo.modules.ui.state.WorkletCallback
|
|
39
|
+
import expo.modules.ui.textfield.BasicTextFieldContent
|
|
40
|
+
import expo.modules.ui.textfield.BasicTextFieldProps
|
|
41
|
+
import expo.modules.ui.textfield.InnerTextFieldView
|
|
42
|
+
import expo.modules.ui.textfield.KeyboardActionEvent
|
|
43
|
+
import expo.modules.ui.textfield.PlaceholderView
|
|
44
|
+
import expo.modules.ui.textfield.TextFieldContent
|
|
45
|
+
import expo.modules.ui.textfield.TextFieldProps
|
|
46
|
+
import expo.modules.ui.textfield.TextFieldSelectionPayload
|
|
47
|
+
import expo.modules.ui.textfield.TextFieldValuePayload
|
|
39
48
|
import expo.modules.ui.menu.ExposedDropdownMenuBoxContent
|
|
40
49
|
import expo.modules.ui.menu.ExposedDropdownMenuBoxProps
|
|
41
50
|
import expo.modules.ui.menu.ExposedDropdownMenuContent
|
|
@@ -109,8 +118,7 @@ class ExpoUIModule : Module() {
|
|
|
109
118
|
//region Views use expo-modules-core DSL for uncommon features
|
|
110
119
|
|
|
111
120
|
View(HostView::class) {
|
|
112
|
-
|
|
113
|
-
Events("onLayoutContent", "onExpoUISyncFlush")
|
|
121
|
+
Events("onLayoutContent")
|
|
114
122
|
|
|
115
123
|
OnViewDidUpdateProps { view ->
|
|
116
124
|
view.onViewDidUpdateProps()
|
|
@@ -147,14 +155,13 @@ class ExpoUIModule : Module() {
|
|
|
147
155
|
colorScheme.toTokenMap()
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
View(RNHostView::class)
|
|
151
|
-
// See ShadowNodeSyncFlush.kt for why this internal phantom event is needed.
|
|
152
|
-
Events("onExpoUISyncFlush")
|
|
153
|
-
}
|
|
158
|
+
View(RNHostView::class)
|
|
154
159
|
|
|
155
160
|
View(SlotView::class) {
|
|
156
161
|
Events("onSlotEvent")
|
|
157
162
|
}
|
|
163
|
+
View(InnerTextFieldView::class)
|
|
164
|
+
View(PlaceholderView::class)
|
|
158
165
|
View(IconView::class)
|
|
159
166
|
View(LazyColumnView::class)
|
|
160
167
|
View(LazyRowView::class)
|
|
@@ -697,6 +704,33 @@ class ExpoUIModule : Module() {
|
|
|
697
704
|
}
|
|
698
705
|
}
|
|
699
706
|
|
|
707
|
+
ExpoUIView<BasicTextFieldProps>("BasicTextFieldView") {
|
|
708
|
+
val setText by AsyncFunction<String>()
|
|
709
|
+
val setSelection by AsyncFunction<Int, Int>()
|
|
710
|
+
val clear by AsyncFunction()
|
|
711
|
+
val focus by AsyncFunction()
|
|
712
|
+
val blur by AsyncFunction()
|
|
713
|
+
val onValueChange by Event<TextFieldValuePayload>()
|
|
714
|
+
val onFocusChanged by Event<GenericEventPayload1<Boolean>>()
|
|
715
|
+
val onKeyboardAction by Event<KeyboardActionEvent>()
|
|
716
|
+
val onSelectionChange by Event<TextFieldSelectionPayload>()
|
|
717
|
+
|
|
718
|
+
Content { props ->
|
|
719
|
+
BasicTextFieldContent(
|
|
720
|
+
props,
|
|
721
|
+
setText,
|
|
722
|
+
setSelection,
|
|
723
|
+
clear,
|
|
724
|
+
focus,
|
|
725
|
+
blur,
|
|
726
|
+
onValueChanged = { onValueChange(it) },
|
|
727
|
+
onFocusChange = { onFocusChanged(it) },
|
|
728
|
+
onKeyboardActionTriggered = { onKeyboardAction(it) },
|
|
729
|
+
onSelectionChanged = { onSelectionChange(it) }
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
700
734
|
ExpoUIView<RadioButtonProps>("RadioButtonView") {
|
|
701
735
|
val onButtonPressed by Event<Unit>()
|
|
702
736
|
|
|
@@ -193,7 +193,6 @@ 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()
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
|
|
@@ -222,7 +221,6 @@ internal class HostView(context: Context, appContext: AppContext) :
|
|
|
222
221
|
val styleWidth = if (matchContentsHorizontal == true && width > 0) width else null
|
|
223
222
|
val styleHeight = if (matchContentsVertical == true && height > 0) height else null
|
|
224
223
|
shadowNodeProxy.setStyleSize(styleWidth?.toDouble(), styleHeight?.toDouble())
|
|
225
|
-
flushPendingStateUpdates()
|
|
226
224
|
}
|
|
227
225
|
|
|
228
226
|
onLayoutContent(LayoutContentEvent(width.toDouble(), height.toDouble()))
|
|
@@ -10,6 +10,7 @@ import androidx.compose.material3.ModalBottomSheetProperties
|
|
|
10
10
|
import androidx.compose.material3.contentColorFor
|
|
11
11
|
import androidx.compose.material3.rememberModalBottomSheetState
|
|
12
12
|
import androidx.compose.runtime.Composable
|
|
13
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
13
14
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
14
15
|
import expo.modules.kotlin.records.Field
|
|
15
16
|
import expo.modules.kotlin.records.Record
|
|
@@ -30,6 +31,7 @@ data class ModalBottomSheetPropertiesRecord(
|
|
|
30
31
|
@OptimizedComposeProps
|
|
31
32
|
data class ModalBottomSheetViewProps(
|
|
32
33
|
val skipPartiallyExpanded: Boolean = false,
|
|
34
|
+
val initialFullyExpanded: Boolean = false,
|
|
33
35
|
val containerColor: Color? = null,
|
|
34
36
|
val contentColor: Color? = null,
|
|
35
37
|
val scrimColor: Color? = null,
|
|
@@ -112,4 +114,16 @@ fun FunctionalComposableScope.ModalBottomSheetContent(
|
|
|
112
114
|
) {
|
|
113
115
|
Children(UIComposableScope(), filter = { !isSlotView(it) })
|
|
114
116
|
}
|
|
117
|
+
|
|
118
|
+
LaunchedEffect(Unit) {
|
|
119
|
+
if (props.initialFullyExpanded && !props.skipPartiallyExpanded) {
|
|
120
|
+
try {
|
|
121
|
+
sheetState.expand()
|
|
122
|
+
} catch (_: CancellationException) {
|
|
123
|
+
// Dismissal can cancel the expand animation.
|
|
124
|
+
} catch (_: Exception) {
|
|
125
|
+
// Expanded anchor may be unreachable; never crash the view.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
115
129
|
}
|
|
@@ -57,8 +57,10 @@ import androidx.compose.ui.graphics.Shape
|
|
|
57
57
|
import androidx.compose.ui.graphics.TransformOrigin
|
|
58
58
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
59
59
|
import androidx.compose.ui.graphics.shadow.Shadow
|
|
60
|
+
import androidx.compose.ui.layout.onGloballyPositioned
|
|
60
61
|
import androidx.compose.ui.layout.onSizeChanged
|
|
61
62
|
import androidx.compose.ui.layout.onVisibilityChanged
|
|
63
|
+
import androidx.compose.ui.layout.positionInWindow
|
|
62
64
|
import androidx.compose.ui.platform.LocalDensity
|
|
63
65
|
import androidx.compose.ui.semantics.Role
|
|
64
66
|
import androidx.compose.ui.semantics.contentType
|
|
@@ -692,6 +694,24 @@ object ModifierRegistry {
|
|
|
692
694
|
}
|
|
693
695
|
}
|
|
694
696
|
|
|
697
|
+
register("onGloballyPositioned") { _, _, _, eventDispatcher ->
|
|
698
|
+
val density = LocalDensity.current
|
|
699
|
+
Modifier.onGloballyPositioned { coordinates ->
|
|
700
|
+
val position = coordinates.positionInWindow()
|
|
701
|
+
with(density) {
|
|
702
|
+
eventDispatcher(
|
|
703
|
+
"onGloballyPositioned",
|
|
704
|
+
mapOf(
|
|
705
|
+
"x" to position.x.toDp().value,
|
|
706
|
+
"y" to position.y.toDp().value,
|
|
707
|
+
"width" to coordinates.size.width.toDp().value,
|
|
708
|
+
"height" to coordinates.size.height.toDp().value
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
695
715
|
register("clickable") { map, _, _, eventDispatcher ->
|
|
696
716
|
val params = recordFromMap<ClickableParams>(map)
|
|
697
717
|
if (params.indication) {
|
|
@@ -19,6 +19,9 @@ import androidx.compose.ui.layout.onSizeChanged
|
|
|
19
19
|
import androidx.compose.ui.platform.LocalDensity
|
|
20
20
|
import androidx.compose.ui.unit.IntSize
|
|
21
21
|
import androidx.compose.ui.viewinterop.AndroidView
|
|
22
|
+
import androidx.core.view.NestedScrollingChild3
|
|
23
|
+
import androidx.core.view.NestedScrollingChildHelper
|
|
24
|
+
import androidx.core.view.ViewCompat
|
|
22
25
|
import com.facebook.react.bridge.ReactContext
|
|
23
26
|
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
24
27
|
import com.facebook.react.config.ReactFeatureFlags
|
|
@@ -158,7 +161,6 @@ internal class RNHostView(context: Context, appContext: AppContext) :
|
|
|
158
161
|
size.width.toDp().value.toDouble(),
|
|
159
162
|
size.height.toDp().value.toDouble()
|
|
160
163
|
)
|
|
161
|
-
flushPendingStateUpdates()
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
}
|
|
@@ -168,13 +170,30 @@ internal class RNHostView(context: Context, appContext: AppContext) :
|
|
|
168
170
|
* A thin FrameLayout that intercepts touch events and dispatches them to JS via
|
|
169
171
|
* JSTouchDispatcher/JSPointerDispatcher, replicating the pattern from React Native's
|
|
170
172
|
* DialogRootViewGroup in ReactModalHostView.
|
|
173
|
+
* Implements NestedScrollingChild3 to forward scroll events up to the parent Compose view, because Compose only listens for NestedScrollingChild3 nested-scroll events.
|
|
171
174
|
*/
|
|
172
175
|
private class TouchDispatchingRootViewGroup(
|
|
173
176
|
context: Context
|
|
174
|
-
) : FrameLayout(context), RootView {
|
|
177
|
+
) : FrameLayout(context), RootView, NestedScrollingChild3 {
|
|
175
178
|
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
176
179
|
private var jsPointerDispatcher: JSPointerDispatcher? = null
|
|
177
180
|
|
|
181
|
+
// The "child face": this helper does the real work of finding the nearest scrolling-aware
|
|
182
|
+
// ancestor (Compose, here) and forwarding our scroll offers to it.
|
|
183
|
+
private val childHelper = NestedScrollingChildHelper(this)
|
|
184
|
+
|
|
185
|
+
// How far the sheet has slid this view on-screen since the gesture began. Used to keep the
|
|
186
|
+
// FlatList's touch coordinates coherent while the view moves under the finger.
|
|
187
|
+
private val gestureStartLocation = IntArray(2)
|
|
188
|
+
private val currentLocation = IntArray(2)
|
|
189
|
+
private var trackingGestureOffset = false
|
|
190
|
+
|
|
191
|
+
// True if the sheet consumed scroll on the most recent drag frame; drives the settle decision.
|
|
192
|
+
private var sheetMovingOnLastDragFrame = false
|
|
193
|
+
|
|
194
|
+
// True once a fling was dispatched this gesture, so the gentle-release settle doesn't double-fire.
|
|
195
|
+
private var flingHandledThisGesture = false
|
|
196
|
+
|
|
178
197
|
var eventDispatcher: EventDispatcher? = null
|
|
179
198
|
|
|
180
199
|
private val reactContext: ThemedReactContext
|
|
@@ -184,6 +203,7 @@ private class TouchDispatchingRootViewGroup(
|
|
|
184
203
|
if (ReactFeatureFlags.dispatchPointerEvents) {
|
|
185
204
|
jsPointerDispatcher = JSPointerDispatcher(this)
|
|
186
205
|
}
|
|
206
|
+
childHelper.isNestedScrollingEnabled = true
|
|
187
207
|
}
|
|
188
208
|
|
|
189
209
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
@@ -200,6 +220,38 @@ private class TouchDispatchingRootViewGroup(
|
|
|
200
220
|
// and we must not override those values.
|
|
201
221
|
}
|
|
202
222
|
|
|
223
|
+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
224
|
+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
225
|
+
// dispatchTouchEvent is the true start of every gesture, so reset all per-gesture state here.
|
|
226
|
+
getLocationInWindow(gestureStartLocation)
|
|
227
|
+
trackingGestureOffset = true
|
|
228
|
+
sheetMovingOnLastDragFrame = false
|
|
229
|
+
flingHandledThisGesture = false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// While a nested scroll is in flight the sheet may be sliding this whole view up/down. Re-express
|
|
233
|
+
// every event as if the view hadn't moved, so the FlatList's own scroll tracking stays coherent.
|
|
234
|
+
val handled = if (trackingGestureOffset && hasNestedScrollingParent()) {
|
|
235
|
+
getLocationInWindow(currentLocation)
|
|
236
|
+
val dy = currentLocation[1] - gestureStartLocation[1]
|
|
237
|
+
if (dy != 0) {
|
|
238
|
+
ev.offsetLocation(0f, dy.toFloat())
|
|
239
|
+
val result = super.dispatchTouchEvent(ev)
|
|
240
|
+
ev.offsetLocation(0f, -dy.toFloat())
|
|
241
|
+
result
|
|
242
|
+
} else {
|
|
243
|
+
super.dispatchTouchEvent(ev)
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
super.dispatchTouchEvent(ev)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (ev.actionMasked == MotionEvent.ACTION_UP || ev.actionMasked == MotionEvent.ACTION_CANCEL) {
|
|
250
|
+
trackingGestureOffset = false
|
|
251
|
+
}
|
|
252
|
+
return handled
|
|
253
|
+
}
|
|
254
|
+
|
|
203
255
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
204
256
|
eventDispatcher?.let { dispatcher ->
|
|
205
257
|
jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
@@ -241,12 +293,136 @@ private class TouchDispatchingRootViewGroup(
|
|
|
241
293
|
jsPointerDispatcher?.onChildEndedNativeGesture()
|
|
242
294
|
}
|
|
243
295
|
|
|
296
|
+
override fun handleException(t: Throwable) {
|
|
297
|
+
reactContext.reactApplicationContext.handleException(RuntimeException(t))
|
|
298
|
+
}
|
|
299
|
+
|
|
244
300
|
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
|
245
|
-
//
|
|
246
|
-
//
|
|
301
|
+
// Forward the request up so Compose learns the list claimed the gesture and its own sheet-drag
|
|
302
|
+
// yields. But don't call super: setting our own FLAG_DISALLOW_INTERCEPT would skip
|
|
303
|
+
// onInterceptTouchEvent, which must keep firing to dispatch touches to JS (the reason #43716
|
|
304
|
+
// added this override).
|
|
305
|
+
parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
|
|
247
306
|
}
|
|
248
307
|
|
|
249
|
-
|
|
250
|
-
|
|
308
|
+
// --- Parent face: catch the FlatList's scroll offers and relay them up via the child face. ---
|
|
309
|
+
override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean =
|
|
310
|
+
axes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
|
|
311
|
+
|
|
312
|
+
override fun onNestedScrollAccepted(child: View, target: View, axes: Int) {
|
|
313
|
+
super.onNestedScrollAccepted(child, target, axes)
|
|
314
|
+
startNestedScroll(axes)
|
|
251
315
|
}
|
|
316
|
+
|
|
317
|
+
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
|
|
318
|
+
// Expand path: offer the delta to the sheet (above us) before the list scrolls with the rest.
|
|
319
|
+
dispatchNestedPreScroll(dx, dy, consumed, null)
|
|
320
|
+
// Did the sheet move this frame? consumed[1] is what it consumed in y axis.
|
|
321
|
+
sheetMovingOnLastDragFrame = consumed[1] != 0
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) {
|
|
325
|
+
// Collapse path: after the list scrolled what it could, hand the leftover up to the sheet.
|
|
326
|
+
// Use the (…, type, consumed) variant so we can read how much the sheet ate (consumed[1]).
|
|
327
|
+
val consumed = IntArray(2)
|
|
328
|
+
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, ViewCompat.TYPE_TOUCH, consumed)
|
|
329
|
+
if (consumed[1] != 0) sheetMovingOnLastDragFrame = true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
|
|
333
|
+
// A real fling is being dispatched, so its settle is already in motion; note that so the
|
|
334
|
+
// gentle-release fallback in onStopNestedScroll doesn't add a second, redundant settle.
|
|
335
|
+
flingHandledThisGesture = true
|
|
336
|
+
val composeConsumed = dispatchNestedPreFling(velocityX, velocityY)
|
|
337
|
+
// RN's ScrollView wants a synchronous yes/no, but Compose's pre-fling is async (it returns false
|
|
338
|
+
// now and settles later). So decide here: if the sheet was mid-move and this is an up-flick
|
|
339
|
+
// (velocityY > 0), swallow the list's fling so the sheet finishes expanding. A down-flick passes
|
|
340
|
+
// through so it can still settle/collapse.
|
|
341
|
+
return composeConsumed || (sheetMovingOnLastDragFrame && velocityY > 0)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
override fun onNestedFling(target: View, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean =
|
|
345
|
+
// Post-fling: hand the leftover flick up so the sheet can fling to its next anchor.
|
|
346
|
+
dispatchNestedFling(velocityX, velocityY, consumed)
|
|
347
|
+
|
|
348
|
+
override fun onStopNestedScroll(target: View) {
|
|
349
|
+
// Must run BEFORE stopNestedScroll() so Compose is still our parent and can receive the fling.
|
|
350
|
+
settleSheetIfNoFling()
|
|
351
|
+
super.onStopNestedScroll(target)
|
|
352
|
+
stopNestedScroll()
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// A slow (sub-threshold) release never flings, so the sheet would hang where it was dragged. If the
|
|
356
|
+
// sheet moved this gesture and no real fling settled it, dispatch a zero-velocity fling so the
|
|
357
|
+
// holder snaps it to the nearest anchor.
|
|
358
|
+
private fun settleSheetIfNoFling() {
|
|
359
|
+
if (sheetMovingOnLastDragFrame && !flingHandledThisGesture) {
|
|
360
|
+
flingHandledThisGesture = true
|
|
361
|
+
dispatchNestedFling(0f, 0f, false)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// --- Child face: the wrapper's upstream voice. The parent hooks above relay through these. ---
|
|
366
|
+
// So we listen to ViewGroup nested scroll methods and call below methods from them,
|
|
367
|
+
// which essentially converts regular Nested scrolling methods to NestedScrollingChild3.
|
|
368
|
+
override fun setNestedScrollingEnabled(enabled: Boolean) {
|
|
369
|
+
childHelper.isNestedScrollingEnabled = enabled
|
|
370
|
+
}
|
|
371
|
+
override fun isNestedScrollingEnabled(): Boolean = childHelper.isNestedScrollingEnabled
|
|
372
|
+
override fun startNestedScroll(axes: Int): Boolean = childHelper.startNestedScroll(axes)
|
|
373
|
+
override fun startNestedScroll(axes: Int, type: Int): Boolean = childHelper.startNestedScroll(axes, type)
|
|
374
|
+
override fun stopNestedScroll() = childHelper.stopNestedScroll()
|
|
375
|
+
override fun stopNestedScroll(type: Int) = childHelper.stopNestedScroll(type)
|
|
376
|
+
override fun hasNestedScrollingParent(): Boolean = childHelper.hasNestedScrollingParent()
|
|
377
|
+
override fun hasNestedScrollingParent(type: Int): Boolean = childHelper.hasNestedScrollingParent(type)
|
|
378
|
+
|
|
379
|
+
override fun dispatchNestedScroll(
|
|
380
|
+
dxConsumed: Int,
|
|
381
|
+
dyConsumed: Int,
|
|
382
|
+
dxUnconsumed: Int,
|
|
383
|
+
dyUnconsumed: Int,
|
|
384
|
+
offsetInWindow: IntArray?
|
|
385
|
+
): Boolean = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow)
|
|
386
|
+
|
|
387
|
+
override fun dispatchNestedScroll(
|
|
388
|
+
dxConsumed: Int,
|
|
389
|
+
dyConsumed: Int,
|
|
390
|
+
dxUnconsumed: Int,
|
|
391
|
+
dyUnconsumed: Int,
|
|
392
|
+
offsetInWindow: IntArray?,
|
|
393
|
+
type: Int
|
|
394
|
+
): Boolean = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type)
|
|
395
|
+
|
|
396
|
+
override fun dispatchNestedScroll(
|
|
397
|
+
dxConsumed: Int,
|
|
398
|
+
dyConsumed: Int,
|
|
399
|
+
dxUnconsumed: Int,
|
|
400
|
+
dyUnconsumed: Int,
|
|
401
|
+
offsetInWindow: IntArray?,
|
|
402
|
+
type: Int,
|
|
403
|
+
consumed: IntArray
|
|
404
|
+
) {
|
|
405
|
+
childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
override fun dispatchNestedPreScroll(
|
|
409
|
+
dx: Int,
|
|
410
|
+
dy: Int,
|
|
411
|
+
consumed: IntArray?,
|
|
412
|
+
offsetInWindow: IntArray?
|
|
413
|
+
): Boolean = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
|
|
414
|
+
|
|
415
|
+
override fun dispatchNestedPreScroll(
|
|
416
|
+
dx: Int,
|
|
417
|
+
dy: Int,
|
|
418
|
+
consumed: IntArray?,
|
|
419
|
+
offsetInWindow: IntArray?,
|
|
420
|
+
type: Int
|
|
421
|
+
): Boolean = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
|
|
422
|
+
|
|
423
|
+
override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean =
|
|
424
|
+
childHelper.dispatchNestedFling(velocityX, velocityY, consumed)
|
|
425
|
+
|
|
426
|
+
override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean =
|
|
427
|
+
childHelper.dispatchNestedPreFling(velocityX, velocityY)
|
|
252
428
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
package expo.modules.ui.textfield
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import androidx.compose.foundation.text.BasicTextField
|
|
7
|
+
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
|
8
|
+
import androidx.compose.foundation.text.selection.TextSelectionColors
|
|
9
|
+
import androidx.compose.material3.MaterialTheme
|
|
10
|
+
import androidx.compose.runtime.Composable
|
|
11
|
+
import androidx.compose.runtime.CompositionLocalProvider
|
|
12
|
+
import androidx.compose.runtime.compositionLocalOf
|
|
13
|
+
import androidx.compose.ui.graphics.SolidColor
|
|
14
|
+
import androidx.compose.ui.graphics.isUnspecified
|
|
15
|
+
import expo.modules.kotlin.AppContext
|
|
16
|
+
import expo.modules.kotlin.views.AsyncFunctionHandle
|
|
17
|
+
import expo.modules.kotlin.views.AsyncFunctionHandle2
|
|
18
|
+
import expo.modules.kotlin.views.ComposableScope
|
|
19
|
+
import expo.modules.kotlin.views.ComposeProps
|
|
20
|
+
import expo.modules.kotlin.views.ExpoComposeView
|
|
21
|
+
import expo.modules.kotlin.views.FunctionalComposableScope
|
|
22
|
+
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
23
|
+
import expo.modules.ui.GenericEventPayload1
|
|
24
|
+
import expo.modules.ui.ModifierList
|
|
25
|
+
import expo.modules.ui.composeOrNull
|
|
26
|
+
import expo.modules.ui.findChildSlotView
|
|
27
|
+
import expo.modules.ui.renderSlot
|
|
28
|
+
import expo.modules.ui.state.ObservableState
|
|
29
|
+
import expo.modules.ui.state.WorkletCallback
|
|
30
|
+
|
|
31
|
+
// region Inner text field plumbing
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Carries the framework-provided `innerTextField` lambda from `BasicTextField`'s
|
|
35
|
+
* `decorationBox` down to the [InnerTextFieldView] marker, wherever the JS
|
|
36
|
+
* decoration places it. `null` when read outside a decoration (the marker then
|
|
37
|
+
* renders nothing rather than crashing).
|
|
38
|
+
*/
|
|
39
|
+
val LocalInnerTextField = compositionLocalOf<(@Composable () -> Unit)?> { null }
|
|
40
|
+
|
|
41
|
+
class InnerTextFieldProps : ComposeProps
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Slot view for `BasicTextField.InnerTextField`.
|
|
45
|
+
*/
|
|
46
|
+
@SuppressLint("ViewConstructor")
|
|
47
|
+
class InnerTextFieldView(context: Context, appContext: AppContext) :
|
|
48
|
+
ExpoComposeView<InnerTextFieldProps>(context, appContext) {
|
|
49
|
+
override val props = InnerTextFieldProps()
|
|
50
|
+
|
|
51
|
+
@Composable
|
|
52
|
+
override fun ComposableScope.Content() {
|
|
53
|
+
LocalInnerTextField.current?.invoke()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// endregion Inner text field plumbing
|
|
58
|
+
|
|
59
|
+
// region Placeholder plumbing
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Tracks whether textfield is empty, in order to render placeholder slot
|
|
63
|
+
*/
|
|
64
|
+
val LocalTextFieldIsEmpty = compositionLocalOf { true }
|
|
65
|
+
|
|
66
|
+
class PlaceholderProps : ComposeProps
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Slot view for `BasicTextField.Placeholder`. Renders its children only while
|
|
70
|
+
* the field is empty.
|
|
71
|
+
*/
|
|
72
|
+
@SuppressLint("ViewConstructor")
|
|
73
|
+
class PlaceholderView(context: Context, appContext: AppContext) :
|
|
74
|
+
ExpoComposeView<PlaceholderProps>(context, appContext) {
|
|
75
|
+
override val props = PlaceholderProps()
|
|
76
|
+
|
|
77
|
+
@Composable
|
|
78
|
+
override fun ComposableScope.Content() {
|
|
79
|
+
if (LocalTextFieldIsEmpty.current) {
|
|
80
|
+
Children(this)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// endregion Placeholder plumbing
|
|
86
|
+
|
|
87
|
+
// region Props
|
|
88
|
+
|
|
89
|
+
@OptimizedComposeProps
|
|
90
|
+
data class BasicTextFieldProps(
|
|
91
|
+
val value: ObservableState = ObservableState(""),
|
|
92
|
+
val selection: ObservableState = ObservableState(mapOf("start" to 0, "end" to 0)),
|
|
93
|
+
val maxLength: Int? = null,
|
|
94
|
+
val autoFocus: Boolean = false,
|
|
95
|
+
val enabled: Boolean = true,
|
|
96
|
+
val readOnly: Boolean = false,
|
|
97
|
+
val singleLine: Boolean = false,
|
|
98
|
+
val maxLines: Int? = null,
|
|
99
|
+
val minLines: Int? = null,
|
|
100
|
+
val textStyle: TextFieldTextStyleRecord? = null,
|
|
101
|
+
val visualTransformation: String? = null,
|
|
102
|
+
val keyboardOptions: TextFieldKeyboardOptionsRecord? = null,
|
|
103
|
+
val cursorColor: Color? = null,
|
|
104
|
+
val textSelectionColors: TextFieldSelectionColorsRecord? = null,
|
|
105
|
+
val onValueChangeSync: WorkletCallback? = null,
|
|
106
|
+
val modifiers: ModifierList = emptyList()
|
|
107
|
+
) : ComposeProps
|
|
108
|
+
|
|
109
|
+
// endregion Props
|
|
110
|
+
|
|
111
|
+
// region View
|
|
112
|
+
|
|
113
|
+
@Composable
|
|
114
|
+
fun FunctionalComposableScope.BasicTextFieldContent(
|
|
115
|
+
props: BasicTextFieldProps,
|
|
116
|
+
setText: AsyncFunctionHandle<String>,
|
|
117
|
+
setSelection: AsyncFunctionHandle2<Int, Int>,
|
|
118
|
+
clear: AsyncFunctionHandle<Unit>,
|
|
119
|
+
focus: AsyncFunctionHandle<Unit>,
|
|
120
|
+
blur: AsyncFunctionHandle<Unit>,
|
|
121
|
+
onValueChanged: (TextFieldValuePayload) -> Unit,
|
|
122
|
+
onFocusChange: (GenericEventPayload1<Boolean>) -> Unit,
|
|
123
|
+
onKeyboardActionTriggered: (KeyboardActionEvent) -> Unit,
|
|
124
|
+
onSelectionChanged: (TextFieldSelectionPayload) -> Unit
|
|
125
|
+
) {
|
|
126
|
+
val core = rememberTextFieldCore(
|
|
127
|
+
value = props.value,
|
|
128
|
+
selection = props.selection,
|
|
129
|
+
maxLength = props.maxLength,
|
|
130
|
+
autoFocus = props.autoFocus,
|
|
131
|
+
keyboardOptionsRecord = props.keyboardOptions,
|
|
132
|
+
modifiers = props.modifiers,
|
|
133
|
+
onValueChangeSync = props.onValueChangeSync,
|
|
134
|
+
setText = setText,
|
|
135
|
+
setSelection = setSelection,
|
|
136
|
+
clear = clear,
|
|
137
|
+
focus = focus,
|
|
138
|
+
blur = blur,
|
|
139
|
+
onValueChanged = onValueChanged,
|
|
140
|
+
onFocusChange = onFocusChange,
|
|
141
|
+
onKeyboardActionTriggered = onKeyboardActionTriggered,
|
|
142
|
+
onSelectionChanged = onSelectionChanged
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
val singleLine = props.singleLine
|
|
146
|
+
val maxLines = props.maxLines ?: if (singleLine) 1 else Int.MAX_VALUE
|
|
147
|
+
val minLines = props.minLines ?: 1
|
|
148
|
+
|
|
149
|
+
val textStyle = props.textStyle.toTextStyle(appContext.reactContext).let {
|
|
150
|
+
if (it.color.isUnspecified) it.copy(color = MaterialTheme.colorScheme.onSurface) else it
|
|
151
|
+
}
|
|
152
|
+
val visualTransformation = props.visualTransformation.toVisualTransformation()
|
|
153
|
+
val cursorBrush = SolidColor(props.cursorColor.composeOrNull ?: MaterialTheme.colorScheme.primary)
|
|
154
|
+
|
|
155
|
+
val current = LocalTextSelectionColors.current
|
|
156
|
+
val selectionColors = props.textSelectionColors?.let { record ->
|
|
157
|
+
val handle = record.handleColor.composeOrNull
|
|
158
|
+
val background = record.backgroundColor.composeOrNull
|
|
159
|
+
if (handle == null && background == null) {
|
|
160
|
+
current
|
|
161
|
+
} else {
|
|
162
|
+
TextSelectionColors(
|
|
163
|
+
handleColor = handle ?: current.handleColor,
|
|
164
|
+
backgroundColor = background ?: handle?.copy(alpha = 0.4f) ?: current.backgroundColor
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
} ?: current
|
|
168
|
+
|
|
169
|
+
val decoration: (@Composable () -> Unit)? =
|
|
170
|
+
findChildSlotView(view, "decorationBox")?.let { slot -> { slot.renderSlot() } }
|
|
171
|
+
|
|
172
|
+
CompositionLocalProvider(LocalTextSelectionColors provides selectionColors) {
|
|
173
|
+
BasicTextField(
|
|
174
|
+
value = core.value,
|
|
175
|
+
onValueChange = core.onValueChange,
|
|
176
|
+
modifier = core.modifier,
|
|
177
|
+
enabled = props.enabled,
|
|
178
|
+
readOnly = props.readOnly,
|
|
179
|
+
textStyle = textStyle,
|
|
180
|
+
keyboardOptions = core.keyboardOptions,
|
|
181
|
+
keyboardActions = core.keyboardActions,
|
|
182
|
+
singleLine = singleLine,
|
|
183
|
+
maxLines = maxLines,
|
|
184
|
+
minLines = minLines,
|
|
185
|
+
visualTransformation = visualTransformation,
|
|
186
|
+
cursorBrush = cursorBrush,
|
|
187
|
+
decorationBox = { innerTextField ->
|
|
188
|
+
if (decoration != null) {
|
|
189
|
+
CompositionLocalProvider(
|
|
190
|
+
LocalInnerTextField provides innerTextField,
|
|
191
|
+
LocalTextFieldIsEmpty provides core.value.text.isEmpty()
|
|
192
|
+
) {
|
|
193
|
+
decoration()
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
innerTextField()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// endregion View
|