1 /*
<lambda>null2  * Copyright 2022 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.material3
18 
19 import androidx.compose.animation.VectorConverter
20 import androidx.compose.animation.core.Animatable
21 import androidx.compose.animation.core.AnimationVector1D
22 import androidx.compose.animation.core.AnimationVector4D
23 import androidx.compose.animation.core.VectorConverter
24 import androidx.compose.animation.core.snap
25 import androidx.compose.foundation.ScrollState
26 import androidx.compose.foundation.interaction.FocusInteraction
27 import androidx.compose.foundation.interaction.Interaction
28 import androidx.compose.foundation.interaction.InteractionSource
29 import androidx.compose.foundation.interaction.MutableInteractionSource
30 import androidx.compose.foundation.interaction.collectIsFocusedAsState
31 import androidx.compose.foundation.layout.Box
32 import androidx.compose.foundation.layout.PaddingValues
33 import androidx.compose.foundation.layout.calculateEndPadding
34 import androidx.compose.foundation.layout.calculateStartPadding
35 import androidx.compose.foundation.layout.defaultMinSize
36 import androidx.compose.foundation.layout.heightIn
37 import androidx.compose.foundation.layout.padding
38 import androidx.compose.foundation.layout.wrapContentHeight
39 import androidx.compose.foundation.rememberScrollState
40 import androidx.compose.foundation.text.BasicTextField
41 import androidx.compose.foundation.text.KeyboardActions
42 import androidx.compose.foundation.text.KeyboardOptions
43 import androidx.compose.foundation.text.input.InputTransformation
44 import androidx.compose.foundation.text.input.KeyboardActionHandler
45 import androidx.compose.foundation.text.input.OutputTransformation
46 import androidx.compose.foundation.text.input.TextFieldLineLimits
47 import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
48 import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
49 import androidx.compose.foundation.text.input.TextFieldState
50 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
51 import androidx.compose.material3.TextFieldDefaults.defaultTextFieldColors
52 import androidx.compose.material3.internal.AboveLabelBottomPadding
53 import androidx.compose.material3.internal.AboveLabelHorizontalPadding
54 import androidx.compose.material3.internal.ContainerId
55 import androidx.compose.material3.internal.FloatProducer
56 import androidx.compose.material3.internal.LabelId
57 import androidx.compose.material3.internal.LeadingId
58 import androidx.compose.material3.internal.MinFocusedLabelLineHeight
59 import androidx.compose.material3.internal.MinSupportingTextLineHeight
60 import androidx.compose.material3.internal.MinTextLineHeight
61 import androidx.compose.material3.internal.PlaceholderId
62 import androidx.compose.material3.internal.PrefixId
63 import androidx.compose.material3.internal.PrefixSuffixTextPadding
64 import androidx.compose.material3.internal.Strings
65 import androidx.compose.material3.internal.SuffixId
66 import androidx.compose.material3.internal.SupportingId
67 import androidx.compose.material3.internal.TextFieldId
68 import androidx.compose.material3.internal.TrailingId
69 import androidx.compose.material3.internal.defaultErrorSemantics
70 import androidx.compose.material3.internal.expandedAlignment
71 import androidx.compose.material3.internal.getString
72 import androidx.compose.material3.internal.heightOrZero
73 import androidx.compose.material3.internal.layoutId
74 import androidx.compose.material3.internal.minimizedAlignment
75 import androidx.compose.material3.internal.minimizedLabelHalfHeight
76 import androidx.compose.material3.internal.subtractConstraintSafely
77 import androidx.compose.material3.internal.textFieldHorizontalIconPadding
78 import androidx.compose.material3.internal.textFieldLabelMinHeight
79 import androidx.compose.material3.internal.widthOrZero
80 import androidx.compose.material3.tokens.FilledTextFieldTokens
81 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
82 import androidx.compose.material3.tokens.MotionTokens.EasingEmphasizedAccelerateCubicBezier
83 import androidx.compose.runtime.Composable
84 import androidx.compose.runtime.CompositionLocalProvider
85 import androidx.compose.runtime.remember
86 import androidx.compose.ui.Alignment
87 import androidx.compose.ui.Modifier
88 import androidx.compose.ui.draw.CacheDrawModifierNode
89 import androidx.compose.ui.geometry.Rect
90 import androidx.compose.ui.graphics.Color
91 import androidx.compose.ui.graphics.Path
92 import androidx.compose.ui.graphics.Shape
93 import androidx.compose.ui.graphics.SolidColor
94 import androidx.compose.ui.graphics.addOutline
95 import androidx.compose.ui.graphics.takeOrElse
96 import androidx.compose.ui.layout.IntrinsicMeasurable
97 import androidx.compose.ui.layout.IntrinsicMeasureScope
98 import androidx.compose.ui.layout.Layout
99 import androidx.compose.ui.layout.Measurable
100 import androidx.compose.ui.layout.MeasurePolicy
101 import androidx.compose.ui.layout.MeasureResult
102 import androidx.compose.ui.layout.MeasureScope
103 import androidx.compose.ui.layout.Placeable
104 import androidx.compose.ui.layout.layoutId
105 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
106 import androidx.compose.ui.node.DelegatingNode
107 import androidx.compose.ui.node.ModifierNodeElement
108 import androidx.compose.ui.node.currentValueOf
109 import androidx.compose.ui.platform.InspectorInfo
110 import androidx.compose.ui.platform.LocalLayoutDirection
111 import androidx.compose.ui.text.TextLayoutResult
112 import androidx.compose.ui.text.TextStyle
113 import androidx.compose.ui.text.input.ImeAction
114 import androidx.compose.ui.text.input.KeyboardType
115 import androidx.compose.ui.text.input.TextFieldValue
116 import androidx.compose.ui.text.input.VisualTransformation
117 import androidx.compose.ui.unit.Constraints
118 import androidx.compose.ui.unit.Density
119 import androidx.compose.ui.unit.Dp
120 import androidx.compose.ui.unit.IntOffset
121 import androidx.compose.ui.unit.LayoutDirection
122 import androidx.compose.ui.unit.coerceAtLeast
123 import androidx.compose.ui.unit.constrainHeight
124 import androidx.compose.ui.unit.constrainWidth
125 import androidx.compose.ui.unit.dp
126 import androidx.compose.ui.unit.lerp
127 import androidx.compose.ui.unit.offset
128 import androidx.compose.ui.util.fastFirst
129 import androidx.compose.ui.util.fastFirstOrNull
130 import androidx.compose.ui.util.lerp
131 import kotlin.math.max
132 import kotlin.math.roundToInt
133 import kotlinx.coroutines.Job
134 import kotlinx.coroutines.launch
135 
136 /**
137  * [Material Design filled text field](https://m3.material.io/components/text-fields/overview)
138  *
139  * Text fields allow users to enter text into a UI. They typically appear in forms and dialogs.
140  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
141  * when surrounded by other content and components.
142  *
143  * ![Filled text field
144  * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-text-field.png)
145  *
146  * If you are looking for an outlined version, see [OutlinedTextField]. For a text field
147  * specifically designed for passwords or other secure content, see [SecureTextField].
148  *
149  * This overload of [TextField] uses [TextFieldState] to keep track of its text content and position
150  * of the cursor or selection.
151  *
152  * A simple single line text field looks like:
153  *
154  * @sample androidx.compose.material3.samples.SimpleTextFieldSample
155  *
156  * You can control the initial text input and selection:
157  *
158  * @sample androidx.compose.material3.samples.TextFieldWithInitialValueAndSelection
159  *
160  * Use input and output transformations to control user input and the displayed text:
161  *
162  * @sample androidx.compose.material3.samples.TextFieldWithTransformations
163  *
164  * You may provide a placeholder:
165  *
166  * @sample androidx.compose.material3.samples.TextFieldWithPlaceholder
167  *
168  * You can also provide leading and trailing icons:
169  *
170  * @sample androidx.compose.material3.samples.TextFieldWithIcons
171  *
172  * You can also provide a prefix or suffix to the text:
173  *
174  * @sample androidx.compose.material3.samples.TextFieldWithPrefixAndSuffix
175  *
176  * To handle the error input state, use [isError] parameter:
177  *
178  * @sample androidx.compose.material3.samples.TextFieldWithErrorState
179  *
180  * Additionally, you may provide additional message at the bottom:
181  *
182  * @sample androidx.compose.material3.samples.TextFieldWithSupportingText
183  *
184  * You can change the content padding to create a dense text field:
185  *
186  * @sample androidx.compose.material3.samples.DenseTextFieldContentPadding
187  *
188  * Hiding a software keyboard on IME action performed:
189  *
190  * @sample androidx.compose.material3.samples.TextFieldWithHideKeyboardOnImeAction
191  * @param state [TextFieldState] object that holds the internal editing state of the text field.
192  * @param modifier the [Modifier] to be applied to this text field.
193  * @param enabled controls the enabled state of this text field. When `false`, this component will
194  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
195  *   services.
196  * @param readOnly controls the editable state of the text field. When `true`, the text field cannot
197  *   be modified. However, a user can focus it and copy text from it. Read-only text fields are
198  *   usually used to display pre-filled forms that a user cannot edit.
199  * @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
200  * @param labelPosition the position of the label. See [TextFieldLabelPosition].
201  * @param label the optional label to be displayed with this text field. The default text style uses
202  *   [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
203  * @param placeholder the optional placeholder to be displayed when the input text is empty. The
204  *   default text style uses [Typography.bodyLarge].
205  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
206  *   container.
207  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
208  *   container.
209  * @param prefix the optional prefix to be displayed before the input text in the text field.
210  * @param suffix the optional suffix to be displayed after the input text in the text field.
211  * @param supportingText the optional supporting text to be displayed below the text field.
212  * @param isError indicates if the text field's current value is in error. When `true`, the
213  *   components of the text field will be displayed in an error color, and an error will be
214  *   announced to accessibility services.
215  * @param inputTransformation optional [InputTransformation] that will be used to transform changes
216  *   to the [TextFieldState] made by the user. The transformation will be applied to changes made by
217  *   hardware and software keyboard events, pasting or dropping text, accessibility services, and
218  *   tests. The transformation will _not_ be applied when changing the [state] programmatically, or
219  *   when the transformation is changed. If the transformation is changed on an existing text field,
220  *   it will be applied to the next user edit. The transformation will not immediately affect the
221  *   current [state].
222  * @param outputTransformation optional [OutputTransformation] that transforms how the contents of
223  *   the text field are presented.
224  * @param keyboardOptions software keyboard options that contains configuration such as
225  *   [KeyboardType] and [ImeAction].
226  * @param onKeyboardAction called when the user presses the action button in the input method editor
227  *   (IME), or by pressing the enter key on a hardware keyboard. By default this parameter is null,
228  *   and would execute the default behavior for a received IME Action e.g., [ImeAction.Done] would
229  *   close the keyboard, [ImeAction.Next] would switch the focus to the next focusable item on the
230  *   screen.
231  * @param lineLimits whether the text field should be [SingleLine], scroll horizontally, and ignore
232  *   newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed, all newline
233  *   characters ('\n') within the text will be replaced with regular whitespace (' ').
234  * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
235  *   callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
236  *   or null if it cannot. The function reads the layout result from a snapshot state object, and
237  *   will invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
238  *   paragraph information, size of the text, baselines and other details. [Density] scope is the
239  *   one that was used while creating the given text layout.
240  * @param scrollState scroll state that manages either horizontal or vertical scroll of the text
241  *   field. If [lineLimits] is [SingleLine], this text field is treated as single line with
242  *   horizontal scroll behavior. Otherwise, the text field becomes vertically scrollable.
243  * @param shape defines the shape of this text field's container.
244  * @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
245  *   in different states. See [TextFieldDefaults.colors].
246  * @param contentPadding the padding applied to the inner text field that separates it from the
247  *   surrounding elements of the text field. Note that the padding values may not be respected if
248  *   they are incompatible with the text field's size constraints or layout. See
249  *   [TextFieldDefaults.contentPaddingWithLabel] and [TextFieldDefaults.contentPaddingWithoutLabel].
250  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
251  *   emitting [Interaction]s for this text field. You can use this to change the text field's
252  *   appearance or preview the text field in different states. Note that if `null` is provided,
253  *   interactions will still happen internally.
254  */
255 @OptIn(ExperimentalMaterial3Api::class)
256 @Composable
257 fun TextField(
258     state: TextFieldState,
259     modifier: Modifier = Modifier,
260     enabled: Boolean = true,
261     readOnly: Boolean = false,
262     textStyle: TextStyle = LocalTextStyle.current,
263     labelPosition: TextFieldLabelPosition = TextFieldLabelPosition.Attached(),
264     label: @Composable (TextFieldLabelScope.() -> Unit)? = null,
265     placeholder: @Composable (() -> Unit)? = null,
266     leadingIcon: @Composable (() -> Unit)? = null,
267     trailingIcon: @Composable (() -> Unit)? = null,
268     prefix: @Composable (() -> Unit)? = null,
269     suffix: @Composable (() -> Unit)? = null,
270     supportingText: @Composable (() -> Unit)? = null,
271     isError: Boolean = false,
272     inputTransformation: InputTransformation? = null,
273     outputTransformation: OutputTransformation? = null,
274     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
275     onKeyboardAction: KeyboardActionHandler? = null,
276     lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
277     onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
278     scrollState: ScrollState = rememberScrollState(),
279     shape: Shape = TextFieldDefaults.shape,
280     colors: TextFieldColors = TextFieldDefaults.colors(),
281     contentPadding: PaddingValues =
282         if (label == null || labelPosition is TextFieldLabelPosition.Above) {
283             TextFieldDefaults.contentPaddingWithoutLabel()
284         } else {
285             TextFieldDefaults.contentPaddingWithLabel()
286         },
287     interactionSource: MutableInteractionSource? = null,
288 ) {
289     @Suppress("NAME_SHADOWING")
<lambda>null290     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
291     // If color is not provided via the text style, use content color as a default
292     val textColor =
<lambda>null293         textStyle.color.takeOrElse {
294             val focused = interactionSource.collectIsFocusedAsState().value
295             colors.textColor(enabled, isError, focused)
296         }
297     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
298 
<lambda>null299     CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) {
300         BasicTextField(
301             state = state,
302             modifier =
303                 modifier
304                     .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
305                     .defaultMinSize(
306                         minWidth = TextFieldDefaults.MinWidth,
307                         minHeight = TextFieldDefaults.MinHeight
308                     ),
309             enabled = enabled,
310             readOnly = readOnly,
311             textStyle = mergedTextStyle,
312             cursorBrush = SolidColor(colors.cursorColor(isError)),
313             keyboardOptions = keyboardOptions,
314             onKeyboardAction = onKeyboardAction,
315             lineLimits = lineLimits,
316             onTextLayout = onTextLayout,
317             interactionSource = interactionSource,
318             inputTransformation = inputTransformation,
319             outputTransformation = outputTransformation,
320             scrollState = scrollState,
321             decorator =
322                 TextFieldDefaults.decorator(
323                     state = state,
324                     enabled = enabled,
325                     lineLimits = lineLimits,
326                     outputTransformation = outputTransformation,
327                     interactionSource = interactionSource,
328                     labelPosition = labelPosition,
329                     label = label,
330                     placeholder = placeholder,
331                     leadingIcon = leadingIcon,
332                     trailingIcon = trailingIcon,
333                     prefix = prefix,
334                     suffix = suffix,
335                     supportingText = supportingText,
336                     isError = isError,
337                     colors = colors,
338                     contentPadding = contentPadding,
339                     container = {
340                         TextFieldDefaults.Container(
341                             enabled = enabled,
342                             isError = isError,
343                             interactionSource = interactionSource,
344                             colors = colors,
345                             shape = shape,
346                         )
347                     }
348                 )
349         )
350     }
351 }
352 
353 /**
354  * [Material Design filled text field](https://m3.material.io/components/text-fields/overview)
355  *
356  * Text fields allow users to enter text into a UI. They typically appear in forms and dialogs.
357  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
358  * when surrounded by other content and components.
359  *
360  * ![Filled text field
361  * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-text-field.png)
362  *
363  * If you are looking for an outlined version, see [OutlinedTextField].
364  *
365  * If apart from input text change you also want to observe the cursor location, selection range, or
366  * IME composition use the TextField overload with the [TextFieldValue] parameter instead.
367  *
368  * @param value the input text to be shown in the text field
369  * @param onValueChange the callback that is triggered when the input service updates the text. An
370  *   updated text comes as a parameter of the callback
371  * @param modifier the [Modifier] to be applied to this text field
372  * @param enabled controls the enabled state of this text field. When `false`, this component will
373  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
374  *   services.
375  * @param readOnly controls the editable state of the text field. When `true`, the text field cannot
376  *   be modified. However, a user can focus it and copy text from it. Read-only text fields are
377  *   usually used to display pre-filled forms that a user cannot edit.
378  * @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
379  * @param label the optional label to be displayed with this text field. The default text style uses
380  *   [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
381  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
382  *   the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge]
383  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
384  *   container
385  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
386  *   container
387  * @param prefix the optional prefix to be displayed before the input text in the text field
388  * @param suffix the optional suffix to be displayed after the input text in the text field
389  * @param supportingText the optional supporting text to be displayed below the text field
390  * @param isError indicates if the text field's current value is in error. If set to true, the
391  *   label, bottom indicator and trailing icon by default will be displayed in error color
392  * @param visualTransformation transforms the visual representation of the input [value] For
393  *   example, you can use
394  *   [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to
395  *   create a password text field. By default, no visual transformation is applied.
396  * @param keyboardOptions software keyboard options that contains configuration such as
397  *   [KeyboardType] and [ImeAction].
398  * @param keyboardActions when the input service emits an IME action, the corresponding callback is
399  *   called. Note that this IME action may be different from what you specified in
400  *   [KeyboardOptions.imeAction].
401  * @param singleLine when `true`, this text field becomes a single horizontally scrolling text field
402  *   instead of wrapping onto multiple lines. The keyboard will be informed to not show the return
403  *   key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the maxLines
404  *   attribute will be automatically set to 1.
405  * @param maxLines the maximum height in terms of maximum number of visible lines. It is required
406  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
407  * @param minLines the minimum height in terms of minimum number of visible lines. It is required
408  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
409  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
410  *   emitting [Interaction]s for this text field. You can use this to change the text field's
411  *   appearance or preview the text field in different states. Note that if `null` is provided,
412  *   interactions will still happen internally.
413  * @param shape defines the shape of this text field's container
414  * @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
415  *   in different states. See [TextFieldDefaults.colors].
416  */
417 @OptIn(ExperimentalMaterial3Api::class)
418 @Composable
TextFieldnull419 fun TextField(
420     value: String,
421     onValueChange: (String) -> Unit,
422     modifier: Modifier = Modifier,
423     enabled: Boolean = true,
424     readOnly: Boolean = false,
425     textStyle: TextStyle = LocalTextStyle.current,
426     label: @Composable (() -> Unit)? = null,
427     placeholder: @Composable (() -> Unit)? = null,
428     leadingIcon: @Composable (() -> Unit)? = null,
429     trailingIcon: @Composable (() -> Unit)? = null,
430     prefix: @Composable (() -> Unit)? = null,
431     suffix: @Composable (() -> Unit)? = null,
432     supportingText: @Composable (() -> Unit)? = null,
433     isError: Boolean = false,
434     visualTransformation: VisualTransformation = VisualTransformation.None,
435     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
436     keyboardActions: KeyboardActions = KeyboardActions.Default,
437     singleLine: Boolean = false,
438     maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
439     minLines: Int = 1,
440     interactionSource: MutableInteractionSource? = null,
441     shape: Shape = TextFieldDefaults.shape,
442     colors: TextFieldColors = TextFieldDefaults.colors()
443 ) {
444     @Suppress("NAME_SHADOWING")
445     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
446     // If color is not provided via the text style, use content color as a default
447     val textColor =
448         textStyle.color.takeOrElse {
449             val focused = interactionSource.collectIsFocusedAsState().value
450             colors.textColor(enabled, isError, focused)
451         }
452     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
453 
454     CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) {
455         BasicTextField(
456             value = value,
457             modifier =
458                 modifier
459                     .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
460                     .defaultMinSize(
461                         minWidth = TextFieldDefaults.MinWidth,
462                         minHeight = TextFieldDefaults.MinHeight
463                     ),
464             onValueChange = onValueChange,
465             enabled = enabled,
466             readOnly = readOnly,
467             textStyle = mergedTextStyle,
468             cursorBrush = SolidColor(colors.cursorColor(isError)),
469             visualTransformation = visualTransformation,
470             keyboardOptions = keyboardOptions,
471             keyboardActions = keyboardActions,
472             interactionSource = interactionSource,
473             singleLine = singleLine,
474             maxLines = maxLines,
475             minLines = minLines,
476             decorationBox =
477                 @Composable { innerTextField ->
478                     // places leading icon, text field with label and placeholder, trailing icon
479                     TextFieldDefaults.DecorationBox(
480                         value = value,
481                         visualTransformation = visualTransformation,
482                         innerTextField = innerTextField,
483                         placeholder = placeholder,
484                         label = label,
485                         leadingIcon = leadingIcon,
486                         trailingIcon = trailingIcon,
487                         prefix = prefix,
488                         suffix = suffix,
489                         supportingText = supportingText,
490                         shape = shape,
491                         singleLine = singleLine,
492                         enabled = enabled,
493                         isError = isError,
494                         interactionSource = interactionSource,
495                         colors = colors
496                     )
497                 }
498         )
499     }
500 }
501 
502 /**
503  * [Material Design filled text field](https://m3.material.io/components/text-fields/overview)
504  *
505  * Text fields allow users to enter text into a UI. They typically appear in forms and dialogs.
506  * Filled text fields have more visual emphasis than outlined text fields, making them stand out
507  * when surrounded by other content and components.
508  *
509  * ![Filled text field
510  * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-text-field.png)
511  *
512  * If you are looking for an outlined version, see [OutlinedTextField].
513  *
514  * This overload provides access to the input text, cursor position, selection range and IME
515  * composition. If you only want to observe an input text change, use the TextField overload with
516  * the [String] parameter instead.
517  *
518  * @param value the input [TextFieldValue] to be shown in the text field
519  * @param onValueChange the callback that is triggered when the input service updates values in
520  *   [TextFieldValue]. An updated [TextFieldValue] comes as a parameter of the callback
521  * @param modifier the [Modifier] to be applied to this text field
522  * @param enabled controls the enabled state of this text field. When `false`, this component will
523  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
524  *   services.
525  * @param readOnly controls the editable state of the text field. When `true`, the text field cannot
526  *   be modified. However, a user can focus it and copy text from it. Read-only text fields are
527  *   usually used to display pre-filled forms that a user cannot edit.
528  * @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
529  * @param label the optional label to be displayed with this text field. The default text style uses
530  *   [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
531  * @param placeholder the optional placeholder to be displayed when the text field is in focus and
532  *   the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge]
533  * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
534  *   container
535  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
536  *   container
537  * @param prefix the optional prefix to be displayed before the input text in the text field
538  * @param suffix the optional suffix to be displayed after the input text in the text field
539  * @param supportingText the optional supporting text to be displayed below the text field
540  * @param isError indicates if the text field's current value is in error state. If set to true, the
541  *   label, bottom indicator and trailing icon by default will be displayed in error color
542  * @param visualTransformation transforms the visual representation of the input [value]. For
543  *   example, you can use
544  *   [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to
545  *   create a password text field. By default, no visual transformation is applied.
546  * @param keyboardOptions software keyboard options that contains configuration such as
547  *   [KeyboardType] and [ImeAction].
548  * @param keyboardActions when the input service emits an IME action, the corresponding callback is
549  *   called. Note that this IME action may be different from what you specified in
550  *   [KeyboardOptions.imeAction].
551  * @param singleLine when `true`, this text field becomes a single horizontally scrolling text field
552  *   instead of wrapping onto multiple lines. The keyboard will be informed to not show the return
553  *   key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the maxLines
554  *   attribute will be automatically set to 1.
555  * @param maxLines the maximum height in terms of maximum number of visible lines. It is required
556  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
557  * @param minLines the minimum height in terms of minimum number of visible lines. It is required
558  *   that 1 <= [minLines] <= [maxLines]. This parameter is ignored when [singleLine] is true.
559  * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
560  *   emitting [Interaction]s for this text field. You can use this to change the text field's
561  *   appearance or preview the text field in different states. Note that if `null` is provided,
562  *   interactions will still happen internally.
563  * @param shape defines the shape of this text field's container
564  * @param colors [TextFieldColors] that will be used to resolve the colors used for this text field
565  *   in different states. See [TextFieldDefaults.colors].
566  */
567 @OptIn(ExperimentalMaterial3Api::class)
568 @Composable
TextFieldnull569 fun TextField(
570     value: TextFieldValue,
571     onValueChange: (TextFieldValue) -> Unit,
572     modifier: Modifier = Modifier,
573     enabled: Boolean = true,
574     readOnly: Boolean = false,
575     textStyle: TextStyle = LocalTextStyle.current,
576     label: @Composable (() -> Unit)? = null,
577     placeholder: @Composable (() -> Unit)? = null,
578     leadingIcon: @Composable (() -> Unit)? = null,
579     trailingIcon: @Composable (() -> Unit)? = null,
580     prefix: @Composable (() -> Unit)? = null,
581     suffix: @Composable (() -> Unit)? = null,
582     supportingText: @Composable (() -> Unit)? = null,
583     isError: Boolean = false,
584     visualTransformation: VisualTransformation = VisualTransformation.None,
585     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
586     keyboardActions: KeyboardActions = KeyboardActions.Default,
587     singleLine: Boolean = false,
588     maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
589     minLines: Int = 1,
590     interactionSource: MutableInteractionSource? = null,
591     shape: Shape = TextFieldDefaults.shape,
592     colors: TextFieldColors = TextFieldDefaults.colors()
593 ) {
594     @Suppress("NAME_SHADOWING")
595     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
596     // If color is not provided via the text style, use content color as a default
597     val textColor =
598         textStyle.color.takeOrElse {
599             val focused = interactionSource.collectIsFocusedAsState().value
600             colors.textColor(enabled, isError, focused)
601         }
602     val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
603 
604     CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) {
605         BasicTextField(
606             value = value,
607             modifier =
608                 modifier
609                     .defaultErrorSemantics(isError, getString(Strings.DefaultErrorMessage))
610                     .defaultMinSize(
611                         minWidth = TextFieldDefaults.MinWidth,
612                         minHeight = TextFieldDefaults.MinHeight
613                     ),
614             onValueChange = onValueChange,
615             enabled = enabled,
616             readOnly = readOnly,
617             textStyle = mergedTextStyle,
618             cursorBrush = SolidColor(colors.cursorColor(isError)),
619             visualTransformation = visualTransformation,
620             keyboardOptions = keyboardOptions,
621             keyboardActions = keyboardActions,
622             interactionSource = interactionSource,
623             singleLine = singleLine,
624             maxLines = maxLines,
625             minLines = minLines,
626             decorationBox =
627                 @Composable { innerTextField ->
628                     // places leading icon, text field with label and placeholder, trailing icon
629                     TextFieldDefaults.DecorationBox(
630                         value = value.text,
631                         visualTransformation = visualTransformation,
632                         innerTextField = innerTextField,
633                         placeholder = placeholder,
634                         label = label,
635                         leadingIcon = leadingIcon,
636                         trailingIcon = trailingIcon,
637                         prefix = prefix,
638                         suffix = suffix,
639                         supportingText = supportingText,
640                         shape = shape,
641                         singleLine = singleLine,
642                         enabled = enabled,
643                         isError = isError,
644                         interactionSource = interactionSource,
645                         colors = colors
646                     )
647                 }
648         )
649     }
650 }
651 
652 /**
653  * Composable responsible for measuring and laying out leading and trailing icons, label,
654  * placeholder and the input field.
655  */
656 @Composable
657 internal fun TextFieldLayout(
658     modifier: Modifier,
659     textField: @Composable () -> Unit,
660     label: @Composable (() -> Unit)?,
661     placeholder: @Composable ((Modifier) -> Unit)?,
662     leading: @Composable (() -> Unit)?,
663     trailing: @Composable (() -> Unit)?,
664     prefix: @Composable (() -> Unit)?,
665     suffix: @Composable (() -> Unit)?,
666     singleLine: Boolean,
667     labelPosition: TextFieldLabelPosition,
668     labelProgress: FloatProducer,
669     container: @Composable () -> Unit,
670     supporting: @Composable (() -> Unit)?,
671     paddingValues: PaddingValues
672 ) {
673     val minimizedLabelHalfHeight = minimizedLabelHalfHeight()
674     val measurePolicy =
675         remember(
676             singleLine,
677             labelPosition,
678             labelProgress,
679             paddingValues,
680             minimizedLabelHalfHeight,
<lambda>null681         ) {
682             TextFieldMeasurePolicy(
683                 singleLine = singleLine,
684                 labelPosition = labelPosition,
685                 labelProgress = labelProgress,
686                 paddingValues = paddingValues,
687                 minimizedLabelHalfHeight = minimizedLabelHalfHeight,
688             )
689         }
690     val layoutDirection = LocalLayoutDirection.current
691     Layout(
692         modifier = modifier,
<lambda>null693         content = {
694             // The container is given as a Composable instead of a background modifier so that
695             // elements like supporting text can be placed outside of it while still contributing
696             // to the text field's measurements overall.
697             container()
698 
699             if (leading != null) {
700                 Box(
701                     modifier = Modifier.layoutId(LeadingId).minimumInteractiveComponentSize(),
702                     contentAlignment = Alignment.Center
703                 ) {
704                     leading()
705                 }
706             }
707             if (trailing != null) {
708                 Box(
709                     modifier = Modifier.layoutId(TrailingId).minimumInteractiveComponentSize(),
710                     contentAlignment = Alignment.Center
711                 ) {
712                     trailing()
713                 }
714             }
715 
716             val startTextFieldPadding = paddingValues.calculateStartPadding(layoutDirection)
717             val endTextFieldPadding = paddingValues.calculateEndPadding(layoutDirection)
718 
719             val horizontalIconPadding = textFieldHorizontalIconPadding()
720             val startPadding =
721                 if (leading != null) {
722                     (startTextFieldPadding - horizontalIconPadding).coerceAtLeast(0.dp)
723                 } else {
724                     startTextFieldPadding
725                 }
726             val endPadding =
727                 if (trailing != null) {
728                     (endTextFieldPadding - horizontalIconPadding).coerceAtLeast(0.dp)
729                 } else {
730                     endTextFieldPadding
731                 }
732 
733             if (prefix != null) {
734                 Box(
735                     Modifier.layoutId(PrefixId)
736                         .heightIn(min = MinTextLineHeight)
737                         .wrapContentHeight()
738                         .padding(start = startPadding, end = PrefixSuffixTextPadding)
739                 ) {
740                     prefix()
741                 }
742             }
743             if (suffix != null) {
744                 Box(
745                     Modifier.layoutId(SuffixId)
746                         .heightIn(min = MinTextLineHeight)
747                         .wrapContentHeight()
748                         .padding(start = PrefixSuffixTextPadding, end = endPadding)
749                 ) {
750                     suffix()
751                 }
752             }
753 
754             val labelPadding =
755                 if (labelPosition is TextFieldLabelPosition.Above) {
756                     Modifier.padding(
757                         start = AboveLabelHorizontalPadding,
758                         end = AboveLabelHorizontalPadding,
759                         bottom = AboveLabelBottomPadding,
760                     )
761                 } else {
762                     Modifier.padding(start = startPadding, end = endPadding)
763                 }
764             if (label != null) {
765                 Box(
766                     Modifier.layoutId(LabelId)
767                         .textFieldLabelMinHeight {
768                             lerp(MinTextLineHeight, MinFocusedLabelLineHeight, labelProgress())
769                         }
770                         .wrapContentHeight()
771                         .then(labelPadding)
772                 ) {
773                     label()
774                 }
775             }
776 
777             val textPadding =
778                 Modifier.heightIn(min = MinTextLineHeight)
779                     .wrapContentHeight()
780                     .padding(
781                         start = if (prefix == null) startPadding else 0.dp,
782                         end = if (suffix == null) endPadding else 0.dp,
783                     )
784 
785             if (placeholder != null) {
786                 placeholder(Modifier.layoutId(PlaceholderId).then(textPadding))
787             }
788             Box(
789                 modifier = Modifier.layoutId(TextFieldId).then(textPadding),
790                 propagateMinConstraints = true,
791             ) {
792                 textField()
793             }
794 
795             if (supporting != null) {
796                 @OptIn(ExperimentalMaterial3Api::class)
797                 Box(
798                     Modifier.layoutId(SupportingId)
799                         .heightIn(min = MinSupportingTextLineHeight)
800                         .wrapContentHeight()
801                         .padding(TextFieldDefaults.supportingTextPadding())
802                 ) {
803                     supporting()
804                 }
805             }
806         },
807         measurePolicy = measurePolicy
808     )
809 }
810 
811 private class TextFieldMeasurePolicy(
812     private val singleLine: Boolean,
813     private val labelPosition: TextFieldLabelPosition,
814     private val labelProgress: FloatProducer,
815     private val paddingValues: PaddingValues,
816     private val minimizedLabelHalfHeight: Dp,
817 ) : MeasurePolicy {
measurenull818     override fun MeasureScope.measure(
819         measurables: List<Measurable>,
820         constraints: Constraints
821     ): MeasureResult {
822         val labelProgress = labelProgress()
823         val topPaddingValue = paddingValues.calculateTopPadding().roundToPx()
824         val bottomPaddingValue = paddingValues.calculateBottomPadding().roundToPx()
825 
826         var occupiedSpaceHorizontally = 0
827         var occupiedSpaceVertically = 0
828 
829         val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
830 
831         // measure leading icon
832         val leadingPlaceable =
833             measurables.fastFirstOrNull { it.layoutId == LeadingId }?.measure(looseConstraints)
834         occupiedSpaceHorizontally += leadingPlaceable.widthOrZero
835         occupiedSpaceVertically = max(occupiedSpaceVertically, leadingPlaceable.heightOrZero)
836 
837         // measure trailing icon
838         val trailingPlaceable =
839             measurables
840                 .fastFirstOrNull { it.layoutId == TrailingId }
841                 ?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
842         occupiedSpaceHorizontally += trailingPlaceable.widthOrZero
843         occupiedSpaceVertically = max(occupiedSpaceVertically, trailingPlaceable.heightOrZero)
844 
845         // measure prefix
846         val prefixPlaceable =
847             measurables
848                 .fastFirstOrNull { it.layoutId == PrefixId }
849                 ?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
850         occupiedSpaceHorizontally += prefixPlaceable.widthOrZero
851         occupiedSpaceVertically = max(occupiedSpaceVertically, prefixPlaceable.heightOrZero)
852 
853         // measure suffix
854         val suffixPlaceable =
855             measurables
856                 .fastFirstOrNull { it.layoutId == SuffixId }
857                 ?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
858         occupiedSpaceHorizontally += suffixPlaceable.widthOrZero
859         occupiedSpaceVertically = max(occupiedSpaceVertically, suffixPlaceable.heightOrZero)
860 
861         val isLabelAbove = labelPosition is TextFieldLabelPosition.Above
862         val labelMeasurable = measurables.fastFirstOrNull { it.layoutId == LabelId }
863         var labelPlaceable: Placeable? = null
864         val labelIntrinsicHeight: Int
865         if (!isLabelAbove) {
866             // if label is not Above, we can measure it like normal
867             val labelConstraints =
868                 looseConstraints.offset(
869                     vertical = -bottomPaddingValue,
870                     horizontal = -occupiedSpaceHorizontally
871                 )
872             labelPlaceable = labelMeasurable?.measure(labelConstraints)
873             labelIntrinsicHeight = 0
874         } else {
875             // if label is Above, it must be measured after other elements, but we
876             // reserve space for it using its intrinsic height as a heuristic
877             labelIntrinsicHeight = labelMeasurable?.minIntrinsicHeight(constraints.minWidth) ?: 0
878         }
879 
880         // supporting text must be measured after other elements, but we
881         // reserve space for it using its intrinsic height as a heuristic
882         val supportingMeasurable = measurables.fastFirstOrNull { it.layoutId == SupportingId }
883         val supportingIntrinsicHeight =
884             supportingMeasurable?.minIntrinsicHeight(constraints.minWidth) ?: 0
885 
886         // at most one of these is non-zero
887         val labelHeightOrIntrinsic = labelPlaceable.heightOrZero + labelIntrinsicHeight
888 
889         // measure input field
890         val effectiveTopOffset = topPaddingValue + labelHeightOrIntrinsic
891         val textFieldConstraints =
892             constraints
893                 .copy(minHeight = 0)
894                 .offset(
895                     vertical = -effectiveTopOffset - bottomPaddingValue - supportingIntrinsicHeight,
896                     horizontal = -occupiedSpaceHorizontally
897                 )
898         val textFieldPlaceable =
899             measurables.fastFirst { it.layoutId == TextFieldId }.measure(textFieldConstraints)
900 
901         // measure placeholder
902         val placeholderConstraints = textFieldConstraints.copy(minWidth = 0)
903         val placeholderPlaceable =
904             measurables
905                 .fastFirstOrNull { it.layoutId == PlaceholderId }
906                 ?.measure(placeholderConstraints)
907 
908         occupiedSpaceVertically =
909             max(
910                 occupiedSpaceVertically,
911                 max(textFieldPlaceable.heightOrZero, placeholderPlaceable.heightOrZero) +
912                     effectiveTopOffset +
913                     bottomPaddingValue
914             )
915         val width =
916             calculateWidth(
917                 leadingWidth = leadingPlaceable.widthOrZero,
918                 trailingWidth = trailingPlaceable.widthOrZero,
919                 prefixWidth = prefixPlaceable.widthOrZero,
920                 suffixWidth = suffixPlaceable.widthOrZero,
921                 textFieldWidth = textFieldPlaceable.width,
922                 labelWidth = labelPlaceable.widthOrZero,
923                 placeholderWidth = placeholderPlaceable.widthOrZero,
924                 constraints = constraints,
925             )
926 
927         if (isLabelAbove) {
928             // now that we know the width, measure label
929             val labelConstraints =
930                 looseConstraints.copy(maxHeight = labelIntrinsicHeight, maxWidth = width)
931             labelPlaceable = labelMeasurable?.measure(labelConstraints)
932         }
933 
934         // measure supporting text
935         val supportingConstraints =
936             looseConstraints
937                 .offset(vertical = -occupiedSpaceVertically)
938                 .copy(minHeight = 0, maxWidth = width)
939         val supportingPlaceable = supportingMeasurable?.measure(supportingConstraints)
940         val supportingHeight = supportingPlaceable.heightOrZero
941 
942         val totalHeight =
943             calculateHeight(
944                 textFieldHeight = textFieldPlaceable.height,
945                 labelHeight = labelPlaceable.heightOrZero,
946                 leadingHeight = leadingPlaceable.heightOrZero,
947                 trailingHeight = trailingPlaceable.heightOrZero,
948                 prefixHeight = prefixPlaceable.heightOrZero,
949                 suffixHeight = suffixPlaceable.heightOrZero,
950                 placeholderHeight = placeholderPlaceable.heightOrZero,
951                 supportingHeight = supportingPlaceable.heightOrZero,
952                 constraints = constraints,
953                 isLabelAbove = isLabelAbove,
954                 labelProgress = labelProgress,
955             )
956         val height =
957             totalHeight - supportingHeight - (if (isLabelAbove) labelPlaceable.heightOrZero else 0)
958 
959         val containerPlaceable =
960             measurables
961                 .fastFirst { it.layoutId == ContainerId }
962                 .measure(
963                     Constraints(
964                         minWidth = if (width != Constraints.Infinity) width else 0,
965                         maxWidth = width,
966                         minHeight = if (height != Constraints.Infinity) height else 0,
967                         maxHeight = height
968                     )
969                 )
970 
971         return layout(width, totalHeight) {
972             if (labelPlaceable != null) {
973                 val labelStartY =
974                     when {
975                         isLabelAbove -> 0
976                         singleLine ->
977                             Alignment.CenterVertically.align(labelPlaceable.height, height)
978                         else ->
979                             // The padding defined by the user only applies to the text field when
980                             // the label is focused. More padding needs to be added when the text
981                             // field is unfocused.
982                             topPaddingValue + minimizedLabelHalfHeight.roundToPx()
983                     }
984                 val labelEndY =
985                     when {
986                         isLabelAbove -> 0
987                         else -> topPaddingValue
988                     }
989                 placeWithLabel(
990                     width = width,
991                     totalHeight = totalHeight,
992                     textfieldPlaceable = textFieldPlaceable,
993                     labelPlaceable = labelPlaceable,
994                     placeholderPlaceable = placeholderPlaceable,
995                     leadingPlaceable = leadingPlaceable,
996                     trailingPlaceable = trailingPlaceable,
997                     prefixPlaceable = prefixPlaceable,
998                     suffixPlaceable = suffixPlaceable,
999                     containerPlaceable = containerPlaceable,
1000                     supportingPlaceable = supportingPlaceable,
1001                     labelStartY = labelStartY,
1002                     labelEndY = labelEndY,
1003                     isLabelAbove = isLabelAbove,
1004                     labelProgress = labelProgress,
1005                     textPosition =
1006                         topPaddingValue + (if (isLabelAbove) 0 else labelPlaceable.height),
1007                     layoutDirection = layoutDirection,
1008                 )
1009             } else {
1010                 placeWithoutLabel(
1011                     width = width,
1012                     totalHeight = totalHeight,
1013                     textPlaceable = textFieldPlaceable,
1014                     placeholderPlaceable = placeholderPlaceable,
1015                     leadingPlaceable = leadingPlaceable,
1016                     trailingPlaceable = trailingPlaceable,
1017                     prefixPlaceable = prefixPlaceable,
1018                     suffixPlaceable = suffixPlaceable,
1019                     containerPlaceable = containerPlaceable,
1020                     supportingPlaceable = supportingPlaceable,
1021                     density = density,
1022                 )
1023             }
1024         }
1025     }
1026 
maxIntrinsicHeightnull1027     override fun IntrinsicMeasureScope.maxIntrinsicHeight(
1028         measurables: List<IntrinsicMeasurable>,
1029         width: Int
1030     ): Int {
1031         return intrinsicHeight(measurables, width) { intrinsicMeasurable, w ->
1032             intrinsicMeasurable.maxIntrinsicHeight(w)
1033         }
1034     }
1035 
minIntrinsicHeightnull1036     override fun IntrinsicMeasureScope.minIntrinsicHeight(
1037         measurables: List<IntrinsicMeasurable>,
1038         width: Int
1039     ): Int {
1040         return intrinsicHeight(measurables, width) { intrinsicMeasurable, w ->
1041             intrinsicMeasurable.minIntrinsicHeight(w)
1042         }
1043     }
1044 
maxIntrinsicWidthnull1045     override fun IntrinsicMeasureScope.maxIntrinsicWidth(
1046         measurables: List<IntrinsicMeasurable>,
1047         height: Int
1048     ): Int {
1049         return intrinsicWidth(measurables, height) { intrinsicMeasurable, h ->
1050             intrinsicMeasurable.maxIntrinsicWidth(h)
1051         }
1052     }
1053 
minIntrinsicWidthnull1054     override fun IntrinsicMeasureScope.minIntrinsicWidth(
1055         measurables: List<IntrinsicMeasurable>,
1056         height: Int
1057     ): Int {
1058         return intrinsicWidth(measurables, height) { intrinsicMeasurable, h ->
1059             intrinsicMeasurable.minIntrinsicWidth(h)
1060         }
1061     }
1062 
intrinsicWidthnull1063     private fun intrinsicWidth(
1064         measurables: List<IntrinsicMeasurable>,
1065         height: Int,
1066         intrinsicMeasurer: (IntrinsicMeasurable, Int) -> Int
1067     ): Int {
1068         val textFieldWidth =
1069             intrinsicMeasurer(measurables.fastFirst { it.layoutId == TextFieldId }, height)
1070         val labelWidth =
1071             measurables
1072                 .fastFirstOrNull { it.layoutId == LabelId }
1073                 ?.let { intrinsicMeasurer(it, height) } ?: 0
1074         val trailingWidth =
1075             measurables
1076                 .fastFirstOrNull { it.layoutId == TrailingId }
1077                 ?.let { intrinsicMeasurer(it, height) } ?: 0
1078         val prefixWidth =
1079             measurables
1080                 .fastFirstOrNull { it.layoutId == PrefixId }
1081                 ?.let { intrinsicMeasurer(it, height) } ?: 0
1082         val suffixWidth =
1083             measurables
1084                 .fastFirstOrNull { it.layoutId == SuffixId }
1085                 ?.let { intrinsicMeasurer(it, height) } ?: 0
1086         val leadingWidth =
1087             measurables
1088                 .fastFirstOrNull { it.layoutId == LeadingId }
1089                 ?.let { intrinsicMeasurer(it, height) } ?: 0
1090         val placeholderWidth =
1091             measurables
1092                 .fastFirstOrNull { it.layoutId == PlaceholderId }
1093                 ?.let { intrinsicMeasurer(it, height) } ?: 0
1094         return calculateWidth(
1095             leadingWidth = leadingWidth,
1096             trailingWidth = trailingWidth,
1097             prefixWidth = prefixWidth,
1098             suffixWidth = suffixWidth,
1099             textFieldWidth = textFieldWidth,
1100             labelWidth = labelWidth,
1101             placeholderWidth = placeholderWidth,
1102             constraints = Constraints(),
1103         )
1104     }
1105 
intrinsicHeightnull1106     private fun IntrinsicMeasureScope.intrinsicHeight(
1107         measurables: List<IntrinsicMeasurable>,
1108         width: Int,
1109         intrinsicMeasurer: (IntrinsicMeasurable, Int) -> Int
1110     ): Int {
1111         var remainingWidth = width
1112         val leadingHeight =
1113             measurables
1114                 .fastFirstOrNull { it.layoutId == LeadingId }
1115                 ?.let {
1116                     remainingWidth =
1117                         remainingWidth.subtractConstraintSafely(
1118                             it.maxIntrinsicWidth(Constraints.Infinity)
1119                         )
1120                     intrinsicMeasurer(it, width)
1121                 } ?: 0
1122         val trailingHeight =
1123             measurables
1124                 .fastFirstOrNull { it.layoutId == TrailingId }
1125                 ?.let {
1126                     remainingWidth =
1127                         remainingWidth.subtractConstraintSafely(
1128                             it.maxIntrinsicWidth(Constraints.Infinity)
1129                         )
1130                     intrinsicMeasurer(it, width)
1131                 } ?: 0
1132         val labelHeight =
1133             measurables
1134                 .fastFirstOrNull { it.layoutId == LabelId }
1135                 ?.let { intrinsicMeasurer(it, remainingWidth) } ?: 0
1136 
1137         val prefixHeight =
1138             measurables
1139                 .fastFirstOrNull { it.layoutId == PrefixId }
1140                 ?.let {
1141                     val height = intrinsicMeasurer(it, remainingWidth)
1142                     remainingWidth =
1143                         remainingWidth.subtractConstraintSafely(
1144                             it.maxIntrinsicWidth(Constraints.Infinity)
1145                         )
1146                     height
1147                 } ?: 0
1148         val suffixHeight =
1149             measurables
1150                 .fastFirstOrNull { it.layoutId == SuffixId }
1151                 ?.let {
1152                     val height = intrinsicMeasurer(it, remainingWidth)
1153                     remainingWidth =
1154                         remainingWidth.subtractConstraintSafely(
1155                             it.maxIntrinsicWidth(Constraints.Infinity)
1156                         )
1157                     height
1158                 } ?: 0
1159 
1160         val textFieldHeight =
1161             intrinsicMeasurer(measurables.fastFirst { it.layoutId == TextFieldId }, remainingWidth)
1162         val placeholderHeight =
1163             measurables
1164                 .fastFirstOrNull { it.layoutId == PlaceholderId }
1165                 ?.let { intrinsicMeasurer(it, remainingWidth) } ?: 0
1166 
1167         val supportingHeight =
1168             measurables
1169                 .fastFirstOrNull { it.layoutId == SupportingId }
1170                 ?.let { intrinsicMeasurer(it, width) } ?: 0
1171 
1172         return calculateHeight(
1173             textFieldHeight = textFieldHeight,
1174             labelHeight = labelHeight,
1175             leadingHeight = leadingHeight,
1176             trailingHeight = trailingHeight,
1177             prefixHeight = prefixHeight,
1178             suffixHeight = suffixHeight,
1179             placeholderHeight = placeholderHeight,
1180             supportingHeight = supportingHeight,
1181             constraints = Constraints(),
1182             isLabelAbove = labelPosition is TextFieldLabelPosition.Above,
1183             labelProgress = labelProgress(),
1184         )
1185     }
1186 
calculateWidthnull1187     private fun calculateWidth(
1188         leadingWidth: Int,
1189         trailingWidth: Int,
1190         prefixWidth: Int,
1191         suffixWidth: Int,
1192         textFieldWidth: Int,
1193         labelWidth: Int,
1194         placeholderWidth: Int,
1195         constraints: Constraints
1196     ): Int {
1197         val affixTotalWidth = prefixWidth + suffixWidth
1198         val middleSection =
1199             maxOf(
1200                 textFieldWidth + affixTotalWidth,
1201                 placeholderWidth + affixTotalWidth,
1202                 // Prefix/suffix does not get applied to label
1203                 labelWidth,
1204             )
1205         val wrappedWidth = leadingWidth + middleSection + trailingWidth
1206         return constraints.constrainWidth(wrappedWidth)
1207     }
1208 
calculateHeightnull1209     private fun Density.calculateHeight(
1210         textFieldHeight: Int,
1211         labelHeight: Int,
1212         leadingHeight: Int,
1213         trailingHeight: Int,
1214         prefixHeight: Int,
1215         suffixHeight: Int,
1216         placeholderHeight: Int,
1217         supportingHeight: Int,
1218         constraints: Constraints,
1219         isLabelAbove: Boolean,
1220         labelProgress: Float,
1221     ): Int {
1222         val verticalPadding =
1223             (paddingValues.calculateTopPadding() + paddingValues.calculateBottomPadding())
1224                 .roundToPx()
1225 
1226         val inputFieldHeight =
1227             maxOf(
1228                 textFieldHeight,
1229                 placeholderHeight,
1230                 prefixHeight,
1231                 suffixHeight,
1232                 if (isLabelAbove) 0 else lerp(labelHeight, 0, labelProgress)
1233             )
1234 
1235         val hasLabel = labelHeight > 0
1236         val nonOverlappedLabelHeight =
1237             if (hasLabel && !isLabelAbove) {
1238                 // The label animates from overlapping the input field to floating above it,
1239                 // so its contribution to the height calculation changes over time. A baseline
1240                 // height is provided in the unfocused state to keep the overall height consistent
1241                 // across the animation.
1242                 max(
1243                     (minimizedLabelHalfHeight * 2).roundToPx(),
1244                     lerp(
1245                         0,
1246                         labelHeight,
1247                         EasingEmphasizedAccelerateCubicBezier.transform(labelProgress)
1248                     )
1249                 )
1250             } else {
1251                 0
1252             }
1253 
1254         val middleSectionHeight = verticalPadding + nonOverlappedLabelHeight + inputFieldHeight
1255 
1256         return constraints.constrainHeight(
1257             (if (isLabelAbove) labelHeight else 0) +
1258                 maxOf(leadingHeight, trailingHeight, middleSectionHeight) +
1259                 supportingHeight
1260         )
1261     }
1262 
1263     /**
1264      * Places the provided text field, placeholder, and label in the TextField given the
1265      * PaddingValues when there is a label. When there is no label, [placeWithoutLabel] is used
1266      * instead.
1267      */
placeWithLabelnull1268     private fun Placeable.PlacementScope.placeWithLabel(
1269         width: Int,
1270         totalHeight: Int,
1271         textfieldPlaceable: Placeable,
1272         labelPlaceable: Placeable,
1273         placeholderPlaceable: Placeable?,
1274         leadingPlaceable: Placeable?,
1275         trailingPlaceable: Placeable?,
1276         prefixPlaceable: Placeable?,
1277         suffixPlaceable: Placeable?,
1278         containerPlaceable: Placeable,
1279         supportingPlaceable: Placeable?,
1280         labelStartY: Int,
1281         labelEndY: Int,
1282         isLabelAbove: Boolean,
1283         labelProgress: Float,
1284         textPosition: Int,
1285         layoutDirection: LayoutDirection,
1286     ) {
1287         val yOffset = if (isLabelAbove) labelPlaceable.height else 0
1288 
1289         // place container
1290         containerPlaceable.place(0, yOffset)
1291 
1292         // Most elements should be positioned w.r.t the text field's "visual" height, i.e.,
1293         // excluding the label (if it's Above) and the supporting text on bottom
1294         val height =
1295             totalHeight -
1296                 supportingPlaceable.heightOrZero -
1297                 (if (isLabelAbove) labelPlaceable.height else 0)
1298 
1299         leadingPlaceable?.placeRelative(
1300             0,
1301             yOffset + Alignment.CenterVertically.align(leadingPlaceable.height, height)
1302         )
1303 
1304         val labelY = lerp(labelStartY, labelEndY, labelProgress)
1305         if (isLabelAbove) {
1306             val labelX =
1307                 labelPosition.minimizedAlignment.align(
1308                     size = labelPlaceable.width,
1309                     space = width,
1310                     layoutDirection = layoutDirection,
1311                 )
1312             // Not placeRelative because alignment already handles RTL
1313             labelPlaceable.place(labelX, labelY)
1314         } else {
1315             val leftIconWidth =
1316                 if (layoutDirection == LayoutDirection.Ltr) leadingPlaceable.widthOrZero
1317                 else trailingPlaceable.widthOrZero
1318             val labelStartX =
1319                 labelPosition.expandedAlignment.align(
1320                     size = labelPlaceable.width,
1321                     space = width - leadingPlaceable.widthOrZero - trailingPlaceable.widthOrZero,
1322                     layoutDirection = layoutDirection,
1323                 ) + leftIconWidth
1324             val labelEndX =
1325                 labelPosition.minimizedAlignment.align(
1326                     size = labelPlaceable.width,
1327                     space = width - leadingPlaceable.widthOrZero - trailingPlaceable.widthOrZero,
1328                     layoutDirection = layoutDirection,
1329                 ) + leftIconWidth
1330             val labelX = lerp(labelStartX, labelEndX, labelProgress)
1331             // Not placeRelative because alignment already handles RTL
1332             labelPlaceable.place(labelX, labelY)
1333         }
1334 
1335         prefixPlaceable?.placeRelative(leadingPlaceable.widthOrZero, yOffset + textPosition)
1336 
1337         val textHorizontalPosition = leadingPlaceable.widthOrZero + prefixPlaceable.widthOrZero
1338         textfieldPlaceable.placeRelative(textHorizontalPosition, yOffset + textPosition)
1339         placeholderPlaceable?.placeRelative(textHorizontalPosition, yOffset + textPosition)
1340 
1341         suffixPlaceable?.placeRelative(
1342             width - trailingPlaceable.widthOrZero - suffixPlaceable.width,
1343             yOffset + textPosition,
1344         )
1345 
1346         trailingPlaceable?.placeRelative(
1347             width - trailingPlaceable.width,
1348             yOffset + Alignment.CenterVertically.align(trailingPlaceable.height, height)
1349         )
1350 
1351         supportingPlaceable?.placeRelative(0, yOffset + height)
1352     }
1353 
1354     /**
1355      * Places the provided text field and placeholder in [TextField] when there is no label. When
1356      * there is a label, [placeWithLabel] is used
1357      */
Placeablenull1358     private fun Placeable.PlacementScope.placeWithoutLabel(
1359         width: Int,
1360         totalHeight: Int,
1361         textPlaceable: Placeable,
1362         placeholderPlaceable: Placeable?,
1363         leadingPlaceable: Placeable?,
1364         trailingPlaceable: Placeable?,
1365         prefixPlaceable: Placeable?,
1366         suffixPlaceable: Placeable?,
1367         containerPlaceable: Placeable,
1368         supportingPlaceable: Placeable?,
1369         density: Float,
1370     ) {
1371         // place container
1372         containerPlaceable.place(IntOffset.Zero)
1373 
1374         // Most elements should be positioned w.r.t the text field's "visual" height, i.e.,
1375         // excluding the supporting text on bottom
1376         val height = totalHeight - supportingPlaceable.heightOrZero
1377         val topPadding = (paddingValues.calculateTopPadding().value * density).roundToInt()
1378 
1379         leadingPlaceable?.placeRelative(
1380             0,
1381             Alignment.CenterVertically.align(leadingPlaceable.height, height)
1382         )
1383 
1384         // Single line text field without label places its text components centered vertically.
1385         // Multiline text field without label places its text components at the top with padding.
1386         fun calculateVerticalPosition(placeable: Placeable): Int {
1387             return if (singleLine) {
1388                 Alignment.CenterVertically.align(placeable.height, height)
1389             } else {
1390                 topPadding
1391             }
1392         }
1393 
1394         prefixPlaceable?.placeRelative(
1395             leadingPlaceable.widthOrZero,
1396             calculateVerticalPosition(prefixPlaceable)
1397         )
1398 
1399         val textHorizontalPosition = leadingPlaceable.widthOrZero + prefixPlaceable.widthOrZero
1400 
1401         textPlaceable.placeRelative(
1402             textHorizontalPosition,
1403             calculateVerticalPosition(textPlaceable)
1404         )
1405 
1406         placeholderPlaceable?.placeRelative(
1407             textHorizontalPosition,
1408             calculateVerticalPosition(placeholderPlaceable)
1409         )
1410 
1411         suffixPlaceable?.placeRelative(
1412             width - trailingPlaceable.widthOrZero - suffixPlaceable.width,
1413             calculateVerticalPosition(suffixPlaceable),
1414         )
1415 
1416         trailingPlaceable?.placeRelative(
1417             width - trailingPlaceable.width,
1418             Alignment.CenterVertically.align(trailingPlaceable.height, height)
1419         )
1420 
1421         supportingPlaceable?.placeRelative(0, height)
1422     }
1423 }
1424 
1425 internal data class IndicatorLineElement(
1426     val enabled: Boolean,
1427     val isError: Boolean,
1428     val interactionSource: InteractionSource,
1429     val colors: TextFieldColors?,
1430     val textFieldShape: Shape?,
1431     val focusedIndicatorLineThickness: Dp,
1432     val unfocusedIndicatorLineThickness: Dp,
1433 ) : ModifierNodeElement<IndicatorLineNode>() {
createnull1434     override fun create(): IndicatorLineNode {
1435         return IndicatorLineNode(
1436             enabled = enabled,
1437             isError = isError,
1438             interactionSource = interactionSource,
1439             colors = colors,
1440             textFieldShape = textFieldShape,
1441             focusedIndicatorWidth = focusedIndicatorLineThickness,
1442             unfocusedIndicatorWidth = unfocusedIndicatorLineThickness,
1443         )
1444     }
1445 
updatenull1446     override fun update(node: IndicatorLineNode) {
1447         node.update(
1448             enabled = enabled,
1449             isError = isError,
1450             interactionSource = interactionSource,
1451             colors = colors,
1452             textFieldShape = textFieldShape,
1453             focusedIndicatorWidth = focusedIndicatorLineThickness,
1454             unfocusedIndicatorWidth = unfocusedIndicatorLineThickness,
1455         )
1456     }
1457 
inspectablePropertiesnull1458     override fun InspectorInfo.inspectableProperties() {
1459         name = "indicatorLine"
1460         properties["enabled"] = enabled
1461         properties["isError"] = isError
1462         properties["interactionSource"] = interactionSource
1463         properties["colors"] = colors
1464         properties["textFieldShape"] = textFieldShape
1465         properties["focusedIndicatorLineThickness"] = focusedIndicatorLineThickness
1466         properties["unfocusedIndicatorLineThickness"] = unfocusedIndicatorLineThickness
1467     }
1468 }
1469 
1470 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
1471 internal class IndicatorLineNode(
1472     private var enabled: Boolean,
1473     private var isError: Boolean,
1474     private var interactionSource: InteractionSource,
1475     colors: TextFieldColors?,
1476     textFieldShape: Shape?,
1477     private var focusedIndicatorWidth: Dp,
1478     private var unfocusedIndicatorWidth: Dp,
1479 ) : DelegatingNode(), CompositionLocalConsumerModifierNode {
1480     private var focused = false
1481     private var trackFocusStateJob: Job? = null
1482 
1483     private var _colors: TextFieldColors? = colors
1484     private val colors: TextFieldColors
1485         get() =
1486             _colors
1487                 ?: currentValueOf(LocalColorScheme)
1488                     .defaultTextFieldColors(currentValueOf(LocalTextSelectionColors))
1489 
1490     // Must be initialized in `onAttach` so `colors` can read from the `MaterialTheme`
1491     private var colorAnimatable: Animatable<Color, AnimationVector4D>? = null
1492 
1493     private var _shape: Shape? = textFieldShape
1494         private set(value) {
1495             if (field != value) {
1496                 field = value
1497                 drawWithCacheModifierNode.invalidateDrawCache()
1498             }
1499         }
1500 
1501     private val shape: Shape
1502         get() =
1503             _shape ?: currentValueOf(LocalShapes).fromToken(FilledTextFieldTokens.ContainerShape)
1504 
1505     private val widthAnimatable: Animatable<Dp, AnimationVector1D> =
1506         Animatable(
1507             initialValue =
1508                 if (focused && this.enabled) this.focusedIndicatorWidth
1509                 else this.unfocusedIndicatorWidth,
1510             typeConverter = Dp.VectorConverter,
1511         )
1512 
updatenull1513     fun update(
1514         enabled: Boolean,
1515         isError: Boolean,
1516         interactionSource: InteractionSource,
1517         colors: TextFieldColors?,
1518         textFieldShape: Shape?,
1519         focusedIndicatorWidth: Dp,
1520         unfocusedIndicatorWidth: Dp,
1521     ) {
1522         var shouldInvalidate = false
1523 
1524         if (this.enabled != enabled) {
1525             this.enabled = enabled
1526             shouldInvalidate = true
1527         }
1528 
1529         if (this.isError != isError) {
1530             this.isError = isError
1531             shouldInvalidate = true
1532         }
1533 
1534         if (this.interactionSource !== interactionSource) {
1535             this.interactionSource = interactionSource
1536             trackFocusStateJob?.cancel()
1537             trackFocusStateJob = coroutineScope.launch { trackFocusState() }
1538         }
1539 
1540         if (this._colors != colors) {
1541             this._colors = colors
1542             shouldInvalidate = true
1543         }
1544 
1545         if (this._shape != textFieldShape) {
1546             this._shape = textFieldShape
1547             shouldInvalidate = true
1548         }
1549 
1550         if (this.focusedIndicatorWidth != focusedIndicatorWidth) {
1551             this.focusedIndicatorWidth = focusedIndicatorWidth
1552             shouldInvalidate = true
1553         }
1554 
1555         if (this.unfocusedIndicatorWidth != unfocusedIndicatorWidth) {
1556             this.unfocusedIndicatorWidth = unfocusedIndicatorWidth
1557             shouldInvalidate = true
1558         }
1559 
1560         if (shouldInvalidate) {
1561             invalidateIndicator()
1562         }
1563     }
1564 
1565     override val shouldAutoInvalidate: Boolean
1566         get() = false
1567 
onAttachnull1568     override fun onAttach() {
1569         trackFocusStateJob = coroutineScope.launch { trackFocusState() }
1570         if (colorAnimatable == null) {
1571             val initialColor = colors.indicatorColor(enabled, isError, focused)
1572             colorAnimatable =
1573                 Animatable(
1574                     initialValue = initialColor,
1575                     typeConverter = Color.VectorConverter(initialColor.colorSpace),
1576                 )
1577         }
1578     }
1579 
1580     /** Copied from [InteractionSource.collectIsFocusedAsState] */
trackFocusStatenull1581     private suspend fun trackFocusState() {
1582         focused = false
1583         val focusInteractions = mutableListOf<FocusInteraction.Focus>()
1584         interactionSource.interactions.collect { interaction ->
1585             when (interaction) {
1586                 is FocusInteraction.Focus -> focusInteractions.add(interaction)
1587                 is FocusInteraction.Unfocus -> focusInteractions.remove(interaction.focus)
1588             }
1589             val isFocused = focusInteractions.isNotEmpty()
1590             if (isFocused != focused) {
1591                 focused = isFocused
1592                 invalidateIndicator()
1593             }
1594         }
1595     }
1596 
invalidateIndicatornull1597     private fun invalidateIndicator() {
1598         coroutineScope.launch {
1599             colorAnimatable?.animateTo(
1600                 targetValue = colors.indicatorColor(enabled, isError, focused),
1601                 animationSpec =
1602                     if (enabled) {
1603                         currentValueOf(LocalMotionScheme)
1604                             .fromToken(MotionSchemeKeyTokens.FastEffects)
1605                     } else {
1606                         snap()
1607                     },
1608             )
1609         }
1610         coroutineScope.launch {
1611             widthAnimatable.animateTo(
1612                 targetValue =
1613                     if (focused && enabled) focusedIndicatorWidth else unfocusedIndicatorWidth,
1614                 animationSpec =
1615                     if (enabled) {
1616                         currentValueOf(LocalMotionScheme)
1617                             .fromToken(MotionSchemeKeyTokens.FastSpatial)
1618                     } else {
1619                         snap()
1620                     },
1621             )
1622         }
1623     }
1624 
1625     private val drawWithCacheModifierNode =
1626         delegate(
<lambda>null1627             CacheDrawModifierNode {
1628                 val strokeWidth = widthAnimatable.value.toPx()
1629                 val textFieldShapePath =
1630                     Path().apply {
1631                         addOutline(
1632                             this@IndicatorLineNode.shape.createOutline(
1633                                 size,
1634                                 layoutDirection,
1635                                 density = this@CacheDrawModifierNode
1636                             )
1637                         )
1638                     }
1639                 val linePath =
1640                     Path().apply {
1641                         addRect(
1642                             Rect(
1643                                 left = 0f,
1644                                 top = size.height - strokeWidth,
1645                                 right = size.width,
1646                                 bottom = size.height,
1647                             )
1648                         )
1649                     }
1650                 val clippedLine = linePath and textFieldShapePath
1651 
1652                 onDrawWithContent {
1653                     drawContent()
1654                     drawPath(
1655                         path = clippedLine,
1656                         brush = SolidColor(colorAnimatable!!.value),
1657                     )
1658                 }
1659             }
1660         )
1661 }
1662 
1663 /** Padding from text field top to label top, and from input field bottom to text field bottom */
1664 /*@VisibleForTesting*/
1665 internal val TextFieldWithLabelVerticalPadding = 8.dp
1666