Merge "Date picker input mode validation error height" into androidx-main
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DateInputTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DateInputTest.kt
index b857ce6..b2ce6f2 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DateInputTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DateInputTest.kt
@@ -20,17 +20,23 @@
 import androidx.compose.material3.internal.Strings
 import androidx.compose.material3.internal.formatWithSkeleton
 import androidx.compose.material3.internal.getString
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.test.SemanticsMatcher.Companion.expectValue
 import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertContentDescriptionEquals
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.getBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.unit.height
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -228,6 +234,29 @@
     }
 
     @Test
+    fun heightUnchangedWithError() {
+        lateinit var dateInputLabel: String
+        rule.setMaterialContent(lightColorScheme()) {
+            dateInputLabel = getString(string = Strings.DateInputLabel)
+            DatePicker(
+                state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input),
+                modifier = Modifier.testTag(DateInputTestTag)
+            )
+        }
+        val withoutErrorBounds = rule.onNodeWithTag(DateInputTestTag).getBoundsInRoot()
+        // Type a date that will trigger an error text.
+        rule.onNodeWithText(dateInputLabel).performClick().performTextInput("12123000")
+        rule.waitForIdle()
+        val withErrorBounds = rule.onNodeWithTag(DateInputTestTag).getBoundsInRoot()
+
+        // Check that the height of the component did not change after having the error text visible
+        withoutErrorBounds.height.assertIsEqualTo(
+            withErrorBounds.height,
+            subject = "Date input height"
+        )
+    }
+
+    @Test
     fun defaultSemantics() {
         val selectedDateInUtcMillis = dayInUtcMilliseconds(year = 2010, month = 5, dayOfMonth = 11)
         lateinit var expectedHeadlineStringFormat: String
@@ -268,4 +297,6 @@
         firstDayCalendar[Calendar.DAY_OF_MONTH] = dayOfMonth
         return firstDayCalendar.timeInMillis
     }
+
+    private val DateInputTestTag = "DateInput"
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt
index 4af5390..ddaa0c4 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt
@@ -138,6 +138,17 @@
             )
         }
 
+    // Calculate how much bottom padding should be added. In case there is an error text, which is
+    // added as a supportingText, take into account the default supportingText padding to ensure
+    // the padding does not trigger a component height change.
+    val bottomPadding =
+        if (errorText.value.isBlank()) {
+            InputTextNonErroneousBottomPadding
+        } else {
+            val textFieldPadding = TextFieldDefaults.supportingTextPadding()
+            InputTextNonErroneousBottomPadding -
+                (textFieldPadding.calculateBottomPadding() + textFieldPadding.calculateTopPadding())
+        }
     OutlinedTextField(
         value = text,
         onValueChange = { input ->
@@ -175,18 +186,9 @@
             }
         },
         modifier =
-            modifier
-                // Add bottom padding when there is no error. Otherwise, remove it as the error text
-                // will take additional height.
-                .padding(
-                    bottom =
-                        if (errorText.value.isNotBlank()) {
-                            0.dp
-                        } else {
-                            InputTextNonErroneousBottomPadding
-                        }
-                )
-                .semantics { if (errorText.value.isNotBlank()) error(errorText.value) },
+            modifier.padding(bottom = bottomPadding).semantics {
+                if (errorText.value.isNotBlank()) error(errorText.value)
+            },
         label = label,
         placeholder = placeholder,
         supportingText = { if (errorText.value.isNotBlank()) Text(errorText.value) },