import React, { useState, useImperativeHandle, forwardRef, useRef, useEffect, } from "react"; import classNames from "classnames"; import { TreeContext, TreeContextValue } from "./TreeContext"; import { Check } from "../../check"; import { Icon } from "../../icon"; import { StyledProps } from "../../_type"; import { injectValue } from "../../_util/inject-value"; import { useConfig } from "../../_util/config-context"; import { noop } from "../../_util/noop"; import { forwardRefWithStatics } from "../../_util/forward-ref-with-statics"; import { callBoth } from "../../util"; 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 TreeNodeProps extends StyledProps { /** * 节点 ID,整棵树中唯一 */ id: string; /** * 节点内容 */ content: React.ReactNode; /** * 节点图标 * * @docType React.ReactNode | (context: { expanded: boolean }) => React.ReactNode */ icon?: | ((context: { expanded: boolean }) => React.ReactNode) | React.ReactNode; /** * hover 后展示的操作 * * 推荐使用文字(TreeNode.ActionLink),若超过一项,建议收至 Dropdown 下 */ operation?: React.ReactNode; /** * 当树为 selectable 时,设置当前节点是否展示 Checkbox * * @default true */ selectable?: boolean; /** * 当树为 selectable 时,设置当前节点 Checkbox 是否禁用 * * @default false */ disableSelect?: boolean; /** * 当前节点是否支持展开(用于异步加载) * * @default false */ expandable?: boolean; /** * 当前节点是否不需要附加样式(如 Hover 样式) * * @default false * @since 2.2.4 */ pure?: boolean; /** * 包含的树节点 */ children?: React.ReactNode; } export const TreeNode = forwardRefWithStatics( function TreeNode(props: TreeNodeProps, ref: React.Ref) { return ( {context => { return ; }} ); }, { ActionLink: TreeActionLink, } ); TreeNode.displayName = "TreeNode"; export const TreeNodeInner = React.forwardRef(function TreeNodeInner( { id, content, icon, operation, selectable, activable, activeIds, onActive, expandable, children, expandedIds, fullExpandable, onExpand, onLoad, onLoadError, switcherIcon, pure, disableSelect, // 外层使用 className, style, ...props }: TreeNodeProps & TreeContextValue, ref: React.Ref ) { const { classPrefix } = useConfig(); const expanded = expandedIds.includes(id); 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 ? (
onActive(id)} > {nodeIcon} {content} {operation && (
{operation}
)}
) : (
{selectable ? ( {nodeIcon} {content} ) : ( <> {nodeIcon} {content} )} {operation && (
{operation}
)}
); return (
  • !selectable && activable && onActive(id), (props as any).onClick )} > {nodeContent}
    {expanded && (
      {children}
    )}
  • ); }); 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; } interface TreeNodeSwitcherInstance { handleExpand: (event: React.MouseEvent) => Promise; } const TreeNodeSwitcher = forwardRef(function TreeNodeSwitcher( { id, enable, expanded, onExpand, onLoad, onLoadError = () => null, icon = ({ expanded }) => ( ), classPrefix, }: 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); return; } if (onLoad) { setError(false); setLoading(true); try { await onLoad(id); // 外层 expand 状态变化导致 onExpand 更新 onExpandRef.current(id, true); } catch (err) { setError(true); onLoadError(id, err); } finally { setLoading(false); } } else { onExpand(id, true); } } if (loading) { return ( ); } let switcher = React.isValidElement(switcherIcon) ? ( switcherIcon ) : (
    {switcherIcon}
    ); if (error) { switcher = ; } return enable ? ( {switcher} ) : (
    ); }); TreeNodeSwitcher.displayName = "TreeNodeSwitcher";