1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-26 16:44:22 +02:00

Rewrite drag&drop code related to void nodes. (#1193)

* Remove `onDragEnter`, `onDragLeave` and `onDrop` `Void` handlers.
Add `getDropPoint` to `utils`.

* Resolved caret position can be inside a void node even if the DOM
drop target isn't inside it. So resolve first the caret position,
then, if necessary, adjust the Slate drop target.
This commit is contained in:
AlbertHilb
2017-10-13 19:34:56 +02:00
committed by Ian Storm Taylor
parent 79562f51de
commit 671c5857eb
3 changed files with 104 additions and 74 deletions

View File

@@ -15,6 +15,7 @@ import findClosestNode from '../utils/find-closest-node'
import getCaretPosition from '../utils/get-caret-position' import getCaretPosition from '../utils/get-caret-position'
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste' import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
import getPoint from '../utils/get-point' import getPoint from '../utils/get-point'
import getDropPoint from '../utils/get-drop-point'
import getTransferData from '../utils/get-transfer-data' import getTransferData from '../utils/get-transfer-data'
import setTransferData from '../utils/set-transfer-data' import setTransferData from '../utils/set-transfer-data'
import scrollToSelection from '../utils/scroll-to-selection' import scrollToSelection from '../utils/scroll-to-selection'
@@ -369,7 +370,7 @@ class Content extends React.Component {
*/ */
onDragEnd = (event) => { onDragEnd = (event) => {
if (!this.isInEditor(event.target)) return event.stopPropagation()
this.tmp.isDragging = false this.tmp.isDragging = false
this.tmp.isInternalDrag = null this.tmp.isInternalDrag = null
@@ -384,7 +385,8 @@ class Content extends React.Component {
*/ */
onDragOver = (event) => { onDragOver = (event) => {
if (!this.isInEditor(event.target)) return event.stopPropagation()
if (this.tmp.isDragging) return if (this.tmp.isDragging) return
this.tmp.isDragging = true this.tmp.isDragging = true
this.tmp.isInternalDrag = false this.tmp.isInternalDrag = false
@@ -425,33 +427,21 @@ class Content extends React.Component {
*/ */
onDrop = (event) => { onDrop = (event) => {
event.stopPropagation()
event.preventDefault() event.preventDefault()
if (this.props.readOnly) return if (this.props.readOnly) return
if (!this.isInEditor(event.target)) return
const window = getWindow(event.target) const { editor, state } = this.props
const { state, editor } = this.props
const { nativeEvent } = event const { nativeEvent } = event
const { dataTransfer, x, y } = nativeEvent const { dataTransfer } = nativeEvent
const data = getTransferData(dataTransfer) const data = getTransferData(dataTransfer)
const point = getDropPoint(event, state, editor)
// Resolve the point where the drop occured.
let range
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
if (window.document.caretRangeFromPoint) {
range = window.document.caretRangeFromPoint(x, y)
} else {
range = window.document.createRange()
range.setStart(nativeEvent.rangeParent, nativeEvent.rangeOffset)
}
const { startContainer, startOffset } = range
const point = getPoint(startContainer, startOffset, state, editor)
if (!point) return if (!point) return
const target = Selection.create({ // Add drop-specific information to the data.
data.target = Selection.create({
anchorKey: point.key, anchorKey: point.key,
anchorOffset: point.offset, anchorOffset: point.offset,
focusKey: point.key, focusKey: point.key,
@@ -459,9 +449,6 @@ class Content extends React.Component {
isFocused: true isFocused: true
}) })
// Add drop-specific information to the data.
data.target = target
// COMPAT: Edge throws "Permission denied" errors when // COMPAT: Edge throws "Permission denied" errors when
// accessing `dropEffect` or `effectAllowed` (2017/7/12) // accessing `dropEffect` or `effectAllowed` (2017/7/12)
try { try {

View File

@@ -41,17 +41,6 @@ class Void extends React.Component {
state: SlateTypes.state.isRequired, state: SlateTypes.state.isRequired,
} }
/**
* State
*
* @type {Object}
*/
state = {
dragCounter: 0,
editable: false,
}
/** /**
* Debug. * Debug.
* *
@@ -90,44 +79,9 @@ class Void extends React.Component {
}) })
} }
/** onDragOver = event => event.preventDefault()
* Increment counter, and temporarily switch node to editable to allow drop events
* Counter required as onDragLeave fires when hovering over child elements
*
* @param {Event} event
*/
onDragEnter = () => { onDragEnter = event => event.preventDefault()
this.setState((prevState) => {
const dragCounter = prevState.dragCounter + 1
return { dragCounter, editable: undefined }
})
}
/**
* Decrement counter, and if counter 0, then no longer dragging over node
* and thus switch back to non-editable
*
* @param {Event} event
*/
onDragLeave = () => {
this.setState((prevState) => {
const dragCounter = prevState.dragCounter - 1
const editable = dragCounter === 0 ? false : undefined
return { dragCounter, editable }
})
}
/**
* If dropped item onto node, then reset state
*
* @param {Event} event
*/
onDrop = () => {
this.setState({ dragCounter: 0, editable: false })
}
/** /**
* Render. * Render.
@@ -145,13 +99,13 @@ class Void extends React.Component {
return ( return (
<Tag <Tag
data-slate-void data-slate-void
data-key={node.key}
onClick={this.onClick} onClick={this.onClick}
onDragOver={this.onDragOver}
onDragEnter={this.onDragEnter} onDragEnter={this.onDragEnter}
onDragLeave={this.onDragLeave}
onDrop={this.onDrop}
> >
{this.renderSpacer()} {this.renderSpacer()}
<Tag contentEditable={this.state.editable}> <Tag contentEditable={false}>
{children} {children}
</Tag> </Tag>
</Tag> </Tag>

View File

@@ -0,0 +1,89 @@
import getWindow from 'get-window'
import findClosestNode from './find-closest-node'
import getPoint from './get-point'
/**
* Get the target point for a drop event.
*
* @param {Event} event
* @param {State} state
* @param {Editor} editor
* @return {Object}
*/
function getDropPoint(event, state, editor) {
const { document } = state
const { nativeEvent, target } = event
const { x, y } = nativeEvent
// Resolve the caret position where the drop occured.
const window = getWindow(target)
let n, o
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
if (window.document.caretRangeFromPoint) {
const range = window.document.caretRangeFromPoint(x, y)
n = range.startContainer
o = range.startOffset
} else {
const position = window.document.caretPositionFromPoint(x, y)
n = position.offsetNode
o = position.offset
}
const nodeEl = findClosestNode(n, '[data-key]')
const nodeKey = nodeEl.getAttribute('data-key')
const node = document.key === nodeKey ? document : document.getDescendant(nodeKey)
// If the drop DOM target is inside an inline void node use last position of
// the previous sibling text node or first position of the next sibling text
// node as Slate target.
if (node.isVoid && node.kind === 'inline') {
const rect = nodeEl.getBoundingClientRect()
const previous = x - rect.left < rect.left + rect.width - x
const text = previous ?
document.getPreviousSibling(nodeKey) :
document.getNextSibling(nodeKey)
const key = text.key
const offset = previous ? text.characters.size : 0
return { key, offset }
}
// If the drop DOM target is inside a block void node use last position of
// the previous sibling block node or first position of the next sibling block
// node as Slate target.
if (node.isVoid) {
const rect = nodeEl.getBoundingClientRect()
const previous = y - rect.top < rect.top + rect.height - y
if (previous) {
const block = document.getPreviousBlock(nodeKey)
const text = block.getLastText()
const { key } = text
const offset = text.characters.size
return { key, offset }
}
const block = document.getNextBlock(nodeKey)
const text = block.getLastText()
const { key } = text
const offset = 0
return { key, offset }
}
const point = getPoint(n, o, state, editor)
return point
}
/**
* Export.
*
* @type {Function}
*/
export default getDropPoint