@fabiomdf/vue-hierarchy-tree
Version:
A Vue 3 component for creating interactive hierarchy trees with drag-and-drop functionality
2 lines (1 loc) • 13.4 kB
JavaScript
(function(p,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(p=typeof globalThis<"u"?globalThis:p||self,e(p.VueHierarchyTree={},p.Vue))})(this,function(p,e){"use strict";const $=["title"],I=e.defineComponent({__name:"ActionButton",props:{title:{},position:{},backgroundColor:{},borderColor:{},size:{},shape:{},onClick:{type:Function}},setup(i){const a=i,o=l=>{l.stopPropagation(),a.onClick(l)},t=l=>{l.stopPropagation()};return(l,s)=>(e.openBlock(),e.createElementBlock("button",{class:e.normalizeClass(["action-button",`action-button--${l.position}`,`action-button--${a.shape||"circle"}`]),style:e.normalizeStyle({backgroundColor:a.backgroundColor||"#4CAF50",borderColor:a.borderColor||"white",width:(a.size||30)+"px",height:(a.size||30)+"px",fontSize:Math.floor((a.size||30)*.6)+"px"}),onMousedown:o,onClick:t,title:l.title},[e.renderSlot(l.$slots,"default",{},void 0,!0)],46,$))}}),L=(i,a)=>{const o=i.__vccOpts||i;for(const[t,l]of a)o[t]=l;return o},E=L(I,[["__scopeId","data-v-cb73231d"]]),M=e.defineComponent({__name:"AddNewDraggableBox",props:{parentId:{},backgroundColor:{},borderColor:{},content:{},size:{},shape:{}},emits:["addChild"],setup(i,{emit:a}){const o=i,t=a,l=()=>{t("addChild",o.parentId)};return(s,h)=>(e.openBlock(),e.createBlock(E,{title:"Add Child Box",position:"bottom",backgroundColor:o.backgroundColor||"#4CAF50",borderColor:o.borderColor||"white",size:o.size||30,shape:o.shape||"circle",onClick:l},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(o.content||"+"),1)]),_:1},8,["backgroundColor","borderColor","size","shape"]))}}),N=e.defineComponent({__name:"RemoveDraggableBox",props:{boxId:{},backgroundColor:{},borderColor:{},content:{},size:{},shape:{}},emits:["removeBox"],setup(i,{emit:a}){const o=i,t=a,l=()=>{t("removeBox",o.boxId)};return(s,h)=>(e.openBlock(),e.createBlock(E,{title:"Remove Box",position:"top",backgroundColor:o.backgroundColor||"#f44336",borderColor:o.borderColor||"white",size:o.size||30,shape:o.shape||"circle",onClick:l},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(o.content||"×"),1)]),_:1},8,["backgroundColor","borderColor","size","shape"]))}});function D(i,a){const o=e.ref(i),t=e.ref(!1),l=e.ref({x:0,y:0}),s=B=>{t.value=!0,l.value={x:B.clientX-o.value.x,y:B.clientY-o.value.y},window.addEventListener("mousemove",h),window.addEventListener("mouseup",g)},h=B=>{t.value&&(o.value={x:B.clientX-l.value.x,y:B.clientY-l.value.y})},g=()=>{t.value=!1,window.removeEventListener("mousemove",h),window.removeEventListener("mouseup",g),a(o.value)};return{position:o,dragging:t,startDrag:s}}function F(i,a){const o=typeof i=="string"?e.ref(i):i,t=e.ref(!1),l=e.ref(o.value),s=e.ref(null);e.watch(o,f=>{t.value||(l.value=f)});const h=async()=>{t.value=!0,l.value=o.value,await e.nextTick(),s.value&&(s.value.focus(),s.value.select())},g=()=>{const f=l.value.trim();f!==o.value&&f!==""&&a(f),t.value=!1},B=()=>{l.value=o.value,t.value=!1};return{isEditing:t,tempLabel:l,inputRef:s,startEditing:h,saveLabel:g,handleKeydown:f=>{f.key==="Enter"?g():f.key==="Escape"&&B()}}}const H=L(e.defineComponent({__name:"DraggableBox",props:{initialPosition:{},size:{},id:{},label:{},showAddButton:{type:Boolean},showRemoveButton:{type:Boolean},allowLabelEdit:{type:Boolean},enableShadow:{type:Boolean},boxBackgroundColor:{},boxLabelColor:{},editFieldBorderColor:{},editFieldBackgroundColor:{},editFieldTextColor:{},addButtonBackgroundColor:{},addButtonBorderColor:{},addButtonContent:{},addButtonSize:{},addButtonShape:{},removeButtonBackgroundColor:{},removeButtonBorderColor:{},removeButtonContent:{},removeButtonSize:{},removeButtonShape:{},isSelected:{type:Boolean},highlightSelected:{type:Boolean},selectedBorderColor:{},selectedBorderWidth:{}},emits:["addChild","updateLabel","updatePosition","removeBox","selectBox"],setup(i,{expose:a,emit:o}){const t=i,l=o,s=e.ref(null),h={width:150,height:100},g=e.computed(()=>({width:t.size?.width??h.width,height:t.size?.height??h.height})),{position:B,startDrag:w}=D(t.initialPosition||{x:100,y:100},n=>l("updatePosition",t.id,n)),{isEditing:f,tempLabel:x,inputRef:_,startEditing:S,saveLabel:k,handleKeydown:m}=F(e.toRef(t,"label"),n=>l("updateLabel",t.id,n)),C=n=>{const u=n.target;u&&(u.tagName==="BUTTON"||u.closest("button"))||l("selectBox",t.id)},b=n=>{f.value||(C(n),w(n))},z=n=>{n.stopPropagation();const u=n.target;u&&(u.tagName==="BUTTON"||u.closest("button"))||t.allowLabelEdit&&S()},r=n=>{n.stopPropagation()},d=n=>{l("addChild",n)},c=n=>{l("removeBox",n)};return a({boxRef:s}),(n,u)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["draggable-box",{"with-shadow":t.enableShadow,selected:t.isSelected&&t.highlightSelected}]),style:e.normalizeStyle({top:e.unref(B).y+"px",left:e.unref(B).x+"px",width:g.value.width+"px",height:g.value.height+"px",backgroundColor:t.boxBackgroundColor||"#42b983",color:t.boxLabelColor||"white",border:t.isSelected&&t.highlightSelected?`${t.selectedBorderWidth||3}px solid ${t.selectedBorderColor||"#ffd700"}`:"none"}),onMousedown:b,onDblclick:z,onClick:C,ref_key:"boxRef",ref:s},[!e.unref(f)||!t.allowLabelEdit?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["box-content",{"non-editable":!t.allowLabelEdit}])},e.toDisplayString(t.label),3)):t.allowLabelEdit?e.withDirectives((e.openBlock(),e.createElementBlock("input",{key:1,ref_key:"inputRef",ref:_,"onUpdate:modelValue":u[0]||(u[0]=y=>e.isRef(x)?x.value=y:null),class:"label-input",style:e.normalizeStyle({backgroundColor:t.editFieldBackgroundColor||"rgba(255, 255, 255, 0.9)",borderColor:t.editFieldBorderColor||"#4CAF50",color:t.editFieldTextColor||"#333"}),onKeydown:u[1]||(u[1]=(...y)=>e.unref(m)&&e.unref(m)(...y)),onBlur:u[2]||(u[2]=(...y)=>e.unref(k)&&e.unref(k)(...y)),onClick:r},null,36)),[[e.vModelText,e.unref(x)]]):e.createCommentVNode("",!0),t.showAddButton?(e.openBlock(),e.createBlock(M,{key:2,"parent-id":n.id,"background-color":t.addButtonBackgroundColor,"border-color":t.addButtonBorderColor,content:t.addButtonContent,size:t.addButtonSize,shape:t.addButtonShape,onAddChild:d},null,8,["parent-id","background-color","border-color","content","size","shape"])):e.createCommentVNode("",!0),t.showRemoveButton?(e.openBlock(),e.createBlock(N,{key:3,"box-id":n.id,"background-color":t.removeButtonBackgroundColor,"border-color":t.removeButtonBorderColor,content:t.removeButtonContent,size:t.removeButtonSize,shape:t.removeButtonShape,onRemoveBox:c},null,8,["box-id","background-color","border-color","content","size","shape"])):e.createCommentVNode("",!0)],38))}}),[["__scopeId","data-v-c6c90518"]]),V={class:"connection-line"},O=["x1","y1","x2","y2"],W=L(e.defineComponent({__name:"ConnectionLine",props:{start:{},end:{}},setup(i){const a=i,o=e.ref(null),t=e.ref(null),l=()=>{if(a.start?.boxRef&&a.end?.boxRef){const s=a.start.boxRef.getBoundingClientRect(),h=a.end.boxRef.getBoundingClientRect();o.value={x:s.left+s.width/2,y:s.top+s.height/2},t.value={x:h.left+h.width/2,y:h.top+h.height/2}}requestAnimationFrame(l)};return e.onMounted(()=>{l()}),(s,h)=>(e.openBlock(),e.createElementBlock("svg",V,[o.value&&t.value?(e.openBlock(),e.createElementBlock("line",{key:0,x1:o.value.x,y1:o.value.y,x2:t.value.x,y2:t.value.y,stroke:"black","stroke-width":"2"},null,8,O)):e.createCommentVNode("",!0)]))}}),[["__scopeId","data-v-d6f9434c"]]);function A(i,a){const o=e.reactive({boxes:[...i.boxes],connections:[...i.connections]}),t=e.reactive({}),l=e.ref(1),s=e.ref(null),h=(r,d)=>{d&&typeof d=="object"&&"boxRef"in d&&(t[r]=d)},g=()=>`box_${Date.now()}_${l.value++}`,B=(r,d)=>Math.abs(r.x-d.x)<a.boxWidth+20&&Math.abs(r.y-d.y)<a.boxHeight+20,w=r=>{if(!a.enableCollisionAvoidance)return r;let d={...r},c=0;const n=50,u=50;for(;c<n;){if(!o.boxes.some(K=>B(d,K.position)))return d;const P=c*45%360,v=Math.floor(c/8)*u+u;d={x:r.x+Math.cos(P*Math.PI/180)*v,y:r.y+Math.sin(P*Math.PI/180)*v},c++}return d},f=r=>{const d=o.boxes.find(u=>u.id===r);if(!d)return{x:100,y:100};const c=o.connections.filter(u=>u.start===r).length,n={x:d.position.x+c*180-90,y:d.position.y+150};return w(n)},x=r=>{const d=[],c=o.connections.filter(n=>n.start===r).map(n=>n.end);for(const n of c)d.push(n),d.push(...x(n));return d},_=r=>{const d=g(),c=f(r),n={id:d,label:`Child ${d}`,position:c};o.boxes.push(n),o.connections.push({start:r,end:d}),C("Child added")},S=(r,d)=>{const c=o.boxes.find(n=>n.id===r);c&&(c.label=d,C("Label updated"))},k=(r,d)=>{const c=o.boxes.find(n=>n.id===r);c&&(c.position=d,C("Position updated"))},m=r=>{const d=x(r),c=[r,...d];o.connections=o.connections.filter(n=>!c.includes(n.start)&&!c.includes(n.end)),o.boxes=o.boxes.filter(n=>!c.includes(n.id)),c.forEach(n=>{delete t[n]}),C("Box removed")},C=r=>{a.enableConsoleLog&&console.log(`${r} - FlowData:`,JSON.stringify(o,null,2))},b=r=>{s.value=r},z=e.computed(()=>o.connections.filter(r=>t[r.start]&&t[r.end]));return{flowData:o,boxRefs:t,selectedBoxId:s,setBoxRef:h,addChildBox:_,updateBoxLabel:S,updateBoxPosition:k,removeBox:m,selectBox:b,validConnections:z}}const R=L(e.defineComponent({__name:"HierarchyTree",props:{initialData:{},showAddButton:{type:Boolean,default:!0},showRemoveButton:{type:Boolean,default:!0},allowLabelEdit:{type:Boolean,default:!0},boxWidth:{default:150},boxHeight:{default:100},enableShadow:{type:Boolean,default:!0},boxBackgroundColor:{default:"#42b983"},boxLabelColor:{default:"white"},editFieldBorderColor:{default:"#4CAF50"},editFieldBackgroundColor:{default:"rgba(255, 255, 255, 0.9)"},editFieldTextColor:{default:"#333"},addButtonBackgroundColor:{default:"#4CAF50"},addButtonBorderColor:{default:"white"},addButtonContent:{default:"+"},addButtonSize:{default:30},addButtonShape:{default:"circle"},removeButtonBackgroundColor:{default:"#f44336"},removeButtonBorderColor:{default:"white"},removeButtonContent:{default:"×"},removeButtonSize:{default:30},removeButtonShape:{default:"circle"},enableCollisionAvoidance:{type:Boolean,default:!0},enableConsoleLog:{type:Boolean,default:!1},highlightSelectedBox:{type:Boolean,default:!0},selectedBoxBorderColor:{default:"#ffd700"},selectedBoxBorderWidth:{default:3},showButtonsOnlyOnSelected:{type:Boolean,default:!1}},setup(i,{expose:a}){const o=i,t=m=>({width:m.size?.width??o.boxWidth,height:m.size?.height??o.boxHeight}),{flowData:l,boxRefs:s,selectedBoxId:h,setBoxRef:g,addChildBox:B,updateBoxLabel:w,updateBoxPosition:f,removeBox:x,selectBox:_,validConnections:S}=A(o.initialData,{boxWidth:o.boxWidth,boxHeight:o.boxHeight,enableCollisionAvoidance:o.enableCollisionAvoidance,enableConsoleLog:o.enableConsoleLog,highlightSelectedBox:o.highlightSelectedBox}),k=(m,C)=>(C==="add"?o.showAddButton:o.showRemoveButton)?o.showButtonsOnlyOnSelected?h.value===m:!0:!1;return a({flowData:l}),(m,C)=>(e.openBlock(),e.createElementBlock(e.Fragment,null,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(l).boxes,b=>(e.openBlock(),e.createBlock(H,{key:b.id,ref_for:!0,ref:z=>e.unref(g)(b.id,z),id:b.id,label:b.label,initialPosition:b.position,size:t(b),"show-add-button":k(b.id,"add"),"show-remove-button":k(b.id,"remove"),"allow-label-edit":o.allowLabelEdit,"enable-shadow":o.enableShadow,"box-background-color":o.boxBackgroundColor,"box-label-color":o.boxLabelColor,"edit-field-border-color":o.editFieldBorderColor,"edit-field-background-color":o.editFieldBackgroundColor,"edit-field-text-color":o.editFieldTextColor,"add-button-background-color":o.addButtonBackgroundColor,"add-button-border-color":o.addButtonBorderColor,"add-button-content":o.addButtonContent,"add-button-size":o.addButtonSize,"add-button-shape":o.addButtonShape,"remove-button-background-color":o.removeButtonBackgroundColor,"remove-button-border-color":o.removeButtonBorderColor,"remove-button-content":o.removeButtonContent,"remove-button-size":o.removeButtonSize,"remove-button-shape":o.removeButtonShape,"is-selected":e.unref(h)===b.id,"highlight-selected":o.highlightSelectedBox,"selected-border-color":o.selectedBoxBorderColor,"selected-border-width":o.selectedBoxBorderWidth,onAddChild:e.unref(B),onUpdateLabel:e.unref(w),onUpdatePosition:e.unref(f),onRemoveBox:e.unref(x),onSelectBox:e.unref(_)},null,8,["id","label","initialPosition","size","show-add-button","show-remove-button","allow-label-edit","enable-shadow","box-background-color","box-label-color","edit-field-border-color","edit-field-background-color","edit-field-text-color","add-button-background-color","add-button-border-color","add-button-content","add-button-size","add-button-shape","remove-button-background-color","remove-button-border-color","remove-button-content","remove-button-size","remove-button-shape","is-selected","highlight-selected","selected-border-color","selected-border-width","onAddChild","onUpdateLabel","onUpdatePosition","onRemoveBox","onSelectBox"]))),128)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(S),b=>(e.openBlock(),e.createBlock(W,{key:`${b.start}-${b.end}`,start:e.unref(s)[b.start],end:e.unref(s)[b.end]},null,8,["start","end"]))),128))],64))}}),[["__scopeId","data-v-d2d3a352"]]);function T(i){i.component("HierarchyTree",R)}const U={install:T,HierarchyTree:R};p.HierarchyTree=R,p.default=U,p.install=T,p.useDrag=D,p.useFlowData=A,p.useLabelEdit=F,Object.defineProperties(p,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});