1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-04-20 13:22:04 +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_
#### BREAKING CHANGES

View File

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

View File

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

View File

@ -26,6 +26,10 @@ img {
max-height: 20em;
}
img.active {
box-shadow: 0 0 0 2px blue;
}
blockquote {
border-left: 2px solid #ddd;
margin-left: 0;
@ -156,11 +160,3 @@ input:focus {
.hover-menu .button[data-active="true"] {
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) {
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)
return isContained
}

View File

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

View File

@ -5,10 +5,11 @@ import React from 'react'
import ReactDOM from 'react-dom'
import TYPES from '../utils/types'
import Text from './text'
import Void from './void'
import scrollTo from 'element-scroll-to'
/**
* Debugger.
* Debug.
*
* @type {Function}
*/
@ -23,6 +24,12 @@ const debug = Debug('slate:node')
class Node extends React.Component {
/**
* Property types.
*
* @type {Object}
*/
static propTypes = {
editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired,
@ -32,10 +39,31 @@ class Node extends React.Component {
state: React.PropTypes.object.isRequired
};
/**
* Default properties.
*
* @type {Object}
*/
static defaultProps = {
style: {}
}
/**
* Constructor.
*
* @param {Object} props
*/
constructor(props) {
super(props)
this.state = {}
if (props.node.kind != 'text') {
this.state.Component = props.renderNode(props.node)
}
}
/**
* Debug.
*
@ -50,6 +78,21 @@ class Node extends React.Component {
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?
*
@ -59,10 +102,36 @@ class Node extends React.Component {
*/
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.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 = () => {
const { editor, node, renderNode, state } = this.props
const Component = renderNode(node)
const { editor, node, state } = this.props
const { Component } = this.state
const children = node.nodes
.map(child => this.renderNode(child))
.toArray()
@ -188,7 +257,7 @@ class Node extends React.Component {
if (direction == 'rtl') attributes.dir = 'rtl'
}
return (
const element = (
<Component
attributes={attributes}
key={node.key}
@ -199,6 +268,10 @@ class Node extends React.Component {
{children}
</Component>
)
return node.isVoid
? <Void {...this.props}>{element}</Void>
: element
}
/**

View File

@ -29,36 +29,11 @@ class Void extends React.Component {
static propTypes = {
children: React.PropTypes.any.isRequired,
className: React.PropTypes.string,
editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired,
state: React.PropTypes.object.isRequired,
style: React.PropTypes.object
state: React.PropTypes.object.isRequired
};
/**
* 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.
*
@ -84,29 +59,19 @@ class Void extends React.Component {
*/
render = () => {
const { children, node, className, style } = this.props
const { children, node } = this.props
const Tag = node.kind == 'block' ? 'div' : 'span'
// Make the outer wrapper relative, so the spacer can overlay it.
const styles = {
...style,
const style = {
position: 'relative'
}
return (
<Tag
contentEditable={false}
data-void="true"
onClick={this.onClick}
>
<Tag
contentEditable
suppressContentEditableWarning
className={className}
style={styles}
>
{this.renderSpacer()}
<Tag contentEditable={false}>{children}</Tag>
<Tag style={style} onClick={this.onClick}>
{this.renderSpacer()}
<Tag contentEditable={false}>
{children}
</Tag>
</Tag>
)

View File

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