feat(richtext-lexical): make decoratorNodes and blocks selectable. Centralize selection and deletion logic (#10735)
- Blocks can now be selected (only inline blocks were possible before). - Any DecoratorNode that users create will have the necessary logic out of the box so that they are selected with a click and deleted with backspace/delete. - By having the code for selecting and deleting centralized, a lot of repetitive code was eliminated - More performant code due to the use of event delegation. There is only one listener, previously there was one for each decoratorNode. - Heuristics to exclude scenarios where you don't want to select the node: if it is inside the DecoratorNode, but is also inside a button, input, textarea, contentEditable, .react-select, .code-editor or .no-select-decorator. That last one was added as a means of opt-out. - Fix #10634 Note: arrow navigation will be introduced in a later PR. https://github.com/user-attachments/assets/92f91cad-4f70-4f72-a36f-c68afbe33c0d
This commit is contained in:
@@ -4,7 +4,7 @@ import type {
|
||||
SerializedParagraphNode,
|
||||
SerializedTextNode,
|
||||
} from '@payloadcms/richtext-lexical/lexical'
|
||||
import type { BrowserContext, Page } from '@playwright/test'
|
||||
import type { BrowserContext, Locator, Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
@@ -28,6 +28,7 @@ import { RESTClient } from '../../../../../helpers/rest.js'
|
||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../../../playwright.config.js'
|
||||
import { lexicalFieldsSlug } from '../../../../slugs.js'
|
||||
import { lexicalDocData } from '../../data.js'
|
||||
import { except } from 'drizzle-orm/mysql-core'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const currentFolder = path.dirname(filename)
|
||||
@@ -1294,4 +1295,59 @@ describe('lexicalMain', () => {
|
||||
await navigateToLexicalFields(true, true)
|
||||
})
|
||||
})
|
||||
|
||||
test('select decoratorNodes', async () => {
|
||||
// utils
|
||||
const decoratorLocator = page.locator('.decorator-selected') // [data-lexical-decorator="true"]
|
||||
const expectInsideSelectedDecorator = async (innerLocator: Locator) => {
|
||||
await expect(decoratorLocator).toBeVisible()
|
||||
await expect(decoratorLocator.locator(innerLocator)).toBeVisible()
|
||||
}
|
||||
|
||||
// test
|
||||
await navigateToLexicalFields()
|
||||
const bottomOfUploadNode = page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^payload\.jpg$/ })
|
||||
.first()
|
||||
await bottomOfUploadNode.click()
|
||||
await expectInsideSelectedDecorator(bottomOfUploadNode)
|
||||
|
||||
const textNode = page.getByText('Upload Node:', { exact: true })
|
||||
await textNode.click()
|
||||
await expect(decoratorLocator).not.toBeVisible()
|
||||
|
||||
const closeTagInMultiSelect = page
|
||||
.getByRole('button', { name: 'payload.jpg Edit payload.jpg' })
|
||||
.getByLabel('Remove')
|
||||
await closeTagInMultiSelect.click()
|
||||
await expect(decoratorLocator).not.toBeVisible()
|
||||
|
||||
const labelInsideCollapsableBody = page.locator('label').getByText('Sub Blocks')
|
||||
await labelInsideCollapsableBody.click()
|
||||
await expectInsideSelectedDecorator(labelInsideCollapsableBody)
|
||||
|
||||
const textNodeInNestedEditor = page.getByText('Some text below relationship node 1')
|
||||
await textNodeInNestedEditor.click()
|
||||
await expect(decoratorLocator).not.toBeVisible()
|
||||
|
||||
await page.getByRole('button', { name: 'Tab2' }).click()
|
||||
await expect(decoratorLocator).not.toBeVisible()
|
||||
|
||||
const labelInsideCollapsableBody2 = page.getByText('Text2')
|
||||
await labelInsideCollapsableBody2.click()
|
||||
await expectInsideSelectedDecorator(labelInsideCollapsableBody2)
|
||||
|
||||
// TEST DELETE!
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(labelInsideCollapsableBody2).not.toBeVisible()
|
||||
|
||||
const monacoLabel = page.locator('label').getByText('Code')
|
||||
await monacoLabel.click()
|
||||
await expectInsideSelectedDecorator(monacoLabel)
|
||||
|
||||
const monacoCode = page.getByText('Some code')
|
||||
await monacoCode.click()
|
||||
await expect(decoratorLocator).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user