Fix Slate crash after inserting emoji from picker/autocomplete
This commit is contained in:
@@ -82,13 +82,21 @@ function RenderEmoticonElement({
|
||||
const selected = useSelected();
|
||||
const focused = useFocused();
|
||||
|
||||
// Void inline: attributes + contentEditable={false} on the same root, children
|
||||
// as a sibling of the visual (not nested inside another non-editable span).
|
||||
// Nesting {children} under an inner contentEditable={false} breaks Slate's
|
||||
// DOM↔node map and crashes ReactEditor.focus after insert.
|
||||
return (
|
||||
<span className={css.EmoticonBase} {...attributes}>
|
||||
<span
|
||||
{...attributes}
|
||||
contentEditable={false}
|
||||
className={css.EmoticonBase}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<span
|
||||
className={css.Emoticon({
|
||||
focus: selected && focused,
|
||||
})}
|
||||
contentEditable={false}
|
||||
>
|
||||
{element.key.startsWith('mxc://') ? (
|
||||
<img
|
||||
@@ -99,8 +107,8 @@ function RenderEmoticonElement({
|
||||
) : (
|
||||
element.key
|
||||
)}
|
||||
{children}
|
||||
</span>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BasePoint, BaseRange, Editor, Element, Point, Range, Text, Transforms } from 'slate';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import { BlockType, MarkType } from './types';
|
||||
import {
|
||||
CommandElement,
|
||||
@@ -206,6 +207,29 @@ export const moveCursor = (editor: Editor, withSpace?: boolean) => {
|
||||
if (withSpace) editor.insertText(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the editor after React has committed DOM for recent transforms.
|
||||
* Calling ReactEditor.focus immediately after insertNode/insertText (e.g. emoji +
|
||||
* trailing space) races Slate's DOM map and throws:
|
||||
* "Cannot resolve a DOM node from Slate node: {"text":" "}"
|
||||
*/
|
||||
export const safeFocusEditor = (editor: Editor) => {
|
||||
const tryFocus = () => {
|
||||
try {
|
||||
ReactEditor.focus(editor);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Defer past the current React commit; retry once if the DOM map isn't ready yet.
|
||||
requestAnimationFrame(() => {
|
||||
if (tryFocus()) return;
|
||||
setTimeout(tryFocus, 0);
|
||||
});
|
||||
};
|
||||
|
||||
interface PointUntilCharOptions {
|
||||
match: (char: string) => boolean;
|
||||
reverse?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user