1
0
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:
Ian Storm Taylor
2017-10-15 19:23:07 -07:00
committed by GitHub
parent c2ba87d327
commit f69d2c4a12
19 changed files with 200 additions and 296 deletions

View File

@@ -6,7 +6,8 @@ A few resources that are helpful for building with Slate.
## Libraries ## 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 ## Tooling

View File

@@ -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. 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). 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` ### `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. 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` ### `onPaste`
`Function onPaste(event: Event, data: Object, change: Change, editor: Editor) => Change || Void` `Function onPaste(event: Event, data: Object, change: Change, editor: Editor) => Change || Void`

View File

@@ -49,9 +49,9 @@ class App extends React.Component {
this.setState({ state }) 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) => { onKeyDown = (event, data, change) => {
console.log(event.which) console.log(event.key)
} }
render() { render() {
@@ -85,8 +85,8 @@ class App extends React.Component {
} }
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
// Return with no changes if it's not the "7" key with shift pressed. // Return with no changes if it's not the "&" key.
if (event.which != 55 || !event.shiftKey) return if (event.key != '&') return
// Prevent the ampersand character from being inserted. // Prevent the ampersand character from being inserted.
event.preventDefault() event.preventDefault()

View File

@@ -28,7 +28,7 @@ class App extends React.Component {
} }
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
if (event.which != 67 || !event.metaKey || !event.altKey) return if (event.key != '`' || !event.metaKey) return
event.preventDefault() event.preventDefault()
const isCode = change.state.blocks.some(block => block.type == 'code') const isCode = change.state.blocks.some(block => block.type == 'code')
@@ -72,16 +72,15 @@ class App extends React.Component {
if (!event.metaKey) return if (!event.metaKey) return
// Decide what to do based on the key code... // 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. // When "B" is pressed, add a "bold" mark to the text.
case 66: { case 'b': {
event.preventDefault() event.preventDefault()
change.addMark('bold') change.addMark('bold')
return true return true
} }
// When "`" is pressed, keep our existing code block logic. // When "`" is pressed, keep our existing code block logic.
case 67: { case '`': {
if (!event.altKey) return
const isCode = change.state.blocks.some(block => block.type == 'code') const isCode = change.state.blocks.some(block => block.type == 'code')
event.preventDefault() event.preventDefault()
change.setBlock(isCode ? 'paragraph' : 'code') change.setBlock(isCode ? 'paragraph' : 'code')
@@ -148,14 +147,13 @@ class App extends React.Component {
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
if (!event.metaKey) return if (!event.metaKey) return
switch (event.which) { switch (event.key) {
case 66: { case 'b': {
event.preventDefault() event.preventDefault()
change.toggleMark('bold') change.toggleMark('bold')
return true return true
} }
case 67: { case '`': {
if (!event.altKey) return
const isCode = change.state.blocks.some(block => block.type == 'code') const isCode = change.state.blocks.some(block => block.type == 'code')
event.preventDefault() event.preventDefault()
state.setBlock(isCode ? 'paragraph' : 'code') state.setBlock(isCode ? 'paragraph' : 'code')

View File

@@ -23,10 +23,8 @@ class App extends React.Component {
} }
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
if (event.which != 55 || !event.shiftKey) return if (event.key != '&') return
event.preventDefault() event.preventDefault()
change.insertText('and'); change.insertText('and');
return true return true
} }
@@ -87,10 +85,8 @@ class App extends React.Component {
} }
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
if (event.which != 55 || !event.shiftKey) return if (event.key != '&') return
event.preventDefault() event.preventDefault()
change.insertText('and') change.insertText('and')
return true 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 ```js
function CodeNode(props) { function CodeNode(props) {
@@ -134,7 +130,7 @@ class App extends React.Component {
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
// Return with no changes if it's not the "`" key with cmd/ctrl pressed. // 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. // Prevent the "`" from being inserted by default.
event.preventDefault() 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 ```js
function CodeNode(props) { function CodeNode(props) {
@@ -183,7 +179,7 @@ class App extends React.Component {
} }
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
if (event.which != 67 || !event.metaKey || !event.altKey) return if (event.key != '`' || !event.metaKey) return
event.preventDefault() 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/> <br/>
<p align="center"><strong>Next:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p> <p align="center"><strong>Next:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>

View File

@@ -30,7 +30,7 @@ class App extends React.Component {
} }
onKeyDown = (event, data, change) => { onKeyDown = (event, data, change) => {
if (!event.metaKey || event.which != 66) return if (event.key != 'b' || !event.metaKey) return
event.preventDefault() event.preventDefault()
change.toggleMark('bold') change.toggleMark('bold')
return true 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 ```js
function MarkHotkey(options) { function MarkHotkey(options) {
// Grab our options from the ones passed in. // 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 ```js
function MarkHotkey(options) { function MarkHotkey(options) {
const { type, code, isAltKey = false } = options const { type, key } = options
// Return our "plugin" object, containing the `onKeyDown` handler. // Return our "plugin" object, containing the `onKeyDown` handler.
return { return {
onKeyDown(event, data, change) { onKeyDown(event, data, change) {
// Check that the key pressed matches our `code` option. // Check that the key pressed matches our `key` option.
if (!event.metaKey || event.which != code || event.altKey != isAltKey) return if (!event.metaKey || event.key != key) return
// Prevent the default characters from being inserted. // Prevent the default characters from being inserted.
event.preventDefault() 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. // Initialize our bold-mark-adding plugin.
const boldPlugin = MarkHotkey({ const boldPlugin = MarkHotkey({
type: 'bold', type: 'bold',
code: 66 key: 'b'
}) })
// Create an array of plugins. // Create an array of plugins.
@@ -139,11 +139,11 @@ Let's add _italic_, `code`, ~~strikethrough~~ and underline marks:
```js ```js
// Initialize a plugin for each mark... // Initialize a plugin for each mark...
const plugins = [ const plugins = [
MarkHotkey({ code: 66, type: 'bold' }), MarkHotkey({ key: 'b', type: 'bold' }),
MarkHotkey({ code: 67, type: 'code', isAltKey: true }), MarkHotkey({ key: '`', type: 'code' }),
MarkHotkey({ code: 73, type: 'italic' }), MarkHotkey({ key: 'i', type: 'italic' }),
MarkHotkey({ code: 68, type: 'strikethrough' }), MarkHotkey({ key: '~', type: 'strikethrough' }),
MarkHotkey({ code: 85, type: 'underline' }) MarkHotkey({ key: 'u', type: 'underline' })
] ]
class App extends React.Component { 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. 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! 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!

View File

@@ -116,7 +116,7 @@ class CheckLists extends React.Component {
const { state } = change const { state } = change
if ( if (
data.key == 'enter' && e.key == 'Enter' &&
state.startBlock.type == 'check-list-item' state.startBlock.type == 'check-list-item'
) { ) {
return change return change
@@ -125,7 +125,7 @@ class CheckLists extends React.Component {
} }
if ( if (
data.key == 'backspace' && e.key == 'Backspace' &&
state.isCollapsed && state.isCollapsed &&
state.startBlock.type == 'check-list-item' && state.startBlock.type == 'check-list-item' &&
state.selection.startOffset == 0 state.selection.startOffset == 0

View File

@@ -174,7 +174,7 @@ class CodeHighlighting extends React.Component {
onKeyDown = (e, data, change) => { onKeyDown = (e, data, change) => {
const { state } = change const { state } = change
const { startBlock } = state const { startBlock } = state
if (data.key != 'enter') return if (e.key != 'Enter') return
if (startBlock.type != 'code') return if (startBlock.type != 'code') return
if (state.isExpanded) change.delete() if (state.isExpanded) change.delete()
return change.insertText('\n') return change.insertText('\n')

View File

@@ -96,40 +96,6 @@ class HugeDocument extends React.Component {
this.setState({ state }) 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. * Render the editor.
* *

View File

@@ -105,10 +105,10 @@ class MarkdownShortcuts extends React.Component {
*/ */
onKeyDown = (e, data, change) => { onKeyDown = (e, data, change) => {
switch (data.key) { switch (e.key) {
case 'space': return this.onSpace(e, change) case ' ': return this.onSpace(e, change)
case 'backspace': return this.onBackspace(e, change) case 'Backspace': return this.onBackspace(e, change)
case 'enter': return this.onEnter(e, change) case 'Enter': return this.onEnter(e, change)
} }
} }

View File

@@ -3,14 +3,28 @@ import { Editor } from 'slate-react'
import { State } from 'slate' import { State } from 'slate'
import React from 'react' import React from 'react'
import isHotkey from 'is-hotkey'
import initialState from './state.json' import initialState from './state.json'
/** /**
* Define the default node type. * Define the default node type.
*
* @type {String}
*/ */
const DEFAULT_NODE = 'paragraph' 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. * Define a schema.
* *
@@ -107,24 +121,18 @@ class RichTextExample extends React.Component {
*/ */
onKeyDown = (e, data, change) => { onKeyDown = (e, data, change) => {
if (!data.isMod) return
let mark let mark
switch (data.key) { if (isBoldHotkey(e)) {
case 'b': mark = 'bold'
mark = 'bold' } else if (isItalicHotkey(e)) {
break mark = 'italic'
case 'i': } else if (isUnderlinedHotkey(e)) {
mark = 'italic' mark = 'underlined'
break } else if (isCodeHotkey(e)) {
case 'u': mark = 'code'
mark = 'underlined' } else {
break return
case '`':
mark = 'code'
break
default:
return
} }
e.preventDefault() e.preventDefault()

View File

@@ -54,7 +54,7 @@ class PlainText extends React.Component {
*/ */
onKeyDown = (e, data, change) => { onKeyDown = (e, data, change) => {
if (data.key == 'enter' && data.isShift) { if (e.key == 'Enter' && e.shiftKey) {
e.preventDefault() e.preventDefault()
change.insertText('\n') change.insertText('\n')
return true return true

View File

@@ -3,8 +3,20 @@ import { Editor } from 'slate-react'
import { State } from 'slate' import { State } from 'slate'
import React from 'react' import React from 'react'
import isHotkey from 'is-hotkey'
import initialState from './state.json' 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. * Define a schema.
* *
@@ -100,24 +112,18 @@ class SyncingEditor extends React.Component {
*/ */
onKeyDown = (e, data, change) => { onKeyDown = (e, data, change) => {
if (!data.isMod) return
let mark let mark
switch (data.key) { if (isBoldHotkey(e)) {
case 'b': mark = 'bold'
mark = 'bold' } else if (isItalicHotkey(e)) {
break mark = 'italic'
case 'i': } else if (isUnderlinedHotkey(e)) {
mark = 'italic' mark = 'underlined'
break } else if (isCodeHotkey(e)) {
case 'u': mark = 'code'
mark = 'underlined' } else {
break return
case '`':
mark = 'code'
break
default:
return
} }
e.preventDefault() e.preventDefault()

View File

@@ -118,10 +118,10 @@ class Tables extends React.Component {
return return
} }
switch (data.key) { switch (e.key) {
case 'backspace': return this.onBackspace(e, state) case 'Backspace': return this.onBackspace(e, state)
case 'delete': return this.onDelete(e, state) case 'Delete': return this.onDelete(e, state)
case 'enter': return this.onEnter(e, state) case 'Enter': return this.onEnter(e, state)
} }
} }

View File

@@ -27,6 +27,7 @@
"gh-pages": "^0.11.0", "gh-pages": "^0.11.0",
"http-server": "^0.9.0", "http-server": "^0.9.0",
"immutable": "^3.8.1", "immutable": "^3.8.1",
"is-hotkey": "^0.0.1",
"is-image": "^1.0.1", "is-image": "^1.0.1",
"is-url": "^1.2.2", "is-url": "^1.2.2",
"jest": "^17.0.3", "jest": "^17.0.3",

View File

@@ -572,14 +572,17 @@ class Content extends React.Component {
if (this.props.readOnly) return if (this.props.readOnly) return
if (!this.isInEditor(event.target)) return if (!this.isInEditor(event.target)) return
const { altKey, ctrlKey, metaKey, shiftKey, which } = event const { key, metaKey, ctrlKey } = event
const key = keycode(which)
const data = {} 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 // 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 // "Paste and Match Style" commands, but isn't available on the event in a
// normal paste event. // normal paste event.
if (key == 'shift') { if (key == 'Shift') {
this.tmp.isShifting = true this.tmp.isShifting = true
} }
@@ -588,35 +591,23 @@ class Content extends React.Component {
// selection-moving behavior. // selection-moving behavior.
if ( if (
this.tmp.isComposing && this.tmp.isComposing &&
(key == 'left' || key == 'right' || key == 'up' || key == 'down') (key == 'ArrowLeft' || key == 'ArrowRight' || key == 'ArrowUp' || key == 'ArrowDown')
) { ) {
event.preventDefault() event.preventDefault()
return 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 // These key commands have native behavior in contenteditable elements which
// will cause our state to be out of sync, so prevent them. // will cause our state to be out of sync, so prevent them.
if ( if (
(key == 'enter') || (key == 'Enter') ||
(key == 'backspace') || (key == 'Backspace') ||
(key == 'delete') || (key == 'Delete') ||
(key == 'b' && data.isMod) || (key == 'b' && modKey) ||
(key == 'i' && data.isMod) || (key == 'i' && modKey) ||
(key == 'y' && data.isMod) || (key == 'y' && modKey) ||
(key == 'z' && data.isMod) (key == 'z' && modKey) ||
(key == 'Z' && modKey)
) { ) {
event.preventDefault() event.preventDefault()
} }
@@ -632,27 +623,15 @@ class Content extends React.Component {
*/ */
onKeyUp = (event) => { onKeyUp = (event) => {
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
const key = keycode(which)
const data = {} const data = {}
if (key == 'shift') { // COMPAT: add the deprecated keyboard event properties.
addDeprecatedKeyProperties(data, event)
if (event.key == 'Shift') {
this.tmp.isShifting = false 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 }) debug('onKeyUp', { event, data })
this.props.onKeyUp(event, data) this.props.onKeyUp(event, data)
} }
@@ -669,9 +648,16 @@ class Content extends React.Component {
const data = getTransferData(event.clipboardData) const data = getTransferData(event.clipboardData)
// Attach the `isShift` flag, so that people can use it to trigger "Paste // COMPAT: Attach the `isShift` flag, so that people can use it to trigger
// and Match Style" logic. // "Paste and Match Style" logic.
data.isShift = !!this.tmp.isShifting 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 }) debug('onPaste', { event, data })
// COMPAT: In IE 11, only plain text can be retrieved from the event's // 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. * Export.
* *

View File

@@ -407,19 +407,20 @@ function Plugin(options = {}) {
function onKeyDown(e, data, change) { function onKeyDown(e, data, change) {
debug('onKeyDown', { data }) debug('onKeyDown', { data })
switch (data.key) { switch (e.key) {
case 'enter': return onKeyDownEnter(e, data, change) case 'Enter': return onKeyDownEnter(e, data, change)
case 'backspace': return onKeyDownBackspace(e, data, change) case 'Backspace': return onKeyDownBackspace(e, data, change)
case 'delete': return onKeyDownDelete(e, data, change) case 'Delete': return onKeyDownDelete(e, data, change)
case 'left': return onKeyDownLeft(e, data, change) case 'ArrowLeft': return onKeyDownLeft(e, data, change)
case 'right': return onKeyDownRight(e, data, change) case 'ArrowRight': return onKeyDownRight(e, data, change)
case 'up': return onKeyDownUp(e, data, change) case 'ArrowUp': return onKeyDownUp(e, data, change)
case 'down': return onKeyDownDown(e, data, change) case 'ArrowDown': return onKeyDownDown(e, data, change)
case 'd': return onKeyDownD(e, data, change) case 'd': return onKeyDownD(e, data, change)
case 'h': return onKeyDownH(e, data, change) case 'h': return onKeyDownH(e, data, change)
case 'k': return onKeyDownK(e, data, change) case 'k': return onKeyDownK(e, data, change)
case 'y': return onKeyDownY(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) { function onKeyDownBackspace(e, data, change) {
const isWord = IS_MAC ? e.altKey : e.ctrlKey
const isLine = IS_MAC ? e.metaKey : false
let boundary = 'Char' let boundary = 'Char'
if (data.isWord) boundary = 'Word' if (isWord) boundary = 'Word'
if (data.isLine) boundary = 'Line' if (isLine) boundary = 'Line'
change[`delete${boundary}Backward`]() change[`delete${boundary}Backward`]()
} }
@@ -472,9 +477,13 @@ function Plugin(options = {}) {
*/ */
function onKeyDownDelete(e, data, change) { function onKeyDownDelete(e, data, change) {
const isWord = IS_MAC ? e.altKey : e.ctrlKey
const isLine = IS_MAC ? e.metaKey : false
let boundary = 'Char' let boundary = 'Char'
if (data.isWord) boundary = 'Word' if (isWord) boundary = 'Word'
if (data.isLine) boundary = 'Line' if (isLine) boundary = 'Line'
change[`delete${boundary}Forward`]() change[`delete${boundary}Forward`]()
} }
@@ -496,8 +505,8 @@ function Plugin(options = {}) {
function onKeyDownLeft(e, data, change) { function onKeyDownLeft(e, data, change) {
const { state } = change const { state } = change
if (data.isCtrl) return if (e.ctrlKey) return
if (data.isAlt) return if (e.altKey) return
if (state.isExpanded) return if (state.isExpanded) return
const { document, startKey, startText } = state const { document, startKey, startText } = state
@@ -519,7 +528,7 @@ function Plugin(options = {}) {
const previousInline = document.getClosestInline(previous.key) const previousInline = document.getClosestInline(previous.key)
if (previousBlock === startBlock && previousInline && !previousInline.isVoid) { if (previousBlock === startBlock && previousInline && !previousInline.isVoid) {
const extendOrMove = data.isShift ? 'extend' : 'move' const extendOrMove = e.shiftKey ? 'extend' : 'move'
change.collapseToEndOf(previous)[extendOrMove](-1) change.collapseToEndOf(previous)[extendOrMove](-1)
return return
} }
@@ -552,8 +561,8 @@ function Plugin(options = {}) {
function onKeyDownRight(e, data, change) { function onKeyDownRight(e, data, change) {
const { state } = change const { state } = change
if (data.isCtrl) return if (e.ctrlKey) return
if (data.isAlt) return if (e.altKey) return
if (state.isExpanded) return if (state.isExpanded) return
const { document, startKey, startText } = state const { document, startKey, startText } = state
@@ -581,7 +590,7 @@ function Plugin(options = {}) {
const nextInline = document.getClosestInline(next.key) const nextInline = document.getClosestInline(next.key)
if (nextBlock == startBlock && nextInline) { if (nextBlock == startBlock && nextInline) {
const extendOrMove = data.isShift ? 'extend' : 'move' const extendOrMove = e.shiftKey ? 'extend' : 'move'
change.collapseToStartOf(next)[extendOrMove](1) change.collapseToStartOf(next)[extendOrMove](1)
return return
} }
@@ -604,11 +613,11 @@ function Plugin(options = {}) {
*/ */
function onKeyDownUp(e, data, change) { 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 { state } = change
const { selection, document, focusKey, focusBlock } = state const { selection, document, focusKey, focusBlock } = state
const transform = data.isShift ? 'extendToStartOf' : 'collapseToStartOf' const transform = e.shiftKey ? 'extendToStartOf' : 'collapseToStartOf'
const block = selection.hasFocusAtStartOf(focusBlock) const block = selection.hasFocusAtStartOf(focusBlock)
? document.getPreviousBlock(focusKey) ? document.getPreviousBlock(focusKey)
: focusBlock : focusBlock
@@ -633,11 +642,11 @@ function Plugin(options = {}) {
*/ */
function onKeyDownDown(e, data, change) { 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 { state } = change
const { selection, document, focusKey, focusBlock } = state const { selection, document, focusKey, focusBlock } = state
const transform = data.isShift ? 'extendToEndOf' : 'collapseToEndOf' const transform = e.shiftKey ? 'extendToEndOf' : 'collapseToEndOf'
const block = selection.hasFocusAtEndOf(focusBlock) const block = selection.hasFocusAtEndOf(focusBlock)
? document.getNextBlock(focusKey) ? document.getNextBlock(focusKey)
: focusBlock : focusBlock
@@ -658,7 +667,7 @@ function Plugin(options = {}) {
*/ */
function onKeyDownD(e, data, change) { function onKeyDownD(e, data, change) {
if (!IS_MAC || !data.isCtrl) return if (!IS_MAC || !e.ctrlKey || e.altKey) return
e.preventDefault() e.preventDefault()
change.deleteCharForward() change.deleteCharForward()
} }
@@ -672,7 +681,7 @@ function Plugin(options = {}) {
*/ */
function onKeyDownH(e, data, change) { function onKeyDownH(e, data, change) {
if (!IS_MAC || !data.isCtrl) return if (!IS_MAC || !e.ctrlKey || e.altKey) return
e.preventDefault() e.preventDefault()
change.deleteCharBackward() change.deleteCharBackward()
} }
@@ -686,7 +695,7 @@ function Plugin(options = {}) {
*/ */
function onKeyDownK(e, data, change) { function onKeyDownK(e, data, change) {
if (!IS_MAC || !data.isCtrl) return if (!IS_MAC || !e.ctrlKey || e.altKey) return
e.preventDefault() e.preventDefault()
change.deleteLineForward() change.deleteLineForward()
} }
@@ -700,7 +709,8 @@ function Plugin(options = {}) {
*/ */
function onKeyDownY(e, data, change) { function onKeyDownY(e, data, change) {
if (!data.isMod) return const modKey = IS_MAC ? e.metaKey : e.ctrlKey
if (!modKey) return
change.redo() change.redo()
} }
@@ -713,8 +723,9 @@ function Plugin(options = {}) {
*/ */
function onKeyDownZ(e, data, change) { function onKeyDownZ(e, data, change) {
if (!data.isMod) return const modKey = IS_MAC ? e.metaKey : e.ctrlKey
change[data.isShift ? 'redo' : 'undo']() if (!modKey) return
change[e.shiftKey ? 'redo' : 'undo']()
} }
/** /**

View File

@@ -3,7 +3,7 @@
import h from '../../../helpers/h' import h from '../../../helpers/h'
export default function (simulator) { export default function (simulator) {
simulator.keyDown(null, { key: 'enter' }) simulator.keyDown({ key: 'Enter' })
} }
export const input = ( export const input = (

View File

@@ -3360,6 +3360,10 @@ is-glob@^3.1.0:
dependencies: dependencies:
is-extglob "^2.1.0" 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: is-image@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/is-image/-/is-image-1.0.1.tgz#6fd51a752a1a111506d060d952118b0b989b426e" resolved "https://registry.yarnpkg.com/is-image/-/is-image-1.0.1.tgz#6fd51a752a1a111506d060d952118b0b989b426e"