Skip to content

Commit 50823be

Browse files
authored
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.
1 parent f5aad49 commit 50823be

File tree

2 files changed

+71
-29
lines changed

2 files changed

+71
-29
lines changed

packages/richtext-lexical/src/features/typesServer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export type AfterChangeNodeHookArgs<T extends SerializedLexicalNode> = {
176176
operation: 'create' | 'delete' | 'read' | 'update'
177177
/** The value of the node before any changes. Not available in afterRead hooks */
178178
originalNode: T
179+
previousNode: T
179180
}
180181
export type BeforeValidateNodeHookArgs<T extends SerializedLexicalNode> = {
181182
/** 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> = {
199200
* The original node with locales (not modified by any hooks).
200201
*/
201202
originalNodeWithLocales?: T
203+
previousNode: T
204+
202205
skipValidation: boolean
203206
}
204207

packages/richtext-lexical/src/index.ts

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,19 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
183183
hooks: {
184184
afterChange: [
185185
async (args) => {
186-
const { collection, context: _context, global, operation, path, req, schemaPath } = args
186+
const {
187+
collection,
188+
context: _context,
189+
data,
190+
global,
191+
operation,
192+
originalDoc,
193+
path,
194+
previousDoc,
195+
previousValue,
196+
req,
197+
schemaPath,
198+
} = args
187199
let { value } = args
188200
if (finalSanitizedEditorConfig?.features?.hooks?.afterChange?.length) {
189201
for (const hook of finalSanitizedEditorConfig.features.hooks.afterChange) {
@@ -203,6 +215,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
203215
[key: string]: SerializedLexicalNode
204216
} = {}
205217

218+
const previousNodeIDMap: {
219+
[key: string]: SerializedLexicalNode
220+
} = {}
221+
206222
/**
207223
* Get the originalNodeIDMap from the beforeValidate hook, which is always run before this hook.
208224
*/
@@ -219,6 +235,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
219235
nodes: (value as SerializedEditorState)?.root?.children ?? [],
220236
})
221237

238+
recurseNodeTree({
239+
nodeIDMap: previousNodeIDMap,
240+
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
241+
})
242+
222243
// eslint-disable-next-line prefer-const
223244
for (let [id, node] of Object.entries(nodeIDMap)) {
224245
const afterChangeHooks = finalSanitizedEditorConfig.features.nodeHooks?.afterChange
@@ -243,6 +264,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
243264
originalNode: originalNodeIDMap[id],
244265
parentRichTextFieldPath: path,
245266
parentRichTextFieldSchemaPath: schemaPath,
267+
previousNode: previousNodeIDMap[id],
246268
req,
247269
})
248270
}
@@ -254,25 +276,27 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
254276

255277
if (subFieldFn && subFieldDataFn) {
256278
const subFields = subFieldFn({ node, req })
257-
const data = subFieldDataFn({ node, req }) ?? {}
258-
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
279+
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
280+
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
281+
const nodePreviousSiblingDoc =
282+
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
259283

260284
if (subFields?.length) {
261285
await afterChangeTraverseFields({
262286
collection,
263287
context,
264-
data: originalData,
265-
doc: data,
288+
data: data ?? {},
289+
doc: originalDoc,
266290
fields: subFields,
267291
global,
268292
operation,
269293
path,
270-
previousDoc: data,
271-
previousSiblingDoc: { ...data },
294+
previousDoc,
295+
previousSiblingDoc: { ...nodePreviousSiblingDoc },
272296
req,
273297
schemaPath,
274-
siblingData: originalData || {},
275-
siblingDoc: { ...data },
298+
siblingData: nodeSiblingData || {},
299+
siblingDoc: { ...nodeSiblingDoc },
276300
})
277301
}
278302
}
@@ -297,6 +321,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
297321
flattenLocales,
298322
global,
299323
locale,
324+
originalDoc,
300325
overrideAccess,
301326
path,
302327
populate,
@@ -363,15 +388,15 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
363388

364389
if (subFieldFn && subFieldDataFn) {
365390
const subFields = subFieldFn({ node, req })
366-
const data = subFieldDataFn({ node, req }) ?? {}
391+
const nodeSliblingData = subFieldDataFn({ node, req }) ?? {}
367392

368393
if (subFields?.length) {
369394
afterReadTraverseFields({
370395
collection,
371396
context,
372397
currentDepth: currentDepth!,
373398
depth: depth!,
374-
doc: data,
399+
doc: originalDoc,
375400
draft: draft!,
376401
fallbackLocale: fallbackLocale!,
377402
fieldPromises: fieldPromises!,
@@ -387,7 +412,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
387412
req,
388413
schemaPath,
389414
showHiddenFields: showHiddenFields!,
390-
siblingDoc: data,
415+
siblingDoc: nodeSliblingData,
391416
triggerAccessControl,
392417
triggerHooks,
393418
})
@@ -403,12 +428,16 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
403428
const {
404429
collection,
405430
context: _context,
431+
data,
432+
docWithLocales,
406433
errors,
407434
field,
408435
global,
409436
mergeLocaleActions,
410437
operation,
438+
originalDoc,
411439
path,
440+
previousValue,
412441
req,
413442
schemaPath,
414443
siblingData,
@@ -447,7 +476,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
447476
if (!originalNodeIDMap || !Object.keys(originalNodeIDMap).length || !value) {
448477
return value
449478
}
450-
479+
const previousNodeIDMap: {
480+
[key: string]: SerializedLexicalNode
481+
} = {}
451482
const originalNodeWithLocalesIDMap: {
452483
[key: string]: SerializedLexicalNode
453484
} = {}
@@ -457,6 +488,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
457488
nodes: (value as SerializedEditorState)?.root?.children ?? [],
458489
})
459490

491+
recurseNodeTree({
492+
nodeIDMap: previousNodeIDMap,
493+
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
494+
})
460495
if (field.name && siblingDocWithLocales?.[field.name]) {
461496
recurseNodeTree({
462497
nodeIDMap: originalNodeWithLocalesIDMap,
@@ -493,6 +528,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
493528
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
494529
parentRichTextFieldPath: path,
495530
parentRichTextFieldSchemaPath: schemaPath,
531+
previousNode: previousNodeIDMap[id],
496532
req,
497533
skipValidation: skipValidation!,
498534
})
@@ -506,22 +542,23 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
506542

507543
if (subFieldFn && subFieldDataFn) {
508544
const subFields = subFieldFn({ node, req })
509-
const data = subFieldDataFn({ node, req }) ?? {}
510-
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
511-
const originalDataWithLocales =
545+
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
546+
const nodeSiblingDocWithLocales =
512547
subFieldDataFn({
513548
node: originalNodeWithLocalesIDMap[id],
514549
req,
515550
}) ?? {}
551+
const nodePreviousSiblingDoc =
552+
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
516553

517554
if (subFields?.length) {
518555
await beforeChangeTraverseFields({
519556
id,
520557
collection,
521558
context,
522-
data,
523-
doc: originalData,
524-
docWithLocales: originalDataWithLocales ?? {},
559+
data: data ?? {},
560+
doc: originalDoc ?? {},
561+
docWithLocales: docWithLocales ?? {},
525562
errors: errors!,
526563
fields: subFields,
527564
global,
@@ -530,9 +567,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
530567
path,
531568
req,
532569
schemaPath,
533-
siblingData: data,
534-
siblingDoc: originalData,
535-
siblingDocWithLocales: originalDataWithLocales ?? {},
570+
siblingData: nodeSiblingData,
571+
siblingDoc: nodePreviousSiblingDoc,
572+
siblingDocWithLocales: nodeSiblingDocWithLocales ?? {},
536573
skipValidation,
537574
})
538575
}
@@ -553,11 +590,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
553590
[key: string]: SerializedLexicalNode
554591
} = {}
555592

556-
const previousValue = siblingData[field.name!]
593+
const previousOriginalValue = siblingData[field.name!]
557594

558595
recurseNodeTree({
559596
nodeIDMap: newOriginalNodeIDMap,
560-
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
597+
nodes: (previousOriginalValue as SerializedEditorState)?.root?.children ?? [],
561598
})
562599

563600
if (!context.internal) {
@@ -579,8 +616,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
579616
const {
580617
collection,
581618
context,
619+
data,
582620
global,
583621
operation,
622+
originalDoc,
584623
overrideAccess,
585624
path,
586625
previousValue,
@@ -700,25 +739,25 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
700739

701740
if (subFieldFn && subFieldDataFn) {
702741
const subFields = subFieldFn({ node, req })
703-
const data = subFieldDataFn({ node, req }) ?? {}
704-
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
742+
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
743+
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
705744

706745
if (subFields?.length) {
707746
await beforeValidateTraverseFields({
708747
id,
709748
collection,
710749
context,
711750
data,
712-
doc: originalData,
751+
doc: originalDoc,
713752
fields: subFields,
714753
global,
715754
operation,
716755
overrideAccess: overrideAccess!,
717756
path,
718757
req,
719758
schemaPath,
720-
siblingData: data,
721-
siblingDoc: originalData,
759+
siblingData: nodeSiblingData,
760+
siblingDoc: nodeSiblingDoc,
722761
})
723762
}
724763
}

0 commit comments

Comments
 (0)