@bigplay/tree-view-core 0.5.0 → 0.6.0
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +243 -215
- package/dist/index.js.map +1 -1
- package/dist/rounds.d.ts +17 -0
- package/dist/scroll.d.ts +10 -0
- package/package.json +1 -1
- package/styles.css +51 -11
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D={nodeWidth:180,nodeHeight:64,levelGap:60,siblingGap:16,direction:"top-to-bottom",linkStyle:"elbow",levelLayout:"tree",levelAlign:"start"};function W(e){return{...D,...e}}function it(e){return e?.children}function k(e){return e?.id}function ot(e,t={}){if(e==null)return[];const n=Array.isArray(e)?e:[e];return n.length===0?[]:t.getParentId?st(n,t):rt(n,t)}function rt(e,t){const n=t.getId??k,i=t.getChildren??it,o=new Set;function s(r,l,c,h){const a=B(n(r),h);z(a,o);const u=(i(r)??[]).map((f,p)=>s(f,l+1,a,`${h}.${p}`));return{id:a,data:r,depth:l,parentId:c,children:u,hasChildren:u.length>0,collapsed:!1}}return e.map((r,l)=>s(r,0,null,String(l)))}function st(e,t){const n=t.getId??k,i=new Set,o=e.map((c,h)=>{const a=B(n(c),String(h));return z(a,i),{id:a,data:c,depth:0,parentId:null,children:[],hasChildren:!1,collapsed:!1}}),s=new Map(o.map(c=>[c.id,c])),r=[];if(o.forEach((c,h)=>{const a=t.getParentId(e[h]),d=a==null?void 0:s.get(String(a));if(!d){r.push(c);return}c.parentId=d.id,d.children.push(c),d.hasChildren=!0}),ct(r)!==o.length)throw new Error("[tree-view] В данных есть цикл: часть узлов ссылается друг на друга по кругу.");return r}function ct(e){const t=[...e];for(let n=0;n<t.length;n+=1){const i=t[n];for(const o of i.children)o.depth=i.depth+1,t.push(o)}return t.length}function B(e,t){return e==null||e===""?t:String(e)}function z(e,t){if(t.has(e))throw new Error(`[tree-view] Повторяющийся id узла: "${e}". Идентификаторы должны быть уникальными.`);t.add(e)}function ht(e,t={}){const n=e.filter(s=>s.length>0);if(n.length===0)return[];const i=n.length-1,o=n.map((s,r)=>s.map((l,c)=>({id:lt(l,r,c,t.getId),data:l,depth:i-r,parentId:null,children:[],hasChildren:!1,collapsed:!1})));for(let s=0;s<i;s+=1){const r=o[s],l=o[s+1],c=Math.ceil(r.length/l.length);r.forEach((h,a)=>{const d=l[Math.min(Math.floor(a/c),l.length-1)];h.parentId=d.id,d.children.push(h),d.hasChildren=!0})}return o[i]}function lt(e,t,n,i){const o=i?i(e,t,n):e?.id;return o==null||o===""?`${t}.${n}`:String(o)}function at(e,t){if(t.size===0)return e;function n(i){const o=t.has(i.id);return{...i,collapsed:o,children:o?[]:i.children.map(n)}}return e.map(n)}function dt(e,t){const n=new Set(e);return n.delete(t)||n.add(t),n}const A=2,H=40;function ut(e,t={}){const n=e?.teams??[],i=Math.max(t.rows??A,n.length),o=t.techDefeatLabel??"ТП",s=n.map((r,l)=>{const c=r.isTechDefeat===!0,h=r.isWinner===!0;return{key:String(r.id??`team-${l}`),team:r,score:c?o:String(r.score??""),isWinner:h,isLoser:!h,isMyTeam:r.isMyTeam===!0,isTechDefeat:c,isEmpty:!1}});for(let r=n.length;r<i;r+=1)s.push({key:`empty-${r}`,team:null,score:"",isWinner:!1,isLoser:!1,isMyTeam:!1,isTechDefeat:!1,isEmpty:!0});return{rows:s,hasMyTeam:n.some(r=>r.isMyTeam===!0)}}function ft(e,t={}){const n=e?.teams??[];return Math.max(t.rows??A,n.length)*(t.rowHeight??H)}const v={min:.2,max:3},C={x:0,y:0,scale:1};function L(e,t,n){return Math.min(Math.max(e,t),n)}function G(e,t,n=24,i=v){if(e.width<=0||e.height<=0||t.width<=0||t.height<=0)return{...C};const o=Math.max(t.width-n*2,1),s=Math.max(t.height-n*2,1),r=Math.min(o/e.width,s/e.height,1),l=L(r,i.min,i.max);return{scale:l,x:(t.width-e.width*l)/2,y:(t.height-e.height*l)/2}}function U(e,t,n){return{...e,x:e.x+t,y:e.y+n}}function b(e,t,n,i=v){const o=L(e.scale*t,i.min,i.max);if(o===e.scale)return e;const s=(n.x-e.x)/e.scale,r=(n.y-e.y)/e.scale;return{scale:o,x:n.x-s*o,y:n.y-r*o}}function pt(e){return`translate(${e.x}px, ${e.y}px) scale(${e.scale})`}function mt(e,t){const n=t.nodeWidth+t.levelGap,i=(e??[]).map((o,s)=>{const r=o.id??s;return{key:String(r),id:r,name:o.name,index:s,width:n}});return{columns:i,width:n*i.length,offset:t.levelGap/2}}function gt(e){const t=e.left+e.width/2-e.viewport/2;return L(t,0,Math.max(0,e.scrollWidth-e.viewport))}function wt(e,t){return Math.max(0,e-t)}function yt(e,t,n){if(t<=0||n<=0)return 0;const i=Math.ceil(e/t-.001);return L(i,0,n-1)}function xt(e,t,n){return n?t-1-e:e}const F=4;function Et(e,t){return t*L(e,1,F)}const Lt=20;function Mt(e){if(typeof window>"u")return null;let t=e?.parentElement??null;for(;t;){const{overflowX:n}=window.getComputedStyle(t);if((n==="auto"||n==="scroll")&&t.scrollWidth>t.clientWidth)return t;t=t.parentElement}return null}function St(e,t){if(typeof e.scrollTo=="function"){e.scrollTo({left:t,behavior:"smooth"});return}e.scrollLeft=t}function N(e,t){return e.getBoundingClientRect().left-t.getBoundingClientRect().left+t.scrollLeft}const Y=".tree-view__node";function $t(e,t,n,i){const o=e.getBoundingClientRect().left+i;return Array.from(e.querySelectorAll(Y)).filter(r=>{const l=r.getBoundingClientRect();return Math.floor((l.left+l.width/2-o)/n)===t})}function bt(e,t){return{left:N(e,t),width:e.getBoundingClientRect().width}}const X="tv-hidden";function Ct(e,t,n){e.querySelectorAll("[data-depth]").forEach(i=>{const o=Number(i.getAttribute("data-depth"));i.classList.toggle(X,n(o)<t)})}const Tt="(max-width: 768px)";function vt(e,t){if(typeof window>"u"||typeof window.matchMedia!="function")return()=>{};const n=window.matchMedia(e),i=()=>t(n.matches);return i(),typeof n.addEventListener=="function"?(n.addEventListener("change",i),()=>n.removeEventListener("change",i)):(n.addListener(i),()=>n.removeListener(i))}function It(e,t){if(!e)return()=>{};if(t(e.clientWidth),typeof ResizeObserver>"u")return()=>{};const n=new ResizeObserver(()=>t(e.clientWidth));return n.observe(e),()=>n.disconnect()}function Pt(e,t){if(!e||typeof MutationObserver>"u")return()=>{};const n=new MutationObserver(t);return n.observe(e,{childList:!0,subtree:!0}),()=>n.disconnect()}function x(e){return Math.round(e*100)/100}function V(e){return e==="top-to-bottom"||e==="bottom-to-top"}function Z(e,t,n){const i=e.x+e.width/2,o=e.y+e.height/2,s=t.x+t.width/2,r=t.y+t.height/2;switch(n){case"top-to-bottom":return{from:{x:i,y:e.y+e.height},to:{x:s,y:t.y}};case"bottom-to-top":return{from:{x:i,y:e.y},to:{x:s,y:t.y+t.height}};case"left-to-right":return{from:{x:e.x+e.width,y:o},to:{x:t.x,y:r}};case"right-to-left":return{from:{x:e.x,y:o},to:{x:t.x+t.width,y:r}}}}function q(e,t,n,i,o={}){const{elbowOffset:s,groupMid:r}=o,l=x(e.x),c=x(e.y),h=x(t.x),a=x(t.y);if(n==="straight")return`M ${l} ${c} L ${h} ${a}`;const d=V(i),u=x((l+h)/2),f=x((c+a)/2),p=(g,m,w)=>{if(s==null)return w;const y=Math.abs(m-g);return x(g+Math.sign(m-g)*Math.min(s,y))};if(n==="bracket"){if(d){const w=p(a,c,f),y=x(r??l);return`M ${l} ${c} L ${y} ${c} L ${y} ${w} L ${h} ${w} L ${h} ${a}`}const g=p(h,l,u),m=x(r??c);return`M ${l} ${c} L ${l} ${m} L ${g} ${m} L ${g} ${a} L ${h} ${a}`}if(n==="elbow"){if(d){const m=p(a,c,f);return`M ${l} ${c} L ${l} ${m} L ${h} ${m} L ${h} ${a}`}const g=p(h,l,u);return`M ${l} ${c} L ${g} ${c} L ${g} ${a} L ${h} ${a}`}return d?`M ${l} ${c} C ${l} ${f} ${h} ${f} ${h} ${a}`:`M ${l} ${c} C ${u} ${c} ${u} ${a} ${h} ${a}`}function Ot(e,t){const n=new Map;for(const i of e){if(i.parentId==null)continue;const o=t?i.x+i.width/2:i.y+i.height/2,s=n.get(i.parentId);s?(s.min=Math.min(s.min,o),s.max=Math.max(s.max,o)):n.set(i.parentId,{min:o,max:o})}return new Map([...n].map(([i,o])=>[i,(o.min+o.max)/2]))}function K(e,t){const n=new Map(e.map(s=>[s.id,s])),i=[],o=t.linkStyle==="bracket"?Ot(e,V(t.direction)):void 0;for(const s of e){const r=s.parentId==null?void 0:n.get(s.parentId);if(!r)continue;const{from:l,to:c}=Z(r,s,t.direction);i.push({id:`${r.id}->${s.id}`,source:r,target:s,path:q(l,c,t.linkStyle,t.direction,{elbowOffset:t.elbowOffset,groupMid:o?.get(r.id)})})}return i}function T(e,t){return typeof e=="function"?e(t):e}function j(e,t,n){return typeof e=="function"?e(t,n):e}function At(e){const t=[];for(const n of I(e))t[n.depth]=(t[n.depth]??0)+1;return t}function Q(e,t,n){const i=[];let o=0;for(let c=0;c<t.length;c+=1)i[c]=o,o+=(t[c]??0)+n;let s=0;for(const c of e){const h=t[c.depth]??c.alongSize;c.along=i[c.depth]+(h-c.alongSize)/2,s=Math.max(s,c.across+c.acrossSize)}const r=t.length-1;return{alongExtent:r<0?0:i[r]+t[r],acrossExtent:s}}function Rt(e,t){const n=t.direction==="top-to-bottom"||t.direction==="bottom-to-top",i=new Map,o=[],s=[],r=[];for(const h of I(e))(r[h.depth]??=[]).push(h);const l=[],c=[];if(r.forEach((h,a)=>{const d=[];let u=0;h.forEach((p,g)=>{const m=T(t.nodeWidth,p),w=T(t.nodeHeight,p),y=n?w:m,M=n?m:w;s[a]=Math.max(s[a]??0,y);const E={depth:a,along:0,across:u,alongSize:y,acrossSize:M};i.set(p.id,E),d.push(E),o.push(E);const $=h[g+1],P=$&&$.parentId!==p.parentId?t.groupGap??t.siblingGap:t.siblingGap;u+=M+j(P,p,h.length)});const f=d[d.length-1];l[a]=d,c[a]=f?f.across+f.acrossSize:0}),t.levelAlign==="center"){const h=Math.max(0,...c);l.forEach((a,d)=>{const u=(h-(c[d]??0))/2;if(u>0)for(const f of a)f.across+=u})}return{byId:i,...Q(o,s,t.levelGap)}}function J(e,t){if(t.levelLayout==="stack")return Rt(e,t);const n=t.direction==="top-to-bottom"||t.direction==="bottom-to-top",i=At(e),o=new Map,s=[];let r=0;const l=[];function c(h){const a=T(t.nodeWidth,h),d=T(t.nodeHeight,h),u=n?d:a,f=n?a:d;l[h.depth]=Math.max(l[h.depth]??0,u);const p=r,g=s.length;let m=r;if(h.children.length>0){const y=h.children.map(c),M=y[0],E=y[y.length-1],$=M.across+M.acrossSize/2,P=E.across+E.acrossSize/2;m=($+P)/2-f/2;const R=p-m;if(R>0){for(let O=g;O<s.length;O+=1)s[O].across+=R;m=p}}const w={depth:h.depth,along:0,across:m,alongSize:u,acrossSize:f};return o.set(h.id,w),s.push(w),r=Math.max(r,m+f+j(t.siblingGap,h,i[h.depth]??1)),w}return e.forEach(c),{byId:o,...Q(s,l,t.levelGap)}}function I(e){const t=[];function n(i){t.push(i),i.children.forEach(n)}return e.forEach(n),t}function _t(e,t){const n=W(t);if(e.length===0)return{nodes:[],links:[],width:0,height:0};const i=n.direction==="top-to-bottom"||n.direction==="bottom-to-top",{byId:o,alongExtent:s,acrossExtent:r}=J(e,n),l=n.direction==="bottom-to-top"||n.direction==="right-to-left",c=I(e).map(h=>{const{along:a,across:d,alongSize:u,acrossSize:f}=o.get(h.id),p=l?s-u-a:a;return{id:h.id,data:h.data,depth:h.depth,parentId:h.parentId,x:i?d:p,y:i?p:d,width:i?f:u,height:i?u:f,hasChildren:h.hasChildren,collapsed:h.collapsed}});return{nodes:c,links:K(c,n),width:i?r:s,height:i?s:r}}const S=1.2;class Dt{constructor(t){this.params=t,this.transform={...C},this.element=null,this.pointers=new Map,this.lastPinchDistance=0,this.onPointerDown=n=>{this.isPannable()&&(n.target?.closest?.("[data-tv-no-pan]")||(this.pointers.set(n.pointerId,{x:n.clientX,y:n.clientY}),this.element?.setPointerCapture?.(n.pointerId)))},this.onPointerMove=n=>{const i=this.pointers.get(n.pointerId);if(i){if(this.pointers.set(n.pointerId,{x:n.clientX,y:n.clientY}),this.pointers.size===2){this.pinch();return}this.set(U(this.transform,n.clientX-i.x,n.clientY-i.y))}},this.onPointerUp=n=>{this.pointers.delete(n.pointerId),this.pointers.size<2&&(this.lastPinchDistance=0)},this.onWheel=n=>{if(!this.isZoomable())return;n.preventDefault();const i=n.deltaY<0?S:1/S;this.set(b(this.transform,i,this.toLocalPoint(n),this.limits))}}attach(t){this.detach(),this.element=t,t.addEventListener("pointerdown",this.onPointerDown),t.addEventListener("pointermove",this.onPointerMove),t.addEventListener("pointerup",this.onPointerUp),t.addEventListener("pointercancel",this.onPointerUp),t.addEventListener("wheel",this.onWheel,{passive:!1})}detach(){const t=this.element;t&&(t.removeEventListener("pointerdown",this.onPointerDown),t.removeEventListener("pointermove",this.onPointerMove),t.removeEventListener("pointerup",this.onPointerUp),t.removeEventListener("pointercancel",this.onPointerUp),t.removeEventListener("wheel",this.onWheel),this.element=null,this.pointers.clear(),this.lastPinchDistance=0)}fit(t,n=24){const i=this.element;i&&this.set(G(t,{width:i.clientWidth,height:i.clientHeight},n,this.limits))}zoomIn(){this.zoomToCenter(S)}zoomOut(){this.zoomToCenter(1/S)}reset(){this.set({...C})}set(t){this.transform={...t},this.params.onChange(this.transform)}get limits(){return this.params.limits?.()??v}isPannable(){return this.params.pannable?.()??!0}isZoomable(){return this.params.zoomable?.()??!0}toLocalPoint(t){const n=this.element?.getBoundingClientRect();return{x:t.clientX-(n?.left??0),y:t.clientY-(n?.top??0)}}zoomToCenter(t){const n=this.element,i={x:(n?.clientWidth??0)/2,y:(n?.clientHeight??0)/2};this.set(b(this.transform,t,i,this.limits))}pinch(){if(!this.isZoomable())return;const[t,n]=[...this.pointers.values()];if(!t||!n)return;const i=Math.hypot(t.x-n.x,t.y-n.y);if(this.lastPinchDistance>0&&i>0){const o=this.toLocalPoint({clientX:(t.x+n.x)/2,clientY:(t.y+n.y)/2});this.set(b(this.transform,i/this.lastPinchDistance,o,this.limits))}this.lastPinchDistance=i}}function Wt(e,t="tree-view-styles"){if(typeof document>"u"||document.getElementById(t))return;const n=document.createElement("style");n.id=t,n.textContent=e,document.head.appendChild(n)}const tt=400,et=150;function _(e,t){return e.x<=t.x+t.width&&e.x+e.width>=t.x&&e.y<=t.y+t.height&&e.y+e.height>=t.y}function kt(e){const{source:t,target:n}=e,i=Math.min(t.x,n.x),o=Math.min(t.y,n.y);return{x:i,y:o,width:Math.max(t.x+t.width,n.x+n.width)-i,height:Math.max(t.y+t.height,n.y+n.height)-o}}function nt(e,t){return{x:e.x-t,y:e.y-t,width:e.width+t*2,height:e.height+t*2}}function Bt(e,t,n=tt){if(!t||e.nodes.length<et)return e;if(t.width<=0||t.height<=0)return{nodes:[],links:[],width:e.width,height:e.height};const i=nt(t,n),o=[],s=[];for(const r of e.nodes)_(i,r)&&o.push(r);for(const r of e.links)_(i,kt(r))&&s.push(r);return{nodes:o,links:s,width:e.width,height:e.height}}function zt(){return typeof window>"u"?null:{x:0,y:0,width:window.innerWidth,height:window.innerHeight}}function Ht(e,t){if(!e||typeof window>"u"||t<=0)return null;const n=e.getBoundingClientRect(),i=Math.max(n.left,0),o=Math.max(n.top,0),s=Math.min(n.right,window.innerWidth),r=Math.min(n.bottom,window.innerHeight);return s<=i||r<=o?{x:0,y:0,width:0,height:0}:{x:(i-n.left)/t,y:(o-n.top)/t,width:(s-i)/t,height:(r-o)/t}}exports.DEFAULT_BRACKET_ROWS=A;exports.DEFAULT_BRACKET_ROW_HEIGHT=H;exports.DEFAULT_OPTIONS=D;exports.DEFAULT_OVERSCAN=tt;exports.DEFAULT_SCALE_LIMITS=v;exports.HIDDEN_CLASS=X;exports.IDENTITY_TRANSFORM=C;exports.MAX_GAP_STEPS=F;exports.MOBILE_MEDIA_QUERY=Tt;exports.PanZoomController=Dt;exports.SWIPE_ELBOW_OFFSET=Lt;exports.TREE_NODE_SELECTOR=Y;exports.VIRTUALIZE_THRESHOLD=et;exports.ZOOM_STEP=S;exports.applyCollapsed=at;exports.bracketCardHeight=ft;exports.buildBracketCard=ut;exports.buildBracketRounds=mt;exports.buildLinks=K;exports.buildPath=q;exports.clamp=L;exports.estimateVisibleRect=zt;exports.expandRect=nt;exports.findScrollParent=Mt;exports.fitToViewport=G;exports.flatten=I;exports.fromLevels=ht;exports.getAnchors=Z;exports.hideRoundsBefore=Ct;exports.horizontalBox=bt;exports.injectStyles=Wt;exports.layoutTree=_t;exports.levelGapForWidth=wt;exports.measureVisibleRect=Ht;exports.nodesInColumn=$t;exports.offsetWithin=N;exports.panBy=U;exports.placeNodes=J;exports.resolveOptions=W;exports.roundAtScroll=yt;exports.roundOfDepth=xt;exports.roundScrollLeft=gt;exports.roundSiblingGap=Et;exports.smoothScrollLeft=St;exports.toCssTransform=pt;exports.toTree=ot;exports.toggleCollapsed=dt;exports.visibleLayout=Bt;exports.watchChildren=Pt;exports.watchMedia=vt;exports.watchWidth=It;exports.zoomAtPoint=b;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D={nodeWidth:180,nodeHeight:64,levelGap:60,siblingGap:16,direction:"top-to-bottom",linkStyle:"elbow",levelLayout:"tree",levelAlign:"start"};function W(e){return{...D,...e}}function ot(e){return e?.children}function k(e){return e?.id}function rt(e,t={}){if(e==null)return[];const n=Array.isArray(e)?e:[e];return n.length===0?[]:t.getParentId?ct(n,t):st(n,t)}function st(e,t){const n=t.getId??k,i=t.getChildren??ot,o=new Set;function r(s,h,c,l){const a=B(n(s),l);z(a,o);const u=(i(s)??[]).map((f,p)=>r(f,h+1,a,`${l}.${p}`));return{id:a,data:s,depth:h,parentId:c,children:u,hasChildren:u.length>0,collapsed:!1}}return e.map((s,h)=>r(s,0,null,String(h)))}function ct(e,t){const n=t.getId??k,i=new Set,o=e.map((c,l)=>{const a=B(n(c),String(l));return z(a,i),{id:a,data:c,depth:0,parentId:null,children:[],hasChildren:!1,collapsed:!1}}),r=new Map(o.map(c=>[c.id,c])),s=[];if(o.forEach((c,l)=>{const a=t.getParentId(e[l]),d=a==null?void 0:r.get(String(a));if(!d){s.push(c);return}c.parentId=d.id,d.children.push(c),d.hasChildren=!0}),lt(s)!==o.length)throw new Error("[tree-view] В данных есть цикл: часть узлов ссылается друг на друга по кругу.");return s}function lt(e){const t=[...e];for(let n=0;n<t.length;n+=1){const i=t[n];for(const o of i.children)o.depth=i.depth+1,t.push(o)}return t.length}function B(e,t){return e==null||e===""?t:String(e)}function z(e,t){if(t.has(e))throw new Error(`[tree-view] Повторяющийся id узла: "${e}". Идентификаторы должны быть уникальными.`);t.add(e)}function ht(e,t={}){const n=e.filter(r=>r.length>0);if(n.length===0)return[];const i=n.length-1,o=n.map((r,s)=>r.map((h,c)=>({id:at(h,s,c,t.getId),data:h,depth:i-s,parentId:null,children:[],hasChildren:!1,collapsed:!1})));for(let r=0;r<i;r+=1){const s=o[r],h=o[r+1],c=Math.ceil(s.length/h.length);s.forEach((l,a)=>{const d=h[Math.min(Math.floor(a/c),h.length-1)];l.parentId=d.id,d.children.push(l),d.hasChildren=!0})}return o[i]}function at(e,t,n,i){const o=i?i(e,t,n):e?.id;return o==null||o===""?`${t}.${n}`:String(o)}function dt(e,t){if(t.size===0)return e;function n(i){const o=t.has(i.id);return{...i,collapsed:o,children:o?[]:i.children.map(n)}}return e.map(n)}function ut(e,t){const n=new Set(e);return n.delete(t)||n.add(t),n}const O=2,H=40;function ft(e,t={}){const n=e?.teams??[],i=Math.max(t.rows??O,n.length),o=t.techDefeatLabel??"ТП",r=n.map((s,h)=>{const c=s.isTechDefeat===!0,l=s.isWinner===!0;return{key:String(s.id??`team-${h}`),team:s,score:c?o:String(s.score??""),isWinner:l,isLoser:!l,isMyTeam:s.isMyTeam===!0,isTechDefeat:c,isEmpty:!1}});for(let s=n.length;s<i;s+=1)r.push({key:`empty-${s}`,team:null,score:"",isWinner:!1,isLoser:!1,isMyTeam:!1,isTechDefeat:!1,isEmpty:!0});return{rows:r,hasMyTeam:n.some(s=>s.isMyTeam===!0)}}function pt(e,t={}){const n=e?.teams??[];return Math.max(t.rows??O,n.length)*(t.rowHeight??H)}const T={min:.2,max:3},C={x:0,y:0,scale:1};function L(e,t,n){return Math.min(Math.max(e,t),n)}function F(e,t,n=24,i=T){if(e.width<=0||e.height<=0||t.width<=0||t.height<=0)return{...C};const o=Math.max(t.width-n*2,1),r=Math.max(t.height-n*2,1),s=Math.min(o/e.width,r/e.height,1),h=L(s,i.min,i.max);return{scale:h,x:(t.width-e.width*h)/2,y:(t.height-e.height*h)/2}}function G(e,t,n){return{...e,x:e.x+t,y:e.y+n}}function b(e,t,n,i=T){const o=L(e.scale*t,i.min,i.max);if(o===e.scale)return e;const r=(n.x-e.x)/e.scale,s=(n.y-e.y)/e.scale;return{scale:o,x:n.x-r*o,y:n.y-s*o}}function gt(e){return`translate(${e.x}px, ${e.y}px) scale(${e.scale})`}function mt(e,t){const n=t.nodeWidth+t.levelGap,i=(e??[]).map((o,r)=>{const s=o.id??r;return{key:String(s),id:s,name:o.name,index:r,width:n}});return{columns:i,width:n*i.length,offset:t.levelGap/2}}function wt(e){const t=e.left+e.width/2-e.viewport/2;return L(t,0,Math.max(0,e.scrollWidth-e.viewport))}function yt(e,t){return Math.max(0,e-t)}function Et(e,t,n){if(t<=0||n<=0)return 0;const i=Math.ceil(e/t-.001);return L(i,0,n-1)}function U(e,t,n){return n?t-1-e:e}const N=4;function xt(e,t){return t*L(e,1,N)}const Lt=20;function Mt(e,t,n,i){let o=0;for(const r of e)U(r.depth,n,i)===t&&(o=Math.max(o,r.y+r.height));return o}function St(e){if(typeof window>"u")return null;let t=e?.parentElement??null;for(;t;){const{overflowX:n}=window.getComputedStyle(t);if((n==="auto"||n==="scroll")&&t.scrollWidth>t.clientWidth)return t;t=t.parentElement}return null}function $t(e,t){if(typeof e.scrollTo=="function"){e.scrollTo({left:t,behavior:"smooth"});return}e.scrollLeft=t}function Y(e,t){return e.getBoundingClientRect().left-t.getBoundingClientRect().left+t.scrollLeft}const X=".tree-view__node";function bt(e,t,n,i){const o=e.getBoundingClientRect().left+i;return Array.from(e.querySelectorAll(X)).filter(s=>{const h=s.getBoundingClientRect();return Math.floor((h.left+h.width/2-o)/n)===t})}function Ct(e,t){return{left:Y(e,t),width:e.getBoundingClientRect().width}}const V="tv-hidden";function vt(e,t,n){e.querySelectorAll("[data-depth]").forEach(i=>{const o=Number(i.getAttribute("data-depth"));i.classList.toggle(V,n(o)<t)})}function Tt(e,t,n=1e3){if(typeof e.onscrollend<"u"){const h=()=>{e.removeEventListener("scrollend",h),t()};e.addEventListener("scrollend",h,{once:!0}),setTimeout(h,n);return}const o=Date.now();let r=e.scrollLeft;const s=()=>{const h=e.scrollLeft===r;if(r=e.scrollLeft,h||Date.now()-o>n){t();return}requestAnimationFrame(s)};requestAnimationFrame(s)}const It="(max-width: 768px)";function Pt(e,t){if(typeof window>"u"||typeof window.matchMedia!="function")return()=>{};const n=window.matchMedia(e),i=()=>t(n.matches);return i(),typeof n.addEventListener=="function"?(n.addEventListener("change",i),()=>n.removeEventListener("change",i)):(n.addListener(i),()=>n.removeListener(i))}function At(e,t){if(!e)return()=>{};if(t(e.clientWidth),typeof ResizeObserver>"u")return()=>{};const n=new ResizeObserver(()=>t(e.clientWidth));return n.observe(e),()=>n.disconnect()}function Ot(e,t){if(!e||typeof MutationObserver>"u")return()=>{};const n=new MutationObserver(t);return n.observe(e,{childList:!0,subtree:!0}),()=>n.disconnect()}function E(e){return Math.round(e*100)/100}function Z(e){return e==="top-to-bottom"||e==="bottom-to-top"}function q(e,t,n){const i=e.x+e.width/2,o=e.y+e.height/2,r=t.x+t.width/2,s=t.y+t.height/2;switch(n){case"top-to-bottom":return{from:{x:i,y:e.y+e.height},to:{x:r,y:t.y}};case"bottom-to-top":return{from:{x:i,y:e.y},to:{x:r,y:t.y+t.height}};case"left-to-right":return{from:{x:e.x+e.width,y:o},to:{x:t.x,y:s}};case"right-to-left":return{from:{x:e.x,y:o},to:{x:t.x+t.width,y:s}}}}function K(e,t,n,i,o={}){const{elbowOffset:r,groupMid:s}=o,h=E(e.x),c=E(e.y),l=E(t.x),a=E(t.y);if(n==="straight")return`M ${h} ${c} L ${l} ${a}`;const d=Z(i),u=E((h+l)/2),f=E((c+a)/2),p=(m,g,w)=>{if(r==null)return w;const y=Math.abs(g-m);return E(m+Math.sign(g-m)*Math.min(r,y))};if(n==="bracket"){if(d){const w=p(a,c,f),y=E(s??h);return`M ${h} ${c} L ${y} ${c} L ${y} ${w} L ${l} ${w} L ${l} ${a}`}const m=p(l,h,u),g=E(s??c);return`M ${h} ${c} L ${h} ${g} L ${m} ${g} L ${m} ${a} L ${l} ${a}`}if(n==="elbow"){if(d){const g=p(a,c,f);return`M ${h} ${c} L ${h} ${g} L ${l} ${g} L ${l} ${a}`}const m=p(l,h,u);return`M ${h} ${c} L ${m} ${c} L ${m} ${a} L ${l} ${a}`}return d?`M ${h} ${c} C ${h} ${f} ${l} ${f} ${l} ${a}`:`M ${h} ${c} C ${u} ${c} ${u} ${a} ${l} ${a}`}function Rt(e,t){const n=new Map;for(const i of e){if(i.parentId==null)continue;const o=t?i.x+i.width/2:i.y+i.height/2,r=n.get(i.parentId);r?(r.min=Math.min(r.min,o),r.max=Math.max(r.max,o)):n.set(i.parentId,{min:o,max:o})}return new Map([...n].map(([i,o])=>[i,(o.min+o.max)/2]))}function j(e,t){const n=new Map(e.map(r=>[r.id,r])),i=[],o=t.linkStyle==="bracket"?Rt(e,Z(t.direction)):void 0;for(const r of e){const s=r.parentId==null?void 0:n.get(r.parentId);if(!s)continue;const{from:h,to:c}=q(s,r,t.direction);i.push({id:`${s.id}->${r.id}`,source:s,target:r,path:K(h,c,t.linkStyle,t.direction,{elbowOffset:t.elbowOffset,groupMid:o?.get(s.id)})})}return i}function v(e,t){return typeof e=="function"?e(t):e}function Q(e,t,n){return typeof e=="function"?e(t,n):e}function _t(e){const t=[];for(const n of I(e))t[n.depth]=(t[n.depth]??0)+1;return t}function J(e,t,n){const i=[];let o=0;for(let c=0;c<t.length;c+=1)i[c]=o,o+=(t[c]??0)+n;let r=0;for(const c of e){const l=t[c.depth]??c.alongSize;c.along=i[c.depth]+(l-c.alongSize)/2,r=Math.max(r,c.across+c.acrossSize)}const s=t.length-1;return{alongExtent:s<0?0:i[s]+t[s],acrossExtent:r}}function Dt(e,t){const n=t.direction==="top-to-bottom"||t.direction==="bottom-to-top",i=new Map,o=[],r=[],s=[];for(const l of I(e))(s[l.depth]??=[]).push(l);const h=[],c=[];if(s.forEach((l,a)=>{const d=[];let u=0;l.forEach((p,m)=>{const g=v(t.nodeWidth,p),w=v(t.nodeHeight,p),y=n?w:g,M=n?g:w;r[a]=Math.max(r[a]??0,y);const x={depth:a,along:0,across:u,alongSize:y,acrossSize:M};i.set(p.id,x),d.push(x),o.push(x);const $=l[m+1],P=$&&$.parentId!==p.parentId?t.groupGap??t.siblingGap:t.siblingGap;u+=M+Q(P,p,l.length)});const f=d[d.length-1];h[a]=d,c[a]=f?f.across+f.acrossSize:0}),t.levelAlign==="center"){const l=Math.max(0,...c);h.forEach((a,d)=>{const u=(l-(c[d]??0))/2;if(u>0)for(const f of a)f.across+=u})}return{byId:i,...J(o,r,t.levelGap)}}function tt(e,t){if(t.levelLayout==="stack")return Dt(e,t);const n=t.direction==="top-to-bottom"||t.direction==="bottom-to-top",i=_t(e),o=new Map,r=[];let s=0;const h=[];function c(l){const a=v(t.nodeWidth,l),d=v(t.nodeHeight,l),u=n?d:a,f=n?a:d;h[l.depth]=Math.max(h[l.depth]??0,u);const p=s,m=r.length;let g=s;if(l.children.length>0){const y=l.children.map(c),M=y[0],x=y[y.length-1],$=M.across+M.acrossSize/2,P=x.across+x.acrossSize/2;g=($+P)/2-f/2;const R=p-g;if(R>0){for(let A=m;A<r.length;A+=1)r[A].across+=R;g=p}}const w={depth:l.depth,along:0,across:g,alongSize:u,acrossSize:f};return o.set(l.id,w),r.push(w),s=Math.max(s,g+f+Q(t.siblingGap,l,i[l.depth]??1)),w}return e.forEach(c),{byId:o,...J(r,h,t.levelGap)}}function I(e){const t=[];function n(i){t.push(i),i.children.forEach(n)}return e.forEach(n),t}function Wt(e,t){const n=W(t);if(e.length===0)return{nodes:[],links:[],width:0,height:0};const i=n.direction==="top-to-bottom"||n.direction==="bottom-to-top",{byId:o,alongExtent:r,acrossExtent:s}=tt(e,n),h=n.direction==="bottom-to-top"||n.direction==="right-to-left",c=I(e).map(l=>{const{along:a,across:d,alongSize:u,acrossSize:f}=o.get(l.id),p=h?r-u-a:a;return{id:l.id,data:l.data,depth:l.depth,parentId:l.parentId,x:i?d:p,y:i?p:d,width:i?f:u,height:i?u:f,hasChildren:l.hasChildren,collapsed:l.collapsed}});return{nodes:c,links:j(c,n),width:i?s:r,height:i?r:s}}const S=1.2;class kt{constructor(t){this.params=t,this.transform={...C},this.element=null,this.pointers=new Map,this.lastPinchDistance=0,this.onPointerDown=n=>{this.isPannable()&&(n.target?.closest?.("[data-tv-no-pan]")||(this.pointers.set(n.pointerId,{x:n.clientX,y:n.clientY}),this.element?.setPointerCapture?.(n.pointerId)))},this.onPointerMove=n=>{const i=this.pointers.get(n.pointerId);if(i){if(this.pointers.set(n.pointerId,{x:n.clientX,y:n.clientY}),this.pointers.size===2){this.pinch();return}this.set(G(this.transform,n.clientX-i.x,n.clientY-i.y))}},this.onPointerUp=n=>{this.pointers.delete(n.pointerId),this.pointers.size<2&&(this.lastPinchDistance=0)},this.onWheel=n=>{if(!this.isZoomable())return;n.preventDefault();const i=n.deltaY<0?S:1/S;this.set(b(this.transform,i,this.toLocalPoint(n),this.limits))}}attach(t){this.detach(),this.element=t,t.addEventListener("pointerdown",this.onPointerDown),t.addEventListener("pointermove",this.onPointerMove),t.addEventListener("pointerup",this.onPointerUp),t.addEventListener("pointercancel",this.onPointerUp),t.addEventListener("wheel",this.onWheel,{passive:!1})}detach(){const t=this.element;t&&(t.removeEventListener("pointerdown",this.onPointerDown),t.removeEventListener("pointermove",this.onPointerMove),t.removeEventListener("pointerup",this.onPointerUp),t.removeEventListener("pointercancel",this.onPointerUp),t.removeEventListener("wheel",this.onWheel),this.element=null,this.pointers.clear(),this.lastPinchDistance=0)}fit(t,n=24){const i=this.element;i&&this.set(F(t,{width:i.clientWidth,height:i.clientHeight},n,this.limits))}zoomIn(){this.zoomToCenter(S)}zoomOut(){this.zoomToCenter(1/S)}reset(){this.set({...C})}set(t){this.transform={...t},this.params.onChange(this.transform)}get limits(){return this.params.limits?.()??T}isPannable(){return this.params.pannable?.()??!0}isZoomable(){return this.params.zoomable?.()??!0}toLocalPoint(t){const n=this.element?.getBoundingClientRect();return{x:t.clientX-(n?.left??0),y:t.clientY-(n?.top??0)}}zoomToCenter(t){const n=this.element,i={x:(n?.clientWidth??0)/2,y:(n?.clientHeight??0)/2};this.set(b(this.transform,t,i,this.limits))}pinch(){if(!this.isZoomable())return;const[t,n]=[...this.pointers.values()];if(!t||!n)return;const i=Math.hypot(t.x-n.x,t.y-n.y);if(this.lastPinchDistance>0&&i>0){const o=this.toLocalPoint({clientX:(t.x+n.x)/2,clientY:(t.y+n.y)/2});this.set(b(this.transform,i/this.lastPinchDistance,o,this.limits))}this.lastPinchDistance=i}}function Bt(e,t="tree-view-styles"){if(typeof document>"u"||document.getElementById(t))return;const n=document.createElement("style");n.id=t,n.textContent=e,document.head.appendChild(n)}const et=400,nt=150;function _(e,t){return e.x<=t.x+t.width&&e.x+e.width>=t.x&&e.y<=t.y+t.height&&e.y+e.height>=t.y}function zt(e){const{source:t,target:n}=e,i=Math.min(t.x,n.x),o=Math.min(t.y,n.y);return{x:i,y:o,width:Math.max(t.x+t.width,n.x+n.width)-i,height:Math.max(t.y+t.height,n.y+n.height)-o}}function it(e,t){return{x:e.x-t,y:e.y-t,width:e.width+t*2,height:e.height+t*2}}function Ht(e,t,n=et){if(!t||e.nodes.length<nt)return e;if(t.width<=0||t.height<=0)return{nodes:[],links:[],width:e.width,height:e.height};const i=it(t,n),o=[],r=[];for(const s of e.nodes)_(i,s)&&o.push(s);for(const s of e.links)_(i,zt(s))&&r.push(s);return{nodes:o,links:r,width:e.width,height:e.height}}function Ft(){return typeof window>"u"?null:{x:0,y:0,width:window.innerWidth,height:window.innerHeight}}function Gt(e,t){if(!e||typeof window>"u"||t<=0)return null;const n=e.getBoundingClientRect(),i=Math.max(n.left,0),o=Math.max(n.top,0),r=Math.min(n.right,window.innerWidth),s=Math.min(n.bottom,window.innerHeight);return r<=i||s<=o?{x:0,y:0,width:0,height:0}:{x:(i-n.left)/t,y:(o-n.top)/t,width:(r-i)/t,height:(s-o)/t}}exports.DEFAULT_BRACKET_ROWS=O;exports.DEFAULT_BRACKET_ROW_HEIGHT=H;exports.DEFAULT_OPTIONS=D;exports.DEFAULT_OVERSCAN=et;exports.DEFAULT_SCALE_LIMITS=T;exports.HIDDEN_CLASS=V;exports.IDENTITY_TRANSFORM=C;exports.MAX_GAP_STEPS=N;exports.MOBILE_MEDIA_QUERY=It;exports.PanZoomController=kt;exports.SWIPE_ELBOW_OFFSET=Lt;exports.TREE_NODE_SELECTOR=X;exports.VIRTUALIZE_THRESHOLD=nt;exports.ZOOM_STEP=S;exports.afterScrollEnd=Tt;exports.applyCollapsed=dt;exports.bracketCardHeight=pt;exports.buildBracketCard=ft;exports.buildBracketRounds=mt;exports.buildLinks=j;exports.buildPath=K;exports.clamp=L;exports.estimateVisibleRect=Ft;exports.expandRect=it;exports.findScrollParent=St;exports.fitToViewport=F;exports.flatten=I;exports.fromLevels=ht;exports.getAnchors=q;exports.hideRoundsBefore=vt;exports.horizontalBox=Ct;exports.injectStyles=Bt;exports.layoutTree=Wt;exports.levelGapForWidth=yt;exports.measureVisibleRect=Gt;exports.nodesInColumn=bt;exports.offsetWithin=Y;exports.panBy=G;exports.placeNodes=tt;exports.resolveOptions=W;exports.roundAtScroll=Et;exports.roundContentHeight=Mt;exports.roundOfDepth=U;exports.roundScrollLeft=wt;exports.roundSiblingGap=xt;exports.smoothScrollLeft=$t;exports.toCssTransform=gt;exports.toTree=rt;exports.toggleCollapsed=ut;exports.visibleLayout=Ht;exports.watchChildren=Ot;exports.watchMedia=Pt;exports.watchWidth=At;exports.zoomAtPoint=b;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/defaults.ts","../src/normalize.ts","../src/fromLevels.ts","../src/collapse.ts","../src/bracket.ts","../src/viewport.ts","../src/rounds.ts","../src/scroll.ts","../src/media.ts","../src/links.ts","../src/layout.ts","../src/panZoom.ts","../src/injectStyles.ts","../src/virtualize.ts"],"sourcesContent":["import type { TreeViewOptions } from './types'\n\n/** Значения по умолчанию. Меняются пропом `options` — частично, по одному полю. */\nexport const DEFAULT_OPTIONS: TreeViewOptions = {\n nodeWidth: 180,\n nodeHeight: 64,\n levelGap: 60,\n siblingGap: 16,\n direction: 'top-to-bottom',\n linkStyle: 'elbow',\n levelLayout: 'tree',\n levelAlign: 'start',\n}\n\n/**\n * Дополняет пользовательские настройки значениями по умолчанию.\n * Благодаря этому можно передать только `{ nodeWidth: 240 }` и ничего не сломать.\n */\nexport function resolveOptions<T>(options?: Partial<TreeViewOptions<T>>): TreeViewOptions<T> {\n return { ...DEFAULT_OPTIONS, ...options }\n}\n","import type { TreeNode } from './types'\n\n/**\n * Как читать чужие данные.\n * Любую из функций можно не передавать — тогда работает поведение по умолчанию.\n */\nexport interface Accessors<T> {\n /** Идентификатор узла. По умолчанию — поле `id`, а если его нет — путь вида `\"0.1.2\"`. */\n getId?: (item: T) => string | number | null | undefined\n /** Дети узла. По умолчанию — поле `children`. */\n getChildren?: (item: T) => T[] | null | undefined\n /**\n * Идентификатор родителя. Если передан — данные считаются\n * ПЛОСКИМ списком (`[{ id, parentId }, ...]`), а не вложенным деревом.\n */\n getParentId?: (item: T) => string | number | null | undefined\n}\n\n/** Читает `item.children`, если пользователь не задал свой аксессор. */\nfunction defaultGetChildren<T>(item: T): T[] | null | undefined {\n return (item as { children?: T[] })?.children\n}\n\n/** Читает `item.id`, если пользователь не задал свой аксессор. */\nfunction defaultGetId<T>(item: T): string | number | null | undefined {\n return (item as { id?: string | number })?.id\n}\n\n/**\n * Превращает входные данные в массив корней дерева.\n *\n * Поддерживает два формата:\n * 1. Вложенный: `{ id: 1, children: [...] }` или массив таких объектов;\n * 2. Плоский: `[{ id: 2, parentId: 1 }, ...]` — включается передачей `getParentId`.\n */\nexport function toTree<T>(input: T | T[] | null | undefined, accessors: Accessors<T> = {}): TreeNode<T>[] {\n if (input == null) return []\n\n const items = Array.isArray(input) ? input : [input]\n if (items.length === 0) return []\n\n return accessors.getParentId\n ? fromFlatList(items, accessors as Accessors<T> & { getParentId: NonNullable<Accessors<T>['getParentId']> })\n : fromNested(items, accessors)\n}\n\n/** Вложенный формат: рекурсивно обходим `children`. */\nfunction fromNested<T>(items: T[], accessors: Accessors<T>): TreeNode<T>[] {\n const getId = accessors.getId ?? defaultGetId\n const getChildren = accessors.getChildren ?? defaultGetChildren\n const usedIds = new Set<string>()\n\n /** `path` — запасной идентификатор: он стабилен, пока не меняется порядок данных. */\n function build(item: T, depth: number, parentId: string | null, path: string): TreeNode<T> {\n const id = toId(getId(item), path)\n assertUniqueId(id, usedIds)\n\n const rawChildren = getChildren(item) ?? []\n const children = rawChildren.map((child, index) => build(child, depth + 1, id, `${path}.${index}`))\n\n return { id, data: item, depth, parentId, children, hasChildren: children.length > 0, collapsed: false }\n }\n\n return items.map((item, index) => build(item, 0, null, String(index)))\n}\n\n/** Плоский формат: собираем детей по `parentId`. */\nfunction fromFlatList<T>(\n items: T[],\n accessors: Accessors<T> & { getParentId: NonNullable<Accessors<T>['getParentId']> },\n): TreeNode<T>[] {\n const getId = accessors.getId ?? defaultGetId\n const usedIds = new Set<string>()\n\n // Шаг 1: заготовки узлов без связей.\n const nodes = items.map((item, index) => {\n const id = toId(getId(item), String(index))\n assertUniqueId(id, usedIds)\n return { id, data: item, depth: 0, parentId: null, children: [], hasChildren: false, collapsed: false } as TreeNode<T>\n })\n\n const byId = new Map(nodes.map((node) => [node.id, node]))\n const roots: TreeNode<T>[] = []\n\n // Шаг 2: каждого ребёнка кладём в массив его родителя.\n nodes.forEach((node, index) => {\n const rawParentId = accessors.getParentId(items[index]!)\n const parent = rawParentId == null ? undefined : byId.get(String(rawParentId))\n\n if (!parent) {\n roots.push(node)\n return\n }\n node.parentId = parent.id\n parent.children.push(node)\n parent.hasChildren = true\n })\n\n // Шаг 3: считаем глубину сверху вниз. Заодно ловим циклы (A → B → A).\n const visited = setDepths(roots)\n if (visited !== nodes.length) {\n throw new Error('[tree-view] В данных есть цикл: часть узлов ссылается друг на друга по кругу.')\n }\n\n return roots\n}\n\n/**\n * Проставляет `depth` обходом в ширину и возвращает число посещённых узлов.\n *\n * Очередь читается указателем, а не `shift()`: тот сдвигает весь массив,\n * и на десятках тысяч узлов обход превращается в квадрат.\n */\nfunction setDepths<T>(roots: TreeNode<T>[]): number {\n const queue = [...roots]\n\n for (let index = 0; index < queue.length; index += 1) {\n const node = queue[index]!\n for (const child of node.children) {\n child.depth = node.depth + 1\n queue.push(child)\n }\n }\n return queue.length\n}\n\n/** Приводит идентификатор к строке, подставляя путь, если его нет. */\nfunction toId(rawId: string | number | null | undefined, fallback: string): string {\n return rawId == null || rawId === '' ? fallback : String(rawId)\n}\n\n/** Одинаковые id ломают отрисовку молча — лучше сразу сказать об этом вслух. */\nfunction assertUniqueId(id: string, usedIds: Set<string>): void {\n if (usedIds.has(id)) {\n throw new Error(`[tree-view] Повторяющийся id узла: \"${id}\". Идентификаторы должны быть уникальными.`)\n }\n usedIds.add(id)\n}\n","import type { TreeNode } from './types'\n\nexport interface FromLevelsOptions<T> {\n /** Идентификатор узла. По умолчанию — поле `id`, иначе «номер уровня.номер в уровне». */\n getId?: (item: T, level: number, index: number) => string | number | null | undefined\n}\n\n/**\n * Собирает дерево из «уровней», когда в данных нет ссылок на родителя,\n * а связь задана только позицией — так устроены bounty-сетки: пары групп\n * одного раунда сходятся в одну группу следующего.\n *\n * Уровни передаются от самого раннего раунда к финалу:\n *\n * ```ts\n * fromLevels([\n * [g1, g2, g3, g4], // первый раунд\n * [g5, g6], // второй: g1+g2 → g5, g3+g4 → g6\n * [g7], // финал: g5+g6 → g7\n * ])\n * ```\n *\n * Если уровни делятся неровно (например 5 групп на 2), лишние узлы\n * достаются последнему родителю — так же, как это выглядит в вёрстке.\n */\nexport function fromLevels<T>(levels: T[][], options: FromLevelsOptions<T> = {}): TreeNode<T>[] {\n const filled = levels.filter((level) => level.length > 0)\n if (filled.length === 0) return []\n\n const lastLevel = filled.length - 1\n\n // Сначала создаём узлы всех уровней, связи проставим следующим шагом.\n const nodes: TreeNode<T>[][] = filled.map((level, levelIndex) =>\n level.map((item, index) => ({\n id: resolveId(item, levelIndex, index, options.getId),\n data: item,\n // Финал — корень, поэтому глубина считается от последнего уровня.\n depth: lastLevel - levelIndex,\n parentId: null,\n children: [],\n hasChildren: false,\n collapsed: false,\n })),\n )\n\n for (let levelIndex = 0; levelIndex < lastLevel; levelIndex += 1) {\n const current = nodes[levelIndex]!\n const parents = nodes[levelIndex + 1]!\n // Сколько узлов этого уровня приходится на одного родителя.\n const perParent = Math.ceil(current.length / parents.length)\n\n current.forEach((node, index) => {\n const parent = parents[Math.min(Math.floor(index / perParent), parents.length - 1)]!\n node.parentId = parent.id\n parent.children.push(node)\n parent.hasChildren = true\n })\n }\n\n return nodes[lastLevel]!\n}\n\nfunction resolveId<T>(\n item: T,\n level: number,\n index: number,\n getId: FromLevelsOptions<T>['getId'],\n): string {\n const raw = getId ? getId(item, level, index) : (item as { id?: string | number })?.id\n return raw == null || raw === '' ? `${level}.${index}` : String(raw)\n}\n","import type { TreeNode } from './types'\n\n/**\n * Возвращает КОПИЮ дерева, где у свёрнутых узлов дети убраны из раскладки.\n *\n * Важно: исходные данные не изменяются. Старая версия библиотеки писала\n * `node.children = null` прямо в объект пользователя — из-за этого ломался\n * реактивный пересчёт и «терялись» ветки. Здесь такого нет.\n */\nexport function applyCollapsed<T>(roots: TreeNode<T>[], collapsedIds: ReadonlySet<string>): TreeNode<T>[] {\n if (collapsedIds.size === 0) return roots\n\n function walk(node: TreeNode<T>): TreeNode<T> {\n const collapsed = collapsedIds.has(node.id)\n return {\n ...node,\n collapsed,\n children: collapsed ? [] : node.children.map(walk),\n }\n }\n\n return roots.map(walk)\n}\n\n/** Возвращает новое множество свёрнутых узлов: был свёрнут — развернём, и наоборот. */\nexport function toggleCollapsed(collapsedIds: ReadonlySet<string>, id: string): Set<string> {\n const next = new Set(collapsedIds)\n if (!next.delete(id)) next.add(id)\n return next\n}\n","/**\n * Карточка матча турнирной сетки.\n *\n * Здесь — только та часть, которая не зависит от Vue: из матча получается\n * список строк, которые нужно нарисовать. Компоненты `BracketCard` для Vue 2\n * и Vue 3 берут этот список и превращают его в разметку, поэтому выглядят\n * и ведут себя одинаково.\n */\n\n/** Команда в карточке матча. Все поля, кроме имени, необязательны. */\nexport interface BracketCardTeam {\n id?: string | number\n name: string\n /** Счёт: число выигранных карт, очки — что угодно. */\n score?: number | string\n isWinner?: boolean\n /** Техническое поражение: вместо счёта показывается «ТП». */\n isTechDefeat?: boolean\n /** Команда пользователя — подсвечивается отдельно. */\n isMyTeam?: boolean\n}\n\n/** Матч глазами карточки: только команды, ничего больше ей не нужно. */\nexport interface BracketCardMatch {\n teams: BracketCardTeam[]\n}\n\n/** Одна строка карточки. `team === null` — пустое место под несыгранный матч. */\nexport interface BracketCardRow {\n /** Ключ для `v-for`. */\n key: string\n team: BracketCardTeam | null\n /** Готовый текст ячейки счёта. */\n score: string\n isWinner: boolean\n isLoser: boolean\n isMyTeam: boolean\n isTechDefeat: boolean\n isEmpty: boolean\n}\n\nexport interface BracketCardOptions {\n /**\n * Сколько строк в карточке. По умолчанию две — обычный матч.\n * У финала команда-победитель одна, поэтому там передают `rows: 1`.\n */\n rows?: number\n /** Что писать в ячейке счёта при техническом поражении. */\n techDefeatLabel?: string\n}\n\n/** Что рисовать: строки и признак «в матче есть моя команда». */\nexport interface BracketCardModel {\n rows: BracketCardRow[]\n hasMyTeam: boolean\n}\n\n/** Обычный матч — две команды. */\nexport const DEFAULT_BRACKET_ROWS = 2\n\n/** Высота одной строки карточки в пикселях. */\nexport const DEFAULT_BRACKET_ROW_HEIGHT = 40\n\n/**\n * Превращает матч в список строк карточки.\n *\n * Команд может быть меньше, чем строк (матч ещё не сыгран) — недостающие\n * добавляются пустыми, чтобы карточка не «худела» и сетка не разъезжалась.\n */\nexport function buildBracketCard(\n match: BracketCardMatch | null | undefined,\n options: BracketCardOptions = {},\n): BracketCardModel {\n const teams = match?.teams ?? []\n const rowCount = Math.max(options.rows ?? DEFAULT_BRACKET_ROWS, teams.length)\n const techDefeatLabel = options.techDefeatLabel ?? 'ТП'\n\n const rows: BracketCardRow[] = teams.map((team, index) => {\n const isTechDefeat = team.isTechDefeat === true\n const isWinner = team.isWinner === true\n\n return {\n key: String(team.id ?? `team-${index}`),\n team,\n score: isTechDefeat ? techDefeatLabel : String(team.score ?? ''),\n isWinner,\n isLoser: !isWinner,\n isMyTeam: team.isMyTeam === true,\n isTechDefeat,\n isEmpty: false,\n }\n })\n\n for (let index = teams.length; index < rowCount; index += 1) {\n rows.push({\n key: `empty-${index}`,\n team: null,\n score: '',\n isWinner: false,\n isLoser: false,\n isMyTeam: false,\n isTechDefeat: false,\n isEmpty: true,\n })\n }\n\n return { rows, hasMyTeam: teams.some((team) => team.isMyTeam === true) }\n}\n\n/**\n * Высота карточки: строк × высота строки.\n *\n * Пригодится для `nodeHeight`, если у разных матчей разное число строк:\n *\n * ```ts\n * nodeHeight: (node) => bracketCardHeight(node.data, { rows: isFinal(node.data) ? 1 : 2 })\n * ```\n */\nexport function bracketCardHeight(\n match: BracketCardMatch | null | undefined,\n options: BracketCardOptions & { rowHeight?: number } = {},\n): number {\n const teams = match?.teams ?? []\n const rowCount = Math.max(options.rows ?? DEFAULT_BRACKET_ROWS, teams.length)\n\n return rowCount * (options.rowHeight ?? DEFAULT_BRACKET_ROW_HEIGHT)\n}\n","import type { Point, Size, Transform } from './types'\n\n/** Ограничения зума. */\nexport interface ScaleLimits {\n min: number\n max: number\n}\n\nexport const DEFAULT_SCALE_LIMITS: ScaleLimits = { min: 0.2, max: 3 }\n\n/** Начальное состояние холста: ничего не сдвинуто, масштаб 1. */\nexport const IDENTITY_TRANSFORM: Transform = { x: 0, y: 0, scale: 1 }\n\n/** Зажимает число в диапазоне [min, max]. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * Подбирает масштаб и сдвиг так, чтобы всё дерево влезло в видимую область\n * и оказалось по центру. `padding` — отступ от краёв в пикселях.\n */\nexport function fitToViewport(\n content: Size,\n viewport: Size,\n padding = 24,\n limits: ScaleLimits = DEFAULT_SCALE_LIMITS,\n): Transform {\n if (content.width <= 0 || content.height <= 0 || viewport.width <= 0 || viewport.height <= 0) {\n return { ...IDENTITY_TRANSFORM }\n }\n\n const availableWidth = Math.max(viewport.width - padding * 2, 1)\n const availableHeight = Math.max(viewport.height - padding * 2, 1)\n // Берём меньший из двух коэффициентов — иначе дерево вылезет за край.\n const raw = Math.min(availableWidth / content.width, availableHeight / content.height, 1)\n const scale = clamp(raw, limits.min, limits.max)\n\n return {\n scale,\n x: (viewport.width - content.width * scale) / 2,\n y: (viewport.height - content.height * scale) / 2,\n }\n}\n\n/** Сдвигает холст на `dx`/`dy` пикселей экрана. */\nexport function panBy(transform: Transform, dx: number, dy: number): Transform {\n return { ...transform, x: transform.x + dx, y: transform.y + dy }\n}\n\n/**\n * Меняет масштаб так, чтобы точка `point` (координаты экрана) осталась\n * под курсором — это и есть «зум к курсору».\n */\nexport function zoomAtPoint(\n transform: Transform,\n factor: number,\n point: Point,\n limits: ScaleLimits = DEFAULT_SCALE_LIMITS,\n): Transform {\n const scale = clamp(transform.scale * factor, limits.min, limits.max)\n if (scale === transform.scale) return transform\n\n // Точка в координатах содержимого — она не должна поменяться.\n const contentX = (point.x - transform.x) / transform.scale\n const contentY = (point.y - transform.y) / transform.scale\n\n return { scale, x: point.x - contentX * scale, y: point.y - contentY * scale }\n}\n\n/** Готовая CSS-строка для `transform` холста. */\nexport function toCssTransform(transform: Transform): string {\n return `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`\n}\n","import { clamp } from './viewport'\n\n/**\n * Полоса раундов над турнирной сеткой.\n *\n * Как и у карточки матча, здесь только та часть, которая не зависит от Vue:\n * из списка раундов получаются колонки с готовой шириной. Компоненты\n * `BracketRounds` для Vue 2 и Vue 3 берут этот список и рисуют разметку.\n *\n * Зачем считать ширину, а не задавать её руками: колонка раунда шире карточки\n * ровно на промежуток между уровнями (`levelGap`), поэтому карточки стоят не по\n * центру своих раундов, а прижимаются к левому краю. Лечится половиной\n * промежутка слева от сетки — это и есть `offset`.\n */\n\n/** Раунд глазами полосы: нужны только название и идентификатор. */\nexport interface BracketRound {\n id?: string | number\n name: string\n}\n\nexport interface BracketRoundsOptions {\n /** Ширина карточки матча — то же число, что в `nodeWidth` у сетки. */\n nodeWidth: number\n /** Промежуток между раундами — то же число, что в `levelGap` у сетки. */\n levelGap: number\n}\n\n/** Одна колонка полосы. */\nexport interface BracketRoundColumn {\n /** Ключ для `v-for`. */\n key: string\n /** Идентификатор раунда; если в данных его нет — порядковый номер. */\n id: string | number\n name: string\n index: number\n /** Ширина колонки в пикселях: карточка + промежуток между раундами. */\n width: number\n}\n\n/** Что рисовать: колонки, общая ширина полосы и отступ сетки под ней. */\nexport interface BracketRoundsModel {\n columns: BracketRoundColumn[]\n /** Ширина всей полосы в пикселях. */\n width: number\n /** Отступ сетки слева, после которого матчи встают по центру своих раундов. */\n offset: number\n}\n\n/**\n * Превращает список раундов в список колонок.\n *\n * Ширина у всех колонок одинаковая, потому что уровни сетки расставлены\n * с одинаковым шагом: `nodeWidth + levelGap`.\n */\nexport function buildBracketRounds(\n rounds: BracketRound[] | null | undefined,\n options: BracketRoundsOptions,\n): BracketRoundsModel {\n const width = options.nodeWidth + options.levelGap\n\n const columns: BracketRoundColumn[] = (rounds ?? []).map((round, index) => {\n const id = round.id ?? index\n\n return { key: String(id), id, name: round.name, index, width }\n })\n\n return { columns, width: width * columns.length, offset: options.levelGap / 2 }\n}\n\n/** Прокручиваемый блок глазами полосы: где колонка и сколько видно. */\nexport interface RoundScrollBox {\n /** Левый край колонки раунда внутри прокручиваемого блока. */\n left: number\n /** Ширина колонки. */\n width: number\n /** Ширина видимой части блока. */\n viewport: number\n /** Вся ширина содержимого блока. */\n scrollWidth: number\n}\n\n/**\n * Куда прокрутить блок, чтобы раунд оказался по центру видимой части.\n *\n * У крайних раундов центр недостижим — прокрутка упирается в край,\n * поэтому результат обрезается по границам блока.\n */\nexport function roundScrollLeft(box: RoundScrollBox): number {\n const centered = box.left + box.width / 2 - box.viewport / 2\n\n return clamp(centered, 0, Math.max(0, box.scrollWidth - box.viewport))\n}\n\n/**\n * Промежуток между раундами, при котором один раунд занимает всю ширину блока.\n *\n * На узком экране из сетки получается «свайпер»: колонка раунда становится\n * шириной с экран, карточка встаёт по его центру (за это отвечает `offset`),\n * а линии между матчами остаются на месте — сетка по-прежнему одна.\n */\nexport function levelGapForWidth(width: number, nodeWidth: number): number {\n return Math.max(0, width - nodeWidth)\n}\n\n/**\n * Раунд, к которому относится прокрутка.\n *\n * Считаем «вверх»: стоит полосе сдвинуться с раунда, как текущим становится\n * следующий. Предыдущий раунд уходит сразу, в начале движения, а не на полпути —\n * иначе он половину свайпа висит на экране поверх приезжающего.\n */\nexport function roundAtScroll(scrollLeft: number, columnWidth: number, count: number): number {\n if (columnWidth <= 0 || count <= 0) return 0\n\n // Мелкая поправка: прокрутка бывает дробной, и ровно на границе\n // без неё мы бы уже перескакивали на следующий раунд.\n const passed = Math.ceil(scrollLeft / columnWidth - 0.001)\n\n return clamp(passed, 0, count - 1)\n}\n\n/**\n * Номер раунда по глубине уровня.\n *\n * У турнирной сетки дерево растёт справа налево: корень — финал, поэтому\n * первый раунд оказывается самым глубоким. При обычном направлении номер\n * раунда совпадает с глубиной.\n */\nexport function roundOfDepth(depth: number, count: number, mirrored: boolean): number {\n return mirrored ? count - 1 - depth : depth\n}\n\n/** Дальше четырёх матчей отступ не растёт: экран не резиновый. */\nexport const MAX_GAP_STEPS = 4\n\n/**\n * Отступ между матчами одного раунда в свайпере.\n *\n * Чем больше матчей, тем крупнее отступ: в первом раунде их восемь и с\n * маленьким отступом они сливаются в одну стену, а в полуфинале матча два —\n * там разводить нечего, и лишнее место только гонит вниз по экрану.\n */\nexport function roundSiblingGap(count: number, base: number): number {\n return base * clamp(count, 1, MAX_GAP_STEPS)\n}\n\n/**\n * На каком расстоянии от карточки сходятся линии пары в свайпере.\n *\n * Промежуток между раундами там во весь экран, и колено «ступеньки» посередине\n * уезжало бы далеко вправо: вместо скобки на пару у каждого матча получался бы\n * свой длинный хвост.\n */\nexport const SWIPE_ELBOW_OFFSET = 20\n","/**\n * Плавная прокрутка к раунду.\n *\n * Полоса раундов не знает, кто её прокручивает: на странице турнира это блок\n * с `overflow-x: auto` вокруг сетки. Поэтому нужный блок ищется по дереву вверх.\n *\n * Двигаем только по горизонтали. Вертикаль не трогаем специально: страница\n * должна остаться там, где её оставил пользователь, — иначе сетка «прыгает»\n * под руками просто от нажатия на раунд.\n */\n\n/**\n * Ближайший предок, который реально прокручивается по горизонтали.\n * Не нашли — значит прокручивать нечего.\n */\nexport function findScrollParent(element: HTMLElement | null | undefined): HTMLElement | null {\n if (typeof window === 'undefined') return null\n\n let current = element?.parentElement ?? null\n\n while (current) {\n const { overflowX } = window.getComputedStyle(current)\n const scrolls = overflowX === 'auto' || overflowX === 'scroll'\n\n if (scrolls && current.scrollWidth > current.clientWidth) return current\n\n current = current.parentElement\n }\n\n return null\n}\n\n/**\n * Прокручивает блок по горизонтали.\n * Плавно, а где `scrollTo` не поддержан — сразу.\n */\nexport function smoothScrollLeft(element: HTMLElement, left: number): void {\n if (typeof element.scrollTo === 'function') {\n element.scrollTo({ left, behavior: 'smooth' })\n return\n }\n\n element.scrollLeft = left\n}\n\n/**\n * Насколько левый край элемента сдвинут относительно начала содержимого\n * прокручиваемого блока. С этой точки и отсчитываются колонки раундов.\n */\nexport function offsetWithin(element: HTMLElement, container: HTMLElement): number {\n return (\n element.getBoundingClientRect().left -\n container.getBoundingClientRect().left +\n container.scrollLeft\n )\n}\n\n/** Класс карточки в сетке — по нему полоса находит матчи раунда. */\nexport const TREE_NODE_SELECTOR = '.tree-view__node'\n\n/**\n * Карточки сетки, попавшие в колонку раунда.\n *\n * Колонки одинаковой ширины и идут подряд, поэтому номер раунда получается\n * делением: центр карточки делим на ширину колонки. Пусто — значит сетка\n * эти карточки не нарисовала (виртуализация) или рисует их по-своему.\n */\nexport function nodesInColumn(\n content: HTMLElement,\n index: number,\n width: number,\n offset: number,\n): HTMLElement[] {\n // Сетка начинается после отступа, центрующего матчи внутри раундов.\n const origin = content.getBoundingClientRect().left + offset\n const nodes = Array.from(content.querySelectorAll<HTMLElement>(TREE_NODE_SELECTOR))\n\n return nodes.filter((node) => {\n const rect = node.getBoundingClientRect()\n\n return Math.floor((rect.left + rect.width / 2 - origin) / width) === index\n })\n}\n\n/** Горизонтальное место элемента внутри прокручиваемого блока. */\nexport function horizontalBox(\n element: HTMLElement,\n container: HTMLElement,\n): { left: number; width: number } {\n return { left: offsetWithin(element, container), width: element.getBoundingClientRect().width }\n}\n\n/** Класс, которым помечается скрытое: карточка или линия пройденного раунда. */\nexport const HIDDEN_CLASS = 'tv-hidden'\n\n/**\n * Прячет всё, что относится к раундам левее текущего: и карточки, и линии,\n * которые из них выходят. Работает по атрибуту `data-depth`, который сетка\n * ставит на карточки и линии.\n *\n * Прячем видимостью, а не удалением: раскладка остаётся на месте, поэтому\n * прокрутка и точки прилипания не съезжают под пальцем.\n */\nexport function hideRoundsBefore(\n content: HTMLElement,\n index: number,\n roundOf: (depth: number) => number,\n): void {\n content.querySelectorAll<HTMLElement>('[data-depth]').forEach((element) => {\n const depth = Number(element.getAttribute('data-depth'))\n\n element.classList.toggle(HIDDEN_CLASS, roundOf(depth) < index)\n })\n}\n","/**\n * Подписки на размер экрана и блока — чтобы компоненты не писали работу с\n * `matchMedia` и `ResizeObserver` дважды и одинаково вели себя на сервере.\n */\n\n/** Узкий экран: ниже этой ширины раунды показываются табами. */\nexport const MOBILE_MEDIA_QUERY = '(max-width: 768px)'\n\n/**\n * Следит за медиавыражением и сразу сообщает текущее значение.\n * Возвращает функцию отписки.\n *\n * На сервере (SSR) ничего не делает: там нет `window`, а после гидрации\n * подписка появится вместе с компонентом.\n */\nexport function watchMedia(query: string, onChange: (matches: boolean) => void): () => void {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => {}\n\n const media = window.matchMedia(query)\n const listener = () => onChange(media.matches)\n\n listener()\n\n // addEventListener появился не сразу: в старых Safari есть только addListener.\n if (typeof media.addEventListener === 'function') {\n media.addEventListener('change', listener)\n return () => media.removeEventListener('change', listener)\n }\n\n media.addListener(listener)\n return () => media.removeListener(listener)\n}\n\n/**\n * Следит за шириной блока и сразу сообщает текущую.\n * Возвращает функцию отписки. На сервере ничего не делает.\n */\nexport function watchWidth(\n element: HTMLElement | null | undefined,\n onChange: (width: number) => void,\n): () => void {\n if (!element) return () => {}\n\n onChange(element.clientWidth)\n\n // Без ResizeObserver останется первая ширина — это лучше, чем ничего.\n if (typeof ResizeObserver === 'undefined') return () => {}\n\n const observer = new ResizeObserver(() => onChange(element.clientWidth))\n observer.observe(element)\n\n return () => observer.disconnect()\n}\n\n/**\n * Следит за тем, как меняется содержимое блока: сетка перерисовывает карточки\n * при новых данных и при виртуализации, и скрытое приходится расставлять заново.\n *\n * Смотрим только на состав детей — на изменения классов не реагируем,\n * иначе собственная правка классов запускала бы бесконечный круг.\n */\nexport function watchChildren(\n element: HTMLElement | null | undefined,\n onChange: () => void,\n): () => void {\n if (!element || typeof MutationObserver === 'undefined') return () => {}\n\n const observer = new MutationObserver(onChange)\n observer.observe(element, { childList: true, subtree: true })\n\n return () => observer.disconnect()\n}\n","import type { Direction, LayoutLink, LayoutNode, LinkStyle, Point, TreeViewOptions } from './types'\n\n/** Округление до сотых: убирает «хвосты» вроде 12.000000001 в атрибуте `d`. */\nfunction round(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/** Вертикальные направления рисуются сверху вниз, горизонтальные — слева направо. */\nfunction isVertical(direction: Direction): boolean {\n return direction === 'top-to-bottom' || direction === 'bottom-to-top'\n}\n\n/**\n * Точки, из которой линия выходит, и в которую приходит.\n * Всегда середина стороны карточки, обращённой к собеседнику.\n */\nexport function getAnchors<T>(\n source: LayoutNode<T>,\n target: LayoutNode<T>,\n direction: Direction,\n): { from: Point; to: Point } {\n const sourceCenterX = source.x + source.width / 2\n const sourceCenterY = source.y + source.height / 2\n const targetCenterX = target.x + target.width / 2\n const targetCenterY = target.y + target.height / 2\n\n switch (direction) {\n case 'top-to-bottom':\n return { from: { x: sourceCenterX, y: source.y + source.height }, to: { x: targetCenterX, y: target.y } }\n case 'bottom-to-top':\n return { from: { x: sourceCenterX, y: source.y }, to: { x: targetCenterX, y: target.y + target.height } }\n case 'left-to-right':\n return { from: { x: source.x + source.width, y: sourceCenterY }, to: { x: target.x, y: targetCenterY } }\n case 'right-to-left':\n return { from: { x: source.x, y: sourceCenterY }, to: { x: target.x + target.width, y: targetCenterY } }\n }\n}\n\n/** Что ещё нужно знать линии, кроме своих концов. */\nexport interface PathOptions {\n /**\n * На каком расстоянии от ребёнка проходит колено. Тогда линии двух детей\n * сходятся сразу за их карточками и читаются как скобка на пару, а не как\n * два длинных хвоста. Не задано — колено ровно посередине.\n */\n elbowOffset?: number\n /**\n * Где сходятся дети одного родителя — координата поперёк уровня.\n * Нужна стилю `'bracket'`; без неё он ведёт себя как `'elbow'`.\n */\n groupMid?: number\n}\n\n/**\n * Строит атрибут `d` для `<path>`: прямая, «ступенька», скобка или кривая Безье.\n */\nexport function buildPath(\n from: Point,\n to: Point,\n style: LinkStyle,\n direction: Direction,\n options: PathOptions = {},\n): string {\n const { elbowOffset, groupMid } = options\n const x1 = round(from.x)\n const y1 = round(from.y)\n const x2 = round(to.x)\n const y2 = round(to.y)\n\n if (style === 'straight') {\n return `M ${x1} ${y1} L ${x2} ${y2}`\n }\n\n const vertical = isVertical(direction)\n // Середина между узлами — по той оси, вдоль которой растёт дерево.\n const midX = round((x1 + x2) / 2)\n const midY = round((y1 + y2) / 2)\n\n // Колено: посередине или на заданном расстоянии от ребёнка,\n // но не дальше самого родителя.\n const cornerOf = (child: number, parent: number, middle: number) => {\n if (elbowOffset == null) return middle\n const span = Math.abs(parent - child)\n return round(child + Math.sign(parent - child) * Math.min(elbowOffset, span))\n }\n\n if (style === 'bracket') {\n // Дети сходятся в одну вертикаль на `mid`, и уже от неё линия идёт\n // к родителю — вдоль его края, если он стоит не по центру детей.\n if (vertical) {\n const corner = cornerOf(y2, y1, midY)\n const mid = round(groupMid ?? x1)\n return `M ${x1} ${y1} L ${mid} ${y1} L ${mid} ${corner} L ${x2} ${corner} L ${x2} ${y2}`\n }\n\n const corner = cornerOf(x2, x1, midX)\n const mid = round(groupMid ?? y1)\n return `M ${x1} ${y1} L ${x1} ${mid} L ${corner} ${mid} L ${corner} ${y2} L ${x2} ${y2}`\n }\n\n if (style === 'elbow') {\n if (vertical) {\n const cornerY = cornerOf(y2, y1, midY)\n return `M ${x1} ${y1} L ${x1} ${cornerY} L ${x2} ${cornerY} L ${x2} ${y2}`\n }\n\n const cornerX = cornerOf(x2, x1, midX)\n return `M ${x1} ${y1} L ${cornerX} ${y1} L ${cornerX} ${y2} L ${x2} ${y2}`\n }\n\n return vertical\n ? `M ${x1} ${y1} C ${x1} ${midY} ${x2} ${midY} ${x2} ${y2}`\n : `M ${x1} ${y1} C ${midX} ${y1} ${midX} ${y2} ${x2} ${y2}`\n}\n\n/**\n * Середина группы детей поперёк уровня: там сходится «скобка».\n * Считается по крайним детям, как и положение родителя в обычной раскладке.\n */\nfunction groupMiddles<T>(nodes: LayoutNode<T>[], vertical: boolean): Map<string, number> {\n const spans = new Map<string, { min: number; max: number }>()\n\n for (const node of nodes) {\n if (node.parentId == null) continue\n\n const center = vertical ? node.x + node.width / 2 : node.y + node.height / 2\n const span = spans.get(node.parentId)\n\n if (!span) spans.set(node.parentId, { min: center, max: center })\n else {\n span.min = Math.min(span.min, center)\n span.max = Math.max(span.max, center)\n }\n }\n\n return new Map([...spans].map(([id, span]) => [id, (span.min + span.max) / 2]))\n}\n\n/** Для каждого узла с родителем создаёт линию «родитель → ребёнок». */\nexport function buildLinks<T>(nodes: LayoutNode<T>[], options: TreeViewOptions): LayoutLink<T>[] {\n const byId = new Map(nodes.map((node) => [node.id, node]))\n const links: LayoutLink<T>[] = []\n const middles =\n options.linkStyle === 'bracket'\n ? groupMiddles(nodes, isVertical(options.direction))\n : undefined\n\n for (const node of nodes) {\n const parent = node.parentId == null ? undefined : byId.get(node.parentId)\n if (!parent) continue\n\n const { from, to } = getAnchors(parent, node, options.direction)\n links.push({\n id: `${parent.id}->${node.id}`,\n source: parent,\n target: node,\n path: buildPath(from, to, options.linkStyle, options.direction, {\n elbowOffset: options.elbowOffset,\n groupMid: middles?.get(parent.id),\n }),\n })\n }\n\n return links\n}\n","import { resolveOptions } from './defaults'\nimport { buildLinks } from './links'\nimport type { GapSize, Layout, LayoutNode, NodeSize, TreeNode, TreeViewOptions } from './types'\n\n/**\n * Раскладка работает в двух «своих» осях, чтобы не писать четыре варианта кода:\n * - along — ось роста дерева (глубина): уровень 0, уровень 1, ...\n * - across — ось поперёк уровня: соседи стоят здесь друг за другом.\n * В самом конце пара (along, across) превращается в привычные (x, y).\n */\n\n/** Место узла на плоскости до того, как оси превратились в x/y. */\nexport interface Placement {\n depth: number\n /** Отступ от начала своего уровня. Заполняется во втором проходе. */\n along: number\n /** Отступ поперёк уровня. */\n across: number\n /** Размер карточки вдоль оси роста. */\n alongSize: number\n /** Размер карточки поперёк оси роста. */\n acrossSize: number\n}\n\nexport interface Placements {\n byId: Map<string, Placement>\n /** Размер содержимого вдоль оси роста. */\n alongExtent: number\n /** Размер содержимого поперёк оси роста. */\n acrossExtent: number\n}\n\n/** Размер конкретного узла: число берём как есть, у функции — спрашиваем. */\nfunction sizeOf<T>(size: NodeSize<T>, node: TreeNode<T>): number {\n return typeof size === 'function' ? size(node) : size\n}\n\n/** Отступ после конкретного узла. Функции сообщаем, сколько народу на уровне. */\nfunction gapOf<T>(gap: GapSize<T>, node: TreeNode<T>, levelCount: number): number {\n return typeof gap === 'function' ? gap(node, levelCount) : gap\n}\n\n/** Сколько узлов на каждом уровне — это нужно `siblingGap`-функции. */\nfunction countByDepth<T>(roots: TreeNode<T>[]): number[] {\n const counts: number[] = []\n\n for (const node of flatten(roots)) counts[node.depth] = (counts[node.depth] ?? 0) + 1\n\n return counts\n}\n\n/**\n * Второй проход, общий для обеих раскладок: толщина уровней уже известна,\n * остаётся расставить их друг за другом и центровать карточки поменьше.\n */\nfunction placeLevels(\n order: Placement[],\n levelSizes: number[],\n levelGap: number,\n): { alongExtent: number; acrossExtent: number } {\n const levelOffsets: number[] = []\n let offset = 0\n\n for (let depth = 0; depth < levelSizes.length; depth += 1) {\n levelOffsets[depth] = offset\n offset += (levelSizes[depth] ?? 0) + levelGap\n }\n\n let acrossExtent = 0\n\n for (const placement of order) {\n const levelSize = levelSizes[placement.depth] ?? placement.alongSize\n // Карточка меньше своего уровня — стоит по центру полосы, а не по краю.\n placement.along = levelOffsets[placement.depth]! + (levelSize - placement.alongSize) / 2\n acrossExtent = Math.max(acrossExtent, placement.across + placement.acrossSize)\n }\n\n const lastLevel = levelSizes.length - 1\n const alongExtent = lastLevel < 0 ? 0 : levelOffsets[lastLevel]! + levelSizes[lastLevel]!\n\n return { alongExtent, acrossExtent }\n}\n\n/**\n * Раскладка «уровнями»: каждый уровень укладывается сам по себе, узел за узлом.\n * Родитель больше не садится по центру детей — зато высота уровня зависит\n * только от того, сколько на нём узлов и какой у них отступ.\n *\n * Пригождается, когда уровни показывают по одному: в свайпере турнирной сетки\n * раунд из восьми матчей и полуфинал из двух получают каждый свой отступ,\n * а не тот, что достался от соседнего уровня.\n *\n * По умолчанию уровни начинаются с одной черты — так при свайпе матчи каждого\n * раунда оказываются сверху, а не уезжают к середине экрана. `levelAlign:\n * 'center'` центрует их относительно самого населённого уровня.\n */\nfunction stackNodes<T>(roots: TreeNode<T>[], options: TreeViewOptions<T>): Placements {\n const vertical = options.direction === 'top-to-bottom' || options.direction === 'bottom-to-top'\n const byId = new Map<string, Placement>()\n const order: Placement[] = []\n const levelSizes: number[] = []\n const levels: TreeNode<T>[][] = []\n\n for (const node of flatten(roots)) (levels[node.depth] ??= []).push(node)\n\n /** Места по уровням и то, сколько уровень занял поперёк. */\n const byLevel: Placement[][] = []\n const extents: number[] = []\n\n levels.forEach((level, depth) => {\n const placements: Placement[] = []\n let cursor = 0\n\n level.forEach((node, index) => {\n const width = sizeOf(options.nodeWidth, node)\n const height = sizeOf(options.nodeHeight, node)\n const alongSize = vertical ? height : width\n const acrossSize = vertical ? width : height\n\n levelSizes[depth] = Math.max(levelSizes[depth] ?? 0, alongSize)\n\n const placement: Placement = { depth, along: 0, across: cursor, alongSize, acrossSize }\n byId.set(node.id, placement)\n placements.push(placement)\n order.push(placement)\n\n // Следующий узел из другой ветки — между ними промежуток крупнее:\n // так видно, какие узлы сходятся в один на следующем уровне.\n const next = level[index + 1]\n const gap =\n next && next.parentId !== node.parentId\n ? (options.groupGap ?? options.siblingGap)\n : options.siblingGap\n\n cursor += acrossSize + gapOf(gap, node, level.length)\n })\n\n const last = placements[placements.length - 1]\n byLevel[depth] = placements\n extents[depth] = last ? last.across + last.acrossSize : 0\n })\n\n // Второй проход по уровням: короткие сдвигаются к центру самого длинного.\n if (options.levelAlign === 'center') {\n const widest = Math.max(0, ...extents)\n\n byLevel.forEach((placements, depth) => {\n const shift = (widest - (extents[depth] ?? 0)) / 2\n if (shift > 0) for (const placement of placements) placement.across += shift\n })\n }\n\n return { byId, ...placeLevels(order, levelSizes, options.levelGap) }\n}\n\n/**\n * Считает место каждого узла.\n *\n * Правила всего два, и их легко проверить руками:\n * - лист встаёт сразу за предыдущим: «конец соседа + siblingGap»;\n * - родитель встаёт ровно по центру между первым и последним ребёнком.\n *\n * Карточки могут быть разного размера, поэтому позиции считаются в пикселях,\n * а не в номерах колонок: следующий узел всегда начинается там, где кончился\n * предыдущий. Уровень занимает столько, сколько занимает самая крупная\n * карточка на нём, а карточки поменьше центруются внутри этой полосы.\n */\nexport function placeNodes<T>(roots: TreeNode<T>[], options: TreeViewOptions<T>): Placements {\n if (options.levelLayout === 'stack') return stackNodes(roots, options)\n\n const vertical = options.direction === 'top-to-bottom' || options.direction === 'bottom-to-top'\n const counts = countByDepth(roots)\n const byId = new Map<string, Placement>()\n /** Те же места, но по порядку размещения — нужно, чтобы двигать ветку целиком. */\n const order: Placement[] = []\n /** Следующая свободная позиция поперёк уровня. */\n let cursor = 0\n /** Насколько толстый каждый уровень: максимум по его карточкам. */\n const levelSizes: number[] = []\n\n function place(node: TreeNode<T>): Placement {\n const width = sizeOf(options.nodeWidth, node)\n const height = sizeOf(options.nodeHeight, node)\n const alongSize = vertical ? height : width\n const acrossSize = vertical ? width : height\n\n levelSizes[node.depth] = Math.max(levelSizes[node.depth] ?? 0, alongSize)\n\n const branchStart = cursor\n const branchIndex = order.length\n let across = cursor\n\n if (node.children.length > 0) {\n const children = node.children.map(place)\n const first = children[0]!\n const last = children[children.length - 1]!\n const firstCenter = first.across + first.acrossSize / 2\n const lastCenter = last.across + last.acrossSize / 2\n const center = (firstCenter + lastCenter) / 2\n\n across = center - acrossSize / 2\n\n // Карточка родителя шире, чем разлёт его детей, — вся ветка едет вправо,\n // иначе родитель налез бы на соседнюю ветку слева.\n const overflow = branchStart - across\n if (overflow > 0) {\n for (let i = branchIndex; i < order.length; i += 1) order[i]!.across += overflow\n across = branchStart\n }\n }\n\n const placement: Placement = { depth: node.depth, along: 0, across, alongSize, acrossSize }\n byId.set(node.id, placement)\n order.push(placement)\n\n // Соседям — стартовать за самым правым краем ветки, включая саму карточку.\n cursor = Math.max(\n cursor,\n across + acrossSize + gapOf(options.siblingGap, node, counts[node.depth] ?? 1),\n )\n return placement\n }\n\n roots.forEach(place)\n\n return { byId, ...placeLevels(order, levelSizes, options.levelGap) }\n}\n\n/** Разворачивает дерево в плоский массив (порядок — «сверху вниз», как в данных). */\nexport function flatten<T>(roots: TreeNode<T>[]): TreeNode<T>[] {\n const result: TreeNode<T>[] = []\n\n function walk(node: TreeNode<T>): void {\n result.push(node)\n node.children.forEach(walk)\n }\n\n roots.forEach(walk)\n return result\n}\n\n/**\n * Главная функция раскладки: из дерева делает координаты и линии.\n * Чистая — одни и те же данные всегда дают один и тот же результат.\n */\nexport function layoutTree<T>(roots: TreeNode<T>[], options?: Partial<TreeViewOptions<T>>): Layout<T> {\n const resolved = resolveOptions(options)\n\n if (roots.length === 0) {\n return { nodes: [], links: [], width: 0, height: 0 }\n }\n\n const vertical = resolved.direction === 'top-to-bottom' || resolved.direction === 'bottom-to-top'\n const { byId, alongExtent, acrossExtent } = placeNodes(roots, resolved)\n // У «перевёрнутых» направлений уровень 0 должен оказаться в конце холста.\n const mirrored = resolved.direction === 'bottom-to-top' || resolved.direction === 'right-to-left'\n\n const nodes: LayoutNode<T>[] = flatten(roots).map((node) => {\n const { along, across, alongSize, acrossSize } = byId.get(node.id)!\n const alongPosition = mirrored ? alongExtent - alongSize - along : along\n\n return {\n id: node.id,\n data: node.data,\n depth: node.depth,\n parentId: node.parentId,\n x: vertical ? across : alongPosition,\n y: vertical ? alongPosition : across,\n width: vertical ? acrossSize : alongSize,\n height: vertical ? alongSize : acrossSize,\n hasChildren: node.hasChildren,\n collapsed: node.collapsed,\n }\n })\n\n return {\n nodes,\n links: buildLinks(nodes, resolved),\n width: vertical ? acrossExtent : alongExtent,\n height: vertical ? alongExtent : acrossExtent,\n }\n}\n","import {\n DEFAULT_SCALE_LIMITS,\n IDENTITY_TRANSFORM,\n fitToViewport,\n panBy,\n zoomAtPoint,\n type ScaleLimits,\n} from './viewport'\nimport type { Size, Transform } from './types'\n\n/** Во сколько раз меняется масштаб за одно движение колеса или клик по кнопке. */\nexport const ZOOM_STEP = 1.2\n\nexport interface PanZoomParams {\n /** Можно ли двигать холст. По умолчанию да. */\n pannable?: () => boolean\n /** Можно ли менять масштаб. По умолчанию да. */\n zoomable?: () => boolean\n /** Пределы масштаба. */\n limits?: () => ScaleLimits\n /** Вызывается после каждого изменения — здесь фреймворк обновляет свой state. */\n onChange: (transform: Transform) => void\n}\n\n/**\n * Перетаскивание и зум без привязки к фреймворку: и Vue 2, и Vue 3, и что угодно\n * ещё просто создают контроллер и подписываются на `onChange`.\n *\n * Слушаем pointer-события — они одинаково работают мышью и пальцем,\n * поэтому отдельного кода для мобильных не нужно.\n */\nexport class PanZoomController {\n transform: Transform = { ...IDENTITY_TRANSFORM }\n\n private element: HTMLElement | null = null\n /** Активные касания: id указателя → последняя позиция на экране. */\n private readonly pointers = new Map<number, { x: number; y: number }>()\n /** Расстояние между двумя пальцами на прошлом событии — для «щипка». */\n private lastPinchDistance = 0\n\n constructor(private readonly params: PanZoomParams) {}\n\n /** Подписывается на события элемента. Повторный вызов сначала отписывается. */\n attach(element: HTMLElement): void {\n this.detach()\n this.element = element\n\n element.addEventListener('pointerdown', this.onPointerDown)\n element.addEventListener('pointermove', this.onPointerMove)\n element.addEventListener('pointerup', this.onPointerUp)\n element.addEventListener('pointercancel', this.onPointerUp)\n // passive: false — иначе браузер не даст отменить прокрутку страницы.\n element.addEventListener('wheel', this.onWheel, { passive: false })\n }\n\n /** Снимает все слушатели. Обязательно вызывать при размонтировании. */\n detach(): void {\n const element = this.element\n if (!element) return\n\n element.removeEventListener('pointerdown', this.onPointerDown)\n element.removeEventListener('pointermove', this.onPointerMove)\n element.removeEventListener('pointerup', this.onPointerUp)\n element.removeEventListener('pointercancel', this.onPointerUp)\n element.removeEventListener('wheel', this.onWheel)\n\n this.element = null\n this.pointers.clear()\n this.lastPinchDistance = 0\n }\n\n /** Вписывает содержимое в размер элемента и центрирует. */\n fit(content: Size, padding = 24): void {\n const element = this.element\n if (!element) return\n\n this.set(\n fitToViewport(\n content,\n { width: element.clientWidth, height: element.clientHeight },\n padding,\n this.limits,\n ),\n )\n }\n\n zoomIn(): void {\n this.zoomToCenter(ZOOM_STEP)\n }\n\n zoomOut(): void {\n this.zoomToCenter(1 / ZOOM_STEP)\n }\n\n reset(): void {\n this.set({ ...IDENTITY_TRANSFORM })\n }\n\n /** Устанавливает состояние напрямую (например, восстановление после перезагрузки). */\n set(transform: Transform): void {\n this.transform = { ...transform }\n this.params.onChange(this.transform)\n }\n\n private get limits(): ScaleLimits {\n return this.params.limits?.() ?? DEFAULT_SCALE_LIMITS\n }\n\n private isPannable(): boolean {\n return this.params.pannable?.() ?? true\n }\n\n private isZoomable(): boolean {\n return this.params.zoomable?.() ?? true\n }\n\n /** Переводит координаты экрана в координаты внутри элемента. */\n private toLocalPoint(point: { clientX: number; clientY: number }) {\n const rect = this.element?.getBoundingClientRect()\n return { x: point.clientX - (rect?.left ?? 0), y: point.clientY - (rect?.top ?? 0) }\n }\n\n private zoomToCenter(factor: number): void {\n const element = this.element\n const center = { x: (element?.clientWidth ?? 0) / 2, y: (element?.clientHeight ?? 0) / 2 }\n this.set(zoomAtPoint(this.transform, factor, center, this.limits))\n }\n\n // Стрелочные функции — чтобы `this` оставался контроллером и чтобы\n // removeEventListener получил ту же самую ссылку на обработчик.\n\n private readonly onPointerDown = (event: PointerEvent): void => {\n if (!this.isPannable()) return\n // Элементу с атрибутом data-tv-no-pan разрешаем свои жесты (кнопки, инпуты).\n if ((event.target as HTMLElement | null)?.closest?.('[data-tv-no-pan]')) return\n\n this.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })\n this.element?.setPointerCapture?.(event.pointerId)\n }\n\n private readonly onPointerMove = (event: PointerEvent): void => {\n const previous = this.pointers.get(event.pointerId)\n if (!previous) return\n\n this.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })\n\n if (this.pointers.size === 2) {\n this.pinch()\n return\n }\n\n this.set(panBy(this.transform, event.clientX - previous.x, event.clientY - previous.y))\n }\n\n private readonly onPointerUp = (event: PointerEvent): void => {\n this.pointers.delete(event.pointerId)\n if (this.pointers.size < 2) this.lastPinchDistance = 0\n }\n\n private readonly onWheel = (event: WheelEvent): void => {\n if (!this.isZoomable()) return\n event.preventDefault()\n\n const factor = event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP\n this.set(zoomAtPoint(this.transform, factor, this.toLocalPoint(event), this.limits))\n }\n\n /** Два пальца: масштабируем вокруг точки ровно между ними. */\n private pinch(): void {\n if (!this.isZoomable()) return\n\n const [first, second] = [...this.pointers.values()]\n if (!first || !second) return\n\n const distance = Math.hypot(first.x - second.x, first.y - second.y)\n\n if (this.lastPinchDistance > 0 && distance > 0) {\n const middle = this.toLocalPoint({\n clientX: (first.x + second.x) / 2,\n clientY: (first.y + second.y) / 2,\n })\n this.set(zoomAtPoint(this.transform, distance / this.lastPinchDistance, middle, this.limits))\n }\n\n this.lastPinchDistance = distance\n }\n}\n","/**\n * Вставляет стили в документ один раз.\n *\n * Зачем: чтобы в проекте не приходилось писать лишний\n * `import '@bigplay/tree-view/style.css'` — всё работает сразу после установки.\n *\n * На сервере (SSR) функция ничего не делает: там нет `document`,\n * а на клиенте стили появятся при гидрации.\n */\nexport function injectStyles(css: string, id = 'tree-view-styles'): void {\n if (typeof document === 'undefined') return\n if (document.getElementById(id)) return\n\n const style = document.createElement('style')\n style.id = id\n style.textContent = css\n\n document.head.appendChild(style)\n}\n","import type { Layout, LayoutLink, LayoutNode } from './types'\n\n/**\n * Виртуализация: рисуем только то, что видно на экране.\n *\n * Математика раскладки дешёвая — тысяча матчей считается меньше чем за\n * миллисекунду. Дорого стоит DOM: у каждой карточки свои элементы, и на\n * тысяче матчей их около десяти тысяч. Поэтому список узлов и линий\n * фильтруется по видимой области, а при перетаскивании пересобирается\n * только разница.\n */\n\n/** Прямоугольник в координатах раскладки (не экрана). */\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** Запас вокруг видимой области, чтобы при перетаскивании не мигали края. */\nexport const DEFAULT_OVERSCAN = 400\n\n/** Меньше этого числа узлов фильтровать смысла нет — дешевле нарисовать всё. */\nexport const VIRTUALIZE_THRESHOLD = 150\n\n/** Пересекаются ли прямоугольники (касание краями считается пересечением). */\nfunction intersects(a: Rect, b: Rect): boolean {\n return (\n a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y\n )\n}\n\n/** Прямоугольник, в который целиком помещается линия вместе с обоими узлами. */\nfunction linkRect<T>(link: LayoutLink<T>): Rect {\n const { source, target } = link\n const x = Math.min(source.x, target.x)\n const y = Math.min(source.y, target.y)\n\n return {\n x,\n y,\n width: Math.max(source.x + source.width, target.x + target.width) - x,\n height: Math.max(source.y + source.height, target.y + target.height) - y,\n }\n}\n\n/** Расширяет прямоугольник во все стороны. */\nexport function expandRect(rect: Rect, by: number): Rect {\n return { x: rect.x - by, y: rect.y - by, width: rect.width + by * 2, height: rect.height + by * 2 }\n}\n\n/**\n * Оставляет в раскладке только узлы и линии, попадающие в видимую область.\n *\n * Размеры холста (`width`/`height`) не меняются: полоса прокрутки и\n * `fit()` должны видеть дерево целиком, даже если нарисована его часть.\n */\nexport function visibleLayout<T>(\n layout: Layout<T>,\n view: Rect | null,\n overscan: number = DEFAULT_OVERSCAN,\n): Layout<T> {\n if (!view || layout.nodes.length < VIRTUALIZE_THRESHOLD) return layout\n\n // Холста не видно совсем (уехал за экран) — рисовать нечего.\n if (view.width <= 0 || view.height <= 0) {\n return { nodes: [], links: [], width: layout.width, height: layout.height }\n }\n\n const area = expandRect(view, overscan)\n const nodes: LayoutNode<T>[] = []\n const links: LayoutLink<T>[] = []\n\n for (const node of layout.nodes) {\n if (intersects(area, node)) nodes.push(node)\n }\n for (const link of layout.links) {\n if (intersects(area, linkRect(link))) links.push(link)\n }\n\n return { nodes, links, width: layout.width, height: layout.height }\n}\n\n/**\n * Грубая оценка видимой области до первого измерения — размер окна.\n *\n * Нужна, чтобы самый первый кадр не рисовал дерево целиком: измерить холст\n * можно только после того, как он окажется в DOM, а к этому моменту тысяча\n * карточек уже была бы создана и выброшена.\n */\nexport function estimateVisibleRect(): Rect | null {\n if (typeof window === 'undefined') return null\n\n return { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight }\n}\n\n/**\n * Считает видимую часть холста по его же положению на экране.\n *\n * Работает и когда дерево двигают мышью внутри контейнера, и когда оно\n * стоит в обычном скролле страницы: холст сам сдвинут и отмасштабирован,\n * поэтому достаточно пересечь его прямоугольник с окном браузера.\n */\nexport function measureVisibleRect(canvas: Element | null | undefined, scale: number): Rect | null {\n if (!canvas || typeof window === 'undefined' || scale <= 0) return null\n\n const box = canvas.getBoundingClientRect()\n const left = Math.max(box.left, 0)\n const top = Math.max(box.top, 0)\n const right = Math.min(box.right, window.innerWidth)\n const bottom = Math.min(box.bottom, window.innerHeight)\n\n // Холст полностью за пределами экрана — рисовать нечего.\n if (right <= left || bottom <= top) return { x: 0, y: 0, width: 0, height: 0 }\n\n return {\n x: (left - box.left) / scale,\n y: (top - box.top) / scale,\n width: (right - left) / scale,\n height: (bottom - top) / scale,\n }\n}\n"],"names":["DEFAULT_OPTIONS","resolveOptions","options","defaultGetChildren","item","defaultGetId","toTree","input","accessors","items","fromFlatList","fromNested","getId","getChildren","usedIds","build","depth","parentId","path","id","toId","assertUniqueId","children","child","index","nodes","byId","node","roots","rawParentId","parent","setDepths","queue","rawId","fallback","fromLevels","levels","filled","level","lastLevel","levelIndex","resolveId","current","parents","perParent","raw","applyCollapsed","collapsedIds","walk","collapsed","toggleCollapsed","next","DEFAULT_BRACKET_ROWS","DEFAULT_BRACKET_ROW_HEIGHT","buildBracketCard","match","teams","rowCount","techDefeatLabel","rows","team","isTechDefeat","isWinner","bracketCardHeight","DEFAULT_SCALE_LIMITS","IDENTITY_TRANSFORM","clamp","value","min","max","fitToViewport","content","viewport","padding","limits","availableWidth","availableHeight","scale","panBy","transform","dx","dy","zoomAtPoint","factor","point","contentX","contentY","toCssTransform","buildBracketRounds","rounds","width","columns","round","roundScrollLeft","box","centered","levelGapForWidth","nodeWidth","roundAtScroll","scrollLeft","columnWidth","count","passed","roundOfDepth","mirrored","MAX_GAP_STEPS","roundSiblingGap","base","SWIPE_ELBOW_OFFSET","findScrollParent","element","overflowX","smoothScrollLeft","left","offsetWithin","container","TREE_NODE_SELECTOR","nodesInColumn","offset","origin","rect","horizontalBox","HIDDEN_CLASS","hideRoundsBefore","roundOf","MOBILE_MEDIA_QUERY","watchMedia","query","onChange","media","listener","watchWidth","observer","watchChildren","isVertical","direction","getAnchors","source","target","sourceCenterX","sourceCenterY","targetCenterX","targetCenterY","buildPath","from","to","style","elbowOffset","groupMid","x1","y1","x2","y2","vertical","midX","midY","cornerOf","middle","span","corner","mid","cornerY","cornerX","groupMiddles","spans","center","buildLinks","links","middles","sizeOf","size","gapOf","gap","levelCount","countByDepth","counts","flatten","placeLevels","order","levelSizes","levelGap","levelOffsets","acrossExtent","placement","levelSize","stackNodes","byLevel","extents","placements","cursor","height","alongSize","acrossSize","last","widest","shift","placeNodes","place","branchStart","branchIndex","across","first","firstCenter","lastCenter","overflow","i","result","layoutTree","resolved","alongExtent","along","alongPosition","ZOOM_STEP","PanZoomController","params","event","previous","second","distance","injectStyles","css","DEFAULT_OVERSCAN","VIRTUALIZE_THRESHOLD","intersects","a","b","linkRect","link","x","y","expandRect","by","visibleLayout","layout","view","overscan","area","estimateVisibleRect","measureVisibleRect","canvas","top","right","bottom"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/defaults.ts","../src/normalize.ts","../src/fromLevels.ts","../src/collapse.ts","../src/bracket.ts","../src/viewport.ts","../src/rounds.ts","../src/scroll.ts","../src/media.ts","../src/links.ts","../src/layout.ts","../src/panZoom.ts","../src/injectStyles.ts","../src/virtualize.ts"],"sourcesContent":["import type { TreeViewOptions } from './types'\n\n/** Значения по умолчанию. Меняются пропом `options` — частично, по одному полю. */\nexport const DEFAULT_OPTIONS: TreeViewOptions = {\n nodeWidth: 180,\n nodeHeight: 64,\n levelGap: 60,\n siblingGap: 16,\n direction: 'top-to-bottom',\n linkStyle: 'elbow',\n levelLayout: 'tree',\n levelAlign: 'start',\n}\n\n/**\n * Дополняет пользовательские настройки значениями по умолчанию.\n * Благодаря этому можно передать только `{ nodeWidth: 240 }` и ничего не сломать.\n */\nexport function resolveOptions<T>(options?: Partial<TreeViewOptions<T>>): TreeViewOptions<T> {\n return { ...DEFAULT_OPTIONS, ...options }\n}\n","import type { TreeNode } from './types'\n\n/**\n * Как читать чужие данные.\n * Любую из функций можно не передавать — тогда работает поведение по умолчанию.\n */\nexport interface Accessors<T> {\n /** Идентификатор узла. По умолчанию — поле `id`, а если его нет — путь вида `\"0.1.2\"`. */\n getId?: (item: T) => string | number | null | undefined\n /** Дети узла. По умолчанию — поле `children`. */\n getChildren?: (item: T) => T[] | null | undefined\n /**\n * Идентификатор родителя. Если передан — данные считаются\n * ПЛОСКИМ списком (`[{ id, parentId }, ...]`), а не вложенным деревом.\n */\n getParentId?: (item: T) => string | number | null | undefined\n}\n\n/** Читает `item.children`, если пользователь не задал свой аксессор. */\nfunction defaultGetChildren<T>(item: T): T[] | null | undefined {\n return (item as { children?: T[] })?.children\n}\n\n/** Читает `item.id`, если пользователь не задал свой аксессор. */\nfunction defaultGetId<T>(item: T): string | number | null | undefined {\n return (item as { id?: string | number })?.id\n}\n\n/**\n * Превращает входные данные в массив корней дерева.\n *\n * Поддерживает два формата:\n * 1. Вложенный: `{ id: 1, children: [...] }` или массив таких объектов;\n * 2. Плоский: `[{ id: 2, parentId: 1 }, ...]` — включается передачей `getParentId`.\n */\nexport function toTree<T>(input: T | T[] | null | undefined, accessors: Accessors<T> = {}): TreeNode<T>[] {\n if (input == null) return []\n\n const items = Array.isArray(input) ? input : [input]\n if (items.length === 0) return []\n\n return accessors.getParentId\n ? fromFlatList(items, accessors as Accessors<T> & { getParentId: NonNullable<Accessors<T>['getParentId']> })\n : fromNested(items, accessors)\n}\n\n/** Вложенный формат: рекурсивно обходим `children`. */\nfunction fromNested<T>(items: T[], accessors: Accessors<T>): TreeNode<T>[] {\n const getId = accessors.getId ?? defaultGetId\n const getChildren = accessors.getChildren ?? defaultGetChildren\n const usedIds = new Set<string>()\n\n /** `path` — запасной идентификатор: он стабилен, пока не меняется порядок данных. */\n function build(item: T, depth: number, parentId: string | null, path: string): TreeNode<T> {\n const id = toId(getId(item), path)\n assertUniqueId(id, usedIds)\n\n const rawChildren = getChildren(item) ?? []\n const children = rawChildren.map((child, index) => build(child, depth + 1, id, `${path}.${index}`))\n\n return { id, data: item, depth, parentId, children, hasChildren: children.length > 0, collapsed: false }\n }\n\n return items.map((item, index) => build(item, 0, null, String(index)))\n}\n\n/** Плоский формат: собираем детей по `parentId`. */\nfunction fromFlatList<T>(\n items: T[],\n accessors: Accessors<T> & { getParentId: NonNullable<Accessors<T>['getParentId']> },\n): TreeNode<T>[] {\n const getId = accessors.getId ?? defaultGetId\n const usedIds = new Set<string>()\n\n // Шаг 1: заготовки узлов без связей.\n const nodes = items.map((item, index) => {\n const id = toId(getId(item), String(index))\n assertUniqueId(id, usedIds)\n return { id, data: item, depth: 0, parentId: null, children: [], hasChildren: false, collapsed: false } as TreeNode<T>\n })\n\n const byId = new Map(nodes.map((node) => [node.id, node]))\n const roots: TreeNode<T>[] = []\n\n // Шаг 2: каждого ребёнка кладём в массив его родителя.\n nodes.forEach((node, index) => {\n const rawParentId = accessors.getParentId(items[index]!)\n const parent = rawParentId == null ? undefined : byId.get(String(rawParentId))\n\n if (!parent) {\n roots.push(node)\n return\n }\n node.parentId = parent.id\n parent.children.push(node)\n parent.hasChildren = true\n })\n\n // Шаг 3: считаем глубину сверху вниз. Заодно ловим циклы (A → B → A).\n const visited = setDepths(roots)\n if (visited !== nodes.length) {\n throw new Error('[tree-view] В данных есть цикл: часть узлов ссылается друг на друга по кругу.')\n }\n\n return roots\n}\n\n/**\n * Проставляет `depth` обходом в ширину и возвращает число посещённых узлов.\n *\n * Очередь читается указателем, а не `shift()`: тот сдвигает весь массив,\n * и на десятках тысяч узлов обход превращается в квадрат.\n */\nfunction setDepths<T>(roots: TreeNode<T>[]): number {\n const queue = [...roots]\n\n for (let index = 0; index < queue.length; index += 1) {\n const node = queue[index]!\n for (const child of node.children) {\n child.depth = node.depth + 1\n queue.push(child)\n }\n }\n return queue.length\n}\n\n/** Приводит идентификатор к строке, подставляя путь, если его нет. */\nfunction toId(rawId: string | number | null | undefined, fallback: string): string {\n return rawId == null || rawId === '' ? fallback : String(rawId)\n}\n\n/** Одинаковые id ломают отрисовку молча — лучше сразу сказать об этом вслух. */\nfunction assertUniqueId(id: string, usedIds: Set<string>): void {\n if (usedIds.has(id)) {\n throw new Error(`[tree-view] Повторяющийся id узла: \"${id}\". Идентификаторы должны быть уникальными.`)\n }\n usedIds.add(id)\n}\n","import type { TreeNode } from './types'\n\nexport interface FromLevelsOptions<T> {\n /** Идентификатор узла. По умолчанию — поле `id`, иначе «номер уровня.номер в уровне». */\n getId?: (item: T, level: number, index: number) => string | number | null | undefined\n}\n\n/**\n * Собирает дерево из «уровней», когда в данных нет ссылок на родителя,\n * а связь задана только позицией — так устроены bounty-сетки: пары групп\n * одного раунда сходятся в одну группу следующего.\n *\n * Уровни передаются от самого раннего раунда к финалу:\n *\n * ```ts\n * fromLevels([\n * [g1, g2, g3, g4], // первый раунд\n * [g5, g6], // второй: g1+g2 → g5, g3+g4 → g6\n * [g7], // финал: g5+g6 → g7\n * ])\n * ```\n *\n * Если уровни делятся неровно (например 5 групп на 2), лишние узлы\n * достаются последнему родителю — так же, как это выглядит в вёрстке.\n */\nexport function fromLevels<T>(levels: T[][], options: FromLevelsOptions<T> = {}): TreeNode<T>[] {\n const filled = levels.filter((level) => level.length > 0)\n if (filled.length === 0) return []\n\n const lastLevel = filled.length - 1\n\n // Сначала создаём узлы всех уровней, связи проставим следующим шагом.\n const nodes: TreeNode<T>[][] = filled.map((level, levelIndex) =>\n level.map((item, index) => ({\n id: resolveId(item, levelIndex, index, options.getId),\n data: item,\n // Финал — корень, поэтому глубина считается от последнего уровня.\n depth: lastLevel - levelIndex,\n parentId: null,\n children: [],\n hasChildren: false,\n collapsed: false,\n })),\n )\n\n for (let levelIndex = 0; levelIndex < lastLevel; levelIndex += 1) {\n const current = nodes[levelIndex]!\n const parents = nodes[levelIndex + 1]!\n // Сколько узлов этого уровня приходится на одного родителя.\n const perParent = Math.ceil(current.length / parents.length)\n\n current.forEach((node, index) => {\n const parent = parents[Math.min(Math.floor(index / perParent), parents.length - 1)]!\n node.parentId = parent.id\n parent.children.push(node)\n parent.hasChildren = true\n })\n }\n\n return nodes[lastLevel]!\n}\n\nfunction resolveId<T>(\n item: T,\n level: number,\n index: number,\n getId: FromLevelsOptions<T>['getId'],\n): string {\n const raw = getId ? getId(item, level, index) : (item as { id?: string | number })?.id\n return raw == null || raw === '' ? `${level}.${index}` : String(raw)\n}\n","import type { TreeNode } from './types'\n\n/**\n * Возвращает КОПИЮ дерева, где у свёрнутых узлов дети убраны из раскладки.\n *\n * Важно: исходные данные не изменяются. Старая версия библиотеки писала\n * `node.children = null` прямо в объект пользователя — из-за этого ломался\n * реактивный пересчёт и «терялись» ветки. Здесь такого нет.\n */\nexport function applyCollapsed<T>(roots: TreeNode<T>[], collapsedIds: ReadonlySet<string>): TreeNode<T>[] {\n if (collapsedIds.size === 0) return roots\n\n function walk(node: TreeNode<T>): TreeNode<T> {\n const collapsed = collapsedIds.has(node.id)\n return {\n ...node,\n collapsed,\n children: collapsed ? [] : node.children.map(walk),\n }\n }\n\n return roots.map(walk)\n}\n\n/** Возвращает новое множество свёрнутых узлов: был свёрнут — развернём, и наоборот. */\nexport function toggleCollapsed(collapsedIds: ReadonlySet<string>, id: string): Set<string> {\n const next = new Set(collapsedIds)\n if (!next.delete(id)) next.add(id)\n return next\n}\n","/**\n * Карточка матча турнирной сетки.\n *\n * Здесь — только та часть, которая не зависит от Vue: из матча получается\n * список строк, которые нужно нарисовать. Компоненты `BracketCard` для Vue 2\n * и Vue 3 берут этот список и превращают его в разметку, поэтому выглядят\n * и ведут себя одинаково.\n */\n\n/** Команда в карточке матча. Все поля, кроме имени, необязательны. */\nexport interface BracketCardTeam {\n id?: string | number\n name: string\n /** Счёт: число выигранных карт, очки — что угодно. */\n score?: number | string\n isWinner?: boolean\n /** Техническое поражение: вместо счёта показывается «ТП». */\n isTechDefeat?: boolean\n /** Команда пользователя — подсвечивается отдельно. */\n isMyTeam?: boolean\n}\n\n/** Матч глазами карточки: только команды, ничего больше ей не нужно. */\nexport interface BracketCardMatch {\n teams: BracketCardTeam[]\n}\n\n/** Одна строка карточки. `team === null` — пустое место под несыгранный матч. */\nexport interface BracketCardRow {\n /** Ключ для `v-for`. */\n key: string\n team: BracketCardTeam | null\n /** Готовый текст ячейки счёта. */\n score: string\n isWinner: boolean\n isLoser: boolean\n isMyTeam: boolean\n isTechDefeat: boolean\n isEmpty: boolean\n}\n\nexport interface BracketCardOptions {\n /**\n * Сколько строк в карточке. По умолчанию две — обычный матч.\n * У финала команда-победитель одна, поэтому там передают `rows: 1`.\n */\n rows?: number\n /** Что писать в ячейке счёта при техническом поражении. */\n techDefeatLabel?: string\n}\n\n/** Что рисовать: строки и признак «в матче есть моя команда». */\nexport interface BracketCardModel {\n rows: BracketCardRow[]\n hasMyTeam: boolean\n}\n\n/** Обычный матч — две команды. */\nexport const DEFAULT_BRACKET_ROWS = 2\n\n/** Высота одной строки карточки в пикселях. */\nexport const DEFAULT_BRACKET_ROW_HEIGHT = 40\n\n/**\n * Превращает матч в список строк карточки.\n *\n * Команд может быть меньше, чем строк (матч ещё не сыгран) — недостающие\n * добавляются пустыми, чтобы карточка не «худела» и сетка не разъезжалась.\n */\nexport function buildBracketCard(\n match: BracketCardMatch | null | undefined,\n options: BracketCardOptions = {},\n): BracketCardModel {\n const teams = match?.teams ?? []\n const rowCount = Math.max(options.rows ?? DEFAULT_BRACKET_ROWS, teams.length)\n const techDefeatLabel = options.techDefeatLabel ?? 'ТП'\n\n const rows: BracketCardRow[] = teams.map((team, index) => {\n const isTechDefeat = team.isTechDefeat === true\n const isWinner = team.isWinner === true\n\n return {\n key: String(team.id ?? `team-${index}`),\n team,\n score: isTechDefeat ? techDefeatLabel : String(team.score ?? ''),\n isWinner,\n isLoser: !isWinner,\n isMyTeam: team.isMyTeam === true,\n isTechDefeat,\n isEmpty: false,\n }\n })\n\n for (let index = teams.length; index < rowCount; index += 1) {\n rows.push({\n key: `empty-${index}`,\n team: null,\n score: '',\n isWinner: false,\n isLoser: false,\n isMyTeam: false,\n isTechDefeat: false,\n isEmpty: true,\n })\n }\n\n return { rows, hasMyTeam: teams.some((team) => team.isMyTeam === true) }\n}\n\n/**\n * Высота карточки: строк × высота строки.\n *\n * Пригодится для `nodeHeight`, если у разных матчей разное число строк:\n *\n * ```ts\n * nodeHeight: (node) => bracketCardHeight(node.data, { rows: isFinal(node.data) ? 1 : 2 })\n * ```\n */\nexport function bracketCardHeight(\n match: BracketCardMatch | null | undefined,\n options: BracketCardOptions & { rowHeight?: number } = {},\n): number {\n const teams = match?.teams ?? []\n const rowCount = Math.max(options.rows ?? DEFAULT_BRACKET_ROWS, teams.length)\n\n return rowCount * (options.rowHeight ?? DEFAULT_BRACKET_ROW_HEIGHT)\n}\n","import type { Point, Size, Transform } from './types'\n\n/** Ограничения зума. */\nexport interface ScaleLimits {\n min: number\n max: number\n}\n\nexport const DEFAULT_SCALE_LIMITS: ScaleLimits = { min: 0.2, max: 3 }\n\n/** Начальное состояние холста: ничего не сдвинуто, масштаб 1. */\nexport const IDENTITY_TRANSFORM: Transform = { x: 0, y: 0, scale: 1 }\n\n/** Зажимает число в диапазоне [min, max]. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * Подбирает масштаб и сдвиг так, чтобы всё дерево влезло в видимую область\n * и оказалось по центру. `padding` — отступ от краёв в пикселях.\n */\nexport function fitToViewport(\n content: Size,\n viewport: Size,\n padding = 24,\n limits: ScaleLimits = DEFAULT_SCALE_LIMITS,\n): Transform {\n if (content.width <= 0 || content.height <= 0 || viewport.width <= 0 || viewport.height <= 0) {\n return { ...IDENTITY_TRANSFORM }\n }\n\n const availableWidth = Math.max(viewport.width - padding * 2, 1)\n const availableHeight = Math.max(viewport.height - padding * 2, 1)\n // Берём меньший из двух коэффициентов — иначе дерево вылезет за край.\n const raw = Math.min(availableWidth / content.width, availableHeight / content.height, 1)\n const scale = clamp(raw, limits.min, limits.max)\n\n return {\n scale,\n x: (viewport.width - content.width * scale) / 2,\n y: (viewport.height - content.height * scale) / 2,\n }\n}\n\n/** Сдвигает холст на `dx`/`dy` пикселей экрана. */\nexport function panBy(transform: Transform, dx: number, dy: number): Transform {\n return { ...transform, x: transform.x + dx, y: transform.y + dy }\n}\n\n/**\n * Меняет масштаб так, чтобы точка `point` (координаты экрана) осталась\n * под курсором — это и есть «зум к курсору».\n */\nexport function zoomAtPoint(\n transform: Transform,\n factor: number,\n point: Point,\n limits: ScaleLimits = DEFAULT_SCALE_LIMITS,\n): Transform {\n const scale = clamp(transform.scale * factor, limits.min, limits.max)\n if (scale === transform.scale) return transform\n\n // Точка в координатах содержимого — она не должна поменяться.\n const contentX = (point.x - transform.x) / transform.scale\n const contentY = (point.y - transform.y) / transform.scale\n\n return { scale, x: point.x - contentX * scale, y: point.y - contentY * scale }\n}\n\n/** Готовая CSS-строка для `transform` холста. */\nexport function toCssTransform(transform: Transform): string {\n return `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`\n}\n","import { clamp } from './viewport'\nimport type { LayoutNode } from './types'\n\n/**\n * Полоса раундов над турнирной сеткой.\n *\n * Как и у карточки матча, здесь только та часть, которая не зависит от Vue:\n * из списка раундов получаются колонки с готовой шириной. Компоненты\n * `BracketRounds` для Vue 2 и Vue 3 берут этот список и рисуют разметку.\n *\n * Зачем считать ширину, а не задавать её руками: колонка раунда шире карточки\n * ровно на промежуток между уровнями (`levelGap`), поэтому карточки стоят не по\n * центру своих раундов, а прижимаются к левому краю. Лечится половиной\n * промежутка слева от сетки — это и есть `offset`.\n */\n\n/** Раунд глазами полосы: нужны только название и идентификатор. */\nexport interface BracketRound {\n id?: string | number\n name: string\n}\n\nexport interface BracketRoundsOptions {\n /** Ширина карточки матча — то же число, что в `nodeWidth` у сетки. */\n nodeWidth: number\n /** Промежуток между раундами — то же число, что в `levelGap` у сетки. */\n levelGap: number\n}\n\n/** Одна колонка полосы. */\nexport interface BracketRoundColumn {\n /** Ключ для `v-for`. */\n key: string\n /** Идентификатор раунда; если в данных его нет — порядковый номер. */\n id: string | number\n name: string\n index: number\n /** Ширина колонки в пикселях: карточка + промежуток между раундами. */\n width: number\n}\n\n/** Что рисовать: колонки, общая ширина полосы и отступ сетки под ней. */\nexport interface BracketRoundsModel {\n columns: BracketRoundColumn[]\n /** Ширина всей полосы в пикселях. */\n width: number\n /** Отступ сетки слева, после которого матчи встают по центру своих раундов. */\n offset: number\n}\n\n/**\n * Превращает список раундов в список колонок.\n *\n * Ширина у всех колонок одинаковая, потому что уровни сетки расставлены\n * с одинаковым шагом: `nodeWidth + levelGap`.\n */\nexport function buildBracketRounds(\n rounds: BracketRound[] | null | undefined,\n options: BracketRoundsOptions,\n): BracketRoundsModel {\n const width = options.nodeWidth + options.levelGap\n\n const columns: BracketRoundColumn[] = (rounds ?? []).map((round, index) => {\n const id = round.id ?? index\n\n return { key: String(id), id, name: round.name, index, width }\n })\n\n return { columns, width: width * columns.length, offset: options.levelGap / 2 }\n}\n\n/** Прокручиваемый блок глазами полосы: где колонка и сколько видно. */\nexport interface RoundScrollBox {\n /** Левый край колонки раунда внутри прокручиваемого блока. */\n left: number\n /** Ширина колонки. */\n width: number\n /** Ширина видимой части блока. */\n viewport: number\n /** Вся ширина содержимого блока. */\n scrollWidth: number\n}\n\n/**\n * Куда прокрутить блок, чтобы раунд оказался по центру видимой части.\n *\n * У крайних раундов центр недостижим — прокрутка упирается в край,\n * поэтому результат обрезается по границам блока.\n */\nexport function roundScrollLeft(box: RoundScrollBox): number {\n const centered = box.left + box.width / 2 - box.viewport / 2\n\n return clamp(centered, 0, Math.max(0, box.scrollWidth - box.viewport))\n}\n\n/**\n * Промежуток между раундами, при котором один раунд занимает всю ширину блока.\n *\n * На узком экране из сетки получается «свайпер»: колонка раунда становится\n * шириной с экран, карточка встаёт по его центру (за это отвечает `offset`),\n * а линии между матчами остаются на месте — сетка по-прежнему одна.\n */\nexport function levelGapForWidth(width: number, nodeWidth: number): number {\n return Math.max(0, width - nodeWidth)\n}\n\n/**\n * Раунд, к которому относится прокрутка.\n *\n * Считаем «вверх»: стоит полосе сдвинуться с раунда, как текущим становится\n * следующий. Предыдущий раунд уходит сразу, в начале движения, а не на полпути —\n * иначе он половину свайпа висит на экране поверх приезжающего.\n */\nexport function roundAtScroll(scrollLeft: number, columnWidth: number, count: number): number {\n if (columnWidth <= 0 || count <= 0) return 0\n\n // Мелкая поправка: прокрутка бывает дробной, и ровно на границе\n // без неё мы бы уже перескакивали на следующий раунд.\n const passed = Math.ceil(scrollLeft / columnWidth - 0.001)\n\n return clamp(passed, 0, count - 1)\n}\n\n/**\n * Номер раунда по глубине уровня.\n *\n * У турнирной сетки дерево растёт справа налево: корень — финал, поэтому\n * первый раунд оказывается самым глубоким. При обычном направлении номер\n * раунда совпадает с глубиной.\n */\nexport function roundOfDepth(depth: number, count: number, mirrored: boolean): number {\n return mirrored ? count - 1 - depth : depth\n}\n\n/** Дальше четырёх матчей отступ не растёт: экран не резиновый. */\nexport const MAX_GAP_STEPS = 4\n\n/**\n * Отступ между матчами одного раунда в свайпере.\n *\n * Чем больше матчей, тем крупнее отступ: в первом раунде их восемь и с\n * маленьким отступом они сливаются в одну стену, а в полуфинале матча два —\n * там разводить нечего, и лишнее место только гонит вниз по экрану.\n */\nexport function roundSiblingGap(count: number, base: number): number {\n return base * clamp(count, 1, MAX_GAP_STEPS)\n}\n\n/**\n * На каком расстоянии от карточки сходятся линии пары в свайпере.\n *\n * Промежуток между раундами там во весь экран, и колено «ступеньки» посередине\n * уезжало бы далеко вправо: вместо скобки на пару у каждого матча получался бы\n * свой длинный хвост.\n */\nexport const SWIPE_ELBOW_OFFSET = 20\n\n/**\n * Высота содержимого одного раунда — по самой нижней карточке в нём.\n *\n * Нужна свайперу: там виден один раунд, а высота холста считается по всей\n * раскладке, то есть по самому населённому раунду. Из-за этого под финалом\n * с единственной ячейкой оставалась пустота до конца сетки.\n *\n * Считается по оси Y, потому что свайпер включается только для горизонтальных\n * сеток (`right-to-left` и `left-to-right`): в вертикальных раунды идут\n * сверху вниз и прятать их по горизонтали нечего.\n *\n * Раскладка `levelLayout: 'stack'`, на которой работает свайпер, начинает\n * каждый уровень от нуля, поэтому ограничить высоту достаточно — карточки\n * и так стоят сверху, переставлять их не нужно.\n */\nexport function roundContentHeight<T>(\n nodes: ReadonlyArray<LayoutNode<T>>,\n round: number,\n rounds: number,\n mirrored: boolean,\n): number {\n let height = 0\n\n for (const node of nodes) {\n if (roundOfDepth(node.depth, rounds, mirrored) !== round) continue\n height = Math.max(height, node.y + node.height)\n }\n\n return height\n}\n","/**\n * Плавная прокрутка к раунду.\n *\n * Полоса раундов не знает, кто её прокручивает: на странице турнира это блок\n * с `overflow-x: auto` вокруг сетки. Поэтому нужный блок ищется по дереву вверх.\n *\n * Двигаем только по горизонтали. Вертикаль не трогаем специально: страница\n * должна остаться там, где её оставил пользователь, — иначе сетка «прыгает»\n * под руками просто от нажатия на раунд.\n */\n\n/**\n * Ближайший предок, который реально прокручивается по горизонтали.\n * Не нашли — значит прокручивать нечего.\n */\nexport function findScrollParent(element: HTMLElement | null | undefined): HTMLElement | null {\n if (typeof window === 'undefined') return null\n\n let current = element?.parentElement ?? null\n\n while (current) {\n const { overflowX } = window.getComputedStyle(current)\n const scrolls = overflowX === 'auto' || overflowX === 'scroll'\n\n if (scrolls && current.scrollWidth > current.clientWidth) return current\n\n current = current.parentElement\n }\n\n return null\n}\n\n/**\n * Прокручивает блок по горизонтали.\n * Плавно, а где `scrollTo` не поддержан — сразу.\n */\nexport function smoothScrollLeft(element: HTMLElement, left: number): void {\n if (typeof element.scrollTo === 'function') {\n element.scrollTo({ left, behavior: 'smooth' })\n return\n }\n\n element.scrollLeft = left\n}\n\n/**\n * Насколько левый край элемента сдвинут относительно начала содержимого\n * прокручиваемого блока. С этой точки и отсчитываются колонки раундов.\n */\nexport function offsetWithin(element: HTMLElement, container: HTMLElement): number {\n return (\n element.getBoundingClientRect().left -\n container.getBoundingClientRect().left +\n container.scrollLeft\n )\n}\n\n/** Класс карточки в сетке — по нему полоса находит матчи раунда. */\nexport const TREE_NODE_SELECTOR = '.tree-view__node'\n\n/**\n * Карточки сетки, попавшие в колонку раунда.\n *\n * Колонки одинаковой ширины и идут подряд, поэтому номер раунда получается\n * делением: центр карточки делим на ширину колонки. Пусто — значит сетка\n * эти карточки не нарисовала (виртуализация) или рисует их по-своему.\n */\nexport function nodesInColumn(\n content: HTMLElement,\n index: number,\n width: number,\n offset: number,\n): HTMLElement[] {\n // Сетка начинается после отступа, центрующего матчи внутри раундов.\n const origin = content.getBoundingClientRect().left + offset\n const nodes = Array.from(content.querySelectorAll<HTMLElement>(TREE_NODE_SELECTOR))\n\n return nodes.filter((node) => {\n const rect = node.getBoundingClientRect()\n\n return Math.floor((rect.left + rect.width / 2 - origin) / width) === index\n })\n}\n\n/** Горизонтальное место элемента внутри прокручиваемого блока. */\nexport function horizontalBox(\n element: HTMLElement,\n container: HTMLElement,\n): { left: number; width: number } {\n return { left: offsetWithin(element, container), width: element.getBoundingClientRect().width }\n}\n\n/** Класс, которым помечается скрытое: карточка или линия пройденного раунда. */\nexport const HIDDEN_CLASS = 'tv-hidden'\n\n/**\n * Прячет всё, что относится к раундам левее текущего: и карточки, и линии,\n * которые из них выходят. Работает по атрибуту `data-depth`, который сетка\n * ставит на карточки и линии.\n *\n * Прячем видимостью, а не удалением: раскладка остаётся на месте, поэтому\n * прокрутка и точки прилипания не съезжают под пальцем.\n */\nexport function hideRoundsBefore(\n content: HTMLElement,\n index: number,\n roundOf: (depth: number) => number,\n): void {\n content.querySelectorAll<HTMLElement>('[data-depth]').forEach((element) => {\n const depth = Number(element.getAttribute('data-depth'))\n\n element.classList.toggle(HIDDEN_CLASS, roundOf(depth) < index)\n })\n}\n\n/**\n * Вызывает `done`, когда плавная прокрутка блока закончилась.\n *\n * Где есть событие `scrollend` — слушаем его. Где нет (Safari на момент\n * написания), ждём, пока `scrollLeft` перестанет меняться: смотрим кадр за\n * кадром и считаем прокрутку законченной после двух одинаковых значений.\n * Верхняя граница ожидания нужна, чтобы флаг не завис, если прокрутка так\n * и не стартовала — например, ехать уже некуда.\n */\nexport function afterScrollEnd(element: HTMLElement, done: () => void, timeout = 1000): void {\n // Через `in` нельзя: TypeScript сужает тип до never в ветке без свойства.\n const hasScrollEnd = typeof (element as { onscrollend?: unknown }).onscrollend !== 'undefined'\n\n if (hasScrollEnd) {\n const handler = () => {\n element.removeEventListener('scrollend', handler)\n done()\n }\n\n element.addEventListener('scrollend', handler, { once: true })\n setTimeout(handler, timeout)\n return\n }\n\n const started = Date.now()\n let previous = element.scrollLeft\n\n const tick = () => {\n const settled = element.scrollLeft === previous\n previous = element.scrollLeft\n\n if (settled || Date.now() - started > timeout) {\n done()\n return\n }\n\n requestAnimationFrame(tick)\n }\n\n requestAnimationFrame(tick)\n}\n","/**\n * Подписки на размер экрана и блока — чтобы компоненты не писали работу с\n * `matchMedia` и `ResizeObserver` дважды и одинаково вели себя на сервере.\n */\n\n/** Узкий экран: ниже этой ширины раунды показываются табами. */\nexport const MOBILE_MEDIA_QUERY = '(max-width: 768px)'\n\n/**\n * Следит за медиавыражением и сразу сообщает текущее значение.\n * Возвращает функцию отписки.\n *\n * На сервере (SSR) ничего не делает: там нет `window`, а после гидрации\n * подписка появится вместе с компонентом.\n */\nexport function watchMedia(query: string, onChange: (matches: boolean) => void): () => void {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => {}\n\n const media = window.matchMedia(query)\n const listener = () => onChange(media.matches)\n\n listener()\n\n // addEventListener появился не сразу: в старых Safari есть только addListener.\n if (typeof media.addEventListener === 'function') {\n media.addEventListener('change', listener)\n return () => media.removeEventListener('change', listener)\n }\n\n media.addListener(listener)\n return () => media.removeListener(listener)\n}\n\n/**\n * Следит за шириной блока и сразу сообщает текущую.\n * Возвращает функцию отписки. На сервере ничего не делает.\n */\nexport function watchWidth(\n element: HTMLElement | null | undefined,\n onChange: (width: number) => void,\n): () => void {\n if (!element) return () => {}\n\n onChange(element.clientWidth)\n\n // Без ResizeObserver останется первая ширина — это лучше, чем ничего.\n if (typeof ResizeObserver === 'undefined') return () => {}\n\n const observer = new ResizeObserver(() => onChange(element.clientWidth))\n observer.observe(element)\n\n return () => observer.disconnect()\n}\n\n/**\n * Следит за тем, как меняется содержимое блока: сетка перерисовывает карточки\n * при новых данных и при виртуализации, и скрытое приходится расставлять заново.\n *\n * Смотрим только на состав детей — на изменения классов не реагируем,\n * иначе собственная правка классов запускала бы бесконечный круг.\n */\nexport function watchChildren(\n element: HTMLElement | null | undefined,\n onChange: () => void,\n): () => void {\n if (!element || typeof MutationObserver === 'undefined') return () => {}\n\n const observer = new MutationObserver(onChange)\n observer.observe(element, { childList: true, subtree: true })\n\n return () => observer.disconnect()\n}\n","import type { Direction, LayoutLink, LayoutNode, LinkStyle, Point, TreeViewOptions } from './types'\n\n/** Округление до сотых: убирает «хвосты» вроде 12.000000001 в атрибуте `d`. */\nfunction round(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/** Вертикальные направления рисуются сверху вниз, горизонтальные — слева направо. */\nfunction isVertical(direction: Direction): boolean {\n return direction === 'top-to-bottom' || direction === 'bottom-to-top'\n}\n\n/**\n * Точки, из которой линия выходит, и в которую приходит.\n * Всегда середина стороны карточки, обращённой к собеседнику.\n */\nexport function getAnchors<T>(\n source: LayoutNode<T>,\n target: LayoutNode<T>,\n direction: Direction,\n): { from: Point; to: Point } {\n const sourceCenterX = source.x + source.width / 2\n const sourceCenterY = source.y + source.height / 2\n const targetCenterX = target.x + target.width / 2\n const targetCenterY = target.y + target.height / 2\n\n switch (direction) {\n case 'top-to-bottom':\n return { from: { x: sourceCenterX, y: source.y + source.height }, to: { x: targetCenterX, y: target.y } }\n case 'bottom-to-top':\n return { from: { x: sourceCenterX, y: source.y }, to: { x: targetCenterX, y: target.y + target.height } }\n case 'left-to-right':\n return { from: { x: source.x + source.width, y: sourceCenterY }, to: { x: target.x, y: targetCenterY } }\n case 'right-to-left':\n return { from: { x: source.x, y: sourceCenterY }, to: { x: target.x + target.width, y: targetCenterY } }\n }\n}\n\n/** Что ещё нужно знать линии, кроме своих концов. */\nexport interface PathOptions {\n /**\n * На каком расстоянии от ребёнка проходит колено. Тогда линии двух детей\n * сходятся сразу за их карточками и читаются как скобка на пару, а не как\n * два длинных хвоста. Не задано — колено ровно посередине.\n */\n elbowOffset?: number\n /**\n * Где сходятся дети одного родителя — координата поперёк уровня.\n * Нужна стилю `'bracket'`; без неё он ведёт себя как `'elbow'`.\n */\n groupMid?: number\n}\n\n/**\n * Строит атрибут `d` для `<path>`: прямая, «ступенька», скобка или кривая Безье.\n */\nexport function buildPath(\n from: Point,\n to: Point,\n style: LinkStyle,\n direction: Direction,\n options: PathOptions = {},\n): string {\n const { elbowOffset, groupMid } = options\n const x1 = round(from.x)\n const y1 = round(from.y)\n const x2 = round(to.x)\n const y2 = round(to.y)\n\n if (style === 'straight') {\n return `M ${x1} ${y1} L ${x2} ${y2}`\n }\n\n const vertical = isVertical(direction)\n // Середина между узлами — по той оси, вдоль которой растёт дерево.\n const midX = round((x1 + x2) / 2)\n const midY = round((y1 + y2) / 2)\n\n // Колено: посередине или на заданном расстоянии от ребёнка,\n // но не дальше самого родителя.\n const cornerOf = (child: number, parent: number, middle: number) => {\n if (elbowOffset == null) return middle\n const span = Math.abs(parent - child)\n return round(child + Math.sign(parent - child) * Math.min(elbowOffset, span))\n }\n\n if (style === 'bracket') {\n // Дети сходятся в одну вертикаль на `mid`, и уже от неё линия идёт\n // к родителю — вдоль его края, если он стоит не по центру детей.\n if (vertical) {\n const corner = cornerOf(y2, y1, midY)\n const mid = round(groupMid ?? x1)\n return `M ${x1} ${y1} L ${mid} ${y1} L ${mid} ${corner} L ${x2} ${corner} L ${x2} ${y2}`\n }\n\n const corner = cornerOf(x2, x1, midX)\n const mid = round(groupMid ?? y1)\n return `M ${x1} ${y1} L ${x1} ${mid} L ${corner} ${mid} L ${corner} ${y2} L ${x2} ${y2}`\n }\n\n if (style === 'elbow') {\n if (vertical) {\n const cornerY = cornerOf(y2, y1, midY)\n return `M ${x1} ${y1} L ${x1} ${cornerY} L ${x2} ${cornerY} L ${x2} ${y2}`\n }\n\n const cornerX = cornerOf(x2, x1, midX)\n return `M ${x1} ${y1} L ${cornerX} ${y1} L ${cornerX} ${y2} L ${x2} ${y2}`\n }\n\n return vertical\n ? `M ${x1} ${y1} C ${x1} ${midY} ${x2} ${midY} ${x2} ${y2}`\n : `M ${x1} ${y1} C ${midX} ${y1} ${midX} ${y2} ${x2} ${y2}`\n}\n\n/**\n * Середина группы детей поперёк уровня: там сходится «скобка».\n * Считается по крайним детям, как и положение родителя в обычной раскладке.\n */\nfunction groupMiddles<T>(nodes: LayoutNode<T>[], vertical: boolean): Map<string, number> {\n const spans = new Map<string, { min: number; max: number }>()\n\n for (const node of nodes) {\n if (node.parentId == null) continue\n\n const center = vertical ? node.x + node.width / 2 : node.y + node.height / 2\n const span = spans.get(node.parentId)\n\n if (!span) spans.set(node.parentId, { min: center, max: center })\n else {\n span.min = Math.min(span.min, center)\n span.max = Math.max(span.max, center)\n }\n }\n\n return new Map([...spans].map(([id, span]) => [id, (span.min + span.max) / 2]))\n}\n\n/** Для каждого узла с родителем создаёт линию «родитель → ребёнок». */\nexport function buildLinks<T>(nodes: LayoutNode<T>[], options: TreeViewOptions): LayoutLink<T>[] {\n const byId = new Map(nodes.map((node) => [node.id, node]))\n const links: LayoutLink<T>[] = []\n const middles =\n options.linkStyle === 'bracket'\n ? groupMiddles(nodes, isVertical(options.direction))\n : undefined\n\n for (const node of nodes) {\n const parent = node.parentId == null ? undefined : byId.get(node.parentId)\n if (!parent) continue\n\n const { from, to } = getAnchors(parent, node, options.direction)\n links.push({\n id: `${parent.id}->${node.id}`,\n source: parent,\n target: node,\n path: buildPath(from, to, options.linkStyle, options.direction, {\n elbowOffset: options.elbowOffset,\n groupMid: middles?.get(parent.id),\n }),\n })\n }\n\n return links\n}\n","import { resolveOptions } from './defaults'\nimport { buildLinks } from './links'\nimport type { GapSize, Layout, LayoutNode, NodeSize, TreeNode, TreeViewOptions } from './types'\n\n/**\n * Раскладка работает в двух «своих» осях, чтобы не писать четыре варианта кода:\n * - along — ось роста дерева (глубина): уровень 0, уровень 1, ...\n * - across — ось поперёк уровня: соседи стоят здесь друг за другом.\n * В самом конце пара (along, across) превращается в привычные (x, y).\n */\n\n/** Место узла на плоскости до того, как оси превратились в x/y. */\nexport interface Placement {\n depth: number\n /** Отступ от начала своего уровня. Заполняется во втором проходе. */\n along: number\n /** Отступ поперёк уровня. */\n across: number\n /** Размер карточки вдоль оси роста. */\n alongSize: number\n /** Размер карточки поперёк оси роста. */\n acrossSize: number\n}\n\nexport interface Placements {\n byId: Map<string, Placement>\n /** Размер содержимого вдоль оси роста. */\n alongExtent: number\n /** Размер содержимого поперёк оси роста. */\n acrossExtent: number\n}\n\n/** Размер конкретного узла: число берём как есть, у функции — спрашиваем. */\nfunction sizeOf<T>(size: NodeSize<T>, node: TreeNode<T>): number {\n return typeof size === 'function' ? size(node) : size\n}\n\n/** Отступ после конкретного узла. Функции сообщаем, сколько народу на уровне. */\nfunction gapOf<T>(gap: GapSize<T>, node: TreeNode<T>, levelCount: number): number {\n return typeof gap === 'function' ? gap(node, levelCount) : gap\n}\n\n/** Сколько узлов на каждом уровне — это нужно `siblingGap`-функции. */\nfunction countByDepth<T>(roots: TreeNode<T>[]): number[] {\n const counts: number[] = []\n\n for (const node of flatten(roots)) counts[node.depth] = (counts[node.depth] ?? 0) + 1\n\n return counts\n}\n\n/**\n * Второй проход, общий для обеих раскладок: толщина уровней уже известна,\n * остаётся расставить их друг за другом и центровать карточки поменьше.\n */\nfunction placeLevels(\n order: Placement[],\n levelSizes: number[],\n levelGap: number,\n): { alongExtent: number; acrossExtent: number } {\n const levelOffsets: number[] = []\n let offset = 0\n\n for (let depth = 0; depth < levelSizes.length; depth += 1) {\n levelOffsets[depth] = offset\n offset += (levelSizes[depth] ?? 0) + levelGap\n }\n\n let acrossExtent = 0\n\n for (const placement of order) {\n const levelSize = levelSizes[placement.depth] ?? placement.alongSize\n // Карточка меньше своего уровня — стоит по центру полосы, а не по краю.\n placement.along = levelOffsets[placement.depth]! + (levelSize - placement.alongSize) / 2\n acrossExtent = Math.max(acrossExtent, placement.across + placement.acrossSize)\n }\n\n const lastLevel = levelSizes.length - 1\n const alongExtent = lastLevel < 0 ? 0 : levelOffsets[lastLevel]! + levelSizes[lastLevel]!\n\n return { alongExtent, acrossExtent }\n}\n\n/**\n * Раскладка «уровнями»: каждый уровень укладывается сам по себе, узел за узлом.\n * Родитель больше не садится по центру детей — зато высота уровня зависит\n * только от того, сколько на нём узлов и какой у них отступ.\n *\n * Пригождается, когда уровни показывают по одному: в свайпере турнирной сетки\n * раунд из восьми матчей и полуфинал из двух получают каждый свой отступ,\n * а не тот, что достался от соседнего уровня.\n *\n * По умолчанию уровни начинаются с одной черты — так при свайпе матчи каждого\n * раунда оказываются сверху, а не уезжают к середине экрана. `levelAlign:\n * 'center'` центрует их относительно самого населённого уровня.\n */\nfunction stackNodes<T>(roots: TreeNode<T>[], options: TreeViewOptions<T>): Placements {\n const vertical = options.direction === 'top-to-bottom' || options.direction === 'bottom-to-top'\n const byId = new Map<string, Placement>()\n const order: Placement[] = []\n const levelSizes: number[] = []\n const levels: TreeNode<T>[][] = []\n\n for (const node of flatten(roots)) (levels[node.depth] ??= []).push(node)\n\n /** Места по уровням и то, сколько уровень занял поперёк. */\n const byLevel: Placement[][] = []\n const extents: number[] = []\n\n levels.forEach((level, depth) => {\n const placements: Placement[] = []\n let cursor = 0\n\n level.forEach((node, index) => {\n const width = sizeOf(options.nodeWidth, node)\n const height = sizeOf(options.nodeHeight, node)\n const alongSize = vertical ? height : width\n const acrossSize = vertical ? width : height\n\n levelSizes[depth] = Math.max(levelSizes[depth] ?? 0, alongSize)\n\n const placement: Placement = { depth, along: 0, across: cursor, alongSize, acrossSize }\n byId.set(node.id, placement)\n placements.push(placement)\n order.push(placement)\n\n // Следующий узел из другой ветки — между ними промежуток крупнее:\n // так видно, какие узлы сходятся в один на следующем уровне.\n const next = level[index + 1]\n const gap =\n next && next.parentId !== node.parentId\n ? (options.groupGap ?? options.siblingGap)\n : options.siblingGap\n\n cursor += acrossSize + gapOf(gap, node, level.length)\n })\n\n const last = placements[placements.length - 1]\n byLevel[depth] = placements\n extents[depth] = last ? last.across + last.acrossSize : 0\n })\n\n // Второй проход по уровням: короткие сдвигаются к центру самого длинного.\n if (options.levelAlign === 'center') {\n const widest = Math.max(0, ...extents)\n\n byLevel.forEach((placements, depth) => {\n const shift = (widest - (extents[depth] ?? 0)) / 2\n if (shift > 0) for (const placement of placements) placement.across += shift\n })\n }\n\n return { byId, ...placeLevels(order, levelSizes, options.levelGap) }\n}\n\n/**\n * Считает место каждого узла.\n *\n * Правила всего два, и их легко проверить руками:\n * - лист встаёт сразу за предыдущим: «конец соседа + siblingGap»;\n * - родитель встаёт ровно по центру между первым и последним ребёнком.\n *\n * Карточки могут быть разного размера, поэтому позиции считаются в пикселях,\n * а не в номерах колонок: следующий узел всегда начинается там, где кончился\n * предыдущий. Уровень занимает столько, сколько занимает самая крупная\n * карточка на нём, а карточки поменьше центруются внутри этой полосы.\n */\nexport function placeNodes<T>(roots: TreeNode<T>[], options: TreeViewOptions<T>): Placements {\n if (options.levelLayout === 'stack') return stackNodes(roots, options)\n\n const vertical = options.direction === 'top-to-bottom' || options.direction === 'bottom-to-top'\n const counts = countByDepth(roots)\n const byId = new Map<string, Placement>()\n /** Те же места, но по порядку размещения — нужно, чтобы двигать ветку целиком. */\n const order: Placement[] = []\n /** Следующая свободная позиция поперёк уровня. */\n let cursor = 0\n /** Насколько толстый каждый уровень: максимум по его карточкам. */\n const levelSizes: number[] = []\n\n function place(node: TreeNode<T>): Placement {\n const width = sizeOf(options.nodeWidth, node)\n const height = sizeOf(options.nodeHeight, node)\n const alongSize = vertical ? height : width\n const acrossSize = vertical ? width : height\n\n levelSizes[node.depth] = Math.max(levelSizes[node.depth] ?? 0, alongSize)\n\n const branchStart = cursor\n const branchIndex = order.length\n let across = cursor\n\n if (node.children.length > 0) {\n const children = node.children.map(place)\n const first = children[0]!\n const last = children[children.length - 1]!\n const firstCenter = first.across + first.acrossSize / 2\n const lastCenter = last.across + last.acrossSize / 2\n const center = (firstCenter + lastCenter) / 2\n\n across = center - acrossSize / 2\n\n // Карточка родителя шире, чем разлёт его детей, — вся ветка едет вправо,\n // иначе родитель налез бы на соседнюю ветку слева.\n const overflow = branchStart - across\n if (overflow > 0) {\n for (let i = branchIndex; i < order.length; i += 1) order[i]!.across += overflow\n across = branchStart\n }\n }\n\n const placement: Placement = { depth: node.depth, along: 0, across, alongSize, acrossSize }\n byId.set(node.id, placement)\n order.push(placement)\n\n // Соседям — стартовать за самым правым краем ветки, включая саму карточку.\n cursor = Math.max(\n cursor,\n across + acrossSize + gapOf(options.siblingGap, node, counts[node.depth] ?? 1),\n )\n return placement\n }\n\n roots.forEach(place)\n\n return { byId, ...placeLevels(order, levelSizes, options.levelGap) }\n}\n\n/** Разворачивает дерево в плоский массив (порядок — «сверху вниз», как в данных). */\nexport function flatten<T>(roots: TreeNode<T>[]): TreeNode<T>[] {\n const result: TreeNode<T>[] = []\n\n function walk(node: TreeNode<T>): void {\n result.push(node)\n node.children.forEach(walk)\n }\n\n roots.forEach(walk)\n return result\n}\n\n/**\n * Главная функция раскладки: из дерева делает координаты и линии.\n * Чистая — одни и те же данные всегда дают один и тот же результат.\n */\nexport function layoutTree<T>(roots: TreeNode<T>[], options?: Partial<TreeViewOptions<T>>): Layout<T> {\n const resolved = resolveOptions(options)\n\n if (roots.length === 0) {\n return { nodes: [], links: [], width: 0, height: 0 }\n }\n\n const vertical = resolved.direction === 'top-to-bottom' || resolved.direction === 'bottom-to-top'\n const { byId, alongExtent, acrossExtent } = placeNodes(roots, resolved)\n // У «перевёрнутых» направлений уровень 0 должен оказаться в конце холста.\n const mirrored = resolved.direction === 'bottom-to-top' || resolved.direction === 'right-to-left'\n\n const nodes: LayoutNode<T>[] = flatten(roots).map((node) => {\n const { along, across, alongSize, acrossSize } = byId.get(node.id)!\n const alongPosition = mirrored ? alongExtent - alongSize - along : along\n\n return {\n id: node.id,\n data: node.data,\n depth: node.depth,\n parentId: node.parentId,\n x: vertical ? across : alongPosition,\n y: vertical ? alongPosition : across,\n width: vertical ? acrossSize : alongSize,\n height: vertical ? alongSize : acrossSize,\n hasChildren: node.hasChildren,\n collapsed: node.collapsed,\n }\n })\n\n return {\n nodes,\n links: buildLinks(nodes, resolved),\n width: vertical ? acrossExtent : alongExtent,\n height: vertical ? alongExtent : acrossExtent,\n }\n}\n","import {\n DEFAULT_SCALE_LIMITS,\n IDENTITY_TRANSFORM,\n fitToViewport,\n panBy,\n zoomAtPoint,\n type ScaleLimits,\n} from './viewport'\nimport type { Size, Transform } from './types'\n\n/** Во сколько раз меняется масштаб за одно движение колеса или клик по кнопке. */\nexport const ZOOM_STEP = 1.2\n\nexport interface PanZoomParams {\n /** Можно ли двигать холст. По умолчанию да. */\n pannable?: () => boolean\n /** Можно ли менять масштаб. По умолчанию да. */\n zoomable?: () => boolean\n /** Пределы масштаба. */\n limits?: () => ScaleLimits\n /** Вызывается после каждого изменения — здесь фреймворк обновляет свой state. */\n onChange: (transform: Transform) => void\n}\n\n/**\n * Перетаскивание и зум без привязки к фреймворку: и Vue 2, и Vue 3, и что угодно\n * ещё просто создают контроллер и подписываются на `onChange`.\n *\n * Слушаем pointer-события — они одинаково работают мышью и пальцем,\n * поэтому отдельного кода для мобильных не нужно.\n */\nexport class PanZoomController {\n transform: Transform = { ...IDENTITY_TRANSFORM }\n\n private element: HTMLElement | null = null\n /** Активные касания: id указателя → последняя позиция на экране. */\n private readonly pointers = new Map<number, { x: number; y: number }>()\n /** Расстояние между двумя пальцами на прошлом событии — для «щипка». */\n private lastPinchDistance = 0\n\n constructor(private readonly params: PanZoomParams) {}\n\n /** Подписывается на события элемента. Повторный вызов сначала отписывается. */\n attach(element: HTMLElement): void {\n this.detach()\n this.element = element\n\n element.addEventListener('pointerdown', this.onPointerDown)\n element.addEventListener('pointermove', this.onPointerMove)\n element.addEventListener('pointerup', this.onPointerUp)\n element.addEventListener('pointercancel', this.onPointerUp)\n // passive: false — иначе браузер не даст отменить прокрутку страницы.\n element.addEventListener('wheel', this.onWheel, { passive: false })\n }\n\n /** Снимает все слушатели. Обязательно вызывать при размонтировании. */\n detach(): void {\n const element = this.element\n if (!element) return\n\n element.removeEventListener('pointerdown', this.onPointerDown)\n element.removeEventListener('pointermove', this.onPointerMove)\n element.removeEventListener('pointerup', this.onPointerUp)\n element.removeEventListener('pointercancel', this.onPointerUp)\n element.removeEventListener('wheel', this.onWheel)\n\n this.element = null\n this.pointers.clear()\n this.lastPinchDistance = 0\n }\n\n /** Вписывает содержимое в размер элемента и центрирует. */\n fit(content: Size, padding = 24): void {\n const element = this.element\n if (!element) return\n\n this.set(\n fitToViewport(\n content,\n { width: element.clientWidth, height: element.clientHeight },\n padding,\n this.limits,\n ),\n )\n }\n\n zoomIn(): void {\n this.zoomToCenter(ZOOM_STEP)\n }\n\n zoomOut(): void {\n this.zoomToCenter(1 / ZOOM_STEP)\n }\n\n reset(): void {\n this.set({ ...IDENTITY_TRANSFORM })\n }\n\n /** Устанавливает состояние напрямую (например, восстановление после перезагрузки). */\n set(transform: Transform): void {\n this.transform = { ...transform }\n this.params.onChange(this.transform)\n }\n\n private get limits(): ScaleLimits {\n return this.params.limits?.() ?? DEFAULT_SCALE_LIMITS\n }\n\n private isPannable(): boolean {\n return this.params.pannable?.() ?? true\n }\n\n private isZoomable(): boolean {\n return this.params.zoomable?.() ?? true\n }\n\n /** Переводит координаты экрана в координаты внутри элемента. */\n private toLocalPoint(point: { clientX: number; clientY: number }) {\n const rect = this.element?.getBoundingClientRect()\n return { x: point.clientX - (rect?.left ?? 0), y: point.clientY - (rect?.top ?? 0) }\n }\n\n private zoomToCenter(factor: number): void {\n const element = this.element\n const center = { x: (element?.clientWidth ?? 0) / 2, y: (element?.clientHeight ?? 0) / 2 }\n this.set(zoomAtPoint(this.transform, factor, center, this.limits))\n }\n\n // Стрелочные функции — чтобы `this` оставался контроллером и чтобы\n // removeEventListener получил ту же самую ссылку на обработчик.\n\n private readonly onPointerDown = (event: PointerEvent): void => {\n if (!this.isPannable()) return\n // Элементу с атрибутом data-tv-no-pan разрешаем свои жесты (кнопки, инпуты).\n if ((event.target as HTMLElement | null)?.closest?.('[data-tv-no-pan]')) return\n\n this.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })\n this.element?.setPointerCapture?.(event.pointerId)\n }\n\n private readonly onPointerMove = (event: PointerEvent): void => {\n const previous = this.pointers.get(event.pointerId)\n if (!previous) return\n\n this.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })\n\n if (this.pointers.size === 2) {\n this.pinch()\n return\n }\n\n this.set(panBy(this.transform, event.clientX - previous.x, event.clientY - previous.y))\n }\n\n private readonly onPointerUp = (event: PointerEvent): void => {\n this.pointers.delete(event.pointerId)\n if (this.pointers.size < 2) this.lastPinchDistance = 0\n }\n\n private readonly onWheel = (event: WheelEvent): void => {\n if (!this.isZoomable()) return\n event.preventDefault()\n\n const factor = event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP\n this.set(zoomAtPoint(this.transform, factor, this.toLocalPoint(event), this.limits))\n }\n\n /** Два пальца: масштабируем вокруг точки ровно между ними. */\n private pinch(): void {\n if (!this.isZoomable()) return\n\n const [first, second] = [...this.pointers.values()]\n if (!first || !second) return\n\n const distance = Math.hypot(first.x - second.x, first.y - second.y)\n\n if (this.lastPinchDistance > 0 && distance > 0) {\n const middle = this.toLocalPoint({\n clientX: (first.x + second.x) / 2,\n clientY: (first.y + second.y) / 2,\n })\n this.set(zoomAtPoint(this.transform, distance / this.lastPinchDistance, middle, this.limits))\n }\n\n this.lastPinchDistance = distance\n }\n}\n","/**\n * Вставляет стили в документ один раз.\n *\n * Зачем: чтобы в проекте не приходилось писать лишний\n * `import '@bigplay/tree-view/style.css'` — всё работает сразу после установки.\n *\n * На сервере (SSR) функция ничего не делает: там нет `document`,\n * а на клиенте стили появятся при гидрации.\n */\nexport function injectStyles(css: string, id = 'tree-view-styles'): void {\n if (typeof document === 'undefined') return\n if (document.getElementById(id)) return\n\n const style = document.createElement('style')\n style.id = id\n style.textContent = css\n\n document.head.appendChild(style)\n}\n","import type { Layout, LayoutLink, LayoutNode } from './types'\n\n/**\n * Виртуализация: рисуем только то, что видно на экране.\n *\n * Математика раскладки дешёвая — тысяча матчей считается меньше чем за\n * миллисекунду. Дорого стоит DOM: у каждой карточки свои элементы, и на\n * тысяче матчей их около десяти тысяч. Поэтому список узлов и линий\n * фильтруется по видимой области, а при перетаскивании пересобирается\n * только разница.\n */\n\n/** Прямоугольник в координатах раскладки (не экрана). */\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** Запас вокруг видимой области, чтобы при перетаскивании не мигали края. */\nexport const DEFAULT_OVERSCAN = 400\n\n/** Меньше этого числа узлов фильтровать смысла нет — дешевле нарисовать всё. */\nexport const VIRTUALIZE_THRESHOLD = 150\n\n/** Пересекаются ли прямоугольники (касание краями считается пересечением). */\nfunction intersects(a: Rect, b: Rect): boolean {\n return (\n a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y\n )\n}\n\n/** Прямоугольник, в который целиком помещается линия вместе с обоими узлами. */\nfunction linkRect<T>(link: LayoutLink<T>): Rect {\n const { source, target } = link\n const x = Math.min(source.x, target.x)\n const y = Math.min(source.y, target.y)\n\n return {\n x,\n y,\n width: Math.max(source.x + source.width, target.x + target.width) - x,\n height: Math.max(source.y + source.height, target.y + target.height) - y,\n }\n}\n\n/** Расширяет прямоугольник во все стороны. */\nexport function expandRect(rect: Rect, by: number): Rect {\n return { x: rect.x - by, y: rect.y - by, width: rect.width + by * 2, height: rect.height + by * 2 }\n}\n\n/**\n * Оставляет в раскладке только узлы и линии, попадающие в видимую область.\n *\n * Размеры холста (`width`/`height`) не меняются: полоса прокрутки и\n * `fit()` должны видеть дерево целиком, даже если нарисована его часть.\n */\nexport function visibleLayout<T>(\n layout: Layout<T>,\n view: Rect | null,\n overscan: number = DEFAULT_OVERSCAN,\n): Layout<T> {\n if (!view || layout.nodes.length < VIRTUALIZE_THRESHOLD) return layout\n\n // Холста не видно совсем (уехал за экран) — рисовать нечего.\n if (view.width <= 0 || view.height <= 0) {\n return { nodes: [], links: [], width: layout.width, height: layout.height }\n }\n\n const area = expandRect(view, overscan)\n const nodes: LayoutNode<T>[] = []\n const links: LayoutLink<T>[] = []\n\n for (const node of layout.nodes) {\n if (intersects(area, node)) nodes.push(node)\n }\n for (const link of layout.links) {\n if (intersects(area, linkRect(link))) links.push(link)\n }\n\n return { nodes, links, width: layout.width, height: layout.height }\n}\n\n/**\n * Грубая оценка видимой области до первого измерения — размер окна.\n *\n * Нужна, чтобы самый первый кадр не рисовал дерево целиком: измерить холст\n * можно только после того, как он окажется в DOM, а к этому моменту тысяча\n * карточек уже была бы создана и выброшена.\n */\nexport function estimateVisibleRect(): Rect | null {\n if (typeof window === 'undefined') return null\n\n return { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight }\n}\n\n/**\n * Считает видимую часть холста по его же положению на экране.\n *\n * Работает и когда дерево двигают мышью внутри контейнера, и когда оно\n * стоит в обычном скролле страницы: холст сам сдвинут и отмасштабирован,\n * поэтому достаточно пересечь его прямоугольник с окном браузера.\n */\nexport function measureVisibleRect(canvas: Element | null | undefined, scale: number): Rect | null {\n if (!canvas || typeof window === 'undefined' || scale <= 0) return null\n\n const box = canvas.getBoundingClientRect()\n const left = Math.max(box.left, 0)\n const top = Math.max(box.top, 0)\n const right = Math.min(box.right, window.innerWidth)\n const bottom = Math.min(box.bottom, window.innerHeight)\n\n // Холст полностью за пределами экрана — рисовать нечего.\n if (right <= left || bottom <= top) return { x: 0, y: 0, width: 0, height: 0 }\n\n return {\n x: (left - box.left) / scale,\n y: (top - box.top) / scale,\n width: (right - left) / scale,\n height: (bottom - top) / scale,\n }\n}\n"],"names":["DEFAULT_OPTIONS","resolveOptions","options","defaultGetChildren","item","defaultGetId","toTree","input","accessors","items","fromFlatList","fromNested","getId","getChildren","usedIds","build","depth","parentId","path","id","toId","assertUniqueId","children","child","index","nodes","byId","node","roots","rawParentId","parent","setDepths","queue","rawId","fallback","fromLevels","levels","filled","level","lastLevel","levelIndex","resolveId","current","parents","perParent","raw","applyCollapsed","collapsedIds","walk","collapsed","toggleCollapsed","next","DEFAULT_BRACKET_ROWS","DEFAULT_BRACKET_ROW_HEIGHT","buildBracketCard","match","teams","rowCount","techDefeatLabel","rows","team","isTechDefeat","isWinner","bracketCardHeight","DEFAULT_SCALE_LIMITS","IDENTITY_TRANSFORM","clamp","value","min","max","fitToViewport","content","viewport","padding","limits","availableWidth","availableHeight","scale","panBy","transform","dx","dy","zoomAtPoint","factor","point","contentX","contentY","toCssTransform","buildBracketRounds","rounds","width","columns","round","roundScrollLeft","box","centered","levelGapForWidth","nodeWidth","roundAtScroll","scrollLeft","columnWidth","count","passed","roundOfDepth","mirrored","MAX_GAP_STEPS","roundSiblingGap","base","SWIPE_ELBOW_OFFSET","roundContentHeight","height","findScrollParent","element","overflowX","smoothScrollLeft","left","offsetWithin","container","TREE_NODE_SELECTOR","nodesInColumn","offset","origin","rect","horizontalBox","HIDDEN_CLASS","hideRoundsBefore","roundOf","afterScrollEnd","done","timeout","handler","started","previous","tick","settled","MOBILE_MEDIA_QUERY","watchMedia","query","onChange","media","listener","watchWidth","observer","watchChildren","isVertical","direction","getAnchors","source","target","sourceCenterX","sourceCenterY","targetCenterX","targetCenterY","buildPath","from","to","style","elbowOffset","groupMid","x1","y1","x2","y2","vertical","midX","midY","cornerOf","middle","span","corner","mid","cornerY","cornerX","groupMiddles","spans","center","buildLinks","links","middles","sizeOf","size","gapOf","gap","levelCount","countByDepth","counts","flatten","placeLevels","order","levelSizes","levelGap","levelOffsets","acrossExtent","placement","levelSize","stackNodes","byLevel","extents","placements","cursor","alongSize","acrossSize","last","widest","shift","placeNodes","place","branchStart","branchIndex","across","first","firstCenter","lastCenter","overflow","i","result","layoutTree","resolved","alongExtent","along","alongPosition","ZOOM_STEP","PanZoomController","params","event","second","distance","injectStyles","css","DEFAULT_OVERSCAN","VIRTUALIZE_THRESHOLD","intersects","a","b","linkRect","link","x","y","expandRect","by","visibleLayout","layout","view","overscan","area","estimateVisibleRect","measureVisibleRect","canvas","top","right","bottom"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export { toTree, type Accessors } from './normalize';
|
|
|
5
5
|
export { fromLevels, type FromLevelsOptions } from './fromLevels';
|
|
6
6
|
export { applyCollapsed, toggleCollapsed } from './collapse';
|
|
7
7
|
export { buildBracketCard, bracketCardHeight, DEFAULT_BRACKET_ROWS, DEFAULT_BRACKET_ROW_HEIGHT, type BracketCardMatch, type BracketCardModel, type BracketCardOptions, type BracketCardRow, type BracketCardTeam, } from './bracket';
|
|
8
|
-
export { buildBracketRounds, levelGapForWidth, roundSiblingGap, MAX_GAP_STEPS, SWIPE_ELBOW_OFFSET, roundAtScroll, roundOfDepth, roundScrollLeft, type BracketRound, type BracketRoundColumn, type BracketRoundsModel, type BracketRoundsOptions, type RoundScrollBox, } from './rounds';
|
|
9
|
-
export { findScrollParent, smoothScrollLeft, offsetWithin, nodesInColumn, horizontalBox, hideRoundsBefore, HIDDEN_CLASS, TREE_NODE_SELECTOR, } from './scroll';
|
|
8
|
+
export { buildBracketRounds, levelGapForWidth, roundSiblingGap, MAX_GAP_STEPS, SWIPE_ELBOW_OFFSET, roundAtScroll, roundContentHeight, roundOfDepth, roundScrollLeft, type BracketRound, type BracketRoundColumn, type BracketRoundsModel, type BracketRoundsOptions, type RoundScrollBox, } from './rounds';
|
|
9
|
+
export { afterScrollEnd, findScrollParent, smoothScrollLeft, offsetWithin, nodesInColumn, horizontalBox, hideRoundsBefore, HIDDEN_CLASS, TREE_NODE_SELECTOR, } from './scroll';
|
|
10
10
|
export { watchMedia, watchWidth, watchChildren, MOBILE_MEDIA_QUERY } from './media';
|
|
11
11
|
export { layoutTree, placeNodes, flatten, type Placement, type Placements } from './layout';
|
|
12
12
|
export { buildLinks, buildPath, getAnchors } from './links';
|