Merge pull request #34487 from overleaf/mg-fix-nested-context-menu

Block native context menu on editor context menu and fix focus styling

GitOrigin-RevId: c2d59e7acc59019b5e2777cc1bf3ad24a7d8e54e
This commit is contained in:
Malik Glossop
2026-06-17 08:06:49 +00:00
committed by Copybot
parent 8ef463f8b0
commit bf22bf9eed
3 changed files with 78 additions and 36 deletions
@@ -49,42 +49,44 @@ const EditorContextMenuContent: FC = memo(function EditorContextMenuContent() {
return (
<Dropdown show onToggle={onToggle}>
<DropdownMenu
ref={menuRef}
show
tabIndex={0}
className="dropdown-menu-unpositioned"
onKeyDown={event => {
switch (event.code) {
case 'Escape':
case 'Tab':
event.preventDefault()
closeMenu()
break
}
}}
>
{menuItems.map((menuItem, index) => (
<Fragment key={index}>
{menuItem.separatorAbove && <DropdownDivider />}
<DropdownListItem>
<DropdownItem
as="button"
onClick={() => menuItem.handler()}
disabled={menuItem.disabled}
trailingIcon={
menuItem.shortcut ? (
<span>{menuItem.shortcut}</span>
) : undefined
}
>
{menuItem.label}
</DropdownItem>
</DropdownListItem>
</Fragment>
))}
<EditorContextMenuFeedback />
</DropdownMenu>
<div onContextMenu={event => event.preventDefault()}>
<DropdownMenu
ref={menuRef}
show
tabIndex={0}
className="dropdown-menu-unpositioned"
onKeyDown={event => {
switch (event.code) {
case 'Escape':
case 'Tab':
event.preventDefault()
closeMenu()
break
}
}}
>
{menuItems.map((menuItem, index) => (
<Fragment key={index}>
{menuItem.separatorAbove && <DropdownDivider />}
<DropdownListItem>
<DropdownItem
as="button"
onClick={() => menuItem.handler()}
disabled={menuItem.disabled}
trailingIcon={
menuItem.shortcut ? (
<span>{menuItem.shortcut}</span>
) : undefined
}
>
{menuItem.label}
</DropdownItem>
</DropdownListItem>
</Fragment>
))}
<EditorContextMenuFeedback />
</DropdownMenu>
</div>
</Dropdown>
)
})
@@ -133,6 +133,10 @@ $dropdown-item-min-height: 36px;
&:focus {
background: none;
// Bootstrap sets the hover text colour on plain :focus too; reset it so a
// mouse-focused (not :focus-visible), non-hovered item stays readable.
color: var(--dropdown-text-color);
}
&:focus-visible {
@@ -1288,4 +1288,40 @@ describe('editor context menu', { scrollBehavior: false }, function () {
cy.findByRole('menu').should('not.exist')
})
})
describe('when right-clicking inside the open menu', function () {
it('should suppress the native menu and stay open', function () {
const scope = mockScope()
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<CodeMirrorEditor />
</EditorProviders>
</TestContainer>
)
cy.get('.cm-line').eq(10).rightclick()
cy.findByRole('menu').should('be.visible')
cy.findByRole('menu').within(() => {
cy.findAllByRole('menuitem')
.first()
.then($item => {
const event = new MouseEvent('contextmenu', {
bubbles: true,
cancelable: true,
})
// dispatchEvent returns false when preventDefault was called
const notPrevented = $item[0].dispatchEvent(event)
expect(notPrevented, 'native context menu prevented').to.equal(
false
)
})
})
// The second contextmenu event keeps the custom menu open
cy.findByRole('menu').should('be.visible')
})
})
})