Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@ package androidx.compose.foundation.text
import androidx.compose.ui.dom.domEventOrNull
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.type
import kotlin.js.js
import org.w3c.dom.events.KeyboardEvent

internal actual val KeyEvent.isTypedEvent: Boolean
get() = type == KeyEventType.KeyDown
&& !isMetaPressed
&& !isCtrlPressed
&& domEventOrNull?.isPrintable() == true
get() = type == KeyEventType.KeyDown && domEventOrNull?.isTypedEvent == true

private val KeyboardEvent.isTypedEvent: Boolean
get() = isTypedEvent(this)

private fun isTypedEvent(evt: KeyboardEvent): Boolean =
js("!evt.metaKey && !evt.ctrlKey && evt.key.charAt(0) === evt.key")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now we have 3 different declarations.

We can't avoid implementing the expect/actual and we can't avoid extracting the js calls into a separate function.

But do we need private val KeyboardEvent.isTypedEvent: Boolean?

I think 2 declarations would be enough. WDYT?


private fun KeyboardEvent.isPrintable(): Boolean {
return key.firstOrNull()?.toString() == key
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal interface ComposeCommandCommunicator {
fun sendEditCommand(commands: List<EditCommand>)
fun sendEditCommand(command: EditCommand) = sendEditCommand(listOf(command))

fun sendKeyboardEvent(keyboardEvent: KeyEvent)
fun sendKeyboardEvent(keyboardEvent: KeyEvent): Boolean
}

private fun setBackingInputBox(container: HTMLElement, left: Float, top: Float, width: Float, height: Float) { js("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ import kotlin.js.ExperimentalWasmJsInterop
import kotlin.js.JsAny
import kotlin.js.JsName
import kotlin.js.definedExternally
import kotlin.js.js
import kotlin.js.unsafeCast
import kotlinx.browser.document
import kotlinx.browser.window
import org.w3c.dom.EventInit
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.CompositionEvent
import org.w3c.dom.events.Event
import org.w3c.dom.events.InputEvent
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.UIEvent

internal class DomInputStrategy(
imeOptions: ImeOptions,
Expand Down Expand Up @@ -77,18 +78,19 @@ internal class DomInputStrategy(
}
}

private val tabKeyCode = Key.Tab.keyCode.toInt()
private fun KeyboardEvent.isComposeOnlyEvent(): Boolean {
return when (keyCode.toLong()) {
// In text inputs we want tab to be controlled entirely by Compose, same about navigation via errors
Key.Tab.keyCode, Key.DirectionLeft.keyCode, Key.DirectionUp.keyCode, Key.DirectionRight.keyCode, Key.DirectionDown.keyCode -> true
else -> false
}
}

private fun initEvents() {
htmlInput.addEventListener("blur", { evt ->
// TODO: any actions here?
})

htmlInput.addEventListener("keydown", { evt ->
nativeInputEventsProcessor.registerEvent(evt as KeyboardEvent)

if (evt.keyCode == tabKeyCode) {
// Compose logic will handle the focus movement or insert Tabs if necessary
if (evt.isComposeOnlyEvent()) {
evt.preventDefault()
}

Expand All @@ -113,7 +115,11 @@ internal class DomInputStrategy(
})

htmlInput.addEventListener("compositionstart", { evt ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isInIMEComposition includes compositionstart, so I think this event listener should remain here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this logic - basically, we need to register this event but don't care about any handlers

nativeInputEventsProcessor.registerEvent(evt as CompositionEvent)
nativeInputEventsProcessor.updateImeStatus(evt as CompositionEvent)
})

htmlInput.addEventListener("compositionupdate", { evt ->
nativeInputEventsProcessor.updateImeStatus(evt as CompositionEvent)
})

htmlInput.addEventListener("compositionend", { evt ->
Expand Down Expand Up @@ -159,16 +165,16 @@ private external interface DocumentOrShadowRootLike : JsAny {
}

@JsName("InputEvent")
internal external class InputEventExt : JsAny {
internal external class InputEventExt : UIEvent {
val data: String?
val inputType: String
var textRangeStart: Int
var textRangeEnd: Int
}

internal val InputEvent.textRangeSize: Int
get() = this.asInputEventExt().let { it.textRangeEnd - it.textRangeStart }
constructor(type: String, eventInitDict: EventInit = definedExternally)
}

internal expect inline fun InputEvent.asInputEventExt(): InputEventExt
internal inline fun UIEvent.asInputEventExt(): InputEventExt = unsafeCast<InputEventExt>()

private fun ImeOptions.createDomElement(): HTMLElement {
val htmlElement = document.createElement(
Expand Down Expand Up @@ -250,13 +256,11 @@ private fun ImeOptions.createDomElement(): HTMLElement {
return htmlElement
}

// HTMLTextAreaElement and HTMLInputElement do not intersect in Kotlin definition, so we introduce helper interface
private external interface HTMLElementWithValue {
var value: String
val selectionStart: Int
val selectionEnd: Int
val selectionDirection: String?
fun setSelectionRange(start: Int, end: Int, direction: String = definedExternally)
}

internal fun isTypedEvent(evt: KeyboardEvent): Boolean =
js("!evt.metaKey && !evt.ctrlKey && evt.key.charAt(0) === evt.key")
}
Loading
Loading