@vuu-ui/vuu-popups 0.6.21 → 0.6.22-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,1297 @@
1
- import{Scrim as nt,Toolbar as ot,ToolbarButton as rt}from"@heswell/salt-lab";import{Text as st}from"@salt-ds/core";import it from"classnames";import{useCallback as fe,useRef as ut,useState as ge}from"react";import{useLayoutEffect as de,useMemo as tt}from"react";import*as me from"react-dom";import*as ae from"react-dom";import{SaltProvider as Ye}from"@salt-ds/core";import{jsx as et}from"react/jsx-runtime";var Ze=1,_e=(e=0,t=0,n=window)=>{let o=n.document.createElement("div");return o.className="vuuPopup "+Ze++,o.style.cssText=`left:${e}px; top:${t}px;`,n.document.body.appendChild(o),o},je=(e,t)=>_e(e,t),F=(e,t,n,o,r)=>{t.style.cssText=`left:${n}px; top:${o}px;position: absolute;`,ae.render(et(Ye,{applyClassesTo:"child",children:e}),t,r)},pe=je;var G=function({children:t,x:n=0,y:o=0,onRender:r}){let c=tt(()=>pe(),[]);return de(()=>{F(t,c,n,o,r)},[t,r,c,n,o]),de(()=>()=>{var i;c&&(me.unmountComponentAtNode(c),c.classList.contains("vuuPopup")&&((i=c.parentElement)==null||i.removeChild(c)))},[c]),null};var un=e=>{let t=getComputedStyle(document.body).getPropertyValue("--installed-themes");document.body.style.setProperty("--installed-themes",`${t} ${e}`)};import{jsx as W,jsxs as Me}from"react/jsx-runtime";var Z="vuuDialog",In=({children:e,className:t,isOpen:n=!1,onClose:o,title:r,...c})=>{let i=ut(null),[u]=ge(0),[a]=ge(0),d=fe(()=>{o==null||o()},[o]),l=fe(()=>{},[]);return n?W(G,{onRender:l,x:u,y:a,children:W(nt,{className:`${Z}-scrim`,open:n,children:Me("div",{...c,className:it(Z,t),ref:i,children:[Me(ot,{className:`${Z}-header`,children:[W(st,{children:r}),W(rt,{onClick:d,"data-align-end":!0,"data-icon":"close"},"close")]}),e]})})}):null};import{useIdMemo as Kt}from"@salt-ds/core";import{useCallback as ne,useRef as Se}from"react";import vt,{useLayoutEffect as yt,useMemo as It,useRef as Ct}from"react";import Le from"classnames";import{useIdMemo as wt}from"@salt-ds/core";import{useCallback as q,useMemo as gt,useRef as _,useState as Mt}from"react";var he=e=>e.closest("[data-root='true']")!==null,xe=(e,t)=>{var n;return e.ariaHasPopup==="true"&&((n=e.dataset)==null?void 0:n.idx)===`${t}`||e.querySelector(`:scope > [data-idx='${t}'][aria-haspopup='true']`)!==null};function ct(e,...t){let n=new Set(e);for(let o of t)for(let r of o)n.add(r);return n}var lt="Enter";var at="Delete",pt=new Set([lt,at]),dt=new Set(["Tab"]),mt=new Set(["ArrowRight","ArrowLeft"]),be=new Set(["Home","End","ArrowDown","ArrowUp"]),ve=new Set(["Home","End","ArrowRight","ArrowLeft"]),ft=new Set(["F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12"]),Rn=ct(pt,ve,be,mt,ft,dt);var ye=({key:e},t="vertical")=>(t==="vertical"?be:ve).has(e);var Ie=({autoHighlightFirstItem:e=!1,count:t,highlightedIndex:n,onActivate:o,onHighlight:r,onCloseMenu:c,onOpenMenu:i})=>{let u=_((n!=null?n:e)?0:-1),[,a]=Mt(null),d=n!==void 0,l=q(s=>{u.current=s,r==null||r(s),a({})},[r]),h=q(s=>{s!==u.current&&(d||l(s))},[d,l]),f=_(!0),C=_(!1),v=s=>C.current=s,g=d?n:u.current,E=q(s=>{let m=ht(t,s.key,u.current);m!==u.current&&h(m)},[t,h]),R=q(s=>{ye(s)?(s.preventDefault(),s.stopPropagation(),f.current=!0,E(s)):(s.key==="ArrowRight"||s.key==="Enter")&&xe(s.target,g)?i(g):s.key==="ArrowLeft"&&!he(s.target)?c(g):s.key==="Enter"&&o&&o(g)},[g,E,o,c,i]),L=gt(()=>({onFocus:()=>{g===-1&&l(0)},onKeyDown:R,onMouseDownCapture:()=>{f.current=!1,v(!0)},onMouseMove:()=>{f.current&&(f.current=!1)},onMouseLeave:()=>{f.current=!0,v(!1),h(-1)}}),[g,h,E,o,c,i,l]);return{focusVisible:f.current?g:-1,controlledHighlighting:d,highlightedIndex:g,setHighlightedIndex:h,listProps:L,setIgnoreFocus:v}};function ht(e,t,n){return t==="ArrowUp"?n>0?n-1:n:n===null?0:n===e-1?n:n+1}import Ce,{useCallback as xt,useMemo as bt}from"react";var j=e=>e.type===V||!!e.props["data-group"],we=e=>{let t=xt(()=>{let r=(i,u="root",a={},d={})=>{let l=a[u]=[],h=0,f=!1;return Ce.Children.forEach(i,C=>{if(C.type===Ee)f=!0;else{let v=j(C),g=u==="root"?`${h}`:`${u}.${h}`,{props:{action:E,options:R}}=C,{childWithId:L,grandChildren:s}=c(C,g,v,f);l.push(L),s?r(s,g,a,d):d[g]={action:E,options:R},h+=1,f=!1}}),[a,d]},c=(i,u,a,d=!1)=>{let{props:{children:l}}=i;return{childWithId:Ce.cloneElement(i,{hasSeparator:d,id:`${u}`,key:u,children:a?void 0:l}),grandChildren:a?l:void 0}};return r(e)},[e]),[n,o]=bt(()=>t(),[t]);return[n,o]};import{jsx as K}from"react/jsx-runtime";var Pe="vuuMenuList",Ee=()=>K("li",{className:"vuuMenuItem-divider"}),V=()=>null,ee=({children:e,idx:t,...n})=>K("div",{...n,children:e}),Et=e=>e.props["data-icon"],Re=({activatedByKeyboard:e,childMenuShowing:t=-1,children:n,className:o,highlightedIdx:r,id:c,isRoot:i,listItemProps:u,menuId:a,onHighlightMenuItem:d,onActivate:l,onCloseMenu:h,onOpenMenu:f,...C})=>{let v=wt(c),g=Ct(null),E=It(()=>new Map,[]),R=y=>{var b;let M=(b=g.current)==null?void 0:b.querySelector(`:scope > [data-idx='${y}']`);M!=null&&M.id&&(f==null||f(M.id))},L=y=>{var b;let M=(b=g.current)==null?void 0:b.querySelector(`:scope > [data-idx='${y}']`);M!=null&&M.id&&(l==null||l(M.id))},{focusVisible:s,highlightedIndex:m,listProps:p}=Ie({count:vt.Children.count(n),highlightedIndex:r,onActivate:L,onHighlight:d,onOpenMenu:R,onCloseMenu:h}),x=t==-1?s:-1;return yt(()=>{var y;t===-1&&e&&((y=g.current)==null||y.focus())},[e,t]),K("div",{...C,...p,"aria-activedescendant":(()=>m===void 0||m===-1?void 0:E.get(m))(),className:Le(Pe,o,{[`${Pe}-childMenuShowing`]:t!==-1}),"data-root":i||void 0,id:`${v}-${a}`,ref:g,role:"menu",tabIndex:0,children:k()});function k(){let y={...u,role:"menuitem"},M=(I,D,$)=>D?[K("span",{className:"vuuIconContainer","data-icon":$},"icon")].concat(I):I;function b(I,D,$,ie){var le;let{children:Ve,className:Ue,"data-icon":ue,id:X,hasSeparator:ze,label:Je,...Qe}=D.props,Y=j(D),ce=Y&&t===$,Xe=ce?`${v}-${X}`:void 0;I.push(K(ee,{...Qe,...y,...Pt(`${v}-${a}`,X,$,(le=D.key)!=null?le:X,m,x,Ue,ze),"aria-controls":Xe,"aria-haspopup":Y||void 0,"aria-expanded":ce||void 0,children:M(Y?Je:Ve,ie,ue)}))}let P=[];if(n.length>0){let I=n.some(Et);n.forEach((D,$)=>{b(P,D,$,I)})}return P}},Pt=(e,t,n,o,r,c,i,u)=>({id:`${e}-${t}`,key:o!=null?o:n,"data-idx":n,"data-highlighted":n===r||void 0,className:Le("vuuMenuItem",i,{"vuuMenuItem-separator":u,focusVisible:c===n})});Re.displayName="MenuList";var He=Re;import{useCallback as A,useMemo as Lt,useRef as N,useState as Rt}from"react";function ke(e){if(e){let t=e.dataset.idx;if(t)return parseInt(t,10);if(e.ariaPosInSet)return parseInt(e.ariaPosInSet,10)-1}}var te=e=>e==null?void 0:e.closest("[data-idx],[aria-posinset]");var Te=(e,t,n)=>e.map((o,r)=>r===e.length-1?{...o,[n]:o[n]-t}:o),Ht=(e,t)=>Te(e,t,"left"),kt=(e,t)=>Te(e,t,"top"),Tt=(e,t)=>{let[n,o]=t.slice(-2),r=document.getElementById(`${e}-${o.id}`);if(r===null)throw Error(`useCascade.flipSides element with id ${o.id} not found`);let{width:c}=r.getBoundingClientRect();return t.map(i=>i===o?{...i,left:n.left-(c-2)}:i)},Dt=(e,t)=>{let[{left:n,top:o}]=t.slice(-1),{offsetWidth:r,offsetTop:c}=e;return{left:n+r,top:c+o}},O=e=>{let t=e.lastIndexOf("-");return t===-1?e:e.slice(t+1)},U=e=>{let t=O(e),n=t.lastIndexOf(".");return n>-1?t.slice(0,n):"root"},At=e=>{let t=0,n=e.indexOf(".",0);for(;n!==-1;)t+=1,n=e.indexOf(".",n+1);return t},St=e=>({menuId:U(e.id),itemId:O(e.id),isGroup:e.ariaHasPopup==="true",isOpen:e.ariaExpanded==="true",level:At(e.id)}),De=({id:e,onActivate:t,onMouseEnterItem:n,position:{x:o,y:r}})=>{let[,c]=Rt({}),i=N([{id:"root",left:o,top:r}]),u=A(s=>{i.current=s,c({})},[]),a=N(),d=N(),l=N({root:"no-popup"}),h=N(0),f=A((s="root",m=null,p=null)=>{if(s==="root"&&m===null)u([{id:"root",left:o,top:r}]);else{l.current[s]="popup-open";let w=(p?p.ownerDocument:document).getElementById(`${e}-${s}-${m}`),{left:k,top:y}=Dt(w,i.current);u(i.current.concat({id:m,left:k,top:y}))}},[e,o,r,u]),C=A(s=>{u(s==="root"?[]:i.current.slice(0,-1))},[u]),v=A((s,m)=>{let p=i.current.slice(),{id:x}=p[p.length-1];for(;p.length>1&&!m.startsWith(x);){let w=U(x);p.pop(),l.current[x]="no-popup",l.current[w]="no-popup",{id:x}=p[p.length-1]}p.length<i.current.length&&u(p)},[u]),g=A((s,m,p)=>{a.current&&clearTimeout(a.current),a.current=window.setTimeout(()=>{console.log(`scheduleOpen timed out opening ${m}`),v(s,m),l.current[s]="popup-open",l.current[m]="no-popup",f(s,m,p)},400)},[v,f]),E=A((s,m,p)=>{console.log(`scheduleClose openMenuId ${s} menuId ${m} itemId ${p}`),l.current[s]="pending-close",d.current=window.setTimeout(()=>{v(m,p)},400)},[v]),R=A(()=>{let{current:s}=i,[m]=s.slice(-1),p=document.getElementById(`${e}-${m.id}`);if(p){let{right:x,bottom:w}=p.getBoundingClientRect(),{clientHeight:k,clientWidth:y}=document.body;if(x>y){let M=s.length>1?Tt(e,s):Ht(s,x-y);u(M)}else if(w>k){let M=kt(s,w-k);u(M)}}},[e,u]),L=Lt(()=>({onMouseEnter:s=>{let m=te(s.target),{menuId:p,itemId:x,isGroup:w,isOpen:k,level:y}=St(m),M=h.current===y,{current:{[p]:b}}=l;if(h.current=y,b==="no-popup"&&w)l.current[p]="popup-pending",g(p,x,m);else if(b==="popup-pending"&&!w)l.current[p]="no-popup",clearTimeout(a.current),a.current=void 0;else if(b==="popup-pending"&&w)clearTimeout(a.current),g(p,x,m);else if(b==="popup-open"){let[{id:P},{id:I}]=i.current.slice(-2);P===p&&l.current[I]!=="pending-close"&&M?(E(I,p,x),w&&!k&&g(p,x,m)):P===p&&w&&x!==I&&l.current[I]==="pending-close"?g(p,x,m):w?(v(p,x),g(p,x,m)):l.current[I]==="pending-close"&&M||v(p,x)}b==="pending-close"&&(a.current&&(clearTimeout(a.current),a.current=void 0),clearTimeout(d.current),d.current=void 0,l.current[p]="popup-open"),n(s,x)},onClick:s=>{let m=s.target,p=te(m),x=ke(p);console.log(`list item click [${x}] hasPopup ${p.ariaHasPopup}`),p.ariaHasPopup==="true"?p.ariaExpanded!=="true"&&f(x):t(O(p.id))}}),[v,t,n,f,E,g]);return{closeMenu:C,handleRender:R,listItemProps:L,openMenu:f,openMenus:i.current}};import{useEffect as $t}from"react";var Ae=({containerClassName:e,isOpen:t,onClose:n})=>{$t(()=>{let o;return t&&(o=r=>{r.target.closest(`.${e}`)===null&&(n==null||n("root"))},document.body.addEventListener("click",o,!0)),()=>{o&&document.body.removeEventListener("click",o,!0)}},[e,t,n])};import{Fragment as Bt,jsx as $e}from"react/jsx-runtime";import{createElement as Ot}from"react";var Nt=()=>{},oe=({activatedByKeyboard:e,children:t,className:n,id:o,onClose:r=()=>{},position:c={x:0,y:0},style:i,...u})=>{let a=Kt(o),d=Se(Nt),[l,h]=we(t),f=Se(e),C=ne(()=>{f.current=!1},[]),v=ne(M=>{let{action:b,options:P}=h[M];d.current("root"),r(b,P)},[h,r]),{closeMenu:g,listItemProps:E,openMenu:R,openMenus:L,handleRender:s}=De({id:a,onActivate:v,onMouseEnterItem:C,position:c});d.current=g,console.log({openMenus:L});let m=ne(()=>{g(),r()},[g,r]);Ae({containerClassName:"vuuMenuList",onClose:m,isOpen:L.length>0});let p=M=>{let b=O(M),P=U(b);f.current=!0,R(P,b)},x=()=>{f.current=!0,g()},w=()=>{},k=L.length-1,y=M=>{if(M>=k)return-1;{let{id:b}=L[M+1],P=b.lastIndexOf(".");return parseInt(P===-1?b:b.slice(-P),10)}};return $e(Bt,{children:L.map(({id:M,left:b,top:P},I)=>{let D=y(I);return $e(G,{x:b,y:P,onRender:s,children:Ot(He,{...u,activatedByKeyboard:f.current,childMenuShowing:D,className:n,id:a,menuId:M,isRoot:I===0,key:I,listItemProps:E,onActivate:v,onHighlightMenuItem:w,onCloseMenu:x,onOpenMenu:p,style:i},l[M])},I)})})};oe.displayName="ContextMenu";import{createContext as Ft,useCallback as Gt,useMemo as Wt}from"react";import{jsx as re}from"react/jsx-runtime";var z=Ft(null),Ke=e=>e!==void 0&&"children"in e,qt=({children:e,context:t,menuActionHandler:n,menuBuilder:o})=>{let r=Wt(()=>t!=null&&t.menuBuilders&&o?t.menuBuilders.concat(o):o?[o]:(t==null?void 0:t.menuBuilders)||[],[t,o]),c=Gt((i,u)=>{var a;if(n!=null&&n(i,u)||(a=t==null?void 0:t.menuActionHandler)!=null&&a.call(t,i,u))return!0},[t,n]);return re(z.Provider,{value:{menuActionHandler:c,menuBuilders:r},children:e})},yo=({children:e,label:t,menuActionHandler:n,menuBuilder:o})=>re(z.Consumer,{children:r=>re(qt,{context:r,label:t,menuActionHandler:n,menuBuilder:o,children:e})});import{useCallback as qe,useContext as Zt}from"react";import Vt from"classnames";import Oe,{createElement as Be,useEffect as Ut,useRef as Ne}from"react";import B from"react-dom";var S=!1,H=[];function Q(e){if(e.key==="Esc"){if(H.length)Ge();else if(S){let t=document.body.querySelector(".vuuDialog");t&&B.unmountComponentAtNode(t)}}}function Fe(e){if(H.length){let t=document.body.querySelectorAll(".vuuPopup");for(let n=0;n<t.length;n++)if(t[n].contains(e.target))return;Ge()}}function Ge(){if(H.length){let e=document.body.querySelectorAll(".vuuPopup");for(let t=0;t<e.length;t++)B.unmountComponentAtNode(e[t]);We("*")}}function zt(){S===!1&&(S=!0,window.addEventListener("keydown",Q,!0))}function Jt(){S&&(S=!1,window.removeEventListener("keydown",Q,!0))}function Qt(e){H.indexOf(e)===-1&&(H.push(e),S===!1&&(window.addEventListener("keydown",Q,!0),window.addEventListener("click",Fe,!0)))}function We(e){if(H.length){if(e==="*")H.length=0;else{let t=H.indexOf(e);t!==-1&&H.splice(t,1)}H.length===0&&S===!1&&(window.removeEventListener("keydown",Q,!0),window.removeEventListener("click",Fe,!0))}}var Xt=({children:e,position:t,style:n})=>{let o=Vt("hwPopup","hwPopupContainer",t);return Be("div",{className:o,style:n},e)},Yt=1,T=class{static showPopup({name:t="anon",group:n="all",position:o="",left:r=0,right:c="auto",top:i=0,width:u="auto",component:a}){if(!a)throw Error("PopupService showPopup, no component supplied");Qt(t);let d=document.body.querySelector(".vuuPopup."+n);d===null&&(d=document.createElement("div"),d.className="vuuPopup "+n,document.body.appendChild(d));let l={width:u};F(Be(Xt,{key:Yt++,position:o,style:l},a),d,r,i,()=>{T.keepWithinThePage(d,c)})}static hidePopup(t="anon",n="all"){if(H.indexOf(t)!==-1){We(t);let o=document.body.querySelector(`.vuuPopup.${n}`);o&&B.unmountComponentAtNode(o)}}static keepWithinThePage(t,n="auto"){let o=t.querySelector(".vuuPopupContainer > *");if(o){let{top:r,left:c,width:i,height:u,right:a}=o.getBoundingClientRect(),d=window.innerWidth,h=window.innerHeight-(r+u);h<0&&(o.style.top=Math.round(r)+h+"px");let f=d-(c+i);if(f<0&&(o.style.left=Math.round(c)+f+"px"),typeof n=="number"&&n!==a){let C=n-a;o.style.left=c+C+"px"}}}},J=class{static showDialog(t){let n=".vuuDialog",o=t.props.onClose;zt(),B.render(Oe.cloneElement(t,{container:n,onClose:()=>{J.closeDialog(),o&&o()}}),document.body.querySelector(n))}static closeDialog(){Jt();let t=document.body.querySelector(".vuuDialog");t&&B.unmountComponentAtNode(t)}},So=e=>{let t=Ne(),n=Ne(null),o=(r,c)=>{let{name:i,group:u,depth:a,width:d}=r,l,h;if(t.current&&(window.clearTimeout(t.current),t.current=void 0),r.close===!0)T.hidePopup(i,u);else{let{position:f,children:C}=r,{left:v,top:g,width:E,bottom:R}=c;f==="below"?(l=v,h=R):f==="above"&&(l=v,h=g),t.current=window.setTimeout(()=>{T.showPopup({name:i,group:u,depth:a,position:f,left:l,top:h,width:d||E,component:C})},10)}};return Ut(()=>{if(n.current){let r=n.current.parentElement,c=r==null?void 0:r.getBoundingClientRect();c&&o(e,c)}return()=>{T.hidePopup(e.name,e.group)}},[e]),Oe.createElement("div",{className:"popup-proxy",ref:n})};import{jsx as se}from"react/jsx-runtime";var Jo=()=>{let e=Zt(z),t=qe((o,r,c)=>{let i=[];for(let u of o)i=i.concat(u(r,c));return i},[]);return qe((o,r,c)=>{var a;o.stopPropagation(),o.preventDefault();let i=(a=e==null?void 0:e.menuBuilders)!=null?a:[],u=t(i,r,c);console.log({menuItemDescriptors:u}),u.length&&(e!=null&&e.menuActionHandler)&&(console.log(`showContextMenu ${r}`,{options:c}),_t(o,u,e.menuActionHandler))},[t,e])},_t=(e,t,n)=>{let{clientX:o,clientY:r}=e,u=se(oe,{onClose:(a,d)=>{a&&(n(a,d),T.hidePopup())},position:{x:o,y:r},children:(a=>{let d=(l,h)=>Ke(l)?se(V,{label:l.label,children:l.children.map(d)},h):se(ee,{action:l.action,"data-icon":l.icon,options:l.options,children:l.label},h);return a.map(d)})(t)});T.showPopup({left:0,top:0,component:u})};export{oe as ContextMenu,z as ContextMenuContext,yo as ContextMenuProvider,In as Dialog,J as DialogService,ee as MenuItem,V as MenuItemGroup,So as Popup,T as PopupService,G as Portal,Ee as Separator,pe as createContainer,un as installTheme,Ke as isGroupMenuItemDescriptor,F as renderPortal,Jo as useContextMenu};
1
+ // src/dialog/Dialog.tsx
2
+ import { Scrim, Toolbar, ToolbarButton } from "@heswell/salt-lab";
3
+ import { Text } from "@salt-ds/core";
4
+ import cx from "classnames";
5
+ import { useCallback, useRef, useState } from "react";
6
+
7
+ // src/portal/Portal.tsx
8
+ import { useLayoutEffect, useMemo } from "react";
9
+ import * as ReactDOM2 from "react-dom";
10
+
11
+ // src/portal/render-portal.tsx
12
+ import * as ReactDOM from "react-dom";
13
+ import { SaltProvider } from "@salt-ds/core";
14
+ import { jsx } from "react/jsx-runtime";
15
+ var containerId = 1;
16
+ var getPortalContainer = (x = 0, y = 0, win = window) => {
17
+ const el = win.document.createElement("div");
18
+ el.className = "vuuPopup " + containerId++;
19
+ el.style.cssText = `left:${x}px; top:${y}px;`;
20
+ win.document.body.appendChild(el);
21
+ return el;
22
+ };
23
+ var createDOMContainer = (x, y) => getPortalContainer(x, y);
24
+ var renderPortal = (component, container, x, y, onRender) => {
25
+ container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
26
+ ReactDOM.render(
27
+ /* @__PURE__ */ jsx(SaltProvider, { applyClassesTo: "child", children: component }),
28
+ container,
29
+ onRender
30
+ );
31
+ };
32
+ var createContainer = createDOMContainer;
33
+
34
+ // src/portal/Portal.tsx
35
+ var Portal = function Portal2({
36
+ children,
37
+ x = 0,
38
+ y = 0,
39
+ onRender
40
+ }) {
41
+ const renderContainer = useMemo(() => {
42
+ return createContainer();
43
+ }, []);
44
+ useLayoutEffect(() => {
45
+ renderPortal(children, renderContainer, x, y, onRender);
46
+ }, [children, onRender, renderContainer, x, y]);
47
+ useLayoutEffect(() => {
48
+ return () => {
49
+ var _a;
50
+ if (renderContainer) {
51
+ ReactDOM2.unmountComponentAtNode(renderContainer);
52
+ if (renderContainer.classList.contains("vuuPopup")) {
53
+ (_a = renderContainer.parentElement) == null ? void 0 : _a.removeChild(renderContainer);
54
+ }
55
+ }
56
+ };
57
+ }, [renderContainer]);
58
+ return null;
59
+ };
60
+
61
+ // src/portal/portal-utils.ts
62
+ var installTheme = (themeId) => {
63
+ const installedThemes = getComputedStyle(document.body).getPropertyValue(
64
+ "--installed-themes"
65
+ );
66
+ document.body.style.setProperty(
67
+ "--installed-themes",
68
+ `${installedThemes} ${themeId}`
69
+ );
70
+ };
71
+
72
+ // src/dialog/Dialog.tsx
73
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
74
+ var classBase = "vuuDialog";
75
+ var Dialog = ({
76
+ children,
77
+ className,
78
+ isOpen = false,
79
+ onClose,
80
+ title,
81
+ ...props
82
+ }) => {
83
+ const root = useRef(null);
84
+ const [posX] = useState(0);
85
+ const [posY] = useState(0);
86
+ const close = useCallback(() => {
87
+ onClose == null ? void 0 : onClose();
88
+ }, [onClose]);
89
+ const handleRender = useCallback(() => {
90
+ }, []);
91
+ if (!isOpen) {
92
+ return null;
93
+ }
94
+ return /* @__PURE__ */ jsx2(Portal, { onRender: handleRender, x: posX, y: posY, children: /* @__PURE__ */ jsx2(Scrim, { className: `${classBase}-scrim`, open: isOpen, children: /* @__PURE__ */ jsxs("div", { ...props, className: cx(classBase, className), ref: root, children: [
95
+ /* @__PURE__ */ jsxs(Toolbar, { className: `${classBase}-header`, children: [
96
+ /* @__PURE__ */ jsx2(Text, { children: title }),
97
+ /* @__PURE__ */ jsx2(
98
+ ToolbarButton,
99
+ {
100
+ onClick: close,
101
+ "data-align-end": true,
102
+ "data-icon": "close"
103
+ },
104
+ "close"
105
+ )
106
+ ] }),
107
+ children
108
+ ] }) }) });
109
+ };
110
+
111
+ // src/menu/ContextMenu.tsx
112
+ import { useIdMemo as useId2 } from "@salt-ds/core";
113
+ import { useCallback as useCallback5, useRef as useRef5 } from "react";
114
+
115
+ // src/menu/MenuList.tsx
116
+ import React2, {
117
+ useLayoutEffect as useLayoutEffect2,
118
+ useMemo as useMemo4,
119
+ useRef as useRef3
120
+ } from "react";
121
+ import cx2 from "classnames";
122
+ import { useIdMemo as useId } from "@salt-ds/core";
123
+
124
+ // src/menu/use-keyboard-navigation.ts
125
+ import {
126
+ useCallback as useCallback2,
127
+ useMemo as useMemo2,
128
+ useRef as useRef2,
129
+ useState as useState2
130
+ } from "react";
131
+
132
+ // src/menu/utils.ts
133
+ var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
134
+ var hasPopup = (el, idx) => {
135
+ var _a;
136
+ return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-idx='${idx}'][aria-haspopup='true']`) !== null;
137
+ };
138
+
139
+ // src/menu/key-code.ts
140
+ function union(set1, ...sets) {
141
+ const result = new Set(set1);
142
+ for (const set of sets) {
143
+ for (const element of set) {
144
+ result.add(element);
145
+ }
146
+ }
147
+ return result;
148
+ }
149
+ var Enter = "Enter";
150
+ var Delete = "Delete";
151
+ var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
152
+ var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
153
+ var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
154
+ var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
155
+ var horizontalNavigationKeys = /* @__PURE__ */ new Set([
156
+ "Home",
157
+ "End",
158
+ "ArrowRight",
159
+ "ArrowLeft"
160
+ ]);
161
+ var functionKeys = /* @__PURE__ */ new Set([
162
+ "F1",
163
+ "F2",
164
+ "F3",
165
+ "F4",
166
+ "F5",
167
+ "F6",
168
+ "F7",
169
+ "F8",
170
+ "F9",
171
+ "F10",
172
+ "F11",
173
+ "F12"
174
+ ]);
175
+ var specialKeys = union(
176
+ actionKeys,
177
+ horizontalNavigationKeys,
178
+ verticalNavigationKeys,
179
+ arrowLeftRightKeys,
180
+ functionKeys,
181
+ focusKeys
182
+ );
183
+ var isNavigationKey = ({ key }, orientation = "vertical") => {
184
+ const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
185
+ return navigationKeys.has(key);
186
+ };
187
+
188
+ // src/menu/use-keyboard-navigation.ts
189
+ var useKeyboardNavigation = ({
190
+ autoHighlightFirstItem = false,
191
+ count,
192
+ highlightedIndex: highlightedIndexProp,
193
+ onActivate,
194
+ onHighlight,
195
+ // onKeyDown,
196
+ onCloseMenu,
197
+ onOpenMenu
198
+ }) => {
199
+ const highlightedIndexRef = useRef2(
200
+ (highlightedIndexProp != null ? highlightedIndexProp : autoHighlightFirstItem) ? 0 : -1
201
+ );
202
+ const [, forceRender] = useState2(null);
203
+ const controlledHighlighting = highlightedIndexProp !== void 0;
204
+ const setHighlightedIdx = useCallback2(
205
+ (idx) => {
206
+ highlightedIndexRef.current = idx;
207
+ onHighlight == null ? void 0 : onHighlight(idx);
208
+ forceRender({});
209
+ },
210
+ [onHighlight]
211
+ );
212
+ const setHighlightedIndex = useCallback2(
213
+ (idx) => {
214
+ if (idx !== highlightedIndexRef.current) {
215
+ if (!controlledHighlighting) {
216
+ setHighlightedIdx(idx);
217
+ }
218
+ }
219
+ },
220
+ [controlledHighlighting, setHighlightedIdx]
221
+ );
222
+ const keyBoardNavigation = useRef2(true);
223
+ const ignoreFocus = useRef2(false);
224
+ const setIgnoreFocus = (value) => ignoreFocus.current = value;
225
+ const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
226
+ const navigateChildldItems = useCallback2(
227
+ (e) => {
228
+ const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
229
+ if (nextIdx !== highlightedIndexRef.current) {
230
+ setHighlightedIndex(nextIdx);
231
+ }
232
+ },
233
+ [count, setHighlightedIndex]
234
+ );
235
+ const handleKeyDown = useCallback2(
236
+ (e) => {
237
+ if (isNavigationKey(e)) {
238
+ e.preventDefault();
239
+ e.stopPropagation();
240
+ keyBoardNavigation.current = true;
241
+ navigateChildldItems(e);
242
+ } else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
243
+ onOpenMenu(highlightedIndex);
244
+ } else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
245
+ onCloseMenu(highlightedIndex);
246
+ } else if (e.key === "Enter") {
247
+ onActivate && onActivate(highlightedIndex);
248
+ }
249
+ },
250
+ [
251
+ highlightedIndex,
252
+ navigateChildldItems,
253
+ onActivate,
254
+ onCloseMenu,
255
+ onOpenMenu
256
+ ]
257
+ );
258
+ const listProps = useMemo2(
259
+ () => ({
260
+ onFocus: () => {
261
+ if (highlightedIndex === -1) {
262
+ setHighlightedIdx(0);
263
+ }
264
+ },
265
+ onKeyDown: handleKeyDown,
266
+ onMouseDownCapture: () => {
267
+ keyBoardNavigation.current = false;
268
+ setIgnoreFocus(true);
269
+ },
270
+ // onMouseEnter would seem less expensive but it misses some cases
271
+ onMouseMove: () => {
272
+ if (keyBoardNavigation.current) {
273
+ keyBoardNavigation.current = false;
274
+ }
275
+ },
276
+ onMouseLeave: () => {
277
+ keyBoardNavigation.current = true;
278
+ setIgnoreFocus(false);
279
+ setHighlightedIndex(-1);
280
+ }
281
+ }),
282
+ [
283
+ highlightedIndex,
284
+ setHighlightedIndex,
285
+ navigateChildldItems,
286
+ onActivate,
287
+ onCloseMenu,
288
+ onOpenMenu,
289
+ setHighlightedIdx
290
+ ]
291
+ );
292
+ return {
293
+ focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
294
+ controlledHighlighting,
295
+ highlightedIndex,
296
+ setHighlightedIndex,
297
+ // keyBoardNavigation,
298
+ listProps,
299
+ setIgnoreFocus
300
+ };
301
+ };
302
+ function nextItemIdx(count, key, idx) {
303
+ if (key === "ArrowUp") {
304
+ if (idx > 0) {
305
+ return idx - 1;
306
+ } else {
307
+ return idx;
308
+ }
309
+ } else {
310
+ if (idx === null) {
311
+ return 0;
312
+ } else if (idx === count - 1) {
313
+ return idx;
314
+ } else {
315
+ return idx + 1;
316
+ }
317
+ }
318
+ }
319
+
320
+ // src/menu/use-items-with-ids.ts
321
+ import React, { useCallback as useCallback3, useMemo as useMemo3 } from "react";
322
+ var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
323
+ var useItemsWithIds = (childrenProp) => {
324
+ const normalizeChildren = useCallback3(() => {
325
+ const collectChildren = (children, path = "root", menus2 = {}, actions2 = {}) => {
326
+ const list = menus2[path] = [];
327
+ let idx = 0;
328
+ let hasSeparator = false;
329
+ React.Children.forEach(children, (child) => {
330
+ if (child.type === Separator) {
331
+ hasSeparator = true;
332
+ } else {
333
+ const group = isMenuItemGroup(child);
334
+ const childPath = path === "root" ? `${idx}` : `${path}.${idx}`;
335
+ const {
336
+ props: { action, options }
337
+ } = child;
338
+ const { childWithId, grandChildren } = assignId(
339
+ child,
340
+ childPath,
341
+ group,
342
+ hasSeparator
343
+ );
344
+ list.push(childWithId);
345
+ if (grandChildren) {
346
+ collectChildren(grandChildren, childPath, menus2, actions2);
347
+ } else {
348
+ actions2[childPath] = { action, options };
349
+ }
350
+ idx += 1;
351
+ hasSeparator = false;
352
+ }
353
+ });
354
+ return [menus2, actions2];
355
+ };
356
+ const assignId = (child, path, group, hasSeparator = false) => {
357
+ const {
358
+ props: { children }
359
+ } = child;
360
+ return {
361
+ childWithId: React.cloneElement(child, {
362
+ hasSeparator,
363
+ id: `${path}`,
364
+ key: path,
365
+ children: group ? void 0 : children
366
+ }),
367
+ grandChildren: group ? children : void 0
368
+ };
369
+ };
370
+ return collectChildren(childrenProp);
371
+ }, [childrenProp]);
372
+ const [menus, actions] = useMemo3(
373
+ () => normalizeChildren(),
374
+ [normalizeChildren]
375
+ );
376
+ return [menus, actions];
377
+ };
378
+
379
+ // src/menu/MenuList.tsx
380
+ import { jsx as jsx3 } from "react/jsx-runtime";
381
+ var classBase2 = "vuuMenuList";
382
+ var Separator = () => /* @__PURE__ */ jsx3("li", { className: "vuuMenuItem-divider" });
383
+ var MenuItemGroup = () => null;
384
+ var MenuItem = ({ children, idx, ...props }) => {
385
+ return /* @__PURE__ */ jsx3("div", { ...props, children });
386
+ };
387
+ var hasIcon = (child) => child.props["data-icon"];
388
+ var MenuList = ({
389
+ activatedByKeyboard,
390
+ childMenuShowing = -1,
391
+ children,
392
+ className,
393
+ highlightedIdx: highlightedIdxProp,
394
+ id: idProp,
395
+ isRoot: isRoot2,
396
+ listItemProps,
397
+ menuId,
398
+ onHighlightMenuItem,
399
+ onActivate,
400
+ onCloseMenu,
401
+ onOpenMenu,
402
+ ...props
403
+ }) => {
404
+ const id = useId(idProp);
405
+ const root = useRef3(null);
406
+ const mapIdxToId = useMemo4(() => /* @__PURE__ */ new Map(), []);
407
+ const handleOpenMenu = (idx) => {
408
+ var _a;
409
+ const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-idx='${idx}']`);
410
+ (el == null ? void 0 : el.id) && (onOpenMenu == null ? void 0 : onOpenMenu(el.id));
411
+ };
412
+ const handleActivate = (idx) => {
413
+ var _a;
414
+ const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-idx='${idx}']`);
415
+ (el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
416
+ };
417
+ const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
418
+ count: React2.Children.count(children),
419
+ highlightedIndex: highlightedIdxProp,
420
+ onActivate: handleActivate,
421
+ onHighlight: onHighlightMenuItem,
422
+ onOpenMenu: handleOpenMenu,
423
+ onCloseMenu
424
+ });
425
+ const appliedFocusVisible = childMenuShowing == -1 ? focusVisible : -1;
426
+ useLayoutEffect2(() => {
427
+ var _a;
428
+ if (childMenuShowing === -1 && activatedByKeyboard) {
429
+ (_a = root.current) == null ? void 0 : _a.focus();
430
+ }
431
+ }, [activatedByKeyboard, childMenuShowing]);
432
+ const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
433
+ return /* @__PURE__ */ jsx3(
434
+ "div",
435
+ {
436
+ ...props,
437
+ ...listProps,
438
+ "aria-activedescendant": getActiveDescendant(),
439
+ className: cx2(classBase2, className, {
440
+ [`${classBase2}-childMenuShowing`]: childMenuShowing !== -1
441
+ }),
442
+ "data-root": isRoot2 || void 0,
443
+ id: `${id}-${menuId}`,
444
+ ref: root,
445
+ role: "menu",
446
+ tabIndex: 0,
447
+ children: renderContent()
448
+ }
449
+ );
450
+ function renderContent() {
451
+ const propsCommonToAllListItems = {
452
+ ...listItemProps,
453
+ role: "menuitem"
454
+ };
455
+ const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
456
+ /* @__PURE__ */ jsx3(
457
+ "span",
458
+ {
459
+ className: "vuuIconContainer",
460
+ "data-icon": iconName
461
+ },
462
+ "icon"
463
+ )
464
+ ].concat(childElement) : childElement;
465
+ function addClonedChild(list, child, idx, withIcon) {
466
+ var _a;
467
+ const {
468
+ children: children2,
469
+ className: className2,
470
+ "data-icon": iconName,
471
+ id: itemId,
472
+ hasSeparator,
473
+ label,
474
+ ...props2
475
+ } = child.props;
476
+ const hasSubMenu = isMenuItemGroup(child);
477
+ const subMenuShowing = hasSubMenu && childMenuShowing === idx;
478
+ const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
479
+ list.push(
480
+ /* @__PURE__ */ jsx3(
481
+ MenuItem,
482
+ {
483
+ ...props2,
484
+ ...propsCommonToAllListItems,
485
+ ...getMenuItemProps(
486
+ `${id}-${menuId}`,
487
+ itemId,
488
+ idx,
489
+ (_a = child.key) != null ? _a : itemId,
490
+ highlightedIndex,
491
+ appliedFocusVisible,
492
+ className2,
493
+ hasSeparator
494
+ ),
495
+ "aria-controls": ariaControls,
496
+ "aria-haspopup": hasSubMenu || void 0,
497
+ "aria-expanded": subMenuShowing || void 0,
498
+ children: hasSubMenu ? maybeIcon(label, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
499
+ }
500
+ )
501
+ );
502
+ }
503
+ const listItems = [];
504
+ if (children.length > 0) {
505
+ const withIcon = children.some(hasIcon);
506
+ children.forEach((child, idx) => {
507
+ addClonedChild(listItems, child, idx, withIcon);
508
+ });
509
+ }
510
+ return listItems;
511
+ }
512
+ };
513
+ var getMenuItemProps = (baseId, itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
514
+ id: `${baseId}-${itemId}`,
515
+ key: key != null ? key : idx,
516
+ "data-idx": idx,
517
+ "data-highlighted": idx === highlightedIdx || void 0,
518
+ className: cx2("vuuMenuItem", className, {
519
+ "vuuMenuItem-separator": hasSeparator,
520
+ focusVisible: focusVisible === idx
521
+ })
522
+ });
523
+ MenuList.displayName = "MenuList";
524
+ var MenuList_default = MenuList;
525
+
526
+ // src/menu/use-cascade.ts
527
+ import {
528
+ useCallback as useCallback4,
529
+ useMemo as useMemo5,
530
+ useRef as useRef4,
531
+ useState as useState3
532
+ } from "react";
533
+
534
+ // src/menu/list-dom-utils.ts
535
+ function listItemIndex(listItemEl) {
536
+ if (listItemEl) {
537
+ const idx = listItemEl.dataset.idx;
538
+ if (idx) {
539
+ return parseInt(idx, 10);
540
+ } else if (listItemEl.ariaPosInSet) {
541
+ return parseInt(listItemEl.ariaPosInSet, 10) - 1;
542
+ }
543
+ }
544
+ }
545
+ var closestListItem = (el) => el == null ? void 0 : el.closest("[data-idx],[aria-posinset]");
546
+
547
+ // src/menu/use-cascade.ts
548
+ var nudge = (menus, distance, pos) => {
549
+ return menus.map(
550
+ (m, i) => i === menus.length - 1 ? {
551
+ ...m,
552
+ [pos]: m[pos] - distance
553
+ } : m
554
+ );
555
+ };
556
+ var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
557
+ var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
558
+ var flipSides = (id, menus) => {
559
+ const [parentMenu, menu] = menus.slice(-2);
560
+ const el = document.getElementById(`${id}-${menu.id}`);
561
+ if (el === null) {
562
+ throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
563
+ }
564
+ const { width } = el.getBoundingClientRect();
565
+ return menus.map(
566
+ (m) => m === menu ? {
567
+ ...m,
568
+ left: parentMenu.left - (width - 2)
569
+ } : m
570
+ );
571
+ };
572
+ var getPosition = (el, openMenus) => {
573
+ const [{ left, top: menuTop }] = openMenus.slice(-1);
574
+ const { offsetWidth: width, offsetTop: top } = el;
575
+ return { left: left + width, top: top + menuTop };
576
+ };
577
+ var getItemId = (id) => {
578
+ const pos = id.lastIndexOf("-");
579
+ return pos === -1 ? id : id.slice(pos + 1);
580
+ };
581
+ var getMenuId = (id) => {
582
+ const itemId = getItemId(id);
583
+ const pos = itemId.lastIndexOf(".");
584
+ return pos > -1 ? itemId.slice(0, pos) : "root";
585
+ };
586
+ var getMenuDepth = (id) => {
587
+ let count = 0, pos = id.indexOf(".", 0);
588
+ while (pos !== -1) {
589
+ count += 1;
590
+ pos = id.indexOf(".", pos + 1);
591
+ }
592
+ return count;
593
+ };
594
+ var identifyItem = (el) => ({
595
+ menuId: getMenuId(el.id),
596
+ itemId: getItemId(el.id),
597
+ isGroup: el.ariaHasPopup === "true",
598
+ isOpen: el.ariaExpanded === "true",
599
+ level: getMenuDepth(el.id)
600
+ });
601
+ var useCascade = ({
602
+ id,
603
+ onActivate,
604
+ onMouseEnterItem,
605
+ position: { x: posX, y: posY }
606
+ }) => {
607
+ const [, forceRefresh] = useState3({});
608
+ const openMenus = useRef4([
609
+ { id: "root", left: posX, top: posY }
610
+ ]);
611
+ const setOpenMenus = useCallback4((menus) => {
612
+ openMenus.current = menus;
613
+ forceRefresh({});
614
+ }, []);
615
+ const menuOpenPendingTimeout = useRef4();
616
+ const menuClosePendingTimeout = useRef4();
617
+ const menuState = useRef4({ root: "no-popup" });
618
+ const prevLevel = useRef4(0);
619
+ const openMenu = useCallback4(
620
+ (menuId = "root", itemId = null, listItemEl = null) => {
621
+ if (menuId === "root" && itemId === null) {
622
+ setOpenMenus([{ id: "root", left: posX, top: posY }]);
623
+ } else {
624
+ menuState.current[menuId] = "popup-open";
625
+ const doc = listItemEl ? listItemEl.ownerDocument : document;
626
+ const el = doc.getElementById(`${id}-${menuId}-${itemId}`);
627
+ const { left, top } = getPosition(el, openMenus.current);
628
+ setOpenMenus(openMenus.current.concat({ id: itemId, left, top }));
629
+ }
630
+ },
631
+ [id, posX, posY, setOpenMenus]
632
+ );
633
+ const closeMenu = useCallback4(
634
+ (menuId) => {
635
+ if (menuId === "root") {
636
+ setOpenMenus([]);
637
+ } else {
638
+ setOpenMenus(openMenus.current.slice(0, -1));
639
+ }
640
+ },
641
+ [setOpenMenus]
642
+ );
643
+ const closeMenus = useCallback4(
644
+ (menuId, itemId) => {
645
+ const menus = openMenus.current.slice();
646
+ let { id: lastMenuId } = menus[menus.length - 1];
647
+ while (menus.length > 1 && !itemId.startsWith(lastMenuId)) {
648
+ const parentMenuId = getMenuId(lastMenuId);
649
+ menus.pop();
650
+ menuState.current[lastMenuId] = "no-popup";
651
+ menuState.current[parentMenuId] = "no-popup";
652
+ ({ id: lastMenuId } = menus[menus.length - 1]);
653
+ }
654
+ if (menus.length < openMenus.current.length) {
655
+ setOpenMenus(menus);
656
+ }
657
+ },
658
+ [setOpenMenus]
659
+ );
660
+ const scheduleOpen = useCallback4(
661
+ (menuId, itemId, listItemEl) => {
662
+ if (menuOpenPendingTimeout.current) {
663
+ clearTimeout(menuOpenPendingTimeout.current);
664
+ }
665
+ menuOpenPendingTimeout.current = window.setTimeout(() => {
666
+ console.log(`scheduleOpen timed out opening ${itemId}`);
667
+ closeMenus(menuId, itemId);
668
+ menuState.current[menuId] = "popup-open";
669
+ menuState.current[itemId] = "no-popup";
670
+ openMenu(menuId, itemId, listItemEl);
671
+ }, 400);
672
+ },
673
+ [closeMenus, openMenu]
674
+ );
675
+ const scheduleClose = useCallback4(
676
+ (openMenuId, menuId, itemId) => {
677
+ console.log(
678
+ `scheduleClose openMenuId ${openMenuId} menuId ${menuId} itemId ${itemId}`
679
+ );
680
+ menuState.current[openMenuId] = "pending-close";
681
+ menuClosePendingTimeout.current = window.setTimeout(() => {
682
+ closeMenus(menuId, itemId);
683
+ }, 400);
684
+ },
685
+ [closeMenus]
686
+ );
687
+ const handleRender = useCallback4(() => {
688
+ const { current: menus } = openMenus;
689
+ const [menu] = menus.slice(-1);
690
+ const el = document.getElementById(`${id}-${menu.id}`);
691
+ if (el) {
692
+ const { right, bottom } = el.getBoundingClientRect();
693
+ const { clientHeight, clientWidth } = document.body;
694
+ if (right > clientWidth) {
695
+ const newMenus = menus.length > 1 ? flipSides(id, menus) : nudgeLeft(menus, right - clientWidth);
696
+ setOpenMenus(newMenus);
697
+ } else if (bottom > clientHeight) {
698
+ const newMenus = nudgeUp(menus, bottom - clientHeight);
699
+ setOpenMenus(newMenus);
700
+ }
701
+ }
702
+ }, [id, setOpenMenus]);
703
+ const listItemProps = useMemo5(
704
+ () => ({
705
+ onMouseEnter: (evt) => {
706
+ const listItemEl = closestListItem(evt.target);
707
+ const { menuId, itemId, isGroup, isOpen, level } = identifyItem(listItemEl);
708
+ const sameLevel = prevLevel.current === level;
709
+ const {
710
+ current: { [menuId]: state }
711
+ } = menuState;
712
+ prevLevel.current = level;
713
+ if (state === "no-popup" && isGroup) {
714
+ menuState.current[menuId] = "popup-pending";
715
+ scheduleOpen(menuId, itemId, listItemEl);
716
+ } else if (state === "popup-pending" && !isGroup) {
717
+ menuState.current[menuId] = "no-popup";
718
+ clearTimeout(menuOpenPendingTimeout.current);
719
+ menuOpenPendingTimeout.current = void 0;
720
+ } else if (state === "popup-pending" && isGroup) {
721
+ clearTimeout(menuOpenPendingTimeout.current);
722
+ scheduleOpen(menuId, itemId, listItemEl);
723
+ } else if (state === "popup-open") {
724
+ const [{ id: parentMenuId }, { id: openMenuId }] = openMenus.current.slice(-2);
725
+ if (parentMenuId === menuId && menuState.current[openMenuId] !== "pending-close" && sameLevel) {
726
+ scheduleClose(openMenuId, menuId, itemId);
727
+ if (isGroup && !isOpen) {
728
+ scheduleOpen(menuId, itemId, listItemEl);
729
+ }
730
+ } else if (parentMenuId === menuId && isGroup && itemId !== openMenuId && menuState.current[openMenuId] === "pending-close") {
731
+ scheduleOpen(menuId, itemId, listItemEl);
732
+ } else if (isGroup) {
733
+ closeMenus(menuId, itemId);
734
+ scheduleOpen(menuId, itemId, listItemEl);
735
+ } else if (!(menuState.current[openMenuId] === "pending-close" && sameLevel)) {
736
+ closeMenus(menuId, itemId);
737
+ }
738
+ }
739
+ if (state === "pending-close") {
740
+ if (menuOpenPendingTimeout.current) {
741
+ clearTimeout(menuOpenPendingTimeout.current);
742
+ menuOpenPendingTimeout.current = void 0;
743
+ }
744
+ clearTimeout(menuClosePendingTimeout.current);
745
+ menuClosePendingTimeout.current = void 0;
746
+ menuState.current[menuId] = "popup-open";
747
+ }
748
+ onMouseEnterItem(evt, itemId);
749
+ },
750
+ onClick: (evt) => {
751
+ const targetElement = evt.target;
752
+ const listItemEl = closestListItem(targetElement);
753
+ const idx = listItemIndex(listItemEl);
754
+ console.log(
755
+ `list item click [${idx}] hasPopup ${listItemEl.ariaHasPopup}`
756
+ );
757
+ if (listItemEl.ariaHasPopup === "true") {
758
+ if (listItemEl.ariaExpanded !== "true") {
759
+ openMenu(idx);
760
+ } else {
761
+ }
762
+ } else {
763
+ onActivate(getItemId(listItemEl.id));
764
+ }
765
+ }
766
+ }),
767
+ [
768
+ closeMenus,
769
+ onActivate,
770
+ onMouseEnterItem,
771
+ openMenu,
772
+ scheduleClose,
773
+ scheduleOpen
774
+ ]
775
+ );
776
+ return {
777
+ closeMenu,
778
+ handleRender,
779
+ listItemProps,
780
+ openMenu,
781
+ openMenus: openMenus.current
782
+ };
783
+ };
784
+
785
+ // src/menu/use-click-away.ts
786
+ import { useEffect } from "react";
787
+ var useClickAway = ({
788
+ containerClassName,
789
+ isOpen,
790
+ onClose
791
+ }) => {
792
+ useEffect(() => {
793
+ let clickHandler;
794
+ if (isOpen) {
795
+ clickHandler = (evt) => {
796
+ const target = evt.target;
797
+ const container = target.closest(`.${containerClassName}`);
798
+ if (container === null) {
799
+ onClose == null ? void 0 : onClose("root");
800
+ }
801
+ };
802
+ document.body.addEventListener("click", clickHandler, true);
803
+ }
804
+ return () => {
805
+ if (clickHandler) {
806
+ document.body.removeEventListener("click", clickHandler, true);
807
+ }
808
+ };
809
+ }, [containerClassName, isOpen, onClose]);
810
+ };
811
+
812
+ // src/menu/ContextMenu.tsx
813
+ import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
814
+ import { createElement } from "react";
815
+ var noop = () => void 0;
816
+ var ContextMenu = ({
817
+ activatedByKeyboard,
818
+ children: childrenProp,
819
+ className,
820
+ id: idProp,
821
+ onClose = () => void 0,
822
+ position = { x: 0, y: 0 },
823
+ style,
824
+ ...menuListProps
825
+ }) => {
826
+ const id = useId2(idProp);
827
+ const closeMenuRef = useRef5(noop);
828
+ const [menus, actions] = useItemsWithIds(childrenProp);
829
+ const navigatingWithKeyboard = useRef5(activatedByKeyboard);
830
+ const handleMouseEnterItem = useCallback5(() => {
831
+ navigatingWithKeyboard.current = false;
832
+ }, []);
833
+ const handleActivate = useCallback5(
834
+ (menuId) => {
835
+ const { action, options } = actions[menuId];
836
+ closeMenuRef.current("root");
837
+ onClose(action, options);
838
+ },
839
+ [actions, onClose]
840
+ );
841
+ const { closeMenu, listItemProps, openMenu, openMenus, handleRender } = useCascade({
842
+ id,
843
+ onActivate: handleActivate,
844
+ onMouseEnterItem: handleMouseEnterItem,
845
+ position
846
+ });
847
+ closeMenuRef.current = closeMenu;
848
+ console.log({ openMenus });
849
+ const handleClose = useCallback5(() => {
850
+ closeMenu();
851
+ onClose();
852
+ }, [closeMenu, onClose]);
853
+ useClickAway({
854
+ containerClassName: "vuuMenuList",
855
+ onClose: handleClose,
856
+ isOpen: openMenus.length > 0
857
+ });
858
+ const handleOpenMenu = (id2) => {
859
+ const itemId = getItemId(id2);
860
+ const menuId = getMenuId(itemId);
861
+ navigatingWithKeyboard.current = true;
862
+ openMenu(menuId, itemId);
863
+ };
864
+ const handleCloseMenu = () => {
865
+ navigatingWithKeyboard.current = true;
866
+ closeMenu();
867
+ };
868
+ const handleHighlightMenuItem = () => {
869
+ };
870
+ const lastMenu = openMenus.length - 1;
871
+ const getChildMenuIndex = (i) => {
872
+ if (i >= lastMenu) {
873
+ return -1;
874
+ } else {
875
+ const { id: menuId } = openMenus[i + 1];
876
+ const pos = menuId.lastIndexOf(".");
877
+ const idx = pos === -1 ? parseInt(menuId, 10) : parseInt(menuId.slice(-pos), 10);
878
+ return idx;
879
+ }
880
+ };
881
+ return /* @__PURE__ */ jsx4(Fragment, { children: openMenus.map(({ id: menuId, left, top }, i) => {
882
+ const childMenuIndex = getChildMenuIndex(i);
883
+ return /* @__PURE__ */ jsx4(Portal, { x: left, y: top, onRender: handleRender, children: /* @__PURE__ */ createElement(
884
+ MenuList_default,
885
+ {
886
+ ...menuListProps,
887
+ activatedByKeyboard: navigatingWithKeyboard.current,
888
+ childMenuShowing: childMenuIndex,
889
+ className,
890
+ id,
891
+ menuId,
892
+ isRoot: i === 0,
893
+ key: i,
894
+ listItemProps,
895
+ onActivate: handleActivate,
896
+ onHighlightMenuItem: handleHighlightMenuItem,
897
+ onCloseMenu: handleCloseMenu,
898
+ onOpenMenu: handleOpenMenu,
899
+ style
900
+ },
901
+ menus[menuId]
902
+ ) }, i);
903
+ }) });
904
+ };
905
+ ContextMenu.displayName = "ContextMenu";
906
+
907
+ // src/menu/context-menu-provider.tsx
908
+ import { createContext, useCallback as useCallback6, useMemo as useMemo6 } from "react";
909
+ import { jsx as jsx5 } from "react/jsx-runtime";
910
+ var ContextMenuContext = createContext(
911
+ null
912
+ );
913
+ var isGroupMenuItemDescriptor = (menuItem) => menuItem !== void 0 && "children" in menuItem;
914
+ var Provider = ({
915
+ children,
916
+ context,
917
+ menuActionHandler,
918
+ menuBuilder
919
+ }) => {
920
+ const menuBuilders = useMemo6(() => {
921
+ if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
922
+ return context.menuBuilders.concat(menuBuilder);
923
+ } else if (menuBuilder) {
924
+ return [menuBuilder];
925
+ } else {
926
+ return (context == null ? void 0 : context.menuBuilders) || [];
927
+ }
928
+ }, [context, menuBuilder]);
929
+ const handleMenuAction = useCallback6(
930
+ (type, options) => {
931
+ var _a;
932
+ if (menuActionHandler == null ? void 0 : menuActionHandler(type, options)) {
933
+ return true;
934
+ }
935
+ if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, type, options)) {
936
+ return true;
937
+ }
938
+ },
939
+ [context, menuActionHandler]
940
+ );
941
+ return /* @__PURE__ */ jsx5(
942
+ ContextMenuContext.Provider,
943
+ {
944
+ value: {
945
+ menuActionHandler: handleMenuAction,
946
+ menuBuilders
947
+ },
948
+ children
949
+ }
950
+ );
951
+ };
952
+ var ContextMenuProvider = ({
953
+ children,
954
+ label,
955
+ menuActionHandler,
956
+ menuBuilder
957
+ }) => {
958
+ return /* @__PURE__ */ jsx5(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ jsx5(
959
+ Provider,
960
+ {
961
+ context: parentContext,
962
+ label,
963
+ menuActionHandler,
964
+ menuBuilder,
965
+ children
966
+ }
967
+ ) });
968
+ };
969
+
970
+ // src/menu/useContextMenu.tsx
971
+ import { useCallback as useCallback7, useContext } from "react";
972
+
973
+ // src/popup/popup-service.ts
974
+ import cx3 from "classnames";
975
+ import React3, {
976
+ createElement as createElement2,
977
+ useEffect as useEffect2,
978
+ useRef as useRef6
979
+ } from "react";
980
+ import ReactDOM3 from "react-dom";
981
+ var _dialogOpen = false;
982
+ var _popups = [];
983
+ function specialKeyHandler(e) {
984
+ if (e.key === "Esc") {
985
+ if (_popups.length) {
986
+ closeAllPopups();
987
+ } else if (_dialogOpen) {
988
+ const dialogRoot = document.body.querySelector(".vuuDialog");
989
+ if (dialogRoot) {
990
+ ReactDOM3.unmountComponentAtNode(dialogRoot);
991
+ }
992
+ }
993
+ }
994
+ }
995
+ function outsideClickHandler(e) {
996
+ if (_popups.length) {
997
+ const popupContainers = document.body.querySelectorAll(".vuuPopup");
998
+ for (let i = 0; i < popupContainers.length; i++) {
999
+ if (popupContainers[i].contains(e.target)) {
1000
+ return;
1001
+ }
1002
+ }
1003
+ closeAllPopups();
1004
+ }
1005
+ }
1006
+ function closeAllPopups() {
1007
+ if (_popups.length) {
1008
+ const popupContainers = document.body.querySelectorAll(".vuuPopup");
1009
+ for (let i = 0; i < popupContainers.length; i++) {
1010
+ ReactDOM3.unmountComponentAtNode(popupContainers[i]);
1011
+ }
1012
+ popupClosed("*");
1013
+ }
1014
+ }
1015
+ function dialogOpened() {
1016
+ if (_dialogOpen === false) {
1017
+ _dialogOpen = true;
1018
+ window.addEventListener("keydown", specialKeyHandler, true);
1019
+ }
1020
+ }
1021
+ function dialogClosed() {
1022
+ if (_dialogOpen) {
1023
+ _dialogOpen = false;
1024
+ window.removeEventListener("keydown", specialKeyHandler, true);
1025
+ }
1026
+ }
1027
+ function popupOpened(name) {
1028
+ if (_popups.indexOf(name) === -1) {
1029
+ _popups.push(name);
1030
+ if (_dialogOpen === false) {
1031
+ window.addEventListener("keydown", specialKeyHandler, true);
1032
+ window.addEventListener("click", outsideClickHandler, true);
1033
+ }
1034
+ }
1035
+ }
1036
+ function popupClosed(name) {
1037
+ if (_popups.length) {
1038
+ if (name === "*") {
1039
+ _popups.length = 0;
1040
+ } else {
1041
+ const pos = _popups.indexOf(name);
1042
+ if (pos !== -1) {
1043
+ _popups.splice(pos, 1);
1044
+ }
1045
+ }
1046
+ if (_popups.length === 0 && _dialogOpen === false) {
1047
+ window.removeEventListener("keydown", specialKeyHandler, true);
1048
+ window.removeEventListener("click", outsideClickHandler, true);
1049
+ }
1050
+ }
1051
+ }
1052
+ var PopupComponent = ({
1053
+ children,
1054
+ position,
1055
+ style
1056
+ }) => {
1057
+ const className = cx3("hwPopup", "hwPopupContainer", position);
1058
+ return createElement2("div", { className, style }, children);
1059
+ };
1060
+ var incrementingKey = 1;
1061
+ var PopupService = class {
1062
+ static showPopup({
1063
+ name = "anon",
1064
+ group = "all",
1065
+ position = "",
1066
+ left = 0,
1067
+ right = "auto",
1068
+ top = 0,
1069
+ width = "auto",
1070
+ component
1071
+ }) {
1072
+ if (!component) {
1073
+ throw Error(`PopupService showPopup, no component supplied`);
1074
+ }
1075
+ popupOpened(name);
1076
+ let el = document.body.querySelector(".vuuPopup." + group);
1077
+ if (el === null) {
1078
+ el = document.createElement("div");
1079
+ el.className = "vuuPopup " + group;
1080
+ document.body.appendChild(el);
1081
+ }
1082
+ const style = { width };
1083
+ renderPortal(
1084
+ createElement2(
1085
+ PopupComponent,
1086
+ { key: incrementingKey++, position, style },
1087
+ component
1088
+ ),
1089
+ el,
1090
+ left,
1091
+ top,
1092
+ () => {
1093
+ PopupService.keepWithinThePage(el, right);
1094
+ }
1095
+ );
1096
+ }
1097
+ static hidePopup(name = "anon", group = "all") {
1098
+ if (_popups.indexOf(name) !== -1) {
1099
+ popupClosed(name);
1100
+ const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
1101
+ if (popupRoot) {
1102
+ ReactDOM3.unmountComponentAtNode(popupRoot);
1103
+ }
1104
+ }
1105
+ }
1106
+ static keepWithinThePage(el, right = "auto") {
1107
+ const target = el.querySelector(".vuuPopupContainer > *");
1108
+ if (target) {
1109
+ const {
1110
+ top,
1111
+ left,
1112
+ width,
1113
+ height,
1114
+ right: currentRight
1115
+ } = target.getBoundingClientRect();
1116
+ const w = window.innerWidth;
1117
+ const h = window.innerHeight;
1118
+ const overflowH = h - (top + height);
1119
+ if (overflowH < 0) {
1120
+ target.style.top = Math.round(top) + overflowH + "px";
1121
+ }
1122
+ const overflowW = w - (left + width);
1123
+ if (overflowW < 0) {
1124
+ target.style.left = Math.round(left) + overflowW + "px";
1125
+ }
1126
+ if (typeof right === "number" && right !== currentRight) {
1127
+ const adjustment = right - currentRight;
1128
+ target.style.left = left + adjustment + "px";
1129
+ }
1130
+ }
1131
+ }
1132
+ };
1133
+ var DialogService = class {
1134
+ static showDialog(dialog) {
1135
+ const containerEl = ".vuuDialog";
1136
+ const onClose = dialog.props.onClose;
1137
+ dialogOpened();
1138
+ ReactDOM3.render(
1139
+ React3.cloneElement(dialog, {
1140
+ container: containerEl,
1141
+ onClose: () => {
1142
+ DialogService.closeDialog();
1143
+ if (onClose) {
1144
+ onClose();
1145
+ }
1146
+ }
1147
+ }),
1148
+ document.body.querySelector(containerEl)
1149
+ );
1150
+ }
1151
+ static closeDialog() {
1152
+ dialogClosed();
1153
+ const dialogRoot = document.body.querySelector(".vuuDialog");
1154
+ if (dialogRoot) {
1155
+ ReactDOM3.unmountComponentAtNode(dialogRoot);
1156
+ }
1157
+ }
1158
+ };
1159
+ var Popup = (props) => {
1160
+ const pendingTask = useRef6();
1161
+ const ref = useRef6(null);
1162
+ const show = (props2, boundingClientRect) => {
1163
+ const { name, group, depth, width } = props2;
1164
+ let left;
1165
+ let top;
1166
+ if (pendingTask.current) {
1167
+ window.clearTimeout(pendingTask.current);
1168
+ pendingTask.current = void 0;
1169
+ }
1170
+ if (props2.close === true) {
1171
+ PopupService.hidePopup(name, group);
1172
+ } else {
1173
+ const { position, children: component } = props2;
1174
+ const {
1175
+ left: targetLeft,
1176
+ top: targetTop,
1177
+ width: clientWidth,
1178
+ bottom: targetBottom
1179
+ } = boundingClientRect;
1180
+ if (position === "below") {
1181
+ left = targetLeft;
1182
+ top = targetBottom;
1183
+ } else if (position === "above") {
1184
+ left = targetLeft;
1185
+ top = targetTop;
1186
+ }
1187
+ pendingTask.current = window.setTimeout(() => {
1188
+ PopupService.showPopup({
1189
+ name,
1190
+ group,
1191
+ depth,
1192
+ position,
1193
+ left,
1194
+ top,
1195
+ width: width || clientWidth,
1196
+ component
1197
+ });
1198
+ }, 10);
1199
+ }
1200
+ };
1201
+ useEffect2(() => {
1202
+ if (ref.current) {
1203
+ const el = ref.current.parentElement;
1204
+ const boundingClientRect = el == null ? void 0 : el.getBoundingClientRect();
1205
+ if (boundingClientRect) {
1206
+ show(props, boundingClientRect);
1207
+ }
1208
+ }
1209
+ return () => {
1210
+ PopupService.hidePopup(props.name, props.group);
1211
+ };
1212
+ }, [props]);
1213
+ return React3.createElement("div", { className: "popup-proxy", ref });
1214
+ };
1215
+
1216
+ // src/menu/useContextMenu.tsx
1217
+ import { jsx as jsx6 } from "react/jsx-runtime";
1218
+ var useContextMenu = () => {
1219
+ const ctx = useContext(ContextMenuContext);
1220
+ const buildMenuOptions = useCallback7(
1221
+ (menuBuilders, location, options) => {
1222
+ let results = [];
1223
+ for (const menuBuilder of menuBuilders) {
1224
+ results = results.concat(menuBuilder(location, options));
1225
+ }
1226
+ return results;
1227
+ },
1228
+ []
1229
+ );
1230
+ const handleShowContextMenu = useCallback7(
1231
+ (e, location, options) => {
1232
+ var _a;
1233
+ e.stopPropagation();
1234
+ e.preventDefault();
1235
+ const menuBuilders = (_a = ctx == null ? void 0 : ctx.menuBuilders) != null ? _a : [];
1236
+ const menuItemDescriptors = buildMenuOptions(
1237
+ menuBuilders,
1238
+ location,
1239
+ options
1240
+ );
1241
+ console.log({
1242
+ menuItemDescriptors
1243
+ });
1244
+ if (menuItemDescriptors.length && (ctx == null ? void 0 : ctx.menuActionHandler)) {
1245
+ console.log(`showContextMenu ${location}`, {
1246
+ options
1247
+ });
1248
+ showContextMenu(e, menuItemDescriptors, ctx.menuActionHandler);
1249
+ }
1250
+ },
1251
+ [buildMenuOptions, ctx]
1252
+ );
1253
+ return handleShowContextMenu;
1254
+ };
1255
+ var showContextMenu = (e, menuDescriptors, handleContextMenuAction) => {
1256
+ const { clientX: left, clientY: top } = e;
1257
+ const menuItems = (menuDescriptors2) => {
1258
+ const fromDescriptor = (menuItem, i) => isGroupMenuItemDescriptor(menuItem) ? /* @__PURE__ */ jsx6(MenuItemGroup, { label: menuItem.label, children: menuItem.children.map(fromDescriptor) }, i) : /* @__PURE__ */ jsx6(
1259
+ MenuItem,
1260
+ {
1261
+ action: menuItem.action,
1262
+ "data-icon": menuItem.icon,
1263
+ options: menuItem.options,
1264
+ children: menuItem.label
1265
+ },
1266
+ i
1267
+ );
1268
+ return menuDescriptors2.map(fromDescriptor);
1269
+ };
1270
+ const handleClose = (menuId, options) => {
1271
+ if (menuId) {
1272
+ handleContextMenuAction(menuId, options);
1273
+ PopupService.hidePopup();
1274
+ }
1275
+ };
1276
+ const component = /* @__PURE__ */ jsx6(ContextMenu, { onClose: handleClose, position: { x: left, y: top }, children: menuItems(menuDescriptors) });
1277
+ PopupService.showPopup({ left: 0, top: 0, component });
1278
+ };
1279
+ export {
1280
+ ContextMenu,
1281
+ ContextMenuContext,
1282
+ ContextMenuProvider,
1283
+ Dialog,
1284
+ DialogService,
1285
+ MenuItem,
1286
+ MenuItemGroup,
1287
+ Popup,
1288
+ PopupService,
1289
+ Portal,
1290
+ Separator,
1291
+ createContainer,
1292
+ installTheme,
1293
+ isGroupMenuItemDescriptor,
1294
+ renderPortal,
1295
+ useContextMenu
1296
+ };
2
1297
  //# sourceMappingURL=index.js.map