perf(richtext-lexical)!: significantly reduce lexical rerendering and amount of network requests from blocks (#9255)
The field RSC now provides an initial state for all lexical blocks. This completely obliterates any flashes and lexical block loading states when loading or saving a document. Previously, when a document is loaded or saved, every lexical block was sending a network request in order to fetch their form state. Now, this is batched and handled in the lexical server component. All lexical block form states are sent to the client together with the parent lexical field, and are thus available immediately. We also do the same with block collapsed preferences. Thus, there are no loading states or layout shifts/flashes of blocks anymore. Additionally, when saving a document while your cursor is inside a lexical field, the cursor position is preserved. Previously, a document save would kick your cursor out of the lexical field. ## Look at how nice this is: https://github.com/user-attachments/assets/21d736d4-8f80-4df0-a782-7509edd993da **BREAKING:** This removes the `feature.hooks.load` and `feature.hooks.save` interfaces from custom lexical features, as they weren't used internally and added unnecessary, additional overhead. If you have custom features that use those, you can migrate to using normal payload hooks that run on the server instead of the client.
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
ensureCompilationIsDone,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
saveDocHotkeyAndAssert,
|
||||
throttleTest,
|
||||
} from '../../../../../helpers.js'
|
||||
import { AdminUrlUtil } from '../../../../../helpers/adminUrlUtil.js'
|
||||
@@ -201,6 +202,63 @@ describe('lexicalMain', () => {
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
})
|
||||
|
||||
test('ensure saving document does not kick cursor / focus out of rich text field', async () => {
|
||||
await navigateToLexicalFields()
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
await expect(richTextField).toBeVisible()
|
||||
// Wait until there at least 10 blocks visible in that richtext field - thus wait for it to be fully loaded
|
||||
await expect(richTextField.locator('.lexical-block')).toHaveCount(10)
|
||||
await expect(page.locator('.shimmer-effect')).toHaveCount(0)
|
||||
|
||||
const spanInEditor = richTextField.locator('span').getByText('Upload Node:').first()
|
||||
await expect(spanInEditor).toBeVisible()
|
||||
|
||||
await spanInEditor.click() // Click works better than focus
|
||||
// Now go to the END of the span
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await page.keyboard.press('ArrowRight')
|
||||
}
|
||||
|
||||
await page.keyboard.type('more')
|
||||
await expect(spanInEditor).toHaveText('Upload Node:more')
|
||||
|
||||
await wait(500)
|
||||
|
||||
await saveDocHotkeyAndAssert(page) // Use hotkey to save, as clicking the save button will obviously remove focus from the richtext field
|
||||
await wait(500)
|
||||
// Keep writing after save, assuming the cursor position is still at the end of the span
|
||||
await page.keyboard.type('text')
|
||||
await expect(spanInEditor).toHaveText('Upload Node:moretext')
|
||||
await wait(500)
|
||||
await saveDocAndAssert(page) // Use hotkey to save, as clicking the save button will obviously remove focus from the richtext field
|
||||
|
||||
await expect(async () => {
|
||||
const lexicalDoc: LexicalField = (
|
||||
await payload.find({
|
||||
collection: lexicalFieldsSlug,
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
where: {
|
||||
title: {
|
||||
equals: lexicalDocData.title,
|
||||
},
|
||||
},
|
||||
})
|
||||
).docs[0] as never
|
||||
|
||||
const lexicalField: SerializedEditorState = lexicalDoc.lexicalWithBlocks
|
||||
const firstParagraphTextNode: SerializedTextNode = (
|
||||
lexicalField.root.children[0] as SerializedParagraphNode
|
||||
).children[0] as SerializedTextNode
|
||||
|
||||
expect(firstParagraphTextNode.text).toBe('Upload Node:moretext')
|
||||
}).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
})
|
||||
|
||||
test('should be able to bold text using floating select toolbar', async () => {
|
||||
await navigateToLexicalFields()
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
|
||||
|
||||
Reference in New Issue
Block a user