1 /*
<lambda>null2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.material
18 
19 import androidx.compose.foundation.BorderStroke
20 import androidx.compose.foundation.ScrollState
21 import androidx.compose.foundation.interaction.Interaction
22 import androidx.compose.foundation.interaction.MutableInteractionSource
23 import androidx.compose.foundation.layout.Box
24 import androidx.compose.foundation.layout.PaddingValues
25 import androidx.compose.foundation.layout.calculateEndPadding
26 import androidx.compose.foundation.layout.calculateStartPadding
27 import androidx.compose.foundation.layout.defaultMinSize
28 import androidx.compose.foundation.layout.padding
29 import androidx.compose.foundation.rememberScrollState
30 import androidx.compose.foundation.shape.ZeroCornerSize
31 import androidx.compose.foundation.text.BasicTextField
32 import androidx.compose.foundation.text.KeyboardActions
33 import androidx.compose.foundation.text.KeyboardOptions
34 import androidx.compose.foundation.text.input.InputTransformation
35 import androidx.compose.foundation.text.input.KeyboardActionHandler
36 import androidx.compose.foundation.text.input.OutputTransformation
37 import androidx.compose.foundation.text.input.TextFieldBuffer
38 import androidx.compose.foundation.text.input.TextFieldLineLimits
39 import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
40 import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
41 import androidx.compose.foundation.text.input.TextFieldState
42 import androidx.compose.material.TextFieldDefaults.indicatorLine
43 import androidx.compose.material.internal.subtractConstraintSafely
44 import androidx.compose.runtime.Composable
45 import androidx.compose.runtime.remember
46 import androidx.compose.ui.Alignment
47 import androidx.compose.ui.Modifier
48 import androidx.compose.ui.draw.drawWithContent
49 import androidx.compose.ui.geometry.Offset
50 import androidx.compose.ui.graphics.Shape
51 import androidx.compose.ui.graphics.SolidColor
52 import androidx.compose.ui.graphics.takeOrElse
53 import androidx.compose.ui.layout.AlignmentLine
54 import androidx.compose.ui.layout.IntrinsicMeasurable
55 import androidx.compose.ui.layout.IntrinsicMeasureScope
56 import androidx.compose.ui.layout.LastBaseline
57 import androidx.compose.ui.layout.Layout
58 import androidx.compose.ui.layout.Measurable
59 import androidx.compose.ui.layout.MeasurePolicy
60 import androidx.compose.ui.layout.MeasureResult
61 import androidx.compose.ui.layout.MeasureScope
62 import androidx.compose.ui.layout.Placeable
63 import androidx.compose.ui.layout.layoutId
64 import androidx.compose.ui.platform.LocalLayoutDirection
65 import androidx.compose.ui.text.TextStyle
66 import androidx.compose.ui.text.input.ImeAction
67 import androidx.compose.ui.text.input.KeyboardType
68 import androidx.compose.ui.text.input.TextFieldValue
69 import androidx.compose.ui.text.input.VisualTransformation
70 import androidx.compose.ui.unit.Constraints
71 import androidx.compose.ui.unit.Dp
72 import androidx.compose.ui.unit.coerceAtLeast
73 import androidx.compose.ui.unit.constrainHeight
74 import androidx.compose.ui.unit.constrainWidth
75 import androidx.compose.ui.unit.dp
76 import androidx.compose.ui.unit.offset
77 import androidx.compose.ui.util.fastFirst
78 import androidx.compose.ui.util.fastFirstOrNull
79 import kotlin.math.max
80 import kotlin.math.roundToInt
81 
82 /**
83  * [Material Design filled text
84  * field](https://m2.material.io/components/text-fields#filled-text-field).
85  *
86  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
87  * when surrounded by other content and components.
88  *
89  * ![Filled text field
90  * image](https://developer.android.com/images/reference/androidx/compose/material/filled-text-field.png)
91  *
92  * If you are looking for an outlined version, see [OutlinedTextField].
93  *
94  * This overload of [TextField] uses [TextFieldState] to keep track of its text content and position
95  * of the cursor or selection.
96  *
97  * A simple single line text field looks like:
98  *
99  * @sample androidx.compose.material.samples.SimpleTextFieldSample
100  *
101  * You can control the initial text input and selection:
102  *
103  * @sample androidx.compose.material.samples.TextFieldWithInitialValueAndSelection
104  *
105  * You may provide a placeholder:
106  *
107  * @sample androidx.compose.material.samples.TextFieldWithPlaceholder
108  *
109  * You can also provide leading and trailing icons:
110  *
111  * @sample androidx.compose.material.samples.TextFieldWithIcons
112  *
113  * To handle the error input state, use [isError] parameter:
114  *
115  * @sample androidx.compose.material.samples.TextFieldWithErrorState
116  *
117  * Additionally, you may provide additional message at the bottom:
118  *
119  * @sample androidx.compose.material.samples.TextFieldWithHelperMessage
120  *
121  * Hiding a software keyboard on IME action performed:
122  *
123  * @sample androidx.compose.material.samples.TextFieldWithHideKeyboardOnImeAction
124  * @param state [TextFieldState] object that holds the internal editing state of this text field.
125  * @param modifier a [Modifier] for this text field
126  * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
127  *   be neither editable nor focusable, the input of the text field will not be selectable, visually
128  *   text field will appear in the disabled UI state
129  * @param readOnly controls the editable state of the [TextField]. When `true`, the text field can
130  *   not be modified, however, a user can focus it and copy text from it. Read-only text fields are
131  *   usually used to display pre-filled forms that user can not edit
132  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
133  *   [LocalTextStyle] defined by the theme
134  * @param label the optional label to be displayed inside the text field container. The default text
135  *   style for internal [Text] is [Typography.caption] when the text field is in focus and
136  *   [Typography.subtitle1] when the text field is not in focus
137  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
138  *   the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
139  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
140  *   container
141  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
142  *   container
143  * @param isError indicates if the text field's current value is in error. If set to true, the
144  *   label, bottom indicator and trailing icon by default will be displayed in error color
145  * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
146  *   to the [TextFieldState] made by the user. The transformation will be applied to changes made by
147  *   hardware and software keyboard events, pasting or dropping text, accessibility services, and
148  *   tests. The transformation will _not_ be applied when changing the [state] programmatically, or
149  *   when the transformation is changed. If the transformation is changed on an existing text field,
150  *   it will be applied to the next user edit. the transformation will not immediately affect the
151  *   current [state].
152  * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
153  *   text field are presented.
154  * @param keyboardOptions software keyboard options that contains configuration such as
155  *   [KeyboardType] and [ImeAction].
156  * @param onKeyboardAction Called when the user presses the action button in the input method editor
157  *   (IME), or by pressing the enter key on a hardware keyboard. By default this parameter is null,
158  *   and would execute the default behavior for a received IME Action e.g., [ImeAction.Done] would
159  *   close the keyboard, [ImeAction.Next] would switch the focus to the next focusable item on the
160  *   screen.
161  * @param lineLimits Whether the text field should be [SingleLine], scroll horizontally, and ignore
162  *   newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed, all newline
163  *   characters ('\n') within the text will be replaced with regular whitespace (' '), ensuring that
164  *   the contents of the text field are presented in a single line.
165  * @param scrollState Scroll state that manages either horizontal or vertical scroll of the text
166  *   field. If [lineLimits] is [SingleLine], this text field is treated as single line with
167  *   horizontal scroll behavior. In other cases the text field becomes vertically scrollable.
168  * @param shape the shape of the text field's container
169  * @param colors [TextFieldColors] that will be used to resolve color of the text, content
170  *   (including label, placeholder, leading and trailing icons, indicator line) and background for
171  *   this text field in different states. See [TextFieldDefaults.textFieldColors]
172  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
173  *   emitting [Interaction]s for this text field. You can use this to change the text field's
174  *   appearance or preview the text field in different states. Note that if `null` is provided,
175  *   interactions will still happen internally.
176  */
177 @Composable
178 fun TextField(
179     state: TextFieldState,
180     modifier: Modifier = Modifier,
181     enabled: Boolean = true,
182     readOnly: Boolean = false,
183     textStyle: TextStyle = LocalTextStyle.current,
184     label: @Composable (() -> Unit)? = null,
185     placeholder: @Composable (() -> Unit)? = null,
186     leadingIcon: @Composable (() -> Unit)? = null,
187     trailingIcon: @Composable (() -> Unit)? = null,
188     isError: Boolean = false,
189     inputTransformation: InputTransformation? = null,
190     outputTransformation: OutputTransformation? = null,
191     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
192     onKeyboardAction: KeyboardActionHandler? = null,
193     lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
194     scrollState: ScrollState = rememberScrollState(),
195     shape: Shape = TextFieldDefaults.TextFieldShape,
196     colors: TextFieldColors = TextFieldDefaults.textFieldColors(),
197     interactionSource: MutableInteractionSource? = null,
198 ) {
199     @Suppress("NAME_SHADOWING")
200     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
201     // If color is not provided via the text style, use content color as a default
202     val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
203     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
204 
205     BasicTextField(
206         state = state,
207         modifier =
208             modifier
209                 .indicatorLine(enabled, isError, interactionSource, colors)
210                 .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
211                 .defaultMinSize(
212                     minWidth = TextFieldDefaults.MinWidth,
213                     minHeight = TextFieldDefaults.MinHeight
214                 ),
215         enabled = enabled,
216         readOnly = readOnly,
217         textStyle = mergedTextStyle,
218         cursorBrush = SolidColor(colors.cursorColor(isError).value),
219         inputTransformation = inputTransformation,
220         outputTransformation = outputTransformation,
221         keyboardOptions = keyboardOptions,
222         onKeyboardAction = onKeyboardAction,
223         interactionSource = interactionSource,
224         scrollState = scrollState,
225         lineLimits = lineLimits,
226         decorator = { innerTextField ->
227             val textPostTransformation =
228                 if (outputTransformation == null) {
229                     state.text.toString()
230                 } else {
231                     // TODO: use constructor to create TextFieldBuffer from TextFieldState when
232                     // available
233                     lateinit var buffer: TextFieldBuffer
234                     state.edit { buffer = this }
235                     // after edit completes, mutations on buffer are ineffective
236                     with(outputTransformation) { buffer.transformOutput() }
237                     buffer.asCharSequence().toString()
238                 }
239 
240             TextFieldDefaults.TextFieldDecorationBox(
241                 value = textPostTransformation,
242                 visualTransformation = VisualTransformation.None,
243                 innerTextField = innerTextField,
244                 placeholder = placeholder,
245                 label = label,
246                 leadingIcon = leadingIcon,
247                 trailingIcon = trailingIcon,
248                 singleLine = lineLimits == SingleLine,
249                 enabled = enabled,
250                 isError = isError,
251                 interactionSource = interactionSource,
252                 shape = shape,
253                 colors = colors,
254             )
255         }
256     )
257 }
258 
259 /**
260  * [Material Design filled text
261  * field](https://m2.material.io/components/text-fields#filled-text-field).
262  *
263  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
264  * when surrounded by other content and components.
265  *
266  * ![Filled text field
267  * image](https://developer.android.com/images/reference/androidx/compose/material/filled-text-field.png)
268  *
269  * If you are looking for an outlined version, see [OutlinedTextField].
270  *
271  * If apart from input text change you also want to observe the cursor location, selection range, or
272  * IME composition use the TextField overload with the [TextFieldValue] parameter instead.
273  *
274  * @param value the input text to be shown in the text field
275  * @param onValueChange the callback that is triggered when the input service updates the text. An
276  *   updated text comes as a parameter of the callback
277  * @param modifier a [Modifier] for this text field
278  * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
279  *   be neither editable nor focusable, the input of the text field will not be selectable, visually
280  *   text field will appear in the disabled UI state
281  * @param readOnly controls the editable state of the [TextField]. When `true`, the text field can
282  *   not be modified, however, a user can focus it and copy text from it. Read-only text fields are
283  *   usually used to display pre-filled forms that user can not edit
284  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
285  *   [LocalTextStyle] defined by the theme
286  * @param label the optional label to be displayed inside the text field container. The default text
287  *   style for internal [Text] is [Typography.caption] when the text field is in focus and
288  *   [Typography.subtitle1] when the text field is not in focus
289  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
290  *   the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
291  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
292  *   container
293  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
294  *   container
295  * @param isError indicates if the text field's current value is in error. If set to true, the
296  *   label, bottom indicator and trailing icon by default will be displayed in error color
297  * @param visualTransformation transforms the visual representation of the input [value] For
298  *   example, you can use
299  *   [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to
300  *   create a password text field. By default no visual transformation is applied
301  * @param keyboardOptions software keyboard options that contains configuration such as
302  *   [KeyboardType] and [ImeAction].
303  * @param keyboardActions when the input service emits an IME action, the corresponding callback is
304  *   called. Note that this IME action may be different from what you specified in
305  *   [KeyboardOptions.imeAction].
306  * @param singleLine when set to true, this text field becomes a single horizontally scrolling text
307  *   field instead of wrapping onto multiple lines. The keyboard will be informed to not show the
308  *   return key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the maxLines
309  *   attribute will be automatically set to 1.
310  * @param maxLines the maximum height in terms of maximum number of visible lines. It is required
311  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
312  * @param minLines the minimum height in terms of minimum number of visible lines. It is required
313  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
314  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
315  *   emitting [Interaction]s for this text field. You can use this to change the text field's
316  *   appearance or preview the text field in different states. Note that if `null` is provided,
317  *   interactions will still happen internally.
318  * @param shape the shape of the text field's container
319  * @param colors [TextFieldColors] that will be used to resolve color of the text, content
320  *   (including label, placeholder, leading and trailing icons, indicator line) and background for
321  *   this text field in different states. See [TextFieldDefaults.textFieldColors]
322  */
323 @Composable
TextFieldnull324 fun TextField(
325     value: String,
326     onValueChange: (String) -> Unit,
327     modifier: Modifier = Modifier,
328     enabled: Boolean = true,
329     readOnly: Boolean = false,
330     textStyle: TextStyle = LocalTextStyle.current,
331     label: @Composable (() -> Unit)? = null,
332     placeholder: @Composable (() -> Unit)? = null,
333     leadingIcon: @Composable (() -> Unit)? = null,
334     trailingIcon: @Composable (() -> Unit)? = null,
335     isError: Boolean = false,
336     visualTransformation: VisualTransformation = VisualTransformation.None,
337     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
338     keyboardActions: KeyboardActions = KeyboardActions(),
339     singleLine: Boolean = false,
340     maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
341     minLines: Int = 1,
342     interactionSource: MutableInteractionSource? = null,
343     shape: Shape = TextFieldDefaults.TextFieldShape,
344     colors: TextFieldColors = TextFieldDefaults.textFieldColors()
345 ) {
346     @Suppress("NAME_SHADOWING")
347     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
348     // If color is not provided via the text style, use content color as a default
349     val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
350     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
351 
352     BasicTextField(
353         value = value,
354         modifier =
355             modifier
356                 .indicatorLine(enabled, isError, interactionSource, colors)
357                 .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
358                 .defaultMinSize(
359                     minWidth = TextFieldDefaults.MinWidth,
360                     minHeight = TextFieldDefaults.MinHeight
361                 ),
362         onValueChange = onValueChange,
363         enabled = enabled,
364         readOnly = readOnly,
365         textStyle = mergedTextStyle,
366         cursorBrush = SolidColor(colors.cursorColor(isError).value),
367         visualTransformation = visualTransformation,
368         keyboardOptions = keyboardOptions,
369         keyboardActions = keyboardActions,
370         interactionSource = interactionSource,
371         singleLine = singleLine,
372         maxLines = maxLines,
373         minLines = minLines,
374         decorationBox =
375             @Composable { innerTextField ->
376                 // places leading icon, text field with label and placeholder, trailing icon
377                 TextFieldDefaults.TextFieldDecorationBox(
378                     value = value,
379                     visualTransformation = visualTransformation,
380                     innerTextField = innerTextField,
381                     placeholder = placeholder,
382                     label = label,
383                     leadingIcon = leadingIcon,
384                     trailingIcon = trailingIcon,
385                     singleLine = singleLine,
386                     enabled = enabled,
387                     isError = isError,
388                     interactionSource = interactionSource,
389                     shape = shape,
390                     colors = colors,
391                 )
392             }
393     )
394 }
395 
396 @Deprecated(
397     "Maintained for binary compatibility. Use version with minLines instead",
398     level = DeprecationLevel.HIDDEN
399 )
400 @Composable
TextFieldnull401 fun TextField(
402     value: String,
403     onValueChange: (String) -> Unit,
404     modifier: Modifier = Modifier,
405     enabled: Boolean = true,
406     readOnly: Boolean = false,
407     textStyle: TextStyle = LocalTextStyle.current,
408     label: @Composable (() -> Unit)? = null,
409     placeholder: @Composable (() -> Unit)? = null,
410     leadingIcon: @Composable (() -> Unit)? = null,
411     trailingIcon: @Composable (() -> Unit)? = null,
412     isError: Boolean = false,
413     visualTransformation: VisualTransformation = VisualTransformation.None,
414     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
415     keyboardActions: KeyboardActions = KeyboardActions(),
416     singleLine: Boolean = false,
417     maxLines: Int = Int.MAX_VALUE,
418     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
419     shape: Shape =
420         MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
421     colors: TextFieldColors = TextFieldDefaults.textFieldColors()
422 ) {
423     TextField(
424         value,
425         onValueChange,
426         modifier,
427         enabled,
428         readOnly,
429         textStyle,
430         label,
431         placeholder,
432         leadingIcon,
433         trailingIcon,
434         isError,
435         visualTransformation,
436         keyboardOptions,
437         keyboardActions,
438         singleLine,
439         maxLines,
440         1,
441         interactionSource,
442         shape,
443         colors
444     )
445 }
446 
447 /**
448  * [Material Design filled text
449  * field](https://m2.material.io/components/text-fields#filled-text-field).
450  *
451  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
452  * when surrounded by other content and components.
453  *
454  * ![Filled text field
455  * image](https://developer.android.com/images/reference/androidx/compose/material/filled-text-field.png)
456  *
457  * If you are looking for an outlined version, see [OutlinedTextField]. For a text field
458  * specifically designed for passwords or other secure content, see [SecureTextField].
459  *
460  * This overload provides access to the input text, cursor position, selection range and IME
461  * composition. If you only want to observe an input text change, use the TextField overload with
462  * the [String] parameter instead.
463  *
464  * @param value the input [TextFieldValue] to be shown in the text field
465  * @param onValueChange the callback that is triggered when the input service updates values in
466  *   [TextFieldValue]. An updated [TextFieldValue] comes as a parameter of the callback
467  * @param modifier a [Modifier] for this text field
468  * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
469  *   be neither editable nor focusable, the input of the text field will not be selectable, visually
470  *   text field will appear in the disabled UI state
471  * @param readOnly controls the editable state of the [TextField]. When `true`, the text field can
472  *   not be modified, however, a user can focus it and copy text from it. Read-only text fields are
473  *   usually used to display pre-filled forms that user can not edit
474  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
475  *   [LocalTextStyle] defined by the theme
476  * @param label the optional label to be displayed inside the text field container. The default text
477  *   style for internal [Text] is [Typography.caption] when the text field is in focus and
478  *   [Typography.subtitle1] when the text field is not in focus
479  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
480  *   the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
481  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
482  *   container
483  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
484  *   container
485  * @param isError indicates if the text field's current value is in error state. If set to true, the
486  *   label, bottom indicator and trailing icon by default will be displayed in error color
487  * @param visualTransformation transforms the visual representation of the input [value]. For
488  *   example, you can use
489  *   [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to
490  *   create a password text field. By default no visual transformation is applied
491  * @param keyboardOptions software keyboard options that contains configuration such as
492  *   [KeyboardType] and [ImeAction].
493  * @param keyboardActions when the input service emits an IME action, the corresponding callback is
494  *   called. Note that this IME action may be different from what you specified in
495  *   [KeyboardOptions.imeAction].
496  * @param singleLine when set to true, this text field becomes a single horizontally scrolling text
497  *   field instead of wrapping onto multiple lines. The keyboard will be informed to not show the
498  *   return key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the maxLines
499  *   attribute will be automatically set to 1.
500  * @param maxLines the maximum height in terms of maximum number of visible lines. It is required
501  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
502  * @param minLines the minimum height in terms of minimum number of visible lines. It is required
503  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
504  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
505  *   emitting [Interaction]s for this text field. You can use this to change the text field's
506  *   appearance or preview the text field in different states. Note that if `null` is provided,
507  *   interactions will still happen internally.
508  * @param shape the shape of the text field's container
509  * @param colors [TextFieldColors] that will be used to resolve color of the text, content
510  *   (including label, placeholder, leading and trailing icons, indicator line) and background for
511  *   this text field in different states. See [TextFieldDefaults.textFieldColors]
512  */
513 @Composable
TextFieldnull514 fun TextField(
515     value: TextFieldValue,
516     onValueChange: (TextFieldValue) -> Unit,
517     modifier: Modifier = Modifier,
518     enabled: Boolean = true,
519     readOnly: Boolean = false,
520     textStyle: TextStyle = LocalTextStyle.current,
521     label: @Composable (() -> Unit)? = null,
522     placeholder: @Composable (() -> Unit)? = null,
523     leadingIcon: @Composable (() -> Unit)? = null,
524     trailingIcon: @Composable (() -> Unit)? = null,
525     isError: Boolean = false,
526     visualTransformation: VisualTransformation = VisualTransformation.None,
527     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
528     keyboardActions: KeyboardActions = KeyboardActions(),
529     singleLine: Boolean = false,
530     maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
531     minLines: Int = 1,
532     interactionSource: MutableInteractionSource? = null,
533     shape: Shape = TextFieldDefaults.TextFieldShape,
534     colors: TextFieldColors = TextFieldDefaults.textFieldColors()
535 ) {
536     @Suppress("NAME_SHADOWING")
537     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
538     // If color is not provided via the text style, use content color as a default
539     val textColor = textStyle.color.takeOrElse { colors.textColor(enabled).value }
540     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
541 
542     BasicTextField(
543         value = value,
544         modifier =
545             modifier
546                 .indicatorLine(enabled, isError, interactionSource, colors)
547                 .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
548                 .defaultMinSize(
549                     minWidth = TextFieldDefaults.MinWidth,
550                     minHeight = TextFieldDefaults.MinHeight
551                 ),
552         onValueChange = onValueChange,
553         enabled = enabled,
554         readOnly = readOnly,
555         textStyle = mergedTextStyle,
556         cursorBrush = SolidColor(colors.cursorColor(isError).value),
557         visualTransformation = visualTransformation,
558         keyboardOptions = keyboardOptions,
559         keyboardActions = keyboardActions,
560         interactionSource = interactionSource,
561         singleLine = singleLine,
562         maxLines = maxLines,
563         minLines = minLines,
564         decorationBox =
565             @Composable { innerTextField ->
566                 // places leading icon, text field with label and placeholder, trailing icon
567                 TextFieldDefaults.TextFieldDecorationBox(
568                     value = value.text,
569                     visualTransformation = visualTransformation,
570                     innerTextField = innerTextField,
571                     placeholder = placeholder,
572                     label = label,
573                     leadingIcon = leadingIcon,
574                     trailingIcon = trailingIcon,
575                     singleLine = singleLine,
576                     enabled = enabled,
577                     isError = isError,
578                     interactionSource = interactionSource,
579                     shape = shape,
580                     colors = colors,
581                 )
582             }
583     )
584 }
585 
586 @Deprecated(
587     "Maintained for binary compatibility. Use version with minLines instead",
588     level = DeprecationLevel.HIDDEN
589 )
590 @Composable
TextFieldnull591 fun TextField(
592     value: TextFieldValue,
593     onValueChange: (TextFieldValue) -> Unit,
594     modifier: Modifier = Modifier,
595     enabled: Boolean = true,
596     readOnly: Boolean = false,
597     textStyle: TextStyle = LocalTextStyle.current,
598     label: @Composable (() -> Unit)? = null,
599     placeholder: @Composable (() -> Unit)? = null,
600     leadingIcon: @Composable (() -> Unit)? = null,
601     trailingIcon: @Composable (() -> Unit)? = null,
602     isError: Boolean = false,
603     visualTransformation: VisualTransformation = VisualTransformation.None,
604     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
605     keyboardActions: KeyboardActions = KeyboardActions(),
606     singleLine: Boolean = false,
607     maxLines: Int = Int.MAX_VALUE,
608     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
609     shape: Shape = TextFieldDefaults.TextFieldShape,
610     colors: TextFieldColors = TextFieldDefaults.textFieldColors()
611 ) {
612     TextField(
613         value,
614         onValueChange,
615         modifier,
616         enabled,
617         readOnly,
618         textStyle,
619         label,
620         placeholder,
621         leadingIcon,
622         trailingIcon,
623         isError,
624         visualTransformation,
625         keyboardOptions,
626         keyboardActions,
627         singleLine,
628         maxLines,
629         1,
630         interactionSource,
631         shape,
632         colors
633     )
634 }
635 
636 /**
637  * Composable responsible for measuring and laying out leading and trailing icons, label,
638  * placeholder and the input field.
639  */
640 @Composable
641 internal fun TextFieldLayout(
642     modifier: Modifier,
643     textField: @Composable () -> Unit,
644     label: @Composable (() -> Unit)?,
645     placeholder: @Composable ((Modifier) -> Unit)?,
646     leading: @Composable (() -> Unit)?,
647     trailing: @Composable (() -> Unit)?,
648     singleLine: Boolean,
649     animationProgress: Float,
650     paddingValues: PaddingValues
651 ) {
652     val measurePolicy =
<lambda>null653         remember(singleLine, animationProgress, paddingValues) {
654             TextFieldMeasurePolicy(singleLine, animationProgress, paddingValues)
655         }
656     val layoutDirection = LocalLayoutDirection.current
657     Layout(
658         modifier = modifier,
<lambda>null659         content = {
660             if (leading != null) {
661                 Box(
662                     modifier = Modifier.layoutId(LeadingId).minimumInteractiveComponentSize(),
663                     contentAlignment = Alignment.Center
664                 ) {
665                     leading()
666                 }
667             }
668             if (trailing != null) {
669                 Box(
670                     modifier = Modifier.layoutId(TrailingId).minimumInteractiveComponentSize(),
671                     contentAlignment = Alignment.Center
672                 ) {
673                     trailing()
674                 }
675             }
676 
677             val startTextFieldPadding = paddingValues.calculateStartPadding(layoutDirection)
678             val endTextFieldPadding = paddingValues.calculateEndPadding(layoutDirection)
679             val padding =
680                 Modifier.padding(
681                     start =
682                         if (leading != null) {
683                             (startTextFieldPadding - HorizontalIconPadding).coerceAtLeast(0.dp)
684                         } else {
685                             startTextFieldPadding
686                         },
687                     end =
688                         if (trailing != null) {
689                             (endTextFieldPadding - HorizontalIconPadding).coerceAtLeast(0.dp)
690                         } else {
691                             endTextFieldPadding
692                         }
693                 )
694             if (placeholder != null) {
695                 placeholder(Modifier.layoutId(PlaceholderId).then(padding))
696             }
697             if (label != null) {
698                 Box(Modifier.layoutId(LabelId).then(padding)) { label() }
699             }
700             Box(
701                 modifier = Modifier.layoutId(TextFieldId).then(padding),
702                 propagateMinConstraints = true,
703             ) {
704                 textField()
705             }
706         },
707         measurePolicy = measurePolicy
708     )
709 }
710 
711 private class TextFieldMeasurePolicy(
712     private val singleLine: Boolean,
713     private val animationProgress: Float,
714     private val paddingValues: PaddingValues
715 ) : MeasurePolicy {
measurenull716     override fun MeasureScope.measure(
717         measurables: List<Measurable>,
718         constraints: Constraints
719     ): MeasureResult {
720         val topPaddingValue = paddingValues.calculateTopPadding().roundToPx()
721         val bottomPaddingValue = paddingValues.calculateBottomPadding().roundToPx()
722 
723         // padding between label and input text
724         val topPadding = TextFieldTopPadding.roundToPx()
725         var occupiedSpaceHorizontally = 0
726 
727         // measure leading icon
728         val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
729         val leadingPlaceable =
730             measurables.fastFirstOrNull { it.layoutId == LeadingId }?.measure(looseConstraints)
731         occupiedSpaceHorizontally += widthOrZero(leadingPlaceable)
732 
733         // measure trailing icon
734         val trailingPlaceable =
735             measurables
736                 .fastFirstOrNull { it.layoutId == TrailingId }
737                 ?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
738         occupiedSpaceHorizontally += widthOrZero(trailingPlaceable)
739 
740         // measure label
741         val labelConstraints =
742             looseConstraints.offset(
743                 vertical = -bottomPaddingValue,
744                 horizontal = -occupiedSpaceHorizontally
745             )
746         val labelPlaceable =
747             measurables.fastFirstOrNull { it.layoutId == LabelId }?.measure(labelConstraints)
748         val lastBaseline =
749             labelPlaceable?.get(LastBaseline)?.let {
750                 if (it != AlignmentLine.Unspecified) it else labelPlaceable.height
751             } ?: 0
752         val effectiveLabelBaseline = max(lastBaseline, topPaddingValue)
753 
754         // measure input field
755         // input field is laid out differently depending on whether the label is present or not
756         val verticalConstraintOffset =
757             if (labelPlaceable != null) {
758                 -bottomPaddingValue - topPadding - effectiveLabelBaseline
759             } else {
760                 -topPaddingValue - bottomPaddingValue
761             }
762         val textFieldConstraints =
763             constraints
764                 .copy(minHeight = 0)
765                 .offset(
766                     vertical = verticalConstraintOffset,
767                     horizontal = -occupiedSpaceHorizontally
768                 )
769         val textFieldPlaceable =
770             measurables.fastFirst { it.layoutId == TextFieldId }.measure(textFieldConstraints)
771 
772         // measure placeholder
773         val placeholderConstraints = textFieldConstraints.copy(minWidth = 0)
774         val placeholderPlaceable =
775             measurables
776                 .fastFirstOrNull { it.layoutId == PlaceholderId }
777                 ?.measure(placeholderConstraints)
778 
779         val width =
780             calculateWidth(
781                 widthOrZero(leadingPlaceable),
782                 widthOrZero(trailingPlaceable),
783                 textFieldPlaceable.width,
784                 widthOrZero(labelPlaceable),
785                 widthOrZero(placeholderPlaceable),
786                 constraints
787             )
788         val height =
789             calculateHeight(
790                 textFieldPlaceable.height,
791                 labelPlaceable != null,
792                 effectiveLabelBaseline,
793                 heightOrZero(leadingPlaceable),
794                 heightOrZero(trailingPlaceable),
795                 heightOrZero(placeholderPlaceable),
796                 constraints,
797                 density,
798                 paddingValues
799             )
800 
801         return layout(width, height) {
802             if (labelPlaceable != null) {
803                 // label's final position is always relative to the baseline
804                 val labelEndPosition = (topPaddingValue - lastBaseline).coerceAtLeast(0)
805                 placeWithLabel(
806                     width,
807                     height,
808                     textFieldPlaceable,
809                     labelPlaceable,
810                     placeholderPlaceable,
811                     leadingPlaceable,
812                     trailingPlaceable,
813                     singleLine,
814                     labelEndPosition,
815                     effectiveLabelBaseline + topPadding,
816                     animationProgress,
817                     density
818                 )
819             } else {
820                 placeWithoutLabel(
821                     width,
822                     height,
823                     textFieldPlaceable,
824                     placeholderPlaceable,
825                     leadingPlaceable,
826                     trailingPlaceable,
827                     singleLine,
828                     density,
829                     paddingValues
830                 )
831             }
832         }
833     }
834 
maxIntrinsicHeightnull835     override fun IntrinsicMeasureScope.maxIntrinsicHeight(
836         measurables: List<IntrinsicMeasurable>,
837         width: Int
838     ): Int {
839         return intrinsicHeight(measurables, width) { intrinsicMeasurable, w ->
840             intrinsicMeasurable.maxIntrinsicHeight(w)
841         }
842     }
843 
minIntrinsicHeightnull844     override fun IntrinsicMeasureScope.minIntrinsicHeight(
845         measurables: List<IntrinsicMeasurable>,
846         width: Int
847     ): Int {
848         return intrinsicHeight(measurables, width) { intrinsicMeasurable, w ->
849             intrinsicMeasurable.minIntrinsicHeight(w)
850         }
851     }
852 
maxIntrinsicWidthnull853     override fun IntrinsicMeasureScope.maxIntrinsicWidth(
854         measurables: List<IntrinsicMeasurable>,
855         height: Int
856     ): Int {
857         return intrinsicWidth(measurables, height) { intrinsicMeasurable, h ->
858             intrinsicMeasurable.maxIntrinsicWidth(h)
859         }
860     }
861 
minIntrinsicWidthnull862     override fun IntrinsicMeasureScope.minIntrinsicWidth(
863         measurables: List<IntrinsicMeasurable>,
864         height: Int
865     ): Int {
866         return intrinsicWidth(measurables, height) { intrinsicMeasurable, h ->
867             intrinsicMeasurable.minIntrinsicWidth(h)
868         }
869     }
870 
intrinsicWidthnull871     private fun intrinsicWidth(
872         measurables: List<IntrinsicMeasurable>,
873         height: Int,
874         intrinsicMeasurer: (IntrinsicMeasurable, Int) -> Int
875     ): Int {
876         val textFieldWidth =
877             intrinsicMeasurer(measurables.fastFirst { it.layoutId == TextFieldId }, height)
878         val labelWidth =
879             measurables
880                 .fastFirstOrNull { it.layoutId == LabelId }
881                 ?.let { intrinsicMeasurer(it, height) } ?: 0
882         val trailingWidth =
883             measurables
884                 .fastFirstOrNull { it.layoutId == TrailingId }
885                 ?.let { intrinsicMeasurer(it, height) } ?: 0
886         val leadingWidth =
887             measurables
888                 .fastFirstOrNull { it.layoutId == LeadingId }
889                 ?.let { intrinsicMeasurer(it, height) } ?: 0
890         val placeholderWidth =
891             measurables
892                 .fastFirstOrNull { it.layoutId == PlaceholderId }
893                 ?.let { intrinsicMeasurer(it, height) } ?: 0
894         return calculateWidth(
895             leadingWidth = leadingWidth,
896             trailingWidth = trailingWidth,
897             textFieldWidth = textFieldWidth,
898             labelWidth = labelWidth,
899             placeholderWidth = placeholderWidth,
900             constraints = Constraints()
901         )
902     }
903 
intrinsicHeightnull904     private fun IntrinsicMeasureScope.intrinsicHeight(
905         measurables: List<IntrinsicMeasurable>,
906         width: Int,
907         intrinsicMeasurer: (IntrinsicMeasurable, Int) -> Int
908     ): Int {
909         var remainingWidth = width
910         val leadingHeight =
911             measurables
912                 .fastFirstOrNull { it.layoutId == LeadingId }
913                 ?.let {
914                     remainingWidth =
915                         remainingWidth.subtractConstraintSafely(
916                             it.maxIntrinsicWidth(Constraints.Infinity)
917                         )
918                     intrinsicMeasurer(it, width)
919                 } ?: 0
920         val trailingHeight =
921             measurables
922                 .fastFirstOrNull { it.layoutId == TrailingId }
923                 ?.let {
924                     remainingWidth =
925                         remainingWidth.subtractConstraintSafely(
926                             it.maxIntrinsicWidth(Constraints.Infinity)
927                         )
928                     intrinsicMeasurer(it, width)
929                 } ?: 0
930 
931         val labelHeight =
932             measurables
933                 .fastFirstOrNull { it.layoutId == LabelId }
934                 ?.let { intrinsicMeasurer(it, remainingWidth) } ?: 0
935 
936         val textFieldHeight =
937             intrinsicMeasurer(measurables.fastFirst { it.layoutId == TextFieldId }, remainingWidth)
938         val placeholderHeight =
939             measurables
940                 .fastFirstOrNull { it.layoutId == PlaceholderId }
941                 ?.let { intrinsicMeasurer(it, remainingWidth) } ?: 0
942 
943         return calculateHeight(
944             textFieldHeight = textFieldHeight,
945             hasLabel = labelHeight > 0,
946             labelBaseline = labelHeight,
947             leadingHeight = leadingHeight,
948             trailingHeight = trailingHeight,
949             placeholderHeight = placeholderHeight,
950             constraints = Constraints(),
951             density = density,
952             paddingValues = paddingValues
953         )
954     }
955 }
956 
calculateWidthnull957 private fun calculateWidth(
958     leadingWidth: Int,
959     trailingWidth: Int,
960     textFieldWidth: Int,
961     labelWidth: Int,
962     placeholderWidth: Int,
963     constraints: Constraints
964 ): Int {
965     val middleSection = maxOf(textFieldWidth, labelWidth, placeholderWidth)
966     val wrappedWidth = leadingWidth + middleSection + trailingWidth
967     return constraints.constrainWidth(wrappedWidth)
968 }
969 
calculateHeightnull970 private fun calculateHeight(
971     textFieldHeight: Int,
972     hasLabel: Boolean,
973     labelBaseline: Int,
974     leadingHeight: Int,
975     trailingHeight: Int,
976     placeholderHeight: Int,
977     constraints: Constraints,
978     density: Float,
979     paddingValues: PaddingValues
980 ): Int {
981     val paddingToLabel = TextFieldTopPadding.value * density
982     val topPaddingValue = paddingValues.calculateTopPadding().value * density
983     val bottomPaddingValue = paddingValues.calculateBottomPadding().value * density
984 
985     val inputFieldHeight = max(textFieldHeight, placeholderHeight)
986     val middleSectionHeight =
987         if (hasLabel) {
988             labelBaseline + paddingToLabel + inputFieldHeight + bottomPaddingValue
989         } else {
990             topPaddingValue + inputFieldHeight + bottomPaddingValue
991         }
992     return constraints.constrainHeight(
993         maxOf(middleSectionHeight.roundToInt(), leadingHeight, trailingHeight)
994     )
995 }
996 
997 /**
998  * Places the provided text field, placeholder and label with respect to the baseline offsets in
999  * [TextField] when there is a label. When there is no label, [placeWithoutLabel] is used.
1000  */
placeWithLabelnull1001 private fun Placeable.PlacementScope.placeWithLabel(
1002     width: Int,
1003     height: Int,
1004     textfieldPlaceable: Placeable,
1005     labelPlaceable: Placeable?,
1006     placeholderPlaceable: Placeable?,
1007     leadingPlaceable: Placeable?,
1008     trailingPlaceable: Placeable?,
1009     singleLine: Boolean,
1010     labelEndPosition: Int,
1011     textPosition: Int,
1012     animationProgress: Float,
1013     density: Float
1014 ) {
1015     leadingPlaceable?.placeRelative(
1016         0,
1017         Alignment.CenterVertically.align(leadingPlaceable.height, height)
1018     )
1019     trailingPlaceable?.placeRelative(
1020         width - trailingPlaceable.width,
1021         Alignment.CenterVertically.align(trailingPlaceable.height, height)
1022     )
1023     labelPlaceable?.let {
1024         // if it's a single line, the label's start position is in the center of the
1025         // container. When it's a multiline text field, the label's start position is at the
1026         // top with padding
1027         val startPosition =
1028             if (singleLine) {
1029                 Alignment.CenterVertically.align(it.height, height)
1030             } else {
1031                 // even though the padding is defined by developer, it only affects text field when
1032                 // animation progress == 1,
1033                 // which is when text field is focused or non-empty input text. The start position
1034                 // of the label is always 16.dp.
1035                 (TextFieldPadding.value * density).roundToInt()
1036             }
1037         val distance = startPosition - labelEndPosition
1038         val positionY = startPosition - (distance * animationProgress).roundToInt()
1039         it.placeRelative(widthOrZero(leadingPlaceable), positionY)
1040     }
1041     textfieldPlaceable.placeRelative(widthOrZero(leadingPlaceable), textPosition)
1042     placeholderPlaceable?.placeRelative(widthOrZero(leadingPlaceable), textPosition)
1043 }
1044 
1045 /**
1046  * Places the provided text field and placeholder in [TextField] when there is no label. When there
1047  * is a label, [placeWithLabel] is used
1048  */
Placeablenull1049 private fun Placeable.PlacementScope.placeWithoutLabel(
1050     width: Int,
1051     height: Int,
1052     textPlaceable: Placeable,
1053     placeholderPlaceable: Placeable?,
1054     leadingPlaceable: Placeable?,
1055     trailingPlaceable: Placeable?,
1056     singleLine: Boolean,
1057     density: Float,
1058     paddingValues: PaddingValues
1059 ) {
1060     val topPadding = (paddingValues.calculateTopPadding().value * density).roundToInt()
1061 
1062     leadingPlaceable?.placeRelative(
1063         0,
1064         Alignment.CenterVertically.align(leadingPlaceable.height, height)
1065     )
1066     trailingPlaceable?.placeRelative(
1067         width - trailingPlaceable.width,
1068         Alignment.CenterVertically.align(trailingPlaceable.height, height)
1069     )
1070 
1071     // Single line text field without label places its input center vertically. Multiline text
1072     // field without label places its input at the top with padding
1073     val textVerticalPosition =
1074         if (singleLine) {
1075             Alignment.CenterVertically.align(textPlaceable.height, height)
1076         } else {
1077             topPadding
1078         }
1079     textPlaceable.placeRelative(widthOrZero(leadingPlaceable), textVerticalPosition)
1080 
1081     // placeholder is placed similar to the text input above
1082     placeholderPlaceable?.let {
1083         val placeholderVerticalPosition =
1084             if (singleLine) {
1085                 Alignment.CenterVertically.align(placeholderPlaceable.height, height)
1086             } else {
1087                 topPadding
1088             }
1089         it.placeRelative(widthOrZero(leadingPlaceable), placeholderVerticalPosition)
1090     }
1091 }
1092 
1093 /** A draw modifier that draws a bottom indicator line in [TextField] */
drawIndicatorLinenull1094 internal fun Modifier.drawIndicatorLine(indicatorBorder: BorderStroke): Modifier {
1095     val strokeWidthDp = indicatorBorder.width
1096     return drawWithContent {
1097         drawContent()
1098         if (strokeWidthDp == Dp.Hairline) return@drawWithContent
1099         val strokeWidth = strokeWidthDp.value * density
1100         val y = size.height - strokeWidth / 2
1101         drawLine(indicatorBorder.brush, Offset(0f, y), Offset(size.width, y), strokeWidth)
1102     }
1103 }
1104 
1105 /** Padding from the label's baseline to the top */
1106 internal val FirstBaselineOffset = 20.dp
1107 
1108 /** Padding from input field to the bottom */
1109 internal val TextFieldBottomPadding = 10.dp
1110 
1111 /** Padding from label's baseline (or FirstBaselineOffset) to the input field */
1112 /*@VisibleForTesting*/
1113 internal val TextFieldTopPadding = 2.dp
1114