Skip to content

FloatingTree

当嵌套浮动元素在 DOM 上不是彼此的子元素时,为它们提供上下文。

¥Provides context for nested floating elements when they are not children of each other on the DOM.

并非在所有情况下都需要这样做,除非父浮动元素和子浮动元素之间必须有显式通信。这是必要的:

¥This is not necessary in all cases, except when there must be explicit communication between parent and child floating elements. It is necessary for:

  • useDismiss() Hook 中的 bubbles 选项

    ¥The bubbles option in the useDismiss() Hook

  • 嵌套虚拟列表导航

    ¥Nested virtual list navigation

  • 嵌套的浮动元素在悬停时打开

    ¥Nested floating elements that each open on hover

  • 父子浮动元素之间的自定义通信

    ¥Custom communication between parent and child floating elements

用法

¥Usage

以下创建了一个无限可嵌套的 <Popover> 组件。该组件的使用必须封装在单个 <FloatingTree> 提供程序中:

¥The following creates an infinitely nestable <Popover> component. Usage of this component must be wrapped in a single <FloatingTree> provider:

import {
  FloatingTree,
  FloatingNode,
  useFloatingNodeId,
} from '@floating-ui/react';
 
function Popover({children, content}) {
  const [isOpen, setIsOpen] = useState(false);
 
  // Subscribe this component to the <FloatingTree> wrapper:
  const nodeId = useFloatingNodeId();
 
  // Pass the subscribed `nodeId` to `useFloating`:
  const {refs, floatingStyles} = useFloating({
    nodeId,
    open: isOpen,
    onOpenChange: setIsOpen,
  });
 
  // Wrap the rendered floating element in a `<FloatingNode>`,
  // passing in the subscribed `nodeId`:
  return (
    <>
      {cloneElement(children, {ref: refs.setReference})}
      <FloatingNode id={nodeId}>
        {isOpen && (
          <FloatingPortal>
            <div ref={refs.setFloating}>{content}</div>
          </FloatingPortal>
        )}
      </FloatingNode>
    </>
  );
}
 
function App() {
  return (
    <FloatingTree>
      <Popover
        content={
          <Popover content="Nested content">
            <button>Nested reference</button>
          </Popover>
        }
      >
        <button>Root reference</button>
      </Popover>
    </FloatingTree>
  );
}

钩子

¥Hooks

  • useFloatingNodeId() 将该组件订阅到树上下文。仅调用一次,因为它有副作用。

    ¥useFloatingNodeId() subscribes the component to the tree context. Call this only once as it has side effects.

  • useFloatingParentNodeId() 返回父 FloatingNode id(如果存在)。这对于根来说是 null(不嵌套)。

    ¥useFloatingParentNodeId() returns the parent FloatingNode id, if it exists. This will be null for roots (not nested).

  • useFloatingTree() 用于访问树对象,其中包括用于跨树组件进行通信的事件触发器 (events)。

    ¥useFloatingTree() for accessing the tree object, which includes an event emitter to communicate across the tree components (events).

interface FloatingTreeType {
  nodesRef: React.MutableRefObject<Array<FloatingNodeType>>;
  events: FloatingEvents;
  addNode(node: FloatingNodeType): void;
  removeNode(node: FloatingNodeType): void;
}

自定义父级

¥Custom parent

默认情况下,父节点是最近的 <FloatingNode />,但你可以通过传入 parentIduseFloatingNodeId() 来指定自定义父节点:

¥By default, the parent node is the closest <FloatingNode />, but you can specify a custom parent node by passing in the parentId to useFloatingNodeId():

const nodeId = useFloatingNodeId(parentId);

如果你想将 React 树中的同级浮动元素标记为另一个同级的子元素,这非常有用。

¥This is useful if you want to mark a sibling floating element in the React tree as the child of another sibling.

<FloatingTree> 封装器

¥<FloatingTree> wrapper

你可以使用以下技术来避免消费者指定 <FloatingTree> 封装器:

¥You can use the following technique to avoid having the consumer specify the <FloatingTree> wrapper:

function PopoverComponent() {
  // Main logic as seen earlier
}
 
// This is the component the consumer uses
export function Popover(props) {
  const parentId = useFloatingParentNodeId();
 
  // This is a root, so we wrap it with the tree
  if (parentId === null) {
    return (
      <FloatingTree>
        <PopoverComponent {...props} />
      </FloatingTree>
    );
  }
 
  return <PopoverComponent {...props} />;
}

故障排除

¥Troubleshooting

确保你调用 useFloatingNodeId() 的组件是 <FloatingTree> 封装器的子组件。如果不是,那么它将无法找到树上下文。

¥Ensure that the component that you’re calling useFloatingNodeId() is a child of the <FloatingTree> wrapper. If it is not, then it will not be able to find the tree context.