fix(richtext-lexical): ensure hooks from sub-fields receive document data and doc props, and not node data and doc props (#9406)
This also adds a new `previousNode` prop to afterChange node hooks, and makes sure that sub-fields receive the correct previousSiblingDoc.
This commit is contained in:
@@ -176,6 +176,7 @@ export type AfterChangeNodeHookArgs<T extends SerializedLexicalNode> = {
|
||||
operation: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The value of the node before any changes. Not available in afterRead hooks */
|
||||
originalNode: T
|
||||
previousNode: T
|
||||
}
|
||||
export type BeforeValidateNodeHookArgs<T extends SerializedLexicalNode> = {
|
||||
/** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */
|
||||
@@ -199,6 +200,8 @@ export type BeforeChangeNodeHookArgs<T extends SerializedLexicalNode> = {
|
||||
* The original node with locales (not modified by any hooks).
|
||||
*/
|
||||
originalNodeWithLocales?: T
|
||||
previousNode: T
|
||||
|
||||
skipValidation: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,19 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async (args) => {
|
||||
const { collection, context: _context, global, operation, path, req, schemaPath } = args
|
||||
const {
|
||||
collection,
|
||||
context: _context,
|
||||
data,
|
||||
global,
|
||||
operation,
|
||||
originalDoc,
|
||||
path,
|
||||
previousDoc,
|
||||
previousValue,
|
||||
req,
|
||||
schemaPath,
|
||||
} = args
|
||||
let { value } = args
|
||||
if (finalSanitizedEditorConfig?.features?.hooks?.afterChange?.length) {
|
||||
for (const hook of finalSanitizedEditorConfig.features.hooks.afterChange) {
|
||||
@@ -203,6 +215,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
[key: string]: SerializedLexicalNode
|
||||
} = {}
|
||||
|
||||
const previousNodeIDMap: {
|
||||
[key: string]: SerializedLexicalNode
|
||||
} = {}
|
||||
|
||||
/**
|
||||
* Get the originalNodeIDMap from the beforeValidate hook, which is always run before this hook.
|
||||
*/
|
||||
@@ -219,6 +235,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
nodes: (value as SerializedEditorState)?.root?.children ?? [],
|
||||
})
|
||||
|
||||
recurseNodeTree({
|
||||
nodeIDMap: previousNodeIDMap,
|
||||
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [id, node] of Object.entries(nodeIDMap)) {
|
||||
const afterChangeHooks = finalSanitizedEditorConfig.features.nodeHooks?.afterChange
|
||||
@@ -243,6 +264,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
originalNode: originalNodeIDMap[id],
|
||||
parentRichTextFieldPath: path,
|
||||
parentRichTextFieldSchemaPath: schemaPath,
|
||||
previousNode: previousNodeIDMap[id],
|
||||
req,
|
||||
})
|
||||
}
|
||||
@@ -254,25 +276,27 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
|
||||
if (subFieldFn && subFieldDataFn) {
|
||||
const subFields = subFieldFn({ node, req })
|
||||
const data = subFieldDataFn({ node, req }) ?? {}
|
||||
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||
const nodePreviousSiblingDoc =
|
||||
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
await afterChangeTraverseFields({
|
||||
collection,
|
||||
context,
|
||||
data: originalData,
|
||||
doc: data,
|
||||
data: data ?? {},
|
||||
doc: originalDoc,
|
||||
fields: subFields,
|
||||
global,
|
||||
operation,
|
||||
path,
|
||||
previousDoc: data,
|
||||
previousSiblingDoc: { ...data },
|
||||
previousDoc,
|
||||
previousSiblingDoc: { ...nodePreviousSiblingDoc },
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData: originalData || {},
|
||||
siblingDoc: { ...data },
|
||||
siblingData: nodeSiblingData || {},
|
||||
siblingDoc: { ...nodeSiblingDoc },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -297,6 +321,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
flattenLocales,
|
||||
global,
|
||||
locale,
|
||||
originalDoc,
|
||||
overrideAccess,
|
||||
path,
|
||||
populate,
|
||||
@@ -363,7 +388,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
|
||||
if (subFieldFn && subFieldDataFn) {
|
||||
const subFields = subFieldFn({ node, req })
|
||||
const data = subFieldDataFn({ node, req }) ?? {}
|
||||
const nodeSliblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
afterReadTraverseFields({
|
||||
@@ -371,7 +396,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
context,
|
||||
currentDepth: currentDepth!,
|
||||
depth: depth!,
|
||||
doc: data,
|
||||
doc: originalDoc,
|
||||
draft: draft!,
|
||||
fallbackLocale: fallbackLocale!,
|
||||
fieldPromises: fieldPromises!,
|
||||
@@ -387,7 +412,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
req,
|
||||
schemaPath,
|
||||
showHiddenFields: showHiddenFields!,
|
||||
siblingDoc: data,
|
||||
siblingDoc: nodeSliblingData,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
@@ -403,12 +428,16 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
const {
|
||||
collection,
|
||||
context: _context,
|
||||
data,
|
||||
docWithLocales,
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc,
|
||||
path,
|
||||
previousValue,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData,
|
||||
@@ -447,7 +476,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
if (!originalNodeIDMap || !Object.keys(originalNodeIDMap).length || !value) {
|
||||
return value
|
||||
}
|
||||
|
||||
const previousNodeIDMap: {
|
||||
[key: string]: SerializedLexicalNode
|
||||
} = {}
|
||||
const originalNodeWithLocalesIDMap: {
|
||||
[key: string]: SerializedLexicalNode
|
||||
} = {}
|
||||
@@ -457,6 +488,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
nodes: (value as SerializedEditorState)?.root?.children ?? [],
|
||||
})
|
||||
|
||||
recurseNodeTree({
|
||||
nodeIDMap: previousNodeIDMap,
|
||||
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
|
||||
})
|
||||
if (field.name && siblingDocWithLocales?.[field.name]) {
|
||||
recurseNodeTree({
|
||||
nodeIDMap: originalNodeWithLocalesIDMap,
|
||||
@@ -493,6 +528,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
|
||||
parentRichTextFieldPath: path,
|
||||
parentRichTextFieldSchemaPath: schemaPath,
|
||||
previousNode: previousNodeIDMap[id],
|
||||
req,
|
||||
skipValidation: skipValidation!,
|
||||
})
|
||||
@@ -506,22 +542,23 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
|
||||
if (subFieldFn && subFieldDataFn) {
|
||||
const subFields = subFieldFn({ node, req })
|
||||
const data = subFieldDataFn({ node, req }) ?? {}
|
||||
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||
const originalDataWithLocales =
|
||||
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
const nodeSiblingDocWithLocales =
|
||||
subFieldDataFn({
|
||||
node: originalNodeWithLocalesIDMap[id],
|
||||
req,
|
||||
}) ?? {}
|
||||
const nodePreviousSiblingDoc =
|
||||
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
await beforeChangeTraverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc: originalData,
|
||||
docWithLocales: originalDataWithLocales ?? {},
|
||||
data: data ?? {},
|
||||
doc: originalDoc ?? {},
|
||||
docWithLocales: docWithLocales ?? {},
|
||||
errors: errors!,
|
||||
fields: subFields,
|
||||
global,
|
||||
@@ -530,9 +567,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
path,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData: data,
|
||||
siblingDoc: originalData,
|
||||
siblingDocWithLocales: originalDataWithLocales ?? {},
|
||||
siblingData: nodeSiblingData,
|
||||
siblingDoc: nodePreviousSiblingDoc,
|
||||
siblingDocWithLocales: nodeSiblingDocWithLocales ?? {},
|
||||
skipValidation,
|
||||
})
|
||||
}
|
||||
@@ -553,11 +590,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
[key: string]: SerializedLexicalNode
|
||||
} = {}
|
||||
|
||||
const previousValue = siblingData[field.name!]
|
||||
const previousOriginalValue = siblingData[field.name!]
|
||||
|
||||
recurseNodeTree({
|
||||
nodeIDMap: newOriginalNodeIDMap,
|
||||
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
|
||||
nodes: (previousOriginalValue as SerializedEditorState)?.root?.children ?? [],
|
||||
})
|
||||
|
||||
if (!context.internal) {
|
||||
@@ -579,8 +616,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
const {
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
global,
|
||||
operation,
|
||||
originalDoc,
|
||||
overrideAccess,
|
||||
path,
|
||||
previousValue,
|
||||
@@ -700,8 +739,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
|
||||
if (subFieldFn && subFieldDataFn) {
|
||||
const subFields = subFieldFn({ node, req })
|
||||
const data = subFieldDataFn({ node, req }) ?? {}
|
||||
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
await beforeValidateTraverseFields({
|
||||
@@ -709,7 +748,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc: originalData,
|
||||
doc: originalDoc,
|
||||
fields: subFields,
|
||||
global,
|
||||
operation,
|
||||
@@ -717,8 +756,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
path,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData: data,
|
||||
siblingDoc: originalData,
|
||||
siblingData: nodeSiblingData,
|
||||
siblingDoc: nodeSiblingDoc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user