@bigplay/tree-view 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,15 +10,28 @@ type __VLS_Props = {
10
10
  techDefeatLabel?: string;
11
11
  /** Подсказка по наведению на техническое поражение. */
12
12
  techDefeatHint?: string;
13
+ /** У матча есть предыдущие — рисуем кнопку, которая их сворачивает. */
14
+ hasChildren?: boolean;
15
+ /** Предыдущие матчи сейчас свёрнуты. */
16
+ collapsed?: boolean;
17
+ /** Подписи кнопки для screen reader. */
18
+ collapseHint?: string;
19
+ expandHint?: string;
13
20
  };
14
21
  declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
15
22
  select: (team: BracketCardTeam, row: BracketCardRow) => any;
23
+ toggle: () => any;
16
24
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
17
25
  onSelect?: ((team: BracketCardTeam, row: BracketCardRow) => any) | undefined;
26
+ onToggle?: (() => any) | undefined;
18
27
  }>, {
28
+ hasChildren: boolean;
29
+ collapsed: boolean;
19
30
  rows: number;
20
31
  myTeamHint: string;
21
32
  techDefeatLabel: string;
22
33
  techDefeatHint: string;
34
+ collapseHint: string;
35
+ expandHint: string;
23
36
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
24
37
  export default _default;
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"),n=require("@bigplay/tree-view-core"),R={class:"tree-view-card"},F={class:"tree-view-card__label"},A=["aria-label"],L=e.defineComponent({__name:"TreeNodeCard",props:{label:{},hasChildren:{type:Boolean},collapsed:{type:Boolean}},emits:["toggle"],setup(r,{emit:c}){const i=c;return(t,l)=>(e.openBlock(),e.createElementBlock("div",R,[e.createElementVNode("span",F,e.toDisplayString(r.label),1),r.hasChildren?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"tree-view-card__toggle","data-tv-no-pan":"","aria-label":r.collapsed?"Развернуть":"Свернуть",onClick:l[0]||(l[0]=e.withModifiers(v=>i("toggle"),["stop"]))},e.toDisplayString(r.collapsed?"+":"−"),9,A)):e.createCommentVNode("",!0)]))}});function O(r){const c=e.ref({...n.IDENTITY_TRANSFORM}),i=new n.PanZoomController({pannable:r.pannable,zoomable:r.zoomable,limits:r.limits,onChange:t=>{c.value=t}});return e.watch(r.element,(t,l,v)=>{t&&(i.attach(t),v(()=>i.detach()))},{immediate:!0}),e.onBeforeUnmount(()=>i.detach()),{transform:c,fit:(t,l)=>i.fit(t,l),zoomIn:()=>i.zoomIn(),zoomOut:()=>i.zoomOut(),reset:()=>i.reset(),setTransform:t=>i.set(t)}}function I(r){const c=e.ref(new Set),i=e.computed(()=>n.toTree(r.data(),r.accessors())),t=e.computed(()=>n.applyCollapsed(i.value,c.value)),l=e.computed(()=>n.layoutTree(t.value,r.options()));function v(b){return c.value.has(b)}function h(b){c.value=n.toggleCollapsed(c.value,b)}function d(){c.value=new Set}return{layout:l,isCollapsed:v,toggle:h,expandAll:d}}function W(r,c,i){const t=e.ref(i()?n.estimateVisibleRect():null);function l(){t.value=i()?n.measureVisibleRect(r.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 j=["width","height"],U=["data-depth"],G=["d"],H=["data-depth","onClick"],N=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:n.DEFAULT_OVERSCAN}},emits:["node-click","toggle","transform"],setup(r,{expose:c,emit:i}){const t=r,l=i,v=e.ref(null),h=e.ref(null),{layout:d,isCollapsed:b,toggle:s,expandAll:m}=I({data:()=>t.data,options:()=>t.options,accessors:()=>({getId:t.getId,getChildren:t.getChildren,getParentId:t.getParentId})}),p=O({element:v,pannable:()=>t.pannable,zoomable:()=>t.zoomable,limits:()=>t.scaleLimits??n.DEFAULT_SCALE_LIMITS}),y=W(h,()=>p.transform.value.scale,()=>t.virtualize),_=e.computed(()=>t.virtualize?n.visibleLayout(d.value,y.rect.value,t.overscan):d.value),g=e.computed(()=>({width:`${d.value.width}px`,height:`${d.value.height}px`,transform:n.toCssTransform(p.transform.value)})),C=e.computed(()=>{if(t.size!=="content")return;const{scale:a}=p.transform.value;return{width:`${d.value.width*a}px`,height:`${d.value.height*a}px`,overflow:"visible"}});function E(a){return{left:`${a.x}px`,top:`${a.y}px`,width:`${a.width}px`,height:`${a.height}px`}}function S(a){return typeof t.nodeClass=="function"?t.nodeClass(a):t.nodeClass}function T(a){const k=a.data;return k?.name??k?.title??k?.label??a.id}function w(a){s(a.id),l("toggle",a,b(a.id))}function z(a,k){l("node-click",a,k),t.collapsible&&a.hasChildren&&w(a)}function x(a){p.fit({width:d.value.width,height:d.value.height},a)}return e.onMounted(()=>{t.fitOnMount&&e.nextTick(()=>x())}),e.watch(()=>p.transform.value,a=>{y.update(),l("transform",a)}),e.watch(d,()=>{e.nextTick(()=>y.update())}),c({fit:x,zoomIn:p.zoomIn,zoomOut:p.zoomOut,resetTransform:p.reset,setTransform:p.setTransform,expandAll:m,toggle:s,layout:d,transform:p.transform}),(a,k)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:v,class:e.normalizeClass(["tree-view",{"tree-view--pannable":r.pannable,"tree-view--content":r.size==="content"}]),style:e.normalizeStyle(C.value)},[e.createElementVNode("div",{ref_key:"canvasElement",ref:h,class:"tree-view__canvas",style:e.normalizeStyle(g.value)},[(e.openBlock(),e.createElementBlock("svg",{class:"tree-view__links",width:e.unref(d).width,height:e.unref(d).height},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(_.value.links,o=>(e.openBlock(),e.createElementBlock("g",{key:o.id,"data-depth":o.target.depth},[e.renderSlot(a.$slots,"link",{link:o},()=>[e.createElementVNode("path",{class:"tree-view__link",d:o.path},null,8,G)])],8,U))),128))],8,j)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(_.value.nodes,o=>(e.openBlock(),e.createElementBlock("div",{key:o.id,class:e.normalizeClass(["tree-view__node",S(o)]),style:e.normalizeStyle(E(o)),"data-depth":o.depth,onClick:f=>z(o,f)},[e.renderSlot(a.$slots,"node",{node:o,data:o.data,depth:o.depth,collapsed:o.collapsed,hasChildren:o.hasChildren,toggle:()=>w(o)},()=>[e.createVNode(L,{label:T(o),"has-children":o.hasChildren,collapsed:o.collapsed,onToggle:f=>w(o)},null,8,["label","has-children","collapsed","onToggle"])])],14,H))),128))],4)],6))}}),Q={key:0,class:"tv-bracket__hint"},Y=["title","onClick","onMouseenter"],Z={key:0,class:"tv-bracket__tooltip"},q={class:"tv-bracket__name"},K={class:"tv-bracket__score"},M=e.defineComponent({__name:"BracketCard",props:{match:{},rows:{default:void 0},myTeamHint:{default:"Нажми для перехода в матч 👇"},techDefeatLabel:{default:"ТП"},techDefeatHint:{default:"Техническое поражение"}},emits:["select"],setup(r,{emit:c}){const i=r,t=c,l=e.ref(null),v=e.computed(()=>n.buildBracketCard(i.match,{rows:i.rows,techDefeatLabel:i.techDefeatLabel}));function h(d){d.team&&t("select",d.team,d)}return(d,b)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["tv-bracket",{"tv-bracket--my-team":v.value.hasMyTeam}])},[v.value.hasMyTeam&&r.myTeamHint?(e.openBlock(),e.createElementBlock("div",Q,e.toDisplayString(r.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:m=>h(s),onMouseenter:m=>l.value=s.key,onMouseleave:b[0]||(b[0]=m=>l.value=null)},[s.team?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[s.isTechDefeat&&l.value===s.key?(e.openBlock(),e.createElementBlock("span",Z,e.toDisplayString(r.techDefeatHint),1)):e.createCommentVNode("",!0),e.createElementVNode("span",q,e.toDisplayString(s.team.name),1),e.createElementVNode("span",K,e.toDisplayString(s.score),1)],64)):e.createCommentVNode("",!0)],42,Y))),128))],2))}}),J=["onClick"],D=e.defineComponent({__name:"BracketRounds",props:{rounds:{},options:{default:void 0},isShow:{type:Boolean,default:!0},swipeSiblingGap:{default:12},hidePassed:{type:Boolean,default:!0},swipe:{type:Boolean,default:void 0},mobileQuery:{default:n.MOBILE_MEDIA_QUERY}},emits:["select","round"],setup(r,{expose:c,emit:i}){const t=r,l=i,v=e.ref(null),h=e.ref(null),d=e.ref(!1),b=e.ref(0),s=e.ref(0),m=e.computed(()=>t.isShow&&(t.swipe??d.value)),p=e.computed(()=>n.resolveOptions(t.options)),y=e.computed(()=>typeof p.value.nodeWidth=="number"?p.value.nodeWidth:n.DEFAULT_OPTIONS.nodeWidth),_=e.computed(()=>m.value?n.levelGapForWidth(b.value,y.value):p.value.levelGap),g=e.computed(()=>n.buildBracketRounds(t.rounds,{nodeWidth:y.value,levelGap:_.value})),C=e.computed(()=>p.value.direction==="right-to-left"||p.value.direction==="bottom-to-top"),E=e.computed(()=>m.value?{...t.options,levelGap:_.value,levelLayout:"stack",siblingGap:(o,f)=>n.roundSiblingGap(f,t.swipeSiblingGap)}:{...t.options}),S=e.computed(()=>({width:`${g.value.width}px`})),T=e.computed(()=>t.isShow?{paddingLeft:`${g.value.offset}px`}:void 0);function w(){const o=h.value;if(!o)return;const f=m.value&&t.hidePassed?s.value:0,u=g.value.columns.length;n.hideRoundsBefore(o,f,B=>n.roundOfDepth(B,u,C.value))}function z(){const o=v.value;if(!o||!m.value)return;const f=g.value.columns[0]?.width??0,u=n.roundAtScroll(o.scrollLeft,f,g.value.columns.length);u!==s.value&&(s.value=u,l("round",u))}e.watch([s,m,g],()=>w());const x=[];e.onMounted(()=>{x.push(n.watchMedia(t.mobileQuery,o=>{d.value=o}),n.watchWidth(v.value?.parentElement,o=>{b.value=o}),n.watchChildren(h.value,w)),w()}),e.onBeforeUnmount(()=>x.forEach(o=>o()));function a(o){const f=v.value;if(!f)return;const u=m.value?f:n.findScrollParent(f);if(!u)return;const B=h.value,$=B?n.nodesInColumn(B,o.index,o.width,g.value.offset)[0]:void 0,P=$?n.horizontalBox($,u):{left:n.offsetWithin(f,u)+o.index*o.width,width:o.width};n.smoothScrollLeft(u,n.roundScrollLeft({...P,viewport:u.clientWidth,scrollWidth:u.scrollWidth}))}function k(o){l("select",o),a(o)}return c({scrollToRound:a}),(o,f)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:v,class:e.normalizeClass(["tv-rounds",{"tv-rounds--swipe":m.value}]),onScroll:z},[r.isShow?(e.openBlock(),e.createElementBlock("div",{key:0,class:"tv-rounds__bar",style:e.normalizeStyle(S.value)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(g.value.columns,u=>(e.openBlock(),e.createElementBlock("div",{key:u.key,class:"tv-rounds__item",style:e.normalizeStyle({width:`${u.width}px`}),onClick:B=>k(u)},[e.renderSlot(o.$slots,"round",{round:u},()=>[e.createTextVNode(e.toDisplayString(u.name),1)])],12,J))),128))],4)):e.createCommentVNode("",!0),e.createElementVNode("div",{ref_key:"contentElement",ref:h,class:"tv-rounds__content",style:e.normalizeStyle(T.value)},[e.renderSlot(o.$slots,"default",{options:E.value,isSwipe:m.value,round:s.value})],4)],34))}}),X=".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}.tree-view--pannable{cursor:grab;touch-action:none}.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;--tv-rounds-fade: .22s;--tv-rounds-link-color: #94a3b8;--tv-rounds-link-width: 3px;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)}.tv-rounds--swipe .tree-view{--tv-link-color: var(--tv-rounds-link-color);--tv-link-width: var(--tv-rounds-link-width)}.tv-rounds--swipe{width:100%;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:none}.tv-rounds--swipe::-webkit-scrollbar{display:none}.tv-rounds--swipe .tv-rounds__item{scroll-snap-align:start}.tv-rounds--swipe [data-depth]{transition:opacity var(--tv-rounds-fade, .22s) ease}.tv-rounds--swipe [data-depth].tv-hidden{visibility:hidden;opacity:0;transition:opacity var(--tv-rounds-fade, .22s) ease,visibility 0s linear var(--tv-rounds-fade, .22s)}.tv-hidden{visibility:hidden}@media(prefers-reduced-motion:reduce){.tv-rounds--swipe [data-depth],.tv-rounds--swipe [data-depth].tv-hidden{transition:none}}";n.injectStyles(X);const V={install(r){r.component("TreeView",N),r.component("TreeNodeCard",L),r.component("BracketCard",M),r.component("BracketRounds",D)}};Object.defineProperty(exports,"DEFAULT_OPTIONS",{enumerable:!0,get:()=>n.DEFAULT_OPTIONS});Object.defineProperty(exports,"MOBILE_MEDIA_QUERY",{enumerable:!0,get:()=>n.MOBILE_MEDIA_QUERY});Object.defineProperty(exports,"bracketCardHeight",{enumerable:!0,get:()=>n.bracketCardHeight});Object.defineProperty(exports,"buildBracketCard",{enumerable:!0,get:()=>n.buildBracketCard});Object.defineProperty(exports,"buildBracketRounds",{enumerable:!0,get:()=>n.buildBracketRounds});exports.BracketCard=M;exports.BracketRounds=D;exports.TreeNodeCard=L;exports.TreeView=N;exports.TreeViewPlugin=V;exports.default=V;exports.usePanZoom=O;exports.useTreeLayout=I;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),r=require("@bigplay/tree-view-core"),F={class:"tree-view-card"},R={class:"tree-view-card__label"},A=["aria-label"],L=e.defineComponent({__name:"TreeNodeCard",props:{label:{},hasChildren:{type:Boolean},collapsed:{type:Boolean}},emits:["toggle"],setup(n,{emit:c}){const s=c;return(t,l)=>(e.openBlock(),e.createElementBlock("div",F,[e.createElementVNode("span",R,e.toDisplayString(n.label),1),n.hasChildren?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"tree-view-card__toggle","data-tv-no-pan":"","aria-label":n.collapsed?"Развернуть":"Свернуть",onClick:l[0]||(l[0]=e.withModifiers(v=>s("toggle"),["stop"]))},e.toDisplayString(n.collapsed?"+":"−"),9,A)):e.createCommentVNode("",!0)]))}});function O(n){const c=e.ref({...r.IDENTITY_TRANSFORM}),s=new r.PanZoomController({pannable:n.pannable,zoomable:n.zoomable,limits:n.limits,onChange:t=>{c.value=t}});return e.watch(n.element,(t,l,v)=>{t&&(s.attach(t),v(()=>s.detach()))},{immediate:!0}),e.onBeforeUnmount(()=>s.detach()),{transform:c,fit:(t,l)=>s.fit(t,l),zoomIn:()=>s.zoomIn(),zoomOut:()=>s.zoomOut(),reset:()=>s.reset(),setTransform:t=>s.set(t)}}function N(n){const c=e.ref(new Set),s=e.computed(()=>r.toTree(n.data(),n.accessors())),t=e.computed(()=>r.applyCollapsed(s.value,c.value)),l=e.computed(()=>r.layoutTree(t.value,n.options()));function v(b){return c.value.has(b)}function m(b){c.value=r.toggleCollapsed(c.value,b)}function d(){c.value=new Set}return{layout:l,isCollapsed:v,toggle:m,expandAll:d}}function H(n,c,s){const t=e.ref(s()?r.estimateVisibleRect():null);function l(){t.value=s()?r.measureVisibleRect(n.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 W=["width","height"],j=["data-depth"],G=["d"],U=["data-depth","onClick"],I=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(n,{expose:c,emit:s}){const t=n,l=s,v=e.ref(null),m=e.ref(null),{layout:d,isCollapsed:b,toggle:i,expandAll:h}=N({data:()=>t.data,options:()=>t.options,accessors:()=>({getId:t.getId,getChildren:t.getChildren,getParentId:t.getParentId})}),p=O({element:v,pannable:()=>t.pannable,zoomable:()=>t.zoomable,limits:()=>t.scaleLimits??r.DEFAULT_SCALE_LIMITS}),y=H(m,()=>p.transform.value.scale,()=>t.virtualize),_=e.computed(()=>t.virtualize?r.visibleLayout(d.value,y.rect.value,t.overscan):d.value),g=e.computed(()=>({width:`${d.value.width}px`,height:`${d.value.height}px`,transform:r.toCssTransform(p.transform.value)})),C=e.computed(()=>{if(t.size!=="content")return;const{scale:a}=p.transform.value;return{width:`${d.value.width*a}px`,height:`${d.value.height*a}px`,overflow:"visible"}});function E(a){return{left:`${a.x}px`,top:`${a.y}px`,width:`${a.width}px`,height:`${a.height}px`}}function S(a){return typeof t.nodeClass=="function"?t.nodeClass(a):t.nodeClass}function z(a){const k=a.data;return k?.name??k?.title??k?.label??a.id}function w(a){i(a.id),l("toggle",a,b(a.id))}function T(a,k){l("node-click",a,k),t.collapsible&&a.hasChildren&&w(a)}function x(a){p.fit({width:d.value.width,height:d.value.height},a)}return e.onMounted(()=>{t.fitOnMount&&e.nextTick(()=>x())}),e.watch(()=>p.transform.value,a=>{y.update(),l("transform",a)}),e.watch(d,()=>{e.nextTick(()=>y.update())}),c({fit:x,zoomIn:p.zoomIn,zoomOut:p.zoomOut,resetTransform:p.reset,setTransform:p.setTransform,expandAll:h,toggle:i,layout:d,transform:p.transform}),(a,k)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:v,class:e.normalizeClass(["tree-view",{"tree-view--pannable":n.pannable,"tree-view--content":n.size==="content"}]),style:e.normalizeStyle(C.value)},[e.createElementVNode("div",{ref_key:"canvasElement",ref:m,class:"tree-view__canvas",style:e.normalizeStyle(g.value)},[(e.openBlock(),e.createElementBlock("svg",{class:"tree-view__links",width:e.unref(d).width,height:e.unref(d).height},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(_.value.links,o=>(e.openBlock(),e.createElementBlock("g",{key:o.id,"data-depth":o.target.depth},[e.renderSlot(a.$slots,"link",{link:o},()=>[e.createElementVNode("path",{class:"tree-view__link",d:o.path},null,8,G)])],8,j))),128))],8,W)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(_.value.nodes,o=>(e.openBlock(),e.createElementBlock("div",{key:o.id,class:e.normalizeClass(["tree-view__node",S(o)]),style:e.normalizeStyle(E(o)),"data-depth":o.depth,onClick:f=>T(o,f)},[e.renderSlot(a.$slots,"node",{node:o,data:o.data,depth:o.depth,collapsed:o.collapsed,hasChildren:o.hasChildren,toggle:()=>w(o)},()=>[e.createVNode(L,{label:z(o),"has-children":o.hasChildren,collapsed:o.collapsed,onToggle:f=>w(o)},null,8,["label","has-children","collapsed","onToggle"])])],14,U))),128))],4)],6))}}),Q={key:0,class:"tv-bracket__hint"},Y=["title","aria-label","aria-expanded"],Z={class:"tv-bracket__rows"},q=["title","onClick","onMouseenter"],K={key:0,class:"tv-bracket__tooltip"},J={class:"tv-bracket__name"},X={class:"tv-bracket__score"},M=e.defineComponent({__name:"BracketCard",props:{match:{},rows:{default:void 0},myTeamHint:{default:"Нажми для перехода в матч 👇"},techDefeatLabel:{default:"ТП"},techDefeatHint:{default:"Техническое поражение"},hasChildren:{type:Boolean,default:!1},collapsed:{type:Boolean,default:!1},collapseHint:{default:"Свернуть предыдущие матчи"},expandHint:{default:"Показать предыдущие матчи"}},emits:["select","toggle"],setup(n,{emit:c}){const s=n,t=c,l=e.ref(null),v=e.computed(()=>r.buildBracketCard(s.match,{rows:s.rows,techDefeatLabel:s.techDefeatLabel}));function m(d){d.team&&t("select",d.team,d)}return(d,b)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["tv-bracket",{"tv-bracket--my-team":v.value.hasMyTeam}])},[v.value.hasMyTeam&&n.myTeamHint?(e.openBlock(),e.createElementBlock("div",Q,e.toDisplayString(n.myTeamHint),1)):e.createCommentVNode("",!0),n.hasChildren?(e.openBlock(),e.createElementBlock("button",{key:1,type:"button",class:e.normalizeClass(["tv-bracket__toggle",{"tv-bracket__toggle--collapsed":n.collapsed}]),title:n.collapsed?n.expandHint:n.collapseHint,"aria-label":n.collapsed?n.expandHint:n.collapseHint,"aria-expanded":!n.collapsed,"data-tv-no-pan":"",onClick:b[0]||(b[0]=e.withModifiers(i=>t("toggle"),["stop"]))},e.toDisplayString(n.collapsed?"+":"−"),11,Y)):e.createCommentVNode("",!0),e.createElementVNode("div",Z,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(v.value.rows,i=>(e.openBlock(),e.createElementBlock("div",{key:i.key,class:e.normalizeClass(["tv-bracket__row",{"tv-bracket__row--winner":i.isWinner,"tv-bracket__row--loser":i.isLoser&&!i.isEmpty,"tv-bracket__row--my":i.isMyTeam,"tv-bracket__row--empty":i.isEmpty}]),title:i.isMyTeam?"Моя команда":void 0,onClick:h=>m(i),onMouseenter:h=>l.value=i.key,onMouseleave:b[1]||(b[1]=h=>l.value=null)},[i.team?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[i.isTechDefeat&&l.value===i.key?(e.openBlock(),e.createElementBlock("span",K,e.toDisplayString(n.techDefeatHint),1)):e.createCommentVNode("",!0),e.createElementVNode("span",J,e.toDisplayString(i.team.name),1),e.createElementVNode("span",X,e.toDisplayString(i.score),1)],64)):e.createCommentVNode("",!0)],42,q))),128))])],2))}}),ee=["onClick"],V=e.defineComponent({__name:"BracketRounds",props:{rounds:{},options:{default:void 0},isShow:{type:Boolean,default:!0},swipeSiblingGap:{default:12},hidePassed:{type:Boolean,default:!0},swipe:{type:Boolean,default:void 0},mobileQuery:{default:r.MOBILE_MEDIA_QUERY}},emits:["select","round"],setup(n,{expose:c,emit:s}){const t=n,l=s,v=e.ref(null),m=e.ref(null),d=e.ref(!1),b=e.ref(0),i=e.ref(0),h=e.computed(()=>t.isShow&&(t.swipe??d.value)),p=e.computed(()=>r.resolveOptions(t.options)),y=e.computed(()=>typeof p.value.nodeWidth=="number"?p.value.nodeWidth:r.DEFAULT_OPTIONS.nodeWidth),_=e.computed(()=>h.value?r.levelGapForWidth(b.value,y.value):p.value.levelGap),g=e.computed(()=>r.buildBracketRounds(t.rounds,{nodeWidth:y.value,levelGap:_.value})),C=e.computed(()=>p.value.direction==="right-to-left"||p.value.direction==="bottom-to-top"),E=e.computed(()=>h.value?{...t.options,levelGap:_.value,levelLayout:"stack",siblingGap:t.swipeSiblingGap,groupGap:(o,f)=>r.roundSiblingGap(f,t.swipeSiblingGap),linkStyle:"bracket",elbowOffset:t.options?.elbowOffset??r.SWIPE_ELBOW_OFFSET}:{...t.options}),S=e.computed(()=>({width:`${g.value.width}px`})),z=e.computed(()=>t.isShow?{paddingLeft:`${g.value.offset}px`}:void 0);function w(){const o=m.value;if(!o)return;const f=h.value&&t.hidePassed?i.value:0,u=g.value.columns.length;r.hideRoundsBefore(o,f,B=>r.roundOfDepth(B,u,C.value))}function T(){const o=v.value;if(!o||!h.value)return;const f=g.value.columns[0]?.width??0,u=r.roundAtScroll(o.scrollLeft,f,g.value.columns.length);u!==i.value&&(i.value=u,l("round",u))}e.watch([i,h,g],()=>w());const x=[];e.onMounted(()=>{x.push(r.watchMedia(t.mobileQuery,o=>{d.value=o}),r.watchWidth(v.value?.parentElement,o=>{b.value=o}),r.watchChildren(m.value,w)),w()}),e.onBeforeUnmount(()=>x.forEach(o=>o()));function a(o){const f=v.value;if(!f)return;const u=h.value?f:r.findScrollParent(f);if(!u)return;const B=m.value,$=B?r.nodesInColumn(B,o.index,o.width,g.value.offset)[0]:void 0,P=$?r.horizontalBox($,u):{left:r.offsetWithin(f,u)+o.index*o.width,width:o.width};r.smoothScrollLeft(u,r.roundScrollLeft({...P,viewport:u.clientWidth,scrollWidth:u.scrollWidth}))}function k(o){l("select",o),a(o)}return c({scrollToRound:a}),(o,f)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:v,class:e.normalizeClass(["tv-rounds",{"tv-rounds--swipe":h.value}]),onScroll:T},[n.isShow?(e.openBlock(),e.createElementBlock("div",{key:0,class:"tv-rounds__bar",style:e.normalizeStyle(S.value)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(g.value.columns,u=>(e.openBlock(),e.createElementBlock("div",{key:u.key,class:"tv-rounds__item",style:e.normalizeStyle({width:`${u.width}px`}),onClick:B=>k(u)},[e.renderSlot(o.$slots,"round",{round:u},()=>[e.createTextVNode(e.toDisplayString(u.name),1)])],12,ee))),128))],4)):e.createCommentVNode("",!0),e.createElementVNode("div",{ref_key:"contentElement",ref:m,class:"tv-rounds__content",style:e.normalizeStyle(z.value)},[e.renderSlot(o.$slots,"default",{options:E.value,isSwipe:h.value,round:i.value})],4)],34))}}),te=".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}.tree-view--pannable{cursor:grab;touch-action:none}.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;--tv-bracket-shadow: 0 0 0 1px rgb(15 23 42 / 8%), 0 1px 3px rgb(15 23 42 / 10%);position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;font-size:12px}.tv-bracket__rows{box-sizing:border-box;display:flex;flex:1 1 auto;flex-direction:column;min-height:0;overflow:hidden;border-radius:var(--tv-bracket-radius);box-shadow:var(--tv-bracket-shadow)}.tv-bracket--my-team .tv-bracket__rows{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__toggle{position:absolute;top:50%;left:-11px;z-index:1;display:flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;font:inherit;font-size:13px;font-weight:700;line-height:1;color:var(--tv-bracket-winner-bg);cursor:pointer;background:#fff;border:1px solid #d1d5db;border-radius:50%;box-shadow:0 1px 3px #0f172a2e;transform:translateY(-50%)}.tv-bracket__toggle:hover{border-color:var(--tv-bracket-winner-bg)}.tv-bracket__toggle--collapsed{color:var(--tv-bracket-winner-color);background:var(--tv-bracket-winner-bg);border-color:var(--tv-bracket-winner-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;--tv-rounds-fade: .22s;--tv-rounds-collapse-scale: .86;--tv-rounds-collapse-ease: cubic-bezier(.4, 0, .2, 1);--tv-rounds-link-color: #94a3b8;--tv-rounds-link-width: 3px;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)}.tv-rounds--swipe .tv-bracket__hint{display:none}.tv-rounds--swipe .tree-view{--tv-link-color: var(--tv-rounds-link-color);--tv-link-width: var(--tv-rounds-link-width)}.tv-rounds--swipe{width:100%;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:none}.tv-rounds--swipe::-webkit-scrollbar{display:none}.tv-rounds--swipe .tv-rounds__item{scroll-snap-align:start;scroll-snap-stop:always}.tv-rounds--swipe [data-depth]{transition:opacity var(--tv-rounds-fade, .22s) ease,transform var(--tv-rounds-fade, .22s) var(--tv-rounds-collapse-ease)}.tv-rounds--swipe [data-depth].tv-hidden{visibility:hidden;opacity:0;transition:opacity var(--tv-rounds-fade, .22s) ease,transform var(--tv-rounds-fade, .22s) var(--tv-rounds-collapse-ease),visibility 0s linear var(--tv-rounds-fade, .22s)}.tv-rounds--swipe .tree-view__node.tv-hidden{transform:scale(var(--tv-rounds-collapse-scale));transform-origin:right center}.tv-hidden{visibility:hidden}@media(prefers-reduced-motion:reduce){.tv-rounds--swipe [data-depth],.tv-rounds--swipe [data-depth].tv-hidden{transition:none}.tv-rounds--swipe .tree-view__node.tv-hidden{transform:none}}";r.injectStyles(te);const D={install(n){n.component("TreeView",I),n.component("TreeNodeCard",L),n.component("BracketCard",M),n.component("BracketRounds",V)}};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=V;exports.TreeNodeCard=L;exports.TreeView=I;exports.TreeViewPlugin=D;exports.default=D;exports.usePanZoom=O;exports.useTreeLayout=N;
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 <!-- data-depth — уровень, из которого линия выходит: по нему её находят\n снаружи, например когда полоса раундов прячет пройденное. -->\n <g v-for=\"link in drawn.links\" :key=\"link.id\" :data-depth=\"link.target.depth\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </g>\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 :data-depth=\"node.depth\"\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 * Меняются только два числа: промежуток между раундами растягивается до\n * ширины экрана, а матчи внутри раунда встают ближе друг к другу. Настройки\n * с этими числами компонент отдаёт в слот — их и передают в `TreeView`.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n hideRoundsBefore,\n horizontalBox,\n levelGapForWidth,\n nodesInColumn,\n offsetWithin,\n resolveOptions,\n roundAtScroll,\n roundOfDepth,\n roundScrollLeft,\n roundSiblingGap,\n smoothScrollLeft,\n watchChildren,\n watchMedia,\n watchWidth,\n type BracketRound,\n type BracketRoundColumn,\n type TreeNode,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Настройки сетки — те же, что уходят в `TreeView`. */\n options?: Partial<TreeViewOptions>\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /**\n * Базовое расстояние между матчами одного раунда в свайпере.\n * Раунд с несколькими матчами получает кратно больше — до четырёх раз.\n */\n swipeSiblingGap?: number\n /** Прятать пройденные раунды и линии, которые из них выходят. */\n hidePassed?: boolean\n /** Включить свайпер принудительно. Не передан — решает ширина экрана. */\n swipe?: boolean\n /** Медиавыражение «узкий экран», при котором включается свайпер. */\n mobileQuery?: string\n }>(),\n {\n options: undefined,\n isShow: true,\n swipeSiblingGap: 12,\n hidePassed: true,\n swipe: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'select', round: BracketRoundColumn): void\n (e: 'round', index: number): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\n/** Сколько места отведено полосе: по нему считается ширина раунда в свайпере. */\nconst available = ref(0)\n/** Раунд, на котором стоит прокрутка. Меняется прямо во время свайпа. */\nconst current = ref(0)\n\n/** Полосы нет — свайпать нечего: сетку в это время таскают и масштабируют. */\nconst isSwipe = computed(() => props.isShow && (props.swipe ?? isNarrow.value))\n\nconst base = computed(() => resolveOptions(props.options))\n\n/**\n * Карточки матчей одной ширины, поэтому у сетки с раундами `nodeWidth` —\n * число. Если это всё-таки функция, считать колонки не по чему: берём значение\n * по умолчанию, чтобы полоса не развалилась.\n */\nconst nodeWidth = computed(() =>\n typeof base.value.nodeWidth === 'number'\n ? base.value.nodeWidth\n : (DEFAULT_OPTIONS.nodeWidth as number),\n)\n\nconst levelGap = computed(() =>\n isSwipe.value ? levelGapForWidth(available.value, nodeWidth.value) : base.value.levelGap,\n)\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: nodeWidth.value, levelGap: levelGap.value }),\n)\n\n/** Дерево турнирной сетки растёт справа налево: первый раунд — самый глубокий. */\nconst mirrored = computed(\n () => base.value.direction === 'right-to-left' || base.value.direction === 'bottom-to-top',\n)\n\n/**\n * Настройки для сетки в свайпере.\n *\n * Раунды растягиваются на ширину экрана, а уровни укладываются каждый сам по\n * себе: в дереве место второго раунда выводится из первого, поэтому отдельно\n * его отступ не задать. Сам отступ зависит от того, сколько матчей в раунде.\n */\nconst gridOptions = computed<Partial<TreeViewOptions>>(() =>\n isSwipe.value\n ? {\n ...props.options,\n levelGap: levelGap.value,\n levelLayout: 'stack',\n siblingGap: (_node: TreeNode<unknown>, count: number) =>\n roundSiblingGap(count, props.swipeSiblingGap),\n }\n : { ...props.options },\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/** Прячет всё, что осталось позади: карточки пройденных раундов и их линии. */\nfunction applyHidden(): void {\n const content = contentElement.value\n if (!content) return\n\n const hideBefore = isSwipe.value && props.hidePassed ? current.value : 0\n const count = model.value.columns.length\n\n hideRoundsBefore(content, hideBefore, (depth) => roundOfDepth(depth, count, mirrored.value))\n}\n\nfunction onScroll(): void {\n const element = rootElement.value\n if (!element || !isSwipe.value) return\n\n const width = model.value.columns[0]?.width ?? 0\n const next = roundAtScroll(element.scrollLeft, width, model.value.columns.length)\n if (next === current.value) return\n\n current.value = next\n emit('round', next)\n}\n\nwatch([current, isSwipe, model], () => applyHidden())\n\nconst stop: Array<() => void> = []\n\nonMounted(() => {\n stop.push(\n watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n }),\n watchWidth(rootElement.value?.parentElement, (width) => {\n available.value = width\n }),\n // Сетка перерисовывает карточки — скрытое расставляем заново.\n watchChildren(contentElement.value, applyHidden),\n )\n\n applyHidden()\n})\n\nonBeforeUnmount(() => stop.forEach((off) => off()))\n\n/**\n * Подводит сетку к матчам раунда — только по горизонтали.\n *\n * Меряем по самой карточке: так раунд встаёт по центру экрана точно, а у финала\n * прокрутка упирается в правый край и он виден целиком. Карточек не нашли\n * (виртуализация) — считаем по колонке, она той же ширины и на том же месте.\n *\n * Вертикаль не трогаем: страница остаётся там, где была.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const element = rootElement.value\n if (!element) return\n\n // В свайпере прокручивается сама полоса, иначе — блок с overflow-x вокруг неё.\n const container = isSwipe.value ? element : findScrollParent(element)\n if (!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\nfunction onSelect(column: BracketRoundColumn): void {\n emit('select', column)\n scrollToRound(column)\n}\n\ndefineExpose({ scrollToRound })\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tv-rounds\"\n :class=\"{ 'tv-rounds--swipe': isSwipe }\"\n @scroll=\"onScroll\"\n >\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 Сюда кладут саму сетку. `options` — настройки, подогнанные под экран:\n в свайпере раунд занимает всю ширину, а матчи внутри стоят ближе.\n -->\n <slot :options=\"gridOptions\" :is-swipe=\"isSwipe\" :round=\"current\" />\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","available","current","isSwipe","base","resolveOptions","nodeWidth","DEFAULT_OPTIONS","levelGap","levelGapForWidth","model","buildBracketRounds","mirrored","gridOptions","_node","count","roundSiblingGap","barStyle","contentStyle","applyHidden","hideBefore","hideRoundsBefore","depth","roundOfDepth","onScroll","width","roundAtScroll","stop","watchMedia","matches","watchWidth","watchChildren","off","scrollToRound","column","container","findScrollParent","nodesInColumn","box","horizontalBox","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 <!-- data-depth — уровень, из которого линия выходит: по нему её находят\n снаружи, например когда полоса раундов прячет пройденное. -->\n <g v-for=\"link in drawn.links\" :key=\"link.id\" :data-depth=\"link.target.depth\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </g>\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 :data-depth=\"node.depth\"\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 hasChildren?: boolean\n /** Предыдущие матчи сейчас свёрнуты. */\n collapsed?: boolean\n /** Подписи кнопки для screen reader. */\n collapseHint?: string\n expandHint?: string\n }>(),\n {\n rows: undefined,\n myTeamHint: 'Нажми для перехода в матч 👇',\n techDefeatLabel: 'ТП',\n techDefeatHint: 'Техническое поражение',\n hasChildren: false,\n collapsed: false,\n collapseHint: 'Свернуть предыдущие матчи',\n expandHint: 'Показать предыдущие матчи',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'select', team: BracketCardTeam, row: BracketCardRow): void\n (e: 'toggle'): void\n}>()\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 <!--\n Кнопка стоит со стороны предыдущих матчей — туда же уходят линии.\n data-tv-no-pan: нажатие не должно утаскивать холст.\n -->\n <button\n v-if=\"hasChildren\"\n type=\"button\"\n class=\"tv-bracket__toggle\"\n :class=\"{ 'tv-bracket__toggle--collapsed': collapsed }\"\n :title=\"collapsed ? expandHint : collapseHint\"\n :aria-label=\"collapsed ? expandHint : collapseHint\"\n :aria-expanded=\"!collapsed\"\n data-tv-no-pan\n @click.stop=\"emit('toggle')\"\n >\n {{ collapsed ? '+' : '−' }}\n </button>\n\n <!-- Строки лежат в своей обёртке: она держит скругление и контур, поэтому\n матч читается как один блок, а не как список из четырёх команд.\n Подсказка и кнопка остаются снаружи — их обрезать нельзя. -->\n <div class=\"tv-bracket__rows\">\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 </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 * Меняются только два числа: промежуток между раундами растягивается до\n * ширины экрана, а матчи внутри раунда встают ближе друг к другу. Настройки\n * с этими числами компонент отдаёт в слот — их и передают в `TreeView`.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n hideRoundsBefore,\n horizontalBox,\n levelGapForWidth,\n nodesInColumn,\n offsetWithin,\n resolveOptions,\n roundAtScroll,\n roundOfDepth,\n roundScrollLeft,\n roundSiblingGap,\n SWIPE_ELBOW_OFFSET,\n smoothScrollLeft,\n watchChildren,\n watchMedia,\n watchWidth,\n type BracketRound,\n type BracketRoundColumn,\n type TreeNode,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Настройки сетки — те же, что уходят в `TreeView`. */\n options?: Partial<TreeViewOptions>\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /**\n * Базовое расстояние между матчами одного раунда в свайпере.\n * Раунд с несколькими матчами получает кратно больше — до четырёх раз.\n */\n swipeSiblingGap?: number\n /** Прятать пройденные раунды и линии, которые из них выходят. */\n hidePassed?: boolean\n /** Включить свайпер принудительно. Не передан — решает ширина экрана. */\n swipe?: boolean\n /** Медиавыражение «узкий экран», при котором включается свайпер. */\n mobileQuery?: string\n }>(),\n {\n options: undefined,\n isShow: true,\n swipeSiblingGap: 12,\n hidePassed: true,\n swipe: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'select', round: BracketRoundColumn): void\n (e: 'round', index: number): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\n/** Сколько места отведено полосе: по нему считается ширина раунда в свайпере. */\nconst available = ref(0)\n/** Раунд, на котором стоит прокрутка. Меняется прямо во время свайпа. */\nconst current = ref(0)\n\n/** Полосы нет — свайпать нечего: сетку в это время таскают и масштабируют. */\nconst isSwipe = computed(() => props.isShow && (props.swipe ?? isNarrow.value))\n\nconst base = computed(() => resolveOptions(props.options))\n\n/**\n * Карточки матчей одной ширины, поэтому у сетки с раундами `nodeWidth` —\n * число. Если это всё-таки функция, считать колонки не по чему: берём значение\n * по умолчанию, чтобы полоса не развалилась.\n */\nconst nodeWidth = computed(() =>\n typeof base.value.nodeWidth === 'number'\n ? base.value.nodeWidth\n : (DEFAULT_OPTIONS.nodeWidth as number),\n)\n\nconst levelGap = computed(() =>\n isSwipe.value ? levelGapForWidth(available.value, nodeWidth.value) : base.value.levelGap,\n)\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: nodeWidth.value, levelGap: levelGap.value }),\n)\n\n/** Дерево турнирной сетки растёт справа налево: первый раунд — самый глубокий. */\nconst mirrored = computed(\n () => base.value.direction === 'right-to-left' || base.value.direction === 'bottom-to-top',\n)\n\n/**\n * Настройки для сетки в свайпере.\n *\n * Раунды растягиваются на ширину экрана, а уровни укладываются каждый сам по\n * себе: в дереве место второго раунда выводится из первого, поэтому отдельно\n * его отступ не задать.\n *\n * Внутри пары матчи стоят тесно, а между парами промежуток крупнее — иначе\n * весь раунд читается как один список и непонятно, кто с кем сходится дальше.\n * Линии рисуются скобкой: пара сходится в одну вертикаль сразу за карточками,\n * и уже от её середины идёт линия к следующему матчу. В раскладке `'stack'`\n * родитель стоит не по центру детей, поэтому обычная «ступенька» выходила бы\n * из верхнего матча пары, а не из середины.\n */\nconst gridOptions = computed<Partial<TreeViewOptions>>(() =>\n isSwipe.value\n ? {\n ...props.options,\n levelGap: levelGap.value,\n levelLayout: 'stack',\n siblingGap: props.swipeSiblingGap,\n groupGap: (_node: TreeNode<unknown>, count: number) =>\n roundSiblingGap(count, props.swipeSiblingGap),\n linkStyle: 'bracket',\n elbowOffset: props.options?.elbowOffset ?? SWIPE_ELBOW_OFFSET,\n }\n : { ...props.options },\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/** Прячет всё, что осталось позади: карточки пройденных раундов и их линии. */\nfunction applyHidden(): void {\n const content = contentElement.value\n if (!content) return\n\n const hideBefore = isSwipe.value && props.hidePassed ? current.value : 0\n const count = model.value.columns.length\n\n hideRoundsBefore(content, hideBefore, (depth) => roundOfDepth(depth, count, mirrored.value))\n}\n\nfunction onScroll(): void {\n const element = rootElement.value\n if (!element || !isSwipe.value) return\n\n const width = model.value.columns[0]?.width ?? 0\n const next = roundAtScroll(element.scrollLeft, width, model.value.columns.length)\n if (next === current.value) return\n\n current.value = next\n emit('round', next)\n}\n\nwatch([current, isSwipe, model], () => applyHidden())\n\nconst stop: Array<() => void> = []\n\nonMounted(() => {\n stop.push(\n watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n }),\n watchWidth(rootElement.value?.parentElement, (width) => {\n available.value = width\n }),\n // Сетка перерисовывает карточки — скрытое расставляем заново.\n watchChildren(contentElement.value, applyHidden),\n )\n\n applyHidden()\n})\n\nonBeforeUnmount(() => stop.forEach((off) => off()))\n\n/**\n * Подводит сетку к матчам раунда — только по горизонтали.\n *\n * Меряем по самой карточке: так раунд встаёт по центру экрана точно, а у финала\n * прокрутка упирается в правый край и он виден целиком. Карточек не нашли\n * (виртуализация) — считаем по колонке, она той же ширины и на том же месте.\n *\n * Вертикаль не трогаем: страница остаётся там, где была.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const element = rootElement.value\n if (!element) return\n\n // В свайпере прокручивается сама полоса, иначе — блок с overflow-x вокруг неё.\n const container = isSwipe.value ? element : findScrollParent(element)\n if (!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\nfunction onSelect(column: BracketRoundColumn): void {\n emit('select', column)\n scrollToRound(column)\n}\n\ndefineExpose({ scrollToRound })\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tv-rounds\"\n :class=\"{ 'tv-rounds--swipe': isSwipe }\"\n @scroll=\"onScroll\"\n >\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 Сюда кладут саму сетку. `options` — настройки, подогнанные под экран:\n в свайпере раунд занимает всю ширину, а матчи внутри стоят ближе.\n -->\n <slot :options=\"gridOptions\" :is-swipe=\"isSwipe\" :round=\"current\" />\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_5","_hoisted_6","_hoisted_7","contentElement","isNarrow","available","current","isSwipe","base","resolveOptions","nodeWidth","DEFAULT_OPTIONS","levelGap","levelGapForWidth","model","buildBracketRounds","mirrored","gridOptions","_node","count","roundSiblingGap","SWIPE_ELBOW_OFFSET","barStyle","contentStyle","applyHidden","hideBefore","hideRoundsBefore","depth","roundOfDepth","onScroll","width","roundAtScroll","stop","watchMedia","matches","watchWidth","watchChildren","off","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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { defineComponent as D, openBlock as c, createElementBlock as u, createElementVNode as z, toDisplayString as x, withModifiers as Z, createCommentVNode as B, ref as g, watch as O, onBeforeUnmount as P, computed as h, onMounted as G, nextTick as U, normalizeStyle as T, normalizeClass as L, unref as j, Fragment as I, renderList as M, renderSlot as R, createVNode as K, createTextVNode as q } from "vue";
2
- import { IDENTITY_TRANSFORM as J, PanZoomController as X, toTree as ee, applyCollapsed as te, layoutTree as oe, toggleCollapsed as ne, estimateVisibleRect as ae, measureVisibleRect as re, DEFAULT_OVERSCAN as ie, visibleLayout as le, toCssTransform as se, DEFAULT_SCALE_LIMITS as de, buildBracketCard as ce, MOBILE_MEDIA_QUERY as ue, resolveOptions as ve, DEFAULT_OPTIONS as pe, levelGapForWidth as he, buildBracketRounds as fe, roundSiblingGap as be, watchMedia as me, watchWidth as we, watchChildren as ge, hideRoundsBefore as ke, roundOfDepth as _e, roundAtScroll as ye, findScrollParent as xe, nodesInColumn as Ce, horizontalBox as Te, offsetWithin as ze, smoothScrollLeft as Se, roundScrollLeft as $e, injectStyles as Ee } from "@bigplay/tree-view-core";
3
- import { DEFAULT_OPTIONS as tt, MOBILE_MEDIA_QUERY as ot, bracketCardHeight as nt, buildBracketCard as at, buildBracketRounds as rt } from "@bigplay/tree-view-core";
4
- const Be = { class: "tree-view-card" }, Le = { class: "tree-view-card__label" }, Ie = ["aria-label"], Q = /* @__PURE__ */ D({
1
+ import { defineComponent as D, openBlock as s, createElementBlock as d, createElementVNode as C, toDisplayString as _, withModifiers as Q, createCommentVNode as z, ref as g, watch as O, onBeforeUnmount as P, computed as f, onMounted as V, nextTick as U, normalizeStyle as S, normalizeClass as E, unref as j, Fragment as I, renderList as M, renderSlot as R, createVNode as K, createTextVNode as q } from "vue";
2
+ import { IDENTITY_TRANSFORM as J, PanZoomController as X, toTree as ee, applyCollapsed as te, layoutTree as oe, toggleCollapsed as ne, estimateVisibleRect as ae, measureVisibleRect as re, DEFAULT_OVERSCAN as ie, visibleLayout as le, toCssTransform as se, DEFAULT_SCALE_LIMITS as de, buildBracketCard as ce, MOBILE_MEDIA_QUERY as ve, resolveOptions as ue, DEFAULT_OPTIONS as pe, levelGapForWidth as fe, buildBracketRounds as he, SWIPE_ELBOW_OFFSET as be, roundSiblingGap as we, watchMedia as me, watchWidth as ge, watchChildren as ke, hideRoundsBefore as xe, roundOfDepth as _e, roundAtScroll as ye, findScrollParent as Ce, nodesInColumn as Te, horizontalBox as ze, offsetWithin as Se, smoothScrollLeft as Ee, roundScrollLeft as $e, injectStyles as Be } from "@bigplay/tree-view-core";
3
+ import { DEFAULT_OPTIONS as at, MOBILE_MEDIA_QUERY as rt, bracketCardHeight as it, buildBracketCard as lt, buildBracketRounds as st } from "@bigplay/tree-view-core";
4
+ const Le = { class: "tree-view-card" }, Ie = { class: "tree-view-card__label" }, Oe = ["aria-label"], Y = /* @__PURE__ */ D({
5
5
  __name: "TreeNodeCard",
6
6
  props: {
7
7
  label: {},
@@ -9,70 +9,70 @@ const Be = { class: "tree-view-card" }, Le = { class: "tree-view-card__label" },
9
9
  collapsed: { type: Boolean }
10
10
  },
11
11
  emits: ["toggle"],
12
- setup(o, { emit: s }) {
13
- const r = s;
14
- return (e, a) => (c(), u("div", Be, [
15
- z("span", Le, x(o.label), 1),
16
- o.hasChildren ? (c(), u("button", {
12
+ setup(o, { emit: c }) {
13
+ const i = c;
14
+ return (e, a) => (s(), d("div", Le, [
15
+ C("span", Ie, _(o.label), 1),
16
+ o.hasChildren ? (s(), d("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": o.collapsed ? "Развернуть" : "Свернуть",
22
- onClick: a[0] || (a[0] = Z((v) => r("toggle"), ["stop"]))
23
- }, x(o.collapsed ? "+" : "−"), 9, Ie)) : B("", !0)
22
+ onClick: a[0] || (a[0] = Q((u) => i("toggle"), ["stop"]))
23
+ }, _(o.collapsed ? "+" : "−"), 9, Oe)) : z("", !0)
24
24
  ]));
25
25
  }
26
26
  });
27
- function Oe(o) {
28
- const s = g({ ...J }), r = new X({
27
+ function Me(o) {
28
+ const c = g({ ...J }), i = new X({
29
29
  pannable: o.pannable,
30
30
  zoomable: o.zoomable,
31
31
  limits: o.limits,
32
32
  onChange: (e) => {
33
- s.value = e;
33
+ c.value = e;
34
34
  }
35
35
  });
36
36
  return O(
37
37
  o.element,
38
- (e, a, v) => {
39
- e && (r.attach(e), v(() => r.detach()));
38
+ (e, a, u) => {
39
+ e && (i.attach(e), u(() => i.detach()));
40
40
  },
41
41
  { immediate: !0 }
42
- ), P(() => r.detach()), {
43
- transform: s,
44
- fit: (e, a) => r.fit(e, a),
45
- zoomIn: () => r.zoomIn(),
46
- zoomOut: () => r.zoomOut(),
47
- reset: () => r.reset(),
48
- setTransform: (e) => r.set(e)
42
+ ), P(() => i.detach()), {
43
+ transform: c,
44
+ fit: (e, a) => i.fit(e, a),
45
+ zoomIn: () => i.zoomIn(),
46
+ zoomOut: () => i.zoomOut(),
47
+ reset: () => i.reset(),
48
+ setTransform: (e) => i.set(e)
49
49
  };
50
50
  }
51
- function Me(o) {
52
- const s = g(/* @__PURE__ */ new Set()), r = h(() => ee(o.data(), o.accessors())), e = h(() => te(r.value, s.value)), a = h(() => oe(e.value, o.options()));
53
- function v(w) {
54
- return s.value.has(w);
51
+ function Re(o) {
52
+ const c = g(/* @__PURE__ */ new Set()), i = f(() => ee(o.data(), o.accessors())), e = f(() => te(i.value, c.value)), a = f(() => oe(e.value, o.options()));
53
+ function u(b) {
54
+ return c.value.has(b);
55
55
  }
56
- function m(w) {
57
- s.value = ne(s.value, w);
56
+ function m(b) {
57
+ c.value = ne(c.value, b);
58
58
  }
59
59
  function l() {
60
- s.value = /* @__PURE__ */ new Set();
60
+ c.value = /* @__PURE__ */ new Set();
61
61
  }
62
- return { layout: a, isCollapsed: v, toggle: m, expandAll: l };
62
+ return { layout: a, isCollapsed: u, toggle: m, expandAll: l };
63
63
  }
64
- function Re(o, s, r) {
65
- const e = g(r() ? ae() : null);
64
+ function De(o, c, i) {
65
+ const e = g(i() ? ae() : null);
66
66
  function a() {
67
- e.value = r() ? re(o.value, s()) : null;
67
+ e.value = i() ? re(o.value, c()) : null;
68
68
  }
69
- return G(() => {
69
+ return V(() => {
70
70
  a(), window.addEventListener("scroll", a, { passive: !0, capture: !0 }), window.addEventListener("resize", a, { passive: !0 });
71
71
  }), P(() => {
72
72
  window.removeEventListener("scroll", a, { capture: !0 }), window.removeEventListener("resize", a);
73
73
  }), { rect: e, update: a };
74
74
  }
75
- const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-depth", "onClick"], We = /* @__PURE__ */ D({
75
+ const Ne = ["width", "height"], We = ["data-depth"], Fe = ["d"], He = ["data-depth", "onClick"], Ae = /* @__PURE__ */ D({
76
76
  __name: "TreeView",
77
77
  props: {
78
78
  data: {},
@@ -91,8 +91,8 @@ const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-dep
91
91
  overscan: { default: ie }
92
92
  },
93
93
  emits: ["node-click", "toggle", "transform"],
94
- setup(o, { expose: s, emit: r }) {
95
- const e = o, a = r, v = g(null), m = g(null), { layout: l, isCollapsed: w, toggle: i, expandAll: b } = Me({
94
+ setup(o, { expose: c, emit: i }) {
95
+ const e = o, a = i, u = g(null), m = g(null), { layout: l, isCollapsed: b, toggle: r, expandAll: w } = Re({
96
96
  data: () => e.data,
97
97
  options: () => e.options,
98
98
  accessors: () => ({
@@ -100,22 +100,22 @@ const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-dep
100
100
  getChildren: e.getChildren,
101
101
  getParentId: e.getParentId
102
102
  })
103
- }), p = Oe({
104
- element: v,
103
+ }), p = Me({
104
+ element: u,
105
105
  pannable: () => e.pannable,
106
106
  zoomable: () => e.zoomable,
107
107
  limits: () => e.scaleLimits ?? de
108
- }), C = Re(
108
+ }), T = De(
109
109
  m,
110
110
  () => p.transform.value.scale,
111
111
  () => e.virtualize
112
- ), S = h(
113
- () => e.virtualize ? le(l.value, C.rect.value, e.overscan) : l.value
114
- ), k = h(() => ({
112
+ ), $ = f(
113
+ () => e.virtualize ? le(l.value, T.rect.value, e.overscan) : l.value
114
+ ), k = f(() => ({
115
115
  width: `${l.value.width}px`,
116
116
  height: `${l.value.height}px`,
117
117
  transform: se(p.transform.value)
118
- })), N = h(() => {
118
+ })), N = f(() => {
119
119
  if (e.size !== "content") return;
120
120
  const { scale: n } = p.transform.value;
121
121
  return {
@@ -124,7 +124,7 @@ const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-dep
124
124
  overflow: "visible"
125
125
  };
126
126
  });
127
- function A(n) {
127
+ function W(n) {
128
128
  return {
129
129
  left: `${n.x}px`,
130
130
  top: `${n.y}px`,
@@ -132,76 +132,76 @@ const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-dep
132
132
  height: `${n.height}px`
133
133
  };
134
134
  }
135
- function V(n) {
135
+ function F(n) {
136
136
  return typeof e.nodeClass == "function" ? e.nodeClass(n) : e.nodeClass;
137
137
  }
138
- function W(n) {
139
- const _ = n.data;
140
- return _?.name ?? _?.title ?? _?.label ?? n.id;
138
+ function H(n) {
139
+ const x = n.data;
140
+ return x?.name ?? x?.title ?? x?.label ?? n.id;
141
141
  }
142
142
  function y(n) {
143
- i(n.id), a("toggle", n, w(n.id));
143
+ r(n.id), a("toggle", n, b(n.id));
144
144
  }
145
- function F(n, _) {
146
- a("node-click", n, _), e.collapsible && n.hasChildren && y(n);
145
+ function A(n, x) {
146
+ a("node-click", n, x), e.collapsible && n.hasChildren && y(n);
147
147
  }
148
- function $(n) {
148
+ function B(n) {
149
149
  p.fit({ width: l.value.width, height: l.value.height }, n);
150
150
  }
151
- return G(() => {
152
- e.fitOnMount && U(() => $());
151
+ return V(() => {
152
+ e.fitOnMount && U(() => B());
153
153
  }), O(
154
154
  () => p.transform.value,
155
155
  (n) => {
156
- C.update(), a("transform", n);
156
+ T.update(), a("transform", n);
157
157
  }
158
158
  ), O(l, () => {
159
- U(() => C.update());
160
- }), s({
161
- fit: $,
159
+ U(() => T.update());
160
+ }), c({
161
+ fit: B,
162
162
  zoomIn: p.zoomIn,
163
163
  zoomOut: p.zoomOut,
164
164
  resetTransform: p.reset,
165
165
  setTransform: p.setTransform,
166
- expandAll: b,
167
- toggle: i,
166
+ expandAll: w,
167
+ toggle: r,
168
168
  layout: l,
169
169
  transform: p.transform
170
- }), (n, _) => (c(), u("div", {
170
+ }), (n, x) => (s(), d("div", {
171
171
  ref_key: "rootElement",
172
- ref: v,
173
- class: L(["tree-view", { "tree-view--pannable": o.pannable, "tree-view--content": o.size === "content" }]),
174
- style: T(N.value)
172
+ ref: u,
173
+ class: E(["tree-view", { "tree-view--pannable": o.pannable, "tree-view--content": o.size === "content" }]),
174
+ style: S(N.value)
175
175
  }, [
176
- z("div", {
176
+ C("div", {
177
177
  ref_key: "canvasElement",
178
178
  ref: m,
179
179
  class: "tree-view__canvas",
180
- style: T(k.value)
180
+ style: S(k.value)
181
181
  }, [
182
- (c(), u("svg", {
182
+ (s(), d("svg", {
183
183
  class: "tree-view__links",
184
184
  width: j(l).width,
185
185
  height: j(l).height
186
186
  }, [
187
- (c(!0), u(I, null, M(S.value.links, (t) => (c(), u("g", {
187
+ (s(!0), d(I, null, M($.value.links, (t) => (s(), d("g", {
188
188
  key: t.id,
189
189
  "data-depth": t.target.depth
190
190
  }, [
191
191
  R(n.$slots, "link", { link: t }, () => [
192
- z("path", {
192
+ C("path", {
193
193
  class: "tree-view__link",
194
194
  d: t.path
195
- }, null, 8, Ae)
195
+ }, null, 8, Fe)
196
196
  ])
197
- ], 8, Ne))), 128))
198
- ], 8, De)),
199
- (c(!0), u(I, null, M(S.value.nodes, (t) => (c(), u("div", {
197
+ ], 8, We))), 128))
198
+ ], 8, Ne)),
199
+ (s(!0), d(I, null, M($.value.nodes, (t) => (s(), d("div", {
200
200
  key: t.id,
201
- class: L(["tree-view__node", V(t)]),
202
- style: T(A(t)),
201
+ class: E(["tree-view__node", F(t)]),
202
+ style: S(W(t)),
203
203
  "data-depth": t.depth,
204
- onClick: (f) => F(t, f)
204
+ onClick: (h) => A(t, h)
205
205
  }, [
206
206
  R(n.$slots, "node", {
207
207
  node: t,
@@ -211,66 +211,82 @@ const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-dep
211
211
  hasChildren: t.hasChildren,
212
212
  toggle: () => y(t)
213
213
  }, () => [
214
- K(Q, {
215
- label: W(t),
214
+ K(Y, {
215
+ label: H(t),
216
216
  "has-children": t.hasChildren,
217
217
  collapsed: t.collapsed,
218
- onToggle: (f) => y(t)
218
+ onToggle: (h) => y(t)
219
219
  }, null, 8, ["label", "has-children", "collapsed", "onToggle"])
220
220
  ])
221
- ], 14, Ve))), 128))
221
+ ], 14, He))), 128))
222
222
  ], 4)
223
223
  ], 6));
224
224
  }
225
- }), Fe = {
225
+ }), Pe = {
226
226
  key: 0,
227
227
  class: "tv-bracket__hint"
228
- }, Pe = ["title", "onClick", "onMouseenter"], Ge = {
228
+ }, Ve = ["title", "aria-label", "aria-expanded"], Ge = { class: "tv-bracket__rows" }, Ue = ["title", "onClick", "onMouseenter"], je = {
229
229
  key: 0,
230
230
  class: "tv-bracket__tooltip"
231
- }, He = { class: "tv-bracket__name" }, Ue = { class: "tv-bracket__score" }, je = /* @__PURE__ */ D({
231
+ }, Qe = { class: "tv-bracket__name" }, Ye = { class: "tv-bracket__score" }, Ze = /* @__PURE__ */ D({
232
232
  __name: "BracketCard",
233
233
  props: {
234
234
  match: {},
235
235
  rows: { default: void 0 },
236
236
  myTeamHint: { default: "Нажми для перехода в матч 👇" },
237
237
  techDefeatLabel: { default: "ТП" },
238
- techDefeatHint: { default: "Техническое поражение" }
238
+ techDefeatHint: { default: "Техническое поражение" },
239
+ hasChildren: { type: Boolean, default: !1 },
240
+ collapsed: { type: Boolean, default: !1 },
241
+ collapseHint: { default: "Свернуть предыдущие матчи" },
242
+ expandHint: { default: "Показать предыдущие матчи" }
239
243
  },
240
- emits: ["select"],
241
- setup(o, { emit: s }) {
242
- const r = o, e = s, a = g(null), v = h(
243
- () => ce(r.match, { rows: r.rows, techDefeatLabel: r.techDefeatLabel })
244
+ emits: ["select", "toggle"],
245
+ setup(o, { emit: c }) {
246
+ const i = o, e = c, a = g(null), u = f(
247
+ () => ce(i.match, { rows: i.rows, techDefeatLabel: i.techDefeatLabel })
244
248
  );
245
249
  function m(l) {
246
250
  l.team && e("select", l.team, l);
247
251
  }
248
- return (l, w) => (c(), u("div", {
249
- class: L(["tv-bracket", { "tv-bracket--my-team": v.value.hasMyTeam }])
252
+ return (l, b) => (s(), d("div", {
253
+ class: E(["tv-bracket", { "tv-bracket--my-team": u.value.hasMyTeam }])
250
254
  }, [
251
- v.value.hasMyTeam && o.myTeamHint ? (c(), u("div", Fe, x(o.myTeamHint), 1)) : B("", !0),
252
- (c(!0), u(I, null, M(v.value.rows, (i) => (c(), u("div", {
253
- key: i.key,
254
- class: L(["tv-bracket__row", {
255
- "tv-bracket__row--winner": i.isWinner,
256
- "tv-bracket__row--loser": i.isLoser && !i.isEmpty,
257
- "tv-bracket__row--my": i.isMyTeam,
258
- "tv-bracket__row--empty": i.isEmpty
259
- }]),
260
- title: i.isMyTeam ? "Моя команда" : void 0,
261
- onClick: (b) => m(i),
262
- onMouseenter: (b) => a.value = i.key,
263
- onMouseleave: w[0] || (w[0] = (b) => a.value = null)
264
- }, [
265
- i.team ? (c(), u(I, { key: 0 }, [
266
- i.isTechDefeat && a.value === i.key ? (c(), u("span", Ge, x(o.techDefeatHint), 1)) : B("", !0),
267
- z("span", He, x(i.team.name), 1),
268
- z("span", Ue, x(i.score), 1)
269
- ], 64)) : B("", !0)
270
- ], 42, Pe))), 128))
255
+ u.value.hasMyTeam && o.myTeamHint ? (s(), d("div", Pe, _(o.myTeamHint), 1)) : z("", !0),
256
+ o.hasChildren ? (s(), d("button", {
257
+ key: 1,
258
+ type: "button",
259
+ class: E(["tv-bracket__toggle", { "tv-bracket__toggle--collapsed": o.collapsed }]),
260
+ title: o.collapsed ? o.expandHint : o.collapseHint,
261
+ "aria-label": o.collapsed ? o.expandHint : o.collapseHint,
262
+ "aria-expanded": !o.collapsed,
263
+ "data-tv-no-pan": "",
264
+ onClick: b[0] || (b[0] = Q((r) => e("toggle"), ["stop"]))
265
+ }, _(o.collapsed ? "+" : "−"), 11, Ve)) : z("", !0),
266
+ C("div", Ge, [
267
+ (s(!0), d(I, null, M(u.value.rows, (r) => (s(), d("div", {
268
+ key: r.key,
269
+ class: E(["tv-bracket__row", {
270
+ "tv-bracket__row--winner": r.isWinner,
271
+ "tv-bracket__row--loser": r.isLoser && !r.isEmpty,
272
+ "tv-bracket__row--my": r.isMyTeam,
273
+ "tv-bracket__row--empty": r.isEmpty
274
+ }]),
275
+ title: r.isMyTeam ? "Моя команда" : void 0,
276
+ onClick: (w) => m(r),
277
+ onMouseenter: (w) => a.value = r.key,
278
+ onMouseleave: b[1] || (b[1] = (w) => a.value = null)
279
+ }, [
280
+ r.team ? (s(), d(I, { key: 0 }, [
281
+ r.isTechDefeat && a.value === r.key ? (s(), d("span", je, _(o.techDefeatHint), 1)) : z("", !0),
282
+ C("span", Qe, _(r.team.name), 1),
283
+ C("span", Ye, _(r.score), 1)
284
+ ], 64)) : z("", !0)
285
+ ], 42, Ue))), 128))
286
+ ])
271
287
  ], 2));
272
288
  }
273
- }), Qe = ["onClick"], Ye = /* @__PURE__ */ D({
289
+ }), Ke = ["onClick"], qe = /* @__PURE__ */ D({
274
290
  __name: "BracketRounds",
275
291
  props: {
276
292
  rounds: {},
@@ -279,128 +295,131 @@ const De = ["width", "height"], Ne = ["data-depth"], Ae = ["d"], Ve = ["data-dep
279
295
  swipeSiblingGap: { default: 12 },
280
296
  hidePassed: { type: Boolean, default: !0 },
281
297
  swipe: { type: Boolean, default: void 0 },
282
- mobileQuery: { default: ue }
298
+ mobileQuery: { default: ve }
283
299
  },
284
300
  emits: ["select", "round"],
285
- setup(o, { expose: s, emit: r }) {
286
- const e = o, a = r, v = g(null), m = g(null), l = g(!1), w = g(0), i = g(0), b = h(() => e.isShow && (e.swipe ?? l.value)), p = h(() => ve(e.options)), C = h(
301
+ setup(o, { expose: c, emit: i }) {
302
+ const e = o, a = i, u = g(null), m = g(null), l = g(!1), b = g(0), r = g(0), w = f(() => e.isShow && (e.swipe ?? l.value)), p = f(() => ue(e.options)), T = f(
287
303
  () => typeof p.value.nodeWidth == "number" ? p.value.nodeWidth : pe.nodeWidth
288
- ), S = h(
289
- () => b.value ? he(w.value, C.value) : p.value.levelGap
290
- ), k = h(
291
- () => fe(e.rounds, { nodeWidth: C.value, levelGap: S.value })
292
- ), N = h(
304
+ ), $ = f(
305
+ () => w.value ? fe(b.value, T.value) : p.value.levelGap
306
+ ), k = f(
307
+ () => he(e.rounds, { nodeWidth: T.value, levelGap: $.value })
308
+ ), N = f(
293
309
  () => p.value.direction === "right-to-left" || p.value.direction === "bottom-to-top"
294
- ), A = h(
295
- () => b.value ? {
310
+ ), W = f(
311
+ () => w.value ? {
296
312
  ...e.options,
297
- levelGap: S.value,
313
+ levelGap: $.value,
298
314
  levelLayout: "stack",
299
- siblingGap: (t, f) => be(f, e.swipeSiblingGap)
315
+ siblingGap: e.swipeSiblingGap,
316
+ groupGap: (t, h) => we(h, e.swipeSiblingGap),
317
+ linkStyle: "bracket",
318
+ elbowOffset: e.options?.elbowOffset ?? be
300
319
  } : { ...e.options }
301
- ), V = h(() => ({ width: `${k.value.width}px` })), W = h(
320
+ ), F = f(() => ({ width: `${k.value.width}px` })), H = f(
302
321
  () => e.isShow ? { paddingLeft: `${k.value.offset}px` } : void 0
303
322
  );
304
323
  function y() {
305
324
  const t = m.value;
306
325
  if (!t) return;
307
- const f = b.value && e.hidePassed ? i.value : 0, d = k.value.columns.length;
308
- ke(t, f, (E) => _e(E, d, N.value));
326
+ const h = w.value && e.hidePassed ? r.value : 0, v = k.value.columns.length;
327
+ xe(t, h, (L) => _e(L, v, N.value));
309
328
  }
310
- function F() {
311
- const t = v.value;
312
- if (!t || !b.value) return;
313
- const f = k.value.columns[0]?.width ?? 0, d = ye(t.scrollLeft, f, k.value.columns.length);
314
- d !== i.value && (i.value = d, a("round", d));
329
+ function A() {
330
+ const t = u.value;
331
+ if (!t || !w.value) return;
332
+ const h = k.value.columns[0]?.width ?? 0, v = ye(t.scrollLeft, h, k.value.columns.length);
333
+ v !== r.value && (r.value = v, a("round", v));
315
334
  }
316
- O([i, b, k], () => y());
317
- const $ = [];
318
- G(() => {
319
- $.push(
335
+ O([r, w, k], () => y());
336
+ const B = [];
337
+ V(() => {
338
+ B.push(
320
339
  me(e.mobileQuery, (t) => {
321
340
  l.value = t;
322
341
  }),
323
- we(v.value?.parentElement, (t) => {
324
- w.value = t;
342
+ ge(u.value?.parentElement, (t) => {
343
+ b.value = t;
325
344
  }),
326
345
  // Сетка перерисовывает карточки — скрытое расставляем заново.
327
- ge(m.value, y)
346
+ ke(m.value, y)
328
347
  ), y();
329
- }), P(() => $.forEach((t) => t()));
348
+ }), P(() => B.forEach((t) => t()));
330
349
  function n(t) {
331
- const f = v.value;
332
- if (!f) return;
333
- const d = b.value ? f : xe(f);
334
- if (!d) return;
335
- const E = m.value, H = E ? Ce(E, t.index, t.width, k.value.offset)[0] : void 0, Y = H ? Te(H, d) : { left: ze(f, d) + t.index * t.width, width: t.width };
336
- Se(
337
- d,
350
+ const h = u.value;
351
+ if (!h) return;
352
+ const v = w.value ? h : Ce(h);
353
+ if (!v) return;
354
+ const L = m.value, G = L ? Te(L, t.index, t.width, k.value.offset)[0] : void 0, Z = G ? ze(G, v) : { left: Se(h, v) + t.index * t.width, width: t.width };
355
+ Ee(
356
+ v,
338
357
  $e({
339
- ...Y,
340
- viewport: d.clientWidth,
341
- scrollWidth: d.scrollWidth
358
+ ...Z,
359
+ viewport: v.clientWidth,
360
+ scrollWidth: v.scrollWidth
342
361
  })
343
362
  );
344
363
  }
345
- function _(t) {
364
+ function x(t) {
346
365
  a("select", t), n(t);
347
366
  }
348
- return s({ scrollToRound: n }), (t, f) => (c(), u("div", {
367
+ return c({ scrollToRound: n }), (t, h) => (s(), d("div", {
349
368
  ref_key: "rootElement",
350
- ref: v,
351
- class: L(["tv-rounds", { "tv-rounds--swipe": b.value }]),
352
- onScroll: F
369
+ ref: u,
370
+ class: E(["tv-rounds", { "tv-rounds--swipe": w.value }]),
371
+ onScroll: A
353
372
  }, [
354
- o.isShow ? (c(), u("div", {
373
+ o.isShow ? (s(), d("div", {
355
374
  key: 0,
356
375
  class: "tv-rounds__bar",
357
- style: T(V.value)
376
+ style: S(F.value)
358
377
  }, [
359
- (c(!0), u(I, null, M(k.value.columns, (d) => (c(), u("div", {
360
- key: d.key,
378
+ (s(!0), d(I, null, M(k.value.columns, (v) => (s(), d("div", {
379
+ key: v.key,
361
380
  class: "tv-rounds__item",
362
- style: T({ width: `${d.width}px` }),
363
- onClick: (E) => _(d)
381
+ style: S({ width: `${v.width}px` }),
382
+ onClick: (L) => x(v)
364
383
  }, [
365
- R(t.$slots, "round", { round: d }, () => [
366
- q(x(d.name), 1)
384
+ R(t.$slots, "round", { round: v }, () => [
385
+ q(_(v.name), 1)
367
386
  ])
368
- ], 12, Qe))), 128))
369
- ], 4)) : B("", !0),
370
- z("div", {
387
+ ], 12, Ke))), 128))
388
+ ], 4)) : z("", !0),
389
+ C("div", {
371
390
  ref_key: "contentElement",
372
391
  ref: m,
373
392
  class: "tv-rounds__content",
374
- style: T(W.value)
393
+ style: S(H.value)
375
394
  }, [
376
395
  R(t.$slots, "default", {
377
- options: A.value,
378
- isSwipe: b.value,
379
- round: i.value
396
+ options: W.value,
397
+ isSwipe: w.value,
398
+ round: r.value
380
399
  })
381
400
  ], 4)
382
401
  ], 34));
383
402
  }
384
- }), Ze = ".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}.tree-view--pannable{cursor:grab;touch-action:none}.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;--tv-rounds-fade: .22s;--tv-rounds-link-color: #94a3b8;--tv-rounds-link-width: 3px;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)}.tv-rounds--swipe .tree-view{--tv-link-color: var(--tv-rounds-link-color);--tv-link-width: var(--tv-rounds-link-width)}.tv-rounds--swipe{width:100%;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:none}.tv-rounds--swipe::-webkit-scrollbar{display:none}.tv-rounds--swipe .tv-rounds__item{scroll-snap-align:start}.tv-rounds--swipe [data-depth]{transition:opacity var(--tv-rounds-fade, .22s) ease}.tv-rounds--swipe [data-depth].tv-hidden{visibility:hidden;opacity:0;transition:opacity var(--tv-rounds-fade, .22s) ease,visibility 0s linear var(--tv-rounds-fade, .22s)}.tv-hidden{visibility:hidden}@media(prefers-reduced-motion:reduce){.tv-rounds--swipe [data-depth],.tv-rounds--swipe [data-depth].tv-hidden{transition:none}}";
385
- Ee(Ze);
386
- const Je = {
403
+ }), Je = ".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}.tree-view--pannable{cursor:grab;touch-action:none}.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;--tv-bracket-shadow: 0 0 0 1px rgb(15 23 42 / 8%), 0 1px 3px rgb(15 23 42 / 10%);position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:100%;height:100%;font-size:12px}.tv-bracket__rows{box-sizing:border-box;display:flex;flex:1 1 auto;flex-direction:column;min-height:0;overflow:hidden;border-radius:var(--tv-bracket-radius);box-shadow:var(--tv-bracket-shadow)}.tv-bracket--my-team .tv-bracket__rows{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__toggle{position:absolute;top:50%;left:-11px;z-index:1;display:flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;font:inherit;font-size:13px;font-weight:700;line-height:1;color:var(--tv-bracket-winner-bg);cursor:pointer;background:#fff;border:1px solid #d1d5db;border-radius:50%;box-shadow:0 1px 3px #0f172a2e;transform:translateY(-50%)}.tv-bracket__toggle:hover{border-color:var(--tv-bracket-winner-bg)}.tv-bracket__toggle--collapsed{color:var(--tv-bracket-winner-color);background:var(--tv-bracket-winner-bg);border-color:var(--tv-bracket-winner-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;--tv-rounds-fade: .22s;--tv-rounds-collapse-scale: .86;--tv-rounds-collapse-ease: cubic-bezier(.4, 0, .2, 1);--tv-rounds-link-color: #94a3b8;--tv-rounds-link-width: 3px;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)}.tv-rounds--swipe .tv-bracket__hint{display:none}.tv-rounds--swipe .tree-view{--tv-link-color: var(--tv-rounds-link-color);--tv-link-width: var(--tv-rounds-link-width)}.tv-rounds--swipe{width:100%;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:none}.tv-rounds--swipe::-webkit-scrollbar{display:none}.tv-rounds--swipe .tv-rounds__item{scroll-snap-align:start;scroll-snap-stop:always}.tv-rounds--swipe [data-depth]{transition:opacity var(--tv-rounds-fade, .22s) ease,transform var(--tv-rounds-fade, .22s) var(--tv-rounds-collapse-ease)}.tv-rounds--swipe [data-depth].tv-hidden{visibility:hidden;opacity:0;transition:opacity var(--tv-rounds-fade, .22s) ease,transform var(--tv-rounds-fade, .22s) var(--tv-rounds-collapse-ease),visibility 0s linear var(--tv-rounds-fade, .22s)}.tv-rounds--swipe .tree-view__node.tv-hidden{transform:scale(var(--tv-rounds-collapse-scale));transform-origin:right center}.tv-hidden{visibility:hidden}@media(prefers-reduced-motion:reduce){.tv-rounds--swipe [data-depth],.tv-rounds--swipe [data-depth].tv-hidden{transition:none}.tv-rounds--swipe .tree-view__node.tv-hidden{transform:none}}";
404
+ Be(Je);
405
+ const tt = {
387
406
  install(o) {
388
- o.component("TreeView", We), o.component("TreeNodeCard", Q), o.component("BracketCard", je), o.component("BracketRounds", Ye);
407
+ o.component("TreeView", Ae), o.component("TreeNodeCard", Y), o.component("BracketCard", Ze), o.component("BracketRounds", qe);
389
408
  }
390
409
  };
391
410
  export {
392
- je as BracketCard,
393
- Ye as BracketRounds,
394
- tt as DEFAULT_OPTIONS,
395
- ot as MOBILE_MEDIA_QUERY,
396
- Q as TreeNodeCard,
397
- We as TreeView,
398
- Je as TreeViewPlugin,
399
- nt as bracketCardHeight,
400
- at as buildBracketCard,
401
- rt as buildBracketRounds,
402
- Je as default,
403
- Oe as usePanZoom,
404
- Me as useTreeLayout
411
+ Ze as BracketCard,
412
+ qe as BracketRounds,
413
+ at as DEFAULT_OPTIONS,
414
+ rt as MOBILE_MEDIA_QUERY,
415
+ Y as TreeNodeCard,
416
+ Ae as TreeView,
417
+ tt as TreeViewPlugin,
418
+ it as bracketCardHeight,
419
+ lt as buildBracketCard,
420
+ st as buildBracketRounds,
421
+ tt as default,
422
+ Me as usePanZoom,
423
+ Re as useTreeLayout
405
424
  };
406
425
  //# sourceMappingURL=index.js.map
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 <!-- data-depth — уровень, из которого линия выходит: по нему её находят\n снаружи, например когда полоса раундов прячет пройденное. -->\n <g v-for=\"link in drawn.links\" :key=\"link.id\" :data-depth=\"link.target.depth\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </g>\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 :data-depth=\"node.depth\"\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 * Меняются только два числа: промежуток между раундами растягивается до\n * ширины экрана, а матчи внутри раунда встают ближе друг к другу. Настройки\n * с этими числами компонент отдаёт в слот — их и передают в `TreeView`.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n hideRoundsBefore,\n horizontalBox,\n levelGapForWidth,\n nodesInColumn,\n offsetWithin,\n resolveOptions,\n roundAtScroll,\n roundOfDepth,\n roundScrollLeft,\n roundSiblingGap,\n smoothScrollLeft,\n watchChildren,\n watchMedia,\n watchWidth,\n type BracketRound,\n type BracketRoundColumn,\n type TreeNode,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Настройки сетки — те же, что уходят в `TreeView`. */\n options?: Partial<TreeViewOptions>\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /**\n * Базовое расстояние между матчами одного раунда в свайпере.\n * Раунд с несколькими матчами получает кратно больше — до четырёх раз.\n */\n swipeSiblingGap?: number\n /** Прятать пройденные раунды и линии, которые из них выходят. */\n hidePassed?: boolean\n /** Включить свайпер принудительно. Не передан — решает ширина экрана. */\n swipe?: boolean\n /** Медиавыражение «узкий экран», при котором включается свайпер. */\n mobileQuery?: string\n }>(),\n {\n options: undefined,\n isShow: true,\n swipeSiblingGap: 12,\n hidePassed: true,\n swipe: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'select', round: BracketRoundColumn): void\n (e: 'round', index: number): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\n/** Сколько места отведено полосе: по нему считается ширина раунда в свайпере. */\nconst available = ref(0)\n/** Раунд, на котором стоит прокрутка. Меняется прямо во время свайпа. */\nconst current = ref(0)\n\n/** Полосы нет — свайпать нечего: сетку в это время таскают и масштабируют. */\nconst isSwipe = computed(() => props.isShow && (props.swipe ?? isNarrow.value))\n\nconst base = computed(() => resolveOptions(props.options))\n\n/**\n * Карточки матчей одной ширины, поэтому у сетки с раундами `nodeWidth` —\n * число. Если это всё-таки функция, считать колонки не по чему: берём значение\n * по умолчанию, чтобы полоса не развалилась.\n */\nconst nodeWidth = computed(() =>\n typeof base.value.nodeWidth === 'number'\n ? base.value.nodeWidth\n : (DEFAULT_OPTIONS.nodeWidth as number),\n)\n\nconst levelGap = computed(() =>\n isSwipe.value ? levelGapForWidth(available.value, nodeWidth.value) : base.value.levelGap,\n)\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: nodeWidth.value, levelGap: levelGap.value }),\n)\n\n/** Дерево турнирной сетки растёт справа налево: первый раунд — самый глубокий. */\nconst mirrored = computed(\n () => base.value.direction === 'right-to-left' || base.value.direction === 'bottom-to-top',\n)\n\n/**\n * Настройки для сетки в свайпере.\n *\n * Раунды растягиваются на ширину экрана, а уровни укладываются каждый сам по\n * себе: в дереве место второго раунда выводится из первого, поэтому отдельно\n * его отступ не задать. Сам отступ зависит от того, сколько матчей в раунде.\n */\nconst gridOptions = computed<Partial<TreeViewOptions>>(() =>\n isSwipe.value\n ? {\n ...props.options,\n levelGap: levelGap.value,\n levelLayout: 'stack',\n siblingGap: (_node: TreeNode<unknown>, count: number) =>\n roundSiblingGap(count, props.swipeSiblingGap),\n }\n : { ...props.options },\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/** Прячет всё, что осталось позади: карточки пройденных раундов и их линии. */\nfunction applyHidden(): void {\n const content = contentElement.value\n if (!content) return\n\n const hideBefore = isSwipe.value && props.hidePassed ? current.value : 0\n const count = model.value.columns.length\n\n hideRoundsBefore(content, hideBefore, (depth) => roundOfDepth(depth, count, mirrored.value))\n}\n\nfunction onScroll(): void {\n const element = rootElement.value\n if (!element || !isSwipe.value) return\n\n const width = model.value.columns[0]?.width ?? 0\n const next = roundAtScroll(element.scrollLeft, width, model.value.columns.length)\n if (next === current.value) return\n\n current.value = next\n emit('round', next)\n}\n\nwatch([current, isSwipe, model], () => applyHidden())\n\nconst stop: Array<() => void> = []\n\nonMounted(() => {\n stop.push(\n watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n }),\n watchWidth(rootElement.value?.parentElement, (width) => {\n available.value = width\n }),\n // Сетка перерисовывает карточки — скрытое расставляем заново.\n watchChildren(contentElement.value, applyHidden),\n )\n\n applyHidden()\n})\n\nonBeforeUnmount(() => stop.forEach((off) => off()))\n\n/**\n * Подводит сетку к матчам раунда — только по горизонтали.\n *\n * Меряем по самой карточке: так раунд встаёт по центру экрана точно, а у финала\n * прокрутка упирается в правый край и он виден целиком. Карточек не нашли\n * (виртуализация) — считаем по колонке, она той же ширины и на том же месте.\n *\n * Вертикаль не трогаем: страница остаётся там, где была.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const element = rootElement.value\n if (!element) return\n\n // В свайпере прокручивается сама полоса, иначе — блок с overflow-x вокруг неё.\n const container = isSwipe.value ? element : findScrollParent(element)\n if (!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\nfunction onSelect(column: BracketRoundColumn): void {\n emit('select', column)\n scrollToRound(column)\n}\n\ndefineExpose({ scrollToRound })\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tv-rounds\"\n :class=\"{ 'tv-rounds--swipe': isSwipe }\"\n @scroll=\"onScroll\"\n >\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 Сюда кладут саму сетку. `options` — настройки, подогнанные под экран:\n в свайпере раунд занимает всю ширину, а матчи внутри стоят ближе.\n -->\n <slot :options=\"gridOptions\" :is-swipe=\"isSwipe\" :round=\"current\" />\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","available","current","isSwipe","base","resolveOptions","nodeWidth","DEFAULT_OPTIONS","levelGap","levelGapForWidth","model","buildBracketRounds","mirrored","gridOptions","_node","count","roundSiblingGap","barStyle","contentStyle","applyHidden","hideBefore","hideRoundsBefore","depth","roundOfDepth","onScroll","width","roundAtScroll","stop","watchMedia","matches","watchWidth","watchChildren","off","scrollToRound","column","container","findScrollParent","nodesInColumn","box","horizontalBox","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 <!-- data-depth — уровень, из которого линия выходит: по нему её находят\n снаружи, например когда полоса раундов прячет пройденное. -->\n <g v-for=\"link in drawn.links\" :key=\"link.id\" :data-depth=\"link.target.depth\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </g>\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 :data-depth=\"node.depth\"\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 hasChildren?: boolean\n /** Предыдущие матчи сейчас свёрнуты. */\n collapsed?: boolean\n /** Подписи кнопки для screen reader. */\n collapseHint?: string\n expandHint?: string\n }>(),\n {\n rows: undefined,\n myTeamHint: 'Нажми для перехода в матч 👇',\n techDefeatLabel: 'ТП',\n techDefeatHint: 'Техническое поражение',\n hasChildren: false,\n collapsed: false,\n collapseHint: 'Свернуть предыдущие матчи',\n expandHint: 'Показать предыдущие матчи',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'select', team: BracketCardTeam, row: BracketCardRow): void\n (e: 'toggle'): void\n}>()\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 <!--\n Кнопка стоит со стороны предыдущих матчей — туда же уходят линии.\n data-tv-no-pan: нажатие не должно утаскивать холст.\n -->\n <button\n v-if=\"hasChildren\"\n type=\"button\"\n class=\"tv-bracket__toggle\"\n :class=\"{ 'tv-bracket__toggle--collapsed': collapsed }\"\n :title=\"collapsed ? expandHint : collapseHint\"\n :aria-label=\"collapsed ? expandHint : collapseHint\"\n :aria-expanded=\"!collapsed\"\n data-tv-no-pan\n @click.stop=\"emit('toggle')\"\n >\n {{ collapsed ? '+' : '−' }}\n </button>\n\n <!-- Строки лежат в своей обёртке: она держит скругление и контур, поэтому\n матч читается как один блок, а не как список из четырёх команд.\n Подсказка и кнопка остаются снаружи — их обрезать нельзя. -->\n <div class=\"tv-bracket__rows\">\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 </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 * Меняются только два числа: промежуток между раундами растягивается до\n * ширины экрана, а матчи внутри раунда встают ближе друг к другу. Настройки\n * с этими числами компонент отдаёт в слот — их и передают в `TreeView`.\n *\n * Что рисовать, решает `buildBracketRounds` из core, поэтому версия\n * для Vue 2 выглядит точно так же.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_OPTIONS,\n MOBILE_MEDIA_QUERY,\n buildBracketRounds,\n findScrollParent,\n hideRoundsBefore,\n horizontalBox,\n levelGapForWidth,\n nodesInColumn,\n offsetWithin,\n resolveOptions,\n roundAtScroll,\n roundOfDepth,\n roundScrollLeft,\n roundSiblingGap,\n SWIPE_ELBOW_OFFSET,\n smoothScrollLeft,\n watchChildren,\n watchMedia,\n watchWidth,\n type BracketRound,\n type BracketRoundColumn,\n type TreeNode,\n type TreeViewOptions,\n} from '@bigplay/tree-view-core'\n\nconst props = withDefaults(\n defineProps<{\n /** Раунды по порядку — от первого к финалу. */\n rounds: BracketRound[] | null | undefined\n /** Настройки сетки — те же, что уходят в `TreeView`. */\n options?: Partial<TreeViewOptions>\n /** Показывать полосу. Содержимое слота рисуется в любом случае. */\n isShow?: boolean\n /**\n * Базовое расстояние между матчами одного раунда в свайпере.\n * Раунд с несколькими матчами получает кратно больше — до четырёх раз.\n */\n swipeSiblingGap?: number\n /** Прятать пройденные раунды и линии, которые из них выходят. */\n hidePassed?: boolean\n /** Включить свайпер принудительно. Не передан — решает ширина экрана. */\n swipe?: boolean\n /** Медиавыражение «узкий экран», при котором включается свайпер. */\n mobileQuery?: string\n }>(),\n {\n options: undefined,\n isShow: true,\n swipeSiblingGap: 12,\n hidePassed: true,\n swipe: undefined,\n mobileQuery: MOBILE_MEDIA_QUERY,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'select', round: BracketRoundColumn): void\n (e: 'round', index: number): void\n}>()\n\nconst rootElement = ref<HTMLElement | null>(null)\nconst contentElement = ref<HTMLElement | null>(null)\n\n/** Узкий ли экран. До монтирования — нет: на сервере ширину знать неоткуда. */\nconst isNarrow = ref(false)\n/** Сколько места отведено полосе: по нему считается ширина раунда в свайпере. */\nconst available = ref(0)\n/** Раунд, на котором стоит прокрутка. Меняется прямо во время свайпа. */\nconst current = ref(0)\n\n/** Полосы нет — свайпать нечего: сетку в это время таскают и масштабируют. */\nconst isSwipe = computed(() => props.isShow && (props.swipe ?? isNarrow.value))\n\nconst base = computed(() => resolveOptions(props.options))\n\n/**\n * Карточки матчей одной ширины, поэтому у сетки с раундами `nodeWidth` —\n * число. Если это всё-таки функция, считать колонки не по чему: берём значение\n * по умолчанию, чтобы полоса не развалилась.\n */\nconst nodeWidth = computed(() =>\n typeof base.value.nodeWidth === 'number'\n ? base.value.nodeWidth\n : (DEFAULT_OPTIONS.nodeWidth as number),\n)\n\nconst levelGap = computed(() =>\n isSwipe.value ? levelGapForWidth(available.value, nodeWidth.value) : base.value.levelGap,\n)\n\nconst model = computed(() =>\n buildBracketRounds(props.rounds, { nodeWidth: nodeWidth.value, levelGap: levelGap.value }),\n)\n\n/** Дерево турнирной сетки растёт справа налево: первый раунд — самый глубокий. */\nconst mirrored = computed(\n () => base.value.direction === 'right-to-left' || base.value.direction === 'bottom-to-top',\n)\n\n/**\n * Настройки для сетки в свайпере.\n *\n * Раунды растягиваются на ширину экрана, а уровни укладываются каждый сам по\n * себе: в дереве место второго раунда выводится из первого, поэтому отдельно\n * его отступ не задать.\n *\n * Внутри пары матчи стоят тесно, а между парами промежуток крупнее — иначе\n * весь раунд читается как один список и непонятно, кто с кем сходится дальше.\n * Линии рисуются скобкой: пара сходится в одну вертикаль сразу за карточками,\n * и уже от её середины идёт линия к следующему матчу. В раскладке `'stack'`\n * родитель стоит не по центру детей, поэтому обычная «ступенька» выходила бы\n * из верхнего матча пары, а не из середины.\n */\nconst gridOptions = computed<Partial<TreeViewOptions>>(() =>\n isSwipe.value\n ? {\n ...props.options,\n levelGap: levelGap.value,\n levelLayout: 'stack',\n siblingGap: props.swipeSiblingGap,\n groupGap: (_node: TreeNode<unknown>, count: number) =>\n roundSiblingGap(count, props.swipeSiblingGap),\n linkStyle: 'bracket',\n elbowOffset: props.options?.elbowOffset ?? SWIPE_ELBOW_OFFSET,\n }\n : { ...props.options },\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/** Прячет всё, что осталось позади: карточки пройденных раундов и их линии. */\nfunction applyHidden(): void {\n const content = contentElement.value\n if (!content) return\n\n const hideBefore = isSwipe.value && props.hidePassed ? current.value : 0\n const count = model.value.columns.length\n\n hideRoundsBefore(content, hideBefore, (depth) => roundOfDepth(depth, count, mirrored.value))\n}\n\nfunction onScroll(): void {\n const element = rootElement.value\n if (!element || !isSwipe.value) return\n\n const width = model.value.columns[0]?.width ?? 0\n const next = roundAtScroll(element.scrollLeft, width, model.value.columns.length)\n if (next === current.value) return\n\n current.value = next\n emit('round', next)\n}\n\nwatch([current, isSwipe, model], () => applyHidden())\n\nconst stop: Array<() => void> = []\n\nonMounted(() => {\n stop.push(\n watchMedia(props.mobileQuery, (matches) => {\n isNarrow.value = matches\n }),\n watchWidth(rootElement.value?.parentElement, (width) => {\n available.value = width\n }),\n // Сетка перерисовывает карточки — скрытое расставляем заново.\n watchChildren(contentElement.value, applyHidden),\n )\n\n applyHidden()\n})\n\nonBeforeUnmount(() => stop.forEach((off) => off()))\n\n/**\n * Подводит сетку к матчам раунда — только по горизонтали.\n *\n * Меряем по самой карточке: так раунд встаёт по центру экрана точно, а у финала\n * прокрутка упирается в правый край и он виден целиком. Карточек не нашли\n * (виртуализация) — считаем по колонке, она той же ширины и на том же месте.\n *\n * Вертикаль не трогаем: страница остаётся там, где была.\n */\nfunction scrollToRound(column: BracketRoundColumn): void {\n const element = rootElement.value\n if (!element) return\n\n // В свайпере прокручивается сама полоса, иначе — блок с overflow-x вокруг неё.\n const container = isSwipe.value ? element : findScrollParent(element)\n if (!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\nfunction onSelect(column: BracketRoundColumn): void {\n emit('select', column)\n scrollToRound(column)\n}\n\ndefineExpose({ scrollToRound })\n</script>\n\n<template>\n <div\n ref=\"rootElement\"\n class=\"tv-rounds\"\n :class=\"{ 'tv-rounds--swipe': isSwipe }\"\n @scroll=\"onScroll\"\n >\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 Сюда кладут саму сетку. `options` — настройки, подогнанные под экран:\n в свайпере раунд занимает всю ширину, а матчи внутри стоят ближе.\n -->\n <slot :options=\"gridOptions\" :is-swipe=\"isSwipe\" :round=\"current\" />\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_5","_hoisted_6","_hoisted_7","contentElement","isNarrow","available","current","isSwipe","base","resolveOptions","nodeWidth","DEFAULT_OPTIONS","levelGap","levelGapForWidth","model","buildBracketRounds","mirrored","gridOptions","_node","count","roundSiblingGap","SWIPE_ELBOW_OFFSET","barStyle","contentStyle","applyHidden","hideBefore","hideRoundsBefore","depth","roundOfDepth","onScroll","width","roundAtScroll","stop","watchMedia","matches","watchWidth","watchChildren","off","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.4.0",
3
+ "version": "0.5.0",
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.4.0"
38
+ "@bigplay/tree-view-core": "^0.5.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "vue": "^3.3.0"