feat(richtext-lexical): more powerful custom Block RSCs, improved selection handling (#9422)
Now, custom Lexical block & inline block components are re-rendered if the fields drawer is saved. This ensures that RSCs receive the updated values, without having to resort to a client component that utilizes the `useForm` hook. Additionally, this PRs fixes the lexical selection jumping around after opening a Block or InlineBlock drawer and clicking inside of it.
This commit is contained in:
@@ -67,7 +67,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
slug: `lexical-blocks-create-${uuidFromContext}-${formData.id}`,
|
||||
depth: editDepth,
|
||||
})
|
||||
const { toggleDrawer } = useLexicalDrawer(drawerSlug, true)
|
||||
const { toggleDrawer } = useLexicalDrawer(drawerSlug)
|
||||
|
||||
// Used for saving collapsed to preferences (and gettin' it from there again)
|
||||
// Remember, these preferences are scoped to the whole document, not just this form. This
|
||||
@@ -92,6 +92,16 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
: false,
|
||||
)
|
||||
|
||||
const [CustomLabel, setCustomLabel] = React.useState<React.ReactNode | undefined>(
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
initialState?.['_components']?.customComponents?.BlockLabel,
|
||||
)
|
||||
|
||||
const [CustomBlock, setCustomBlock] = React.useState<React.ReactNode | undefined>(
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
initialState?.['_components']?.customComponents?.Block,
|
||||
)
|
||||
|
||||
// Initial state for newly created blocks
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController()
|
||||
@@ -124,6 +134,8 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
}
|
||||
|
||||
setInitialState(state)
|
||||
setCustomLabel(state._components?.customComponents?.BlockLabel)
|
||||
setCustomBlock(state._components?.customComponents?.Block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +190,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
formState: prevFormState,
|
||||
globalSlug,
|
||||
operation: 'update',
|
||||
renderAllFields: submit ? true : false,
|
||||
schemaPath: schemaFieldsPath,
|
||||
signal: controller.signal,
|
||||
})
|
||||
@@ -209,6 +222,9 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
}, 0)
|
||||
|
||||
if (submit) {
|
||||
setCustomLabel(newFormState._components?.customComponents?.BlockLabel)
|
||||
setCustomBlock(newFormState._components?.customComponents?.Block)
|
||||
|
||||
let rowErrorCount = 0
|
||||
for (const formField of Object.values(newFormState)) {
|
||||
if (formField?.valid === false) {
|
||||
@@ -246,11 +262,6 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
})
|
||||
}, [editor, nodeKey])
|
||||
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
const CustomLabel = initialState?.['_components']?.customComponents?.BlockLabel
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
const CustomBlock = initialState?.['_components']?.customComponents?.Block
|
||||
|
||||
const blockDisplayName = clientBlock?.labels?.singular
|
||||
? getTranslation(clientBlock.labels.singular, i18n)
|
||||
: clientBlock?.slug
|
||||
@@ -291,10 +302,18 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
buttonStyle="icon-label"
|
||||
className={`${baseClass}__editButton`}
|
||||
disabled={readOnly}
|
||||
el="div"
|
||||
el="button"
|
||||
icon="edit"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleDrawer()
|
||||
return false
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
// Needed to preserve lexical selection for toggleDrawer lexical selection restore.
|
||||
// I believe this is needed due to this button (usually) being inside of a collapsible.
|
||||
e.preventDefault()
|
||||
}}
|
||||
round
|
||||
size="small"
|
||||
@@ -453,6 +472,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
<Form
|
||||
beforeSubmit={[
|
||||
async ({ formState }) => {
|
||||
// This is only called when form is submitted from drawer - usually only the case if the block has a custom Block component
|
||||
return await onChange({ formState, submit: true })
|
||||
},
|
||||
]}
|
||||
@@ -460,7 +480,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
initialState={initialState}
|
||||
onChange={[onChange]}
|
||||
onSubmit={(formState) => {
|
||||
// THis is only called when form is submitted from drawer - usually only the case if the block has a custom Block component
|
||||
// This is only called when form is submitted from drawer - usually only the case if the block has a custom Block component
|
||||
const newData: any = reduceFieldsToValues(formState)
|
||||
newData.blockType = formData.blockType
|
||||
editor.update(() => {
|
||||
|
||||
@@ -86,6 +86,16 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
initialLexicalFormState?.[formData.id]?.formState,
|
||||
)
|
||||
|
||||
const [CustomLabel, setCustomLabel] = React.useState<React.ReactNode | undefined>(
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
initialState?.['_components']?.customComponents?.BlockLabel,
|
||||
)
|
||||
|
||||
const [CustomBlock, setCustomBlock] = React.useState<React.ReactNode | undefined>(
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
initialState?.['_components']?.customComponents?.Block,
|
||||
)
|
||||
|
||||
const drawerSlug = formatDrawerSlug({
|
||||
slug: `lexical-inlineBlocks-create-` + uuidFromContext,
|
||||
depth: editDepth,
|
||||
@@ -194,6 +204,8 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
|
||||
if (state) {
|
||||
setInitialState(state)
|
||||
setCustomLabel(state['_components']?.customComponents?.BlockLabel)
|
||||
setCustomBlock(state['_components']?.customComponents?.Block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +231,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
* HANDLE ONCHANGE
|
||||
*/
|
||||
const onChange = useCallback(
|
||||
async ({ formState: prevFormState }: { formState: FormState }) => {
|
||||
async ({ formState: prevFormState, submit }: { formState: FormState; submit?: boolean }) => {
|
||||
abortAndIgnore(onChangeAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
@@ -235,6 +247,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
formState: prevFormState,
|
||||
globalSlug,
|
||||
operation: 'update',
|
||||
renderAllFields: submit ? true : false,
|
||||
schemaPath: schemaFieldsPath,
|
||||
signal: controller.signal,
|
||||
})
|
||||
@@ -243,6 +256,11 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
return prevFormState
|
||||
}
|
||||
|
||||
if (submit) {
|
||||
setCustomLabel(state['_components']?.customComponents?.BlockLabel)
|
||||
setCustomBlock(state['_components']?.customComponents?.Block)
|
||||
}
|
||||
|
||||
return state
|
||||
},
|
||||
[getFormState, id, collectionSlug, getDocPreferences, globalSlug, schemaFieldsPath],
|
||||
@@ -270,10 +288,6 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
},
|
||||
[editor, nodeKey, formData],
|
||||
)
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
const CustomLabel = initialState?.['_components']?.customComponents?.BlockLabel
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
const CustomBlock = initialState?.['_components']?.customComponents?.Block
|
||||
|
||||
const RemoveButton = useMemo(
|
||||
() => () => (
|
||||
@@ -300,7 +314,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
buttonStyle="icon-label"
|
||||
className={`${baseClass}__editButton`}
|
||||
disabled={readOnly}
|
||||
el="div"
|
||||
el="button"
|
||||
icon="edit"
|
||||
onClick={() => {
|
||||
toggleDrawer()
|
||||
@@ -342,7 +356,12 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
beforeSubmit={[onChange]}
|
||||
beforeSubmit={[
|
||||
async ({ formState }) => {
|
||||
// This is only called when form is submitted from drawer
|
||||
return await onChange({ formState, submit: true })
|
||||
},
|
||||
]}
|
||||
disableValidationOnSubmit
|
||||
fields={clientBlock.fields}
|
||||
initialState={initialState || {}}
|
||||
|
||||
Reference in New Issue
Block a user