mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
[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
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6ba0fa2f89
commit
049d01cd7a
@@ -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,
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<String>,
|
||||
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<String>,
|
||||
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<String>,
|
||||
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<String>,
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user