Skip to content

[iOS] Backspace on the first character of a paragraph does not delete an unconfirmed IME character (Japanese/CJK composition) #673

Description

@fly-gon

Environment

  • Package: react-native-enriched-html@1.0.0 (also reproduces on the previous react-native-enriched@0.6.1 and 0.8.1; our app is currently pinned to react-native-enriched@0.6.1 because 1.0.0 introduced separate regressions we haven't finished isolating yet)
  • Platform: iOS (physical iPhone, iOS 17+)
  • React Native version: 0.85
  • Language input: Japanese IME (expected to reproduce on Chinese / Korean IMEs)

Reproduction Steps

  1. Open an EnrichedTextInput (new / empty, or an existing document — both reproduce).
  2. Focus the editor. Cursor is at the beginning of the first paragraph (i.e. right after the <p> opening tag in the canonical model).
  3. Start typing a Japanese IME character (a character with the composition underline still shown, i.e. markedText state — for example, type「あ」).
  4. Press Backspace while the character is still unconfirmed (underlined).

Expected

The unconfirmed character is deleted, mirroring the standard iOS UITextView behavior in Notes.app / Safari / plain TextInput.

Actual

The character is not deleted. Repeatedly pressing Backspace has no effect. Committing the character first (e.g. tapping the confirm button), then pressing Backspace, works normally.

Additionally: If the same operation is performed not at the very beginning of a paragraph (e.g. after typing and confirming「a」then starting a new IME composition), Backspace deletes the unconfirmed character correctly. The bug is specific to the paragraph-start position.

Analysis

  • -textView:shouldChangeTextInRange:replacementText: is not called by UIKit while markedTextRange is non-nil (this is documented iOS behavior).
  • handleKeyPressInRange: and thus onKeyPress in this library are wired through shouldChangeTextInRange:, so they cannot observe the Backspace either.
  • I suspect the ZWS-based paragraph tracking (ZeroWidthSpaceUtils handleBackspaceInRange:) plus the composition state combine to leave the deletion in a no-op state when the caret is right after the paragraph's ZWS / <p> open.

Suggested Direction

Overriding -[UITextView deleteBackward] (or wrapping the internal UITextView with a subclass) in EnrichedTextInputView.mm could give a path to intercept the composition-time Backspace at the paragraph boundary and route it through the paragraph handling code even when markedTextRange != nil.

Happy to test any nightly build or patch. Thanks for the excellent library!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions