Skip to content

Commit bd08e1f

Browse files
authored
fix: Right clicking with a custom context menu open should open another context menu (#1526)
Fixes #1525
1 parent 9d905fc commit bd08e1f

2 files changed

Lines changed: 51 additions & 33 deletions

File tree

packages/components/src/context-actions/ContextMenuRoot.tsx

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,44 @@ class ContextMenuRoot extends Component<
5858
openMenu: React.RefObject<ContextMenu>;
5959

6060
handleContextMenu(e: MouseEvent): void {
61-
if (!ContextActionUtils.isContextActionEvent(e)) {
61+
if (!this.container.current) {
6262
return;
6363
}
6464

65-
if (!this.container.current) {
65+
const parentRect = this.container.current.getBoundingClientRect();
66+
const top = e.clientY - parentRect.top;
67+
const left = e.clientX - parentRect.left;
68+
const { actions } = this.state;
69+
70+
// Context menu is open and user clicked on the context-root blocking layer
71+
// Mac and Linux appear to trigger contextmenu events on mousedown vs. mouseup on Windows
72+
// Mouseup on Windows triggers blur before contextmenu which effectively does what this path does
73+
if (actions != null && e.target === this.container.current) {
74+
// re-emit right clicks that hit the context-root blocking layer
75+
// while we already have a custom context menu open
76+
e.preventDefault();
77+
78+
// Set actions to null removes the menu
79+
// That allows a new menu to be opened on a different element so initial position is set properly
80+
// Otherwise the instance of this menu may be reused
81+
// A new contextmenu event is triggered on the element at the location the user clicked on the blocking layer
82+
this.setState({ actions: null }, () => {
83+
const element = document.elementFromPoint(left, top); // x y
84+
85+
const mouseEvent = new MouseEvent('contextmenu', {
86+
clientX: e.clientX,
87+
clientY: e.clientY,
88+
bubbles: true,
89+
cancelable: true,
90+
});
91+
92+
element?.dispatchEvent(mouseEvent);
93+
});
94+
return;
95+
}
96+
97+
if (!ContextActionUtils.isContextActionEvent(e)) {
98+
// Open native menu if no custom context actions
6699
return;
67100
}
68101

@@ -73,38 +106,8 @@ class ContextMenuRoot extends Component<
73106

74107
const contextActions = ContextActionUtils.getMenuItems(e.contextActions);
75108

76-
const parentRect = this.container.current.getBoundingClientRect();
77-
const top = e.clientY - parentRect.top;
78-
const left = e.clientX - parentRect.left;
79-
80109
if (contextActions.length === 0) {
81-
// This code path seems to only exist for Chrome on Mac
82-
// Mac appears to trigger contextmenu events on mousedown vs. mouseup on Windows
83-
// Mouseup on Windows triggers blur before contextmenu which effectively does what this path does
84-
if (e.target === this.container.current) {
85-
// re-emit right clicks that hit the context-root blocking layer
86-
e.preventDefault();
87-
88-
// Set actions to null removes the menu
89-
// That allows a new menu to be opened on a different element so initial position is set properly
90-
// Otherwise the instance of this menu may be reused
91-
// A new contextmenu event is triggered on the element at the location the user clicked on the blocking layer
92-
this.setState({ actions: null }, () => {
93-
const element = document.elementFromPoint(left, top); // x y
94-
95-
const mouseEvent = new MouseEvent('contextmenu', {
96-
clientX: e.clientX,
97-
clientY: e.clientY,
98-
bubbles: true,
99-
cancelable: true,
100-
});
101-
102-
element?.dispatchEvent(mouseEvent);
103-
});
104-
return;
105-
}
106-
107-
// target was a menu item
110+
// No actions after filtering. Use native menu
108111
return;
109112
}
110113

tests/context-menu.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test('open custom context menu with another custom context menu open', async ({
4+
page,
5+
}) => {
6+
await page.goto('');
7+
8+
await page.getByText('Console').click({ button: 'right' });
9+
await expect(page.getByText('Close', { exact: true })).toHaveCount(1);
10+
11+
await page
12+
.getByText('Command History')
13+
.click({ button: 'right', force: true });
14+
await expect(page.getByText('Close', { exact: true })).toHaveCount(1);
15+
});

0 commit comments

Comments
 (0)