@bigplay/tree-view 0.3.0 → 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.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),r=require("@bigplay/tree-view-core"),I={class:"tree-view-card"},V={class:"tree-view-card__label"},D=["aria-label"],E=e.defineComponent({__name:"TreeNodeCard",props:{label:{},hasChildren:{type:Boolean},collapsed:{type:Boolean}},emits:["toggle"],setup(o,{emit:c}){const n=c;return(t,l)=>(e.openBlock(),e.createElementBlock("div",I,[e.createElementVNode("span",V,e.toDisplayString(o.label),1),o.hasChildren?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"tree-view-card__toggle","data-tv-no-pan":"","aria-label":o.collapsed?"Развернуть":"Свернуть",onClick:l[0]||(l[0]=e.withModifiers(v=>n("toggle"),["stop"]))},e.toDisplayString(o.collapsed?"+":"−"),9,D)):e.createCommentVNode("",!0)]))}});function z(o){const c=e.ref({...r.IDENTITY_TRANSFORM}),n=new r.PanZoomController({pannable:o.pannable,zoomable:o.zoomable,limits:o.limits,onChange:t=>{c.value=t}});return e.watch(o.element,(t,l,v)=>{t&&(n.attach(t),v(()=>n.detach()))},{immediate:!0}),e.onBeforeUnmount(()=>n.detach()),{transform:c,fit:(t,l)=>n.fit(t,l),zoomIn:()=>n.zoomIn(),zoomOut:()=>n.zoomOut(),reset:()=>n.reset(),setTransform:t=>n.set(t)}}function $(o){const c=e.ref(new Set),n=e.computed(()=>r.toTree(o.data(),o.accessors())),t=e.computed(()=>r.applyCollapsed(n.value,c.value)),l=e.computed(()=>r.layoutTree(t.value,o.options()));function v(f){return c.value.has(f)}function b(f){c.value=r.toggleCollapsed(c.value,f)}function i(){c.value=new Set}return{layout:l,isCollapsed:v,toggle:b,expandAll:i}}function P(o,c,n){const t=e.ref(n()?r.estimateVisibleRect():null);function l(){t.value=n()?r.measureVisibleRect(o.value,c()):null}return e.onMounted(()=>{l(),window.addEventListener("scroll",l,{passive:!0,capture:!0}),window.addEventListener("resize",l,{passive:!0})}),e.onBeforeUnmount(()=>{window.removeEventListener("scroll",l,{capture:!0}),window.removeEventListener("resize",l)}),{rect:t,update:l}}const R=["width","height"],F=["d"],A=["onClick"],L=e.defineComponent({__name:"TreeView",props:{data:{},options:{default:void 0},getId:{type:Function,default:void 0},getChildren:{type:Function,default:void 0},getParentId:{type:Function,default:void 0},collapsible:{type:Boolean,default:!1},pannable:{type:Boolean,default:!0},zoomable:{type:Boolean,default:!0},fitOnMount:{type:Boolean,default:!0},size:{default:"fill"},scaleLimits:{default:void 0},nodeClass:{type:[String,Function],default:void 0},virtualize:{type:Boolean,default:!0},overscan:{default:r.DEFAULT_OVERSCAN}},emits:["node-click","toggle","transform"],setup(o,{expose:c,emit:n}){const t=o,l=n,v=e.ref(null),b=e.ref(null),{layout:i,isCollapsed:f,toggle:s,expandAll:k}=$({data:()=>t.data,options:()=>t.options,accessors:()=>({getId:t.getId,getChildren:t.getChildren,getParentId:t.getParentId})}),p=z({element:v,pannable:()=>t.pannable,zoomable:()=>t.zoomable,limits:()=>t.scaleLimits??r.DEFAULT_SCALE_LIMITS}),w=P(b,()=>p.transform.value.scale,()=>t.virtualize),B=e.computed(()=>t.virtualize?r.visibleLayout(i.value,w.rect.value,t.overscan):i.value),u=e.computed(()=>({width:`${i.value.width}px`,height:`${i.value.height}px`,transform:r.toCssTransform(p.transform.value)})),_=e.computed(()=>{if(t.size!=="content")return;const{scale:a}=p.transform.value;return{width:`${i.value.width*a}px`,height:`${i.value.height*a}px`,overflow:"visible"}});function m(a){return{left:`${a.x}px`,top:`${a.y}px`,width:`${a.width}px`,height:`${a.height}px`}}function y(a){return typeof t.nodeClass=="function"?t.nodeClass(a):t.nodeClass}function x(a){const g=a.data;return g?.name??g?.title??g?.label??a.id}function h(a){s(a.id),l("toggle",a,f(a.id))}function C(a,g){l("node-click",a,g),t.collapsible&&a.hasChildren&&h(a)}function T(a){p.fit({width:i.value.width,height:i.value.height},a)}return e.onMounted(()=>{t.fitOnMount&&e.nextTick(()=>T())}),e.watch(()=>p.transform.value,a=>{w.update(),l("transform",a)}),e.watch(i,()=>{e.nextTick(()=>w.update())}),c({fit:T,zoomIn:p.zoomIn,zoomOut:p.zoomOut,resetTransform:p.reset,setTransform:p.setTransform,expandAll:k,toggle:s,layout:i,transform:p.transform}),(a,g)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:v,class:e.normalizeClass(["tree-view",{"tree-view--pannable":o.pannable,"tree-view--content":o.size==="content"}]),style:e.normalizeStyle(_.value)},[e.createElementVNode("div",{ref_key:"canvasElement",ref:b,class:"tree-view__canvas",style:e.normalizeStyle(u.value)},[(e.openBlock(),e.createElementBlock("svg",{class:"tree-view__links",width:e.unref(i).width,height:e.unref(i).height},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(B.value.links,d=>e.renderSlot(a.$slots,"link",{link:d},()=>[e.createElementVNode("path",{class:"tree-view__link",d:d.path},null,8,F)],void 0,d.id)),128))],8,R)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(B.value.nodes,d=>(e.openBlock(),e.createElementBlock("div",{key:d.id,class:e.normalizeClass(["tree-view__node",y(d)]),style:e.normalizeStyle(m(d)),onClick:S=>C(d,S)},[e.renderSlot(a.$slots,"node",{node:d,data:d.data,depth:d.depth,collapsed:d.collapsed,hasChildren:d.hasChildren,toggle:()=>h(d)},()=>[e.createVNode(E,{label:x(d),"has-children":d.hasChildren,collapsed:d.collapsed,onToggle:S=>h(d)},null,8,["label","has-children","collapsed","onToggle"])])],14,A))),128))],4)],6))}}),j={key:0,class:"tv-bracket__hint"},U=["title","onClick","onMouseenter"],W={key:0,class:"tv-bracket__tooltip"},H={class:"tv-bracket__name"},Q={class:"tv-bracket__score"},M=e.defineComponent({__name:"BracketCard",props:{match:{},rows:{default:void 0},myTeamHint:{default:"Нажми для перехода в матч 👇"},techDefeatLabel:{default:"ТП"},techDefeatHint:{default:"Техническое поражение"}},emits:["select"],setup(o,{emit:c}){const n=o,t=c,l=e.ref(null),v=e.computed(()=>r.buildBracketCard(n.match,{rows:n.rows,techDefeatLabel:n.techDefeatLabel}));function b(i){i.team&&t("select",i.team,i)}return(i,f)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["tv-bracket",{"tv-bracket--my-team":v.value.hasMyTeam}])},[v.value.hasMyTeam&&o.myTeamHint?(e.openBlock(),e.createElementBlock("div",j,e.toDisplayString(o.myTeamHint),1)):e.createCommentVNode("",!0),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(v.value.rows,s=>(e.openBlock(),e.createElementBlock("div",{key:s.key,class:e.normalizeClass(["tv-bracket__row",{"tv-bracket__row--winner":s.isWinner,"tv-bracket__row--loser":s.isLoser&&!s.isEmpty,"tv-bracket__row--my":s.isMyTeam,"tv-bracket__row--empty":s.isEmpty}]),title:s.isMyTeam?"Моя команда":void 0,onClick:k=>b(s),onMouseenter:k=>l.value=s.key,onMouseleave:f[0]||(f[0]=k=>l.value=null)},[s.team?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[s.isTechDefeat&&l.value===s.key?(e.openBlock(),e.createElementBlock("span",W,e.toDisplayString(o.techDefeatHint),1)):e.createCommentVNode("",!0),e.createElementVNode("span",H,e.toDisplayString(s.team.name),1),e.createElementVNode("span",Q,e.toDisplayString(s.score),1)],64)):e.createCommentVNode("",!0)],42,U))),128))],2))}}),Y=["onClick"],N=e.defineComponent({__name:"BracketRounds",props:{rounds:{},nodeWidth:{default:180},levelGap:{default:60},isShow:{type:Boolean,default:!0},scrollOnClick:{type:Boolean,default:void 0},mobileQuery:{default:r.MOBILE_MEDIA_QUERY}},emits:["select"],setup(o,{emit:c}){const n=o,t=c,l=e.ref(null),v=e.ref(null),b=e.ref(!1);let i=null;e.onMounted(()=>{i=r.watchMedia(n.mobileQuery,u=>{b.value=u})}),e.onBeforeUnmount(()=>i?.());const f=e.computed(()=>n.isShow&&(n.scrollOnClick??b.value)),s=e.computed(()=>r.buildBracketRounds(n.rounds,{nodeWidth:n.nodeWidth,levelGap:n.levelGap})),k=e.computed(()=>({width:`${s.value.width}px`})),p=e.computed(()=>n.isShow?{paddingLeft:`${s.value.offset}px`}:void 0);function w(u){const _=v.value,m=_?r.nodesInColumn(_,u.index,u.width,s.value.offset):[],y=m[Math.floor(m.length/2)];if(y&&r.scrollToNode(y))return;const x=l.value,h=r.findScrollParent(x);if(!x||!h)return;const C=r.offsetWithin(x,h)+u.index*u.width;r.smoothScrollLeft(h,r.roundScrollLeft({left:C,width:u.width,viewport:h.clientWidth,scrollWidth:h.scrollWidth}))}function B(u){f.value&&(t("select",u),w(u))}return(u,_)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:l,class:"tv-rounds"},[o.isShow?(e.openBlock(),e.createElementBlock("div",{key:0,class:"tv-rounds__bar",style:e.normalizeStyle(k.value)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(s.value.columns,m=>(e.openBlock(),e.createElementBlock("div",{key:m.key,class:"tv-rounds__item",style:e.normalizeStyle({width:`${m.width}px`}),onClick:y=>B(m)},[e.renderSlot(u.$slots,"round",{round:m},()=>[e.createTextVNode(e.toDisplayString(m.name),1)])],12,Y))),128))],4)):e.createCommentVNode("",!0),e.createElementVNode("div",{ref_key:"contentElement",ref:v,class:"tv-rounds__content",style:e.normalizeStyle(p.value)},[e.renderSlot(u.$slots,"default")],4)],512))}}),Z=".tree-view{--tv-link-color: #cbd5e1;--tv-link-width: 2px;--tv-node-bg: #ffffff;--tv-node-color: #0f172a;--tv-node-border: 1px solid #e2e8f0;--tv-node-radius: 10px;--tv-node-shadow: 0 1px 2px rgb(15 23 42 / 8%);--tv-node-padding: 8px 12px;--tv-accent: #2563eb;position:relative;width:100%;height:100%;overflow:hidden;touch-action:none}.tree-view--pannable{cursor:grab}.tree-view--content{width:auto;height:auto;overflow:visible}.tree-view__canvas{position:absolute;top:0;left:0;transform-origin:0 0}.tree-view__links{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}.tree-view__link{fill:none;stroke:var(--tv-link-color);stroke-width:var(--tv-link-width)}.tree-view__node{position:absolute;box-sizing:border-box;display:flex;align-items:stretch;justify-content:stretch}.tree-view-card{box-sizing:border-box;display:flex;gap:8px;align-items:center;justify-content:space-between;width:100%;height:100%;padding:var(--tv-node-padding);overflow:hidden;color:var(--tv-node-color);background:var(--tv-node-bg);border:var(--tv-node-border);border-radius:var(--tv-node-radius);box-shadow:var(--tv-node-shadow)}.tree-view-card__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-view-card__toggle{flex:none;width:20px;height:20px;padding:0;font:inherit;line-height:1;color:var(--tv-accent);cursor:pointer;background:none;border:none}.tv-bracket{--tv-bracket-winner-bg: #0427c4;--tv-bracket-winner-color: #ffffff;--tv-bracket-loser-bg: #e5e5e5;--tv-bracket-loser-color: #7d7d8d;--tv-bracket-my-bg: #00146c;--tv-bracket-my-color: #ffffff;--tv-bracket-radius: 6px;position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;font-size:12px}.tv-bracket--my-team{border-radius:var(--tv-bracket-radius);outline:2px solid var(--tv-bracket-my-bg)}.tv-bracket__hint{position:absolute;right:0;bottom:100%;left:0;padding:3px 8px;overflow:hidden;font-size:11px;font-weight:600;line-height:1.2;color:var(--tv-bracket-my-color);text-align:center;text-overflow:ellipsis;white-space:nowrap;background:var(--tv-bracket-my-bg);border-radius:var(--tv-bracket-radius) var(--tv-bracket-radius) 0 0}.tv-bracket__row{position:relative;box-sizing:border-box;display:flex;flex:1 1 0;align-items:center;justify-content:space-between;min-height:0;padding:4px 8px;cursor:pointer;transition:all 75ms}.tv-bracket__row--winner{font-weight:700;color:var(--tv-bracket-winner-color);background:var(--tv-bracket-winner-bg)}.tv-bracket__row--loser{color:var(--tv-bracket-loser-color);background:var(--tv-bracket-loser-bg)}.tv-bracket__row--my{color:var(--tv-bracket-my-color);background:var(--tv-bracket-my-bg)}.tv-bracket__row--empty{cursor:default;background:var(--tv-bracket-loser-bg)}.tv-bracket__name{max-width:120px;overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tv-bracket__score{display:flex;flex:none;align-items:center;justify-content:center;width:20px;height:20px;font-weight:700;font-variant-numeric:tabular-nums}.tv-bracket__tooltip{position:absolute;top:-26px;left:6px;z-index:5000;padding:4px 8px;font-weight:400;color:var(--tv-bracket-loser-color);white-space:nowrap;background:#fff;border:1px solid #d1d5db;border-radius:4px;box-shadow:0 4px 10px #0f172a26}.tv-rounds{--tv-rounds-bg: #f5f5f7;--tv-rounds-color: #9ca3af;--tv-rounds-border: 2px solid #e5e7eb;--tv-rounds-gap: 20px;display:flex;flex-direction:column;width:max-content;min-width:100%}.tv-rounds__bar{display:flex;min-width:100%;margin-bottom:var(--tv-rounds-gap);background:var(--tv-rounds-bg);border-bottom:var(--tv-rounds-border)}.tv-rounds__item{flex:none;box-sizing:border-box;padding:10px 20px;font:inherit;font-size:14px;font-weight:600;color:var(--tv-rounds-color);text-align:center;text-transform:uppercase;white-space:nowrap;background:none;border:none;border-right:var(--tv-rounds-border)}";r.injectStyles(Z);const O={install(o){o.component("TreeView",L),o.component("TreeNodeCard",E),o.component("BracketCard",M),o.component("BracketRounds",N)}};Object.defineProperty(exports,"DEFAULT_OPTIONS",{enumerable:!0,get:()=>r.DEFAULT_OPTIONS});Object.defineProperty(exports,"MOBILE_MEDIA_QUERY",{enumerable:!0,get:()=>r.MOBILE_MEDIA_QUERY});Object.defineProperty(exports,"bracketCardHeight",{enumerable:!0,get:()=>r.bracketCardHeight});Object.defineProperty(exports,"buildBracketCard",{enumerable:!0,get:()=>r.buildBracketCard});Object.defineProperty(exports,"buildBracketRounds",{enumerable:!0,get:()=>r.buildBracketRounds});exports.BracketCard=M;exports.BracketRounds=N;exports.TreeNodeCard=E;exports.TreeView=L;exports.TreeViewPlugin=O;exports.default=O;exports.usePanZoom=z;exports.useTreeLayout=$;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),r=require("@bigplay/tree-view-core"),N={class:"tree-view-card"},V={class:"tree-view-card__label"},D=["aria-label"],C=e.defineComponent({__name:"TreeNodeCard",props:{label:{},hasChildren:{type:Boolean},collapsed:{type:Boolean}},emits:["toggle"],setup(o,{emit:c}){const n=c;return(t,l)=>(e.openBlock(),e.createElementBlock("div",N,[e.createElementVNode("span",V,e.toDisplayString(o.label),1),o.hasChildren?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"tree-view-card__toggle","data-tv-no-pan":"","aria-label":o.collapsed?"Развернуть":"Свернуть",onClick:l[0]||(l[0]=e.withModifiers(v=>n("toggle"),["stop"]))},e.toDisplayString(o.collapsed?"+":"−"),9,D)):e.createCommentVNode("",!0)]))}});function S(o){const c=e.ref({...r.IDENTITY_TRANSFORM}),n=new r.PanZoomController({pannable:o.pannable,zoomable:o.zoomable,limits:o.limits,onChange:t=>{c.value=t}});return e.watch(o.element,(t,l,v)=>{t&&(n.attach(t),v(()=>n.detach()))},{immediate:!0}),e.onBeforeUnmount(()=>n.detach()),{transform:c,fit:(t,l)=>n.fit(t,l),zoomIn:()=>n.zoomIn(),zoomOut:()=>n.zoomOut(),reset:()=>n.reset(),setTransform:t=>n.set(t)}}function z(o){const c=e.ref(new Set),n=e.computed(()=>r.toTree(o.data(),o.accessors())),t=e.computed(()=>r.applyCollapsed(n.value,c.value)),l=e.computed(()=>r.layoutTree(t.value,o.options()));function v(m){return c.value.has(m)}function b(m){c.value=r.toggleCollapsed(c.value,m)}function i(){c.value=new Set}return{layout:l,isCollapsed:v,toggle:b,expandAll:i}}function P(o,c,n){const t=e.ref(n()?r.estimateVisibleRect():null);function l(){t.value=n()?r.measureVisibleRect(o.value,c()):null}return e.onMounted(()=>{l(),window.addEventListener("scroll",l,{passive:!0,capture:!0}),window.addEventListener("resize",l,{passive:!0})}),e.onBeforeUnmount(()=>{window.removeEventListener("scroll",l,{capture:!0}),window.removeEventListener("resize",l)}),{rect:t,update:l}}const R=["width","height"],F=["d"],A=["onClick"],$=e.defineComponent({__name:"TreeView",props:{data:{},options:{default:void 0},getId:{type:Function,default:void 0},getChildren:{type:Function,default:void 0},getParentId:{type:Function,default:void 0},collapsible:{type:Boolean,default:!1},pannable:{type:Boolean,default:!0},zoomable:{type:Boolean,default:!0},fitOnMount:{type:Boolean,default:!0},size:{default:"fill"},scaleLimits:{default:void 0},nodeClass:{type:[String,Function],default:void 0},virtualize:{type:Boolean,default:!0},overscan:{default:r.DEFAULT_OVERSCAN}},emits:["node-click","toggle","transform"],setup(o,{expose:c,emit:n}){const t=o,l=n,v=e.ref(null),b=e.ref(null),{layout:i,isCollapsed:m,toggle:s,expandAll:h}=z({data:()=>t.data,options:()=>t.options,accessors:()=>({getId:t.getId,getChildren:t.getChildren,getParentId:t.getParentId})}),f=S({element:v,pannable:()=>t.pannable,zoomable:()=>t.zoomable,limits:()=>t.scaleLimits??r.DEFAULT_SCALE_LIMITS}),w=P(b,()=>f.transform.value.scale,()=>t.virtualize),x=e.computed(()=>t.virtualize?r.visibleLayout(i.value,w.rect.value,t.overscan):i.value),u=e.computed(()=>({width:`${i.value.width}px`,height:`${i.value.height}px`,transform:r.toCssTransform(f.transform.value)})),k=e.computed(()=>{if(t.size!=="content")return;const{scale:a}=f.transform.value;return{width:`${i.value.width*a}px`,height:`${i.value.height*a}px`,overflow:"visible"}});function p(a){return{left:`${a.x}px`,top:`${a.y}px`,width:`${a.width}px`,height:`${a.height}px`}}function _(a){return typeof t.nodeClass=="function"?t.nodeClass(a):t.nodeClass}function B(a){const g=a.data;return g?.name??g?.title??g?.label??a.id}function y(a){s(a.id),l("toggle",a,m(a.id))}function M(a,g){l("node-click",a,g),t.collapsible&&a.hasChildren&&y(a)}function E(a){f.fit({width:i.value.width,height:i.value.height},a)}return e.onMounted(()=>{t.fitOnMount&&e.nextTick(()=>E())}),e.watch(()=>f.transform.value,a=>{w.update(),l("transform",a)}),e.watch(i,()=>{e.nextTick(()=>w.update())}),c({fit:E,zoomIn:f.zoomIn,zoomOut:f.zoomOut,resetTransform:f.reset,setTransform:f.setTransform,expandAll:h,toggle:s,layout:i,transform:f.transform}),(a,g)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:v,class:e.normalizeClass(["tree-view",{"tree-view--pannable":o.pannable,"tree-view--content":o.size==="content"}]),style:e.normalizeStyle(k.value)},[e.createElementVNode("div",{ref_key:"canvasElement",ref:b,class:"tree-view__canvas",style:e.normalizeStyle(u.value)},[(e.openBlock(),e.createElementBlock("svg",{class:"tree-view__links",width:e.unref(i).width,height:e.unref(i).height},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(x.value.links,d=>e.renderSlot(a.$slots,"link",{link:d},()=>[e.createElementVNode("path",{class:"tree-view__link",d:d.path},null,8,F)],void 0,d.id)),128))],8,R)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(x.value.nodes,d=>(e.openBlock(),e.createElementBlock("div",{key:d.id,class:e.normalizeClass(["tree-view__node",_(d)]),style:e.normalizeStyle(p(d)),onClick:T=>M(d,T)},[e.renderSlot(a.$slots,"node",{node:d,data:d.data,depth:d.depth,collapsed:d.collapsed,hasChildren:d.hasChildren,toggle:()=>y(d)},()=>[e.createVNode(C,{label:B(d),"has-children":d.hasChildren,collapsed:d.collapsed,onToggle:T=>y(d)},null,8,["label","has-children","collapsed","onToggle"])])],14,A))),128))],4)],6))}}),j={key:0,class:"tv-bracket__hint"},U=["title","onClick","onMouseenter"],W={key:0,class:"tv-bracket__tooltip"},H={class:"tv-bracket__name"},Q={class:"tv-bracket__score"},L=e.defineComponent({__name:"BracketCard",props:{match:{},rows:{default:void 0},myTeamHint:{default:"Нажми для перехода в матч 👇"},techDefeatLabel:{default:"ТП"},techDefeatHint:{default:"Техническое поражение"}},emits:["select"],setup(o,{emit:c}){const n=o,t=c,l=e.ref(null),v=e.computed(()=>r.buildBracketCard(n.match,{rows:n.rows,techDefeatLabel:n.techDefeatLabel}));function b(i){i.team&&t("select",i.team,i)}return(i,m)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["tv-bracket",{"tv-bracket--my-team":v.value.hasMyTeam}])},[v.value.hasMyTeam&&o.myTeamHint?(e.openBlock(),e.createElementBlock("div",j,e.toDisplayString(o.myTeamHint),1)):e.createCommentVNode("",!0),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(v.value.rows,s=>(e.openBlock(),e.createElementBlock("div",{key:s.key,class:e.normalizeClass(["tv-bracket__row",{"tv-bracket__row--winner":s.isWinner,"tv-bracket__row--loser":s.isLoser&&!s.isEmpty,"tv-bracket__row--my":s.isMyTeam,"tv-bracket__row--empty":s.isEmpty}]),title:s.isMyTeam?"Моя команда":void 0,onClick:h=>b(s),onMouseenter:h=>l.value=s.key,onMouseleave:m[0]||(m[0]=h=>l.value=null)},[s.team?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[s.isTechDefeat&&l.value===s.key?(e.openBlock(),e.createElementBlock("span",W,e.toDisplayString(o.techDefeatHint),1)):e.createCommentVNode("",!0),e.createElementVNode("span",H,e.toDisplayString(s.team.name),1),e.createElementVNode("span",Q,e.toDisplayString(s.score),1)],64)):e.createCommentVNode("",!0)],42,U))),128))],2))}}),Y=["onClick"],O=e.defineComponent({__name:"BracketRounds",props:{rounds:{},nodeWidth:{default:180},levelGap:{default:60},isShow:{type:Boolean,default:!0},scrollOnClick:{type:Boolean,default:void 0},mobileQuery:{default:r.MOBILE_MEDIA_QUERY}},emits:["select"],setup(o,{emit:c}){const n=o,t=c,l=e.ref(null),v=e.ref(null),b=e.ref(!1);let i=null;e.onMounted(()=>{i=r.watchMedia(n.mobileQuery,u=>{b.value=u})}),e.onBeforeUnmount(()=>i?.());const m=e.computed(()=>n.isShow&&(n.scrollOnClick??b.value)),s=e.computed(()=>r.buildBracketRounds(n.rounds,{nodeWidth:n.nodeWidth,levelGap:n.levelGap})),h=e.computed(()=>({width:`${s.value.width}px`})),f=e.computed(()=>n.isShow?{paddingLeft:`${s.value.offset}px`}:void 0);function w(u){const k=l.value,p=r.findScrollParent(k);if(!k||!p)return;const _=v.value,B=_?r.nodesInColumn(_,u.index,u.width,s.value.offset)[0]:void 0,y=B?r.horizontalBox(B,p):{left:r.offsetWithin(k,p)+u.index*u.width,width:u.width};r.smoothScrollLeft(p,r.roundScrollLeft({...y,viewport:p.clientWidth,scrollWidth:p.scrollWidth}))}function x(u){m.value&&(t("select",u),w(u))}return(u,k)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:l,class:"tv-rounds"},[o.isShow?(e.openBlock(),e.createElementBlock("div",{key:0,class:"tv-rounds__bar",style:e.normalizeStyle(h.value)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(s.value.columns,p=>(e.openBlock(),e.createElementBlock("div",{key:p.key,class:"tv-rounds__item",style:e.normalizeStyle({width:`${p.width}px`}),onClick:_=>x(p)},[e.renderSlot(u.$slots,"round",{round:p},()=>[e.createTextVNode(e.toDisplayString(p.name),1)])],12,Y))),128))],4)):e.createCommentVNode("",!0),e.createElementVNode("div",{ref_key:"contentElement",ref:v,class:"tv-rounds__content",style:e.normalizeStyle(f.value)},[e.renderSlot(u.$slots,"default")],4)],512))}}),Z=".tree-view{--tv-link-color: #cbd5e1;--tv-link-width: 2px;--tv-node-bg: #ffffff;--tv-node-color: #0f172a;--tv-node-border: 1px solid #e2e8f0;--tv-node-radius: 10px;--tv-node-shadow: 0 1px 2px rgb(15 23 42 / 8%);--tv-node-padding: 8px 12px;--tv-accent: #2563eb;position:relative;width:100%;height:100%;overflow:hidden;touch-action:none}.tree-view--pannable{cursor:grab}.tree-view--content{width:auto;height:auto;overflow:visible}.tree-view__canvas{position:absolute;top:0;left:0;transform-origin:0 0}.tree-view__links{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}.tree-view__link{fill:none;stroke:var(--tv-link-color);stroke-width:var(--tv-link-width)}.tree-view__node{position:absolute;box-sizing:border-box;display:flex;align-items:stretch;justify-content:stretch}.tree-view-card{box-sizing:border-box;display:flex;gap:8px;align-items:center;justify-content:space-between;width:100%;height:100%;padding:var(--tv-node-padding);overflow:hidden;color:var(--tv-node-color);background:var(--tv-node-bg);border:var(--tv-node-border);border-radius:var(--tv-node-radius);box-shadow:var(--tv-node-shadow)}.tree-view-card__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-view-card__toggle{flex:none;width:20px;height:20px;padding:0;font:inherit;line-height:1;color:var(--tv-accent);cursor:pointer;background:none;border:none}.tv-bracket{--tv-bracket-winner-bg: #0427c4;--tv-bracket-winner-color: #ffffff;--tv-bracket-loser-bg: #e5e5e5;--tv-bracket-loser-color: #7d7d8d;--tv-bracket-my-bg: #00146c;--tv-bracket-my-color: #ffffff;--tv-bracket-radius: 6px;position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;font-size:12px}.tv-bracket--my-team{border-radius:var(--tv-bracket-radius);outline:2px solid var(--tv-bracket-my-bg)}.tv-bracket__hint{position:absolute;right:0;bottom:100%;left:0;padding:3px 8px;overflow:hidden;font-size:11px;font-weight:600;line-height:1.2;color:var(--tv-bracket-my-color);text-align:center;text-overflow:ellipsis;white-space:nowrap;background:var(--tv-bracket-my-bg);border-radius:var(--tv-bracket-radius) var(--tv-bracket-radius) 0 0}.tv-bracket__row{position:relative;box-sizing:border-box;display:flex;flex:1 1 0;align-items:center;justify-content:space-between;min-height:0;padding:4px 8px;cursor:pointer;transition:all 75ms}.tv-bracket__row--winner{font-weight:700;color:var(--tv-bracket-winner-color);background:var(--tv-bracket-winner-bg)}.tv-bracket__row--loser{color:var(--tv-bracket-loser-color);background:var(--tv-bracket-loser-bg)}.tv-bracket__row--my{color:var(--tv-bracket-my-color);background:var(--tv-bracket-my-bg)}.tv-bracket__row--empty{cursor:default;background:var(--tv-bracket-loser-bg)}.tv-bracket__name{max-width:120px;overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tv-bracket__score{display:flex;flex:none;align-items:center;justify-content:center;width:20px;height:20px;font-weight:700;font-variant-numeric:tabular-nums}.tv-bracket__tooltip{position:absolute;top:-26px;left:6px;z-index:5000;padding:4px 8px;font-weight:400;color:var(--tv-bracket-loser-color);white-space:nowrap;background:#fff;border:1px solid #d1d5db;border-radius:4px;box-shadow:0 4px 10px #0f172a26}.tv-rounds{--tv-rounds-bg: #f5f5f7;--tv-rounds-color: #9ca3af;--tv-rounds-border: 2px solid #e5e7eb;--tv-rounds-gap: 20px;display:flex;flex-direction:column;width:max-content;min-width:100%}.tv-rounds__bar{display:flex;min-width:100%;margin-bottom:var(--tv-rounds-gap);background:var(--tv-rounds-bg);border-bottom:var(--tv-rounds-border)}.tv-rounds__item{flex:none;box-sizing:border-box;padding:10px 20px;font:inherit;font-size:14px;font-weight:600;color:var(--tv-rounds-color);text-align:center;text-transform:uppercase;white-space:nowrap;background:none;border:none;border-right:var(--tv-rounds-border)}";r.injectStyles(Z);const I={install(o){o.component("TreeView",$),o.component("TreeNodeCard",C),o.component("BracketCard",L),o.component("BracketRounds",O)}};Object.defineProperty(exports,"DEFAULT_OPTIONS",{enumerable:!0,get:()=>r.DEFAULT_OPTIONS});Object.defineProperty(exports,"MOBILE_MEDIA_QUERY",{enumerable:!0,get:()=>r.MOBILE_MEDIA_QUERY});Object.defineProperty(exports,"bracketCardHeight",{enumerable:!0,get:()=>r.bracketCardHeight});Object.defineProperty(exports,"buildBracketCard",{enumerable:!0,get:()=>r.buildBracketCard});Object.defineProperty(exports,"buildBracketRounds",{enumerable:!0,get:()=>r.buildBracketRounds});exports.BracketCard=L;exports.BracketRounds=O;exports.TreeNodeCard=C;exports.TreeView=$;exports.TreeViewPlugin=I;exports.default=I;exports.usePanZoom=S;exports.useTreeLayout=z;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/TreeNodeCard.vue","../src/usePanZoom.ts","../src/useTreeLayout.ts","../src/useVisibleRect.ts","../src/TreeView.vue","../src/BracketCard.vue","../src/BracketRounds.vue","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Карточка узла по умолчанию.\n * Нужна только чтобы «из коробки» что-то отображалось —\n * в реальном проекте обычно используют слот #node и рисуют свой блок.\n */\ndefineProps<{\n label: string\n hasChildren?: boolean\n collapsed?: boolean\n}>()\n\nconst emit = defineEmits<{ (e: 'toggle'): void }>()\n</script>\n\n<template>\n <div class=\"tree-view-card\">\n <span class=\"tree-view-card__label\">{{ label }}</span>\n\n <button\n v-if=\"hasChildren\"\n type=\"button\"\n class=\"tree-view-card__toggle\"\n data-tv-no-pan\n :aria-label=\"collapsed ? 'Развернуть' : 'Свернуть'\"\n @click.stop=\"emit('toggle')\"\n >\n {{ collapsed ? '+' : '−' }}\n </button>\n </div>\n</template>\n","import { onBeforeUnmount, ref, watch, type Ref } from 'vue'\nimport {\n IDENTITY_TRANSFORM,\n PanZoomController,\n type ScaleLimits,\n type Size,\n type Transform,\n} from '@bigplay/tree-view-core'\n\nexport interface UsePanZoomParams {\n /** Элемент, который слушаем: по нему тянут и крутят колесо. */\n element: Ref<HTMLElement | null>\n pannable: () => boolean\n zoomable: () => boolean\n limits: () => ScaleLimits\n}\n\n/**\n * Тонкая обёртка над `PanZoomController` из core:\n * переносит его состояние в реактивный `ref` и следит за жизненным циклом.\n */\nexport function usePanZoom(params: UsePanZoomParams) {\n const transform = ref<Transform>({ ...IDENTITY_TRANSFORM })\n\n const controller = new PanZoomController({\n pannable: params.pannable,\n zoomable: params.zoomable,\n limits: params.limits,\n onChange: (next) => {\n transform.value = next\n },\n })\n\n watch(\n params.element,\n (element, _previous, onCleanup) => {\n if (!element) return\n\n controller.attach(element)\n onCleanup(() => controller.detach())\n },\n { immediate: true },\n )\n\n onBeforeUnmount(() => controller.detach())\n\n return {\n transform,\n fit: (content: Size, padding?: number) => controller.fit(content, padding),\n zoomIn: () => controller.zoomIn(),\n zoomOut: () => controller.zoomOut(),\n reset: () => controller.reset(),\n setTransform: (next: Transform) => controller.set(next),\n }\n}\n","import { computed, ref, type ComputedRef } from 'vue'\nimport {\n applyCollapsed,\n layoutTree,\n toggleCollapsed,\n toTree,\n type Accessors,\n type Layout,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\n/** Функции-геттеры, чтобы композабл видел свежие значения пропов. */\nexport interface UseTreeLayoutParams<T> {\n data: () => T | T[] | null | undefined\n options: () => Partial<TreeViewOptions<T>> | undefined\n accessors: () => Accessors<T>\n}\n\nexport interface UseTreeLayoutResult<T> {\n /** Готовая раскладка: узлы с координатами и линии. */\n layout: ComputedRef<Layout<T>>\n /** Свёрнут ли узел с таким id. */\n isCollapsed: (id: string) => boolean\n /** Свернуть/развернуть узел. */\n toggle: (id: string) => void\n /** Развернуть всё дерево. */\n expandAll: () => void\n}\n\n/**\n * Вся «логика» компонента: данные → дерево → раскладка.\n * Каждый шаг — отдельная чистая функция из core, здесь только реактивность.\n */\nexport function useTreeLayout<T>(params: UseTreeLayoutParams<T>): UseTreeLayoutResult<T> {\n const collapsedIds = ref(new Set<string>())\n\n const tree = computed(() => toTree(params.data(), params.accessors()))\n const visibleTree = computed(() => applyCollapsed(tree.value, collapsedIds.value))\n const layout = computed(() => layoutTree(visibleTree.value, params.options()))\n\n function isCollapsed(id: string): boolean {\n return collapsedIds.value.has(id)\n }\n\n function toggle(id: string): void {\n // Новое множество вместо мутации — иначе Vue не заметит изменения.\n collapsedIds.value = toggleCollapsed(collapsedIds.value, id)\n }\n\n function expandAll(): void {\n collapsedIds.value = new Set()\n }\n\n return { layout, isCollapsed, toggle, expandAll }\n}\n","import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'\nimport { estimateVisibleRect, measureVisibleRect, type Rect } from '@bigplay/tree-view-core'\n\n/**\n * Следит за тем, какая часть холста сейчас видна.\n *\n * Пересчитывается там же, где это меняется: при перетаскивании и зуме,\n * при скролле страницы и при изменении размеров окна. Отдельный троттлинг\n * не нужен — и те, и другие события браузер отдаёт не чаще кадра.\n */\nexport function useVisibleRect(canvas: Ref<HTMLElement | null>, scale: () => number, enabled: () => boolean) {\n // До первого измерения берём размер окна — иначе первый кадр нарисует всё дерево.\n const rect = ref<Rect | null>(enabled() ? estimateVisibleRect() : null)\n\n function update(): void {\n rect.value = enabled() ? measureVisibleRect(canvas.value, scale()) : null\n }\n\n onMounted(() => {\n update()\n window.addEventListener('scroll', update, { passive: true, capture: true })\n window.addEventListener('resize', update, { passive: true })\n })\n\n onBeforeUnmount(() => {\n window.removeEventListener('scroll', update, { capture: true })\n window.removeEventListener('resize', update)\n })\n\n return { rect, update }\n}\n","<script setup lang=\"ts\" generic=\"T\">\n/**\n * Компонент-обёртка. Вся математика — в пакете core, здесь только\n * реактивность, разметка и события.\n */\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OVERSCAN,\n DEFAULT_SCALE_LIMITS,\n toCssTransform,\n visibleLayout,\n type Accessors,\n type LayoutNode,\n type ScaleLimits,\n type Transform,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport { usePanZoom } from './usePanZoom'\nimport { useTreeLayout } from './useTreeLayout'\nimport { useVisibleRect } from './useVisibleRect'\n\nconst props = withDefaults(\n defineProps<{\n /** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */\n data: T | T[] | null | undefined\n /** Размеры и направление. Можно передать только часть полей. */\n options?: Partial<TreeViewOptions<T>>\n /** Как достать id узла. По умолчанию — поле `id`. */\n getId?: Accessors<T>['getId']\n /** Как достать детей. По умолчанию — поле `children`. */\n getChildren?: Accessors<T>['getChildren']\n /** Как достать id родителя. Передан — значит данные плоские. */\n getParentId?: Accessors<T>['getParentId']\n /** Клик по узлу сворачивает/разворачивает ветку. */\n collapsible?: boolean\n /** Разрешить перетаскивание холста. */\n pannable?: boolean\n /** Разрешить зум колесом и «щипком». */\n zoomable?: boolean\n /** Вписать дерево в контейнер при первом отображении. */\n fitOnMount?: boolean\n /**\n * Как компонент занимает место:\n * - `'fill'` (по умолчанию) — растягивается на родителя, лишнее обрезается,\n * дерево двигают мышью;\n * - `'content'` — контейнер принимает размер дерева и ничего не обрезает.\n * Нужен, когда сетка стоит внутри страницы со своим скроллом.\n */\n size?: 'fill' | 'content'\n /** Пределы масштаба. */\n scaleLimits?: ScaleLimits\n /** Дополнительный класс на обёртке узла — строкой или функцией. */\n nodeClass?: string | ((node: LayoutNode<T>) => string)\n /**\n * Рисовать только то, что видно на экране. На больших сетках это\n * главная экономия: в DOM попадает несколько десятков карточек\n * вместо тысячи. На маленьких деревьях ничего не меняет.\n */\n virtualize?: boolean\n /** Запас вокруг видимой области в пикселях. */\n overscan?: number\n }>(),\n {\n options: undefined,\n getId: undefined,\n getChildren: undefined,\n getParentId: undefined,\n collapsible: false,\n pannable: true,\n zoomable: true,\n fitOnMount: true,\n size: 'fill',\n scaleLimits: undefined,\n nodeClass: undefined,\n virtualize: true,\n overscan: DEFAULT_OVERSCAN,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'node-click', node: LayoutNode<T>, event: MouseEvent): void\n (e: 'toggle', node: LayoutNode<T>, collapsed: boolean): void\n (e: 'transform', transform: Transform): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst canvasElement = ref<HTMLElement | null>(null)\n\nconst { layout, isCollapsed, toggle, expandAll } = useTreeLayout<T>({\n data: () => props.data,\n options: () => props.options,\n accessors: () => ({\n getId: props.getId,\n getChildren: props.getChildren,\n getParentId: props.getParentId,\n }),\n})\n\nconst panZoom = usePanZoom({\n element: rootElement,\n pannable: () => props.pannable,\n zoomable: () => props.zoomable,\n limits: () => props.scaleLimits ?? DEFAULT_SCALE_LIMITS,\n})\n\nconst visible = useVisibleRect(\n canvasElement,\n () => panZoom.transform.value.scale,\n () => props.virtualize,\n)\n\n/** То, что реально попадёт в DOM: при `virtualize` — только видимая часть. */\nconst drawn = computed(() =>\n props.virtualize ? visibleLayout(layout.value, visible.rect.value, props.overscan) : layout.value,\n)\n\nconst canvasStyle = computed(() => ({\n width: `${layout.value.width}px`,\n height: `${layout.value.height}px`,\n transform: toCssTransform(panZoom.transform.value),\n}))\n\nconst rootStyle = computed(() => {\n if (props.size !== 'content') return undefined\n\n const { scale } = panZoom.transform.value\n return {\n width: `${layout.value.width * scale}px`,\n height: `${layout.value.height * scale}px`,\n overflow: 'visible',\n }\n})\n\nfunction nodeStyle(node: LayoutNode<T>) {\n return {\n left: `${node.x}px`,\n top: `${node.y}px`,\n width: `${node.width}px`,\n height: `${node.height}px`,\n }\n}\n\nfunction nodeClassOf(node: LayoutNode<T>): string | undefined {\n return typeof props.nodeClass === 'function' ? props.nodeClass(node) : props.nodeClass\n}\n\n/** Подпись карточки по умолчанию: name → title → label → id. */\nfunction labelOf(node: LayoutNode<T>): string {\n const data = node.data as { name?: string; title?: string; label?: string } | null\n return data?.name ?? data?.title ?? data?.label ?? node.id\n}\n\nfunction toggleNode(node: LayoutNode<T>): void {\n toggle(node.id)\n emit('toggle', node, isCollapsed(node.id))\n}\n\nfunction onNodeClick(node: LayoutNode<T>, event: MouseEvent): void {\n emit('node-click', node, event)\n if (props.collapsible && node.hasChildren) toggleNode(node)\n}\n\n/** Вписывает дерево в видимую область. */\nfunction fit(padding?: number): void {\n panZoom.fit({ width: layout.value.width, height: layout.value.height }, padding)\n}\n\nonMounted(() => {\n if (props.fitOnMount) void nextTick(() => fit())\n})\n\nwatch(\n () => panZoom.transform.value,\n (transform) => {\n // Холст поехал — видимая часть стала другой.\n visible.update()\n emit('transform', transform)\n },\n)\n\n// Новые данные — и пересчитывать видимую часть надо уже по ним.\nwatch(layout, () => void nextTick(() => visible.update()))\n\ndefineExpose({\n fit,\n zoomIn: panZoom.zoomIn,\n zoomOut: panZoom.zoomOut,\n resetTransform: panZoom.reset,\n setTransform: panZoom.setTransform,\n expandAll,\n toggle,\n layout,\n transform: panZoom.transform,\n})\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tree-view\"\n :class=\"{ 'tree-view--pannable': pannable, 'tree-view--content': size === 'content' }\"\n :style=\"rootStyle\"\n >\n <div ref=\"canvasElement\" class=\"tree-view__canvas\" :style=\"canvasStyle\">\n <svg class=\"tree-view__links\" :width=\"layout.width\" :height=\"layout.height\">\n <template v-for=\"link in drawn.links\" :key=\"link.id\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </template>\n </svg>\n\n <div\n v-for=\"node in drawn.nodes\"\n :key=\"node.id\"\n class=\"tree-view__node\"\n :class=\"nodeClassOf(node)\"\n :style=\"nodeStyle(node)\"\n @click=\"onNodeClick(node, $event)\"\n >\n <!--\n Главная точка кастомизации: сюда подставляют свою карточку.\n Доступно всё, что может понадобиться для отрисовки и управления.\n -->\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"node.data\"\n :depth=\"node.depth\"\n :collapsed=\"node.collapsed\"\n :has-children=\"node.hasChildren\"\n :toggle=\"() => toggleNode(node)\"\n >\n <TreeNodeCard\n :label=\"labelOf(node)\"\n :has-children=\"node.hasChildren\"\n :collapsed=\"node.collapsed\"\n @toggle=\"toggleNode(node)\"\n />\n </slot>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Карточка матча турнирной сетки — та самая, что на сайте:\n * победитель синий, проигравший серый, команда пользователя в рамке\n * с подсказкой, техническое поражение показывает «ТП» и объяснение по наведению.\n *\n * Какие строки рисовать — решает `buildBracketCard` из core, поэтому\n * версия для Vue 2 выглядит точно так же.\n */\nimport { computed, ref } from 'vue'\nimport {\n buildBracketCard,\n type BracketCardMatch,\n type BracketCardRow,\n type BracketCardTeam,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Матч: нужны только команды. */\n match: BracketCardMatch | null | undefined\n /** Сколько строк рисовать. У финала — одна. */\n rows?: number\n /** Подсказка над карточкой с моей командой. Пустая строка — не показывать. */\n myTeamHint?: string\n /** Текст в ячейке счёта при техническом поражении. */\n techDefeatLabel?: string\n /** Подсказка по наведению на техническое поражение. */\n techDefeatHint?: string\n }>(),\n {\n rows: undefined,\n myTeamHint: 'Нажми для перехода в матч 👇',\n techDefeatLabel: 'ТП',\n techDefeatHint: 'Техническое поражение',\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', team: BracketCardTeam, row: BracketCardRow): void }>()\n\nconst hoveredKey = ref<string | null>(null)\n\nconst card = computed(() =>\n buildBracketCard(props.match, { rows: props.rows, techDefeatLabel: props.techDefeatLabel }),\n)\n\nfunction onRowClick(row: BracketCardRow): void {\n if (row.team) emit('select', row.team, row)\n}\n</script>\n\n<template>\n <div class=\"tv-bracket\" :class=\"{ 'tv-bracket--my-team': card.hasMyTeam }\">\n <div v-if=\"card.hasMyTeam && myTeamHint\" class=\"tv-bracket__hint\">{{ myTeamHint }}</div>\n\n <div\n v-for=\"row in card.rows\"\n :key=\"row.key\"\n class=\"tv-bracket__row\"\n :class=\"{\n 'tv-bracket__row--winner': row.isWinner,\n 'tv-bracket__row--loser': row.isLoser && !row.isEmpty,\n 'tv-bracket__row--my': row.isMyTeam,\n 'tv-bracket__row--empty': row.isEmpty,\n }\"\n :title=\"row.isMyTeam ? 'Моя команда' : undefined\"\n @click=\"onRowClick(row)\"\n @mouseenter=\"hoveredKey = row.key\"\n @mouseleave=\"hoveredKey = null\"\n >\n <template v-if=\"row.team\">\n <span v-if=\"row.isTechDefeat && hoveredKey === row.key\" class=\"tv-bracket__tooltip\">\n {{ techDefeatHint }}\n </span>\n\n <span class=\"tv-bracket__name\">{{ row.team.name }}</span>\n <span class=\"tv-bracket__score\">{{ row.score }}</span>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Полоса раундов над турнирной сеткой — та самая, что на странице турнира.\n *\n * Делает три вещи:\n * 1. рисует названия раундов колонками той же ширины, что и уровни сетки;\n * 2. центрует матчи внутри раунда — сетка в слоте получает отступ слева\n * в половину `levelGap`, иначе карточки прижимаются к левому краю колонки;\n * 3. на узком экране добавляет раундам нажатие: сетка плавно подъезжает\n * к матчам этого раунда. Выглядит полоса при этом везде одинаково.\n *\n * Данные сетки полоса не трогает: на экране всегда вся сетка целиком.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport {\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n nodesInColumn,\n offsetWithin,\n roundScrollLeft,\n scrollToNode,\n smoothScrollLeft,\n watchMedia,\n type BracketRound,\n type BracketRoundColumn,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Ширина карточки матча — то же число, что в `nodeWidth` у сетки. */\n nodeWidth?: number\n /** Промежуток между раундами — то же число, что в `levelGap` у сетки. */\n levelGap?: number\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /** Нажатие на раунд подводит к нему сетку. Не передан — решает ширина экрана. */\n scrollOnClick?: boolean\n /** Медиавыражение «узкий экран», при котором нажатие включается. */\n mobileQuery?: string\n }>(),\n {\n nodeWidth: 180,\n levelGap: 60,\n isShow: true,\n scrollOnClick: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', round: BracketRoundColumn): void }>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\nlet unwatch: (() => void) | null = null\n\nonMounted(() => {\n unwatch = watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n })\n})\n\nonBeforeUnmount(() => unwatch?.())\n\n/** Полосы нет — нажимать не на что. */\nconst isClickable = computed(() => props.isShow && (props.scrollOnClick ?? isNarrow.value))\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: props.nodeWidth, levelGap: props.levelGap }),\n)\n\nconst barStyle = computed(() => ({ width: `${model.value.width}px` }))\n\nconst contentStyle = computed(() =>\n props.isShow ? { paddingLeft: `${model.value.offset}px` } : undefined,\n)\n\n/**\n * Подводит сетку к матчам раунда.\n *\n * Сначала пробуем по самим карточкам: браузер сам подвинет и страницу, и блок\n * с прокруткой, поэтому финал оказывается на экране целиком, а не только\n * «где-то справа». Карточек не нашли (виртуализация) — считаем по колонкам.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const content = contentElement.value\n const matches = content ? nodesInColumn(content, column.index, column.width, model.value.offset) : []\n const middle = matches[Math.floor(matches.length / 2)]\n\n if (middle && scrollToNode(middle)) return\n\n const element = rootElement.value\n const container = findScrollParent(element)\n if (!element || !container) return\n\n const left = offsetWithin(element, container) + column.index * column.width\n\n smoothScrollLeft(\n container,\n roundScrollLeft({\n left,\n width: column.width,\n viewport: container.clientWidth,\n scrollWidth: container.scrollWidth,\n }),\n )\n}\n\n/** Нажатие — только на узком экране: на широком колонка раунда это просто подпись. */\nfunction onSelect(column: BracketRoundColumn): void {\n if (!isClickable.value) return\n\n emit('select', column)\n scrollToRound(column)\n}\n</script>\n\n<template>\n <div ref=\"rootElement\" class=\"tv-rounds\">\n <div v-if=\"isShow\" class=\"tv-rounds__bar\" :style=\"barStyle\">\n <div\n v-for=\"column in model.columns\"\n :key=\"column.key\"\n class=\"tv-rounds__item\"\n :style=\"{ width: `${column.width}px` }\"\n @click=\"onSelect(column)\"\n >\n <slot name=\"round\" :round=\"column\">{{ column.name }}</slot>\n </div>\n </div>\n\n <div ref=\"contentElement\" class=\"tv-rounds__content\" :style=\"contentStyle\">\n <!-- Сюда кладут саму сетку: ей достаётся отступ, центрующий матчи. -->\n <slot />\n </div>\n </div>\n</template>\n","import type { App, Plugin } from 'vue'\nimport TreeView from './TreeView.vue'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport BracketCard from './BracketCard.vue'\nimport BracketRounds from './BracketRounds.vue'\n// Стили общие для Vue 2 и Vue 3 — лежат в core, чтобы не расходились.\n// Они попадают прямо в бандл и подключаются сами: отдельный импорт в проекте не нужен.\nimport { injectStyles } from '@bigplay/tree-view-core'\nimport styles from '@bigplay/tree-view-core/style.css?inline'\n\ninjectStyles(styles)\n\nexport { TreeView, TreeNodeCard, BracketCard, BracketRounds }\nexport { useTreeLayout } from './useTreeLayout'\nexport { usePanZoom } from './usePanZoom'\n\n// Типы из core переэкспортируем, чтобы их не приходилось ставить отдельно.\nexport type {\n Accessors,\n BracketCardMatch,\n BracketCardModel,\n BracketCardOptions,\n BracketCardRow,\n BracketCardTeam,\n BracketRound,\n BracketRoundColumn,\n BracketRoundsModel,\n BracketRoundsOptions,\n Direction,\n Layout,\n LayoutLink,\n LayoutNode,\n LinkStyle,\n ScaleLimits,\n Transform,\n TreeNode,\n TreeViewOptions,\n} from '@bigplay/tree-view-core'\nexport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketCard,\n bracketCardHeight,\n buildBracketRounds,\n} from '@bigplay/tree-view-core'\n\n/** Плагин: `app.use(TreeViewPlugin)` регистрирует компоненты глобально. */\nexport const TreeViewPlugin: Plugin = {\n install(app: App) {\n app.component('TreeView', TreeView)\n app.component('TreeNodeCard', TreeNodeCard)\n app.component('BracketCard', BracketCard)\n app.component('BracketRounds', BracketRounds)\n },\n}\n\nexport default TreeViewPlugin\n"],"names":["emit","__emit","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_toDisplayString","__props","_hoisted_3","usePanZoom","params","transform","ref","IDENTITY_TRANSFORM","controller","PanZoomController","next","watch","element","_previous","onCleanup","onBeforeUnmount","content","padding","useTreeLayout","collapsedIds","tree","computed","toTree","visibleTree","applyCollapsed","layout","layoutTree","isCollapsed","id","toggle","toggleCollapsed","expandAll","useVisibleRect","canvas","scale","enabled","rect","estimateVisibleRect","update","measureVisibleRect","onMounted","props","rootElement","canvasElement","panZoom","DEFAULT_SCALE_LIMITS","visible","drawn","visibleLayout","canvasStyle","toCssTransform","rootStyle","nodeStyle","node","nodeClassOf","labelOf","data","toggleNode","onNodeClick","event","fit","nextTick","__expose","_normalizeClass","_unref","_Fragment","_renderList","link","_renderSlot","_ctx","_normalizeStyle","$event","_createVNode","TreeNodeCard","hoveredKey","card","buildBracketCard","onRowClick","row","_hoisted_4","_hoisted_5","contentElement","isNarrow","unwatch","watchMedia","matches","isClickable","model","buildBracketRounds","barStyle","contentStyle","scrollToRound","column","nodesInColumn","middle","scrollToNode","container","findScrollParent","left","offsetWithin","smoothScrollLeft","roundScrollLeft","onSelect","_createTextVNode","injectStyles","styles","TreeViewPlugin","app","TreeView","BracketCard","BracketRounds"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/TreeNodeCard.vue","../src/usePanZoom.ts","../src/useTreeLayout.ts","../src/useVisibleRect.ts","../src/TreeView.vue","../src/BracketCard.vue","../src/BracketRounds.vue","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Карточка узла по умолчанию.\n * Нужна только чтобы «из коробки» что-то отображалось —\n * в реальном проекте обычно используют слот #node и рисуют свой блок.\n */\ndefineProps<{\n label: string\n hasChildren?: boolean\n collapsed?: boolean\n}>()\n\nconst emit = defineEmits<{ (e: 'toggle'): void }>()\n</script>\n\n<template>\n <div class=\"tree-view-card\">\n <span class=\"tree-view-card__label\">{{ label }}</span>\n\n <button\n v-if=\"hasChildren\"\n type=\"button\"\n class=\"tree-view-card__toggle\"\n data-tv-no-pan\n :aria-label=\"collapsed ? 'Развернуть' : 'Свернуть'\"\n @click.stop=\"emit('toggle')\"\n >\n {{ collapsed ? '+' : '−' }}\n </button>\n </div>\n</template>\n","import { onBeforeUnmount, ref, watch, type Ref } from 'vue'\nimport {\n IDENTITY_TRANSFORM,\n PanZoomController,\n type ScaleLimits,\n type Size,\n type Transform,\n} from '@bigplay/tree-view-core'\n\nexport interface UsePanZoomParams {\n /** Элемент, который слушаем: по нему тянут и крутят колесо. */\n element: Ref<HTMLElement | null>\n pannable: () => boolean\n zoomable: () => boolean\n limits: () => ScaleLimits\n}\n\n/**\n * Тонкая обёртка над `PanZoomController` из core:\n * переносит его состояние в реактивный `ref` и следит за жизненным циклом.\n */\nexport function usePanZoom(params: UsePanZoomParams) {\n const transform = ref<Transform>({ ...IDENTITY_TRANSFORM })\n\n const controller = new PanZoomController({\n pannable: params.pannable,\n zoomable: params.zoomable,\n limits: params.limits,\n onChange: (next) => {\n transform.value = next\n },\n })\n\n watch(\n params.element,\n (element, _previous, onCleanup) => {\n if (!element) return\n\n controller.attach(element)\n onCleanup(() => controller.detach())\n },\n { immediate: true },\n )\n\n onBeforeUnmount(() => controller.detach())\n\n return {\n transform,\n fit: (content: Size, padding?: number) => controller.fit(content, padding),\n zoomIn: () => controller.zoomIn(),\n zoomOut: () => controller.zoomOut(),\n reset: () => controller.reset(),\n setTransform: (next: Transform) => controller.set(next),\n }\n}\n","import { computed, ref, type ComputedRef } from 'vue'\nimport {\n applyCollapsed,\n layoutTree,\n toggleCollapsed,\n toTree,\n type Accessors,\n type Layout,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\n/** Функции-геттеры, чтобы композабл видел свежие значения пропов. */\nexport interface UseTreeLayoutParams<T> {\n data: () => T | T[] | null | undefined\n options: () => Partial<TreeViewOptions<T>> | undefined\n accessors: () => Accessors<T>\n}\n\nexport interface UseTreeLayoutResult<T> {\n /** Готовая раскладка: узлы с координатами и линии. */\n layout: ComputedRef<Layout<T>>\n /** Свёрнут ли узел с таким id. */\n isCollapsed: (id: string) => boolean\n /** Свернуть/развернуть узел. */\n toggle: (id: string) => void\n /** Развернуть всё дерево. */\n expandAll: () => void\n}\n\n/**\n * Вся «логика» компонента: данные → дерево → раскладка.\n * Каждый шаг — отдельная чистая функция из core, здесь только реактивность.\n */\nexport function useTreeLayout<T>(params: UseTreeLayoutParams<T>): UseTreeLayoutResult<T> {\n const collapsedIds = ref(new Set<string>())\n\n const tree = computed(() => toTree(params.data(), params.accessors()))\n const visibleTree = computed(() => applyCollapsed(tree.value, collapsedIds.value))\n const layout = computed(() => layoutTree(visibleTree.value, params.options()))\n\n function isCollapsed(id: string): boolean {\n return collapsedIds.value.has(id)\n }\n\n function toggle(id: string): void {\n // Новое множество вместо мутации — иначе Vue не заметит изменения.\n collapsedIds.value = toggleCollapsed(collapsedIds.value, id)\n }\n\n function expandAll(): void {\n collapsedIds.value = new Set()\n }\n\n return { layout, isCollapsed, toggle, expandAll }\n}\n","import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'\nimport { estimateVisibleRect, measureVisibleRect, type Rect } from '@bigplay/tree-view-core'\n\n/**\n * Следит за тем, какая часть холста сейчас видна.\n *\n * Пересчитывается там же, где это меняется: при перетаскивании и зуме,\n * при скролле страницы и при изменении размеров окна. Отдельный троттлинг\n * не нужен — и те, и другие события браузер отдаёт не чаще кадра.\n */\nexport function useVisibleRect(canvas: Ref<HTMLElement | null>, scale: () => number, enabled: () => boolean) {\n // До первого измерения берём размер окна — иначе первый кадр нарисует всё дерево.\n const rect = ref<Rect | null>(enabled() ? estimateVisibleRect() : null)\n\n function update(): void {\n rect.value = enabled() ? measureVisibleRect(canvas.value, scale()) : null\n }\n\n onMounted(() => {\n update()\n window.addEventListener('scroll', update, { passive: true, capture: true })\n window.addEventListener('resize', update, { passive: true })\n })\n\n onBeforeUnmount(() => {\n window.removeEventListener('scroll', update, { capture: true })\n window.removeEventListener('resize', update)\n })\n\n return { rect, update }\n}\n","<script setup lang=\"ts\" generic=\"T\">\n/**\n * Компонент-обёртка. Вся математика — в пакете core, здесь только\n * реактивность, разметка и события.\n */\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OVERSCAN,\n DEFAULT_SCALE_LIMITS,\n toCssTransform,\n visibleLayout,\n type Accessors,\n type LayoutNode,\n type ScaleLimits,\n type Transform,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport { usePanZoom } from './usePanZoom'\nimport { useTreeLayout } from './useTreeLayout'\nimport { useVisibleRect } from './useVisibleRect'\n\nconst props = withDefaults(\n defineProps<{\n /** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */\n data: T | T[] | null | undefined\n /** Размеры и направление. Можно передать только часть полей. */\n options?: Partial<TreeViewOptions<T>>\n /** Как достать id узла. По умолчанию — поле `id`. */\n getId?: Accessors<T>['getId']\n /** Как достать детей. По умолчанию — поле `children`. */\n getChildren?: Accessors<T>['getChildren']\n /** Как достать id родителя. Передан — значит данные плоские. */\n getParentId?: Accessors<T>['getParentId']\n /** Клик по узлу сворачивает/разворачивает ветку. */\n collapsible?: boolean\n /** Разрешить перетаскивание холста. */\n pannable?: boolean\n /** Разрешить зум колесом и «щипком». */\n zoomable?: boolean\n /** Вписать дерево в контейнер при первом отображении. */\n fitOnMount?: boolean\n /**\n * Как компонент занимает место:\n * - `'fill'` (по умолчанию) — растягивается на родителя, лишнее обрезается,\n * дерево двигают мышью;\n * - `'content'` — контейнер принимает размер дерева и ничего не обрезает.\n * Нужен, когда сетка стоит внутри страницы со своим скроллом.\n */\n size?: 'fill' | 'content'\n /** Пределы масштаба. */\n scaleLimits?: ScaleLimits\n /** Дополнительный класс на обёртке узла — строкой или функцией. */\n nodeClass?: string | ((node: LayoutNode<T>) => string)\n /**\n * Рисовать только то, что видно на экране. На больших сетках это\n * главная экономия: в DOM попадает несколько десятков карточек\n * вместо тысячи. На маленьких деревьях ничего не меняет.\n */\n virtualize?: boolean\n /** Запас вокруг видимой области в пикселях. */\n overscan?: number\n }>(),\n {\n options: undefined,\n getId: undefined,\n getChildren: undefined,\n getParentId: undefined,\n collapsible: false,\n pannable: true,\n zoomable: true,\n fitOnMount: true,\n size: 'fill',\n scaleLimits: undefined,\n nodeClass: undefined,\n virtualize: true,\n overscan: DEFAULT_OVERSCAN,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'node-click', node: LayoutNode<T>, event: MouseEvent): void\n (e: 'toggle', node: LayoutNode<T>, collapsed: boolean): void\n (e: 'transform', transform: Transform): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst canvasElement = ref<HTMLElement | null>(null)\n\nconst { layout, isCollapsed, toggle, expandAll } = useTreeLayout<T>({\n data: () => props.data,\n options: () => props.options,\n accessors: () => ({\n getId: props.getId,\n getChildren: props.getChildren,\n getParentId: props.getParentId,\n }),\n})\n\nconst panZoom = usePanZoom({\n element: rootElement,\n pannable: () => props.pannable,\n zoomable: () => props.zoomable,\n limits: () => props.scaleLimits ?? DEFAULT_SCALE_LIMITS,\n})\n\nconst visible = useVisibleRect(\n canvasElement,\n () => panZoom.transform.value.scale,\n () => props.virtualize,\n)\n\n/** То, что реально попадёт в DOM: при `virtualize` — только видимая часть. */\nconst drawn = computed(() =>\n props.virtualize ? visibleLayout(layout.value, visible.rect.value, props.overscan) : layout.value,\n)\n\nconst canvasStyle = computed(() => ({\n width: `${layout.value.width}px`,\n height: `${layout.value.height}px`,\n transform: toCssTransform(panZoom.transform.value),\n}))\n\nconst rootStyle = computed(() => {\n if (props.size !== 'content') return undefined\n\n const { scale } = panZoom.transform.value\n return {\n width: `${layout.value.width * scale}px`,\n height: `${layout.value.height * scale}px`,\n overflow: 'visible',\n }\n})\n\nfunction nodeStyle(node: LayoutNode<T>) {\n return {\n left: `${node.x}px`,\n top: `${node.y}px`,\n width: `${node.width}px`,\n height: `${node.height}px`,\n }\n}\n\nfunction nodeClassOf(node: LayoutNode<T>): string | undefined {\n return typeof props.nodeClass === 'function' ? props.nodeClass(node) : props.nodeClass\n}\n\n/** Подпись карточки по умолчанию: name → title → label → id. */\nfunction labelOf(node: LayoutNode<T>): string {\n const data = node.data as { name?: string; title?: string; label?: string } | null\n return data?.name ?? data?.title ?? data?.label ?? node.id\n}\n\nfunction toggleNode(node: LayoutNode<T>): void {\n toggle(node.id)\n emit('toggle', node, isCollapsed(node.id))\n}\n\nfunction onNodeClick(node: LayoutNode<T>, event: MouseEvent): void {\n emit('node-click', node, event)\n if (props.collapsible && node.hasChildren) toggleNode(node)\n}\n\n/** Вписывает дерево в видимую область. */\nfunction fit(padding?: number): void {\n panZoom.fit({ width: layout.value.width, height: layout.value.height }, padding)\n}\n\nonMounted(() => {\n if (props.fitOnMount) void nextTick(() => fit())\n})\n\nwatch(\n () => panZoom.transform.value,\n (transform) => {\n // Холст поехал — видимая часть стала другой.\n visible.update()\n emit('transform', transform)\n },\n)\n\n// Новые данные — и пересчитывать видимую часть надо уже по ним.\nwatch(layout, () => void nextTick(() => visible.update()))\n\ndefineExpose({\n fit,\n zoomIn: panZoom.zoomIn,\n zoomOut: panZoom.zoomOut,\n resetTransform: panZoom.reset,\n setTransform: panZoom.setTransform,\n expandAll,\n toggle,\n layout,\n transform: panZoom.transform,\n})\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tree-view\"\n :class=\"{ 'tree-view--pannable': pannable, 'tree-view--content': size === 'content' }\"\n :style=\"rootStyle\"\n >\n <div ref=\"canvasElement\" class=\"tree-view__canvas\" :style=\"canvasStyle\">\n <svg class=\"tree-view__links\" :width=\"layout.width\" :height=\"layout.height\">\n <template v-for=\"link in drawn.links\" :key=\"link.id\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </template>\n </svg>\n\n <div\n v-for=\"node in drawn.nodes\"\n :key=\"node.id\"\n class=\"tree-view__node\"\n :class=\"nodeClassOf(node)\"\n :style=\"nodeStyle(node)\"\n @click=\"onNodeClick(node, $event)\"\n >\n <!--\n Главная точка кастомизации: сюда подставляют свою карточку.\n Доступно всё, что может понадобиться для отрисовки и управления.\n -->\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"node.data\"\n :depth=\"node.depth\"\n :collapsed=\"node.collapsed\"\n :has-children=\"node.hasChildren\"\n :toggle=\"() => toggleNode(node)\"\n >\n <TreeNodeCard\n :label=\"labelOf(node)\"\n :has-children=\"node.hasChildren\"\n :collapsed=\"node.collapsed\"\n @toggle=\"toggleNode(node)\"\n />\n </slot>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Карточка матча турнирной сетки — та самая, что на сайте:\n * победитель синий, проигравший серый, команда пользователя в рамке\n * с подсказкой, техническое поражение показывает «ТП» и объяснение по наведению.\n *\n * Какие строки рисовать — решает `buildBracketCard` из core, поэтому\n * версия для Vue 2 выглядит точно так же.\n */\nimport { computed, ref } from 'vue'\nimport {\n buildBracketCard,\n type BracketCardMatch,\n type BracketCardRow,\n type BracketCardTeam,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Матч: нужны только команды. */\n match: BracketCardMatch | null | undefined\n /** Сколько строк рисовать. У финала — одна. */\n rows?: number\n /** Подсказка над карточкой с моей командой. Пустая строка — не показывать. */\n myTeamHint?: string\n /** Текст в ячейке счёта при техническом поражении. */\n techDefeatLabel?: string\n /** Подсказка по наведению на техническое поражение. */\n techDefeatHint?: string\n }>(),\n {\n rows: undefined,\n myTeamHint: 'Нажми для перехода в матч 👇',\n techDefeatLabel: 'ТП',\n techDefeatHint: 'Техническое поражение',\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', team: BracketCardTeam, row: BracketCardRow): void }>()\n\nconst hoveredKey = ref<string | null>(null)\n\nconst card = computed(() =>\n buildBracketCard(props.match, { rows: props.rows, techDefeatLabel: props.techDefeatLabel }),\n)\n\nfunction onRowClick(row: BracketCardRow): void {\n if (row.team) emit('select', row.team, row)\n}\n</script>\n\n<template>\n <div class=\"tv-bracket\" :class=\"{ 'tv-bracket--my-team': card.hasMyTeam }\">\n <div v-if=\"card.hasMyTeam && myTeamHint\" class=\"tv-bracket__hint\">{{ myTeamHint }}</div>\n\n <div\n v-for=\"row in card.rows\"\n :key=\"row.key\"\n class=\"tv-bracket__row\"\n :class=\"{\n 'tv-bracket__row--winner': row.isWinner,\n 'tv-bracket__row--loser': row.isLoser && !row.isEmpty,\n 'tv-bracket__row--my': row.isMyTeam,\n 'tv-bracket__row--empty': row.isEmpty,\n }\"\n :title=\"row.isMyTeam ? 'Моя команда' : undefined\"\n @click=\"onRowClick(row)\"\n @mouseenter=\"hoveredKey = row.key\"\n @mouseleave=\"hoveredKey = null\"\n >\n <template v-if=\"row.team\">\n <span v-if=\"row.isTechDefeat && hoveredKey === row.key\" class=\"tv-bracket__tooltip\">\n {{ techDefeatHint }}\n </span>\n\n <span class=\"tv-bracket__name\">{{ row.team.name }}</span>\n <span class=\"tv-bracket__score\">{{ row.score }}</span>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Полоса раундов над турнирной сеткой — та самая, что на странице турнира.\n *\n * Делает три вещи:\n * 1. рисует названия раундов колонками той же ширины, что и уровни сетки;\n * 2. центрует матчи внутри раунда — сетка в слоте получает отступ слева\n * в половину `levelGap`, иначе карточки прижимаются к левому краю колонки;\n * 3. на узком экране добавляет раундам нажатие: сетка плавно подъезжает\n * к матчам этого раунда. Выглядит полоса при этом везде одинаково.\n *\n * Данные сетки полоса не трогает: на экране всегда вся сетка целиком.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport {\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n horizontalBox,\n nodesInColumn,\n offsetWithin,\n roundScrollLeft,\n smoothScrollLeft,\n watchMedia,\n type BracketRound,\n type BracketRoundColumn,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Ширина карточки матча — то же число, что в `nodeWidth` у сетки. */\n nodeWidth?: number\n /** Промежуток между раундами — то же число, что в `levelGap` у сетки. */\n levelGap?: number\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /** Нажатие на раунд подводит к нему сетку. Не передан — решает ширина экрана. */\n scrollOnClick?: boolean\n /** Медиавыражение «узкий экран», при котором нажатие включается. */\n mobileQuery?: string\n }>(),\n {\n nodeWidth: 180,\n levelGap: 60,\n isShow: true,\n scrollOnClick: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', round: BracketRoundColumn): void }>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\nlet unwatch: (() => void) | null = null\n\nonMounted(() => {\n unwatch = watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n })\n})\n\nonBeforeUnmount(() => unwatch?.())\n\n/** Полосы нет — нажимать не на что. */\nconst isClickable = computed(() => props.isShow && (props.scrollOnClick ?? isNarrow.value))\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: props.nodeWidth, levelGap: props.levelGap }),\n)\n\nconst barStyle = computed(() => ({ width: `${model.value.width}px` }))\n\nconst contentStyle = computed(() =>\n props.isShow ? { paddingLeft: `${model.value.offset}px` } : undefined,\n)\n\n/**\n * Подводит сетку к матчам раунда — только по горизонтали.\n *\n * Меряем по самой карточке: так раунд встаёт по центру экрана точно, а у финала\n * прокрутка упирается в правый край и он виден целиком. Карточек не нашли\n * (виртуализация) — считаем по колонке, она той же ширины и на том же месте.\n *\n * Вертикаль не трогаем: страница остаётся там, где была.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const element = rootElement.value\n const container = findScrollParent(element)\n if (!element || !container) return\n\n const content = contentElement.value\n // Карточки раунда стоят друг под другом, поэтому по горизонтали годится любая.\n const card = content\n ? nodesInColumn(content, column.index, column.width, model.value.offset)[0]\n : undefined\n\n const box = card\n ? horizontalBox(card, container)\n : { left: offsetWithin(element, container) + column.index * column.width, width: column.width }\n\n smoothScrollLeft(\n container,\n roundScrollLeft({\n ...box,\n viewport: container.clientWidth,\n scrollWidth: container.scrollWidth,\n }),\n )\n}\n\n/** Нажатие — только на узком экране: на широком колонка раунда это просто подпись. */\nfunction onSelect(column: BracketRoundColumn): void {\n if (!isClickable.value) return\n\n emit('select', column)\n scrollToRound(column)\n}\n</script>\n\n<template>\n <div ref=\"rootElement\" class=\"tv-rounds\">\n <div v-if=\"isShow\" class=\"tv-rounds__bar\" :style=\"barStyle\">\n <div\n v-for=\"column in model.columns\"\n :key=\"column.key\"\n class=\"tv-rounds__item\"\n :style=\"{ width: `${column.width}px` }\"\n @click=\"onSelect(column)\"\n >\n <slot name=\"round\" :round=\"column\">{{ column.name }}</slot>\n </div>\n </div>\n\n <div ref=\"contentElement\" class=\"tv-rounds__content\" :style=\"contentStyle\">\n <!-- Сюда кладут саму сетку: ей достаётся отступ, центрующий матчи. -->\n <slot />\n </div>\n </div>\n</template>\n","import type { App, Plugin } from 'vue'\nimport TreeView from './TreeView.vue'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport BracketCard from './BracketCard.vue'\nimport BracketRounds from './BracketRounds.vue'\n// Стили общие для Vue 2 и Vue 3 — лежат в core, чтобы не расходились.\n// Они попадают прямо в бандл и подключаются сами: отдельный импорт в проекте не нужен.\nimport { injectStyles } from '@bigplay/tree-view-core'\nimport styles from '@bigplay/tree-view-core/style.css?inline'\n\ninjectStyles(styles)\n\nexport { TreeView, TreeNodeCard, BracketCard, BracketRounds }\nexport { useTreeLayout } from './useTreeLayout'\nexport { usePanZoom } from './usePanZoom'\n\n// Типы из core переэкспортируем, чтобы их не приходилось ставить отдельно.\nexport type {\n Accessors,\n BracketCardMatch,\n BracketCardModel,\n BracketCardOptions,\n BracketCardRow,\n BracketCardTeam,\n BracketRound,\n BracketRoundColumn,\n BracketRoundsModel,\n BracketRoundsOptions,\n Direction,\n Layout,\n LayoutLink,\n LayoutNode,\n LinkStyle,\n ScaleLimits,\n Transform,\n TreeNode,\n TreeViewOptions,\n} from '@bigplay/tree-view-core'\nexport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketCard,\n bracketCardHeight,\n buildBracketRounds,\n} from '@bigplay/tree-view-core'\n\n/** Плагин: `app.use(TreeViewPlugin)` регистрирует компоненты глобально. */\nexport const TreeViewPlugin: Plugin = {\n install(app: App) {\n app.component('TreeView', TreeView)\n app.component('TreeNodeCard', TreeNodeCard)\n app.component('BracketCard', BracketCard)\n app.component('BracketRounds', BracketRounds)\n },\n}\n\nexport default TreeViewPlugin\n"],"names":["emit","__emit","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_toDisplayString","__props","_hoisted_3","usePanZoom","params","transform","ref","IDENTITY_TRANSFORM","controller","PanZoomController","next","watch","element","_previous","onCleanup","onBeforeUnmount","content","padding","useTreeLayout","collapsedIds","tree","computed","toTree","visibleTree","applyCollapsed","layout","layoutTree","isCollapsed","id","toggle","toggleCollapsed","expandAll","useVisibleRect","canvas","scale","enabled","rect","estimateVisibleRect","update","measureVisibleRect","onMounted","props","rootElement","canvasElement","panZoom","DEFAULT_SCALE_LIMITS","visible","drawn","visibleLayout","canvasStyle","toCssTransform","rootStyle","nodeStyle","node","nodeClassOf","labelOf","data","toggleNode","onNodeClick","event","fit","nextTick","__expose","_normalizeClass","_unref","_Fragment","_renderList","link","_renderSlot","_ctx","_normalizeStyle","$event","_createVNode","TreeNodeCard","hoveredKey","card","buildBracketCard","onRowClick","row","_hoisted_4","_hoisted_5","contentElement","isNarrow","unwatch","watchMedia","matches","isClickable","model","buildBracketRounds","barStyle","contentStyle","scrollToRound","column","container","findScrollParent","nodesInColumn","box","horizontalBox","offsetWithin","smoothScrollLeft","roundScrollLeft","onSelect","_createTextVNode","injectStyles","styles","TreeViewPlugin","app","TreeView","BracketCard","BracketRounds"],"mappings":"yYAYA,MAAMA,EAAOC,gBAIXC,YAAA,EAAAC,qBAaM,MAbNC,EAaM,CAZJC,EAAAA,mBAAsD,OAAtDC,EAAsDC,EAAAA,gBAAfC,EAAA,KAAK,EAAA,CAAA,EAGpCA,EAAA,2BADRL,EAAAA,mBASS,SAAA,OAPP,KAAK,SACL,MAAM,yBACN,iBAAA,GACC,aAAYK,EAAA,UAAS,aAAA,WACrB,uCAAYR,EAAI,QAAA,EAAA,CAAA,MAAA,CAAA,EAAA,oBAEdQ,EAAA,UAAS,IAAA,GAAA,EAAA,EAAAC,CAAA,qCCNX,SAASC,EAAWC,EAA0B,CACnD,MAAMC,EAAYC,EAAAA,IAAe,CAAE,GAAGC,EAAAA,mBAAoB,EAEpDC,EAAa,IAAIC,oBAAkB,CACvC,SAAUL,EAAO,SACjB,SAAUA,EAAO,SACjB,OAAQA,EAAO,OACf,SAAWM,GAAS,CAClBL,EAAU,MAAQK,CACpB,CAAA,CACD,EAEDC,OAAAA,EAAAA,MACEP,EAAO,QACP,CAACQ,EAASC,EAAWC,IAAc,CAC5BF,IAELJ,EAAW,OAAOI,CAAO,EACzBE,EAAU,IAAMN,EAAW,QAAQ,EACrC,EACA,CAAE,UAAW,EAAA,CAAK,EAGpBO,kBAAgB,IAAMP,EAAW,QAAQ,EAElC,CACL,UAAAH,EACA,IAAK,CAACW,EAAeC,IAAqBT,EAAW,IAAIQ,EAASC,CAAO,EACzE,OAAQ,IAAMT,EAAW,OAAA,EACzB,QAAS,IAAMA,EAAW,QAAA,EAC1B,MAAO,IAAMA,EAAW,MAAA,EACxB,aAAeE,GAAoBF,EAAW,IAAIE,CAAI,CAAA,CAE1D,CCrBO,SAASQ,EAAiBd,EAAwD,CACvF,MAAMe,EAAeb,EAAAA,IAAI,IAAI,GAAa,EAEpCc,EAAOC,WAAS,IAAMC,SAAOlB,EAAO,OAAQA,EAAO,UAAA,CAAW,CAAC,EAC/DmB,EAAcF,EAAAA,SAAS,IAAMG,EAAAA,eAAeJ,EAAK,MAAOD,EAAa,KAAK,CAAC,EAC3EM,EAASJ,EAAAA,SAAS,IAAMK,EAAAA,WAAWH,EAAY,MAAOnB,EAAO,QAAA,CAAS,CAAC,EAE7E,SAASuB,EAAYC,EAAqB,CACxC,OAAOT,EAAa,MAAM,IAAIS,CAAE,CAClC,CAEA,SAASC,EAAOD,EAAkB,CAEhCT,EAAa,MAAQW,EAAAA,gBAAgBX,EAAa,MAAOS,CAAE,CAC7D,CAEA,SAASG,GAAkB,CACzBZ,EAAa,UAAY,GAC3B,CAEA,MAAO,CAAE,OAAAM,EAAQ,YAAAE,EAAa,OAAAE,EAAQ,UAAAE,CAAA,CACxC,CC5CO,SAASC,EAAeC,EAAiCC,EAAqBC,EAAwB,CAE3G,MAAMC,EAAO9B,EAAAA,IAAiB6B,EAAA,EAAYE,EAAAA,oBAAA,EAAwB,IAAI,EAEtE,SAASC,GAAe,CACtBF,EAAK,MAAQD,IAAYI,EAAAA,mBAAmBN,EAAO,MAAOC,EAAA,CAAO,EAAI,IACvE,CAEAM,OAAAA,EAAAA,UAAU,IAAM,CACdF,EAAA,EACA,OAAO,iBAAiB,SAAUA,EAAQ,CAAE,QAAS,GAAM,QAAS,GAAM,EAC1E,OAAO,iBAAiB,SAAUA,EAAQ,CAAE,QAAS,GAAM,CAC7D,CAAC,EAEDvB,EAAAA,gBAAgB,IAAM,CACpB,OAAO,oBAAoB,SAAUuB,EAAQ,CAAE,QAAS,GAAM,EAC9D,OAAO,oBAAoB,SAAUA,CAAM,CAC7C,CAAC,EAEM,CAAE,KAAAF,EAAM,OAAAE,CAAA,CACjB,moBCRA,MAAMG,EAAQxC,EA0DRR,EAAOC,EAMPgD,EAAcpC,EAAAA,IAAwB,IAAI,EAC1CqC,EAAgBrC,EAAAA,IAAwB,IAAI,EAE5C,CAAE,OAAAmB,EAAQ,YAAAE,EAAa,OAAAE,EAAQ,UAAAE,CAAA,EAAcb,EAAiB,CAClE,KAAM,IAAMuB,EAAM,KAClB,QAAS,IAAMA,EAAM,QACrB,UAAW,KAAO,CAChB,MAAOA,EAAM,MACb,YAAaA,EAAM,YACnB,YAAaA,EAAM,WAAA,EACrB,CACD,EAEKG,EAAUzC,EAAW,CACzB,QAASuC,EACT,SAAU,IAAMD,EAAM,SACtB,SAAU,IAAMA,EAAM,SACtB,OAAQ,IAAMA,EAAM,aAAeI,EAAAA,oBAAA,CACpC,EAEKC,EAAUd,EACdW,EACA,IAAMC,EAAQ,UAAU,MAAM,MAC9B,IAAMH,EAAM,UAAA,EAIRM,EAAQ1B,EAAAA,SAAS,IACrBoB,EAAM,WAAaO,EAAAA,cAAcvB,EAAO,MAAOqB,EAAQ,KAAK,MAAOL,EAAM,QAAQ,EAAIhB,EAAO,KAAA,EAGxFwB,EAAc5B,EAAAA,SAAS,KAAO,CAClC,MAAO,GAAGI,EAAO,MAAM,KAAK,KAC5B,OAAQ,GAAGA,EAAO,MAAM,MAAM,KAC9B,UAAWyB,EAAAA,eAAeN,EAAQ,UAAU,KAAK,CAAA,EACjD,EAEIO,EAAY9B,EAAAA,SAAS,IAAM,CAC/B,GAAIoB,EAAM,OAAS,UAAW,OAE9B,KAAM,CAAE,MAAAP,CAAA,EAAUU,EAAQ,UAAU,MACpC,MAAO,CACL,MAAO,GAAGnB,EAAO,MAAM,MAAQS,CAAK,KACpC,OAAQ,GAAGT,EAAO,MAAM,OAASS,CAAK,KACtC,SAAU,SAAA,CAEd,CAAC,EAED,SAASkB,EAAUC,EAAqB,CACtC,MAAO,CACL,KAAM,GAAGA,EAAK,CAAC,KACf,IAAK,GAAGA,EAAK,CAAC,KACd,MAAO,GAAGA,EAAK,KAAK,KACpB,OAAQ,GAAGA,EAAK,MAAM,IAAA,CAE1B,CAEA,SAASC,EAAYD,EAAyC,CAC5D,OAAO,OAAOZ,EAAM,WAAc,WAAaA,EAAM,UAAUY,CAAI,EAAIZ,EAAM,SAC/E,CAGA,SAASc,EAAQF,EAA6B,CAC5C,MAAMG,EAAOH,EAAK,KAClB,OAAOG,GAAM,MAAQA,GAAM,OAASA,GAAM,OAASH,EAAK,EAC1D,CAEA,SAASI,EAAWJ,EAA2B,CAC7CxB,EAAOwB,EAAK,EAAE,EACd5D,EAAK,SAAU4D,EAAM1B,EAAY0B,EAAK,EAAE,CAAC,CAC3C,CAEA,SAASK,EAAYL,EAAqBM,EAAyB,CACjElE,EAAK,aAAc4D,EAAMM,CAAK,EAC1BlB,EAAM,aAAeY,EAAK,eAAwBA,CAAI,CAC5D,CAGA,SAASO,EAAI3C,EAAwB,CACnC2B,EAAQ,IAAI,CAAE,MAAOnB,EAAO,MAAM,MAAO,OAAQA,EAAO,MAAM,MAAA,EAAUR,CAAO,CACjF,CAEAuB,OAAAA,EAAAA,UAAU,IAAM,CACVC,EAAM,YAAiBoB,EAAAA,SAAS,IAAMD,GAAK,CACjD,CAAC,EAEDjD,EAAAA,MACE,IAAMiC,EAAQ,UAAU,MACvBvC,GAAc,CAEbyC,EAAQ,OAAA,EACRrD,EAAK,YAAaY,CAAS,CAC7B,CAAA,EAIFM,EAAAA,MAAMc,EAAQ,KAAWoC,EAAAA,SAAS,IAAMf,EAAQ,OAAA,CAAQ,EAAC,EAEzDgB,EAAa,CACX,IAAAF,EACA,OAAQhB,EAAQ,OAChB,QAASA,EAAQ,QACjB,eAAgBA,EAAQ,MACxB,aAAcA,EAAQ,aACtB,UAAAb,EACA,OAAAF,EACA,OAAAJ,EACA,UAAWmB,EAAQ,SAAA,CACpB,wBAIChD,EAAAA,mBA6CM,MAAA,SA5CA,cAAJ,IAAI8C,EACJ,MAAKqB,EAAAA,eAAA,CAAC,YAAW,CAAA,sBACgB9D,EAAA,8BAAgCA,EAAA,OAAI,SAAA,CAAA,CAAA,EACpE,uBAAOkD,EAAA,KAAS,CAAA,GAEjBrD,EAAAA,mBAsCM,MAAA,SAtCG,gBAAJ,IAAI6C,EAAgB,MAAM,oBAAqB,uBAAOM,EAAA,KAAW,CAAA,kBACpErD,EAAAA,mBAMM,MAAA,CAND,MAAM,mBAAoB,MAAOoE,EAAAA,MAAAvC,CAAA,EAAO,MAAQ,OAAQuC,EAAAA,MAAAvC,CAAA,EAAO,MAAA,IAClE9B,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBAIWqE,WAAA,KAAAC,EAAAA,WAJcnB,EAAA,MAAM,MAAdoB,GACfC,EAAAA,WAEOC,EAAA,OAAA,OAAA,CAFY,KAAAF,CAAA,EAAnB,IAEO,CADLrE,EAAAA,mBAA+C,OAAA,CAAzC,MAAM,kBAAmB,EAAGqE,EAAK,IAAA,aAFC,OAAAA,EAAK,EAAE,iBAOrDxE,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBA4BMqE,WAAA,KAAAC,EAAAA,WA3BWnB,EAAA,MAAM,MAAdM,kBADTzD,EAAAA,mBA4BM,MAAA,CA1BH,IAAKyD,EAAK,GACX,MAAKU,EAAAA,eAAA,CAAC,kBACET,EAAYD,CAAI,CAAA,CAAA,EACvB,MAAKiB,EAAAA,eAAElB,EAAUC,CAAI,CAAA,EACrB,QAAKkB,GAAEb,EAAYL,EAAMkB,CAAM,CAAA,GAMhCH,aAeOC,EAAA,OAAA,OAAA,CAbJ,KAAAhB,EACA,KAAMA,EAAK,KACX,MAAOA,EAAK,MACZ,UAAWA,EAAK,UAChB,YAAcA,EAAK,YACnB,OAAM,IAAQI,EAAWJ,CAAI,CAAA,EAPhC,IAeO,CANLmB,EAAAA,YAKEC,EAAA,CAJC,MAAOlB,EAAQF,CAAI,EACnB,eAAcA,EAAK,YACnB,UAAWA,EAAK,UAChB,SAAMkB,GAAEd,EAAWJ,CAAI,CAAA,wfC7NpC,MAAMZ,EAAQxC,EAqBRR,EAAOC,EAEPgF,EAAapE,EAAAA,IAAmB,IAAI,EAEpCqE,EAAOtD,EAAAA,SAAS,IACpBuD,EAAAA,iBAAiBnC,EAAM,MAAO,CAAE,KAAMA,EAAM,KAAM,gBAAiBA,EAAM,eAAA,CAAiB,CAAA,EAG5F,SAASoC,EAAWC,EAA2B,CACzCA,EAAI,MAAMrF,EAAK,SAAUqF,EAAI,KAAMA,CAAG,CAC5C,6BAIElF,EAAAA,mBA2BM,MAAA,CA3BD,MAAKmE,EAAAA,eAAA,CAAC,aAAY,CAAA,sBAAkCY,EAAA,MAAK,UAAS,CAAA,CAAA,GAC1DA,EAAA,MAAK,WAAa1E,EAAA,0BAA7BL,EAAAA,mBAAwF,MAAxFC,EAAwFG,EAAAA,gBAAnBC,EAAA,UAAU,EAAA,CAAA,gCAE/EN,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBAuBMqE,WAAA,KAAAC,EAAAA,WAtBUS,EAAA,MAAK,KAAZG,kBADTlF,EAAAA,mBAuBM,MAAA,CArBH,IAAKkF,EAAI,IACV,wBAAM,kBAAiB,CACsB,0BAAAA,EAAI,SAA4C,yBAAAA,EAAI,SAAO,CAAKA,EAAI,QAAwC,sBAAAA,EAAI,SAA4C,yBAAAA,EAAI,OAAA,IAM5M,MAAOA,EAAI,uBAA2B,OACtC,QAAKP,GAAEM,EAAWC,CAAG,EACrB,aAAUP,GAAEG,EAAA,MAAaI,EAAI,IAC7B,4BAAYJ,EAAA,MAAU,KAAA,GAEPI,EAAI,oBAApBlF,EAAAA,mBAOWqE,EAAAA,SAAA,CAAA,IAAA,GAAA,CANGa,EAAI,cAAgBJ,UAAeI,EAAI,mBAAnDlF,qBAEO,OAFPM,EAEOF,EAAAA,gBADFC,EAAA,cAAc,EAAA,CAAA,+BAGnBH,qBAAyD,OAAzDiF,EAAyD/E,EAAAA,gBAAvB8E,EAAI,KAAK,IAAI,EAAA,CAAA,EAC/ChF,EAAAA,mBAAsD,OAAtDkF,EAAsDhF,EAAAA,gBAAnB8E,EAAI,KAAK,EAAA,CAAA,CAAA,+UC7CpD,MAAMrC,EAAQxC,EAwBRR,EAAOC,EAEPgD,EAAcpC,EAAAA,IAAwB,IAAI,EAC1C2E,EAAiB3E,EAAAA,IAAwB,IAAI,EAG7C4E,EAAW5E,EAAAA,IAAI,EAAK,EAC1B,IAAI6E,EAA+B,KAEnC3C,EAAAA,UAAU,IAAM,CACd2C,EAAUC,EAAAA,WAAW3C,EAAM,YAAc4C,GAAY,CACnDH,EAAS,MAAQG,CACnB,CAAC,CACH,CAAC,EAEDtE,EAAAA,gBAAgB,IAAMoE,KAAW,EAGjC,MAAMG,EAAcjE,WAAS,IAAMoB,EAAM,SAAWA,EAAM,eAAiByC,EAAS,MAAM,EAEpFK,EAAQlE,EAAAA,SAAS,IACrBmE,EAAAA,mBAAmB/C,EAAM,OAAQ,CAAE,UAAWA,EAAM,UAAW,SAAUA,EAAM,QAAA,CAAU,CAAA,EAGrFgD,EAAWpE,EAAAA,SAAS,KAAO,CAAE,MAAO,GAAGkE,EAAM,MAAM,KAAK,IAAA,EAAO,EAE/DG,EAAerE,EAAAA,SAAS,IAC5BoB,EAAM,OAAS,CAAE,YAAa,GAAG8C,EAAM,MAAM,MAAM,MAAS,MAAA,EAY9D,SAASI,EAAcC,EAAkC,CACvD,MAAMhF,EAAU8B,EAAY,MACtBmD,EAAYC,EAAAA,iBAAiBlF,CAAO,EAC1C,GAAI,CAACA,GAAW,CAACiF,EAAW,OAE5B,MAAM7E,EAAUiE,EAAe,MAEzBN,EAAO3D,EACT+E,EAAAA,cAAc/E,EAAS4E,EAAO,MAAOA,EAAO,MAAOL,EAAM,MAAM,MAAM,EAAE,CAAC,EACxE,OAEES,EAAMrB,EACRsB,EAAAA,cAActB,EAAMkB,CAAS,EAC7B,CAAE,KAAMK,EAAAA,aAAatF,EAASiF,CAAS,EAAID,EAAO,MAAQA,EAAO,MAAO,MAAOA,EAAO,KAAA,EAE1FO,EAAAA,iBACEN,EACAO,kBAAgB,CACd,GAAGJ,EACH,SAAUH,EAAU,YACpB,YAAaA,EAAU,WAAA,CACxB,CAAA,CAEL,CAGA,SAASQ,EAAST,EAAkC,CAC7CN,EAAY,QAEjB7F,EAAK,SAAUmG,CAAM,EACrBD,EAAcC,CAAM,EACtB,6BAIEhG,EAAAA,mBAiBM,MAAA,SAjBG,cAAJ,IAAI8C,EAAc,MAAM,WAAA,GAChBzC,EAAA,sBAAXL,EAAAA,mBAUM,MAAA,OAVa,MAAM,iBAAkB,uBAAO6F,EAAA,KAAQ,CAAA,IACxD9F,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBAQMqE,WAAA,KAAAC,EAAAA,WAPaqB,EAAA,MAAM,QAAhBK,kBADThG,EAAAA,mBAQM,MAAA,CANH,IAAKgG,EAAO,IACb,MAAM,kBACL,MAAKtB,EAAAA,eAAA,CAAA,MAAA,GAAcsB,EAAO,KAAK,KAAA,EAC/B,QAAKrB,GAAE8B,EAAST,CAAM,CAAA,GAEvBxB,EAAAA,WAA2DC,EAAA,OAAA,QAAA,CAAvC,MAAOuB,CAAA,EAA3B,IAA2D,CAArBU,EAAAA,gBAAAtG,EAAAA,gBAAA4F,EAAO,IAAI,EAAA,CAAA,CAAA,mDAIrD9F,EAAAA,mBAGM,MAAA,SAHG,iBAAJ,IAAImF,EAAiB,MAAM,qBAAsB,uBAAOS,EAAA,KAAY,CAAA,GAEvEtB,aAAQC,EAAA,OAAA,SAAA,CAAA,+wHCtIdkC,EAAAA,aAAaC,CAAM,EAqCZ,MAAMC,EAAyB,CACpC,QAAQC,EAAU,CAChBA,EAAI,UAAU,WAAYC,CAAQ,EAClCD,EAAI,UAAU,eAAgBjC,CAAY,EAC1CiC,EAAI,UAAU,cAAeE,CAAW,EACxCF,EAAI,UAAU,gBAAiBG,CAAa,CAC9C,CACF"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { defineComponent as N, openBlock as c, createElementBlock as d, createElementVNode as C, toDisplayString as k, withModifiers as j, createCommentVNode as S, ref as w, watch as D, onBeforeUnmount as V, computed as p, onMounted as A, nextTick as W, normalizeStyle as y, normalizeClass as I, unref as H, Fragment as B, renderList as M, renderSlot as O, createVNode as Q, createTextVNode as G } from "vue";
2
- import { IDENTITY_TRANSFORM as Y, PanZoomController as Z, toTree as K, applyCollapsed as q, layoutTree as J, toggleCollapsed as X, estimateVisibleRect as ee, measureVisibleRect as te, DEFAULT_OVERSCAN as oe, visibleLayout as ne, toCssTransform as re, DEFAULT_SCALE_LIMITS as ae, buildBracketCard as ie, MOBILE_MEDIA_QUERY as le, watchMedia as se, buildBracketRounds as ce, nodesInColumn as de, scrollToNode as ue, findScrollParent as ve, offsetWithin as fe, smoothScrollLeft as pe, roundScrollLeft as he, injectStyles as be } from "@bigplay/tree-view-core";
1
+ import { defineComponent as R, openBlock as d, createElementBlock as c, createElementVNode as C, toDisplayString as w, withModifiers as j, createCommentVNode as E, ref as g, watch as N, onBeforeUnmount as D, computed as h, onMounted as V, nextTick as P, normalizeStyle as y, normalizeClass as I, unref as W, Fragment as B, renderList as M, renderSlot as O, createVNode as Q, createTextVNode as G } from "vue";
2
+ import { IDENTITY_TRANSFORM as Y, PanZoomController as Z, toTree as K, applyCollapsed as q, layoutTree as J, toggleCollapsed as X, estimateVisibleRect as ee, measureVisibleRect as te, DEFAULT_OVERSCAN as oe, visibleLayout as ne, toCssTransform as re, DEFAULT_SCALE_LIMITS as ae, buildBracketCard as ie, MOBILE_MEDIA_QUERY as le, watchMedia as se, buildBracketRounds as de, findScrollParent as ce, nodesInColumn as ue, horizontalBox as ve, offsetWithin as fe, smoothScrollLeft as pe, roundScrollLeft as he, injectStyles as be } from "@bigplay/tree-view-core";
3
3
  import { DEFAULT_OPTIONS as Pe, MOBILE_MEDIA_QUERY as We, bracketCardHeight as He, buildBracketCard as Ue, buildBracketRounds as je } from "@bigplay/tree-view-core";
4
- const me = { class: "tree-view-card" }, ge = { class: "tree-view-card__label" }, we = ["aria-label"], U = /* @__PURE__ */ N({
4
+ const me = { class: "tree-view-card" }, ge = { class: "tree-view-card__label" }, we = ["aria-label"], H = /* @__PURE__ */ R({
5
5
  __name: "TreeNodeCard",
6
6
  props: {
7
7
  label: {},
@@ -11,21 +11,21 @@ const me = { class: "tree-view-card" }, ge = { class: "tree-view-card__label" },
11
11
  emits: ["toggle"],
12
12
  setup(t, { emit: l }) {
13
13
  const o = l;
14
- return (e, r) => (c(), d("div", me, [
15
- C("span", ge, k(t.label), 1),
16
- t.hasChildren ? (c(), d("button", {
14
+ return (e, r) => (d(), c("div", me, [
15
+ C("span", ge, w(t.label), 1),
16
+ t.hasChildren ? (d(), c("button", {
17
17
  key: 0,
18
18
  type: "button",
19
19
  class: "tree-view-card__toggle",
20
20
  "data-tv-no-pan": "",
21
21
  "aria-label": t.collapsed ? "Развернуть" : "Свернуть",
22
22
  onClick: r[0] || (r[0] = j((v) => o("toggle"), ["stop"]))
23
- }, k(t.collapsed ? "+" : "−"), 9, we)) : S("", !0)
23
+ }, w(t.collapsed ? "+" : "−"), 9, we)) : E("", !0)
24
24
  ]));
25
25
  }
26
26
  });
27
27
  function ke(t) {
28
- const l = w({ ...Y }), o = new Z({
28
+ const l = g({ ...Y }), o = new Z({
29
29
  pannable: t.pannable,
30
30
  zoomable: t.zoomable,
31
31
  limits: t.limits,
@@ -33,13 +33,13 @@ function ke(t) {
33
33
  l.value = e;
34
34
  }
35
35
  });
36
- return D(
36
+ return N(
37
37
  t.element,
38
38
  (e, r, v) => {
39
39
  e && (o.attach(e), v(() => o.detach()));
40
40
  },
41
41
  { immediate: !0 }
42
- ), V(() => o.detach()), {
42
+ ), D(() => o.detach()), {
43
43
  transform: l,
44
44
  fit: (e, r) => o.fit(e, r),
45
45
  zoomIn: () => o.zoomIn(),
@@ -49,12 +49,12 @@ function ke(t) {
49
49
  };
50
50
  }
51
51
  function _e(t) {
52
- const l = w(/* @__PURE__ */ new Set()), o = p(() => K(t.data(), t.accessors())), e = p(() => q(o.value, l.value)), r = p(() => J(e.value, t.options()));
53
- function v(h) {
54
- return l.value.has(h);
52
+ const l = g(/* @__PURE__ */ new Set()), o = h(() => K(t.data(), t.accessors())), e = h(() => q(o.value, l.value)), r = h(() => J(e.value, t.options()));
53
+ function v(b) {
54
+ return l.value.has(b);
55
55
  }
56
- function m(h) {
57
- l.value = X(l.value, h);
56
+ function m(b) {
57
+ l.value = X(l.value, b);
58
58
  }
59
59
  function a() {
60
60
  l.value = /* @__PURE__ */ new Set();
@@ -62,17 +62,17 @@ function _e(t) {
62
62
  return { layout: r, isCollapsed: v, toggle: m, expandAll: a };
63
63
  }
64
64
  function xe(t, l, o) {
65
- const e = w(o() ? ee() : null);
65
+ const e = g(o() ? ee() : null);
66
66
  function r() {
67
67
  e.value = o() ? te(t.value, l()) : null;
68
68
  }
69
- return A(() => {
69
+ return V(() => {
70
70
  r(), window.addEventListener("scroll", r, { passive: !0, capture: !0 }), window.addEventListener("resize", r, { passive: !0 });
71
- }), V(() => {
71
+ }), D(() => {
72
72
  window.removeEventListener("scroll", r, { capture: !0 }), window.removeEventListener("resize", r);
73
73
  }), { rect: e, update: r };
74
74
  }
75
- const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__ */ N({
75
+ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__ */ R({
76
76
  __name: "TreeView",
77
77
  props: {
78
78
  data: {},
@@ -92,7 +92,7 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
92
92
  },
93
93
  emits: ["node-click", "toggle", "transform"],
94
94
  setup(t, { expose: l, emit: o }) {
95
- const e = t, r = o, v = w(null), m = w(null), { layout: a, isCollapsed: h, toggle: i, expandAll: _ } = _e({
95
+ const e = t, r = o, v = g(null), m = g(null), { layout: a, isCollapsed: b, toggle: i, expandAll: k } = _e({
96
96
  data: () => e.data,
97
97
  options: () => e.options,
98
98
  accessors: () => ({
@@ -100,31 +100,31 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
100
100
  getChildren: e.getChildren,
101
101
  getParentId: e.getParentId
102
102
  })
103
- }), f = ke({
103
+ }), p = ke({
104
104
  element: v,
105
105
  pannable: () => e.pannable,
106
106
  zoomable: () => e.zoomable,
107
107
  limits: () => e.scaleLimits ?? ae
108
108
  }), T = xe(
109
109
  m,
110
- () => f.transform.value.scale,
110
+ () => p.transform.value.scale,
111
111
  () => e.virtualize
112
- ), L = p(
112
+ ), S = h(
113
113
  () => e.virtualize ? ne(a.value, T.rect.value, e.overscan) : a.value
114
- ), u = p(() => ({
114
+ ), u = h(() => ({
115
115
  width: `${a.value.width}px`,
116
116
  height: `${a.value.height}px`,
117
- transform: re(f.transform.value)
118
- })), z = p(() => {
117
+ transform: re(p.transform.value)
118
+ })), _ = h(() => {
119
119
  if (e.size !== "content") return;
120
- const { scale: n } = f.transform.value;
120
+ const { scale: n } = p.transform.value;
121
121
  return {
122
122
  width: `${a.value.width * n}px`,
123
123
  height: `${a.value.height * n}px`,
124
124
  overflow: "visible"
125
125
  };
126
126
  });
127
- function b(n) {
127
+ function f(n) {
128
128
  return {
129
129
  left: `${n.x}px`,
130
130
  top: `${n.y}px`,
@@ -132,46 +132,46 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
132
132
  height: `${n.height}px`
133
133
  };
134
134
  }
135
- function $(n) {
135
+ function z(n) {
136
136
  return typeof e.nodeClass == "function" ? e.nodeClass(n) : e.nodeClass;
137
137
  }
138
- function E(n) {
138
+ function L(n) {
139
139
  const x = n.data;
140
140
  return x?.name ?? x?.title ?? x?.label ?? n.id;
141
141
  }
142
- function g(n) {
143
- i(n.id), r("toggle", n, h(n.id));
142
+ function $(n) {
143
+ i(n.id), r("toggle", n, b(n.id));
144
144
  }
145
- function R(n, x) {
146
- r("node-click", n, x), e.collapsible && n.hasChildren && g(n);
145
+ function U(n, x) {
146
+ r("node-click", n, x), e.collapsible && n.hasChildren && $(n);
147
147
  }
148
- function F(n) {
149
- f.fit({ width: a.value.width, height: a.value.height }, n);
148
+ function A(n) {
149
+ p.fit({ width: a.value.width, height: a.value.height }, n);
150
150
  }
151
- return A(() => {
152
- e.fitOnMount && W(() => F());
153
- }), D(
154
- () => f.transform.value,
151
+ return V(() => {
152
+ e.fitOnMount && P(() => A());
153
+ }), N(
154
+ () => p.transform.value,
155
155
  (n) => {
156
156
  T.update(), r("transform", n);
157
157
  }
158
- ), D(a, () => {
159
- W(() => T.update());
158
+ ), N(a, () => {
159
+ P(() => T.update());
160
160
  }), l({
161
- fit: F,
162
- zoomIn: f.zoomIn,
163
- zoomOut: f.zoomOut,
164
- resetTransform: f.reset,
165
- setTransform: f.setTransform,
166
- expandAll: _,
161
+ fit: A,
162
+ zoomIn: p.zoomIn,
163
+ zoomOut: p.zoomOut,
164
+ resetTransform: p.reset,
165
+ setTransform: p.setTransform,
166
+ expandAll: k,
167
167
  toggle: i,
168
168
  layout: a,
169
- transform: f.transform
170
- }), (n, x) => (c(), d("div", {
169
+ transform: p.transform
170
+ }), (n, x) => (d(), c("div", {
171
171
  ref_key: "rootElement",
172
172
  ref: v,
173
173
  class: I(["tree-view", { "tree-view--pannable": t.pannable, "tree-view--content": t.size === "content" }]),
174
- style: y(z.value)
174
+ style: y(_.value)
175
175
  }, [
176
176
  C("div", {
177
177
  ref_key: "canvasElement",
@@ -179,23 +179,23 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
179
179
  class: "tree-view__canvas",
180
180
  style: y(u.value)
181
181
  }, [
182
- (c(), d("svg", {
182
+ (d(), c("svg", {
183
183
  class: "tree-view__links",
184
- width: H(a).width,
185
- height: H(a).height
184
+ width: W(a).width,
185
+ height: W(a).height
186
186
  }, [
187
- (c(!0), d(B, null, M(L.value.links, (s) => O(n.$slots, "link", { link: s }, () => [
187
+ (d(!0), c(B, null, M(S.value.links, (s) => O(n.$slots, "link", { link: s }, () => [
188
188
  C("path", {
189
189
  class: "tree-view__link",
190
190
  d: s.path
191
191
  }, null, 8, Ce)
192
192
  ], void 0, s.id)), 128))
193
193
  ], 8, ye)),
194
- (c(!0), d(B, null, M(L.value.nodes, (s) => (c(), d("div", {
194
+ (d(!0), c(B, null, M(S.value.nodes, (s) => (d(), c("div", {
195
195
  key: s.id,
196
- class: I(["tree-view__node", $(s)]),
197
- style: y(b(s)),
198
- onClick: (P) => R(s, P)
196
+ class: I(["tree-view__node", z(s)]),
197
+ style: y(f(s)),
198
+ onClick: (F) => U(s, F)
199
199
  }, [
200
200
  O(n.$slots, "node", {
201
201
  node: s,
@@ -203,13 +203,13 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
203
203
  depth: s.depth,
204
204
  collapsed: s.collapsed,
205
205
  hasChildren: s.hasChildren,
206
- toggle: () => g(s)
206
+ toggle: () => $(s)
207
207
  }, () => [
208
- Q(U, {
209
- label: E(s),
208
+ Q(H, {
209
+ label: L(s),
210
210
  "has-children": s.hasChildren,
211
211
  collapsed: s.collapsed,
212
- onToggle: (P) => g(s)
212
+ onToggle: (F) => $(s)
213
213
  }, null, 8, ["label", "has-children", "collapsed", "onToggle"])
214
214
  ])
215
215
  ], 14, Te))), 128))
@@ -219,10 +219,10 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
219
219
  }), $e = {
220
220
  key: 0,
221
221
  class: "tv-bracket__hint"
222
- }, Ee = ["title", "onClick", "onMouseenter"], Se = {
222
+ }, Ee = ["title", "onClick", "onMouseenter"], Be = {
223
223
  key: 0,
224
224
  class: "tv-bracket__tooltip"
225
- }, Be = { class: "tv-bracket__name" }, Le = { class: "tv-bracket__score" }, Ie = /* @__PURE__ */ N({
225
+ }, Se = { class: "tv-bracket__name" }, Le = { class: "tv-bracket__score" }, Ie = /* @__PURE__ */ R({
226
226
  __name: "BracketCard",
227
227
  props: {
228
228
  match: {},
@@ -233,17 +233,17 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
233
233
  },
234
234
  emits: ["select"],
235
235
  setup(t, { emit: l }) {
236
- const o = t, e = l, r = w(null), v = p(
236
+ const o = t, e = l, r = g(null), v = h(
237
237
  () => ie(o.match, { rows: o.rows, techDefeatLabel: o.techDefeatLabel })
238
238
  );
239
239
  function m(a) {
240
240
  a.team && e("select", a.team, a);
241
241
  }
242
- return (a, h) => (c(), d("div", {
242
+ return (a, b) => (d(), c("div", {
243
243
  class: I(["tv-bracket", { "tv-bracket--my-team": v.value.hasMyTeam }])
244
244
  }, [
245
- v.value.hasMyTeam && t.myTeamHint ? (c(), d("div", $e, k(t.myTeamHint), 1)) : S("", !0),
246
- (c(!0), d(B, null, M(v.value.rows, (i) => (c(), d("div", {
245
+ v.value.hasMyTeam && t.myTeamHint ? (d(), c("div", $e, w(t.myTeamHint), 1)) : E("", !0),
246
+ (d(!0), c(B, null, M(v.value.rows, (i) => (d(), c("div", {
247
247
  key: i.key,
248
248
  class: I(["tv-bracket__row", {
249
249
  "tv-bracket__row--winner": i.isWinner,
@@ -252,19 +252,19 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
252
252
  "tv-bracket__row--empty": i.isEmpty
253
253
  }]),
254
254
  title: i.isMyTeam ? "Моя команда" : void 0,
255
- onClick: (_) => m(i),
256
- onMouseenter: (_) => r.value = i.key,
257
- onMouseleave: h[0] || (h[0] = (_) => r.value = null)
255
+ onClick: (k) => m(i),
256
+ onMouseenter: (k) => r.value = i.key,
257
+ onMouseleave: b[0] || (b[0] = (k) => r.value = null)
258
258
  }, [
259
- i.team ? (c(), d(B, { key: 0 }, [
260
- i.isTechDefeat && r.value === i.key ? (c(), d("span", Se, k(t.techDefeatHint), 1)) : S("", !0),
261
- C("span", Be, k(i.team.name), 1),
262
- C("span", Le, k(i.score), 1)
263
- ], 64)) : S("", !0)
259
+ i.team ? (d(), c(B, { key: 0 }, [
260
+ i.isTechDefeat && r.value === i.key ? (d(), c("span", Be, w(t.techDefeatHint), 1)) : E("", !0),
261
+ C("span", Se, w(i.team.name), 1),
262
+ C("span", Le, w(i.score), 1)
263
+ ], 64)) : E("", !0)
264
264
  ], 42, Ee))), 128))
265
265
  ], 2));
266
266
  }
267
- }), Me = ["onClick"], Oe = /* @__PURE__ */ N({
267
+ }), Me = ["onClick"], Oe = /* @__PURE__ */ R({
268
268
  __name: "BracketRounds",
269
269
  props: {
270
270
  rounds: {},
@@ -276,73 +276,70 @@ const ye = ["width", "height"], Ce = ["d"], Te = ["onClick"], ze = /* @__PURE__
276
276
  },
277
277
  emits: ["select"],
278
278
  setup(t, { emit: l }) {
279
- const o = t, e = l, r = w(null), v = w(null), m = w(!1);
279
+ const o = t, e = l, r = g(null), v = g(null), m = g(!1);
280
280
  let a = null;
281
- A(() => {
281
+ V(() => {
282
282
  a = se(o.mobileQuery, (u) => {
283
283
  m.value = u;
284
284
  });
285
- }), V(() => a?.());
286
- const h = p(() => o.isShow && (o.scrollOnClick ?? m.value)), i = p(
287
- () => ce(o.rounds, { nodeWidth: o.nodeWidth, levelGap: o.levelGap })
288
- ), _ = p(() => ({ width: `${i.value.width}px` })), f = p(
285
+ }), D(() => a?.());
286
+ const b = h(() => o.isShow && (o.scrollOnClick ?? m.value)), i = h(
287
+ () => de(o.rounds, { nodeWidth: o.nodeWidth, levelGap: o.levelGap })
288
+ ), k = h(() => ({ width: `${i.value.width}px` })), p = h(
289
289
  () => o.isShow ? { paddingLeft: `${i.value.offset}px` } : void 0
290
290
  );
291
291
  function T(u) {
292
- const z = v.value, b = z ? de(z, u.index, u.width, i.value.offset) : [], $ = b[Math.floor(b.length / 2)];
293
- if ($ && ue($)) return;
294
- const E = r.value, g = ve(E);
295
- if (!E || !g) return;
296
- const R = fe(E, g) + u.index * u.width;
292
+ const _ = r.value, f = ce(_);
293
+ if (!_ || !f) return;
294
+ const z = v.value, L = z ? ue(z, u.index, u.width, i.value.offset)[0] : void 0, $ = L ? ve(L, f) : { left: fe(_, f) + u.index * u.width, width: u.width };
297
295
  pe(
298
- g,
296
+ f,
299
297
  he({
300
- left: R,
301
- width: u.width,
302
- viewport: g.clientWidth,
303
- scrollWidth: g.scrollWidth
298
+ ...$,
299
+ viewport: f.clientWidth,
300
+ scrollWidth: f.scrollWidth
304
301
  })
305
302
  );
306
303
  }
307
- function L(u) {
308
- h.value && (e("select", u), T(u));
304
+ function S(u) {
305
+ b.value && (e("select", u), T(u));
309
306
  }
310
- return (u, z) => (c(), d("div", {
307
+ return (u, _) => (d(), c("div", {
311
308
  ref_key: "rootElement",
312
309
  ref: r,
313
310
  class: "tv-rounds"
314
311
  }, [
315
- t.isShow ? (c(), d("div", {
312
+ t.isShow ? (d(), c("div", {
316
313
  key: 0,
317
314
  class: "tv-rounds__bar",
318
- style: y(_.value)
315
+ style: y(k.value)
319
316
  }, [
320
- (c(!0), d(B, null, M(i.value.columns, (b) => (c(), d("div", {
321
- key: b.key,
317
+ (d(!0), c(B, null, M(i.value.columns, (f) => (d(), c("div", {
318
+ key: f.key,
322
319
  class: "tv-rounds__item",
323
- style: y({ width: `${b.width}px` }),
324
- onClick: ($) => L(b)
320
+ style: y({ width: `${f.width}px` }),
321
+ onClick: (z) => S(f)
325
322
  }, [
326
- O(u.$slots, "round", { round: b }, () => [
327
- G(k(b.name), 1)
323
+ O(u.$slots, "round", { round: f }, () => [
324
+ G(w(f.name), 1)
328
325
  ])
329
326
  ], 12, Me))), 128))
330
- ], 4)) : S("", !0),
327
+ ], 4)) : E("", !0),
331
328
  C("div", {
332
329
  ref_key: "contentElement",
333
330
  ref: v,
334
331
  class: "tv-rounds__content",
335
- style: y(f.value)
332
+ style: y(p.value)
336
333
  }, [
337
334
  O(u.$slots, "default")
338
335
  ], 4)
339
336
  ], 512));
340
337
  }
341
- }), Ne = ".tree-view{--tv-link-color: #cbd5e1;--tv-link-width: 2px;--tv-node-bg: #ffffff;--tv-node-color: #0f172a;--tv-node-border: 1px solid #e2e8f0;--tv-node-radius: 10px;--tv-node-shadow: 0 1px 2px rgb(15 23 42 / 8%);--tv-node-padding: 8px 12px;--tv-accent: #2563eb;position:relative;width:100%;height:100%;overflow:hidden;touch-action:none}.tree-view--pannable{cursor:grab}.tree-view--content{width:auto;height:auto;overflow:visible}.tree-view__canvas{position:absolute;top:0;left:0;transform-origin:0 0}.tree-view__links{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}.tree-view__link{fill:none;stroke:var(--tv-link-color);stroke-width:var(--tv-link-width)}.tree-view__node{position:absolute;box-sizing:border-box;display:flex;align-items:stretch;justify-content:stretch}.tree-view-card{box-sizing:border-box;display:flex;gap:8px;align-items:center;justify-content:space-between;width:100%;height:100%;padding:var(--tv-node-padding);overflow:hidden;color:var(--tv-node-color);background:var(--tv-node-bg);border:var(--tv-node-border);border-radius:var(--tv-node-radius);box-shadow:var(--tv-node-shadow)}.tree-view-card__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-view-card__toggle{flex:none;width:20px;height:20px;padding:0;font:inherit;line-height:1;color:var(--tv-accent);cursor:pointer;background:none;border:none}.tv-bracket{--tv-bracket-winner-bg: #0427c4;--tv-bracket-winner-color: #ffffff;--tv-bracket-loser-bg: #e5e5e5;--tv-bracket-loser-color: #7d7d8d;--tv-bracket-my-bg: #00146c;--tv-bracket-my-color: #ffffff;--tv-bracket-radius: 6px;position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;font-size:12px}.tv-bracket--my-team{border-radius:var(--tv-bracket-radius);outline:2px solid var(--tv-bracket-my-bg)}.tv-bracket__hint{position:absolute;right:0;bottom:100%;left:0;padding:3px 8px;overflow:hidden;font-size:11px;font-weight:600;line-height:1.2;color:var(--tv-bracket-my-color);text-align:center;text-overflow:ellipsis;white-space:nowrap;background:var(--tv-bracket-my-bg);border-radius:var(--tv-bracket-radius) var(--tv-bracket-radius) 0 0}.tv-bracket__row{position:relative;box-sizing:border-box;display:flex;flex:1 1 0;align-items:center;justify-content:space-between;min-height:0;padding:4px 8px;cursor:pointer;transition:all 75ms}.tv-bracket__row--winner{font-weight:700;color:var(--tv-bracket-winner-color);background:var(--tv-bracket-winner-bg)}.tv-bracket__row--loser{color:var(--tv-bracket-loser-color);background:var(--tv-bracket-loser-bg)}.tv-bracket__row--my{color:var(--tv-bracket-my-color);background:var(--tv-bracket-my-bg)}.tv-bracket__row--empty{cursor:default;background:var(--tv-bracket-loser-bg)}.tv-bracket__name{max-width:120px;overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tv-bracket__score{display:flex;flex:none;align-items:center;justify-content:center;width:20px;height:20px;font-weight:700;font-variant-numeric:tabular-nums}.tv-bracket__tooltip{position:absolute;top:-26px;left:6px;z-index:5000;padding:4px 8px;font-weight:400;color:var(--tv-bracket-loser-color);white-space:nowrap;background:#fff;border:1px solid #d1d5db;border-radius:4px;box-shadow:0 4px 10px #0f172a26}.tv-rounds{--tv-rounds-bg: #f5f5f7;--tv-rounds-color: #9ca3af;--tv-rounds-border: 2px solid #e5e7eb;--tv-rounds-gap: 20px;display:flex;flex-direction:column;width:max-content;min-width:100%}.tv-rounds__bar{display:flex;min-width:100%;margin-bottom:var(--tv-rounds-gap);background:var(--tv-rounds-bg);border-bottom:var(--tv-rounds-border)}.tv-rounds__item{flex:none;box-sizing:border-box;padding:10px 20px;font:inherit;font-size:14px;font-weight:600;color:var(--tv-rounds-color);text-align:center;text-transform:uppercase;white-space:nowrap;background:none;border:none;border-right:var(--tv-rounds-border)}";
342
- be(Ne);
338
+ }), Re = ".tree-view{--tv-link-color: #cbd5e1;--tv-link-width: 2px;--tv-node-bg: #ffffff;--tv-node-color: #0f172a;--tv-node-border: 1px solid #e2e8f0;--tv-node-radius: 10px;--tv-node-shadow: 0 1px 2px rgb(15 23 42 / 8%);--tv-node-padding: 8px 12px;--tv-accent: #2563eb;position:relative;width:100%;height:100%;overflow:hidden;touch-action:none}.tree-view--pannable{cursor:grab}.tree-view--content{width:auto;height:auto;overflow:visible}.tree-view__canvas{position:absolute;top:0;left:0;transform-origin:0 0}.tree-view__links{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}.tree-view__link{fill:none;stroke:var(--tv-link-color);stroke-width:var(--tv-link-width)}.tree-view__node{position:absolute;box-sizing:border-box;display:flex;align-items:stretch;justify-content:stretch}.tree-view-card{box-sizing:border-box;display:flex;gap:8px;align-items:center;justify-content:space-between;width:100%;height:100%;padding:var(--tv-node-padding);overflow:hidden;color:var(--tv-node-color);background:var(--tv-node-bg);border:var(--tv-node-border);border-radius:var(--tv-node-radius);box-shadow:var(--tv-node-shadow)}.tree-view-card__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-view-card__toggle{flex:none;width:20px;height:20px;padding:0;font:inherit;line-height:1;color:var(--tv-accent);cursor:pointer;background:none;border:none}.tv-bracket{--tv-bracket-winner-bg: #0427c4;--tv-bracket-winner-color: #ffffff;--tv-bracket-loser-bg: #e5e5e5;--tv-bracket-loser-color: #7d7d8d;--tv-bracket-my-bg: #00146c;--tv-bracket-my-color: #ffffff;--tv-bracket-radius: 6px;position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;font-size:12px}.tv-bracket--my-team{border-radius:var(--tv-bracket-radius);outline:2px solid var(--tv-bracket-my-bg)}.tv-bracket__hint{position:absolute;right:0;bottom:100%;left:0;padding:3px 8px;overflow:hidden;font-size:11px;font-weight:600;line-height:1.2;color:var(--tv-bracket-my-color);text-align:center;text-overflow:ellipsis;white-space:nowrap;background:var(--tv-bracket-my-bg);border-radius:var(--tv-bracket-radius) var(--tv-bracket-radius) 0 0}.tv-bracket__row{position:relative;box-sizing:border-box;display:flex;flex:1 1 0;align-items:center;justify-content:space-between;min-height:0;padding:4px 8px;cursor:pointer;transition:all 75ms}.tv-bracket__row--winner{font-weight:700;color:var(--tv-bracket-winner-color);background:var(--tv-bracket-winner-bg)}.tv-bracket__row--loser{color:var(--tv-bracket-loser-color);background:var(--tv-bracket-loser-bg)}.tv-bracket__row--my{color:var(--tv-bracket-my-color);background:var(--tv-bracket-my-bg)}.tv-bracket__row--empty{cursor:default;background:var(--tv-bracket-loser-bg)}.tv-bracket__name{max-width:120px;overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tv-bracket__score{display:flex;flex:none;align-items:center;justify-content:center;width:20px;height:20px;font-weight:700;font-variant-numeric:tabular-nums}.tv-bracket__tooltip{position:absolute;top:-26px;left:6px;z-index:5000;padding:4px 8px;font-weight:400;color:var(--tv-bracket-loser-color);white-space:nowrap;background:#fff;border:1px solid #d1d5db;border-radius:4px;box-shadow:0 4px 10px #0f172a26}.tv-rounds{--tv-rounds-bg: #f5f5f7;--tv-rounds-color: #9ca3af;--tv-rounds-border: 2px solid #e5e7eb;--tv-rounds-gap: 20px;display:flex;flex-direction:column;width:max-content;min-width:100%}.tv-rounds__bar{display:flex;min-width:100%;margin-bottom:var(--tv-rounds-gap);background:var(--tv-rounds-bg);border-bottom:var(--tv-rounds-border)}.tv-rounds__item{flex:none;box-sizing:border-box;padding:10px 20px;font:inherit;font-size:14px;font-weight:600;color:var(--tv-rounds-color);text-align:center;text-transform:uppercase;white-space:nowrap;background:none;border:none;border-right:var(--tv-rounds-border)}";
339
+ be(Re);
343
340
  const Ve = {
344
341
  install(t) {
345
- t.component("TreeView", ze), t.component("TreeNodeCard", U), t.component("BracketCard", Ie), t.component("BracketRounds", Oe);
342
+ t.component("TreeView", ze), t.component("TreeNodeCard", H), t.component("BracketCard", Ie), t.component("BracketRounds", Oe);
346
343
  }
347
344
  };
348
345
  export {
@@ -350,7 +347,7 @@ export {
350
347
  Oe as BracketRounds,
351
348
  Pe as DEFAULT_OPTIONS,
352
349
  We as MOBILE_MEDIA_QUERY,
353
- U as TreeNodeCard,
350
+ H as TreeNodeCard,
354
351
  ze as TreeView,
355
352
  Ve as TreeViewPlugin,
356
353
  He as bracketCardHeight,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/TreeNodeCard.vue","../src/usePanZoom.ts","../src/useTreeLayout.ts","../src/useVisibleRect.ts","../src/TreeView.vue","../src/BracketCard.vue","../src/BracketRounds.vue","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Карточка узла по умолчанию.\n * Нужна только чтобы «из коробки» что-то отображалось —\n * в реальном проекте обычно используют слот #node и рисуют свой блок.\n */\ndefineProps<{\n label: string\n hasChildren?: boolean\n collapsed?: boolean\n}>()\n\nconst emit = defineEmits<{ (e: 'toggle'): void }>()\n</script>\n\n<template>\n <div class=\"tree-view-card\">\n <span class=\"tree-view-card__label\">{{ label }}</span>\n\n <button\n v-if=\"hasChildren\"\n type=\"button\"\n class=\"tree-view-card__toggle\"\n data-tv-no-pan\n :aria-label=\"collapsed ? 'Развернуть' : 'Свернуть'\"\n @click.stop=\"emit('toggle')\"\n >\n {{ collapsed ? '+' : '−' }}\n </button>\n </div>\n</template>\n","import { onBeforeUnmount, ref, watch, type Ref } from 'vue'\nimport {\n IDENTITY_TRANSFORM,\n PanZoomController,\n type ScaleLimits,\n type Size,\n type Transform,\n} from '@bigplay/tree-view-core'\n\nexport interface UsePanZoomParams {\n /** Элемент, который слушаем: по нему тянут и крутят колесо. */\n element: Ref<HTMLElement | null>\n pannable: () => boolean\n zoomable: () => boolean\n limits: () => ScaleLimits\n}\n\n/**\n * Тонкая обёртка над `PanZoomController` из core:\n * переносит его состояние в реактивный `ref` и следит за жизненным циклом.\n */\nexport function usePanZoom(params: UsePanZoomParams) {\n const transform = ref<Transform>({ ...IDENTITY_TRANSFORM })\n\n const controller = new PanZoomController({\n pannable: params.pannable,\n zoomable: params.zoomable,\n limits: params.limits,\n onChange: (next) => {\n transform.value = next\n },\n })\n\n watch(\n params.element,\n (element, _previous, onCleanup) => {\n if (!element) return\n\n controller.attach(element)\n onCleanup(() => controller.detach())\n },\n { immediate: true },\n )\n\n onBeforeUnmount(() => controller.detach())\n\n return {\n transform,\n fit: (content: Size, padding?: number) => controller.fit(content, padding),\n zoomIn: () => controller.zoomIn(),\n zoomOut: () => controller.zoomOut(),\n reset: () => controller.reset(),\n setTransform: (next: Transform) => controller.set(next),\n }\n}\n","import { computed, ref, type ComputedRef } from 'vue'\nimport {\n applyCollapsed,\n layoutTree,\n toggleCollapsed,\n toTree,\n type Accessors,\n type Layout,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\n/** Функции-геттеры, чтобы композабл видел свежие значения пропов. */\nexport interface UseTreeLayoutParams<T> {\n data: () => T | T[] | null | undefined\n options: () => Partial<TreeViewOptions<T>> | undefined\n accessors: () => Accessors<T>\n}\n\nexport interface UseTreeLayoutResult<T> {\n /** Готовая раскладка: узлы с координатами и линии. */\n layout: ComputedRef<Layout<T>>\n /** Свёрнут ли узел с таким id. */\n isCollapsed: (id: string) => boolean\n /** Свернуть/развернуть узел. */\n toggle: (id: string) => void\n /** Развернуть всё дерево. */\n expandAll: () => void\n}\n\n/**\n * Вся «логика» компонента: данные → дерево → раскладка.\n * Каждый шаг — отдельная чистая функция из core, здесь только реактивность.\n */\nexport function useTreeLayout<T>(params: UseTreeLayoutParams<T>): UseTreeLayoutResult<T> {\n const collapsedIds = ref(new Set<string>())\n\n const tree = computed(() => toTree(params.data(), params.accessors()))\n const visibleTree = computed(() => applyCollapsed(tree.value, collapsedIds.value))\n const layout = computed(() => layoutTree(visibleTree.value, params.options()))\n\n function isCollapsed(id: string): boolean {\n return collapsedIds.value.has(id)\n }\n\n function toggle(id: string): void {\n // Новое множество вместо мутации — иначе Vue не заметит изменения.\n collapsedIds.value = toggleCollapsed(collapsedIds.value, id)\n }\n\n function expandAll(): void {\n collapsedIds.value = new Set()\n }\n\n return { layout, isCollapsed, toggle, expandAll }\n}\n","import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'\nimport { estimateVisibleRect, measureVisibleRect, type Rect } from '@bigplay/tree-view-core'\n\n/**\n * Следит за тем, какая часть холста сейчас видна.\n *\n * Пересчитывается там же, где это меняется: при перетаскивании и зуме,\n * при скролле страницы и при изменении размеров окна. Отдельный троттлинг\n * не нужен — и те, и другие события браузер отдаёт не чаще кадра.\n */\nexport function useVisibleRect(canvas: Ref<HTMLElement | null>, scale: () => number, enabled: () => boolean) {\n // До первого измерения берём размер окна — иначе первый кадр нарисует всё дерево.\n const rect = ref<Rect | null>(enabled() ? estimateVisibleRect() : null)\n\n function update(): void {\n rect.value = enabled() ? measureVisibleRect(canvas.value, scale()) : null\n }\n\n onMounted(() => {\n update()\n window.addEventListener('scroll', update, { passive: true, capture: true })\n window.addEventListener('resize', update, { passive: true })\n })\n\n onBeforeUnmount(() => {\n window.removeEventListener('scroll', update, { capture: true })\n window.removeEventListener('resize', update)\n })\n\n return { rect, update }\n}\n","<script setup lang=\"ts\" generic=\"T\">\n/**\n * Компонент-обёртка. Вся математика — в пакете core, здесь только\n * реактивность, разметка и события.\n */\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OVERSCAN,\n DEFAULT_SCALE_LIMITS,\n toCssTransform,\n visibleLayout,\n type Accessors,\n type LayoutNode,\n type ScaleLimits,\n type Transform,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport { usePanZoom } from './usePanZoom'\nimport { useTreeLayout } from './useTreeLayout'\nimport { useVisibleRect } from './useVisibleRect'\n\nconst props = withDefaults(\n defineProps<{\n /** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */\n data: T | T[] | null | undefined\n /** Размеры и направление. Можно передать только часть полей. */\n options?: Partial<TreeViewOptions<T>>\n /** Как достать id узла. По умолчанию — поле `id`. */\n getId?: Accessors<T>['getId']\n /** Как достать детей. По умолчанию — поле `children`. */\n getChildren?: Accessors<T>['getChildren']\n /** Как достать id родителя. Передан — значит данные плоские. */\n getParentId?: Accessors<T>['getParentId']\n /** Клик по узлу сворачивает/разворачивает ветку. */\n collapsible?: boolean\n /** Разрешить перетаскивание холста. */\n pannable?: boolean\n /** Разрешить зум колесом и «щипком». */\n zoomable?: boolean\n /** Вписать дерево в контейнер при первом отображении. */\n fitOnMount?: boolean\n /**\n * Как компонент занимает место:\n * - `'fill'` (по умолчанию) — растягивается на родителя, лишнее обрезается,\n * дерево двигают мышью;\n * - `'content'` — контейнер принимает размер дерева и ничего не обрезает.\n * Нужен, когда сетка стоит внутри страницы со своим скроллом.\n */\n size?: 'fill' | 'content'\n /** Пределы масштаба. */\n scaleLimits?: ScaleLimits\n /** Дополнительный класс на обёртке узла — строкой или функцией. */\n nodeClass?: string | ((node: LayoutNode<T>) => string)\n /**\n * Рисовать только то, что видно на экране. На больших сетках это\n * главная экономия: в DOM попадает несколько десятков карточек\n * вместо тысячи. На маленьких деревьях ничего не меняет.\n */\n virtualize?: boolean\n /** Запас вокруг видимой области в пикселях. */\n overscan?: number\n }>(),\n {\n options: undefined,\n getId: undefined,\n getChildren: undefined,\n getParentId: undefined,\n collapsible: false,\n pannable: true,\n zoomable: true,\n fitOnMount: true,\n size: 'fill',\n scaleLimits: undefined,\n nodeClass: undefined,\n virtualize: true,\n overscan: DEFAULT_OVERSCAN,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'node-click', node: LayoutNode<T>, event: MouseEvent): void\n (e: 'toggle', node: LayoutNode<T>, collapsed: boolean): void\n (e: 'transform', transform: Transform): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst canvasElement = ref<HTMLElement | null>(null)\n\nconst { layout, isCollapsed, toggle, expandAll } = useTreeLayout<T>({\n data: () => props.data,\n options: () => props.options,\n accessors: () => ({\n getId: props.getId,\n getChildren: props.getChildren,\n getParentId: props.getParentId,\n }),\n})\n\nconst panZoom = usePanZoom({\n element: rootElement,\n pannable: () => props.pannable,\n zoomable: () => props.zoomable,\n limits: () => props.scaleLimits ?? DEFAULT_SCALE_LIMITS,\n})\n\nconst visible = useVisibleRect(\n canvasElement,\n () => panZoom.transform.value.scale,\n () => props.virtualize,\n)\n\n/** То, что реально попадёт в DOM: при `virtualize` — только видимая часть. */\nconst drawn = computed(() =>\n props.virtualize ? visibleLayout(layout.value, visible.rect.value, props.overscan) : layout.value,\n)\n\nconst canvasStyle = computed(() => ({\n width: `${layout.value.width}px`,\n height: `${layout.value.height}px`,\n transform: toCssTransform(panZoom.transform.value),\n}))\n\nconst rootStyle = computed(() => {\n if (props.size !== 'content') return undefined\n\n const { scale } = panZoom.transform.value\n return {\n width: `${layout.value.width * scale}px`,\n height: `${layout.value.height * scale}px`,\n overflow: 'visible',\n }\n})\n\nfunction nodeStyle(node: LayoutNode<T>) {\n return {\n left: `${node.x}px`,\n top: `${node.y}px`,\n width: `${node.width}px`,\n height: `${node.height}px`,\n }\n}\n\nfunction nodeClassOf(node: LayoutNode<T>): string | undefined {\n return typeof props.nodeClass === 'function' ? props.nodeClass(node) : props.nodeClass\n}\n\n/** Подпись карточки по умолчанию: name → title → label → id. */\nfunction labelOf(node: LayoutNode<T>): string {\n const data = node.data as { name?: string; title?: string; label?: string } | null\n return data?.name ?? data?.title ?? data?.label ?? node.id\n}\n\nfunction toggleNode(node: LayoutNode<T>): void {\n toggle(node.id)\n emit('toggle', node, isCollapsed(node.id))\n}\n\nfunction onNodeClick(node: LayoutNode<T>, event: MouseEvent): void {\n emit('node-click', node, event)\n if (props.collapsible && node.hasChildren) toggleNode(node)\n}\n\n/** Вписывает дерево в видимую область. */\nfunction fit(padding?: number): void {\n panZoom.fit({ width: layout.value.width, height: layout.value.height }, padding)\n}\n\nonMounted(() => {\n if (props.fitOnMount) void nextTick(() => fit())\n})\n\nwatch(\n () => panZoom.transform.value,\n (transform) => {\n // Холст поехал — видимая часть стала другой.\n visible.update()\n emit('transform', transform)\n },\n)\n\n// Новые данные — и пересчитывать видимую часть надо уже по ним.\nwatch(layout, () => void nextTick(() => visible.update()))\n\ndefineExpose({\n fit,\n zoomIn: panZoom.zoomIn,\n zoomOut: panZoom.zoomOut,\n resetTransform: panZoom.reset,\n setTransform: panZoom.setTransform,\n expandAll,\n toggle,\n layout,\n transform: panZoom.transform,\n})\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tree-view\"\n :class=\"{ 'tree-view--pannable': pannable, 'tree-view--content': size === 'content' }\"\n :style=\"rootStyle\"\n >\n <div ref=\"canvasElement\" class=\"tree-view__canvas\" :style=\"canvasStyle\">\n <svg class=\"tree-view__links\" :width=\"layout.width\" :height=\"layout.height\">\n <template v-for=\"link in drawn.links\" :key=\"link.id\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </template>\n </svg>\n\n <div\n v-for=\"node in drawn.nodes\"\n :key=\"node.id\"\n class=\"tree-view__node\"\n :class=\"nodeClassOf(node)\"\n :style=\"nodeStyle(node)\"\n @click=\"onNodeClick(node, $event)\"\n >\n <!--\n Главная точка кастомизации: сюда подставляют свою карточку.\n Доступно всё, что может понадобиться для отрисовки и управления.\n -->\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"node.data\"\n :depth=\"node.depth\"\n :collapsed=\"node.collapsed\"\n :has-children=\"node.hasChildren\"\n :toggle=\"() => toggleNode(node)\"\n >\n <TreeNodeCard\n :label=\"labelOf(node)\"\n :has-children=\"node.hasChildren\"\n :collapsed=\"node.collapsed\"\n @toggle=\"toggleNode(node)\"\n />\n </slot>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Карточка матча турнирной сетки — та самая, что на сайте:\n * победитель синий, проигравший серый, команда пользователя в рамке\n * с подсказкой, техническое поражение показывает «ТП» и объяснение по наведению.\n *\n * Какие строки рисовать — решает `buildBracketCard` из core, поэтому\n * версия для Vue 2 выглядит точно так же.\n */\nimport { computed, ref } from 'vue'\nimport {\n buildBracketCard,\n type BracketCardMatch,\n type BracketCardRow,\n type BracketCardTeam,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Матч: нужны только команды. */\n match: BracketCardMatch | null | undefined\n /** Сколько строк рисовать. У финала — одна. */\n rows?: number\n /** Подсказка над карточкой с моей командой. Пустая строка — не показывать. */\n myTeamHint?: string\n /** Текст в ячейке счёта при техническом поражении. */\n techDefeatLabel?: string\n /** Подсказка по наведению на техническое поражение. */\n techDefeatHint?: string\n }>(),\n {\n rows: undefined,\n myTeamHint: 'Нажми для перехода в матч 👇',\n techDefeatLabel: 'ТП',\n techDefeatHint: 'Техническое поражение',\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', team: BracketCardTeam, row: BracketCardRow): void }>()\n\nconst hoveredKey = ref<string | null>(null)\n\nconst card = computed(() =>\n buildBracketCard(props.match, { rows: props.rows, techDefeatLabel: props.techDefeatLabel }),\n)\n\nfunction onRowClick(row: BracketCardRow): void {\n if (row.team) emit('select', row.team, row)\n}\n</script>\n\n<template>\n <div class=\"tv-bracket\" :class=\"{ 'tv-bracket--my-team': card.hasMyTeam }\">\n <div v-if=\"card.hasMyTeam && myTeamHint\" class=\"tv-bracket__hint\">{{ myTeamHint }}</div>\n\n <div\n v-for=\"row in card.rows\"\n :key=\"row.key\"\n class=\"tv-bracket__row\"\n :class=\"{\n 'tv-bracket__row--winner': row.isWinner,\n 'tv-bracket__row--loser': row.isLoser && !row.isEmpty,\n 'tv-bracket__row--my': row.isMyTeam,\n 'tv-bracket__row--empty': row.isEmpty,\n }\"\n :title=\"row.isMyTeam ? 'Моя команда' : undefined\"\n @click=\"onRowClick(row)\"\n @mouseenter=\"hoveredKey = row.key\"\n @mouseleave=\"hoveredKey = null\"\n >\n <template v-if=\"row.team\">\n <span v-if=\"row.isTechDefeat && hoveredKey === row.key\" class=\"tv-bracket__tooltip\">\n {{ techDefeatHint }}\n </span>\n\n <span class=\"tv-bracket__name\">{{ row.team.name }}</span>\n <span class=\"tv-bracket__score\">{{ row.score }}</span>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Полоса раундов над турнирной сеткой — та самая, что на странице турнира.\n *\n * Делает три вещи:\n * 1. рисует названия раундов колонками той же ширины, что и уровни сетки;\n * 2. центрует матчи внутри раунда — сетка в слоте получает отступ слева\n * в половину `levelGap`, иначе карточки прижимаются к левому краю колонки;\n * 3. на узком экране добавляет раундам нажатие: сетка плавно подъезжает\n * к матчам этого раунда. Выглядит полоса при этом везде одинаково.\n *\n * Данные сетки полоса не трогает: на экране всегда вся сетка целиком.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport {\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n nodesInColumn,\n offsetWithin,\n roundScrollLeft,\n scrollToNode,\n smoothScrollLeft,\n watchMedia,\n type BracketRound,\n type BracketRoundColumn,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Ширина карточки матча — то же число, что в `nodeWidth` у сетки. */\n nodeWidth?: number\n /** Промежуток между раундами — то же число, что в `levelGap` у сетки. */\n levelGap?: number\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /** Нажатие на раунд подводит к нему сетку. Не передан — решает ширина экрана. */\n scrollOnClick?: boolean\n /** Медиавыражение «узкий экран», при котором нажатие включается. */\n mobileQuery?: string\n }>(),\n {\n nodeWidth: 180,\n levelGap: 60,\n isShow: true,\n scrollOnClick: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', round: BracketRoundColumn): void }>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\nlet unwatch: (() => void) | null = null\n\nonMounted(() => {\n unwatch = watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n })\n})\n\nonBeforeUnmount(() => unwatch?.())\n\n/** Полосы нет — нажимать не на что. */\nconst isClickable = computed(() => props.isShow && (props.scrollOnClick ?? isNarrow.value))\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: props.nodeWidth, levelGap: props.levelGap }),\n)\n\nconst barStyle = computed(() => ({ width: `${model.value.width}px` }))\n\nconst contentStyle = computed(() =>\n props.isShow ? { paddingLeft: `${model.value.offset}px` } : undefined,\n)\n\n/**\n * Подводит сетку к матчам раунда.\n *\n * Сначала пробуем по самим карточкам: браузер сам подвинет и страницу, и блок\n * с прокруткой, поэтому финал оказывается на экране целиком, а не только\n * «где-то справа». Карточек не нашли (виртуализация) — считаем по колонкам.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const content = contentElement.value\n const matches = content ? nodesInColumn(content, column.index, column.width, model.value.offset) : []\n const middle = matches[Math.floor(matches.length / 2)]\n\n if (middle && scrollToNode(middle)) return\n\n const element = rootElement.value\n const container = findScrollParent(element)\n if (!element || !container) return\n\n const left = offsetWithin(element, container) + column.index * column.width\n\n smoothScrollLeft(\n container,\n roundScrollLeft({\n left,\n width: column.width,\n viewport: container.clientWidth,\n scrollWidth: container.scrollWidth,\n }),\n )\n}\n\n/** Нажатие — только на узком экране: на широком колонка раунда это просто подпись. */\nfunction onSelect(column: BracketRoundColumn): void {\n if (!isClickable.value) return\n\n emit('select', column)\n scrollToRound(column)\n}\n</script>\n\n<template>\n <div ref=\"rootElement\" class=\"tv-rounds\">\n <div v-if=\"isShow\" class=\"tv-rounds__bar\" :style=\"barStyle\">\n <div\n v-for=\"column in model.columns\"\n :key=\"column.key\"\n class=\"tv-rounds__item\"\n :style=\"{ width: `${column.width}px` }\"\n @click=\"onSelect(column)\"\n >\n <slot name=\"round\" :round=\"column\">{{ column.name }}</slot>\n </div>\n </div>\n\n <div ref=\"contentElement\" class=\"tv-rounds__content\" :style=\"contentStyle\">\n <!-- Сюда кладут саму сетку: ей достаётся отступ, центрующий матчи. -->\n <slot />\n </div>\n </div>\n</template>\n","import type { App, Plugin } from 'vue'\nimport TreeView from './TreeView.vue'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport BracketCard from './BracketCard.vue'\nimport BracketRounds from './BracketRounds.vue'\n// Стили общие для Vue 2 и Vue 3 — лежат в core, чтобы не расходились.\n// Они попадают прямо в бандл и подключаются сами: отдельный импорт в проекте не нужен.\nimport { injectStyles } from '@bigplay/tree-view-core'\nimport styles from '@bigplay/tree-view-core/style.css?inline'\n\ninjectStyles(styles)\n\nexport { TreeView, TreeNodeCard, BracketCard, BracketRounds }\nexport { useTreeLayout } from './useTreeLayout'\nexport { usePanZoom } from './usePanZoom'\n\n// Типы из core переэкспортируем, чтобы их не приходилось ставить отдельно.\nexport type {\n Accessors,\n BracketCardMatch,\n BracketCardModel,\n BracketCardOptions,\n BracketCardRow,\n BracketCardTeam,\n BracketRound,\n BracketRoundColumn,\n BracketRoundsModel,\n BracketRoundsOptions,\n Direction,\n Layout,\n LayoutLink,\n LayoutNode,\n LinkStyle,\n ScaleLimits,\n Transform,\n TreeNode,\n TreeViewOptions,\n} from '@bigplay/tree-view-core'\nexport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketCard,\n bracketCardHeight,\n buildBracketRounds,\n} from '@bigplay/tree-view-core'\n\n/** Плагин: `app.use(TreeViewPlugin)` регистрирует компоненты глобально. */\nexport const TreeViewPlugin: Plugin = {\n install(app: App) {\n app.component('TreeView', TreeView)\n app.component('TreeNodeCard', TreeNodeCard)\n app.component('BracketCard', BracketCard)\n app.component('BracketRounds', BracketRounds)\n },\n}\n\nexport default TreeViewPlugin\n"],"names":["emit","__emit","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_toDisplayString","__props","_hoisted_3","usePanZoom","params","transform","ref","IDENTITY_TRANSFORM","controller","PanZoomController","next","watch","element","_previous","onCleanup","onBeforeUnmount","content","padding","useTreeLayout","collapsedIds","tree","computed","toTree","visibleTree","applyCollapsed","layout","layoutTree","isCollapsed","id","toggle","toggleCollapsed","expandAll","useVisibleRect","canvas","scale","enabled","rect","estimateVisibleRect","update","measureVisibleRect","onMounted","props","rootElement","canvasElement","panZoom","DEFAULT_SCALE_LIMITS","visible","drawn","visibleLayout","canvasStyle","toCssTransform","rootStyle","nodeStyle","node","nodeClassOf","labelOf","data","toggleNode","onNodeClick","event","fit","nextTick","__expose","_normalizeClass","_unref","_Fragment","_renderList","link","_renderSlot","_ctx","_normalizeStyle","$event","_createVNode","TreeNodeCard","hoveredKey","card","buildBracketCard","onRowClick","row","_hoisted_4","_hoisted_5","contentElement","isNarrow","unwatch","watchMedia","matches","isClickable","model","buildBracketRounds","barStyle","contentStyle","scrollToRound","column","nodesInColumn","middle","scrollToNode","container","findScrollParent","left","offsetWithin","smoothScrollLeft","roundScrollLeft","onSelect","_createTextVNode","injectStyles","styles","TreeViewPlugin","app","TreeView","BracketCard","BracketRounds"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/TreeNodeCard.vue","../src/usePanZoom.ts","../src/useTreeLayout.ts","../src/useVisibleRect.ts","../src/TreeView.vue","../src/BracketCard.vue","../src/BracketRounds.vue","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Карточка узла по умолчанию.\n * Нужна только чтобы «из коробки» что-то отображалось —\n * в реальном проекте обычно используют слот #node и рисуют свой блок.\n */\ndefineProps<{\n label: string\n hasChildren?: boolean\n collapsed?: boolean\n}>()\n\nconst emit = defineEmits<{ (e: 'toggle'): void }>()\n</script>\n\n<template>\n <div class=\"tree-view-card\">\n <span class=\"tree-view-card__label\">{{ label }}</span>\n\n <button\n v-if=\"hasChildren\"\n type=\"button\"\n class=\"tree-view-card__toggle\"\n data-tv-no-pan\n :aria-label=\"collapsed ? 'Развернуть' : 'Свернуть'\"\n @click.stop=\"emit('toggle')\"\n >\n {{ collapsed ? '+' : '−' }}\n </button>\n </div>\n</template>\n","import { onBeforeUnmount, ref, watch, type Ref } from 'vue'\nimport {\n IDENTITY_TRANSFORM,\n PanZoomController,\n type ScaleLimits,\n type Size,\n type Transform,\n} from '@bigplay/tree-view-core'\n\nexport interface UsePanZoomParams {\n /** Элемент, который слушаем: по нему тянут и крутят колесо. */\n element: Ref<HTMLElement | null>\n pannable: () => boolean\n zoomable: () => boolean\n limits: () => ScaleLimits\n}\n\n/**\n * Тонкая обёртка над `PanZoomController` из core:\n * переносит его состояние в реактивный `ref` и следит за жизненным циклом.\n */\nexport function usePanZoom(params: UsePanZoomParams) {\n const transform = ref<Transform>({ ...IDENTITY_TRANSFORM })\n\n const controller = new PanZoomController({\n pannable: params.pannable,\n zoomable: params.zoomable,\n limits: params.limits,\n onChange: (next) => {\n transform.value = next\n },\n })\n\n watch(\n params.element,\n (element, _previous, onCleanup) => {\n if (!element) return\n\n controller.attach(element)\n onCleanup(() => controller.detach())\n },\n { immediate: true },\n )\n\n onBeforeUnmount(() => controller.detach())\n\n return {\n transform,\n fit: (content: Size, padding?: number) => controller.fit(content, padding),\n zoomIn: () => controller.zoomIn(),\n zoomOut: () => controller.zoomOut(),\n reset: () => controller.reset(),\n setTransform: (next: Transform) => controller.set(next),\n }\n}\n","import { computed, ref, type ComputedRef } from 'vue'\nimport {\n applyCollapsed,\n layoutTree,\n toggleCollapsed,\n toTree,\n type Accessors,\n type Layout,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\n/** Функции-геттеры, чтобы композабл видел свежие значения пропов. */\nexport interface UseTreeLayoutParams<T> {\n data: () => T | T[] | null | undefined\n options: () => Partial<TreeViewOptions<T>> | undefined\n accessors: () => Accessors<T>\n}\n\nexport interface UseTreeLayoutResult<T> {\n /** Готовая раскладка: узлы с координатами и линии. */\n layout: ComputedRef<Layout<T>>\n /** Свёрнут ли узел с таким id. */\n isCollapsed: (id: string) => boolean\n /** Свернуть/развернуть узел. */\n toggle: (id: string) => void\n /** Развернуть всё дерево. */\n expandAll: () => void\n}\n\n/**\n * Вся «логика» компонента: данные → дерево → раскладка.\n * Каждый шаг — отдельная чистая функция из core, здесь только реактивность.\n */\nexport function useTreeLayout<T>(params: UseTreeLayoutParams<T>): UseTreeLayoutResult<T> {\n const collapsedIds = ref(new Set<string>())\n\n const tree = computed(() => toTree(params.data(), params.accessors()))\n const visibleTree = computed(() => applyCollapsed(tree.value, collapsedIds.value))\n const layout = computed(() => layoutTree(visibleTree.value, params.options()))\n\n function isCollapsed(id: string): boolean {\n return collapsedIds.value.has(id)\n }\n\n function toggle(id: string): void {\n // Новое множество вместо мутации — иначе Vue не заметит изменения.\n collapsedIds.value = toggleCollapsed(collapsedIds.value, id)\n }\n\n function expandAll(): void {\n collapsedIds.value = new Set()\n }\n\n return { layout, isCollapsed, toggle, expandAll }\n}\n","import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'\nimport { estimateVisibleRect, measureVisibleRect, type Rect } from '@bigplay/tree-view-core'\n\n/**\n * Следит за тем, какая часть холста сейчас видна.\n *\n * Пересчитывается там же, где это меняется: при перетаскивании и зуме,\n * при скролле страницы и при изменении размеров окна. Отдельный троттлинг\n * не нужен — и те, и другие события браузер отдаёт не чаще кадра.\n */\nexport function useVisibleRect(canvas: Ref<HTMLElement | null>, scale: () => number, enabled: () => boolean) {\n // До первого измерения берём размер окна — иначе первый кадр нарисует всё дерево.\n const rect = ref<Rect | null>(enabled() ? estimateVisibleRect() : null)\n\n function update(): void {\n rect.value = enabled() ? measureVisibleRect(canvas.value, scale()) : null\n }\n\n onMounted(() => {\n update()\n window.addEventListener('scroll', update, { passive: true, capture: true })\n window.addEventListener('resize', update, { passive: true })\n })\n\n onBeforeUnmount(() => {\n window.removeEventListener('scroll', update, { capture: true })\n window.removeEventListener('resize', update)\n })\n\n return { rect, update }\n}\n","<script setup lang=\"ts\" generic=\"T\">\n/**\n * Компонент-обёртка. Вся математика — в пакете core, здесь только\n * реактивность, разметка и события.\n */\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OVERSCAN,\n DEFAULT_SCALE_LIMITS,\n toCssTransform,\n visibleLayout,\n type Accessors,\n type LayoutNode,\n type ScaleLimits,\n type Transform,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport { usePanZoom } from './usePanZoom'\nimport { useTreeLayout } from './useTreeLayout'\nimport { useVisibleRect } from './useVisibleRect'\n\nconst props = withDefaults(\n defineProps<{\n /** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */\n data: T | T[] | null | undefined\n /** Размеры и направление. Можно передать только часть полей. */\n options?: Partial<TreeViewOptions<T>>\n /** Как достать id узла. По умолчанию — поле `id`. */\n getId?: Accessors<T>['getId']\n /** Как достать детей. По умолчанию — поле `children`. */\n getChildren?: Accessors<T>['getChildren']\n /** Как достать id родителя. Передан — значит данные плоские. */\n getParentId?: Accessors<T>['getParentId']\n /** Клик по узлу сворачивает/разворачивает ветку. */\n collapsible?: boolean\n /** Разрешить перетаскивание холста. */\n pannable?: boolean\n /** Разрешить зум колесом и «щипком». */\n zoomable?: boolean\n /** Вписать дерево в контейнер при первом отображении. */\n fitOnMount?: boolean\n /**\n * Как компонент занимает место:\n * - `'fill'` (по умолчанию) — растягивается на родителя, лишнее обрезается,\n * дерево двигают мышью;\n * - `'content'` — контейнер принимает размер дерева и ничего не обрезает.\n * Нужен, когда сетка стоит внутри страницы со своим скроллом.\n */\n size?: 'fill' | 'content'\n /** Пределы масштаба. */\n scaleLimits?: ScaleLimits\n /** Дополнительный класс на обёртке узла — строкой или функцией. */\n nodeClass?: string | ((node: LayoutNode<T>) => string)\n /**\n * Рисовать только то, что видно на экране. На больших сетках это\n * главная экономия: в DOM попадает несколько десятков карточек\n * вместо тысячи. На маленьких деревьях ничего не меняет.\n */\n virtualize?: boolean\n /** Запас вокруг видимой области в пикселях. */\n overscan?: number\n }>(),\n {\n options: undefined,\n getId: undefined,\n getChildren: undefined,\n getParentId: undefined,\n collapsible: false,\n pannable: true,\n zoomable: true,\n fitOnMount: true,\n size: 'fill',\n scaleLimits: undefined,\n nodeClass: undefined,\n virtualize: true,\n overscan: DEFAULT_OVERSCAN,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'node-click', node: LayoutNode<T>, event: MouseEvent): void\n (e: 'toggle', node: LayoutNode<T>, collapsed: boolean): void\n (e: 'transform', transform: Transform): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst canvasElement = ref<HTMLElement | null>(null)\n\nconst { layout, isCollapsed, toggle, expandAll } = useTreeLayout<T>({\n data: () => props.data,\n options: () => props.options,\n accessors: () => ({\n getId: props.getId,\n getChildren: props.getChildren,\n getParentId: props.getParentId,\n }),\n})\n\nconst panZoom = usePanZoom({\n element: rootElement,\n pannable: () => props.pannable,\n zoomable: () => props.zoomable,\n limits: () => props.scaleLimits ?? DEFAULT_SCALE_LIMITS,\n})\n\nconst visible = useVisibleRect(\n canvasElement,\n () => panZoom.transform.value.scale,\n () => props.virtualize,\n)\n\n/** То, что реально попадёт в DOM: при `virtualize` — только видимая часть. */\nconst drawn = computed(() =>\n props.virtualize ? visibleLayout(layout.value, visible.rect.value, props.overscan) : layout.value,\n)\n\nconst canvasStyle = computed(() => ({\n width: `${layout.value.width}px`,\n height: `${layout.value.height}px`,\n transform: toCssTransform(panZoom.transform.value),\n}))\n\nconst rootStyle = computed(() => {\n if (props.size !== 'content') return undefined\n\n const { scale } = panZoom.transform.value\n return {\n width: `${layout.value.width * scale}px`,\n height: `${layout.value.height * scale}px`,\n overflow: 'visible',\n }\n})\n\nfunction nodeStyle(node: LayoutNode<T>) {\n return {\n left: `${node.x}px`,\n top: `${node.y}px`,\n width: `${node.width}px`,\n height: `${node.height}px`,\n }\n}\n\nfunction nodeClassOf(node: LayoutNode<T>): string | undefined {\n return typeof props.nodeClass === 'function' ? props.nodeClass(node) : props.nodeClass\n}\n\n/** Подпись карточки по умолчанию: name → title → label → id. */\nfunction labelOf(node: LayoutNode<T>): string {\n const data = node.data as { name?: string; title?: string; label?: string } | null\n return data?.name ?? data?.title ?? data?.label ?? node.id\n}\n\nfunction toggleNode(node: LayoutNode<T>): void {\n toggle(node.id)\n emit('toggle', node, isCollapsed(node.id))\n}\n\nfunction onNodeClick(node: LayoutNode<T>, event: MouseEvent): void {\n emit('node-click', node, event)\n if (props.collapsible && node.hasChildren) toggleNode(node)\n}\n\n/** Вписывает дерево в видимую область. */\nfunction fit(padding?: number): void {\n panZoom.fit({ width: layout.value.width, height: layout.value.height }, padding)\n}\n\nonMounted(() => {\n if (props.fitOnMount) void nextTick(() => fit())\n})\n\nwatch(\n () => panZoom.transform.value,\n (transform) => {\n // Холст поехал — видимая часть стала другой.\n visible.update()\n emit('transform', transform)\n },\n)\n\n// Новые данные — и пересчитывать видимую часть надо уже по ним.\nwatch(layout, () => void nextTick(() => visible.update()))\n\ndefineExpose({\n fit,\n zoomIn: panZoom.zoomIn,\n zoomOut: panZoom.zoomOut,\n resetTransform: panZoom.reset,\n setTransform: panZoom.setTransform,\n expandAll,\n toggle,\n layout,\n transform: panZoom.transform,\n})\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tree-view\"\n :class=\"{ 'tree-view--pannable': pannable, 'tree-view--content': size === 'content' }\"\n :style=\"rootStyle\"\n >\n <div ref=\"canvasElement\" class=\"tree-view__canvas\" :style=\"canvasStyle\">\n <svg class=\"tree-view__links\" :width=\"layout.width\" :height=\"layout.height\">\n <template v-for=\"link in drawn.links\" :key=\"link.id\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </template>\n </svg>\n\n <div\n v-for=\"node in drawn.nodes\"\n :key=\"node.id\"\n class=\"tree-view__node\"\n :class=\"nodeClassOf(node)\"\n :style=\"nodeStyle(node)\"\n @click=\"onNodeClick(node, $event)\"\n >\n <!--\n Главная точка кастомизации: сюда подставляют свою карточку.\n Доступно всё, что может понадобиться для отрисовки и управления.\n -->\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"node.data\"\n :depth=\"node.depth\"\n :collapsed=\"node.collapsed\"\n :has-children=\"node.hasChildren\"\n :toggle=\"() => toggleNode(node)\"\n >\n <TreeNodeCard\n :label=\"labelOf(node)\"\n :has-children=\"node.hasChildren\"\n :collapsed=\"node.collapsed\"\n @toggle=\"toggleNode(node)\"\n />\n </slot>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Карточка матча турнирной сетки — та самая, что на сайте:\n * победитель синий, проигравший серый, команда пользователя в рамке\n * с подсказкой, техническое поражение показывает «ТП» и объяснение по наведению.\n *\n * Какие строки рисовать — решает `buildBracketCard` из core, поэтому\n * версия для Vue 2 выглядит точно так же.\n */\nimport { computed, ref } from 'vue'\nimport {\n buildBracketCard,\n type BracketCardMatch,\n type BracketCardRow,\n type BracketCardTeam,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Матч: нужны только команды. */\n match: BracketCardMatch | null | undefined\n /** Сколько строк рисовать. У финала — одна. */\n rows?: number\n /** Подсказка над карточкой с моей командой. Пустая строка — не показывать. */\n myTeamHint?: string\n /** Текст в ячейке счёта при техническом поражении. */\n techDefeatLabel?: string\n /** Подсказка по наведению на техническое поражение. */\n techDefeatHint?: string\n }>(),\n {\n rows: undefined,\n myTeamHint: 'Нажми для перехода в матч 👇',\n techDefeatLabel: 'ТП',\n techDefeatHint: 'Техническое поражение',\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', team: BracketCardTeam, row: BracketCardRow): void }>()\n\nconst hoveredKey = ref<string | null>(null)\n\nconst card = computed(() =>\n buildBracketCard(props.match, { rows: props.rows, techDefeatLabel: props.techDefeatLabel }),\n)\n\nfunction onRowClick(row: BracketCardRow): void {\n if (row.team) emit('select', row.team, row)\n}\n</script>\n\n<template>\n <div class=\"tv-bracket\" :class=\"{ 'tv-bracket--my-team': card.hasMyTeam }\">\n <div v-if=\"card.hasMyTeam && myTeamHint\" class=\"tv-bracket__hint\">{{ myTeamHint }}</div>\n\n <div\n v-for=\"row in card.rows\"\n :key=\"row.key\"\n class=\"tv-bracket__row\"\n :class=\"{\n 'tv-bracket__row--winner': row.isWinner,\n 'tv-bracket__row--loser': row.isLoser && !row.isEmpty,\n 'tv-bracket__row--my': row.isMyTeam,\n 'tv-bracket__row--empty': row.isEmpty,\n }\"\n :title=\"row.isMyTeam ? 'Моя команда' : undefined\"\n @click=\"onRowClick(row)\"\n @mouseenter=\"hoveredKey = row.key\"\n @mouseleave=\"hoveredKey = null\"\n >\n <template v-if=\"row.team\">\n <span v-if=\"row.isTechDefeat && hoveredKey === row.key\" class=\"tv-bracket__tooltip\">\n {{ techDefeatHint }}\n </span>\n\n <span class=\"tv-bracket__name\">{{ row.team.name }}</span>\n <span class=\"tv-bracket__score\">{{ row.score }}</span>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Полоса раундов над турнирной сеткой — та самая, что на странице турнира.\n *\n * Делает три вещи:\n * 1. рисует названия раундов колонками той же ширины, что и уровни сетки;\n * 2. центрует матчи внутри раунда — сетка в слоте получает отступ слева\n * в половину `levelGap`, иначе карточки прижимаются к левому краю колонки;\n * 3. на узком экране добавляет раундам нажатие: сетка плавно подъезжает\n * к матчам этого раунда. Выглядит полоса при этом везде одинаково.\n *\n * Данные сетки полоса не трогает: на экране всегда вся сетка целиком.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport {\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n horizontalBox,\n nodesInColumn,\n offsetWithin,\n roundScrollLeft,\n smoothScrollLeft,\n watchMedia,\n type BracketRound,\n type BracketRoundColumn,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Ширина карточки матча — то же число, что в `nodeWidth` у сетки. */\n nodeWidth?: number\n /** Промежуток между раундами — то же число, что в `levelGap` у сетки. */\n levelGap?: number\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /** Нажатие на раунд подводит к нему сетку. Не передан — решает ширина экрана. */\n scrollOnClick?: boolean\n /** Медиавыражение «узкий экран», при котором нажатие включается. */\n mobileQuery?: string\n }>(),\n {\n nodeWidth: 180,\n levelGap: 60,\n isShow: true,\n scrollOnClick: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{ (e: 'select', round: BracketRoundColumn): void }>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\nlet unwatch: (() => void) | null = null\n\nonMounted(() => {\n unwatch = watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n })\n})\n\nonBeforeUnmount(() => unwatch?.())\n\n/** Полосы нет — нажимать не на что. */\nconst isClickable = computed(() => props.isShow && (props.scrollOnClick ?? isNarrow.value))\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: props.nodeWidth, levelGap: props.levelGap }),\n)\n\nconst barStyle = computed(() => ({ width: `${model.value.width}px` }))\n\nconst contentStyle = computed(() =>\n props.isShow ? { paddingLeft: `${model.value.offset}px` } : undefined,\n)\n\n/**\n * Подводит сетку к матчам раунда — только по горизонтали.\n *\n * Меряем по самой карточке: так раунд встаёт по центру экрана точно, а у финала\n * прокрутка упирается в правый край и он виден целиком. Карточек не нашли\n * (виртуализация) — считаем по колонке, она той же ширины и на том же месте.\n *\n * Вертикаль не трогаем: страница остаётся там, где была.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const element = rootElement.value\n const container = findScrollParent(element)\n if (!element || !container) return\n\n const content = contentElement.value\n // Карточки раунда стоят друг под другом, поэтому по горизонтали годится любая.\n const card = content\n ? nodesInColumn(content, column.index, column.width, model.value.offset)[0]\n : undefined\n\n const box = card\n ? horizontalBox(card, container)\n : { left: offsetWithin(element, container) + column.index * column.width, width: column.width }\n\n smoothScrollLeft(\n container,\n roundScrollLeft({\n ...box,\n viewport: container.clientWidth,\n scrollWidth: container.scrollWidth,\n }),\n )\n}\n\n/** Нажатие — только на узком экране: на широком колонка раунда это просто подпись. */\nfunction onSelect(column: BracketRoundColumn): void {\n if (!isClickable.value) return\n\n emit('select', column)\n scrollToRound(column)\n}\n</script>\n\n<template>\n <div ref=\"rootElement\" class=\"tv-rounds\">\n <div v-if=\"isShow\" class=\"tv-rounds__bar\" :style=\"barStyle\">\n <div\n v-for=\"column in model.columns\"\n :key=\"column.key\"\n class=\"tv-rounds__item\"\n :style=\"{ width: `${column.width}px` }\"\n @click=\"onSelect(column)\"\n >\n <slot name=\"round\" :round=\"column\">{{ column.name }}</slot>\n </div>\n </div>\n\n <div ref=\"contentElement\" class=\"tv-rounds__content\" :style=\"contentStyle\">\n <!-- Сюда кладут саму сетку: ей достаётся отступ, центрующий матчи. -->\n <slot />\n </div>\n </div>\n</template>\n","import type { App, Plugin } from 'vue'\nimport TreeView from './TreeView.vue'\nimport TreeNodeCard from './TreeNodeCard.vue'\nimport BracketCard from './BracketCard.vue'\nimport BracketRounds from './BracketRounds.vue'\n// Стили общие для Vue 2 и Vue 3 — лежат в core, чтобы не расходились.\n// Они попадают прямо в бандл и подключаются сами: отдельный импорт в проекте не нужен.\nimport { injectStyles } from '@bigplay/tree-view-core'\nimport styles from '@bigplay/tree-view-core/style.css?inline'\n\ninjectStyles(styles)\n\nexport { TreeView, TreeNodeCard, BracketCard, BracketRounds }\nexport { useTreeLayout } from './useTreeLayout'\nexport { usePanZoom } from './usePanZoom'\n\n// Типы из core переэкспортируем, чтобы их не приходилось ставить отдельно.\nexport type {\n Accessors,\n BracketCardMatch,\n BracketCardModel,\n BracketCardOptions,\n BracketCardRow,\n BracketCardTeam,\n BracketRound,\n BracketRoundColumn,\n BracketRoundsModel,\n BracketRoundsOptions,\n Direction,\n Layout,\n LayoutLink,\n LayoutNode,\n LinkStyle,\n ScaleLimits,\n Transform,\n TreeNode,\n TreeViewOptions,\n} from '@bigplay/tree-view-core'\nexport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketCard,\n bracketCardHeight,\n buildBracketRounds,\n} from '@bigplay/tree-view-core'\n\n/** Плагин: `app.use(TreeViewPlugin)` регистрирует компоненты глобально. */\nexport const TreeViewPlugin: Plugin = {\n install(app: App) {\n app.component('TreeView', TreeView)\n app.component('TreeNodeCard', TreeNodeCard)\n app.component('BracketCard', BracketCard)\n app.component('BracketRounds', BracketRounds)\n },\n}\n\nexport default TreeViewPlugin\n"],"names":["emit","__emit","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_toDisplayString","__props","_hoisted_3","usePanZoom","params","transform","ref","IDENTITY_TRANSFORM","controller","PanZoomController","next","watch","element","_previous","onCleanup","onBeforeUnmount","content","padding","useTreeLayout","collapsedIds","tree","computed","toTree","visibleTree","applyCollapsed","layout","layoutTree","isCollapsed","id","toggle","toggleCollapsed","expandAll","useVisibleRect","canvas","scale","enabled","rect","estimateVisibleRect","update","measureVisibleRect","onMounted","props","rootElement","canvasElement","panZoom","DEFAULT_SCALE_LIMITS","visible","drawn","visibleLayout","canvasStyle","toCssTransform","rootStyle","nodeStyle","node","nodeClassOf","labelOf","data","toggleNode","onNodeClick","event","fit","nextTick","__expose","_normalizeClass","_unref","_Fragment","_renderList","link","_renderSlot","_ctx","_normalizeStyle","$event","_createVNode","TreeNodeCard","hoveredKey","card","buildBracketCard","onRowClick","row","_hoisted_4","_hoisted_5","contentElement","isNarrow","unwatch","watchMedia","matches","isClickable","model","buildBracketRounds","barStyle","contentStyle","scrollToRound","column","container","findScrollParent","nodesInColumn","box","horizontalBox","offsetWithin","smoothScrollLeft","roundScrollLeft","onSelect","_createTextVNode","injectStyles","styles","TreeViewPlugin","app","TreeView","BracketCard","BracketRounds"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigplay/tree-view",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Компонент дерева и турнирных сеток для Vue 3",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "typecheck": "vue-tsc --noEmit -p tsconfig.json"
36
36
  },
37
37
  "dependencies": {
38
- "@bigplay/tree-view-core": "^0.3.0"
38
+ "@bigplay/tree-view-core": "^0.3.1"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "vue": "^3.3.0"