Skip to content

Fix iOS TextField cursor placement on CJK punctuation - #3342

Merged
Andrei Salavei (ASalavei) merged 3 commits into
JetBrains:jb-mainfrom
Kyle-Ye:bugfix/cjk-punctuation-cursor
Sep 1, 2026
Merged

Fix iOS TextField cursor placement on CJK punctuation#3342
Andrei Salavei (ASalavei) merged 3 commits into
JetBrains:jb-mainfrom
Kyle-Ye:bugfix/cjk-punctuation-cursor

Conversation

@Kyle-Ye

Copy link
Copy Markdown

Tapping CJK punctuation in an iOS TextField can move the caret to a later
whitespace instead of keeping it near the tapped character.

For example, tapping at offset 6 in the reproduction attached to
CMP-10699 moves the selection to offset 130.

determineCursorDesiredOffset treats punctuation as part of Cupertino's
Latin word-snapping behavior. CJK punctuation therefore enters
findNextNonWhitespaceSymbolsSubsequenceStartOffset and may scan across
the following CJK text until it reaches the next whitespace.

Keep character-level cursor placement for CJK scripts and full-width forms
before applying the existing Latin word adjustment. Both
BasicTextField(TextFieldValue) and BasicTextField(TextFieldState) use
this shared iOS path. Android behavior is unchanged.

Fixes https://youtrack.jetbrains.com/issue/CMP-10699

Testing

  • Added a regression test for tapping U+3001 IDEOGRAPHIC COMMA.
  • Added tests covering CJK scripts, full-width forms, Latin punctuation,
    emoji, supplementary CJK characters, and invalid offsets.
  • Ran:
    ./gradlew :compose:foundation:foundation:iosSimulatorArm64Test
  • Verified with the reproducer on iPhone 14 / iOS 26.0:
    the selection remains at offset 6 instead of jumping to 130.

Release Notes

Fixes - iOS

  • Fixed the TextField caret jumping to a later whitespace when tapping
    CJK punctuation.

Google CLA

Signed the Google Contributor License Agreement.

}

/**
* CJK scripts and full-width forms use character-level cursor placement on iOS. Applying

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment says about iOS only. Is it applicable for Desktop and Web as well?

cc Alexander Maryanovsky (@m-sasha) , Oleksandr Karpovich (@eymar)

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.

No, the behavior change is iOS-specific IMO.

requiresCharacterLevelCursorPlacement is defined in skikoMain because
the existing Cupertino cursor adjustment and its tests live there, but
determineCursorDesiredOffset only has iOS production call sites:

  • the legacy TextField path through TextFieldPointerModifier.ios.kt
  • the state-based TextField path through TextFieldSelectionState.ios.kt

Desktop, Web, and macOS use defaultTextFieldPointer and do not invoke this
logic, so their cursor behavior is unchanged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's also used in internal fun TextFieldDelegate.Companion.cupertinoSetCursorOffsetFocused which is in skikoMain.

But in the end it's iosMain only.

LGTM from web perspective (not affecting web).

@Kyle-Ye

Copy link
Copy Markdown
Author

Could you please trigger the CI workflows for this PR? Andrei Salavei (@ASalavei)

And is there a process for regular external contributors (I’ve previously had #3183 and #3203 merged) to become trusted for CI purposes so that future fork PR workflows no longer require manual approval?

textLayoutResult.getLineEnd(lineNumber)
}

currentText.requiresCharacterLevelCursorPlacement(offset) -> offset

@ASalavei Andrei Salavei (ASalavei) Aug 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The range inside the requiresCharacterLevelCursorPlacement includes U+3000 IDEOGRAPHIC SPACE (Unicode Zs). The new branch runs before isWhitespaceOrPunctuation, so tapping a fullwidth space now leaves the caret on the space instead of moving to the next word.

I would consider moving this check after the currentText.isWhitespaceOrPunctuation(offset)

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.

Good catch, thank you. U+3000 is indeed included in that range, and its existing whitespace behavior should be preserved.
Moving the character-level check after isWhitespaceOrPunctuation, however, would also route U+3001 IDEOGRAPHIC COMMA back through the punctuation-scanning branch and reintroduce the original cursor-jump issue.
I’ve therefore updated requiresCharacterLevelCursorPlacement to explicitly exclude Unicode whitespace. This preserves the existing Cupertino behavior for U+3000 while keeping character-level placement for CJK punctuation such as U+3001.
I’ve also added regression tests covering both cases.

ASalavei
Andrei Salavei (ASalavei) approved these changes Aug 31, 2026
@Kyle-Ye

Kyle (Kyle-Ye) commented Aug 31, 2026

Copy link
Copy Markdown
Author

Please see the attached video. (Disclaimer: I don't know Chinese and how the language input should work).
After this PR, selection https://github.com/user-attachments/assets/2c0a35bc-4b5b-406d-8955-d4e0ee16a9be

I can't see this file Andrei Salavei (@ASalavei) anymore. Could you create a youtrack ticket to track it?

@ASalavei

Copy link
Copy Markdown

Please see the attached video. (Disclaimer: I don't know Chinese and how the language input should work).
After this PR, selection https://github.com/user-attachments/assets/2c0a35bc-4b5b-406d-8955-d4e0ee16a9be

I can't see this file Andrei Salavei (Andrei Salavei (@ASalavei)) anymore. Could you create a youtrack ticket to track it?

I re-attached it to the YT ticket.

Thank you for the PR! Merging it...

@ASalavei
Andrei Salavei (ASalavei) merged commit e0f99c0 into JetBrains:jb-main Sep 1, 2026
22 checks passed
@Kyle-Ye
Kyle (Kyle-Ye) deleted the bugfix/cjk-punctuation-cursor branch September 2, 2026 03:22
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