diff --git a/.changeset/twelve-mails-warn.md b/.changeset/twelve-mails-warn.md new file mode 100644 index 0000000000..89ae3dd424 --- /dev/null +++ b/.changeset/twelve-mails-warn.md @@ -0,0 +1,5 @@ +--- +'slate-react': patch +--- + +Suppressed error throwing on selection processing for externally updated DOM content diff --git a/packages/slate-react/src/components/editable.tsx b/packages/slate-react/src/components/editable.tsx index b2cae4d98a..cf4269a120 100644 --- a/packages/slate-react/src/components/editable.tsx +++ b/packages/slate-react/src/components/editable.tsx @@ -384,7 +384,13 @@ export const Editable = (props: EditableProps) => { state.isUpdatingSelection = true const newDomRange: DOMRange | null = - selection && ReactEditor.toDOMRange(editor, selection) + selection && + ReactEditor.toDOMRange(editor, selection, { + // Even when we have a selection in Slate, the DOM might have moved + // underneath us, so we have to check that the anchor and focus nodes + // still exist. + suppressThrow: true, + }) if (newDomRange) { if (ReactEditor.isComposing(editor) && !IS_ANDROID) { diff --git a/packages/slate-react/src/plugin/react-editor.ts b/packages/slate-react/src/plugin/react-editor.ts index 422b1e816d..4aaae0d35e 100644 --- a/packages/slate-react/src/plugin/react-editor.ts +++ b/packages/slate-react/src/plugin/react-editor.ts @@ -217,7 +217,11 @@ export interface ReactEditorInterface { /** * Find a native DOM selection point from a Slate point. */ - toDOMPoint: (editor: ReactEditor, point: Point) => DOMPoint + toDOMPoint: ( + editor: ReactEditor, + point: Point, + options?: { suppressThrow: T } + ) => T extends true ? DOMPoint | null : DOMPoint /** * Find a native DOM range from a Slate `range`. @@ -227,7 +231,11 @@ export interface ReactEditorInterface { * there is no way to create a reverse DOM Range using Range.setStart/setEnd * according to https://dom.spec.whatwg.org/#concept-range-bp-set. */ - toDOMRange: (editor: ReactEditor, range: Range) => DOMRange + toDOMRange: ( + editor: ReactEditor, + range: Range, + options?: { suppressThrow: T } + ) => T extends true ? DOMRange | null : DOMRange /** * Find a Slate node from a native DOM `element`. @@ -558,7 +566,11 @@ export const ReactEditor: ReactEditorInterface = { return domNode }, - toDOMPoint: (editor, point) => { + toDOMPoint: ( + editor: ReactEditor, + point: Point, + options?: { suppressThrow: T } + ) => { const [node] = Editor.node(editor, point.path) const el = ReactEditor.toDOMNode(editor, node) let domPoint: DOMPoint | undefined @@ -620,6 +632,10 @@ export const ReactEditor: ReactEditorInterface = { } if (!domPoint) { + const suppressThrow = options?.suppressThrow + if (suppressThrow === true) { + return null as T extends true ? DOMPoint | null : DOMPoint + } throw new Error( `Cannot resolve a DOM point from Slate point: ${Scrubber.stringify( point @@ -630,13 +646,38 @@ export const ReactEditor: ReactEditorInterface = { return domPoint }, - toDOMRange: (editor, range) => { + toDOMRange: ( + editor: ReactEditor, + range: Range, + options?: { suppressThrow: T } + ) => { const { anchor, focus } = range const isBackward = Range.isBackward(range) - const domAnchor = ReactEditor.toDOMPoint(editor, anchor) + const domAnchor = ReactEditor.toDOMPoint(editor, anchor, options) + if (!domAnchor) { + if (options?.suppressThrow) { + return null as T extends true ? DOMRange | null : DOMRange + } + throw new Error( + `Cannot resolve a DOM range from Slate range: ${Scrubber.stringify( + range + )}` + ) + } + const domFocus = Range.isCollapsed(range) ? domAnchor - : ReactEditor.toDOMPoint(editor, focus) + : ReactEditor.toDOMPoint(editor, focus, options) + if (!domFocus) { + if (options?.suppressThrow) { + return null as T extends true ? DOMRange | null : DOMRange + } + throw new Error( + `Cannot resolve a DOM range from Slate range: ${Scrubber.stringify( + range + )}` + ) + } const window = ReactEditor.getWindow(editor) const domRange = window.document.createRange()