@ryandymock/ancestor-tree
Version:
Interactive ancestor tree React component built with ReactFlow, featuring expandable generations and customizable UI controls
2 lines • 11 kB
JavaScript
'use strict';var material=require('@mui/material'),K=require('react'),He=require('reactflow');require('reactflow/dist/style.css');var Ee=require('@mui/icons-material/ArrowForwardIos'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var K__default=/*#__PURE__*/_interopDefault(K);var He__default=/*#__PURE__*/_interopDefault(He);var Ee__default=/*#__PURE__*/_interopDefault(Ee);function q(e,t,n=2){let[r,i]=K.useState([]),o=K.useMemo(()=>{let p=[],s=e[t];if(!s)return p;let c=s.spouseId?e[s.spouseId]:void 0;p.push(c?[s,c]:[s]);let l=[...s.parentIds??[],...c?.parentIds??[]].filter(u=>u!==void 0);p.push(l.map(u=>e[u]).filter(Boolean));let x=p[1].flatMap(u=>u.parentIds??[]).filter(u=>u!==void 0).map(u=>e[u]).filter(Boolean);return p.push(x),r.forEach(u=>{let g=e[u];if(!g?.parentIds)return;let C=g.parentIds.filter(m=>m!==void 0).map(m=>e[m]).filter(Boolean);p.push(C);let y=C.flatMap(m=>m.parentIds??[]).filter(m=>m!==void 0).map(m=>e[m]).filter(Boolean);p.push(y);}),p},[e,t,r,n]),d=K.useCallback(p=>{if(p<=n)i([]);else {let s=Math.floor((p-(n+1))/2);i(c=>c.slice(0,s+1));}},[n]),a=K.useCallback((p,s)=>{if(!e[p]?.parentIds?.length)return;let l;if(s===n)l=[p];else {let x=Math.floor((s-(n+1))/2);l=[...r.slice(0,x),p];}i(l);},[e,r,n]);return {generations:o,handleSelect:d,handleExpand:a}}function L({person:e,isEdge:t,onClick:n,registerPosition:r,formatPersonSubtitle:i,personNodeWidth:o=160}){let d=K__default.default.useRef(null);return K__default.default.useLayoutEffect(()=>{d.current&&r&&r(e.id,d.current.getBoundingClientRect());}),jsxRuntime.jsx(material.Card,{variant:"outlined",sx:{my:1,width:o},ref:d,children:jsxRuntime.jsx(material.CardActionArea,{onClick:n,children:jsxRuntime.jsxs(material.CardContent,{sx:{display:"flex",flexDirection:"column",alignItems:"center",position:"relative"},children:[jsxRuntime.jsx(material.Avatar,{src:e.imageUrl,alt:e.name,sx:{width:56,height:56,mb:1}}),jsxRuntime.jsx(material.Typography,{variant:"subtitle2",align:"center",sx:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",width:"100%",px:1},children:e.name}),jsxRuntime.jsx(material.Typography,{variant:"caption",color:"text.secondary",children:i?i(e):e.birth||e.death?`${e.birth??"\u2013"} \u2013 ${e.death??""}`:""}),t&&jsxRuntime.jsx(material.IconButton,{size:"small",sx:{position:"absolute",right:4,top:4},"aria-label":"expand ancestors",children:jsxRuntime.jsx(Ee__default.default,{fontSize:"inherit"})})]})})})}function Te({data:e}){let{person:t,isEdge:n,onPersonClick:r,registerPosition:i,formatPersonSubtitle:o,personNodeWidth:d}=e,a=()=>{r?.(t);};return jsxRuntime.jsxs("div",{style:{position:"relative"},children:[jsxRuntime.jsx(He.Handle,{type:"target",position:He.Position.Left}),jsxRuntime.jsx(L,{person:t,isEdge:n,onClick:a,registerPosition:i,formatPersonSubtitle:o,personNodeWidth:d}),jsxRuntime.jsx(He.Handle,{type:"source",position:He.Position.Top})]})}var oe=K.memo(Te);function X({partner1:e,partner2:t,isEdge:n,onPersonClick:r,registerPosition:i,formatPersonSubtitle:o,coupleNodeWidth:d=320}){let a=K__default.default.useRef(null);K__default.default.useLayoutEffect(()=>{a.current&&i&&(i(e.id,a.current.getBoundingClientRect()),t&&i(t.id,a.current.getBoundingClientRect()));});let p=s=>{let c="";if(o)c=o(s);else {let l="";s.birth&&s.death?l=`${s.birth} \u2013 ${s.death}`:s.birth?l=`${s.birth}`:s.death&&(l=`\u2013 ${s.death}`),c=l?`${l} \u2022 ${s.id}`:s.id;}return jsxRuntime.jsxs(material.Box,{sx:{display:"flex",alignItems:"center",py:1,cursor:"pointer","&:hover":{backgroundColor:"rgba(0, 0, 0, 0.04)"}},onClick:()=>r?.(s),children:[jsxRuntime.jsx(material.Avatar,{src:s.imageUrl,alt:s.name,sx:{width:56,height:56,mr:2}}),jsxRuntime.jsxs(material.Box,{sx:{flex:1,minWidth:0},children:[jsxRuntime.jsx(material.Typography,{variant:"h6",sx:{lineHeight:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:s.name}),jsxRuntime.jsx(material.Typography,{variant:"body2",color:"text.secondary",children:c})]})]})};return jsxRuntime.jsx(material.Card,{sx:{my:1,width:d,borderRadius:2,boxShadow:3,position:"relative"},ref:a,children:jsxRuntime.jsxs(material.Box,{sx:{px:2,py:1},children:[p(e),t&&p(t),n&&jsxRuntime.jsx(Ee__default.default,{sx:{position:"absolute",right:8,top:8,fontSize:18}})]})})}function Be({data:e}){let{partner1:t,partner2:n,onPersonClick:r,onExpand:i,gen:o,formatPersonSubtitle:d,coupleNodeWidth:a}=e;return jsxRuntime.jsxs("div",{style:{position:"relative"},children:[jsxRuntime.jsx(He.Handle,{type:"target",position:He.Position.Left}),jsxRuntime.jsx(X,{partner1:t,partner2:n,onPersonClick:r,formatPersonSubtitle:d,coupleNodeWidth:a}),jsxRuntime.jsx(He.Handle,{id:"dad",type:"source",position:He.Position.Top}),n&&jsxRuntime.jsx(He.Handle,{id:"mom",type:"source",position:He.Position.Bottom}),i&&o>=2&&o%2===0&&jsxRuntime.jsx(material.IconButton,{size:"small",onClick:i,sx:{position:"absolute",right:-32,top:"50%",transform:"translateY(-50%)",backgroundColor:"#fff",boxShadow:3,"&:hover":{backgroundColor:"#f5f5f5"}},children:jsxRuntime.jsx(Ee__default.default,{fontSize:"inherit"})}),i&&o>=2&&o%2===0&&jsxRuntime.jsx(He.Handle,{id:"arrow",type:"source",position:He.Position.Right,style:{top:"50%"}})]})}var re=K.memo(Be);function Z(){let[e,t]=K.useState(void 0),[n,r]=K.useState(0);return {expandedId:e,token:n,expand:d=>{t(a=>{if(a!==d)return d}),r(a=>a+1);},reset:()=>t(void 0)}}var de={edgeStyle:{stroke:"#999",strokeWidth:3}};function ce({people:e,rootId:t,callbacks:n={},uiControls:r={}}){let{generations:i}=q(e,t),{expandedId:o,token:d,expand:a}=Z(),{showControls:p=true,showMiniMap:s=false,showBackground:c=true,enablePan:l=true,enableZoom:x=true,enableFitView:u=true,backgroundColor:g="#fafafa",nodeHeight:C=120,verticalGaps:y=[0,325,100,325,100],defaultVerticalGap:m=50,formatPersonSubtitle:E,coupleNodeWidth:M=320,personNodeWidth:G=160}=r,v=K.useMemo(()=>{let f=[0,225,600,1e3,1400],h=M-320,b=f.map((S,k)=>S+k*h);return {nodeHeight:C,xPositions:b,defaultXStep:200+h,verticalGaps:y,defaultGap:m,coupleNodeWidth:M,personNodeWidth:G,edgeStyle:{stroke:"#999",strokeWidth:3}}},[C,M,G,y,m]),$=`${d}-${o??"root"}-${c}-${p}-${s}`,N=K.useRef({}),H=f=>{let P=o===f;a(f);let h=N.current[f],[b,S]=h||[void 0,void 0];n.onCoupleExpansion?.(P?void 0:b,P?void 0:S,!P);},A=()=>(He.useOnViewportChange({onChange:({x:f,y:P,zoom:h})=>{n.onViewportChange?.(f,P,h),n.onTreePan?.(f,P),n.onTreeZoom?.(h);}}),null),{nodes:V,edges:D}=K.useMemo(()=>{let f=[],P=[],h={},b={};i.forEach((w,ue)=>{let{nodes:fe,mappings:_}=Ye(w,ue,H,v,n.onPersonClick,E);f.push(...fe),Object.assign(h,_.personToCouple),Object.assign(b,_.coupleToPartners);}),N.current={...b};let S=Ue(f,h);if(P.push(...S),o&&b[o]){let w=Xe(e,o,b,f,$,v,n.onPersonClick,E);f.push(...w.nodes),P.push(...w.edges),Object.assign(h,w.mappings.personToCouple),Object.assign(b,w.mappings.coupleToPartners),N.current={...b};}let k=new Set(f.map(w=>w.id)),T=P.filter(w=>k.has(w.source)&&k.has(w.target)),le=_e(T,o,f);return {nodes:f,edges:le}},[i,o,d,e,n.onPersonClick,$,r.formatPersonSubtitle,v]);return jsxRuntime.jsx(He.ReactFlowProvider,{children:jsxRuntime.jsx(material.Box,{sx:{width:"100%",height:"100%",backgroundColor:c?"transparent":g},children:jsxRuntime.jsxs(He__default.default,{nodes:V,edges:D,nodeTypes:{person:oe,couple:re},panOnScroll:l,zoomOnScroll:x,fitView:u,zoomOnDoubleClick:false,connectionMode:He.ConnectionMode.Loose,defaultEdgeOptions:{type:"step",animated:false,style:{strokeWidth:3}},children:[jsxRuntime.jsx(A,{}),c&&jsxRuntime.jsx(He.Background,{gap:16,color:"#eee"}),p&&jsxRuntime.jsx(He.Controls,{}),s&&jsxRuntime.jsx(He.MiniMap,{nodeColor:f=>{switch(f.type){case "person":return "#4fc3f7";case "couple":return "#81c784";default:return "#eee"}},nodeStrokeWidth:3,zoomable:true,pannable:true})]},$)})})}function ze(e,t,n){let r=n.xPositions[e]??n.xPositions[n.xPositions.length-1]+(e-n.xPositions.length+1)*n.defaultXStep,i=n.verticalGaps[e]??n.defaultGap,d=-(t*n.nodeHeight+(t-1)*i)/2;return {xPosition:r,gap:i,startY:d}}function Ye(e,t,n,r,i,o){let d=[],a={},p={},s=Math.ceil(e.length/2),{xPosition:c,gap:l,startY:x}=ze(t,s,r);for(let u=0;u<e.length;u+=2){let g=e[u],C=e[u+1],y=u/2,m=x+y*(r.nodeHeight+l);if(C){let E=`c-${g.id}-${C.id}`;d.push({id:E,position:{x:c,y:m},type:"couple",data:{partner1:g,partner2:C,gen:t,onExpand:t===2?()=>n(E):void 0,onPersonClick:i,formatPersonSubtitle:o,coupleNodeWidth:r.coupleNodeWidth}}),a[g.id]=E,a[C.id]=E,p[E]=[g,C];}else d.push({id:g.id,position:{x:c,y:m},type:"person",data:{person:g,gen:t,isEdge:!!g.parentIds?.length,onPersonClick:i,formatPersonSubtitle:o,personNodeWidth:r.personNodeWidth}});}return {nodes:d,mappings:{personToCouple:a,coupleToPartners:p}}}function Ue(e,t){let n=[],r=new Set,i=new Set;return Object.values(t).forEach(o=>{if(i.has(o))return;i.add(o);let d=e.find(l=>l.id===o);if(!d?.data.partner1||!d?.data.partner2)return;let{partner1:a,partner2:p}=d.data,s=ae(o,a,"dad",t,de);s&&!r.has(s.id)&&(r.add(s.id),n.push(s));let c=ae(o,p,"mom",t,de);c&&!r.has(c.id)&&(r.add(c.id),n.push(c));}),n}function ae(e,t,n,r,i){if(!t.parentIds)return null;let[o,d]=t.parentIds,a=o?r[o]:d?r[d]:void 0;return a?{id:`${e}-${n}-${a}`,source:e,sourceHandle:n,target:a,type:"step",style:i.edgeStyle}:null}function Xe(e,t,n,r,i,o,d,a){let p=[],s=[],c={},l={},[x,u]=n[t],g=pe([x,u],e),C=pe(g,e),y=[g,C],m=[],E=r.find(v=>v.id===t),M=E?E.position.y+o.nodeHeight/2:0;y.forEach((v,$)=>{let N=3+$,H=Math.ceil(v.length/2),A=o.verticalGaps[N]??o.defaultGap,V=H*o.nodeHeight+(H-1)*A,D=M-V/2,f=o.xPositions[N]??o.xPositions[o.xPositions.length-1];for(let P=0;P<v.length;P+=2){let h=v[P],b=v[P+1],S=P/2,k=D+S*(o.nodeHeight+A);if(b){let T=`c-${h.id}-${b.id}`;p.push({id:T,position:{x:f,y:k},type:"couple",data:{partner1:h,partner2:b,gen:N,onPersonClick:d,formatPersonSubtitle:a,coupleNodeWidth:o.coupleNodeWidth}}),c[h.id]=T,c[b.id]=T,l[T]=[h,b],$===0&&(m.push(T),s.push({id:`t${i}-${t}-arrow-${T}`,source:t,sourceHandle:"arrow",target:T,type:"step",style:o.edgeStyle}));}else p.push({id:h.id,position:{x:f,y:k},type:"person",data:{person:h,gen:N,isEdge:!!h.parentIds?.length,onPersonClick:d,formatPersonSubtitle:a,personNodeWidth:o.personNodeWidth}});}});let G=Ze(m,l,c,i,o);return s.push(...G),{nodes:p,edges:s,mappings:{personToCouple:c,coupleToPartners:l}}}function pe(e,t){let n=[];return e.forEach(r=>{r.parentIds?.forEach(i=>{if(i){let o=t[i];o&&n.push(o);}});}),n}function Ze(e,t,n,r,i){let o=[],d=new Set;return e.forEach(a=>{let[p,s]=t[a];[p,s].forEach(c=>{c.parentIds?.forEach(l=>{if(!l)return;let x=n[l];if(!x)return;let u=c===p?"dad":"mom",g=`t${r}-${a}-${u}-${x}`;d.has(g)||(d.add(g),o.push({id:g,source:a,sourceHandle:u,target:x,type:"step",style:i.edgeStyle}));});});}),o}function _e(e,t,n){return t?e:e.filter(r=>{if(r.id.startsWith("t"))return false;let i=n.find(d=>d.id===r.source),o=n.find(d=>d.id===r.target);return (i?.data.gen??0)<=2&&(o?.data.gen??0)<=2})}exports.AncestorTree=ce;//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map