Skip to content

Commit 7bbd07c

Browse files
authored
fix(ui): opening relationship field with appearance: "drawer" inside rich text inline block closes drawer (#13830)
Previously, clicking on a relationship with `appearance: 'drawer'` within a lexical inline block drawer would close both drawers, instead of opening a new drawer to select the relationship document. Fixes #13778 ## Before https://github.com/user-attachments/assets/371619d2-c64b-4e12-b8f3-72ad599db5a9 ## After https://github.com/user-attachments/assets/a05b9338-3b1d-4b0c-b78c-8e6b3b57014c ## Technical Notes The issue happened due to the [`ModalContainer`'s onClick](https://github.com/faceless-ui/modal/blob/main/src/ModalContainer/index.tsx#L43) function being triggered when mouseUp on the relationship field is triggered. **Causes issue**: MouseDown => drawer opens => mouseUp **Does not cause issue**: MouseDown => MouseUp => drawer opens This is why the previous `setTimeout()` fix worked: it delayed the drawer opening until the mouseUp event occured. If you click very slow, the issue could still happen though. I was not able to figure out _why_ the `onClick` of the ModalContainer is triggered. ## The Fix This is the ModalProvider `onClick` handler: ```ts (e: MouseEvent<HTMLElement>) => { if (closeOnBlur) closeAllModals(); if (typeof onClick === 'function') onClick(e); }, ``` The fix is to simply set `closeOnBlur` to `false`, so that `closeAllModals` is no longer called. I was not able to manually trigger the onClick event of the `ModalProvider`. I figured that it is more often called by strange React Components triggering events like maniacs (react-select) rather than some genuine user action. In case some piece of functionality somehow relied on this event being triggered and then closing the modal, that piece of functionality could manually call `closeModal()` or attach a custom `onClick` function to the modal. That way, this mechanism will be run in a more deliberate, expected way. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211375615406672
1 parent dea91f3 commit 7bbd07c

9 files changed

Lines changed: 58 additions & 8 deletions

File tree

packages/ui/src/elements/ConfirmationModal/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export function ConfirmationModal(props: ConfirmationModalProps) {
6969
return (
7070
<Modal
7171
className={[baseClass, className].filter(Boolean).join(' ')}
72+
// Fixes https://github.com/payloadcms/payload/issues/13778
73+
closeOnBlur={false}
7274
slug={modalSlug}
7375
style={{
7476
zIndex: drawerZBase + editDepth,

packages/ui/src/elements/DocumentLocked/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const DocumentLocked: React.FC<{
5353
return (
5454
<Modal
5555
className={baseClass}
56+
// Fixes https://github.com/payloadcms/payload/issues/13778
57+
closeOnBlur={false}
5658
onClose={() => {
5759
startRouteTransition(() => handleGoBack())
5860
}}

packages/ui/src/elements/DocumentTakeOver/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export const DocumentTakeOver: React.FC<{
3131
}, [isActive, openModal, closeModal])
3232

3333
return (
34-
<Modal className={baseClass} slug={modalSlug}>
34+
<Modal
35+
className={baseClass}
36+
// // Fixes https://github.com/payloadcms/payload/issues/13778
37+
closeOnBlur={false}
38+
slug={modalSlug}
39+
>
3540
<div className={`${baseClass}__wrapper`}>
3641
<div className={`${baseClass}__content`}>
3742
<h1>{t('general:editingTakenOver')}</h1>

packages/ui/src/elements/Drawer/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ export const Drawer: React.FC<Props> = ({
7979
]
8080
.filter(Boolean)
8181
.join(' ')}
82+
// Fixes https://github.com/payloadcms/payload/issues/13778
83+
closeOnBlur={false}
8284
slug={slug}
8385
style={{
8486
zIndex: drawerZBase + drawerDepth,

packages/ui/src/elements/FullscreenModal/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export function FullscreenModal(props: Parameters<typeof ModalType>[0]) {
1111

1212
return (
1313
<Modal
14+
// Fixes https://github.com/payloadcms/payload/issues/13778
15+
closeOnBlur={false}
1416
{...props}
1517
style={{
1618
...(props.style || {}),

packages/ui/src/fields/Relationship/Input.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -788,11 +788,7 @@ export const RelationshipInput: React.FC<RelationshipInputProps> = (props) => {
788788
}}
789789
onMenuOpen={() => {
790790
if (appearance === 'drawer') {
791-
// TODO: This timeout is only necessary for inline blocks in the lexical editor
792-
// and when the devtools are closed. Temporary solution, we can probably do better.
793-
setTimeout(() => {
794-
openListDrawer()
795-
}, 100)
791+
openListDrawer()
796792
} else if (appearance === 'select') {
797793
setMenuIsOpen(true)
798794
if (!hasLoadedFirstPageRef.current) {

test/lexical/collections/_LexicalFullyFeatured/e2e.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('Lexical Fully Featured', () => {
4545
test('prevent extra paragraph when inserting decorator blocks like blocks or upload node', async () => {
4646
await lexical.slashCommand('block')
4747
await expect(lexical.editor.locator('.lexical-block')).toBeVisible()
48-
await lexical.slashCommand('relationship')
48+
await lexical.slashCommand('relationship', true, 'Relationship')
4949
await lexical.drawer.locator('.list-drawer__header').getByText('Create New').click()
5050
await lexical.save('drawer')
5151
await expect(lexical.decorator).toHaveCount(2)
@@ -129,4 +129,26 @@ describe('Lexical Fully Featured', () => {
129129
const someButton = dropdownItems!.locator(`[data-item-key="bg-red"]`)
130130
await expect(someButton).toHaveAttribute('aria-disabled', 'false')
131131
})
132+
133+
test('ensure opening relationship field with appearance: "drawer" inside rich text inline block does not close drawer', async ({
134+
page,
135+
}) => {
136+
// https://github.com/payloadcms/payload/pull/13830
137+
138+
await lexical.slashCommand('inlineblockwithrelationship')
139+
140+
await expect(lexical.drawer).toBeVisible()
141+
142+
await lexical.drawer.locator('.react-select').click()
143+
// At this point, the drawer would close if the issue is not fixed
144+
145+
await page.getByText('Seeded text document').click()
146+
147+
await expect(
148+
lexical.drawer.locator('.react-select .relationship--single-value__text'),
149+
).toHaveText('Seeded text document')
150+
151+
await lexical.drawer.getByText('Save changes').click()
152+
await expect(lexical.drawer).toBeHidden()
153+
})
132154
})

test/lexical/collections/_LexicalFullyFeatured/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ export const LexicalFullyFeatured: CollectionConfig = {
5757
},
5858
],
5959
},
60+
{
61+
slug: 'inlineBlockWithRelationship',
62+
fields: [
63+
{
64+
name: 'relationship',
65+
type: 'relationship',
66+
relationTo: 'text-fields',
67+
admin: {
68+
// Required to reproduce issue: https://github.com/payloadcms/payload/issues/13778
69+
appearance: 'drawer',
70+
},
71+
},
72+
],
73+
},
6074
],
6175
}),
6276
],

test/lexical/collections/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,19 @@ export class LexicalHelpers {
191191
command: ('block' | 'check' | 'code' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' |'h6' | 'inline'
192192
| 'link' | 'ordered' | 'paragraph' | 'quote' | 'relationship' | 'table' | 'unordered'|'upload') | ({} & string),
193193
expectMenuToClose = true,
194+
labelToMatch?: string,
194195
) {
195196
await this.page.keyboard.press(`/`)
196197

197198
const slashMenuPopover = this.page.locator('#slash-menu .slash-menu-popup')
198199
await expect(slashMenuPopover).toBeVisible()
199200
await this.page.keyboard.type(command)
200201
await wait(200)
201-
await this.page.keyboard.press(`Enter`)
202+
if (labelToMatch) {
203+
await slashMenuPopover.getByText(labelToMatch).click()
204+
} else {
205+
await this.page.keyboard.press(`Enter`)
206+
}
202207
if (expectMenuToClose) {
203208
await expect(slashMenuPopover).toBeHidden()
204209
}

0 commit comments

Comments
 (0)