@kine-design/core 0.0.1-beta.1 → 0.0.1-beta.3

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.
Files changed (325) hide show
  1. package/assets/style/global.css +1 -0
  2. package/assets/style/var/Wuxing.css +71 -0
  3. package/assets/style/var.css +23 -0
  4. package/components/base/affix/api.ts +16 -0
  5. package/components/base/affix/index.ts +17 -0
  6. package/components/base/affix/props.d.ts +34 -0
  7. package/components/base/affix/useAffix.ts +123 -0
  8. package/components/base/alert/api.ts +18 -0
  9. package/components/base/alert/index.ts +15 -0
  10. package/components/base/alert/props.d.ts +50 -0
  11. package/components/base/anchor/api.ts +20 -0
  12. package/components/base/anchor/index.ts +18 -0
  13. package/components/base/anchor/props.d.ts +46 -0
  14. package/components/base/anchor/useAnchor.ts +83 -0
  15. package/components/base/autoComplete/api.ts +24 -0
  16. package/components/base/autoComplete/index.ts +17 -0
  17. package/components/base/autoComplete/props.d.ts +75 -0
  18. package/components/base/autoComplete/useAutoComplete.ts +149 -0
  19. package/components/base/avatar/api.ts +17 -0
  20. package/components/base/avatar/avatar.css +61 -0
  21. package/components/base/avatar/index.ts +15 -0
  22. package/components/base/avatar/props.d.ts +37 -0
  23. package/components/base/backTop/api.ts +17 -0
  24. package/components/base/backTop/index.ts +17 -0
  25. package/components/base/backTop/props.d.ts +38 -0
  26. package/components/base/backTop/useBackTop.ts +62 -0
  27. package/components/base/badge/api.ts +22 -0
  28. package/components/base/badge/index.ts +15 -0
  29. package/components/base/badge/props.d.ts +50 -0
  30. package/components/base/button/api.ts +28 -0
  31. package/components/base/button/button.css +34 -0
  32. package/components/base/button/index.ts +17 -0
  33. package/components/base/button/props.d.ts +64 -0
  34. package/components/base/button/useButton.ts +37 -0
  35. package/components/base/card/api.ts +17 -0
  36. package/components/base/card/index.ts +15 -0
  37. package/components/base/card/props.d.ts +37 -0
  38. package/components/base/carousel/api.ts +28 -0
  39. package/components/base/carousel/index.ts +17 -0
  40. package/components/base/carousel/props.d.ts +72 -0
  41. package/components/base/carousel/useCarousel.ts +149 -0
  42. package/components/base/cascader/api.ts +23 -0
  43. package/components/base/cascader/index.ts +18 -0
  44. package/components/base/cascader/props.d.ts +103 -0
  45. package/components/base/cascader/useCascader.ts +334 -0
  46. package/components/base/checkbox/api.ts +24 -0
  47. package/components/base/checkbox/checkbox.css +0 -0
  48. package/components/base/checkbox/index.ts +17 -0
  49. package/components/base/checkbox/props.d.ts +77 -0
  50. package/components/base/checkbox/useCheckbox.ts +42 -0
  51. package/components/base/collapse/api.ts +21 -0
  52. package/components/base/collapse/index.ts +18 -0
  53. package/components/base/collapse/props.d.ts +45 -0
  54. package/components/base/collapse/useCollapse.ts +80 -0
  55. package/components/base/datePicker/api.ts +18 -0
  56. package/components/base/datePicker/index.ts +19 -0
  57. package/components/base/datePicker/props.d.ts +60 -0
  58. package/components/base/datePicker/useDatePicker.ts +392 -0
  59. package/components/base/divider/api.ts +15 -0
  60. package/components/base/divider/divider.css +11 -0
  61. package/components/base/divider/index.ts +15 -0
  62. package/components/base/divider/props.d.ts +30 -0
  63. package/components/base/dropdown/api.ts +33 -0
  64. package/components/base/dropdown/index.ts +18 -0
  65. package/components/base/dropdown/props.d.ts +60 -0
  66. package/components/base/dropdown/useDropdown.ts +123 -0
  67. package/components/base/empty/api.ts +15 -0
  68. package/components/base/empty/index.ts +15 -0
  69. package/components/base/empty/props.d.ts +26 -0
  70. package/components/base/image/api.ts +25 -0
  71. package/components/base/image/index.ts +18 -0
  72. package/components/base/image/props.d.ts +67 -0
  73. package/components/base/image/useImage.ts +119 -0
  74. package/components/base/input/api.ts +19 -0
  75. package/components/base/input/index.ts +17 -0
  76. package/components/base/input/input.css +19 -0
  77. package/components/base/input/props.d.ts +60 -0
  78. package/components/base/input/useInput.ts +53 -0
  79. package/components/base/inputNumber/api.ts +21 -0
  80. package/components/base/inputNumber/index.ts +17 -0
  81. package/components/base/inputNumber/props.d.ts +64 -0
  82. package/components/base/inputNumber/useInputNumber.ts +140 -0
  83. package/components/base/li/api.ts +15 -0
  84. package/components/base/li/index.ts +15 -0
  85. package/components/base/li/props.d.ts +30 -0
  86. package/components/base/list/api.ts +16 -0
  87. package/components/base/list/index.ts +17 -0
  88. package/components/base/list/props.d.ts +33 -0
  89. package/components/base/list/useList.ts +36 -0
  90. package/components/base/loading/api.ts +17 -0
  91. package/components/base/loading/index.ts +15 -0
  92. package/components/base/loading/props.d.ts +38 -0
  93. package/components/base/popover/api.ts +28 -0
  94. package/components/base/popover/index.ts +17 -0
  95. package/components/base/popover/props.d.ts +73 -0
  96. package/components/base/popover/usePopover.ts +192 -0
  97. package/components/base/progress/api.ts +18 -0
  98. package/components/base/progress/index.ts +17 -0
  99. package/components/base/progress/props.d.ts +53 -0
  100. package/components/base/progress/useProgress.ts +28 -0
  101. package/components/base/radio/api.ts +19 -0
  102. package/components/base/radio/index.ts +19 -0
  103. package/components/base/radio/props.d.ts +59 -0
  104. package/components/base/radio/useRadio.ts +11 -0
  105. package/components/base/rate/api.ts +18 -0
  106. package/components/base/rate/index.ts +17 -0
  107. package/components/base/rate/props.d.ts +49 -0
  108. package/components/base/rate/useRate.ts +75 -0
  109. package/components/base/result/api.ts +20 -0
  110. package/components/base/result/index.ts +15 -0
  111. package/components/base/result/props.d.ts +36 -0
  112. package/components/base/select/api.ts +31 -0
  113. package/components/base/select/index.ts +18 -0
  114. package/components/base/select/props.d.ts +132 -0
  115. package/components/base/select/select.css +7 -0
  116. package/components/base/select/useSelect.ts +280 -0
  117. package/components/base/select/useSelectTools.ts +60 -0
  118. package/components/base/skeleton/api.ts +18 -0
  119. package/components/base/skeleton/index.ts +15 -0
  120. package/components/base/skeleton/props.d.ts +41 -0
  121. package/components/base/slider/api.ts +20 -0
  122. package/components/base/slider/index.ts +17 -0
  123. package/components/base/slider/props.d.ts +65 -0
  124. package/components/base/slider/useSlider.ts +83 -0
  125. package/components/base/space/api.ts +17 -0
  126. package/components/base/space/index.ts +15 -0
  127. package/components/base/space/props.d.ts +39 -0
  128. package/components/base/steps/api.ts +30 -0
  129. package/components/base/steps/index.ts +22 -0
  130. package/components/base/steps/props.d.ts +88 -0
  131. package/components/base/steps/useSteps.ts +101 -0
  132. package/components/base/switch/api.ts +22 -0
  133. package/components/base/switch/index.ts +19 -0
  134. package/components/base/switch/props.d.ts +66 -0
  135. package/components/base/switch/useSwitch.tsx +79 -0
  136. package/components/base/tabs/api.ts +23 -0
  137. package/components/base/tabs/index.ts +18 -0
  138. package/components/base/tabs/props.d.ts +41 -0
  139. package/components/base/tabs/useTabs.ts +66 -0
  140. package/components/base/tag/api.ts +17 -0
  141. package/components/base/tag/index.ts +15 -0
  142. package/components/base/tag/props.d.ts +49 -0
  143. package/components/base/timePicker/api.ts +21 -0
  144. package/components/base/timePicker/index.ts +18 -0
  145. package/components/base/timePicker/props.d.ts +66 -0
  146. package/components/base/timePicker/useTimePicker.ts +161 -0
  147. package/components/base/timeline/api.ts +24 -0
  148. package/components/base/timeline/index.ts +16 -0
  149. package/components/base/timeline/props.d.ts +60 -0
  150. package/components/base/tooltip/api.ts +19 -0
  151. package/components/base/tooltip/index.ts +17 -0
  152. package/components/base/tooltip/props.d.ts +34 -0
  153. package/components/base/tooltip/useTooltip.ts +89 -0
  154. package/components/base/transfer/api.ts +18 -0
  155. package/components/base/transfer/index.ts +17 -0
  156. package/components/base/transfer/props.d.ts +63 -0
  157. package/components/base/transfer/useTransfer.ts +207 -0
  158. package/components/base/tree/api.ts +47 -0
  159. package/components/base/tree/index.ts +29 -0
  160. package/components/base/tree/props.d.ts +108 -0
  161. package/components/base/tree/tree.ts +263 -0
  162. package/components/base/tree/useTree.ts +114 -0
  163. package/components/message/confirm/api.ts +21 -0
  164. package/components/message/confirm/index.ts +15 -0
  165. package/components/message/confirm/props.d.ts +69 -0
  166. package/components/message/dialog/api.ts +19 -0
  167. package/components/message/dialog/index.ts +15 -0
  168. package/components/message/dialog/props.d.ts +55 -0
  169. package/components/message/drawer/api.ts +32 -0
  170. package/components/message/drawer/index.ts +15 -0
  171. package/components/message/drawer/props.d.ts +73 -0
  172. package/components/message/message/api.ts +27 -0
  173. package/components/message/message/index.ts +20 -0
  174. package/components/message/message/props.d.ts +54 -0
  175. package/components/message/message/useMessage.ts +61 -0
  176. package/components/message/notification/api.ts +23 -0
  177. package/components/message/notification/index.ts +19 -0
  178. package/components/message/notification/props.d.ts +64 -0
  179. package/components/message/notification/useNotification.ts +79 -0
  180. package/components/message/popover/MPopover.tsx +94 -0
  181. package/components/message/popover/api.ts +54 -0
  182. package/components/message/popover/index.ts +17 -0
  183. package/components/message/popover/popover.css +21 -0
  184. package/components/message/popover/props.d.ts +76 -0
  185. package/components/message/popover/usePopover.ts +234 -0
  186. package/components/other/darkMode/api.ts +17 -0
  187. package/components/other/darkMode/index.ts +17 -0
  188. package/components/other/darkMode/props.d.ts +37 -0
  189. package/components/other/darkMode/useDarkMode.ts +129 -0
  190. package/components/template/border/api.ts +18 -0
  191. package/components/template/border/index.ts +15 -0
  192. package/components/template/border/props.d.ts +41 -0
  193. package/components/template/breadcrumb/api.ts +15 -0
  194. package/components/template/breadcrumb/index.ts +15 -0
  195. package/components/template/breadcrumb/props.d.ts +45 -0
  196. package/components/template/descriptions/api.ts +23 -0
  197. package/components/template/descriptions/index.ts +16 -0
  198. package/components/template/descriptions/props.d.ts +54 -0
  199. package/components/template/form/api.ts +23 -0
  200. package/components/template/form/index.ts +20 -0
  201. package/components/template/form/props.d.ts +60 -0
  202. package/components/template/grid/api.ts +20 -0
  203. package/components/template/grid/index.ts +15 -0
  204. package/components/template/grid/props.d.ts +48 -0
  205. package/components/template/menu/api.ts +26 -0
  206. package/components/template/menu/index.ts +18 -0
  207. package/components/template/menu/props.d.ts +93 -0
  208. package/components/template/menu/useMenu.ts +155 -0
  209. package/components/template/pagination/api.ts +22 -0
  210. package/components/template/pagination/index.ts +19 -0
  211. package/components/template/pagination/props.d.ts +65 -0
  212. package/components/template/pagination/usePagination.ts +186 -0
  213. package/components/template/table/api.ts +18 -0
  214. package/components/template/table/index.ts +18 -0
  215. package/components/template/table/props.d.ts +36 -0
  216. package/components/template/table/useTable.ts +138 -0
  217. package/components/template/tableColumn/api.ts +17 -0
  218. package/components/template/tableColumn/index.ts +15 -0
  219. package/components/template/tableColumn/props.d.ts +32 -0
  220. package/components/template/virtualList/api.ts +16 -0
  221. package/components/template/virtualList/index.ts +17 -0
  222. package/components/template/virtualList/props.d.ts +25 -0
  223. package/components/template/virtualList/useVirtualList.ts +237 -0
  224. package/components/types/hook.d.ts +13 -0
  225. package/components/types/props.d.ts +52 -0
  226. package/components/types/template.d.ts +59 -0
  227. package/compositions/common/defineCore.ts +55 -0
  228. package/compositions/common/useDebounceFn.ts +27 -0
  229. package/compositions/common/useDrag.ts +65 -0
  230. package/compositions/common/useElementSize.ts +37 -0
  231. package/compositions/common/useEventListener.ts +48 -0
  232. package/compositions/common/usePopover.ts +45 -0
  233. package/compositions/common/useResizeObserver.ts +43 -0
  234. package/compositions/common/useTeleport.ts +24 -0
  235. package/compositions/input/useBooleanInput.ts +52 -0
  236. package/compositions/modal/useModal.ts +72 -0
  237. package/compositions/popper/useClickAway.ts +41 -0
  238. package/compositions/popper/usePopper.ts +63 -0
  239. package/compositions/utils/filters.ts +135 -0
  240. package/compositions/virtualList/enums.ts +52 -0
  241. package/compositions/virtualList/useContainerObserver.ts +89 -0
  242. package/compositions/virtualList/useEntries.ts +248 -0
  243. package/compositions/virtualList/useHeightCache.ts +83 -0
  244. package/compositions/virtualList/useSentinelObserver.ts +81 -0
  245. package/dist/components/base/affix/index.d.ts +2 -1
  246. package/dist/components/base/affix/useAffix.d.ts +6 -5
  247. package/dist/components/base/anchor/index.d.ts +2 -1
  248. package/dist/components/base/anchor/useAnchor.d.ts +2 -2
  249. package/dist/components/base/autoComplete/useAutoComplete.d.ts +12 -4
  250. package/dist/components/base/avatar/index.d.ts +1 -0
  251. package/dist/components/base/backTop/index.d.ts +2 -1
  252. package/dist/components/base/backTop/useBackTop.d.ts +2 -2
  253. package/dist/components/base/button/index.d.ts +3 -21
  254. package/dist/components/base/button/useButton.d.ts +5 -2
  255. package/dist/components/base/carousel/useCarousel.d.ts +6 -3
  256. package/dist/components/base/cascader/useCascader.d.ts +23 -11
  257. package/dist/components/base/checkbox/index.d.ts +2 -1
  258. package/dist/components/base/checkbox/useCheckbox.d.ts +4 -3
  259. package/dist/components/base/collapse/index.d.ts +2 -1
  260. package/dist/components/base/collapse/useCollapse.d.ts +3 -3
  261. package/dist/components/base/datePicker/useDatePicker.d.ts +140 -8
  262. package/dist/components/base/dropdown/index.d.ts +2 -1
  263. package/dist/components/base/dropdown/useDropdown.d.ts +12 -6
  264. package/dist/components/base/image/useImage.d.ts +5 -5
  265. package/dist/components/base/input/index.d.ts +2 -1
  266. package/dist/components/base/input/useInput.d.ts +1 -2
  267. package/dist/components/base/inputNumber/index.d.ts +2 -1
  268. package/dist/components/base/inputNumber/useInputNumber.d.ts +3 -3
  269. package/dist/components/base/li/index.d.ts +1 -0
  270. package/dist/components/base/list/index.d.ts +2 -1
  271. package/dist/components/base/list/useList.d.ts +1 -1
  272. package/dist/components/base/popover/index.d.ts +2 -1
  273. package/dist/components/base/popover/usePopover.d.ts +9 -9
  274. package/dist/components/base/progress/index.d.ts +2 -1
  275. package/dist/components/base/progress/useProgress.d.ts +2 -2
  276. package/dist/components/base/rate/index.d.ts +2 -1
  277. package/dist/components/base/rate/useRate.d.ts +2 -2
  278. package/dist/components/base/select/useSelect.d.ts +8 -8
  279. package/dist/components/base/slider/index.d.ts +2 -1
  280. package/dist/components/base/slider/useSlider.d.ts +4 -4
  281. package/dist/components/base/steps/index.d.ts +1 -1
  282. package/dist/components/base/steps/useSteps.d.ts +5 -5
  283. package/dist/components/base/switch/index.d.ts +2 -1
  284. package/dist/components/base/switch/useSwitch.d.ts +8 -3
  285. package/dist/components/base/tabs/index.d.ts +1 -1
  286. package/dist/components/base/tabs/useTabs.d.ts +3 -3
  287. package/dist/components/base/tag/index.d.ts +1 -0
  288. package/dist/components/base/timePicker/useTimePicker.d.ts +14 -6
  289. package/dist/components/base/tooltip/index.d.ts +1 -1
  290. package/dist/components/base/tooltip/useTooltip.d.ts +15 -5
  291. package/dist/components/base/transfer/useTransfer.d.ts +15 -15
  292. package/dist/components/base/tree/index.d.ts +1 -1
  293. package/dist/components/base/tree/useTree.d.ts +2 -1
  294. package/dist/components/message/drawer/index.d.ts +2 -2
  295. package/dist/components/message/message/useMessage.d.ts +11 -1
  296. package/dist/components/message/notification/useNotification.d.ts +17 -1
  297. package/dist/components/message/popover/MPopover.d.ts +6 -1
  298. package/dist/components/message/popover/index.d.ts +1 -1
  299. package/dist/components/message/popover/usePopover.d.ts +6 -6
  300. package/dist/components/other/darkMode/useDarkMode.d.ts +1 -1
  301. package/dist/components/template/menu/index.d.ts +0 -1
  302. package/dist/components/template/menu/useMenu.d.ts +2 -1
  303. package/dist/components/template/pagination/index.d.ts +2 -1
  304. package/dist/components/template/virtualList/index.d.ts +0 -1
  305. package/dist/components/template/virtualList/useVirtualList.d.ts +10 -7
  306. package/dist/compositions/common/useDrag.d.ts +1 -1
  307. package/dist/compositions/common/useElementSize.d.ts +2 -2
  308. package/dist/compositions/common/useTeleport.d.ts +4 -2
  309. package/dist/compositions/modal/useModal.d.ts +1 -1
  310. package/dist/core.js +6147 -4186
  311. package/dist/runtime/defineHook.d.ts +1 -1
  312. package/index.css +1 -0
  313. package/index.ts +71 -0
  314. package/package.json +19 -16
  315. package/runtime/defineHook.ts +21 -0
  316. package/tools/empty.ts +81 -0
  317. package/tools/index.ts +15 -0
  318. package/tools/types.ts +11 -0
  319. package/tsconfig.json +8 -0
  320. package/types/common/common.d.ts +25 -0
  321. package/types/common/model.d.ts +25 -0
  322. package/types/index.d.ts +11 -0
  323. package/types/props.d.ts +13 -0
  324. package/vite.config.build.ts +41 -0
  325. package/dist/vite.config.build.d.ts +0 -2
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @description Transfer 组件核心 composable
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ *
9
+ * 核心逻辑:
10
+ * - 左右数据分割:data 按 modelValue(右侧 key 列表)分为左侧/右侧两列表
11
+ * - 勾选状态:leftChecked / rightChecked 分别维护左右侧勾选集合
12
+ * - 过滤:filterable 模式下各自按搜索词过滤
13
+ * - 穿梭操作:将勾选项从一侧移到另一侧,emit change 事件
14
+ */
15
+ import { computed, ref, toRef } from 'vue';
16
+ import { TransferItem, TransferProps } from './props';
17
+
18
+ export function useTransfer(props: TransferProps, ctx: any) {
19
+ const dataRef = toRef(() => props.data ?? []);
20
+ const modelValueRef = toRef(() => props.modelValue ?? []);
21
+
22
+ // --- 搜索关键词 ---
23
+ const leftSearch = ref('');
24
+ const rightSearch = ref('');
25
+
26
+ // --- 勾选状态(key 集合) ---
27
+ const leftChecked = ref<Set<string | number>>(new Set());
28
+ const rightChecked = ref<Set<string | number>>(new Set());
29
+
30
+ // --- 左右数据分割 ---
31
+
32
+ /** 右侧数据:key 在 modelValue 中的项 */
33
+ const rightData = computed<TransferItem[]>(() => {
34
+ const rightKeys = new Set(modelValueRef.value);
35
+ return dataRef.value.filter(item => rightKeys.has(item.key));
36
+ });
37
+
38
+ /** 左侧数据:key 不在 modelValue 中的项 */
39
+ const leftData = computed<TransferItem[]>(() => {
40
+ const rightKeys = new Set(modelValueRef.value);
41
+ return dataRef.value.filter(item => !rightKeys.has(item.key));
42
+ });
43
+
44
+ /** 过滤后的左侧列表 */
45
+ const filteredLeftData = computed<TransferItem[]>(() => {
46
+ if (!props.filterable || !leftSearch.value) return leftData.value;
47
+ const q = leftSearch.value.toLowerCase();
48
+ return leftData.value.filter(item => item.label.toLowerCase().includes(q));
49
+ });
50
+
51
+ /** 过滤后的右侧列表 */
52
+ const filteredRightData = computed<TransferItem[]>(() => {
53
+ if (!props.filterable || !rightSearch.value) return rightData.value;
54
+ const q = rightSearch.value.toLowerCase();
55
+ return rightData.value.filter(item => item.label.toLowerCase().includes(q));
56
+ });
57
+
58
+ // --- 全选状态 ---
59
+
60
+ /** 左侧是否全选(仅计算过滤后的可勾选项) */
61
+ const isLeftAllChecked = computed(() => {
62
+ const checkable = filteredLeftData.value.filter(item => !item.disabled);
63
+ return checkable.length > 0 && checkable.every(item => leftChecked.value.has(item.key));
64
+ });
65
+
66
+ /** 左侧是否半选 */
67
+ const isLeftIndeterminate = computed(() => {
68
+ const checkable = filteredLeftData.value.filter(item => !item.disabled);
69
+ const checkedCount = checkable.filter(item => leftChecked.value.has(item.key)).length;
70
+ return checkedCount > 0 && checkedCount < checkable.length;
71
+ });
72
+
73
+ /** 右侧是否全选 */
74
+ const isRightAllChecked = computed(() => {
75
+ const checkable = filteredRightData.value.filter(item => !item.disabled);
76
+ return checkable.length > 0 && checkable.every(item => rightChecked.value.has(item.key));
77
+ });
78
+
79
+ /** 右侧是否半选 */
80
+ const isRightIndeterminate = computed(() => {
81
+ const checkable = filteredRightData.value.filter(item => !item.disabled);
82
+ const checkedCount = checkable.filter(item => rightChecked.value.has(item.key)).length;
83
+ return checkedCount > 0 && checkedCount < checkable.length;
84
+ });
85
+
86
+ // --- 事件处理 ---
87
+
88
+ /** 切换左侧某项的勾选 */
89
+ const toggleLeftCheck = (key: string | number, disabled: boolean) => {
90
+ if (disabled) return;
91
+ const newSet = new Set(leftChecked.value);
92
+ if (newSet.has(key)) {
93
+ newSet.delete(key);
94
+ } else {
95
+ newSet.add(key);
96
+ }
97
+ leftChecked.value = newSet;
98
+ };
99
+
100
+ /** 切换右侧某项的勾选 */
101
+ const toggleRightCheck = (key: string | number, disabled: boolean) => {
102
+ if (disabled) return;
103
+ const newSet = new Set(rightChecked.value);
104
+ if (newSet.has(key)) {
105
+ newSet.delete(key);
106
+ } else {
107
+ newSet.add(key);
108
+ }
109
+ rightChecked.value = newSet;
110
+ };
111
+
112
+ /** 左侧全选 / 取消全选 */
113
+ const toggleLeftAll = () => {
114
+ const checkable = filteredLeftData.value.filter(item => !item.disabled);
115
+ if (isLeftAllChecked.value) {
116
+ // 取消勾选过滤后的项
117
+ const newSet = new Set(leftChecked.value);
118
+ checkable.forEach(item => newSet.delete(item.key));
119
+ leftChecked.value = newSet;
120
+ } else {
121
+ // 全选过滤后的项
122
+ const newSet = new Set(leftChecked.value);
123
+ checkable.forEach(item => newSet.add(item.key));
124
+ leftChecked.value = newSet;
125
+ }
126
+ };
127
+
128
+ /** 右侧全选 / 取消全选 */
129
+ const toggleRightAll = () => {
130
+ const checkable = filteredRightData.value.filter(item => !item.disabled);
131
+ if (isRightAllChecked.value) {
132
+ const newSet = new Set(rightChecked.value);
133
+ checkable.forEach(item => newSet.delete(item.key));
134
+ rightChecked.value = newSet;
135
+ } else {
136
+ const newSet = new Set(rightChecked.value);
137
+ checkable.forEach(item => newSet.add(item.key));
138
+ rightChecked.value = newSet;
139
+ }
140
+ };
141
+
142
+ /** 将左侧勾选项移到右侧(→ 方向) */
143
+ const moveToRight = () => {
144
+ if (!leftChecked.value.size) return;
145
+
146
+ // 要移动的 key 列表
147
+ const movedKeys = [...leftChecked.value];
148
+ // 新的右侧 key 列表 = 原右侧 + 移过来的
149
+ const newModelValue = [...modelValueRef.value, ...movedKeys];
150
+
151
+ ctx.emit('update:modelValue', newModelValue);
152
+ ctx.emit('change', newModelValue, 'right', movedKeys);
153
+
154
+ // 清空左侧勾选
155
+ leftChecked.value = new Set();
156
+ };
157
+
158
+ /** 将右侧勾选项移到左侧(← 方向) */
159
+ const moveToLeft = () => {
160
+ if (!rightChecked.value.size) return;
161
+
162
+ const movedKeys = [...rightChecked.value];
163
+ const movedSet = new Set(movedKeys);
164
+ // 新的右侧 key 列表 = 原右侧中去掉移走的
165
+ const newModelValue = modelValueRef.value.filter(key => !movedSet.has(key));
166
+
167
+ ctx.emit('update:modelValue', newModelValue);
168
+ ctx.emit('change', newModelValue, 'left', movedKeys);
169
+
170
+ // 清空右侧勾选
171
+ rightChecked.value = new Set();
172
+ };
173
+
174
+ /** 左侧是否有勾选项(控制按钮是否可用) */
175
+ const hasLeftChecked = computed(() => leftChecked.value.size > 0);
176
+
177
+ /** 右侧是否有勾选项 */
178
+ const hasRightChecked = computed(() => rightChecked.value.size > 0);
179
+
180
+ return {
181
+ // 搜索
182
+ leftSearch,
183
+ rightSearch,
184
+ // 数据
185
+ leftData,
186
+ rightData,
187
+ filteredLeftData,
188
+ filteredRightData,
189
+ // 勾选状态
190
+ leftChecked,
191
+ rightChecked,
192
+ hasLeftChecked,
193
+ hasRightChecked,
194
+ // 全选
195
+ isLeftAllChecked,
196
+ isLeftIndeterminate,
197
+ isRightAllChecked,
198
+ isRightIndeterminate,
199
+ // 事件
200
+ toggleLeftCheck,
201
+ toggleRightCheck,
202
+ toggleLeftAll,
203
+ toggleRightAll,
204
+ moveToRight,
205
+ moveToLeft,
206
+ };
207
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @description tree 运行时 props 定义
3
+ * @author 阿怪
4
+ * @date 2026/2/25
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { MCOPO, MPropType } from '../../types/props';
10
+ import { TreeNodeData, TreeNodeProps, TreeProps } from './props';
11
+ import { DEFAULT_CONFIG } from './tree';
12
+
13
+ /** 默认树配置(对外导出供使用方引用) */
14
+ export const DEFAULT_TREE_CONFIG = DEFAULT_CONFIG;
15
+
16
+ /** MTree 组件的 props 定义 */
17
+ export const props: MCOPO<TreeProps> = {
18
+ data: { type: [Object, Array], default: () => [] },
19
+ config: { type: Object, default: () => DEFAULT_CONFIG },
20
+ checkbox: { type: Boolean, default: false },
21
+ defaultExpandAll: { type: Boolean, default: false },
22
+ checkedKeys: { type: Array, default: () => [] },
23
+ checkStrictly: { type: Boolean, default: true },
24
+ };
25
+
26
+ /** MTreeNode 子组件的 props 定义 */
27
+ export const treeNodeProps: MCOPO<TreeNodeProps> = {
28
+ data: { type: Array, default: () => [] },
29
+ config: { type: Object, default: () => DEFAULT_CONFIG },
30
+ checkbox: { type: Boolean, default: false },
31
+ getNodesByKeys: {
32
+ type: Function as MPropType<(keys: TreeNodeData['key'][]) => TreeNodeData[]>,
33
+ required: true,
34
+ },
35
+ handleCheck: {
36
+ type: Function as MPropType<(node: TreeNodeData, check: boolean) => void>,
37
+ required: true,
38
+ },
39
+ handleExpand: {
40
+ type: Function as MPropType<(node: TreeNodeData, e: MouseEvent) => void>,
41
+ required: true,
42
+ },
43
+ handleItemClick: {
44
+ type: Function as MPropType<(node: TreeNodeData, e: MouseEvent) => void>,
45
+ required: true,
46
+ },
47
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @description tree core 导出
3
+ * @author 阿怪
4
+ * @date 2026/2/25
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { props, treeNodeProps } from './api';
10
+ import { useTree } from './useTree';
11
+
12
+ export const TreeCore = {
13
+ props,
14
+ treeNodeProps,
15
+ useTree,
16
+ };
17
+
18
+ export type {
19
+ TreeProps,
20
+ TreeData,
21
+ TreeNodeData,
22
+ TreeConfig,
23
+ TreeStatusKey,
24
+ TreeNodeProps,
25
+ } from './props';
26
+
27
+ export { useTree, fixKey } from './useTree';
28
+ export { default as Tree } from './tree';
29
+ export { DEFAULT_TREE_CONFIG, props as treeProps, treeNodeProps } from './api';
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @description tree 组件类型定义
3
+ * @author 阿怪
4
+ * @date 2026/2/25
5
+ * @version v1.0.0
6
+ *
7
+ * @name m-tree
8
+ * @docDescription Tree component with shuimo-ui style.
9
+ * 水墨组件的树组件。
10
+ * @docUrl https://shuimo.design/tree
11
+ */
12
+
13
+ export declare type TreeProps = {
14
+ /**
15
+ * @description 树数据
16
+ * @type TreeData | TreeData[]
17
+ * @default []
18
+ */
19
+ data?: TreeData | TreeData[];
20
+ /**
21
+ * @description 树配置项,用于字段映射
22
+ * @type TreeConfig
23
+ * @default { key:'key', label:'label', value:'value', children: 'children' }
24
+ */
25
+ config?: TreeConfig;
26
+ /**
27
+ * @description 是否使用 checkbox 模式
28
+ * @type boolean
29
+ * @default false
30
+ */
31
+ checkbox?: boolean;
32
+ /**
33
+ * @description 是否默认展开所有节点
34
+ * @type boolean
35
+ * @default false
36
+ */
37
+ defaultExpandAll?: boolean;
38
+ /**
39
+ * @description 选中的 key 列表(v-model:checkedKeys)
40
+ * @type Array<string|number>
41
+ * @default []
42
+ */
43
+ checkedKeys?: Array<string | number>;
44
+ /**
45
+ * @description 父子节点选中状态是否关联(true 表示关联)
46
+ * @type boolean
47
+ * @default true
48
+ */
49
+ checkStrictly?: boolean;
50
+ };
51
+
52
+ export interface TreeData {
53
+ key: string | number;
54
+ label?: string;
55
+ value?: string | number;
56
+ children?: TreeData[];
57
+ disabled?: boolean;
58
+ [k: string]: unknown;
59
+ }
60
+
61
+ export interface TreeNodeData extends TreeData {
62
+ /** checkbox 选中状态 */
63
+ checked?: boolean;
64
+ /** 半选状态(部分子节点选中) */
65
+ indeterminate?: boolean;
66
+ /** 点击选中状态 */
67
+ selected?: boolean;
68
+ /** 展开状态 */
69
+ expand?: boolean;
70
+ /** 收起状态 */
71
+ close?: boolean;
72
+ /** 子节点列表 */
73
+ children?: TreeNodeData[];
74
+ /** 父节点引用 */
75
+ parent?: TreeNodeData | null;
76
+ /** 是否为根节点 */
77
+ isRoot?: boolean;
78
+ }
79
+
80
+ export type TreeStatusKey = 'checked' | 'selected' | 'expand' | 'close';
81
+
82
+ export interface TreeConfig {
83
+ key: string | number;
84
+ label: string;
85
+ value: string;
86
+ children: string;
87
+ expand?: string;
88
+ }
89
+
90
+ /**
91
+ * MTreeNode 子组件的 props 类型
92
+ */
93
+ export declare type TreeNodeProps = {
94
+ /** 当前层节点数据列表 */
95
+ data?: TreeNodeData[];
96
+ /** 字段映射配置 */
97
+ config?: TreeConfig;
98
+ /** 是否使用 checkbox */
99
+ checkbox?: boolean;
100
+ /** 根据 key 批量获取节点 */
101
+ getNodesByKeys: (keys: (string | number)[]) => TreeNodeData[];
102
+ /** 切换展开/收起 */
103
+ handleExpand: (node: TreeNodeData, e: MouseEvent) => void;
104
+ /** 切换 checkbox 选中 */
105
+ handleCheck: (node: TreeNodeData, checked: boolean) => void;
106
+ /** 节点点击 */
107
+ handleItemClick: (node: TreeNodeData, e: MouseEvent) => void;
108
+ };
@@ -0,0 +1,263 @@
1
+ /**
2
+ * @description Tree 核心数据结构类,负责树节点的构建、缓存和状态管理
3
+ * @author 阿怪
4
+ * @date 2026/2/25
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { TreeConfig, TreeData, TreeNodeData, TreeStatusKey } from './props';
10
+
11
+ /** 节点缓存 Map 类型 */
12
+ export type TreeDataMap = Map<TreeNodeData['key'], TreeNodeData>;
13
+
14
+ /** 默认字段映射配置 */
15
+ export const DEFAULT_CONFIG: TreeConfig = {
16
+ key: 'key',
17
+ label: 'label',
18
+ value: 'value',
19
+ children: 'children',
20
+ expand: 'expand',
21
+ };
22
+
23
+ /** 合并用户配置与默认配置 */
24
+ export const mergeConfig = (config: Partial<TreeConfig>): TreeConfig => {
25
+ if (config === DEFAULT_CONFIG) {
26
+ return DEFAULT_CONFIG;
27
+ }
28
+ return {
29
+ key: config.key ?? DEFAULT_CONFIG.key,
30
+ label: config.label ?? DEFAULT_CONFIG.label,
31
+ value: config.value ?? DEFAULT_CONFIG.value,
32
+ children: config.children ?? DEFAULT_CONFIG.children,
33
+ expand: config.expand ?? DEFAULT_CONFIG.expand,
34
+ };
35
+ };
36
+
37
+ export interface TreeNodeOptions {
38
+ data: TreeData | TreeData[];
39
+ config?: TreeConfig;
40
+ defaultExpandAll?: boolean;
41
+ checkStrictly?: boolean;
42
+ }
43
+
44
+ export interface TreeAttrs {
45
+ defaultExpandAll: boolean;
46
+ checkStrictly: boolean;
47
+ }
48
+
49
+ export default class Tree {
50
+ /** 源数据(已深拷贝) */
51
+ #source: TreeNodeData[];
52
+ /** 节点缓存 Map,key 为节点标识 */
53
+ #cacheMap: TreeDataMap;
54
+ /** 字段映射配置 */
55
+ #config: TreeConfig = DEFAULT_CONFIG;
56
+ /** 初始化配置(defaultExpandAll / checkStrictly) */
57
+ #initialConfig: TreeAttrs;
58
+
59
+ constructor(options: TreeNodeOptions) {
60
+ const {
61
+ data,
62
+ config = DEFAULT_CONFIG,
63
+ defaultExpandAll = false,
64
+ checkStrictly = true,
65
+ } = options;
66
+ this.#cacheMap = new Map<TreeNodeData['key'], TreeNodeData>();
67
+ this.#source = Array.isArray(data) ? data : [data];
68
+ this.#config = mergeConfig(config);
69
+ this.#initialConfig = { defaultExpandAll, checkStrictly };
70
+ this.#init();
71
+ }
72
+
73
+ /** 暴露配置,供组件层读取 */
74
+ get config() {
75
+ return this.#config;
76
+ }
77
+
78
+ /** 递归生成单个 TreeNodeData,并写入 cacheMap */
79
+ #genTreeNodeData(data: TreeData, parentKey?: TreeData['key']): TreeNodeData {
80
+ const { defaultExpandAll } = this.#initialConfig;
81
+ const { key: k, children: c, expand: e } = this.#config;
82
+
83
+ const node: TreeNodeData = {
84
+ ...data,
85
+ expand: defaultExpandAll || !!(data[e!] as boolean | undefined),
86
+ selected: false,
87
+ checked: false,
88
+ indeterminate: false,
89
+ parent: parentKey ? this.#cacheMap.get(parentKey) : undefined,
90
+ isRoot: !parentKey,
91
+ };
92
+
93
+ // 使用节点自身的 key 字段值作为 Map 键
94
+ this.#cacheMap.set(node[k] as TreeNodeData['key'], node);
95
+
96
+ // 递归处理子节点
97
+ const nodeChildren = node[c] as TreeNodeData[] | undefined;
98
+ if (nodeChildren && nodeChildren.length > 0) {
99
+ node[c] = this.#genCacheMap(nodeChildren, node[k] as TreeData['key']) as unknown;
100
+ }
101
+
102
+ return node;
103
+ }
104
+
105
+ /** 清空缓存 */
106
+ #clearCache() {
107
+ this.#cacheMap.clear();
108
+ }
109
+
110
+ /** 初始化:清空缓存后重新构建 */
111
+ #init() {
112
+ this.#clearCache();
113
+ this.#genCacheMap();
114
+ }
115
+
116
+ /** 递归批量构建节点并填充 cacheMap */
117
+ #genCacheMap(data: TreeNodeData[] = this.#source, parentKey?: TreeData['key']): TreeNodeData[] {
118
+ const len = data.length;
119
+ const treeNodeData: TreeNodeData[] = [];
120
+ for (let i = 0; i < len; i++) {
121
+ const treeNode = this.#genTreeNodeData(data[i], parentKey);
122
+ treeNodeData.push(treeNode);
123
+ }
124
+ return treeNodeData;
125
+ }
126
+
127
+ /** 获取所有缓存节点列表 */
128
+ #getCacheList(): TreeNodeData[] {
129
+ return Array.from(this.#cacheMap.values());
130
+ }
131
+
132
+ /**
133
+ * 向上递归更新父节点的选中/半选状态
134
+ * 规则:所有子节点选中 → 父节点选中;部分选中或有半选 → 父节点半选
135
+ */
136
+ #setParentChecked(parent: TreeNodeData) {
137
+ if (parent.disabled) {
138
+ return;
139
+ }
140
+ const { children: c } = this.#config;
141
+ const children: TreeNodeData[] = (parent[c] as TreeNodeData[]) ?? [];
142
+ const allChecked = children.every(child => child.checked);
143
+ const oneChecked = children.some(child => child.checked);
144
+ const onIndeterminate = children.some(child => child.indeterminate);
145
+
146
+ if (allChecked) {
147
+ parent.indeterminate = false;
148
+ parent.checked = true;
149
+ } else {
150
+ parent.indeterminate = oneChecked || onIndeterminate;
151
+ parent.checked = false;
152
+ }
153
+
154
+ // 继续向上传播
155
+ if (parent.parent) {
156
+ this.#setParentChecked(parent.parent);
157
+ }
158
+ }
159
+
160
+ /** 递归设置子节点的某个状态字段 */
161
+ #setChildrenStatus(statusKey: TreeStatusKey, nodes: TreeNodeData[], value: boolean) {
162
+ const { children: c } = this.#config;
163
+ nodes.forEach(node => {
164
+ if (node.disabled) {
165
+ return;
166
+ }
167
+ (node as Record<string, unknown>)[statusKey] = value;
168
+ const nodeChildren = node[c] as TreeNodeData[] | undefined;
169
+ if (nodeChildren) {
170
+ this.#setChildrenStatus(statusKey, nodeChildren, value);
171
+ }
172
+ });
173
+ }
174
+
175
+ // ==================== 公开方法 ====================
176
+
177
+ /** 获取某节点的直接子节点 key 列表 */
178
+ getChildrenKeys(node: TreeNodeData): TreeNodeData['key'][] {
179
+ const { children: c, key: k } = this.#config;
180
+ const children = node[c] as TreeNodeData[] | undefined;
181
+ if (children) {
182
+ return children.map((it) => it[k] as TreeNodeData['key']);
183
+ }
184
+ return [];
185
+ }
186
+
187
+ /** 根据 key 数组批量获取节点 */
188
+ getNodesByKeys(keys: TreeNodeData['key'][]): TreeNodeData[] {
189
+ return keys.map(key => this.#cacheMap.get(key)!);
190
+ }
191
+
192
+ /**
193
+ * 获取树形数据
194
+ * @param keys 若传入则返回对应 key 的节点列表,否则返回所有根节点
195
+ */
196
+ getTreeData(keys?: TreeNodeData['key'][]): TreeNodeData[] {
197
+ if (keys) {
198
+ return this.getNodesByKeys(keys);
199
+ }
200
+ return this.#getCacheList().filter(it => it.isRoot);
201
+ }
202
+
203
+ /** 获取当前所有选中 key 和展开 key */
204
+ getKeys(): { checkedKeys: TreeNodeData['key'][]; expandKeys: TreeNodeData['key'][] } {
205
+ const expandKeys: TreeNodeData['key'][] = [];
206
+ const checkedKeys: TreeNodeData['key'][] = [];
207
+ const iterator = this.#cacheMap[Symbol.iterator]();
208
+ let next = iterator.next();
209
+ while (!next.done) {
210
+ const [, node] = next.value;
211
+ const key = Reflect.get(node as Record<string, unknown>, this.#config.key as string) as TreeNodeData['key'];
212
+ // 排除半选状态
213
+ if (node.checked && !node.indeterminate) {
214
+ checkedKeys.push(key);
215
+ }
216
+ if (node.children && node.expand) {
217
+ expandKeys.push(key);
218
+ }
219
+ next = iterator.next();
220
+ }
221
+ return { checkedKeys, expandKeys };
222
+ }
223
+
224
+ /** 切换节点展开/收起状态 */
225
+ toggleExpand(node: TreeNodeData, value?: boolean) {
226
+ if (value !== undefined) {
227
+ node.expand = value;
228
+ } else {
229
+ node.expand = !node.expand;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * 设置节点 checkbox 选中状态
235
+ * checkStrictly=true(默认)时级联更新子节点和父节点状态
236
+ */
237
+ setNodeCheckbox(node: TreeNodeData, checked: boolean) {
238
+ node.checked = checked;
239
+ node.indeterminate = false;
240
+ const { checkStrictly } = this.#initialConfig;
241
+ if (!checkStrictly) {
242
+ return;
243
+ }
244
+ // 向下级联:设置所有子孙节点
245
+ if (node.children) {
246
+ this.#setChildrenStatus('checked', node.children, checked);
247
+ }
248
+ // 向上级联:更新父节点的半选/全选状态
249
+ if (node.parent) {
250
+ this.#setParentChecked(node.parent);
251
+ }
252
+ }
253
+
254
+ /** 根据 key 数组批量设置节点为选中状态 */
255
+ setCheckedByKeys(keys: TreeNodeData['key'][]) {
256
+ const nodes = this.getNodesByKeys(keys);
257
+ nodes.forEach(node => {
258
+ if (node) {
259
+ this.setNodeCheckbox(node, true);
260
+ }
261
+ });
262
+ }
263
+ }