diff --git a/components/vc-tree-select2/LegacyContext.tsx b/components/vc-tree-select2/LegacyContext.tsx new file mode 100644 index 000000000..9e13bf2f4 --- /dev/null +++ b/components/vc-tree-select2/LegacyContext.tsx @@ -0,0 +1,63 @@ +/** + * BaseSelect provide some parsed data into context. + * You can use this hooks to get them. + */ + +import type { InjectionKey } from 'vue'; +import { inject, provide } from 'vue'; +import type { DataEntity, IconType } from '../vc-tree/interface'; +import type { Key, LegacyDataNode, RawValueType } from './interface'; + +interface LegacyContextProps { + checkable: boolean; + checkedKeys: Key[]; + customCheckable: () => any; + halfCheckedKeys: Key[]; + treeExpandedKeys: Key[]; + treeDefaultExpandedKeys: Key[]; + onTreeExpand: (keys: Key[]) => void; + treeDefaultExpandAll: boolean; + treeIcon: IconType; + showTreeIcon: boolean; + switcherIcon: IconType; + treeLine: boolean; + treeNodeFilterProp: string; + treeLoadedKeys: Key[]; + treeMotion: any; + loadData: (treeNode: LegacyDataNode) => Promise; + onTreeLoad: (loadedKeys: Key[]) => void; + + keyEntities: Record>; + + // slots: { + // title?: (data: InternalDataEntity) => any; + // titleRender?: (data: InternalDataEntity) => any; + // [key: string]: ((...args: any[]) => any) | undefined; + // }; +} + +const TreeSelectLegacyContextPropsKey: InjectionKey = Symbol( + 'TreeSelectLegacyContextPropsKey', +); + +// export const LegacySelectContext = defineComponent({ +// name: 'SelectContext', +// props: { +// value: { type: Object as PropType }, +// }, +// setup(props, { slots }) { +// provide( +// TreeSelectLegacyContextPropsKey, +// computed(() => props.value), +// ); +// return () => slots.default?.(); +// }, +// }); + +export function useProvideLegacySelectContext(props: LegacyContextProps) { + return provide(TreeSelectLegacyContextPropsKey, props); +} + +export default function useInjectLegacySelectContext() { + return inject(TreeSelectLegacyContextPropsKey, {} as LegacyContextProps); +} diff --git a/components/vc-tree-select2/OptionList.tsx b/components/vc-tree-select2/OptionList.tsx index 6d3ec67e4..3db2c88fb 100644 --- a/components/vc-tree-select2/OptionList.tsx +++ b/components/vc-tree-select2/OptionList.tsx @@ -1,14 +1,16 @@ -import type { DataNode, TreeDataNode, Key } from './interface'; -import { useInjectTreeSelectContext } from './Context'; +import type { TreeDataNode, Key } from './interface'; import type { RefOptionListProps } from '../vc-select/OptionList'; import type { ScrollTo } from '../vc-virtual-list/List'; import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue'; -import { optionListProps } from './props'; import useMemo from '../_util/hooks/useMemo'; import type { EventDataNode } from '../tree'; import KeyCode from '../_util/KeyCode'; import Tree from '../vc-tree/Tree'; import type { TreeProps } from '../vc-tree/props'; +import { getAllKeys, isCheckDisabled } from './utils/valueUtil'; +import { useBaseProps } from '../vc-select'; +import useInjectLegacySelectContext from './LegacyContext'; +import useInjectSelectContext from './TreeSelectContext'; const HIDDEN_STYLE = { width: 0, @@ -32,44 +34,36 @@ type ReviseRefOptionListProps = Omit & { scrollT export default defineComponent({ name: 'OptionList', inheritAttrs: false, - props: optionListProps(), slots: ['notFoundContent', 'menuItemSelectedIcon'], - setup(props, { slots, expose }) { - const context = useInjectTreeSelectContext(); - + setup(_, { slots, expose }) { + const baseProps = useBaseProps(); + const legacyContext = useInjectLegacySelectContext(); + const context = useInjectSelectContext(); const treeRef = ref(); - const memoOptions = useMemo( - () => props.options, - [() => props.open, () => props.options], + const memoTreeData = useMemo( + () => context.treeData, + [() => baseProps.open, () => context.treeData], next => next[0], ); - const valueKeys = computed(() => { - const { checkedKeys, getEntityByValue } = context.value; - return checkedKeys.map(val => { - const entity = getEntityByValue(val); - return entity ? entity.key : null; - }); - }); - const mergedCheckedKeys = computed(() => { - const { checkable, halfCheckedKeys } = context.value; + const { checkable, halfCheckedKeys, checkedKeys } = legacyContext; if (!checkable) { return null; } return { - checked: valueKeys.value, + checked: checkedKeys, halfChecked: halfCheckedKeys, }; }); watch( - () => props.open, + () => baseProps.open, () => { nextTick(() => { - if (props.open && !props.multiple && valueKeys.value.length) { - treeRef.value?.scrollTo({ key: valueKeys.value[0] }); + if (baseProps.open && !baseProps.multiple && legacyContext.checkedKeys.length) { + treeRef.value?.scrollTo({ key: legacyContext.checkedKeys[0] }); } }); }, @@ -77,25 +71,25 @@ export default defineComponent({ ); // ========================== Search ========================== - const lowerSearchValue = computed(() => String(props.searchValue).toLowerCase()); + const lowerSearchValue = computed(() => String(baseProps.searchValue).toLowerCase()); const filterTreeNode = (treeNode: EventDataNode) => { if (!lowerSearchValue.value) { return false; } - return String(treeNode[context.value.treeNodeFilterProp]) + return String(treeNode[legacyContext.treeNodeFilterProp]) .toLowerCase() .includes(lowerSearchValue.value); }; // =========================== Keys =========================== - const expandedKeys = shallowRef(context.value.treeDefaultExpandedKeys); + const expandedKeys = shallowRef(legacyContext.treeDefaultExpandedKeys); const searchExpandedKeys = shallowRef(null); watch( - () => props.searchValue, + () => baseProps.searchValue, () => { - if (props.searchValue) { - searchExpandedKeys.value = props.flattenOptions.map(o => o.key); + if (baseProps.searchValue) { + searchExpandedKeys.value = getAllKeys(context.treeData, context.fieldNames); } }, { @@ -103,17 +97,17 @@ export default defineComponent({ }, ); const mergedExpandedKeys = computed(() => { - if (context.value.treeExpandedKeys) { - return [...context.value.treeExpandedKeys]; + if (legacyContext.treeExpandedKeys) { + return [...legacyContext.treeExpandedKeys]; } - return props.searchValue ? searchExpandedKeys.value : expandedKeys.value; + return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value; }); const onInternalExpand = (keys: Key[]) => { expandedKeys.value = keys; searchExpandedKeys.value = keys; - context.value.onTreeExpand?.(keys); + legacyContext.onTreeExpand?.(keys); }; // ========================== Events ========================== @@ -121,23 +115,23 @@ export default defineComponent({ event.preventDefault(); }; - const onInternalSelect = (_: Key[], { node: { key } }: TreeEventInfo) => { - const { getEntityByKey, checkable, checkedKeys } = context.value; - const entity = getEntityByKey(key, checkable ? 'checkbox' : 'select'); - if (entity !== null) { - props.onSelect?.(entity.data.value, { - selected: !checkedKeys.includes(entity.data.value), - }); + const onInternalSelect = (_: Key[], { node }: TreeEventInfo) => { + const { checkable, checkedKeys } = legacyContext; + if (checkable && isCheckDisabled(node)) { + return; } + context.onSelect?.(node.key, { + selected: !checkedKeys.includes(node.key), + }); - if (!props.multiple) { - props.onToggleOpen?.(false); + if (!baseProps.multiple) { + baseProps.toggleOpen?.(false); } }; // ========================= Keyboard ========================= const activeKey = ref(null); - const activeEntity = computed(() => context.value.getEntityByKey(activeKey.value)); + const activeEntity = computed(() => legacyContext.keyEntities[activeKey.value]); const setActiveKey = (key: Key) => { activeKey.value = key; @@ -157,11 +151,11 @@ export default defineComponent({ // >>> Select item case KeyCode.ENTER: { - const { selectable, value } = activeEntity.value?.data.node || {}; + const { selectable, value } = activeEntity.value?.node || {}; if (selectable !== false) { onInternalSelect(null, { node: { key: activeKey.value }, - selected: !context.value.checkedKeys.includes(value), + selected: !legacyContext.checkedKeys.includes(value), }); } break; @@ -169,7 +163,7 @@ export default defineComponent({ // >>> Close case KeyCode.ESC: { - props.onToggleOpen(false); + baseProps.toggleOpen(false); } } }, @@ -179,15 +173,12 @@ export default defineComponent({ return () => { const { prefixCls, - height, - itemHeight, - virtual, multiple, searchValue, open, notFoundContent = slots.notFoundContent?.(), - onMouseenter, - } = props; + } = baseProps; + const { listHeight, listItemHeight, virtual } = context; const { checkable, treeDefaultExpandAll, @@ -199,9 +190,10 @@ export default defineComponent({ treeLoadedKeys, treeMotion, onTreeLoad, - } = context.value; + checkedKeys, + } = legacyContext; // ========================== Render ========================== - if (memoOptions.value.length === 0) { + if (memoTreeData.value.length === 0) { return (
{notFoundContent} @@ -217,10 +209,10 @@ export default defineComponent({ treeProps.expandedKeys = mergedExpandedKeys.value; } return ( -
+
{activeEntity.value && open && ( - {activeEntity.value.data.value} + {activeEntity.value.node.value} )} @@ -228,9 +220,9 @@ export default defineComponent({ ref={treeRef} focusable={false} prefixCls={`${prefixCls}-tree`} - treeData={memoOptions.value as TreeDataNode[]} - height={height} - itemHeight={itemHeight} + treeData={memoTreeData.value as TreeDataNode[]} + height={listHeight} + itemHeight={listItemHeight} virtual={virtual} multiple={multiple} icon={treeIcon} @@ -243,7 +235,7 @@ export default defineComponent({ checkable={checkable} checkStrictly checkedKeys={mergedCheckedKeys.value} - selectedKeys={!checkable ? valueKeys.value : []} + selectedKeys={!checkable ? checkedKeys : []} defaultExpandAll={treeDefaultExpandAll} {...treeProps} // Proxy event out @@ -253,7 +245,7 @@ export default defineComponent({ onExpand={onInternalExpand} onLoad={onTreeLoad} filterTreeNode={filterTreeNode} - v-slots={{ ...slots, checkable: context.value.customCheckable }} + v-slots={{ ...slots, checkable: legacyContext.customCheckable }} />
); diff --git a/components/vc-tree-select2/TreeSelect.tsx b/components/vc-tree-select2/TreeSelect.tsx index 1f2d1684c..3667dc277 100644 --- a/components/vc-tree-select2/TreeSelect.tsx +++ b/components/vc-tree-select2/TreeSelect.tsx @@ -1,6 +1,210 @@ -import generate from './generate'; import OptionList from './OptionList'; +import TreeNode from './TreeNode'; +import { formatStrategyValues, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './utils/strategyUtil'; +import type { CheckedStrategy } from './utils/strategyUtil'; +import TreeSelectContext from './TreeSelectContext'; +import type { TreeSelectContextProps } from './TreeSelectContext'; +import LegacyContext from './LegacyContext'; +import useTreeData from './hooks/useTreeData'; +import { toArray, fillFieldNames, isNil } from './utils/valueUtil'; +import useCache from './hooks/useCache'; +import useRefFunc from './hooks/useRefFunc'; +import useDataEntities from './hooks/useDataEntities'; +import { fillAdditionalInfo, fillLegacyProps } from './utils/legacyUtil'; +import useCheckedKeys from './hooks/useCheckedKeys'; +import useFilterTreeData from './hooks/useFilterTreeData'; +import warningProps from './utils/warningPropsUtil'; +import type { Key } from './interface'; +import { baseSelectPropsWithoutPrivate } from '../vc-select/BaseSelect'; +import { defineComponent } from 'vue'; +import type { ExtractPropTypes, PropType } from 'vue'; +import omit from '../_util/omit'; +import PropTypes from '../_util/vue-types'; +import type { SelectProps } from '../vc-select'; -const TreeSelect = generate({ prefixCls: 'vc-tree-select', optionList: OptionList as any }); +export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void; -export default TreeSelect; +export type RawValueType = string | number; + +export interface LabeledValueType { + key?: Key; + value?: RawValueType; + label?: any; + /** Only works on `treeCheckStrictly` */ + halfChecked?: boolean; +} + +export type SelectSource = 'option' | 'selection' | 'input' | 'clear'; + +export type DraftValueType = RawValueType | LabeledValueType | (RawValueType | LabeledValueType)[]; + +/** @deprecated This is only used for legacy compatible. Not works on new code. */ +export interface LegacyCheckedNode { + pos: string; + node: any; + children?: LegacyCheckedNode[]; +} + +export interface ChangeEventExtra { + /** @deprecated Please save prev value by control logic instead */ + preValue: LabeledValueType[]; + triggerValue: RawValueType; + /** @deprecated Use `onSelect` or `onDeselect` instead. */ + selected?: boolean; + /** @deprecated Use `onSelect` or `onDeselect` instead. */ + checked?: boolean; + + // Not sure if exist user still use this. We have to keep but not recommend user to use + /** @deprecated This prop not work as react node anymore. */ + triggerNode: any; + /** @deprecated This prop not work as react node anymore. */ + allCheckedNodes: LegacyCheckedNode[]; +} + +export interface FieldNames { + value?: string; + label?: string; + children?: string; +} + +export interface InternalFieldName extends Omit { + _title: string[]; +} + +export interface SimpleModeConfig { + id?: Key; + pId?: Key; + rootPId?: Key; +} + +export interface BaseOptionType { + disabled?: boolean; + checkable?: boolean; + disableCheckbox?: boolean; + children?: BaseOptionType[]; + [name: string]: any; +} + +export interface DefaultOptionType extends BaseOptionType { + value?: RawValueType; + title?: any; + label?: any; + key?: Key; + children?: DefaultOptionType[]; +} + +export interface LegacyDataNode extends DefaultOptionType { + props: any; +} + +export function treeSelectProps< + ValueType = any, + OptionType extends BaseOptionType = DefaultOptionType, +>() { + return { + ...omit(baseSelectPropsWithoutPrivate(), ['mode']), + + prefixCls: String, + id: String, + value: { type: [String, Number, Object, Array] as PropType }, + defaultValue: { type: [String, Number, Object, Array] as PropType }, + onChange: { + type: Function as PropType< + (value: ValueType, labelList: any[], extra: ChangeEventExtra) => void + >, + }, + searchValue: String, + /** @deprecated Use `searchValue` instead */ + inputValue: String, + onSearch: { type: Function as PropType<(value: string) => void> }, + autoClearSearchValue: { type: Boolean, default: undefined }, + + filterTreeNode: { + type: [Boolean, Function] as PropType< + boolean | ((inputValue: string, treeNode: DefaultOptionType) => boolean) + >, + default: undefined, + }, + treeNodeFilterProp: String, + + // >>> Select + onSelect: Function as PropType, + onDeselect: Function as PropType, + + showCheckedStrategy: { type: String as PropType }, + treeNodeLabelProp: String, + + fieldNames: { type: Object as PropType }, + + // >>> Mode + multiple: { type: Boolean, default: undefined }, + treeCheckable: { type: Boolean, default: undefined }, + treeCheckStrictly: { type: Boolean, default: undefined }, + labelInValue: { type: Boolean, default: undefined }, + + // >>> Data + treeData: { type: Array as PropType }, + treeDataSimpleMode: { + type: [Boolean, Object] as PropType, + default: undefined, + }, + treeLoadedKeys: { type: Array as PropType }, + onTreeLoad: { type: Function as PropType<(loadedKeys: Key[]) => void> }, + + // >>> Options + virtual: { type: Boolean, default: undefined }, + listHeight: Number, + listItemHeight: Number, + onDropdownVisibleChange: { type: Function as PropType<(open: boolean) => void> }, + + // >>> Tree + treeLine: { type: Boolean, default: undefined }, + treeIcon: PropTypes.any, + showTreeIcon: { type: Boolean, default: undefined }, + switcherIcon: PropTypes.any, + treeMotion: PropTypes.any, + + // showArrow: { type: Boolean, default: undefined }, + // showSearch: { type: Boolean, default: undefined }, + // open: { type: Boolean, default: undefined }, + // defaultOpen: { type: Boolean, default: undefined }, + + // disabled: { type: Boolean, default: undefined }, + + // placeholder: PropTypes.any, + + // maxTagPlaceholder: { type: Function as PropType<(omittedValues: LabelValueType[]) => any> }, + + // loadData: { type: Function as PropType<(dataNode: LegacyDataNode) => Promise> }, + + // treeExpandedKeys: { type: Array as PropType }, + // treeDefaultExpandedKeys: { type: Array as PropType }, + + // treeDefaultExpandAll: { type: Boolean, default: undefined }, + + // children: Array, + + // dropdownPopupAlign: PropTypes.any, + + // // Event + + // onTreeExpand: { type: Function as PropType<(expandedKeys: Key[]) => void> }, + }; +} + +export type TreeSelectProps = Partial>>; + +function isRawValue(value: RawValueType | LabeledValueType): value is RawValueType { + return !value || typeof value !== 'object'; +} + +export default defineComponent({ + name: 'TreeSelect', + inheritAttrs: false, + props: treeSelectProps(), + setup() { + return () => { + return null; + }; + }, +}); diff --git a/components/vc-tree-select2/TreeSelectContext.ts b/components/vc-tree-select2/TreeSelectContext.ts new file mode 100644 index 000000000..c8ffe20cc --- /dev/null +++ b/components/vc-tree-select2/TreeSelectContext.ts @@ -0,0 +1,23 @@ +import type { InjectionKey } from 'vue'; +import { provide, inject } from 'vue'; +import type { DefaultOptionType, InternalFieldName, OnInternalSelect } from './TreeSelect'; + +export interface TreeSelectContextProps { + virtual?: boolean; + listHeight: number; + listItemHeight: number; + treeData: DefaultOptionType[]; + fieldNames: InternalFieldName; + onSelect: OnInternalSelect; +} + +const TreeSelectContextPropsKey: InjectionKey = Symbol( + 'TreeSelectContextPropsKey', +); + +export function useProvideSelectContext(props: TreeSelectContextProps) { + return provide(TreeSelectContextPropsKey, props); +} +export default function useInjectSelectContext() { + return inject(TreeSelectContextPropsKey, {} as TreeSelectContextProps); +} diff --git a/components/vc-tree-select2/index.tsx b/components/vc-tree-select2/index.tsx index d3e52682a..86cfc2e02 100644 --- a/components/vc-tree-select2/index.tsx +++ b/components/vc-tree-select2/index.tsx @@ -1,4 +1,4 @@ -// base rc-tree-select@4.6.1 +// base rc-tree-select@5.0.0-alpha.4 import TreeSelect from './TreeSelect'; import TreeNode from './TreeNode'; import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil'; diff --git a/components/vc-tree-select2/utils/legacyUtil.tsx b/components/vc-tree-select2/utils/legacyUtil.tsx index 9bafb1969..621898bb8 100644 --- a/components/vc-tree-select2/utils/legacyUtil.tsx +++ b/components/vc-tree-select2/utils/legacyUtil.tsx @@ -1,16 +1,10 @@ import { filterEmpty } from '../../_util/props-util'; import { camelize } from 'vue'; import { warning } from '../../vc-util/warning'; -import type { - DataNode, - LegacyDataNode, - ChangeEventExtra, - InternalDataEntity, - RawValueType, - LegacyCheckedNode, -} from '../interface'; +import type { DataNode, ChangeEventExtra, RawValueType, LegacyCheckedNode } from '../interface'; import TreeNode from '../TreeNode'; import type { VueNode } from '../../_util/type'; +import type { DefaultOptionType, FieldNames } from '../TreeSelect'; function isTreeSelectNode(node: any) { return node && node.type && (node.type as any).isTreeSelectNode; @@ -66,10 +60,10 @@ export function convertChildrenToData(rootNodes: VueNode[]): DataNode[] { return dig(rootNodes as any[]); } -export function fillLegacyProps(dataNode: DataNode): LegacyDataNode { +export function fillLegacyProps(dataNode: DataNode): any { // Skip if not dataNode exist if (!dataNode) { - return dataNode as LegacyDataNode; + return dataNode; } const cloneNode = { ...dataNode }; @@ -79,37 +73,43 @@ export function fillLegacyProps(dataNode: DataNode): LegacyDataNode { get() { warning( false, - 'New `rc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.', + 'New `vc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.', ); return cloneNode; }, }); } - return cloneNode as LegacyDataNode; + return cloneNode; } export function fillAdditionalInfo( extra: ChangeEventExtra, triggerValue: RawValueType, checkedValues: RawValueType[], - treeData: InternalDataEntity[], + treeData: DefaultOptionType[], showPosition: boolean, + fieldNames: FieldNames, ) { let triggerNode = null; let nodeList: LegacyCheckedNode[] = null; function generateMap() { - function dig(list: InternalDataEntity[], level = '0', parentIncluded = false) { + function dig(list: DefaultOptionType[], level = '0', parentIncluded = false) { return list - .map((dataNode, index) => { + .map((option, index) => { const pos = `${level}-${index}`; - const included = checkedValues.includes(dataNode.value); - const children = dig(dataNode.children || [], pos, included); - const node = {children.map(child => child.node)}; + const value = option[fieldNames.value]; + const included = checkedValues.includes(value); + const children = dig(option[fieldNames.children] || [], pos, included); + const node = ( + )}> + {children.map(child => child.node)} + + ); // Link with trigger node - if (triggerValue === dataNode.value) { + if (triggerValue === value) { triggerNode = node; } diff --git a/components/vc-tree-select2/utils/strategyUtil.ts b/components/vc-tree-select2/utils/strategyUtil.ts index ad47e768a..7d7116cd8 100644 --- a/components/vc-tree-select2/utils/strategyUtil.ts +++ b/components/vc-tree-select2/utils/strategyUtil.ts @@ -1,5 +1,6 @@ import type { DataEntity } from '../../vc-tree/interface'; -import type { RawValueType, Key, DataNode } from '../interface'; +import type { InternalFieldName } from '../TreeSelect'; +import type { RawValueType, Key } from '../interface'; import { isCheckDisabled } from './valueUtil'; export const SHOW_ALL = 'SHOW_ALL'; @@ -8,22 +9,23 @@ export const SHOW_CHILD = 'SHOW_CHILD'; export type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD; -export function formatStrategyKeys( - keys: Key[], +export function formatStrategyValues( + values: Key[], strategy: CheckedStrategy, keyEntities: Record, + fieldNames: InternalFieldName, ): RawValueType[] { - const keySet = new Set(keys); + const valueSet = new Set(values); if (strategy === SHOW_CHILD) { - return keys.filter(key => { + return values.filter(key => { const entity = keyEntities[key]; if ( entity && entity.children && entity.children.every( - ({ node }) => isCheckDisabled(node) || keySet.has((node as DataNode).key), + ({ node }) => isCheckDisabled(node) || valueSet.has(node[fieldNames.value]), ) ) { return false; @@ -32,15 +34,15 @@ export function formatStrategyKeys( }); } if (strategy === SHOW_PARENT) { - return keys.filter(key => { + return values.filter(key => { const entity = keyEntities[key]; const parent = entity ? entity.parent : null; - if (parent && !isCheckDisabled(parent.node) && keySet.has((parent.node as DataNode).key)) { + if (parent && !isCheckDisabled(parent.node) && valueSet.has(parent.node.key)) { return false; } return true; }); } - return keys; + return values; } diff --git a/components/vc-tree-select2/utils/valueUtil.ts b/components/vc-tree-select2/utils/valueUtil.ts index 33f71a74f..00ba0efec 100644 --- a/components/vc-tree-select2/utils/valueUtil.ts +++ b/components/vc-tree-select2/utils/valueUtil.ts @@ -1,21 +1,5 @@ -import type { - FlattenDataNode, - Key, - RawValueType, - DataNode, - DefaultValueType, - LabelValueType, - LegacyDataNode, - FieldNames, - InternalDataEntity, -} from '../interface'; -import { fillLegacyProps } from './legacyUtil'; -import type { SkipType } from '../hooks/useKeyValueMapping'; -import type { FlattenNode } from '../../vc-tree/interface'; -import { flattenTreeData } from '../../vc-tree/utils/treeUtil'; -import type { FilterFunc } from '../../vc-select/interface/generator'; - -type CompatibleDataNode = Omit; +import type { Key, DataNode, FieldNames } from '../interface'; +import type { DefaultOptionType, InternalFieldName } from '../TreeSelect'; export function toArray(value: T | T[]): T[] { if (Array.isArray(value)) { @@ -24,221 +8,43 @@ export function toArray(value: T | T[]): T[] { return value !== undefined ? [value] : []; } -/** - * Fill `fieldNames` with default field names. - * - * @param fieldNames passed props - * @param skipTitle Skip if no need fill `title`. This is useful since we have 2 name as same title level - * @returns - */ -export function fillFieldNames(fieldNames?: FieldNames, skipTitle = false) { +export function fillFieldNames(fieldNames?: FieldNames) { const { label, value, children } = fieldNames || {}; - const filledNames: FieldNames = { - value: value || 'value', + const mergedValue = value || 'value'; + + return { + _title: label ? [label] : ['title', 'label'], + value: mergedValue, + key: mergedValue, children: children || 'children', }; - - if (!skipTitle || label) { - filledNames.label = label || 'label'; - } - - return filledNames; -} - -export function findValueOption(values: RawValueType[], options: CompatibleDataNode[]): DataNode[] { - const optionMap: Map = new Map(); - - options.forEach(flattenItem => { - const { data, value } = flattenItem; - optionMap.set(value, data.node); - }); - - return values.map(val => fillLegacyProps(optionMap.get(val))); -} - -export function isValueDisabled(value: RawValueType, options: CompatibleDataNode[]): boolean { - const option = findValueOption([value], options)[0]; - if (option) { - return option.disabled; - } - - return false; } export function isCheckDisabled(node: DataNode) { return node.disabled || node.disableCheckbox || node.checkable === false; } -interface TreeDataNode extends InternalDataEntity { - key: Key; - children?: TreeDataNode[]; -} +/** Loop fetch all the keys exist in the tree */ +export function getAllKeys(treeData: DefaultOptionType[], fieldNames: InternalFieldName) { + const keys: Key[] = []; -function getLevel({ parent }: FlattenNode): number { - let level = 0; - let current = parent; - - while (current) { - current = current.parent; - level += 1; - } - - return level; -} - -/** - * Before reuse `rc-tree` logic, we need to add key since TreeSelect use `value` instead of `key`. - */ -export function flattenOptions(options: any): FlattenDataNode[] { - const typedOptions = options as InternalDataEntity[]; - - // Add missing key - function fillKey(list: InternalDataEntity[]): TreeDataNode[] { - return (list || []).map(node => { - const { value, key, children } = node; - - const clone: TreeDataNode = { - ...node, - key: 'key' in node ? key : value, - }; + function dig(list: DefaultOptionType[]) { + list.forEach(item => { + keys.push(item[fieldNames.value]); + const children = item[fieldNames.children]; if (children) { - clone.children = fillKey(children); + dig(children); } - - return clone; }); } - const flattenList = flattenTreeData(fillKey(typedOptions), true, null); + dig(treeData); - const cacheMap = new Map(); - const flattenDateNodeList: (FlattenDataNode & { parentKey?: Key })[] = flattenList.map(option => { - const { data, key, value } = option as any as Omit & { - value: RawValueType; - data: InternalDataEntity; - }; - - const flattenNode = { - key, - value, - data, - level: getLevel(option), - parentKey: option.parent?.data.key, - }; - - cacheMap.set(key, flattenNode); - - return flattenNode; - }); - - // Fill parent - flattenDateNodeList.forEach(flattenNode => { - // eslint-disable-next-line no-param-reassign - flattenNode.parent = cacheMap.get(flattenNode.parentKey); - }); - - return flattenDateNodeList; + return keys; } -function getDefaultFilterOption(optionFilterProp: string) { - return (searchValue: string, dataNode: LegacyDataNode) => { - const value = dataNode[optionFilterProp]; - - return String(value).toLowerCase().includes(String(searchValue).toLowerCase()); - }; -} - -/** Filter options and return a new options by the search text */ -export function filterOptions( - searchValue: string, - options: DataNode[], - { - optionFilterProp, - filterOption, - }: { - optionFilterProp: string; - filterOption: boolean | FilterFunc; - }, -): DataNode[] { - if (filterOption === false) { - return options; - } - - let filterOptionFunc: FilterFunc; - if (typeof filterOption === 'function') { - filterOptionFunc = filterOption; - } else { - filterOptionFunc = getDefaultFilterOption(optionFilterProp); - } - - function dig(list: DataNode[], keepAll = false) { - return list - .map(dataNode => { - const { children } = dataNode; - - const match = keepAll || filterOptionFunc(searchValue, fillLegacyProps(dataNode)); - const childList = dig(children || [], match); - - if (match || childList.length) { - return { - ...dataNode, - children: childList, - }; - } - return null; - }) - .filter(node => node); - } - - return dig(options); -} - -export function getRawValueLabeled( - values: RawValueType[], - prevValue: DefaultValueType, - getEntityByValue: ( - value: RawValueType, - skipType?: SkipType, - ignoreDisabledCheck?: boolean, - ) => FlattenDataNode, - getLabelProp: (entity: FlattenDataNode) => any, -): LabelValueType[] { - const valueMap = new Map(); - - toArray(prevValue).forEach(item => { - if (item && typeof item === 'object' && 'value' in item) { - valueMap.set(item.value, item); - } - }); - - return values.map(val => { - const item: LabelValueType = { value: val }; - const entity = getEntityByValue(val, 'select', true); - const label = entity ? getLabelProp(entity) : val; - - if (valueMap.has(val)) { - const labeledValue = valueMap.get(val); - item.label = 'label' in labeledValue ? labeledValue.label : label; - if ('halfChecked' in labeledValue) { - item.halfChecked = labeledValue.halfChecked; - } - } else { - item.label = label; - } - - return item; - }); -} - -export function addValue(rawValues: RawValueType[], value: RawValueType) { - const values = new Set(rawValues); - values.add(value); - return Array.from(values); -} -export function removeValue(rawValues: RawValueType[], value: RawValueType) { - const values = new Set(rawValues); - values.delete(value); - return Array.from(values); +export function isNil(val: any) { + return val === null || val === undefined; } diff --git a/components/vc-tree-select2/utils/warningPropsUtil.ts b/components/vc-tree-select2/utils/warningPropsUtil.ts index 2b58b8936..1af3d888e 100644 --- a/components/vc-tree-select2/utils/warningPropsUtil.ts +++ b/components/vc-tree-select2/utils/warningPropsUtil.ts @@ -1,7 +1,8 @@ import { warning } from '../../vc-util/warning'; +import type { TreeSelectProps } from '../TreeSelect'; import { toArray } from './valueUtil'; -function warningProps(props: any) { +function warningProps(props: TreeSelectProps & { searchPlaceholder?: string }) { const { searchPlaceholder, treeCheckStrictly, treeCheckable, labelInValue, value, multiple } = props;