1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-16 20:24:01 +02:00

Add a Point model, and standardize Range/Point logic (#2035)

* add `Node.createRange` for range resolution

* fix lint

* fix range offset defaults to be `null`

* change `isBackward` to be computed from paths

* remove debuggers

* add point model, update range with deprecations, update hyperscript

* got all tests passing

* get tests passing, convert changes

* fix lint

* fix examples

* update deprecations

* update docs

* update slate-react point utils

* fix document.normalizeRange

* fix lint
This commit is contained in:
Ian Storm Taylor
2018-08-03 14:45:40 -07:00
committed by GitHub
parent b3535e11df
commit 08f270dc1b
95 changed files with 4199 additions and 1902 deletions

View File

@@ -376,7 +376,7 @@ class Content extends React.Component {
editor.change(change => {
if (change.value.isInVoid) {
change.collapseToStartOfNextText()
change.moveToStartOfNextText()
} else {
change.splitBlockAtRange(range)
}

View File

@@ -109,20 +109,20 @@ class Text extends React.Component {
const { key } = node
const decs = decorations.filter(d => {
const { startKey, endKey, startPath, endPath } = d
const { start, end } = d
// If either of the decoration's keys match, include it.
if (startKey === key || endKey === key) return true
if (start.key === key || end.key === key) return true
// Otherwise, if the decoration is in a single node, it's not ours.
if (startKey === endKey) return false
if (start.key === end.key) return false
// If the node's path is before the start path, ignore it.
const path = document.assertPathByKey(key)
if (PathUtils.compare(path, startPath) === -1) return false
if (PathUtils.compare(path, start.path) === -1) return false
// If the node's path is after the end path, ignore it.
if (PathUtils.compare(path, endPath) === 1) return false
if (PathUtils.compare(path, end.path) === 1) return false
// Otherwise, include it.
return true

View File

@@ -85,7 +85,7 @@ function AfterPlugin() {
// an inline node will be automatically replaced to be at the last offset
// of a previous inline node, which screws us up, so we always want to set
// it to the end of the node. (2016/11/29)
change.focus().collapseToEndOf(node)
change.focus().moveToEndOfNode(node)
}
debug('onClick', { event })
@@ -125,7 +125,8 @@ function AfterPlugin() {
// If user cuts a void block node or a void inline node,
// manually removes it since selection is collapsed in this case.
const { value } = change
const { endBlock, endInline, isCollapsed } = value
const { endBlock, endInline, selection } = value
const { isCollapsed } = selection
const isVoidBlock = endBlock && endBlock.isVoid && isCollapsed
const isVoidInline = endInline && endInline.isVoid && isCollapsed
@@ -219,13 +220,13 @@ function AfterPlugin() {
// needs to account for the selection's content being deleted.
if (
isDraggingInternally &&
selection.endKey == target.endKey &&
selection.endOffset < target.endOffset
selection.end.key == target.end.key &&
selection.end.offset < target.end.offset
) {
target = target.move(
selection.startKey == selection.endKey
? 0 - selection.endOffset + selection.startOffset
: 0 - selection.endOffset
selection.start.key == selection.end.key
? 0 - selection.end.offset + selection.start.offset
: 0 - selection.end.offset
)
}
@@ -236,11 +237,11 @@ function AfterPlugin() {
change.select(target)
if (type == 'text' || type == 'html') {
const { anchorKey } = target
let hasVoidParent = document.hasVoidParent(anchorKey)
const { anchor } = target
let hasVoidParent = document.hasVoidParent(anchor.key)
if (hasVoidParent) {
let n = document.getNode(anchorKey)
let n = document.getNode(anchor.key)
while (hasVoidParent) {
n = document.getNextText(n.key)
@@ -248,7 +249,7 @@ function AfterPlugin() {
hasVoidParent = document.hasVoidParent(n.key)
}
if (n) change.collapseToStartOf(n)
if (n) change.moveToStartOfNode(n)
}
if (text) {
@@ -275,7 +276,7 @@ function AfterPlugin() {
// has fired in a node: https://github.com/facebook/react/issues/11379.
// Until this is fixed in React, we dispatch a mouseup event on that
// DOM node, since that will make it go back to normal.
const focusNode = document.getNode(target.focusKey)
const focusNode = document.getNode(target.focus.key)
const el = findDOMNode(focusNode, window)
if (!el) return
@@ -343,12 +344,12 @@ function AfterPlugin() {
// Determine what the selection should be after changing the text.
const delta = textContent.length - text.length
const corrected = selection.collapseToEnd().move(delta)
const corrected = selection.moveToEnd().moveForward(delta)
let entire = selection
.moveAnchorTo(point.key, start)
.moveFocusTo(point.key, end)
entire = document.normalizeRange(entire)
entire = document.resolveRange(entire)
// Change the current value to have the leaf's text replaced.
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
@@ -372,7 +373,7 @@ function AfterPlugin() {
// preserve native autocorrect behavior, so they shouldn't be handled here.
if (Hotkeys.isSplitBlock(event) && !IS_IOS) {
return value.isInVoid
? change.collapseToStartOfNextText()
? change.moveToStartOfNextText()
: change.splitBlock()
}
@@ -413,22 +414,22 @@ function AfterPlugin() {
// selection isn't properly collapsed. (2017/10/17)
if (Hotkeys.isCollapseLineBackward(event)) {
event.preventDefault()
return change.collapseLineBackward()
return change.moveToStartOfBlock()
}
if (Hotkeys.isCollapseLineForward(event)) {
event.preventDefault()
return change.collapseLineForward()
return change.moveToEndOfBlock()
}
if (Hotkeys.isExtendLineBackward(event)) {
event.preventDefault()
return change.extendLineBackward()
return change.moveFocusToStartOfBlock()
}
if (Hotkeys.isExtendLineForward(event)) {
event.preventDefault()
return change.extendLineForward()
return change.moveFocusToEndOfBlock()
}
// COMPAT: If a void node is selected, or a zero-width text node adjacent to
@@ -441,7 +442,7 @@ function AfterPlugin() {
if (isInVoid || isPreviousInVoid || startText.text == '') {
event.preventDefault()
return change.collapseCharBackward()
return change.moveBackward()
}
}
@@ -451,7 +452,7 @@ function AfterPlugin() {
if (isInVoid || isNextInVoid || startText.text == '') {
event.preventDefault()
return change.collapseCharForward()
return change.moveForward()
}
}
@@ -462,7 +463,7 @@ function AfterPlugin() {
if (isInVoid || isPreviousInVoid || startText.text == '') {
event.preventDefault()
return change.extendCharBackward()
return change.moveFocusBackward()
}
}
@@ -472,7 +473,7 @@ function AfterPlugin() {
if (isInVoid || isNextInVoid || startText.text == '') {
event.preventDefault()
return change.extendCharForward()
return change.moveFocusForward()
}
}
}
@@ -535,13 +536,13 @@ function AfterPlugin() {
let range = findRange(native, value)
if (!range) return
const { anchorKey, anchorOffset, focusKey, focusOffset } = range
const anchorText = document.getNode(anchorKey)
const focusText = document.getNode(focusKey)
const anchorInline = document.getClosestInline(anchorKey)
const focusInline = document.getClosestInline(focusKey)
const focusBlock = document.getClosestBlock(focusKey)
const anchorBlock = document.getClosestBlock(anchorKey)
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
@@ -553,12 +554,12 @@ function AfterPlugin() {
if (
anchorBlock &&
!anchorBlock.isVoid &&
anchorOffset == 0 &&
anchor.offset == 0 &&
focusBlock &&
focusBlock.isVoid &&
focusOffset != 0
focus.offset != 0
) {
range = range.set('focusOffset', 0)
range = range.setFocus(focus.setOffset(0))
}
// COMPAT: If the selection is at the end of a non-void inline node, and
@@ -567,24 +568,24 @@ function AfterPlugin() {
if (
anchorInline &&
!anchorInline.isVoid &&
anchorOffset == anchorText.text.length
anchor.offset == anchorText.text.length
) {
const block = document.getClosestBlock(anchorKey)
const next = block.getNextText(anchorKey)
const block = document.getClosestBlock(anchor.key)
const next = block.getNextText(anchor.key)
if (next) range = range.moveAnchorTo(next.key, 0)
}
if (
focusInline &&
!focusInline.isVoid &&
focusOffset == focusText.text.length
focus.offset == focusText.text.length
) {
const block = document.getClosestBlock(focusKey)
const next = block.getNextText(focusKey)
const block = document.getClosestBlock(focus.key)
const next = block.getNextText(focus.key)
if (next) range = range.moveFocusTo(next.key, 0)
}
range = document.normalizeRange(range)
range = document.resolveRange(range)
change.select(range)
}

View File

@@ -19,9 +19,9 @@ const { FRAGMENT, HTML, TEXT } = TRANSFER_TYPES
function cloneFragment(event, value, fragment = value.fragment) {
const window = getWindow(event.target)
const native = window.getSelection()
const { startKey, endKey } = value
const startVoid = value.document.getClosestVoid(startKey)
const endVoid = value.document.getClosestVoid(endKey)
const { start, end } = value.selection
const startVoid = value.document.getClosestVoid(start.key)
const endVoid = value.document.getClosestVoid(end.key)
// If the selection is collapsed, and it isn't inside a void node, abort.
if (native.isCollapsed && !startVoid) return

View File

@@ -1,16 +1,15 @@
import findDOMNode from './find-dom-node'
/**
* Find a native DOM selection point from a Slate `key` and `offset`.
* Find a native DOM selection point from a Slate `point`.
*
* @param {String} key
* @param {Number} offset
* @param {Point} point
* @param {Window} win (optional)
* @return {Object|Null}
*/
function findDOMPoint(key, offset, win = window) {
const el = findDOMNode(key, win)
function findDOMPoint(point, win = window) {
const el = findDOMNode(point.key, win)
let start = 0
let n
@@ -27,8 +26,8 @@ function findDOMPoint(key, offset, win = window) {
const { length } = n.textContent
const end = start + length
if (offset <= end) {
const o = offset - start
if (point.offset <= end) {
const o = point.offset - start
return { node: n, offset: o >= 0 ? o : 0 }
}

View File

@@ -9,21 +9,15 @@ import findDOMPoint from './find-dom-point'
*/
function findDOMRange(range, win = window) {
const {
anchorKey,
anchorOffset,
focusKey,
focusOffset,
isBackward,
isCollapsed,
} = range
const anchor = findDOMPoint(anchorKey, anchorOffset, win)
const focus = isCollapsed ? anchor : findDOMPoint(focusKey, focusOffset, win)
if (!anchor || !focus) return null
const { anchor, focus, isBackward, isCollapsed } = range
const domAnchor = findDOMPoint(anchor, win)
const domFocus = isCollapsed ? domAnchor : findDOMPoint(focus, win)
if (!domAnchor || !domFocus) return null
const r = win.document.createRange()
const start = isBackward ? focus : anchor
const end = isBackward ? anchor : focus
const start = isBackward ? domFocus : domAnchor
const end = isBackward ? domAnchor : domFocus
r.setStart(start.node, start.offset)
r.setEnd(end.node, end.offset)
return r

View File

@@ -21,7 +21,7 @@ const VOID_SELECTOR = '[data-slate-void]'
* @param {Element} nativeNode
* @param {Number} nativeOffset
* @param {Value} value
* @return {Object}
* @return {Point}
*/
function findPoint(nativeNode, nativeOffset, value) {
@@ -78,10 +78,8 @@ function findPoint(nativeNode, nativeOffset, value) {
// then afterwards for the correct `element`. (2017/03/03)
if (!value.document.hasDescendant(key)) return null
return {
key,
offset,
}
const point = value.document.createPoint({ key, offset })
return point
}
/**

View File

@@ -48,8 +48,8 @@ function findRange(native, value) {
// last word of a span, it sets the endContainer to the containing span.
// `selection-is-backward` doesn't handle this case.
if (IS_IE || IS_EDGE) {
const domAnchor = findDOMPoint(anchor.key, anchor.offset)
const domFocus = findDOMPoint(focus.key, focus.offset)
const domAnchor = findDOMPoint(anchor)
const domFocus = findDOMPoint(focus)
native = {
anchorNode: domAnchor.node,
@@ -61,10 +61,8 @@ function findRange(native, value) {
const { document } = value
const range = document.createRange({
anchorKey: anchor.key,
anchorOffset: anchor.offset,
focusKey: focus.key,
focusOffset: focus.offset,
anchor,
focus,
isBackward: isCollapsed ? false : isBackward(native),
isFocused: true,
})

View File

@@ -80,7 +80,7 @@ function orderChildDecorations(node, decorations) {
// Range start.
// A rangeStart should be before the child containing its startKey, in order
// to consider it active before going down the child.
const startKeyOrder = keyOrders[decoration.startKey]
const startKeyOrder = keyOrders[decoration.start.key]
const containingChildOrder =
startKeyOrder === undefined
? 0
@@ -93,7 +93,7 @@ function orderChildDecorations(node, decorations) {
})
// Range end.
const endKeyOrder = (keyOrders[decoration.endKey] || globalOrder) + 0.5
const endKeyOrder = (keyOrders[decoration.end.key] || globalOrder) + 0.5
endPoints.push({
isRangeEnd: true,

View File

@@ -7,10 +7,14 @@ function decorateNode(block) {
const text = block.getFirstText()
return [
{
anchorKey: text.key,
anchorOffset: 1,
focusKey: text.key,
focusOffset: 2,
anchor: {
key: text.key,
offset: 1,
},
focus: {
key: text.key,
offset: 2,
},
marks: [{ type: 'bold' }],
},
]