diff --git a/compose/foundation/foundation/src/webMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.web.kt b/compose/foundation/foundation/src/webMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.web.kt index f5f17347824d1..c46763652c8ca 100644 --- a/compose/foundation/foundation/src/webMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.web.kt +++ b/compose/foundation/foundation/src/webMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.web.kt @@ -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") -private fun KeyboardEvent.isPrintable(): Boolean { - return key.firstOrNull()?.toString() == key -} diff --git a/compose/ui/ui/src/jsMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.js.kt b/compose/ui/ui/src/jsMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.js.kt deleted file mode 100644 index baf50183fe04a..0000000000000 --- a/compose/ui/ui/src/jsMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.js.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2025 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.internal.jsinterop - -import org.w3c.dom.events.Event - -internal actual fun Event.timestampAsInt(): Int = this.timeStamp.toInt() -internal actual fun Event.timestampAsDouble(): Double = this.timeStamp.toDouble() \ No newline at end of file diff --git a/compose/ui/ui/src/jsMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.js.kt b/compose/ui/ui/src/jsMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.js.kt deleted file mode 100644 index f4a312315bf2a..0000000000000 --- a/compose/ui/ui/src/jsMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.js.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2025 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.platform - -import org.w3c.dom.events.InputEvent - -internal actual inline fun InputEvent.asInputEventExt(): InputEventExt { - return unsafeCast() -} \ No newline at end of file diff --git a/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.wasmJs.kt b/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.wasmJs.kt deleted file mode 100644 index 3e1b5c12a22fd..0000000000000 --- a/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.wasmJs.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2025 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.internal.jsinterop - -import org.w3c.dom.events.Event - -internal actual fun Event.timestampAsInt(): Int = this.timeStamp.toInt() -internal actual fun Event.timestampAsDouble(): Double = this.timeStamp.toDouble() \ No newline at end of file diff --git a/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.wasmJs.kt b/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.wasmJs.kt deleted file mode 100644 index f4a312315bf2a..0000000000000 --- a/compose/ui/ui/src/wasmJsMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.wasmJs.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2025 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.platform - -import org.w3c.dom.events.InputEvent - -internal actual inline fun InputEvent.asInputEventExt(): InputEventExt { - return unsafeCast() -} \ No newline at end of file diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.kt deleted file mode 100644 index b7ede4ea4efdb..0000000000000 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/internal/jsinterop/JsInteropUtils.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2025 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.internal.jsinterop - -import org.w3c.dom.events.Event - - -internal expect fun Event.timestampAsInt(): Int -internal expect fun Event.timestampAsDouble(): Double \ No newline at end of file diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/BackingDomInput.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/BackingDomInput.kt index 92096286a53e9..72fc558205713 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/BackingDomInput.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/BackingDomInput.kt @@ -29,7 +29,7 @@ internal interface ComposeCommandCommunicator { fun sendEditCommand(commands: List) 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(""" diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt index 23d1e21542740..f844809ac1020 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt @@ -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, @@ -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() } @@ -113,7 +115,11 @@ internal class DomInputStrategy( }) htmlInput.addEventListener("compositionstart", { evt -> - nativeInputEventsProcessor.registerEvent(evt as CompositionEvent) + nativeInputEventsProcessor.updateImeStatus(evt as CompositionEvent) + }) + + htmlInput.addEventListener("compositionupdate", { evt -> + nativeInputEventsProcessor.updateImeStatus(evt as CompositionEvent) }) htmlInput.addEventListener("compositionend", { evt -> @@ -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() private fun ImeOptions.createDomElement(): HTMLElement { val htmlElement = document.createElement( @@ -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") +} \ No newline at end of file diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessor.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessor.kt index cf3ed2574af0c..24c280442bfaa 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessor.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessor.kt @@ -18,19 +18,19 @@ package androidx.compose.ui.platform import androidx.compose.runtime.TestOnly import androidx.compose.ui.input.key.toComposeEvent -import androidx.compose.ui.internal.jsinterop.timestampAsDouble -import androidx.compose.ui.internal.jsinterop.timestampAsInt import androidx.compose.ui.text.input.BackspaceCommand import androidx.compose.ui.text.input.CommitTextCommand import androidx.compose.ui.text.input.SetComposingTextCommand import androidx.compose.ui.text.input.SetSelectionCommand import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastForEach import org.w3c.dom.events.CompositionEvent import org.w3c.dom.events.InputEvent import org.w3c.dom.events.KeyboardEvent import org.w3c.dom.events.UIEvent +import kotlin.js.js +import kotlin.js.toDouble +import kotlin.js.toInt /** * Processes native input events and handles their translation to commands @@ -46,9 +46,13 @@ internal abstract class NativeInputEventsProcessor( ) { private val collectedEvents = mutableListOf() - private var isCheckpointScheduled = false + + @get:TestOnly + @set:TestOnly + internal var isCheckpointScheduled = false private var lastCompositionEndTimestamp = 0.0 // Double because of k/wasm where Number.toLong() leads to a compilation error - var lastProcessedEventIsBackspace: Boolean = false + var isInIMEComposition = false + private var lastKeydownStatus: ComposeKeyDownStatus? = null /** * Schedules a checkpoint for processing input events. @@ -63,24 +67,23 @@ internal abstract class NativeInputEventsProcessor( private fun internalScheduleCheckpoint() { if (!isCheckpointScheduled) { scheduleCheckpoint() + isCheckpointScheduled = true } } + private fun UIEvent.isInIMEComposition(): Boolean { + return (this is CompositionEvent) + || type == "keydown" && (this as KeyboardEvent).isComposing + || type == "beforeinput" && (this as InputEvent).isComposing + } + fun runCheckpoint(currentTextFieldValue: TextFieldValue) { isCheckpointScheduled = false - collectedEvents.sortBy { it.timestampAsInt() } - - val isInIMEComposition = collectedEvents.fastAny { - it.type == "compositionstart" - || it.type == "compositionupdate" - || it.type == "compositionend" - || it.type == "keydown" && (it as KeyboardEvent).isComposing - || it.type == "beforeinput" && (it as InputEvent).isComposing - } + collectedEvents.sortBy { it.timeStamp.toInt() } collectedEvents.fastForEach { evt -> - val timestamp = evt.timestampAsDouble() + val timestamp = evt.timeStamp.toDouble() when (evt.type) { "keydown" -> { @@ -90,7 +93,7 @@ internal abstract class NativeInputEventsProcessor( if (isTypedEvent(evt)) { // we need to reset this each time we consider something to be typed // see https://youtrack.jetbrains.com/issue/CMP-8773 - lastProcessedEventIsBackspace = evt.key == "Backspace" + lastKeydownStatus = null return@fastForEach } @@ -105,8 +108,11 @@ internal abstract class NativeInputEventsProcessor( val shouldBeProcessed = timestamp == 0.0 || !isFromLastComposition if (shouldBeProcessed) { - lastProcessedEventIsBackspace = evt.key == "Backspace" - composeSender.sendKeyboardEvent(evt.toComposeEvent()) + lastKeydownStatus = ComposeKeyDownStatus( + evt, + composeSender.sendKeyboardEvent(evt.toComposeEvent()) + ) + } } @@ -116,76 +122,75 @@ internal abstract class NativeInputEventsProcessor( } "beforeinput" -> { - (evt as InputEvent).process( - lastProcessedEventIsBackspace = lastProcessedEventIsBackspace, - currentTextFieldValue = currentTextFieldValue - ) + evt.asInputEventExt().process(currentTextFieldValue = currentTextFieldValue) } } } + isInIMEComposition = false collectedEvents.clear() } - private fun InputEvent.process(lastProcessedEventIsBackspace: Boolean, currentTextFieldValue: TextFieldValue) { - val inputExt = this.asInputEventExt() - val editCommands = when (inputExt.inputType) { - "deleteContentBackward" -> buildList { - // this means "deleteContentBackward" happened because of an earlier "keydown" event, so skipping it here - if (lastProcessedEventIsBackspace) return@buildList - - if (!currentTextFieldValue.selection.collapsed) { - // Likely it's on mobile, where the Backspace has Unidentified key value. - // When Compose TextField shows text selection, - // a good UX for deleteContentBackward would be to emulate Backspace - add(BackspaceCommand()) - } else { - // This happens when an autocorrection is applied on mobile: - // The system first tells us to delete the old text, - // and then it would send the "insertText" event. - if (textRangeSize > 0) { - // deleteContentBackward can happen under very non-trivial circumstances, - // for instance; when an input suggestion on Android Chrome is accepted, - // the browser then deletes space after the word just to add space again - add(SetSelectionCommand(inputExt.textRangeStart, inputExt.textRangeEnd)) - add(BackspaceCommand()) - } else if (textRangeSize == 0) { - // under specific circumstance previous symbol can be deleted while inputing new one - // see https://youtrack.jetbrains.com/issue/CMP-8773 + private fun InputEventExt.process(currentTextFieldValue: TextFieldValue) { + val textRangeSize = textRangeEnd - textRangeStart + + val editCommands = buildList { + when (inputType) { + "deleteContentBackward" -> { + if (lastKeydownStatus?.getProcessedEvent()?.isBackspace() == true) return@buildList + + if (!currentTextFieldValue.selection.collapsed) { + // Likely it's on mobile, where the Backspace has Unidentified key value. + // When Compose TextField shows text selection, + // a good UX for deleteContentBackward would be to emulate Backspace add(BackspaceCommand()) + } else { + // This happens when an autocorrection is applied on mobile: + // The system first tells us to delete the old text, + // and then it would send the "insertText" event. + if (textRangeSize > 0) { + // deleteContentBackward can happen under very non-trivial circumstances, + // for instance; when an input suggestion on Android Chrome is accepted, + // the browser then deletes space after the word just to add space again + add(SetSelectionCommand(textRangeStart, textRangeEnd)) + add(BackspaceCommand()) + } else if (textRangeSize == 0) { + // under specific circumstance previous symbol can be deleted while inputting new one + // see https://youtrack.jetbrains.com/issue/CMP-8773 + add(BackspaceCommand()) + } } } - } - "insertReplacementText" -> buildList { - if (data == null) return@buildList - if (textRangeSize > 0) { - add(SetSelectionCommand(inputExt.textRangeStart, inputExt.textRangeEnd)) + "insertReplacementText" -> { + if (data == null) return@buildList + if (textRangeSize > 0) { + add(SetSelectionCommand(textRangeStart, textRangeEnd)) + } + + add(CommitTextCommand(data, 1)) } - add(CommitTextCommand(data, 1)) - } + "insertText" -> { + if (data == null) return@buildList + if (textRangeSize > 0 && currentTextFieldValue.selection.collapsed) { + add(SetSelectionCommand(textRangeStart, textRangeEnd)) + } - "insertText" -> buildList { - if (data == null) return@buildList - if (textRangeSize > 0 && currentTextFieldValue.selection.collapsed) { - add(SetSelectionCommand(inputExt.textRangeStart, inputExt.textRangeEnd)) + add(CommitTextCommand(data, 1)) } - add(CommitTextCommand(data, 1)) - } - - "insertCompositionText" -> buildList { - if (data == null) return@buildList - if (textRangeSize > 0) { - add(SetSelectionCommand(inputExt.textRangeStart, inputExt.textRangeEnd)) + "insertCompositionText" -> { + if (data == null) return@buildList + if (textRangeSize > 0) { + add(SetSelectionCommand(textRangeStart, textRangeEnd)) + } + add(SetComposingTextCommand(data, 1)) } - add(SetComposingTextCommand(data, 1)) - } - // "insertFromComposition", "deleteCompositionText" are triggered in Safari just before the 'compositionEnd' event. - // They're ignored because Safari also sends 'insertCompositionText' which we handle (alongside 'compositionEnd') - else -> emptyList() + // "insertFromComposition", "deleteCompositionText" are triggered in Safari just before the 'compositionEnd' event. + // They're ignored because Safari also sends 'insertCompositionText' which we handle (alongside 'compositionEnd') + } } if (editCommands.isNotEmpty()) { @@ -193,11 +198,31 @@ internal abstract class NativeInputEventsProcessor( } } + internal fun updateImeStatus(event: UIEvent) { + isInIMEComposition = isInIMEComposition || event.isInIMEComposition() + } + internal fun registerEvent(event: UIEvent) { + updateImeStatus(event) collectedEvents.add(event) internalScheduleCheckpoint() } @TestOnly internal fun getCollectedEvents() = collectedEvents -} \ No newline at end of file +} + +private fun isTypedEvent(evt: KeyboardEvent): Boolean = + js("!evt.metaKey && !evt.ctrlKey && evt.key.charAt(0) === evt.key") + + +private class ComposeKeyDownStatus( + val event: KeyboardEvent, + val processed: Boolean +) + +private fun ComposeKeyDownStatus.getProcessedEvent(): KeyboardEvent? { + return if (processed) event else null +} + +private fun KeyboardEvent.isBackspace(): Boolean = key == "Backspace" diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/WebTextInputService.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/WebTextInputService.kt index 29951f4485e88..29221e363c9f1 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/WebTextInputService.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/WebTextInputService.kt @@ -64,21 +64,16 @@ internal abstract class WebTextInputService : */ abstract val backingDomInputContainer: HTMLElement - override fun startInput( - value: TextFieldValue, - imeOptions: ImeOptions, - onEditCommand: (List) -> Unit, - onImeActionPerformed: (ImeAction) -> Unit - ) { + fun startInput(request: PlatformTextInputMethodRequest) { backingDomInput = BackingDomInput( - imeOptions = imeOptions, + imeOptions = request.imeOptions, composeCommunicator = object : ComposeCommandCommunicator { - override fun sendKeyboardEvent(keyboardEvent: KeyEvent) { - this@WebTextInputService.processKeyboardEvent(keyboardEvent) + override fun sendKeyboardEvent(keyboardEvent: KeyEvent): Boolean { + return this@WebTextInputService.processKeyboardEvent(keyboardEvent) } override fun sendEditCommand(commands: List) { - onEditCommand(commands) + request.onEditCommand(commands) } }, inputContainer = backingDomInputContainer, @@ -95,6 +90,15 @@ internal abstract class WebTextInputService : showSoftwareKeyboard() } + override fun startInput( + value: TextFieldValue, + imeOptions: ImeOptions, + onEditCommand: (List) -> Unit, + onImeActionPerformed: (ImeAction) -> Unit + ) { + // This method is not used in the new API, but we keep it for backward compatibility. + } + fun getBackingInput(): HTMLElement? { return backingDomInput?.backingElement?.takeIf { it.isConnected } } diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/WebTextInputSession.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/WebTextInputSession.kt index 246f1e51bcd1a..60ff3ae1c0afd 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/WebTextInputSession.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/WebTextInputSession.kt @@ -49,13 +49,9 @@ internal class WebTextInputSession( } } } - suspendCancellableCoroutine { continuation -> - webTextInputService.startInput( - value = request.value(), - imeOptions = request.imeOptions, - onEditCommand = request.onEditCommand, - onImeActionPerformed = request.onImeAction ?: {} - ) + + suspendCancellableCoroutine { continuation -> + webTextInputService.startInput(request) continuation.invokeOnCancellation { webTextInputService.stopInput() diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessorTest.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessorTest.kt index 435c5996c7032..0e6c82475f9dc 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessorTest.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessorTest.kt @@ -65,8 +65,9 @@ class NativeInputEventsProcessorTest { commands.forEach { it.applyTo(editingBuffer) } } - override fun sendKeyboardEvent(keyboardEvent: KeyEvent) { + override fun sendKeyboardEvent(keyboardEvent: KeyEvent): Boolean { keyboardEvents.add(keyboardEvent) + return true } @Suppress("INVISIBLE_REFERENCE") @@ -84,14 +85,13 @@ class NativeInputEventsProcessorTest { private class TestNativeInputEventsProcessor( composeSender: ComposeCommandCommunicator ) : NativeInputEventsProcessor(composeSender) { - var checkpointScheduled = false override fun scheduleCheckpoint() { - checkpointScheduled = true + isCheckpointScheduled = true } fun manuallyRunCheckpoint(currentTextFieldValue: TextFieldValue) { - checkpointScheduled = false + isCheckpointScheduled = false runCheckpoint(currentTextFieldValue) } } @@ -100,19 +100,19 @@ class NativeInputEventsProcessorTest { fun testCheckpointScheduling() { val communicator = MockComposeCommandCommunicator() val processor = TestNativeInputEventsProcessor(communicator) - assertFalse(processor.checkpointScheduled) + assertFalse(processor.isCheckpointScheduled) processor.registerEvent(keyEvent("a")) - assertTrue(processor.checkpointScheduled) + assertTrue(processor.isCheckpointScheduled) - processor.checkpointScheduled = false + processor.isCheckpointScheduled = false val compositionEvent = compositionStart() processor.registerEvent(compositionEvent) - assertTrue(processor.checkpointScheduled) + assertTrue(processor.isCheckpointScheduled) - processor.checkpointScheduled = false + processor.isCheckpointScheduled = false processor.registerEvent(beforeInput("insertText", "") as InputEvent) - assertTrue(processor.checkpointScheduled) + assertTrue(processor.isCheckpointScheduled) assertEquals(3, processor.getCollectedEvents().size) processor.manuallyRunCheckpoint(communicator.currentTextFieldValue())