-
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
defer native events within Editable to avoid bugs with Editor #4605
defer native events within Editable to avoid bugs with Editor #4605
Conversation
🦋 Changeset detectedLatest commit: a8c431e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the initial couple of discussions started on slate 0.4x, and the implementation was ported to slate 0.50+ at which point some of the changes stopped being relevant, maybe blocking operations was one of them. Anyway, the original PR i believe contained some ports that should not apply to slate 0.50+.
On thing i can't figure out right now is if this forced re-rendering is necessary - https://github.com/ianstormtaylor/slate/blob/main/packages/slate-react/src/components/string.tsx#L61 as it came as part of #3888. I've removed it and had less remounts, but curious if anyone else can take a look.
Couple of suggestions added - capture the op and apply it later - closes onInput
for further changes if new types of native ops are added (removes the switch basically).
Trying to replicate the issues with latest main branch:
Old behavior showing wrong undo behavior: With the last commit Side question: |
I have done a quick test with react 17 and main branch behave the same on my env. For the spellcheck squigglies, it only squigglies when I type a whitespace. Is this expected? |
Yes, the idea is to not show spellcheck errors until you've had a chance to finish typing a word which matches typical behavior. |
For whatever reason spellcheck in slate works on the entire "element" as opposed to the word you just finished. I have been trying to figure this one out and have not been able to - the default behavior of the spellchecker is to show squigglies whenever a word is finished, as you can test in a vanilla contenteditable. I wasn't sure if it's react doing this, so i double checked by looking at the form example and the answer is no - squigglies still have the same behavior. Then i removed this guy (not sure how to call it) to make sure it isn't the re-mounts, and it isn't that either. Any ideas would be appreciated. |
…type Co-authored-by: Nemanja Tosic <[email protected]>
@jaked I have further tried this PR in various cases and found the following issues:
|
@jaked Did a clean build, and now undo/redo works great with this PR. Sorry for the confusion here. Regarding my 2nd point for the issue reported on slack https://slate-js.slack.com/archives/C1RH7AXSS/p1634146964395500, I guess this is out of scope of this PR. Just wanted to give it a try to see if it would bring changes. |
I think it covers the case where the native event changes an element that the Slate operation does not change, so without the forced re-rendering the element changed by the native event is not updated. This leads to double-character bugs, see discussion towards the end of #4445. It's easy to trigger with decorations, probably possible to trigger other ways. It should only cause a remount when the DOM text doesn't match the Slate text. |
Description
In #3888 some
insertText
events are allowed to run their default native behavior, in order to avoid re-rendering DOM nodes (re-rendering defeats some browser behaviors—in particular, showing spellchecking squigglies only when typing has stopped). The corresponding SlateinsertText
operation is deferred until theonInput
handler; after the Slate operation runs, the DOM already matches the new Slate state, so React does not re-render the node.In the original #3888 code, this is achieved by setting a global flag
AS_NATIVE
inonDOMBeforeInput
, then runningeditor.insertText
. ThewithReact
plugin overrideseditor.apply
to add Slate ops to a queue whenAS_NATIVE
is set; then this queue is flushed in theonInput
handler. However, this interacts badly with other plugins that expect ops to be applied immediately (see #4530, #4541).To achieve the desired result, it's not necessary to defer individual Slate ops; it's enough to defer the whole
insertText
call.This can be done entirely in
Editable
, without involving theEditor
implementation, so that's what this change does. Note that it doesn't try to bail out wheninsertText
is overridden (as #3888 does ineditor.apply
), but I think this is fine—in that case a re-render is triggered, so the effect of the native event is overwritten.(I guess there are possible overrides of
insertText
where this is not true—one which skips some inserts, or re-renders a different part of the tree. If this is a valid use case then maybe a deeper change is needed.)Issue
Fixes: #3888 #4530 #4541
Example
Old behavior showing spellcheck squigglies (current code with
native
alwaysfalse
):New behavior showing proper spellcheck squigglies
Old behavior showing wrong undo behavior:
New behavior showing correct undo behavior:
Context
I am not totally sure why it's necessary to defer the Slate operation. If my explanation above about how #3888 works is correct, then it shouldn't really be necessary—the React redraw is already deferred with
ReactDOM.unstable_batchedUpdates
in theonChange
fromwithReact
.I think maybe it's necessary to wait foronDOMSelectionChange
to fire in order to update Slate's view of the selection to match the changed selection due to the native operation, or else the Slate operation can fail because the selection is wrong. So waiting untilonInput
givesonDOMSelectionChange
a chance to run first. I haven't investigated this theory however.On second thought this theory doesn't make sense—we want the Slate selection to be in the original spot before running the Slate operation. Still, if we don't
preventDefault
and also don't defer the Slate operation, we get aCannot resolve a DOM point from Slate point
, so somehow the selections are out of sync.Checks
yarn test
.yarn lint
. (Fix errors withyarn fix
.)yarn start
.)yarn changeset add
.)