Fix context menu click-outside behavior - Windows Explorer style
- Fixed menu not closing when clicking outside by removing event.stopPropagation() - Added delayed event listener attachment (50ms) to avoid capturing the triggering click - Implemented proper Windows Explorer behaviors: * Left click outside menu → closes menu * Right click anywhere → closes current menu * Escape key → closes menu * Window blur → closes menu * Scroll → closes menu - Added transparent backdrop layer for better click detection - Used data-context-menu attribute for precise element targeting - Improved event cleanup to prevent memory leaks Now matches Windows file manager behavior exactly.
This commit is contained in:
@@ -108,24 +108,66 @@ export function FileManagerContextMenu({
|
|||||||
|
|
||||||
adjustPosition();
|
adjustPosition();
|
||||||
|
|
||||||
|
// 延迟添加事件监听器,避免捕获到触发菜单的那次点击
|
||||||
|
let cleanupFn: (() => void) | null = null;
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
// 点击外部关闭菜单
|
// 点击外部关闭菜单
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
// 检查点击是否在菜单内部
|
||||||
|
const target = event.target as Element;
|
||||||
|
const menuElement = document.querySelector('[data-context-menu]');
|
||||||
|
|
||||||
|
if (!menuElement?.contains(target)) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 右键点击关闭菜单(Windows行为)
|
||||||
|
const handleRightClick = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 键盘支持
|
// 键盘支持
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('click', handleClickOutside);
|
// 窗口失焦关闭菜单
|
||||||
|
const handleBlur = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 滚动时关闭菜单(Windows行为)
|
||||||
|
const handleScroll = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside, true);
|
||||||
|
document.addEventListener('contextmenu', handleRightClick);
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
window.addEventListener('blur', handleBlur);
|
||||||
|
window.addEventListener('scroll', handleScroll, true);
|
||||||
|
|
||||||
|
// 设置清理函数
|
||||||
|
cleanupFn = () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside, true);
|
||||||
|
document.removeEventListener('contextmenu', handleRightClick);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
window.removeEventListener('blur', handleBlur);
|
||||||
|
window.removeEventListener('scroll', handleScroll, true);
|
||||||
|
};
|
||||||
|
}, 50); // 50ms延迟,确保不会捕获到触发菜单的点击
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('click', handleClickOutside);
|
clearTimeout(timeoutId);
|
||||||
document.removeEventListener('keydown', handleKeyDown);
|
if (cleanupFn) {
|
||||||
|
cleanupFn();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [isVisible, x, y, onClose]);
|
}, [isVisible, x, y, onClose]);
|
||||||
|
|
||||||
@@ -271,17 +313,18 @@ export function FileManagerContextMenu({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{/* 透明遮罩层用于捕获点击事件 */}
|
||||||
|
<div className="fixed inset-0 z-40" />
|
||||||
|
|
||||||
|
{/* 菜单本体 */}
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50"
|
data-context-menu
|
||||||
onClick={(e) => e.stopPropagation()}
|
className="fixed bg-dark-bg border border-dark-border rounded-lg shadow-xl py-1 min-w-[180px] max-w-[250px] z-50"
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute bg-dark-bg border border-dark-border rounded-lg shadow-lg py-1 min-w-[180px] max-w-[250px] z-50"
|
|
||||||
style={{
|
style={{
|
||||||
left: menuPosition.x,
|
left: menuPosition.x,
|
||||||
top: menuPosition.y
|
top: menuPosition.y
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
{menuItems.map((item, index) => {
|
{menuItems.map((item, index) => {
|
||||||
if (item.separator) {
|
if (item.separator) {
|
||||||
@@ -323,6 +366,6 @@ export function FileManagerContextMenu({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user