diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/bug/BugReproducers.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/bug/BugReproducers.kt index 0c299a329e519..9b2db5331924f 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/bug/BugReproducers.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/bug/BugReproducers.kt @@ -27,5 +27,5 @@ val BugReproducers = Screen.Selection( IOSDynamicKeyboardType, NoPressInteractionInOutlinedTextField, WebBaselineAlways0, - ResizePopupCrashOnJS + ResizePopupCrashOnJS, ) diff --git a/compose/ui/ui/src/jsWasmMain/kotlin/androidx/compose/ui/input/key/Key.jsWasm.kt b/compose/ui/ui/src/jsWasmMain/kotlin/androidx/compose/ui/input/key/Key.jsWasm.kt index b848c3c8dd55d..dca94f55c5e78 100644 --- a/compose/ui/ui/src/jsWasmMain/kotlin/androidx/compose/ui/input/key/Key.jsWasm.kt +++ b/compose/ui/ui/src/jsWasmMain/kotlin/androidx/compose/ui/input/key/Key.jsWasm.kt @@ -158,7 +158,7 @@ actual value class Key(val keyCode: Long) { actual val S = Key(83) /** 'T' key. */ - actual val T = Key(54) + actual val T = Key(84) /** 'U' key. */ actual val U = Key(85) diff --git a/compose/ui/ui/src/webTest/kotlin/CanvasBasedWindowTests.kt b/compose/ui/ui/src/webTest/kotlin/CanvasBasedWindowTests.kt index 0ca4e1a559dba..8a95e96672f74 100644 --- a/compose/ui/ui/src/webTest/kotlin/CanvasBasedWindowTests.kt +++ b/compose/ui/ui/src/webTest/kotlin/CanvasBasedWindowTests.kt @@ -14,18 +14,30 @@ * limitations under the License. */ +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.unit.dp import kotlin.test.Test import kotlinx.browser.document import org.w3c.dom.HTMLCanvasElement import androidx.compose.ui.window.* import kotlin.test.AfterTest -import kotlin.test.Ignore import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.assertFalse @@ -77,19 +89,109 @@ class CanvasBasedWindowTests { }) // dispatchEvent synchronously invokes all the listeners - canvasElement.dispatchEvent(createCopyKeyboardEvent()) + canvasElement.dispatchEvent(createTypedEvent()) assertEquals(1, stack.size) assertTrue(stack.last()) - canvasElement.dispatchEvent(createTypedEvent()) + canvasElement.dispatchEvent(createEventShouldNotBePrevented()) assertEquals(2, stack.size) - assertTrue( stack.last()) - assertEquals("c", changedValue) + assertFalse(stack.last()) - canvasElement.dispatchEvent(createEventShouldNotBePrevented()) + // copy shortcut should not be prevented (we let browser create a corresponding event) + canvasElement.dispatchEvent(createCopyKeyboardEvent()) assertEquals(3, stack.size) assertFalse(stack.last()) } + + @Test + // https://github.com/JetBrains/compose-multiplatform/issues/3644 + fun keyMappingIsValid() { + if (isHeadlessBrowser()) return + + val canvasElement = document.createElement("canvas") as HTMLCanvasElement + canvasElement.setAttribute("id", canvasId) + document.body!!.appendChild(canvasElement) + + val fr = FocusRequester() + var mapping = "" + var k: Key? = null + CanvasBasedWindow(canvasElementId = canvasId) { + Box(Modifier.size(1000.dp).background(Color.Red).focusRequester(fr).focusTarget().onKeyEvent { + k = it.key + mapping = it.key.toString() + println(mapping) + false + }) { + Text("Try to press different keys and look at the console...") + } + SideEffect { + fr.requestFocus() + } + } + + val listOfKeys = listOf( + Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, + Key.H, Key.I, Key.J, Key.K, Key.L, Key.M, Key.N, + Key.O, Key.P, Key.Q, Key.R, Key.S, Key.T, Key.U, + Key.V, Key.W, Key.X, Key.Y, Key.Z + ) + + ('a'..'z').forEachIndexed { index, c -> + canvasElement.dispatchEvent(createTypedEvent(c)) + assertEquals(listOfKeys[index], k) + } + + val listOfNumbers = listOf( + Key.Zero, Key.One, Key.Two, Key.Three, Key.Four, + Key.Five, Key.Six, Key.Seven, Key.Eight, Key.Nine + ) + + ('0'..'9').forEachIndexed { index, c -> + canvasElement.dispatchEvent(createTypedEvent(c)) + assertEquals(listOfNumbers[index], k) + } + } + + @Test + // https://github.com/JetBrains/compose-multiplatform/issues/2296 + fun onPreviewKeyEventShouldWork() { + if (isHeadlessBrowser()) return + val canvasElement = document.createElement("canvas") as HTMLCanvasElement + canvasElement.setAttribute("id", canvasId) + document.body!!.appendChild(canvasElement) + + val fr = FocusRequester() + val textValue = mutableStateOf("") + var lastKeyEvent: KeyEvent? = null + var stopPropagation = true + + CanvasBasedWindow(canvasElementId = canvasId) { + TextField( + value = textValue.value, + onValueChange = { textValue.value = it }, + modifier = Modifier.fillMaxSize().focusRequester(fr).onPreviewKeyEvent { + lastKeyEvent = it + return@onPreviewKeyEvent stopPropagation + } + ) + SideEffect { + fr.requestFocus() + } + } + + canvasElement.dispatchEvent(createTypedEvent('t')) + assertEquals(Key.T, lastKeyEvent!!.key) + assertEquals("", textValue.value) + + stopPropagation = false + canvasElement.dispatchEvent(createTypedEvent('t')) + canvasElement.dispatchEvent(createTypedEvent('e')) + canvasElement.dispatchEvent(createTypedEvent('s')) + canvasElement.dispatchEvent(createTypedEvent('t')) + canvasElement.dispatchEvent(createTypedEvent('x')) + assertEquals(Key.X, lastKeyEvent!!.key) + assertEquals("testx", textValue.value) + } } internal external interface KeyboardEventInitExtended : KeyboardEventInit { @@ -106,8 +208,8 @@ internal fun createCopyKeyboardEvent(): KeyboardEvent = .withKeyCode() .keyDownEvent() -internal fun createTypedEvent(): KeyboardEvent = - KeyboardEventInit(key = "c", code = "KeyC", cancelable = true) +internal fun createTypedEvent(c: Char = 'c'): KeyboardEvent = + KeyboardEventInit(key = "$c", code = "Key${c.uppercase()}", cancelable = true) .withKeyCode() .keyDownEvent()