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:
5
.changeset/shy-brooms-notice.md
Normal file
5
.changeset/shy-brooms-notice.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate-react': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed bug: setting selection from `contentEditable:false` element causes crash
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user