@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +54 -6
  4. package/android/src/main/java/expo/modules/ui/HostView.kt +0 -2
  5. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +65 -0
  6. package/android/src/main/java/expo/modules/ui/NavigationBarView.kt +95 -0
  7. package/android/src/main/java/expo/modules/ui/RNHostView.kt +182 -6
  8. package/android/src/main/java/expo/modules/ui/textfield/BasicTextField.kt +203 -0
  9. package/android/src/main/java/expo/modules/ui/{TextFieldView.kt → textfield/TextField.kt} +63 -267
  10. package/android/src/main/java/expo/modules/ui/textfield/TextFieldShared.kt +299 -0
  11. package/build/State/useNativeState.d.ts +8 -3
  12. package/build/State/useNativeState.d.ts.map +1 -1
  13. package/build/community/pager-view/PagerView.android.d.ts.map +1 -1
  14. package/build/jetpack-compose/NavigationBar/index.d.ts +101 -0
  15. package/build/jetpack-compose/NavigationBar/index.d.ts.map +1 -0
  16. package/build/jetpack-compose/TextField/BasicTextField.d.ts +36 -0
  17. package/build/jetpack-compose/TextField/BasicTextField.d.ts.map +1 -0
  18. package/build/jetpack-compose/TextField/TextField.d.ts +131 -0
  19. package/build/jetpack-compose/TextField/TextField.d.ts.map +1 -0
  20. package/build/jetpack-compose/TextField/index.d.ts +3 -244
  21. package/build/jetpack-compose/TextField/index.d.ts.map +1 -1
  22. package/build/jetpack-compose/TextField/shared.d.ts +171 -0
  23. package/build/jetpack-compose/TextField/shared.d.ts.map +1 -0
  24. package/build/jetpack-compose/index.d.ts +2 -1
  25. package/build/jetpack-compose/index.d.ts.map +1 -1
  26. package/build/jetpack-compose/modifiers/index.d.ts +42 -0
  27. package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
  28. package/build/swift-ui/DisclosureGroup/index.d.ts +11 -2
  29. package/build/swift-ui/DisclosureGroup/index.d.ts.map +1 -1
  30. package/build/swift-ui/Image/index.d.ts +7 -1
  31. package/build/swift-ui/Image/index.d.ts.map +1 -1
  32. package/build/swift-ui/Label/index.d.ts +5 -0
  33. package/build/swift-ui/Label/index.d.ts.map +1 -1
  34. package/build/swift-ui/modifiers/index.d.ts +100 -4
  35. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  36. package/build/universal/Collapsible/index.android.d.ts +1 -1
  37. package/build/universal/Collapsible/index.android.d.ts.map +1 -1
  38. package/build/universal/Collapsible/index.d.ts +1 -1
  39. package/build/universal/Collapsible/index.d.ts.map +1 -1
  40. package/build/universal/Collapsible/index.ios.d.ts +1 -1
  41. package/build/universal/Collapsible/index.ios.d.ts.map +1 -1
  42. package/build/universal/Collapsible/types.d.ts +5 -0
  43. package/build/universal/Collapsible/types.d.ts.map +1 -1
  44. package/build/universal/TextInput/index.android.d.ts.map +1 -1
  45. package/build/universal/TextInput/types.d.ts +5 -1
  46. package/build/universal/TextInput/types.d.ts.map +1 -1
  47. package/expo-module.config.json +1 -1
  48. package/ios/BottomSheetView.swift +1 -1
  49. package/ios/DisclosureGroupView.swift +36 -13
  50. package/ios/ImageView.swift +20 -14
  51. package/ios/Label.swift +26 -2
  52. package/ios/Modifiers/ButtonBorderShapeModifier.swift +46 -0
  53. package/ios/Modifiers/DynamicTypeSizeModifier.swift +56 -0
  54. package/ios/Modifiers/FontModifier.swift +4 -1
  55. package/ios/Modifiers/ImageScaleModifier.swift +29 -0
  56. package/ios/Modifiers/OnGeometryChangeModifier.swift +8 -16
  57. package/ios/Modifiers/ViewModifierRegistry.swift +89 -8
  58. package/ios/SecureFieldView.swift +17 -1
  59. package/ios/TextFieldView.swift +33 -2
  60. 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
  61. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.md5 +1 -0
  62. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha1 +1 -0
  63. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha256 +1 -0
  64. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha512 +1 -0
  65. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar +0 -0
  66. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.md5 +1 -0
  67. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha1 +1 -0
  68. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha256 +1 -0
  69. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha512 +1 -0
  70. 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
  71. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.md5 +1 -0
  72. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha1 +1 -0
  73. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha256 +1 -0
  74. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha512 +1 -0
  75. 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
  76. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.md5 +1 -0
  77. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha1 +1 -0
  78. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha256 +1 -0
  79. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha512 +1 -0
  80. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  81. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  82. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  83. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  84. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  85. package/package.json +4 -4
  86. package/src/State/index.fx.ts +4 -1
  87. package/src/State/useNativeState.ts +24 -13
  88. package/src/community/datetime-picker/DateTimePicker.tsx +1 -1
  89. package/src/community/menu/MenuView.ios.tsx +1 -1
  90. package/src/community/pager-view/PagerView.android.tsx +16 -2
  91. package/src/community/pager-view/PagerView.ios.tsx +1 -1
  92. package/src/community/picker/Picker.ios.tsx +1 -1
  93. package/src/community/segmented-control/SegmentedControl.ios.tsx +1 -1
  94. package/src/community/slider/Slider.ios.tsx +1 -1
  95. package/src/jetpack-compose/NavigationBar/index.tsx +174 -0
  96. package/src/jetpack-compose/TextField/BasicTextField.tsx +118 -0
  97. package/src/jetpack-compose/TextField/TextField.tsx +198 -0
  98. package/src/jetpack-compose/TextField/index.ts +19 -0
  99. package/src/jetpack-compose/TextField/{index.tsx → shared.ts} +71 -203
  100. package/src/jetpack-compose/index.ts +7 -0
  101. package/src/jetpack-compose/modifiers/index.ts +49 -0
  102. package/src/swift-ui/BottomSheet/index.tsx +1 -1
  103. package/src/swift-ui/DisclosureGroup/index.tsx +14 -2
  104. package/src/swift-ui/Image/index.tsx +16 -3
  105. package/src/swift-ui/Label/index.tsx +8 -1
  106. package/src/swift-ui/modifiers/index.ts +143 -5
  107. package/src/universal/Collapsible/index.android.tsx +10 -2
  108. package/src/universal/Collapsible/index.ios.tsx +17 -3
  109. package/src/universal/Collapsible/index.tsx +8 -2
  110. package/src/universal/Collapsible/types.ts +7 -0
  111. package/src/universal/TextInput/index.android.tsx +26 -33
  112. package/src/universal/TextInput/types.ts +5 -1
  113. package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +0 -28
  114. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.md5 +0 -1
  115. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha1 +0 -1
  116. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha256 +0 -1
  117. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha512 +0 -1
  118. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar +0 -0
  119. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.md5 +0 -1
  120. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha1 +0 -1
  121. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha256 +0 -1
  122. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha512 +0 -1
  123. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.md5 +0 -1
  124. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha1 +0 -1
  125. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha256 +0 -1
  126. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha512 +0 -1
  127. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.md5 +0 -1
  128. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha1 +0 -1
  129. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha256 +0 -1
  130. 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.types.OptimizedRecord
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 focusManager = LocalFocusManager.current
294
- val focusRequester = remember { FocusRequester() }
295
- val state = props.value
296
-
297
- setText.handle { text ->
298
- state.value = text
299
- // setText moves the cursor to the end; use setSelection afterwards to override.
300
- props.selection.value = mapOf("start" to text.length, "end" to text.length)
301
- }
302
- focus.handle {
303
- focusRequester.requestFocus()
304
- }
305
- blur.handle {
306
- focusManager.clearFocus()
307
- }
308
- setSelection.handle { start, end ->
309
- val text = state.value as? String ?: ""
310
- val clampedStart = start.coerceIn(0, text.length)
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 text = state.value as? String ?: ""
402
- val selection = props.selection.extractSelection(text.length)
403
-
404
- val localValue = remember { mutableStateOf(TextFieldValue(text, selection)) }
405
- if (localValue.value.text != text || localValue.value.selection != selection) {
406
- localValue.value = TextFieldValue(text, selection)
407
- }
408
-
409
- val value = localValue.value
410
-
411
- val onValueChange: (TextFieldValue) -> Unit = { incoming ->
412
- val new = props.maxLength?.let { max ->
413
- if (incoming.text.length > max) {
414
- val truncated = incoming.text.substring(0, max)
415
- incoming.copy(
416
- text = truncated,
417
- selection = TextRange(
418
- incoming.selection.start.coerceAtMost(max),
419
- incoming.selection.end.coerceAtMost(max)
420
- )
421
- )
422
- } else {
423
- null
424
- }
425
- } ?: incoming
426
- val prev = localValue.value
427
- localValue.value = new
428
- if (new.selection != prev.selection) {
429
- val cur = props.selection.value as? Map<*, *>
430
- val curStart = (cur?.get("start") as? Number)?.toInt()
431
- val curEnd = (cur?.get("end") as? Number)?.toInt()
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