mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-16 20:24:01 +02:00
Refactor to utils (#2522)
* Refactored set-selection-from-dom into utils as prep for Android support * Added and refactored to use set-text-from-dom-node with improved set selection after input
This commit is contained in:
@@ -8,11 +8,12 @@ import { IS_IOS } from 'slate-dev-environment'
|
||||
import cloneFragment from '../utils/clone-fragment'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findNode from '../utils/find-node'
|
||||
import findPoint from '../utils/find-point'
|
||||
import findRange from '../utils/find-range'
|
||||
import getEventRange from '../utils/get-event-range'
|
||||
import getEventTransfer from '../utils/get-event-transfer'
|
||||
import setEventTransfer from '../utils/set-event-transfer'
|
||||
import setSelectionFromDom from '../utils/set-selection-from-dom'
|
||||
import setTextFromDomNode from '../utils/set-text-from-dom-node'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
@@ -409,62 +410,15 @@ function AfterPlugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onInput(event, editor, next) {
|
||||
debug('onInput')
|
||||
const window = getWindow(event.target)
|
||||
const { value } = editor
|
||||
|
||||
// Get the selection point.
|
||||
const native = window.getSelection()
|
||||
const { anchorNode } = native
|
||||
const point = findPoint(anchorNode, 0, editor)
|
||||
if (!point) return next()
|
||||
const selection = window.getSelection()
|
||||
const { anchorNode } = selection
|
||||
|
||||
// Get the text node and leaf in question.
|
||||
const { document, selection } = value
|
||||
const node = document.getDescendant(point.key)
|
||||
const block = document.getClosestBlock(node.key)
|
||||
const leaves = node.getLeaves()
|
||||
const lastText = block.getLastText()
|
||||
const lastLeaf = leaves.last()
|
||||
let start = 0
|
||||
let end = 0
|
||||
|
||||
const leaf =
|
||||
leaves.find(r => {
|
||||
start = end
|
||||
end += r.text.length
|
||||
if (end > point.offset) return true
|
||||
}) || lastLeaf
|
||||
|
||||
// Get the text information.
|
||||
const { text } = leaf
|
||||
let { textContent } = anchorNode
|
||||
const isLastText = node == lastText
|
||||
const isLastLeaf = leaf == lastLeaf
|
||||
const lastChar = textContent.charAt(textContent.length - 1)
|
||||
|
||||
// COMPAT: If this is the last leaf, and the DOM text ends in a new line,
|
||||
// we will have added another new line in <Leaf>'s render method to account
|
||||
// for browsers collapsing a single trailing new lines, so remove it.
|
||||
if (isLastText && isLastLeaf && lastChar == '\n') {
|
||||
textContent = textContent.slice(0, -1)
|
||||
}
|
||||
|
||||
// If the text is no different, abort.
|
||||
if (textContent == text) return next()
|
||||
|
||||
debug('onInput', { event })
|
||||
|
||||
// Determine what the selection should be after changing the text.
|
||||
const delta = textContent.length - text.length
|
||||
const corrected = selection.moveToEnd().moveForward(delta)
|
||||
let entire = selection
|
||||
.moveAnchorTo(point.key, start)
|
||||
.moveFocusTo(point.key, end)
|
||||
|
||||
entire = document.resolveRange(entire)
|
||||
|
||||
// Change the current value to have the leaf's text replaced.
|
||||
editor.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
|
||||
setTextFromDomNode(window, editor, anchorNode)
|
||||
setSelectionFromDom(window, editor, selection)
|
||||
next()
|
||||
}
|
||||
|
||||
@@ -677,82 +631,9 @@ function AfterPlugin(options = {}) {
|
||||
|
||||
function onSelect(event, editor, next) {
|
||||
debug('onSelect', { event })
|
||||
|
||||
const window = getWindow(event.target)
|
||||
const { value } = editor
|
||||
const { document } = value
|
||||
const native = window.getSelection()
|
||||
|
||||
// If there are no ranges, the editor was blurred natively.
|
||||
if (!native.rangeCount) {
|
||||
editor.blur()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, determine the Slate selection from the native one.
|
||||
let range = findRange(native, editor)
|
||||
|
||||
if (!range) {
|
||||
return
|
||||
}
|
||||
|
||||
const { anchor, focus } = range
|
||||
const anchorText = document.getNode(anchor.key)
|
||||
const focusText = document.getNode(focus.key)
|
||||
const anchorInline = document.getClosestInline(anchor.key)
|
||||
const focusInline = document.getClosestInline(focus.key)
|
||||
const focusBlock = document.getClosestBlock(focus.key)
|
||||
const anchorBlock = document.getClosestBlock(anchor.key)
|
||||
|
||||
// COMPAT: If the anchor point is at the start of a non-void, and the
|
||||
// focus point is inside a void node with an offset that isn't `0`, set
|
||||
// the focus offset to `0`. This is due to void nodes <span>'s being
|
||||
// positioned off screen, resulting in the offset always being greater
|
||||
// than `0`. Since we can't know what it really should be, and since an
|
||||
// offset of `0` is less destructive because it creates a hanging
|
||||
// selection, go with `0`. (2017/09/07)
|
||||
if (
|
||||
anchorBlock &&
|
||||
!editor.isVoid(anchorBlock) &&
|
||||
anchor.offset == 0 &&
|
||||
focusBlock &&
|
||||
editor.isVoid(focusBlock) &&
|
||||
focus.offset != 0
|
||||
) {
|
||||
range = range.setFocus(focus.setOffset(0))
|
||||
}
|
||||
|
||||
// COMPAT: If the selection is at the end of a non-void inline node, and
|
||||
// there is a node after it, put it in the node after instead. This
|
||||
// standardizes the behavior, since it's indistinguishable to the user.
|
||||
if (
|
||||
anchorInline &&
|
||||
!editor.isVoid(anchorInline) &&
|
||||
anchor.offset == anchorText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(anchor.key)
|
||||
const nextText = block.getNextText(anchor.key)
|
||||
if (nextText) range = range.moveAnchorTo(nextText.key, 0)
|
||||
}
|
||||
|
||||
if (
|
||||
focusInline &&
|
||||
!editor.isVoid(focusInline) &&
|
||||
focus.offset == focusText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(focus.key)
|
||||
const nextText = block.getNextText(focus.key)
|
||||
if (nextText) range = range.moveFocusTo(nextText.key, 0)
|
||||
}
|
||||
|
||||
let selection = document.createSelection(range)
|
||||
selection = selection.setIsFocused(true)
|
||||
|
||||
// Preserve active marks from the current selection.
|
||||
// They will be cleared by `editor.select` if the selection actually moved.
|
||||
selection = selection.set('marks', value.selection.marks)
|
||||
|
||||
editor.select(selection)
|
||||
const selection = window.getSelection()
|
||||
setSelectionFromDom(window, editor, selection)
|
||||
next()
|
||||
}
|
||||
|
||||
|
77
packages/slate-react/src/utils/set-selection-from-dom.js
Normal file
77
packages/slate-react/src/utils/set-selection-from-dom.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import findRange from './find-range'
|
||||
|
||||
export default function setSelectionFromDOM(window, editor, domSelection) {
|
||||
const { value } = editor
|
||||
const { document } = value
|
||||
|
||||
// If there are no ranges, the editor was blurred natively.
|
||||
if (!domSelection.rangeCount) {
|
||||
editor.blur()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, determine the Slate selection from the native one.
|
||||
let range = findRange(domSelection, editor)
|
||||
|
||||
if (!range) {
|
||||
return
|
||||
}
|
||||
|
||||
const { anchor, focus } = range
|
||||
const anchorText = document.getNode(anchor.key)
|
||||
const focusText = document.getNode(focus.key)
|
||||
const anchorInline = document.getClosestInline(anchor.key)
|
||||
const focusInline = document.getClosestInline(focus.key)
|
||||
const focusBlock = document.getClosestBlock(focus.key)
|
||||
const anchorBlock = document.getClosestBlock(anchor.key)
|
||||
|
||||
// COMPAT: If the anchor point is at the start of a non-void, and the
|
||||
// focus point is inside a void node with an offset that isn't `0`, set
|
||||
// the focus offset to `0`. This is due to void nodes <span>'s being
|
||||
// positioned off screen, resulting in the offset always being greater
|
||||
// than `0`. Since we can't know what it really should be, and since an
|
||||
// offset of `0` is less destructive because it creates a hanging
|
||||
// selection, go with `0`. (2017/09/07)
|
||||
if (
|
||||
anchorBlock &&
|
||||
!editor.isVoid(anchorBlock) &&
|
||||
anchor.offset == 0 &&
|
||||
focusBlock &&
|
||||
editor.isVoid(focusBlock) &&
|
||||
focus.offset != 0
|
||||
) {
|
||||
range = range.setFocus(focus.setOffset(0))
|
||||
}
|
||||
|
||||
// COMPAT: If the selection is at the end of a non-void inline node, and
|
||||
// there is a node after it, put it in the node after instead. This
|
||||
// standardizes the behavior, since it's indistinguishable to the user.
|
||||
if (
|
||||
anchorInline &&
|
||||
!editor.isVoid(anchorInline) &&
|
||||
anchor.offset == anchorText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(anchor.key)
|
||||
const nextText = block.getNextText(anchor.key)
|
||||
if (nextText) range = range.moveAnchorTo(nextText.key, 0)
|
||||
}
|
||||
|
||||
if (
|
||||
focusInline &&
|
||||
!editor.isVoid(focusInline) &&
|
||||
focus.offset == focusText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(focus.key)
|
||||
const nextText = block.getNextText(focus.key)
|
||||
if (nextText) range = range.moveFocusTo(nextText.key, 0)
|
||||
}
|
||||
|
||||
let selection = document.createSelection(range)
|
||||
selection = selection.setIsFocused(true)
|
||||
|
||||
// Preserve active marks from the current selection.
|
||||
// They will be cleared by `editor.select` if the selection actually moved.
|
||||
selection = selection.set('marks', value.selection.marks)
|
||||
|
||||
editor.select(selection)
|
||||
}
|
53
packages/slate-react/src/utils/set-text-from-dom-node.js
Normal file
53
packages/slate-react/src/utils/set-text-from-dom-node.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import findPoint from './find-point'
|
||||
|
||||
export default function setTextFromDomNode(window, editor, domNode) {
|
||||
const point = findPoint(domNode, 0, editor)
|
||||
if (!point) return
|
||||
|
||||
// Get the text node and leaf in question.
|
||||
const { value } = editor
|
||||
const { document, selection } = value
|
||||
const node = document.getDescendant(point.key)
|
||||
const block = document.getClosestBlock(node.key)
|
||||
const leaves = node.getLeaves()
|
||||
const lastText = block.getLastText()
|
||||
const lastLeaf = leaves.last()
|
||||
let start = 0
|
||||
let end = 0
|
||||
|
||||
const leaf =
|
||||
leaves.find(r => {
|
||||
start = end
|
||||
end += r.text.length
|
||||
if (end > point.offset) return true
|
||||
}) || lastLeaf
|
||||
|
||||
// Get the text information.
|
||||
const { text } = leaf
|
||||
let { textContent } = domNode
|
||||
const isLastText = node == lastText
|
||||
const isLastLeaf = leaf == lastLeaf
|
||||
const lastChar = textContent.charAt(textContent.length - 1)
|
||||
|
||||
// COMPAT: If this is the last leaf, and the DOM text ends in a new line,
|
||||
// we will have added another new line in <Leaf>'s render method to account
|
||||
// for browsers collapsing a single trailing new lines, so remove it.
|
||||
if (isLastText && isLastLeaf && lastChar == '\n') {
|
||||
textContent = textContent.slice(0, -1)
|
||||
}
|
||||
|
||||
// If the text is no different, abort.
|
||||
if (textContent == text) return
|
||||
|
||||
// Determine what the selection should be after changing the text.
|
||||
// const delta = textContent.length - text.length
|
||||
// const corrected = selection.moveToEnd().moveForward(delta)
|
||||
let entire = selection
|
||||
.moveAnchorTo(point.key, start)
|
||||
.moveFocusTo(point.key, end)
|
||||
|
||||
entire = document.resolveRange(entire)
|
||||
|
||||
// Change the current value to have the leaf's text replaced.
|
||||
editor.insertTextAtRange(entire, textContent, leaf.marks)
|
||||
}
|
Reference in New Issue
Block a user