@expo/ui 56.0.15 → 56.0.17
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 +47 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +54 -6
- package/android/src/main/java/expo/modules/ui/HostView.kt +0 -2
- package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +65 -0
- package/android/src/main/java/expo/modules/ui/NavigationBarView.kt +95 -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} +63 -267
- 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/pager-view/PagerView.android.d.ts.map +1 -1
- package/build/jetpack-compose/NavigationBar/index.d.ts +101 -0
- package/build/jetpack-compose/NavigationBar/index.d.ts.map +1 -0
- 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 +2 -1
- package/build/jetpack-compose/index.d.ts.map +1 -1
- package/build/jetpack-compose/modifiers/index.d.ts +42 -0
- package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/DisclosureGroup/index.d.ts +11 -2
- package/build/swift-ui/DisclosureGroup/index.d.ts.map +1 -1
- package/build/swift-ui/Image/index.d.ts +7 -1
- package/build/swift-ui/Image/index.d.ts.map +1 -1
- package/build/swift-ui/Label/index.d.ts +5 -0
- package/build/swift-ui/Label/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +100 -4
- package/build/swift-ui/modifiers/index.d.ts.map +1 -1
- package/build/universal/Collapsible/index.android.d.ts +1 -1
- package/build/universal/Collapsible/index.android.d.ts.map +1 -1
- package/build/universal/Collapsible/index.d.ts +1 -1
- package/build/universal/Collapsible/index.d.ts.map +1 -1
- package/build/universal/Collapsible/index.ios.d.ts +1 -1
- package/build/universal/Collapsible/index.ios.d.ts.map +1 -1
- package/build/universal/Collapsible/types.d.ts +5 -0
- package/build/universal/Collapsible/types.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/expo-module.config.json +1 -1
- package/ios/BottomSheetView.swift +1 -1
- package/ios/DisclosureGroupView.swift +36 -13
- package/ios/ImageView.swift +20 -14
- package/ios/Label.swift +26 -2
- package/ios/Modifiers/ButtonBorderShapeModifier.swift +46 -0
- package/ios/Modifiers/DynamicTypeSizeModifier.swift +56 -0
- package/ios/Modifiers/FontModifier.swift +4 -1
- package/ios/Modifiers/ImageScaleModifier.swift +29 -0
- package/ios/Modifiers/OnGeometryChangeModifier.swift +8 -16
- package/ios/Modifiers/ViewModifierRegistry.swift +89 -8
- package/ios/SecureFieldView.swift +17 -1
- package/ios/TextFieldView.swift +33 -2
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.15/expo.modules.ui-56.0.15-sources.jar → 56.0.17/expo.modules.ui-56.0.17-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.15/expo.modules.ui-56.0.15.module → 56.0.17/expo.modules.ui-56.0.17.module} +22 -22
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.15/expo.modules.ui-56.0.15.pom → 56.0.17/expo.modules.ui-56.0.17.pom} +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.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 +4 -4
- package/src/State/index.fx.ts +4 -1
- package/src/State/useNativeState.ts +24 -13
- 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 +16 -2
- package/src/community/pager-view/PagerView.ios.tsx +1 -1
- package/src/community/picker/Picker.ios.tsx +1 -1
- package/src/community/segmented-control/SegmentedControl.ios.tsx +1 -1
- package/src/community/slider/Slider.ios.tsx +1 -1
- package/src/jetpack-compose/NavigationBar/index.tsx +174 -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 +7 -0
- package/src/jetpack-compose/modifiers/index.ts +49 -0
- package/src/swift-ui/BottomSheet/index.tsx +1 -1
- package/src/swift-ui/DisclosureGroup/index.tsx +14 -2
- package/src/swift-ui/Image/index.tsx +16 -3
- package/src/swift-ui/Label/index.tsx +8 -1
- package/src/swift-ui/modifiers/index.ts +143 -5
- package/src/universal/Collapsible/index.android.tsx +10 -2
- package/src/universal/Collapsible/index.ios.tsx +17 -3
- package/src/universal/Collapsible/index.tsx +8 -2
- package/src/universal/Collapsible/types.ts +7 -0
- package/src/universal/TextInput/index.android.tsx +26 -33
- package/src/universal/TextInput/types.ts +5 -1
- package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +0 -28
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha512 +0 -1
|
@@ -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
|
|
@@ -1,44 +1,34 @@
|
|
|
1
|
-
package expo.modules.ui
|
|
1
|
+
package expo.modules.ui.textfield
|
|
2
2
|
|
|
3
3
|
import android.graphics.Color
|
|
4
|
-
import androidx.compose.foundation.text.KeyboardActions
|
|
5
|
-
import androidx.compose.foundation.text.KeyboardOptions
|
|
6
4
|
import androidx.compose.foundation.text.selection.TextSelectionColors
|
|
5
|
+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|
6
|
+
import androidx.compose.material3.MaterialExpressiveTheme
|
|
7
|
+
import androidx.compose.material3.MotionScheme
|
|
7
8
|
import androidx.compose.material3.OutlinedTextField
|
|
8
9
|
import androidx.compose.material3.OutlinedTextFieldDefaults
|
|
9
10
|
import androidx.compose.material3.TextField
|
|
10
11
|
import androidx.compose.material3.TextFieldColors
|
|
11
12
|
import androidx.compose.material3.TextFieldDefaults
|
|
12
13
|
import androidx.compose.runtime.Composable
|
|
13
|
-
import androidx.compose.runtime.LaunchedEffect
|
|
14
|
-
import androidx.compose.runtime.mutableStateOf
|
|
15
|
-
import androidx.compose.runtime.remember
|
|
16
|
-
import androidx.compose.ui.focus.FocusRequester
|
|
17
|
-
import androidx.compose.ui.focus.focusRequester
|
|
18
|
-
import androidx.compose.ui.focus.onFocusChanged
|
|
19
|
-
import androidx.compose.ui.platform.LocalFocusManager
|
|
20
|
-
import androidx.compose.ui.text.TextRange
|
|
21
|
-
import androidx.compose.ui.text.TextStyle
|
|
22
|
-
import androidx.compose.ui.text.input.ImeAction
|
|
23
|
-
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
24
|
-
import androidx.compose.ui.text.input.KeyboardType
|
|
25
|
-
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|
26
|
-
import androidx.compose.ui.text.input.TextFieldValue
|
|
27
|
-
import androidx.compose.ui.text.input.VisualTransformation
|
|
28
|
-
import androidx.compose.ui.text.style.TextAlign
|
|
29
|
-
import androidx.compose.ui.unit.TextUnit
|
|
30
|
-
import androidx.compose.ui.unit.sp
|
|
31
14
|
import expo.modules.kotlin.records.Field
|
|
32
15
|
import expo.modules.kotlin.records.Record
|
|
33
16
|
import expo.modules.kotlin.types.Enumerable
|
|
17
|
+
import expo.modules.kotlin.types.OptimizedRecord
|
|
34
18
|
import expo.modules.kotlin.views.AsyncFunctionHandle
|
|
35
19
|
import expo.modules.kotlin.views.AsyncFunctionHandle2
|
|
36
20
|
import expo.modules.kotlin.views.ComposeProps
|
|
37
21
|
import expo.modules.kotlin.views.FunctionalComposableScope
|
|
38
|
-
import expo.modules.kotlin.
|
|
22
|
+
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
23
|
+
import expo.modules.ui.GenericEventPayload1
|
|
24
|
+
import expo.modules.ui.ModifierList
|
|
25
|
+
import expo.modules.ui.ShapeRecord
|
|
26
|
+
import expo.modules.ui.composeOrNull
|
|
27
|
+
import expo.modules.ui.findChildSlotView
|
|
28
|
+
import expo.modules.ui.renderSlot
|
|
29
|
+
import expo.modules.ui.shapeFromShapeRecord
|
|
39
30
|
import expo.modules.ui.state.ObservableState
|
|
40
31
|
import expo.modules.ui.state.WorkletCallback
|
|
41
|
-
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
42
32
|
|
|
43
33
|
// region Records
|
|
44
34
|
|
|
@@ -47,25 +37,6 @@ enum class TextFieldVariant(val value: String) : Enumerable {
|
|
|
47
37
|
OUTLINED("outlined")
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
@OptimizedRecord
|
|
51
|
-
data class TextFieldKeyboardOptionsRecord(
|
|
52
|
-
@Field val capitalization: String? = null,
|
|
53
|
-
@Field val autoCorrectEnabled: Boolean? = null,
|
|
54
|
-
@Field val keyboardType: String? = null,
|
|
55
|
-
@Field val imeAction: String? = null
|
|
56
|
-
) : Record
|
|
57
|
-
|
|
58
|
-
@OptimizedRecord
|
|
59
|
-
data class TextFieldTextStyleRecord(
|
|
60
|
-
@Field val textAlign: TextAlignType? = null,
|
|
61
|
-
@Field val color: Color? = null,
|
|
62
|
-
@Field val fontSize: Float? = null,
|
|
63
|
-
@Field val fontFamily: String? = null,
|
|
64
|
-
@Field val fontWeight: TextFontWeight? = null,
|
|
65
|
-
@Field val lineHeight: Float? = null,
|
|
66
|
-
@Field val letterSpacing: Float? = null
|
|
67
|
-
) : Record
|
|
68
|
-
|
|
69
40
|
@OptimizedRecord
|
|
70
41
|
data class TextFieldColorsRecord(
|
|
71
42
|
// Text
|
|
@@ -129,21 +100,6 @@ data class TextFieldSelectionColorsRecord(
|
|
|
129
100
|
@Field val backgroundColor: Color? = null
|
|
130
101
|
) : Record
|
|
131
102
|
|
|
132
|
-
data class KeyboardActionEvent(
|
|
133
|
-
@Field val action: String,
|
|
134
|
-
@Field val value: String
|
|
135
|
-
) : Record
|
|
136
|
-
|
|
137
|
-
data class TextFieldSelectionPayload(
|
|
138
|
-
@Field val start: Int,
|
|
139
|
-
@Field val end: Int
|
|
140
|
-
) : Record
|
|
141
|
-
|
|
142
|
-
data class TextFieldValuePayload(
|
|
143
|
-
@Field val text: String,
|
|
144
|
-
@Field val selection: TextFieldSelectionPayload
|
|
145
|
-
) : Record
|
|
146
|
-
|
|
147
103
|
// endregion Records
|
|
148
104
|
|
|
149
105
|
// region Color builder
|
|
@@ -227,56 +183,9 @@ data class TextFieldProps(
|
|
|
227
183
|
|
|
228
184
|
// endregion Props
|
|
229
185
|
|
|
230
|
-
// region Mappers
|
|
231
|
-
|
|
232
|
-
private fun String?.toKeyboardType(): KeyboardType = when (this) {
|
|
233
|
-
"text" -> KeyboardType.Text
|
|
234
|
-
"number" -> KeyboardType.Number
|
|
235
|
-
"email" -> KeyboardType.Email
|
|
236
|
-
"phone" -> KeyboardType.Phone
|
|
237
|
-
"decimal" -> KeyboardType.Decimal
|
|
238
|
-
"password" -> KeyboardType.Password
|
|
239
|
-
"ascii" -> KeyboardType.Ascii
|
|
240
|
-
"uri" -> KeyboardType.Uri
|
|
241
|
-
"numberPassword" -> KeyboardType.NumberPassword
|
|
242
|
-
else -> KeyboardType.Text
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private fun String?.toCapitalization(): KeyboardCapitalization = when (this) {
|
|
246
|
-
"characters" -> KeyboardCapitalization.Characters
|
|
247
|
-
"none" -> KeyboardCapitalization.None
|
|
248
|
-
"sentences" -> KeyboardCapitalization.Sentences
|
|
249
|
-
"words" -> KeyboardCapitalization.Words
|
|
250
|
-
else -> KeyboardCapitalization.None
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private fun String?.toImeAction(): ImeAction = when (this) {
|
|
254
|
-
"default" -> ImeAction.Default
|
|
255
|
-
"none" -> ImeAction.None
|
|
256
|
-
"go" -> ImeAction.Go
|
|
257
|
-
"search" -> ImeAction.Search
|
|
258
|
-
"send" -> ImeAction.Send
|
|
259
|
-
"previous" -> ImeAction.Previous
|
|
260
|
-
"next" -> ImeAction.Next
|
|
261
|
-
"done" -> ImeAction.Done
|
|
262
|
-
else -> ImeAction.Default
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// endregion Mappers
|
|
266
|
-
|
|
267
|
-
// region Value helpers
|
|
268
|
-
|
|
269
|
-
private fun ObservableState.extractSelection(textLength: Int): TextRange {
|
|
270
|
-
val selMap = value as? Map<*, *>
|
|
271
|
-
val start = (selMap?.get("start") as? Number)?.toInt()?.coerceIn(0, textLength) ?: 0
|
|
272
|
-
val end = (selMap?.get("end") as? Number)?.toInt()?.coerceIn(0, textLength) ?: 0
|
|
273
|
-
return TextRange(start, end)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// endregion Value helpers
|
|
277
|
-
|
|
278
186
|
// region View
|
|
279
187
|
|
|
188
|
+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
|
280
189
|
@Composable
|
|
281
190
|
fun FunctionalComposableScope.TextFieldContent(
|
|
282
191
|
props: TextFieldProps,
|
|
@@ -290,31 +199,24 @@ fun FunctionalComposableScope.TextFieldContent(
|
|
|
290
199
|
onKeyboardActionTriggered: (KeyboardActionEvent) -> Unit,
|
|
291
200
|
onSelectionChanged: (TextFieldSelectionPayload) -> Unit
|
|
292
201
|
) {
|
|
293
|
-
val
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
val clampedEnd = end.coerceIn(0, text.length)
|
|
312
|
-
props.selection.value = mapOf("start" to clampedStart, "end" to clampedEnd)
|
|
313
|
-
}
|
|
314
|
-
clear.handle {
|
|
315
|
-
state.value = ""
|
|
316
|
-
props.selection.value = mapOf("start" to 0, "end" to 0)
|
|
317
|
-
}
|
|
202
|
+
val core = rememberTextFieldCore(
|
|
203
|
+
value = props.value,
|
|
204
|
+
selection = props.selection,
|
|
205
|
+
maxLength = props.maxLength,
|
|
206
|
+
autoFocus = props.autoFocus,
|
|
207
|
+
keyboardOptionsRecord = props.keyboardOptions,
|
|
208
|
+
modifiers = props.modifiers,
|
|
209
|
+
onValueChangeSync = props.onValueChangeSync,
|
|
210
|
+
setText = setText,
|
|
211
|
+
setSelection = setSelection,
|
|
212
|
+
clear = clear,
|
|
213
|
+
focus = focus,
|
|
214
|
+
blur = blur,
|
|
215
|
+
onValueChanged = onValueChanged,
|
|
216
|
+
onFocusChange = onFocusChange,
|
|
217
|
+
onKeyboardActionTriggered = onKeyboardActionTriggered,
|
|
218
|
+
onSelectionChanged = onSelectionChanged
|
|
219
|
+
)
|
|
318
220
|
|
|
319
221
|
// Slots
|
|
320
222
|
val label: (@Composable () -> Unit)? = findChildSlotView(view, "label")?.let { slot -> { slot.renderSlot() } }
|
|
@@ -325,58 +227,11 @@ fun FunctionalComposableScope.TextFieldContent(
|
|
|
325
227
|
val suffix: (@Composable () -> Unit)? = findChildSlotView(view, "suffix")?.let { slot -> { slot.renderSlot() } }
|
|
326
228
|
val supportingText: (@Composable () -> Unit)? = findChildSlotView(view, "supportingText")?.let { slot -> { slot.renderSlot() } }
|
|
327
229
|
|
|
328
|
-
// Keyboard
|
|
329
|
-
val kbOpts = props.keyboardOptions
|
|
330
|
-
val keyboardOptions = KeyboardOptions.Default.copy(
|
|
331
|
-
keyboardType = kbOpts?.keyboardType.toKeyboardType(),
|
|
332
|
-
autoCorrectEnabled = kbOpts?.autoCorrectEnabled ?: true,
|
|
333
|
-
capitalization = kbOpts?.capitalization.toCapitalization(),
|
|
334
|
-
imeAction = kbOpts?.imeAction.toImeAction()
|
|
335
|
-
)
|
|
336
|
-
val currentText = { state.value as? String ?: "" }
|
|
337
|
-
val keyboardActions = KeyboardActions(
|
|
338
|
-
onDone = {
|
|
339
|
-
defaultKeyboardAction(ImeAction.Done)
|
|
340
|
-
onKeyboardActionTriggered(KeyboardActionEvent("done", currentText()))
|
|
341
|
-
},
|
|
342
|
-
onGo = {
|
|
343
|
-
defaultKeyboardAction(ImeAction.Go)
|
|
344
|
-
onKeyboardActionTriggered(KeyboardActionEvent("go", currentText()))
|
|
345
|
-
},
|
|
346
|
-
onNext = {
|
|
347
|
-
defaultKeyboardAction(ImeAction.Next)
|
|
348
|
-
onKeyboardActionTriggered(KeyboardActionEvent("next", currentText()))
|
|
349
|
-
},
|
|
350
|
-
onPrevious = {
|
|
351
|
-
defaultKeyboardAction(ImeAction.Previous)
|
|
352
|
-
onKeyboardActionTriggered(KeyboardActionEvent("previous", currentText()))
|
|
353
|
-
},
|
|
354
|
-
onSearch = {
|
|
355
|
-
defaultKeyboardAction(ImeAction.Search)
|
|
356
|
-
onKeyboardActionTriggered(KeyboardActionEvent("search", currentText()))
|
|
357
|
-
},
|
|
358
|
-
onSend = {
|
|
359
|
-
defaultKeyboardAction(ImeAction.Send)
|
|
360
|
-
onKeyboardActionTriggered(KeyboardActionEvent("send", currentText()))
|
|
361
|
-
}
|
|
362
|
-
)
|
|
363
|
-
|
|
364
230
|
// Lines
|
|
365
231
|
val singleLine = props.singleLine
|
|
366
232
|
val maxLines = props.maxLines ?: if (singleLine) 1 else Int.MAX_VALUE
|
|
367
233
|
val minLines = props.minLines ?: 1
|
|
368
234
|
|
|
369
|
-
// Modifier
|
|
370
|
-
val modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
|
|
371
|
-
.focusRequester(focusRequester)
|
|
372
|
-
.onFocusChanged { focusState ->
|
|
373
|
-
onFocusChange(GenericEventPayload1(focusState.isFocused))
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (props.autoFocus) {
|
|
377
|
-
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
|
378
|
-
}
|
|
379
|
-
|
|
380
235
|
val isOutlined = props.variant == TextFieldVariant.OUTLINED
|
|
381
236
|
val shape = shapeFromShapeRecord(props.shape)
|
|
382
237
|
?: if (isOutlined) OutlinedTextFieldDefaults.shape else TextFieldDefaults.shape
|
|
@@ -398,99 +253,40 @@ fun FunctionalComposableScope.TextFieldContent(
|
|
|
398
253
|
}
|
|
399
254
|
} ?: baseColors
|
|
400
255
|
|
|
401
|
-
val
|
|
402
|
-
val
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (curStart != new.selection.start || curEnd != new.selection.end) {
|
|
433
|
-
props.selection.value = mapOf(
|
|
434
|
-
"start" to new.selection.start,
|
|
435
|
-
"end" to new.selection.end
|
|
436
|
-
)
|
|
437
|
-
}
|
|
438
|
-
onSelectionChanged(TextFieldSelectionPayload(new.selection.start, new.selection.end))
|
|
439
|
-
}
|
|
440
|
-
if (new.text != prev.text) {
|
|
441
|
-
state.value = new.text
|
|
442
|
-
val payload = TextFieldValuePayload(
|
|
443
|
-
text = new.text,
|
|
444
|
-
selection = TextFieldSelectionPayload(new.selection.start, new.selection.end)
|
|
256
|
+
val textStyle = props.textStyle.toTextStyle(appContext.reactContext)
|
|
257
|
+
val visualTransformation = props.visualTransformation.toVisualTransformation()
|
|
258
|
+
|
|
259
|
+
// Workaround (pending upstream fix, https://issuetracker.google.com/issues/519816993)
|
|
260
|
+
// the expressive motion scheme's spring overshoots >1f, and TextField's calculateHeight
|
|
261
|
+
// extrapolates that overshoot, transiently growing the field and jiggling surrounding
|
|
262
|
+
// content. Forcing the standard (non-overshooting) spatial spring removes the jiggle.
|
|
263
|
+
MaterialExpressiveTheme(motionScheme = MotionScheme.standard()) {
|
|
264
|
+
if (isOutlined) {
|
|
265
|
+
OutlinedTextField(
|
|
266
|
+
value = core.value, onValueChange = core.onValueChange, modifier = core.modifier,
|
|
267
|
+
enabled = props.enabled, readOnly = props.readOnly, textStyle = textStyle,
|
|
268
|
+
label = label, placeholder = placeholder,
|
|
269
|
+
leadingIcon = leadingIcon, trailingIcon = trailingIcon,
|
|
270
|
+
prefix = prefix, suffix = suffix, supportingText = supportingText,
|
|
271
|
+
isError = props.isError, visualTransformation = visualTransformation,
|
|
272
|
+
keyboardOptions = core.keyboardOptions, keyboardActions = core.keyboardActions,
|
|
273
|
+
singleLine = singleLine, maxLines = maxLines, minLines = minLines,
|
|
274
|
+
shape = shape, colors = colors
|
|
275
|
+
)
|
|
276
|
+
} else {
|
|
277
|
+
TextField(
|
|
278
|
+
value = core.value, onValueChange = core.onValueChange, modifier = core.modifier,
|
|
279
|
+
enabled = props.enabled, readOnly = props.readOnly, textStyle = textStyle,
|
|
280
|
+
label = label, placeholder = placeholder,
|
|
281
|
+
leadingIcon = leadingIcon, trailingIcon = trailingIcon,
|
|
282
|
+
prefix = prefix, suffix = suffix, supportingText = supportingText,
|
|
283
|
+
isError = props.isError, visualTransformation = visualTransformation,
|
|
284
|
+
keyboardOptions = core.keyboardOptions, keyboardActions = core.keyboardActions,
|
|
285
|
+
singleLine = singleLine, maxLines = maxLines, minLines = minLines,
|
|
286
|
+
shape = shape, colors = colors
|
|
445
287
|
)
|
|
446
|
-
onValueChanged(payload)
|
|
447
|
-
props.onValueChangeSync?.invoke(new.text)
|
|
448
288
|
}
|
|
449
289
|
}
|
|
450
|
-
|
|
451
|
-
val context = appContext.reactContext
|
|
452
|
-
val textStyle = props.textStyle?.let { textStyleProps ->
|
|
453
|
-
TextStyle(
|
|
454
|
-
color = colorToComposeColorOrNull(textStyleProps.color) ?: androidx.compose.ui.graphics.Color.Unspecified,
|
|
455
|
-
fontSize = textStyleProps.fontSize?.sp ?: TextUnit.Unspecified,
|
|
456
|
-
fontWeight = textStyleProps.fontWeight?.toComposeFontWeight(),
|
|
457
|
-
fontFamily = context?.let { resolveFontFamily(textStyleProps.fontFamily, it) },
|
|
458
|
-
letterSpacing = textStyleProps.letterSpacing?.sp ?: TextUnit.Unspecified,
|
|
459
|
-
lineHeight = textStyleProps.lineHeight?.sp ?: TextUnit.Unspecified,
|
|
460
|
-
textAlign = textStyleProps.textAlign?.toComposeTextAlign() ?: TextAlign.Unspecified
|
|
461
|
-
)
|
|
462
|
-
} ?: TextStyle.Default
|
|
463
|
-
|
|
464
|
-
val visualTransformation = when (props.visualTransformation) {
|
|
465
|
-
"password" -> PasswordVisualTransformation()
|
|
466
|
-
else -> VisualTransformation.None
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (isOutlined) {
|
|
470
|
-
OutlinedTextField(
|
|
471
|
-
value = value, onValueChange = onValueChange, modifier = modifier,
|
|
472
|
-
enabled = props.enabled, readOnly = props.readOnly, textStyle = textStyle,
|
|
473
|
-
label = label, placeholder = placeholder,
|
|
474
|
-
leadingIcon = leadingIcon, trailingIcon = trailingIcon,
|
|
475
|
-
prefix = prefix, suffix = suffix, supportingText = supportingText,
|
|
476
|
-
isError = props.isError, visualTransformation = visualTransformation,
|
|
477
|
-
keyboardOptions = keyboardOptions, keyboardActions = keyboardActions,
|
|
478
|
-
singleLine = singleLine, maxLines = maxLines, minLines = minLines,
|
|
479
|
-
shape = shape, colors = colors
|
|
480
|
-
)
|
|
481
|
-
} else {
|
|
482
|
-
TextField(
|
|
483
|
-
value = value, onValueChange = onValueChange, modifier = modifier,
|
|
484
|
-
enabled = props.enabled, readOnly = props.readOnly, textStyle = textStyle,
|
|
485
|
-
label = label, placeholder = placeholder,
|
|
486
|
-
leadingIcon = leadingIcon, trailingIcon = trailingIcon,
|
|
487
|
-
prefix = prefix, suffix = suffix, supportingText = supportingText,
|
|
488
|
-
isError = props.isError, visualTransformation = visualTransformation,
|
|
489
|
-
keyboardOptions = keyboardOptions, keyboardActions = keyboardActions,
|
|
490
|
-
singleLine = singleLine, maxLines = maxLines, minLines = minLines,
|
|
491
|
-
shape = shape, colors = colors
|
|
492
|
-
)
|
|
493
|
-
}
|
|
494
290
|
}
|
|
495
291
|
|
|
496
292
|
// endregion View
|