This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT LICENSE
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+149
View File
@@ -0,0 +1,149 @@
# @rc-component/tree
Tree component.
[![NPM version][npm-image]][npm-url]
[![npm download][download-image]][download-url]
[![build status][github-actions-image]][github-actions-url]
[![Codecov][codecov-image]][codecov-url]
[![bundle size][bundlephobia-image]][bundlephobia-url]
[![dumi][dumi-image]][dumi-url]
[npm-image]: http://img.shields.io/npm/v/@rc-component/tree.svg?style=flat-square
[npm-url]: http://npmjs.org/package/@rc-component/tree
[github-actions-image]: https://github.com/react-component/tree/actions/workflows/main.yml/badge.svg
[github-actions-url]: https://github.com/react-component/tree/actions/workflows/main.yml
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tree/master.svg?style=flat-square
[codecov-url]: https://codecov.io/gh/react-component/tree/
[david-url]: https://david-dm.org/react-component/tree
[david-image]: https://david-dm.org/react-component/tree/status.svg?style=flat-square
[david-dev-url]: https://david-dm.org/react-component/tree?type=dev
[david-dev-image]: https://david-dm.org/react-component/tree/dev-status.svg?style=flat-square
[download-image]: https://img.shields.io/npm/dm/@rc-component/tree.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/tree
[bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/tree
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/tree
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
## Screenshots
<img src="https://t.alipayobjects.com/images/T15BpfXn8nXXXXXXXX.png" width="288"/>
## Feature
- Support all popular browsers, including Internet Explorer 9 and above.
## Example
http://localhost:9001/
online example: https://tree.react-component.now.sh/
## Install
[![@rc-component/tree](https://nodei.co/npm/@rc-component/tree.png)](https://npmjs.org/package/@rc-component/tree)
## Usage
> Note: `import "@rc-component/tree/assets/index.css"`
see examples
## API
### Tree props
| name | description | type | default |
| --- | --- | --- | --- |
| autoExpandParent | whether auto expand parent treeNodes | bool | false |
| checkable | whether support checked | bool/React Node | false |
| checkedKeys | Controlled checked treeNodes(After setting, defaultCheckedKeys will not work). Note: parent and children nodes are associated, if the parent node's key exists, it all children node will be checked, and vice versa. When set checkable and checkStrictly, it should be an object, which contains checked array and halfChecked array. | String[]/{checked:Array<String>,halfChecked:Array<String>} | [] |
| checkStrictly | check node precisely, parent and children nodes are not associated | bool | false |
| className | additional css class of root dom node | String | '' |
| defaultCheckedKeys | default checked treeNodes | String[] | [] |
| defaultExpandedKeys | expand specific treeNodes | String[] | [] |
| defaultExpandAll | expand all treeNodes | bool | false |
| defaultExpandParent | auto expand parent treeNodes when init | bool | true |
| defaultSelectedKeys | default selected treeNodes | String[] | [] |
| disabled | whether disabled the tree | bool | false |
| draggable | whether can drag treeNode. (drag events are not supported in Internet Explorer 8 and earlier versions or Safari 5.1 and earlier versions.) | bool \| ({ node }) => boolean | false |
| expandedKeys | Controlled expand specific treeNodes | String[] | - |
| filterTreeNode | filter some treeNodes as you need. it should return true | function(node) | - |
| icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | element/Function(props) | - |
| loadedKeys | Mark node is loaded when `loadData` is true | String[] | - |
| loadData | load data asynchronously and the return value should be a promise | function(node) | - |
| multiple | whether multiple select | bool | false |
| prefixCls | prefix class | String | 'rc-tree' |
| selectable | whether can be selected | bool | true |
| selectedKeys | Controlled selected treeNodes(After setting, defaultSelectedKeys will not work) | String[] | [] |
| showIcon | whether show icon | bool | true |
| showLine | whether show line | bool | false |
| treeData | treeNodes data Array, if set it then you need not to construct children TreeNode. (value should be unique across the whole array) | array<{key,title,children, [disabled, selectable]}> | - |
| onCheck | click the treeNode/checkbox to fire | function(checkedKeys, e:{checked: bool, checkedNodes, node, event, nativeEvent}) | - |
| onExpand | fire on treeNode expand or not | function(expandedKeys, {expanded: bool, node, nativeEvent}) | - |
| onDragEnd | it execs when fire the tree's dragend event | function({event,node}) | - |
| onDragEnter | it execs when fire the tree's dragenter event | function({event,node,expandedKeys}) | - |
| onDragLeave | it execs when fire the tree's dragleave event | function({event,node}) | - |
| onDragOver | it execs when fire the tree's dragover event | function({event,node}) | - |
| onDragStart | it execs when fire the tree's dragstart event | function({event,node}) | - |
| onDrop | it execs when fire the tree's drop event | function({event, node, dragNode, dragNodesKeys}) | - |
| onLoad | Trigger when a node is loaded. If you set the `loadedKeys`, you must handle `onLoad` to avoid infinity loop | function(loadedKeys, {event, node}) | - |
| onMouseEnter | call when mouse enter a treeNode | function({event,node}) | - |
| onMouseLeave | call when mouse leave a treeNode | function({event,node}) | - |
| onRightClick | select current treeNode and show customized contextmenu | function({event,node}) | - |
| onSelect | click the treeNode to fire | function(selectedKeys, e:{selected: bool, selectedNodes, node, event, nativeEvent}) | - |
| switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
| virtual | Disable virtual scroll when `false` | boolean | - |
| allowDrop | Whether to allow drop on node | ({ dragNode, dropNode, dropPosition }) => boolean | - |
| dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
| direction | Display direction of the tree, it may affect dragging behavior | `ltr` \| `rtl` | - |
| expandAction | Tree open logic, optional: false \| `click` \| `doubleClick` | string \| boolean | `click` |
### TreeNode props
> note: if you have a lot of TreeNode, like more than 1000,
> make the parent node is collapsed by default, will obvious effect, very fast.
> Because the children hide TreeNode will not insert into dom.
| name | description | type | default |
| --- | --- | --- | --- |
| className | additional class to treeNode | String | '' |
| checkable | control node checkable if Tree is checkable | bool | false |
| style | set style to treeNode | Object | '' |
| disabled | whether disabled the treeNode | bool | false |
| disableCheckbox | whether disable the treeNode' checkbox | bool | false |
| title | tree/subTree's title | String/element/((data: DataNode) => React.ReactNode) | '---' |
| key | it's used with tree props's (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys. you'd better to set it, and it must be unique in the tree's all treeNodes | String | treeNode's position |
| isLeaf | whether it's leaf node | bool | false |
| icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | element/Function(props) | - |
| switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
## Note
The number of treeNodes can be very large, but when enable `checkable`, it will spend more computing time, so we cached some calculations(e.g. `this.treeNodesStates`), to avoid double computing. But, this bring some restrictions, **when you async load treeNodes, you should render tree like this** `{this.state.treeData.length ? <Tree ...>{this.state.treeData.map(t => <TreeNode ... />)}</Tree> : 'loading tree'}`
## Development
```bash
npm install
npm start
```
## Test Case
http://localhost:8018/tests/runner.html?coverage
## Coverage
http://localhost:8018/node_modules/rc-server/node_modules/node-jscover/lib/front-end/jscoverage.html?w=http://localhost:8018/tests/runner.html?coverage
## License
@rc-component/tree is released under the MIT license.
## Other tree views
- [zTree](http://www.treejs.cn/)
- [jqTree](https://mbraak.github.io/jqTree/)
- [jquery.treeselect](https://travistidwell.com/jquery.treeselect.js/)
- [Angular Multi Select Tree](https://a5hik.github.io/angular-multi-select-tree/)
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

@@ -0,0 +1,8 @@
import React from 'react';
export interface DropIndicatorProps {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
indent: number;
}
declare const DropIndicator: React.FC<Readonly<DropIndicatorProps>>;
export default DropIndicator;
+36
View File
@@ -0,0 +1,36 @@
import React from 'react';
const DropIndicator = props => {
const {
dropPosition,
dropLevelOffset,
indent
} = props;
const style = {
pointerEvents: 'none',
position: 'absolute',
right: 0,
backgroundColor: 'red',
height: 2
};
switch (dropPosition) {
case -1:
style.top = 0;
style.left = -dropLevelOffset * indent;
break;
case 1:
style.bottom = 0;
style.left = -dropLevelOffset * indent;
break;
case 0:
style.bottom = 0;
style.left = indent;
break;
}
return /*#__PURE__*/React.createElement("div", {
style: style
});
};
if (process.env.NODE_ENV !== 'production') {
DropIndicator.displayName = 'DropIndicator';
}
export default DropIndicator;
+9
View File
@@ -0,0 +1,9 @@
import * as React from 'react';
interface IndentProps {
prefixCls: string;
level: number;
isStart: boolean[];
isEnd: boolean[];
}
declare const _default: React.NamedExoticComponent<IndentProps>;
export default _default;
+25
View File
@@ -0,0 +1,25 @@
import { clsx } from 'clsx';
import * as React from 'react';
const Indent = ({
prefixCls,
level,
isStart,
isEnd
}) => {
const baseClassName = `${prefixCls}-indent-unit`;
const list = [];
for (let i = 0; i < level; i += 1) {
list.push( /*#__PURE__*/React.createElement("span", {
key: i,
className: clsx(baseClassName, {
[`${baseClassName}-start`]: isStart[i],
[`${baseClassName}-end`]: isEnd[i]
})
}));
}
return /*#__PURE__*/React.createElement("span", {
"aria-hidden": "true",
className: `${prefixCls}-indent`
}, list);
};
export default /*#__PURE__*/React.memo(Indent);
+14
View File
@@ -0,0 +1,14 @@
import * as React from 'react';
import type { FlattenNode, TreeNodeProps } from './interface';
import { type TreeNodeRequiredProps } from './utils/treeUtil';
interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
active: boolean;
motion?: any;
motionNodes?: FlattenNode[];
onMotionStart: () => void;
onMotionEnd: () => void;
motionType?: 'show' | 'hide';
treeNodeRequiredProps: TreeNodeRequiredProps;
}
declare const MotionTreeNode: React.ForwardRefExoticComponent<MotionTreeNodeProps & React.RefAttributes<HTMLDivElement>>;
export default MotionTreeNode;
+109
View File
@@ -0,0 +1,109 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { clsx } from 'clsx';
import CSSMotion from '@rc-component/motion';
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
import * as React from 'react';
import { TreeContext } from "./contextTypes";
import TreeNode from "./TreeNode";
import useUnmount from "./useUnmount";
import { getTreeNodeProps } from "./utils/treeUtil";
const MotionTreeNode = /*#__PURE__*/React.forwardRef((oriProps, ref) => {
const {
className,
style,
motion,
motionNodes,
motionType,
onMotionStart: onOriginMotionStart,
onMotionEnd: onOriginMotionEnd,
active,
treeNodeRequiredProps,
...props
} = oriProps;
const [visible, setVisible] = React.useState(true);
const {
prefixCls
} = React.useContext(TreeContext);
// Calculate target visible here.
// And apply in effect to make `leave` motion work.
const targetVisible = motionNodes && motionType !== 'hide';
useLayoutEffect(() => {
if (motionNodes) {
if (targetVisible !== visible) {
setVisible(targetVisible);
}
}
}, [motionNodes]);
const triggerMotionStart = () => {
if (motionNodes) {
onOriginMotionStart();
}
};
// Should only trigger once
const triggerMotionEndRef = React.useRef(false);
const triggerMotionEnd = () => {
if (motionNodes && !triggerMotionEndRef.current) {
triggerMotionEndRef.current = true;
onOriginMotionEnd();
}
};
// Effect if unmount
useUnmount(triggerMotionStart, triggerMotionEnd);
// Motion end event
const onVisibleChanged = nextVisible => {
if (targetVisible === nextVisible) {
triggerMotionEnd();
}
};
if (motionNodes) {
return /*#__PURE__*/React.createElement(CSSMotion, _extends({
ref: ref,
visible: visible
}, motion, {
motionAppear: motionType === 'show',
onVisibleChanged: onVisibleChanged
}), ({
className: motionClassName,
style: motionStyle
}, motionRef) => /*#__PURE__*/React.createElement("div", {
ref: motionRef,
className: clsx(`${prefixCls}-treenode-motion`, motionClassName),
style: motionStyle
}, motionNodes.map(treeNode => {
const {
data: {
...restProps
},
title,
key,
isStart,
isEnd
} = treeNode;
delete restProps.children;
const treeNodeProps = getTreeNodeProps(key, treeNodeRequiredProps);
return /*#__PURE__*/React.createElement(TreeNode, _extends({}, restProps, treeNodeProps, {
title: title,
active: active,
data: treeNode.data,
key: key,
isStart: isStart,
isEnd: isEnd
}));
})));
}
return /*#__PURE__*/React.createElement(TreeNode, _extends({
domRef: ref,
className: className,
style: style
}, props, {
active: active
}));
});
if (process.env.NODE_ENV !== 'production') {
MotionTreeNode.displayName = 'MotionTreeNode';
}
export default MotionTreeNode;
+51
View File
@@ -0,0 +1,51 @@
/**
* Handle virtual list of the TreeNodes.
*/
import * as React from 'react';
import type { BasicDataNode, DataEntity, DataNode, FlattenNode, Key, KeyEntities, ScrollTo } from './interface';
export declare const MOTION_KEY: string;
export declare const MotionEntity: DataEntity;
export interface NodeListRef {
scrollTo: ScrollTo;
getIndentWidth: () => number;
}
interface NodeListProps<TreeDataType extends BasicDataNode> {
prefixCls: string;
style: React.CSSProperties;
data: FlattenNode<TreeDataType>[];
motion: any;
focusable?: boolean;
activeItem: FlattenNode<TreeDataType>;
tabIndex: number;
checkable?: boolean;
selectable?: boolean;
disabled?: boolean;
expandedKeys: Key[];
selectedKeys: Key[];
checkedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
halfCheckedKeys: Key[];
keyEntities: KeyEntities;
dragging: boolean;
dragOverNodeKey: Key;
dropPosition: number;
height: number;
itemHeight: number;
virtual?: boolean;
scrollWidth?: number;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
onFocus?: React.FocusEventHandler<HTMLDivElement>;
onBlur?: React.FocusEventHandler<HTMLDivElement>;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseUp?: React.MouseEventHandler<HTMLDivElement>;
onActiveChange: (key: Key) => void;
onListChangeStart: () => void;
onListChangeEnd: () => void;
}
/**
* We only need get visible content items to play the animation.
*/
export declare function getMinimumRangeTransitionRange(list: FlattenNode[], virtual: boolean, height: number, itemHeight: number): FlattenNode<DataNode>[];
declare const NodeList: React.ForwardRefExoticComponent<NodeListProps<any> & React.RefAttributes<NodeListRef>>;
export default NodeList;
+253
View File
@@ -0,0 +1,253 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
/**
* Handle virtual list of the TreeNodes.
*/
import { getId } from "@rc-component/util/es/hooks/useId";
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
import VirtualList from '@rc-component/virtual-list';
import * as React from 'react';
import MotionTreeNode from "./MotionTreeNode";
import { findExpandedKeys, getExpandRange } from "./utils/diffUtil";
import { getKey, getTreeNodeProps } from "./utils/treeUtil";
import useId from "@rc-component/util/es/hooks/useId";
export const MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`;
const MotionNode = {
key: MOTION_KEY
};
export const MotionEntity = {
key: MOTION_KEY,
level: 0,
index: 0,
pos: '0',
node: MotionNode,
nodes: [MotionNode]
};
const MotionFlattenData = {
parent: null,
children: [],
pos: MotionEntity.pos,
data: MotionNode,
title: null,
key: MOTION_KEY,
/** Hold empty list here since we do not use it */
isStart: [],
isEnd: []
};
/**
* We only need get visible content items to play the animation.
*/
export function getMinimumRangeTransitionRange(list, virtual, height, itemHeight) {
if (virtual === false || !height) {
return list;
}
return list.slice(0, Math.ceil(height / itemHeight) + 1);
}
function itemKey(item) {
const {
key,
pos
} = item;
return getKey(key, pos);
}
const NodeList = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
data,
selectable,
checkable,
expandedKeys,
selectedKeys,
checkedKeys,
loadedKeys,
loadingKeys,
halfCheckedKeys,
keyEntities,
disabled,
dragging,
dragOverNodeKey,
dropPosition,
motion,
height,
itemHeight,
virtual,
scrollWidth,
focusable,
activeItem,
tabIndex,
onKeyDown,
onFocus,
onBlur,
onMouseDown,
onMouseUp,
onActiveChange,
onListChangeStart,
onListChangeEnd,
...domProps
} = props;
const treeId = useId();
// =============================== Ref ================================
const listRef = React.useRef(null);
const indentMeasurerRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
scrollTo: scroll => {
listRef.current.scrollTo(scroll);
},
getIndentWidth: () => indentMeasurerRef.current.offsetWidth
}));
// ============================== Motion ==============================
const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
const [prevData, setPrevData] = React.useState(data);
const [transitionData, setTransitionData] = React.useState(data);
const [transitionRange, setTransitionRange] = React.useState([]);
const [motionType, setMotionType] = React.useState(null);
// When motion end but data change, this will makes data back to previous one
const dataRef = React.useRef(data);
dataRef.current = data;
function onMotionEnd() {
const latestData = dataRef.current;
setPrevData(latestData);
setTransitionData(latestData);
setTransitionRange([]);
setMotionType(null);
onListChangeEnd();
}
// Do animation if expanded keys changed
// layoutEffect here to avoid blink of node removing
useLayoutEffect(() => {
setPrevExpandedKeys(expandedKeys);
const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);
if (diffExpanded.key !== null) {
if (diffExpanded.add) {
const keyIndex = prevData.findIndex(({
key
}) => key === diffExpanded.key);
const rangeNodes = getMinimumRangeTransitionRange(getExpandRange(prevData, data, diffExpanded.key), virtual, height, itemHeight);
const newTransitionData = prevData.slice();
newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);
setTransitionData(newTransitionData);
setTransitionRange(rangeNodes);
setMotionType('show');
} else {
const keyIndex = data.findIndex(({
key
}) => key === diffExpanded.key);
const rangeNodes = getMinimumRangeTransitionRange(getExpandRange(data, prevData, diffExpanded.key), virtual, height, itemHeight);
const newTransitionData = data.slice();
newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);
setTransitionData(newTransitionData);
setTransitionRange(rangeNodes);
setMotionType('hide');
}
} else if (prevData !== data) {
// If whole data changed, we just refresh the list
setPrevData(data);
setTransitionData(data);
}
}, [expandedKeys, data]);
// We should clean up motion if is changed by dragging
React.useEffect(() => {
if (!dragging) {
onMotionEnd();
}
}, [dragging]);
const mergedData = motion ? transitionData : data;
const treeNodeRequiredProps = {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities
};
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-treenode`,
"aria-hidden": true,
style: {
position: 'absolute',
pointerEvents: 'none',
visibility: 'hidden',
height: 0,
overflow: 'hidden',
border: 0,
padding: 0
}
}, /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-indent`
}, /*#__PURE__*/React.createElement("div", {
ref: indentMeasurerRef,
className: `${prefixCls}-indent-unit`
}))), /*#__PURE__*/React.createElement(VirtualList, _extends({}, domProps, {
data: mergedData,
itemKey: itemKey,
height: height,
fullHeight: false,
virtual: virtual,
itemHeight: itemHeight,
scrollWidth: scrollWidth,
prefixCls: `${prefixCls}-list`,
ref: listRef,
role: "tree",
tabIndex: focusable !== false && !disabled ? tabIndex : undefined,
"aria-activedescendant": activeItem ? getId(treeId, activeItem.key) : undefined,
onKeyDown: onKeyDown,
onFocus: onFocus,
onBlur: onBlur,
onMouseDown: onMouseDown,
onMouseUp: onMouseUp,
onVisibleChange: originList => {
// The best match is using `fullList` - `originList` = `restList`
// and check the `restList` to see if has the MOTION_KEY node
// but this will cause performance issue for long list compare
// we just check `originList` and repeat trigger `onMotionEnd`
if (originList.every(item => itemKey(item) !== MOTION_KEY)) {
onMotionEnd();
}
}
}), treeNode => {
const {
pos,
data: {
...restProps
},
title,
key,
isStart,
isEnd
} = treeNode;
const mergedKey = getKey(key, pos);
delete restProps.key;
delete restProps.children;
const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
return /*#__PURE__*/React.createElement(MotionTreeNode, _extends({}, restProps, treeNodeProps, {
title: title,
active: !!activeItem && key === activeItem.key,
pos: pos,
data: treeNode.data,
isStart: isStart,
isEnd: isEnd,
motion: motion,
motionNodes: key === MOTION_KEY ? transitionRange : null,
motionType: motionType,
onMotionStart: onListChangeStart,
onMotionEnd: onMotionEnd,
treeNodeRequiredProps: treeNodeRequiredProps,
treeId: treeId,
onMouseMove: () => {
onActiveChange(null);
}
}));
}));
});
if (process.env.NODE_ENV !== 'production') {
NodeList.displayName = 'NodeList';
}
export default NodeList;
+252
View File
@@ -0,0 +1,252 @@
import * as React from 'react';
import type { NodeDragEventHandler, NodeDragEventParams, NodeMouseEventHandler, NodeMouseEventParams } from './contextTypes';
import type { DropIndicatorProps } from './DropIndicator';
import type { BasicDataNode, DataNode, Direction, EventDataNode, FieldNames, FlattenNode, IconType, Key, KeyEntities, SafeKey, ScrollTo, TreeNodeProps } from './interface';
import { type NodeListRef } from './NodeList';
export interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
event: 'check';
node: EventDataNode<TreeDataType>;
checked: boolean;
nativeEvent: MouseEvent;
checkedNodes: TreeDataType[];
checkedNodesPositions?: {
node: TreeDataType;
pos: string;
}[];
halfCheckedKeys?: Key[];
}
export interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
dragNode: TreeDataType;
dropNode: TreeDataType;
dropPosition: -1 | 0 | 1;
}
export type AllowDrop<TreeDataType extends BasicDataNode = DataNode> = (options: AllowDropOptions<TreeDataType>) => boolean;
export type DraggableFn = (node: DataNode) => boolean;
export type DraggableConfig = {
icon?: React.ReactNode | false;
nodeDraggable?: DraggableFn;
};
export type ExpandAction = false | 'click' | 'doubleClick';
export type SemanticName = 'itemIcon' | 'item' | 'itemTitle';
export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
prefixCls: string;
className?: string;
style?: React.CSSProperties;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
classNames?: Partial<Record<SemanticName, string>>;
focusable?: boolean;
activeKey?: Key | null;
tabIndex?: number;
children?: React.ReactNode;
treeData?: TreeDataType[];
fieldNames?: FieldNames;
showLine?: boolean;
showIcon?: boolean;
icon?: IconType;
selectable?: boolean;
expandAction?: ExpandAction;
disabled?: boolean;
multiple?: boolean;
checkable?: boolean | React.ReactNode;
checkStrictly?: boolean;
draggable?: DraggableFn | boolean | DraggableConfig;
defaultExpandParent?: boolean;
autoExpandParent?: boolean;
defaultExpandAll?: boolean;
defaultExpandedKeys?: Key[];
expandedKeys?: Key[];
defaultCheckedKeys?: Key[];
checkedKeys?: Key[] | {
checked: Key[];
halfChecked: Key[];
};
defaultSelectedKeys?: Key[];
selectedKeys?: Key[];
allowDrop?: AllowDrop<TreeDataType>;
titleRender?: (node: TreeDataType) => React.ReactNode;
dropIndicatorRender?: (props: DropIndicatorProps) => React.ReactNode;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseUp?: React.MouseEventHandler<HTMLDivElement>;
onFocus?: React.FocusEventHandler<HTMLDivElement>;
onBlur?: React.FocusEventHandler<HTMLDivElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
onClick?: NodeMouseEventHandler<TreeDataType>;
onDoubleClick?: NodeMouseEventHandler<TreeDataType>;
onScroll?: React.UIEventHandler<HTMLElement>;
onExpand?: (expandedKeys: Key[], info: {
node: EventDataNode<TreeDataType>;
expanded: boolean;
nativeEvent: MouseEvent;
}) => void;
onCheck?: (checked: {
checked: Key[];
halfChecked: Key[];
} | Key[], info: CheckInfo<TreeDataType>) => void;
onSelect?: (selectedKeys: Key[], info: {
event: 'select';
selected: boolean;
node: EventDataNode<TreeDataType>;
selectedNodes: TreeDataType[];
nativeEvent: MouseEvent;
}) => void;
onLoad?: (loadedKeys: Key[], info: {
event: 'load';
node: EventDataNode<TreeDataType>;
}) => void;
loadData?: (treeNode: EventDataNode<TreeDataType>) => Promise<any>;
loadedKeys?: Key[];
onMouseEnter?: (info: NodeMouseEventParams<TreeDataType>) => void;
onMouseLeave?: (info: NodeMouseEventParams<TreeDataType>) => void;
onRightClick?: (info: {
event: React.MouseEvent;
node: EventDataNode<TreeDataType>;
}) => void;
onDragStart?: (info: NodeDragEventParams<TreeDataType>) => void;
onDragEnter?: (info: NodeDragEventParams<TreeDataType> & {
expandedKeys: Key[];
}) => void;
onDragOver?: (info: NodeDragEventParams<TreeDataType>) => void;
onDragLeave?: (info: NodeDragEventParams<TreeDataType>) => void;
onDragEnd?: (info: NodeDragEventParams<TreeDataType>) => void;
onDrop?: (info: NodeDragEventParams<TreeDataType> & {
dragNode: EventDataNode<TreeDataType>;
dragNodesKeys: Key[];
dropPosition: number;
dropToGap: boolean;
}) => void;
/**
* Used for `rc-tree-select` only.
* Do not use in your production code directly since this will be refactor.
*/
onActiveChange?: (key: Key) => void;
filterTreeNode?: (treeNode: EventDataNode<TreeDataType>) => boolean;
motion?: any;
switcherIcon?: IconType;
height?: number;
itemHeight?: number;
scrollWidth?: number;
itemScrollOffset?: number;
virtual?: boolean;
direction?: Direction;
rootClassName?: string;
rootStyle?: React.CSSProperties;
}
interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
keyEntities: KeyEntities<TreeDataType>;
indent: number | null;
selectedKeys: Key[];
checkedKeys: Key[];
halfCheckedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
expandedKeys: Key[];
draggingNodeKey: Key;
dragChildrenKeys: Key[];
dropPosition: -1 | 0 | 1 | null;
dropLevelOffset: number | null;
dropContainerKey: Key | null;
dropTargetKey: Key | null;
dropTargetPos: string | null;
dropAllowed: boolean;
dragOverNodeKey: Key | null;
treeData: TreeDataType[];
flattenNodes: FlattenNode<TreeDataType>[];
activeKey: Key | null;
listChanging: boolean;
prevProps: TreeProps;
fieldNames: FieldNames;
}
declare class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<TreeProps<TreeDataType>, TreeState<TreeDataType>> {
static defaultProps: {
prefixCls: string;
showLine: boolean;
showIcon: boolean;
selectable: boolean;
multiple: boolean;
checkable: boolean;
disabled: boolean;
checkStrictly: boolean;
draggable: boolean;
defaultExpandParent: boolean;
autoExpandParent: boolean;
defaultExpandAll: boolean;
defaultExpandedKeys: any[];
defaultCheckedKeys: any[];
defaultSelectedKeys: any[];
dropIndicatorRender: React.FC<Readonly<DropIndicatorProps>>;
allowDrop: () => boolean;
expandAction: boolean;
};
static TreeNode: React.FC<Readonly<TreeNodeProps<DataNode>>>;
destroyed: boolean;
delayedDragEnterLogic: Record<SafeKey, number>;
loadingRetryTimes: Record<SafeKey, number>;
state: TreeState<TreeDataType>;
dragStartMousePosition: any;
dragNodeProps: TreeNodeProps<TreeDataType>;
currentMouseOverDroppableNodeKey: any;
focusedByMouse: boolean;
listRef: React.RefObject<NodeListRef>;
componentDidMount(): void;
componentDidUpdate(): void;
onUpdated(): void;
componentWillUnmount(): void;
static getDerivedStateFromProps(props: TreeProps, prevState: TreeState): Partial<TreeState<DataNode>>;
onNodeDragStart: NodeDragEventHandler<TreeDataType, HTMLDivElement>;
/**
* [Legacy] Select handler is smaller than node,
* so that this will trigger when drag enter node or select handler.
* This is a little tricky if customize css without padding.
* Better for use mouse move event to refresh drag state.
* But let's just keep it to avoid event trigger logic change.
*/
onNodeDragEnter: (event: React.DragEvent<HTMLDivElement>, nodeProps: TreeNodeProps<TreeDataType>) => void;
onNodeDragOver: (event: React.DragEvent<HTMLDivElement>, nodeProps: TreeNodeProps<TreeDataType>) => void;
onNodeDragLeave: NodeDragEventHandler<TreeDataType>;
onWindowDragEnd: (event: any) => void;
onNodeDragEnd: NodeDragEventHandler<TreeDataType>;
onNodeDrop: (event: React.DragEvent<HTMLDivElement>, _: TreeNodeProps<TreeDataType>, outsideTree?: boolean) => void;
resetDragState(): void;
cleanDragState: () => void;
triggerExpandActionExpand: NodeMouseEventHandler;
onNodeClick: NodeMouseEventHandler<TreeDataType>;
onNodeDoubleClick: NodeMouseEventHandler<TreeDataType>;
onNodeSelect: NodeMouseEventHandler<TreeDataType>;
onNodeCheck: (e: React.MouseEvent<HTMLSpanElement>, treeNode: EventDataNode<TreeDataType>, checked: boolean) => void;
onNodeLoad: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
onNodeMouseEnter: NodeMouseEventHandler<TreeDataType>;
onNodeMouseLeave: NodeMouseEventHandler<TreeDataType>;
onNodeContextMenu: NodeMouseEventHandler<TreeDataType>;
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
onMouseUp: React.MouseEventHandler<HTMLDivElement>;
onFocus: React.FocusEventHandler<HTMLDivElement>;
onBlur: React.FocusEventHandler<HTMLDivElement>;
getTreeNodeRequiredProps: () => {
expandedKeys: React.Key[];
selectedKeys: React.Key[];
loadedKeys: React.Key[];
loadingKeys: React.Key[];
checkedKeys: React.Key[];
halfCheckedKeys: React.Key[];
dragOverNodeKey: React.Key;
dropPosition: 0 | 1 | -1;
keyEntities: KeyEntities<TreeDataType>;
};
/** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
setExpandedKeys: (expandedKeys: Key[]) => void;
onNodeExpand: (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode<TreeDataType>) => void;
onListChangeStart: () => void;
onListChangeEnd: () => void;
onActiveChange: (newActiveKey: Key | null) => void;
getActiveItem: () => FlattenNode<TreeDataType>;
offsetActiveKey: (offset: number) => void;
onKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
/**
* Only update the value which is not in props
*/
setUncontrolledState: (state: Partial<TreeState<TreeDataType>>, atomic?: boolean, forceState?: Partial<TreeState<TreeDataType>> | null) => void;
scrollTo: ScrollTo;
render(): React.JSX.Element;
}
export default Tree;
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
import React from 'react';
import type { TreeNodeProps } from './interface';
export type { TreeNodeProps } from './interface';
declare const TreeNode: React.FC<Readonly<TreeNodeProps>>;
export default TreeNode;
+387
View File
@@ -0,0 +1,387 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React from 'react';
import { clsx } from 'clsx';
import { getId } from "@rc-component/util/es/hooks/useId";
import pickAttrs from "@rc-component/util/es/pickAttrs";
import { TreeContext, UnstableContext } from "./contextTypes";
import Indent from "./Indent";
import getEntity from "./utils/keyUtil";
import { convertNodePropsToEventData, isLeafNode } from "./utils/treeUtil";
const ICON_OPEN = 'open';
const ICON_CLOSE = 'close';
const defaultTitle = '---';
const TreeNode = props => {
const {
eventKey,
className,
style,
dragOver,
dragOverGapTop,
dragOverGapBottom,
isLeaf,
isStart,
isEnd,
expanded,
selected,
checked,
halfChecked,
loading,
domRef,
active,
data,
onMouseMove,
selectable,
treeId,
...otherProps
} = props;
const nodeId = getId(treeId, eventKey);
const context = React.useContext(TreeContext);
const {
classNames: treeClassNames,
styles
} = context || {};
const unstableContext = React.useContext(UnstableContext);
const selectHandleRef = React.useRef(null);
const [dragNodeHighlight, setDragNodeHighlight] = React.useState(false);
// ======= State: Disabled State =======
const isDisabled = !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
const isCheckable = React.useMemo(() => {
// Return false if tree or treeNode is not checkable
if (!context.checkable || props.checkable === false) {
return false;
}
return context.checkable;
}, [context.checkable, props.checkable]);
// ======= Event Handlers: Selection and Check =======
const onSelect = e => {
if (isDisabled) {
return;
}
context.onNodeSelect(e, convertNodePropsToEventData(props));
};
const onCheck = e => {
if (isDisabled) {
return;
}
if (!isCheckable || props.disableCheckbox) {
return;
}
context.onNodeCheck(e, convertNodePropsToEventData(props), !checked);
};
// ======= State: Selectable Check =======
const isSelectable = React.useMemo(() => {
// Ignore when selectable is undefined or null
if (typeof selectable === 'boolean') {
return selectable;
}
return context.selectable;
}, [selectable, context.selectable]);
const onSelectorClick = e => {
// Click trigger before select/check operation
context.onNodeClick(e, convertNodePropsToEventData(props));
if (isSelectable) {
onSelect(e);
} else {
onCheck(e);
}
};
const onSelectorDoubleClick = e => {
context.onNodeDoubleClick(e, convertNodePropsToEventData(props));
};
const onMouseEnter = e => {
context.onNodeMouseEnter(e, convertNodePropsToEventData(props));
};
const onMouseLeave = e => {
context.onNodeMouseLeave(e, convertNodePropsToEventData(props));
};
const onContextMenu = e => {
context.onNodeContextMenu(e, convertNodePropsToEventData(props));
};
// ======= Drag: Drag Enabled =======
const isDraggable = React.useMemo(() => {
return !!(context.draggable && (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data)));
}, [context.draggable, data]);
// ======= Drag: Drag Event Handlers =======
const onDragStart = e => {
e.stopPropagation();
setDragNodeHighlight(true);
context.onNodeDragStart(e, props);
try {
// ie throw error
// firefox-need-it
e.dataTransfer.setData('text/plain', '');
} catch {
// empty
}
};
const onDragEnter = e => {
e.preventDefault();
e.stopPropagation();
context.onNodeDragEnter(e, props);
};
const onDragOver = e => {
e.preventDefault();
e.stopPropagation();
context.onNodeDragOver(e, props);
};
const onDragLeave = e => {
e.stopPropagation();
context.onNodeDragLeave(e, props);
};
const onDragEnd = e => {
e.stopPropagation();
setDragNodeHighlight(false);
context.onNodeDragEnd(e, props);
};
const onDrop = e => {
e.preventDefault();
e.stopPropagation();
setDragNodeHighlight(false);
context.onNodeDrop(e, props);
};
// ======= Expand: Node Expansion =======
const onExpand = e => {
if (loading) {
return;
}
context.onNodeExpand(e, convertNodePropsToEventData(props));
};
// ======= State: Has Children =======
const hasChildren = React.useMemo(() => {
const {
children
} = getEntity(context.keyEntities, eventKey) || {};
return Boolean((children || []).length);
}, [context.keyEntities, eventKey]);
// ======= State: Leaf Check =======
const memoizedIsLeaf = React.useMemo(() => {
return isLeafNode(isLeaf, context.loadData, hasChildren, props.loaded);
}, [isLeaf, context.loadData, hasChildren, props.loaded]);
// ============== Effect ==============
React.useEffect(() => {
// Load data to avoid default expanded tree without data
if (loading) {
return;
}
// read from state to avoid loadData at same time
if (typeof context.loadData === 'function' && expanded && !memoizedIsLeaf && !props.loaded) {
// We needn't reload data when has children in sync logic
// It's only needed in node expanded
context.onNodeLoad(convertNodePropsToEventData(props));
}
}, [loading, context.loadData, context.onNodeLoad, expanded, memoizedIsLeaf, props]);
// ==================== Render: Drag Handler ====================
const dragHandlerNode = React.useMemo(() => {
if (!context.draggable?.icon) {
return null;
}
return /*#__PURE__*/React.createElement("span", {
className: `${context.prefixCls}-draggable-icon`
}, context.draggable.icon);
}, [context.draggable]);
// ====================== Render: Switcher ======================
const renderSwitcherIconDom = isInternalLeaf => {
const switcherIcon = props.switcherIcon || context.switcherIcon;
// if switcherIconDom is null, no render switcher span
if (typeof switcherIcon === 'function') {
return switcherIcon({
...props,
isLeaf: isInternalLeaf
});
}
return switcherIcon;
};
// Switcher
const renderSwitcher = () => {
if (memoizedIsLeaf) {
// if switcherIconDom is null, no render switcher span
const switcherIconDom = renderSwitcherIconDom(true);
return switcherIconDom !== false ? /*#__PURE__*/React.createElement("span", {
className: clsx(`${context.prefixCls}-switcher`, `${context.prefixCls}-switcher-noop`)
}, switcherIconDom) : null;
}
const switcherIconDom = renderSwitcherIconDom(false);
return switcherIconDom !== false ? /*#__PURE__*/React.createElement("span", {
onClick: onExpand,
className: clsx(`${context.prefixCls}-switcher`, `${context.prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`)
}, switcherIconDom) : null;
};
// ====================== Checkbox ======================
const checkboxNode = React.useMemo(() => {
if (!isCheckable) {
return null;
}
// [Legacy] Custom element should be separate with `checkable` in future
const $custom = typeof isCheckable !== 'boolean' ? isCheckable : null;
return /*#__PURE__*/React.createElement("span", {
className: clsx(`${context.prefixCls}-checkbox`, {
[`${context.prefixCls}-checkbox-checked`]: checked,
[`${context.prefixCls}-checkbox-indeterminate`]: !checked && halfChecked,
[`${context.prefixCls}-checkbox-disabled`]: isDisabled || props.disableCheckbox
}),
onClick: onCheck,
role: "checkbox",
"aria-checked": halfChecked ? 'mixed' : checked,
"aria-disabled": isDisabled || props.disableCheckbox,
"aria-labelledby": nodeId
}, $custom);
}, [isCheckable, checked, halfChecked, isDisabled, props.disableCheckbox, nodeId]);
// ============== State: Node State (Open/Close) ==============
const nodeState = React.useMemo(() => {
if (memoizedIsLeaf) {
return null;
}
return expanded ? ICON_OPEN : ICON_CLOSE;
}, [memoizedIsLeaf, expanded]);
// ==================== Render: Title + Icon ====================
const iconNode = React.useMemo(() => {
return /*#__PURE__*/React.createElement("span", {
className: clsx(treeClassNames?.itemIcon, `${context.prefixCls}-iconEle`, `${context.prefixCls}-icon__${nodeState || 'docu'}`, {
[`${context.prefixCls}-icon_loading`]: loading
}),
style: styles?.itemIcon
});
}, [context.prefixCls, nodeState, loading]);
// =================== Drop Indicator ===================
const dropIndicatorNode = React.useMemo(() => {
const rootDraggable = Boolean(context.draggable);
// allowDrop is calculated in Tree.tsx, there is no need for calc it here
const showIndicator = !props.disabled && rootDraggable && context.dragOverNodeKey === eventKey;
if (!showIndicator) {
return null;
}
return context.dropIndicatorRender({
dropPosition: context.dropPosition,
dropLevelOffset: context.dropLevelOffset,
indent: context.indent,
prefixCls: context.prefixCls,
direction: context.direction
});
}, [context.dropPosition, context.dropLevelOffset, context.indent, context.prefixCls, context.direction, context.draggable, context.dragOverNodeKey, context.dropIndicatorRender]);
// Icon + Title
const selectorNode = React.useMemo(() => {
const {
title = defaultTitle
} = props;
const wrapClass = `${context.prefixCls}-node-content-wrapper`;
// Icon - Still show loading icon when loading without showIcon
let $icon;
if (context.showIcon) {
const currentIcon = props.icon || context.icon;
$icon = currentIcon ? /*#__PURE__*/React.createElement("span", {
className: clsx(treeClassNames?.itemIcon, `${context.prefixCls}-iconEle`, `${context.prefixCls}-icon__customize`),
style: styles?.itemIcon
}, typeof currentIcon === 'function' ? currentIcon(props) : currentIcon) : iconNode;
} else if (context.loadData && loading) {
$icon = iconNode;
}
// Title
let titleNode;
if (typeof title === 'function') {
titleNode = title(data);
} else if (context.titleRender) {
titleNode = context.titleRender(data);
} else {
titleNode = title;
}
return /*#__PURE__*/React.createElement("span", {
ref: selectHandleRef,
title: typeof title === 'string' ? title : '',
className: clsx(wrapClass, `${wrapClass}-${nodeState || 'normal'}`, {
[`${context.prefixCls}-node-selected`]: !isDisabled && (selected || dragNodeHighlight)
}),
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onContextMenu: onContextMenu,
onClick: onSelectorClick,
onDoubleClick: onSelectorDoubleClick
}, $icon, /*#__PURE__*/React.createElement("span", {
className: clsx(`${context.prefixCls}-title`, treeClassNames?.itemTitle),
style: styles?.itemTitle
}, titleNode), dropIndicatorNode);
}, [context.prefixCls, context.showIcon, props, context.icon, iconNode, context.titleRender, data, nodeState, onMouseEnter, onMouseLeave, onContextMenu, onSelectorClick, onSelectorDoubleClick]);
const dataOrAriaAttributeProps = pickAttrs(otherProps, {
aria: true,
data: true
});
const {
level
} = getEntity(context.keyEntities, eventKey) || {};
const isEndNode = isEnd[isEnd.length - 1];
const draggableWithoutDisabled = !isDisabled && isDraggable;
const dragging = context.draggingNodeKey === eventKey;
return /*#__PURE__*/React.createElement("div", _extends({
ref: domRef,
role: "treeitem",
id: nodeId,
"aria-expanded": memoizedIsLeaf ? undefined : expanded,
"aria-selected": isSelectable && !isDisabled ? selected : undefined,
"aria-checked": isCheckable && !isDisabled ? halfChecked ? 'mixed' : checked : undefined,
"aria-disabled": isDisabled,
className: clsx(className, `${context.prefixCls}-treenode`, treeClassNames?.item, {
[`${context.prefixCls}-treenode-disabled`]: isDisabled,
[`${context.prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
[`${context.prefixCls}-treenode-checkbox-checked`]: checked,
[`${context.prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
[`${context.prefixCls}-treenode-selected`]: selected,
[`${context.prefixCls}-treenode-loading`]: loading,
[`${context.prefixCls}-treenode-active`]: active,
[`${context.prefixCls}-treenode-leaf-last`]: isEndNode,
[`${context.prefixCls}-treenode-draggable`]: isDraggable,
dragging,
'drop-target': context.dropTargetKey === eventKey,
'drop-container': context.dropContainerKey === eventKey,
'drag-over': !isDisabled && dragOver,
'drag-over-gap-top': !isDisabled && dragOverGapTop,
'drag-over-gap-bottom': !isDisabled && dragOverGapBottom,
'filter-node': context.filterTreeNode?.(convertNodePropsToEventData(props)),
[`${context.prefixCls}-treenode-leaf`]: memoizedIsLeaf
}),
style: {
...style,
...styles?.item
}
// Draggable config
,
draggable: draggableWithoutDisabled,
onDragStart: draggableWithoutDisabled ? onDragStart : undefined
// Drop config
,
onDragEnter: isDraggable ? onDragEnter : undefined,
onDragOver: isDraggable ? onDragOver : undefined,
onDragLeave: isDraggable ? onDragLeave : undefined,
onDrop: isDraggable ? onDrop : undefined,
onDragEnd: isDraggable ? onDragEnd : undefined,
onMouseMove: onMouseMove
}, dataOrAriaAttributeProps), /*#__PURE__*/React.createElement(Indent, {
prefixCls: context.prefixCls,
level: level,
isStart: isStart,
isEnd: isEnd
}), dragHandlerNode, renderSwitcher(), checkboxNode, selectorNode);
};
TreeNode.isTreeNode = 1;
if (process.env.NODE_ENV !== 'production') {
TreeNode.displayName = 'TreeNode';
}
export default TreeNode;
+69
View File
@@ -0,0 +1,69 @@
/**
* Webpack has bug for import loop, which is not the same behavior as ES module.
* When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
*/
import * as React from 'react';
import type { BasicDataNode, DataNode, Direction, EventDataNode, IconType, Key, KeyEntities, TreeNodeProps } from './interface';
import type { DraggableConfig, SemanticName } from './Tree';
export type NodeMouseEventParams<TreeDataType extends BasicDataNode = DataNode, T = HTMLSpanElement> = {
event: React.MouseEvent<T>;
node: EventDataNode<TreeDataType>;
};
export type NodeDragEventParams<TreeDataType extends BasicDataNode = DataNode, T = HTMLDivElement> = {
event: React.DragEvent<T>;
node: EventDataNode<TreeDataType>;
};
export type NodeMouseEventHandler<TreeDataType extends BasicDataNode = DataNode, T = HTMLSpanElement> = (e: React.MouseEvent<T>, node: EventDataNode<TreeDataType>) => void;
export type NodeDragEventHandler<TreeDataType extends BasicDataNode = DataNode, T = HTMLDivElement> = (e: React.DragEvent<T>, nodeProps: TreeNodeProps<TreeDataType>, outsideTree?: boolean) => void;
export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode> {
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
classNames?: Partial<Record<SemanticName, string>>;
prefixCls: string;
selectable: boolean;
showIcon: boolean;
icon: IconType;
switcherIcon: IconType;
draggable?: DraggableConfig;
draggingNodeKey?: Key;
checkable: boolean | React.ReactNode;
checkStrictly: boolean;
disabled: boolean;
keyEntities: KeyEntities;
dropLevelOffset?: number;
dropContainerKey: Key | null;
dropTargetKey: Key | null;
dropPosition: -1 | 0 | 1 | null;
indent: number | null;
dropIndicatorRender: (props: {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
indent: number;
prefixCls: string;
direction: Direction;
}) => React.ReactNode;
dragOverNodeKey: Key | null;
direction: Direction;
loadData: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
filterTreeNode: (treeNode: EventDataNode<TreeDataType>) => boolean;
titleRender?: (node: any) => React.ReactNode;
onNodeClick: NodeMouseEventHandler<TreeDataType>;
onNodeDoubleClick: NodeMouseEventHandler<TreeDataType>;
onNodeExpand: NodeMouseEventHandler<TreeDataType>;
onNodeSelect: NodeMouseEventHandler<TreeDataType>;
onNodeCheck: (e: React.MouseEvent<HTMLSpanElement>, treeNode: EventDataNode<TreeDataType>, checked: boolean) => void;
onNodeLoad: (treeNode: EventDataNode<TreeDataType>) => void;
onNodeMouseEnter: NodeMouseEventHandler<TreeDataType>;
onNodeMouseLeave: NodeMouseEventHandler<TreeDataType>;
onNodeContextMenu: NodeMouseEventHandler<TreeDataType>;
onNodeDragStart: NodeDragEventHandler<any, any>;
onNodeDragEnter: NodeDragEventHandler<any, any>;
onNodeDragOver: NodeDragEventHandler<any, any>;
onNodeDragLeave: NodeDragEventHandler<any, any>;
onNodeDragEnd: NodeDragEventHandler<any, any>;
onNodeDrop: NodeDragEventHandler<any, any>;
}
export declare const TreeContext: React.Context<TreeContextProps<any>>;
/** Internal usage, safe to remove. Do not use in prod */
export declare const UnstableContext: React.Context<{
nodeDisabled?: (n: DataNode) => boolean;
}>;
+9
View File
@@ -0,0 +1,9 @@
/**
* Webpack has bug for import loop, which is not the same behavior as ES module.
* When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
*/
import * as React from 'react';
export const TreeContext = /*#__PURE__*/React.createContext(null);
/** Internal usage, safe to remove. Do not use in prod */
export const UnstableContext = /*#__PURE__*/React.createContext({});
+8
View File
@@ -0,0 +1,8 @@
import Tree from './Tree';
import TreeNode from './TreeNode';
import type { TreeProps } from './Tree';
import type { TreeNodeProps, BasicDataNode, FieldDataNode } from './interface';
import { UnstableContext } from './contextTypes';
export { TreeNode, UnstableContext };
export type { TreeProps, TreeNodeProps, BasicDataNode, FieldDataNode };
export default Tree;
+5
View File
@@ -0,0 +1,5 @@
import Tree from "./Tree";
import TreeNode from "./TreeNode";
import { UnstableContext } from "./contextTypes";
export { TreeNode, UnstableContext };
export default Tree;
+123
View File
@@ -0,0 +1,123 @@
import * as React from 'react';
export type { ScrollTo } from '@rc-component/virtual-list/lib/List';
export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
eventKey?: Key;
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
id?: string;
treeId?: string;
expanded?: boolean;
selected?: boolean;
checked?: boolean;
loaded?: boolean;
loading?: boolean;
halfChecked?: boolean;
title?: React.ReactNode | ((data: TreeDataType) => React.ReactNode);
dragOver?: boolean;
dragOverGapTop?: boolean;
dragOverGapBottom?: boolean;
pos?: string;
domRef?: React.Ref<HTMLDivElement>;
/** New added in Tree for easy data access */
data?: TreeDataType;
isStart?: boolean[];
isEnd?: boolean[];
active?: boolean;
onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
isLeaf?: boolean;
checkable?: boolean;
selectable?: boolean;
disabled?: boolean;
disableCheckbox?: boolean;
icon?: IconType;
switcherIcon?: IconType;
children?: React.ReactNode;
}
/** For fieldNames, we provides a abstract interface */
export interface BasicDataNode {
checkable?: boolean;
disabled?: boolean;
disableCheckbox?: boolean;
icon?: IconType;
isLeaf?: boolean;
selectable?: boolean;
switcherIcon?: IconType;
/** Set style of TreeNode. This is not recommend if you don't have any force requirement */
className?: string;
style?: React.CSSProperties;
}
/** Provide a wrap type define for developer to wrap with customize fieldNames data type */
export type FieldDataNode<T, ChildFieldName extends string = 'children'> = BasicDataNode & T & Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
export type Key = React.Key;
/**
* Typescript not support `bigint` as index type yet.
* We use this to mark the `bigint` type is for `Key` usage.
* It's safe to remove this when typescript fix:
* https://github.com/microsoft/TypeScript/issues/50217
*/
export type SafeKey = Exclude<Key, bigint>;
export type KeyEntities<DateType extends BasicDataNode = any> = Record<SafeKey, DataEntity<DateType>>;
export type DataNode = FieldDataNode<{
key: Key;
title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
}>;
export type EventDataNode<TreeDataType> = {
key: Key;
expanded: boolean;
selected: boolean;
checked: boolean;
loaded: boolean;
loading: boolean;
halfChecked: boolean;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
pos: string;
active: boolean;
} & TreeDataType & BasicDataNode;
export type IconType = React.ReactNode | ((props: TreeNodeProps) => React.ReactNode);
export type NodeElement = React.ReactElement<TreeNodeProps> & {
selectHandle?: HTMLSpanElement;
type: {
isTreeNode: boolean;
};
};
export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> = React.Component<TreeNodeProps<TreeDataType>> & {
selectHandle?: HTMLSpanElement;
};
export interface Entity {
node: NodeElement;
index: number;
key: Key;
pos: string;
parent?: Entity;
children?: Entity[];
}
export interface DataEntity<TreeDataType extends BasicDataNode = DataNode> extends Omit<Entity, 'node' | 'parent' | 'children'> {
node: TreeDataType;
nodes: TreeDataType[];
parent?: DataEntity<TreeDataType>;
children?: DataEntity<TreeDataType>[];
level: number;
}
export interface FlattenNode<TreeDataType extends BasicDataNode = DataNode> {
parent: FlattenNode<TreeDataType> | null;
children: FlattenNode<TreeDataType>[];
pos: string;
data: TreeDataType;
title: React.ReactNode;
key: Key;
isStart: boolean[];
isEnd: boolean[];
}
export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
export type Direction = 'ltr' | 'rtl' | undefined;
export interface FieldNames {
title?: string;
/** @private Internal usage for `rc-tree-select`, safe to remove if no need */
_title?: string[];
key?: string;
children?: string;
}
+1
View File
@@ -0,0 +1 @@
export {};
+5
View File
@@ -0,0 +1,5 @@
/**
* Trigger only when component unmount
*/
declare function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidFunction): void;
export default useUnmount;
+24
View File
@@ -0,0 +1,24 @@
import * as React from 'react';
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
/**
* Trigger only when component unmount
*/
function useUnmount(triggerStart, triggerEnd) {
const [firstMount, setFirstMount] = React.useState(false);
useLayoutEffect(() => {
if (firstMount) {
triggerStart();
return () => {
triggerEnd();
};
}
}, [firstMount]);
useLayoutEffect(() => {
setFirstMount(true);
return () => {
setFirstMount(false);
};
}, []);
}
export default useUnmount;
+51
View File
@@ -0,0 +1,51 @@
/**
* Legacy code. Should avoid to use if you are new to import these code.
*/
import React from 'react';
import type { BasicDataNode, DataEntity, DataNode, Direction, FlattenNode, Key, KeyEntities, NodeElement, TreeNodeProps } from './interface';
import type { AllowDrop, TreeProps } from './Tree';
export { getPosition, isTreeNode } from './utils/treeUtil';
export declare function arrDel(list: Key[], value: Key): React.Key[];
export declare function arrAdd(list: Key[], value: Key): React.Key[];
export declare function posToArr(pos: string): string[];
export declare function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(dragNodeKey: Key, keyEntities: KeyEntities<TreeDataType>): Key[];
export declare function isLastChild<TreeDataType extends BasicDataNode = DataNode>(treeNodeEntity: DataEntity<TreeDataType>): boolean;
export declare function isFirstChild<TreeDataType extends BasicDataNode = DataNode>(treeNodeEntity: DataEntity<TreeDataType>): boolean;
export declare function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(event: React.MouseEvent, dragNodeProps: TreeNodeProps<TreeDataType>, targetNodeProps: TreeNodeProps<TreeDataType>, indent: number, startMousePosition: {
x: number;
y: number;
}, allowDrop: AllowDrop<TreeDataType>, flattenedNodes: FlattenNode<TreeDataType>[], keyEntities: KeyEntities<TreeDataType>, expandKeys: Key[], direction: Direction): {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
dropTargetKey: Key;
dropTargetPos: string;
dropContainerKey: Key;
dragOverNodeKey: Key;
dropAllowed: boolean;
};
/**
* Return selectedKeys according with multiple prop
* @param selectedKeys
* @param props
* @returns [string]
*/
export declare function calcSelectedKeys(selectedKeys: Key[], props: TreeProps): React.Key[];
export declare function convertDataToTree(treeData: DataNode[], processor?: {
processProps: (prop: DataNode) => any;
}): NodeElement[];
/**
* Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
*/
export declare function parseCheckedKeys(keys: Key[] | {
checked: Key[];
halfChecked: Key[];
}): {
checkedKeys?: Key[];
halfCheckedKeys?: Key[];
};
/**
* If user use `autoExpandParent` we should get the list of parent node
* @param keyList
* @param keyEntities
*/
export declare function conductExpandParent(keyList: Key[], keyEntities: KeyEntities): Key[];
+281
View File
@@ -0,0 +1,281 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
/* eslint-disable no-lonely-if */
/**
* Legacy code. Should avoid to use if you are new to import these code.
*/
import warning from "@rc-component/util/es/warning";
import React from 'react';
import TreeNode from "./TreeNode";
import getEntity from "./utils/keyUtil";
export { getPosition, isTreeNode } from "./utils/treeUtil";
export function arrDel(list, value) {
if (!list) return [];
const clone = list.slice();
const index = clone.indexOf(value);
if (index >= 0) {
clone.splice(index, 1);
}
return clone;
}
export function arrAdd(list, value) {
const clone = (list || []).slice();
if (clone.indexOf(value) === -1) {
clone.push(value);
}
return clone;
}
export function posToArr(pos) {
return pos.split('-');
}
export function getDragChildrenKeys(dragNodeKey, keyEntities) {
// not contains self
// self for left or right drag
const dragChildrenKeys = [];
const entity = getEntity(keyEntities, dragNodeKey);
function dig(list = []) {
list.forEach(({
key,
children
}) => {
dragChildrenKeys.push(key);
dig(children);
});
}
dig(entity.children);
return dragChildrenKeys;
}
export function isLastChild(treeNodeEntity) {
if (treeNodeEntity.parent) {
const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
}
return false;
}
export function isFirstChild(treeNodeEntity) {
const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === 0;
}
// Only used when drag, not affect SSR.
export function calcDropPosition(event, dragNodeProps, targetNodeProps, indent, startMousePosition, allowDrop, flattenedNodes, keyEntities, expandKeys, direction) {
const {
clientX,
clientY
} = event;
const {
top,
height
} = event.target.getBoundingClientRect();
// optional chain for testing
const horizontalMouseOffset = (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX);
const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
// Filter the expanded keys to exclude the node that not has children currently (like async nodes).
const filteredExpandKeys = expandKeys.filter(key => keyEntities[key]?.children?.length);
// find abstract drop node by horizontal offset
let abstractDropNodeEntity = getEntity(keyEntities, targetNodeProps.eventKey);
if (clientY < top + height / 2) {
// first half, set abstract drop node to previous node
const nodeIndex = flattenedNodes.findIndex(flattenedNode => flattenedNode.key === abstractDropNodeEntity.key);
const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
const prevNodeKey = flattenedNodes[prevNodeIndex].key;
abstractDropNodeEntity = getEntity(keyEntities, prevNodeKey);
}
const initialAbstractDropNodeKey = abstractDropNodeEntity.key;
const abstractDragOverEntity = abstractDropNodeEntity;
const dragOverNodeKey = abstractDropNodeEntity.key;
let dropPosition = 0;
let dropLevelOffset = 0;
// Only allow cross level drop when dragging on a non-expanded node
if (!filteredExpandKeys.includes(initialAbstractDropNodeKey)) {
for (let i = 0; i < rawDropLevelOffset; i += 1) {
if (isLastChild(abstractDropNodeEntity)) {
abstractDropNodeEntity = abstractDropNodeEntity.parent;
dropLevelOffset += 1;
} else {
break;
}
}
}
const abstractDragDataNode = dragNodeProps.data;
const abstractDropDataNode = abstractDropNodeEntity.node;
let dropAllowed = true;
if (isFirstChild(abstractDropNodeEntity) && abstractDropNodeEntity.level === 0 && clientY < top + height / 2 && allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: -1
}) && abstractDropNodeEntity.key === targetNodeProps.eventKey) {
// first half of first node in first level
dropPosition = -1;
} else if ((abstractDragOverEntity.children || []).length && filteredExpandKeys.includes(dragOverNodeKey)) {
// drop on expanded node
// only allow drop inside
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 0
})) {
dropPosition = 0;
} else {
dropAllowed = false;
}
} else if (dropLevelOffset === 0) {
if (rawDropLevelOffset > -1.5) {
// | Node | <- abstractDropNode
// | -^-===== | <- mousePosition
// 1. try drop after
// 2. do not allow drop
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) {
dropPosition = 1;
} else {
dropAllowed = false;
}
} else {
// | Node | <- abstractDropNode
// | ---==^== | <- mousePosition
// whether it has children or doesn't has children
// always
// 1. try drop inside
// 2. try drop after
// 3. do not allow drop
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 0
})) {
dropPosition = 0;
} else if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) {
dropPosition = 1;
} else {
dropAllowed = false;
}
}
} else {
// | Node1 | <- abstractDropNode
// | Node2 |
// --^--|----=====| <- mousePosition
// 1. try insert after Node1
// 2. do not allow drop
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) {
dropPosition = 1;
} else {
dropAllowed = false;
}
}
return {
dropPosition,
dropLevelOffset,
dropTargetKey: abstractDropNodeEntity.key,
dropTargetPos: abstractDropNodeEntity.pos,
dragOverNodeKey,
dropContainerKey: dropPosition === 0 ? null : abstractDropNodeEntity.parent?.key || null,
dropAllowed
};
}
/**
* Return selectedKeys according with multiple prop
* @param selectedKeys
* @param props
* @returns [string]
*/
export function calcSelectedKeys(selectedKeys, props) {
if (!selectedKeys) return undefined;
const {
multiple
} = props;
if (multiple) {
return selectedKeys.slice();
}
if (selectedKeys.length) {
return [selectedKeys[0]];
}
return selectedKeys;
}
const internalProcessProps = props => props;
export function convertDataToTree(treeData, processor) {
if (!treeData) return [];
const {
processProps = internalProcessProps
} = processor || {};
const list = Array.isArray(treeData) ? treeData : [treeData];
return list.map(({
children,
...props
}) => {
const childrenNodes = convertDataToTree(children, processor);
return /*#__PURE__*/React.createElement(TreeNode, _extends({
key: props.key
}, processProps(props)), childrenNodes);
});
}
/**
* Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
*/
export function parseCheckedKeys(keys) {
if (!keys) {
return null;
}
// Convert keys to object format
let keyProps;
if (Array.isArray(keys)) {
// [Legacy] Follow the api doc
keyProps = {
checkedKeys: keys,
halfCheckedKeys: undefined
};
} else if (typeof keys === 'object') {
keyProps = {
checkedKeys: keys.checked || undefined,
halfCheckedKeys: keys.halfChecked || undefined
};
} else {
warning(false, '`checkedKeys` is not an array or an object');
return null;
}
return keyProps;
}
/**
* If user use `autoExpandParent` we should get the list of parent node
* @param keyList
* @param keyEntities
*/
export function conductExpandParent(keyList, keyEntities) {
const expandedKeys = new Set();
function conductUp(key) {
if (expandedKeys.has(key)) return;
const entity = getEntity(keyEntities, key);
if (!entity) return;
expandedKeys.add(key);
const {
parent,
node
} = entity;
if (node.disabled) return;
if (parent) {
conductUp(parent.key);
}
}
(keyList || []).forEach(key => {
conductUp(key);
});
return [...expandedKeys];
}
@@ -0,0 +1,17 @@
import type { BasicDataNode, DataNode, GetCheckDisabled, Key, KeyEntities } from '../interface';
interface ConductReturnType {
checkedKeys: Key[];
halfCheckedKeys: Key[];
}
export declare function isCheckDisabled<TreeDataType>(node: TreeDataType): boolean;
/**
* Conduct with keys.
* @param keyList current key list
* @param keyEntities key - dataEntity map
* @param mode `fill` to fill missing key, `clean` to remove useless key
*/
export declare function conductCheck<TreeDataType extends BasicDataNode = DataNode>(keyList: Key[], checked: true | {
checked: false;
halfCheckedKeys: Key[];
}, keyEntities: KeyEntities<TreeDataType>, getCheckDisabled?: GetCheckDisabled<TreeDataType>): ConductReturnType;
export {};
+210
View File
@@ -0,0 +1,210 @@
import warning from "@rc-component/util/es/warning";
import getEntity from "./keyUtil";
function removeFromCheckedKeys(halfCheckedKeys, checkedKeys) {
const filteredKeys = new Set();
halfCheckedKeys.forEach(key => {
if (!checkedKeys.has(key)) {
filteredKeys.add(key);
}
});
return filteredKeys;
}
export function isCheckDisabled(node) {
const {
disabled,
disableCheckbox,
checkable
} = node || {};
return !!(disabled || disableCheckbox) || checkable === false;
}
// Fill miss keys
function fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled) {
const checkedKeys = new Set(keys);
const halfCheckedKeys = new Set();
// Add checked keys top to bottom
for (let level = 0; level <= maxLevel; level += 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
key,
node,
children = []
} = entity;
if (checkedKeys.has(key) && !syntheticGetCheckDisabled(node)) {
children.filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(childEntity => {
checkedKeys.add(childEntity.key);
});
}
});
}
// Add checked keys from bottom to top
const visitedKeys = new Set();
for (let level = maxLevel; level >= 0; level -= 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
parent,
node
} = entity;
// Skip if no need to check
if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
return;
}
// Skip if parent is disabled
if (syntheticGetCheckDisabled(entity.parent.node)) {
visitedKeys.add(parent.key);
return;
}
let allChecked = true;
let partialChecked = false;
(parent.children || []).filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(({
key
}) => {
const checked = checkedKeys.has(key);
if (allChecked && !checked) {
allChecked = false;
}
if (!partialChecked && (checked || halfCheckedKeys.has(key))) {
partialChecked = true;
}
});
if (allChecked) {
checkedKeys.add(parent.key);
}
if (partialChecked) {
halfCheckedKeys.add(parent.key);
}
visitedKeys.add(parent.key);
});
}
return {
checkedKeys: Array.from(checkedKeys),
halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys))
};
}
// Remove useless key
function cleanConductCheck(keys, halfKeys, levelEntities, maxLevel, syntheticGetCheckDisabled) {
const checkedKeys = new Set(keys);
let halfCheckedKeys = new Set(halfKeys);
// Remove checked keys from top to bottom
for (let level = 0; level <= maxLevel; level += 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
key,
node,
children = []
} = entity;
if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !syntheticGetCheckDisabled(node)) {
children.filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(childEntity => {
checkedKeys.delete(childEntity.key);
});
}
});
}
// Remove checked keys form bottom to top
halfCheckedKeys = new Set();
const visitedKeys = new Set();
for (let level = maxLevel; level >= 0; level -= 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
parent,
node
} = entity;
// Skip if no need to check
if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
return;
}
// Skip if parent is disabled
if (syntheticGetCheckDisabled(entity.parent.node)) {
visitedKeys.add(parent.key);
return;
}
let allChecked = true;
let partialChecked = false;
(parent.children || []).filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(({
key
}) => {
const checked = checkedKeys.has(key);
if (allChecked && !checked) {
allChecked = false;
}
if (!partialChecked && (checked || halfCheckedKeys.has(key))) {
partialChecked = true;
}
});
if (!allChecked) {
checkedKeys.delete(parent.key);
}
if (partialChecked) {
halfCheckedKeys.add(parent.key);
}
visitedKeys.add(parent.key);
});
}
return {
checkedKeys: Array.from(checkedKeys),
halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys))
};
}
/**
* Conduct with keys.
* @param keyList current key list
* @param keyEntities key - dataEntity map
* @param mode `fill` to fill missing key, `clean` to remove useless key
*/
export function conductCheck(keyList, checked, keyEntities, getCheckDisabled) {
const warningMissKeys = [];
let syntheticGetCheckDisabled;
if (getCheckDisabled) {
syntheticGetCheckDisabled = getCheckDisabled;
} else {
syntheticGetCheckDisabled = isCheckDisabled;
}
// We only handle exist keys
const keys = new Set(keyList.filter(key => {
const hasEntity = !!getEntity(keyEntities, key);
if (!hasEntity) {
warningMissKeys.push(key);
}
return hasEntity;
}));
const levelEntities = new Map();
let maxLevel = 0;
// Convert entities by level for calculation
Object.keys(keyEntities).forEach(key => {
const entity = keyEntities[key];
const {
level
} = entity;
let levelSet = levelEntities.get(level);
if (!levelSet) {
levelSet = new Set();
levelEntities.set(level, levelSet);
}
levelSet.add(entity);
maxLevel = Math.max(maxLevel, level);
});
warning(!warningMissKeys.length, `Tree missing follow keys: ${warningMissKeys.slice(0, 100).map(key => `'${key}'`).join(', ')}`);
let result;
if (checked === true) {
result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled);
} else {
result = cleanConductCheck(keys, checked.halfCheckedKeys, levelEntities, maxLevel, syntheticGetCheckDisabled);
}
return result;
}
@@ -0,0 +1,7 @@
/// <reference types="react" />
import type { Key, FlattenNode } from '../interface';
export declare function findExpandedKeys(prev?: Key[], next?: Key[]): {
add: boolean;
key: import("react").Key;
};
export declare function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: Key): FlattenNode<import("../interface").DataNode>[];
+38
View File
@@ -0,0 +1,38 @@
export function findExpandedKeys(prev = [], next = []) {
const prevLen = prev.length;
const nextLen = next.length;
if (Math.abs(prevLen - nextLen) !== 1) {
return {
add: false,
key: null
};
}
function find(shorter, longer) {
const cache = new Map();
shorter.forEach(key => {
cache.set(key, true);
});
const keys = longer.filter(key => !cache.has(key));
return keys.length === 1 ? keys[0] : null;
}
if (prevLen < nextLen) {
return {
add: true,
key: find(prev, next)
};
}
return {
add: false,
key: find(next, prev)
};
}
export function getExpandRange(shorter, longer, key) {
const shorterStartIndex = shorter.findIndex(data => data.key === key);
const shorterEndNode = shorter[shorterStartIndex + 1];
const longerStartIndex = longer.findIndex(data => data.key === key);
if (shorterEndNode) {
const longerEndIndex = longer.findIndex(data => data.key === shorterEndNode.key);
return longer.slice(longerStartIndex + 1, longerEndIndex);
}
return longer.slice(longerStartIndex + 1);
}
@@ -0,0 +1,2 @@
import type { Key, KeyEntities } from '../interface';
export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: Key): import("../interface").DataEntity<T>;
+3
View File
@@ -0,0 +1,3 @@
export default function getEntity(keyEntities, key) {
return keyEntities[key];
}
+87
View File
@@ -0,0 +1,87 @@
import * as React from 'react';
import type { BasicDataNode, DataEntity, DataNode, EventDataNode, FieldNames, FlattenNode, GetKey, Key, KeyEntities, NodeElement, TreeNodeProps } from '../interface';
export declare function getPosition(level: string | number, index: number): string;
export declare function isTreeNode(node: NodeElement): boolean;
export declare function getKey(key: Key, pos: string): React.Key;
export declare function fillFieldNames(fieldNames?: FieldNames): Required<FieldNames>;
/**
* Warning if TreeNode do not provides key
*/
export declare function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames): void;
/**
* Convert `children` of Tree into `treeData` structure.
*/
export declare function convertTreeToData(rootNodes: React.ReactNode): DataNode[];
/**
* Flat nest tree data into flatten list. This is used for virtual list render.
* @param treeNodeList Origin data node list
* @param expandedKeys
* need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
*/
export declare function flattenTreeData<TreeDataType extends BasicDataNode = DataNode>(treeNodeList: TreeDataType[], expandedKeys: Key[] | true, fieldNames: FieldNames): FlattenNode<TreeDataType>[];
type ExternalGetKey = GetKey<DataNode> | string;
interface TraverseDataNodesConfig {
childrenPropName?: string;
externalGetKey?: ExternalGetKey;
fieldNames?: FieldNames;
}
/**
* Traverse all the data by `treeData`.
* Please not use it out of the `rc-tree` since we may refactor this code.
*/
export declare function traverseDataNodes(dataNodes: DataNode[], callback: (data: {
node: DataNode;
index: number;
pos: string;
key: Key;
parentPos: string | number;
level: number;
nodes: DataNode[];
}) => void, config?: TraverseDataNodesConfig | string): void;
interface Wrapper {
posEntities: Record<string, DataEntity>;
keyEntities: KeyEntities;
}
/**
* Convert `treeData` into entity records.
*/
export declare function convertDataToEntities(dataNodes: DataNode[], { initWrapper, processEntity, onProcessFinished, externalGetKey, childrenPropName, fieldNames, }?: {
initWrapper?: (wrapper: Wrapper) => Wrapper;
processEntity?: (entity: DataEntity, wrapper: Wrapper) => void;
onProcessFinished?: (wrapper: Wrapper) => void;
externalGetKey?: ExternalGetKey;
childrenPropName?: string;
fieldNames?: FieldNames;
},
/** @deprecated Use `config.externalGetKey` instead */
legacyExternalGetKey?: ExternalGetKey): Wrapper;
export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
expandedKeys: Key[];
selectedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
checkedKeys: Key[];
halfCheckedKeys: Key[];
dragOverNodeKey: Key;
dropPosition: number;
keyEntities: KeyEntities<TreeDataType>;
}
export declare function isLeafNode<TreeDataType extends BasicDataNode = DataNode>(isLeaf: boolean | undefined, loadData: ((node: EventDataNode<TreeDataType>) => Promise<any>) | undefined, hasChildren: boolean, loaded: boolean): boolean;
/**
* Get TreeNode props with Tree props.
*/
export declare function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(key: Key, { expandedKeys, selectedKeys, loadedKeys, loadingKeys, checkedKeys, halfCheckedKeys, dragOverNodeKey, dropPosition, keyEntities, }: TreeNodeRequiredProps<TreeDataType>): {
eventKey: React.Key;
expanded: boolean;
selected: boolean;
loaded: boolean;
loading: boolean;
checked: boolean;
halfChecked: boolean;
pos: string;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
};
export declare function convertNodePropsToEventData<TreeDataType extends BasicDataNode = DataNode>(props: TreeNodeProps<TreeDataType>): EventDataNode<TreeDataType>;
export {};
+359
View File
@@ -0,0 +1,359 @@
import toArray from "@rc-component/util/es/Children/toArray";
import omit from "@rc-component/util/es/omit";
import warning from "@rc-component/util/es/warning";
import getEntity from "./keyUtil";
export function getPosition(level, index) {
return `${level}-${index}`;
}
export function isTreeNode(node) {
return node && node.type && node.type.isTreeNode;
}
export function getKey(key, pos) {
if (key !== null && key !== undefined) {
return key;
}
return pos;
}
export function fillFieldNames(fieldNames) {
const {
title,
_title,
key,
children
} = fieldNames || {};
const mergedTitle = title || 'title';
return {
title: mergedTitle,
_title: _title || [mergedTitle],
key: key || 'key',
children: children || 'children'
};
}
/**
* Warning if TreeNode do not provides key
*/
export function warningWithoutKey(treeData, fieldNames) {
const keys = new Map();
function dig(list, path = '') {
(list || []).forEach(treeNode => {
const key = treeNode[fieldNames.key];
const children = treeNode[fieldNames.children];
warning(key !== null && key !== undefined, `Tree node must have a certain key: [${path}${key}]`);
const recordKey = String(key);
warning(!keys.has(recordKey) || key === null || key === undefined, `Same 'key' exist in the Tree: ${recordKey}`);
keys.set(recordKey, true);
dig(children, `${path}${recordKey} > `);
});
}
dig(treeData);
}
/**
* Convert `children` of Tree into `treeData` structure.
*/
export function convertTreeToData(rootNodes) {
function dig(node) {
const treeNodes = toArray(node);
return treeNodes.map(treeNode => {
// Filter invalidate node
if (!isTreeNode(treeNode)) {
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
return null;
}
const {
key
} = treeNode;
const {
children,
...rest
} = treeNode.props;
const dataNode = {
key: key,
...rest
};
const parsedChildren = dig(children);
if (parsedChildren.length) {
dataNode.children = parsedChildren;
}
return dataNode;
}).filter(dataNode => dataNode);
}
return dig(rootNodes);
}
/**
* Flat nest tree data into flatten list. This is used for virtual list render.
* @param treeNodeList Origin data node list
* @param expandedKeys
* need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
*/
export function flattenTreeData(treeNodeList, expandedKeys, fieldNames) {
const {
_title: fieldTitles,
key: fieldKey,
children: fieldChildren
} = fillFieldNames(fieldNames);
const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
const flattenList = [];
function dig(list, parent = null) {
return list.map((treeNode, index) => {
const pos = getPosition(parent ? parent.pos : '0', index);
const mergedKey = getKey(treeNode[fieldKey], pos);
// Pick matched title in field title list
let mergedTitle;
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
// We use `Object.assign` here to save perf since babel's `objectSpread` has perf issue
const flattenNode = Object.assign(omit(treeNode, [...fieldTitles, fieldKey, fieldChildren]), {
title: mergedTitle,
key: mergedKey,
parent,
pos,
children: null,
data: treeNode,
isStart: [...(parent ? parent.isStart : []), index === 0],
isEnd: [...(parent ? parent.isEnd : []), index === list.length - 1]
});
flattenList.push(flattenNode);
// Loop treeNode children
if (expandedKeys === true || expandedKeySet.has(mergedKey)) {
flattenNode.children = dig(treeNode[fieldChildren] || [], flattenNode);
} else {
flattenNode.children = [];
}
return flattenNode;
});
}
dig(treeNodeList);
return flattenList;
}
/**
* Traverse all the data by `treeData`.
* Please not use it out of the `rc-tree` since we may refactor this code.
*/
export function traverseDataNodes(dataNodes, callback,
// To avoid too many params, let use config instead of origin param
config) {
let mergedConfig = {};
if (typeof config === 'object') {
mergedConfig = config;
} else {
mergedConfig = {
externalGetKey: config
};
}
mergedConfig = mergedConfig || {};
// Init config
const {
childrenPropName,
externalGetKey,
fieldNames
} = mergedConfig;
const {
key: fieldKey,
children: fieldChildren
} = fillFieldNames(fieldNames);
const mergeChildrenPropName = childrenPropName || fieldChildren;
// Get keys
let syntheticGetKey;
if (externalGetKey) {
if (typeof externalGetKey === 'string') {
syntheticGetKey = node => node[externalGetKey];
} else if (typeof externalGetKey === 'function') {
syntheticGetKey = node => externalGetKey(node);
}
} else {
syntheticGetKey = (node, pos) => getKey(node[fieldKey], pos);
}
// Process
function processNode(node, index, parent, pathNodes) {
const children = node ? node[mergeChildrenPropName] : dataNodes;
const pos = node ? getPosition(parent.pos, index) : '0';
const connectNodes = node ? [...pathNodes, node] : [];
// Process node if is not root
if (node) {
const key = syntheticGetKey(node, pos);
const data = {
node,
index,
pos,
key,
parentPos: parent.node ? parent.pos : null,
level: parent.level + 1,
nodes: connectNodes
};
callback(data);
}
// Process children node
if (children) {
children.forEach((subNode, subIndex) => {
processNode(subNode, subIndex, {
node,
pos,
level: parent ? parent.level + 1 : -1
}, connectNodes);
});
}
}
processNode(null);
}
/**
* Convert `treeData` into entity records.
*/
export function convertDataToEntities(dataNodes, {
initWrapper,
processEntity,
onProcessFinished,
externalGetKey,
childrenPropName,
fieldNames
} = {}, /** @deprecated Use `config.externalGetKey` instead */
legacyExternalGetKey) {
// Init config
const mergedExternalGetKey = externalGetKey || legacyExternalGetKey;
const posEntities = {};
const keyEntities = {};
let wrapper = {
posEntities,
keyEntities
};
if (initWrapper) {
wrapper = initWrapper(wrapper) || wrapper;
}
traverseDataNodes(dataNodes, item => {
const {
node,
index,
pos,
key,
parentPos,
level,
nodes
} = item;
const entity = {
node,
nodes,
index,
key,
pos,
level
};
const mergedKey = getKey(key, pos);
posEntities[pos] = entity;
keyEntities[mergedKey] = entity;
// Fill children
entity.parent = posEntities[parentPos];
if (entity.parent) {
entity.parent.children = entity.parent.children || [];
entity.parent.children.push(entity);
}
if (processEntity) {
processEntity(entity, wrapper);
}
}, {
externalGetKey: mergedExternalGetKey,
childrenPropName,
fieldNames
});
if (onProcessFinished) {
onProcessFinished(wrapper);
}
return wrapper;
}
export function isLeafNode(isLeaf, loadData, hasChildren, loaded) {
if (isLeaf === false) {
return false;
}
return isLeaf || !loadData && !hasChildren || loadData && loaded && !hasChildren;
}
/**
* Get TreeNode props with Tree props.
*/
export function getTreeNodeProps(key, {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities
}) {
const entity = getEntity(keyEntities, key);
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,
pos: String(entity ? entity.pos : ''),
// [Legacy] Drag props
// Since the interaction of drag is changed, the semantic of the props are
// not accuracy, I think it should be finally removed
dragOver: dragOverNodeKey === key && dropPosition === 0,
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1
};
return treeNodeProps;
}
export function convertNodePropsToEventData(props) {
const {
data,
expanded,
selected,
checked,
loaded,
loading,
halfChecked,
dragOver,
dragOverGapTop,
dragOverGapBottom,
pos,
active,
eventKey
} = props;
const eventData = {
...data,
expanded,
selected,
checked,
loaded,
loading,
halfChecked,
dragOver,
dragOverGapTop,
dragOverGapBottom,
pos,
active,
key: eventKey
};
if (!('props' in eventData)) {
Object.defineProperty(eventData, 'props', {
get() {
warning(false, 'Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`.');
return props;
}
});
}
return eventData;
}
@@ -0,0 +1,8 @@
import React from 'react';
export interface DropIndicatorProps {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
indent: number;
}
declare const DropIndicator: React.FC<Readonly<DropIndicatorProps>>;
export default DropIndicator;
+43
View File
@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const DropIndicator = props => {
const {
dropPosition,
dropLevelOffset,
indent
} = props;
const style = {
pointerEvents: 'none',
position: 'absolute',
right: 0,
backgroundColor: 'red',
height: 2
};
switch (dropPosition) {
case -1:
style.top = 0;
style.left = -dropLevelOffset * indent;
break;
case 1:
style.bottom = 0;
style.left = -dropLevelOffset * indent;
break;
case 0:
style.bottom = 0;
style.left = indent;
break;
}
return /*#__PURE__*/_react.default.createElement("div", {
style: style
});
};
if (process.env.NODE_ENV !== 'production') {
DropIndicator.displayName = 'DropIndicator';
}
var _default = exports.default = DropIndicator;
+9
View File
@@ -0,0 +1,9 @@
import * as React from 'react';
interface IndentProps {
prefixCls: string;
level: number;
isStart: boolean[];
isEnd: boolean[];
}
declare const _default: React.NamedExoticComponent<IndentProps>;
export default _default;
+33
View File
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Indent = ({
prefixCls,
level,
isStart,
isEnd
}) => {
const baseClassName = `${prefixCls}-indent-unit`;
const list = [];
for (let i = 0; i < level; i += 1) {
list.push( /*#__PURE__*/React.createElement("span", {
key: i,
className: (0, _clsx.clsx)(baseClassName, {
[`${baseClassName}-start`]: isStart[i],
[`${baseClassName}-end`]: isEnd[i]
})
}));
}
return /*#__PURE__*/React.createElement("span", {
"aria-hidden": "true",
className: `${prefixCls}-indent`
}, list);
};
var _default = exports.default = /*#__PURE__*/React.memo(Indent);
@@ -0,0 +1,14 @@
import * as React from 'react';
import type { FlattenNode, TreeNodeProps } from './interface';
import { type TreeNodeRequiredProps } from './utils/treeUtil';
interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
active: boolean;
motion?: any;
motionNodes?: FlattenNode[];
onMotionStart: () => void;
onMotionEnd: () => void;
motionType?: 'show' | 'hide';
treeNodeRequiredProps: TreeNodeRequiredProps;
}
declare const MotionTreeNode: React.ForwardRefExoticComponent<MotionTreeNodeProps & React.RefAttributes<HTMLDivElement>>;
export default MotionTreeNode;
+118
View File
@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _motion = _interopRequireDefault(require("@rc-component/motion"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var React = _interopRequireWildcard(require("react"));
var _contextTypes = require("./contextTypes");
var _TreeNode = _interopRequireDefault(require("./TreeNode"));
var _useUnmount = _interopRequireDefault(require("./useUnmount"));
var _treeUtil = require("./utils/treeUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const MotionTreeNode = /*#__PURE__*/React.forwardRef((oriProps, ref) => {
const {
className,
style,
motion,
motionNodes,
motionType,
onMotionStart: onOriginMotionStart,
onMotionEnd: onOriginMotionEnd,
active,
treeNodeRequiredProps,
...props
} = oriProps;
const [visible, setVisible] = React.useState(true);
const {
prefixCls
} = React.useContext(_contextTypes.TreeContext);
// Calculate target visible here.
// And apply in effect to make `leave` motion work.
const targetVisible = motionNodes && motionType !== 'hide';
(0, _useLayoutEffect.default)(() => {
if (motionNodes) {
if (targetVisible !== visible) {
setVisible(targetVisible);
}
}
}, [motionNodes]);
const triggerMotionStart = () => {
if (motionNodes) {
onOriginMotionStart();
}
};
// Should only trigger once
const triggerMotionEndRef = React.useRef(false);
const triggerMotionEnd = () => {
if (motionNodes && !triggerMotionEndRef.current) {
triggerMotionEndRef.current = true;
onOriginMotionEnd();
}
};
// Effect if unmount
(0, _useUnmount.default)(triggerMotionStart, triggerMotionEnd);
// Motion end event
const onVisibleChanged = nextVisible => {
if (targetVisible === nextVisible) {
triggerMotionEnd();
}
};
if (motionNodes) {
return /*#__PURE__*/React.createElement(_motion.default, _extends({
ref: ref,
visible: visible
}, motion, {
motionAppear: motionType === 'show',
onVisibleChanged: onVisibleChanged
}), ({
className: motionClassName,
style: motionStyle
}, motionRef) => /*#__PURE__*/React.createElement("div", {
ref: motionRef,
className: (0, _clsx.clsx)(`${prefixCls}-treenode-motion`, motionClassName),
style: motionStyle
}, motionNodes.map(treeNode => {
const {
data: {
...restProps
},
title,
key,
isStart,
isEnd
} = treeNode;
delete restProps.children;
const treeNodeProps = (0, _treeUtil.getTreeNodeProps)(key, treeNodeRequiredProps);
return /*#__PURE__*/React.createElement(_TreeNode.default, _extends({}, restProps, treeNodeProps, {
title: title,
active: active,
data: treeNode.data,
key: key,
isStart: isStart,
isEnd: isEnd
}));
})));
}
return /*#__PURE__*/React.createElement(_TreeNode.default, _extends({
domRef: ref,
className: className,
style: style
}, props, {
active: active
}));
});
if (process.env.NODE_ENV !== 'production') {
MotionTreeNode.displayName = 'MotionTreeNode';
}
var _default = exports.default = MotionTreeNode;
+51
View File
@@ -0,0 +1,51 @@
/**
* Handle virtual list of the TreeNodes.
*/
import * as React from 'react';
import type { BasicDataNode, DataEntity, DataNode, FlattenNode, Key, KeyEntities, ScrollTo } from './interface';
export declare const MOTION_KEY: string;
export declare const MotionEntity: DataEntity;
export interface NodeListRef {
scrollTo: ScrollTo;
getIndentWidth: () => number;
}
interface NodeListProps<TreeDataType extends BasicDataNode> {
prefixCls: string;
style: React.CSSProperties;
data: FlattenNode<TreeDataType>[];
motion: any;
focusable?: boolean;
activeItem: FlattenNode<TreeDataType>;
tabIndex: number;
checkable?: boolean;
selectable?: boolean;
disabled?: boolean;
expandedKeys: Key[];
selectedKeys: Key[];
checkedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
halfCheckedKeys: Key[];
keyEntities: KeyEntities;
dragging: boolean;
dragOverNodeKey: Key;
dropPosition: number;
height: number;
itemHeight: number;
virtual?: boolean;
scrollWidth?: number;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
onFocus?: React.FocusEventHandler<HTMLDivElement>;
onBlur?: React.FocusEventHandler<HTMLDivElement>;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseUp?: React.MouseEventHandler<HTMLDivElement>;
onActiveChange: (key: Key) => void;
onListChangeStart: () => void;
onListChangeEnd: () => void;
}
/**
* We only need get visible content items to play the animation.
*/
export declare function getMinimumRangeTransitionRange(list: FlattenNode[], virtual: boolean, height: number, itemHeight: number): FlattenNode<DataNode>[];
declare const NodeList: React.ForwardRefExoticComponent<NodeListProps<any> & React.RefAttributes<NodeListRef>>;
export default NodeList;
+260
View File
@@ -0,0 +1,260 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.MotionEntity = exports.MOTION_KEY = void 0;
exports.getMinimumRangeTransitionRange = getMinimumRangeTransitionRange;
var _useId = _interopRequireWildcard(require("@rc-component/util/lib/hooks/useId"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var _virtualList = _interopRequireDefault(require("@rc-component/virtual-list"));
var React = _interopRequireWildcard(require("react"));
var _MotionTreeNode = _interopRequireDefault(require("./MotionTreeNode"));
var _diffUtil = require("./utils/diffUtil");
var _treeUtil = require("./utils/treeUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /**
* Handle virtual list of the TreeNodes.
*/
const MOTION_KEY = exports.MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`;
const MotionNode = {
key: MOTION_KEY
};
const MotionEntity = exports.MotionEntity = {
key: MOTION_KEY,
level: 0,
index: 0,
pos: '0',
node: MotionNode,
nodes: [MotionNode]
};
const MotionFlattenData = {
parent: null,
children: [],
pos: MotionEntity.pos,
data: MotionNode,
title: null,
key: MOTION_KEY,
/** Hold empty list here since we do not use it */
isStart: [],
isEnd: []
};
/**
* We only need get visible content items to play the animation.
*/
function getMinimumRangeTransitionRange(list, virtual, height, itemHeight) {
if (virtual === false || !height) {
return list;
}
return list.slice(0, Math.ceil(height / itemHeight) + 1);
}
function itemKey(item) {
const {
key,
pos
} = item;
return (0, _treeUtil.getKey)(key, pos);
}
const NodeList = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
data,
selectable,
checkable,
expandedKeys,
selectedKeys,
checkedKeys,
loadedKeys,
loadingKeys,
halfCheckedKeys,
keyEntities,
disabled,
dragging,
dragOverNodeKey,
dropPosition,
motion,
height,
itemHeight,
virtual,
scrollWidth,
focusable,
activeItem,
tabIndex,
onKeyDown,
onFocus,
onBlur,
onMouseDown,
onMouseUp,
onActiveChange,
onListChangeStart,
onListChangeEnd,
...domProps
} = props;
const treeId = (0, _useId.default)();
// =============================== Ref ================================
const listRef = React.useRef(null);
const indentMeasurerRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
scrollTo: scroll => {
listRef.current.scrollTo(scroll);
},
getIndentWidth: () => indentMeasurerRef.current.offsetWidth
}));
// ============================== Motion ==============================
const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
const [prevData, setPrevData] = React.useState(data);
const [transitionData, setTransitionData] = React.useState(data);
const [transitionRange, setTransitionRange] = React.useState([]);
const [motionType, setMotionType] = React.useState(null);
// When motion end but data change, this will makes data back to previous one
const dataRef = React.useRef(data);
dataRef.current = data;
function onMotionEnd() {
const latestData = dataRef.current;
setPrevData(latestData);
setTransitionData(latestData);
setTransitionRange([]);
setMotionType(null);
onListChangeEnd();
}
// Do animation if expanded keys changed
// layoutEffect here to avoid blink of node removing
(0, _useLayoutEffect.default)(() => {
setPrevExpandedKeys(expandedKeys);
const diffExpanded = (0, _diffUtil.findExpandedKeys)(prevExpandedKeys, expandedKeys);
if (diffExpanded.key !== null) {
if (diffExpanded.add) {
const keyIndex = prevData.findIndex(({
key
}) => key === diffExpanded.key);
const rangeNodes = getMinimumRangeTransitionRange((0, _diffUtil.getExpandRange)(prevData, data, diffExpanded.key), virtual, height, itemHeight);
const newTransitionData = prevData.slice();
newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);
setTransitionData(newTransitionData);
setTransitionRange(rangeNodes);
setMotionType('show');
} else {
const keyIndex = data.findIndex(({
key
}) => key === diffExpanded.key);
const rangeNodes = getMinimumRangeTransitionRange((0, _diffUtil.getExpandRange)(data, prevData, diffExpanded.key), virtual, height, itemHeight);
const newTransitionData = data.slice();
newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);
setTransitionData(newTransitionData);
setTransitionRange(rangeNodes);
setMotionType('hide');
}
} else if (prevData !== data) {
// If whole data changed, we just refresh the list
setPrevData(data);
setTransitionData(data);
}
}, [expandedKeys, data]);
// We should clean up motion if is changed by dragging
React.useEffect(() => {
if (!dragging) {
onMotionEnd();
}
}, [dragging]);
const mergedData = motion ? transitionData : data;
const treeNodeRequiredProps = {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities
};
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-treenode`,
"aria-hidden": true,
style: {
position: 'absolute',
pointerEvents: 'none',
visibility: 'hidden',
height: 0,
overflow: 'hidden',
border: 0,
padding: 0
}
}, /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-indent`
}, /*#__PURE__*/React.createElement("div", {
ref: indentMeasurerRef,
className: `${prefixCls}-indent-unit`
}))), /*#__PURE__*/React.createElement(_virtualList.default, _extends({}, domProps, {
data: mergedData,
itemKey: itemKey,
height: height,
fullHeight: false,
virtual: virtual,
itemHeight: itemHeight,
scrollWidth: scrollWidth,
prefixCls: `${prefixCls}-list`,
ref: listRef,
role: "tree",
tabIndex: focusable !== false && !disabled ? tabIndex : undefined,
"aria-activedescendant": activeItem ? (0, _useId.getId)(treeId, activeItem.key) : undefined,
onKeyDown: onKeyDown,
onFocus: onFocus,
onBlur: onBlur,
onMouseDown: onMouseDown,
onMouseUp: onMouseUp,
onVisibleChange: originList => {
// The best match is using `fullList` - `originList` = `restList`
// and check the `restList` to see if has the MOTION_KEY node
// but this will cause performance issue for long list compare
// we just check `originList` and repeat trigger `onMotionEnd`
if (originList.every(item => itemKey(item) !== MOTION_KEY)) {
onMotionEnd();
}
}
}), treeNode => {
const {
pos,
data: {
...restProps
},
title,
key,
isStart,
isEnd
} = treeNode;
const mergedKey = (0, _treeUtil.getKey)(key, pos);
delete restProps.key;
delete restProps.children;
const treeNodeProps = (0, _treeUtil.getTreeNodeProps)(mergedKey, treeNodeRequiredProps);
return /*#__PURE__*/React.createElement(_MotionTreeNode.default, _extends({}, restProps, treeNodeProps, {
title: title,
active: !!activeItem && key === activeItem.key,
pos: pos,
data: treeNode.data,
isStart: isStart,
isEnd: isEnd,
motion: motion,
motionNodes: key === MOTION_KEY ? transitionRange : null,
motionType: motionType,
onMotionStart: onListChangeStart,
onMotionEnd: onMotionEnd,
treeNodeRequiredProps: treeNodeRequiredProps,
treeId: treeId,
onMouseMove: () => {
onActiveChange(null);
}
}));
}));
});
if (process.env.NODE_ENV !== 'production') {
NodeList.displayName = 'NodeList';
}
var _default = exports.default = NodeList;
+252
View File
@@ -0,0 +1,252 @@
import * as React from 'react';
import type { NodeDragEventHandler, NodeDragEventParams, NodeMouseEventHandler, NodeMouseEventParams } from './contextTypes';
import type { DropIndicatorProps } from './DropIndicator';
import type { BasicDataNode, DataNode, Direction, EventDataNode, FieldNames, FlattenNode, IconType, Key, KeyEntities, SafeKey, ScrollTo, TreeNodeProps } from './interface';
import { type NodeListRef } from './NodeList';
export interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
event: 'check';
node: EventDataNode<TreeDataType>;
checked: boolean;
nativeEvent: MouseEvent;
checkedNodes: TreeDataType[];
checkedNodesPositions?: {
node: TreeDataType;
pos: string;
}[];
halfCheckedKeys?: Key[];
}
export interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
dragNode: TreeDataType;
dropNode: TreeDataType;
dropPosition: -1 | 0 | 1;
}
export type AllowDrop<TreeDataType extends BasicDataNode = DataNode> = (options: AllowDropOptions<TreeDataType>) => boolean;
export type DraggableFn = (node: DataNode) => boolean;
export type DraggableConfig = {
icon?: React.ReactNode | false;
nodeDraggable?: DraggableFn;
};
export type ExpandAction = false | 'click' | 'doubleClick';
export type SemanticName = 'itemIcon' | 'item' | 'itemTitle';
export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
prefixCls: string;
className?: string;
style?: React.CSSProperties;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
classNames?: Partial<Record<SemanticName, string>>;
focusable?: boolean;
activeKey?: Key | null;
tabIndex?: number;
children?: React.ReactNode;
treeData?: TreeDataType[];
fieldNames?: FieldNames;
showLine?: boolean;
showIcon?: boolean;
icon?: IconType;
selectable?: boolean;
expandAction?: ExpandAction;
disabled?: boolean;
multiple?: boolean;
checkable?: boolean | React.ReactNode;
checkStrictly?: boolean;
draggable?: DraggableFn | boolean | DraggableConfig;
defaultExpandParent?: boolean;
autoExpandParent?: boolean;
defaultExpandAll?: boolean;
defaultExpandedKeys?: Key[];
expandedKeys?: Key[];
defaultCheckedKeys?: Key[];
checkedKeys?: Key[] | {
checked: Key[];
halfChecked: Key[];
};
defaultSelectedKeys?: Key[];
selectedKeys?: Key[];
allowDrop?: AllowDrop<TreeDataType>;
titleRender?: (node: TreeDataType) => React.ReactNode;
dropIndicatorRender?: (props: DropIndicatorProps) => React.ReactNode;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseUp?: React.MouseEventHandler<HTMLDivElement>;
onFocus?: React.FocusEventHandler<HTMLDivElement>;
onBlur?: React.FocusEventHandler<HTMLDivElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
onClick?: NodeMouseEventHandler<TreeDataType>;
onDoubleClick?: NodeMouseEventHandler<TreeDataType>;
onScroll?: React.UIEventHandler<HTMLElement>;
onExpand?: (expandedKeys: Key[], info: {
node: EventDataNode<TreeDataType>;
expanded: boolean;
nativeEvent: MouseEvent;
}) => void;
onCheck?: (checked: {
checked: Key[];
halfChecked: Key[];
} | Key[], info: CheckInfo<TreeDataType>) => void;
onSelect?: (selectedKeys: Key[], info: {
event: 'select';
selected: boolean;
node: EventDataNode<TreeDataType>;
selectedNodes: TreeDataType[];
nativeEvent: MouseEvent;
}) => void;
onLoad?: (loadedKeys: Key[], info: {
event: 'load';
node: EventDataNode<TreeDataType>;
}) => void;
loadData?: (treeNode: EventDataNode<TreeDataType>) => Promise<any>;
loadedKeys?: Key[];
onMouseEnter?: (info: NodeMouseEventParams<TreeDataType>) => void;
onMouseLeave?: (info: NodeMouseEventParams<TreeDataType>) => void;
onRightClick?: (info: {
event: React.MouseEvent;
node: EventDataNode<TreeDataType>;
}) => void;
onDragStart?: (info: NodeDragEventParams<TreeDataType>) => void;
onDragEnter?: (info: NodeDragEventParams<TreeDataType> & {
expandedKeys: Key[];
}) => void;
onDragOver?: (info: NodeDragEventParams<TreeDataType>) => void;
onDragLeave?: (info: NodeDragEventParams<TreeDataType>) => void;
onDragEnd?: (info: NodeDragEventParams<TreeDataType>) => void;
onDrop?: (info: NodeDragEventParams<TreeDataType> & {
dragNode: EventDataNode<TreeDataType>;
dragNodesKeys: Key[];
dropPosition: number;
dropToGap: boolean;
}) => void;
/**
* Used for `rc-tree-select` only.
* Do not use in your production code directly since this will be refactor.
*/
onActiveChange?: (key: Key) => void;
filterTreeNode?: (treeNode: EventDataNode<TreeDataType>) => boolean;
motion?: any;
switcherIcon?: IconType;
height?: number;
itemHeight?: number;
scrollWidth?: number;
itemScrollOffset?: number;
virtual?: boolean;
direction?: Direction;
rootClassName?: string;
rootStyle?: React.CSSProperties;
}
interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
keyEntities: KeyEntities<TreeDataType>;
indent: number | null;
selectedKeys: Key[];
checkedKeys: Key[];
halfCheckedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
expandedKeys: Key[];
draggingNodeKey: Key;
dragChildrenKeys: Key[];
dropPosition: -1 | 0 | 1 | null;
dropLevelOffset: number | null;
dropContainerKey: Key | null;
dropTargetKey: Key | null;
dropTargetPos: string | null;
dropAllowed: boolean;
dragOverNodeKey: Key | null;
treeData: TreeDataType[];
flattenNodes: FlattenNode<TreeDataType>[];
activeKey: Key | null;
listChanging: boolean;
prevProps: TreeProps;
fieldNames: FieldNames;
}
declare class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<TreeProps<TreeDataType>, TreeState<TreeDataType>> {
static defaultProps: {
prefixCls: string;
showLine: boolean;
showIcon: boolean;
selectable: boolean;
multiple: boolean;
checkable: boolean;
disabled: boolean;
checkStrictly: boolean;
draggable: boolean;
defaultExpandParent: boolean;
autoExpandParent: boolean;
defaultExpandAll: boolean;
defaultExpandedKeys: any[];
defaultCheckedKeys: any[];
defaultSelectedKeys: any[];
dropIndicatorRender: React.FC<Readonly<DropIndicatorProps>>;
allowDrop: () => boolean;
expandAction: boolean;
};
static TreeNode: React.FC<Readonly<TreeNodeProps<DataNode>>>;
destroyed: boolean;
delayedDragEnterLogic: Record<SafeKey, number>;
loadingRetryTimes: Record<SafeKey, number>;
state: TreeState<TreeDataType>;
dragStartMousePosition: any;
dragNodeProps: TreeNodeProps<TreeDataType>;
currentMouseOverDroppableNodeKey: any;
focusedByMouse: boolean;
listRef: React.RefObject<NodeListRef>;
componentDidMount(): void;
componentDidUpdate(): void;
onUpdated(): void;
componentWillUnmount(): void;
static getDerivedStateFromProps(props: TreeProps, prevState: TreeState): Partial<TreeState<DataNode>>;
onNodeDragStart: NodeDragEventHandler<TreeDataType, HTMLDivElement>;
/**
* [Legacy] Select handler is smaller than node,
* so that this will trigger when drag enter node or select handler.
* This is a little tricky if customize css without padding.
* Better for use mouse move event to refresh drag state.
* But let's just keep it to avoid event trigger logic change.
*/
onNodeDragEnter: (event: React.DragEvent<HTMLDivElement>, nodeProps: TreeNodeProps<TreeDataType>) => void;
onNodeDragOver: (event: React.DragEvent<HTMLDivElement>, nodeProps: TreeNodeProps<TreeDataType>) => void;
onNodeDragLeave: NodeDragEventHandler<TreeDataType>;
onWindowDragEnd: (event: any) => void;
onNodeDragEnd: NodeDragEventHandler<TreeDataType>;
onNodeDrop: (event: React.DragEvent<HTMLDivElement>, _: TreeNodeProps<TreeDataType>, outsideTree?: boolean) => void;
resetDragState(): void;
cleanDragState: () => void;
triggerExpandActionExpand: NodeMouseEventHandler;
onNodeClick: NodeMouseEventHandler<TreeDataType>;
onNodeDoubleClick: NodeMouseEventHandler<TreeDataType>;
onNodeSelect: NodeMouseEventHandler<TreeDataType>;
onNodeCheck: (e: React.MouseEvent<HTMLSpanElement>, treeNode: EventDataNode<TreeDataType>, checked: boolean) => void;
onNodeLoad: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
onNodeMouseEnter: NodeMouseEventHandler<TreeDataType>;
onNodeMouseLeave: NodeMouseEventHandler<TreeDataType>;
onNodeContextMenu: NodeMouseEventHandler<TreeDataType>;
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
onMouseUp: React.MouseEventHandler<HTMLDivElement>;
onFocus: React.FocusEventHandler<HTMLDivElement>;
onBlur: React.FocusEventHandler<HTMLDivElement>;
getTreeNodeRequiredProps: () => {
expandedKeys: React.Key[];
selectedKeys: React.Key[];
loadedKeys: React.Key[];
loadingKeys: React.Key[];
checkedKeys: React.Key[];
halfCheckedKeys: React.Key[];
dragOverNodeKey: React.Key;
dropPosition: 0 | 1 | -1;
keyEntities: KeyEntities<TreeDataType>;
};
/** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
setExpandedKeys: (expandedKeys: Key[]) => void;
onNodeExpand: (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode<TreeDataType>) => void;
onListChangeStart: () => void;
onListChangeEnd: () => void;
onActiveChange: (newActiveKey: Key | null) => void;
getActiveItem: () => FlattenNode<TreeDataType>;
offsetActiveKey: (offset: number) => void;
onKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
/**
* Only update the value which is not in props
*/
setUncontrolledState: (state: Partial<TreeState<TreeDataType>>, atomic?: boolean, forceState?: Partial<TreeState<TreeDataType>> | null) => void;
scrollTo: ScrollTo;
render(): React.JSX.Element;
}
export default Tree;
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
import React from 'react';
import type { TreeNodeProps } from './interface';
export type { TreeNodeProps } from './interface';
declare const TreeNode: React.FC<Readonly<TreeNodeProps>>;
export default TreeNode;
+394
View File
@@ -0,0 +1,394 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _clsx = require("clsx");
var _useId = require("@rc-component/util/lib/hooks/useId");
var _pickAttrs = _interopRequireDefault(require("@rc-component/util/lib/pickAttrs"));
var _contextTypes = require("./contextTypes");
var _Indent = _interopRequireDefault(require("./Indent"));
var _keyUtil = _interopRequireDefault(require("./utils/keyUtil"));
var _treeUtil = require("./utils/treeUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const ICON_OPEN = 'open';
const ICON_CLOSE = 'close';
const defaultTitle = '---';
const TreeNode = props => {
const {
eventKey,
className,
style,
dragOver,
dragOverGapTop,
dragOverGapBottom,
isLeaf,
isStart,
isEnd,
expanded,
selected,
checked,
halfChecked,
loading,
domRef,
active,
data,
onMouseMove,
selectable,
treeId,
...otherProps
} = props;
const nodeId = (0, _useId.getId)(treeId, eventKey);
const context = _react.default.useContext(_contextTypes.TreeContext);
const {
classNames: treeClassNames,
styles
} = context || {};
const unstableContext = _react.default.useContext(_contextTypes.UnstableContext);
const selectHandleRef = _react.default.useRef(null);
const [dragNodeHighlight, setDragNodeHighlight] = _react.default.useState(false);
// ======= State: Disabled State =======
const isDisabled = !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
const isCheckable = _react.default.useMemo(() => {
// Return false if tree or treeNode is not checkable
if (!context.checkable || props.checkable === false) {
return false;
}
return context.checkable;
}, [context.checkable, props.checkable]);
// ======= Event Handlers: Selection and Check =======
const onSelect = e => {
if (isDisabled) {
return;
}
context.onNodeSelect(e, (0, _treeUtil.convertNodePropsToEventData)(props));
};
const onCheck = e => {
if (isDisabled) {
return;
}
if (!isCheckable || props.disableCheckbox) {
return;
}
context.onNodeCheck(e, (0, _treeUtil.convertNodePropsToEventData)(props), !checked);
};
// ======= State: Selectable Check =======
const isSelectable = _react.default.useMemo(() => {
// Ignore when selectable is undefined or null
if (typeof selectable === 'boolean') {
return selectable;
}
return context.selectable;
}, [selectable, context.selectable]);
const onSelectorClick = e => {
// Click trigger before select/check operation
context.onNodeClick(e, (0, _treeUtil.convertNodePropsToEventData)(props));
if (isSelectable) {
onSelect(e);
} else {
onCheck(e);
}
};
const onSelectorDoubleClick = e => {
context.onNodeDoubleClick(e, (0, _treeUtil.convertNodePropsToEventData)(props));
};
const onMouseEnter = e => {
context.onNodeMouseEnter(e, (0, _treeUtil.convertNodePropsToEventData)(props));
};
const onMouseLeave = e => {
context.onNodeMouseLeave(e, (0, _treeUtil.convertNodePropsToEventData)(props));
};
const onContextMenu = e => {
context.onNodeContextMenu(e, (0, _treeUtil.convertNodePropsToEventData)(props));
};
// ======= Drag: Drag Enabled =======
const isDraggable = _react.default.useMemo(() => {
return !!(context.draggable && (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data)));
}, [context.draggable, data]);
// ======= Drag: Drag Event Handlers =======
const onDragStart = e => {
e.stopPropagation();
setDragNodeHighlight(true);
context.onNodeDragStart(e, props);
try {
// ie throw error
// firefox-need-it
e.dataTransfer.setData('text/plain', '');
} catch {
// empty
}
};
const onDragEnter = e => {
e.preventDefault();
e.stopPropagation();
context.onNodeDragEnter(e, props);
};
const onDragOver = e => {
e.preventDefault();
e.stopPropagation();
context.onNodeDragOver(e, props);
};
const onDragLeave = e => {
e.stopPropagation();
context.onNodeDragLeave(e, props);
};
const onDragEnd = e => {
e.stopPropagation();
setDragNodeHighlight(false);
context.onNodeDragEnd(e, props);
};
const onDrop = e => {
e.preventDefault();
e.stopPropagation();
setDragNodeHighlight(false);
context.onNodeDrop(e, props);
};
// ======= Expand: Node Expansion =======
const onExpand = e => {
if (loading) {
return;
}
context.onNodeExpand(e, (0, _treeUtil.convertNodePropsToEventData)(props));
};
// ======= State: Has Children =======
const hasChildren = _react.default.useMemo(() => {
const {
children
} = (0, _keyUtil.default)(context.keyEntities, eventKey) || {};
return Boolean((children || []).length);
}, [context.keyEntities, eventKey]);
// ======= State: Leaf Check =======
const memoizedIsLeaf = _react.default.useMemo(() => {
return (0, _treeUtil.isLeafNode)(isLeaf, context.loadData, hasChildren, props.loaded);
}, [isLeaf, context.loadData, hasChildren, props.loaded]);
// ============== Effect ==============
_react.default.useEffect(() => {
// Load data to avoid default expanded tree without data
if (loading) {
return;
}
// read from state to avoid loadData at same time
if (typeof context.loadData === 'function' && expanded && !memoizedIsLeaf && !props.loaded) {
// We needn't reload data when has children in sync logic
// It's only needed in node expanded
context.onNodeLoad((0, _treeUtil.convertNodePropsToEventData)(props));
}
}, [loading, context.loadData, context.onNodeLoad, expanded, memoizedIsLeaf, props]);
// ==================== Render: Drag Handler ====================
const dragHandlerNode = _react.default.useMemo(() => {
if (!context.draggable?.icon) {
return null;
}
return /*#__PURE__*/_react.default.createElement("span", {
className: `${context.prefixCls}-draggable-icon`
}, context.draggable.icon);
}, [context.draggable]);
// ====================== Render: Switcher ======================
const renderSwitcherIconDom = isInternalLeaf => {
const switcherIcon = props.switcherIcon || context.switcherIcon;
// if switcherIconDom is null, no render switcher span
if (typeof switcherIcon === 'function') {
return switcherIcon({
...props,
isLeaf: isInternalLeaf
});
}
return switcherIcon;
};
// Switcher
const renderSwitcher = () => {
if (memoizedIsLeaf) {
// if switcherIconDom is null, no render switcher span
const switcherIconDom = renderSwitcherIconDom(true);
return switcherIconDom !== false ? /*#__PURE__*/_react.default.createElement("span", {
className: (0, _clsx.clsx)(`${context.prefixCls}-switcher`, `${context.prefixCls}-switcher-noop`)
}, switcherIconDom) : null;
}
const switcherIconDom = renderSwitcherIconDom(false);
return switcherIconDom !== false ? /*#__PURE__*/_react.default.createElement("span", {
onClick: onExpand,
className: (0, _clsx.clsx)(`${context.prefixCls}-switcher`, `${context.prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`)
}, switcherIconDom) : null;
};
// ====================== Checkbox ======================
const checkboxNode = _react.default.useMemo(() => {
if (!isCheckable) {
return null;
}
// [Legacy] Custom element should be separate with `checkable` in future
const $custom = typeof isCheckable !== 'boolean' ? isCheckable : null;
return /*#__PURE__*/_react.default.createElement("span", {
className: (0, _clsx.clsx)(`${context.prefixCls}-checkbox`, {
[`${context.prefixCls}-checkbox-checked`]: checked,
[`${context.prefixCls}-checkbox-indeterminate`]: !checked && halfChecked,
[`${context.prefixCls}-checkbox-disabled`]: isDisabled || props.disableCheckbox
}),
onClick: onCheck,
role: "checkbox",
"aria-checked": halfChecked ? 'mixed' : checked,
"aria-disabled": isDisabled || props.disableCheckbox,
"aria-labelledby": nodeId
}, $custom);
}, [isCheckable, checked, halfChecked, isDisabled, props.disableCheckbox, nodeId]);
// ============== State: Node State (Open/Close) ==============
const nodeState = _react.default.useMemo(() => {
if (memoizedIsLeaf) {
return null;
}
return expanded ? ICON_OPEN : ICON_CLOSE;
}, [memoizedIsLeaf, expanded]);
// ==================== Render: Title + Icon ====================
const iconNode = _react.default.useMemo(() => {
return /*#__PURE__*/_react.default.createElement("span", {
className: (0, _clsx.clsx)(treeClassNames?.itemIcon, `${context.prefixCls}-iconEle`, `${context.prefixCls}-icon__${nodeState || 'docu'}`, {
[`${context.prefixCls}-icon_loading`]: loading
}),
style: styles?.itemIcon
});
}, [context.prefixCls, nodeState, loading]);
// =================== Drop Indicator ===================
const dropIndicatorNode = _react.default.useMemo(() => {
const rootDraggable = Boolean(context.draggable);
// allowDrop is calculated in Tree.tsx, there is no need for calc it here
const showIndicator = !props.disabled && rootDraggable && context.dragOverNodeKey === eventKey;
if (!showIndicator) {
return null;
}
return context.dropIndicatorRender({
dropPosition: context.dropPosition,
dropLevelOffset: context.dropLevelOffset,
indent: context.indent,
prefixCls: context.prefixCls,
direction: context.direction
});
}, [context.dropPosition, context.dropLevelOffset, context.indent, context.prefixCls, context.direction, context.draggable, context.dragOverNodeKey, context.dropIndicatorRender]);
// Icon + Title
const selectorNode = _react.default.useMemo(() => {
const {
title = defaultTitle
} = props;
const wrapClass = `${context.prefixCls}-node-content-wrapper`;
// Icon - Still show loading icon when loading without showIcon
let $icon;
if (context.showIcon) {
const currentIcon = props.icon || context.icon;
$icon = currentIcon ? /*#__PURE__*/_react.default.createElement("span", {
className: (0, _clsx.clsx)(treeClassNames?.itemIcon, `${context.prefixCls}-iconEle`, `${context.prefixCls}-icon__customize`),
style: styles?.itemIcon
}, typeof currentIcon === 'function' ? currentIcon(props) : currentIcon) : iconNode;
} else if (context.loadData && loading) {
$icon = iconNode;
}
// Title
let titleNode;
if (typeof title === 'function') {
titleNode = title(data);
} else if (context.titleRender) {
titleNode = context.titleRender(data);
} else {
titleNode = title;
}
return /*#__PURE__*/_react.default.createElement("span", {
ref: selectHandleRef,
title: typeof title === 'string' ? title : '',
className: (0, _clsx.clsx)(wrapClass, `${wrapClass}-${nodeState || 'normal'}`, {
[`${context.prefixCls}-node-selected`]: !isDisabled && (selected || dragNodeHighlight)
}),
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onContextMenu: onContextMenu,
onClick: onSelectorClick,
onDoubleClick: onSelectorDoubleClick
}, $icon, /*#__PURE__*/_react.default.createElement("span", {
className: (0, _clsx.clsx)(`${context.prefixCls}-title`, treeClassNames?.itemTitle),
style: styles?.itemTitle
}, titleNode), dropIndicatorNode);
}, [context.prefixCls, context.showIcon, props, context.icon, iconNode, context.titleRender, data, nodeState, onMouseEnter, onMouseLeave, onContextMenu, onSelectorClick, onSelectorDoubleClick]);
const dataOrAriaAttributeProps = (0, _pickAttrs.default)(otherProps, {
aria: true,
data: true
});
const {
level
} = (0, _keyUtil.default)(context.keyEntities, eventKey) || {};
const isEndNode = isEnd[isEnd.length - 1];
const draggableWithoutDisabled = !isDisabled && isDraggable;
const dragging = context.draggingNodeKey === eventKey;
return /*#__PURE__*/_react.default.createElement("div", _extends({
ref: domRef,
role: "treeitem",
id: nodeId,
"aria-expanded": memoizedIsLeaf ? undefined : expanded,
"aria-selected": isSelectable && !isDisabled ? selected : undefined,
"aria-checked": isCheckable && !isDisabled ? halfChecked ? 'mixed' : checked : undefined,
"aria-disabled": isDisabled,
className: (0, _clsx.clsx)(className, `${context.prefixCls}-treenode`, treeClassNames?.item, {
[`${context.prefixCls}-treenode-disabled`]: isDisabled,
[`${context.prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
[`${context.prefixCls}-treenode-checkbox-checked`]: checked,
[`${context.prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
[`${context.prefixCls}-treenode-selected`]: selected,
[`${context.prefixCls}-treenode-loading`]: loading,
[`${context.prefixCls}-treenode-active`]: active,
[`${context.prefixCls}-treenode-leaf-last`]: isEndNode,
[`${context.prefixCls}-treenode-draggable`]: isDraggable,
dragging,
'drop-target': context.dropTargetKey === eventKey,
'drop-container': context.dropContainerKey === eventKey,
'drag-over': !isDisabled && dragOver,
'drag-over-gap-top': !isDisabled && dragOverGapTop,
'drag-over-gap-bottom': !isDisabled && dragOverGapBottom,
'filter-node': context.filterTreeNode?.((0, _treeUtil.convertNodePropsToEventData)(props)),
[`${context.prefixCls}-treenode-leaf`]: memoizedIsLeaf
}),
style: {
...style,
...styles?.item
}
// Draggable config
,
draggable: draggableWithoutDisabled,
onDragStart: draggableWithoutDisabled ? onDragStart : undefined
// Drop config
,
onDragEnter: isDraggable ? onDragEnter : undefined,
onDragOver: isDraggable ? onDragOver : undefined,
onDragLeave: isDraggable ? onDragLeave : undefined,
onDrop: isDraggable ? onDrop : undefined,
onDragEnd: isDraggable ? onDragEnd : undefined,
onMouseMove: onMouseMove
}, dataOrAriaAttributeProps), /*#__PURE__*/_react.default.createElement(_Indent.default, {
prefixCls: context.prefixCls,
level: level,
isStart: isStart,
isEnd: isEnd
}), dragHandlerNode, renderSwitcher(), checkboxNode, selectorNode);
};
TreeNode.isTreeNode = 1;
if (process.env.NODE_ENV !== 'production') {
TreeNode.displayName = 'TreeNode';
}
var _default = exports.default = TreeNode;
+69
View File
@@ -0,0 +1,69 @@
/**
* Webpack has bug for import loop, which is not the same behavior as ES module.
* When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
*/
import * as React from 'react';
import type { BasicDataNode, DataNode, Direction, EventDataNode, IconType, Key, KeyEntities, TreeNodeProps } from './interface';
import type { DraggableConfig, SemanticName } from './Tree';
export type NodeMouseEventParams<TreeDataType extends BasicDataNode = DataNode, T = HTMLSpanElement> = {
event: React.MouseEvent<T>;
node: EventDataNode<TreeDataType>;
};
export type NodeDragEventParams<TreeDataType extends BasicDataNode = DataNode, T = HTMLDivElement> = {
event: React.DragEvent<T>;
node: EventDataNode<TreeDataType>;
};
export type NodeMouseEventHandler<TreeDataType extends BasicDataNode = DataNode, T = HTMLSpanElement> = (e: React.MouseEvent<T>, node: EventDataNode<TreeDataType>) => void;
export type NodeDragEventHandler<TreeDataType extends BasicDataNode = DataNode, T = HTMLDivElement> = (e: React.DragEvent<T>, nodeProps: TreeNodeProps<TreeDataType>, outsideTree?: boolean) => void;
export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode> {
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
classNames?: Partial<Record<SemanticName, string>>;
prefixCls: string;
selectable: boolean;
showIcon: boolean;
icon: IconType;
switcherIcon: IconType;
draggable?: DraggableConfig;
draggingNodeKey?: Key;
checkable: boolean | React.ReactNode;
checkStrictly: boolean;
disabled: boolean;
keyEntities: KeyEntities;
dropLevelOffset?: number;
dropContainerKey: Key | null;
dropTargetKey: Key | null;
dropPosition: -1 | 0 | 1 | null;
indent: number | null;
dropIndicatorRender: (props: {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
indent: number;
prefixCls: string;
direction: Direction;
}) => React.ReactNode;
dragOverNodeKey: Key | null;
direction: Direction;
loadData: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
filterTreeNode: (treeNode: EventDataNode<TreeDataType>) => boolean;
titleRender?: (node: any) => React.ReactNode;
onNodeClick: NodeMouseEventHandler<TreeDataType>;
onNodeDoubleClick: NodeMouseEventHandler<TreeDataType>;
onNodeExpand: NodeMouseEventHandler<TreeDataType>;
onNodeSelect: NodeMouseEventHandler<TreeDataType>;
onNodeCheck: (e: React.MouseEvent<HTMLSpanElement>, treeNode: EventDataNode<TreeDataType>, checked: boolean) => void;
onNodeLoad: (treeNode: EventDataNode<TreeDataType>) => void;
onNodeMouseEnter: NodeMouseEventHandler<TreeDataType>;
onNodeMouseLeave: NodeMouseEventHandler<TreeDataType>;
onNodeContextMenu: NodeMouseEventHandler<TreeDataType>;
onNodeDragStart: NodeDragEventHandler<any, any>;
onNodeDragEnter: NodeDragEventHandler<any, any>;
onNodeDragOver: NodeDragEventHandler<any, any>;
onNodeDragLeave: NodeDragEventHandler<any, any>;
onNodeDragEnd: NodeDragEventHandler<any, any>;
onNodeDrop: NodeDragEventHandler<any, any>;
}
export declare const TreeContext: React.Context<TreeContextProps<any>>;
/** Internal usage, safe to remove. Do not use in prod */
export declare const UnstableContext: React.Context<{
nodeDisabled?: (n: DataNode) => boolean;
}>;
+18
View File
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UnstableContext = exports.TreeContext = void 0;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* Webpack has bug for import loop, which is not the same behavior as ES module.
* When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
*/
const TreeContext = exports.TreeContext = /*#__PURE__*/React.createContext(null);
/** Internal usage, safe to remove. Do not use in prod */
const UnstableContext = exports.UnstableContext = /*#__PURE__*/React.createContext({});
+8
View File
@@ -0,0 +1,8 @@
import Tree from './Tree';
import TreeNode from './TreeNode';
import type { TreeProps } from './Tree';
import type { TreeNodeProps, BasicDataNode, FieldDataNode } from './interface';
import { UnstableContext } from './contextTypes';
export { TreeNode, UnstableContext };
export type { TreeProps, TreeNodeProps, BasicDataNode, FieldDataNode };
export default Tree;
+23
View File
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "TreeNode", {
enumerable: true,
get: function () {
return _TreeNode.default;
}
});
Object.defineProperty(exports, "UnstableContext", {
enumerable: true,
get: function () {
return _contextTypes.UnstableContext;
}
});
exports.default = void 0;
var _Tree = _interopRequireDefault(require("./Tree"));
var _TreeNode = _interopRequireDefault(require("./TreeNode"));
var _contextTypes = require("./contextTypes");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = exports.default = _Tree.default;
+123
View File
@@ -0,0 +1,123 @@
import * as React from 'react';
export type { ScrollTo } from '@rc-component/virtual-list/lib/List';
export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
eventKey?: Key;
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
id?: string;
treeId?: string;
expanded?: boolean;
selected?: boolean;
checked?: boolean;
loaded?: boolean;
loading?: boolean;
halfChecked?: boolean;
title?: React.ReactNode | ((data: TreeDataType) => React.ReactNode);
dragOver?: boolean;
dragOverGapTop?: boolean;
dragOverGapBottom?: boolean;
pos?: string;
domRef?: React.Ref<HTMLDivElement>;
/** New added in Tree for easy data access */
data?: TreeDataType;
isStart?: boolean[];
isEnd?: boolean[];
active?: boolean;
onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
isLeaf?: boolean;
checkable?: boolean;
selectable?: boolean;
disabled?: boolean;
disableCheckbox?: boolean;
icon?: IconType;
switcherIcon?: IconType;
children?: React.ReactNode;
}
/** For fieldNames, we provides a abstract interface */
export interface BasicDataNode {
checkable?: boolean;
disabled?: boolean;
disableCheckbox?: boolean;
icon?: IconType;
isLeaf?: boolean;
selectable?: boolean;
switcherIcon?: IconType;
/** Set style of TreeNode. This is not recommend if you don't have any force requirement */
className?: string;
style?: React.CSSProperties;
}
/** Provide a wrap type define for developer to wrap with customize fieldNames data type */
export type FieldDataNode<T, ChildFieldName extends string = 'children'> = BasicDataNode & T & Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
export type Key = React.Key;
/**
* Typescript not support `bigint` as index type yet.
* We use this to mark the `bigint` type is for `Key` usage.
* It's safe to remove this when typescript fix:
* https://github.com/microsoft/TypeScript/issues/50217
*/
export type SafeKey = Exclude<Key, bigint>;
export type KeyEntities<DateType extends BasicDataNode = any> = Record<SafeKey, DataEntity<DateType>>;
export type DataNode = FieldDataNode<{
key: Key;
title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
}>;
export type EventDataNode<TreeDataType> = {
key: Key;
expanded: boolean;
selected: boolean;
checked: boolean;
loaded: boolean;
loading: boolean;
halfChecked: boolean;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
pos: string;
active: boolean;
} & TreeDataType & BasicDataNode;
export type IconType = React.ReactNode | ((props: TreeNodeProps) => React.ReactNode);
export type NodeElement = React.ReactElement<TreeNodeProps> & {
selectHandle?: HTMLSpanElement;
type: {
isTreeNode: boolean;
};
};
export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> = React.Component<TreeNodeProps<TreeDataType>> & {
selectHandle?: HTMLSpanElement;
};
export interface Entity {
node: NodeElement;
index: number;
key: Key;
pos: string;
parent?: Entity;
children?: Entity[];
}
export interface DataEntity<TreeDataType extends BasicDataNode = DataNode> extends Omit<Entity, 'node' | 'parent' | 'children'> {
node: TreeDataType;
nodes: TreeDataType[];
parent?: DataEntity<TreeDataType>;
children?: DataEntity<TreeDataType>[];
level: number;
}
export interface FlattenNode<TreeDataType extends BasicDataNode = DataNode> {
parent: FlattenNode<TreeDataType> | null;
children: FlattenNode<TreeDataType>[];
pos: string;
data: TreeDataType;
title: React.ReactNode;
key: Key;
isStart: boolean[];
isEnd: boolean[];
}
export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
export type Direction = 'ltr' | 'rtl' | undefined;
export interface FieldNames {
title?: string;
/** @private Internal usage for `rc-tree-select`, safe to remove if no need */
_title?: string[];
key?: string;
children?: string;
}
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
+5
View File
@@ -0,0 +1,5 @@
/**
* Trigger only when component unmount
*/
declare function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidFunction): void;
export default useUnmount;
+32
View File
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* Trigger only when component unmount
*/
function useUnmount(triggerStart, triggerEnd) {
const [firstMount, setFirstMount] = React.useState(false);
(0, _useLayoutEffect.default)(() => {
if (firstMount) {
triggerStart();
return () => {
triggerEnd();
};
}
}, [firstMount]);
(0, _useLayoutEffect.default)(() => {
setFirstMount(true);
return () => {
setFirstMount(false);
};
}, []);
}
var _default = exports.default = useUnmount;
+51
View File
@@ -0,0 +1,51 @@
/**
* Legacy code. Should avoid to use if you are new to import these code.
*/
import React from 'react';
import type { BasicDataNode, DataEntity, DataNode, Direction, FlattenNode, Key, KeyEntities, NodeElement, TreeNodeProps } from './interface';
import type { AllowDrop, TreeProps } from './Tree';
export { getPosition, isTreeNode } from './utils/treeUtil';
export declare function arrDel(list: Key[], value: Key): React.Key[];
export declare function arrAdd(list: Key[], value: Key): React.Key[];
export declare function posToArr(pos: string): string[];
export declare function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(dragNodeKey: Key, keyEntities: KeyEntities<TreeDataType>): Key[];
export declare function isLastChild<TreeDataType extends BasicDataNode = DataNode>(treeNodeEntity: DataEntity<TreeDataType>): boolean;
export declare function isFirstChild<TreeDataType extends BasicDataNode = DataNode>(treeNodeEntity: DataEntity<TreeDataType>): boolean;
export declare function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(event: React.MouseEvent, dragNodeProps: TreeNodeProps<TreeDataType>, targetNodeProps: TreeNodeProps<TreeDataType>, indent: number, startMousePosition: {
x: number;
y: number;
}, allowDrop: AllowDrop<TreeDataType>, flattenedNodes: FlattenNode<TreeDataType>[], keyEntities: KeyEntities<TreeDataType>, expandKeys: Key[], direction: Direction): {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
dropTargetKey: Key;
dropTargetPos: string;
dropContainerKey: Key;
dragOverNodeKey: Key;
dropAllowed: boolean;
};
/**
* Return selectedKeys according with multiple prop
* @param selectedKeys
* @param props
* @returns [string]
*/
export declare function calcSelectedKeys(selectedKeys: Key[], props: TreeProps): React.Key[];
export declare function convertDataToTree(treeData: DataNode[], processor?: {
processProps: (prop: DataNode) => any;
}): NodeElement[];
/**
* Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
*/
export declare function parseCheckedKeys(keys: Key[] | {
checked: Key[];
halfChecked: Key[];
}): {
checkedKeys?: Key[];
halfCheckedKeys?: Key[];
};
/**
* If user use `autoExpandParent` we should get the list of parent node
* @param keyList
* @param keyEntities
*/
export declare function conductExpandParent(keyList: Key[], keyEntities: KeyEntities): Key[];
+307
View File
@@ -0,0 +1,307 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.arrAdd = arrAdd;
exports.arrDel = arrDel;
exports.calcDropPosition = calcDropPosition;
exports.calcSelectedKeys = calcSelectedKeys;
exports.conductExpandParent = conductExpandParent;
exports.convertDataToTree = convertDataToTree;
exports.getDragChildrenKeys = getDragChildrenKeys;
Object.defineProperty(exports, "getPosition", {
enumerable: true,
get: function () {
return _treeUtil.getPosition;
}
});
exports.isFirstChild = isFirstChild;
exports.isLastChild = isLastChild;
Object.defineProperty(exports, "isTreeNode", {
enumerable: true,
get: function () {
return _treeUtil.isTreeNode;
}
});
exports.parseCheckedKeys = parseCheckedKeys;
exports.posToArr = posToArr;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _react = _interopRequireDefault(require("react"));
var _TreeNode = _interopRequireDefault(require("./TreeNode"));
var _keyUtil = _interopRequireDefault(require("./utils/keyUtil"));
var _treeUtil = require("./utils/treeUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /* eslint-disable no-lonely-if */ /**
* Legacy code. Should avoid to use if you are new to import these code.
*/
function arrDel(list, value) {
if (!list) return [];
const clone = list.slice();
const index = clone.indexOf(value);
if (index >= 0) {
clone.splice(index, 1);
}
return clone;
}
function arrAdd(list, value) {
const clone = (list || []).slice();
if (clone.indexOf(value) === -1) {
clone.push(value);
}
return clone;
}
function posToArr(pos) {
return pos.split('-');
}
function getDragChildrenKeys(dragNodeKey, keyEntities) {
// not contains self
// self for left or right drag
const dragChildrenKeys = [];
const entity = (0, _keyUtil.default)(keyEntities, dragNodeKey);
function dig(list = []) {
list.forEach(({
key,
children
}) => {
dragChildrenKeys.push(key);
dig(children);
});
}
dig(entity.children);
return dragChildrenKeys;
}
function isLastChild(treeNodeEntity) {
if (treeNodeEntity.parent) {
const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
}
return false;
}
function isFirstChild(treeNodeEntity) {
const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === 0;
}
// Only used when drag, not affect SSR.
function calcDropPosition(event, dragNodeProps, targetNodeProps, indent, startMousePosition, allowDrop, flattenedNodes, keyEntities, expandKeys, direction) {
const {
clientX,
clientY
} = event;
const {
top,
height
} = event.target.getBoundingClientRect();
// optional chain for testing
const horizontalMouseOffset = (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX);
const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
// Filter the expanded keys to exclude the node that not has children currently (like async nodes).
const filteredExpandKeys = expandKeys.filter(key => keyEntities[key]?.children?.length);
// find abstract drop node by horizontal offset
let abstractDropNodeEntity = (0, _keyUtil.default)(keyEntities, targetNodeProps.eventKey);
if (clientY < top + height / 2) {
// first half, set abstract drop node to previous node
const nodeIndex = flattenedNodes.findIndex(flattenedNode => flattenedNode.key === abstractDropNodeEntity.key);
const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
const prevNodeKey = flattenedNodes[prevNodeIndex].key;
abstractDropNodeEntity = (0, _keyUtil.default)(keyEntities, prevNodeKey);
}
const initialAbstractDropNodeKey = abstractDropNodeEntity.key;
const abstractDragOverEntity = abstractDropNodeEntity;
const dragOverNodeKey = abstractDropNodeEntity.key;
let dropPosition = 0;
let dropLevelOffset = 0;
// Only allow cross level drop when dragging on a non-expanded node
if (!filteredExpandKeys.includes(initialAbstractDropNodeKey)) {
for (let i = 0; i < rawDropLevelOffset; i += 1) {
if (isLastChild(abstractDropNodeEntity)) {
abstractDropNodeEntity = abstractDropNodeEntity.parent;
dropLevelOffset += 1;
} else {
break;
}
}
}
const abstractDragDataNode = dragNodeProps.data;
const abstractDropDataNode = abstractDropNodeEntity.node;
let dropAllowed = true;
if (isFirstChild(abstractDropNodeEntity) && abstractDropNodeEntity.level === 0 && clientY < top + height / 2 && allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: -1
}) && abstractDropNodeEntity.key === targetNodeProps.eventKey) {
// first half of first node in first level
dropPosition = -1;
} else if ((abstractDragOverEntity.children || []).length && filteredExpandKeys.includes(dragOverNodeKey)) {
// drop on expanded node
// only allow drop inside
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 0
})) {
dropPosition = 0;
} else {
dropAllowed = false;
}
} else if (dropLevelOffset === 0) {
if (rawDropLevelOffset > -1.5) {
// | Node | <- abstractDropNode
// | -^-===== | <- mousePosition
// 1. try drop after
// 2. do not allow drop
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) {
dropPosition = 1;
} else {
dropAllowed = false;
}
} else {
// | Node | <- abstractDropNode
// | ---==^== | <- mousePosition
// whether it has children or doesn't has children
// always
// 1. try drop inside
// 2. try drop after
// 3. do not allow drop
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 0
})) {
dropPosition = 0;
} else if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) {
dropPosition = 1;
} else {
dropAllowed = false;
}
}
} else {
// | Node1 | <- abstractDropNode
// | Node2 |
// --^--|----=====| <- mousePosition
// 1. try insert after Node1
// 2. do not allow drop
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) {
dropPosition = 1;
} else {
dropAllowed = false;
}
}
return {
dropPosition,
dropLevelOffset,
dropTargetKey: abstractDropNodeEntity.key,
dropTargetPos: abstractDropNodeEntity.pos,
dragOverNodeKey,
dropContainerKey: dropPosition === 0 ? null : abstractDropNodeEntity.parent?.key || null,
dropAllowed
};
}
/**
* Return selectedKeys according with multiple prop
* @param selectedKeys
* @param props
* @returns [string]
*/
function calcSelectedKeys(selectedKeys, props) {
if (!selectedKeys) return undefined;
const {
multiple
} = props;
if (multiple) {
return selectedKeys.slice();
}
if (selectedKeys.length) {
return [selectedKeys[0]];
}
return selectedKeys;
}
const internalProcessProps = props => props;
function convertDataToTree(treeData, processor) {
if (!treeData) return [];
const {
processProps = internalProcessProps
} = processor || {};
const list = Array.isArray(treeData) ? treeData : [treeData];
return list.map(({
children,
...props
}) => {
const childrenNodes = convertDataToTree(children, processor);
return /*#__PURE__*/_react.default.createElement(_TreeNode.default, _extends({
key: props.key
}, processProps(props)), childrenNodes);
});
}
/**
* Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
*/
function parseCheckedKeys(keys) {
if (!keys) {
return null;
}
// Convert keys to object format
let keyProps;
if (Array.isArray(keys)) {
// [Legacy] Follow the api doc
keyProps = {
checkedKeys: keys,
halfCheckedKeys: undefined
};
} else if (typeof keys === 'object') {
keyProps = {
checkedKeys: keys.checked || undefined,
halfCheckedKeys: keys.halfChecked || undefined
};
} else {
(0, _warning.default)(false, '`checkedKeys` is not an array or an object');
return null;
}
return keyProps;
}
/**
* If user use `autoExpandParent` we should get the list of parent node
* @param keyList
* @param keyEntities
*/
function conductExpandParent(keyList, keyEntities) {
const expandedKeys = new Set();
function conductUp(key) {
if (expandedKeys.has(key)) return;
const entity = (0, _keyUtil.default)(keyEntities, key);
if (!entity) return;
expandedKeys.add(key);
const {
parent,
node
} = entity;
if (node.disabled) return;
if (parent) {
conductUp(parent.key);
}
}
(keyList || []).forEach(key => {
conductUp(key);
});
return [...expandedKeys];
}
@@ -0,0 +1,17 @@
import type { BasicDataNode, DataNode, GetCheckDisabled, Key, KeyEntities } from '../interface';
interface ConductReturnType {
checkedKeys: Key[];
halfCheckedKeys: Key[];
}
export declare function isCheckDisabled<TreeDataType>(node: TreeDataType): boolean;
/**
* Conduct with keys.
* @param keyList current key list
* @param keyEntities key - dataEntity map
* @param mode `fill` to fill missing key, `clean` to remove useless key
*/
export declare function conductCheck<TreeDataType extends BasicDataNode = DataNode>(keyList: Key[], checked: true | {
checked: false;
halfCheckedKeys: Key[];
}, keyEntities: KeyEntities<TreeDataType>, getCheckDisabled?: GetCheckDisabled<TreeDataType>): ConductReturnType;
export {};
@@ -0,0 +1,218 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.conductCheck = conductCheck;
exports.isCheckDisabled = isCheckDisabled;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _keyUtil = _interopRequireDefault(require("./keyUtil"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function removeFromCheckedKeys(halfCheckedKeys, checkedKeys) {
const filteredKeys = new Set();
halfCheckedKeys.forEach(key => {
if (!checkedKeys.has(key)) {
filteredKeys.add(key);
}
});
return filteredKeys;
}
function isCheckDisabled(node) {
const {
disabled,
disableCheckbox,
checkable
} = node || {};
return !!(disabled || disableCheckbox) || checkable === false;
}
// Fill miss keys
function fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled) {
const checkedKeys = new Set(keys);
const halfCheckedKeys = new Set();
// Add checked keys top to bottom
for (let level = 0; level <= maxLevel; level += 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
key,
node,
children = []
} = entity;
if (checkedKeys.has(key) && !syntheticGetCheckDisabled(node)) {
children.filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(childEntity => {
checkedKeys.add(childEntity.key);
});
}
});
}
// Add checked keys from bottom to top
const visitedKeys = new Set();
for (let level = maxLevel; level >= 0; level -= 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
parent,
node
} = entity;
// Skip if no need to check
if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
return;
}
// Skip if parent is disabled
if (syntheticGetCheckDisabled(entity.parent.node)) {
visitedKeys.add(parent.key);
return;
}
let allChecked = true;
let partialChecked = false;
(parent.children || []).filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(({
key
}) => {
const checked = checkedKeys.has(key);
if (allChecked && !checked) {
allChecked = false;
}
if (!partialChecked && (checked || halfCheckedKeys.has(key))) {
partialChecked = true;
}
});
if (allChecked) {
checkedKeys.add(parent.key);
}
if (partialChecked) {
halfCheckedKeys.add(parent.key);
}
visitedKeys.add(parent.key);
});
}
return {
checkedKeys: Array.from(checkedKeys),
halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys))
};
}
// Remove useless key
function cleanConductCheck(keys, halfKeys, levelEntities, maxLevel, syntheticGetCheckDisabled) {
const checkedKeys = new Set(keys);
let halfCheckedKeys = new Set(halfKeys);
// Remove checked keys from top to bottom
for (let level = 0; level <= maxLevel; level += 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
key,
node,
children = []
} = entity;
if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !syntheticGetCheckDisabled(node)) {
children.filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(childEntity => {
checkedKeys.delete(childEntity.key);
});
}
});
}
// Remove checked keys form bottom to top
halfCheckedKeys = new Set();
const visitedKeys = new Set();
for (let level = maxLevel; level >= 0; level -= 1) {
const entities = levelEntities.get(level) || new Set();
entities.forEach(entity => {
const {
parent,
node
} = entity;
// Skip if no need to check
if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
return;
}
// Skip if parent is disabled
if (syntheticGetCheckDisabled(entity.parent.node)) {
visitedKeys.add(parent.key);
return;
}
let allChecked = true;
let partialChecked = false;
(parent.children || []).filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)).forEach(({
key
}) => {
const checked = checkedKeys.has(key);
if (allChecked && !checked) {
allChecked = false;
}
if (!partialChecked && (checked || halfCheckedKeys.has(key))) {
partialChecked = true;
}
});
if (!allChecked) {
checkedKeys.delete(parent.key);
}
if (partialChecked) {
halfCheckedKeys.add(parent.key);
}
visitedKeys.add(parent.key);
});
}
return {
checkedKeys: Array.from(checkedKeys),
halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys))
};
}
/**
* Conduct with keys.
* @param keyList current key list
* @param keyEntities key - dataEntity map
* @param mode `fill` to fill missing key, `clean` to remove useless key
*/
function conductCheck(keyList, checked, keyEntities, getCheckDisabled) {
const warningMissKeys = [];
let syntheticGetCheckDisabled;
if (getCheckDisabled) {
syntheticGetCheckDisabled = getCheckDisabled;
} else {
syntheticGetCheckDisabled = isCheckDisabled;
}
// We only handle exist keys
const keys = new Set(keyList.filter(key => {
const hasEntity = !!(0, _keyUtil.default)(keyEntities, key);
if (!hasEntity) {
warningMissKeys.push(key);
}
return hasEntity;
}));
const levelEntities = new Map();
let maxLevel = 0;
// Convert entities by level for calculation
Object.keys(keyEntities).forEach(key => {
const entity = keyEntities[key];
const {
level
} = entity;
let levelSet = levelEntities.get(level);
if (!levelSet) {
levelSet = new Set();
levelEntities.set(level, levelSet);
}
levelSet.add(entity);
maxLevel = Math.max(maxLevel, level);
});
(0, _warning.default)(!warningMissKeys.length, `Tree missing follow keys: ${warningMissKeys.slice(0, 100).map(key => `'${key}'`).join(', ')}`);
let result;
if (checked === true) {
result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled);
} else {
result = cleanConductCheck(keys, checked.halfCheckedKeys, levelEntities, maxLevel, syntheticGetCheckDisabled);
}
return result;
}
@@ -0,0 +1,7 @@
/// <reference types="react" />
import type { Key, FlattenNode } from '../interface';
export declare function findExpandedKeys(prev?: Key[], next?: Key[]): {
add: boolean;
key: import("react").Key;
};
export declare function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: Key): FlattenNode<import("../interface").DataNode>[];
+45
View File
@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findExpandedKeys = findExpandedKeys;
exports.getExpandRange = getExpandRange;
function findExpandedKeys(prev = [], next = []) {
const prevLen = prev.length;
const nextLen = next.length;
if (Math.abs(prevLen - nextLen) !== 1) {
return {
add: false,
key: null
};
}
function find(shorter, longer) {
const cache = new Map();
shorter.forEach(key => {
cache.set(key, true);
});
const keys = longer.filter(key => !cache.has(key));
return keys.length === 1 ? keys[0] : null;
}
if (prevLen < nextLen) {
return {
add: true,
key: find(prev, next)
};
}
return {
add: false,
key: find(next, prev)
};
}
function getExpandRange(shorter, longer, key) {
const shorterStartIndex = shorter.findIndex(data => data.key === key);
const shorterEndNode = shorter[shorterStartIndex + 1];
const longerStartIndex = longer.findIndex(data => data.key === key);
if (shorterEndNode) {
const longerEndIndex = longer.findIndex(data => data.key === shorterEndNode.key);
return longer.slice(longerStartIndex + 1, longerEndIndex);
}
return longer.slice(longerStartIndex + 1);
}
@@ -0,0 +1,2 @@
import type { Key, KeyEntities } from '../interface';
export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: Key): import("../interface").DataEntity<T>;
+9
View File
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = getEntity;
function getEntity(keyEntities, key) {
return keyEntities[key];
}
@@ -0,0 +1,87 @@
import * as React from 'react';
import type { BasicDataNode, DataEntity, DataNode, EventDataNode, FieldNames, FlattenNode, GetKey, Key, KeyEntities, NodeElement, TreeNodeProps } from '../interface';
export declare function getPosition(level: string | number, index: number): string;
export declare function isTreeNode(node: NodeElement): boolean;
export declare function getKey(key: Key, pos: string): React.Key;
export declare function fillFieldNames(fieldNames?: FieldNames): Required<FieldNames>;
/**
* Warning if TreeNode do not provides key
*/
export declare function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames): void;
/**
* Convert `children` of Tree into `treeData` structure.
*/
export declare function convertTreeToData(rootNodes: React.ReactNode): DataNode[];
/**
* Flat nest tree data into flatten list. This is used for virtual list render.
* @param treeNodeList Origin data node list
* @param expandedKeys
* need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
*/
export declare function flattenTreeData<TreeDataType extends BasicDataNode = DataNode>(treeNodeList: TreeDataType[], expandedKeys: Key[] | true, fieldNames: FieldNames): FlattenNode<TreeDataType>[];
type ExternalGetKey = GetKey<DataNode> | string;
interface TraverseDataNodesConfig {
childrenPropName?: string;
externalGetKey?: ExternalGetKey;
fieldNames?: FieldNames;
}
/**
* Traverse all the data by `treeData`.
* Please not use it out of the `rc-tree` since we may refactor this code.
*/
export declare function traverseDataNodes(dataNodes: DataNode[], callback: (data: {
node: DataNode;
index: number;
pos: string;
key: Key;
parentPos: string | number;
level: number;
nodes: DataNode[];
}) => void, config?: TraverseDataNodesConfig | string): void;
interface Wrapper {
posEntities: Record<string, DataEntity>;
keyEntities: KeyEntities;
}
/**
* Convert `treeData` into entity records.
*/
export declare function convertDataToEntities(dataNodes: DataNode[], { initWrapper, processEntity, onProcessFinished, externalGetKey, childrenPropName, fieldNames, }?: {
initWrapper?: (wrapper: Wrapper) => Wrapper;
processEntity?: (entity: DataEntity, wrapper: Wrapper) => void;
onProcessFinished?: (wrapper: Wrapper) => void;
externalGetKey?: ExternalGetKey;
childrenPropName?: string;
fieldNames?: FieldNames;
},
/** @deprecated Use `config.externalGetKey` instead */
legacyExternalGetKey?: ExternalGetKey): Wrapper;
export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
expandedKeys: Key[];
selectedKeys: Key[];
loadedKeys: Key[];
loadingKeys: Key[];
checkedKeys: Key[];
halfCheckedKeys: Key[];
dragOverNodeKey: Key;
dropPosition: number;
keyEntities: KeyEntities<TreeDataType>;
}
export declare function isLeafNode<TreeDataType extends BasicDataNode = DataNode>(isLeaf: boolean | undefined, loadData: ((node: EventDataNode<TreeDataType>) => Promise<any>) | undefined, hasChildren: boolean, loaded: boolean): boolean;
/**
* Get TreeNode props with Tree props.
*/
export declare function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(key: Key, { expandedKeys, selectedKeys, loadedKeys, loadingKeys, checkedKeys, halfCheckedKeys, dragOverNodeKey, dropPosition, keyEntities, }: TreeNodeRequiredProps<TreeDataType>): {
eventKey: React.Key;
expanded: boolean;
selected: boolean;
loaded: boolean;
loading: boolean;
checked: boolean;
halfChecked: boolean;
pos: string;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
};
export declare function convertNodePropsToEventData<TreeDataType extends BasicDataNode = DataNode>(props: TreeNodeProps<TreeDataType>): EventDataNode<TreeDataType>;
export {};
+377
View File
@@ -0,0 +1,377 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.convertDataToEntities = convertDataToEntities;
exports.convertNodePropsToEventData = convertNodePropsToEventData;
exports.convertTreeToData = convertTreeToData;
exports.fillFieldNames = fillFieldNames;
exports.flattenTreeData = flattenTreeData;
exports.getKey = getKey;
exports.getPosition = getPosition;
exports.getTreeNodeProps = getTreeNodeProps;
exports.isLeafNode = isLeafNode;
exports.isTreeNode = isTreeNode;
exports.traverseDataNodes = traverseDataNodes;
exports.warningWithoutKey = warningWithoutKey;
var _toArray = _interopRequireDefault(require("@rc-component/util/lib/Children/toArray"));
var _omit = _interopRequireDefault(require("@rc-component/util/lib/omit"));
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _keyUtil = _interopRequireDefault(require("./keyUtil"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function getPosition(level, index) {
return `${level}-${index}`;
}
function isTreeNode(node) {
return node && node.type && node.type.isTreeNode;
}
function getKey(key, pos) {
if (key !== null && key !== undefined) {
return key;
}
return pos;
}
function fillFieldNames(fieldNames) {
const {
title,
_title,
key,
children
} = fieldNames || {};
const mergedTitle = title || 'title';
return {
title: mergedTitle,
_title: _title || [mergedTitle],
key: key || 'key',
children: children || 'children'
};
}
/**
* Warning if TreeNode do not provides key
*/
function warningWithoutKey(treeData, fieldNames) {
const keys = new Map();
function dig(list, path = '') {
(list || []).forEach(treeNode => {
const key = treeNode[fieldNames.key];
const children = treeNode[fieldNames.children];
(0, _warning.default)(key !== null && key !== undefined, `Tree node must have a certain key: [${path}${key}]`);
const recordKey = String(key);
(0, _warning.default)(!keys.has(recordKey) || key === null || key === undefined, `Same 'key' exist in the Tree: ${recordKey}`);
keys.set(recordKey, true);
dig(children, `${path}${recordKey} > `);
});
}
dig(treeData);
}
/**
* Convert `children` of Tree into `treeData` structure.
*/
function convertTreeToData(rootNodes) {
function dig(node) {
const treeNodes = (0, _toArray.default)(node);
return treeNodes.map(treeNode => {
// Filter invalidate node
if (!isTreeNode(treeNode)) {
(0, _warning.default)(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
return null;
}
const {
key
} = treeNode;
const {
children,
...rest
} = treeNode.props;
const dataNode = {
key: key,
...rest
};
const parsedChildren = dig(children);
if (parsedChildren.length) {
dataNode.children = parsedChildren;
}
return dataNode;
}).filter(dataNode => dataNode);
}
return dig(rootNodes);
}
/**
* Flat nest tree data into flatten list. This is used for virtual list render.
* @param treeNodeList Origin data node list
* @param expandedKeys
* need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
*/
function flattenTreeData(treeNodeList, expandedKeys, fieldNames) {
const {
_title: fieldTitles,
key: fieldKey,
children: fieldChildren
} = fillFieldNames(fieldNames);
const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
const flattenList = [];
function dig(list, parent = null) {
return list.map((treeNode, index) => {
const pos = getPosition(parent ? parent.pos : '0', index);
const mergedKey = getKey(treeNode[fieldKey], pos);
// Pick matched title in field title list
let mergedTitle;
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
// We use `Object.assign` here to save perf since babel's `objectSpread` has perf issue
const flattenNode = Object.assign((0, _omit.default)(treeNode, [...fieldTitles, fieldKey, fieldChildren]), {
title: mergedTitle,
key: mergedKey,
parent,
pos,
children: null,
data: treeNode,
isStart: [...(parent ? parent.isStart : []), index === 0],
isEnd: [...(parent ? parent.isEnd : []), index === list.length - 1]
});
flattenList.push(flattenNode);
// Loop treeNode children
if (expandedKeys === true || expandedKeySet.has(mergedKey)) {
flattenNode.children = dig(treeNode[fieldChildren] || [], flattenNode);
} else {
flattenNode.children = [];
}
return flattenNode;
});
}
dig(treeNodeList);
return flattenList;
}
/**
* Traverse all the data by `treeData`.
* Please not use it out of the `rc-tree` since we may refactor this code.
*/
function traverseDataNodes(dataNodes, callback,
// To avoid too many params, let use config instead of origin param
config) {
let mergedConfig = {};
if (typeof config === 'object') {
mergedConfig = config;
} else {
mergedConfig = {
externalGetKey: config
};
}
mergedConfig = mergedConfig || {};
// Init config
const {
childrenPropName,
externalGetKey,
fieldNames
} = mergedConfig;
const {
key: fieldKey,
children: fieldChildren
} = fillFieldNames(fieldNames);
const mergeChildrenPropName = childrenPropName || fieldChildren;
// Get keys
let syntheticGetKey;
if (externalGetKey) {
if (typeof externalGetKey === 'string') {
syntheticGetKey = node => node[externalGetKey];
} else if (typeof externalGetKey === 'function') {
syntheticGetKey = node => externalGetKey(node);
}
} else {
syntheticGetKey = (node, pos) => getKey(node[fieldKey], pos);
}
// Process
function processNode(node, index, parent, pathNodes) {
const children = node ? node[mergeChildrenPropName] : dataNodes;
const pos = node ? getPosition(parent.pos, index) : '0';
const connectNodes = node ? [...pathNodes, node] : [];
// Process node if is not root
if (node) {
const key = syntheticGetKey(node, pos);
const data = {
node,
index,
pos,
key,
parentPos: parent.node ? parent.pos : null,
level: parent.level + 1,
nodes: connectNodes
};
callback(data);
}
// Process children node
if (children) {
children.forEach((subNode, subIndex) => {
processNode(subNode, subIndex, {
node,
pos,
level: parent ? parent.level + 1 : -1
}, connectNodes);
});
}
}
processNode(null);
}
/**
* Convert `treeData` into entity records.
*/
function convertDataToEntities(dataNodes, {
initWrapper,
processEntity,
onProcessFinished,
externalGetKey,
childrenPropName,
fieldNames
} = {}, /** @deprecated Use `config.externalGetKey` instead */
legacyExternalGetKey) {
// Init config
const mergedExternalGetKey = externalGetKey || legacyExternalGetKey;
const posEntities = {};
const keyEntities = {};
let wrapper = {
posEntities,
keyEntities
};
if (initWrapper) {
wrapper = initWrapper(wrapper) || wrapper;
}
traverseDataNodes(dataNodes, item => {
const {
node,
index,
pos,
key,
parentPos,
level,
nodes
} = item;
const entity = {
node,
nodes,
index,
key,
pos,
level
};
const mergedKey = getKey(key, pos);
posEntities[pos] = entity;
keyEntities[mergedKey] = entity;
// Fill children
entity.parent = posEntities[parentPos];
if (entity.parent) {
entity.parent.children = entity.parent.children || [];
entity.parent.children.push(entity);
}
if (processEntity) {
processEntity(entity, wrapper);
}
}, {
externalGetKey: mergedExternalGetKey,
childrenPropName,
fieldNames
});
if (onProcessFinished) {
onProcessFinished(wrapper);
}
return wrapper;
}
function isLeafNode(isLeaf, loadData, hasChildren, loaded) {
if (isLeaf === false) {
return false;
}
return isLeaf || !loadData && !hasChildren || loadData && loaded && !hasChildren;
}
/**
* Get TreeNode props with Tree props.
*/
function getTreeNodeProps(key, {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities
}) {
const entity = (0, _keyUtil.default)(keyEntities, key);
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,
pos: String(entity ? entity.pos : ''),
// [Legacy] Drag props
// Since the interaction of drag is changed, the semantic of the props are
// not accuracy, I think it should be finally removed
dragOver: dragOverNodeKey === key && dropPosition === 0,
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1
};
return treeNodeProps;
}
function convertNodePropsToEventData(props) {
const {
data,
expanded,
selected,
checked,
loaded,
loading,
halfChecked,
dragOver,
dragOverGapTop,
dragOverGapBottom,
pos,
active,
eventKey
} = props;
const eventData = {
...data,
expanded,
selected,
checked,
loaded,
loading,
halfChecked,
dragOver,
dragOverGapTop,
dragOverGapBottom,
pos,
active,
key: eventKey
};
if (!('props' in eventData)) {
Object.defineProperty(eventData, 'props', {
get() {
(0, _warning.default)(false, 'Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`.');
return props;
}
});
}
return eventData;
}
+86
View File
@@ -0,0 +1,86 @@
{
"name": "@rc-component/tree",
"version": "1.2.4",
"description": "tree ui component for react",
"engines": {
"node": ">=10.x"
},
"keywords": [
"react",
"react-component",
"react-tree",
"tree"
],
"files": [
"assets",
"es",
"lib"
],
"homepage": "http://github.com/react-component/tree",
"author": "smith3816@gmail.com",
"repository": {
"type": "git",
"url": "git@github.com:react-component/tree.git"
},
"bugs": {
"url": "http://github.com/react-component/tree/issues"
},
"license": "MIT",
"main": "./lib/index",
"module": "./es/index",
"scripts": {
"start": "dumi dev",
"docs:build": "dumi build",
"docs:deploy": "gh-pages -d dist",
"compile": "father build && lessc assets/index.less assets/index.css",
"prepare": "husky",
"prepublishOnly": "npm run compile && rc-np",
"postpublish": "npm run gh-pages",
"lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
"test": "rc-test",
"coverage": "rc-test --coverage",
"gh-pages": "npm run docs:build && npm run docs:deploy",
"now-build": "npm run docs:build"
},
"lint-staged": {
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@rc-component/motion": "^1.0.0",
"@rc-component/util": "^1.8.1",
"@rc-component/virtual-list": "^1.0.1",
"clsx": "^2.1.1"
},
"devDependencies": {
"@rc-component/dialog": "^1.0.0",
"@rc-component/father-plugin": "^2.0.3",
"@rc-component/np": "^1.0.0",
"@rc-component/tooltip": "^1.0.0",
"@rc-component/trigger": "^3.0.0",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^16.1.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.10",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"@types/warning": "^3.0.0",
"@umijs/fabric": "^4.0.1",
"dumi": "^2.1.0",
"eslint": "^8.55.0",
"father": "^4.4.0",
"gh-pages": "^6.1.1",
"glob": "^11.0.0",
"husky": "^9.1.6",
"less": "^4.2.1",
"lint-staged": "^16.1.2",
"prettier": "^3.3.3",
"rc-test": "^7.0.15",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.3.3"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
}
}