1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-17 20:51:20 +02:00

refactor placeholder to use schema (#1253)

* refactor placeholder to use schema

* update placeholder, remove old export

* add maxWidth to prevent overflow

* update docs
This commit is contained in:
Ian Storm Taylor
2017-10-18 00:23:39 -07:00
committed by GitHub
parent 117d8c55cc
commit f42a64ac8f
31 changed files with 209 additions and 275 deletions

View File

@@ -0,0 +1,51 @@
import React from 'react'
import SlateTypes from 'slate-prop-types'
import Types from 'prop-types'
/**
* Default node.
*
* @type {Component}
*/
class DefaultNode extends React.Component {
/**
* Prop types.
*
* @type {Object}
*/
static propTypes = {
attributes: Types.object.isRequired,
editor: Types.object.isRequired,
isSelected: Types.bool.isRequired,
node: SlateTypes.node.isRequired,
parent: SlateTypes.node.isRequired,
readOnly: Types.bool.isRequired,
state: SlateTypes.state.isRequired,
}
/**
* Render.
*
* @return {Element}
*/
render() {
const { attributes, children, node } = this.props
const Tag = node.kind == 'block' ? 'div' : 'span'
const style = { position: 'relative' }
return <Tag {...attributes} style={style}>{children}</Tag>
}
}
/**
* Export.
*
* @type {Component}
*/
export default DefaultNode

View File

@@ -0,0 +1,64 @@
import React from 'react'
import SlateTypes from 'slate-prop-types'
import Types from 'prop-types'
/**
* Default placeholder.
*
* @type {Component}
*/
class DefaultPlaceholder extends React.Component {
/**
* Property types.
*
* @type {Object}
*/
static propTypes = {
editor: Types.object.isRequired,
isSelected: Types.bool.isRequired,
node: SlateTypes.node.isRequired,
parent: SlateTypes.node.isRequired,
readOnly: Types.bool.isRequired,
state: SlateTypes.state.isRequired,
}
/**
* Render.
*
* @return {Element}
*/
render() {
const { editor, state } = this.props
if (state.document.nodes.size > 1) return null
if (!editor.props.placeholder) return null
const style = {
pointerEvents: 'none',
display: 'inline-block',
width: '0',
maxWidth: '100%',
whiteSpace: 'nowrap',
opacity: '0.333',
}
return (
<span contentEditable={false} style={style}>
{editor.props.placeholder}
</span>
)
}
}
/**
* Export.
*
* @type {Component}
*/
export default DefaultPlaceholder

View File

@@ -54,6 +54,7 @@ class Node extends React.Component {
const { node, schema } = props
this.state = {}
this.state.Component = node.getComponent(schema)
this.state.Placeholder = node.getPlaceholder(schema)
}
/**
@@ -78,7 +79,8 @@ class Node extends React.Component {
componentWillReceiveProps = (props) => {
if (props.node == this.props.node) return
const Component = props.node.getComponent(props.schema)
this.setState({ Component })
const Placeholder = props.node.getPlaceholder(props.schema)
this.setState({ Component, Placeholder })
}
/**
@@ -154,7 +156,7 @@ class Node extends React.Component {
this.debug('render', { props })
const { editor, isSelected, node, parent, readOnly, state } = props
const { Component } = this.state
const { Component, Placeholder } = this.state
const { selection } = state
const indexes = node.getSelectionIndexes(selection, isSelected)
const children = node.nodes.toArray().map((child, i) => {
@@ -173,17 +175,19 @@ class Node extends React.Component {
if (direction == 'rtl') attributes.dir = 'rtl'
}
const p = {
editor,
isSelected,
key: node.key,
node,
parent,
readOnly,
state
}
const element = (
<Component
attributes={attributes}
editor={editor}
isSelected={isSelected}
key={node.key}
node={node}
parent={parent}
readOnly={readOnly}
state={state}
>
<Component {...p} attributes={attributes}>
{Placeholder && <Placeholder {...p} />}
{children}
</Component>
)

View File

@@ -1,125 +0,0 @@
import React from 'react'
import SlateTypes from 'slate-prop-types'
import Types from 'prop-types'
/**
* Placeholder.
*
* @type {Component}
*/
class Placeholder extends React.Component {
/**
* Property types.
*
* @type {Object}
*/
static propTypes = {
children: Types.any.isRequired,
className: Types.string,
firstOnly: Types.bool,
node: SlateTypes.node.isRequired,
parent: SlateTypes.node,
state: SlateTypes.state.isRequired,
style: Types.object,
}
/**
* Default properties.
*
* @type {Object}
*/
static defaultProps = {
firstOnly: true,
}
/**
* Should the placeholder update?
*
* @param {Object} props
* @param {Object} state
* @return {Boolean}
*/
shouldComponentUpdate = (props, state) => {
return (
props.children != this.props.children ||
props.className != this.props.className ||
props.firstOnly != this.props.firstOnly ||
props.parent != this.props.parent ||
props.node != this.props.node ||
props.style != this.props.style
)
}
/**
* Is the placeholder visible?
*
* @return {Boolean}
*/
isVisible = () => {
const { firstOnly, node, parent } = this.props
if (node.text) return false
if (firstOnly) {
if (parent.nodes.size > 1) return false
if (parent.nodes.first() === node) return true
return false
} else {
return true
}
}
/**
* Render.
*
* If the placeholder is a string, and no `className` or `style` has been
* passed, give it a default style of lowered opacity.
*
* @return {Element}
*/
render() {
const isVisible = this.isVisible()
if (!isVisible) return null
const { children, className } = this.props
let { style } = this.props
if (typeof children === 'string' && style == null && className == null) {
style = { opacity: '0.333' }
} else if (style == null) {
style = {}
}
const styles = {
position: 'absolute',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
pointerEvents: 'none',
...style
}
return (
<span contentEditable={false} className={className} style={styles}>
{children}
</span>
)
}
}
/**
* Export.
*
* @type {Component}
*/
export default Placeholder

View File

@@ -1,6 +1,5 @@
import Editor from './components/editor'
import Placeholder from './components/placeholder'
import findDOMNode from './utils/find-dom-node'
import findDOMRange from './utils/find-dom-range'
import findNode from './utils/find-node'
@@ -17,7 +16,6 @@ import setEventTransfer from './utils/set-event-transfer'
export {
Editor,
Placeholder,
findDOMNode,
findDOMRange,
findNode,
@@ -29,7 +27,6 @@ export {
export default {
Editor,
Placeholder,
findDOMNode,
findDOMRange,
findNode,

View File

@@ -4,12 +4,13 @@ import Debug from 'debug'
import Plain from 'slate-plain-serializer'
import React from 'react'
import getWindow from 'get-window'
import { Block, Inline, coreSchema } from 'slate'
import { Block, Inline, Text, coreSchema } from 'slate'
import EVENT_HANDLERS from '../constants/event-handlers'
import HOTKEYS from '../constants/hotkeys'
import Content from '../components/content'
import Placeholder from '../components/placeholder'
import DefaultNode from '../components/default-node'
import DefaultPlaceholder from '../components/default-placeholder'
import findDOMNode from '../utils/find-dom-node'
import findNode from '../utils/find-node'
import findPoint from '../utils/find-point'
@@ -31,19 +32,10 @@ const debug = Debug('slate:core:after')
* The after plugin.
*
* @param {Object} options
* @property {Element} placeholder
* @property {String} placeholderClassName
* @property {Object} placeholderStyle
* @return {Object}
*/
function AfterPlugin(options = {}) {
const {
placeholder,
placeholderClassName,
placeholderStyle,
} = options
let isDraggingInternally = null
/**
@@ -726,55 +718,6 @@ function AfterPlugin(options = {}) {
)
}
/**
* A default schema rule to render block nodes.
*
* @type {Object}
*/
const BLOCK_RENDER_RULE = {
match: (node) => {
return node.kind == 'block'
},
render: (props) => {
return (
<div {...props.attributes} style={{ position: 'relative' }}>
{props.children}
{placeholder
? <Placeholder
className={placeholderClassName}
node={props.node}
parent={props.state.document}
state={props.state}
style={placeholderStyle}
>
{placeholder}
</Placeholder>
: null}
</div>
)
}
}
/**
* A default schema rule to render inline nodes.
*
* @type {Object}
*/
const INLINE_RENDER_RULE = {
match: (node) => {
return node.kind == 'inline'
},
render: (props) => {
return (
<span {...props.attributes} style={{ position: 'relative' }}>
{props.children}
</span>
)
}
}
/**
* Add default rendering rules to the schema.
*
@@ -783,8 +726,14 @@ function AfterPlugin(options = {}) {
const schema = {
rules: [
BLOCK_RENDER_RULE,
INLINE_RENDER_RULE
{
match: obj => obj.kind == 'block' || obj.kind == 'inline',
render: DefaultNode,
},
{
match: obj => obj.kind == 'block' && Text.isTextList(obj.nodes) && obj.text == '',
placeholder: DefaultPlaceholder,
},
]
}