Skip to content

Commit

Permalink
fix(richtext-lexical): richtext fields in drawers aren't editable, in…
Browse files Browse the repository at this point in the history
…line toolbar artifacts are shown for readOnly editors (#8774)

Fixes this:


https://github.com/user-attachments/assets/cf78082d-9054-4324-90cd-c81219a4f26d
  • Loading branch information
AlessioGr authored Oct 18, 2024
1 parent fa49215 commit f3bec93
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,22 +295,18 @@ function InlineToolbar({
return (
<div className="inline-toolbar-popup" ref={floatingToolbarRef}>
<div className="caret" ref={caretRef} />
{editor.isEditable() && (
<React.Fragment>
{editorConfig?.features &&
editorConfig.features?.toolbarInline?.groups.map((group, i) => {
return (
<ToolbarGroupComponent
anchorElem={anchorElem}
editor={editor}
group={group}
index={i}
key={group.key}
/>
)
})}
</React.Fragment>
)}
{editorConfig?.features &&
editorConfig.features?.toolbarInline?.groups.map((group, i) => {
return (
<ToolbarGroupComponent
anchorElem={anchorElem}
editor={editor}
group={group}
index={i}
key={group.key}
/>
)
})}
</div>
)
}
Expand Down Expand Up @@ -392,7 +388,7 @@ function useInlineToolbar(
)
}, [editor, updatePopup])

if (!isText) {
if (!isText || !editor.isEditable()) {
return null
}

Expand Down
9 changes: 7 additions & 2 deletions packages/richtext-lexical/src/field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
FieldDescription,
FieldError,
FieldLabel,
useEditDepth,
useField,
useFieldProps,
withCondition,
Expand Down Expand Up @@ -47,6 +48,8 @@ const RichTextComponent: React.FC<
const Label = components?.Label
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin

const editDepth = useEditDepth()

const memoizedValidate = useCallback(
(value, validationOptions) => {
if (typeof validate === 'function') {
Expand Down Expand Up @@ -82,10 +85,12 @@ const RichTextComponent: React.FC<
.filter(Boolean)
.join(' ')

const pathWithEditDepth = `${path}.${editDepth}`

return (
<div
className={classes}
key={path}
key={pathWithEditDepth}
style={{
...style,
width,
Expand All @@ -102,6 +107,7 @@ const RichTextComponent: React.FC<
<div className={`${baseClass}__wrap`}>
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
<LexicalProvider
composerKey={pathWithEditDepth}
editorConfig={editorConfig}
field={field}
key={JSON.stringify({ initialValue, path })} // makes sure lexical is completely re-rendered when initialValue changes, bypassing the lexical-internal value memoization. That way, external changes to the form will update the editor. More infos in PR description (https://github.com/payloadcms/payload/pull/5010)
Expand All @@ -117,7 +123,6 @@ const RichTextComponent: React.FC<

setValue(serializedEditorState)
}}
path={path}
readOnly={disabled}
value={value}
/>
Expand Down
10 changes: 6 additions & 4 deletions packages/richtext-lexical/src/lexical/LexicalProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import { LexicalEditor as LexicalEditorComponent } from './LexicalEditor.js'
import { getEnabledNodes } from './nodes/index.js'

export type LexicalProviderProps = {
composerKey: string
editorConfig: SanitizedClientEditorConfig
field: LexicalRichTextFieldProps['field']
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void
path: string
readOnly: boolean
value: SerializedEditorState
}
Expand All @@ -41,7 +41,7 @@ const NestProviders = ({ children, providers }) => {
}

export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
const { editorConfig, field, onChange, path, readOnly, value } = props
const { composerKey, editorConfig, field, onChange, readOnly, value } = props

const parentContext = useEditorConfigContext()

Expand Down Expand Up @@ -82,7 +82,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
editable: readOnly !== true,
editorState: processedValue != null ? JSON.stringify(processedValue) : undefined,
namespace: editorConfig.lexical.namespace,
nodes: [...getEnabledNodes({ editorConfig })],
nodes: getEnabledNodes({ editorConfig }),
onError: (error: Error) => {
throw error
},
Expand All @@ -94,8 +94,10 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
return <p>Loading...</p>
}

// We need to add initialConfig.editable to the key to force a re-render when the readOnly prop changes.
// Without it, there were cases where lexical editors inside drawers turn readOnly initially - a few miliseconds later they turn editable, but the editor does not re-render and stays readOnly.
return (
<LexicalComposer initialConfig={initialConfig} key={path}>
<LexicalComposer initialConfig={initialConfig} key={composerKey + initialConfig.editable}>
<EditorConfigProvider
editorConfig={editorConfig}
editorContainerRef={editorContainerRef}
Expand Down
2 changes: 2 additions & 0 deletions test/buildConfigWithDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
HeadingFeature,
IndentFeature,
InlineCodeFeature,
InlineToolbarFeature,
ItalicFeature,
lexicalEditor,
LinkFeature,
Expand Down Expand Up @@ -84,6 +85,7 @@ export async function buildConfigWithDefaults(
SubscriptFeature(),
SuperscriptFeature(),
InlineCodeFeature(),
InlineToolbarFeature(),
TreeViewFeature(),
HeadingFeature(),
IndentFeature(),
Expand Down
26 changes: 13 additions & 13 deletions test/fields/collections/Lexical/e2e/blocks/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('lexicalBlocks', () => {
describe('nested lexical editor in block', () => {
test('should type and save typed text', async () => {
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -157,7 +157,7 @@ describe('lexicalBlocks', () => {
test('should be able to bold text using floating select toolbar', async () => {
// Reproduces https://github.com/payloadcms/payload/issues/4025
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -239,7 +239,7 @@ describe('lexicalBlocks', () => {
test('should be able to select text, make it an external link and receive the updated link value', async () => {
// Reproduces https://github.com/payloadcms/payload/issues/4025
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -323,7 +323,7 @@ describe('lexicalBlocks', () => {
test('ensure slash menu is not hidden behind other blocks', async () => {
// This test makes sure there are no z-index issues here
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -396,7 +396,7 @@ describe('lexicalBlocks', () => {
})
test('should allow adding new blocks to a sub-blocks field, part of a parent lexical blocks field', async () => {
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -471,7 +471,7 @@ describe('lexicalBlocks', () => {
// Big test which tests a bunch of things: Creation of blocks via slash commands, creation of deeply nested sub-lexical-block fields via slash commands, properly populated deeply nested fields within those
test('ensure creation of a lexical, lexical-field-block, which contains another lexical, lexical-and-upload-field-block, works and that the sub-upload field is properly populated', async () => {
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -690,7 +690,7 @@ describe('lexicalBlocks', () => {
// This test ensures that https://github.com/payloadcms/payload/issues/3911 does not happen again

await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -762,7 +762,7 @@ describe('lexicalBlocks', () => {
// 3. In the issue, after writing one character, the cursor focuses back into the parent editor

await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -802,7 +802,7 @@ describe('lexicalBlocks', () => {
})

const shouldRespectRowRemovalTest = async () => {
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand Down Expand Up @@ -859,7 +859,7 @@ describe('lexicalBlocks', () => {
await navigateToLexicalFields()

// Wait for lexical to be loaded up fully
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand All @@ -882,7 +882,7 @@ describe('lexicalBlocks', () => {
test('ensure pre-seeded uploads node is visible', async () => {
// Due to issues with the relationships condition, we had issues with that not being visible. Checking for visibility ensures there is no breakage there again
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second
await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()

Expand All @@ -897,7 +897,7 @@ describe('lexicalBlocks', () => {

test('should respect required error state in deeply nested text field', async () => {
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second

await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()
Expand Down Expand Up @@ -946,7 +946,7 @@ describe('lexicalBlocks', () => {
// Reproduces https://github.com/payloadcms/payload/issues/6631
test('ensure tabs field within lexical block correctly loads and saves data', async () => {
await navigateToLexicalFields()
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
const richTextField = page.locator('.rich-text-lexical').nth(2) // second

await richTextField.scrollIntoViewIfNeeded()
await expect(richTextField).toBeVisible()
Expand Down
Loading

0 comments on commit f3bec93

Please sign in to comment.