mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 20:51:20 +02:00
Add prop "isFocused" / "isSelected" for custom nodes (#1950)
* Change the definition of isSelected and add isFocused * Document prop "isFocused" * Add unit tests for isFocused / isSelected * Adapt examples * Lint
This commit is contained in:
committed by
Ian Storm Taylor
parent
37418643e2
commit
0ceefea2e7
@@ -10,6 +10,7 @@ Slate will render custom nodes for [`Block`](../slate/block.md) and [`Inline`](.
|
|||||||
children={Object}
|
children={Object}
|
||||||
editor={Editor}
|
editor={Editor}
|
||||||
isSelected={Boolean}
|
isSelected={Boolean}
|
||||||
|
isFocused={Boolean}
|
||||||
node={Node}
|
node={Node}
|
||||||
parent={Node}
|
parent={Node}
|
||||||
readOnly={Boolean}
|
readOnly={Boolean}
|
||||||
@@ -66,6 +67,14 @@ editor.change(change => {
|
|||||||
|
|
||||||
A boolean representing whether the node you are rendering is currently selected. You can use this to render a visual representation of the selection.
|
A boolean representing whether the node you are rendering is currently selected. You can use this to render a visual representation of the selection.
|
||||||
|
|
||||||
|
This boolean is true when the node is selected and the editor is blurred.
|
||||||
|
|
||||||
|
### `isFocused`
|
||||||
|
|
||||||
|
`Boolean`
|
||||||
|
|
||||||
|
A boolean representing whether the node you are rendering is currently focused. You can use this to render a visual representation of the focused selection.
|
||||||
|
|
||||||
### `node`
|
### `node`
|
||||||
|
|
||||||
`Node`
|
`Node`
|
||||||
|
@@ -37,10 +37,12 @@ class Video extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { isSelected } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...this.props.attributes}>
|
<div {...this.props.attributes}>
|
||||||
{this.renderVideo()}
|
{this.renderVideo()}
|
||||||
{this.renderInput()}
|
{isSelected ? this.renderInput() : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -52,16 +54,16 @@ class Video extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderVideo = () => {
|
renderVideo = () => {
|
||||||
const { node, isSelected } = this.props
|
const { node, isFocused } = this.props
|
||||||
const video = node.data.get('video')
|
const video = node.data.get('video')
|
||||||
|
|
||||||
const wrapperStyle = {
|
const wrapperStyle = {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
outline: isSelected ? '2px solid blue' : 'none',
|
outline: isFocused ? '2px solid blue' : 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
const maskStyle = {
|
const maskStyle = {
|
||||||
display: isSelected ? 'none' : 'block',
|
display: isFocused ? 'none' : 'block',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '0',
|
top: '0',
|
||||||
left: '0',
|
left: '0',
|
||||||
|
@@ -101,7 +101,7 @@ class Emojis extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = props => {
|
||||||
const { attributes, children, node, isSelected } = props
|
const { attributes, children, node, isFocused } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'paragraph': {
|
case 'paragraph': {
|
||||||
@@ -112,7 +112,7 @@ class Emojis extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Emoji
|
<Emoji
|
||||||
{...props.attributes}
|
{...props.attributes}
|
||||||
selected={isSelected}
|
selected={isFocused}
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
onDrop={noop}
|
onDrop={noop}
|
||||||
>
|
>
|
||||||
|
@@ -125,12 +125,12 @@ class Images extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = props => {
|
||||||
const { attributes, node, isSelected } = props
|
const { attributes, node, isFocused } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'image': {
|
case 'image': {
|
||||||
const src = node.data.get('src')
|
const src = node.data.get('src')
|
||||||
return <Image src={src} selected={isSelected} {...attributes} />
|
return <Image src={src} selected={isFocused} {...attributes} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -190,7 +190,7 @@ class PasteHtml extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = props => {
|
||||||
const { attributes, children, node, isSelected } = props
|
const { attributes, children, node, isFocused } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'quote':
|
case 'quote':
|
||||||
@@ -230,7 +230,7 @@ class PasteHtml extends React.Component {
|
|||||||
}
|
}
|
||||||
case 'image': {
|
case 'image': {
|
||||||
const src = node.data.get('src')
|
const src = node.data.get('src')
|
||||||
return <Image src={src} selected={isSelected} {...attributes} />
|
return <Image src={src} selected={isFocused} {...attributes} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -458,7 +458,7 @@ class Content extends React.Component {
|
|||||||
const { value, stack } = editor
|
const { value, stack } = editor
|
||||||
const Container = tagName
|
const Container = tagName
|
||||||
const { document, selection, decorations } = value
|
const { document, selection, decorations } = value
|
||||||
const indexes = document.getSelectionIndexes(selection, selection.isFocused)
|
const indexes = document.getSelectionIndexes(selection)
|
||||||
const decs = document.getDecorations(stack).concat(decorations || [])
|
const decs = document.getDecorations(stack).concat(decorations || [])
|
||||||
const childrenDecorations = getChildrenDecorations(document, decs)
|
const childrenDecorations = getChildrenDecorations(document, decs)
|
||||||
|
|
||||||
@@ -541,7 +541,7 @@ class Content extends React.Component {
|
|||||||
renderNode = (child, isSelected, decorations) => {
|
renderNode = (child, isSelected, decorations) => {
|
||||||
const { editor, readOnly } = this.props
|
const { editor, readOnly } = this.props
|
||||||
const { value } = editor
|
const { value } = editor
|
||||||
const { document } = value
|
const { document, isFocused } = value
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Node
|
<Node
|
||||||
@@ -549,6 +549,7 @@ class Content extends React.Component {
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
decorations={decorations}
|
decorations={decorations}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
isFocused={isFocused && isSelected}
|
||||||
key={child.key}
|
key={child.key}
|
||||||
node={child}
|
node={child}
|
||||||
parent={document}
|
parent={document}
|
||||||
|
@@ -34,6 +34,7 @@ class Node extends React.Component {
|
|||||||
block: SlateTypes.block,
|
block: SlateTypes.block,
|
||||||
decorations: ImmutableTypes.list.isRequired,
|
decorations: ImmutableTypes.list.isRequired,
|
||||||
editor: Types.object.isRequired,
|
editor: Types.object.isRequired,
|
||||||
|
isFocused: Types.bool.isRequired,
|
||||||
isSelected: Types.bool.isRequired,
|
isSelected: Types.bool.isRequired,
|
||||||
node: SlateTypes.node.isRequired,
|
node: SlateTypes.node.isRequired,
|
||||||
parent: SlateTypes.node.isRequired,
|
parent: SlateTypes.node.isRequired,
|
||||||
@@ -103,6 +104,7 @@ class Node extends React.Component {
|
|||||||
// selection value of some of its children could have been changed and they
|
// selection value of some of its children could have been changed and they
|
||||||
// need to be rendered again.
|
// need to be rendered again.
|
||||||
if (n.isSelected || p.isSelected) return true
|
if (n.isSelected || p.isSelected) return true
|
||||||
|
if (n.isFocused || p.isFocused) return true
|
||||||
|
|
||||||
// If the decorations have changed, update.
|
// If the decorations have changed, update.
|
||||||
if (!n.decorations.equals(p.decorations)) return true
|
if (!n.decorations.equals(p.decorations)) return true
|
||||||
@@ -122,6 +124,7 @@ class Node extends React.Component {
|
|||||||
const {
|
const {
|
||||||
editor,
|
editor,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
isFocused,
|
||||||
node,
|
node,
|
||||||
decorations,
|
decorations,
|
||||||
parent,
|
parent,
|
||||||
@@ -158,6 +161,7 @@ class Node extends React.Component {
|
|||||||
const props = {
|
const props = {
|
||||||
key: node.key,
|
key: node.key,
|
||||||
editor,
|
editor,
|
||||||
|
isFocused,
|
||||||
isSelected,
|
isSelected,
|
||||||
node,
|
node,
|
||||||
parent,
|
parent,
|
||||||
@@ -193,7 +197,7 @@ class Node extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = (child, isSelected, decorations) => {
|
renderNode = (child, isSelected, decorations) => {
|
||||||
const { block, editor, node, readOnly } = this.props
|
const { block, editor, node, readOnly, isFocused } = this.props
|
||||||
const Component = child.object == 'text' ? Text : Node
|
const Component = child.object == 'text' ? Text : Node
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -202,6 +206,7 @@ class Node extends React.Component {
|
|||||||
decorations={decorations}
|
decorations={decorations}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
isFocused={isFocused && isSelected}
|
||||||
key={child.key}
|
key={child.key}
|
||||||
node={child}
|
node={child}
|
||||||
parent={node}
|
parent={node}
|
||||||
|
@@ -102,21 +102,13 @@ class Void extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
renderText = () => {
|
renderText = () => {
|
||||||
const {
|
const { block, decorations, node, readOnly, editor } = this.props
|
||||||
block,
|
|
||||||
decorations,
|
|
||||||
isSelected,
|
|
||||||
node,
|
|
||||||
readOnly,
|
|
||||||
editor,
|
|
||||||
} = this.props
|
|
||||||
const child = node.getFirstText()
|
const child = node.getFirstText()
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
block={node.object == 'block' ? node : block}
|
block={node.object == 'block' ? node : block}
|
||||||
decorations={decorations}
|
decorations={decorations}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
isSelected={isSelected}
|
|
||||||
key={child.key}
|
key={child.key}
|
||||||
node={child}
|
node={child}
|
||||||
parent={node}
|
parent={node}
|
||||||
|
@@ -0,0 +1,83 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import h from '../../helpers/h'
|
||||||
|
|
||||||
|
function Image(props) {
|
||||||
|
return React.createElement('img', {
|
||||||
|
className: props.isFocused ? 'focused' : '',
|
||||||
|
src: props.node.data.get('src'),
|
||||||
|
...props.attributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNode(props) {
|
||||||
|
switch (props.node.type) {
|
||||||
|
case 'image':
|
||||||
|
return Image(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
renderNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const value = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
<anchor />
|
||||||
|
</paragraph>
|
||||||
|
<image key="a" src="https://example.com/image.png" />
|
||||||
|
<paragraph>
|
||||||
|
<focus />
|
||||||
|
</paragraph>
|
||||||
|
<image key="b" src="https://example.com/image2.png" />
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
.change()
|
||||||
|
.blur().value
|
||||||
|
|
||||||
|
export const output = `
|
||||||
|
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="n"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slate-void="true">
|
||||||
|
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="z">​</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div contenteditable="false">
|
||||||
|
<img class="" src="https://example.com/image.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="position:relative">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="n"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slate-void="true">
|
||||||
|
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="z">​</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div contenteditable="false">
|
||||||
|
<img class="" src="https://example.com/image2.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`.trim()
|
@@ -0,0 +1,81 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import h from '../../helpers/h'
|
||||||
|
|
||||||
|
function Image(props) {
|
||||||
|
return React.createElement('img', {
|
||||||
|
className: props.isFocused ? 'focused' : '',
|
||||||
|
src: props.node.data.get('src'),
|
||||||
|
...props.attributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNode(props) {
|
||||||
|
switch (props.node.type) {
|
||||||
|
case 'image':
|
||||||
|
return Image(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
renderNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const value = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
<anchor />
|
||||||
|
</paragraph>
|
||||||
|
<image key="a" src="https://example.com/image.png" />
|
||||||
|
<paragraph>
|
||||||
|
<focus />
|
||||||
|
</paragraph>
|
||||||
|
<image key="b" src="https://example.com/image2.png" />
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = `
|
||||||
|
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="n"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slate-void="true">
|
||||||
|
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="z">​</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div contenteditable="false">
|
||||||
|
<img class="focused" src="https://example.com/image.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="position:relative">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="n"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slate-void="true">
|
||||||
|
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="z">​</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div contenteditable="false">
|
||||||
|
<img class="" src="https://example.com/image2.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`.trim()
|
@@ -0,0 +1,81 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import h from '../../helpers/h'
|
||||||
|
|
||||||
|
function Image(props) {
|
||||||
|
return React.createElement('img', {
|
||||||
|
className: props.isSelected ? 'selected' : '',
|
||||||
|
src: props.node.data.get('src'),
|
||||||
|
...props.attributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNode(props) {
|
||||||
|
switch (props.node.type) {
|
||||||
|
case 'image':
|
||||||
|
return Image(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const props = {
|
||||||
|
renderNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const value = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
<anchor />
|
||||||
|
</paragraph>
|
||||||
|
<image key="a" src="https://example.com/image.png" />
|
||||||
|
<paragraph>
|
||||||
|
<focus />
|
||||||
|
</paragraph>
|
||||||
|
<image key="b" src="https://example.com/image2.png" />
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = `
|
||||||
|
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||||
|
<div style="position:relative">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="n"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slate-void="true">
|
||||||
|
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="z">​</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div contenteditable="false">
|
||||||
|
<img class="selected" src="https://example.com/image.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="position:relative">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="n"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slate-void="true">
|
||||||
|
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<span data-slate-zero-width="z">​</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div contenteditable="false">
|
||||||
|
<img class="" src="https://example.com/image2.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`.trim()
|
@@ -1523,11 +1523,11 @@ class Node {
|
|||||||
* @return {Object|Null}
|
* @return {Object|Null}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getSelectionIndexes(range, isSelected = false) {
|
getSelectionIndexes(range, isSelected = true) {
|
||||||
const { startKey, endKey } = range
|
const { startKey, endKey } = range
|
||||||
|
|
||||||
// PERF: if we're not selected, or the range is blurred, we can exit early.
|
// PERF: if we're not selected, we can exit early.
|
||||||
if (!isSelected || range.isBlurred) {
|
if (!isSelected) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user