@splicetree/adapter-vue 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +450 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Michael Cocova
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @splicetree/adapter-vue
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@splicetree/adapter-vue)
|
|
4
|
+
[](https://npmcharts.com/compare/%40splicetree%2Fadapter-vue?minimal=true)
|
|
5
|
+
[](https://www.npmjs.com/package/@splicetree/adapter-vue)
|
|
6
|
+
[](https://www.splicetree.dev)
|
|
7
|
+
[](https://github.com/michaelcocova/splicetree)
|
|
8
|
+
|
|
9
|
+
提供响应式 `items` 与操作方法,自动监听核心事件刷新视图。
|
|
10
|
+
|
|
11
|
+
## 简介
|
|
12
|
+
|
|
13
|
+
SpliceTree Vue 适配层,将核心 `items()` 适配为 `shallowRef` 响应式数据,并复用核心 API。
|
|
14
|
+
|
|
15
|
+
## 官方文档
|
|
16
|
+
|
|
17
|
+
文档与示例位于 [https://www.splicetree.dev](https://www.splicetree.dev)
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm add @splicetree/adapter-vue
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 使用
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { useSpliceTree } from '@splicetree/adapter-vue'
|
|
29
|
+
|
|
30
|
+
const { items, expand, collapse, toggleExpand } = useSpliceTree(data, {
|
|
31
|
+
defaultExpanded: ['a'],
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// 在组件中渲染
|
|
35
|
+
// <template>
|
|
36
|
+
// <div v-for="n in items" :key="n.id">{{ n.original.title }}</div>
|
|
37
|
+
// </template>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 返回值
|
|
41
|
+
|
|
42
|
+
- `items: ShallowRef<SpliceTreeNode[]>` 响应式可见节点列表
|
|
43
|
+
- 其余方法属性与核心一致(除 `items()`)
|
|
44
|
+
|
|
45
|
+
## 选项(继承 Core)
|
|
46
|
+
|
|
47
|
+
| 名称 | 类型 | 默认值 | 说明 |
|
|
48
|
+
| ----------------- | -------------------- | ---------- | ------------ |
|
|
49
|
+
| `keyField` | `string` | `'id'` | 主键字段名 |
|
|
50
|
+
| `parentField` | `string` | `'parent'` | 父级字段名 |
|
|
51
|
+
| `plugins` | `SpliceTreePlugin[]` | `[]` | 插件列表 |
|
|
52
|
+
| `defaultExpanded` | `string[]` | `[]` | 初始展开集合 |
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
[MIT](https://github.com/michaelcocova/splicetree/blob/main/LICENSE),仓库地址 [https://github.com/michaelcocova/splicetree](https://github.com/michaelcocova/splicetree)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Ref, ShallowRef, WritableComputedRef } from "vue";
|
|
2
|
+
import { SpliceTreeData, SpliceTreeData as SpliceTreeData$1, SpliceTreeInstance, SpliceTreeInstance as SpliceTreeInstance$1, SpliceTreeNode, SpliceTreeNode as SpliceTreeNode$1, UseSpliceTreeOptions, UseSpliceTreeOptions as UseSpliceTreeOptions$1 } from "@splicetree/core";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
interface UseSpliceTreeReturn<T extends SpliceTreeData$1 = SpliceTreeData$1> extends ShallowRef<Omit<SpliceTreeInstance$1<T>, 'items'>> {
|
|
6
|
+
items: ShallowRef<SpliceTreeNode$1<T>[]>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Vue 3 适配器
|
|
10
|
+
* - 以 shallowRef 暴露 items,使其在核心派发 visibility 事件时刷新
|
|
11
|
+
* - 保留核心 API 的完整返回(展开/收起/移动等方法)
|
|
12
|
+
* - 适用于任意自定义渲染逻辑与组件绑定
|
|
13
|
+
*/
|
|
14
|
+
declare function useSpliceTree<T extends SpliceTreeData$1 = SpliceTreeData$1>(data: Ref<T[]> | T[] | WritableComputedRef<T[]>, options?: UseSpliceTreeOptions$1<T>): UseSpliceTreeReturn<T>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { type SpliceTreeData, type SpliceTreeInstance, type SpliceTreeNode, type UseSpliceTreeOptions, UseSpliceTreeReturn, useSpliceTree };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { shallowRef, toValue, watch } from "vue";
|
|
2
|
+
|
|
3
|
+
//#region ../../core/dist/index.js
|
|
4
|
+
/**
|
|
5
|
+
* 轻量事件总线:支持订阅与派发,供核心与插件通信
|
|
6
|
+
*/
|
|
7
|
+
function createEmitter() {
|
|
8
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
9
|
+
const on = (name, handler) => {
|
|
10
|
+
if (!listeners.has(name)) listeners.set(name, /* @__PURE__ */ new Set());
|
|
11
|
+
const set = listeners.get(name);
|
|
12
|
+
set.add(handler);
|
|
13
|
+
return () => set.delete(handler);
|
|
14
|
+
};
|
|
15
|
+
const emit = (payload) => {
|
|
16
|
+
const set = listeners.get(payload.name);
|
|
17
|
+
if (!set) return;
|
|
18
|
+
for (const h of set) h(payload);
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
on,
|
|
22
|
+
emit
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 创建运行时树节点,并注入必要的 API
|
|
27
|
+
*/
|
|
28
|
+
function createSpliceTreeNode(id, original, api) {
|
|
29
|
+
return {
|
|
30
|
+
id,
|
|
31
|
+
original,
|
|
32
|
+
level: 0,
|
|
33
|
+
isExpanded: api.isExpanded,
|
|
34
|
+
hasChildren: api.hasChildren,
|
|
35
|
+
getParent: api.getParent,
|
|
36
|
+
getChildren: api.getChildren,
|
|
37
|
+
toggleExpand: api.toggleExpand
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 基于扁平数据构建树结构缓存
|
|
42
|
+
* - by id 的节点 map
|
|
43
|
+
* - parent/children 缓存
|
|
44
|
+
* - 根节点列表
|
|
45
|
+
* 并计算每个节点的 level
|
|
46
|
+
*/
|
|
47
|
+
function buildTree(data, keyField, parentField, expandedKeys = /* @__PURE__ */ new Set()) {
|
|
48
|
+
const map = /* @__PURE__ */ new Map();
|
|
49
|
+
const roots = [];
|
|
50
|
+
const parentCache = /* @__PURE__ */ new Map();
|
|
51
|
+
const childrenCache = /* @__PURE__ */ new Map();
|
|
52
|
+
data.forEach((item) => {
|
|
53
|
+
const id = String(Reflect.get(item, keyField || "id"));
|
|
54
|
+
const node = createSpliceTreeNode(id, item, {
|
|
55
|
+
hasChildren: () => !!childrenCache.get(id)?.length,
|
|
56
|
+
getParent: () => parentCache.get(id),
|
|
57
|
+
getChildren: () => childrenCache.get(id) ?? [],
|
|
58
|
+
isExpanded: () => expandedKeys.has(id),
|
|
59
|
+
toggleExpand: () => {
|
|
60
|
+
if (expandedKeys.has(id)) expandedKeys.delete(id);
|
|
61
|
+
else expandedKeys.add(id);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
map.set(id, node);
|
|
65
|
+
});
|
|
66
|
+
for (const id of map.keys()) childrenCache.set(id, []);
|
|
67
|
+
for (const node of map.values()) {
|
|
68
|
+
const parentId = Reflect.get(node.original, parentField || "parentId");
|
|
69
|
+
if (!parentId) {
|
|
70
|
+
parentCache.set(node.id, void 0);
|
|
71
|
+
roots.push(node);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const parentNode = map.get(String(parentId));
|
|
75
|
+
parentCache.set(node.id, parentNode ?? void 0);
|
|
76
|
+
if (parentNode) childrenCache.get(parentNode.id).push(node);
|
|
77
|
+
else roots.push(node);
|
|
78
|
+
}
|
|
79
|
+
const dfs = (node, level) => {
|
|
80
|
+
node.level = level;
|
|
81
|
+
for (const child of childrenCache.get(node.id) ?? []) dfs(child, level + 1);
|
|
82
|
+
};
|
|
83
|
+
for (const r of roots) dfs(r, 0);
|
|
84
|
+
return {
|
|
85
|
+
roots,
|
|
86
|
+
map,
|
|
87
|
+
parentCache,
|
|
88
|
+
childrenCache
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function initDefaultExpansion(map, expanded, def, lvl) {
|
|
92
|
+
const expandAll = () => {
|
|
93
|
+
for (const id of map.keys()) expanded.add(id);
|
|
94
|
+
};
|
|
95
|
+
const expandByLevel = (lv) => {
|
|
96
|
+
for (const n of map.values()) if (n.level < lv) expanded.add(n.id);
|
|
97
|
+
};
|
|
98
|
+
if (def === true) {
|
|
99
|
+
expandAll();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(def)) {
|
|
103
|
+
for (const id of def) expanded.add(id);
|
|
104
|
+
if (lvl === "deepest") {
|
|
105
|
+
expandAll();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (typeof lvl === "number" && Number.isFinite(lvl) && lvl > 0) expandByLevel(lvl);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (lvl === "deepest") {
|
|
112
|
+
expandAll();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (typeof lvl === "number" && Number.isFinite(lvl) && lvl > 0) expandByLevel(lvl);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 为 Set/Map/Object 创建响应式代理
|
|
119
|
+
* 在集合或对象发生更改时,触发回调以便同步状态(如可见性)
|
|
120
|
+
*/
|
|
121
|
+
function createReactive(target, callback) {
|
|
122
|
+
const isSet = target instanceof Set;
|
|
123
|
+
const isMap = target instanceof Map;
|
|
124
|
+
return new Proxy(target, {
|
|
125
|
+
get(obj, prop, receiver) {
|
|
126
|
+
if (isSet) {
|
|
127
|
+
if (prop === "size") return Reflect.get(obj, prop, obj);
|
|
128
|
+
const value = Reflect.get(obj, prop, obj);
|
|
129
|
+
if (prop === "add") return (v) => {
|
|
130
|
+
const existed = obj.has(v);
|
|
131
|
+
const r = obj.add(v);
|
|
132
|
+
if (!existed) callback({
|
|
133
|
+
type: "ADD",
|
|
134
|
+
target: obj,
|
|
135
|
+
newValue: v
|
|
136
|
+
});
|
|
137
|
+
return r;
|
|
138
|
+
};
|
|
139
|
+
if (prop === "delete") return (v) => {
|
|
140
|
+
const existed = obj.has(v);
|
|
141
|
+
const old = v;
|
|
142
|
+
const r = obj.delete(v);
|
|
143
|
+
if (existed) callback({
|
|
144
|
+
type: "DELETE",
|
|
145
|
+
target: obj,
|
|
146
|
+
oldValue: old
|
|
147
|
+
});
|
|
148
|
+
return r;
|
|
149
|
+
};
|
|
150
|
+
if (prop === "clear") return () => {
|
|
151
|
+
if (obj.size > 0) callback({
|
|
152
|
+
type: "CLEAR",
|
|
153
|
+
target: obj
|
|
154
|
+
});
|
|
155
|
+
return obj.clear();
|
|
156
|
+
};
|
|
157
|
+
return typeof value === "function" ? value.bind(obj) : value;
|
|
158
|
+
}
|
|
159
|
+
if (isMap) {
|
|
160
|
+
if (prop === "size") return Reflect.get(obj, prop, obj);
|
|
161
|
+
const value = Reflect.get(obj, prop, obj);
|
|
162
|
+
if (prop === "set") return (k, v) => {
|
|
163
|
+
const old = obj.get(k);
|
|
164
|
+
const r = obj.set(k, v);
|
|
165
|
+
callback({
|
|
166
|
+
type: "MAP_SET",
|
|
167
|
+
target: obj,
|
|
168
|
+
property: k,
|
|
169
|
+
oldValue: old,
|
|
170
|
+
newValue: v
|
|
171
|
+
});
|
|
172
|
+
return r;
|
|
173
|
+
};
|
|
174
|
+
if (prop === "delete") return (k) => {
|
|
175
|
+
const existed = obj.has(k);
|
|
176
|
+
const old = obj.get(k);
|
|
177
|
+
const r = obj.delete(k);
|
|
178
|
+
if (existed) callback({
|
|
179
|
+
type: "DELETE",
|
|
180
|
+
target: obj,
|
|
181
|
+
property: k,
|
|
182
|
+
oldValue: old
|
|
183
|
+
});
|
|
184
|
+
return r;
|
|
185
|
+
};
|
|
186
|
+
if (prop === "clear") return () => {
|
|
187
|
+
if (obj.size > 0) callback({
|
|
188
|
+
type: "CLEAR",
|
|
189
|
+
target: obj
|
|
190
|
+
});
|
|
191
|
+
return obj.clear();
|
|
192
|
+
};
|
|
193
|
+
return typeof value === "function" ? value.bind(obj) : value;
|
|
194
|
+
}
|
|
195
|
+
return Reflect.get(obj, prop, receiver);
|
|
196
|
+
},
|
|
197
|
+
set(obj, prop, value, receiver) {
|
|
198
|
+
const oldValue = Reflect.get(obj, prop, receiver);
|
|
199
|
+
const r = Reflect.set(obj, prop, value);
|
|
200
|
+
callback({
|
|
201
|
+
type: "SET",
|
|
202
|
+
target: obj,
|
|
203
|
+
property: prop,
|
|
204
|
+
oldValue,
|
|
205
|
+
newValue: value
|
|
206
|
+
});
|
|
207
|
+
return r;
|
|
208
|
+
},
|
|
209
|
+
deleteProperty(obj, prop) {
|
|
210
|
+
const oldValue = Reflect.get(obj, prop);
|
|
211
|
+
const r = Reflect.deleteProperty(obj, prop);
|
|
212
|
+
callback({
|
|
213
|
+
type: "DELETE",
|
|
214
|
+
target: obj,
|
|
215
|
+
property: prop,
|
|
216
|
+
oldValue
|
|
217
|
+
});
|
|
218
|
+
return r;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 以 DFS 方式计算当前可见节点序列
|
|
224
|
+
* 仅在父节点展开时展开其子节点
|
|
225
|
+
*/
|
|
226
|
+
function computeVisibleItems(roots) {
|
|
227
|
+
const result = [];
|
|
228
|
+
const walk = (node) => {
|
|
229
|
+
result.push(node);
|
|
230
|
+
if (node.isExpanded() && node.hasChildren()) for (const child of node.getChildren()) walk(child);
|
|
231
|
+
};
|
|
232
|
+
for (const root of roots) walk(root);
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* 递归设置节点层级
|
|
237
|
+
*/
|
|
238
|
+
function setLevelRecursively(node, childrenCache, startLevel) {
|
|
239
|
+
node.level = startLevel ?? node.level;
|
|
240
|
+
for (const c of childrenCache.get(node.id) ?? []) setLevelRecursively(c, childrenCache, (node.level ?? 0) + 1);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 追加子节点到指定父节点(或根)
|
|
244
|
+
* 自动维护缓存与层级,并触发通知
|
|
245
|
+
*/
|
|
246
|
+
function appendChildren(ctx, parentId, children) {
|
|
247
|
+
const parent = parentId ? ctx.map.get(parentId) : void 0;
|
|
248
|
+
for (const item of children) {
|
|
249
|
+
const id = String(Reflect.get(item, ctx.keyField || "id"));
|
|
250
|
+
if (ctx.map.has(id)) continue;
|
|
251
|
+
const node = {
|
|
252
|
+
id,
|
|
253
|
+
original: item,
|
|
254
|
+
level: parent ? parent.level + 1 : 0,
|
|
255
|
+
hasChildren: () => !!ctx.childrenCache.get(id)?.length,
|
|
256
|
+
getParent: () => ctx.parentCache.get(id),
|
|
257
|
+
getChildren: () => ctx.childrenCache.get(id) ?? [],
|
|
258
|
+
isExpanded: () => ctx.expandedKeys.has(id),
|
|
259
|
+
toggleExpand: (expand) => {
|
|
260
|
+
if (expand === void 0) ctx.tree.toggleExpand(id);
|
|
261
|
+
else if (expand) ctx.tree.expand(id);
|
|
262
|
+
else ctx.tree.collapse(id);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
ctx.map.set(id, node);
|
|
266
|
+
ctx.childrenCache.set(id, []);
|
|
267
|
+
ctx.parentCache.set(id, parent);
|
|
268
|
+
if (parent) ctx.childrenCache.get(parent.id).push(node);
|
|
269
|
+
else ctx.roots.push(node);
|
|
270
|
+
}
|
|
271
|
+
if (parent) setLevelRecursively(parent, ctx.childrenCache, parent.level);
|
|
272
|
+
else ctx.roots.forEach((r) => setLevelRecursively(r, ctx.childrenCache, 0));
|
|
273
|
+
ctx.notify();
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 移动节点到新父级,并支持在某个兄弟节点之前插入
|
|
277
|
+
* 自动维护缓存与层级,并触发通知
|
|
278
|
+
*/
|
|
279
|
+
function moveNode(ctx, id, newParentId, beforeId) {
|
|
280
|
+
const node = ctx.map.get(id);
|
|
281
|
+
if (!node) return;
|
|
282
|
+
const oldParent = ctx.parentCache.get(id);
|
|
283
|
+
const newParent = newParentId ? ctx.map.get(newParentId) : void 0;
|
|
284
|
+
if (oldParent) {
|
|
285
|
+
const arr = ctx.childrenCache.get(oldParent.id) ?? [];
|
|
286
|
+
const idx = arr.findIndex((n) => n.id === id);
|
|
287
|
+
if (idx >= 0) arr.splice(idx, 1);
|
|
288
|
+
} else {
|
|
289
|
+
const idx = ctx.roots.findIndex((n) => n.id === id);
|
|
290
|
+
if (idx >= 0) ctx.roots.splice(idx, 1);
|
|
291
|
+
}
|
|
292
|
+
ctx.parentCache.set(id, newParent);
|
|
293
|
+
node.level = newParent ? newParent.level + 1 : 0;
|
|
294
|
+
if (newParent) {
|
|
295
|
+
const arr = ctx.childrenCache.get(newParent.id) ?? [];
|
|
296
|
+
if (!ctx.childrenCache.has(newParent.id)) ctx.childrenCache.set(newParent.id, arr);
|
|
297
|
+
if (beforeId) {
|
|
298
|
+
const idx = arr.findIndex((n) => n.id === beforeId);
|
|
299
|
+
if (idx >= 0) arr.splice(idx, 0, node);
|
|
300
|
+
else arr.push(node);
|
|
301
|
+
} else arr.push(node);
|
|
302
|
+
} else if (beforeId) {
|
|
303
|
+
const idx = ctx.roots.findIndex((n) => n.id === beforeId);
|
|
304
|
+
if (idx >= 0) ctx.roots.splice(idx, 0, node);
|
|
305
|
+
else ctx.roots.push(node);
|
|
306
|
+
} else ctx.roots.push(node);
|
|
307
|
+
setLevelRecursively(node, ctx.childrenCache, node.level);
|
|
308
|
+
ctx.notify();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* 创建 SpliceTree 树实例
|
|
312
|
+
* - 构建缓存结构
|
|
313
|
+
* - 暴露操作方法(展开/收起/追加/移动)
|
|
314
|
+
* - 提供插件扩展点(setup/extendNode)
|
|
315
|
+
*/
|
|
316
|
+
function createSpliceTree(data, options = {}) {
|
|
317
|
+
const keyField = options.keyField ?? "id";
|
|
318
|
+
const parentField = options.parentField ?? "parent";
|
|
319
|
+
const events = createEmitter();
|
|
320
|
+
const expandedKeys = createReactive(/* @__PURE__ */ new Set(), (payload) => {
|
|
321
|
+
events.emit({
|
|
322
|
+
name: "visibility",
|
|
323
|
+
keys: Array.from(payload.target)
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
const { roots, map, parentCache, childrenCache } = buildTree(data, keyField, parentField, expandedKeys);
|
|
327
|
+
initDefaultExpansion(map, expandedKeys, options.defaultExpanded, options.defaultExpandedLevel);
|
|
328
|
+
const emitVisibility = () => {
|
|
329
|
+
events.emit({
|
|
330
|
+
name: "visibility",
|
|
331
|
+
keys: Array.from(expandedKeys)
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
const tree = {
|
|
335
|
+
data,
|
|
336
|
+
options,
|
|
337
|
+
items: () => computeVisibleItems(roots),
|
|
338
|
+
getNode: (id) => map.get(id),
|
|
339
|
+
events,
|
|
340
|
+
expandedKeys: () => Array.from(expandedKeys),
|
|
341
|
+
isExpanded: (id) => expandedKeys.has(id),
|
|
342
|
+
expand(ids) {
|
|
343
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
344
|
+
for (const id of list) if (!expandedKeys.has(id)) expandedKeys.add(id);
|
|
345
|
+
emitVisibility();
|
|
346
|
+
},
|
|
347
|
+
collapse(ids) {
|
|
348
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
349
|
+
for (const id of list) if (expandedKeys.has(id)) expandedKeys.delete(id);
|
|
350
|
+
emitVisibility();
|
|
351
|
+
},
|
|
352
|
+
toggleExpand(ids) {
|
|
353
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
354
|
+
const toExpand = [];
|
|
355
|
+
const toCollapse = [];
|
|
356
|
+
for (const id of list) if (tree.isExpanded(id)) toCollapse.push(id);
|
|
357
|
+
else toExpand.push(id);
|
|
358
|
+
if (toExpand.length) tree.expand(toExpand);
|
|
359
|
+
if (toCollapse.length) tree.collapse(toCollapse);
|
|
360
|
+
},
|
|
361
|
+
expandAll() {
|
|
362
|
+
for (const id of map.keys()) expandedKeys.add(id);
|
|
363
|
+
emitVisibility();
|
|
364
|
+
},
|
|
365
|
+
collapseAll() {
|
|
366
|
+
expandedKeys.clear();
|
|
367
|
+
emitVisibility();
|
|
368
|
+
},
|
|
369
|
+
toggleExpandAll() {
|
|
370
|
+
if (expandedKeys.size > 0) tree.collapseAll();
|
|
371
|
+
else tree.expandAll();
|
|
372
|
+
},
|
|
373
|
+
appendChildren(parentId, children) {
|
|
374
|
+
appendChildren({
|
|
375
|
+
map,
|
|
376
|
+
tree,
|
|
377
|
+
roots,
|
|
378
|
+
keyField,
|
|
379
|
+
parentCache,
|
|
380
|
+
childrenCache,
|
|
381
|
+
expandedKeys,
|
|
382
|
+
notify: emitVisibility
|
|
383
|
+
}, parentId, children);
|
|
384
|
+
},
|
|
385
|
+
moveNode(id, newParentId, beforeId) {
|
|
386
|
+
moveNode({
|
|
387
|
+
map,
|
|
388
|
+
tree,
|
|
389
|
+
roots,
|
|
390
|
+
keyField,
|
|
391
|
+
parentCache,
|
|
392
|
+
childrenCache,
|
|
393
|
+
expandedKeys,
|
|
394
|
+
notify: emitVisibility
|
|
395
|
+
}, id, newParentId, beforeId);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
const pluginCtx = {
|
|
399
|
+
tree,
|
|
400
|
+
options,
|
|
401
|
+
events
|
|
402
|
+
};
|
|
403
|
+
options?.plugins?.forEach((plugin) => {
|
|
404
|
+
const api = plugin.setup?.(pluginCtx);
|
|
405
|
+
Object.assign(tree, api);
|
|
406
|
+
});
|
|
407
|
+
if (options?.plugins?.length) for (const n of map.values()) for (const plugin of options.plugins) plugin.extendNode?.(n, pluginCtx);
|
|
408
|
+
for (const node of map.values()) {
|
|
409
|
+
node.isExpanded = () => tree.isExpanded(node.id);
|
|
410
|
+
node.toggleExpand = (expand) => {
|
|
411
|
+
if (expand === void 0) tree.toggleExpand(node.id);
|
|
412
|
+
else if (expand) tree.expand(node.id);
|
|
413
|
+
else tree.collapse(node.id);
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return tree;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/index.ts
|
|
421
|
+
/**
|
|
422
|
+
* Vue 3 适配器
|
|
423
|
+
* - 以 shallowRef 暴露 items,使其在核心派发 visibility 事件时刷新
|
|
424
|
+
* - 保留核心 API 的完整返回(展开/收起/移动等方法)
|
|
425
|
+
* - 适用于任意自定义渲染逻辑与组件绑定
|
|
426
|
+
*/
|
|
427
|
+
function useSpliceTree(data, options = {}) {
|
|
428
|
+
const api = shallowRef();
|
|
429
|
+
const items = shallowRef(api.value?.items?.() ?? []);
|
|
430
|
+
const createTree = () => {
|
|
431
|
+
api.value = createSpliceTree(toValue(data), options);
|
|
432
|
+
api.value.events.on("visibility", () => {
|
|
433
|
+
items.value = api.value.items();
|
|
434
|
+
});
|
|
435
|
+
items.value = api.value.items();
|
|
436
|
+
};
|
|
437
|
+
watch(() => toValue(data), () => {
|
|
438
|
+
createTree();
|
|
439
|
+
}, {
|
|
440
|
+
deep: true,
|
|
441
|
+
immediate: true
|
|
442
|
+
});
|
|
443
|
+
return {
|
|
444
|
+
...api,
|
|
445
|
+
items
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
export { useSpliceTree };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@splicetree/adapter-vue",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"author": {
|
|
6
|
+
"email": "michael.cocova@gmail.com",
|
|
7
|
+
"name": "Michael Cocova"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"homepage": "https://www.splicetree.dev",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/michaelcocova/splicetree"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./dist/index.js",
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"vue": "^3.0.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"unplugin-vue": "^7.1.0",
|
|
30
|
+
"vue-tsc": "^3.1.5"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"vue": "^3.0.0",
|
|
34
|
+
"@splicetree/core": "0.0.1"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"dev": "tsdown --watch",
|
|
41
|
+
"build": "tsdown"
|
|
42
|
+
}
|
|
43
|
+
}
|