Skip to content

Commit

Permalink
feat: sync selection to extactly matched DOM selection (ianstormtaylo…
Browse files Browse the repository at this point in the history
…r#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>
  • Loading branch information
2 people authored and beorn committed Apr 13, 2021
1 parent 59c5adf commit 6ce60e1
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-poets-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'slate-react': patch
---

Fixed a bug when syncing the selection for IME-based editing.
26 changes: 16 additions & 10 deletions packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
hasDomSelection &&
hasDomSelectionInEditor &&
selection &&
Range.equals(ReactEditor.toSlateRange(editor, domSelection), selection)
) {
return
if (hasDomSelection && hasDomSelectionInEditor && selection) {
const slateRange = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: true,
})
if (slateRange && Range.equals(slateRange, selection)) {
return
}
}

// when <Editable/> is being controlled through external value
// then its children might just change - DOM responds to it on its own
// but Slate's value is not being updated through any operation
// and thus it doesn't transform selection on its own
if (selection && !ReactEditor.hasRange(editor, selection)) {
editor.selection = ReactEditor.toSlateRange(editor, domSelection)
editor.selection = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false,
})
return
}

Expand Down Expand Up @@ -280,7 +282,9 @@ export const Editable = (props: EditableProps) => {
const [targetRange] = (event as any).getTargetRanges()

if (targetRange) {
const range = ReactEditor.toSlateRange(editor, targetRange)
const range = ReactEditor.toSlateRange(editor, targetRange, {
exactMatch: false,
})

if (!selection || !Range.equals(selection, range)) {
Transforms.select(editor, range)
Expand Down Expand Up @@ -444,7 +448,9 @@ export const Editable = (props: EditableProps) => {
isTargetInsideVoid(editor, focusNode)

if (anchorNodeSelectable && focusNodeSelectable) {
const range = ReactEditor.toSlateRange(editor, domSelection)
const range = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false,
})
Transforms.select(editor, range)
} else {
Transforms.deselect(editor)
Expand Down
48 changes: 38 additions & 10 deletions packages/slate-react/src/plugin/react-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,16 +432,24 @@ export const ReactEditor = {
}

// Resolve a Slate range from the DOM range.
const range = ReactEditor.toSlateRange(editor, domRange)
const range = ReactEditor.toSlateRange(editor, domRange, {
exactMatch: false,
})
return range
},

/**
* Find a Slate point from a DOM selection's `domNode` and `domOffset`.
*/

toSlatePoint(editor: ReactEditor, domPoint: DOMPoint): Point {
const [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint)
toSlatePoint<T extends boolean>(
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
let textNode: DOMElement | null = null
let offset = 0
Expand Down Expand Up @@ -513,6 +521,9 @@ export const ReactEditor = {
}

if (!textNode) {
if (extractMatch) {
return null as T extends true ? Point | null : Point
}
throw new Error(
`Cannot resolve a Slate point from DOM point: ${domPoint}`
)
Expand All @@ -523,17 +534,21 @@ export const ReactEditor = {
// first, and then afterwards for the correct `element`. (2017/03/03)
const slateNode = ReactEditor.toSlateNode(editor, textNode!)
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.
*/

toSlateRange(
toSlateRange<T extends boolean>(
editor: ReactEditor,
domRange: DOMRange | DOMStaticRange | DOMSelection
): Range {
domRange: DOMRange | DOMStaticRange | DOMSelection,
options: {
exactMatch: T
}
): T extends true ? Range | null : Range {
const { exactMatch } = options
const el = isDOMSelection(domRange)
? domRange.anchorNode
: domRange.startContainer
Expand Down Expand Up @@ -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
? 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 {
Expand Down

0 comments on commit 6ce60e1

Please sign in to comment.