JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.95k stars 1.16k forks source link

I can't input text to TextField When open the Popup #2810

Closed sunny-zhang-98 closed 3 months ago

sunny-zhang-98 commented 1 year ago

Describe the bug I can't input text to TextField When open the Popup, the popup is focusable, If Popup is set to unfocused, it won't be able to close externally by clicking on it, and it won't be able to close with the Escape key

I want to implement a drop-down selector, which can input text through an input box and filter the content of the drop-down list through text, so it must meet several conditions.

  1. when the drop-down box can input content into the input box;
  2. When the drop-down box pops up, click the pop-up window to close the drop-down box and make the input box lose focus;
  3. press the Escape button to close the drop-down box, and make the data box out of focus;
  4. Click the input box when the popover is open to operate the text without closing the drop-down box

Affected platforms Select one of the platforms below:

Versions

Code

package ...

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.awtEventOrNull
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.*
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import com.sun.helper.common.Log
import com.sun.helper.component.TimePickerForm
import java.awt.event.KeyEvent
import java.time.LocalTime

internal val androidx.compose.ui.input.key.KeyEvent.isDirectionUp: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_UP

internal val androidx.compose.ui.input.key.KeyEvent.isDirectionDown: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_DOWN

internal val androidx.compose.ui.input.key.KeyEvent.isDirectionRight: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_RIGHT

internal val androidx.compose.ui.input.key.KeyEvent.isDirectionLeft: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_LEFT

internal val androidx.compose.ui.input.key.KeyEvent.isHome: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_HOME

internal val androidx.compose.ui.input.key.KeyEvent.isMoveEnd: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_END

internal val androidx.compose.ui.input.key.KeyEvent.isPgUp: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_PAGE_UP

internal val androidx.compose.ui.input.key.KeyEvent.isPgDn: Boolean
    get() = key.nativeKeyCode == KeyEvent.VK_PAGE_DOWN

@ExperimentalComposeUiApi
private fun preHandlePopupOnKeyEvent(
    keyEvent: androidx.compose.ui.input.key.KeyEvent
): Boolean {
    return if (keyEvent.type == KeyEventType.KeyDown && keyEvent.awtEventOrNull?.keyCode == KeyEvent.VK_ESCAPE)
        true
    else if (keyEvent.type == KeyEventType.KeyDown)
        when {
            keyEvent.isDirectionDown -> true

            keyEvent.isDirectionUp -> true

            else -> false
        }
    else false
}

@ExperimentalComposeUiApi
private fun handlePopupOnKeyEvent(
    keyEvent: androidx.compose.ui.input.key.KeyEvent,
    onDismissRequest: () -> Unit,
    focusManager: FocusManager,
    inputModeManager: InputModeManager
): Boolean {
    return if (keyEvent.type == KeyEventType.KeyDown && keyEvent.awtEventOrNull?.keyCode == KeyEvent.VK_ESCAPE) {
        onDismissRequest()
        true
    } else if (keyEvent.type == KeyEventType.KeyDown) {
        when {
            keyEvent.isDirectionDown -> {
                inputModeManager.requestInputMode(InputMode.Keyboard)
                focusManager.moveFocus(FocusDirection.Next)
                true
            }

            keyEvent.isDirectionUp -> {
                inputModeManager.requestInputMode(InputMode.Keyboard)
                focusManager.moveFocus(FocusDirection.Previous)
                true
            }

            else -> false
        }
    } else {
        false
    }
}

@ExperimentalComposeUiApi
@Composable
fun ComponentShow() {
    Column {
        var text by remember { mutableStateOf("") }
        var isExpand by remember { mutableStateOf(false) }
        val focusManagerCurrent = LocalFocusManager.current
        OutlinedTextField(
            text,
            onValueChange = { text = it },
            placeholder = { Text("What") },
            modifier = Modifier.onFocusChanged {
                isExpand = it.isFocused
                Log.log("ComponentShow", "it.isFocused ${it.isFocused}")
            }.onKeyEvent {
                Log.log("ComponentShow", "OutlinedTextField -- onKeyEvent", it.toString())
                false
            })
        if (isExpand) {
            var focusManager: FocusManager? by mutableStateOf(null)
            var inputModeManager: InputModeManager? by mutableStateOf(null)
            val onDismissRequest: () -> Unit = { isExpand = false; focusManagerCurrent.clearFocus() }
            Popup(popupPositionProvider = object : PopupPositionProvider {
                override fun calculatePosition(
                    anchorBounds: IntRect,
                    windowSize: IntSize,
                    layoutDirection: LayoutDirection,
                    popupContentSize: IntSize
                ): IntOffset = IntOffset(anchorBounds.left, anchorBounds.bottom + 10)
            }, onDismissRequest = onDismissRequest, focusable = true, onPreviewKeyEvent = {
                Log.log("ComponentShow", "onPreviewKeyEvent", it.toString())
                preHandlePopupOnKeyEvent(it)
            }, onKeyEvent = {
                val r = handlePopupOnKeyEvent(
                    it, onDismissRequest, focusManager!!, inputModeManager!!
                )
                Log.log("ComponentShow", "onKeyEvent", it.toString(), r)
                r
            }) {
                focusManager = LocalFocusManager.current
                inputModeManager = LocalInputModeManager.current
                Box(Modifier.size(250.dp).background(Color.White).shadow(4.dp, RoundedCornerShape(15))) {

                }
            }
        }
    }
}

To Reproduce Steps and/or the code snippet to reproduce the behavior:

  1. Click TextField, when TextField get focus, Popup is open.
  2. Press any keyboard key, You can see TextField not have any text.

Expected behavior I want to implement a drop-down selector, which can input text through an input box and filter the content of the drop-down list through text, so it must meet several conditions.

  1. when the drop-down box can input content into the input box;
  2. When the drop-down box pops up, click the pop-up window to close the drop-down box and make the input box lose focus;
  3. press the Escape button to close the drop-down box, and make the data box out of focus;
  4. Click the input box when the popover is open to operate the text without closing the drop-down box

Screenshots None

Additional context None

igordmn commented 1 year ago

Thanks!

See this comment. You can leave this issue open, as we will think what to do about this inconsistency.

kodeplateform commented 1 year ago

Thanks!

See this comment. You can leave this issue open, as we will think what to do about this inconsistency.

I'm also facing this issue, the workoaround works on ios and desktop the problem is i can't compile the android app, error is "Cannot find a parameter with this name: focusable " for :

Popup(alignment = Alignment.Center, focusable = true)

without focusable = true it compiles on aandroid too but then click on textfield doesnt show the keyboard

mathiastck commented 3 months ago

Focusable is now set in the PopupProperties.

My issue is I want the individual text fields to focusable in talkback mode, and to be readback by talkback.

MatkovIvan commented 3 months ago

It looks the issue about focus is outdated/fixed a while ago so I see no reason to keep this thread open.

@mathiastck it's separate a11y problem, could you please open a new issue with more detailed description?

okushnikov commented 3 weeks ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.