feat(components): [tree/tree-select] add check-on-click-leaf attribute (#19494)

* chore: last leaf node clickable on `show-checkbox`

* chore: revert

* chore: leaf node checkable

* test: checkable by click on leaf node

* chore: test & doc

* chore: add version

* feat: handle tree-v2

* test: tree-v2
This commit is contained in:
Noblet Ouways 2025-02-26 14:24:58 +01:00 committed by GitHub
parent 3ef10ec5b8
commit 0ed86e74ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 274 additions and 46 deletions

View File

@ -82,23 +82,24 @@ tree-v2/filter
### Attributes
| Name | Description | Type | Default |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------- |
| data | tree data | array | — |
| empty-text | text displayed when data is void | string | — |
| props | configuration options, see the following table | object | — |
| highlight-current | whether current node is highlighted | boolean | false |
| expand-on-click-node | whether to expand or collapse node when clicking on the node, if false, then expand or collapse node only when clicking on the arrow icon. | boolean | true |
| check-on-click-node | whether to check or uncheck node when clicking on the node, if false, the node can only be checked or unchecked by clicking on the checkbox. | boolean | false |
| default-expanded-keys | array of keys of initially expanded nodes | array | — |
| show-checkbox | whether node is selectable | boolean | false |
| check-strictly | whether checked state of a node not affects its father and child nodes when `show-checkbox` is `true` | boolean | false |
| default-checked-keys | array of keys of initially checked nodes | array | — |
| current-node-key | key of initially selected node | string / number | — |
| filter-method | this function will be executed on each node when use filter method. if return `false`, tree node will be hidden. | Function(value, data, node) | — |
| indent | horizontal indentation of nodes in adjacent levels in pixels | number | 16 |
| icon | custom tree node icon | `string \| Component` | — |
| item-size ^(2.2.33) | custom tree node height | number | 26 |
| Name | Description | Type | Default |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------- |
| data | tree data | array | — |
| empty-text | text displayed when data is void | string | — |
| props | configuration options, see the following table | object | — |
| highlight-current | whether current node is highlighted | boolean | false |
| expand-on-click-node | whether to expand or collapse node when clicking on the node, if false, then expand or collapse node only when clicking on the arrow icon. | boolean | true |
| check-on-click-node | whether to check or uncheck node when clicking on the node, if false, the node can only be checked or unchecked by clicking on the checkbox. | boolean | false |
| check-on-click-leaf ^(2.9.6) | whether to check or uncheck node when clicking on leaf node (last children). | ^[boolean] | true |
| default-expanded-keys | array of keys of initially expanded nodes | array | — |
| show-checkbox | whether node is selectable | boolean | false |
| check-strictly | whether checked state of a node not affects its father and child nodes when `show-checkbox` is `true` | boolean | false |
| default-checked-keys | array of keys of initially checked nodes | array | — |
| current-node-key | key of initially selected node | string / number | — |
| filter-method | this function will be executed on each node when use filter method. if return `false`, tree node will be hidden. | Function(value, data, node) | — |
| indent | horizontal indentation of nodes in adjacent levels in pixels | number | 16 |
| icon | custom tree node icon | `string \| Component` | — |
| item-size ^(2.2.33) | custom tree node height | number | 26 |
### props

View File

@ -125,33 +125,34 @@ tree/draggable
### Attributes
| Name | Description | Type | Default |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------- |
| data | tree data | ^[object]`Array<{[key: string]: any}>` | — |
| empty-text | text displayed when data is void | ^[string] | — |
| node-key | unique identity key name for nodes, its value should be unique across the whole tree | ^[string] | — |
| props | configuration options, see the following table | [props](#props) | — |
| render-after-expand | whether to render child nodes only after a parent node is expanded for the first time | ^[boolean] | true |
| load | method for loading subtree data, only works when `lazy` is true | ^[Function]`(node, resolve, reject) => void` | — |
| render-content | render function for tree node | ^[Function]`(h, { node, data, store }) => void` | — |
| highlight-current | whether current node is highlighted | ^[boolean] | false |
| default-expand-all | whether to expand all nodes by default | ^[boolean] | false |
| expand-on-click-node | whether to expand or collapse node when clicking on the node, if false, then expand or collapse node only when clicking on the arrow icon. | ^[boolean] | true |
| check-on-click-node | whether to check or uncheck node when clicking on the node, if false, the node can only be checked or unchecked by clicking on the checkbox. | ^[boolean] | false |
| auto-expand-parent | whether to expand father node when a child node is expanded | ^[boolean] | true |
| default-expanded-keys | array of keys of initially expanded nodes | ^[object]`Array<string \| number>` | — |
| show-checkbox | whether node is selectable | ^[boolean] | false |
| check-strictly | whether checked state of a node not affects its father and child nodes when `show-checkbox` is `true` | ^[boolean] | false |
| default-checked-keys | array of keys of initially checked nodes | ^[object]`Array<string \| number>` | — |
| current-node-key | key of initially selected node | ^[string] / ^[number] | — |
| filter-node-method | this function will be executed on each node when use filter method. if return `false`, tree node will be hidden. | ^[Function]`(value, data, node) => boolean` | — |
| accordion | whether only one node among the same level can be expanded at one time | ^[boolean] | false |
| indent | horizontal indentation of nodes in adjacent levels in pixels | ^[number] | 18 |
| icon | custom tree node icon component | ^[string] / ^[Component] | — |
| lazy | whether to lazy load leaf node, used with `load` attribute | ^[boolean] | false |
| draggable | whether enable tree nodes drag and drop | ^[boolean] | false |
| allow-drag | this function will be executed before dragging a node. If `false` is returned, the node can not be dragged | ^[Function]`(node) => boolean` | — |
| allow-drop | this function will be executed before the dragging node is dropped. If `false` is returned, the dragging node can not be dropped at the target node. `type` has three possible values: 'prev' (inserting the dragging node before the target node), 'inner' (inserting the dragging node to the target node) and 'next' (inserting the dragging node after the target node) | ^[Function]`(draggingNode, dropNode, type) => boolean` | — |
| Name | Description | Type | Default |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------- |
| data | tree data | ^[object]`Array<{[key: string]: any}>` | — |
| empty-text | text displayed when data is void | ^[string] | — |
| node-key | unique identity key name for nodes, its value should be unique across the whole tree | ^[string] | — |
| props | configuration options, see the following table | [props](#props) | — |
| render-after-expand | whether to render child nodes only after a parent node is expanded for the first time | ^[boolean] | true |
| load | method for loading subtree data, only works when `lazy` is true | ^[Function]`(node, resolve, reject) => void` | — |
| render-content | render function for tree node | ^[Function]`(h, { node, data, store }) => void` | — |
| highlight-current | whether current node is highlighted | ^[boolean] | false |
| default-expand-all | whether to expand all nodes by default | ^[boolean] | false |
| expand-on-click-node | whether to expand or collapse node when clicking on the node, if false, then expand or collapse node only when clicking on the arrow icon. | ^[boolean] | true |
| check-on-click-node | whether to check or uncheck node when clicking on the node, if false, the node can only be checked or unchecked by clicking on the checkbox. | ^[boolean] | false |
| check-on-click-leaf ^(2.9.6) | whether to check or uncheck node when clicking on leaf node (last children). | ^[boolean] | true |
| auto-expand-parent | whether to expand father node when a child node is expanded | ^[boolean] | true |
| default-expanded-keys | array of keys of initially expanded nodes | ^[object]`Array<string \| number>` | — |
| show-checkbox | whether node is selectable | ^[boolean] | false |
| check-strictly | whether checked state of a node not affects its father and child nodes when `show-checkbox` is `true` | ^[boolean] | false |
| default-checked-keys | array of keys of initially checked nodes | ^[object]`Array<string \| number>` | — |
| current-node-key | key of initially selected node | ^[string] / ^[number] | — |
| filter-node-method | this function will be executed on each node when use filter method. if return `false`, tree node will be hidden. | ^[Function]`(value, data, node) => boolean` | — |
| accordion | whether only one node among the same level can be expanded at one time | ^[boolean] | false |
| indent | horizontal indentation of nodes in adjacent levels in pixels | ^[number] | 18 |
| icon | custom tree node icon component | ^[string] / ^[Component] | — |
| lazy | whether to lazy load leaf node, used with `load` attribute | ^[boolean] | false |
| draggable | whether enable tree nodes drag and drop | ^[boolean] | false |
| allow-drag | this function will be executed before dragging a node. If `false` is returned, the node can not be dragged | ^[Function]`(node) => boolean` | — |
| allow-drop | this function will be executed before the dragging node is dropped. If `false` is returned, the dragging node can not be dropped at the target node. `type` has three possible values: 'prev' (inserting the dragging node before the target node), 'inner' (inserting the dragging node to the target node) and 'next' (inserting the dragging node after the target node) | ^[Function]`(draggingNode, dropNode, type) => boolean` | — |
### props

View File

@ -802,6 +802,45 @@ describe('TreeSelect.vue', () => {
expect(select.vm.modelValue).toEqual([5, 6, 4, 2])
})
test('check by click on leaf node', async () => {
const { tree, select } = createComponent({
props: {
showCheckbox: true,
},
})
const treeVm = tree.vm
expect(treeVm.getCheckedNodes().length).toEqual(0)
await tree.findAll('.el-tree-node__content')[0].trigger('click')
await tree.findAll('.el-tree-node__content')[1].trigger('click')
await tree.findAll('.el-tree-node__content')[2].trigger('click')
expect(select.vm.modelValue).toEqual(111)
expect(treeVm.getCheckedNodes().length).toEqual(3)
expect(treeVm.getCheckedNodes(true).length).toEqual(1)
})
test('show-checkbox :check-on-click-leaf="false"', async () => {
const { tree, select } = createComponent({
props: {
showCheckbox: true,
checkOnClickLeaf: false,
},
})
const treeVm = tree.vm
expect(treeVm.getCheckedNodes().length).toEqual(0)
await tree.findAll('.el-tree-node__content')[0].trigger('click')
await tree.findAll('.el-tree-node__content')[1].trigger('click')
await tree.findAll('.el-tree-node__content')[2].trigger('click')
expect(select.vm.modelValue).toBeUndefined()
expect(treeVm.getCheckedNodes().length).toEqual(0)
expect(treeVm.getCheckedNodes(true).length).toEqual(0)
})
test('no checkbox and check on click node', async () => {
const { select, tree } = createComponent({
props: {

View File

@ -68,6 +68,7 @@ interface TreeProps {
iconClass?: string
expandOnClickNode?: boolean
checkOnClickNode?: boolean
checkOnClickLeaf?: boolean
currentNodeKey?: TreeKey
filterMethod?: FilterMethod
}
@ -130,6 +131,7 @@ const createTree = (
:icon-class="iconClass"
:expand-on-click-node="expandOnClickNode"
:check-on-click-node="checkOnClickNode"
:check-on-click-leaf="checkOnClickLeaf"
:current-node-key="currentNodeKey"
:filter-method="filterMethod"
@node-click="onNodeClick"
@ -162,6 +164,7 @@ const createTree = (
iconClass: undefined,
expandOnClickNode: true,
checkOnClickNode: false,
checkOnClickLeaf: true,
currentNodeKey: undefined,
filterMethod: undefined,
...(options.data && options.data()),
@ -455,6 +458,137 @@ describe('Virtual Tree', () => {
expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(0)
})
test('showCheckbox checkOnClickLeaf', async () => {
const { wrapper, treeRef } = createTree({
data() {
return {
showCheckbox: true,
checkOnClickLeaf: true,
height: 400,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
expect(treeRef.getCheckedKeys()).toHaveLength(0)
let nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[1].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
expect(nodes).toHaveLength(7)
await nodes[2].trigger('click')
expect(treeRef.getCheckedKeys().toString()).toBe(['1-1-1'].toString())
await nodes[3].trigger('click')
expect(nodes).toHaveLength(7)
expect(treeRef.getCheckedKeys().toString()).toBe(
['1-1-1', '1-1-2', '1-1'].toString()
)
})
test('showCheckbox :checkOnClickLeaf="false"', async () => {
const { wrapper, treeRef } = createTree({
data() {
return {
showCheckbox: true,
checkOnClickLeaf: false,
height: 400,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
expect(treeRef.getCheckedKeys()).toHaveLength(0)
let nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[1].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
expect(nodes).toHaveLength(7)
await nodes[2].trigger('click')
expect(treeRef.getCheckedKeys()).toHaveLength(0)
})
test('defaultCheckedKeys', async () => {
const { treeRef } = createTree({
data() {

View File

@ -216,7 +216,11 @@ export function useTree(
if (props.expandOnClickNode) {
toggleExpand(node)
}
if (props.showCheckbox && props.checkOnClickNode && !node.disabled) {
if (
props.showCheckbox &&
(props.checkOnClickNode || (node.isLeaf && props.checkOnClickLeaf)) &&
!node.disabled
) {
toggleCheckbox(node, !isChecked(node), true)
}
}

View File

@ -107,6 +107,10 @@ export const treeProps = buildProps({
type: Boolean,
default: false,
},
checkOnClickLeaf: {
type: Boolean,
default: true,
},
currentNodeKey: {
type: definePropType<TreeKey>([String, Number]),
},

View File

@ -542,6 +542,42 @@ describe('Tree.vue', () => {
expect(args.checkedNodes.length).toEqual(3)
})
test('check by clicking on leaf node', async () => {
const { wrapper } = getTreeVm(`:props="defaultProps" show-checkbox`)
const treeVm = wrapper.findComponent(Tree).vm
expect(treeVm.getCheckedNodes().length).toEqual(0)
const secondTreeNodeWrapper = wrapper.findAll('.el-tree-node')[2]
await secondTreeNodeWrapper.trigger('click')
const secondNodeContentWrapper = secondTreeNodeWrapper.findAll(
'.el-tree-node__content'
)[1]
await secondNodeContentWrapper.trigger('click')
expect(treeVm.getCheckedNodes().length).toEqual(1)
})
test('show-checkbox :check-on-click-leaf="false"', async () => {
const { wrapper } = getTreeVm(
`:props="defaultProps" show-checkbox :check-on-click-leaf="false"`
)
const treeVm = wrapper.findComponent(Tree).vm
expect(treeVm.getCheckedNodes().length).toEqual(0)
const secondTreeNodeWrapper = wrapper.findAll('.el-tree-node')[2]
await secondTreeNodeWrapper.trigger('click')
const secondNodeContentWrapper = secondTreeNodeWrapper.findAll(
'.el-tree-node__content'
)[1]
await secondNodeContentWrapper.trigger('click')
expect(treeVm.getCheckedNodes().length).toEqual(0)
})
test('setCheckedNodes', async () => {
const { wrapper } = getTreeVm(
`:props="defaultProps" show-checkbox node-key="id"`

View File

@ -247,7 +247,11 @@ export default defineComponent({
handleExpandIconClick()
}
if (tree.props.checkOnClickNode && !props.node.disabled) {
if (
(tree.props.checkOnClickNode ||
(props.node.isLeaf && tree.props.checkOnClickLeaf)) &&
!props.node.disabled
) {
handleCheckChange(!props.node.checked)
}
tree.ctx.emit('node-click', props.node.data, props.node, instance, e)

View File

@ -104,6 +104,7 @@ export interface TreeComponentProps {
expandOnClickNode: boolean
defaultExpandAll: boolean
checkOnClickNode: boolean
checkOnClickLeaf: boolean
checkDescendants: boolean
autoExpandParent: boolean
defaultCheckedKeys: TreeKey[]

View File

@ -90,6 +90,10 @@ export default defineComponent({
default: true,
},
checkOnClickNode: Boolean,
checkOnClickLeaf: {
type: Boolean,
default: true,
},
checkDescendants: {
type: Boolean,
default: false,