1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-21 07:32:24 +01:00

Add renderPlaceholder (#4190)

This commit is contained in:
Julian Krispel-Samsel 2021-04-23 21:22:11 +01:00 committed by GitHub
parent 3c5cb198df
commit ea2eefefb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 28 deletions

View File

@ -0,0 +1,5 @@
---
'slate-react': patch
---
Added a `renderPlaceholder` prop to the `<Editable>` component for customizing how placeholders are rendered.

View File

@ -34,9 +34,9 @@ import {
getDefaultView,
isDOMElement,
isDOMNode,
DOMStaticRange,
isPlainTextOnlyPaste,
} from '../utils/dom'
import {
EDITOR_TO_ELEMENT,
ELEMENT_TO_NODE,
@ -98,6 +98,7 @@ export type EditableProps = {
style?: React.CSSProperties
renderElement?: (props: RenderElementProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
renderPlaceholder?: (props: RenderPlaceholderProps) => JSX.Element
as?: React.ElementType
} & React.TextareaHTMLAttributes<HTMLDivElement>
@ -114,6 +115,7 @@ export const Editable = (props: EditableProps) => {
readOnly = false,
renderElement,
renderLeaf,
renderPlaceholder = props => <DefaultPlaceholder {...props} />,
style = {},
as: Component = 'div',
...attributes
@ -225,6 +227,7 @@ export const Editable = (props: EditableProps) => {
scrollMode: 'if-needed',
boundary: el,
})
// @ts-ignore
delete leafEl.getBoundingClientRect
} else {
domSelection.removeAllRanges()
@ -1046,6 +1049,7 @@ export const Editable = (props: EditableProps) => {
decorations,
node: editor,
renderElement,
renderPlaceholder,
renderLeaf,
selection: editor.selection,
})}
@ -1055,6 +1059,29 @@ export const Editable = (props: EditableProps) => {
)
}
/**
* The props that get passed to renderPlaceholder
*/
export type RenderPlaceholderProps = {
children: any
attributes: {
'data-slate-placeholder': boolean
dir?: 'rtl'
contentEditable: boolean
ref: React.RefObject<any>
style: React.CSSProperties
}
}
/**
* The default placeholder element
*/
export const DefaultPlaceholder = ({
attributes,
children,
}: RenderPlaceholderProps) => <span {...attributes}>{children}</span>
/**
* A default memoized decorate function.
*/

View File

@ -15,7 +15,11 @@ import {
KEY_TO_ELEMENT,
} from '../utils/weak-maps'
import { isDecoratorRangeListEqual } from '../utils/range-list'
import { RenderElementProps, RenderLeafProps } from './editable'
import {
RenderElementProps,
RenderLeafProps,
RenderPlaceholderProps,
} from './editable'
/**
* Element.
@ -25,6 +29,7 @@ const Element = (props: {
decorations: Range[]
element: SlateElement
renderElement?: (props: RenderElementProps) => JSX.Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
selection: Range | null
}) => {
@ -32,6 +37,7 @@ const Element = (props: {
decorations,
element,
renderElement = (p: RenderElementProps) => <DefaultElement {...p} />,
renderPlaceholder,
renderLeaf,
selection,
} = props
@ -44,6 +50,7 @@ const Element = (props: {
decorations,
node: element,
renderElement,
renderPlaceholder,
renderLeaf,
selection,
})
@ -98,7 +105,13 @@ const Element = (props: {
position: 'absolute',
}}
>
<Text decorations={[]} isLast={false} parent={element} text={text} />
<Text
renderPlaceholder={renderPlaceholder}
decorations={[]}
isLast={false}
parent={element}
text={text}
/>
</Tag>
)

View File

@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react'
import { Element, Text } from 'slate'
import String from './string'
import { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'
import { RenderLeafProps } from './editable'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
// auto-incrementing key for String component, force it refresh to
// prevent inconsistent rendering by React with IME input
@ -15,6 +15,7 @@ const Leaf = (props: {
isLast: boolean
leaf: Text
parent: Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
text: Text
}) => {
@ -23,6 +24,7 @@ const Leaf = (props: {
isLast,
text,
parent,
renderPlaceholder,
renderLeaf = (props: RenderLeafProps) => <DefaultLeaf {...props} />,
} = props
@ -43,7 +45,7 @@ const Leaf = (props: {
return () => {
editorEl.style.minHeight = 'auto'
}
}, [placeholderRef])
}, [placeholderRef, leaf])
let children = (
<String
@ -56,27 +58,28 @@ const Leaf = (props: {
)
if (leaf[PLACEHOLDER_SYMBOL]) {
const placeholderProps: RenderPlaceholderProps = {
children: leaf.placeholder,
attributes: {
'data-slate-placeholder': true,
style: {
position: 'absolute',
pointerEvents: 'none',
width: '100%',
maxWidth: '100%',
display: 'block',
opacity: '0.333',
userSelect: 'none',
textDecoration: 'none',
},
contentEditable: false,
ref: placeholderRef,
},
}
children = (
<React.Fragment>
<span
ref={placeholderRef}
contentEditable={false}
style={{
pointerEvents: 'none',
display: 'inline-block',
width: '100%',
maxWidth: '100%',
whiteSpace: 'nowrap',
opacity: '0.333',
userSelect: 'none',
fontStyle: 'normal',
fontWeight: 'normal',
textDecoration: 'none',
position: 'absolute',
}}
>
{leaf.placeholder}
</span>
{renderPlaceholder(placeholderProps)}
{children}
</React.Fragment>
)
@ -99,6 +102,7 @@ const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
next.parent === prev.parent &&
next.isLast === prev.isLast &&
next.renderLeaf === prev.renderLeaf &&
next.renderPlaceholder === prev.renderPlaceholder &&
next.text === prev.text &&
next.leaf.text === prev.leaf.text &&
Text.matches(next.leaf, prev.leaf) &&

View File

@ -3,7 +3,7 @@ import { Range, Element, Text as SlateText } from 'slate'
import Leaf from './leaf'
import { ReactEditor, useSlateStatic } from '..'
import { RenderLeafProps } from './editable'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
import {
KEY_TO_ELEMENT,
@ -20,10 +20,18 @@ const Text = (props: {
decorations: Range[]
isLast: boolean
parent: Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
text: SlateText
}) => {
const { decorations, isLast, parent, renderLeaf, text } = props
const {
decorations,
isLast,
parent,
renderPlaceholder,
renderLeaf,
text,
} = props
const editor = useSlateStatic()
const ref = useRef<HTMLSpanElement>(null)
const leaves = SlateText.decorations(text, decorations)
@ -37,6 +45,7 @@ const Text = (props: {
<Leaf
isLast={isLast && i === leaves.length - 1}
key={`${key.id}-${i}`}
renderPlaceholder={renderPlaceholder}
leaf={leaf}
text={text}
parent={parent}

View File

@ -7,7 +7,11 @@ import { ReactEditor } from '..'
import { useSlateStatic } from './use-slate-static'
import { useDecorate } from './use-decorate'
import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
import { RenderElementProps, RenderLeafProps } from '../components/editable'
import {
RenderElementProps,
RenderLeafProps,
RenderPlaceholderProps,
} from '../components/editable'
/**
* Children.
@ -17,10 +21,18 @@ const useChildren = (props: {
decorations: Range[]
node: Ancestor
renderElement?: (props: RenderElementProps) => JSX.Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
selection: Range | null
}) => {
const { decorations, node, renderElement, renderLeaf, selection } = props
const {
decorations,
node,
renderElement,
renderPlaceholder,
renderLeaf,
selection,
} = props
const decorate = useDecorate()
const editor = useSlateStatic()
const path = ReactEditor.findPath(editor, node)
@ -53,6 +65,7 @@ const useChildren = (props: {
element={n}
key={key.id}
renderElement={renderElement}
renderPlaceholder={renderPlaceholder}
renderLeaf={renderLeaf}
selection={sel}
/>
@ -64,6 +77,7 @@ const useChildren = (props: {
key={key.id}
isLast={isLeafBlock && i === node.children.length - 1}
parent={node}
renderPlaceholder={renderPlaceholder}
renderLeaf={renderLeaf}
text={n}
/>

View File

@ -3,6 +3,8 @@ export {
RenderElementProps,
RenderLeafProps,
Editable,
RenderPlaceholderProps,
DefaultPlaceholder,
} from './components/editable'
export { DefaultElement } from './components/element'
export { DefaultLeaf } from './components/leaf'

View File

@ -0,0 +1,34 @@
import React, { useState, useMemo } from 'react'
import { createEditor, Descendant } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
import { withHistory } from 'slate-history'
const PlainTextExample = () => {
const [value, setValue] = useState<Descendant[]>(initialValue)
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Editable
placeholder="Type something"
renderPlaceholder={({ children, attributes }) => (
<div {...attributes}>
<p>{children}</p>
<pre>
Use the renderPlaceholder prop to customize rendering of the
placeholder
</pre>
</div>
)}
/>
</Slate>
)
}
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [{ text: '' }],
},
]
export default PlainTextExample

View File

@ -26,6 +26,7 @@ import SearchHighlighting from '../../examples/search-highlighting'
import ShadowDOM from '../../examples/shadow-dom'
import Tables from '../../examples/tables'
import IFrames from '../../examples/iframe'
import CustomPlaceholder from '../../examples/custom-placeholder'
// node
import { getAllExamples } from '../api'
@ -50,6 +51,7 @@ const EXAMPLES = [
['Shadow DOM', ShadowDOM, 'shadow-dom'],
['Tables', Tables, 'tables'],
['Rendering in iframes', IFrames, 'iframe'],
['Custom placeholder', CustomPlaceholder, 'custom-placeholder'],
]
const Header = props => (