refactor: tree-select
parent
314461848c
commit
4e8a16f2bb
|
@ -10,7 +10,7 @@ import VcTreeSelect, {
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||||
import type { SizeType } from '../config-provider';
|
import type { SizeType } from '../config-provider';
|
||||||
import type { DefaultValueType, FieldNames } from '../vc-tree-select/interface';
|
import type { FieldNames, Key } from '../vc-tree-select/interface';
|
||||||
import omit from '../_util/omit';
|
import omit from '../_util/omit';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
@ -21,6 +21,9 @@ import type { AntTreeNodeProps } from '../tree/Tree';
|
||||||
import { warning } from '../vc-util/warning';
|
import { warning } from '../vc-util/warning';
|
||||||
import { flattenChildren } from '../_util/props-util';
|
import { flattenChildren } from '../_util/props-util';
|
||||||
import { useInjectFormItemContext } from '../form/FormItemContext';
|
import { useInjectFormItemContext } from '../form/FormItemContext';
|
||||||
|
import type { BaseSelectRef } from '../vc-select';
|
||||||
|
import type { BaseOptionType, DefaultOptionType } from '../vc-tree-select/TreeSelect';
|
||||||
|
import type { TreeProps } from '../tree';
|
||||||
|
|
||||||
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
|
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
|
||||||
if (transitionName !== undefined) {
|
if (transitionName !== undefined) {
|
||||||
|
@ -34,29 +37,43 @@ type RawValue = string | number;
|
||||||
export interface LabeledValue {
|
export interface LabeledValue {
|
||||||
key?: string;
|
key?: string;
|
||||||
value: RawValue;
|
value: RawValue;
|
||||||
label: any;
|
label?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[];
|
export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[];
|
||||||
|
|
||||||
export interface RefTreeSelectProps {
|
export type RefTreeSelectProps = BaseSelectRef;
|
||||||
focus: () => void;
|
|
||||||
blur: () => void;
|
export function treeSelectProps<
|
||||||
|
ValueType = any,
|
||||||
|
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
|
||||||
|
>() {
|
||||||
|
return {
|
||||||
|
...omit(vcTreeSelectProps<ValueType, OptionType>(), [
|
||||||
|
'showTreeIcon',
|
||||||
|
'treeMotion',
|
||||||
|
'inputIcon',
|
||||||
|
'getInputElement',
|
||||||
|
'treeLine',
|
||||||
|
'customSlots',
|
||||||
|
]),
|
||||||
|
suffixIcon: PropTypes.any,
|
||||||
|
size: { type: String as PropType<SizeType> },
|
||||||
|
bordered: { type: Boolean, default: undefined },
|
||||||
|
treeLine: { type: [Boolean, Object] as PropType<TreeProps['showLine']>, default: undefined },
|
||||||
|
replaceFields: { type: Object as PropType<FieldNames> },
|
||||||
|
'onUpdate:value': { type: Function as PropType<(value: any) => void> },
|
||||||
|
'onUpdate:treeExpandedKeys': { type: Function as PropType<(keys: Key[]) => void> },
|
||||||
|
'onUpdate:searchValue': { type: Function as PropType<(value: string) => void> },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export const treeSelectProps = {
|
export type TreeSelectProps = Partial<ExtractPropTypes<ReturnType<typeof treeSelectProps>>>;
|
||||||
...omit(vcTreeSelectProps<DefaultValueType>(), ['showTreeIcon', 'treeMotion', 'inputIcon']),
|
|
||||||
suffixIcon: PropTypes.any,
|
|
||||||
size: { type: String as PropType<SizeType> },
|
|
||||||
bordered: { type: Boolean, default: undefined },
|
|
||||||
replaceFields: { type: Object as PropType<FieldNames> },
|
|
||||||
};
|
|
||||||
export type TreeSelectProps = Partial<ExtractPropTypes<typeof treeSelectProps>>;
|
|
||||||
|
|
||||||
const TreeSelect = defineComponent({
|
const TreeSelect = defineComponent({
|
||||||
TreeNode,
|
TreeNode,
|
||||||
name: 'ATreeSelect',
|
name: 'ATreeSelect',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: initDefaultProps(treeSelectProps, {
|
props: initDefaultProps(treeSelectProps(), {
|
||||||
choiceTransitionName: '',
|
choiceTransitionName: '',
|
||||||
listHeight: 256,
|
listHeight: 256,
|
||||||
treeIcon: false,
|
treeIcon: false,
|
||||||
|
@ -135,18 +152,18 @@ const TreeSelect = defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (...args: any[]) => {
|
const handleChange: TreeSelectProps['onChange'] = (...args: any[]) => {
|
||||||
emit('update:value', args[0]);
|
emit('update:value', args[0]);
|
||||||
emit('change', ...args);
|
emit('change', ...args);
|
||||||
formItemContext.onFieldChange();
|
formItemContext.onFieldChange();
|
||||||
};
|
};
|
||||||
const handleTreeExpand = (...args: any[]) => {
|
const handleTreeExpand: TreeSelectProps['onTreeExpand'] = (keys: Key[]) => {
|
||||||
emit('update:treeExpandedKeys', args[0]);
|
emit('update:treeExpandedKeys', keys);
|
||||||
emit('treeExpand', ...args);
|
emit('treeExpand', keys);
|
||||||
};
|
};
|
||||||
const handleSearch = (...args: any[]) => {
|
const handleSearch: TreeSelectProps['onSearch'] = (value: string) => {
|
||||||
emit('update:searchValue', args[0]);
|
emit('update:searchValue', value);
|
||||||
emit('search', ...args);
|
emit('search', value);
|
||||||
};
|
};
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
emit('blur');
|
emit('blur');
|
||||||
|
@ -190,6 +207,10 @@ const TreeSelect = defineComponent({
|
||||||
'removeIcon',
|
'removeIcon',
|
||||||
'clearIcon',
|
'clearIcon',
|
||||||
'switcherIcon',
|
'switcherIcon',
|
||||||
|
'bordered',
|
||||||
|
'onUpdate:value',
|
||||||
|
'onUpdate:treeExpandedKeys',
|
||||||
|
'onUpdate:searchValue',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const mergedClassName = classNames(
|
const mergedClassName = classNames(
|
||||||
|
@ -242,6 +263,10 @@ const TreeSelect = defineComponent({
|
||||||
}}
|
}}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
transitionName={transitionName.value}
|
transitionName={transitionName.value}
|
||||||
|
customSlots={{
|
||||||
|
...slots,
|
||||||
|
treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
.@{tree-select-prefix-cls} {
|
.@{tree-select-prefix-cls} {
|
||||||
// ======================= Dropdown =======================
|
// ======================= Dropdown =======================
|
||||||
&-dropdown {
|
&-dropdown {
|
||||||
padding: @padding-xs (@padding-xs / 2) 0;
|
padding: @padding-xs (@padding-xs / 2);
|
||||||
|
|
||||||
&-rtl {
|
&-rtl {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
|
@ -24,8 +24,6 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
.@{select-tree-prefix-cls}-treenode {
|
.@{select-tree-prefix-cls}-treenode {
|
||||||
padding-bottom: @padding-xs;
|
|
||||||
|
|
||||||
.@{select-tree-prefix-cls}-node-content-wrapper {
|
.@{select-tree-prefix-cls}-node-content-wrapper {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { flattenChildren, isValidElement } from '../_util/props-util';
|
|
||||||
|
|
||||||
export function convertChildrenToData(nodes: any[]): any[] {
|
|
||||||
return flattenChildren(nodes)
|
|
||||||
.map(node => {
|
|
||||||
if (!isValidElement(node) || !node.type) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { default: d, ...restSlot } = node.children || {};
|
|
||||||
const children = d ? d() : [];
|
|
||||||
const {
|
|
||||||
key,
|
|
||||||
props: { value, ...restProps },
|
|
||||||
} = node;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
...restProps,
|
|
||||||
};
|
|
||||||
Object.keys(restSlot).forEach(p => {
|
|
||||||
if (typeof restSlot[p] === 'function') {
|
|
||||||
data[p] = <>{restSlot[p]()}</>;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const childData = convertChildrenToData(children);
|
|
||||||
if (childData.length) {
|
|
||||||
data.children = childData;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
})
|
|
||||||
.filter(data => data);
|
|
||||||
}
|
|
|
@ -35,9 +35,9 @@ import isMobile from '../vc-util/isMobile';
|
||||||
import KeyCode from '../_util/KeyCode';
|
import KeyCode from '../_util/KeyCode';
|
||||||
import { toReactive } from '../_util/toReactive';
|
import { toReactive } from '../_util/toReactive';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import OptionList from './OptionList';
|
|
||||||
import createRef from '../_util/createRef';
|
import createRef from '../_util/createRef';
|
||||||
import type { BaseOptionType } from './Select';
|
import type { BaseOptionType } from './Select';
|
||||||
|
import useInjectLegacySelectContext from '../vc-tree-select/LegacyContext';
|
||||||
|
|
||||||
const DEFAULT_OMIT_PROPS = [
|
const DEFAULT_OMIT_PROPS = [
|
||||||
'value',
|
'value',
|
||||||
|
@ -269,7 +269,7 @@ export default defineComponent({
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
mobile.value = isMobile();
|
mobile.value = isMobile();
|
||||||
});
|
});
|
||||||
|
const legacyTreeSelectContext = useInjectLegacySelectContext();
|
||||||
// ============================== Refs ==============================
|
// ============================== Refs ==============================
|
||||||
const containerRef = ref<HTMLDivElement>(null);
|
const containerRef = ref<HTMLDivElement>(null);
|
||||||
const selectorDomRef = createRef();
|
const selectorDomRef = createRef();
|
||||||
|
@ -673,6 +673,7 @@ export default defineComponent({
|
||||||
emptyOptions,
|
emptyOptions,
|
||||||
activeDescendantId,
|
activeDescendantId,
|
||||||
activeValue,
|
activeValue,
|
||||||
|
OptionList,
|
||||||
|
|
||||||
...restProps
|
...restProps
|
||||||
} = { ...props, ...attrs } as BaseSelectProps;
|
} = { ...props, ...attrs } as BaseSelectProps;
|
||||||
|
@ -752,7 +753,12 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================== OptionList ===========================
|
// =========================== OptionList ===========================
|
||||||
const optionList = <OptionList ref={listRef} v-slots={{ option: slots.option }} />;
|
const optionList = (
|
||||||
|
<OptionList
|
||||||
|
ref={listRef}
|
||||||
|
v-slots={{ ...legacyTreeSelectContext.customSlots, option: slots.option }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
// ============================= Select =============================
|
// ============================= Select =============================
|
||||||
const mergedClassName = classNames(prefixCls, attrs.class, {
|
const mergedClassName = classNames(prefixCls, attrs.class, {
|
||||||
|
@ -840,7 +846,7 @@ export default defineComponent({
|
||||||
// onFocus={onContainerFocus}
|
// onFocus={onContainerFocus}
|
||||||
// onBlur={onContainerBlur}
|
// onBlur={onContainerBlur}
|
||||||
>
|
>
|
||||||
{mockFocused && !mergedOpen.value && (
|
{mockFocused.value && !mergedOpen.value && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
width: 0,
|
width: 0,
|
||||||
|
|
|
@ -3,8 +3,8 @@ import Input from './Input';
|
||||||
import type { InnerSelectorProps } from './interface';
|
import type { InnerSelectorProps } from './interface';
|
||||||
import { Fragment, computed, defineComponent, ref, watch } from 'vue';
|
import { Fragment, computed, defineComponent, ref, watch } from 'vue';
|
||||||
import PropTypes from '../../_util/vue-types';
|
import PropTypes from '../../_util/vue-types';
|
||||||
import { useInjectTreeSelectContext } from '../../vc-tree-select/Context';
|
|
||||||
import type { VueNode } from '../../_util/type';
|
import type { VueNode } from '../../_util/type';
|
||||||
|
import useInjectLegacySelectContext from '../../vc-tree-select/LegacyContext';
|
||||||
|
|
||||||
interface SelectorProps extends InnerSelectorProps {
|
interface SelectorProps extends InnerSelectorProps {
|
||||||
inputElement: VueNode;
|
inputElement: VueNode;
|
||||||
|
@ -50,7 +50,7 @@ const SingleSelector = defineComponent<SelectorProps>({
|
||||||
}
|
}
|
||||||
return inputValue;
|
return inputValue;
|
||||||
});
|
});
|
||||||
const treeSelectContext = useInjectTreeSelectContext();
|
const legacyTreeSelectContext = useInjectLegacySelectContext();
|
||||||
watch(
|
watch(
|
||||||
[combobox, () => props.activeValue],
|
[combobox, () => props.activeValue],
|
||||||
() => {
|
() => {
|
||||||
|
@ -108,13 +108,16 @@ const SingleSelector = defineComponent<SelectorProps>({
|
||||||
const item = values[0];
|
const item = values[0];
|
||||||
let titleNode = null;
|
let titleNode = null;
|
||||||
// custom tree-select title by slot
|
// custom tree-select title by slot
|
||||||
if (item && treeSelectContext.value.slots) {
|
|
||||||
|
if (item && legacyTreeSelectContext.customSlots) {
|
||||||
|
const key = item.key ?? item.value;
|
||||||
|
const originData = legacyTreeSelectContext.keyEntities[key]?.node || {};
|
||||||
titleNode =
|
titleNode =
|
||||||
treeSelectContext.value.slots[item?.option?.data?.slots?.title] ||
|
legacyTreeSelectContext.customSlots[originData.slots?.title] ||
|
||||||
treeSelectContext.value.slots.title ||
|
legacyTreeSelectContext.customSlots.title ||
|
||||||
item.label;
|
item.label;
|
||||||
if (typeof titleNode === 'function') {
|
if (typeof titleNode === 'function') {
|
||||||
titleNode = titleNode(item.option?.data || {});
|
titleNode = titleNode(originData);
|
||||||
}
|
}
|
||||||
// else if (treeSelectContext.value.slots.titleRender) {
|
// else if (treeSelectContext.value.slots.titleRender) {
|
||||||
// // 因历史 title 是覆盖逻辑,新增 titleRender,所有的 title 都走一遍 titleRender
|
// // 因历史 title 是覆盖逻辑,新增 titleRender,所有的 title 都走一遍 titleRender
|
||||||
|
@ -155,7 +158,7 @@ const SingleSelector = defineComponent<SelectorProps>({
|
||||||
{/* Display value */}
|
{/* Display value */}
|
||||||
{!combobox.value && item && !hasTextInput.value && (
|
{!combobox.value && item && !hasTextInput.value && (
|
||||||
<span class={`${prefixCls}-selection-item`} title={title.value}>
|
<span class={`${prefixCls}-selection-item`} title={title.value}>
|
||||||
<Fragment key={item.key || item.value}>{titleNode}</Fragment>
|
<Fragment key={item.key ?? item.value}>{titleNode}</Fragment>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
import type { InjectionKey } from 'vue';
|
import type { InjectionKey } from 'vue';
|
||||||
import { inject, provide } from 'vue';
|
import { inject, provide } from 'vue';
|
||||||
import type { DataEntity, IconType } from '../vc-tree/interface';
|
import type { DataEntity, IconType } from '../vc-tree/interface';
|
||||||
import type { Key, LegacyDataNode, RawValueType } from './interface';
|
import type { InternalDataEntity, Key, LegacyDataNode, RawValueType } from './interface';
|
||||||
|
|
||||||
interface LegacyContextProps {
|
export interface LegacyContextProps {
|
||||||
checkable: boolean;
|
checkable: boolean;
|
||||||
checkedKeys: Key[];
|
checkedKeys: Key[];
|
||||||
customCheckable: () => any;
|
customCheckable: () => any;
|
||||||
|
@ -29,11 +29,11 @@ interface LegacyContextProps {
|
||||||
|
|
||||||
keyEntities: Record<RawValueType, DataEntity<any>>;
|
keyEntities: Record<RawValueType, DataEntity<any>>;
|
||||||
|
|
||||||
// slots: {
|
customSlots: {
|
||||||
// title?: (data: InternalDataEntity) => any;
|
title?: (data: InternalDataEntity) => any;
|
||||||
// titleRender?: (data: InternalDataEntity) => any;
|
treeCheckable: () => any;
|
||||||
// [key: string]: ((...args: any[]) => any) | undefined;
|
[key: string]: ((...args: any[]) => any) | undefined;
|
||||||
// };
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const TreeSelectLegacyContextPropsKey: InjectionKey<LegacyContextProps> = Symbol(
|
const TreeSelectLegacyContextPropsKey: InjectionKey<LegacyContextProps> = Symbol(
|
|
@ -1,14 +1,16 @@
|
||||||
import type { DataNode, TreeDataNode, Key } from './interface';
|
import type { TreeDataNode, Key } from './interface';
|
||||||
import { useInjectTreeSelectContext } from './Context';
|
|
||||||
import type { RefOptionListProps } from '../vc-select/OptionList';
|
import type { RefOptionListProps } from '../vc-select/OptionList';
|
||||||
import type { ScrollTo } from '../vc-virtual-list/List';
|
import type { ScrollTo } from '../vc-virtual-list/List';
|
||||||
import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue';
|
import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue';
|
||||||
import { optionListProps } from './props';
|
|
||||||
import useMemo from '../_util/hooks/useMemo';
|
import useMemo from '../_util/hooks/useMemo';
|
||||||
import type { EventDataNode } from '../tree';
|
import type { EventDataNode } from '../tree';
|
||||||
import KeyCode from '../_util/KeyCode';
|
import KeyCode from '../_util/KeyCode';
|
||||||
import Tree from '../vc-tree/Tree';
|
import Tree from '../vc-tree/Tree';
|
||||||
import type { TreeProps } from '../vc-tree/props';
|
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 = {
|
const HIDDEN_STYLE = {
|
||||||
width: 0,
|
width: 0,
|
||||||
|
@ -32,44 +34,36 @@ type ReviseRefOptionListProps = Omit<RefOptionListProps, 'scrollTo'> & { scrollT
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'OptionList',
|
name: 'OptionList',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: optionListProps<DataNode>(),
|
|
||||||
slots: ['notFoundContent', 'menuItemSelectedIcon'],
|
slots: ['notFoundContent', 'menuItemSelectedIcon'],
|
||||||
setup(props, { slots, expose }) {
|
setup(_, { slots, expose }) {
|
||||||
const context = useInjectTreeSelectContext();
|
const baseProps = useBaseProps();
|
||||||
|
const legacyContext = useInjectLegacySelectContext();
|
||||||
|
const context = useInjectSelectContext();
|
||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const memoOptions = useMemo(
|
const memoTreeData = useMemo(
|
||||||
() => props.options,
|
() => context.treeData,
|
||||||
[() => props.open, () => props.options],
|
[() => baseProps.open, () => context.treeData],
|
||||||
next => next[0],
|
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 mergedCheckedKeys = computed(() => {
|
||||||
const { checkable, halfCheckedKeys } = context.value;
|
const { checkable, halfCheckedKeys, checkedKeys } = legacyContext;
|
||||||
if (!checkable) {
|
if (!checkable) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checked: valueKeys.value,
|
checked: checkedKeys,
|
||||||
halfChecked: halfCheckedKeys,
|
halfChecked: halfCheckedKeys,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.open,
|
() => baseProps.open,
|
||||||
() => {
|
() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (props.open && !props.multiple && valueKeys.value.length) {
|
if (baseProps.open && !baseProps.multiple && legacyContext.checkedKeys.length) {
|
||||||
treeRef.value?.scrollTo({ key: valueKeys.value[0] });
|
treeRef.value?.scrollTo({ key: legacyContext.checkedKeys[0] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -77,25 +71,25 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========================== Search ==========================
|
// ========================== Search ==========================
|
||||||
const lowerSearchValue = computed(() => String(props.searchValue).toLowerCase());
|
const lowerSearchValue = computed(() => String(baseProps.searchValue).toLowerCase());
|
||||||
const filterTreeNode = (treeNode: EventDataNode) => {
|
const filterTreeNode = (treeNode: EventDataNode) => {
|
||||||
if (!lowerSearchValue.value) {
|
if (!lowerSearchValue.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return String(treeNode[context.value.treeNodeFilterProp])
|
return String(treeNode[legacyContext.treeNodeFilterProp])
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(lowerSearchValue.value);
|
.includes(lowerSearchValue.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// =========================== Keys ===========================
|
// =========================== Keys ===========================
|
||||||
const expandedKeys = shallowRef<Key[]>(context.value.treeDefaultExpandedKeys);
|
const expandedKeys = shallowRef<Key[]>(legacyContext.treeDefaultExpandedKeys);
|
||||||
const searchExpandedKeys = shallowRef<Key[]>(null);
|
const searchExpandedKeys = shallowRef<Key[]>(null);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.searchValue,
|
() => baseProps.searchValue,
|
||||||
() => {
|
() => {
|
||||||
if (props.searchValue) {
|
if (baseProps.searchValue) {
|
||||||
searchExpandedKeys.value = props.flattenOptions.map(o => o.key);
|
searchExpandedKeys.value = getAllKeys(context.treeData, context.fieldNames);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -103,17 +97,17 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const mergedExpandedKeys = computed(() => {
|
const mergedExpandedKeys = computed(() => {
|
||||||
if (context.value.treeExpandedKeys) {
|
if (legacyContext.treeExpandedKeys) {
|
||||||
return [...context.value.treeExpandedKeys];
|
return [...legacyContext.treeExpandedKeys];
|
||||||
}
|
}
|
||||||
return props.searchValue ? searchExpandedKeys.value : expandedKeys.value;
|
return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onInternalExpand = (keys: Key[]) => {
|
const onInternalExpand = (keys: Key[]) => {
|
||||||
expandedKeys.value = keys;
|
expandedKeys.value = keys;
|
||||||
searchExpandedKeys.value = keys;
|
searchExpandedKeys.value = keys;
|
||||||
|
|
||||||
context.value.onTreeExpand?.(keys);
|
legacyContext.onTreeExpand?.(keys);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================== Events ==========================
|
// ========================== Events ==========================
|
||||||
|
@ -121,23 +115,23 @@ export default defineComponent({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInternalSelect = (_: Key[], { node: { key } }: TreeEventInfo) => {
|
const onInternalSelect = (_: Key[], { node }: TreeEventInfo) => {
|
||||||
const { getEntityByKey, checkable, checkedKeys } = context.value;
|
const { checkable, checkedKeys } = legacyContext;
|
||||||
const entity = getEntityByKey(key, checkable ? 'checkbox' : 'select');
|
if (checkable && isCheckDisabled(node)) {
|
||||||
if (entity !== null) {
|
return;
|
||||||
props.onSelect?.(entity.data.value, {
|
|
||||||
selected: !checkedKeys.includes(entity.data.value),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
context.onSelect?.(node.key, {
|
||||||
|
selected: !checkedKeys.includes(node.key),
|
||||||
|
});
|
||||||
|
|
||||||
if (!props.multiple) {
|
if (!baseProps.multiple) {
|
||||||
props.onToggleOpen?.(false);
|
baseProps.toggleOpen?.(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================= Keyboard =========================
|
// ========================= Keyboard =========================
|
||||||
const activeKey = ref<Key>(null);
|
const activeKey = ref<Key>(null);
|
||||||
const activeEntity = computed(() => context.value.getEntityByKey(activeKey.value));
|
const activeEntity = computed(() => legacyContext.keyEntities[activeKey.value]);
|
||||||
|
|
||||||
const setActiveKey = (key: Key) => {
|
const setActiveKey = (key: Key) => {
|
||||||
activeKey.value = key;
|
activeKey.value = key;
|
||||||
|
@ -157,11 +151,11 @@ export default defineComponent({
|
||||||
|
|
||||||
// >>> Select item
|
// >>> Select item
|
||||||
case KeyCode.ENTER: {
|
case KeyCode.ENTER: {
|
||||||
const { selectable, value } = activeEntity.value?.data.node || {};
|
const { selectable, value } = activeEntity.value?.node || {};
|
||||||
if (selectable !== false) {
|
if (selectable !== false) {
|
||||||
onInternalSelect(null, {
|
onInternalSelect(null, {
|
||||||
node: { key: activeKey.value },
|
node: { key: activeKey.value },
|
||||||
selected: !context.value.checkedKeys.includes(value),
|
selected: !legacyContext.checkedKeys.includes(value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -169,7 +163,7 @@ export default defineComponent({
|
||||||
|
|
||||||
// >>> Close
|
// >>> Close
|
||||||
case KeyCode.ESC: {
|
case KeyCode.ESC: {
|
||||||
props.onToggleOpen(false);
|
baseProps.toggleOpen(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -179,15 +173,12 @@ export default defineComponent({
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
height,
|
|
||||||
itemHeight,
|
|
||||||
virtual,
|
|
||||||
multiple,
|
multiple,
|
||||||
searchValue,
|
searchValue,
|
||||||
open,
|
open,
|
||||||
notFoundContent = slots.notFoundContent?.(),
|
notFoundContent = slots.notFoundContent?.(),
|
||||||
onMouseenter,
|
} = baseProps;
|
||||||
} = props;
|
const { listHeight, listItemHeight, virtual } = context;
|
||||||
const {
|
const {
|
||||||
checkable,
|
checkable,
|
||||||
treeDefaultExpandAll,
|
treeDefaultExpandAll,
|
||||||
|
@ -199,9 +190,10 @@ export default defineComponent({
|
||||||
treeLoadedKeys,
|
treeLoadedKeys,
|
||||||
treeMotion,
|
treeMotion,
|
||||||
onTreeLoad,
|
onTreeLoad,
|
||||||
} = context.value;
|
checkedKeys,
|
||||||
|
} = legacyContext;
|
||||||
// ========================== Render ==========================
|
// ========================== Render ==========================
|
||||||
if (memoOptions.value.length === 0) {
|
if (memoTreeData.value.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div role="listbox" class={`${prefixCls}-empty`} onMousedown={onListMouseDown}>
|
<div role="listbox" class={`${prefixCls}-empty`} onMousedown={onListMouseDown}>
|
||||||
{notFoundContent}
|
{notFoundContent}
|
||||||
|
@ -209,7 +201,9 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const treeProps: Partial<TreeProps> = {};
|
const treeProps: Partial<TreeProps> = {
|
||||||
|
fieldNames: context.fieldNames,
|
||||||
|
};
|
||||||
if (treeLoadedKeys) {
|
if (treeLoadedKeys) {
|
||||||
treeProps.loadedKeys = treeLoadedKeys;
|
treeProps.loadedKeys = treeLoadedKeys;
|
||||||
}
|
}
|
||||||
|
@ -217,10 +211,10 @@ export default defineComponent({
|
||||||
treeProps.expandedKeys = mergedExpandedKeys.value;
|
treeProps.expandedKeys = mergedExpandedKeys.value;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div onMousedown={onListMouseDown} onMouseenter={onMouseenter}>
|
<div onMousedown={onListMouseDown}>
|
||||||
{activeEntity.value && open && (
|
{activeEntity.value && open && (
|
||||||
<span style={HIDDEN_STYLE} aria-live="assertive">
|
<span style={HIDDEN_STYLE} aria-live="assertive">
|
||||||
{activeEntity.value.data.value}
|
{activeEntity.value.node.value}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -228,9 +222,9 @@ export default defineComponent({
|
||||||
ref={treeRef}
|
ref={treeRef}
|
||||||
focusable={false}
|
focusable={false}
|
||||||
prefixCls={`${prefixCls}-tree`}
|
prefixCls={`${prefixCls}-tree`}
|
||||||
treeData={memoOptions.value as TreeDataNode[]}
|
treeData={memoTreeData.value as TreeDataNode[]}
|
||||||
height={height}
|
height={listHeight}
|
||||||
itemHeight={itemHeight}
|
itemHeight={listItemHeight}
|
||||||
virtual={virtual}
|
virtual={virtual}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
icon={treeIcon}
|
icon={treeIcon}
|
||||||
|
@ -243,7 +237,7 @@ export default defineComponent({
|
||||||
checkable={checkable}
|
checkable={checkable}
|
||||||
checkStrictly
|
checkStrictly
|
||||||
checkedKeys={mergedCheckedKeys.value}
|
checkedKeys={mergedCheckedKeys.value}
|
||||||
selectedKeys={!checkable ? valueKeys.value : []}
|
selectedKeys={!checkable ? checkedKeys : []}
|
||||||
defaultExpandAll={treeDefaultExpandAll}
|
defaultExpandAll={treeDefaultExpandAll}
|
||||||
{...treeProps}
|
{...treeProps}
|
||||||
// Proxy event out
|
// Proxy event out
|
||||||
|
@ -253,7 +247,7 @@ export default defineComponent({
|
||||||
onExpand={onInternalExpand}
|
onExpand={onInternalExpand}
|
||||||
onLoad={onTreeLoad}
|
onLoad={onTreeLoad}
|
||||||
filterTreeNode={filterTreeNode}
|
filterTreeNode={filterTreeNode}
|
||||||
v-slots={{ ...slots, checkable: context.value.customCheckable }}
|
v-slots={{ ...slots, checkable: legacyContext.customSlots.treeCheckable }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,734 @@
|
||||||
import generate from './generate';
|
|
||||||
import OptionList from './OptionList';
|
import OptionList from './OptionList';
|
||||||
|
import { formatStrategyValues, SHOW_CHILD } from './utils/strategyUtil';
|
||||||
|
import type { CheckedStrategy } from './utils/strategyUtil';
|
||||||
|
import { useProvideSelectContext } from './TreeSelectContext';
|
||||||
|
import type { TreeSelectContextProps } from './TreeSelectContext';
|
||||||
|
import type { LegacyContextProps } from './LegacyContext';
|
||||||
|
import { useProvideLegacySelectContext } from './LegacyContext';
|
||||||
|
import useTreeData from './hooks/useTreeData';
|
||||||
|
import { toArray, fillFieldNames, isNil } from './utils/valueUtil';
|
||||||
|
import useCache from './hooks/useCache';
|
||||||
|
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 type { DisplayValueType } from '../vc-select/BaseSelect';
|
||||||
|
import { baseSelectPropsWithoutPrivate } from '../vc-select/BaseSelect';
|
||||||
|
import { computed, defineComponent, ref, shallowRef, toRaw, toRef, toRefs, watchEffect } from 'vue';
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue';
|
||||||
|
import omit from '../_util/omit';
|
||||||
|
import PropTypes from '../_util/vue-types';
|
||||||
|
import type { SelectProps, BaseSelectProps, BaseSelectRef } from '../vc-select';
|
||||||
|
import { BaseSelect } from '../vc-select';
|
||||||
|
import { initDefaultProps } from '../_util/props-util';
|
||||||
|
import useId from '../vc-select/hooks/useId';
|
||||||
|
import useMergedState from '../_util/hooks/useMergedState';
|
||||||
|
import type { VueNode } from '../_util/type';
|
||||||
|
import { conductCheck } from '../vc-tree/utils/conductUtil';
|
||||||
|
import { warning } from '../vc-util/warning';
|
||||||
|
import { toReactive } from '../_util/toReactive';
|
||||||
|
|
||||||
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<FieldNames, 'label'> {
|
||||||
|
_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<ValueType> },
|
||||||
|
defaultValue: { type: [String, Number, Object, Array] as PropType<ValueType> },
|
||||||
|
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<SelectProps['onSelect']>,
|
||||||
|
onDeselect: Function as PropType<SelectProps['onDeselect']>,
|
||||||
|
|
||||||
|
showCheckedStrategy: { type: String as PropType<CheckedStrategy> },
|
||||||
|
treeNodeLabelProp: String,
|
||||||
|
|
||||||
|
fieldNames: { type: Object as PropType<FieldNames> },
|
||||||
|
|
||||||
|
// >>> 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<OptionType[]> },
|
||||||
|
treeDataSimpleMode: {
|
||||||
|
type: [Boolean, Object] as PropType<boolean | SimpleModeConfig>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
loadData: { type: Function as PropType<(dataNode: LegacyDataNode) => Promise<unknown>> },
|
||||||
|
treeLoadedKeys: { type: Array as PropType<Key[]> },
|
||||||
|
onTreeLoad: { type: Function as PropType<(loadedKeys: Key[]) => void> },
|
||||||
|
|
||||||
|
// >>> Expanded
|
||||||
|
treeDefaultExpandAll: { type: Boolean, default: undefined },
|
||||||
|
treeExpandedKeys: { type: Array as PropType<Key[]> },
|
||||||
|
treeDefaultExpandedKeys: { type: Array as PropType<Key[]> },
|
||||||
|
onTreeExpand: { type: Function as PropType<(expandedKeys: 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,
|
||||||
|
children: Array as PropType<VueNode[]>,
|
||||||
|
|
||||||
|
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: DisplayValueType[]) => any> },
|
||||||
|
|
||||||
|
dropdownPopupAlign: PropTypes.any,
|
||||||
|
customSlots: Object,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TreeSelectProps = Partial<ExtractPropTypes<ReturnType<typeof treeSelectProps>>>;
|
||||||
|
|
||||||
|
function isRawValue(value: RawValueType | LabeledValueType): value is RawValueType {
|
||||||
|
return !value || typeof value !== 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TreeSelect',
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: initDefaultProps(treeSelectProps(), {
|
||||||
|
treeNodeFilterProp: 'value',
|
||||||
|
autoClearSearchValue: true,
|
||||||
|
showCheckedStrategy: SHOW_CHILD,
|
||||||
|
listHeight: 200,
|
||||||
|
listItemHeight: 20,
|
||||||
|
prefixCls: 'vc-tree-select',
|
||||||
|
}),
|
||||||
|
setup(props, { attrs, expose, slots }) {
|
||||||
|
const mergedId = useId(toRef(props, 'id'));
|
||||||
|
const treeConduction = computed(() => props.treeCheckable && !props.treeCheckStrictly);
|
||||||
|
const mergedCheckable = computed(() => props.treeCheckable || props.treeCheckStrictly);
|
||||||
|
const mergedLabelInValue = computed(() => props.treeCheckStrictly || props.labelInValue);
|
||||||
|
const mergedMultiple = computed(() => mergedCheckable.value || props.multiple);
|
||||||
|
|
||||||
|
// ========================== Warning ===========================
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
watchEffect(() => {
|
||||||
|
warningProps(props);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= FieldNames =========================
|
||||||
|
const mergedFieldNames = computed<InternalFieldName>(() => fillFieldNames(props.fieldNames));
|
||||||
|
|
||||||
|
// =========================== Search ===========================
|
||||||
|
const [mergedSearchValue, setSearchValue] = useMergedState('', {
|
||||||
|
value: computed(() =>
|
||||||
|
props.searchValue !== undefined ? props.searchValue : props.inputValue,
|
||||||
|
),
|
||||||
|
postState: search => search || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onInternalSearch: BaseSelectProps['onSearch'] = searchText => {
|
||||||
|
setSearchValue(searchText);
|
||||||
|
props.onSearch?.(searchText);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================ Data ============================
|
||||||
|
// `useTreeData` only do convert of `children` or `simpleMode`.
|
||||||
|
// Else will return origin `treeData` for perf consideration.
|
||||||
|
// Do not do anything to loop the data.
|
||||||
|
const mergedTreeData = useTreeData(
|
||||||
|
toRef(props, 'treeData'),
|
||||||
|
toRef(props, 'children'),
|
||||||
|
toRef(props, 'treeDataSimpleMode'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { keyEntities, valueEntities } = useDataEntities(mergedTreeData, mergedFieldNames);
|
||||||
|
|
||||||
|
/** Get `missingRawValues` which not exist in the tree yet */
|
||||||
|
const splitRawValues = (newRawValues: RawValueType[]) => {
|
||||||
|
const missingRawValues = [];
|
||||||
|
const existRawValues = [];
|
||||||
|
|
||||||
|
// Keep missing value in the cache
|
||||||
|
newRawValues.forEach(val => {
|
||||||
|
if (valueEntities.value.has(val)) {
|
||||||
|
existRawValues.push(val);
|
||||||
|
} else {
|
||||||
|
missingRawValues.push(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { missingRawValues, existRawValues };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filtered Tree
|
||||||
|
const filteredTreeData = useFilterTreeData(mergedTreeData, mergedSearchValue, {
|
||||||
|
fieldNames: mergedFieldNames,
|
||||||
|
treeNodeFilterProp: toRef(props, 'treeNodeFilterProp'),
|
||||||
|
filterTreeNode: toRef(props, 'filterTreeNode'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================== Label ============================
|
||||||
|
const getLabel = (item: DefaultOptionType) => {
|
||||||
|
if (item) {
|
||||||
|
if (props.treeNodeLabelProp) {
|
||||||
|
return item[props.treeNodeLabelProp];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop from fieldNames
|
||||||
|
const { _title: titleList } = mergedFieldNames.value;
|
||||||
|
|
||||||
|
for (let i = 0; i < titleList.length; i += 1) {
|
||||||
|
const title = item[titleList[i]];
|
||||||
|
if (title !== undefined) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================= Wrap Value =========================
|
||||||
|
const toLabeledValues = (draftValues: DraftValueType) => {
|
||||||
|
const values = toArray(draftValues);
|
||||||
|
|
||||||
|
return values.map(val => {
|
||||||
|
if (isRawValue(val)) {
|
||||||
|
return { value: val };
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const convert2LabelValues = (draftValues: DraftValueType) => {
|
||||||
|
const values = toLabeledValues(draftValues);
|
||||||
|
|
||||||
|
return values.map(item => {
|
||||||
|
let { label: rawLabel } = item;
|
||||||
|
const { value: rawValue, halfChecked: rawHalfChecked } = item;
|
||||||
|
|
||||||
|
let rawDisabled: boolean | undefined;
|
||||||
|
|
||||||
|
const entity = valueEntities.value.get(rawValue);
|
||||||
|
|
||||||
|
// Fill missing label & status
|
||||||
|
if (entity) {
|
||||||
|
rawLabel = rawLabel ?? getLabel(entity.node);
|
||||||
|
rawDisabled = entity.node.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: rawLabel,
|
||||||
|
value: rawValue,
|
||||||
|
halfChecked: rawHalfChecked,
|
||||||
|
disabled: rawDisabled,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================== Values ===========================
|
||||||
|
const [internalValue, setInternalValue] = useMergedState(props.defaultValue, {
|
||||||
|
value: toRef(props, 'value'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawMixedLabeledValues = computed(() => toLabeledValues(internalValue.value));
|
||||||
|
|
||||||
|
// Split value into full check and half check
|
||||||
|
const rawLabeledValues = shallowRef([]);
|
||||||
|
const rawHalfLabeledValues = shallowRef([]);
|
||||||
|
watchEffect(() => {
|
||||||
|
const fullCheckValues: LabeledValueType[] = [];
|
||||||
|
const halfCheckValues: LabeledValueType[] = [];
|
||||||
|
|
||||||
|
rawMixedLabeledValues.value.forEach(item => {
|
||||||
|
if (item.halfChecked) {
|
||||||
|
halfCheckValues.push(item);
|
||||||
|
} else {
|
||||||
|
fullCheckValues.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rawLabeledValues.value = fullCheckValues;
|
||||||
|
rawHalfLabeledValues.value = halfCheckValues;
|
||||||
|
});
|
||||||
|
|
||||||
|
// const [mergedValues] = useCache(rawLabeledValues);
|
||||||
|
const rawValues = computed(() => rawLabeledValues.value.map(item => item.value));
|
||||||
|
|
||||||
|
// Convert value to key. Will fill missed keys for conduct check.
|
||||||
|
const [rawCheckedValues, rawHalfCheckedValues] = useCheckedKeys(
|
||||||
|
rawLabeledValues,
|
||||||
|
rawHalfLabeledValues,
|
||||||
|
treeConduction,
|
||||||
|
keyEntities,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert rawCheckedKeys to check strategy related values
|
||||||
|
const displayValues = computed(() => {
|
||||||
|
// Collect keys which need to show
|
||||||
|
const displayKeys = formatStrategyValues(
|
||||||
|
rawCheckedValues.value,
|
||||||
|
props.showCheckedStrategy,
|
||||||
|
keyEntities.value,
|
||||||
|
mergedFieldNames.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert to value and filled with label
|
||||||
|
const values = displayKeys.map(
|
||||||
|
key => keyEntities.value[key]?.node?.[mergedFieldNames.value.value] ?? key,
|
||||||
|
);
|
||||||
|
const rawDisplayValues = convert2LabelValues(values);
|
||||||
|
|
||||||
|
const firstVal = rawDisplayValues[0];
|
||||||
|
|
||||||
|
if (!mergedMultiple.value && firstVal && isNil(firstVal.value) && isNil(firstVal.label)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return rawDisplayValues.map(item => ({
|
||||||
|
...item,
|
||||||
|
label: item.label ?? item.value,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const [cachedDisplayValues] = useCache(displayValues);
|
||||||
|
|
||||||
|
// =========================== Change ===========================
|
||||||
|
const triggerChange = (
|
||||||
|
newRawValues: RawValueType[],
|
||||||
|
extra: { triggerValue?: RawValueType; selected?: boolean },
|
||||||
|
source: SelectSource,
|
||||||
|
) => {
|
||||||
|
const labeledValues = convert2LabelValues(newRawValues);
|
||||||
|
setInternalValue(labeledValues);
|
||||||
|
|
||||||
|
// Clean up if needed
|
||||||
|
if (props.autoClearSearchValue) {
|
||||||
|
setSearchValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate rest parameters is costly, so only do it when necessary
|
||||||
|
if (props.onChange) {
|
||||||
|
let eventValues: RawValueType[] = newRawValues;
|
||||||
|
if (treeConduction.value) {
|
||||||
|
const formattedKeyList = formatStrategyValues(
|
||||||
|
newRawValues,
|
||||||
|
props.showCheckedStrategy,
|
||||||
|
keyEntities.value,
|
||||||
|
mergedFieldNames.value,
|
||||||
|
);
|
||||||
|
eventValues = formattedKeyList.map(key => {
|
||||||
|
const entity = valueEntities.value.get(key);
|
||||||
|
return entity ? entity.node[mergedFieldNames.value.value] : key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { triggerValue, selected } = extra || {
|
||||||
|
triggerValue: undefined,
|
||||||
|
selected: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
let returnRawValues: (LabeledValueType | RawValueType)[] = eventValues;
|
||||||
|
|
||||||
|
// We need fill half check back
|
||||||
|
if (props.treeCheckStrictly) {
|
||||||
|
const halfValues = rawHalfLabeledValues.value.filter(
|
||||||
|
item => !eventValues.includes(item.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
returnRawValues = [...returnRawValues, ...halfValues];
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnLabeledValues = convert2LabelValues(returnRawValues);
|
||||||
|
const additionalInfo = {
|
||||||
|
// [Legacy] Always return as array contains label & value
|
||||||
|
preValue: rawLabeledValues.value,
|
||||||
|
triggerValue,
|
||||||
|
} as ChangeEventExtra;
|
||||||
|
|
||||||
|
// [Legacy] Fill legacy data if user query.
|
||||||
|
// This is expansive that we only fill when user query
|
||||||
|
// https://github.com/react-component/tree-select/blob/fe33eb7c27830c9ac70cd1fdb1ebbe7bc679c16a/src/Select.jsx
|
||||||
|
let showPosition = true;
|
||||||
|
if (props.treeCheckStrictly || (source === 'selection' && !selected)) {
|
||||||
|
showPosition = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fillAdditionalInfo(
|
||||||
|
additionalInfo,
|
||||||
|
triggerValue,
|
||||||
|
newRawValues,
|
||||||
|
mergedTreeData.value,
|
||||||
|
showPosition,
|
||||||
|
mergedFieldNames.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mergedCheckable.value) {
|
||||||
|
additionalInfo.checked = selected;
|
||||||
|
} else {
|
||||||
|
additionalInfo.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnValues = mergedLabelInValue.value
|
||||||
|
? returnLabeledValues
|
||||||
|
: returnLabeledValues.map(item => item.value);
|
||||||
|
|
||||||
|
props.onChange(
|
||||||
|
mergedMultiple.value ? returnValues : returnValues[0],
|
||||||
|
mergedLabelInValue.value ? null : returnLabeledValues.map(item => item.label),
|
||||||
|
additionalInfo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================== Options ===========================
|
||||||
|
/** Trigger by option list */
|
||||||
|
const onOptionSelect = (
|
||||||
|
selectedKey: Key,
|
||||||
|
{ selected, source }: { selected: boolean; source: SelectSource },
|
||||||
|
) => {
|
||||||
|
const entity = keyEntities.value[selectedKey];
|
||||||
|
const node = entity?.node;
|
||||||
|
const selectedValue = node?.[mergedFieldNames.value.value] ?? selectedKey;
|
||||||
|
|
||||||
|
// Never be falsy but keep it safe
|
||||||
|
if (!mergedMultiple.value) {
|
||||||
|
// Single mode always set value
|
||||||
|
triggerChange([selectedValue], { selected: true, triggerValue: selectedValue }, 'option');
|
||||||
|
} else {
|
||||||
|
let newRawValues = selected
|
||||||
|
? [...rawValues.value, selectedValue]
|
||||||
|
: rawCheckedValues.value.filter(v => v !== selectedValue);
|
||||||
|
|
||||||
|
// Add keys if tree conduction
|
||||||
|
if (treeConduction.value) {
|
||||||
|
// Should keep missing values
|
||||||
|
const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
|
||||||
|
const keyList = existRawValues.map(val => valueEntities.value.get(val).key);
|
||||||
|
|
||||||
|
// Conduction by selected or not
|
||||||
|
let checkedKeys: Key[];
|
||||||
|
if (selected) {
|
||||||
|
({ checkedKeys } = conductCheck(keyList, true, keyEntities.value));
|
||||||
|
} else {
|
||||||
|
({ checkedKeys } = conductCheck(
|
||||||
|
keyList,
|
||||||
|
{ checked: false, halfCheckedKeys: rawHalfCheckedValues.value },
|
||||||
|
keyEntities.value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill back of keys
|
||||||
|
newRawValues = [
|
||||||
|
...missingRawValues,
|
||||||
|
...checkedKeys.map(key => keyEntities.value[key].node[mergedFieldNames.value.value]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
triggerChange(newRawValues, { selected, triggerValue: selectedValue }, source || 'option');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger select event
|
||||||
|
if (selected || !mergedMultiple.value) {
|
||||||
|
props.onSelect?.(selectedValue, fillLegacyProps(node));
|
||||||
|
} else {
|
||||||
|
props.onDeselect?.(selectedValue, fillLegacyProps(node));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================== Dropdown ==========================
|
||||||
|
const onInternalDropdownVisibleChange = (open: boolean) => {
|
||||||
|
if (props.onDropdownVisibleChange) {
|
||||||
|
const legacyParam = {};
|
||||||
|
|
||||||
|
Object.defineProperty(legacyParam, 'documentClickClose', {
|
||||||
|
get() {
|
||||||
|
warning(false, 'Second param of `onDropdownVisibleChange` has been removed.');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
(props.onDropdownVisibleChange as any)(open, legacyParam);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====================== Display Change ========================
|
||||||
|
const onDisplayValuesChange: BaseSelectProps['onDisplayValuesChange'] = (newValues, info) => {
|
||||||
|
const newRawValues = newValues.map(item => item.value);
|
||||||
|
|
||||||
|
if (info.type === 'clear') {
|
||||||
|
triggerChange(newRawValues, {}, 'selection');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TreeSelect only have multiple mode which means display change only has remove
|
||||||
|
if (info.values.length) {
|
||||||
|
onOptionSelect(info.values[0].value, { selected: false, source: 'selection' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const {
|
||||||
|
treeNodeFilterProp,
|
||||||
|
|
||||||
|
// Data
|
||||||
|
loadData,
|
||||||
|
treeLoadedKeys,
|
||||||
|
onTreeLoad,
|
||||||
|
|
||||||
|
// Expanded
|
||||||
|
treeDefaultExpandAll,
|
||||||
|
treeExpandedKeys,
|
||||||
|
treeDefaultExpandedKeys,
|
||||||
|
onTreeExpand,
|
||||||
|
|
||||||
|
// Options
|
||||||
|
virtual,
|
||||||
|
listHeight,
|
||||||
|
listItemHeight,
|
||||||
|
|
||||||
|
// Tree
|
||||||
|
treeLine,
|
||||||
|
treeIcon,
|
||||||
|
showTreeIcon,
|
||||||
|
switcherIcon,
|
||||||
|
treeMotion,
|
||||||
|
customSlots,
|
||||||
|
} = toRefs(props);
|
||||||
|
toRaw;
|
||||||
|
useProvideLegacySelectContext(
|
||||||
|
toReactive({
|
||||||
|
checkable: mergedCheckable,
|
||||||
|
|
||||||
|
loadData,
|
||||||
|
treeLoadedKeys,
|
||||||
|
onTreeLoad,
|
||||||
|
checkedKeys: rawCheckedValues,
|
||||||
|
halfCheckedKeys: rawHalfCheckedValues,
|
||||||
|
treeDefaultExpandAll,
|
||||||
|
treeExpandedKeys,
|
||||||
|
treeDefaultExpandedKeys,
|
||||||
|
onTreeExpand,
|
||||||
|
treeIcon,
|
||||||
|
treeMotion,
|
||||||
|
showTreeIcon,
|
||||||
|
switcherIcon,
|
||||||
|
treeLine,
|
||||||
|
treeNodeFilterProp,
|
||||||
|
keyEntities,
|
||||||
|
customSlots,
|
||||||
|
} as unknown as LegacyContextProps),
|
||||||
|
);
|
||||||
|
useProvideSelectContext(
|
||||||
|
toReactive({
|
||||||
|
virtual,
|
||||||
|
listHeight,
|
||||||
|
listItemHeight,
|
||||||
|
treeData: filteredTreeData,
|
||||||
|
fieldNames: mergedFieldNames,
|
||||||
|
onSelect: onOptionSelect,
|
||||||
|
} as unknown as TreeSelectContextProps),
|
||||||
|
);
|
||||||
|
const selectRef = ref<BaseSelectRef>();
|
||||||
|
expose({
|
||||||
|
focus() {
|
||||||
|
selectRef.value?.focus();
|
||||||
|
},
|
||||||
|
blur() {
|
||||||
|
selectRef.value?.blur();
|
||||||
|
},
|
||||||
|
scrollTo(arg) {
|
||||||
|
selectRef.value?.scrollTo(arg);
|
||||||
|
},
|
||||||
|
} as BaseSelectRef);
|
||||||
|
return () => {
|
||||||
|
const restProps = omit(props, [
|
||||||
|
'id',
|
||||||
|
'prefixCls',
|
||||||
|
|
||||||
|
// Value
|
||||||
|
'value',
|
||||||
|
'defaultValue',
|
||||||
|
'onChange',
|
||||||
|
'onSelect',
|
||||||
|
'onDeselect',
|
||||||
|
|
||||||
|
// Search
|
||||||
|
'searchValue',
|
||||||
|
'inputValue',
|
||||||
|
'onSearch',
|
||||||
|
'autoClearSearchValue',
|
||||||
|
'filterTreeNode',
|
||||||
|
'treeNodeFilterProp',
|
||||||
|
|
||||||
|
// Selector
|
||||||
|
'showCheckedStrategy',
|
||||||
|
'treeNodeLabelProp',
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
'multiple',
|
||||||
|
'treeCheckable',
|
||||||
|
'treeCheckStrictly',
|
||||||
|
'labelInValue',
|
||||||
|
|
||||||
|
// FieldNames
|
||||||
|
'fieldNames',
|
||||||
|
|
||||||
|
// Data
|
||||||
|
'treeDataSimpleMode',
|
||||||
|
'treeData',
|
||||||
|
'children',
|
||||||
|
'loadData',
|
||||||
|
'treeLoadedKeys',
|
||||||
|
'onTreeLoad',
|
||||||
|
|
||||||
|
// Expanded
|
||||||
|
'treeDefaultExpandAll',
|
||||||
|
'treeExpandedKeys',
|
||||||
|
'treeDefaultExpandedKeys',
|
||||||
|
'onTreeExpand',
|
||||||
|
|
||||||
|
// Options
|
||||||
|
'virtual',
|
||||||
|
'listHeight',
|
||||||
|
'listItemHeight',
|
||||||
|
'onDropdownVisibleChange',
|
||||||
|
|
||||||
|
// Tree
|
||||||
|
'treeLine',
|
||||||
|
'treeIcon',
|
||||||
|
'showTreeIcon',
|
||||||
|
'switcherIcon',
|
||||||
|
'treeMotion',
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<BaseSelect
|
||||||
|
v-slots={slots}
|
||||||
|
ref={selectRef}
|
||||||
|
{...attrs}
|
||||||
|
{...restProps}
|
||||||
|
// >>> MISC
|
||||||
|
id={mergedId}
|
||||||
|
prefixCls={props.prefixCls}
|
||||||
|
mode={mergedMultiple.value ? 'multiple' : undefined}
|
||||||
|
// >>> Display Value
|
||||||
|
displayValues={cachedDisplayValues.value}
|
||||||
|
onDisplayValuesChange={onDisplayValuesChange}
|
||||||
|
// >>> Search
|
||||||
|
searchValue={mergedSearchValue.value}
|
||||||
|
onSearch={onInternalSearch}
|
||||||
|
// >>> Options
|
||||||
|
OptionList={OptionList}
|
||||||
|
emptyOptions={!mergedTreeData.value.length}
|
||||||
|
onDropdownVisibleChange={onInternalDropdownVisibleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -3,28 +3,28 @@ import type { DataEntity } from '../../vc-tree/interface';
|
||||||
import { conductCheck } from '../../vc-tree/utils/conductUtil';
|
import { conductCheck } from '../../vc-tree/utils/conductUtil';
|
||||||
import type { LabeledValueType, RawValueType } from '../TreeSelect';
|
import type { LabeledValueType, RawValueType } from '../TreeSelect';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { computed } from 'vue';
|
import { shallowRef, watchEffect } from 'vue';
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
rawLabeledValues: Ref<LabeledValueType[]>,
|
rawLabeledValues: Ref<LabeledValueType[]>,
|
||||||
rawHalfCheckedValues: Ref<LabeledValueType[]>,
|
rawHalfCheckedValues: Ref<LabeledValueType[]>,
|
||||||
treeConduction: Ref<boolean>,
|
treeConduction: Ref<boolean>,
|
||||||
keyEntities: Ref<Record<Key, DataEntity>>,
|
keyEntities: Ref<Record<Key, DataEntity>>,
|
||||||
) =>
|
) => {
|
||||||
computed(() => {
|
const newRawCheckedValues = shallowRef<RawValueType[]>([]);
|
||||||
|
const newRawHalfCheckedValues = shallowRef<RawValueType[]>([]);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
let checkedKeys: RawValueType[] = rawLabeledValues.value.map(({ value }) => value);
|
let checkedKeys: RawValueType[] = rawLabeledValues.value.map(({ value }) => value);
|
||||||
let halfCheckedKeys: RawValueType[] = rawHalfCheckedValues.value.map(({ value }) => value);
|
let halfCheckedKeys: RawValueType[] = rawHalfCheckedValues.value.map(({ value }) => value);
|
||||||
|
|
||||||
const missingValues = checkedKeys.filter(key => !keyEntities[key]);
|
const missingValues = checkedKeys.filter(key => !keyEntities.value[key]);
|
||||||
|
|
||||||
if (treeConduction.value) {
|
if (treeConduction.value) {
|
||||||
({ checkedKeys, halfCheckedKeys } = conductCheck(checkedKeys, true, keyEntities.value));
|
({ checkedKeys, halfCheckedKeys } = conductCheck(checkedKeys, true, keyEntities.value));
|
||||||
}
|
}
|
||||||
|
newRawCheckedValues.value = Array.from(new Set([...missingValues, ...checkedKeys]));
|
||||||
return [
|
newRawHalfCheckedValues.value = halfCheckedKeys;
|
||||||
// Checked keys should fill with missing keys which should de-duplicated
|
|
||||||
Array.from(new Set([...missingValues, ...checkedKeys])),
|
|
||||||
// Half checked keys
|
|
||||||
halfCheckedKeys,
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
return [newRawCheckedValues, newRawHalfCheckedValues];
|
||||||
|
};
|
|
@ -4,14 +4,13 @@ import type { FieldNames, RawValueType } from '../TreeSelect';
|
||||||
|
|
||||||
import { isNil } from '../utils/valueUtil';
|
import { isNil } from '../utils/valueUtil';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { computed } from 'vue';
|
import { ref, watchEffect } from 'vue';
|
||||||
import { warning } from '../../vc-util/warning';
|
import { warning } from '../../vc-util/warning';
|
||||||
|
|
||||||
export default (treeData: Ref<any>, fieldNames: Ref<FieldNames>) =>
|
export default (treeData: Ref<any>, fieldNames: Ref<FieldNames>) => {
|
||||||
computed<{
|
const valueEntities = ref<Map<RawValueType, DataEntity>>(new Map());
|
||||||
valueEntities: Map<RawValueType, DataEntity>;
|
const keyEntities = ref<Record<string, DataEntity>>({});
|
||||||
keyEntities: Record<string, DataEntity>;
|
watchEffect(() => {
|
||||||
}>(() => {
|
|
||||||
const collection = convertDataToEntities(treeData.value, {
|
const collection = convertDataToEntities(treeData.value, {
|
||||||
fieldNames: fieldNames.value,
|
fieldNames: fieldNames.value,
|
||||||
initWrapper: wrapper => ({
|
initWrapper: wrapper => ({
|
||||||
|
@ -34,7 +33,9 @@ export default (treeData: Ref<any>, fieldNames: Ref<FieldNames>) =>
|
||||||
}
|
}
|
||||||
wrapper.valueEntities.set(val, entity);
|
wrapper.valueEntities.set(val, entity);
|
||||||
},
|
},
|
||||||
});
|
}) as any;
|
||||||
|
valueEntities.value = collection.valueEntities;
|
||||||
return collection as any;
|
keyEntities.value = collection.keyEntities;
|
||||||
});
|
});
|
||||||
|
return { valueEntities, keyEntities };
|
||||||
|
};
|
|
@ -1,16 +1,9 @@
|
||||||
import { warning } from '../../vc-util/warning';
|
import type { Ref } from 'vue';
|
||||||
import type { ComputedRef, Ref } from 'vue';
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type {
|
import type { DataNode, SimpleModeConfig } from '../interface';
|
||||||
DataNode,
|
|
||||||
InternalDataEntity,
|
|
||||||
SimpleModeConfig,
|
|
||||||
RawValueType,
|
|
||||||
FieldNames,
|
|
||||||
} from '../interface';
|
|
||||||
import { convertChildrenToData } from '../utils/legacyUtil';
|
import { convertChildrenToData } from '../utils/legacyUtil';
|
||||||
|
import type { DefaultOptionType } from '../TreeSelect';
|
||||||
const MAX_WARNING_TIMES = 10;
|
import type { VueNode } from 'ant-design-vue/es/_util/type';
|
||||||
|
|
||||||
function parseSimpleTreeData(
|
function parseSimpleTreeData(
|
||||||
treeData: DataNode[],
|
treeData: DataNode[],
|
||||||
|
@ -48,108 +41,27 @@ function parseSimpleTreeData(
|
||||||
return rootNodeList;
|
return rootNodeList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format `treeData` with `value` & `key` which is used for calculation
|
|
||||||
*/
|
|
||||||
function formatTreeData(
|
|
||||||
treeData: DataNode[],
|
|
||||||
getLabelProp: (node: DataNode) => any,
|
|
||||||
fieldNames: FieldNames,
|
|
||||||
): InternalDataEntity[] {
|
|
||||||
let warningTimes = 0;
|
|
||||||
const valueSet = new Set<RawValueType>();
|
|
||||||
|
|
||||||
// Field names
|
|
||||||
const { value: fieldValue, children: fieldChildren } = fieldNames;
|
|
||||||
|
|
||||||
function dig(dataNodes: DataNode[]) {
|
|
||||||
return (dataNodes || []).map(node => {
|
|
||||||
const { key, disableCheckbox, disabled, checkable, selectable, isLeaf } = node;
|
|
||||||
|
|
||||||
const value = node[fieldValue];
|
|
||||||
const mergedValue = fieldValue in node ? value : key;
|
|
||||||
|
|
||||||
const dataNode: InternalDataEntity = {
|
|
||||||
disableCheckbox,
|
|
||||||
disabled,
|
|
||||||
key: key !== null && key !== undefined ? key : mergedValue,
|
|
||||||
value: mergedValue,
|
|
||||||
title: getLabelProp(node),
|
|
||||||
node,
|
|
||||||
selectable,
|
|
||||||
isLeaf,
|
|
||||||
dataRef: node,
|
|
||||||
checkable,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (node.slots) {
|
|
||||||
dataNode.slots = node.slots;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check `key` & `value` and warning user
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
if (
|
|
||||||
key !== null &&
|
|
||||||
key !== undefined &&
|
|
||||||
value !== undefined &&
|
|
||||||
String(key) !== String(value) &&
|
|
||||||
warningTimes < MAX_WARNING_TIMES
|
|
||||||
) {
|
|
||||||
warningTimes += 1;
|
|
||||||
warning(
|
|
||||||
false,
|
|
||||||
`\`key\` or \`value\` with TreeNode must be the same or you can remove one of them. key: ${key}, value: ${value}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
warning(!valueSet.has(value), `Same \`value\` exist in the tree: ${value}`);
|
|
||||||
valueSet.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldChildren in node) {
|
|
||||||
dataNode.children = dig(node[fieldChildren]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataNode;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return dig(treeData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert `treeData` or `children` into formatted `treeData`.
|
* Convert `treeData` or `children` into formatted `treeData`.
|
||||||
* Will not re-calculate if `treeData` or `children` not change.
|
* Will not re-calculate if `treeData` or `children` not change.
|
||||||
*/
|
*/
|
||||||
export default function useTreeData(
|
export default function useTreeData(
|
||||||
treeData: Ref<DataNode[]>,
|
treeData: Ref<DataNode[]>,
|
||||||
children: Ref<any[]>,
|
children: Ref<VueNode[]>,
|
||||||
{
|
simpleMode: Ref<boolean | SimpleModeConfig>,
|
||||||
getLabelProp,
|
): Ref<DefaultOptionType[]> {
|
||||||
simpleMode,
|
|
||||||
fieldNames,
|
|
||||||
}: {
|
|
||||||
getLabelProp: (node: DataNode) => any;
|
|
||||||
simpleMode: Ref<boolean | SimpleModeConfig>;
|
|
||||||
fieldNames: Ref<FieldNames>;
|
|
||||||
},
|
|
||||||
): ComputedRef<InternalDataEntity[]> {
|
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
if (treeData.value) {
|
if (treeData.value) {
|
||||||
return formatTreeData(
|
return simpleMode.value
|
||||||
simpleMode.value
|
? parseSimpleTreeData(treeData.value, {
|
||||||
? parseSimpleTreeData(treeData.value, {
|
id: 'id',
|
||||||
id: 'id',
|
pId: 'pId',
|
||||||
pId: 'pId',
|
rootPId: null,
|
||||||
rootPId: null,
|
...(simpleMode.value !== true ? simpleMode.value : {}),
|
||||||
...(simpleMode.value !== true ? simpleMode.value : {}),
|
})
|
||||||
})
|
: treeData.value;
|
||||||
: treeData.value,
|
|
||||||
getLabelProp,
|
|
||||||
fieldNames.value,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return formatTreeData(convertChildrenToData(children.value), getLabelProp, fieldNames.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return convertChildrenToData(children.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// base rc-tree-select@4.6.1
|
// base rc-tree-select@5.0.0-alpha.4
|
||||||
import TreeSelect from './TreeSelect';
|
import type { TreeSelectProps } from './TreeSelect';
|
||||||
|
import TreeSelect, { treeSelectProps } from './TreeSelect';
|
||||||
import TreeNode from './TreeNode';
|
import TreeNode from './TreeNode';
|
||||||
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
|
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
|
||||||
import type { TreeSelectProps } from './props';
|
|
||||||
import { treeSelectProps } from './props';
|
|
||||||
|
|
||||||
export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT, treeSelectProps };
|
export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT, treeSelectProps };
|
||||||
export type { TreeSelectProps };
|
export type { TreeSelectProps };
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
import { filterEmpty } from '../../_util/props-util';
|
import { filterEmpty } from '../../_util/props-util';
|
||||||
import { camelize } from 'vue';
|
import { camelize } from 'vue';
|
||||||
import { warning } from '../../vc-util/warning';
|
import { warning } from '../../vc-util/warning';
|
||||||
import type {
|
import type { DataNode, ChangeEventExtra, RawValueType, LegacyCheckedNode } from '../interface';
|
||||||
DataNode,
|
|
||||||
LegacyDataNode,
|
|
||||||
ChangeEventExtra,
|
|
||||||
InternalDataEntity,
|
|
||||||
RawValueType,
|
|
||||||
LegacyCheckedNode,
|
|
||||||
} from '../interface';
|
|
||||||
import TreeNode from '../TreeNode';
|
import TreeNode from '../TreeNode';
|
||||||
import type { VueNode } from '../../_util/type';
|
import type { VueNode } from '../../_util/type';
|
||||||
|
import type { DefaultOptionType, FieldNames } from '../TreeSelect';
|
||||||
|
|
||||||
function isTreeSelectNode(node: any) {
|
function isTreeSelectNode(node: any) {
|
||||||
return node && node.type && (node.type as any).isTreeSelectNode;
|
return node && node.type && (node.type as any).isTreeSelectNode;
|
||||||
}
|
}
|
||||||
export function convertChildrenToData(rootNodes: VueNode): DataNode[] {
|
export function convertChildrenToData(rootNodes: VueNode[]): DataNode[] {
|
||||||
function dig(treeNodes: any[] = []): DataNode[] {
|
function dig(treeNodes: any[] = []): DataNode[] {
|
||||||
return filterEmpty(treeNodes).map(treeNode => {
|
return filterEmpty(treeNodes).map(treeNode => {
|
||||||
// Filter invalidate node
|
// Filter invalidate node
|
||||||
|
@ -66,10 +60,10 @@ export function convertChildrenToData(rootNodes: VueNode): DataNode[] {
|
||||||
return dig(rootNodes as any[]);
|
return dig(rootNodes as any[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fillLegacyProps(dataNode: DataNode): LegacyDataNode {
|
export function fillLegacyProps(dataNode: DataNode): any {
|
||||||
// Skip if not dataNode exist
|
// Skip if not dataNode exist
|
||||||
if (!dataNode) {
|
if (!dataNode) {
|
||||||
return dataNode as LegacyDataNode;
|
return dataNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloneNode = { ...dataNode };
|
const cloneNode = { ...dataNode };
|
||||||
|
@ -79,37 +73,43 @@ export function fillLegacyProps(dataNode: DataNode): LegacyDataNode {
|
||||||
get() {
|
get() {
|
||||||
warning(
|
warning(
|
||||||
false,
|
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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloneNode as LegacyDataNode;
|
return cloneNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fillAdditionalInfo(
|
export function fillAdditionalInfo(
|
||||||
extra: ChangeEventExtra,
|
extra: ChangeEventExtra,
|
||||||
triggerValue: RawValueType,
|
triggerValue: RawValueType,
|
||||||
checkedValues: RawValueType[],
|
checkedValues: RawValueType[],
|
||||||
treeData: InternalDataEntity[],
|
treeData: DefaultOptionType[],
|
||||||
showPosition: boolean,
|
showPosition: boolean,
|
||||||
|
fieldNames: FieldNames,
|
||||||
) {
|
) {
|
||||||
let triggerNode = null;
|
let triggerNode = null;
|
||||||
let nodeList: LegacyCheckedNode[] = null;
|
let nodeList: LegacyCheckedNode[] = null;
|
||||||
|
|
||||||
function generateMap() {
|
function generateMap() {
|
||||||
function dig(list: InternalDataEntity[], level = '0', parentIncluded = false) {
|
function dig(list: DefaultOptionType[], level = '0', parentIncluded = false) {
|
||||||
return list
|
return list
|
||||||
.map((dataNode, index) => {
|
.map((option, index) => {
|
||||||
const pos = `${level}-${index}`;
|
const pos = `${level}-${index}`;
|
||||||
const included = checkedValues.includes(dataNode.value);
|
const value = option[fieldNames.value];
|
||||||
const children = dig(dataNode.children || [], pos, included);
|
const included = checkedValues.includes(value);
|
||||||
const node = <TreeNode {...dataNode}>{children.map(child => child.node)}</TreeNode>;
|
const children = dig(option[fieldNames.children] || [], pos, included);
|
||||||
|
const node = (
|
||||||
|
<TreeNode {...(option as Required<DefaultOptionType>)}>
|
||||||
|
{children.map(child => child.node)}
|
||||||
|
</TreeNode>
|
||||||
|
);
|
||||||
|
|
||||||
// Link with trigger node
|
// Link with trigger node
|
||||||
if (triggerValue === dataNode.value) {
|
if (triggerValue === value) {
|
||||||
triggerNode = node;
|
triggerNode = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { DataEntity } from '../../vc-tree/interface';
|
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';
|
import { isCheckDisabled } from './valueUtil';
|
||||||
|
|
||||||
export const SHOW_ALL = 'SHOW_ALL';
|
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 type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD;
|
||||||
|
|
||||||
export function formatStrategyKeys(
|
export function formatStrategyValues(
|
||||||
keys: Key[],
|
values: Key[],
|
||||||
strategy: CheckedStrategy,
|
strategy: CheckedStrategy,
|
||||||
keyEntities: Record<Key, DataEntity>,
|
keyEntities: Record<Key, DataEntity>,
|
||||||
|
fieldNames: InternalFieldName,
|
||||||
): RawValueType[] {
|
): RawValueType[] {
|
||||||
const keySet = new Set(keys);
|
const valueSet = new Set(values);
|
||||||
|
|
||||||
if (strategy === SHOW_CHILD) {
|
if (strategy === SHOW_CHILD) {
|
||||||
return keys.filter(key => {
|
return values.filter(key => {
|
||||||
const entity = keyEntities[key];
|
const entity = keyEntities[key];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
entity &&
|
entity &&
|
||||||
entity.children &&
|
entity.children &&
|
||||||
entity.children.every(
|
entity.children.every(
|
||||||
({ node }) => isCheckDisabled(node) || keySet.has((node as DataNode).key),
|
({ node }) => isCheckDisabled(node) || valueSet.has(node[fieldNames.value]),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -32,15 +34,14 @@ export function formatStrategyKeys(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (strategy === SHOW_PARENT) {
|
if (strategy === SHOW_PARENT) {
|
||||||
return keys.filter(key => {
|
return values.filter(key => {
|
||||||
const entity = keyEntities[key];
|
const entity = keyEntities[key];
|
||||||
const parent = entity ? entity.parent : null;
|
const parent = entity ? entity.parent : null;
|
||||||
|
if (parent && !isCheckDisabled(parent.node) && valueSet.has(parent.key)) {
|
||||||
if (parent && !isCheckDisabled(parent.node) && keySet.has((parent.node as DataNode).key)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return keys;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,5 @@
|
||||||
import type {
|
import type { Key, DataNode, FieldNames } from '../interface';
|
||||||
FlattenDataNode,
|
import type { DefaultOptionType, InternalFieldName } from '../TreeSelect';
|
||||||
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<FlattenDataNode, 'level'>;
|
|
||||||
|
|
||||||
export function toArray<T>(value: T | T[]): T[] {
|
export function toArray<T>(value: T | T[]): T[] {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
|
@ -24,221 +8,43 @@ export function toArray<T>(value: T | T[]): T[] {
|
||||||
return value !== undefined ? [value] : [];
|
return value !== undefined ? [value] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function fillFieldNames(fieldNames?: FieldNames) {
|
||||||
* 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) {
|
|
||||||
const { label, value, children } = fieldNames || {};
|
const { label, value, children } = fieldNames || {};
|
||||||
|
|
||||||
const filledNames: FieldNames = {
|
const mergedValue = value || 'value';
|
||||||
value: value || 'value',
|
|
||||||
|
return {
|
||||||
|
_title: label ? [label] : ['title', 'label'],
|
||||||
|
value: mergedValue,
|
||||||
|
key: mergedValue,
|
||||||
children: children || 'children',
|
children: children || 'children',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!skipTitle || label) {
|
|
||||||
filledNames.label = label || 'label';
|
|
||||||
}
|
|
||||||
|
|
||||||
return filledNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findValueOption(values: RawValueType[], options: CompatibleDataNode[]): DataNode[] {
|
|
||||||
const optionMap: Map<RawValueType, DataNode> = 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) {
|
export function isCheckDisabled(node: DataNode) {
|
||||||
return node.disabled || node.disableCheckbox || node.checkable === false;
|
return node.disabled || node.disableCheckbox || node.checkable === false;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeDataNode extends InternalDataEntity {
|
/** Loop fetch all the keys exist in the tree */
|
||||||
key: Key;
|
export function getAllKeys(treeData: DefaultOptionType[], fieldNames: InternalFieldName) {
|
||||||
children?: TreeDataNode[];
|
const keys: Key[] = [];
|
||||||
}
|
|
||||||
|
|
||||||
function getLevel({ parent }: FlattenNode): number {
|
function dig(list: DefaultOptionType[]) {
|
||||||
let level = 0;
|
list.forEach(item => {
|
||||||
let current = parent;
|
keys.push(item[fieldNames.value]);
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const children = item[fieldNames.children];
|
||||||
if (children) {
|
if (children) {
|
||||||
clone.children = fillKey(children);
|
dig(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return clone;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const flattenList = flattenTreeData(fillKey(typedOptions), true, null);
|
dig(treeData);
|
||||||
|
|
||||||
const cacheMap = new Map<Key, FlattenDataNode>();
|
return keys;
|
||||||
const flattenDateNodeList: (FlattenDataNode & { parentKey?: Key })[] = flattenList.map(option => {
|
|
||||||
const { data, key, value } = option as any as Omit<FlattenNode, 'data'> & {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultFilterOption(optionFilterProp: string) {
|
export function isNil(val: any) {
|
||||||
return (searchValue: string, dataNode: LegacyDataNode) => {
|
return val === null || val === undefined;
|
||||||
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<LegacyDataNode>;
|
|
||||||
},
|
|
||||||
): DataNode[] {
|
|
||||||
if (filterOption === false) {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filterOptionFunc: FilterFunc<LegacyDataNode>;
|
|
||||||
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<RawValueType, LabelValueType>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { warning } from '../../vc-util/warning';
|
import { warning } from '../../vc-util/warning';
|
||||||
|
import type { TreeSelectProps } from '../TreeSelect';
|
||||||
import { toArray } from './valueUtil';
|
import { toArray } from './valueUtil';
|
||||||
|
|
||||||
function warningProps(props: any) {
|
function warningProps(props: TreeSelectProps & { searchPlaceholder?: string }) {
|
||||||
const { searchPlaceholder, treeCheckStrictly, treeCheckable, labelInValue, value, multiple } =
|
const { searchPlaceholder, treeCheckStrictly, treeCheckable, labelInValue, value, multiple } =
|
||||||
props;
|
props;
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import type { TreeDataNode, Key } from './interface';
|
import type { DataNode, TreeDataNode, Key } from './interface';
|
||||||
|
import { useInjectTreeSelectContext } from './Context';
|
||||||
import type { RefOptionListProps } from '../vc-select/OptionList';
|
import type { RefOptionListProps } from '../vc-select/OptionList';
|
||||||
import type { ScrollTo } from '../vc-virtual-list/List';
|
import type { ScrollTo } from '../vc-virtual-list/List';
|
||||||
import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue';
|
import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue';
|
||||||
|
import { optionListProps } from './props';
|
||||||
import useMemo from '../_util/hooks/useMemo';
|
import useMemo from '../_util/hooks/useMemo';
|
||||||
import type { EventDataNode } from '../tree';
|
import type { EventDataNode } from '../tree';
|
||||||
import KeyCode from '../_util/KeyCode';
|
import KeyCode from '../_util/KeyCode';
|
||||||
import Tree from '../vc-tree/Tree';
|
import Tree from '../vc-tree/Tree';
|
||||||
import type { TreeProps } from '../vc-tree/props';
|
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 = {
|
const HIDDEN_STYLE = {
|
||||||
width: 0,
|
width: 0,
|
||||||
|
@ -34,36 +32,44 @@ type ReviseRefOptionListProps = Omit<RefOptionListProps, 'scrollTo'> & { scrollT
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'OptionList',
|
name: 'OptionList',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
props: optionListProps<DataNode>(),
|
||||||
slots: ['notFoundContent', 'menuItemSelectedIcon'],
|
slots: ['notFoundContent', 'menuItemSelectedIcon'],
|
||||||
setup(_, { slots, expose }) {
|
setup(props, { slots, expose }) {
|
||||||
const baseProps = useBaseProps();
|
const context = useInjectTreeSelectContext();
|
||||||
const legacyContext = useInjectLegacySelectContext();
|
|
||||||
const context = useInjectSelectContext();
|
|
||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const memoTreeData = useMemo(
|
const memoOptions = useMemo(
|
||||||
() => context.treeData,
|
() => props.options,
|
||||||
[() => baseProps.open, () => context.treeData],
|
[() => props.open, () => props.options],
|
||||||
next => next[0],
|
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 mergedCheckedKeys = computed(() => {
|
||||||
const { checkable, halfCheckedKeys, checkedKeys } = legacyContext;
|
const { checkable, halfCheckedKeys } = context.value;
|
||||||
if (!checkable) {
|
if (!checkable) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checked: checkedKeys,
|
checked: valueKeys.value,
|
||||||
halfChecked: halfCheckedKeys,
|
halfChecked: halfCheckedKeys,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => baseProps.open,
|
() => props.open,
|
||||||
() => {
|
() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (baseProps.open && !baseProps.multiple && legacyContext.checkedKeys.length) {
|
if (props.open && !props.multiple && valueKeys.value.length) {
|
||||||
treeRef.value?.scrollTo({ key: legacyContext.checkedKeys[0] });
|
treeRef.value?.scrollTo({ key: valueKeys.value[0] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -71,25 +77,25 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========================== Search ==========================
|
// ========================== Search ==========================
|
||||||
const lowerSearchValue = computed(() => String(baseProps.searchValue).toLowerCase());
|
const lowerSearchValue = computed(() => String(props.searchValue).toLowerCase());
|
||||||
const filterTreeNode = (treeNode: EventDataNode) => {
|
const filterTreeNode = (treeNode: EventDataNode) => {
|
||||||
if (!lowerSearchValue.value) {
|
if (!lowerSearchValue.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return String(treeNode[legacyContext.treeNodeFilterProp])
|
return String(treeNode[context.value.treeNodeFilterProp])
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(lowerSearchValue.value);
|
.includes(lowerSearchValue.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// =========================== Keys ===========================
|
// =========================== Keys ===========================
|
||||||
const expandedKeys = shallowRef<Key[]>(legacyContext.treeDefaultExpandedKeys);
|
const expandedKeys = shallowRef<Key[]>(context.value.treeDefaultExpandedKeys);
|
||||||
const searchExpandedKeys = shallowRef<Key[]>(null);
|
const searchExpandedKeys = shallowRef<Key[]>(null);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => baseProps.searchValue,
|
() => props.searchValue,
|
||||||
() => {
|
() => {
|
||||||
if (baseProps.searchValue) {
|
if (props.searchValue) {
|
||||||
searchExpandedKeys.value = getAllKeys(context.treeData, context.fieldNames);
|
searchExpandedKeys.value = props.flattenOptions.map(o => o.key);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -97,17 +103,17 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const mergedExpandedKeys = computed(() => {
|
const mergedExpandedKeys = computed(() => {
|
||||||
if (legacyContext.treeExpandedKeys) {
|
if (context.value.treeExpandedKeys) {
|
||||||
return [...legacyContext.treeExpandedKeys];
|
return [...context.value.treeExpandedKeys];
|
||||||
}
|
}
|
||||||
return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value;
|
return props.searchValue ? searchExpandedKeys.value : expandedKeys.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onInternalExpand = (keys: Key[]) => {
|
const onInternalExpand = (keys: Key[]) => {
|
||||||
expandedKeys.value = keys;
|
expandedKeys.value = keys;
|
||||||
searchExpandedKeys.value = keys;
|
searchExpandedKeys.value = keys;
|
||||||
|
|
||||||
legacyContext.onTreeExpand?.(keys);
|
context.value.onTreeExpand?.(keys);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================== Events ==========================
|
// ========================== Events ==========================
|
||||||
|
@ -115,23 +121,23 @@ export default defineComponent({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInternalSelect = (_: Key[], { node }: TreeEventInfo) => {
|
const onInternalSelect = (_: Key[], { node: { key } }: TreeEventInfo) => {
|
||||||
const { checkable, checkedKeys } = legacyContext;
|
const { getEntityByKey, checkable, checkedKeys } = context.value;
|
||||||
if (checkable && isCheckDisabled(node)) {
|
const entity = getEntityByKey(key, checkable ? 'checkbox' : 'select');
|
||||||
return;
|
if (entity !== null) {
|
||||||
|
props.onSelect?.(entity.data.value, {
|
||||||
|
selected: !checkedKeys.includes(entity.data.value),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
context.onSelect?.(node.key, {
|
|
||||||
selected: !checkedKeys.includes(node.key),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!baseProps.multiple) {
|
if (!props.multiple) {
|
||||||
baseProps.toggleOpen?.(false);
|
props.onToggleOpen?.(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================= Keyboard =========================
|
// ========================= Keyboard =========================
|
||||||
const activeKey = ref<Key>(null);
|
const activeKey = ref<Key>(null);
|
||||||
const activeEntity = computed(() => legacyContext.keyEntities[activeKey.value]);
|
const activeEntity = computed(() => context.value.getEntityByKey(activeKey.value));
|
||||||
|
|
||||||
const setActiveKey = (key: Key) => {
|
const setActiveKey = (key: Key) => {
|
||||||
activeKey.value = key;
|
activeKey.value = key;
|
||||||
|
@ -151,11 +157,11 @@ export default defineComponent({
|
||||||
|
|
||||||
// >>> Select item
|
// >>> Select item
|
||||||
case KeyCode.ENTER: {
|
case KeyCode.ENTER: {
|
||||||
const { selectable, value } = activeEntity.value?.node || {};
|
const { selectable, value } = activeEntity.value?.data.node || {};
|
||||||
if (selectable !== false) {
|
if (selectable !== false) {
|
||||||
onInternalSelect(null, {
|
onInternalSelect(null, {
|
||||||
node: { key: activeKey.value },
|
node: { key: activeKey.value },
|
||||||
selected: !legacyContext.checkedKeys.includes(value),
|
selected: !context.value.checkedKeys.includes(value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -163,7 +169,7 @@ export default defineComponent({
|
||||||
|
|
||||||
// >>> Close
|
// >>> Close
|
||||||
case KeyCode.ESC: {
|
case KeyCode.ESC: {
|
||||||
baseProps.toggleOpen(false);
|
props.onToggleOpen(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -173,12 +179,15 @@ export default defineComponent({
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
|
height,
|
||||||
|
itemHeight,
|
||||||
|
virtual,
|
||||||
multiple,
|
multiple,
|
||||||
searchValue,
|
searchValue,
|
||||||
open,
|
open,
|
||||||
notFoundContent = slots.notFoundContent?.(),
|
notFoundContent = slots.notFoundContent?.(),
|
||||||
} = baseProps;
|
onMouseenter,
|
||||||
const { listHeight, listItemHeight, virtual } = context;
|
} = props;
|
||||||
const {
|
const {
|
||||||
checkable,
|
checkable,
|
||||||
treeDefaultExpandAll,
|
treeDefaultExpandAll,
|
||||||
|
@ -190,10 +199,9 @@ export default defineComponent({
|
||||||
treeLoadedKeys,
|
treeLoadedKeys,
|
||||||
treeMotion,
|
treeMotion,
|
||||||
onTreeLoad,
|
onTreeLoad,
|
||||||
checkedKeys,
|
} = context.value;
|
||||||
} = legacyContext;
|
|
||||||
// ========================== Render ==========================
|
// ========================== Render ==========================
|
||||||
if (memoTreeData.value.length === 0) {
|
if (memoOptions.value.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div role="listbox" class={`${prefixCls}-empty`} onMousedown={onListMouseDown}>
|
<div role="listbox" class={`${prefixCls}-empty`} onMousedown={onListMouseDown}>
|
||||||
{notFoundContent}
|
{notFoundContent}
|
||||||
|
@ -209,10 +217,10 @@ export default defineComponent({
|
||||||
treeProps.expandedKeys = mergedExpandedKeys.value;
|
treeProps.expandedKeys = mergedExpandedKeys.value;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div onMousedown={onListMouseDown}>
|
<div onMousedown={onListMouseDown} onMouseenter={onMouseenter}>
|
||||||
{activeEntity.value && open && (
|
{activeEntity.value && open && (
|
||||||
<span style={HIDDEN_STYLE} aria-live="assertive">
|
<span style={HIDDEN_STYLE} aria-live="assertive">
|
||||||
{activeEntity.value.node.value}
|
{activeEntity.value.data.value}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -220,9 +228,9 @@ export default defineComponent({
|
||||||
ref={treeRef}
|
ref={treeRef}
|
||||||
focusable={false}
|
focusable={false}
|
||||||
prefixCls={`${prefixCls}-tree`}
|
prefixCls={`${prefixCls}-tree`}
|
||||||
treeData={memoTreeData.value as TreeDataNode[]}
|
treeData={memoOptions.value as TreeDataNode[]}
|
||||||
height={listHeight}
|
height={height}
|
||||||
itemHeight={listItemHeight}
|
itemHeight={itemHeight}
|
||||||
virtual={virtual}
|
virtual={virtual}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
icon={treeIcon}
|
icon={treeIcon}
|
||||||
|
@ -235,7 +243,7 @@ export default defineComponent({
|
||||||
checkable={checkable}
|
checkable={checkable}
|
||||||
checkStrictly
|
checkStrictly
|
||||||
checkedKeys={mergedCheckedKeys.value}
|
checkedKeys={mergedCheckedKeys.value}
|
||||||
selectedKeys={!checkable ? checkedKeys : []}
|
selectedKeys={!checkable ? valueKeys.value : []}
|
||||||
defaultExpandAll={treeDefaultExpandAll}
|
defaultExpandAll={treeDefaultExpandAll}
|
||||||
{...treeProps}
|
{...treeProps}
|
||||||
// Proxy event out
|
// Proxy event out
|
||||||
|
@ -245,7 +253,7 @@ export default defineComponent({
|
||||||
onExpand={onInternalExpand}
|
onExpand={onInternalExpand}
|
||||||
onLoad={onTreeLoad}
|
onLoad={onTreeLoad}
|
||||||
filterTreeNode={filterTreeNode}
|
filterTreeNode={filterTreeNode}
|
||||||
v-slots={{ ...slots, checkable: legacyContext.customCheckable }}
|
v-slots={{ ...slots, checkable: context.value.customCheckable }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -0,0 +1,6 @@
|
||||||
|
import generate from './generate';
|
||||||
|
import OptionList from './OptionList';
|
||||||
|
|
||||||
|
const TreeSelect = generate({ prefixCls: 'vc-tree-select', optionList: OptionList as any });
|
||||||
|
|
||||||
|
export default TreeSelect;
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { warning } from '../../vc-util/warning';
|
||||||
|
import type { ComputedRef, Ref } from 'vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import type {
|
||||||
|
DataNode,
|
||||||
|
InternalDataEntity,
|
||||||
|
SimpleModeConfig,
|
||||||
|
RawValueType,
|
||||||
|
FieldNames,
|
||||||
|
} from '../interface';
|
||||||
|
import { convertChildrenToData } from '../utils/legacyUtil';
|
||||||
|
|
||||||
|
const MAX_WARNING_TIMES = 10;
|
||||||
|
|
||||||
|
function parseSimpleTreeData(
|
||||||
|
treeData: DataNode[],
|
||||||
|
{ id, pId, rootPId }: SimpleModeConfig,
|
||||||
|
): DataNode[] {
|
||||||
|
const keyNodes = {};
|
||||||
|
const rootNodeList = [];
|
||||||
|
|
||||||
|
// Fill in the map
|
||||||
|
const nodeList = treeData.map(node => {
|
||||||
|
const clone = { ...node };
|
||||||
|
const key = clone[id];
|
||||||
|
keyNodes[key] = clone;
|
||||||
|
clone.key = clone.key || key;
|
||||||
|
return clone;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect tree
|
||||||
|
nodeList.forEach(node => {
|
||||||
|
const parentKey = node[pId];
|
||||||
|
const parent = keyNodes[parentKey];
|
||||||
|
|
||||||
|
// Fill parent
|
||||||
|
if (parent) {
|
||||||
|
parent.children = parent.children || [];
|
||||||
|
parent.children.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill root tree node
|
||||||
|
if (parentKey === rootPId || (!parent && rootPId === null)) {
|
||||||
|
rootNodeList.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return rootNodeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format `treeData` with `value` & `key` which is used for calculation
|
||||||
|
*/
|
||||||
|
function formatTreeData(
|
||||||
|
treeData: DataNode[],
|
||||||
|
getLabelProp: (node: DataNode) => any,
|
||||||
|
fieldNames: FieldNames,
|
||||||
|
): InternalDataEntity[] {
|
||||||
|
let warningTimes = 0;
|
||||||
|
const valueSet = new Set<RawValueType>();
|
||||||
|
|
||||||
|
// Field names
|
||||||
|
const { value: fieldValue, children: fieldChildren } = fieldNames;
|
||||||
|
|
||||||
|
function dig(dataNodes: DataNode[]) {
|
||||||
|
return (dataNodes || []).map(node => {
|
||||||
|
const { key, disableCheckbox, disabled, checkable, selectable, isLeaf } = node;
|
||||||
|
|
||||||
|
const value = node[fieldValue];
|
||||||
|
const mergedValue = fieldValue in node ? value : key;
|
||||||
|
|
||||||
|
const dataNode: InternalDataEntity = {
|
||||||
|
disableCheckbox,
|
||||||
|
disabled,
|
||||||
|
key: key !== null && key !== undefined ? key : mergedValue,
|
||||||
|
value: mergedValue,
|
||||||
|
title: getLabelProp(node),
|
||||||
|
node,
|
||||||
|
selectable,
|
||||||
|
isLeaf,
|
||||||
|
dataRef: node,
|
||||||
|
checkable,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node.slots) {
|
||||||
|
dataNode.slots = node.slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check `key` & `value` and warning user
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (
|
||||||
|
key !== null &&
|
||||||
|
key !== undefined &&
|
||||||
|
value !== undefined &&
|
||||||
|
String(key) !== String(value) &&
|
||||||
|
warningTimes < MAX_WARNING_TIMES
|
||||||
|
) {
|
||||||
|
warningTimes += 1;
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
`\`key\` or \`value\` with TreeNode must be the same or you can remove one of them. key: ${key}, value: ${value}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(!valueSet.has(value), `Same \`value\` exist in the tree: ${value}`);
|
||||||
|
valueSet.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldChildren in node) {
|
||||||
|
dataNode.children = dig(node[fieldChildren]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataNode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dig(treeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert `treeData` or `children` into formatted `treeData`.
|
||||||
|
* Will not re-calculate if `treeData` or `children` not change.
|
||||||
|
*/
|
||||||
|
export default function useTreeData(
|
||||||
|
treeData: Ref<DataNode[]>,
|
||||||
|
children: Ref<any[]>,
|
||||||
|
{
|
||||||
|
getLabelProp,
|
||||||
|
simpleMode,
|
||||||
|
fieldNames,
|
||||||
|
}: {
|
||||||
|
getLabelProp: (node: DataNode) => any;
|
||||||
|
simpleMode: Ref<boolean | SimpleModeConfig>;
|
||||||
|
fieldNames: Ref<FieldNames>;
|
||||||
|
},
|
||||||
|
): ComputedRef<InternalDataEntity[]> {
|
||||||
|
return computed(() => {
|
||||||
|
if (treeData.value) {
|
||||||
|
return formatTreeData(
|
||||||
|
simpleMode.value
|
||||||
|
? parseSimpleTreeData(treeData.value, {
|
||||||
|
id: 'id',
|
||||||
|
pId: 'pId',
|
||||||
|
rootPId: null,
|
||||||
|
...(simpleMode.value !== true ? simpleMode.value : {}),
|
||||||
|
})
|
||||||
|
: treeData.value,
|
||||||
|
getLabelProp,
|
||||||
|
fieldNames.value,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return formatTreeData(convertChildrenToData(children.value), getLabelProp, fieldNames.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// base rc-tree-select@5.0.0-alpha.4
|
// base rc-tree-select@4.6.1
|
||||||
import TreeSelect from './TreeSelect';
|
import TreeSelect from './TreeSelect';
|
||||||
import TreeNode from './TreeNode';
|
import TreeNode from './TreeNode';
|
||||||
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
|
import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
|
|
@ -1,15 +1,21 @@
|
||||||
import { filterEmpty } from '../../_util/props-util';
|
import { filterEmpty } from '../../_util/props-util';
|
||||||
import { camelize } from 'vue';
|
import { camelize } from 'vue';
|
||||||
import { warning } from '../../vc-util/warning';
|
import { warning } from '../../vc-util/warning';
|
||||||
import type { DataNode, ChangeEventExtra, RawValueType, LegacyCheckedNode } from '../interface';
|
import type {
|
||||||
|
DataNode,
|
||||||
|
LegacyDataNode,
|
||||||
|
ChangeEventExtra,
|
||||||
|
InternalDataEntity,
|
||||||
|
RawValueType,
|
||||||
|
LegacyCheckedNode,
|
||||||
|
} from '../interface';
|
||||||
import TreeNode from '../TreeNode';
|
import TreeNode from '../TreeNode';
|
||||||
import type { VueNode } from '../../_util/type';
|
import type { VueNode } from '../../_util/type';
|
||||||
import type { DefaultOptionType, FieldNames } from '../TreeSelect';
|
|
||||||
|
|
||||||
function isTreeSelectNode(node: any) {
|
function isTreeSelectNode(node: any) {
|
||||||
return node && node.type && (node.type as any).isTreeSelectNode;
|
return node && node.type && (node.type as any).isTreeSelectNode;
|
||||||
}
|
}
|
||||||
export function convertChildrenToData(rootNodes: VueNode[]): DataNode[] {
|
export function convertChildrenToData(rootNodes: VueNode): DataNode[] {
|
||||||
function dig(treeNodes: any[] = []): DataNode[] {
|
function dig(treeNodes: any[] = []): DataNode[] {
|
||||||
return filterEmpty(treeNodes).map(treeNode => {
|
return filterEmpty(treeNodes).map(treeNode => {
|
||||||
// Filter invalidate node
|
// Filter invalidate node
|
||||||
|
@ -60,10 +66,10 @@ export function convertChildrenToData(rootNodes: VueNode[]): DataNode[] {
|
||||||
return dig(rootNodes as any[]);
|
return dig(rootNodes as any[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fillLegacyProps(dataNode: DataNode): any {
|
export function fillLegacyProps(dataNode: DataNode): LegacyDataNode {
|
||||||
// Skip if not dataNode exist
|
// Skip if not dataNode exist
|
||||||
if (!dataNode) {
|
if (!dataNode) {
|
||||||
return dataNode;
|
return dataNode as LegacyDataNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloneNode = { ...dataNode };
|
const cloneNode = { ...dataNode };
|
||||||
|
@ -73,43 +79,37 @@ export function fillLegacyProps(dataNode: DataNode): any {
|
||||||
get() {
|
get() {
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'New `vc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.',
|
'New `rc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.',
|
||||||
);
|
);
|
||||||
return cloneNode;
|
return cloneNode;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloneNode;
|
return cloneNode as LegacyDataNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fillAdditionalInfo(
|
export function fillAdditionalInfo(
|
||||||
extra: ChangeEventExtra,
|
extra: ChangeEventExtra,
|
||||||
triggerValue: RawValueType,
|
triggerValue: RawValueType,
|
||||||
checkedValues: RawValueType[],
|
checkedValues: RawValueType[],
|
||||||
treeData: DefaultOptionType[],
|
treeData: InternalDataEntity[],
|
||||||
showPosition: boolean,
|
showPosition: boolean,
|
||||||
fieldNames: FieldNames,
|
|
||||||
) {
|
) {
|
||||||
let triggerNode = null;
|
let triggerNode = null;
|
||||||
let nodeList: LegacyCheckedNode[] = null;
|
let nodeList: LegacyCheckedNode[] = null;
|
||||||
|
|
||||||
function generateMap() {
|
function generateMap() {
|
||||||
function dig(list: DefaultOptionType[], level = '0', parentIncluded = false) {
|
function dig(list: InternalDataEntity[], level = '0', parentIncluded = false) {
|
||||||
return list
|
return list
|
||||||
.map((option, index) => {
|
.map((dataNode, index) => {
|
||||||
const pos = `${level}-${index}`;
|
const pos = `${level}-${index}`;
|
||||||
const value = option[fieldNames.value];
|
const included = checkedValues.includes(dataNode.value);
|
||||||
const included = checkedValues.includes(value);
|
const children = dig(dataNode.children || [], pos, included);
|
||||||
const children = dig(option[fieldNames.children] || [], pos, included);
|
const node = <TreeNode {...dataNode}>{children.map(child => child.node)}</TreeNode>;
|
||||||
const node = (
|
|
||||||
<TreeNode {...(option as Required<DefaultOptionType>)}>
|
|
||||||
{children.map(child => child.node)}
|
|
||||||
</TreeNode>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Link with trigger node
|
// Link with trigger node
|
||||||
if (triggerValue === value) {
|
if (triggerValue === dataNode.value) {
|
||||||
triggerNode = node;
|
triggerNode = node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type { DataEntity } from '../../vc-tree/interface';
|
import type { DataEntity } from '../../vc-tree/interface';
|
||||||
import type { InternalFieldName } from '../TreeSelect';
|
import type { RawValueType, Key, DataNode } from '../interface';
|
||||||
import type { RawValueType, Key } from '../interface';
|
|
||||||
import { isCheckDisabled } from './valueUtil';
|
import { isCheckDisabled } from './valueUtil';
|
||||||
|
|
||||||
export const SHOW_ALL = 'SHOW_ALL';
|
export const SHOW_ALL = 'SHOW_ALL';
|
||||||
|
@ -9,23 +8,22 @@ export const SHOW_CHILD = 'SHOW_CHILD';
|
||||||
|
|
||||||
export type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD;
|
export type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD;
|
||||||
|
|
||||||
export function formatStrategyValues(
|
export function formatStrategyKeys(
|
||||||
values: Key[],
|
keys: Key[],
|
||||||
strategy: CheckedStrategy,
|
strategy: CheckedStrategy,
|
||||||
keyEntities: Record<Key, DataEntity>,
|
keyEntities: Record<Key, DataEntity>,
|
||||||
fieldNames: InternalFieldName,
|
|
||||||
): RawValueType[] {
|
): RawValueType[] {
|
||||||
const valueSet = new Set(values);
|
const keySet = new Set(keys);
|
||||||
|
|
||||||
if (strategy === SHOW_CHILD) {
|
if (strategy === SHOW_CHILD) {
|
||||||
return values.filter(key => {
|
return keys.filter(key => {
|
||||||
const entity = keyEntities[key];
|
const entity = keyEntities[key];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
entity &&
|
entity &&
|
||||||
entity.children &&
|
entity.children &&
|
||||||
entity.children.every(
|
entity.children.every(
|
||||||
({ node }) => isCheckDisabled(node) || valueSet.has(node[fieldNames.value]),
|
({ node }) => isCheckDisabled(node) || keySet.has((node as DataNode).key),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -34,15 +32,15 @@ export function formatStrategyValues(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (strategy === SHOW_PARENT) {
|
if (strategy === SHOW_PARENT) {
|
||||||
return values.filter(key => {
|
return keys.filter(key => {
|
||||||
const entity = keyEntities[key];
|
const entity = keyEntities[key];
|
||||||
const parent = entity ? entity.parent : null;
|
const parent = entity ? entity.parent : null;
|
||||||
|
|
||||||
if (parent && !isCheckDisabled(parent.node) && valueSet.has(parent.node.key)) {
|
if (parent && !isCheckDisabled(parent.node) && keySet.has((parent.node as DataNode).key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return values;
|
return keys;
|
||||||
}
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
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<FlattenDataNode, 'level'>;
|
||||||
|
|
||||||
|
export function toArray<T>(value: T | T[]): T[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
const { label, value, children } = fieldNames || {};
|
||||||
|
|
||||||
|
const filledNames: FieldNames = {
|
||||||
|
value: value || 'value',
|
||||||
|
children: children || 'children',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!skipTitle || label) {
|
||||||
|
filledNames.label = label || 'label';
|
||||||
|
}
|
||||||
|
|
||||||
|
return filledNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findValueOption(values: RawValueType[], options: CompatibleDataNode[]): DataNode[] {
|
||||||
|
const optionMap: Map<RawValueType, DataNode> = 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[];
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (children) {
|
||||||
|
clone.children = fillKey(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const flattenList = flattenTreeData(fillKey(typedOptions), true, null);
|
||||||
|
|
||||||
|
const cacheMap = new Map<Key, FlattenDataNode>();
|
||||||
|
const flattenDateNodeList: (FlattenDataNode & { parentKey?: Key })[] = flattenList.map(option => {
|
||||||
|
const { data, key, value } = option as any as Omit<FlattenNode, 'data'> & {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LegacyDataNode>;
|
||||||
|
},
|
||||||
|
): DataNode[] {
|
||||||
|
if (filterOption === false) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
let filterOptionFunc: FilterFunc<LegacyDataNode>;
|
||||||
|
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<RawValueType, LabelValueType>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
import { warning } from '../../vc-util/warning';
|
import { warning } from '../../vc-util/warning';
|
||||||
import type { TreeSelectProps } from '../TreeSelect';
|
|
||||||
import { toArray } from './valueUtil';
|
import { toArray } from './valueUtil';
|
||||||
|
|
||||||
function warningProps(props: TreeSelectProps & { searchPlaceholder?: string }) {
|
function warningProps(props: any) {
|
||||||
const { searchPlaceholder, treeCheckStrictly, treeCheckable, labelInValue, value, multiple } =
|
const { searchPlaceholder, treeCheckStrictly, treeCheckable, labelInValue, value, multiple } =
|
||||||
props;
|
props;
|
||||||
|
|
|
@ -1,210 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
||||||
export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void;
|
|
||||||
|
|
||||||
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<FieldNames, 'label'> {
|
|
||||||
_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<ValueType> },
|
|
||||||
defaultValue: { type: [String, Number, Object, Array] as PropType<ValueType> },
|
|
||||||
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<SelectProps['onSelect']>,
|
|
||||||
onDeselect: Function as PropType<SelectProps['onDeselect']>,
|
|
||||||
|
|
||||||
showCheckedStrategy: { type: String as PropType<CheckedStrategy> },
|
|
||||||
treeNodeLabelProp: String,
|
|
||||||
|
|
||||||
fieldNames: { type: Object as PropType<FieldNames> },
|
|
||||||
|
|
||||||
// >>> 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<OptionType[]> },
|
|
||||||
treeDataSimpleMode: {
|
|
||||||
type: [Boolean, Object] as PropType<boolean | SimpleModeConfig>,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
treeLoadedKeys: { type: Array as PropType<Key[]> },
|
|
||||||
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<unknown>> },
|
|
||||||
|
|
||||||
// treeExpandedKeys: { type: Array as PropType<Key[]> },
|
|
||||||
// treeDefaultExpandedKeys: { type: Array as PropType<Key[]> },
|
|
||||||
|
|
||||||
// treeDefaultExpandAll: { type: Boolean, default: undefined },
|
|
||||||
|
|
||||||
// children: Array,
|
|
||||||
|
|
||||||
// dropdownPopupAlign: PropTypes.any,
|
|
||||||
|
|
||||||
// // Event
|
|
||||||
|
|
||||||
// onTreeExpand: { type: Function as PropType<(expandedKeys: Key[]) => void> },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TreeSelectProps = Partial<ExtractPropTypes<ReturnType<typeof treeSelectProps>>>;
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,514 +0,0 @@
|
||||||
import type { GenerateConfig } from '../vc-select/generate';
|
|
||||||
import generateSelector from '../vc-select/generate';
|
|
||||||
import TreeNode from './TreeNode';
|
|
||||||
import type {
|
|
||||||
DefaultValueType,
|
|
||||||
DataNode,
|
|
||||||
LabelValueType,
|
|
||||||
RawValueType,
|
|
||||||
ChangeEventExtra,
|
|
||||||
SelectSource,
|
|
||||||
FlattenDataNode,
|
|
||||||
} from './interface';
|
|
||||||
import {
|
|
||||||
flattenOptions,
|
|
||||||
filterOptions,
|
|
||||||
isValueDisabled,
|
|
||||||
findValueOption,
|
|
||||||
addValue,
|
|
||||||
removeValue,
|
|
||||||
getRawValueLabeled,
|
|
||||||
toArray,
|
|
||||||
fillFieldNames,
|
|
||||||
} from './utils/valueUtil';
|
|
||||||
import warningProps from './utils/warningPropsUtil';
|
|
||||||
import { SelectContext } from './Context';
|
|
||||||
import useTreeData from './hooks/useTreeData';
|
|
||||||
import useKeyValueMap from './hooks/useKeyValueMap';
|
|
||||||
import useKeyValueMapping from './hooks/useKeyValueMapping';
|
|
||||||
import { formatStrategyKeys, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './utils/strategyUtil';
|
|
||||||
import { fillAdditionalInfo } from './utils/legacyUtil';
|
|
||||||
import useSelectValues from './hooks/useSelectValues';
|
|
||||||
import type { TreeSelectProps } from './props';
|
|
||||||
import { treeSelectProps } from './props';
|
|
||||||
import { getLabeledValue } from '../vc-select/utils/valueUtil';
|
|
||||||
import omit from '../_util/omit';
|
|
||||||
import { computed, defineComponent, ref, shallowRef, toRef, watch, watchEffect } from 'vue';
|
|
||||||
import { convertDataToEntities } from '../vc-tree/utils/treeUtil';
|
|
||||||
import { conductCheck } from '../vc-tree/utils/conductUtil';
|
|
||||||
import { warning } from '../vc-util/warning';
|
|
||||||
import { INTERNAL_PROPS_MARK } from '../vc-select/interface/generator';
|
|
||||||
|
|
||||||
const OMIT_PROPS: (keyof TreeSelectProps)[] = [
|
|
||||||
'expandedKeys' as any,
|
|
||||||
'treeData',
|
|
||||||
'treeCheckable',
|
|
||||||
'showCheckedStrategy',
|
|
||||||
'searchPlaceholder',
|
|
||||||
'treeLine',
|
|
||||||
'treeIcon',
|
|
||||||
'showTreeIcon',
|
|
||||||
'switcherIcon',
|
|
||||||
'treeNodeFilterProp',
|
|
||||||
'filterTreeNode',
|
|
||||||
'dropdownPopupAlign',
|
|
||||||
'treeDefaultExpandAll',
|
|
||||||
'treeCheckStrictly',
|
|
||||||
'treeExpandedKeys',
|
|
||||||
'treeLoadedKeys',
|
|
||||||
'treeMotion',
|
|
||||||
'onTreeExpand',
|
|
||||||
'onTreeLoad',
|
|
||||||
'labelRender',
|
|
||||||
'loadData',
|
|
||||||
'treeDataSimpleMode',
|
|
||||||
'treeNodeLabelProp',
|
|
||||||
'treeDefaultExpandedKeys',
|
|
||||||
'bordered',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function generate(config: {
|
|
||||||
prefixCls: string;
|
|
||||||
optionList: GenerateConfig<DataNode>['components']['optionList'];
|
|
||||||
}) {
|
|
||||||
const { prefixCls, optionList } = config;
|
|
||||||
|
|
||||||
const RefSelect = generateSelector<DataNode>({
|
|
||||||
prefixCls,
|
|
||||||
components: {
|
|
||||||
optionList,
|
|
||||||
},
|
|
||||||
// Not use generate since we will handle ourself
|
|
||||||
convertChildrenToData: () => null,
|
|
||||||
flattenOptions,
|
|
||||||
// Handle `optionLabelProp` in TreeSelect component
|
|
||||||
getLabeledValue: getLabeledValue as any,
|
|
||||||
filterOptions,
|
|
||||||
isValueDisabled,
|
|
||||||
findValueOption,
|
|
||||||
omitDOMProps: (props: TreeSelectProps<any>) => omit(props, OMIT_PROPS),
|
|
||||||
});
|
|
||||||
|
|
||||||
return defineComponent({
|
|
||||||
name: 'TreeSelect',
|
|
||||||
props: treeSelectProps(),
|
|
||||||
slots: [
|
|
||||||
'title',
|
|
||||||
'placeholder',
|
|
||||||
'maxTagPlaceholder',
|
|
||||||
'treeIcon',
|
|
||||||
'switcherIcon',
|
|
||||||
'notFoundContent',
|
|
||||||
'treeCheckable',
|
|
||||||
],
|
|
||||||
TreeNode,
|
|
||||||
SHOW_ALL,
|
|
||||||
SHOW_PARENT,
|
|
||||||
SHOW_CHILD,
|
|
||||||
setup(props, { expose, slots, attrs }) {
|
|
||||||
const mergedCheckable = computed(() => props.treeCheckable || props.treeCheckStrictly);
|
|
||||||
const mergedMultiple = computed(() => props.multiple || mergedCheckable.value);
|
|
||||||
const treeConduction = computed(() => props.treeCheckable && !props.treeCheckStrictly);
|
|
||||||
const mergedLabelInValue = computed(() => props.treeCheckStrictly || props.labelInValue);
|
|
||||||
|
|
||||||
// ======================= Tree Data =======================
|
|
||||||
// FieldNames
|
|
||||||
const mergedFieldNames = computed(() => fillFieldNames(props.fieldNames, true));
|
|
||||||
// Legacy both support `label` or `title` if not set.
|
|
||||||
// We have to fallback to function to handle this
|
|
||||||
const getTreeNodeTitle = (node: DataNode) => {
|
|
||||||
if (!props.treeData) {
|
|
||||||
return node.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedFieldNames.value?.label) {
|
|
||||||
return node[mergedFieldNames.value.label];
|
|
||||||
}
|
|
||||||
|
|
||||||
return node.label || node.title;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTreeNodeLabelProp = (entity: FlattenDataNode) => {
|
|
||||||
const { labelRender, treeNodeLabelProp } = props;
|
|
||||||
const { node } = entity.data;
|
|
||||||
|
|
||||||
if (labelRender) {
|
|
||||||
return labelRender(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (treeNodeLabelProp) {
|
|
||||||
return node[treeNodeLabelProp];
|
|
||||||
}
|
|
||||||
|
|
||||||
return getTreeNodeTitle(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mergedTreeData = useTreeData(toRef(props, 'treeData'), toRef(props, 'children'), {
|
|
||||||
getLabelProp: getTreeNodeTitle,
|
|
||||||
simpleMode: toRef(props, 'treeDataSimpleMode'),
|
|
||||||
fieldNames: mergedFieldNames,
|
|
||||||
});
|
|
||||||
|
|
||||||
const flattedOptions = computed(() => flattenOptions(mergedTreeData.value));
|
|
||||||
const [cacheKeyMap, cacheValueMap] = useKeyValueMap(flattedOptions);
|
|
||||||
const [getEntityByKey, getEntityByValue] = useKeyValueMapping(cacheKeyMap, cacheValueMap);
|
|
||||||
|
|
||||||
// Only generate keyEntities for check conduction when is `treeCheckable`
|
|
||||||
const conductKeyEntities = computed(() => {
|
|
||||||
if (treeConduction.value) {
|
|
||||||
return convertDataToEntities(mergedTreeData.value).keyEntities;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ========================== Ref ==========================
|
|
||||||
const selectRef = ref();
|
|
||||||
|
|
||||||
expose({
|
|
||||||
scrollTo: (...args: any[]) => selectRef.value.scrollTo?.(...args),
|
|
||||||
focus: () => selectRef.value.focus?.(),
|
|
||||||
blur: () => selectRef.value?.blur(),
|
|
||||||
|
|
||||||
/** @private Internal usage. It's save to remove if `rc-cascader` not use it any longer */
|
|
||||||
getEntityByValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
const valueRef = ref<DefaultValueType>(
|
|
||||||
props.value === undefined ? props.defaultValue : props.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
() => {
|
|
||||||
valueRef.value = props.value;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Get `missingRawValues` which not exist in the tree yet */
|
|
||||||
const splitRawValues = (newRawValues: RawValueType[]) => {
|
|
||||||
const missingRawValues = [];
|
|
||||||
const existRawValues = [];
|
|
||||||
|
|
||||||
// Keep missing value in the cache
|
|
||||||
newRawValues.forEach(val => {
|
|
||||||
if (getEntityByValue(val)) {
|
|
||||||
existRawValues.push(val);
|
|
||||||
} else {
|
|
||||||
missingRawValues.push(val);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { missingRawValues, existRawValues };
|
|
||||||
};
|
|
||||||
|
|
||||||
const rawValues = shallowRef<RawValueType[]>([]);
|
|
||||||
const rawHalfCheckedKeys = shallowRef<RawValueType[]>([]);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
const valueHalfCheckedKeys: RawValueType[] = [];
|
|
||||||
const newRawValues: RawValueType[] = [];
|
|
||||||
|
|
||||||
toArray(valueRef.value).forEach(item => {
|
|
||||||
if (item && typeof item === 'object' && 'value' in item) {
|
|
||||||
if (item.halfChecked && props.treeCheckStrictly) {
|
|
||||||
const entity = getEntityByValue(item.value);
|
|
||||||
valueHalfCheckedKeys.push(entity ? entity.key : item.value);
|
|
||||||
} else {
|
|
||||||
newRawValues.push(item.value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newRawValues.push(item as RawValueType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// We need do conduction of values
|
|
||||||
if (treeConduction.value) {
|
|
||||||
const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
|
|
||||||
const keyList = existRawValues.map(val => getEntityByValue(val).key);
|
|
||||||
|
|
||||||
const { checkedKeys, halfCheckedKeys } = conductCheck(
|
|
||||||
keyList,
|
|
||||||
true,
|
|
||||||
conductKeyEntities.value,
|
|
||||||
);
|
|
||||||
rawValues.value = [
|
|
||||||
...missingRawValues,
|
|
||||||
...checkedKeys.map(key => getEntityByKey(key).data.value),
|
|
||||||
];
|
|
||||||
rawHalfCheckedKeys.value = halfCheckedKeys;
|
|
||||||
} else {
|
|
||||||
[rawValues.value, rawHalfCheckedKeys.value] = [newRawValues, valueHalfCheckedKeys];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectValues = useSelectValues(rawValues, {
|
|
||||||
treeConduction,
|
|
||||||
value: valueRef,
|
|
||||||
showCheckedStrategy: toRef(props, 'showCheckedStrategy'),
|
|
||||||
conductKeyEntities,
|
|
||||||
getEntityByValue,
|
|
||||||
getEntityByKey,
|
|
||||||
getLabelProp: getTreeNodeLabelProp,
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerChange = (
|
|
||||||
newRawValues: RawValueType[],
|
|
||||||
extra: { triggerValue: RawValueType; selected: boolean },
|
|
||||||
source: SelectSource,
|
|
||||||
) => {
|
|
||||||
const { onChange, showCheckedStrategy, treeCheckStrictly } = props;
|
|
||||||
const preValue = valueRef.value;
|
|
||||||
valueRef.value = mergedMultiple.value ? newRawValues : newRawValues[0];
|
|
||||||
if (onChange) {
|
|
||||||
let eventValues: RawValueType[] = newRawValues;
|
|
||||||
if (treeConduction.value && showCheckedStrategy !== 'SHOW_ALL') {
|
|
||||||
const keyList = newRawValues.map(val => {
|
|
||||||
const entity = getEntityByValue(val);
|
|
||||||
return entity ? entity.key : val;
|
|
||||||
});
|
|
||||||
const formattedKeyList = formatStrategyKeys(
|
|
||||||
keyList,
|
|
||||||
showCheckedStrategy,
|
|
||||||
conductKeyEntities.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
eventValues = formattedKeyList.map(key => {
|
|
||||||
const entity = getEntityByKey(key);
|
|
||||||
return entity ? entity.data.value : key;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { triggerValue, selected } = extra || {
|
|
||||||
triggerValue: undefined,
|
|
||||||
selected: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
let returnValues = mergedLabelInValue.value
|
|
||||||
? getRawValueLabeled(eventValues, preValue, getEntityByValue, getTreeNodeLabelProp)
|
|
||||||
: eventValues;
|
|
||||||
|
|
||||||
// We need fill half check back
|
|
||||||
if (treeCheckStrictly) {
|
|
||||||
const halfValues = rawHalfCheckedKeys.value
|
|
||||||
.map(key => {
|
|
||||||
const entity = getEntityByKey(key);
|
|
||||||
return entity ? entity.data.value : key;
|
|
||||||
})
|
|
||||||
.filter(val => !eventValues.includes(val));
|
|
||||||
|
|
||||||
returnValues = [
|
|
||||||
...(returnValues as LabelValueType[]),
|
|
||||||
...getRawValueLabeled(halfValues, preValue, getEntityByValue, getTreeNodeLabelProp),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalInfo = {
|
|
||||||
// [Legacy] Always return as array contains label & value
|
|
||||||
preValue: selectValues.value,
|
|
||||||
triggerValue,
|
|
||||||
} as ChangeEventExtra;
|
|
||||||
|
|
||||||
// [Legacy] Fill legacy data if user query.
|
|
||||||
// This is expansive that we only fill when user query
|
|
||||||
// https://github.com/react-component/tree-select/blob/fe33eb7c27830c9ac70cd1fdb1ebbe7bc679c16a/src/Select.jsx
|
|
||||||
let showPosition = true;
|
|
||||||
if (treeCheckStrictly || (source === 'selection' && !selected)) {
|
|
||||||
showPosition = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fillAdditionalInfo(
|
|
||||||
additionalInfo,
|
|
||||||
triggerValue,
|
|
||||||
newRawValues,
|
|
||||||
mergedTreeData.value,
|
|
||||||
showPosition,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mergedCheckable.value) {
|
|
||||||
additionalInfo.checked = selected;
|
|
||||||
} else {
|
|
||||||
additionalInfo.selected = selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(
|
|
||||||
mergedMultiple.value ? returnValues : returnValues[0],
|
|
||||||
mergedLabelInValue.value
|
|
||||||
? null
|
|
||||||
: eventValues.map(val => {
|
|
||||||
const entity = getEntityByValue(val);
|
|
||||||
return entity ? entity.data.title : null;
|
|
||||||
}),
|
|
||||||
additionalInfo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInternalSelect = (
|
|
||||||
selectValue: RawValueType,
|
|
||||||
option: DataNode,
|
|
||||||
source: SelectSource,
|
|
||||||
) => {
|
|
||||||
const eventValue = mergedLabelInValue.value ? selectValue : selectValue;
|
|
||||||
|
|
||||||
if (!mergedMultiple.value) {
|
|
||||||
// Single mode always set value
|
|
||||||
triggerChange([selectValue], { selected: true, triggerValue: selectValue }, source);
|
|
||||||
} else {
|
|
||||||
let newRawValues = addValue(rawValues.value, selectValue);
|
|
||||||
|
|
||||||
// Add keys if tree conduction
|
|
||||||
if (treeConduction.value) {
|
|
||||||
// Should keep missing values
|
|
||||||
const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
|
|
||||||
const keyList = existRawValues.map(val => getEntityByValue(val).key);
|
|
||||||
const { checkedKeys } = conductCheck(keyList, true, conductKeyEntities.value);
|
|
||||||
newRawValues = [
|
|
||||||
...missingRawValues,
|
|
||||||
...checkedKeys.map(key => getEntityByKey(key).data.value),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerChange(newRawValues, { selected: true, triggerValue: selectValue }, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
props.onSelect?.(eventValue, option);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInternalDeselect = (
|
|
||||||
selectValue: RawValueType,
|
|
||||||
option: DataNode,
|
|
||||||
source: SelectSource,
|
|
||||||
) => {
|
|
||||||
const eventValue = selectValue;
|
|
||||||
|
|
||||||
let newRawValues = removeValue(rawValues.value, selectValue);
|
|
||||||
|
|
||||||
// Remove keys if tree conduction
|
|
||||||
if (treeConduction.value) {
|
|
||||||
const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
|
|
||||||
const keyList = existRawValues.map(val => getEntityByValue(val).key);
|
|
||||||
const { checkedKeys } = conductCheck(
|
|
||||||
keyList,
|
|
||||||
{ checked: false, halfCheckedKeys: rawHalfCheckedKeys.value },
|
|
||||||
conductKeyEntities.value,
|
|
||||||
);
|
|
||||||
newRawValues = [
|
|
||||||
...missingRawValues,
|
|
||||||
...checkedKeys.map(key => getEntityByKey(key).data.value),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerChange(newRawValues, { selected: false, triggerValue: selectValue }, source);
|
|
||||||
|
|
||||||
props.onDeselect?.(eventValue, option);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInternalClear = () => {
|
|
||||||
triggerChange([], null, 'clear');
|
|
||||||
};
|
|
||||||
|
|
||||||
// ========================= Open ==========================
|
|
||||||
const onInternalDropdownVisibleChange = (open: boolean) => {
|
|
||||||
if (props.onDropdownVisibleChange) {
|
|
||||||
const legacyParam = {};
|
|
||||||
|
|
||||||
Object.defineProperty(legacyParam, 'documentClickClose', {
|
|
||||||
get() {
|
|
||||||
warning(false, 'Second param of `onDropdownVisibleChange` has been removed.');
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
(props.onDropdownVisibleChange as any)(open, legacyParam);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ======================== Warning ========================
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
warningProps(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const {
|
|
||||||
treeNodeFilterProp,
|
|
||||||
dropdownPopupAlign,
|
|
||||||
filterTreeNode,
|
|
||||||
treeDefaultExpandAll,
|
|
||||||
treeExpandedKeys,
|
|
||||||
treeDefaultExpandedKeys,
|
|
||||||
onTreeExpand,
|
|
||||||
treeIcon,
|
|
||||||
treeMotion,
|
|
||||||
showTreeIcon,
|
|
||||||
switcherIcon,
|
|
||||||
treeLine,
|
|
||||||
loadData,
|
|
||||||
treeLoadedKeys,
|
|
||||||
onTreeLoad,
|
|
||||||
} = props;
|
|
||||||
// ======================== Render =========================
|
|
||||||
// We pass some props into select props style
|
|
||||||
const selectProps = {
|
|
||||||
optionLabelProp: null,
|
|
||||||
optionFilterProp: treeNodeFilterProp,
|
|
||||||
dropdownAlign: dropdownPopupAlign,
|
|
||||||
internalProps: {
|
|
||||||
mark: INTERNAL_PROPS_MARK,
|
|
||||||
onClear: onInternalClear,
|
|
||||||
skipTriggerChange: true,
|
|
||||||
skipTriggerSelect: true,
|
|
||||||
onRawSelect: onInternalSelect,
|
|
||||||
onRawDeselect: onInternalDeselect,
|
|
||||||
},
|
|
||||||
filterOption: filterTreeNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (props.filterTreeNode === undefined) {
|
|
||||||
delete selectProps.filterOption;
|
|
||||||
}
|
|
||||||
const selectContext = {
|
|
||||||
checkable: mergedCheckable.value,
|
|
||||||
loadData,
|
|
||||||
treeLoadedKeys,
|
|
||||||
onTreeLoad,
|
|
||||||
checkedKeys: rawValues.value,
|
|
||||||
halfCheckedKeys: rawHalfCheckedKeys.value,
|
|
||||||
treeDefaultExpandAll,
|
|
||||||
treeExpandedKeys,
|
|
||||||
treeDefaultExpandedKeys,
|
|
||||||
onTreeExpand,
|
|
||||||
treeIcon,
|
|
||||||
treeMotion,
|
|
||||||
showTreeIcon,
|
|
||||||
switcherIcon,
|
|
||||||
treeLine,
|
|
||||||
treeNodeFilterProp,
|
|
||||||
getEntityByKey,
|
|
||||||
getEntityByValue,
|
|
||||||
customCheckable: slots.treeCheckable,
|
|
||||||
slots,
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<SelectContext value={selectContext}>
|
|
||||||
<RefSelect
|
|
||||||
{...attrs}
|
|
||||||
ref={selectRef}
|
|
||||||
mode={mergedMultiple.value ? 'multiple' : null}
|
|
||||||
{...props}
|
|
||||||
{...selectProps}
|
|
||||||
value={selectValues.value}
|
|
||||||
// We will handle this ourself since we need calculate conduction
|
|
||||||
labelInValue
|
|
||||||
options={mergedTreeData.value}
|
|
||||||
onChange={null}
|
|
||||||
onSelect={null}
|
|
||||||
onDeselect={null}
|
|
||||||
onDropdownVisibleChange={onInternalDropdownVisibleChange}
|
|
||||||
v-slots={slots}
|
|
||||||
/>
|
|
||||||
</SelectContext>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import type { ComputedRef, Ref } from 'vue';
|
|
||||||
import { shallowRef, watchEffect } from 'vue';
|
|
||||||
import type { FlattenDataNode, Key, RawValueType } from '../interface';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return cached Key Value map with DataNode.
|
|
||||||
* Only re-calculate when `flattenOptions` changed.
|
|
||||||
*/
|
|
||||||
export default function useKeyValueMap(flattenOptions: ComputedRef<FlattenDataNode[]>) {
|
|
||||||
const cacheKeyMap: Ref<Map<Key, FlattenDataNode>> = shallowRef(new Map());
|
|
||||||
const cacheValueMap: Ref<Map<RawValueType, FlattenDataNode>> = shallowRef(new Map());
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
const newCacheKeyMap = new Map();
|
|
||||||
const newCacheValueMap = new Map();
|
|
||||||
// Cache options by key
|
|
||||||
flattenOptions.value.forEach((dataNode: FlattenDataNode) => {
|
|
||||||
newCacheKeyMap.set(dataNode.key, dataNode);
|
|
||||||
newCacheValueMap.set(dataNode.data.value, dataNode);
|
|
||||||
});
|
|
||||||
cacheKeyMap.value = newCacheKeyMap;
|
|
||||||
cacheValueMap.value = newCacheValueMap;
|
|
||||||
});
|
|
||||||
return [cacheKeyMap, cacheValueMap];
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import type { FlattenDataNode, Key, RawValueType } from '../interface';
|
|
||||||
|
|
||||||
export type SkipType = null | 'select' | 'checkbox';
|
|
||||||
|
|
||||||
export function isDisabled(dataNode: FlattenDataNode, skipType: SkipType): boolean {
|
|
||||||
if (!dataNode) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { disabled, disableCheckbox } = dataNode.data.node;
|
|
||||||
|
|
||||||
switch (skipType) {
|
|
||||||
case 'checkbox':
|
|
||||||
return disabled || disableCheckbox;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function useKeyValueMapping(
|
|
||||||
cacheKeyMap: Ref<Map<Key, FlattenDataNode>>,
|
|
||||||
cacheValueMap: Ref<Map<RawValueType, FlattenDataNode>>,
|
|
||||||
): [
|
|
||||||
(key: Key, skipType?: SkipType, ignoreDisabledCheck?: boolean) => FlattenDataNode,
|
|
||||||
(value: RawValueType, skipType?: SkipType, ignoreDisabledCheck?: boolean) => FlattenDataNode,
|
|
||||||
] {
|
|
||||||
const getEntityByKey = (
|
|
||||||
key: Key,
|
|
||||||
skipType: SkipType = 'select',
|
|
||||||
ignoreDisabledCheck?: boolean,
|
|
||||||
) => {
|
|
||||||
const dataNode = cacheKeyMap.value.get(key);
|
|
||||||
|
|
||||||
if (!ignoreDisabledCheck && isDisabled(dataNode, skipType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEntityByValue = (
|
|
||||||
value: RawValueType,
|
|
||||||
skipType: SkipType = 'select',
|
|
||||||
ignoreDisabledCheck?: boolean,
|
|
||||||
) => {
|
|
||||||
const dataNode = cacheValueMap.value.get(value);
|
|
||||||
|
|
||||||
if (!ignoreDisabledCheck && isDisabled(dataNode, skipType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
return [getEntityByKey, getEntityByValue];
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
import type { RawValueType, FlattenDataNode, Key, LabelValueType } from '../interface';
|
|
||||||
import type { SkipType } from './useKeyValueMapping';
|
|
||||||
import { getRawValueLabeled } from '../utils/valueUtil';
|
|
||||||
import type { CheckedStrategy } from '../utils/strategyUtil';
|
|
||||||
import { formatStrategyKeys } from '../utils/strategyUtil';
|
|
||||||
import type { DefaultValueType } from '../../vc-select/interface/generator';
|
|
||||||
import type { DataEntity } from '../../vc-tree/interface';
|
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import { shallowRef, watchEffect } from 'vue';
|
|
||||||
|
|
||||||
interface Config {
|
|
||||||
treeConduction: Ref<boolean>;
|
|
||||||
/** Current `value` of TreeSelect */
|
|
||||||
value: Ref<DefaultValueType>;
|
|
||||||
showCheckedStrategy: Ref<CheckedStrategy>;
|
|
||||||
conductKeyEntities: Ref<Record<Key, DataEntity>>;
|
|
||||||
getEntityByKey: (key: Key, skipType?: SkipType, ignoreDisabledCheck?: boolean) => FlattenDataNode;
|
|
||||||
getEntityByValue: (
|
|
||||||
value: RawValueType,
|
|
||||||
skipType?: SkipType,
|
|
||||||
ignoreDisabledCheck?: boolean,
|
|
||||||
) => FlattenDataNode;
|
|
||||||
getLabelProp: (entity: FlattenDataNode) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Return */
|
|
||||||
export default function useSelectValues(
|
|
||||||
rawValues: Ref<RawValueType[]>,
|
|
||||||
{
|
|
||||||
value,
|
|
||||||
getEntityByValue,
|
|
||||||
getEntityByKey,
|
|
||||||
treeConduction,
|
|
||||||
showCheckedStrategy,
|
|
||||||
conductKeyEntities,
|
|
||||||
getLabelProp,
|
|
||||||
}: Config,
|
|
||||||
): Ref<LabelValueType[]> {
|
|
||||||
const rawValueLabeled = shallowRef([]);
|
|
||||||
watchEffect(() => {
|
|
||||||
let mergedRawValues = rawValues.value;
|
|
||||||
|
|
||||||
if (treeConduction.value) {
|
|
||||||
const rawKeys = formatStrategyKeys(
|
|
||||||
rawValues.value.map(val => {
|
|
||||||
const entity = getEntityByValue(val);
|
|
||||||
return entity ? entity.key : val;
|
|
||||||
}),
|
|
||||||
showCheckedStrategy.value,
|
|
||||||
conductKeyEntities.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
mergedRawValues = rawKeys.map(key => {
|
|
||||||
const entity = getEntityByKey(key);
|
|
||||||
return entity ? entity.data.value : key;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
rawValueLabeled.value = getRawValueLabeled(
|
|
||||||
mergedRawValues,
|
|
||||||
value.value,
|
|
||||||
getEntityByValue,
|
|
||||||
getLabelProp,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return rawValueLabeled;
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import type { DataNode, SimpleModeConfig } from '../interface';
|
|
||||||
import { convertChildrenToData } from '../utils/legacyUtil';
|
|
||||||
import type { DefaultOptionType } from '../TreeSelect';
|
|
||||||
import type { VueNode } from 'ant-design-vue/es/_util/type';
|
|
||||||
|
|
||||||
function parseSimpleTreeData(
|
|
||||||
treeData: DataNode[],
|
|
||||||
{ id, pId, rootPId }: SimpleModeConfig,
|
|
||||||
): DataNode[] {
|
|
||||||
const keyNodes = {};
|
|
||||||
const rootNodeList = [];
|
|
||||||
|
|
||||||
// Fill in the map
|
|
||||||
const nodeList = treeData.map(node => {
|
|
||||||
const clone = { ...node };
|
|
||||||
const key = clone[id];
|
|
||||||
keyNodes[key] = clone;
|
|
||||||
clone.key = clone.key || key;
|
|
||||||
return clone;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect tree
|
|
||||||
nodeList.forEach(node => {
|
|
||||||
const parentKey = node[pId];
|
|
||||||
const parent = keyNodes[parentKey];
|
|
||||||
|
|
||||||
// Fill parent
|
|
||||||
if (parent) {
|
|
||||||
parent.children = parent.children || [];
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill root tree node
|
|
||||||
if (parentKey === rootPId || (!parent && rootPId === null)) {
|
|
||||||
rootNodeList.push(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return rootNodeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert `treeData` or `children` into formatted `treeData`.
|
|
||||||
* Will not re-calculate if `treeData` or `children` not change.
|
|
||||||
*/
|
|
||||||
export default function useTreeData(
|
|
||||||
treeData: Ref<DataNode[]>,
|
|
||||||
children: Ref<VueNode[]>,
|
|
||||||
simpleMode: Ref<boolean | SimpleModeConfig>,
|
|
||||||
): Ref<DefaultOptionType[]> {
|
|
||||||
return computed(() => {
|
|
||||||
if (treeData.value) {
|
|
||||||
return simpleMode.value
|
|
||||||
? parseSimpleTreeData(treeData.value, {
|
|
||||||
id: 'id',
|
|
||||||
pId: 'pId',
|
|
||||||
rootPId: null,
|
|
||||||
...(simpleMode.value !== true ? simpleMode.value : {}),
|
|
||||||
})
|
|
||||||
: treeData.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertChildrenToData(children.value);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
import type { ExtractPropTypes, PropType } from 'vue';
|
|
||||||
import type {
|
|
||||||
DataNode,
|
|
||||||
ChangeEventExtra,
|
|
||||||
DefaultValueType,
|
|
||||||
FieldNames,
|
|
||||||
FlattenDataNode,
|
|
||||||
LabelValueType,
|
|
||||||
LegacyDataNode,
|
|
||||||
RawValueType,
|
|
||||||
SimpleModeConfig,
|
|
||||||
} from './interface';
|
|
||||||
import { selectBaseProps } from '../vc-select';
|
|
||||||
import type { FilterFunc } from '../vc-select/interface/generator';
|
|
||||||
import omit from '../_util/omit';
|
|
||||||
import type { Key } from '../_util/type';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
import type { CheckedStrategy } from './utils/strategyUtil';
|
|
||||||
|
|
||||||
export function optionListProps<OptionsType>() {
|
|
||||||
return {
|
|
||||||
prefixCls: String,
|
|
||||||
id: String,
|
|
||||||
options: { type: Array as PropType<OptionsType[]> },
|
|
||||||
flattenOptions: { type: Array as PropType<FlattenDataNode[]> },
|
|
||||||
height: Number,
|
|
||||||
itemHeight: Number,
|
|
||||||
virtual: { type: Boolean, default: undefined },
|
|
||||||
values: { type: Set as PropType<Set<RawValueType>> },
|
|
||||||
multiple: { type: Boolean, default: undefined },
|
|
||||||
open: { type: Boolean, default: undefined },
|
|
||||||
defaultActiveFirstOption: { type: Boolean, default: undefined },
|
|
||||||
notFoundContent: PropTypes.any,
|
|
||||||
menuItemSelectedIcon: PropTypes.any,
|
|
||||||
childrenAsData: { type: Boolean, default: undefined },
|
|
||||||
searchValue: String,
|
|
||||||
|
|
||||||
onSelect: {
|
|
||||||
type: Function as PropType<(value: RawValueType, option: { selected: boolean }) => void>,
|
|
||||||
},
|
|
||||||
onToggleOpen: { type: Function as PropType<(open?: boolean) => void> },
|
|
||||||
/** Tell Select that some value is now active to make accessibility work */
|
|
||||||
onActiveValue: { type: Function as PropType<(value: RawValueType, index: number) => void> },
|
|
||||||
onScroll: { type: Function as PropType<(e: UIEvent) => void> },
|
|
||||||
|
|
||||||
onMouseenter: { type: Function as PropType<() => void> },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function treeSelectProps<ValueType = DefaultValueType>() {
|
|
||||||
const selectProps = omit(selectBaseProps<DataNode, ValueType>(), [
|
|
||||||
'onChange',
|
|
||||||
'mode',
|
|
||||||
'menuItemSelectedIcon',
|
|
||||||
'dropdownAlign',
|
|
||||||
'backfill',
|
|
||||||
'getInputElement',
|
|
||||||
'optionLabelProp',
|
|
||||||
'tokenSeparators',
|
|
||||||
'filterOption',
|
|
||||||
]);
|
|
||||||
return {
|
|
||||||
...selectProps,
|
|
||||||
|
|
||||||
multiple: { type: Boolean, default: undefined },
|
|
||||||
showArrow: { type: Boolean, default: undefined },
|
|
||||||
showSearch: { type: Boolean, default: undefined },
|
|
||||||
open: { type: Boolean, default: undefined },
|
|
||||||
defaultOpen: { type: Boolean, default: undefined },
|
|
||||||
value: { type: [String, Number, Object, Array] as PropType<ValueType> },
|
|
||||||
defaultValue: { type: [String, Number, Object, Array] as PropType<ValueType> },
|
|
||||||
disabled: { type: Boolean, default: undefined },
|
|
||||||
|
|
||||||
placeholder: PropTypes.any,
|
|
||||||
/** @deprecated Use `searchValue` instead */
|
|
||||||
inputValue: String,
|
|
||||||
searchValue: String,
|
|
||||||
autoClearSearchValue: { type: Boolean, default: undefined },
|
|
||||||
|
|
||||||
maxTagPlaceholder: { type: Function as PropType<(omittedValues: LabelValueType[]) => any> },
|
|
||||||
|
|
||||||
fieldNames: { type: Object as PropType<FieldNames> },
|
|
||||||
loadData: { type: Function as PropType<(dataNode: LegacyDataNode) => Promise<unknown>> },
|
|
||||||
treeNodeFilterProp: String,
|
|
||||||
treeNodeLabelProp: String,
|
|
||||||
treeDataSimpleMode: {
|
|
||||||
type: [Boolean, Object] as PropType<boolean | SimpleModeConfig>,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
treeExpandedKeys: { type: Array as PropType<Key[]> },
|
|
||||||
treeDefaultExpandedKeys: { type: Array as PropType<Key[]> },
|
|
||||||
treeLoadedKeys: { type: Array as PropType<Key[]> },
|
|
||||||
treeCheckable: { type: Boolean, default: undefined },
|
|
||||||
treeCheckStrictly: { type: Boolean, default: undefined },
|
|
||||||
showCheckedStrategy: { type: String as PropType<CheckedStrategy> },
|
|
||||||
treeDefaultExpandAll: { type: Boolean, default: undefined },
|
|
||||||
treeData: { type: Array as PropType<DataNode[]> },
|
|
||||||
treeLine: { type: Boolean, default: undefined },
|
|
||||||
treeIcon: PropTypes.any,
|
|
||||||
showTreeIcon: { type: Boolean, default: undefined },
|
|
||||||
switcherIcon: PropTypes.any,
|
|
||||||
treeMotion: PropTypes.any,
|
|
||||||
children: Array,
|
|
||||||
|
|
||||||
filterTreeNode: {
|
|
||||||
type: [Boolean, Function] as PropType<boolean | FilterFunc<LegacyDataNode>>,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
dropdownPopupAlign: PropTypes.any,
|
|
||||||
|
|
||||||
// Event
|
|
||||||
onSearch: { type: Function as PropType<(value: string) => void> },
|
|
||||||
onChange: {
|
|
||||||
type: Function as PropType<
|
|
||||||
(value: ValueType, labelList: any[], extra: ChangeEventExtra) => void
|
|
||||||
>,
|
|
||||||
},
|
|
||||||
onTreeExpand: { type: Function as PropType<(expandedKeys: Key[]) => void> },
|
|
||||||
onTreeLoad: { type: Function as PropType<(loadedKeys: Key[]) => void> },
|
|
||||||
onDropdownVisibleChange: { type: Function as PropType<(open: boolean) => void> },
|
|
||||||
|
|
||||||
// Legacy
|
|
||||||
/** `searchPlaceholder` has been removed since search box has been merged into input box */
|
|
||||||
searchPlaceholder: PropTypes.any,
|
|
||||||
|
|
||||||
/** @private This is not standard API since we only used in `rc-cascader`. Do not use in your production */
|
|
||||||
labelRender: { type: Function as PropType<(entity: FlattenDataNode) => any> },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Helper<T> {
|
|
||||||
ReturnOptionListProps = optionListProps<T>();
|
|
||||||
ReturnTreeSelectProps = treeSelectProps<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OptionListProps = Partial<ExtractPropTypes<Helper<DataNode>['ReturnOptionListProps']>>;
|
|
||||||
|
|
||||||
export type TreeSelectProps<T = DefaultValueType> = Partial<
|
|
||||||
ExtractPropTypes<Helper<T>['ReturnTreeSelectProps']>
|
|
||||||
>;
|
|
|
@ -1,50 +0,0 @@
|
||||||
import type { Key, DataNode, FieldNames } from '../interface';
|
|
||||||
import type { DefaultOptionType, InternalFieldName } from '../TreeSelect';
|
|
||||||
|
|
||||||
export function toArray<T>(value: T | T[]): T[] {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return value !== undefined ? [value] : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fillFieldNames(fieldNames?: FieldNames) {
|
|
||||||
const { label, value, children } = fieldNames || {};
|
|
||||||
|
|
||||||
const mergedValue = value || 'value';
|
|
||||||
|
|
||||||
return {
|
|
||||||
_title: label ? [label] : ['title', 'label'],
|
|
||||||
value: mergedValue,
|
|
||||||
key: mergedValue,
|
|
||||||
children: children || 'children',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCheckDisabled(node: DataNode) {
|
|
||||||
return node.disabled || node.disableCheckbox || node.checkable === false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Loop fetch all the keys exist in the tree */
|
|
||||||
export function getAllKeys(treeData: DefaultOptionType[], fieldNames: InternalFieldName) {
|
|
||||||
const keys: Key[] = [];
|
|
||||||
|
|
||||||
function dig(list: DefaultOptionType[]) {
|
|
||||||
list.forEach(item => {
|
|
||||||
keys.push(item[fieldNames.value]);
|
|
||||||
|
|
||||||
const children = item[fieldNames.children];
|
|
||||||
if (children) {
|
|
||||||
dig(children);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dig(treeData);
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNil(val: any) {
|
|
||||||
return val === null || val === undefined;
|
|
||||||
}
|
|
Loading…
Reference in New Issue