perf: tree & treeselect #5365

pull/5369/head
tangjinzhou 2022-03-20 16:42:00 +08:00
parent 96f508104c
commit 7127a5d816
15 changed files with 220 additions and 107 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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(() => {

View File

@ -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;
});

View File

@ -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);
}
}
}

View File

@ -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;
});

View File

@ -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');

View File

@ -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);

View File

@ -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;

View File

@ -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') {

View File

@ -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));
});
};

View File

@ -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));
});
}

View File

@ -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;

View File

@ -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,
};
}

View File

@ -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,