perf: tree & treeselect #5365
parent
96f508104c
commit
7127a5d816
|
@ -28,6 +28,7 @@ import type {
|
|||
ExpandType,
|
||||
GetPopupContainer,
|
||||
} from '../interface';
|
||||
import useMaxLevel from '../../vc-tree/useMaxLevel';
|
||||
|
||||
// TODO: warning if use ajax!!!
|
||||
|
||||
|
@ -120,7 +121,7 @@ export default function useSelection<RecordType>(
|
|||
|
||||
const keyEntities = computed(() =>
|
||||
mergedRowSelection.value.checkStrictly
|
||||
? { keyEntities: null }
|
||||
? null
|
||||
: convertDataToEntities(configRef.data.value as unknown as DataNode[], {
|
||||
externalGetKey: configRef.getRowKey.value as any,
|
||||
childrenPropName: configRef.childrenColumnName.value,
|
||||
|
@ -155,7 +156,7 @@ export default function useSelection<RecordType>(
|
|||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
const { maxLevel, levelEntities } = useMaxLevel(keyEntities);
|
||||
const isCheckboxDisabled: GetCheckDisabled<RecordType> = (r: RecordType) =>
|
||||
!!checkboxPropsMap.value.get(configRef.getRowKey.value(r))?.disabled;
|
||||
|
||||
|
@ -167,6 +168,8 @@ export default function useSelection<RecordType>(
|
|||
mergedSelectedKeys.value,
|
||||
true,
|
||||
keyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
isCheckboxDisabled as any,
|
||||
);
|
||||
return [checkedKeys || [], halfCheckedKeys];
|
||||
|
@ -571,6 +574,8 @@ export default function useSelection<RecordType>(
|
|||
[...originCheckedKeys, key],
|
||||
true,
|
||||
keyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
isCheckboxDisabled as any,
|
||||
);
|
||||
const { checkedKeys, halfCheckedKeys } = result;
|
||||
|
@ -584,6 +589,8 @@ export default function useSelection<RecordType>(
|
|||
Array.from(tempKeySet),
|
||||
{ checked: false, halfCheckedKeys },
|
||||
keyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
isCheckboxDisabled as any,
|
||||
).checkedKeys;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { useProvideCascader } from './context';
|
|||
import OptionList from './OptionList';
|
||||
import { BaseSelect } from '../vc-select';
|
||||
import devWarning from '../vc-util/devWarning';
|
||||
import useMaxLevel from '../vc-tree/useMaxLevel';
|
||||
|
||||
export interface ShowSearchType<OptionType extends BaseOptionType = DefaultOptionType> {
|
||||
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean;
|
||||
|
@ -259,6 +260,8 @@ export default defineComponent({
|
|||
ref<SingleValueType[]>([]),
|
||||
ref<SingleValueType[]>([]),
|
||||
];
|
||||
|
||||
const { maxLevel, levelEntities } = useMaxLevel(pathKeyEntities);
|
||||
watchEffect(() => {
|
||||
const [existValues, missingValues] = missingValuesInfo.value;
|
||||
|
||||
|
@ -274,7 +277,13 @@ export default defineComponent({
|
|||
const keyPathValues = toPathKeys(existValues);
|
||||
const ketPathEntities = pathKeyEntities.value;
|
||||
|
||||
const { checkedKeys, halfCheckedKeys } = conductCheck(keyPathValues, true, ketPathEntities);
|
||||
const { checkedKeys, halfCheckedKeys } = conductCheck(
|
||||
keyPathValues,
|
||||
true,
|
||||
ketPathEntities,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
);
|
||||
|
||||
// Convert key back to value cells
|
||||
[checkedValues.value, halfCheckedValues.value, missingCheckedValues.value] = [
|
||||
|
@ -356,9 +365,17 @@ export default defineComponent({
|
|||
nextRawCheckedKeys,
|
||||
{ checked: false, halfCheckedKeys: halfCheckedPathKeys },
|
||||
pathKeyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
));
|
||||
} else {
|
||||
({ checkedKeys } = conductCheck(nextRawCheckedKeys, true, pathKeyEntities.value));
|
||||
({ checkedKeys } = conductCheck(
|
||||
nextRawCheckedKeys,
|
||||
true,
|
||||
pathKeyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
));
|
||||
}
|
||||
|
||||
// Roll up to parent level keys
|
||||
|
|
|
@ -5,7 +5,7 @@ import classNames from '../_util/classNames';
|
|||
import pickAttrs from '../_util/pickAttrs';
|
||||
import { isValidElement } from '../_util/props-util';
|
||||
import createRef from '../_util/createRef';
|
||||
import { computed, defineComponent, nextTick, reactive, watch } from 'vue';
|
||||
import { computed, defineComponent, nextTick, reactive, toRaw, watch } from 'vue';
|
||||
import List from '../vc-virtual-list';
|
||||
import useMemo from '../_util/hooks/useMemo';
|
||||
import { isPlatformMac } from './utils/platformUtil';
|
||||
|
@ -105,7 +105,9 @@ const OptionList = defineComponent({
|
|||
() => {
|
||||
if (!baseProps.multiple && baseProps.open && props.rawValues.size === 1) {
|
||||
const value = Array.from(props.rawValues)[0];
|
||||
const index = memoFlattenOptions.value.findIndex(({ data }) => data.value === value);
|
||||
const index = toRaw(memoFlattenOptions.value).findIndex(
|
||||
({ data }) => data.value === value,
|
||||
);
|
||||
if (index !== -1) {
|
||||
setActive(index);
|
||||
nextTick(() => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import type {
|
|||
} from '../Select';
|
||||
import { injectPropsWithOption } from '../utils/valueUtil';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { toRaw, computed } from 'vue';
|
||||
|
||||
function includes(test: any, search: string) {
|
||||
return toArray(test).join('').toUpperCase().includes(search);
|
||||
|
@ -22,22 +22,24 @@ export default (
|
|||
optionFilterProp?: Ref<string>,
|
||||
) =>
|
||||
computed(() => {
|
||||
if (!searchValue.value || filterOption.value === false) {
|
||||
const searchValueVal = searchValue.value;
|
||||
const optionFilterPropValue = optionFilterProp?.value;
|
||||
const filterOptionValue = filterOption?.value;
|
||||
if (!searchValueVal || filterOptionValue === false) {
|
||||
return options.value;
|
||||
}
|
||||
|
||||
const { options: fieldOptions, label: fieldLabel, value: fieldValue } = fieldNames.value;
|
||||
const filteredOptions: DefaultOptionType[] = [];
|
||||
|
||||
const customizeFilter = typeof filterOption.value === 'function';
|
||||
const customizeFilter = typeof filterOptionValue === 'function';
|
||||
|
||||
const upperSearch = searchValue.value.toUpperCase();
|
||||
const upperSearch = searchValueVal.toUpperCase();
|
||||
const filterFunc = customizeFilter
|
||||
? (filterOption.value as FilterFunc<BaseOptionType>)
|
||||
? (filterOptionValue as FilterFunc<BaseOptionType>)
|
||||
: (_: string, option: DefaultOptionType) => {
|
||||
// Use provided `optionFilterProp`
|
||||
if (optionFilterProp.value) {
|
||||
return includes(option[optionFilterProp.value], upperSearch);
|
||||
if (optionFilterPropValue) {
|
||||
return includes(option[optionFilterPropValue], upperSearch);
|
||||
}
|
||||
|
||||
// Auto select `label` or `value` by option type
|
||||
|
@ -53,17 +55,17 @@ export default (
|
|||
? opt => injectPropsWithOption(opt)
|
||||
: opt => opt;
|
||||
|
||||
options.value.forEach(item => {
|
||||
toRaw(options.value).forEach(item => {
|
||||
// Group should check child options
|
||||
if (item[fieldOptions]) {
|
||||
// Check group first
|
||||
const matchGroup = filterFunc(searchValue.value, wrapOption(item));
|
||||
const matchGroup = filterFunc(searchValueVal, wrapOption(item));
|
||||
if (matchGroup) {
|
||||
filteredOptions.push(item);
|
||||
} else {
|
||||
// Check option
|
||||
const subOptions = item[fieldOptions].filter((subItem: DefaultOptionType) =>
|
||||
filterFunc(searchValue.value, wrapOption(subItem)),
|
||||
filterFunc(searchValueVal, wrapOption(subItem)),
|
||||
);
|
||||
if (subOptions.length) {
|
||||
filteredOptions.push({
|
||||
|
@ -76,10 +78,9 @@ export default (
|
|||
return;
|
||||
}
|
||||
|
||||
if (filterFunc(searchValue.value, wrapOption(item))) {
|
||||
if (filterFunc(searchValueVal, wrapOption(item))) {
|
||||
filteredOptions.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return filteredOptions;
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { shallowRef, watchEffect } from 'vue';
|
||||
import { toRaw, shallowRef, watchEffect } from 'vue';
|
||||
import type { FieldNames, RawValueType } from '../Select';
|
||||
import { convertChildrenToData } from '../utils/legacyUtil';
|
||||
|
||||
|
@ -16,7 +16,7 @@ export default function useOptions<OptionType>(
|
|||
const valueOptions = shallowRef();
|
||||
const labelOptions = shallowRef();
|
||||
watchEffect(() => {
|
||||
let newOptions = options.value;
|
||||
let newOptions = toRaw(options.value);
|
||||
const childrenAsData = !options.value;
|
||||
|
||||
if (childrenAsData) {
|
||||
|
@ -25,16 +25,16 @@ export default function useOptions<OptionType>(
|
|||
|
||||
const newValueOptions = new Map<RawValueType, OptionType>();
|
||||
const newLabelOptions = new Map<any, OptionType>();
|
||||
|
||||
const fieldNamesValue = fieldNames.value;
|
||||
function dig(optionList: OptionType[], isChildren = false) {
|
||||
// for loop to speed up collection speed
|
||||
for (let i = 0; i < optionList.length; i += 1) {
|
||||
const option = optionList[i];
|
||||
if (!option[fieldNames.value.options] || isChildren) {
|
||||
newValueOptions.set(option[fieldNames.value.value], option);
|
||||
newLabelOptions.set(option[fieldNames.value.label], option);
|
||||
if (!option[fieldNamesValue.options] || isChildren) {
|
||||
newValueOptions.set(option[fieldNamesValue.value], option);
|
||||
newLabelOptions.set(option[fieldNamesValue.label], option);
|
||||
} else {
|
||||
dig(option[fieldNames.value.options], true);
|
||||
dig(option[fieldNamesValue.options], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { TreeDataNode, Key } from './interface';
|
||||
import type { RefOptionListProps } from '../vc-select/OptionList';
|
||||
import type { ScrollTo } from '../vc-virtual-list/List';
|
||||
import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue';
|
||||
import { computed, defineComponent, nextTick, ref, shallowRef, toRaw, watch } from 'vue';
|
||||
import useMemo from '../_util/hooks/useMemo';
|
||||
import type { EventDataNode } from '../tree';
|
||||
import KeyCode from '../_util/KeyCode';
|
||||
|
@ -89,7 +89,7 @@ export default defineComponent({
|
|||
() => baseProps.searchValue,
|
||||
() => {
|
||||
if (baseProps.searchValue) {
|
||||
searchExpandedKeys.value = getAllKeys(context.treeData, context.fieldNames);
|
||||
searchExpandedKeys.value = getAllKeys(toRaw(context.treeData), toRaw(context.fieldNames));
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ export default defineComponent({
|
|||
);
|
||||
const mergedExpandedKeys = computed(() => {
|
||||
if (legacyContext.treeExpandedKeys) {
|
||||
return [...legacyContext.treeExpandedKeys];
|
||||
return toRaw(legacyContext.treeExpandedKeys).slice();
|
||||
}
|
||||
return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value;
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ import type { VueNode } from '../_util/type';
|
|||
import { conductCheck } from '../vc-tree/utils/conductUtil';
|
||||
import { warning } from '../vc-util/warning';
|
||||
import { toReactive } from '../_util/toReactive';
|
||||
import useMaxLevel from '../vc-tree/useMaxLevel';
|
||||
|
||||
export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void;
|
||||
|
||||
|
@ -364,13 +365,15 @@ export default defineComponent({
|
|||
|
||||
// const [mergedValues] = useCache(rawLabeledValues);
|
||||
const rawValues = computed(() => rawLabeledValues.value.map(item => item.value));
|
||||
|
||||
const { maxLevel, levelEntities } = useMaxLevel(keyEntities);
|
||||
// Convert value to key. Will fill missed keys for conduct check.
|
||||
const [rawCheckedValues, rawHalfCheckedValues] = useCheckedKeys(
|
||||
rawLabeledValues,
|
||||
rawHalfLabeledValues,
|
||||
treeConduction,
|
||||
keyEntities,
|
||||
maxLevel,
|
||||
levelEntities,
|
||||
);
|
||||
|
||||
// Convert rawCheckedKeys to check strategy related values
|
||||
|
@ -504,7 +507,9 @@ export default defineComponent({
|
|||
selectedKey: Key,
|
||||
{ selected, source }: { selected: boolean; source: SelectSource },
|
||||
) => {
|
||||
const entity = keyEntities.value[selectedKey];
|
||||
const keyEntitiesValue = toRaw(keyEntities.value);
|
||||
const valueEntitiesValue = toRaw(valueEntities.value);
|
||||
const entity = keyEntitiesValue[selectedKey];
|
||||
const node = entity?.node;
|
||||
const selectedValue = node?.[mergedFieldNames.value.value] ?? selectedKey;
|
||||
|
||||
|
@ -521,24 +526,32 @@ export default defineComponent({
|
|||
if (treeConduction.value) {
|
||||
// Should keep missing values
|
||||
const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
|
||||
const keyList = existRawValues.map(val => valueEntities.value.get(val).key);
|
||||
const keyList = existRawValues.map(val => valueEntitiesValue.get(val).key);
|
||||
|
||||
// Conduction by selected or not
|
||||
let checkedKeys: Key[];
|
||||
if (selected) {
|
||||
({ checkedKeys } = conductCheck(keyList, true, keyEntities.value));
|
||||
({ checkedKeys } = conductCheck(
|
||||
keyList,
|
||||
true,
|
||||
keyEntitiesValue,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
));
|
||||
} else {
|
||||
({ checkedKeys } = conductCheck(
|
||||
keyList,
|
||||
{ checked: false, halfCheckedKeys: rawHalfCheckedValues.value },
|
||||
keyEntities.value,
|
||||
keyEntitiesValue,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
));
|
||||
}
|
||||
|
||||
// Fill back of keys
|
||||
newRawValues = [
|
||||
...missingRawValues,
|
||||
...checkedKeys.map(key => keyEntities.value[key].node[mergedFieldNames.value.value]),
|
||||
...checkedKeys.map(key => keyEntitiesValue[key].node[mergedFieldNames.value.value]),
|
||||
];
|
||||
}
|
||||
triggerChange(newRawValues, { selected, triggerValue: selectedValue }, source || 'option');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { computed, shallowRef } from 'vue';
|
||||
import { toRaw, computed, shallowRef } from 'vue';
|
||||
import type { LabeledValueType, RawValueType } from '../TreeSelect';
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,7 @@ export default (values: Ref<LabeledValueType[]>): [Ref<LabeledValueType[]>] => {
|
|||
const { valueLabels } = cacheRef.value;
|
||||
const valueLabelsCache = new Map<RawValueType, any>();
|
||||
|
||||
const filledValues = values.value.map(item => {
|
||||
const filledValues = toRaw(values.value).map(item => {
|
||||
const { value } = item;
|
||||
const mergedLabel = item.label ?? valueLabels.get(value);
|
||||
|
||||
|
|
|
@ -2,26 +2,36 @@ import type { Key } from '../../_util/type';
|
|||
import type { DataEntity } from '../../vc-tree/interface';
|
||||
import { conductCheck } from '../../vc-tree/utils/conductUtil';
|
||||
import type { LabeledValueType, RawValueType } from '../TreeSelect';
|
||||
import type { Ref } from 'vue';
|
||||
import { shallowRef, watchEffect } from 'vue';
|
||||
import type { Ref, ShallowRef } from 'vue';
|
||||
import { toRaw, shallowRef, watchEffect } from 'vue';
|
||||
|
||||
export default (
|
||||
rawLabeledValues: Ref<LabeledValueType[]>,
|
||||
rawHalfCheckedValues: Ref<LabeledValueType[]>,
|
||||
treeConduction: Ref<boolean>,
|
||||
keyEntities: Ref<Record<Key, DataEntity>>,
|
||||
maxLevel: Ref<number>,
|
||||
levelEntities: ShallowRef<Map<number, Set<DataEntity>>>,
|
||||
) => {
|
||||
const newRawCheckedValues = shallowRef<RawValueType[]>([]);
|
||||
const newRawHalfCheckedValues = shallowRef<RawValueType[]>([]);
|
||||
|
||||
watchEffect(() => {
|
||||
let checkedKeys: RawValueType[] = rawLabeledValues.value.map(({ value }) => value);
|
||||
let halfCheckedKeys: RawValueType[] = rawHalfCheckedValues.value.map(({ value }) => value);
|
||||
let checkedKeys: RawValueType[] = toRaw(rawLabeledValues.value).map(({ value }) => value);
|
||||
let halfCheckedKeys: RawValueType[] = toRaw(rawHalfCheckedValues.value).map(
|
||||
({ value }) => value,
|
||||
);
|
||||
|
||||
const missingValues = checkedKeys.filter(key => !keyEntities.value[key]);
|
||||
|
||||
if (treeConduction.value) {
|
||||
({ checkedKeys, halfCheckedKeys } = conductCheck(checkedKeys, true, keyEntities.value));
|
||||
({ checkedKeys, halfCheckedKeys } = conductCheck(
|
||||
checkedKeys,
|
||||
true,
|
||||
keyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
));
|
||||
}
|
||||
newRawCheckedValues.value = Array.from(new Set([...missingValues, ...checkedKeys]));
|
||||
newRawHalfCheckedValues.value = halfCheckedKeys;
|
||||
|
|
|
@ -4,21 +4,22 @@ import type { FieldNames, RawValueType } from '../TreeSelect';
|
|||
|
||||
import { isNil } from '../utils/valueUtil';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { toRaw, ref, watchEffect } from 'vue';
|
||||
import { warning } from '../../vc-util/warning';
|
||||
|
||||
export default (treeData: Ref<any>, fieldNames: Ref<FieldNames>) => {
|
||||
const valueEntities = ref<Map<RawValueType, DataEntity>>(new Map());
|
||||
const keyEntities = ref<Record<string, DataEntity>>({});
|
||||
watchEffect(() => {
|
||||
const collection = convertDataToEntities(treeData.value, {
|
||||
fieldNames: fieldNames.value,
|
||||
const fieldNamesValue = fieldNames.value;
|
||||
const collection = convertDataToEntities(toRaw(treeData.value), {
|
||||
fieldNames: fieldNamesValue,
|
||||
initWrapper: wrapper => ({
|
||||
...wrapper,
|
||||
valueEntities: new Map(),
|
||||
}),
|
||||
processEntity: (entity, wrapper: any) => {
|
||||
const val = entity.node[fieldNames.value.value];
|
||||
const val = entity.node[fieldNamesValue.value];
|
||||
|
||||
// Check if exist same value
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { toRaw, computed } from 'vue';
|
||||
import type { DefaultOptionType, InternalFieldName, TreeSelectProps } from '../TreeSelect';
|
||||
import { fillLegacyProps } from '../utils/legacyUtil';
|
||||
|
||||
|
@ -21,7 +21,9 @@ export default (
|
|||
) => {
|
||||
return computed(() => {
|
||||
const { children: fieldChildren } = fieldNames.value;
|
||||
if (!searchValue.value || filterTreeNode.value === false) {
|
||||
const searchValueVal = searchValue.value;
|
||||
const treeNodeFilterPropValue = treeNodeFilterProp?.value;
|
||||
if (!searchValueVal || filterTreeNode.value === false) {
|
||||
return treeData.value;
|
||||
}
|
||||
|
||||
|
@ -29,33 +31,49 @@ export default (
|
|||
if (typeof filterTreeNode.value === 'function') {
|
||||
filterOptionFunc = filterTreeNode.value;
|
||||
} else {
|
||||
const upperStr = searchValue.value.toUpperCase();
|
||||
const upperStr = searchValueVal.toUpperCase();
|
||||
filterOptionFunc = (_, dataNode) => {
|
||||
const value = dataNode[treeNodeFilterProp.value];
|
||||
const value = dataNode[treeNodeFilterPropValue];
|
||||
|
||||
return String(value).toUpperCase().includes(upperStr);
|
||||
};
|
||||
}
|
||||
|
||||
function dig(list: DefaultOptionType[], keepAll = false) {
|
||||
return list
|
||||
.map(dataNode => {
|
||||
const children = dataNode[fieldChildren];
|
||||
const res = [];
|
||||
for (let index = 0, len = list.length; index < len; index++) {
|
||||
const dataNode = list[index];
|
||||
const children = dataNode[fieldChildren];
|
||||
|
||||
const match = keepAll || filterOptionFunc(searchValue.value, fillLegacyProps(dataNode));
|
||||
const childList = dig(children || [], match);
|
||||
const match = keepAll || filterOptionFunc(searchValueVal, fillLegacyProps(dataNode));
|
||||
const childList = dig(children || [], match);
|
||||
|
||||
if (match || childList.length) {
|
||||
return {
|
||||
...dataNode,
|
||||
[fieldChildren]: childList,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(node => node);
|
||||
if (match || childList.length) {
|
||||
res.push({
|
||||
...dataNode,
|
||||
[fieldChildren]: childList,
|
||||
});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
// return list
|
||||
// .map(dataNode => {
|
||||
// const children = dataNode[fieldChildren];
|
||||
|
||||
// const match = keepAll || filterOptionFunc(searchValueVal, fillLegacyProps(dataNode));
|
||||
// const childList = dig(children || [], match);
|
||||
|
||||
// if (match || childList.length) {
|
||||
// return {
|
||||
// ...dataNode,
|
||||
// [fieldChildren]: childList,
|
||||
// };
|
||||
// }
|
||||
// return null;
|
||||
// })
|
||||
// .filter(node => node);
|
||||
}
|
||||
|
||||
return dig(treeData.value);
|
||||
return dig(toRaw(treeData.value));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { toRaw, computed } from 'vue';
|
||||
import type { DataNode, SimpleModeConfig } from '../interface';
|
||||
import { convertChildrenToData } from '../utils/legacyUtil';
|
||||
import type { DefaultOptionType } from '../TreeSelect';
|
||||
|
@ -51,17 +51,18 @@ export default function useTreeData(
|
|||
simpleMode: Ref<boolean | SimpleModeConfig>,
|
||||
): Ref<DefaultOptionType[]> {
|
||||
return computed(() => {
|
||||
const simpleModeValue = simpleMode.value;
|
||||
if (treeData.value) {
|
||||
return simpleMode.value
|
||||
? parseSimpleTreeData(treeData.value, {
|
||||
? parseSimpleTreeData(toRaw(treeData.value), {
|
||||
id: 'id',
|
||||
pId: 'pId',
|
||||
rootPId: null,
|
||||
...(simpleMode.value !== true ? simpleMode.value : {}),
|
||||
...(simpleModeValue !== true ? simpleModeValue : {}),
|
||||
})
|
||||
: treeData.value;
|
||||
}
|
||||
|
||||
return convertChildrenToData(children.value);
|
||||
return convertChildrenToData(toRaw(children.value));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
watch,
|
||||
watchEffect,
|
||||
nextTick,
|
||||
toRaw,
|
||||
} from 'vue';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import type { CheckInfo, DraggableFn } from './props';
|
||||
|
@ -40,6 +41,7 @@ import { warning } from '../vc-util/warning';
|
|||
import KeyCode from '../_util/KeyCode';
|
||||
import classNames from '../_util/classNames';
|
||||
import pickAttrs from '../_util/pickAttrs';
|
||||
import useMaxLevel from './useMaxLevel';
|
||||
|
||||
const MAX_RETRY_TIMES = 10;
|
||||
|
||||
|
@ -101,8 +103,12 @@ export default defineComponent({
|
|||
// abstract-drag-over-node is the top node
|
||||
dragOverNodeKey: null,
|
||||
});
|
||||
const treeData = computed(() => {
|
||||
return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children);
|
||||
const treeData = shallowRef([]);
|
||||
watchEffect(() => {
|
||||
treeData.value =
|
||||
props.treeData !== undefined
|
||||
? toRaw(props.treeData)
|
||||
: convertTreeToData(toRaw(props.children));
|
||||
});
|
||||
const keyEntities = shallowRef({});
|
||||
|
||||
|
@ -137,7 +143,9 @@ export default defineComponent({
|
|||
|
||||
watchEffect(() => {
|
||||
if (treeData.value) {
|
||||
const entitiesMap = convertDataToEntities(treeData.value, { fieldNames: fieldNames.value });
|
||||
const entitiesMap = convertDataToEntities(toRaw(treeData.value), {
|
||||
fieldNames: fieldNames.value,
|
||||
});
|
||||
keyEntities.value = {
|
||||
[MOTION_KEY]: MotionEntity,
|
||||
...entitiesMap.keyEntities,
|
||||
|
@ -180,32 +188,37 @@ export default defineComponent({
|
|||
);
|
||||
|
||||
// ================ flattenNodes =================
|
||||
const flattenNodes = computed(() => {
|
||||
return flattenTreeData(treeData.value, expandedKeys.value, fieldNames.value);
|
||||
const flattenNodes = shallowRef([]);
|
||||
watchEffect(() => {
|
||||
flattenNodes.value = flattenTreeData(
|
||||
toRaw(treeData.value),
|
||||
toRaw(expandedKeys.value),
|
||||
fieldNames.value,
|
||||
);
|
||||
});
|
||||
// ================ selectedKeys =================
|
||||
watchEffect(() => {
|
||||
if (props.selectable) {
|
||||
if (props.selectedKeys !== undefined) {
|
||||
selectedKeys.value = calcSelectedKeys(props.selectedKeys, props);
|
||||
selectedKeys.value = calcSelectedKeys(toRaw(props.selectedKeys), props);
|
||||
} else if (!init && props.defaultSelectedKeys) {
|
||||
selectedKeys.value = calcSelectedKeys(props.defaultSelectedKeys, props);
|
||||
selectedKeys.value = calcSelectedKeys(toRaw(props.defaultSelectedKeys), props);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { maxLevel, levelEntities } = useMaxLevel(keyEntities);
|
||||
// ================= checkedKeys =================
|
||||
watchEffect(() => {
|
||||
if (props.checkable) {
|
||||
let checkedKeyEntity;
|
||||
|
||||
if (props.checkedKeys !== undefined) {
|
||||
checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {};
|
||||
checkedKeyEntity = parseCheckedKeys(toRaw(props.checkedKeys)) || {};
|
||||
} else if (!init && props.defaultCheckedKeys) {
|
||||
checkedKeyEntity = parseCheckedKeys(props.defaultCheckedKeys) || {};
|
||||
checkedKeyEntity = parseCheckedKeys(toRaw(props.defaultCheckedKeys)) || {};
|
||||
} else if (treeData.value) {
|
||||
// If `treeData` changed, we also need check it
|
||||
checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {
|
||||
checkedKeyEntity = parseCheckedKeys(toRaw(props.checkedKeys)) || {
|
||||
checkedKeys: checkedKeys.value,
|
||||
halfCheckedKeys: halfCheckedKeys.value,
|
||||
};
|
||||
|
@ -216,7 +229,13 @@ export default defineComponent({
|
|||
checkedKeyEntity;
|
||||
|
||||
if (!props.checkStrictly) {
|
||||
const conductKeys = conductCheck(newCheckedKeys, true, keyEntities.value);
|
||||
const conductKeys = conductCheck(
|
||||
newCheckedKeys,
|
||||
true,
|
||||
keyEntities.value,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
);
|
||||
({ checkedKeys: newCheckedKeys, halfCheckedKeys: newHalfCheckedKeys } = conductKeys);
|
||||
}
|
||||
|
||||
|
@ -399,7 +418,7 @@ export default defineComponent({
|
|||
delayedDragEnterLogic[pos] = window.setTimeout(() => {
|
||||
if (dragState.draggingNodeKey === null) return;
|
||||
|
||||
let newExpandedKeys = [...expandedKeys.value];
|
||||
let newExpandedKeys = expandedKeys.value.slice();
|
||||
const entity = keyEntities.value[node.eventKey];
|
||||
|
||||
if (entity && (entity.children || []).length) {
|
||||
|
@ -549,7 +568,7 @@ export default defineComponent({
|
|||
|
||||
if (dropTargetKey === null) return;
|
||||
const abstractDropNodeProps = {
|
||||
...getTreeNodeProps(dropTargetKey, treeNodeRequiredProps.value),
|
||||
...getTreeNodeProps(dropTargetKey, toRaw(treeNodeRequiredProps.value)),
|
||||
active: activeItem.value?.key === dropTargetKey,
|
||||
data: keyEntities.value[dropTargetKey].node,
|
||||
};
|
||||
|
@ -646,7 +665,7 @@ export default defineComponent({
|
|||
checked,
|
||||
nativeEvent: e,
|
||||
};
|
||||
|
||||
const keyEntitiesValue = keyEntities.value;
|
||||
if (checkStrictly) {
|
||||
const newCheckedKeys = checked
|
||||
? arrAdd(checkedKeys.value, key)
|
||||
|
@ -654,7 +673,6 @@ export default defineComponent({
|
|||
const newHalfCheckedKeys = arrDel(halfCheckedKeys.value, key);
|
||||
checkedObj = { checked: newCheckedKeys, halfChecked: newHalfCheckedKeys };
|
||||
|
||||
const keyEntitiesValue = keyEntities.value;
|
||||
eventObj.checkedNodes = newCheckedKeys
|
||||
.map(checkedKey => keyEntitiesValue[checkedKey])
|
||||
.filter(entity => entity)
|
||||
|
@ -668,7 +686,9 @@ export default defineComponent({
|
|||
let { checkedKeys: newCheckedKeys, halfCheckedKeys: newHalfCheckedKeys } = conductCheck(
|
||||
[...checkedKeys.value, key],
|
||||
true,
|
||||
keyEntities.value,
|
||||
keyEntitiesValue,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
);
|
||||
|
||||
// If remove, we do it again to correction
|
||||
|
@ -678,7 +698,9 @@ export default defineComponent({
|
|||
({ checkedKeys: newCheckedKeys, halfCheckedKeys: newHalfCheckedKeys } = conductCheck(
|
||||
Array.from(keySet),
|
||||
{ checked: false, halfCheckedKeys: newHalfCheckedKeys },
|
||||
keyEntities.value,
|
||||
keyEntitiesValue,
|
||||
maxLevel.value,
|
||||
levelEntities.value,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -689,7 +711,7 @@ export default defineComponent({
|
|||
eventObj.checkedNodesPositions = [];
|
||||
eventObj.halfCheckedKeys = newHalfCheckedKeys;
|
||||
newCheckedKeys.forEach(checkedKey => {
|
||||
const entity = keyEntities.value[checkedKey];
|
||||
const entity = keyEntitiesValue[checkedKey];
|
||||
if (!entity) return;
|
||||
|
||||
const { node, pos } = entity;
|
||||
|
@ -906,7 +928,6 @@ export default defineComponent({
|
|||
|
||||
const offsetActiveKey = (offset: number) => {
|
||||
let index = flattenNodes.value.findIndex(({ key }) => key === activeKey.value);
|
||||
|
||||
// Align with index
|
||||
if (index === -1 && offset < 0) {
|
||||
index = flattenNodes.value.length;
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import type { ShallowRef } from 'vue';
|
||||
import { shallowRef, ref, watchEffect } from 'vue';
|
||||
import type { BasicDataNode, DataEntity, DataNode, Key } from './interface';
|
||||
|
||||
export default function useMaxLevel<TreeDataType extends BasicDataNode = DataNode>(
|
||||
keyEntities: ShallowRef<Record<Key, DataEntity<TreeDataType>>>,
|
||||
) {
|
||||
const maxLevel = ref(0);
|
||||
const levelEntities = shallowRef<Map<number, Set<DataEntity<TreeDataType>>>>();
|
||||
watchEffect(() => {
|
||||
const newLevelEntities = new Map<number, Set<DataEntity<TreeDataType>>>();
|
||||
let newMaxLevel = 0;
|
||||
const keyEntitiesValue = keyEntities.value || {};
|
||||
// Convert entities by level for calculation
|
||||
for (const key in keyEntitiesValue) {
|
||||
if (Object.prototype.hasOwnProperty.call(keyEntitiesValue, key)) {
|
||||
const entity = keyEntitiesValue[key];
|
||||
const { level } = entity;
|
||||
|
||||
let levelSet: Set<DataEntity<TreeDataType>> = newLevelEntities.get(level);
|
||||
if (!levelSet) {
|
||||
levelSet = new Set();
|
||||
newLevelEntities.set(level, levelSet);
|
||||
}
|
||||
|
||||
levelSet.add(entity);
|
||||
|
||||
newMaxLevel = Math.max(newMaxLevel, level);
|
||||
}
|
||||
}
|
||||
maxLevel.value = newMaxLevel;
|
||||
levelEntities.value = newLevelEntities;
|
||||
});
|
||||
return {
|
||||
maxLevel,
|
||||
levelEntities,
|
||||
};
|
||||
}
|
|
@ -186,6 +186,8 @@ export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
|
|||
keyList: Key[],
|
||||
checked: true | { checked: false; halfCheckedKeys: Key[] },
|
||||
keyEntities: Record<Key, DataEntity<TreeDataType>>,
|
||||
maxLevel: number,
|
||||
levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
|
||||
getCheckDisabled?: GetCheckDisabled<TreeDataType>,
|
||||
): ConductReturnType {
|
||||
const warningMissKeys: Key[] = [];
|
||||
|
@ -208,24 +210,6 @@ export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
|
|||
return hasEntity;
|
||||
}),
|
||||
);
|
||||
const levelEntities = new Map<number, Set<DataEntity<TreeDataType>>>();
|
||||
let maxLevel = 0;
|
||||
|
||||
// Convert entities by level for calculation
|
||||
Object.keys(keyEntities).forEach(key => {
|
||||
const entity = keyEntities[key];
|
||||
const { level } = entity;
|
||||
|
||||
let levelSet: Set<DataEntity<TreeDataType>> = levelEntities.get(level);
|
||||
if (!levelSet) {
|
||||
levelSet = new Set();
|
||||
levelEntities.set(level, levelSet);
|
||||
}
|
||||
|
||||
levelSet.add(entity);
|
||||
|
||||
maxLevel = Math.max(maxLevel, level);
|
||||
});
|
||||
|
||||
warning(
|
||||
!warningMissKeys.length,
|
||||
|
|
Loading…
Reference in New Issue