compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

OutlinedTextField does not respect Modifier.click()

Open Metric-Void opened this issue 5 years ago • 4 comments

Windows 10, using 0.3.0-build136

var fieldText by remember { mutableStateOf("lorem") }
OutlinedTextField(
    value = fieldText,
    modifier = Modifier.clickable(
        onClick = { fieldText = "single-click" },
        onDoubleClick = {
            fieldText = "double-click"
        }
    ),
    label = { Text("Example") },
    onValueChange = { fieldText = it },
    singleLine = true
)

The outlined text field does not respond to mouse click events, either single or double click. Changing to onValueChange = {} does not resolve the problem.

Metric-Void avatar Dec 26 '20 09:12 Metric-Void

It has probably been overridden underneath to provide focus related features.

Dominaezzz avatar Dec 26 '20 16:12 Dominaezzz

If you want to handle clicks on TextField you can override interactionSource. For example,

 val interactionSource = remember {
                        object : MutableInteractionSource {
                            override val interactions = MutableSharedFlow<Interaction>(
                                extraBufferCapacity = 16,
                                onBufferOverflow = BufferOverflow.DROP_OLDEST,
                            )

                            override suspend fun emit(interaction: Interaction) {
                                if (interaction is PressInteraction.Release) {
                                    // Clicked!
                                }

                                interactions.emit(interaction)
                            }

                            override fun tryEmit(interaction: Interaction): Boolean {
                                return interactions.tryEmit(interaction)
                            }
                        }
                    }

Also, if you want to prevent typing you can set readOnly param of TextField to true.

And then

OutlinedTextField(
                        value = selected,
                        readOnly = true,
                        onValueChange = {},
                        interactionSource = interactionSource
                    )

BulatMukhutdinov avatar May 15 '21 13:05 BulatMukhutdinov

I tried the interaction source workaround and is not working for 1.1.0 version

kmenesesp avatar Mar 03 '22 16:03 kmenesesp

In previous versions it was necessary to set enabled=false in order to make it work (see StackOverflow, due to missing propagation, probably on purpose).

With the newer versions clickable does not have onDoubleClick anymore and combinedClickable should be used instead. combinedClickable should define onLongClick too for touch conventions / behavior in case touch displays are used, but that would still come in conflict with the modal for copying / selecting / pasting text that pops up when long-clicking on text.

Therefore, it is still necessary to disable the field with enabled=false in order to make the click events work on the entire area.

With enabled=false the following sample should work:

OutlinedTextField(
  value = fieldText,
  enabled = false,  // <-- Set enabled false to apply click events on entire OutlinedTextField
  modifier = Modifier.combinedClickable( // <-- I had to use combinedClickable in order to define onClick and onDoubleClick since clickable doesn't have onDoubleClick because it is coming from Android / touch
    onClick = { fieldText = "single-click" },
    onDoubleClick = {
      fieldText = "double-click"
    }
    onDoubleClick = {
      fieldText = "long-click"
    }
  ),
  label = { Text("Example") },
  onValueChange = { fieldText = it },
  singleLine = true
)

The example with the interaction source works on Android but not on Desktop (probably because desktop does not trigger press interactions?):

var fieldText by remember { mutableStateOf("lorem") }
    val interactionSource = remember {
        object : MutableInteractionSource {
            override val interactions = MutableSharedFlow<Interaction>(
                extraBufferCapacity = 16,
                onBufferOverflow = BufferOverflow.DROP_OLDEST,
            )

            override suspend fun emit(interaction: Interaction) {
                when (interaction) {
                    is PressInteraction.Press -> {
                        fieldText = "lorem ips" // <-- sets text when pressed
                    }
                    is PressInteraction.Release -> {
                        fieldText = "lorem ipsum" // <-- sets text when released, but long clicks still open the modal
                    }
                }

                interactions.emit(interaction)
            }

            override fun tryEmit(interaction: Interaction): Boolean {
                return interactions.tryEmit(interaction)
            }
        }
    }

    OutlinedTextField(
        value = fieldText,
        label = { Text("Example") },
        onValueChange = { fieldText = it },
        singleLine = true,
        interactionSource = interactionSource,
    )

You can see that the click events are registered properly with combinedClickable if you click outside of the text input area, e.g. on the label or border where the mouse cursor on desktop is a pointer. The click events are probably not propagated to the underneath layers if set with the modifier and the field is not disabled, as said, probably on purpose.

@VeselovAlex are there any plans on doing anything here? Because this ticket aged well.

malliaridis avatar Apr 04 '23 15:04 malliaridis

this solution works for my custom composable :

@Composable
fun FormTextField(
    placeholder: String,
    value: String,
    onChange: (String) -> Unit,
    readOnly: Boolean,
    supportingText: String = "Empty Field",
    supportingTextCondition: () -> Boolean = { false },
    minLines: Int = 1,
    onClick: () -> Unit = {},
    keyboardType: KeyboardType = KeyboardType.Text
) {
    val containerColor = colorResource(id = R.color.light_grey)

    val interactionSource = remember {
        object : MutableInteractionSource {
            override val interactions = MutableSharedFlow<Interaction>(
                extraBufferCapacity = 16,
                onBufferOverflow = BufferOverflow.DROP_OLDEST,
            )

            override suspend fun emit(interaction: Interaction) {
                when (interaction) {
                    is PressInteraction.Press -> {
                        onClick()
                    }
                }

                interactions.emit(interaction)
            }

            override fun tryEmit(interaction: Interaction): Boolean {
                return interactions.tryEmit(interaction)
            }
        }
    }

    TextField(
        value = value,
        onValueChange = { onChange(it) },
        readOnly = readOnly,
        placeholder = { Text(text = placeholder) },
        shape = MaterialTheme.shapes.medium,
        colors = TextFieldDefaults.colors(
            focusedContainerColor = containerColor,
            unfocusedContainerColor = containerColor,
            disabledContainerColor = containerColor,
            focusedIndicatorColor = Color.Transparent,
            unfocusedIndicatorColor = Color.Transparent,
        ),
        modifier = Modifier
            .fillMaxWidth(0.85f),
        supportingText = {
            if (supportingTextCondition()) Text(
                text = supportingText,
                color = MaterialTheme.colorScheme.error
            )
        },
        label = { Text(text = placeholder) },
        minLines = minLines,
        keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
        interactionSource = interactionSource
    )

}

graj-naxtre avatar Jul 26 '24 09:07 graj-naxtre