From b62999d4c08672d5ea8d46f2da2e06d21c1aae81 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Oct 2026 09:31:15 +0200 Subject: [PATCH] Draw the focus indicator around the whole SelectedItem Previously, only the small remove button could get the keyboard focus, and the focus ring was barely visible. Now the whole item is focusable, the focus ring is drawn around it, and pressing an activation key (Enter, Space, D-pad center) removes the item. Touch behavior is unchanged: only the remove button is clickable. Also add a shared `activationKeys` set in the designsystem module. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../designsystem/utils/ActivationKeys.kt | 20 ++++++++++++ .../matrix/ui/components/SelectedItem.kt | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ActivationKeys.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ActivationKeys.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ActivationKeys.kt new file mode 100644 index 0000000000..60db43ecb3 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ActivationKeys.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.compose.ui.input.key.Key + +/** + * Keys which can be used to activate (i.e. click) the component having the keyboard focus. + */ +val activationKeys = setOf( + Key.Enter, + Key.NumPadEnter, + Key.DirectionCenter, + Key.Spacebar, +) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt index 29d2f469a0..36f2c674d8 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt @@ -9,6 +9,8 @@ package io.element.android.libraries.matrix.ui.components import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -25,11 +27,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics @@ -48,6 +55,7 @@ import io.element.android.libraries.designsystem.text.toPx import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.activationKeys import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -62,9 +70,31 @@ fun SelectedItem( modifier: Modifier = Modifier, ) { val actionRemove = stringResource(id = CommonStrings.action_remove) + val interactionSource = remember { MutableInteractionSource() } Box( modifier = modifier .width(avatarData.size.dp) + .then( + if (canRemove) { + // Let the whole item get the keyboard focus, so that the focus indicator is drawn around it. + Modifier + .onKeyEvent { event -> + if (event.type == KeyEventType.KeyUp && event.key in activationKeys) { + onRemoveClick() + true + } else { + false + } + } + .indication( + interactionSource = interactionSource, + indication = ripple(), + ) + .focusable(interactionSource = interactionSource) + } else { + Modifier + } + ) .clearAndSetSemantics { contentDescription = a11yContentDescription if (canRemove) { @@ -130,6 +160,8 @@ fun SelectedItem( .clip(CircleShape) .size(20.dp) .align(Alignment.TopEnd) + // The whole item is focusable, not the remove button + .focusProperties { canFocus = false } .clickable( indication = ripple(), interactionSource = remember { MutableInteractionSource() },