@splicetree/plugin-checkable 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/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +129 -0
- package/package.json +32 -0
- package/src/index.ts +241 -0
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +11 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @splicetree/plugin-checkable
|
|
2
|
+
|
|
3
|
+
## 0.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- chore: 搭建 SpliceTree 框架基础架构与工程体系
|
|
8
|
+
|
|
9
|
+
框架架构:
|
|
10
|
+
- 建立无头树(Headless Tree)核心模型与数据结构
|
|
11
|
+
- 定义节点操作 API、事件系统和状态管理机制
|
|
12
|
+
- 设计并实现插件体系(生命周期、注册机制、能力扩展)
|
|
13
|
+
- 引入模块化架构,明确 core / plugins / adapters 的边界
|
|
14
|
+
- 预留扩展点与内部协议,构建可插拔式架构基础
|
|
15
|
+
|
|
16
|
+
适配层与生态:
|
|
17
|
+
- 添加 Vue 3 适配层(渲染无关、纯接口绑定)
|
|
18
|
+
- 设计独立的 UI 层解耦策略,确保跨框架可迁移
|
|
19
|
+
- 预留未来 React/Svelte/WebComponents 的适配接口
|
|
20
|
+
|
|
21
|
+
工程化与工具链:
|
|
22
|
+
- 初始化 monorepo 工程结构(packages + docs)
|
|
23
|
+
- 配置构建工具链 tsdown(多包构建、类型输出)
|
|
24
|
+
- 构建文档系统(VitePress)与基础导航结构
|
|
25
|
+
- 设置开发环境、代码规范(ESLint)、格式化流程
|
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,55 @@
|
|
|
1
|
+
# @splicetree/plugin-checkable
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@splicetree/plugin-checkable)
|
|
4
|
+
[](https://npmcharts.com/compare/%40splicetree%2Fplugin-checkable?minimal=true)
|
|
5
|
+
[](https://www.npmjs.com/package/@splicetree/plugin-checkable)
|
|
6
|
+
[](https://www.splicetree.dev)
|
|
7
|
+
[](https://github.com/michaelcocova/splicetree)
|
|
8
|
+
|
|
9
|
+
为树节点提供勾选与半选能力,支持向下级联与向上计算半选。
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
`pnpm add @splicetree/plugin-checkable`
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createSpliceTree } from '@splicetree/core'
|
|
19
|
+
import checkable from '@splicetree/plugin-checkable'
|
|
20
|
+
|
|
21
|
+
const tree = createSpliceTree(data, {
|
|
22
|
+
plugins: [checkable],
|
|
23
|
+
defaultExpanded: ['a'],
|
|
24
|
+
defaultChecked: ['a'],
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Api
|
|
29
|
+
|
|
30
|
+
### Options
|
|
31
|
+
|
|
32
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
33
|
+
| ---------------- | ---------- | ------ | ------------ |
|
|
34
|
+
| `defaultChecked` | `string[]` | `[]` | 初始勾选集合 |
|
|
35
|
+
|
|
36
|
+
### Events
|
|
37
|
+
|
|
38
|
+
| 事件 | 负载 | 说明 |
|
|
39
|
+
| --------- | -------------------- | ------------------ |
|
|
40
|
+
| `checked` | `{ keys: string[] }` | 勾选状态变化时触发 |
|
|
41
|
+
|
|
42
|
+
### 实例方法
|
|
43
|
+
|
|
44
|
+
| 名称 | 参数 | 说明 |
|
|
45
|
+
| ------------------- | ---- | ------------ |
|
|
46
|
+
| `checkedKeys` | `无` | 当前勾选集合 |
|
|
47
|
+
| `indeterminateKeys` | `无` | 当前半选集合 |
|
|
48
|
+
|
|
49
|
+
### 节点方法
|
|
50
|
+
|
|
51
|
+
| 名称 | 参数 | 说明 |
|
|
52
|
+
| ----------------------- | ------------------- | -------------- |
|
|
53
|
+
| `isChecked()` | `无` | 是否勾选 |
|
|
54
|
+
| `isIndeterminate()` | `无` | 是否半选 |
|
|
55
|
+
| `toggleCheck(checked?)` | `checked?: boolean` | 切换或显式设置 |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { SpliceTreePlugin } from "@splicetree/core";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
declare module '@splicetree/core' {
|
|
5
|
+
/**
|
|
6
|
+
* 选项扩展(Checkable)
|
|
7
|
+
* - defaultChecked:初始勾选的节点 id 列表
|
|
8
|
+
*/
|
|
9
|
+
interface UseSpliceTreeOptions {
|
|
10
|
+
/**
|
|
11
|
+
* 初始勾选的节点 id 列表
|
|
12
|
+
* 在初始化时将这些节点设置为已勾选
|
|
13
|
+
*/
|
|
14
|
+
defaultChecked?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 节点扩展(Checkable)
|
|
18
|
+
* - isChecked:是否已勾选
|
|
19
|
+
* - isIndeterminate:是否半选
|
|
20
|
+
* - toggleCheck:切换或显式设置勾选状态
|
|
21
|
+
*/
|
|
22
|
+
interface SpliceTreeNode {
|
|
23
|
+
/**
|
|
24
|
+
* 当前节点是否已勾选
|
|
25
|
+
*/
|
|
26
|
+
isChecked: () => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 当前节点是否为半选状态
|
|
29
|
+
*/
|
|
30
|
+
isIndeterminate: () => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* 切换或显式设置当前节点的勾选状态
|
|
33
|
+
* @param checked 不传表示切换;true/false 表示显式设置
|
|
34
|
+
*/
|
|
35
|
+
toggleCheck: (checked?: boolean) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 事件扩展(Checkable)
|
|
39
|
+
* - checked:勾选集合变化时派发
|
|
40
|
+
*/
|
|
41
|
+
interface SpliceTreeEventPayloadMap {
|
|
42
|
+
/**
|
|
43
|
+
* 勾选集合变化事件负载
|
|
44
|
+
* @property keys 当前所有已勾选节点的 id 列表
|
|
45
|
+
*/
|
|
46
|
+
checked: {
|
|
47
|
+
keys: string[];
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 实例扩展(Checkable)
|
|
52
|
+
* - checkedKeys/indeterminateKeys:当前勾选/半选集合
|
|
53
|
+
* - isChecked/isIndeterminate:查询节点状态
|
|
54
|
+
* - check/uncheck/toggleCheck:操作勾选状态
|
|
55
|
+
*/
|
|
56
|
+
interface SpliceTreeInstance {
|
|
57
|
+
/**
|
|
58
|
+
* 已勾选的节点 id 集合
|
|
59
|
+
*/
|
|
60
|
+
checkedKeys: Set<string>;
|
|
61
|
+
/**
|
|
62
|
+
* 半选的节点 id 集合
|
|
63
|
+
*/
|
|
64
|
+
indeterminateKeys: Set<string>;
|
|
65
|
+
/**
|
|
66
|
+
* 查询指定节点是否已勾选
|
|
67
|
+
*/
|
|
68
|
+
isChecked: (id: string) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* 查询指定节点是否为半选状态
|
|
71
|
+
*/
|
|
72
|
+
isIndeterminate: (id: string) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* 勾选指定节点
|
|
75
|
+
*/
|
|
76
|
+
check: (id: string) => void;
|
|
77
|
+
/**
|
|
78
|
+
* 取消勾选指定节点
|
|
79
|
+
*/
|
|
80
|
+
uncheck: (id: string) => void;
|
|
81
|
+
/**
|
|
82
|
+
* 切换指定节点的勾选状态
|
|
83
|
+
*/
|
|
84
|
+
toggleCheck: (id: string) => void;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
declare const checkable: SpliceTreePlugin;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { checkable, checkable as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* 设置某节点的勾选状态并维护集合
|
|
4
|
+
* @param id 节点 id
|
|
5
|
+
* @param state 目标状态(checked/unchecked/indeterminate)
|
|
6
|
+
* @param checked 已勾选集合
|
|
7
|
+
* @param indeterminate 半选集合
|
|
8
|
+
*/
|
|
9
|
+
function setChecked(id, state, checked, indeterminate) {
|
|
10
|
+
if (state === "checked") {
|
|
11
|
+
checked.add(id);
|
|
12
|
+
indeterminate.delete(id);
|
|
13
|
+
} else if (state === "unchecked") {
|
|
14
|
+
checked.delete(id);
|
|
15
|
+
indeterminate.delete(id);
|
|
16
|
+
} else {
|
|
17
|
+
checked.delete(id);
|
|
18
|
+
indeterminate.add(id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const checkable = {
|
|
22
|
+
name: "checkable",
|
|
23
|
+
setup(ctx) {
|
|
24
|
+
const { defaultChecked = [] } = ctx.options;
|
|
25
|
+
const checkedKeys = new Set(defaultChecked);
|
|
26
|
+
const indeterminateKeys = /* @__PURE__ */ new Set();
|
|
27
|
+
const isChecked = (id) => checkedKeys.has(id);
|
|
28
|
+
const isIndeterminate = (id) => indeterminateKeys.has(id);
|
|
29
|
+
/**
|
|
30
|
+
* 向下遍历所有子节点并执行回调
|
|
31
|
+
* @param id 起始父节点 id
|
|
32
|
+
* @param visit 对每个子节点执行的回调
|
|
33
|
+
*/
|
|
34
|
+
const descend = (id, visit) => {
|
|
35
|
+
const n = ctx.tree.getNode(id);
|
|
36
|
+
if (!n) return;
|
|
37
|
+
for (const c of n.getChildren()) {
|
|
38
|
+
visit(c.id);
|
|
39
|
+
descend(c.id, visit);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 自底向上更新祖先节点的勾选/半选状态
|
|
44
|
+
* - 所有子节点全勾选 → 祖先设为勾选
|
|
45
|
+
* - 所有子节点全未勾选 → 祖先设为未勾选
|
|
46
|
+
* - 否则 → 祖先设为半选
|
|
47
|
+
* 同步派发 visibility 与 checked 事件
|
|
48
|
+
* @param id 起始子节点 id
|
|
49
|
+
*/
|
|
50
|
+
const updateAncestors = (id) => {
|
|
51
|
+
let cur = ctx.tree.getNode(id)?.getParent();
|
|
52
|
+
while (cur) {
|
|
53
|
+
const children = cur.getChildren();
|
|
54
|
+
const allChecked = children.every((ch) => checkedKeys.has(ch.id));
|
|
55
|
+
const noneChecked = children.every((ch) => !checkedKeys.has(ch.id) && !indeterminateKeys.has(ch.id));
|
|
56
|
+
if (allChecked) setChecked(cur.id, "checked", checkedKeys, indeterminateKeys);
|
|
57
|
+
else if (noneChecked) setChecked(cur.id, "unchecked", checkedKeys, indeterminateKeys);
|
|
58
|
+
else setChecked(cur.id, "indeterminate", checkedKeys, indeterminateKeys);
|
|
59
|
+
cur = cur.getParent();
|
|
60
|
+
}
|
|
61
|
+
ctx.events.emit({
|
|
62
|
+
name: "visibility",
|
|
63
|
+
keys: ctx.tree.expandedKeys()
|
|
64
|
+
});
|
|
65
|
+
ctx.events.emit({
|
|
66
|
+
name: "checked",
|
|
67
|
+
keys: Array.from(checkedKeys)
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* 勾选指定节点,并向下级联勾选所有子节点
|
|
72
|
+
* @param id 节点 id
|
|
73
|
+
*/
|
|
74
|
+
const check = (id) => {
|
|
75
|
+
setChecked(id, "checked", checkedKeys, indeterminateKeys);
|
|
76
|
+
descend(id, (nid) => setChecked(nid, "checked", checkedKeys, indeterminateKeys));
|
|
77
|
+
updateAncestors(id);
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* 取消勾选指定节点,并向下级联取消所有子节点
|
|
81
|
+
* @param id 节点 id
|
|
82
|
+
*/
|
|
83
|
+
const uncheck = (id) => {
|
|
84
|
+
setChecked(id, "unchecked", checkedKeys, indeterminateKeys);
|
|
85
|
+
descend(id, (nid) => setChecked(nid, "unchecked", checkedKeys, indeterminateKeys));
|
|
86
|
+
updateAncestors(id);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 切换指定节点的勾选状态
|
|
90
|
+
* @param id 节点 id
|
|
91
|
+
*/
|
|
92
|
+
const toggleCheck = (id) => {
|
|
93
|
+
if (isChecked(id)) uncheck(id);
|
|
94
|
+
else check(id);
|
|
95
|
+
updateAncestors(id);
|
|
96
|
+
};
|
|
97
|
+
for (const id of defaultChecked) check(id);
|
|
98
|
+
ctx.events.emit({
|
|
99
|
+
name: "visibility",
|
|
100
|
+
keys: ctx.tree.expandedKeys()
|
|
101
|
+
});
|
|
102
|
+
ctx.events.emit({
|
|
103
|
+
name: "checked",
|
|
104
|
+
keys: Array.from(checkedKeys)
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
checkedKeys,
|
|
108
|
+
indeterminateKeys,
|
|
109
|
+
isChecked,
|
|
110
|
+
isIndeterminate,
|
|
111
|
+
check,
|
|
112
|
+
uncheck,
|
|
113
|
+
toggleCheck
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
extendNode(node, ctx) {
|
|
117
|
+
node.isChecked = () => ctx.tree.isChecked(node.id);
|
|
118
|
+
node.isIndeterminate = () => ctx.tree.isIndeterminate(node.id);
|
|
119
|
+
node.toggleCheck = (checked) => {
|
|
120
|
+
if (checked === void 0) ctx.tree.toggleCheck(node.id);
|
|
121
|
+
else if (checked) ctx.tree.check(node.id);
|
|
122
|
+
else ctx.tree.uncheck(node.id);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var src_default = checkable;
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
export { checkable, src_default as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@splicetree/plugin-checkable",
|
|
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
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@splicetree/core": "0.0.1"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "tsdown --watch",
|
|
30
|
+
"build": "tsdown"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { SpliceTreePlugin, SpliceTreePluginContext } from '@splicetree/core'
|
|
2
|
+
import '@splicetree/core'
|
|
3
|
+
|
|
4
|
+
declare module '@splicetree/core' {
|
|
5
|
+
/**
|
|
6
|
+
* 选项扩展(Checkable)
|
|
7
|
+
* - defaultChecked:初始勾选的节点 id 列表
|
|
8
|
+
*/
|
|
9
|
+
interface UseSpliceTreeOptions {
|
|
10
|
+
/**
|
|
11
|
+
* 初始勾选的节点 id 列表
|
|
12
|
+
* 在初始化时将这些节点设置为已勾选
|
|
13
|
+
*/
|
|
14
|
+
defaultChecked?: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 节点扩展(Checkable)
|
|
19
|
+
* - isChecked:是否已勾选
|
|
20
|
+
* - isIndeterminate:是否半选
|
|
21
|
+
* - toggleCheck:切换或显式设置勾选状态
|
|
22
|
+
*/
|
|
23
|
+
interface SpliceTreeNode {
|
|
24
|
+
/**
|
|
25
|
+
* 当前节点是否已勾选
|
|
26
|
+
*/
|
|
27
|
+
isChecked: () => boolean
|
|
28
|
+
/**
|
|
29
|
+
* 当前节点是否为半选状态
|
|
30
|
+
*/
|
|
31
|
+
isIndeterminate: () => boolean
|
|
32
|
+
/**
|
|
33
|
+
* 切换或显式设置当前节点的勾选状态
|
|
34
|
+
* @param checked 不传表示切换;true/false 表示显式设置
|
|
35
|
+
*/
|
|
36
|
+
toggleCheck: (checked?: boolean) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 事件扩展(Checkable)
|
|
41
|
+
* - checked:勾选集合变化时派发
|
|
42
|
+
*/
|
|
43
|
+
interface SpliceTreeEventPayloadMap {
|
|
44
|
+
/**
|
|
45
|
+
* 勾选集合变化事件负载
|
|
46
|
+
* @property keys 当前所有已勾选节点的 id 列表
|
|
47
|
+
*/
|
|
48
|
+
checked: { keys: string[] }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 实例扩展(Checkable)
|
|
53
|
+
* - checkedKeys/indeterminateKeys:当前勾选/半选集合
|
|
54
|
+
* - isChecked/isIndeterminate:查询节点状态
|
|
55
|
+
* - check/uncheck/toggleCheck:操作勾选状态
|
|
56
|
+
*/
|
|
57
|
+
interface SpliceTreeInstance {
|
|
58
|
+
/**
|
|
59
|
+
* 已勾选的节点 id 集合
|
|
60
|
+
*/
|
|
61
|
+
checkedKeys: Set<string>
|
|
62
|
+
/**
|
|
63
|
+
* 半选的节点 id 集合
|
|
64
|
+
*/
|
|
65
|
+
indeterminateKeys: Set<string>
|
|
66
|
+
/**
|
|
67
|
+
* 查询指定节点是否已勾选
|
|
68
|
+
*/
|
|
69
|
+
isChecked: (id: string) => boolean
|
|
70
|
+
/**
|
|
71
|
+
* 查询指定节点是否为半选状态
|
|
72
|
+
*/
|
|
73
|
+
isIndeterminate: (id: string) => boolean
|
|
74
|
+
/**
|
|
75
|
+
* 勾选指定节点
|
|
76
|
+
*/
|
|
77
|
+
check: (id: string) => void
|
|
78
|
+
/**
|
|
79
|
+
* 取消勾选指定节点
|
|
80
|
+
*/
|
|
81
|
+
uncheck: (id: string) => void
|
|
82
|
+
/**
|
|
83
|
+
* 切换指定节点的勾选状态
|
|
84
|
+
*/
|
|
85
|
+
toggleCheck: (id: string) => void
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 设置某节点的勾选状态并维护集合
|
|
91
|
+
* @param id 节点 id
|
|
92
|
+
* @param state 目标状态(checked/unchecked/indeterminate)
|
|
93
|
+
* @param checked 已勾选集合
|
|
94
|
+
* @param indeterminate 半选集合
|
|
95
|
+
*/
|
|
96
|
+
function setChecked(
|
|
97
|
+
id: string,
|
|
98
|
+
state: 'checked' | 'unchecked' | 'indeterminate',
|
|
99
|
+
checked: Set<string>,
|
|
100
|
+
indeterminate: Set<string>,
|
|
101
|
+
) {
|
|
102
|
+
if (state === 'checked') {
|
|
103
|
+
checked.add(id)
|
|
104
|
+
indeterminate.delete(id)
|
|
105
|
+
} else if (state === 'unchecked') {
|
|
106
|
+
checked.delete(id)
|
|
107
|
+
indeterminate.delete(id)
|
|
108
|
+
} else {
|
|
109
|
+
checked.delete(id)
|
|
110
|
+
indeterminate.add(id)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const checkable: SpliceTreePlugin = {
|
|
115
|
+
name: 'checkable',
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 勾选/半选插件:支持向下级联与向上计算半选
|
|
119
|
+
*/
|
|
120
|
+
setup(ctx: SpliceTreePluginContext) {
|
|
121
|
+
const { defaultChecked = [] } = ctx.options
|
|
122
|
+
|
|
123
|
+
const checkedKeys = new Set<string>(defaultChecked)
|
|
124
|
+
const indeterminateKeys = new Set<string>()
|
|
125
|
+
|
|
126
|
+
const isChecked = (id: string) => checkedKeys.has(id)
|
|
127
|
+
const isIndeterminate = (id: string) => indeterminateKeys.has(id)
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 向下遍历所有子节点并执行回调
|
|
131
|
+
* @param id 起始父节点 id
|
|
132
|
+
* @param visit 对每个子节点执行的回调
|
|
133
|
+
*/
|
|
134
|
+
const descend = (id: string, visit: (nid: string) => void) => {
|
|
135
|
+
const n = ctx.tree.getNode(id)
|
|
136
|
+
if (!n) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
for (const c of n.getChildren()) {
|
|
140
|
+
visit(c.id)
|
|
141
|
+
descend(c.id, visit)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 自底向上更新祖先节点的勾选/半选状态
|
|
147
|
+
* - 所有子节点全勾选 → 祖先设为勾选
|
|
148
|
+
* - 所有子节点全未勾选 → 祖先设为未勾选
|
|
149
|
+
* - 否则 → 祖先设为半选
|
|
150
|
+
* 同步派发 visibility 与 checked 事件
|
|
151
|
+
* @param id 起始子节点 id
|
|
152
|
+
*/
|
|
153
|
+
const updateAncestors = (id: string) => {
|
|
154
|
+
let cur = ctx.tree.getNode(id)?.getParent()
|
|
155
|
+
while (cur) {
|
|
156
|
+
const children = cur.getChildren()
|
|
157
|
+
const allChecked = children.every(ch => checkedKeys.has(ch.id))
|
|
158
|
+
const noneChecked = children.every(ch => !checkedKeys.has(ch.id) && !indeterminateKeys.has(ch.id))
|
|
159
|
+
if (allChecked) {
|
|
160
|
+
setChecked(cur.id, 'checked', checkedKeys, indeterminateKeys)
|
|
161
|
+
} else if (noneChecked) {
|
|
162
|
+
setChecked(cur.id, 'unchecked', checkedKeys, indeterminateKeys)
|
|
163
|
+
} else {
|
|
164
|
+
setChecked(cur.id, 'indeterminate', checkedKeys, indeterminateKeys)
|
|
165
|
+
}
|
|
166
|
+
cur = cur.getParent()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
170
|
+
ctx.events.emit({ name: 'checked', keys: Array.from(checkedKeys) })
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 勾选指定节点,并向下级联勾选所有子节点
|
|
175
|
+
* @param id 节点 id
|
|
176
|
+
*/
|
|
177
|
+
const check = (id: string) => {
|
|
178
|
+
setChecked(id, 'checked', checkedKeys, indeterminateKeys)
|
|
179
|
+
descend(id, nid => setChecked(nid, 'checked', checkedKeys, indeterminateKeys))
|
|
180
|
+
updateAncestors(id)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 取消勾选指定节点,并向下级联取消所有子节点
|
|
185
|
+
* @param id 节点 id
|
|
186
|
+
*/
|
|
187
|
+
const uncheck = (id: string) => {
|
|
188
|
+
setChecked(id, 'unchecked', checkedKeys, indeterminateKeys)
|
|
189
|
+
descend(id, nid => setChecked(nid, 'unchecked', checkedKeys, indeterminateKeys))
|
|
190
|
+
updateAncestors(id)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 切换指定节点的勾选状态
|
|
195
|
+
* @param id 节点 id
|
|
196
|
+
*/
|
|
197
|
+
const toggleCheck = (id: string) => {
|
|
198
|
+
if (isChecked(id)) {
|
|
199
|
+
uncheck(id)
|
|
200
|
+
} else {
|
|
201
|
+
check(id)
|
|
202
|
+
}
|
|
203
|
+
updateAncestors(id)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const id of defaultChecked) {
|
|
207
|
+
check(id)
|
|
208
|
+
}
|
|
209
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
210
|
+
ctx.events.emit({ name: 'checked', keys: Array.from(checkedKeys) } as any)
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
checkedKeys,
|
|
214
|
+
indeterminateKeys,
|
|
215
|
+
isChecked,
|
|
216
|
+
isIndeterminate,
|
|
217
|
+
check,
|
|
218
|
+
uncheck,
|
|
219
|
+
toggleCheck,
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 为节点扩展勾选相关方法
|
|
225
|
+
*/
|
|
226
|
+
extendNode(node, ctx) {
|
|
227
|
+
node.isChecked = () => ctx.tree.isChecked(node.id)
|
|
228
|
+
node.isIndeterminate = () => ctx.tree.isIndeterminate(node.id)
|
|
229
|
+
node.toggleCheck = (checked?: boolean) => {
|
|
230
|
+
if (checked === undefined) {
|
|
231
|
+
ctx.tree.toggleCheck(node.id)
|
|
232
|
+
} else if (checked) {
|
|
233
|
+
ctx.tree.check(node.id)
|
|
234
|
+
} else {
|
|
235
|
+
ctx.tree.uncheck(node.id)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default checkable
|
package/tsconfig.json
ADDED