gw-popup
Version:
A configurable popup component for Vue 3
17 lines (16 loc) • 18 kB
JavaScript
(function(d,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("@vueuse/core"),require("vue"),require("pinia")):typeof define=="function"&&define.amd?define(["exports","@vueuse/core","vue","pinia"],f):(d=typeof globalThis<"u"?globalThis:d||self,f(d.GWPopup={},d.VueUse,d.Vue,d.Pinia))})(this,function(d,f,n,M){"use strict";const C=n.defineComponent({name:"PopUp",setup(t){const i=n.inject("popupManager");if(!i)throw new Error("PopupManager not provided");const s={top:0,left:0,width:0,height:0},l={},r={},h={backgroundFill:"transparent",popupPosition:s,showRequested:!1,hideRequested:!1,isDisplayed:!1,isInit:!1,popupId:"",retries:0,positioner:null,svgParams:null},a={current:{left:0,top:0},original:{left:0,top:0}},o={arrowDistance:2,arrowHeight:7,arrowOffset:12,arrowPosition:"center",arrowWidth:9,class:"",cornerRadius:3,debugMode:!1,direction:"e",eventOff:"mouseleave",eventOn:"mouseenter",isManual:!1,isModal:!1,isPositioned:!0,isTooltip:!0,logging:!1,paddingX:6,paddingY:2,popupOffset:0,popupPosition:"center",positioner:"",shiftX:0,shiftY:0,zIndex:100};h.popupId=t.id;const m=i.mergeConfig(t.mode,o,t.props);return{data:n.ref(h),shifts:n.ref(a),styles:n.ref({content:l,element:r}),config:m,closeHandle:()=>{},popupManager:i}},props:{id:{type:String,default:null},mode:{type:String,required:!0},props:{type:Object,default:()=>{}},refSource:{type:Object,default:null},autoOpen:{type:Boolean,default:!1}},methods:{getFill(t){var e,i;return((e=this.data.svgParams)==null?void 0:e.paths[t].fill)==="backgroundColor"?this.data.backgroundFill:(i=this.data.svgParams)==null?void 0:i.paths[t].fill},forceHide(){this.data.isDisplayed&&!this.config.isModal&&(this.styles.element={top:"-3000px",left:"-3000px"}),this.data.hideRequested=!1,this.data.showRequested=!1,this.data.isDisplayed=!1,this.popupManager.setStatus(this.data.popupId,"isOpen",!1),this.closeHandle()},refresh(t){this.data.isDisplayed&&this.data.positioner&&this.show({positioner:this.data.positioner})},log(t){this.config.logging===!0&&console.log(t)},show(t){var i,s,l;this.log("show: "+this.data.popupId);let e=null;if(this.config.isPositioned){const r=t.positioner;typeof r=="string"?(e=(i=this.$parent)==null?void 0:i.$refs[r],typeof e<"u"?this.data.positioner=e:e=(l=(s=this.$parent)==null?void 0:s.$parent)==null?void 0:l.$refs[r]):e=r,e||this.log("Popup.vue could not find positioner for id: "+this.id);let h=e;this.data.showRequested&&(this.data.hideRequested=!1),this.data.showRequested=!0;const a=setTimeout(()=>{this.data.showRequested=!1,this.data.hideRequested&&(this.hide({}),this.log("hide requested")),clearTimeout(a)},200);this.data.isDisplayed=!0,this.popupManager.closeOpenTooltips(this.data.popupId);let o;const m=this.$refs.popupContent;let y=m.children;if(typeof y>"u")return;y=m.children;try{o={positioner:h.getBoundingClientRect(),content:y[0].getBoundingClientRect(),window:this.popupManager.percentHeightWidth(100)}}catch(w){this.log(`the content or positioner probably no longer rendered, so close:
`+w),this.hide({});return}if(o.positioner.top>2e4){const w=setTimeout(()=>{this.data.retries++,this.data.retries>1?this.log("we are hosed on popup retries"):this.show({positioner:this.data.positioner}),clearTimeout(w)},100)}let u={left:0,top:0};this.data.retries=0;const k=m.children[0];this.data.backgroundFill=window.getComputedStyle(k).backgroundColor;let c=this.config.direction;t.direction!==null&&typeof t.direction<"u"&&(c=t.direction),this.data.isInit?u={left:this.shifts.current.left,top:this.shifts.current.top}:(this.shifts.original.left=0,this.shifts.original.top=0,this.shifts.current.left=this.config.shiftX,this.shifts.current.top=this.config.shiftY,this.data.isInit=!0);let b=!1,p={},g={top:0,left:0},E=0;do{switch(b=!1,this.log("Direction: "+this.config.direction),c){case"n":p={left:this.getPos(o,c),top:o.positioner.top-this.config.arrowDistance-(o.content.height+2*this.config.paddingY+this.config.arrowHeight),width:o.content.width+2*this.config.paddingX,height:o.content.height+2*this.config.paddingY+this.config.arrowHeight},g.left=this.config.paddingX,g.top=this.config.paddingY;break;case"e":p={left:o.positioner.right+this.config.arrowDistance,top:this.getPos(o,c),width:o.content.width+this.config.arrowWidth+2*this.config.paddingX,height:o.content.height+2*this.config.paddingY},g.left=this.config.arrowHeight+this.config.paddingX,g.top=this.config.paddingY;break;case"s":p={left:this.getPos(o,c),top:o.positioner.top+o.positioner.height,width:o.content.width+2*this.config.paddingX,height:o.content.height+2*this.config.paddingY+this.config.arrowHeight},g.left=this.config.paddingX,g.top=this.config.paddingY+this.config.arrowHeight;break;case"w":p={left:o.positioner.left-o.content.width-2*this.config.paddingX-this.config.arrowDistance-this.config.arrowHeight,top:this.getPos(o,c),width:o.content.width+2*this.config.paddingX+this.config.arrowHeight,height:o.content.height+2*this.config.paddingY},g.left=this.config.paddingX,g.top=this.config.paddingY;break}p.top+p.height>o.window.height&&(u.top=u.top-(o.window.height-(p.top+p.height))+this.popupManager.percentHeightWidth(10).height,b=!0);const w=c==="e"||c==="w";p.top+p.height<o.positioner.top-(w?o.positioner.height/2:this.popupManager.percentHeightWidth(10).height)&&(u={top:this.shifts.original.top,left:this.shifts.original.left},b=!0),b&&(this.shifts.current.left=u.left,this.shifts.current.top=u.top),E++}while(b&&E<2);let V={width:p.width+"px",height:p.height+"px",top:p.top+"px",left:p.left+"px",zIndex:this.config.zIndex};this.data.popupPosition=p;let P=0,T="height";switch(c){case"n":case"s":T="width";break}switch(this.config.arrowPosition){case"min":P=this.config.arrowDistance;break;case"center":P=p[T]/2;break;case"max":P=p[T]-this.config.arrowDistance;break}this.data.svgParams=this.popupManager.buildArrowBox({mode:this.mode,arrowHeight:this.config.arrowHeight,arrowWidth:this.config.arrowWidth,arrowPosition:P,height:p.height,width:p.width,cornerRadius:this.config.cornerRadius,direction:c,refX:o.positioner.x,refY:o.positioner.y,refWidth:o.positioner.width,refHeight:o.positioner.height}),this.styles.element=V,this.styles.content={left:g.left+"px",top:g.top+"px"},this.config.isTooltip&&(this.closeHandle=f.useEventListener(window,"mouseMove",w=>{(Math.abs(w.clientX-p.left)>200||Math.abs(w.clientY-p.top)>200)&&(this.forceHide(),this.log("force hidden"))}))}else this.styles.element={},this.data.isDisplayed=!0,this.$nextTick(()=>{const r=this.$refs.popupContent;let h=r.children;if(typeof h>"u")return;h=r.children;const a=h[0].getBoundingClientRect();this.styles.content={width:a.width+2*this.config.paddingX+"px",height:a.height+2*this.config.paddingY+"px",background:window.getComputedStyle(h[0]).backgroundColor,padding:this.config.paddingY+"px "+(this.config.paddingX+"px")}})},getPos(t,e){const i=["n","s"].indexOf(e)>-1;let s=i?"width":"height",l=this.config[i?"paddingX":"paddingY"];const r=t.positioner,h=t.content;let a=r[i?"x":"y"];switch(this.config.popupPosition){case"max":a=a+r[s]+this.config.popupOffset-h[s]-l;break;case"center":a=a+(r[s]-h[s]-l*2)/2;break;case"min":a=a-l-this.config.popupOffset;break}return a},hide(t){if(this.log("hide: "+this.data.popupId),!this.config.isPositioned){this.data.isDisplayed=!1;return}if(!this.data.isDisplayed)return;if(this.data.showRequested){this.data.hideRequested=!0;return}const e=setTimeout(()=>{if(this.data.showRequested)clearTimeout(e),this.data.hideRequested=!1;else{let i=this.data.popupPosition;i.top=this.config.debugMode?300:-3e3,i.left=this.config.debugMode?300:-3e3;let s={width:i.width+"px",height:i.height+"px",top:i.top+"px",left:i.left+"px",zIndex:this.config.zIndex};this.styles.element=s,this.data.isDisplayed=!1,this.popupManager.setStatus(this.data.popupId,"isOpen",!1),this.closeHandle()}},2e3);this.data.hideRequested=!0},startUp(t){var r,h;const e={show:this.show,hide:this.hide,refresh:this.refresh};let i=null,s=null;if(!this.config.isManual){let a=null;if(typeof((r=this.$parent)==null?void 0:r.$refs[this.config.positioner])<"u"&&(s=(h=this.$parent)==null?void 0:h.$refs[this.config.positioner],s&&(i=s),i!==null&&(a=i.getBoundingClientRect())),a===null){t++,t<11?setTimeout(()=>{this.startUp(t)},20):this.log("PopUp.vue could not find ref: "+this.config.positioner);return}else this.log("Popup found positioner: "+this.config.positioner+" on try "+t)}const l={isTooltip:this.config.isTooltip,isModal:this.config.isModal,isManual:this.config.isManual,positioner:this.config.positioner,eventObject:i,eventOn:this.config.eventOn,eventOff:this.config.eventOff};this.data.popupId=this.popupManager.registerPopup(this.id,l,e),this.popupManager.setStatus(this.data.popupId,"isOpen",!1),this.autoOpen&&this.show({positioner:this.config.positioner})}},mounted(){this.$nextTick(()=>this.startUp(1))},beforeUnmount(){this.popupManager.destroyPopup(this.data.popupId),this.closeHandle()}}),R=(t,e)=>{const i=t.__vccOpts||t;for(const[s,l]of e)i[s]=l;return i},v=["id"],$=["width","height","viewBox"],X=["fill","d"];function Y(t,e,i,s,l,r){return n.openBlock(),n.createBlock(n.Teleport,{to:"body"},[t.config.isPositioned||t.data.isDisplayed?(n.openBlock(),n.createElementBlock("div",{key:0,class:n.normalizeClass(["gw-popup",t.config.class])},[n.createElementVNode("div",{class:"popupContainer",id:t.data.popupId},[n.createElementVNode("div",{class:"popupElements",style:n.normalizeStyle(t.styles.element)},[t.data.svgParams!==null?(n.openBlock(!0),n.createElementBlock(n.Fragment,{key:0},n.renderList(t.data.svgParams.paths,(h,a)=>(n.openBlock(),n.createElementBlock("svg",{key:h.class,"xml:space":"preserve",width:t.data.svgParams.width,height:t.data.svgParams.height,viewBox:t.data.svgParams.viewbox},[n.createElementVNode("path",{class:n.normalizeClass(["popupPath",h.class]),fill:t.getFill(a),d:h.path},null,10,X)],8,$))),128)):n.createCommentVNode("",!0),t.config.isModal&&t.data.isDisplayed?(n.openBlock(),n.createElementBlock("div",{key:1,class:"modalBackground",onClick:e[0]||(e[0]=h=>t.forceHide())})):n.createCommentVNode("",!0),n.createElementVNode("div",{class:"popupContent",ref:"popupContent",style:n.normalizeStyle(t.styles.content)},[n.renderSlot(t.$slots,"default",{ref:"content"})],4)],4)],8,v)],2)):n.createCommentVNode("",!0)])}const H=R(C,[["render",Y]]),B=n.defineComponent({name:"ToolTip",components:{PopUp:H},setup(){const t={toolTipText:"",ready:!1,tips:[],tipIndex:{}},e=[],i=n.inject("popupManager");return{data:n.ref(t),callbacks:n.ref(e),popupManager:i}},methods:{registerTooltips(t){t.forEach(e=>{const i=this.data.tipIndex[e.refName]?this.data.tipIndex[e.refName]:this.data.tips.length;this.data.tipIndex[e.refName]=i;const s={refName:e.refName,ref:e.ref,text:e.text,direction:e.direction};this.data.tips[i]=s;const l=f.useEventListener(s.ref,"mouseenter",()=>{this.show(s.refName)});this.callbacks[i]=l;const r=f.useEventListener(s.ref,"mouseleave",()=>{this.hide()});this.callbacks[i]=r}),this.data.ready=!0},show(t){const e=this.data.tips[this.data.tipIndex[t]];this.data.toolTipText=e.text,this.popupManager.showPopup(this.id,e.ref,e.direction)},hide(){this.popupManager.hidePopup(this.id)}},props:{id:{type:String,default:"ToolTip"},cssClass:{type:String,default:"toolTip"}},mounted(){this.popupManager.connectToolTip(this.id,this.registerTooltips)},beforeUnmount(){this.popupManager.disconnectToolTip(this.id),this.callbacks.forEach(t=>{t()}),this.callbacks=[]}});function W(t,e,i,s,l,r){const h=n.resolveComponent("PopUp");return n.openBlock(),n.createBlock(h,{mode:"tooltip",props:{isManual:!0},id:t.id},{default:n.withCtx(()=>[n.createElementVNode("div",{class:n.normalizeClass(t.cssClass)},n.toDisplayString(t.data.toolTipText),3)]),_:1},8,["id"])}const q=R(B,[["render",W]]),O={log:!1,debugMode:!1,modes:{dialog:{props:{isPositioned:!1,class:"centered",paddingX:10,paddingY:10,arrowOffset:6,logging:!1,isManual:!0,isModal:!0,direction:"e"}},tooltip:{props:{isManual:!1,direction:"e"},directionMap:{n:{yReverse:!0,switchXY:!1,svgElements:["arrowBox"],startOffset:"H"},e:{yReverse:!1,switchXY:!0,svgElements:["arrowBox"],startOffset:"0"},w:{yReverse:!0,switchXY:!0,svgElements:["arrowBox"],startOffset:"W"},s:{yReverse:!1,switchXY:!1,svgElements:["arrowBox"],startOffset:"0"}}},popup:{props:{isManual:!0,logging:!1,isModal:!0,direction:"e"}},windowPopup:{props:{isPositioned:!1,class:"centered",isManual:!0,logging:!0,isModal:!0}}},svgElements:{arrowBox:{fill:"backgroundColor",pathTemplate:`
M {{T}},{{S}}
l {{AW/2}},{{AH}}
l {{W-T-R-AW/2}},0
q {{R}},0 {{R}},{{R}}
l 0,{{H-R-R-AH}}
q 0,{{R}} -{{R}},{{R}}
l -{{W-R-R}},0
q -{{R}},0 -{{R}},-{{R}}
l 0,-{{H-R-R-AH}}
q 0,-{{R}} {{R}},-{{R}}
l {{W-T-AW/2-R}},0
z
`}}};let S={...O};function D(t){S={...O,...t}}function j(){return S}const x=M.defineStore("popupManager",{state:()=>({popupIndex:0,status:{},callbacks:{},callbackHandles:{},settings:null,toolTips:{},globalConfig:j()}),actions:{mergeConfig(t,e,i){try{const s=this.globalConfig.modes[t].props;if(typeof s>"u")return console.error("Popup Service handled non-existent mode key: "+t),e;e=this.mergeTool(e,s),typeof i<"u"&&(e=this.mergeTool(e,i))}catch{console.error("Unexpected error in PopupManager mergeConfig for: "+t)}return e},mergeTool(t,e){return Object.keys(e).forEach(s=>{const l=e[s];typeof l<"u"&&this.setPropValue(s,t,l)}),t},setPropValue(t,e,i){e[t]=i},connectToolTip(t,e){this.toolTips[t]=e},registerToolTips(t,e,i=5){this.toolTips[t]?this.toolTips[t](e):i>0?setTimeout(()=>this.registerToolTips(t,e,i-1),100):console.error(`Tooltip handler for id '${t}' not found`)},disconnectToolTip(t){delete this.toolTips[t]},config(t){this.settings=t,D(t)},buildArrowBox(t){const e={width:t.width+"px",height:t.height+"px",viewbox:`0 0 ${t.width} ${t.height}`,paths:[]},i=this.globalConfig.modes[t.mode].directionMap;if(!i)return null;const s=i[t.direction];return s?(s.svgElements.forEach(l=>{const r=this.globalConfig.svgElements[l],h=s.switchXY?t.height:t.width,a=s.switchXY?t.width:t.height,m={H:t.height,W:t.width,"-H":-t.height,"-W":-t.width,0:0}[s.startOffset]??0,y=s.switchXY?t.arrowWidth:t.arrowHeight,u=s.switchXY?t.arrowHeight:t.arrowWidth,k={T:t.arrowPosition,S:m,W:h,H:a,AW:u,AH:y,R:t.cornerRadius,RefX:t.refX,RefY:t.refY,RefW:t.refWidth,RefH:t.refHeight},c=this.processSvgTemplate(r.pathTemplate,k,s);e.paths.push({class:l,path:c,fill:r.fill})}),e):(console.error("Popup.vue expected to find a direction map item for:"+t.direction),null)},processSvgTemplate(t,e,i){const l=t.replace(/\{\{(.*?)\}\}/g,(r,h)=>{try{return String(Function(...Object.keys(e),`return ${h}`)(...Object.values(e)))}catch(a){return console.error(`Error processing template expression: ${h}`,a),"NaN"}}).trim().split(`
`).map(r=>r.trim());return i.yReverse&&l.forEach((r,h)=>{const a=r.split(/[ ,]/);if(a[0]!=="M")for(let o=2;o<a.length;o+=2)a[o]=","+String(-Number(a[o]));l[h]=a.join(" ").split(" ,").join(",")}),i.switchXY&&l.forEach((r,h)=>{const a=r.split(/[ ,]/);for(let o=1;o<a.length;o+=2)[a[o],a[o+1]]=[a[o+1],","+a[o]];l[h]=a.join(" ").split(" ,").join(",")}),l.join(" ")},percentHeightWidth(t){return{height:t*window.innerWidth/100,width:t*window.innerHeight/100}},log(t,e){var i;(i=this.settings)!=null&&i.log&&console.log(t+":"+JSON.stringify(e,null," "))},logSimple(t){var e;(e=this.settings)!=null&&e.log&&console.log(t)},attachEvents(t,e){const i=this.callbackHandles[t].show;i!==null&&i(),this.logSimple("attachEvent: show");const s=f.useEventListener(e.eventObject,e.eventOn,()=>{this.callbacks[t].show({positioner:e.positioner});const l=this.callbackHandles[t].hide;l!==null&&l(),this.logSimple("attachEvent: hide");const r=f.useEventListener(e.eventObject,e.eventOff,()=>{this.callbacks[t].hide({positioner:""})});this.callbackHandles[t].hide=r});this.callbackHandles[t].show=s},attachEvent(t,e,i,s){const l=this.callbackHandles[t][s];l!==null&&i&&l(),this.logSimple("attachEvent: "+s);const r=f.useEventListener(e,i,()=>{this.callbacks[t][s]({})});this.callbackHandles[t][s]=r},registerPopup(t,e,i){return t||(t="popup_"+this.popupIndex++),this.callbacks[t]&&(this.logSimple("duplicate popup register, removing prior callback"),this.destroyPopup(t),this.logSimple("Popup "+t+" was re-registered")),this.callbacks[t]=i,this.callbackHandles[t]={show:null,hide:null,refresh:null},e.eventObject&&!e.isManual&&e.positioner&&this.attachEvents(t,e),this.attachEvent(t,window,"resize","refresh"),this.status[t]={isTooltip:e.isTooltip,isOpen:!1},this.logSimple("registered popup: "+t),t},destroyPopup(t){this.logSimple("Destroyed pop up: "+t);try{this.callbackHandles[t].hide!==null&&this.callbackHandles[t].hide(),this.callbackHandles[t].show!==null&&this.callbackHandles[t].show(),this.callbackHandles[t].refresh!==null&&this.callbackHandles[t].refresh(),delete this.callbacks[t],delete this.callbackHandles[t],delete this.status[t],this.popupIndex=0}catch{console.error("Error destroying popup "+t)}},closeOpenTooltips(t){Object.keys(this.status).forEach(e=>{e!==t&&this.status[e].isOpen&&this.status[e].isTooltip&&(this.logSimple("Tooltip: "+e+" autoclosed"),this.callbacks[e].hide({}),this.logSimple("autoclosed: "+e),this.status[e].isOpen=!1)})},showPopup(t,e,i){setTimeout(()=>{this.callbacks[t]?(this.callbacks[t].show({positioner:e||"",direction:i}),this.status[t].isOpen=!0):console.log("showPopup: Popup "+t+" was not found")},10)},hidePopup(t){this.callbacks[t]?(this.callbacks[t].hide({}),this.status[t].isOpen=!1):this.logSimple("hidePopup: Popup "+t+" was not found")},refreshPopup(t){const e=this.callbacks[t];e&&setTimeout(()=>e.refresh({}),100)},setStatus(t,e,i){this.status[t]||(this.status[t]={isOpen:!1,isTooltip:!0},this.logSimple("PopupManager.setStatus:"+t+","+e+","+i)),this.status[t][e]=i},reset(){this.callbacks={},this.callbackHandles={},this.status={},this.popupIndex=0}}}),N={install(t){const e=M.createPinia();t.use(e);const i=x(e);t.provide("popupManager",i)}};d.PopUp=H,d.PopupPlugin=N,d.ToolTip=q,d.usePopupManager=x,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});