Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add renderPlaceholder #4190

Merged
5 changes: 5 additions & 0 deletions .changeset/few-planets-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'slate-react': patch
---

Added a `renderPlaceholder` prop to the `<Editable>` component for customizing how placeholders are rendered.
29 changes: 28 additions & 1 deletion packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import {
getDefaultView,
isDOMElement,
isDOMNode,
DOMStaticRange,
isPlainTextOnlyPaste,
} from '../utils/dom'

import {
EDITOR_TO_ELEMENT,
ELEMENT_TO_NODE,
Expand Down Expand Up @@ -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>

Expand All @@ -114,6 +115,7 @@ export const Editable = (props: EditableProps) => {
readOnly = false,
renderElement,
renderLeaf,
renderPlaceholder = props => <DefaultPlaceholder {...props} />,
style = {},
as: Component = 'div',
...attributes
Expand Down Expand Up @@ -225,6 +227,7 @@ export const Editable = (props: EditableProps) => {
scrollMode: 'if-needed',
boundary: el,
})
// @ts-ignore
delete leafEl.getBoundingClientRect
} else {
domSelection.removeAllRanges()
Expand Down Expand Up @@ -1046,6 +1049,7 @@ export const Editable = (props: EditableProps) => {
decorations,
node: editor,
renderElement,
renderPlaceholder,
renderLeaf,
selection: editor.selection,
})}
Expand All @@ -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.
*/
Expand Down
17 changes: 15 additions & 2 deletions packages/slate-react/src/components/element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -25,13 +29,15 @@ 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
}) => {
const {
decorations,
element,
renderElement = (p: RenderElementProps) => <DefaultElement {...p} />,
renderPlaceholder,
renderLeaf,
selection,
} = props
Expand All @@ -44,6 +50,7 @@ const Element = (props: {
decorations,
node: element,
renderElement,
renderPlaceholder,
renderLeaf,
selection,
})
Expand Down Expand Up @@ -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>
)

Expand Down
46 changes: 25 additions & 21 deletions packages/slate-react/src/components/leaf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}) => {
Expand All @@ -23,6 +24,7 @@ const Leaf = (props: {
isLast,
text,
parent,
renderPlaceholder,
renderLeaf = (props: RenderLeafProps) => <DefaultLeaf {...props} />,
} = props

Expand All @@ -43,7 +45,7 @@ const Leaf = (props: {
return () => {
editorEl.style.minHeight = 'auto'
}
}, [placeholderRef])
}, [placeholderRef, leaf])

let children = (
<String
Expand All @@ -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>
)
Expand All @@ -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) &&
Expand Down
13 changes: 11 additions & 2 deletions packages/slate-react/src/components/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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}
Expand Down
18 changes: 16 additions & 2 deletions packages/slate-react/src/hooks/use-children.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -53,6 +65,7 @@ const useChildren = (props: {
element={n}
key={key.id}
renderElement={renderElement}
renderPlaceholder={renderPlaceholder}
renderLeaf={renderLeaf}
selection={sel}
/>
Expand All @@ -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}
/>
Expand Down
2 changes: 2 additions & 0 deletions packages/slate-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export {
RenderElementProps,
RenderLeafProps,
Editable,
RenderPlaceholderProps,
DefaultPlaceholder,
} from './components/editable'
export { DefaultElement } from './components/element'
export { DefaultLeaf } from './components/leaf'
Expand Down
34 changes: 34 additions & 0 deletions site/examples/custom-placeholder.tsx
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions site/pages/examples/[example].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 => (
Expand Down