@totase/react-context-menu
Version:
Minimal context menu component for React
3 lines (2 loc) • 3.71 kB
JavaScript
import"./main.css";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as n,useRef as i,useEffect as r,useContext as a,useCallback as c,createContext as o,Children as l,cloneElement as s}from"react";import u from"clsx";let d=o(null);var m=({hide:t,children:n})=>e(d.Provider,{value:{hide:t},children:n}),x=({children:t,onClick:i,className:r,disabled:o=!1,...l})=>{let s=a(d),[m,x]=n({clicked:!1,eventRef:null}),p=c(()=>{x(e=>({...e,clicked:!1})),m.clicked&&m.eventRef&&(s?.hide(),i(m.eventRef))},[m.clicked,m.eventRef]),v=u("react-context-menu__item",r,{"react-context-menu__item--disabled":o,"react-context-menu__item--clicked":m.clicked});return e("button",{...l,onClick:e=>{e.stopPropagation(),i&&x({clicked:!0,eventRef:e})},onAnimationEnd:p,className:v,"aria-disabled":o,disabled:o,role:"menuitem",type:"button",tabIndex:-1,children:t})},p=({className:t,...n})=>e("hr",{className:u("react-context-menu__separator",t),...n});let v="react-context-menu__submenu-right",f="react-context-menu__submenu-bottom",b=["click","resize","scroll","contextmenu"],h=e=>{let t={x:e.clientX,y:e.clientY};return(!t.x||t.x<0)&&(t.x=0),(!t.y||t.y<0)&&(t.y=0),t},g=(e,t)=>{if(!t)return e;let{x:n,y:i}=e,{innerWidth:r,innerHeight:a}=window,{offsetWidth:c,offsetHeight:o}=t;return n+c>r&&(n-=n+c-r),i+o>a&&(i-=i+o-a),{x:n,y:i}},_=e=>l.toArray(e).filter(Boolean).map(e=>s(e));var y=({label:a,children:c,className:o,disabled:l=!1,...s})=>{let[d,m]=n(!1),x=i(null),p=i(null),b=i(null);r(()=>()=>{x.current&&clearTimeout(x.current)},[]);let h=()=>{x.current&&clearTimeout(x.current)},g=u("react-context-menu__item",o,{"react-context-menu__item--disabled":l});return t("div",{...s,ref:p,className:g,"aria-haspopup":"true",role:"menuitem",tabIndex:-1,onMouseEnter:()=>{if(b.current&&p.current){h(),m(!0),b.current.style.top="0",b.current.classList.remove(v,f);let{height:e}=p.current.getBoundingClientRect(),{right:t,bottom:n}=b.current.getBoundingClientRect();t>window.innerWidth&&b.current.classList.add(v),n-window.innerHeight>0&&(b.current.style.top=`${window.innerHeight-n-e}px`,b.current.classList.add(f))}},onMouseLeave:()=>{h(),x.current=setTimeout(()=>{m(!1)},150)},onClick:e=>e.stopPropagation(),children:[t("div",{className:"react-context-menu__label",children:[a,e("span",{className:"react-context-menu__arrow"})]}),e("div",{ref:b,style:{visibility:d?"visible":"hidden"},className:"react-context-menu__submenu",children:_(c)})]})};let $=({triggerId:t,children:a,className:c,triggerEvent:o="contextmenu",animateExit:l=!0,...s})=>{let[d,x]=n({active:!1,leaving:!1,position:{x:0,y:0}}),p=i(null),v=e=>{let t=h(e);t=g(t,p.current),JSON.stringify(d.position)!==JSON.stringify(t)&&(e.stopPropagation(),e.preventDefault(),x(e=>({...e,active:!0,position:t})))},f=()=>{l?x(e=>({...e,leaving:!0})):x(e=>({...e,active:!1}))};if(r(()=>{let{position:e}=d;d.active&&x(t=>({...t,position:g(e,p.current)}))},[d.active]),r(()=>{let e=document.getElementById(t);if(e&&e.addEventListener(o,v),d.active)for(let e of b)window.addEventListener(e,f);return()=>{for(let t of(e?.removeEventListener(o,v),b))window.removeEventListener(t,f)}},[t,d.active,d.position,v,f]),!d.active)return null;let y=u("react-context-menu",c,{"react-context-menu--exit":d.leaving});return e("div",{...s,className:y,style:{left:d.position.x,top:d.position.y},role:"menu",ref:p,onAnimationEnd:()=>{let{leaving:e,active:t}=d;e&&t&&x(()=>({position:{x:0,y:0},active:!1,leaving:!1}))},onClick:e=>e.stopPropagation(),tabIndex:-1,children:e(m,{hide:f,children:_(a)})})};$.Item=x,$.Separator=p,$.SubMenu=y;var C=$;export{C as ContextMenu,x as MenuItem,p as Separator,y as SubMenu,m as ContextProvider,d as ContextMenuContext};
//# sourceMappingURL=main.js.map