a11y: let Markdown/Rich text input render the placeholder to help for accessibility.

This commit is contained in:
Benoit Marty
2025-06-19 09:54:50 +02:00
parent 47ec84f6b3
commit 3ee5ee3f45
4 changed files with 20 additions and 22 deletions

View File

@@ -31,6 +31,7 @@ object ElementRichTextEditorStyle {
} else {
ElementTheme.colors.textSecondary
},
placeholderColor = ElementTheme.colors.textSecondary,
lineHeight = TextUnit.Unspecified,
includeFontPadding = true,
)

View File

@@ -39,7 +39,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -176,12 +175,12 @@ fun TextComposer(
TextInputBox(
composerMode = composerMode,
onResetComposerMode = onResetComposerMode,
placeholder = placeholder,
showPlaceholder = state.richTextEditorState.messageHtml.isEmpty(),
isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(),
subcomposing = subcomposing,
) {
RichTextEditor(
state = state.richTextEditorState,
placeholder = placeholder,
// Disable most of the editor functionality if it's just being measured for a subcomposition.
// This prevents it gaining focus and mutating the state.
registerStateUpdates = !subcomposing,
@@ -205,12 +204,13 @@ fun TextComposer(
TextInputBox(
composerMode = composerMode,
onResetComposerMode = onResetComposerMode,
placeholder = placeholder,
showPlaceholder = state.state.text.value().isEmpty(),
isTextEmpty = state.state.text.value().isEmpty(),
subcomposing = subcomposing,
) {
MarkdownTextInput(
state = state.state,
placeholder = placeholder,
placeholderColor = ElementTheme.colors.textSecondary,
subcomposing = subcomposing,
onTyping = onTyping,
onReceiveSuggestion = onReceiveSuggestion,
@@ -492,8 +492,7 @@ private fun TextFormattingLayout(
private fun TextInputBox(
composerMode: MessageComposerMode,
onResetComposerMode: () -> Unit,
placeholder: String,
showPlaceholder: Boolean,
isTextEmpty: Boolean,
subcomposing: Boolean,
textInput: @Composable () -> Unit,
) {
@@ -515,7 +514,6 @@ private fun TextInputBox(
onResetComposerMode = onResetComposerMode,
)
}
val defaultTypography = ElementTheme.typography.fontBodyLgRegular
Box(
modifier = Modifier
.padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp)
@@ -523,21 +521,8 @@ private fun TextInputBox(
.then(if (!subcomposing) Modifier.testTag(TestTags.textEditor) else Modifier),
contentAlignment = Alignment.CenterStart,
) {
// Placeholder
if (showPlaceholder) {
Text(
text = placeholder,
style = defaultTypography.copy(
color = ElementTheme.colors.textSecondary,
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
textInput()
if (showPlaceholder && composerMode.showCaptionCompatibilityWarning()) {
if (isTextEmpty && composerMode.showCaptionCompatibilityWarning()) {
var showBottomSheet by remember { mutableStateOf(false) }
Icon(
modifier = Modifier

View File

@@ -8,6 +8,7 @@
package io.element.android.libraries.textcomposer.components.markdown
import android.content.ClipData
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.text.Editable
@@ -18,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.getSpans
@@ -26,6 +28,7 @@ import androidx.core.view.OnReceiveContentListener
import androidx.core.view.ViewCompat
import androidx.core.view.setPadding
import androidx.core.widget.addTextChangedListener
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.testtags.TestTags
@@ -43,6 +46,8 @@ import io.element.android.wysiwyg.compose.internal.applyStyleInCompose
@Composable
fun MarkdownTextInput(
state: MarkdownTextEditorState,
placeholder: String,
placeholderColor: androidx.compose.ui.graphics.Color,
subcomposing: Boolean,
onTyping: (Boolean) -> Unit,
onReceiveSuggestion: (Suggestion?) -> Unit,
@@ -87,6 +92,8 @@ fun MarkdownTextInput(
setBackgroundColor(Color.TRANSPARENT)
val text = state.text.value()
setText(text)
setHint(placeholder)
setHintTextColor(ColorStateList.valueOf(placeholderColor.toArgb()))
inputType = InputType.TYPE_CLASS_TEXT or
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or
InputType.TYPE_TEXT_FLAG_MULTI_LINE or
@@ -189,6 +196,8 @@ internal fun MarkdownTextInputPreview() {
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = true)
MarkdownTextInput(
state = aMarkdownTextEditorState(initialText = "Hello, World!"),
placeholder = "Placeholder",
placeholderColor = ElementTheme.colors.textSecondary,
subcomposing = false,
onTyping = {},
onReceiveSuggestion = {},

View File

@@ -14,6 +14,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.core.text.getSpans
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
@@ -175,6 +176,8 @@ class MarkdownTextInputTest {
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus)
MarkdownTextInput(
state = state,
placeholder = "Placeholder",
placeholderColor = ElementTheme.colors.textSecondary,
subcomposing = subcomposing,
onTyping = onTyping,
onReceiveSuggestion = onSuggestionReceived,