Fork `InteractiveComponentSize` from compose.material3
Add ExperimentalMaterial3CommonApi annotation.

Bug: b/295367813
Test: Ran ./gradlew :compileReleaseKotlin locally
Relnote: N/A
Change-Id: If8438aa56aa5e1d5d31c6faf882f2e4cbcc6da27
diff --git a/compose/material3/material3-common/api/current.txt b/compose/material3/material3-common/api/current.txt
index e6f50d0..3538cd0 100644
--- a/compose/material3/material3-common/api/current.txt
+++ b/compose/material3/material3-common/api/current.txt
@@ -1 +1,16 @@
 // Signature format: 4.0
+package androidx.compose.material3.common {
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-common API is experimental and is likely to change or to " + "be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3CommonApi {
+  }
+
+  public final class InteractiveComponentSizeKt {
+    method @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumTouchTargetEnforcement();
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
+    property @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
+    property @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
+  }
+
+}
+
diff --git a/compose/material3/material3-common/api/restricted_current.txt b/compose/material3/material3-common/api/restricted_current.txt
index e6f50d0..3538cd0 100644
--- a/compose/material3/material3-common/api/restricted_current.txt
+++ b/compose/material3/material3-common/api/restricted_current.txt
@@ -1 +1,16 @@
 // Signature format: 4.0
+package androidx.compose.material3.common {
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-common API is experimental and is likely to change or to " + "be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3CommonApi {
+  }
+
+  public final class InteractiveComponentSizeKt {
+    method @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumTouchTargetEnforcement();
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
+    property @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
+    property @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
+  }
+
+}
+
diff --git a/compose/material3/material3-common/build.gradle b/compose/material3/material3-common/build.gradle
index 23b5dba..19fc1a7 100644
--- a/compose/material3/material3-common/build.gradle
+++ b/compose/material3/material3-common/build.gradle
@@ -35,6 +35,14 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
+
+                api(project(":compose:foundation:foundation"))
+                api(project(":compose:foundation:foundation-layout"))
+                api(project(":compose:runtime:runtime"))
+                api(project(":compose:ui:ui-graphics"))
+                api(project(":compose:ui:ui-text"))
+
+                implementation(project(":compose:ui:ui-util"))
             }
         }
         androidMain.dependencies {
diff --git a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/ExperimentalMaterial3CommonApi.kt b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/ExperimentalMaterial3CommonApi.kt
new file mode 100644
index 0000000..09e229a
--- /dev/null
+++ b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/ExperimentalMaterial3CommonApi.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.common
+
+@RequiresOptIn(
+    "This material3-common API is experimental and is likely to change or to " +
+        "be removed in the future."
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalMaterial3CommonApi
diff --git a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt
new file mode 100644
index 0000000..6cb3de9
--- /dev/null
+++ b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.common
+
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
+
+/**
+ * Reserves at least 48.dp in size to disambiguate touch interactions if the element would measure
+ * smaller.
+ *
+ * https://m3.material.io/foundations/accessible-design/accessibility-basics
+ *
+ * This uses the Material recommended minimum size of 48.dp x 48.dp, which may not the same as the
+ * system enforced minimum size. The minimum clickable / touch target size (48.dp by default) is
+ * controlled by the system via ViewConfiguration and automatically expanded at the touch input
+ * layer.
+ *
+ * This modifier is not needed for touch target expansion to happen. It only affects layout, to make
+ * sure there is adequate space for touch target expansion.
+ */
+@Stable
+fun Modifier.minimumInteractiveComponentSize(): Modifier = this then MinimumInteractiveModifier
+
+internal object MinimumInteractiveModifier :
+    ModifierNodeElement<MinimumInteractiveModifierNode>() {
+
+    override fun create(): MinimumInteractiveModifierNode = MinimumInteractiveModifierNode()
+
+    override fun update(node: MinimumInteractiveModifierNode) {}
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "minimumInteractiveComponentSize"
+        // TODO: b/214589635 - surface this information through the layout inspector in a better way
+        //  - for now just add some information to help developers debug what this size represents.
+        properties["README"] = "Reserves at least 48.dp in size to disambiguate touch " +
+            "interactions if the element would measure smaller"
+    }
+
+    override fun hashCode(): Int = System.identityHashCode(this)
+    override fun equals(other: Any?) = (other === this)
+}
+
+internal class MinimumInteractiveModifierNode :
+    Modifier.Node(),
+    CompositionLocalConsumerModifierNode,
+    LayoutModifierNode {
+
+    @OptIn(ExperimentalMaterial3CommonApi::class)
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val size = minimumInteractiveComponentSize
+        val placeable = measurable.measure(constraints)
+        val enforcement = isAttached && currentValueOf(LocalMinimumInteractiveComponentEnforcement)
+
+        // Be at least as big as the minimum dimension in both dimensions
+        val width = if (enforcement) {
+            maxOf(placeable.width, size.width.roundToPx())
+        } else {
+            placeable.width
+        }
+        val height = if (enforcement) {
+            maxOf(placeable.height, size.height.roundToPx())
+        } else {
+            placeable.height
+        }
+
+        return layout(width, height) {
+            val centerX = ((width - placeable.width) / 2f).roundToInt()
+            val centerY = ((height - placeable.height) / 2f).roundToInt()
+            placeable.place(centerX, centerY)
+        }
+    }
+}
+
+/**
+ * CompositionLocal that configures whether Material components that have a visual size that is
+ * lower than the minimum touch target size for accessibility (such as Button) will include
+ * extra space outside the component to ensure that they are accessible. If set to false there
+ * will be no extra space, and so it is possible that if the component is placed near the edge of
+ * a layout / near to another component without any padding, there will not be enough space for
+ * an accessible touch target.
+ */
+@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:ExperimentalMaterial3CommonApi
+@ExperimentalMaterial3CommonApi
+val LocalMinimumInteractiveComponentEnforcement: ProvidableCompositionLocal<Boolean> =
+    staticCompositionLocalOf { true }
+
+/**
+ * CompositionLocal that configures whether Material components that have a visual size that is
+ * lower than the minimum touch target size for accessibility (such as [Button]) will include
+ * extra space outside the component to ensure that they are accessible. If set to false there
+ * will be no extra space, and so it is possible that if the component is placed near the edge of
+ * a layout / near to another component without any padding, there will not be enough space for
+ * an accessible touch target.
+ */
+@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:ExperimentalMaterial3CommonApi
+@ExperimentalMaterial3CommonApi
+@Deprecated(
+    message = "Use LocalMinimumInteractiveComponentEnforcement instead.",
+    replaceWith = ReplaceWith(
+        "LocalMinimumInteractiveComponentEnforcement"
+    ),
+    level = DeprecationLevel.WARNING
+)
+val LocalMinimumTouchTargetEnforcement: ProvidableCompositionLocal<Boolean> =
+    LocalMinimumInteractiveComponentEnforcement
+
+private class MinimumInteractiveComponentSizeModifier(val size: DpSize) : LayoutModifier {
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+
+        val placeable = measurable.measure(constraints)
+
+        // Be at least as big as the minimum dimension in both dimensions
+        val width = maxOf(placeable.width, size.width.roundToPx())
+        val height = maxOf(placeable.height, size.height.roundToPx())
+
+        return layout(width, height) {
+            val centerX = ((width - placeable.width) / 2f).roundToInt()
+            val centerY = ((height - placeable.height) / 2f).roundToInt()
+            placeable.place(centerX, centerY)
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        val otherModifier = other as? MinimumInteractiveComponentSizeModifier ?: return false
+        return size == otherModifier.size
+    }
+
+    override fun hashCode(): Int {
+        return size.hashCode()
+    }
+}
+
+private val minimumInteractiveComponentSize: DpSize = DpSize(48.dp, 48.dp)
diff --git a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/androidx-compose-material3-material3-common-documentation.md b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/androidx-compose-material3-material3-common-documentation.md
similarity index 100%
rename from compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/androidx-compose-material3-material3-common-documentation.md
rename to compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/androidx-compose-material3-material3-common-documentation.md