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

Accept type-keyed react render functions #5244

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
88 changes: 81 additions & 7 deletions packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,33 @@ const Children = (props: Parameters<typeof useChildren>[0]) => (
// The number of Editable components currently mounted.
let mountedCount = 0

/**
* `ElementType` represents the `type` string property on any `Element`.
*/

export type ElementType = Element extends infer E
? E extends { type: string }
? E['type']
: never
: never

/**
* `TypedElement` represents the `Element` with a `type` property of `T`.
*/

export type TypedElement<T extends ElementType> = Element extends infer E
? E extends { type: T }
? E
: never
: never

/**
* `RenderElementProps` are passed to the `renderElement` handler.
*/

export interface RenderElementProps {
export interface RenderElementProps<T extends ElementType = ElementType> {
children: any
element: Element
element: ElementType extends T ? Element : TypedElement<T>
attributes: {
'data-slate-node': 'element'
'data-slate-inline'?: true
Expand All @@ -96,19 +116,71 @@ export interface RenderElementProps {
}
}

/**
* `ElementRenderer` renders an element.
*/

export type ElementRenderer<T extends ElementType> = (
props: RenderElementProps<T>
) => JSX.Element

/**
* `ElementRenderers` maps element types to render functions.
*/

export type ElementRenderers = {
[T in ElementType]?: ElementRenderer<T>
}

/**
* `TextType` represents the `type` string property on any `Text`.
*/

export type TextType = Text extends infer T
? T extends { type: string }
? T['type']
: never
: never

/**
* `TypedText` represents the `Text` with a `type` property of `T`.
*/

export type TypedText<T extends TextType> = Text extends infer L
? L extends { type: T }
? T
: never
: never

/**
* `RenderLeafProps` are passed to the `renderLeaf` handler.
*/

export interface RenderLeafProps {
export interface RenderLeafProps<T extends TextType = TextType> {
children: any
leaf: Text
text: Text
leaf: TextType extends T ? Text : TypedText<T>
text: TextType extends T ? Text : TypedText<T>
attributes: {
'data-slate-leaf': true
}
}

/**
* `LeafRenderer` renders a leaf.
*/

export type LeafRenderer<T extends TextType> = (
props: RenderLeafProps<T>
) => JSX.Element

/**
* `LeafRenderers` maps leaf types to render functions.
*/

export type LeafRenderers = {
[T in TextType]?: LeafRenderer<T>
}

/**
* `EditableProps` are passed to the `<Editable>` component.
*/
Expand All @@ -120,8 +192,10 @@ export type EditableProps = {
readOnly?: boolean
role?: string
style?: React.CSSProperties
renderElement?: (props: RenderElementProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
renderElement?:
| ((props: RenderElementProps) => JSX.Element)
| ElementRenderers
renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | LeafRenderers
renderPlaceholder?: (props: RenderPlaceholderProps) => JSX.Element
scrollSelectionIntoView?: (editor: ReactEditor, domRange: DOMRange) => void
as?: React.ElementType
Expand Down
20 changes: 18 additions & 2 deletions packages/slate-react/src/components/element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
} from '../utils/weak-maps'
import { isDecoratorRangeListEqual } from '../utils/range-list'
import {
ElementRenderers,
LeafRenderers,
RenderElementProps,
RenderLeafProps,
RenderPlaceholderProps,
Expand All @@ -26,9 +28,11 @@ import {
const Element = (props: {
decorations: Range[]
element: SlateElement
renderElement?: (props: RenderElementProps) => JSX.Element
renderElement?:
| ((props: RenderElementProps) => JSX.Element)
| ElementRenderers
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | LeafRenderers
selection: Range | null
}) => {
const {
Expand Down Expand Up @@ -131,6 +135,18 @@ const Element = (props: {
NODE_TO_PARENT.set(text, element)
}

if (typeof renderElement !== 'function') {
const elementType = Reflect.get(element, 'type')
if (typeof elementType === 'string' && elementType in renderElement) {
return renderElement[elementType]({ attributes, children, element })
}
return (
<DefaultElement attributes={attributes} element={element}>
{children}
</DefaultElement>
)
}

return renderElement({ attributes, children, element })
}

Expand Down
20 changes: 18 additions & 2 deletions packages/slate-react/src/components/leaf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
EDITOR_TO_PLACEHOLDER_ELEMENT,
EDITOR_TO_STYLE_ELEMENT,
} from '../utils/weak-maps'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
import {
LeafRenderers,
RenderLeafProps,
RenderPlaceholderProps,
} from './editable'
import { useSlateStatic } from '../hooks/use-slate-static'

/**
Expand All @@ -18,7 +22,7 @@ const Leaf = (props: {
leaf: Text
parent: Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | LeafRenderers
text: Text
}) => {
const {
Expand Down Expand Up @@ -126,6 +130,18 @@ const Leaf = (props: {
'data-slate-leaf': true,
}

if (typeof renderLeaf !== 'function') {
const leafType = Reflect.get(leaf, 'type')
if (typeof leafType === 'string' && leafType in renderLeaf) {
return renderLeaf[leafType]({ attributes, children, leaf, text })
}
return (
<DefaultLeaf attributes={attributes} leaf={leaf} text={text}>
{children}
</DefaultLeaf>
)
}

return renderLeaf({ attributes, children, leaf, text })
}

Expand Down
8 changes: 6 additions & 2 deletions packages/slate-react/src/components/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
ELEMENT_TO_NODE,
NODE_TO_ELEMENT,
} from '../utils/weak-maps'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
import {
LeafRenderers,
RenderLeafProps,
RenderPlaceholderProps,
} from './editable'
import Leaf from './leaf'

/**
Expand All @@ -20,7 +24,7 @@ const Text = (props: {
isLast: boolean
parent: Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | LeafRenderers
text: SlateText
}) => {
const {
Expand Down
8 changes: 6 additions & 2 deletions packages/slate-react/src/hooks/use-children.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { useSlateStatic } from './use-slate-static'
import { useDecorate } from './use-decorate'
import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
import {
ElementRenderers,
LeafRenderers,
RenderElementProps,
RenderLeafProps,
RenderPlaceholderProps,
Expand All @@ -21,9 +23,11 @@ import { SelectedContext } from './use-selected'
const useChildren = (props: {
decorations: Range[]
node: Ancestor
renderElement?: (props: RenderElementProps) => JSX.Element
renderElement?:
| ((props: RenderElementProps) => JSX.Element)
| ElementRenderers
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
renderLeaf?: (props: RenderLeafProps) => JSX.Element
renderLeaf?: ((props: RenderLeafProps) => JSX.Element) | LeafRenderers
selection: Range | null
}) => {
const {
Expand Down
2 changes: 2 additions & 0 deletions packages/slate-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Components
export {
Editable,
ElementRenderer,
ElementRenderers,
RenderElementProps,
RenderLeafProps,
RenderPlaceholderProps,
Expand Down
28 changes: 13 additions & 15 deletions site/examples/inlines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ const InlinesExample = () => {
<ToggleEditableButtonButton />
</Toolbar>
<Editable
renderElement={props => <Element {...props} />}
renderElement={{
button: EditableButtonComponent,
link: LinkComponent,
}}
renderLeaf={props => <Text {...props} />}
placeholder="Enter some text..."
onKeyDown={onKeyDown}
Expand Down Expand Up @@ -230,7 +233,11 @@ const InlineChromiumBugfix = () => (
</span>
)

const LinkComponent = ({ attributes, children, element }) => {
const LinkComponent: SlateReact.ElementRenderer<'link'> = ({
attributes,
children,
element,
}) => {
const selected = useSelected()
return (
<a
Expand All @@ -251,7 +258,10 @@ const LinkComponent = ({ attributes, children, element }) => {
)
}

const EditableButtonComponent = ({ attributes, children }) => {
const EditableButtonComponent: SlateReact.ElementRenderer<'button'> = ({
attributes,
children,
}) => {
return (
/*
Note that this is not a true button, but a span with button-like CSS.
Expand Down Expand Up @@ -283,18 +293,6 @@ const EditableButtonComponent = ({ attributes, children }) => {
)
}

const Element = props => {
const { attributes, children, element } = props
switch (element.type) {
case 'link':
return <LinkComponent {...props} />
case 'button':
return <EditableButtonComponent {...props} />
default:
return <p {...attributes}>{children}</p>
}
}

const Text = props => {
const { attributes, children, leaf } = props
return (
Expand Down