ruffle icon indicating copy to clipboard operation
ruffle copied to clipboard

web: Implement basic IME

Open kjarosh opened this issue 8 months ago • 19 comments

This patch implements IME preediting and committing on web. It does not implement moving the cursor and proper positioning yet.

  • Progresses https://github.com/ruffle-rs/ruffle/issues/1778

kjarosh avatar Mar 23 '25 11:03 kjarosh

This isn't working on Android when I try to type in an EditText with the virtual keyboard.

danielhjacobs avatar Mar 24 '25 18:03 danielhjacobs

A virtual keyboard like the one on Android isComposing.

danielhjacobs avatar Mar 24 '25 19:03 danielhjacobs

To explain the current logic, which I guess may need comments.

  1. If you type a single character using the virtual keyboard, that character fires a keydown and then keyup event into the focused EditText.
  2. If you backspace or delete a single character using the virtual keyboard, that backspace/delete fires a keydown and then keyup event into the focused EditText.
  3. If you paste a string using the virtual keyboard, each character in that string in sequence fires a keydown and then keyup event into the focused EditText.

danielhjacobs avatar Mar 24 '25 19:03 danielhjacobs

I'm guessing the IME logic can be used to support this more properly, but landing this PR without IME support would regress the virtual keyboard.

danielhjacobs avatar Mar 24 '25 19:03 danielhjacobs

Maybe we can do exactly this but also keydown and keyup the event.data character(s) on compositionend.

danielhjacobs avatar Mar 24 '25 20:03 danielhjacobs

@danielhjacobs can you check if the current code works properly? It does for me

kjarosh avatar Mar 29 '25 21:03 kjarosh

Tried with GBoard and it worked perfectly. With Samsung Keyboard it's unfortunately a different story, see recording.

https://github.com/user-attachments/assets/d3b00611-e2df-4131-8584-3868ea23ead2

danielhjacobs avatar Mar 29 '25 21:03 danielhjacobs

It seems that this keyboard uses IME for inputting all text. Without implementing IME on web we cannot have both IME preview and IME input working :/ This PR breaks IME preview, but fixes IME input.

kjarosh avatar Mar 29 '25 21:03 kjarosh

Okay, as IME on web is a mess, I've decided to implement basic IME mechanics (including preediting) on web.

@danielhjacobs @n0samu You can test it out here: https://kjarosh.github.io/ruffle/pr19896/

kjarosh avatar Mar 30 '25 11:03 kjarosh

As stated on Discord, but putting here for future people, this is now working with Samsung Keyboard.

danielhjacobs avatar Apr 01 '25 21:04 danielhjacobs

@jmousy Could you check if it works properly? It's available at https://kjarosh.github.io/ruffle/pr19896/

kjarosh avatar Apr 01 '25 22:04 kjarosh

This is a summary of the chat on Discord. Tested across OS and keyboard software on the same text and flash file.

  • '*' is cursor
  • 'Default' means using a regular hardware keyboard.
  • This is the result when you type "가나다" (rkskek, ㄱ + ㅏ + ㄴ + ㅏ + ㄷ + ㅏ).
  • Tested with the following flash files: 흥해라편의점.zip
  • Windows: Windows 10 24H2 & Google Chrome 135
  • macOS: macOS 15.3.2 & Google Chrome 135
  • iOS: iPhone 13 Pro iOS 18.4 & Safari
  • Android: Samsung Galaxy Z Fold 6 & Android 14 & One UI 6.1.1 & Samsung Internet
  • Linux: Ubuntu 24.04.2 LTS & Firefox latest (with VMware virtual machine)
OS Keyboard Type Result Other Issue
iOS Default ㄱㅏㄴㅏㄷㅏ*
iOS GBoard The text you entered appears in the following order and then disappears: ㄱ-가-간-{empty}-낟-{empty}
Android Samsung Keyboard 가나다*
Android GBoard 가나다* If you try to type "123가나다", it will be entered as "123ㄱㅏ나다".
Windows Default 가나다* If you click on the screen after entering text "가나다", an extra "다" is entered: "가나다다"
macOS Default ㄱ가나다*ㅏㄷㅏㄴㅏ The cursor will be positioned after "다" not the end. Also, if you type only "ㄱ", it will output "ㄱㄱ".
Linux Default 가나다*

jmousy avatar Apr 02 '25 12:04 jmousy

Below is the output from the link below, as requested by Daniel Jacobs on Discord: https://codepen.io/danieljacobs/pen/ByaMLpL

Input text: '가나다' (rkskek)

iOS 18.4 (default keyboard)
keydown - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㄱ, inputType: insertText, isComposing: N/A
keyup - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㄴ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㄴ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄴ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㄴ, inputType: insertText, isComposing: N/A
keyup - key: ㄴ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㄷ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㄷ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄷ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㄷ, inputType: insertText, isComposing: N/A
keyup - key: ㄷ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
Android 14 (samsung keyboard)
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 가, inputType: N/A, isComposing: N/A
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 나, inputType: N/A, isComposing: N/A
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A
Android (GBoard)
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가나, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가낟, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가나다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가나다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가나다, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 가나다, inputType: N/A, isComposing: N/A
Windows 11
keydown - key: Process, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: r, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: s, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 가, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: e, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 나, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertText, isComposing: N/A
input - key: N/A, data: 다, inputType: insertText, isComposing: N/A
Ubuntu 24.04
keydown - key: Process, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: r, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: s, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertText, isComposing: N/A
input - key: N/A, data: 가, inputType: insertText, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: e, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertText, isComposing: N/A
input - key: N/A, data: 나, inputType: insertText, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: N/A
macOS 15.4
keydown - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: ㄱ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㄴ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: ㄴ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 가, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㄷ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: ㄷ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 나, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A

jmousy avatar Apr 04 '25 02:04 jmousy

Explanation of issues:

iOS default keyboard Because we clear the input for each character typed, the characters do not combine, as the only input event that fires is an insertText for each individual character (all six) and there are no composition events or advanced text events. We'd need to properly handle the advanced text events anyway.
iOS GBoard Unknown issue as I don't know what events it fires. Probably relates at least partially to clearing the input for each character typed.
Android Samsung Keyboard No issue, every input event occurs during composition and the data at compositionend is correct, containing all the entered text.
Android GBoard For the initially mentioned input, all input events happen during composition and the data at compositionend is correct. I'd need to see the events that fire when entering 1 + 2 + 3 + ㄱ + ㅏ + ㄴ + ㅏ + ㄷ + ㅏ to know the issue there.
Windows default keyboard Because we clear the input for each character typed, the deleteContentBackward event that is supposed to fire before a final input event duplicates the last combined character does not fire. Even if it did, we don't handle deleteContentBackward.
macOS default keyboard Unsure, based on the listed events I would expect everything to work correctly. If you ignore the order, macOS seems to be typing everything twice.
Linux default keyboard No issue, there is a non-composing insertText input event containing 가 that occurs following a compositionend event with no data, a non-composing insertText input event containing 나 that occurs following a compositionend event with no data, and a compositionend event at the end containing 다 as data.

danielhjacobs avatar Apr 04 '25 16:04 danielhjacobs

I'm about 99.9% sure we need to stop clearing the hidden input for each character typed in the future.

danielhjacobs avatar Apr 24 '25 14:04 danielhjacobs

I have a theory for what Mac is doing wrong, and if I'm right I wonder if this would help:

this.virtualKeyboard.addEventListener("keydown", this.ignoreComposingKeyEvents.bind(this));
this.virtualKeyboard.addEventListener("keyup", this.ignoreComposingKeyEvents.bind(this));
ignoreComposingKeyEvents(event: KeyboardEvent) {
    if (event.isComposing) {
        event.preventDefault();
        event.stopPropagation();
        return;
    }
}

My theory is maybe Mac attempts to fire events for composing key events, causing the keyup/keydown to write text too.

danielhjacobs avatar Apr 24 '25 15:04 danielhjacobs

I'm about 99.9% sure we need to stop clearing the hidden input for each character typed in the future.

Me too. Moreover, I'm 97% sure we should just synchronize the text field with the HTML input and issue Flash events based on changes.

But that's a separate issue from IME, as IME in Flash behaves differently to normal input (and it gets underlined). Even with full text synchronization we should translate IME events to Flash.

kjarosh avatar Apr 24 '25 15:04 kjarosh

Note, the tsx files weren't being linted so it's entirely possible that when this gets rebased it ends up with lint failures.

danielhjacobs avatar May 21 '25 15:05 danielhjacobs

BTW I'm still trying to write a test for it, but the process is tedious. I'll try my best to include it here

kjarosh avatar May 22 '25 07:05 kjarosh