1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-07-31 04:20:26 +02:00

loosen default shouldComponentUpdate for nodes, and make Void implicit

This commit is contained in:
Ian Storm Taylor
2016-08-04 14:12:27 -07:00
parent 18acc638fd
commit 9a70188f34
9 changed files with 104 additions and 67 deletions

View File

@@ -5,6 +5,13 @@ This document maintains a list of changes to Slate with each new version. Until
--- ---
### `0.11.0` — _August 4, 2016_
#### BREAKING CHANGES
- **Void nodes are renderered implicitly again!** Previously Slate had required that you wrap void node renderers yourself with the exposed `<Void>` wrapping component. This was to allow for selection styling, but a change was made to make selection styling able to handled in Javascript. Now the `<Void>` wrapper will be implicitly rendered by Slate, so you do not need to worry about it, and "voidness" only needs to toggled in one place, the `isVoid: true` property of a node.
### `0.10.0` — _July 29, 2016_ ### `0.10.0` — _July 29, 2016_
#### BREAKING CHANGES #### BREAKING CHANGES

View File

@@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import { Void } from '../..'
/** /**
* An video embed component. * An video embed component.
@@ -50,10 +49,10 @@ class Video extends React.Component {
render = () => { render = () => {
return ( return (
<Void {...this.props}> <div {...this.props.attributes}>
{this.renderVideo()} {this.renderVideo()}
{this.renderInput()} {this.renderInput()}
</Void> </div>
) )
} }

View File

@@ -15,11 +15,11 @@ import isUrl from 'is-url'
const NODES = { const NODES = {
image: (props) => { image: (props) => {
const { node, state } = props const { node, state } = props
const isFocused = state.selection.hasEdgeIn(node)
const src = node.data.get('src') const src = node.data.get('src')
const className = isFocused ? 'active' : null
return ( return (
<Void {...props} className="image-block"> <img src={src} className={className} {...props.attributes} />
<img src={src} {...props.attributes} />
</Void>
) )
} }
} }

View File

@@ -26,6 +26,10 @@ img {
max-height: 20em; max-height: 20em;
} }
img.active {
box-shadow: 0 0 0 2px blue;
}
blockquote { blockquote {
border-left: 2px solid #ddd; border-left: 2px solid #ddd;
margin-left: 0; margin-left: 0;
@@ -156,11 +160,3 @@ input:focus {
.hover-menu .button[data-active="true"] { .hover-menu .button[data-active="true"] {
color: #fff; color: #fff;
} }
.image-block:focus {
outline: none;
}
.image-block:focus img {
box-shadow: 0 0 0 2px blue;
}

View File

@@ -772,7 +772,7 @@ class Content extends React.Component {
function isNonEditable(event) { function isNonEditable(event) {
const { target, currentTarget } = event const { target, currentTarget } = event
const nonEditable = target.closest('[contenteditable="false"]:not([data-void="true"])') const nonEditable = target.closest('[contenteditable="false"]')
const isContained = currentTarget.contains(nonEditable) const isContained = currentTarget.contains(nonEditable)
return isContained return isContained
} }

View File

@@ -163,7 +163,7 @@ class Editor extends React.Component {
*/ */
onBeforeChange = (state) => { onBeforeChange = (state) => {
if (state == this.state.state) return if (state == this.state.state) return state
for (const plugin of this.state.plugins) { for (const plugin of this.state.plugins) {
if (!plugin.onBeforeChange) continue if (!plugin.onBeforeChange) continue

View File

@@ -5,10 +5,11 @@ import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import TYPES from '../utils/types' import TYPES from '../utils/types'
import Text from './text' import Text from './text'
import Void from './void'
import scrollTo from 'element-scroll-to' import scrollTo from 'element-scroll-to'
/** /**
* Debugger. * Debug.
* *
* @type {Function} * @type {Function}
*/ */
@@ -23,6 +24,12 @@ const debug = Debug('slate:node')
class Node extends React.Component { class Node extends React.Component {
/**
* Property types.
*
* @type {Object}
*/
static propTypes = { static propTypes = {
editor: React.PropTypes.object.isRequired, editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired, node: React.PropTypes.object.isRequired,
@@ -32,10 +39,31 @@ class Node extends React.Component {
state: React.PropTypes.object.isRequired state: React.PropTypes.object.isRequired
}; };
/**
* Default properties.
*
* @type {Object}
*/
static defaultProps = { static defaultProps = {
style: {} style: {}
} }
/**
* Constructor.
*
* @param {Object} props
*/
constructor(props) {
super(props)
this.state = {}
if (props.node.kind != 'text') {
this.state.Component = props.renderNode(props.node)
}
}
/** /**
* Debug. * Debug.
* *
@@ -50,6 +78,21 @@ class Node extends React.Component {
debug(message, `${id}`, ...args) debug(message, `${id}`, ...args)
} }
/**
* On receiving new props, update the `Component` renderer.
*
* @param {Object} props
*/
componentWillReceiveProps = (props) => {
if (props.node.kind == 'text') return
if (props.node == this.props.node) return
this.setState({
Component: props.renderNode(props.node)
})
}
/** /**
* Should the node update? * Should the node update?
* *
@@ -59,10 +102,36 @@ class Node extends React.Component {
*/ */
shouldComponentUpdate = (props) => { shouldComponentUpdate = (props) => {
return ( const { Component } = this.state
// If the node is rendered with a `Component` that has enabled suppression
// of update checking, always return true so that it can deal with update
// checking itself.
if (Component && Component.suppressShouldComponentUpdate) {
return true
}
// Otherwise, perform a peformant update check by default.
if (
props.node != this.props.node || props.node != this.props.node ||
(props.state.isFocused && props.state.selection.hasEdgeIn(props.node)) (props.state.isFocused && props.state.selection.hasEdgeIn(props.node))
) ) {
return true
}
// For block and inline nodes, which can have custom renderers, we need to
// include another check for whether the previous selection had an edge in
// the node, to allow for intuitive selection-based rendering.
if (
this.props.node.kind != 'text' &&
this.props.state.isFocused &&
this.props.state.selection.hasEdgeIn(this.props.node)
) {
return true
}
// Otherwise, don't update.
return false
} }
/** /**
@@ -168,8 +237,8 @@ class Node extends React.Component {
*/ */
renderElement = () => { renderElement = () => {
const { editor, node, renderNode, state } = this.props const { editor, node, state } = this.props
const Component = renderNode(node) const { Component } = this.state
const children = node.nodes const children = node.nodes
.map(child => this.renderNode(child)) .map(child => this.renderNode(child))
.toArray() .toArray()
@@ -188,7 +257,7 @@ class Node extends React.Component {
if (direction == 'rtl') attributes.dir = 'rtl' if (direction == 'rtl') attributes.dir = 'rtl'
} }
return ( const element = (
<Component <Component
attributes={attributes} attributes={attributes}
key={node.key} key={node.key}
@@ -199,6 +268,10 @@ class Node extends React.Component {
{children} {children}
</Component> </Component>
) )
return node.isVoid
? <Void {...this.props}>{element}</Void>
: element
} }
/** /**

View File

@@ -29,36 +29,11 @@ class Void extends React.Component {
static propTypes = { static propTypes = {
children: React.PropTypes.any.isRequired, children: React.PropTypes.any.isRequired,
className: React.PropTypes.string,
editor: React.PropTypes.object.isRequired, editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired, node: React.PropTypes.object.isRequired,
state: React.PropTypes.object.isRequired, state: React.PropTypes.object.isRequired
style: React.PropTypes.object
}; };
/**
* Default properties.
*/
static defaultProps = {
style: {}
}
/**
* Should the component update?
*
* @param {Object} props
* @param {Object} state
* @return {Boolean}
*/
shouldComponentUpdate = (props, state) => {
return (
props.node != this.props.node ||
(props.state.isFocused && props.state.selection.hasEdgeIn(props.node))
)
}
/** /**
* When one of the wrapper elements it clicked, select the void node. * When one of the wrapper elements it clicked, select the void node.
* *
@@ -84,29 +59,19 @@ class Void extends React.Component {
*/ */
render = () => { render = () => {
const { children, node, className, style } = this.props const { children, node } = this.props
const Tag = node.kind == 'block' ? 'div' : 'span' const Tag = node.kind == 'block' ? 'div' : 'span'
// Make the outer wrapper relative, so the spacer can overlay it. // Make the outer wrapper relative, so the spacer can overlay it.
const styles = { const style = {
...style,
position: 'relative' position: 'relative'
} }
return ( return (
<Tag <Tag style={style} onClick={this.onClick}>
contentEditable={false} {this.renderSpacer()}
data-void="true" <Tag contentEditable={false}>
onClick={this.onClick} {children}
>
<Tag
contentEditable
suppressContentEditableWarning
className={className}
style={styles}
>
{this.renderSpacer()}
<Tag contentEditable={false}>{children}</Tag>
</Tag> </Tag>
</Tag> </Tag>
) )

View File

@@ -5,7 +5,6 @@
import Editor from './components/editor' import Editor from './components/editor'
import Placeholder from './components/placeholder' import Placeholder from './components/placeholder'
import Void from './components/void'
/** /**
* Models. * Models.
@@ -54,7 +53,6 @@ export {
Selection, Selection,
State, State,
Text, Text,
Void,
findDOMNode findDOMNode
} }
@@ -73,6 +71,5 @@ export default {
Selection, Selection,
State, State,
Text, Text,
Void,
findDOMNode findDOMNode
} }