diff --git a/docs/reference/slate-react/utils.md b/docs/reference/slate-react/utils.md
index 43f864139..e6625d4a3 100644
--- a/docs/reference/slate-react/utils.md
+++ b/docs/reference/slate-react/utils.md
@@ -9,6 +9,7 @@ import {
findRange,
getEventRange,
getEventTransfer,
+ setEventTransfer,
} from 'slate-react'
```
@@ -41,9 +42,33 @@ Find the Slate range from a DOM `range` or `selection` and a Slate `state`.
### `getEventRange`
`getEventRange(event: DOMEvent, state: State) => Range`
-Find the affected Slate range from a DOM `event` and Slate `state`.
+Get the affected Slate range from a DOM `event` and Slate `state`.
### `getEventTransfer`
`getEventTransfer(event: DOMEvent) => Object`
-Find the Slate-related data from a DOM `event` and Slate `state`.
+Get the Slate-related data from a DOM `event` and Slate `state`.
+
+```js
+function onDrop(event, change, editor) {
+ const transfer = getEventTransfer(event)
+ const { type, node } = transfer
+
+ if (type == 'node') {
+ // Do something with `node`...
+ }
+}
+```
+
+### `setEventTransfer`
+`setEventTransfer(event: DOMEvent, type: String, data: Any)`
+
+Sets the Slate-related `data` with `type` on an `event`. The `type` must be one of the types Slate recognizes: `'fragment'`, `'html'`, `'node'`, `'rich'`, or `'text'`.
+
+```js
+function onDragStart(event, change, editor) {
+ const { state } = change
+ const { startNode } = state
+ setEventTransfer(event, 'node', startNode)
+}
+```
diff --git a/examples/check-lists/index.js b/examples/check-lists/index.js
index c1e1008ad..df81d579e 100644
--- a/examples/check-lists/index.js
+++ b/examples/check-lists/index.js
@@ -16,11 +16,11 @@ class CheckListItem extends React.Component {
/**
* On change, set the new checked value on the block.
*
- * @param {Event} e
+ * @param {Event} event
*/
- onChange = (e) => {
- const checked = e.target.checked
+ onChange = (event) => {
+ const checked = event.target.checked
const { editor, node } = this.props
editor.change(c => c.setNodeByKey(node.key, { data: { checked }}))
}
@@ -106,32 +106,30 @@ class CheckLists extends React.Component {
* If backspace is pressed when collapsed at the start of a check list item,
* then turn it back into a paragraph.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
* @return {State|Void}
*/
- onKeyDown = (e, data, change) => {
+ onKeyDown = (event, change) => {
const { state } = change
if (
- e.key == 'Enter' &&
+ event.key == 'Enter' &&
state.startBlock.type == 'check-list-item'
) {
- return change
- .splitBlock()
- .setBlock({ data: { checked: false }})
+ change.splitBlock().setBlock({ data: { checked: false }})
+ return true
}
if (
- e.key == 'Backspace' &&
+ event.key == 'Backspace' &&
state.isCollapsed &&
state.startBlock.type == 'check-list-item' &&
state.selection.startOffset == 0
) {
- return change
- .setBlock('paragraph')
+ change.setBlock('paragraph')
+ return true
}
}
diff --git a/examples/code-highlighting/index.js b/examples/code-highlighting/index.js
index aff2ce2fb..f196d2abb 100644
--- a/examples/code-highlighting/index.js
+++ b/examples/code-highlighting/index.js
@@ -17,8 +17,8 @@ function CodeBlock(props) {
const { editor, node } = props
const language = node.data.get('language')
- function onChange(e) {
- editor.change(c => c.setNodeByKey(node.key, { data: { language: e.target.value }}))
+ function onChange(event) {
+ editor.change(c => c.setNodeByKey(node.key, { data: { language: event.target.value }}))
}
return (
@@ -165,19 +165,19 @@ class CodeHighlighting extends React.Component {
/**
* On key down inside code blocks, insert soft new lines.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
* @return {Change}
*/
- onKeyDown = (e, data, change) => {
+ onKeyDown = (event, change) => {
const { state } = change
const { startBlock } = state
- if (e.key != 'Enter') return
+ if (event.key != 'Enter') return
if (startBlock.type != 'code') return
if (state.isExpanded) change.delete()
- return change.insertText('\n')
+ change.insertText('\n')
+ return true
}
/**
diff --git a/examples/forced-layout/index.js b/examples/forced-layout/index.js
index 57f98f858..043e3f255 100644
--- a/examples/forced-layout/index.js
+++ b/examples/forced-layout/index.js
@@ -23,51 +23,59 @@ class ForcedLayout extends React.Component {
state: State.fromJSON(initialState),
schema: {
nodes: {
- 'title': props =>
{props.children}
,
- 'paragraph': props => {props.children}
+ title: props => {props.children}
,
+ paragraph: props => {props.children}
,
},
rules: [
/* Rule that always makes the first block a title, normalizes by inserting one if no children, or setting the top to be a title */
{
- match: node => node.kind === 'document',
- validate: document => !document.nodes.size || document.nodes.first().type !== 'title' ? document.nodes : null,
+ match: (object) => {
+ return object.kind == 'document'
+ },
+ validate: (document) => {
+ return !document.nodes.size || document.nodes.first().type != 'title' ? document.nodes : null
+ },
normalize: (change, document, nodes) => {
if (!nodes.size) {
const title = Block.create({ type: 'title', data: {}})
- return change.insertNodeByKey(document.key, 0, title)
+ change.insertNodeByKey(document.key, 0, title)
+ return
}
- return change.setNodeByKey(nodes.first().key, 'title')
+ change.setNodeByKey(nodes.first().key, 'title')
}
},
/* Rule that only allows for one title, normalizes by making titles paragraphs */
{
- match: node => node.kind === 'document',
+ match: (object) => {
+ return object.kind == 'document'
+ },
validate: (document) => {
- const invalidChildren = document.nodes.filter((child, index) => child.type === 'title' && index !== 0)
+ const invalidChildren = document.nodes.filter((child, index) => child.type == 'title' && index != 0)
return invalidChildren.size ? invalidChildren : null
},
normalize: (change, document, invalidChildren) => {
- let updatedTransform = change
invalidChildren.forEach((child) => {
- updatedTransform = change.setNodeByKey(child.key, 'paragraph')
+ change.setNodeByKey(child.key, 'paragraph')
})
-
- return updatedTransform
}
},
/* Rule that forces at least one paragraph, normalizes by inserting an empty paragraph */
{
- match: node => node.kind === 'document',
- validate: document => document.nodes.size < 2 ? true : null,
+ match: (object) => {
+ return object.kind == 'document'
+ },
+ validate: (document) => {
+ return document.nodes.size < 2 ? true : null
+ },
normalize: (change, document) => {
const paragraph = Block.create({ type: 'paragraph', data: {}})
- return change.insertNodeByKey(document.key, 1, paragraph)
+ change.insertNodeByKey(document.key, 1, paragraph)
}
}
]
diff --git a/examples/hovering-menu/index.js b/examples/hovering-menu/index.js
index 267b320e6..f8bfc991c 100644
--- a/examples/hovering-menu/index.js
+++ b/examples/hovering-menu/index.js
@@ -23,7 +23,14 @@ const schema = {
}
}
-function Menu({ menuRef, onChange, state }) {
+/**
+ * The menu.
+ *
+ * @type {Component}
+ */
+
+class Menu extends React.Component {
+
/**
* Check if the current selection has a mark with `type` in it.
*
@@ -31,22 +38,22 @@ function Menu({ menuRef, onChange, state }) {
* @return {Boolean}
*/
- function hasMark(type) {
+ hasMark(type) {
+ const { state } = this.props
return state.activeMarks.some(mark => mark.type == type)
}
/**
* When a mark button is clicked, toggle the current mark.
*
- * @param {Event} e
+ * @param {Event} event
* @param {String} type
*/
- function onClickMark(e, type) {
- e.preventDefault()
- const change = state
- .change()
- .toggleMark(type)
+ onClickMark(event, type) {
+ const { state, onChange } = this.props
+ event.preventDefault()
+ const change = state.change().toggleMark(type)
onChange(change)
}
@@ -58,11 +65,9 @@ function Menu({ menuRef, onChange, state }) {
* @return {Element}
*/
- function renderMarkButton(type, icon) {
- const isActive = hasMark(type)
- function onMouseDown(e) {
- onClickMark(e, type)
- }
+ renderMarkButton(type, icon) {
+ const isActive = this.hasMark(type)
+ const onMouseDown = event => this.onClickMark(event, type)
return (
@@ -71,16 +76,26 @@ function Menu({ menuRef, onChange, state }) {
)
}
- return (
- ReactDOM.createPortal(
-
- {renderMarkButton('bold', 'format_bold')}
- {renderMarkButton('italic', 'format_italic')}
- {renderMarkButton('underlined', 'format_underlined')}
- {renderMarkButton('code', 'code')}
-
, root
+ /**
+ * Render.
+ *
+ * @return {Element}
+ */
+
+ render() {
+ return (
+ ReactDOM.createPortal(
+
+ {this.renderMarkButton('bold', 'format_bold')}
+ {this.renderMarkButton('italic', 'format_italic')}
+ {this.renderMarkButton('underlined', 'format_underlined')}
+ {this.renderMarkButton('code', 'code')}
+
,
+ root
+ )
)
- )
+ }
+
}
@@ -125,11 +140,14 @@ class HoveringMenu extends React.Component {
}
/**
- * Set menu ref
+ * Save the `menu` ref.
*
+ * @param {Menu} menu
*/
- menuRef = el => this.menu = el
+ menuRef = (menu) => {
+ this.menu = menu
+ }
/**
* Render.
diff --git a/examples/huge-document/index.js b/examples/huge-document/index.js
index ec5b29437..d4cbd1ab7 100644
--- a/examples/huge-document/index.js
+++ b/examples/huge-document/index.js
@@ -23,14 +23,14 @@ for (let h = 0; h < HEADINGS; h++) {
nodes.push({
kind: 'block',
type: 'heading',
- nodes: [{ kind: 'text', ranges: [{ text: faker.lorem.sentence() }] }]
+ nodes: [{ kind: 'text', leaves: [{ text: faker.lorem.sentence() }] }]
})
for (let p = 0; p < PARAGRAPHS; p++) {
nodes.push({
kind: 'block',
type: 'paragraph',
- nodes: [{ kind: 'text', ranges: [{ text: faker.lorem.paragraph() }] }]
+ nodes: [{ kind: 'text', leaves: [{ text: faker.lorem.paragraph() }] }]
})
}
}
diff --git a/examples/images/index.js b/examples/images/index.js
index 2b9850be1..e397d258a 100644
--- a/examples/images/index.js
+++ b/examples/images/index.js
@@ -175,11 +175,11 @@ class Images extends React.Component {
/**
* On clicking the image button, prompt for an image and insert it.
*
- * @param {Event} e
+ * @param {Event} event
*/
- onClickImage = (e) => {
- e.preventDefault()
+ onClickImage = (event) => {
+ event.preventDefault()
const src = window.prompt('Enter the URL of the image:')
if (!src) return
@@ -193,17 +193,16 @@ class Images extends React.Component {
/**
* On drop, insert the image wherever it is dropped.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
* @param {Editor} editor
*/
- onDropOrPaste = (e, data, change, editor) => {
- const target = getEventRange(e)
+ onDropOrPaste = (event, change, editor) => {
+ const target = getEventRange(event)
if (!target) return
- const transfer = getEventTransfer(e)
+ const transfer = getEventTransfer(event)
const { type, text, files } = transfer
if (type == 'files') {
diff --git a/examples/links/index.js b/examples/links/index.js
index 6de53252c..9f1018a5e 100644
--- a/examples/links/index.js
+++ b/examples/links/index.js
@@ -92,11 +92,11 @@ class Links extends React.Component {
* When clicking a link, if the selection has a link in it, remove the link.
* Otherwise, add a new link with an href and text.
*
- * @param {Event} e
+ * @param {Event} event
*/
- onClickLink = (e) => {
- e.preventDefault()
+ onClickLink = (event) => {
+ event.preventDefault()
const { state } = this.state
const hasLinks = this.hasLinks()
const change = state.change()
@@ -125,15 +125,14 @@ class Links extends React.Component {
/**
* On paste, if the text is a link, wrap the selection in a link.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
*/
- onPaste = (e, data, change) => {
+ onPaste = (event, change) => {
if (change.state.isCollapsed) return
- const transfer = getEventTransfer(e)
+ const transfer = getEventTransfer(event)
const { type, text } = transfer
if (type != 'text' && type != 'html') return
if (!isUrl(text)) return
diff --git a/examples/markdown-shortcuts/index.js b/examples/markdown-shortcuts/index.js
index fd51a0272..a2b842b69 100644
--- a/examples/markdown-shortcuts/index.js
+++ b/examples/markdown-shortcuts/index.js
@@ -99,16 +99,15 @@ class MarkdownShortcuts extends React.Component {
/**
* On key down, check for our specific key shortcuts.
*
- * @param {Event} e
- * @param {Data} data
+ * @param {Event} event
* @param {Change} change
*/
- onKeyDown = (e, data, change) => {
- switch (e.key) {
- case ' ': return this.onSpace(e, change)
- case 'Backspace': return this.onBackspace(e, change)
- case 'Enter': return this.onEnter(e, change)
+ onKeyDown = (event, change) => {
+ switch (event.key) {
+ case ' ': return this.onSpace(event, change)
+ case 'Backspace': return this.onBackspace(event, change)
+ case 'Enter': return this.onEnter(event, change)
}
}
@@ -116,12 +115,12 @@ class MarkdownShortcuts extends React.Component {
* On space, if it was after an auto-markdown shortcut, convert the current
* node into the shortcut's corresponding type.
*
- * @param {Event} e
+ * @param {Event} event
* @param {State} change
* @return {State or Null} state
*/
- onSpace = (e, change) => {
+ onSpace = (event, change) => {
const { state } = change
if (state.isExpanded) return
@@ -131,7 +130,7 @@ class MarkdownShortcuts extends React.Component {
if (!type) return
if (type == 'list-item' && startBlock.type == 'list-item') return
- e.preventDefault()
+ event.preventDefault()
change.setBlock(type)
@@ -139,10 +138,7 @@ class MarkdownShortcuts extends React.Component {
change.wrapBlock('bulleted-list')
}
- change
- .extendToStartOf(startBlock)
- .delete()
-
+ change.extendToStartOf(startBlock).delete()
return true
}
@@ -150,12 +146,12 @@ class MarkdownShortcuts extends React.Component {
* On backspace, if at the start of a non-paragraph, convert it back into a
* paragraph node.
*
- * @param {Event} e
+ * @param {Event} event
* @param {State} change
* @return {State or Null} state
*/
- onBackspace = (e, change) => {
+ onBackspace = (event, change) => {
const { state } = change
if (state.isExpanded) return
if (state.startOffset != 0) return
@@ -163,7 +159,7 @@ class MarkdownShortcuts extends React.Component {
const { startBlock } = state
if (startBlock.type == 'paragraph') return
- e.preventDefault()
+ event.preventDefault()
change.setBlock('paragraph')
if (startBlock.type == 'list-item') {
@@ -177,17 +173,17 @@ class MarkdownShortcuts extends React.Component {
* On return, if at the end of a node type that should not be extended,
* create a new paragraph below it.
*
- * @param {Event} e
+ * @param {Event} event
* @param {State} change
* @return {State or Null} state
*/
- onEnter = (e, change) => {
+ onEnter = (event, change) => {
const { state } = change
if (state.isExpanded) return
const { startBlock, startOffset, endOffset } = state
- if (startOffset == 0 && startBlock.text.length == 0) return this.onBackspace(e, change)
+ if (startOffset == 0 && startBlock.text.length == 0) return this.onBackspace(event, change)
if (endOffset != startBlock.text.length) return
if (
@@ -202,12 +198,8 @@ class MarkdownShortcuts extends React.Component {
return
}
- e.preventDefault()
-
- change
- .splitBlock()
- .setBlock('paragraph')
-
+ event.preventDefault()
+ change.splitBlock().setBlock('paragraph')
return true
}
diff --git a/examples/paste-html/index.js b/examples/paste-html/index.js
index e190593f6..5186b9a71 100644
--- a/examples/paste-html/index.js
+++ b/examples/paste-html/index.js
@@ -174,13 +174,12 @@ class PasteHtml extends React.Component {
/**
* On paste, deserialize the HTML and then insert the fragment.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
*/
- onPaste = (e, data, change) => {
- const transfer = getEventTransfer(e)
+ onPaste = (event, change) => {
+ const transfer = getEventTransfer(event)
if (transfer.type != 'html') return
const { document } = serializer.deserialize(transfer.html)
change.insertFragment(document)
diff --git a/examples/rich-text/index.js b/examples/rich-text/index.js
index c28cd9d6b..102e7af4b 100644
--- a/examples/rich-text/index.js
+++ b/examples/rich-text/index.js
@@ -114,28 +114,27 @@ class RichTextExample extends React.Component {
/**
* On key down, if it's a formatting command toggle a mark.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
* @return {Change}
*/
- onKeyDown = (e, data, change) => {
+ onKeyDown = (event, change) => {
let mark
- if (isBoldHotkey(e)) {
+ if (isBoldHotkey(event)) {
mark = 'bold'
- } else if (isItalicHotkey(e)) {
+ } else if (isItalicHotkey(event)) {
mark = 'italic'
- } else if (isUnderlinedHotkey(e)) {
+ } else if (isUnderlinedHotkey(event)) {
mark = 'underlined'
- } else if (isCodeHotkey(e)) {
+ } else if (isCodeHotkey(event)) {
mark = 'code'
} else {
return
}
- e.preventDefault()
+ event.preventDefault()
change.toggleMark(mark)
return true
}
@@ -143,12 +142,12 @@ class RichTextExample extends React.Component {
/**
* When a mark button is clicked, toggle the current mark.
*
- * @param {Event} e
+ * @param {Event} event
* @param {String} type
*/
- onClickMark = (e, type) => {
- e.preventDefault()
+ onClickMark = (event, type) => {
+ event.preventDefault()
const { state } = this.state
const change = state.change().toggleMark(type)
this.onChange(change)
@@ -157,12 +156,12 @@ class RichTextExample extends React.Component {
/**
* When a block button is clicked, toggle the block type.
*
- * @param {Event} e
+ * @param {Event} event
* @param {String} type
*/
- onClickBlock = (e, type) => {
- e.preventDefault()
+ onClickBlock = (event, type) => {
+ event.preventDefault()
const { state } = this.state
const change = state.change()
const { document } = state
@@ -258,7 +257,7 @@ class RichTextExample extends React.Component {
renderMarkButton = (type, icon) => {
const isActive = this.hasMark(type)
- const onMouseDown = e => this.onClickMark(e, type)
+ const onMouseDown = event => this.onClickMark(event, type)
return (
@@ -277,7 +276,7 @@ class RichTextExample extends React.Component {
renderBlockButton = (type, icon) => {
const isActive = this.hasBlock(type)
- const onMouseDown = e => this.onClickBlock(e, type)
+ const onMouseDown = event => this.onClickBlock(event, type)
return (
diff --git a/examples/rtl/index.js b/examples/rtl/index.js
index a0e43d4a7..c5df00c71 100644
--- a/examples/rtl/index.js
+++ b/examples/rtl/index.js
@@ -48,14 +48,13 @@ class PlainText extends React.Component {
/**
* On key down, if it's add a soft break.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
*/
- onKeyDown = (e, data, change) => {
- if (e.key == 'Enter' && e.shiftKey) {
- e.preventDefault()
+ onKeyDown = (event, change) => {
+ if (event.key == 'Enter' && event.shiftKey) {
+ event.preventDefault()
change.insertText('\n')
return true
}
diff --git a/examples/search-highlighting/index.js b/examples/search-highlighting/index.js
index 50c0850ca..bd6756aca 100644
--- a/examples/search-highlighting/index.js
+++ b/examples/search-highlighting/index.js
@@ -50,12 +50,12 @@ class SearchHighlighting extends React.Component {
/**
* On input change, update the decorations.
*
- * @param {Event} e
+ * @param {Event} event
*/
- onInputChange = (e) => {
+ onInputChange = (event) => {
const { state } = this.state
- const string = e.target.value
+ const string = event.target.value
const texts = state.document.getTexts()
const decorations = []
diff --git a/examples/syncing-operations/index.js b/examples/syncing-operations/index.js
index 93d4d5f06..aa3f2faf3 100644
--- a/examples/syncing-operations/index.js
+++ b/examples/syncing-operations/index.js
@@ -105,28 +105,27 @@ class SyncingEditor extends React.Component {
/**
* On key down, if it's a formatting command toggle a mark.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
* @return {Change}
*/
- onKeyDown = (e, data, change) => {
+ onKeyDown = (event, change) => {
let mark
- if (isBoldHotkey(e)) {
+ if (isBoldHotkey(event)) {
mark = 'bold'
- } else if (isItalicHotkey(e)) {
+ } else if (isItalicHotkey(event)) {
mark = 'italic'
- } else if (isUnderlinedHotkey(e)) {
+ } else if (isUnderlinedHotkey(event)) {
mark = 'underlined'
- } else if (isCodeHotkey(e)) {
+ } else if (isCodeHotkey(event)) {
mark = 'code'
} else {
return
}
- e.preventDefault()
+ event.preventDefault()
change.toggleMark(mark)
return true
}
@@ -134,12 +133,12 @@ class SyncingEditor extends React.Component {
/**
* When a mark button is clicked, toggle the current mark.
*
- * @param {Event} e
+ * @param {Event} event
* @param {String} type
*/
- onClickMark = (e, type) => {
- e.preventDefault()
+ onClickMark = (event, type) => {
+ event.preventDefault()
const { state } = this.state
const change = state.change().toggleMark(type)
this.onChange(change)
@@ -187,7 +186,7 @@ class SyncingEditor extends React.Component {
renderButton = (type, icon) => {
const isActive = this.hasMark(type)
- const onMouseDown = e => this.onClickMark(e, type)
+ const onMouseDown = event => this.onClickMark(event, type)
return (
diff --git a/examples/tables/index.js b/examples/tables/index.js
index 9d934c8a4..47be1fc65 100644
--- a/examples/tables/index.js
+++ b/examples/tables/index.js
@@ -43,14 +43,14 @@ class Tables extends React.Component {
/**
* On backspace, do nothing if at the start of a table cell.
*
- * @param {Event} e
+ * @param {Event} event
* @param {Change} change
*/
- onBackspace = (e, change) => {
+ onBackspace = (event, change) => {
const { state } = change
if (state.startOffset != 0) return
- e.preventDefault()
+ event.preventDefault()
return true
}
@@ -67,38 +67,37 @@ class Tables extends React.Component {
/**
* On delete, do nothing if at the end of a table cell.
*
- * @param {Event} e
+ * @param {Event} event
* @param {Change} change
*/
- onDelete = (e, change) => {
+ onDelete = (event, change) => {
const { state } = change
if (state.endOffset != state.startText.text.length) return
- e.preventDefault()
+ event.preventDefault()
return true
}
/**
* On return, do nothing if inside a table cell.
*
- * @param {Event} e
+ * @param {Event} event
* @param {Change} change
*/
- onEnter = (e, change) => {
- e.preventDefault()
+ onEnter = (event, change) => {
+ event.preventDefault()
return true
}
/**
* On key down, check for our specific key shortcuts.
*
- * @param {Event} e
- * @param {Object} data
+ * @param {Event} event
* @param {Change} change
*/
- onKeyDown = (e, data, change) => {
+ onKeyDown = (event, change) => {
const { state } = change
const { document, selection } = state
const { startKey } = selection
@@ -109,7 +108,7 @@ class Tables extends React.Component {
const prevBlock = document.getClosestBlock(previous.key)
if (prevBlock.type == 'table-cell') {
- e.preventDefault()
+ event.preventDefault()
return true
}
}
@@ -118,10 +117,10 @@ class Tables extends React.Component {
return
}
- switch (e.key) {
- case 'Backspace': return this.onBackspace(e, state)
- case 'Delete': return this.onDelete(e, state)
- case 'Enter': return this.onEnter(e, state)
+ switch (event.key) {
+ case 'Backspace': return this.onBackspace(event, state)
+ case 'Delete': return this.onDelete(event, state)
+ case 'Enter': return this.onEnter(event, state)
}
}
diff --git a/packages/slate-react/src/components/content.js b/packages/slate-react/src/components/content.js
index ab16a4b37..b4b89b5d2 100644
--- a/packages/slate-react/src/components/content.js
+++ b/packages/slate-react/src/components/content.js
@@ -11,9 +11,8 @@ import Node from './node'
import findClosestNode from '../utils/find-closest-node'
import findDOMRange from '../utils/find-dom-range'
import findRange from '../utils/find-range'
-import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
import scrollToSelection from '../utils/scroll-to-selection'
-import { IS_FIREFOX, IS_IE, SUPPORTED_EVENTS } from '../constants/environment'
+import { IS_FIREFOX, SUPPORTED_EVENTS } from '../constants/environment'
/**
* Debug.
@@ -229,18 +228,6 @@ class Content extends React.Component {
this.tmp.key++
}
- // COMPAT: In IE 11, only plain text can be retrieved from the event's
- // `clipboardData`. To get HTML, use the browser's native paste action which
- // can only be handled synchronously. (2017/06/23)
- if (handler == 'onPaste' && IS_IE) {
- getHtmlFromNativePaste(event.target, (html) => {
- const data = html ? { html, type: 'html' } : {}
- this.props.onPaste(event, data)
- })
-
- return
- }
-
// If the `onSelect` handler fires while the `isUpdatingSelection` flag is
// set it's a result of updating the selection manually, so skip it.
if (handler == 'onSelect' && this.tmp.isUpdatingSelection) {
@@ -274,7 +261,6 @@ class Content extends React.Component {
handler == 'onCompositionStart' ||
handler == 'onCopy' ||
handler == 'onCut' ||
- handler == 'onDragStart' ||
handler == 'onFocus' ||
handler == 'onInput' ||
handler == 'onKeyDown' ||
@@ -285,7 +271,7 @@ class Content extends React.Component {
if (!this.isInEditor(event.target)) return
}
- this.props[handler](event, {})
+ this.props[handler](event)
}
/**
@@ -315,7 +301,6 @@ class Content extends React.Component {
if (text == null) return
- debug('onNativeBeforeInput', { event, text })
event.preventDefault()
const { editor, state } = this.props
diff --git a/packages/slate-react/src/components/void.js b/packages/slate-react/src/components/void.js
index cc3011ee4..69700a1f1 100644
--- a/packages/slate-react/src/components/void.js
+++ b/packages/slate-react/src/components/void.js
@@ -1,13 +1,10 @@
-import Base64 from 'slate-base64-serializer'
import Debug from 'debug'
import React from 'react'
import SlateTypes from 'slate-prop-types'
import Types from 'prop-types'
-import setTransferData from '../utils/set-transfer-data'
import Text from './text'
-import TRANSFER_TYPES from '../constants/transfer-types'
/**
* Debug.
@@ -56,68 +53,6 @@ class Void extends React.Component {
debug(message, `${id}`, ...args)
}
- /**
- * When one of the wrapper elements it clicked, select the void node.
- *
- * @param {Event} event
- */
-
- onClick = (event) => {
- if (this.props.readOnly) return
-
- this.debug('onClick', { event })
-
- const { node, editor } = this.props
-
- editor.change((change) => {
- change
- // COMPAT: In Chrome & Safari, selections that are at the zero offset of
- // 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)
- .collapseToEndOf(node)
- .focus()
- })
- }
-
- /**
- * On drag enter, prevent default to allow drops.
- *
- * @type {Event} event
- */
-
- onDragEnter = (event) => {
- if (this.props.readOnly) return
- event.preventDefault()
- }
-
- /**
- * On drag over, prevent default to allow drops.
- *
- * @type {Event} event
- */
-
- onDragOver = (event) => {
- if (this.props.readOnly) return
- event.preventDefault()
- }
-
- /**
- * On drag start, add a serialized representation of the node to the data.
- *
- * @param {Event} event
- */
-
- onDragStart = (event) => {
- const { node } = this.props
- const encoded = Base64.serializeNode(node, { preserveKeys: true })
- const { dataTransfer } = event.nativeEvent
-
- setTransferData(dataTransfer, TRANSFER_TYPES.NODE, encoded)
-
- this.debug('onDragStart', event)
- }
-
/**
* Render.
*
@@ -136,14 +71,7 @@ class Void extends React.Component {
this.debug('render', { props })
return (
-
+
{!readOnly &&
{this.renderText()}
}
diff --git a/packages/slate-react/src/constants/event-handlers.js b/packages/slate-react/src/constants/event-handlers.js
index b6473ece0..d15cfe809 100644
--- a/packages/slate-react/src/constants/event-handlers.js
+++ b/packages/slate-react/src/constants/event-handlers.js
@@ -8,11 +8,15 @@
const EVENT_HANDLERS = [
'onBeforeInput',
'onBlur',
+ 'onClick',
'onCompositionEnd',
'onCompositionStart',
'onCopy',
'onCut',
'onDragEnd',
+ 'onDragEnter',
+ 'onDragExit',
+ 'onDragLeave',
'onDragOver',
'onDragStart',
'onDrop',
diff --git a/packages/slate-react/src/constants/hotkeys.js b/packages/slate-react/src/constants/hotkeys.js
index 026f9cd77..675c0706c 100644
--- a/packages/slate-react/src/constants/hotkeys.js
+++ b/packages/slate-react/src/constants/hotkeys.js
@@ -38,6 +38,13 @@ const CONTENTEDITABLE = e => (
UNDO(e)
)
+const COMPOSING = e => (
+ e.key == 'ArrowDown' ||
+ e.key == 'ArrowLeft' ||
+ e.key == 'ArrowRight' ||
+ e.key == 'ArrowUp'
+)
+
/**
* Export.
*
@@ -46,6 +53,7 @@ const CONTENTEDITABLE = e => (
export default {
BOLD,
+ COMPOSING,
CONTENTEDITABLE,
DELETE_CHAR_BACKWARD,
DELETE_CHAR_FORWARD,
diff --git a/packages/slate-react/src/constants/transfer-types.js b/packages/slate-react/src/constants/transfer-types.js
index bf5ae1595..3fa4e6bcb 100644
--- a/packages/slate-react/src/constants/transfer-types.js
+++ b/packages/slate-react/src/constants/transfer-types.js
@@ -1,12 +1,16 @@
+
/**
- * Slate-specific data transfer types.
+ * The transfer types that Slate recognizes.
*
* @type {Object}
*/
-const TYPES = {
+const TRANSFER_TYPES = {
FRAGMENT: 'application/x-slate-fragment',
+ HTML: 'text/html',
NODE: 'application/x-slate-node',
+ RICH: 'text/rtf',
+ TEXT: 'text/plain',
}
/**
@@ -15,4 +19,4 @@ const TYPES = {
* @type {Object}
*/
-export default TYPES
+export default TRANSFER_TYPES
diff --git a/packages/slate-react/src/index.js b/packages/slate-react/src/index.js
index 8b92fa3cd..9461742b3 100644
--- a/packages/slate-react/src/index.js
+++ b/packages/slate-react/src/index.js
@@ -1,12 +1,13 @@
import Editor from './components/editor'
import Placeholder from './components/placeholder'
-import getEventRange from './utils/get-event-range'
-import getEventTransfer from './utils/get-event-transfer'
import findDOMNode from './utils/find-dom-node'
import findDOMRange from './utils/find-dom-range'
import findNode from './utils/find-node'
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'
/**
* Export.
@@ -17,21 +18,23 @@ import findRange from './utils/find-range'
export {
Editor,
Placeholder,
- getEventRange,
- getEventTransfer,
findDOMNode,
findDOMRange,
findNode,
findRange,
+ getEventRange,
+ getEventTransfer,
+ setEventTransfer,
}
export default {
Editor,
Placeholder,
- getEventRange,
- getEventTransfer,
findDOMNode,
findDOMRange,
findNode,
findRange,
+ getEventRange,
+ getEventTransfer,
+ setEventTransfer,
}
diff --git a/packages/slate-react/src/plugins/after.js b/packages/slate-react/src/plugins/after.js
index bd6d47f4a..c2d7b52f4 100644
--- a/packages/slate-react/src/plugins/after.js
+++ b/packages/slate-react/src/plugins/after.js
@@ -11,10 +11,12 @@ import HOTKEYS from '../constants/hotkeys'
import Content from '../components/content'
import Placeholder from '../components/placeholder'
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 { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
/**
@@ -60,21 +62,23 @@ function AfterPlugin(options = {}) {
// normalize changes to the document, not selection.
if (prevState && state.document == prevState.document) return
+ debug('onBeforeChange')
+
change.normalize(coreSchema)
change.normalize(schema)
- debug('onBeforeChange')
}
/**
* On before input, correct any browser inconsistencies.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onBeforeInput(event, data, change) {
- debug('onBeforeInput', { data })
+ function onBeforeInput(event, change, editor) {
+ debug('onBeforeInput', { event })
+
event.preventDefault()
change.insertText(event.data)
}
@@ -83,40 +87,69 @@ function AfterPlugin(options = {}) {
* On blur.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onBlur(event, data, change) {
- debug('onBlur', { data })
+ function onBlur(event, change, editor) {
+ debug('onBlur', { event })
+
change.blur()
}
+ /**
+ * On click.
+ *
+ * @param {Event} event
+ * @param {Change} change
+ * @param {Editor} editor
+ */
+
+ function onClick(event, change, editor) {
+ if (editor.props.readOnly) return true
+
+ const { state } = change
+ const { document } = state
+ const node = findNode(event.target, state)
+ const isVoid = node && (node.isVoid || document.hasVoidParent(node.key))
+
+ if (isVoid) {
+ // COMPAT: In Chrome & Safari, selections that are at the zero offset of
+ // 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)
+ }
+
+ debug('onClick', { event })
+ }
+
/**
* On copy.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onCopy(event, data, change) {
- debug('onCopy', data)
- onCutOrCopy(event, data, change)
+ function onCopy(event, change, editor) {
+ debug('onCopy', { event })
+
+ onCutOrCopy(event, change)
}
/**
* On cut.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onCut(event, data, change, editor) {
- debug('onCut', data)
- onCutOrCopy(event, data, change)
+ function onCut(event, change, editor) {
+ debug('onCut', { event })
+
+ onCutOrCopy(event, change)
const window = getWindow(event.target)
// Once the fake cut content has successfully been added to the clipboard,
@@ -130,11 +163,11 @@ function AfterPlugin(options = {}) {
* On cut or copy.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onCutOrCopy(event, data, change) {
+ function onCutOrCopy(event, change, editor) {
const window = getWindow(event.target)
const native = window.getSelection()
const { state } = change
@@ -242,12 +275,11 @@ function AfterPlugin(options = {}) {
* On drag end.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDragEnd(event, data, change, editor) {
+ function onDragEnd(event, change, editor) {
debug('onDragEnd', { event })
isDraggingInternally = null
@@ -257,12 +289,11 @@ function AfterPlugin(options = {}) {
* On drag over.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDragOver(event, data, change, editor) {
+ function onDragOver(event, change, editor) {
debug('onDragOver', { event })
isDraggingInternally = false
@@ -272,26 +303,39 @@ function AfterPlugin(options = {}) {
* On drag start.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDragStart(event, data, change, editor) {
+ function onDragStart(event, change, editor) {
debug('onDragStart', { event })
isDraggingInternally = true
+
+ const { state } = change
+ const { document } = state
+ const node = findNode(event.target, state)
+ const isVoid = node && (node.isVoid || document.hasVoidParent(node.key))
+
+ if (isVoid) {
+ const encoded = Base64.serializeNode(node, { preserveKeys: true })
+ setEventTransfer(event, 'node', encoded)
+ } else {
+ const { fragment } = state
+ const encoded = Base64.serializeNode(fragment)
+ setEventTransfer(event, 'fragment', encoded)
+ }
}
/**
* On drop.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onDrop(event, data, change, editor) {
+ function onDrop(event, change, editor) {
debug('onDrop', { event })
const { state } = change
@@ -363,12 +407,12 @@ function AfterPlugin(options = {}) {
* On input.
*
* @param {Event} eventvent
- * @param {Object} data
* @param {Change} change
- * @param {Editor} editor
*/
- function onInput(event, data, change, editor) {
+ function onInput(event, change, editor) {
+ debug('onInput', { event })
+
const window = getWindow(event.target)
const { state } = change
@@ -428,21 +472,21 @@ function AfterPlugin(options = {}) {
* On key down.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDown(event, data, change) {
- debug('onKeyDown', { data })
+ function onKeyDown(event, change, editor) {
+ debug('onKeyDown', { event })
switch (event.key) {
- case 'Enter': return onKeyDownEnter(event, data, change)
- case 'Backspace': return onKeyDownBackspace(event, data, change)
- case 'Delete': return onKeyDownDelete(event, data, change)
- case 'ArrowLeft': return onKeyDownLeft(event, data, change)
- case 'ArrowRight': return onKeyDownRight(event, data, change)
- case 'ArrowUp': return onKeyDownUp(event, data, change)
- case 'ArrowDown': return onKeyDownDown(event, data, change)
+ case 'Enter': return onKeyDownEnter(event, change)
+ case 'Backspace': return onKeyDownBackspace(event, change)
+ case 'Delete': return onKeyDownDelete(event, change)
+ case 'ArrowLeft': return onKeyDownLeft(event, change)
+ case 'ArrowRight': return onKeyDownRight(event, change)
+ case 'ArrowUp': return onKeyDownUp(event, change)
+ case 'ArrowDown': return onKeyDownDown(event, change)
}
if (HOTKEYS.DELETE_CHAR_BACKWARD(event)) {
@@ -470,11 +514,11 @@ function AfterPlugin(options = {}) {
* On `enter` key down, split the current block in half.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownEnter(event, data, change) {
+ function onKeyDownEnter(event, change, editor) {
const { state } = change
const { document, startKey } = state
const hasVoidParent = document.hasVoidParent(startKey)
@@ -495,11 +539,11 @@ function AfterPlugin(options = {}) {
* On `backspace` key down, delete backwards.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownBackspace(event, data, change) {
+ function onKeyDownBackspace(event, change, editor) {
const isWord = IS_MAC ? event.altKey : event.ctrlKey
const isLine = IS_MAC ? event.metaKey : false
@@ -514,11 +558,11 @@ function AfterPlugin(options = {}) {
* On `delete` key down, delete forwards.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownDelete(event, data, change) {
+ function onKeyDownDelete(event, change, editor) {
const isWord = IS_MAC ? event.altKey : event.ctrlKey
const isLine = IS_MAC ? event.metaKey : false
@@ -540,11 +584,11 @@ function AfterPlugin(options = {}) {
* the zero-width spaces will cause two arrow keys to jump to the next text.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownLeft(event, data, change) {
+ function onKeyDownLeft(event, change, editor) {
const { state } = change
if (event.ctrlKey) return
@@ -596,11 +640,11 @@ function AfterPlugin(options = {}) {
* selection to the very start of an inline node here. (2016/11/29)
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownRight(event, data, change) {
+ function onKeyDownRight(event, change, editor) {
const { state } = change
if (event.ctrlKey) return
@@ -650,11 +694,11 @@ function AfterPlugin(options = {}) {
* Firefox, option-up doesn't properly move the selection.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownUp(event, data, change) {
+ function onKeyDownUp(event, change, editor) {
if (!IS_MAC || event.ctrlKey || !event.altKey) return
const { state } = change
@@ -679,11 +723,11 @@ function AfterPlugin(options = {}) {
* Firefox, option-down doesn't properly move the selection.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onKeyDownDown(event, data, change) {
+ function onKeyDownDown(event, change, editor) {
if (!IS_MAC || event.ctrlKey || !event.altKey) return
const { state } = change
@@ -704,12 +748,12 @@ function AfterPlugin(options = {}) {
* On paste.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onPaste(event, data, change) {
- debug('onPaste', { data })
+ function onPaste(event, change, editor) {
+ debug('onPaste', { event })
const transfer = getEventTransfer(event)
const { type, fragment, text } = transfer
@@ -734,12 +778,12 @@ function AfterPlugin(options = {}) {
* On select.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
+ * @param {Editor} editor
*/
- function onSelect(event, data, change) {
- debug('onSelect', { data })
+ function onSelect(event, change, editor) {
+ debug('onSelect', { event })
const window = getWindow(event.target)
const { state } = change
@@ -916,6 +960,7 @@ function AfterPlugin(options = {}) {
onBeforeChange,
onBeforeInput,
onBlur,
+ onClick,
onCopy,
onCut,
onDragEnd,
diff --git a/packages/slate-react/src/plugins/before.js b/packages/slate-react/src/plugins/before.js
index df2ba13b3..25f4a80d9 100644
--- a/packages/slate-react/src/plugins/before.js
+++ b/packages/slate-react/src/plugins/before.js
@@ -1,18 +1,10 @@
-import Base64 from 'slate-base64-serializer'
import Debug from 'debug'
import getWindow from 'get-window'
-import keycode from 'keycode'
-import logger from 'slate-dev-logger'
import { findDOMNode } from 'react-dom'
import HOTKEYS from '../constants/hotkeys'
-import TRANSFER_TYPES from '../constants/transfer-types'
-import findRange from '../utils/find-range'
-import getEventRange from '../utils/get-event-range'
-import getEventTransfer from '../utils/get-event-transfer'
-import setTransferData from '../utils/set-transfer-data'
-import { IS_FIREFOX, IS_MAC, SUPPORTED_EVENTS } from '../constants/environment'
+import { IS_FIREFOX, SUPPORTED_EVENTS } from '../constants/environment'
/**
* Debug.
@@ -33,19 +25,16 @@ function BeforePlugin() {
let isComposing = false
let isCopying = false
let isDragging = false
- let isShifting = false
- let isInternalDrag = null
/**
* On before input.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onBeforeInput(event, data, change, editor) {
+ function onBeforeInput(event, change, editor) {
if (editor.props.readOnly) return true
// COMPAT: React's `onBeforeInput` synthetic event is based on the native
@@ -63,12 +52,11 @@ function BeforePlugin() {
* On blur.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onBlur(event, data, change, editor) {
+ function onBlur(event, change, editor) {
if (isCopying) return true
if (editor.props.readOnly) return true
@@ -86,12 +74,11 @@ function BeforePlugin() {
* On composition end.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onCompositionEnd(event, data, change, editor) {
+ function onCompositionEnd(event, change, editor) {
const n = compositionCount
// The `count` check here ensures that if another composition starts
@@ -109,12 +96,11 @@ function BeforePlugin() {
* On composition start.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onCompositionStart(event, data, change, editor) {
+ function onCompositionStart(event, change, editor) {
isComposing = true
compositionCount++
@@ -125,20 +111,15 @@ function BeforePlugin() {
* On copy.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onCopy(event, data, change, editor) {
+ function onCopy(event, change, editor) {
const window = getWindow(event.target)
isCopying = true
window.requestAnimationFrame(() => isCopying = false)
- const { state } = change
- defineDeprecatedData(data, 'type', 'fragment')
- defineDeprecatedData(data, 'fragment', state.fragment)
-
debug('onCopy', { event })
}
@@ -146,22 +127,17 @@ function BeforePlugin() {
* On cut.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onCut(event, data, change, editor) {
+ function onCut(event, change, editor) {
if (editor.props.readOnly) return true
const window = getWindow(event.target)
isCopying = true
window.requestAnimationFrame(() => isCopying = false)
- const { state } = change
- defineDeprecatedData(data, 'type', 'fragment')
- defineDeprecatedData(data, 'fragment', state.fragment)
-
debug('onCut', { event })
}
@@ -169,33 +145,84 @@ function BeforePlugin() {
* On drag end.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDragEnd(event, data, change, editor) {
+ function onDragEnd(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
event.stopPropagation()
+
isDragging = false
- isInternalDrag = null
debug('onDragEnd', { event })
}
+ /**
+ * On drag enter.
+ *
+ * @param {Event} event
+ * @param {Change} change
+ * @param {Editor} editor
+ */
+
+ function onDragEnter(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
+ event.stopPropagation()
+
+ debug('onDragEnter', { event })
+ }
+
+ /**
+ * On drag exit.
+ *
+ * @param {Event} event
+ * @param {Change} change
+ * @param {Editor} editor
+ */
+
+ function onDragExit(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
+ event.stopPropagation()
+
+ debug('onDragExit', { event })
+ }
+
+ /**
+ * On drag leave.
+ *
+ * @param {Event} event
+ * @param {Change} change
+ * @param {Editor} editor
+ */
+
+ function onDragLeave(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
+ event.stopPropagation()
+
+ debug('onDragLeave', { event })
+ }
+
/**
* On drag over.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDragOver(event, data, change, editor) {
- if (isDragging) return true
+ function onDragOver(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
event.stopPropagation()
+
+ // If a drag is already in progress, don't do this again.
+ if (!isDragging) return true
+
isDragging = true
- isInternalDrag = false
+ event.nativeEvent.dataTransfer.dropEffect = 'move'
+
+ // You must call `preventDefault` to signal that drops are allowed.
+ event.preventDefault()
debug('onDragOver', { event })
}
@@ -204,29 +231,15 @@ function BeforePlugin() {
* On drag start.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDragStart(event, data, change, editor) {
+ function onDragStart(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
+ event.stopPropagation()
+
isDragging = true
- isInternalDrag = true
-
- const d = getEventTransfer(event)
- const { nativeEvent } = event
- const { dataTransfer } = nativeEvent
-
- Object.keys(d).forEach((key) => {
- defineDeprecatedData(data, key, d[key])
- })
-
- if (d.type != 'node') {
- const { state } = change
- const { fragment } = state
- const encoded = Base64.serializeNode(fragment)
- setTransferData(dataTransfer, TRANSFER_TYPES.FRAGMENT, encoded)
- }
debug('onDragStart', { event })
}
@@ -235,43 +248,19 @@ function BeforePlugin() {
* On drop.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onDrop(event, data, change, editor) {
+ function onDrop(event, change, editor) {
+ // Stop propagation so the event isn't visible to parent editors.
event.stopPropagation()
- event.preventDefault()
+ // Nothing happens in read-only mode.
if (editor.props.readOnly) return true
- const { state } = change
- const { nativeEvent } = event
- const { dataTransfer } = nativeEvent
- const d = getEventTransfer(event)
-
- Object.keys(d).forEach((key) => {
- defineDeprecatedData(data, key, d[key])
- })
-
- const range = getEventRange(event, state)
- if (!range) return true
-
- // Add drop-specific information to the data.
- defineDeprecatedData(data, 'target', range)
-
- // COMPAT: Edge throws "Permission denied" errors when
- // accessing `dropEffect` or `effectAllowed` (2017/7/12)
- try {
- defineDeprecatedData(data, 'effect', dataTransfer.dropEffect)
- } catch (err) {
- defineDeprecatedData(data, 'effect', null)
- }
-
- if (d.type == 'fragment' || d.type == 'node') {
- defineDeprecatedData(data, 'isInternal', isInternalDrag)
- }
+ // Prevent default so the DOM's state isn't corrupted.
+ event.preventDefault()
debug('onDrop', { event })
}
@@ -280,12 +269,11 @@ function BeforePlugin() {
* On focus.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onFocus(event, data, change, editor) {
+ function onFocus(event, change, editor) {
if (isCopying) return true
if (editor.props.readOnly) return true
@@ -306,12 +294,11 @@ function BeforePlugin() {
* On input.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onInput(event, data, change, editor) {
+ function onInput(event, change, editor) {
if (isComposing) return true
if (change.state.isBlurred) return true
@@ -322,23 +309,17 @@ function BeforePlugin() {
* On key down.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onKeyDown(event, data, change, editor) {
+ function onKeyDown(event, change, editor) {
if (editor.props.readOnly) return true
- const { key } = event
-
// When composing, these characters commit the composition but also move the
// selection before we're able to handle it, so prevent their default,
// selection-moving behavior.
- if (
- isComposing &&
- (key == 'ArrowLeft' || key == 'ArrowRight' || key == 'ArrowUp' || key == 'ArrowDown')
- ) {
+ if (isComposing && HOTKEYS.COMPOSING(event)) {
event.preventDefault()
return true
}
@@ -349,59 +330,22 @@ function BeforePlugin() {
event.preventDefault()
}
- // Keep track of an `isShifting` flag, because it's often used to trigger
- // "Paste and Match Style" commands, but isn't available on the event in a
- // normal paste event.
- if (key == 'Shift') {
- isShifting = true
- }
-
- // COMPAT: add the deprecated keyboard event properties.
- addDeprecatedKeyProperties(data, event)
-
debug('onKeyDown', { event })
}
- /**
- * On key up.
- *
- * @param {Event} event
- * @param {Object} data
- * @param {Change} change
- * @param {Editor} editor
- */
-
- function onKeyUp(event, data, change, editor) {
- // COMPAT: add the deprecated keyboard event properties.
- addDeprecatedKeyProperties(data, event)
-
- if (event.key == 'Shift') {
- isShifting = false
- }
-
- debug('onKeyUp', { event })
- }
-
/**
* On paste.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onPaste(event, data, change, editor) {
+ function onPaste(event, change, editor) {
if (editor.props.readOnly) return true
+ // Prevent defaults so the DOM state isn't corrupted.
event.preventDefault()
- const d = getEventTransfer(event)
-
- Object.keys(d).forEach((key) => {
- defineDeprecatedData(data, key, d[key])
- })
-
- defineDeprecatedData(data, 'isShift', isShifting)
debug('onPaste', { event })
}
@@ -410,84 +354,15 @@ function BeforePlugin() {
* On select.
*
* @param {Event} event
- * @param {Object} data
* @param {Change} change
* @param {Editor} editor
*/
- function onSelect(event, data, change, editor) {
+ function onSelect(event, change, editor) {
if (isCopying) return true
if (isComposing) return true
if (editor.props.readOnly) return true
- const window = getWindow(event.target)
- const { state } = change
- const { document, selection } = state
- const native = window.getSelection()
-
- // If there are no ranges, the editor was blurred natively.
- if (!native.rangeCount) {
- defineDeprecatedData(data, 'selection', selection.blur())
- }
-
- // Otherwise, determine the Slate selection from the native one.
- else {
- let range = findRange(native, state)
- if (!range) return true
-
- 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)
-
- // 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 '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 &&
- !anchorBlock.isVoid &&
- anchorOffset == 0 &&
- focusBlock &&
- focusBlock.isVoid &&
- focusOffset != 0
- ) {
- range = range.set('focusOffset', 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 &&
- !anchorInline.isVoid &&
- anchorOffset == anchorText.text.length
- ) {
- const block = document.getClosestBlock(anchorKey)
- const next = block.getNextText(anchorKey)
- if (next) range = range.moveAnchorTo(next.key, 0)
- }
-
- if (
- focusInline &&
- !focusInline.isVoid &&
- focusOffset == focusText.text.length
- ) {
- const block = document.getClosestBlock(focusKey)
- const next = block.getNextText(focusKey)
- if (next) range = range.moveFocusTo(next.key, 0)
- }
-
- range = range.normalize(document)
- defineDeprecatedData(data, 'selection', range)
- }
-
debug('onSelect', { event })
}
@@ -505,48 +380,20 @@ function BeforePlugin() {
onCopy,
onCut,
onDragEnd,
+ onDragEnter,
+ onDragExit,
+ onDragLeave,
onDragOver,
onDragStart,
onDrop,
onFocus,
onInput,
onKeyDown,
- onKeyUp,
onPaste,
onSelect,
}
}
-/**
- * Deprecated.
- */
-
-function defineDeprecatedData(data, key, value) {
- Object.defineProperty(data, key, {
- enumerable: true,
- get() {
- logger.deprecate('slate-react@0.5.0', `Accessing the \`data.${key}\` property is deprecated, please use the native \`event\` properties instead, or one of the newly exposed helper utilities.`)
- return value
- }
- })
-}
-
-function addDeprecatedKeyProperties(data, event) {
- const { altKey, ctrlKey, metaKey, shiftKey, which } = event
- const name = keycode(which)
- defineDeprecatedData(data, 'code', which)
- defineDeprecatedData(data, 'key', name)
- defineDeprecatedData(data, 'isAlt', altKey)
- defineDeprecatedData(data, 'isCmd', IS_MAC ? metaKey && !altKey : false)
- defineDeprecatedData(data, 'isCtrl', ctrlKey && !altKey)
- defineDeprecatedData(data, 'isLine', IS_MAC ? metaKey : false)
- defineDeprecatedData(data, 'isMeta', metaKey)
- defineDeprecatedData(data, 'isMod', IS_MAC ? metaKey && !altKey : ctrlKey && !altKey)
- defineDeprecatedData(data, 'isModAlt', IS_MAC ? metaKey && altKey : ctrlKey && altKey)
- defineDeprecatedData(data, 'isShift', shiftKey)
- defineDeprecatedData(data, 'isWord', IS_MAC ? altKey : ctrlKey)
-}
-
/**
* Export.
*
diff --git a/packages/slate-react/src/utils/find-range.js b/packages/slate-react/src/utils/find-range.js
index 5647f3df5..6ec544e14 100644
--- a/packages/slate-react/src/utils/find-range.js
+++ b/packages/slate-react/src/utils/find-range.js
@@ -15,6 +15,8 @@ import findPoint from './find-point'
function findRange(native, state) {
const el = native.anchorNode || native.startContainer
+ if (!el) return null
+
const window = getWindow(el)
// If the `native` object is a DOM `Range` or `StaticRange` object, change it
diff --git a/packages/slate-react/src/utils/get-event-transfer.js b/packages/slate-react/src/utils/get-event-transfer.js
index a3eed8a1f..6e953fb57 100644
--- a/packages/slate-react/src/utils/get-event-transfer.js
+++ b/packages/slate-react/src/utils/get-event-transfer.js
@@ -3,6 +3,20 @@ import Base64 from 'slate-base64-serializer'
import TRANSFER_TYPES from '../constants/transfer-types'
+/**
+ * Trasnfer types.
+ *
+ * @type {String}
+ */
+
+const {
+ FRAGMENT,
+ HTML,
+ NODE,
+ RICH,
+ TEXT
+} = TRANSFER_TYPES
+
/**
* Fragment matching regexp for HTML nodes.
*
@@ -24,11 +38,11 @@ function getEventTransfer(event) {
}
const transfer = event.dataTransfer || event.clipboardData
- let fragment = getType(transfer, TRANSFER_TYPES.FRAGMENT)
- let node = getType(transfer, TRANSFER_TYPES.NODE)
- const html = getType(transfer, 'text/html')
- const rich = getType(transfer, 'text/rtf')
- let text = getType(transfer, 'text/plain')
+ let fragment = getType(transfer, FRAGMENT)
+ let node = getType(transfer, NODE)
+ const html = getType(transfer, HTML)
+ const rich = getType(transfer, RICH)
+ let text = getType(transfer, TEXT)
let files
// If there isn't a fragment, but there is HTML, check to see if the HTML is
@@ -48,9 +62,9 @@ function getEventTransfer(event) {
if (text) {
const embeddedTypes = getEmbeddedTypes(text)
- if (embeddedTypes[TRANSFER_TYPES.FRAGMENT]) fragment = embeddedTypes[TRANSFER_TYPES.FRAGMENT]
- if (embeddedTypes[TRANSFER_TYPES.NODE]) node = embeddedTypes[TRANSFER_TYPES.NODE]
- if (embeddedTypes['text/plain']) text = embeddedTypes['text/plain']
+ if (embeddedTypes[FRAGMENT]) fragment = embeddedTypes[FRAGMENT]
+ if (embeddedTypes[NODE]) node = embeddedTypes[NODE]
+ if (embeddedTypes[TEXT]) text = embeddedTypes[TEXT]
}
// Decode a fragment or node if they exist.
@@ -91,8 +105,8 @@ function getEventTransfer(event) {
function getEmbeddedTypes(text) {
const prefix = 'SLATE-DATA-EMBED::'
- if (text.substring(0, prefix.length) !== prefix) {
- return { 'text/plain': text }
+ if (text.substring(0, prefix.length) != prefix) {
+ return { TEXT: text }
}
// Attempt to parse, if fails then just standard text/plain
@@ -141,7 +155,7 @@ function getType(transfer, type) {
if (!transfer.types || !transfer.types.length) {
// COMPAT: In IE 11, there is no `types` field but `getData('Text')`
// is supported`. (2017/06/23)
- return type === 'text/plain' ? transfer.getData('Text') || null : null
+ return type == TEXT ? transfer.getData('Text') || null : null
}
return transfer.types.indexOf(type) !== -1 ? transfer.getData(type) || null : null
diff --git a/packages/slate-react/src/utils/set-transfer-data.js b/packages/slate-react/src/utils/set-event-transfer.js
similarity index 50%
rename from packages/slate-react/src/utils/set-transfer-data.js
rename to packages/slate-react/src/utils/set-event-transfer.js
index 95eecc580..8bb4b6408 100644
--- a/packages/slate-react/src/utils/set-transfer-data.js
+++ b/packages/slate-react/src/utils/set-event-transfer.js
@@ -1,21 +1,43 @@
+import TRANSFER_TYPES from '../constants/transfer-types'
+
/**
- * Set data with `type` and `content` on a `dataTransfer` object.
+ * The default plain text transfer type.
+ *
+ * @type {String}
+ */
+
+const { TEXT } = TRANSFER_TYPES
+
+/**
+ * Set data with `type` and `content` on an `event`.
*
* COMPAT: In Edge, custom types throw errors, so embed all non-standard
* types in text/plain compound object. (2017/7/12)
*
- * @param {DataTransfer} dataTransfer
+ * @param {Event} event
* @param {String} type
* @param {String} content
*/
-function setTransferData(dataTransfer, type, content) {
+function setEventTransfer(event, type, content) {
+ const mime = TRANSFER_TYPES[type.toUpperCase()]
+
+ if (!mime) {
+ throw new Error(`Cannot set unknown transfer type "${mime}"`)
+ }
+
+ if (event.nativeEvent) {
+ event = event.nativeEvent
+ }
+
+ const transfer = event.dataTransfer || event.clipboardData
+
try {
- dataTransfer.setData(type, content)
+ transfer.setData(mime, content)
} catch (err) {
const prefix = 'SLATE-DATA-EMBED::'
- const text = dataTransfer.getData('text/plain')
+ const text = transfer.getData(TEXT)
let obj = {}
// If the existing plain text data is prefixed, it's Slate JSON data.
@@ -29,12 +51,12 @@ function setTransferData(dataTransfer, type, content) {
// Otherwise, it's just set it as is.
else {
- obj['text/plain'] = text
+ obj[TEXT] = text
}
- obj[type] = content
+ obj[mime] = content
const string = `${prefix}${JSON.stringify(obj)}`
- dataTransfer.setData('text/plain', string)
+ transfer.setData(TEXT, string)
}
}
@@ -44,4 +66,4 @@ function setTransferData(dataTransfer, type, content) {
* @type {Function}
*/
-export default setTransferData
+export default setEventTransfer
diff --git a/packages/slate-react/test/rendering/fixtures/custom-block-void.js b/packages/slate-react/test/rendering/fixtures/custom-block-void.js
index 3c684f90e..f1a8bedf5 100644
--- a/packages/slate-react/test/rendering/fixtures/custom-block-void.js
+++ b/packages/slate-react/test/rendering/fixtures/custom-block-void.js
@@ -23,7 +23,7 @@ export const state = (
export const output = `
-
+
diff --git a/packages/slate-react/test/rendering/fixtures/custom-inline-void.js b/packages/slate-react/test/rendering/fixtures/custom-inline-void.js
index d1627c4a2..b7da3c6f4 100644
--- a/packages/slate-react/test/rendering/fixtures/custom-inline-void.js
+++ b/packages/slate-react/test/rendering/fixtures/custom-inline-void.js
@@ -31,7 +31,7 @@ export const output = `
-
+