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 dea1947d7e741..83eca1f5270bd 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 @@ -24,20 +24,30 @@ import androidx.compose.ui.text.input.SetSelectionCommand import androidx.compose.ui.text.input.TextFieldValue import kotlin.js.ExperimentalWasmJsInterop import kotlin.js.JsAny +import kotlin.js.JsArray import kotlin.js.JsName +import kotlin.js.JsNumber import kotlin.js.definedExternally +import kotlin.js.get import kotlin.js.js +import kotlin.js.length +import kotlin.js.toInt +import kotlin.js.toJsArray +import kotlin.js.toJsNumber import kotlin.js.unsafeCast import kotlinx.browser.document import kotlinx.browser.window import org.w3c.dom.HTMLElement import org.w3c.dom.EventInit +import org.w3c.dom.Node +import org.w3c.dom.Range import org.w3c.dom.events.CompositionEvent import org.w3c.dom.events.Event import org.w3c.dom.events.UIEvent import org.w3c.dom.events.InputEvent import org.w3c.dom.events.KeyboardEvent + internal class DomInputStrategy( imeOptions: ImeOptions, private val composeSender: ComposeCommandCommunicator, @@ -45,6 +55,8 @@ internal class DomInputStrategy( val htmlInput = imeOptions.createDomElement() private var lastMeaningfulUpdate = TextFieldValue("") + private var latestSelection = TextSelection(0, 0) + private var isInCompositionMode = false // To avoid the re-triggering of the selection change private var pauseSelectionChangeListener = false @@ -63,24 +75,26 @@ internal class DomInputStrategy( } fun updateState(textFieldValue: TextFieldValue) { - htmlInput as HTMLElementWithValue - - val needsTextUpdate = lastMeaningfulUpdate.text != textFieldValue.text - val needsSelectionUpdate = lastMeaningfulUpdate.selection != textFieldValue.selection + val needsTextUpdate = (lastMeaningfulUpdate.text != textFieldValue.text) && !isInCompositionMode + val needsSelectionUpdate = (lastMeaningfulUpdate.selection != textFieldValue.selection) && !isInCompositionMode lastMeaningfulUpdate = textFieldValue if (needsTextUpdate) { - htmlInput.value = textFieldValue.text + htmlInput.textContent = textFieldValue.text + + htmlInput.focus() } - if (needsSelectionUpdate) { + + if (needsTextUpdate || needsSelectionUpdate) { pauseSelectionChangeListener = true - htmlInput.setSelectionRange(textFieldValue.selection.min, textFieldValue.selection.max) + setSelectionRange(htmlInput,textFieldValue.selection.min, textFieldValue.selection.max) pauseSelectionChangeListener = false } } private val tabKeyCode = Key.Tab.keyCode.toInt() + @OptIn(ExperimentalWasmJsInterop::class) private fun initEvents() { // Whenever new type of event is processed, don't forget to sync the NativeInputEventsProcessor::runCheckpoint isIME check htmlInput.addEventListener("keydown", { evt -> @@ -101,25 +115,34 @@ internal class DomInputStrategy( htmlInput.addEventListener("beforeinput", { evt -> if (evt is InputEvent) { - htmlInput as HTMLElementWithValue - val inputExt = evt.asInputEventExt() - inputExt.textRangeStart = htmlInput.selectionStart - inputExt.textRangeEnd = htmlInput.selectionEnd + + inputExt.textRangeStart = latestSelection.start + inputExt.textRangeEnd = latestSelection.end + + inputExt.firstRange = inputExt.getTargetRanges()[0] nativeInputEventsProcessor.registerEvent(evt) } }) + htmlInput.addEventListener("compositionstart", {evt -> + isInCompositionMode = true + }) + htmlInput.addEventListener("compositionend", { evt -> + isInCompositionMode = false nativeInputEventsProcessor.registerEvent(evt as CompositionEvent) }) selectionChangeListener = listener@{ _ -> if (pauseSelectionChangeListener || !isInputActive()) return@listener - htmlInput as HTMLElementWithValue - val start = htmlInput.selectionStart - val end = htmlInput.selectionEnd + + val currentSelection = getSelectionRange(htmlInput) + val start = currentSelection?.get(0)?.toInt() ?: 0 + val end = currentSelection?.get(1)?.toInt() ?: 0 + latestSelection = TextSelection(start, end) + val selection = lastMeaningfulUpdate.selection if (start != selection.min || end != selection.max) { @@ -160,18 +183,40 @@ internal external class InputEventExt : UIEvent { var textRangeStart: Int var textRangeEnd: Int + var firstRange: StaticRange? + constructor(type: String, eventInitDict: EventInit = definedExternally) + + /** + * Returns an array of static ranges that will be affected by a change to the DOM + * if the input event is not canceled. + * + * See https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/getTargetRanges + */ + fun getTargetRanges(): JsArray } -internal inline fun UIEvent.asInputEventExt(): InputEventExt = unsafeCast() +/** + * Represents a [StaticRange] - a range of content in a document that is not updated + * when the underlying DOM tree is modified. + * + * See https://developer.mozilla.org/en-US/docs/Web/API/StaticRange + */ +@OptIn(ExperimentalWasmJsInterop::class) +internal external interface StaticRange : JsAny { + val startContainer: JsAny + val startOffset: Int + val endContainer: JsAny + val endOffset: Int + val collapsed: Boolean +} -internal val InputEventExt.textRangeSize: Int - get() = this.asInputEventExt().let { it.textRangeEnd - it.textRangeStart } +internal inline fun UIEvent.asInputEventExt(): InputEventExt = unsafeCast() private fun ImeOptions.createDomElement(): HTMLElement { val htmlElement = document.createElement( - if (singleLine) "input" else "textarea" + if (singleLine) "span" else "div" ) as HTMLElement // without autocorrect set "on" iOS virtual keyboard won't suggest @@ -181,6 +226,8 @@ private fun ImeOptions.createDomElement(): HTMLElement { htmlElement.setAttribute("autocapitalize", "off") htmlElement.setAttribute("spellcheck", "false") + htmlElement.setAttribute("contenteditable", "true") + val inputMode = when (keyboardType) { KeyboardType.Text -> "text" KeyboardType.Ascii -> "text" @@ -213,13 +260,84 @@ private fun ImeOptions.createDomElement(): HTMLElement { return htmlElement } -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) +@OptIn(ExperimentalWasmJsInterop::class) +private external interface HasDomSelection : JsAny { + fun getSelection(): Selection? } +/** + * Represents a [Selection] - the range of text selected by the user or the current position of the caret. + * + * Minimal definition sufficient for [setSelectionRange] and [getSelectionOffsets]. + * + * See https://developer.mozilla.org/en-US/docs/Web/API/Selection + */ +@OptIn(ExperimentalWasmJsInterop::class) +private external interface Selection : JsAny { + // https://developer.mozilla.org/en-US/docs/Web/API/Selection/setBaseAndExtent + fun setBaseAndExtent(anchorNode: Node, anchorOffset: Int, focusNode: Node, focusOffset: Int) +} + +@OptIn(ExperimentalWasmJsInterop::class) +private fun getSelectionRange(element: HTMLElement): JsArray? = js( + """{ + var selection = window.getSelection(); + if (selection == null) return null; + var root = element.getRootNode(); + if (root == null) return null; + + if (typeof selection.getComposedRanges === 'function') { + try { + // The modern standard approach + var composedRanges = selection.getComposedRanges({ shadowRoots: [root] }); + if (composedRanges.length > 0) { + var firstRange = composedRanges[0]; + return [firstRange.startOffset, firstRange.endOffset]; + } + return null; + } catch (e) { + // Fallback for early Safari 17 point-releases + var composedRanges = selection.getComposedRanges(root); + if (composedRanges.length > 0) { + var firstRange = composedRanges[0]; + return [firstRange.startOffset, firstRange.endOffset]; + } + return null; + } + } + + if (typeof root.getSelection === 'function') { + var rootSelection = root.getSelection(); + if (rootSelection == null) return [0, 0]; + if (rootSelection.rangeCount > 0) { + var rootRange = rootSelection.getRangeAt(0); + return [rootRange.startOffset, rootRange.endOffset]; + } + return null; + } + + if (selection.rangeCount > 0) { + var selectionRange = selection.getRangeAt(0); + return [selectionRange.startOffset, selectionRange.endOffset]; + } + return null; + }""" +) + +internal fun setSelectionRange(element: HTMLElement, startOffset: Int, endOffset: Int) { + val selection = window.unsafeCast().getSelection() + + val textNode = element.firstChild + if (textNode != null) { + selection?.setBaseAndExtent(textNode, startOffset, textNode, endOffset) + } else { + selection?.setBaseAndExtent(element, 0, element, 0) + } +} + + internal fun isTypedEvent(evt: KeyboardEvent): Boolean = js("!evt.metaKey && !evt.ctrlKey && evt.key.charAt(0) === evt.key") + + +private data class TextSelection(val start: Int, val end: Int) \ 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 052a708e74af7..e5d5c933290e7 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 @@ -168,18 +168,18 @@ internal abstract class NativeInputEventsProcessor( // When Compose TextField has text selection, a good UX for deleteContentBackward would be to emulate Backspace. add(BackspaceCommand()) } - } else { // Empty selection case. - // 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; - // - or when a browser performs Fast Delete; - add(SetSelectionCommand(textRangeStart, textRangeEnd)) - add(BackspaceCommand()) - } else if (textRangeSize == 0 && lastProcessedKeydown?.isBackspace() != true) { + } else { + val targetRange = firstRange + if (targetRange != null) { + // Empty selection case. + // 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 (!targetRange.collapsed) { + add(SetSelectionCommand(targetRange.startOffset, targetRange.endOffset)) + add(BackspaceCommand()) + } + } else { // We skip this branch if the lastProcessedKeydown is Backspace, because Compose must have already processed this. // Otherwise, under specific circumstance previous symbol can be deleted while inputting the new one // see https://youtrack.jetbrains.com/issue/CMP-8773 @@ -205,17 +205,18 @@ internal abstract class NativeInputEventsProcessor( "insertReplacementText" -> buildList { if (data == null) return@buildList - if (textRangeSize > 0) { - add(SetSelectionCommand(textRangeStart, textRangeEnd)) - } + resolveSelection()?.let { add(it) } add(CommitTextCommand(data, 1)) } "insertText" -> buildList { if (data == null) return@buildList - if (textRangeSize > 0 && currentTextFieldValue.selection.collapsed) { - add(SetSelectionCommand(textRangeStart, textRangeEnd)) + + resolveSelection()?.let { + if (currentTextFieldValue.selection.collapsed) { + add(it) + } } add(CommitTextCommand(data, 1)) @@ -223,9 +224,7 @@ internal abstract class NativeInputEventsProcessor( "insertCompositionText" -> buildList { if (data == null) return@buildList - if (textRangeSize > 0) { - add(SetSelectionCommand(textRangeStart, textRangeEnd)) - } + resolveSelection()?.let { add(it) } add(SetComposingTextCommand(data, 1)) } @@ -248,4 +247,15 @@ internal abstract class NativeInputEventsProcessor( internal fun getCollectedEvents() = collectedEvents } + private fun KeyboardEvent.isBackspace(): Boolean = key == "Backspace" + +private fun InputEventExt.resolveSelection(): SetSelectionCommand? { + firstRange?.let { targetRange -> + if (!targetRange.collapsed) { + return SetSelectionCommand(targetRange.startOffset, targetRange.endOffset) + } + } + + return null +} \ No newline at end of file diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt index 9b7cf83e81cb7..4c35c804ea2b2 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt @@ -129,7 +129,8 @@ fun ComposeViewport( width: calc(var(--compose-internal-web-backing-input-width) * 1px); left: min(var(--compose-internal-web-backing-input-left) * 1px, 100vw - var(--compose-internal-web-backing-input-width) * 1px); top: min(var(--compose-internal-web-backing-input-top) * 1px, 100vh - var(--compose-internal-web-backing-input-height) * 1px); - + + overflow: hidden; align-content: center; background: transparent; border: none; @@ -142,7 +143,7 @@ fun ComposeViewport( resize: none; text-shadow: none; user-select: none; - white-space: pre; + white-space: break-spaces; z-index: -1; } """.trimIndent() diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/events/InputEvent.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/events/InputEvent.kt index 2a2f84fe18241..8a6c3c80bbe03 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/events/InputEvent.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/events/InputEvent.kt @@ -16,6 +16,8 @@ package androidx.compose.ui.events +import androidx.compose.ui.platform.InputEventExt +import androidx.compose.ui.platform.StaticRange import org.w3c.dom.events.UIEvent private external interface InputEventInit { @@ -29,3 +31,10 @@ private external class InputEvent(type: String, options: InputEventInit) : UIEv internal fun beforeInput(inputType: String, data: String?, isComposing: Boolean = false): UIEvent = InputEvent("beforeinput", InputEventInit(inputType = inputType, data = data, isComposing = isComposing)) + +private fun createStaticRange(startOffset: Int, endOffset: Int): StaticRange = + js("({ startContainer: null, endContainer: null, startOffset: startOffset, endOffset: endOffset, collapsed: startOffset === endOffset })") + +internal fun InputEventExt.setFirstRange(startOffset: Int, endOffset: Int) { + firstRange = createStaticRange(startOffset, endOffset) +} diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/ExternalSelectionChangeListenerTest.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/ExternalSelectionChangeListenerTest.kt index 92bbe6fee6c2e..62e9f11257f1d 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/ExternalSelectionChangeListenerTest.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/ExternalSelectionChangeListenerTest.kt @@ -20,19 +20,16 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.compose.ui.OnCanvasTests -import androidx.compose.ui.WebApplicationScope import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.setSelectionRange import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import kotlin.test.Test import kotlin.test.assertEquals import kotlinx.browser.document -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.withContext import kotlinx.coroutines.yield -import org.w3c.dom.HTMLTextAreaElement +import org.w3c.dom.HTMLDivElement import org.w3c.dom.events.Event class ExternalSelectionChangeListenerTest : OnCanvasTests { @@ -63,14 +60,14 @@ class ExternalSelectionChangeListenerTest : OnCanvasTests { assertEquals(TextRange(text.length), textFieldValue.value.selection) - htmlInput.setSelectionRange(1, 7) + setSelectionRange(htmlInput, 1, 7) document.dispatchEvent(Event("selectionchange")) awaitAnimationFrame() awaitIdle() assertEquals(TextRange(1, 7), textFieldValue.value.selection) - htmlInput.setSelectionRange(8, 8) + setSelectionRange(htmlInput, 8, 8) document.dispatchEvent(Event("selectionchange")) awaitAnimationFrame() awaitIdle() @@ -78,10 +75,10 @@ class ExternalSelectionChangeListenerTest : OnCanvasTests { assertEquals(TextRange(8, 8), textFieldValue.value.selection) } - private suspend fun WebApplicationScope.waitForHtmlInput(): HTMLTextAreaElement { + private suspend fun waitForHtmlInput(): HTMLDivElement { while (true) { - val element = getShadowRoot().querySelector("textarea") - if (element is HTMLTextAreaElement) { + val element = getShadowRoot().querySelector("div.compose-backing-field") + if (element is HTMLDivElement) { return element } yield() diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/MouseTextInputTests.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/MouseTextInputTests.kt index 45f0f1e3e9cf8..c05ad4cde4c5d 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/MouseTextInputTests.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/MouseTextInputTests.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import kotlinx.coroutines.yield -import org.w3c.dom.HTMLTextAreaElement +import org.w3c.dom.HTMLDivElement import org.w3c.dom.pointerevents.PointerEvent import org.w3c.dom.pointerevents.PointerEventInit @@ -70,10 +70,10 @@ class MouseTextInputTests: OnCanvasTests { awaitIdle() assertEquals(TextRange(0, 0), textRange.value) - val textArea = getShadowRoot().querySelector("textarea") - assertIs(textArea) + val backingInput = getShadowRoot().querySelector("div.compose-backing-field") + assertIs(backingInput) - val textAreaRect = textArea.getBoundingClientRect() + val textAreaRect = backingInput.getBoundingClientRect() // Do a manual hit-test val elementsAtPos = getShadowRoot().elementFromPoint( textAreaRect.left + textAreaRect.width / 2 , diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/TextFieldFocusTest.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/TextFieldFocusTest.kt index 88b33e70e1360..e8e9d3d4f6bcf 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/TextFieldFocusTest.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/TextFieldFocusTest.kt @@ -44,7 +44,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlinx.coroutines.yield -import org.w3c.dom.HTMLInputElement +import org.w3c.dom.HTMLSpanElement import org.w3c.dom.events.Event import org.w3c.dom.events.KeyboardEvent import org.w3c.dom.pointerevents.PointerEvent @@ -56,10 +56,10 @@ class TextFieldFocusTest : OnCanvasTests { fun canMoveFocusForwardAndBackUsingTab() = runApplicationTest { val focusRequester = FocusRequester() - suspend fun waitForSingleLineHtmlInput(): HTMLInputElement { + suspend fun waitForSingleLineHtmlInput(): HTMLSpanElement { while (true) { - val element = getShadowRoot().querySelector("input") - if (element is HTMLInputElement) { + val element = getShadowRoot().querySelector("span.compose-backing-field") + if (element is HTMLSpanElement) { return element } yield() diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/CompositeInputTestSpec.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/CompositeInputTestSpec.kt index 80d7991c45577..940f8d349784c 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/CompositeInputTestSpec.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/CompositeInputTestSpec.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.events.eventsSequence import androidx.compose.ui.events.keyEvent import kotlin.test.Test import kotlin.test.assertIs -import org.w3c.dom.HTMLTextAreaElement +import org.w3c.dom.HTMLDivElement internal interface СompositeInputTestSpec : TextFieldTestSpec { @@ -39,8 +39,8 @@ internal interface СompositeInputTestSpec : TextFieldTestSpec { fun compositeInput() = runApplicationTest { val textFieldValue = createApplicationWithHolder() - val backingTextField = getShadowRoot().querySelector("textarea") - assertIs(backingTextField) + val backingTextField = getShadowRoot().querySelector("div.compose-backing-field") + assertIs(backingTextField) triggerComposingSequence("a", "1", "啊").sendToHtmlInput() diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/TextFieldTestSpec.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/TextFieldTestSpec.kt index d075b8a58d7b3..1de38805c1f4d 100644 --- a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/TextFieldTestSpec.kt +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/TextFieldTestSpec.kt @@ -25,11 +25,11 @@ import androidx.compose.ui.events.keyEvent import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.text.TextRange import kotlinx.coroutines.yield -import org.w3c.dom.HTMLTextAreaElement +import org.w3c.dom.HTMLDivElement import org.w3c.dom.events.Event internal interface TextFieldTestSpec : OnCanvasTests { - fun currentHtmlInput() = getShadowRoot().querySelector("textarea") as HTMLTextAreaElement + fun currentHtmlInput() = getShadowRoot().querySelector("div.compose-backing-field") as HTMLDivElement suspend fun createTestInputState( initialText: String = "", @@ -61,10 +61,10 @@ internal interface TextFieldTestSpec : OnCanvasTests { fun EventsSequence.sendToHtmlInput() = sendToHtmlInput(*toList().toTypedArray()) - suspend fun WebApplicationScope.waitForHtmlInput(): HTMLTextAreaElement { + suspend fun WebApplicationScope.waitForHtmlInput(): HTMLDivElement { while (true) { - val element = getShadowRoot().querySelector("textarea") - if (element is HTMLTextAreaElement) { + val element = getShadowRoot().querySelector("div.compose-backing-field") + if (element is HTMLDivElement) { return element } yield() 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 a2f67bb8a73dd..93cdd23e149d9 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 @@ -41,6 +41,7 @@ import androidx.compose.ui.events.beforeInput import androidx.compose.ui.events.compositionEnd import androidx.compose.ui.events.compositionStart import androidx.compose.ui.events.keyEvent +import androidx.compose.ui.events.setFirstRange import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -218,8 +219,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent( beforeInput("insertText", "a").asInputEventExt().apply { - textRangeStart = 3 - textRangeEnd = 4 + setFirstRange(3, 4) } ) processor.manuallyRunCheckpoint(communicator.currentTextFieldValue()) @@ -246,8 +246,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent( beforeInput("deleteContentBackward", "").asInputEventExt().apply { - textRangeStart = 3 - textRangeEnd = 4 + setFirstRange(3, 4) } ) processor.manuallyRunCheckpoint(communicator.currentTextFieldValue()) @@ -318,8 +317,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent( beforeInput("insertReplacementText", "replacement").asInputEventExt().apply { - textRangeStart = 5 - textRangeEnd = 9 + setFirstRange(5, 9) }, ) @@ -378,8 +376,7 @@ class NativeInputEventsProcessorTest { // 3. Simulate the input event for the accented character processor.registerEvent( beforeInput("insertText", "é").asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) } ) @@ -446,8 +443,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent( beforeInput("insertText", "è").asInputEventExt().apply { // to replace `e` - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) }, ) @@ -511,8 +507,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent(keyEvent(key = "ArrowRight", code = "ArrowRight", isComposing = true)) processor.registerEvent( beforeInput("insertText", "è", isComposing = true).asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) } ) processor.manuallyRunCheckpoint(communicator.currentTextFieldValue()) @@ -522,8 +517,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent(keyEvent(key = "ArrowRight", code = "ArrowRight", isComposing = true)) processor.registerEvent( beforeInput("insertCompositionText", "é").asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) } ) @@ -534,8 +528,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent(keyEvent(key = "ArrowRight", code = "ArrowRight", isComposing = true)) processor.registerEvent( beforeInput("insertCompositionText", "ê").asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) } ) @@ -553,8 +546,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent( beforeInput("insertCompositionText", "é").asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) } ) @@ -568,8 +560,7 @@ class NativeInputEventsProcessorTest { // 4. Simulate the input event for the selected accented character processor.registerEvent( beforeInput("insertCompositionText", "é").asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) } ) @@ -624,8 +615,7 @@ class NativeInputEventsProcessorTest { // Add deleteContentBackward event processor.registerEvent( beforeInput("deleteContentBackward", "").asInputEventExt().apply { - textRangeStart = 3 - textRangeEnd = 5 + setFirstRange(3, 5) }, ) @@ -694,8 +684,7 @@ class NativeInputEventsProcessorTest { processor.registerEvent( beforeInput("deleteContentBackward", "").asInputEventExt().apply { - textRangeStart = 8 - textRangeEnd = 12 + setFirstRange(8, 12) }, ) @@ -736,8 +725,7 @@ class NativeInputEventsProcessorTest { // Then add a deleteContentBackward event processor.registerEvent( beforeInput("deleteContentBackward", "").asInputEventExt().apply { - textRangeStart = 0 - textRangeEnd = 1 + setFirstRange(0, 1) }, )