@veltra/desktop 1.7.16 → 1.7.17
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/dist/components/multi-tree-select/multi-tree-select.js +8 -2
- package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
- package/dist/components/tree/tree-node.js +1 -1
- package/dist/components/tree/tree-node.js.map +1 -1
- package/dist/components/tree/tree.js +3 -6
- package/dist/components/tree/tree.js.map +1 -1
- package/dist/components/tree/use-check.js +32 -9
- package/dist/components/tree/use-check.js.map +1 -1
- package/package.json +1 -1
- package/src/components/multi-tree-select/__test__/multi-tree-select.test.ts +222 -0
- package/src/components/multi-tree-select/multi-tree-select.vue +3 -1
- package/src/components/tree/tree-node.vue +1 -1
- package/src/components/tree/tree.vue +8 -6
- package/src/components/tree/use-check.ts +63 -33
|
@@ -55,8 +55,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
55
55
|
childrenKey: {},
|
|
56
56
|
data: {},
|
|
57
57
|
disabledNode: {},
|
|
58
|
-
checkOnClickNode: {
|
|
59
|
-
|
|
58
|
+
checkOnClickNode: {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
default: true
|
|
61
|
+
},
|
|
62
|
+
checkStrictly: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false
|
|
65
|
+
},
|
|
60
66
|
slots: {},
|
|
61
67
|
scrollToView: { type: Boolean }
|
|
62
68
|
}, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi-tree-select.js","names":["$attrs"],"sources":["../../../src/components/multi-tree-select/multi-tree-select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n :class=\"[cls.b, cls.m(size), bem.is('disabled', disabled), bem.is('focus', dropdownVisible)]\"\n trigger=\"click\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n :disabled=\"disabled\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n ref=\"dropdownRef\"\n :min-width=\"minWidth\"\n :width=\"width\"\n @update:visible=\"handleDropdownVisible\"\n >\n <template #trigger>\n <!-- 默认展示 -->\n <span :class=\"cls.e('placeholder')\" v-if=\"!filterable && !tags.length\">\n {{ placeholder }}\n </span>\n\n <!-- 选择的数据项 -->\n <div v-if=\"tags.length || filterable\" :class=\"cls.e('tags')\">\n <u-tag\n v-for=\"(tag, index) in visibleTags\"\n :key=\"tag[valueKey]\"\n :closable=\"!disabled\"\n @close=\"handleRemove(index)\"\n >\n {{ tag[labelKey] }}\n </u-tag>\n <u-tag v-if=\"hiddenCount > 0\">+{{ hiddenCount }}</u-tag>\n\n <!-- 内嵌查询输入框:承载过滤 -->\n <input\n v-if=\"filterable\"\n ref=\"inputRef\"\n :class=\"cls.e('input')\"\n v-model=\"qs\"\n :placeholder=\"tags.length ? '' : placeholder\"\n :disabled=\"disabled\"\n @click.stop=\"handleInputClick\"\n @focus=\"handleInputFocus\"\n />\n </div>\n <!-- 清空 icon -->\n <transition name=\"zoom-in\" mode=\"out-in\">\n <u-icon v-if=\"showClear\" :class=\"cls.e('clear')\" @click.stop=\"handleClear\">\n <Close />\n </u-icon>\n\n <!-- 下拉 icon -->\n <u-icon :class=\"cls.e(`arrow`)\" v-else-if=\"!readonly\">\n <ArrowDown />\n </u-icon>\n </transition>\n </template>\n <template #content>\n <div :class=\"[cls.e('content-header'), cls.m(size)]\">\n <!-- 全选 -->\n <u-checkbox\n :model-value=\"allChecked\"\n :indeterminate=\"indeterminate\"\n @update:model-value=\"handleCheckAll\"\n >\n 全选\n </u-checkbox>\n\n <u-button size=\"small\" text type=\"primary\" @click=\"handleToggleExpandAll\">\n {{ allExpanded ? '收起全部' : '展开全部' }}\n </u-button>\n </div>\n <!-- 菜单列表 -->\n <u-tree\n v-bind=\"treeProps\"\n v-model:checked=\"model\"\n @update:checked=\"handleCheck\"\n ref=\"treeRef\"\n :class=\"cls.e('content-tree')\"\n checkable\n :data=\"data\"\n :slots=\"slots\"\n scroll-to-view\n ></u-tree>\n </template>\n </u-dropdown>\n\n <div v-else-if=\"model.length\" :class=\"[cls.m(size), cls.e('readonly-tags')]\">\n <div :class=\"cls.e('tags')\">\n <u-tag v-for=\"option of tags\" :key=\"option[valueKey]\">\n {{ option[labelKey] }}\n </u-tag>\n </div>\n </div>\n\n <template v-else>\n {{ FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { dfs, o } from '@cat-kit/core'\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { MultiTreeSelectProps, MultiTreeSelectEmits, TreeExposed } from '../../types'\nimport type { DropdownExposed } from '../../types'\nimport { UButton } from '../button'\nimport { UCheckbox } from '../checkbox'\nimport { UDropdown } from '../dropdown'\nimport { UIcon } from '../icon'\nimport { UTag } from '../tag'\nimport { UTree } from '../tree'\nimport type { TreeSlotsScope } from '../tree/di'\n\ndefineOptions({ name: 'UMultiTreeSelect', inheritAttrs: false })\n\nconst cls = bem('multi-tree-select')\n\nconst props = withDefaults(defineProps<MultiTreeSelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n expandAll: false,\n clearable: true,\n disabled: undefined,\n readonly: undefined,\n filterable: false,\n visibilityLimit: 3,\n minWidth: '280px'\n})\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst treeProps = computed(() => {\n return o(props as Record<string, any>).omit([\n 'tips',\n 'field',\n 'placeholder',\n 'disabled',\n 'label',\n 'readonly',\n 'modelValue',\n 'contentClass',\n 'contentStyle',\n 'minWidth',\n 'width'\n ])\n})\n\nconst emit = defineEmits<MultiTreeSelectEmits>()\n\nconst slots = defineSlots<{ default?: (props: TreeSlotsScope) => any }>()\n\n/**过滤 */\nconst qs = shallowRef('')\nwatch(qs, (qs) => {\n treeRef.value?.filter(qs)\n})\n\nconst model = defineModel<(string | number)[]>({ default: () => [] })\n\nconst hovered = shallowRef(false)\n\nconst tags = shallowRef<Record<string, any>[]>([])\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst showClear = computed(() => {\n return props.clearable && model.value?.length && hovered.value && !disabled.value\n})\n\nconst treeRef = shallowRef<TreeExposed>()\n\nconst dropdownRef = shallowRef<DropdownExposed>()\n\nlet changedByEvent = false\n\nfunction markEvent() {\n changedByEvent = true\n nextTick(() => {\n changedByEvent = false\n })\n}\n\n/**是否全选 */\nconst allChecked = computed(() => {\n return model.value?.length === treeRef.value?.forest.size\n})\n\n/**部分 */\nconst indeterminate = computed(() => {\n return model.value.length! > 0 && !allChecked.value\n})\n\n/** 全选*/\nconst handleCheckAll = (checked: boolean) => {\n treeRef.value?.checkAll(checked)\n\n dropdownRef.value?.updateDropdown()\n}\n\n/**选中 */\nconst handleCheck = (checked: (string | number)[], checkedData: Record<string, any>[]) => {\n markEvent()\n tags.value = checkedData\n emit('change', checkedData!)\n\n dropdownRef.value?.updateDropdown()\n}\n\n/**删除 */\nconst handleRemove = (index: number) => {\n markEvent()\n tags.value = tags.value.filter((_, i) => i !== index)\n model.value = model.value.filter((_, i) => i !== index)\n emit('change', tags.value)\n}\n\n/**清空 */\nconst handleClear = () => {\n markEvent()\n tags.value = []\n model.value = []\n emit('clear')\n}\n\nconst keyDicts = shallowRef(new Map<string | number, Record<string, any>>())\n\nwatch(\n () => props.data,\n (data) => {\n if (!data?.length) {\n keyDicts.value = new Map()\n } else {\n const newDict = new Map()\n data.forEach((item) => {\n dfs(\n item,\n (v) => {\n newDict.set(v[valueKey.value], v)\n },\n props.childrenKey\n )\n })\n keyDicts.value = newDict\n }\n },\n { immediate: true }\n)\n\nwatch(\n [keyDicts, model],\n ([keyDicts, model]) => {\n if (changedByEvent) return\n\n if (!keyDicts.size || !model?.length) {\n tags.value = []\n return\n }\n\n tags.value = model.filter((v) => keyDicts.has(v)).map((v) => keyDicts.get(v)!)\n },\n { immediate: true }\n)\n\nconst allExpanded = shallowRef(props.expandAll)\nfunction handleToggleExpandAll() {\n allExpanded.value = !allExpanded.value\n allExpanded.value ? treeRef.value?.expandAll() : treeRef.value?.collapseAll()\n}\n\nwatch(treeRef, (treeRef) => {\n if (!treeRef) {\n allExpanded.value = props.expandAll\n }\n})\n\nconst limit = () => {\n let { visibilityLimit } = props\n\n if (visibilityLimit < 0) {\n visibilityLimit = 0\n }\n if (disabled.value || readonly.value) {\n visibilityLimit = model.value?.length ?? 0\n }\n return visibilityLimit\n}\n\nconst visibleTags = computed(() => {\n if (tags.value.length > limit()) {\n return tags.value.slice(0, limit())\n }\n return tags.value\n})\n\nconst hiddenCount = computed(() => {\n if (tags.value.length > limit()) {\n return tags.value.length - limit()\n }\n return 0\n})\n\nconst dropdownVisible = shallowRef(false)\n\nconst inputRef = useTemplateRef<HTMLInputElement>('inputRef')\n\nconst handleDropdownVisible = (visible: boolean) => {\n dropdownVisible.value = visible\n if (!visible) {\n qs.value = ''\n }\n}\n\n/**\n * 拦截输入区域的点击(@click.stop):阻止 dropdown 的 trigger 开合切换,\n * 保持面板展开以不中断输入。\n */\nconst handleInputClick = () => {\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nconst handleInputFocus = () => {\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"multi-tree-select.js","names":["$attrs"],"sources":["../../../src/components/multi-tree-select/multi-tree-select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n :class=\"[cls.b, cls.m(size), bem.is('disabled', disabled), bem.is('focus', dropdownVisible)]\"\n trigger=\"click\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n :disabled=\"disabled\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n ref=\"dropdownRef\"\n :min-width=\"minWidth\"\n :width=\"width\"\n @update:visible=\"handleDropdownVisible\"\n >\n <template #trigger>\n <!-- 默认展示 -->\n <span :class=\"cls.e('placeholder')\" v-if=\"!filterable && !tags.length\">\n {{ placeholder }}\n </span>\n\n <!-- 选择的数据项 -->\n <div v-if=\"tags.length || filterable\" :class=\"cls.e('tags')\">\n <u-tag\n v-for=\"(tag, index) in visibleTags\"\n :key=\"tag[valueKey]\"\n :closable=\"!disabled\"\n @close=\"handleRemove(index)\"\n >\n {{ tag[labelKey] }}\n </u-tag>\n <u-tag v-if=\"hiddenCount > 0\">+{{ hiddenCount }}</u-tag>\n\n <!-- 内嵌查询输入框:承载过滤 -->\n <input\n v-if=\"filterable\"\n ref=\"inputRef\"\n :class=\"cls.e('input')\"\n v-model=\"qs\"\n :placeholder=\"tags.length ? '' : placeholder\"\n :disabled=\"disabled\"\n @click.stop=\"handleInputClick\"\n @focus=\"handleInputFocus\"\n />\n </div>\n <!-- 清空 icon -->\n <transition name=\"zoom-in\" mode=\"out-in\">\n <u-icon v-if=\"showClear\" :class=\"cls.e('clear')\" @click.stop=\"handleClear\">\n <Close />\n </u-icon>\n\n <!-- 下拉 icon -->\n <u-icon :class=\"cls.e(`arrow`)\" v-else-if=\"!readonly\">\n <ArrowDown />\n </u-icon>\n </transition>\n </template>\n <template #content>\n <div :class=\"[cls.e('content-header'), cls.m(size)]\">\n <!-- 全选 -->\n <u-checkbox\n :model-value=\"allChecked\"\n :indeterminate=\"indeterminate\"\n @update:model-value=\"handleCheckAll\"\n >\n 全选\n </u-checkbox>\n\n <u-button size=\"small\" text type=\"primary\" @click=\"handleToggleExpandAll\">\n {{ allExpanded ? '收起全部' : '展开全部' }}\n </u-button>\n </div>\n <!-- 菜单列表 -->\n <u-tree\n v-bind=\"treeProps\"\n v-model:checked=\"model\"\n @update:checked=\"handleCheck\"\n ref=\"treeRef\"\n :class=\"cls.e('content-tree')\"\n checkable\n :data=\"data\"\n :slots=\"slots\"\n scroll-to-view\n ></u-tree>\n </template>\n </u-dropdown>\n\n <div v-else-if=\"model.length\" :class=\"[cls.m(size), cls.e('readonly-tags')]\">\n <div :class=\"cls.e('tags')\">\n <u-tag v-for=\"option of tags\" :key=\"option[valueKey]\">\n {{ option[labelKey] }}\n </u-tag>\n </div>\n </div>\n\n <template v-else>\n {{ FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { dfs, o } from '@cat-kit/core'\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { MultiTreeSelectProps, MultiTreeSelectEmits, TreeExposed } from '../../types'\nimport type { DropdownExposed } from '../../types'\nimport { UButton } from '../button'\nimport { UCheckbox } from '../checkbox'\nimport { UDropdown } from '../dropdown'\nimport { UIcon } from '../icon'\nimport { UTag } from '../tag'\nimport { UTree } from '../tree'\nimport type { TreeSlotsScope } from '../tree/di'\n\ndefineOptions({ name: 'UMultiTreeSelect', inheritAttrs: false })\n\nconst cls = bem('multi-tree-select')\n\nconst props = withDefaults(defineProps<MultiTreeSelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n expandAll: false,\n clearable: true,\n disabled: undefined,\n readonly: undefined,\n filterable: false,\n visibilityLimit: 3,\n minWidth: '280px',\n checkStrictly: false,\n checkOnClickNode: true\n})\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst treeProps = computed(() => {\n return o(props as Record<string, any>).omit([\n 'tips',\n 'field',\n 'placeholder',\n 'disabled',\n 'label',\n 'readonly',\n 'modelValue',\n 'contentClass',\n 'contentStyle',\n 'minWidth',\n 'width'\n ])\n})\n\nconst emit = defineEmits<MultiTreeSelectEmits>()\n\nconst slots = defineSlots<{ default?: (props: TreeSlotsScope) => any }>()\n\n/**过滤 */\nconst qs = shallowRef('')\nwatch(qs, (qs) => {\n treeRef.value?.filter(qs)\n})\n\nconst model = defineModel<(string | number)[]>({ default: () => [] })\n\nconst hovered = shallowRef(false)\n\nconst tags = shallowRef<Record<string, any>[]>([])\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst showClear = computed(() => {\n return props.clearable && model.value?.length && hovered.value && !disabled.value\n})\n\nconst treeRef = shallowRef<TreeExposed>()\n\nconst dropdownRef = shallowRef<DropdownExposed>()\n\nlet changedByEvent = false\n\nfunction markEvent() {\n changedByEvent = true\n nextTick(() => {\n changedByEvent = false\n })\n}\n\n/**是否全选 */\nconst allChecked = computed(() => {\n return model.value?.length === treeRef.value?.forest.size\n})\n\n/**部分 */\nconst indeterminate = computed(() => {\n return model.value.length! > 0 && !allChecked.value\n})\n\n/** 全选*/\nconst handleCheckAll = (checked: boolean) => {\n treeRef.value?.checkAll(checked)\n\n dropdownRef.value?.updateDropdown()\n}\n\n/**选中 */\nconst handleCheck = (checked: (string | number)[], checkedData: Record<string, any>[]) => {\n markEvent()\n tags.value = checkedData\n emit('change', checkedData!)\n\n dropdownRef.value?.updateDropdown()\n}\n\n/**删除 */\nconst handleRemove = (index: number) => {\n markEvent()\n tags.value = tags.value.filter((_, i) => i !== index)\n model.value = model.value.filter((_, i) => i !== index)\n emit('change', tags.value)\n}\n\n/**清空 */\nconst handleClear = () => {\n markEvent()\n tags.value = []\n model.value = []\n emit('clear')\n}\n\nconst keyDicts = shallowRef(new Map<string | number, Record<string, any>>())\n\nwatch(\n () => props.data,\n (data) => {\n if (!data?.length) {\n keyDicts.value = new Map()\n } else {\n const newDict = new Map()\n data.forEach((item) => {\n dfs(\n item,\n (v) => {\n newDict.set(v[valueKey.value], v)\n },\n props.childrenKey\n )\n })\n keyDicts.value = newDict\n }\n },\n { immediate: true }\n)\n\nwatch(\n [keyDicts, model],\n ([keyDicts, model]) => {\n if (changedByEvent) return\n\n if (!keyDicts.size || !model?.length) {\n tags.value = []\n return\n }\n\n tags.value = model.filter((v) => keyDicts.has(v)).map((v) => keyDicts.get(v)!)\n },\n { immediate: true }\n)\n\nconst allExpanded = shallowRef(props.expandAll)\nfunction handleToggleExpandAll() {\n allExpanded.value = !allExpanded.value\n allExpanded.value ? treeRef.value?.expandAll() : treeRef.value?.collapseAll()\n}\n\nwatch(treeRef, (treeRef) => {\n if (!treeRef) {\n allExpanded.value = props.expandAll\n }\n})\n\nconst limit = () => {\n let { visibilityLimit } = props\n\n if (visibilityLimit < 0) {\n visibilityLimit = 0\n }\n if (disabled.value || readonly.value) {\n visibilityLimit = model.value?.length ?? 0\n }\n return visibilityLimit\n}\n\nconst visibleTags = computed(() => {\n if (tags.value.length > limit()) {\n return tags.value.slice(0, limit())\n }\n return tags.value\n})\n\nconst hiddenCount = computed(() => {\n if (tags.value.length > limit()) {\n return tags.value.length - limit()\n }\n return 0\n})\n\nconst dropdownVisible = shallowRef(false)\n\nconst inputRef = useTemplateRef<HTMLInputElement>('inputRef')\n\nconst handleDropdownVisible = (visible: boolean) => {\n dropdownVisible.value = visible\n if (!visible) {\n qs.value = ''\n }\n}\n\n/**\n * 拦截输入区域的点击(@click.stop):阻止 dropdown 的 trigger 开合切换,\n * 保持面板展开以不中断输入。\n */\nconst handleInputClick = () => {\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nconst handleInputFocus = () => {\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n</script>\n"],"mappings":"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"}
|
|
@@ -77,7 +77,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
77
77
|
key: 0,
|
|
78
78
|
class: normalizeClass(unref(cls).e("checkbox")),
|
|
79
79
|
"model-value": __props.node.checked,
|
|
80
|
-
indeterminate: __props.node.indeterminate,
|
|
80
|
+
indeterminate: !unref(treeProps).checkStrictly && __props.node.indeterminate,
|
|
81
81
|
disabled: __props.node.disabled,
|
|
82
82
|
onChange: handleChange,
|
|
83
83
|
onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"]))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-node.js","names":[],"sources":["../../../src/components/tree/tree-node.vue"],"sourcesContent":["<template>\n <div\n :class=\"[cls.e('node'), bem.is('expanded', node.expanded), bem.is('disabled', node.disabled)]\"\n :ref=\"measureRef\"\n >\n <span v-for=\"i in node.depth\" :key=\"i\" :class=\"cls.e('indent')\" aria-hidden=\"true\"></span>\n\n <u-icon v-if=\"!node.isLeaf\" :class=\"cls.e('expand-icon')\" @click.stop=\"toggleExpand\">\n <ArrowRight />\n </u-icon>\n <u-icon v-else :class=\"cls.e('leaf-icon')\" aria-hidden=\"true\">\n <Dot />\n </u-icon>\n\n <div\n :class=\"cls.e('node-content')\"\n @click=\"handleClick\"\n @contextmenu=\"treeEmit('node-contextmenu', $event, node)\"\n v-ripple=\"\n (treeProps.selectable || (treeProps.checkable && treeProps.checkOnClickNode)) &&\n !node.disabled\n ? cls.e('ripple')\n : false\n \"\n >\n <u-checkbox\n v-if=\"treeProps.checkable\"\n :class=\"cls.e('checkbox')\"\n :model-value=\"node.checked\"\n :indeterminate=\"node.indeterminate\"\n :disabled=\"node.disabled\"\n @change=\"handleChange\"\n @click.stop\n />\n\n <u-node-render :content=\"getTreeSlotsNode({ node, data: node.data })\" />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { vRipple } from '@veltra/directives'\nimport { ArrowRight, Dot } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject } from 'vue'\n\nimport type { TreeNodeProps } from '../../types'\nimport UCheckbox from '../checkbox/checkbox.vue'\nimport { UIcon } from '../icon'\nimport { UNodeRender } from '../node-render'\nimport { TreeDIKey } from './di'\n\ndefineOptions({ name: 'UTreeNode' })\n\nconst props = defineProps<TreeNodeProps>()\n\nconst { treeProps, treeEmit, cls, getTreeSlotsNode, getFlattedNodes, toggleCheck, handleSelect } =\n inject(TreeDIKey)!\n\nfunction measureRef(el: unknown) {\n if (typeof props.index !== 'number' || !props.measureElement) return\n props.measureElement(props.index, el as Element | null)\n}\n\nfunction toggleExpand() {\n const { node } = props\n node.expanded = !node.expanded\n getFlattedNodes()\n\n treeEmit('expand', node)\n}\n\nfunction handleChange(checked: boolean, e: MouseEvent) {\n toggleCheck(props.node, checked, e.ctrlKey)\n}\n\nfunction handleClick(e: MouseEvent) {\n const { node } = props\n treeEmit('node-click', node)\n\n treeProps.selectable && handleSelect(node)\n treeProps.expandOnClickNode && toggleExpand()\n treeProps.checkable &&\n treeProps.checkOnClickNode &&\n !node.disabled &&\n !treeProps.expandOnClickNode &&\n toggleCheck(node, !node.checked, e.ctrlKey)\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAsDA,MAAM,QAAQ;EAEd,MAAM,EAAE,WAAW,UAAU,KAAK,kBAAkB,iBAAiB,aAAa,iBAChF,OAAO,SAAS;EAElB,SAAS,WAAW,IAAa;GAC/B,IAAI,OAAO,MAAM,UAAU,YAAY,CAAC,MAAM,gBAAgB;GAC9D,MAAM,eAAe,MAAM,OAAO,EAAoB;EACxD;EAEA,SAAS,eAAe;GACtB,MAAM,EAAE,SAAS;GACjB,KAAK,WAAW,CAAC,KAAK;GACtB,gBAAgB;GAEhB,SAAS,UAAU,IAAI;EACzB;EAEA,SAAS,aAAa,SAAkB,GAAe;GACrD,YAAY,MAAM,MAAM,SAAS,EAAE,OAAO;EAC5C;EAEA,SAAS,YAAY,GAAe;GAClC,MAAM,EAAE,SAAS;GACjB,SAAS,cAAc,IAAI;GAE3B,UAAU,cAAc,aAAa,IAAI;GACzC,UAAU,qBAAqB,aAAa;GAC5C,UAAU,aACR,UAAU,oBACV,CAAC,KAAK,YACN,CAAC,UAAU,qBACX,YAAY,MAAM,CAAC,KAAK,SAAS,EAAE,OAAO;EAC9C;;GAtFE,OAAA,UAAA,GAAA,mBAoCM,OAAA;IAnCH,OAAK,eAAA;KAAG,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA;KAAU,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,QAAA,KAAK,QAAQ;KAAG,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,QAAA,KAAK,QAAQ;IAAA,CAAA;IAC1F,KAAK;;KAEN,UAAA,IAAA,GAAA,mBAA0F,UAAA,MAAA,WAAxE,QAAA,KAAK,QAAV,MAAC;KAAd,OAAA,UAAA,GAAA,mBAA0F,QAAA;MAA3D,KAAK;MAAI,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,QAAA,CAAA;MAAY,eAAY;;;IAE7D,CAAA,QAAA,KAAK,UAApB,UAAA,GAAA,YAES,MAAA,WAAA,GAAA;;KAFoB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA;KAAkB,SAAK,cAAO,cAAY,CAAA,MAAA,CAAA;;KACjF,SAAA,cAAc,CAAd,YAAc,MAAA,UAAA,CAAA,CAAA,CAAA;;IAEhB,GAAA,GAAA,CAAA,OAAA,CAAA,MAAA,UAAA,GAAA,YAES,MAAA,WAAA,GAAA;;KAFO,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,CAAA;KAAe,eAAY;;KACrD,SAAA,cAAO,CAAP,YAAO,MAAA,GAAA,CAAA,CAAA,CAAA;;;IAGT,gBAAA,UAAA,GAAA,mBAsBM,OAAA;KArBH,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;KACZ,SAAO;KACP,eAAW,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,QAAA,CAAQ,CAAA,oBAAqB,QAAQ,QAAA,IAAI;IAS/C,GAAA,CAAA,MAAA,SAAA,CAAS,CAAC,aADlB,UAAA,GAAA,YAQE,aAAA;;KANC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,UAAA,CAAA;KACZ,eAAa,QAAA,KAAK;KAClB,
|
|
1
|
+
{"version":3,"file":"tree-node.js","names":[],"sources":["../../../src/components/tree/tree-node.vue"],"sourcesContent":["<template>\n <div\n :class=\"[cls.e('node'), bem.is('expanded', node.expanded), bem.is('disabled', node.disabled)]\"\n :ref=\"measureRef\"\n >\n <span v-for=\"i in node.depth\" :key=\"i\" :class=\"cls.e('indent')\" aria-hidden=\"true\"></span>\n\n <u-icon v-if=\"!node.isLeaf\" :class=\"cls.e('expand-icon')\" @click.stop=\"toggleExpand\">\n <ArrowRight />\n </u-icon>\n <u-icon v-else :class=\"cls.e('leaf-icon')\" aria-hidden=\"true\">\n <Dot />\n </u-icon>\n\n <div\n :class=\"cls.e('node-content')\"\n @click=\"handleClick\"\n @contextmenu=\"treeEmit('node-contextmenu', $event, node)\"\n v-ripple=\"\n (treeProps.selectable || (treeProps.checkable && treeProps.checkOnClickNode)) &&\n !node.disabled\n ? cls.e('ripple')\n : false\n \"\n >\n <u-checkbox\n v-if=\"treeProps.checkable\"\n :class=\"cls.e('checkbox')\"\n :model-value=\"node.checked\"\n :indeterminate=\"!treeProps.checkStrictly && node.indeterminate\"\n :disabled=\"node.disabled\"\n @change=\"handleChange\"\n @click.stop\n />\n\n <u-node-render :content=\"getTreeSlotsNode({ node, data: node.data })\" />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { vRipple } from '@veltra/directives'\nimport { ArrowRight, Dot } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject } from 'vue'\n\nimport type { TreeNodeProps } from '../../types'\nimport UCheckbox from '../checkbox/checkbox.vue'\nimport { UIcon } from '../icon'\nimport { UNodeRender } from '../node-render'\nimport { TreeDIKey } from './di'\n\ndefineOptions({ name: 'UTreeNode' })\n\nconst props = defineProps<TreeNodeProps>()\n\nconst { treeProps, treeEmit, cls, getTreeSlotsNode, getFlattedNodes, toggleCheck, handleSelect } =\n inject(TreeDIKey)!\n\nfunction measureRef(el: unknown) {\n if (typeof props.index !== 'number' || !props.measureElement) return\n props.measureElement(props.index, el as Element | null)\n}\n\nfunction toggleExpand() {\n const { node } = props\n node.expanded = !node.expanded\n getFlattedNodes()\n\n treeEmit('expand', node)\n}\n\nfunction handleChange(checked: boolean, e: MouseEvent) {\n toggleCheck(props.node, checked, e.ctrlKey)\n}\n\nfunction handleClick(e: MouseEvent) {\n const { node } = props\n treeEmit('node-click', node)\n\n treeProps.selectable && handleSelect(node)\n treeProps.expandOnClickNode && toggleExpand()\n treeProps.checkable &&\n treeProps.checkOnClickNode &&\n !node.disabled &&\n !treeProps.expandOnClickNode &&\n toggleCheck(node, !node.checked, e.ctrlKey)\n}\n</script>\n"],"mappings":"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"}
|
|
@@ -85,9 +85,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
85
85
|
nodeDict,
|
|
86
86
|
getFlattedNodes
|
|
87
87
|
});
|
|
88
|
-
const { checkedData, toggleCheck } = useCheck({
|
|
88
|
+
const { checkedData, toggleCheck, checkAll } = useCheck({
|
|
89
89
|
props,
|
|
90
90
|
emit,
|
|
91
|
+
forest,
|
|
91
92
|
nodeDict,
|
|
92
93
|
getFlattedNodes
|
|
93
94
|
});
|
|
@@ -163,11 +164,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
163
164
|
nodes,
|
|
164
165
|
checkNode: toggleCheck,
|
|
165
166
|
selectNode: handleSelect,
|
|
166
|
-
checkAll
|
|
167
|
-
forest.value.roots.forEach((node) => {
|
|
168
|
-
toggleCheck(node, check);
|
|
169
|
-
});
|
|
170
|
-
},
|
|
167
|
+
checkAll,
|
|
171
168
|
getSelected() {
|
|
172
169
|
return selectedData;
|
|
173
170
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.js","names":[],"sources":["../../../src/components/tree/tree.vue"],"sourcesContent":["<template>\n <u-scroll\n :class=\"className\"\n ref=\"scrollRef\"\n :content-class=\"[cls.e('wrap'), bem.is('virtual', virtualEnabled)]\"\n >\n <template v-if=\"virtualEnabled\">\n <UTreeNode\n v-for=\"{ node, key, offset, index } of virtualNodes\"\n :node=\"node\"\n :key=\"key\"\n :class=\"bem.is('selected', node.data === selectedData)\"\n :index=\"index\"\n :measure-element=\"measureElement\"\n :style=\"{ transform: `translateY(${offset}px)` }\"\n />\n </template>\n <template v-else>\n <UTreeNode\n v-for=\"node of nodes\"\n :node=\"node\"\n :key=\"node.key\"\n :class=\"bem.is('selected', node.data === selectedData)\"\n />\n </template>\n\n <div :class=\"cls.e('empty')\" v-if=\"!nodes.length\">\n <UEmpty />\n </div>\n </u-scroll>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps, useVirtualizer } from '@veltra/compositions'\nimport { bem, nextFrame, scrollIntoContainerView } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, provide, shallowRef, useSlots, watch, watchEffect, type VNode } from 'vue'\n\nimport type { TreeProps, TreeEmit, _TreeExposed } from '../../types'\nimport type { ScrollExposed } from '../../types'\nimport { UEmpty } from '../empty'\nimport { UScroll } from '../scroll'\nimport { TreeDIKey, type TreeConText, type TreeSlotsScope } from './di'\nimport UTreeNode from './tree-node.vue'\nimport { useCheck } from './use-check'\nimport { useFilter } from './use-filter'\nimport { useSelect } from './use-select'\nimport { useTreeNodes } from './use-tree-nodes'\n\ndefineOptions({ name: 'UTree' })\n\nconst props = withDefaults(defineProps<TreeProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n childrenKey: 'children',\n expandOnClickNode: false,\n checkOnClickNode: true,\n checkStrictly: false,\n data: () => []\n})\n\nconst emit = defineEmits<TreeEmit>()\n\nconst cls = bem('tree')\n\nconst { formProps } = injectFormContext()\n\nconst { size } = useFormFallbackProps([formProps ?? {}, props], { size: 'default' })\n\nconst scrollRef = shallowRef<ScrollExposed>()\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('selectable', props.selectable),\n bem.is('checkable', props.checkable)\n ]\n})\n\ndefineSlots<{ default: (props: TreeSlotsScope) => any }>()\n\nconst slots = useSlots()\n\nfunction getTreeSlotsNode(ctx: TreeSlotsScope): VNode[] | string | undefined {\n return (props.slots?.default ?? slots.default)?.(ctx) ?? ctx.node.label\n}\n\nconst { nodes, forest, getFlattedNodes, nodeDict } = useTreeNodes({ props })\n\nwatch(\n [() => props.selectable, () => props.checkable, forest],\n ([s, c]) => {\n // 如果选择或勾选,则不更新节点\n if (s || c) return\n getFlattedNodes()\n },\n { immediate: true }\n)\n\nconst { filter } = useFilter({ forest, getFlattedNodes })\n\nconst { handleSelect, selectedData } = useSelect({ props, emit, nodeDict, getFlattedNodes })\n\nconst { checkedData, toggleCheck } = useCheck({
|
|
1
|
+
{"version":3,"file":"tree.js","names":[],"sources":["../../../src/components/tree/tree.vue"],"sourcesContent":["<template>\n <u-scroll\n :class=\"className\"\n ref=\"scrollRef\"\n :content-class=\"[cls.e('wrap'), bem.is('virtual', virtualEnabled)]\"\n >\n <template v-if=\"virtualEnabled\">\n <UTreeNode\n v-for=\"{ node, key, offset, index } of virtualNodes\"\n :node=\"node\"\n :key=\"key\"\n :class=\"bem.is('selected', node.data === selectedData)\"\n :index=\"index\"\n :measure-element=\"measureElement\"\n :style=\"{ transform: `translateY(${offset}px)` }\"\n />\n </template>\n <template v-else>\n <UTreeNode\n v-for=\"node of nodes\"\n :node=\"node\"\n :key=\"node.key\"\n :class=\"bem.is('selected', node.data === selectedData)\"\n />\n </template>\n\n <div :class=\"cls.e('empty')\" v-if=\"!nodes.length\">\n <UEmpty />\n </div>\n </u-scroll>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps, useVirtualizer } from '@veltra/compositions'\nimport { bem, nextFrame, scrollIntoContainerView } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, provide, shallowRef, useSlots, watch, watchEffect, type VNode } from 'vue'\n\nimport type { TreeProps, TreeEmit, _TreeExposed } from '../../types'\nimport type { ScrollExposed } from '../../types'\nimport { UEmpty } from '../empty'\nimport { UScroll } from '../scroll'\nimport { TreeDIKey, type TreeConText, type TreeSlotsScope } from './di'\nimport UTreeNode from './tree-node.vue'\nimport { useCheck } from './use-check'\nimport { useFilter } from './use-filter'\nimport { useSelect } from './use-select'\nimport { useTreeNodes } from './use-tree-nodes'\n\ndefineOptions({ name: 'UTree' })\n\nconst props = withDefaults(defineProps<TreeProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n childrenKey: 'children',\n expandOnClickNode: false,\n checkOnClickNode: true,\n checkStrictly: false,\n data: () => []\n})\n\nconst emit = defineEmits<TreeEmit>()\n\nconst cls = bem('tree')\n\nconst { formProps } = injectFormContext()\n\nconst { size } = useFormFallbackProps([formProps ?? {}, props], { size: 'default' })\n\nconst scrollRef = shallowRef<ScrollExposed>()\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('selectable', props.selectable),\n bem.is('checkable', props.checkable)\n ]\n})\n\ndefineSlots<{ default: (props: TreeSlotsScope) => any }>()\n\nconst slots = useSlots()\n\nfunction getTreeSlotsNode(ctx: TreeSlotsScope): VNode[] | string | undefined {\n return (props.slots?.default ?? slots.default)?.(ctx) ?? ctx.node.label\n}\n\nconst { nodes, forest, getFlattedNodes, nodeDict } = useTreeNodes({ props })\n\nwatch(\n [() => props.selectable, () => props.checkable, forest],\n ([s, c]) => {\n // 如果选择或勾选,则不更新节点\n if (s || c) return\n getFlattedNodes()\n },\n { immediate: true }\n)\n\nconst { filter } = useFilter({ forest, getFlattedNodes })\n\nconst { handleSelect, selectedData } = useSelect({ props, emit, nodeDict, getFlattedNodes })\n\nconst { checkedData, toggleCheck, checkAll } = useCheck({\n props,\n emit,\n forest,\n nodeDict,\n getFlattedNodes\n})\n\nconst estimateSize = (): number => {\n // 基于 style.scss 中节点稳态高度推算的默认值;\n // 实际高度仍由 `measureElement` 通过 ResizeObserver 回填覆盖。\n switch (size.value) {\n case 'small':\n return 32\n case 'large':\n return 44\n default:\n return 36\n }\n}\n\n// 80 阈值内化为消费者侧语义;低于此值时不启用虚拟化。\nconst virtualEnabled = computed(() => nodes.value.length > 80)\n\nconst { virtualizer, items } = useVirtualizer({\n count: computed(() => nodes.value.length),\n scrollEl: () => scrollRef.value?.containerRef ?? null,\n // 仅在虚拟化启用时把内容容器的 height 撑开到 totalSize;\n // 关闭时传 null,hook 会清除此前写入的内联 height,回到 CSS 默认。\n contentEl: () => (virtualEnabled.value ? (scrollRef.value?.contentRef ?? null) : null),\n estimateSize,\n gap: 2,\n // 以节点稳定 key 作为虚拟项身份,展开 / 收起 / 过滤引起的 nodes 数组变更\n // 保留未移动节点的真实测量值,避免再次滚动首屏抖动。\n getItemKey: (i) => nodes.value[i]?.key ?? i\n})\n\n// 身份稳定性已由底层 `getItemKey` 保证;直接以 `node.key` 作为 Vue 层 key。\n// `item.index` 仅作为 nodes / snapshot 竞态边缘的 fallback。\nconst virtualNodes = computed(() => {\n const _nodes = nodes.value\n return items.value.map((item) => {\n const node = _nodes[item.index]!\n return { node, key: node?.key ?? item.index, offset: item.start, index: item.index }\n })\n})\n\nconst measureElement: (index: number, el: Element | null) => void = (index, el) =>\n virtualizer.measureElement(index, el)\n\nfunction scrollTo(index: number): void {\n virtualizer.scrollToIndex(index, { align: 'center' })\n}\n\nfunction scrollIntoView() {\n const { selectable, checkable, scrollToView } = props\n if (!scrollToView || (!selectable && !checkable) || !nodes.value) return\n let index = -1\n\n if (selectable) {\n if (!selectedData.value) return\n index = nodes.value.findIndex((node) => node.data === selectedData.value)\n } else if (checkable) {\n if (!checkedData.size) return\n index = nodes.value.findIndex((node) => checkedData.has(node.data))\n }\n\n if (virtualEnabled.value) {\n scrollTo(index)\n } else {\n const { contentRef, containerRef } = scrollRef.value ?? {}\n const el = contentRef?.children[index]\n el && scrollIntoContainerView(el as HTMLElement, containerRef ?? null)\n }\n}\n\nwatchEffect(() => {\n nextFrame(() => {\n scrollIntoView()\n })\n})\n\nprovide(TreeDIKey, {\n cls,\n selectedData,\n checkedData,\n getFlattedNodes,\n getTreeSlotsNode,\n treeEmit: emit as TreeEmit,\n treeProps: props as TreeProps,\n toggleCheck: toggleCheck as TreeConText['toggleCheck'],\n handleSelect: handleSelect as TreeConText['handleSelect']\n})\n\ndefineExpose<_TreeExposed>({\n filter,\n forest,\n nodes,\n checkNode: toggleCheck,\n selectNode: handleSelect,\n checkAll,\n getSelected() {\n return selectedData\n },\n getChecked() {\n return Array.from(checkedData)\n },\n scrollTo,\n\n expandAll() {\n forest.value.dfs((node) => {\n node.expanded = true\n })\n getFlattedNodes()\n },\n collapseAll() {\n forest.value.dfs((node) => {\n node.expanded = false\n })\n getFlattedNodes()\n }\n})\n</script>\n"],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ function expandAncestors(node) {
|
|
|
17
17
|
return changed;
|
|
18
18
|
}
|
|
19
19
|
function useCheck(options) {
|
|
20
|
-
const { emit, props, nodeDict, getFlattedNodes } = options;
|
|
20
|
+
const { emit, props, forest, nodeDict, getFlattedNodes } = options;
|
|
21
21
|
const checkedData = /* @__PURE__ */ new Set();
|
|
22
22
|
/**
|
|
23
23
|
* 上一次回显使用的 value 集合(仅限 props.checked)。
|
|
@@ -29,13 +29,13 @@ function useCheck(options) {
|
|
|
29
29
|
function checkNode(node) {
|
|
30
30
|
if (node.checked) return;
|
|
31
31
|
node.checked = true;
|
|
32
|
-
if (node.parent) node.parent.childrenCheckCount++;
|
|
32
|
+
if (!props.checkStrictly && node.parent) node.parent.childrenCheckCount++;
|
|
33
33
|
checkedData.add(node.data);
|
|
34
34
|
}
|
|
35
35
|
function uncheckNode(node) {
|
|
36
36
|
if (!node.checked) return;
|
|
37
37
|
node.checked = false;
|
|
38
|
-
if (node.parent) node.parent.childrenCheckCount--;
|
|
38
|
+
if (!props.checkStrictly && node.parent) node.parent.childrenCheckCount--;
|
|
39
39
|
checkedData.delete(node.data);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
@@ -74,8 +74,11 @@ function useCheck(options) {
|
|
|
74
74
|
}, { immediate: true });
|
|
75
75
|
function handleCheck(node, ctrlKey) {
|
|
76
76
|
const { checkStrictly } = props;
|
|
77
|
-
if (ctrlKey)
|
|
78
|
-
|
|
77
|
+
if (ctrlKey) {
|
|
78
|
+
!node.disabled && checkNode(node);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
dfs(node, (n) => {
|
|
79
82
|
const tn = n;
|
|
80
83
|
!tn.disabled && checkNode(tn);
|
|
81
84
|
}, props.childrenKey ?? "children");
|
|
@@ -86,12 +89,15 @@ function useCheck(options) {
|
|
|
86
89
|
}
|
|
87
90
|
function handleUncheck(node, ctrlKey) {
|
|
88
91
|
const { checkStrictly } = props;
|
|
89
|
-
if (ctrlKey)
|
|
90
|
-
|
|
92
|
+
if (ctrlKey || checkStrictly) {
|
|
93
|
+
!node.disabled && uncheckNode(node);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
dfs(node, (n) => {
|
|
91
97
|
const tn = n;
|
|
92
98
|
!tn.disabled && uncheckNode(tn);
|
|
93
99
|
}, props.childrenKey ?? "children");
|
|
94
|
-
|
|
100
|
+
node.bubbleSet((node) => {
|
|
95
101
|
const { parent } = node;
|
|
96
102
|
if (parent) uncheckNode(parent);
|
|
97
103
|
});
|
|
@@ -108,9 +114,26 @@ function useCheck(options) {
|
|
|
108
114
|
checkedByEvent = false;
|
|
109
115
|
});
|
|
110
116
|
}
|
|
117
|
+
function checkAll(check) {
|
|
118
|
+
checkedByEvent = true;
|
|
119
|
+
forest.value.dfs((node) => {
|
|
120
|
+
const tn = node;
|
|
121
|
+
if (!tn.disabled) check ? checkNode(tn) : uncheckNode(tn);
|
|
122
|
+
});
|
|
123
|
+
const checkedArr = Array.from(checkedData);
|
|
124
|
+
const valueKey = fieldKey(props.valueKey, "value");
|
|
125
|
+
const nextValues = checkedArr.map((item) => o(item).get(valueKey));
|
|
126
|
+
lastCheckedSet = new Set(nextValues);
|
|
127
|
+
emit("update:checked", nextValues, checkedArr);
|
|
128
|
+
getFlattedNodes();
|
|
129
|
+
nextTick(() => {
|
|
130
|
+
checkedByEvent = false;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
111
133
|
return {
|
|
112
134
|
checkedData,
|
|
113
|
-
toggleCheck
|
|
135
|
+
toggleCheck,
|
|
136
|
+
checkAll
|
|
114
137
|
};
|
|
115
138
|
}
|
|
116
139
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-check.js","names":[],"sources":["../../../src/components/tree/use-check.ts"],"sourcesContent":["import { dfs, o } from '@cat-kit/core'\nimport { fieldKey } from '@veltra/utils'\nimport { nextTick, watch, type ComputedRef } from 'vue'\n\nimport type { TreeEmit, TreeProps } from '../../types'\nimport type { TreeNode } from './tree-node'\n\ninterface Options {\n emit: TreeEmit\n props: TreeProps\n nodeDict: ComputedRef<Map<any, TreeNode>>\n getFlattedNodes: () => void\n}\n\ninterface UseCheckReturned {\n checkedData: Set<Record<string, any>>\n toggleCheck: (node: TreeNode, check: boolean, ctrlKey?: boolean) => void\n}\n\n/**\n * 展开目标节点的所有祖先。返回是否发生了实际展开。\n */\nfunction expandAncestors(node: TreeNode): boolean {\n let changed = false\n node.bubbleSet((n) => {\n if (n.parent) {\n if (n.parent.expanded) return false\n n.parent.expanded = true\n changed = true\n }\n })\n return changed\n}\n\nexport function useCheck(options: Options): UseCheckReturned {\n const { emit, props, nodeDict, getFlattedNodes } = options\n\n const checkedData = new Set<Record<string, any>>()\n\n /**\n * 上一次回显使用的 value 集合(仅限 props.checked)。\n * 用于在相同 `nodeDict` 下走差集回显,避免每次全量清空再重建。\n */\n let lastCheckedSet = new Set<unknown>()\n /** 最近一次参与差集比较的 nodeDict 引用;引用变化时视为整棵树重建。 */\n let lastDict: Map<any, TreeNode> | null = null\n\n function checkNode(node: TreeNode): void {\n if (node.checked) return\n node.checked = true\n if (node.parent) {\n node.parent.childrenCheckCount++\n }\n checkedData.add(node.data)\n }\n\n function uncheckNode(node: TreeNode): void {\n if (!node.checked) return\n node.checked = false\n if (node.parent) {\n node.parent.childrenCheckCount--\n }\n checkedData.delete(node.data)\n }\n\n /**\n * 模型值是否由事件触发\n */\n let checkedByEvent = false\n\n // 回显\n watch(\n [() => props.checked, nodeDict],\n ([c, dict]) => {\n // 事件已经触发模型变更了,所以不再进行下面的计算\n if (checkedByEvent || !props.checkable) return\n if (!dict.size) return\n\n const nextSet = new Set<unknown>(c ?? [])\n\n // 森林重建(dict 引用变化)→ 走全量清空重建路径,\n // 因为所有 TreeNode 实例都是新的,上一轮缓存的 lastCheckedSet 失效。\n const dictChanged = dict !== lastDict\n if (dictChanged) {\n checkedData.clear()\n lastCheckedSet = new Set()\n }\n\n let mutated = false\n\n // 取消:上一轮有但本轮没有的 value\n for (const v of lastCheckedSet) {\n if (!nextSet.has(v)) {\n const node = dict.get(v)\n if (node) {\n uncheckNode(node)\n mutated = true\n }\n }\n }\n\n // 新增:本轮有但上一轮没有的 value\n let ancestorsExpanded = false\n for (const v of nextSet) {\n if (!lastCheckedSet.has(v) || dictChanged) {\n const node = dict.get(v)\n if (node) {\n checkNode(node)\n mutated = true\n if (expandAncestors(node)) {\n ancestorsExpanded = true\n }\n }\n }\n }\n\n lastCheckedSet = nextSet\n lastDict = dict\n\n // 只在真实发生变化时才触发扁平化,避免空转。\n if (mutated || ancestorsExpanded) {\n getFlattedNodes()\n }\n },\n { immediate: true }\n )\n\n function handleCheck(node: TreeNode, ctrlKey?: boolean) {\n const { checkStrictly } = props\n\n if (ctrlKey) {\n checkNode(node)\n }
|
|
1
|
+
{"version":3,"file":"use-check.js","names":[],"sources":["../../../src/components/tree/use-check.ts"],"sourcesContent":["import type { Forest } from '@cat-kit/core'\nimport { dfs, o } from '@cat-kit/core'\nimport { fieldKey } from '@veltra/utils'\nimport { nextTick, watch, type ComputedRef } from 'vue'\n\nimport type { TreeEmit, TreeProps } from '../../types'\nimport type { TreeNode } from './tree-node'\n\ninterface Options {\n emit: TreeEmit\n props: TreeProps\n forest: ComputedRef<Forest<Record<string, unknown>, any>>\n nodeDict: ComputedRef<Map<any, TreeNode>>\n getFlattedNodes: () => void\n}\n\ninterface UseCheckReturned {\n checkedData: Set<Record<string, any>>\n toggleCheck: (node: TreeNode, check: boolean, ctrlKey?: boolean) => void\n checkAll: (check: boolean) => void\n}\n\n/**\n * 展开目标节点的所有祖先。返回是否发生了实际展开。\n */\nfunction expandAncestors(node: TreeNode): boolean {\n let changed = false\n node.bubbleSet((n) => {\n if (n.parent) {\n if (n.parent.expanded) return false\n n.parent.expanded = true\n changed = true\n }\n })\n return changed\n}\n\nexport function useCheck(options: Options): UseCheckReturned {\n const { emit, props, forest, nodeDict, getFlattedNodes } = options\n\n const checkedData = new Set<Record<string, any>>()\n\n /**\n * 上一次回显使用的 value 集合(仅限 props.checked)。\n * 用于在相同 `nodeDict` 下走差集回显,避免每次全量清空再重建。\n */\n let lastCheckedSet = new Set<unknown>()\n /** 最近一次参与差集比较的 nodeDict 引用;引用变化时视为整棵树重建。 */\n let lastDict: Map<any, TreeNode> | null = null\n\n function checkNode(node: TreeNode): void {\n if (node.checked) return\n node.checked = true\n if (!props.checkStrictly && node.parent) {\n node.parent.childrenCheckCount++\n }\n checkedData.add(node.data)\n }\n\n function uncheckNode(node: TreeNode): void {\n if (!node.checked) return\n node.checked = false\n if (!props.checkStrictly && node.parent) {\n node.parent.childrenCheckCount--\n }\n checkedData.delete(node.data)\n }\n\n /**\n * 模型值是否由事件触发\n */\n let checkedByEvent = false\n\n // 回显\n watch(\n [() => props.checked, nodeDict],\n ([c, dict]) => {\n // 事件已经触发模型变更了,所以不再进行下面的计算\n if (checkedByEvent || !props.checkable) return\n if (!dict.size) return\n\n const nextSet = new Set<unknown>(c ?? [])\n\n // 森林重建(dict 引用变化)→ 走全量清空重建路径,\n // 因为所有 TreeNode 实例都是新的,上一轮缓存的 lastCheckedSet 失效。\n const dictChanged = dict !== lastDict\n if (dictChanged) {\n checkedData.clear()\n lastCheckedSet = new Set()\n }\n\n let mutated = false\n\n // 取消:上一轮有但本轮没有的 value\n for (const v of lastCheckedSet) {\n if (!nextSet.has(v)) {\n const node = dict.get(v)\n if (node) {\n uncheckNode(node)\n mutated = true\n }\n }\n }\n\n // 新增:本轮有但上一轮没有的 value\n let ancestorsExpanded = false\n for (const v of nextSet) {\n if (!lastCheckedSet.has(v) || dictChanged) {\n const node = dict.get(v)\n if (node) {\n checkNode(node)\n mutated = true\n if (expandAncestors(node)) {\n ancestorsExpanded = true\n }\n }\n }\n }\n\n lastCheckedSet = nextSet\n lastDict = dict\n\n // 只在真实发生变化时才触发扁平化,避免空转。\n if (mutated || ancestorsExpanded) {\n getFlattedNodes()\n }\n },\n { immediate: true }\n )\n\n function handleCheck(node: TreeNode, ctrlKey?: boolean) {\n const { checkStrictly } = props\n\n if (ctrlKey) {\n !node.disabled && checkNode(node)\n return\n }\n\n dfs(\n node as unknown as Record<string, unknown>,\n (n) => {\n const tn = n as unknown as TreeNode\n !tn.disabled && checkNode(tn)\n },\n props.childrenKey ?? 'children'\n )\n\n // 非严格选择时还需要更新祖先节点,\n // 一旦子节点全部选中,父节点也要设置为选中状态\n if (!checkStrictly) {\n node.bubbleSet((node) => {\n const { parent } = node\n\n if (parent) {\n const parentChecked = parent.childrenCheckCount === parent.children!.length\n\n parentChecked ? checkNode(parent) : uncheckNode(parent)\n }\n })\n }\n }\n\n function handleUncheck(node: TreeNode, ctrlKey?: boolean) {\n const { checkStrictly } = props\n\n if (ctrlKey || checkStrictly) {\n !node.disabled && uncheckNode(node)\n return\n }\n\n dfs(\n node as unknown as Record<string, unknown>,\n (n) => {\n const tn = n as unknown as TreeNode\n !tn.disabled && uncheckNode(tn)\n },\n props.childrenKey ?? 'children'\n )\n\n // 非严格模式下,取消选中时,需要更新父节点\n node.bubbleSet((node) => {\n const { parent } = node\n if (parent) {\n uncheckNode(parent)\n }\n })\n }\n\n function toggleCheck(node: TreeNode, check: boolean, ctrlKey?: boolean) {\n checkedByEvent = true\n\n check ? handleCheck(node, ctrlKey) : handleUncheck(node, ctrlKey)\n\n const checkedArr = Array.from(checkedData)\n\n const valueKey = fieldKey(props.valueKey, 'value')\n const nextValues = checkedArr.map((item) => o(item).get(valueKey) as unknown)\n\n // 与外部 checked 保持差集缓存同步,避免下一次 watch 回调把刚勾选的值又误判为“新增”。\n lastCheckedSet = new Set(nextValues)\n\n emit('update:checked', nextValues as any[], checkedArr)\n\n nextTick(() => {\n checkedByEvent = false\n })\n }\n\n function checkAll(check: boolean) {\n checkedByEvent = true\n\n forest.value.dfs((node) => {\n const tn = node as unknown as TreeNode\n if (!tn.disabled) {\n check ? checkNode(tn) : uncheckNode(tn)\n }\n })\n\n const checkedArr = Array.from(checkedData)\n\n const valueKey = fieldKey(props.valueKey, 'value')\n const nextValues = checkedArr.map((item) => o(item).get(valueKey) as unknown)\n\n lastCheckedSet = new Set(nextValues)\n\n emit('update:checked', nextValues as any[], checkedArr)\n\n getFlattedNodes()\n\n nextTick(() => {\n checkedByEvent = false\n })\n }\n\n return { checkedData, toggleCheck, checkAll }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createApp, h, nextTick, ref, type Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import UMultiTreeSelect from '../multi-tree-select.vue'
|
|
5
|
+
|
|
6
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
7
|
+
|
|
8
|
+
const treeData = [
|
|
9
|
+
{
|
|
10
|
+
label: '亚洲',
|
|
11
|
+
value: 'asia',
|
|
12
|
+
children: [
|
|
13
|
+
{ label: '中国', value: 'cn' },
|
|
14
|
+
{ label: '日本', value: 'jp' }
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: '欧洲',
|
|
19
|
+
value: 'europe',
|
|
20
|
+
children: [
|
|
21
|
+
{ label: '英国', value: 'uk' },
|
|
22
|
+
{ label: '法国', value: 'fr' }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
function mountMultiTreeSelect(props: Record<string, unknown> = {}) {
|
|
28
|
+
const host = document.createElement('div')
|
|
29
|
+
document.body.appendChild(host)
|
|
30
|
+
|
|
31
|
+
const model = ref(props.modelValue ?? []) as Ref<(string | number)[]>
|
|
32
|
+
const data = ref(props.data ?? treeData)
|
|
33
|
+
|
|
34
|
+
const app = createApp({
|
|
35
|
+
render() {
|
|
36
|
+
return h(UMultiTreeSelect, {
|
|
37
|
+
...props,
|
|
38
|
+
modelValue: model.value,
|
|
39
|
+
data: data.value,
|
|
40
|
+
expandAll: true,
|
|
41
|
+
'onUpdate:modelValue': (value: (string | number)[]) => {
|
|
42
|
+
model.value = value
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
app.mount(host)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
host,
|
|
52
|
+
model,
|
|
53
|
+
data,
|
|
54
|
+
unmount() {
|
|
55
|
+
app.unmount()
|
|
56
|
+
host.remove()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function openDropdown(host: HTMLElement) {
|
|
62
|
+
const trigger = host.querySelector('.u-multi-tree-select') as HTMLElement
|
|
63
|
+
trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
64
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
65
|
+
await nextTick()
|
|
66
|
+
await sleep(25)
|
|
67
|
+
if (queryNodeContents().length > 0) return
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function queryNodeContents() {
|
|
72
|
+
return [...document.body.querySelectorAll<HTMLElement>('.u-tree__node-content')]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function queryCheckboxes() {
|
|
76
|
+
return [...document.body.querySelectorAll<HTMLInputElement>('.u-tree .u-checkbox__native')]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function clickNodeByLabel(label: string, ctrlKey = false) {
|
|
80
|
+
const node = queryNodeContents().find((el) => el.textContent?.includes(label))
|
|
81
|
+
expect(node, `tree node "${label}" not found`).toBeTruthy()
|
|
82
|
+
node!.dispatchEvent(new MouseEvent('click', { bubbles: true, ctrlKey }))
|
|
83
|
+
await nextTick()
|
|
84
|
+
await sleep(50)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe('UMultiTreeSelect check-strictly and ctrl-key behavior', () => {
|
|
88
|
+
it('cascades check and uncheck to children by default (checkStrictly = false)', async () => {
|
|
89
|
+
const { host, model, unmount } = mountMultiTreeSelect()
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await openDropdown(host)
|
|
93
|
+
await clickNodeByLabel('亚洲')
|
|
94
|
+
expect(model.value).toEqual(['asia', 'cn', 'jp'])
|
|
95
|
+
|
|
96
|
+
// 取消勾选「亚洲」时级联取消子节点
|
|
97
|
+
await clickNodeByLabel('亚洲')
|
|
98
|
+
expect(model.value).toEqual([])
|
|
99
|
+
} finally {
|
|
100
|
+
unmount()
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('independently checks node when pressing ctrl in default mode', async () => {
|
|
105
|
+
const { host, model, unmount } = mountMultiTreeSelect()
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await openDropdown(host)
|
|
109
|
+
// 按住 ctrl 点击「亚洲」,仅勾选「亚洲」自身
|
|
110
|
+
await clickNodeByLabel('亚洲', true)
|
|
111
|
+
expect(model.value).toEqual(['asia'])
|
|
112
|
+
|
|
113
|
+
// 按住 ctrl 再次点击「亚洲」,仅取消「亚洲」自身
|
|
114
|
+
await clickNodeByLabel('亚洲', true)
|
|
115
|
+
expect(model.value).toEqual([])
|
|
116
|
+
} finally {
|
|
117
|
+
unmount()
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('cascades children on check, but only unchecks self on uncheck in checkStrictly mode', async () => {
|
|
122
|
+
const { host, model, unmount } = mountMultiTreeSelect({ 'check-strictly': true })
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await openDropdown(host)
|
|
126
|
+
// 勾选父节点「亚洲」-> 把所有子节点都勾上
|
|
127
|
+
await clickNodeByLabel('亚洲')
|
|
128
|
+
expect(model.value).toEqual(['asia', 'cn', 'jp'])
|
|
129
|
+
|
|
130
|
+
// 取消勾选父节点「亚洲」-> 只取消自己,子节点保持勾选
|
|
131
|
+
await clickNodeByLabel('亚洲')
|
|
132
|
+
expect(model.value).toEqual(['cn', 'jp'])
|
|
133
|
+
} finally {
|
|
134
|
+
unmount()
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('does not show indeterminate on parent when only child is checked in checkStrictly mode', async () => {
|
|
139
|
+
const { host, unmount } = mountMultiTreeSelect({ checkStrictly: true })
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await openDropdown(host)
|
|
143
|
+
await clickNodeByLabel('中国')
|
|
144
|
+
|
|
145
|
+
// 父节点「亚洲」不应有半选状态
|
|
146
|
+
const asiaNodeContent = queryNodeContents().find((el) => el.textContent?.includes('亚洲'))
|
|
147
|
+
const asiaCheckbox = asiaNodeContent?.closest('.u-tree__node')?.querySelector('.u-checkbox')
|
|
148
|
+
expect(asiaCheckbox?.classList.contains('is-indeterminate')).toBe(false)
|
|
149
|
+
expect(asiaCheckbox?.querySelector('.u-checkbox--indeterminate')).toBeNull()
|
|
150
|
+
} finally {
|
|
151
|
+
unmount()
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('selects all nodes when clicking select all in checkStrictly mode', async () => {
|
|
156
|
+
const { host, model, unmount } = mountMultiTreeSelect({ checkStrictly: true })
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await openDropdown(host)
|
|
160
|
+
// 点击全选 checkbox
|
|
161
|
+
const allCheckboxInput = document.body.querySelector(
|
|
162
|
+
'.u-multi-tree-select__content-header .u-checkbox__native'
|
|
163
|
+
) as HTMLInputElement
|
|
164
|
+
expect(allCheckboxInput).toBeTruthy()
|
|
165
|
+
allCheckboxInput.click()
|
|
166
|
+
await nextTick()
|
|
167
|
+
await sleep(50)
|
|
168
|
+
|
|
169
|
+
expect(model.value).toEqual(['asia', 'cn', 'jp', 'europe', 'uk', 'fr'])
|
|
170
|
+
|
|
171
|
+
// 再次点击取消全选
|
|
172
|
+
allCheckboxInput.click()
|
|
173
|
+
await nextTick()
|
|
174
|
+
await sleep(50)
|
|
175
|
+
expect(model.value).toEqual([])
|
|
176
|
+
} finally {
|
|
177
|
+
unmount()
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('echoes only parent node when initialized with parent id in checkStrictly mode', async () => {
|
|
182
|
+
const { host, model, unmount } = mountMultiTreeSelect({
|
|
183
|
+
checkStrictly: true,
|
|
184
|
+
modelValue: ['asia']
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
await openDropdown(host)
|
|
189
|
+
expect(model.value).toEqual(['asia'])
|
|
190
|
+
|
|
191
|
+
// tags 只有「亚洲」
|
|
192
|
+
const tags = host.querySelectorAll('.u-tag')
|
|
193
|
+
expect(tags.length).toBe(1)
|
|
194
|
+
expect(tags[0].textContent).toContain('亚洲')
|
|
195
|
+
} finally {
|
|
196
|
+
unmount()
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('directly clicking checkbox cascades on check and only unchecks self on uncheck in checkStrictly mode', async () => {
|
|
201
|
+
const { host, model, unmount } = mountMultiTreeSelect({ checkStrictly: true })
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await openDropdown(host)
|
|
205
|
+
const checkboxes = queryCheckboxes()
|
|
206
|
+
expect(checkboxes.length).toBeGreaterThan(0)
|
|
207
|
+
// 点击第一个 checkbox(亚洲)-> 勾选所有子节点
|
|
208
|
+
checkboxes[0].click()
|
|
209
|
+
await nextTick()
|
|
210
|
+
await sleep(50)
|
|
211
|
+
expect(model.value).toEqual(['asia', 'cn', 'jp'])
|
|
212
|
+
|
|
213
|
+
// 再次点击第一个 checkbox(亚洲)-> 仅取消亚洲自身
|
|
214
|
+
checkboxes[0].click()
|
|
215
|
+
await nextTick()
|
|
216
|
+
await sleep(50)
|
|
217
|
+
expect(model.value).toEqual(['cn', 'jp'])
|
|
218
|
+
} finally {
|
|
219
|
+
unmount()
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
})
|
|
@@ -131,7 +131,9 @@ const props = withDefaults(defineProps<MultiTreeSelectProps>(), {
|
|
|
131
131
|
readonly: undefined,
|
|
132
132
|
filterable: false,
|
|
133
133
|
visibilityLimit: 3,
|
|
134
|
-
minWidth: '280px'
|
|
134
|
+
minWidth: '280px',
|
|
135
|
+
checkStrictly: false,
|
|
136
|
+
checkOnClickNode: true
|
|
135
137
|
})
|
|
136
138
|
|
|
137
139
|
const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
v-if="treeProps.checkable"
|
|
28
28
|
:class="cls.e('checkbox')"
|
|
29
29
|
:model-value="node.checked"
|
|
30
|
-
:indeterminate="node.indeterminate"
|
|
30
|
+
:indeterminate="!treeProps.checkStrictly && node.indeterminate"
|
|
31
31
|
:disabled="node.disabled"
|
|
32
32
|
@change="handleChange"
|
|
33
33
|
@click.stop
|
|
@@ -102,7 +102,13 @@ const { filter } = useFilter({ forest, getFlattedNodes })
|
|
|
102
102
|
|
|
103
103
|
const { handleSelect, selectedData } = useSelect({ props, emit, nodeDict, getFlattedNodes })
|
|
104
104
|
|
|
105
|
-
const { checkedData, toggleCheck } = useCheck({
|
|
105
|
+
const { checkedData, toggleCheck, checkAll } = useCheck({
|
|
106
|
+
props,
|
|
107
|
+
emit,
|
|
108
|
+
forest,
|
|
109
|
+
nodeDict,
|
|
110
|
+
getFlattedNodes
|
|
111
|
+
})
|
|
106
112
|
|
|
107
113
|
const estimateSize = (): number => {
|
|
108
114
|
// 基于 style.scss 中节点稳态高度推算的默认值;
|
|
@@ -196,11 +202,7 @@ defineExpose<_TreeExposed>({
|
|
|
196
202
|
nodes,
|
|
197
203
|
checkNode: toggleCheck,
|
|
198
204
|
selectNode: handleSelect,
|
|
199
|
-
checkAll
|
|
200
|
-
forest.value.roots.forEach((node) => {
|
|
201
|
-
toggleCheck(node, check)
|
|
202
|
-
})
|
|
203
|
-
},
|
|
205
|
+
checkAll,
|
|
204
206
|
getSelected() {
|
|
205
207
|
return selectedData
|
|
206
208
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Forest } from '@cat-kit/core'
|
|
1
2
|
import { dfs, o } from '@cat-kit/core'
|
|
2
3
|
import { fieldKey } from '@veltra/utils'
|
|
3
4
|
import { nextTick, watch, type ComputedRef } from 'vue'
|
|
@@ -8,6 +9,7 @@ import type { TreeNode } from './tree-node'
|
|
|
8
9
|
interface Options {
|
|
9
10
|
emit: TreeEmit
|
|
10
11
|
props: TreeProps
|
|
12
|
+
forest: ComputedRef<Forest<Record<string, unknown>, any>>
|
|
11
13
|
nodeDict: ComputedRef<Map<any, TreeNode>>
|
|
12
14
|
getFlattedNodes: () => void
|
|
13
15
|
}
|
|
@@ -15,6 +17,7 @@ interface Options {
|
|
|
15
17
|
interface UseCheckReturned {
|
|
16
18
|
checkedData: Set<Record<string, any>>
|
|
17
19
|
toggleCheck: (node: TreeNode, check: boolean, ctrlKey?: boolean) => void
|
|
20
|
+
checkAll: (check: boolean) => void
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -33,7 +36,7 @@ function expandAncestors(node: TreeNode): boolean {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export function useCheck(options: Options): UseCheckReturned {
|
|
36
|
-
const { emit, props, nodeDict, getFlattedNodes } = options
|
|
39
|
+
const { emit, props, forest, nodeDict, getFlattedNodes } = options
|
|
37
40
|
|
|
38
41
|
const checkedData = new Set<Record<string, any>>()
|
|
39
42
|
|
|
@@ -48,7 +51,7 @@ export function useCheck(options: Options): UseCheckReturned {
|
|
|
48
51
|
function checkNode(node: TreeNode): void {
|
|
49
52
|
if (node.checked) return
|
|
50
53
|
node.checked = true
|
|
51
|
-
if (node.parent) {
|
|
54
|
+
if (!props.checkStrictly && node.parent) {
|
|
52
55
|
node.parent.childrenCheckCount++
|
|
53
56
|
}
|
|
54
57
|
checkedData.add(node.data)
|
|
@@ -57,7 +60,7 @@ export function useCheck(options: Options): UseCheckReturned {
|
|
|
57
60
|
function uncheckNode(node: TreeNode): void {
|
|
58
61
|
if (!node.checked) return
|
|
59
62
|
node.checked = false
|
|
60
|
-
if (node.parent) {
|
|
63
|
+
if (!props.checkStrictly && node.parent) {
|
|
61
64
|
node.parent.childrenCheckCount--
|
|
62
65
|
}
|
|
63
66
|
checkedData.delete(node.data)
|
|
@@ -129,18 +132,19 @@ export function useCheck(options: Options): UseCheckReturned {
|
|
|
129
132
|
const { checkStrictly } = props
|
|
130
133
|
|
|
131
134
|
if (ctrlKey) {
|
|
132
|
-
checkNode(node)
|
|
133
|
-
|
|
134
|
-
dfs(
|
|
135
|
-
node as unknown as Record<string, unknown>,
|
|
136
|
-
(n) => {
|
|
137
|
-
const tn = n as unknown as TreeNode
|
|
138
|
-
!tn.disabled && checkNode(tn)
|
|
139
|
-
},
|
|
140
|
-
props.childrenKey ?? 'children'
|
|
141
|
-
)
|
|
135
|
+
!node.disabled && checkNode(node)
|
|
136
|
+
return
|
|
142
137
|
}
|
|
143
138
|
|
|
139
|
+
dfs(
|
|
140
|
+
node as unknown as Record<string, unknown>,
|
|
141
|
+
(n) => {
|
|
142
|
+
const tn = n as unknown as TreeNode
|
|
143
|
+
!tn.disabled && checkNode(tn)
|
|
144
|
+
},
|
|
145
|
+
props.childrenKey ?? 'children'
|
|
146
|
+
)
|
|
147
|
+
|
|
144
148
|
// 非严格选择时还需要更新祖先节点,
|
|
145
149
|
// 一旦子节点全部选中,父节点也要设置为选中状态
|
|
146
150
|
if (!checkStrictly) {
|
|
@@ -158,28 +162,28 @@ export function useCheck(options: Options): UseCheckReturned {
|
|
|
158
162
|
|
|
159
163
|
function handleUncheck(node: TreeNode, ctrlKey?: boolean) {
|
|
160
164
|
const { checkStrictly } = props
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
node as unknown as Record<string, unknown>,
|
|
166
|
-
(n) => {
|
|
167
|
-
const tn = n as unknown as TreeNode
|
|
168
|
-
!tn.disabled && uncheckNode(tn)
|
|
169
|
-
},
|
|
170
|
-
props.childrenKey ?? 'children'
|
|
171
|
-
)
|
|
165
|
+
|
|
166
|
+
if (ctrlKey || checkStrictly) {
|
|
167
|
+
!node.disabled && uncheckNode(node)
|
|
168
|
+
return
|
|
172
169
|
}
|
|
173
170
|
|
|
171
|
+
dfs(
|
|
172
|
+
node as unknown as Record<string, unknown>,
|
|
173
|
+
(n) => {
|
|
174
|
+
const tn = n as unknown as TreeNode
|
|
175
|
+
!tn.disabled && uncheckNode(tn)
|
|
176
|
+
},
|
|
177
|
+
props.childrenKey ?? 'children'
|
|
178
|
+
)
|
|
179
|
+
|
|
174
180
|
// 非严格模式下,取消选中时,需要更新父节点
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
})
|
|
182
|
-
}
|
|
181
|
+
node.bubbleSet((node) => {
|
|
182
|
+
const { parent } = node
|
|
183
|
+
if (parent) {
|
|
184
|
+
uncheckNode(parent)
|
|
185
|
+
}
|
|
186
|
+
})
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
function toggleCheck(node: TreeNode, check: boolean, ctrlKey?: boolean) {
|
|
@@ -202,5 +206,31 @@ export function useCheck(options: Options): UseCheckReturned {
|
|
|
202
206
|
})
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
function checkAll(check: boolean) {
|
|
210
|
+
checkedByEvent = true
|
|
211
|
+
|
|
212
|
+
forest.value.dfs((node) => {
|
|
213
|
+
const tn = node as unknown as TreeNode
|
|
214
|
+
if (!tn.disabled) {
|
|
215
|
+
check ? checkNode(tn) : uncheckNode(tn)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const checkedArr = Array.from(checkedData)
|
|
220
|
+
|
|
221
|
+
const valueKey = fieldKey(props.valueKey, 'value')
|
|
222
|
+
const nextValues = checkedArr.map((item) => o(item).get(valueKey) as unknown)
|
|
223
|
+
|
|
224
|
+
lastCheckedSet = new Set(nextValues)
|
|
225
|
+
|
|
226
|
+
emit('update:checked', nextValues as any[], checkedArr)
|
|
227
|
+
|
|
228
|
+
getFlattedNodes()
|
|
229
|
+
|
|
230
|
+
nextTick(() => {
|
|
231
|
+
checkedByEvent = false
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { checkedData, toggleCheck, checkAll }
|
|
206
236
|
}
|