feat: tree

refactor-cascader
tangjinzhou 2022-01-06 22:10:23 +08:00
parent 98755f332c
commit 54cdc3ff40
12 changed files with 305 additions and 140 deletions

View File

@ -6,6 +6,7 @@ import { computed, defineComponent, ref, shallowRef, watch } from 'vue';
import VirtualList from '../vc-virtual-list'; import VirtualList from '../vc-virtual-list';
import type { FlattenNode, DataEntity, DataNode, ScrollTo } from './interface'; import type { FlattenNode, DataEntity, DataNode, ScrollTo } from './interface';
import MotionTreeNode from './MotionTreeNode'; import MotionTreeNode from './MotionTreeNode';
import type { NodeListProps } from './props';
import { nodeListProps } from './props'; import { nodeListProps } from './props';
import { findExpandedKeys, getExpandRange } from './utils/diffUtil'; import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
import { getTreeNodeProps, getKey } from './utils/treeUtil'; import { getTreeNodeProps, getKey } from './utils/treeUtil';
@ -35,6 +36,7 @@ export const MotionEntity: DataEntity = {
index: 0, index: 0,
pos: '0', pos: '0',
node: MotionNode, node: MotionNode,
nodes: [MotionNode],
}; };
const MotionFlattenData: FlattenNode = { const MotionFlattenData: FlattenNode = {
@ -208,7 +210,7 @@ export default defineComponent({
onListChangeEnd, onListChangeEnd,
...domProps ...domProps
} = { ...props, ...attrs }; } = { ...props, ...attrs } as NodeListProps;
const treeNodeRequiredProps = { const treeNodeRequiredProps = {
expandedKeys, expandedKeys,
@ -269,6 +271,15 @@ export default defineComponent({
itemHeight={itemHeight} itemHeight={itemHeight}
prefixCls={`${prefixCls}-list`} prefixCls={`${prefixCls}-list`}
ref={listRef} ref={listRef}
onVisibleChange={(originList, fullList) => {
const originSet = new Set(originList);
const restList = fullList.filter(item => !originSet.has(item));
// Motion node is not render. Skip motion
if (restList.some(item => itemKey(item) === MOTION_KEY)) {
onMotionEnd();
}
}}
v-slots={{ v-slots={{
default: (treeNode: FlattenNode) => { default: (treeNode: FlattenNode) => {
const { const {

View File

@ -1,7 +1,6 @@
import type { NodeMouseEventHandler, NodeDragEventHandler } from './contextTypes'; import type { NodeMouseEventHandler, NodeDragEventHandler } from './contextTypes';
import { TreeContext } from './contextTypes'; import { TreeContext } from './contextTypes';
import { import {
getDataAndAria,
getDragChildrenKeys, getDragChildrenKeys,
parseCheckedKeys, parseCheckedKeys,
conductExpandParent, conductExpandParent,
@ -34,11 +33,19 @@ import {
watchEffect, watchEffect,
} from 'vue'; } from 'vue';
import initDefaultProps from '../_util/props-util/initDefaultProps'; import initDefaultProps from '../_util/props-util/initDefaultProps';
import type { CheckInfo } from './props'; import type { CheckInfo, DraggableFn } from './props';
import { treeProps } from './props'; import { treeProps } from './props';
import { warning } from '../vc-util/warning'; import { warning } from '../vc-util/warning';
import KeyCode from '../_util/KeyCode'; import KeyCode from '../_util/KeyCode';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import pickAttrs from '../_util/pickAttrs';
const MAX_RETRY_TIMES = 10;
export type DraggableConfig = {
icon?: any;
nodeDraggable?: DraggableFn;
};
export default defineComponent({ export default defineComponent({
name: 'Tree', name: 'Tree',
@ -74,9 +81,9 @@ export default defineComponent({
const loadedKeys = shallowRef([]); const loadedKeys = shallowRef([]);
const loadingKeys = shallowRef([]); const loadingKeys = shallowRef([]);
const expandedKeys = shallowRef([]); const expandedKeys = shallowRef([]);
const loadingRetryTimes: Record<Key, number> = {};
const dragState = reactive({ const dragState = reactive({
dragging: false, draggingNodeKey: null,
dragChildrenKeys: [], dragChildrenKeys: [],
// dropTargetKey is the key of abstract-drop-node // dropTargetKey is the key of abstract-drop-node
@ -111,6 +118,8 @@ export default defineComponent({
let dragNode: DragNodeEvent = null; let dragNode: DragNodeEvent = null;
let currentMouseOverDroppableNodeKey = null;
const treeNodeRequiredProps = computed(() => { const treeNodeRequiredProps = computed(() => {
return { return {
expandedKeys: expandedKeys.value || [], expandedKeys: expandedKeys.value || [],
@ -219,6 +228,17 @@ export default defineComponent({
} }
}); });
const resetDragState = () => {
Object.assign(dragState, {
dragOverNodeKey: null,
dropPosition: null,
dropLevelOffset: null,
dropTargetKey: null,
dropContainerKey: null,
dropTargetPos: null,
dropAllowed: false,
});
};
const scrollTo: ScrollTo = scroll => { const scrollTo: ScrollTo = scroll => {
listRef.value.scrollTo(scroll); listRef.value.scrollTo(scroll);
}; };
@ -231,9 +251,9 @@ export default defineComponent({
}; };
const cleanDragState = () => { const cleanDragState = () => {
if (dragState.dragging) { if (dragState.draggingNodeKey !== null) {
Object.assign(dragState, { Object.assign(dragState, {
dragging: false, draggingNodeKey: null,
dropPosition: null, dropPosition: null,
dropContainerKey: null, dropContainerKey: null,
dropTargetKey: null, dropTargetKey: null,
@ -243,6 +263,7 @@ export default defineComponent({
}); });
} }
dragStartMousePosition = null; dragStartMousePosition = null;
currentMouseOverDroppableNodeKey = null;
}; };
// if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called // if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called
const onNodeDragEnd: NodeDragEventHandler = (event, node, outsideTree = false) => { const onNodeDragEnd: NodeDragEventHandler = (event, node, outsideTree = false) => {
@ -277,7 +298,7 @@ export default defineComponent({
const newExpandedKeys = arrDel(expandedKeys.value, eventKey); const newExpandedKeys = arrDel(expandedKeys.value, eventKey);
dragState.dragging = true; dragState.draggingNodeKey = eventKey;
dragState.dragChildrenKeys = getDragChildrenKeys(eventKey, keyEntities.value); dragState.dragChildrenKeys = getDragChildrenKeys(eventKey, keyEntities.value);
indent.value = listRef.value.getIndentWidth(); indent.value = listRef.value.getIndentWidth();
@ -296,9 +317,18 @@ export default defineComponent({
* Better for use mouse move event to refresh drag state. * Better for use mouse move event to refresh drag state.
* But let's just keep it to avoid event trigger logic change. * But let's just keep it to avoid event trigger logic change.
*/ */
const onNodeDragEnter = (event: MouseEvent, node: DragNodeEvent) => { const onNodeDragEnter = (event: DragEvent, node: DragNodeEvent) => {
const { onDragenter, onExpand, allowDrop, direction } = props; const { onDragenter, onExpand, allowDrop, direction } = props;
const { pos, eventKey } = node;
// record the key of node which is latest entered, used in dragleave event.
if (currentMouseOverDroppableNodeKey !== eventKey) {
currentMouseOverDroppableNodeKey = eventKey;
}
if (!dragNode) {
resetDragState();
return;
}
const { const {
dropPosition, dropPosition,
dropLevelOffset, dropLevelOffset,
@ -321,21 +351,12 @@ export default defineComponent({
); );
if ( if (
!dragNode ||
// don't allow drop inside its children // don't allow drop inside its children
dragState.dragChildrenKeys.indexOf(dropTargetKey) !== -1 || dragState.dragChildrenKeys.indexOf(dropTargetKey) !== -1 ||
// don't allow drop when drop is not allowed caculated by calcDropPosition // don't allow drop when drop is not allowed caculated by calcDropPosition
!dropAllowed !dropAllowed
) { ) {
Object.assign(dragState, { resetDragState();
dragOverNodeKey: null,
dropPosition: null,
dropLevelOffset: null,
dropTargetKey: null,
dropContainerKey: null,
dropTargetPos: null,
dropAllowed: false,
});
return; return;
} }
@ -352,8 +373,8 @@ export default defineComponent({
// since if logic is on the bottom // since if logic is on the bottom
// it will be blocked by abstract dragover node check // it will be blocked by abstract dragover node check
// => if you dragenter from top, you mouse will still be consider as in the top node // => if you dragenter from top, you mouse will still be consider as in the top node
delayedDragEnterLogic[node.pos] = window.setTimeout(() => { delayedDragEnterLogic[pos] = window.setTimeout(() => {
if (!dragState.dragging) return; if (dragState.draggingNodeKey === null) return;
let newExpandedKeys = [...expandedKeys.value]; let newExpandedKeys = [...expandedKeys.value];
const entity = keyEntities.value[node.eventKey]; const entity = keyEntities.value[node.eventKey];
@ -375,15 +396,7 @@ export default defineComponent({
// Skip if drag node is self // Skip if drag node is self
if (dragNode.eventKey === dropTargetKey && dropLevelOffset === 0) { if (dragNode.eventKey === dropTargetKey && dropLevelOffset === 0) {
Object.assign(dragState, { resetDragState();
dragOverNodeKey: null,
dropPosition: null,
dropLevelOffset: null,
dropTargetKey: null,
dropContainerKey: null,
dropTargetPos: null,
dropAllowed: false,
});
return; return;
} }
@ -407,9 +420,12 @@ export default defineComponent({
} }
}; };
const onNodeDragOver = (event: MouseEvent, node: DragNodeEvent) => { const onNodeDragOver = (event: DragEvent, node: DragNodeEvent) => {
const { onDragover, allowDrop, direction } = props; const { onDragover, allowDrop, direction } = props;
if (!dragNode) {
return;
}
const { const {
dropPosition, dropPosition,
dropLevelOffset, dropLevelOffset,
@ -431,7 +447,7 @@ export default defineComponent({
direction, direction,
); );
if (!dragNode || dragState.dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) { if (dragState.dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) {
// don't allow drop inside its children // don't allow drop inside its children
// don't allow drop when drop is not allowed caculated by calcDropPosition // don't allow drop when drop is not allowed caculated by calcDropPosition
return; return;
@ -451,15 +467,7 @@ export default defineComponent({
dragState.dragOverNodeKey === null dragState.dragOverNodeKey === null
) )
) { ) {
Object.assign(dragState, { resetDragState();
dropPosition: null,
dropLevelOffset: null,
dropTargetKey: null,
dropContainerKey: null,
dropTargetPos: null,
dropAllowed: false,
dragOverNodeKey: null,
});
} }
} else if ( } else if (
!( !(
@ -489,13 +497,23 @@ export default defineComponent({
}; };
const onNodeDragLeave: NodeDragEventHandler = (event, node) => { const onNodeDragLeave: NodeDragEventHandler = (event, node) => {
// if it is outside the droppable area
// currentMouseOverDroppableNodeKey will be updated in dragenter event when into another droppable receiver.
if (
currentMouseOverDroppableNodeKey === node.eventKey &&
!(event.currentTarget as any).contains(event.relatedTarget as Node)
) {
resetDragState();
currentMouseOverDroppableNodeKey = null;
}
const { onDragleave } = props; const { onDragleave } = props;
if (onDragleave) { if (onDragleave) {
onDragleave({ event, node: node.eventData }); onDragleave({ event, node: node.eventData });
} }
}; };
const onNodeDrop = (event: MouseEvent, _node, outsideTree = false) => { const onNodeDrop = (event: DragEvent, _node, outsideTree = false) => {
const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } = const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
dragState; dragState;
@ -666,11 +684,11 @@ export default defineComponent({
} }
}; };
const onNodeLoad = (treeNode: EventDataNode) => const onNodeLoad = (treeNode: EventDataNode) => {
new Promise<void>((resolve, reject) => { const key = treeNode[fieldNames.value.key];
const loadPromise = new Promise<void>((resolve, reject) => {
// We need to get the latest state of loading/loaded keys // We need to get the latest state of loading/loaded keys
const { loadData, onLoad } = props; const { loadData, onLoad } = props;
const key = treeNode[fieldNames.value.key];
if ( if (
!loadData || !loadData ||
@ -705,12 +723,28 @@ export default defineComponent({
.catch(e => { .catch(e => {
const newLoadingKeys = arrDel(loadingKeys.value, key); const newLoadingKeys = arrDel(loadingKeys.value, key);
loadingKeys.value = newLoadingKeys; loadingKeys.value = newLoadingKeys;
// If exceed max retry times, we give up retry
loadingRetryTimes[key] = (loadingRetryTimes[key] || 0) + 1;
if (loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
warning(false, 'Retry for `loadData` many times but still failed. No more retry.');
const newLoadedKeys = arrAdd(loadedKeys.value, key);
if (props.loadedKeys === undefined) {
loadedKeys.value = newLoadedKeys;
}
resolve();
}
reject(e); reject(e);
}); });
loadingKeys.value = arrAdd(loadingKeys.value, key); loadingKeys.value = arrAdd(loadingKeys.value, key);
}); });
// Not care warning if we ignore this
loadPromise.catch(() => {});
return loadPromise;
};
const onNodeMouseEnter: NodeMouseEventHandler = (event, node) => { const onNodeMouseEnter: NodeMouseEventHandler = (event, node) => {
const { onMouseenter } = props; const { onMouseenter } = props;
if (onMouseenter) { if (onMouseenter) {
@ -968,7 +1002,7 @@ export default defineComponent({
// focused, // focused,
// flattenNodes, // flattenNodes,
// keyEntities, // keyEntities,
dragging, draggingNodeKey,
// activeKey, // activeKey,
dropLevelOffset, dropLevelOffset,
dropContainerKey, dropContainerKey,
@ -1003,7 +1037,27 @@ export default defineComponent({
} = props; } = props;
const { class: className, style } = attrs; const { class: className, style } = attrs;
const domProps = getDataAndAria({ ...props, ...attrs }); const domProps = pickAttrs(
{ ...props, ...attrs },
{
aria: true,
data: true,
},
);
// It's better move to hooks but we just simply keep here
let draggableConfig: DraggableConfig;
if (draggable) {
if (typeof draggable === 'object') {
draggableConfig = draggable;
} else if (typeof draggable === 'function') {
draggableConfig = {
nodeDraggable: draggable,
};
} else {
draggableConfig = {};
}
}
return ( return (
<TreeContext <TreeContext
value={{ value={{
@ -1012,7 +1066,8 @@ export default defineComponent({
showIcon, showIcon,
icon, icon,
switcherIcon, switcherIcon,
draggable, draggable: draggableConfig,
draggingNodeKey,
checkable, checkable,
customCheckable: slots.checkable, customCheckable: slots.checkable,
checkStrictly, checkStrictly,
@ -1065,7 +1120,7 @@ export default defineComponent({
selectable={selectable} selectable={selectable}
checkable={!!checkable} checkable={!!checkable}
motion={motion} motion={motion}
dragging={dragging} dragging={draggingNodeKey !== null}
height={height} height={height}
itemHeight={itemHeight} itemHeight={itemHeight}
virtual={virtual} virtual={virtual}

View File

@ -1,5 +1,4 @@
import { useInjectTreeContext } from './contextTypes'; import { useInjectTreeContext } from './contextTypes';
import { getDataAndAria } from './util';
import Indent from './Indent'; import Indent from './Indent';
import { convertNodePropsToEventData } from './utils/treeUtil'; import { convertNodePropsToEventData } from './utils/treeUtil';
import { import {
@ -16,6 +15,7 @@ import classNames from '../_util/classNames';
import { warning } from '../vc-util/warning'; import { warning } from '../vc-util/warning';
import type { DragNodeEvent, Key } from './interface'; import type { DragNodeEvent, Key } from './interface';
import pick from 'lodash-es/pick'; import pick from 'lodash-es/pick';
import pickAttrs from '../_util/pickAttrs';
const ICON_OPEN = 'open'; const ICON_OPEN = 'open';
const ICON_CLOSE = 'close'; const ICON_CLOSE = 'close';
@ -248,6 +248,20 @@ export default defineComponent({
onNodeExpand(e, eventData.value); onNodeExpand(e, eventData.value);
}; };
const isDraggable = () => {
const { data } = props;
const { draggable } = context.value;
return !!(draggable && (!draggable.nodeDraggable || draggable.nodeDraggable(data)));
};
// ==================== Render: Drag Handler ====================
const renderDragHandler = () => {
const { draggable, prefixCls } = context.value;
return draggable?.icon ? (
<span class={`${prefixCls}-draggable-icon`}>{draggable.icon}</span>
) : null;
};
const renderSwitcherIconDom = () => { const renderSwitcherIconDom = () => {
const { const {
switcherIcon: switcherIconFromProps = slots.switcherIcon || switcherIcon: switcherIconFromProps = slots.switcherIcon ||
@ -368,9 +382,9 @@ export default defineComponent({
dragOverNodeKey, dragOverNodeKey,
direction, direction,
} = context.value; } = context.value;
const mergedDraggable = draggable !== false; const rootDraggable = draggable !== false;
// allowDrop is calculated in Tree.tsx, there is no need for calc it here // allowDrop is calculated in Tree.tsx, there is no need for calc it here
const showIndicator = !disabled && mergedDraggable && dragOverNodeKey === eventKey; const showIndicator = !disabled && rootDraggable && dragOverNodeKey === eventKey;
return showIndicator return showIndicator
? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction }) ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction })
: null; : null;
@ -396,12 +410,10 @@ export default defineComponent({
prefixCls, prefixCls,
showIcon, showIcon,
icon: treeIcon, icon: treeIcon,
draggable,
loadData, loadData,
// slots: contextSlots, // slots: contextSlots,
} = context.value; } = context.value;
const disabled = isDisabled.value; const disabled = isDisabled.value;
const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
const wrapClass = `${prefixCls}-node-content-wrapper`; const wrapClass = `${prefixCls}-node-content-wrapper`;
@ -443,16 +455,12 @@ export default defineComponent({
`${wrapClass}`, `${wrapClass}`,
`${wrapClass}-${nodeState.value || 'normal'}`, `${wrapClass}-${nodeState.value || 'normal'}`,
!disabled && (selected || dragNodeHighlight.value) && `${prefixCls}-node-selected`, !disabled && (selected || dragNodeHighlight.value) && `${prefixCls}-node-selected`,
!disabled && mergedDraggable && 'draggable',
)} )}
draggable={(!disabled && mergedDraggable) || undefined}
aria-grabbed={(!disabled && mergedDraggable) || undefined}
onMouseenter={onMouseEnter} onMouseenter={onMouseEnter}
onMouseleave={onMouseLeave} onMouseleave={onMouseLeave}
onContextmenu={onContextmenu} onContextmenu={onContextmenu}
onClick={onSelectorClick} onClick={onSelectorClick}
onDblclick={onSelectorDoubleClick} onDblclick={onSelectorDoubleClick}
onDragstart={mergedDraggable ? onDragStart : undefined}
> >
{$icon} {$icon}
{$title} {$title}
@ -478,15 +486,28 @@ export default defineComponent({
active, active,
data, data,
onMousemove, onMousemove,
selectable,
...otherProps ...otherProps
} = { ...props, ...attrs }; } = { ...props, ...attrs };
const { prefixCls, filterTreeNode, draggable, keyEntities, dropContainerKey, dropTargetKey } = const {
context.value; prefixCls,
filterTreeNode,
keyEntities,
dropContainerKey,
dropTargetKey,
draggingNodeKey,
} = context.value;
const disabled = isDisabled.value; const disabled = isDisabled.value;
const dataOrAriaAttributeProps = getDataAndAria(otherProps); const dataOrAriaAttributeProps = pickAttrs(otherProps, { aria: true, data: true });
const { level } = keyEntities[eventKey] || {}; const { level } = keyEntities[eventKey] || {};
const isEndNode = isEnd[isEnd.length - 1]; const isEndNode = isEnd[isEnd.length - 1];
const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
const mergedDraggable = isDraggable();
const draggableWithoutDisabled = !disabled && mergedDraggable;
const dragging = draggingNodeKey === eventKey;
const ariaSelected = selectable !== undefined ? { 'aria-selected': !!selectable } : undefined;
return ( return (
<div <div
ref={domRef} ref={domRef}
@ -499,7 +520,9 @@ export default defineComponent({
[`${prefixCls}-treenode-loading`]: loading, [`${prefixCls}-treenode-loading`]: loading,
[`${prefixCls}-treenode-active`]: active, [`${prefixCls}-treenode-active`]: active,
[`${prefixCls}-treenode-leaf-last`]: isEndNode, [`${prefixCls}-treenode-leaf-last`]: isEndNode,
[`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
dragging,
'drop-target': dropTargetKey === eventKey, 'drop-target': dropTargetKey === eventKey,
'drop-container': dropContainerKey === eventKey, 'drop-container': dropContainerKey === eventKey,
'drag-over': !disabled && dragOver, 'drag-over': !disabled && dragOver,
@ -508,15 +531,22 @@ export default defineComponent({
'filter-node': filterTreeNode && filterTreeNode(eventData.value), 'filter-node': filterTreeNode && filterTreeNode(eventData.value),
})} })}
style={attrs.style} style={attrs.style}
// Draggable config
draggable={draggableWithoutDisabled}
aria-grabbed={dragging}
onDragstart={draggableWithoutDisabled ? onDragStart : undefined}
// Drop config
onDragenter={mergedDraggable ? onDragEnter : undefined} onDragenter={mergedDraggable ? onDragEnter : undefined}
onDragover={mergedDraggable ? onDragOver : undefined} onDragover={mergedDraggable ? onDragOver : undefined}
onDragleave={mergedDraggable ? onDragLeave : undefined} onDragleave={mergedDraggable ? onDragLeave : undefined}
onDrop={mergedDraggable ? onDrop : undefined} onDrop={mergedDraggable ? onDrop : undefined}
onDragend={mergedDraggable ? onDragEnd : undefined} onDragend={mergedDraggable ? onDragEnd : undefined}
onMousemove={onMousemove} onMousemove={onMousemove}
{...ariaSelected}
{...dataOrAriaAttributeProps} {...dataOrAriaAttributeProps}
> >
<Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} /> <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
{renderDragHandler()}
{renderSwitcher()} {renderSwitcher()}
{renderCheckbox()} {renderCheckbox()}
{renderSelector()} {renderSelector()}

View File

@ -12,22 +12,23 @@ import type {
DataEntity, DataEntity,
EventDataNode, EventDataNode,
DragNodeEvent, DragNodeEvent,
DataNode,
Direction, Direction,
} from './interface'; } from './interface';
import type { DraggableConfig } from './Tree';
export type NodeMouseEventParams = { export type NodeMouseEventParams = {
event: MouseEvent; event: MouseEvent;
node: EventDataNode; node: EventDataNode;
}; };
export type NodeDragEventParams = { export type NodeDragEventParams = {
event: MouseEvent; event: DragEvent;
node: EventDataNode; node: EventDataNode;
}; };
export type NodeMouseEventHandler = (e: MouseEvent, node: EventDataNode) => void; export type NodeMouseEventHandler = (e: MouseEvent, node: EventDataNode) => void;
export type NodeDragEventHandler = ( export type NodeDragEventHandler = (
e: MouseEvent, e: DragEvent,
node: DragNodeEvent, node: DragNodeEvent,
outsideTree?: boolean, outsideTree?: boolean,
) => void; ) => void;
@ -38,12 +39,13 @@ export interface TreeContextProps {
showIcon: boolean; showIcon: boolean;
icon: IconType; icon: IconType;
switcherIcon: IconType; switcherIcon: IconType;
draggable: ((node: DataNode) => boolean) | boolean; draggable: DraggableConfig;
draggingNodeKey?: Key;
checkable: boolean; checkable: boolean;
customCheckable: () => any; customCheckable: () => any;
checkStrictly: boolean; checkStrictly: boolean;
disabled: boolean; disabled: boolean;
keyEntities: Record<Key, DataEntity>; keyEntities: Record<Key, DataEntity<any>>;
// for details see comment in Tree.state (Tree.tsx) // for details see comment in Tree.state (Tree.tsx)
dropLevelOffset?: number; dropLevelOffset?: number;
dropContainerKey: Key | null; dropContainerKey: Key | null;
@ -79,8 +81,8 @@ export interface TreeContextProps {
onNodeDragEnd: NodeDragEventHandler; onNodeDragEnd: NodeDragEventHandler;
onNodeDrop: NodeDragEventHandler; onNodeDrop: NodeDragEventHandler;
slots: { slots: {
title?: (data: DataNode) => any; title?: (data: any) => any;
titleRender?: (data: DataNode) => any; titleRender?: (data: any) => any;
[key: string]: ((...args: any[]) => any) | undefined; [key: string]: ((...args: any[]) => any) | undefined;
}; };
} }

View File

@ -1,7 +1,8 @@
// base rc-tree 5.0.1 // base rc-tree 5.3.7
import type { TreeProps, TreeNodeProps } from './props'; import type { TreeProps, TreeNodeProps } from './props';
import Tree from './Tree'; import Tree from './Tree';
import TreeNode from './TreeNode'; import TreeNode from './TreeNode';
import type { BasicDataNode } from './interface';
export { TreeNode }; export { TreeNode };
export type { TreeProps, TreeNodeProps }; export type { TreeProps, TreeNodeProps, BasicDataNode };
export default Tree; export default Tree;

View File

@ -2,15 +2,13 @@ import type { CSSProperties, VNode } from 'vue';
import type { TreeNodeProps } from './props'; import type { TreeNodeProps } from './props';
export type { ScrollTo } from '../vc-virtual-list/List'; export type { ScrollTo } from '../vc-virtual-list/List';
export interface DataNode { /** For fieldNames, we provides a abstract interface */
export interface BasicDataNode {
checkable?: boolean; checkable?: boolean;
children?: DataNode[];
disabled?: boolean; disabled?: boolean;
disableCheckbox?: boolean; disableCheckbox?: boolean;
icon?: IconType; icon?: IconType;
isLeaf?: boolean; isLeaf?: boolean;
key: string | number;
title?: any;
selectable?: boolean; selectable?: boolean;
switcherIcon?: IconType; switcherIcon?: IconType;
@ -21,6 +19,12 @@ export interface DataNode {
[key: string]: any; [key: string]: any;
} }
export interface DataNode extends BasicDataNode {
children?: DataNode[];
key: string | number;
title?: any;
}
export interface EventDataNode extends DataNode { export interface EventDataNode extends DataNode {
expanded?: boolean; expanded?: boolean;
selected?: boolean; selected?: boolean;
@ -60,10 +64,12 @@ export interface Entity {
children?: Entity[]; children?: Entity[];
} }
export interface DataEntity extends Omit<Entity, 'node' | 'parent' | 'children'> { export interface DataEntity<TreeDataType extends BasicDataNode = DataNode>
node: DataNode; extends Omit<Entity, 'node' | 'parent' | 'children'> {
parent?: DataEntity; node: TreeDataType;
children?: DataEntity[]; nodes: TreeDataType[];
parent?: DataEntity<TreeDataType>;
children?: DataEntity<TreeDataType>[];
level: number; level: number;
} }
@ -86,6 +92,8 @@ export type Direction = 'ltr' | 'rtl' | undefined;
export interface FieldNames { export interface FieldNames {
title?: string; title?: string;
/** @private Internal usage for `vc-tree-select`, safe to remove if no need */
_title?: string[];
key?: string; key?: string;
children?: string; children?: string;
} }

View File

@ -1,4 +1,5 @@
import type { ExtractPropTypes, PropType } from 'vue'; import type { ExtractPropTypes, PropType } from 'vue';
import type { BasicDataNode } from '.';
import type { EventHandler } from '../_util/EventInterface'; import type { EventHandler } from '../_util/EventInterface';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import type { import type {
@ -10,10 +11,10 @@ import type {
DataNode, DataNode,
Key, Key,
FlattenNode, FlattenNode,
DataEntity,
EventDataNode, EventDataNode,
Direction, Direction,
FieldNames, FieldNames,
DataEntity,
} from './interface'; } from './interface';
export interface CheckInfo { export interface CheckInfo {
@ -83,7 +84,7 @@ export const nodeListProps = {
loadedKeys: { type: Array as PropType<Key[]> }, loadedKeys: { type: Array as PropType<Key[]> },
loadingKeys: { type: Array as PropType<Key[]> }, loadingKeys: { type: Array as PropType<Key[]> },
halfCheckedKeys: { type: Array as PropType<Key[]> }, halfCheckedKeys: { type: Array as PropType<Key[]> },
keyEntities: { type: Object as PropType<Record<Key, DataEntity>> }, keyEntities: { type: Object as PropType<Record<Key, DataEntity<DataNode>>> },
dragging: { type: Boolean as PropType<boolean> }, dragging: { type: Boolean as PropType<boolean> },
dragOverNodeKey: { type: [String, Number] as PropType<Key> }, dragOverNodeKey: { type: [String, Number] as PropType<Key> },
@ -106,8 +107,17 @@ export const nodeListProps = {
}; };
export type NodeListProps = Partial<ExtractPropTypes<typeof nodeListProps>>; export type NodeListProps = Partial<ExtractPropTypes<typeof nodeListProps>>;
export type AllowDrop = (options: { dropNode: DataNode; dropPosition: -1 | 0 | 1 }) => boolean;
export interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
dragNode: EventDataNode;
dropNode: TreeDataType;
dropPosition: -1 | 0 | 1;
}
export type AllowDrop<TreeDataType extends BasicDataNode = DataNode> = (
options: AllowDropOptions<TreeDataType>,
) => boolean;
export type DraggableFn = (node: DataNode) => boolean;
export const treeProps = () => ({ export const treeProps = () => ({
prefixCls: String, prefixCls: String,
focusable: { type: Boolean, default: undefined }, focusable: { type: Boolean, default: undefined },
@ -123,7 +133,7 @@ export const treeProps = () => ({
multiple: { type: Boolean, default: undefined }, multiple: { type: Boolean, default: undefined },
checkable: { type: Boolean, default: undefined }, checkable: { type: Boolean, default: undefined },
checkStrictly: { type: Boolean, default: undefined }, checkStrictly: { type: Boolean, default: undefined },
draggable: { type: [Function, Boolean] as PropType<((node: DataNode) => boolean) | boolean> }, draggable: { type: [Function, Boolean] as PropType<DraggableFn | boolean> },
defaultExpandParent: { type: Boolean, default: undefined }, defaultExpandParent: { type: Boolean, default: undefined },
autoExpandParent: { type: Boolean, default: undefined }, autoExpandParent: { type: Boolean, default: undefined },
defaultExpandAll: { type: Boolean, default: undefined }, defaultExpandAll: { type: Boolean, default: undefined },

View File

@ -12,11 +12,13 @@ import type {
FlattenNode, FlattenNode,
Direction, Direction,
DragNodeEvent, DragNodeEvent,
BasicDataNode,
} from './interface'; } from './interface';
import { warning } from '../vc-util/warning'; import { warning } from '../vc-util/warning';
import type { AllowDrop, TreeNodeProps, TreeProps } from './props'; import type { AllowDrop, TreeNodeProps, TreeProps } from './props';
export function arrDel(list: Key[], value: Key) { export function arrDel(list: Key[], value: Key) {
if (!list) return [];
const clone = list.slice(); const clone = list.slice();
const index = clone.indexOf(value); const index = clone.indexOf(value);
if (index >= 0) { if (index >= 0) {
@ -26,7 +28,7 @@ export function arrDel(list: Key[], value: Key) {
} }
export function arrAdd(list: Key[], value: Key) { export function arrAdd(list: Key[], value: Key) {
const clone = list.slice(); const clone = (list || []).slice();
if (clone.indexOf(value) === -1) { if (clone.indexOf(value) === -1) {
clone.push(value); clone.push(value);
} }
@ -45,13 +47,16 @@ export function isTreeNode(node: NodeElement) {
return node && node.type && (node.type as any).isTreeNode; return node && node.type && (node.type as any).isTreeNode;
} }
export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] { export function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(
dragNodeKey: Key,
keyEntities: Record<Key, DataEntity<TreeDataType>>,
): Key[] {
// not contains self // not contains self
// self for left or right drag // self for left or right drag
const dragChildrenKeys = []; const dragChildrenKeys = [];
const entity = keyEntities[dragNodeKey]; const entity = keyEntities[dragNodeKey];
function dig(list: DataEntity[] = []) { function dig(list: DataEntity<TreeDataType>[] = []) {
list.forEach(({ key, children }) => { list.forEach(({ key, children }) => {
dragChildrenKeys.push(key); dragChildrenKeys.push(key);
dig(children); dig(children);
@ -63,7 +68,9 @@ export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, D
return dragChildrenKeys; return dragChildrenKeys;
} }
export function isLastChild(treeNodeEntity: DataEntity) { export function isLastChild<TreeDataType extends BasicDataNode = DataNode>(
treeNodeEntity: DataEntity<TreeDataType>,
) {
if (treeNodeEntity.parent) { if (treeNodeEntity.parent) {
const posArr = posToArr(treeNodeEntity.pos); const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1; return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
@ -71,24 +78,26 @@ export function isLastChild(treeNodeEntity: DataEntity) {
return false; return false;
} }
export function isFirstChild(treeNodeEntity: DataEntity) { export function isFirstChild<TreeDataType extends BasicDataNode = DataNode>(
treeNodeEntity: DataEntity<TreeDataType>,
) {
const posArr = posToArr(treeNodeEntity.pos); const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === 0; return Number(posArr[posArr.length - 1]) === 0;
} }
// Only used when drag, not affect SSR. // Only used when drag, not affect SSR.
export function calcDropPosition( export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
event: MouseEvent, event: MouseEvent,
_dragNode: DragNodeEvent, dragNode: DragNodeEvent,
targetNode: DragNodeEvent, targetNode: DragNodeEvent,
indent: number, indent: number,
startMousePosition: { startMousePosition: {
x: number; x: number;
y: number; y: number;
}, },
allowDrop: AllowDrop, allowDrop: AllowDrop<TreeDataType>,
flattenedNodes: FlattenNode[], flattenedNodes: FlattenNode[],
keyEntities: Record<Key, DataEntity>, keyEntities: Record<Key, DataEntity<TreeDataType>>,
expandKeys: Key[], expandKeys: Key[],
direction: Direction, direction: Direction,
): { ): {
@ -108,7 +117,7 @@ export function calcDropPosition(
const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent; const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
// find abstract drop node by horizontal offset // find abstract drop node by horizontal offset
let abstractDropNodeEntity: DataEntity = keyEntities[targetNode.eventKey]; let abstractDropNodeEntity: DataEntity<TreeDataType> = keyEntities[targetNode.eventKey];
if (clientY < top + height / 2) { if (clientY < top + height / 2) {
// first half, set abstract drop node to previous node // first half, set abstract drop node to previous node
@ -139,7 +148,7 @@ export function calcDropPosition(
} }
} }
} }
const abstractDragDataNode = dragNode.eventData;
const abstractDropDataNode = abstractDropNodeEntity.node; const abstractDropDataNode = abstractDropNodeEntity.node;
let dropAllowed = true; let dropAllowed = true;
if ( if (
@ -147,6 +156,7 @@ export function calcDropPosition(
abstractDropNodeEntity.level === 0 && abstractDropNodeEntity.level === 0 &&
clientY < top + height / 2 && clientY < top + height / 2 &&
allowDrop({ allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode, dropNode: abstractDropDataNode,
dropPosition: -1, dropPosition: -1,
}) && }) &&
@ -162,6 +172,7 @@ export function calcDropPosition(
// only allow drop inside // only allow drop inside
if ( if (
allowDrop({ allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode, dropNode: abstractDropDataNode,
dropPosition: 0, dropPosition: 0,
}) })
@ -178,6 +189,7 @@ export function calcDropPosition(
// 2. do not allow drop // 2. do not allow drop
if ( if (
allowDrop({ allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode, dropNode: abstractDropDataNode,
dropPosition: 1, dropPosition: 1,
}) })
@ -196,6 +208,7 @@ export function calcDropPosition(
// 3. do not allow drop // 3. do not allow drop
if ( if (
allowDrop({ allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode, dropNode: abstractDropDataNode,
dropPosition: 0, dropPosition: 0,
}) })
@ -203,6 +216,7 @@ export function calcDropPosition(
dropPosition = 0; dropPosition = 0;
} else if ( } else if (
allowDrop({ allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode, dropNode: abstractDropDataNode,
dropPosition: 1, dropPosition: 1,
}) })
@ -220,6 +234,7 @@ export function calcDropPosition(
// 2. do not allow drop // 2. do not allow drop
if ( if (
allowDrop({ allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode, dropNode: abstractDropDataNode,
dropPosition: 1, dropPosition: 1,
}) })
@ -336,17 +351,3 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
return [...expandedKeys]; return [...expandedKeys];
} }
/**
* Returns only the data- and aria- key/value pairs
*/
export function getDataAndAria(props: Partial<TreeProps | TreeNodeProps>) {
const omitProps: Record<string, string> = {};
Object.keys(props).forEach(key => {
if (key.startsWith('data-') || key.startsWith('aria-')) {
omitProps[key] = props[key];
}
});
return omitProps;
}

View File

@ -1,5 +1,5 @@
import { warning } from '../../vc-util/warning'; import { warning } from '../../vc-util/warning';
import type { Key, DataEntity, DataNode, GetCheckDisabled } from '../interface'; import type { Key, DataEntity, DataNode, GetCheckDisabled, BasicDataNode } from '../interface';
interface ConductReturnType { interface ConductReturnType {
checkedKeys: Key[]; checkedKeys: Key[];
@ -16,17 +16,17 @@ function removeFromCheckedKeys(halfCheckedKeys: Set<Key>, checkedKeys: Set<Key>)
return filteredKeys; return filteredKeys;
} }
export function isCheckDisabled(node: DataNode) { export function isCheckDisabled<TreeDataType>(node: TreeDataType) {
const { disabled, disableCheckbox, checkable } = (node || {}) as DataNode; const { disabled, disableCheckbox, checkable } = (node || {}) as DataNode;
return !!(disabled || disableCheckbox) || checkable === false; return !!(disabled || disableCheckbox) || checkable === false;
} }
// Fill miss keys // Fill miss keys
function fillConductCheck( function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
keys: Set<Key>, keys: Set<Key>,
levelEntities: Map<number, Set<DataEntity>>, levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
maxLevel: number, maxLevel: number,
syntheticGetCheckDisabled: GetCheckDisabled<DataNode>, syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
): ConductReturnType { ): ConductReturnType {
const checkedKeys = new Set<Key>(keys); const checkedKeys = new Set<Key>(keys);
const halfCheckedKeys = new Set<Key>(); const halfCheckedKeys = new Set<Key>();
@ -98,12 +98,12 @@ function fillConductCheck(
} }
// Remove useless key // Remove useless key
function cleanConductCheck( function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
keys: Set<Key>, keys: Set<Key>,
halfKeys: Key[], halfKeys: Key[],
levelEntities: Map<number, Set<DataEntity>>, levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
maxLevel: number, maxLevel: number,
syntheticGetCheckDisabled: GetCheckDisabled<DataNode>, syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
): ConductReturnType { ): ConductReturnType {
const checkedKeys = new Set<Key>(keys); const checkedKeys = new Set<Key>(keys);
let halfCheckedKeys = new Set<Key>(halfKeys); let halfCheckedKeys = new Set<Key>(halfKeys);
@ -182,15 +182,15 @@ function cleanConductCheck(
* @param keyEntities key - dataEntity map * @param keyEntities key - dataEntity map
* @param mode `fill` to fill missing key, `clean` to remove useless key * @param mode `fill` to fill missing key, `clean` to remove useless key
*/ */
export function conductCheck( export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
keyList: Key[], keyList: Key[],
checked: true | { checked: false; halfCheckedKeys: Key[] }, checked: true | { checked: false; halfCheckedKeys: Key[] },
keyEntities: Record<Key, DataEntity>, keyEntities: Record<Key, DataEntity<TreeDataType>>,
getCheckDisabled?: GetCheckDisabled<DataNode>, getCheckDisabled?: GetCheckDisabled<TreeDataType>,
): ConductReturnType { ): ConductReturnType {
const warningMissKeys: Key[] = []; const warningMissKeys: Key[] = [];
let syntheticGetCheckDisabled: GetCheckDisabled<DataNode>; let syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>;
if (getCheckDisabled) { if (getCheckDisabled) {
syntheticGetCheckDisabled = getCheckDisabled; syntheticGetCheckDisabled = getCheckDisabled;
} else { } else {
@ -208,7 +208,7 @@ export function conductCheck(
return hasEntity; return hasEntity;
}), }),
); );
const levelEntities = new Map<number, Set<DataEntity>>(); const levelEntities = new Map<number, Set<DataEntity<TreeDataType>>>();
let maxLevel = 0; let maxLevel = 0;
// Convert entities by level for calculation // Convert entities by level for calculation
@ -216,7 +216,7 @@ export function conductCheck(
const entity = keyEntities[key]; const entity = keyEntities[key];
const { level } = entity; const { level } = entity;
let levelSet: Set<DataEntity> = levelEntities.get(level); let levelSet: Set<DataEntity<TreeDataType>> = levelEntities.get(level);
if (!levelSet) { if (!levelSet) {
levelSet = new Set(); levelSet = new Set();
levelEntities.set(level, levelSet); levelEntities.set(level, levelSet);
@ -237,7 +237,12 @@ export function conductCheck(
let result: ConductReturnType; let result: ConductReturnType;
if (checked === true) { if (checked === true) {
result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled); result = fillConductCheck<TreeDataType>(
keys,
levelEntities,
maxLevel,
syntheticGetCheckDisabled,
);
} else { } else {
result = cleanConductCheck( result = cleanConductCheck(
keys, keys,

View File

@ -7,6 +7,7 @@ import type {
EventDataNode, EventDataNode,
GetKey, GetKey,
FieldNames, FieldNames,
BasicDataNode,
} from '../interface'; } from '../interface';
import { getPosition, isTreeNode } from '../util'; import { getPosition, isTreeNode } from '../util';
import { warning } from '../../vc-util/warning'; import { warning } from '../../vc-util/warning';
@ -23,11 +24,13 @@ export function getKey(key: Key, pos: string) {
return pos; return pos;
} }
export function fillFieldNames(fieldNames?: FieldNames) { export function fillFieldNames(fieldNames?: FieldNames): Required<FieldNames> {
const { title, key, children } = fieldNames || {}; const { title, _title, key, children } = fieldNames || {};
const mergedTitle = title || 'title';
return { return {
title: title || 'title', title: mergedTitle,
_title: _title || [mergedTitle],
key: key || 'key', key: key || 'key',
children: children || 'children', children: children || 'children',
}; };
@ -129,7 +132,11 @@ export function flattenTreeData(
expandedKeys: Key[] | true, expandedKeys: Key[] | true,
fieldNames: FieldNames, fieldNames: FieldNames,
): FlattenNode[] { ): FlattenNode[] {
const { title: fieldTitle, key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames); const {
_title: fieldTitles,
key: fieldKey,
children: fieldChildren,
} = fillFieldNames(fieldNames);
const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys); const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
const flattenList: FlattenNode[] = []; const flattenList: FlattenNode[] = [];
@ -139,10 +146,19 @@ export function flattenTreeData(
const pos: string = getPosition(parent ? parent.pos : '0', index); const pos: string = getPosition(parent ? parent.pos : '0', index);
const mergedKey = getKey(treeNode[fieldKey], pos); const mergedKey = getKey(treeNode[fieldKey], pos);
// Pick matched title in field title list
let mergedTitle: any;
for (let i = 0; i < fieldTitles.length; i += 1) {
const fieldTitle = fieldTitles[i];
if (treeNode[fieldTitle] !== undefined) {
mergedTitle = treeNode[fieldTitle];
break;
}
}
// Add FlattenDataNode into list // Add FlattenDataNode into list
const flattenNode: FlattenNode = { const flattenNode: FlattenNode = {
...omit(treeNode, [fieldTitle, fieldKey, fieldChildren] as any), ...omit(treeNode, [...fieldTitles, fieldKey, fieldChildren] as any),
title: treeNode[fieldTitle], title: mergedTitle,
key: mergedKey, key: mergedKey,
parent, parent,
pos, pos,
@ -191,6 +207,7 @@ export function traverseDataNodes(
key: Key; key: Key;
parentPos: string | number; parentPos: string | number;
level: number; level: number;
nodes: DataNode[];
}) => void, }) => void,
// To avoid too many params, let use config instead of origin param // To avoid too many params, let use config instead of origin param
config?: TraverseDataNodesConfig | string, config?: TraverseDataNodesConfig | string,
@ -227,9 +244,11 @@ export function traverseDataNodes(
node: DataNode, node: DataNode,
index?: number, index?: number,
parent?: { node: DataNode; pos: string; level: number }, parent?: { node: DataNode; pos: string; level: number },
pathNodes?: DataNode[],
) { ) {
const children = node ? node[mergeChildrenPropName] : dataNodes; const children = node ? node[mergeChildrenPropName] : dataNodes;
const pos = node ? getPosition(parent.pos, index) : '0'; const pos = node ? getPosition(parent.pos, index) : '0';
const connectNodes = node ? [...pathNodes, node] : [];
// Process node if is not root // Process node if is not root
if (node) { if (node) {
@ -241,6 +260,7 @@ export function traverseDataNodes(
key, key,
parentPos: parent.node ? parent.pos : null, parentPos: parent.node ? parent.pos : null,
level: parent.level + 1, level: parent.level + 1,
nodes: connectNodes,
}; };
callback(data); callback(data);
@ -249,11 +269,16 @@ export function traverseDataNodes(
// Process children node // Process children node
if (children) { if (children) {
children.forEach((subNode, subIndex) => { children.forEach((subNode, subIndex) => {
processNode(subNode, subIndex, { processNode(
node, subNode,
pos, subIndex,
level: parent ? parent.level + 1 : -1, {
}); node,
pos,
level: parent ? parent.level + 1 : -1,
},
connectNodes,
);
}); });
} }
} }
@ -306,8 +331,8 @@ export function convertDataToEntities(
traverseDataNodes( traverseDataNodes(
dataNodes, dataNodes,
item => { item => {
const { node, index, pos, key, parentPos, level } = item; const { node, index, pos, key, parentPos, level, nodes } = item;
const entity: DataEntity = { node, index, key, pos, level }; const entity: DataEntity = { node, nodes, index, key, pos, level };
const mergedKey = getKey(key, pos); const mergedKey = getKey(key, pos);
@ -335,7 +360,7 @@ export function convertDataToEntities(
return wrapper; return wrapper;
} }
export interface TreeNodeRequiredProps { export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
expandedKeys: Key[]; expandedKeys: Key[];
selectedKeys: Key[]; selectedKeys: Key[];
loadedKeys: Key[]; loadedKeys: Key[];
@ -344,13 +369,13 @@ export interface TreeNodeRequiredProps {
halfCheckedKeys: Key[]; halfCheckedKeys: Key[];
dragOverNodeKey: Key; dragOverNodeKey: Key;
dropPosition: number; dropPosition: number;
keyEntities: Record<Key, DataEntity>; keyEntities: Record<Key, DataEntity<TreeDataType>>;
} }
/** /**
* Get TreeNode props with Tree props. * Get TreeNode props with Tree props.
*/ */
export function getTreeNodeProps( export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
key: Key, key: Key,
{ {
expandedKeys, expandedKeys,
@ -362,7 +387,7 @@ export function getTreeNodeProps(
dragOverNodeKey, dragOverNodeKey,
dropPosition, dropPosition,
keyEntities, keyEntities,
}: TreeNodeRequiredProps, }: TreeNodeRequiredProps<TreeDataType>,
) { ) {
const entity = keyEntities[key]; const entity = keyEntities[key];
@ -418,6 +443,7 @@ export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode
pos, pos,
active, active,
eventKey, eventKey,
key: eventKey,
}; };
if (!('props' in eventData)) { if (!('props' in eventData)) {
Object.defineProperty(eventData, 'props', { Object.defineProperty(eventData, 'props', {

View File

@ -96,6 +96,7 @@ const List = defineComponent({
onScroll: PropTypes.func, onScroll: PropTypes.func,
onMousedown: PropTypes.func, onMousedown: PropTypes.func,
onMouseenter: PropTypes.func, onMouseenter: PropTypes.func,
onVisibleChange: Function as PropType<(visibleList: any[], fullList: any[]) => void>,
}, },
setup(props, { expose }) { setup(props, { expose }) {
// ================================= MISC ================================= // ================================= MISC =================================
@ -400,6 +401,20 @@ const List = defineComponent({
return cs; return cs;
}); });
// ================================ Effect ================================
/** We need told outside that some list not rendered */
watch(
[() => calRes.start, () => calRes.end, mergedData],
() => {
if (props.onVisibleChange) {
const renderList = mergedData.value.slice(calRes.start, calRes.end + 1);
props.onVisibleChange(renderList, mergedData.value);
}
},
{ flush: 'post' },
);
return { return {
state, state,
mergedData, mergedData,

View File

@ -1,3 +1,4 @@
// base rc-virtual-list 3.4.2
import List from './List'; import List from './List';
export default List; export default List;