Hacker News new | ask | show | jobs
by jitl 1466 days ago
How do you handle Android and multi-lingual input? Writing the state machine to deal with Chrome/Android’s bananas selection bugs in Notion’s editor gave me a lot of headaches. Do you add extra DOM nodes on Android? Any key insights that help deal with IME on Android?
1 comments

Android is indeed a headache to support. The biggest issue is how all keyboards force composition on all keystrokes, meaning you're constantly having to read the DOM input and work out what might have been entered. We don't use extra DOM nodes, but we do listen to `beforeinput`, `input` and use DOM MutationObserver to try and detect common Android patterns. I'd search this module for references to Android:

https://github.com/facebook/lexical/blob/main/packages/lexic...

> event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY

I tried so hard to avoid adding timeouts/timestamp logic for Android because it feels like a nondeterministic hack. In the end I gave up and did something similar.

I also found beforeInput doesn’t offer much solace on Android or with CJK language, because messing around with preventDefault or the DOM during composition disturbs the user.

One weird thing I never figured out is that if the user puts the selection in an empty block, GBoard wants to jump to the nearest word in a different paragraph. We do some brutal hacks to get around that :-/

I read your comment on ProseMirror forum about preventing interference from Granmarly, etc. Do you do this by reverting MutationRecords? Where is the code/docs for that?

Yeah, I really wanted to avoid them too. There doesn't seem a good way. The issue you describe with selection jumping is a well known issue with GBoard. Here's the logic for the mutation handling:

https://github.com/facebook/lexical/blob/main/packages/lexic...

https://github.com/facebook/lexical/blob/5802651c24d88b2f7d2...

> // Check for any random auto-added <br> elements, and remove them.

> // These get added by the browser when we undo the above mutations

> // and this can lead to a broken UI.

This made me laugh out loud. Browsers!

Until here our approach looked quite similar. Leave the observer connected during reverts and avoiding double-peocessing using .takeRecords never occurred to me, I always disconnect it. Maybe some of these brs are causing chaos…

Now thinking about it, I don't know why we didn't just do that. There must have been a reason, but I really can't recall what it was. If you're ever interested in contributing, this would be an epic contribution! :D
It's currently a bit broken on Android.

I select some text and then try to change the font size. The selection is lost when tapping on the font size menu.

ProseMirror does keep the selection intact.

It's probably because we're using a native select element, which likes to steal selection on Android.
ANDROID!!!!!