From 049d01cd7a876defebda6bddbab65c14b0eb4eb3 Mon Sep 17 00:00:00 2001 From: Ivan Morgillo Date: Fri, 21 Feb 2025 15:07:55 +0100 Subject: [PATCH] [jewel] JEWEL-774 Introduce new stateless *ListComboBox fix typo in ListComboBoxUiTest update ListComboBox tests and add new ones update UI API file introduce the new stateless EditableListComboBox introduce the new stateless ListComboBox closes https://github.com/JetBrains/intellij-community/pull/2955 (cherry picked from commit 2470c5e8f0ee56eb7157f0f3e28586adba305e22) IJ-MR-155570 GitOrigin-RevId: 88ef0a6984383e282977f75e4971710599e2c79b --- .../samples/showcase/components/Dropdowns.kt | 33 ++- .../jewel/ui/component/ListComboBoxUiTest.kt | 149 ++++++++++ platform/jewel/ui/api/ui.api | 3 + .../jewel/ui/component/ListComboBox.kt | 254 +++++++++++++++++- 4 files changed, 424 insertions(+), 15 deletions(-) diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Dropdowns.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Dropdowns.kt index 22b4b789c6f5..e79575e8ffb0 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Dropdowns.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Dropdowns.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -216,21 +217,20 @@ private fun ListComboBoxes() { ) } - var selectedComboBox1: String? by remember { mutableStateOf(comboBoxItems.first()) } - var selectedComboBox2: String? by remember { mutableStateOf(comboBoxItems.first()) } - var selectedComboBox3: String? by remember { mutableStateOf(comboBoxItems.first()) } - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Column(Modifier.weight(1f).padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Enabled and Editable") - Text(text = "Selected item: $selectedComboBox1", maxLines = 1, overflow = TextOverflow.Ellipsis) + var selectedIndex by remember { mutableIntStateOf(2) } + val selectedItemText = if (selectedIndex >= 0) comboBoxItems[selectedIndex] else "" + Text(text = "Selected item: $selectedItemText", maxLines = 1, overflow = TextOverflow.Ellipsis) EditableListComboBox( items = comboBoxItems, + selectedIndex = selectedIndex, + onItemSelected = { index, text -> selectedIndex = index }, modifier = Modifier.width(200.dp), maxPopupHeight = 150.dp, - onSelectedItemChange = { _, text -> selectedComboBox1 = text }, itemContent = { item, isSelected, isActive -> SimpleListItem( text = item, @@ -244,14 +244,16 @@ private fun ListComboBoxes() { Column(Modifier.weight(1f).padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Enabled") - - Text(text = "Selected item: $selectedComboBox2", maxLines = 1, overflow = TextOverflow.Ellipsis) + var selectedIndex by remember { mutableIntStateOf(2) } + val selectedItemText = if (selectedIndex >= 0) comboBoxItems[selectedIndex] else "" + Text(text = "Selected item: $selectedItemText", maxLines = 1, overflow = TextOverflow.Ellipsis) ListComboBox( items = comboBoxItems, + selectedIndex = selectedIndex, modifier = Modifier.width(200.dp), maxPopupHeight = 150.dp, - onSelectedItemChange = { _, text -> selectedComboBox2 = text }, + onItemSelected = { index, text -> selectedIndex = index }, itemContent = { item, isSelected, isActive -> SimpleListItem( text = item, @@ -265,14 +267,17 @@ private fun ListComboBoxes() { Column(Modifier.weight(1f).padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Disabled") - - Text(text = "Selected item: $selectedComboBox3", maxLines = 1, overflow = TextOverflow.Ellipsis) + var selectedIndex by remember { mutableIntStateOf(2) } + val selectedItemText = if (selectedIndex >= 0) comboBoxItems[selectedIndex] else "" + Text(text = "Selected item: $selectedItemText", maxLines = 1, overflow = TextOverflow.Ellipsis) ListComboBox( - items = comboBoxItems, - modifier = Modifier.width(200.dp), isEnabled = false, - onSelectedItemChange = { _, text -> selectedComboBox3 = text }, + items = comboBoxItems, + selectedIndex = selectedIndex, + modifier = Modifier.width(200.dp), + maxPopupHeight = 150.dp, + onItemSelected = { index, text -> selectedIndex = index }, itemContent = { item, isSelected, isActive -> SimpleListItem( text = item, diff --git a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/ListComboBoxUiTest.kt b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/ListComboBoxUiTest.kt index 80eb875953c5..25d31c8fd7e6 100644 --- a/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/ListComboBoxUiTest.kt +++ b/platform/jewel/ui-tests/src/test/kotlin/org/jetbrains/jewel/ui/component/ListComboBoxUiTest.kt @@ -5,6 +5,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -422,6 +426,149 @@ class ListComboBoxUiTest { comboBox.assertTextEquals("Item 2", includeEditableText = false) } + @Test + fun `stateless ListComboBox displays and selects initial selectedIndex item`() { + var selectedIdx = 0 + var selectedText = "" + + composeRule.setContent { + IntUiTheme { + ListComboBox( + items = comboBoxItems, + selectedIndex = 2, // Start with "Item 3" selected + onItemSelected = { index, text -> + selectedIdx = index + selectedText = text + }, + modifier = Modifier.testTag("ComboBox").width(200.dp), + itemContent = { item, isSelected, isActive -> + SimpleListItem( + text = item, + isSelected = isSelected, + isActive = isActive, + modifier = Modifier.testTag(item), + iconContentDescription = item, + ) + }, + ) + } + } + + composeRule.onNode(hasTestTag("ComboBox")).assertTextEquals("Item 3", includeEditableText = false) + composeRule.onNodeWithTag("Jewel.ComboBox.ChevronContainer", useUnmergedTree = true).performClick() + composeRule.onNodeWithTag("Item 1").performClick() + assert(selectedIdx == 0) { "Expected selectedIdx to be 0, but was $selectedIdx" } + assert(selectedText == "Item 1") { "Expected selectedText to be 'Item 1', but was $selectedText" } + } + + @Test + fun `when selectedIndex changes externally ListComboBox updates`() { + var selectedIndex by mutableStateOf(0) + + composeRule.setContent { + IntUiTheme { + ListComboBox( + items = comboBoxItems, + selectedIndex = selectedIndex, + onItemSelected = { index, _ -> selectedIndex = index }, + modifier = Modifier.testTag("ComboBox").width(200.dp), + itemContent = { item, isSelected, isActive -> + SimpleListItem( + text = item, + isSelected = isSelected, + isActive = isActive, + modifier = Modifier.testTag(item), + iconContentDescription = item, + ) + }, + ) + } + } + + composeRule.onNode(hasTestTag("ComboBox")).assertTextEquals("Item 1", includeEditableText = false) + selectedIndex = 3 + composeRule.waitForIdle() + composeRule.onNodeWithTag("Jewel.ComboBox.ChevronContainer", useUnmergedTree = true).performClick() + composeRule.onNodeWithTag("Book").assertIsSelected() + } + + @Test + fun `when editable ListComboBox text is edited then selectedIndex remains unchanged`() { + var selectedIdx = 1 + + composeRule.setContent { + val textState = rememberTextFieldState("Item 2") + IntUiTheme { + EditableListComboBox( + items = comboBoxItems, + selectedIndex = selectedIdx, + onItemSelected = { index, _ -> selectedIdx = index }, + textFieldState = textState, + modifier = Modifier.testTag("ComboBox").width(200.dp), + itemContent = { item, isSelected, isActive -> + SimpleListItem( + text = item, + isSelected = isSelected, + isActive = isActive, + modifier = Modifier.testTag(item), + iconContentDescription = item, + ) + }, + ) + } + } + + textField.assertTextEquals("Item 2") + + textField.performTextClearance() + textField.performTextInput("Custom text") + + assert(selectedIdx == 1) { "Expected selectedIdx to remain 1, but was $selectedIdx" } + + chevronContainer.performClick() + composeRule.onNodeWithTag("Item 2").assertIsSelected() + } + + @Test + fun `when editable ListComboBox selectedIndex changes then text field updates`() { + var selectedIndex by mutableStateOf(0) + + composeRule.setContent { + val textState = rememberTextFieldState(comboBoxItems[selectedIndex]) + + // Update text state when selectedIndex changes + LaunchedEffect(selectedIndex) { textState.edit { replace(0, length, comboBoxItems[selectedIndex]) } } + + IntUiTheme { + EditableListComboBox( + items = comboBoxItems, + selectedIndex = selectedIndex, + onItemSelected = { index, _ -> selectedIndex = index }, + textFieldState = textState, // Pass the explicitly managed text state + modifier = Modifier.testTag("ComboBox").width(200.dp), + itemContent = { item, isSelected, isActive -> + SimpleListItem( + text = item, + isSelected = isSelected, + isActive = isActive, + modifier = Modifier.testTag(item), + iconContentDescription = item, + ) + }, + ) + } + } + + textField.assertTextEquals("Item 1") + selectedIndex = 3 + composeRule.waitForIdle() + + textField.assertTextEquals("Book") + + chevronContainer.performClick() + composeRule.onNodeWithTag("Book").assertIsSelected() + } + private fun editableListComboBox(): SemanticsNodeInteraction { val focusRequester = FocusRequester() injectEditableListComboBox(focusRequester, isEnabled = true) @@ -459,6 +606,8 @@ class ListComboBoxUiTest { IntUiTheme { ListComboBox( items = comboBoxItems, + selectedIndex = 0, + onItemSelected = { _, _ -> }, modifier = Modifier.testTag("ComboBox").width(200.dp).focusRequester(focusRequester), isEnabled = isEnabled, itemContent = { item, isSelected, isActive -> diff --git a/platform/jewel/ui/api/ui.api b/platform/jewel/ui/api/ui.api index 3de7e8426183..483505ff42cd 100644 --- a/platform/jewel/ui/api/ui.api +++ b/platform/jewel/ui/api/ui.api @@ -554,7 +554,10 @@ public final class org/jetbrains/jewel/ui/component/LinkState$Companion { public final class org/jetbrains/jewel/ui/component/ListComboBoxKt { public static final fun EditableListComboBox-lYrZsNM (Ljava/util/List;Landroidx/compose/ui/Modifier;ZILorg/jetbrains/jewel/ui/Outline;FLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ComboBoxStyle;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun EditableListComboBox-xKBSf-U (Ljava/util/List;ILkotlin/jvm/functions/Function2;Landroidx/compose/foundation/text/input/TextFieldState;Landroidx/compose/ui/Modifier;ZLorg/jetbrains/jewel/ui/Outline;FLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ComboBoxStyle;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun ListComboBox-8u0NR3k (Ljava/util/List;ILkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;ZLorg/jetbrains/jewel/ui/Outline;FLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ComboBoxStyle;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V public static final fun ListComboBox-lYrZsNM (Ljava/util/List;Landroidx/compose/ui/Modifier;ZILorg/jetbrains/jewel/ui/Outline;FLandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/ComboBoxStyle;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun selectedItemIndex (Lorg/jetbrains/jewel/foundation/lazy/SelectableLazyListState;)I } public final class org/jetbrains/jewel/ui/component/ListItemState { diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ListComboBox.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ListComboBox.kt index c3081051c72e..ab7568e9a78b 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ListComboBox.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/ListComboBox.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -24,7 +25,9 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp +import kotlin.collections.indexOf import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn import org.jetbrains.jewel.foundation.lazy.SelectableLazyListState import org.jetbrains.jewel.foundation.lazy.SelectionMode @@ -39,6 +42,127 @@ import org.jetbrains.jewel.ui.Outline import org.jetbrains.jewel.ui.component.styling.ComboBoxStyle import org.jetbrains.jewel.ui.theme.comboBoxStyle +@Composable +public fun ListComboBox( + items: List, + selectedIndex: Int, + onItemSelected: (Int, String) -> Unit, + modifier: Modifier = Modifier, + isEnabled: Boolean = true, + outline: Outline = Outline.None, + maxPopupHeight: Dp = Dp.Unspecified, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + style: ComboBoxStyle = JewelTheme.comboBoxStyle, + textStyle: TextStyle = JewelTheme.defaultTextStyle, + onPopupVisibleChange: (visible: Boolean) -> Unit = {}, + itemContent: @Composable (text: String, isSelected: Boolean, isActive: Boolean) -> Unit, +) { + val listState = rememberSelectableLazyListState() + listState.selectedKeys = setOf(selectedIndex) + + var labelText by remember { mutableStateOf(items[selectedIndex]) } + var previewSelectedIndex by remember { mutableIntStateOf(selectedIndex) } + val scope = rememberCoroutineScope() + + fun setSelectedItem(index: Int) { + if (index >= 0 && index <= items.lastIndex) { + listState.selectedKeys = setOf(index) + labelText = items[index] + onItemSelected(index, items[index]) + scope.launch { listState.lazyListState.scrollToIndex(index) } + } else { + JewelLogger.getInstance("ListComboBox").trace("Ignoring item index $index as it's invalid") + } + } + + fun resetPreviewSelectedIndex() { + previewSelectedIndex = -1 + } + + val contentPadding = JewelTheme.comboBoxStyle.metrics.popupContentPadding + val popupMaxHeight = + if (maxPopupHeight == Dp.Unspecified) { + JewelTheme.comboBoxStyle.metrics.maxPopupHeight + } else { + maxPopupHeight + } + + val popupManager = remember { + PopupManager( + onPopupVisibleChange = { visible -> + resetPreviewSelectedIndex() + onPopupVisibleChange(visible) + }, + name = "ListComboBoxPopup", + ) + } + + ComboBox( + modifier = + modifier.onPreviewKeyEvent { + if (it.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false + + if (it.key == Key.Enter || it.key == Key.NumPadEnter) { + if (popupManager.isPopupVisible.value && previewSelectedIndex >= 0) { + setSelectedItem(previewSelectedIndex) + resetPreviewSelectedIndex() + } + popupManager.setPopupVisible(false) + true + } else { + false + } + }, + isEnabled = isEnabled, + labelText = labelText, + maxPopupHeight = popupMaxHeight, + onArrowDownPress = { + var currentSelectedIndex = listState.selectedItemIndex() + + // When there is a preview-selected item, pressing down will actually change the + // selected value to the one underneath it (unless it's the last one) + if (previewSelectedIndex >= 0 && previewSelectedIndex < items.lastIndex) { + currentSelectedIndex = previewSelectedIndex + resetPreviewSelectedIndex() + } + + setSelectedItem((currentSelectedIndex + 1).coerceAtMost(items.lastIndex)) + }, + onArrowUpPress = { + var currentSelectedIndex = listState.selectedItemIndex() + + // When there is a preview-selected item, pressing up will actually change the + // selected value to the one above it (unless it's the first one) + if (previewSelectedIndex > 0) { + currentSelectedIndex = previewSelectedIndex + resetPreviewSelectedIndex() + } + + setSelectedItem((currentSelectedIndex - 1).coerceAtLeast(0)) + }, + style = style, + textStyle = textStyle, + interactionSource = interactionSource, + outline = outline, + popupManager = popupManager, + ) { + PopupContent( + items = items, + previewSelectedItemIndex = previewSelectedIndex, + listState = listState, + popupMaxHeight = popupMaxHeight, + contentPadding = contentPadding, + onPreviewSelectedItemChange = { + if (it >= 0 && previewSelectedIndex != it) { + previewSelectedIndex = it + } + }, + onSelectedItemChange = ::setSelectedItem, + itemContent = itemContent, + ) + } +} + /** * A non-editable dropdown list component that follows the standard visual styling. * @@ -69,6 +193,129 @@ import org.jetbrains.jewel.ui.theme.comboBoxStyle * @see com.intellij.openapi.ui.ComboBox */ @Composable +public fun EditableListComboBox( + items: List, + selectedIndex: Int, + onItemSelected: (Int, String) -> Unit, + textFieldState: TextFieldState = rememberTextFieldState(items[selectedIndex]), + modifier: Modifier = Modifier, + isEnabled: Boolean = true, + outline: Outline = Outline.None, + maxPopupHeight: Dp = Dp.Unspecified, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + style: ComboBoxStyle = JewelTheme.comboBoxStyle, + textStyle: TextStyle = JewelTheme.defaultTextStyle, + onPopupVisibleChange: (visible: Boolean) -> Unit = {}, + itemContent: @Composable (text: String, isSelected: Boolean, isActive: Boolean) -> Unit, +) { + val listState = rememberSelectableLazyListState() + listState.selectedKeys = setOf(selectedIndex) + + var previewSelectedIndex by remember { mutableIntStateOf(selectedIndex) } + val scope = rememberCoroutineScope() + + fun setSelectedItem(index: Int) { + if (index >= 0 && index <= items.lastIndex) { + // Note: it's important to do the edit _before_ updating the list state, + // since updating the list state will cause another, asynchronous and + // potentially nested call to edit, which is not supported. + // This is because setting the selected keys on the SLC will eventually + // cause a call to this very function via SLC's onSelectedIndexesChange. + textFieldState.edit { replace(0, length, items[index]) } + + if (listState.selectedKeys.size != 1 || listState.selectedItemIndex() != index) { + // This guard condition should also help avoid issues caused by side effects + // of setting new selected keys, as per the comment above. + listState.selectedKeys = setOf(index) + } + onItemSelected(index, items[index]) + scope.launch { listState.lazyListState.scrollToIndex(index) } + } else { + JewelLogger.getInstance("EditableListComboBox").trace("Ignoring item index $index as it's invalid") + } + } + + val contentPadding = JewelTheme.comboBoxStyle.metrics.popupContentPadding + val popupMaxHeight = + if (maxPopupHeight == Dp.Unspecified) { + JewelTheme.comboBoxStyle.metrics.maxPopupHeight + } else { + maxPopupHeight + } + + EditableComboBox( + textFieldState = textFieldState, + modifier = modifier, + isEnabled = isEnabled, + outline = outline, + interactionSource = interactionSource, + style = style, + textStyle = textStyle, + onArrowDownPress = { + var currentSelectedIndex = listState.selectedItemIndex() + + // When there is a preview-selected item, pressing down will actually change the + // selected value to the one underneath it (unless it's the last one) + if (previewSelectedIndex >= 0 && previewSelectedIndex < items.lastIndex) { + currentSelectedIndex = previewSelectedIndex + previewSelectedIndex = -1 + } + + setSelectedItem((currentSelectedIndex + 1).coerceAtMost(items.lastIndex)) + }, + onArrowUpPress = { + var currentSelectedIndex = listState.selectedItemIndex() + + // When there is a preview-selected item, pressing up will actually change the + // selected value to the one above it (unless it's the first one) + if (previewSelectedIndex > 0) { + currentSelectedIndex = previewSelectedIndex + previewSelectedIndex = -1 + } + + setSelectedItem((currentSelectedIndex - 1).coerceAtLeast(0)) + }, + onEnterPress = { + val indexOfSelected = items.indexOf(textFieldState.text) + if (indexOfSelected != -1) { + setSelectedItem(indexOfSelected) + } + }, + popupManager = + remember { + PopupManager( + onPopupVisibleChange = { + previewSelectedIndex = -1 + onPopupVisibleChange(it) + }, + name = "EditableListComboBoxPopup", + ) + }, + popupContent = { + PopupContent( + items = items, + previewSelectedItemIndex = previewSelectedIndex, + listState = listState, + popupMaxHeight = popupMaxHeight, + contentPadding = contentPadding, + onPreviewSelectedItemChange = { + if (it >= 0 && previewSelectedIndex != it) { + previewSelectedIndex = it + } + }, + onSelectedItemChange = ::setSelectedItem, + itemContent = itemContent, + ) + }, + ) +} + +@Deprecated( + message = "Use the stateless ListComboBox with selectedIndex and onItemSelected parameters instead", + level = DeprecationLevel.WARNING, +) +@ScheduledForRemoval(inVersion = "Before 1.0") +@Composable public fun ListComboBox( items: List, modifier: Modifier = Modifier, @@ -220,6 +467,11 @@ public fun ListComboBox( * @param itemContent Composable content for rendering each item in the list * @see com.intellij.openapi.ui.ComboBox */ +@Deprecated( + "Use the stateless EditableListComboBox with selectedIndex and onItemSelected parameters instead", + level = DeprecationLevel.WARNING, +) +@ScheduledForRemoval(inVersion = "Before 1.0") @Composable public fun EditableListComboBox( items: List, @@ -372,7 +624,7 @@ private suspend fun LazyListState.scrollToIndex(itemIndex: Int) { } /** Returns the index of the selected item in the list, returning -1 if there is no selected item. */ -private fun SelectableLazyListState.selectedItemIndex(): Int = selectedKeys.firstOrNull() as Int? ?: -1 +public fun SelectableLazyListState.selectedItemIndex(): Int = selectedKeys.firstOrNull() as Int? ?: -1 @Composable private fun PopupContent(