mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-10 17:24:02 +02:00
feat: sync selection to extactly matched DOM selection (#4157)
* feat: sync selection to extractly dom selection * revert mistakenly modified files * add changeset * toSlatePoint will throw error again when not extractMatch * Fix misspellings, rename extractMatch to exactMatch * rename option to match code style * Update four-poets-move.md Co-authored-by: Ian Storm Taylor <ian@ianstormtaylor.com>
This commit is contained in:
5
.changeset/four-poets-move.md
Normal file
5
.changeset/four-poets-move.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate-react': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed a bug when syncing the selection for IME-based editing.
|
@@ -175,21 +175,23 @@ export const Editable = (props: EditableProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the DOM selection is in the editor and the editor selection is already correct, we're done.
|
// If the DOM selection is in the editor and the editor selection is already correct, we're done.
|
||||||
if (
|
if (hasDomSelection && hasDomSelectionInEditor && selection) {
|
||||||
hasDomSelection &&
|
const slateRange = ReactEditor.toSlateRange(editor, domSelection, {
|
||||||
hasDomSelectionInEditor &&
|
exactMatch: true,
|
||||||
selection &&
|
})
|
||||||
Range.equals(ReactEditor.toSlateRange(editor, domSelection), selection)
|
if (slateRange && Range.equals(slateRange, selection)) {
|
||||||
) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// when <Editable/> is being controlled through external value
|
// when <Editable/> is being controlled through external value
|
||||||
// then its children might just change - DOM responds to it on its own
|
// then its children might just change - DOM responds to it on its own
|
||||||
// but Slate's value is not being updated through any operation
|
// but Slate's value is not being updated through any operation
|
||||||
// and thus it doesn't transform selection on its own
|
// and thus it doesn't transform selection on its own
|
||||||
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,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +282,9 @@ export const Editable = (props: EditableProps) => {
|
|||||||
const [targetRange] = (event as any).getTargetRanges()
|
const [targetRange] = (event as any).getTargetRanges()
|
||||||
|
|
||||||
if (targetRange) {
|
if (targetRange) {
|
||||||
const range = ReactEditor.toSlateRange(editor, targetRange)
|
const range = ReactEditor.toSlateRange(editor, targetRange, {
|
||||||
|
exactMatch: false,
|
||||||
|
})
|
||||||
|
|
||||||
if (!selection || !Range.equals(selection, range)) {
|
if (!selection || !Range.equals(selection, range)) {
|
||||||
Transforms.select(editor, range)
|
Transforms.select(editor, range)
|
||||||
@@ -444,7 +448,9 @@ export const Editable = (props: EditableProps) => {
|
|||||||
isTargetInsideVoid(editor, focusNode)
|
isTargetInsideVoid(editor, focusNode)
|
||||||
|
|
||||||
if (anchorNodeSelectable && focusNodeSelectable) {
|
if (anchorNodeSelectable && focusNodeSelectable) {
|
||||||
const range = ReactEditor.toSlateRange(editor, domSelection)
|
const range = ReactEditor.toSlateRange(editor, domSelection, {
|
||||||
|
exactMatch: false,
|
||||||
|
})
|
||||||
Transforms.select(editor, range)
|
Transforms.select(editor, range)
|
||||||
} else {
|
} else {
|
||||||
Transforms.deselect(editor)
|
Transforms.deselect(editor)
|
||||||
|
@@ -432,7 +432,9 @@ 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,
|
||||||
|
})
|
||||||
return range
|
return range
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -440,8 +442,14 @@ export const ReactEditor = {
|
|||||||
* Find a Slate point from a DOM selection's `domNode` and `domOffset`.
|
* Find a Slate point from a DOM selection's `domNode` and `domOffset`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
toSlatePoint(editor: ReactEditor, domPoint: DOMPoint): Point {
|
toSlatePoint<T extends boolean>(
|
||||||
const [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint)
|
editor: ReactEditor,
|
||||||
|
domPoint: DOMPoint,
|
||||||
|
extractMatch: T
|
||||||
|
): T extends true ? Point | null : Point {
|
||||||
|
const [nearestNode, nearestOffset] = extractMatch
|
||||||
|
? domPoint
|
||||||
|
: normalizeDOMPoint(domPoint)
|
||||||
const parentNode = nearestNode.parentNode as DOMElement
|
const parentNode = nearestNode.parentNode as DOMElement
|
||||||
let textNode: DOMElement | null = null
|
let textNode: DOMElement | null = null
|
||||||
let offset = 0
|
let offset = 0
|
||||||
@@ -513,6 +521,9 @@ export const ReactEditor = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!textNode) {
|
if (!textNode) {
|
||||||
|
if (extractMatch) {
|
||||||
|
return null as T extends true ? Point | null : Point
|
||||||
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot resolve a Slate point from DOM point: ${domPoint}`
|
`Cannot resolve a Slate point from DOM point: ${domPoint}`
|
||||||
)
|
)
|
||||||
@@ -523,17 +534,21 @@ export const ReactEditor = {
|
|||||||
// first, and then afterwards for the correct `element`. (2017/03/03)
|
// first, and then afterwards for the correct `element`. (2017/03/03)
|
||||||
const slateNode = ReactEditor.toSlateNode(editor, textNode!)
|
const slateNode = ReactEditor.toSlateNode(editor, textNode!)
|
||||||
const path = ReactEditor.findPath(editor, slateNode)
|
const path = ReactEditor.findPath(editor, slateNode)
|
||||||
return { path, offset }
|
return { path, offset } as T extends true ? Point | null : Point
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a Slate range from a DOM range or selection.
|
* Find a Slate range from a DOM range or selection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
toSlateRange(
|
toSlateRange<T extends boolean>(
|
||||||
editor: ReactEditor,
|
editor: ReactEditor,
|
||||||
domRange: DOMRange | DOMStaticRange | DOMSelection
|
domRange: DOMRange | DOMStaticRange | DOMSelection,
|
||||||
): Range {
|
options: {
|
||||||
|
exactMatch: T
|
||||||
|
}
|
||||||
|
): T extends true ? Range | null : Range {
|
||||||
|
const { exactMatch } = options
|
||||||
const el = isDOMSelection(domRange)
|
const el = isDOMSelection(domRange)
|
||||||
? domRange.anchorNode
|
? domRange.anchorNode
|
||||||
: domRange.startContainer
|
: domRange.startContainer
|
||||||
@@ -580,12 +595,25 @@ export const ReactEditor = {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset])
|
const anchor = ReactEditor.toSlatePoint(
|
||||||
|
editor,
|
||||||
|
[anchorNode, anchorOffset],
|
||||||
|
exactMatch
|
||||||
|
)
|
||||||
|
if (!anchor) {
|
||||||
|
return null as T extends true ? Range | null : Range
|
||||||
|
}
|
||||||
|
|
||||||
const focus = isCollapsed
|
const focus = isCollapsed
|
||||||
? anchor
|
? anchor
|
||||||
: ReactEditor.toSlatePoint(editor, [focusNode, focusOffset])
|
: ReactEditor.toSlatePoint(editor, [focusNode, focusOffset], exactMatch)
|
||||||
|
if (!focus) {
|
||||||
|
return null as T extends true ? Range | null : Range
|
||||||
|
}
|
||||||
|
|
||||||
return { anchor, focus }
|
return ({ anchor, focus } as unknown) as T extends true
|
||||||
|
? Range | null
|
||||||
|
: Range
|
||||||
},
|
},
|
||||||
|
|
||||||
hasRange(editor: ReactEditor, range: Range): boolean {
|
hasRange(editor: ReactEditor, range: Range): boolean {
|
||||||
|
Reference in New Issue
Block a user