import React, { useState, useImperativeHandle, forwardRef, useRef, useEffect, useMemo, useContext, memo, } from "react"; import classNames from "classnames"; import { TreeContext, TreeContextValue } from "./TreeContext"; import { Check } from "../check"; import { Icon } from "../icon"; import { injectValue } from "../_util/inject-value"; import { useConfig } from "../_util/config-context"; import { forwardRefWithStatics } from "../_util/forward-ref-with-statics"; import { TreeNodeProps, TreeNodeEventData } from "./TreeProps"; import { mergeEventProps } from "../_util/merge-event-props"; import { TreeNode as DeprecatedTreeNode } from "./deprecated/TreeNode"; export const TreeActionLink = React.forwardRef(function TreeActionLink( { children, className, ...props }: React.AnchorHTMLAttributes, ref: React.Ref ) { const { classPrefix } = useConfig(); return ( {children} ); }); TreeActionLink.displayName = "TreeActionLink"; export interface TreeNodeInnerProps { /** * 深度 */ level?: number; /** * 包含完整路径的 id 串 */ pathKey?: string; } export const TreeNode = forwardRefWithStatics( function TreeNode(props: TreeNodeProps, ref: React.Ref) { const { flatten, activable, onActive, expandedIds, fullExpandable, fullActivable, onExpand, onLoad, onLoadError, switcherIcon, selectable: treeSelectable, } = useContext(TreeContext); const expanded = useMemo(() => expandedIds.includes(props.id), [ expandedIds, props.id, ]); if (!flatten) { return ; } return ( ); }, { ActionLink: TreeActionLink, } ); TreeNode.displayName = "TreeNode"; const TreeNodeInner = memo( forwardRef(function TreeNodeInner( allProps: TreeNodeProps & TreeNodeInnerProps & Pick< TreeContextValue, | "activable" | "onActive" | "fullExpandable" | "fullActivable" | "onExpand" | "onLoad" | "onLoadError" | "switcherIcon" > & { expanded?: boolean; treeSelectable?: boolean }, ref: React.Ref ) { const { id, level = 0, content, icon, operation, expandable, children, selectable: nodeSelectable, // Context activable, fullActivable, onActive, fullExpandable, expanded, onExpand, onLoad, onLoadError, switcherIcon, treeSelectable, // 外层使用 disableSelect, pure, className, style, // 节点内无用 height, pathKey, // 可透传属性 ...props } = allProps; const { classPrefix } = useConfig(); const selectable = typeof nodeSelectable !== "undefined" ? nodeSelectable && treeSelectable : treeSelectable; const nodeIcon = injectValue(icon)({ expanded }); const switcherRef = useRef(null); function handleExpand(event: React.MouseEvent) { if (switcherRef.current) { switcherRef.current.handleExpand(event); } } // activable 与 selectable 同时开启时 Checkbox 与内容结构分离 const nodeContent = activable && selectable ? (
{ if (!fullActivable) { onActive(id, event, allProps); } }} > {nodeIcon} {content} {operation && (
e.stopPropagation()} > {operation}
)}
) : (
{ if (!fullActivable && activable) { onActive(id, event, allProps); } }} > {selectable ? ( {nodeIcon} {content} ) : ( <> {nodeIcon} {content} )} {operation && (
e.stopPropagation()} > {operation}
)}
); return (
{ if (fullActivable && activable) { onActive(id, event, allProps); } if (fullExpandable && Boolean(expandable || children)) { handleExpand(event); } }, })} > {new Array(level).fill(null).map((_, i) => ( ))} {nodeContent}
); }) ); TreeNodeInner.displayName = "TreeNodeInner"; interface TreeNodeSwitcherProps { id: TreeNodeProps["id"]; icon: TreeContextValue["switcherIcon"]; enable: boolean; expanded: boolean; onExpand: TreeContextValue["onExpand"]; onLoad: TreeContextValue["onLoad"]; onLoadError: TreeContextValue["onLoadError"]; classPrefix: string; allProps: TreeNodeEventData; } interface TreeNodeSwitcherInstance { handleExpand: (event: React.MouseEvent) => Promise; } const TreeNodeSwitcher = forwardRef(function TreeNodeSwitcher( { id, enable, expanded, onExpand, onLoad, onLoadError = () => null, icon = ({ expanded }) => ( ), classPrefix, allProps, }: TreeNodeSwitcherProps, ref: React.Ref ) { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const onExpandRef = useRef(onExpand); useEffect(() => { onExpandRef.current = onExpand; }, [onExpand]); const switcherIcon = injectValue(icon)({ expanded, nodeId: id }); useImperativeHandle(ref, () => ({ handleExpand })); async function handleExpand(event: React.MouseEvent) { event.stopPropagation(); if (expanded) { onExpand(id, false, event, allProps); return; } if (onLoad) { setError(false); setLoading(true); try { await onLoad(id); // 外层 expand 状态变化导致 onExpand 更新 onExpandRef.current(id, true, event, allProps); } catch (err) { setError(true); onLoadError(id, err); } finally { setLoading(false); } } else { onExpand(id, true, event, allProps); } } if (loading) { return ( ); } let switcher = React.isValidElement(switcherIcon) ? ( switcherIcon ) : (
{switcherIcon}
); if (error) { switcher = ; } return enable ? ( {switcher} ) : (
); }); TreeNodeSwitcher.displayName = "TreeNodeSwitcher";