@vuu-ui/vuu-popups 0.8.25 → 0.8.26-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.js CHANGED
@@ -1,2 +1,2144 @@
1
- import Xt from"clsx";import{useThemeAttributes as Jt}from"@vuu-ui/vuu-utils";import{useCallback as Yt,useLayoutEffect as jt,useRef as $e}from"react";import{Button as Tt,Text as kt}from"@salt-ds/core";import Rt from"clsx";import{jsx as Te,jsxs as Lt}from"react/jsx-runtime";var Ht="vuuDialogHeader",ke=({hideCloseButton:e=!1,title:o,onClose:t,...n})=>Lt("div",{...n,className:Rt(Ht,"vuuToolbarProxy"),children:[Te(kt,{className:"dialogHeader",children:o}),!e&&Te(Tt,{onClick:t,"data-align":"end","data-icon":"close",variant:"secondary"},"close")]});import Dt from"clsx";import St,{createElement as Ie}from"react";import q from"react-dom";import*as Re from"react-dom";import It from"clsx";var At=1,Nt=({className:e,dataMode:o,x:t=0,y:n=0,win:r=window})=>{let i=r.document.createElement("div");return i.className=It(`vuuPopup ${At++}`,e),i.style.cssText=`left:${t}px; top:${n}px;`,o&&(i.dataset.mode=o),r.document.body.appendChild(i),i},zn=e=>Nt(e),He=(e,o,t,n,r)=>{o.style.cssText=`left:${t}px; top:${n}px;position: absolute;`,Re.render(e,o,r)};var S=!1,I=[],Ae=e=>(e==null?void 0:e.type)==="menu-action",Ne=e=>(e==null?void 0:e.type)==="click-away";function X(e){if(e.key==="Esc"){if(I.length)Se();else if(S){let o=document.body.querySelector(".vuuDialog");o&&q.unmountComponentAtNode(o)}}}function De(e){if(I.length){let o=document.body.querySelectorAll(".vuuPopup,#vuu-portal-root");for(let t=0;t<o.length;t++)if(o[t].contains(e.target))return;Se({mouseEvt:e,type:"click-away"})}}function Se(e){if(I.length===1)B.hidePopup(e,"anon","all");else if(I.length){let o=document.body.querySelectorAll(".vuuPopup");for(let t=0;t<o.length;t++)q.unmountComponentAtNode(o[t]);Be("*")}}function Bt(){S===!1&&(S=!0,window.addEventListener("keydown",X,!0))}function Ot(){S&&(S=!1,window.removeEventListener("keydown",X,!0))}function $t(e){I.indexOf(e)===-1&&(I.push(e),S===!1&&(window.addEventListener("keydown",X,!0),window.addEventListener("click",De,!0)))}function Be(e){if(I.length){if(e==="*")I.length=0;else{let o=I.indexOf(e);o!==-1&&I.splice(o,1)}I.length===0&&S===!1&&(window.removeEventListener("keydown",X,!0),window.removeEventListener("click",De,!0))}}var Kt=({children:e,position:o,style:t})=>{let n=Dt("hwPopup","hwPopupContainer",o);return Ie("div",{className:n,style:t},e)},zt=1,B=class e{static showPopup({group:o="all",name:t="anon",left:n=0,position:r="",right:i="auto",top:s=0,width:d="auto",component:l}){if(!l)throw Error("PopupService showPopup, no component supplied");typeof l.props.onClose=="function"?e.onClose=l.props.onClose:e.onClose=void 0,$t(t),document.addEventListener("keydown",e.escapeKeyListener,!0);let u=document.body.querySelector(".vuuPopup."+o);u===null&&(u=document.createElement("div"),u.className="vuuPopup "+o,document.body.appendChild(u));let a={width:d};He(Ie(Kt,{key:zt++,position:r,style:a},l),u,n,s,()=>{e.keepWithinThePage(u,i)})}static escapeKeyListener(o){o.key==="Escape"&&e.hidePopup({type:"escape",event:o})}static hidePopup(o,t="anon",n="all"){var r;if(I.indexOf(t)!==-1){Be(t);let i=document.body.querySelector(`.vuuPopup.${n}`);i&&q.unmountComponentAtNode(i)}document.removeEventListener("keydown",e.escapeKeyListener,!0),(r=e==null?void 0:e.onClose)==null||r.call(e,o?{...o,closedBy:"popup-service"}:void 0)}static keepWithinThePage(o,t="auto"){let n=o.querySelector(".vuuPopupContainer > *");if(n){let{top:r,left:i,width:s,height:d,right:l}=n.getBoundingClientRect(),u=window.innerWidth,p=window.innerHeight-(r+d);p<0&&(n.style.top=Math.round(r)+p+"px");let c=u-(i+s);if(c<0&&(n.style.left=Math.round(i)+c+"px"),typeof t=="number"&&t!==l){let f=t-l;n.style.left=i+f+"px"}}}},Le=class e{static showDialog(o){let t=".vuuDialog",n=o.props.onClose;Bt(),q.render(St.cloneElement(o,{container:t,onClose:()=>{e.closeDialog(),n&&n()}}),document.body.querySelector(t))}static closeDialog(){Ot();let o=document.body.querySelector(".vuuDialog");o&&q.unmountComponentAtNode(o)}};import Vt from"clsx";import{useCallback as Ft,useLayoutEffect as Wt,useRef as qt,useState as Gt}from"react";var G=(e,o,t,n,r,i)=>{let{bottom:s,height:d,left:l,right:u,top:a,width:p}=e.getBoundingClientRect();switch(o){case"below":return{left:l+t,top:s+n};case"right":return{left:u+t,top:a+n};case"below-center":return{left:l+p/2+t,top:s+n};case"below-right":return{left:l,minWidth:r,top:s+n};case"below-full-width":return{left:l+t,minWidth:r,top:s+n,width:p};case"center":return i?{left:p/2-i.width/2+t,top:d/2-i.height/2+n,visibility:"visible"}:{left:p/2+t,top:d/2+n,visibility:"hidden"};default:throw Error(`Popup getPositionRelativeToAnchor non-supported placement value ${o}`)}};var $=({anchorElement:e,minWidth:o,offsetLeft:t=0,offsetTop:n=0,placement:r,position:i})=>{let s=qt(null),[d,l]=Gt(i);Wt(()=>{if(r==="absolute"&&i)l(i);else if(e.current&&r!=="auto"){let a=s.current===null?void 0:s.current.getBoundingClientRect(),p=G(e.current,r,t,n,o,a);l(p)}},[e,o,t,n,r,i]);let u=Ft(a=>{if(s.current=a,a&&r==="center"&&e.current){let{height:p,width:c}=a.getBoundingClientRect();l(G(e.current,r,t,n,void 0,{height:p,width:c}))}},[e,t,n,r]);return{position:d,popupRef:r==="center"?u:void 0}};import{jsx as Ut}from"react/jsx-runtime";var Oe=({children:e,className:o,anchorElement:t,minWidth:n,placement:r,position:i})=>{let{popupRef:s,position:d}=$({anchorElement:t,minWidth:n,placement:r,position:i});return d===void 0?null:Ut("div",{className:Vt("vuuPortal",o),ref:s,style:d,children:e})};import{jsx as se,jsxs as _t}from"react/jsx-runtime";var Ke="vuuDialog",Qt={current:document.body},Zt={},ze=({PopupProps:e=Zt,children:o,className:t,isOpen:n=!1,onClose:r,style:i,title:s,hideCloseButton:d=!1,...l})=>{let{anchorElement:u=Qt,offsetLeft:a=0,offsetTop:p=0,placement:c="auto"}=e,f=$e(null),y=$e(null),[b,P,M]=Jt(),{position:E}=$({anchorElement:u,offsetLeft:a,offsetTop:p,placement:c}),w=Yt(()=>{r==null||r()},[r]);return jt(()=>{if(f.current){if(n){f.current.showModal();let{left:T,top:x}=f.current.getBoundingClientRect();y.current&&(y.current.style.cssText=`left:-${T}px;position:absolute;top:-${x}px;`)}else f.current.close();if(c.endsWith("center")){let{width:T}=f.current.getBoundingClientRect();f.current.style.marginLeft=`-${T/2}px`}}},[n,c]),_t("dialog",{...l,className:Xt(Ke,b),"data-mode":M,onClose:w,id:"vuu-dialog",ref:f,style:{...i,...E},children:[se(ke,{hideCloseButton:d,onClose:w,title:s}),se("div",{className:`${Ke}-body`,children:o}),se("div",{id:"vuu-dialog-portal-root",ref:y})]})};import{useCallback as eo,useState as to}from"react";import{jsx as oo}from"react/jsx-runtime";var Er=()=>{let[e,o]=to(),t=eo(()=>{o(void 0)},[]);return{dialog:e?oo(ze,{className:"vuDialog",isOpen:!0,onClose:t,style:{maxHeight:500},title:e.title,hideCloseButton:e.hideCloseButton,children:e.content}):null,setDialogState:o}};import{useId as zo}from"@vuu-ui/vuu-utils";import{useCallback as me,useRef as it}from"react";import{useThemeAttributes as no}from"@vuu-ui/vuu-utils";import{useLayoutEffect as Fe,useRef as ro,useState as io}from"react";import{createPortal as so}from"react-dom";function uo(e){return typeof e=="function"?e():e}var ao=["vuu-dialog-portal-root","vuu-portal-root"],lo=e=>{if(Array.isArray(e))for(let o of e){let t=document.getElementById(o);if(t)return t}else return document.getElementById(e);return null},K=({children:e,container:o=document.body,id:t=ao,onRender:n,open:r=!0,themeAttributes:i})=>{var f;let[s,d]=io(!1),l=ro(null),u=(f=uo(o))!=null?f:document.body,[a,p,c]=no(i);return Fe(()=>{let y=lo(t);y?l.current=y:(l.current=document.createElement("div"),l.current.id=typeof t=="string"?t:t.length>0?t.at(-1):"vuu-portal-root");let b=l.current;u.contains(b)||u.appendChild(b),b.classList.add(a,p),b.dataset.mode=c,d(!0)},[t,u,a,p,c]),Fe(()=>{requestAnimationFrame(()=>{n==null||n()})},[n]),r&&s&&l.current&&e?so(e,l.current):null};import Ze from"clsx";import _e,{useLayoutEffect as Eo,useMemo as To,useRef as ko}from"react";import{useId as Ro}from"@vuu-ui/vuu-utils";import We,{useCallback as po,useMemo as co}from"react";var ue=e=>e.type===J||!!e.props["data-group"],mo=e=>{if(Array.isArray(e)&&ae(e[0]))return e[0]},fo=(e,o,t,n=!1)=>{let{props:{children:r}}=e;return{childWithId:We.cloneElement(e,{hasSeparator:n,id:`${o}`,key:o,children:t?mo(r):r}),grandChildren:t?r:void 0}},qe=(e,o)=>{let t=po(()=>{let i=(s,d=o,l={},u={})=>{let a=l[d]=[],p=0,c=!1;return We.Children.forEach(s,f=>{if(!ae(f))if(f.type===Ge)c=!0;else{let y=ue(f),b=`${d}-${p}`,{props:{action:P,options:M}}=f,{childWithId:E,grandChildren:w}=fo(f,b,y,c);a.push(E),w?i(w,b,l,u):u[b]={action:P,options:M},p+=1,c=!1}}),[l,u]};return i(e)},[o,e]),[n,r]=co(()=>t(),[t]);return[n,r]};import{useCallback as Y,useMemo as Po,useRef as le,useState as wo}from"react";var Ve=e=>e.closest("[data-root='true']")!==null,Ue=(e,o)=>{var t;return e.ariaHasPopup==="true"&&((t=e.dataset)==null?void 0:t.idx)===`${o}`||e.querySelector(`:scope > [data-index='${o}'][aria-haspopup='true']`)!==null};function go(e,...o){let t=new Set(e);for(let n of o)for(let r of n)t.add(r);return t}var vo="Enter";var bo="Delete",ho=new Set([vo,bo]),xo=new Set(["Tab"]),Mo=new Set(["ArrowRight","ArrowLeft"]),Xe=new Set(["Home","End","ArrowDown","ArrowUp"]),Je=new Set(["Home","End","ArrowRight","ArrowLeft"]),yo=new Set(["F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12"]),qr=go(ho,Je,Xe,Mo,yo,xo);var Ye=({key:e},o="vertical")=>(o==="vertical"?Xe:Je).has(e);import{isValidNumber as pe}from"@vuu-ui/vuu-utils";var je=({autoHighlightFirstItem:e=!1,count:o,defaultHighlightedIdx:t,highlightedIndex:n,onActivate:r,onHighlight:i,onCloseMenu:s,onOpenMenu:d})=>{var T;if(pe(n)&&pe(t))throw Error("useKeyboardNavigation do not pass values for both highlightedIndex and defaultHighlightedIdx");let l=pe(n),u=le((T=t!=null?t:n)!=null?T:e?0:-1),[,a]=wo(null),p=Y(x=>{u.current=x,i==null||i(x),a({})},[i]),c=Y(x=>{x!==u.current&&(l||p(x))},[l,p]),f=le(!0),y=le(!1),b=x=>y.current=x,P=l?n:u.current,M=Y(x=>{let m=Co(o,x.key,u.current);m!==u.current&&c(m)},[o,c]),E=Y(x=>{if(Ye(x))x.preventDefault(),x.stopPropagation(),f.current=!0,M(x);else if((x.key==="ArrowRight"||x.key==="Enter")&&Ue(x.target,P)){let g=x.target.querySelector(`:scope > [data-index='${P}']`);g&&(d==null||d(g,!0))}else x.key==="ArrowLeft"&&!Ve(x.target)?s(x,"close-child-menu"):x.key==="Enter"?(x.preventDefault(),x.stopPropagation(),r&&r(P)):x.key==="Tab"&&s(x,"tab-away")},[P,M,r,s,d]),w=Po(()=>({onFocus:()=>{P===-1&&p(0)},onKeyDown:E,onMouseDownCapture:()=>{f.current=!1,b(!0)},onMouseMove:()=>{f.current&&(f.current=!1)},onMouseLeave:()=>{f.current=!0,b(!1),c(-1)}}),[E,P,p,c]);return{focusVisible:f.current?P:-1,controlledHighlighting:l,highlightedIndex:P,setHighlightedIndex:c,listProps:w,setIgnoreFocus:b}};function Co(e,o,t){return o==="ArrowUp"?t>0?t-1:t:t===null?0:t===e-1?t:t+1}import{Fragment as Ao,jsx as z}from"react/jsx-runtime";var Qe="vuuMenuList",Ge=()=>z("li",{className:"vuuMenuItem-divider"}),J=()=>null,j=({children:e,idx:o,options:t,...n})=>z("div",{...n,children:e}),et=({children:e})=>z(Ao,{children:e});et.displayName="MenuItemLabel";j.Label=et;var Ho=e=>_e.isValidElement(e)&&typeof e.type!="string"&&"displayName"in e.type?e.type.displayName:void 0,ae=e=>Ho(e)==="MenuItemLabel",Lo=e=>e.props["data-icon"],ce=({activatedByKeyboard:e,childMenuShowing:o,children:t,className:n,defaultHighlightedIdx:r,highlightedIdx:i,id:s,isRoot:d,listItemProps:l,onHighlightMenuItem:u,onActivate:a,onCloseMenu:p,openMenu:c,...f})=>{let y=Ro(s),b=ko(null),P=To(()=>new Map,[]),M=h=>{var C;let v=(C=b.current)==null?void 0:C.querySelector(`:scope > [data-index='${h}']`);v!=null&&v.id&&(a==null||a(v.id))},{focusVisible:E,highlightedIndex:w,listProps:T}=je({count:_e.Children.count(t),defaultHighlightedIdx:r,highlightedIndex:i,onActivate:M,onHighlight:u,onOpenMenu:c,onCloseMenu:p}),x=o==null?E:-1;Eo(()=>{var h;o===void 0&&e&&((h=b.current)==null||h.focus())},[e,o]);let m=()=>w===void 0||w===-1?void 0:P.get(w);function g(){let h={...l,role:"menuitem"},v=(R,H,L)=>H?[z("span",{className:"vuuIconContainer","data-icon":L},"icon")].concat(R):R;function C(R,H,L,D){var Ee;let{children:N,className:Pt,"data-icon":we,id:U,hasSeparator:wt,label:re,...Ct}=H.props,ie=ue(H),Ce=ie&&o===U,Et=Ce?`${y}-${U}`:void 0;R.push(z(j,{...Ct,...h,...Io(U,L,(Ee=H.key)!=null?Ee:U,w,x,Pt,wt),"aria-controls":Et,"aria-haspopup":ie||void 0,"aria-expanded":Ce||void 0,children:v(ie&&re!=null?re:N,D,we)}))}let k=[];if(t.length>0){let R=t.some(Lo);t.forEach((H,L)=>{C(k,H,L,R)})}return k}return z("div",{...f,...T,"aria-activedescendant":m(),className:Ze(Qe,n,{[`${Qe}-childMenuShowing`]:o!==void 0}),"data-root":d||void 0,id:y,ref:b,role:"menu",children:g()})},Io=(e,o,t,n,r,i,s)=>({id:`menuitem-${e}`,key:t!=null?t:o,"data-index":o,className:Ze("vuuMenuItem",i,{"vuuMenuItem-separator":s,vuuHighlighted:o===n,focusVisible:r===o})});ce.displayName="MenuList";import{useCallback as A,useMemo as No,useRef as Q,useState as Do}from"react";var de=e=>e==null?void 0:e.closest("[data-index],[aria-posinset]");var ot=(e,o,t)=>e.map((n,r)=>r===e.length-1?{...n,[t]:n[t]-o}:n),So=(e,o)=>ot(e,o,"left"),Bo=(e,o)=>ot(e,o,"top"),Oo=(e,o)=>{let[t,n]=o.slice(-2),r=document.getElementById(`${e}-${n.id}`);if(r===null)throw Error(`useCascade.flipSides element with id ${n.id} not found`);let{width:i}=r.getBoundingClientRect();return o.map(s=>s===n?{...s,left:t.left-(i-2)}:s)},$o=(e,o)=>{let[{left:t,top:n}]=o.slice(-1),{offsetWidth:r,offsetTop:i}=e;return{left:t+r,top:i+n}},nt=(e,o)=>{let t=e.lastIndexOf("-");return e.startsWith("menuitem")?t>-1?e.slice(9,t):o:t>-1?e.slice(0,t):o},Ko=e=>e.slice(9),tt=({ariaExpanded:e,ariaHasPopup:o,id:t},n)=>{if(t.startsWith("menuitem"))return{hostMenuId:nt(t,n),targetMenuId:Ko(t),menuItemId:t,isGroup:o==="true",isOpen:e==="true"};throw Error(`getMenuItemDetails #${t} is not a menuitem`)},rt=({id:e,onActivate:o,onMouseEnterItem:t,position:{x:n,y:r}})=>{let[,i]=Do({}),s=Q([{id:e,left:n,top:r}]),d=A(m=>s.current.findIndex(g=>g.id===m)!==-1,[]),l=A(m=>{let g=c.current[m];if(g===void 0)throw Error(`getOpenMenuState no entry for menu ${m}`);return g},[]),u=A(m=>{s.current=m,i({})},[]),a=Q(),p=Q(),c=Q({[e]:"no-popup"}),f=A((m=e,g,h=null)=>{if(m===e&&h===null)u([{id:e,left:n,top:r}]);else{c.current[m]="popup-open";let v=document.getElementById(h);if(v!==null){let{left:C,top:k}=$o(v,s.current);u(s.current.concat({id:g,left:C,top:k}))}else throw Error(`openMenu no menuItem ${h}`)}},[e,n,r,u]),y=A(m=>{if(m===e)u([]);else{let g=s.current.slice(),h=g.pop();c.current[h.id]="no-popup";let v=g.at(-1);v&&(c.current[v.id]="no-popup"),u(g)}},[e,u]),b=A(m=>{let g=s.current.slice(),h=m.slice(9),{id:v}=g.at(-1);for(;g.length>1&&!h.startsWith(v);){let C=nt(v,e);g.pop(),c.current[v]="no-popup",c.current[C]="no-popup",{id:v}=g[g.length-1]}g.length<s.current.length&&u(g)},[e,u]),P=A(()=>{a.current&&(clearTimeout(a.current),a.current=void 0)},[]),M=A((m,g,h,v=300)=>{P(),a.current=window.setTimeout(()=>{b(h),c.current[m]="popup-open",c.current[g]="no-popup",f(m,g,h)},v)},[P,b,f]),E=A((m,g,h)=>{c.current[g]="pending-close",p.current=window.setTimeout(()=>{b(h)},400)},[b]),w=A(()=>{let{current:m}=s,g=m.at(-1),h=g?document.getElementById(g.id):void 0;if(h){let{right:v,bottom:C}=h.getBoundingClientRect(),{clientHeight:k,clientWidth:R}=document.body;if(v>R){let H=m.length>1?Oo(e,m):So(m,v-R);u(H)}else if(C>k){let H=Bo(m,C-k);u(H)}typeof h.tabIndex=="number"&&h.focus()}},[e,u]),T=A((m,g=!1)=>{let{hostMenuId:h,targetMenuId:v,menuItemId:C,isGroup:k,isOpen:R}=tt(m,e),{current:{[h]:H}}=c,L=g?0:void 0;if(H==="no-popup"&&k)c.current[h]="popup-pending",M(h,v,C,L);else if(H==="popup-pending"&&!k)c.current[h]="no-popup",clearTimeout(a.current),a.current=void 0;else if(H==="popup-pending"&&k)clearTimeout(a.current),M(h,v,C,L);else if(H==="popup-open")if(d(v)){let D=l(v);switch(b(C),D){case"pending-close":clearTimeout(p.current),p.current=void 0,c.current[v]="no-popup",P();break;default:}}else{let[D,N]=s.current.slice(-2);D.id===h&&c.current[N.id]!=="pending-close"?(E(h,N.id,C),k&&!R&&M(h,v,C,L)):D.id===h&&k&&C!==N.id&&c.current[N.id]==="pending-close"||k?M(h,v,C,L):c.current[N.id]!=="pending-close"&&b(C)}H==="pending-close"&&(P(),clearTimeout(p.current),p.current=void 0,c.current[h]="popup-open")},[P,b,l,d,e,E,M]),x=No(()=>({onMouseEnter:m=>{let g=de(m.target);T(g),t(m,g.id)},onClick:m=>{let g=de(m.target),{isGroup:h,menuItemId:v}=tt(g,e);h?T(g):o(v)}}),[o,t,e,T]);return{closeMenu:y,handleRender:w,listItemProps:x,openMenu:T,openMenus:s.current}};import{Fragment as Wo,jsx as ut}from"react/jsx-runtime";import{createElement as st}from"react";var Fo=()=>{},fe=({PortalProps:e,activatedByKeyboard:o,children:t,className:n,id:r,onClose:i=()=>{},position:s={x:0,y:0},style:d,...l})=>{let u=zo(r),a=it(Fo),[p,c]=qe(t,u),f=it(o),y=me(()=>{f.current=!1},[]),b=me(v=>{let C=v.slice(9),{action:k,options:R}=c[C];a.current(u),i({type:"menu-action",menuId:k,options:R})},[c,u,i]),{closeMenu:P,listItemProps:M,openMenu:E,openMenus:w,handleRender:T}=rt({id:`${u}`,onActivate:b,onMouseEnterItem:y,position:s});a.current=P;let x=me((v,C)=>{f.current=!0,P(),C==="tab-away"&&i({event:v,type:"tab-away"})},[P,i]),m=()=>{},g=w.length-1,h=v=>{if(!(v>=g)){let{id:C}=w[v+1];return C}};return ut(Wo,{children:w.map(({id:v,left:C,top:k},R,H)=>{let L=h(R);return st(K,{...e,key:R,onRender:T},ut(Oe,{anchorElement:{current:document.body},placement:"absolute",position:{left:C,top:k},children:st(ce,{...l,activatedByKeyboard:f.current,childMenuShowing:L,className:n,id:v,isRoot:R===0,key:R,listItemProps:M,onActivate:b,onHighlightMenuItem:m,onCloseMenu:x,openMenu:E,style:d,tabIndex:R===H.length-1?0:void 0},p[v])}))})})};fe.displayName="ContextMenu";import{createContext as qo,useCallback as Go,useMemo as Vo}from"react";import{jsx as ge}from"react/jsx-runtime";var Z=qo(null),Uo=({children:e,context:o,menuActionHandler:t,menuBuilder:n})=>{let r=Vo(()=>o!=null&&o.menuBuilders&&n?o.menuBuilders.concat(n):n?[n]:(o==null?void 0:o.menuBuilders)||[],[o,n]),i=Go(s=>{var d;if(t!=null&&t(s)||(d=o==null?void 0:o.menuActionHandler)!=null&&d.call(o,s))return!0},[o,t]);return ge(Z.Provider,{value:{menuActionHandler:i,menuBuilders:r},children:e})},Di=({children:e,label:o,menuActionHandler:t,menuBuilder:n})=>ge(Z.Consumer,{children:r=>ge(Uo,{context:r,label:o,menuActionHandler:t,menuBuilder:n,children:e})});import{isGroupMenuItemDescriptor as Xo,useThemeAttributes as Jo}from"@vuu-ui/vuu-utils";import{cloneElement as Yo,useCallback as ve,useContext as jo,useMemo as Qo}from"react";import{jsx as be}from"react/jsx-runtime";var at=(e,o)=>{let t=jo(Z),[n,r,i]=Jo(),s=Qo(()=>({themeClass:n,densityClass:r,dataMode:i}),[i,r,n]),d=ve((a,p,c)=>{let f=[];for(let y of a)f=f.concat(y(p,c));return f},[]),l=ve((a,p,{ContextMenuProps:c,contextMenu:f,...y})=>{var P,M;if((P=a.stopPropagation)==null||P.call(a),(M=a.preventDefault)==null||M.call(a),f)return _o({x:a.clientX,y:a.clientY},f);let b=[];if(e&&b.push(e),t&&Array.isArray(t==null?void 0:t.menuBuilders)&&t.menuBuilders.length>0&&b.push(...t.menuBuilders),b.length>0){let E=d(b,p,y),w=T=>(o==null?void 0:o(T))===!0?!0:t==null?void 0:t.menuActionHandler(T);E.length&&w&&en(a,E,w,{PortalProps:{themeAttributes:s},...c})}else console.warn("useContextMenu, no menuBuilders configured. These should be supplied via the ContextMenuProvider(s)")},[d,t,o,e,s]),u=ve(()=>{console.log("hide context menu")},[]);return[l,u]},Zo={},_o=(e,o)=>{B.showPopup({focus:!0,left:0,top:0,component:Yo(o,{position:e})})},en=(e,o,t,{position:n,...r}=Zo)=>{let i=u=>{let a=(p,c)=>Xo(p)?be(J,{label:p.label,children:p.children.map(a)},c):be(j,{action:p.action,className:p.className,"data-icon":p.icon,options:p.options,children:p.label},c);return u.map(a)},s=u=>{var a;if(Ae(u)){if((u==null?void 0:u.closedBy)==="popup-service")return;t(u),B.hidePopup(u)}(a=r==null?void 0:r.onClose)==null||a.call(r,u)},d=n!=null?n:{x:e.clientX,y:e.clientY},l=be(fe,{...r,onClose:s,position:d,children:i(o)});B.showPopup({left:0,top:0,component:l,focus:!0})};import{Icon as on,IconButton as nn}from"@vuu-ui/vuu-ui-controls";import{useId as rn}from"@vuu-ui/vuu-utils";import{Button as sn}from"@salt-ds/core";import ct from"clsx";import{useCallback as _,useRef as lt,useState as tn}from"react";var pt=({anchorElement:e,id:o,menuActionHandler:t,menuBuilder:n,menuClassName:r,menuLocation:i,menuOptions:s,onMenuClose:d,onMenuOpen:l,popupPlacement:u,tabIndex:a})=>{let[p,c]=tn(!1),f=lt(!1),y=lt(null),b=_(m=>{c(m),m&&(l==null||l())},[l]),[P]=at(n,t),M=_(m=>{console.log("menu Open ",{el:m})},[]),E=_(m=>{console.log("onClose"),b(!1),Ne(m)?(m.mouseEvt.target===y.current&&(f.current=!0),d==null||d(m)):requestAnimationFrame(()=>{var g;d==null||d(m),a!==-1&&(m==null?void 0:m.type)!=="tab-away"&&((g=y.current)==null||g.focus())})},[d,b,a]),w=_(m=>{var g;if(f.current)f.current=!1;else{let h=(g=e==null?void 0:e.current)!=null?g:y.current;if(h){let{left:v,top:C,width:k}=G(h,u,0,0);b(!0),P(m,i,{ContextMenuProps:{className:r,id:`${o}-menu`,onClose:E,openMenu:M,position:{x:v,y:C},style:{width:k?k-2:void 0}},...s})}}},[e,E,M,o,r,i,s,u,b,P]);return{ariaAttributes:{"aria-controls":p?`${o}-menu`:void 0,"aria-expanded":p,"aria-haspopup":"menu"},buttonProps:{id:o,onClick:w,tabIndex:a},menuOpen:p,rootRef:y}};import{jsx as dt,jsxs as un}from"react/jsx-runtime";var he="vuuPopupMenu",cs=({anchorElement:e,className:o,disabled:t=!1,label:n,icon:r=n?"chevron-down":"more-vert",id:i,menuActionHandler:s,menuBuilder:d,menuClassName:l,menuLocation:u="header",menuOptions:a,onMenuClose:p,onMenuOpen:c,popupPlacement:f="below-right",tabIndex:y=0,variant:b="secondary",...P})=>{let M=rn(i),{ariaAttributes:E,buttonProps:w,menuOpen:T,rootRef:x}=pt({anchorElement:e,id:M,menuActionHandler:s,menuBuilder:d,menuClassName:l,menuLocation:u,onMenuClose:p,onMenuOpen:c,menuOptions:a,popupPlacement:f,tabIndex:y});return n?un(sn,{...P,...E,...w,className:ct(he,o,`${he}-withCaption`,{"saltButton-active":T}),disabled:t,ref:x,variant:"secondary",children:[r?dt(on,{name:r}):null,n]}):r?dt(nn,{...P,...E,...w,className:ct(he,o,{"saltButton-active":T}),disabled:t,icon:r,ref:x,variant:b}):(console.error("PopupMenu must have a label or an icon (or both)"),null)};import{useThemeAttributes as an}from"@vuu-ui/vuu-utils";import{Button as mt}from"@salt-ds/core";import ln from"clsx";import{useLayoutEffect as pn,useRef as ft}from"react";import{jsx as V,jsxs as gt}from"react/jsx-runtime";var F="vuuPrompt",cn={current:document.body},dn={},Cs=({PopupProps:e=dn,cancelButtonLabel:o="Cancel",confirmButtonLabel:t="Confirm",icon:n,onCancel:r,onConfirm:i,style:s,text:d,title:l,variant:u="info",...a})=>{let{anchorElement:p=cn,offsetLeft:c=0,offsetTop:f=0,placement:y="below"}=e,[b,P,M]=an(),{position:E}=$({anchorElement:p,offsetLeft:c,offsetTop:f,placement:y}),w=ft(null),T=ft(null);return pn(()=>{if(w.current&&(w.current.showModal(),T.current&&T.current.focus(),y.endsWith("center"))){let{width:x}=w.current.getBoundingClientRect();w.current.style.marginLeft=`-${x/2}px`}},[y]),V("dialog",{...a,className:ln(F,`${F}-${u}`,b),"data-mode":M,ref:w,style:{...s,...E},children:gt("form",{className:`${F}-form`,children:[V("div",{className:`${F}-header`,"data-icon":n,children:l}),V("div",{className:`${F}-text`,children:d}),gt("div",{className:`${F}-buttonBar`,children:[V(mt,{onClick:r,variant:"secondary",children:o}),V(mt,{onClick:i,ref:T,value:"default",children:t})]})]})})};import{useLayoutEffect as mn,useState as fn}from"react";var gn=(e,o,t,n)=>{let{bottom:r,height:i,left:s,right:d,top:l,width:u}=e.getBoundingClientRect(),a=s+u/2,p=l+i/2;switch(o){case"above":return{left:a+t,top:l+n};case"below":return{left:a+t,top:r+n};case"right":return{left:d+t,top:p+t};case"left":return{left:s+t,top:p+t};default:throw Error("Tooltip getPositionRelativeToAnchor only supported placement values are left, right, below and right")}},vt=({anchorElement:e,offsetLeft:o=0,offsetTop:t=0,placement:n})=>{let[r,i]=fn();return mn(()=>{if(e.current){let s=gn(e.current,n,o,t);i(s)}},[e,o,t,n]),r};import vn from"clsx";import{jsx as Me}from"react/jsx-runtime";var xe="vuuTooltip",Os=({anchorElement:e,children:o,id:t,onMouseEnter:n,onMouseLeave:r,placement:i,status:s,style:d})=>{let l=vt({anchorElement:e,placement:i});return l===void 0?null:Me(K,{children:Me("div",{className:vn(xe,{[`${xe}-error`]:s==="error"}),"data-align":i,id:t,style:{...d,...l},children:Me("span",{className:`${xe}-content`,onMouseEnter:n,onMouseLeave:r,children:o})})})};import{useId as bn}from"@vuu-ui/vuu-utils";import{useCallback as O,useRef as ee,useState as hn}from"react";var Gs=({id:e,placement:o="right",tooltipContent:t})=>{let n=ee(),r=ee(null),i=ee(),s=ee(),[d,l]=hn(),u=bn(e),a=O(M=>{var E;M.key==="Escape"&&((E=n.current)==null||E.call(n))},[]);n.current=O(()=>{l(void 0),document.removeEventListener("keydown",a)},[a]);let p=O(()=>{window.clearTimeout(s.current)},[]),c=O(()=>{var M;(M=n.current)==null||M.call(n)},[]),f=O(()=>{let{current:M}=r;M&&(l({anchorElement:r,children:t,id:`${u}-tooltip`,onMouseEnter:p,onMouseLeave:c,placement:o}),document.addEventListener("keydown",a)),i.current=void 0},[a,p,c,u,o,t]),y=O(M=>{let E=M.target;E&&(r.current=E,i.current=window.setTimeout(f,800))},[f]),b=O(()=>{r.current&&(i.current?(window.clearTimeout(i.current),i.current=void 0):n.current&&(s.current=window.setTimeout(n.current,200)))},[]);return{anchorProps:{"aria-describedby":`${u}-tooltip`,onMouseEnter:y,onMouseLeave:b},tooltipProps:d}};import xn,{useState as ht,useContext as Mn,useCallback as yn,useEffect as Pn}from"react";import bt from"clsx";import{getUniqueId as wn}from"@vuu-ui/vuu-utils";import{jsx as W,jsxs as Pe}from"react/jsx-runtime";var Cn=60,xt=6e3,ye=1e3,En=300,Mt=56,oe=300,Tn=10,kn=10,ne=50,te="vuuToastNotifications",Rn=(r=>(r.Info="info",r.Success="success",r.Warning="warning",r.Error="error",r))(Rn||{}),yt=xn.createContext({notify:()=>"have you forgotten to provide a NotificationProvider?"}),_s=e=>{let[o,t]=ht([]),n=yn(r=>{let i={...r,id:wn()};t(s=>[...s,i]),setTimeout(()=>{t(s=>s.filter(d=>d!==i))},xt+ye*2)},[]);return Pe(yt.Provider,{value:{notify:n},children:[W("div",{style:{width:oe+ne+kn},children:o.map((r,i)=>W(Hn,{top:Cn+(Mt+Tn)*i,notification:r},r.id))}),e.children]})},eu=()=>Mn(yt),Hn=e=>{let{top:o,notification:t,animated:n=!0}=e,[r,i]=ht(-oe-ne);return Pn(()=>{setTimeout(()=>i(ne)),n&&setTimeout(()=>i(-oe-ne),xt+ye)},[n]),W(K,{children:Pe("div",{className:bt(`${te}-toast`,t.type),style:{height:Mt,right:r,width:oe,top:o,transition:n?`right ${ye}ms, top ${En}ms `:"none"},children:[W("div",{className:bt(`${te}-toastIcon`,`${t.type}-icon`)}),Pe("div",{className:`${te}-toastContent`,children:[W("strong",{className:`${te}-toastHeader`,children:t.header}),W("div",{children:t.body})]})]})})};export{fe as ContextMenu,Z as ContextMenuContext,Di as ContextMenuProvider,ze as Dialog,ke as DialogHeader,Le as DialogService,j as MenuItem,J as MenuItemGroup,ce as MenuList,Rn as NotificationLevel,yt as NotificationsContext,_s as NotificationsProvider,Oe as PopupComponent,cs as PopupMenu,B as PopupService,K as Portal,Cs as Prompt,Ge as Separator,Hn as ToastNotification,Os as Tooltip,zn as createContainer,ae as isMenuItemLabel,Ne as reasonIsClickAway,Ae as reasonIsMenuAction,He as renderPortal,$ as useAnchoredPosition,at as useContextMenu,Er as useDialog,eu as useNotifications,Gs as useTooltip};
1
+ // src/dialog/Dialog.tsx
2
+ import cx5 from "clsx";
3
+ import { useThemeAttributes } from "@vuu-ui/vuu-utils";
4
+ import { useCallback as useCallback2, useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
5
+
6
+ // src/dialog-header/DialogHeader.tsx
7
+ import { Button, Text } from "@salt-ds/core";
8
+ import cx from "clsx";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var classBase = "vuuDialogHeader";
11
+ var DialogHeader = ({
12
+ hideCloseButton = false,
13
+ title,
14
+ onClose,
15
+ ...htmlAttributes
16
+ }) => {
17
+ return /* @__PURE__ */ jsxs("div", { ...htmlAttributes, className: cx(classBase, "vuuToolbarProxy"), children: [
18
+ /* @__PURE__ */ jsx(Text, { className: "dialogHeader", children: title }),
19
+ !hideCloseButton && /* @__PURE__ */ jsx(
20
+ Button,
21
+ {
22
+ onClick: onClose,
23
+ "data-align": "end",
24
+ "data-icon": "close",
25
+ variant: "secondary"
26
+ },
27
+ "close"
28
+ )
29
+ ] });
30
+ };
31
+
32
+ // src/popup/popup-service.ts
33
+ import cx3 from "clsx";
34
+ import React, {
35
+ createElement
36
+ } from "react";
37
+ import ReactDOM2 from "react-dom";
38
+
39
+ // src/portal-deprecated/render-portal.tsx
40
+ import * as ReactDOM from "react-dom";
41
+ import cx2 from "clsx";
42
+ var containerId = 1;
43
+ var getPortalContainer = ({
44
+ className,
45
+ dataMode,
46
+ x = 0,
47
+ y = 0,
48
+ win = window
49
+ }) => {
50
+ const el = win.document.createElement("div");
51
+ el.className = cx2(`vuuPopup ${containerId++}`, className);
52
+ el.style.cssText = `left:${x}px; top:${y}px;`;
53
+ if (dataMode) {
54
+ el.dataset.mode = dataMode;
55
+ }
56
+ win.document.body.appendChild(el);
57
+ return el;
58
+ };
59
+ var createContainer = (props) => getPortalContainer(props);
60
+ var renderPortal = (component, container, x, y, onRender) => {
61
+ container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
62
+ ReactDOM.render(component, container, onRender);
63
+ };
64
+
65
+ // src/popup/popup-service.ts
66
+ var _dialogOpen = false;
67
+ var _popups = [];
68
+ var reasonIsMenuAction = (reason) => (reason == null ? void 0 : reason.type) === "menu-action";
69
+ var reasonIsClickAway = (reason) => (reason == null ? void 0 : reason.type) === "click-away";
70
+ function specialKeyHandler(e) {
71
+ if (e.key === "Esc") {
72
+ if (_popups.length) {
73
+ closeAllPopups();
74
+ } else if (_dialogOpen) {
75
+ const dialogRoot = document.body.querySelector(".vuuDialog");
76
+ if (dialogRoot) {
77
+ ReactDOM2.unmountComponentAtNode(dialogRoot);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ function outsideClickHandler(e) {
83
+ if (_popups.length) {
84
+ const popupContainers = document.body.querySelectorAll(
85
+ ".vuuPopup,#vuu-portal-root"
86
+ );
87
+ for (let i = 0; i < popupContainers.length; i++) {
88
+ if (popupContainers[i].contains(e.target)) {
89
+ return;
90
+ }
91
+ }
92
+ closeAllPopups({ mouseEvt: e, type: "click-away" });
93
+ }
94
+ }
95
+ function closeAllPopups(reason) {
96
+ if (_popups.length === 1) {
97
+ PopupService.hidePopup(reason, "anon", "all");
98
+ } else if (_popups.length) {
99
+ const popupContainers = document.body.querySelectorAll(".vuuPopup");
100
+ for (let i = 0; i < popupContainers.length; i++) {
101
+ ReactDOM2.unmountComponentAtNode(popupContainers[i]);
102
+ }
103
+ popupClosed("*");
104
+ }
105
+ }
106
+ function dialogOpened() {
107
+ if (_dialogOpen === false) {
108
+ _dialogOpen = true;
109
+ window.addEventListener("keydown", specialKeyHandler, true);
110
+ }
111
+ }
112
+ function dialogClosed() {
113
+ if (_dialogOpen) {
114
+ _dialogOpen = false;
115
+ window.removeEventListener("keydown", specialKeyHandler, true);
116
+ }
117
+ }
118
+ function popupOpened(name) {
119
+ if (_popups.indexOf(name) === -1) {
120
+ _popups.push(name);
121
+ if (_dialogOpen === false) {
122
+ window.addEventListener("keydown", specialKeyHandler, true);
123
+ window.addEventListener("click", outsideClickHandler, true);
124
+ }
125
+ }
126
+ }
127
+ function popupClosed(name) {
128
+ if (_popups.length) {
129
+ if (name === "*") {
130
+ _popups.length = 0;
131
+ } else {
132
+ const pos = _popups.indexOf(name);
133
+ if (pos !== -1) {
134
+ _popups.splice(pos, 1);
135
+ }
136
+ }
137
+ if (_popups.length === 0 && _dialogOpen === false) {
138
+ window.removeEventListener("keydown", specialKeyHandler, true);
139
+ window.removeEventListener("click", outsideClickHandler, true);
140
+ }
141
+ }
142
+ }
143
+ var PopupComponent = ({
144
+ children,
145
+ position,
146
+ style
147
+ }) => {
148
+ const className = cx3("hwPopup", "hwPopupContainer", position);
149
+ return createElement("div", { className, style }, children);
150
+ };
151
+ var incrementingKey = 1;
152
+ var PopupService = class _PopupService {
153
+ static showPopup({
154
+ group = "all",
155
+ name = "anon",
156
+ left = 0,
157
+ position = "",
158
+ right = "auto",
159
+ top = 0,
160
+ width = "auto",
161
+ component
162
+ }) {
163
+ if (!component) {
164
+ throw Error(`PopupService showPopup, no component supplied`);
165
+ }
166
+ if (typeof component.props.onClose === "function") {
167
+ _PopupService.onClose = component.props.onClose;
168
+ } else {
169
+ _PopupService.onClose = void 0;
170
+ }
171
+ popupOpened(name);
172
+ document.addEventListener("keydown", _PopupService.escapeKeyListener, true);
173
+ let el = document.body.querySelector(".vuuPopup." + group);
174
+ if (el === null) {
175
+ el = document.createElement("div");
176
+ el.className = "vuuPopup " + group;
177
+ document.body.appendChild(el);
178
+ }
179
+ const style = { width };
180
+ renderPortal(
181
+ createElement(
182
+ PopupComponent,
183
+ { key: incrementingKey++, position, style },
184
+ component
185
+ ),
186
+ el,
187
+ left,
188
+ top,
189
+ () => {
190
+ _PopupService.keepWithinThePage(el, right);
191
+ }
192
+ );
193
+ }
194
+ static escapeKeyListener(evt) {
195
+ if (evt.key === "Escape") {
196
+ _PopupService.hidePopup({ type: "escape", event: evt });
197
+ }
198
+ }
199
+ static hidePopup(reason, name = "anon", group = "all") {
200
+ var _a;
201
+ if (_popups.indexOf(name) !== -1) {
202
+ popupClosed(name);
203
+ const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
204
+ if (popupRoot) {
205
+ ReactDOM2.unmountComponentAtNode(popupRoot);
206
+ }
207
+ }
208
+ document.removeEventListener(
209
+ "keydown",
210
+ _PopupService.escapeKeyListener,
211
+ true
212
+ );
213
+ (_a = _PopupService == null ? void 0 : _PopupService.onClose) == null ? void 0 : _a.call(
214
+ _PopupService,
215
+ reason ? {
216
+ ...reason,
217
+ closedBy: "popup-service"
218
+ } : void 0
219
+ );
220
+ }
221
+ static keepWithinThePage(el, right = "auto") {
222
+ const target = el.querySelector(".vuuPopupContainer > *");
223
+ if (target) {
224
+ const {
225
+ top,
226
+ left,
227
+ width,
228
+ height,
229
+ right: currentRight
230
+ } = target.getBoundingClientRect();
231
+ const w = window.innerWidth;
232
+ const h = window.innerHeight;
233
+ const overflowH = h - (top + height);
234
+ if (overflowH < 0) {
235
+ target.style.top = Math.round(top) + overflowH + "px";
236
+ }
237
+ const overflowW = w - (left + width);
238
+ if (overflowW < 0) {
239
+ target.style.left = Math.round(left) + overflowW + "px";
240
+ }
241
+ if (typeof right === "number" && right !== currentRight) {
242
+ const adjustment = right - currentRight;
243
+ target.style.left = left + adjustment + "px";
244
+ }
245
+ }
246
+ }
247
+ };
248
+ var DialogService = class _DialogService {
249
+ static showDialog(dialog) {
250
+ const containerEl = ".vuuDialog";
251
+ const onClose = dialog.props.onClose;
252
+ dialogOpened();
253
+ ReactDOM2.render(
254
+ React.cloneElement(dialog, {
255
+ container: containerEl,
256
+ onClose: () => {
257
+ _DialogService.closeDialog();
258
+ if (onClose) {
259
+ onClose();
260
+ }
261
+ }
262
+ }),
263
+ document.body.querySelector(containerEl)
264
+ );
265
+ }
266
+ static closeDialog() {
267
+ dialogClosed();
268
+ const dialogRoot = document.body.querySelector(".vuuDialog");
269
+ if (dialogRoot) {
270
+ ReactDOM2.unmountComponentAtNode(dialogRoot);
271
+ }
272
+ }
273
+ };
274
+
275
+ // src/popup/Popup.tsx
276
+ import cx4 from "clsx";
277
+
278
+ // src/popup/useAnchoredPosition.ts
279
+ import { useCallback, useLayoutEffect, useRef, useState } from "react";
280
+
281
+ // src/popup/getPositionRelativeToAnchor.ts
282
+ var getPositionRelativeToAnchor = (anchorElement, placement, offsetLeft, offsetTop, minWidth, dimensions) => {
283
+ const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
284
+ switch (placement) {
285
+ case "below":
286
+ return { left: left + offsetLeft, top: bottom + offsetTop };
287
+ case "right":
288
+ return { left: right + offsetLeft, top: top + offsetTop };
289
+ case "below-center":
290
+ return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };
291
+ case "below-right":
292
+ return { left, minWidth, top: bottom + offsetTop };
293
+ case "below-full-width":
294
+ return {
295
+ left: left + offsetLeft,
296
+ minWidth,
297
+ top: bottom + offsetTop,
298
+ width
299
+ };
300
+ case "center":
301
+ if (dimensions) {
302
+ return {
303
+ left: width / 2 - dimensions.width / 2 + offsetLeft,
304
+ top: height / 2 - dimensions.height / 2 + offsetTop,
305
+ visibility: "visible"
306
+ };
307
+ } else {
308
+ return {
309
+ left: width / 2 + offsetLeft,
310
+ top: height / 2 + offsetTop,
311
+ visibility: "hidden"
312
+ };
313
+ }
314
+ default:
315
+ throw Error(
316
+ `Popup getPositionRelativeToAnchor non-supported placement value ${placement}`
317
+ );
318
+ }
319
+ };
320
+
321
+ // src/popup/useAnchoredPosition.ts
322
+ var useAnchoredPosition = ({
323
+ anchorElement,
324
+ minWidth,
325
+ offsetLeft = 0,
326
+ offsetTop = 0,
327
+ placement,
328
+ position: positionProp
329
+ }) => {
330
+ const popupRef = useRef(null);
331
+ const [position, setPosition] = useState(positionProp);
332
+ useLayoutEffect(() => {
333
+ if (placement === "absolute" && positionProp) {
334
+ setPosition(positionProp);
335
+ } else if (anchorElement.current && placement !== "auto") {
336
+ const dimensions = popupRef.current === null ? void 0 : popupRef.current.getBoundingClientRect();
337
+ const position2 = getPositionRelativeToAnchor(
338
+ anchorElement.current,
339
+ placement,
340
+ offsetLeft,
341
+ offsetTop,
342
+ minWidth,
343
+ dimensions
344
+ );
345
+ setPosition(position2);
346
+ }
347
+ }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);
348
+ const popupCallbackRef = useCallback(
349
+ (el) => {
350
+ popupRef.current = el;
351
+ if (el && placement === "center" && anchorElement.current) {
352
+ const { height, width } = el.getBoundingClientRect();
353
+ setPosition(
354
+ getPositionRelativeToAnchor(
355
+ anchorElement.current,
356
+ placement,
357
+ offsetLeft,
358
+ offsetTop,
359
+ void 0,
360
+ { height, width }
361
+ )
362
+ );
363
+ }
364
+ },
365
+ [anchorElement, offsetLeft, offsetTop, placement]
366
+ );
367
+ return {
368
+ position,
369
+ popupRef: placement === "center" ? popupCallbackRef : void 0
370
+ };
371
+ };
372
+
373
+ // src/popup/Popup.tsx
374
+ import { jsx as jsx2 } from "react/jsx-runtime";
375
+ var PopupComponent2 = ({
376
+ children,
377
+ className,
378
+ anchorElement,
379
+ minWidth,
380
+ placement,
381
+ position: positionProp
382
+ }) => {
383
+ const { popupRef, position } = useAnchoredPosition({
384
+ anchorElement,
385
+ minWidth,
386
+ placement,
387
+ position: positionProp
388
+ });
389
+ return position === void 0 ? null : /* @__PURE__ */ jsx2("div", { className: cx4(`vuuPortal`, className), ref: popupRef, style: position, children });
390
+ };
391
+
392
+ // src/dialog/Dialog.tsx
393
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
394
+ var classBase2 = "vuuDialog";
395
+ var AnchorBody = { current: document.body };
396
+ var EMPTY_PROPS = {};
397
+ var Dialog = ({
398
+ PopupProps = EMPTY_PROPS,
399
+ children,
400
+ className,
401
+ isOpen = false,
402
+ onClose,
403
+ style,
404
+ title,
405
+ hideCloseButton = false,
406
+ ...htmlAttributes
407
+ }) => {
408
+ const {
409
+ anchorElement = AnchorBody,
410
+ offsetLeft = 0,
411
+ offsetTop = 0,
412
+ placement = "auto"
413
+ } = PopupProps;
414
+ const rootRef = useRef2(null);
415
+ const portalRef = useRef2(null);
416
+ const [themeClass, _, dataMode] = useThemeAttributes();
417
+ const { position } = useAnchoredPosition({
418
+ anchorElement,
419
+ offsetLeft,
420
+ offsetTop,
421
+ placement
422
+ });
423
+ const close = useCallback2(() => {
424
+ onClose == null ? void 0 : onClose();
425
+ }, [onClose]);
426
+ useLayoutEffect2(() => {
427
+ if (rootRef.current) {
428
+ if (isOpen) {
429
+ rootRef.current.showModal();
430
+ const { left, top } = rootRef.current.getBoundingClientRect();
431
+ if (portalRef.current) {
432
+ portalRef.current.style.cssText = `left:-${left}px;position:absolute;top:-${top}px;`;
433
+ }
434
+ } else {
435
+ rootRef.current.close();
436
+ }
437
+ if (placement.endsWith("center")) {
438
+ const { width } = rootRef.current.getBoundingClientRect();
439
+ rootRef.current.style.marginLeft = `-${width / 2}px`;
440
+ }
441
+ }
442
+ }, [isOpen, placement]);
443
+ return /* @__PURE__ */ jsxs2(
444
+ "dialog",
445
+ {
446
+ ...htmlAttributes,
447
+ className: cx5(classBase2, themeClass),
448
+ "data-mode": dataMode,
449
+ onClose: close,
450
+ id: "vuu-dialog",
451
+ ref: rootRef,
452
+ style: { ...style, ...position },
453
+ children: [
454
+ /* @__PURE__ */ jsx3(
455
+ DialogHeader,
456
+ {
457
+ hideCloseButton,
458
+ onClose: close,
459
+ title
460
+ }
461
+ ),
462
+ /* @__PURE__ */ jsx3("div", { className: `${classBase2}-body`, children }),
463
+ /* @__PURE__ */ jsx3("div", { id: "vuu-dialog-portal-root", ref: portalRef })
464
+ ]
465
+ }
466
+ );
467
+ };
468
+
469
+ // src/dialog/useDialog.tsx
470
+ import { useCallback as useCallback3, useState as useState2 } from "react";
471
+ import { jsx as jsx4 } from "react/jsx-runtime";
472
+ var useDialog = () => {
473
+ const [dialogState, setDialogState] = useState2();
474
+ const handleClose = useCallback3(() => {
475
+ setDialogState(void 0);
476
+ }, []);
477
+ const dialog = dialogState ? /* @__PURE__ */ jsx4(
478
+ Dialog,
479
+ {
480
+ className: "vuDialog",
481
+ isOpen: true,
482
+ onClose: handleClose,
483
+ style: { maxHeight: 500 },
484
+ title: dialogState.title,
485
+ hideCloseButton: dialogState.hideCloseButton,
486
+ children: dialogState.content
487
+ }
488
+ ) : null;
489
+ return {
490
+ dialog,
491
+ setDialogState
492
+ };
493
+ };
494
+
495
+ // src/menu/ContextMenu.tsx
496
+ import { useId as useId2 } from "@vuu-ui/vuu-utils";
497
+ import { useCallback as useCallback7, useRef as useRef7 } from "react";
498
+
499
+ // src/portal/Portal.tsx
500
+ import { useThemeAttributes as useThemeAttributes2 } from "@vuu-ui/vuu-utils";
501
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState3 } from "react";
502
+ import { createPortal } from "react-dom";
503
+ function getContainer(container) {
504
+ return typeof container === "function" ? container() : container;
505
+ }
506
+ var DEFAULT_ID = ["vuu-dialog-portal-root", "vuu-portal-root"];
507
+ var getFirstAvailableElementById = (id) => {
508
+ if (Array.isArray(id)) {
509
+ for (const i of id) {
510
+ const element = document.getElementById(i);
511
+ if (element) {
512
+ return element;
513
+ }
514
+ }
515
+ } else {
516
+ return document.getElementById(id);
517
+ }
518
+ return null;
519
+ };
520
+ var Portal = ({
521
+ children,
522
+ container: containerProp = document.body,
523
+ id = DEFAULT_ID,
524
+ onRender,
525
+ open = true,
526
+ themeAttributes
527
+ }) => {
528
+ var _a;
529
+ const [mounted, setMounted] = useState3(false);
530
+ const portalRef = useRef3(null);
531
+ const container = (_a = getContainer(containerProp)) != null ? _a : document.body;
532
+ const [themeClass, densityClass, dataMode] = useThemeAttributes2(themeAttributes);
533
+ useLayoutEffect3(() => {
534
+ const root = getFirstAvailableElementById(id);
535
+ if (root) {
536
+ portalRef.current = root;
537
+ } else {
538
+ portalRef.current = document.createElement("div");
539
+ portalRef.current.id = typeof id === "string" ? id : id.length > 0 ? id.at(-1) : "vuu-portal-root";
540
+ }
541
+ const el = portalRef.current;
542
+ if (!container.contains(el)) {
543
+ container.appendChild(el);
544
+ }
545
+ el.classList.add(themeClass, densityClass);
546
+ el.dataset.mode = dataMode;
547
+ setMounted(true);
548
+ }, [id, container, themeClass, densityClass, dataMode]);
549
+ useLayoutEffect3(() => {
550
+ requestAnimationFrame(() => {
551
+ onRender == null ? void 0 : onRender();
552
+ });
553
+ }, [onRender]);
554
+ if (open && mounted && portalRef.current && children) {
555
+ return createPortal(children, portalRef.current);
556
+ }
557
+ return null;
558
+ };
559
+
560
+ // src/menu/MenuList.tsx
561
+ import cx6 from "clsx";
562
+ import React3, {
563
+ useLayoutEffect as useLayoutEffect4,
564
+ useMemo as useMemo3,
565
+ useRef as useRef5
566
+ } from "react";
567
+ import { useId } from "@vuu-ui/vuu-utils";
568
+
569
+ // src/menu/use-items-with-ids-next.ts
570
+ import React2, { useCallback as useCallback4, useMemo } from "react";
571
+ var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
572
+ var getLabelFromChildren = (children) => {
573
+ if (Array.isArray(children) && isMenuItemLabel(children[0])) {
574
+ return children[0];
575
+ }
576
+ };
577
+ var assignId = (child, path, group, hasSeparator = false) => {
578
+ const {
579
+ props: { children }
580
+ } = child;
581
+ return {
582
+ childWithId: React2.cloneElement(child, {
583
+ hasSeparator,
584
+ id: `${path}`,
585
+ key: path,
586
+ children: group ? getLabelFromChildren(children) : children
587
+ }),
588
+ grandChildren: group ? children : void 0
589
+ };
590
+ };
591
+ var useItemsWithIdsNext = (childrenProp, rootId) => {
592
+ const normalizeChildren = useCallback4(() => {
593
+ const collectChildren = (children, path = rootId, menus2 = {}, actions2 = {}) => {
594
+ const list = menus2[path] = [];
595
+ let idx = 0;
596
+ let hasSeparator = false;
597
+ React2.Children.forEach(children, (child) => {
598
+ if (isMenuItemLabel(child)) {
599
+ } else if (child.type === Separator) {
600
+ hasSeparator = true;
601
+ } else {
602
+ const hasChildItems = isMenuItemGroup(child);
603
+ const childPath = `${path}-${idx}`;
604
+ const {
605
+ props: { action, options }
606
+ } = child;
607
+ const { childWithId, grandChildren } = assignId(
608
+ child,
609
+ childPath,
610
+ hasChildItems,
611
+ hasSeparator
612
+ );
613
+ list.push(childWithId);
614
+ if (grandChildren) {
615
+ collectChildren(grandChildren, childPath, menus2, actions2);
616
+ } else {
617
+ actions2[childPath] = { action, options };
618
+ }
619
+ idx += 1;
620
+ hasSeparator = false;
621
+ }
622
+ });
623
+ return [menus2, actions2];
624
+ };
625
+ return collectChildren(childrenProp);
626
+ }, [rootId, childrenProp]);
627
+ const [menus, actions] = useMemo(
628
+ () => normalizeChildren(),
629
+ [normalizeChildren]
630
+ );
631
+ return [menus, actions];
632
+ };
633
+
634
+ // src/menu/use-keyboard-navigation.ts
635
+ import {
636
+ useCallback as useCallback5,
637
+ useMemo as useMemo2,
638
+ useRef as useRef4,
639
+ useState as useState4
640
+ } from "react";
641
+
642
+ // src/menu/utils.ts
643
+ var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
644
+ var hasPopup = (el, idx) => {
645
+ var _a;
646
+ return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-index='${idx}'][aria-haspopup='true']`) !== null;
647
+ };
648
+
649
+ // src/menu/key-code.ts
650
+ function union(set1, ...sets) {
651
+ const result = new Set(set1);
652
+ for (const set of sets) {
653
+ for (const element of set) {
654
+ result.add(element);
655
+ }
656
+ }
657
+ return result;
658
+ }
659
+ var Enter = "Enter";
660
+ var Delete = "Delete";
661
+ var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
662
+ var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
663
+ var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
664
+ var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
665
+ var horizontalNavigationKeys = /* @__PURE__ */ new Set([
666
+ "Home",
667
+ "End",
668
+ "ArrowRight",
669
+ "ArrowLeft"
670
+ ]);
671
+ var functionKeys = /* @__PURE__ */ new Set([
672
+ "F1",
673
+ "F2",
674
+ "F3",
675
+ "F4",
676
+ "F5",
677
+ "F6",
678
+ "F7",
679
+ "F8",
680
+ "F9",
681
+ "F10",
682
+ "F11",
683
+ "F12"
684
+ ]);
685
+ var specialKeys = union(
686
+ actionKeys,
687
+ horizontalNavigationKeys,
688
+ verticalNavigationKeys,
689
+ arrowLeftRightKeys,
690
+ functionKeys,
691
+ focusKeys
692
+ );
693
+ var isNavigationKey = ({ key }, orientation = "vertical") => {
694
+ const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
695
+ return navigationKeys.has(key);
696
+ };
697
+
698
+ // src/menu/use-keyboard-navigation.ts
699
+ import { isValidNumber } from "@vuu-ui/vuu-utils";
700
+ var useKeyboardNavigation = ({
701
+ autoHighlightFirstItem = false,
702
+ count,
703
+ defaultHighlightedIdx,
704
+ highlightedIndex: highlightedIndexProp,
705
+ onActivate,
706
+ onHighlight,
707
+ // onKeyDown,
708
+ onCloseMenu,
709
+ onOpenMenu
710
+ }) => {
711
+ var _a;
712
+ if (isValidNumber(highlightedIndexProp) && isValidNumber(defaultHighlightedIdx)) {
713
+ throw Error(
714
+ "useKeyboardNavigation do not pass values for both highlightedIndex and defaultHighlightedIdx"
715
+ );
716
+ }
717
+ const controlledHighlighting = isValidNumber(highlightedIndexProp);
718
+ const highlightedIndexRef = useRef4(
719
+ (_a = defaultHighlightedIdx != null ? defaultHighlightedIdx : highlightedIndexProp) != null ? _a : autoHighlightFirstItem ? 0 : -1
720
+ );
721
+ const [, forceRender] = useState4(null);
722
+ const setHighlightedIdx = useCallback5(
723
+ (idx) => {
724
+ highlightedIndexRef.current = idx;
725
+ onHighlight == null ? void 0 : onHighlight(idx);
726
+ forceRender({});
727
+ },
728
+ [onHighlight]
729
+ );
730
+ const setHighlightedIndex = useCallback5(
731
+ (idx) => {
732
+ if (idx !== highlightedIndexRef.current) {
733
+ if (!controlledHighlighting) {
734
+ setHighlightedIdx(idx);
735
+ }
736
+ }
737
+ },
738
+ [controlledHighlighting, setHighlightedIdx]
739
+ );
740
+ const keyBoardNavigation = useRef4(true);
741
+ const ignoreFocus = useRef4(false);
742
+ const setIgnoreFocus = (value) => ignoreFocus.current = value;
743
+ const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
744
+ const navigateChildldItems = useCallback5(
745
+ (e) => {
746
+ const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
747
+ if (nextIdx !== highlightedIndexRef.current) {
748
+ setHighlightedIndex(nextIdx);
749
+ }
750
+ },
751
+ [count, setHighlightedIndex]
752
+ );
753
+ const handleKeyDown = useCallback5(
754
+ (e) => {
755
+ if (isNavigationKey(e)) {
756
+ e.preventDefault();
757
+ e.stopPropagation();
758
+ keyBoardNavigation.current = true;
759
+ navigateChildldItems(e);
760
+ } else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
761
+ const menuEl = e.target;
762
+ const menuItemEl = menuEl.querySelector(
763
+ `:scope > [data-index='${highlightedIndex}']`
764
+ );
765
+ if (menuItemEl) {
766
+ onOpenMenu == null ? void 0 : onOpenMenu(menuItemEl, true);
767
+ }
768
+ } else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
769
+ onCloseMenu(e, "close-child-menu");
770
+ } else if (e.key === "Enter") {
771
+ e.preventDefault();
772
+ e.stopPropagation();
773
+ onActivate && onActivate(highlightedIndex);
774
+ } else if (e.key === "Tab") {
775
+ onCloseMenu(e, "tab-away");
776
+ }
777
+ },
778
+ [
779
+ highlightedIndex,
780
+ navigateChildldItems,
781
+ onActivate,
782
+ onCloseMenu,
783
+ onOpenMenu
784
+ ]
785
+ );
786
+ const listProps = useMemo2(
787
+ () => ({
788
+ onFocus: () => {
789
+ if (highlightedIndex === -1) {
790
+ setHighlightedIdx(0);
791
+ }
792
+ },
793
+ onKeyDown: handleKeyDown,
794
+ onMouseDownCapture: () => {
795
+ keyBoardNavigation.current = false;
796
+ setIgnoreFocus(true);
797
+ },
798
+ // onMouseEnter would seem less expensive but it misses some cases
799
+ onMouseMove: () => {
800
+ if (keyBoardNavigation.current) {
801
+ keyBoardNavigation.current = false;
802
+ }
803
+ },
804
+ onMouseLeave: () => {
805
+ keyBoardNavigation.current = true;
806
+ setIgnoreFocus(false);
807
+ setHighlightedIndex(-1);
808
+ }
809
+ }),
810
+ [handleKeyDown, highlightedIndex, setHighlightedIdx, setHighlightedIndex]
811
+ );
812
+ return {
813
+ focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
814
+ controlledHighlighting,
815
+ highlightedIndex,
816
+ setHighlightedIndex,
817
+ // keyBoardNavigation,
818
+ listProps,
819
+ setIgnoreFocus
820
+ };
821
+ };
822
+ function nextItemIdx(count, key, idx) {
823
+ if (key === "ArrowUp") {
824
+ if (idx > 0) {
825
+ return idx - 1;
826
+ } else {
827
+ return idx;
828
+ }
829
+ } else {
830
+ if (idx === null) {
831
+ return 0;
832
+ } else if (idx === count - 1) {
833
+ return idx;
834
+ } else {
835
+ return idx + 1;
836
+ }
837
+ }
838
+ }
839
+
840
+ // src/menu/MenuList.tsx
841
+ import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
842
+ var classBase3 = "vuuMenuList";
843
+ var Separator = () => /* @__PURE__ */ jsx5("li", { className: "vuuMenuItem-divider" });
844
+ var MenuItemGroup = () => null;
845
+ var MenuItem = ({
846
+ children,
847
+ idx,
848
+ options,
849
+ ...props
850
+ }) => {
851
+ return /* @__PURE__ */ jsx5("div", { ...props, children });
852
+ };
853
+ var MenuItemLabel = ({ children }) => /* @__PURE__ */ jsx5(Fragment, { children });
854
+ MenuItemLabel.displayName = "MenuItemLabel";
855
+ MenuItem.Label = MenuItemLabel;
856
+ var getDisplayName = (item) => React3.isValidElement(item) && typeof item.type !== "string" && "displayName" in item.type ? item.type.displayName : void 0;
857
+ var isMenuItemLabel = (item) => getDisplayName(item) === "MenuItemLabel";
858
+ var hasIcon = (child) => child.props["data-icon"];
859
+ var MenuList = ({
860
+ activatedByKeyboard,
861
+ childMenuShowing,
862
+ children,
863
+ className,
864
+ defaultHighlightedIdx,
865
+ highlightedIdx: highlightedIdxProp,
866
+ id: idProp,
867
+ isRoot: isRoot2,
868
+ listItemProps,
869
+ onHighlightMenuItem,
870
+ onActivate,
871
+ onCloseMenu,
872
+ openMenu: onOpenMenu,
873
+ ...props
874
+ }) => {
875
+ const id = useId(idProp);
876
+ const root = useRef5(null);
877
+ const mapIdxToId = useMemo3(() => /* @__PURE__ */ new Map(), []);
878
+ const handleActivate = (idx) => {
879
+ var _a;
880
+ const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-index='${idx}']`);
881
+ (el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
882
+ };
883
+ const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
884
+ count: React3.Children.count(children),
885
+ defaultHighlightedIdx,
886
+ highlightedIndex: highlightedIdxProp,
887
+ onActivate: handleActivate,
888
+ onHighlight: onHighlightMenuItem,
889
+ onOpenMenu,
890
+ onCloseMenu
891
+ });
892
+ const appliedFocusVisible = childMenuShowing == void 0 ? focusVisible : -1;
893
+ useLayoutEffect4(() => {
894
+ var _a;
895
+ if (childMenuShowing === void 0 && activatedByKeyboard) {
896
+ (_a = root.current) == null ? void 0 : _a.focus();
897
+ }
898
+ }, [activatedByKeyboard, childMenuShowing]);
899
+ const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
900
+ function renderContent() {
901
+ const propsCommonToAllListItems = {
902
+ ...listItemProps,
903
+ role: "menuitem"
904
+ };
905
+ const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
906
+ /* @__PURE__ */ jsx5(
907
+ "span",
908
+ {
909
+ className: "vuuIconContainer",
910
+ "data-icon": iconName
911
+ },
912
+ "icon"
913
+ )
914
+ ].concat(childElement) : childElement;
915
+ function addClonedChild(list, child, idx, withIcon) {
916
+ var _a;
917
+ const {
918
+ children: children2,
919
+ className: className2,
920
+ "data-icon": iconName,
921
+ id: itemId,
922
+ hasSeparator,
923
+ label,
924
+ ...props2
925
+ } = child.props;
926
+ const hasSubMenu = isMenuItemGroup(child);
927
+ const subMenuShowing = hasSubMenu && childMenuShowing === itemId;
928
+ const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
929
+ list.push(
930
+ /* @__PURE__ */ jsx5(
931
+ MenuItem,
932
+ {
933
+ ...props2,
934
+ ...propsCommonToAllListItems,
935
+ ...getMenuItemProps(
936
+ itemId,
937
+ idx,
938
+ (_a = child.key) != null ? _a : itemId,
939
+ highlightedIndex,
940
+ appliedFocusVisible,
941
+ className2,
942
+ hasSeparator
943
+ ),
944
+ "aria-controls": ariaControls,
945
+ "aria-haspopup": hasSubMenu || void 0,
946
+ "aria-expanded": subMenuShowing || void 0,
947
+ children: hasSubMenu ? maybeIcon(label != null ? label : children2, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
948
+ }
949
+ )
950
+ );
951
+ }
952
+ const listItems = [];
953
+ if (children.length > 0) {
954
+ const withIcon = children.some(hasIcon);
955
+ children.forEach((child, idx) => {
956
+ addClonedChild(listItems, child, idx, withIcon);
957
+ });
958
+ }
959
+ return listItems;
960
+ }
961
+ return /* @__PURE__ */ jsx5(
962
+ "div",
963
+ {
964
+ ...props,
965
+ ...listProps,
966
+ "aria-activedescendant": getActiveDescendant(),
967
+ className: cx6(classBase3, className, {
968
+ [`${classBase3}-childMenuShowing`]: childMenuShowing !== void 0
969
+ }),
970
+ "data-root": isRoot2 || void 0,
971
+ id,
972
+ ref: root,
973
+ role: "menu",
974
+ children: renderContent()
975
+ }
976
+ );
977
+ };
978
+ var getMenuItemProps = (itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
979
+ id: `menuitem-${itemId}`,
980
+ key: key != null ? key : idx,
981
+ "data-index": idx,
982
+ className: cx6("vuuMenuItem", className, {
983
+ "vuuMenuItem-separator": hasSeparator,
984
+ vuuHighlighted: idx === highlightedIdx,
985
+ focusVisible: focusVisible === idx
986
+ })
987
+ });
988
+ MenuList.displayName = "MenuList";
989
+
990
+ // src/menu/use-cascade.ts
991
+ import {
992
+ useCallback as useCallback6,
993
+ useMemo as useMemo4,
994
+ useRef as useRef6,
995
+ useState as useState5
996
+ } from "react";
997
+
998
+ // src/menu/list-dom-utils.ts
999
+ var closestListItem = (el) => el == null ? void 0 : el.closest("[data-index],[aria-posinset]");
1000
+
1001
+ // src/menu/use-cascade.ts
1002
+ var nudge = (menus, distance, pos) => {
1003
+ return menus.map(
1004
+ (m, i) => i === menus.length - 1 ? {
1005
+ ...m,
1006
+ [pos]: m[pos] - distance
1007
+ } : m
1008
+ );
1009
+ };
1010
+ var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
1011
+ var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
1012
+ var flipSides = (id, menus) => {
1013
+ const [parentMenu, menu] = menus.slice(-2);
1014
+ const el = document.getElementById(`${id}-${menu.id}`);
1015
+ if (el === null) {
1016
+ throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
1017
+ }
1018
+ const { width } = el.getBoundingClientRect();
1019
+ return menus.map(
1020
+ (m) => m === menu ? {
1021
+ ...m,
1022
+ left: parentMenu.left - (width - 2)
1023
+ } : m
1024
+ );
1025
+ };
1026
+ var getPosition = (el, openMenus) => {
1027
+ const [{ left, top: menuTop }] = openMenus.slice(-1);
1028
+ const { offsetWidth: width, offsetTop: top } = el;
1029
+ return { left: left + width, top: top + menuTop };
1030
+ };
1031
+ var getHostMenuId = (id, rootId) => {
1032
+ const pos = id.lastIndexOf("-");
1033
+ if (id.startsWith("menuitem")) {
1034
+ return pos > -1 ? id.slice(9, pos) : rootId;
1035
+ } else {
1036
+ return pos > -1 ? id.slice(0, pos) : rootId;
1037
+ }
1038
+ };
1039
+ var getTargetMenuId = (id) => id.slice(9);
1040
+ var getMenuItemDetails = ({ ariaExpanded, ariaHasPopup, id }, rootId) => {
1041
+ if (id.startsWith("menuitem")) {
1042
+ return {
1043
+ hostMenuId: getHostMenuId(id, rootId),
1044
+ targetMenuId: getTargetMenuId(id),
1045
+ menuItemId: id,
1046
+ isGroup: ariaHasPopup === "true",
1047
+ isOpen: ariaExpanded === "true"
1048
+ };
1049
+ } else {
1050
+ throw Error(`getMenuItemDetails #${id} is not a menuitem`);
1051
+ }
1052
+ };
1053
+ var useCascade = ({
1054
+ id: rootId,
1055
+ onActivate,
1056
+ onMouseEnterItem,
1057
+ position: { x: posX, y: posY }
1058
+ }) => {
1059
+ const [, forceRefresh] = useState5({});
1060
+ const openMenus = useRef6([
1061
+ { id: rootId, left: posX, top: posY }
1062
+ ]);
1063
+ const menuIsOpen = useCallback6(
1064
+ (menuId) => openMenus.current.findIndex((menu) => menu.id === menuId) !== -1,
1065
+ []
1066
+ );
1067
+ const getOpenMenuStatus = useCallback6((menuId) => {
1068
+ const state = menuState.current[menuId];
1069
+ if (state === void 0) {
1070
+ throw Error(`getOpenMenuState no entry for menu ${menuId}`);
1071
+ }
1072
+ return state;
1073
+ }, []);
1074
+ const setOpenMenus = useCallback6((menus) => {
1075
+ openMenus.current = menus;
1076
+ forceRefresh({});
1077
+ }, []);
1078
+ const menuOpenPendingTimeout = useRef6();
1079
+ const menuClosePendingTimeout = useRef6();
1080
+ const menuState = useRef6({ [rootId]: "no-popup" });
1081
+ const openMenu = useCallback6(
1082
+ (hostMenuId = rootId, targetMenuId, itemId = null) => {
1083
+ if (hostMenuId === rootId && itemId === null) {
1084
+ setOpenMenus([{ id: rootId, left: posX, top: posY }]);
1085
+ } else {
1086
+ menuState.current[hostMenuId] = "popup-open";
1087
+ const el = document.getElementById(itemId);
1088
+ if (el !== null) {
1089
+ const { left, top } = getPosition(el, openMenus.current);
1090
+ setOpenMenus(
1091
+ openMenus.current.concat({ id: targetMenuId, left, top })
1092
+ );
1093
+ } else {
1094
+ throw Error(`openMenu no menuItem ${itemId}`);
1095
+ }
1096
+ }
1097
+ },
1098
+ [rootId, posX, posY, setOpenMenus]
1099
+ );
1100
+ const closeMenu = useCallback6(
1101
+ (menuId) => {
1102
+ if (menuId === rootId) {
1103
+ setOpenMenus([]);
1104
+ } else {
1105
+ const menus = openMenus.current.slice();
1106
+ const lastMenu = menus.pop();
1107
+ menuState.current[lastMenu.id] = "no-popup";
1108
+ const parentMenu = menus.at(-1);
1109
+ if (parentMenu) {
1110
+ menuState.current[parentMenu.id] = "no-popup";
1111
+ }
1112
+ setOpenMenus(menus);
1113
+ }
1114
+ },
1115
+ [rootId, setOpenMenus]
1116
+ );
1117
+ const closeMenus = useCallback6(
1118
+ (menuItemId) => {
1119
+ const menus = openMenus.current.slice();
1120
+ const menuItemMenuId = menuItemId.slice(9);
1121
+ let { id: lastMenuId } = menus.at(-1);
1122
+ while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) {
1123
+ const parentMenuId = getHostMenuId(lastMenuId, rootId);
1124
+ menus.pop();
1125
+ menuState.current[lastMenuId] = "no-popup";
1126
+ menuState.current[parentMenuId] = "no-popup";
1127
+ ({ id: lastMenuId } = menus[menus.length - 1]);
1128
+ }
1129
+ if (menus.length < openMenus.current.length) {
1130
+ setOpenMenus(menus);
1131
+ }
1132
+ },
1133
+ [rootId, setOpenMenus]
1134
+ );
1135
+ const clearAnyScheduledOpenTasks = useCallback6(() => {
1136
+ if (menuOpenPendingTimeout.current) {
1137
+ clearTimeout(menuOpenPendingTimeout.current);
1138
+ menuOpenPendingTimeout.current = void 0;
1139
+ }
1140
+ }, []);
1141
+ const scheduleOpen = useCallback6(
1142
+ (hostMenuId, targetMenuId, menuItemId, delay = 300) => {
1143
+ clearAnyScheduledOpenTasks();
1144
+ menuOpenPendingTimeout.current = window.setTimeout(() => {
1145
+ closeMenus(menuItemId);
1146
+ menuState.current[hostMenuId] = "popup-open";
1147
+ menuState.current[targetMenuId] = "no-popup";
1148
+ openMenu(hostMenuId, targetMenuId, menuItemId);
1149
+ }, delay);
1150
+ },
1151
+ [clearAnyScheduledOpenTasks, closeMenus, openMenu]
1152
+ );
1153
+ const scheduleClose = useCallback6(
1154
+ (hostMenuId, openMenuId, itemId) => {
1155
+ menuState.current[openMenuId] = "pending-close";
1156
+ menuClosePendingTimeout.current = window.setTimeout(() => {
1157
+ closeMenus(itemId);
1158
+ }, 400);
1159
+ },
1160
+ [closeMenus]
1161
+ );
1162
+ const handleRender = useCallback6(() => {
1163
+ const { current: menus } = openMenus;
1164
+ const menu = menus.at(-1);
1165
+ const el = menu ? document.getElementById(menu.id) : void 0;
1166
+ if (el) {
1167
+ const { right, bottom } = el.getBoundingClientRect();
1168
+ const { clientHeight, clientWidth } = document.body;
1169
+ if (right > clientWidth) {
1170
+ const newMenus = menus.length > 1 ? flipSides(rootId, menus) : nudgeLeft(menus, right - clientWidth);
1171
+ setOpenMenus(newMenus);
1172
+ } else if (bottom > clientHeight) {
1173
+ const newMenus = nudgeUp(menus, bottom - clientHeight);
1174
+ setOpenMenus(newMenus);
1175
+ }
1176
+ if (typeof el.tabIndex === "number") {
1177
+ el.focus();
1178
+ }
1179
+ }
1180
+ }, [rootId, setOpenMenus]);
1181
+ const triggerChildMenu = useCallback6(
1182
+ (menuItemEl, immediate = false) => {
1183
+ const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId);
1184
+ const {
1185
+ current: { [hostMenuId]: state }
1186
+ } = menuState;
1187
+ const delay = immediate ? 0 : void 0;
1188
+ if (state === "no-popup" && isGroup) {
1189
+ menuState.current[hostMenuId] = "popup-pending";
1190
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1191
+ } else if (state === "popup-pending" && !isGroup) {
1192
+ menuState.current[hostMenuId] = "no-popup";
1193
+ clearTimeout(menuOpenPendingTimeout.current);
1194
+ menuOpenPendingTimeout.current = void 0;
1195
+ } else if (state === "popup-pending" && isGroup) {
1196
+ clearTimeout(menuOpenPendingTimeout.current);
1197
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1198
+ } else if (state === "popup-open") {
1199
+ if (menuIsOpen(targetMenuId)) {
1200
+ const menuStatus = getOpenMenuStatus(targetMenuId);
1201
+ closeMenus(menuItemId);
1202
+ switch (menuStatus) {
1203
+ case "pending-close":
1204
+ clearTimeout(menuClosePendingTimeout.current);
1205
+ menuClosePendingTimeout.current = void 0;
1206
+ menuState.current[targetMenuId] = "no-popup";
1207
+ clearAnyScheduledOpenTasks();
1208
+ break;
1209
+ default:
1210
+ }
1211
+ } else {
1212
+ const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2);
1213
+ if (parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close") {
1214
+ scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId);
1215
+ if (isGroup && !isOpen) {
1216
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1217
+ }
1218
+ } else if (parentOfLastOpenedMenu.id === hostMenuId && isGroup && menuItemId !== lastOpenedMenu.id && menuState.current[lastOpenedMenu.id] === "pending-close") {
1219
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1220
+ } else if (isGroup) {
1221
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1222
+ } else if (!(menuState.current[lastOpenedMenu.id] === "pending-close")) {
1223
+ closeMenus(menuItemId);
1224
+ }
1225
+ }
1226
+ }
1227
+ if (state === "pending-close") {
1228
+ clearAnyScheduledOpenTasks();
1229
+ clearTimeout(menuClosePendingTimeout.current);
1230
+ menuClosePendingTimeout.current = void 0;
1231
+ menuState.current[hostMenuId] = "popup-open";
1232
+ }
1233
+ },
1234
+ [
1235
+ clearAnyScheduledOpenTasks,
1236
+ closeMenus,
1237
+ getOpenMenuStatus,
1238
+ menuIsOpen,
1239
+ rootId,
1240
+ scheduleClose,
1241
+ scheduleOpen
1242
+ ]
1243
+ );
1244
+ const listItemProps = useMemo4(
1245
+ () => ({
1246
+ onMouseEnter: (evt) => {
1247
+ const menuItemEl = closestListItem(evt.target);
1248
+ triggerChildMenu(menuItemEl);
1249
+ onMouseEnterItem(evt, menuItemEl.id);
1250
+ },
1251
+ onClick: (evt) => {
1252
+ const listItemEl = closestListItem(evt.target);
1253
+ const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId);
1254
+ if (isGroup) {
1255
+ triggerChildMenu(listItemEl);
1256
+ } else {
1257
+ onActivate(menuItemId);
1258
+ }
1259
+ }
1260
+ }),
1261
+ [onActivate, onMouseEnterItem, rootId, triggerChildMenu]
1262
+ );
1263
+ return {
1264
+ closeMenu,
1265
+ handleRender,
1266
+ listItemProps,
1267
+ openMenu: triggerChildMenu,
1268
+ openMenus: openMenus.current
1269
+ };
1270
+ };
1271
+
1272
+ // src/menu/ContextMenu.tsx
1273
+ import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1274
+ import { createElement as createElement2 } from "react";
1275
+ var noop = () => void 0;
1276
+ var ContextMenu = ({
1277
+ PortalProps: PortalProps2,
1278
+ activatedByKeyboard,
1279
+ children: childrenProp,
1280
+ className,
1281
+ id: idProp,
1282
+ onClose = () => void 0,
1283
+ position = { x: 0, y: 0 },
1284
+ style,
1285
+ ...menuListProps
1286
+ }) => {
1287
+ const id = useId2(idProp);
1288
+ const closeMenuRef = useRef7(noop);
1289
+ const [menus, actions] = useItemsWithIdsNext(childrenProp, id);
1290
+ const navigatingWithKeyboard = useRef7(activatedByKeyboard);
1291
+ const handleMouseEnterItem = useCallback7(() => {
1292
+ navigatingWithKeyboard.current = false;
1293
+ }, []);
1294
+ const handleActivate = useCallback7(
1295
+ (menuItemId) => {
1296
+ const actionId = menuItemId.slice(9);
1297
+ const { action, options } = actions[actionId];
1298
+ closeMenuRef.current(id);
1299
+ onClose({
1300
+ type: "menu-action",
1301
+ menuId: action,
1302
+ options
1303
+ });
1304
+ },
1305
+ [actions, id, onClose]
1306
+ );
1307
+ const {
1308
+ closeMenu,
1309
+ listItemProps,
1310
+ openMenu: onOpenMenu,
1311
+ openMenus,
1312
+ handleRender
1313
+ } = useCascade({
1314
+ // FIXME
1315
+ id: `${id}`,
1316
+ onActivate: handleActivate,
1317
+ onMouseEnterItem: handleMouseEnterItem,
1318
+ position
1319
+ });
1320
+ closeMenuRef.current = closeMenu;
1321
+ const handleCloseMenu = useCallback7(
1322
+ (evt, reason) => {
1323
+ navigatingWithKeyboard.current = true;
1324
+ closeMenu();
1325
+ if (reason === "tab-away") {
1326
+ onClose({
1327
+ event: evt,
1328
+ type: "tab-away"
1329
+ });
1330
+ }
1331
+ },
1332
+ [closeMenu, onClose]
1333
+ );
1334
+ const handleHighlightMenuItem = () => {
1335
+ };
1336
+ const lastMenu = openMenus.length - 1;
1337
+ const getChildMenuId = (i) => {
1338
+ if (i >= lastMenu) {
1339
+ return void 0;
1340
+ } else {
1341
+ const { id: id2 } = openMenus[i + 1];
1342
+ return id2;
1343
+ }
1344
+ };
1345
+ return /* @__PURE__ */ jsx6(Fragment2, { children: openMenus.map(({ id: menuId, left, top }, i, all) => {
1346
+ const childMenuId = getChildMenuId(i);
1347
+ return /* @__PURE__ */ createElement2(Portal, { ...PortalProps2, key: i, onRender: handleRender }, /* @__PURE__ */ jsx6(
1348
+ PopupComponent2,
1349
+ {
1350
+ anchorElement: { current: document.body },
1351
+ placement: "absolute",
1352
+ position: { left, top },
1353
+ children: /* @__PURE__ */ createElement2(
1354
+ MenuList,
1355
+ {
1356
+ ...menuListProps,
1357
+ activatedByKeyboard: navigatingWithKeyboard.current,
1358
+ childMenuShowing: childMenuId,
1359
+ className,
1360
+ id: menuId,
1361
+ isRoot: i === 0,
1362
+ key: i,
1363
+ listItemProps,
1364
+ onActivate: handleActivate,
1365
+ onHighlightMenuItem: handleHighlightMenuItem,
1366
+ onCloseMenu: handleCloseMenu,
1367
+ openMenu: onOpenMenu,
1368
+ style,
1369
+ tabIndex: i === all.length - 1 ? 0 : void 0
1370
+ },
1371
+ menus[menuId]
1372
+ )
1373
+ }
1374
+ ));
1375
+ }) });
1376
+ };
1377
+ ContextMenu.displayName = "ContextMenu";
1378
+
1379
+ // src/menu/context-menu-provider.tsx
1380
+ import { createContext, useCallback as useCallback8, useMemo as useMemo5 } from "react";
1381
+ import { jsx as jsx7 } from "react/jsx-runtime";
1382
+ var ContextMenuContext = createContext(
1383
+ null
1384
+ );
1385
+ var Provider = ({
1386
+ children,
1387
+ context,
1388
+ menuActionHandler,
1389
+ menuBuilder
1390
+ }) => {
1391
+ const menuBuilders = useMemo5(() => {
1392
+ if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
1393
+ return context.menuBuilders.concat(menuBuilder);
1394
+ } else if (menuBuilder) {
1395
+ return [menuBuilder];
1396
+ } else {
1397
+ return (context == null ? void 0 : context.menuBuilders) || [];
1398
+ }
1399
+ }, [context, menuBuilder]);
1400
+ const handleMenuAction = useCallback8(
1401
+ (reason) => {
1402
+ var _a;
1403
+ if (menuActionHandler == null ? void 0 : menuActionHandler(reason)) {
1404
+ return true;
1405
+ }
1406
+ if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, reason)) {
1407
+ return true;
1408
+ }
1409
+ },
1410
+ [context, menuActionHandler]
1411
+ );
1412
+ return /* @__PURE__ */ jsx7(
1413
+ ContextMenuContext.Provider,
1414
+ {
1415
+ value: {
1416
+ menuActionHandler: handleMenuAction,
1417
+ menuBuilders
1418
+ },
1419
+ children
1420
+ }
1421
+ );
1422
+ };
1423
+ var ContextMenuProvider = ({
1424
+ children,
1425
+ label,
1426
+ menuActionHandler,
1427
+ menuBuilder
1428
+ }) => {
1429
+ return /* @__PURE__ */ jsx7(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ jsx7(
1430
+ Provider,
1431
+ {
1432
+ context: parentContext,
1433
+ label,
1434
+ menuActionHandler,
1435
+ menuBuilder,
1436
+ children
1437
+ }
1438
+ ) });
1439
+ };
1440
+
1441
+ // src/menu/useContextMenu.tsx
1442
+ import {
1443
+ isGroupMenuItemDescriptor,
1444
+ useThemeAttributes as useThemeAttributes3
1445
+ } from "@vuu-ui/vuu-utils";
1446
+ import { cloneElement, useCallback as useCallback9, useContext, useMemo as useMemo6 } from "react";
1447
+ import { jsx as jsx8 } from "react/jsx-runtime";
1448
+ var useContextMenu = (menuBuilder, menuActionHandler) => {
1449
+ const ctx = useContext(ContextMenuContext);
1450
+ const [themeClass, densityClass, dataMode] = useThemeAttributes3();
1451
+ const themeAttributes = useMemo6(
1452
+ () => ({
1453
+ themeClass,
1454
+ densityClass,
1455
+ dataMode
1456
+ }),
1457
+ [dataMode, densityClass, themeClass]
1458
+ );
1459
+ const buildMenuOptions = useCallback9(
1460
+ (menuBuilders, location, options) => {
1461
+ let results = [];
1462
+ for (const menuBuilder2 of menuBuilders) {
1463
+ results = results.concat(menuBuilder2(location, options));
1464
+ }
1465
+ return results;
1466
+ },
1467
+ []
1468
+ );
1469
+ const handleShowContextMenu = useCallback9(
1470
+ (e, location, { ContextMenuProps: ContextMenuProps2, contextMenu, ...options }) => {
1471
+ var _a, _b;
1472
+ (_a = e.stopPropagation) == null ? void 0 : _a.call(e);
1473
+ (_b = e.preventDefault) == null ? void 0 : _b.call(e);
1474
+ if (contextMenu) {
1475
+ return showContextMenuComponent(
1476
+ {
1477
+ x: e.clientX,
1478
+ y: e.clientY
1479
+ },
1480
+ contextMenu
1481
+ );
1482
+ }
1483
+ const menuBuilders = [];
1484
+ if (menuBuilder) {
1485
+ menuBuilders.push(menuBuilder);
1486
+ }
1487
+ if (ctx && Array.isArray(ctx == null ? void 0 : ctx.menuBuilders) && ctx.menuBuilders.length > 0) {
1488
+ menuBuilders.push(...ctx.menuBuilders);
1489
+ }
1490
+ if (menuBuilders.length > 0) {
1491
+ const menuItemDescriptors = buildMenuOptions(
1492
+ menuBuilders,
1493
+ location,
1494
+ options
1495
+ );
1496
+ const menuHandler = (action) => {
1497
+ if ((menuActionHandler == null ? void 0 : menuActionHandler(action)) === true) {
1498
+ return true;
1499
+ } else {
1500
+ return ctx == null ? void 0 : ctx.menuActionHandler(action);
1501
+ }
1502
+ };
1503
+ if (menuItemDescriptors.length && menuHandler) {
1504
+ showContextMenu(e, menuItemDescriptors, menuHandler, {
1505
+ PortalProps: {
1506
+ themeAttributes
1507
+ },
1508
+ ...ContextMenuProps2
1509
+ });
1510
+ }
1511
+ } else {
1512
+ console.warn(
1513
+ "useContextMenu, no menuBuilders configured. These should be supplied via the ContextMenuProvider(s)"
1514
+ );
1515
+ }
1516
+ },
1517
+ [buildMenuOptions, ctx, menuActionHandler, menuBuilder, themeAttributes]
1518
+ );
1519
+ const hideContextMenu = useCallback9(() => {
1520
+ console.log("hide context menu");
1521
+ }, []);
1522
+ return [handleShowContextMenu, hideContextMenu];
1523
+ };
1524
+ var NO_OPTIONS = {};
1525
+ var showContextMenuComponent = (position, contextMenu) => {
1526
+ PopupService.showPopup({
1527
+ focus: true,
1528
+ left: 0,
1529
+ top: 0,
1530
+ component: cloneElement(contextMenu, { position })
1531
+ });
1532
+ };
1533
+ var showContextMenu = (e, menuDescriptors, handleContextMenuAction, {
1534
+ position: positionProp,
1535
+ ...contextMenuProps
1536
+ } = NO_OPTIONS) => {
1537
+ const menuItems = (menuDescriptors2) => {
1538
+ const fromDescriptor = (menuItem, i) => isGroupMenuItemDescriptor(menuItem) ? /* @__PURE__ */ jsx8(MenuItemGroup, { label: menuItem.label, children: menuItem.children.map(fromDescriptor) }, i) : /* @__PURE__ */ jsx8(
1539
+ MenuItem,
1540
+ {
1541
+ action: menuItem.action,
1542
+ className: menuItem.className,
1543
+ "data-icon": menuItem.icon,
1544
+ options: menuItem.options,
1545
+ children: menuItem.label
1546
+ },
1547
+ i
1548
+ );
1549
+ return menuDescriptors2.map(fromDescriptor);
1550
+ };
1551
+ const handleClose = (reason) => {
1552
+ var _a;
1553
+ if (reasonIsMenuAction(reason)) {
1554
+ if ((reason == null ? void 0 : reason.closedBy) === "popup-service") {
1555
+ return;
1556
+ }
1557
+ handleContextMenuAction(reason);
1558
+ PopupService.hidePopup(reason);
1559
+ }
1560
+ (_a = contextMenuProps == null ? void 0 : contextMenuProps.onClose) == null ? void 0 : _a.call(contextMenuProps, reason);
1561
+ };
1562
+ const position = positionProp != null ? positionProp : {
1563
+ x: e.clientX,
1564
+ y: e.clientY
1565
+ };
1566
+ const component = /* @__PURE__ */ jsx8(
1567
+ ContextMenu,
1568
+ {
1569
+ ...contextMenuProps,
1570
+ onClose: handleClose,
1571
+ position,
1572
+ children: menuItems(menuDescriptors)
1573
+ }
1574
+ );
1575
+ PopupService.showPopup({ left: 0, top: 0, component, focus: true });
1576
+ };
1577
+
1578
+ // src/popup-menu/PopupMenu.tsx
1579
+ import { Icon, IconButton } from "@vuu-ui/vuu-ui-controls";
1580
+ import { useId as useId3 } from "@vuu-ui/vuu-utils";
1581
+ import { Button as Button2 } from "@salt-ds/core";
1582
+ import cx7 from "clsx";
1583
+
1584
+ // src/popup-menu/usePopupMenu.ts
1585
+ import {
1586
+ useCallback as useCallback10,
1587
+ useRef as useRef8,
1588
+ useState as useState6
1589
+ } from "react";
1590
+ var usePopupMenu = ({
1591
+ anchorElement,
1592
+ id,
1593
+ menuActionHandler,
1594
+ menuBuilder,
1595
+ menuClassName,
1596
+ menuLocation,
1597
+ menuOptions,
1598
+ onMenuClose,
1599
+ onMenuOpen,
1600
+ popupPlacement,
1601
+ tabIndex
1602
+ }) => {
1603
+ const [menuOpen, _setMenuOpen] = useState6(false);
1604
+ const suppressShowMenuRef = useRef8(false);
1605
+ const rootRef = useRef8(null);
1606
+ const setMenuOpen = useCallback10(
1607
+ (isOpen) => {
1608
+ _setMenuOpen(isOpen);
1609
+ if (isOpen) {
1610
+ onMenuOpen == null ? void 0 : onMenuOpen();
1611
+ }
1612
+ },
1613
+ [onMenuOpen]
1614
+ );
1615
+ const [showContextMenu2] = useContextMenu(menuBuilder, menuActionHandler);
1616
+ const handleOpenMenu = useCallback10((el) => {
1617
+ console.log(`menu Open `, {
1618
+ el
1619
+ });
1620
+ }, []);
1621
+ const handleMenuClose = useCallback10(
1622
+ (reason) => {
1623
+ console.log("onClose");
1624
+ setMenuOpen(false);
1625
+ if (reasonIsClickAway(reason)) {
1626
+ const target = reason.mouseEvt.target;
1627
+ if (target === rootRef.current) {
1628
+ suppressShowMenuRef.current = true;
1629
+ }
1630
+ onMenuClose == null ? void 0 : onMenuClose(reason);
1631
+ } else {
1632
+ requestAnimationFrame(() => {
1633
+ var _a;
1634
+ onMenuClose == null ? void 0 : onMenuClose(reason);
1635
+ if (tabIndex !== -1 && (reason == null ? void 0 : reason.type) !== "tab-away") {
1636
+ (_a = rootRef.current) == null ? void 0 : _a.focus();
1637
+ }
1638
+ });
1639
+ }
1640
+ },
1641
+ [onMenuClose, setMenuOpen, tabIndex]
1642
+ );
1643
+ const showMenu = useCallback10(
1644
+ (e) => {
1645
+ var _a;
1646
+ if (suppressShowMenuRef.current) {
1647
+ suppressShowMenuRef.current = false;
1648
+ } else {
1649
+ const anchorEl = (_a = anchorElement == null ? void 0 : anchorElement.current) != null ? _a : rootRef.current;
1650
+ if (anchorEl) {
1651
+ const {
1652
+ left: x,
1653
+ top: y,
1654
+ width
1655
+ } = getPositionRelativeToAnchor(anchorEl, popupPlacement, 0, 0);
1656
+ setMenuOpen(true);
1657
+ showContextMenu2(e, menuLocation, {
1658
+ ContextMenuProps: {
1659
+ className: menuClassName,
1660
+ id: `${id}-menu`,
1661
+ onClose: handleMenuClose,
1662
+ openMenu: handleOpenMenu,
1663
+ position: {
1664
+ x,
1665
+ y
1666
+ },
1667
+ style: { width: width ? width - 2 : void 0 }
1668
+ },
1669
+ ...menuOptions
1670
+ });
1671
+ }
1672
+ }
1673
+ },
1674
+ [
1675
+ anchorElement,
1676
+ handleMenuClose,
1677
+ handleOpenMenu,
1678
+ id,
1679
+ menuClassName,
1680
+ menuLocation,
1681
+ menuOptions,
1682
+ popupPlacement,
1683
+ setMenuOpen,
1684
+ showContextMenu2
1685
+ ]
1686
+ );
1687
+ const ariaAttributes = {
1688
+ "aria-controls": menuOpen ? `${id}-menu` : void 0,
1689
+ "aria-expanded": menuOpen,
1690
+ "aria-haspopup": "menu"
1691
+ };
1692
+ const buttonProps = {
1693
+ id,
1694
+ onClick: showMenu,
1695
+ tabIndex
1696
+ };
1697
+ return { ariaAttributes, buttonProps, menuOpen, rootRef };
1698
+ };
1699
+
1700
+ // src/popup-menu/PopupMenu.tsx
1701
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
1702
+ var classBase4 = "vuuPopupMenu";
1703
+ var PopupMenu = ({
1704
+ anchorElement,
1705
+ className,
1706
+ disabled = false,
1707
+ label,
1708
+ icon = label ? "chevron-down" : "more-vert",
1709
+ id: idProp,
1710
+ menuActionHandler,
1711
+ menuBuilder,
1712
+ menuClassName,
1713
+ menuLocation = "header",
1714
+ menuOptions,
1715
+ onMenuClose,
1716
+ onMenuOpen,
1717
+ popupPlacement = "below-right",
1718
+ tabIndex = 0,
1719
+ variant = "secondary",
1720
+ ...htmlAttributes
1721
+ }) => {
1722
+ const id = useId3(idProp);
1723
+ const { ariaAttributes, buttonProps, menuOpen, rootRef } = usePopupMenu({
1724
+ anchorElement,
1725
+ id,
1726
+ menuActionHandler,
1727
+ menuBuilder,
1728
+ menuClassName,
1729
+ menuLocation,
1730
+ onMenuClose,
1731
+ onMenuOpen,
1732
+ menuOptions,
1733
+ popupPlacement,
1734
+ tabIndex
1735
+ });
1736
+ if (label) {
1737
+ return /* @__PURE__ */ jsxs3(
1738
+ Button2,
1739
+ {
1740
+ ...htmlAttributes,
1741
+ ...ariaAttributes,
1742
+ ...buttonProps,
1743
+ className: cx7(classBase4, className, `${classBase4}-withCaption`, {
1744
+ "saltButton-active": menuOpen
1745
+ }),
1746
+ disabled,
1747
+ ref: rootRef,
1748
+ variant: "secondary",
1749
+ children: [
1750
+ icon ? /* @__PURE__ */ jsx9(Icon, { name: icon }) : null,
1751
+ label
1752
+ ]
1753
+ }
1754
+ );
1755
+ } else if (icon) {
1756
+ return /* @__PURE__ */ jsx9(
1757
+ IconButton,
1758
+ {
1759
+ ...htmlAttributes,
1760
+ ...ariaAttributes,
1761
+ ...buttonProps,
1762
+ className: cx7(classBase4, className, {
1763
+ "saltButton-active": menuOpen
1764
+ }),
1765
+ disabled,
1766
+ icon,
1767
+ ref: rootRef,
1768
+ variant
1769
+ }
1770
+ );
1771
+ } else {
1772
+ console.error("PopupMenu must have a label or an icon (or both)");
1773
+ return null;
1774
+ }
1775
+ };
1776
+
1777
+ // src/prompt/Prompt.tsx
1778
+ import { useThemeAttributes as useThemeAttributes4 } from "@vuu-ui/vuu-utils";
1779
+ import { Button as Button3 } from "@salt-ds/core";
1780
+ import cx8 from "clsx";
1781
+ import { useLayoutEffect as useLayoutEffect5, useRef as useRef9 } from "react";
1782
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1783
+ var classBase5 = "vuuPrompt";
1784
+ var AnchorBody2 = { current: document.body };
1785
+ var EMPTY_PROPS2 = {};
1786
+ var Prompt = ({
1787
+ PopupProps = EMPTY_PROPS2,
1788
+ cancelButtonLabel = "Cancel",
1789
+ confirmButtonLabel = "Confirm",
1790
+ icon,
1791
+ onCancel,
1792
+ onConfirm,
1793
+ style,
1794
+ text,
1795
+ title,
1796
+ variant = "info",
1797
+ ...htmlAttributes
1798
+ }) => {
1799
+ const {
1800
+ anchorElement = AnchorBody2,
1801
+ offsetLeft = 0,
1802
+ offsetTop = 0,
1803
+ placement = "below"
1804
+ } = PopupProps;
1805
+ const [themeClass, _, dataMode] = useThemeAttributes4();
1806
+ const { position } = useAnchoredPosition({
1807
+ anchorElement,
1808
+ offsetLeft,
1809
+ offsetTop,
1810
+ placement
1811
+ });
1812
+ const rootRef = useRef9(null);
1813
+ const confirmRef = useRef9(null);
1814
+ useLayoutEffect5(() => {
1815
+ if (rootRef.current) {
1816
+ rootRef.current.showModal();
1817
+ if (confirmRef.current) {
1818
+ confirmRef.current.focus();
1819
+ }
1820
+ if (placement.endsWith("center")) {
1821
+ const { width } = rootRef.current.getBoundingClientRect();
1822
+ rootRef.current.style.marginLeft = `-${width / 2}px`;
1823
+ }
1824
+ }
1825
+ }, [placement]);
1826
+ return /* @__PURE__ */ jsx10(
1827
+ "dialog",
1828
+ {
1829
+ ...htmlAttributes,
1830
+ className: cx8(classBase5, `${classBase5}-${variant}`, themeClass),
1831
+ "data-mode": dataMode,
1832
+ ref: rootRef,
1833
+ style: { ...style, ...position },
1834
+ children: /* @__PURE__ */ jsxs4("form", { className: `${classBase5}-form`, children: [
1835
+ /* @__PURE__ */ jsx10("div", { className: `${classBase5}-header`, "data-icon": icon, children: title }),
1836
+ /* @__PURE__ */ jsx10("div", { className: `${classBase5}-text`, children: text }),
1837
+ /* @__PURE__ */ jsxs4("div", { className: `${classBase5}-buttonBar`, children: [
1838
+ /* @__PURE__ */ jsx10(Button3, { onClick: onCancel, variant: "secondary", children: cancelButtonLabel }),
1839
+ /* @__PURE__ */ jsx10(Button3, { onClick: onConfirm, ref: confirmRef, value: "default", children: confirmButtonLabel })
1840
+ ] })
1841
+ ] })
1842
+ }
1843
+ );
1844
+ };
1845
+
1846
+ // src/tooltip/useAnchoredPosition.ts
1847
+ import { useLayoutEffect as useLayoutEffect6, useState as useState7 } from "react";
1848
+ var getPositionRelativeToAnchor2 = (anchorElement, placement, offsetLeft, offsetTop) => {
1849
+ const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
1850
+ const midX = left + width / 2;
1851
+ const midY = top + height / 2;
1852
+ switch (placement) {
1853
+ case "above":
1854
+ return { left: midX + offsetLeft, top: top + offsetTop };
1855
+ case "below":
1856
+ return { left: midX + offsetLeft, top: bottom + offsetTop };
1857
+ case "right":
1858
+ return { left: right + offsetLeft, top: midY + offsetLeft };
1859
+ case "left":
1860
+ return { left: left + offsetLeft, top: midY + offsetLeft };
1861
+ default:
1862
+ throw Error(
1863
+ "Tooltip getPositionRelativeToAnchor only supported placement values are left, right, below and right"
1864
+ );
1865
+ }
1866
+ };
1867
+ var useAnchoredPosition2 = ({
1868
+ anchorElement,
1869
+ offsetLeft = 0,
1870
+ offsetTop = 0,
1871
+ placement
1872
+ }) => {
1873
+ const [position, setPosition] = useState7();
1874
+ useLayoutEffect6(() => {
1875
+ if (anchorElement.current) {
1876
+ const position2 = getPositionRelativeToAnchor2(
1877
+ anchorElement.current,
1878
+ placement,
1879
+ offsetLeft,
1880
+ offsetTop
1881
+ );
1882
+ setPosition(position2);
1883
+ }
1884
+ }, [anchorElement, offsetLeft, offsetTop, placement]);
1885
+ return position;
1886
+ };
1887
+
1888
+ // src/tooltip/Tooltip.tsx
1889
+ import cx9 from "clsx";
1890
+ import { jsx as jsx11 } from "react/jsx-runtime";
1891
+ var classBase6 = "vuuTooltip";
1892
+ var Tooltip = ({
1893
+ anchorElement,
1894
+ children,
1895
+ id,
1896
+ onMouseEnter,
1897
+ onMouseLeave,
1898
+ placement,
1899
+ status,
1900
+ style: styleProp
1901
+ }) => {
1902
+ const position = useAnchoredPosition2({ anchorElement, placement });
1903
+ if (position === void 0) {
1904
+ return null;
1905
+ }
1906
+ return /* @__PURE__ */ jsx11(Portal, { children: /* @__PURE__ */ jsx11(
1907
+ "div",
1908
+ {
1909
+ className: cx9(classBase6, {
1910
+ [`${classBase6}-error`]: status === "error"
1911
+ }),
1912
+ "data-align": placement,
1913
+ id,
1914
+ style: { ...styleProp, ...position },
1915
+ children: /* @__PURE__ */ jsx11(
1916
+ "span",
1917
+ {
1918
+ className: `${classBase6}-content`,
1919
+ onMouseEnter,
1920
+ onMouseLeave,
1921
+ children
1922
+ }
1923
+ )
1924
+ }
1925
+ ) });
1926
+ };
1927
+
1928
+ // src/tooltip/useTooltip.ts
1929
+ import { useId as useId4 } from "@vuu-ui/vuu-utils";
1930
+ import { useCallback as useCallback11, useRef as useRef10, useState as useState8 } from "react";
1931
+ var useTooltip = ({
1932
+ id: idProp,
1933
+ placement = "right",
1934
+ tooltipContent
1935
+ }) => {
1936
+ const hideTooltipRef = useRef10();
1937
+ const anchorElementRef = useRef10(null);
1938
+ const mouseEnterTimerRef = useRef10();
1939
+ const mouseLeaveTimerRef = useRef10();
1940
+ const [tooltipProps, setTooltipProps] = useState8();
1941
+ const id = useId4(idProp);
1942
+ const escapeListener = useCallback11((evt) => {
1943
+ var _a;
1944
+ if (evt.key === "Escape") {
1945
+ (_a = hideTooltipRef.current) == null ? void 0 : _a.call(hideTooltipRef);
1946
+ }
1947
+ }, []);
1948
+ hideTooltipRef.current = useCallback11(() => {
1949
+ setTooltipProps(void 0);
1950
+ document.removeEventListener("keydown", escapeListener);
1951
+ }, [escapeListener]);
1952
+ const handleMouseEnterTooltip = useCallback11(() => {
1953
+ window.clearTimeout(mouseLeaveTimerRef.current);
1954
+ }, []);
1955
+ const handleMouseLeaveTooltip = useCallback11(() => {
1956
+ var _a;
1957
+ (_a = hideTooltipRef.current) == null ? void 0 : _a.call(hideTooltipRef);
1958
+ }, []);
1959
+ const showTooltip = useCallback11(() => {
1960
+ const { current: anchorEl } = anchorElementRef;
1961
+ if (anchorEl) {
1962
+ setTooltipProps({
1963
+ anchorElement: anchorElementRef,
1964
+ children: tooltipContent,
1965
+ id: `${id}-tooltip`,
1966
+ onMouseEnter: handleMouseEnterTooltip,
1967
+ onMouseLeave: handleMouseLeaveTooltip,
1968
+ placement
1969
+ });
1970
+ document.addEventListener("keydown", escapeListener);
1971
+ }
1972
+ mouseEnterTimerRef.current = void 0;
1973
+ }, [
1974
+ escapeListener,
1975
+ handleMouseEnterTooltip,
1976
+ handleMouseLeaveTooltip,
1977
+ id,
1978
+ placement,
1979
+ tooltipContent
1980
+ ]);
1981
+ const handleMouseEnter = useCallback11(
1982
+ (evt) => {
1983
+ const el = evt.target;
1984
+ if (el) {
1985
+ anchorElementRef.current = el;
1986
+ mouseEnterTimerRef.current = window.setTimeout(showTooltip, 800);
1987
+ }
1988
+ },
1989
+ [showTooltip]
1990
+ );
1991
+ const handleMouseLeave = useCallback11(() => {
1992
+ if (anchorElementRef.current)
1993
+ if (mouseEnterTimerRef.current) {
1994
+ window.clearTimeout(mouseEnterTimerRef.current);
1995
+ mouseEnterTimerRef.current = void 0;
1996
+ } else {
1997
+ if (hideTooltipRef.current) {
1998
+ mouseLeaveTimerRef.current = window.setTimeout(
1999
+ hideTooltipRef.current,
2000
+ 200
2001
+ );
2002
+ }
2003
+ }
2004
+ }, []);
2005
+ const anchorProps = {
2006
+ "aria-describedby": `${id}-tooltip`,
2007
+ onMouseEnter: handleMouseEnter,
2008
+ onMouseLeave: handleMouseLeave
2009
+ };
2010
+ return {
2011
+ anchorProps,
2012
+ tooltipProps
2013
+ };
2014
+ };
2015
+
2016
+ // src/notifications/NotificationsProvider.tsx
2017
+ import React4, { useState as useState9, useContext as useContext2, useCallback as useCallback12, useEffect } from "react";
2018
+ import classNames from "clsx";
2019
+ import { getUniqueId } from "@vuu-ui/vuu-utils";
2020
+ import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
2021
+ var toastOffsetTop = 60;
2022
+ var toastDisplayDuration = 6e3;
2023
+ var horizontalTransitionDuration = 1e3;
2024
+ var verticalTransitionDuration = 300;
2025
+ var toastHeight = 56;
2026
+ var toastWidth = 300;
2027
+ var toastContainerContentGap = 10;
2028
+ var toastContainerLeftPadding = 10;
2029
+ var toastContainerRightPadding = 50;
2030
+ var classBase7 = "vuuToastNotifications";
2031
+ var NotificationLevel = /* @__PURE__ */ ((NotificationLevel2) => {
2032
+ NotificationLevel2["Info"] = "info";
2033
+ NotificationLevel2["Success"] = "success";
2034
+ NotificationLevel2["Warning"] = "warning";
2035
+ NotificationLevel2["Error"] = "error";
2036
+ return NotificationLevel2;
2037
+ })(NotificationLevel || {});
2038
+ var NotificationsContext = React4.createContext({
2039
+ notify: () => "have you forgotten to provide a NotificationProvider?"
2040
+ });
2041
+ var NotificationsProvider = (props) => {
2042
+ const [notifications, setNotifications] = useState9([]);
2043
+ const notify = useCallback12((notification) => {
2044
+ const newNotification = { ...notification, id: getUniqueId() };
2045
+ setNotifications((prev) => [...prev, newNotification]);
2046
+ setTimeout(() => {
2047
+ setNotifications((prev) => prev.filter((n) => n !== newNotification));
2048
+ }, toastDisplayDuration + horizontalTransitionDuration * 2);
2049
+ }, []);
2050
+ return /* @__PURE__ */ jsxs5(NotificationsContext.Provider, { value: { notify }, children: [
2051
+ /* @__PURE__ */ jsx12(
2052
+ "div",
2053
+ {
2054
+ style: {
2055
+ width: toastWidth + toastContainerRightPadding + toastContainerLeftPadding
2056
+ },
2057
+ children: notifications.map((notification, i) => /* @__PURE__ */ jsx12(
2058
+ ToastNotification,
2059
+ {
2060
+ top: toastOffsetTop + (toastHeight + toastContainerContentGap) * i,
2061
+ notification
2062
+ },
2063
+ notification.id
2064
+ ))
2065
+ }
2066
+ ),
2067
+ props.children
2068
+ ] });
2069
+ };
2070
+ var useNotifications = () => useContext2(NotificationsContext);
2071
+ var ToastNotification = (props) => {
2072
+ const { top, notification, animated = true } = props;
2073
+ const [right, setRight] = useState9(-toastWidth - toastContainerRightPadding);
2074
+ useEffect(() => {
2075
+ setTimeout(() => setRight(toastContainerRightPadding));
2076
+ if (animated) {
2077
+ setTimeout(
2078
+ () => setRight(-toastWidth - toastContainerRightPadding),
2079
+ toastDisplayDuration + horizontalTransitionDuration
2080
+ );
2081
+ }
2082
+ }, [animated]);
2083
+ return /* @__PURE__ */ jsx12(Portal, { children: /* @__PURE__ */ jsxs5(
2084
+ "div",
2085
+ {
2086
+ className: classNames(`${classBase7}-toast`, notification.type),
2087
+ style: {
2088
+ height: toastHeight,
2089
+ right,
2090
+ width: toastWidth,
2091
+ top,
2092
+ transition: animated ? `right ${horizontalTransitionDuration}ms, top ${verticalTransitionDuration}ms ` : "none"
2093
+ },
2094
+ children: [
2095
+ /* @__PURE__ */ jsx12(
2096
+ "div",
2097
+ {
2098
+ className: classNames(
2099
+ `${classBase7}-toastIcon`,
2100
+ `${notification.type}-icon`
2101
+ )
2102
+ }
2103
+ ),
2104
+ /* @__PURE__ */ jsxs5("div", { className: `${classBase7}-toastContent`, children: [
2105
+ /* @__PURE__ */ jsx12("strong", { className: `${classBase7}-toastHeader`, children: notification.header }),
2106
+ /* @__PURE__ */ jsx12("div", { children: notification.body })
2107
+ ] })
2108
+ ]
2109
+ }
2110
+ ) });
2111
+ };
2112
+ export {
2113
+ ContextMenu,
2114
+ ContextMenuContext,
2115
+ ContextMenuProvider,
2116
+ Dialog,
2117
+ DialogHeader,
2118
+ DialogService,
2119
+ MenuItem,
2120
+ MenuItemGroup,
2121
+ MenuList,
2122
+ NotificationLevel,
2123
+ NotificationsContext,
2124
+ NotificationsProvider,
2125
+ PopupComponent2 as PopupComponent,
2126
+ PopupMenu,
2127
+ PopupService,
2128
+ Portal,
2129
+ Prompt,
2130
+ Separator,
2131
+ ToastNotification,
2132
+ Tooltip,
2133
+ createContainer,
2134
+ isMenuItemLabel,
2135
+ reasonIsClickAway,
2136
+ reasonIsMenuAction,
2137
+ renderPortal,
2138
+ useAnchoredPosition,
2139
+ useContextMenu,
2140
+ useDialog,
2141
+ useNotifications,
2142
+ useTooltip
2143
+ };
2
2144
  //# sourceMappingURL=index.js.map