@bigplay/tree-view-core 0.2.1 → 0.3.1

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 CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _={nodeWidth:180,nodeHeight:64,levelGap:60,siblingGap:16,direction:"top-to-bottom",linkStyle:"elbow"};function z(e){return{..._,...e}}function tt(e){return e?.children}function k(e){return e?.id}function et(e,t={}){if(e==null)return[];const n=Array.isArray(e)?e:[e];return n.length===0?[]:t.getParentId?it(n,t):nt(n,t)}function nt(e,t){const n=t.getId??k,i=t.getChildren??tt,o=new Set;function r(s,h,c,l){const d=H(n(s),l);U(d,o);const p=(i(s)??[]).map((a,f)=>r(a,h+1,d,`${l}.${f}`));return{id:d,data:s,depth:h,parentId:c,children:p,hasChildren:p.length>0,collapsed:!1}}return e.map((s,h)=>r(s,0,null,String(h)))}function it(e,t){const n=t.getId??k,i=new Set,o=e.map((c,l)=>{const d=H(n(c),String(l));return U(d,i),{id:d,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 d=t.getParentId(e[l]),u=d==null?void 0:r.get(String(d));if(!u){s.push(c);return}c.parentId=u.id,u.children.push(c),u.hasChildren=!0}),ot(s)!==o.length)throw new Error("[tree-view] В данных есть цикл: часть узлов ссылается друг на друга по кругу.");return s}function ot(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 H(e,t){return e==null||e===""?t:String(e)}function U(e,t){if(t.has(e))throw new Error(`[tree-view] Повторяющийся id узла: "${e}". Идентификаторы должны быть уникальными.`);t.add(e)}function st(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:rt(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,d)=>{const u=h[Math.min(Math.floor(d/c),h.length-1)];l.parentId=u.id,u.children.push(l),u.hasChildren=!0})}return o[i]}function rt(e,t,n,i){const o=i?i(e,t,n):e?.id;return o==null||o===""?`${t}.${n}`:String(o)}function ht(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 ct(e,t){const n=new Set(e);return n.delete(t)||n.add(t),n}const M=2,W=40;function lt(e,t={}){const n=e?.teams??[],i=Math.max(t.rows??M,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 at(e,t={}){const n=e?.teams??[];return Math.max(t.rows??M,n.length)*(t.rowHeight??W)}function m(e){return Math.round(e*100)/100}function dt(e){return e==="top-to-bottom"||e==="bottom-to-top"}function F(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 Y(e,t,n,i){const o=m(e.x),r=m(e.y),s=m(t.x),h=m(t.y);if(n==="straight")return`M ${o} ${r} L ${s} ${h}`;const c=dt(i),l=m((o+s)/2),d=m((r+h)/2);return n==="elbow"?c?`M ${o} ${r} L ${o} ${d} L ${s} ${d} L ${s} ${h}`:`M ${o} ${r} L ${l} ${r} L ${l} ${h} L ${s} ${h}`:c?`M ${o} ${r} C ${o} ${d} ${s} ${d} ${s} ${h}`:`M ${o} ${r} C ${l} ${r} ${l} ${h} ${s} ${h}`}function B(e,t){const n=new Map(e.map(o=>[o.id,o])),i=[];for(const o of e){const r=o.parentId==null?void 0:n.get(o.parentId);if(!r)continue;const{from:s,to:h}=F(r,o,t.direction);i.push({id:`${r.id}->${o.id}`,source:r,target:o,path:Y(s,h,t.linkStyle,t.direction)})}return i}function R(e,t){return typeof e=="function"?e(t):e}function N(e,t){const n=t.direction==="top-to-bottom"||t.direction==="bottom-to-top",i=new Map,o=[];let r=0;const s=[];function h(a){const f=R(t.nodeWidth,a),S=R(t.nodeHeight,a),b=n?S:f,C=n?f:S;s[a.depth]=Math.max(s[a.depth]??0,b);const P=r,q=o.length;let g=r;if(a.children.length>0){const L=a.children.map(h),v=L[0],D=L[L.length-1],J=v.across+v.acrossSize/2,Q=D.across+D.acrossSize/2;g=(J+Q)/2-C/2;const A=P-g;if(A>0){for(let $=q;$<o.length;$+=1)o[$].across+=A;g=P}}const T={depth:a.depth,along:0,across:g,alongSize:b,acrossSize:C};return i.set(a.id,T),o.push(T),r=Math.max(r,g+C+t.siblingGap),T}e.forEach(h);const c=[];let l=0;for(let a=0;a<s.length;a+=1)c[a]=l,l+=(s[a]??0)+t.levelGap;let d=0;for(const a of o){const f=s[a.depth]??a.alongSize;a.along=c[a.depth]+(f-a.alongSize)/2,d=Math.max(d,a.across+a.acrossSize)}const u=s.length-1,p=u<0?0:c[u]+s[u];return{byId:i,alongExtent:p,acrossExtent:d}}function V(e){const t=[];function n(i){t.push(i),i.children.forEach(n)}return e.forEach(n),t}function ut(e,t){const n=z(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}=N(e,n),h=n.direction==="bottom-to-top"||n.direction==="right-to-left",c=V(e).map(l=>{const{along:d,across:u,alongSize:p,acrossSize:a}=o.get(l.id),f=h?r-p-d:d;return{id:l.id,data:l.data,depth:l.depth,parentId:l.parentId,x:i?u:f,y:i?f:u,width:i?a:p,height:i?p:a,hasChildren:l.hasChildren,collapsed:l.collapsed}});return{nodes:c,links:B(c,n),width:i?s:r,height:i?r:s}}const E={min:.2,max:3},w={x:0,y:0,scale:1};function I(e,t,n){return Math.min(Math.max(e,t),n)}function X(e,t,n=24,i=E){if(e.width<=0||e.height<=0||t.width<=0||t.height<=0)return{...w};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=I(s,i.min,i.max);return{scale:h,x:(t.width-e.width*h)/2,y:(t.height-e.height*h)/2}}function Z(e,t,n){return{...e,x:e.x+t,y:e.y+n}}function y(e,t,n,i=E){const o=I(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 ft(e){return`translate(${e.x}px, ${e.y}px) scale(${e.scale})`}const x=1.2;class pt{constructor(t){this.params=t,this.transform={...w},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(Z(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?x:1/x;this.set(y(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(X(t,{width:i.clientWidth,height:i.clientHeight},n,this.limits))}zoomIn(){this.zoomToCenter(x)}zoomOut(){this.zoomToCenter(1/x)}reset(){this.set({...w})}set(t){this.transform={...t},this.params.onChange(this.transform)}get limits(){return this.params.limits?.()??E}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(y(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(y(this.transform,i/this.lastPinchDistance,o,this.limits))}this.lastPinchDistance=i}}function mt(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 G=400,K=150;function O(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 gt(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 j(e,t){return{x:e.x-t,y:e.y-t,width:e.width+t*2,height:e.height+t*2}}function xt(e,t,n=G){if(!t||e.nodes.length<K)return e;if(t.width<=0||t.height<=0)return{nodes:[],links:[],width:e.width,height:e.height};const i=j(t,n),o=[],r=[];for(const s of e.nodes)O(i,s)&&o.push(s);for(const s of e.links)O(i,gt(s))&&r.push(s);return{nodes:o,links:r,width:e.width,height:e.height}}function yt(){return typeof window>"u"?null:{x:0,y:0,width:window.innerWidth,height:window.innerHeight}}function wt(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=M;exports.DEFAULT_BRACKET_ROW_HEIGHT=W;exports.DEFAULT_OPTIONS=_;exports.DEFAULT_OVERSCAN=G;exports.DEFAULT_SCALE_LIMITS=E;exports.IDENTITY_TRANSFORM=w;exports.PanZoomController=pt;exports.VIRTUALIZE_THRESHOLD=K;exports.ZOOM_STEP=x;exports.applyCollapsed=ht;exports.bracketCardHeight=at;exports.buildBracketCard=lt;exports.buildLinks=B;exports.buildPath=Y;exports.clamp=I;exports.estimateVisibleRect=yt;exports.expandRect=j;exports.fitToViewport=X;exports.flatten=V;exports.fromLevels=st;exports.getAnchors=F;exports.injectStyles=mt;exports.layoutTree=ut;exports.measureVisibleRect=wt;exports.panBy=Z;exports.placeNodes=N;exports.resolveOptions=z;exports.toCssTransform=ft;exports.toTree=et;exports.toggleCollapsed=ct;exports.visibleLayout=xt;exports.zoomAtPoint=y;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const O={nodeWidth:180,nodeHeight:64,levelGap:60,siblingGap:16,direction:"top-to-bottom",linkStyle:"elbow"};function k(e){return{...O,...e}}function nt(e){return e?.children}function z(e){return e?.id}function it(e,t={}){if(e==null)return[];const n=Array.isArray(e)?e:[e];return n.length===0?[]:t.getParentId?rt(n,t):ot(n,t)}function ot(e,t){const n=t.getId??z,i=t.getChildren??nt,o=new Set;function s(r,c,h,l){const d=W(n(r),l);B(d,o);const p=(i(r)??[]).map((a,f)=>s(a,c+1,d,`${l}.${f}`));return{id:d,data:r,depth:c,parentId:h,children:p,hasChildren:p.length>0,collapsed:!1}}return e.map((r,c)=>s(r,0,null,String(c)))}function rt(e,t){const n=t.getId??z,i=new Set,o=e.map((h,l)=>{const d=W(n(h),String(l));return B(d,i),{id:d,data:h,depth:0,parentId:null,children:[],hasChildren:!1,collapsed:!1}}),s=new Map(o.map(h=>[h.id,h])),r=[];if(o.forEach((h,l)=>{const d=t.getParentId(e[l]),u=d==null?void 0:s.get(String(d));if(!u){r.push(h);return}h.parentId=u.id,u.children.push(h),u.hasChildren=!0}),st(r)!==o.length)throw new Error("[tree-view] В данных есть цикл: часть узлов ссылается друг на друга по кругу.");return r}function st(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 W(e,t){return e==null||e===""?t:String(e)}function B(e,t){if(t.has(e))throw new Error(`[tree-view] Повторяющийся id узла: "${e}". Идентификаторы должны быть уникальными.`);t.add(e)}function ct(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((c,h)=>({id:ht(c,r,h,t.getId),data:c,depth:i-r,parentId:null,children:[],hasChildren:!1,collapsed:!1})));for(let s=0;s<i;s+=1){const r=o[s],c=o[s+1],h=Math.ceil(r.length/c.length);r.forEach((l,d)=>{const u=c[Math.min(Math.floor(d/h),c.length-1)];l.parentId=u.id,u.children.push(l),u.hasChildren=!0})}return o[i]}function ht(e,t,n,i){const o=i?i(e,t,n):e?.id;return o==null||o===""?`${t}.${n}`:String(o)}function lt(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 at(e,t){const n=new Set(e);return n.delete(t)||n.add(t),n}const I=2,U=40;function dt(e,t={}){const n=e?.teams??[],i=Math.max(t.rows??I,n.length),o=t.techDefeatLabel??"ТП",s=n.map((r,c)=>{const h=r.isTechDefeat===!0,l=r.isWinner===!0;return{key:String(r.id??`team-${c}`),team:r,score:h?o:String(r.score??""),isWinner:l,isLoser:!l,isMyTeam:r.isMyTeam===!0,isTechDefeat:h,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 ut(e,t={}){const n=e?.teams??[];return Math.max(t.rows??I,n.length)*(t.rowHeight??U)}const E={min:.2,max:3},x={x:0,y:0,scale:1};function L(e,t,n){return Math.min(Math.max(e,t),n)}function H(e,t,n=24,i=E){if(e.width<=0||e.height<=0||t.width<=0||t.height<=0)return{...x};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),c=L(r,i.min,i.max);return{scale:c,x:(t.width-e.width*c)/2,y:(t.height-e.height*c)/2}}function Y(e,t,n){return{...e,x:e.x+t,y:e.y+n}}function y(e,t,n,i=E){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 ft(e){return`translate(${e.x}px, ${e.y}px) scale(${e.scale})`}function pt(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 mt(e){const t=e.left+e.width/2-e.viewport/2;return L(t,0,Math.max(0,e.scrollWidth-e.viewport))}function gt(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 wt(e,t){if(typeof e.scrollTo=="function"){e.scrollTo({left:t,behavior:"smooth"});return}e.scrollLeft=t}function F(e,t){return e.getBoundingClientRect().left-t.getBoundingClientRect().left+t.scrollLeft}const N=".tree-view__node";function yt(e,t,n,i){const o=e.getBoundingClientRect().left+i;return Array.from(e.querySelectorAll(N)).filter(r=>{const c=r.getBoundingClientRect();return Math.floor((c.left+c.width/2-o)/n)===t})}function xt(e,t){return{left:F(e,t),width:e.getBoundingClientRect().width}}const Et="(max-width: 768px)";function Lt(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 m(e){return Math.round(e*100)/100}function Ct(e){return e==="top-to-bottom"||e==="bottom-to-top"}function V(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 G(e,t,n,i){const o=m(e.x),s=m(e.y),r=m(t.x),c=m(t.y);if(n==="straight")return`M ${o} ${s} L ${r} ${c}`;const h=Ct(i),l=m((o+r)/2),d=m((s+c)/2);return n==="elbow"?h?`M ${o} ${s} L ${o} ${d} L ${r} ${d} L ${r} ${c}`:`M ${o} ${s} L ${l} ${s} L ${l} ${c} L ${r} ${c}`:h?`M ${o} ${s} C ${o} ${d} ${r} ${d} ${r} ${c}`:`M ${o} ${s} C ${l} ${s} ${l} ${c} ${r} ${c}`}function X(e,t){const n=new Map(e.map(o=>[o.id,o])),i=[];for(const o of e){const s=o.parentId==null?void 0:n.get(o.parentId);if(!s)continue;const{from:r,to:c}=V(s,o,t.direction);i.push({id:`${s.id}->${o.id}`,source:s,target:o,path:G(r,c,t.linkStyle,t.direction)})}return i}function _(e,t){return typeof e=="function"?e(t):e}function Z(e,t){const n=t.direction==="top-to-bottom"||t.direction==="bottom-to-top",i=new Map,o=[];let s=0;const r=[];function c(a){const f=_(t.nodeWidth,a),$=_(t.nodeHeight,a),v=n?$:f,C=n?f:$;r[a.depth]=Math.max(r[a.depth]??0,v);const b=s,J=o.length;let g=s;if(a.children.length>0){const M=a.children.map(c),P=M[0],R=M[M.length-1],tt=P.across+P.acrossSize/2,et=R.across+R.acrossSize/2;g=(tt+et)/2-C/2;const D=b-g;if(D>0){for(let S=J;S<o.length;S+=1)o[S].across+=D;g=b}}const T={depth:a.depth,along:0,across:g,alongSize:v,acrossSize:C};return i.set(a.id,T),o.push(T),s=Math.max(s,g+C+t.siblingGap),T}e.forEach(c);const h=[];let l=0;for(let a=0;a<r.length;a+=1)h[a]=l,l+=(r[a]??0)+t.levelGap;let d=0;for(const a of o){const f=r[a.depth]??a.alongSize;a.along=h[a.depth]+(f-a.alongSize)/2,d=Math.max(d,a.across+a.acrossSize)}const u=r.length-1,p=u<0?0:h[u]+r[u];return{byId:i,alongExtent:p,acrossExtent:d}}function K(e){const t=[];function n(i){t.push(i),i.children.forEach(n)}return e.forEach(n),t}function Tt(e,t){const n=k(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}=Z(e,n),c=n.direction==="bottom-to-top"||n.direction==="right-to-left",h=K(e).map(l=>{const{along:d,across:u,alongSize:p,acrossSize:a}=o.get(l.id),f=c?s-p-d:d;return{id:l.id,data:l.data,depth:l.depth,parentId:l.parentId,x:i?u:f,y:i?f:u,width:i?a:p,height:i?p:a,hasChildren:l.hasChildren,collapsed:l.collapsed}});return{nodes:h,links:X(h,n),width:i?r:s,height:i?s:r}}const w=1.2;class Mt{constructor(t){this.params=t,this.transform={...x},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(Y(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?w:1/w;this.set(y(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(H(t,{width:i.clientWidth,height:i.clientHeight},n,this.limits))}zoomIn(){this.zoomToCenter(w)}zoomOut(){this.zoomToCenter(1/w)}reset(){this.set({...x})}set(t){this.transform={...t},this.params.onChange(this.transform)}get limits(){return this.params.limits?.()??E}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(y(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(y(this.transform,i/this.lastPinchDistance,o,this.limits))}this.lastPinchDistance=i}}function St(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 j=400,q=150;function A(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 It(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 Q(e,t){return{x:e.x-t,y:e.y-t,width:e.width+t*2,height:e.height+t*2}}function $t(e,t,n=j){if(!t||e.nodes.length<q)return e;if(t.width<=0||t.height<=0)return{nodes:[],links:[],width:e.width,height:e.height};const i=Q(t,n),o=[],s=[];for(const r of e.nodes)A(i,r)&&o.push(r);for(const r of e.links)A(i,It(r))&&s.push(r);return{nodes:o,links:s,width:e.width,height:e.height}}function vt(){return typeof window>"u"?null:{x:0,y:0,width:window.innerWidth,height:window.innerHeight}}function bt(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=I;exports.DEFAULT_BRACKET_ROW_HEIGHT=U;exports.DEFAULT_OPTIONS=O;exports.DEFAULT_OVERSCAN=j;exports.DEFAULT_SCALE_LIMITS=E;exports.IDENTITY_TRANSFORM=x;exports.MOBILE_MEDIA_QUERY=Et;exports.PanZoomController=Mt;exports.TREE_NODE_SELECTOR=N;exports.VIRTUALIZE_THRESHOLD=q;exports.ZOOM_STEP=w;exports.applyCollapsed=lt;exports.bracketCardHeight=ut;exports.buildBracketCard=dt;exports.buildBracketRounds=pt;exports.buildLinks=X;exports.buildPath=G;exports.clamp=L;exports.estimateVisibleRect=vt;exports.expandRect=Q;exports.findScrollParent=gt;exports.fitToViewport=H;exports.flatten=K;exports.fromLevels=ct;exports.getAnchors=V;exports.horizontalBox=xt;exports.injectStyles=St;exports.layoutTree=Tt;exports.measureVisibleRect=bt;exports.nodesInColumn=yt;exports.offsetWithin=F;exports.panBy=Y;exports.placeNodes=Z;exports.resolveOptions=k;exports.roundScrollLeft=mt;exports.smoothScrollLeft=wt;exports.toCssTransform=ft;exports.toTree=it;exports.toggleCollapsed=at;exports.visibleLayout=$t;exports.watchMedia=Lt;exports.zoomAtPoint=y;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -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/links.ts","../src/layout.ts","../src/viewport.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}\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 { 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/** Строит атрибут `d` для `<path>`: прямая, «ступенька» или кривая Безье. */\nexport function buildPath(from: Point, to: Point, style: LinkStyle, direction: Direction): string {\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 if (style === 'elbow') {\n return vertical\n ? `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`\n : `M ${x1} ${y1} L ${midX} ${y1} L ${midX} ${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/** Для каждого узла с родителем создаёт линию «родитель → ребёнок». */\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\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 })\n }\n\n return links\n}\n","import { resolveOptions } from './defaults'\nimport { buildLinks } from './links'\nimport type { 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/**\n * Считает место каждого узла.\n *\n * Правила всего два, и их легко проверить руками:\n * - лист встаёт сразу за предыдущим: «конец соседа + siblingGap»;\n * - родитель встаёт ровно по центру между первым и последним ребёнком.\n *\n * Карточки могут быть разного размера, поэтому позиции считаются в пикселях,\n * а не в номерах колонок: следующий узел всегда начинается там, где кончился\n * предыдущий. Уровень занимает столько, сколько занимает самая крупная\n * карточка на нём, а карточки поменьше центруются внутри этой полосы.\n */\nexport function placeNodes<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 /** Те же места, но по порядку размещения — нужно, чтобы двигать ветку целиком. */\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(cursor, across + acrossSize + options.siblingGap)\n return placement\n }\n\n roots.forEach(place)\n\n // Второй проход: толщина уровней уже известна, можно расставить их друг за другом.\n const levelOffsets: number[] = []\n let offset = 0\n for (let depth = 0; depth < levelSizes.length; depth += 1) {\n levelOffsets[depth] = offset\n offset += (levelSizes[depth] ?? 0) + options.levelGap\n }\n\n let acrossExtent = 0\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 { byId, alongExtent, acrossExtent }\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 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 {\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","round","value","isVertical","direction","getAnchors","source","target","sourceCenterX","sourceCenterY","targetCenterX","targetCenterY","buildPath","from","to","style","x1","y1","x2","y2","vertical","midX","midY","buildLinks","links","sizeOf","size","placeNodes","order","cursor","levelSizes","place","width","height","alongSize","acrossSize","branchStart","branchIndex","across","first","last","firstCenter","lastCenter","overflow","i","placement","levelOffsets","offset","acrossExtent","levelSize","alongExtent","flatten","result","layoutTree","resolved","mirrored","along","alongPosition","DEFAULT_SCALE_LIMITS","IDENTITY_TRANSFORM","clamp","min","max","fitToViewport","content","viewport","padding","limits","availableWidth","availableHeight","scale","panBy","transform","dx","dy","zoomAtPoint","factor","point","contentX","contentY","toCssTransform","ZOOM_STEP","PanZoomController","params","event","previous","element","rect","center","second","distance","middle","injectStyles","css","DEFAULT_OVERSCAN","VIRTUALIZE_THRESHOLD","intersects","a","b","linkRect","link","x","y","expandRect","by","visibleLayout","layout","view","overscan","area","estimateVisibleRect","measureVisibleRect","canvas","box","left","top","right","bottom"],"mappings":"gFAGO,MAAMA,EAAmC,CAC9C,UAAW,IACX,WAAY,GACZ,SAAU,GACV,WAAY,GACZ,UAAW,gBACX,UAAW,OACb,EAMO,SAASC,EAAkBC,EAA2D,CAC3F,MAAO,CAAE,GAAGF,EAAiB,GAAGE,CAAA,CAClC,CCCA,SAASC,GAAsBC,EAAiC,CAC9D,OAAQA,GAA6B,QACvC,CAGA,SAASC,EAAgBD,EAA6C,CACpE,OAAQA,GAAmC,EAC7C,CASO,SAASE,GAAUC,EAAmCC,EAA0B,GAAmB,CACxG,GAAID,GAAS,KAAM,MAAO,CAAA,EAE1B,MAAME,EAAQ,MAAM,QAAQF,CAAK,EAAIA,EAAQ,CAACA,CAAK,EACnD,OAAIE,EAAM,SAAW,EAAU,CAAA,EAExBD,EAAU,YACbE,GAAaD,EAAOD,CAAqF,EACzGG,GAAWF,EAAOD,CAAS,CACjC,CAGA,SAASG,GAAcF,EAAYD,EAAwC,CACzE,MAAMI,EAAQJ,EAAU,OAASH,EAC3BQ,EAAcL,EAAU,aAAeL,GACvCW,MAAc,IAGpB,SAASC,EAAMX,EAASY,EAAeC,EAAyBC,EAA2B,CACzF,MAAMC,EAAKC,EAAKR,EAAMR,CAAI,EAAGc,CAAI,EACjCG,EAAeF,EAAIL,CAAO,EAG1B,MAAMQ,GADcT,EAAYT,CAAI,GAAK,CAAA,GACZ,IAAI,CAACmB,EAAOC,IAAUT,EAAMQ,EAAOP,EAAQ,EAAGG,EAAI,GAAGD,CAAI,IAAIM,CAAK,EAAE,CAAC,EAElG,MAAO,CAAE,GAAAL,EAAI,KAAMf,EAAM,MAAAY,EAAO,SAAAC,EAAU,SAAAK,EAAU,YAAaA,EAAS,OAAS,EAAG,UAAW,EAAA,CACnG,CAEA,OAAOb,EAAM,IAAI,CAACL,EAAMoB,IAAUT,EAAMX,EAAM,EAAG,KAAM,OAAOoB,CAAK,CAAC,CAAC,CACvE,CAGA,SAASd,GACPD,EACAD,EACe,CACf,MAAMI,EAAQJ,EAAU,OAASH,EAC3BS,MAAc,IAGdW,EAAQhB,EAAM,IAAI,CAACL,EAAMoB,IAAU,CACvC,MAAML,EAAKC,EAAKR,EAAMR,CAAI,EAAG,OAAOoB,CAAK,CAAC,EAC1C,OAAAH,EAAeF,EAAIL,CAAO,EACnB,CAAE,GAAAK,EAAI,KAAMf,EAAM,MAAO,EAAG,SAAU,KAAM,SAAU,CAAA,EAAI,YAAa,GAAO,UAAW,EAAA,CAClG,CAAC,EAEKsB,EAAO,IAAI,IAAID,EAAM,IAAKE,GAAS,CAACA,EAAK,GAAIA,CAAI,CAAC,CAAC,EACnDC,EAAuB,CAAA,EAkB7B,GAfAH,EAAM,QAAQ,CAACE,EAAMH,IAAU,CAC7B,MAAMK,EAAcrB,EAAU,YAAYC,EAAMe,CAAK,CAAE,EACjDM,EAASD,GAAe,KAAO,OAAYH,EAAK,IAAI,OAAOG,CAAW,CAAC,EAE7E,GAAI,CAACC,EAAQ,CACXF,EAAM,KAAKD,CAAI,EACf,MACF,CACAA,EAAK,SAAWG,EAAO,GACvBA,EAAO,SAAS,KAAKH,CAAI,EACzBG,EAAO,YAAc,EACvB,CAAC,EAGeC,GAAUH,CAAK,IACfH,EAAM,OACpB,MAAM,IAAI,MAAM,+EAA+E,EAGjG,OAAOG,CACT,CAQA,SAASG,GAAaH,EAA8B,CAClD,MAAMI,EAAQ,CAAC,GAAGJ,CAAK,EAEvB,QAASJ,EAAQ,EAAGA,EAAQQ,EAAM,OAAQR,GAAS,EAAG,CACpD,MAAMG,EAAOK,EAAMR,CAAK,EACxB,UAAWD,KAASI,EAAK,SACvBJ,EAAM,MAAQI,EAAK,MAAQ,EAC3BK,EAAM,KAAKT,CAAK,CAEpB,CACA,OAAOS,EAAM,MACf,CAGA,SAASZ,EAAKa,EAA2CC,EAA0B,CACjF,OAAOD,GAAS,MAAQA,IAAU,GAAKC,EAAW,OAAOD,CAAK,CAChE,CAGA,SAASZ,EAAeF,EAAYL,EAA4B,CAC9D,GAAIA,EAAQ,IAAIK,CAAE,EAChB,MAAM,IAAI,MAAM,uCAAuCA,CAAE,4CAA4C,EAEvGL,EAAQ,IAAIK,CAAE,CAChB,CChHO,SAASgB,GAAcC,EAAelC,EAAgC,GAAmB,CAC9F,MAAMmC,EAASD,EAAO,OAAQE,GAAUA,EAAM,OAAS,CAAC,EACxD,GAAID,EAAO,SAAW,EAAG,MAAO,CAAA,EAEhC,MAAME,EAAYF,EAAO,OAAS,EAG5BZ,EAAyBY,EAAO,IAAI,CAACC,EAAOE,IAChDF,EAAM,IAAI,CAAClC,EAAMoB,KAAW,CAC1B,GAAIiB,GAAUrC,EAAMoC,EAAYhB,EAAOtB,EAAQ,KAAK,EACpD,KAAME,EAEN,MAAOmC,EAAYC,EACnB,SAAU,KACV,SAAU,CAAA,EACV,YAAa,GACb,UAAW,EAAA,EACX,CAAA,EAGJ,QAASA,EAAa,EAAGA,EAAaD,EAAWC,GAAc,EAAG,CAChE,MAAME,EAAUjB,EAAMe,CAAU,EAC1BG,EAAUlB,EAAMe,EAAa,CAAC,EAE9BI,EAAY,KAAK,KAAKF,EAAQ,OAASC,EAAQ,MAAM,EAE3DD,EAAQ,QAAQ,CAACf,EAAMH,IAAU,CAC/B,MAAMM,EAASa,EAAQ,KAAK,IAAI,KAAK,MAAMnB,EAAQoB,CAAS,EAAGD,EAAQ,OAAS,CAAC,CAAC,EAClFhB,EAAK,SAAWG,EAAO,GACvBA,EAAO,SAAS,KAAKH,CAAI,EACzBG,EAAO,YAAc,EACvB,CAAC,CACH,CAEA,OAAOL,EAAMc,CAAS,CACxB,CAEA,SAASE,GACPrC,EACAkC,EACAd,EACAZ,EACQ,CACR,MAAMiC,EAAMjC,EAAQA,EAAMR,EAAMkC,EAAOd,CAAK,EAAKpB,GAAmC,GACpF,OAAOyC,GAAO,MAAQA,IAAQ,GAAK,GAAGP,CAAK,IAAId,CAAK,GAAK,OAAOqB,CAAG,CACrE,CC7DO,SAASC,GAAkBlB,EAAsBmB,EAAkD,CACxG,GAAIA,EAAa,OAAS,EAAG,OAAOnB,EAEpC,SAASoB,EAAKrB,EAAgC,CAC5C,MAAMsB,EAAYF,EAAa,IAAIpB,EAAK,EAAE,EAC1C,MAAO,CACL,GAAGA,EACH,UAAAsB,EACA,SAAUA,EAAY,CAAA,EAAKtB,EAAK,SAAS,IAAIqB,CAAI,CAAA,CAErD,CAEA,OAAOpB,EAAM,IAAIoB,CAAI,CACvB,CAGO,SAASE,GAAgBH,EAAmC5B,EAAyB,CAC1F,MAAMgC,EAAO,IAAI,IAAIJ,CAAY,EACjC,OAAKI,EAAK,OAAOhC,CAAE,GAAGgC,EAAK,IAAIhC,CAAE,EAC1BgC,CACT,CC6BO,MAAMC,EAAuB,EAGvBC,EAA6B,GAQnC,SAASC,GACdC,EACArD,EAA8B,GACZ,CAClB,MAAMsD,EAAQD,GAAO,OAAS,CAAA,EACxBE,EAAW,KAAK,IAAIvD,EAAQ,MAAQkD,EAAsBI,EAAM,MAAM,EACtEE,EAAkBxD,EAAQ,iBAAmB,KAE7CyD,EAAyBH,EAAM,IAAI,CAACI,EAAMpC,IAAU,CACxD,MAAMqC,EAAeD,EAAK,eAAiB,GACrCE,EAAWF,EAAK,WAAa,GAEnC,MAAO,CACL,IAAK,OAAOA,EAAK,IAAM,QAAQpC,CAAK,EAAE,EACtC,KAAAoC,EACA,MAAOC,EAAeH,EAAkB,OAAOE,EAAK,OAAS,EAAE,EAC/D,SAAAE,EACA,QAAS,CAACA,EACV,SAAUF,EAAK,WAAa,GAC5B,aAAAC,EACA,QAAS,EAAA,CAEb,CAAC,EAED,QAASrC,EAAQgC,EAAM,OAAQhC,EAAQiC,EAAUjC,GAAS,EACxDmC,EAAK,KAAK,CACR,IAAK,SAASnC,CAAK,GACnB,KAAM,KACN,MAAO,GACP,SAAU,GACV,QAAS,GACT,SAAU,GACV,aAAc,GACd,QAAS,EAAA,CACV,EAGH,MAAO,CAAE,KAAAmC,EAAM,UAAWH,EAAM,KAAMI,GAASA,EAAK,WAAa,EAAI,CAAA,CACvE,CAWO,SAASG,GACdR,EACArD,EAAuD,GAC/C,CACR,MAAMsD,EAAQD,GAAO,OAAS,CAAA,EAG9B,OAFiB,KAAK,IAAIrD,EAAQ,MAAQkD,EAAsBI,EAAM,MAAM,GAEzDtD,EAAQ,WAAamD,EAC1C,CC3HA,SAASW,EAAMC,EAAuB,CACpC,OAAO,KAAK,MAAMA,EAAQ,GAAG,EAAI,GACnC,CAGA,SAASC,GAAWC,EAA+B,CACjD,OAAOA,IAAc,iBAAmBA,IAAc,eACxD,CAMO,SAASC,EACdC,EACAC,EACAH,EAC4B,CAC5B,MAAMI,EAAgBF,EAAO,EAAIA,EAAO,MAAQ,EAC1CG,EAAgBH,EAAO,EAAIA,EAAO,OAAS,EAC3CI,EAAgBH,EAAO,EAAIA,EAAO,MAAQ,EAC1CI,EAAgBJ,EAAO,EAAIA,EAAO,OAAS,EAEjD,OAAQH,EAAA,CACN,IAAK,gBACH,MAAO,CAAE,KAAM,CAAE,EAAGI,EAAe,EAAGF,EAAO,EAAIA,EAAO,MAAA,EAAU,GAAI,CAAE,EAAGI,EAAe,EAAGH,EAAO,EAAE,EACxG,IAAK,gBACH,MAAO,CAAE,KAAM,CAAE,EAAGC,EAAe,EAAGF,EAAO,CAAA,EAAK,GAAI,CAAE,EAAGI,EAAe,EAAGH,EAAO,EAAIA,EAAO,OAAO,EACxG,IAAK,gBACH,MAAO,CAAE,KAAM,CAAE,EAAGD,EAAO,EAAIA,EAAO,MAAO,EAAGG,CAAA,EAAiB,GAAI,CAAE,EAAGF,EAAO,EAAG,EAAGI,EAAc,EACvG,IAAK,gBACH,MAAO,CAAE,KAAM,CAAE,EAAGL,EAAO,EAAG,EAAGG,CAAA,EAAiB,GAAI,CAAE,EAAGF,EAAO,EAAIA,EAAO,MAAO,EAAGI,EAAc,CAAE,CAE7G,CAGO,SAASC,EAAUC,EAAaC,EAAWC,EAAkBX,EAA8B,CAChG,MAAMY,EAAKf,EAAMY,EAAK,CAAC,EACjBI,EAAKhB,EAAMY,EAAK,CAAC,EACjBK,EAAKjB,EAAMa,EAAG,CAAC,EACfK,EAAKlB,EAAMa,EAAG,CAAC,EAErB,GAAIC,IAAU,WACZ,MAAO,KAAKC,CAAE,IAAIC,CAAE,MAAMC,CAAE,IAAIC,CAAE,GAGpC,MAAMC,EAAWjB,GAAWC,CAAS,EAE/BiB,EAAOpB,GAAOe,EAAKE,GAAM,CAAC,EAC1BI,EAAOrB,GAAOgB,EAAKE,GAAM,CAAC,EAEhC,OAAIJ,IAAU,QACLK,EACH,KAAKJ,CAAE,IAAIC,CAAE,MAAMD,CAAE,IAAIM,CAAI,MAAMJ,CAAE,IAAII,CAAI,MAAMJ,CAAE,IAAIC,CAAE,GAC3D,KAAKH,CAAE,IAAIC,CAAE,MAAMI,CAAI,IAAIJ,CAAE,MAAMI,CAAI,IAAIF,CAAE,MAAMD,CAAE,IAAIC,CAAE,GAG1DC,EACH,KAAKJ,CAAE,IAAIC,CAAE,MAAMD,CAAE,IAAIM,CAAI,IAAIJ,CAAE,IAAII,CAAI,IAAIJ,CAAE,IAAIC,CAAE,GACvD,KAAKH,CAAE,IAAIC,CAAE,MAAMI,CAAI,IAAIJ,CAAE,IAAII,CAAI,IAAIF,CAAE,IAAID,CAAE,IAAIC,CAAE,EAC7D,CAGO,SAASI,EAAc7D,EAAwBvB,EAA2C,CAC/F,MAAMwB,EAAO,IAAI,IAAID,EAAM,IAAKE,GAAS,CAACA,EAAK,GAAIA,CAAI,CAAC,CAAC,EACnD4D,EAAyB,CAAA,EAE/B,UAAW5D,KAAQF,EAAO,CACxB,MAAMK,EAASH,EAAK,UAAY,KAAO,OAAYD,EAAK,IAAIC,EAAK,QAAQ,EACzE,GAAI,CAACG,EAAQ,SAEb,KAAM,CAAE,KAAA8C,EAAM,GAAAC,GAAOT,EAAWtC,EAAQH,EAAMzB,EAAQ,SAAS,EAC/DqF,EAAM,KAAK,CACT,GAAI,GAAGzD,EAAO,EAAE,KAAKH,EAAK,EAAE,GAC5B,OAAQG,EACR,OAAQH,EACR,KAAMgD,EAAUC,EAAMC,EAAI3E,EAAQ,UAAWA,EAAQ,SAAS,CAAA,CAC/D,CACH,CAEA,OAAOqF,CACT,CCnDA,SAASC,EAAUC,EAAmB9D,EAA2B,CAC/D,OAAO,OAAO8D,GAAS,WAAaA,EAAK9D,CAAI,EAAI8D,CACnD,CAcO,SAASC,EAAc9D,EAAsB1B,EAAyC,CAC3F,MAAMiF,EAAWjF,EAAQ,YAAc,iBAAmBA,EAAQ,YAAc,gBAC1EwB,MAAW,IAEXiE,EAAqB,CAAA,EAE3B,IAAIC,EAAS,EAEb,MAAMC,EAAuB,CAAA,EAE7B,SAASC,EAAMnE,EAA8B,CAC3C,MAAMoE,EAAQP,EAAOtF,EAAQ,UAAWyB,CAAI,EACtCqE,EAASR,EAAOtF,EAAQ,WAAYyB,CAAI,EACxCsE,EAAYd,EAAWa,EAASD,EAChCG,EAAaf,EAAWY,EAAQC,EAEtCH,EAAWlE,EAAK,KAAK,EAAI,KAAK,IAAIkE,EAAWlE,EAAK,KAAK,GAAK,EAAGsE,CAAS,EAExE,MAAME,EAAcP,EACdQ,EAAcT,EAAM,OAC1B,IAAIU,EAAST,EAEb,GAAIjE,EAAK,SAAS,OAAS,EAAG,CAC5B,MAAML,EAAWK,EAAK,SAAS,IAAImE,CAAK,EAClCQ,EAAQhF,EAAS,CAAC,EAClBiF,EAAOjF,EAASA,EAAS,OAAS,CAAC,EACnCkF,EAAcF,EAAM,OAASA,EAAM,WAAa,EAChDG,EAAaF,EAAK,OAASA,EAAK,WAAa,EAGnDF,GAFgBG,EAAcC,GAAc,EAE1BP,EAAa,EAI/B,MAAMQ,EAAWP,EAAcE,EAC/B,GAAIK,EAAW,EAAG,CAChB,QAASC,EAAIP,EAAaO,EAAIhB,EAAM,OAAQgB,GAAK,EAAGhB,EAAMgB,CAAC,EAAG,QAAUD,EACxEL,EAASF,CACX,CACF,CAEA,MAAMS,EAAuB,CAAE,MAAOjF,EAAK,MAAO,MAAO,EAAG,OAAA0E,EAAQ,UAAAJ,EAAW,WAAAC,CAAA,EAC/E,OAAAxE,EAAK,IAAIC,EAAK,GAAIiF,CAAS,EAC3BjB,EAAM,KAAKiB,CAAS,EAGpBhB,EAAS,KAAK,IAAIA,EAAQS,EAASH,EAAahG,EAAQ,UAAU,EAC3D0G,CACT,CAEAhF,EAAM,QAAQkE,CAAK,EAGnB,MAAMe,EAAyB,CAAA,EAC/B,IAAIC,EAAS,EACb,QAAS9F,EAAQ,EAAGA,EAAQ6E,EAAW,OAAQ7E,GAAS,EACtD6F,EAAa7F,CAAK,EAAI8F,EACtBA,IAAWjB,EAAW7E,CAAK,GAAK,GAAKd,EAAQ,SAG/C,IAAI6G,EAAe,EACnB,UAAWH,KAAajB,EAAO,CAC7B,MAAMqB,EAAYnB,EAAWe,EAAU,KAAK,GAAKA,EAAU,UAE3DA,EAAU,MAAQC,EAAaD,EAAU,KAAK,GAAMI,EAAYJ,EAAU,WAAa,EACvFG,EAAe,KAAK,IAAIA,EAAcH,EAAU,OAASA,EAAU,UAAU,CAC/E,CAEA,MAAMrE,EAAYsD,EAAW,OAAS,EAChCoB,EAAc1E,EAAY,EAAI,EAAIsE,EAAatE,CAAS,EAAKsD,EAAWtD,CAAS,EAEvF,MAAO,CAAE,KAAAb,EAAM,YAAAuF,EAAa,aAAAF,CAAA,CAC9B,CAGO,SAASG,EAAWtF,EAAqC,CAC9D,MAAMuF,EAAwB,CAAA,EAE9B,SAASnE,EAAKrB,EAAyB,CACrCwF,EAAO,KAAKxF,CAAI,EAChBA,EAAK,SAAS,QAAQqB,CAAI,CAC5B,CAEA,OAAApB,EAAM,QAAQoB,CAAI,EACXmE,CACT,CAMO,SAASC,GAAcxF,EAAsB1B,EAAkD,CACpG,MAAMmH,EAAWpH,EAAeC,CAAO,EAEvC,GAAI0B,EAAM,SAAW,EACnB,MAAO,CAAE,MAAO,CAAA,EAAI,MAAO,CAAA,EAAI,MAAO,EAAG,OAAQ,CAAA,EAGnD,MAAMuD,EAAWkC,EAAS,YAAc,iBAAmBA,EAAS,YAAc,gBAC5E,CAAE,KAAA3F,EAAM,YAAAuF,EAAa,aAAAF,GAAiBrB,EAAW9D,EAAOyF,CAAQ,EAEhEC,EAAWD,EAAS,YAAc,iBAAmBA,EAAS,YAAc,gBAE5E5F,EAAyByF,EAAQtF,CAAK,EAAE,IAAKD,GAAS,CAC1D,KAAM,CAAE,MAAA4F,EAAO,OAAAlB,EAAQ,UAAAJ,EAAW,WAAAC,GAAexE,EAAK,IAAIC,EAAK,EAAE,EAC3D6F,EAAgBF,EAAWL,EAAchB,EAAYsB,EAAQA,EAEnE,MAAO,CACL,GAAI5F,EAAK,GACT,KAAMA,EAAK,KACX,MAAOA,EAAK,MACZ,SAAUA,EAAK,SACf,EAAGwD,EAAWkB,EAASmB,EACvB,EAAGrC,EAAWqC,EAAgBnB,EAC9B,MAAOlB,EAAWe,EAAaD,EAC/B,OAAQd,EAAWc,EAAYC,EAC/B,YAAavE,EAAK,YAClB,UAAWA,EAAK,SAAA,CAEpB,CAAC,EAED,MAAO,CACL,MAAAF,EACA,MAAO6D,EAAW7D,EAAO4F,CAAQ,EACjC,MAAOlC,EAAW4B,EAAeE,EACjC,OAAQ9B,EAAW8B,EAAcF,CAAA,CAErC,CCxKO,MAAMU,EAAoC,CAAE,IAAK,GAAK,IAAK,CAAA,EAGrDC,EAAgC,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,CAAA,EAG3D,SAASC,EAAM1D,EAAe2D,EAAaC,EAAqB,CACrE,OAAO,KAAK,IAAI,KAAK,IAAI5D,EAAO2D,CAAG,EAAGC,CAAG,CAC3C,CAMO,SAASC,EACdC,EACAC,EACAC,EAAU,GACVC,EAAsBT,EACX,CACX,GAAIM,EAAQ,OAAS,GAAKA,EAAQ,QAAU,GAAKC,EAAS,OAAS,GAAKA,EAAS,QAAU,EACzF,MAAO,CAAE,GAAGN,CAAA,EAGd,MAAMS,EAAiB,KAAK,IAAIH,EAAS,MAAQC,EAAU,EAAG,CAAC,EACzDG,EAAkB,KAAK,IAAIJ,EAAS,OAASC,EAAU,EAAG,CAAC,EAE3DpF,EAAM,KAAK,IAAIsF,EAAiBJ,EAAQ,MAAOK,EAAkBL,EAAQ,OAAQ,CAAC,EAClFM,EAAQV,EAAM9E,EAAKqF,EAAO,IAAKA,EAAO,GAAG,EAE/C,MAAO,CACL,MAAAG,EACA,GAAIL,EAAS,MAAQD,EAAQ,MAAQM,GAAS,EAC9C,GAAIL,EAAS,OAASD,EAAQ,OAASM,GAAS,CAAA,CAEpD,CAGO,SAASC,EAAMC,EAAsBC,EAAYC,EAAuB,CAC7E,MAAO,CAAE,GAAGF,EAAW,EAAGA,EAAU,EAAIC,EAAI,EAAGD,EAAU,EAAIE,CAAA,CAC/D,CAMO,SAASC,EACdH,EACAI,EACAC,EACAV,EAAsBT,EACX,CACX,MAAMY,EAAQV,EAAMY,EAAU,MAAQI,EAAQT,EAAO,IAAKA,EAAO,GAAG,EACpE,GAAIG,IAAUE,EAAU,MAAO,OAAOA,EAGtC,MAAMM,GAAYD,EAAM,EAAIL,EAAU,GAAKA,EAAU,MAC/CO,GAAYF,EAAM,EAAIL,EAAU,GAAKA,EAAU,MAErD,MAAO,CAAE,MAAAF,EAAO,EAAGO,EAAM,EAAIC,EAAWR,EAAO,EAAGO,EAAM,EAAIE,EAAWT,CAAA,CACzE,CAGO,SAASU,GAAeR,EAA8B,CAC3D,MAAO,aAAaA,EAAU,CAAC,OAAOA,EAAU,CAAC,aAAaA,EAAU,KAAK,GAC/E,CC9DO,MAAMS,EAAY,IAoBlB,MAAMC,EAAkB,CAS7B,YAA6BC,EAAuB,CAAvB,KAAA,OAAAA,EAR7B,KAAA,UAAuB,CAAE,GAAGxB,CAAA,EAE5B,KAAQ,QAA8B,KAEtC,KAAiB,aAAe,IAEhC,KAAQ,kBAAoB,EA6F5B,KAAiB,cAAiByB,GAA8B,CACzD,KAAK,eAELA,EAAM,QAA+B,UAAU,kBAAkB,IAEtE,KAAK,SAAS,IAAIA,EAAM,UAAW,CAAE,EAAGA,EAAM,QAAS,EAAGA,EAAM,OAAA,CAAS,EACzE,KAAK,SAAS,oBAAoBA,EAAM,SAAS,GACnD,EAEA,KAAiB,cAAiBA,GAA8B,CAC9D,MAAMC,EAAW,KAAK,SAAS,IAAID,EAAM,SAAS,EAClD,GAAKC,EAIL,IAFA,KAAK,SAAS,IAAID,EAAM,UAAW,CAAE,EAAGA,EAAM,QAAS,EAAGA,EAAM,OAAA,CAAS,EAErE,KAAK,SAAS,OAAS,EAAG,CAC5B,KAAK,MAAA,EACL,MACF,CAEA,KAAK,IAAIb,EAAM,KAAK,UAAWa,EAAM,QAAUC,EAAS,EAAGD,EAAM,QAAUC,EAAS,CAAC,CAAC,EACxF,EAEA,KAAiB,YAAeD,GAA8B,CAC5D,KAAK,SAAS,OAAOA,EAAM,SAAS,EAChC,KAAK,SAAS,KAAO,SAAQ,kBAAoB,EACvD,EAEA,KAAiB,QAAWA,GAA4B,CACtD,GAAI,CAAC,KAAK,aAAc,OACxBA,EAAM,eAAA,EAEN,MAAMR,EAASQ,EAAM,OAAS,EAAIH,EAAY,EAAIA,EAClD,KAAK,IAAIN,EAAY,KAAK,UAAWC,EAAQ,KAAK,aAAaQ,CAAK,EAAG,KAAK,MAAM,CAAC,CACrF,CA7HqD,CAGrD,OAAOE,EAA4B,CACjC,KAAK,OAAA,EACL,KAAK,QAAUA,EAEfA,EAAQ,iBAAiB,cAAe,KAAK,aAAa,EAC1DA,EAAQ,iBAAiB,cAAe,KAAK,aAAa,EAC1DA,EAAQ,iBAAiB,YAAa,KAAK,WAAW,EACtDA,EAAQ,iBAAiB,gBAAiB,KAAK,WAAW,EAE1DA,EAAQ,iBAAiB,QAAS,KAAK,QAAS,CAAE,QAAS,GAAO,CACpE,CAGA,QAAe,CACb,MAAMA,EAAU,KAAK,QAChBA,IAELA,EAAQ,oBAAoB,cAAe,KAAK,aAAa,EAC7DA,EAAQ,oBAAoB,cAAe,KAAK,aAAa,EAC7DA,EAAQ,oBAAoB,YAAa,KAAK,WAAW,EACzDA,EAAQ,oBAAoB,gBAAiB,KAAK,WAAW,EAC7DA,EAAQ,oBAAoB,QAAS,KAAK,OAAO,EAEjD,KAAK,QAAU,KACf,KAAK,SAAS,MAAA,EACd,KAAK,kBAAoB,EAC3B,CAGA,IAAItB,EAAeE,EAAU,GAAU,CACrC,MAAMoB,EAAU,KAAK,QAChBA,GAEL,KAAK,IACHvB,EACEC,EACA,CAAE,MAAOsB,EAAQ,YAAa,OAAQA,EAAQ,YAAA,EAC9CpB,EACA,KAAK,MAAA,CACP,CAEJ,CAEA,QAAe,CACb,KAAK,aAAae,CAAS,CAC7B,CAEA,SAAgB,CACd,KAAK,aAAa,EAAIA,CAAS,CACjC,CAEA,OAAc,CACZ,KAAK,IAAI,CAAE,GAAGtB,EAAoB,CACpC,CAGA,IAAIa,EAA4B,CAC9B,KAAK,UAAY,CAAE,GAAGA,CAAA,EACtB,KAAK,OAAO,SAAS,KAAK,SAAS,CACrC,CAEA,IAAY,QAAsB,CAChC,OAAO,KAAK,OAAO,SAAA,GAAcd,CACnC,CAEQ,YAAsB,CAC5B,OAAO,KAAK,OAAO,WAAA,GAAgB,EACrC,CAEQ,YAAsB,CAC5B,OAAO,KAAK,OAAO,WAAA,GAAgB,EACrC,CAGQ,aAAamB,EAA6C,CAChE,MAAMU,EAAO,KAAK,SAAS,sBAAA,EAC3B,MAAO,CAAE,EAAGV,EAAM,SAAWU,GAAM,MAAQ,GAAI,EAAGV,EAAM,SAAWU,GAAM,KAAO,EAAA,CAClF,CAEQ,aAAaX,EAAsB,CACzC,MAAMU,EAAU,KAAK,QACfE,EAAS,CAAE,GAAIF,GAAS,aAAe,GAAK,EAAG,GAAIA,GAAS,cAAgB,GAAK,CAAA,EACvF,KAAK,IAAIX,EAAY,KAAK,UAAWC,EAAQY,EAAQ,KAAK,MAAM,CAAC,CACnE,CA0CQ,OAAc,CACpB,GAAI,CAAC,KAAK,aAAc,OAExB,KAAM,CAACjD,EAAOkD,CAAM,EAAI,CAAC,GAAG,KAAK,SAAS,QAAQ,EAClD,GAAI,CAAClD,GAAS,CAACkD,EAAQ,OAEvB,MAAMC,EAAW,KAAK,MAAMnD,EAAM,EAAIkD,EAAO,EAAGlD,EAAM,EAAIkD,EAAO,CAAC,EAElE,GAAI,KAAK,kBAAoB,GAAKC,EAAW,EAAG,CAC9C,MAAMC,EAAS,KAAK,aAAa,CAC/B,SAAUpD,EAAM,EAAIkD,EAAO,GAAK,EAChC,SAAUlD,EAAM,EAAIkD,EAAO,GAAK,CAAA,CACjC,EACD,KAAK,IAAId,EAAY,KAAK,UAAWe,EAAW,KAAK,kBAAmBC,EAAQ,KAAK,MAAM,CAAC,CAC9F,CAEA,KAAK,kBAAoBD,CAC3B,CACF,CCjLO,SAASE,GAAaC,EAAazI,EAAK,mBAA0B,CAEvE,GADI,OAAO,SAAa,KACpB,SAAS,eAAeA,CAAE,EAAG,OAEjC,MAAM2D,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,GAAK3D,EACX2D,EAAM,YAAc8E,EAEpB,SAAS,KAAK,YAAY9E,CAAK,CACjC,CCGO,MAAM+E,EAAmB,IAGnBC,EAAuB,IAGpC,SAASC,EAAWC,EAASC,EAAkB,CAC7C,OACED,EAAE,GAAKC,EAAE,EAAIA,EAAE,OAASD,EAAE,EAAIA,EAAE,OAASC,EAAE,GAAKD,EAAE,GAAKC,EAAE,EAAIA,EAAE,QAAUD,EAAE,EAAIA,EAAE,QAAUC,EAAE,CAEjG,CAGA,SAASC,GAAYC,EAA2B,CAC9C,KAAM,CAAE,OAAA9F,EAAQ,OAAAC,CAAA,EAAW6F,EACrBC,EAAI,KAAK,IAAI/F,EAAO,EAAGC,EAAO,CAAC,EAC/B+F,EAAI,KAAK,IAAIhG,EAAO,EAAGC,EAAO,CAAC,EAErC,MAAO,CACL,EAAA8F,EACA,EAAAC,EACA,MAAO,KAAK,IAAIhG,EAAO,EAAIA,EAAO,MAAOC,EAAO,EAAIA,EAAO,KAAK,EAAI8F,EACpE,OAAQ,KAAK,IAAI/F,EAAO,EAAIA,EAAO,OAAQC,EAAO,EAAIA,EAAO,MAAM,EAAI+F,CAAA,CAE3E,CAGO,SAASC,EAAWhB,EAAYiB,EAAkB,CACvD,MAAO,CAAE,EAAGjB,EAAK,EAAIiB,EAAI,EAAGjB,EAAK,EAAIiB,EAAI,MAAOjB,EAAK,MAAQiB,EAAK,EAAG,OAAQjB,EAAK,OAASiB,EAAK,CAAA,CAClG,CAQO,SAASC,GACdC,EACAC,EACAC,EAAmBd,EACR,CACX,GAAI,CAACa,GAAQD,EAAO,MAAM,OAASX,EAAsB,OAAOW,EAGhE,GAAIC,EAAK,OAAS,GAAKA,EAAK,QAAU,EACpC,MAAO,CAAE,MAAO,GAAI,MAAO,CAAA,EAAI,MAAOD,EAAO,MAAO,OAAQA,EAAO,MAAA,EAGrE,MAAMG,EAAON,EAAWI,EAAMC,CAAQ,EAChClJ,EAAyB,CAAA,EACzB8D,EAAyB,CAAA,EAE/B,UAAW5D,KAAQ8I,EAAO,MACpBV,EAAWa,EAAMjJ,CAAI,GAAGF,EAAM,KAAKE,CAAI,EAE7C,UAAWwI,KAAQM,EAAO,MACpBV,EAAWa,EAAMV,GAASC,CAAI,CAAC,GAAG5E,EAAM,KAAK4E,CAAI,EAGvD,MAAO,CAAE,MAAA1I,EAAO,MAAA8D,EAAO,MAAOkF,EAAO,MAAO,OAAQA,EAAO,MAAA,CAC7D,CASO,SAASI,IAAmC,CACjD,OAAI,OAAO,OAAW,IAAoB,KAEnC,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,OAAO,WAAY,OAAQ,OAAO,WAAA,CAChE,CASO,SAASC,GAAmBC,EAAoC1C,EAA4B,CACjG,GAAI,CAAC0C,GAAU,OAAO,OAAW,KAAe1C,GAAS,EAAG,OAAO,KAEnE,MAAM2C,EAAMD,EAAO,sBAAA,EACbE,EAAO,KAAK,IAAID,EAAI,KAAM,CAAC,EAC3BE,EAAM,KAAK,IAAIF,EAAI,IAAK,CAAC,EACzBG,EAAQ,KAAK,IAAIH,EAAI,MAAO,OAAO,UAAU,EAC7CI,EAAS,KAAK,IAAIJ,EAAI,OAAQ,OAAO,WAAW,EAGtD,OAAIG,GAASF,GAAQG,GAAUF,EAAY,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAG,OAAQ,CAAA,EAEpE,CACL,GAAID,EAAOD,EAAI,MAAQ3C,EACvB,GAAI6C,EAAMF,EAAI,KAAO3C,EACrB,OAAQ8C,EAAQF,GAAQ5C,EACxB,QAAS+C,EAASF,GAAO7C,CAAA,CAE7B"}
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}\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 * с `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 * Подписка на медиавыражение — чтобы компоненты не писали работу с\n * `matchMedia` дважды и одинаково вели себя на сервере.\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","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/** Строит атрибут `d` для `<path>`: прямая, «ступенька» или кривая Безье. */\nexport function buildPath(from: Point, to: Point, style: LinkStyle, direction: Direction): string {\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 if (style === 'elbow') {\n return vertical\n ? `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`\n : `M ${x1} ${y1} L ${midX} ${y1} L ${midX} ${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/** Для каждого узла с родителем создаёт линию «родитель → ребёнок». */\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\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 })\n }\n\n return links\n}\n","import { resolveOptions } from './defaults'\nimport { buildLinks } from './links'\nimport type { 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/**\n * Считает место каждого узла.\n *\n * Правила всего два, и их легко проверить руками:\n * - лист встаёт сразу за предыдущим: «конец соседа + siblingGap»;\n * - родитель встаёт ровно по центру между первым и последним ребёнком.\n *\n * Карточки могут быть разного размера, поэтому позиции считаются в пикселях,\n * а не в номерах колонок: следующий узел всегда начинается там, где кончился\n * предыдущий. Уровень занимает столько, сколько занимает самая крупная\n * карточка на нём, а карточки поменьше центруются внутри этой полосы.\n */\nexport function placeNodes<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 /** Те же места, но по порядку размещения — нужно, чтобы двигать ветку целиком. */\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(cursor, across + acrossSize + options.siblingGap)\n return placement\n }\n\n roots.forEach(place)\n\n // Второй проход: толщина уровней уже известна, можно расставить их друг за другом.\n const levelOffsets: number[] = []\n let offset = 0\n for (let depth = 0; depth < levelSizes.length; depth += 1) {\n levelOffsets[depth] = offset\n offset += (levelSizes[depth] ?? 0) + options.levelGap\n }\n\n let acrossExtent = 0\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 { byId, alongExtent, acrossExtent }\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","findScrollParent","element","overflowX","smoothScrollLeft","left","offsetWithin","container","TREE_NODE_SELECTOR","nodesInColumn","offset","origin","rect","horizontalBox","MOBILE_MEDIA_QUERY","watchMedia","query","onChange","media","listener","isVertical","direction","getAnchors","source","target","sourceCenterX","sourceCenterY","targetCenterX","targetCenterY","buildPath","from","to","style","x1","y1","x2","y2","vertical","midX","midY","buildLinks","links","sizeOf","size","placeNodes","order","cursor","levelSizes","place","height","alongSize","acrossSize","branchStart","branchIndex","across","first","last","firstCenter","lastCenter","overflow","i","placement","levelOffsets","acrossExtent","levelSize","alongExtent","flatten","result","layoutTree","resolved","mirrored","along","alongPosition","ZOOM_STEP","PanZoomController","params","event","previous","center","second","distance","middle","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,6 +5,9 @@ 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, roundScrollLeft, type BracketRound, type BracketRoundColumn, type BracketRoundsModel, type BracketRoundsOptions, type RoundScrollBox, } from './rounds';
9
+ export { findScrollParent, smoothScrollLeft, offsetWithin, nodesInColumn, horizontalBox, TREE_NODE_SELECTOR, } from './scroll';
10
+ export { watchMedia, MOBILE_MEDIA_QUERY } from './media';
8
11
  export { layoutTree, placeNodes, flatten, type Placement, type Placements } from './layout';
9
12
  export { buildLinks, buildPath, getAnchors } from './links';
10
13
  export { PanZoomController, ZOOM_STEP, type PanZoomParams } from './panZoom';