perf: tree, close #5551

pull/5564/head
tangjinzhou 2022-04-27 22:37:22 +08:00
parent 53b4c5d8b2
commit 9aeadaf877
11 changed files with 266 additions and 207 deletions

View File

@ -16,8 +16,8 @@ import PropTypes from '../_util/vue-types';
import useConfigInject from '../_util/hooks/useConfigInject';
import devWarning from '../vc-util/devWarning';
import getIcons from '../select/utils/iconUtil';
import type { SwitcherIconProps } from '../tree/utils/iconUtil';
import renderSwitcherIcon from '../tree/utils/iconUtil';
import type { AntTreeNodeProps } from '../tree/Tree';
import { warning } from '../vc-util/warning';
import { flattenChildren } from '../_util/props-util';
import { useInjectFormItemContext } from '../form/FormItemContext';
@ -239,7 +239,7 @@ const TreeSelect = defineComponent({
multiple={multiple}
removeIcon={removeIcon}
clearIcon={clearIcon}
switcherIcon={(nodeProps: AntTreeNodeProps) =>
switcherIcon={(nodeProps: SwitcherIconProps) =>
renderSwitcherIcon(treePrefixCls.value, switcherIcon, treeLine, nodeProps)
}
showTreeIcon={treeIcon as any}

View File

@ -9,6 +9,7 @@ import type { DataNode, EventDataNode, FieldNames, Key } from '../vc-tree/interf
import type { TreeNodeProps } from '../vc-tree/props';
import { treeProps as vcTreeProps } from '../vc-tree/props';
import useConfigInject from '../_util/hooks/useConfigInject';
import type { SwitcherIconProps } from './utils/iconUtil';
import renderSwitcherIcon from './utils/iconUtil';
import dropIndicatorRender from './utils/dropIndicator';
import devWarning from '../vc-util/devWarning';
@ -229,7 +230,7 @@ export default defineComponent({
icon,
itemHeight,
};
const children = slots.default ? filterEmpty(slots.default()) : undefined;
return (
<VcTree
{...newProps}
@ -249,7 +250,7 @@ export default defineComponent({
direction={direction.value}
checkable={checkable}
selectable={selectable}
switcherIcon={(nodeProps: AntTreeNodeProps) =>
switcherIcon={(nodeProps: SwitcherIconProps) =>
renderSwitcherIcon(prefixCls.value, switcherIcon, showLine, nodeProps)
}
onCheck={handleCheck}
@ -260,7 +261,7 @@ export default defineComponent({
...slots,
checkable: () => <span class={`${prefixCls.value}-checkbox-inner`} />,
}}
children={filterEmpty(slots.default?.())}
children={children}
></VcTree>
);
};

View File

@ -7,12 +7,15 @@ import type { AntTreeNodeProps } from '../Tree';
import { isValidElement } from '../../_util/props-util';
import { cloneVNode } from 'vue';
export interface SwitcherIconProps extends AntTreeNodeProps {
expanded: boolean;
loading: boolean;
}
export default function renderSwitcherIcon(
prefixCls: string,
switcherIcon: any,
showLine: boolean | { showLeafIcon: boolean } | undefined,
props: AntTreeNodeProps,
props: SwitcherIconProps,
) {
const { isLeaf, expanded, loading } = props;
let icon = switcherIcon;

View File

@ -1,7 +1,5 @@
import TreeNode from './TreeNode';
import type { FlattenNode } from './interface';
import type { TreeNodeRequiredProps } from './utils/treeUtil';
import { getTreeNodeProps } from './utils/treeUtil';
import { useInjectTreeContext } from './contextTypes';
import type { PropType } from 'vue';
import {
@ -28,7 +26,7 @@ export default defineComponent({
onMotionStart: Function,
onMotionEnd: Function,
motionType: String,
treeNodeRequiredProps: { type: Object as PropType<TreeNodeRequiredProps> },
// treeNodeRequiredProps: { type: Object as PropType<TreeNodeRequiredProps> },
},
slots: ['title', 'icon', 'switcherIcon', 'checkable'],
setup(props, { attrs, slots }) {
@ -73,8 +71,7 @@ export default defineComponent({
});
return () => {
const { motion, motionNodes, motionType, active, treeNodeRequiredProps, ...otherProps } =
props;
const { motion, motionNodes, motionType, active, eventKey, ...otherProps } = props;
if (motionNodes) {
return (
<Transition
@ -94,17 +91,15 @@ export default defineComponent({
} = treeNode;
delete restProps.children;
const treeNodeProps = getTreeNodeProps(key, treeNodeRequiredProps);
return (
<TreeNode
v-slots={slots}
{...restProps}
{...treeNodeProps}
title={title}
active={active}
data={treeNode.data}
key={key}
eventKey={key}
isStart={isStart}
isEnd={isEnd}
/>
@ -122,6 +117,7 @@ export default defineComponent({
style={attrs.style}
{...otherProps}
active={active}
eventKey={eventKey}
/>
);
};

View File

@ -4,12 +4,14 @@
import { computed, defineComponent, ref, shallowRef, watch } from 'vue';
import VirtualList from '../vc-virtual-list';
import omit from '../_util/omit';
import { useInjectKeysState, useInjectTreeContext } from './contextTypes';
import type { FlattenNode, DataEntity, DataNode, ScrollTo } from './interface';
import MotionTreeNode from './MotionTreeNode';
import type { NodeListProps } from './props';
import { nodeListProps } from './props';
import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
import { getTreeNodeProps, getKey } from './utils/treeUtil';
import { getKey } from './utils/treeUtil';
const HIDDEN_STYLE = {
width: 0,
@ -97,6 +99,7 @@ export default defineComponent({
// =============================== Ref ================================
const listRef = ref();
const indentMeasurerRef = ref();
const { expandedKeys, flattenNodes } = useInjectKeysState();
expose({
scrollTo: scroll => {
listRef.value.scrollTo(scroll);
@ -104,19 +107,21 @@ export default defineComponent({
getIndentWidth: () => indentMeasurerRef.value.offsetWidth,
});
// ============================== Motion ==============================
const transitionData = shallowRef<FlattenNode[]>(props.data);
const transitionData = shallowRef<FlattenNode[]>(flattenNodes.value);
const transitionRange = shallowRef([]);
const motionType = ref<'show' | 'hide' | null>(null);
function onMotionEnd() {
transitionData.value = props.data;
transitionData.value = flattenNodes.value;
transitionRange.value = [];
motionType.value = null;
props.onListChangeEnd();
}
const context = useInjectTreeContext();
watch(
[() => [...props.expandedKeys], () => props.data],
[() => expandedKeys.value.slice(), flattenNodes],
([expandedKeys, data], [prevExpandedKeys, prevData]) => {
const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);
if (diffExpanded.key !== null) {
@ -160,7 +165,7 @@ export default defineComponent({
);
// We should clean up motion if is changed by dragging
watch(
() => props.dragging,
() => context.value.dragging,
dragging => {
if (!dragging) {
onMotionEnd();
@ -169,27 +174,18 @@ export default defineComponent({
);
const mergedData = computed(() =>
props.motion === undefined ? transitionData.value : props.data,
props.motion === undefined ? transitionData.value : flattenNodes.value,
);
const onActiveChange = () => {
props.onActiveChange(null);
};
return () => {
const {
prefixCls,
data,
selectable,
checkable,
expandedKeys,
selectedKeys,
checkedKeys,
loadedKeys,
loadingKeys,
halfCheckedKeys,
keyEntities,
disabled,
dragging,
dragOverNodeKey,
dropPosition,
motion,
height,
@ -204,25 +200,12 @@ export default defineComponent({
onKeydown,
onFocus,
onBlur,
onActiveChange,
onListChangeStart,
onListChangeEnd,
...domProps
} = { ...props, ...attrs } as NodeListProps;
const treeNodeRequiredProps = {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities,
};
return (
<>
{focused && activeItem && (
@ -262,7 +245,7 @@ export default defineComponent({
</div>
<VirtualList
{...domProps}
{...omit(domProps, ['onActiveChange'])}
data={mergedData.value}
itemKey={itemKey as any}
height={height}
@ -293,15 +276,12 @@ export default defineComponent({
const mergedKey = getKey(key, pos);
delete restProps.key;
delete restProps.children;
const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
return (
<MotionTreeNode
{...restProps}
{...treeNodeProps}
eventKey={mergedKey}
title={title}
active={!!activeItem && key === activeItem.key}
pos={pos}
data={treeNode.data}
isStart={isStart}
isEnd={isEnd}
@ -310,10 +290,7 @@ export default defineComponent({
motionType={motionType.value}
onMotionStart={onListChangeStart}
onMotionEnd={onMotionEnd}
treeNodeRequiredProps={treeNodeRequiredProps}
onMousemove={() => {
onActiveChange(null);
}}
onMousemove={onActiveChange}
/>
);
},

View File

@ -1,5 +1,5 @@
import type { NodeMouseEventHandler, NodeDragEventHandler } from './contextTypes';
import { TreeContext } from './contextTypes';
import { useProvideKeysState, TreeContext } from './contextTypes';
import {
getDragChildrenKeys,
parseCheckedKeys,
@ -11,6 +11,7 @@ import {
posToArr,
} from './util';
import type { Key, FlattenNode, EventDataNode, ScrollTo, DragNodeEvent } from './interface';
import type { TreeNodeRequiredProps } from './utils/treeUtil';
import {
flattenTreeData,
convertTreeToData,
@ -78,12 +79,12 @@ export default defineComponent({
const destroyed = ref(false);
let delayedDragEnterLogic: Record<Key, number> = {};
const indent = ref();
const selectedKeys = shallowRef([]);
const checkedKeys = shallowRef([]);
const halfCheckedKeys = shallowRef([]);
const loadedKeys = shallowRef([]);
const loadingKeys = shallowRef([]);
const expandedKeys = shallowRef([]);
const selectedKeys = shallowRef<Key[]>([]);
const checkedKeys = shallowRef<Key[]>([]);
const halfCheckedKeys = shallowRef<Key[]>([]);
const loadedKeys = shallowRef<Key[]>([]);
const loadingKeys = shallowRef<Key[]>([]);
const expandedKeys = shallowRef<Key[]>([]);
const loadingRetryTimes: Record<Key, number> = {};
const dragState = reactive({
draggingNodeKey: null,
@ -112,7 +113,10 @@ export default defineComponent({
? toRaw(props.treeData).slice()
: convertTreeToData(toRaw(props.children));
},
{ immediate: true, deep: true },
{
immediate: true,
deep: true,
},
);
const keyEntities = shallowRef({});
@ -131,19 +135,37 @@ export default defineComponent({
let currentMouseOverDroppableNodeKey = null;
const treeNodeRequiredProps = computed(() => {
const treeNodeRequiredProps = computed<TreeNodeRequiredProps>(() => {
return {
expandedKeys: expandedKeys.value || [],
selectedKeys: selectedKeys.value || [],
loadedKeys: loadedKeys.value || [],
loadingKeys: loadingKeys.value || [],
checkedKeys: checkedKeys.value || [],
halfCheckedKeys: halfCheckedKeys.value || [],
expandedKeysSet: expandedKeysSet.value,
selectedKeysSet: selectedKeysSet.value,
loadedKeysSet: loadedKeysSet.value,
loadingKeysSet: loadingKeysSet.value,
checkedKeysSet: checkedKeysSet.value,
halfCheckedKeysSet: halfCheckedKeysSet.value,
dragOverNodeKey: dragState.dragOverNodeKey,
dropPosition: dragState.dropPosition,
keyEntities: keyEntities.value,
};
});
const expandedKeysSet = computed(() => {
return new Set(expandedKeys.value);
});
const selectedKeysSet = computed(() => {
return new Set(selectedKeys.value);
});
const loadedKeysSet = computed(() => {
return new Set(loadedKeys.value);
});
const loadingKeysSet = computed(() => {
return new Set(loadingKeys.value);
});
const checkedKeysSet = computed(() => {
return new Set(checkedKeys.value);
});
const halfCheckedKeysSet = computed(() => {
return new Set(halfCheckedKeys.value);
});
watchEffect(() => {
if (treeData.value) {
@ -388,7 +410,7 @@ export default defineComponent({
allowDrop,
flattenNodes.value,
keyEntities.value,
expandedKeys.value,
expandedKeysSet.value,
direction,
);
@ -485,7 +507,7 @@ export default defineComponent({
allowDrop,
flattenNodes.value,
keyEntities.value,
expandedKeys.value,
expandedKeysSet.value,
direction,
);
@ -558,7 +580,6 @@ export default defineComponent({
const onNodeDrop = (event: DragEvent, _node, outsideTree = false) => {
const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
dragState;
if (!dropAllowed) return;
const { onDrop } = props;
@ -735,11 +756,7 @@ export default defineComponent({
// We need to get the latest state of loading/loaded keys
const { loadData, onLoad } = props;
if (
!loadData ||
loadedKeys.value.indexOf(key) !== -1 ||
loadingKeys.value.indexOf(key) !== -1
) {
if (!loadData || loadedKeysSet.value.has(key) || loadingKeysSet.value.has(key)) {
return null;
}
@ -977,7 +994,7 @@ export default defineComponent({
// >>> Expand
case KeyCode.LEFT: {
// Collapse if possible
if (expandable && expandedKeys.value.includes(activeKey.value)) {
if (expandable && expandedKeysSet.value.has(activeKey.value)) {
onNodeExpand({} as MouseEvent, eventNode);
} else if (item.parent) {
onActiveChange(item.parent.key);
@ -987,7 +1004,7 @@ export default defineComponent({
}
case KeyCode.RIGHT: {
// Expand if possible
if (expandable && !expandedKeys.value.includes(activeKey.value)) {
if (expandable && !expandedKeysSet.value.has(activeKey.value)) {
onNodeExpand({} as MouseEvent, eventNode);
} else if (item.children && item.children.length) {
onActiveChange(item.children[0].key);
@ -1005,11 +1022,7 @@ export default defineComponent({
eventNode.checkable !== false &&
!eventNode.disableCheckbox
) {
onNodeCheck(
{} as MouseEvent,
eventNode,
!checkedKeys.value.includes(activeKey.value),
);
onNodeCheck({} as MouseEvent, eventNode, !checkedKeysSet.value.has(activeKey.value));
} else if (
!checkable &&
selectable &&
@ -1042,6 +1055,21 @@ export default defineComponent({
window.removeEventListener('dragend', onWindowDragEnd);
destroyed.value = true;
});
useProvideKeysState({
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
expandedKeysSet,
selectedKeysSet,
loadedKeysSet,
loadingKeysSet,
checkedKeysSet,
halfCheckedKeysSet,
flattenNodes,
});
return () => {
const {
// focused,
@ -1123,6 +1151,7 @@ export default defineComponent({
dropTargetKey,
dropPosition,
dragOverNodeKey,
dragging: draggingNodeKey !== null,
indent: indent.value,
direction,
dropIndicatorRender,
@ -1160,12 +1189,10 @@ export default defineComponent({
ref={listRef}
prefixCls={prefixCls}
style={style}
data={flattenNodes.value}
disabled={disabled}
selectable={selectable}
checkable={!!checkable}
motion={motion}
dragging={draggingNodeKey !== null}
height={height}
itemHeight={itemHeight}
virtual={virtual}
@ -1181,7 +1208,6 @@ export default defineComponent({
onListChangeEnd={onListChangeEnd}
onContextmenu={onContextmenu}
onScroll={onScroll}
{...treeNodeRequiredProps.value}
{...domProps}
/>
</div>

View File

@ -1,6 +1,6 @@
import { useInjectTreeContext } from './contextTypes';
import { useInjectKeysState, useInjectTreeContext } from './contextTypes';
import Indent from './Indent';
import { convertNodePropsToEventData } from './utils/treeUtil';
import { convertNodePropsToEventData, getTreeNodeProps } from './utils/treeUtil';
import {
computed,
defineComponent,
@ -14,8 +14,8 @@ import { treeNodeProps } from './props';
import classNames from '../_util/classNames';
import { warning } from '../vc-util/warning';
import type { DragNodeEvent, Key } from './interface';
import pick from 'lodash-es/pick';
import pickAttrs from '../_util/pickAttrs';
import eagerComputed from '../_util/eagerComputed';
const ICON_OPEN = 'open';
const ICON_CLOSE = 'close';
@ -35,8 +35,43 @@ export default defineComponent({
key => '`v-slot:' + key + '` ',
)}instead`,
);
const dragNodeHighlight = ref(false);
const context = useInjectTreeContext();
const {
expandedKeysSet,
selectedKeysSet,
loadedKeysSet,
loadingKeysSet,
checkedKeysSet,
halfCheckedKeysSet,
} = useInjectKeysState();
const { dragOverNodeKey, dropPosition, keyEntities } = context.value;
const mergedTreeNodeProps = computed(() => {
return getTreeNodeProps(props.eventKey, {
expandedKeysSet: expandedKeysSet.value,
selectedKeysSet: selectedKeysSet.value,
loadedKeysSet: loadedKeysSet.value,
loadingKeysSet: loadingKeysSet.value,
checkedKeysSet: checkedKeysSet.value,
halfCheckedKeysSet: halfCheckedKeysSet.value,
dragOverNodeKey,
dropPosition,
keyEntities,
});
});
const expanded = eagerComputed(() => mergedTreeNodeProps.value.expanded);
const selected = eagerComputed(() => mergedTreeNodeProps.value.selected);
const checked = eagerComputed(() => mergedTreeNodeProps.value.checked);
const loaded = eagerComputed(() => mergedTreeNodeProps.value.loaded);
const loading = eagerComputed(() => mergedTreeNodeProps.value.loading);
const halfChecked = eagerComputed(() => mergedTreeNodeProps.value.halfChecked);
const dragOver = eagerComputed(() => mergedTreeNodeProps.value.dragOver);
const dragOverGapTop = eagerComputed(() => mergedTreeNodeProps.value.dragOverGapTop);
const dragOverGapBottom = eagerComputed(() => mergedTreeNodeProps.value.dragOverGapBottom);
const pos = eagerComputed(() => mergedTreeNodeProps.value.pos);
const selectHandle = ref();
const hasChildren = computed(() => {
@ -48,7 +83,7 @@ export default defineComponent({
});
const isLeaf = computed(() => {
const { isLeaf, loaded } = props;
const { isLeaf } = props;
const { loadData } = context.value;
const has = hasChildren.value;
@ -57,16 +92,14 @@ export default defineComponent({
return false;
}
return isLeaf || (!loadData && !has) || (loadData && loaded && !has);
return isLeaf || (!loadData && !has) || (loadData && loaded.value && !has);
});
const nodeState = computed(() => {
const { expanded } = props;
if (isLeaf.value) {
return null;
}
return expanded ? ICON_OPEN : ICON_CLOSE;
return expanded.value ? ICON_OPEN : ICON_CLOSE;
});
const isDisabled = computed(() => {
@ -97,24 +130,22 @@ export default defineComponent({
return treeSelectable;
});
const renderArgsData = computed(() => {
const { data, active, checkable, disableCheckbox, disabled, selectable } = props;
return {
...pick(props, [
'active',
'checkable',
'checked',
'disableCheckbox',
'disabled',
'expanded',
'isLeaf',
'loading',
'selectable',
'selected',
'halfChecked',
]),
...props.data,
dataRef: props.data,
data: props.data,
active,
checkable,
disableCheckbox,
disabled,
selectable,
...data,
dataRef: data,
data,
isLeaf: isLeaf.value,
checked: checked.value,
expanded: expanded.value,
loading: loading.value,
selected: selected.value,
halfChecked: halfChecked.value,
};
});
const instance = getCurrentInstance();
@ -122,13 +153,16 @@ export default defineComponent({
const { eventKey } = props;
const { keyEntities } = context.value;
const { parent } = keyEntities[eventKey] || {};
return { ...convertNodePropsToEventData(props), parent };
return {
...convertNodePropsToEventData(Object.assign({}, props, mergedTreeNodeProps.value)),
parent,
};
});
const dragNodeEvent: DragNodeEvent = reactive({
eventData,
eventKey: computed(() => props.eventKey),
selectHandle,
pos: computed(() => props.pos),
pos,
key: instance.vnode.key as Key,
});
expose(dragNodeEvent);
@ -148,13 +182,13 @@ export default defineComponent({
const onCheck = (e: MouseEvent) => {
if (isDisabled.value) return;
const { disableCheckbox, checked } = props;
const { disableCheckbox } = props;
const { onNodeCheck } = context.value;
if (!isCheckable.value || disableCheckbox) return;
e.preventDefault();
const targetChecked = !checked;
const targetChecked = !checked.value;
onNodeCheck(e, eventData.value, targetChecked);
};
@ -244,7 +278,7 @@ export default defineComponent({
// Disabled item still can be switch
const onExpand = e => {
const { onNodeExpand } = context.value;
if (props.loading) return;
if (loading.value) return;
onNodeExpand(e, eventData.value);
};
@ -279,18 +313,18 @@ export default defineComponent({
// Load data to avoid default expanded tree without data
const syncLoadData = () => {
const { expanded, loading, loaded } = props;
//const { expanded, loading, loaded } = props;
const { loadData, onNodeLoad } = context.value;
if (loading) {
if (loading.value) {
return;
}
// read from state to avoid loadData at same time
if (loadData && expanded && !isLeaf.value) {
if (loadData && expanded.value && !isLeaf.value) {
// We needn't reload data when has children in sync logic
// It's only needed in node expanded
if (!hasChildren.value && !loaded) {
if (!hasChildren.value && !loaded.value) {
onNodeLoad(eventData.value);
}
}
@ -306,7 +340,6 @@ export default defineComponent({
// Switcher
const renderSwitcher = () => {
const { expanded } = props;
const { prefixCls } = context.value;
// if switcherIconDom is null, no render switcher span
const switcherIconDom = renderSwitcherIconDom();
@ -320,7 +353,7 @@ export default defineComponent({
const switcherCls = classNames(
`${prefixCls}-switcher`,
`${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
`${prefixCls}-switcher_${expanded.value ? ICON_OPEN : ICON_CLOSE}`,
);
return switcherIconDom !== false ? (
@ -332,7 +365,7 @@ export default defineComponent({
// Checkbox
const renderCheckbox = () => {
const { checked, halfChecked, disableCheckbox } = props;
const { disableCheckbox } = props;
const { prefixCls } = context.value;
const disabled = isDisabled.value;
@ -344,8 +377,8 @@ export default defineComponent({
<span
class={classNames(
`${prefixCls}-checkbox`,
checked && `${prefixCls}-checkbox-checked`,
!checked && halfChecked && `${prefixCls}-checkbox-indeterminate`,
checked.value && `${prefixCls}-checkbox-checked`,
!checked.value && halfChecked.value && `${prefixCls}-checkbox-indeterminate`,
(disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`,
)}
onClick={onCheck}
@ -356,7 +389,6 @@ export default defineComponent({
};
const renderIcon = () => {
const { loading } = props;
const { prefixCls } = context.value;
return (
@ -364,7 +396,7 @@ export default defineComponent({
class={classNames(
`${prefixCls}-iconEle`,
`${prefixCls}-icon__${nodeState.value || 'docu'}`,
loading && `${prefixCls}-icon_loading`,
loading.value && `${prefixCls}-icon_loading`,
)}
/>
);
@ -396,9 +428,9 @@ export default defineComponent({
// title = slots.title ||
// context.value.slots?.[props.data?.slots?.title] ||
// context.value.slots?.title,
selected,
// selected,
icon = slots.icon,
loading,
// loading,
data,
} = props;
const title =
@ -430,7 +462,7 @@ export default defineComponent({
) : (
renderIcon()
);
} else if (loadData && loading) {
} else if (loadData && loading.value) {
$icon = renderIcon();
}
@ -454,7 +486,9 @@ export default defineComponent({
class={classNames(
`${wrapClass}`,
`${wrapClass}-${nodeState.value || 'normal'}`,
!disabled && (selected || dragNodeHighlight.value) && `${prefixCls}-node-selected`,
!disabled &&
(selected.value || dragNodeHighlight.value) &&
`${prefixCls}-node-selected`,
)}
onMouseenter={onMouseEnter}
onMouseleave={onMouseLeave}
@ -471,17 +505,9 @@ export default defineComponent({
return () => {
const {
eventKey,
dragOver,
dragOverGapTop,
dragOverGapBottom,
isLeaf,
isStart,
isEnd,
expanded,
selected,
checked,
halfChecked,
loading,
domRef,
active,
data,
@ -507,17 +533,17 @@ export default defineComponent({
const dragging = draggingNodeKey === eventKey;
const ariaSelected = selectable !== undefined ? { 'aria-selected': !!selectable } : undefined;
// console.log(1);
return (
<div
ref={domRef}
class={classNames(attrs.class, `${prefixCls}-treenode`, {
[`${prefixCls}-treenode-disabled`]: disabled,
[`${prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
[`${prefixCls}-treenode-checkbox-checked`]: checked,
[`${prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
[`${prefixCls}-treenode-selected`]: selected,
[`${prefixCls}-treenode-loading`]: loading,
[`${prefixCls}-treenode-switcher-${expanded.value ? 'open' : 'close'}`]: !isLeaf,
[`${prefixCls}-treenode-checkbox-checked`]: checked.value,
[`${prefixCls}-treenode-checkbox-indeterminate`]: halfChecked.value,
[`${prefixCls}-treenode-selected`]: selected.value,
[`${prefixCls}-treenode-loading`]: loading.value,
[`${prefixCls}-treenode-active`]: active,
[`${prefixCls}-treenode-leaf-last`]: isEndNode,
[`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
@ -525,9 +551,9 @@ export default defineComponent({
dragging,
'drop-target': dropTargetKey === eventKey,
'drop-container': dropContainerKey === eventKey,
'drag-over': !disabled && dragOver,
'drag-over-gap-top': !disabled && dragOverGapTop,
'drag-over-gap-bottom': !disabled && dragOverGapBottom,
'drag-over': !disabled && dragOver.value,
'drag-over-gap-top': !disabled && dragOverGapTop.value,
'drag-over-gap-bottom': !disabled && dragOverGapBottom.value,
'filter-node': filterTreeNode && filterTreeNode(eventData.value),
})}
style={attrs.style}

View File

@ -3,8 +3,8 @@
* When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
*/
import type { ComputedRef, InjectionKey, PropType } from 'vue';
import { inject, computed, defineComponent, provide } from 'vue';
import type { ComputedRef, InjectionKey, PropType, ShallowRef } from 'vue';
import { shallowRef, inject, computed, defineComponent, provide } from 'vue';
import type { VueNode } from '../_util/type';
import type {
IconType,
@ -13,6 +13,7 @@ import type {
EventDataNode,
DragNodeEvent,
Direction,
FlattenNode,
} from './interface';
import type { DraggableConfig } from './Tree';
@ -60,6 +61,7 @@ export interface TreeContextProps {
direction: Direction;
}) => VueNode;
dragOverNodeKey: Key | null;
dragging: boolean;
direction: Direction;
loadData: (treeNode: EventDataNode) => Promise<void>;
@ -108,3 +110,40 @@ export const useInjectTreeContext = () => {
computed(() => ({} as TreeContextProps)),
);
};
type KeysStateKeyType = {
expandedKeysSet: ComputedRef<Set<Key>>;
selectedKeysSet: ComputedRef<Set<Key>>;
loadedKeysSet: ComputedRef<Set<Key>>;
loadingKeysSet: ComputedRef<Set<Key>>;
checkedKeysSet: ComputedRef<Set<Key>>;
halfCheckedKeysSet: ComputedRef<Set<Key>>;
expandedKeys: ShallowRef<Key[]>;
selectedKeys: ShallowRef<Key[]>;
loadedKeys: ShallowRef<Key[]>;
loadingKeys: ShallowRef<Key[]>;
checkedKeys: ShallowRef<Key[]>;
halfCheckedKeys: ShallowRef<Key[]>;
flattenNodes: ShallowRef<FlattenNode[]>;
};
const KeysStateKey: InjectionKey<KeysStateKeyType> = Symbol('KeysStateKey');
export const useProvideKeysState = (state: KeysStateKeyType) => {
provide(KeysStateKey, state);
};
export const useInjectKeysState = () => {
return inject(KeysStateKey, {
expandedKeys: shallowRef<Key[]>([]),
selectedKeys: shallowRef<Key[]>([]),
loadedKeys: shallowRef<Key[]>([]),
loadingKeys: shallowRef<Key[]>([]),
checkedKeys: shallowRef<Key[]>([]),
halfCheckedKeys: shallowRef<Key[]>([]),
expandedKeysSet: computed<Set<Key>>(() => new Set()),
selectedKeysSet: computed<Set<Key>>(() => new Set()),
loadedKeysSet: computed<Set<Key>>(() => new Set()),
loadingKeysSet: computed<Set<Key>>(() => new Set()),
checkedKeysSet: computed<Set<Key>>(() => new Set()),
halfCheckedKeysSet: computed<Set<Key>>(() => new Set()),
flattenNodes: shallowRef<FlattenNode[]>([]),
});
};

View File

@ -7,15 +7,7 @@ import type {
NodeMouseEventHandler,
NodeMouseEventParams,
} from './contextTypes';
import type {
DataNode,
Key,
FlattenNode,
EventDataNode,
Direction,
FieldNames,
DataEntity,
} from './interface';
import type { DataNode, Key, FlattenNode, EventDataNode, Direction, FieldNames } from './interface';
export interface CheckInfo {
event: 'check';
@ -32,18 +24,18 @@ export const treeNodeProps = {
prefixCls: String,
// By parent
expanded: { type: Boolean, default: undefined },
selected: { type: Boolean, default: undefined },
checked: { type: Boolean, default: undefined },
loaded: { type: Boolean, default: undefined },
loading: { type: Boolean, default: undefined },
halfChecked: { type: Boolean, default: undefined },
title: PropTypes.any,
dragOver: { type: Boolean, default: undefined },
dragOverGapTop: { type: Boolean, default: undefined },
dragOverGapBottom: { type: Boolean, default: undefined },
pos: String,
// expanded: { type: Boolean, default: undefined },
// selected: { type: Boolean, default: undefined },
// checked: { type: Boolean, default: undefined },
// loaded: { type: Boolean, default: undefined },
// loading: { type: Boolean, default: undefined },
// halfChecked: { type: Boolean, default: undefined },
// dragOver: { type: Boolean, default: undefined },
// dragOverGapTop: { type: Boolean, default: undefined },
// dragOverGapBottom: { type: Boolean, default: undefined },
// pos: String,
title: PropTypes.any,
/** New added in Tree for easy data access */
data: { type: Object as PropType<DataNode>, default: undefined as DataNode },
parent: { type: Object as PropType<DataNode>, default: undefined as DataNode },
@ -68,7 +60,7 @@ export type TreeNodeProps = Partial<ExtractPropTypes<typeof treeNodeProps>>;
export const nodeListProps = {
prefixCls: { type: String as PropType<string> },
data: { type: Array as PropType<FlattenNode[]> },
// data: { type: Array as PropType<FlattenNode[]> },
motion: { type: Object as PropType<any> },
focusable: { type: Boolean as PropType<boolean> },
activeItem: { type: Object as PropType<FlattenNode> },
@ -78,17 +70,17 @@ export const nodeListProps = {
selectable: { type: Boolean as PropType<boolean> },
disabled: { type: Boolean as PropType<boolean> },
expandedKeys: { type: Array as PropType<Key[]> },
selectedKeys: { type: Array as PropType<Key[]> },
checkedKeys: { type: Array as PropType<Key[]> },
loadedKeys: { type: Array as PropType<Key[]> },
loadingKeys: { type: Array as PropType<Key[]> },
halfCheckedKeys: { type: Array as PropType<Key[]> },
keyEntities: { type: Object as PropType<Record<Key, DataEntity<DataNode>>> },
// expandedKeys: { type: Array as PropType<Key[]> },
// selectedKeys: { type: Array as PropType<Key[]> },
// checkedKeys: { type: Array as PropType<Key[]> },
// loadedKeys: { type: Array as PropType<Key[]> },
// loadingKeys: { type: Array as PropType<Key[]> },
// halfCheckedKeys: { type: Array as PropType<Key[]> },
// keyEntities: { type: Object as PropType<Record<Key, DataEntity<DataNode>>> },
dragging: { type: Boolean as PropType<boolean> },
dragOverNodeKey: { type: [String, Number] as PropType<Key> },
dropPosition: { type: Number as PropType<number> },
// dragging: { type: Boolean as PropType<boolean> },
// dragOverNodeKey: { type: [String, Number] as PropType<Key> },
// dropPosition: { type: Number as PropType<number> },
// Virtual list
height: { type: Number as PropType<number> },

View File

@ -98,7 +98,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
allowDrop: AllowDrop<TreeDataType>,
flattenedNodes: FlattenNode[],
keyEntities: Record<Key, DataEntity<TreeDataType>>,
expandKeys: Key[],
expandKeysSet: Set<Key>,
direction: Direction,
): {
dropPosition: -1 | 0 | 1;
@ -138,7 +138,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
let dropLevelOffset = 0;
// Only allow cross level drop when dragging on a non-expanded node
if (!expandKeys.includes(initialAbstractDropNodeKey)) {
if (!expandKeysSet.has(initialAbstractDropNodeKey)) {
for (let i = 0; i < rawDropLevelOffset; i += 1) {
if (isLastChild(abstractDropNodeEntity)) {
abstractDropNodeEntity = abstractDropNodeEntity.parent;
@ -164,10 +164,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
) {
// first half of first node in first level
dropPosition = -1;
} else if (
(abstractDragOverEntity.children || []).length &&
expandKeys.includes(dragOverNodeKey)
) {
} else if ((abstractDragOverEntity.children || []).length && expandKeysSet.has(dragOverNodeKey)) {
// drop on expanded node
// only allow drop inside
if (

View File

@ -361,12 +361,12 @@ export function convertDataToEntities(
}
export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
expandedKeys: Key[];
selectedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
checkedKeys: Key[];
halfCheckedKeys: Key[];
expandedKeysSet: Set<Key>;
selectedKeysSet: Set<Key>;
loadedKeysSet: Set<Key>;
loadingKeysSet: Set<Key>;
checkedKeysSet: Set<Key>;
halfCheckedKeysSet: Set<Key>;
dragOverNodeKey: Key;
dropPosition: number;
keyEntities: Record<Key, DataEntity<TreeDataType>>;
@ -378,12 +378,12 @@ export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = Data
export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
key: Key,
{
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
expandedKeysSet,
selectedKeysSet,
loadedKeysSet,
loadingKeysSet,
checkedKeysSet,
halfCheckedKeysSet,
dragOverNodeKey,
dropPosition,
keyEntities,
@ -393,12 +393,12 @@ export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
const treeNodeProps = {
eventKey: key,
expanded: expandedKeys.indexOf(key) !== -1,
selected: selectedKeys.indexOf(key) !== -1,
loaded: loadedKeys.indexOf(key) !== -1,
loading: loadingKeys.indexOf(key) !== -1,
checked: checkedKeys.indexOf(key) !== -1,
halfChecked: halfCheckedKeys.indexOf(key) !== -1,
expanded: expandedKeysSet.has(key),
selected: selectedKeysSet.has(key),
loaded: loadedKeysSet.has(key),
loading: loadingKeysSet.has(key),
checked: checkedKeysSet.has(key),
halfChecked: halfCheckedKeysSet.has(key),
pos: String(entity ? entity.pos : ''),
parent: entity.parent,
// [Legacy] Drag props
@ -412,7 +412,9 @@ export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
return treeNodeProps;
}
export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode {
export function convertNodePropsToEventData(
props: TreeNodeProps & ReturnType<typeof getTreeNodeProps>,
): EventDataNode {
const {
data,
expanded,