From ce4f4778e370562ee8bf4130c663d2c182d37d90 Mon Sep 17 00:00:00 2001 From: saller Date: Mon, 8 Apr 2024 13:50:42 +0800 Subject: [PATCH] fix(comp:tree-select): removing item from selector doesn't uncheck tree node (#1877) --- packages/cdk/utils/src/tree.ts | 4 +- .../components/tree-select/src/TreeSelect.tsx | 3 +- .../src/composables/useSelectedState.ts | 9 +- .../components/tree/__tests__/tree.spec.ts | 10 +- packages/components/tree/src/Tree.tsx | 23 +- .../tree/src/composables/useCheckable.ts | 239 +++++++----------- .../tree/src/composables/useDataSource.ts | 227 +++++++++-------- .../tree/src/composables/useExpandable.ts | 28 +- .../components/tree/src/node/Checkbox.tsx | 11 +- packages/components/tree/src/types.ts | 1 + .../utils/src/useTreeCheckStateResolver.ts | 4 +- 11 files changed, 264 insertions(+), 295 deletions(-) diff --git a/packages/cdk/utils/src/tree.ts b/packages/cdk/utils/src/tree.ts index 1eb4331ca..f65928446 100644 --- a/packages/cdk/utils/src/tree.ts +++ b/packages/cdk/utils/src/tree.ts @@ -36,13 +36,13 @@ export function traverseTree, C extends keyof V>( export function mapTree, R extends object, C extends keyof V>( data: V[], childrenKey: C, - mapFn: (item: V, parents: V[]) => R | undefined, + mapFn: (item: V, parents: V[], index: number) => R | undefined, ): (R & TreeTypeData)[] { const map = (_data: V[], parents: V[]) => { const res: (TreeTypeData & R)[] = [] for (let idx = 0; idx < _data.length; idx++) { const item = _data[idx] - const mappedItem = mapFn(item, parents) as R & TreeTypeData + const mappedItem = mapFn(item, parents, idx) as R & TreeTypeData if (!mappedItem) { continue } diff --git a/packages/components/tree-select/src/TreeSelect.tsx b/packages/components/tree-select/src/TreeSelect.tsx index 0fee3572c..7dd4fe30a 100644 --- a/packages/components/tree-select/src/TreeSelect.tsx +++ b/packages/components/tree-select/src/TreeSelect.tsx @@ -45,6 +45,7 @@ export default defineComponent({ const mergedLabelKey = computed(() => props.labelKey ?? config.labelKey) const triggerRef = ref() + const treeRef = ref() const [inputValue, setInputValue] = useState('') const focus = () => triggerRef.value?.focus() const blur = () => triggerRef.value?.blur() @@ -63,11 +64,11 @@ export default defineComponent({ const { selectedValue, selectedNodes, changeSelected, handleRemove, handleClear } = useSelectedState( props, accessor, + treeRef, mergedNodeMap, ) const [overlayOpened, setOverlayOpened] = useControlledProp(props, 'open', false) - const treeRef = ref() const scrollTo: VirtualScrollToFn = options => { treeRef.value?.scrollTo(options) } diff --git a/packages/components/tree-select/src/composables/useSelectedState.ts b/packages/components/tree-select/src/composables/useSelectedState.ts index d8c52bc28..3fabd45f8 100644 --- a/packages/components/tree-select/src/composables/useSelectedState.ts +++ b/packages/components/tree-select/src/composables/useSelectedState.ts @@ -7,7 +7,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { type ComputedRef, computed, toRaw } from 'vue' +import type { TreeInstance } from '@idux/components/tree' + +import { type ComputedRef, type Ref, computed, toRaw } from 'vue' import { type FormAccessor } from '@idux/cdk/forms' import { type VKey, callEmit, convertArray } from '@idux/cdk/utils' @@ -15,8 +17,6 @@ import { type VKey, callEmit, convertArray } from '@idux/cdk/utils' import { type MergedNode } from './useDataSource' import { type TreeSelectNode, type TreeSelectProps } from '../types' -//import { generateOption } from '../utils/generateOption' - export interface SelectedStateContext { selectedValue: ComputedRef selectedNodes: ComputedRef @@ -28,6 +28,7 @@ export interface SelectedStateContext { export function useSelectedState( props: TreeSelectProps, accessor: FormAccessor, + treeRef: Ref, mergedNodeMap: ComputedRef>, ): SelectedStateContext { const selectedValue = computed(() => convertArray(accessor.value)) @@ -54,7 +55,7 @@ export function useSelectedState( } const handleRemove = (key: VKey) => { - setValue(selectedValue.value.filter(item => key !== item)) + treeRef.value?._handleCheck(key) } const handleClear = (evt: MouseEvent) => { diff --git a/packages/components/tree/__tests__/tree.spec.ts b/packages/components/tree/__tests__/tree.spec.ts index feec552b1..812e306ca 100644 --- a/packages/components/tree/__tests__/tree.spec.ts +++ b/packages/components/tree/__tests__/tree.spec.ts @@ -193,16 +193,16 @@ describe('Tree', () => { const allNodes = wrapper.findAll('.ix-tree-node') - expect(allNodes[0].find('.ix-checkbox-checked').exists()).toBe(false) + expect(allNodes[0].find('.ix-checkbox-checked').exists()).toBe(true) expect(allNodes[1].find('.ix-checkbox-checked').exists()).toBe(true) // 0-1 exclude disabled - expect(allNodes[2].find('.ix-checkbox-checked').exists()).toBe(false) + expect(allNodes[2].find('.ix-checkbox-checked').exists()).toBe(true) expect(allNodes[3].find('.ix-checkbox-checked').exists()).toBe(true) // 0-0, unchecked await allNodes[1].find('input').setValue(false) - expect(onUpdateCheckedKeys).toBeCalledWith(['0-2']) + expect(onUpdateCheckedKeys).toBeCalledWith(['0-1', '0-2']) await wrapper.setProps({ checkedKeys: [] }) @@ -262,8 +262,8 @@ describe('Tree', () => { await wrapper.setProps({ checkedKeys: [] }) expect(allNodes[0].find('.ix-checkbox-checked').exists()).toBe(false) - expect(allNodes[0].find('.ix-checkbox-checked').exists()).toBe(false) - expect(allNodes[0].find('.ix-checkbox-checked').exists()).toBe(false) + expect(allNodes[1].find('.ix-checkbox-checked').exists()).toBe(false) + expect(allNodes[2].find('.ix-checkbox-checked').exists()).toBe(false) //0, checked await allNodes[0].find('input').setValue(true) diff --git a/packages/components/tree/src/Tree.tsx b/packages/components/tree/src/Tree.tsx index 3195e41d8..666843e30 100644 --- a/packages/components/tree/src/Tree.tsx +++ b/packages/components/tree/src/Tree.tsx @@ -62,21 +62,26 @@ export default defineComponent({ const mergedShowLine = computed(() => props.showLine ?? config.showLine) const mergedBlocked = computed(() => props.blocked ?? config.blocked) const mergedCheckOnClick = computed(() => props.checkable && props.checkOnClick) - const { mergedNodes, mergedNodeMap } = useMergeNodes(props, mergedChildrenKey, mergedGetKey, mergedLabelKey) + const { mergedNodes, mergedNodeMap, parentKeyMap, depthMap, setLoadedNodes } = useMergeNodes( + props, + mergedChildrenKey, + mergedGetKey, + mergedLabelKey, + ) const { searchedKeys } = useSearchable(props, mergedNodeMap, mergedLabelKey) const expandableContext = useExpandable( props, config, mergedChildrenKey, - mergedGetKey, - mergedLabelKey, mergedNodeMap, searchedKeys, + setLoadedNodes, ) const flattedNodes = useFlattedNodes(mergedNodes, expandableContext, props, searchedKeys) - const checkableContext = useCheckable(props, mergedNodeMap) + const checkableContext = useCheckable(props, mergedNodes, mergedNodeMap, parentKeyMap, depthMap) const dragDropContext = useDragDrop(props, config, expandableContext) const selectableContext = useSelectable(props, mergedNodeMap) + const { handleCheck } = checkableContext provide(treeToken, { props, @@ -155,6 +160,15 @@ export default defineComponent({ const _getFlattedNodes = () => { return flattedNodes.value } + const _handleCheck = (key: VKey) => { + const node = mergedNodeMap.value.get(key) + + if (!node) { + return + } + + handleCheck(node) + } expose({ focus, @@ -166,6 +180,7 @@ export default defineComponent({ // private _getFlattedNodes, + _handleCheck, }) const handleScrolledChange = (startIndex: number, endIndex: number, visibleNodes: MergedNode[]) => { diff --git a/packages/components/tree/src/composables/useCheckable.ts b/packages/components/tree/src/composables/useCheckable.ts index 5356e3ee9..03cb0f694 100644 --- a/packages/components/tree/src/composables/useCheckable.ts +++ b/packages/components/tree/src/composables/useCheckable.ts @@ -5,34 +5,59 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type ComputedRef, type WritableComputedRef, computed } from 'vue' +import { type ComputedRef, type WritableComputedRef, computed, ref, watch } from 'vue' import { isNil } from 'lodash-es' import { type VKey, callEmit, useControlledProp } from '@idux/cdk/utils' -import { type CascaderStrategy } from '@idux/components/cascader' +import { type TreeCheckStateResolverContext, useTreeCheckStateResolver } from '@idux/components/utils' import { type MergedNode } from './useDataSource' import { type TreeNode, type TreeProps } from '../types' -import { callChange, getChildrenKeys, getParentKeys } from '../utils' +import { callChange } from '../utils' export interface CheckableContext { checkedKeys: WritableComputedRef - allCheckedKeys: ComputedRef - checkDisabledKeys: ComputedRef - indeterminateKeys: ComputedRef + isChecked: (key: VKey) => boolean + isIndeterminate: (key: VKey) => boolean handleCheck: (node: MergedNode) => void } -export function useCheckable(props: TreeProps, mergedNodeMap: ComputedRef>): CheckableContext { +export function useCheckable( + props: TreeProps, + mergedNodes: ComputedRef, + mergedNodeMap: ComputedRef>, + parentKeyMap: ComputedRef>, + depthMap: ComputedRef>, +): CheckableContext { const [checkedKeys, setCheckedKeys] = useControlledProp(props, 'checkedKeys', () => []) - const checkDisabledKeys = computed(() => { - const disabledKeys: VKey[] = [] + const resolverContext = computed>(() => { + return { + data: mergedNodes.value, + dataMap: mergedNodeMap.value, + parentKeyMap: parentKeyMap.value, + depthMap: depthMap.value, + } + }) + const childrenKey = ref('children' as const) + const getKey = ref((item: MergedNode) => item.key) + const cascaderStrategy = computed(() => props.cascaderStrategy) + + const checkedStateResolver = useTreeCheckStateResolver(resolverContext, childrenKey, getKey, cascaderStrategy) + const allCheckedStateResolver = useTreeCheckStateResolver( + resolverContext, + childrenKey, + getKey, + computed(() => (cascaderStrategy.value === 'off' ? 'off' : 'all')), + ) + + const checkDisabledKeySet = computed(() => { + const disabledKeys = new Set() if (props.checkable) { mergedNodeMap.value.forEach((node, key) => { if (node.checkDisabled) { - disabledKeys.push(key) + disabledKeys.add(key) } }) } @@ -40,69 +65,77 @@ export function useCheckable(props: TreeProps, mergedNodeMap: ComputedRef { - if (props.cascaderStrategy !== 'off') { - return findAllCheckedKeys(mergedNodeMap.value, checkedKeys.value, checkDisabledKeys.value) - } else { - return checkedKeys.value - } + const allCheckedKeySet = computed(() => { + const keys = allCheckedStateResolver.appendKeys([], checkedKeys.value) + + return new Set(keys) }) - const indeterminateKeys = computed(() => { - const _checkedKeys = allCheckedKeys.value - if (_checkedKeys.length === 0 || props.cascaderStrategy === 'off') { - return [] + const indeterminateKeySet = computed(() => { + const _checkedKeySet = allCheckedKeySet.value + if (_checkedKeySet.size === 0 || props.cascaderStrategy === 'off') { + return new Set() } - const indeterminateKeySet = new Set() + const keySet = new Set() const nodeMap = mergedNodeMap.value - _checkedKeys.forEach(key => { + _checkedKeySet.forEach(key => { const { parentKey } = nodeMap.get(key) || {} if (!isNil(parentKey)) { let parent = nodeMap.get(parentKey) - if (parent && !_checkedKeys.includes(parent.key)) { - indeterminateKeySet.add(parentKey) + if (parent && !_checkedKeySet.has(parent.key)) { + keySet.add(parentKey) while (parent && !isNil(parent.parentKey)) { - indeterminateKeySet.add(parent.parentKey) + keySet.add(parent.parentKey) parent = nodeMap.get(parent.parentKey) } } } }) - return [...indeterminateKeySet] + return keySet }) + const isCheckDisabled = (key: VKey) => { + return checkDisabledKeySet.value.has(key) + } + + const isChecked = (key: VKey) => { + return allCheckedKeySet.value.has(key) + } + + const isIndeterminate = (key: VKey) => { + return indeterminateKeySet.value.has(key) + } + const handleCheck = (node: MergedNode) => { const currKey = node.key - const nodeMap = mergedNodeMap.value - const disabledKeys = checkDisabledKeys.value - const cascaderStrategy = props.cascaderStrategy - const cascaderEnabled = cascaderStrategy !== 'off' - const childrenKeys = cascaderEnabled ? getChildrenKeys(node, disabledKeys) : [] - const index = allCheckedKeys.value.indexOf(currKey) - - let tempKeys = [...allCheckedKeys.value] - const checked = - index > -1 || - (!!childrenKeys.length && - childrenKeys.every(key => tempKeys.includes(key) || indeterminateKeys.value.includes(key))) - - if (checked) { - const parentKeys = cascaderEnabled ? getParentKeys(nodeMap, node, disabledKeys) : [] - tempKeys.splice(index, 1) - tempKeys = tempKeys.filter(key => !parentKeys.includes(key) && !childrenKeys.includes(key)) - } else { - tempKeys.push(currKey) - cascaderEnabled && setParentChecked(nodeMap, node, tempKeys, disabledKeys) - tempKeys.push(...childrenKeys) + if (node.checkDisabled) { + return } - handleChange( - checked, - node.rawNode, - processAllCheckedKeysByCheckStrategy(cascaderStrategy, tempKeys, nodeMap, disabledKeys), + const children = cascaderStrategy.value !== 'off' ? node.children ?? [] : [] + const checked = + isChecked(currKey) || + (!!children.length && + children.every(child => isChecked(child.key) || isIndeterminate(child.key) || isCheckDisabled(child.key))) + + const _checkedKeys = checkedStateResolver.appendKeys([], checkedKeys.value) + const newCheckedKeys = checkedStateResolver[checked ? 'removeKeys' : 'appendKeys'](_checkedKeys, [currKey]) + + const disabledKeys = checked + ? [...allCheckedKeySet.value].filter(key => isCheckDisabled(key)) + : (cascaderStrategy.value === 'parent' + ? allCheckedStateResolver.appendKeys([], newCheckedKeys) + : newCheckedKeys + ).filter(key => isCheckDisabled(key) && !allCheckedKeySet.value.has(key)) + + const resolvedCheckedKeys = checkedStateResolver[checked ? 'appendKeys' : 'removeKeys']( + newCheckedKeys, + disabledKeys, ) + + handleChange(checked, node.rawNode, resolvedCheckedKeys) } const handleChange = (checked: boolean, rawNode: TreeNode, newKeys: VKey[]) => { @@ -112,105 +145,15 @@ export function useCheckable(props: TreeProps, mergedNodeMap: ComputedRef { + const newCheckedKeys = checkedStateResolver.appendKeys([], checkedKeys.value) + setCheckedKeys(newCheckedKeys) + }) + return { checkedKeys, - allCheckedKeys, - checkDisabledKeys, - indeterminateKeys, + isChecked, + isIndeterminate, handleCheck, } } - -function setParentChecked( - dataMap: Map, - currNode: MergedNode | undefined, - tempKeys: VKey[], - disabledKeys: VKey[], -) { - let parentSelected = true - while (parentSelected && currNode && !isNil(currNode.parentKey)) { - const parent = dataMap.get(currNode.parentKey) - if (parent && !disabledKeys.includes(currNode.parentKey)) { - parentSelected = parent.children!.every(item => disabledKeys.includes(item.key) || tempKeys.includes(item.key)) - if (parentSelected) { - tempKeys.push(currNode.parentKey) - } - } - currNode = parent - } -} -function processAllCheckedKeysByCheckStrategy( - cascaderStrategy: CascaderStrategy, - checkedKys: VKey[], - dataMap: Map, - disabledKeys: VKey[], -) { - if (cascaderStrategy === 'off') { - return [...new Set(checkedKys)] - } - - let res: VKey[] = [] - checkedKys = filterCheckedKeysWithDisabled(dataMap, checkedKys, disabledKeys) - - // 将禁用且勾选的节点先push - if (disabledKeys.length) { - res = checkedKys.filter(item => disabledKeys.includes(item)) - } - - for (const checkedKey of checkedKys) { - const currNode = dataMap.get(checkedKey) - if (currNode) { - const selfKey = currNode.key - const parentKey = currNode.parentKey - - if (cascaderStrategy === 'parent') { - if (!checkedKys.includes(parentKey!)) { - res.push(selfKey) - } - } else if (cascaderStrategy === 'child') { - if (currNode.isLeaf) { - res.push(selfKey) - } - } else { - res.push(selfKey) - } - } - } - - return [...new Set(res)] -} - -function findAllCheckedKeys(dataMap: Map, checkedKys: VKey[], disabledKeys: VKey[]) { - let res = [...checkedKys] - let lastParentKey - - for (const checkedKey of checkedKys) { - const currNode = dataMap.get(checkedKey) - const parentKey = currNode?.parentKey - const childrenKeys = getChildrenKeys(currNode, disabledKeys) - res = res.concat(childrenKeys) - if (!isNil(parentKey) && lastParentKey !== parentKey) { - setParentChecked(dataMap, currNode, res, disabledKeys) - lastParentKey = parentKey - } - } - - res = filterCheckedKeysWithDisabled(dataMap, [...new Set(res)], disabledKeys) - - return res -} - -// 把禁用且未勾选节点的父节点从checkedKeys中排除 -function filterCheckedKeysWithDisabled(dataMap: Map, tempKeys: VKey[], disabledKeys: VKey[]) { - let res = [...tempKeys] - if (disabledKeys.length) { - for (const disabledKey of disabledKeys) { - if (!res.includes(disabledKey)) { - const currNode = dataMap.get(disabledKey) - const parentKeys = getParentKeys(dataMap, currNode, disabledKeys) - res = res.filter(key => !parentKeys.includes(key)) - } - } - } - return res -} diff --git a/packages/components/tree/src/composables/useDataSource.ts b/packages/components/tree/src/composables/useDataSource.ts index 10b286d9e..b8a1f957a 100644 --- a/packages/components/tree/src/composables/useDataSource.ts +++ b/packages/components/tree/src/composables/useDataSource.ts @@ -8,13 +8,10 @@ import type { ExpandableContext } from './useExpandable' import type { TreeNode, TreeNodeDisabled, TreeProps } from '../types' import type { VKey } from '@idux/cdk/utils' -import type { CascaderStrategy } from '@idux/components/cascader' import type { GetKeyFn } from '@idux/components/utils' import type { ComputedRef } from 'vue' -import { computed } from 'vue' - -import { isNil } from 'lodash-es' +import { computed, ref } from 'vue' export interface MergedNode { children?: MergedNode[] @@ -42,18 +39,57 @@ export function useMergeNodes( ): { mergedNodes: ComputedRef mergedNodeMap: ComputedRef> + parentKeyMap: ComputedRef> + depthMap: ComputedRef> + setLoadedNodes: (key: VKey, nodes: TreeNode[]) => void } { - const mergedNodes = computed(() => - convertMergeNodes(props, props.dataSource, mergedChildrenKey.value, mergedGetKey.value, mergedLabelKey.value), - ) - - const mergedNodeMap = computed(() => { - const map = new Map() - convertMergedNodeMap(mergedNodes.value, map) - return map + const loadedNodes = ref>({}) + + const setLoadedNodes = (key: VKey, nodes: TreeNode[]) => { + loadedNodes.value[key] = nodes + } + const getLoadedNodes = (key: VKey): TreeNode[] | undefined => { + return loadedNodes.value[key] + } + + const context = computed(() => { + const dataKeyMap = new Map() + const parentKeyMap = new Map() + const depthMap = new Map() + + const mergedNodes = convertMergeNodes( + props.dataSource, + getLoadedNodes, + { + props, + childrenKey: mergedChildrenKey.value, + getKey: mergedGetKey.value, + labelKey: mergedLabelKey.value, + parentKey: undefined, + parentLevel: -1, + parentsDisabled: [], + }, + { + dataKeyMap, + parentKeyMap, + depthMap, + }, + ) + + return { + mergedNodes, + dataKeyMap, + parentKeyMap, + depthMap, + } }) - return { mergedNodes, mergedNodeMap } + const mergedNodes = computed(() => context.value.mergedNodes) + const mergedNodeMap = computed(() => context.value.dataKeyMap) + const parentKeyMap = computed(() => context.value.parentKeyMap) + const depthMap = computed(() => context.value.depthMap) + + return { mergedNodes, mergedNodeMap, parentKeyMap, depthMap, setLoadedNodes } } export function useFlattedNodes( @@ -81,102 +117,87 @@ export function useFlattedNodes( } export function convertMergeNodes( - props: TreeProps, nodes: TreeNode[], - childrenKey: string, - getKey: GetKeyFn, - labelKey: string, - parentKey?: VKey, - parentLevel?: number, - parentDisabled?: TreeNodeDisabled, + getLoadedNodes: (key: VKey) => TreeNode[] | undefined, + options: { + props: TreeProps + childrenKey: string + getKey: GetKeyFn + labelKey: string + parentKey: VKey | undefined + parentLevel: number + parentsDisabled: TreeNodeDisabled[] + }, + maps: { + dataKeyMap: Map + parentKeyMap: Map + depthMap: Map + }, ): MergedNode[] { + const { props, childrenKey, getKey, labelKey, parentKey, parentLevel, parentsDisabled } = options const { cascaderStrategy, disabled, loadChildren } = props - const level = isNil(parentLevel) ? -1 : parentLevel - - return nodes.map((node, index) => - convertMergeNode( - node, - childrenKey, - getKey, - labelKey, - disabled, - !!loadChildren, - index === 0, - index === nodes.length - 1, - level, - cascaderStrategy, - parentDisabled ? [parentDisabled] : [], - parentKey, - ), - ) -} + const { dataKeyMap, parentKeyMap, depthMap } = maps + const hasLoad = !!loadChildren + + return nodes.map((node, index) => { + const key = getKey(node) + const nodeDisabled = convertDisabled(node, disabled) + const subNodes = ((node as Record)[childrenKey] ?? getLoadedNodes(key)) as TreeNode[] | undefined + const label = node[labelKey] as string + const level = parentLevel + 1 + + const children = + subNodes && + convertMergeNodes( + subNodes, + getLoadedNodes, + { + props, + childrenKey, + getKey, + labelKey, + parentKey: key, + parentLevel: level, + parentsDisabled: [nodeDisabled, ...(parentsDisabled ?? [])], + }, + maps, + ) + + const mergedDisabled = mergeDisabled( + nodeDisabled, + parentsDisabled ?? [], + children?.map(child => ({ + check: child.checkDisabled, + drag: child.dragDisabled, + drop: child.dropDisabled, + select: child.selectDisabled, + })) ?? [], + cascaderStrategy !== 'off', + ) -function convertMergeNode( - rawNode: TreeNode, - childrenKey: string, - getKey: GetKeyFn, - labelKey: string, - disabled: ((node: TreeNode) => boolean | TreeNodeDisabled) | undefined, - hasLoad: boolean, - isFirst: boolean, - isLast: boolean, - level: number, - cascaderStrategy: CascaderStrategy, - parentsDisabled: TreeNodeDisabled[], - parentKey?: VKey, -): MergedNode { - const key = getKey(rawNode) - const nodeDisabled = convertDisabled(rawNode, disabled) - const subNodes = (rawNode as Record)[childrenKey] as TreeNode[] | undefined - const label = rawNode[labelKey] as string - - level++ - - const children = subNodes?.map((subNode, index) => - convertMergeNode( - subNode, - childrenKey, - getKey, - labelKey, - disabled, - hasLoad, - index === 0, - index === subNodes.length - 1, - level, - cascaderStrategy, - [nodeDisabled, ...parentsDisabled], + const mergedNode = { + label, key, - ), - ) - - const mergedDisabled = mergeDisabled( - nodeDisabled, - parentsDisabled, - children?.map(child => ({ - check: child.checkDisabled, - drag: child.dragDisabled, - drop: child.dropDisabled, - select: child.selectDisabled, - })) ?? [], - cascaderStrategy !== 'off', - ) + children, + isFirst: index === 0, + isLeaf: node.isLeaf ?? !(subNodes?.length || hasLoad), + isLast: index === nodes.length - 1, + parentKey, + expanded: false, + level, + rawNode: node, + checkDisabled: mergedDisabled.check, + dragDisabled: mergedDisabled.drag, + dropDisabled: mergedDisabled.drop, + selectDisabled: mergedDisabled.select, + } - return { - label, - key, - children, - isFirst, - isLeaf: rawNode.isLeaf ?? !(subNodes?.length || hasLoad), - isLast, - parentKey, - expanded: false, - level, - rawNode, - checkDisabled: mergedDisabled.check, - dragDisabled: mergedDisabled.drag, - dropDisabled: mergedDisabled.drop, - selectDisabled: mergedDisabled.select, - } + dataKeyMap.set(key, mergedNode) + parentKeyMap.set(key, parentKey) + depthMap.set(key, level) + + return mergedNode + }) } function convertDisabled(node: TreeNode, disabled?: (node: TreeNode) => boolean | TreeNodeDisabled) { diff --git a/packages/components/tree/src/composables/useExpandable.ts b/packages/components/tree/src/composables/useExpandable.ts index 799902201..4950840f8 100644 --- a/packages/components/tree/src/composables/useExpandable.ts +++ b/packages/components/tree/src/composables/useExpandable.ts @@ -5,6 +5,9 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { MergedNode } from './useDataSource' +import type { TreeExpandIconRenderer, TreeNode, TreeProps } from '../types' + import { type ComputedRef, type Ref, computed, h, ref, watch } from 'vue' import { isArray, isFunction, isString } from 'lodash-es' @@ -12,10 +15,7 @@ import { isArray, isFunction, isString } from 'lodash-es' import { VKey, callEmit, useControlledProp } from '@idux/cdk/utils' import { type TreeConfig } from '@idux/components/config' import { IxIcon } from '@idux/components/icon' -import { type GetKeyFn } from '@idux/components/utils' -import { type MergedNode, convertMergeNodes, convertMergedNodeMap } from './useDataSource' -import { type TreeExpandIconRenderer, type TreeNode, type TreeProps } from '../types' import { callChange, getParentKeys } from '../utils' export interface ExpandableContext { @@ -32,10 +32,9 @@ export function useExpandable( props: TreeProps, config: TreeConfig, mergedChildrenKey: ComputedRef, - mergedGetKey: ComputedRef, - mergedLabelKey: ComputedRef, mergedNodeMap: ComputedRef>, searchedKeys: ComputedRef, + setLoadedNodes: (key: VKey, nodes: TreeNode[]) => void, ): ExpandableContext { const expandIcon = computed(() => props.expandIcon ?? config.expandIcon) const [expandedKeys, setExpandedKeys] = useControlledProp(props, 'expandedKeys', () => []) @@ -91,25 +90,8 @@ export function useExpandable( const nodeMap = mergedNodeMap.value const currNode = nodeMap.get(key)! if (childrenNodes?.length) { - const level = currNode.level - const mergedChildren = convertMergeNodes( - props, - childrenNodes, - childrenKey, - mergedGetKey.value, - mergedLabelKey.value, - key, - level, - { - check: !!currNode.checkDisabled, - drag: !!currNode.dragDisabled, - drop: !!currNode.dropDisabled, - select: !!currNode.selectDisabled, - }, - ) - convertMergedNodeMap(mergedChildren, nodeMap) + setLoadedNodes(key, childrenNodes) currNode.rawNode[childrenKey] = childrenNodes - currNode.children = mergedChildren const newLoadedKeys = [...loadedKeys.value, key] setLoadedKeys(newLoadedKeys) callEmit(props.onLoaded, newLoadedKeys, rawNode) diff --git a/packages/components/tree/src/node/Checkbox.tsx b/packages/components/tree/src/node/Checkbox.tsx index 55b68808c..b9ef664b0 100644 --- a/packages/components/tree/src/node/Checkbox.tsx +++ b/packages/components/tree/src/node/Checkbox.tsx @@ -15,11 +15,16 @@ import { treeNodeCheckboxProps } from '../types' export default defineComponent({ props: treeNodeCheckboxProps, setup(props) { - const { mergedPrefixCls, allCheckedKeys, indeterminateKeys, handleCheck } = inject(treeToken)! + const { + mergedPrefixCls, + isChecked: _isChecked, + isIndeterminate: _isIndeterminate, + handleCheck, + } = inject(treeToken)! const isChecked = computed(() => { - return allCheckedKeys.value.includes(props.node.key) + return _isChecked(props.node.key) }) - const isIndeterminate = computed(() => indeterminateKeys.value.includes(props.node.key)) + const isIndeterminate = computed(() => _isIndeterminate(props.node.key)) const onChange = () => handleCheck(props.node) diff --git a/packages/components/tree/src/types.ts b/packages/components/tree/src/types.ts index ce5f27c5d..4e3b15e8a 100644 --- a/packages/components/tree/src/types.ts +++ b/packages/components/tree/src/types.ts @@ -134,6 +134,7 @@ export interface TreeBindings { //private _getFlattedNodes: () => MergedNode[] + _handleCheck: (key: VKey) => void } export type TreeComponent = DefineComponent & TreePublicProps, TreeBindings> export type TreeInstance = InstanceType> diff --git a/packages/components/utils/src/useTreeCheckStateResolver.ts b/packages/components/utils/src/useTreeCheckStateResolver.ts index 0bb8b47c2..aa153dfd1 100644 --- a/packages/components/utils/src/useTreeCheckStateResolver.ts +++ b/packages/components/utils/src/useTreeCheckStateResolver.ts @@ -25,7 +25,7 @@ interface GetAllUncheckedKeys, C extends keyof V> { export interface TreeCheckStateResolverContext, C extends keyof V> { data: V[] | undefined dataMap: Map - parentKeyMap: Map + parentKeyMap: Map depthMap: Map } export interface TreeCheckStateResolver, C extends keyof V> { @@ -151,7 +151,7 @@ export function useTreeCheckStateResolver, C extend appendedKeys.forEach(key => { _getParents(key, resolverContext).forEach(parent => { if (parent[childrenKey.value]?.every(child => newKeySet.has(getKey.value(child)))) { - newKeySet.add(key) + newKeySet.add(getKey.value(parent)) } }) })