/** * Handle virtual list of the TreeNodes. */ import * as React from "react"; import VirtualList from "rc-virtual-list"; import { FlattenNode, Key, DataEntity, DataNode, ScrollTo } from "./interface"; import { RefMotionTreeNode as MotionTreeNode } from "./motion-tree-node"; import { findExpandedKeys, getExpandRange } from "./utils/diff-util"; import { getTreeNodeProps, getKey } from "./utils/tree-util"; //Hidden Styles const HIDDEN_STYLE = { width: 0, height: 0, display: "flex", overflow: "hidden", opacity: 0, border: 0, padding: 0, margin: 0, }; //foo-bar func const noop = () => {}; // const MOTION_KEY = `CO_TREE_MOTION_${Math.random()}`; const MotionNode: DataNode = { key: MOTION_KEY, }; //Basic setting default value // const MotionEntity: DataEntity = { key: MOTION_KEY, level: 0, index: 0, pos: "0", node: MotionNode, }; const MotionFlattenData: FlattenNode = { parent: null, children: [], pos: MotionEntity.pos, data: MotionNode, /** Hold empty list here since we do not use it */ isStart: [], isEnd: [], }; //node已挂载列表 // interface NodeListRef { scrollTo: ScrollTo; } interface NodeListProps { prefixCls: string; style: React.CSSProperties; data: FlattenNode[]; motion: any; focusable?: boolean; activeItem: FlattenNode; focused?: boolean; tabIndex: number; selectable?: boolean; disabled?: boolean; expandedKeys: Key[]; selectedKeys?: Key[]; loadedKeys: Key[]; loadingKeys: Key[]; keyEntities: Record; dragging: boolean; dragOverNodeKey: Key; dropPosition: number; // Virtual list height: number; itemHeight: number; virtual?: boolean; onKeyDown?: React.KeyboardEventHandler; onFocus?: React.FocusEventHandler; onBlur?: React.FocusEventHandler; onActiveChange: (key: Key) => void; onListChangeStart: () => void; onListChangeEnd: () => void; } /** * We only need get visible content items to play the animation. */ // const getMinimumRangeTransitionRange = ( list: FlattenNode[], height: number, itemHeight: number ) => { if (!height) { return list; } return list.slice(0, Math.ceil(height / itemHeight) + 1); }; //Item Key // const itemKey = (item: FlattenNode) => { const { data: { key }, pos, } = item; return getKey(key, pos); }; // Access Path const getAccessibilityPath = (item: FlattenNode): string => { let path = String(item.data.key); let current = item; while (current.parent) { current = current.parent; path = `${current.data.key} > ${path}`; } return path; }; //Declaration of RefNodeList with the old type NodeListRef and NodeListProps const RefNodeList: React.RefForwardingComponent = ( props, ref ) => { const { prefixCls, data, selectable, expandedKeys, selectedKeys, loadedKeys, loadingKeys, keyEntities, disabled, dragging, dragOverNodeKey, dropPosition, motion, height, itemHeight, virtual, focusable, activeItem, focused, tabIndex, onKeyDown, onFocus, onBlur, onActiveChange, onListChangeStart, onListChangeEnd, ...domProps } = props; // =============================== Ref ================================ const listRef = React.useRef>(null); React.useImperativeHandle(ref, () => ({ scrollTo: (scroll) => { listRef.current.scrollTo(scroll); }, })); // ============================== Motion ============================== const [disableVirtual, setDisableVirtual] = React.useState(false); 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<"show" | "hide" | null>( null ); const onMotionEnd = () => { setPrevData(data); setTransitionData(data); setTransitionRange([]); setMotionType(null); setDisableVirtual(false); onListChangeEnd(); }; // Do animation if expanded keys changed React.useEffect(() => { setPrevExpandedKeys(expandedKeys); const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys); if (diffExpanded.key !== null) { if (diffExpanded.add) { const keyIndex = prevData.findIndex( ({ data: { key } }) => key === diffExpanded.key ); if (motion) setDisableVirtual(true); const rangeNodes = getMinimumRangeTransitionRange( getExpandRange(prevData, data, diffExpanded.key), height, itemHeight ); const newTransitionData: FlattenNode[] = prevData.slice(); newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData); setTransitionData(newTransitionData); setTransitionRange(rangeNodes); setMotionType("show"); } else { const keyIndex = data.findIndex( ({ data: { key } }) => key === diffExpanded.key ); if (motion) setDisableVirtual(true); const rangeNodes = getMinimumRangeTransitionRange( getExpandRange(data, prevData, diffExpanded.key), height, itemHeight ); const newTransitionData: FlattenNode[] = data.slice(); newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData); setTransitionData(newTransitionData); setTransitionRange(rangeNodes); setMotionType("hide"); } // Trigger when `motion` provided if (motion) { onListChangeStart(); } } 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, dragOverNodeKey, dropPosition, keyEntities, }; return (
{focused && activeItem && ( {getAccessibilityPath(activeItem)} )}
{...domProps} disabled={disableVirtual} data={mergedData} itemKey={itemKey} height={height} fullHeight={false} virtual={virtual} itemHeight={itemHeight} onSkipRender={onMotionEnd} prefixCls={`${prefixCls}-list`} ref={listRef} > {(treeNode: FlattenNode) => { const { pos, data: { key, ...restProps }, isStart, isEnd, } = treeNode; const mergedKey = getKey(key, pos); delete restProps.children; const treeNodeProps = getTreeNodeProps( mergedKey, treeNodeRequiredProps ); return ( { onActiveChange(null); }} /> ); }}
); }; const NodeList = React.forwardRef(RefNodeList); NodeList.displayName = "NodeList"; export { NodeList, itemKey, getMinimumRangeTransitionRange, NodeListRef, MotionEntity, MOTION_KEY, };