Skip to content

Commit

Permalink
[web] Process keydown and keyup keys for identified keys from virtual…
Browse files Browse the repository at this point in the history
… keyboard (#1380)

This fixes some issues with pressing some virtual keys that don't change
the input value - like navigation with arrows.
Also, this will make finally possible to switch to
KeybardEvent::key-backed toComposeEvent conversion
  • Loading branch information
Schahen committed Jun 11, 2024
1 parent 6c2eca1 commit e90c98a
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package androidx.compose.ui.platform

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.toComposeEvent
import androidx.compose.ui.text.input.CommitTextCommand
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.ImeAction
Expand All @@ -29,6 +27,7 @@ import androidx.compose.ui.text.input.SetComposingTextCommand
import androidx.compose.ui.text.input.TextFieldValue
import kotlinx.browser.document
import org.w3c.dom.HTMLTextAreaElement
import org.w3c.dom.events.Event
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.KeyboardEventInit

Expand All @@ -41,10 +40,19 @@ internal class BackingTextArea(
private val imeOptions: ImeOptions,
private val onEditCommand: (List<EditCommand>) -> Unit,
private val onImeActionPerformed: (ImeAction) -> Unit,
private val sendKey: (evt: KeyEvent) -> Unit
private val processKeyboardEvent: (KeyboardEvent) -> Unit
) {
private val textArea: HTMLTextAreaElement = createHtmlInput()

private fun processIdentifiedEvent(evt: Event) {
if (evt !is KeyboardEvent) return
// TODO: In theory nothing stops us from passing Unidentified keys but this yet to be investigated:
// First, this way we will pass (and attempt to process) "dummy" KeyboardEvents that were designed not to have physical representation at all
// Second, we need more tests on keyboard in general before doing this anyways
if (evt.key == "Unidentified") return
processKeyboardEvent(evt)
}

private fun createHtmlInput(): HTMLTextAreaElement {
val htmlInput = document.createElement("textarea") as HTMLTextAreaElement

Expand Down Expand Up @@ -100,6 +108,14 @@ internal class BackingTextArea(
setProperty("text-shadow", "none")
}

htmlInput.addEventListener("keydown", { evt ->
processIdentifiedEvent(evt)
})

htmlInput.addEventListener("keyup", { evt ->
processIdentifiedEvent(evt)
})

htmlInput.addEventListener("input", { evt ->
evt.preventDefault()
evt as InputEventExtended
Expand All @@ -118,23 +134,15 @@ internal class BackingTextArea(

"insertText" -> {
val data = evt.data ?: return@addEventListener
if (data.length == 1) {
sendKey(
KeyboardEvent(
"keydown", KeyboardEventInit(key = data)
).toComposeEvent()
)
} else if (data.length > 1) {
onEditCommand(listOf(CommitTextCommand(data, 1)))
}
onEditCommand(listOf(CommitTextCommand(data, 1)))
}

"deleteContentBackward" -> {
sendKey(
processKeyboardEvent(
KeyboardEvent(
"keydown",
KeyboardEventInit(key = "Backspace", code = "Backspace").withKeyCode(Key.Backspace)
).toComposeEvent()
)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ internal class WebImeInputService(parentInputService: InputAwareInputService) :
onImeActionPerformed: (ImeAction) -> Unit
) {
backingTextArea =
BackingTextArea(imeOptions, onEditCommand, onImeActionPerformed, sendKey = { evt ->
sendKeyEvent(evt)
})
BackingTextArea(
imeOptions,
onEditCommand,
onImeActionPerformed,
processKeyboardEvent = this@WebImeInputService::processKeyboardEvent
)
backingTextArea?.register()

showSoftwareKeyboard()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.PlatformTextInputService
import androidx.compose.ui.text.input.TextFieldValue
import org.w3c.dom.events.KeyboardEvent

internal interface InputAwareInputService {
fun getOffset(rect: Rect): Offset
fun sendKeyEvent(event: KeyEvent)
fun processKeyboardEvent(keyboardEvent: KeyboardEvent)
fun isVirtualKeyboard(): Boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ internal class ComposeWindow(
return Offset(offsetX, offsetY)
}

override fun sendKeyEvent(event: KeyEvent) {
layer.onKeyboardEvent(event)
override fun processKeyboardEvent(keyboardEvent: KeyboardEvent) {
this@ComposeWindow.processKeyboardEvent(keyboardEvent)
}
}

Expand Down Expand Up @@ -218,6 +218,11 @@ internal class ComposeWindow(
canvasEvents.addDisposableEvent(type) { event -> handler(event as T) }
}

private fun processKeyboardEvent(keyboardEvent: KeyboardEvent) {
val processed = layer.onKeyboardEvent(keyboardEvent.toComposeEvent())
if (processed) keyboardEvent.preventDefault()
}

private fun initEvents(canvas: HTMLCanvasElement) {
var offset = Offset.Zero

Expand Down Expand Up @@ -275,13 +280,11 @@ internal class ComposeWindow(
})

addTypedEvent<KeyboardEvent>("keydown") { event ->
val processed = layer.onKeyboardEvent(event.toComposeEvent())
if (processed) event.preventDefault()
processKeyboardEvent(event)
}

addTypedEvent<KeyboardEvent>("keyup") { event ->
val processed = layer.onKeyboardEvent(event.toComposeEvent())
if (processed) event.preventDefault()
processKeyboardEvent(event)
}

state.globalEvents.addDisposableEvent("focus") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ internal interface OnCanvasTests {

fun commonAfterTest() {
document.getElementById(canvasId)?.remove()
val childNodes = document.body!!.childNodes.asList()
childNodes.forEach {
document.body!!.removeChild(it)
}
}

fun assertApproximatelyEqual(expected: Float, actual: Float, tolerance: Float = 1f) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class SelectionContainerTests : OnCanvasTests {
fun setup() {
// Because AfterTest is fixed only in kotlin 2.0
// https://youtrack.jetbrains.com/issue/KT-61888
document.getElementById(canvasId)?.remove()
commonAfterTest()
}

private fun HTMLCanvasElement.doClick() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.events

import org.w3c.dom.events.Event

internal external interface InputEventInit {
val data: String
val inputType: String
}

internal fun InputEventInit(inputType: String, data: String): InputEventInit = js("({data, inputType})")

internal external class InputEvent(type: String, options: InputEventInit) : Event
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.events

import org.w3c.dom.events.Event

internal external class MouseEvent(type: String) : Event
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.events

import org.w3c.dom.events.Event

internal external interface TouchEventInit

internal external class TouchEvent(type: String, initParams: TouchEventInit) : Event
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
* limitations under the License.
*/

package androidx.compose.ui.input
package androidx.compose.ui.events

import androidx.compose.ui.input.key.Key
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.KeyboardEventInit
import org.w3c.dom.events.MouseEvent

internal external interface KeyboardEventInitExtended : KeyboardEventInit {
var keyCode: Int?
Expand Down Expand Up @@ -54,4 +55,15 @@ internal fun keyDownEvent(
internal fun keyDownEventUnprevented(): KeyboardEvent =
KeyboardEventInit(ctrlKey = true, cancelable = true, key = "Control")
.withKeyCode(Key.CtrlLeft.keyCode.toInt())
.keyDownEvent()
.keyDownEvent()

private fun DummyTouchEventInit(): TouchEventInit = js("({ changedTouches: [new Touch({identifier: 0, target: document})] })")

internal fun createTouchEvent(type: String): TouchEvent {
return TouchEvent(type, DummyTouchEventInit())
}

internal fun createMouseEvent(type: String): MouseEvent {
return MouseEvent(type)
}

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package androidx.compose.ui.input

import androidx.compose.foundation.text.isTypedEvent
import androidx.compose.ui.events.keyDownEvent
import androidx.compose.ui.input.key.toComposeEvent
import kotlin.test.Test
import kotlin.test.assertFalse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package androidx.compose.ui.input

import androidx.compose.ui.events.keyDownEvent
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.toComposeEvent
Expand Down
Loading

0 comments on commit e90c98a

Please sign in to comment.