mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 20:51:20 +02:00
remove keyboard data.*
properties (#1235)
* update examples and walkthroughs * deprecate data keyboard properties * update examples * add is-hotkey to resources doc * udpate docs * update docs * fix split-block test
This commit is contained in:
@@ -6,7 +6,8 @@ A few resources that are helpful for building with Slate.
|
||||
|
||||
## Libraries
|
||||
|
||||
- [`react-broadcast`] works well when you need to have your custom node components re-render based on state that lives outside the `document`. It's the same pattern that `react-router` uses to update `<Link>` components.
|
||||
- [`is-hotkey`](https://github.com/ianstormtaylor/is-hotkey) is a simple way to check whether an `onKeyDown` handler should fire for a given hotkey, handling cross-platform concerns like <kbd>cmd</kbd> vs. <kbd>ctrl</kbd> keys for you automatically.
|
||||
- [`react-broadcast`](https://github.com/ReactTraining/react-broadcast) works well when you need to have your custom node components re-render based on state that lives outside the `document`. It's the same pattern that `react-router` uses to update `<Link>` components.
|
||||
|
||||
|
||||
## Tooling
|
||||
|
@@ -115,30 +115,6 @@ If no other plugin handles this event, it will be handled by the [Core plugin](.
|
||||
|
||||
This handler is called when any key is pressed in the `contenteditable` element, before any action is taken.
|
||||
|
||||
The `data` object contains the `key` which is a string name of the key that was pressed, as well as it's `code`. It also contains a series of helpful utility properties for determining hotkey logic. For example, `isCtrl` is true if the `control` key was pressed before.
|
||||
|
||||
```js
|
||||
{
|
||||
key: String,
|
||||
code: Number,
|
||||
isAlt: Boolean,
|
||||
isCmd: Boolean,
|
||||
isCtrl: Boolean,
|
||||
isLine: Boolean,
|
||||
isMeta: Boolean,
|
||||
isMod: Boolean,
|
||||
isModAlt: Boolean,
|
||||
isShift: Boolean,
|
||||
isWord: Boolean
|
||||
}
|
||||
```
|
||||
|
||||
The `isMod` boolean is `true` if the `control` key was pressed on Windows or the `command` key was pressed on Mac _without_ the `alt/option` key also being pressed. This is a convenience for adding hotkeys like `command+b`.
|
||||
|
||||
The `isModAlt` boolean is `true` if the `control` key was pressed on Windows or the `command` key was pressed on Mac _and_ the `alt/option` key was also being pressed. This is a convenience for secondary hotkeys like `command+option+1`.
|
||||
|
||||
The `isLine` and `isWord` booleans represent whether the "line modifier" or "word modifier" hotkeys are pressed when deleting or moving the cursor. For example, on a Mac `option + right` moves the cursor to the right one word at a time.
|
||||
|
||||
Make sure to `event.preventDefault()` if you do not want the default insertion behavior to occur! If no other plugin handles this event, it will be handled by the [Core plugin](./core.md).
|
||||
|
||||
### `onKeyUp`
|
||||
@@ -146,8 +122,6 @@ Make sure to `event.preventDefault()` if you do not want the default insertion b
|
||||
|
||||
This handler is called when any key is released in the `contenteditable` element.
|
||||
|
||||
The `data` object contains the same information as the `data` object of `onKeyDown`.
|
||||
|
||||
### `onPaste`
|
||||
`Function onPaste(event: Event, data: Object, change: Change, editor: Editor) => Change || Void`
|
||||
|
||||
|
@@ -49,9 +49,9 @@ class App extends React.Component {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
// Define a new handler which prints the key code that was pressed.
|
||||
// Define a new handler which prints the key that was pressed.
|
||||
onKeyDown = (event, data, change) => {
|
||||
console.log(event.which)
|
||||
console.log(event.key)
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -85,8 +85,8 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
// Return with no changes if it's not the "7" key with shift pressed.
|
||||
if (event.which != 55 || !event.shiftKey) return
|
||||
// Return with no changes if it's not the "&" key.
|
||||
if (event.key != '&') return
|
||||
|
||||
// Prevent the ampersand character from being inserted.
|
||||
event.preventDefault()
|
||||
|
@@ -28,7 +28,7 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
if (event.which != 67 || !event.metaKey || !event.altKey) return
|
||||
if (event.key != '`' || !event.metaKey) return
|
||||
event.preventDefault()
|
||||
const isCode = change.state.blocks.some(block => block.type == 'code')
|
||||
|
||||
@@ -72,16 +72,15 @@ class App extends React.Component {
|
||||
if (!event.metaKey) return
|
||||
|
||||
// Decide what to do based on the key code...
|
||||
switch (event.which) {
|
||||
switch (event.key) {
|
||||
// When "B" is pressed, add a "bold" mark to the text.
|
||||
case 66: {
|
||||
case 'b': {
|
||||
event.preventDefault()
|
||||
change.addMark('bold')
|
||||
return true
|
||||
}
|
||||
// When "`" is pressed, keep our existing code block logic.
|
||||
case 67: {
|
||||
if (!event.altKey) return
|
||||
case '`': {
|
||||
const isCode = change.state.blocks.some(block => block.type == 'code')
|
||||
event.preventDefault()
|
||||
change.setBlock(isCode ? 'paragraph' : 'code')
|
||||
@@ -148,14 +147,13 @@ class App extends React.Component {
|
||||
onKeyDown = (event, data, change) => {
|
||||
if (!event.metaKey) return
|
||||
|
||||
switch (event.which) {
|
||||
case 66: {
|
||||
switch (event.key) {
|
||||
case 'b': {
|
||||
event.preventDefault()
|
||||
change.toggleMark('bold')
|
||||
return true
|
||||
}
|
||||
case 67: {
|
||||
if (!event.altKey) return
|
||||
case '`': {
|
||||
const isCode = change.state.blocks.some(block => block.type == 'code')
|
||||
event.preventDefault()
|
||||
state.setBlock(isCode ? 'paragraph' : 'code')
|
||||
|
@@ -23,10 +23,8 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
if (event.which != 55 || !event.shiftKey) return
|
||||
|
||||
if (event.key != '&') return
|
||||
event.preventDefault()
|
||||
|
||||
change.insertText('and');
|
||||
return true
|
||||
}
|
||||
@@ -87,10 +85,8 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
if (event.which != 55 || !event.shiftKey) return
|
||||
|
||||
if (event.key != '&') return
|
||||
event.preventDefault()
|
||||
|
||||
change.insertText('and')
|
||||
return true
|
||||
}
|
||||
@@ -110,7 +106,7 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
Okay, but now we'll need a way for the user to actually turn a block into a code block. So let's change our `onKeyDown` function to add a `⌘-Alt-C` shortcut that does just that:
|
||||
Okay, but now we'll need a way for the user to actually turn a block into a code block. So let's change our `onKeyDown` function to add a `⌘-\`` shortcut that does just that:
|
||||
|
||||
```js
|
||||
function CodeNode(props) {
|
||||
@@ -134,7 +130,7 @@ class App extends React.Component {
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
// Return with no changes if it's not the "`" key with cmd/ctrl pressed.
|
||||
if (event.which != 67 || !event.metaKey || !event.altKey) return
|
||||
if (event.key != '`' || !event.metaKey) return
|
||||
|
||||
// Prevent the "`" from being inserted by default.
|
||||
event.preventDefault()
|
||||
@@ -158,9 +154,9 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you press `⌘-Alt-C`, the block your cursor is in should turn into a code block! Magic!
|
||||
Now, if you press `⌘-\`` the block your cursor is in should turn into a code block! Magic!
|
||||
|
||||
But we forgot one thing. When you hit `⌘-Alt-C` again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:
|
||||
But we forgot one thing. When you hit `⌘-\`` again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:
|
||||
|
||||
```js
|
||||
function CodeNode(props) {
|
||||
@@ -183,7 +179,7 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
if (event.which != 67 || !event.metaKey || !event.altKey) return
|
||||
if (event.key != '`' || !event.metaKey) return
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
@@ -209,7 +205,7 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
And there you have it! If you press `⌘-Alt-C` while inside a code block, it should turn back into a paragraph!
|
||||
And there you have it! If you press `⌘-\`` while inside a code block, it should turn back into a paragraph!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>
|
||||
|
@@ -30,7 +30,7 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
onKeyDown = (event, data, change) => {
|
||||
if (!event.metaKey || event.which != 66) return
|
||||
if (event.key != 'b' || !event.metaKey) return
|
||||
event.preventDefault()
|
||||
change.toggleMark('bold')
|
||||
return true
|
||||
@@ -50,12 +50,12 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
Let's write a new function, that takes a set of options: the mark `type` to toggle and the key `code` to press.
|
||||
Let's write a new function, that takes a set of options: the mark `type` to toggle and the `key` to press.
|
||||
|
||||
```js
|
||||
function MarkHotkey(options) {
|
||||
// Grab our options from the ones passed in.
|
||||
const { type, code, isAltKey = false } = options
|
||||
const { type, key } = options
|
||||
}
|
||||
```
|
||||
|
||||
@@ -67,13 +67,13 @@ In this case our plugin object will have one property: a `onKeyDown` handler, wi
|
||||
|
||||
```js
|
||||
function MarkHotkey(options) {
|
||||
const { type, code, isAltKey = false } = options
|
||||
const { type, key } = options
|
||||
|
||||
// Return our "plugin" object, containing the `onKeyDown` handler.
|
||||
return {
|
||||
onKeyDown(event, data, change) {
|
||||
// Check that the key pressed matches our `code` option.
|
||||
if (!event.metaKey || event.which != code || event.altKey != isAltKey) return
|
||||
// Check that the key pressed matches our `key` option.
|
||||
if (!event.metaKey || event.key != key) return
|
||||
|
||||
// Prevent the default characters from being inserted.
|
||||
event.preventDefault()
|
||||
@@ -94,7 +94,7 @@ Now that we have our plugin, let's remove the hard-coded logic from our app, and
|
||||
// Initialize our bold-mark-adding plugin.
|
||||
const boldPlugin = MarkHotkey({
|
||||
type: 'bold',
|
||||
code: 66
|
||||
key: 'b'
|
||||
})
|
||||
|
||||
// Create an array of plugins.
|
||||
@@ -139,11 +139,11 @@ Let's add _italic_, `code`, ~~strikethrough~~ and underline marks:
|
||||
```js
|
||||
// Initialize a plugin for each mark...
|
||||
const plugins = [
|
||||
MarkHotkey({ code: 66, type: 'bold' }),
|
||||
MarkHotkey({ code: 67, type: 'code', isAltKey: true }),
|
||||
MarkHotkey({ code: 73, type: 'italic' }),
|
||||
MarkHotkey({ code: 68, type: 'strikethrough' }),
|
||||
MarkHotkey({ code: 85, type: 'underline' })
|
||||
MarkHotkey({ key: 'b', type: 'bold' }),
|
||||
MarkHotkey({ key: '`', type: 'code' }),
|
||||
MarkHotkey({ key: 'i', type: 'italic' }),
|
||||
MarkHotkey({ key: '~', type: 'strikethrough' }),
|
||||
MarkHotkey({ key: 'u', type: 'underline' })
|
||||
]
|
||||
|
||||
class App extends React.Component {
|
||||
@@ -182,87 +182,6 @@ class App extends React.Component {
|
||||
|
||||
And there you have it! We just added a ton of functionality to the editor with very little work. And we can keep all of our mark hotkey logic tested and isolated in a single place, making maintaining the code easier.
|
||||
|
||||
Of course... now that it's reusable, we could actually make our `MarkHotkey` plugin a little easier to use. What if instead of a `code` argument it took the text of the `key` itself? That would make the calling code a lot clearer, since key codes are really obtuse.
|
||||
|
||||
In fact, unless you have weirdly good keycode knowledge, you probably have no idea what our current hotkeys actually are.
|
||||
|
||||
Let's fix that.
|
||||
|
||||
Using the `keycode` module in npm makes this dead simple.
|
||||
|
||||
First install it:
|
||||
|
||||
```
|
||||
npm install keycode
|
||||
```
|
||||
|
||||
And then we can add it our plugin:
|
||||
|
||||
```js
|
||||
// Import the keycode module.
|
||||
import keycode from `keycode`
|
||||
|
||||
function MarkHotkey(options) {
|
||||
// Change the options to take a `key`.
|
||||
const { type, key, isAltKey = false } = options
|
||||
|
||||
return {
|
||||
onKeyDown(event, data, change) {
|
||||
// Change the comparison to use the key name.
|
||||
if (!event.metaKey || keycode(event.which) != key || event.altKey != isAltKey) return
|
||||
event.preventDefault()
|
||||
change.toggleMark(type)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And now we can make our app code much clearer for the next person who reads it:
|
||||
|
||||
```js
|
||||
// Use the much clearer key names instead of key codes!
|
||||
const plugins = [
|
||||
MarkHotkey({ key: 'b', type: 'bold' }),
|
||||
MarkHotkey({ key: 'c', type: 'code', isAltKey: true }),
|
||||
MarkHotkey({ key: 'i', type: 'italic' }),
|
||||
MarkHotkey({ key: 'd', type: 'strikethrough' }),
|
||||
MarkHotkey({ key: 'u', type: 'underline' })
|
||||
]
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
marks: {
|
||||
bold: props => <strong>{props.children}</strong>,
|
||||
code: props => <code>{props.children}</code>,
|
||||
italic: props => <em>{props.children}</em>,
|
||||
strikethrough: props => <del>{props.children}</del>,
|
||||
underline: props => <u>{props.children}</u>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChange = ({ state }) => {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
plugins={plugins}
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
That's why plugins are awesome. They let you get really expressive while also making your codebase easier to manage. And since Slate is built with plugins as a primary consideration, using them is dead simple!
|
||||
|
||||
|
||||
|
@@ -116,7 +116,7 @@ class CheckLists extends React.Component {
|
||||
const { state } = change
|
||||
|
||||
if (
|
||||
data.key == 'enter' &&
|
||||
e.key == 'Enter' &&
|
||||
state.startBlock.type == 'check-list-item'
|
||||
) {
|
||||
return change
|
||||
@@ -125,7 +125,7 @@ class CheckLists extends React.Component {
|
||||
}
|
||||
|
||||
if (
|
||||
data.key == 'backspace' &&
|
||||
e.key == 'Backspace' &&
|
||||
state.isCollapsed &&
|
||||
state.startBlock.type == 'check-list-item' &&
|
||||
state.selection.startOffset == 0
|
||||
|
@@ -174,7 +174,7 @@ class CodeHighlighting extends React.Component {
|
||||
onKeyDown = (e, data, change) => {
|
||||
const { state } = change
|
||||
const { startBlock } = state
|
||||
if (data.key != 'enter') return
|
||||
if (e.key != 'Enter') return
|
||||
if (startBlock.type != 'code') return
|
||||
if (state.isExpanded) change.delete()
|
||||
return change.insertText('\n')
|
||||
|
@@ -96,40 +96,6 @@ class HugeDocument extends React.Component {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
/**
|
||||
* On key down, if it's a formatting command toggle a mark.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
if (!data.isMod) return
|
||||
let mark
|
||||
|
||||
switch (data.key) {
|
||||
case 'b':
|
||||
mark = 'bold'
|
||||
break
|
||||
case 'i':
|
||||
mark = 'italic'
|
||||
break
|
||||
case 'u':
|
||||
mark = 'underlined'
|
||||
break
|
||||
case '`':
|
||||
mark = 'code'
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
change.toggleMark(mark)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the editor.
|
||||
*
|
||||
|
@@ -105,10 +105,10 @@ class MarkdownShortcuts extends React.Component {
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
switch (data.key) {
|
||||
case 'space': return this.onSpace(e, change)
|
||||
case 'backspace': return this.onBackspace(e, change)
|
||||
case 'enter': return this.onEnter(e, change)
|
||||
switch (e.key) {
|
||||
case ' ': return this.onSpace(e, change)
|
||||
case 'Backspace': return this.onBackspace(e, change)
|
||||
case 'Enter': return this.onEnter(e, change)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,14 +3,28 @@ import { Editor } from 'slate-react'
|
||||
import { State } from 'slate'
|
||||
|
||||
import React from 'react'
|
||||
import isHotkey from 'is-hotkey'
|
||||
import initialState from './state.json'
|
||||
|
||||
/**
|
||||
* Define the default node type.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const DEFAULT_NODE = 'paragraph'
|
||||
|
||||
/**
|
||||
* Hotkey matchers.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const isBoldHotkey = isHotkey('mod+b')
|
||||
const isItalicHotkey = isHotkey('mod+i')
|
||||
const isUnderlinedHotkey = isHotkey('mod+u')
|
||||
const isCodeHotkey = isHotkey('mod+`')
|
||||
|
||||
/**
|
||||
* Define a schema.
|
||||
*
|
||||
@@ -107,23 +121,17 @@ class RichTextExample extends React.Component {
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
if (!data.isMod) return
|
||||
let mark
|
||||
|
||||
switch (data.key) {
|
||||
case 'b':
|
||||
if (isBoldHotkey(e)) {
|
||||
mark = 'bold'
|
||||
break
|
||||
case 'i':
|
||||
} else if (isItalicHotkey(e)) {
|
||||
mark = 'italic'
|
||||
break
|
||||
case 'u':
|
||||
} else if (isUnderlinedHotkey(e)) {
|
||||
mark = 'underlined'
|
||||
break
|
||||
case '`':
|
||||
} else if (isCodeHotkey(e)) {
|
||||
mark = 'code'
|
||||
break
|
||||
default:
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -54,7 +54,7 @@ class PlainText extends React.Component {
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
if (data.key == 'enter' && data.isShift) {
|
||||
if (e.key == 'Enter' && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
change.insertText('\n')
|
||||
return true
|
||||
|
@@ -3,8 +3,20 @@ import { Editor } from 'slate-react'
|
||||
import { State } from 'slate'
|
||||
|
||||
import React from 'react'
|
||||
import isHotkey from 'is-hotkey'
|
||||
import initialState from './state.json'
|
||||
|
||||
/**
|
||||
* Hotkey matchers.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const isBoldHotkey = isHotkey('mod+b')
|
||||
const isItalicHotkey = isHotkey('mod+i')
|
||||
const isUnderlinedHotkey = isHotkey('mod+u')
|
||||
const isCodeHotkey = isHotkey('mod+`')
|
||||
|
||||
/**
|
||||
* Define a schema.
|
||||
*
|
||||
@@ -100,23 +112,17 @@ class SyncingEditor extends React.Component {
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
if (!data.isMod) return
|
||||
let mark
|
||||
|
||||
switch (data.key) {
|
||||
case 'b':
|
||||
if (isBoldHotkey(e)) {
|
||||
mark = 'bold'
|
||||
break
|
||||
case 'i':
|
||||
} else if (isItalicHotkey(e)) {
|
||||
mark = 'italic'
|
||||
break
|
||||
case 'u':
|
||||
} else if (isUnderlinedHotkey(e)) {
|
||||
mark = 'underlined'
|
||||
break
|
||||
case '`':
|
||||
} else if (isCodeHotkey(e)) {
|
||||
mark = 'code'
|
||||
break
|
||||
default:
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -118,10 +118,10 @@ class Tables extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
switch (data.key) {
|
||||
case 'backspace': return this.onBackspace(e, state)
|
||||
case 'delete': return this.onDelete(e, state)
|
||||
case 'enter': return this.onEnter(e, state)
|
||||
switch (e.key) {
|
||||
case 'Backspace': return this.onBackspace(e, state)
|
||||
case 'Delete': return this.onDelete(e, state)
|
||||
case 'Enter': return this.onEnter(e, state)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@
|
||||
"gh-pages": "^0.11.0",
|
||||
"http-server": "^0.9.0",
|
||||
"immutable": "^3.8.1",
|
||||
"is-hotkey": "^0.0.1",
|
||||
"is-image": "^1.0.1",
|
||||
"is-url": "^1.2.2",
|
||||
"jest": "^17.0.3",
|
||||
|
@@ -572,14 +572,17 @@ class Content extends React.Component {
|
||||
if (this.props.readOnly) return
|
||||
if (!this.isInEditor(event.target)) return
|
||||
|
||||
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
||||
const key = keycode(which)
|
||||
const { key, metaKey, ctrlKey } = event
|
||||
const data = {}
|
||||
const modKey = IS_MAC ? metaKey : ctrlKey
|
||||
|
||||
// COMPAT: add the deprecated keyboard event properties.
|
||||
addDeprecatedKeyProperties(data, event)
|
||||
|
||||
// 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') {
|
||||
if (key == 'Shift') {
|
||||
this.tmp.isShifting = true
|
||||
}
|
||||
|
||||
@@ -588,35 +591,23 @@ class Content extends React.Component {
|
||||
// selection-moving behavior.
|
||||
if (
|
||||
this.tmp.isComposing &&
|
||||
(key == 'left' || key == 'right' || key == 'up' || key == 'down')
|
||||
(key == 'ArrowLeft' || key == 'ArrowRight' || key == 'ArrowUp' || key == 'ArrowDown')
|
||||
) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
// Add helpful properties for handling hotkeys to the data object.
|
||||
data.code = which
|
||||
data.key = key
|
||||
data.isAlt = altKey
|
||||
data.isCmd = IS_MAC ? metaKey && !altKey : false
|
||||
data.isCtrl = ctrlKey && !altKey
|
||||
data.isLine = IS_MAC ? metaKey : false
|
||||
data.isMeta = metaKey
|
||||
data.isMod = IS_MAC ? metaKey && !altKey : ctrlKey && !altKey
|
||||
data.isModAlt = IS_MAC ? metaKey && altKey : ctrlKey && altKey
|
||||
data.isShift = shiftKey
|
||||
data.isWord = IS_MAC ? altKey : ctrlKey
|
||||
|
||||
// These key commands have native behavior in contenteditable elements which
|
||||
// will cause our state to be out of sync, so prevent them.
|
||||
if (
|
||||
(key == 'enter') ||
|
||||
(key == 'backspace') ||
|
||||
(key == 'delete') ||
|
||||
(key == 'b' && data.isMod) ||
|
||||
(key == 'i' && data.isMod) ||
|
||||
(key == 'y' && data.isMod) ||
|
||||
(key == 'z' && data.isMod)
|
||||
(key == 'Enter') ||
|
||||
(key == 'Backspace') ||
|
||||
(key == 'Delete') ||
|
||||
(key == 'b' && modKey) ||
|
||||
(key == 'i' && modKey) ||
|
||||
(key == 'y' && modKey) ||
|
||||
(key == 'z' && modKey) ||
|
||||
(key == 'Z' && modKey)
|
||||
) {
|
||||
event.preventDefault()
|
||||
}
|
||||
@@ -632,27 +623,15 @@ class Content extends React.Component {
|
||||
*/
|
||||
|
||||
onKeyUp = (event) => {
|
||||
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
||||
const key = keycode(which)
|
||||
const data = {}
|
||||
|
||||
if (key == 'shift') {
|
||||
// COMPAT: add the deprecated keyboard event properties.
|
||||
addDeprecatedKeyProperties(data, event)
|
||||
|
||||
if (event.key == 'Shift') {
|
||||
this.tmp.isShifting = false
|
||||
}
|
||||
|
||||
// Add helpful properties for handling hotkeys to the data object.
|
||||
data.code = which
|
||||
data.key = key
|
||||
data.isAlt = altKey
|
||||
data.isCmd = IS_MAC ? metaKey && !altKey : false
|
||||
data.isCtrl = ctrlKey && !altKey
|
||||
data.isLine = IS_MAC ? metaKey : false
|
||||
data.isMeta = metaKey
|
||||
data.isMod = IS_MAC ? metaKey && !altKey : ctrlKey && !altKey
|
||||
data.isModAlt = IS_MAC ? metaKey && altKey : ctrlKey && altKey
|
||||
data.isShift = shiftKey
|
||||
data.isWord = IS_MAC ? altKey : ctrlKey
|
||||
|
||||
debug('onKeyUp', { event, data })
|
||||
this.props.onKeyUp(event, data)
|
||||
}
|
||||
@@ -669,9 +648,16 @@ class Content extends React.Component {
|
||||
|
||||
const data = getTransferData(event.clipboardData)
|
||||
|
||||
// Attach the `isShift` flag, so that people can use it to trigger "Paste
|
||||
// and Match Style" logic.
|
||||
data.isShift = !!this.tmp.isShifting
|
||||
// COMPAT: Attach the `isShift` flag, so that people can use it to trigger
|
||||
// "Paste and Match Style" logic.
|
||||
Object.defineProperty(data, 'isShift', {
|
||||
enumerable: true,
|
||||
get() {
|
||||
logger.deprecate('0.28.0', 'The `data.isShift` property of paste events has been deprecated. If you need this functionality, you\'ll need to keep track of that state with `onKeyDown` and `onKeyUp` events instead')
|
||||
return !!this.tmp.isShifting
|
||||
}
|
||||
})
|
||||
|
||||
debug('onPaste', { event, data })
|
||||
|
||||
// COMPAT: In IE 11, only plain text can be retrieved from the event's
|
||||
@@ -897,6 +883,40 @@ class Content extends React.Component {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add deprecated `data` fields from a key `event`.
|
||||
*
|
||||
* @param {Object} data
|
||||
* @param {Object} event
|
||||
*/
|
||||
|
||||
function addDeprecatedKeyProperties(data, event) {
|
||||
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
||||
const name = keycode(which)
|
||||
|
||||
function define(key, value) {
|
||||
Object.defineProperty(data, key, {
|
||||
enumerable: true,
|
||||
get() {
|
||||
logger.deprecate('0.28.0', `The \`data.${key}\` property of keyboard events is deprecated, please use the native \`event\` properties instead.`)
|
||||
return value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
define('code', which)
|
||||
define('key', name)
|
||||
define('isAlt', altKey)
|
||||
define('isCmd', IS_MAC ? metaKey && !altKey : false)
|
||||
define('isCtrl', ctrlKey && !altKey)
|
||||
define('isLine', IS_MAC ? metaKey : false)
|
||||
define('isMeta', metaKey)
|
||||
define('isMod', IS_MAC ? metaKey && !altKey : ctrlKey && !altKey)
|
||||
define('isModAlt', IS_MAC ? metaKey && altKey : ctrlKey && altKey)
|
||||
define('isShift', shiftKey)
|
||||
define('isWord', IS_MAC ? altKey : ctrlKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
|
@@ -407,19 +407,20 @@ function Plugin(options = {}) {
|
||||
function onKeyDown(e, data, change) {
|
||||
debug('onKeyDown', { data })
|
||||
|
||||
switch (data.key) {
|
||||
case 'enter': return onKeyDownEnter(e, data, change)
|
||||
case 'backspace': return onKeyDownBackspace(e, data, change)
|
||||
case 'delete': return onKeyDownDelete(e, data, change)
|
||||
case 'left': return onKeyDownLeft(e, data, change)
|
||||
case 'right': return onKeyDownRight(e, data, change)
|
||||
case 'up': return onKeyDownUp(e, data, change)
|
||||
case 'down': return onKeyDownDown(e, data, change)
|
||||
switch (e.key) {
|
||||
case 'Enter': return onKeyDownEnter(e, data, change)
|
||||
case 'Backspace': return onKeyDownBackspace(e, data, change)
|
||||
case 'Delete': return onKeyDownDelete(e, data, change)
|
||||
case 'ArrowLeft': return onKeyDownLeft(e, data, change)
|
||||
case 'ArrowRight': return onKeyDownRight(e, data, change)
|
||||
case 'ArrowUp': return onKeyDownUp(e, data, change)
|
||||
case 'ArrowDown': return onKeyDownDown(e, data, change)
|
||||
case 'd': return onKeyDownD(e, data, change)
|
||||
case 'h': return onKeyDownH(e, data, change)
|
||||
case 'k': return onKeyDownK(e, data, change)
|
||||
case 'y': return onKeyDownY(e, data, change)
|
||||
case 'z': return onKeyDownZ(e, data, change)
|
||||
case 'z':
|
||||
case 'Z': return onKeyDownZ(e, data, change)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,9 +458,13 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownBackspace(e, data, change) {
|
||||
const isWord = IS_MAC ? e.altKey : e.ctrlKey
|
||||
const isLine = IS_MAC ? e.metaKey : false
|
||||
|
||||
let boundary = 'Char'
|
||||
if (data.isWord) boundary = 'Word'
|
||||
if (data.isLine) boundary = 'Line'
|
||||
if (isWord) boundary = 'Word'
|
||||
if (isLine) boundary = 'Line'
|
||||
|
||||
change[`delete${boundary}Backward`]()
|
||||
}
|
||||
|
||||
@@ -472,9 +477,13 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownDelete(e, data, change) {
|
||||
const isWord = IS_MAC ? e.altKey : e.ctrlKey
|
||||
const isLine = IS_MAC ? e.metaKey : false
|
||||
|
||||
let boundary = 'Char'
|
||||
if (data.isWord) boundary = 'Word'
|
||||
if (data.isLine) boundary = 'Line'
|
||||
if (isWord) boundary = 'Word'
|
||||
if (isLine) boundary = 'Line'
|
||||
|
||||
change[`delete${boundary}Forward`]()
|
||||
}
|
||||
|
||||
@@ -496,8 +505,8 @@ function Plugin(options = {}) {
|
||||
function onKeyDownLeft(e, data, change) {
|
||||
const { state } = change
|
||||
|
||||
if (data.isCtrl) return
|
||||
if (data.isAlt) return
|
||||
if (e.ctrlKey) return
|
||||
if (e.altKey) return
|
||||
if (state.isExpanded) return
|
||||
|
||||
const { document, startKey, startText } = state
|
||||
@@ -519,7 +528,7 @@ function Plugin(options = {}) {
|
||||
const previousInline = document.getClosestInline(previous.key)
|
||||
|
||||
if (previousBlock === startBlock && previousInline && !previousInline.isVoid) {
|
||||
const extendOrMove = data.isShift ? 'extend' : 'move'
|
||||
const extendOrMove = e.shiftKey ? 'extend' : 'move'
|
||||
change.collapseToEndOf(previous)[extendOrMove](-1)
|
||||
return
|
||||
}
|
||||
@@ -552,8 +561,8 @@ function Plugin(options = {}) {
|
||||
function onKeyDownRight(e, data, change) {
|
||||
const { state } = change
|
||||
|
||||
if (data.isCtrl) return
|
||||
if (data.isAlt) return
|
||||
if (e.ctrlKey) return
|
||||
if (e.altKey) return
|
||||
if (state.isExpanded) return
|
||||
|
||||
const { document, startKey, startText } = state
|
||||
@@ -581,7 +590,7 @@ function Plugin(options = {}) {
|
||||
const nextInline = document.getClosestInline(next.key)
|
||||
|
||||
if (nextBlock == startBlock && nextInline) {
|
||||
const extendOrMove = data.isShift ? 'extend' : 'move'
|
||||
const extendOrMove = e.shiftKey ? 'extend' : 'move'
|
||||
change.collapseToStartOf(next)[extendOrMove](1)
|
||||
return
|
||||
}
|
||||
@@ -604,11 +613,11 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownUp(e, data, change) {
|
||||
if (!IS_MAC || data.isCtrl || !data.isAlt) return
|
||||
if (!IS_MAC || e.ctrlKey || !e.altKey) return
|
||||
|
||||
const { state } = change
|
||||
const { selection, document, focusKey, focusBlock } = state
|
||||
const transform = data.isShift ? 'extendToStartOf' : 'collapseToStartOf'
|
||||
const transform = e.shiftKey ? 'extendToStartOf' : 'collapseToStartOf'
|
||||
const block = selection.hasFocusAtStartOf(focusBlock)
|
||||
? document.getPreviousBlock(focusKey)
|
||||
: focusBlock
|
||||
@@ -633,11 +642,11 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownDown(e, data, change) {
|
||||
if (!IS_MAC || data.isCtrl || !data.isAlt) return
|
||||
if (!IS_MAC || e.ctrlKey || !e.altKey) return
|
||||
|
||||
const { state } = change
|
||||
const { selection, document, focusKey, focusBlock } = state
|
||||
const transform = data.isShift ? 'extendToEndOf' : 'collapseToEndOf'
|
||||
const transform = e.shiftKey ? 'extendToEndOf' : 'collapseToEndOf'
|
||||
const block = selection.hasFocusAtEndOf(focusBlock)
|
||||
? document.getNextBlock(focusKey)
|
||||
: focusBlock
|
||||
@@ -658,7 +667,7 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownD(e, data, change) {
|
||||
if (!IS_MAC || !data.isCtrl) return
|
||||
if (!IS_MAC || !e.ctrlKey || e.altKey) return
|
||||
e.preventDefault()
|
||||
change.deleteCharForward()
|
||||
}
|
||||
@@ -672,7 +681,7 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownH(e, data, change) {
|
||||
if (!IS_MAC || !data.isCtrl) return
|
||||
if (!IS_MAC || !e.ctrlKey || e.altKey) return
|
||||
e.preventDefault()
|
||||
change.deleteCharBackward()
|
||||
}
|
||||
@@ -686,7 +695,7 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownK(e, data, change) {
|
||||
if (!IS_MAC || !data.isCtrl) return
|
||||
if (!IS_MAC || !e.ctrlKey || e.altKey) return
|
||||
e.preventDefault()
|
||||
change.deleteLineForward()
|
||||
}
|
||||
@@ -700,7 +709,8 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownY(e, data, change) {
|
||||
if (!data.isMod) return
|
||||
const modKey = IS_MAC ? e.metaKey : e.ctrlKey
|
||||
if (!modKey) return
|
||||
change.redo()
|
||||
}
|
||||
|
||||
@@ -713,8 +723,9 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
function onKeyDownZ(e, data, change) {
|
||||
if (!data.isMod) return
|
||||
change[data.isShift ? 'redo' : 'undo']()
|
||||
const modKey = IS_MAC ? e.metaKey : e.ctrlKey
|
||||
if (!modKey) return
|
||||
change[e.shiftKey ? 'redo' : 'undo']()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,7 +3,7 @@
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default function (simulator) {
|
||||
simulator.keyDown(null, { key: 'enter' })
|
||||
simulator.keyDown({ key: 'Enter' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@@ -3360,6 +3360,10 @@ is-glob@^3.1.0:
|
||||
dependencies:
|
||||
is-extglob "^2.1.0"
|
||||
|
||||
is-hotkey@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-hotkey/-/is-hotkey-0.0.1.tgz#d8d817209b34292551a85357e65cdbfcfa763443"
|
||||
|
||||
is-image@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-image/-/is-image-1.0.1.tgz#6fd51a752a1a111506d060d952118b0b989b426e"
|
||||
|
Reference in New Issue
Block a user