From cdff8c16df9d7215a536c0329552ccbb447f0e3f Mon Sep 17 00:00:00 2001 From: yellworyan <877520264@qq.com> Date: Tue, 22 Apr 2025 16:25:28 +0800 Subject: [PATCH 1/3] fix:parent node not disabled when child nodes count greater than `maxCount` --- src/OptionList.tsx | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/OptionList.tsx b/src/OptionList.tsx index be4d9dfe..60d94069 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -3,7 +3,7 @@ import type { RefOptionListProps } from '@rc-component/select/lib/OptionList'; import type { TreeProps } from '@rc-component/tree'; import Tree from '@rc-component/tree'; import { UnstableContext } from '@rc-component/tree'; -import type { EventDataNode, ScrollTo } from '@rc-component/tree/lib/interface'; +import type { DataEntity, EventDataNode, ScrollTo } from '@rc-component/tree/lib/interface'; import KeyCode from '@rc-component/util/lib/KeyCode'; import useMemo from '@rc-component/util/lib/hooks/useMemo'; import * as React from 'react'; @@ -178,14 +178,31 @@ const OptionList: React.ForwardRefRenderFunction = (_, const isLeaf = (entity.children || []).length === 0; if (!isLeaf) { - const checkableChildren = entity.children.filter( - childTreeNode => - !childTreeNode.node.disabled && - !childTreeNode.node.disableCheckbox && - !checkedKeys.includes(childTreeNode.node[fieldNames.value]), - ); - - const checkableChildrenCount = checkableChildren.length; + const getLeafCheckableCount = (children: DataEntity[]): number => { + return children.reduce((count, current) => { + const currentValue = current.node[fieldNames.value]; + const currentEntity = valueEntities.get(currentValue); + const isCurrentLeaf = (currentEntity.children || []).length === 0; + + if (isCurrentLeaf) { + if ( + !current.node.disabled && + !current.node.disableCheckbox && + !checkedKeys.includes(currentValue) + ) { + return count + 1; + } + } else if ( + !current.node.disabled && + !current.node.disableCheckbox && + !checkedKeys.includes(currentValue) + ) { + return count + getLeafCheckableCount(currentEntity.children); + } + return count; + }, 0); + }; + const checkableChildrenCount = getLeafCheckableCount(entity.children); disabledCache.set(value, checkableChildrenCount > leftMaxCount); } else { disabledCache.set(value, false); From fa856216d3bc3571b4327c6194cf6f431137837d Mon Sep 17 00:00:00 2001 From: yellworyan <877520264@qq.com> Date: Tue, 22 Apr 2025 22:25:10 +0800 Subject: [PATCH 2/3] perf: code optimization --- examples/mutiple-with-maxCount.tsx | 103 ++++++++++++----------------- src/OptionList.tsx | 61 ++++++++++------- 2 files changed, 80 insertions(+), 84 deletions(-) diff --git a/examples/mutiple-with-maxCount.tsx b/examples/mutiple-with-maxCount.tsx index ba6c062a..06442bdb 100644 --- a/examples/mutiple-with-maxCount.tsx +++ b/examples/mutiple-with-maxCount.tsx @@ -2,61 +2,62 @@ import React, { useState } from 'react'; import TreeSelect from '../src'; export default () => { - const [value, setValue] = useState(['1']); - const [checkValue, setCheckValue] = useState(['1']); + const [value, setValue] = useState(); + const [checkValue, setCheckValue] = useState(); const treeData = [ { - key: '1', - value: '1', - title: '1', + title: 'Parent 1', + value: 'parent1', children: [ { - key: '1-1', - value: '1-1', - title: '1-1', + title: 'Child 1-1', + value: 'child1-1', }, { - key: '1-2', - value: '1-2', - title: '1-2', - disabled: true, + title: 'Child 1-2', + value: 'child1-2', children: [ { - key: '1-2-1', - value: '1-2-1', - title: '1-2-1', - disabled: true, + title: 'Child 1-2-1', + value: 'child1-2-1', + children: [ + { + title: 'child 1-2-1-1', + value: 'child1-2-1-1', + children: [ + { + title: 'child 1-2-1-1-1', + value: 'child1-2-1-1-1', + }, + ], + }, + { + title: 'child 1-2-1-2', + value: 'child1-2-1-2', + }, + { + title: 'child 1-2-1-3', + value: 'child1-2-1-3', + }, + ], }, { - key: '1-2-2', - value: '1-2-2', - title: '1-2-2', + title: 'Child 1-2-2', + value: 'child1-2-2', + }, + { + title: 'Child 1-2-3', + value: 'child1-2-3', + }, + { + title: 'Child 1-2-4', + value: 'child1-2-4', }, ], }, - { - key: '1-3', - value: '1-3', - title: '1-3', - }, ], }, - { - key: '2', - value: '2', - title: '2', - }, - { - key: '3', - value: '3', - title: '3', - }, - { - key: '4', - value: '4', - title: '4', - }, ]; const onChange = (val: string[]) => { @@ -69,36 +70,18 @@ export default () => { return ( <> -

multiple with maxCount

- -

checkable with maxCount

+

maxCount = 3

-

checkable with maxCount and treeCheckStrictly

- ); }; diff --git a/src/OptionList.tsx b/src/OptionList.tsx index 60d94069..2527625e 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -178,32 +178,45 @@ const OptionList: React.ForwardRefRenderFunction = (_, const isLeaf = (entity.children || []).length === 0; if (!isLeaf) { - const getLeafCheckableCount = (children: DataEntity[]): number => { - return children.reduce((count, current) => { - const currentValue = current.node[fieldNames.value]; - const currentEntity = valueEntities.get(currentValue); - const isCurrentLeaf = (currentEntity.children || []).length === 0; - - if (isCurrentLeaf) { - if ( - !current.node.disabled && - !current.node.disableCheckbox && - !checkedKeys.includes(currentValue) - ) { - return count + 1; + const visited = new Set(); + const stack: DataEntity[] = [entity]; + let checkableCount = 0; + + while (stack.length > 0) { + const currentEntity = stack.pop(); + const currentValue = currentEntity.node[fieldNames.value]; + + if (visited.has(currentValue)) { + continue; + } + visited.add(currentValue); + + const isCurrentLeaf = (currentEntity.children || []).length === 0; + const isDisabled = + currentEntity.node.disabled || + currentEntity.node.disableCheckbox || + checkedKeys.includes(currentValue); + + if (isCurrentLeaf) { + if (!isDisabled) { + checkableCount++; + + // break early + if (checkableCount > leftMaxCount) { + disabledCache.set(value, true); + break; } - } else if ( - !current.node.disabled && - !current.node.disableCheckbox && - !checkedKeys.includes(currentValue) - ) { - return count + getLeafCheckableCount(currentEntity.children); } - return count; - }, 0); - }; - const checkableChildrenCount = getLeafCheckableCount(entity.children); - disabledCache.set(value, checkableChildrenCount > leftMaxCount); + continue; + } + + if (!isDisabled) { + for (let i = currentEntity.children.length - 1; i >= 0; i--) { + stack.push(currentEntity.children[i]); + } + } + } + disabledCache.set(value, checkableCount > leftMaxCount); } else { disabledCache.set(value, false); } From b69ed2b498a56081d5c1eb0a5cc7ed2846046842 Mon Sep 17 00:00:00 2001 From: yellworyan <877520264@qq.com> Date: Wed, 23 Apr 2025 17:32:42 +0800 Subject: [PATCH 3/3] chore: unchange demo --- examples/mutiple-with-maxCount.tsx | 103 +++++++++++++++++------------ 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/examples/mutiple-with-maxCount.tsx b/examples/mutiple-with-maxCount.tsx index 06442bdb..ba6c062a 100644 --- a/examples/mutiple-with-maxCount.tsx +++ b/examples/mutiple-with-maxCount.tsx @@ -2,62 +2,61 @@ import React, { useState } from 'react'; import TreeSelect from '../src'; export default () => { - const [value, setValue] = useState(); - const [checkValue, setCheckValue] = useState(); + const [value, setValue] = useState(['1']); + const [checkValue, setCheckValue] = useState(['1']); const treeData = [ { - title: 'Parent 1', - value: 'parent1', + key: '1', + value: '1', + title: '1', children: [ { - title: 'Child 1-1', - value: 'child1-1', + key: '1-1', + value: '1-1', + title: '1-1', }, { - title: 'Child 1-2', - value: 'child1-2', + key: '1-2', + value: '1-2', + title: '1-2', + disabled: true, children: [ { - title: 'Child 1-2-1', - value: 'child1-2-1', - children: [ - { - title: 'child 1-2-1-1', - value: 'child1-2-1-1', - children: [ - { - title: 'child 1-2-1-1-1', - value: 'child1-2-1-1-1', - }, - ], - }, - { - title: 'child 1-2-1-2', - value: 'child1-2-1-2', - }, - { - title: 'child 1-2-1-3', - value: 'child1-2-1-3', - }, - ], + key: '1-2-1', + value: '1-2-1', + title: '1-2-1', + disabled: true, }, { - title: 'Child 1-2-2', - value: 'child1-2-2', - }, - { - title: 'Child 1-2-3', - value: 'child1-2-3', - }, - { - title: 'Child 1-2-4', - value: 'child1-2-4', + key: '1-2-2', + value: '1-2-2', + title: '1-2-2', }, ], }, + { + key: '1-3', + value: '1-3', + title: '1-3', + }, ], }, + { + key: '2', + value: '2', + title: '2', + }, + { + key: '3', + value: '3', + title: '3', + }, + { + key: '4', + value: '4', + title: '4', + }, ]; const onChange = (val: string[]) => { @@ -70,18 +69,36 @@ export default () => { return ( <> -

maxCount = 3

+

multiple with maxCount

+

checkable with maxCount

+ +

checkable with maxCount and treeCheckStrictly

+ ); };