Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions packages/@mantine/core/src/components/Menu/MenuItem/MenuItem.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { act } from '@testing-library/react';
import { createContextContainer, render, screen, tests, userEvent } from '@mantine-tests/core';
import { Menu } from '../Menu';
import { MenuItem, MenuItemProps, MenuItemStylesNames } from './MenuItem';
Expand Down Expand Up @@ -57,4 +58,124 @@ describe('@mantine/core/MenuItem', () => {
expect(screen.getByText('test-left-section')).toBeInTheDocument();
expect(screen.getByText('test-right-section')).toBeInTheDocument();
});

it('should not prevent default mousedown behavior for draggable elements inside Menu.Item', () => {
const onDragStart = jest.fn();
const onMouseDown = jest.fn();

render(
<Menu opened closeOnItemClick={false} withInitialFocusPlaceholder={false}>
<Menu.Target>
<button type="button">Target</button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item>
<div
data-testid="draggable-element"
draggable
onDragStart={onDragStart}
onMouseDown={onMouseDown}
role="button"
tabIndex={0}
>
Draggable content
</div>
</Menu.Item>
</Menu.Dropdown>
</Menu>
);

const draggableElement = screen.getByTestId('draggable-element');

const mouseDownEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
button: 0, // left mouse button
});

const preventDefaultSpy = jest.spyOn(mouseDownEvent, 'preventDefault');

act(() => {
draggableElement.dispatchEvent(mouseDownEvent);
});

expect(onMouseDown).toHaveBeenCalled();

expect(preventDefaultSpy).not.toHaveBeenCalled();
});

it('should still prevent default mousedown behavior for non-draggable elements', () => {
const onMouseDown = jest.fn();

render(
<Menu opened closeOnItemClick={false} withInitialFocusPlaceholder={false}>
<Menu.Target>
<button type="button">Target</button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item>
<div data-testid="regular-element" onMouseDown={onMouseDown} role="button" tabIndex={0}>
Regular content
</div>
</Menu.Item>
</Menu.Dropdown>
</Menu>
);

const regularElement = screen.getByTestId('regular-element');

const mouseDownEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
button: 0,
});

const preventDefaultSpy = jest.spyOn(mouseDownEvent, 'preventDefault');

act(() => {
regularElement.dispatchEvent(mouseDownEvent);
});

expect(onMouseDown).toHaveBeenCalled();

expect(preventDefaultSpy).toHaveBeenCalled();
});

it('should not prevent default mousedown for interactive elements', () => {
const onMouseDown = jest.fn();

render(
<Menu opened closeOnItemClick={false} withInitialFocusPlaceholder={false}>
<Menu.Target>
<button type="button">Target</button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item>
<input
data-testid="input-element"
type="text"
onMouseDown={onMouseDown}
placeholder="Test input"
/>
</Menu.Item>
</Menu.Dropdown>
</Menu>
);

const inputElement = screen.getByTestId('input-element');
const mouseDownEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
button: 0,
});

const preventDefaultSpy = jest.spyOn(mouseDownEvent, 'preventDefault');

act(() => {
inputElement.dispatchEvent(mouseDownEvent);
});

expect(onMouseDown).toHaveBeenCalled();
expect(preventDefaultSpy).not.toHaveBeenCalled();
});
});
19 changes: 18 additions & 1 deletion packages/@mantine/core/src/components/Menu/MenuItem/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,26 @@ export const MenuItem = polymorphicFactory<MenuItemFactory>((props, ref) => {
}
});

const handleMouseDown = createEventHandler<any>(_others.onMouseDown, (event) => {
let target = event.target as Element;
while (target && target !== event.currentTarget) {
if (target.getAttribute) {
if (
target.getAttribute('draggable') === 'true' ||
['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A'].includes(target.tagName) ||
target.getAttribute('contenteditable') === 'true'
) {
return;
}
}
target = target.parentElement as Element;
}
event.preventDefault();
});

return (
<UnstyledButton
onMouseDown={(event) => event.preventDefault()}
onMouseDown={handleMouseDown}
{...others}
unstyled={ctx.unstyled}
tabIndex={ctx.menuItemTabIndex}
Expand Down