Skip to content

Fix iOS TextField cursor placement on CJK punctuation - #3342

Open
Kyle (Kyle-Ye) wants to merge 2 commits into
JetBrains:jb-mainfrom
Kyle-Ye:bugfix/cjk-punctuation-cursor
Open

Fix iOS TextField cursor placement on CJK punctuation#3342
Kyle (Kyle-Ye) wants to merge 2 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)

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.

3 participants