[Tooltip] Adding caret feature to rich tooltip variant

Adding an enum that determines what type of caret is drawn in drawCaretWithPath(). Updating the rich tooltip positioning logic to allow for carets to be drawn.

Test: Adding a caret-anchor positioning test for rich tooltip. Change assert to use .isWithin().of() for a more flexible test.
Bug: 297037973
Relnote: Adding a default caret for rich tooltip, new rich tooltip API now allows for custom caret to be drawn given anchor LayoutCoordinates.
Change-Id: Ifd42c2be34f72060cccce6414e28c1b2c01e025a
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt
index 765d502..d0095e9 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt
@@ -127,7 +127,7 @@
     }
 
     @Composable
-    private fun RichTooltipTest() {
+    private fun CaretScope.RichTooltipTest() {
         RichTooltip(
             title = { Text("Subhead") },
             action = {
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 5cad6eb..0d4ebce 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1965,7 +1965,6 @@
   }
 
   public final class TooltipKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.CaretScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -1978,6 +1977,7 @@
 
   public final class Tooltip_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.CaretScope, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.CaretProperties? caretProperties, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.CaretScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.CaretProperties? caretProperties, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 5cad6eb..0d4ebce 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1965,7 +1965,6 @@
   }
 
   public final class TooltipKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.CaretScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -1978,6 +1977,7 @@
 
   public final class Tooltip_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.CaretScope, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.CaretProperties? caretProperties, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.CaretScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.CaretProperties? caretProperties, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index f90bda5..4b9f5d2 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -105,6 +105,7 @@
 import androidx.compose.material3.samples.PinnedTopAppBar
 import androidx.compose.material3.samples.PlainTooltipSample
 import androidx.compose.material3.samples.PlainTooltipWithCaret
+import androidx.compose.material3.samples.PlainTooltipWithCustomCaret
 import androidx.compose.material3.samples.PlainTooltipWithManualInvocationSample
 import androidx.compose.material3.samples.PrimaryIconTabs
 import androidx.compose.material3.samples.PrimaryTextTabs
@@ -116,6 +117,8 @@
 import androidx.compose.material3.samples.RangeSliderSample
 import androidx.compose.material3.samples.RangeSliderWithCustomComponents
 import androidx.compose.material3.samples.RichTooltipSample
+import androidx.compose.material3.samples.RichTooltipWithCaretSample
+import androidx.compose.material3.samples.RichTooltipWithCustomCaretSample
 import androidx.compose.material3.samples.RichTooltipWithManualInvocationSample
 import androidx.compose.material3.samples.ScaffoldWithCoroutinesSnackbar
 import androidx.compose.material3.samples.ScaffoldWithCustomSnackbar
@@ -1231,6 +1234,13 @@
         PlainTooltipWithCaret()
     },
     Example(
+        name = ::PlainTooltipWithCustomCaret.name,
+        description = TooltipsExampleDescription,
+        sourceUrl = TooltipsExampleSourceUrl
+    ) {
+        PlainTooltipWithCustomCaret()
+    },
+    Example(
         name = ::RichTooltipSample.name,
         description = TooltipsExampleDescription,
         sourceUrl = TooltipsExampleSourceUrl
@@ -1243,5 +1253,19 @@
         sourceUrl = TooltipsExampleSourceUrl
     ) {
         RichTooltipWithManualInvocationSample()
+    },
+    Example(
+        name = ::RichTooltipWithCaretSample.name,
+        description = TooltipsExampleDescription,
+        sourceUrl = TooltipsExampleSourceUrl
+    ) {
+        RichTooltipWithCaretSample()
+    },
+    Example(
+        name = ::RichTooltipWithCustomCaretSample.name,
+        description = TooltipsExampleDescription,
+        sourceUrl = TooltipsExampleSourceUrl
+    ) {
+        RichTooltipWithCustomCaretSample()
     }
 )
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
index bb5327a..f2c1c0d 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
@@ -24,6 +24,7 @@
 import androidx.compose.material.icons.filled.AddCircle
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.filled.Info
+import androidx.compose.material3.CaretProperties
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
@@ -130,6 +131,32 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Sampled
 @Composable
+fun PlainTooltipWithCustomCaret() {
+    TooltipBox(
+        positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+        tooltip = {
+            PlainTooltip(
+                caretProperties = CaretProperties(12.dp, 24.dp)
+            ) {
+                Text("Add to favorites")
+            }
+        },
+        state = rememberTooltipState()
+    ) {
+        IconButton(
+            onClick = { /* Icon button's click event */ }
+        ) {
+            Icon(
+                imageVector = Icons.Filled.Favorite,
+                contentDescription = "Localized Description"
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
 fun RichTooltipSample() {
     val tooltipState = rememberTooltipState(isPersistent = true)
     val scope = rememberCoroutineScope()
@@ -201,6 +228,74 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun RichTooltipWithCaretSample() {
+    val tooltipState = rememberTooltipState(isPersistent = true)
+    val scope = rememberCoroutineScope()
+    TooltipBox(
+        positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+        tooltip = {
+            RichTooltip(
+                title = { Text(richTooltipSubheadText) },
+                action = {
+                    TextButton(
+                        onClick = { scope.launch { tooltipState.dismiss() } }
+                    ) { Text(richTooltipActionText) }
+                },
+                caretProperties = TooltipDefaults.caretProperties
+            ) {
+                Text(richTooltipText)
+            }
+        },
+        state = tooltipState
+    ) {
+        IconButton(
+            onClick = { /* Icon button's click event */ }
+        ) {
+            Icon(
+                imageVector = Icons.Filled.Info,
+                contentDescription = "Localized Description"
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun RichTooltipWithCustomCaretSample() {
+    val tooltipState = rememberTooltipState(isPersistent = true)
+    val scope = rememberCoroutineScope()
+    TooltipBox(
+        positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+        tooltip = {
+            RichTooltip(
+                title = { Text(richTooltipSubheadText) },
+                action = {
+                    TextButton(
+                        onClick = { scope.launch { tooltipState.dismiss() } }
+                    ) { Text(richTooltipActionText) }
+                },
+                caretProperties = CaretProperties(16.dp, 32.dp)
+            ) {
+                Text(richTooltipText)
+            }
+        },
+        state = tooltipState
+    ) {
+        IconButton(
+            onClick = { /* Icon button's click event */ }
+        ) {
+            Icon(
+                imageVector = Icons.Filled.Info,
+                contentDescription = "Localized Description"
+            )
+        }
+    }
+}
+
 const val richTooltipSubheadText = "Permissions"
 const val richTooltipText =
     "Configure permissions for selected service accounts. " +
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
index 72e05c8..1cc240e 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
@@ -493,7 +493,52 @@
             )
         }
 
-        assertThat(anchorBounds == expectedAnchorBounds).isTrue()
+        assertThat(anchorBounds.left).isWithin(0.001f).of(expectedAnchorBounds.left)
+        assertThat(anchorBounds.top).isWithin(0.001f).of(expectedAnchorBounds.top)
+        assertThat(anchorBounds.right).isWithin(0.001f).of(expectedAnchorBounds.right)
+        assertThat(anchorBounds.bottom).isWithin(0.001f).of(expectedAnchorBounds.bottom)
+    }
+
+    @Test
+    fun richTooltip_caretAnchorPositioning() {
+        var anchorBounds = Rect.Zero
+        rule.setContent {
+            TooltipBox(
+                positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+                state = rememberTooltipState(initialIsVisible = true, isPersistent = true),
+                tooltip = {
+                    RichTooltip(
+                        modifier = Modifier.drawCaret {
+                            it?.let { anchorBounds = it.boundsInWindow() }
+                            onDrawBehind {}
+                        }
+                    ) {}
+                }
+            ) {
+                Icon(
+                    Icons.Filled.Favorite,
+                    modifier = Modifier.testTag(AnchorTestTag),
+                    contentDescription = null
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        val expectedAnchorBoundsDp =
+            rule.onNodeWithTag(AnchorTestTag, true).getUnclippedBoundsInRoot()
+        val expectedAnchorBounds = with(rule.density) {
+            Rect(
+                expectedAnchorBoundsDp.left.roundToPx().toFloat(),
+                expectedAnchorBoundsDp.top.roundToPx().toFloat(),
+                expectedAnchorBoundsDp.right.roundToPx().toFloat(),
+                expectedAnchorBoundsDp.bottom.roundToPx().toFloat()
+            )
+        }
+
+        assertThat(anchorBounds.left).isWithin(0.001f).of(expectedAnchorBounds.left)
+        assertThat(anchorBounds.top).isWithin(0.001f).of(expectedAnchorBounds.top)
+        assertThat(anchorBounds.right).isWithin(0.001f).of(expectedAnchorBounds.right)
+        assertThat(anchorBounds.bottom).isWithin(0.001f).of(expectedAnchorBounds.bottom)
     }
 
     @Test
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
index e6368fc..4a98b2c 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
@@ -18,9 +18,13 @@
 
 import android.content.res.Configuration
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
@@ -58,7 +62,7 @@
 @ExperimentalMaterial3Api
 actual fun CaretScope.PlainTooltip(
     modifier: Modifier,
-    caretProperties: (CaretProperties)?,
+    caretProperties: CaretProperties?,
     shape: Shape,
     contentColor: Color,
     containerColor: Color,
@@ -66,23 +70,23 @@
     shadowElevation: Dp,
     content: @Composable () -> Unit
 ) {
-    val customModifier =
+    val drawCaretModifier =
         if (caretProperties != null) {
             val density = LocalDensity.current
             val configuration = LocalConfiguration.current
             Modifier.drawCaret { anchorLayoutCoordinates ->
-                    drawCaretWithPath(
-                        density,
-                        configuration,
-                        containerColor,
-                        caretProperties,
-                        anchorLayoutCoordinates
-                    )
-                }.then(modifier)
+                drawCaretWithPath(
+                    CaretType.Plain,
+                    density,
+                    configuration,
+                    containerColor,
+                    caretProperties,
+                    anchorLayoutCoordinates
+                )
+            }.then(modifier)
         } else modifier
-
     Surface(
-        modifier = customModifier,
+        modifier = drawCaretModifier,
         shape = shape,
         color = containerColor,
         tonalElevation = tonalElevation,
@@ -108,8 +112,123 @@
     }
 }
 
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretProperties [CaretProperties] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Pass in null for this parameter if no
+ * caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+actual fun CaretScope.RichTooltip(
+    modifier: Modifier,
+    title: (@Composable () -> Unit)?,
+    action: (@Composable () -> Unit)?,
+    caretProperties: CaretProperties?,
+    shape: Shape,
+    colors: RichTooltipColors,
+    tonalElevation: Dp,
+    shadowElevation: Dp,
+    text: @Composable () -> Unit
+) {
+    val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
+    val elevatedColor =
+        MaterialTheme.colorScheme.applyTonalElevation(
+            colors.containerColor,
+            absoluteElevation
+        )
+    val drawCaretModifier =
+        if (caretProperties != null) {
+            val density = LocalDensity.current
+            val configuration = LocalConfiguration.current
+            Modifier.drawCaret { anchorLayoutCoordinates ->
+                drawCaretWithPath(
+                    CaretType.Rich,
+                    density,
+                    configuration,
+                    elevatedColor,
+                    caretProperties,
+                    anchorLayoutCoordinates
+                )
+            }.then(modifier)
+        } else modifier
+    Surface(
+        modifier = drawCaretModifier
+            .sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = RichTooltipMaxWidth,
+                minHeight = TooltipMinHeight
+            ),
+        shape = shape,
+        color = colors.containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        val actionLabelTextStyle =
+            MaterialTheme.typography.fromToken(RichTooltipTokens.ActionLabelTextFont)
+        val subheadTextStyle =
+            MaterialTheme.typography.fromToken(RichTooltipTokens.SubheadFont)
+        val supportingTextStyle =
+            MaterialTheme.typography.fromToken(RichTooltipTokens.SupportingTextFont)
+
+        Column(
+            modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+        ) {
+            title?.let {
+                Box(
+                    modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.titleContentColor,
+                        LocalTextStyle provides subheadTextStyle,
+                        content = it
+                    )
+                }
+            }
+            Box(
+                modifier = Modifier.textVerticalPadding(
+                    title != null,
+                    action != null
+                )
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.contentColor,
+                    LocalTextStyle provides supportingTextStyle,
+                    content = text
+                )
+            }
+            action?.let {
+                Box(
+                    modifier = Modifier
+                        .requiredHeightIn(min = ActionLabelMinHeight)
+                        .padding(bottom = ActionLabelBottomPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.actionContentColor,
+                        LocalTextStyle provides actionLabelTextStyle,
+                        content = it
+                    )
+                }
+            }
+        }
+    }
+}
+
 @ExperimentalMaterial3Api
 private fun CacheDrawScope.drawCaretWithPath(
+    caretType: CaretType,
     density: Density,
     configuration: Configuration,
     containerColor: Color,
@@ -140,18 +259,46 @@
         val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
         val caretY = if (isCaretTop) { 0f } else { tooltipHeight }
 
-        val position =
-            if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
-                val anchorMidFromRightScreenEdge =
-                    screenWidthPx - anchorMid
-                val caretX = tooltipWidth - anchorMidFromRightScreenEdge
-                Offset(caretX, caretY)
-            } else {
-                val tooltipLeft =
-                    anchorLeft - (this.size.width / 2 - anchorWidth / 2)
-                val caretX = anchorMid - maxOf(tooltipLeft, 0f)
-                Offset(caretX, caretY)
+        val position: Offset
+        if (caretType == CaretType.Plain) {
+            position =
+                if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
+                    // Caret needs to be near the right
+                    val anchorMidFromRightScreenEdge =
+                        screenWidthPx - anchorMid
+                    val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+                    Offset(caretX, caretY)
+                } else {
+                    // Caret needs to be near the left
+                    val tooltipLeft =
+                        anchorLeft - (this.size.width / 2 - anchorWidth / 2)
+                    val caretX = anchorMid - maxOf(tooltipLeft, 0f)
+                    Offset(caretX, caretY)
+                }
+        } else {
+            // Default the caret to the left
+            var preferredPosition = Offset(anchorMid - anchorLeft, caretY)
+            if (anchorLeft + tooltipWidth > screenWidthPx) {
+                // Need to move the caret to the right
+                preferredPosition = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
+                if (anchorRight - tooltipWidth < 0) {
+                    // Need to center the caret
+                    // Caret might need to be offset depending on where
+                    // the tooltip is placed relative to the anchor
+                    if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
+                        preferredPosition = Offset(anchorMid, caretY)
+                    } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
+                        val anchorMidFromRightScreenEdge =
+                            screenWidthPx - anchorMid
+                        val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+                        preferredPosition = Offset(caretX, caretY)
+                    } else {
+                        preferredPosition = Offset(tooltipWidth / 2, caretY)
+                    }
+                }
             }
+            position = preferredPosition
+        }
 
         if (isCaretTop) {
             path.apply {
@@ -182,3 +329,8 @@
         }
     }
 }
+
+@ExperimentalMaterial3Api
+private enum class CaretType {
+    Plain, Rich
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index a0dc11e..458be3a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -26,16 +26,12 @@
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.paddingFromBaseline
-import androidx.compose.foundation.layout.requiredHeightIn
-import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.tokens.PlainTooltipTokens
 import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
@@ -84,6 +80,10 @@
  *
  * @sample androidx.compose.material3.samples.PlainTooltipWithCaret
  *
+ * Plain tooltip shown on long press with a custom [CaretProperties]
+ *
+ * @sample androidx.compose.material3.samples.PlainTooltipWithCustomCaret
+ *
  * Tooltip that is invoked when the anchor is long pressed:
  *
  * @sample androidx.compose.material3.samples.RichTooltipSample
@@ -92,6 +92,14 @@
  *
  * @sample androidx.compose.material3.samples.RichTooltipWithManualInvocationSample
  *
+ * Rich tooltip with caret shown on long press:
+ *
+ * @sample androidx.compose.material3.samples.RichTooltipWithCaretSample
+ *
+ * Rich tooltip shown on long press with a custom [CaretProperties]
+ *
+ * @sample androidx.compose.material3.samples.RichTooltipWithCustomCaretSample
+ *
  * @param positionProvider [PopupPositionProvider] that will be used to place the tooltip
  * relative to the anchor content.
  * @param tooltip the composable that will be used to populate the tooltip's content.
@@ -185,7 +193,7 @@
 @ExperimentalMaterial3Api
 expect fun CaretScope.PlainTooltip(
     modifier: Modifier = Modifier,
-    caretProperties: (CaretProperties)? = null,
+    caretProperties: CaretProperties? = null,
     shape: Shape = TooltipDefaults.plainTooltipContainerShape,
     contentColor: Color = TooltipDefaults.plainTooltipContentColor,
     containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
@@ -195,6 +203,38 @@
 )
 
 /**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretProperties [CaretProperties] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Pass in null for this parameter if no
+ * caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+expect fun CaretScope.RichTooltip(
+    modifier: Modifier = Modifier,
+    title: (@Composable () -> Unit)? = null,
+    action: (@Composable () -> Unit)? = null,
+    caretProperties: CaretProperties? = null,
+    shape: Shape = TooltipDefaults.richTooltipContainerShape,
+    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
+    tonalElevation: Dp = RichTooltipTokens.ContainerElevation,
+    shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
+    text: @Composable () -> Unit
+)
+
+/**
  * Properties for the caret of the tooltip if enabled.
  *
  * @param caretHeight the height of the caret
@@ -208,92 +248,6 @@
 )
 
 /**
- * Rich text tooltip that allows the user to pass in a title, text, and action.
- * Tooltips are used to provide a descriptive message.
- *
- * Usually used with [TooltipBox]
- *
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param title An optional title for the tooltip.
- * @param action An optional action for the tooltip.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param text the composable that will be used to populate the rich tooltip's text.
- */
-@Composable
-@ExperimentalMaterial3Api
-fun RichTooltip(
-    modifier: Modifier = Modifier,
-    title: (@Composable () -> Unit)? = null,
-    action: (@Composable () -> Unit)? = null,
-    shape: Shape = TooltipDefaults.richTooltipContainerShape,
-    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
-    tonalElevation: Dp = RichTooltipTokens.ContainerElevation,
-    shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
-    text: @Composable () -> Unit
-) {
-    Surface(
-        modifier = modifier
-            .sizeIn(
-                minWidth = TooltipMinWidth,
-                maxWidth = RichTooltipMaxWidth,
-                minHeight = TooltipMinHeight
-            ),
-        shape = shape,
-        color = colors.containerColor,
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation
-    ) {
-        val actionLabelTextStyle =
-            MaterialTheme.typography.fromToken(RichTooltipTokens.ActionLabelTextFont)
-        val subheadTextStyle =
-            MaterialTheme.typography.fromToken(RichTooltipTokens.SubheadFont)
-        val supportingTextStyle =
-            MaterialTheme.typography.fromToken(RichTooltipTokens.SupportingTextFont)
-
-        Column(
-            modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
-        ) {
-            title?.let {
-                Box(
-                    modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
-                ) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.titleContentColor,
-                        LocalTextStyle provides subheadTextStyle,
-                        content = it
-                    )
-                }
-            }
-            Box(
-                modifier = Modifier.textVerticalPadding(title != null, action != null)
-            ) {
-                CompositionLocalProvider(
-                    LocalContentColor provides colors.contentColor,
-                    LocalTextStyle provides supportingTextStyle,
-                    content = text
-                )
-            }
-            action?.let {
-                Box(
-                    modifier = Modifier
-                        .requiredHeightIn(min = ActionLabelMinHeight)
-                        .padding(bottom = ActionLabelBottomPadding)
-                ) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.actionContentColor,
-                        LocalTextStyle provides actionLabelTextStyle,
-                        content = it
-                    )
-                }
-            }
-        }
-    }
-}
-
-/**
  * Tooltip defaults that contain default values for both [PlainTooltip] and [RichTooltip]
  */
 @ExperimentalMaterial3Api
@@ -420,11 +374,11 @@
                     layoutDirection: LayoutDirection,
                     popupContentSize: IntSize
                 ): IntOffset {
-                    var x = anchorBounds.right
+                    var x = anchorBounds.left
                     // Try to shift it to the left of the anchor
                     // if the tooltip would collide with the right side of the screen
                     if (x + popupContentSize.width > windowSize.width) {
-                        x = anchorBounds.left - popupContentSize.width
+                        x = anchorBounds.right - popupContentSize.width
                         // Center if it'll also collide with the left side of the screen
                         if (x < 0)
                             x = anchorBounds.left +
@@ -632,7 +586,7 @@
 }
 
 @Stable
-private fun Modifier.textVerticalPadding(
+internal fun Modifier.textVerticalPadding(
     subheadExists: Boolean,
     actionExists: Boolean
 ): Modifier {
@@ -706,13 +660,13 @@
 private val PlainTooltipHorizontalPadding = 8.dp
 internal val PlainTooltipContentPadding =
     PaddingValues(PlainTooltipHorizontalPadding, PlainTooltipVerticalPadding)
-private val RichTooltipMaxWidth = 320.dp
+internal val RichTooltipMaxWidth = 320.dp
 internal val RichTooltipHorizontalPadding = 16.dp
-private val HeightToSubheadFirstLine = 28.dp
+internal val HeightToSubheadFirstLine = 28.dp
 private val HeightFromSubheadToTextFirstLine = 24.dp
 private val TextBottomPadding = 16.dp
-private val ActionLabelMinHeight = 36.dp
-private val ActionLabelBottomPadding = 8.dp
+internal val ActionLabelMinHeight = 36.dp
+internal val ActionLabelBottomPadding = 8.dp
 // No specification for fade in and fade out duration, so aligning it with the behavior for snack bar
 internal const val TooltipFadeInDuration = 150
 internal const val TooltipFadeOutDuration = 75
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
index 6760005..f867216 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
@@ -17,9 +17,13 @@
 package androidx.compose.material3
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
@@ -47,7 +51,7 @@
 @ExperimentalMaterial3Api
 actual fun CaretScope.PlainTooltip(
     modifier: Modifier,
-    caretProperties: (CaretProperties)?,
+    caretProperties: CaretProperties?,
     shape: Shape,
     contentColor: Color,
     containerColor: Color,
@@ -80,3 +84,93 @@
         }
     }
 }
+
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretProperties [CaretProperties] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Pass in null for this parameter if no
+ * caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+actual fun CaretScope.RichTooltip(
+    modifier: Modifier,
+    title: (@Composable () -> Unit)?,
+    action: (@Composable () -> Unit)?,
+    caretProperties: CaretProperties?,
+    shape: Shape,
+    colors: RichTooltipColors,
+    tonalElevation: Dp,
+    shadowElevation: Dp,
+    text: @Composable () -> Unit
+) {
+    Surface(
+        modifier = modifier
+            .sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = RichTooltipMaxWidth,
+                minHeight = TooltipMinHeight
+            ),
+        shape = shape,
+        color = colors.containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        val actionLabelTextStyle =
+            MaterialTheme.typography.fromToken(RichTooltipTokens.ActionLabelTextFont)
+        val subheadTextStyle =
+            MaterialTheme.typography.fromToken(RichTooltipTokens.SubheadFont)
+        val supportingTextStyle =
+            MaterialTheme.typography.fromToken(RichTooltipTokens.SupportingTextFont)
+
+        Column(
+            modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+        ) {
+            title?.let {
+                Box(
+                    modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.titleContentColor,
+                        LocalTextStyle provides subheadTextStyle,
+                        content = it
+                    )
+                }
+            }
+            Box(
+                modifier = Modifier.textVerticalPadding(title != null, action != null)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.contentColor,
+                    LocalTextStyle provides supportingTextStyle,
+                    content = text
+                )
+            }
+            action?.let {
+                Box(
+                    modifier = Modifier
+                        .requiredHeightIn(min = ActionLabelMinHeight)
+                        .padding(bottom = ActionLabelBottomPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.actionContentColor,
+                        LocalTextStyle provides actionLabelTextStyle,
+                        content = it
+                    )
+                }
+            }
+        }
+    }
+}