1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-09 08:46:35 +02:00

Fix bug: setting selection from contentEditable:false element causes crash (#4584)

* Fix bug: setting selection from contentEditable:false element causes crash

Fixes https://github.com/ianstormtaylor/slate/issues/4583

When clicking some text in a `contentEditable:false` element, if the
handler for this sets the selection, Slate crashes. Slate tries to find
a Slate range for the text that was clicked on, but there is no such
range, because the text is inside a `contentEditable:false` element.
Slate seems to be making a bad assumption that the current DOM selection
necessarily corresponds to a Slate range, but this is not the case if
the user just clicked into an element with `contentEditable: false`.
To fix this, I changed `exactMatch: false` to `exactMatch: true`,
which seems to mean "fail gracefully if there is no exact match".

* changeset

* Revert "Fix bug: setting selection from contentEditable:false element causes crash"

This reverts commit 71234284cd.

* Unconflate exactMatch flag: add suppressThrow flag for separate behavior

* Fix bug: setting selection from contentEditable:false element causes crash

Fixes #4583

When clicking some text in a `contentEditable:false` element, if the
handler for this sets the selection, Slate crashes. Slate tries to find
a Slate range for the text that was clicked on, but there is no such
range, because the text is inside a `contentEditable:false` element.
Slate seems to be making a bad assumption that the current DOM selection
necessarily corresponds to a Slate range, but this is not the case if
the user just clicked into an element with `contentEditable: false`.
This commit is contained in:
Jim Fisher
2021-10-13 19:45:00 +01:00
committed by GitHub
parent 43e740c88d
commit f40e515dc7
4 changed files with 29 additions and 5 deletions

View File

@@ -0,0 +1,5 @@
---
'slate-react': patch
---
Fixed bug: setting selection from `contentEditable:false` element causes crash

View File

@@ -124,6 +124,7 @@ export const AndroidEditable = (props: EditableProps): JSX.Element => {
if (hasDomSelection && hasDomSelectionInEditor && selection) { if (hasDomSelection && hasDomSelectionInEditor && selection) {
const slateRange = ReactEditor.toSlateRange(editor, domSelection, { const slateRange = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: true, exactMatch: true,
suppressThrow: true,
}) })
if (slateRange && Range.equals(slateRange, selection)) { if (slateRange && Range.equals(slateRange, selection)) {
return return
@@ -137,6 +138,7 @@ export const AndroidEditable = (props: EditableProps): JSX.Element => {
if (selection && !ReactEditor.hasRange(editor, selection)) { if (selection && !ReactEditor.hasRange(editor, selection)) {
editor.selection = ReactEditor.toSlateRange(editor, domSelection, { editor.selection = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false, exactMatch: false,
suppressThrow: false,
}) })
return return
} }
@@ -266,6 +268,7 @@ export const AndroidEditable = (props: EditableProps): JSX.Element => {
if (anchorNodeSelectable && focusNodeSelectable) { if (anchorNodeSelectable && focusNodeSelectable) {
const range = ReactEditor.toSlateRange(editor, domSelection, { const range = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false, exactMatch: false,
suppressThrow: false,
}) })
Transforms.select(editor, range) Transforms.select(editor, range)
} else { } else {

View File

@@ -183,6 +183,10 @@ export const Editable = (props: EditableProps) => {
if (hasDomSelection && hasDomSelectionInEditor && selection) { if (hasDomSelection && hasDomSelectionInEditor && selection) {
const slateRange = ReactEditor.toSlateRange(editor, domSelection, { const slateRange = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false, exactMatch: false,
// domSelection is not necessarily a valid Slate range
// (e.g. when clicking on contentEditable:false element)
suppressThrow: true,
}) })
if (slateRange && Range.equals(slateRange, selection)) { if (slateRange && Range.equals(slateRange, selection)) {
return return
@@ -196,6 +200,7 @@ export const Editable = (props: EditableProps) => {
if (selection && !ReactEditor.hasRange(editor, selection)) { if (selection && !ReactEditor.hasRange(editor, selection)) {
editor.selection = ReactEditor.toSlateRange(editor, domSelection, { editor.selection = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false, exactMatch: false,
suppressThrow: false,
}) })
return return
} }
@@ -323,6 +328,7 @@ export const Editable = (props: EditableProps) => {
if (targetRange) { if (targetRange) {
const range = ReactEditor.toSlateRange(editor, targetRange, { const range = ReactEditor.toSlateRange(editor, targetRange, {
exactMatch: false, exactMatch: false,
suppressThrow: false,
}) })
if (!selection || !Range.equals(selection, range)) { if (!selection || !Range.equals(selection, range)) {
@@ -503,6 +509,7 @@ export const Editable = (props: EditableProps) => {
if (anchorNodeSelectable && focusNodeSelectable) { if (anchorNodeSelectable && focusNodeSelectable) {
const range = ReactEditor.toSlateRange(editor, domSelection, { const range = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false, exactMatch: false,
suppressThrow: false,
}) })
Transforms.select(editor, range) Transforms.select(editor, range)
} }

View File

@@ -430,6 +430,7 @@ export const ReactEditor = {
// Resolve a Slate range from the DOM range. // Resolve a Slate range from the DOM range.
const range = ReactEditor.toSlateRange(editor, domRange, { const range = ReactEditor.toSlateRange(editor, domRange, {
exactMatch: false, exactMatch: false,
suppressThrow: false,
}) })
return range return range
}, },
@@ -441,8 +442,12 @@ export const ReactEditor = {
toSlatePoint<T extends boolean>( toSlatePoint<T extends boolean>(
editor: ReactEditor, editor: ReactEditor,
domPoint: DOMPoint, domPoint: DOMPoint,
exactMatch: T options: {
exactMatch: T
suppressThrow: T
}
): T extends true ? Point | null : Point { ): T extends true ? Point | null : Point {
const { exactMatch, suppressThrow } = options
const [nearestNode, nearestOffset] = exactMatch const [nearestNode, nearestOffset] = exactMatch
? domPoint ? domPoint
: normalizeDOMPoint(domPoint) : normalizeDOMPoint(domPoint)
@@ -522,7 +527,7 @@ export const ReactEditor = {
} }
if (!textNode) { if (!textNode) {
if (exactMatch) { if (suppressThrow) {
return null as T extends true ? Point | null : Point return null as T extends true ? Point | null : Point
} }
throw new Error( throw new Error(
@@ -547,9 +552,10 @@ export const ReactEditor = {
domRange: DOMRange | DOMStaticRange | DOMSelection, domRange: DOMRange | DOMStaticRange | DOMSelection,
options: { options: {
exactMatch: T exactMatch: T
suppressThrow: T
} }
): T extends true ? Range | null : Range { ): T extends true ? Range | null : Range {
const { exactMatch } = options const { exactMatch, suppressThrow } = options
const el = isDOMSelection(domRange) const el = isDOMSelection(domRange)
? domRange.anchorNode ? domRange.anchorNode
: domRange.startContainer : domRange.startContainer
@@ -599,7 +605,7 @@ export const ReactEditor = {
const anchor = ReactEditor.toSlatePoint( const anchor = ReactEditor.toSlatePoint(
editor, editor,
[anchorNode, anchorOffset], [anchorNode, anchorOffset],
exactMatch { exactMatch, suppressThrow }
) )
if (!anchor) { if (!anchor) {
return null as T extends true ? Range | null : Range return null as T extends true ? Range | null : Range
@@ -607,7 +613,10 @@ export const ReactEditor = {
const focus = isCollapsed const focus = isCollapsed
? anchor ? anchor
: ReactEditor.toSlatePoint(editor, [focusNode, focusOffset], exactMatch) : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset], {
exactMatch,
suppressThrow,
})
if (!focus) { if (!focus) {
return null as T extends true ? Range | null : Range return null as T extends true ? Range | null : Range
} }