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
16.33k stars 1.18k forks source link

textfield can't input any when request focus #2812

Closed wznshuai closed 1 year ago

wznshuai commented 1 year ago

Select one of the platforms below: Desktop

Versions Kotlin version: 1.8.0 Compose Multiplatform version: 1.3.0 JDK (for desktop issues): 16

I need textfield auto request focus when lose focus. i add code like this:

@Composable
fun App() {
    var text by remember { mutableStateOf("Hello, World!") }
    val focusRequester = FocusRequester()

    MaterialTheme {
        Row {
            Button(modifier = Modifier.focusable(false), onClick = {
                text = "Hello, Desktop!"
            }) {
                Text(text)
            }
            TextField(
                value = text,
                onValueChange = { text = it },
                modifier = Modifier.focusRequester(focusRequester).onFocusChanged {
                    println("$it -- hasFocus: ${it.hasFocus} -- isFocused: ${it.isFocused}")
                    if (!it.isFocused) focusRequester.requestFocus()
                })
        }

    }
}

when i click the button, i can't input any in textfield

dima-avdeev-jb commented 1 year ago

Made repo with this reproducer: https://github.com/dima-avdeev-jb/desktop-bug-textfield-focus

On Android, this works fine.

m-sasha commented 1 year ago

There do seem to be some bugs visible with your example (caret remains in the textfield but it has not focus), and our API is not very intuitive, which we intend to improve. However, the right way to achieve what you want is:

To prevent the button from being focusable

modifier = Modifier.focusProperties {
    canFocus = false
},

To grant initial focus to the text field

LaunchedEffect(Unit){
    focusRequester.requestFocus()
}

To return focus to the textfield if it loses it (although I wouldn't recommend doing that)

val coroutineScope = rememberCoroutineScope()
...
modifier = Modifier
    .onFocusChanged {
        if (!it.hasFocus){
            coroutineScope.launch{
                focusRequester.requestFocus()
            }
        }
    }

Here's the complete example:

@Composable
fun App() {
    var text by remember { mutableStateOf("Hello, World!") }
    val focusRequester = FocusRequester()

    MaterialTheme {
        Row {
            Button(
                modifier = Modifier.focusProperties {
                    canFocus = false
                },
                onClick = {
                    text = "Hello, Desktop!"
                }
            ) {
                Text(text)
            }

            val coroutineScope = rememberCoroutineScope()
            TextField(
                value = text,
                onValueChange = { text = it },
                modifier = Modifier
                    .focusRequester(focusRequester)
                    .onFocusChanged {
                        if (!it.hasFocus){
                            coroutineScope.launch{
                                focusRequester.requestFocus()
                            }
                        }
                    }
            )
            LaunchedEffect(Unit){
                focusRequester.requestFocus()
            }
        }

    }
}
wznshuai commented 1 year ago

There do seem to be some bugs visible with your example (caret remains in the textfield but it has not focus), and our API is not very intuitive, which we intend to improve. However, the right way to achieve what you want is:

To prevent the button from being focusable

modifier = Modifier.focusProperties {
    canFocus = false
},

To grant initial focus to the text field

LaunchedEffect(Unit){
    focusRequester.requestFocus()
}

To return focus to the textfield if it loses it (although I wouldn't recommend doing that)

val coroutineScope = rememberCoroutineScope()
...
modifier = Modifier
    .onFocusChanged {
        if (!it.hasFocus){
            coroutineScope.launch{
                focusRequester.requestFocus()
            }
        }
    }

Here's the complete example:

@Composable
fun App() {
    var text by remember { mutableStateOf("Hello, World!") }
    val focusRequester = FocusRequester()

    MaterialTheme {
        Row {
            Button(
                modifier = Modifier.focusProperties {
                    canFocus = false
                },
                onClick = {
                    text = "Hello, Desktop!"
                }
            ) {
                Text(text)
            }

            val coroutineScope = rememberCoroutineScope()
            TextField(
                value = text,
                onValueChange = { text = it },
                modifier = Modifier
                    .focusRequester(focusRequester)
                    .onFocusChanged {
                        if (!it.hasFocus){
                            coroutineScope.launch{
                                focusRequester.requestFocus()
                            }
                        }
                    }
            )
            LaunchedEffect(Unit){
                focusRequester.requestFocus()
            }
        }

    }
}

as you said,API is not very intuitive. However, I think that if requestFocus succeeds, other widgets should lose focus

okushnikov commented 3 months ago

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