Skip to content

Fix selection for transformed text with Native Text Input - #3526

Merged
Andrei Salavei (ASalavei) merged 11 commits into
jb-mainfrom
andrei.salavei/transformed-text-range-fix
Nov 5, 2026
Merged

Andrei Salavei (ASalavei) merged 11 commits into
jb-mainfrom
andrei.salavei/transformed-text-range-fix

Conversation

@ASalavei

@ASalavei Andrei Salavei (ASalavei) commented Oct 22, 2026 •

Copy link
Copy Markdown

The UITextInput protocol uses original text and original selection range to provide to the user corresponding text editing operations. The new parameter offsetMapping is added in order to provide correct selection layout using the TextLayoutResult - which uses ranges of the transformed text.

Introduce TextEditorOffsetMapping that provides bidirectional offset mapping between original and transformed text. Add corresponding parameter to the SkikoPlatformTextInputMethodRequest.
Update text selection and cursor rects calculation according to the selection offset in transformed text.

Fixes https://youtrack.jetbrains.com/issue/CMP-10073/NITI-OutputTransformation-is-broken-on-iOS-using-the-new-usingNativeTextInput

Release Notes

Fixes - iOS

  • Fix selection for transformed text with Native Text Input.

@m-sasha

Alexander Maryanovsky (m-sasha) commented Oct 26, 2026 •

Copy link
Copy Markdown

Could you explain why TextEditorOffsetMapping is needed? Is TransformedTextFieldState not sufficient?

Is it just to put OffsetMapping (BTF1) and TransformedTextFieldState (BTF2) behind a common facade?

@ASalavei

Andrei Salavei (ASalavei) commented Oct 26, 2026 •

Copy link
Copy Markdown
Author

Could you explain why TextEditorOffsetMapping is needed? Is TransformedTextFieldState not sufficient?

Is it just to put OffsetMapping (BTF1) and TransformedTextFieldState (BTF2) behind a common facade?

Yes. The issue that the TransformedTextFieldState is internal and the OffsetMapping is deprecated. So the best way is to introduce a new API there. Also, there are some differences in behavior between BTF1 and BTF2 offset mapping - so the interface also helps to make them work identically (the same way Android does).

@m-sasha

Copy link
Copy Markdown

Yes. The issue that the TransformedTextFieldState is internal and the OffsetMapping is deprecated. So the best way is to introduce a new API there. Also, there are some differences in behavior between BTF1 and BTF2 offset mapping - so the interface also helps to make them work identically (the same way Android does).

Do we need a public API? If not, could we just use TransformedTextFieldState and ignore OffsetMapping as BTF1 is deprecated?

@ASalavei

Copy link
Copy Markdown
Author

Yes. The issue that the TransformedTextFieldState is internal and the OffsetMapping is deprecated. So the best way is to introduce a new API there. Also, there are some differences in behavior between BTF1 and BTF2 offset mapping - so the interface also helps to make them work identically (the same way Android does).

Do we need a public API? If not, could we just use TransformedTextFieldState and ignore OffsetMapping as BTF1 is deprecated?

Yes, we need this API to be available in ui:ui. The TransformedTextFieldState is common internal (in foundation) API so cannot be used for these purposes.

/**
* Maps a [range] in the original text to the corresponding range in the transformed text.
*/
fun mapToTransformed(range: TextRange): TextRange

Choose a reason for hiding this comment

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

Would it not be more convenient to map indices rather than ranges?

@ASalavei Andrei Salavei (ASalavei) Oct 27, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I choose ranges because they provide more universal API then indices and add more variety to the selection capabilities. When developing the feature, I met cases where range selection work different then individual indices and includes not only extra symbols before the original text characters, but also symbols after it.
Currently, aforementioned capabilities are are unused and we indeed falling back to the individual indices API to match Android behavior, but I would keep it like this in case we'll fase any issues.

# Conflicts:
#	compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/text/input/ComposeTextInputConnection.ios.kt
#	compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/text/input/TextInputConnection.ios.kt
return null
}
if (position > currentTextLayoutResult.multiParagraph.intrinsics.annotatedString.length) {
val transformedPosition = offsetMapping.mapToTransformed(position)

Choose a reason for hiding this comment

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

Don't we need transformed range here as well?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes - position in the original text coordinates.
currentTextLayoutResult uses transformed text coordinates, so we have to convert it.

@ASalavei
Andrei Salavei (ASalavei) merged commit d40101a into jb-main Nov 5, 2026
37 of 54 checks passed
@ASalavei
Andrei Salavei (ASalavei) deleted the andrei.salavei/transformed-text-range-fix branch November 5, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants