From 68f5df2b315151a4c177167c8642019d54185a91 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Thu, 27 Jun 2024 17:10:23 +0200 Subject: [PATCH 1/2] Correctly merge `ExposedDropdownMenu` from Material 3 1.3 --- .../material3/ExposedDropdownMenu.android.kt | 338 +++++++ .../compose/material3/ExposedDropdownMenu.kt | 847 ++++++++++++------ .../material3/ExposedDropdownMenu.skiko.kt | 260 ++++-- .../compose/material3/SkikoMenu.skiko.kt | 4 +- .../material3/DropdownMenu3Example.kt | 3 +- 5 files changed, 1090 insertions(+), 362 deletions(-) create mode 100644 compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt new file mode 100644 index 0000000000000..479925f2d11f2 --- /dev/null +++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt @@ -0,0 +1,338 @@ +/* + * Copyright 2022 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 + +import android.graphics.Rect as ViewRect +import android.view.View +import android.view.ViewTreeObserver +import android.view.WindowManager +import androidx.activity.compose.BackHandler +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.statusBars +import androidx.compose.material3.internal.Strings +import androidx.compose.material3.internal.getString +import androidx.compose.material3.internal.rememberAccessibilityServiceState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.toComposeRect +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.constrainHeight +import androidx.compose.ui.unit.constrainWidth +import androidx.compose.ui.unit.toSize +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import kotlin.math.max +import kotlin.math.roundToInt + +@ExperimentalMaterial3Api +@Composable +actual fun ExposedDropdownMenuBox( + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, + modifier: Modifier, + content: @Composable ExposedDropdownMenuBoxScope.() -> Unit +) { + val config = LocalConfiguration.current + val view = LocalView.current + val density = LocalDensity.current + + val verticalMargin = with(density) { MenuVerticalMargin.roundToPx() } + + var anchorCoordinates by remember { mutableStateOf(null) } + var anchorWidth by remember { mutableIntStateOf(0) } + var menuMaxHeight by remember { mutableIntStateOf(0) } + + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + val expandedDescription = getString(Strings.MenuExpanded) + val collapsedDescription = getString(Strings.MenuCollapsed) + val toggleDescription = getString(Strings.ToggleDropdownMenu) + val anchorTypeState = remember { mutableStateOf(MenuAnchorType.PrimaryNotEditable) } + + val scope = + remember(expanded, onExpandedChange, config, view, density) { + object : ExposedDropdownMenuBoxScopeImpl() { + override fun Modifier.menuAnchor(type: MenuAnchorType, enabled: Boolean): Modifier = + this.focusRequester(focusRequester) + .then( + if (!enabled) Modifier + else + Modifier.expandable( + expanded = expanded, + onExpandedChange = { + anchorTypeState.value = type + onExpandedChange(!expanded) + }, + anchorType = type, + expandedDescription = expandedDescription, + collapsedDescription = collapsedDescription, + toggleDescription = toggleDescription, + keyboardController = keyboardController, + ) + ) + + override val anchorType: MenuAnchorType + get() = anchorTypeState.value + + override fun Modifier.exposedDropdownSize(matchTextFieldWidth: Boolean): Modifier = + layout { measurable, constraints -> + val menuWidth = constraints.constrainWidth(anchorWidth) + val menuConstraints = + constraints.copy( + maxHeight = constraints.constrainHeight(menuMaxHeight), + minWidth = + if (matchTextFieldWidth) menuWidth else constraints.minWidth, + maxWidth = + if (matchTextFieldWidth) menuWidth else constraints.maxWidth, + ) + val placeable = measurable.measure(menuConstraints) + layout(placeable.width, placeable.height) { placeable.place(0, 0) } + } + } + } + + Box( + modifier.onGloballyPositioned { + anchorCoordinates = it + anchorWidth = it.size.width + menuMaxHeight = + calculateMaxHeight( + windowBounds = view.rootView.getWindowBounds(), + anchorBounds = anchorCoordinates.getAnchorBounds(), + verticalMargin = verticalMargin, + ) + } + ) { + scope.content() + } + + if (expanded) { + SoftKeyboardListener(view, density) { + menuMaxHeight = + calculateMaxHeight( + windowBounds = view.rootView.getWindowBounds(), + anchorBounds = anchorCoordinates.getAnchorBounds(), + verticalMargin = verticalMargin, + ) + } + } + + SideEffect { if (expanded) focusRequester.requestFocus() } + + // Back events are handled in the Popup layer if the menu is focusable. + // If it's not focusable, we handle them here. + BackHandler(enabled = expanded) { onExpandedChange(false) } +} + +@Composable +private fun SoftKeyboardListener( + view: View, + density: Density, + onKeyboardVisibilityChange: () -> Unit, +) { + // It would be easier to listen to WindowInsets.ime, but that doesn't work with + // `setDecorFitsSystemWindows(window, true)`. Instead, listen to the view tree's global layout. + DisposableEffect(view, density) { + val listener = + object : View.OnAttachStateChangeListener, ViewTreeObserver.OnGlobalLayoutListener { + private var isListeningToGlobalLayout = false + + init { + view.addOnAttachStateChangeListener(this) + registerOnGlobalLayoutListener() + } + + override fun onViewAttachedToWindow(p0: View) = registerOnGlobalLayoutListener() + + override fun onViewDetachedFromWindow(p0: View) = unregisterOnGlobalLayoutListener() + + override fun onGlobalLayout() = onKeyboardVisibilityChange() + + private fun registerOnGlobalLayoutListener() { + if (isListeningToGlobalLayout || !view.isAttachedToWindow) return + view.viewTreeObserver.addOnGlobalLayoutListener(this) + isListeningToGlobalLayout = true + } + + private fun unregisterOnGlobalLayoutListener() { + if (!isListeningToGlobalLayout) return + view.viewTreeObserver.removeOnGlobalLayoutListener(this) + isListeningToGlobalLayout = false + } + + fun dispose() { + unregisterOnGlobalLayoutListener() + view.removeOnAttachStateChangeListener(this) + } + } + + onDispose { listener.dispose() } + } +} + +@Composable +internal actual fun ExposedDropdownMenuBoxScope.ExposedDropdownMenuDefaultImpl( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier, + scrollState: ScrollState, + matchTextFieldWidth: Boolean, + shape: Shape, + containerColor: Color, + tonalElevation: Dp, + shadowElevation: Dp, + border: BorderStroke?, + content: @Composable ColumnScope.() -> Unit, +) { + // Workaround for b/326394521. We create a state that's read in `calculatePosition`. + // Then trigger a state change in `SoftKeyboardListener` to force recalculation. + val keyboardSignalState = remember { mutableStateOf(Unit, neverEqualPolicy()) } + val view = LocalView.current + val density = LocalDensity.current + val topWindowInsets = WindowInsets.statusBars.getTop(density) + + if (expanded) { + SoftKeyboardListener(view, density) { keyboardSignalState.value = Unit } + } + + // TODO(b/326064777): use DropdownMenu when it supports custom PositionProvider + val expandedState = remember { MutableTransitionState(false) } + expandedState.targetState = expanded + + if (expandedState.currentState || expandedState.targetState) { + val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) } + val popupPositionProvider = + remember(density, topWindowInsets) { + ExposedDropdownMenuPositionProvider( + density = density, + topWindowInsets = topWindowInsets, + keyboardSignalState = keyboardSignalState, + ) { anchorBounds, menuBounds -> + transformOriginState.value = + calculateTransformOrigin(anchorBounds, menuBounds) + } + } + + Popup( + onDismissRequest = onDismissRequest, + popupPositionProvider = popupPositionProvider, + properties = popupProperties(anchorType), + ) { + DropdownMenuContent( + expandedState = expandedState, + transformOriginState = transformOriginState, + scrollState = scrollState, + shape = shape, + containerColor = containerColor, + tonalElevation = tonalElevation, + shadowElevation = shadowElevation, + border = border, + modifier = modifier.exposedDropdownSize(matchTextFieldWidth), + content = content, + ) + } + } +} + +/** + * Creates a [PopupProperties] used for [ExposedDropdownMenuBoxScope.ExposedDropdownMenu]. + * + * @param anchorType the type of element that is anchoring the menu. See [MenuAnchorType]. + */ +@Composable +private fun popupProperties(anchorType: MenuAnchorType): PopupProperties { + val a11yServicesEnabled by rememberAccessibilityServiceState() + var flags = + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + + // In order for a11y focus to jump to the menu when opened, it needs to be + // focusable and touch modal (NOT_FOCUSABLE and NOT_TOUCH_MODAL are *not* set). + if (!a11yServicesEnabled) { + flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + } + // If typing on the IME is required, the menu should not be focusable + // in order to prevent stealing focus from the input method. + val imeRequired = + anchorType == MenuAnchorType.PrimaryEditable || + (anchorType == MenuAnchorType.SecondaryEditable && !a11yServicesEnabled) + if (imeRequired) { + flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + } + + return PopupProperties(flags = flags) +} + +private fun calculateMaxHeight( + windowBounds: Rect, + anchorBounds: Rect?, + verticalMargin: Int, +): Int { + anchorBounds ?: return 0 + + val marginedWindowTop = windowBounds.top + verticalMargin + val marginedWindowBottom = windowBounds.bottom - verticalMargin + val availableHeight = + if (anchorBounds.top > windowBounds.bottom || anchorBounds.bottom < windowBounds.top) { + (marginedWindowBottom - marginedWindowTop).roundToInt() + } else { + val heightAbove = anchorBounds.top - marginedWindowTop + val heightBelow = marginedWindowBottom - anchorBounds.bottom + max(heightAbove, heightBelow).roundToInt() + } + + return max(availableHeight, 0) +} + +private fun View.getWindowBounds(): Rect = + ViewRect().let { + this.getWindowVisibleDisplayFrame(it) + it.toComposeRect() + } + +private fun LayoutCoordinates?.getAnchorBounds(): Rect { + // Don't use `boundsInWindow()` because it can report 0 when the window is animating/resizing + return if (this == null) Rect.Zero else Rect(positionInWindow(), size.toSize()) +} diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt index 04edfd1a97f38..a2088097f7885 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt @@ -23,33 +23,25 @@ import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.LocalTextSelectionColors import androidx.compose.foundation.text.selection.TextSelectionColors import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material3.internal.MenuPosition -import androidx.compose.material3.internal.Strings -import androidx.compose.material3.internal.getString -import androidx.compose.material3.internal.rememberAccessibilityServiceState import androidx.compose.material3.tokens.FilledAutocompleteTokens import androidx.compose.material3.tokens.OutlinedAutocompleteTokens import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.rotate +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.onClick @@ -62,18 +54,15 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.constrainHeight -import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.toSize -import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupPositionProvider -import androidx.compose.ui.window.PopupProperties +import kotlin.jvm.JvmInline import kotlin.math.max import kotlin.math.roundToInt /** - * Material Design Exposed Dropdown Menu. + * Material Design Exposed Dropdown Menu. * * Menus display a list of choices on a temporary surface. They appear when users interact with a * button, action, or other control. @@ -83,26 +72,27 @@ import kotlin.math.roundToInt * display user input (whether or not it’s listed as a menu choice), in which case it may be used to * implement autocomplete. * - * ![Exposed dropdown menu image](https://developer.android.com/images/reference/androidx/compose/material3/exposed-dropdown-menu.png) + * ![Exposed dropdown menu + * image](https://developer.android.com/images/reference/androidx/compose/material3/exposed-dropdown-menu.png) * * The [ExposedDropdownMenuBox] is expected to contain a [TextField] (or [OutlinedTextField]) and * [ExposedDropdownMenu][ExposedDropdownMenuBoxScope.ExposedDropdownMenu] as content. The * [menuAnchor][ExposedDropdownMenuBoxScope.menuAnchor] modifier should be passed to the text field. * * An example of a read-only Exposed Dropdown Menu: + * * @sample androidx.compose.material3.samples.ExposedDropdownMenuSample * * An example of an editable Exposed Dropdown Menu: + * * @sample androidx.compose.material3.samples.EditableExposedDropdownMenuSample * * @param expanded whether the menu is expanded or not * @param onExpandedChange called when the exposed dropdown menu is clicked and the expansion state - * changes. + * changes. * @param modifier the [Modifier] to be applied to this ExposedDropdownMenuBox * @param content the content of this ExposedDropdownMenuBox, typically a [TextField] and an - * [ExposedDropdownMenu][ExposedDropdownMenuBoxScope.ExposedDropdownMenu]. The - * [menuAnchor][ExposedDropdownMenuBoxScope.menuAnchor] modifier should be passed to the text field - * for proper menu behavior. + * [ExposedDropdownMenu][ExposedDropdownMenuBoxScope.ExposedDropdownMenu]. */ @ExperimentalMaterial3Api @Composable @@ -110,68 +100,68 @@ expect fun ExposedDropdownMenuBox( expanded: Boolean, onExpandedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, - content: @Composable ExposedDropdownMenuBoxScope.() -> Unit + content: @Composable ExposedDropdownMenuBoxScope.() -> Unit, ) -/** - * Scope for [ExposedDropdownMenuBox]. - */ +/** Scope for [ExposedDropdownMenuBox]. */ @ExperimentalMaterial3Api -abstract class ExposedDropdownMenuBoxScope { +sealed class ExposedDropdownMenuBoxScope { /** - * Modifier which should be applied to a [TextField] (or [OutlinedTextField]) placed inside the - * scope. It's responsible for handling menu expansion state on click, applying semantics to - * the component, and requesting focus. + * Modifier which should be applied to an element inside the [ExposedDropdownMenuBoxScope], + * typically a text field or an icon within the text field. It's responsible for expanding and + * collapsing the menu on click, applying semantics to the component, and requesting focus. + * + * @param type the type of element that is anchoring the menu. See [MenuAnchorType]. + * @param enabled controls the enabled state. When `false`, the component will not expand or + * collapse the menu in response to user input, and menu semantics will be invisible to + * accessibility services. Note that this value only controls interactions with the menu. It + * does not affect the enabled state of other kinds of interactions, such as [TextField]'s + * `enabled` parameter. */ - abstract fun Modifier.menuAnchor(): Modifier + abstract fun Modifier.menuAnchor(type: MenuAnchorType, enabled: Boolean = true): Modifier /** * Modifier which should be applied to a menu placed inside the [ExposedDropdownMenuBoxScope]. - * It will set constraints on the width and height of the menu so it will not overlap the - * text field or software keyboard. + * It will set constraints on the width and height of the menu so it will not overlap the text + * field or software keyboard. * - * [ExposedDropdownMenu] applies this modifier automatically, so this is only needed when - * using custom menu components. + * [ExposedDropdownMenu] applies this modifier automatically, so this is only needed when using + * custom menu components. * - * @param matchTextFieldWidth whether the menu's width should be forcefully constrained to - * match the width of the text field to which it's attached. + * @param matchTextFieldWidth whether the menu's width should be forcefully constrained to match + * the width of the text field to which it's attached. */ - abstract fun Modifier.exposedDropdownSize( - matchTextFieldWidth: Boolean = true - ): Modifier + abstract fun Modifier.exposedDropdownSize(matchTextFieldWidth: Boolean = true): Modifier + + internal abstract val anchorType: MenuAnchorType /** * Popup which contains content for Exposed Dropdown Menu. Should be used inside the content of * [ExposedDropdownMenuBox]. * * @param expanded whether the menu is expanded - * @param onDismissRequest called when the user requests to dismiss the menu, such as by - * tapping outside the menu's bounds + * @param onDismissRequest called when the user requests to dismiss the menu, such as by tapping + * outside the menu's bounds * @param modifier the [Modifier] to be applied to this menu * @param scrollState a [ScrollState] used by the menu's content for items vertical scrolling - * @param focusable whether the menu is focusable. If the text field is editable, this should - * be set to `false` so the menu doesn't steal focus from the input method. In the presence of - * certain accessibility services, this value will be overwritten to `true` to preserve - * accessibility. - * @param matchTextFieldWidth whether the menu's width should be forcefully constrained to - * match the width of the text field to which it's attached. + * @param matchTextFieldWidth whether the menu's width should be forcefully constrained to match + * the width of the text field to which it's attached. * @param shape the shape of the menu * @param containerColor the container color of the menu * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary - * color overlay is applied on top of the container. A higher tonal elevation value will result - * in a darker color in light theme and lighter color in dark theme. See also: [Surface]. + * color overlay is applied on top of the container. A higher tonal elevation value will + * result in a darker color in light theme and lighter color in dark theme. See also: + * [Surface]. * @param shadowElevation the elevation for the shadow below the menu * @param border the border to draw around the container of the menu. Pass `null` for no border. * @param content the content of the menu */ - @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE") @Composable fun ExposedDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, scrollState: ScrollState = rememberScrollState(), - focusable: Boolean = true, matchTextFieldWidth: Boolean = true, shape: Shape = MenuDefaults.shape, containerColor: Color = MenuDefaults.containerColor, @@ -181,9 +171,90 @@ abstract class ExposedDropdownMenuBoxScope { content: @Composable ColumnScope.() -> Unit, ) { ExposedDropdownMenuDefaultImpl( - expanded, onDismissRequest, modifier, scrollState, content + expanded = expanded, + onDismissRequest = onDismissRequest, + modifier = modifier, + scrollState = scrollState, + matchTextFieldWidth = matchTextFieldWidth, + shape = shape, + containerColor = containerColor, + tonalElevation = tonalElevation, + shadowElevation = shadowElevation, + border = border, + content = content ) } + + @Deprecated( + level = DeprecationLevel.WARNING, + message = "Use overload that takes MenuAnchorType and enabled parameters", + replaceWith = ReplaceWith("menuAnchor(type, enabled)") + ) + fun Modifier.menuAnchor(): Modifier = menuAnchor(MenuAnchorType.PrimaryNotEditable) + + @Deprecated( + level = DeprecationLevel.WARNING, + message = + "The `focusable` parameter is unused. Pass the proper MenuAnchorType to " + + "Modifier.menuAnchor instead, which will handle focusability automatically.", + ) + @Suppress("DeprecatedCallableAddReplaceWith", "UNUSED_PARAMETER") + @Composable + fun ExposedDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + scrollState: ScrollState = rememberScrollState(), + focusable: Boolean = true, + matchTextFieldWidth: Boolean = true, + shape: Shape = MenuDefaults.shape, + containerColor: Color = MenuDefaults.containerColor, + tonalElevation: Dp = MenuDefaults.TonalElevation, + shadowElevation: Dp = MenuDefaults.ShadowElevation, + border: BorderStroke? = null, + content: @Composable ColumnScope.() -> Unit, + ) = + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = onDismissRequest, + modifier = modifier, + scrollState = scrollState, + matchTextFieldWidth = matchTextFieldWidth, + shape = shape, + containerColor = containerColor, + tonalElevation = tonalElevation, + shadowElevation = shadowElevation, + border = border, + content = content, + ) + + @Deprecated( + level = DeprecationLevel.HIDDEN, + message = + "Maintained for binary compatibility. " + + "Use overload with customization options parameters." + ) + @Composable + fun ExposedDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + scrollState: ScrollState = rememberScrollState(), + content: @Composable ColumnScope.() -> Unit, + ) = + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = onDismissRequest, + modifier = modifier, + matchTextFieldWidth = true, + scrollState = scrollState, + shape = MenuDefaults.shape, + containerColor = MenuDefaults.containerColor, + tonalElevation = MenuDefaults.TonalElevation, + shadowElevation = MenuDefaults.ShadowElevation, + border = null, + content = content, + ) } @Composable @@ -192,12 +263,54 @@ internal expect fun ExposedDropdownMenuBoxScope.ExposedDropdownMenuDefaultImpl( onDismissRequest: () -> Unit, modifier: Modifier, scrollState: ScrollState, - content: @Composable ColumnScope.() -> Unit + matchTextFieldWidth: Boolean, + shape: Shape, + containerColor: Color, + tonalElevation: Dp, + shadowElevation: Dp, + border: BorderStroke?, + content: @Composable ColumnScope.() -> Unit, ) -/** - * Contains default values used by Exposed Dropdown Menu. - */ +// Sealed classes don't allow dynamic anonymous subclasses. +@OptIn(ExperimentalMaterial3Api::class) +internal abstract class ExposedDropdownMenuBoxScopeImpl : ExposedDropdownMenuBoxScope() + +/** The type of element that can serve as a dropdown menu anchor. */ +@JvmInline +value class MenuAnchorType private constructor(private val name: String) { + companion object { + /** + * A non-editable primary anchor of the dropdown menu, such as a read-only text field. + * + * An anchor of this type will open the menu with focus. + */ + val PrimaryNotEditable = MenuAnchorType("PrimaryNotEditable") + + /** + * An editable primary anchor of the dropdown menu, such as a text field that allows user + * input. + * + * An anchor of this type will open the menu without focus in order to preserve focus on the + * soft keyboard (IME). + */ + val PrimaryEditable = MenuAnchorType("PrimaryEditable") + + /** + * A secondary anchor of the dropdown menu that lives alongside an editable primary anchor, + * such as an icon within an editable text field. + * + * If accessibility services are enabled, an anchor of this type will open the menu with + * focus. Otherwise, the menu is opened without focus in order to preserve focus on the soft + * keyboard (IME). + */ + val SecondaryEditable = MenuAnchorType("SecondaryEditable") + } + + override fun toString(): String = name +} + +/** Contains default values used by Exposed Dropdown Menu. */ @ExperimentalMaterial3Api object ExposedDropdownMenuDefaults { /** @@ -212,11 +325,7 @@ object ExposedDropdownMenuDefaults { expanded: Boolean, modifier: Modifier = Modifier, ) { - Icon( - Icons.Filled.ArrowDropDown, - null, - modifier.rotate(if (expanded) 180f else 0f) - ) + Icon(Icons.Filled.ArrowDropDown, null, modifier.rotate(if (expanded) 180f else 0f)) } /** @@ -226,10 +335,10 @@ object ExposedDropdownMenuDefaults { * * @param focusedTextColor the color used for the input text of this text field when focused * @param unfocusedTextColor the color used for the input text of this text field when not - * focused + * focused * @param disabledTextColor the color used for the input text of this text field when disabled * @param errorTextColor the color used for the input text of this text field when in error - * state + * state * @param focusedContainerColor the container color for this text field when focused * @param unfocusedContainerColor the container color for this text field when not focused * @param disabledContainerColor the container color for this text field when disabled @@ -247,7 +356,7 @@ object ExposedDropdownMenuDefaults { * @param errorLeadingIconColor the leading icon color for this text field when in error state * @param focusedTrailingIconColor the trailing icon color for this text field when focused * @param unfocusedTrailingIconColor the trailing icon color for this text field when not - * focused + * focused * @param disabledTrailingIconColor the trailing icon color for this text field when disabled * @param errorTrailingIconColor the trailing icon color for this text field when in error state * @param focusedLabelColor the label color for this text field when focused @@ -271,8 +380,10 @@ object ExposedDropdownMenuDefaults { fun textFieldColors( focusedTextColor: Color = FilledAutocompleteTokens.FieldFocusInputTextColor.value, unfocusedTextColor: Color = FilledAutocompleteTokens.FieldInputTextColor.value, - disabledTextColor: Color = FilledAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity), + disabledTextColor: Color = + FilledAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity + ), errorTextColor: Color = FilledAutocompleteTokens.FieldErrorInputTextColor.value, focusedContainerColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.value, unfocusedContainerColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.value, @@ -286,17 +397,18 @@ object ExposedDropdownMenuDefaults { unfocusedIndicatorColor: Color = FilledAutocompleteTokens.TextFieldActiveIndicatorColor.value, disabledIndicatorColor: Color = - FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorOpacity), + FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorOpacity + ), errorIndicatorColor: Color = FilledAutocompleteTokens.TextFieldErrorActiveIndicatorColor.value, focusedLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldFocusLeadingIconColor.value, - unfocusedLeadingIconColor: Color = - FilledAutocompleteTokens.TextFieldLeadingIconColor.value, + unfocusedLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldLeadingIconColor.value, disabledLeadingIconColor: Color = - FilledAutocompleteTokens.TextFieldDisabledLeadingIconColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledLeadingIconOpacity), + FilledAutocompleteTokens.TextFieldDisabledLeadingIconColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledLeadingIconOpacity + ), errorLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldErrorLeadingIconColor.value, focusedTrailingIconColor: Color = @@ -304,31 +416,35 @@ object ExposedDropdownMenuDefaults { unfocusedTrailingIconColor: Color = FilledAutocompleteTokens.TextFieldTrailingIconColor.value, disabledTrailingIconColor: Color = - FilledAutocompleteTokens.TextFieldDisabledTrailingIconColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledTrailingIconOpacity), + FilledAutocompleteTokens.TextFieldDisabledTrailingIconColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledTrailingIconOpacity + ), errorTrailingIconColor: Color = FilledAutocompleteTokens.TextFieldErrorTrailingIconColor.value, focusedLabelColor: Color = FilledAutocompleteTokens.FieldFocusLabelTextColor.value, unfocusedLabelColor: Color = FilledAutocompleteTokens.FieldLabelTextColor.value, disabledLabelColor: Color = FilledAutocompleteTokens.FieldDisabledLabelTextColor.value, errorLabelColor: Color = FilledAutocompleteTokens.FieldErrorLabelTextColor.value, - focusedPlaceholderColor: Color = - FilledAutocompleteTokens.FieldSupportingTextColor.value, - unfocusedPlaceholderColor: Color = - FilledAutocompleteTokens.FieldSupportingTextColor.value, + focusedPlaceholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, + unfocusedPlaceholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, disabledPlaceholderColor: Color = - FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity), + FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorPlaceholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, focusedPrefixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, unfocusedPrefixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, - disabledPrefixColor: Color = FilledAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledPrefixColor: Color = + FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorPrefixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, focusedSuffixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, unfocusedSuffixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, - disabledSuffixColor: Color = FilledAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledSuffixColor: Color = + FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorSuffixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, ): TextFieldColors = TextFieldDefaults.colors( @@ -380,10 +496,10 @@ object ExposedDropdownMenuDefaults { * * @param focusedTextColor the color used for the input text of this text field when focused * @param unfocusedTextColor the color used for the input text of this text field when not - * focused + * focused * @param disabledTextColor the color used for the input text of this text field when disabled * @param errorTextColor the color used for the input text of this text field when in error - * state + * state * @param focusedContainerColor the container color for this text field when focused * @param unfocusedContainerColor the container color for this text field when not focused * @param disabledContainerColor the container color for this text field when disabled @@ -400,7 +516,8 @@ object ExposedDropdownMenuDefaults { * @param disabledLeadingIconColor the leading icon color for this text field when disabled * @param errorLeadingIconColor the leading icon color for this text field when in error state * @param focusedTrailingIconColor the trailing icon color for this text field when focused - * @param unfocusedTrailingIconColor the trailing icon color for this text field when not focused + * @param unfocusedTrailingIconColor the trailing icon color for this text field when not + * focused * @param disabledTrailingIconColor the trailing icon color for this text field when disabled * @param errorTrailingIconColor the trailing icon color for this text field when in error state * @param focusedLabelColor the label color for this text field when focused @@ -424,30 +541,33 @@ object ExposedDropdownMenuDefaults { fun outlinedTextFieldColors( focusedTextColor: Color = OutlinedAutocompleteTokens.FieldFocusInputTextColor.value, unfocusedTextColor: Color = OutlinedAutocompleteTokens.FieldInputTextColor.value, - disabledTextColor: Color = OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity), + disabledTextColor: Color = + OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity + ), errorTextColor: Color = OutlinedAutocompleteTokens.FieldErrorInputTextColor.value, focusedContainerColor: Color = Color.Transparent, unfocusedContainerColor: Color = Color.Transparent, disabledContainerColor: Color = Color.Transparent, errorContainerColor: Color = Color.Transparent, cursorColor: Color = OutlinedAutocompleteTokens.TextFieldCaretColor.value, - errorCursorColor: Color = - OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.value, + errorCursorColor: Color = OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.value, selectionColors: TextSelectionColors = LocalTextSelectionColors.current, focusedBorderColor: Color = OutlinedAutocompleteTokens.TextFieldFocusOutlineColor.value, unfocusedBorderColor: Color = OutlinedAutocompleteTokens.TextFieldOutlineColor.value, disabledBorderColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledOutlineColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledOutlineOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledOutlineColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledOutlineOpacity + ), errorBorderColor: Color = OutlinedAutocompleteTokens.TextFieldErrorOutlineColor.value, focusedLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldFocusLeadingIconColor.value, unfocusedLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldLeadingIconColor.value, disabledLeadingIconColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconOpacity + ), errorLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldErrorLeadingIconColor.value, focusedTrailingIconColor: Color = @@ -455,33 +575,39 @@ object ExposedDropdownMenuDefaults { unfocusedTrailingIconColor: Color = OutlinedAutocompleteTokens.TextFieldTrailingIconColor.value, disabledTrailingIconColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconOpacity + ), errorTrailingIconColor: Color = OutlinedAutocompleteTokens.TextFieldErrorTrailingIconColor.value, focusedLabelColor: Color = OutlinedAutocompleteTokens.FieldFocusLabelTextColor.value, unfocusedLabelColor: Color = OutlinedAutocompleteTokens.FieldLabelTextColor.value, - disabledLabelColor: Color = OutlinedAutocompleteTokens.FieldDisabledLabelTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledLabelTextOpacity), + disabledLabelColor: Color = + OutlinedAutocompleteTokens.FieldDisabledLabelTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledLabelTextOpacity + ), errorLabelColor: Color = OutlinedAutocompleteTokens.FieldErrorLabelTextColor.value, - focusedPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + focusedPlaceholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, unfocusedPlaceholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, disabledPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), - errorPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), + errorPlaceholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, focusedPrefixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, unfocusedPrefixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledPrefixColor: Color = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledPrefixColor: Color = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorPrefixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, focusedSuffixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, unfocusedSuffixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledSuffixColor: Color = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledSuffixColor: Color = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorSuffixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, ): TextFieldColors = OutlinedTextFieldDefaults.colors( @@ -530,10 +656,8 @@ object ExposedDropdownMenuDefaults { * Padding for [DropdownMenuItem]s within [ExposedDropdownMenuBoxScope.ExposedDropdownMenu] to * align them properly with [TextField] components. */ - val ItemContentPadding: PaddingValues = PaddingValues( - horizontal = ExposedDropdownMenuItemHorizontalPadding, - vertical = 0.dp - ) + val ItemContentPadding: PaddingValues = + PaddingValues(horizontal = ExposedDropdownMenuItemHorizontalPadding, vertical = 0.dp) @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN) @ExperimentalMaterial3Api @@ -545,8 +669,10 @@ object ExposedDropdownMenuDefaults { fun textFieldColors( focusedTextColor: Color = FilledAutocompleteTokens.FieldFocusInputTextColor.value, unfocusedTextColor: Color = FilledAutocompleteTokens.FieldInputTextColor.value, - disabledTextColor: Color = FilledAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity), + disabledTextColor: Color = + FilledAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity + ), errorTextColor: Color = FilledAutocompleteTokens.FieldErrorInputTextColor.value, containerColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.value, errorContainerColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.value, @@ -558,17 +684,18 @@ object ExposedDropdownMenuDefaults { unfocusedIndicatorColor: Color = FilledAutocompleteTokens.TextFieldActiveIndicatorColor.value, disabledIndicatorColor: Color = - FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorOpacity), + FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorOpacity + ), errorIndicatorColor: Color = FilledAutocompleteTokens.TextFieldErrorActiveIndicatorColor.value, focusedLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldFocusLeadingIconColor.value, - unfocusedLeadingIconColor: Color = - FilledAutocompleteTokens.TextFieldLeadingIconColor.value, + unfocusedLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldLeadingIconColor.value, disabledLeadingIconColor: Color = - FilledAutocompleteTokens.TextFieldDisabledLeadingIconColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledLeadingIconOpacity), + FilledAutocompleteTokens.TextFieldDisabledLeadingIconColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledLeadingIconOpacity + ), errorLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldErrorLeadingIconColor.value, focusedTrailingIconColor: Color = @@ -576,31 +703,35 @@ object ExposedDropdownMenuDefaults { unfocusedTrailingIconColor: Color = FilledAutocompleteTokens.TextFieldTrailingIconColor.value, disabledTrailingIconColor: Color = - FilledAutocompleteTokens.TextFieldDisabledTrailingIconColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledTrailingIconOpacity), + FilledAutocompleteTokens.TextFieldDisabledTrailingIconColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledTrailingIconOpacity + ), errorTrailingIconColor: Color = FilledAutocompleteTokens.TextFieldErrorTrailingIconColor.value, focusedLabelColor: Color = FilledAutocompleteTokens.FieldFocusLabelTextColor.value, unfocusedLabelColor: Color = FilledAutocompleteTokens.FieldLabelTextColor.value, disabledLabelColor: Color = FilledAutocompleteTokens.FieldDisabledLabelTextColor.value, errorLabelColor: Color = FilledAutocompleteTokens.FieldErrorLabelTextColor.value, - focusedPlaceholderColor: Color = - FilledAutocompleteTokens.FieldSupportingTextColor.value, - unfocusedPlaceholderColor: Color = - FilledAutocompleteTokens.FieldSupportingTextColor.value, + focusedPlaceholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, + unfocusedPlaceholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, disabledPlaceholderColor: Color = - FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity), + FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorPlaceholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, focusedPrefixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, unfocusedPrefixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, - disabledPrefixColor: Color = FilledAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledPrefixColor: Color = + FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorPrefixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, focusedSuffixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, unfocusedSuffixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, - disabledSuffixColor: Color = FilledAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledSuffixColor: Color = + FilledAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorSuffixColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, ): TextFieldColors = textFieldColors( @@ -650,28 +781,31 @@ object ExposedDropdownMenuDefaults { fun outlinedTextFieldColors( focusedTextColor: Color = OutlinedAutocompleteTokens.FieldFocusInputTextColor.value, unfocusedTextColor: Color = OutlinedAutocompleteTokens.FieldInputTextColor.value, - disabledTextColor: Color = OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity), + disabledTextColor: Color = + OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity + ), errorTextColor: Color = OutlinedAutocompleteTokens.FieldErrorInputTextColor.value, containerColor: Color = Color.Transparent, errorContainerColor: Color = Color.Transparent, cursorColor: Color = OutlinedAutocompleteTokens.TextFieldCaretColor.value, - errorCursorColor: Color = - OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.value, + errorCursorColor: Color = OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.value, selectionColors: TextSelectionColors = LocalTextSelectionColors.current, focusedBorderColor: Color = OutlinedAutocompleteTokens.TextFieldFocusOutlineColor.value, unfocusedBorderColor: Color = OutlinedAutocompleteTokens.TextFieldOutlineColor.value, disabledBorderColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledOutlineColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledOutlineOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledOutlineColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledOutlineOpacity + ), errorBorderColor: Color = OutlinedAutocompleteTokens.TextFieldErrorOutlineColor.value, focusedLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldFocusLeadingIconColor.value, unfocusedLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldLeadingIconColor.value, disabledLeadingIconColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconOpacity + ), errorLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldErrorLeadingIconColor.value, focusedTrailingIconColor: Color = @@ -679,33 +813,39 @@ object ExposedDropdownMenuDefaults { unfocusedTrailingIconColor: Color = OutlinedAutocompleteTokens.TextFieldTrailingIconColor.value, disabledTrailingIconColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconOpacity + ), errorTrailingIconColor: Color = OutlinedAutocompleteTokens.TextFieldErrorTrailingIconColor.value, focusedLabelColor: Color = OutlinedAutocompleteTokens.FieldFocusLabelTextColor.value, unfocusedLabelColor: Color = OutlinedAutocompleteTokens.FieldLabelTextColor.value, - disabledLabelColor: Color = OutlinedAutocompleteTokens.FieldDisabledLabelTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledLabelTextOpacity), + disabledLabelColor: Color = + OutlinedAutocompleteTokens.FieldDisabledLabelTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledLabelTextOpacity + ), errorLabelColor: Color = OutlinedAutocompleteTokens.FieldErrorLabelTextColor.value, - focusedPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + focusedPlaceholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, unfocusedPlaceholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, disabledPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), - errorPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), + errorPlaceholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, focusedPrefixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, unfocusedPrefixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledPrefixColor: Color = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledPrefixColor: Color = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorPrefixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, focusedSuffixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, unfocusedSuffixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledSuffixColor: Color = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor - .value.copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), + disabledSuffixColor: Color = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), errorSuffixColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, ): TextFieldColors = outlinedTextFieldColors( @@ -754,8 +894,10 @@ object ExposedDropdownMenuDefaults { @Composable fun textFieldColors( textColor: Color = FilledAutocompleteTokens.FieldInputTextColor.value, - disabledTextColor: Color = FilledAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity), + disabledTextColor: Color = + FilledAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity + ), containerColor: Color = FilledAutocompleteTokens.TextFieldContainerColor.value, cursorColor: Color = FilledAutocompleteTokens.TextFieldCaretColor.value, errorCursorColor: Color = FilledAutocompleteTokens.TextFieldErrorFocusCaretColor.value, @@ -765,17 +907,18 @@ object ExposedDropdownMenuDefaults { unfocusedIndicatorColor: Color = FilledAutocompleteTokens.TextFieldActiveIndicatorColor.value, disabledIndicatorColor: Color = - FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorOpacity), + FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledActiveIndicatorOpacity + ), errorIndicatorColor: Color = FilledAutocompleteTokens.TextFieldErrorActiveIndicatorColor.value, focusedLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldFocusLeadingIconColor.value, - unfocusedLeadingIconColor: Color = - FilledAutocompleteTokens.TextFieldLeadingIconColor.value, + unfocusedLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldLeadingIconColor.value, disabledLeadingIconColor: Color = - FilledAutocompleteTokens.TextFieldDisabledLeadingIconColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledLeadingIconOpacity), + FilledAutocompleteTokens.TextFieldDisabledLeadingIconColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledLeadingIconOpacity + ), errorLeadingIconColor: Color = FilledAutocompleteTokens.TextFieldErrorLeadingIconColor.value, focusedTrailingIconColor: Color = @@ -783,8 +926,9 @@ object ExposedDropdownMenuDefaults { unfocusedTrailingIconColor: Color = FilledAutocompleteTokens.TextFieldTrailingIconColor.value, disabledTrailingIconColor: Color = - FilledAutocompleteTokens.TextFieldDisabledTrailingIconColor.value - .copy(alpha = FilledAutocompleteTokens.TextFieldDisabledTrailingIconOpacity), + FilledAutocompleteTokens.TextFieldDisabledTrailingIconColor.value.copy( + alpha = FilledAutocompleteTokens.TextFieldDisabledTrailingIconOpacity + ), errorTrailingIconColor: Color = FilledAutocompleteTokens.TextFieldErrorTrailingIconColor.value, focusedLabelColor: Color = FilledAutocompleteTokens.FieldFocusLabelTextColor.value, @@ -793,76 +937,85 @@ object ExposedDropdownMenuDefaults { errorLabelColor: Color = FilledAutocompleteTokens.FieldErrorLabelTextColor.value, placeholderColor: Color = FilledAutocompleteTokens.FieldSupportingTextColor.value, disabledPlaceholderColor: Color = - FilledAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity) - ): TextFieldColors = textFieldColors( - focusedTextColor = textColor, - unfocusedTextColor = textColor, - disabledTextColor = disabledTextColor, - errorTextColor = textColor, - focusedContainerColor = containerColor, - unfocusedContainerColor = containerColor, - disabledContainerColor = containerColor, - errorContainerColor = containerColor, - cursorColor = cursorColor, - errorCursorColor = errorCursorColor, - selectionColors = selectionColors, - focusedIndicatorColor = focusedIndicatorColor, - unfocusedIndicatorColor = unfocusedIndicatorColor, - disabledIndicatorColor = disabledIndicatorColor, - errorIndicatorColor = errorIndicatorColor, - focusedLeadingIconColor = focusedLeadingIconColor, - unfocusedLeadingIconColor = unfocusedLeadingIconColor, - disabledLeadingIconColor = disabledLeadingIconColor, - errorLeadingIconColor = errorLeadingIconColor, - focusedTrailingIconColor = focusedTrailingIconColor, - unfocusedTrailingIconColor = unfocusedTrailingIconColor, - disabledTrailingIconColor = disabledTrailingIconColor, - errorTrailingIconColor = errorTrailingIconColor, - focusedLabelColor = focusedLabelColor, - unfocusedLabelColor = unfocusedLabelColor, - disabledLabelColor = disabledLabelColor, - errorLabelColor = errorLabelColor, - focusedPlaceholderColor = placeholderColor, - unfocusedPlaceholderColor = placeholderColor, - disabledPlaceholderColor = disabledPlaceholderColor, - errorPlaceholderColor = placeholderColor, - focusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - unfocusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledPrefixColor = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), - errorPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - focusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - unfocusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledSuffixColor = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), - errorSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - ) + FilledAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = FilledAutocompleteTokens.FieldDisabledInputTextOpacity + ) + ): TextFieldColors = + textFieldColors( + focusedTextColor = textColor, + unfocusedTextColor = textColor, + disabledTextColor = disabledTextColor, + errorTextColor = textColor, + focusedContainerColor = containerColor, + unfocusedContainerColor = containerColor, + disabledContainerColor = containerColor, + errorContainerColor = containerColor, + cursorColor = cursorColor, + errorCursorColor = errorCursorColor, + selectionColors = selectionColors, + focusedIndicatorColor = focusedIndicatorColor, + unfocusedIndicatorColor = unfocusedIndicatorColor, + disabledIndicatorColor = disabledIndicatorColor, + errorIndicatorColor = errorIndicatorColor, + focusedLeadingIconColor = focusedLeadingIconColor, + unfocusedLeadingIconColor = unfocusedLeadingIconColor, + disabledLeadingIconColor = disabledLeadingIconColor, + errorLeadingIconColor = errorLeadingIconColor, + focusedTrailingIconColor = focusedTrailingIconColor, + unfocusedTrailingIconColor = unfocusedTrailingIconColor, + disabledTrailingIconColor = disabledTrailingIconColor, + errorTrailingIconColor = errorTrailingIconColor, + focusedLabelColor = focusedLabelColor, + unfocusedLabelColor = unfocusedLabelColor, + disabledLabelColor = disabledLabelColor, + errorLabelColor = errorLabelColor, + focusedPlaceholderColor = placeholderColor, + unfocusedPlaceholderColor = placeholderColor, + disabledPlaceholderColor = disabledPlaceholderColor, + errorPlaceholderColor = placeholderColor, + focusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + unfocusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + disabledPrefixColor = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), + errorPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + focusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + unfocusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + disabledSuffixColor = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), + errorSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + ) @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN) @Composable fun outlinedTextFieldColors( textColor: Color = OutlinedAutocompleteTokens.FieldInputTextColor.value, - disabledTextColor: Color = OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity), + disabledTextColor: Color = + OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity + ), containerColor: Color = Color.Transparent, cursorColor: Color = OutlinedAutocompleteTokens.TextFieldCaretColor.value, - errorCursorColor: Color = - OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.value, + errorCursorColor: Color = OutlinedAutocompleteTokens.TextFieldErrorFocusCaretColor.value, selectionColors: TextSelectionColors = LocalTextSelectionColors.current, focusedBorderColor: Color = OutlinedAutocompleteTokens.TextFieldFocusOutlineColor.value, unfocusedBorderColor: Color = OutlinedAutocompleteTokens.TextFieldOutlineColor.value, disabledBorderColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledOutlineColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledOutlineOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledOutlineColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledOutlineOpacity + ), errorBorderColor: Color = OutlinedAutocompleteTokens.TextFieldErrorOutlineColor.value, focusedLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldFocusLeadingIconColor.value, unfocusedLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldLeadingIconColor.value, disabledLeadingIconColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledLeadingIconOpacity + ), errorLeadingIconColor: Color = OutlinedAutocompleteTokens.TextFieldErrorLeadingIconColor.value, focusedTrailingIconColor: Color = @@ -870,87 +1023,213 @@ object ExposedDropdownMenuDefaults { unfocusedTrailingIconColor: Color = OutlinedAutocompleteTokens.TextFieldTrailingIconColor.value, disabledTrailingIconColor: Color = - OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconColor.value - .copy(alpha = OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconOpacity), + OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconColor.value.copy( + alpha = OutlinedAutocompleteTokens.TextFieldDisabledTrailingIconOpacity + ), errorTrailingIconColor: Color = OutlinedAutocompleteTokens.TextFieldErrorTrailingIconColor.value, focusedLabelColor: Color = OutlinedAutocompleteTokens.FieldFocusLabelTextColor.value, unfocusedLabelColor: Color = OutlinedAutocompleteTokens.FieldLabelTextColor.value, - disabledLabelColor: Color = OutlinedAutocompleteTokens.FieldDisabledLabelTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledLabelTextOpacity), + disabledLabelColor: Color = + OutlinedAutocompleteTokens.FieldDisabledLabelTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledLabelTextOpacity + ), errorLabelColor: Color = OutlinedAutocompleteTokens.FieldErrorLabelTextColor.value, placeholderColor: Color = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, disabledPlaceholderColor: Color = - OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity) - ): TextFieldColors = outlinedTextFieldColors( - focusedTextColor = textColor, - unfocusedTextColor = textColor, - disabledTextColor = disabledTextColor, - errorTextColor = textColor, - focusedContainerColor = containerColor, - unfocusedContainerColor = containerColor, - disabledContainerColor = containerColor, - errorContainerColor = containerColor, - cursorColor = cursorColor, - errorCursorColor = errorCursorColor, - selectionColors = selectionColors, - focusedBorderColor = focusedBorderColor, - unfocusedBorderColor = unfocusedBorderColor, - disabledBorderColor = disabledBorderColor, - errorBorderColor = errorBorderColor, - focusedLeadingIconColor = focusedLeadingIconColor, - unfocusedLeadingIconColor = unfocusedLeadingIconColor, - disabledLeadingIconColor = disabledLeadingIconColor, - errorLeadingIconColor = errorLeadingIconColor, - focusedTrailingIconColor = focusedTrailingIconColor, - unfocusedTrailingIconColor = unfocusedTrailingIconColor, - disabledTrailingIconColor = disabledTrailingIconColor, - errorTrailingIconColor = errorTrailingIconColor, - focusedLabelColor = focusedLabelColor, - unfocusedLabelColor = unfocusedLabelColor, - disabledLabelColor = disabledLabelColor, - errorLabelColor = errorLabelColor, - focusedPlaceholderColor = placeholderColor, - unfocusedPlaceholderColor = placeholderColor, - disabledPlaceholderColor = disabledPlaceholderColor, - errorPlaceholderColor = placeholderColor, - focusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - unfocusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledPrefixColor = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), - errorPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - focusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - unfocusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - disabledSuffixColor = OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value - .copy(alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity), - errorSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, - ) + OutlinedAutocompleteTokens.FieldDisabledInputTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledInputTextOpacity + ) + ): TextFieldColors = + outlinedTextFieldColors( + focusedTextColor = textColor, + unfocusedTextColor = textColor, + disabledTextColor = disabledTextColor, + errorTextColor = textColor, + focusedContainerColor = containerColor, + unfocusedContainerColor = containerColor, + disabledContainerColor = containerColor, + errorContainerColor = containerColor, + cursorColor = cursorColor, + errorCursorColor = errorCursorColor, + selectionColors = selectionColors, + focusedBorderColor = focusedBorderColor, + unfocusedBorderColor = unfocusedBorderColor, + disabledBorderColor = disabledBorderColor, + errorBorderColor = errorBorderColor, + focusedLeadingIconColor = focusedLeadingIconColor, + unfocusedLeadingIconColor = unfocusedLeadingIconColor, + disabledLeadingIconColor = disabledLeadingIconColor, + errorLeadingIconColor = errorLeadingIconColor, + focusedTrailingIconColor = focusedTrailingIconColor, + unfocusedTrailingIconColor = unfocusedTrailingIconColor, + disabledTrailingIconColor = disabledTrailingIconColor, + errorTrailingIconColor = errorTrailingIconColor, + focusedLabelColor = focusedLabelColor, + unfocusedLabelColor = unfocusedLabelColor, + disabledLabelColor = disabledLabelColor, + errorLabelColor = errorLabelColor, + focusedPlaceholderColor = placeholderColor, + unfocusedPlaceholderColor = placeholderColor, + disabledPlaceholderColor = disabledPlaceholderColor, + errorPlaceholderColor = placeholderColor, + focusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + unfocusedPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + disabledPrefixColor = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), + errorPrefixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + focusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + unfocusedSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + disabledSuffixColor = + OutlinedAutocompleteTokens.FieldDisabledSupportingTextColor.value.copy( + alpha = OutlinedAutocompleteTokens.FieldDisabledSupportingTextOpacity + ), + errorSuffixColor = OutlinedAutocompleteTokens.FieldSupportingTextColor.value, + ) +} + +@Stable +internal class ExposedDropdownMenuPositionProvider( + val density: Density, + val topWindowInsets: Int, + val keyboardSignalState: State? = null, + val verticalMargin: Int = with(density) { MenuVerticalMargin.roundToPx() }, + val onPositionCalculated: (anchorBounds: IntRect, menuBounds: IntRect) -> Unit = { _, _ -> } +) : PopupPositionProvider { + // Horizontal position + private val startToAnchorStart = MenuPosition.startToAnchorStart() + private val endToAnchorEnd = MenuPosition.endToAnchorEnd() + private val leftToWindowLeft = MenuPosition.leftToWindowLeft() + private val rightToWindowRight = MenuPosition.rightToWindowRight() + + // Vertical position + private val topToAnchorBottom = MenuPosition.topToAnchorBottom() + private val bottomToAnchorTop = MenuPosition.bottomToAnchorTop() + private val topToWindowTop = MenuPosition.topToWindowTop(margin = verticalMargin) + private val bottomToWindowBottom = MenuPosition.bottomToWindowBottom(margin = verticalMargin) + + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize + ): IntOffset { + // Workaround for b/326394521 + // Read the state because we want any changes to the state to trigger recalculation. + // See PopupLayout.snapshotStateObserver and PopupLayout.onCommitAffectingPopupPosition + // for more info. + keyboardSignalState?.value + + // Workaround for b/256233441 + // Popup fails to account for window insets so we do it here instead + @Suppress("NAME_SHADOWING") + val windowSize = IntSize(windowSize.width, windowSize.height + topWindowInsets) + + val xCandidates = + listOf( + startToAnchorStart, + endToAnchorEnd, + if (anchorBounds.center.x < windowSize.width / 2) { + leftToWindowLeft + } else { + rightToWindowRight + } + ) + var x = 0 + for (index in xCandidates.indices) { + val xCandidate = + xCandidates[index].position( + anchorBounds = anchorBounds, + windowSize = windowSize, + menuWidth = popupContentSize.width, + layoutDirection = layoutDirection + ) + if ( + index == xCandidates.lastIndex || + (xCandidate >= 0 && xCandidate + popupContentSize.width <= windowSize.width) + ) { + x = xCandidate + break + } + } + + val yCandidates = + listOf( + topToAnchorBottom, + bottomToAnchorTop, + if (anchorBounds.center.y < windowSize.height / 2) { + topToWindowTop + } else { + bottomToWindowBottom + } + ) + var y = 0 + for (index in yCandidates.indices) { + val yCandidate = + yCandidates[index].position( + anchorBounds = anchorBounds, + windowSize = windowSize, + menuHeight = popupContentSize.height + ) + if ( + index == yCandidates.lastIndex || + (yCandidate >= 0 && yCandidate + popupContentSize.height <= windowSize.height) + ) { + y = yCandidate + break + } + } + + val menuOffset = IntOffset(x, y) + onPositionCalculated( + /* anchorBounds = */ anchorBounds, + /* menuBounds = */ IntRect(offset = menuOffset, size = popupContentSize) + ) + return menuOffset + } } -@Suppress("ComposableModifierFactory") internal fun Modifier.expandable( expanded: Boolean, onExpandedChange: () -> Unit, + anchorType: MenuAnchorType, expandedDescription: String, collapsedDescription: String, -) = pointerInput(onExpandedChange) { - awaitEachGesture { - // Must be PointerEventPass.Initial to observe events before the text field consumes them - // in the Main pass - awaitFirstDown(pass = PointerEventPass.Initial) - val upEvent = waitForUpOrCancellation(pass = PointerEventPass.Initial) - if (upEvent != null) { - onExpandedChange() + toggleDescription: String, + keyboardController: SoftwareKeyboardController?, +) = + pointerInput(onExpandedChange) { + awaitEachGesture { + // Modifier.clickable doesn't work for text fields, so we use Modifier.pointerInput + // in the Initial pass to observe events before the text field consumes them + // in the Main pass. + val downEvent = awaitFirstDown(pass = PointerEventPass.Initial) + if (anchorType == MenuAnchorType.SecondaryEditable) { + downEvent.consume() + } + val upEvent = waitForUpOrCancellation(pass = PointerEventPass.Initial) + if (upEvent != null) { + onExpandedChange() + } } } -}.semantics { - stateDescription = if (expanded) expandedDescription else collapsedDescription - role = Role.DropdownList - onClick { - onExpandedChange() - true - } -} + .semantics { + if (anchorType == MenuAnchorType.SecondaryEditable) { + role = Role.Button + stateDescription = if (expanded) expandedDescription else collapsedDescription + contentDescription = toggleDescription + } else { + role = Role.DropdownList + } + onClick { + onExpandedChange() + if (anchorType == MenuAnchorType.PrimaryEditable) { + keyboardController?.show() + } + true + } + } private val ExposedDropdownMenuItemHorizontalPadding = 16.dp diff --git a/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.skiko.kt b/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.skiko.kt index fcd9604181fb3..ce40c2833b31e 100644 --- a/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.skiko.kt +++ b/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.skiko.kt @@ -16,62 +16,55 @@ package androidx.compose.material3 +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.statusBars import androidx.compose.material3.internal.Strings import androidx.compose.material3.internal.getString +import androidx.compose.material3.internal.rememberAccessibilityServiceState import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.input.InputModeManager +import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalInputModeManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.toIntRect +import androidx.compose.ui.unit.toSize +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties import kotlin.math.max +import kotlin.math.roundToInt - -/** - * Material Design Exposed Dropdown Menu. - * - * Menus display a list of choices on a temporary surface. They appear when users interact with a - * button, action, or other control. - * - * Exposed dropdown menus display the currently selected item in a text field to which the menu is - * anchored. In some cases, it can accept and display user input (whether or not it’s listed as a - * menu choice). If the text field input is used to filter results in the menu, the component is - * also known as "autocomplete" or a "combobox". - * - * ![Exposed dropdown menu image](https://developer.android.com/images/reference/androidx/compose/material3/exposed-dropdown-menu.png) - * - * The [ExposedDropdownMenuBox] is expected to contain a [TextField] (or [OutlinedTextField]) and - * [ExposedDropdownMenuBoxScope.ExposedDropdownMenu] as content. - * - * An example of read-only Exposed Dropdown Menu: - * @sample androidx.compose.material3.samples.ExposedDropdownMenuSample - * - * An example of editable Exposed Dropdown Menu: - * @sample androidx.compose.material3.samples.EditableExposedDropdownMenuSample - * - * @param expanded whether the menu is expanded or not - * @param onExpandedChange called when the exposed dropdown menu is clicked and the expansion state - * changes. - * @param modifier the [Modifier] to be applied to this exposed dropdown menu - * @param content the content of this exposed dropdown menu, typically a [TextField] and an - * [ExposedDropdownMenuBoxScope.ExposedDropdownMenu]. The [TextField] within [content] should be - * passed the [ExposedDropdownMenuBoxScope.menuAnchor] modifier for proper menu behavior. - */ @OptIn(ExperimentalComposeUiApi::class) @ExperimentalMaterial3Api @Composable @@ -83,56 +76,78 @@ actual fun ExposedDropdownMenuBox( ) { val density = LocalDensity.current val windowInfo = LocalWindowInfo.current + + val verticalMargin = with(density) { MenuVerticalMargin.roundToPx() } + + var anchorCoordinates by remember { mutableStateOf(null) } var anchorWidth by remember { mutableIntStateOf(0) } var menuMaxHeight by remember { mutableIntStateOf(0) } - val verticalMarginInPx = with(density) { MenuVerticalMargin.roundToPx() } val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current val expandedDescription = getString(Strings.MenuExpanded) val collapsedDescription = getString(Strings.MenuCollapsed) + val toggleDescription = getString(Strings.ToggleDropdownMenu) + val anchorTypeState = remember { mutableStateOf(MenuAnchorType.PrimaryNotEditable) } - val scope = remember(expanded, onExpandedChange, windowInfo, density) { - object : ExposedDropdownMenuBoxScope() { - override fun Modifier.menuAnchor(): Modifier = this - .onGloballyPositioned { - anchorWidth = it.size.width - val boundsInWindow = it.boundsInWindow() - val visibleWindowBounds = windowInfo.containerSize.toIntRect() - val heightAbove = boundsInWindow.top - visibleWindowBounds.top - val heightBelow = visibleWindowBounds.height - boundsInWindow.bottom - menuMaxHeight = max(heightAbove, heightBelow).toInt() - verticalMarginInPx - } - .expandable( - expanded = expanded, - onExpandedChange = { onExpandedChange(!expanded) }, - expandedDescription = expandedDescription, - collapsedDescription = collapsedDescription, - ) - .focusRequester(focusRequester) - - override fun Modifier.exposedDropdownSize(matchTextFieldWidth: Boolean): Modifier = - layout { measurable, constraints -> - val menuWidth = constraints.constrainWidth(anchorWidth) - val menuConstraints = constraints.copy( - maxHeight = constraints.constrainHeight(menuMaxHeight), - minWidth = if (matchTextFieldWidth) menuWidth else constraints.minWidth, - maxWidth = if (matchTextFieldWidth) menuWidth else constraints.maxWidth, - ) - val placeable = measurable.measure(menuConstraints) - layout(placeable.width, placeable.height) { - placeable.place(0, 0) + val scope = + remember(expanded, onExpandedChange, windowInfo, density) { + object : ExposedDropdownMenuBoxScopeImpl() { + override fun Modifier.menuAnchor(type: MenuAnchorType, enabled: Boolean): Modifier = + this.focusRequester(focusRequester) + .then( + if (!enabled) Modifier + else + Modifier.expandable( + expanded = expanded, + onExpandedChange = { + anchorTypeState.value = type + onExpandedChange(!expanded) + }, + anchorType = type, + expandedDescription = expandedDescription, + collapsedDescription = collapsedDescription, + toggleDescription = toggleDescription, + keyboardController = keyboardController, + ) + ) + + override val anchorType: MenuAnchorType + get() = anchorTypeState.value + + override fun Modifier.exposedDropdownSize(matchTextFieldWidth: Boolean): Modifier = + layout { measurable, constraints -> + val menuWidth = constraints.constrainWidth(anchorWidth) + val menuConstraints = + constraints.copy( + maxHeight = constraints.constrainHeight(menuMaxHeight), + minWidth = + if (matchTextFieldWidth) menuWidth else constraints.minWidth, + maxWidth = + if (matchTextFieldWidth) menuWidth else constraints.maxWidth, + ) + val placeable = measurable.measure(menuConstraints) + layout(placeable.width, placeable.height) { placeable.place(0, 0) } } - } + } } - } - Box(modifier) { + Box( + modifier.onGloballyPositioned { + anchorCoordinates = it + anchorWidth = it.size.width + menuMaxHeight = + calculateMaxHeight( + windowBounds = windowInfo.containerSize.toIntRect(), + anchorBounds = anchorCoordinates.getAnchorBounds(), + verticalMargin = verticalMargin, + ) + } + ) { scope.content() } - SideEffect { - if (expanded) focusRequester.requestFocus() - } + SideEffect { if (expanded) focusRequester.requestFocus() } } @Composable @@ -141,13 +156,108 @@ internal actual fun ExposedDropdownMenuBoxScope.ExposedDropdownMenuDefaultImpl( onDismissRequest: () -> Unit, modifier: Modifier, scrollState: ScrollState, - content: @Composable ColumnScope.() -> Unit + matchTextFieldWidth: Boolean, + shape: Shape, + containerColor: Color, + tonalElevation: Dp, + shadowElevation: Dp, + border: BorderStroke?, + content: @Composable ColumnScope.() -> Unit, ) { - DropdownMenu( - expanded = expanded, - onDismissRequest = onDismissRequest, - modifier = modifier.exposedDropdownSize(), - scrollState = scrollState, - content = content + // Workaround for b/326394521. We create a state that's read in `calculatePosition`. + // Then trigger a state change in `SoftKeyboardListener` to force recalculation. + val keyboardSignalState = remember { mutableStateOf(Unit, neverEqualPolicy()) } + val density = LocalDensity.current + val topWindowInsets = WindowInsets.statusBars.getTop(density) + + // TODO(b/326064777): use DropdownMenu when it supports custom PositionProvider + val expandedState = remember { MutableTransitionState(false) } + expandedState.targetState = expanded + + if (expandedState.currentState || expandedState.targetState) { + val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) } + val popupPositionProvider = + remember(density, topWindowInsets) { + ExposedDropdownMenuPositionProvider( + density = density, + topWindowInsets = topWindowInsets, + keyboardSignalState = keyboardSignalState, + ) { anchorBounds, menuBounds -> + transformOriginState.value = + calculateTransformOrigin(anchorBounds, menuBounds) + } + } + + var focusManager: FocusManager? by mutableStateOf(null) + var inputModeManager: InputModeManager? by mutableStateOf(null) + Popup( + onDismissRequest = onDismissRequest, + popupPositionProvider = popupPositionProvider, + properties = popupProperties(anchorType), + onKeyEvent = { + handleDropdownOnKeyEvent(it, focusManager, inputModeManager) + }, + ) { + focusManager = LocalFocusManager.current + inputModeManager = LocalInputModeManager.current + + DropdownMenuContent( + expandedState = expandedState, + transformOriginState = transformOriginState, + scrollState = scrollState, + shape = shape, + containerColor = containerColor, + tonalElevation = tonalElevation, + shadowElevation = shadowElevation, + border = border, + modifier = modifier.exposedDropdownSize(matchTextFieldWidth), + content = content, + ) + } + } +} + +/** + * Creates a [PopupProperties] used for [ExposedDropdownMenuBoxScope.ExposedDropdownMenu]. + * + * @param anchorType the type of element that is anchoring the menu. See [MenuAnchorType]. + */ +@Composable +private fun popupProperties(anchorType: MenuAnchorType): PopupProperties { + val a11yServicesEnabled by rememberAccessibilityServiceState() + + // If typing on the IME is required, the menu should not be focusable + // in order to prevent stealing focus from the input method. + val imeRequired = + anchorType == MenuAnchorType.PrimaryEditable || + (anchorType == MenuAnchorType.SecondaryEditable && !a11yServicesEnabled) + return PopupProperties( + focusable = !imeRequired ) } + +private fun calculateMaxHeight( + windowBounds: IntRect, + anchorBounds: Rect?, + verticalMargin: Int, +): Int { + anchorBounds ?: return 0 + + val marginedWindowTop = windowBounds.top + verticalMargin + val marginedWindowBottom = windowBounds.bottom - verticalMargin + val availableHeight = + if (anchorBounds.top > windowBounds.bottom || anchorBounds.bottom < windowBounds.top) { + (marginedWindowBottom - marginedWindowTop) + } else { + val heightAbove = anchorBounds.top - marginedWindowTop + val heightBelow = marginedWindowBottom - anchorBounds.bottom + max(heightAbove, heightBelow).roundToInt() + } + + return max(availableHeight, 0) +} + +private fun LayoutCoordinates?.getAnchorBounds(): Rect { + // Don't use `boundsInWindow()` because it can report 0 when the window is animating/resizing + return if (this == null) Rect.Zero else Rect(positionInWindow(), size.toSize()) +} diff --git a/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/SkikoMenu.skiko.kt b/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/SkikoMenu.skiko.kt index ae5aabf0c6925..45763c850f707 100644 --- a/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/SkikoMenu.skiko.kt +++ b/compose/material3/material3/src/skikoMain/kotlin/androidx/compose/material3/SkikoMenu.skiko.kt @@ -184,7 +184,7 @@ actual fun DropdownMenu( popupPositionProvider = popupPositionProvider, properties = properties, onKeyEvent = { - handlePopupOnKeyEvent(it, focusManager, inputModeManager) + handleDropdownOnKeyEvent(it, focusManager, inputModeManager) }, ) { focusManager = LocalFocusManager.current @@ -288,7 +288,7 @@ actual fun DropdownMenuItem( } @OptIn(ExperimentalComposeUiApi::class) -private fun handlePopupOnKeyEvent( +internal fun handleDropdownOnKeyEvent( keyEvent: KeyEvent, focusManager: FocusManager?, inputModeManager: InputModeManager? diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/DropdownMenu3Example.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/DropdownMenu3Example.kt index 60a926b42033b..d1655f8c579b0 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/DropdownMenu3Example.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/components/material3/DropdownMenu3Example.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -107,7 +108,7 @@ internal fun TextFieldWithExposedDropdown() { onExpandedChange = { expanded = !expanded }, ) { TextField( - modifier = Modifier.menuAnchor(), + modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable), readOnly = true, value = selectedOptionText, onValueChange = {}, From 89f1362e221d8f467f93deb955fdf5861a4dc1ae Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Thu, 27 Jun 2024 19:59:23 +0200 Subject: [PATCH 2/2] Update API dump --- .../material3/api/desktop/material3.api | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/compose/material3/material3/api/desktop/material3.api b/compose/material3/material3/api/desktop/material3.api index ea0ec7428dab7..d86b29781a4ec 100644 --- a/compose/material3/material3/api/desktop/material3.api +++ b/compose/material3/material3/api/desktop/material3.api @@ -731,11 +731,14 @@ public abstract interface annotation class androidx/compose/material3/Experiment public abstract class androidx/compose/material3/ExposedDropdownMenuBoxScope { public static final field $stable I - public fun ()V + public final synthetic fun ExposedDropdownMenu (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/ScrollState;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V public final fun ExposedDropdownMenu-kbRbctU (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/ScrollState;ZZLandroidx/compose/ui/graphics/Shape;JFFLandroidx/compose/foundation/BorderStroke;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V + public final fun ExposedDropdownMenu-vNxi1II (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/ScrollState;ZLandroidx/compose/ui/graphics/Shape;JFFLandroidx/compose/foundation/BorderStroke;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V public abstract fun exposedDropdownSize (Landroidx/compose/ui/Modifier;Z)Landroidx/compose/ui/Modifier; public static synthetic fun exposedDropdownSize$default (Landroidx/compose/material3/ExposedDropdownMenuBoxScope;Landroidx/compose/ui/Modifier;ZILjava/lang/Object;)Landroidx/compose/ui/Modifier; - public abstract fun menuAnchor (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; + public final fun menuAnchor (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier; + public abstract fun menuAnchor-fsE2BvY (Landroidx/compose/ui/Modifier;Ljava/lang/String;Z)Landroidx/compose/ui/Modifier; + public static synthetic fun menuAnchor-fsE2BvY$default (Landroidx/compose/material3/ExposedDropdownMenuBoxScope;Landroidx/compose/ui/Modifier;Ljava/lang/String;ZILjava/lang/Object;)Landroidx/compose/ui/Modifier; } public final class androidx/compose/material3/ExposedDropdownMenuDefaults { @@ -954,6 +957,25 @@ public final class androidx/compose/material3/MaterialThemeKt { public static final fun MaterialTheme (Landroidx/compose/material3/ColorScheme;Landroidx/compose/material3/Shapes;Landroidx/compose/material3/Typography;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } +public final class androidx/compose/material3/MenuAnchorType { + public static final field Companion Landroidx/compose/material3/MenuAnchorType$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Landroidx/compose/material3/MenuAnchorType; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final class androidx/compose/material3/MenuAnchorType$Companion { + public final fun getPrimaryEditable-Mg6Rgbw ()Ljava/lang/String; + public final fun getPrimaryNotEditable-Mg6Rgbw ()Ljava/lang/String; + public final fun getSecondaryEditable-Mg6Rgbw ()Ljava/lang/String; +} + public final class androidx/compose/material3/MenuDefaults { public static final field $stable I public static final field INSTANCE Landroidx/compose/material3/MenuDefaults;