From 56035c131cacbdf32c3eee3387f266768a85124b Mon Sep 17 00:00:00 2001 From: saller Date: Fri, 13 Jan 2023 17:27:42 +0800 Subject: [PATCH] feat(cdk:utils): add tree utils, fix data param of getAllSelectedKeys, fix filtered or paginated tree data value error (#1406) * feat(cdk:utils): add tree utils * fix(comp:transfer): correct `data` param of `getAllSelectedKeys` `data` param of `getAllSelectedKeys` strategy fuction should be datasource when mod is immediate * fix(pro:transfer): filtered or paginated tree data value error when tree data is filtered or paginated, target data is overidden by new keys which should be appended insteadt --- packages/cdk/utils/__tests__/tree.spec.ts | 257 ++++++++++++++ packages/cdk/utils/index.ts | 1 + packages/cdk/utils/src/tree.ts | 188 ++++++++++ .../src/composables/useTransferSelectState.ts | 3 +- packages/components/utils/index.ts | 1 + .../utils/src/useTreeCheckStateResolver.ts | 282 +++++++++++++++ .../transfer/__tests__/proTransfer.spec.ts | 11 +- packages/pro/transfer/demo/BasicTable.vue | 4 +- packages/pro/transfer/demo/BasicTree.vue | 4 +- packages/pro/transfer/demo/FlattenTree.vue | 4 +- .../pro/transfer/demo/TableCustomLabel.vue | 4 +- .../pro/transfer/demo/TableMaxSelectedCnt.vue | 4 +- packages/pro/transfer/demo/TableOneWay.vue | 4 +- .../pro/transfer/demo/TablePagination.vue | 4 +- packages/pro/transfer/demo/TableRemote.vue | 5 +- packages/pro/transfer/demo/TableVirtual.vue | 8 +- .../transfer/demo/TreeCascaderStrategy.vue | 5 +- .../pro/transfer/demo/TreeCustomLabel.vue | 4 +- .../pro/transfer/demo/TreeLoadChildren.vue | 10 +- packages/pro/transfer/demo/TreeOneWay.vue | 21 +- packages/pro/transfer/demo/TreePagination.vue | 18 +- packages/pro/transfer/demo/TreeRemote.vue | 4 +- packages/pro/transfer/demo/TreeVirtual.vue | 6 +- .../src/composables/useTransferData.ts | 20 +- .../src/composables/useTransferTableProps.ts | 31 +- .../src/composables/useTransferTreeProps.ts | 77 ++-- .../src/composables/useTreeDataStrategy.ts | 328 +++--------------- .../composables/useTreeDataStrategyContext.ts | 88 +++-- .../src/composables/useTreeExpandedKeys.ts | 8 +- .../transfer/src/content/ProTransferTree.tsx | 29 +- packages/pro/transfer/src/token.ts | 13 +- packages/pro/transfer/src/types.ts | 9 +- packages/pro/transfer/src/utils.ts | 148 -------- .../src/wrapper/TreeTransferWrapper.tsx | 29 +- 34 files changed, 1060 insertions(+), 572 deletions(-) create mode 100644 packages/cdk/utils/__tests__/tree.spec.ts create mode 100644 packages/cdk/utils/src/tree.ts create mode 100644 packages/components/utils/src/useTreeCheckStateResolver.ts delete mode 100644 packages/pro/transfer/src/utils.ts diff --git a/packages/cdk/utils/__tests__/tree.spec.ts b/packages/cdk/utils/__tests__/tree.spec.ts new file mode 100644 index 000000000..9f57806b7 --- /dev/null +++ b/packages/cdk/utils/__tests__/tree.spec.ts @@ -0,0 +1,257 @@ +import { filterTree, flattenTree, getTreeKeys, mapTree, mergeTree, traverseTree } from '../src/tree' + +interface Data { + key: string + label: string + children?: Data[] +} + +const treeData: Data[] = [ + { + label: 'Node 0', + key: '0', + children: [ + { + label: 'Node 0-0', + key: '0-0', + children: [ + { label: 'Node 0-0-0', key: '0-0-0' }, + { label: 'Node 0-0-1', key: '0-0-1' }, + { label: 'Node 0-0-2', key: '0-0-2' }, + ], + }, + { + label: 'Node 0-1', + key: '0-1', + children: [ + { label: 'Node 0-1-0', key: '0-1-0' }, + { label: 'Node 0-1-1', key: '0-1-1' }, + { label: 'Node 0-1-2', key: '0-1-2' }, + ], + }, + ], + }, + { label: 'Node 1', key: '1' }, +] +const treeDataKeys = ['0', '0-0', '0-0-0', '0-0-1', '0-0-2', '0-1', '0-1-0', '0-1-1', '0-1-2', '1'] +const treeDataParentsMap = new Map([ + ['0', []], + ['1', []], + ['0-0', ['0']], + ['0-1', ['0']], + ['0-0-0', ['0-0', '0']], + ['0-0-1', ['0-0', '0']], + ['0-0-2', ['0-0', '0']], + ['0-1-0', ['0-1', '0']], + ['0-1-1', ['0-1', '0']], + ['0-1-2', ['0-1', '0']], +]) + +describe('tree.ts', () => { + test('traverseTree work', () => { + const keys: string[] = [] + traverseTree(treeData, 'children', (item, parents) => { + keys.push(item.key) + expect(parents.map(parent => parent.key)).toEqual(treeDataParentsMap.get(item.key)) + expect(parents.every(parent => parent.label === `Node ${parent.key}`)).toBeTruthy() + }) + + expect(keys).toEqual(treeDataKeys) + }) + + test('mapTree work', () => { + expect( + mapTree(treeData, 'children', (item, parents) => { + expect(parents.map(parent => parent.key)).toEqual(treeDataParentsMap.get(item.key)) + expect(parents.every(parent => parent.label === `Node ${parent.key}`)).toBeTruthy() + + return { + ...item, + mapped: true, + } + }), + ).toEqual([ + { + label: 'Node 0', + key: '0', + mapped: true, + children: [ + { + label: 'Node 0-0', + key: '0-0', + mapped: true, + children: [ + { label: 'Node 0-0-0', key: '0-0-0', mapped: true }, + { label: 'Node 0-0-1', key: '0-0-1', mapped: true }, + { label: 'Node 0-0-2', key: '0-0-2', mapped: true }, + ], + }, + { + label: 'Node 0-1', + key: '0-1', + mapped: true, + children: [ + { label: 'Node 0-1-0', key: '0-1-0', mapped: true }, + { label: 'Node 0-1-1', key: '0-1-1', mapped: true }, + { label: 'Node 0-1-2', key: '0-1-2', mapped: true }, + ], + }, + ], + }, + { label: 'Node 1', key: '1', mapped: true }, + ]) + }) + + test('filterTree work with straytegy or work', () => { + expect( + filterTree( + treeData, + 'children', + (item, parents) => { + expect(parents.map(parent => parent.key)).toEqual(treeDataParentsMap.get(item.key)) + expect(parents.every(parent => parent.label === `Node ${parent.key}`)).toBeTruthy() + return /1/.test(item.label) + }, + 'or', + ), + ).toEqual([ + { + label: 'Node 0', + key: '0', + children: [ + { + label: 'Node 0-0', + key: '0-0', + children: [{ label: 'Node 0-0-1', key: '0-0-1' }], + }, + { + label: 'Node 0-1', + key: '0-1', + children: [ + { label: 'Node 0-1-0', key: '0-1-0' }, + { label: 'Node 0-1-1', key: '0-1-1' }, + { label: 'Node 0-1-2', key: '0-1-2' }, + ], + }, + ], + }, + { label: 'Node 1', key: '1' }, + ]) + }) + + test('filterTree work with straytegy and work', () => { + expect( + filterTree( + treeData, + 'children', + (item, parents) => { + expect(parents.map(parent => parent.key)).toEqual(treeDataParentsMap.get(item.key)) + expect(parents.every(parent => parent.label === `Node ${parent.key}`)).toBeTruthy() + return /1/.test(item.label) + }, + 'and', + ), + ).toEqual([{ label: 'Node 1', key: '1' }]) + }) + + test('mergeTree work', () => { + interface TargetData extends Data { + overidden?: boolean + children?: TargetData[] + } + + expect( + mergeTree( + treeData, + [ + { + label: 'Node 0', + key: '0', + children: [ + { + label: 'Node 0-0', + key: '0-0', + children: [ + { label: 'Node 0-0-1-overidden', key: '0-0-1', overidden: true }, + { label: 'Node 0-0-3', key: '0-0-3' }, + ], + }, + { + label: 'Node 0-1', + key: '0-1', + children: [{ label: 'Node 0-1-3', key: '0-1-3' }], + }, + ], + }, + { label: 'Node 2', key: '2' }, + ] as TargetData[], + 'children', + item => item.key, + ), + ).toEqual([ + { + label: 'Node 0', + key: '0', + children: [ + { + label: 'Node 0-0', + key: '0-0', + children: [ + { label: 'Node 0-0-0', key: '0-0-0' }, + { label: 'Node 0-0-1-overidden', key: '0-0-1', overidden: true }, + { label: 'Node 0-0-2', key: '0-0-2' }, + { label: 'Node 0-0-3', key: '0-0-3' }, + ], + }, + { + label: 'Node 0-1', + key: '0-1', + children: [ + { label: 'Node 0-1-0', key: '0-1-0' }, + { label: 'Node 0-1-1', key: '0-1-1' }, + { label: 'Node 0-1-2', key: '0-1-2' }, + { label: 'Node 0-1-3', key: '0-1-3' }, + ], + }, + ], + }, + { label: 'Node 1', key: '1' }, + { label: 'Node 2', key: '2' }, + ]) + }) + + test('flattenTree work', () => { + expect(flattenTree(treeData, 'children').map(item => item.key)).toEqual(treeDataKeys) + }) + test('flattenTree with Map work', () => { + const flattenedTree = flattenTree(treeData, 'children', item => ({ ...item, mapped: true })) + expect(flattenedTree.map(item => item.key)).toEqual(treeDataKeys) + expect(flattenedTree.every(item => item.mapped)).toBeTruthy() + }) + test('flattenTree with leafOnly work', () => { + expect(flattenTree(treeData, 'children', undefined, true).map(item => item.key)).toEqual([ + '0-0-0', + '0-0-1', + '0-0-2', + '0-1-0', + '0-1-1', + '0-1-2', + '1', + ]) + }) + + test('getTreeKeys work', () => { + expect(getTreeKeys(treeData, 'children', item => item.key)).toEqual(treeDataKeys) + }) + test('getTreeKeys with leafOnly work', () => { + expect(getTreeKeys(treeData, 'children', item => item.key, true)).toEqual([ + '0-0-0', + '0-0-1', + '0-0-2', + '0-1-0', + '0-1-1', + '0-1-2', + '1', + ]) + }) +}) diff --git a/packages/cdk/utils/index.ts b/packages/cdk/utils/index.ts index 7ea3562c7..73d848462 100644 --- a/packages/cdk/utils/index.ts +++ b/packages/cdk/utils/index.ts @@ -18,3 +18,4 @@ export * from './src/typeof' export * from './src/uniqueId' export * from './src/useEventListener' export * from './src/vNode' +export * from './src/tree' diff --git a/packages/cdk/utils/src/tree.ts b/packages/cdk/utils/src/tree.ts new file mode 100644 index 000000000..0a0c3185d --- /dev/null +++ b/packages/cdk/utils/src/tree.ts @@ -0,0 +1,188 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { VKey } from './props' + +import { isArray, isNil } from 'lodash-es' + +export type TreeTypeData = { + [key in C]?: (TreeTypeData & V)[] +} + +export function traverseTree, C extends keyof V>( + data: V[], + childrenKey: C, + fn: (item: V, parents: V[]) => void, + traverseStrategy: 'pre' | 'post' = 'pre', +): void { + const traverse = (_data: V[], parents: V[]) => { + for (let idx = 0; idx < _data.length; idx++) { + const item = _data[idx] + traverseStrategy === 'pre' && fn(item, parents) + if (item[childrenKey]) { + traverse(item[childrenKey]!, [item, ...parents]) + } + traverseStrategy === 'post' && fn(item, parents) + } + } + + traverse(data, []) +} + +export function mapTree, R extends object, C extends keyof V>( + data: V[], + childrenKey: C, + mapFn: (item: V, parents: V[]) => 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 + if (!mappedItem) { + continue + } + + if (item[childrenKey]) { + const mappedChildren = map(item[childrenKey]!, [item, ...parents]) + ;(mappedItem[childrenKey] as (R & TreeTypeData)[]) = mappedChildren + } + + res.push(mappedItem) + } + + return res + } + return map(data, []) +} + +export function filterTree, C extends keyof V>( + data: V[], + childrenKey: C, + filterFn: (item: V, parents: V[]) => boolean, + filterStrategy: 'and' | 'or' = 'or', +): V[] { + const filter = (_data: V[], parents: V[]): V[] => { + const res: V[] = [] + for (let idx = 0; idx < _data.length; idx++) { + let children: V[] | undefined + const item = _data[idx] + + if (item[childrenKey]) { + children = filter(item[childrenKey]!, [item, ...parents]) + } + + let itemValid = filterFn(item, parents) + const childrenValid = (children && children.length > 0) || (!item[childrenKey]?.length && itemValid) + + itemValid = filterStrategy === 'and' ? childrenValid && itemValid : childrenValid || itemValid + + if (itemValid) { + res.push({ + ...item, + [childrenKey]: item[childrenKey] && children, + }) + } + } + + return res + } + + return filter(data, []) +} + +export function mergeTree, R extends V, C extends keyof V | keyof R>( + originalTree: V[], + overideTree: R[], + childrenKey: C, + getKey: (item: V) => VKey, +): (V & Partial)[] { + const result = originalTree.map(item => ({ ...item })) as (V & Partial)[] + for (const overideItem of overideTree) { + const originalItem = result.find(item => { + const overideItemKey = getKey(overideItem) + const originalItemKey = getKey(item) + + return !isNil(overideItemKey) && !isNil(originalItemKey) && overideItemKey === originalItemKey + }) + + if (!originalItem) { + result.push({ ...overideItem }) + continue + } + + for (const key of [ + ...Object.getOwnPropertyNames(overideItem), + ...Object.getOwnPropertySymbols(overideItem), + ] as (keyof R)[]) { + if (key === childrenKey) { + continue + } + + originalItem[key] = overideItem[key] + } + + if (isArray(overideItem[childrenKey])) { + originalItem[childrenKey] = mergeTree( + originalItem[childrenKey] ?? [], + overideItem[childrenKey]!, + childrenKey, + getKey, + ) as (V & Partial)[C] + } + } + + return result +} + +export function flattenTree, C extends keyof V>(data: V[], childrenKey: C): V[] +export function flattenTree, C extends keyof V>( + data: V[], + childrenKey: C, + mapFn: undefined, + leafOnly?: boolean, +): V[] +export function flattenTree, R extends object, C extends keyof V>( + data: V[], + childrenKey: C, + mapFn: (item: V) => R, + leafOnly?: boolean, +): (R & TreeTypeData)[] +export function flattenTree, R extends object, C extends keyof V>( + data: V[], + childrenKey: C, + mapFn?: (item: V) => R, + leafOnly = false, +): V[] | (R & TreeTypeData)[] { + const res: V[] | (R & TreeTypeData)[] = [] + + traverseTree(data, childrenKey, item => { + if (!leafOnly || !item[childrenKey] || item[childrenKey]!.length <= 0) { + const mappedItem = mapFn ? mapFn(item) : item + mappedItem && res.push(mappedItem as V & (R & TreeTypeData)) + } + }) + + return res +} + +export function getTreeKeys, C extends keyof V>( + data: V[], + childrenKey: C, + getKey: (item: V) => VKey, + leafOnly = false, +): VKey[] { + const keys: VKey[] = [] + + traverseTree(data, childrenKey, item => { + if (!leafOnly || !item[childrenKey]?.length) { + keys.push(getKey(item)) + } + }) + + return keys +} diff --git a/packages/components/transfer/src/composables/useTransferSelectState.ts b/packages/components/transfer/src/composables/useTransferSelectState.ts index e8ac61c2d..467ac45d5 100644 --- a/packages/components/transfer/src/composables/useTransferSelectState.ts +++ b/packages/components/transfer/src/composables/useTransferSelectState.ts @@ -47,6 +47,7 @@ export function useTransferSelectState( const { dataKeyMap, + dataSource, sourceData, targetData, sourceDataKeys, @@ -187,7 +188,7 @@ export function useTransferSelectState( return } - const data = isSource ? sourceData.value : targetData.value + const data = isSource ? (props.mode === 'default' ? sourceData.value : dataSource.value) : targetData.value const _disabledKeys = isSource ? (props.mode === 'default' ? disabledSourceKeys : disabledKeys) : disabledTargetKeys const selectedKeySet = isSource ? sourceSelectedKeySet : targetSelectedKeySet const setSelectedKeys = isSource ? setSourceSelectedKeys : setTargetSelectedKeys diff --git a/packages/components/utils/index.ts b/packages/components/utils/index.ts index b3110daa2..40faef9ea 100644 --- a/packages/components/utils/index.ts +++ b/packages/components/utils/index.ts @@ -11,4 +11,5 @@ export * from './src/convertVNode' export * from './src/portalTarget' export * from './src/useDisabled' export * from './src/useKey' +export * from './src/useTreeCheckStateResolver' export * from './src/zIndex' diff --git a/packages/components/utils/src/useTreeCheckStateResolver.ts b/packages/components/utils/src/useTreeCheckStateResolver.ts new file mode 100644 index 000000000..0bb8b47c2 --- /dev/null +++ b/packages/components/utils/src/useTreeCheckStateResolver.ts @@ -0,0 +1,282 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { type Ref, computed } from 'vue' + +import { isArray, isObject } from 'lodash-es' + +import { type TreeTypeData, type VKey, getTreeKeys, traverseTree } from '@idux/cdk/utils' + +interface GetAllCheckedKeys, C extends keyof V> { + (data: V[]): VKey[] + (defaultUnCheckedKeys: VKey[]): VKey[] + (data: V[], defaultUnCheckedKeys: VKey[]): VKey[] +} +interface GetAllUncheckedKeys, C extends keyof V> { + (data: V[]): VKey[] + (defaultCheckedKeys: VKey[]): VKey[] + (data: V[], defaultCheckedKeys: VKey[]): VKey[] +} + +export interface TreeCheckStateResolverContext, C extends keyof V> { + data: V[] | undefined + dataMap: Map + parentKeyMap: Map + depthMap: Map +} +export interface TreeCheckStateResolver, C extends keyof V> { + appendKeys: (checkedKeys: VKey[] | Set, appendedKeys: VKey[]) => VKey[] + removeKeys: (checkedKeys: VKey[] | Set, removedKeys: VKey[]) => VKey[] + + getAllCheckedKeys: GetAllCheckedKeys + getAllUncheckedKeys: GetAllUncheckedKeys +} +export type TreeCascadeStrategy = 'all' | 'child' | 'parent' | 'off' + +export function useTreeCheckStateResolver, C extends keyof V>( + data: Ref, + childrenKey: Ref, + getKey: Ref<(item: V) => VKey>, + cascadeStrategy?: Ref, +): TreeCheckStateResolver +export function useTreeCheckStateResolver, C extends keyof V>( + context: Ref>, + childrenKey: Ref, + getKey: Ref<(item: V) => VKey>, + cascadeStrategy?: Ref, +): TreeCheckStateResolver +export function useTreeCheckStateResolver, C extends keyof V>( + dataOrContext: Ref>, + childrenKey: Ref, + getKey: Ref<(item: V) => VKey>, + cascadeStrategy?: Ref, +): TreeCheckStateResolver { + const _getContext = ( + dataOrContext: V[] | TreeCheckStateResolverContext, + ): TreeCheckStateResolverContext => { + if (!isArray(dataOrContext)) { + return dataOrContext + } + + const dataMap = new Map() + const parentKeyMap = new Map() + const depthMap = new Map() + + traverseTree(dataOrContext, childrenKey.value, (item, parents) => { + const key = getKey.value(item) + const parent = parents[0] + dataMap.set(key, item) + depthMap.set(key, parents.length) + + if (parent) { + parentKeyMap.set(key, getKey.value(parent)) + } + }) + + return { + data: dataOrContext, + dataMap, + parentKeyMap, + depthMap, + } + } + + const mergedCascadeStrategy = computed(() => cascadeStrategy?.value ?? 'all') + const _context = computed(() => _getContext(dataOrContext.value)) + + const _getParents = (key: VKey, resolverContext: TreeCheckStateResolverContext) => { + const { parentKeyMap, dataMap } = resolverContext + const parents: V[] = [] + + let currentKey = key + while (parentKeyMap.has(currentKey)) { + const parentKey = parentKeyMap.get(currentKey)! + const parent = dataMap.get(parentKey) + parent && parents.push(parent) + currentKey = parentKey + } + return parents + } + const _getAllData = () => { + const { data, parentKeyMap, dataMap } = _context.value + if (data) { + return data + } + + const _data: V[] = [] + for (const key of parentKeyMap.keys()) { + if (!parentKeyMap.has(key) && dataMap.has(key)) { + _data.push(dataMap.get(key)!) + } + } + + return _data + } + + const _append = ( + checkedKeys: VKey[] | Set, + appendedKeys: VKey[], + resolverContext: TreeCheckStateResolverContext, + ) => { + if (!appendedKeys.length) { + return Array.from(checkedKeys) + } + + if (mergedCascadeStrategy.value === 'off') { + return Array.from(new Set([...checkedKeys, ...appendedKeys])) + } + + const { dataMap } = resolverContext + + const newKeySet = new Set(checkedKeys) + appendedKeys.forEach(key => { + if (!dataMap.has(key)) { + return + } + + const treeData = dataMap.get(key)! + getTreeKeys([treeData], childrenKey.value, getKey.value, mergedCascadeStrategy.value === 'child').forEach(key => { + newKeySet.add(key) + }) + }) + + if (mergedCascadeStrategy.value === 'child') { + return Array.from(newKeySet).filter(key => !dataMap.get(key)?.[childrenKey.value]?.length) + } + + appendedKeys.forEach(key => { + _getParents(key, resolverContext).forEach(parent => { + if (parent[childrenKey.value]?.every(child => newKeySet.has(getKey.value(child)))) { + newKeySet.add(key) + } + }) + }) + + if (mergedCascadeStrategy.value === 'all') { + return Array.from(newKeySet) + } + + newKeySet.forEach(key => { + if (!newKeySet.has(key)) { + return + } + + const children = dataMap.get(key)?.[childrenKey.value] + if (children) { + traverseTree(children, childrenKey.value, item => { + newKeySet.delete(getKey.value(item)) + }) + } + }) + + return Array.from(newKeySet) + } + const _remove = ( + checkedKeys: VKey[] | Set, + removedKeys: VKey[], + resolverContext: TreeCheckStateResolverContext, + ) => { + if (!removedKeys.length) { + return Array.from(checkedKeys) + } + + const newKeySet = new Set(checkedKeys) + if (mergedCascadeStrategy.value === 'off') { + removedKeys.forEach(key => { + newKeySet.delete(key) + }) + return Array.from(newKeySet) + } + + const { dataMap } = resolverContext + + const deletedKeys = new Set() + // store already deleted keys + const deleteKey = (key: VKey) => { + deletedKeys.add(key) + newKeySet.delete(key) + } + + removedKeys.forEach(key => { + if (!dataMap.has(key)) { + return + } + + getTreeKeys([dataMap.get(key)!], childrenKey.value, getKey.value).forEach(key => { + deleteKey(key) + }) + + const parents = _getParents(key, resolverContext) + + if (mergedCascadeStrategy.value === 'parent') { + const keysInChain = [key, ...parents.map(getKey.value)] + const selectedKeyIdx = keysInChain.findIndex(key => newKeySet.has(key)) + + // if one of the ancestors was selected + // replace parent node with child nodes + if (selectedKeyIdx > -1) { + // only travers chain from selected node to the bottom + parents.slice(0, selectedKeyIdx).forEach(parent => { + if (!parent[childrenKey.value]) { + return + } + + parent[childrenKey.value]!.forEach(child => { + const childKey = getKey.value(child) + // add only if the child node hasn't been deleted before + if (!deletedKeys.has(childKey)) { + newKeySet.add(childKey) + } + }) + + deleteKey(getKey.value(parent)) + }) + } + } else { + parents.forEach(parent => { + deleteKey(getKey.value(parent)) + }) + } + }) + return Array.from(newKeySet) + } + const appendKeys = (checkedKeys: VKey[] | Set, appendedKeys: VKey[]) => + _append(checkedKeys, appendedKeys, _context.value) + const removeKeys = (checkedKeys: VKey[] | Set, removedKeys: VKey[]) => + _remove(checkedKeys, removedKeys, _context.value) + + const _getAllCheckedKeys = (data: V[] | undefined, defaultUnCheckedKeys: VKey[]) => { + const _data = data ?? _getAllData() + const tempKeys = + mergedCascadeStrategy.value === 'parent' + ? _data.map(getKey.value) + : new Set(getTreeKeys(_data, childrenKey.value, getKey.value, mergedCascadeStrategy.value === 'child')) + + return _remove(tempKeys, defaultUnCheckedKeys, data ? _getContext(data) : _context.value) + } + const _getAllUncheckedKeys = (data: V[] | undefined, defaultCheckedKeys: VKey[]) => { + return _append([], defaultCheckedKeys, data ? _getContext(data) : _context.value) + } + + const getAllCheckedKeys = (data?: V[] | VKey[], defaultUnCheckedKeys?: VKey[]) => { + const dataProvided = ((data?: V[] | VKey[]): data is V[] => isObject(data?.[0]))(data) + + return _getAllCheckedKeys(dataProvided ? data : undefined, defaultUnCheckedKeys ?? (dataProvided ? [] : data ?? [])) + } + const getAllUncheckedKeys = (data?: V[] | VKey[], defaultCheckedKeys?: VKey[]) => { + const dataProvided = ((data?: V[] | VKey[]): data is V[] => isObject(data?.[0]))(data) + + return _getAllUncheckedKeys(dataProvided ? data : undefined, defaultCheckedKeys ?? (dataProvided ? [] : data ?? [])) + } + + return { + appendKeys, + removeKeys, + getAllCheckedKeys, + getAllUncheckedKeys, + } +} diff --git a/packages/pro/transfer/__tests__/proTransfer.spec.ts b/packages/pro/transfer/__tests__/proTransfer.spec.ts index aa483d7cb..b41286655 100644 --- a/packages/pro/transfer/__tests__/proTransfer.spec.ts +++ b/packages/pro/transfer/__tests__/proTransfer.spec.ts @@ -7,12 +7,13 @@ import { IxTransferList } from '@idux/components/transfer' import TransferOperations from '@idux/components/transfer/src/TransferOperations' import ProTransfer from '../src/ProTransfer' -import { ProTransferProps, TreeTransferData } from '../src/types' +import { ProTransferProps, TransferData } from '../src/types' -interface Data extends TreeTransferData<'children'> { +interface Data extends TransferData { key: string label: string disabled?: boolean + children?: Data[] } const createData = (idx: number, includeDisabled = true): Data => ({ @@ -201,14 +202,14 @@ describe('ProTransfer', () => { await sourceTree.findAll('.ix-tree-node')[0].find('input').setValue(true) await appendTrigger.trigger('click') - expect(onChange).toBeCalledWith(['1-2-2', '1', '1-1', '1-2', '1-2-1', '1-3'], ['1-2-2']) + expect(onChange).toBeCalledWith(['1-2-2', '1', '1-2', '1-2-1', '1-1', '1-3'], ['1-2-2']) - await wrapper.setProps({ value: ['1-2-2', '1', '1-1', '1-2', '1-2-1', '1-3'] }) + await wrapper.setProps({ value: ['1-2-2', '1', '1-2', '1-2-1', '1-1', '1-3'] }) await targetTree.findAll('.ix-tree-node')[0].find('input').setValue(true) await removeTrigger.trigger('click') - expect(onChange).toBeCalledWith(['1-2-2'], ['1-2-2', '1', '1-1', '1-2', '1-2-1', '1-3']) + expect(onChange).toBeCalledWith(['1-2-2'], ['1-2-2', '1', '1-2', '1-2-1', '1-1', '1-3']) }) test('table immediate work', async () => { diff --git a/packages/pro/transfer/demo/BasicTable.vue b/packages/pro/transfer/demo/BasicTable.vue index 55301e7c2..d6cebd310 100644 --- a/packages/pro/transfer/demo/BasicTable.vue +++ b/packages/pro/transfer/demo/BasicTable.vue @@ -9,11 +9,13 @@