-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix editing with "soft keyboards" (eg. Android, IMEs) #2062
Comments
I've done some input tests in different browsers and devices using Dan Burzo's input tester. I think the only way we're going to arrive at proper Android support is to use a tool like this, and take extremely detailed recordings of inputing the exact same text across the different platforms. Otherwise the intricacies of the ordering of events, and when compositions do or do not start is going to be too complex to guess. I've started with a few, we'll need more. I think we'll need:
Even from just the ones I did, we can already see complex composition behaviors on Android that we're going to have to solve for:
I think we'll need to store additional information in these tests of what the current |
You are making a lot of progress here. I’ve committed next week to working on mobile support but by a large margin, you are more qualified to solve this than I. I think we might make more progress if you give me any menial tasks, research, etc. In order to help you. Don’t feel obligated to give me work but if you have any tasks, I will work on them full time for you starting Monday. Let me know. |
@thesunny thanks! One of the most helpful things would be getting more of those detailed event samplings across devices/browsers for each type of edit. That would allow anyone who works on this in the future to reference them to make sure they're thinking through the event logic correctly. Without those I think we'd just be fumbling around guessing what logic we need to add to the |
Okay, I will start with that Monday |
@ianstormtaylor Dan Burzo's input tester is exactly what I was looking for the other day to send you. Glad you found it/already had it. @thesunny and @ianstormtaylor how/where are you laying down code for this? I don't think there's much I can contribute in the way of code in the short term, but I'm happy to test ideas on my Android device and help any other way that you two need. |
@thesunny can you list the browser versions for the Android tests? I'm guessing Chrome 68? Also, we should agree on which field we're using in that input tester, either the I think it's reasonable to expect Slate to only have out of the box mobile support via React. This would simplify the task, I think. What do you think @ianstormtaylor ? I believe the only events we should be relying on, if we want the widest base of support on Android, are:
FYI when typing, each keystroke updates the composition, but choosing a suggestion ends the composition and creates the final word like this: compositionData: Barnac If you click Barnacles, the final word is constructed like this.
after this word, Google suggests the word, "the" - If you click this word, it is inserted like this:
Note, the above data is from the React contentEditable, but appears to be the same in the simple contentEditable. |
The browser version I believe is locked to the Android API version. I'm using whatever Chrome came with the specific Android version. I guess one thing where you can help is to look up which Chrome comes with which Android version. I'm using the I was considering writing a web App that would store all this information in a database so I wouldn't have to screenshot everything and we'd also have the raw data as JSON somewhere. I'm not sure how long that would take though so for now I'm just manually see if I can get this all done. I'm surprised how big the differences are between each Android version. |
@ianstormtaylor Just a heads up that I'm going to redo your Input Tests adding the multiple Android versions. Edit: Completed tests that don't require blocks as the Input Tester doesn't support them. Edit: I'm going to go back and add tests for Android API 27. It's a minor upgrade Android 8.0 - 8.1 but just to be safe. Edit: Also missed Android API 24 (which was mistakenly API 23). Fixing with an @v1.1 to identify that it's been fixed. Edit: Forked |
@thesunny these are absolutely amazing! Thank you! Having them for multiple versions of Android is a great idea. And using the non-React input is best too, since we need to know what exact DOM events are fired, regardless of how React chooses to interpret them. Because we'll probably need to bypass React's event handling logic in places, until they catch up. Another question for us to answer would be what are the usage levels of the different Android versions. Regardless, starting by trying to get things working with the latest versions that have Chrome's that fire |
@ianstormtaylor popularity of platform can be found here but unfortunately If we go back to API 23, we can capture 66.4%. The biggest version in distribution is API 23 which has 23.5% distribution. I'm just looking through some of the charts and looking for some patterns. In Inserting Text I think we may be able to get away with:
Some analysis I did on other input methods to see if this strategy holds:
It seems like the code might be written something like:
If we aren't in Android, we could theoretically do a Of course we'd still have to handle ENTER, BACKSPACE, etc. individually but we can probably make a short list of operator keys which is probably ENTER, BACKSPACE, SPACE, and PUNCTUATION. Usually we don't have Plugin actions from typing in Slate unless they are accompanied with one of these keys anyways. For example, a The plugins may need a different API though that exposes something like |
@thesunny great points all around. It sounds like one thing we need is a list of key presses that don't trigger compositions and instead use Do we need to worry about Fair enough for backwards compatibility. I think whoever implements it would likely want to start with API 28, since it's the most standardized. Or even just ignore Android quirks to start and focus purely on the |
I'll do some more research on what keys have special cases with respect to composition like That's an interesting idea about not worrying about In my opinion, we should focus on the My next goal is to see if I can create a sample Edit: A preliminary check just on Android 28 suggests that punctuation doesn't trigger |
OMG! I'm super excited! I created a working prototype. It's a simple React contenteditable editor (one text node in a div) and tested this on three Android versions (including the newest and oldest API versions) and so far it works perfectly!
It's generalized enough that it works on desktop with no code changes. I created a custom editor because I don't know Slate internals well enough yet but I tried to make it as close to Slate as possible.
It works like this:
I also track Here's a screenshot for now... I'll try and get this published tomorrow to get some feedback. There will probably be issues but it's promising so far! |
Here is where you can access the Live Composition Editor This is the GitHub Repo for Composition Editor If you type in it from most browsers, it is diffing on every keystroke to create the insert operations and then the Editor is being rendered. After render, I set the selection so the cursor doesn't get lost. If you use it from Android, when you start a composition via the I dump the Editor state for transparency which includes all the You can reset the editor to the default text and empty the @slapbox can you test this on your Android device? |
I think to implement this, we would have to change the Plugin event handler properties. Here's my recommendations.
We could have My suggestion is to replace the key handlers with a whitelist of specially named key handlers for all the cases we want to make sure Slate can handle. This is safer than having a catch all like Here's handlers I'd recommend to replace
I will look into punctuation but I'm not hopeful that we can rely on these being 100% handled outside compositions because of contractions like |
@thesunny I think it might be best to separate the work of "getting Android working" from "getting the plugin system fully adapted for compositions". Because the latter is going to be more work, and require thinking lots of different API considerations through. As for how events flow through the editor...
That's the current flow. There's a bit of confusing indirection around how the As for how to handle compositions in plugins. I don't think we need to deprecate I'd rather avoid having |
@ianstormtaylor Thanks for the feedback. Just working my way through everything now. |
Here is my execution plan for now
Testing IME
Gesture Writing
Regular Writing
Suggestions
Auto-correct
Physical Keyboard
|
@thesunny is there anything in particular I can be of help testing? I checked out what you've put together and it looks very useful. All input and output is rendered to the editor as expected. One note, not a problem of any sort; I'm surprised by how backspacing is handled. Backspacing away the last character of a completed word fires a single |
Thanks for working on this. Please do test with Firefox on Android too. We are looking to update Firefox's behavior (see discussion in w3c/uievents#202) and would appreciate your feedback. |
@birtles You're welcome. I'll test out Firefox once I get Android Chrome working. As a quick update, I have many things working properly now although I'm not using the diffing algorithm yet and just the replace algorithm that currently exists. It's pretty much working except there are issues with I will add the special cases we need to test for in the execution plan above. |
@thesunny can you give an example of a situation where |
@thesunny: Great to hear that using MutationObserver is working out so well! 👍 With Draft (which now uses MutationObserver for all composition-based input, not just on Android) I have also seen good results. Just one question though: when you say that "everything works" on Android 8.1 / 9, what exactly do you mean? In my experience, whether composition-based input works on Android is not just limited to the API version, but also varies depending on:
|
To clarify I only meant that all my manual tests pass on Android Chrome on Gboard with the exception of continuous backspace. I’m sure there are more bugs which is why I’m asking for bug reports. My tests do include gesture writing, foreign IME but not some of the others; however if they work as compositions then they theoretically should work. I did nothing extra for IME or gestures for example but they both work. The initial goal is to get the most common use case for Android at an MVP (mostly works and no unrecoverable crashes) then build on there. I could see a switch to using only MutationObserver being a path for Slate and I even had Desktop Chrome working on MutationObserver while developing the Android version; however that’s probably a decision ultimately for Ian. |
Made the PR for Android support using mutations here |
I believe that this may be fixed by #3093, which has changed a lot of the logic in Slate and |
Hey, where can I find code example for this demo https://thesunny.github.io/slate/#/composition/split-join or any other example that works well on Android? This is the only demo I found that works fine on my Android device |
@fainir likely somewhere in here: https://github.com/thesunny/slate/ Sorry I can't be more helpful with an exact location. |
Tested on my android device and and it has the same bugs as this demo https://www.slatejs.org/examples/richtext. The only demo I found that works well on my device is https://thesunny.github.io/slate/#/composition/split-join that mentioned few comments above by @thesunny. Does anyone has the code for this demo? Why isn't working on the main demo? It is a huge bug and @thesunny fixed it so I think it should be implemented inside the package and the main demo. |
@fainir you can find out more about that situation here #3573 The code is in that repo, but may not be at the tip of the master branch. It looks like the master is on Basically @thesunny did a ton of work to get it working on |
Thank you very much. I downgraded to 0.47.9 and it works :) |
@slapbox Thank you for this amazing project. Is there a roadmap to add support for android devices in 0.5.x? Thanks! |
@vasanthps just to clarify @ianstormtaylor is the amazing person who leads the Slate project. There's no official roadmap to add support for Android devices and it's not planned right now, hence #3573. |
@thesunny @ianstormtaylor I am using |
Android uses mutations and not the |
Thanks @thesunny |
Sorry, I may have been confusing your query. For clarity, the |
@thesunny I'm running into the same issue with Slate Tested on a couple of different Android versions using BrowserStack (Android 10, 9, 8) and as far as I can tell the |
It's been a while but this may be due to how Android decides when a composition ends which is when changes are committed. It is possible for a composition to span multiple locations. Android is the only browser that does this. Selection deletes always cause a change to happen I believe. But deleting/backspacing within an element won't. But backspacing across a boundary (ie. between two spans) will. The reasons for this have to do with a balance between performance and consistency. If we reconcile too often, we can have consistency issues because we are trying to reconcile while android is still executing mutations. We have an escape hatch where when things get so far out of sync that React crashes, that we have an error boundary that just rebuilds everything from scratch. But that's jarring so we don't want to see that often. It's better than a fatal crash though. I'm not working on that code anymore but if you want to take a shot at fixing it, a possible solution or improvement could be to identify when there is a change in selection even if it's in the middle of a composition and then reconcile the browser DOM with the Slate DOM. If you do take a crack at it, make sure to test against all the test cases in the demo that I wrote specifically for compositions or else you may end up putting the editor in a worse state than it is now. |
Hey @thesunny, I'd be happy to look into that if that was the only thing that was broken, but unless I'm missing something it seems like the Slate value is never updated on Android, and therefore the As mentioned above, the only working scenario I have seen is with an expanded selection and pressing backspace, this will indeed trigger an I'm trying to see if this could be a regression at some point before |
I went as far back as this commit locally and I still was seeing the same behaviour 7d4062c Maybe I'm missing something? |
It was working at some point so it may be a regression but it's also possibly a change in Android which may be causing the issue. I haven't looked at it since about a year ago so I don't really know what the state is. I'm pretty certain it was working at some point though as some people were relying on it. Also, a lot of the timing issues that I had to deal with had to do with how React re-renders during editing so those issues wouldn't have popped up if onChange never got called. Also try clicking outside the editor (ie. lose focus) to see if at least that calls |
Is there an open issue tracking this I can monitor? Latest version is not playing well with GBoard |
I still see this issue with
Selecting all the text and deleting it triggers a crash |
Never before has there been such a team! Nice job @thesunny @ianstormtaylor. I don't actually use the library but stumbled across this while trying to support Android/IoS for my own ContentEditable stuff and it's been a massive help. |
I'll link this issue: udecode/plate#1347 because it seems to be related And what about https://github.com/danburzo/input-methods? |
Do you want to request a feature or report a bug?
Bug.
What's the current behavior?
The issue with using Slate on Android is complex, and due to a difference in how its OS keyboard is designed. On mobile devices, keyboards are starting to move away from the "keys" concepts in many ways...
Because of this, it sounds like the Android team (reasonably from their point of view) decided to not reliably fire key-related events.
It sounds like the behavior is:
keypress
event is never triggered, which is fine for us.keydown
event but always with a key code of229
, indicating that the key is unidentifiable because the keyboard is still busy processing IME input, which may invalidate the actual key pressed.keydown
event as normal, with anevent.key
that can be recognized? (This is unclear whether this happens or not.)keydown
event.A few different resources:
What's the expected behavior?
The fix for this is also complicated. There are a handful of different, overlapping pieces of logic that need to change, to accommodate a handful of different input types...
The first stage is to handle basic insertions, and auto-suggestions...
preventDefault
inonBeforeInput
, so that the DOM is updated, and theonInput
logic will trigger, diffing the insertion and then "fixing" it.<Leaf>
(and other?) components to increment theirkey
such that React properly unmounts and reconciles the DOM, since it has changed out from under it.This is actually the same starting steps as is required for #2060, so I'd recommend we solve that issue in its entirety first, to work from a solid base.
This fixes the actual text insertion pieces, and probably deletions as well. Splitting blocks can still be handled by enter because it still provide proper key codes.
keydown
events.And then there's some selection issues, which apparently can mess up Android's IME (and potentially others) if the selection is manually changed during a composition.
compositionstart
andcompositionend
.I think this would solve the 90% case for soft keyboard input.
Separately, there's still another question of how to properly handle these kinds of behaviors other plugins. For example if a plugin uses backspace at the start of a block to reset that block, that won't work on Android. So after solving the input issues, we need to step back to an architectural level and solve this plugin handling problem. But that can wait.
The text was updated successfully, but these errors were encountered: