diff --git a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx index 9df01e47f9..8cae0bdd1b 100644 --- a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx +++ b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx @@ -284,10 +284,22 @@ export const InlineBlockComponent: React.FC = (props) => { ) // cleanup effect useEffect(() => { + const isStateOutOfSync = (formData: InlineBlockFields, initialState: FormState) => { + return Object.keys(initialState).some( + (key) => initialState[key] && formData[key] !== initialState[key].value, + ) + } + return () => { + // If the component is unmounted (either via removeInlineBlock or via lexical itself) and the form state got changed before, + // we need to reset the initial state to force a re-fetch of the initial state when it gets mounted again (e.g. via lexical history undo). + // Otherwise it would use an outdated initial state. + if (initialState && isStateOutOfSync(formData, initialState)) { + setInitialState(false) + } abortAndIgnore(onChangeAbortControllerRef.current) } - }, []) + }, [formData, initialState]) /** * HANDLE FORM SUBMIT diff --git a/test/lexical/collections/Lexical/e2e/blocks/e2e.spec.ts b/test/lexical/collections/Lexical/e2e/blocks/e2e.spec.ts index b1c5ddc986..eac8657da0 100644 --- a/test/lexical/collections/Lexical/e2e/blocks/e2e.spec.ts +++ b/test/lexical/collections/Lexical/e2e/blocks/e2e.spec.ts @@ -1666,5 +1666,55 @@ describe('lexicalBlocks', () => { }, }) }) + + test('ensure inline blocks restore their state after undoing a removal', async () => { + await page.goto('http://localhost:3000/admin/collections/LexicalInBlock?limit=10') + + await page.locator('.cell-id a').first().click() + await page.waitForURL(`**/collections/LexicalInBlock/**`) + + // Wait for the page to be fully loaded and elements to be stable + await page.waitForLoadState('domcontentloaded') + + // Wait for the specific row to be visible and have its content loaded + const row2 = page.locator('#blocks-row-2') + await expect(row2).toBeVisible() + + // Get initial count and ensure it's stable + const inlineBlocks = page.locator('#blocks-row-2 .inline-block-container') + const inlineBlockCount = await inlineBlocks.count() + await expect(() => { + expect(inlineBlockCount).toBeGreaterThan(0) + }).toPass() + + const inlineBlockElement = inlineBlocks.first() + await inlineBlockElement.locator('.inline-block__editButton').first().click() + + await page.locator('.drawer--is-open #field-text').fill('value1') + await page.locator('.drawer--is-open button[type="submit"]').first().click() + + // remove inline block + await inlineBlockElement.click() + await page.keyboard.press('Backspace') + + // Check both that this specific element is removed and the total count decreased + await expect(inlineBlocks).toHaveCount(inlineBlockCount - 1) + + await page.keyboard.press('Escape') + + await inlineBlockElement.click() + + // Undo the removal using keyboard shortcut + await page.keyboard.press('ControlOrMeta+Z') + + // Wait for the block to be restored + await expect(inlineBlocks).toHaveCount(inlineBlockCount) + + // Open the drawer again + await inlineBlockElement.locator('.inline-block__editButton').first().click() + + // Check if the text field still contains 'value1' + await expect(page.locator('.drawer--is-open #field-text')).toHaveValue('value1') + }) }) })