Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
type CollapsedPreferences,
type FormState,
} from 'payload'
import { deepCopyObjectSimpleWithoutReactComponents, reduceFieldsToValues } from 'payload/shared'
import { deepCopyObjectSimpleWithoutReactComponents } from 'payload/shared'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { v4 as uuid } from 'uuid'

Expand All @@ -49,13 +49,16 @@ import {
useDrawerSubmit,
} from '../../../../utilities/fieldsDrawer/useDrawerSubmit.js'
import { useLexicalDrawer } from '../../../../utilities/fieldsDrawer/useLexicalDrawer.js'
import {
getCachedFormStateIfDataMatches,
reduceFormStateToBlockData,
} from '../getCachedFormStateIfDataMatches.js'
import { $isBlockNode } from '../nodes/BlocksNode.js'
import {
type BlockCollapsibleWithErrorProps,
BlockContent,
useBlockComponentContext,
} from './BlockContent.js'
import { removeEmptyArrayValues } from './removeEmptyArrayValues.js'

export type BlockComponentProps<TFormData extends Record<string, unknown> = BlockFields> = {
/**
Expand Down Expand Up @@ -128,41 +131,40 @@ export const BlockComponent: React.FC<BlockComponentProps> = (props) => {
const isEditable = useLexicalEditable()

const blockType = formData.blockType
const formDataRef = useRef(formData)
formDataRef.current = formData

const { getFormState } = useServerFunctions()
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_blocks.${blockType}.fields`

const [initialState, setInitialState] = React.useState<false | FormState | undefined>(() => {
// Initial form state that was calculated server-side. May have stale values
const cachedFormState = initialLexicalFormState?.[formData.id]?.formState
const cachedState = initialLexicalFormState?.[formData.id]
const cachedFormState = cachedState?.formState
if (!cachedFormState) {
return false
}

// Merge current formData values into the cached form state
// This ensures that when the component remounts (e.g., due to view changes), we don't lose user edits
const mergedState = Object.fromEntries(
Object.entries(cachedFormState).map(([fieldName, fieldState]) => [
fieldName,
fieldName in formData
? {
...fieldState,
initialValue: formData[fieldName],
value: formData[fieldName],
}
: fieldState,
]),
)
const matchingCachedState = getCachedFormStateIfDataMatches({
cachedFormState,
cachedSchemaPath: cachedState.schemaPath,
currentSchemaPath: schemaFieldsPath,
formData,
})
if (!matchingCachedState) {
return false
}

// Manually add blockName, as it's not part of cachedFormState
mergedState.blockName = {
initialValue: formData.blockName,
passesCondition: true,
valid: true,
value: formData.blockName,
return {
...matchingCachedState,
blockName: {
initialValue: formData.blockName,
passesCondition: true,
valid: true,
value: formData.blockName,
},
}

return mergedState
})

const hasMounted = useRef(false)
Expand Down Expand Up @@ -271,9 +273,8 @@ export const BlockComponent: React.FC<BlockComponentProps> = (props) => {
value: formData.blockName,
}

const newFormStateData: BlockFields = reduceFieldsToValues(
const newFormStateData = reduceFormStateToBlockData(
deepCopyObjectSimpleWithoutReactComponents(state, { excludeFiles: true }),
true,
) as BlockFields

// Things like default values may come back from the server => update the node with the new data
Expand Down Expand Up @@ -351,10 +352,15 @@ export const BlockComponent: React.FC<BlockComponentProps> = (props) => {

const controller = new AbortController()
onChangeAbortControllerRef.current = controller
const blockData = reduceFormStateToBlockData(
deepCopyObjectSimpleWithoutReactComponents(prevFormState, { excludeFiles: true }),
formDataRef.current,
)

const { state: newFormState } = await getFormState({
id,
collectionSlug,
data: blockData,
docPermissions: {
fields: true,
},
Expand All @@ -364,7 +370,7 @@ export const BlockComponent: React.FC<BlockComponentProps> = (props) => {
}),
formState: prevFormState,
globalSlug,
initialBlockFormState: prevFormState,
initialBlockData: blockData,
operation: 'update',
readOnly: !isEditable,
renderAllFields: submit ? true : false,
Expand All @@ -380,11 +386,9 @@ export const BlockComponent: React.FC<BlockComponentProps> = (props) => {
newFormState.blockName = prevFormState.blockName
}

const newFormStateData: BlockFields = reduceFieldsToValues(
removeEmptyArrayValues({
fields: deepCopyObjectSimpleWithoutReactComponents(newFormState, { excludeFiles: true }),
}),
true,
const newFormStateData = reduceFormStateToBlockData(
deepCopyObjectSimpleWithoutReactComponents(newFormState, { excludeFiles: true }),
blockData,
) as BlockFields

setTimeout(() => {
Expand Down Expand Up @@ -764,14 +768,18 @@ export const BlockComponent: React.FC<BlockComponentProps> = (props) => {
fields={clientBlock?.fields ?? []}
initialState={initialState}
onChange={[onChange]}
onSubmit={(formState, newData) => {
onSubmit={(formState) => {
// This is only called when form is submitted from drawer - usually only the case if the block has a custom Block component
const newData = reduceFormStateToBlockData(
formState,
formDataRef.current,
) as BlockFields
newData.blockType = blockType
editor.update(
() => {
const node = $getNodeByKey(nodeKey)
if (node && $isBlockNode(node)) {
node.setFields(newData as BlockFields, true)
node.setFields(newData, true)
}
},
// Without this, the outer editor's reconciler resets DOM selection
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import type { BlocksFieldClient, ClientBlock, Data, FormState } from 'payload'
import type { BlocksFieldClient, ClientBlock, FormState } from 'payload'

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useLexicalEditable } from '@lexical/react/useLexicalEditable'
Expand All @@ -26,7 +26,7 @@ import { $getNodeByKey, SKIP_DOM_SELECTION_TAG } from 'lexical'
import './index.css'
import '../../../../utilities/fieldsDrawer/index.css'

import { deepCopyObjectSimpleWithoutReactComponents, reduceFieldsToValues } from 'payload/shared'
import { deepCopyObjectSimpleWithoutReactComponents } from 'payload/shared'
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react'
import { v4 as uuid } from 'uuid'

Expand All @@ -40,6 +40,10 @@ import {
useDrawerSubmit,
} from '../../../../utilities/fieldsDrawer/useDrawerSubmit.js'
import { useLexicalDrawer } from '../../../../utilities/fieldsDrawer/useLexicalDrawer.js'
import {
getCachedFormStateIfDataMatches,
reduceFormStateToBlockData,
} from '../getCachedFormStateIfDataMatches.js'
import { $isInlineBlockNode } from '../nodes/InlineBlocksNode.js'

export type InlineBlockComponentProps<
Expand Down Expand Up @@ -90,28 +94,25 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
const { getFormState } = useServerFunctions()
const editDepth = useEditDepth()
const firstTimeDrawer = useRef(false)
const blockType = formData.blockType
const formDataRef = useRef(formData)
formDataRef.current = formData
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.${blockType}.fields`

const [initialState, setInitialState] = React.useState<false | FormState | undefined>(() => {
// Initial form state that was calculated server-side. May have stale values
const cachedFormState = initialLexicalFormState?.[formData.id]?.formState
const cachedState = initialLexicalFormState?.[formData.id]
const cachedFormState = cachedState?.formState
if (!cachedFormState) {
return false
}

// Merge current formData values into the cached form state
// This ensures that when the component remounts (e.g., due to view changes), we don't lose user edits
return Object.fromEntries(
Object.entries(cachedFormState).map(([fieldName, fieldState]) => [
fieldName,
fieldName in formData
? {
...fieldState,
initialValue: formData[fieldName],
value: formData[fieldName],
}
: fieldState,
]),
)
return getCachedFormStateIfDataMatches({
cachedFormState,
cachedSchemaPath: cachedState.schemaPath,
currentSchemaPath: schemaFieldsPath,
formData,
})
})

const hasMounted = useRef(false)
Expand Down Expand Up @@ -191,7 +192,7 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
const { id, collectionSlug, getDocPreferences, globalSlug } = useDocumentInfo()
const { config } = useConfig()

const componentMapRenderedBlockPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.${formData.blockType}`
const componentMapRenderedBlockPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.${blockType}`

const clientSchemaMap = featureClientSchemaMap['blocks']

Expand Down Expand Up @@ -228,7 +229,6 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
: clientBlock?.slug

const onChangeAbortControllerRef = useRef(new AbortController())
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.${clientBlock?.slug}.fields`

// Initial state for newly created blocks
useEffect(() => {
Expand All @@ -251,7 +251,6 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
}),
globalSlug,
initialBlockData: formData,
initialBlockFormState: formData,
operation: 'update',
readOnly: !isEditable,
renderAllFields: true,
Expand All @@ -260,9 +259,8 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
})

if (state) {
const newFormStateData: InlineBlockFields = reduceFieldsToValues(
const newFormStateData = reduceFormStateToBlockData(
deepCopyObjectSimpleWithoutReactComponents(state, { excludeFiles: true }),
true,
) as InlineBlockFields

// Things like default values may come back from the server => update the node with the new data
Expand Down Expand Up @@ -324,10 +322,15 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc

const controller = new AbortController()
onChangeAbortControllerRef.current = controller
const blockData = reduceFormStateToBlockData(
deepCopyObjectSimpleWithoutReactComponents(prevFormState, { excludeFiles: true }),
formDataRef.current,
)

const { state } = await getFormState({
id,
collectionSlug,
data: blockData,
docPermissions: {
fields: true,
},
Expand All @@ -337,7 +340,7 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
}),
formState: prevFormState,
globalSlug,
initialBlockFormState: prevFormState,
initialBlockData: blockData,
operation: 'update',
readOnly: !isEditable,
renderAllFields: submit ? true : false,
Expand Down Expand Up @@ -396,8 +399,9 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
* HANDLE FORM SUBMIT
*/
const onFormSubmit = useCallback(
(formState: FormState, newData: Data) => {
newData.blockType = formData.blockType
(formState: FormState) => {
const newData = reduceFormStateToBlockData(formState, formDataRef.current)
newData.blockType = blockType

editor.update(
() => {
Expand All @@ -411,7 +415,7 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
{ tag: SKIP_DOM_SELECTION_TAG },
)
},
[editor, nodeKey, formData],
[blockType, editor, nodeKey],
)

const { headerActions, submitRef } = useDrawerSubmit()
Expand Down Expand Up @@ -519,8 +523,8 @@ export const InlineBlockComponent: React.FC<InlineBlockComponentProps<InlineBloc
fields={clientBlock?.fields}
initialState={initialState || {}}
onChange={[onChange]}
onSubmit={(formState, data) => {
onFormSubmit(formState, data)
onSubmit={(formState) => {
onFormSubmit(formState)
toggleDrawer()
}}
uuid={formUuid}
Expand Down
Loading
Loading