253 lines
7.7 KiB
JavaScript
253 lines
7.7 KiB
JavaScript
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; |