fix(components): [tree-select] emit change when toggling node labels (#22863)

* fix(components): [tree-select] emit change when toggling node labels

closed #22862

* fix(components): [tree-select] avoid double change on checkbox

closed #22862

* refactor(components): [tree-select] extract update helper

closed #22862

---------

Co-authored-by: alex.yang <alex.yang@hytechc.com>
Co-authored-by: 云游君 <me@yunyoujun.cn>
This commit is contained in:
mortis.yi
2026-02-18 07:35:09 +08:00
committed by GitHub
parent 90ffc112d8
commit e53956863a
3 changed files with 103 additions and 41 deletions

View File

@@ -499,6 +499,54 @@ describe('TreeSelect.vue', () => {
expect(select.vm.modelValue).toBe(undefined)
})
test('emit change when toggling node via label click', async () => {
const handleChange = vi.fn()
const { tree } = createComponent({
props: {
showCheckbox: true,
checkOnClickNode: true,
onChange: handleChange,
},
})
await nextTick()
const target = tree.findAll('.el-tree-node__content').slice(-1)[0]
await target.trigger('click')
await nextTick()
expect(handleChange).toHaveBeenLastCalledWith(111)
await target.trigger('click')
await nextTick()
expect(handleChange).toHaveBeenLastCalledWith(undefined)
expect(handleChange).toHaveBeenCalledTimes(2)
})
test('emit change once when toggling node via checkbox click', async () => {
const handleChange = vi.fn()
const { tree } = createComponent({
props: {
showCheckbox: true,
checkOnClickNode: true,
onChange: handleChange,
},
})
await nextTick()
const checkbox = tree
.findAll('.el-tree-node__content .el-checkbox__original')
.slice(-1)[0]
await checkbox.trigger('click')
await nextTick()
expect(handleChange).toHaveBeenLastCalledWith(111)
await checkbox.trigger('click')
await nextTick()
expect(handleChange).toHaveBeenLastCalledWith(undefined)
expect(handleChange).toHaveBeenCalledTimes(2)
})
test('expand selected node`s parent in first time', async () => {
const value = ref(111)
const { tree } = createComponent({

View File

@@ -26,19 +26,27 @@ export default defineComponent({
},
},
setup(props, context) {
const { slots, expose } = context
const { slots, expose, emit, attrs } = context
const childAttrs = {
...attrs,
onChange: undefined,
}
const select = ref<SelectInstance>()
const tree = ref<TreeInstance>()
const key = computed(() => props.nodeKey || props.valueKey || 'value')
const selectProps = useSelect(props, context, { select, tree, key })
const { cacheOptions, ...treeProps } = useTree(props, context, {
select,
tree,
key,
})
const selectProps = useSelect(props, { attrs, emit }, { select, tree, key })
const { cacheOptions, ...treeProps } = useTree(
props,
{ attrs: childAttrs, slots, emit },
{
select,
tree,
key,
}
)
// expose ElTree/ElSelect methods
const methods = reactive({})

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { computed, nextTick, toRefs, watch } from 'vue'
import { isEqual, isNil, pick } from 'lodash-unified'
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { escapeStringRegexp, isEmpty, isFunction } from '@element-plus/utils'
import ElTree from '@element-plus/components/tree'
import TreeSelectOption from './tree-select-option'
@@ -121,6 +121,17 @@ export const useTree = (
})
}
const emitChange = (val: any | any[]) => {
if (!isEqual(props.modelValue, val)) {
emit(CHANGE_EVENT, val)
}
}
function update(val) {
emit(UPDATE_MODEL_EVENT, val)
emitChange(val)
}
return {
...pick(toRefs(props), Object.keys(ElTree.props)),
...attrs,
@@ -202,9 +213,8 @@ export const useTree = (
const checkedKeys = cachedKeys.concat(uncachedCheckedKeys)
if (props.checkStrictly) {
emit(
UPDATE_MODEL_EVENT,
// Checking for changes may come from `check-on-node-click`
// Checking for changes may come from `check-on-node-click`
update(
props.multiple
? checkedKeys
: checkedKeys.includes(dataValue)
@@ -213,40 +223,36 @@ export const useTree = (
)
}
// only can select leaf node
else {
if (props.multiple) {
const childKeys = getChildCheckedKeys()
else if (props.multiple) {
const childKeys = getChildCheckedKeys()
update(cachedKeys.concat(childKeys))
} else {
// select first leaf node when check parent
const firstLeaf = treeFind(
[data],
(data) =>
!isValidArray(getNodeValByProp('children', data)) &&
!getNodeValByProp('disabled', data),
(data) => getNodeValByProp('children', data)
)
const firstLeafKey = firstLeaf
? getNodeValByProp('value', firstLeaf)
: undefined
emit(UPDATE_MODEL_EVENT, cachedKeys.concat(childKeys))
} else {
// select first leaf node when check parent
const firstLeaf = treeFind(
// unselect when any child checked
const hasCheckedChild =
isValidValue(props.modelValue) &&
!!treeFind(
[data],
(data) =>
!isValidArray(getNodeValByProp('children', data)) &&
!getNodeValByProp('disabled', data),
(data) => getNodeValByProp('value', data) === props.modelValue,
(data) => getNodeValByProp('children', data)
)
const firstLeafKey = firstLeaf
? getNodeValByProp('value', firstLeaf)
: undefined
// unselect when any child checked
const hasCheckedChild =
isValidValue(props.modelValue) &&
!!treeFind(
[data],
(data) => getNodeValByProp('value', data) === props.modelValue,
(data) => getNodeValByProp('children', data)
)
emit(
UPDATE_MODEL_EVENT,
firstLeafKey === props.modelValue || hasCheckedChild
? undefined
: firstLeafKey
)
}
update(
firstLeafKey === props.modelValue || hasCheckedChild
? undefined
: firstLeafKey
)
}
nextTick(() => {
@@ -289,7 +295,7 @@ export const useTree = (
)
const childKeys = getChildCheckedKeys()
emit(UPDATE_MODEL_EVENT, cachedKeys.concat(childKeys))
update(cachedKeys.concat(childKeys))
}
})
},