@bigplay/tree-view 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/TreeNodeCard.vue.d.ts +11 -0
- package/dist/TreeView.vue.d.ts +83 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +210 -0
- package/dist/index.js.map +1 -0
- package/dist/usePanZoom.d.ts +29 -0
- package/dist/useTreeLayout.d.ts +23 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @bigplay/tree-view
|
|
2
|
+
|
|
3
|
+
Дерево и турнирные сетки для Vue 3. Без зависимостей (d3 не нужен).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i @bigplay/tree-view
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<script setup>
|
|
11
|
+
import { TreeView } from '@bigplay/tree-view'
|
|
12
|
+
|
|
13
|
+
const data = { id: 'final', name: 'Финал', children: [{ id: 'sf-1', name: 'Полуфинал' }] }
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div style="height: 600px">
|
|
18
|
+
<TreeView :data="data" :options="{ direction: 'right-to-left' }" collapsible />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Свой вид узла — через слот:
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<TreeView :data="data">
|
|
27
|
+
<template #node="{ data, collapsed, hasChildren, toggle }">
|
|
28
|
+
<div class="my-card" @click="toggle">{{ data.name }}</div>
|
|
29
|
+
</template>
|
|
30
|
+
</TreeView>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Стили подключать не нужно — они внутри пакета и вставляются сами.
|
|
34
|
+
|
|
35
|
+
Полная документация — в [корневом README](https://github.com/dadashi44/tree-view#readme):
|
|
36
|
+
пропы, события, методы, форматы данных, CSS-переменные.
|
|
37
|
+
|
|
38
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
label: string;
|
|
3
|
+
hasChildren?: boolean;
|
|
4
|
+
collapsed?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
7
|
+
toggle: () => any;
|
|
8
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
+
onToggle?: (() => any) | undefined;
|
|
10
|
+
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Accessors, LayoutNode, ScaleLimits, Transform, TreeViewOptions } from '@bigplay/tree-view-core';
|
|
2
|
+
declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
3
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
4
|
+
readonly onToggle?: ((node: LayoutNode<T>, collapsed: boolean) => any) | undefined;
|
|
5
|
+
readonly "onNode-click"?: ((node: LayoutNode<T>, event: MouseEvent) => any) | undefined;
|
|
6
|
+
readonly onTransform?: ((transform: Transform) => any) | undefined;
|
|
7
|
+
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onToggle" | "onNode-click" | "onTransform"> & {
|
|
8
|
+
/** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */
|
|
9
|
+
data: T | T[] | null | undefined;
|
|
10
|
+
/** Размеры и направление. Можно передать только часть полей. */
|
|
11
|
+
options?: Partial<TreeViewOptions>;
|
|
12
|
+
/** Как достать id узла. По умолчанию — поле `id`. */
|
|
13
|
+
getId?: Accessors<T>["getId"];
|
|
14
|
+
/** Как достать детей. По умолчанию — поле `children`. */
|
|
15
|
+
getChildren?: Accessors<T>["getChildren"];
|
|
16
|
+
/** Как достать id родителя. Передан — значит данные плоские. */
|
|
17
|
+
getParentId?: Accessors<T>["getParentId"];
|
|
18
|
+
/** Клик по узлу сворачивает/разворачивает ветку. */
|
|
19
|
+
collapsible?: boolean;
|
|
20
|
+
/** Разрешить перетаскивание холста. */
|
|
21
|
+
pannable?: boolean;
|
|
22
|
+
/** Разрешить зум колесом и «щипком». */
|
|
23
|
+
zoomable?: boolean;
|
|
24
|
+
/** Вписать дерево в контейнер при первом отображении. */
|
|
25
|
+
fitOnMount?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Как компонент занимает место:
|
|
28
|
+
* - `'fill'` (по умолчанию) — растягивается на родителя, лишнее обрезается,
|
|
29
|
+
* дерево двигают мышью;
|
|
30
|
+
* - `'content'` — контейнер принимает размер дерева и ничего не обрезает.
|
|
31
|
+
* Нужен, когда сетка стоит внутри страницы со своим скроллом.
|
|
32
|
+
*/
|
|
33
|
+
size?: "fill" | "content";
|
|
34
|
+
/** Пределы масштаба. */
|
|
35
|
+
scaleLimits?: ScaleLimits;
|
|
36
|
+
/** Дополнительный класс на обёртке узла — строкой или функцией. */
|
|
37
|
+
nodeClass?: string | ((node: LayoutNode<T>) => string);
|
|
38
|
+
} & Partial<{}>> & import('vue').PublicProps;
|
|
39
|
+
expose(exposed: import('vue').ShallowUnwrapRef<{
|
|
40
|
+
fit: (padding?: number) => void;
|
|
41
|
+
zoomIn: () => void;
|
|
42
|
+
zoomOut: () => void;
|
|
43
|
+
resetTransform: () => void;
|
|
44
|
+
setTransform: (next: Transform) => void;
|
|
45
|
+
expandAll: () => void;
|
|
46
|
+
toggle: (id: string) => void;
|
|
47
|
+
layout: import('vue').ComputedRef<import('@bigplay/tree-view-core').Layout<T>>;
|
|
48
|
+
transform: import('vue').Ref<{
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
scale: number;
|
|
52
|
+
}, Transform | {
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
scale: number;
|
|
56
|
+
}>;
|
|
57
|
+
}>): void;
|
|
58
|
+
attrs: any;
|
|
59
|
+
slots: {
|
|
60
|
+
link?(_: {
|
|
61
|
+
link: import('@bigplay/tree-view-core').LayoutLink<T>;
|
|
62
|
+
}): any;
|
|
63
|
+
node?(_: {
|
|
64
|
+
node: LayoutNode<T>;
|
|
65
|
+
data: T;
|
|
66
|
+
depth: number;
|
|
67
|
+
collapsed: boolean;
|
|
68
|
+
hasChildren: boolean;
|
|
69
|
+
toggle: () => void;
|
|
70
|
+
}): any;
|
|
71
|
+
};
|
|
72
|
+
emit: {
|
|
73
|
+
(e: "node-click", node: LayoutNode<T>, event: MouseEvent): void;
|
|
74
|
+
(e: "toggle", node: LayoutNode<T>, collapsed: boolean): void;
|
|
75
|
+
(e: "transform", transform: Transform): void;
|
|
76
|
+
};
|
|
77
|
+
}>) => import('vue').VNode & {
|
|
78
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
79
|
+
};
|
|
80
|
+
export default _default;
|
|
81
|
+
type __VLS_PrettifyLocal<T> = {
|
|
82
|
+
[K in keyof T]: T[K];
|
|
83
|
+
} & {};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),d=require("@bigplay/tree-view-core"),I={class:"tree-view-card"},$={class:"tree-view-card__label"},O=["aria-label"],m=e.defineComponent({__name:"TreeNodeCard",props:{label:{},hasChildren:{type:Boolean},collapsed:{type:Boolean}},emits:["toggle"],setup(n,{emit:r}){const a=r;return(o,s)=>(e.openBlock(),e.createElementBlock("div",I,[e.createElementVNode("span",$,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:s[0]||(s[0]=e.withModifiers(u=>a("toggle"),["stop"]))},e.toDisplayString(n.collapsed?"+":"−"),9,O)):e.createCommentVNode("",!0)]))}});function b(n){const r=e.ref({...d.IDENTITY_TRANSFORM}),a=new d.PanZoomController({pannable:n.pannable,zoomable:n.zoomable,limits:n.limits,onChange:o=>{r.value=o}});return e.watch(n.element,(o,s,u)=>{o&&(a.attach(o),u(()=>a.detach()))},{immediate:!0}),e.onBeforeUnmount(()=>a.detach()),{transform:r,fit:(o,s)=>a.fit(o,s),zoomIn:()=>a.zoomIn(),zoomOut:()=>a.zoomOut(),reset:()=>a.reset(),setTransform:o=>a.set(o)}}function _(n){const r=e.ref(new Set),a=e.computed(()=>d.toTree(n.data(),n.accessors())),o=e.computed(()=>d.applyCollapsed(a.value,r.value)),s=e.computed(()=>d.layoutTree(o.value,n.options()));function u(v){return r.value.has(v)}function i(v){r.value=d.toggleCollapsed(r.value,v)}function h(){r.value=new Set}return{layout:s,isCollapsed:u,toggle:i,expandAll:h}}const N=["width","height"],P=["d"],L=["onClick"],y=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}},emits:["node-click","toggle","transform"],setup(n,{expose:r,emit:a}){const o=n,s=a,u=e.ref(null),{layout:i,isCollapsed:h,toggle:v,expandAll:C}=_({data:()=>o.data,options:()=>o.options,accessors:()=>({getId:o.getId,getChildren:o.getChildren,getParentId:o.getParentId})}),c=b({element:u,pannable:()=>o.pannable,zoomable:()=>o.zoomable,limits:()=>o.scaleLimits??d.DEFAULT_SCALE_LIMITS}),x=e.computed(()=>({width:`${i.value.width}px`,height:`${i.value.height}px`,transform:d.toCssTransform(c.transform.value)})),T=e.computed(()=>{if(o.size!=="content")return;const{scale:t}=c.transform.value;return{width:`${i.value.width*t}px`,height:`${i.value.height*t}px`,overflow:"visible"}});function z(t){return{left:`${t.x}px`,top:`${t.y}px`,width:`${t.width}px`,height:`${t.height}px`}}function B(t){return typeof o.nodeClass=="function"?o.nodeClass(t):o.nodeClass}function S(t){const p=t.data;return p?.name??p?.title??p?.label??t.id}function f(t){v(t.id),s("toggle",t,h(t.id))}function E(t,p){s("node-click",t,p),o.collapsible&&t.hasChildren&&f(t)}function g(t){c.fit({width:i.value.width,height:i.value.height},t)}return e.onMounted(()=>{o.fitOnMount&&e.nextTick(()=>g())}),e.watch(()=>c.transform.value,t=>s("transform",t)),r({fit:g,zoomIn:c.zoomIn,zoomOut:c.zoomOut,resetTransform:c.reset,setTransform:c.setTransform,expandAll:C,toggle:v,layout:i,transform:c.transform}),(t,p)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootElement",ref:u,class:e.normalizeClass(["tree-view",{"tree-view--pannable":n.pannable,"tree-view--content":n.size==="content"}]),style:e.normalizeStyle(T.value)},[e.createElementVNode("div",{class:"tree-view__canvas",style:e.normalizeStyle(x.value)},[(e.openBlock(),e.createElementBlock("svg",{class:"tree-view__links",width:e.unref(i).width,height:e.unref(i).height},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(i).links,l=>e.renderSlot(t.$slots,"link",{link:l},()=>[e.createElementVNode("path",{class:"tree-view__link",d:l.path},null,8,P)],void 0,l.id)),128))],8,N)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(i).nodes,l=>(e.openBlock(),e.createElementBlock("div",{key:l.id,class:e.normalizeClass(["tree-view__node",B(l)]),style:e.normalizeStyle(z(l)),onClick:w=>E(l,w)},[e.renderSlot(t.$slots,"node",{node:l,data:l.data,depth:l.depth,collapsed:l.collapsed,hasChildren:l.hasChildren,toggle:()=>f(l)},()=>[e.createVNode(m,{label:S(l),"has-children":l.hasChildren,collapsed:l.collapsed,onToggle:w=>f(l)},null,8,["label","has-children","collapsed","onToggle"])])],14,L))),128))],4)],6))}}),V=".tree-view{--tv-link-color: #cbd5e1;--tv-link-width: 2px;--tv-node-bg: #ffffff;--tv-node-color: #0f172a;--tv-node-border: 1px solid #e2e8f0;--tv-node-radius: 10px;--tv-node-shadow: 0 1px 2px rgb(15 23 42 / 8%);--tv-node-padding: 8px 12px;--tv-accent: #2563eb;position:relative;width:100%;height:100%;overflow:hidden;touch-action:none}.tree-view--pannable{cursor:grab}.tree-view--content{width:auto;height:auto;overflow:visible}.tree-view__canvas{position:absolute;top:0;left:0;transform-origin:0 0}.tree-view__links{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}.tree-view__link{fill:none;stroke:var(--tv-link-color);stroke-width:var(--tv-link-width)}.tree-view__node{position:absolute;box-sizing:border-box;display:flex;align-items:stretch;justify-content:stretch}.tree-view-card{box-sizing:border-box;display:flex;gap:8px;align-items:center;justify-content:space-between;width:100%;height:100%;padding:var(--tv-node-padding);overflow:hidden;color:var(--tv-node-color);background:var(--tv-node-bg);border:var(--tv-node-border);border-radius:var(--tv-node-radius);box-shadow:var(--tv-node-shadow)}.tree-view-card__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-view-card__toggle{flex:none;width:20px;height:20px;padding:0;font:inherit;line-height:1;color:var(--tv-accent);cursor:pointer;background:none;border:none}";d.injectStyles(V);const k={install(n){n.component("TreeView",y),n.component("TreeNodeCard",m)}};Object.defineProperty(exports,"DEFAULT_OPTIONS",{enumerable:!0,get:()=>d.DEFAULT_OPTIONS});exports.TreeNodeCard=m;exports.TreeView=y;exports.TreeViewPlugin=k;exports.default=k;exports.usePanZoom=b;exports.useTreeLayout=_;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/TreeNodeCard.vue","../src/usePanZoom.ts","../src/useTreeLayout.ts","../src/TreeView.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> | 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","<script setup lang=\"ts\" generic=\"T\">\n/**\n * Компонент-обёртка. Вся математика — в пакете core, здесь только\n * реактивность, разметка и события.\n */\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_SCALE_LIMITS,\n toCssTransform,\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'\n\nconst props = withDefaults(\n defineProps<{\n /** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */\n data: T | T[] | null | undefined\n /** Размеры и направление. Можно передать только часть полей. */\n options?: Partial<TreeViewOptions>\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 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 },\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)\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 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) => emit('transform', transform),\n)\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 class=\"tree-view__canvas\" :style=\"canvasStyle\">\n <svg class=\"tree-view__links\" :width=\"layout.width\" :height=\"layout.height\">\n <template v-for=\"link in layout.links\" :key=\"link.id\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </template>\n </svg>\n\n <div\n v-for=\"node in layout.nodes\"\n :key=\"node.id\"\n class=\"tree-view__node\"\n :class=\"nodeClassOf(node)\"\n :style=\"nodeStyle(node)\"\n @click=\"onNodeClick(node, $event)\"\n >\n <!--\n Главная точка кастомизации: сюда подставляют свою карточку.\n Доступно всё, что может понадобиться для отрисовки и управления.\n -->\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"node.data\"\n :depth=\"node.depth\"\n :collapsed=\"node.collapsed\"\n :has-children=\"node.hasChildren\"\n :toggle=\"() => toggleNode(node)\"\n >\n <TreeNodeCard\n :label=\"labelOf(node)\"\n :has-children=\"node.hasChildren\"\n :collapsed=\"node.collapsed\"\n @toggle=\"toggleNode(node)\"\n />\n </slot>\n </div>\n </div>\n </div>\n</template>\n","import type { App, Plugin } from 'vue'\nimport TreeView from './TreeView.vue'\nimport TreeNodeCard from './TreeNodeCard.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 }\nexport { useTreeLayout } from './useTreeLayout'\nexport { usePanZoom } from './usePanZoom'\n\n// Типы из core переэкспортируем, чтобы их не приходилось ставить отдельно.\nexport type {\n Accessors,\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 { DEFAULT_OPTIONS } 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 },\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","props","rootElement","panZoom","DEFAULT_SCALE_LIMITS","canvasStyle","toCssTransform","rootStyle","scale","nodeStyle","node","nodeClassOf","labelOf","data","toggleNode","onNodeClick","event","fit","onMounted","nextTick","__expose","_normalizeClass","_unref","_Fragment","_renderList","link","_renderSlot","_ctx","_normalizeStyle","$event","_createVNode","TreeNodeCard","injectStyles","styles","TreeViewPlugin","app","TreeView"],"mappings":"yYAYA,MAAMA,EAAOC,gBAIXC,YAAA,EAAAC,qBAaM,MAbNC,EAaM,CAZJC,EAAAA,mBAAsD,OAAtDC,EAAsDC,EAAAA,gBAAfC,EAAA,KAAK,EAAA,CAAA,EAGpCA,EAAA,2BADRL,EAAAA,mBASS,SAAA,OAPP,KAAK,SACL,MAAM,yBACN,iBAAA,GACC,aAAYK,EAAA,UAAS,aAAA,WACrB,uCAAYR,EAAI,QAAA,EAAA,CAAA,MAAA,CAAA,EAAA,oBAEdQ,EAAA,UAAS,IAAA,GAAA,EAAA,EAAAC,CAAA,qCCNX,SAASC,EAAWC,EAA0B,CACnD,MAAMC,EAAYC,EAAAA,IAAe,CAAE,GAAGC,EAAAA,mBAAoB,EAEpDC,EAAa,IAAIC,oBAAkB,CACvC,SAAUL,EAAO,SACjB,SAAUA,EAAO,SACjB,OAAQA,EAAO,OACf,SAAWM,GAAS,CAClBL,EAAU,MAAQK,CACpB,CAAA,CACD,EAEDC,OAAAA,EAAAA,MACEP,EAAO,QACP,CAACQ,EAASC,EAAWC,IAAc,CAC5BF,IAELJ,EAAW,OAAOI,CAAO,EACzBE,EAAU,IAAMN,EAAW,QAAQ,EACrC,EACA,CAAE,UAAW,EAAA,CAAK,EAGpBO,kBAAgB,IAAMP,EAAW,QAAQ,EAElC,CACL,UAAAH,EACA,IAAK,CAACW,EAAeC,IAAqBT,EAAW,IAAIQ,EAASC,CAAO,EACzE,OAAQ,IAAMT,EAAW,OAAA,EACzB,QAAS,IAAMA,EAAW,QAAA,EAC1B,MAAO,IAAMA,EAAW,MAAA,EACxB,aAAeE,GAAoBF,EAAW,IAAIE,CAAI,CAAA,CAE1D,CCrBO,SAASQ,EAAiBd,EAAwD,CACvF,MAAMe,EAAeb,EAAAA,IAAI,IAAI,GAAa,EAEpCc,EAAOC,WAAS,IAAMC,SAAOlB,EAAO,OAAQA,EAAO,UAAA,CAAW,CAAC,EAC/DmB,EAAcF,EAAAA,SAAS,IAAMG,EAAAA,eAAeJ,EAAK,MAAOD,EAAa,KAAK,CAAC,EAC3EM,EAASJ,EAAAA,SAAS,IAAMK,EAAAA,WAAWH,EAAY,MAAOnB,EAAO,QAAA,CAAS,CAAC,EAE7E,SAASuB,EAAYC,EAAqB,CACxC,OAAOT,EAAa,MAAM,IAAIS,CAAE,CAClC,CAEA,SAASC,EAAOD,EAAkB,CAEhCT,EAAa,MAAQW,EAAAA,gBAAgBX,EAAa,MAAOS,CAAE,CAC7D,CAEA,SAASG,GAAkB,CACzBZ,EAAa,UAAY,GAC3B,CAEA,MAAO,CAAE,OAAAM,EAAQ,YAAAE,EAAa,OAAAE,EAAQ,UAAAE,CAAA,CACxC,wjBCnCA,MAAMC,EAAQ/B,EAgDRR,EAAOC,EAMPuC,EAAc3B,EAAAA,IAAwB,IAAI,EAE1C,CAAE,OAAAmB,EAAQ,YAAAE,EAAa,OAAAE,EAAQ,UAAAE,CAAA,EAAcb,EAAiB,CAClE,KAAM,IAAMc,EAAM,KAClB,QAAS,IAAMA,EAAM,QACrB,UAAW,KAAO,CAChB,MAAOA,EAAM,MACb,YAAaA,EAAM,YACnB,YAAaA,EAAM,WAAA,EACrB,CACD,EAEKE,EAAU/B,EAAW,CACzB,QAAS8B,EACT,SAAU,IAAMD,EAAM,SACtB,SAAU,IAAMA,EAAM,SACtB,OAAQ,IAAMA,EAAM,aAAeG,EAAAA,oBAAA,CACpC,EAEKC,EAAcf,EAAAA,SAAS,KAAO,CAClC,MAAO,GAAGI,EAAO,MAAM,KAAK,KAC5B,OAAQ,GAAGA,EAAO,MAAM,MAAM,KAC9B,UAAWY,EAAAA,eAAeH,EAAQ,UAAU,KAAK,CAAA,EACjD,EAEII,EAAYjB,EAAAA,SAAS,IAAM,CAC/B,GAAIW,EAAM,OAAS,UAAW,OAE9B,KAAM,CAAE,MAAAO,CAAA,EAAUL,EAAQ,UAAU,MACpC,MAAO,CACL,MAAO,GAAGT,EAAO,MAAM,MAAQc,CAAK,KACpC,OAAQ,GAAGd,EAAO,MAAM,OAASc,CAAK,KACtC,SAAU,SAAA,CAEd,CAAC,EAED,SAASC,EAAUC,EAAqB,CACtC,MAAO,CACL,KAAM,GAAGA,EAAK,CAAC,KACf,IAAK,GAAGA,EAAK,CAAC,KACd,MAAO,GAAGA,EAAK,KAAK,KACpB,OAAQ,GAAGA,EAAK,MAAM,IAAA,CAE1B,CAEA,SAASC,EAAYD,EAAyC,CAC5D,OAAO,OAAOT,EAAM,WAAc,WAAaA,EAAM,UAAUS,CAAI,EAAIT,EAAM,SAC/E,CAGA,SAASW,EAAQF,EAA6B,CAC5C,MAAMG,EAAOH,EAAK,KAClB,OAAOG,GAAM,MAAQA,GAAM,OAASA,GAAM,OAASH,EAAK,EAC1D,CAEA,SAASI,EAAWJ,EAA2B,CAC7CZ,EAAOY,EAAK,EAAE,EACdhD,EAAK,SAAUgD,EAAMd,EAAYc,EAAK,EAAE,CAAC,CAC3C,CAEA,SAASK,EAAYL,EAAqBM,EAAyB,CACjEtD,EAAK,aAAcgD,EAAMM,CAAK,EAC1Bf,EAAM,aAAeS,EAAK,eAAwBA,CAAI,CAC5D,CAGA,SAASO,EAAI/B,EAAwB,CACnCiB,EAAQ,IAAI,CAAE,MAAOT,EAAO,MAAM,MAAO,OAAQA,EAAO,MAAM,MAAA,EAAUR,CAAO,CACjF,CAEAgC,OAAAA,EAAAA,UAAU,IAAM,CACVjB,EAAM,YAAiBkB,EAAAA,SAAS,IAAMF,GAAK,CACjD,CAAC,EAEDrC,EAAAA,MACE,IAAMuB,EAAQ,UAAU,MACvB7B,GAAcZ,EAAK,YAAaY,CAAS,CAAA,EAG5C8C,EAAa,CACX,IAAAH,EACA,OAAQd,EAAQ,OAChB,QAASA,EAAQ,QACjB,eAAgBA,EAAQ,MACxB,aAAcA,EAAQ,aACtB,UAAAH,EACA,OAAAF,EACA,OAAAJ,EACA,UAAWS,EAAQ,SAAA,CACpB,wBAICtC,EAAAA,mBA6CM,MAAA,SA5CA,cAAJ,IAAIqC,EACJ,MAAKmB,EAAAA,eAAA,CAAC,YAAW,CAAA,sBACgBnD,EAAA,8BAAgCA,EAAA,OAAI,SAAA,CAAA,CAAA,EACpE,uBAAOqC,EAAA,KAAS,CAAA,GAEjBxC,EAAAA,mBAsCM,MAAA,CAtCD,MAAM,oBAAqB,uBAAOsC,EAAA,KAAW,CAAA,kBAChDxC,EAAAA,mBAMM,MAAA,CAND,MAAM,mBAAoB,MAAOyD,EAAAA,MAAA5B,CAAA,EAAO,MAAQ,OAAQ4B,EAAAA,MAAA5B,CAAA,EAAO,MAAA,IAClE9B,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBAIW0D,EAAAA,SAAA,KAAAC,aAJcF,EAAAA,MAAA5B,CAAA,EAAO,MAAf+B,GACfC,EAAAA,WAEOC,EAAA,OAAA,OAAA,CAFY,KAAAF,CAAA,EAAnB,IAEO,CADL1D,EAAAA,mBAA+C,OAAA,CAAzC,MAAM,kBAAmB,EAAG0D,EAAK,IAAA,aAFE,OAAAA,EAAK,EAAE,iBAOtD7D,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBA4BM0D,EAAAA,SAAA,KAAAC,aA3BWF,EAAAA,MAAA5B,CAAA,EAAO,MAAfgB,kBADT7C,EAAAA,mBA4BM,MAAA,CA1BH,IAAK6C,EAAK,GACX,MAAKW,EAAAA,eAAA,CAAC,kBACEV,EAAYD,CAAI,CAAA,CAAA,EACvB,MAAKkB,EAAAA,eAAEnB,EAAUC,CAAI,CAAA,EACrB,QAAKmB,GAAEd,EAAYL,EAAMmB,CAAM,CAAA,GAMhCH,aAeOC,EAAA,OAAA,OAAA,CAbJ,KAAAjB,EACA,KAAMA,EAAK,KACX,MAAOA,EAAK,MACZ,UAAWA,EAAK,UAChB,YAAcA,EAAK,YACnB,OAAM,IAAQI,EAAWJ,CAAI,CAAA,EAPhC,IAeO,CANLoB,EAAAA,YAKEC,EAAA,CAJC,MAAOnB,EAAQF,CAAI,EACnB,eAAcA,EAAK,YACnB,UAAWA,EAAK,UAChB,SAAMmB,GAAEf,EAAWJ,CAAI,CAAA,46CCtMpCsB,EAAAA,aAAaC,CAAM,EAsBZ,MAAMC,EAAyB,CACpC,QAAQC,EAAU,CAChBA,EAAI,UAAU,WAAYC,CAAQ,EAClCD,EAAI,UAAU,eAAgBJ,CAAY,CAC5C,CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Plugin } from 'vue';
|
|
2
|
+
import { default as TreeView } from './TreeView.vue';
|
|
3
|
+
import { default as TreeNodeCard } from './TreeNodeCard.vue';
|
|
4
|
+
export { TreeView, TreeNodeCard };
|
|
5
|
+
export { useTreeLayout } from './useTreeLayout';
|
|
6
|
+
export { usePanZoom } from './usePanZoom';
|
|
7
|
+
export type { Accessors, Direction, Layout, LayoutLink, LayoutNode, LinkStyle, ScaleLimits, Transform, TreeNode, TreeViewOptions, } from '@bigplay/tree-view-core';
|
|
8
|
+
export { DEFAULT_OPTIONS } from '@bigplay/tree-view-core';
|
|
9
|
+
/** Плагин: `app.use(TreeViewPlugin)` регистрирует компоненты глобально. */
|
|
10
|
+
export declare const TreeViewPlugin: Plugin;
|
|
11
|
+
export default TreeViewPlugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { defineComponent as $, openBlock as c, createElementBlock as u, createElementVNode as b, toDisplayString as C, withModifiers as A, createCommentVNode as M, ref as _, watch as S, onBeforeUnmount as D, computed as p, onMounted as j, nextTick as U, normalizeStyle as w, normalizeClass as T, unref as f, Fragment as k, renderList as z, renderSlot as I, createVNode as Z } from "vue";
|
|
2
|
+
import { IDENTITY_TRANSFORM as R, PanZoomController as Y, toTree as q, applyCollapsed as G, layoutTree as H, toggleCollapsed as J, toCssTransform as K, DEFAULT_SCALE_LIMITS as Q, injectStyles as W } from "@bigplay/tree-view-core";
|
|
3
|
+
import { DEFAULT_OPTIONS as pe } from "@bigplay/tree-view-core";
|
|
4
|
+
const X = { class: "tree-view-card" }, ee = { class: "tree-view-card__label" }, te = ["aria-label"], N = /* @__PURE__ */ $({
|
|
5
|
+
__name: "TreeNodeCard",
|
|
6
|
+
props: {
|
|
7
|
+
label: {},
|
|
8
|
+
hasChildren: { type: Boolean },
|
|
9
|
+
collapsed: { type: Boolean }
|
|
10
|
+
},
|
|
11
|
+
emits: ["toggle"],
|
|
12
|
+
setup(o, { emit: a }) {
|
|
13
|
+
const l = a;
|
|
14
|
+
return (t, r) => (c(), u("div", X, [
|
|
15
|
+
b("span", ee, C(o.label), 1),
|
|
16
|
+
o.hasChildren ? (c(), u("button", {
|
|
17
|
+
key: 0,
|
|
18
|
+
type: "button",
|
|
19
|
+
class: "tree-view-card__toggle",
|
|
20
|
+
"data-tv-no-pan": "",
|
|
21
|
+
"aria-label": o.collapsed ? "Развернуть" : "Свернуть",
|
|
22
|
+
onClick: r[0] || (r[0] = A((d) => l("toggle"), ["stop"]))
|
|
23
|
+
}, C(o.collapsed ? "+" : "−"), 9, te)) : M("", !0)
|
|
24
|
+
]));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
function oe(o) {
|
|
28
|
+
const a = _({ ...R }), l = new Y({
|
|
29
|
+
pannable: o.pannable,
|
|
30
|
+
zoomable: o.zoomable,
|
|
31
|
+
limits: o.limits,
|
|
32
|
+
onChange: (t) => {
|
|
33
|
+
a.value = t;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return S(
|
|
37
|
+
o.element,
|
|
38
|
+
(t, r, d) => {
|
|
39
|
+
t && (l.attach(t), d(() => l.detach()));
|
|
40
|
+
},
|
|
41
|
+
{ immediate: !0 }
|
|
42
|
+
), D(() => l.detach()), {
|
|
43
|
+
transform: a,
|
|
44
|
+
fit: (t, r) => l.fit(t, r),
|
|
45
|
+
zoomIn: () => l.zoomIn(),
|
|
46
|
+
zoomOut: () => l.zoomOut(),
|
|
47
|
+
reset: () => l.reset(),
|
|
48
|
+
setTransform: (t) => l.set(t)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function ne(o) {
|
|
52
|
+
const a = _(/* @__PURE__ */ new Set()), l = p(() => q(o.data(), o.accessors())), t = p(() => G(l.value, a.value)), r = p(() => H(t.value, o.options()));
|
|
53
|
+
function d(v) {
|
|
54
|
+
return a.value.has(v);
|
|
55
|
+
}
|
|
56
|
+
function i(v) {
|
|
57
|
+
a.value = J(a.value, v);
|
|
58
|
+
}
|
|
59
|
+
function g() {
|
|
60
|
+
a.value = /* @__PURE__ */ new Set();
|
|
61
|
+
}
|
|
62
|
+
return { layout: r, isCollapsed: d, toggle: i, expandAll: g };
|
|
63
|
+
}
|
|
64
|
+
const le = ["width", "height"], ie = ["d"], ae = ["onClick"], re = /* @__PURE__ */ $({
|
|
65
|
+
__name: "TreeView",
|
|
66
|
+
props: {
|
|
67
|
+
data: {},
|
|
68
|
+
options: { default: void 0 },
|
|
69
|
+
getId: { type: Function, default: void 0 },
|
|
70
|
+
getChildren: { type: Function, default: void 0 },
|
|
71
|
+
getParentId: { type: Function, default: void 0 },
|
|
72
|
+
collapsible: { type: Boolean, default: !1 },
|
|
73
|
+
pannable: { type: Boolean, default: !0 },
|
|
74
|
+
zoomable: { type: Boolean, default: !0 },
|
|
75
|
+
fitOnMount: { type: Boolean, default: !0 },
|
|
76
|
+
size: { default: "fill" },
|
|
77
|
+
scaleLimits: { default: void 0 },
|
|
78
|
+
nodeClass: { type: [String, Function], default: void 0 }
|
|
79
|
+
},
|
|
80
|
+
emits: ["node-click", "toggle", "transform"],
|
|
81
|
+
setup(o, { expose: a, emit: l }) {
|
|
82
|
+
const t = o, r = l, d = _(null), { layout: i, isCollapsed: g, toggle: v, expandAll: O } = ne({
|
|
83
|
+
data: () => t.data,
|
|
84
|
+
options: () => t.options,
|
|
85
|
+
accessors: () => ({
|
|
86
|
+
getId: t.getId,
|
|
87
|
+
getChildren: t.getChildren,
|
|
88
|
+
getParentId: t.getParentId
|
|
89
|
+
})
|
|
90
|
+
}), s = oe({
|
|
91
|
+
element: d,
|
|
92
|
+
pannable: () => t.pannable,
|
|
93
|
+
zoomable: () => t.zoomable,
|
|
94
|
+
limits: () => t.scaleLimits ?? Q
|
|
95
|
+
}), B = p(() => ({
|
|
96
|
+
width: `${i.value.width}px`,
|
|
97
|
+
height: `${i.value.height}px`,
|
|
98
|
+
transform: K(s.transform.value)
|
|
99
|
+
})), E = p(() => {
|
|
100
|
+
if (t.size !== "content") return;
|
|
101
|
+
const { scale: e } = s.transform.value;
|
|
102
|
+
return {
|
|
103
|
+
width: `${i.value.width * e}px`,
|
|
104
|
+
height: `${i.value.height * e}px`,
|
|
105
|
+
overflow: "visible"
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
function F(e) {
|
|
109
|
+
return {
|
|
110
|
+
left: `${e.x}px`,
|
|
111
|
+
top: `${e.y}px`,
|
|
112
|
+
width: `${e.width}px`,
|
|
113
|
+
height: `${e.height}px`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function L(e) {
|
|
117
|
+
return typeof t.nodeClass == "function" ? t.nodeClass(e) : t.nodeClass;
|
|
118
|
+
}
|
|
119
|
+
function P(e) {
|
|
120
|
+
const h = e.data;
|
|
121
|
+
return h?.name ?? h?.title ?? h?.label ?? e.id;
|
|
122
|
+
}
|
|
123
|
+
function m(e) {
|
|
124
|
+
v(e.id), r("toggle", e, g(e.id));
|
|
125
|
+
}
|
|
126
|
+
function V(e, h) {
|
|
127
|
+
r("node-click", e, h), t.collapsible && e.hasChildren && m(e);
|
|
128
|
+
}
|
|
129
|
+
function y(e) {
|
|
130
|
+
s.fit({ width: i.value.width, height: i.value.height }, e);
|
|
131
|
+
}
|
|
132
|
+
return j(() => {
|
|
133
|
+
t.fitOnMount && U(() => y());
|
|
134
|
+
}), S(
|
|
135
|
+
() => s.transform.value,
|
|
136
|
+
(e) => r("transform", e)
|
|
137
|
+
), a({
|
|
138
|
+
fit: y,
|
|
139
|
+
zoomIn: s.zoomIn,
|
|
140
|
+
zoomOut: s.zoomOut,
|
|
141
|
+
resetTransform: s.reset,
|
|
142
|
+
setTransform: s.setTransform,
|
|
143
|
+
expandAll: O,
|
|
144
|
+
toggle: v,
|
|
145
|
+
layout: i,
|
|
146
|
+
transform: s.transform
|
|
147
|
+
}), (e, h) => (c(), u("div", {
|
|
148
|
+
ref_key: "rootElement",
|
|
149
|
+
ref: d,
|
|
150
|
+
class: T(["tree-view", { "tree-view--pannable": o.pannable, "tree-view--content": o.size === "content" }]),
|
|
151
|
+
style: w(E.value)
|
|
152
|
+
}, [
|
|
153
|
+
b("div", {
|
|
154
|
+
class: "tree-view__canvas",
|
|
155
|
+
style: w(B.value)
|
|
156
|
+
}, [
|
|
157
|
+
(c(), u("svg", {
|
|
158
|
+
class: "tree-view__links",
|
|
159
|
+
width: f(i).width,
|
|
160
|
+
height: f(i).height
|
|
161
|
+
}, [
|
|
162
|
+
(c(!0), u(k, null, z(f(i).links, (n) => I(e.$slots, "link", { link: n }, () => [
|
|
163
|
+
b("path", {
|
|
164
|
+
class: "tree-view__link",
|
|
165
|
+
d: n.path
|
|
166
|
+
}, null, 8, ie)
|
|
167
|
+
], void 0, n.id)), 128))
|
|
168
|
+
], 8, le)),
|
|
169
|
+
(c(!0), u(k, null, z(f(i).nodes, (n) => (c(), u("div", {
|
|
170
|
+
key: n.id,
|
|
171
|
+
class: T(["tree-view__node", L(n)]),
|
|
172
|
+
style: w(F(n)),
|
|
173
|
+
onClick: (x) => V(n, x)
|
|
174
|
+
}, [
|
|
175
|
+
I(e.$slots, "node", {
|
|
176
|
+
node: n,
|
|
177
|
+
data: n.data,
|
|
178
|
+
depth: n.depth,
|
|
179
|
+
collapsed: n.collapsed,
|
|
180
|
+
hasChildren: n.hasChildren,
|
|
181
|
+
toggle: () => m(n)
|
|
182
|
+
}, () => [
|
|
183
|
+
Z(N, {
|
|
184
|
+
label: P(n),
|
|
185
|
+
"has-children": n.hasChildren,
|
|
186
|
+
collapsed: n.collapsed,
|
|
187
|
+
onToggle: (x) => m(n)
|
|
188
|
+
}, null, 8, ["label", "has-children", "collapsed", "onToggle"])
|
|
189
|
+
])
|
|
190
|
+
], 14, ae))), 128))
|
|
191
|
+
], 4)
|
|
192
|
+
], 6));
|
|
193
|
+
}
|
|
194
|
+
}), se = ".tree-view{--tv-link-color: #cbd5e1;--tv-link-width: 2px;--tv-node-bg: #ffffff;--tv-node-color: #0f172a;--tv-node-border: 1px solid #e2e8f0;--tv-node-radius: 10px;--tv-node-shadow: 0 1px 2px rgb(15 23 42 / 8%);--tv-node-padding: 8px 12px;--tv-accent: #2563eb;position:relative;width:100%;height:100%;overflow:hidden;touch-action:none}.tree-view--pannable{cursor:grab}.tree-view--content{width:auto;height:auto;overflow:visible}.tree-view__canvas{position:absolute;top:0;left:0;transform-origin:0 0}.tree-view__links{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}.tree-view__link{fill:none;stroke:var(--tv-link-color);stroke-width:var(--tv-link-width)}.tree-view__node{position:absolute;box-sizing:border-box;display:flex;align-items:stretch;justify-content:stretch}.tree-view-card{box-sizing:border-box;display:flex;gap:8px;align-items:center;justify-content:space-between;width:100%;height:100%;padding:var(--tv-node-padding);overflow:hidden;color:var(--tv-node-color);background:var(--tv-node-bg);border:var(--tv-node-border);border-radius:var(--tv-node-radius);box-shadow:var(--tv-node-shadow)}.tree-view-card__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-view-card__toggle{flex:none;width:20px;height:20px;padding:0;font:inherit;line-height:1;color:var(--tv-accent);cursor:pointer;background:none;border:none}";
|
|
195
|
+
W(se);
|
|
196
|
+
const ue = {
|
|
197
|
+
install(o) {
|
|
198
|
+
o.component("TreeView", re), o.component("TreeNodeCard", N);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
export {
|
|
202
|
+
pe as DEFAULT_OPTIONS,
|
|
203
|
+
N as TreeNodeCard,
|
|
204
|
+
re as TreeView,
|
|
205
|
+
ue as TreeViewPlugin,
|
|
206
|
+
ue as default,
|
|
207
|
+
oe as usePanZoom,
|
|
208
|
+
ne as useTreeLayout
|
|
209
|
+
};
|
|
210
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/TreeNodeCard.vue","../src/usePanZoom.ts","../src/useTreeLayout.ts","../src/TreeView.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> | 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","<script setup lang=\"ts\" generic=\"T\">\n/**\n * Компонент-обёртка. Вся математика — в пакете core, здесь только\n * реактивность, разметка и события.\n */\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\nimport {\n DEFAULT_SCALE_LIMITS,\n toCssTransform,\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'\n\nconst props = withDefaults(\n defineProps<{\n /** Данные: объект, массив объектов или плоский список (вместе с `getParentId`). */\n data: T | T[] | null | undefined\n /** Размеры и направление. Можно передать только часть полей. */\n options?: Partial<TreeViewOptions>\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 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 },\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)\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 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) => emit('transform', transform),\n)\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 class=\"tree-view__canvas\" :style=\"canvasStyle\">\n <svg class=\"tree-view__links\" :width=\"layout.width\" :height=\"layout.height\">\n <template v-for=\"link in layout.links\" :key=\"link.id\">\n <slot name=\"link\" :link=\"link\">\n <path class=\"tree-view__link\" :d=\"link.path\" />\n </slot>\n </template>\n </svg>\n\n <div\n v-for=\"node in layout.nodes\"\n :key=\"node.id\"\n class=\"tree-view__node\"\n :class=\"nodeClassOf(node)\"\n :style=\"nodeStyle(node)\"\n @click=\"onNodeClick(node, $event)\"\n >\n <!--\n Главная точка кастомизации: сюда подставляют свою карточку.\n Доступно всё, что может понадобиться для отрисовки и управления.\n -->\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"node.data\"\n :depth=\"node.depth\"\n :collapsed=\"node.collapsed\"\n :has-children=\"node.hasChildren\"\n :toggle=\"() => toggleNode(node)\"\n >\n <TreeNodeCard\n :label=\"labelOf(node)\"\n :has-children=\"node.hasChildren\"\n :collapsed=\"node.collapsed\"\n @toggle=\"toggleNode(node)\"\n />\n </slot>\n </div>\n </div>\n </div>\n</template>\n","import type { App, Plugin } from 'vue'\nimport TreeView from './TreeView.vue'\nimport TreeNodeCard from './TreeNodeCard.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 }\nexport { useTreeLayout } from './useTreeLayout'\nexport { usePanZoom } from './usePanZoom'\n\n// Типы из core переэкспортируем, чтобы их не приходилось ставить отдельно.\nexport type {\n Accessors,\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 { DEFAULT_OPTIONS } 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 },\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","props","rootElement","panZoom","DEFAULT_SCALE_LIMITS","canvasStyle","toCssTransform","rootStyle","scale","nodeStyle","node","nodeClassOf","labelOf","data","toggleNode","onNodeClick","event","fit","onMounted","nextTick","__expose","_normalizeClass","_unref","_Fragment","_renderList","link","_renderSlot","_ctx","_normalizeStyle","$event","_createVNode","TreeNodeCard","injectStyles","styles","TreeViewPlugin","app","TreeView"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { ScaleLimits, Size, Transform } from '@bigplay/tree-view-core';
|
|
3
|
+
export interface UsePanZoomParams {
|
|
4
|
+
/** Элемент, который слушаем: по нему тянут и крутят колесо. */
|
|
5
|
+
element: Ref<HTMLElement | null>;
|
|
6
|
+
pannable: () => boolean;
|
|
7
|
+
zoomable: () => boolean;
|
|
8
|
+
limits: () => ScaleLimits;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Тонкая обёртка над `PanZoomController` из core:
|
|
12
|
+
* переносит его состояние в реактивный `ref` и следит за жизненным циклом.
|
|
13
|
+
*/
|
|
14
|
+
export declare function usePanZoom(params: UsePanZoomParams): {
|
|
15
|
+
transform: Ref<{
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
scale: number;
|
|
19
|
+
}, Transform | {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
scale: number;
|
|
23
|
+
}>;
|
|
24
|
+
fit: (content: Size, padding?: number) => void;
|
|
25
|
+
zoomIn: () => void;
|
|
26
|
+
zoomOut: () => void;
|
|
27
|
+
reset: () => void;
|
|
28
|
+
setTransform: (next: Transform) => void;
|
|
29
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComputedRef } from 'vue';
|
|
2
|
+
import { Accessors, Layout, TreeViewOptions } from '@bigplay/tree-view-core';
|
|
3
|
+
/** Функции-геттеры, чтобы композабл видел свежие значения пропов. */
|
|
4
|
+
export interface UseTreeLayoutParams<T> {
|
|
5
|
+
data: () => T | T[] | null | undefined;
|
|
6
|
+
options: () => Partial<TreeViewOptions> | undefined;
|
|
7
|
+
accessors: () => Accessors<T>;
|
|
8
|
+
}
|
|
9
|
+
export interface UseTreeLayoutResult<T> {
|
|
10
|
+
/** Готовая раскладка: узлы с координатами и линии. */
|
|
11
|
+
layout: ComputedRef<Layout<T>>;
|
|
12
|
+
/** Свёрнут ли узел с таким id. */
|
|
13
|
+
isCollapsed: (id: string) => boolean;
|
|
14
|
+
/** Свернуть/развернуть узел. */
|
|
15
|
+
toggle: (id: string) => void;
|
|
16
|
+
/** Развернуть всё дерево. */
|
|
17
|
+
expandAll: () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Вся «логика» компонента: данные → дерево → раскладка.
|
|
21
|
+
* Каждый шаг — отдельная чистая функция из core, здесь только реактивность.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useTreeLayout<T>(params: UseTreeLayoutParams<T>): UseTreeLayoutResult<T>;
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bigplay/tree-view",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Компонент дерева и турнирных сеток для Vue 3",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"*.css"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"vue",
|
|
25
|
+
"vue3",
|
|
26
|
+
"tree",
|
|
27
|
+
"bracket",
|
|
28
|
+
"tournament",
|
|
29
|
+
"diagram"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "vite build",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@bigplay/tree-view-core": "^0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"vue": "^3.3.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.19.0",
|
|
45
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
46
|
+
"@vue/test-utils": "^2.4.6",
|
|
47
|
+
"happy-dom": "^15.11.7",
|
|
48
|
+
"typescript": "^5.8.3",
|
|
49
|
+
"vite": "^7.1.2",
|
|
50
|
+
"vite-plugin-dts": "^4.5.4",
|
|
51
|
+
"vitest": "^3.2.4",
|
|
52
|
+
"vue": "^3.5.18",
|
|
53
|
+
"vue-tsc": "^3.0.5"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/dadashi44/tree-view.git",
|
|
58
|
+
"directory": "packages/vue3"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/dadashi44/tree-view#readme",
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/dadashi44/tree-view/issues"
|
|
63
|
+
},
|
|
64
|
+
"author": "Daniyar Kudaibergenev",
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|