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

feat(pte): initial support for renderEditable in portable text inputs #6627

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable react/jsx-no-bind */
import {Card} from '@sanity/ui'
import {BlockEditor, defineType, type PortableTextInputProps} from 'sanity'

export const ptCustomBlockEditors = defineType({
Expand All @@ -19,7 +21,6 @@ export const ptCustomBlockEditors = defineType({
{
name: 'hiddenToolbar',
title: 'Hidden toolbar',
description: 'hideToolbar=true',
type: 'array',
components: {
input: (props: PortableTextInputProps) => <BlockEditor {...props} hideToolbar />,
Expand All @@ -29,12 +30,39 @@ export const ptCustomBlockEditors = defineType({
{
name: 'readOnly',
title: 'Read only',
description: 'readOnly=true',
type: 'array',
components: {
input: (props: PortableTextInputProps) => <BlockEditor {...props} readOnly />,
},
of: [{type: 'block'}],
},
{
name: 'renderEditable',
title: 'Custom renderEditable',
description: 'Wrapped in card components with a custom placeholder',
type: 'array',
components: {
input: (props: PortableTextInputProps) => (
<BlockEditor
{...props}
renderEditable={(editableProps) => {
return (
<Card border padding={2} tone="critical">
<Card border padding={2} tone="critical">
{editableProps.renderDefault({
...editableProps,
renderPlaceholder: () => (
<span style={{opacity: 0.25}}>Nothing to see here</span>
),
})}
</Card>
</Card>
)
}}
/>
),
},
of: [{type: 'block'}],
},
],
})
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
forwardRef,
type HTMLProps,
type KeyboardEvent,
type MutableRefObject,
type ReactNode,
type TextareaHTMLAttributes,
useCallback,
Expand Down Expand Up @@ -92,6 +93,7 @@ export type PortableTextEditableProps = Omit<
onBeforeInput?: (event: InputEvent) => void
onPaste?: OnPasteFn
onCopy?: OnCopyFn
ref: MutableRefObject<HTMLDivElement | null>
rangeDecorations?: RangeDecoration[]
renderAnnotation?: RenderAnnotationFunction
renderBlock?: RenderBlockFunction
Expand Down
4 changes: 4 additions & 0 deletions packages/@sanity/portable-text-editor/src/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {type Descendant, type Node as SlateNode, type Operation as SlateOperatio
import {type ReactEditor} from 'slate-react'
import {type DOMNode} from 'slate-react/dist/utils/dom'

import {type PortableTextEditableProps} from '../editor/Editable'
import {type PortableTextEditor} from '../editor/PortableTextEditor'
import {type Patch} from '../types/patch'

Expand Down Expand Up @@ -484,6 +485,9 @@ export type RenderBlockFunction = (props: BlockRenderProps) => JSX.Element
/** @beta */
export type RenderChildFunction = (props: BlockChildRenderProps) => JSX.Element

/** @beta */
export type RenderEditableFunction = (props: PortableTextEditableProps) => JSX.Element

/** @beta */
export type RenderAnnotationFunction = (props: BlockAnnotationRenderProps) => JSX.Element

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {type ReactNode, useCallback, useMemo, useState} from 'react'
import {ChangeIndicator} from '../../../changeIndicators'
import {EMPTY_ARRAY} from '../../../util'
import {ActivateOnFocus} from '../../components/ActivateOnFocus/ActivateOnFocus'
import {type ArrayOfObjectsInputProps, type RenderCustomMarkers} from '../../types'
import {
type ArrayOfObjectsInputProps,
type PortableTextInputProps,
type RenderCustomMarkers,
} from '../../types'
import {type RenderBlockActionsCallback} from '../../types/_transitional'
import {ExpandedLayer, Root} from './Compositor.styles'
import {Editor} from './Editor'
Expand All @@ -42,6 +46,7 @@ interface InputProps extends ArrayOfObjectsInputProps<PortableTextBlock> {
rangeDecorations?: RangeDecoration[]
renderBlockActions?: RenderBlockActionsCallback
renderCustomMarkers?: RenderCustomMarkers
renderEditable?: PortableTextInputProps['renderEditable']
}

/** @internal */
Expand Down Expand Up @@ -74,6 +79,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderBlock,
renderBlockActions,
renderCustomMarkers,
renderEditable,
renderField,
renderInlineBlock,
renderInput,
Expand Down Expand Up @@ -408,6 +414,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
renderAnnotation={editorRenderAnnotation}
renderBlock={editorRenderBlock}
renderChild={editorRenderChild}
renderEditable={renderEditable}
setPortalElement={setPortalElement}
scrollElement={scrollElement}
setScrollElement={setScrollElement}
Expand All @@ -433,6 +440,7 @@ export function Compositor(props: Omit<InputProps, 'schemaType' | 'arrayFunction
path,
rangeDecorations,
readOnly,
renderEditable,
scrollElement,
],
)
Expand Down
72 changes: 41 additions & 31 deletions packages/sanity/src/core/form/inputs/PortableText/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type OnCopyFn,
type OnPasteFn,
PortableTextEditable,
type PortableTextEditableProps,
type RangeDecoration,
type RenderAnnotationFunction,
type RenderBlockFunction,
Expand All @@ -16,11 +17,13 @@ import {type Path} from '@sanity/types'
import {BoundaryElementProvider, useBoundaryElement, useGlobalKeyDown, useLayer} from '@sanity/ui'
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import {omit} from 'lodash'
import {type ReactNode, useCallback, useMemo} from 'react'
import {css, styled} from 'styled-components'

import {TooltipDelayGroupProvider} from '../../../../ui-components'
import {useTranslation} from '../../../i18n'
import {type PortableTextInputProps} from '../../types/inputProps'
import {useFormBuilder} from '../../useFormBuilder'
import {
EditableCard,
Expand All @@ -31,7 +34,7 @@ import {
ToolbarCard,
} from './Editor.styles'
import {useScrollSelectionIntoView} from './hooks/useScrollSelectionIntoView'
import {useSpellcheck} from './hooks/useSpellCheck'
import {useSpellCheck} from './hooks/useSpellCheck'
import {Decorator} from './text'
import {ListItem} from './text/ListItem'
import {Style} from './text/Style'
Expand Down Expand Up @@ -67,6 +70,7 @@ interface EditorProps {
renderAnnotation: RenderAnnotationFunction
renderBlock: RenderBlockFunction
renderChild: RenderChildFunction
renderEditable?: PortableTextInputProps['renderEditable']
scrollElement: HTMLElement | null
setPortalElement?: (portalElement: HTMLDivElement | null) => void
setScrollElement: (scrollElement: HTMLElement | null) => void
Expand Down Expand Up @@ -105,6 +109,7 @@ export function Editor(props: EditorProps): ReactNode {
renderAnnotation,
renderBlock,
renderChild,
renderEditable,
scrollElement,
setPortalElement,
setScrollElement,
Expand Down Expand Up @@ -139,48 +144,53 @@ export function Editor(props: EditorProps): ReactNode {
),
[t],
)
const spellcheck = useSpellcheck()
const spellCheck = useSpellCheck()

const scrollSelectionIntoView = useScrollSelectionIntoView(scrollElement)

const editable = useMemo(
() => (
<PortableTextEditable
aria-describedby={ariaDescribedBy}
hotkeys={hotkeys}
onCopy={onCopy}
onPaste={onPaste}
ref={elementRef}
rangeDecorations={rangeDecorations}
renderAnnotation={renderAnnotation}
renderBlock={renderBlock}
renderChild={renderChild}
renderDecorator={renderDecorator}
renderListItem={renderListItem}
renderPlaceholder={renderPlaceholder}
renderStyle={renderStyle}
scrollSelectionIntoView={scrollSelectionIntoView}
selection={initialSelection}
spellCheck={spellcheck}
style={noOutlineStyle}
/>
),
[
ariaDescribedBy,
elementRef,
const editable = useMemo(() => {
const editableProps = {
'aria-describedby': ariaDescribedBy,
hotkeys,
initialSelection,
onCopy,
onPaste,
rangeDecorations,
'ref': elementRef,
renderAnnotation,
renderBlock,
renderChild,
renderDecorator,
renderListItem,
renderPlaceholder,
renderStyle,
scrollSelectionIntoView,
spellcheck,
],
)
'selection': initialSelection,
spellCheck,
'style': noOutlineStyle,
} satisfies PortableTextEditableProps
const defaultRender = (defaultRenderProps: PortableTextEditableProps) => (
<PortableTextEditable {...editableProps} {...omit(defaultRenderProps, ['renderDefault'])} />
)
if (renderEditable) {
return renderEditable({...editableProps, renderDefault: defaultRender})
}
return defaultRender(editableProps)
}, [
ariaDescribedBy,
elementRef,
hotkeys,
initialSelection,
onCopy,
onPaste,
rangeDecorations,
renderAnnotation,
renderBlock,
renderChild,
renderEditable,
renderPlaceholder,
scrollSelectionIntoView,
spellCheck,
])

const handleToolBarOnMemberOpen = useCallback(
(relativePath: Path) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
type OnPasteFn,
type Patch as EditorPatch,
type Patch,
type PortableTextEditableProps,
PortableTextEditor,
type RangeDecoration,
type RenderEditableFunction,
} from '@sanity/portable-text-editor'
import {useTelemetry} from '@sanity/telemetry/react'
import {isKeySegment, type PortableTextBlock} from '@sanity/types'
Expand Down Expand Up @@ -63,6 +65,10 @@ export interface PortableTextMemberItem {
elementRef?: MutableRefObject<PortableTextEditorElement | null>
input?: ReactNode
}
/** @public */
export interface RenderPortableTextInputEditableProps extends PortableTextEditableProps {
renderDefault: RenderEditableFunction
}

/**
* Input component for editing block content
Expand Down Expand Up @@ -96,6 +102,7 @@ export function PortableTextInput(props: PortableTextInputProps): ReactNode {
rangeDecorations: rangeDecorationsProp,
renderBlockActions,
renderCustomMarkers,
renderEditable,
schemaType,
value,
resolveUploader,
Expand Down Expand Up @@ -397,6 +404,7 @@ export function PortableTextInput(props: PortableTextInputProps): ReactNode {
rangeDecorations={rangeDecorations}
renderBlockActions={renderBlockActions}
renderCustomMarkers={renderCustomMarkers}
renderEditable={renderEditable}
/>
</PortableTextEditor>
</PortableTextMemberItemsProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {usePortableTextEditor} from '@sanity/portable-text-editor'
import {useMemo} from 'react'

export function useSpellcheck(): boolean {
export function useSpellCheck(): boolean {
const editor = usePortableTextEditor()
return useMemo(() => {
// Chrome 96. has serious perf. issues with spellchecking
Expand Down
8 changes: 8 additions & 0 deletions packages/sanity/src/core/form/types/inputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
type ReactElement,
} from 'react'

import {type RenderPortableTextInputEditableProps} from '../inputs'
import {type FormPatch, type PatchEvent} from '../patch'
import {type FormFieldGroup} from '../store'
import {
Expand Down Expand Up @@ -555,6 +556,13 @@ export interface PortableTextInputProps
* Use the `renderBlock` interface instead.
*/
renderCustomMarkers?: RenderCustomMarkers
/**
* Function to render the PortableTextInput's editable component.
* This is the actual contentEditable element that users type into.
* @hidden
* @beta
*/
renderEditable?: (props: RenderPortableTextInputEditableProps) => JSX.Element
/**
* Array of {@link RangeDecoration} that can be used to decorate the content.
*/
Expand Down