-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] useRightClickMenu / useContextMenu #1771
Comments
这里的设计是否可以不传入菜单以及对应的容器。 xPos, yPos, showMenu。 |
我认为你说的有道理,这是改造之后的useRightClickMenu type RightClickMenuInstance = [number, number, (visible: boolean) => void];
export const useRightClickMenu = (
menu: JSX.Element | (() => JSX.Element),
container: HTMLElement | Element = document.body,
overflow: 'auto' | 'visible' = 'auto'
): RightClickMenuInstance
|
这个 overflow 不是很理解。假如是控制菜单的展示位置,感觉我们自动帮它处理了就可以了「也就是你提到的解决不传入 menu 的文图」,估计就是 auto?还有其他的模式代表什么含义呢? |
|
我理解我们这个边界处理应该是针对页面的吧?不应该针对容器? |
你说的有道理,边界处理确实不应该针对容器,调整了一下,现在同时也支持了ref: type RightClickMenuInstance = [number, number, (visible: boolean) => void];
export const useRightClickMenu = (
menu: JSX.Element | (() => JSX.Element),
target:
| HTMLElement
| (() => HTMLElement)
| React.MutableRefObject<HTMLElement> = document.body
): RightClickMenuInstance 那么现在overflow去掉了,弹窗将依据页面高宽进行边界处理 |
@brickspert @crazylxr |
情况如何 |
很糟糕,没有进展,但是你可以先尝试使用下边的代码 // useAppendRootNode.tsx
import { useEffect } from 'react';
import ReactDOM from 'react-dom';
export const isBrowser = () => typeof window !== 'undefined';
interface AppendRootNodeInstance {
show: () => void;
destory: () => void;
}
type AppendRootNodeResult = [string, AppendRootNodeInstance];
export const useAppendRootNode = (
id: string,
render: (() => JSX.Element) | JSX.Element,
createElement?: () => HTMLElement,
parent: HTMLElement = isBrowser() ? document.body : null
): AppendRootNodeResult => {
const show = () => {
if (document.getElementById(id)) {
return;
}
const ele = createElement?.() ?? document.createElement('div');
ele.id = id;
parent.append(ele);
};
const destory = () => {
const ele = document.getElementById(id);
if (!ele) return;
parent.removeChild(ele);
};
useEffect(() => {
show();
return () => {
destory();
};
}, []);
useEffect(() => {
ReactDOM.render(
render instanceof Function ? render() : render,
document.getElementById(id)
);
}, [render, id]);
return [id, { show, destory }];
};
export default useAppendRootNode;
// useRightClickMenu.tsx
import React, { useEffect, useState, useRef } from 'react';
import { useAppendRootNode } from './useAppendRootNode';
import { throttle } from 'lodash-es';
type RightClickMenuInstance = [number, number, (visible: boolean) => void];
export const useRightClickMenu = (
menu: JSX.Element | (() => JSX.Element),
target:
| HTMLElement
| (() => HTMLElement)
| React.MutableRefObject<HTMLElement> = document.body
): RightClickMenuInstance => {
const [contextMenu, setContextMenu] = useState({
x: 0,
y: 0,
visible: true,
});
const memoAttr = useRef(null);
const ref = useRef(null);
const container = (() => {
if (!target) return null;
if (target instanceof Function) {
return target();
}
// @ts-ignore
if (target.current !== void 0) {
// @ts-ignore
return target.current;
}
return target;
})();
useAppendRootNode(
'right-click-context-menu',
<div
className="absolute"
ref={ref}
style={{
position: 'absolute',
left: contextMenu.x,
top: contextMenu.y,
display: contextMenu.visible ? 'flex' : 'none',
zIndex: 9999999,
visibility: memoAttr.current === null ? 'hidden' : 'visible',
}}
>
{menu instanceof Function ? menu() : menu}
</div>
);
useEffect(() => {
if (!ref.current) return;
const { clientHeight, clientWidth } = ref.current;
memoAttr.current = {
clientHeight,
clientWidth,
};
setContextMenu({
x: 0,
y: 0,
visible: false,
});
}, [ref.current]);
useEffect(() => {
if (!container) return;
const handleContextMenuClick = (e: PointerEvent) => {
e.preventDefault();
const { pageX, pageY } = e;
const { clientHeight, clientWidth } = memoAttr.current;
const {
scrollHeight: windowHeight,
scrollWidth: windowWidth,
} = document.body;
if (clientHeight > windowHeight || clientWidth > windowWidth) {
throw new Error('the menu is longer than the browser');
}
const x = clientWidth + pageX > windowWidth ? pageX - clientWidth : pageX;
const y =
clientHeight + pageY > windowHeight ? pageY - clientHeight : pageY;
setContextMenu({
x,
y,
visible: true,
});
};
const handleOutsideClick = (
e: PointerEvent & { path: Array<HTMLElement> }
) => {
if (e.path.includes(ref.current)) {
return;
}
setContextMenu({
...contextMenu,
visible: false,
});
};
const handleThrottleOutSideClick = throttle(handleOutsideClick, 800);
container.addEventListener('contextmenu', handleContextMenuClick);
document.addEventListener('click', handleOutsideClick);
document.addEventListener('scroll', handleThrottleOutSideClick);
window.addEventListener('resize', handleThrottleOutSideClick);
return () => {
container.removeEventListener('contextmenu', handleContextMenuClick);
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('scroll', handleThrottleOutSideClick);
window.removeEventListener('resize', handleThrottleOutSideClick);
};
}, [container]);
return [
contextMenu.x,
contextMenu.y,
visible => {
setContextMenu({
...contextMenu,
visible,
});
},
];
};
export default useRightClickMenu; |
用于添加自定义右键菜单,只需要传入需要展现的菜单以及对应的容器
API
经过一定讨论,基本形成如下的结论
DEMO
The text was updated successfully, but these errors were encountered: